@bpmnkit/plugins 0.0.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +153 -0
- package/dist/ai-bridge/css.d.ts +2 -0
- package/dist/ai-bridge/css.js +366 -0
- package/dist/ai-bridge/index.d.ts +44 -0
- package/dist/ai-bridge/index.js +132 -0
- package/dist/ai-bridge/panel.d.ts +27 -0
- package/dist/ai-bridge/panel.js +715 -0
- package/dist/ascii-view/css.d.ts +4 -0
- package/dist/ascii-view/css.js +83 -0
- package/dist/ascii-view/index.d.ts +25 -0
- package/dist/ascii-view/index.js +92 -0
- package/dist/command-palette/css.d.ts +4 -0
- package/dist/command-palette/css.js +157 -0
- package/dist/command-palette/index.d.ts +53 -0
- package/dist/command-palette/index.js +378 -0
- package/dist/command-palette-editor/index.d.ts +35 -0
- package/dist/command-palette-editor/index.js +60 -0
- package/dist/config-panel/css.d.ts +4 -0
- package/dist/config-panel/css.js +719 -0
- package/dist/config-panel/index.d.ts +36 -0
- package/dist/config-panel/index.js +73 -0
- package/dist/config-panel/renderer.d.ts +127 -0
- package/dist/config-panel/renderer.js +1162 -0
- package/dist/config-panel/types.d.ts +118 -0
- package/dist/config-panel/types.js +2 -0
- package/dist/config-panel-bpmn/index.d.ts +58 -0
- package/dist/config-panel-bpmn/index.js +1320 -0
- package/dist/config-panel-bpmn/template-engine.d.ts +17 -0
- package/dist/config-panel-bpmn/template-engine.js +307 -0
- package/dist/config-panel-bpmn/template-to-service-task.d.ts +30 -0
- package/dist/config-panel-bpmn/template-to-service-task.js +92 -0
- package/dist/config-panel-bpmn/template-types.d.ts +126 -0
- package/dist/config-panel-bpmn/template-types.js +6 -0
- package/dist/config-panel-bpmn/templates/generated.d.ts +3 -0
- package/dist/config-panel-bpmn/templates/generated.js +57865 -0
- package/dist/config-panel-bpmn/util.d.ts +46 -0
- package/dist/config-panel-bpmn/util.js +195 -0
- package/dist/dmn-editor/css.d.ts +3 -0
- package/dist/dmn-editor/css.js +667 -0
- package/dist/dmn-editor/dmn-editor.d.ts +34 -0
- package/dist/dmn-editor/dmn-editor.js +646 -0
- package/dist/dmn-editor/drd-canvas.d.ts +85 -0
- package/dist/dmn-editor/drd-canvas.js +1087 -0
- package/dist/dmn-editor/index.d.ts +3 -0
- package/dist/dmn-editor/index.js +2 -0
- package/dist/dmn-viewer/css.d.ts +3 -0
- package/dist/dmn-viewer/css.js +151 -0
- package/dist/dmn-viewer/dmn-viewer.d.ts +30 -0
- package/dist/dmn-viewer/dmn-viewer.js +160 -0
- package/dist/dmn-viewer/feel.d.ts +3 -0
- package/dist/dmn-viewer/feel.js +2 -0
- package/dist/dmn-viewer/index.d.ts +14 -0
- package/dist/dmn-viewer/index.js +12 -0
- package/dist/element-docs/content.d.ts +20 -0
- package/dist/element-docs/content.js +1032 -0
- package/dist/element-docs/index.d.ts +7 -0
- package/dist/element-docs/index.js +643 -0
- package/dist/feel-playground/css.d.ts +4 -0
- package/dist/feel-playground/css.js +292 -0
- package/dist/feel-playground/index.d.ts +4 -0
- package/dist/feel-playground/index.js +3 -0
- package/dist/feel-playground/playground-plugin.d.ts +14 -0
- package/dist/feel-playground/playground-plugin.js +292 -0
- package/dist/form-editor/css.d.ts +3 -0
- package/dist/form-editor/css.js +703 -0
- package/dist/form-editor/form-editor.d.ts +48 -0
- package/dist/form-editor/form-editor.js +1039 -0
- package/dist/form-editor/index.d.ts +3 -0
- package/dist/form-editor/index.js +2 -0
- package/dist/form-viewer/css.d.ts +3 -0
- package/dist/form-viewer/css.js +303 -0
- package/dist/form-viewer/form-viewer.d.ts +29 -0
- package/dist/form-viewer/form-viewer.js +376 -0
- package/dist/form-viewer/index.d.ts +12 -0
- package/dist/form-viewer/index.js +11 -0
- package/dist/history/checkpoint.d.ts +10 -0
- package/dist/history/checkpoint.js +92 -0
- package/dist/history/css.d.ts +2 -0
- package/dist/history/css.js +130 -0
- package/dist/history/history-panel.d.ts +12 -0
- package/dist/history/history-panel.js +142 -0
- package/dist/history/index.d.ts +5 -0
- package/dist/history/index.js +3 -0
- package/dist/main-menu/css.d.ts +4 -0
- package/dist/main-menu/css.js +272 -0
- package/dist/main-menu/index.d.ts +59 -0
- package/dist/main-menu/index.js +285 -0
- package/dist/minimap/css.d.ts +10 -0
- package/dist/minimap/css.js +54 -0
- package/dist/minimap/index.d.ts +38 -0
- package/dist/minimap/index.js +69 -0
- package/dist/minimap/minimap.d.ts +35 -0
- package/dist/minimap/minimap.js +166 -0
- package/dist/optimize/index.d.ts +21 -0
- package/dist/optimize/index.js +304 -0
- package/dist/process-runner/css.d.ts +2 -0
- package/dist/process-runner/css.js +467 -0
- package/dist/process-runner/index.d.ts +56 -0
- package/dist/process-runner/index.js +549 -0
- package/dist/storage/auto-save.d.ts +11 -0
- package/dist/storage/auto-save.js +44 -0
- package/dist/storage/css.d.ts +2 -0
- package/dist/storage/css.js +2 -0
- package/dist/storage/db.d.ts +99 -0
- package/dist/storage/db.js +116 -0
- package/dist/storage/dialog.d.ts +15 -0
- package/dist/storage/dialog.js +201 -0
- package/dist/storage/export.d.ts +3 -0
- package/dist/storage/export.js +137 -0
- package/dist/storage/index.d.ts +44 -0
- package/dist/storage/index.js +201 -0
- package/dist/storage/sidebar.d.ts +2 -0
- package/dist/storage/sidebar.js +2 -0
- package/dist/storage/storage-api.d.ts +76 -0
- package/dist/storage/storage-api.js +342 -0
- package/dist/storage/types.d.ts +44 -0
- package/dist/storage/types.js +2 -0
- package/dist/storage-tabs-bridge/index.d.ts +96 -0
- package/dist/storage-tabs-bridge/index.js +502 -0
- package/dist/tabs/css.d.ts +3 -0
- package/dist/tabs/css.js +642 -0
- package/dist/tabs/file-resolver.d.ts +71 -0
- package/dist/tabs/file-resolver.js +46 -0
- package/dist/tabs/index.d.ts +15 -0
- package/dist/tabs/index.js +13 -0
- package/dist/tabs/tabs-plugin.d.ts +250 -0
- package/dist/tabs/tabs-plugin.js +1119 -0
- package/dist/token-highlight/css.d.ts +2 -0
- package/dist/token-highlight/css.js +81 -0
- package/dist/token-highlight/index.d.ts +44 -0
- package/dist/token-highlight/index.js +164 -0
- package/dist/watermark/css.d.ts +10 -0
- package/dist/watermark/css.js +54 -0
- package/dist/watermark/index.d.ts +48 -0
- package/dist/watermark/index.js +64 -0
- package/dist/zoom-controls/css.d.ts +4 -0
- package/dist/zoom-controls/css.js +48 -0
- package/dist/zoom-controls/index.d.ts +36 -0
- package/dist/zoom-controls/index.js +111 -0
- package/package.json +121 -0
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
import { Bpmn } from "@bpmnkit/core";
|
|
2
|
+
import { showConfirmDialog, showInputDialog } from "./dialog.js";
|
|
3
|
+
import { exportProjectAsZip } from "./export.js";
|
|
4
|
+
import { StorageApi } from "./storage-api.js";
|
|
5
|
+
export { StorageApi } from "./storage-api.js";
|
|
6
|
+
export { showInputDialog, showConfirmDialog } from "./dialog.js";
|
|
7
|
+
/**
|
|
8
|
+
* Creates an IndexedDB-backed storage plugin.
|
|
9
|
+
*
|
|
10
|
+
* Integrates with the main menu plugin to provide workspace/project navigation
|
|
11
|
+
* via drill-down menus. Auto-saves BPMN changes with a 500 ms debounce.
|
|
12
|
+
* Persists the last-opened project across page refreshes.
|
|
13
|
+
*/
|
|
14
|
+
export function createStoragePlugin(options) {
|
|
15
|
+
const storageApi = new StorageApi(options);
|
|
16
|
+
let offDiagramChange;
|
|
17
|
+
let offChange;
|
|
18
|
+
function getProjectLabel(projectId) {
|
|
19
|
+
const workspaces = storageApi.getCachedWorkspaces();
|
|
20
|
+
for (const ws of workspaces) {
|
|
21
|
+
const projects = storageApi.getCachedProjects(ws.id);
|
|
22
|
+
const p = projects.find((pr) => pr.id === projectId);
|
|
23
|
+
if (p)
|
|
24
|
+
return `${ws.name} / ${p.name}`;
|
|
25
|
+
}
|
|
26
|
+
return "Project";
|
|
27
|
+
}
|
|
28
|
+
function buildWorkspaceDrillItems(onProjectClick) {
|
|
29
|
+
const workspaces = storageApi.getCachedWorkspaces();
|
|
30
|
+
if (workspaces.length === 0) {
|
|
31
|
+
return [
|
|
32
|
+
{
|
|
33
|
+
type: "info",
|
|
34
|
+
text: "No workspaces yet",
|
|
35
|
+
actionLabel: "Create",
|
|
36
|
+
onAction: () => {
|
|
37
|
+
void handleCreateWorkspace(onProjectClick);
|
|
38
|
+
},
|
|
39
|
+
},
|
|
40
|
+
];
|
|
41
|
+
}
|
|
42
|
+
const items = [];
|
|
43
|
+
for (const ws of workspaces) {
|
|
44
|
+
items.push({
|
|
45
|
+
type: "drill",
|
|
46
|
+
label: ws.name,
|
|
47
|
+
items: () => buildProjectDrillItems(ws, onProjectClick),
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
items.push({ type: "separator" });
|
|
51
|
+
items.push({
|
|
52
|
+
label: "New Workspace…",
|
|
53
|
+
onClick: () => void handleCreateWorkspace(onProjectClick),
|
|
54
|
+
});
|
|
55
|
+
return items;
|
|
56
|
+
}
|
|
57
|
+
function buildProjectDrillItems(ws, onProjectClick) {
|
|
58
|
+
const projects = storageApi.getCachedProjects(ws.id);
|
|
59
|
+
const items = [];
|
|
60
|
+
if (projects.length === 0) {
|
|
61
|
+
items.push({ type: "info", text: "No projects yet" });
|
|
62
|
+
}
|
|
63
|
+
for (const proj of projects) {
|
|
64
|
+
items.push({
|
|
65
|
+
label: proj.name,
|
|
66
|
+
onClick: () => onProjectClick(ws, proj),
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
items.push({ type: "separator" });
|
|
70
|
+
items.push({
|
|
71
|
+
label: "New Project…",
|
|
72
|
+
onClick: () => void handleCreateProject(ws, onProjectClick),
|
|
73
|
+
});
|
|
74
|
+
items.push({
|
|
75
|
+
label: "Rename Workspace…",
|
|
76
|
+
onClick: async () => {
|
|
77
|
+
const newName = await showInputDialog({ title: "Rename workspace", defaultValue: ws.name });
|
|
78
|
+
if (newName)
|
|
79
|
+
void storageApi.renameWorkspace(ws.id, newName);
|
|
80
|
+
},
|
|
81
|
+
});
|
|
82
|
+
items.push({
|
|
83
|
+
label: "Delete Workspace",
|
|
84
|
+
onClick: async () => {
|
|
85
|
+
const ok = await showConfirmDialog({
|
|
86
|
+
title: "Delete workspace",
|
|
87
|
+
message: `Delete workspace "${ws.name}" and all its data?`,
|
|
88
|
+
danger: true,
|
|
89
|
+
});
|
|
90
|
+
if (ok)
|
|
91
|
+
void storageApi.deleteWorkspace(ws.id);
|
|
92
|
+
},
|
|
93
|
+
});
|
|
94
|
+
return items;
|
|
95
|
+
}
|
|
96
|
+
async function handleCreateWorkspace(onProjectClick) {
|
|
97
|
+
const name = await showInputDialog({ title: "New workspace", placeholder: "Workspace name" });
|
|
98
|
+
if (!name)
|
|
99
|
+
return;
|
|
100
|
+
await storageApi.createWorkspace(name);
|
|
101
|
+
}
|
|
102
|
+
async function handleCreateProject(ws, onProjectClick) {
|
|
103
|
+
const name = await showInputDialog({ title: "New project", placeholder: "Project name" });
|
|
104
|
+
if (!name)
|
|
105
|
+
return;
|
|
106
|
+
await storageApi.createProject(ws.id, name);
|
|
107
|
+
}
|
|
108
|
+
function buildDynamicItems() {
|
|
109
|
+
const currentProjectId = storageApi.getCurrentProjectId();
|
|
110
|
+
const items = [];
|
|
111
|
+
if (currentProjectId) {
|
|
112
|
+
items.push({
|
|
113
|
+
type: "info",
|
|
114
|
+
text: getProjectLabel(currentProjectId),
|
|
115
|
+
actionLabel: "Leave",
|
|
116
|
+
onAction: () => {
|
|
117
|
+
storageApi.setCurrentProjectId(null);
|
|
118
|
+
options.mainMenu.api.setTitle(options.initialTitle ?? "");
|
|
119
|
+
options.onLeaveProject?.();
|
|
120
|
+
},
|
|
121
|
+
});
|
|
122
|
+
const currentFileId = storageApi.getCurrentFileId();
|
|
123
|
+
if (currentFileId && options.onRenameCurrentFile) {
|
|
124
|
+
const onRename = options.onRenameCurrentFile;
|
|
125
|
+
items.push({
|
|
126
|
+
label: "Rename current file\u2026",
|
|
127
|
+
onClick: async () => {
|
|
128
|
+
const newName = await showInputDialog({ title: "Rename file" });
|
|
129
|
+
if (!newName)
|
|
130
|
+
return;
|
|
131
|
+
await storageApi.renameFile(currentFileId, newName);
|
|
132
|
+
onRename(currentFileId, newName);
|
|
133
|
+
},
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
items.push({
|
|
137
|
+
label: "Export Project\u2026",
|
|
138
|
+
onClick: () => void exportProjectAsZip(storageApi),
|
|
139
|
+
});
|
|
140
|
+
items.push({ type: "separator" });
|
|
141
|
+
}
|
|
142
|
+
items.push({
|
|
143
|
+
type: "drill",
|
|
144
|
+
label: "Open Project",
|
|
145
|
+
items: () => buildWorkspaceDrillItems((ws, proj) => {
|
|
146
|
+
void storageApi.openProject(proj.id).then(() => {
|
|
147
|
+
options.mainMenu.api.setTitle(`${ws.name} / ${proj.name}`);
|
|
148
|
+
});
|
|
149
|
+
}),
|
|
150
|
+
});
|
|
151
|
+
items.push({
|
|
152
|
+
type: "drill",
|
|
153
|
+
label: "Save All to Project…",
|
|
154
|
+
items: () => buildWorkspaceDrillItems((ws, proj) => {
|
|
155
|
+
const tabs = options.getOpenTabs();
|
|
156
|
+
void storageApi.saveTabsToProject(proj.id, ws.id, tabs).then(() => {
|
|
157
|
+
options.mainMenu.api.setTitle(`${ws.name} / ${proj.name}`);
|
|
158
|
+
});
|
|
159
|
+
}),
|
|
160
|
+
});
|
|
161
|
+
items.push({ type: "separator" });
|
|
162
|
+
return items;
|
|
163
|
+
}
|
|
164
|
+
return {
|
|
165
|
+
name: "storage",
|
|
166
|
+
api: storageApi,
|
|
167
|
+
install(canvasApi) {
|
|
168
|
+
// Register dynamic items supplier
|
|
169
|
+
options.mainMenu.api.setDynamicItems(buildDynamicItems);
|
|
170
|
+
// Load caches so getCachedWorkspaces / getRecentProjects work immediately.
|
|
171
|
+
// Do NOT auto-restore the last project — always show the welcome screen.
|
|
172
|
+
void storageApi.initialize().then(() => {
|
|
173
|
+
storageApi.setCurrentProjectId(null);
|
|
174
|
+
options.onReady?.();
|
|
175
|
+
});
|
|
176
|
+
// Rebuild dynamic items next time menu opens when data changes
|
|
177
|
+
offChange = storageApi.onChange(() => {
|
|
178
|
+
options.mainMenu.api.setDynamicItems(buildDynamicItems);
|
|
179
|
+
});
|
|
180
|
+
// Subscribe to diagram:change and diagram:load for auto-save.
|
|
181
|
+
// diagram:load fires when XML is programmatically loaded (e.g. AI apply, history restore).
|
|
182
|
+
const anyOn = canvasApi.on.bind(canvasApi);
|
|
183
|
+
function onDiagramUpdate(rawDefs) {
|
|
184
|
+
const currentId = storageApi.getCurrentFileId();
|
|
185
|
+
if (!currentId)
|
|
186
|
+
return;
|
|
187
|
+
const defs = rawDefs;
|
|
188
|
+
const xml = Bpmn.export(defs);
|
|
189
|
+
storageApi.scheduleSave(currentId, xml);
|
|
190
|
+
}
|
|
191
|
+
offDiagramChange = anyOn("diagram:change", onDiagramUpdate);
|
|
192
|
+
anyOn("diagram:load", onDiagramUpdate);
|
|
193
|
+
},
|
|
194
|
+
uninstall() {
|
|
195
|
+
offDiagramChange?.();
|
|
196
|
+
offChange?.();
|
|
197
|
+
void storageApi.flush();
|
|
198
|
+
},
|
|
199
|
+
};
|
|
200
|
+
}
|
|
201
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import type { FileRecord, FileType, ProjectRecord, WorkspaceRecord } from "./types.js";
|
|
2
|
+
export interface StorageApiOptions {
|
|
3
|
+
/** Called when the user opens a file from the menu. */
|
|
4
|
+
onOpenFile(file: FileRecord, content: string): void;
|
|
5
|
+
}
|
|
6
|
+
export declare class StorageApi {
|
|
7
|
+
private readonly _options;
|
|
8
|
+
private _currentFileId;
|
|
9
|
+
private _currentProjectId;
|
|
10
|
+
private readonly _autoSave;
|
|
11
|
+
private _listeners;
|
|
12
|
+
private _workspaces;
|
|
13
|
+
private _projects;
|
|
14
|
+
constructor(_options: StorageApiOptions);
|
|
15
|
+
/** Load caches and restore last-opened project. Returns the project files if restoring. */
|
|
16
|
+
initialize(): Promise<FileRecord[] | null>;
|
|
17
|
+
getCurrentProjectId(): string | null;
|
|
18
|
+
setCurrentProjectId(id: string | null): void;
|
|
19
|
+
getCachedWorkspaces(): WorkspaceRecord[];
|
|
20
|
+
getCachedProjects(wsId: string): ProjectRecord[];
|
|
21
|
+
getProjectName(id: string): string | null;
|
|
22
|
+
/** Open a project: sets it as current and returns its files. */
|
|
23
|
+
openProject(id: string): Promise<FileRecord[]>;
|
|
24
|
+
/**
|
|
25
|
+
* Save the given open tabs as files in a project, creating new file records
|
|
26
|
+
* (or updating existing ones by matching name+type) and activating the project.
|
|
27
|
+
*/
|
|
28
|
+
saveTabsToProject(projectId: string, workspaceId: string, tabs: Array<{
|
|
29
|
+
tabId: string;
|
|
30
|
+
name: string;
|
|
31
|
+
type: FileType;
|
|
32
|
+
content: string;
|
|
33
|
+
}>): Promise<string[]>;
|
|
34
|
+
createWorkspace(name: string): Promise<WorkspaceRecord>;
|
|
35
|
+
getWorkspaces(): Promise<WorkspaceRecord[]>;
|
|
36
|
+
renameWorkspace(id: string, name: string): Promise<void>;
|
|
37
|
+
deleteWorkspace(id: string): Promise<void>;
|
|
38
|
+
createProject(workspaceId: string, name: string): Promise<ProjectRecord>;
|
|
39
|
+
getProjects(workspaceId: string): Promise<ProjectRecord[]>;
|
|
40
|
+
renameProject(id: string, name: string): Promise<void>;
|
|
41
|
+
deleteProject(id: string): Promise<void>;
|
|
42
|
+
private _deleteProjectData;
|
|
43
|
+
createFile(projectId: string, workspaceId: string, name: string, type: FileType, content: string): Promise<FileRecord>;
|
|
44
|
+
getFiles(projectId: string): Promise<FileRecord[]>;
|
|
45
|
+
getSharedFiles(): Promise<FileRecord[]>;
|
|
46
|
+
getFileContent(fileId: string): Promise<string | null>;
|
|
47
|
+
renameFile(id: string, name: string): Promise<void>;
|
|
48
|
+
setFileShared(id: string, isShared: boolean): Promise<void>;
|
|
49
|
+
deleteFile(id: string): Promise<void>;
|
|
50
|
+
openFile(fileId: string): Promise<void>;
|
|
51
|
+
getCurrentFileId(): string | null;
|
|
52
|
+
setCurrentFileId(id: string | null): void;
|
|
53
|
+
getCurrentContext(): {
|
|
54
|
+
projectId: string;
|
|
55
|
+
fileId: string;
|
|
56
|
+
} | null;
|
|
57
|
+
scheduleSave(fileId: string, content: string): void;
|
|
58
|
+
flush(): Promise<void>;
|
|
59
|
+
private _persistContent;
|
|
60
|
+
private _updateProjectCacheTimestamp;
|
|
61
|
+
/** Returns the MRU file-ID list for a project (most recent first). */
|
|
62
|
+
getMru(projectId: string): Promise<string[]>;
|
|
63
|
+
/** Pushes a file ID to the front of the project's MRU list (deduplicates, max 50). */
|
|
64
|
+
pushMruFile(projectId: string, fileId: string): Promise<void>;
|
|
65
|
+
/**
|
|
66
|
+
* Returns up to 10 projects sorted by most recently saved (descending).
|
|
67
|
+
* Uses the in-memory cache — call after `initialize()` completes.
|
|
68
|
+
*/
|
|
69
|
+
getRecentProjects(): Array<{
|
|
70
|
+
project: ProjectRecord;
|
|
71
|
+
workspace: WorkspaceRecord;
|
|
72
|
+
}>;
|
|
73
|
+
onChange(cb: () => void): () => void;
|
|
74
|
+
private _notify;
|
|
75
|
+
}
|
|
76
|
+
//# sourceMappingURL=storage-api.d.ts.map
|
|
@@ -0,0 +1,342 @@
|
|
|
1
|
+
import { AutoSave } from "./auto-save.js";
|
|
2
|
+
import { db } from "./db.js";
|
|
3
|
+
const CURRENT_PROJECT_KEY = "bpmn-sdk-current-project";
|
|
4
|
+
function now() {
|
|
5
|
+
return Date.now();
|
|
6
|
+
}
|
|
7
|
+
function newId() {
|
|
8
|
+
return crypto.randomUUID();
|
|
9
|
+
}
|
|
10
|
+
export class StorageApi {
|
|
11
|
+
_options;
|
|
12
|
+
_currentFileId = null;
|
|
13
|
+
_currentProjectId;
|
|
14
|
+
_autoSave;
|
|
15
|
+
_listeners = [];
|
|
16
|
+
// In-memory caches for synchronous menu building
|
|
17
|
+
_workspaces = [];
|
|
18
|
+
_projects = new Map();
|
|
19
|
+
constructor(_options) {
|
|
20
|
+
this._options = _options;
|
|
21
|
+
this._currentProjectId = localStorage.getItem(CURRENT_PROJECT_KEY);
|
|
22
|
+
this._autoSave = new AutoSave(async (fileId, content) => {
|
|
23
|
+
await this._persistContent(fileId, content);
|
|
24
|
+
this._notify();
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
/** Load caches and restore last-opened project. Returns the project files if restoring. */
|
|
28
|
+
async initialize() {
|
|
29
|
+
this._workspaces = await db.workspaces.orderBy("name").toArray();
|
|
30
|
+
for (const ws of this._workspaces) {
|
|
31
|
+
const projects = await db.projects.where("workspaceId").equals(ws.id).sortBy("name");
|
|
32
|
+
this._projects.set(ws.id, projects);
|
|
33
|
+
}
|
|
34
|
+
if (this._currentProjectId) {
|
|
35
|
+
const project = await db.projects.get(this._currentProjectId);
|
|
36
|
+
if (project) {
|
|
37
|
+
return db.files.where("projectId").equals(this._currentProjectId).sortBy("name");
|
|
38
|
+
}
|
|
39
|
+
// Project no longer exists — clear persisted id
|
|
40
|
+
this.setCurrentProjectId(null);
|
|
41
|
+
}
|
|
42
|
+
return null;
|
|
43
|
+
}
|
|
44
|
+
getCurrentProjectId() {
|
|
45
|
+
return this._currentProjectId;
|
|
46
|
+
}
|
|
47
|
+
setCurrentProjectId(id) {
|
|
48
|
+
this._currentProjectId = id;
|
|
49
|
+
if (id === null) {
|
|
50
|
+
localStorage.removeItem(CURRENT_PROJECT_KEY);
|
|
51
|
+
}
|
|
52
|
+
else {
|
|
53
|
+
localStorage.setItem(CURRENT_PROJECT_KEY, id);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
getCachedWorkspaces() {
|
|
57
|
+
return this._workspaces;
|
|
58
|
+
}
|
|
59
|
+
getCachedProjects(wsId) {
|
|
60
|
+
return this._projects.get(wsId) ?? [];
|
|
61
|
+
}
|
|
62
|
+
getProjectName(id) {
|
|
63
|
+
for (const projects of this._projects.values()) {
|
|
64
|
+
const p = projects.find((pr) => pr.id === id);
|
|
65
|
+
if (p)
|
|
66
|
+
return p.name;
|
|
67
|
+
}
|
|
68
|
+
return null;
|
|
69
|
+
}
|
|
70
|
+
/** Open a project: sets it as current and returns its files. */
|
|
71
|
+
async openProject(id) {
|
|
72
|
+
this.setCurrentProjectId(id);
|
|
73
|
+
const files = await db.files.where("projectId").equals(id).sortBy("name");
|
|
74
|
+
for (const file of files) {
|
|
75
|
+
const content = await this.getFileContent(file.id);
|
|
76
|
+
if (content !== null) {
|
|
77
|
+
this._options.onOpenFile(file, content);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
this._notify();
|
|
81
|
+
return files;
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Save the given open tabs as files in a project, creating new file records
|
|
85
|
+
* (or updating existing ones by matching name+type) and activating the project.
|
|
86
|
+
*/
|
|
87
|
+
async saveTabsToProject(projectId, workspaceId, tabs) {
|
|
88
|
+
const existingFiles = await db.files.where("projectId").equals(projectId).toArray();
|
|
89
|
+
const fileIdByKey = new Map();
|
|
90
|
+
for (const f of existingFiles) {
|
|
91
|
+
fileIdByKey.set(`${f.name}::${f.type}`, f.id);
|
|
92
|
+
}
|
|
93
|
+
const tabIds = [];
|
|
94
|
+
for (const tab of tabs) {
|
|
95
|
+
const key = `${tab.name}::${tab.type}`;
|
|
96
|
+
const existingId = fileIdByKey.get(key);
|
|
97
|
+
if (existingId) {
|
|
98
|
+
await this._persistContent(existingId, tab.content);
|
|
99
|
+
await db.files.update(existingId, { updatedAt: now() });
|
|
100
|
+
tabIds.push(existingId);
|
|
101
|
+
}
|
|
102
|
+
else {
|
|
103
|
+
const file = await this.createFile(projectId, workspaceId, tab.name, tab.type, tab.content);
|
|
104
|
+
tabIds.push(file.id);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
this.setCurrentProjectId(projectId);
|
|
108
|
+
this._notify();
|
|
109
|
+
return tabIds;
|
|
110
|
+
}
|
|
111
|
+
// ─── Workspaces ──────────────────────────────────────────────────────────────
|
|
112
|
+
async createWorkspace(name) {
|
|
113
|
+
const ws = { id: newId(), name, createdAt: now(), updatedAt: now() };
|
|
114
|
+
await db.workspaces.add(ws);
|
|
115
|
+
this._workspaces = [...this._workspaces, ws].sort((a, b) => a.name.localeCompare(b.name));
|
|
116
|
+
this._notify();
|
|
117
|
+
return ws;
|
|
118
|
+
}
|
|
119
|
+
async getWorkspaces() {
|
|
120
|
+
return db.workspaces.orderBy("name").toArray();
|
|
121
|
+
}
|
|
122
|
+
async renameWorkspace(id, name) {
|
|
123
|
+
await db.workspaces.update(id, { name, updatedAt: now() });
|
|
124
|
+
this._workspaces = this._workspaces.map((w) => (w.id === id ? { ...w, name } : w));
|
|
125
|
+
this._workspaces.sort((a, b) => a.name.localeCompare(b.name));
|
|
126
|
+
this._notify();
|
|
127
|
+
}
|
|
128
|
+
async deleteWorkspace(id) {
|
|
129
|
+
const projects = await db.projects.where("workspaceId").equals(id).toArray();
|
|
130
|
+
for (const p of projects)
|
|
131
|
+
await this._deleteProjectData(p.id);
|
|
132
|
+
await db.workspaces.delete(id);
|
|
133
|
+
this._workspaces = this._workspaces.filter((w) => w.id !== id);
|
|
134
|
+
this._projects.delete(id);
|
|
135
|
+
if (this._currentFileId !== null) {
|
|
136
|
+
const f = await db.files.get(this._currentFileId);
|
|
137
|
+
if (!f || f.workspaceId === id)
|
|
138
|
+
this._currentFileId = null;
|
|
139
|
+
}
|
|
140
|
+
if (this._currentProjectId) {
|
|
141
|
+
const p = await db.projects.get(this._currentProjectId);
|
|
142
|
+
if (!p || p.workspaceId === id)
|
|
143
|
+
this.setCurrentProjectId(null);
|
|
144
|
+
}
|
|
145
|
+
this._notify();
|
|
146
|
+
}
|
|
147
|
+
// ─── Projects ────────────────────────────────────────────────────────────────
|
|
148
|
+
async createProject(workspaceId, name) {
|
|
149
|
+
const p = { id: newId(), workspaceId, name, createdAt: now(), updatedAt: now() };
|
|
150
|
+
await db.projects.add(p);
|
|
151
|
+
const existing = this._projects.get(workspaceId) ?? [];
|
|
152
|
+
const updated = [...existing, p].sort((a, b) => a.name.localeCompare(b.name));
|
|
153
|
+
this._projects.set(workspaceId, updated);
|
|
154
|
+
this._notify();
|
|
155
|
+
return p;
|
|
156
|
+
}
|
|
157
|
+
async getProjects(workspaceId) {
|
|
158
|
+
return db.projects.where("workspaceId").equals(workspaceId).sortBy("name");
|
|
159
|
+
}
|
|
160
|
+
async renameProject(id, name) {
|
|
161
|
+
await db.projects.update(id, { name, updatedAt: now() });
|
|
162
|
+
for (const [wsId, projects] of this._projects) {
|
|
163
|
+
const idx = projects.findIndex((p) => p.id === id);
|
|
164
|
+
if (idx !== -1) {
|
|
165
|
+
const updated = projects.map((p) => (p.id === id ? { ...p, name } : p));
|
|
166
|
+
updated.sort((a, b) => a.name.localeCompare(b.name));
|
|
167
|
+
this._projects.set(wsId, updated);
|
|
168
|
+
break;
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
this._notify();
|
|
172
|
+
}
|
|
173
|
+
async deleteProject(id) {
|
|
174
|
+
await this._deleteProjectData(id);
|
|
175
|
+
if (this._currentProjectId === id)
|
|
176
|
+
this.setCurrentProjectId(null);
|
|
177
|
+
this._notify();
|
|
178
|
+
}
|
|
179
|
+
async _deleteProjectData(projectId) {
|
|
180
|
+
const files = await db.files.where("projectId").equals(projectId).toArray();
|
|
181
|
+
for (const f of files) {
|
|
182
|
+
await db.fileContents.delete(f.id);
|
|
183
|
+
if (this._currentFileId === f.id)
|
|
184
|
+
this._currentFileId = null;
|
|
185
|
+
}
|
|
186
|
+
await db.files.where("projectId").equals(projectId).delete();
|
|
187
|
+
await db.projects.delete(projectId);
|
|
188
|
+
// Update cache
|
|
189
|
+
for (const [wsId, projects] of this._projects) {
|
|
190
|
+
if (projects.some((p) => p.id === projectId)) {
|
|
191
|
+
this._projects.set(wsId, projects.filter((p) => p.id !== projectId));
|
|
192
|
+
break;
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
// ─── Files ───────────────────────────────────────────────────────────────────
|
|
197
|
+
async createFile(projectId, workspaceId, name, type, content) {
|
|
198
|
+
const file = {
|
|
199
|
+
id: newId(),
|
|
200
|
+
projectId,
|
|
201
|
+
workspaceId,
|
|
202
|
+
name,
|
|
203
|
+
type,
|
|
204
|
+
isShared: false,
|
|
205
|
+
gitPath: null,
|
|
206
|
+
createdAt: now(),
|
|
207
|
+
updatedAt: now(),
|
|
208
|
+
};
|
|
209
|
+
const fc = { fileId: file.id, content, version: 1 };
|
|
210
|
+
await db.files.add(file);
|
|
211
|
+
await db.fileContents.add(fc);
|
|
212
|
+
this._notify();
|
|
213
|
+
return file;
|
|
214
|
+
}
|
|
215
|
+
async getFiles(projectId) {
|
|
216
|
+
return db.files.where("projectId").equals(projectId).sortBy("name");
|
|
217
|
+
}
|
|
218
|
+
async getSharedFiles() {
|
|
219
|
+
return db.files.filter((f) => f.isShared).toArray();
|
|
220
|
+
}
|
|
221
|
+
async getFileContent(fileId) {
|
|
222
|
+
const fc = await db.fileContents.get(fileId);
|
|
223
|
+
return fc?.content ?? null;
|
|
224
|
+
}
|
|
225
|
+
async renameFile(id, name) {
|
|
226
|
+
await db.files.update(id, { name, updatedAt: now() });
|
|
227
|
+
this._notify();
|
|
228
|
+
}
|
|
229
|
+
async setFileShared(id, isShared) {
|
|
230
|
+
await db.files.update(id, { isShared, updatedAt: now() });
|
|
231
|
+
this._notify();
|
|
232
|
+
}
|
|
233
|
+
async deleteFile(id) {
|
|
234
|
+
await db.fileContents.delete(id);
|
|
235
|
+
await db.files.delete(id);
|
|
236
|
+
if (this._currentFileId === id)
|
|
237
|
+
this._currentFileId = null;
|
|
238
|
+
this._notify();
|
|
239
|
+
}
|
|
240
|
+
// ─── Navigation ──────────────────────────────────────────────────────────────
|
|
241
|
+
async openFile(fileId) {
|
|
242
|
+
const file = await db.files.get(fileId);
|
|
243
|
+
if (!file)
|
|
244
|
+
return;
|
|
245
|
+
const content = await this.getFileContent(fileId);
|
|
246
|
+
if (content === null)
|
|
247
|
+
return;
|
|
248
|
+
this._currentFileId = fileId;
|
|
249
|
+
this._options.onOpenFile(file, content);
|
|
250
|
+
this._notify();
|
|
251
|
+
}
|
|
252
|
+
getCurrentFileId() {
|
|
253
|
+
return this._currentFileId;
|
|
254
|
+
}
|
|
255
|
+
setCurrentFileId(id) {
|
|
256
|
+
this._currentFileId = id;
|
|
257
|
+
}
|
|
258
|
+
getCurrentContext() {
|
|
259
|
+
if (this._currentProjectId === null || this._currentFileId === null)
|
|
260
|
+
return null;
|
|
261
|
+
return { projectId: this._currentProjectId, fileId: this._currentFileId };
|
|
262
|
+
}
|
|
263
|
+
// ─── Auto-save ───────────────────────────────────────────────────────────────
|
|
264
|
+
scheduleSave(fileId, content) {
|
|
265
|
+
this._autoSave.schedule(fileId, content);
|
|
266
|
+
}
|
|
267
|
+
async flush() {
|
|
268
|
+
await this._autoSave.flush();
|
|
269
|
+
}
|
|
270
|
+
async _persistContent(fileId, content) {
|
|
271
|
+
const existing = await db.fileContents.get(fileId);
|
|
272
|
+
if (existing) {
|
|
273
|
+
await db.fileContents.update(fileId, { content, version: existing.version + 1 });
|
|
274
|
+
}
|
|
275
|
+
else {
|
|
276
|
+
await db.fileContents.add({ fileId, content, version: 1 });
|
|
277
|
+
}
|
|
278
|
+
const ts = now();
|
|
279
|
+
await db.files.update(fileId, { updatedAt: ts });
|
|
280
|
+
// Bump project's updatedAt so it sorts correctly in getRecentProjects
|
|
281
|
+
if (this._currentProjectId !== null) {
|
|
282
|
+
await db.projects.update(this._currentProjectId, { updatedAt: ts });
|
|
283
|
+
this._updateProjectCacheTimestamp(this._currentProjectId, ts);
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
_updateProjectCacheTimestamp(projectId, ts) {
|
|
287
|
+
for (const [wsId, projects] of this._projects) {
|
|
288
|
+
const idx = projects.findIndex((p) => p.id === projectId);
|
|
289
|
+
if (idx === -1)
|
|
290
|
+
continue;
|
|
291
|
+
this._projects.set(wsId, projects.map((p) => (p.id === projectId ? { ...p, updatedAt: ts } : p)));
|
|
292
|
+
break;
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
// ─── MRU (most-recently-used files per project) ───────────────────────────────
|
|
296
|
+
/** Returns the MRU file-ID list for a project (most recent first). */
|
|
297
|
+
async getMru(projectId) {
|
|
298
|
+
const record = await db.projectMru.get(projectId);
|
|
299
|
+
return record?.fileIds ?? [];
|
|
300
|
+
}
|
|
301
|
+
/** Pushes a file ID to the front of the project's MRU list (deduplicates, max 50). */
|
|
302
|
+
async pushMruFile(projectId, fileId) {
|
|
303
|
+
const existing = await db.projectMru.get(projectId);
|
|
304
|
+
const current = existing?.fileIds ?? [];
|
|
305
|
+
const updated = [fileId, ...current.filter((id) => id !== fileId)].slice(0, 50);
|
|
306
|
+
if (existing) {
|
|
307
|
+
await db.projectMru.update(projectId, { fileIds: updated });
|
|
308
|
+
}
|
|
309
|
+
else {
|
|
310
|
+
await db.projectMru.add({ projectId, fileIds: updated });
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
// ─── Recent projects ─────────────────────────────────────────────────────────
|
|
314
|
+
/**
|
|
315
|
+
* Returns up to 10 projects sorted by most recently saved (descending).
|
|
316
|
+
* Uses the in-memory cache — call after `initialize()` completes.
|
|
317
|
+
*/
|
|
318
|
+
getRecentProjects() {
|
|
319
|
+
const result = [];
|
|
320
|
+
for (const [wsId, projects] of this._projects) {
|
|
321
|
+
const ws = this._workspaces.find((w) => w.id === wsId);
|
|
322
|
+
if (!ws)
|
|
323
|
+
continue;
|
|
324
|
+
for (const p of projects) {
|
|
325
|
+
result.push({ project: p, workspace: ws });
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
return result.sort((a, b) => b.project.updatedAt - a.project.updatedAt).slice(0, 10);
|
|
329
|
+
}
|
|
330
|
+
// ─── Change listeners ─────────────────────────────────────────────────────────
|
|
331
|
+
onChange(cb) {
|
|
332
|
+
this._listeners.push(cb);
|
|
333
|
+
return () => {
|
|
334
|
+
this._listeners = this._listeners.filter((l) => l !== cb);
|
|
335
|
+
};
|
|
336
|
+
}
|
|
337
|
+
_notify() {
|
|
338
|
+
for (const l of this._listeners)
|
|
339
|
+
l();
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
//# sourceMappingURL=storage-api.js.map
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
export type FileType = "bpmn" | "dmn" | "form";
|
|
2
|
+
export interface WorkspaceRecord {
|
|
3
|
+
id: string;
|
|
4
|
+
name: string;
|
|
5
|
+
createdAt: number;
|
|
6
|
+
updatedAt: number;
|
|
7
|
+
}
|
|
8
|
+
export interface ProjectRecord {
|
|
9
|
+
id: string;
|
|
10
|
+
workspaceId: string;
|
|
11
|
+
name: string;
|
|
12
|
+
createdAt: number;
|
|
13
|
+
updatedAt: number;
|
|
14
|
+
}
|
|
15
|
+
export interface FileRecord {
|
|
16
|
+
id: string;
|
|
17
|
+
projectId: string;
|
|
18
|
+
/** Denormalized from project — avoids extra joins in workspace-level queries. */
|
|
19
|
+
workspaceId: string;
|
|
20
|
+
name: string;
|
|
21
|
+
type: FileType;
|
|
22
|
+
/** When true this file is accessible cross-workspace for reference resolution. */
|
|
23
|
+
isShared: boolean;
|
|
24
|
+
/** Reserved for future GitHub sync — maps to `workspace/project/file.ext`. */
|
|
25
|
+
gitPath: string | null;
|
|
26
|
+
createdAt: number;
|
|
27
|
+
updatedAt: number;
|
|
28
|
+
}
|
|
29
|
+
export interface FileContentRecord {
|
|
30
|
+
/** Same as FileRecord.id — used as the primary key. */
|
|
31
|
+
fileId: string;
|
|
32
|
+
/** BPMN XML, DMN XML, or Form JSON string. */
|
|
33
|
+
content: string;
|
|
34
|
+
/** Monotonically increasing; incremented on every save. */
|
|
35
|
+
version: number;
|
|
36
|
+
}
|
|
37
|
+
/** Per-project most-recently-used file order for Ctrl+Tab switching. */
|
|
38
|
+
export interface ProjectMruRecord {
|
|
39
|
+
/** Primary key — matches ProjectRecord.id. */
|
|
40
|
+
projectId: string;
|
|
41
|
+
/** File IDs ordered most-recently-used first (index 0 = most recent). */
|
|
42
|
+
fileIds: string[];
|
|
43
|
+
}
|
|
44
|
+
//# sourceMappingURL=types.d.ts.map
|