mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2025-12-23 10:11:54 +00:00
Frontend refactor to move response handler, key input handling, and more into new utilities folder (#260)
Part of #124
This commit is contained in:
parent
9d34aa4867
commit
726152759f
14 changed files with 80 additions and 66 deletions
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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());
|
||||
|
||||
|
|
|
|||
|
|
@ -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";
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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: {
|
||||
|
|
|
|||
|
|
@ -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");
|
||||
|
||||
|
|
|
|||
|
|
@ -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";
|
||||
|
|
|
|||
|
|
@ -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");
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
45
client/web/src/utilities/input.ts
Normal file
45
client/web/src/utilities/input.ts
Normal 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);
|
||||
}
|
||||
3
client/web/src/utilities/math.ts
Normal file
3
client/web/src/utilities/math.ts
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
export function clamp(value: number, min = 0, max = 1) {
|
||||
return Math.max(min, Math.min(value, max));
|
||||
}
|
||||
4
client/web/src/utilities/response-handler-binding.ts
Normal file
4
client/web/src/utilities/response-handler-binding.ts
Normal 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";
|
||||
|
|
@ -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 };
|
||||
}
|
||||
|
||||
|
|
@ -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>;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue