Skip to content

Commit 07736a9

Browse files
committed
Tidy up the full frontend codebase and use optional chaining where possible (#620)
* Tidy up the full frontend codebase and use optional chaining where possible * Code review changes
1 parent 92ee3bb commit 07736a9

28 files changed

+643
-625
lines changed

frontend/.eslintrc.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@ module.exports = {
5959
"linebreak-style": ["error", "unix"],
6060
"eol-last": ["error", "always"],
6161
"max-len": ["error", { code: 200, tabWidth: 4 }],
62+
"prefer-destructuring": "off",
6263
"no-console": "warn",
6364
"no-debugger": "warn",
6465
"no-param-reassign": ["error", { props: false }],
@@ -71,7 +72,7 @@ module.exports = {
7172
"@typescript-eslint/indent": "off",
7273
"@typescript-eslint/camelcase": "off",
7374
"@typescript-eslint/no-use-before-define": "off",
74-
"@typescript-eslint/no-unused-vars": ["error", { argsIgnorePattern: "^_" }],
75+
"@typescript-eslint/no-unused-vars": ["error", { argsIgnorePattern: "^_", ignoreRestSiblings: true }],
7576
"@typescript-eslint/explicit-function-return-type": ["error"],
7677

7778
// Import plugin config (used to intelligently validate module import statements)

frontend/src/App.vue

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -318,11 +318,8 @@ export default defineComponent({
318318
this.inputManager = createInputManager(this.editor, this.$el.parentElement, this.dialog, this.documents, this.fullscreen);
319319
},
320320
beforeUnmount() {
321-
const { inputManager } = this;
322-
if (inputManager) inputManager.removeListeners();
323-
324-
const { editor } = this;
325-
editor.instance.free();
321+
this.inputManager?.removeListeners();
322+
this.editor.instance.free();
326323
},
327324
components: {
328325
MainWindow,

frontend/src/components/panels/Document.vue

Lines changed: 24 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -329,22 +329,21 @@ export default defineComponent({
329329
330330
const rulerHorizontal = this.$refs.rulerHorizontal as typeof CanvasRuler;
331331
const rulerVertical = this.$refs.rulerVertical as typeof CanvasRuler;
332-
if (rulerHorizontal) rulerHorizontal.handleResize();
333-
if (rulerVertical) rulerVertical.handleResize();
332+
rulerHorizontal?.handleResize();
333+
rulerVertical?.handleResize();
334334
},
335335
pasteFile(e: DragEvent) {
336336
const { dataTransfer } = e;
337337
if (!dataTransfer) return;
338338
e.preventDefault();
339339
340-
Array.from(dataTransfer.items).forEach((item) => {
340+
Array.from(dataTransfer.items).forEach(async (item) => {
341341
const file = item.getAsFile();
342-
if (file && file.type.startsWith("image")) {
343-
file.arrayBuffer().then((buffer): void => {
344-
const u8Array = new Uint8Array(buffer);
342+
if (file?.type.startsWith("image")) {
343+
const buffer = await file.arrayBuffer();
344+
const u8Array = new Uint8Array(buffer);
345345
346-
this.editor.instance.paste_image(file.type, u8Array, e.clientX, e.clientY);
347-
});
346+
this.editor.instance.paste_image(file.type, u8Array, e.clientX, e.clientY);
348347
}
349348
});
350349
},
@@ -400,11 +399,13 @@ export default defineComponent({
400399
401400
const range = document.createRange();
402401
range.selectNodeContents(addedInput);
402+
403403
const selection = window.getSelection();
404404
if (selection) {
405405
selection.removeAllRanges();
406406
selection.addRange(range);
407407
}
408+
408409
addedInput.focus();
409410
addedInput.click();
410411
});
@@ -455,24 +456,20 @@ export default defineComponent({
455456
this.canvasCursor = updateMouseCursor.cursor;
456457
});
457458
this.editor.dispatcher.subscribeJsMessage(TriggerTextCommit, () => {
458-
if (this.textInput) this.editor.instance.on_change_text(textInputCleanup(this.textInput.innerText));
459+
if (this.textInput) {
460+
const textCleaned = textInputCleanup(this.textInput.innerText);
461+
this.editor.instance.on_change_text(textCleaned);
462+
}
459463
});
460-
this.editor.dispatcher.subscribeJsMessage(TriggerFontLoad, (triggerFontLoad) => {
461-
fetch(triggerFontLoad.font)
462-
.then((response) => response.arrayBuffer())
463-
.then((response) => {
464-
this.editor.instance.on_font_load(triggerFontLoad.font, new Uint8Array(response), false);
465-
});
464+
this.editor.dispatcher.subscribeJsMessage(TriggerFontLoad, async (triggerFontLoad) => {
465+
const response = await fetch(triggerFontLoad.font);
466+
const responseBuffer = await response.arrayBuffer();
467+
this.editor.instance.on_font_load(triggerFontLoad.font, new Uint8Array(responseBuffer), false);
466468
});
467469
this.editor.dispatcher.subscribeJsMessage(TriggerDefaultFontLoad, loadDefaultFont);
468-
this.editor.dispatcher.subscribeJsMessage(TriggerTextCopy, async (triggerTextCopy) => {
469-
// Clipboard API supported?
470-
if (!navigator.clipboard) return;
471-
472-
// copy text to clipboard
473-
if (navigator.clipboard.writeText) {
474-
await navigator.clipboard.writeText(triggerTextCopy.copy_text);
475-
}
470+
this.editor.dispatcher.subscribeJsMessage(TriggerTextCopy, (triggerTextCopy) => {
471+
// If the Clipboard API is supported in the browser, copy text to the clipboard
472+
navigator.clipboard?.writeText?.(triggerTextCopy.copy_text);
476473
});
477474
478475
this.editor.dispatcher.subscribeJsMessage(DisplayEditableTextbox, (displayEditableTextbox) => {
@@ -511,15 +508,15 @@ export default defineComponent({
511508
this.editor.dispatcher.subscribeJsMessage(TriggerViewportResize, this.viewportResize);
512509
513510
this.editor.dispatcher.subscribeJsMessage(UpdateImageData, (updateImageData) => {
514-
updateImageData.image_data.forEach((element) => {
511+
updateImageData.image_data.forEach(async (element) => {
515512
// Using updateImageData.image_data.buffer returns undefined for some reason?
516513
const blob = new Blob([new Uint8Array(element.image_data.values()).buffer], { type: element.mime });
517514
518515
const url = URL.createObjectURL(blob);
519516
520-
createImageBitmap(blob).then((image) => {
521-
this.editor.instance.set_image_blob_url(element.path, url, image.width, image.height);
522-
});
517+
const image = await createImageBitmap(blob);
518+
519+
this.editor.instance.set_image_blob_url(element.path, url, image.width, image.height);
523520
});
524521
});
525522

frontend/src/components/panels/LayerTree.vue

Lines changed: 23 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -42,11 +42,11 @@
4242
class="layer-row"
4343
v-for="(listing, index) in layers"
4444
:key="String(listing.entry.path.slice(-1))"
45-
:class="{ 'insert-folder': draggingData && draggingData.highlightFolder && draggingData.insertFolder === listing.entry.path }"
45+
:class="{ 'insert-folder': draggingData?.highlightFolder && draggingData?.insertFolder === listing.entry.path }"
4646
>
4747
<LayoutRow class="visibility">
4848
<IconButton
49-
:action="(e) => (toggleLayerVisibility(listing.entry.path), e && e.stopPropagation())"
49+
:action="(e) => (toggleLayerVisibility(listing.entry.path), e?.stopPropagation())"
5050
:size="24"
5151
:icon="listing.entry.visible ? 'EyeVisible' : 'EyeHidden'"
5252
:title="listing.entry.visible ? 'Visible' : 'Hidden'"
@@ -396,16 +396,16 @@ export default defineComponent({
396396
markTopOffset(height: number): string {
397397
return `${height}px`;
398398
},
399-
async createEmptyFolder() {
399+
createEmptyFolder() {
400400
this.editor.instance.create_empty_folder();
401401
},
402-
async deleteSelectedLayers() {
402+
deleteSelectedLayers() {
403403
this.editor.instance.delete_selected_layers();
404404
},
405-
async toggleLayerVisibility(path: BigUint64Array) {
405+
toggleLayerVisibility(path: BigUint64Array) {
406406
this.editor.instance.toggle_layer_visibility(path);
407407
},
408-
async handleExpandArrowClick(path: BigUint64Array) {
408+
handleExpandArrowClick(path: BigUint64Array) {
409409
this.editor.instance.toggle_layer_expansion(path);
410410
},
411411
onEditLayerName(listing: LayerListingInfo) {
@@ -414,12 +414,12 @@ export default defineComponent({
414414
this.draggable = false;
415415
416416
listing.editingName = true;
417-
const tree = (this.$refs.layerTreeList as typeof LayoutCol).$el as HTMLElement;
417+
const tree: HTMLElement = (this.$refs.layerTreeList as typeof LayoutCol).$el;
418418
this.$nextTick(() => {
419419
(tree.querySelector("[data-text-input]:not([disabled])") as HTMLInputElement).select();
420420
});
421421
},
422-
async onEditLayerNameChange(listing: LayerListingInfo, inputElement: EventTarget | null) {
422+
onEditLayerNameChange(listing: LayerListingInfo, inputElement: EventTarget | null) {
423423
// Eliminate duplicate events
424424
if (!listing.editingName) return;
425425
@@ -434,8 +434,7 @@ export default defineComponent({
434434
435435
listing.editingName = false;
436436
this.$nextTick(() => {
437-
const selection = window.getSelection();
438-
if (selection) selection.removeAllRanges();
437+
window.getSelection()?.removeAllRanges();
439438
});
440439
},
441440
async setLayerBlendMode(newSelectedIndex: number) {
@@ -536,7 +535,7 @@ export default defineComponent({
536535
// Stop the drag from being shown as cancelled
537536
event.preventDefault();
538537
539-
const tree = (this.$refs.layerTreeList as typeof LayoutCol).$el as HTMLElement;
538+
const tree: HTMLElement = (this.$refs.layerTreeList as typeof LayoutCol).$el;
540539
this.draggingData = this.calculateDragIndex(tree, event.clientY);
541540
},
542541
async drop() {
@@ -594,8 +593,8 @@ export default defineComponent({
594593
mounted() {
595594
this.editor.dispatcher.subscribeJsMessage(DisplayDocumentLayerTreeStructure, (displayDocumentLayerTreeStructure) => {
596595
const layerWithNameBeingEdited = this.layers.find((layer: LayerListingInfo) => layer.editingName);
597-
const layerPathWithNameBeingEdited = layerWithNameBeingEdited && layerWithNameBeingEdited.entry.path;
598-
const layerIdWithNameBeingEdited = layerPathWithNameBeingEdited && layerPathWithNameBeingEdited.slice(-1)[0];
596+
const layerPathWithNameBeingEdited = layerWithNameBeingEdited?.entry.path;
597+
const layerIdWithNameBeingEdited = layerPathWithNameBeingEdited?.slice(-1)[0];
599598
const path = [] as bigint[];
600599
this.layers = [] as LayerListingInfo[];
601600
@@ -606,9 +605,18 @@ export default defineComponent({
606605
path.push(layerId);
607606
608607
const mapping = cache.get(path.toString());
609-
if (mapping) layers.push({ folderIndex: index, bottomLayer: index === folder.children.length - 1, entry: mapping, editingName: layerIdWithNameBeingEdited === layerId });
608+
if (mapping) {
609+
layers.push({
610+
folderIndex: index,
611+
bottomLayer: index === folder.children.length - 1,
612+
entry: mapping,
613+
editingName: layerIdWithNameBeingEdited === layerId,
614+
});
615+
}
610616
617+
// Call self recursively if there are any children
611618
if (item.children.length >= 1) recurse(item, layers, cache);
619+
612620
path.pop();
613621
});
614622
};
@@ -626,6 +634,7 @@ export default defineComponent({
626634
} else {
627635
this.layerCache.set(targetPath.toString(), targetLayer);
628636
}
637+
629638
this.setBlendModeForSelectedLayers();
630639
this.setOpacityForSelectedLayers();
631640
});

frontend/src/components/widgets/WidgetRow.vue

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
<template>
2-
<div>{{ widgetData.name }}</div>
32
<div class="widget-row">
43
<template v-for="(component, index) in widgetData.widgets" :key="index">
54
<!-- TODO: Use `<component :is="" v-bind="attributesObject"></component>` to avoid all the separate components with `v-if` -->

frontend/src/components/widgets/buttons/PopoverButton.vue

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ export default defineComponent({
6969
handleClick() {
7070
(this.$refs.floatingMenu as typeof FloatingMenu).setOpen();
7171
72-
if (this.action) this.action();
72+
this.action?.();
7373
},
7474
},
7575
});

frontend/src/components/widgets/floating-menus/ColorPicker.vue

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -161,13 +161,13 @@ export default defineComponent({
161161
},
162162
onPointerDown(e: PointerEvent) {
163163
const saturationPicker = this.$refs.saturationPicker as typeof LayoutCol;
164-
const saturationPickerElement = saturationPicker && (saturationPicker.$el as HTMLElement);
164+
const saturationPickerElement = saturationPicker?.$el as HTMLElement | undefined;
165165
166166
const huePicker = this.$refs.huePicker as typeof LayoutCol;
167-
const huePickerElement = huePicker && (huePicker.$el as HTMLElement);
167+
const huePickerElement = huePicker?.$el as HTMLElement | undefined;
168168
169169
const opacityPicker = this.$refs.opacityPicker as typeof LayoutCol;
170-
const opacityPickerElement = opacityPicker && (opacityPicker.$el as HTMLElement);
170+
const opacityPickerElement = opacityPicker?.$el as HTMLElement | undefined;
171171
172172
if (!(e.currentTarget instanceof HTMLElement) || !saturationPickerElement || !huePickerElement || !opacityPickerElement) return;
173173
@@ -216,13 +216,13 @@ export default defineComponent({
216216
},
217217
updateRects() {
218218
const saturationPicker = this.$refs.saturationPicker as typeof LayoutCol;
219-
const saturationPickerElement = saturationPicker && (saturationPicker.$el as HTMLElement);
219+
const saturationPickerElement = saturationPicker?.$el as HTMLElement | undefined;
220220
221221
const huePicker = this.$refs.huePicker as typeof LayoutCol;
222-
const huePickerElement = huePicker && (huePicker.$el as HTMLElement);
222+
const huePickerElement = huePicker?.$el as HTMLElement | undefined;
223223
224224
const opacityPicker = this.$refs.opacityPicker as typeof LayoutCol;
225-
const opacityPickerElement = opacityPicker && (opacityPicker.$el as HTMLElement);
225+
const opacityPickerElement = opacityPicker?.$el as HTMLElement | undefined;
226226
227227
if (!saturationPickerElement || !huePickerElement || !opacityPickerElement) return;
228228

frontend/src/components/widgets/floating-menus/DialogModal.vue

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
<TextLabel :bold="true" class="heading">{{ dialog.state.heading }}</TextLabel>
1010
<TextLabel class="details">{{ dialog.state.details }}</TextLabel>
1111
<LayoutRow class="buttons-row" v-if="dialog.state.buttons.length > 0">
12-
<TextButton v-for="(button, index) in dialog.state.buttons" :key="index" :title="button.tooltip" :action="() => button.callback && button.callback()" v-bind="button.props" />
12+
<TextButton v-for="(button, index) in dialog.state.buttons" :key="index" :title="button.tooltip" :action="() => button.callback?.()" v-bind="button.props" />
1313
</LayoutRow>
1414
</LayoutCol>
1515
</LayoutRow>

frontend/src/components/widgets/floating-menus/FloatingMenu.vue

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -212,7 +212,7 @@ export default defineComponent({
212212
const workspace = document.querySelector("[data-workspace]");
213213
const floatingMenuContainer = this.$refs.floatingMenuContainer as HTMLElement;
214214
const floatingMenuContentComponent = this.$refs.floatingMenuContent as typeof LayoutCol;
215-
const floatingMenuContent = floatingMenuContentComponent && (floatingMenuContentComponent.$el as HTMLElement);
215+
const floatingMenuContent: HTMLElement | undefined = floatingMenuContentComponent?.$el;
216216
const floatingMenu = this.$refs.floatingMenu as HTMLElement;
217217
218218
if (!workspace || !floatingMenuContainer || !floatingMenuContentComponent || !floatingMenuContent || !floatingMenu) return;
@@ -298,30 +298,28 @@ export default defineComponent({
298298
},
299299
getWidth(callback: (width: number) => void) {
300300
this.$nextTick(() => {
301-
const floatingMenuContent = (this.$refs.floatingMenuContent as typeof LayoutCol).$el as HTMLElement;
301+
const floatingMenuContent: HTMLElement = (this.$refs.floatingMenuContent as typeof LayoutCol).$el;
302302
const width = floatingMenuContent.clientWidth;
303303
callback(width);
304304
});
305305
},
306306
disableMinWidth(callback: (minWidth: string) => void) {
307307
this.$nextTick(() => {
308-
const floatingMenuContent = (this.$refs.floatingMenuContent as typeof LayoutCol).$el as HTMLElement;
308+
const floatingMenuContent: HTMLElement = (this.$refs.floatingMenuContent as typeof LayoutCol).$el;
309309
const initialMinWidth = floatingMenuContent.style.minWidth;
310310
floatingMenuContent.style.minWidth = "0";
311311
callback(initialMinWidth);
312312
});
313313
},
314314
enableMinWidth(minWidth: string) {
315-
const floatingMenuContent = (this.$refs.floatingMenuContent as typeof LayoutCol).$el as HTMLElement;
315+
const floatingMenuContent: HTMLElement = (this.$refs.floatingMenuContent as typeof LayoutCol).$el;
316316
floatingMenuContent.style.minWidth = minWidth;
317317
},
318318
pointerMoveHandler(e: PointerEvent) {
319-
const target = e.target as HTMLElement;
320-
const pointerOverFloatingMenuKeepOpen = target && (target.closest("[data-hover-menu-keep-open]") as HTMLElement);
321-
const pointerOverFloatingMenuSpawner = target && (target.closest("[data-hover-menu-spawner]") as HTMLElement);
322-
// TODO: Simplify the following expression when optional chaining is supported by the build system
323-
const pointerOverOwnFloatingMenuSpawner =
324-
pointerOverFloatingMenuSpawner && pointerOverFloatingMenuSpawner.parentElement && pointerOverFloatingMenuSpawner.parentElement.contains(this.$refs.floatingMenu as HTMLElement);
319+
const target = e.target as HTMLElement | undefined;
320+
const pointerOverFloatingMenuKeepOpen = target?.closest("[data-hover-menu-keep-open]") as HTMLElement | undefined;
321+
const pointerOverFloatingMenuSpawner = target?.closest("[data-hover-menu-spawner]") as HTMLElement | undefined;
322+
const pointerOverOwnFloatingMenuSpawner = pointerOverFloatingMenuSpawner?.parentElement?.contains(this.$refs.floatingMenu as HTMLElement);
325323
// Swap this open floating menu with the one created by the floating menu spawner being hovered over
326324
if (pointerOverFloatingMenuSpawner && !pointerOverOwnFloatingMenuSpawner) {
327325
this.setClosed();
@@ -372,10 +370,12 @@ export default defineComponent({
372370
},
373371
isPointerEventOutsideMenuElement(e: PointerEvent, element: HTMLElement, extraDistanceAllowed = 0): boolean {
374372
const floatingMenuBounds = element.getBoundingClientRect();
373+
375374
if (floatingMenuBounds.left - e.clientX >= extraDistanceAllowed) return true;
376375
if (e.clientX - floatingMenuBounds.right >= extraDistanceAllowed) return true;
377376
if (floatingMenuBounds.top - e.clientY >= extraDistanceAllowed) return true;
378377
if (e.clientY - floatingMenuBounds.bottom >= extraDistanceAllowed) return true;
378+
379379
return false;
380380
},
381381
},

0 commit comments

Comments
 (0)