File size: 5,095 Bytes
4af09f9 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 | import { openDB, DBSchema, IDBPDatabase } from 'idb';
export interface FileNode {
id: string;
name: string;
type: 'file' | 'folder';
parentId: string | null;
content?: string; // Lexical JSON string or plain text
icon?: string;
color?: string;
orderIndex: number;
comments?: Record<string, string>; // Line-based comments: { "line-1": "comment text" }
}
export interface SettingsType {
typography: 'IBM Plex Mono' | 'ClaudeSans' | 'WixMadeForDisplay' | 'CourierNew';
theme: string;
highlighters: HighlighterRule[];
bgOpacity: number;
fgOpacity: number;
}
export interface HighlighterRule {
id: string;
openSymbol: string;
closeSymbol: string;
color: string;
baseOpacity: number; // 0.1 for 10%
stackMode: 'smaller-lighter' | 'larger-lighter';
}
export const DEFAULT_HIGHLIGHTERS: HighlighterRule[] = [
{ id: '1', openSymbol: '(', closeSymbol: ')', color: '#ffb3ba', baseOpacity: 0.6, stackMode: 'larger-lighter' },
{ id: '2', openSymbol: '[', closeSymbol: ']', color: '#baffc9', baseOpacity: 0.6, stackMode: 'larger-lighter' },
{ id: '3', openSymbol: '{', closeSymbol: '}', color: '#ffdfba', baseOpacity: 0.6, stackMode: 'larger-lighter' },
{ id: '4', openSymbol: '<', closeSymbol: '>', color: '#bae1ff', baseOpacity: 0.6, stackMode: 'larger-lighter' },
{ id: '5', openSymbol: "'", closeSymbol: "'", color: '#ffffba', baseOpacity: 0.6, stackMode: 'larger-lighter' },
{ id: '6', openSymbol: '"', closeSymbol: '"', color: '#e0bbff', baseOpacity: 0.6, stackMode: 'larger-lighter' },
{ id: '7', openSymbol: '`', closeSymbol: '`', color: '#c7f9cc', baseOpacity: 0.6, stackMode: 'larger-lighter' },
{ id: '8', openSymbol: '*', closeSymbol: '*', color: '#f9dcc4', baseOpacity: 0.6, stackMode: 'larger-lighter' },
{ id: '9', openSymbol: '**', closeSymbol: '**', color: '#f9bcc4', baseOpacity: 0.6, stackMode: 'larger-lighter' },
{ id: '10', openSymbol: '~~', closeSymbol: '~~', color: '#d9ed92', baseOpacity: 0.6, stackMode: 'larger-lighter' },
{ id: '11', openSymbol: '$$', closeSymbol: '$$', color: '#99d98c', baseOpacity: 0.6, stackMode: 'larger-lighter' },
];
export const DEFAULT_SETTINGS: SettingsType = {
typography: 'IBM Plex Mono',
theme: 'minimal-light',
highlighters: DEFAULT_HIGHLIGHTERS,
bgOpacity: 0.15,
fgOpacity: 1.0,
};
interface EditorDB extends DBSchema {
files: {
key: string;
value: FileNode;
indexes: { 'by-parent': string };
};
config: {
key: string;
value: any;
};
images: {
key: string;
value: { id: string; blob: Blob | string };
};
}
let dbPromise: Promise<IDBPDatabase<EditorDB>>;
export function initDB() {
if (!dbPromise) {
dbPromise = openDB<EditorDB>('HybridEditorDB', 1, {
upgrade(db) {
const fileStore = db.createObjectStore('files', { keyPath: 'id' });
fileStore.createIndex('by-parent', 'parentId');
db.createObjectStore('config');
db.createObjectStore('images', { keyPath: 'id' });
},
});
}
return dbPromise;
}
export async function getFiles(): Promise<FileNode[]> {
const db = await initDB();
return db.getAll('files');
}
export async function saveFile(file: FileNode): Promise<string> {
const db = await initDB();
await db.put('files', file);
return file.id;
}
export async function deleteFile(id: string): Promise<void> {
const db = await initDB();
await db.delete('files', id);
}
export async function getSettings(): Promise<SettingsType> {
const db = await initDB();
const settings = await db.get('config', 'settings');
return { ...DEFAULT_SETTINGS, ...(settings || {}) };
}
export async function saveSettings(settings: SettingsType): Promise<void> {
const db = await initDB();
await db.put('config', settings, 'settings');
}
export async function getBackgroundImage(): Promise<Blob | string | undefined> {
const db = await initDB();
const res = await db.get('images', 'background');
return res?.blob;
}
export async function saveBackgroundImage(blob: Blob | string): Promise<void> {
const db = await initDB();
await db.put('images', { id: 'background', blob });
}
export async function resetBackgroundImage(): Promise<void> {
const db = await initDB();
await db.delete('images', 'background');
}
/**
* EXPORT/IMPORT Logic for local JSON persistence
*/
export interface WorkspaceExport {
files: FileNode[];
settings: SettingsType;
lastUpdated: string;
}
export async function exportFullWorkspace(): Promise<WorkspaceExport> {
const files = await getFiles();
const settings = await getSettings();
return {
files,
settings,
lastUpdated: new Date().toISOString()
};
}
export async function importWorkspace(data: WorkspaceExport): Promise<void> {
const db = await initDB();
// Clear existing (optional, but safer for full recovery)
await db.clear('files');
const tx = db.transaction(['files', 'config'], 'readwrite');
for (const file of data.files) {
await tx.objectStore('files').put(file);
}
await tx.objectStore('config').put(data.settings, 'settings');
await tx.done;
}
|