[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:
+28
-23
@@ -1,5 +1,7 @@
|
||||
import { Suspense, lazy } from 'react';
|
||||
|
||||
import { CommandMenuItemErrorBoundary } from '@/command-menu-item/display/components/CommandMenuItemErrorBoundary';
|
||||
import { useUnmountHeadlessFrontComponent } from '@/front-components/hooks/useUnmountHeadlessFrontComponent';
|
||||
import { mountedHeadlessFrontComponentMapsState } from '@/front-components/states/mountedHeadlessFrontComponentMapsState';
|
||||
import { LayoutRenderingProvider } from '@/ui/layout/contexts/LayoutRenderingContext';
|
||||
import { useAtomStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomStateValue';
|
||||
@@ -16,34 +18,37 @@ export const HeadlessFrontComponentMountRoot = () => {
|
||||
const mountedHeadlessFrontComponentMaps = useAtomStateValue(
|
||||
mountedHeadlessFrontComponentMapsState,
|
||||
);
|
||||
|
||||
if (mountedHeadlessFrontComponentMaps.size === 0) {
|
||||
return null;
|
||||
}
|
||||
const unmountHeadlessFrontComponent = useUnmountHeadlessFrontComponent();
|
||||
|
||||
return (
|
||||
<>
|
||||
{[...mountedHeadlessFrontComponentMaps.entries()].map(
|
||||
([frontComponentId, mountContext]) => (
|
||||
<Suspense key={frontComponentId} fallback={null}>
|
||||
<LayoutRenderingProvider
|
||||
value={{
|
||||
targetRecordIdentifier:
|
||||
isDefined(mountContext) &&
|
||||
isDefined(mountContext.objectNameSingular)
|
||||
? {
|
||||
id: mountContext.recordId,
|
||||
targetObjectNameSingular:
|
||||
mountContext.objectNameSingular,
|
||||
}
|
||||
: undefined,
|
||||
layoutType: PageLayoutType.DASHBOARD,
|
||||
isInSidePanel: false,
|
||||
}}
|
||||
>
|
||||
<FrontComponentRenderer frontComponentId={frontComponentId} />
|
||||
</LayoutRenderingProvider>
|
||||
</Suspense>
|
||||
<CommandMenuItemErrorBoundary
|
||||
key={frontComponentId}
|
||||
engineCommandId={frontComponentId}
|
||||
onError={() => unmountHeadlessFrontComponent(frontComponentId)}
|
||||
>
|
||||
<Suspense fallback={null}>
|
||||
<LayoutRenderingProvider
|
||||
value={{
|
||||
targetRecordIdentifier:
|
||||
isDefined(mountContext) &&
|
||||
isDefined(mountContext.objectNameSingular)
|
||||
? {
|
||||
id: mountContext.recordId,
|
||||
targetObjectNameSingular:
|
||||
mountContext.objectNameSingular,
|
||||
}
|
||||
: undefined,
|
||||
layoutType: PageLayoutType.DASHBOARD,
|
||||
isInSidePanel: false,
|
||||
}}
|
||||
>
|
||||
<FrontComponentRenderer frontComponentId={frontComponentId} />
|
||||
</LayoutRenderingProvider>
|
||||
</Suspense>
|
||||
</CommandMenuItemErrorBoundary>
|
||||
),
|
||||
)}
|
||||
</>
|
||||
|
||||
Reference in New Issue
Block a user