mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-04-30 03:01:54 +00:00
Even further app fixes (#9617)
- Allow dragging zoom pane with middle click - Prevent selection of canvasOnly widgets - These widgets would not display in app mode, so allow selection would only cause confusion. - Support displaying the error dialogue in app mode - Add a somewhat involved mobile app mode error indication system <img width="300" alt="image" src="https://github.com/user-attachments/assets/d8793bbd-fff5-4b2a-a316-6ff154bae2c4" /> <img width="300" alt="image" src="https://github.com/user-attachments/assets/cb88b0f6-f7e5-409e-ae43-f1348f946b19" /> ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-9617-Even-further-app-fixes-31d6d73d365081c891dfdfe3477cfd61) by [Unito](https://www.unito.io)
This commit is contained in:
@@ -160,7 +160,7 @@ function handleClick(e: MouseEvent) {
|
|||||||
else appModeStore.selectedOutputs.splice(index, 1)
|
else appModeStore.selectedOutputs.splice(index, 1)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if (!isSelectInputsMode.value) return
|
if (!isSelectInputsMode.value || widget.options.canvasOnly) return
|
||||||
|
|
||||||
const index = appModeStore.selectedInputs.findIndex(
|
const index = appModeStore.selectedInputs.findIndex(
|
||||||
([nodeId, widgetName]) => node.id == nodeId && widget.name === widgetName
|
([nodeId, widgetName]) => node.id == nodeId && widget.name === widgetName
|
||||||
|
|||||||
51
src/components/common/Dialogue.vue
Normal file
51
src/components/common/Dialogue.vue
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import {
|
||||||
|
DialogClose,
|
||||||
|
DialogContent,
|
||||||
|
DialogOverlay,
|
||||||
|
DialogPortal,
|
||||||
|
DialogRoot,
|
||||||
|
DialogTitle,
|
||||||
|
DialogTrigger
|
||||||
|
} from 'reka-ui'
|
||||||
|
import { useI18n } from 'vue-i18n'
|
||||||
|
|
||||||
|
import Button from '@/components/ui/button/Button.vue'
|
||||||
|
|
||||||
|
defineProps<{ title?: string; to?: string | HTMLElement }>()
|
||||||
|
|
||||||
|
const { t } = useI18n()
|
||||||
|
</script>
|
||||||
|
<template>
|
||||||
|
<DialogRoot v-slot="{ close }">
|
||||||
|
<DialogTrigger as-child>
|
||||||
|
<slot name="button" />
|
||||||
|
</DialogTrigger>
|
||||||
|
<DialogPortal :to>
|
||||||
|
<DialogOverlay
|
||||||
|
class="data-[state=open]:animate-overlayShow fixed inset-0 z-30 bg-black/70"
|
||||||
|
/>
|
||||||
|
<DialogContent
|
||||||
|
v-bind="$attrs"
|
||||||
|
class="data-[state=open]:animate-contentShow fixed top-[50%] left-[50%] z-1700 max-h-[85vh] w-[90vw] max-w-[450px] translate-x-[-50%] translate-y-[-50%] rounded-2xl border border-border-subtle bg-base-background p-2 shadow-sm"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
v-if="title"
|
||||||
|
class="flex w-full items-center justify-between border-b border-border-subtle px-4"
|
||||||
|
>
|
||||||
|
<DialogTitle class="text-sm">{{ title }}</DialogTitle>
|
||||||
|
<DialogClose as-child>
|
||||||
|
<Button
|
||||||
|
:aria-label="t('g.close')"
|
||||||
|
size="icon"
|
||||||
|
variant="muted-textonly"
|
||||||
|
>
|
||||||
|
<i class="icon-[lucide--x]" />
|
||||||
|
</Button>
|
||||||
|
</DialogClose>
|
||||||
|
</div>
|
||||||
|
<slot :close />
|
||||||
|
</DialogContent>
|
||||||
|
</DialogPortal>
|
||||||
|
</DialogRoot>
|
||||||
|
</template>
|
||||||
@@ -50,7 +50,9 @@
|
|||||||
{{ t('g.dismiss') }}
|
{{ t('g.dismiss') }}
|
||||||
</Button>
|
</Button>
|
||||||
<Button variant="secondary" size="lg" @click="seeErrors">
|
<Button variant="secondary" size="lg" @click="seeErrors">
|
||||||
{{ t('errorOverlay.seeErrors') }}
|
{{
|
||||||
|
appMode ? t('linearMode.error.goto') : t('errorOverlay.seeErrors')
|
||||||
|
}}
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -69,6 +71,8 @@ import { useRightSidePanelStore } from '@/stores/workspace/rightSidePanelStore'
|
|||||||
import { useCanvasStore } from '@/renderer/core/canvas/canvasStore'
|
import { useCanvasStore } from '@/renderer/core/canvas/canvasStore'
|
||||||
import { useErrorGroups } from '@/components/rightSidePanel/errors/useErrorGroups'
|
import { useErrorGroups } from '@/components/rightSidePanel/errors/useErrorGroups'
|
||||||
|
|
||||||
|
defineProps<{ appMode?: boolean }>()
|
||||||
|
|
||||||
const { t } = useI18n()
|
const { t } = useI18n()
|
||||||
const executionErrorStore = useExecutionErrorStore()
|
const executionErrorStore = useExecutionErrorStore()
|
||||||
const rightSidePanelStore = useRightSidePanelStore()
|
const rightSidePanelStore = useRightSidePanelStore()
|
||||||
@@ -94,6 +98,7 @@ function dismiss() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function seeErrors() {
|
function seeErrors() {
|
||||||
|
canvasStore.linearMode = false
|
||||||
if (canvasStore.canvas) {
|
if (canvasStore.canvas) {
|
||||||
canvasStore.canvas.deselectAll()
|
canvasStore.canvas.deselectAll()
|
||||||
canvasStore.updateSelectedItems()
|
canvasStore.updateSelectedItems()
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { computed, reactive, ref, watch } from 'vue'
|
import { computed, reactive, ref, toValue, watch } from 'vue'
|
||||||
import type { Ref } from 'vue'
|
import type { MaybeRefOrGetter } from 'vue'
|
||||||
import Fuse from 'fuse.js'
|
import Fuse from 'fuse.js'
|
||||||
import type { IFuseOptions } from 'fuse.js'
|
import type { IFuseOptions } from 'fuse.js'
|
||||||
|
|
||||||
@@ -227,7 +227,7 @@ function searchErrorGroups(groups: ErrorGroup[], query: string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function useErrorGroups(
|
export function useErrorGroups(
|
||||||
searchQuery: Ref<string>,
|
searchQuery: MaybeRefOrGetter<string>,
|
||||||
t: (key: string) => string
|
t: (key: string) => string
|
||||||
) {
|
) {
|
||||||
const executionErrorStore = useExecutionErrorStore()
|
const executionErrorStore = useExecutionErrorStore()
|
||||||
@@ -584,7 +584,7 @@ export function useErrorGroups(
|
|||||||
})
|
})
|
||||||
|
|
||||||
const filteredGroups = computed<ErrorGroup[]>(() => {
|
const filteredGroups = computed<ErrorGroup[]>(() => {
|
||||||
const query = searchQuery.value.trim()
|
const query = toValue(searchQuery).trim()
|
||||||
return searchErrorGroups(tabErrorGroups.value, query)
|
return searchErrorGroups(tabErrorGroups.value, query)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ function handleWheel(e: WheelEvent) {
|
|||||||
|
|
||||||
let dragging = false
|
let dragging = false
|
||||||
function handleDown(e: PointerEvent) {
|
function handleDown(e: PointerEvent) {
|
||||||
if (e.button !== 0) return
|
if (e.button !== 0 && e.button !== 1) return
|
||||||
|
|
||||||
const zoomPaneEl = zoomPane.value
|
const zoomPaneEl = zoomPane.value
|
||||||
if (!zoomPaneEl) return
|
if (!zoomPaneEl) return
|
||||||
|
|||||||
@@ -3180,6 +3180,7 @@
|
|||||||
"cancelThisRun": "Cancel this run",
|
"cancelThisRun": "Cancel this run",
|
||||||
"deleteAllAssets": "Delete all assets from this run",
|
"deleteAllAssets": "Delete all assets from this run",
|
||||||
"hasCreditCost": "Requires additional credits",
|
"hasCreditCost": "Requires additional credits",
|
||||||
|
"viewGraph": "View node graph",
|
||||||
"welcome": {
|
"welcome": {
|
||||||
"title": "App Mode",
|
"title": "App Mode",
|
||||||
"message": "A simplified view that hides the node graph so you can focus on creating.",
|
"message": "A simplified view that hides the node graph so you can focus on creating.",
|
||||||
@@ -3224,6 +3225,19 @@
|
|||||||
"outputPlaceholder": "Output nodes will show up here",
|
"outputPlaceholder": "Output nodes will show up here",
|
||||||
"outputRequiredPlaceholder": "At least one node is required"
|
"outputRequiredPlaceholder": "At least one node is required"
|
||||||
},
|
},
|
||||||
|
"error": {
|
||||||
|
"header": "This app encountered an error",
|
||||||
|
"log": "Error Logs",
|
||||||
|
"mobileFixable": "Check {0} for errors",
|
||||||
|
"requiresGraph": "Something went wrong during generation. This could be due to invalid hidden inputs, missing resources, or workflow configuration issues.",
|
||||||
|
"promptVisitGraph": "View the node graph to see the full error.",
|
||||||
|
"getHelp": "For help, view our {0}, {1}, or {2} with the copied error.",
|
||||||
|
"goto": "Show errors in graph",
|
||||||
|
"github": "submit a GitHub issue",
|
||||||
|
"guide": "troubleshooting guide",
|
||||||
|
"support": "contact our support",
|
||||||
|
"promptShow": "Show error report"
|
||||||
|
},
|
||||||
"queue": {
|
"queue": {
|
||||||
"clickToClear": "Click to clear queue",
|
"clickToClear": "Click to clear queue",
|
||||||
"clear": "Clear queue"
|
"clear": "Clear queue"
|
||||||
|
|||||||
@@ -15,7 +15,9 @@ import { useWorkflowStore } from '@/platform/workflow/management/stores/workflow
|
|||||||
import { useCanvasStore } from '@/renderer/core/canvas/canvasStore'
|
import { useCanvasStore } from '@/renderer/core/canvas/canvasStore'
|
||||||
import LinearControls from '@/renderer/extensions/linearMode/LinearControls.vue'
|
import LinearControls from '@/renderer/extensions/linearMode/LinearControls.vue'
|
||||||
import LinearPreview from '@/renderer/extensions/linearMode/LinearPreview.vue'
|
import LinearPreview from '@/renderer/extensions/linearMode/LinearPreview.vue'
|
||||||
|
import MobileError from '@/renderer/extensions/linearMode/MobileError.vue'
|
||||||
import { useColorPaletteService } from '@/services/colorPaletteService'
|
import { useColorPaletteService } from '@/services/colorPaletteService'
|
||||||
|
import { useExecutionErrorStore } from '@/stores/executionErrorStore'
|
||||||
import { useQueueStore } from '@/stores/queueStore'
|
import { useQueueStore } from '@/stores/queueStore'
|
||||||
import { useMenuItemStore } from '@/stores/menuItemStore'
|
import { useMenuItemStore } from '@/stores/menuItemStore'
|
||||||
import { useColorPaletteStore } from '@/stores/workspace/colorPaletteStore'
|
import { useColorPaletteStore } from '@/stores/workspace/colorPaletteStore'
|
||||||
@@ -31,6 +33,7 @@ const canvasStore = useCanvasStore()
|
|||||||
const colorPaletteService = useColorPaletteService()
|
const colorPaletteService = useColorPaletteService()
|
||||||
const colorPaletteStore = useColorPaletteStore()
|
const colorPaletteStore = useColorPaletteStore()
|
||||||
const { isLoggedIn } = useCurrentUser()
|
const { isLoggedIn } = useCurrentUser()
|
||||||
|
const executionErrorStore = useExecutionErrorStore()
|
||||||
const { t } = useI18n()
|
const { t } = useI18n()
|
||||||
const { commandIdToMenuItem } = useMenuItemStore()
|
const { commandIdToMenuItem } = useMenuItemStore()
|
||||||
const queueStore = useQueueStore()
|
const queueStore = useQueueStore()
|
||||||
@@ -40,7 +43,7 @@ const { toggle: toggleFullscreen } = useFullscreen(undefined, {
|
|||||||
autoExit: true
|
autoExit: true
|
||||||
})
|
})
|
||||||
|
|
||||||
const activeIndex = ref(2)
|
const activeIndex = ref(1)
|
||||||
const sliderPaneRef = useTemplateRef('sliderPaneRef')
|
const sliderPaneRef = useTemplateRef('sliderPaneRef')
|
||||||
const sliderWidth = computed(() => sliderPaneRef.value?.offsetWidth)
|
const sliderWidth = computed(() => sliderPaneRef.value?.offsetWidth)
|
||||||
|
|
||||||
@@ -192,7 +195,11 @@ const menuEntries = computed<MenuItem[]>(() => [
|
|||||||
<div
|
<div
|
||||||
class="absolute top-0 left-[100vw] flex h-full w-screen flex-col bg-base-background"
|
class="absolute top-0 left-[100vw] flex h-full w-screen flex-col bg-base-background"
|
||||||
>
|
>
|
||||||
<LinearPreview mobile />
|
<MobileError
|
||||||
|
v-if="executionErrorStore.isErrorOverlayOpen"
|
||||||
|
@navigate-controls="activeIndex = 0"
|
||||||
|
/>
|
||||||
|
<LinearPreview v-else mobile @navigate-controls="activeIndex = 0" />
|
||||||
</div>
|
</div>
|
||||||
<AssetsSidebarTab
|
<AssetsSidebarTab
|
||||||
class="absolute top-0 left-[200vw] h-full w-screen bg-base-background"
|
class="absolute top-0 left-[200vw] h-full w-screen bg-base-background"
|
||||||
@@ -213,7 +220,11 @@ const menuEntries = computed<MenuItem[]>(() => [
|
|||||||
<div class="relative size-4">
|
<div class="relative size-4">
|
||||||
<i :class="cn('size-4', icon)" />
|
<i :class="cn('size-4', icon)" />
|
||||||
<div
|
<div
|
||||||
v-if="
|
v-if="index === 1 && executionErrorStore.isErrorOverlayOpen"
|
||||||
|
class="absolute -top-1 -right-1 size-2 rounded-full bg-error"
|
||||||
|
/>
|
||||||
|
<div
|
||||||
|
v-else-if="
|
||||||
index === 1 &&
|
index === 1 &&
|
||||||
(queueStore.runningTasks.length > 0 ||
|
(queueStore.runningTasks.length > 0 ||
|
||||||
queueStore.pendingTasks.length > 0)
|
queueStore.pendingTasks.length > 0)
|
||||||
|
|||||||
174
src/renderer/extensions/linearMode/MobileError.vue
Normal file
174
src/renderer/extensions/linearMode/MobileError.vue
Normal file
@@ -0,0 +1,174 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { computed } from 'vue'
|
||||||
|
import { useI18n } from 'vue-i18n'
|
||||||
|
|
||||||
|
import Dialogue from '@/components/common/Dialogue.vue'
|
||||||
|
import { useErrorGroups } from '@/components/rightSidePanel/errors/useErrorGroups'
|
||||||
|
import Button from '@/components/ui/button/Button.vue'
|
||||||
|
import { useAppMode } from '@/composables/useAppMode'
|
||||||
|
import { useCopyToClipboard } from '@/composables/useCopyToClipboard'
|
||||||
|
import { useExternalLink } from '@/composables/useExternalLink'
|
||||||
|
import { buildSupportUrl } from '@/platform/support/config'
|
||||||
|
import { useAppModeStore } from '@/stores/appModeStore'
|
||||||
|
import { useExecutionErrorStore } from '@/stores/executionErrorStore'
|
||||||
|
|
||||||
|
defineEmits<{ navigateControls: [] }>()
|
||||||
|
|
||||||
|
const { t } = useI18n()
|
||||||
|
const appModeStore = useAppModeStore()
|
||||||
|
const { setMode } = useAppMode()
|
||||||
|
const executionErrorStore = useExecutionErrorStore()
|
||||||
|
const { buildDocsUrl, staticUrls } = useExternalLink()
|
||||||
|
const { allErrorGroups } = useErrorGroups('', t)
|
||||||
|
const { copyToClipboard } = useCopyToClipboard()
|
||||||
|
|
||||||
|
const guideUrl = buildDocsUrl('troubleshooting/overview', {
|
||||||
|
includeLocale: true
|
||||||
|
})
|
||||||
|
const supportUrl = buildSupportUrl()
|
||||||
|
|
||||||
|
const inputNodeIds = computed(() => {
|
||||||
|
const ids = new Set()
|
||||||
|
for (const [id] of appModeStore.selectedInputs) ids.add(String(id))
|
||||||
|
return ids
|
||||||
|
})
|
||||||
|
|
||||||
|
const accessibleNodeErrors = computed(() =>
|
||||||
|
Object.keys(executionErrorStore.lastNodeErrors ?? {}).filter((k) =>
|
||||||
|
inputNodeIds.value.has(k)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
const accessibleErrors = computed(() =>
|
||||||
|
accessibleNodeErrors.value.flatMap((k) =>
|
||||||
|
executionErrorStore.lastNodeErrors![k].errors.flatMap((error) => {
|
||||||
|
const { extra_info } = error
|
||||||
|
if (!extra_info) return []
|
||||||
|
|
||||||
|
const selectedInput = appModeStore.selectedInputs.find(
|
||||||
|
([id, name]) => id == k && extra_info.input_name === name
|
||||||
|
)
|
||||||
|
if (!selectedInput) return []
|
||||||
|
|
||||||
|
return [`${selectedInput[1]}: ${error.message}`]
|
||||||
|
})
|
||||||
|
)
|
||||||
|
)
|
||||||
|
const allErrors = computed(() =>
|
||||||
|
allErrorGroups.value.flatMap((group) => {
|
||||||
|
if (group.type !== 'execution') return [group.title]
|
||||||
|
|
||||||
|
return group.cards.flatMap((c) =>
|
||||||
|
c.errors.map((e) =>
|
||||||
|
e.details
|
||||||
|
? `${c.title} (${e.details}): ${e.message}`
|
||||||
|
: `${c.title}: ${e.message}`
|
||||||
|
)
|
||||||
|
)
|
||||||
|
})
|
||||||
|
)
|
||||||
|
|
||||||
|
function copy(obj: unknown) {
|
||||||
|
copyToClipboard(JSON.stringify(obj))
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
<template>
|
||||||
|
<section class="flex h-full flex-col items-center justify-center gap-2 px-4">
|
||||||
|
<i class="icon-[lucide--circle-alert] size-6 bg-error" />
|
||||||
|
{{ t('linearMode.error.header') }}
|
||||||
|
<div class="p-1 text-muted-foreground">
|
||||||
|
<i18n-t
|
||||||
|
v-if="accessibleErrors.length"
|
||||||
|
keypath="linearMode.error.mobileFixable"
|
||||||
|
>
|
||||||
|
<Button @click="$emit('navigateControls')">
|
||||||
|
{{ t('linearMode.mobileControls') }}
|
||||||
|
</Button>
|
||||||
|
</i18n-t>
|
||||||
|
<div v-else class="text-center">
|
||||||
|
<p v-text="t('linearMode.error.requiresGraph')" />
|
||||||
|
<p v-text="t('linearMode.error.promptVisitGraph')" />
|
||||||
|
<p class="*:text-muted-foreground">
|
||||||
|
<i18n-t keypath="linearMode.error.getHelp">
|
||||||
|
<a
|
||||||
|
:href="guideUrl"
|
||||||
|
target="_blank"
|
||||||
|
v-text="t('linearMode.error.guide')"
|
||||||
|
/>
|
||||||
|
<a
|
||||||
|
:href="staticUrls.githubIssues"
|
||||||
|
target="_blank"
|
||||||
|
v-text="t('linearMode.error.github')"
|
||||||
|
/>
|
||||||
|
<a
|
||||||
|
:href="supportUrl"
|
||||||
|
target="_blank"
|
||||||
|
v-text="t('linearMode.error.support')"
|
||||||
|
/>
|
||||||
|
</i18n-t>
|
||||||
|
</p>
|
||||||
|
<Dialogue :title="t('linearMode.error.log')">
|
||||||
|
<template #button>
|
||||||
|
<Button variant="textonly">
|
||||||
|
{{ t('linearMode.error.promptShow') }}
|
||||||
|
<i class="icon-[lucide--chevron-right] size-5" />
|
||||||
|
</Button>
|
||||||
|
</template>
|
||||||
|
<template #default="{ close }">
|
||||||
|
<article class="flex flex-col gap-2 p-4">
|
||||||
|
<section class="flex max-h-[60vh] flex-col gap-2 overflow-y-auto">
|
||||||
|
<div
|
||||||
|
v-for="error in allErrors"
|
||||||
|
:key="error"
|
||||||
|
class="w-full rounded-lg bg-secondary-background p-2 text-muted-foreground"
|
||||||
|
v-text="error"
|
||||||
|
/>
|
||||||
|
</section>
|
||||||
|
<div class="flex items-center justify-end gap-4">
|
||||||
|
<Button variant="muted-textonly" size="lg" @click="close">
|
||||||
|
{{ t('g.close') }}
|
||||||
|
</Button>
|
||||||
|
<Button size="lg" @click="copy(allErrors)">
|
||||||
|
{{ t('importFailed.copyError') }}
|
||||||
|
<i class="icon-[lucide--copy]" />
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</article>
|
||||||
|
</template>
|
||||||
|
</Dialogue>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
v-if="accessibleErrors.length"
|
||||||
|
class="my-8 w-full rounded-lg bg-secondary-background text-muted-foreground"
|
||||||
|
>
|
||||||
|
<ul>
|
||||||
|
<li
|
||||||
|
v-for="error in accessibleErrors"
|
||||||
|
:key="error"
|
||||||
|
class="before:content"
|
||||||
|
v-text="error"
|
||||||
|
/>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
<div class="flex gap-2">
|
||||||
|
<Button
|
||||||
|
variant="textonly"
|
||||||
|
size="lg"
|
||||||
|
@click="executionErrorStore.dismissErrorOverlay()"
|
||||||
|
>
|
||||||
|
{{ t('g.dismiss') }}
|
||||||
|
</Button>
|
||||||
|
<Button variant="textonly" size="lg" @click="setMode('graph')">
|
||||||
|
{{ t('linearMode.viewGraph') }}
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
v-if="accessibleErrors.length"
|
||||||
|
size="lg"
|
||||||
|
@click="copy(accessibleErrors)"
|
||||||
|
>
|
||||||
|
{{ t('importFailed.copyError') }}
|
||||||
|
<i class="icon-[lucide--copy]" />
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</template>
|
||||||
@@ -9,6 +9,7 @@ import { computed, useTemplateRef } from 'vue'
|
|||||||
import AppBuilder from '@/components/builder/AppBuilder.vue'
|
import AppBuilder from '@/components/builder/AppBuilder.vue'
|
||||||
import AppModeToolbar from '@/components/appMode/AppModeToolbar.vue'
|
import AppModeToolbar from '@/components/appMode/AppModeToolbar.vue'
|
||||||
import ExtensionSlot from '@/components/common/ExtensionSlot.vue'
|
import ExtensionSlot from '@/components/common/ExtensionSlot.vue'
|
||||||
|
import ErrorOverlay from '@/components/error/ErrorOverlay.vue'
|
||||||
import TopbarBadges from '@/components/topbar/TopbarBadges.vue'
|
import TopbarBadges from '@/components/topbar/TopbarBadges.vue'
|
||||||
import TopbarSubscribeButton from '@/components/topbar/TopbarSubscribeButton.vue'
|
import TopbarSubscribeButton from '@/components/topbar/TopbarSubscribeButton.vue'
|
||||||
import WorkflowTabs from '@/components/topbar/WorkflowTabs.vue'
|
import WorkflowTabs from '@/components/topbar/WorkflowTabs.vue'
|
||||||
@@ -156,6 +157,7 @@ const linearWorkflowRef = useTemplateRef('linearWorkflowRef')
|
|||||||
</div>
|
</div>
|
||||||
<div ref="bottomLeftRef" class="absolute bottom-7 left-4 z-20" />
|
<div ref="bottomLeftRef" class="absolute bottom-7 left-4 z-20" />
|
||||||
<div ref="bottomRightRef" class="absolute right-4 bottom-7 z-20" />
|
<div ref="bottomRightRef" class="absolute right-4 bottom-7 z-20" />
|
||||||
|
<div class="absolute top-4 right-4 z-20"><ErrorOverlay app-mode /></div>
|
||||||
</SplitterPanel>
|
</SplitterPanel>
|
||||||
<SplitterPanel
|
<SplitterPanel
|
||||||
v-if="hasRightPanel"
|
v-if="hasRightPanel"
|
||||||
|
|||||||
Reference in New Issue
Block a user