tfrere's picture
tfrere HF Staff
feat(frontend): editor refresh (embed studio, comment popover, shiki, top bar, hooks, styles)
76fc93a
Raw
History Blame Contribute Delete
2.98 kB
import { useEffect, useRef, useState } from "react";
import type { Editor as TiptapEditor } from "@tiptap/core";
import type { HocuspocusProvider } from "@hocuspocus/provider";
import { Cloud, CloudOff, Loader2 } from "lucide-react";
import { Tooltip } from "./Tooltip";
type SyncStatus = "connected" | "syncing" | "saved" | "disconnected";
interface Props {
editorInstance: TiptapEditor | null;
providerRef: { current: HocuspocusProvider | null };
}
/**
* Tiny connection/saving indicator for the editor top bar. Listens to the
* Hocuspocus provider (`connect` / `disconnect` / `synced`) and the TipTap
* editor (`update`) to flip between "Saved", "Saving..." and "Offline".
*/
export function SyncIndicator({ editorInstance, providerRef }: Props) {
const [status, setStatus] = useState<SyncStatus>("disconnected");
const timerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
useEffect(() => {
const provider = providerRef.current;
if (!provider) return;
const onConnect = () => setStatus("saved");
const onDisconnect = () => setStatus("disconnected");
const onSynced = () => setStatus("saved");
provider.on("connect", onConnect);
provider.on("disconnect", onDisconnect);
provider.on("synced", onSynced);
const wsProvider = (provider as any).configuration?.websocketProvider;
setStatus(wsProvider?.status === "connected" ? "saved" : "disconnected");
return () => {
provider.off("connect", onConnect);
provider.off("disconnect", onDisconnect);
provider.off("synced", onSynced);
};
}, [providerRef]);
useEffect(() => {
const editor = editorInstance;
const provider = providerRef.current;
if (!editor || !provider) return;
const onUpdate = () => {
setStatus("syncing");
if (timerRef.current) clearTimeout(timerRef.current);
timerRef.current = setTimeout(() => {
const wsProvider = (provider as any).configuration?.websocketProvider;
setStatus(wsProvider?.status === "connected" ? "saved" : "disconnected");
}, 1500);
};
editor.on("update", onUpdate);
return () => { editor.off("update", onUpdate); };
}, [editorInstance, providerRef]);
const label =
status === "saved" ? "Saved" :
status === "syncing" ? "Saving..." :
status === "connected" ? "Connected" :
"Offline";
const tooltip =
status === "saved" ? "All changes saved" :
status === "syncing" ? "Saving..." :
status === "connected" ? "Connected" :
"Disconnected";
return (
<Tooltip title={tooltip}>
<span className={`sync-indicator sync-indicator--${status}`}>
{status === "syncing" && <Loader2 size={14} className="spin" />}
{status === "saved" && <Cloud size={14} />}
{status === "connected" && <Cloud size={14} />}
{status === "disconnected" && <CloudOff size={14} />}
<span className="sync-indicator__label">{label}</span>
</span>
</Tooltip>
);
}