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