Compare commits

...

23 Commits

Author SHA1 Message Date
147268eaa1 Clean up 2025-06-18 10:33:13 -04:00
7f1552b667 back to reg width 2025-06-18 09:53:03 -04:00
e6c9a0f03c WIP: footer buttons 2025-06-18 09:19:41 -04:00
3a9ce876ac Add typecheck 2025-06-17 17:07:14 -04:00
a0edee7393 Fix nodeToEdit not having hidden: true on Shell 2025-06-17 16:41:11 -04:00
9ab00a449a Fixed up more tests related to sweep behavior change 2025-06-17 15:43:29 -04:00
2042339c99 More fixes 2025-06-17 15:19:53 -04:00
92bd8a9f61 Upgrade e2e tests to cmdBar fixtures with fixes 2025-06-17 15:04:14 -04:00
50fb46185e Fix labels for tests 2025-06-17 14:49:15 -04:00
edc4b75077 Remove options bool icon 2025-06-17 14:46:52 -04:00
9c554b4179 More UI polish 2025-06-17 11:56:55 -04:00
78a47ab3e1 Clean up extrude specific changes 2025-06-17 10:48:47 -04:00
fc6a9e33e4 Labels in progress button and option arg section heading 2025-06-17 10:46:07 -04:00
a4c00d0d2c Proper optional args line in review 2025-06-17 10:01:50 -04:00
164df4e864 Merge branch 'main' into pierremtb/issue7495-add-bidirectional-args-to-point-and-click-extrude 2025-06-17 09:15:13 -04:00
d8c168e7f8 Add twistAng 2025-06-16 15:50:56 -04:00
8706767b9f Merge branch 'main' into pierremtb/issue7495-add-bidirectional-args-to-point-and-click-extrude 2025-06-16 14:25:56 -04:00
8138f74cbd WIP 2025-06-16 14:25:22 -04:00
7e578f6d57 Make currentArg always part of header 2025-06-16 13:06:13 -04:00
e8ab2ccbe3 Add bidirectionalLength 2025-06-16 12:57:38 -04:00
4f3742c826 Show skip true args in header in review phase 2025-06-16 12:03:15 -04:00
3339da96b5 Wire up edit flow for symmetric 2025-06-16 11:32:35 -04:00
1a1a152fbf WIP: Add bidirectional args to point-and-click Extrude
Will eventually close #7495
2025-06-16 11:20:57 -04:00
16 changed files with 198 additions and 177 deletions

View File

@ -307,7 +307,7 @@ test.describe('Command bar tests', () => {
)
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()
// Review step and argument hotkeys

View File

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

View File

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

View File

@ -1829,7 +1829,6 @@ profile002 = startProfile(sketch002, at = [0, 0])
currentArgKey: 'sketches',
currentArgValue: '',
headerArguments: {
Sectional: '',
Profiles: '',
Path: '',
},
@ -1843,7 +1842,6 @@ profile002 = startProfile(sketch002, at = [0, 0])
currentArgKey: 'path',
currentArgValue: '',
headerArguments: {
Sectional: '',
Profiles: '1 profile',
Path: '',
},
@ -1856,7 +1854,6 @@ profile002 = startProfile(sketch002, at = [0, 0])
currentArgKey: 'path',
currentArgValue: '',
headerArguments: {
Sectional: '',
Profiles: '1 profile',
Path: '',
},
@ -1869,7 +1866,6 @@ profile002 = startProfile(sketch002, at = [0, 0])
headerArguments: {
Profiles: '1 profile',
Path: '1 segment',
Sectional: '',
},
stage: 'review',
})
@ -1894,6 +1890,9 @@ profile002 = startProfile(sketch002, at = [0, 0])
0
)
await operationButton.dblclick({ button: 'left' })
await page
.getByRole('button', { name: 'sectional', exact: false })
.click()
await cmdBar.expectState({
commandName: 'Sweep',
currentArgKey: 'sectional',
@ -1971,7 +1970,6 @@ profile001 = ${circleCode}`
currentArgKey: 'sketches',
currentArgValue: '',
headerArguments: {
Sectional: '',
Profiles: '',
Path: '',
},
@ -1986,7 +1984,6 @@ profile001 = ${circleCode}`
currentArgKey: 'path',
currentArgValue: '',
headerArguments: {
Sectional: '',
Profiles: '1 profile',
Path: '',
},
@ -2000,7 +1997,6 @@ profile001 = ${circleCode}`
currentArgKey: 'path',
currentArgValue: '',
headerArguments: {
Sectional: '',
Profiles: '1 profile',
Path: '',
},
@ -2013,7 +2009,6 @@ profile001 = ${circleCode}`
headerArguments: {
Profiles: '1 profile',
Path: '1 helix',
Sectional: '',
},
stage: 'review',
})
@ -4734,7 +4729,6 @@ path001 = startProfile(sketch001, at = [0, 0])
headerArguments: {
Profiles: '',
Path: '',
Sectional: '',
},
highlightedHeaderArg: 'Profiles',
commandName: 'Sweep',
@ -4747,7 +4741,6 @@ path001 = startProfile(sketch001, at = [0, 0])
headerArguments: {
Profiles: '2 profiles',
Path: '',
Sectional: '',
},
highlightedHeaderArg: 'path',
commandName: 'Sweep',
@ -4760,7 +4753,6 @@ path001 = startProfile(sketch001, at = [0, 0])
headerArguments: {
Profiles: '2 profiles',
Path: '1 segment',
Sectional: '',
},
commandName: 'Sweep',
})

View File

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

View File

@ -1,6 +1,7 @@
import path from 'path'
import { bracket } from '@e2e/playwright/fixtures/bracket'
import type { Page } from '@playwright/test'
import type { CmdBarFixture } from '@e2e/playwright/fixtures/cmdBarFixture'
import { reportRejection } from '@src/lib/trap'
import * as fsp from 'fs/promises'
@ -421,10 +422,7 @@ extrude002 = extrude(profile002, length = 150)
await page.keyboard.press('Enter')
// Click the checkbox
const submitButton = page.getByText('Confirm Export')
await expect(submitButton).toBeVisible()
await page.keyboard.press('Enter')
await cmdBar.submit()
// Find the toast.
// Look out for the toast message
@ -461,8 +459,7 @@ extrude002 = extrude(profile002, length = 150)
await page.keyboard.press('Enter')
// Click the checkbox
await expect(submitButton).toBeVisible()
await page.keyboard.press('Enter')
await cmdBar.submit()
// Find the toast.
// 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 ({
page,
homePage,
cmdBar,
}) => {
const u = await getUtils(page)
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`)
await test.step('second export', async () => {
await clickExportButton(page)
await clickExportButton(page, cmdBar)
await expect(exportingToastMessage).toBeVisible()
await clickExportButton(page)
await clickExportButton(page, cmdBar)
await test.step('The first export still succeeds', async () => {
await Promise.all([
@ -537,7 +535,7 @@ extrude002 = extrude(profile002, length = 150)
await test.step('Successful, unblocked export', async () => {
// Try exporting again.
await clickExportButton(page)
await clickExportButton(page, cmdBar)
// Find the toast.
// 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 () => {
// export the model
const exportButton = page.getByTestId('export-pane-button')
@ -896,9 +894,6 @@ async function clickExportButton(page: Page) {
await page.keyboard.press('Enter')
// Click the checkbox
const submitButton = page.getByText('Confirm Export')
await expect(submitButton).toBeVisible()
await page.keyboard.press('Enter')
await cmdBar.submit()
})
}

View File

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

View File

@ -10,7 +10,7 @@ import {
import { expect, test } from '@e2e/playwright/zoo-test'
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 () => {
localStorage.setItem(
'persistCode',
@ -50,11 +50,7 @@ test.describe('Testing constraints', () => {
await page.waitForTimeout(100)
await page.getByTestId('constraint-length').click()
await page.getByTestId('cmd-bar-arg-value').getByRole('textbox').fill('20')
await page
.getByRole('button', {
name: 'arrow right Continue',
})
.click()
await cmdBar.continue()
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)`
@ -681,9 +677,6 @@ test.describe('Testing constraints', () => {
.getByRole('textbox')
const cmdBarKclVariableNameInput =
page.getByPlaceholder('Variable name')
const cmdBarSubmitButton = page.getByRole('button', {
name: 'arrow right Continue',
})
await page.addInitScript(async () => {
localStorage.setItem(
@ -736,7 +729,7 @@ part002 = startSketchOn(XZ)
await page.waitForTimeout(500)
const [ang, len] = value.split(', ')
const changedCode = `|> angledLine(angle = ${ang}, length = ${len})`
await cmdBarSubmitButton.click()
await cmdBar.continue()
await expect(page.locator('.cm-content')).toContainText(changedCode)
// checking active assures the cursor is where it should be
@ -1101,11 +1094,7 @@ part002 = startSketchOn(XZ)
await page.waitForTimeout(500)
await page.getByTestId('cmd-bar-arg-value').getByRole('textbox').fill('10')
await page
.getByRole('button', {
name: 'arrow right Continue',
})
.click()
await cmdBar.continue()
await pollEditorLinesSelectedLength(page, 1)
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
test.fail(
'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)
await test.step(`Test setup`, async () => {
await context.addInitScript((code) => {
@ -52,9 +52,6 @@ test.describe('Testing loading external models', () => {
name,
})
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 u.openKclCodePanel()
@ -70,7 +67,7 @@ test.describe('Testing loading external models', () => {
await expect(commandMethodOption('Create new file')).not.toBeVisible()
await commandMethodOption('Overwrite').click()
await expect(warningText).toBeVisible()
await confirmButton.click()
await cmdBar.submit()
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 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 { 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
*/
const _clickConstrained =
(page: Page, editor: EditorFixture) =>
(page: Page, editor: EditorFixture, cmdBar: CmdBarFixture) =>
async ({
hoverPos,
constraintType,
@ -93,11 +94,7 @@ test.describe('Testing segment overlays', () => {
page.getByTestId('cmd-bar-arg-value').getByRole('textbox')
).toBeFocused()
await page.waitForTimeout(500)
await page
.getByRole('button', {
name: 'arrow right Continue',
})
.click()
await cmdBar.continue()
await editor.expectEditor.toContain(expectFinal, {
shouldNormalise: true,
})
@ -113,7 +110,7 @@ test.describe('Testing segment overlays', () => {
* @param {number} options.steps - The number of steps to perform
*/
const _clickUnconstrained =
(page: Page, editor: EditorFixture) =>
(page: Page, editor: EditorFixture, cmdBar: CmdBarFixture) =>
async ({
hoverPos,
constraintType,
@ -163,11 +160,7 @@ test.describe('Testing segment overlays', () => {
page.getByTestId('cmd-bar-arg-value').getByRole('textbox')
).toBeFocused()
await page.waitForTimeout(500)
await page
.getByRole('button', {
name: 'arrow right Continue',
})
.click()
await cmdBar.continue()
await editor.expectEditor.toContain(expectAfterUnconstrained, {
shouldNormalise: true,
})
@ -239,8 +232,8 @@ test.describe('Testing segment overlays', () => {
await expect(page.getByTestId('segment-overlay')).toHaveCount(14)
const clickUnconstrained = _clickUnconstrained(page, editor)
const clickConstrained = _clickConstrained(page, editor)
const clickUnconstrained = _clickUnconstrained(page, editor, cmdBar)
const clickConstrained = _clickConstrained(page, editor, cmdBar)
await u.openAndClearDebugPanel()
await u.sendCustomCmd({
@ -664,7 +657,7 @@ profile002 = circle(sketch001, center = [345, 0], radius = 238.38)
await expect(
page.getByTestId('cmd-bar-arg-value').getByRole('textbox')
).toBeFocused()
await page.getByRole('button', { name: 'arrow right Continue' }).click()
await cmdBar.continue()
// Verify the X constraint was added
await editor.expectEditor.toContain('center = [xAbs001, 0]', {
@ -682,7 +675,7 @@ profile002 = circle(sketch001, center = [345, 0], radius = 238.38)
await expect(
page.getByTestId('cmd-bar-arg-value').getByRole('textbox')
).toBeFocused()
await page.getByRole('button', { name: 'arrow right Continue' }).click()
await cmdBar.continue()
// Verify the Y constraint was added
await editor.expectEditor.toContain('center = [xAbs001, yAbs001]', {
@ -700,7 +693,7 @@ profile002 = circle(sketch001, center = [345, 0], radius = 238.38)
await expect(
page.getByTestId('cmd-bar-arg-value').getByRole('textbox')
).toBeFocused()
await page.getByRole('button', { name: 'arrow right Continue' }).click()
await cmdBar.continue()
// Verify all constraints were added
await editor.expectEditor.toContain(
@ -887,7 +880,7 @@ profile003 = startProfile(sketch001, at = [64.39, 35.16])
await expect(
page.getByTestId('cmd-bar-arg-value').getByRole('textbox')
).toBeFocused()
await page.getByRole('button', { name: 'arrow right Continue' }).click()
await cmdBar.continue()
// Verify the constraint was added
await editor.expectEditor.toContain(
@ -910,7 +903,7 @@ profile003 = startProfile(sketch001, at = [64.39, 35.16])
await expect(
page.getByTestId('cmd-bar-arg-value').getByRole('textbox')
).toBeFocused()
await page.getByRole('button', { name: 'arrow right Continue' }).click()
await cmdBar.continue()
// Verify both constraints were added
await editor.expectEditor.toContain(
@ -935,7 +928,7 @@ profile003 = startProfile(sketch001, at = [64.39, 35.16])
await expect(
page.getByTestId('cmd-bar-arg-value').getByRole('textbox')
).toBeFocused()
await page.getByRole('button', { name: 'arrow right Continue' }).click()
await cmdBar.continue()
// Verify the constraint was added
await editor.expectEditor.toContain('endAbsolute = [xAbs002, 84.07]', {
@ -955,7 +948,7 @@ profile003 = startProfile(sketch001, at = [64.39, 35.16])
await expect(
page.getByTestId('cmd-bar-arg-value').getByRole('textbox')
).toBeFocused()
await page.getByRole('button', { name: 'arrow right Continue' }).click()
await cmdBar.continue()
// Verify all constraints were added
await editor.expectEditor.toContain(

View File

@ -32,7 +32,7 @@ test('Units menu', async ({ page, homePage }) => {
test(
'Successful export shows a success toast',
{ tag: '@skipLocalEngine' },
async ({ page, homePage, tronApp }) => {
async ({ page, homePage, cmdBar, tronApp }) => {
// FYI this test doesn't work with only engine running locally
// And you will need to have the KittyCAD CLI installed
const u = await getUtils(page)
@ -94,7 +94,8 @@ part001 = startSketchOn(-XZ)
presentation: 'pretty',
},
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 ({
page,
homePage,
cmdBar,
}) => {
const u = await getUtils(page)
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({
timeout: 20_000,
})
await page.getByRole('button', { name: 'Continue' }).click()
await expect(
page.getByRole('button', { name: 'Submit command' })
).toBeVisible()
await page.getByRole('button', { name: 'Submit command' }).click()
await cmdBar.continue()
await cmdBar.submit()
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 expect(page.getByText('Confirm Extrude')).toBeVisible()
await cmdBar.progressCmdBar()
await cmdBar.submit()
const result2 = result.genNext`
const sketch002 = extrude(sketch002, length = ${[5, 5]} + 7)`

View File

@ -183,7 +183,7 @@ export const CommandBar = () => {
<kbd className="hotkey ml-4 dark:!bg-chalkboard-80">esc</kbd>
</Tooltip>
</button>
{!commandBarState.matches('Selecting command') && (
{/* {!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">
@ -196,7 +196,7 @@ export const CommandBar = () => {
</kbd>
</Tooltip>
</button>
)}
)} */}
</div>
</WrapperComponent.Panel>
</Transition.Child>

View File

@ -28,7 +28,7 @@ function CommandBarArgument({ stepBack }: { stepBack: () => void }) {
return (
currentArgument && (
<CommandBarHeader>
<CommandBarHeader stepBack={stepBack}>
<ArgumentInput
arg={currentArgument}
stepBack={stepBack}

View File

@ -14,7 +14,10 @@ import { getSelectionTypeDisplayText } from '@src/lib/selections'
import { roundOff } from '@src/lib/utils'
import { commandBarActor, useCommandBarState } from '@src/lib/singletons'
function CommandBarHeader({ children }: React.PropsWithChildren<object>) {
function CommandBarHeader({
children,
stepBack,
}: React.PropsWithChildren<object> & { stepBack: () => void }) {
const commandBarState = useCommandBarState()
const {
context: { selectedCommand, currentArgument, argumentsToSubmit },
@ -76,6 +79,20 @@ function CommandBarHeader({ children }: React.PropsWithChildren<object>) {
[argumentsToSubmit, selectedCommand]
)
const availableOptionalArgs = Object.entries(nonHiddenArgs || {}).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 !(isRequired || argValue)
}
)
return (
selectedCommand &&
argumentsToSubmit && (
@ -102,19 +119,22 @@ function CommandBarHeader({ children }: React.PropsWithChildren<object>) {
<span className="pr-2" />
)}
</p>
{Object.entries(nonHiddenArgs || {})
.filter(
([_, argConfig]) =>
argConfig.skip === false ||
(typeof argConfig.required === 'function'
? argConfig.required(commandBarState.context)
: argConfig.required)
)
.map(([argName, arg], i) => {
{Object.entries(nonHiddenArgs || {}).flatMap(
([argName, arg], i) => {
const argValue =
(typeof argumentsToSubmit[argName] === 'function'
? argumentsToSubmit[argName](commandBarState.context)
: argumentsToSubmit[argName]) || ''
const isCurrentArg = argName === currentArgument?.name
const isSkipFalse = arg.skip === false
const isRequired =
typeof arg.required === 'function'
? arg.required(commandBarState.context)
: arg.required
if (!(argValue || isCurrentArg || isSkipFalse || isRequired)) {
return []
}
return (
<button
@ -161,7 +181,9 @@ function CommandBarHeader({ children }: React.PropsWithChildren<object>) {
),
4
)
) : arg.inputType === 'text' && !arg.valueSummary ? (
) : arg.inputType === 'text' &&
!arg.valueSummary &&
typeof argValue === 'string' ? (
`${argValue.slice(0, 12)}${argValue.length > 12 ? '...' : ''}`
) : typeof argValue === 'object' ? (
arg.valueSummary ? (
@ -207,38 +229,92 @@ function CommandBarHeader({ children }: React.PropsWithChildren<object>) {
)}
</button>
)
})}
}
)}
</div>
{isReviewing ? (
<ReviewingButton
bgClassName={
selectedCommand.status === 'experimental'
? '!bg-ml-green'
: '!bg-primary'
}
iconClassName={
selectedCommand.status === 'experimental'
? '!text-ml-black'
: '!text-chalkboard-10'
}
/>
) : (
<GatheringArgsButton
bgClassName={
selectedCommand.status === 'experimental'
? '!bg-ml-green'
: '!bg-primary'
}
iconClassName={
selectedCommand.status === 'experimental'
? '!text-ml-black'
: '!text-chalkboard-10'
}
/>
)}
</div>
<div className="block w-full my-2 h-[1px] bg-chalkboard-20 dark:bg-chalkboard-80" />
{isReviewing && availableOptionalArgs.length > 0 && (
<>
<div className="px-4">
<p className="mb-2 text-sm">Optional arguments</p>
<div className="text-sm flex gap-4 items-start">
<div className="flex flex-1 flex-wrap gap-2">
{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>
</div>
</div>
</>
)}
{children}
<>
<div className="block w-full mb-2 h-[1px] bg-chalkboard-20 dark:bg-chalkboard-80" />
<div className="px-4 pb-2 flex justify-between items-center gap-2">
<ActionButton
Element="button"
type="button"
form="review-form"
className={`w-fit !p-0 rounded-sm hover:brightness-110 hover:shadow focus:outline-current`}
tabIndex={0}
data-testid="command-bar-submit"
iconStart={{
icon: 'arrowLeft',
bgClassName: `p-1 rounded-sm`,
iconClassName: ``,
}}
onClick={stepBack}
>
<span className={`pr-2`}>Step back</span>
</ActionButton>
{isReviewing ? (
<ReviewingButton
bgClassName={
selectedCommand.status === 'experimental'
? '!bg-ml-green'
: '!bg-primary'
}
iconClassName={
selectedCommand.status === 'experimental'
? '!text-ml-black'
: '!text-chalkboard-10'
}
/>
) : (
<GatheringArgsButton
bgClassName={
selectedCommand.status === 'experimental'
? '!bg-ml-green'
: '!bg-primary'
}
iconClassName={
selectedCommand.status === 'experimental'
? '!text-ml-black'
: '!text-chalkboard-10'
}
/>
)}
</div>
</>
</>
)
)
@ -258,16 +334,16 @@ function ReviewingButton({ bgClassName, iconClassName }: ButtonProps) {
ref={buttonRef}
type="submit"
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}
data-testid="command-bar-submit"
iconStart={{
iconEnd={{
icon: 'checkmark',
bgClassName: `p-1 rounded-sm hover:brightness-110 ${bgClassName}`,
bgClassName: `p-1 rounded-sm ${bgClassName}`,
iconClassName: `${iconClassName}`,
}}
>
<span className="sr-only">Submit command</span>
<span className={`pl-2 ${iconClassName}`}>Submit</span>
</ActionButton>
)
}
@ -278,16 +354,16 @@ function GatheringArgsButton({ bgClassName, iconClassName }: ButtonProps) {
Element="button"
type="submit"
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}
data-testid="command-bar-continue"
iconStart={{
iconEnd={{
icon: 'arrowRight',
bgClassName: `p-1 rounded-sm hover:brightness-110 ${bgClassName}`,
bgClassName: `p-1 rounded-sm ${bgClassName}`,
iconClassName: `${iconClassName}`,
}}
>
<span className="sr-only">Continue</span>
<span className={`pl-2 ${iconClassName}`}>Continue</span>
</ActionButton>
)
}

View File

@ -58,8 +58,8 @@ function CommandBarReview({ stepBack }: { stepBack: () => void }) {
}
return (
<CommandBarHeader>
<p className="px-4 pb-2">
<CommandBarHeader stepBack={stepBack}>
<p className="px-4 py-2">
{selectedCommand?.reviewMessage ? (
selectedCommand.reviewMessage instanceof Function ? (
selectedCommand.reviewMessage(commandBarState.context)

View File

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