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}