this repo has no description
1import { invoke } from "@tauri-apps/api/core";
2import { Node } from "../structs/node";
3import { Tab } from "../structs/Tab";
4import { createSignal } from "solid-js";
5import { listen } from "@tauri-apps/api/event";
6import { getVersion } from "@tauri-apps/api/app";
7import { NodesByID } from "../Nodes/Nodes";
8import { save } from "@tauri-apps/plugin-dialog";
9import { ConfirmationManager } from "./ConfirmationManager";
10import { getCurrentWindow } from "@tauri-apps/api/window";
11
12export interface TabHashMap {
13 [details: string] : Tab;
14}
15
16export class NodeManager{
17 public static Instance: NodeManager;
18
19 private _selectedTab: string | null = null;
20 private _tabs: TabHashMap = {};
21
22 private _nodes: Node[] = [];
23
24 constructor(){
25 NodeManager.Instance = this;
26
27 listen('load_new_tab', ( ev: any ) => {
28 this._loadFromConfig(ev.payload.path, null, ev.payload.graph);
29 });
30
31 invoke('load_previous_tabs').then(async ( tabs: any ) => {
32 let version = await getVersion();
33
34 for(let tab of Object.entries<any>(tabs)){
35 console.log(tab);
36
37 await this._loadFromConfig(tab[1][2], tab[0], JSON.stringify({
38 tab_name: tab[1][1],
39 version,
40 graph: tab[1][0]
41 }));
42 };
43 });
44
45 (async () => {
46 let window = await getCurrentWindow();
47
48 window.onCloseRequested(async _ => {
49 let tabs = Object.values(this._tabs);
50 let tabsNeedingSaving = tabs.filter(x => x.needsSave());
51
52 for(let tab of tabsNeedingSaving){
53 await new Promise<void>(res => {
54 ConfirmationManager.Instance.ShowConfirmation(
55 `Discard Changes in ${tab.name}?`,
56 'If you close this tab without saving you will lose all changes.',
57 [
58 {
59 text: 'Save',
60 callback: async () => {
61 await this.SaveTab(tab);
62 res();
63 }
64 },
65 {
66 text: 'Don\'t Save',
67 callback: () => { res(); }
68 }
69 ]
70 )
71 });
72 }
73 });
74 })();
75 }
76
77
78 private _tabUpdateHook: ( tabs: TabHashMap ) => void = () => {};
79 private _tabChangeHook: () => void = () => {};
80
81 public async AddTab( name: string, id: string | null = null ): Promise<Tab>{
82 let [ selected, setSelected ] = createSignal(false);
83 let [ needsSave, setNeedsSave ] = createSignal(false);
84
85 let tab: Tab = {
86 name: name,
87 id: id || await NodeManager.Instance.GetNewNodeId(),
88 nodes: [],
89 saveLocation: null,
90
91 selected,
92 setSelected,
93
94 needsSave,
95 setNeedsSave,
96
97 refuseSync: false
98 };
99
100 this._tabs[tab.id] = tab;
101
102 this.SelectTab(tab.id);
103 this._tabUpdateHook(this._tabs);
104
105 return tab;
106 }
107
108 public CloseTab( id: string ){
109 let tab = this._tabs[id];
110
111 let closeCB = () => {
112 if(this._selectedTab === id){
113 let tabs = Object.values(this._tabs);
114
115 if(tabs.length === 1){
116 this.SelectTab(null);
117 } else{
118 let index = tabs.indexOf(tab);
119 let nextTab = tabs[index + 1];
120
121 if(nextTab)
122 this.SelectTab(nextTab.id);
123 else
124 this.SelectTab(tabs[0].id);
125 }
126 }
127
128 invoke('discard_tab', { id: id });
129
130 delete this._tabs[id];
131 this._tabUpdateHook(this._tabs);
132 }
133
134 if(tab.needsSave()){
135 ConfirmationManager.Instance.ShowConfirmation(
136 'Discard Changes?',
137 'If you close this tab without saving you will lose all changes.',
138 [
139 {
140 text: 'Save',
141 callback: async () => {
142 await this.SaveTab(tab);
143 closeCB();
144 }
145 },
146 {
147 text: 'Don\'t Save',
148 callback: () => {
149 closeCB();
150 }
151 }
152 ]
153 )
154 } else{
155 closeCB();
156 }
157 }
158
159 public RenameTab( id: string, name: string ){
160 let tab = this._tabs[id];
161 if(!tab)return;
162
163 tab.name = name;
164 this._tabUpdateHook(this._tabs);
165 }
166
167 public SelectTab( id: string | null ){
168 if(this._selectedTab && this._tabs[this._selectedTab]){
169 let tab = this._tabs[this._selectedTab];
170
171 tab.setSelected(false);
172 tab.nodes = this._nodes;
173 }
174
175 this._selectedTab = id;
176 this._tabChangeHook();
177
178 if(this._selectedTab){
179 let tab = this._tabs[this._selectedTab];
180 if(!tab){
181 this._selectedTab = null;
182 return this._nodes = [];
183 }
184
185 tab.setSelected(true);
186 this._nodes = tab.nodes;
187 } else{
188 this._nodes = [];
189 }
190 }
191
192 public async SaveTab( tab: Tab ){
193 let path =
194 tab.saveLocation ||
195 await save({ defaultPath: tab.name + '.macro', filters: [ { name: 'Macro Files', extensions: [ 'macro' ] } ] });
196
197 if(!path)throw new Error("Cannot save");
198
199 tab.saveLocation = path;
200 tab.setNeedsSave(false);
201
202 this._saveConfigToDisk(path, tab.id);
203 }
204
205 public HookTabUpdate( cb: ( tabs: TabHashMap ) => void ){
206 this._tabUpdateHook = cb;
207 }
208
209 public HookTabChange( cb: () => void ){
210 this._tabChangeHook = cb;
211 }
212
213
214 public AddNode( node: Node ){
215 if(!this._selectedTab)return;
216
217 this._nodes.push(node);
218 this.UpdateConfig();
219 }
220
221 public RemoveNode( node: Node ){
222 if(!this._selectedTab)return;
223
224 this._nodes = this._nodes.filter(x => x !== node);
225 this.UpdateConfig();
226 }
227
228 public GetNodes(): Node[] | null{
229 if(this._selectedTab)
230 return this._nodes;
231 else
232 return null;
233 }
234
235 public async GetNewNodeId(){
236 let encoder = new TextEncoder();
237 let data = encoder.encode(Date.now().toString() + Math.random().toString());
238 let hash = await window.crypto.subtle.digest("SHA-256", data); // Probably should get a better ID implementation
239
240 return btoa(String.fromCharCode(...new Uint8Array(hash)));
241 }
242
243
244 public UpdateConfig( needsSave = true ){
245 if(!this._selectedTab)return;
246 let tab = this._tabs[this._selectedTab];
247 if(!tab)return;
248
249 if(tab.refuseSync)return;
250 invoke('sync_tab', { graph: this._generateTabGraph(tab.id)[0], id: tab.id, name: tab.name, location: tab.saveLocation });
251
252 if(needsSave)tab.setNeedsSave(true);
253 }
254
255 private async _loadFromConfig( path: string | null, id: string | null, config: string ){
256 let json = JSON.parse(config);
257
258 if(
259 !json.tab_name ||
260 !json.version ||
261 !json.graph
262 )return;
263
264 let tab = await this.AddTab(json.tab_name, id);
265 tab.refuseSync = true;
266 tab.saveLocation = path;
267
268 this._nodes = [];
269
270 let graph = json.graph;
271
272 // Populate nodes
273 for (let i = 0; i < graph.length; i++) {
274 let node = graph[i];
275
276 let nod = new Node(node.pos, NodesByID[node.typeId], node.id);
277
278 nod.statics = node.statics;
279 await nod.onStaticsUpdate(nod);
280
281 this._nodes.push(nod);
282 }
283
284 // Populate node inputs
285 for (let i = 0; i < graph.length; i++) {
286 let configNode = graph[i];
287 let outputParentNode = this._nodes[i];
288
289 for (let j = 0; j < configNode.outputs.length; j++) {
290 let output = configNode.outputs[j];
291
292 for (let k = 0; k < output.connections.length; k++) {
293 let input = output.connections[k];
294 let node = this._nodes.find(x => x.id === input.node)!;
295
296 let realInput = node.inputs.find(x => x.index === input.index);
297 let realOutput = outputParentNode.outputs[j];
298
299 if(realInput){
300 realInput.connections.push(realOutput);
301 realOutput.connections.push(realInput);
302 } else{
303 let realInput = {
304 name: input.name,
305 type: input.type,
306 parent: node,
307 connections: [ realOutput ],
308 index: input.index
309 };
310
311 node.inputs.push(realInput);
312 realOutput.connections.push(realInput);
313 }
314 }
315 }
316 }
317
318 tab.setNeedsSave(false);
319 tab.nodes = this._nodes;
320
321 tab.refuseSync = false;
322 this.UpdateConfig(false);
323 }
324
325 private _generateTabGraph( tabId: string | null ): [ any, Tab | null ]{
326 // Convert it into a structure we can actually save...
327
328 if(!tabId)return [ null, null ];
329 let tab = this._tabs[tabId];
330 if(!tab)return [ null, null ];
331
332 let nodesToSave = [];
333
334 for (let i = 0; i < tab.nodes.length; i++) {
335 let node = tab.nodes[i];
336
337 let nodeOutputs = [];
338
339 for (let j = 0; j < node.outputs.length; j++) {
340 let output = node.outputs[j];
341
342 nodeOutputs.push({
343 name: output.name,
344 type: output.type,
345 connections: output.connections.map(x => { return {
346 name: x.name,
347 node: x.parent.id,
348 index: x.index,
349 type: x.type
350 }})
351 })
352 }
353
354 nodesToSave.push({
355 name: node.name,
356 id: node.id,
357 typeId: node.typeId,
358 pos: [ node.x, node.y ],
359 outputs: nodeOutputs,
360 statics: node.statics
361 })
362 }
363
364 return [ nodesToSave, tab ];
365 }
366
367 private async _saveConfigToDisk( path: string, tabId: string | null ){
368 let [ nodesToSave, tab ] = this._generateTabGraph(tabId);
369 if(!tab)return;
370
371 invoke('save_graph', { graph: JSON.stringify({
372 tab_name: tab.name,
373 version: await getVersion(),
374 graph: nodesToSave
375 }), path });
376 }
377}