Add support for hidden command arguments (#5534)
* Add configuration/type support for `hidden` * Add UI support for `hidden` configuration * Make `nodeToEdit` args hidden * A snapshot a day keeps the bugs away! 📷🐛 (OS: namespace-profile-ubuntu-8-cores) * Fix cmdBarFixture to actually serialize to "pickCommand" case * Add test step to ensure hidden commands can't be backed into * A snapshot a day keeps the bugs away! 📷🐛 (OS: namespace-profile-ubuntu-8-cores) --------- Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
This commit is contained in:
@ -47,6 +47,8 @@ export class CmdBarFixture {
|
||||
private _serialiseCmdBar = async (): Promise<CmdBarSerialised> => {
|
||||
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 () => {
|
||||
|
@ -2877,6 +2877,8 @@ extrude001 = extrude(profile001, length = 100)
|
||||
shapeColor: [number, number, number]
|
||||
) {
|
||||
await toolbar.openPane('feature-tree')
|
||||
const enterAppearanceFlow = async (stepName: string) =>
|
||||
test.step(stepName, async () => {
|
||||
const operationButton = await toolbar.getFeatureTreeOperation(
|
||||
'Extrude',
|
||||
0
|
||||
@ -2894,6 +2896,22 @@ extrude001 = extrude(profile001, length = 100)
|
||||
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({
|
||||
|
@ -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
|
||||
)
|
||||
|
@ -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}
|
||||
</span>
|
||||
</p>
|
||||
{Object.entries(selectedCommand?.args || {})
|
||||
{Object.entries(nonHiddenArgs || {})
|
||||
.filter(([_, argConfig]) =>
|
||||
typeof argConfig.required === 'function'
|
||||
? argConfig.required(commandBarState.context)
|
||||
|
@ -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',
|
||||
|
@ -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<string, unknown> }, // Should be the commandbarMachine's context, but it creates a circular dependency
|
||||
machineContext?: ContextFrom<T>
|
||||
) => boolean)
|
||||
/** If `true`, arg is used as passed-through data, never for user input */
|
||||
hidden?: boolean
|
||||
skip?: boolean
|
||||
machineActor?: Actor<T>
|
||||
warningMessage?: string
|
||||
|
@ -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,
|
||||
|
@ -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,
|
||||
|
Reference in New Issue
Block a user