179 lines
5.7 KiB
TypeScript
179 lines
5.7 KiB
TypeScript
import { useMemo } from "react";
|
|
import { useQuery, useConvexAuth } from "convex/react";
|
|
import type { SidebarInitialData } from "@/components/sidebar/types";
|
|
import type { MediaAsset } from "@/types/media";
|
|
import { api } from "@/lib/convex/api";
|
|
|
|
/**
|
|
* Convex 模式下的侧边栏数据 hook
|
|
* 使用 Convex 的 useQuery 实现实时订阅,无需手动 refetch
|
|
*
|
|
* 注意:此 hook 仅应在 Convex 模式下使用
|
|
* 后端从 ctx.auth.getUserIdentity() 获取用户身份,前端无需传入 userId
|
|
*/
|
|
export function useConvexSidebarData(workspaceId: string): {
|
|
data: SidebarInitialData | null;
|
|
isLoading: boolean;
|
|
error: Error | null;
|
|
refetch: () => Promise<void>;
|
|
} {
|
|
const { isAuthenticated } = useConvexAuth();
|
|
|
|
// 显式转为 boolean,确保类型正确
|
|
const shouldFetch = Boolean(isAuthenticated && workspaceId);
|
|
const currentUser = useQuery(api.users.currentUser, shouldFetch ? {} : "skip");
|
|
const userId =
|
|
currentUser && typeof currentUser === "object" && typeof (currentUser as any)._id === "string"
|
|
? String((currentUser as any)._id)
|
|
: "";
|
|
|
|
const shouldFetchAuthed = Boolean(shouldFetch && userId);
|
|
|
|
// 使用 Convex 的 useQuery,自动订阅实时更新
|
|
// 后端从 ctx.auth.getUserIdentity() 获取用户身份,无需前端传入 userId
|
|
const documents = useQuery(
|
|
api.documents.listByWorkspace,
|
|
shouldFetch ? { workspaceId } : "skip"
|
|
);
|
|
|
|
const trashedDocuments = useQuery(
|
|
api.documents.listTrashedByWorkspace,
|
|
shouldFetch ? { workspaceId } : "skip"
|
|
);
|
|
|
|
const mindmaps = useQuery(
|
|
api.mindmaps.listByWorkspace,
|
|
shouldFetch ? { workspaceId, includeDeleted: true } : "skip"
|
|
);
|
|
|
|
const mediaAssets = useQuery(
|
|
api.mediaAssets.listByWorkspace,
|
|
shouldFetchAuthed ? { userId, workspaceId, limit: 200 } : "skip",
|
|
);
|
|
|
|
const trashedMediaAssets = useQuery(
|
|
api.mediaAssets.listDeletedByWorkspace,
|
|
shouldFetchAuthed ? { userId, workspaceId, limit: 2000 } : "skip",
|
|
);
|
|
|
|
const workspacesResult = useQuery(
|
|
api.workspaces.fetchWorkspaceSummaries,
|
|
shouldFetch ? undefined : "skip"
|
|
);
|
|
|
|
// 组合数据,格式与 SidebarInitialData 一致
|
|
const data: SidebarInitialData | null = useMemo(() => {
|
|
// 当 skip 时,返回值是 undefined
|
|
if (currentUser === undefined ||
|
|
documents === undefined ||
|
|
trashedDocuments === undefined ||
|
|
mindmaps === undefined ||
|
|
mediaAssets === undefined ||
|
|
trashedMediaAssets === undefined ||
|
|
workspacesResult === undefined) {
|
|
return null;
|
|
}
|
|
|
|
const activeMindmaps = (mindmaps ?? []).filter((r) => !r.deleted_at);
|
|
const trashedMindmaps = (mindmaps ?? []).filter((r) => !!r.deleted_at);
|
|
|
|
const mindmapDocs = Array.from(new Set(activeMindmaps.map((r) => r.document_id)));
|
|
|
|
const mindmapAssets: MediaAsset[] = activeMindmaps.map((r) => {
|
|
const isLegacy = r.mindmap_id.startsWith("legacy-");
|
|
return {
|
|
id: r.mindmap_id,
|
|
workspace_id: r.workspace_id ?? workspaceId,
|
|
document_id: r.document_id,
|
|
asset_type: "mindmap",
|
|
file_url: null,
|
|
thumbnail_url: null,
|
|
bucket: null,
|
|
storage_path: null,
|
|
file_name: isLegacy ? "mindmap.json" : `mindmap-${r.mindmap_id}.json`,
|
|
file_size: null,
|
|
mime_type: "application/json",
|
|
ocr_payload: undefined,
|
|
ocr_strategy: null,
|
|
ocr_text: null,
|
|
ocr_status: null,
|
|
signed_url: null,
|
|
created_at: r.created_at ?? "",
|
|
updated_at: r.updated_at ?? "",
|
|
};
|
|
});
|
|
|
|
const trashedMindmapAssets: MediaAsset[] = trashedMindmaps.map((r) => {
|
|
const isLegacy = r.mindmap_id.startsWith("legacy-");
|
|
return {
|
|
id: r.mindmap_id,
|
|
workspace_id: r.workspace_id ?? workspaceId,
|
|
document_id: r.document_id,
|
|
asset_type: "mindmap",
|
|
file_url: null,
|
|
thumbnail_url: null,
|
|
bucket: null,
|
|
storage_path: null,
|
|
file_name: isLegacy ? "mindmap.json" : `mindmap-${r.mindmap_id}.json`,
|
|
file_size: null,
|
|
mime_type: "application/json",
|
|
ocr_payload: undefined,
|
|
ocr_strategy: null,
|
|
ocr_text: null,
|
|
ocr_status: null,
|
|
deleted_at: r.deleted_at ?? null,
|
|
deleted_by: r.deleted_by ?? null,
|
|
purged_at: null,
|
|
signed_url: null,
|
|
created_at: r.created_at ?? "",
|
|
updated_at: r.updated_at ?? "",
|
|
};
|
|
});
|
|
|
|
return {
|
|
activeWorkspaceId: workspacesResult.activeWorkspaceId || workspaceId,
|
|
workspaces: workspacesResult.workspaces,
|
|
documents,
|
|
trashedDocuments,
|
|
trashedMediaAssets: (trashedMediaAssets ?? []) as MediaAsset[],
|
|
trashedMindmapAssets,
|
|
mindmapDocs,
|
|
mindmapAssets,
|
|
mindmapAssetChildren: {},
|
|
tableAssets: [],
|
|
mediaAssets: (mediaAssets ?? []) as MediaAsset[],
|
|
};
|
|
}, [
|
|
currentUser,
|
|
documents,
|
|
trashedDocuments,
|
|
mindmaps,
|
|
mediaAssets,
|
|
trashedMediaAssets,
|
|
workspacesResult,
|
|
workspaceId,
|
|
]);
|
|
|
|
// loading 状态:只有当 shouldFetch 为 true 且数据未加载时才算 loading
|
|
// 使用 === undefined 判断,因为 skip 时返回 undefined
|
|
const isLoading = shouldFetch && (
|
|
currentUser === undefined ||
|
|
documents === undefined ||
|
|
trashedDocuments === undefined ||
|
|
mindmaps === undefined ||
|
|
mediaAssets === undefined ||
|
|
trashedMediaAssets === undefined ||
|
|
workspacesResult === undefined
|
|
);
|
|
const error = null;
|
|
|
|
// Convex 模式下数据自动实时同步,refetch 是空操作
|
|
// Convex 的 useQuery 会自动实时同步,无需手动 refetch
|
|
// 这个方法只是为了保持与 useSidebarData 的接口兼容
|
|
const refetch = async () => {
|
|
// 空操作 - Convex 会自动同步数据
|
|
};
|
|
|
|
return { data, isLoading, error, refetch };
|
|
}
|