mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-03-30 01:05:46 +00:00
Compare commits
3 Commits
main
...
austin/app
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
145ae4d656 | ||
|
|
0bf58aa241 | ||
|
|
2787a79c9f |
@@ -108,6 +108,13 @@ export class AppModeHelper {
|
||||
return this.page.locator('[data-testid="linear-widgets"]')
|
||||
}
|
||||
|
||||
get centerPanel(): Locator {
|
||||
return this.page.getByTestId(TestIds.linear.centerPanel)
|
||||
}
|
||||
get mobileView(): Locator {
|
||||
return this.page.getByTestId(TestIds.linear.mobile)
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the actions menu trigger for a widget in the app mode widget list.
|
||||
* @param widgetName Text shown in the widget label (e.g. "seed").
|
||||
|
||||
@@ -69,6 +69,13 @@ export const TestIds = {
|
||||
domWidgetTextarea: 'dom-widget-textarea',
|
||||
subgraphEnterButton: 'subgraph-enter-button'
|
||||
},
|
||||
linear: {
|
||||
centerPanel: 'linear-center-panel',
|
||||
mobile: 'linear-mobile',
|
||||
mobileNavigation: 'linear-mobile-navigation',
|
||||
outputInfo: 'linear-output-info',
|
||||
widgetContainer: 'linear-widgets'
|
||||
},
|
||||
builder: {
|
||||
ioItem: 'builder-io-item',
|
||||
ioItemTitle: 'builder-io-item-title',
|
||||
|
||||
60
browser_tests/tests/appMode.spec.ts
Normal file
60
browser_tests/tests/appMode.spec.ts
Normal file
@@ -0,0 +1,60 @@
|
||||
import {
|
||||
comfyPageFixture as test,
|
||||
comfyExpect as expect
|
||||
} from '../fixtures/ComfyPage'
|
||||
import { TestIds } from '../fixtures/selectors'
|
||||
|
||||
test.describe('App mode usage', () => {
|
||||
test('Drag and Drop', async ({ comfyPage }) => {
|
||||
const { centerPanel } = comfyPage.appMode
|
||||
await comfyPage.appMode.enterAppModeWithInputs([['3', 'seed']])
|
||||
await expect(centerPanel).toBeVisible()
|
||||
//an app without an image input will load the workflow
|
||||
await comfyPage.dragDrop.dragAndDropFile('workflowInMedia/workflow.webp')
|
||||
await expect(centerPanel).not.toBeVisible()
|
||||
|
||||
//prep a load image
|
||||
await comfyPage.dragDrop.dragAndDropURL('/assets/images/og-image.png')
|
||||
const loadImage = await comfyPage.vueNodes.getNodeLocator('12')
|
||||
await expect(loadImage).toBeVisible()
|
||||
|
||||
await comfyPage.appMode.enterAppModeWithInputs([['12', 'image']])
|
||||
await expect(centerPanel).toBeVisible()
|
||||
//an app with an image input will upload the image to the input
|
||||
await comfyPage.dragDrop.dragAndDropFile('workflowInMedia/workflow.webp')
|
||||
await expect(centerPanel).toBeVisible()
|
||||
//an app with an image input can load from a uri-source
|
||||
await comfyPage.dragDrop.dragAndDropURL('/assets/images/og-image.png')
|
||||
await expect(centerPanel).toBeVisible()
|
||||
})
|
||||
test('Widget Interaction', async ({ comfyPage }) => {
|
||||
await comfyPage.appMode.enterAppModeWithInputs([
|
||||
['4', 'seed'],
|
||||
['4', 'sampler_name'],
|
||||
['6', 'text']
|
||||
])
|
||||
})
|
||||
test.describe('Mobile', { tag: ['@mobile'] }, () => {
|
||||
test('smoke', async ({ comfyPage }) => {
|
||||
const { mobileView } = comfyPage.appMode
|
||||
await comfyPage.appMode.enterAppModeWithInputs([['3', 'seed']])
|
||||
await expect(mobileView).toBeVisible()
|
||||
|
||||
const navigation = comfyPage.page
|
||||
.getByRole('tablist')
|
||||
.filter({ hasText: 'Assets' })
|
||||
const panel = comfyPage.page.getByRole('tabpanel')
|
||||
await expect(navigation).toBeVisible()
|
||||
const buttons = await navigation.getByRole('tab').all()
|
||||
await buttons[2].click()
|
||||
await expect(panel).toContainClass('left-[200vw]')
|
||||
//expect
|
||||
await buttons[0].dragTo(buttons[2], { steps: 5 })
|
||||
await expect(panel).toContainClass('left-[100vw]')
|
||||
|
||||
await navigation.getByRole('tab', { name: 'Edit & Run' }).click()
|
||||
const widgets = mobileView.getByTestId(TestIds.linear.widgetContainer)
|
||||
await expect(widgets).toBeInViewport({ ratio: 1 })
|
||||
})
|
||||
})
|
||||
})
|
||||
128
browser_tests/tests/appModeBuilder.spec.ts
Normal file
128
browser_tests/tests/appModeBuilder.spec.ts
Normal file
@@ -0,0 +1,128 @@
|
||||
import {
|
||||
comfyPageFixture as test,
|
||||
comfyExpect as expect
|
||||
} from '../fixtures/ComfyPage'
|
||||
import { TestIds } from '../fixtures/selectors'
|
||||
|
||||
test.describe('App mode builder selection', () => {
|
||||
test.beforeEach(async ({ comfyPage }) => {
|
||||
await comfyPage.page.evaluate(() => {
|
||||
window.app!.api.serverFeatureFlags.value = {
|
||||
...window.app!.api.serverFeatureFlags.value,
|
||||
linear_toggle_enabled: true
|
||||
}
|
||||
})
|
||||
await comfyPage.settings.setSetting('Comfy.UseNewMenu', 'Top')
|
||||
await comfyPage.settings.setSetting(
|
||||
'Comfy.AppBuilder.VueNodeSwitchDismissed',
|
||||
true
|
||||
)
|
||||
})
|
||||
|
||||
test('Can independently select inputs of same name', async ({
|
||||
comfyPage
|
||||
}) => {
|
||||
const items = comfyPage.page.getByTestId(TestIds.builder.ioItem)
|
||||
|
||||
await comfyPage.vueNodes.selectNodes(['6', '7'])
|
||||
await comfyPage.command.executeCommand('Comfy.Graph.ConvertToSubgraph')
|
||||
|
||||
await comfyPage.appMode.enterBuilder()
|
||||
await comfyPage.appMode.goToInputs()
|
||||
await expect(items).toHaveCount(0)
|
||||
|
||||
const prompts = await comfyPage.vueNodes
|
||||
.getNodeByTitle('New Subgraph')
|
||||
.locator('.lg-node-widget')
|
||||
const count = await prompts.count()
|
||||
for (let i = 0; i < count; i++) {
|
||||
await expect(prompts.nth(i)).toBeVisible()
|
||||
await prompts.nth(i).click()
|
||||
await expect(items).toHaveCount(i + 1)
|
||||
}
|
||||
})
|
||||
test('Can drag and drop inputs', async ({ comfyPage }) => {
|
||||
const items = comfyPage.page.getByTestId(TestIds.builder.ioItem)
|
||||
await comfyPage.appMode.enterBuilder()
|
||||
await comfyPage.appMode.goToInputs()
|
||||
await expect(items).toHaveCount(0)
|
||||
|
||||
const ksampler = await comfyPage.vueNodes.getNodeLocator('3')
|
||||
for (const widget of await ksampler.locator('.lg-node-widget').all())
|
||||
await widget.click()
|
||||
|
||||
await items.first().dragTo(items.last(), { steps: 5 })
|
||||
await expect(items.first()).toContainText('steps')
|
||||
await items.last().dragTo(items.first(), { steps: 5 })
|
||||
//dragTo doesn't cross the center point, so denoise is moved to position 2
|
||||
await expect(items.nth(1)).toContainText('denoise')
|
||||
})
|
||||
test('Can select outputs', async ({ comfyPage }) => {
|
||||
await comfyPage.appMode.enterBuilder()
|
||||
await comfyPage.appMode.goToOutputs()
|
||||
|
||||
await comfyPage.nodeOps
|
||||
.getNodeRefById('9')
|
||||
.then((ref) => ref.centerOnNode())
|
||||
const saveImage = await comfyPage.vueNodes.getNodeLocator('9')
|
||||
await saveImage.click()
|
||||
|
||||
const items = comfyPage.page.getByTestId(TestIds.builder.ioItem)
|
||||
await expect(items).toHaveCount(1)
|
||||
})
|
||||
test('Can not select nodes with errors or notes', async ({ comfyPage }) => {
|
||||
const items = comfyPage.page.getByTestId(TestIds.builder.ioItem)
|
||||
await comfyPage.appMode.enterBuilder()
|
||||
await comfyPage.appMode.goToInputs()
|
||||
await expect(items).toHaveCount(0)
|
||||
|
||||
await comfyPage.vueNodes
|
||||
.getNodeLocator('4')
|
||||
.locator('.lg-node-widget')
|
||||
.click()
|
||||
await expect.soft(items).toHaveCount(0)
|
||||
|
||||
await comfyPage.workflow.loadWorkflow('nodes/note_nodes')
|
||||
await comfyPage.appMode.enterBuilder()
|
||||
await comfyPage.appMode.goToInputs()
|
||||
await expect(items).toHaveCount(0)
|
||||
await comfyPage.vueNodes
|
||||
.getNodeLocator('1')
|
||||
.locator('.lg-node-widget')
|
||||
.click({ force: true })
|
||||
await comfyPage.vueNodes
|
||||
.getNodeLocator('2')
|
||||
.locator('.lg-node-widget')
|
||||
.click({ force: true })
|
||||
await expect(items).toHaveCount(0)
|
||||
})
|
||||
test('Marks canvas readOnly', async ({ comfyPage }) => {
|
||||
await comfyPage.settings.setSetting(
|
||||
'Comfy.NodeSearchBoxImpl',
|
||||
'v1 (legacy)'
|
||||
)
|
||||
|
||||
await comfyPage.page.mouse.dblclick(100, 100, { delay: 5 })
|
||||
await expect(comfyPage.searchBox.input).toHaveCount(1)
|
||||
await comfyPage.page.keyboard.press('Escape')
|
||||
|
||||
await comfyPage.appMode.enterBuilder()
|
||||
await comfyPage.appMode.goToInputs()
|
||||
|
||||
await comfyPage.page.mouse.dblclick(100, 100, { delay: 5 })
|
||||
await expect(comfyPage.searchBox.input).toHaveCount(0)
|
||||
|
||||
//space toggles panning mode, canvas should remain readOnly after pressing
|
||||
await comfyPage.page.keyboard.press('Space')
|
||||
await comfyPage.page.mouse.dblclick(100, 100, { delay: 5 })
|
||||
await expect(comfyPage.searchBox.input).toHaveCount(0)
|
||||
|
||||
const ksampler = await comfyPage.vueNodes.getFixtureByTitle('KSampler')
|
||||
await ksampler.header.dblclick({ force: true })
|
||||
expect(ksampler.titleInput).not.toBeVisible()
|
||||
|
||||
await comfyPage.page.keyboard.press('Escape')
|
||||
await comfyPage.page.mouse.dblclick(100, 100, { delay: 5 })
|
||||
await expect(comfyPage.searchBox.input).toHaveCount(1)
|
||||
})
|
||||
})
|
||||
@@ -139,43 +139,6 @@ describe('useFeatureFlags', () => {
|
||||
})
|
||||
})
|
||||
|
||||
describe('linearToggleEnabled', () => {
|
||||
it('should return true when isNightly is true', () => {
|
||||
vi.mocked(distributionTypes).isNightly = true
|
||||
|
||||
const { flags } = useFeatureFlags()
|
||||
expect(flags.linearToggleEnabled).toBe(true)
|
||||
expect(api.getServerFeature).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('should check remote config and server feature when isNightly is false', () => {
|
||||
vi.mocked(distributionTypes).isNightly = false
|
||||
vi.mocked(api.getServerFeature).mockImplementation(
|
||||
(path, defaultValue) => {
|
||||
if (path === ServerFeatureFlag.LINEAR_TOGGLE_ENABLED) return true
|
||||
return defaultValue
|
||||
}
|
||||
)
|
||||
|
||||
const { flags } = useFeatureFlags()
|
||||
expect(flags.linearToggleEnabled).toBe(true)
|
||||
expect(api.getServerFeature).toHaveBeenCalledWith(
|
||||
ServerFeatureFlag.LINEAR_TOGGLE_ENABLED,
|
||||
false
|
||||
)
|
||||
})
|
||||
|
||||
it('should return false when isNightly is false and flag is disabled', () => {
|
||||
vi.mocked(distributionTypes).isNightly = false
|
||||
vi.mocked(api.getServerFeature).mockImplementation(
|
||||
(_path, defaultValue) => defaultValue
|
||||
)
|
||||
|
||||
const { flags } = useFeatureFlags()
|
||||
expect(flags.linearToggleEnabled).toBe(false)
|
||||
})
|
||||
})
|
||||
|
||||
describe('dev override via localStorage', () => {
|
||||
afterEach(() => {
|
||||
localStorage.clear()
|
||||
|
||||
@@ -19,7 +19,6 @@ export enum ServerFeatureFlag {
|
||||
ASSET_RENAME_ENABLED = 'asset_rename_enabled',
|
||||
PRIVATE_MODELS_ENABLED = 'private_models_enabled',
|
||||
ONBOARDING_SURVEY_ENABLED = 'onboarding_survey_enabled',
|
||||
LINEAR_TOGGLE_ENABLED = 'linear_toggle_enabled',
|
||||
TEAM_WORKSPACES_ENABLED = 'team_workspaces_enabled',
|
||||
USER_SECRETS_ENABLED = 'user_secrets_enabled',
|
||||
NODE_REPLACEMENTS = 'node_replacements',
|
||||
@@ -84,15 +83,6 @@ export function useFeatureFlags() {
|
||||
false
|
||||
)
|
||||
},
|
||||
get linearToggleEnabled() {
|
||||
if (isNightly) return true
|
||||
|
||||
return resolveFlag(
|
||||
ServerFeatureFlag.LINEAR_TOGGLE_ENABLED,
|
||||
remoteConfig.value.linear_toggle_enabled,
|
||||
false
|
||||
)
|
||||
},
|
||||
/**
|
||||
* Whether team workspaces feature is enabled.
|
||||
* IMPORTANT: Returns false until authenticated remote config is loaded.
|
||||
|
||||
@@ -36,10 +36,6 @@ const mockSubgraphStore = vi.hoisted(() => ({
|
||||
isSubgraphBlueprint: vi.fn(() => false)
|
||||
}))
|
||||
|
||||
const mockMenuItemStore = vi.hoisted(() => ({
|
||||
hasSeenLinear: false
|
||||
}))
|
||||
|
||||
const mockAppModeStore = vi.hoisted(() => ({
|
||||
enterBuilder: vi.fn(),
|
||||
pruneLinearData: vi.fn(
|
||||
@@ -78,10 +74,6 @@ vi.mock('@/stores/subgraphStore', () => ({
|
||||
useSubgraphStore: vi.fn(() => mockSubgraphStore)
|
||||
}))
|
||||
|
||||
vi.mock('@/stores/menuItemStore', () => ({
|
||||
useMenuItemStore: vi.fn(() => mockMenuItemStore)
|
||||
}))
|
||||
|
||||
vi.mock('@/stores/appModeStore', () => ({
|
||||
useAppModeStore: vi.fn(() => mockAppModeStore)
|
||||
}))
|
||||
@@ -116,7 +108,6 @@ describe('useWorkflowActionsMenu', () => {
|
||||
vi.clearAllMocks()
|
||||
mockBookmarkStore.isBookmarked.mockReturnValue(false)
|
||||
mockSubgraphStore.isSubgraphBlueprint.mockReturnValue(false)
|
||||
mockMenuItemStore.hasSeenLinear = false
|
||||
mockFeatureFlags.flags.linearToggleEnabled = false
|
||||
mockAppModeStore.selectedInputs.length = 0
|
||||
mockAppModeStore.selectedOutputs.length = 0
|
||||
@@ -161,43 +152,13 @@ describe('useWorkflowActionsMenu', () => {
|
||||
expect(labels).not.toContain('breadcrumbsMenu.deleteWorkflow')
|
||||
})
|
||||
|
||||
it('shows app mode items when linearToggleEnabled flag is set', () => {
|
||||
mockFeatureFlags.flags.linearToggleEnabled = true
|
||||
|
||||
it('shows app mode items', () => {
|
||||
const { menuItems } = useWorkflowActionsMenu(vi.fn(), { isRoot: true })
|
||||
const labels = menuLabels(menuItems.value)
|
||||
|
||||
expect(labels).toContain('breadcrumbsMenu.enterAppMode')
|
||||
})
|
||||
|
||||
it('shows app mode items when user has seen linear mode', () => {
|
||||
mockMenuItemStore.hasSeenLinear = true
|
||||
|
||||
const { menuItems } = useWorkflowActionsMenu(vi.fn(), { isRoot: true })
|
||||
const labels = menuLabels(menuItems.value)
|
||||
|
||||
expect(labels).toContain('breadcrumbsMenu.enterAppMode')
|
||||
})
|
||||
|
||||
it('hides app mode items when conditions not met', () => {
|
||||
mockMenuItemStore.hasSeenLinear = false
|
||||
mockFeatureFlags.flags.linearToggleEnabled = false
|
||||
|
||||
const { menuItems } = useWorkflowActionsMenu(vi.fn(), { isRoot: true })
|
||||
const labels = menuLabels(menuItems.value)
|
||||
|
||||
expect(labels).not.toContain('breadcrumbsMenu.enterAppMode')
|
||||
})
|
||||
|
||||
it('hides app mode items when not root', () => {
|
||||
mockFeatureFlags.flags.linearToggleEnabled = true
|
||||
|
||||
const { menuItems } = useWorkflowActionsMenu(vi.fn(), { isRoot: false })
|
||||
const labels = menuLabels(menuItems.value)
|
||||
|
||||
expect(labels).not.toContain('breadcrumbsMenu.enterAppMode')
|
||||
})
|
||||
|
||||
it('shows "go to workflow mode" when in linear mode', () => {
|
||||
mockFeatureFlags.flags.linearToggleEnabled = true
|
||||
mockWorkflowStore.activeWorkflow = {
|
||||
|
||||
@@ -13,7 +13,6 @@ import {
|
||||
useWorkflowStore
|
||||
} from '@/platform/workflow/management/stores/workflowStore'
|
||||
import { useCommandStore } from '@/stores/commandStore'
|
||||
import { useMenuItemStore } from '@/stores/menuItemStore'
|
||||
import { useSubgraphStore } from '@/stores/subgraphStore'
|
||||
import { useAppModeStore } from '@/stores/appModeStore'
|
||||
import type {
|
||||
@@ -52,7 +51,6 @@ export function useWorkflowActionsMenu(
|
||||
const bookmarkStore = useWorkflowBookmarkStore()
|
||||
const commandStore = useCommandStore()
|
||||
const subgraphStore = useSubgraphStore()
|
||||
const menuItemStore = useMenuItemStore()
|
||||
const { flags } = useFeatureFlags()
|
||||
const appModeStore = useAppModeStore()
|
||||
const { enterBuilder, pruneLinearData } = appModeStore
|
||||
@@ -98,8 +96,6 @@ export function useWorkflowActionsMenu(
|
||||
const workflowMode =
|
||||
workflow?.activeMode ?? workflow?.initialMode ?? 'graph'
|
||||
const isLinearMode = workflowMode === 'app'
|
||||
const showAppModeItems =
|
||||
isRoot && (menuItemStore.hasSeenLinear || flags.linearToggleEnabled)
|
||||
const isBookmarked = bookmarkStore.isBookmarked(workflow?.path ?? '')
|
||||
|
||||
const toggleLinear = async () => {
|
||||
@@ -206,7 +202,7 @@ export function useWorkflowActionsMenu(
|
||||
label: t('breadcrumbsMenu.enterAppMode'),
|
||||
icon: 'icon-[lucide--panels-top-left]',
|
||||
command: toggleLinear,
|
||||
visible: showAppModeItems && !isLinearMode,
|
||||
visible: isRoot && !isLinearMode,
|
||||
prependSeparator: true,
|
||||
isNew: true
|
||||
})
|
||||
@@ -245,7 +241,7 @@ export function useWorkflowActionsMenu(
|
||||
await ensureWorkflowActive(targetWorkflow.value)
|
||||
enterBuilder()
|
||||
},
|
||||
visible: showAppModeItems,
|
||||
visible: isRoot,
|
||||
isNew: true
|
||||
})
|
||||
|
||||
|
||||
@@ -154,7 +154,10 @@ const menuEntries = computed<MenuItem[]>(() => [
|
||||
])
|
||||
</script>
|
||||
<template>
|
||||
<section class="absolute flex size-full flex-col bg-secondary-background">
|
||||
<section
|
||||
class="absolute flex size-full flex-col bg-secondary-background"
|
||||
data-testid="linear-mobile"
|
||||
>
|
||||
<header
|
||||
class="flex h-16 w-full items-center gap-3 border-b border-border-subtle bg-base-background px-4 py-3"
|
||||
>
|
||||
@@ -191,11 +194,17 @@ const menuEntries = computed<MenuItem[]>(() => [
|
||||
"
|
||||
:style="{ translate }"
|
||||
>
|
||||
<div class="absolute h-full w-screen overflow-y-auto contain-size">
|
||||
<div
|
||||
class="absolute h-full w-screen overflow-y-auto contain-size"
|
||||
role="tabpanel"
|
||||
:aria-hidden="activeIndex !== 0"
|
||||
>
|
||||
<LinearControls mobile @navigate-outputs="activeIndex = 1" />
|
||||
</div>
|
||||
<div
|
||||
class="absolute top-0 left-[100vw] flex h-full w-screen flex-col bg-base-background"
|
||||
role="tabpanel"
|
||||
:aria-hidden="activeIndex !== 1"
|
||||
>
|
||||
<MobileError
|
||||
v-if="executionErrorStore.isErrorOverlayOpen"
|
||||
@@ -205,18 +214,23 @@ const menuEntries = computed<MenuItem[]>(() => [
|
||||
</div>
|
||||
<AssetsSidebarTab
|
||||
class="absolute top-0 left-[200vw] h-full w-screen bg-base-background"
|
||||
role="tabpanel"
|
||||
:aria-hidden="activeIndex !== 2"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
ref="sliderPaneRef"
|
||||
class="flex h-22 w-full items-center justify-around gap-4 bg-secondary-background p-4"
|
||||
role="tablist"
|
||||
>
|
||||
<Button
|
||||
v-for="([label, icon], index) in tabs"
|
||||
:key="label"
|
||||
:variant="index === activeIndex ? 'secondary' : 'muted-textonly'"
|
||||
class="h-14 grow flex-col"
|
||||
role="tab"
|
||||
:aria-selected="index === activeIndex"
|
||||
@click="onClick(index)"
|
||||
>
|
||||
<div class="relative size-4">
|
||||
|
||||
@@ -1,26 +1,16 @@
|
||||
import { whenever } from '@vueuse/core'
|
||||
import { defineStore } from 'pinia'
|
||||
import type { MenuItem } from 'primevue/menuitem'
|
||||
import { ref } from 'vue'
|
||||
|
||||
import { CORE_MENU_COMMANDS } from '@/constants/coreMenuCommands'
|
||||
import { useCanvasStore } from '@/renderer/core/canvas/canvasStore'
|
||||
import type { ComfyExtension } from '@/types/comfy'
|
||||
|
||||
import { useCommandStore } from './commandStore'
|
||||
|
||||
export const useMenuItemStore = defineStore('menuItem', () => {
|
||||
const canvasStore = useCanvasStore()
|
||||
const commandStore = useCommandStore()
|
||||
const menuItems = ref<MenuItem[]>([])
|
||||
const menuItemHasActiveStateChildren = ref<Record<string, boolean>>({})
|
||||
const hasSeenLinear = ref(false)
|
||||
|
||||
whenever(
|
||||
() => canvasStore.linearMode,
|
||||
() => (hasSeenLinear.value = true),
|
||||
{ immediate: true, once: true }
|
||||
)
|
||||
|
||||
const registerMenuGroup = (path: string[], items: MenuItem[]) => {
|
||||
let currentLevel = menuItems.value
|
||||
@@ -113,7 +103,6 @@ export const useMenuItemStore = defineStore('menuItem', () => {
|
||||
loadExtensionMenuCommands,
|
||||
registerCoreMenuCommands,
|
||||
menuItemHasActiveStateChildren,
|
||||
hasSeenLinear,
|
||||
commandIdToMenuItem
|
||||
}
|
||||
})
|
||||
|
||||
@@ -149,6 +149,7 @@ function dragDrop(e: DragEvent) {
|
||||
</SplitterPanel>
|
||||
<SplitterPanel
|
||||
id="linearCenterPanel"
|
||||
data-testid="linear-center-panel"
|
||||
:size="CENTER_PANEL_SIZE"
|
||||
class="relative flex min-w-[20vw] flex-col gap-4 text-muted-foreground outline-none"
|
||||
@drop="dragDrop"
|
||||
|
||||
Reference in New Issue
Block a user