@nbt-dev/components 0.0.7 → 0.0.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{chunk-ZOGIPYYM.js → chunk-235TYE66.js} +3 -2
- package/dist/chunk-235TYE66.js.map +7 -0
- package/dist/core/index.d.ts +2 -0
- package/dist/core/use-gsheets.d.ts +29 -0
- package/dist/index.js +55 -1
- package/dist/index.js.map +3 -3
- package/dist/table/index.js +1 -1
- package/package.json +1 -1
- package/src/core/index.ts +2 -0
- package/src/core/use-gsheets.ts +88 -0
- package/src/table/data-table.tsx +7 -1
- package/dist/chunk-ZOGIPYYM.js.map +0 -7
|
@@ -249,6 +249,7 @@ var DataTable = ({ cart, entity, searchFields, onSelectRow }) => {
|
|
|
249
249
|
stream.setOnRender(() => setTick((n) => n + 1));
|
|
250
250
|
return () => stream.setOnRender(null);
|
|
251
251
|
}, [stream]);
|
|
252
|
+
const hasScroller = stream.columns.length > 0;
|
|
252
253
|
React2.useEffect(() => {
|
|
253
254
|
const el = scrollerRef.current;
|
|
254
255
|
if (!el) return;
|
|
@@ -256,7 +257,7 @@ var DataTable = ({ cart, entity, searchFields, onSelectRow }) => {
|
|
|
256
257
|
ro.observe(el);
|
|
257
258
|
setViewportH(el.clientHeight);
|
|
258
259
|
return () => ro.disconnect();
|
|
259
|
-
}, []);
|
|
260
|
+
}, [hasScroller]);
|
|
260
261
|
const rows = stream.store.rows;
|
|
261
262
|
const columns = stream.columns;
|
|
262
263
|
const totalH = rows.length * ROW_H;
|
|
@@ -464,4 +465,4 @@ export {
|
|
|
464
465
|
value_popover_default,
|
|
465
466
|
data_table_default
|
|
466
467
|
};
|
|
467
|
-
//# sourceMappingURL=chunk-
|
|
468
|
+
//# sourceMappingURL=chunk-235TYE66.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../src/table/data-table.tsx", "../src/core/utils.ts", "../src/table/value-popover.tsx"],
|
|
4
|
+
"sourcesContent": ["import React from \"react\";\nimport { useBulkSubscription } from \"../core/use-bulk-stream\";\nimport { cn } from \"../core/utils\";\nimport { TYPE_DATETIME, TYPE_DOCUMENT } from \"../generated/bulk-protocol\";\nimport ValuePopover, { type ValuePopoverData } from \"./value-popover\";\n\nconst ROW_H = 22;\nconst OVERSCAN = 6;\nconst MIN_COL_W = 80;\nconst GUTTER_W = 32;\n\nconst READONLY_FIELDS = new Set([\"id\", \"createdAt\", \"updatedAt\"]);\nconst READONLY_TYPES = new Set([TYPE_DATETIME, TYPE_DOCUMENT]);\n\ntype DataTableProps = {\n cart: string;\n entity: string;\n searchFields: readonly string[];\n onSelectRow: (rowJson: Record<string, string>) => void;\n};\n\nfunction colWidth(name: string): number {\n if (name === \"id\") return 220;\n if (name === \"createdAt\" || name === \"updatedAt\") return 180;\n if (name === \"email\" || name === \"phone\") return 180;\n if (name.endsWith(\"At\")) return 180;\n return Math.max(MIN_COL_W, Math.min(240, name.length * 9 + 24));\n}\n\nconst DataTable: React.FC<DataTableProps> = ({ cart, entity, searchFields, onSelectRow }) => {\n const stream = useBulkSubscription(cart, entity);\n const scrollerRef = React.useRef<HTMLDivElement | null>(null);\n const [scrollTop, setScrollTop] = React.useState(0);\n const [viewportH, setViewportH] = React.useState(0);\n const [query, setQuery] = React.useState(\"\");\n const [selected, setSelected] = React.useState<{ r: number; c: number } | null>(null);\n const [popover, setPopover] = React.useState<ValuePopoverData | null>(null);\n // Bump on every chunk arrival without re-rendering each FRAME_DATA.\n const [tick, setTick] = React.useState(0);\n\n React.useEffect(() => {\n stream.setOnRender(() => setTick((n) => n + 1));\n return () => stream.setOnRender(null);\n }, [stream]);\n\n // Re-run once the scroller actually mounts. On first load columns are empty,\n // so the \"Waiting for schema\u2026\" placeholder renders instead of the scroller \u2014\n // scrollerRef is null and the observer never attaches, leaving viewportH at 0\n // (only OVERSCAN rows windowed). Keying on columns-present re-attaches the\n // moment the schema arrives and the scroller appears.\n const hasScroller = stream.columns.length > 0;\n React.useEffect(() => {\n const el = scrollerRef.current;\n if (!el) return;\n const ro = new ResizeObserver(() => setViewportH(el.clientHeight));\n ro.observe(el);\n setViewportH(el.clientHeight);\n return () => ro.disconnect();\n }, [hasScroller]);\n\n const rows = stream.store.rows;\n const columns = stream.columns;\n\n const totalH = rows.length * ROW_H;\n const start = Math.max(0, Math.floor(scrollTop / ROW_H) - OVERSCAN);\n const visibleCount = Math.max(0, Math.ceil(viewportH / ROW_H) + OVERSCAN * 2);\n const end = Math.min(rows.length, start + visibleCount);\n\n const onSearchKey = (e: React.KeyboardEvent<HTMLInputElement>) => {\n if (e.key === \"Enter\") {\n stream.search(query.trim());\n } else if (e.key === \"Escape\") {\n setQuery(\"\");\n stream.clearSearch();\n }\n };\n\n const handleRowClick = (rowIdx: number) => {\n const r = rows[rowIdx];\n if (!r) return;\n const out: Record<string, string> = {};\n for (let i = 0; i < columns.length; i++) out[columns[i]!.name] = r[i] ?? \"\";\n onSelectRow(out);\n };\n\n const searchDisabled = searchFields.length === 0;\n const totalColW = GUTTER_W + columns.reduce((s, c) => s + colWidth(c.name), 0);\n const idColIndex = columns.findIndex((c) => c.name === \"id\");\n\n const scrollRowIntoView = (r: number) => {\n const el = scrollerRef.current;\n if (!el) return;\n const top = r * ROW_H;\n if (top < el.scrollTop) el.scrollTop = top;\n else if (top + ROW_H > el.scrollTop + el.clientHeight)\n el.scrollTop = top + ROW_H - el.clientHeight;\n };\n\n const onKeyDown = (e: React.KeyboardEvent<HTMLDivElement>) => {\n if (rows.length === 0 || columns.length === 0) return;\n if ((e.metaKey || e.ctrlKey) && (e.key === \"c\" || e.key === \"C\")) {\n if (!selected) return;\n const v = rows[selected.r]?.[selected.c] ?? \"\";\n void navigator.clipboard?.writeText(v);\n e.preventDefault();\n return;\n }\n const cur = selected ?? { r: start, c: 0 };\n let { r, c } = cur;\n if (e.key === \"ArrowUp\") r--;\n else if (e.key === \"ArrowDown\") r++;\n else if (e.key === \"ArrowLeft\") c--;\n else if (e.key === \"ArrowRight\") c++;\n else return;\n e.preventDefault();\n r = Math.max(0, Math.min(rows.length - 1, r));\n c = Math.max(0, Math.min(columns.length - 1, c));\n setSelected({ r, c });\n setPopover(null);\n scrollRowIntoView(r);\n };\n\n return (\n <div className=\"flex h-full flex-col bg-background\">\n <div className=\"flex h-7 shrink-0 items-center gap-2 border-b border-border px-2\">\n <input\n type=\"text\"\n value={query}\n disabled={searchDisabled}\n onChange={(e) => setQuery(e.target.value)}\n onKeyDown={onSearchKey}\n placeholder={\n searchDisabled\n ? \"Search disabled (no @@search declared)\"\n : `Search ${searchFields.join(\", \")} \u23CE`\n }\n className={cn(\n \"h-5 flex-1 rounded-sm border border-border bg-background px-2 text-[12px] outline-none\",\n \"placeholder:text-muted-foreground/70 focus:border-accent-foreground/30\",\n searchDisabled && \"opacity-50 cursor-not-allowed\",\n )}\n />\n <div className=\"shrink-0 text-[11px] text-muted-foreground tabular-nums\">\n {stream.error ? (\n <span className=\"text-red-400\">{stream.error}</span>\n ) : !stream.connected ? (\n <span>connecting\u2026</span>\n ) : (\n <span>\n {stream.loadedRows.toLocaleString()} / {stream.totalRows.toLocaleString()} rows\n {stream.streaming ? \" \u00B7 streaming\" : \"\"}\n </span>\n )}\n </div>\n </div>\n\n {columns.length === 0 ? (\n <div className=\"flex flex-1 items-center justify-center text-[12px] text-muted-foreground\">\n {stream.error ? stream.error : \"Waiting for schema\u2026\"}\n </div>\n ) : (\n <div\n ref={scrollerRef}\n tabIndex={0}\n onKeyDown={onKeyDown}\n onScroll={(e) => {\n setScrollTop((e.target as HTMLDivElement).scrollTop);\n if (popover) setPopover(null);\n }}\n className=\"group/table relative min-h-0 flex-1 overflow-auto font-mono outline-none\"\n >\n <div style={{ width: totalColW }}>\n {/* Sticky header lives inside the scroller so it shares the native\n horizontal scroll with the rows \u2014 no JS transform sync, no jitter. */}\n <div\n className=\"sticky top-0 z-10 flex h-6 select-none border-b border-border bg-background text-[11px] uppercase tracking-wider text-muted-foreground\"\n style={{ width: totalColW }}\n >\n <div\n style={{ width: GUTTER_W }}\n className=\"shrink-0 border-r border-border\"\n />\n {columns.map((c) => (\n <div\n key={c.name}\n style={{ width: colWidth(c.name) }}\n className=\"flex items-center overflow-hidden border-r border-border px-2\"\n >\n <span className=\"truncate\">{c.name}</span>\n </div>\n ))}\n </div>\n\n <div style={{ height: totalH, width: totalColW, position: \"relative\" }}>\n {rows.slice(start, end).map((row, i) => {\n const rowIdx = start + i;\n return (\n <div\n key={rowIdx}\n style={{\n top: rowIdx * ROW_H,\n height: ROW_H,\n width: totalColW,\n }}\n className={cn(\n \"absolute left-0 flex text-[12px] leading-none\",\n rowIdx % 2 === 0 ? \"bg-background\" : \"bg-muted/20\",\n )}\n >\n <button\n type=\"button\"\n title=\"Open row detail\"\n onClick={() => handleRowClick(rowIdx)}\n style={{ width: GUTTER_W }}\n className={cn(\n \"flex shrink-0 items-center justify-center border-r border-border/60\",\n \"cursor-pointer text-[10px] tabular-nums text-muted-foreground/60\",\n \"hover:bg-accent hover:text-accent-foreground\",\n )}\n >\n {rowIdx + 1}\n </button>\n {columns.map((c, ci) => {\n const isSel = selected?.r === rowIdx && selected?.c === ci;\n return (\n <div\n key={c.name}\n data-selected={isSel}\n onClick={(e) => {\n e.stopPropagation();\n setSelected({ r: rowIdx, c: ci });\n setPopover(null);\n }}\n onDoubleClick={(e) => {\n e.stopPropagation();\n setSelected({ r: rowIdx, c: ci });\n const editable =\n idColIndex >= 0 &&\n !READONLY_FIELDS.has(c.name) &&\n !READONLY_TYPES.has(c.type);\n setPopover({\n r: rowIdx,\n c: ci,\n colName: c.name,\n value: row[ci] ?? \"\",\n rect: (e.currentTarget as HTMLElement).getBoundingClientRect(),\n cart,\n entity,\n rowId: idColIndex >= 0 ? row[idColIndex] ?? \"\" : \"\",\n colType: c.type,\n editable,\n });\n }}\n style={{ width: colWidth(c.name) }}\n className={cn(\n \"flex cursor-pointer items-center overflow-hidden border-r border-border/60 px-2\",\n \"data-[selected=true]:bg-accent data-[selected=true]:text-accent-foreground\",\n \"data-[selected=true]:[box-shadow:inset_0_0_0_1px_#60a5fa]\",\n )}\n title={row[ci]}\n >\n <span className=\"truncate\">{row[ci]}</span>\n </div>\n );\n })}\n </div>\n );\n })}\n </div>\n </div>\n </div>\n )}\n {popover ? <ValuePopover data={popover} onClose={() => setPopover(null)} /> : null}\n {/* tick is bumped on each data chunk to force a re-render of the rows. */}\n {tick < 0 && <span />}\n </div>\n );\n};\n\nexport default DataTable;\n", "import { clsx, type ClassValue } from \"clsx\";\nimport { twMerge } from \"tailwind-merge\";\n\nexport function cn(...inputs: ClassValue[]) {\n return twMerge(clsx(inputs));\n}\n", "import React from \"react\";\nimport { createPortal } from \"react-dom\";\nimport { cn } from \"../core/utils\";\nimport { useDevToolsConfig } from \"../core/config\";\nimport {\n TYPE_U8,\n TYPE_U16,\n TYPE_U32,\n TYPE_U64,\n TYPE_S8,\n TYPE_S16,\n TYPE_S32,\n TYPE_S64,\n TYPE_BOOL,\n TYPE_FLOAT32,\n TYPE_FLOAT64,\n} from \"../generated/bulk-protocol\";\n\nconst WIDTH = 420;\n\nexport type ValuePopoverData = {\n r: number;\n c: number;\n colName: string;\n value: string;\n rect: DOMRect;\n cart: string;\n entity: string;\n rowId: string;\n colType: number;\n editable: boolean;\n};\n\nconst INT_TYPES = new Set([\n TYPE_U8, TYPE_U16, TYPE_U32, TYPE_U64,\n TYPE_S8, TYPE_S16, TYPE_S32, TYPE_S64,\n]);\nconst FLOAT_TYPES = new Set([TYPE_FLOAT32, TYPE_FLOAT64]);\n\nfunction position(rect: DOMRect): React.CSSProperties {\n const margin = 12;\n const left = Math.max(\n margin,\n Math.min(rect.left, window.innerWidth - WIDTH - margin),\n );\n const roomBelow = window.innerHeight - rect.bottom;\n const top = roomBelow >= 180 ? rect.bottom + 6 : Math.max(margin, rect.top - 188);\n return { left, top, width: WIDTH };\n}\n\n// Coerce the edited string to the JSON value the update handler expects for the\n// column's wire type. Returns the value or an error string.\nfunction coerce(draft: string, colType: number): { value: unknown } | { error: string } {\n if (colType === TYPE_BOOL) return { value: draft === \"true\" };\n if (INT_TYPES.has(colType)) {\n const n = Number(draft);\n if (!Number.isInteger(n)) return { error: \"Expected an integer\" };\n return { value: n };\n }\n if (FLOAT_TYPES.has(colType)) {\n const n = Number(draft);\n if (!Number.isFinite(n)) return { error: \"Expected a number\" };\n return { value: n };\n }\n return { value: draft };\n}\n\nconst ValuePopover: React.FC<{ data: ValuePopoverData; onClose: () => void }> = ({\n data,\n onClose,\n}) => {\n const { apiBaseUrl } = useDevToolsConfig();\n const [copied, setCopied] = React.useState(false);\n const [draft, setDraft] = React.useState(data.value);\n const [saving, setSaving] = React.useState(false);\n const [error, setError] = React.useState<string | null>(null);\n\n React.useEffect(() => {\n setDraft(data.value);\n setError(null);\n }, [data.value, data.r, data.c]);\n\n React.useEffect(() => {\n const onKey = (e: KeyboardEvent) => {\n if (e.key === \"Escape\") onClose();\n };\n document.addEventListener(\"keydown\", onKey);\n return () => document.removeEventListener(\"keydown\", onKey);\n }, [onClose]);\n\n const copy = async () => {\n try {\n await navigator.clipboard.writeText(data.value);\n setCopied(true);\n } catch {\n /* clipboard unavailable */\n }\n };\n\n const save = async () => {\n const result = coerce(draft, data.colType);\n if (\"error\" in result) {\n setError(result.error);\n return;\n }\n setSaving(true);\n setError(null);\n try {\n const r = await fetch(\n `${apiBaseUrl}/api/${data.cart}/${data.entity.toLowerCase()}/${encodeURIComponent(data.rowId)}`,\n {\n method: \"PUT\",\n credentials: \"include\",\n headers: { \"content-type\": \"application/json\" },\n body: JSON.stringify({ [data.colName]: result.value }),\n },\n );\n if (!r.ok) {\n setError((await r.text()) || `HTTP ${r.status}`);\n setSaving(false);\n return;\n }\n // Success: the update handler broadcasts FRAME_DELTA_UPD, which refreshes\n // the row in place \u2014 just close.\n onClose();\n } catch (e) {\n setError(e instanceof Error ? e.message : String(e));\n setSaving(false);\n }\n };\n\n const isBool = data.colType === TYPE_BOOL;\n const isNumeric = INT_TYPES.has(data.colType) || FLOAT_TYPES.has(data.colType);\n\n return createPortal(\n <>\n <div className=\"fixed inset-0 z-[60]\" onClick={onClose} />\n <div\n className={cn(\n \"nimbit-devtools dark fixed z-[61] rounded-md border border-border bg-popover p-3\",\n \"text-[12px] text-popover-foreground shadow-lg\",\n )}\n style={position(data.rect)}\n onClick={(e) => e.stopPropagation()}\n >\n <div className=\"mb-2 flex items-center justify-between gap-3\">\n <div className=\"min-w-0\">\n <div className=\"truncate font-medium text-foreground\">{data.colName}</div>\n <div className=\"text-[11px] text-muted-foreground\">Row {data.r + 1}</div>\n </div>\n <div className=\"flex shrink-0 items-center gap-1.5\">\n <button\n type=\"button\"\n onClick={copy}\n className={cn(\n \"rounded border border-border px-2 py-1 text-[11px] leading-none\",\n \"hover:bg-accent hover:text-accent-foreground\",\n )}\n >\n {copied ? \"Copied\" : \"Copy\"}\n </button>\n {data.editable ? (\n <button\n type=\"button\"\n onClick={save}\n disabled={saving}\n className={cn(\n \"rounded border border-border bg-primary/90 px-2 py-1 text-[11px] leading-none text-primary-foreground\",\n \"hover:bg-primary disabled:opacity-50\",\n )}\n >\n {saving ? \"Saving\u2026\" : \"Save\"}\n </button>\n ) : null}\n </div>\n </div>\n\n {data.editable ? (\n <>\n {isBool ? (\n <select\n value={draft}\n disabled={saving}\n onChange={(e) => setDraft(e.target.value)}\n className=\"w-full rounded-sm border border-border bg-background px-2 py-1 text-[12px] outline-none focus:border-accent-foreground/30\"\n >\n <option value=\"true\">true</option>\n <option value=\"false\">false</option>\n </select>\n ) : isNumeric ? (\n <input\n type=\"number\"\n value={draft}\n disabled={saving}\n autoFocus\n onChange={(e) => setDraft(e.target.value)}\n onKeyDown={(e) => {\n if (e.key === \"Enter\") void save();\n }}\n className=\"w-full rounded-sm border border-border bg-background px-2 py-1 font-mono text-[12px] outline-none focus:border-accent-foreground/30\"\n />\n ) : (\n <textarea\n value={draft}\n disabled={saving}\n autoFocus\n onChange={(e) => setDraft(e.target.value)}\n className=\"max-h-48 min-h-[3rem] w-full resize-y overflow-auto rounded-sm border border-border bg-background p-2 font-mono text-[11px] leading-5 outline-none focus:border-accent-foreground/30\"\n />\n )}\n {error ? (\n <div className=\"mt-2 break-words text-[11px] text-red-400\">{error}</div>\n ) : null}\n </>\n ) : (\n <div className=\"max-h-48 overflow-auto rounded-sm bg-muted/40 p-2 font-mono text-[11px] leading-5 whitespace-pre-wrap break-words\">\n {data.value.length > 0 ? (\n data.value\n ) : (\n <span className=\"font-sans italic text-muted-foreground\">(empty)</span>\n )}\n </div>\n )}\n </div>\n </>,\n document.body,\n );\n};\n\nexport default ValuePopover;\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": ["React", "jsx", "jsxs", "React"]
|
|
7
|
+
}
|
package/dist/core/index.d.ts
CHANGED
|
@@ -5,5 +5,7 @@ export { BulkStreamProvider, useBulkSubscription, useBulkRowCounts, } from "./us
|
|
|
5
5
|
export type { BulkSubscription } from "./use-bulk-stream";
|
|
6
6
|
export { useLiveBulkRegistry } from "./use-cartridge-info";
|
|
7
7
|
export type { BulkEntity, BulkRegistry, LiveRegistryState, } from "./use-cartridge-info";
|
|
8
|
+
export { useGsheetsApi } from "./use-gsheets";
|
|
9
|
+
export type { GsheetSyncConfig, GsheetSaveInput } from "./use-gsheets";
|
|
8
10
|
export { getDevToolsToken, setDevToolsToken, clearDevToolsToken, authHeaders, fetchWhoAmI, devToolsSignIn, devToolsSignUp, devToolsSignOut, wsAuthProtocols, } from "./auth";
|
|
9
11
|
export type { WhoAmI } from "./auth";
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
export type GsheetSyncConfig = {
|
|
2
|
+
cartridge: string;
|
|
3
|
+
spreadsheetId: string;
|
|
4
|
+
saEmail: string;
|
|
5
|
+
hasServiceAccount: boolean;
|
|
6
|
+
intervalSeconds: number;
|
|
7
|
+
enabled: boolean;
|
|
8
|
+
lastSyncAt: number;
|
|
9
|
+
status: string;
|
|
10
|
+
lastError: string;
|
|
11
|
+
};
|
|
12
|
+
export type GsheetSaveInput = {
|
|
13
|
+
cartridge: string;
|
|
14
|
+
spreadsheetUrl: string;
|
|
15
|
+
serviceAccountJson?: string;
|
|
16
|
+
intervalSeconds: number;
|
|
17
|
+
enabled: boolean;
|
|
18
|
+
};
|
|
19
|
+
export declare function useGsheetsApi(): {
|
|
20
|
+
list: () => Promise<GsheetSyncConfig[]>;
|
|
21
|
+
save: (input: GsheetSaveInput) => Promise<GsheetSyncConfig>;
|
|
22
|
+
syncNow: (cartridge: string) => Promise<{
|
|
23
|
+
ok: boolean;
|
|
24
|
+
error: string;
|
|
25
|
+
}>;
|
|
26
|
+
remove: (cartridge: string) => Promise<{
|
|
27
|
+
ok: boolean;
|
|
28
|
+
}>;
|
|
29
|
+
};
|
package/dist/index.js
CHANGED
|
@@ -18,7 +18,7 @@ import {
|
|
|
18
18
|
cn,
|
|
19
19
|
data_table_default,
|
|
20
20
|
value_popover_default
|
|
21
|
-
} from "./chunk-
|
|
21
|
+
} from "./chunk-235TYE66.js";
|
|
22
22
|
import {
|
|
23
23
|
BulkStreamProvider,
|
|
24
24
|
DevToolsConfigProvider,
|
|
@@ -101,6 +101,59 @@ function useLiveBulkRegistry() {
|
|
|
101
101
|
const carts = Object.keys(registry).sort();
|
|
102
102
|
return { registry, carts, coreCarts, loading, error };
|
|
103
103
|
}
|
|
104
|
+
|
|
105
|
+
// src/core/use-gsheets.ts
|
|
106
|
+
import { useCallback, useMemo } from "react";
|
|
107
|
+
async function jsonOrThrow(r) {
|
|
108
|
+
const body = await r.json().catch(() => ({}));
|
|
109
|
+
if (!r.ok) throw new Error(body?.error ?? `HTTP ${r.status}`);
|
|
110
|
+
return body;
|
|
111
|
+
}
|
|
112
|
+
function useGsheetsApi() {
|
|
113
|
+
const { apiBaseUrl } = useDevToolsConfig();
|
|
114
|
+
const list = useCallback(
|
|
115
|
+
async () => jsonOrThrow(
|
|
116
|
+
await fetch(`${apiBaseUrl}/_console/integrations/gsheets`, {
|
|
117
|
+
credentials: "include",
|
|
118
|
+
headers: authHeaders()
|
|
119
|
+
})
|
|
120
|
+
),
|
|
121
|
+
[apiBaseUrl]
|
|
122
|
+
);
|
|
123
|
+
const save = useCallback(
|
|
124
|
+
async (input) => jsonOrThrow(
|
|
125
|
+
await fetch(`${apiBaseUrl}/_console/integrations/gsheets`, {
|
|
126
|
+
method: "POST",
|
|
127
|
+
credentials: "include",
|
|
128
|
+
headers: authHeaders({ "content-type": "application/json" }),
|
|
129
|
+
body: JSON.stringify(input)
|
|
130
|
+
})
|
|
131
|
+
),
|
|
132
|
+
[apiBaseUrl]
|
|
133
|
+
);
|
|
134
|
+
const syncNow = useCallback(
|
|
135
|
+
async (cartridge) => jsonOrThrow(
|
|
136
|
+
await fetch(`${apiBaseUrl}/_console/integrations/gsheets/sync`, {
|
|
137
|
+
method: "POST",
|
|
138
|
+
credentials: "include",
|
|
139
|
+
headers: authHeaders({ "content-type": "application/json" }),
|
|
140
|
+
body: JSON.stringify({ cartridge })
|
|
141
|
+
})
|
|
142
|
+
),
|
|
143
|
+
[apiBaseUrl]
|
|
144
|
+
);
|
|
145
|
+
const remove = useCallback(
|
|
146
|
+
async (cartridge) => jsonOrThrow(
|
|
147
|
+
await fetch(`${apiBaseUrl}/_console/integrations/gsheets/${encodeURIComponent(cartridge)}`, {
|
|
148
|
+
method: "DELETE",
|
|
149
|
+
credentials: "include",
|
|
150
|
+
headers: authHeaders()
|
|
151
|
+
})
|
|
152
|
+
),
|
|
153
|
+
[apiBaseUrl]
|
|
154
|
+
);
|
|
155
|
+
return useMemo(() => ({ list, save, syncNow, remove }), [list, save, syncNow, remove]);
|
|
156
|
+
}
|
|
104
157
|
export {
|
|
105
158
|
BulkStreamProvider,
|
|
106
159
|
data_table_default as DataTable,
|
|
@@ -129,6 +182,7 @@ export {
|
|
|
129
182
|
useBulkRowCounts,
|
|
130
183
|
useBulkSubscription,
|
|
131
184
|
useDevToolsConfig,
|
|
185
|
+
useGsheetsApi,
|
|
132
186
|
useLiveBulkRegistry,
|
|
133
187
|
wsAuthProtocols,
|
|
134
188
|
wsBaseFrom
|
package/dist/index.js.map
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
|
-
"sources": ["../src/core/use-cartridge-info.ts"],
|
|
4
|
-
"sourcesContent": ["import { useEffect, useState } from \"react\";\nimport { useDevToolsConfig } from \"./config\";\nimport { authHeaders } from \"./auth\";\n\n// Live cartridge/entity registry for the Data tab. Instead of the build-time\n// `BULK_REGISTRY` (every cart in the repo, generated by `nbt generate`), we\n// read the daemon's `/_console/contracts` \u2014 which only lists *running*\n// cartridges and carries each entity's `searchFields`. Bulk WS routes are\n// derived live (`/_ws/bulk/<cart>/<entity-lower>`) and fed straight into\n// useBulkStream. So the tab reflects what is actually installed right now.\n\nexport type BulkEntity = {\n name: string;\n route: string;\n searchFields: readonly string[];\n};\nexport type BulkRegistry = Record<string, BulkEntity[]>;\n\ntype Contract = {\n cartridge?: string;\n core?: boolean;\n owns?: Record<string, { searchFields?: string[] }>;\n};\n\nfunction buildRegistry(contracts: Contract[]): { reg: BulkRegistry; core: Set<string> } {\n const reg: BulkRegistry = {};\n const core = new Set<string>();\n for (const c of contracts) {\n const cart = c.cartridge;\n if (!cart || !c.owns) continue;\n const entities: BulkEntity[] = [];\n for (const [name, ent] of Object.entries(c.owns)) {\n const sf = Array.isArray(ent?.searchFields) ? ent.searchFields : [];\n entities.push({\n name,\n route: `/_ws/bulk/${cart}/${name.toLowerCase()}`,\n searchFields: sf,\n });\n }\n if (entities.length > 0) {\n reg[cart] = entities;\n if (c.core) core.add(cart);\n }\n }\n return { reg, core };\n}\n\nexport type LiveRegistryState = {\n registry: BulkRegistry;\n carts: string[];\n // Cartridge slugs flagged `core` by the daemon. The Data tab keeps `auth`\n // visible but tucks the rest of these behind a \"more cartridges\" menu so\n // deployed (user) cartridges are what's shown by default.\n coreCarts: Set<string>;\n loading: boolean;\n error: string | null;\n};\n\nexport function useLiveBulkRegistry(): LiveRegistryState {\n const { apiBaseUrl } = useDevToolsConfig();\n const [registry, setRegistry] = useState<BulkRegistry>({});\n const [coreCarts, setCoreCarts] = useState<Set<string>>(new Set());\n const [loading, setLoading] = useState(true);\n const [error, setError] = useState<string | null>(null);\n\n useEffect(() => {\n const ac = new AbortController();\n let cancelled = false;\n setLoading(true);\n setError(null);\n (async () => {\n try {\n const r = await fetch(`${apiBaseUrl}/_console/contracts`, {\n signal: ac.signal,\n credentials: \"include\",\n headers: authHeaders(),\n });\n if (!r.ok) throw new Error(`HTTP ${r.status}`);\n const data = (await r.json()) as Contract[];\n if (!Array.isArray(data)) throw new Error(\"malformed contracts response\");\n if (cancelled) return;\n const { reg, core } = buildRegistry(data);\n setRegistry(reg);\n setCoreCarts(core);\n } catch (e) {\n if (cancelled || (e as { name?: string }).name === \"AbortError\") return;\n setError(e instanceof Error ? e.message : String(e));\n } finally {\n if (!cancelled) setLoading(false);\n }\n })();\n return () => {\n cancelled = true;\n ac.abort();\n };\n }, [apiBaseUrl]);\n\n const carts = Object.keys(registry).sort();\n return { registry, carts, coreCarts, loading, error };\n}\n"],
|
|
5
|
-
"mappings": ";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA,SAAS,WAAW,gBAAgB;AAwBpC,SAAS,cAAc,WAAiE;AACtF,QAAM,MAAoB,CAAC;AAC3B,QAAM,OAAO,oBAAI,IAAY;AAC7B,aAAW,KAAK,WAAW;AACzB,UAAM,OAAO,EAAE;AACf,QAAI,CAAC,QAAQ,CAAC,EAAE,KAAM;AACtB,UAAM,WAAyB,CAAC;AAChC,eAAW,CAAC,MAAM,GAAG,KAAK,OAAO,QAAQ,EAAE,IAAI,GAAG;AAChD,YAAM,KAAK,MAAM,QAAQ,KAAK,YAAY,IAAI,IAAI,eAAe,CAAC;AAClE,eAAS,KAAK;AAAA,QACZ;AAAA,QACA,OAAO,aAAa,IAAI,IAAI,KAAK,YAAY,CAAC;AAAA,QAC9C,cAAc;AAAA,MAChB,CAAC;AAAA,IACH;AACA,QAAI,SAAS,SAAS,GAAG;AACvB,UAAI,IAAI,IAAI;AACZ,UAAI,EAAE,KAAM,MAAK,IAAI,IAAI;AAAA,IAC3B;AAAA,EACF;AACA,SAAO,EAAE,KAAK,KAAK;AACrB;AAaO,SAAS,sBAAyC;AACvD,QAAM,EAAE,WAAW,IAAI,kBAAkB;AACzC,QAAM,CAAC,UAAU,WAAW,IAAI,SAAuB,CAAC,CAAC;AACzD,QAAM,CAAC,WAAW,YAAY,IAAI,SAAsB,oBAAI,IAAI,CAAC;AACjE,QAAM,CAAC,SAAS,UAAU,IAAI,SAAS,IAAI;AAC3C,QAAM,CAAC,OAAO,QAAQ,IAAI,SAAwB,IAAI;AAEtD,YAAU,MAAM;AACd,UAAM,KAAK,IAAI,gBAAgB;AAC/B,QAAI,YAAY;AAChB,eAAW,IAAI;AACf,aAAS,IAAI;AACb,KAAC,YAAY;AACX,UAAI;AACF,cAAM,IAAI,MAAM,MAAM,GAAG,UAAU,uBAAuB;AAAA,UACxD,QAAQ,GAAG;AAAA,UACX,aAAa;AAAA,UACb,SAAS,YAAY;AAAA,QACvB,CAAC;AACD,YAAI,CAAC,EAAE,GAAI,OAAM,IAAI,MAAM,QAAQ,EAAE,MAAM,EAAE;AAC7C,cAAM,OAAQ,MAAM,EAAE,KAAK;AAC3B,YAAI,CAAC,MAAM,QAAQ,IAAI,EAAG,OAAM,IAAI,MAAM,8BAA8B;AACxE,YAAI,UAAW;AACf,cAAM,EAAE,KAAK,KAAK,IAAI,cAAc,IAAI;AACxC,oBAAY,GAAG;AACf,qBAAa,IAAI;AAAA,MACnB,SAAS,GAAG;AACV,YAAI,aAAc,EAAwB,SAAS,aAAc;AACjE,iBAAS,aAAa,QAAQ,EAAE,UAAU,OAAO,CAAC,CAAC;AAAA,MACrD,UAAE;AACA,YAAI,CAAC,UAAW,YAAW,KAAK;AAAA,MAClC;AAAA,IACF,GAAG;AACH,WAAO,MAAM;AACX,kBAAY;AACZ,SAAG,MAAM;AAAA,IACX;AAAA,EACF,GAAG,CAAC,UAAU,CAAC;AAEf,QAAM,QAAQ,OAAO,KAAK,QAAQ,EAAE,KAAK;AACzC,SAAO,EAAE,UAAU,OAAO,WAAW,SAAS,MAAM;AACtD;",
|
|
3
|
+
"sources": ["../src/core/use-cartridge-info.ts", "../src/core/use-gsheets.ts"],
|
|
4
|
+
"sourcesContent": ["import { useEffect, useState } from \"react\";\nimport { useDevToolsConfig } from \"./config\";\nimport { authHeaders } from \"./auth\";\n\n// Live cartridge/entity registry for the Data tab. Instead of the build-time\n// `BULK_REGISTRY` (every cart in the repo, generated by `nbt generate`), we\n// read the daemon's `/_console/contracts` \u2014 which only lists *running*\n// cartridges and carries each entity's `searchFields`. Bulk WS routes are\n// derived live (`/_ws/bulk/<cart>/<entity-lower>`) and fed straight into\n// useBulkStream. So the tab reflects what is actually installed right now.\n\nexport type BulkEntity = {\n name: string;\n route: string;\n searchFields: readonly string[];\n};\nexport type BulkRegistry = Record<string, BulkEntity[]>;\n\ntype Contract = {\n cartridge?: string;\n core?: boolean;\n owns?: Record<string, { searchFields?: string[] }>;\n};\n\nfunction buildRegistry(contracts: Contract[]): { reg: BulkRegistry; core: Set<string> } {\n const reg: BulkRegistry = {};\n const core = new Set<string>();\n for (const c of contracts) {\n const cart = c.cartridge;\n if (!cart || !c.owns) continue;\n const entities: BulkEntity[] = [];\n for (const [name, ent] of Object.entries(c.owns)) {\n const sf = Array.isArray(ent?.searchFields) ? ent.searchFields : [];\n entities.push({\n name,\n route: `/_ws/bulk/${cart}/${name.toLowerCase()}`,\n searchFields: sf,\n });\n }\n if (entities.length > 0) {\n reg[cart] = entities;\n if (c.core) core.add(cart);\n }\n }\n return { reg, core };\n}\n\nexport type LiveRegistryState = {\n registry: BulkRegistry;\n carts: string[];\n // Cartridge slugs flagged `core` by the daemon. The Data tab keeps `auth`\n // visible but tucks the rest of these behind a \"more cartridges\" menu so\n // deployed (user) cartridges are what's shown by default.\n coreCarts: Set<string>;\n loading: boolean;\n error: string | null;\n};\n\nexport function useLiveBulkRegistry(): LiveRegistryState {\n const { apiBaseUrl } = useDevToolsConfig();\n const [registry, setRegistry] = useState<BulkRegistry>({});\n const [coreCarts, setCoreCarts] = useState<Set<string>>(new Set());\n const [loading, setLoading] = useState(true);\n const [error, setError] = useState<string | null>(null);\n\n useEffect(() => {\n const ac = new AbortController();\n let cancelled = false;\n setLoading(true);\n setError(null);\n (async () => {\n try {\n const r = await fetch(`${apiBaseUrl}/_console/contracts`, {\n signal: ac.signal,\n credentials: \"include\",\n headers: authHeaders(),\n });\n if (!r.ok) throw new Error(`HTTP ${r.status}`);\n const data = (await r.json()) as Contract[];\n if (!Array.isArray(data)) throw new Error(\"malformed contracts response\");\n if (cancelled) return;\n const { reg, core } = buildRegistry(data);\n setRegistry(reg);\n setCoreCarts(core);\n } catch (e) {\n if (cancelled || (e as { name?: string }).name === \"AbortError\") return;\n setError(e instanceof Error ? e.message : String(e));\n } finally {\n if (!cancelled) setLoading(false);\n }\n })();\n return () => {\n cancelled = true;\n ac.abort();\n };\n }, [apiBaseUrl]);\n\n const carts = Object.keys(registry).sort();\n return { registry, carts, coreCarts, loading, error };\n}\n", "// Fetchers + hook for the Google Sheets cartridge-sync admin endpoints\n// (/_console/integrations/gsheets/*). Admin-gated server-side \u2014 the requests\n// carry the devtools Bearer token (authHeaders) like the contracts probe.\n\nimport { useCallback, useMemo } from \"react\";\nimport { useDevToolsConfig } from \"./config\";\nimport { authHeaders } from \"./auth\";\n\nexport type GsheetSyncConfig = {\n cartridge: string;\n spreadsheetId: string;\n saEmail: string;\n hasServiceAccount: boolean;\n intervalSeconds: number;\n enabled: boolean;\n lastSyncAt: number; // ms epoch, 0 = never\n status: string; // \"ok\" | \"error\" | \"\"\n lastError: string;\n};\n\nexport type GsheetSaveInput = {\n cartridge: string;\n spreadsheetUrl: string;\n serviceAccountJson?: string; // omit to keep the stored key\n intervalSeconds: number;\n enabled: boolean;\n};\n\nasync function jsonOrThrow(r: Response) {\n const body = await r.json().catch(() => ({}));\n if (!r.ok) throw new Error((body as { error?: string })?.error ?? `HTTP ${r.status}`);\n return body;\n}\n\nexport function useGsheetsApi() {\n const { apiBaseUrl } = useDevToolsConfig();\n\n const list = useCallback(\n async (): Promise<GsheetSyncConfig[]> =>\n jsonOrThrow(\n await fetch(`${apiBaseUrl}/_console/integrations/gsheets`, {\n credentials: \"include\",\n headers: authHeaders(),\n }),\n ),\n [apiBaseUrl],\n );\n\n const save = useCallback(\n async (input: GsheetSaveInput): Promise<GsheetSyncConfig> =>\n jsonOrThrow(\n await fetch(`${apiBaseUrl}/_console/integrations/gsheets`, {\n method: \"POST\",\n credentials: \"include\",\n headers: authHeaders({ \"content-type\": \"application/json\" }),\n body: JSON.stringify(input),\n }),\n ),\n [apiBaseUrl],\n );\n\n const syncNow = useCallback(\n async (cartridge: string): Promise<{ ok: boolean; error: string }> =>\n jsonOrThrow(\n await fetch(`${apiBaseUrl}/_console/integrations/gsheets/sync`, {\n method: \"POST\",\n credentials: \"include\",\n headers: authHeaders({ \"content-type\": \"application/json\" }),\n body: JSON.stringify({ cartridge }),\n }),\n ),\n [apiBaseUrl],\n );\n\n const remove = useCallback(\n async (cartridge: string): Promise<{ ok: boolean }> =>\n jsonOrThrow(\n await fetch(`${apiBaseUrl}/_console/integrations/gsheets/${encodeURIComponent(cartridge)}`, {\n method: \"DELETE\",\n credentials: \"include\",\n headers: authHeaders(),\n }),\n ),\n [apiBaseUrl],\n );\n\n return useMemo(() => ({ list, save, syncNow, remove }), [list, save, syncNow, remove]);\n}\n"],
|
|
5
|
+
"mappings": ";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA,SAAS,WAAW,gBAAgB;AAwBpC,SAAS,cAAc,WAAiE;AACtF,QAAM,MAAoB,CAAC;AAC3B,QAAM,OAAO,oBAAI,IAAY;AAC7B,aAAW,KAAK,WAAW;AACzB,UAAM,OAAO,EAAE;AACf,QAAI,CAAC,QAAQ,CAAC,EAAE,KAAM;AACtB,UAAM,WAAyB,CAAC;AAChC,eAAW,CAAC,MAAM,GAAG,KAAK,OAAO,QAAQ,EAAE,IAAI,GAAG;AAChD,YAAM,KAAK,MAAM,QAAQ,KAAK,YAAY,IAAI,IAAI,eAAe,CAAC;AAClE,eAAS,KAAK;AAAA,QACZ;AAAA,QACA,OAAO,aAAa,IAAI,IAAI,KAAK,YAAY,CAAC;AAAA,QAC9C,cAAc;AAAA,MAChB,CAAC;AAAA,IACH;AACA,QAAI,SAAS,SAAS,GAAG;AACvB,UAAI,IAAI,IAAI;AACZ,UAAI,EAAE,KAAM,MAAK,IAAI,IAAI;AAAA,IAC3B;AAAA,EACF;AACA,SAAO,EAAE,KAAK,KAAK;AACrB;AAaO,SAAS,sBAAyC;AACvD,QAAM,EAAE,WAAW,IAAI,kBAAkB;AACzC,QAAM,CAAC,UAAU,WAAW,IAAI,SAAuB,CAAC,CAAC;AACzD,QAAM,CAAC,WAAW,YAAY,IAAI,SAAsB,oBAAI,IAAI,CAAC;AACjE,QAAM,CAAC,SAAS,UAAU,IAAI,SAAS,IAAI;AAC3C,QAAM,CAAC,OAAO,QAAQ,IAAI,SAAwB,IAAI;AAEtD,YAAU,MAAM;AACd,UAAM,KAAK,IAAI,gBAAgB;AAC/B,QAAI,YAAY;AAChB,eAAW,IAAI;AACf,aAAS,IAAI;AACb,KAAC,YAAY;AACX,UAAI;AACF,cAAM,IAAI,MAAM,MAAM,GAAG,UAAU,uBAAuB;AAAA,UACxD,QAAQ,GAAG;AAAA,UACX,aAAa;AAAA,UACb,SAAS,YAAY;AAAA,QACvB,CAAC;AACD,YAAI,CAAC,EAAE,GAAI,OAAM,IAAI,MAAM,QAAQ,EAAE,MAAM,EAAE;AAC7C,cAAM,OAAQ,MAAM,EAAE,KAAK;AAC3B,YAAI,CAAC,MAAM,QAAQ,IAAI,EAAG,OAAM,IAAI,MAAM,8BAA8B;AACxE,YAAI,UAAW;AACf,cAAM,EAAE,KAAK,KAAK,IAAI,cAAc,IAAI;AACxC,oBAAY,GAAG;AACf,qBAAa,IAAI;AAAA,MACnB,SAAS,GAAG;AACV,YAAI,aAAc,EAAwB,SAAS,aAAc;AACjE,iBAAS,aAAa,QAAQ,EAAE,UAAU,OAAO,CAAC,CAAC;AAAA,MACrD,UAAE;AACA,YAAI,CAAC,UAAW,YAAW,KAAK;AAAA,MAClC;AAAA,IACF,GAAG;AACH,WAAO,MAAM;AACX,kBAAY;AACZ,SAAG,MAAM;AAAA,IACX;AAAA,EACF,GAAG,CAAC,UAAU,CAAC;AAEf,QAAM,QAAQ,OAAO,KAAK,QAAQ,EAAE,KAAK;AACzC,SAAO,EAAE,UAAU,OAAO,WAAW,SAAS,MAAM;AACtD;;;AC/FA,SAAS,aAAa,eAAe;AAwBrC,eAAe,YAAY,GAAa;AACtC,QAAM,OAAO,MAAM,EAAE,KAAK,EAAE,MAAM,OAAO,CAAC,EAAE;AAC5C,MAAI,CAAC,EAAE,GAAI,OAAM,IAAI,MAAO,MAA6B,SAAS,QAAQ,EAAE,MAAM,EAAE;AACpF,SAAO;AACT;AAEO,SAAS,gBAAgB;AAC9B,QAAM,EAAE,WAAW,IAAI,kBAAkB;AAEzC,QAAM,OAAO;AAAA,IACX,YACE;AAAA,MACE,MAAM,MAAM,GAAG,UAAU,kCAAkC;AAAA,QACzD,aAAa;AAAA,QACb,SAAS,YAAY;AAAA,MACvB,CAAC;AAAA,IACH;AAAA,IACF,CAAC,UAAU;AAAA,EACb;AAEA,QAAM,OAAO;AAAA,IACX,OAAO,UACL;AAAA,MACE,MAAM,MAAM,GAAG,UAAU,kCAAkC;AAAA,QACzD,QAAQ;AAAA,QACR,aAAa;AAAA,QACb,SAAS,YAAY,EAAE,gBAAgB,mBAAmB,CAAC;AAAA,QAC3D,MAAM,KAAK,UAAU,KAAK;AAAA,MAC5B,CAAC;AAAA,IACH;AAAA,IACF,CAAC,UAAU;AAAA,EACb;AAEA,QAAM,UAAU;AAAA,IACd,OAAO,cACL;AAAA,MACE,MAAM,MAAM,GAAG,UAAU,uCAAuC;AAAA,QAC9D,QAAQ;AAAA,QACR,aAAa;AAAA,QACb,SAAS,YAAY,EAAE,gBAAgB,mBAAmB,CAAC;AAAA,QAC3D,MAAM,KAAK,UAAU,EAAE,UAAU,CAAC;AAAA,MACpC,CAAC;AAAA,IACH;AAAA,IACF,CAAC,UAAU;AAAA,EACb;AAEA,QAAM,SAAS;AAAA,IACb,OAAO,cACL;AAAA,MACE,MAAM,MAAM,GAAG,UAAU,kCAAkC,mBAAmB,SAAS,CAAC,IAAI;AAAA,QAC1F,QAAQ;AAAA,QACR,aAAa;AAAA,QACb,SAAS,YAAY;AAAA,MACvB,CAAC;AAAA,IACH;AAAA,IACF,CAAC,UAAU;AAAA,EACb;AAEA,SAAO,QAAQ,OAAO,EAAE,MAAM,MAAM,SAAS,OAAO,IAAI,CAAC,MAAM,MAAM,SAAS,MAAM,CAAC;AACvF;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
package/dist/table/index.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nbt-dev/components",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.9",
|
|
4
4
|
"description": "Reusable React building blocks for NBT-console apps: the CodeMirror NBT editor (+LSP), the entity graph, and the live data table.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
package/src/core/index.ts
CHANGED
|
@@ -21,6 +21,8 @@ export type {
|
|
|
21
21
|
BulkRegistry,
|
|
22
22
|
LiveRegistryState,
|
|
23
23
|
} from "./use-cartridge-info";
|
|
24
|
+
export { useGsheetsApi } from "./use-gsheets";
|
|
25
|
+
export type { GsheetSyncConfig, GsheetSaveInput } from "./use-gsheets";
|
|
24
26
|
export {
|
|
25
27
|
getDevToolsToken,
|
|
26
28
|
setDevToolsToken,
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
// Fetchers + hook for the Google Sheets cartridge-sync admin endpoints
|
|
2
|
+
// (/_console/integrations/gsheets/*). Admin-gated server-side — the requests
|
|
3
|
+
// carry the devtools Bearer token (authHeaders) like the contracts probe.
|
|
4
|
+
|
|
5
|
+
import { useCallback, useMemo } from "react";
|
|
6
|
+
import { useDevToolsConfig } from "./config";
|
|
7
|
+
import { authHeaders } from "./auth";
|
|
8
|
+
|
|
9
|
+
export type GsheetSyncConfig = {
|
|
10
|
+
cartridge: string;
|
|
11
|
+
spreadsheetId: string;
|
|
12
|
+
saEmail: string;
|
|
13
|
+
hasServiceAccount: boolean;
|
|
14
|
+
intervalSeconds: number;
|
|
15
|
+
enabled: boolean;
|
|
16
|
+
lastSyncAt: number; // ms epoch, 0 = never
|
|
17
|
+
status: string; // "ok" | "error" | ""
|
|
18
|
+
lastError: string;
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
export type GsheetSaveInput = {
|
|
22
|
+
cartridge: string;
|
|
23
|
+
spreadsheetUrl: string;
|
|
24
|
+
serviceAccountJson?: string; // omit to keep the stored key
|
|
25
|
+
intervalSeconds: number;
|
|
26
|
+
enabled: boolean;
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
async function jsonOrThrow(r: Response) {
|
|
30
|
+
const body = await r.json().catch(() => ({}));
|
|
31
|
+
if (!r.ok) throw new Error((body as { error?: string })?.error ?? `HTTP ${r.status}`);
|
|
32
|
+
return body;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export function useGsheetsApi() {
|
|
36
|
+
const { apiBaseUrl } = useDevToolsConfig();
|
|
37
|
+
|
|
38
|
+
const list = useCallback(
|
|
39
|
+
async (): Promise<GsheetSyncConfig[]> =>
|
|
40
|
+
jsonOrThrow(
|
|
41
|
+
await fetch(`${apiBaseUrl}/_console/integrations/gsheets`, {
|
|
42
|
+
credentials: "include",
|
|
43
|
+
headers: authHeaders(),
|
|
44
|
+
}),
|
|
45
|
+
),
|
|
46
|
+
[apiBaseUrl],
|
|
47
|
+
);
|
|
48
|
+
|
|
49
|
+
const save = useCallback(
|
|
50
|
+
async (input: GsheetSaveInput): Promise<GsheetSyncConfig> =>
|
|
51
|
+
jsonOrThrow(
|
|
52
|
+
await fetch(`${apiBaseUrl}/_console/integrations/gsheets`, {
|
|
53
|
+
method: "POST",
|
|
54
|
+
credentials: "include",
|
|
55
|
+
headers: authHeaders({ "content-type": "application/json" }),
|
|
56
|
+
body: JSON.stringify(input),
|
|
57
|
+
}),
|
|
58
|
+
),
|
|
59
|
+
[apiBaseUrl],
|
|
60
|
+
);
|
|
61
|
+
|
|
62
|
+
const syncNow = useCallback(
|
|
63
|
+
async (cartridge: string): Promise<{ ok: boolean; error: string }> =>
|
|
64
|
+
jsonOrThrow(
|
|
65
|
+
await fetch(`${apiBaseUrl}/_console/integrations/gsheets/sync`, {
|
|
66
|
+
method: "POST",
|
|
67
|
+
credentials: "include",
|
|
68
|
+
headers: authHeaders({ "content-type": "application/json" }),
|
|
69
|
+
body: JSON.stringify({ cartridge }),
|
|
70
|
+
}),
|
|
71
|
+
),
|
|
72
|
+
[apiBaseUrl],
|
|
73
|
+
);
|
|
74
|
+
|
|
75
|
+
const remove = useCallback(
|
|
76
|
+
async (cartridge: string): Promise<{ ok: boolean }> =>
|
|
77
|
+
jsonOrThrow(
|
|
78
|
+
await fetch(`${apiBaseUrl}/_console/integrations/gsheets/${encodeURIComponent(cartridge)}`, {
|
|
79
|
+
method: "DELETE",
|
|
80
|
+
credentials: "include",
|
|
81
|
+
headers: authHeaders(),
|
|
82
|
+
}),
|
|
83
|
+
),
|
|
84
|
+
[apiBaseUrl],
|
|
85
|
+
);
|
|
86
|
+
|
|
87
|
+
return useMemo(() => ({ list, save, syncNow, remove }), [list, save, syncNow, remove]);
|
|
88
|
+
}
|
package/src/table/data-table.tsx
CHANGED
|
@@ -43,6 +43,12 @@ const DataTable: React.FC<DataTableProps> = ({ cart, entity, searchFields, onSel
|
|
|
43
43
|
return () => stream.setOnRender(null);
|
|
44
44
|
}, [stream]);
|
|
45
45
|
|
|
46
|
+
// Re-run once the scroller actually mounts. On first load columns are empty,
|
|
47
|
+
// so the "Waiting for schema…" placeholder renders instead of the scroller —
|
|
48
|
+
// scrollerRef is null and the observer never attaches, leaving viewportH at 0
|
|
49
|
+
// (only OVERSCAN rows windowed). Keying on columns-present re-attaches the
|
|
50
|
+
// moment the schema arrives and the scroller appears.
|
|
51
|
+
const hasScroller = stream.columns.length > 0;
|
|
46
52
|
React.useEffect(() => {
|
|
47
53
|
const el = scrollerRef.current;
|
|
48
54
|
if (!el) return;
|
|
@@ -50,7 +56,7 @@ const DataTable: React.FC<DataTableProps> = ({ cart, entity, searchFields, onSel
|
|
|
50
56
|
ro.observe(el);
|
|
51
57
|
setViewportH(el.clientHeight);
|
|
52
58
|
return () => ro.disconnect();
|
|
53
|
-
}, []);
|
|
59
|
+
}, [hasScroller]);
|
|
54
60
|
|
|
55
61
|
const rows = stream.store.rows;
|
|
56
62
|
const columns = stream.columns;
|
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"version": 3,
|
|
3
|
-
"sources": ["../src/table/data-table.tsx", "../src/core/utils.ts", "../src/table/value-popover.tsx"],
|
|
4
|
-
"sourcesContent": ["import React from \"react\";\nimport { useBulkSubscription } from \"../core/use-bulk-stream\";\nimport { cn } from \"../core/utils\";\nimport { TYPE_DATETIME, TYPE_DOCUMENT } from \"../generated/bulk-protocol\";\nimport ValuePopover, { type ValuePopoverData } from \"./value-popover\";\n\nconst ROW_H = 22;\nconst OVERSCAN = 6;\nconst MIN_COL_W = 80;\nconst GUTTER_W = 32;\n\nconst READONLY_FIELDS = new Set([\"id\", \"createdAt\", \"updatedAt\"]);\nconst READONLY_TYPES = new Set([TYPE_DATETIME, TYPE_DOCUMENT]);\n\ntype DataTableProps = {\n cart: string;\n entity: string;\n searchFields: readonly string[];\n onSelectRow: (rowJson: Record<string, string>) => void;\n};\n\nfunction colWidth(name: string): number {\n if (name === \"id\") return 220;\n if (name === \"createdAt\" || name === \"updatedAt\") return 180;\n if (name === \"email\" || name === \"phone\") return 180;\n if (name.endsWith(\"At\")) return 180;\n return Math.max(MIN_COL_W, Math.min(240, name.length * 9 + 24));\n}\n\nconst DataTable: React.FC<DataTableProps> = ({ cart, entity, searchFields, onSelectRow }) => {\n const stream = useBulkSubscription(cart, entity);\n const scrollerRef = React.useRef<HTMLDivElement | null>(null);\n const [scrollTop, setScrollTop] = React.useState(0);\n const [viewportH, setViewportH] = React.useState(0);\n const [query, setQuery] = React.useState(\"\");\n const [selected, setSelected] = React.useState<{ r: number; c: number } | null>(null);\n const [popover, setPopover] = React.useState<ValuePopoverData | null>(null);\n // Bump on every chunk arrival without re-rendering each FRAME_DATA.\n const [tick, setTick] = React.useState(0);\n\n React.useEffect(() => {\n stream.setOnRender(() => setTick((n) => n + 1));\n return () => stream.setOnRender(null);\n }, [stream]);\n\n React.useEffect(() => {\n const el = scrollerRef.current;\n if (!el) return;\n const ro = new ResizeObserver(() => setViewportH(el.clientHeight));\n ro.observe(el);\n setViewportH(el.clientHeight);\n return () => ro.disconnect();\n }, []);\n\n const rows = stream.store.rows;\n const columns = stream.columns;\n\n const totalH = rows.length * ROW_H;\n const start = Math.max(0, Math.floor(scrollTop / ROW_H) - OVERSCAN);\n const visibleCount = Math.max(0, Math.ceil(viewportH / ROW_H) + OVERSCAN * 2);\n const end = Math.min(rows.length, start + visibleCount);\n\n const onSearchKey = (e: React.KeyboardEvent<HTMLInputElement>) => {\n if (e.key === \"Enter\") {\n stream.search(query.trim());\n } else if (e.key === \"Escape\") {\n setQuery(\"\");\n stream.clearSearch();\n }\n };\n\n const handleRowClick = (rowIdx: number) => {\n const r = rows[rowIdx];\n if (!r) return;\n const out: Record<string, string> = {};\n for (let i = 0; i < columns.length; i++) out[columns[i]!.name] = r[i] ?? \"\";\n onSelectRow(out);\n };\n\n const searchDisabled = searchFields.length === 0;\n const totalColW = GUTTER_W + columns.reduce((s, c) => s + colWidth(c.name), 0);\n const idColIndex = columns.findIndex((c) => c.name === \"id\");\n\n const scrollRowIntoView = (r: number) => {\n const el = scrollerRef.current;\n if (!el) return;\n const top = r * ROW_H;\n if (top < el.scrollTop) el.scrollTop = top;\n else if (top + ROW_H > el.scrollTop + el.clientHeight)\n el.scrollTop = top + ROW_H - el.clientHeight;\n };\n\n const onKeyDown = (e: React.KeyboardEvent<HTMLDivElement>) => {\n if (rows.length === 0 || columns.length === 0) return;\n if ((e.metaKey || e.ctrlKey) && (e.key === \"c\" || e.key === \"C\")) {\n if (!selected) return;\n const v = rows[selected.r]?.[selected.c] ?? \"\";\n void navigator.clipboard?.writeText(v);\n e.preventDefault();\n return;\n }\n const cur = selected ?? { r: start, c: 0 };\n let { r, c } = cur;\n if (e.key === \"ArrowUp\") r--;\n else if (e.key === \"ArrowDown\") r++;\n else if (e.key === \"ArrowLeft\") c--;\n else if (e.key === \"ArrowRight\") c++;\n else return;\n e.preventDefault();\n r = Math.max(0, Math.min(rows.length - 1, r));\n c = Math.max(0, Math.min(columns.length - 1, c));\n setSelected({ r, c });\n setPopover(null);\n scrollRowIntoView(r);\n };\n\n return (\n <div className=\"flex h-full flex-col bg-background\">\n <div className=\"flex h-7 shrink-0 items-center gap-2 border-b border-border px-2\">\n <input\n type=\"text\"\n value={query}\n disabled={searchDisabled}\n onChange={(e) => setQuery(e.target.value)}\n onKeyDown={onSearchKey}\n placeholder={\n searchDisabled\n ? \"Search disabled (no @@search declared)\"\n : `Search ${searchFields.join(\", \")} \u23CE`\n }\n className={cn(\n \"h-5 flex-1 rounded-sm border border-border bg-background px-2 text-[12px] outline-none\",\n \"placeholder:text-muted-foreground/70 focus:border-accent-foreground/30\",\n searchDisabled && \"opacity-50 cursor-not-allowed\",\n )}\n />\n <div className=\"shrink-0 text-[11px] text-muted-foreground tabular-nums\">\n {stream.error ? (\n <span className=\"text-red-400\">{stream.error}</span>\n ) : !stream.connected ? (\n <span>connecting\u2026</span>\n ) : (\n <span>\n {stream.loadedRows.toLocaleString()} / {stream.totalRows.toLocaleString()} rows\n {stream.streaming ? \" \u00B7 streaming\" : \"\"}\n </span>\n )}\n </div>\n </div>\n\n {columns.length === 0 ? (\n <div className=\"flex flex-1 items-center justify-center text-[12px] text-muted-foreground\">\n {stream.error ? stream.error : \"Waiting for schema\u2026\"}\n </div>\n ) : (\n <div\n ref={scrollerRef}\n tabIndex={0}\n onKeyDown={onKeyDown}\n onScroll={(e) => {\n setScrollTop((e.target as HTMLDivElement).scrollTop);\n if (popover) setPopover(null);\n }}\n className=\"group/table relative min-h-0 flex-1 overflow-auto font-mono outline-none\"\n >\n <div style={{ width: totalColW }}>\n {/* Sticky header lives inside the scroller so it shares the native\n horizontal scroll with the rows \u2014 no JS transform sync, no jitter. */}\n <div\n className=\"sticky top-0 z-10 flex h-6 select-none border-b border-border bg-background text-[11px] uppercase tracking-wider text-muted-foreground\"\n style={{ width: totalColW }}\n >\n <div\n style={{ width: GUTTER_W }}\n className=\"shrink-0 border-r border-border\"\n />\n {columns.map((c) => (\n <div\n key={c.name}\n style={{ width: colWidth(c.name) }}\n className=\"flex items-center overflow-hidden border-r border-border px-2\"\n >\n <span className=\"truncate\">{c.name}</span>\n </div>\n ))}\n </div>\n\n <div style={{ height: totalH, width: totalColW, position: \"relative\" }}>\n {rows.slice(start, end).map((row, i) => {\n const rowIdx = start + i;\n return (\n <div\n key={rowIdx}\n style={{\n top: rowIdx * ROW_H,\n height: ROW_H,\n width: totalColW,\n }}\n className={cn(\n \"absolute left-0 flex text-[12px] leading-none\",\n rowIdx % 2 === 0 ? \"bg-background\" : \"bg-muted/20\",\n )}\n >\n <button\n type=\"button\"\n title=\"Open row detail\"\n onClick={() => handleRowClick(rowIdx)}\n style={{ width: GUTTER_W }}\n className={cn(\n \"flex shrink-0 items-center justify-center border-r border-border/60\",\n \"cursor-pointer text-[10px] tabular-nums text-muted-foreground/60\",\n \"hover:bg-accent hover:text-accent-foreground\",\n )}\n >\n {rowIdx + 1}\n </button>\n {columns.map((c, ci) => {\n const isSel = selected?.r === rowIdx && selected?.c === ci;\n return (\n <div\n key={c.name}\n data-selected={isSel}\n onClick={(e) => {\n e.stopPropagation();\n setSelected({ r: rowIdx, c: ci });\n setPopover(null);\n }}\n onDoubleClick={(e) => {\n e.stopPropagation();\n setSelected({ r: rowIdx, c: ci });\n const editable =\n idColIndex >= 0 &&\n !READONLY_FIELDS.has(c.name) &&\n !READONLY_TYPES.has(c.type);\n setPopover({\n r: rowIdx,\n c: ci,\n colName: c.name,\n value: row[ci] ?? \"\",\n rect: (e.currentTarget as HTMLElement).getBoundingClientRect(),\n cart,\n entity,\n rowId: idColIndex >= 0 ? row[idColIndex] ?? \"\" : \"\",\n colType: c.type,\n editable,\n });\n }}\n style={{ width: colWidth(c.name) }}\n className={cn(\n \"flex cursor-pointer items-center overflow-hidden border-r border-border/60 px-2\",\n \"data-[selected=true]:bg-accent data-[selected=true]:text-accent-foreground\",\n \"data-[selected=true]:[box-shadow:inset_0_0_0_1px_#60a5fa]\",\n )}\n title={row[ci]}\n >\n <span className=\"truncate\">{row[ci]}</span>\n </div>\n );\n })}\n </div>\n );\n })}\n </div>\n </div>\n </div>\n )}\n {popover ? <ValuePopover data={popover} onClose={() => setPopover(null)} /> : null}\n {/* tick is bumped on each data chunk to force a re-render of the rows. */}\n {tick < 0 && <span />}\n </div>\n );\n};\n\nexport default DataTable;\n", "import { clsx, type ClassValue } from \"clsx\";\nimport { twMerge } from \"tailwind-merge\";\n\nexport function cn(...inputs: ClassValue[]) {\n return twMerge(clsx(inputs));\n}\n", "import React from \"react\";\nimport { createPortal } from \"react-dom\";\nimport { cn } from \"../core/utils\";\nimport { useDevToolsConfig } from \"../core/config\";\nimport {\n TYPE_U8,\n TYPE_U16,\n TYPE_U32,\n TYPE_U64,\n TYPE_S8,\n TYPE_S16,\n TYPE_S32,\n TYPE_S64,\n TYPE_BOOL,\n TYPE_FLOAT32,\n TYPE_FLOAT64,\n} from \"../generated/bulk-protocol\";\n\nconst WIDTH = 420;\n\nexport type ValuePopoverData = {\n r: number;\n c: number;\n colName: string;\n value: string;\n rect: DOMRect;\n cart: string;\n entity: string;\n rowId: string;\n colType: number;\n editable: boolean;\n};\n\nconst INT_TYPES = new Set([\n TYPE_U8, TYPE_U16, TYPE_U32, TYPE_U64,\n TYPE_S8, TYPE_S16, TYPE_S32, TYPE_S64,\n]);\nconst FLOAT_TYPES = new Set([TYPE_FLOAT32, TYPE_FLOAT64]);\n\nfunction position(rect: DOMRect): React.CSSProperties {\n const margin = 12;\n const left = Math.max(\n margin,\n Math.min(rect.left, window.innerWidth - WIDTH - margin),\n );\n const roomBelow = window.innerHeight - rect.bottom;\n const top = roomBelow >= 180 ? rect.bottom + 6 : Math.max(margin, rect.top - 188);\n return { left, top, width: WIDTH };\n}\n\n// Coerce the edited string to the JSON value the update handler expects for the\n// column's wire type. Returns the value or an error string.\nfunction coerce(draft: string, colType: number): { value: unknown } | { error: string } {\n if (colType === TYPE_BOOL) return { value: draft === \"true\" };\n if (INT_TYPES.has(colType)) {\n const n = Number(draft);\n if (!Number.isInteger(n)) return { error: \"Expected an integer\" };\n return { value: n };\n }\n if (FLOAT_TYPES.has(colType)) {\n const n = Number(draft);\n if (!Number.isFinite(n)) return { error: \"Expected a number\" };\n return { value: n };\n }\n return { value: draft };\n}\n\nconst ValuePopover: React.FC<{ data: ValuePopoverData; onClose: () => void }> = ({\n data,\n onClose,\n}) => {\n const { apiBaseUrl } = useDevToolsConfig();\n const [copied, setCopied] = React.useState(false);\n const [draft, setDraft] = React.useState(data.value);\n const [saving, setSaving] = React.useState(false);\n const [error, setError] = React.useState<string | null>(null);\n\n React.useEffect(() => {\n setDraft(data.value);\n setError(null);\n }, [data.value, data.r, data.c]);\n\n React.useEffect(() => {\n const onKey = (e: KeyboardEvent) => {\n if (e.key === \"Escape\") onClose();\n };\n document.addEventListener(\"keydown\", onKey);\n return () => document.removeEventListener(\"keydown\", onKey);\n }, [onClose]);\n\n const copy = async () => {\n try {\n await navigator.clipboard.writeText(data.value);\n setCopied(true);\n } catch {\n /* clipboard unavailable */\n }\n };\n\n const save = async () => {\n const result = coerce(draft, data.colType);\n if (\"error\" in result) {\n setError(result.error);\n return;\n }\n setSaving(true);\n setError(null);\n try {\n const r = await fetch(\n `${apiBaseUrl}/api/${data.cart}/${data.entity.toLowerCase()}/${encodeURIComponent(data.rowId)}`,\n {\n method: \"PUT\",\n credentials: \"include\",\n headers: { \"content-type\": \"application/json\" },\n body: JSON.stringify({ [data.colName]: result.value }),\n },\n );\n if (!r.ok) {\n setError((await r.text()) || `HTTP ${r.status}`);\n setSaving(false);\n return;\n }\n // Success: the update handler broadcasts FRAME_DELTA_UPD, which refreshes\n // the row in place \u2014 just close.\n onClose();\n } catch (e) {\n setError(e instanceof Error ? e.message : String(e));\n setSaving(false);\n }\n };\n\n const isBool = data.colType === TYPE_BOOL;\n const isNumeric = INT_TYPES.has(data.colType) || FLOAT_TYPES.has(data.colType);\n\n return createPortal(\n <>\n <div className=\"fixed inset-0 z-[60]\" onClick={onClose} />\n <div\n className={cn(\n \"nimbit-devtools dark fixed z-[61] rounded-md border border-border bg-popover p-3\",\n \"text-[12px] text-popover-foreground shadow-lg\",\n )}\n style={position(data.rect)}\n onClick={(e) => e.stopPropagation()}\n >\n <div className=\"mb-2 flex items-center justify-between gap-3\">\n <div className=\"min-w-0\">\n <div className=\"truncate font-medium text-foreground\">{data.colName}</div>\n <div className=\"text-[11px] text-muted-foreground\">Row {data.r + 1}</div>\n </div>\n <div className=\"flex shrink-0 items-center gap-1.5\">\n <button\n type=\"button\"\n onClick={copy}\n className={cn(\n \"rounded border border-border px-2 py-1 text-[11px] leading-none\",\n \"hover:bg-accent hover:text-accent-foreground\",\n )}\n >\n {copied ? \"Copied\" : \"Copy\"}\n </button>\n {data.editable ? (\n <button\n type=\"button\"\n onClick={save}\n disabled={saving}\n className={cn(\n \"rounded border border-border bg-primary/90 px-2 py-1 text-[11px] leading-none text-primary-foreground\",\n \"hover:bg-primary disabled:opacity-50\",\n )}\n >\n {saving ? \"Saving\u2026\" : \"Save\"}\n </button>\n ) : null}\n </div>\n </div>\n\n {data.editable ? (\n <>\n {isBool ? (\n <select\n value={draft}\n disabled={saving}\n onChange={(e) => setDraft(e.target.value)}\n className=\"w-full rounded-sm border border-border bg-background px-2 py-1 text-[12px] outline-none focus:border-accent-foreground/30\"\n >\n <option value=\"true\">true</option>\n <option value=\"false\">false</option>\n </select>\n ) : isNumeric ? (\n <input\n type=\"number\"\n value={draft}\n disabled={saving}\n autoFocus\n onChange={(e) => setDraft(e.target.value)}\n onKeyDown={(e) => {\n if (e.key === \"Enter\") void save();\n }}\n className=\"w-full rounded-sm border border-border bg-background px-2 py-1 font-mono text-[12px] outline-none focus:border-accent-foreground/30\"\n />\n ) : (\n <textarea\n value={draft}\n disabled={saving}\n autoFocus\n onChange={(e) => setDraft(e.target.value)}\n className=\"max-h-48 min-h-[3rem] w-full resize-y overflow-auto rounded-sm border border-border bg-background p-2 font-mono text-[11px] leading-5 outline-none focus:border-accent-foreground/30\"\n />\n )}\n {error ? (\n <div className=\"mt-2 break-words text-[11px] text-red-400\">{error}</div>\n ) : null}\n </>\n ) : (\n <div className=\"max-h-48 overflow-auto rounded-sm bg-muted/40 p-2 font-mono text-[11px] leading-5 whitespace-pre-wrap break-words\">\n {data.value.length > 0 ? (\n data.value\n ) : (\n <span className=\"font-sans italic text-muted-foreground\">(empty)</span>\n )}\n </div>\n )}\n </div>\n </>,\n document.body,\n );\n};\n\nexport default ValuePopover;\n"],
|
|
5
|
-
"mappings": "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",
|
|
6
|
-
"names": ["React", "jsx", "jsxs", "React"]
|
|
7
|
-
}
|