From 3de0ad3f4944c65c13eab3b364a38c9d606304b5 Mon Sep 17 00:00:00 2001 From: Ted Vu Date: Sat, 8 Apr 2023 04:14:33 +0700 Subject: [PATCH 1/2] feat: restore unsaved content --- apps/web/src/editor/input/create.ts | 37 +++++++++++++------ apps/web/src/editor/input/input.tsx | 8 +++++ apps/web/src/editor/input/load.ts | 38 ++++++++++++++++++++ apps/web/src/editor/input/store.ts | 55 +++++++++++++++++++++++++++++ 4 files changed, 127 insertions(+), 11 deletions(-) create mode 100644 apps/web/src/editor/input/load.ts create mode 100644 apps/web/src/editor/input/store.ts diff --git a/apps/web/src/editor/input/create.ts b/apps/web/src/editor/input/create.ts index c120855..10e7378 100644 --- a/apps/web/src/editor/input/create.ts +++ b/apps/web/src/editor/input/create.ts @@ -2,6 +2,9 @@ import * as monaco from "monaco-editor"; import { RefObject, useEffect } from "react"; import { SAMPLE_TAILWIND } from "../../samples/tailwind"; import { EditorState } from "../type"; +import { StoreInterval } from "./input"; +import { loadChangesFromIndexedDB } from "./load"; +import { autoSaveWorkerURL, debounce, saveChangesToIndexedDB } from "./store"; const envDone = { current: false }; @@ -76,17 +79,29 @@ export const useEditorCreate = (params: Params): void => { const container = containerRef.current; if (container === null) throw Error("`container` is null"); - createEnv(); - const editor = monaco.editor.create(container, { - ...OPTIONS, - value: SAMPLE_TAILWIND, - // value: "", - }); - setEditor(editor); + (async () => { + const storedEditorValue = await loadChangesFromIndexedDB(); - return () => { - setEditor(null); - editor.dispose(); - }; + createEnv(); + const editor = monaco.editor.create(container, { + ...OPTIONS, + value: storedEditorValue || SAMPLE_TAILWIND, + // value: "", + }); + + const autoSave = debounce(() => { + const editorValue: string = editor.getValue(); + saveChangesToIndexedDB(editorValue); + }, StoreInterval); + + editor.onDidChangeModelContent(autoSave); + + setEditor(editor); + + return () => { + setEditor(null); + editor.dispose(); + }; + })(); }, [setEditor, containerRef]); }; diff --git a/apps/web/src/editor/input/input.tsx b/apps/web/src/editor/input/input.tsx index ddaae39..c5b8d65 100644 --- a/apps/web/src/editor/input/input.tsx +++ b/apps/web/src/editor/input/input.tsx @@ -11,6 +11,14 @@ interface Props extends EditorState { settings: Settings; } +export enum EditorStoreName { + Database = "monacoEditorDB", + Object = "unsavedChanges", + Key = "data", +} + +export const StoreInterval = 2000; + export const EditorInput = (props: Props): JSX.Element => { const { setEditor, editor, settings } = props; diff --git a/apps/web/src/editor/input/load.ts b/apps/web/src/editor/input/load.ts new file mode 100644 index 0000000..1cf377a --- /dev/null +++ b/apps/web/src/editor/input/load.ts @@ -0,0 +1,38 @@ +import { EditorStoreName } from "./input"; + +export const loadChangesFromIndexedDB = async (): Promise => { + return new Promise((resolve, reject) => { + const dbPromise = window.indexedDB.open(EditorStoreName.Database, 1); + + dbPromise.onupgradeneeded = (event: IDBVersionChangeEvent) => { + const db = (event.target as IDBOpenDBRequest).result as IDBDatabase; + db.createObjectStore(EditorStoreName.Object, { autoIncrement: true }); + }; + + dbPromise.onsuccess = (event: Event) => { + const db = (event.target as IDBOpenDBRequest).result as IDBDatabase; + const transaction: IDBTransaction = db.transaction( + EditorStoreName.Object, + "readonly" + ); + const store: IDBObjectStore = transaction.objectStore( + EditorStoreName.Object + ); + + const request = store.get(EditorStoreName.Key); + + request.onsuccess = () => { + // only get the last changes + resolve(request.result); + }; + + request.onerror = () => { + reject(request.error); + }; + }; + + dbPromise.onerror = (event: Event) => { + reject((event.target as IDBOpenDBRequest).error); + }; + }); +}; diff --git a/apps/web/src/editor/input/store.ts b/apps/web/src/editor/input/store.ts new file mode 100644 index 0000000..d958113 --- /dev/null +++ b/apps/web/src/editor/input/store.ts @@ -0,0 +1,55 @@ +import { EditorStoreName } from "./input"; + +export const debounce = void>( + fn: T, + delay: number +) => { + let timeoutId: ReturnType | null = null; + + return ((...args: Parameters) => { + if (timeoutId !== null) clearTimeout(timeoutId); + + timeoutId = setTimeout(() => { + fn(...args); + timeoutId = null; + }, delay); + }) as T; +}; + +export const saveChangesToIndexedDB = async ( + changes: string +): Promise => { + return new Promise((resolve, reject) => { + const dbPromise = window.indexedDB.open(EditorStoreName.Database, 1); + + dbPromise.onupgradeneeded = (event: IDBVersionChangeEvent) => { + const db = (event.target as IDBOpenDBRequest).result as IDBDatabase; + db.createObjectStore(EditorStoreName.Object); + }; + + dbPromise.onsuccess = (event: Event) => { + const db = (event.target as IDBOpenDBRequest).result as IDBDatabase; + const transaction: IDBTransaction = db.transaction( + EditorStoreName.Object, + "readwrite" + ); + const store: IDBObjectStore = transaction.objectStore( + EditorStoreName.Object + ); + + const request = store.put(changes, EditorStoreName.Key); + + request.onsuccess = () => { + resolve(); + }; + + request.onerror = () => { + reject(request.error); + }; + }; + + dbPromise.onerror = (event: Event) => { + reject((event.target as IDBOpenDBRequest).error); + }; + }); +}; From 5b38163f340562619f1f1c3084848446bcdc8240 Mon Sep 17 00:00:00 2001 From: Ted Vu Date: Sat, 8 Apr 2023 04:20:06 +0700 Subject: [PATCH 2/2] feat: restore unsaved content --- apps/web/src/editor/input/load.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/apps/web/src/editor/input/load.ts b/apps/web/src/editor/input/load.ts index 1cf377a..94a2690 100644 --- a/apps/web/src/editor/input/load.ts +++ b/apps/web/src/editor/input/load.ts @@ -22,7 +22,6 @@ export const loadChangesFromIndexedDB = async (): Promise => { const request = store.get(EditorStoreName.Key); request.onsuccess = () => { - // only get the last changes resolve(request.result); };