[Command menu items] Create engine commands (#18681)
## Description - Introduces a new engine command execution model that replaces the previous approach of mapping `EngineComponentKey` to React components. Instead, engine commands are now mounted headlessly via `HeadlessEngineCommandMountRoot`, with their execution context populated synchronously before mounting. - Creates new headless command components - Moves error handling from the SDK layer to the host app by wrapping all mounted commands with a new `CommandMenuItemErrorBoundary` The new flow works as follows: - When a command menu item with an `engineComponentKey` is clicked, `useCommandMenuItemFrontComponentCommands` calls `useMountEngineCommand`, which synchronously reads the current context store (object metadata, selected records, filters, view ID, etc.) and writes a `MountedEngineCommandContext` into `mountedEngineCommandsState`. - The command is then mounted into `mountedEngineCommandsState`, which triggers `HeadlessEngineCommandMountRoot` to render the corresponding headless component from `ENGINE_COMPONENT_KEY_HEADLESS_COMPONENT_MAP`, wrapped in `CommandMenuItemErrorBoundary`, `ContextStoreComponentInstanceContext.Provider`, and `EngineCommandComponentInstanceContext.Provider`. - Each command component reads its execution context and delegates to one of the 4 execution patterns: `HeadlessEngineCommandWrapperEffect` (simple actions), `HeadlessConfirmationModalEngineCommandEffect` (destructive actions needing confirmation), `HeadlessNavigateEngineCommand` (GO_TO_* commands), or `HeadlessOpenSidePanelPageEngineCommand` (SEARCH_RECORDS, ASK_AI, VIEW_PREVIOUS_AI_CHATS). - After execution, the command self-unmounts via `useUnmountEngineCommand`, which removes the entry from `mountedEngineCommandsState` and stops rendering the component.
This commit is contained in:
+2
-1
@@ -6,9 +6,10 @@ import { useStore } from 'jotai';
|
||||
import { useCallback, useContext } from 'react';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
|
||||
export const useRemoveRecordFilter = () => {
|
||||
export const useRemoveRecordFilter = (instanceId?: string) => {
|
||||
const currentRecordFilters = useAtomComponentStateCallbackState(
|
||||
currentRecordFiltersComponentState,
|
||||
instanceId,
|
||||
);
|
||||
|
||||
const store = useStore();
|
||||
|
||||
+2
-1
@@ -5,9 +5,10 @@ import { useAtomComponentStateCallbackState } from '@/ui/utilities/state/jotai/h
|
||||
import { useStore } from 'jotai';
|
||||
import { useCallback, useContext } from 'react';
|
||||
|
||||
export const useUpsertRecordFilter = () => {
|
||||
export const useUpsertRecordFilter = (instanceId?: string) => {
|
||||
const currentRecordFilters = useAtomComponentStateCallbackState(
|
||||
currentRecordFiltersComponentState,
|
||||
instanceId,
|
||||
);
|
||||
|
||||
const store = useStore();
|
||||
|
||||
+2
@@ -89,6 +89,7 @@ export const useRecordIndexLazyFetchRecords = ({
|
||||
|
||||
const findManyRecordsParams = useFindManyRecordIndexTableParams(
|
||||
objectMetadataItem.nameSingular,
|
||||
recordIndexId,
|
||||
);
|
||||
|
||||
const queryFilter = computeContextStoreFilters({
|
||||
@@ -102,6 +103,7 @@ export const useRecordIndexLazyFetchRecords = ({
|
||||
|
||||
const visibleRecordFields = useAtomComponentSelectorValue(
|
||||
visibleRecordFieldsComponentSelector,
|
||||
recordIndexId,
|
||||
);
|
||||
|
||||
const finalColumns: Pick<
|
||||
|
||||
+5
@@ -17,6 +17,7 @@ import {
|
||||
|
||||
export const useFindManyRecordIndexTableParams = (
|
||||
objectNameSingular: string,
|
||||
instanceId?: string,
|
||||
) => {
|
||||
const { objectMetadataItem } = useObjectMetadataItem({
|
||||
objectNameSingular,
|
||||
@@ -31,14 +32,17 @@ export const useFindManyRecordIndexTableParams = (
|
||||
|
||||
const currentRecordFilterGroups = useAtomComponentStateValue(
|
||||
currentRecordFilterGroupsComponentState,
|
||||
instanceId,
|
||||
);
|
||||
|
||||
const currentRecordSorts = useAtomComponentStateValue(
|
||||
currentRecordSortsComponentState,
|
||||
instanceId,
|
||||
);
|
||||
|
||||
const currentRecordFilters = useAtomComponentStateValue(
|
||||
currentRecordFiltersComponentState,
|
||||
instanceId,
|
||||
);
|
||||
|
||||
const { filterValueDependencies } = useFilterValueDependencies();
|
||||
@@ -52,6 +56,7 @@ export const useFindManyRecordIndexTableParams = (
|
||||
|
||||
const anyFieldFilterValue = useAtomComponentStateValue(
|
||||
anyFieldFilterValueComponentState,
|
||||
instanceId,
|
||||
);
|
||||
|
||||
const { recordGqlOperationFilter: anyFieldFilter } =
|
||||
|
||||
+3
-1
@@ -16,11 +16,13 @@ import { ViewFilterOperand } from 'twenty-shared/types';
|
||||
type UseHandleToggleTrashColumnFilterProps = {
|
||||
objectNameSingular: string;
|
||||
viewBarId: string;
|
||||
recordFiltersInstanceId?: string;
|
||||
};
|
||||
|
||||
export const useHandleToggleTrashColumnFilter = ({
|
||||
viewBarId,
|
||||
objectNameSingular,
|
||||
recordFiltersInstanceId,
|
||||
}: UseHandleToggleTrashColumnFilterProps) => {
|
||||
const { objectMetadataItem } = useObjectMetadataItem({
|
||||
objectNameSingular,
|
||||
@@ -35,7 +37,7 @@ export const useHandleToggleTrashColumnFilter = ({
|
||||
viewBarId,
|
||||
);
|
||||
|
||||
const { upsertRecordFilter } = useUpsertRecordFilter();
|
||||
const { upsertRecordFilter } = useUpsertRecordFilter(recordFiltersInstanceId);
|
||||
|
||||
const handleToggleTrashColumnFilter = useCallback(() => {
|
||||
const trashFieldMetadata = objectMetadataItem.fields.find(
|
||||
|
||||
+3
@@ -9,11 +9,14 @@ import { useAtomStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomState
|
||||
|
||||
export const useBuildRecordInputFromFilters = ({
|
||||
objectMetadataItem,
|
||||
instanceId,
|
||||
}: {
|
||||
objectMetadataItem: ObjectMetadataItem;
|
||||
instanceId?: string;
|
||||
}) => {
|
||||
const currentRecordFilters = useAtomComponentStateValue(
|
||||
currentRecordFiltersComponentState,
|
||||
instanceId,
|
||||
);
|
||||
|
||||
const currentWorkspaceMember = useAtomStateValue(currentWorkspaceMemberState);
|
||||
|
||||
+6
@@ -25,23 +25,28 @@ import { useNavigateApp } from '~/hooks/useNavigateApp';
|
||||
|
||||
type UseCreateNewIndexRecordProps = {
|
||||
objectMetadataItem: ObjectMetadataItem;
|
||||
instanceId?: string;
|
||||
};
|
||||
|
||||
export const useCreateNewIndexRecord = ({
|
||||
objectMetadataItem,
|
||||
instanceId,
|
||||
}: UseCreateNewIndexRecordProps) => {
|
||||
const recordGroupDefinitions = useAtomComponentSelectorValue(
|
||||
recordGroupDefinitionsComponentSelector,
|
||||
instanceId,
|
||||
);
|
||||
|
||||
const store = useStore();
|
||||
const recordIndexRecordIdsByGroupCallbackState =
|
||||
useAtomComponentFamilyStateCallbackState(
|
||||
recordIndexRecordIdsByGroupComponentFamilyState,
|
||||
instanceId,
|
||||
);
|
||||
|
||||
const recordIndexGroupFieldMetadataItem = useAtomComponentStateValue(
|
||||
recordIndexGroupFieldMetadataItemComponentState,
|
||||
instanceId,
|
||||
);
|
||||
|
||||
const { openRecordInSidePanel } = useOpenRecordInSidePanel();
|
||||
@@ -59,6 +64,7 @@ export const useCreateNewIndexRecord = ({
|
||||
|
||||
const { buildRecordInputFromFilters } = useBuildRecordInputFromFilters({
|
||||
objectMetadataItem,
|
||||
instanceId,
|
||||
});
|
||||
|
||||
const { buildRecordInputFromRLSPredicates } =
|
||||
|
||||
Reference in New Issue
Block a user