Adds a shared, persisted auto-save preference for workspace text-file editing, defaulted to on, surfaced as a toolbar toggle in both the Files modal and right-dock Files view. - Add useAutoSavePreference hook: persists the fn-file-editor-auto-save localStorage preference, broadcasts same-window changes via a custom event (storage events only reach other documents), and defaults to true. - Extend useWorkspaceFileEditor with an autoSave flag that debounces (800ms) and triggers save() for a loaded, editable file with real pending changes, keyed by workspace+file+content to avoid re-firing on failed writes. - Add an Auto-save toggle button to FileEditor's toolbar (autoSaveEnabled/onToggleAutoSave/canToggleAutoSave props), hidden for read-only/preview/binary files. - Wire the shared preference into FileBrowserModal and DockFilesView, disabling auto-save for binary files in the modal. - Add fileEditor.autoSave / fileEditor.toggleAutoSave i18n strings and document the new default behavior in docs/dashboard-guide.md. - Add/extend tests covering the new hook, debounced auto-save behavior, and toolbar toggle wiring across FileEditor, FileBrowserModal, and DockFilesView. Files changed: docs/dashboard-guide.md | 3 + .../dashboard/app/components/DockFilesView.tsx | 6 +- .../dashboard/app/components/FileBrowserModal.tsx | 20 ++-- packages/dashboard/app/components/FileEditor.tsx | 17 +++- .../components/__tests__/DockFilesView.test.tsx | 37 ++++++- .../components/__tests__/FileBrowserModal.test.tsx | 86 +++++++++++++--- .../app/components/__tests__/FileEditor.test.tsx | 56 +++++++++++ .../hooks/__tests__/useAutoSavePreference.test.ts | 66 +++++++++++++ .../hooks/__tests__/useWorkspaceFileEditor.test.ts | 108 +++++++++++++++++++++ .../dashboard/app/hooks/useAutoSavePreference.ts | 79 +++++++++++++++ .../dashboard/app/hooks/useWorkspaceFileEditor.ts | 47 ++++++++- packages/i18n/locales/en/app.json | 2 + 12 files changed, 500 insertions(+), 27 deletions(-) Fusion-Task-Id: FN-7866 Fusion-Task-Lineage: 0604de18-666d-4872-abce-2a3886c9ea55 Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
80 lines
3.0 KiB
TypeScript
80 lines
3.0 KiB
TypeScript
import { useCallback, useEffect, useState } from "react";
|
|
|
|
export const FILE_EDITOR_AUTO_SAVE_STORAGE_KEY = "fn-file-editor-auto-save";
|
|
const FILE_EDITOR_AUTO_SAVE_CHANGED_EVENT = "fn:file-editor-auto-save-changed";
|
|
|
|
function readBooleanPref(key: string, defaultValue: boolean): boolean {
|
|
if (typeof window === "undefined") return defaultValue;
|
|
try {
|
|
const raw = window.localStorage.getItem(key);
|
|
if (raw === null) return defaultValue;
|
|
return raw === "true";
|
|
} catch {
|
|
return defaultValue;
|
|
}
|
|
}
|
|
|
|
function writeBooleanPref(key: string, value: boolean): void {
|
|
if (typeof window === "undefined") return;
|
|
try {
|
|
window.localStorage.setItem(key, value ? "true" : "false");
|
|
} catch {
|
|
// Ignore storage failures (quota, private mode, etc.).
|
|
}
|
|
}
|
|
|
|
export interface UseAutoSavePreferenceReturn {
|
|
autoSaveEnabled: boolean;
|
|
toggleAutoSave: () => void;
|
|
setAutoSaveEnabled: (value: boolean) => void;
|
|
}
|
|
|
|
/*
|
|
FNXC:FileEditor 2026-07-12-00:00:
|
|
Workspace file-editor auto-save defaults ON and is toggled from the shared toolbar. Persist one preference key for every workspace editor surface, and broadcast same-window changes because the native storage event only reaches other documents.
|
|
*/
|
|
export function useAutoSavePreference(): UseAutoSavePreferenceReturn {
|
|
const [autoSaveEnabled, setAutoSaveEnabledState] = useState(() => readBooleanPref(FILE_EDITOR_AUTO_SAVE_STORAGE_KEY, true));
|
|
|
|
const setAutoSaveEnabled = useCallback((value: boolean) => {
|
|
setAutoSaveEnabledState(value);
|
|
writeBooleanPref(FILE_EDITOR_AUTO_SAVE_STORAGE_KEY, value);
|
|
if (typeof window !== "undefined") {
|
|
window.dispatchEvent(new CustomEvent(FILE_EDITOR_AUTO_SAVE_CHANGED_EVENT, { detail: value }));
|
|
}
|
|
}, []);
|
|
|
|
const toggleAutoSave = useCallback(() => {
|
|
setAutoSaveEnabledState((current) => {
|
|
const next = !current;
|
|
writeBooleanPref(FILE_EDITOR_AUTO_SAVE_STORAGE_KEY, next);
|
|
if (typeof window !== "undefined") {
|
|
window.dispatchEvent(new CustomEvent(FILE_EDITOR_AUTO_SAVE_CHANGED_EVENT, { detail: next }));
|
|
}
|
|
return next;
|
|
});
|
|
}, []);
|
|
|
|
useEffect(() => {
|
|
if (typeof window === "undefined") return;
|
|
|
|
const syncFromStorage = (event: StorageEvent) => {
|
|
if (event.key !== FILE_EDITOR_AUTO_SAVE_STORAGE_KEY) return;
|
|
setAutoSaveEnabledState(readBooleanPref(FILE_EDITOR_AUTO_SAVE_STORAGE_KEY, true));
|
|
};
|
|
const syncFromLocalEvent = (event: Event) => {
|
|
const nextValue = (event as CustomEvent<boolean>).detail;
|
|
setAutoSaveEnabledState(typeof nextValue === "boolean" ? nextValue : readBooleanPref(FILE_EDITOR_AUTO_SAVE_STORAGE_KEY, true));
|
|
};
|
|
|
|
window.addEventListener("storage", syncFromStorage);
|
|
window.addEventListener(FILE_EDITOR_AUTO_SAVE_CHANGED_EVENT, syncFromLocalEvent);
|
|
return () => {
|
|
window.removeEventListener("storage", syncFromStorage);
|
|
window.removeEventListener(FILE_EDITOR_AUTO_SAVE_CHANGED_EVENT, syncFromLocalEvent);
|
|
};
|
|
}, []);
|
|
|
|
return { autoSaveEnabled, toggleAutoSave, setAutoSaveEnabled };
|
|
}
|