@@ -5,51 +5,36 @@ import type {
55} from "@/types/contestData" ;
66import configSchema from "../../typst-template/config-schema.json" ;
77import Ajv from "ajv" ;
8-
9- const DB_NAME = "cnoi-statement-generator" ;
10- const DB_VERSION = 1 ;
11- const CONFIG_STORE = "config" ;
12- const IMAGE_STORE = "images" ;
8+ import Dexie from "dexie" ;
139
1410const ajv = new Ajv ( { allErrors : true } ) ;
1511const validateSchema = ajv . compile ( configSchema ) ;
1612
1713/**
18- * Open IndexedDB connection
14+ * Dexie database schema
1915 */
20- function openDB ( ) : Promise < IDBDatabase > {
21- return new Promise ( ( resolve , reject ) => {
22- const request = indexedDB . open ( DB_NAME , DB_VERSION ) ;
23-
24- request . onerror = ( ) => reject ( request . error ) ;
25- request . onsuccess = ( ) => resolve ( request . result ) ;
26-
27- request . onupgradeneeded = ( event ) => {
28- const db = ( event . target as IDBOpenDBRequest ) . result ;
29-
30- // Create config store
31- if ( ! db . objectStoreNames . contains ( CONFIG_STORE ) ) {
32- db . createObjectStore ( CONFIG_STORE ) ;
33- }
34-
35- // Create image store with uuid as key
36- if ( ! db . objectStoreNames . contains ( IMAGE_STORE ) ) {
37- db . createObjectStore ( IMAGE_STORE , { keyPath : "uuid" } ) ;
38- }
39- } ;
40- } ) ;
16+ class CnoiDatabase extends Dexie {
17+ // Using Table instead of EntityTable to support non-inlined keys
18+ config ! : Dexie . Table < StoredContestData , string > ;
19+ images ! : Dexie . Table < EditorImageData , string > ;
20+
21+ constructor ( ) {
22+ super ( "cnoi-statement-generator" ) ;
23+ this . version ( 1 ) . stores ( {
24+ config : "" , // Empty string means the key is not part of the object (out-of-line key)
25+ images : "uuid" ,
26+ } ) ;
27+ }
4128}
4229
30+ const db = new CnoiDatabase ( ) ;
31+
4332/**
4433 * Save config to IndexedDB
4534 */
4635export async function saveConfigToDB (
4736 data : ContestDataWithImages ,
4837) : Promise < void > {
49- const db = await openDB ( ) ;
50- const transaction = db . transaction ( CONFIG_STORE , "readwrite" ) ;
51- const store = transaction . objectStore ( CONFIG_STORE ) ;
52-
5338 // Remove url field from images for storage
5439 const storedData : StoredContestData = {
5540 ...data ,
@@ -59,18 +44,7 @@ export async function saveConfigToDB(
5944 } ) ) ,
6045 } ;
6146
62- store . put ( storedData , "current" ) ;
63-
64- return new Promise ( ( resolve , reject ) => {
65- transaction . oncomplete = ( ) => {
66- db . close ( ) ;
67- resolve ( ) ;
68- } ;
69- transaction . onerror = ( ) => {
70- db . close ( ) ;
71- reject ( transaction . error ) ;
72- } ;
73- } ) ;
47+ await db . config . put ( storedData , "current" ) ;
7448}
7549
7650/**
@@ -80,124 +54,49 @@ export async function loadConfigFromDB(): Promise<{
8054 data : StoredContestData ;
8155 images : Map < string , Blob > ; // uuid -> Blob
8256} | null > {
83- const db = await openDB ( ) ;
84- const transaction = db . transaction ( [ CONFIG_STORE , IMAGE_STORE ] , "readonly" ) ;
85- const configStore = transaction . objectStore ( CONFIG_STORE ) ;
86- const imageStore = transaction . objectStore ( IMAGE_STORE ) ;
87-
88- return new Promise ( ( resolve , reject ) => {
89- const configRequest = configStore . get ( "current" ) ;
90-
91- configRequest . onsuccess = async ( ) => {
92- const storedData = configRequest . result as StoredContestData | undefined ;
93-
94- if ( ! storedData ) {
95- db . close ( ) ;
96- resolve ( null ) ;
97- return ;
98- }
57+ const storedData = await db . config . get ( "current" ) ;
9958
100- // Load all images
101- const imageMap = new Map < string , Blob > ( ) ;
102- const imagePromises = ( storedData . images || [ ] ) . map (
103- ( img ) =>
104- new Promise < void > ( ( resolveImg , rejectImg ) => {
105- const imgRequest = imageStore . get ( img . uuid ) ;
106- imgRequest . onsuccess = ( ) => {
107- const imageData = imgRequest . result as
108- | EditorImageData
109- | undefined ;
110- if ( imageData ) {
111- imageMap . set ( imageData . uuid , imageData . blob ) ;
112- }
113- resolveImg ( ) ;
114- } ;
115- imgRequest . onerror = ( ) => rejectImg ( imgRequest . error ) ;
116- } ) ,
117- ) ;
59+ if ( ! storedData ) {
60+ return null ;
61+ }
11862
119- try {
120- await Promise . all ( imagePromises ) ;
121- db . close ( ) ;
63+ // Load all images
64+ const imageMap = new Map < string , Blob > ( ) ;
65+ const imageUuids = ( storedData . images || [ ] ) . map ( ( img ) => img . uuid ) ;
12266
123- resolve ( { data : storedData , images : imageMap } ) ;
124- } catch ( error ) {
125- db . close ( ) ;
126- reject ( error ) ;
67+ if ( imageUuids . length > 0 ) {
68+ const images = await db . images . bulkGet ( imageUuids ) ;
69+ images . forEach ( ( imageData ) => {
70+ if ( imageData ) {
71+ imageMap . set ( imageData . uuid , imageData . blob ) ;
12772 }
128- } ;
73+ } ) ;
74+ }
12975
130- configRequest . onerror = ( ) => {
131- db . close ( ) ;
132- reject ( configRequest . error ) ;
133- } ;
134- } ) ;
76+ return { data : storedData , images : imageMap } ;
13577}
13678
13779/**
13880 * Save image to IndexedDB
13981 */
14082export async function saveImageToDB ( uuid : string , blob : Blob ) : Promise < void > {
141- const db = await openDB ( ) ;
142- const transaction = db . transaction ( IMAGE_STORE , "readwrite" ) ;
143- const store = transaction . objectStore ( IMAGE_STORE ) ;
144-
145- const imageData : EditorImageData = { uuid, blob } ;
146- store . put ( imageData ) ;
147-
148- return new Promise ( ( resolve , reject ) => {
149- transaction . oncomplete = ( ) => {
150- db . close ( ) ;
151- resolve ( ) ;
152- } ;
153- transaction . onerror = ( ) => {
154- db . close ( ) ;
155- reject ( transaction . error ) ;
156- } ;
157- } ) ;
83+ await db . images . put ( { uuid, blob } ) ;
15884}
15985
16086/**
16187 * Delete image from IndexedDB
16288 */
16389export async function deleteImageFromDB ( uuid : string ) : Promise < void > {
164- const db = await openDB ( ) ;
165- const transaction = db . transaction ( IMAGE_STORE , "readwrite" ) ;
166- const store = transaction . objectStore ( IMAGE_STORE ) ;
167-
168- store . delete ( uuid ) ;
169-
170- return new Promise ( ( resolve , reject ) => {
171- transaction . oncomplete = ( ) => {
172- db . close ( ) ;
173- resolve ( ) ;
174- } ;
175- transaction . onerror = ( ) => {
176- db . close ( ) ;
177- reject ( transaction . error ) ;
178- } ;
179- } ) ;
90+ await db . images . delete ( uuid ) ;
18091}
18192
18293/**
18394 * Clear all data from IndexedDB
18495 */
18596export async function clearDB ( ) : Promise < void > {
186- const db = await openDB ( ) ;
187- const transaction = db . transaction ( [ CONFIG_STORE , IMAGE_STORE ] , "readwrite" ) ;
188-
189- transaction . objectStore ( CONFIG_STORE ) . clear ( ) ;
190- transaction . objectStore ( IMAGE_STORE ) . clear ( ) ;
191-
192- return new Promise ( ( resolve , reject ) => {
193- transaction . oncomplete = ( ) => {
194- db . close ( ) ;
195- resolve ( ) ;
196- } ;
197- transaction . onerror = ( ) => {
198- db . close ( ) ;
199- reject ( transaction . error ) ;
200- } ;
97+ await db . transaction ( "rw" , [ db . config , db . images ] , async ( ) => {
98+ await db . config . clear ( ) ;
99+ await db . images . clear ( ) ;
201100 } ) ;
202101}
203102
0 commit comments