wip: Refactor of activeDocumentId

This commit is contained in:
Tom Moor
2026-01-10 14:00:23 -05:00
parent 7945060a0d
commit 75bba4f148
3 changed files with 130 additions and 13 deletions
+11
View File
@@ -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<ActionContextType | undefined>(
@@ -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: <T extends Model>(modelClass: typeof Model): T[] =>
stores.ui.getActiveModels<T>(modelClass),
isModelActive: (model: Model): boolean =>
stores.ui.isModelActive(model),
activeModels: stores.ui.activeModels,
currentUserId: stores.auth.user?.id,
currentTeamId: stores.auth.team?.id,
location,
+110 -13
View File
@@ -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<Model>();
@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<T extends Model>(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<T extends Model>(modelClass: typeof Model): T | undefined {
const models = this.getActiveModels<T>(modelClass);
return models[models.length - 1];
}
@computed
get activeDocumentId(): string | undefined {
return this.getPrimaryActiveModel<Document>(Document)?.id;
}
@computed
get activeCollectionId(): string | undefined {
return this.getPrimaryActiveModel<Collection>(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
+9
View File
@@ -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: <T extends Model>(modelClass: typeof Model) => T[];
isModelActive: (model: Model) => boolean;
activeModels: ReadonlySet<Model>;
currentUserId: string | undefined;
currentTeamId: string | undefined;
location: Location;