git save current version as 0.03

This commit is contained in:
liaibo
2025-11-29 10:46:40 +08:00
parent e5ae2c173e
commit 58340d4080
3 changed files with 99 additions and 10 deletions
@@ -67,8 +67,6 @@ const CompactTablePreview: React.FC<CompactTablePreviewProps> = ({
const detail = (event as CustomEvent<{ tableId?: string }>).detail;
if (detail?.tableId === tableId) {
refresh();
setIframeVersion((value) => value + 1);
setIframeLoading(true);
}
};
const handleDeleted = (event: Event) => {
@@ -76,6 +76,23 @@ const FullScreenTableEditor: React.FC<FullScreenTableEditorProps> = ({ tableId,
const [lastSyncedAt, setLastSyncedAt] = useState<number | null>(null);
const persistSnapshotRef = useRef<((reason: "auto" | "close") => Promise<void>) | null>(null);
const lastPointerDownInGridRef = useRef(false);
const savingHintTimerRef = useRef<number | null>(null);
const [showSavingHint, setShowSavingHint] = useState(false);
const startSavingHint = useCallback(() => {
if (savingHintTimerRef.current !== null) return;
savingHintTimerRef.current = window.setTimeout(() => {
setShowSavingHint(true);
}, 700);
}, []);
const stopSavingHint = useCallback(() => {
if (savingHintTimerRef.current !== null) {
clearTimeout(savingHintTimerRef.current);
savingHintTimerRef.current = null;
}
setShowSavingHint(false);
}, []);
useEffect(() => {
if (typeof window !== "undefined") {
@@ -145,6 +162,7 @@ const FullScreenTableEditor: React.FC<FullScreenTableEditorProps> = ({ tableId,
if (!tableData || !window.luckysheet || typeof window.luckysheet.getluckysheetfile !== "function") {
return;
}
startSavingHint();
setIsSaving(true);
setSaveError(null);
try {
@@ -170,9 +188,10 @@ const FullScreenTableEditor: React.FC<FullScreenTableEditorProps> = ({ tableId,
console.error("保存 Luckysheet 数据失败", error);
setSaveError(reason === "close" ? "关闭前保存失败,请重试" : "自动保存失败");
} finally {
stopSavingHint();
setIsSaving(false);
}
}, [tableData, tableId]);
}, [startSavingHint, stopSavingHint, tableData, tableId]);
const debouncedPersist = useDebouncedCallback(() => {
void persistSnapshot("auto");
@@ -186,8 +205,9 @@ const FullScreenTableEditor: React.FC<FullScreenTableEditorProps> = ({ tableId,
return () => {
debouncedPersist.cancel();
void persistSnapshotRef.current?.("close");
stopSavingHint();
};
}, [debouncedPersist]);
}, [debouncedPersist, stopSavingHint]);
useEffect(() => {
if (!isLuckysheetReady) {
@@ -371,6 +391,7 @@ const FullScreenTableEditor: React.FC<FullScreenTableEditorProps> = ({ tableId,
if (target && !container.contains(target)) {
return;
}
containerRef.current?.focus();
requestAnimationFrame(() => {
const selection = window.luckysheet?.getluckysheet_select_save?.();
if (!selection) {
@@ -511,7 +532,7 @@ const FullScreenTableEditor: React.FC<FullScreenTableEditorProps> = ({ tableId,
const statusText = saveError
? saveError
: isSaving
: isSaving && showSavingHint
? "同步中..."
: hasPendingChanges
? "有未保存变更"
@@ -548,6 +569,8 @@ const FullScreenTableEditor: React.FC<FullScreenTableEditorProps> = ({ tableId,
id={LUCKY_SHEET_CONTAINER_ID}
ref={containerRef}
className="flex-grow w-full h-full"
tabIndex={-1}
aria-label="在线表格全屏编辑区域"
style={{ display: isLuckysheetReady && !isTableLoading && !tableError ? "block" : "none" }}
/>
@@ -14,6 +14,7 @@ import {
import { useLuckysheetLoader } from "@/components/online-table/useLuckysheetLoader";
import { useDebouncedCallback } from "@/hooks/use-debounced-callback";
import { extractRowsForPreview } from "@/components/online-table/utils";
import { supabaseBrowser } from "@/lib/supabase/client";
type LuckysheetSelection =
| {
@@ -59,6 +60,33 @@ const HeadlessTableViewer: React.FC<HeadlessTableViewerProps> = ({ tableId, embe
const [reloadVersion, setReloadVersion] = useState(0);
const [isSaving, setIsSaving] = useState(false);
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 computeSnapshotHash = useCallback((snapshot: unknown) => {
try {
return JSON.stringify(snapshot ?? {});
} catch {
return null;
}
}, []);
const startSavingHint = useCallback(() => {
if (savingHintTimerRef.current !== null) return;
savingHintTimerRef.current = window.setTimeout(() => {
setShowSavingHint(true);
}, 700);
}, []);
const stopSavingHint = useCallback(() => {
if (savingHintTimerRef.current !== null) {
clearTimeout(savingHintTimerRef.current);
savingHintTimerRef.current = null;
}
setShowSavingHint(false);
}, []);
useEffect(() => {
let canceled = false;
@@ -68,6 +96,10 @@ const HeadlessTableViewer: React.FC<HeadlessTableViewerProps> = ({ tableId, embe
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);
}
})
@@ -157,6 +189,7 @@ const HeadlessTableViewer: React.FC<HeadlessTableViewerProps> = ({ tableId, embe
if (!allowInlineEdit || !table || !window.luckysheet || typeof window.luckysheet.getluckysheetfile !== "function") {
return;
}
startSavingHint();
setIsSaving(true);
setSaveError(null);
try {
@@ -170,20 +203,21 @@ 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,
});
setTable((prev) => (prev ? { ...prev, snapshot, rows } : prev));
window.dispatchEvent(new CustomEvent("online-table-saved", { detail: { tableId } }));
} catch (err) {
console.error("内嵌表格保存失败", err);
setSaveError("自动保存失败");
} finally {
stopSavingHint();
setIsSaving(false);
}
}, [allowInlineEdit, table, tableId]);
}, [allowInlineEdit, startSavingHint, stopSavingHint, table, tableId]);
const debouncedPersist = useDebouncedCallback(() => {
void persistSnapshot();
@@ -192,8 +226,40 @@ const HeadlessTableViewer: React.FC<HeadlessTableViewerProps> = ({ tableId, embe
useEffect(() => {
return () => {
debouncedPersist.cancel();
stopSavingHint();
};
}, [debouncedPersist]);
}, [debouncedPersist, stopSavingHint]);
useEffect(() => {
if (!tableId) return;
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);
};
}, [tableId]);
useEffect(() => {
if (!isLuckysheetReady || !table || !containerRef.current || !window.luckysheet) {
@@ -332,9 +398,11 @@ const HeadlessTableViewer: React.FC<HeadlessTableViewerProps> = ({ tableId, embe
</button>
</div>
)}
{allowInlineEdit && (isSaving || saveError) && (
{allowInlineEdit && ((isSaving && showSavingHint) || saveError) && (
<div className="pointer-events-none absolute bottom-2 right-3 flex flex-col items-end text-xs">
{isSaving && <span className="rounded-md bg-white/80 px-2 py-0.5 text-gray-500 shadow">...</span>}
{isSaving && showSavingHint && (
<span className="rounded-md bg-white/80 px-2 py-0.5 text-gray-500 shadow">...</span>
)}
{saveError && <span className="mt-1 rounded-md bg-white/80 px-2 py-0.5 text-red-500 shadow">{saveError}</span>}
</div>
)}