Skip to content

Tidy up the full frontend codebase and use optional chaining where possible #620

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Apr 30, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion frontend/.eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ module.exports = {
"linebreak-style": ["error", "unix"],
"eol-last": ["error", "always"],
"max-len": ["error", { code: 200, tabWidth: 4 }],
"prefer-destructuring": "off",
"no-console": "warn",
"no-debugger": "warn",
"no-param-reassign": ["error", { props: false }],
Expand All @@ -71,7 +72,7 @@ module.exports = {
"@typescript-eslint/indent": "off",
"@typescript-eslint/camelcase": "off",
"@typescript-eslint/no-use-before-define": "off",
"@typescript-eslint/no-unused-vars": ["error", { argsIgnorePattern: "^_" }],
"@typescript-eslint/no-unused-vars": ["error", { argsIgnorePattern: "^_", ignoreRestSiblings: true }],
"@typescript-eslint/explicit-function-return-type": ["error"],

// Import plugin config (used to intelligently validate module import statements)
Expand Down
7 changes: 2 additions & 5 deletions frontend/src/App.vue
Original file line number Diff line number Diff line change
Expand Up @@ -318,11 +318,8 @@ export default defineComponent({
this.inputManager = createInputManager(this.editor, this.$el.parentElement, this.dialog, this.documents, this.fullscreen);
},
beforeUnmount() {
const { inputManager } = this;
if (inputManager) inputManager.removeListeners();

const { editor } = this;
editor.instance.free();
this.inputManager?.removeListeners();
this.editor.instance.free();
},
components: {
MainWindow,
Expand Down
51 changes: 24 additions & 27 deletions frontend/src/components/panels/Document.vue
Original file line number Diff line number Diff line change
Expand Up @@ -329,22 +329,21 @@ export default defineComponent({

const rulerHorizontal = this.$refs.rulerHorizontal as typeof CanvasRuler;
const rulerVertical = this.$refs.rulerVertical as typeof CanvasRuler;
if (rulerHorizontal) rulerHorizontal.handleResize();
if (rulerVertical) rulerVertical.handleResize();
rulerHorizontal?.handleResize();
rulerVertical?.handleResize();
},
pasteFile(e: DragEvent) {
const { dataTransfer } = e;
if (!dataTransfer) return;
e.preventDefault();

Array.from(dataTransfer.items).forEach((item) => {
Array.from(dataTransfer.items).forEach(async (item) => {
const file = item.getAsFile();
if (file && file.type.startsWith("image")) {
file.arrayBuffer().then((buffer): void => {
const u8Array = new Uint8Array(buffer);
if (file?.type.startsWith("image")) {
const buffer = await file.arrayBuffer();
const u8Array = new Uint8Array(buffer);

this.editor.instance.paste_image(file.type, u8Array, e.clientX, e.clientY);
});
this.editor.instance.paste_image(file.type, u8Array, e.clientX, e.clientY);
}
});
},
Expand Down Expand Up @@ -400,11 +399,13 @@ export default defineComponent({

const range = document.createRange();
range.selectNodeContents(addedInput);

const selection = window.getSelection();
if (selection) {
selection.removeAllRanges();
selection.addRange(range);
}

addedInput.focus();
addedInput.click();
});
Expand Down Expand Up @@ -455,24 +456,20 @@ export default defineComponent({
this.canvasCursor = updateMouseCursor.cursor;
});
this.editor.dispatcher.subscribeJsMessage(TriggerTextCommit, () => {
if (this.textInput) this.editor.instance.on_change_text(textInputCleanup(this.textInput.innerText));
if (this.textInput) {
const textCleaned = textInputCleanup(this.textInput.innerText);
this.editor.instance.on_change_text(textCleaned);
}
});
this.editor.dispatcher.subscribeJsMessage(TriggerFontLoad, (triggerFontLoad) => {
fetch(triggerFontLoad.font)
.then((response) => response.arrayBuffer())
.then((response) => {
this.editor.instance.on_font_load(triggerFontLoad.font, new Uint8Array(response), false);
});
this.editor.dispatcher.subscribeJsMessage(TriggerFontLoad, async (triggerFontLoad) => {
const response = await fetch(triggerFontLoad.font);
const responseBuffer = await response.arrayBuffer();
this.editor.instance.on_font_load(triggerFontLoad.font, new Uint8Array(responseBuffer), false);
});
this.editor.dispatcher.subscribeJsMessage(TriggerDefaultFontLoad, loadDefaultFont);
this.editor.dispatcher.subscribeJsMessage(TriggerTextCopy, async (triggerTextCopy) => {
// Clipboard API supported?
if (!navigator.clipboard) return;

// copy text to clipboard
if (navigator.clipboard.writeText) {
await navigator.clipboard.writeText(triggerTextCopy.copy_text);
}
this.editor.dispatcher.subscribeJsMessage(TriggerTextCopy, (triggerTextCopy) => {
// If the Clipboard API is supported in the browser, copy text to the clipboard
navigator.clipboard?.writeText?.(triggerTextCopy.copy_text);
});

this.editor.dispatcher.subscribeJsMessage(DisplayEditableTextbox, (displayEditableTextbox) => {
Expand Down Expand Up @@ -511,15 +508,15 @@ export default defineComponent({
this.editor.dispatcher.subscribeJsMessage(TriggerViewportResize, this.viewportResize);

this.editor.dispatcher.subscribeJsMessage(UpdateImageData, (updateImageData) => {
updateImageData.image_data.forEach((element) => {
updateImageData.image_data.forEach(async (element) => {
// Using updateImageData.image_data.buffer returns undefined for some reason?
const blob = new Blob([new Uint8Array(element.image_data.values()).buffer], { type: element.mime });

const url = URL.createObjectURL(blob);

createImageBitmap(blob).then((image) => {
this.editor.instance.set_image_blob_url(element.path, url, image.width, image.height);
});
const image = await createImageBitmap(blob);

this.editor.instance.set_image_blob_url(element.path, url, image.width, image.height);
});
});

Expand Down
37 changes: 23 additions & 14 deletions frontend/src/components/panels/LayerTree.vue
Original file line number Diff line number Diff line change
Expand Up @@ -42,11 +42,11 @@
class="layer-row"
v-for="(listing, index) in layers"
:key="String(listing.entry.path.slice(-1))"
:class="{ 'insert-folder': draggingData && draggingData.highlightFolder && draggingData.insertFolder === listing.entry.path }"
:class="{ 'insert-folder': draggingData?.highlightFolder && draggingData?.insertFolder === listing.entry.path }"
>
<LayoutRow class="visibility">
<IconButton
:action="(e) => (toggleLayerVisibility(listing.entry.path), e && e.stopPropagation())"
:action="(e) => (toggleLayerVisibility(listing.entry.path), e?.stopPropagation())"
:size="24"
:icon="listing.entry.visible ? 'EyeVisible' : 'EyeHidden'"
:title="listing.entry.visible ? 'Visible' : 'Hidden'"
Expand Down Expand Up @@ -396,16 +396,16 @@ export default defineComponent({
markTopOffset(height: number): string {
return `${height}px`;
},
async createEmptyFolder() {
createEmptyFolder() {
this.editor.instance.create_empty_folder();
},
async deleteSelectedLayers() {
deleteSelectedLayers() {
this.editor.instance.delete_selected_layers();
},
async toggleLayerVisibility(path: BigUint64Array) {
toggleLayerVisibility(path: BigUint64Array) {
this.editor.instance.toggle_layer_visibility(path);
},
async handleExpandArrowClick(path: BigUint64Array) {
handleExpandArrowClick(path: BigUint64Array) {
this.editor.instance.toggle_layer_expansion(path);
},
onEditLayerName(listing: LayerListingInfo) {
Expand All @@ -414,12 +414,12 @@ export default defineComponent({
this.draggable = false;

listing.editingName = true;
const tree = (this.$refs.layerTreeList as typeof LayoutCol).$el as HTMLElement;
const tree: HTMLElement = (this.$refs.layerTreeList as typeof LayoutCol).$el;
this.$nextTick(() => {
(tree.querySelector("[data-text-input]:not([disabled])") as HTMLInputElement).select();
});
},
async onEditLayerNameChange(listing: LayerListingInfo, inputElement: EventTarget | null) {
onEditLayerNameChange(listing: LayerListingInfo, inputElement: EventTarget | null) {
// Eliminate duplicate events
if (!listing.editingName) return;

Expand All @@ -434,8 +434,7 @@ export default defineComponent({

listing.editingName = false;
this.$nextTick(() => {
const selection = window.getSelection();
if (selection) selection.removeAllRanges();
window.getSelection()?.removeAllRanges();
});
},
async setLayerBlendMode(newSelectedIndex: number) {
Expand Down Expand Up @@ -536,7 +535,7 @@ export default defineComponent({
// Stop the drag from being shown as cancelled
event.preventDefault();

const tree = (this.$refs.layerTreeList as typeof LayoutCol).$el as HTMLElement;
const tree: HTMLElement = (this.$refs.layerTreeList as typeof LayoutCol).$el;
this.draggingData = this.calculateDragIndex(tree, event.clientY);
},
async drop() {
Expand Down Expand Up @@ -594,8 +593,8 @@ export default defineComponent({
mounted() {
this.editor.dispatcher.subscribeJsMessage(DisplayDocumentLayerTreeStructure, (displayDocumentLayerTreeStructure) => {
const layerWithNameBeingEdited = this.layers.find((layer: LayerListingInfo) => layer.editingName);
const layerPathWithNameBeingEdited = layerWithNameBeingEdited && layerWithNameBeingEdited.entry.path;
const layerIdWithNameBeingEdited = layerPathWithNameBeingEdited && layerPathWithNameBeingEdited.slice(-1)[0];
const layerPathWithNameBeingEdited = layerWithNameBeingEdited?.entry.path;
const layerIdWithNameBeingEdited = layerPathWithNameBeingEdited?.slice(-1)[0];
const path = [] as bigint[];
this.layers = [] as LayerListingInfo[];

Expand All @@ -606,9 +605,18 @@ export default defineComponent({
path.push(layerId);

const mapping = cache.get(path.toString());
if (mapping) layers.push({ folderIndex: index, bottomLayer: index === folder.children.length - 1, entry: mapping, editingName: layerIdWithNameBeingEdited === layerId });
if (mapping) {
layers.push({
folderIndex: index,
bottomLayer: index === folder.children.length - 1,
entry: mapping,
editingName: layerIdWithNameBeingEdited === layerId,
});
}

// Call self recursively if there are any children
if (item.children.length >= 1) recurse(item, layers, cache);

path.pop();
});
};
Expand All @@ -626,6 +634,7 @@ export default defineComponent({
} else {
this.layerCache.set(targetPath.toString(), targetLayer);
}

this.setBlendModeForSelectedLayers();
this.setOpacityForSelectedLayers();
});
Expand Down
1 change: 0 additions & 1 deletion frontend/src/components/widgets/WidgetRow.vue
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
<template>
<div>{{ widgetData.name }}</div>
<div class="widget-row">
<template v-for="(component, index) in widgetData.widgets" :key="index">
<!-- TODO: Use `<component :is="" v-bind="attributesObject"></component>` to avoid all the separate components with `v-if` -->
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/components/widgets/buttons/PopoverButton.vue
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ export default defineComponent({
handleClick() {
(this.$refs.floatingMenu as typeof FloatingMenu).setOpen();

if (this.action) this.action();
this.action?.();
},
},
});
Expand Down
12 changes: 6 additions & 6 deletions frontend/src/components/widgets/floating-menus/ColorPicker.vue
Original file line number Diff line number Diff line change
Expand Up @@ -161,13 +161,13 @@ export default defineComponent({
},
onPointerDown(e: PointerEvent) {
const saturationPicker = this.$refs.saturationPicker as typeof LayoutCol;
const saturationPickerElement = saturationPicker && (saturationPicker.$el as HTMLElement);
const saturationPickerElement = saturationPicker?.$el as HTMLElement | undefined;

const huePicker = this.$refs.huePicker as typeof LayoutCol;
const huePickerElement = huePicker && (huePicker.$el as HTMLElement);
const huePickerElement = huePicker?.$el as HTMLElement | undefined;

const opacityPicker = this.$refs.opacityPicker as typeof LayoutCol;
const opacityPickerElement = opacityPicker && (opacityPicker.$el as HTMLElement);
const opacityPickerElement = opacityPicker?.$el as HTMLElement | undefined;

if (!(e.currentTarget instanceof HTMLElement) || !saturationPickerElement || !huePickerElement || !opacityPickerElement) return;

Expand Down Expand Up @@ -216,13 +216,13 @@ export default defineComponent({
},
updateRects() {
const saturationPicker = this.$refs.saturationPicker as typeof LayoutCol;
const saturationPickerElement = saturationPicker && (saturationPicker.$el as HTMLElement);
const saturationPickerElement = saturationPicker?.$el as HTMLElement | undefined;

const huePicker = this.$refs.huePicker as typeof LayoutCol;
const huePickerElement = huePicker && (huePicker.$el as HTMLElement);
const huePickerElement = huePicker?.$el as HTMLElement | undefined;

const opacityPicker = this.$refs.opacityPicker as typeof LayoutCol;
const opacityPickerElement = opacityPicker && (opacityPicker.$el as HTMLElement);
const opacityPickerElement = opacityPicker?.$el as HTMLElement | undefined;

if (!saturationPickerElement || !huePickerElement || !opacityPickerElement) return;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
<TextLabel :bold="true" class="heading">{{ dialog.state.heading }}</TextLabel>
<TextLabel class="details">{{ dialog.state.details }}</TextLabel>
<LayoutRow class="buttons-row" v-if="dialog.state.buttons.length > 0">
<TextButton v-for="(button, index) in dialog.state.buttons" :key="index" :title="button.tooltip" :action="() => button.callback && button.callback()" v-bind="button.props" />
<TextButton v-for="(button, index) in dialog.state.buttons" :key="index" :title="button.tooltip" :action="() => button.callback?.()" v-bind="button.props" />
</LayoutRow>
</LayoutCol>
</LayoutRow>
Expand Down
20 changes: 10 additions & 10 deletions frontend/src/components/widgets/floating-menus/FloatingMenu.vue
Original file line number Diff line number Diff line change
Expand Up @@ -212,7 +212,7 @@ export default defineComponent({
const workspace = document.querySelector("[data-workspace]");
const floatingMenuContainer = this.$refs.floatingMenuContainer as HTMLElement;
const floatingMenuContentComponent = this.$refs.floatingMenuContent as typeof LayoutCol;
const floatingMenuContent = floatingMenuContentComponent && (floatingMenuContentComponent.$el as HTMLElement);
const floatingMenuContent: HTMLElement | undefined = floatingMenuContentComponent?.$el;
const floatingMenu = this.$refs.floatingMenu as HTMLElement;

if (!workspace || !floatingMenuContainer || !floatingMenuContentComponent || !floatingMenuContent || !floatingMenu) return;
Expand Down Expand Up @@ -298,30 +298,28 @@ export default defineComponent({
},
getWidth(callback: (width: number) => void) {
this.$nextTick(() => {
const floatingMenuContent = (this.$refs.floatingMenuContent as typeof LayoutCol).$el as HTMLElement;
const floatingMenuContent: HTMLElement = (this.$refs.floatingMenuContent as typeof LayoutCol).$el;
const width = floatingMenuContent.clientWidth;
callback(width);
});
},
disableMinWidth(callback: (minWidth: string) => void) {
this.$nextTick(() => {
const floatingMenuContent = (this.$refs.floatingMenuContent as typeof LayoutCol).$el as HTMLElement;
const floatingMenuContent: HTMLElement = (this.$refs.floatingMenuContent as typeof LayoutCol).$el;
const initialMinWidth = floatingMenuContent.style.minWidth;
floatingMenuContent.style.minWidth = "0";
callback(initialMinWidth);
});
},
enableMinWidth(minWidth: string) {
const floatingMenuContent = (this.$refs.floatingMenuContent as typeof LayoutCol).$el as HTMLElement;
const floatingMenuContent: HTMLElement = (this.$refs.floatingMenuContent as typeof LayoutCol).$el;
floatingMenuContent.style.minWidth = minWidth;
},
pointerMoveHandler(e: PointerEvent) {
const target = e.target as HTMLElement;
const pointerOverFloatingMenuKeepOpen = target && (target.closest("[data-hover-menu-keep-open]") as HTMLElement);
const pointerOverFloatingMenuSpawner = target && (target.closest("[data-hover-menu-spawner]") as HTMLElement);
// TODO: Simplify the following expression when optional chaining is supported by the build system
const pointerOverOwnFloatingMenuSpawner =
pointerOverFloatingMenuSpawner && pointerOverFloatingMenuSpawner.parentElement && pointerOverFloatingMenuSpawner.parentElement.contains(this.$refs.floatingMenu as HTMLElement);
const target = e.target as HTMLElement | undefined;
const pointerOverFloatingMenuKeepOpen = target?.closest("[data-hover-menu-keep-open]") as HTMLElement | undefined;
const pointerOverFloatingMenuSpawner = target?.closest("[data-hover-menu-spawner]") as HTMLElement | undefined;
const pointerOverOwnFloatingMenuSpawner = pointerOverFloatingMenuSpawner?.parentElement?.contains(this.$refs.floatingMenu as HTMLElement);
// Swap this open floating menu with the one created by the floating menu spawner being hovered over
if (pointerOverFloatingMenuSpawner && !pointerOverOwnFloatingMenuSpawner) {
this.setClosed();
Expand Down Expand Up @@ -372,10 +370,12 @@ export default defineComponent({
},
isPointerEventOutsideMenuElement(e: PointerEvent, element: HTMLElement, extraDistanceAllowed = 0): boolean {
const floatingMenuBounds = element.getBoundingClientRect();

if (floatingMenuBounds.left - e.clientX >= extraDistanceAllowed) return true;
if (e.clientX - floatingMenuBounds.right >= extraDistanceAllowed) return true;
if (floatingMenuBounds.top - e.clientY >= extraDistanceAllowed) return true;
if (e.clientY - floatingMenuBounds.bottom >= extraDistanceAllowed) return true;

return false;
},
},
Expand Down
Loading