Skip to content

Commit 86d2a36

Browse files
committed
ui: migrate scratchpad drafts to IndexedDB
1 parent c25c1d5 commit 86d2a36

3 files changed

Lines changed: 57 additions & 21 deletions

File tree

Lines changed: 5 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,9 @@
1-
let cacheKey = `${UserContext._id}/${UiContext.pdoc.domainId}/${UiContext.pdoc.docId}`;
2-
if (UiContext.tdoc?._id) cacheKey += `@${UiContext.tdoc._id}`;
3-
4-
// TODO switch to indexeddb
51
export default function reducer(state = {
6-
lang: localStorage.getItem(`${cacheKey}#lang`) || UiContext.codeLang,
7-
code: localStorage.getItem(cacheKey) || UiContext.codeTemplate,
2+
lang: UiContext.codeLang,
3+
code: UiContext.codeTemplate,
84
}, action: any = {}) {
9-
if (action.type === 'SCRATCHPAD_EDITOR_UPDATE_CODE') {
10-
localStorage.setItem(cacheKey, action.payload);
11-
return {
12-
...state,
13-
code: action.payload,
14-
};
15-
}
16-
if (action.type === 'SCRATCHPAD_EDITOR_SET_LANG') {
17-
localStorage.setItem(`${cacheKey}#lang`, action.payload);
18-
return {
19-
...state,
20-
lang: action.payload,
21-
};
22-
}
5+
if (action.type === 'SCRATCHPAD_EDITOR_HYDRATE') return action.payload;
6+
if (action.type === 'SCRATCHPAD_EDITOR_UPDATE_CODE') return { ...state, code: action.payload };
7+
if (action.type === 'SCRATCHPAD_EDITOR_SET_LANG') return { ...state, lang: action.payload };
238
return state;
249
}

packages/ui-default/pages/problem_detail.page.tsx

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import $ from 'jquery';
22
import * as yaml from 'js-yaml';
3+
import _ from 'lodash';
34
import React from 'react';
45
import { createRoot } from 'react-dom/client';
56
import { confirm } from 'vj/components/dialog';
@@ -144,6 +145,34 @@ const page = new NamedPage(['problem_detail', 'contest_detail_problem', 'homewor
144145
const { default: ScratchpadApp } = await import('../components/scratchpad');
145146
const { default: ScratchpadReducer } = await import('../components/scratchpad/reducers');
146147
const { Provider, store } = await loadReactRedux(ScratchpadReducer);
148+
let cacheKey = `${UserContext._id}/${UiContext.pdoc.domainId}/${UiContext.pdoc.docId}`;
149+
if (UiContext.tdoc?._id) cacheKey += `@${UiContext.tdoc._id}`;
150+
try {
151+
const draft = await (await openDB).get('scratchpad-drafts', cacheKey);
152+
if (draft) {
153+
store.dispatch({
154+
type: 'SCRATCHPAD_EDITOR_HYDRATE',
155+
payload: {
156+
code: draft.code ?? UiContext.codeTemplate,
157+
lang: draft.lang ?? UiContext.codeLang,
158+
},
159+
});
160+
}
161+
} catch { }
162+
163+
let previousEditorState = store.getState().editor;
164+
let write = Promise.resolve();
165+
const saveDraft = _.debounce((code: string, lang: string) => {
166+
write = write
167+
.then(() => openDB.then((db) => db.put('scratchpad-drafts', { id: cacheKey, code, lang })))
168+
.catch(() => { });
169+
}, 400);
170+
store.subscribe(() => {
171+
const editor = store.getState().editor;
172+
if (editor === previousEditorState) return;
173+
previousEditorState = editor;
174+
saveDraft(editor.code, editor.lang);
175+
});
147176

148177
// @ts-ignore
149178
window.store = store;

packages/ui-default/utils/db.ts

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
import { openDB as _open } from 'idb';
22
import { alert } from 'vj/components/dialog';
33

4-
export const openDB = _open('hydro', 2, {
4+
const scratchpadCleanup: string[] = [];
5+
6+
export const openDB = _open('hydro', 3, {
57
upgrade(db, oldVersion) {
68
if (oldVersion < 1) {
79
const solutionStore = db.createObjectStore('solutions', { keyPath: 'id' });
@@ -18,6 +20,23 @@ export const openDB = _open('hydro', 2, {
1820
}
1921
}
2022
if (oldVersion < 2) db.createObjectStore('domain-info', { keyPath: 'id' });
23+
if (oldVersion < 3) {
24+
const scratchpadStore = db.createObjectStore('scratchpad-drafts', { keyPath: 'id' });
25+
const drafts = new Map<string, { id: string, code?: string, lang?: string }>();
26+
const scratchpadKey = /^(\d+\/[A-Za-z]\w{3,31}\/\d+(?:@[\dA-Fa-f]{24})?)(#lang)?$/;
27+
for (const key of Object.keys(localStorage)) {
28+
const match = key.match(scratchpadKey);
29+
if (!match) continue;
30+
const value = localStorage.getItem(key);
31+
if (value === null) continue;
32+
const draft = drafts.get(match[1]) || { id: match[1] };
33+
if (match[2]) draft.lang = value;
34+
else draft.code = value;
35+
drafts.set(draft.id, draft);
36+
scratchpadCleanup.push(key);
37+
}
38+
for (const draft of drafts.values()) scratchpadStore.put(draft);
39+
}
2140
},
2241
blocked(currentVersion, blockedVersion) {
2342
console.error('IDB Blocked by version', blockedVersion, 'want', currentVersion);
@@ -30,4 +49,7 @@ export const openDB = _open('hydro', 2, {
3049
terminated() {
3150
console.error('IDB Terminated');
3251
},
52+
}).then((db) => {
53+
for (const key of scratchpadCleanup) localStorage.removeItem(key);
54+
return db;
3355
});

0 commit comments

Comments
 (0)