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
This commit is contained in:
Keavon Chambers
2022-04-30 02:52:53 -07:00
parent 92ee3bbad3
commit 07736a9fca
28 changed files with 640 additions and 622 deletions
+24 -27
View File
@@ -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);
}
});
},
@@ -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();
});
@@ -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) => {
@@ -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);
});
});
+23 -14
View File
@@ -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'"
@@ -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) {
@@ -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;
@@ -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) {
@@ -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() {
@@ -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[];
@@ -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();
});
};
@@ -626,6 +634,7 @@ export default defineComponent({
} else {
this.layerCache.set(targetPath.toString(), targetLayer);
}
this.setBlendModeForSelectedLayers();
this.setOpacityForSelectedLayers();
});
@@ -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` -->
@@ -69,7 +69,7 @@ export default defineComponent({
handleClick() {
(this.$refs.floatingMenu as typeof FloatingMenu).setOpen();
if (this.action) this.action();
this.action?.();
},
},
});
@@ -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;
@@ -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;
@@ -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>
@@ -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;
@@ -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();
@@ -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;
},
},
@@ -19,9 +19,9 @@
<span class="entry-label">{{ entry.label }}</span>
<IconLabel v-if="entry.shortcutRequiresLock && !fullscreen.state.keyboardLocked" :icon="'Info'" :title="keyboardLockInfoMessage" />
<UserInputLabel v-else-if="entry.shortcut && entry.shortcut.length" :inputKeys="[entry.shortcut]" />
<UserInputLabel v-else-if="entry.shortcut?.length" :inputKeys="[entry.shortcut]" />
<div class="submenu-arrow" v-if="entry.children && entry.children.length"></div>
<div class="submenu-arrow" v-if="entry.children?.length"></div>
<div class="no-submenu-arrow" v-else></div>
<MenuList
@@ -175,10 +175,10 @@ const MenuList = defineComponent({
scrollableY: { type: Boolean as PropType<boolean>, default: false },
},
methods: {
setEntryRefs(menuEntry: MenuListEntry, ref: typeof FloatingMenu) {
setEntryRefs(menuEntry: MenuListEntry, ref: typeof FloatingMenu): void {
if (ref) menuEntry.ref = ref;
},
handleEntryClick(menuEntry: MenuListEntry) {
handleEntryClick(menuEntry: MenuListEntry): void {
(this.$refs.floatingMenu as typeof FloatingMenu).setClosed();
if (menuEntry.checkbox) menuEntry.checked = !menuEntry.checked;
@@ -188,20 +188,20 @@ const MenuList = defineComponent({
this.$emit("update:activeEntry", menuEntry);
},
handleEntryPointerEnter(menuEntry: MenuListEntry) {
if (!menuEntry.children || !menuEntry.children.length) return;
handleEntryPointerEnter(menuEntry: MenuListEntry): void {
if (!menuEntry.children?.length) return;
if (menuEntry.ref) menuEntry.ref.setOpen();
else throw new Error("The menu bar floating menu has no associated ref");
},
handleEntryPointerLeave(menuEntry: MenuListEntry) {
if (!menuEntry.children || !menuEntry.children.length) return;
handleEntryPointerLeave(menuEntry: MenuListEntry): void {
if (!menuEntry.children?.length) return;
if (menuEntry.ref) menuEntry.ref.setClosed();
else throw new Error("The menu bar floating menu has no associated ref");
},
isMenuEntryOpen(menuEntry: MenuListEntry): boolean {
if (!menuEntry.children || !menuEntry.children.length) return false;
if (!menuEntry.children?.length) return false;
if (menuEntry.ref) return menuEntry.ref.isOpen();
@@ -215,7 +215,7 @@ const MenuList = defineComponent({
},
isOpen(): boolean {
const floatingMenu = this.$refs.floatingMenu as typeof FloatingMenu;
return Boolean(floatingMenu && floatingMenu.isOpen());
return Boolean(floatingMenu?.isOpen());
},
async measureAndReportWidth() {
// API is experimental but supported in all browsers - https://developer.mozilla.org/en-US/docs/Web/API/FontFaceSet/ready
@@ -242,10 +242,8 @@ const MenuList = defineComponent({
},
computed: {
menuEntriesWithoutRefs(): MenuListEntryData[][] {
const { menuEntries } = this;
return menuEntries.map((entries) =>
return this.menuEntries.map((entries) =>
entries.map((entry) => {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const { ref, ...entryWithoutRef } = entry;
return entryWithoutRef;
})
@@ -112,34 +112,45 @@ export default defineComponent({
if (!this.disabled) (this.$refs.menuList as typeof MenuList).setOpen();
},
selectFont(newName: string) {
if (this.isStyle) this.$emit("update:fontStyle", newName);
else this.$emit("update:fontFamily", newName);
let fontFamily;
let fontStyle;
{
const fontFamily = this.isStyle ? this.fontFamily : newName;
const fontStyle = this.isStyle ? newName : getFontStyles(newName)[0];
const fontFile = getFontFile(fontFamily, fontStyle);
this.$emit("changeFont", { fontFamily, fontStyle, fontFile });
if (this.isStyle) {
this.$emit("update:fontStyle", newName);
fontFamily = this.fontFamily;
fontStyle = newName;
} else {
this.$emit("update:fontFamily", newName);
fontFamily = newName;
fontStyle = getFontStyles(newName)[0];
}
const fontFile = getFontFile(fontFamily, fontStyle);
this.$emit("changeFont", { fontFamily, fontStyle, fontFile });
},
onWidthChanged(newWidth: number) {
this.minWidth = newWidth;
},
updateEntries(): { menuEntries: SectionsOfMenuListEntries; activeEntry: MenuListEntry } {
let selectedIndex = -1;
const menuEntries: SectionsOfMenuListEntries = [
(this.isStyle ? getFontStyles(this.fontFamily) : fontNames()).map((name, index) => {
if (name === (this.isStyle ? this.fontStyle : this.fontFamily)) selectedIndex = index;
const choices = this.isStyle ? getFontStyles(this.fontFamily) : fontNames();
const selectedChoice = this.isStyle ? this.fontStyle : this.fontFamily;
const result: MenuListEntry = {
label: name,
action: (): void => this.selectFont(name),
};
return result;
}),
];
let selectedEntry: MenuListEntry | undefined;
const entries = choices.map((name) => {
const result: MenuListEntry = {
label: name,
action: (): void => this.selectFont(name),
};
const activeEntry = selectedIndex < 0 ? { label: "-" } : menuEntries.flat()[selectedIndex];
if (name === selectedChoice) selectedEntry = result;
return result;
});
const menuEntries: SectionsOfMenuListEntries = [entries];
const activeEntry = selectedEntry || { label: "-" };
return { menuEntries, activeEntry };
},
@@ -6,7 +6,7 @@
</div>
</div>
<div class="entry-container" v-for="(entry, index) in menuEntries" :key="index">
<div @click="() => handleEntryClick(entry)" class="entry" :class="{ open: entry.ref && entry.ref.isOpen() }" data-hover-menu-spawner>
<div @click="() => handleEntryClick(entry)" class="entry" :class="{ open: entry.ref?.isOpen() }" data-hover-menu-spawner>
<IconLabel :icon="entry.icon" v-if="entry.icon" />
<span v-if="entry.label">{{ entry.label }}</span>
</div>
@@ -152,7 +152,7 @@ export default defineComponent({
onIncrement(direction: IncrementDirection) {
if (Number.isNaN(this.value)) return;
({
const actions = {
Add: (): void => {
const directionAddend = direction === "Increase" ? this.incrementFactor : -this.incrementFactor;
this.updateValue(this.value + directionAddend);
@@ -162,11 +162,13 @@ export default defineComponent({
this.updateValue(this.value * directionMultiplier);
},
Callback: (): void => {
if (direction === "Increase" && this.incrementCallbackIncrease) this.incrementCallbackIncrease();
if (direction === "Decrease" && this.incrementCallbackDecrease) this.incrementCallbackDecrease();
if (direction === "Increase") this.incrementCallbackIncrease?.();
if (direction === "Decrease") this.incrementCallbackDecrease?.();
},
None: (): void => undefined,
}[this.incrementBehavior]());
};
const action = actions[this.incrementBehavior];
action();
},
updateValue(newValue: number) {
const invalid = Number.isNaN(newValue);
@@ -91,7 +91,7 @@ export default defineComponent({
const index = this.entries.indexOf(menuEntry);
this.$emit("update:selectedIndex", index);
if (menuEntry.action) menuEntry.action();
menuEntry.action?.();
},
},
components: {
@@ -27,14 +27,12 @@
</style>
<script lang="ts">
import { DefineComponent, defineComponent, PropType } from "vue";
import { defineComponent, PropType } from "vue";
import { IconName, IconSize, ICON_LIST } from "@/utilities/icons";
import { IconName, icons } from "@/utilities/icons";
import LayoutRow from "@/components/layout/LayoutRow.vue";
const icons: Record<IconName, { component: DefineComponent; size: IconSize }> = ICON_LIST;
export default defineComponent({
props: {
icon: { type: String as PropType<IconName>, required: true },
@@ -47,6 +45,7 @@ export default defineComponent({
},
components: {
LayoutRow,
// Import the components of all the icons
...Object.fromEntries(Object.entries(icons).map(([name, data]) => [name, data.component])),
},
});
@@ -52,7 +52,7 @@ const MINOR_MARK_THICKNESS = 3;
export type RulerDirection = "Horizontal" | "Vertical";
// Apparently the modulo operator in js does not work properly.
// Modulo function that works for negative numbers, unlike the JS % operator
const mod = (n: number, m: number): number => {
const remainder = n % m;
return Math.floor(remainder >= 0 ? remainder : remainder + m);
@@ -190,9 +190,7 @@ export default defineComponent({
this.dragging = false;
},
pointerMove(e: PointerEvent) {
if (this.dragging) {
this.updateHandlePosition(e);
}
if (this.dragging) this.updateHandlePosition(e);
},
changePosition(difference: number) {
this.clampHandlePosition(this.handlePosition + difference / this.trackLength());
+3 -3
View File
@@ -8,11 +8,11 @@
data-tab
v-for="(tabLabel, tabIndex) in tabLabels"
:key="tabIndex"
@click="(e) => (e && e.stopPropagation(), clickAction && clickAction(tabIndex))"
@click.middle="(e) => (e && e.stopPropagation(), closeAction && closeAction(tabIndex))"
@click="(e) => (e?.stopPropagation(), clickAction?.(tabIndex))"
@click.middle="(e) => (e?.stopPropagation(), closeAction?.(tabIndex))"
>
<span>{{ tabLabel }}</span>
<IconButton :action="(e) => (e && e.stopPropagation(), closeAction && closeAction(tabIndex))" :icon="'CloseX'" :size="16" v-if="tabCloseButtons" />
<IconButton :action="(e) => (e?.stopPropagation(), closeAction?.(tabIndex))" :icon="'CloseX'" :size="16" v-if="tabCloseButtons" />
</LayoutRow>
</LayoutRow>
<PopoverButton :icon="'VerticalEllipsis'">
@@ -7,18 +7,8 @@
:tabCloseButtons="true"
:tabMinWidths="true"
:tabLabels="documents.state.documents.map((doc) => doc.displayName)"
:clickAction="
(tabIndex) => {
const targetId = documents.state.documents[tabIndex].id;
editor.instance.select_document(targetId);
}
"
:closeAction="
(tabIndex) => {
const targetId = documents.state.documents[tabIndex].id;
editor.instance.close_document_with_confirmation(targetId);
}
"
:clickAction="(tabIndex) => editor.instance.select_document(documents.state.documents[tabIndex].id)"
:closeAction="(tabIndex) => editor.instance.close_document_with_confirmation(documents.state.documents[tabIndex].id)"
:tabActiveIndex="documents.state.activeDocumentIndex"
ref="documentsPanel"
/>