mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2026-09-29 23:48:11 +08: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:
@@ -31,6 +31,7 @@ module.exports = {
|
|||||||
"no-console": process.env.NODE_ENV === "production" ? "warn" : "off",
|
"no-console": process.env.NODE_ENV === "production" ? "warn" : "off",
|
||||||
"no-debugger": process.env.NODE_ENV === "production" ? "warn" : "off",
|
"no-debugger": process.env.NODE_ENV === "production" ? "warn" : "off",
|
||||||
"no-param-reassign": ["error", { props: false }],
|
"no-param-reassign": ["error", { props: false }],
|
||||||
|
"import/prefer-default-export": "off",
|
||||||
"max-len": ["error", { code: 200, tabWidth: 4 }],
|
"max-len": ["error", { code: 200, tabWidth: 4 }],
|
||||||
"@typescript-eslint/camelcase": "off",
|
"@typescript-eslint/camelcase": "off",
|
||||||
"@typescript-eslint/no-use-before-define": "off",
|
"@typescript-eslint/no-use-before-define": "off",
|
||||||
|
|||||||
@@ -192,7 +192,8 @@
|
|||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { defineComponent } from "vue";
|
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 LayoutRow from "../layout/LayoutRow.vue";
|
||||||
import LayoutCol from "../layout/LayoutCol.vue";
|
import LayoutCol from "../layout/LayoutCol.vue";
|
||||||
import WorkingColors from "../widgets/WorkingColors.vue";
|
import WorkingColors from "../widgets/WorkingColors.vue";
|
||||||
@@ -217,26 +218,6 @@ const modeMenuEntries: SectionsOfMenuListEntries = [
|
|||||||
|
|
||||||
const wasm = import("../../../wasm/pkg");
|
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({
|
export default defineComponent({
|
||||||
methods: {
|
methods: {
|
||||||
async viewportResize() {
|
async viewportResize() {
|
||||||
@@ -273,28 +254,11 @@ export default defineComponent({
|
|||||||
const { set_rotation } = await wasm;
|
const { set_rotation } = await wasm;
|
||||||
set_rotation(newRotation * (Math.PI / 180));
|
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) {
|
async selectTool(toolName: string) {
|
||||||
const { select_tool } = await wasm;
|
const { select_tool } = await wasm;
|
||||||
select_tool(toolName);
|
select_tool(toolName);
|
||||||
},
|
},
|
||||||
async viewModeChanged(toolIndex: number) {
|
async viewModeChanged(toolIndex: number) {
|
||||||
console.log(toolIndex);
|
|
||||||
function todo(_: number) {
|
function todo(_: number) {
|
||||||
return _;
|
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;
|
const canvas = this.$refs.canvas as HTMLDivElement;
|
||||||
canvas.addEventListener("wheel", this.canvasMouseScroll, { passive: false });
|
canvas.addEventListener("wheel", this.canvasMouseScroll, { passive: false });
|
||||||
window.addEventListener("keydown", (e: KeyboardEvent) => this.keyDown(e));
|
|
||||||
window.addEventListener("resize", () => this.viewportResize());
|
window.addEventListener("resize", () => this.viewportResize());
|
||||||
window.addEventListener("DOMContentLoaded", () => this.viewportResize());
|
window.addEventListener("DOMContentLoaded", () => this.viewportResize());
|
||||||
|
|
||||||
|
|||||||
@@ -104,7 +104,7 @@
|
|||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { defineComponent } from "vue";
|
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 LayoutRow from "../layout/LayoutRow.vue";
|
||||||
import LayoutCol from "../layout/LayoutCol.vue";
|
import LayoutCol from "../layout/LayoutCol.vue";
|
||||||
import Separator, { SeparatorType } from "../widgets/Separator.vue";
|
import Separator, { SeparatorType } from "../widgets/Separator.vue";
|
||||||
|
|||||||
@@ -118,7 +118,8 @@
|
|||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { defineComponent } from "vue";
|
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 {
|
const enum ColorPickerState {
|
||||||
Idle = "Idle",
|
Idle = "Idle",
|
||||||
|
|||||||
@@ -66,13 +66,13 @@
|
|||||||
</style>
|
</style>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { rgbToDecimalRgb } from "@/lib/utils";
|
import { rgbToDecimalRgb } from "@/utilities/color";
|
||||||
import { defineComponent } from "vue";
|
import { defineComponent } from "vue";
|
||||||
import ColorPicker from "../floating-menus/ColorPicker.vue";
|
import ColorPicker from "../floating-menus/ColorPicker.vue";
|
||||||
import FloatingMenu, { MenuDirection, MenuType } from "../floating-menus/FloatingMenu.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({
|
export default defineComponent({
|
||||||
components: {
|
components: {
|
||||||
|
|||||||
@@ -150,7 +150,7 @@ import Minimap from "../panels/Minimap.vue";
|
|||||||
import IconButton from "../widgets/buttons/IconButton.vue";
|
import IconButton from "../widgets/buttons/IconButton.vue";
|
||||||
import PopoverButton, { PopoverButtonIcon } from "../widgets/buttons/PopoverButton.vue";
|
import PopoverButton, { PopoverButtonIcon } from "../widgets/buttons/PopoverButton.vue";
|
||||||
import { MenuDirection } from "../widgets/floating-menus/FloatingMenu.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");
|
const wasm = import("../../../wasm/pkg");
|
||||||
|
|
||||||
|
|||||||
@@ -46,7 +46,7 @@
|
|||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { defineComponent } from "vue";
|
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 LayoutRow from "../layout/LayoutRow.vue";
|
||||||
import LayoutCol from "../layout/LayoutCol.vue";
|
import LayoutCol from "../layout/LayoutCol.vue";
|
||||||
import Panel from "./Panel.vue";
|
import Panel from "./Panel.vue";
|
||||||
|
|||||||
@@ -1,9 +1,11 @@
|
|||||||
import { createApp } from "vue";
|
import { createApp } from "vue";
|
||||||
|
import { handleKeyUp, handleKeyDown } from "@/utilities/input";
|
||||||
import App from "./App.vue";
|
import App from "./App.vue";
|
||||||
import { attachResponseHandlerToPage } from "./response-handler";
|
|
||||||
|
|
||||||
|
// Bind global browser events
|
||||||
document.addEventListener("contextmenu", (e) => e.preventDefault());
|
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");
|
createApp(App).mount("#app");
|
||||||
|
|||||||
@@ -63,10 +63,6 @@ export function rgbToDecimalRgb(rgb: RGB) {
|
|||||||
return { r, g, b, a: rgb.a };
|
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
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
export function isRGB(data: any): data is RGB {
|
export function isRGB(data: any): data is RGB {
|
||||||
if (typeof data !== "object" || data === null) return false;
|
if (typeof data !== "object" || data === null) return false;
|
||||||
@@ -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);
|
||||||
|
}
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
export function clamp(value: number, min = 0, max = 1) {
|
||||||
|
return Math.max(min, Math.min(value, max));
|
||||||
|
}
|
||||||
@@ -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 */
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||||
|
|
||||||
type ResponseCallback = (responseData: Response) => void;
|
type ResponseCallback = (responseData: Response) => void;
|
||||||
type ResponseMap = {
|
type ResponseMap = {
|
||||||
[response: string]: ResponseCallback | undefined;
|
[response: string]: ResponseCallback | undefined;
|
||||||
};
|
};
|
||||||
declare global {
|
|
||||||
interface Window {
|
const state = reactive({
|
||||||
responseMap: ResponseMap;
|
responseMap: {} as ResponseMap,
|
||||||
}
|
});
|
||||||
}
|
|
||||||
|
|
||||||
export enum ResponseType {
|
export enum ResponseType {
|
||||||
UpdateCanvas = "UpdateCanvas",
|
UpdateCanvas = "UpdateCanvas",
|
||||||
@@ -25,17 +26,13 @@ export enum ResponseType {
|
|||||||
SetRotation = "SetRotation",
|
SetRotation = "SetRotation",
|
||||||
}
|
}
|
||||||
|
|
||||||
export function attachResponseHandlerToPage() {
|
|
||||||
window.responseMap = {};
|
|
||||||
}
|
|
||||||
|
|
||||||
export function registerResponseHandler(responseType: ResponseType, callback: ResponseCallback) {
|
export function registerResponseHandler(responseType: ResponseType, callback: ResponseCallback) {
|
||||||
window.responseMap[responseType] = callback;
|
state.responseMap[responseType] = callback;
|
||||||
}
|
}
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
export function handleResponse(responseType: string, responseData: any) {
|
export function handleResponse(responseType: string, responseData: any) {
|
||||||
const callback = window.responseMap[responseType];
|
const callback = state.responseMap[responseType];
|
||||||
const data = parseResponse(responseType, responseData);
|
const data = parseResponse(responseType, responseData);
|
||||||
|
|
||||||
if (callback && data) {
|
if (callback && data) {
|
||||||
@@ -97,6 +94,7 @@ export interface Color {
|
|||||||
alpha: number;
|
alpha: number;
|
||||||
}
|
}
|
||||||
function newColor(input: any): Color {
|
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 };
|
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));
|
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" {
|
extern "C" {
|
||||||
#[wasm_bindgen(catch)]
|
#[wasm_bindgen(catch)]
|
||||||
fn handleResponse(responseType: String, responseData: JsValue) -> Result<(), JsValue>;
|
fn handleResponse(responseType: String, responseData: JsValue) -> Result<(), JsValue>;
|
||||||
|
|||||||
Reference in New Issue
Block a user