mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-06-02 19:40:03 +00:00
Compare commits
6 Commits
main
...
feat/app-m
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
68aa291e5b | ||
|
|
4dc831e5da | ||
|
|
112f4b254a | ||
|
|
f44f6f8525 | ||
|
|
48f007f4db | ||
|
|
2727587e35 |
@@ -130,4 +130,44 @@ describe('useStablePrimeVueSplitterSizer', () => {
|
||||
|
||||
expect(validRef.value!.style.flexBasis).toBe('200px')
|
||||
})
|
||||
|
||||
it('passes old→new widths to onResize (null old on first resize)', async () => {
|
||||
const panelRef = createPanel(320)
|
||||
const trigger = ref(0)
|
||||
const onResize = vi.fn()
|
||||
|
||||
const { onResizeEnd } = useStablePrimeVueSplitterSizer(
|
||||
[{ ref: panelRef, storageKey: 'test-onresize-first' }],
|
||||
[trigger],
|
||||
onResize
|
||||
)
|
||||
await flushWatcher()
|
||||
|
||||
onResizeEnd(resizeEndEvent())
|
||||
|
||||
expect(onResize).toHaveBeenCalledWith([
|
||||
{ storageKey: 'test-onresize-first', oldWidth: null, newWidth: 320 }
|
||||
])
|
||||
})
|
||||
|
||||
it('reports the previous width as old on the next resize', async () => {
|
||||
const panelRef = createPanel(320)
|
||||
const trigger = ref(0)
|
||||
const onResize = vi.fn()
|
||||
|
||||
const { onResizeEnd } = useStablePrimeVueSplitterSizer(
|
||||
[{ ref: panelRef, storageKey: 'test-onresize-next' }],
|
||||
[trigger],
|
||||
onResize
|
||||
)
|
||||
await flushWatcher()
|
||||
|
||||
onResizeEnd(resizeEndEvent())
|
||||
panelRef.value = createPanel(480).value
|
||||
onResizeEnd(resizeEndEvent())
|
||||
|
||||
expect(onResize).toHaveBeenLastCalledWith([
|
||||
{ storageKey: 'test-onresize-next', oldWidth: 320, newWidth: 480 }
|
||||
])
|
||||
})
|
||||
})
|
||||
|
||||
@@ -10,6 +10,12 @@ interface PanelConfig {
|
||||
storageKey: string
|
||||
}
|
||||
|
||||
export interface PanelResizeChange {
|
||||
storageKey: string
|
||||
oldWidth: number | null
|
||||
newWidth: number
|
||||
}
|
||||
|
||||
/**
|
||||
* Works around PrimeVue Splitter not properly initializing flexBasis
|
||||
* when panels are conditionally rendered. Captures pixel widths on
|
||||
@@ -21,9 +27,11 @@ interface PanelConfig {
|
||||
*/
|
||||
export function useStablePrimeVueSplitterSizer(
|
||||
panels: PanelConfig[],
|
||||
watchSources: WatchSource[]
|
||||
watchSources: WatchSource[],
|
||||
onResize?: (changes: PanelResizeChange[]) => void
|
||||
) {
|
||||
const storedWidths = panels.map((panel) => ({
|
||||
storageKey: panel.storageKey,
|
||||
ref: panel.ref,
|
||||
width: useStorage<number | null>(panel.storageKey, null)
|
||||
}))
|
||||
@@ -45,10 +53,16 @@ export function useStablePrimeVueSplitterSizer(
|
||||
}
|
||||
|
||||
function onResizeEnd(_event: SplitterResizeEndEvent) {
|
||||
for (const { ref, width } of storedWidths) {
|
||||
const changes: PanelResizeChange[] = []
|
||||
for (const { storageKey, ref, width } of storedWidths) {
|
||||
const el = resolveElement(ref)
|
||||
if (el) width.value = el.offsetWidth
|
||||
if (!el) continue
|
||||
const oldWidth = width.value
|
||||
const newWidth = el.offsetWidth
|
||||
width.value = newWidth
|
||||
changes.push({ storageKey, oldWidth, newWidth })
|
||||
}
|
||||
onResize?.(changes)
|
||||
}
|
||||
|
||||
watch(
|
||||
|
||||
24
src/platform/telemetry/TelemetryRegistry.test.ts
Normal file
24
src/platform/telemetry/TelemetryRegistry.test.ts
Normal file
@@ -0,0 +1,24 @@
|
||||
import { describe, expect, it, vi } from 'vitest'
|
||||
|
||||
import { TelemetryRegistry } from './TelemetryRegistry'
|
||||
import type { AppModePanelResizedMetadata, TelemetryProvider } from './types'
|
||||
|
||||
describe('TelemetryRegistry', () => {
|
||||
it('forwards trackAppModePanelResized to registered providers', () => {
|
||||
const registry = new TelemetryRegistry()
|
||||
const trackAppModePanelResized = vi.fn()
|
||||
const provider: TelemetryProvider = { trackAppModePanelResized }
|
||||
registry.registerProvider(provider)
|
||||
|
||||
const metadata: AppModePanelResizedMetadata = {
|
||||
panel: 'input',
|
||||
direction: 'wider',
|
||||
previous_width_px: 320,
|
||||
new_width_px: 480,
|
||||
sidebar_location: 'left'
|
||||
}
|
||||
registry.trackAppModePanelResized(metadata)
|
||||
|
||||
expect(trackAppModePanelResized).toHaveBeenCalledWith(metadata)
|
||||
})
|
||||
})
|
||||
@@ -1,6 +1,7 @@
|
||||
import type { AuditLog } from '@/services/customerEventsService'
|
||||
|
||||
import type {
|
||||
AppModePanelResizedMetadata,
|
||||
AuthMetadata,
|
||||
BeginCheckoutMetadata,
|
||||
DefaultViewSetMetadata,
|
||||
@@ -241,4 +242,8 @@ export class TelemetryRegistry implements TelemetryDispatcher {
|
||||
trackPageView(pageName: string, properties?: PageViewMetadata): void {
|
||||
this.dispatch((provider) => provider.trackPageView?.(pageName, properties))
|
||||
}
|
||||
|
||||
trackAppModePanelResized(metadata: AppModePanelResizedMetadata): void {
|
||||
this.dispatch((provider) => provider.trackAppModePanelResized?.(metadata))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -190,6 +190,25 @@ describe('PostHogTelemetryProvider', () => {
|
||||
)
|
||||
})
|
||||
|
||||
it('captures App Mode panel resize events with metadata', async () => {
|
||||
const provider = createProvider()
|
||||
await vi.dynamicImportSettled()
|
||||
|
||||
const metadata = {
|
||||
panel: 'input',
|
||||
direction: 'wider',
|
||||
previous_width_px: 320,
|
||||
new_width_px: 480,
|
||||
sidebar_location: 'left'
|
||||
} as const
|
||||
provider.trackAppModePanelResized(metadata)
|
||||
|
||||
expect(hoisted.mockCapture).toHaveBeenCalledWith(
|
||||
TelemetryEvents.APP_MODE_PANEL_RESIZED,
|
||||
metadata
|
||||
)
|
||||
})
|
||||
|
||||
it('queues events before initialization and flushes after', async () => {
|
||||
const provider = createProvider()
|
||||
|
||||
|
||||
@@ -10,6 +10,7 @@ import { remoteConfig } from '@/platform/remoteConfig/remoteConfig'
|
||||
import type { RemoteConfig } from '@/platform/remoteConfig/types'
|
||||
|
||||
import type {
|
||||
AppModePanelResizedMetadata,
|
||||
AuthMetadata,
|
||||
DefaultViewSetMetadata,
|
||||
EnterLinearMetadata,
|
||||
@@ -466,4 +467,8 @@ export class PostHogTelemetryProvider implements TelemetryProvider {
|
||||
...properties
|
||||
})
|
||||
}
|
||||
|
||||
trackAppModePanelResized(metadata: AppModePanelResizedMetadata): void {
|
||||
this.trackEvent(TelemetryEvents.APP_MODE_PANEL_RESIZED, metadata)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -389,6 +389,19 @@ export interface SubscriptionSuccessMetadata extends Record<string, unknown> {
|
||||
ecommerce: EcommerceMetadata
|
||||
}
|
||||
|
||||
/**
|
||||
* App Mode panel resize (LinearView splitter) — UX signal for whether the
|
||||
* default prompt panel is too small (users widening it to read prompts). Only
|
||||
* emitted for genuine resizes, so the direction is always wider or narrower.
|
||||
*/
|
||||
export interface AppModePanelResizedMetadata {
|
||||
panel: 'input' | 'preview'
|
||||
direction: 'wider' | 'narrower'
|
||||
previous_width_px: number
|
||||
new_width_px: number
|
||||
sidebar_location: 'left' | 'right'
|
||||
}
|
||||
|
||||
/**
|
||||
* Telemetry provider interface for individual providers.
|
||||
* All methods are optional - providers only implement what they need.
|
||||
@@ -473,6 +486,9 @@ export interface TelemetryProvider {
|
||||
// Generic UI button click events
|
||||
trackUiButtonClicked?(metadata: UiButtonClickMetadata): void
|
||||
|
||||
// App Mode UI events
|
||||
trackAppModePanelResized?(metadata: AppModePanelResizedMetadata): void
|
||||
|
||||
// Page view tracking
|
||||
trackPageView?(pageName: string, properties?: PageViewMetadata): void
|
||||
}
|
||||
@@ -561,6 +577,9 @@ export const TelemetryEvents = {
|
||||
// Generic UI Button Click
|
||||
UI_BUTTON_CLICKED: 'app:ui_button_clicked',
|
||||
|
||||
// App Mode UI
|
||||
APP_MODE_PANEL_RESIZED: 'app:app_mode_panel_resized',
|
||||
|
||||
// Page View
|
||||
PAGE_VIEW: 'app:page_view'
|
||||
} as const
|
||||
@@ -597,6 +616,7 @@ export type TelemetryEventProperties =
|
||||
| TemplateFilterMetadata
|
||||
| SettingChangedMetadata
|
||||
| UiButtonClickMetadata
|
||||
| AppModePanelResizedMetadata
|
||||
| HelpCenterOpenedMetadata
|
||||
| HelpResourceClickedMetadata
|
||||
| HelpCenterClosedMetadata
|
||||
|
||||
@@ -14,6 +14,7 @@ import TopbarBadges from '@/components/topbar/TopbarBadges.vue'
|
||||
import TopbarSubscribeButton from '@/components/topbar/TopbarSubscribeButton.vue'
|
||||
import WorkflowTabs from '@/components/topbar/WorkflowTabs.vue'
|
||||
import { useSettingStore } from '@/platform/settings/settingStore'
|
||||
import { useTelemetry } from '@/platform/telemetry'
|
||||
import { cn } from '@comfyorg/tailwind-utils'
|
||||
import LinearControls from '@/renderer/extensions/linearMode/LinearControls.vue'
|
||||
import LinearPreview from '@/renderer/extensions/linearMode/LinearPreview.vue'
|
||||
@@ -29,6 +30,10 @@ import {
|
||||
SIDE_PANEL_SIZE
|
||||
} from '@/constants/splitterConstants'
|
||||
import { useAppModeStore } from '@/stores/appModeStore'
|
||||
import {
|
||||
LINEAR_VIEW_PANEL_STORAGE_KEY,
|
||||
buildInputPanelResizeMetadata
|
||||
} from './linearViewPanelTelemetry'
|
||||
|
||||
const settingStore = useSettingStore()
|
||||
const workspaceStore = useWorkspaceStore()
|
||||
@@ -78,12 +83,19 @@ const splitterKey = computed(() => {
|
||||
const leftPanelRef = useTemplateRef<MaybeElement>('leftPanel')
|
||||
const rightPanelRef = useTemplateRef<MaybeElement>('rightPanel')
|
||||
|
||||
const telemetry = useTelemetry()
|
||||
|
||||
const { onResizeEnd } = useStablePrimeVueSplitterSizer(
|
||||
[
|
||||
{ ref: leftPanelRef, storageKey: 'Comfy.LinearView.LeftPanelWidth' },
|
||||
{ ref: rightPanelRef, storageKey: 'Comfy.LinearView.RightPanelWidth' }
|
||||
{ ref: leftPanelRef, storageKey: LINEAR_VIEW_PANEL_STORAGE_KEY.left },
|
||||
{ ref: rightPanelRef, storageKey: LINEAR_VIEW_PANEL_STORAGE_KEY.right }
|
||||
],
|
||||
[activeTab, splitterKey]
|
||||
[activeTab, splitterKey],
|
||||
(changes) => {
|
||||
if (isBuilderMode.value) return
|
||||
const metadata = buildInputPanelResizeMetadata(changes, sidebarOnLeft.value)
|
||||
if (metadata) telemetry?.trackAppModePanelResized(metadata)
|
||||
}
|
||||
)
|
||||
|
||||
const TYPEFORM_WIDGET_ID = 'jmmzmlKw'
|
||||
|
||||
66
src/views/linearViewPanelTelemetry.test.ts
Normal file
66
src/views/linearViewPanelTelemetry.test.ts
Normal file
@@ -0,0 +1,66 @@
|
||||
import { describe, expect, it } from 'vitest'
|
||||
|
||||
import type { PanelResizeChange } from '@/composables/useStablePrimeVueSplitterSizer'
|
||||
|
||||
import {
|
||||
LINEAR_VIEW_PANEL_STORAGE_KEY,
|
||||
buildInputPanelResizeMetadata
|
||||
} from './linearViewPanelTelemetry'
|
||||
|
||||
function change(
|
||||
storageKey: string,
|
||||
oldWidth: number | null,
|
||||
newWidth: number
|
||||
): PanelResizeChange {
|
||||
return { storageKey, oldWidth, newWidth }
|
||||
}
|
||||
|
||||
describe('buildInputPanelResizeMetadata', () => {
|
||||
it('reads the right panel when the sidebar is on the left', () => {
|
||||
const changes = [
|
||||
change(LINEAR_VIEW_PANEL_STORAGE_KEY.left, 200, 260),
|
||||
change(LINEAR_VIEW_PANEL_STORAGE_KEY.right, 320, 480)
|
||||
]
|
||||
|
||||
expect(buildInputPanelResizeMetadata(changes, true)).toEqual({
|
||||
panel: 'input',
|
||||
direction: 'wider',
|
||||
previous_width_px: 320,
|
||||
new_width_px: 480,
|
||||
sidebar_location: 'left'
|
||||
})
|
||||
})
|
||||
|
||||
it('reads the left panel when the sidebar is on the right', () => {
|
||||
const changes = [
|
||||
change(LINEAR_VIEW_PANEL_STORAGE_KEY.left, 480, 320),
|
||||
change(LINEAR_VIEW_PANEL_STORAGE_KEY.right, 200, 260)
|
||||
]
|
||||
|
||||
expect(buildInputPanelResizeMetadata(changes, false)).toEqual({
|
||||
panel: 'input',
|
||||
direction: 'narrower',
|
||||
previous_width_px: 480,
|
||||
new_width_px: 320,
|
||||
sidebar_location: 'right'
|
||||
})
|
||||
})
|
||||
|
||||
it('returns null when only the sidebar panel changed', () => {
|
||||
const changes = [change(LINEAR_VIEW_PANEL_STORAGE_KEY.left, 200, 260)]
|
||||
|
||||
expect(buildInputPanelResizeMetadata(changes, true)).toBeNull()
|
||||
})
|
||||
|
||||
it('returns null when the input panel width did not change', () => {
|
||||
const changes = [change(LINEAR_VIEW_PANEL_STORAGE_KEY.right, 400, 400)]
|
||||
|
||||
expect(buildInputPanelResizeMetadata(changes, true)).toBeNull()
|
||||
})
|
||||
|
||||
it('returns null on the first measurement with no stored width', () => {
|
||||
const changes = [change(LINEAR_VIEW_PANEL_STORAGE_KEY.right, null, 400)]
|
||||
|
||||
expect(buildInputPanelResizeMetadata(changes, true)).toBeNull()
|
||||
})
|
||||
})
|
||||
43
src/views/linearViewPanelTelemetry.ts
Normal file
43
src/views/linearViewPanelTelemetry.ts
Normal file
@@ -0,0 +1,43 @@
|
||||
import type { PanelResizeChange } from '@/composables/useStablePrimeVueSplitterSizer'
|
||||
import type { AppModePanelResizedMetadata } from '@/platform/telemetry/types'
|
||||
|
||||
export const LINEAR_VIEW_PANEL_STORAGE_KEY = {
|
||||
left: 'Comfy.LinearView.LeftPanelWidth',
|
||||
right: 'Comfy.LinearView.RightPanelWidth'
|
||||
} as const
|
||||
|
||||
/**
|
||||
* Builds the telemetry payload for an App Mode input-panel resize, or null when
|
||||
* the resize should not be reported.
|
||||
*
|
||||
* The input/prompt panel renders opposite the sidebar, so a left sidebar puts
|
||||
* the input on the right and vice-versa. We return null unless this resize
|
||||
* actually changed the input panel's width: the splitter re-measures every
|
||||
* panel on resize-end, so dragging an unrelated gutter (or the very first
|
||||
* measurement, with no stored width) reports the input panel unchanged and must
|
||||
* not emit a spurious event.
|
||||
*/
|
||||
export function buildInputPanelResizeMetadata(
|
||||
changes: PanelResizeChange[],
|
||||
sidebarOnLeft: boolean
|
||||
): AppModePanelResizedMetadata | null {
|
||||
const inputPanelKey = sidebarOnLeft
|
||||
? LINEAR_VIEW_PANEL_STORAGE_KEY.right
|
||||
: LINEAR_VIEW_PANEL_STORAGE_KEY.left
|
||||
const inputChange = changes.find((c) => c.storageKey === inputPanelKey)
|
||||
if (
|
||||
!inputChange ||
|
||||
inputChange.oldWidth === null ||
|
||||
inputChange.oldWidth === inputChange.newWidth
|
||||
) {
|
||||
return null
|
||||
}
|
||||
return {
|
||||
panel: 'input',
|
||||
direction:
|
||||
inputChange.newWidth > inputChange.oldWidth ? 'wider' : 'narrower',
|
||||
previous_width_px: inputChange.oldWidth,
|
||||
new_width_px: inputChange.newWidth,
|
||||
sidebar_location: sidebarOnLeft ? 'left' : 'right'
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user