Frontend refactor to move response handler, key input handling, and more into new utilities folder (#260)

Part of #124
This commit is contained in:
Keavon Chambers 2021-07-14 14:31:09 -07:00 committed by GitHub
parent 9d34aa4867
commit 726152759f
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
14 changed files with 80 additions and 66 deletions

View file

@ -31,6 +31,7 @@ module.exports = {
"no-console": process.env.NODE_ENV === "production" ? "warn" : "off",
"no-debugger": process.env.NODE_ENV === "production" ? "warn" : "off",
"no-param-reassign": ["error", { props: false }],
"import/prefer-default-export": "off",
"max-len": ["error", { code: 200, tabWidth: 4 }],
"@typescript-eslint/camelcase": "off",
"@typescript-eslint/no-use-before-define": "off",

View file

@ -192,7 +192,8 @@
<script lang="ts">
import { defineComponent } from "vue";
import { ResponseType, registerResponseHandler, Response, UpdateCanvas, SetActiveTool, ExportDocument, SetCanvasZoom, SetRotation } from "../../response-handler";
import { makeModifiersBitfield } from "@/utilities/input";
import { ResponseType, registerResponseHandler, Response, UpdateCanvas, SetActiveTool, ExportDocument, SetCanvasZoom, SetRotation } from "../../utilities/response-handler";
import LayoutRow from "../layout/LayoutRow.vue";
import LayoutCol from "../layout/LayoutCol.vue";
import WorkingColors from "../widgets/WorkingColors.vue";
@ -217,26 +218,6 @@ const modeMenuEntries: SectionsOfMenuListEntries = [
const wasm = import("../../../wasm/pkg");
function redirectKeyboardEventToBackend(e: KeyboardEvent): boolean {
// Don't redirect user input from text entry into HTML elements
const target = e.target as HTMLElement;
if (target.nodeName === "INPUT" || target.nodeName === "TEXTAREA" || target.isContentEditable) return false;
// Don't redirect a fullscreen request
if (e.key.toLowerCase() === "f11") return false;
// Don't redirect debugging tools
if (e.key.toLowerCase() === "f12") return false;
if (e.ctrlKey && e.shiftKey && e.key.toLowerCase() === "c") return false;
return true;
}
function makeModifiersBitfield(control: boolean, shift: boolean, alt: boolean): number {
// eslint-disable-next-line no-bitwise
return Number(control) | (Number(shift) << 1) | (Number(alt) << 2);
}
export default defineComponent({
methods: {
async viewportResize() {
@ -273,28 +254,11 @@ export default defineComponent({
const { set_rotation } = await wasm;
set_rotation(newRotation * (Math.PI / 180));
},
async keyDown(e: KeyboardEvent) {
if (redirectKeyboardEventToBackend(e)) {
e.preventDefault();
const { on_key_down } = await wasm;
const modifiers = makeModifiersBitfield(e.ctrlKey, e.shiftKey, e.altKey);
on_key_down(e.key, modifiers);
}
},
async keyUp(e: KeyboardEvent) {
if (redirectKeyboardEventToBackend(e)) {
e.preventDefault();
const { on_key_up } = await wasm;
const modifiers = makeModifiersBitfield(e.ctrlKey, e.shiftKey, e.altKey);
on_key_up(e.key, modifiers);
}
},
async selectTool(toolName: string) {
const { select_tool } = await wasm;
select_tool(toolName);
},
async viewModeChanged(toolIndex: number) {
console.log(toolIndex);
function todo(_: number) {
return _;
}
@ -341,10 +305,10 @@ export default defineComponent({
}
});
window.addEventListener("keyup", (e: KeyboardEvent) => this.keyUp(e));
// TODO: Move event listeners to `main.ts`
const canvas = this.$refs.canvas as HTMLDivElement;
canvas.addEventListener("wheel", this.canvasMouseScroll, { passive: false });
window.addEventListener("keydown", (e: KeyboardEvent) => this.keyDown(e));
window.addEventListener("resize", () => this.viewportResize());
window.addEventListener("DOMContentLoaded", () => this.viewportResize());

View file

@ -104,7 +104,7 @@
<script lang="ts">
import { defineComponent } from "vue";
import { ResponseType, registerResponseHandler, Response, ExpandFolder, LayerPanelEntry } from "../../response-handler";
import { ResponseType, registerResponseHandler, Response, ExpandFolder, LayerPanelEntry } from "../../utilities/response-handler";
import LayoutRow from "../layout/LayoutRow.vue";
import LayoutCol from "../layout/LayoutCol.vue";
import Separator, { SeparatorType } from "../widgets/Separator.vue";

View file

@ -118,7 +118,8 @@
<script lang="ts">
import { defineComponent } from "vue";
import { clamp, hsvToRgb, rgbToHsv, isRGB } from "../../../lib/utils";
import { hsvToRgb, rgbToHsv, isRGB } from "@/utilities/color";
import { clamp } from "@/utilities/math";
const enum ColorPickerState {
Idle = "Idle",

View file

@ -66,13 +66,13 @@
</style>
<script lang="ts">
import { rgbToDecimalRgb } from "@/lib/utils";
import { rgbToDecimalRgb } from "@/utilities/color";
import { defineComponent } from "vue";
import ColorPicker from "../floating-menus/ColorPicker.vue";
import FloatingMenu, { MenuDirection, MenuType } from "../floating-menus/FloatingMenu.vue";
import { ResponseType, registerResponseHandler, Response, UpdateWorkingColors } from "../../../response-handler";
import { ResponseType, registerResponseHandler, Response, UpdateWorkingColors } from "../../../utilities/response-handler";
const wasm = import("../../../../wasm/pkg");
const wasm = import("@/../wasm/pkg");
export default defineComponent({
components: {

View file

@ -150,7 +150,7 @@ import Minimap from "../panels/Minimap.vue";
import IconButton from "../widgets/buttons/IconButton.vue";
import PopoverButton, { PopoverButtonIcon } from "../widgets/buttons/PopoverButton.vue";
import { MenuDirection } from "../widgets/floating-menus/FloatingMenu.vue";
import { ResponseType, registerResponseHandler, Response } from "../../response-handler";
import { ResponseType, registerResponseHandler, Response } from "../../utilities/response-handler";
const wasm = import("../../../wasm/pkg");

View file

@ -46,7 +46,7 @@
<script lang="ts">
import { defineComponent } from "vue";
import { ResponseType, registerResponseHandler, Response, SetActiveDocument, NewDocument, CloseDocument } from "../../response-handler";
import { ResponseType, registerResponseHandler, Response, SetActiveDocument, NewDocument, CloseDocument } from "../../utilities/response-handler";
import LayoutRow from "../layout/LayoutRow.vue";
import LayoutCol from "../layout/LayoutCol.vue";
import Panel from "./Panel.vue";

View file

@ -1,9 +1,11 @@
import { createApp } from "vue";
import { handleKeyUp, handleKeyDown } from "@/utilities/input";
import App from "./App.vue";
import { attachResponseHandlerToPage } from "./response-handler";
// Bind global browser events
document.addEventListener("contextmenu", (e) => e.preventDefault());
window.addEventListener("keyup", (e: KeyboardEvent) => handleKeyUp(e));
window.addEventListener("keydown", (e: KeyboardEvent) => handleKeyDown(e));
attachResponseHandlerToPage();
// Initialize the Vue application
createApp(App).mount("#app");

View file

@ -63,10 +63,6 @@ export function rgbToDecimalRgb(rgb: RGB) {
return { r, g, b, a: rgb.a };
}
export function clamp(value: number, min = 0, max = 1) {
return Math.max(min, Math.min(value, max));
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any
export function isRGB(data: any): data is RGB {
if (typeof data !== "object" || data === null) return false;

View file

@ -0,0 +1,45 @@
const wasm = import("@/../wasm/pkg");
export function shouldRedirectKeyboardEventToBackend(e: KeyboardEvent): boolean {
// Don't redirect user input from text entry into HTML elements
const target = e.target as HTMLElement;
if (target.nodeName === "INPUT" || target.nodeName === "TEXTAREA" || target.isContentEditable) return false;
// Don't redirect a fullscreen request
if (e.key.toLowerCase() === "f11") return false;
// Don't redirect a reload request
if (e.key.toLowerCase() === "f5") return false;
// Don't redirect debugging tools
if (e.key.toLowerCase() === "f12") return false;
if (e.ctrlKey && e.shiftKey && e.key.toLowerCase() === "c") return false;
if (e.ctrlKey && e.shiftKey && e.key.toLowerCase() === "i") return false;
if (e.ctrlKey && e.shiftKey && e.key.toLowerCase() === "j") return false;
// Redirect to the backend
return true;
}
export async function handleKeyDown(e: KeyboardEvent) {
if (shouldRedirectKeyboardEventToBackend(e)) {
e.preventDefault();
const { on_key_down } = await wasm;
const modifiers = makeModifiersBitfield(e.ctrlKey, e.shiftKey, e.altKey);
on_key_down(e.key, modifiers);
}
}
export async function handleKeyUp(e: KeyboardEvent) {
if (shouldRedirectKeyboardEventToBackend(e)) {
e.preventDefault();
const { on_key_up } = await wasm;
const modifiers = makeModifiersBitfield(e.ctrlKey, e.shiftKey, e.altKey);
on_key_up(e.key, modifiers);
}
}
export function makeModifiersBitfield(control: boolean, shift: boolean, alt: boolean): number {
// eslint-disable-next-line no-bitwise
return Number(control) | (Number(shift) << 1) | (Number(alt) << 2);
}

View file

@ -0,0 +1,3 @@
export function clamp(value: number, min = 0, max = 1) {
return Math.max(min, Math.min(value, max));
}

View file

@ -0,0 +1,4 @@
// This file is instantiated by wasm-bindgen in `client\web\wasm\src\lib.rs` and re-exports the `handleResponse` function to
// provide access to the global copy of `response-handler.ts` with its shared state, not an isolated duplicate with empty state
export { handleResponse } from "@/utilities/response-handler";

View file

@ -1,14 +1,15 @@
import { reactive } from "vue";
/* eslint-disable @typescript-eslint/no-explicit-any */
type ResponseCallback = (responseData: Response) => void;
type ResponseMap = {
[response: string]: ResponseCallback | undefined;
};
declare global {
interface Window {
responseMap: ResponseMap;
}
}
const state = reactive({
responseMap: {} as ResponseMap,
});
export enum ResponseType {
UpdateCanvas = "UpdateCanvas",
@ -25,17 +26,13 @@ export enum ResponseType {
SetRotation = "SetRotation",
}
export function attachResponseHandlerToPage() {
window.responseMap = {};
}
export function registerResponseHandler(responseType: ResponseType, callback: ResponseCallback) {
window.responseMap[responseType] = callback;
state.responseMap[responseType] = callback;
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any
export function handleResponse(responseType: string, responseData: any) {
const callback = window.responseMap[responseType];
const callback = state.responseMap[responseType];
const data = parseResponse(responseType, responseData);
if (callback && data) {
@ -97,6 +94,7 @@ export interface Color {
alpha: number;
}
function newColor(input: any): Color {
// TODO: Possibly change this in the Rust side to avoid any pitfalls
return { red: input.red * 255, green: input.green * 255, blue: input.blue * 255, alpha: input.alpha };
}

View file

@ -30,7 +30,7 @@ fn send_response(response_type: String, response_data: FrontendMessage) {
let _ = handleResponse(response_type, response_data).map_err(|error| log::error!("javascript threw an error: {:?}", error));
}
#[wasm_bindgen(module = "/../src/response-handler.ts")]
#[wasm_bindgen(module = "/../src/utilities/response-handler-binding.ts")]
extern "C" {
#[wasm_bindgen(catch)]
fn handleResponse(responseType: String, responseData: JsValue) -> Result<(), JsValue>;