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">
import { defineComponent } from "vue";
// State providers
import dialog from "@/utilities/dialog";
import documents from "@/utilities/documents";
import fullscreen from "@/utilities/fullscreen";

View file

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

View file

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

View file

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

View file

@ -1,15 +1,14 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import { reactive } from "vue";
import { plainToInstance } from "class-transformer";
import {
JsMessage,
DisplayConfirmationToCloseAllDocuments,
DisplayConfirmationToCloseDocument,
DisplayError,
DisplayPanic,
ExportDocument,
newDisplayFolderTreeStructure,
newDisplayFolderTreeStructure as DisplayFolderTreeStructure,
OpenDocumentBrowse,
SaveDocument,
SetActiveDocument,
@ -22,29 +21,16 @@ import {
UpdateScrollbars,
UpdateWorkingColors,
UpdateLayer,
JsMessage,
} from "./js-messages";
} from "@/utilities/js-messages";
type JsMessageCallback<T extends JsMessage> = (responseData: T) => void;
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 = {
const messageConstructors = {
UpdateCanvas,
UpdateScrollbars,
UpdateRulers,
ExportDocument,
SaveDocument,
OpenDocumentBrowse,
DisplayFolderTreeStructure: newDisplayFolderTreeStructure,
DisplayFolderTreeStructure,
UpdateLayer,
SetActiveTool,
SetActiveDocument,
@ -57,39 +43,51 @@ const responseMap = {
DisplayConfirmationToCloseDocument,
DisplayConfirmationToCloseAllDocuments,
} 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 {
return (fn as ConstructsJsMessage).jsMessageMarker !== undefined;
type Constructs<T> = new (...args: any[]) => T;
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) {
const messageMaker = responseMap[responseType];
let message: JsMessage;
if (!messageMaker) {
export function handleJsMessage(messageType: JsMessageType, messageData: any) {
const messageConstructor = messageConstructors[messageType];
if (!messageConstructor) {
// 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)) {
message = plainToInstance(messageMaker, responseData[responseType]);
// Messages with non-empty data are provided by wasm-bindgen as an object with one key as the message name, like: { NameOfThisMessage: { ... } }
// 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 {
message = messageMaker(responseData[responseType]);
message = messageConstructor(unwrappedMessageData);
}
// 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) {
callback(message);
} else if (message) {
// 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;
}