Refactor ActionContextProvider to allow more model coverage (#11503)

This commit is contained in:
Tom Moor
2026-02-19 23:10:13 -05:00
committed by GitHub
parent dfd969084b
commit 6e92313f73
10 changed files with 70 additions and 53 deletions
+4 -5
View File
@@ -101,11 +101,10 @@ function DocumentListItem(
return (
<ActionContextProvider
value={{
activeDocumentId: document.id,
activeCollectionId:
!isShared && document.collectionId
? document.collectionId
: undefined,
activeModels: [
document,
...(!isShared && document.collection ? [document.collection] : []),
],
}}
>
<ContextMenu
+1 -1
View File
@@ -125,7 +125,7 @@ const RevisionListItem = ({ item, document, ...rest }: Props) => {
}
return (
<ActionContextProvider value={{ activeDocumentId: document.id }}>
<ActionContextProvider value={{ activeModels: [document] }}>
<ContextMenu
action={contextMenuAction}
ariaLabel={t("Revision options")}
@@ -127,7 +127,7 @@ const CollectionLink: React.FC<Props> = ({
});
return (
<ActionContextProvider value={{ activeCollectionId: collection.id }}>
<ActionContextProvider value={{ activeModels: [collection] }}>
<Relative ref={mergeRefs([parentRef, dropRef])}>
<DropToImport collectionId={collection.id}>
<SidebarLink
@@ -416,7 +416,7 @@ function InnerDocumentLink(
return (
<ActionContextProvider
value={{
activeDocumentId: node.id,
activeModels: document ? [document] : [],
}}
>
<Relative ref={parentRef}>
@@ -103,7 +103,7 @@ const StarredDocumentLink = observer(function StarredDocumentLink({
return (
<ActionContextProvider
value={{
activeDocumentId: document.id,
activeModels: [document],
}}
>
<Draggable key={star.id} ref={draggableRef} $isDragging={isDragging}>
+3 -2
View File
@@ -37,8 +37,9 @@ function Star({ size, document, collection, color, ...rest }: Props) {
return (
<ActionContextProvider
value={{
activeDocumentId: document?.id,
activeCollectionId: collection?.id,
activeModels: [document, collection].filter(
(m): m is Document | Collection => !!m
),
}}
>
<NudeButton
+53 -35
View File
@@ -7,14 +7,25 @@ import useStores from "~/hooks/useStores";
import type Model from "~/models/base/Model";
import type Policy from "~/models/Policy";
import type { ActionContext as ActionContextType } from "~/types";
import type { SidebarContextType } from "~/components/Sidebar/components/SidebarContext";
export const ActionContext = createContext<ActionContextType | undefined>(
undefined
);
interface ActionContextProviderValue {
/** Models to add to the active models context for this subtree. */
activeModels?: Model[];
isMenu?: boolean;
isCommandBar?: boolean;
isButton?: boolean;
sidebarContext?: SidebarContextType;
event?: Event;
}
type ActionContextProviderProps = {
children: ReactNode;
value?: Partial<ActionContextType>;
value?: ActionContextProviderValue;
};
/**
@@ -23,15 +34,15 @@ type ActionContextProviderProps = {
*
* @example
* ```tsx
* // Override context for a command bar
* <ActionContextProvider value={{ isCommandBar: true }}>
* <CommandBar />
* // Override active models for a collection menu
* <ActionContextProvider value={{ activeModels: [collection] }}>
* <CollectionMenu />
* </ActionContextProvider>
*
* // Nested overrides
* <ActionContextProvider value={{ activeCollectionId: "collection-1" }}>
* <ActionContextProvider value={{ activeModels: [collection] }}>
* <CollectionView />
* <ActionContextProvider value={{ activeDocumentId: "doc-1" }}>
* <ActionContextProvider value={{ activeModels: [document] }}>
* <DocumentView />
* </ActionContextProvider>
* </ActionContextProvider>
@@ -45,6 +56,7 @@ export const ActionContextProvider = observer(function ActionContextProvider_({
const stores = useStores();
const { t } = useTranslation();
const location = useLocation();
const { activeModels: valueModels, ...overrides } = value;
// Create the base context if we don't have a parent context
const baseContext: ActionContextType = parentContext ?? {
@@ -56,7 +68,6 @@ export const ActionContextProvider = observer(function ActionContextProvider_({
activeCollectionId: stores.ui.activeCollectionId ?? undefined,
activeDocumentId: stores.ui.activeDocumentId ?? undefined,
// New API
getActiveModels: <T extends Model>(
modelClass: new (...args: any[]) => T
): T[] => stores.ui.getActiveModels<T>(modelClass),
@@ -83,33 +94,18 @@ export const ActionContextProvider = observer(function ActionContextProvider_({
t,
};
// Merge the parent context with the provided overrides
const activeCollectionId =
value.activeCollectionId ?? baseContext.activeCollectionId;
const activeDocumentId =
value.activeDocumentId ?? baseContext.activeDocumentId;
const getActiveModels = <T extends Model>(
modelClass: new (...args: any[]) => T
): T[] => {
// @ts-expect-error modelName
if (activeCollectionId && modelClass.modelName === "Collection") {
const model = stores.collections.get(activeCollectionId);
if (model) {
return [model as unknown as T];
}
}
// @ts-expect-error modelName
if (activeDocumentId && modelClass.modelName === "Document") {
const model = stores.documents.get(activeDocumentId);
if (model) {
return [model as unknown as T];
}
}
return baseContext.getActiveModels(modelClass);
};
// Override model accessors when models are provided in value
const getActiveModels =
valueModels && valueModels.length > 0
? <T extends Model>(modelClass: new (...args: any[]) => T): T[] => {
const matching = valueModels.filter(
(model): model is T => model instanceof modelClass
);
return matching.length > 0
? matching
: baseContext.getActiveModels(modelClass);
}
: baseContext.getActiveModels;
const getActiveModel = <T extends Model>(
modelClass: new (...args: any[]) => T
@@ -122,12 +118,34 @@ export const ActionContextProvider = observer(function ActionContextProvider_({
.map((node) => stores.policies.get(node.id))
.filter((policy): policy is Policy => policy !== undefined);
const allActiveModels =
valueModels && valueModels.length > 0
? new Set([...baseContext.activeModels, ...valueModels])
: baseContext.activeModels;
const isModelActive = (model: Model): boolean => allActiveModels.has(model);
// Derive legacy IDs from value models, falling back to base context
const activeCollectionId =
valueModels?.find(
(m) => (m.constructor as typeof Model).modelName === "Collection"
)?.id ?? baseContext.activeCollectionId;
const activeDocumentId =
valueModels?.find(
(m) => (m.constructor as typeof Model).modelName === "Document"
)?.id ?? baseContext.activeDocumentId;
const contextValue: ActionContextType = {
...baseContext,
...value,
...overrides,
activeCollectionId,
activeDocumentId,
getActiveModels,
getActiveModel,
getActivePolicies,
isModelActive,
activeModels: allActiveModels,
};
return (
+1 -1
View File
@@ -53,7 +53,7 @@ function CollectionMenu({
});
return (
<ActionContextProvider value={{ activeCollectionId: collection.id }}>
<ActionContextProvider value={{ activeModels: [collection] }}>
<DropdownMenu
action={rootAction}
align={align}
+4 -5
View File
@@ -198,11 +198,10 @@ function DocumentMenu({
return (
<ActionContextProvider
value={{
activeDocumentId: document.id,
activeCollectionId:
!isShared && document.collectionId
? document.collectionId
: undefined,
activeModels: [
document,
...(!isShared && document.collection ? [document.collection] : []),
],
}}
>
<DropdownMenu
+1 -1
View File
@@ -33,7 +33,7 @@ function RevisionMenu({ document, revisionId }: Props) {
const rootAction = useMenuAction(actions);
return (
<ActionContextProvider value={{ activeDocumentId: document.id }}>
<ActionContextProvider value={{ activeModels: [document] }}>
<DropdownMenu
action={rootAction}
align="end"