From 75bba4f1486bf4ba47d67c6b83512bc2840f6c9b Mon Sep 17 00:00:00 2001 From: Tom Moor Date: Sat, 10 Jan 2026 14:00:23 -0500 Subject: [PATCH] wip: Refactor of activeDocumentId --- app/hooks/useActionContext.tsx | 11 +++ app/stores/UiStore.ts | 123 +++++++++++++++++++++++++++++---- app/types.ts | 9 +++ 3 files changed, 130 insertions(+), 13 deletions(-) diff --git a/app/hooks/useActionContext.tsx b/app/hooks/useActionContext.tsx index b83458e0c7..097f87c359 100644 --- a/app/hooks/useActionContext.tsx +++ b/app/hooks/useActionContext.tsx @@ -4,6 +4,7 @@ import React, { createContext, useContext } from "react"; import { useTranslation } from "react-i18next"; import { useLocation } from "react-router"; import useStores from "~/hooks/useStores"; +import type Model from "~/models/base/Model"; import type { ActionContext as ActionContextType } from "~/types"; export const ActionContext = createContext( @@ -49,8 +50,18 @@ export const ActionContextProvider = observer(function ActionContextProvider_({ isMenu: false, isCommandBar: false, isButton: false, + + // Legacy (backward compatibility) activeCollectionId: stores.ui.activeCollectionId ?? undefined, activeDocumentId: stores.ui.activeDocumentId ?? undefined, + + // New API + getActiveModels: (modelClass: typeof Model): T[] => + stores.ui.getActiveModels(modelClass), + isModelActive: (model: Model): boolean => + stores.ui.isModelActive(model), + activeModels: stores.ui.activeModels, + currentUserId: stores.auth.user?.id, currentTeamId: stores.auth.team?.id, location, diff --git a/app/stores/UiStore.ts b/app/stores/UiStore.ts index baa2b1c1c3..ce1ad734c4 100644 --- a/app/stores/UiStore.ts +++ b/app/stores/UiStore.ts @@ -2,7 +2,9 @@ import { action, computed, observable } from "mobx"; import { flushSync } from "react-dom"; import { light as defaultTheme } from "@shared/styles/theme"; import Storage from "@shared/utils/Storage"; -import type Document from "~/models/Document"; +import type Model from "~/models/base/Model"; +import Collection from "~/models/Collection"; +import Document from "~/models/Document"; import type { ConnectionStatus } from "~/scenes/Document/components/MultiplayerEditor"; import { startViewTransition } from "~/utils/viewTransition"; import type RootStore from "./RootStore"; @@ -48,10 +50,7 @@ class UiStore { systemTheme: SystemTheme; @observable - activeDocumentId: string | undefined; - - @observable - activeCollectionId?: string | null; + activeModels = new Set(); @observable observingUserId: string | undefined; @@ -140,6 +139,84 @@ class UiStore { }); } + /** + * Add a model instance to the active set. + * + * @param model the model instance to add. + */ + @action + addActiveModel = (model: Model): void => { + this.activeModels.add(model); + }; + + /** + * Remove a model instance from the active set. + * + * @param model the model instance to remove. + */ + @action + removeActiveModel = (model: Model): void => { + this.activeModels.delete(model); + }; + + /** + * Get all active models of a specific type. + * + * @param modelClass the model class to filter by. + * @returns array of active models of the specified type. + */ + getActiveModels(modelClass: typeof Model): T[] { + return Array.from(this.activeModels).filter( + (model) => model.constructor === modelClass + ) as T[]; + } + + /** + * Check if a model instance is in the active set. + * + * @param model the model instance to check. + * @returns true if the model is active. + */ + isModelActive(model: Model): boolean { + return this.activeModels.has(model); + } + + /** + * Clear all active models, or only models of a specific type. + * + * @param modelClass optional model class to filter by. + */ + @action + clearActiveModels(modelClass?: typeof Model): void { + if (modelClass) { + const modelsToRemove = this.getActiveModels(modelClass); + modelsToRemove.forEach((model) => this.activeModels.delete(model)); + } else { + this.activeModels.clear(); + } + } + + /** + * Get the most recently added model of a specific type (primary). + * + * @param modelClass the model class to filter by. + * @returns the most recently added model of the specified type. + */ + getPrimaryActiveModel(modelClass: typeof Model): T | undefined { + const models = this.getActiveModels(modelClass); + return models[models.length - 1]; + } + + @computed + get activeDocumentId(): string | undefined { + return this.getPrimaryActiveModel(Document)?.id; + } + + @computed + get activeCollectionId(): string | undefined { + return this.getPrimaryActiveModel(Collection)?.id; + } + @action setTheme = (theme: Theme) => { startViewTransition(() => { @@ -152,17 +229,28 @@ class UiStore { @action setActiveDocument = (document: Document | string): void => { + let model: Document | undefined; + if (typeof document === "string") { - this.activeDocumentId = document; - this.observingUserId = undefined; + model = this.rootStore.documents.get(document); + } else { + model = document; + } + + if (!model) { return; } - this.activeDocumentId = document.id; + this.clearActiveModels(Document); + this.addActiveModel(model); this.observingUserId = undefined; - if (document.isActive) { - this.activeCollectionId = document.collectionId; + if (model.isActive && model.collectionId) { + const collection = this.rootStore.collections.get(model.collectionId); + if (collection) { + this.clearActiveModels(Collection); + this.addActiveModel(collection); + } } }; @@ -182,7 +270,16 @@ class UiStore { @action setActiveCollection = (collectionId: string | undefined): void => { - this.activeCollectionId = collectionId; + if (collectionId === undefined || collectionId === null) { + this.clearActiveModels(Collection); + return; + } + + const model = this.rootStore.collections.get(collectionId); + if (model) { + this.clearActiveModels(Collection); + this.addActiveModel(model); + } }; @action @@ -192,12 +289,12 @@ class UiStore { @action clearActiveDocument = (): void => { - this.activeDocumentId = undefined; + this.clearActiveModels(Document); this.observingUserId = undefined; // Unset when navigating away from a document (e.g. to another document, home, settings, etc.) // Next document's onMount will set the right activeCollectionId. - this.activeCollectionId = undefined; + this.clearActiveModels(Collection); }; @action diff --git a/app/types.ts b/app/types.ts index d4a6afca6e..abcbd9ae80 100644 --- a/app/types.ts +++ b/app/types.ts @@ -8,6 +8,7 @@ import type { } from "@shared/types"; import type RootStore from "~/stores/RootStore"; import type { SidebarContextType } from "./components/Sidebar/components/SidebarContext"; +import type Model from "./models/base/Model"; import type Document from "./models/Document"; import type FileOperation from "./models/FileOperation"; import type Pin from "./models/Pin"; @@ -96,8 +97,16 @@ export type ActionContext = { isCommandBar: boolean; isButton: boolean; sidebarContext?: SidebarContextType; + + // Legacy (backward compatibility) - returns primary active model's ID activeCollectionId?: string | undefined; activeDocumentId: string | undefined; + + // New API - work directly with Model instances + getActiveModels: (modelClass: typeof Model) => T[]; + isModelActive: (model: Model) => boolean; + activeModels: ReadonlySet; + currentUserId: string | undefined; currentTeamId: string | undefined; location: Location;