diff --git a/e2e/playwright/fixtures/cmdBarFixture.ts b/e2e/playwright/fixtures/cmdBarFixture.ts index d21ed6908..0b88f4e68 100644 --- a/e2e/playwright/fixtures/cmdBarFixture.ts +++ b/e2e/playwright/fixtures/cmdBarFixture.ts @@ -47,6 +47,8 @@ export class CmdBarFixture { private _serialiseCmdBar = async (): Promise => { if (!(await this.page.getByTestId('command-bar-wrapper').isVisible())) { return { stage: 'commandBarClosed' } + } else if (await this.page.getByTestId('cmd-bar-search').isVisible()) { + return { stage: 'pickCommand' } } const reviewForm = this.page.locator('#review-form') const getHeaderArgs = async () => { diff --git a/e2e/playwright/point-click.spec.ts b/e2e/playwright/point-click.spec.ts index 5f67b20de..1e0afb78a 100644 --- a/e2e/playwright/point-click.spec.ts +++ b/e2e/playwright/point-click.spec.ts @@ -2877,23 +2877,41 @@ extrude001 = extrude(profile001, length = 100) shapeColor: [number, number, number] ) { await toolbar.openPane('feature-tree') - const operationButton = await toolbar.getFeatureTreeOperation( - 'Extrude', - 0 - ) - await operationButton.click({ button: 'right' }) - const menuButton = page.getByTestId('context-menu-set-appearance') - await menuButton.click() - await cmdBar.expectState({ - commandName: 'Appearance', - currentArgKey: 'color', - currentArgValue: '', - headerArguments: { - Color: '', - }, - highlightedHeaderArg: 'color', - stage: 'arguments', + const enterAppearanceFlow = async (stepName: string) => + test.step(stepName, async () => { + const operationButton = await toolbar.getFeatureTreeOperation( + 'Extrude', + 0 + ) + await operationButton.click({ button: 'right' }) + const menuButton = page.getByTestId('context-menu-set-appearance') + await menuButton.click() + await cmdBar.expectState({ + commandName: 'Appearance', + currentArgKey: 'color', + currentArgValue: '', + headerArguments: { + Color: '', + }, + highlightedHeaderArg: 'color', + stage: 'arguments', + }) + }) + + await enterAppearanceFlow(`Open Set Appearance flow`) + + await test.step(`Validate hidden argument "nodeToEdit" can't be reached with Backspace`, async () => { + await page.keyboard.press('Backspace') + await cmdBar.expectState({ + stage: 'pickCommand', + }) + await page.keyboard.press('Escape') + await cmdBar.expectState({ + stage: 'commandBarClosed', + }) }) + + await enterAppearanceFlow(`Restart Appearance flow`) const item = page.getByText(option, { exact: true }) await item.click() await cmdBar.expectState({ diff --git a/src/components/CommandBar/CommandBar.tsx b/src/components/CommandBar/CommandBar.tsx index 93fd576d4..23589c46e 100644 --- a/src/components/CommandBar/CommandBar.tsx +++ b/src/components/CommandBar/CommandBar.tsx @@ -43,9 +43,10 @@ export const CommandBar = () => { if (commandBarState.matches('Review')) { const entries = Object.entries(selectedCommand?.args || {}).filter( ([_, argConfig]) => - typeof argConfig.required === 'function' + !argConfig.hidden && + (typeof argConfig.required === 'function' ? argConfig.required(commandBarState.context) - : argConfig.required + : argConfig.required) ) const currentArgName = entries[entries.length - 1][0] @@ -64,7 +65,9 @@ export const CommandBar = () => { commandBarActor.send({ type: 'Deselect command' }) } } else { - const entries = Object.entries(selectedCommand?.args || {}) + const entries = Object.entries(selectedCommand?.args || {}).filter( + (a) => !a[1].hidden + ) const index = entries.findIndex( ([key, _]) => key === currentArgument.name ) diff --git a/src/components/CommandBar/CommandBarHeader.tsx b/src/components/CommandBar/CommandBarHeader.tsx index 1fe4ebb8a..f02f82a25 100644 --- a/src/components/CommandBar/CommandBarHeader.tsx +++ b/src/components/CommandBar/CommandBarHeader.tsx @@ -1,5 +1,5 @@ import { CustomIcon } from '../CustomIcon' -import React, { useState } from 'react' +import React, { useMemo, useState } from 'react' import { ActionButton } from '../ActionButton' import { Selections, getSelectionTypeDisplayText } from 'lib/selections' import { useHotkeys } from 'react-hotkeys-hook' @@ -13,6 +13,14 @@ function CommandBarHeader({ children }: React.PropsWithChildren<{}>) { const { context: { selectedCommand, currentArgument, argumentsToSubmit }, } = commandBarState + const nonHiddenArgs = useMemo(() => { + if (!selectedCommand?.args) return undefined + const s = { ...selectedCommand.args } + for (const [name, arg] of Object.entries(s)) { + if (arg.hidden) delete s[name] + } + return s + }, [selectedCommand]) const isReviewing = commandBarState.matches('Review') const [showShortcuts, setShowShortcuts] = useState(false) @@ -43,11 +51,9 @@ function CommandBarHeader({ children }: React.PropsWithChildren<{}>) { ], (_, b) => { if (b.keys && !Number.isNaN(parseInt(b.keys[0], 10))) { - if (!selectedCommand?.args) return - const argName = Object.keys(selectedCommand.args)[ - parseInt(b.keys[0], 10) - 1 - ] - const arg = selectedCommand?.args[argName] + if (!nonHiddenArgs) return + const argName = Object.keys(nonHiddenArgs)[parseInt(b.keys[0], 10) - 1] + const arg = nonHiddenArgs[argName] if (!argName || !arg) return commandBarActor.send({ type: 'Change current argument', @@ -78,7 +84,7 @@ function CommandBarHeader({ children }: React.PropsWithChildren<{}>) { {selectedCommand.displayName || selectedCommand.name}

- {Object.entries(selectedCommand?.args || {}) + {Object.entries(nonHiddenArgs || {}) .filter(([_, argConfig]) => typeof argConfig.required === 'function' ? argConfig.required(commandBarState.context) diff --git a/src/lib/commandBarConfigs/modelingCommandConfig.ts b/src/lib/commandBarConfigs/modelingCommandConfig.ts index a9f0d54e3..191ea0b41 100644 --- a/src/lib/commandBarConfigs/modelingCommandConfig.ts +++ b/src/lib/commandBarConfigs/modelingCommandConfig.ts @@ -311,6 +311,7 @@ export const modelingMachineCommandConfig: StateMachineCommandSetConfig< skip: true, inputType: 'text', required: false, + hidden: true, }, selection: { inputType: 'selection', @@ -454,6 +455,7 @@ export const modelingMachineCommandConfig: StateMachineCommandSetConfig< skip: true, inputType: 'text', required: false, + hidden: true, }, plane: { inputType: 'selection', @@ -481,6 +483,7 @@ export const modelingMachineCommandConfig: StateMachineCommandSetConfig< skip: true, inputType: 'text', required: false, + hidden: true, }, revolutions: { inputType: 'kcl', @@ -702,6 +705,7 @@ export const modelingMachineCommandConfig: StateMachineCommandSetConfig< skip: true, inputType: 'text', required: false, + hidden: true, }, color: { inputType: 'options', diff --git a/src/lib/commandTypes.ts b/src/lib/commandTypes.ts index 0bb8ee579..791f78d23 100644 --- a/src/lib/commandTypes.ts +++ b/src/lib/commandTypes.ts @@ -119,6 +119,8 @@ export type CommandArgumentConfig< machineContext?: C ) => boolean) warningMessage?: string + /** If `true`, arg is used as passed-through data, never for user input */ + hidden?: boolean skip?: boolean /** For showing a summary display of the current value, such as in * the command bar's header @@ -233,6 +235,8 @@ export type CommandArgument< commandBarContext: { argumentsToSubmit: Record }, // Should be the commandbarMachine's context, but it creates a circular dependency machineContext?: ContextFrom ) => boolean) + /** If `true`, arg is used as passed-through data, never for user input */ + hidden?: boolean skip?: boolean machineActor?: Actor warningMessage?: string diff --git a/src/lib/createMachineCommand.ts b/src/lib/createMachineCommand.ts index c89cdd448..2e3fdaa24 100644 --- a/src/lib/createMachineCommand.ts +++ b/src/lib/createMachineCommand.ts @@ -162,6 +162,7 @@ export function buildCommandArgument< const baseCommandArgument = { description: arg.description, required: arg.required, + hidden: arg.hidden, skip: arg.skip, machineActor, valueSummary: arg.valueSummary, diff --git a/src/machines/commandBarMachine.ts b/src/machines/commandBarMachine.ts index 95e91600b..aa6673ce8 100644 --- a/src/machines/commandBarMachine.ts +++ b/src/machines/commandBarMachine.ts @@ -132,14 +132,15 @@ export const commandBarMachine = setup({ // Find the first argument that is not to be skipped: // that is, the first argument that is not already in the argumentsToSubmit - // or that is not undefined, or that is not marked as "skippable". + // or hidden, or that is not undefined, or that is not marked as "skippable". // TODO validate the type of the existing arguments + const nonHiddenArgs = Object.entries(selectedCommand.args).filter( + (a) => !a[1].hidden + ) let argIndex = 0 - while (argIndex < Object.keys(selectedCommand.args).length) { - const [argName, argConfig] = Object.entries(selectedCommand.args)[ - argIndex - ] + while (argIndex < nonHiddenArgs.length) { + const [argName, argConfig] = nonHiddenArgs[argIndex] const argIsRequired = typeof argConfig.required === 'function' ? argConfig.required(context) @@ -155,7 +156,7 @@ export const commandBarMachine = setup({ if ( mustNotSkipArg === true || - argIndex + 1 === Object.keys(selectedCommand.args).length + argIndex + 1 === Object.keys(nonHiddenArgs).length ) { // If we have reached the end of the arguments and none are skippable, // return the last argument. @@ -259,7 +260,7 @@ export const commandBarMachine = setup({ }, 'All arguments are skippable': ({ context }) => { return Object.values(context.selectedCommand!.args!).every( - (argConfig) => argConfig.skip + (argConfig) => argConfig.skip || argConfig.hidden ) }, 'Has selected command': ({ context }) => !!context.selectedCommand,