From 99da262c551531bf196e4d18e7d9bbfb35dec3b3 Mon Sep 17 00:00:00 2001 From: "Mr. Python" Date: Thu, 13 Nov 2025 16:57:44 +0800 Subject: [PATCH 1/3] Checkpoint from VS Code for coding agent session --- package-lock.json | 15 +++++++++++++++ package.json | 2 ++ 2 files changed, 17 insertions(+) diff --git a/package-lock.json b/package-lock.json index f2cdbd9..5e09126 100644 --- a/package-lock.json +++ b/package-lock.json @@ -28,6 +28,7 @@ "axios": "^1.12.2", "browser-update": "^3.3.63", "dayjs": "^1.11.18", + "dexie": "^4.2.1", "lodash": "^4.17.21", "lodash.debounce": "^4.0.8", "react": "^19.1.1", @@ -46,6 +47,7 @@ "devDependencies": { "@eslint/js": "^9.36.0", "@types/browser-update": "^3.3.3", + "@types/dexie": "^1.3.32", "@types/fontkit": "^2.0.8", "@types/lodash": "^4.17.20", "@types/lodash.debounce": "^4.0.9", @@ -2415,6 +2417,13 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/dexie": { + "version": "1.3.32", + "resolved": "https://registry.npmjs.org/@types/dexie/-/dexie-1.3.32.tgz", + "integrity": "sha512-8olJD9JLM45eVRB/Dt3tLMixWgvUr3ssyZscqhrjYf+TroGeKXtnjD9Y28PKGc4tv+PePF2xxEAj7vHFi0vlKw==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/estree": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", @@ -3750,6 +3759,12 @@ "url": "https://github.com/sponsors/wooorm" } }, + "node_modules/dexie": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/dexie/-/dexie-4.2.1.tgz", + "integrity": "sha512-Ckej0NS6jxQ4Po3OrSQBFddayRhTCic2DoCAG5zacOfOVB9P2Q5Xc5uL/nVa7ZVs+HdMnvUPzLFCB/JwpB6Csg==", + "license": "Apache-2.0" + }, "node_modules/dfa": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/dfa/-/dfa-1.2.0.tgz", diff --git a/package.json b/package.json index 77c4cb6..5c2466c 100644 --- a/package.json +++ b/package.json @@ -40,6 +40,7 @@ "axios": "^1.12.2", "browser-update": "^3.3.63", "dayjs": "^1.11.18", + "dexie": "^4.2.1", "lodash": "^4.17.21", "lodash.debounce": "^4.0.8", "react": "^19.1.1", @@ -58,6 +59,7 @@ "devDependencies": { "@eslint/js": "^9.36.0", "@types/browser-update": "^3.3.3", + "@types/dexie": "^1.3.32", "@types/fontkit": "^2.0.8", "@types/lodash": "^4.17.20", "@types/lodash.debounce": "^4.0.9", From d4fbbdd40bbd30fc1ceb5218fd2dbf99d84a011c Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 13 Nov 2025 09:03:25 +0000 Subject: [PATCH 2/3] Migrate IndexedDB to Dexie.js for cleaner API Co-authored-by: Mr-Python-in-China <89737170+Mr-Python-in-China@users.noreply.github.com> --- src/utils/indexedDBUtils.ts | 176 +++++++++--------------------------- 1 file changed, 42 insertions(+), 134 deletions(-) diff --git a/src/utils/indexedDBUtils.ts b/src/utils/indexedDBUtils.ts index f058faf..2b1405e 100644 --- a/src/utils/indexedDBUtils.ts +++ b/src/utils/indexedDBUtils.ts @@ -5,51 +5,40 @@ import type { } from "@/types/contestData"; import configSchema from "../../typst-template/config-schema.json"; import Ajv from "ajv"; - -const DB_NAME = "cnoi-statement-generator"; -const DB_VERSION = 1; -const CONFIG_STORE = "config"; -const IMAGE_STORE = "images"; +import Dexie, { type EntityTable } from "dexie"; const ajv = new Ajv({ allErrors: true }); const validateSchema = ajv.compile(configSchema); /** - * Open IndexedDB connection + * Dexie database schema */ -function openDB(): Promise { - return new Promise((resolve, reject) => { - const request = indexedDB.open(DB_NAME, DB_VERSION); - - request.onerror = () => reject(request.error); - request.onsuccess = () => resolve(request.result); - - request.onupgradeneeded = (event) => { - const db = (event.target as IDBOpenDBRequest).result; +interface ConfigStore { + key: string; + value: StoredContestData; +} - // Create config store - if (!db.objectStoreNames.contains(CONFIG_STORE)) { - db.createObjectStore(CONFIG_STORE); - } +class CnoiDatabase extends Dexie { + config!: EntityTable; + images!: EntityTable; - // Create image store with uuid as key - if (!db.objectStoreNames.contains(IMAGE_STORE)) { - db.createObjectStore(IMAGE_STORE, { keyPath: "uuid" }); - } - }; - }); + constructor() { + super("cnoi-statement-generator"); + this.version(1).stores({ + config: "key", + images: "uuid", + }); + } } +const db = new CnoiDatabase(); + /** * Save config to IndexedDB */ export async function saveConfigToDB( data: ContestDataWithImages, ): Promise { - const db = await openDB(); - const transaction = db.transaction(CONFIG_STORE, "readwrite"); - const store = transaction.objectStore(CONFIG_STORE); - // Remove url field from images for storage const storedData: StoredContestData = { ...data, @@ -59,17 +48,9 @@ export async function saveConfigToDB( })), }; - store.put(storedData, "current"); - - return new Promise((resolve, reject) => { - transaction.oncomplete = () => { - db.close(); - resolve(); - }; - transaction.onerror = () => { - db.close(); - reject(transaction.error); - }; + await db.config.put({ + key: "current", + value: storedData, }); } @@ -80,124 +61,51 @@ export async function loadConfigFromDB(): Promise<{ data: StoredContestData; images: Map; // uuid -> Blob } | null> { - const db = await openDB(); - const transaction = db.transaction([CONFIG_STORE, IMAGE_STORE], "readonly"); - const configStore = transaction.objectStore(CONFIG_STORE); - const imageStore = transaction.objectStore(IMAGE_STORE); + const configRecord = await db.config.get("current"); - return new Promise((resolve, reject) => { - const configRequest = configStore.get("current"); - - configRequest.onsuccess = async () => { - const storedData = configRequest.result as StoredContestData | undefined; - - if (!storedData) { - db.close(); - resolve(null); - return; - } + if (!configRecord) { + return null; + } - // Load all images - const imageMap = new Map(); - const imagePromises = (storedData.images || []).map( - (img) => - new Promise((resolveImg, rejectImg) => { - const imgRequest = imageStore.get(img.uuid); - imgRequest.onsuccess = () => { - const imageData = imgRequest.result as - | EditorImageData - | undefined; - if (imageData) { - imageMap.set(imageData.uuid, imageData.blob); - } - resolveImg(); - }; - imgRequest.onerror = () => rejectImg(imgRequest.error); - }), - ); + const storedData = configRecord.value; - try { - await Promise.all(imagePromises); - db.close(); + // Load all images + const imageMap = new Map(); + const imageUuids = (storedData.images || []).map((img) => img.uuid); - resolve({ data: storedData, images: imageMap }); - } catch (error) { - db.close(); - reject(error); + if (imageUuids.length > 0) { + const images = await db.images.bulkGet(imageUuids); + images.forEach((imageData) => { + if (imageData) { + imageMap.set(imageData.uuid, imageData.blob); } - }; + }); + } - configRequest.onerror = () => { - db.close(); - reject(configRequest.error); - }; - }); + return { data: storedData, images: imageMap }; } /** * Save image to IndexedDB */ export async function saveImageToDB(uuid: string, blob: Blob): Promise { - const db = await openDB(); - const transaction = db.transaction(IMAGE_STORE, "readwrite"); - const store = transaction.objectStore(IMAGE_STORE); - - const imageData: EditorImageData = { uuid, blob }; - store.put(imageData); - - return new Promise((resolve, reject) => { - transaction.oncomplete = () => { - db.close(); - resolve(); - }; - transaction.onerror = () => { - db.close(); - reject(transaction.error); - }; - }); + await db.images.put({ uuid, blob }); } /** * Delete image from IndexedDB */ export async function deleteImageFromDB(uuid: string): Promise { - const db = await openDB(); - const transaction = db.transaction(IMAGE_STORE, "readwrite"); - const store = transaction.objectStore(IMAGE_STORE); - - store.delete(uuid); - - return new Promise((resolve, reject) => { - transaction.oncomplete = () => { - db.close(); - resolve(); - }; - transaction.onerror = () => { - db.close(); - reject(transaction.error); - }; - }); + await db.images.delete(uuid); } /** * Clear all data from IndexedDB */ export async function clearDB(): Promise { - const db = await openDB(); - const transaction = db.transaction([CONFIG_STORE, IMAGE_STORE], "readwrite"); - - transaction.objectStore(CONFIG_STORE).clear(); - transaction.objectStore(IMAGE_STORE).clear(); - - return new Promise((resolve, reject) => { - transaction.oncomplete = () => { - db.close(); - resolve(); - }; - transaction.onerror = () => { - db.close(); - reject(transaction.error); - }; + await db.transaction("rw", [db.config, db.images], async () => { + await db.config.clear(); + await db.images.clear(); }); } From 523c3beecfe4da99812f4414d33b3532c7dd1293 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 13 Nov 2025 09:38:19 +0000 Subject: [PATCH 3/3] Fix data compatibility - preserve existing IndexedDB structure Co-authored-by: Mr-Python-in-China <89737170+Mr-Python-in-China@users.noreply.github.com> --- src/utils/indexedDBUtils.ts | 25 ++++++++----------------- 1 file changed, 8 insertions(+), 17 deletions(-) diff --git a/src/utils/indexedDBUtils.ts b/src/utils/indexedDBUtils.ts index 2b1405e..a2700ed 100644 --- a/src/utils/indexedDBUtils.ts +++ b/src/utils/indexedDBUtils.ts @@ -5,7 +5,7 @@ import type { } from "@/types/contestData"; import configSchema from "../../typst-template/config-schema.json"; import Ajv from "ajv"; -import Dexie, { type EntityTable } from "dexie"; +import Dexie from "dexie"; const ajv = new Ajv({ allErrors: true }); const validateSchema = ajv.compile(configSchema); @@ -13,19 +13,15 @@ const validateSchema = ajv.compile(configSchema); /** * Dexie database schema */ -interface ConfigStore { - key: string; - value: StoredContestData; -} - class CnoiDatabase extends Dexie { - config!: EntityTable; - images!: EntityTable; + // Using Table instead of EntityTable to support non-inlined keys + config!: Dexie.Table; + images!: Dexie.Table; constructor() { super("cnoi-statement-generator"); this.version(1).stores({ - config: "key", + config: "", // Empty string means the key is not part of the object (out-of-line key) images: "uuid", }); } @@ -48,10 +44,7 @@ export async function saveConfigToDB( })), }; - await db.config.put({ - key: "current", - value: storedData, - }); + await db.config.put(storedData, "current"); } /** @@ -61,14 +54,12 @@ export async function loadConfigFromDB(): Promise<{ data: StoredContestData; images: Map; // uuid -> Blob } | null> { - const configRecord = await db.config.get("current"); + const storedData = await db.config.get("current"); - if (!configRecord) { + if (!storedData) { return null; } - const storedData = configRecord.value; - // Load all images const imageMap = new Map(); const imageUuids = (storedData.images || []).map((img) => img.uuid);