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