Vue initialization and FloatingMenu codebase refactoring and cleanup (#649)

* Clean up Vue initialization-related code

* Rename folder: dispatcher -> interop

* Rename folder: state -> providers

* Comments and clarification

* Rename JS dispatcher to subscription router

* Assorted cleanup and renaming

* Rename: js-messages.ts -> messages.ts

* Comments

* Remove unused Vue component injects

* Clean up coming soon and add warning about freezing the app

* Further cleanup

* Dangerous changes

* Simplify App.vue code

* Move more disparate init code from components into managers

* Rename folder: providers -> state-providers

* Other

* Move Document panel options bar separator to backend

* Add destructors to managers to fix HMR

* Comments and code style

* Rename variable: font -> font_file_url

* Fix async font loading; refactor janky floating menu openness and min-width measurement; fix Vetur errors

* Fix misaligned canvas in viewport until panning on page (re)load

* Add Vue bidirectional props documentation

* More folder renaming for better terminology; add some documentation
This commit is contained in:
Keavon Chambers
2022-05-21 19:46:15 -07:00
parent 4c3c925c2c
commit fc2d983bd7
73 changed files with 1572 additions and 1462 deletions
@@ -0,0 +1,66 @@
import { plainToInstance } from "class-transformer";
import type { WasmEditorInstance, WasmRawInstance } from "@/wasm-communication/editor";
import { JsMessageType, messageMakers, JsMessage } from "@/wasm-communication/messages";
type JsMessageCallback<T extends JsMessage> = (messageData: T) => void;
type JsMessageCallbackMap = {
// Don't know a better way of typing this since it can be any subclass of JsMessage
// The functions interacting with this map are strongly typed though around JsMessage
// eslint-disable-next-line @typescript-eslint/no-explicit-any
[message: string]: JsMessageCallback<any> | undefined;
};
// eslint-disable-next-line @typescript-eslint/explicit-function-return-type
export function createSubscriptionRouter() {
const subscriptions: JsMessageCallbackMap = {};
const subscribeJsMessage = <T extends JsMessage, Args extends unknown[]>(messageType: new (...args: Args) => T, callback: JsMessageCallback<T>): void => {
subscriptions[messageType.name] = callback;
};
const handleJsMessage = (messageType: JsMessageType, messageData: Record<string, unknown>, wasm: WasmRawInstance, instance: WasmEditorInstance): void => {
// Find the message maker for the message type, which can either be a JS class constructor or a function that returns an instance of the JS class
const messageMaker = messageMakers[messageType];
if (!messageMaker) {
// eslint-disable-next-line no-console
console.error(
`Received a frontend message of type "${messageType}" but was not able to parse the data. ` +
"(Perhaps this message parser isn't exported in `messageMakers` at the bottom of `messages.ts`.)"
);
return;
}
// Checks if the provided `messageMaker` is a class extending `JsMessage`. All classes inheriting from `JsMessage` will have a static readonly `jsMessageMarker` which is `true`.
const isJsMessageMaker = (fn: typeof messageMaker): fn is typeof JsMessage => "jsMessageMarker" in fn;
const messageIsClass = isJsMessageMaker(messageMaker);
// 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"
// Here we extract the payload object or use an empty object depending on the situation.
const unwrappedMessageData = messageData[messageType] || {};
// Converts to a `JsMessage` object by turning the JSON message data into an instance of the message class, either automatically or by calling the function that builds it.
// If the `messageMaker` is a `JsMessage` class then we use the class-transformer library's `plainToInstance` function in order to convert the JSON data into the destination class.
// If it is not a `JsMessage` then it should be a custom function that creates a JsMessage from a JSON, so we call the function itself with the raw JSON as an argument.
// The resulting `message` is an instance of a class that extends `JsMessage`.
const message = messageIsClass ? plainToInstance(messageMaker, unwrappedMessageData) : messageMaker(unwrappedMessageData, wasm, instance);
// It is ok to use constructor.name even with minification since it is used consistently with registerHandler
const callback = subscriptions[message.constructor.name];
// If we have constructed a valid message, then we try and execute the callback that the frontend has associated with this message.
// The frontend should always have a callback for all messages, and so we display an error if one was not found.
if (message) {
if (callback) callback(message);
// eslint-disable-next-line no-console
else console.error(`Received a frontend message of type "${messageType}" but no handler was registered for it from the client.`);
}
};
return {
subscribeJsMessage,
handleJsMessage,
};
}
export type SubscriptionRouter = ReturnType<typeof createSubscriptionRouter>;