mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-07-15 11:44:10 +00:00
## Summary Prototype redesign of the embedded 3D viewer controls into a framed top/bottom-bar chrome. Proof-of-concept for design exploration — not intended to merge as-is. ## Changes - **What**: Replaces the floating viewer controls (`Load3DControls`) with a new `Load3DMenuBar` framed chrome: a black top bar with a category dropdown (Scene / 3D Model / Camera / Lighting) and the active category's actions (labels collapse to icons on narrow nodes), plus a black bottom bar with Record (left) and fit + export (right). Export is moved out of the menu into the bottom-right button. Adds a "Clay" material mode that renders meshes with a flat grey material so geometry is visible without textures. - **Breaking**: None — `Load3D.vue` swaps the component and hides its existing right-side button column behind `v-if="false"`; nothing is deleted. ## Review Focus - Design prototype: `Load3D.vue` swaps `Load3DControls` → `Load3DMenuBar` and hides the existing fit/center/expand/record column. The original components are left untouched. - The only changes outside the prototype component are for the **Clay** material mode, which touches shared engine code: `MaterialMode` type (`interfaces.ts`), the runtime material switch (`SceneModelManager.ts`), the capability lists (`MeshModelAdapter.ts`, `ModelAdapter.ts`, `useLoad3d.ts`), and the i18n label. - Known prototype gaps: **Record** is a visual toggle only (pulsing dot, not wired to real recording); **fit** reuses the existing `handleFitToViewer`; **export** reuses the existing format options. ## Screenshots (if applicable) Prototype walkthrough captured during development (top-bar chrome, category dropdown, labeled-vs-icon collapse, Clay material, pulsing Record). Available on request. --------- Co-authored-by: PabloWiedemann <PabloWiedemann@users.noreply.github.com> Co-authored-by: Terry Jia <terryjia88@gmail.com>
131 lines
3.5 KiB
TypeScript
131 lines
3.5 KiB
TypeScript
import { expect } from '@playwright/test'
|
|
import type { Locator } from '@playwright/test'
|
|
|
|
import { TestIds } from '@e2e/fixtures/selectors'
|
|
|
|
export class Load3DHelper {
|
|
constructor(readonly node: Locator) {}
|
|
|
|
get canvas(): Locator {
|
|
return this.node.locator('canvas')
|
|
}
|
|
|
|
get menuButton(): Locator {
|
|
return this.node.getByTestId(TestIds.load3d.categoryMenu)
|
|
}
|
|
|
|
private get menuPanel(): Locator {
|
|
return this.node.page().getByRole('dialog')
|
|
}
|
|
|
|
get recordingButton(): Locator {
|
|
return this.node.getByRole('button', { name: 'Record', exact: true })
|
|
}
|
|
|
|
get stopRecordingButton(): Locator {
|
|
return this.node.getByRole('button', { name: /stop recording/i })
|
|
}
|
|
|
|
get recordingMenuButton(): Locator {
|
|
return this.node.getByTestId(TestIds.load3d.recordingDuration)
|
|
}
|
|
|
|
get downloadRecordingMenuItem(): Locator {
|
|
return this.menuPanel.getByRole('button', { name: 'Download Recording' })
|
|
}
|
|
|
|
get startNewRecordingMenuItem(): Locator {
|
|
return this.menuPanel.getByRole('button', { name: 'Start New Recording' })
|
|
}
|
|
|
|
get deleteRecordingMenuItem(): Locator {
|
|
return this.menuPanel.getByRole('button', { name: 'Delete Recording' })
|
|
}
|
|
|
|
get gridToggleButton(): Locator {
|
|
return this.node.getByRole('button', { name: /show grid/i })
|
|
}
|
|
|
|
get uploadBackgroundImageButton(): Locator {
|
|
return this.node.getByRole('button', { name: 'BG Image' })
|
|
}
|
|
|
|
get removeBackgroundImageButton(): Locator {
|
|
return this.node.getByRole('button', { name: 'Remove BG' })
|
|
}
|
|
|
|
get panoramaModeButton(): Locator {
|
|
return this.node.getByRole('button', { name: /^panorama$/i })
|
|
}
|
|
|
|
get colorInput(): Locator {
|
|
return this.node.locator('input[type="color"]')
|
|
}
|
|
|
|
get exportButton(): Locator {
|
|
return this.node.getByRole('button', { name: 'Export', exact: true })
|
|
}
|
|
|
|
get openViewerButton(): Locator {
|
|
return this.node.getByRole('button', { name: /open in 3d viewer/i })
|
|
}
|
|
|
|
getUploadButton(label: string): Locator {
|
|
return this.node.getByText(label)
|
|
}
|
|
|
|
getMenuCategory(name: string): Locator {
|
|
return this.menuPanel.getByRole('button', { name, exact: true })
|
|
}
|
|
|
|
get gizmoToggleButton(): Locator {
|
|
// The category chip is also named "Gizmo" once that category is active;
|
|
// only the toggle carries aria-pressed.
|
|
return this.node
|
|
.getByRole('button', { name: 'Gizmo' })
|
|
.and(this.node.locator('[aria-pressed]'))
|
|
}
|
|
|
|
get gizmoTranslateButton(): Locator {
|
|
return this.node.getByRole('button', { name: 'Translate' })
|
|
}
|
|
|
|
get gizmoRotateButton(): Locator {
|
|
return this.node.getByRole('button', { name: 'Rotate' })
|
|
}
|
|
|
|
get gizmoScaleButton(): Locator {
|
|
return this.node.getByRole('button', { name: 'Scale' })
|
|
}
|
|
|
|
get gizmoResetButton(): Locator {
|
|
return this.node.getByRole('button', { name: 'Reset', exact: true })
|
|
}
|
|
|
|
async openMenu(): Promise<void> {
|
|
await this.menuButton.click()
|
|
}
|
|
|
|
async openRecordingMenu(): Promise<void> {
|
|
await this.recordingMenuButton.click()
|
|
}
|
|
|
|
async openGizmoCategory(): Promise<void> {
|
|
await this.openMenu()
|
|
await this.getMenuCategory('Gizmo').click()
|
|
}
|
|
|
|
async setBackgroundColor(hex: string): Promise<void> {
|
|
await this.colorInput.evaluate((el, value) => {
|
|
;(el as HTMLInputElement).value = value
|
|
el.dispatchEvent(new Event('input', { bubbles: true }))
|
|
}, hex)
|
|
}
|
|
|
|
async waitForModelLoaded(): Promise<void> {
|
|
await expect(this.node.getByTestId(TestIds.loading.overlay)).toBeHidden({
|
|
timeout: 30000
|
|
})
|
|
}
|
|
}
|