Add standard command menu items (#18527)
## Add standard command menu items
### Summary
This PR introduces standard command menu items, migrating hardcoded
command menu actions to the backend command menu item architecture
powered by front components. It adds a new `twenty-standard-application`
package that defines, builds, and registers front components as standard
command menu items, gated behind the `IS_COMMAND_MENU_ITEM_ENABLED`
feature flag.
### Description
- **New `twenty-standard-application` package**: Contains front
component definitions with an esbuild-based build pipeline that
generates minified `.mjs` bundles and a manifest with checksums.
- **Server-side registration**: New constants register all items with
metadata (labels, icons, positions, availability types, conditional
expressions). A `StandardFrontComponentUploadService` uploads built
components to file storage.
- **`FALLBACK` availability type**: New enum value for command menu
items that appear as fallback options (e.g., "Search Records" fallback).
- **`CommandMenuContextApi` refactor**
- **Conditional availability enhancements**: New array-based helper
functions for evaluating multi-record conditions.
- **Frontend wiring** (twenty-front):
`useCommandMenuItemFrontComponentCommands`
## Next steps
Only simple commands have been implemented for now:
- **Navigation (9)** -- `CommandLink`: go-to-companies,
go-to-dashboards, go-to-notes, go-to-opportunities, go-to-people,
go-to-runs, go-to-settings, go-to-tasks, go-to-workflows
- **Side panel (4)** -- `CommandOpenSidePanelPage`: ask-ai,
search-records, search-records-fallback, view-previous-ai-chats
We still have to implement front components for all the following
commands:
All have placeholder `execute` logic (`async () => {}`) with a `// TODO:
implement execute logic` comment:
**Record (22)**
- `add-to-favorites`, `remove-from-favorites`
- `create-new-record`, `create-new-view`
- `delete-single-record`, `delete-multiple-records`
- `destroy-single-record`, `destroy-multiple-records`
- `restore-single-record`, `restore-multiple-records`
- `export-from-record-index`, `export-from-record-show`,
`export-multiple-records`, `export-note-to-pdf`, `export-view`
- `hide-deleted-records`, `see-deleted-records`
- `import-records`, `merge-multiple-records`, `update-multiple-records`
- `navigate-to-next-record`, `navigate-to-previous-record`
**Page layout (3)** -- `cancel-record-page-layout`,
`edit-record-page-layout`, `save-record-page-layout`
**Dashboard (4)** -- `cancel-dashboard-layout`, `duplicate-dashboard`,
`edit-dashboard-layout`, `save-dashboard-layout`
**Workflow (10)** -- `activate-workflow`, `add-node-workflow`,
`deactivate-workflow`, `discard-draft-workflow`, `duplicate-workflow`,
`see-active-version-workflow`, `see-runs-workflow`,
`see-versions-workflow`, `test-workflow`, `tidy-up-workflow`
**Workflow version (4)** -- `see-runs-workflow-version`,
`see-versions-workflow-version`, `see-workflow-workflow-version`,
`use-as-draft-workflow-version`
**Workflow run (3)** -- `see-version-workflow-run`,
`see-workflow-workflow-run`, `stop-workflow-run`
This commit is contained in:
+18
-11
@@ -1,16 +1,18 @@
|
||||
import { useRunWorkflowRecordCommands } from '@/command-menu-item/record/workflow/hooks/useRunWorkflowRecordCommands';
|
||||
import { useRunWorkflowRecordAgnosticCommands } from '@/command-menu-item/record-agnostic/workflow/hooks/useRunWorkflowRecordAgnosticCommands';
|
||||
import {
|
||||
CommandMenuContext,
|
||||
type CommandMenuContextType,
|
||||
} from '@/command-menu-item/contexts/CommandMenuContext';
|
||||
import { useCommandMenuContextApi } from '@/command-menu-item/hooks/useCommandMenuContextApi';
|
||||
import { useCommandMenuItemFrontComponentCommands } from '@/command-menu-item/hooks/useCommandMenuItemFrontComponentCommands';
|
||||
import { useRegisteredCommandMenuItems } from '@/command-menu-item/hooks/useRegisteredCommandMenuItems';
|
||||
import { useShouldCommandMenuItemBeRegisteredParams } from '@/command-menu-item/hooks/useShouldCommandMenuItemBeRegisteredParams';
|
||||
import { useCommandMenuContextApi } from '@/command-menu-item/hooks/useCommandMenuContextApi';
|
||||
import { useCommandMenuItemFrontComponentActions } from '@/command-menu-item/hooks/useCommandMenuItemFrontComponentActions';
|
||||
import { useRunWorkflowRecordAgnosticCommands } from '@/command-menu-item/record-agnostic/workflow/hooks/useRunWorkflowRecordAgnosticCommands';
|
||||
import { useRunWorkflowRecordCommands } from '@/command-menu-item/record/workflow/hooks/useRunWorkflowRecordCommands';
|
||||
import { contextStoreTargetedRecordsRuleComponentState } from '@/context-store/states/contextStoreTargetedRecordsRuleComponentState';
|
||||
import { type ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
|
||||
import { useAtomComponentStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomComponentStateValue';
|
||||
import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled';
|
||||
import { FeatureFlagKey } from '~/generated-metadata/graphql';
|
||||
|
||||
export const CommandMenuContextProviderDefault = ({
|
||||
objectMetadataItem,
|
||||
@@ -56,7 +58,11 @@ export const CommandMenuContextProviderDefault = ({
|
||||
const commandMenuContextApi = useCommandMenuContextApi();
|
||||
|
||||
const commandMenuItemFrontComponentActions =
|
||||
useCommandMenuItemFrontComponentActions(commandMenuContextApi);
|
||||
useCommandMenuItemFrontComponentCommands(commandMenuContextApi);
|
||||
|
||||
const isCommandMenuItemEnabled = useIsFeatureEnabled(
|
||||
FeatureFlagKey.IS_COMMAND_MENU_ITEM_ENABLED,
|
||||
);
|
||||
|
||||
return (
|
||||
<CommandMenuContext.Provider
|
||||
@@ -64,12 +70,13 @@ export const CommandMenuContextProviderDefault = ({
|
||||
isInSidePanel,
|
||||
displayType,
|
||||
containerType,
|
||||
commandMenuItems: [
|
||||
...commandMenuItems,
|
||||
...runWorkflowRecordCommands,
|
||||
...runWorkflowRecordAgnosticCommands,
|
||||
...commandMenuItemFrontComponentActions,
|
||||
],
|
||||
commandMenuItems: isCommandMenuItemEnabled
|
||||
? commandMenuItemFrontComponentActions
|
||||
: [
|
||||
...commandMenuItems,
|
||||
...runWorkflowRecordCommands,
|
||||
...runWorkflowRecordAgnosticCommands,
|
||||
],
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
|
||||
+39
-15
@@ -1,12 +1,12 @@
|
||||
import { useRunWorkflowRecordAgnosticCommands } from '@/command-menu-item/record-agnostic/workflow/hooks/useRunWorkflowRecordAgnosticCommands';
|
||||
import {
|
||||
CommandMenuContext,
|
||||
type CommandMenuContextType,
|
||||
} from '@/command-menu-item/contexts/CommandMenuContext';
|
||||
import { useCommandMenuContextApi } from '@/command-menu-item/hooks/useCommandMenuContextApi';
|
||||
import { useCommandMenuItemFrontComponentCommands } from '@/command-menu-item/hooks/useCommandMenuItemFrontComponentCommands';
|
||||
import { useRegisteredCommandMenuItems } from '@/command-menu-item/hooks/useRegisteredCommandMenuItems';
|
||||
import { useShouldCommandMenuItemBeRegisteredParams } from '@/command-menu-item/hooks/useShouldCommandMenuItemBeRegisteredParams';
|
||||
import { useCommandMenuContextApi } from '@/command-menu-item/hooks/useCommandMenuContextApi';
|
||||
import { useCommandMenuItemFrontComponentActions } from '@/command-menu-item/hooks/useCommandMenuItemFrontComponentActions';
|
||||
import { useRunWorkflowRecordAgnosticCommands } from '@/command-menu-item/record-agnostic/workflow/hooks/useRunWorkflowRecordAgnosticCommands';
|
||||
import { contextStoreTargetedRecordsRuleComponentState } from '@/context-store/states/contextStoreTargetedRecordsRuleComponentState';
|
||||
import { type ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
|
||||
import { recordStoreFamilyState } from '@/object-record/record-store/states/recordStoreFamilyState';
|
||||
@@ -14,7 +14,9 @@ import { useAtomComponentStateValue } from '@/ui/utilities/state/jotai/hooks/use
|
||||
import { useAtomFamilyStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomFamilyStateValue';
|
||||
import { useWorkflowWithCurrentVersion } from '@/workflow/hooks/useWorkflowWithCurrentVersion';
|
||||
import { type WorkflowWithCurrentVersion } from '@/workflow/types/Workflow';
|
||||
import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { FeatureFlagKey } from '~/generated-metadata/graphql';
|
||||
|
||||
type CommandMenuContextProviderWorkflowObjectsProps = {
|
||||
objectMetadataItem: ObjectMetadataItem;
|
||||
@@ -55,8 +57,30 @@ const CommandMenuContextProviderWorkflowObjectsContent = ({
|
||||
|
||||
const commandMenuContextApi = useCommandMenuContextApi();
|
||||
|
||||
const enrichedSelectedRecords = isDefined(workflowWithCurrentVersion)
|
||||
? commandMenuContextApi.selectedRecords.map((record) =>
|
||||
record.id === workflowWithCurrentVersion.id
|
||||
? {
|
||||
...record,
|
||||
currentVersion: workflowWithCurrentVersion.currentVersion,
|
||||
versions: workflowWithCurrentVersion.versions,
|
||||
statuses: workflowWithCurrentVersion.statuses,
|
||||
}
|
||||
: record,
|
||||
)
|
||||
: commandMenuContextApi.selectedRecords;
|
||||
|
||||
const enrichedCommandMenuContextApi = {
|
||||
...commandMenuContextApi,
|
||||
selectedRecords: enrichedSelectedRecords,
|
||||
};
|
||||
|
||||
const commandMenuItemFrontComponentActions =
|
||||
useCommandMenuItemFrontComponentActions(commandMenuContextApi);
|
||||
useCommandMenuItemFrontComponentCommands(enrichedCommandMenuContextApi);
|
||||
|
||||
const isCommandMenuItemEnabled = useIsFeatureEnabled(
|
||||
FeatureFlagKey.IS_COMMAND_MENU_ITEM_ENABLED,
|
||||
);
|
||||
|
||||
return (
|
||||
<CommandMenuContext.Provider
|
||||
@@ -64,11 +88,9 @@ const CommandMenuContextProviderWorkflowObjectsContent = ({
|
||||
isInSidePanel,
|
||||
displayType,
|
||||
containerType,
|
||||
commandMenuItems: [
|
||||
...commandMenuItems,
|
||||
...runWorkflowRecordAgnosticCommands,
|
||||
...commandMenuItemFrontComponentActions,
|
||||
],
|
||||
commandMenuItems: isCommandMenuItemEnabled
|
||||
? commandMenuItemFrontComponentActions
|
||||
: [...commandMenuItems, ...runWorkflowRecordAgnosticCommands],
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
@@ -104,7 +126,11 @@ const CommandMenuContextProviderWorkflowObjectsWithoutWorkflow = ({
|
||||
const commandMenuContextApi = useCommandMenuContextApi();
|
||||
|
||||
const commandMenuItemFrontComponentActions =
|
||||
useCommandMenuItemFrontComponentActions(commandMenuContextApi);
|
||||
useCommandMenuItemFrontComponentCommands(commandMenuContextApi);
|
||||
|
||||
const isCommandMenuItemEnabled = useIsFeatureEnabled(
|
||||
FeatureFlagKey.IS_COMMAND_MENU_ITEM_ENABLED,
|
||||
);
|
||||
|
||||
return (
|
||||
<CommandMenuContext.Provider
|
||||
@@ -112,11 +138,9 @@ const CommandMenuContextProviderWorkflowObjectsWithoutWorkflow = ({
|
||||
isInSidePanel,
|
||||
displayType,
|
||||
containerType,
|
||||
commandMenuItems: [
|
||||
...commandMenuItems,
|
||||
...runWorkflowRecordAgnosticCommands,
|
||||
...commandMenuItemFrontComponentActions,
|
||||
],
|
||||
commandMenuItems: isCommandMenuItemEnabled
|
||||
? commandMenuItemFrontComponentActions
|
||||
: [...commandMenuItems, ...runWorkflowRecordAgnosticCommands],
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
|
||||
Reference in New Issue
Block a user