Clean up JS message dispatcher and fix a bug thrown on empty-data messages

Fixes bug in #394
This commit is contained in:
Keavon Chambers 2021-12-16 02:18:45 -08:00
parent 1cf90bde9a
commit d4e3684744
5 changed files with 44 additions and 46 deletions

View File

@ -220,6 +220,7 @@ img {
<script lang="ts"> <script lang="ts">
import { defineComponent } from "vue"; import { defineComponent } from "vue";
// State providers
import dialog from "@/utilities/dialog"; import dialog from "@/utilities/dialog";
import documents from "@/utilities/documents"; import documents from "@/utilities/documents";
import fullscreen from "@/utilities/fullscreen"; import fullscreen from "@/utilities/fullscreen";

View File

@ -424,14 +424,14 @@ export default defineComponent({
}); });
subscribeJsMessage(UpdateLayer, (updateLayer) => { subscribeJsMessage(UpdateLayer, (updateLayer) => {
const responsePath = updateLayer.data.path; const targetPath = updateLayer.data.path;
const responseLayer = updateLayer.data; const targetLayer = updateLayer.data;
const layer = this.layerCache.get(responsePath.toString()); const layer = this.layerCache.get(targetPath.toString());
if (layer) { if (layer) {
Object.assign(this.layerCache.get(responsePath.toString()), responseLayer); Object.assign(this.layerCache.get(targetPath.toString()), targetLayer);
} else { } else {
this.layerCache.set(responsePath.toString(), responseLayer); this.layerCache.set(targetPath.toString(), targetLayer);
} }
this.setBlendModeForSelectedLayers(); this.setBlendModeForSelectedLayers();
this.setOpacityForSelectedLayers(); this.setOpacityForSelectedLayers();

View File

@ -1,7 +1,7 @@
import { createDialog, dismissDialog } from "@/utilities/dialog"; import { createDialog, dismissDialog } from "@/utilities/dialog";
import { TextButtonWidget } from "@/components/widgets/widgets"; import { TextButtonWidget } from "@/components/widgets/widgets";
import { subscribeJsMessage } from "@/utilities/js-message-dispatcher"; import { subscribeJsMessage } from "@/utilities/js-message-dispatcher";
import { DisplayError, DisplayPanic } from "./js-messages"; import { DisplayError, DisplayPanic } from "@/utilities/js-messages";
// Coming soon dialog // Coming soon dialog
export function comingSoon(issueNumber?: number) { export function comingSoon(issueNumber?: number) {
@ -139,8 +139,7 @@ function browserVersion(): string {
function operatingSystem(): string { function operatingSystem(): string {
const osTable: Record<string, string> = { const osTable: Record<string, string> = {
"Windows NT 11": "Windows 11", "Windows NT 10": "Windows 10 or 11",
"Windows NT 10": "Windows 10",
"Windows NT 6.3": "Windows 8.1", "Windows NT 6.3": "Windows 8.1",
"Windows NT 6.2": "Windows 8", "Windows NT 6.2": "Windows 8",
"Windows NT 6.1": "Windows 7", "Windows NT 6.1": "Windows 7",

View File

@ -1,7 +1,7 @@
import { toggleFullscreen } from "@/utilities/fullscreen"; import { toggleFullscreen } from "@/utilities/fullscreen";
import { dialogIsVisible, dismissDialog, submitDialog } from "@/utilities/dialog"; import { dialogIsVisible, dismissDialog, submitDialog } from "@/utilities/dialog";
import { panicProxy } from "@/utilities/panic-proxy"; import { panicProxy } from "@/utilities/panic-proxy";
import documents from "./documents"; import documents from "@/utilities/documents";
const wasm = import("@/../wasm/pkg").then(panicProxy); const wasm = import("@/../wasm/pkg").then(panicProxy);

View File

@ -1,15 +1,14 @@
/* eslint-disable @typescript-eslint/no-explicit-any */ /* eslint-disable @typescript-eslint/no-explicit-any */
import { reactive } from "vue";
import { plainToInstance } from "class-transformer"; import { plainToInstance } from "class-transformer";
import { import {
JsMessage,
DisplayConfirmationToCloseAllDocuments, DisplayConfirmationToCloseAllDocuments,
DisplayConfirmationToCloseDocument, DisplayConfirmationToCloseDocument,
DisplayError, DisplayError,
DisplayPanic, DisplayPanic,
ExportDocument, ExportDocument,
newDisplayFolderTreeStructure, newDisplayFolderTreeStructure as DisplayFolderTreeStructure,
OpenDocumentBrowse, OpenDocumentBrowse,
SaveDocument, SaveDocument,
SetActiveDocument, SetActiveDocument,
@ -22,29 +21,16 @@ import {
UpdateScrollbars, UpdateScrollbars,
UpdateWorkingColors, UpdateWorkingColors,
UpdateLayer, UpdateLayer,
JsMessage, } from "@/utilities/js-messages";
} from "./js-messages";
type JsMessageCallback<T extends JsMessage> = (responseData: T) => void; const messageConstructors = {
type JsMessageCallbackMap = {
[response: string]: JsMessageCallback<any> | undefined;
};
const state = reactive({
responseMap: {} as JsMessageCallbackMap,
});
type Constructs<T> = new (...args: any[]) => T;
type ConstructsJsMessage = Constructs<JsMessage> & typeof JsMessage;
const responseMap = {
UpdateCanvas, UpdateCanvas,
UpdateScrollbars, UpdateScrollbars,
UpdateRulers, UpdateRulers,
ExportDocument, ExportDocument,
SaveDocument, SaveDocument,
OpenDocumentBrowse, OpenDocumentBrowse,
DisplayFolderTreeStructure: newDisplayFolderTreeStructure, DisplayFolderTreeStructure,
UpdateLayer, UpdateLayer,
SetActiveTool, SetActiveTool,
SetActiveDocument, SetActiveDocument,
@ -57,39 +43,51 @@ const responseMap = {
DisplayConfirmationToCloseDocument, DisplayConfirmationToCloseDocument,
DisplayConfirmationToCloseAllDocuments, DisplayConfirmationToCloseAllDocuments,
} as const; } as const;
type JsMessageType = keyof typeof messageConstructors;
export type JsMessageType = keyof typeof responseMap; type JsMessageCallback<T extends JsMessage> = (messageData: T) => void;
type JsMessageCallbackMap = {
[message: string]: JsMessageCallback<any> | undefined;
};
function isJsMessageConstructor(fn: ConstructsJsMessage | ((data: any) => JsMessage)): fn is ConstructsJsMessage { type Constructs<T> = new (...args: any[]) => T;
return (fn as ConstructsJsMessage).jsMessageMarker !== undefined; type ConstructsJsMessage = Constructs<JsMessage> & typeof JsMessage;
const subscriptions = {} as JsMessageCallbackMap;
export function subscribeJsMessage<T extends JsMessage>(messageType: Constructs<T>, callback: JsMessageCallback<T>) {
subscriptions[messageType.name] = callback;
} }
export function handleJsMessage(responseType: JsMessageType, responseData: any) { export function handleJsMessage(messageType: JsMessageType, messageData: any) {
const messageMaker = responseMap[responseType]; const messageConstructor = messageConstructors[messageType];
let message: JsMessage; if (!messageConstructor) {
if (!messageMaker) {
// eslint-disable-next-line no-console // eslint-disable-next-line no-console
console.error(`Received a Response of type "${responseType}" but but was not able to parse the data.`); console.error(`Received a frontend message of type "${messageType}" but but was not able to parse the data.`);
return;
} }
if (isJsMessageConstructor(messageMaker)) { // Messages with non-empty data are provided by wasm-bindgen as an object with one key as the message name, like: { NameOfThisMessage: { ... } }
message = plainToInstance(messageMaker, responseData[responseType]); // Messages with empty data are provided by wasm-bindgen as a string with the message name, like: "NameOfThisMessage"
const unwrappedMessageData = messageData[messageType] || {};
const isJsMessageConstructor = (fn: ConstructsJsMessage | ((data: any) => JsMessage)): fn is ConstructsJsMessage => {
return (fn as ConstructsJsMessage).jsMessageMarker !== undefined;
};
let message: JsMessage;
if (isJsMessageConstructor(messageConstructor)) {
message = plainToInstance(messageConstructor, unwrappedMessageData);
} else { } else {
message = messageMaker(responseData[responseType]); message = messageConstructor(unwrappedMessageData);
} }
// It is ok to use constructor.name even with minification since it is used consistently with registerHandler // It is ok to use constructor.name even with minification since it is used consistently with registerHandler
const callback = state.responseMap[message.constructor.name]; const callback = subscriptions[message.constructor.name];
if (callback && message) { if (callback && message) {
callback(message); callback(message);
} else if (message) { } else if (message) {
// eslint-disable-next-line no-console // eslint-disable-next-line no-console
console.error(`Received a Response of type "${responseType}" but no handler was registered for it from the client.`); console.error(`Received a frontend message of type "${messageType}" but no handler was registered for it from the client.`);
} }
} }
export function subscribeJsMessage<T extends JsMessage>(responseType: Constructs<T>, callback: JsMessageCallback<T>) {
state.responseMap[responseType.name] = callback;
}