@agent-native/core 0.26.9 → 0.27.0
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/agent/run-ownership.d.ts +12 -0
- package/dist/agent/run-ownership.d.ts.map +1 -0
- package/dist/agent/run-ownership.js +39 -0
- package/dist/agent/run-ownership.js.map +1 -0
- package/dist/client/db-admin/DataGrid.d.ts +42 -0
- package/dist/client/db-admin/DataGrid.d.ts.map +1 -0
- package/dist/client/db-admin/DataGrid.js +204 -0
- package/dist/client/db-admin/DataGrid.js.map +1 -0
- package/dist/client/db-admin/DbAdminPage.d.ts +2 -0
- package/dist/client/db-admin/DbAdminPage.d.ts.map +1 -0
- package/dist/client/db-admin/DbAdminPage.js +72 -0
- package/dist/client/db-admin/DbAdminPage.js.map +1 -0
- package/dist/client/db-admin/DevDatabaseLink.d.ts +19 -0
- package/dist/client/db-admin/DevDatabaseLink.d.ts.map +1 -0
- package/dist/client/db-admin/DevDatabaseLink.js +25 -0
- package/dist/client/db-admin/DevDatabaseLink.js.map +1 -0
- package/dist/client/db-admin/EditableCell.d.ts +26 -0
- package/dist/client/db-admin/EditableCell.d.ts.map +1 -0
- package/dist/client/db-admin/EditableCell.js +150 -0
- package/dist/client/db-admin/EditableCell.js.map +1 -0
- package/dist/client/db-admin/FilterBar.d.ts +8 -0
- package/dist/client/db-admin/FilterBar.d.ts.map +1 -0
- package/dist/client/db-admin/FilterBar.js +68 -0
- package/dist/client/db-admin/FilterBar.js.map +1 -0
- package/dist/client/db-admin/ResultsGrid.d.ts +6 -0
- package/dist/client/db-admin/ResultsGrid.d.ts.map +1 -0
- package/dist/client/db-admin/ResultsGrid.js +41 -0
- package/dist/client/db-admin/ResultsGrid.js.map +1 -0
- package/dist/client/db-admin/RowSidePanel.d.ts +18 -0
- package/dist/client/db-admin/RowSidePanel.d.ts.map +1 -0
- package/dist/client/db-admin/RowSidePanel.js +104 -0
- package/dist/client/db-admin/RowSidePanel.js.map +1 -0
- package/dist/client/db-admin/SqlEditor.d.ts +8 -0
- package/dist/client/db-admin/SqlEditor.d.ts.map +1 -0
- package/dist/client/db-admin/SqlEditor.js +350 -0
- package/dist/client/db-admin/SqlEditor.js.map +1 -0
- package/dist/client/db-admin/TableBrowser.d.ts +10 -0
- package/dist/client/db-admin/TableBrowser.d.ts.map +1 -0
- package/dist/client/db-admin/TableBrowser.js +61 -0
- package/dist/client/db-admin/TableBrowser.js.map +1 -0
- package/dist/client/db-admin/TableEditor.d.ts +9 -0
- package/dist/client/db-admin/TableEditor.d.ts.map +1 -0
- package/dist/client/db-admin/TableEditor.js +254 -0
- package/dist/client/db-admin/TableEditor.js.map +1 -0
- package/dist/client/db-admin/cell-format.d.ts +55 -0
- package/dist/client/db-admin/cell-format.d.ts.map +1 -0
- package/dist/client/db-admin/cell-format.js +223 -0
- package/dist/client/db-admin/cell-format.js.map +1 -0
- package/dist/client/db-admin/changeset.d.ts +74 -0
- package/dist/client/db-admin/changeset.d.ts.map +1 -0
- package/dist/client/db-admin/changeset.js +169 -0
- package/dist/client/db-admin/changeset.js.map +1 -0
- package/dist/client/db-admin/export-utils.d.ts +15 -0
- package/dist/client/db-admin/export-utils.d.ts.map +1 -0
- package/dist/client/db-admin/export-utils.js +62 -0
- package/dist/client/db-admin/export-utils.js.map +1 -0
- package/dist/client/db-admin/index.d.ts +7 -0
- package/dist/client/db-admin/index.d.ts.map +1 -0
- package/dist/client/db-admin/index.js +8 -0
- package/dist/client/db-admin/index.js.map +1 -0
- package/dist/client/db-admin/sql-storage.d.ts +35 -0
- package/dist/client/db-admin/sql-storage.d.ts.map +1 -0
- package/dist/client/db-admin/sql-storage.js +117 -0
- package/dist/client/db-admin/sql-storage.js.map +1 -0
- package/dist/client/db-admin/storage.d.ts +24 -0
- package/dist/client/db-admin/storage.d.ts.map +1 -0
- package/dist/client/db-admin/storage.js +50 -0
- package/dist/client/db-admin/storage.js.map +1 -0
- package/dist/client/db-admin/useAgentSync.d.ts +22 -0
- package/dist/client/db-admin/useAgentSync.d.ts.map +1 -0
- package/dist/client/db-admin/useAgentSync.js +120 -0
- package/dist/client/db-admin/useAgentSync.js.map +1 -0
- package/dist/client/db-admin/useDbAdmin.d.ts +20 -0
- package/dist/client/db-admin/useDbAdmin.d.ts.map +1 -0
- package/dist/client/db-admin/useDbAdmin.js +154 -0
- package/dist/client/db-admin/useDbAdmin.js.map +1 -0
- package/dist/client/index.d.ts +1 -0
- package/dist/client/index.d.ts.map +1 -1
- package/dist/client/index.js +1 -0
- package/dist/client/index.js.map +1 -1
- package/dist/credentials/index.d.ts.map +1 -1
- package/dist/credentials/index.js +25 -5
- package/dist/credentials/index.js.map +1 -1
- package/dist/db-admin/agent-tools.d.ts +15 -0
- package/dist/db-admin/agent-tools.d.ts.map +1 -0
- package/dist/db-admin/agent-tools.js +147 -0
- package/dist/db-admin/agent-tools.js.map +1 -0
- package/dist/db-admin/operations.d.ts +17 -0
- package/dist/db-admin/operations.d.ts.map +1 -0
- package/dist/db-admin/operations.js +541 -0
- package/dist/db-admin/operations.js.map +1 -0
- package/dist/db-admin/routes.d.ts +5 -0
- package/dist/db-admin/routes.d.ts.map +1 -0
- package/dist/db-admin/routes.js +134 -0
- package/dist/db-admin/routes.js.map +1 -0
- package/dist/db-admin/types.d.ts +85 -0
- package/dist/db-admin/types.d.ts.map +1 -0
- package/dist/db-admin/types.js +9 -0
- package/dist/db-admin/types.js.map +1 -0
- package/dist/extensions/url-safety.d.ts +20 -0
- package/dist/extensions/url-safety.d.ts.map +1 -1
- package/dist/extensions/url-safety.js +43 -0
- package/dist/extensions/url-safety.js.map +1 -1
- package/dist/file-upload/actions/upload-image.d.ts.map +1 -1
- package/dist/file-upload/actions/upload-image.js +6 -1
- package/dist/file-upload/actions/upload-image.js.map +1 -1
- package/dist/integrations/adapters/email.d.ts.map +1 -1
- package/dist/integrations/adapters/email.js +112 -0
- package/dist/integrations/adapters/email.js.map +1 -1
- package/dist/integrations/types.d.ts +11 -0
- package/dist/integrations/types.d.ts.map +1 -1
- package/dist/integrations/types.js.map +1 -1
- package/dist/scripts/db/exec.d.ts.map +1 -1
- package/dist/scripts/db/exec.js +2 -1
- package/dist/scripts/db/exec.js.map +1 -1
- package/dist/scripts/db/index.d.ts.map +1 -1
- package/dist/scripts/db/index.js +1 -0
- package/dist/scripts/db/index.js.map +1 -1
- package/dist/scripts/db/migrate-encrypt-credentials.d.ts +28 -0
- package/dist/scripts/db/migrate-encrypt-credentials.d.ts.map +1 -0
- package/dist/scripts/db/migrate-encrypt-credentials.js +190 -0
- package/dist/scripts/db/migrate-encrypt-credentials.js.map +1 -0
- package/dist/scripts/db/query.d.ts.map +1 -1
- package/dist/scripts/db/query.js +2 -1
- package/dist/scripts/db/query.js.map +1 -1
- package/dist/scripts/db/safety.d.ts +1 -0
- package/dist/scripts/db/safety.d.ts.map +1 -1
- package/dist/scripts/db/safety.js +32 -0
- package/dist/scripts/db/safety.js.map +1 -1
- package/dist/scripts/db/scoping.d.ts.map +1 -1
- package/dist/scripts/db/scoping.js +11 -1
- package/dist/scripts/db/scoping.js.map +1 -1
- package/dist/secrets/crypto.d.ts +28 -0
- package/dist/secrets/crypto.d.ts.map +1 -0
- package/dist/secrets/crypto.js +81 -0
- package/dist/secrets/crypto.js.map +1 -0
- package/dist/secrets/storage.d.ts.map +1 -1
- package/dist/secrets/storage.js +3 -61
- package/dist/secrets/storage.js.map +1 -1
- package/dist/server/action-discovery.d.ts.map +1 -1
- package/dist/server/action-discovery.js +5 -2
- package/dist/server/action-discovery.js.map +1 -1
- package/dist/server/action-routes.d.ts.map +1 -1
- package/dist/server/action-routes.js +24 -7
- package/dist/server/action-routes.js.map +1 -1
- package/dist/server/agent-chat-plugin.d.ts.map +1 -1
- package/dist/server/agent-chat-plugin.js +39 -0
- package/dist/server/agent-chat-plugin.js.map +1 -1
- package/dist/server/auth.d.ts +1 -1
- package/dist/server/auth.d.ts.map +1 -1
- package/dist/server/auth.js.map +1 -1
- package/dist/server/better-auth-instance.js +3 -3
- package/dist/server/better-auth-instance.js.map +1 -1
- package/dist/server/core-routes-plugin.d.ts.map +1 -1
- package/dist/server/core-routes-plugin.js +5 -0
- package/dist/server/core-routes-plugin.js.map +1 -1
- package/dist/server/csrf.d.ts.map +1 -1
- package/dist/server/csrf.js +9 -1
- package/dist/server/csrf.js.map +1 -1
- package/dist/server/design-token-utils.d.ts +8 -1
- package/dist/server/design-token-utils.d.ts.map +1 -1
- package/dist/server/design-token-utils.js +12 -4
- package/dist/server/design-token-utils.js.map +1 -1
- package/dist/templates/default/AGENTS.md +4 -4
- package/dist/templates/default/app/routes/database.tsx +13 -0
- package/dist/templates/workspace-core/.agents/skills/authentication/SKILL.md +9 -2
- package/dist/templates/workspace-core/.agents/skills/sharing/SKILL.md +7 -1
- package/dist/vite/client.d.ts.map +1 -1
- package/dist/vite/client.js +4 -0
- package/dist/vite/client.js.map +1 -1
- package/docs/content/a2a-protocol.md +2 -2
- package/docs/content/actions.md +2 -54
- package/docs/content/agent-mentions.md +1 -1
- package/docs/content/agent-teams.md +1 -1
- package/docs/content/authentication.md +2 -2
- package/docs/content/cli-adapters.md +33 -17
- package/docs/content/client.md +11 -20
- package/docs/content/code-agents-ui.md +19 -6
- package/docs/content/context-awareness.md +36 -20
- package/docs/content/database.md +3 -3
- package/docs/content/deployment.md +8 -8
- package/docs/content/dispatch.md +1 -1
- package/docs/content/external-agents.md +5 -1
- package/docs/content/faq.md +1 -0
- package/docs/content/frames.md +110 -30
- package/docs/content/getting-started.md +15 -14
- package/docs/content/mcp-clients.md +1 -1
- package/docs/content/mcp-protocol.md +11 -88
- package/docs/content/messaging.md +1 -1
- package/docs/content/migration-workbench.md +13 -87
- package/docs/content/multi-app-workspace.md +2 -38
- package/docs/content/multi-tenancy.md +3 -26
- package/docs/content/onboarding.md +10 -3
- package/docs/content/recurring-jobs.md +2 -2
- package/docs/content/security.md +33 -1
- package/docs/content/server.md +1 -1
- package/docs/content/template-assets.md +9 -9
- package/docs/content/template-brain.md +114 -388
- package/docs/content/template-clips.md +42 -2
- package/docs/content/template-content.md +1 -1
- package/docs/content/template-design.md +27 -0
- package/docs/content/template-dispatch.md +3 -3
- package/docs/content/template-forms.md +6 -6
- package/docs/content/template-starter.md +2 -2
- package/docs/content/using-your-agent.md +56 -0
- package/docs/content/workspace-management.md +6 -6
- package/docs/content/workspace.md +19 -0
- package/package.json +10 -3
- package/src/templates/default/AGENTS.md +4 -4
- package/src/templates/default/app/routes/database.tsx +13 -0
- package/src/templates/workspace-core/.agents/skills/authentication/SKILL.md +9 -2
- package/src/templates/workspace-core/.agents/skills/sharing/SKILL.md +7 -1
|
@@ -0,0 +1,254 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
+
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
|
3
|
+
import { createPortal } from "react-dom";
|
|
4
|
+
import { IconPlus, IconFilter, IconRefresh, IconTrash, IconAlertTriangle, IconCode, IconArrowBackUp, IconDeviceFloppy, IconChevronLeft, IconChevronRight, IconChevronsLeft, IconChevronsRight, IconLoader2, IconCheck, IconKeyOff, } from "@tabler/icons-react";
|
|
5
|
+
import { cn } from "../utils.js";
|
|
6
|
+
import { Popover, PopoverContent, PopoverTrigger, } from "../components/ui/popover.js";
|
|
7
|
+
import { useTableSchema, useTableRows, mutateTable } from "./useDbAdmin.js";
|
|
8
|
+
import { loadGridState, saveGridState } from "./storage.js";
|
|
9
|
+
import { useChangeset, pkStringFor } from "./changeset.js";
|
|
10
|
+
import { DataGrid } from "./DataGrid.js";
|
|
11
|
+
import { FilterBar } from "./FilterBar.js";
|
|
12
|
+
import { RowSidePanel } from "./RowSidePanel.js";
|
|
13
|
+
const DEFAULT_PAGE_SIZE = 100;
|
|
14
|
+
const PAGE_SIZES = [25, 50, 100, 250, 500];
|
|
15
|
+
export function TableEditor({ table, dialect, initialFilters, onNavigateToRow, }) {
|
|
16
|
+
// ─── Persisted view state ──────────────────────────────────────────────
|
|
17
|
+
const persisted = useMemo(() => loadGridState(table), [table]);
|
|
18
|
+
const [page, setPage] = useState(1);
|
|
19
|
+
const [pageSize, setPageSize] = useState(persisted.pageSize ?? DEFAULT_PAGE_SIZE);
|
|
20
|
+
const [sort, setSort] = useState(persisted.sort ?? []);
|
|
21
|
+
const [filters, setFilters] = useState(initialFilters ?? persisted.filters ?? []);
|
|
22
|
+
const [columnWidths, setColumnWidths] = useState(persisted.columnWidths ?? {});
|
|
23
|
+
const [selectedPks, setSelectedPks] = useState(new Set());
|
|
24
|
+
const [active, setActive] = useState(null);
|
|
25
|
+
// Reset transient state when switching tables.
|
|
26
|
+
useEffect(() => {
|
|
27
|
+
const p = loadGridState(table);
|
|
28
|
+
setPage(1);
|
|
29
|
+
setPageSize(p.pageSize ?? DEFAULT_PAGE_SIZE);
|
|
30
|
+
setSort(p.sort ?? []);
|
|
31
|
+
setFilters(initialFilters ?? p.filters ?? []);
|
|
32
|
+
setColumnWidths(p.columnWidths ?? {});
|
|
33
|
+
setSelectedPks(new Set());
|
|
34
|
+
setActive(null);
|
|
35
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
36
|
+
}, [table]);
|
|
37
|
+
// Persist view state.
|
|
38
|
+
useEffect(() => {
|
|
39
|
+
saveGridState(table, { columnWidths, sort, filters, pageSize });
|
|
40
|
+
}, [table, columnWidths, sort, filters, pageSize]);
|
|
41
|
+
// ─── Data ────────────────────────────────────────────────────────────────
|
|
42
|
+
const schemaState = useTableSchema(table);
|
|
43
|
+
const rowsReq = useMemo(() => ({ page, pageSize, sort, filters }), [page, pageSize, sort, filters]);
|
|
44
|
+
const rowsState = useTableRows(table, rowsReq);
|
|
45
|
+
const schema = schemaState.data;
|
|
46
|
+
const changeset = useChangeset(schema);
|
|
47
|
+
// ─── Toolbar UI state ──────────────────────────────────────────────────
|
|
48
|
+
const [panel, setPanel] = useState(null);
|
|
49
|
+
const [confirmDelete, setConfirmDelete] = useState(false);
|
|
50
|
+
const [previewSql, setPreviewSql] = useState(null);
|
|
51
|
+
const [committing, setCommitting] = useState(false);
|
|
52
|
+
const [commitError, setCommitError] = useState(null);
|
|
53
|
+
const [toast, setToast] = useState(null);
|
|
54
|
+
const showToast = useCallback((msg) => {
|
|
55
|
+
setToast(msg);
|
|
56
|
+
window.setTimeout(() => setToast(null), 2500);
|
|
57
|
+
}, []);
|
|
58
|
+
// ─── Original rows map (pk → fetched row) ──────────────────────────────
|
|
59
|
+
const originalRows = useMemo(() => {
|
|
60
|
+
const map = new Map();
|
|
61
|
+
for (const row of rowsState.data?.rows ?? []) {
|
|
62
|
+
map.set(pkStringFor(schema, row), row);
|
|
63
|
+
}
|
|
64
|
+
return map;
|
|
65
|
+
}, [rowsState.data, schema]);
|
|
66
|
+
// ─── Build grid rows with staged edits applied ──────────────────────────
|
|
67
|
+
const gridRows = useMemo(() => {
|
|
68
|
+
const out = [];
|
|
69
|
+
// New rows first (top of grid, like Supabase).
|
|
70
|
+
for (const nr of changeset.newRows) {
|
|
71
|
+
out.push({
|
|
72
|
+
pk: `new:${nr._localId}`,
|
|
73
|
+
localId: nr._localId,
|
|
74
|
+
isNew: true,
|
|
75
|
+
values: { ...nr.values },
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
for (const row of rowsState.data?.rows ?? []) {
|
|
79
|
+
const pk = pkStringFor(schema, row);
|
|
80
|
+
const staged = changeset.edits.get(pk);
|
|
81
|
+
out.push({
|
|
82
|
+
pk,
|
|
83
|
+
values: staged ? { ...row, ...staged } : row,
|
|
84
|
+
isDeleted: changeset.deletedKeys.has(pk),
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
return out;
|
|
88
|
+
}, [
|
|
89
|
+
rowsState.data,
|
|
90
|
+
schema,
|
|
91
|
+
changeset.newRows,
|
|
92
|
+
changeset.edits,
|
|
93
|
+
changeset.deletedKeys,
|
|
94
|
+
]);
|
|
95
|
+
// ─── Cell commit routing (existing rows vs new rows) ────────────────────
|
|
96
|
+
const onCellCommit = useCallback((row, col, value) => {
|
|
97
|
+
if (row.isNew && row.localId) {
|
|
98
|
+
changeset.setNewRowCell(row.localId, col, value);
|
|
99
|
+
}
|
|
100
|
+
else {
|
|
101
|
+
changeset.setCell(row.pk, col, value);
|
|
102
|
+
}
|
|
103
|
+
}, [changeset]);
|
|
104
|
+
const isCellDirty = useCallback((row, col) => {
|
|
105
|
+
if (row.isNew)
|
|
106
|
+
return true;
|
|
107
|
+
return changeset.isCellDirty(row.pk, col);
|
|
108
|
+
}, [changeset]);
|
|
109
|
+
const onToggleDelete = useCallback((row) => {
|
|
110
|
+
if (row.isNew && row.localId) {
|
|
111
|
+
changeset.removeNewRow(row.localId);
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
changeset.deleteRows([row.pk]);
|
|
115
|
+
}, [changeset]);
|
|
116
|
+
// ─── FK navigation ───────────────────────────────────────────────────────
|
|
117
|
+
const onFkNavigate = useCallback((fk, value) => {
|
|
118
|
+
onNavigateToRow(fk.refTable, [{ column: fk.refColumn, op: "eq", value }]);
|
|
119
|
+
}, [onNavigateToRow]);
|
|
120
|
+
// ─── Commit / preview ──────────────────────────────────────────────────
|
|
121
|
+
const runCommit = useCallback(async (dryRun) => {
|
|
122
|
+
const mutation = changeset.buildMutation(originalRows, dryRun);
|
|
123
|
+
if (!mutation.inserts && !mutation.updates && !mutation.deletes) {
|
|
124
|
+
return null;
|
|
125
|
+
}
|
|
126
|
+
return mutateTable(table, mutation);
|
|
127
|
+
}, [changeset, originalRows, table]);
|
|
128
|
+
const handlePreview = useCallback(async () => {
|
|
129
|
+
setCommitError(null);
|
|
130
|
+
try {
|
|
131
|
+
const res = await runCommit(true);
|
|
132
|
+
setPreviewSql(res ? res.sql : []);
|
|
133
|
+
}
|
|
134
|
+
catch (err) {
|
|
135
|
+
setCommitError(err instanceof Error ? err.message : String(err));
|
|
136
|
+
}
|
|
137
|
+
}, [runCommit]);
|
|
138
|
+
const handleCommit = useCallback(async () => {
|
|
139
|
+
const hasDeletes = changeset.deletedKeys.size > 0;
|
|
140
|
+
if (hasDeletes && !confirmDelete) {
|
|
141
|
+
setConfirmDelete(true);
|
|
142
|
+
return;
|
|
143
|
+
}
|
|
144
|
+
setCommitting(true);
|
|
145
|
+
setCommitError(null);
|
|
146
|
+
try {
|
|
147
|
+
const res = await runCommit(false);
|
|
148
|
+
changeset.discardAll();
|
|
149
|
+
setSelectedPks(new Set());
|
|
150
|
+
setConfirmDelete(false);
|
|
151
|
+
rowsState.refetch();
|
|
152
|
+
schemaState.refetch();
|
|
153
|
+
if (res) {
|
|
154
|
+
const parts = [];
|
|
155
|
+
if (res.inserted)
|
|
156
|
+
parts.push(`${res.inserted} inserted`);
|
|
157
|
+
if (res.updated)
|
|
158
|
+
parts.push(`${res.updated} updated`);
|
|
159
|
+
if (res.deleted)
|
|
160
|
+
parts.push(`${res.deleted} deleted`);
|
|
161
|
+
showToast(parts.length ? parts.join(", ") : "No changes");
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
catch (err) {
|
|
165
|
+
setCommitError(err instanceof Error ? err.message : String(err));
|
|
166
|
+
}
|
|
167
|
+
finally {
|
|
168
|
+
setCommitting(false);
|
|
169
|
+
}
|
|
170
|
+
}, [changeset, confirmDelete, runCommit, rowsState, schemaState, showToast]);
|
|
171
|
+
// ─── Cmd/Ctrl+S to commit ──────────────────────────────────────────────
|
|
172
|
+
const commitRef = useRef(handleCommit);
|
|
173
|
+
commitRef.current = handleCommit;
|
|
174
|
+
useEffect(() => {
|
|
175
|
+
const onKey = (e) => {
|
|
176
|
+
if ((e.metaKey || e.ctrlKey) && e.key.toLowerCase() === "s") {
|
|
177
|
+
e.preventDefault();
|
|
178
|
+
if (changeset.isDirty)
|
|
179
|
+
void commitRef.current();
|
|
180
|
+
}
|
|
181
|
+
};
|
|
182
|
+
window.addEventListener("keydown", onKey);
|
|
183
|
+
return () => window.removeEventListener("keydown", onKey);
|
|
184
|
+
}, [changeset.isDirty]);
|
|
185
|
+
// ─── Bulk delete from selection ────────────────────────────────────────
|
|
186
|
+
const onBulkDelete = useCallback(() => {
|
|
187
|
+
const pks = [...selectedPks].filter((pk) => !pk.startsWith("new:"));
|
|
188
|
+
changeset.deleteRows(pks);
|
|
189
|
+
setSelectedPks(new Set());
|
|
190
|
+
}, [selectedPks, changeset]);
|
|
191
|
+
// ─── Render ──────────────────────────────────────────────────────────────
|
|
192
|
+
const total = rowsState.data?.total ?? schema?.rowCount ?? 0;
|
|
193
|
+
const totalPages = Math.max(1, Math.ceil(total / pageSize));
|
|
194
|
+
if (schemaState.error) {
|
|
195
|
+
return (_jsxs("div", { className: "flex h-full items-center justify-center p-8 text-sm text-destructive", children: [_jsx(IconAlertTriangle, { className: "mr-2 h-4 w-4" }), schemaState.error.message] }));
|
|
196
|
+
}
|
|
197
|
+
if (!schema) {
|
|
198
|
+
return (_jsxs("div", { className: "flex h-full items-center justify-center p-8 text-sm text-muted-foreground", children: [_jsx(IconLoader2, { className: "mr-2 h-4 w-4 animate-spin" }), "Loading ", table, "\u2026"] }));
|
|
199
|
+
}
|
|
200
|
+
const noPk = schema.primaryKey.length === 0;
|
|
201
|
+
return (_jsxs("div", { className: "flex h-full min-h-0 flex-col", children: [_jsxs("div", { className: "flex flex-col gap-2 border-b border-border px-3 py-2", children: [_jsxs("div", { className: "flex flex-wrap items-center gap-2", children: [_jsxs("div", { className: "flex items-baseline gap-2", children: [_jsx("span", { className: "text-sm font-semibold text-foreground", children: table }), _jsxs("span", { className: "text-xs text-muted-foreground", children: [total.toLocaleString(), " rows"] }), schema.type === "view" && (_jsx("span", { className: "rounded bg-muted px-1.5 py-0.5 text-[10px] text-muted-foreground", children: "view" }))] }), _jsxs("div", { className: "ml-auto flex items-center gap-1.5", children: [_jsx(ToolbarButton, { icon: _jsx(IconRefresh, { className: "h-3.5 w-3.5" }), label: "Refresh", onClick: () => {
|
|
202
|
+
rowsState.refetch();
|
|
203
|
+
schemaState.refetch();
|
|
204
|
+
} }), !noPk && schema.type === "table" && (_jsx(ToolbarButton, { icon: _jsx(IconPlus, { className: "h-3.5 w-3.5" }), label: "Insert", onClick: () => setPanel({ mode: "insert" }), primary: true }))] })] }), _jsxs("div", { className: "flex items-center gap-2", children: [_jsx(IconFilter, { className: "h-3.5 w-3.5 shrink-0 text-muted-foreground" }), _jsx(FilterBar, { columns: schema.columns, filters: filters, onChange: (f) => {
|
|
205
|
+
setFilters(f);
|
|
206
|
+
setPage(1);
|
|
207
|
+
} })] }), noPk && schema.type === "table" && (_jsxs("div", { className: "flex items-center gap-1.5 rounded-md bg-amber-500/10 px-2 py-1 text-[11px] text-amber-600 dark:text-amber-400", children: [_jsx(IconKeyOff, { className: "h-3.5 w-3.5" }), "This table has no primary key \u2014 editing and row deletion are disabled."] })), (selectedPks.size > 0 || changeset.isDirty) && (_jsxs("div", { className: "flex flex-wrap items-center gap-2", children: [selectedPks.size > 0 && (_jsxs(_Fragment, { children: [_jsxs("span", { className: "text-xs text-muted-foreground", children: [selectedPks.size, " selected"] }), !noPk && (_jsxs("button", { type: "button", onClick: onBulkDelete, className: "inline-flex items-center gap-1 rounded-md border border-border px-2 py-1 text-xs text-destructive hover:bg-destructive/10", children: [_jsx(IconTrash, { className: "h-3.5 w-3.5" }), "Delete selected"] })), _jsx("span", { className: "h-4 w-px bg-border" })] })), changeset.isDirty && (_jsxs(_Fragment, { children: [_jsxs("span", { className: "inline-flex items-center gap-1 rounded-md bg-amber-500/15 px-2 py-1 text-xs font-medium text-amber-600 dark:text-amber-400", children: [changeset.pendingCount, " pending change", changeset.pendingCount === 1 ? "" : "s"] }), _jsxs("button", { type: "button", onClick: changeset.discardAll, className: "inline-flex items-center gap-1 rounded-md border border-border px-2 py-1 text-xs text-muted-foreground hover:text-foreground", children: [_jsx(IconArrowBackUp, { className: "h-3.5 w-3.5" }), "Discard"] }), _jsx(PreviewSqlButton, { onPreview: handlePreview, sql: previewSql, onClear: () => setPreviewSql(null), error: commitError }), _jsxs("button", { type: "button", onClick: () => void handleCommit(), disabled: committing, className: "inline-flex items-center gap-1 rounded-md bg-primary px-2.5 py-1 text-xs font-medium text-primary-foreground hover:bg-primary/90 disabled:opacity-60", children: [committing ? (_jsx(IconLoader2, { className: "h-3.5 w-3.5 animate-spin" })) : (_jsx(IconDeviceFloppy, { className: "h-3.5 w-3.5" })), "Commit"] })] }))] })), commitError && !previewSql && (_jsxs("div", { className: "flex items-center gap-1.5 rounded-md bg-destructive/10 px-2 py-1 text-[11px] text-destructive", children: [_jsx(IconAlertTriangle, { className: "h-3.5 w-3.5" }), commitError] }))] }), _jsx(DataGrid, { schema: schema, rows: gridRows, isLoading: rowsState.isLoading, pageSize: pageSize, sort: sort, onSortChange: (s) => {
|
|
208
|
+
setSort(s);
|
|
209
|
+
setPage(1);
|
|
210
|
+
}, selectedPks: selectedPks, onSelectionChange: setSelectedPks, columnWidths: columnWidths, onColumnWidthsChange: setColumnWidths, active: active, onActiveChange: setActive, editable: !noPk && schema.type === "table", onCellCommit: onCellCommit, isCellDirty: isCellDirty, onToggleDelete: onToggleDelete, onNavigateToRow: onFkNavigate }), _jsxs("div", { className: "flex items-center justify-between border-t border-border px-3 py-1.5 text-xs text-muted-foreground", children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx("span", { children: "Rows per page" }), _jsx("select", { value: pageSize, onChange: (e) => {
|
|
211
|
+
setPageSize(Number(e.target.value));
|
|
212
|
+
setPage(1);
|
|
213
|
+
}, className: "h-6 rounded border border-border bg-background px-1 text-xs outline-none focus:ring-1 focus:ring-ring", children: PAGE_SIZES.map((s) => (_jsx("option", { value: s, children: s }, s))) })] }), _jsxs("div", { className: "flex items-center gap-1", children: [_jsxs("span", { className: "mr-2", children: ["Page ", page, " of ", totalPages] }), _jsx(PagerButton, { disabled: page <= 1, onClick: () => setPage(1), icon: _jsx(IconChevronsLeft, { className: "h-3.5 w-3.5" }) }), _jsx(PagerButton, { disabled: page <= 1, onClick: () => setPage((p) => Math.max(1, p - 1)), icon: _jsx(IconChevronLeft, { className: "h-3.5 w-3.5" }) }), _jsx(PagerButton, { disabled: page >= totalPages, onClick: () => setPage((p) => Math.min(totalPages, p + 1)), icon: _jsx(IconChevronRight, { className: "h-3.5 w-3.5" }) }), _jsx(PagerButton, { disabled: page >= totalPages, onClick: () => setPage(totalPages), icon: _jsx(IconChevronsRight, { className: "h-3.5 w-3.5" }) })] })] }), panel && (_jsx(RowSidePanel, { schema: schema, mode: panel.mode, row: panel.pk ? originalRows.get(panel.pk) : undefined, staged: panel.pk ? changeset.edits.get(panel.pk) : undefined, onClose: () => setPanel(null), onSave: (values) => {
|
|
214
|
+
if (panel.mode === "insert") {
|
|
215
|
+
changeset.addRow(values);
|
|
216
|
+
}
|
|
217
|
+
else if (panel.pk) {
|
|
218
|
+
changeset.setCells(panel.pk, values);
|
|
219
|
+
}
|
|
220
|
+
} })), confirmDelete && (_jsx(ConfirmModal, { title: "Commit deletions?", body: `${changeset.deletedKeys.size} row${changeset.deletedKeys.size === 1 ? "" : "s"} will be permanently deleted along with any other pending changes. This cannot be undone.`, confirmLabel: "Delete and commit", destructive: true, onCancel: () => setConfirmDelete(false), onConfirm: () => void handleCommit() })), toast &&
|
|
221
|
+
typeof document !== "undefined" &&
|
|
222
|
+
createPortal(_jsxs("div", { className: "fixed bottom-4 right-4 z-[500] flex items-center gap-2 rounded-md border border-border bg-card px-3 py-2 text-xs text-foreground shadow-lg", children: [_jsx(IconCheck, { className: "h-4 w-4 text-emerald-500" }), toast] }), document.body)] }));
|
|
223
|
+
}
|
|
224
|
+
// ─── Small UI atoms ────────────────────────────────────────────────────────
|
|
225
|
+
function ToolbarButton({ icon, label, onClick, primary, }) {
|
|
226
|
+
return (_jsxs("button", { type: "button", onClick: onClick, className: cn("inline-flex items-center gap-1 rounded-md px-2 py-1 text-xs", primary
|
|
227
|
+
? "bg-primary text-primary-foreground hover:bg-primary/90"
|
|
228
|
+
: "border border-border text-muted-foreground hover:text-foreground"), children: [icon, label] }));
|
|
229
|
+
}
|
|
230
|
+
function PagerButton({ disabled, onClick, icon, }) {
|
|
231
|
+
return (_jsx("button", { type: "button", disabled: disabled, onClick: onClick, className: "rounded border border-border p-1 text-muted-foreground hover:text-foreground disabled:opacity-40", children: icon }));
|
|
232
|
+
}
|
|
233
|
+
function PreviewSqlButton({ onPreview, sql, onClear, error, }) {
|
|
234
|
+
return (_jsxs(Popover, { open: sql !== null, onOpenChange: (o) => {
|
|
235
|
+
if (!o)
|
|
236
|
+
onClear();
|
|
237
|
+
}, children: [_jsx(PopoverTrigger, { asChild: true, children: _jsxs("button", { type: "button", onClick: onPreview, className: "inline-flex items-center gap-1 rounded-md border border-border px-2 py-1 text-xs text-muted-foreground hover:text-foreground", children: [_jsx(IconCode, { className: "h-3.5 w-3.5" }), "Preview SQL"] }) }), _jsxs(PopoverContent, { align: "end", className: "w-[34rem] p-0", children: [_jsx("div", { className: "border-b border-border px-3 py-2 text-xs font-medium text-foreground", children: "Preview SQL" }), _jsx("div", { className: "max-h-80 overflow-auto p-3", children: error ? (_jsx("div", { className: "text-xs text-destructive", children: error })) : sql && sql.length > 0 ? (_jsx("pre", { className: "whitespace-pre-wrap break-all font-mono text-[11px] leading-relaxed text-foreground", children: sql.join(";\n") })) : (_jsx("div", { className: "text-xs text-muted-foreground", children: "No statements to run." })) })] })] }));
|
|
238
|
+
}
|
|
239
|
+
function ConfirmModal({ title, body, confirmLabel, destructive, onCancel, onConfirm, }) {
|
|
240
|
+
useEffect(() => {
|
|
241
|
+
const onKey = (e) => {
|
|
242
|
+
if (e.key === "Escape")
|
|
243
|
+
onCancel();
|
|
244
|
+
};
|
|
245
|
+
window.addEventListener("keydown", onKey);
|
|
246
|
+
return () => window.removeEventListener("keydown", onKey);
|
|
247
|
+
}, [onCancel]);
|
|
248
|
+
if (typeof document === "undefined")
|
|
249
|
+
return null;
|
|
250
|
+
return createPortal(_jsxs("div", { className: "fixed inset-0 z-[450] flex items-center justify-center", children: [_jsx("div", { className: "absolute inset-0 bg-black/40", onClick: onCancel }), _jsxs("div", { className: "relative w-full max-w-sm rounded-lg border border-border bg-card p-4 shadow-xl", children: [_jsxs("div", { className: "mb-1 flex items-center gap-2", children: [destructive && (_jsx(IconAlertTriangle, { className: "h-4 w-4 text-destructive" })), _jsx("h2", { className: "text-sm font-semibold text-foreground", children: title })] }), _jsx("p", { className: "mb-4 text-xs text-muted-foreground", children: body }), _jsxs("div", { className: "flex justify-end gap-2", children: [_jsx("button", { type: "button", onClick: onCancel, className: "rounded-md px-3 py-1.5 text-xs text-muted-foreground hover:bg-muted hover:text-foreground", children: "Cancel" }), _jsx("button", { type: "button", onClick: onConfirm, className: cn("rounded-md px-3 py-1.5 text-xs font-medium text-primary-foreground", destructive
|
|
251
|
+
? "bg-destructive hover:bg-destructive/90"
|
|
252
|
+
: "bg-primary hover:bg-primary/90"), children: confirmLabel })] })] })] }), document.body);
|
|
253
|
+
}
|
|
254
|
+
//# sourceMappingURL=TableEditor.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"TableEditor.js","sourceRoot":"","sources":["../../../src/client/db-admin/TableEditor.tsx"],"names":[],"mappings":"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","sourcesContent":["import { useCallback, useEffect, useMemo, useRef, useState } from \"react\";\nimport { createPortal } from \"react-dom\";\nimport {\n IconPlus,\n IconFilter,\n IconRefresh,\n IconTrash,\n IconAlertTriangle,\n IconCode,\n IconArrowBackUp,\n IconDeviceFloppy,\n IconChevronLeft,\n IconChevronRight,\n IconChevronsLeft,\n IconChevronsRight,\n IconLoader2,\n IconCheck,\n IconKeyOff,\n} from \"@tabler/icons-react\";\nimport { cn } from \"../utils.js\";\nimport {\n Popover,\n PopoverContent,\n PopoverTrigger,\n} from \"../components/ui/popover.js\";\nimport { useTableSchema, useTableRows, mutateTable } from \"./useDbAdmin.js\";\nimport { loadGridState, saveGridState } from \"./storage.js\";\nimport { useChangeset, pkStringFor } from \"./changeset.js\";\nimport { DataGrid, type GridRow, type ActiveCell } from \"./DataGrid.js\";\nimport { FilterBar } from \"./FilterBar.js\";\nimport { RowSidePanel, type RowSidePanelMode } from \"./RowSidePanel.js\";\nimport type {\n DbAdminDialect,\n DbAdminFilter,\n DbAdminForeignKey,\n DbAdminSort,\n DbAdminMutationResult,\n} from \"../../db-admin/types.js\";\n\nexport interface TableEditorProps {\n table: string;\n dialect: DbAdminDialect;\n initialFilters?: DbAdminFilter[];\n onNavigateToRow: (table: string, filters: DbAdminFilter[]) => void;\n}\n\nconst DEFAULT_PAGE_SIZE = 100;\nconst PAGE_SIZES = [25, 50, 100, 250, 500];\n\nexport function TableEditor({\n table,\n dialect,\n initialFilters,\n onNavigateToRow,\n}: TableEditorProps) {\n // ─── Persisted view state ──────────────────────────────────────────────\n const persisted = useMemo(() => loadGridState(table), [table]);\n\n const [page, setPage] = useState(1);\n const [pageSize, setPageSize] = useState(\n persisted.pageSize ?? DEFAULT_PAGE_SIZE,\n );\n const [sort, setSort] = useState<DbAdminSort[]>(persisted.sort ?? []);\n const [filters, setFilters] = useState<DbAdminFilter[]>(\n initialFilters ?? persisted.filters ?? [],\n );\n const [columnWidths, setColumnWidths] = useState<Record<string, number>>(\n persisted.columnWidths ?? {},\n );\n const [selectedPks, setSelectedPks] = useState<Set<string>>(new Set());\n const [active, setActive] = useState<ActiveCell | null>(null);\n\n // Reset transient state when switching tables.\n useEffect(() => {\n const p = loadGridState(table);\n setPage(1);\n setPageSize(p.pageSize ?? DEFAULT_PAGE_SIZE);\n setSort(p.sort ?? []);\n setFilters(initialFilters ?? p.filters ?? []);\n setColumnWidths(p.columnWidths ?? {});\n setSelectedPks(new Set());\n setActive(null);\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [table]);\n\n // Persist view state.\n useEffect(() => {\n saveGridState(table, { columnWidths, sort, filters, pageSize });\n }, [table, columnWidths, sort, filters, pageSize]);\n\n // ─── Data ────────────────────────────────────────────────────────────────\n const schemaState = useTableSchema(table);\n const rowsReq = useMemo(\n () => ({ page, pageSize, sort, filters }),\n [page, pageSize, sort, filters],\n );\n const rowsState = useTableRows(table, rowsReq);\n\n const schema = schemaState.data;\n const changeset = useChangeset(schema);\n\n // ─── Toolbar UI state ──────────────────────────────────────────────────\n const [panel, setPanel] = useState<{\n mode: RowSidePanelMode;\n pk?: string;\n } | null>(null);\n const [confirmDelete, setConfirmDelete] = useState(false);\n const [previewSql, setPreviewSql] = useState<string[] | null>(null);\n const [committing, setCommitting] = useState(false);\n const [commitError, setCommitError] = useState<string | null>(null);\n const [toast, setToast] = useState<string | null>(null);\n\n const showToast = useCallback((msg: string) => {\n setToast(msg);\n window.setTimeout(() => setToast(null), 2500);\n }, []);\n\n // ─── Original rows map (pk → fetched row) ──────────────────────────────\n const originalRows = useMemo(() => {\n const map = new Map<string, Record<string, unknown>>();\n for (const row of rowsState.data?.rows ?? []) {\n map.set(pkStringFor(schema, row), row);\n }\n return map;\n }, [rowsState.data, schema]);\n\n // ─── Build grid rows with staged edits applied ──────────────────────────\n const gridRows = useMemo<GridRow[]>(() => {\n const out: GridRow[] = [];\n // New rows first (top of grid, like Supabase).\n for (const nr of changeset.newRows) {\n out.push({\n pk: `new:${nr._localId}`,\n localId: nr._localId,\n isNew: true,\n values: { ...nr.values },\n });\n }\n for (const row of rowsState.data?.rows ?? []) {\n const pk = pkStringFor(schema, row);\n const staged = changeset.edits.get(pk);\n out.push({\n pk,\n values: staged ? { ...row, ...staged } : row,\n isDeleted: changeset.deletedKeys.has(pk),\n });\n }\n return out;\n }, [\n rowsState.data,\n schema,\n changeset.newRows,\n changeset.edits,\n changeset.deletedKeys,\n ]);\n\n // ─── Cell commit routing (existing rows vs new rows) ────────────────────\n const onCellCommit = useCallback(\n (row: GridRow, col: string, value: unknown) => {\n if (row.isNew && row.localId) {\n changeset.setNewRowCell(row.localId, col, value);\n } else {\n changeset.setCell(row.pk, col, value);\n }\n },\n [changeset],\n );\n\n const isCellDirty = useCallback(\n (row: GridRow, col: string) => {\n if (row.isNew) return true;\n return changeset.isCellDirty(row.pk, col);\n },\n [changeset],\n );\n\n const onToggleDelete = useCallback(\n (row: GridRow) => {\n if (row.isNew && row.localId) {\n changeset.removeNewRow(row.localId);\n return;\n }\n changeset.deleteRows([row.pk]);\n },\n [changeset],\n );\n\n // ─── FK navigation ───────────────────────────────────────────────────────\n const onFkNavigate = useCallback(\n (fk: DbAdminForeignKey, value: unknown) => {\n onNavigateToRow(fk.refTable, [{ column: fk.refColumn, op: \"eq\", value }]);\n },\n [onNavigateToRow],\n );\n\n // ─── Commit / preview ──────────────────────────────────────────────────\n const runCommit = useCallback(\n async (dryRun: boolean) => {\n const mutation = changeset.buildMutation(originalRows, dryRun);\n if (!mutation.inserts && !mutation.updates && !mutation.deletes) {\n return null;\n }\n return mutateTable(table, mutation);\n },\n [changeset, originalRows, table],\n );\n\n const handlePreview = useCallback(async () => {\n setCommitError(null);\n try {\n const res = await runCommit(true);\n setPreviewSql(res ? res.sql : []);\n } catch (err) {\n setCommitError(err instanceof Error ? err.message : String(err));\n }\n }, [runCommit]);\n\n const handleCommit = useCallback(async () => {\n const hasDeletes = changeset.deletedKeys.size > 0;\n if (hasDeletes && !confirmDelete) {\n setConfirmDelete(true);\n return;\n }\n setCommitting(true);\n setCommitError(null);\n try {\n const res: DbAdminMutationResult | null = await runCommit(false);\n changeset.discardAll();\n setSelectedPks(new Set());\n setConfirmDelete(false);\n rowsState.refetch();\n schemaState.refetch();\n if (res) {\n const parts: string[] = [];\n if (res.inserted) parts.push(`${res.inserted} inserted`);\n if (res.updated) parts.push(`${res.updated} updated`);\n if (res.deleted) parts.push(`${res.deleted} deleted`);\n showToast(parts.length ? parts.join(\", \") : \"No changes\");\n }\n } catch (err) {\n setCommitError(err instanceof Error ? err.message : String(err));\n } finally {\n setCommitting(false);\n }\n }, [changeset, confirmDelete, runCommit, rowsState, schemaState, showToast]);\n\n // ─── Cmd/Ctrl+S to commit ──────────────────────────────────────────────\n const commitRef = useRef(handleCommit);\n commitRef.current = handleCommit;\n useEffect(() => {\n const onKey = (e: KeyboardEvent) => {\n if ((e.metaKey || e.ctrlKey) && e.key.toLowerCase() === \"s\") {\n e.preventDefault();\n if (changeset.isDirty) void commitRef.current();\n }\n };\n window.addEventListener(\"keydown\", onKey);\n return () => window.removeEventListener(\"keydown\", onKey);\n }, [changeset.isDirty]);\n\n // ─── Bulk delete from selection ────────────────────────────────────────\n const onBulkDelete = useCallback(() => {\n const pks = [...selectedPks].filter((pk) => !pk.startsWith(\"new:\"));\n changeset.deleteRows(pks);\n setSelectedPks(new Set());\n }, [selectedPks, changeset]);\n\n // ─── Render ──────────────────────────────────────────────────────────────\n const total = rowsState.data?.total ?? schema?.rowCount ?? 0;\n const totalPages = Math.max(1, Math.ceil(total / pageSize));\n\n if (schemaState.error) {\n return (\n <div className=\"flex h-full items-center justify-center p-8 text-sm text-destructive\">\n <IconAlertTriangle className=\"mr-2 h-4 w-4\" />\n {schemaState.error.message}\n </div>\n );\n }\n\n if (!schema) {\n return (\n <div className=\"flex h-full items-center justify-center p-8 text-sm text-muted-foreground\">\n <IconLoader2 className=\"mr-2 h-4 w-4 animate-spin\" />\n Loading {table}…\n </div>\n );\n }\n\n const noPk = schema.primaryKey.length === 0;\n\n return (\n <div className=\"flex h-full min-h-0 flex-col\">\n {/* Toolbar */}\n <div className=\"flex flex-col gap-2 border-b border-border px-3 py-2\">\n <div className=\"flex flex-wrap items-center gap-2\">\n <div className=\"flex items-baseline gap-2\">\n <span className=\"text-sm font-semibold text-foreground\">\n {table}\n </span>\n <span className=\"text-xs text-muted-foreground\">\n {total.toLocaleString()} rows\n </span>\n {schema.type === \"view\" && (\n <span className=\"rounded bg-muted px-1.5 py-0.5 text-[10px] text-muted-foreground\">\n view\n </span>\n )}\n </div>\n\n <div className=\"ml-auto flex items-center gap-1.5\">\n <ToolbarButton\n icon={<IconRefresh className=\"h-3.5 w-3.5\" />}\n label=\"Refresh\"\n onClick={() => {\n rowsState.refetch();\n schemaState.refetch();\n }}\n />\n {!noPk && schema.type === \"table\" && (\n <ToolbarButton\n icon={<IconPlus className=\"h-3.5 w-3.5\" />}\n label=\"Insert\"\n onClick={() => setPanel({ mode: \"insert\" })}\n primary\n />\n )}\n </div>\n </div>\n\n {/* Filters */}\n <div className=\"flex items-center gap-2\">\n <IconFilter className=\"h-3.5 w-3.5 shrink-0 text-muted-foreground\" />\n <FilterBar\n columns={schema.columns}\n filters={filters}\n onChange={(f) => {\n setFilters(f);\n setPage(1);\n }}\n />\n </div>\n\n {/* No-PK warning */}\n {noPk && schema.type === \"table\" && (\n <div className=\"flex items-center gap-1.5 rounded-md bg-amber-500/10 px-2 py-1 text-[11px] text-amber-600 dark:text-amber-400\">\n <IconKeyOff className=\"h-3.5 w-3.5\" />\n This table has no primary key — editing and row deletion are\n disabled.\n </div>\n )}\n\n {/* Selection / changeset bar */}\n {(selectedPks.size > 0 || changeset.isDirty) && (\n <div className=\"flex flex-wrap items-center gap-2\">\n {selectedPks.size > 0 && (\n <>\n <span className=\"text-xs text-muted-foreground\">\n {selectedPks.size} selected\n </span>\n {!noPk && (\n <button\n type=\"button\"\n onClick={onBulkDelete}\n className=\"inline-flex items-center gap-1 rounded-md border border-border px-2 py-1 text-xs text-destructive hover:bg-destructive/10\"\n >\n <IconTrash className=\"h-3.5 w-3.5\" />\n Delete selected\n </button>\n )}\n <span className=\"h-4 w-px bg-border\" />\n </>\n )}\n\n {changeset.isDirty && (\n <>\n <span className=\"inline-flex items-center gap-1 rounded-md bg-amber-500/15 px-2 py-1 text-xs font-medium text-amber-600 dark:text-amber-400\">\n {changeset.pendingCount} pending change\n {changeset.pendingCount === 1 ? \"\" : \"s\"}\n </span>\n <button\n type=\"button\"\n onClick={changeset.discardAll}\n className=\"inline-flex items-center gap-1 rounded-md border border-border px-2 py-1 text-xs text-muted-foreground hover:text-foreground\"\n >\n <IconArrowBackUp className=\"h-3.5 w-3.5\" />\n Discard\n </button>\n <PreviewSqlButton\n onPreview={handlePreview}\n sql={previewSql}\n onClear={() => setPreviewSql(null)}\n error={commitError}\n />\n <button\n type=\"button\"\n onClick={() => void handleCommit()}\n disabled={committing}\n className=\"inline-flex items-center gap-1 rounded-md bg-primary px-2.5 py-1 text-xs font-medium text-primary-foreground hover:bg-primary/90 disabled:opacity-60\"\n >\n {committing ? (\n <IconLoader2 className=\"h-3.5 w-3.5 animate-spin\" />\n ) : (\n <IconDeviceFloppy className=\"h-3.5 w-3.5\" />\n )}\n Commit\n </button>\n </>\n )}\n </div>\n )}\n\n {commitError && !previewSql && (\n <div className=\"flex items-center gap-1.5 rounded-md bg-destructive/10 px-2 py-1 text-[11px] text-destructive\">\n <IconAlertTriangle className=\"h-3.5 w-3.5\" />\n {commitError}\n </div>\n )}\n </div>\n\n {/* Grid */}\n <DataGrid\n schema={schema}\n rows={gridRows}\n isLoading={rowsState.isLoading}\n pageSize={pageSize}\n sort={sort}\n onSortChange={(s) => {\n setSort(s);\n setPage(1);\n }}\n selectedPks={selectedPks}\n onSelectionChange={setSelectedPks}\n columnWidths={columnWidths}\n onColumnWidthsChange={setColumnWidths}\n active={active}\n onActiveChange={setActive}\n editable={!noPk && schema.type === \"table\"}\n onCellCommit={onCellCommit}\n isCellDirty={isCellDirty}\n onToggleDelete={onToggleDelete}\n onNavigateToRow={onFkNavigate}\n />\n\n {/* Pagination footer */}\n <div className=\"flex items-center justify-between border-t border-border px-3 py-1.5 text-xs text-muted-foreground\">\n <div className=\"flex items-center gap-2\">\n <span>Rows per page</span>\n <select\n value={pageSize}\n onChange={(e) => {\n setPageSize(Number(e.target.value));\n setPage(1);\n }}\n className=\"h-6 rounded border border-border bg-background px-1 text-xs outline-none focus:ring-1 focus:ring-ring\"\n >\n {PAGE_SIZES.map((s) => (\n <option key={s} value={s}>\n {s}\n </option>\n ))}\n </select>\n </div>\n\n <div className=\"flex items-center gap-1\">\n <span className=\"mr-2\">\n Page {page} of {totalPages}\n </span>\n <PagerButton\n disabled={page <= 1}\n onClick={() => setPage(1)}\n icon={<IconChevronsLeft className=\"h-3.5 w-3.5\" />}\n />\n <PagerButton\n disabled={page <= 1}\n onClick={() => setPage((p) => Math.max(1, p - 1))}\n icon={<IconChevronLeft className=\"h-3.5 w-3.5\" />}\n />\n <PagerButton\n disabled={page >= totalPages}\n onClick={() => setPage((p) => Math.min(totalPages, p + 1))}\n icon={<IconChevronRight className=\"h-3.5 w-3.5\" />}\n />\n <PagerButton\n disabled={page >= totalPages}\n onClick={() => setPage(totalPages)}\n icon={<IconChevronsRight className=\"h-3.5 w-3.5\" />}\n />\n </div>\n </div>\n\n {/* Row side panel */}\n {panel && (\n <RowSidePanel\n schema={schema}\n mode={panel.mode}\n row={panel.pk ? originalRows.get(panel.pk) : undefined}\n staged={panel.pk ? changeset.edits.get(panel.pk) : undefined}\n onClose={() => setPanel(null)}\n onSave={(values) => {\n if (panel.mode === \"insert\") {\n changeset.addRow(values);\n } else if (panel.pk) {\n changeset.setCells(panel.pk, values);\n }\n }}\n />\n )}\n\n {/* Destructive commit confirmation */}\n {confirmDelete && (\n <ConfirmModal\n title=\"Commit deletions?\"\n body={`${changeset.deletedKeys.size} row${\n changeset.deletedKeys.size === 1 ? \"\" : \"s\"\n } will be permanently deleted along with any other pending changes. This cannot be undone.`}\n confirmLabel=\"Delete and commit\"\n destructive\n onCancel={() => setConfirmDelete(false)}\n onConfirm={() => void handleCommit()}\n />\n )}\n\n {/* Toast */}\n {toast &&\n typeof document !== \"undefined\" &&\n createPortal(\n <div className=\"fixed bottom-4 right-4 z-[500] flex items-center gap-2 rounded-md border border-border bg-card px-3 py-2 text-xs text-foreground shadow-lg\">\n <IconCheck className=\"h-4 w-4 text-emerald-500\" />\n {toast}\n </div>,\n document.body,\n )}\n </div>\n );\n}\n\n// ─── Small UI atoms ────────────────────────────────────────────────────────\n\nfunction ToolbarButton({\n icon,\n label,\n onClick,\n primary,\n}: {\n icon: React.ReactNode;\n label: string;\n onClick: () => void;\n primary?: boolean;\n}) {\n return (\n <button\n type=\"button\"\n onClick={onClick}\n className={cn(\n \"inline-flex items-center gap-1 rounded-md px-2 py-1 text-xs\",\n primary\n ? \"bg-primary text-primary-foreground hover:bg-primary/90\"\n : \"border border-border text-muted-foreground hover:text-foreground\",\n )}\n >\n {icon}\n {label}\n </button>\n );\n}\n\nfunction PagerButton({\n disabled,\n onClick,\n icon,\n}: {\n disabled: boolean;\n onClick: () => void;\n icon: React.ReactNode;\n}) {\n return (\n <button\n type=\"button\"\n disabled={disabled}\n onClick={onClick}\n className=\"rounded border border-border p-1 text-muted-foreground hover:text-foreground disabled:opacity-40\"\n >\n {icon}\n </button>\n );\n}\n\nfunction PreviewSqlButton({\n onPreview,\n sql,\n onClear,\n error,\n}: {\n onPreview: () => void;\n sql: string[] | null;\n onClear: () => void;\n error: string | null;\n}) {\n return (\n <Popover\n open={sql !== null}\n onOpenChange={(o) => {\n if (!o) onClear();\n }}\n >\n <PopoverTrigger asChild>\n <button\n type=\"button\"\n onClick={onPreview}\n className=\"inline-flex items-center gap-1 rounded-md border border-border px-2 py-1 text-xs text-muted-foreground hover:text-foreground\"\n >\n <IconCode className=\"h-3.5 w-3.5\" />\n Preview SQL\n </button>\n </PopoverTrigger>\n <PopoverContent align=\"end\" className=\"w-[34rem] p-0\">\n <div className=\"border-b border-border px-3 py-2 text-xs font-medium text-foreground\">\n Preview SQL\n </div>\n <div className=\"max-h-80 overflow-auto p-3\">\n {error ? (\n <div className=\"text-xs text-destructive\">{error}</div>\n ) : sql && sql.length > 0 ? (\n <pre className=\"whitespace-pre-wrap break-all font-mono text-[11px] leading-relaxed text-foreground\">\n {sql.join(\";\\n\")}\n </pre>\n ) : (\n <div className=\"text-xs text-muted-foreground\">\n No statements to run.\n </div>\n )}\n </div>\n </PopoverContent>\n </Popover>\n );\n}\n\nfunction ConfirmModal({\n title,\n body,\n confirmLabel,\n destructive,\n onCancel,\n onConfirm,\n}: {\n title: string;\n body: string;\n confirmLabel: string;\n destructive?: boolean;\n onCancel: () => void;\n onConfirm: () => void;\n}) {\n useEffect(() => {\n const onKey = (e: KeyboardEvent) => {\n if (e.key === \"Escape\") onCancel();\n };\n window.addEventListener(\"keydown\", onKey);\n return () => window.removeEventListener(\"keydown\", onKey);\n }, [onCancel]);\n\n if (typeof document === \"undefined\") return null;\n return createPortal(\n <div className=\"fixed inset-0 z-[450] flex items-center justify-center\">\n <div className=\"absolute inset-0 bg-black/40\" onClick={onCancel} />\n <div className=\"relative w-full max-w-sm rounded-lg border border-border bg-card p-4 shadow-xl\">\n <div className=\"mb-1 flex items-center gap-2\">\n {destructive && (\n <IconAlertTriangle className=\"h-4 w-4 text-destructive\" />\n )}\n <h2 className=\"text-sm font-semibold text-foreground\">{title}</h2>\n </div>\n <p className=\"mb-4 text-xs text-muted-foreground\">{body}</p>\n <div className=\"flex justify-end gap-2\">\n <button\n type=\"button\"\n onClick={onCancel}\n className=\"rounded-md px-3 py-1.5 text-xs text-muted-foreground hover:bg-muted hover:text-foreground\"\n >\n Cancel\n </button>\n <button\n type=\"button\"\n onClick={onConfirm}\n className={cn(\n \"rounded-md px-3 py-1.5 text-xs font-medium text-primary-foreground\",\n destructive\n ? \"bg-destructive hover:bg-destructive/90\"\n : \"bg-primary hover:bg-primary/90\",\n )}\n >\n {confirmLabel}\n </button>\n </div>\n </div>\n </div>,\n document.body,\n );\n}\n"]}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import type { DbAdminColumn } from "../../db-admin/types.js";
|
|
2
|
+
/**
|
|
3
|
+
* Type-aware cell formatting and parsing helpers for the DB admin grid.
|
|
4
|
+
*
|
|
5
|
+
* These are intentionally dialect-agnostic: they look at the column's `type`
|
|
6
|
+
* string and normalize it into one of a small set of editor "kinds" that drive
|
|
7
|
+
* which editor UI is rendered and how values are parsed back into the mutation
|
|
8
|
+
* payload.
|
|
9
|
+
*/
|
|
10
|
+
export type EditorKind = "text" | "number" | "boolean" | "json" | "timestamp" | "enum" | "uuid";
|
|
11
|
+
/** Sentinel meaning "store SQL NULL". */
|
|
12
|
+
export declare const NULL_VALUE: any;
|
|
13
|
+
/**
|
|
14
|
+
* Try to pull allowed enum values out of a column definition.
|
|
15
|
+
*
|
|
16
|
+
* The base contract column shape (`DbAdminColumn`) only carries a `type`
|
|
17
|
+
* string, but introspection may attach extra fields per dialect. We probe a
|
|
18
|
+
* few likely shapes and also parse a Postgres/MySQL-style inline list embedded
|
|
19
|
+
* in the type string, e.g. `enum('a','b')`.
|
|
20
|
+
*/
|
|
21
|
+
export declare function inferEnumValues(col: DbAdminColumn): string[] | null;
|
|
22
|
+
/** Infer which editor UI a column should use. */
|
|
23
|
+
export declare function inferEditorKind(col: DbAdminColumn): EditorKind;
|
|
24
|
+
/** Whether a value is SQL NULL (vs empty string, 0, false, etc). */
|
|
25
|
+
export declare function isNull(value: unknown): boolean;
|
|
26
|
+
/**
|
|
27
|
+
* Format a DB value for compact in-cell display. Returns a marker the cell uses
|
|
28
|
+
* to render NULL distinctly; for plain string consumers the text is "NULL".
|
|
29
|
+
*/
|
|
30
|
+
export declare function formatCellValue(value: unknown, kind: EditorKind): {
|
|
31
|
+
text: string;
|
|
32
|
+
isNull: boolean;
|
|
33
|
+
};
|
|
34
|
+
/** Compact single-line JSON for cell display. */
|
|
35
|
+
export declare function formatJsonCompact(value: unknown): string;
|
|
36
|
+
/** Pretty multi-line JSON for the expanded editor. */
|
|
37
|
+
export declare function formatJsonPretty(value: unknown): string;
|
|
38
|
+
/** Human-readable timestamp; tolerant of strings, numbers, and Dates. */
|
|
39
|
+
export declare function formatTimestamp(value: unknown): string;
|
|
40
|
+
/** Convert a DB value to the string an editor input should start with. */
|
|
41
|
+
export declare function valueToEditString(value: unknown, kind: EditorKind): string;
|
|
42
|
+
export declare class ParseError extends Error {
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Parse an edited string back into the JS value sent in the mutation payload.
|
|
46
|
+
*
|
|
47
|
+
* `allowEmptyString` distinguishes "" → empty string from "" → NULL. For most
|
|
48
|
+
* types empty means NULL; for text the editor decides.
|
|
49
|
+
*/
|
|
50
|
+
export declare function parseEditValue(raw: string, kind: EditorKind, opts?: {
|
|
51
|
+
allowEmptyString?: boolean;
|
|
52
|
+
}): unknown;
|
|
53
|
+
/** Cycle a tri-state boolean: null → true → false → null. */
|
|
54
|
+
export declare function cycleTriStateBoolean(value: unknown): boolean | null;
|
|
55
|
+
//# sourceMappingURL=cell-format.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cell-format.d.ts","sourceRoot":"","sources":["../../../src/client/db-admin/cell-format.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,yBAAyB,CAAC;AAE7D;;;;;;;GAOG;AAEH,MAAM,MAAM,UAAU,GAClB,MAAM,GACN,QAAQ,GACR,SAAS,GACT,MAAM,GACN,WAAW,GACX,MAAM,GACN,MAAM,CAAC;AAEX,yCAAyC;AACzC,eAAO,MAAM,UAAU,KAAO,CAAC;AAkC/B;;;;;;;GAOG;AACH,wBAAgB,eAAe,CAAC,GAAG,EAAE,aAAa,GAAG,MAAM,EAAE,GAAG,IAAI,CAkBnE;AAED,iDAAiD;AACjD,wBAAgB,eAAe,CAAC,GAAG,EAAE,aAAa,GAAG,UAAU,CAW9D;AAED,oEAAoE;AACpE,wBAAgB,MAAM,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAE9C;AAED;;;GAGG;AACH,wBAAgB,eAAe,CAC7B,KAAK,EAAE,OAAO,EACd,IAAI,EAAE,UAAU,GACf;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,OAAO,CAAA;CAAE,CAenC;AAED,iDAAiD;AACjD,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,CAaxD;AAED,sDAAsD;AACtD,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,CAcvD;AAED,yEAAyE;AACzE,wBAAgB,eAAe,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,CActD;AAED,0EAA0E;AAC1E,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,OAAO,EAAE,IAAI,EAAE,UAAU,GAAG,MAAM,CAM1E;AAED,qBAAa,UAAW,SAAQ,KAAK;CAAG;AAExC;;;;;GAKG;AACH,wBAAgB,cAAc,CAC5B,GAAG,EAAE,MAAM,EACX,IAAI,EAAE,UAAU,EAChB,IAAI,GAAE;IAAE,gBAAgB,CAAC,EAAE,OAAO,CAAA;CAAO,GACxC,OAAO,CAyCT;AAED,6DAA6D;AAC7D,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,GAAG,IAAI,CAInE"}
|
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
/** Sentinel meaning "store SQL NULL". */
|
|
2
|
+
export const NULL_VALUE = null;
|
|
3
|
+
const NUMBER_TYPES = [
|
|
4
|
+
"int",
|
|
5
|
+
"integer",
|
|
6
|
+
"smallint",
|
|
7
|
+
"bigint",
|
|
8
|
+
"serial",
|
|
9
|
+
"bigserial",
|
|
10
|
+
"smallserial",
|
|
11
|
+
"decimal",
|
|
12
|
+
"numeric",
|
|
13
|
+
"real",
|
|
14
|
+
"double",
|
|
15
|
+
"float",
|
|
16
|
+
"money",
|
|
17
|
+
];
|
|
18
|
+
const BOOLEAN_TYPES = ["bool", "boolean"];
|
|
19
|
+
const JSON_TYPES = ["json", "jsonb"];
|
|
20
|
+
const TIMESTAMP_TYPES = [
|
|
21
|
+
"timestamp",
|
|
22
|
+
"timestamptz",
|
|
23
|
+
"datetime",
|
|
24
|
+
"date",
|
|
25
|
+
"time",
|
|
26
|
+
"timetz",
|
|
27
|
+
];
|
|
28
|
+
const UUID_TYPES = ["uuid", "guid"];
|
|
29
|
+
function normalizeType(col) {
|
|
30
|
+
return (col.type || "").toString().trim().toLowerCase();
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Try to pull allowed enum values out of a column definition.
|
|
34
|
+
*
|
|
35
|
+
* The base contract column shape (`DbAdminColumn`) only carries a `type`
|
|
36
|
+
* string, but introspection may attach extra fields per dialect. We probe a
|
|
37
|
+
* few likely shapes and also parse a Postgres/MySQL-style inline list embedded
|
|
38
|
+
* in the type string, e.g. `enum('a','b')`.
|
|
39
|
+
*/
|
|
40
|
+
export function inferEnumValues(col) {
|
|
41
|
+
const anyCol = col;
|
|
42
|
+
const candidates = [anyCol.enumValues, anyCol.values, anyCol.options];
|
|
43
|
+
for (const candidate of candidates) {
|
|
44
|
+
if (Array.isArray(candidate) && candidate.length > 0) {
|
|
45
|
+
return candidate.map((v) => String(v));
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
const type = (col.type || "").toString();
|
|
49
|
+
const match = type.match(/^\s*(?:enum|set)\s*\((.+)\)\s*$/i);
|
|
50
|
+
if (match) {
|
|
51
|
+
const items = match[1]
|
|
52
|
+
.split(",")
|
|
53
|
+
.map((s) => s.trim().replace(/^['"]|['"]$/g, ""))
|
|
54
|
+
.filter((s) => s.length > 0);
|
|
55
|
+
if (items.length > 0)
|
|
56
|
+
return items;
|
|
57
|
+
}
|
|
58
|
+
return null;
|
|
59
|
+
}
|
|
60
|
+
/** Infer which editor UI a column should use. */
|
|
61
|
+
export function inferEditorKind(col) {
|
|
62
|
+
const type = normalizeType(col);
|
|
63
|
+
if (inferEnumValues(col))
|
|
64
|
+
return "enum";
|
|
65
|
+
if (UUID_TYPES.some((t) => type.includes(t)))
|
|
66
|
+
return "uuid";
|
|
67
|
+
if (BOOLEAN_TYPES.some((t) => type === t || type.includes(t)))
|
|
68
|
+
return "boolean";
|
|
69
|
+
if (JSON_TYPES.some((t) => type === t || type.includes(t)))
|
|
70
|
+
return "json";
|
|
71
|
+
if (TIMESTAMP_TYPES.some((t) => type.includes(t)))
|
|
72
|
+
return "timestamp";
|
|
73
|
+
if (NUMBER_TYPES.some((t) => type.includes(t)))
|
|
74
|
+
return "number";
|
|
75
|
+
return "text";
|
|
76
|
+
}
|
|
77
|
+
/** Whether a value is SQL NULL (vs empty string, 0, false, etc). */
|
|
78
|
+
export function isNull(value) {
|
|
79
|
+
return value === null || value === undefined;
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Format a DB value for compact in-cell display. Returns a marker the cell uses
|
|
83
|
+
* to render NULL distinctly; for plain string consumers the text is "NULL".
|
|
84
|
+
*/
|
|
85
|
+
export function formatCellValue(value, kind) {
|
|
86
|
+
if (isNull(value))
|
|
87
|
+
return { text: "NULL", isNull: true };
|
|
88
|
+
switch (kind) {
|
|
89
|
+
case "boolean":
|
|
90
|
+
return { text: value === true ? "true" : "false", isNull: false };
|
|
91
|
+
case "json":
|
|
92
|
+
return { text: formatJsonCompact(value), isNull: false };
|
|
93
|
+
case "timestamp":
|
|
94
|
+
return { text: formatTimestamp(value), isNull: false };
|
|
95
|
+
case "number":
|
|
96
|
+
return { text: String(value), isNull: false };
|
|
97
|
+
default:
|
|
98
|
+
return { text: String(value), isNull: false };
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
/** Compact single-line JSON for cell display. */
|
|
102
|
+
export function formatJsonCompact(value) {
|
|
103
|
+
if (typeof value === "string") {
|
|
104
|
+
try {
|
|
105
|
+
return JSON.stringify(JSON.parse(value));
|
|
106
|
+
}
|
|
107
|
+
catch {
|
|
108
|
+
return value;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
try {
|
|
112
|
+
return JSON.stringify(value);
|
|
113
|
+
}
|
|
114
|
+
catch {
|
|
115
|
+
return String(value);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
/** Pretty multi-line JSON for the expanded editor. */
|
|
119
|
+
export function formatJsonPretty(value) {
|
|
120
|
+
if (isNull(value))
|
|
121
|
+
return "";
|
|
122
|
+
if (typeof value === "string") {
|
|
123
|
+
try {
|
|
124
|
+
return JSON.stringify(JSON.parse(value), null, 2);
|
|
125
|
+
}
|
|
126
|
+
catch {
|
|
127
|
+
return value;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
try {
|
|
131
|
+
return JSON.stringify(value, null, 2);
|
|
132
|
+
}
|
|
133
|
+
catch {
|
|
134
|
+
return String(value);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
/** Human-readable timestamp; tolerant of strings, numbers, and Dates. */
|
|
138
|
+
export function formatTimestamp(value) {
|
|
139
|
+
if (isNull(value))
|
|
140
|
+
return "";
|
|
141
|
+
const date = value instanceof Date
|
|
142
|
+
? value
|
|
143
|
+
: typeof value === "number"
|
|
144
|
+
? new Date(value)
|
|
145
|
+
: new Date(String(value));
|
|
146
|
+
if (Number.isNaN(date.getTime()))
|
|
147
|
+
return String(value);
|
|
148
|
+
const pad = (n) => String(n).padStart(2, "0");
|
|
149
|
+
return (`${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())} ` +
|
|
150
|
+
`${pad(date.getHours())}:${pad(date.getMinutes())}:${pad(date.getSeconds())}`);
|
|
151
|
+
}
|
|
152
|
+
/** Convert a DB value to the string an editor input should start with. */
|
|
153
|
+
export function valueToEditString(value, kind) {
|
|
154
|
+
if (isNull(value))
|
|
155
|
+
return "";
|
|
156
|
+
if (kind === "json")
|
|
157
|
+
return formatJsonPretty(value);
|
|
158
|
+
if (kind === "timestamp")
|
|
159
|
+
return formatTimestamp(value);
|
|
160
|
+
if (kind === "boolean")
|
|
161
|
+
return value === true ? "true" : "false";
|
|
162
|
+
return String(value);
|
|
163
|
+
}
|
|
164
|
+
export class ParseError extends Error {
|
|
165
|
+
}
|
|
166
|
+
/**
|
|
167
|
+
* Parse an edited string back into the JS value sent in the mutation payload.
|
|
168
|
+
*
|
|
169
|
+
* `allowEmptyString` distinguishes "" → empty string from "" → NULL. For most
|
|
170
|
+
* types empty means NULL; for text the editor decides.
|
|
171
|
+
*/
|
|
172
|
+
export function parseEditValue(raw, kind, opts = {}) {
|
|
173
|
+
if (raw === "") {
|
|
174
|
+
if (kind === "text" && opts.allowEmptyString)
|
|
175
|
+
return "";
|
|
176
|
+
return NULL_VALUE;
|
|
177
|
+
}
|
|
178
|
+
const trimmed = raw.trim();
|
|
179
|
+
switch (kind) {
|
|
180
|
+
case "number": {
|
|
181
|
+
const n = Number(trimmed);
|
|
182
|
+
if (trimmed === "" || Number.isNaN(n)) {
|
|
183
|
+
throw new ParseError(`"${raw}" is not a valid number`);
|
|
184
|
+
}
|
|
185
|
+
return n;
|
|
186
|
+
}
|
|
187
|
+
case "boolean": {
|
|
188
|
+
const v = trimmed.toLowerCase();
|
|
189
|
+
if (["true", "t", "1", "yes", "y"].includes(v))
|
|
190
|
+
return true;
|
|
191
|
+
if (["false", "f", "0", "no", "n"].includes(v))
|
|
192
|
+
return false;
|
|
193
|
+
if (v === "null")
|
|
194
|
+
return NULL_VALUE;
|
|
195
|
+
throw new ParseError(`"${raw}" is not a valid boolean`);
|
|
196
|
+
}
|
|
197
|
+
case "json": {
|
|
198
|
+
// Accept ANY valid JSON value, including scalars.
|
|
199
|
+
try {
|
|
200
|
+
return JSON.parse(trimmed);
|
|
201
|
+
}
|
|
202
|
+
catch (err) {
|
|
203
|
+
throw new ParseError(`Invalid JSON: ${err instanceof Error ? err.message : String(err)}`);
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
case "timestamp":
|
|
207
|
+
case "uuid":
|
|
208
|
+
case "enum":
|
|
209
|
+
case "text":
|
|
210
|
+
default:
|
|
211
|
+
// Preserve intentional spaces for text; trim structured types.
|
|
212
|
+
return kind === "text" ? raw : trimmed;
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
/** Cycle a tri-state boolean: null → true → false → null. */
|
|
216
|
+
export function cycleTriStateBoolean(value) {
|
|
217
|
+
if (value === null || value === undefined)
|
|
218
|
+
return true;
|
|
219
|
+
if (value === true)
|
|
220
|
+
return false;
|
|
221
|
+
return null;
|
|
222
|
+
}
|
|
223
|
+
//# sourceMappingURL=cell-format.js.map
|