-
-
Notifications
You must be signed in to change notification settings - Fork 1.3k
Expand file tree
/
Copy pathportfolio.ts
More file actions
207 lines (175 loc) · 8.1 KB
/
Copy pathportfolio.ts
File metadata and controls
207 lines (175 loc) · 8.1 KB
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
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
import { tick } from "svelte";
import { SvelteMap } from "svelte/reactivity";
import { writable } from "svelte/store";
import type { Writable } from "svelte/store";
import type { SubscriptionsRouter } from "/src/subscriptions-router";
import { acceptStringFromFilters, downloadFile, downloadFileBlob, upload } from "/src/utility-functions/files";
import { rasterizeSVG } from "/src/utility-functions/rasterization";
import { patchLayout } from "/src/utility-functions/widgets";
import type { EditorWrapper, DocumentInfo, LayerPanelEntry, LayerStructureEntry, Layout, WorkspacePanelLayout } from "/wrapper/pkg/graphite_wasm_wrapper";
export type PortfolioStore = ReturnType<typeof createPortfolioStore>;
type PortfolioStoreState = {
unsaved: boolean;
documents: DocumentInfo[];
activeDocumentIndex: number;
panelLayout: WorkspacePanelLayout;
layerCache: SvelteMap<string, LayerPanelEntry>;
layerStructure: LayerStructureEntry[];
};
const initialState: PortfolioStoreState = {
unsaved: false,
documents: [],
activeDocumentIndex: 0,
panelLayout: {},
layerCache: new SvelteMap<string, LayerPanelEntry>(),
layerStructure: [],
};
let subscriptionsRouter: SubscriptionsRouter | undefined = undefined;
// Store state persisted across HMR to maintain reactive subscriptions in the component tree
const store: Writable<PortfolioStoreState> = import.meta.hot?.data?.store || writable<PortfolioStoreState>(initialState);
if (import.meta.hot) import.meta.hot.data.store = store;
const { subscribe, update } = store;
export const welcomeScreenButtonsLayout = makeLayoutStore("welcomeScreenButtonsLayout");
export const propertiesPanelLayout = makeLayoutStore("propertiesPanelLayout");
export const dataPanelLayout = makeLayoutStore("dataPanelLayout");
export const layersPanelControlBarLeftLayout = makeLayoutStore("layersPanelControlBarLeftLayout");
export const layersPanelControlBarRightLayout = makeLayoutStore("layersPanelControlBarRightLayout");
export const layersPanelBottomBarLayout = makeLayoutStore("layersPanelBottomBarLayout");
// Each panel layout has its own dedicated store so a layout update only re-renders that panel's consumers.
// Putting them at module scope (not inside the component) lets them survive a Svelte remount during a
// panel-tree restructure, since the backend's diff-based updates aren't re-sent on subscribe.
function makeLayoutStore(name: string): Writable<Layout> {
const persisted = import.meta.hot?.data?.[name];
const layoutStore: Writable<Layout> = persisted || writable<Layout>([]);
if (import.meta.hot) import.meta.hot.data[name] = layoutStore;
return layoutStore;
}
function patchLayoutStore(layoutStore: Writable<Layout>, data: Parameters<typeof patchLayout>[1]) {
layoutStore.update((layout) => {
patchLayout(layout, data);
return layout;
});
}
export function createPortfolioStore(subscriptions: SubscriptionsRouter, editor: EditorWrapper) {
destroyPortfolioStore();
subscriptionsRouter = subscriptions;
subscriptions.subscribeFrontendMessage("UpdateOpenDocumentsList", (data) => {
update((state) => {
state.documents = data.openDocuments;
if (state.documents.length === 0) state.activeDocumentIndex = 0;
return state;
});
});
subscriptions.subscribeFrontendMessage("UpdateActiveDocument", (data) => {
update((state) => {
// Assume we receive a correct document id
const activeId = state.documents.findIndex((doc) => doc.id === data.documentId);
state.activeDocumentIndex = activeId;
return state;
});
});
subscriptions.subscribeFrontendMessage("TriggerFetchAndOpenDocument", async (data) => {
try {
const url = new URL(`demo-artwork/${data.filename}`, document.location.href);
const response = await fetch(url);
editor.openFile(data.filename, await response.bytes());
} catch {
// Needs to be delayed until the end of the current call stack so the existing demo artwork dialog can be closed first, otherwise this dialog won't show
setTimeout(() => {
editor.errorDialog("Failed to open document", "The file could not be reached over the internet. You may be offline, or it may be missing.");
}, 0);
}
});
subscriptions.subscribeFrontendMessage("TriggerOpen", async ({ filters }) => {
const files = await upload(acceptStringFromFilters(filters), "data", true);
files.forEach((file) => editor.openFile(file.filename, file.content));
});
subscriptions.subscribeFrontendMessage("TriggerImport", async ({ filters }) => {
const data = await upload(acceptStringFromFilters(filters), "data");
editor.importFile(data.filename, data.content);
});
subscriptions.subscribeFrontendMessage("TriggerSaveDocument", (data) => {
downloadFile(data.name, data.content);
});
subscriptions.subscribeFrontendMessage("TriggerSaveFile", (data) => {
downloadFile(data.name, data.content);
});
subscriptions.subscribeFrontendMessage("TriggerExportImage", async (data) => {
const { svg, name, mime, size } = data;
// Fill the canvas with white if it'll be a JPEG (which does not support transparency and defaults to black)
const backgroundColor = mime.endsWith("jpeg") ? "white" : undefined;
// Rasterize the SVG to an image file
try {
const blob = await rasterizeSVG(svg, size[0], size[1], mime, backgroundColor);
// Have the browser download the file to the user's disk
downloadFileBlob(name, blob);
} catch {
// Fail silently if there's an error rasterizing the SVG, such as a zero-sized image
}
});
subscriptions.subscribeFrontendMessage("UpdateWorkspacePanelLayout", (data) => {
update((state) => {
state.panelLayout = data.panelLayout;
return state;
});
});
// Each panel layout uses its own store so updates only re-render that panel's consumers
subscriptions.subscribeLayoutUpdate("WelcomeScreenButtons", async (data) => {
await tick();
patchLayoutStore(welcomeScreenButtonsLayout, data);
});
subscriptions.subscribeLayoutUpdate("PropertiesPanel", async (data) => {
await tick();
patchLayoutStore(propertiesPanelLayout, data);
});
subscriptions.subscribeLayoutUpdate("DataPanel", async (data) => {
await tick();
patchLayoutStore(dataPanelLayout, data);
});
subscriptions.subscribeLayoutUpdate("LayersPanelControlLeftBar", async (data) => {
await tick();
patchLayoutStore(layersPanelControlBarLeftLayout, data);
});
subscriptions.subscribeLayoutUpdate("LayersPanelControlRightBar", async (data) => {
await tick();
patchLayoutStore(layersPanelControlBarRightLayout, data);
});
subscriptions.subscribeLayoutUpdate("LayersPanelBottomBar", async (data) => {
await tick();
patchLayoutStore(layersPanelBottomBarLayout, data);
});
subscriptions.subscribeFrontendMessage("UpdateDocumentLayerStructure", (data) => {
update((state) => {
state.layerStructure = data.layerStructure;
return state;
});
});
subscriptions.subscribeFrontendMessage("UpdateDocumentLayerDetails", (data) => {
update((state) => {
state.layerCache.set(String(data.data.id), data.data);
return state;
});
});
return { subscribe };
}
export function destroyPortfolioStore() {
const subscriptions = subscriptionsRouter;
if (!subscriptions) return;
subscriptions.unsubscribeFrontendMessage("UpdateOpenDocumentsList");
subscriptions.unsubscribeFrontendMessage("UpdateActiveDocument");
subscriptions.unsubscribeFrontendMessage("TriggerFetchAndOpenDocument");
subscriptions.unsubscribeFrontendMessage("TriggerOpen");
subscriptions.unsubscribeFrontendMessage("TriggerImport");
subscriptions.unsubscribeFrontendMessage("TriggerSaveDocument");
subscriptions.unsubscribeFrontendMessage("TriggerSaveFile");
subscriptions.unsubscribeFrontendMessage("TriggerExportImage");
subscriptions.unsubscribeFrontendMessage("UpdateWorkspacePanelLayout");
subscriptions.unsubscribeLayoutUpdate("WelcomeScreenButtons");
subscriptions.unsubscribeLayoutUpdate("PropertiesPanel");
subscriptions.unsubscribeLayoutUpdate("DataPanel");
subscriptions.unsubscribeLayoutUpdate("LayersPanelControlLeftBar");
subscriptions.unsubscribeLayoutUpdate("LayersPanelControlRightBar");
subscriptions.unsubscribeLayoutUpdate("LayersPanelBottomBar");
subscriptions.unsubscribeFrontendMessage("UpdateDocumentLayerStructure");
subscriptions.unsubscribeFrontendMessage("UpdateDocumentLayerDetails");
}