Rework wasm initialization and reduce global state (#379)

* wasm: do the async initialization only once

This allows the rest of the app to access wasm synchronously.

This allows removing of a global.

* provide the wasm via vue provide/inject.

There's still code directly accessing the wasm. That will be changed later.

* MenuBarInput: use injected wasm instead of the global instance

* Let the App handle event listeners

* move stateful modules into state/

* state/fullscreen: create per instance

* App: load the initial document list on mount.
This got lost a few commits ago. Now it's back.

* state/dialog: create per instance

* util/input: remove dependency on global dialog instance

* state/documents: create per instance

* reponse-handler: move into EditorWasm

* comingSoon: move into dialog

* wasm: allow instantiating multiple editors

* input handlers: do not look at canvases outside the mounted App

* input: listen on the container instead of the window when possible

* - removed proxy from wasm-loader
- integrated with js-dispatcher
- state functions to classes
- integrated some upstream changes

* fix errors caused by merge

* Getting closer:
- added global state to track all instances
- fix fullscreen close trigger
- wasm-loader is statefull
- panic across instanes

* - fix outline while using editor
- removed circular import rule
- added editorInstance to js message constructor

* - changed input handler to a class
- still need a better way of handeling it in App.vue

* - fixed single instance of inputManager to weakmap

* - fix no-explicit-any in a few places
- removed global state from input.ts

* simplified two long lines

* removed global state

* removed $data from App

* add mut self to functions in api.rs

* Update Workspace.vue

remove outdated import

* fixed missing import

* Changes throughout code review; note this causes some bugs to be fixed in a later commit

* PR review round 1

* - fix coming soon bugs
- changed folder structure

* moved declaration to .d.ts

* - changed from classes to functions
- moved decs back to app.vue

* removed need to export js function to rust

* changed folder structure

* fixed indentation breaking multiline strings

* Fix eslint rule to whitelist @/../

* Simplify strip-indents implementation

* replace type assertions with better annotations or proper runtime checks

* Small tweaks and code rearranging improvements after second code review pass

* maybe fix mouse events

* Add back preventDefault for mouse scroll

* code review round 2

* Comment improvements

* -removed runtime checks
- fixed layers not showing

* - extened proxy to cover classes
- stopped multiple panics from logging
- Stop wasm-bindgen from mut ref counting our struct

* cleaned up messageConstructors exports

* Fix input and fullscreen regressions

Co-authored-by: Max Fisher <maxmfishernj@gmail.com>
Co-authored-by: mfish33 <32677537+mfish33@users.noreply.github.com>
Co-authored-by: Keavon Chambers <keavon@keavon.com>
This commit is contained in:
Christian Authmann
2021-12-20 07:37:19 +01:00
committed by Keavon Chambers
co-authored by Max Fisher mfish33 Keavon Chambers
parent 6d82672a95
commit 5ec8aaa31d
39 changed files with 1524 additions and 1412 deletions
+140
View File
@@ -0,0 +1,140 @@
import { DialogState } from "@/state/dialog";
import { TextButtonWidget } from "@/components/widgets/widgets";
import { DisplayError, DisplayPanic } from "@/dispatcher/js-messages";
import { EditorState } from "@/state/wasm-loader";
import { stripIndents } from "@/utilities/strip-indents";
export function initErrorHandling(editor: EditorState, dialogState: DialogState) {
// Graphite error dialog
editor.dispatcher.subscribeJsMessage(DisplayError, (displayError) => {
const okButton: TextButtonWidget = {
kind: "TextButton",
callback: async () => dialogState.dismissDialog(),
props: { label: "OK", emphasized: true, minWidth: 96 },
};
const buttons = [okButton];
dialogState.createDialog("Warning", displayError.title, displayError.description, buttons);
});
// Code panic dialog and console error
editor.dispatcher.subscribeJsMessage(DisplayPanic, (displayPanic) => {
// `Error.stackTraceLimit` is only available in V8/Chromium
// eslint-disable-next-line @typescript-eslint/no-explicit-any
(Error as any).stackTraceLimit = Infinity;
const stackTrace = new Error().stack || "";
const panicDetails = `${displayPanic.panic_info}\n\n${stackTrace}`;
// eslint-disable-next-line no-console
console.error(panicDetails);
const reloadButton: TextButtonWidget = {
kind: "TextButton",
callback: async () => window.location.reload(),
props: { label: "Reload", emphasized: true, minWidth: 96 },
};
const copyErrorLogButton: TextButtonWidget = {
kind: "TextButton",
callback: async () => navigator.clipboard.writeText(panicDetails),
props: { label: "Copy Error Log", emphasized: false, minWidth: 96 },
};
const reportOnGithubButton: TextButtonWidget = {
kind: "TextButton",
callback: async () => window.open(githubUrl(panicDetails), "_blank"),
props: { label: "Report Bug", emphasized: false, minWidth: 96 },
};
const buttons = [reloadButton, copyErrorLogButton, reportOnGithubButton];
dialogState.createDialog("Warning", displayPanic.title, displayPanic.description, buttons);
});
}
function githubUrl(panicDetails: string) {
const url = new URL("https://github.com/GraphiteEditor/Graphite/issues/new");
const body = stripIndents`
**Describe the Crash**
Explain clearly what you were doing when the crash occurred.
**Steps To Reproduce**
Describe precisely how the crash occurred, step by step, starting with a new editor window.
1. Open the Graphite Editor at https://editor.graphite.design
2.
3.
4.
5.
**Additional Details**
Provide any further information or context that you think would be helpful in fixing the issue. Screenshots or video can be linked or attached to this issue.
**Browser and OS**
${browserVersion()}, ${operatingSystem()}
**Stack Trace**
Copied from the crash dialog in the Graphite Editor:
\`\`\`
${panicDetails}
\`\`\`
`;
const fields = {
title: "[Crash Report] ",
body,
labels: ["Crash"].join(","),
projects: [].join(","),
milestone: "",
assignee: "",
template: "",
};
Object.entries(fields).forEach(([field, value]) => {
if (value) url.searchParams.set(field, value);
});
return url.toString();
}
function browserVersion(): string {
const agent = window.navigator.userAgent;
let match = agent.match(/(opera|chrome|safari|firefox|msie|trident(?=\/))\/?\s*(\d+)/i) || [];
if (/trident/i.test(match[1])) {
const browser = /\brv[ :]+(\d+)/g.exec(agent) || [];
return `IE ${browser[1] || ""}`.trim();
}
if (match[1] === "Chrome") {
let browser = agent.match(/\bEdg\/(\d+)/);
if (browser !== null) return `Edge (Chromium) ${browser[1]}`;
browser = agent.match(/\bOPR\/(\d+)/);
if (browser !== null) return `Opera ${browser[1]}`;
}
match = match[2] ? [match[1], match[2]] : [navigator.appName, navigator.appVersion, "-?"];
const browser = agent.match(/version\/(\d+)/i);
if (browser !== null) match.splice(1, 1, browser[1]);
return `${match[0]} ${match[1]}`;
}
function operatingSystem(): string {
const osTable: Record<string, string> = {
"Windows NT 10": "Windows 10 or 11",
"Windows NT 6.3": "Windows 8.1",
"Windows NT 6.2": "Windows 8",
"Windows NT 6.1": "Windows 7",
"Windows NT 6.0": "Windows Vista",
"Windows NT 5.1": "Windows XP",
"Windows NT 5.0": "Windows 2000",
Mac: "Mac",
X11: "Unix",
Linux: "Linux",
Unknown: "YOUR OPERATING SYSTEM",
};
const userAgentOS = Object.keys(osTable).find((key) => window.navigator.userAgent.includes(key));
return osTable[userAgentOS || "Unknown"];
}