okengine 0.14.1 → 0.16.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/package.json +3 -4
- package/site/content/docs/elements/ai.mdx +2 -1
- package/site/content/docs/elements/channel.mdx +1 -0
- package/site/content/docs/elements/clock.mdx +3 -0
- package/site/content/docs/elements/flow.mdx +9 -0
- package/site/content/docs/elements/gate.mdx +80 -22
- package/site/content/docs/elements/signal.mdx +4 -4
- package/site/content/docs/elements/store.mdx +84 -27
- package/site/content/docs/elements/vault.mdx +4 -0
- package/site/content/docs/get-started/basic-usage.mdx +22 -13
- package/site/content/docs/get-started/index.mdx +5 -0
- package/site/content/docs/get-started/introduction.mdx +11 -3
- package/site/content/docs/get-started/meta.json +1 -1
- package/site/content/docs/get-started/project-structure.mdx +932 -0
- package/site/content/docs/reference/cli.md +13 -10
- package/site/content/docs/reference/client.mdx +28 -21
- package/site/content/docs/reference/fx.mdx +29 -18
- package/site/content/docs/reference/security.md +2 -2
- package/src/auth/api-key-sql.test.ts +47 -0
- package/src/auth/api-key-sql.ts +254 -0
- package/src/auth/api-keys.ts +273 -14
- package/src/auth/auth.test.ts +56 -0
- package/src/auth/bindings.ts +14 -5
- package/src/auth/config.ts +5 -5
- package/src/auth/cross-plane.ts +10 -3
- package/src/auth/index.ts +25 -1
- package/src/auth/schema.ts +8 -0
- package/src/cli/ai-setup/ai-setup.test.ts +62 -0
- package/src/cli/ai-setup/apply.ts +32 -1
- package/src/cli/attach-host-console.test.ts +40 -2
- package/src/cli/attach-host-console.ts +9 -0
- package/src/cli/build.test.ts +4 -2
- package/src/cli/db-seed.ts +48 -15
- package/src/cli/db.test.ts +104 -0
- package/src/cli/db.ts +122 -10
- package/src/cli/dev-controls.test.ts +20 -0
- package/src/cli/dev-controls.ts +13 -0
- package/src/cli/dev.test.ts +15 -4
- package/src/cli/dev.ts +102 -29
- package/src/cli/ensure-drizzle-config.ts +2 -1
- package/src/cli/tui/DevLive.tsx +12 -2
- package/src/cli/tui/keys.test.ts +2 -1
- package/src/cli/tui/keys.ts +3 -2
- package/src/compiler/aot.test.ts +1 -1
- package/src/compiler/effects-infer.ts +17 -2
- package/src/compiler/extract.test.ts +409 -33
- package/src/compiler/extract.ts +318 -67
- package/src/compiler/flow-path.test.ts +96 -0
- package/src/compiler/flow-path.ts +173 -0
- package/src/compiler/generate-adopt.test.ts +112 -3
- package/src/compiler/generate-adopt.ts +280 -26
- package/src/compiler/schema-from-ast.ts +609 -0
- package/src/console/server/access.test.ts +116 -8
- package/src/console/server/access.ts +70 -23
- package/src/console/server/app.ts +1 -0
- package/src/console/server/bind.ts +2 -2
- package/src/console/server/console.test.ts +3 -0
- package/src/console/server/dev-identities.test.ts +127 -0
- package/src/console/server/dev-identities.ts +229 -0
- package/src/console/server/flows-invoke.test.ts +21 -4
- package/src/console/server/flows.ts +233 -98
- package/src/console/server/gates.test.ts +24 -0
- package/src/console/server/gates.ts +8 -21
- package/src/console/server/index.ts +1 -0
- package/src/console/server/invoke-user-flow.test.ts +3 -1
- package/src/console/server/invoke-user-flow.ts +17 -2
- package/src/console/server/iso-at.test.ts +9 -0
- package/src/console/server/iso-at.ts +11 -0
- package/src/console/server/live.test.ts +26 -1
- package/src/console/server/live.ts +4 -2
- package/src/console/server/operator-db.ts +2 -0
- package/src/console/server/security-headers.ts +6 -3
- package/src/console/server/spa-proxy.test.ts +1 -0
- package/src/console/server/sql-catalog.ts +147 -7
- package/src/console/server/sql-rls.test.ts +16 -0
- package/src/console/server/state.ts +37 -55
- package/src/console/server/store.test.ts +7 -4
- package/src/console/server/store.ts +142 -58
- package/src/console/ui-next/dist/assets/access-page-CAGHrA9H.js +4 -0
- package/src/console/ui-next/dist/assets/agent-disclosure-BP0Y0Sux.js +1 -0
- package/src/console/ui-next/dist/assets/cache-glyph-B5X-NM0-.js +1 -0
- package/src/console/ui-next/dist/assets/call-pii-button-ChAUiLo9.js +1 -0
- package/src/console/ui-next/dist/assets/collapsible-DGnOM2ph.js +1 -0
- package/src/console/ui-next/dist/assets/copy-inline-button-DBHhgHKP.js +1 -0
- package/src/console/ui-next/dist/assets/detail-header-DhHM1iaZ.js +1 -0
- package/src/console/ui-next/dist/assets/dropdown-menu-_NjTEo5_.js +1 -0
- package/src/console/ui-next/dist/assets/{duration-tone-oiRxPVsZ.js → duration-tone-sC3lGABz.js} +1 -1
- package/src/console/ui-next/dist/assets/element-icons-BVXtRyd3.js +1 -0
- package/src/console/ui-next/dist/assets/explorer-empty-2uIhBu0_.js +1 -0
- package/src/console/ui-next/dist/assets/flows-page-B-OUtiAu.js +1 -0
- package/src/console/ui-next/dist/assets/{highlighted-json-CvDPvveV.js → highlighted-json-Awq7gYdu.js} +1 -1
- package/src/console/ui-next/dist/assets/http-method-_UHM2ODJ.js +1 -0
- package/src/console/ui-next/dist/assets/index-CGoZkILK.js +66 -0
- package/src/console/ui-next/dist/assets/index-Ck88Jmv8.css +2 -0
- package/src/console/ui-next/dist/assets/link-BX6Vqztd.js +1 -0
- package/src/console/ui-next/dist/assets/observability-page-BDyXalNR.js +4 -0
- package/src/console/ui-next/dist/assets/react-D8E3mtu1.js +1 -0
- package/src/console/ui-next/dist/assets/replica-lag-DKRrbvdo.js +18 -0
- package/src/console/ui-next/dist/assets/request-meta-DV0ywz7t.js +1 -0
- package/src/console/ui-next/dist/assets/shortcut-JQIZlWfm.js +1 -0
- package/src/console/ui-next/dist/assets/shortcut-keys-DKxNTe_m.js +1 -0
- package/src/console/ui-next/dist/assets/{skeleton-CL_X0GCj.js → skeleton-D-czQJT6.js} +1 -1
- package/src/console/ui-next/dist/assets/store-page-Xh8Kn3rx.js +41 -0
- package/src/console/ui-next/dist/assets/trace-detail-sheet-B09O8rA6.js +2 -0
- package/src/console/ui-next/dist/assets/tree-expand-toggle-DkOXA12R.js +54 -0
- package/src/console/ui-next/dist/assets/units-page-Dpk40kOQ.js +1 -0
- package/src/console/ui-next/dist/assets/{use-vault-list-Cl79j_ku.js → use-vault-list-uk4WVboC.js} +1 -1
- package/src/console/ui-next/dist/assets/useMutation-B8EO02Ej.js +1 -0
- package/src/console/ui-next/dist/assets/useRender-BE2A9BWC.js +1 -0
- package/src/console/ui-next/dist/assets/vault-page-B1dB9Ft0.js +2 -0
- package/src/console/ui-next/dist/index.html +12 -10
- package/src/console/ui-next/index.html +3 -3
- package/src/console/ui-next/src/client.ts +202 -7
- package/src/console/ui-next/src/components/motion/table/index.tsx +16 -0
- package/src/console/ui-next/src/components/motion/table/types.ts +4 -0
- package/src/console/ui-next/src/components/shell/app-sidebar.tsx +2 -0
- package/src/console/ui-next/src/components/shell/command-items.test.ts +1 -0
- package/src/console/ui-next/src/components/shell/command-items.ts +8 -0
- package/src/console/ui-next/src/components/shell/shell-layout.tsx +2 -1
- package/src/console/ui-next/src/components/ui/kbd.tsx +2 -2
- package/src/console/ui-next/src/components/ui/sheet-form.tsx +8 -3
- package/src/console/ui-next/src/features/access/access-page.tsx +347 -0
- package/src/console/ui-next/src/features/access/data/use-access.ts +23 -0
- package/src/console/ui-next/src/features/access/detail/access-detail.tsx +552 -0
- package/src/console/ui-next/src/features/access/explorer/access-list.tsx +125 -0
- package/src/console/ui-next/src/features/access/lib/format-when.test.ts +139 -0
- package/src/console/ui-next/src/features/access/lib/format-when.ts +296 -0
- package/src/console/ui-next/src/features/access/lib/scope-groups.test.ts +34 -0
- package/src/console/ui-next/src/features/access/lib/scope-groups.ts +40 -0
- package/src/console/ui-next/src/features/access/sheets/access-allow-fields.tsx +236 -0
- package/src/console/ui-next/src/features/access/sheets/access-create-sheet.tsx +316 -0
- package/src/console/ui-next/src/features/access/sheets/access-edit-sheet.tsx +240 -0
- package/src/console/ui-next/src/features/access/sheets/access-expiry-fields.tsx +66 -0
- package/src/console/ui-next/src/features/access/sheets/access-refresh-sheet.tsx +104 -0
- package/src/console/ui-next/src/features/access/sheets/access-scope-field.tsx +115 -0
- package/src/console/ui-next/src/features/access/sheets/access-secret-sheet.tsx +89 -0
- package/src/console/ui-next/src/features/access/sheets/access-sheet-search.tsx +49 -0
- package/src/console/ui-next/src/features/access/state/access-selection.ts +84 -0
- package/src/console/ui-next/src/features/auth/auth-redirect.test.ts +5 -0
- package/src/console/ui-next/src/features/auth/auth-redirect.ts +18 -2
- package/src/console/ui-next/src/features/flows/traces/http-method.ts +19 -0
- package/src/console/ui-next/src/features/flows/traces/trace-detail.test.ts +19 -1
- package/src/console/ui-next/src/features/flows/traces/traces-pane.tsx +10 -3
- package/src/console/ui-next/src/features/not-found/not-found-page.tsx +1 -1
- package/src/console/ui-next/src/features/store/detail/browse-section.tsx +45 -1
- package/src/console/ui-next/src/features/store/detail/resource-panel.tsx +78 -16
- package/src/console/ui-next/src/features/store/detail/store-row-detail-sheet.tsx +155 -14
- package/src/console/ui-next/src/features/store/explorer/store-tree.tsx +33 -133
- package/src/console/ui-next/src/features/store/files/file-preview.tsx +4 -1
- package/src/console/ui-next/src/features/store/grid/rls-policy-sheet.tsx +326 -165
- package/src/console/ui-next/src/features/store/grid/sql-insert-sheet.tsx +6 -0
- package/src/console/ui-next/src/features/store/grid/store-data-grid.tsx +50 -3
- package/src/console/ui-next/src/features/store/lib/files-meta.test.ts +3 -0
- package/src/console/ui-next/src/features/store/lib/files-meta.ts +12 -0
- package/src/console/ui-next/src/features/store/lib/query-defaults.test.ts +25 -0
- package/src/console/ui-next/src/features/store/lib/query-defaults.ts +85 -3
- package/src/console/ui-next/src/features/store/lib/query-gate.ts +30 -4
- package/src/console/ui-next/src/features/store/lib/rls-policy.test.ts +139 -0
- package/src/console/ui-next/src/features/store/lib/rls-policy.ts +223 -21
- package/src/console/ui-next/src/features/store/lib/schema-graph.ts +4 -0
- package/src/console/ui-next/src/features/store/lib/sql-catalog.test.ts +24 -0
- package/src/console/ui-next/src/features/store/lib/sql-catalog.ts +19 -0
- package/src/console/ui-next/src/features/store/lib/store-tree.test.ts +6 -0
- package/src/console/ui-next/src/features/store/lib/store-tree.ts +13 -0
- package/src/console/ui-next/src/features/store/query/query-console.tsx +48 -14
- package/src/console/ui-next/src/features/store/query/query-gate-menu.tsx +19 -20
- package/src/console/ui-next/src/features/store/query/query-gate-parts.tsx +2 -1
- package/src/console/ui-next/src/features/store/query/query-results.tsx +110 -83
- package/src/console/ui-next/src/features/store/schema/schema-table-node.tsx +17 -0
- package/src/console/ui-next/src/features/units/call/call-api-panel.tsx +112 -72
- package/src/console/ui-next/src/features/units/lib/contract-input.test.ts +206 -1
- package/src/console/ui-next/src/features/units/lib/contract-input.ts +55 -0
- package/src/console/ui-next/src/features/vault/lib/types.ts +1 -1
- package/src/console/ui-next/src/lib/document-meta.test.ts +1 -0
- package/src/console/ui-next/src/lib/document-meta.ts +2 -1
- package/src/console/ui-next/src/lib/last-module-search.test.ts +1 -0
- package/src/console/ui-next/src/lib/last-module-search.ts +2 -1
- package/src/console/ui-next/src/lib/shortcut.test.ts +2 -0
- package/src/console/ui-next/src/lib/shortcut.ts +3 -0
- package/src/console/ui-next/src/router.tsx +10 -1
- package/src/console/ui-next/ui-next-seed-app-schema.ts +88 -37
- package/src/console/ui-next/ui-next-seed-store.ts +43 -15
- package/src/console/xss.gate.test.ts +1 -0
- package/src/docker/compose-health.test.ts +27 -0
- package/src/docker/compose-health.ts +19 -3
- package/src/docker/docker.test.ts +3 -0
- package/src/docker/recipes/pgdog.ts +2 -0
- package/src/drivers/clock-postgres.ts +13 -9
- package/src/drivers/instances-postgres.ts +4 -6
- package/src/drivers/journal-postgres.ts +13 -9
- package/src/drivers/pg-rls.test.ts +87 -0
- package/src/drivers/pg-rls.ts +233 -7
- package/src/drivers/pglite.ts +26 -3
- package/src/drivers/postgres.test.ts +23 -0
- package/src/drivers/postgres.ts +133 -9
- package/src/drivers/types.ts +10 -0
- package/src/elements/clock/declare.ts +26 -1
- package/src/elements/gate/boot.ts +5 -5
- package/src/elements/gate/declare.ts +38 -4
- package/src/elements/gate/flatten.ts +1 -1
- package/src/elements/gate/runtime.ts +1 -1
- package/src/elements/store/cache.ts +1 -1
- package/src/elements/store/declare.ts +9 -6
- package/src/elements/store/emit-drizzle.ts +44 -8
- package/src/elements/store/resource.test.ts +1 -1
- package/src/elements/store/rls-identity.test.ts +95 -0
- package/src/elements/store/rls-identity.ts +116 -0
- package/src/elements/store/runtime.ts +18 -0
- package/src/elements/store/schema-decl.test.ts +37 -0
- package/src/elements/store/schema-decl.ts +260 -41
- package/src/elements/store/seed.ts +1 -1
- package/src/elements/store/sql-condition.test.ts +12 -0
- package/src/elements/store/sql-condition.ts +26 -2
- package/src/elements/store/sql-rls-isolation.test.ts +165 -0
- package/src/elements/store/sql-rls-stamp.test.ts +117 -0
- package/src/elements/store/sql-select.types.test.ts +71 -0
- package/src/elements/store/sql-session.ts +135 -35
- package/src/elements/store/upsert-app.test.ts +1 -1
- package/src/elements/store.ts +17 -0
- package/src/http.ts +20 -3
- package/src/index.ts +11 -1
- package/src/kernel/adopt-barrel-fresh.test.ts +1 -1
- package/src/kernel/adopt-routes.ts +30 -2
- package/src/kernel/api-key-host-persist.test.ts +143 -0
- package/src/kernel/app.ts +145 -16
- package/src/kernel/auth-resolve.ts +10 -5
- package/src/kernel/auto-cache.test.ts +8 -8
- package/src/kernel/auto-registry.test.ts +49 -9
- package/src/kernel/boot.test.ts +5 -5
- package/src/kernel/boot.ts +2 -2
- package/src/kernel/effects-stamping.test.ts +3 -3
- package/src/kernel/element-registries.ts +12 -5
- package/src/kernel/errors.ts +24 -0
- package/src/kernel/flow-units.ts +64 -0
- package/src/kernel/flow.ts +29 -17
- package/src/kernel/fx-auth-keys.test.ts +88 -0
- package/src/kernel/fx-auth-keys.ts +198 -0
- package/src/kernel/fx.test.ts +17 -1
- package/src/kernel/fx.ts +59 -18
- package/src/kernel/horizontal-child.ts +4 -4
- package/src/kernel/http-path-pending.ts +20 -0
- package/src/kernel/http-resource.ts +4 -0
- package/src/kernel/http-stream.test.ts +4 -4
- package/src/kernel/pipeline-api-key.test.ts +192 -7
- package/src/kernel/pipeline.test.ts +2 -2
- package/src/kernel/pipeline.ts +4 -2
- package/src/kernel/plugin-elements.test.ts +1 -1
- package/src/kernel/plugin-needs.test.ts +1 -1
- package/src/kernel/ready.test.ts +2 -2
- package/src/kernel/resource-mount.test.ts +14 -0
- package/src/kernel/run-duration.test.ts +2 -2
- package/src/kernel/stamp-http.test.ts +79 -0
- package/src/kernel/stamp-http.ts +75 -0
- package/src/kernel/triggers.ts +52 -27
- package/src/manifest/types.ts +22 -3
- package/src/release/build-lib.ts +1 -0
- package/src/release/measure.ts +2 -2
- package/src/runtime/json-code-block.test.ts +2 -2
- package/src/runtime/serve.test.ts +4 -4
- package/src/term.test.ts +52 -4
- package/src/term.ts +125 -17
- package/src/test/reset-element-registries.ts +8 -3
- package/src/cli/ask-seed.test.ts +0 -96
- package/src/cli/ask-seed.ts +0 -82
- package/src/console/ui-next/dist/assets/cache-glyph-BanhLsEY.js +0 -1
- package/src/console/ui-next/dist/assets/call-pii-button-bqkxMrJH.js +0 -1
- package/src/console/ui-next/dist/assets/collapsible-DYb0xU8C.js +0 -1
- package/src/console/ui-next/dist/assets/confirm-sheet-DDCRmG62.js +0 -1
- package/src/console/ui-next/dist/assets/explorer-empty-HjTnVQoR.js +0 -1
- package/src/console/ui-next/dist/assets/flows-page-DxDsOd4f.js +0 -1
- package/src/console/ui-next/dist/assets/http-method-CJCBYL2j.js +0 -1
- package/src/console/ui-next/dist/assets/index-Ce6WKWKM.js +0 -66
- package/src/console/ui-next/dist/assets/index-D4Ldtj79.css +0 -2
- package/src/console/ui-next/dist/assets/link-DF7SZ9Ek.js +0 -1
- package/src/console/ui-next/dist/assets/observability-page-BEZDzyYh.js +0 -4
- package/src/console/ui-next/dist/assets/query-gate-parts-1m8m1iNp.js +0 -2
- package/src/console/ui-next/dist/assets/react-B1ML8gxg.js +0 -1
- package/src/console/ui-next/dist/assets/replica-lag-C8_BRt2x.js +0 -18
- package/src/console/ui-next/dist/assets/sheet-form-D-ata7jy.js +0 -1
- package/src/console/ui-next/dist/assets/shortcut-Cgzuv4k1.js +0 -1
- package/src/console/ui-next/dist/assets/shortcut-keys-DO4IsVqv.js +0 -1
- package/src/console/ui-next/dist/assets/store-page-v3LXdYpr.js +0 -45
- package/src/console/ui-next/dist/assets/trace-detail-sheet-DFLFfUUX.js +0 -2
- package/src/console/ui-next/dist/assets/units-page-C0gW6Kdo.js +0 -1
- package/src/console/ui-next/dist/assets/vault-page-DuKzqwzW.js +0 -2
|
@@ -1,12 +1,19 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Store row detail Sheet — Fields / JSON tabs
|
|
2
|
+
* Store row detail Sheet — Fields / JSON tabs, in-sheet edit, delete.
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
5
|
import { useEffect, useMemo, useState, type JSX } from "react";
|
|
6
|
-
import {
|
|
6
|
+
import {
|
|
7
|
+
Cancel01Icon,
|
|
8
|
+
Delete02Icon,
|
|
9
|
+
PencilEdit01Icon,
|
|
10
|
+
Tick02Icon,
|
|
11
|
+
} from "@hugeicons/core-free-icons";
|
|
7
12
|
import { HugeiconsIcon } from "@hugeicons/react";
|
|
8
13
|
import { HighlightedJson } from "@/components/highlighted-json";
|
|
9
14
|
import { Button } from "@/components/ui/button";
|
|
15
|
+
import { Input } from "@/components/ui/input";
|
|
16
|
+
import { SHEET_CONTROL, SheetError } from "@/components/ui/sheet-form.tsx";
|
|
10
17
|
import {
|
|
11
18
|
Sheet,
|
|
12
19
|
SheetContent,
|
|
@@ -15,9 +22,15 @@ import {
|
|
|
15
22
|
SheetTitle,
|
|
16
23
|
} from "@/components/ui/sheet";
|
|
17
24
|
import { cn } from "@/lib/utils";
|
|
25
|
+
import { useStoreEdit } from "../data/use-store-edit.ts";
|
|
18
26
|
import { RevealCell } from "./reveal-cell.tsx";
|
|
19
|
-
import {
|
|
20
|
-
|
|
27
|
+
import {
|
|
28
|
+
formatGridCell,
|
|
29
|
+
type StoreGridColumn,
|
|
30
|
+
type StoreGridModel,
|
|
31
|
+
type StoreGridRow,
|
|
32
|
+
} from "../lib/grid-model.ts";
|
|
33
|
+
import { isStorePiiMask, parseStoreCellDraft, sanitizeStorePatch } from "../lib/patch.ts";
|
|
21
34
|
import { isRtlText } from "../lib/rtl.ts";
|
|
22
35
|
|
|
23
36
|
/** Props for {@link StoreRowDetailSheet}. */
|
|
@@ -31,12 +44,13 @@ export interface StoreRowDetailSheetProps {
|
|
|
31
44
|
readonly childName: string;
|
|
32
45
|
readonly tenant?: string | null;
|
|
33
46
|
readonly masked?: boolean;
|
|
34
|
-
readonly
|
|
47
|
+
readonly asGate?: string | null;
|
|
48
|
+
readonly asUserId?: string | null;
|
|
35
49
|
readonly onDeleteRow?: (row: StoreGridRow) => void;
|
|
36
50
|
}
|
|
37
51
|
|
|
38
52
|
/**
|
|
39
|
-
* Right-side detail for one grid row — Fields (Reveal/edit) and full JSON.
|
|
53
|
+
* Right-side detail for one grid row — Fields (Reveal / edit) and full JSON.
|
|
40
54
|
*
|
|
41
55
|
* @param props - Selected row + facet + mutation callbacks
|
|
42
56
|
*/
|
|
@@ -50,13 +64,28 @@ export function StoreRowDetailSheet({
|
|
|
50
64
|
childName,
|
|
51
65
|
tenant,
|
|
52
66
|
masked = false,
|
|
53
|
-
|
|
67
|
+
asGate = null,
|
|
68
|
+
asUserId = null,
|
|
54
69
|
onDeleteRow,
|
|
55
70
|
}: StoreRowDetailSheetProps): JSX.Element {
|
|
71
|
+
const edit = useStoreEdit();
|
|
56
72
|
const [tab, setTab] = useState<"fields" | "json">("fields");
|
|
73
|
+
const [editing, setEditing] = useState(false);
|
|
74
|
+
const [draft, setDraft] = useState<Record<string, string>>({});
|
|
75
|
+
const [error, setError] = useState<string | null>(null);
|
|
76
|
+
|
|
77
|
+
const editableColumns = useMemo(
|
|
78
|
+
() => model.columns.filter((col) => col.editable),
|
|
79
|
+
[model.columns],
|
|
80
|
+
);
|
|
81
|
+
const canEdit =
|
|
82
|
+
model.editable && (facet === "sql" || facet === "kv") && editableColumns.length > 0;
|
|
57
83
|
|
|
58
84
|
useEffect(() => {
|
|
59
|
-
if (open)
|
|
85
|
+
if (!open) return;
|
|
86
|
+
setTab("fields");
|
|
87
|
+
setEditing(false);
|
|
88
|
+
setError(null);
|
|
60
89
|
}, [open, row]);
|
|
61
90
|
|
|
62
91
|
const json = useMemo(() => {
|
|
@@ -70,7 +99,45 @@ export function StoreRowDetailSheet({
|
|
|
70
99
|
|
|
71
100
|
if (!row) return <></>;
|
|
72
101
|
|
|
73
|
-
const
|
|
102
|
+
const startEdit = (): void => {
|
|
103
|
+
const next: Record<string, string> = {};
|
|
104
|
+
for (const col of editableColumns) {
|
|
105
|
+
next[col.key] = draftFromCell(col.type, row.cells[col.key]);
|
|
106
|
+
}
|
|
107
|
+
setDraft(next);
|
|
108
|
+
setError(null);
|
|
109
|
+
setEditing(true);
|
|
110
|
+
setTab("fields");
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
const save = async (): Promise<void> => {
|
|
114
|
+
setError(null);
|
|
115
|
+
const patch: Record<string, unknown> = {};
|
|
116
|
+
for (const col of editableColumns) {
|
|
117
|
+
const text = draft[col.key] ?? "";
|
|
118
|
+
if (col.pii && text.trim() === "") continue;
|
|
119
|
+
if (text === draftFromCell(col.type, row.cells[col.key])) continue;
|
|
120
|
+
patch[col.key] = parseStoreCellDraft(col.type, text);
|
|
121
|
+
}
|
|
122
|
+
if (Object.keys(patch).length === 0) {
|
|
123
|
+
setEditing(false);
|
|
124
|
+
return;
|
|
125
|
+
}
|
|
126
|
+
try {
|
|
127
|
+
await edit.mutateAsync({
|
|
128
|
+
ref: storeRef,
|
|
129
|
+
...(facet === "sql" ? { child: childName, id: row.id } : { key: row.id }),
|
|
130
|
+
...(tenant ? { tenant } : {}),
|
|
131
|
+
...(asGate ? { asGate } : {}),
|
|
132
|
+
...(asUserId ? { asUserId } : {}),
|
|
133
|
+
patch: sanitizeStorePatch(patch),
|
|
134
|
+
commit: true,
|
|
135
|
+
});
|
|
136
|
+
setEditing(false);
|
|
137
|
+
} catch (err) {
|
|
138
|
+
setError(err instanceof Error ? err.message : String(err));
|
|
139
|
+
}
|
|
140
|
+
};
|
|
74
141
|
|
|
75
142
|
return (
|
|
76
143
|
<Sheet open={open} onOpenChange={onOpenChange}>
|
|
@@ -79,11 +146,12 @@ export function StoreRowDetailSheet({
|
|
|
79
146
|
showOverlay={false}
|
|
80
147
|
className="gap-0 p-0 sm:max-w-md"
|
|
81
148
|
data-slot="store-row-detail-sheet"
|
|
149
|
+
data-editing={editing ? "true" : "false"}
|
|
82
150
|
>
|
|
83
151
|
<SheetHeader className="gap-2 border-b border-border/50">
|
|
84
152
|
<div className="flex flex-col gap-0.5 pr-8">
|
|
85
153
|
<SheetTitle className="text-sm">
|
|
86
|
-
Row <span className="font-mono">{row.id}</span>
|
|
154
|
+
{editing ? "Edit" : "Row"} <span className="font-mono">{row.id}</span>
|
|
87
155
|
</SheetTitle>
|
|
88
156
|
<SheetDescription className="font-mono text-[11px]">
|
|
89
157
|
{storeRef}
|
|
@@ -106,6 +174,7 @@ export function StoreRowDetailSheet({
|
|
|
106
174
|
{model.columns.map((col) => {
|
|
107
175
|
const value = row.cells[col.key];
|
|
108
176
|
const maskedPii = col.pii && masked && isStorePiiMask(value);
|
|
177
|
+
const fieldEditable = editing && col.editable;
|
|
109
178
|
return (
|
|
110
179
|
<li key={col.key} className="flex items-baseline gap-3 py-1.5">
|
|
111
180
|
<span className="flex w-32 shrink-0 items-center gap-1.5">
|
|
@@ -130,7 +199,30 @@ export function StoreRowDetailSheet({
|
|
|
130
199
|
</span>
|
|
131
200
|
) : null}
|
|
132
201
|
</span>
|
|
133
|
-
{
|
|
202
|
+
{fieldEditable ? (
|
|
203
|
+
col.type === "json" ? (
|
|
204
|
+
<textarea
|
|
205
|
+
value={draft[col.key] ?? ""}
|
|
206
|
+
onChange={(event) =>
|
|
207
|
+
setDraft((prev) => ({ ...prev, [col.key]: event.target.value }))
|
|
208
|
+
}
|
|
209
|
+
aria-label={col.key}
|
|
210
|
+
rows={3}
|
|
211
|
+
className="min-h-16 min-w-0 flex-1 rounded-none border-0 bg-transparent font-mono text-[11px] outline-none"
|
|
212
|
+
/>
|
|
213
|
+
) : (
|
|
214
|
+
<Input
|
|
215
|
+
value={draft[col.key] ?? ""}
|
|
216
|
+
onChange={(event) =>
|
|
217
|
+
setDraft((prev) => ({ ...prev, [col.key]: event.target.value }))
|
|
218
|
+
}
|
|
219
|
+
type={col.type === "integer" || col.type === "number" ? "number" : "text"}
|
|
220
|
+
aria-label={col.key}
|
|
221
|
+
flat
|
|
222
|
+
className={cn(SHEET_CONTROL, "min-w-0 flex-1 px-0 font-mono")}
|
|
223
|
+
/>
|
|
224
|
+
)
|
|
225
|
+
) : maskedPii ? (
|
|
134
226
|
<RevealCell
|
|
135
227
|
refName={storeRef}
|
|
136
228
|
child={childName}
|
|
@@ -162,13 +254,42 @@ export function StoreRowDetailSheet({
|
|
|
162
254
|
)}
|
|
163
255
|
</div>
|
|
164
256
|
|
|
257
|
+
{error ? <SheetError slot="row-detail-error">{error}</SheetError> : null}
|
|
258
|
+
|
|
165
259
|
<div className="mt-auto flex items-center justify-between gap-2 border-t border-border/50 px-4 py-2.5">
|
|
166
|
-
{
|
|
260
|
+
{editing ? (
|
|
261
|
+
<div className="flex items-center gap-2">
|
|
262
|
+
<Button
|
|
263
|
+
type="button"
|
|
264
|
+
variant="outline"
|
|
265
|
+
size="sm"
|
|
266
|
+
onClick={() => {
|
|
267
|
+
setEditing(false);
|
|
268
|
+
setError(null);
|
|
269
|
+
}}
|
|
270
|
+
disabled={edit.isPending}
|
|
271
|
+
data-slot="row-detail-cancel"
|
|
272
|
+
>
|
|
273
|
+
<HugeiconsIcon icon={Cancel01Icon} className="size-3.5" aria-hidden />
|
|
274
|
+
Cancel
|
|
275
|
+
</Button>
|
|
276
|
+
<Button
|
|
277
|
+
type="button"
|
|
278
|
+
size="sm"
|
|
279
|
+
onClick={() => void save()}
|
|
280
|
+
disabled={edit.isPending}
|
|
281
|
+
data-slot="row-detail-save"
|
|
282
|
+
>
|
|
283
|
+
<HugeiconsIcon icon={Tick02Icon} className="size-3.5" aria-hidden />
|
|
284
|
+
{edit.isPending ? "Saving…" : "Save"}
|
|
285
|
+
</Button>
|
|
286
|
+
</div>
|
|
287
|
+
) : canEdit ? (
|
|
167
288
|
<Button
|
|
168
289
|
type="button"
|
|
169
290
|
variant="outline"
|
|
170
291
|
size="sm"
|
|
171
|
-
onClick={
|
|
292
|
+
onClick={startEdit}
|
|
172
293
|
data-slot="row-detail-edit"
|
|
173
294
|
>
|
|
174
295
|
<HugeiconsIcon icon={PencilEdit01Icon} className="size-3.5" aria-hidden />
|
|
@@ -177,7 +298,7 @@ export function StoreRowDetailSheet({
|
|
|
177
298
|
) : (
|
|
178
299
|
<span />
|
|
179
300
|
)}
|
|
180
|
-
{onDeleteRow ? (
|
|
301
|
+
{!editing && onDeleteRow ? (
|
|
181
302
|
<Button
|
|
182
303
|
type="button"
|
|
183
304
|
variant="destructive"
|
|
@@ -195,6 +316,26 @@ export function StoreRowDetailSheet({
|
|
|
195
316
|
);
|
|
196
317
|
}
|
|
197
318
|
|
|
319
|
+
/** Seed / compare an edit draft without turning `null` into the display em dash. */
|
|
320
|
+
function draftFromCell(type: StoreGridColumn["type"], raw: unknown): string {
|
|
321
|
+
if (raw === null || raw === undefined) return "";
|
|
322
|
+
if (isStorePiiMask(raw)) return "";
|
|
323
|
+
if (type === "json") {
|
|
324
|
+
try {
|
|
325
|
+
return JSON.stringify(raw, null, 2);
|
|
326
|
+
} catch {
|
|
327
|
+
return "";
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
if (typeof raw === "string") return raw;
|
|
331
|
+
if (typeof raw === "number" || typeof raw === "boolean") return String(raw);
|
|
332
|
+
try {
|
|
333
|
+
return JSON.stringify(raw);
|
|
334
|
+
} catch {
|
|
335
|
+
return "";
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
|
|
198
339
|
function TabButton({
|
|
199
340
|
active,
|
|
200
341
|
onClick,
|
|
@@ -17,8 +17,6 @@ import {
|
|
|
17
17
|
ShieldOff,
|
|
18
18
|
SourceCodeIcon,
|
|
19
19
|
TableIcon,
|
|
20
|
-
ViewIcon,
|
|
21
|
-
ViewOffSlashIcon,
|
|
22
20
|
ZapIcon,
|
|
23
21
|
} from "@hugeicons/core-free-icons";
|
|
24
22
|
import { HugeiconsIcon } from "@hugeicons/react";
|
|
@@ -33,7 +31,6 @@ import {
|
|
|
33
31
|
EXPLORER_GROUP_ROW_CLASS,
|
|
34
32
|
EXPLORER_ICON_BUTTON_CLASS,
|
|
35
33
|
EXPLORER_ICON_CLASS,
|
|
36
|
-
EXPLORER_STRIP_TOKEN_ACTIVE_CLASS,
|
|
37
34
|
EXPLORER_LIST_EMPTY_CLASS,
|
|
38
35
|
EXPLORER_RAIL_ACTIVE_CLASS,
|
|
39
36
|
EXPLORER_RAIL_CLASS,
|
|
@@ -45,16 +42,6 @@ import {
|
|
|
45
42
|
import { ExplorerSearch } from "@/components/explorer/explorer-search.tsx";
|
|
46
43
|
import { TreeExpandToggle } from "@/components/explorer/tree-expand-toggle.tsx";
|
|
47
44
|
import { Collapsible, CollapsibleContent, CollapsibleTrigger } from "@/components/ui/collapsible";
|
|
48
|
-
import {
|
|
49
|
-
DropdownMenu,
|
|
50
|
-
DropdownMenuCheckboxItem,
|
|
51
|
-
DropdownMenuContent,
|
|
52
|
-
DropdownMenuGroup,
|
|
53
|
-
DropdownMenuItem,
|
|
54
|
-
DropdownMenuLabel,
|
|
55
|
-
DropdownMenuSeparator,
|
|
56
|
-
DropdownMenuTrigger,
|
|
57
|
-
} from "@/components/ui/dropdown-menu";
|
|
58
45
|
import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip";
|
|
59
46
|
import type { ElementHugeIcon } from "@/lib/element-icons.ts";
|
|
60
47
|
import { cn } from "@/lib/utils";
|
|
@@ -65,6 +52,7 @@ import {
|
|
|
65
52
|
loadHiddenFacets,
|
|
66
53
|
saveHiddenFacets,
|
|
67
54
|
STORE_FACET_SPECS,
|
|
55
|
+
STORE_FACETS,
|
|
68
56
|
isSingletonStoreLeaf,
|
|
69
57
|
storeTreeAncestorKeys,
|
|
70
58
|
storeTreeFacetKey,
|
|
@@ -127,7 +115,6 @@ export function StoreTree({
|
|
|
127
115
|
const [query, setQuery] = useState("");
|
|
128
116
|
const [openByKey, setOpenByKey] = useState<Readonly<Record<string, boolean>>>({});
|
|
129
117
|
const [hiddenFacets, setHiddenFacets] = useState<ReadonlySet<StoreFacet>>(loadHiddenFacets);
|
|
130
|
-
const allBands = useMemo(() => bandStoreTree(stores), [stores]);
|
|
131
118
|
const bands = useMemo(() => bandStoreTree(filterStoreTree(stores, query)), [stores, query]);
|
|
132
119
|
const visibleBands = useMemo(() => visibleFacetBands(bands, hiddenFacets), [bands, hiddenFacets]);
|
|
133
120
|
const keys = useMemo(() => storeTreeOpenKeys(visibleBands), [visibleBands]);
|
|
@@ -172,15 +159,6 @@ export function StoreTree({
|
|
|
172
159
|
});
|
|
173
160
|
};
|
|
174
161
|
|
|
175
|
-
const showAllFacets = (): void => {
|
|
176
|
-
setHiddenFacets((prev) => {
|
|
177
|
-
if (prev.size === 0) return prev;
|
|
178
|
-
const next = new Set<StoreFacet>();
|
|
179
|
-
saveHiddenFacets(next);
|
|
180
|
-
return next;
|
|
181
|
-
});
|
|
182
|
-
};
|
|
183
|
-
|
|
184
162
|
const toggleAll = (): void => {
|
|
185
163
|
const next = !allOpen;
|
|
186
164
|
setOpenByKey((prev) => {
|
|
@@ -201,12 +179,7 @@ export function StoreTree({
|
|
|
201
179
|
aria-label="Search stores"
|
|
202
180
|
data-slot="store-search"
|
|
203
181
|
/>
|
|
204
|
-
<
|
|
205
|
-
bands={allBands}
|
|
206
|
-
hiddenFacets={hiddenFacets}
|
|
207
|
-
onHiddenChange={setFacetHidden}
|
|
208
|
-
onShowAll={showAllFacets}
|
|
209
|
-
/>
|
|
182
|
+
<FacetIconToggles hiddenFacets={hiddenFacets} onHiddenChange={setFacetHidden} />
|
|
210
183
|
<TreeExpandToggle
|
|
211
184
|
allOpen={allOpen}
|
|
212
185
|
disabled={keys.length === 0}
|
|
@@ -229,7 +202,6 @@ export function StoreTree({
|
|
|
229
202
|
<FacetBand
|
|
230
203
|
key={band.facet}
|
|
231
204
|
band={band}
|
|
232
|
-
onHide={() => setFacetHidden(band.facet, true)}
|
|
233
205
|
open={storeTreeIsOpen(facetKey, openByKey)}
|
|
234
206
|
onOpenChange={(open) => setKeyOpen(facetKey, open)}
|
|
235
207
|
storeOpen={(ref) =>
|
|
@@ -267,79 +239,53 @@ export function StoreTree({
|
|
|
267
239
|
}
|
|
268
240
|
|
|
269
241
|
/**
|
|
270
|
-
*
|
|
242
|
+
* Toolbar tokens for the four Store facets — press to show or hide the band.
|
|
271
243
|
*
|
|
272
|
-
* @param props -
|
|
244
|
+
* @param props - Hidden set + toggle
|
|
273
245
|
*/
|
|
274
|
-
function
|
|
275
|
-
bands,
|
|
246
|
+
function FacetIconToggles({
|
|
276
247
|
hiddenFacets,
|
|
277
248
|
onHiddenChange,
|
|
278
|
-
onShowAll,
|
|
279
249
|
}: {
|
|
280
|
-
readonly bands: readonly StoreFacetBand[];
|
|
281
250
|
readonly hiddenFacets: ReadonlySet<StoreFacet>;
|
|
282
251
|
readonly onHiddenChange: (facet: StoreFacet, hidden: boolean) => void;
|
|
283
|
-
readonly onShowAll: () => void;
|
|
284
252
|
}): JSX.Element {
|
|
285
|
-
const hiddenCount = bands.filter((band) => hiddenFacets.has(band.facet)).length;
|
|
286
|
-
const anyHidden = hiddenCount > 0;
|
|
287
|
-
|
|
288
253
|
return (
|
|
289
|
-
<
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
254
|
+
<div
|
|
255
|
+
className="flex h-full shrink-0 items-stretch"
|
|
256
|
+
role="group"
|
|
257
|
+
aria-label="Store facets"
|
|
258
|
+
data-slot="store-tree-facet-toggles"
|
|
259
|
+
>
|
|
260
|
+
{STORE_FACETS.map((facet) => {
|
|
261
|
+
const spec = STORE_FACET_SPECS[facet];
|
|
262
|
+
const visible = !hiddenFacets.has(facet);
|
|
263
|
+
return (
|
|
264
|
+
<ToolbarTip
|
|
265
|
+
key={facet}
|
|
266
|
+
label={visible ? `Hide ${spec.label}` : `Show ${spec.label}`}
|
|
267
|
+
className="flex self-stretch"
|
|
268
|
+
>
|
|
293
269
|
<button
|
|
294
|
-
{...props}
|
|
295
270
|
type="button"
|
|
296
|
-
aria-label=
|
|
297
|
-
|
|
298
|
-
|
|
271
|
+
aria-label={visible ? `Hide ${spec.label}` : `Show ${spec.label}`}
|
|
272
|
+
aria-pressed={visible}
|
|
273
|
+
data-slot="store-tree-facet-toggle"
|
|
274
|
+
data-facet={facet}
|
|
275
|
+
onClick={() => onHiddenChange(facet, visible)}
|
|
299
276
|
className={cn(
|
|
300
277
|
EXPLORER_ICON_BUTTON_CLASS,
|
|
301
|
-
|
|
278
|
+
visible
|
|
279
|
+
? explorerIconInk(spec.wellClass)
|
|
280
|
+
: "text-muted-foreground/40 hover:text-muted-foreground",
|
|
302
281
|
)}
|
|
303
282
|
>
|
|
304
|
-
<HugeiconsIcon
|
|
305
|
-
icon={anyHidden ? ViewOffSlashIcon : ViewIcon}
|
|
306
|
-
className="size-3.5"
|
|
307
|
-
aria-hidden
|
|
308
|
-
/>
|
|
283
|
+
<HugeiconsIcon icon={spec.icon} className={EXPLORER_ICON_CLASS} aria-hidden />
|
|
309
284
|
</button>
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
<DropdownMenuGroup>
|
|
315
|
-
<DropdownMenuLabel>Facets</DropdownMenuLabel>
|
|
316
|
-
{bands.map((band) => {
|
|
317
|
-
const spec = STORE_FACET_SPECS[band.facet];
|
|
318
|
-
const visible = !hiddenFacets.has(band.facet);
|
|
319
|
-
return (
|
|
320
|
-
<DropdownMenuCheckboxItem
|
|
321
|
-
key={band.facet}
|
|
322
|
-
checked={visible}
|
|
323
|
-
data-slot="store-tree-visibility-facet"
|
|
324
|
-
data-facet={band.facet}
|
|
325
|
-
onCheckedChange={(checked) => onHiddenChange(band.facet, checked === false)}
|
|
326
|
-
>
|
|
327
|
-
<HugeiconsIcon icon={spec.icon} className="size-3.5" aria-hidden />
|
|
328
|
-
{spec.label}
|
|
329
|
-
</DropdownMenuCheckboxItem>
|
|
330
|
-
);
|
|
331
|
-
})}
|
|
332
|
-
</DropdownMenuGroup>
|
|
333
|
-
{anyHidden ? (
|
|
334
|
-
<>
|
|
335
|
-
<DropdownMenuSeparator />
|
|
336
|
-
<DropdownMenuItem data-slot="store-tree-visibility-show-all" onClick={onShowAll}>
|
|
337
|
-
Show all
|
|
338
|
-
</DropdownMenuItem>
|
|
339
|
-
</>
|
|
340
|
-
) : null}
|
|
341
|
-
</DropdownMenuContent>
|
|
342
|
-
</DropdownMenu>
|
|
285
|
+
</ToolbarTip>
|
|
286
|
+
);
|
|
287
|
+
})}
|
|
288
|
+
</div>
|
|
343
289
|
);
|
|
344
290
|
}
|
|
345
291
|
|
|
@@ -350,7 +296,6 @@ function FacetVisibilityMenu({
|
|
|
350
296
|
*/
|
|
351
297
|
function FacetBand({
|
|
352
298
|
band,
|
|
353
|
-
onHide,
|
|
354
299
|
open,
|
|
355
300
|
onOpenChange,
|
|
356
301
|
storeOpen,
|
|
@@ -365,7 +310,6 @@ function FacetBand({
|
|
|
365
310
|
onOpenPerformance,
|
|
366
311
|
}: {
|
|
367
312
|
readonly band: StoreFacetBand;
|
|
368
|
-
readonly onHide: () => void;
|
|
369
313
|
readonly open: boolean;
|
|
370
314
|
readonly onOpenChange: (open: boolean) => void;
|
|
371
315
|
readonly storeOpen: (ref: string) => boolean;
|
|
@@ -444,7 +388,6 @@ function FacetBand({
|
|
|
444
388
|
onOpen={onOpenQuery}
|
|
445
389
|
/>
|
|
446
390
|
) : null}
|
|
447
|
-
<FacetVisibilityButton label={band.label} onHide={onHide} />
|
|
448
391
|
<TreeExpandToggle
|
|
449
392
|
allOpen={storesOpen}
|
|
450
393
|
disabled={storeRefs.length === 0}
|
|
@@ -821,49 +764,6 @@ function PerformanceBandButton({
|
|
|
821
764
|
);
|
|
822
765
|
}
|
|
823
766
|
|
|
824
|
-
/**
|
|
825
|
-
* Hide a facet band from the explorer. Restore it from the search-bar menu.
|
|
826
|
-
*
|
|
827
|
-
* @param props - Band label + hide
|
|
828
|
-
*/
|
|
829
|
-
function FacetVisibilityButton({
|
|
830
|
-
label,
|
|
831
|
-
onHide,
|
|
832
|
-
}: {
|
|
833
|
-
readonly label: string;
|
|
834
|
-
readonly onHide: () => void;
|
|
835
|
-
}): JSX.Element {
|
|
836
|
-
const action = `Hide ${label}`;
|
|
837
|
-
return (
|
|
838
|
-
<Tooltip>
|
|
839
|
-
<TooltipTrigger
|
|
840
|
-
render={(props) => (
|
|
841
|
-
<button
|
|
842
|
-
{...props}
|
|
843
|
-
type="button"
|
|
844
|
-
aria-label={action}
|
|
845
|
-
data-slot="store-facet-visibility"
|
|
846
|
-
onClick={(event) => {
|
|
847
|
-
event.stopPropagation();
|
|
848
|
-
props.onClick?.(event);
|
|
849
|
-
onHide();
|
|
850
|
-
}}
|
|
851
|
-
className={cn(
|
|
852
|
-
"inline-flex size-6 shrink-0 items-center justify-center rounded-md text-muted-foreground transition-colors",
|
|
853
|
-
"hover:bg-muted/80 hover:text-foreground focus-visible:ring-2 focus-visible:ring-ring/40 focus-visible:outline-none",
|
|
854
|
-
)}
|
|
855
|
-
>
|
|
856
|
-
<HugeiconsIcon icon={ViewOffSlashIcon} className="size-3.5" aria-hidden />
|
|
857
|
-
</button>
|
|
858
|
-
)}
|
|
859
|
-
/>
|
|
860
|
-
<TooltipContent side="bottom" className="text-[11px]">
|
|
861
|
-
{action}
|
|
862
|
-
</TooltipContent>
|
|
863
|
-
</Tooltip>
|
|
864
|
-
);
|
|
865
|
-
}
|
|
866
|
-
|
|
867
767
|
/**
|
|
868
768
|
* Chevron well on a facet or store row — reads as the expand control.
|
|
869
769
|
*
|
|
@@ -25,6 +25,7 @@ import {
|
|
|
25
25
|
fileNameFromKey,
|
|
26
26
|
filePreviewMode,
|
|
27
27
|
formatFilePreviewText,
|
|
28
|
+
previewBlobType,
|
|
28
29
|
type FileKind,
|
|
29
30
|
type FilePreviewMode,
|
|
30
31
|
} from "../lib/files-meta.ts";
|
|
@@ -82,7 +83,9 @@ export function FilePreview({
|
|
|
82
83
|
const objectUrl = useMemo(() => {
|
|
83
84
|
if (!object.data || !bytes || object.data.truncated) return null;
|
|
84
85
|
if (mode !== "image" && mode !== "pdf" && mode !== "video" && mode !== "audio") return null;
|
|
85
|
-
const blob = new Blob([bytesToArrayBuffer(bytes)], {
|
|
86
|
+
const blob = new Blob([bytesToArrayBuffer(bytes)], {
|
|
87
|
+
type: previewBlobType(mode, object.data.contentType),
|
|
88
|
+
});
|
|
86
89
|
return URL.createObjectURL(blob);
|
|
87
90
|
}, [object.data, bytes, mode]);
|
|
88
91
|
|