chore(save_indicator): basic infrastructure to display state

This commit is contained in:
Elian Doran 2026-01-02 22:44:29 +02:00
parent 345378d97f
commit e8d1fa7447
No known key found for this signature in database
4 changed files with 28 additions and 9 deletions

View File

@ -23,6 +23,8 @@ export interface SetNoteOpts {
export type GetTextEditorCallback = (editor: CKTextEditor) => void;
export type SaveState = "saved" | "saving" | "unsaved" | "error";
export interface NoteContextDataMap {
toc: HeadingContext;
pdfPages: {
@ -39,6 +41,9 @@ export interface NoteContextDataMap {
layers: Array<{ id: string; name: string; visible: boolean }>;
toggleLayer(layerId: string, visible: boolean): void;
};
saveState: {
state: SaveState;
}
}
type ContextDataKey = keyof NoteContextDataMap;

View File

@ -1,22 +1,29 @@
import type { SaveState } from "../components/note_context";
type Callback = () => Promise<void> | void;
export type StateCallback = (state: SaveState) => void;
export default class SpacedUpdate {
private updater: Callback;
private lastUpdated: number;
private changed: boolean;
private updateInterval: number;
private changeForbidden?: boolean;
private stateCallback?: StateCallback;
constructor(updater: Callback, updateInterval = 1000) {
constructor(updater: Callback, updateInterval = 1000, stateCallback?: StateCallback) {
this.updater = updater;
this.lastUpdated = Date.now();
this.changed = false;
this.updateInterval = updateInterval;
this.stateCallback = stateCallback;
}
scheduleUpdate() {
if (!this.changeForbidden) {
this.changed = true;
this.stateCallback?.("unsaved");
setTimeout(() => this.triggerUpdate());
}
}

View File

@ -7,7 +7,7 @@ import { t } from "../../services/i18n";
import { goToLinkExt } from "../../services/link";
import { Badge, BadgeWithDropdown } from "../react/Badge";
import { FormDropdownDivider, FormListItem } from "../react/FormList";
import { useIsNoteReadOnly, useNoteContext, useNoteLabel, useNoteLabelBoolean } from "../react/hooks";
import { useGetContextData, useIsNoteReadOnly, useNoteContext, useNoteLabel, useNoteLabelBoolean } from "../react/hooks";
import { useShareState } from "../ribbon/BasicPropertiesTab";
import { useShareInfo } from "../shared_info";
@ -110,12 +110,13 @@ function ExecuteBadge() {
}
function SaveStatusBadge() {
const state: "saved" | "saving" | "unsaved" | "error" = "error"; // TODO: implement save state tracking
const saveState = useGetContextData("saveState");
if (!saveState) return;
let icon: string;
let title: string;
let tooltip: string;
switch (state) {
switch (saveState?.state) {
case "saved":
icon = "bx bx-check";
title = t("breadcrumb_badges.save_status_saved");
@ -140,7 +141,7 @@ function SaveStatusBadge() {
return (
<Badge
className={clsx("save-status-badge", state)}
className={clsx("save-status-badge", saveState)}
icon={icon}
text={title}
tooltip={tooltip}

View File

@ -19,7 +19,7 @@ import options, { type OptionValue } from "../../services/options";
import protected_session_holder from "../../services/protected_session_holder";
import server from "../../services/server";
import shortcuts, { Handler, removeIndividualBinding } from "../../services/shortcuts";
import SpacedUpdate from "../../services/spaced_update";
import SpacedUpdate, { type StateCallback } from "../../services/spaced_update";
import toast, { ToastOptions } from "../../services/toast";
import tree from "../../services/tree";
import utils, { escapeRegExp, getErrorMessage, randomString, reloadFrontendApp } from "../../services/utils";
@ -63,11 +63,12 @@ export function useTriliumEvents<T extends EventNames>(eventNames: T[], handler:
useDebugValue(() => eventNames.join(", "));
}
export function useSpacedUpdate(callback: () => void | Promise<void>, interval = 1000) {
export function useSpacedUpdate(callback: () => void | Promise<void>, interval = 1000, stateCallback?: StateCallback) {
const callbackRef = useRef(callback);
const spacedUpdateRef = useRef<SpacedUpdate>(new SpacedUpdate(
() => callbackRef.current(),
interval
interval,
stateCallback
));
// Update callback ref when it changes
@ -121,7 +122,12 @@ export function useEditorSpacedUpdate({ note, noteType, noteContext, getData, on
dataSaved?.(data);
};
}, [ note, getData, dataSaved, noteType, parentComponent ]);
const spacedUpdate = useSpacedUpdate(callback);
const stateCallback = useCallback<StateCallback>((state) => {
noteContext?.setContextData("saveState", {
state
});
}, [ noteContext ]);
const spacedUpdate = useSpacedUpdate(callback, updateInterval, stateCallback);
// React to note/blob changes.
useEffect(() => {