Proper command bar UI support for optional args (#7506)

* WIP: Add bidirectional args to point-and-click Extrude
Will eventually close #7495

* Wire up edit flow for symmetric

* Show skip true args in header in review phase

* Add bidirectionalLength

* Make currentArg always part of header

* WIP

* Add twistAng

* Proper optional args line in review

* Labels in progress button and option arg section heading

* Clean up extrude specific changes

* More UI polish

* Remove options bool icon

* Fix labels for tests

* Upgrade e2e tests to cmdBar fixtures with fixes

* More fixes

* Fixed up more tests related to sweep behavior change

* Fix nodeToEdit not having hidden: true on Shell

* Add typecheck

* WIP: footer buttons

* back to reg width

* Clean up

* Clean up

* Fix tests and remove label

* Refactor

* Fix offset plane test

* Add CommandBarDivider

* Fix step back

* Add comment

* Fix it, thanks bot

* Clean up and inline optional heading

* Little case tweak

* Update src/components/CommandBar/CommandBarReview.tsx

Co-authored-by: graphite-app[bot] <96075541+graphite-app[bot]@users.noreply.github.com>

* Rename to CommandBarHeaderFooter

---------

Co-authored-by: graphite-app[bot] <96075541+graphite-app[bot]@users.noreply.github.com>
This commit is contained in:
Pierre Jacquier
2025-06-20 12:05:20 -04:00
committed by GitHub
parent 5f2a10ec7e
commit 416d0b37a2
17 changed files with 235 additions and 195 deletions

View File

@ -307,7 +307,7 @@ test.describe('Command bar tests', () => {
) )
const continueButton = page.getByRole('button', { name: 'Continue' }) const continueButton = page.getByRole('button', { name: 'Continue' })
const submitButton = page.getByRole('button', { name: 'Submit command' }) const submitButton = page.getByTestId('command-bar-submit')
await continueButton.click() await continueButton.click()
// Review step and argument hotkeys // Review step and argument hotkeys

View File

@ -54,9 +54,7 @@ test(
await page.keyboard.press('Enter') await page.keyboard.press('Enter')
// Click the checkbox // Click the checkbox
const submitButton = page.getByText('Confirm Export') await cmdBar.submit()
await expect(submitButton).toBeVisible()
await page.keyboard.press('Enter')
// Expect it to succeed // Expect it to succeed
const errorToastMessage = page.getByText(`Error while exporting`) const errorToastMessage = page.getByText(`Error while exporting`)
@ -119,9 +117,7 @@ test(
await page.keyboard.press('Enter') await page.keyboard.press('Enter')
// Click the checkbox // Click the checkbox
const submitButton = page.getByText('Confirm Export') await cmdBar.submit()
await expect(submitButton).toBeVisible()
await page.keyboard.press('Enter')
// Look out for the toast message // Look out for the toast message
const exportingToastMessage = page.getByText(`Exporting...`) const exportingToastMessage = page.getByText(`Exporting...`)

View File

@ -118,15 +118,11 @@ export class CmdBarFixture {
return return
} }
const arrowButton = this.page.getByRole('button', { const arrowButton = this.page.getByTestId('command-bar-continue')
name: 'arrow right Continue',
})
if (await arrowButton.isVisible()) { if (await arrowButton.isVisible()) {
await arrowButton.click() await this.continue()
} else { } else {
await this.page await this.submit()
.getByRole('button', { name: 'checkmark Submit command' })
.click()
} }
} }

View File

@ -1083,14 +1083,13 @@ openSketch = startSketchOn(XY)
cmdBar, cmdBar,
}) => { }) => {
// One dumb hardcoded screen pixel value // One dumb hardcoded screen pixel value
const testPoint = { x: 700, y: 150 } const testPoint = { x: 700, y: 200 }
// TODO: replace the testPoint selection with a feature tree click once that's supported #7544
const [clickOnXzPlane] = scene.makeMouseHelpers(testPoint.x, testPoint.y) const [clickOnXzPlane] = scene.makeMouseHelpers(testPoint.x, testPoint.y)
const expectedOutput = `plane001 = offsetPlane(XZ, offset = 5)` const expectedOutput = `plane001 = offsetPlane(XZ, offset = 5)`
await homePage.goToModelingScene() await homePage.goToModelingScene()
// FIXME: Since there is no KCL code loaded. We need to wait for the scene to load before we continue. await scene.settled(cmdBar)
// The engine may not be connected
await page.waitForTimeout(15000)
await test.step(`Look for the blue of the XZ plane`, async () => { await test.step(`Look for the blue of the XZ plane`, async () => {
//await scene.expectPixelColor([50, 51, 96], testPoint, 15) // FIXME //await scene.expectPixelColor([50, 51, 96], testPoint, 15) // FIXME
@ -1829,7 +1828,6 @@ profile002 = startProfile(sketch002, at = [0, 0])
currentArgKey: 'sketches', currentArgKey: 'sketches',
currentArgValue: '', currentArgValue: '',
headerArguments: { headerArguments: {
Sectional: '',
Profiles: '', Profiles: '',
Path: '', Path: '',
}, },
@ -1843,7 +1841,6 @@ profile002 = startProfile(sketch002, at = [0, 0])
currentArgKey: 'path', currentArgKey: 'path',
currentArgValue: '', currentArgValue: '',
headerArguments: { headerArguments: {
Sectional: '',
Profiles: '1 profile', Profiles: '1 profile',
Path: '', Path: '',
}, },
@ -1856,7 +1853,6 @@ profile002 = startProfile(sketch002, at = [0, 0])
currentArgKey: 'path', currentArgKey: 'path',
currentArgValue: '', currentArgValue: '',
headerArguments: { headerArguments: {
Sectional: '',
Profiles: '1 profile', Profiles: '1 profile',
Path: '', Path: '',
}, },
@ -1869,7 +1865,6 @@ profile002 = startProfile(sketch002, at = [0, 0])
headerArguments: { headerArguments: {
Profiles: '1 profile', Profiles: '1 profile',
Path: '1 segment', Path: '1 segment',
Sectional: '',
}, },
stage: 'review', stage: 'review',
}) })
@ -1894,6 +1889,9 @@ profile002 = startProfile(sketch002, at = [0, 0])
0 0
) )
await operationButton.dblclick({ button: 'left' }) await operationButton.dblclick({ button: 'left' })
await page
.getByRole('button', { name: 'sectional', exact: false })
.click()
await cmdBar.expectState({ await cmdBar.expectState({
commandName: 'Sweep', commandName: 'Sweep',
currentArgKey: 'sectional', currentArgKey: 'sectional',
@ -1971,7 +1969,6 @@ profile001 = ${circleCode}`
currentArgKey: 'sketches', currentArgKey: 'sketches',
currentArgValue: '', currentArgValue: '',
headerArguments: { headerArguments: {
Sectional: '',
Profiles: '', Profiles: '',
Path: '', Path: '',
}, },
@ -1986,7 +1983,6 @@ profile001 = ${circleCode}`
currentArgKey: 'path', currentArgKey: 'path',
currentArgValue: '', currentArgValue: '',
headerArguments: { headerArguments: {
Sectional: '',
Profiles: '1 profile', Profiles: '1 profile',
Path: '', Path: '',
}, },
@ -2000,7 +1996,6 @@ profile001 = ${circleCode}`
currentArgKey: 'path', currentArgKey: 'path',
currentArgValue: '', currentArgValue: '',
headerArguments: { headerArguments: {
Sectional: '',
Profiles: '1 profile', Profiles: '1 profile',
Path: '', Path: '',
}, },
@ -2013,7 +2008,6 @@ profile001 = ${circleCode}`
headerArguments: { headerArguments: {
Profiles: '1 profile', Profiles: '1 profile',
Path: '1 helix', Path: '1 helix',
Sectional: '',
}, },
stage: 'review', stage: 'review',
}) })
@ -4734,7 +4728,6 @@ path001 = startProfile(sketch001, at = [0, 0])
headerArguments: { headerArguments: {
Profiles: '', Profiles: '',
Path: '', Path: '',
Sectional: '',
}, },
highlightedHeaderArg: 'Profiles', highlightedHeaderArg: 'Profiles',
commandName: 'Sweep', commandName: 'Sweep',
@ -4747,7 +4740,6 @@ path001 = startProfile(sketch001, at = [0, 0])
headerArguments: { headerArguments: {
Profiles: '2 profiles', Profiles: '2 profiles',
Path: '', Path: '',
Sectional: '',
}, },
highlightedHeaderArg: 'path', highlightedHeaderArg: 'path',
commandName: 'Sweep', commandName: 'Sweep',
@ -4760,7 +4752,6 @@ path001 = startProfile(sketch001, at = [0, 0])
headerArguments: { headerArguments: {
Profiles: '2 profiles', Profiles: '2 profiles',
Path: '1 segment', Path: '1 segment',
Sectional: '',
}, },
commandName: 'Sweep', commandName: 'Sweep',
}) })

View File

@ -475,6 +475,7 @@ test.describe('Can export from electron app', () => {
}, },
tronApp.projectDirName, tronApp.projectDirName,
page, page,
cmdBar,
method method
) )
) )
@ -779,9 +780,6 @@ test.describe(`Project management commands`, () => {
const commandContinueButton = page.getByRole('button', { const commandContinueButton = page.getByRole('button', {
name: 'Continue', name: 'Continue',
}) })
const commandSubmitButton = page.getByRole('button', {
name: 'Submit command',
})
const toastMessage = page.getByText(`Successfully renamed`) const toastMessage = page.getByText(`Successfully renamed`)
await test.step(`Setup`, async () => { await test.step(`Setup`, async () => {
@ -800,8 +798,7 @@ test.describe(`Project management commands`, () => {
await expect(commandContinueButton).toBeVisible() await expect(commandContinueButton).toBeVisible()
await commandContinueButton.click() await commandContinueButton.click()
await expect(commandSubmitButton).toBeVisible() await cmdBar.submit()
await commandSubmitButton.click()
await expect(toastMessage).toBeVisible() await expect(toastMessage).toBeVisible()
}) })
@ -837,9 +834,6 @@ test.describe(`Project management commands`, () => {
}) })
const projectNameOption = page.getByRole('option', { name: projectName }) const projectNameOption = page.getByRole('option', { name: projectName })
const commandWarning = page.getByText('Are you sure you want to delete?') const commandWarning = page.getByText('Are you sure you want to delete?')
const commandSubmitButton = page.getByRole('button', {
name: 'Submit command',
})
const toastMessage = page.getByText(`Successfully deleted`) const toastMessage = page.getByText(`Successfully deleted`)
const noProjectsMessage = page.getByText('No projects found') const noProjectsMessage = page.getByText('No projects found')
@ -859,8 +853,7 @@ test.describe(`Project management commands`, () => {
await projectNameOption.click() await projectNameOption.click()
await expect(commandWarning).toBeVisible() await expect(commandWarning).toBeVisible()
await expect(commandSubmitButton).toBeVisible() await cmdBar.submit()
await commandSubmitButton.click()
await expect(toastMessage).toBeVisible() await expect(toastMessage).toBeVisible()
}) })
@ -894,9 +887,6 @@ test.describe(`Project management commands`, () => {
const commandContinueButton = page.getByRole('button', { const commandContinueButton = page.getByRole('button', {
name: 'Continue', name: 'Continue',
}) })
const commandSubmitButton = page.getByRole('button', {
name: 'Submit command',
})
const toastMessage = page.getByText(`Successfully renamed`) const toastMessage = page.getByText(`Successfully renamed`)
await test.step(`Setup`, async () => { await test.step(`Setup`, async () => {
@ -914,8 +904,7 @@ test.describe(`Project management commands`, () => {
await expect(commandContinueButton).toBeVisible() await expect(commandContinueButton).toBeVisible()
await commandContinueButton.click() await commandContinueButton.click()
await expect(commandSubmitButton).toBeVisible() await cmdBar.submit()
await commandSubmitButton.click()
await expect(toastMessage).toBeVisible() await expect(toastMessage).toBeVisible()
}) })
@ -949,9 +938,6 @@ test.describe(`Project management commands`, () => {
}) })
const projectNameOption = page.getByRole('option', { name: projectName }) const projectNameOption = page.getByRole('option', { name: projectName })
const commandWarning = page.getByText('Are you sure you want to delete?') const commandWarning = page.getByText('Are you sure you want to delete?')
const commandSubmitButton = page.getByRole('button', {
name: 'Submit command',
})
const toastMessage = page.getByText(`Successfully deleted`) const toastMessage = page.getByText(`Successfully deleted`)
const noProjectsMessage = page.getByText('No projects found') const noProjectsMessage = page.getByText('No projects found')
@ -967,8 +953,7 @@ test.describe(`Project management commands`, () => {
await projectNameOption.click() await projectNameOption.click()
await expect(commandWarning).toBeVisible() await expect(commandWarning).toBeVisible()
await expect(commandSubmitButton).toBeVisible() await cmdBar.submit()
await commandSubmitButton.click()
await expect(toastMessage).toBeVisible() await expect(toastMessage).toBeVisible()
}) })

View File

@ -1,6 +1,7 @@
import path from 'path' import path from 'path'
import { bracket } from '@e2e/playwright/fixtures/bracket' import { bracket } from '@e2e/playwright/fixtures/bracket'
import type { Page } from '@playwright/test' import type { Page } from '@playwright/test'
import type { CmdBarFixture } from '@e2e/playwright/fixtures/cmdBarFixture'
import { reportRejection } from '@src/lib/trap' import { reportRejection } from '@src/lib/trap'
import * as fsp from 'fs/promises' import * as fsp from 'fs/promises'
@ -421,10 +422,7 @@ extrude002 = extrude(profile002, length = 150)
await page.keyboard.press('Enter') await page.keyboard.press('Enter')
// Click the checkbox // Click the checkbox
const submitButton = page.getByText('Confirm Export') await cmdBar.submit()
await expect(submitButton).toBeVisible()
await page.keyboard.press('Enter')
// Find the toast. // Find the toast.
// Look out for the toast message // Look out for the toast message
@ -461,8 +459,7 @@ extrude002 = extrude(profile002, length = 150)
await page.keyboard.press('Enter') await page.keyboard.press('Enter')
// Click the checkbox // Click the checkbox
await expect(submitButton).toBeVisible() await cmdBar.submit()
await page.keyboard.press('Enter')
// Find the toast. // Find the toast.
// Look out for the toast message // Look out for the toast message
@ -482,6 +479,7 @@ extrude002 = extrude(profile002, length = 150)
test('ensure you CAN export while an export is already going', async ({ test('ensure you CAN export while an export is already going', async ({
page, page,
homePage, homePage,
cmdBar,
}) => { }) => {
const u = await getUtils(page) const u = await getUtils(page)
await test.step('Set up the code and durations', async () => { await test.step('Set up the code and durations', async () => {
@ -516,11 +514,11 @@ extrude002 = extrude(profile002, length = 150)
const successToastMessage = page.getByText(`Exported successfully`) const successToastMessage = page.getByText(`Exported successfully`)
await test.step('second export', async () => { await test.step('second export', async () => {
await clickExportButton(page) await clickExportButton(page, cmdBar)
await expect(exportingToastMessage).toBeVisible() await expect(exportingToastMessage).toBeVisible()
await clickExportButton(page) await clickExportButton(page, cmdBar)
await test.step('The first export still succeeds', async () => { await test.step('The first export still succeeds', async () => {
await Promise.all([ await Promise.all([
@ -537,7 +535,7 @@ extrude002 = extrude(profile002, length = 150)
await test.step('Successful, unblocked export', async () => { await test.step('Successful, unblocked export', async () => {
// Try exporting again. // Try exporting again.
await clickExportButton(page) await clickExportButton(page, cmdBar)
// Find the toast. // Find the toast.
// Look out for the toast message // Look out for the toast message
@ -880,7 +878,7 @@ s2 = startSketchOn(XY)
}) })
}) })
async function clickExportButton(page: Page) { async function clickExportButton(page: Page, cmdBar: CmdBarFixture) {
await test.step('Running export flow', async () => { await test.step('Running export flow', async () => {
// export the model // export the model
const exportButton = page.getByTestId('export-pane-button') const exportButton = page.getByTestId('export-pane-button')
@ -896,9 +894,6 @@ async function clickExportButton(page: Page) {
await page.keyboard.press('Enter') await page.keyboard.press('Enter')
// Click the checkbox // Click the checkbox
const submitButton = page.getByText('Confirm Export') await cmdBar.submit()
await expect(submitButton).toBeVisible()
await page.keyboard.press('Enter')
}) })
} }

View File

@ -22,6 +22,7 @@ export const token = process.env.token || ''
import type { ProjectConfiguration } from '@rust/kcl-lib/bindings/ProjectConfiguration' import type { ProjectConfiguration } from '@rust/kcl-lib/bindings/ProjectConfiguration'
import type { ElectronZoo } from '@e2e/playwright/fixtures/fixtureSetup' import type { ElectronZoo } from '@e2e/playwright/fixtures/fixtureSetup'
import type { CmdBarFixture } from '@e2e/playwright/fixtures/cmdBarFixture'
import { isErrorWhitelisted } from '@e2e/playwright/lib/console-error-whitelist' import { isErrorWhitelisted } from '@e2e/playwright/lib/console-error-whitelist'
import { TEST_SETTINGS, TEST_SETTINGS_KEY } from '@e2e/playwright/storageStates' import { TEST_SETTINGS, TEST_SETTINGS_KEY } from '@e2e/playwright/storageStates'
import { test } from '@e2e/playwright/zoo-test' import { test } from '@e2e/playwright/zoo-test'
@ -737,6 +738,7 @@ export const doExport = async (
output: Models['OutputFormat3d_type'], output: Models['OutputFormat3d_type'],
rootDir: string, rootDir: string,
page: Page, page: Page,
cmdBar: CmdBarFixture,
exportFrom: 'dropdown' | 'sidebarButton' | 'commandBar' = 'dropdown' exportFrom: 'dropdown' | 'sidebarButton' | 'commandBar' = 'dropdown'
): Promise<Paths> => { ): Promise<Paths> => {
if (exportFrom === 'dropdown') { if (exportFrom === 'dropdown') {
@ -780,9 +782,7 @@ export const doExport = async (
.click() .click()
await page.locator('#arg-form').waitFor({ state: 'detached' }) await page.locator('#arg-form').waitFor({ state: 'detached' })
} }
await expect(page.getByText('Confirm Export')).toBeVisible() await cmdBar.submit()
await page.getByRole('button', { name: 'Submit command' }).click()
await expect(page.getByText('Exported successfully')).toBeVisible() await expect(page.getByText('Exported successfully')).toBeVisible()

View File

@ -10,7 +10,7 @@ import {
import { expect, test } from '@e2e/playwright/zoo-test' import { expect, test } from '@e2e/playwright/zoo-test'
test.describe('Testing constraints', () => { test.describe('Testing constraints', () => {
test('Can constrain line length', async ({ page, homePage }) => { test('Can constrain line length', async ({ page, homePage, cmdBar }) => {
await page.addInitScript(async () => { await page.addInitScript(async () => {
localStorage.setItem( localStorage.setItem(
'persistCode', 'persistCode',
@ -50,11 +50,7 @@ test.describe('Testing constraints', () => {
await page.waitForTimeout(100) await page.waitForTimeout(100)
await page.getByTestId('constraint-length').click() await page.getByTestId('constraint-length').click()
await page.getByTestId('cmd-bar-arg-value').getByRole('textbox').fill('20') await page.getByTestId('cmd-bar-arg-value').getByRole('textbox').fill('20')
await page await cmdBar.continue()
.getByRole('button', {
name: 'arrow right Continue',
})
.click()
await expect(page.locator('.cm-content')).toHaveText( await expect(page.locator('.cm-content')).toHaveText(
`length001 = 20sketch001 = startSketchOn(XY) |> startProfile(at = [-10, -10]) |> line(end = [20, 0]) |> angledLine(angle = 90, length = length001) |> xLine(length = -20)` `length001 = 20sketch001 = startSketchOn(XY) |> startProfile(at = [-10, -10]) |> line(end = [20, 0]) |> angledLine(angle = 90, length = length001) |> xLine(length = -20)`
@ -681,9 +677,6 @@ test.describe('Testing constraints', () => {
.getByRole('textbox') .getByRole('textbox')
const cmdBarKclVariableNameInput = const cmdBarKclVariableNameInput =
page.getByPlaceholder('Variable name') page.getByPlaceholder('Variable name')
const cmdBarSubmitButton = page.getByRole('button', {
name: 'arrow right Continue',
})
await page.addInitScript(async () => { await page.addInitScript(async () => {
localStorage.setItem( localStorage.setItem(
@ -736,7 +729,7 @@ part002 = startSketchOn(XZ)
await page.waitForTimeout(500) await page.waitForTimeout(500)
const [ang, len] = value.split(', ') const [ang, len] = value.split(', ')
const changedCode = `|> angledLine(angle = ${ang}, length = ${len})` const changedCode = `|> angledLine(angle = ${ang}, length = ${len})`
await cmdBarSubmitButton.click() await cmdBar.continue()
await expect(page.locator('.cm-content')).toContainText(changedCode) await expect(page.locator('.cm-content')).toContainText(changedCode)
// checking active assures the cursor is where it should be // checking active assures the cursor is where it should be
@ -1101,11 +1094,7 @@ part002 = startSketchOn(XZ)
await page.waitForTimeout(500) await page.waitForTimeout(500)
await page.getByTestId('cmd-bar-arg-value').getByRole('textbox').fill('10') await page.getByTestId('cmd-bar-arg-value').getByRole('textbox').fill('10')
await page await cmdBar.continue()
.getByRole('button', {
name: 'arrow right Continue',
})
.click()
await pollEditorLinesSelectedLength(page, 1) await pollEditorLinesSelectedLength(page, 1)
activeLinesContent = await page.locator('.cm-activeLine').all() activeLinesContent = await page.locator('.cm-activeLine').all()

View File

@ -21,7 +21,7 @@ test.describe('Testing loading external models', () => {
// We have no more web tests // We have no more web tests
test.fail( test.fail(
'Web: should overwrite current code, cannot create new file', 'Web: should overwrite current code, cannot create new file',
async ({ editor, context, page, homePage }) => { async ({ editor, context, page, homePage, cmdBar }) => {
const u = await getUtils(page) const u = await getUtils(page)
await test.step(`Test setup`, async () => { await test.step(`Test setup`, async () => {
await context.addInitScript((code) => { await context.addInitScript((code) => {
@ -52,9 +52,6 @@ test.describe('Testing loading external models', () => {
name, name,
}) })
const warningText = page.getByText('Overwrite current file with sample?') const warningText = page.getByText('Overwrite current file with sample?')
const confirmButton = page.getByRole('button', {
name: 'Submit command',
})
await test.step(`Precondition: check the initial code`, async () => { await test.step(`Precondition: check the initial code`, async () => {
await u.openKclCodePanel() await u.openKclCodePanel()
@ -70,7 +67,7 @@ test.describe('Testing loading external models', () => {
await expect(commandMethodOption('Create new file')).not.toBeVisible() await expect(commandMethodOption('Create new file')).not.toBeVisible()
await commandMethodOption('Overwrite').click() await commandMethodOption('Overwrite').click()
await expect(warningText).toBeVisible() await expect(warningText).toBeVisible()
await confirmButton.click() await cmdBar.submit()
await editor.expectEditor.toContain('// ' + newSample.title) await editor.expectEditor.toContain('// ' + newSample.title)
}) })

View File

@ -3,6 +3,7 @@ import type { LineInputsType } from '@src/lang/std/sketchcombos'
import { uuidv4 } from '@src/lib/utils' import { uuidv4 } from '@src/lib/utils'
import type { EditorFixture } from '@e2e/playwright/fixtures/editorFixture' import type { EditorFixture } from '@e2e/playwright/fixtures/editorFixture'
import type { CmdBarFixture } from '@e2e/playwright/fixtures/cmdBarFixture'
import { deg, getUtils, wiggleMove } from '@e2e/playwright/test-utils' import { deg, getUtils, wiggleMove } from '@e2e/playwright/test-utils'
import { expect, test } from '@e2e/playwright/zoo-test' import { expect, test } from '@e2e/playwright/zoo-test'
@ -18,7 +19,7 @@ test.describe('Testing segment overlays', () => {
* @param {number} options.steps - The number of steps to perform * @param {number} options.steps - The number of steps to perform
*/ */
const _clickConstrained = const _clickConstrained =
(page: Page, editor: EditorFixture) => (page: Page, editor: EditorFixture, cmdBar: CmdBarFixture) =>
async ({ async ({
hoverPos, hoverPos,
constraintType, constraintType,
@ -93,11 +94,7 @@ test.describe('Testing segment overlays', () => {
page.getByTestId('cmd-bar-arg-value').getByRole('textbox') page.getByTestId('cmd-bar-arg-value').getByRole('textbox')
).toBeFocused() ).toBeFocused()
await page.waitForTimeout(500) await page.waitForTimeout(500)
await page await cmdBar.continue()
.getByRole('button', {
name: 'arrow right Continue',
})
.click()
await editor.expectEditor.toContain(expectFinal, { await editor.expectEditor.toContain(expectFinal, {
shouldNormalise: true, shouldNormalise: true,
}) })
@ -113,7 +110,7 @@ test.describe('Testing segment overlays', () => {
* @param {number} options.steps - The number of steps to perform * @param {number} options.steps - The number of steps to perform
*/ */
const _clickUnconstrained = const _clickUnconstrained =
(page: Page, editor: EditorFixture) => (page: Page, editor: EditorFixture, cmdBar: CmdBarFixture) =>
async ({ async ({
hoverPos, hoverPos,
constraintType, constraintType,
@ -163,11 +160,7 @@ test.describe('Testing segment overlays', () => {
page.getByTestId('cmd-bar-arg-value').getByRole('textbox') page.getByTestId('cmd-bar-arg-value').getByRole('textbox')
).toBeFocused() ).toBeFocused()
await page.waitForTimeout(500) await page.waitForTimeout(500)
await page await cmdBar.continue()
.getByRole('button', {
name: 'arrow right Continue',
})
.click()
await editor.expectEditor.toContain(expectAfterUnconstrained, { await editor.expectEditor.toContain(expectAfterUnconstrained, {
shouldNormalise: true, shouldNormalise: true,
}) })
@ -239,8 +232,8 @@ test.describe('Testing segment overlays', () => {
await expect(page.getByTestId('segment-overlay')).toHaveCount(14) await expect(page.getByTestId('segment-overlay')).toHaveCount(14)
const clickUnconstrained = _clickUnconstrained(page, editor) const clickUnconstrained = _clickUnconstrained(page, editor, cmdBar)
const clickConstrained = _clickConstrained(page, editor) const clickConstrained = _clickConstrained(page, editor, cmdBar)
await u.openAndClearDebugPanel() await u.openAndClearDebugPanel()
await u.sendCustomCmd({ await u.sendCustomCmd({
@ -664,7 +657,7 @@ profile002 = circle(sketch001, center = [345, 0], radius = 238.38)
await expect( await expect(
page.getByTestId('cmd-bar-arg-value').getByRole('textbox') page.getByTestId('cmd-bar-arg-value').getByRole('textbox')
).toBeFocused() ).toBeFocused()
await page.getByRole('button', { name: 'arrow right Continue' }).click() await cmdBar.continue()
// Verify the X constraint was added // Verify the X constraint was added
await editor.expectEditor.toContain('center = [xAbs001, 0]', { await editor.expectEditor.toContain('center = [xAbs001, 0]', {
@ -682,7 +675,7 @@ profile002 = circle(sketch001, center = [345, 0], radius = 238.38)
await expect( await expect(
page.getByTestId('cmd-bar-arg-value').getByRole('textbox') page.getByTestId('cmd-bar-arg-value').getByRole('textbox')
).toBeFocused() ).toBeFocused()
await page.getByRole('button', { name: 'arrow right Continue' }).click() await cmdBar.continue()
// Verify the Y constraint was added // Verify the Y constraint was added
await editor.expectEditor.toContain('center = [xAbs001, yAbs001]', { await editor.expectEditor.toContain('center = [xAbs001, yAbs001]', {
@ -700,7 +693,7 @@ profile002 = circle(sketch001, center = [345, 0], radius = 238.38)
await expect( await expect(
page.getByTestId('cmd-bar-arg-value').getByRole('textbox') page.getByTestId('cmd-bar-arg-value').getByRole('textbox')
).toBeFocused() ).toBeFocused()
await page.getByRole('button', { name: 'arrow right Continue' }).click() await cmdBar.continue()
// Verify all constraints were added // Verify all constraints were added
await editor.expectEditor.toContain( await editor.expectEditor.toContain(
@ -887,7 +880,7 @@ profile003 = startProfile(sketch001, at = [64.39, 35.16])
await expect( await expect(
page.getByTestId('cmd-bar-arg-value').getByRole('textbox') page.getByTestId('cmd-bar-arg-value').getByRole('textbox')
).toBeFocused() ).toBeFocused()
await page.getByRole('button', { name: 'arrow right Continue' }).click() await cmdBar.continue()
// Verify the constraint was added // Verify the constraint was added
await editor.expectEditor.toContain( await editor.expectEditor.toContain(
@ -910,7 +903,7 @@ profile003 = startProfile(sketch001, at = [64.39, 35.16])
await expect( await expect(
page.getByTestId('cmd-bar-arg-value').getByRole('textbox') page.getByTestId('cmd-bar-arg-value').getByRole('textbox')
).toBeFocused() ).toBeFocused()
await page.getByRole('button', { name: 'arrow right Continue' }).click() await cmdBar.continue()
// Verify both constraints were added // Verify both constraints were added
await editor.expectEditor.toContain( await editor.expectEditor.toContain(
@ -935,7 +928,7 @@ profile003 = startProfile(sketch001, at = [64.39, 35.16])
await expect( await expect(
page.getByTestId('cmd-bar-arg-value').getByRole('textbox') page.getByTestId('cmd-bar-arg-value').getByRole('textbox')
).toBeFocused() ).toBeFocused()
await page.getByRole('button', { name: 'arrow right Continue' }).click() await cmdBar.continue()
// Verify the constraint was added // Verify the constraint was added
await editor.expectEditor.toContain('endAbsolute = [xAbs002, 84.07]', { await editor.expectEditor.toContain('endAbsolute = [xAbs002, 84.07]', {
@ -955,7 +948,7 @@ profile003 = startProfile(sketch001, at = [64.39, 35.16])
await expect( await expect(
page.getByTestId('cmd-bar-arg-value').getByRole('textbox') page.getByTestId('cmd-bar-arg-value').getByRole('textbox')
).toBeFocused() ).toBeFocused()
await page.getByRole('button', { name: 'arrow right Continue' }).click() await cmdBar.continue()
// Verify all constraints were added // Verify all constraints were added
await editor.expectEditor.toContain( await editor.expectEditor.toContain(

View File

@ -32,7 +32,7 @@ test('Units menu', async ({ page, homePage }) => {
test( test(
'Successful export shows a success toast', 'Successful export shows a success toast',
{ tag: '@skipLocalEngine' }, { tag: '@skipLocalEngine' },
async ({ page, homePage, tronApp }) => { async ({ page, homePage, cmdBar, tronApp }) => {
// FYI this test doesn't work with only engine running locally // FYI this test doesn't work with only engine running locally
// And you will need to have the KittyCAD CLI installed // And you will need to have the KittyCAD CLI installed
const u = await getUtils(page) const u = await getUtils(page)
@ -94,7 +94,8 @@ part001 = startSketchOn(-XZ)
presentation: 'pretty', presentation: 'pretty',
}, },
tronApp?.projectDirName, tronApp?.projectDirName,
page page,
cmdBar
) )
} }
) )
@ -254,6 +255,7 @@ test('First escape in tool pops you out of tool, second exits sketch mode', asyn
test('Basic default modeling and sketch hotkeys work', async ({ test('Basic default modeling and sketch hotkeys work', async ({
page, page,
homePage, homePage,
cmdBar,
}) => { }) => {
const u = await getUtils(page) const u = await getUtils(page)
await test.step(`Set up test`, async () => { await test.step(`Set up test`, async () => {
@ -397,11 +399,8 @@ test('Basic default modeling and sketch hotkeys work', async ({
await expect(page.getByRole('button', { name: 'Continue' })).toBeVisible({ await expect(page.getByRole('button', { name: 'Continue' })).toBeVisible({
timeout: 20_000, timeout: 20_000,
}) })
await page.getByRole('button', { name: 'Continue' }).click() await cmdBar.continue()
await expect( await cmdBar.submit()
page.getByRole('button', { name: 'Submit command' })
).toBeVisible()
await page.getByRole('button', { name: 'Submit command' }).click()
await expect(page.locator('.cm-content')).toContainText('extrude(') await expect(page.locator('.cm-content')).toContainText('extrude(')
}) })
@ -575,8 +574,7 @@ profile001 = startProfile(sketch002, at = [-12.34, 12.34])
await cmdBar.progressCmdBar() await cmdBar.progressCmdBar()
await cmdBar.progressCmdBar() await cmdBar.progressCmdBar()
await expect(page.getByText('Confirm Extrude')).toBeVisible() await cmdBar.submit()
await cmdBar.progressCmdBar()
const result2 = result.genNext` const result2 = result.genNext`
const sketch002 = extrude(sketch002, length = ${[5, 5]} + 7)` const sketch002 = extrude(sketch002, length = ${[5, 5]} + 7)`

View File

@ -21,7 +21,7 @@ export const CommandBar = () => {
const commandBarState = useCommandBarState() const commandBarState = useCommandBarState()
const { immediateState } = useNetworkContext() const { immediateState } = useNetworkContext()
const { const {
context: { selectedCommand, currentArgument, commands }, context: { selectedCommand, currentArgument, commands, argumentsToSubmit },
} = commandBarState } = commandBarState
const isArgumentThatShouldBeHardToDismiss = const isArgumentThatShouldBeHardToDismiss =
currentArgument?.inputType === 'selection' || currentArgument?.inputType === 'selection' ||
@ -68,16 +68,23 @@ export const CommandBar = () => {
}) })
function stepBack() { function stepBack() {
const entries = Object.entries(selectedCommand?.args || {}).filter(
([argName, arg]) => {
const argValue =
(typeof argumentsToSubmit[argName] === 'function'
? argumentsToSubmit[argName](commandBarState.context)
: argumentsToSubmit[argName]) || ''
const isRequired =
typeof arg.required === 'function'
? arg.required(commandBarState.context)
: arg.required
return !arg.hidden && (argValue || isRequired)
}
)
if (!currentArgument) { if (!currentArgument) {
if (commandBarState.matches('Review')) { if (commandBarState.matches('Review')) {
const entries = Object.entries(selectedCommand?.args || {}).filter(
([_, argConfig]) =>
!argConfig.hidden &&
(typeof argConfig.required === 'function'
? argConfig.required(commandBarState.context)
: argConfig.required)
)
const currentArgName = entries[entries.length - 1][0] const currentArgName = entries[entries.length - 1][0]
const currentArg = { const currentArg = {
name: currentArgName, name: currentArgName,
@ -94,9 +101,6 @@ export const CommandBar = () => {
commandBarActor.send({ type: 'Deselect command' }) commandBarActor.send({ type: 'Deselect command' })
} }
} else { } else {
const entries = Object.entries(selectedCommand?.args || {}).filter(
(a) => !a[1].hidden
)
const index = entries.findIndex( const index = entries.findIndex(
([key, _]) => key === currentArgument.name ([key, _]) => key === currentArgument.name
) )
@ -183,20 +187,6 @@ export const CommandBar = () => {
<kbd className="hotkey ml-4 dark:!bg-chalkboard-80">esc</kbd> <kbd className="hotkey ml-4 dark:!bg-chalkboard-80">esc</kbd>
</Tooltip> </Tooltip>
</button> </button>
{!commandBarState.matches('Selecting command') && (
<button onClick={stepBack} className="m-0 p-0 border-none">
<CustomIcon name="arrowLeft" className="w-5 h-5 rounded-sm" />
<Tooltip position="bottom">
Step back{' '}
<kbd className="hotkey ml-4 dark:!bg-chalkboard-80">
Shift
</kbd>
<kbd className="hotkey ml-4 dark:!bg-chalkboard-80">
Bksp
</kbd>
</Tooltip>
</button>
)}
</div> </div>
</WrapperComponent.Panel> </WrapperComponent.Panel>
</Transition.Child> </Transition.Child>

View File

@ -1,11 +1,12 @@
import CommandArgOptionInput from '@src/components/CommandBar/CommandArgOptionInput' import CommandArgOptionInput from '@src/components/CommandBar/CommandArgOptionInput'
import CommandBarBasicInput from '@src/components/CommandBar/CommandBarBasicInput' import CommandBarBasicInput from '@src/components/CommandBar/CommandBarBasicInput'
import CommandBarHeader from '@src/components/CommandBar/CommandBarHeader' import CommandBarHeaderFooter from '@src/components/CommandBar/CommandBarHeaderFooter'
import CommandBarKclInput from '@src/components/CommandBar/CommandBarKclInput' import CommandBarKclInput from '@src/components/CommandBar/CommandBarKclInput'
import CommandBarPathInput from '@src/components/CommandBar/CommandBarPathInput' import CommandBarPathInput from '@src/components/CommandBar/CommandBarPathInput'
import CommandBarSelectionInput from '@src/components/CommandBar/CommandBarSelectionInput' import CommandBarSelectionInput from '@src/components/CommandBar/CommandBarSelectionInput'
import CommandBarSelectionMixedInput from '@src/components/CommandBar/CommandBarSelectionMixedInput' import CommandBarSelectionMixedInput from '@src/components/CommandBar/CommandBarSelectionMixedInput'
import CommandBarTextareaInput from '@src/components/CommandBar/CommandBarTextareaInput' import CommandBarTextareaInput from '@src/components/CommandBar/CommandBarTextareaInput'
import CommandBarDivider from '@src/components/CommandBar/CommandBarDivider'
import type { CommandArgument } from '@src/lib/commandTypes' import type { CommandArgument } from '@src/lib/commandTypes'
import { commandBarActor, useCommandBarState } from '@src/lib/singletons' import { commandBarActor, useCommandBarState } from '@src/lib/singletons'
@ -28,13 +29,14 @@ function CommandBarArgument({ stepBack }: { stepBack: () => void }) {
return ( return (
currentArgument && ( currentArgument && (
<CommandBarHeader> <CommandBarHeaderFooter stepBack={stepBack}>
<ArgumentInput <ArgumentInput
arg={currentArgument} arg={currentArgument}
stepBack={stepBack} stepBack={stepBack}
onSubmit={onSubmit} onSubmit={onSubmit}
/> />
</CommandBarHeader> <CommandBarDivider />
</CommandBarHeaderFooter>
) )
) )
} }

View File

@ -0,0 +1,5 @@
export default function CommandBarDivider() {
return (
<div className="block w-full my-2 h-[1px] bg-chalkboard-20 dark:bg-chalkboard-80" />
)
}

View File

@ -5,6 +5,7 @@ import { useHotkeys } from 'react-hotkeys-hook'
import { ActionButton } from '@src/components/ActionButton' import { ActionButton } from '@src/components/ActionButton'
import { CustomIcon } from '@src/components/CustomIcon' import { CustomIcon } from '@src/components/CustomIcon'
import Tooltip from '@src/components/Tooltip' import Tooltip from '@src/components/Tooltip'
import CommandBarDivider from '@src/components/CommandBar/CommandBarDivider'
import type { import type {
KclCommandValue, KclCommandValue,
KclExpressionWithVariable, KclExpressionWithVariable,
@ -14,7 +15,10 @@ import { getSelectionTypeDisplayText } from '@src/lib/selections'
import { roundOff } from '@src/lib/utils' import { roundOff } from '@src/lib/utils'
import { commandBarActor, useCommandBarState } from '@src/lib/singletons' import { commandBarActor, useCommandBarState } from '@src/lib/singletons'
function CommandBarHeader({ children }: React.PropsWithChildren<object>) { function CommandBarHeaderFooter({
children,
stepBack,
}: React.PropsWithChildren<object> & { stepBack: () => void }) {
const commandBarState = useCommandBarState() const commandBarState = useCommandBarState()
const { const {
context: { selectedCommand, currentArgument, argumentsToSubmit }, context: { selectedCommand, currentArgument, argumentsToSubmit },
@ -102,19 +106,23 @@ function CommandBarHeader({ children }: React.PropsWithChildren<object>) {
<span className="pr-2" /> <span className="pr-2" />
)} )}
</p> </p>
{Object.entries(nonHiddenArgs || {}) {Object.entries(nonHiddenArgs || {}).flatMap(
.filter( ([argName, arg], i) => {
([_, argConfig]) =>
argConfig.skip === false ||
(typeof argConfig.required === 'function'
? argConfig.required(commandBarState.context)
: argConfig.required)
)
.map(([argName, arg], i) => {
const argValue = const argValue =
(typeof argumentsToSubmit[argName] === 'function' (typeof argumentsToSubmit[argName] === 'function'
? argumentsToSubmit[argName](commandBarState.context) ? argumentsToSubmit[argName](commandBarState.context)
: argumentsToSubmit[argName]) || '' : argumentsToSubmit[argName]) || ''
const isCurrentArg = argName === currentArgument?.name
const isSkipFalse = arg.skip === false
const isRequired =
typeof arg.required === 'function'
? arg.required(commandBarState.context)
: arg.required
// We actually want to show non-hidden optional args that have a value set already
if (!(argValue || isCurrentArg || isSkipFalse || isRequired)) {
return []
}
return ( return (
<button <button
@ -161,7 +169,9 @@ function CommandBarHeader({ children }: React.PropsWithChildren<object>) {
), ),
4 4
) )
) : arg.inputType === 'text' && !arg.valueSummary ? ( ) : arg.inputType === 'text' &&
!arg.valueSummary &&
typeof argValue === 'string' ? (
`${argValue.slice(0, 12)}${argValue.length > 12 ? '...' : ''}` `${argValue.slice(0, 12)}${argValue.length > 12 ? '...' : ''}`
) : typeof argValue === 'object' ? ( ) : typeof argValue === 'object' ? (
arg.valueSummary ? ( arg.valueSummary ? (
@ -207,8 +217,14 @@ function CommandBarHeader({ children }: React.PropsWithChildren<object>) {
)} )}
</button> </button>
) )
})} }
)}
</div> </div>
</div>
<CommandBarDivider />
{children}
<div className="px-4 pb-2 flex justify-between items-center gap-2">
<StepBackButton stepBack={stepBack} />
{isReviewing ? ( {isReviewing ? (
<ReviewingButton <ReviewingButton
bgClassName={ bgClassName={
@ -237,8 +253,6 @@ function CommandBarHeader({ children }: React.PropsWithChildren<object>) {
/> />
)} )}
</div> </div>
<div className="block w-full my-2 h-[1px] bg-chalkboard-20 dark:bg-chalkboard-80" />
{children}
</> </>
) )
) )
@ -258,16 +272,16 @@ function ReviewingButton({ bgClassName, iconClassName }: ButtonProps) {
ref={buttonRef} ref={buttonRef}
type="submit" type="submit"
form="review-form" form="review-form"
className="w-fit !p-0 rounded-sm hover:shadow focus:outline-current" className={`w-fit !p-0 rounded-sm hover:brightness-110 hover:shadow focus:outline-current ${bgClassName}`}
tabIndex={0} tabIndex={0}
data-testid="command-bar-submit" data-testid="command-bar-submit"
iconStart={{ iconEnd={{
icon: 'checkmark', icon: 'checkmark',
bgClassName: `p-1 rounded-sm hover:brightness-110 ${bgClassName}`, bgClassName: `p-1 rounded-sm ${bgClassName}`,
iconClassName: `${iconClassName}`, iconClassName: `${iconClassName}`,
}} }}
> >
<span className="sr-only">Submit command</span> <span className={`pl-2 ${iconClassName}`}>Submit</span>
</ActionButton> </ActionButton>
) )
} }
@ -278,18 +292,48 @@ function GatheringArgsButton({ bgClassName, iconClassName }: ButtonProps) {
Element="button" Element="button"
type="submit" type="submit"
form="arg-form" form="arg-form"
className="w-fit !p-0 rounded-sm hover:shadow focus:outline-current" className={`w-fit !p-0 rounded-sm hover:brightness-110 hover:shadow focus:outline-current ${bgClassName}`}
tabIndex={0} tabIndex={0}
data-testid="command-bar-continue" data-testid="command-bar-continue"
iconStart={{ iconEnd={{
icon: 'arrowRight', icon: 'arrowRight',
bgClassName: `p-1 rounded-sm hover:brightness-110 ${bgClassName}`, bgClassName: `p-1 rounded-sm ${bgClassName}`,
iconClassName: `${iconClassName}`, iconClassName: `${iconClassName}`,
}} }}
> >
<span className="sr-only">Continue</span> <span className={`pl-2 ${iconClassName}`}>Continue</span>
</ActionButton> </ActionButton>
) )
} }
export default CommandBarHeader function StepBackButton({
bgClassName,
iconClassName,
stepBack,
}: ButtonProps & { stepBack: () => void }) {
return (
<ActionButton
Element="button"
type="button"
form="arg-form"
className={`w-fit !p-0 rounded-sm hover:brightness-110 hover:shadow focus:outline-current bg-chalkboard-20/50 dark:bg-chalkboard-80/50 border-chalkboard-20 dark:border-chalkboard-80 ${bgClassName}`}
tabIndex={0}
data-testid="command-bar-step-back"
iconStart={{
icon: 'arrowLeft',
bgClassName: `p-1 rounded-sm bg-chalkboard-20/50 dark:bg-chalkboard-80/50 ${bgClassName}`,
iconClassName: `${iconClassName}`,
}}
onClick={stepBack}
>
<span className={`pr-2 ${iconClassName}`}>Step back</span>
<Tooltip position="bottom">
Step back
<kbd className="hotkey ml-4 dark:!bg-chalkboard-80">Shift</kbd>
<kbd className="hotkey ml-2 dark:!bg-chalkboard-80">Bksp</kbd>
</Tooltip>
</ActionButton>
)
}
export default CommandBarHeaderFooter

View File

@ -1,7 +1,10 @@
import { useHotkeys } from 'react-hotkeys-hook' import { useHotkeys } from 'react-hotkeys-hook'
import CommandBarHeader from '@src/components/CommandBar/CommandBarHeader' import CommandBarHeaderFooter from '@src/components/CommandBar/CommandBarHeaderFooter'
import CommandBarDivider from '@src/components/CommandBar/CommandBarDivider'
import { commandBarActor, useCommandBarState } from '@src/lib/singletons' import { commandBarActor, useCommandBarState } from '@src/lib/singletons'
import { useMemo } from 'react'
import { CustomIcon } from '@src/components/CustomIcon'
function CommandBarReview({ stepBack }: { stepBack: () => void }) { function CommandBarReview({ stepBack }: { stepBack: () => void }) {
const commandBarState = useCommandBarState() const commandBarState = useCommandBarState()
@ -57,19 +60,71 @@ function CommandBarReview({ stepBack }: { stepBack: () => void }) {
}) })
} }
const availableOptionalArgs = useMemo(() => {
if (!selectedCommand?.args) return undefined
const s = { ...selectedCommand.args }
for (const [name, arg] of Object.entries(s)) {
const value =
(typeof argumentsToSubmit[name] === 'function'
? argumentsToSubmit[name](commandBarState.context)
: argumentsToSubmit[name]) || ''
const isHidden =
typeof arg.hidden === 'function'
? arg.hidden(commandBarState.context)
: arg.hidden
const isRequired =
typeof arg.required === 'function'
? arg.required(commandBarState.context)
: arg.required
if (isHidden || isRequired || value) {
delete s[name]
}
}
return s
}, [selectedCommand, argumentsToSubmit, commandBarState.context])
return ( return (
<CommandBarHeader> <CommandBarHeaderFooter stepBack={stepBack}>
<p className="px-4 pb-2"> {selectedCommand?.reviewMessage && (
{selectedCommand?.reviewMessage ? ( <>
selectedCommand.reviewMessage instanceof Function ? ( <p className="px-4 py-2">
selectedCommand.reviewMessage(commandBarState.context) {selectedCommand.reviewMessage instanceof Function
) : ( ? selectedCommand.reviewMessage(commandBarState.context)
selectedCommand.reviewMessage : selectedCommand.reviewMessage}
) </p>
) : ( <CommandBarDivider />
<>Confirm {selectedCommand?.displayName || selectedCommand?.name}</> </>
)} )}
</p> {Object.entries(availableOptionalArgs || {}).length > 0 && (
<>
<div className="px-4 flex flex-wrap gap-2 items-baseline">
<span className="text-sm mr-4">Optional</span>
{Object.entries(availableOptionalArgs || {}).map(
([argName, arg]) => {
return (
<button
data-testid="cmd-bar-add-optional-arg"
type="button"
onClick={() => {
commandBarActor.send({
type: 'Edit argument',
data: { arg: { ...arg, name: argName } },
})
}}
key={argName}
className="w-fit px-2 py-1 m-0 rounded-sm flex gap-2 items-center border"
>
<span className="capitalize">
{arg.displayName || argName}
</span>
<CustomIcon name="plus" className="w-4 h-4" />
</button>
)
}
)}
</div>
<CommandBarDivider />
</>
)}
<form <form
id="review-form" id="review-form"
className="absolute opacity-0 inset-0 pointer-events-none" className="absolute opacity-0 inset-0 pointer-events-none"
@ -97,7 +152,7 @@ function CommandBarReview({ stepBack }: { stepBack: () => void }) {
) )
})} })}
</form> </form>
</CommandBarHeader> </CommandBarHeaderFooter>
) )
} }

View File

@ -275,6 +275,10 @@ export const modelingMachineCommandConfig: StateMachineCommandSetConfig<
['gltf', 'stl', 'ply'].includes( ['gltf', 'stl', 'ply'].includes(
commandContext.argumentsToSubmit.type as string commandContext.argumentsToSubmit.type as string
), ),
hidden: (commandContext) =>
!['gltf', 'stl', 'ply'].includes(
commandContext.argumentsToSubmit.type as string
),
options: (commandContext) => { options: (commandContext) => {
const type = commandContext.argumentsToSubmit.type as const type = commandContext.argumentsToSubmit.type as
| OutputTypeKey | OutputTypeKey
@ -428,9 +432,7 @@ export const modelingMachineCommandConfig: StateMachineCommandSetConfig<
sectional: { sectional: {
inputType: 'options', inputType: 'options',
skip: true, skip: true,
defaultValue: false, required: false,
hidden: false,
required: true,
options: [ options: [
{ name: 'False', value: false }, { name: 'False', value: false },
{ name: 'True', value: true }, { name: 'True', value: true },
@ -464,6 +466,7 @@ export const modelingMachineCommandConfig: StateMachineCommandSetConfig<
skip: true, skip: true,
inputType: 'text', inputType: 'text',
required: false, required: false,
hidden: true,
}, },
sketches: { sketches: {
inputType: 'selection', inputType: 'selection',
@ -484,27 +487,27 @@ export const modelingMachineCommandConfig: StateMachineCommandSetConfig<
hidden: (context) => Boolean(context.argumentsToSubmit.nodeToEdit), hidden: (context) => Boolean(context.argumentsToSubmit.nodeToEdit),
}, },
axis: { axis: {
required: (commandContext) => required: (context) =>
['Axis'].includes( ['Axis'].includes(context.argumentsToSubmit.axisOrEdge as string),
commandContext.argumentsToSubmit.axisOrEdge as string
),
inputType: 'options', inputType: 'options',
displayName: 'Sketch Axis', displayName: 'Sketch Axis',
options: [ options: [
{ name: 'X Axis', isCurrent: true, value: 'X' }, { name: 'X Axis', isCurrent: true, value: 'X' },
{ name: 'Y Axis', isCurrent: false, value: 'Y' }, { name: 'Y Axis', isCurrent: false, value: 'Y' },
], ],
hidden: (context) => Boolean(context.argumentsToSubmit.nodeToEdit), hidden: (context) =>
Boolean(context.argumentsToSubmit.nodeToEdit) ||
!['Axis'].includes(context.argumentsToSubmit.axisOrEdge as string),
}, },
edge: { edge: {
required: (commandContext) => required: (context) =>
['Edge'].includes( ['Edge'].includes(context.argumentsToSubmit.axisOrEdge as string),
commandContext.argumentsToSubmit.axisOrEdge as string
),
inputType: 'selection', inputType: 'selection',
selectionTypes: ['segment', 'sweepEdge', 'edgeCutEdge'], selectionTypes: ['segment', 'sweepEdge', 'edgeCutEdge'],
multiple: false, multiple: false,
hidden: (context) => Boolean(context.argumentsToSubmit.nodeToEdit), hidden: (context) =>
Boolean(context.argumentsToSubmit.nodeToEdit) ||
!['Edge'].includes(context.argumentsToSubmit.axisOrEdge as string),
}, },
angle: { angle: {
inputType: 'kcl', inputType: 'kcl',
@ -524,6 +527,7 @@ export const modelingMachineCommandConfig: StateMachineCommandSetConfig<
skip: true, skip: true,
inputType: 'text', inputType: 'text',
required: false, required: false,
hidden: true,
}, },
selection: { selection: {
inputType: 'selection', inputType: 'selection',