0.4.0 convex及界面修改
This commit is contained in:
@@ -2,6 +2,7 @@
|
||||
|
||||
import React, { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
||||
import { Loader2, RotateCw } from "lucide-react";
|
||||
import { useConvexAuth, useMutation, useQuery } from "convex/react";
|
||||
import type { DocumentTable } from "@/types/online-table";
|
||||
import {
|
||||
DEFAULT_TABLE_COLUMNS,
|
||||
@@ -11,6 +12,8 @@ import {
|
||||
getDocumentTable,
|
||||
saveOnlineTable,
|
||||
} from "@/lib/online-table";
|
||||
import { api } from "@/lib/convex/api";
|
||||
import { isConvexEnabled } from "@/lib/convex/enabled";
|
||||
import { useLuckysheetLoader } from "@/components/online-table/useLuckysheetLoader";
|
||||
import { useDebouncedCallback } from "@/hooks/use-debounced-callback";
|
||||
import { extractRowsForPreview } from "@/components/online-table/utils";
|
||||
@@ -61,16 +64,24 @@ const HeadlessTableViewer: React.FC<HeadlessTableViewerProps> = ({ tableId, embe
|
||||
const [saveError, setSaveError] = useState<string | null>(null);
|
||||
const [showSavingHint, setShowSavingHint] = useState(false);
|
||||
const savingHintTimerRef = useRef<number | null>(null);
|
||||
const lastRemoteSyncedAtRef = useRef<string | null>(null);
|
||||
const lastSnapshotHashRef = useRef<string | null>(null);
|
||||
const lastLocalPersistAtRef = useRef<number>(0);
|
||||
|
||||
const computeSnapshotHash = useCallback((snapshot: unknown) => {
|
||||
try {
|
||||
return JSON.stringify(snapshot ?? {});
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}, []);
|
||||
const convexEnabled = isConvexEnabled();
|
||||
const { isAuthenticated } = useConvexAuth();
|
||||
const currentUser = useQuery(api.users.currentUser, convexEnabled && isAuthenticated ? {} : "skip");
|
||||
const userId =
|
||||
currentUser && typeof currentUser === "object" && typeof (currentUser as any)._id === "string"
|
||||
? String((currentUser as any)._id)
|
||||
: "";
|
||||
|
||||
const shouldFetchTable = Boolean(convexEnabled && userId && tableId);
|
||||
const tableFromConvex = useQuery(
|
||||
api.tables.get,
|
||||
shouldFetchTable ? { userId, tableId } : "skip",
|
||||
);
|
||||
const updateTable = useMutation(api.tables.update);
|
||||
|
||||
const allowInlineEdit = editable ?? embed;
|
||||
|
||||
const startSavingHint = useCallback(() => {
|
||||
if (savingHintTimerRef.current !== null) return;
|
||||
@@ -92,13 +103,37 @@ const HeadlessTableViewer: React.FC<HeadlessTableViewerProps> = ({ tableId, embe
|
||||
setIsLoading(true);
|
||||
setError(null);
|
||||
|
||||
if (convexEnabled) {
|
||||
// Convex 模式下走 useQuery 实时订阅,这里仅维护与旧逻辑兼容的 loading/error 状态
|
||||
if (tableFromConvex === undefined) {
|
||||
// still loading
|
||||
return () => {
|
||||
canceled = true;
|
||||
};
|
||||
}
|
||||
if (!canceled) {
|
||||
if (tableFromConvex === null) {
|
||||
setTable(null);
|
||||
setError("无法加载表格数据");
|
||||
} else {
|
||||
// 可编辑内嵌视图:避免每次自动保存后立即重建 UI(会闪烁)。
|
||||
// 我们在本组件触发保存后的短时间内,忽略来自订阅的回写更新。
|
||||
if (allowInlineEdit && Date.now() - lastLocalPersistAtRef.current < 1500) {
|
||||
// ignore
|
||||
} else {
|
||||
setTable(tableFromConvex as unknown as DocumentTable);
|
||||
}
|
||||
}
|
||||
setIsLoading(false);
|
||||
}
|
||||
return () => {
|
||||
canceled = true;
|
||||
};
|
||||
}
|
||||
|
||||
getDocumentTable(tableId)
|
||||
.then((data) => {
|
||||
if (!canceled) {
|
||||
lastSnapshotHashRef.current = computeSnapshotHash(data.snapshot);
|
||||
if ((data as { last_synced_at?: string }).last_synced_at) {
|
||||
lastRemoteSyncedAtRef.current = (data as { last_synced_at?: string }).last_synced_at ?? null;
|
||||
}
|
||||
setTable(data);
|
||||
}
|
||||
})
|
||||
@@ -118,10 +153,9 @@ const HeadlessTableViewer: React.FC<HeadlessTableViewerProps> = ({ tableId, embe
|
||||
return () => {
|
||||
canceled = true;
|
||||
};
|
||||
}, [computeSnapshotHash, reloadVersion, tableId]);
|
||||
}, [allowInlineEdit, convexEnabled, reloadVersion, tableFromConvex, tableId]);
|
||||
|
||||
|
||||
const allowInlineEdit = editable ?? embed;
|
||||
const focusLuckysheetEditor = useCallback(() => {
|
||||
const applyFocus = () => {
|
||||
const editor = document.getElementById("luckysheet-rich-text-editor");
|
||||
@@ -203,12 +237,22 @@ const HeadlessTableViewer: React.FC<HeadlessTableViewerProps> = ({ tableId, embe
|
||||
rows,
|
||||
luckysheet: Array.isArray(luckysheetData) ? luckysheetData : table.snapshot?.luckysheet ?? [],
|
||||
};
|
||||
lastSnapshotHashRef.current = computeSnapshotHash(snapshot);
|
||||
await saveOnlineTable(tableId, {
|
||||
snapshot,
|
||||
rows,
|
||||
schema: table.schema,
|
||||
});
|
||||
lastLocalPersistAtRef.current = Date.now();
|
||||
if (convexEnabled && userId) {
|
||||
await updateTable({
|
||||
userId,
|
||||
tableId,
|
||||
snapshot,
|
||||
rows,
|
||||
schema: table.schema,
|
||||
});
|
||||
} else {
|
||||
await saveOnlineTable(tableId, {
|
||||
snapshot,
|
||||
rows,
|
||||
schema: table.schema,
|
||||
});
|
||||
}
|
||||
window.dispatchEvent(new CustomEvent("online-table-saved", { detail: { tableId } }));
|
||||
} catch (err) {
|
||||
console.error("内嵌表格保存失败", err);
|
||||
@@ -217,7 +261,7 @@ const HeadlessTableViewer: React.FC<HeadlessTableViewerProps> = ({ tableId, embe
|
||||
stopSavingHint();
|
||||
setIsSaving(false);
|
||||
}
|
||||
}, [allowInlineEdit, computeSnapshotHash, startSavingHint, stopSavingHint, table, tableId]);
|
||||
}, [allowInlineEdit, convexEnabled, startSavingHint, stopSavingHint, table, tableId, updateTable, userId]);
|
||||
|
||||
const debouncedPersist = useDebouncedCallback(() => {
|
||||
void persistSnapshot();
|
||||
@@ -232,36 +276,7 @@ const HeadlessTableViewer: React.FC<HeadlessTableViewerProps> = ({ tableId, embe
|
||||
|
||||
useEffect(() => {
|
||||
if (!tableId) return;
|
||||
// Supabase 已移除:此处不再做实时订阅
|
||||
/* Supabase subscription removed
|
||||
const channel = supabaseBrowser
|
||||
.channel(`table-${tableId}-live`)
|
||||
.on(
|
||||
"postgres_changes",
|
||||
{ event: "UPDATE", schema: "public", table: "document_tables", filter: `id=eq.${tableId}` },
|
||||
(payload) => {
|
||||
const next = payload.new as DocumentTable | null;
|
||||
if (!next) return;
|
||||
const nextSynced = (next as { last_synced_at?: string }).last_synced_at ?? null;
|
||||
if (nextSynced && lastRemoteSyncedAtRef.current && nextSynced <= lastRemoteSyncedAtRef.current) {
|
||||
return;
|
||||
}
|
||||
lastRemoteSyncedAtRef.current = nextSynced;
|
||||
const nextHash = computeSnapshotHash(next.snapshot);
|
||||
const currentHash = lastSnapshotHashRef.current;
|
||||
if (nextHash && currentHash && nextHash === currentHash) {
|
||||
return; // 相同快照无需重建,避免闪烁
|
||||
}
|
||||
lastSnapshotHashRef.current = nextHash;
|
||||
setTable(next);
|
||||
},
|
||||
)
|
||||
.subscribe();
|
||||
|
||||
return () => {
|
||||
supabaseBrowser.removeChannel(channel);
|
||||
};
|
||||
*/
|
||||
// Supabase 已移除:实时订阅由 Convex useQuery 承担(见上方 tableFromConvex)
|
||||
}, [tableId]);
|
||||
|
||||
useEffect(() => {
|
||||
|
||||
Reference in New Issue
Block a user