2
0
Lutz Roeder 1 жил өмнө
parent
commit
f406e545c7
3 өөрчлөгдсөн 63 нэмэгдсэн , 70 устгасан
  1. 5 5
      source/browser.js
  2. 4 9
      source/electron.mjs
  3. 54 56
      source/view.js

+ 5 - 5
source/browser.js

@@ -231,11 +231,11 @@ host.BrowserHost = class {
         return new this.window.Worker(`${id}.js`, { type: 'module' });
     }
 
-    save(name, extension, defaultPath, callback) {
-        callback(`${defaultPath}.${extension}`);
+    async save(name, extension, defaultPath) {
+        return `${defaultPath}.${extension}`;
     }
 
-    export(file, blob) {
+    async export(file, blob) {
         const element = this.document.createElement('a');
         element.download = file;
         element.href = URL.createObjectURL(blob);
@@ -458,12 +458,12 @@ host.BrowserHost = class {
             const text = await this._request(url, { 'Content-Type': 'application/json' }, 'utf-8');
             const json = JSON.parse(text);
             if (json.message) {
-                this.error('Error while loading Gist.', json.message);
+                await this.error('Error while loading Gist.', json.message);
                 return;
             }
             const file = Object.values(json.files).find((file) => this._view.accept(file.filename));
             if (!file) {
-                this.error('Error while loading Gist.', 'Gist does not contain a model file.');
+                await this.error('Error while loading Gist.', 'Gist does not contain a model file.');
                 return;
             }
             const identifier = file.filename;

+ 4 - 9
source/electron.mjs

@@ -253,37 +253,32 @@ host.ElectronHost = class {
         return new this.window.Worker(`${id}.js`, { type: 'module' });
     }
 
-    save(name, extension, defaultPath, callback) {
-        const selectedFile = electron.ipcRenderer.sendSync('show-save-dialog', {
+    async save(name, extension, defaultPath) {
+        return electron.ipcRenderer.sendSync('show-save-dialog', {
             title: 'Export Tensor',
             defaultPath,
             buttonLabel: 'Export',
             filters: [{ name, extensions: [extension] }]
         });
-        if (selectedFile) {
-            callback(selectedFile);
-        }
     }
 
     async export(file, blob) {
         const reader = new FileReader();
         reader.onload = (e) => {
             const data = new Uint8Array(e.target.result);
-            fs.writeFile(file, data, null, (err) => {
+            fs.writeFile(file, data, null, async (err) => {
                 if (err) {
                     this.exception(err, false);
-                    this.error('Error writing file.', err.message);
+                    await this.error('Error writing file.', err.message);
                 }
             });
         };
-
         let err = null;
         if (!blob) {
             err = new Error(`Export blob is '${JSON.stringify(blob)}'.`);
         } else if (!(blob instanceof Blob)) {
             err = new Error(`Export blob type is '${typeof blob}'.`);
         }
-
         if (err) {
             this.exception(err, false);
             await this.error('Error exporting image.', err.message);

+ 54 - 56
source/view.js

@@ -103,13 +103,13 @@ view.View = class {
                     file.add({
                         label: 'Export as &PNG',
                         accelerator: 'CmdOrCtrl+Shift+E',
-                        execute: () => this.export(`${this._host.document.title}.png`),
+                        execute: async () => await this.export(`${this._host.document.title}.png`),
                         enabled: () => this.activeGraph
                     });
                     file.add({
                         label: 'Export as &SVG',
                         accelerator: 'CmdOrCtrl+Alt+E',
-                        execute: () => this.export(`${this._host.document.title}.svg`),
+                        execute: async () => await this.export(`${this._host.document.title}.svg`),
                         enabled: () => this.activeGraph
                     });
                 }
@@ -655,9 +655,7 @@ view.View = class {
             }
             return await this._updateGraph(model, stack);
         } catch (error) {
-            if (error && context.identifier) {
-                error.context = context.identifier;
-            }
+            error.context = !error.context && context && context.identifier ? context.identifier : error.context || '';
             throw error;
         }
     }
@@ -902,7 +900,7 @@ view.View = class {
         }
     }
 
-    export(file) {
+    async export(file) {
         const lastIndex = file.lastIndexOf('.');
         const extension = lastIndex === -1 ? 'png' : file.substring(lastIndex + 1).toLowerCase();
         if (this.activeGraph && (extension === 'png' || extension === 'svg')) {
@@ -922,7 +920,6 @@ view.View = class {
             origin.setAttribute('transform', 'translate(0,0) scale(1)');
             background.removeAttribute('width');
             background.removeAttribute('height');
-
             const parent = canvas.parentElement;
             parent.insertBefore(clone, canvas);
             const size = clone.getBBox();
@@ -938,37 +935,43 @@ view.View = class {
             background.setAttribute('width', width);
             background.setAttribute('height', height);
             background.setAttribute('fill', '#fff');
-
             const data = new XMLSerializer().serializeToString(clone);
-
             if (extension === 'svg') {
                 const blob = new Blob([data], { type: 'image/svg' });
-                this._host.export(file, blob);
+                await this._host.export(file, blob);
             }
-
             if (extension === 'png') {
-                const image = new Image();
-                image.onload = () => {
-                    const max = Math.max(width, height);
-                    const scale = Math.min(24000.0 / max, 2.0);
-                    const canvas = this._host.document.createElement('canvas');
-                    canvas.width = Math.ceil(width * scale);
-                    canvas.height = Math.ceil(height * scale);
-                    const context = canvas.getContext('2d');
-                    context.scale(scale, scale);
-                    context.drawImage(image, 0, 0);
-                    canvas.toBlob((blob) => {
-                        if (blob) {
-                            this._host.export(file, blob);
-                        } else {
-                            const error = new Error('Image may be too large to render as PNG.');
-                            error.name = 'Error exporting image.';
-                            this._host.exception(error, false);
-                            this._host.error(error.name, error.message);
-                        }
-                    }, 'image/png');
-                };
-                image.src = `data:image/svg+xml;base64,${this._host.window.btoa(unescape(encodeURIComponent(data)))}`;
+                try {
+                    const blob = await new Promise((resolve, reject) => {
+                        const image = new Image();
+                        image.onload = async () => {
+                            const max = Math.max(width, height);
+                            const scale = Math.min(24000.0 / max, 2.0);
+                            const canvas = this._host.document.createElement('canvas');
+                            canvas.width = Math.ceil(width * scale);
+                            canvas.height = Math.ceil(height * scale);
+                            const context = canvas.getContext('2d');
+                            context.scale(scale, scale);
+                            context.drawImage(image, 0, 0);
+                            canvas.toBlob((blob) => {
+                                if (blob) {
+                                    resolve(blob);
+                                } else {
+                                    const error = new Error('Image may be too large to render as PNG.');
+                                    error.name = 'Error exporting image.';
+                                    reject(error);
+                                }
+                            }, 'image/png');
+                        };
+                        image.onerror = (error) => {
+                            reject(error);
+                        };
+                        image.src = `data:image/svg+xml;base64,${this._host.window.btoa(unescape(encodeURIComponent(data)))}`;
+                    });
+                    await this._host.export(file, blob);
+                } catch (error) {
+                    await this.error(error);
+                }
             }
         }
     }
@@ -994,9 +997,7 @@ view.View = class {
                 const content = modelSidebar.render();
                 this._sidebar.open(content, 'Model Properties');
             } catch (error) {
-                if (error) {
-                    error.context = this._model.identifier;
-                }
+                this._context(error);
                 this.error(error, 'Error showing model properties.', null);
             }
         }
@@ -1012,14 +1013,13 @@ view.View = class {
                 nodeSidebar.on('show-documentation', async (/* sender, e */) => {
                     await this.showDefinition(node.type);
                 });
-                nodeSidebar.on('export-tensor', (sender, tensor) => {
+                nodeSidebar.on('export-tensor', async (sender, tensor) => {
                     const defaultPath = tensor.name ? tensor.name.split('/').join('_').split(':').join('_').split('.').join('_') : 'tensor';
-                    this._host.save('NumPy Array', 'npy', defaultPath, (file) => {
+                    const file = await this._host.save('NumPy Array', 'npy', defaultPath);
+                    if (file) {
                         try {
                             let data_type = tensor.type.dataType;
-                            if (data_type === 'boolean') {
-                                data_type = 'bool';
-                            }
+                            data_type = data_type === 'boolean' ? 'bool' : data_type;
                             const execution = new python.Execution();
                             const bytes = execution.invoke('io.BytesIO', []);
                             const dtype = execution.invoke('numpy.dtype', [data_type]);
@@ -1027,16 +1027,14 @@ view.View = class {
                             execution.invoke('numpy.save', [bytes, array]);
                             bytes.seek(0);
                             const blob = new Blob([bytes.read()], { type: 'application/octet-stream' });
-                            this._host.export(file, blob);
+                            await this._host.export(file, blob);
                         } catch (error) {
                             this.error(error, 'Error saving NumPy tensor.', null);
                         }
-                    });
+                    }
                 });
                 nodeSidebar.on('exception', (sender, error) => {
-                    if (this._model) {
-                        error.context = this._model.identifier;
-                    }
+                    this._context(error);
                     this._host.exception(error, false);
                 });
                 nodeSidebar.on('activate', (sender, value) => {
@@ -1050,9 +1048,7 @@ view.View = class {
                 });
                 this._sidebar.open(nodeSidebar.render(), 'Node Properties');
             } catch (error) {
-                if (error) {
-                    error.context = this._model.identifier;
-                }
+                this._context(error);
                 this.error(error, 'Error showing node properties.', null);
             }
         }
@@ -1074,20 +1070,22 @@ view.View = class {
                 this.scrollTo(this._graph.activate(value));
             });
             connectionSidebar.on('exception', (sender, error) => {
-                if (this._model) {
-                    error.context = this._model.identifier;
-                }
+                this._context(error);
                 this._host.exception(error, false);
             });
             this._sidebar.open(connectionSidebar.render(), 'Connection Properties');
         } catch (error) {
-            if (error) {
-                error.context = this._model.identifier;
-            }
+            this._context(error);
             this.error(error, 'Error showing connection properties.', null);
         }
     }
 
+    _context(error) {
+        if (error && !error.context) {
+            error.context = this._model && this._model.identifier ? this._model.identifier : '';
+        }
+    }
+
     async showDefinition(type) {
         if (type && (type.description || type.inputs || type.outputs || type.attributes)) {
             if (type.nodes && type.nodes.length > 0) {
@@ -1733,7 +1731,7 @@ view.Graph = class extends grapher.Graph {
                 for (const value of output.value) {
                     if (!value) {
                         const error = new view.Error('Invalid null argument.');
-                        error.context = this.model.identifier;
+                        this._context(error);
                         throw error;
                     }
                     if (value.name !== '') {