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}