Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,6 @@
"editor.tabSize": 2,
"editor.codeActionsOnSave": {
"source.organizeImports": "explicit"
}
},
"jest.runMode": "on-demand"
}
8 changes: 7 additions & 1 deletion src/components/gui/schema-sidebar-list.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,11 @@ import { scc } from "@/core/command";
import { DatabaseSchemaItem } from "@/drivers/base-driver";
import { triggerEditorExtensionTab } from "@/extensions/trigger-editor";
import { ExportFormat, exportTableData } from "@/lib/export-helper";
import { Table } from "@phosphor-icons/react";
import { Icon, Table } from "@phosphor-icons/react";
import { LucideCog, LucideDatabase, LucideView } from "lucide-react";
import { useCallback, useEffect, useMemo, useState } from "react";
import { ListView, ListViewItem } from "../listview";
import { CloudflareIcon } from "../resource-card/icon";
import SchemaCreateDialog from "./schema-editor/schema-create";

interface SchemaListProps {
Expand Down Expand Up @@ -39,12 +40,17 @@ function prepareListViewItem(
let icon = Table;
let iconClassName = "";

console.log("ss", s);

if (s.type === "trigger") {
icon = LucideCog;
iconClassName = "text-purple-500";
} else if (s.type === "view") {
icon = LucideView;
iconClassName = "text-green-600 dark:text-green-300";
} else if (s.type === "table" && s.name === "_cf_KV") {
icon = CloudflareIcon as Icon;
iconClassName = "text-orange-500";
}

return {
Expand Down
92 changes: 85 additions & 7 deletions src/components/gui/table-result/render-cell.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import BlobCell from "@/components/gui/table-cell/blob-cell";
import { DatabaseValue } from "@/drivers/base-driver";
import parseSafeJson from "@/lib/json-safe";
import { deserializeV8 } from "@/lib/v8-derialization";
import { ColumnType } from "@outerbase/sdk-transform";
import { useMemo } from "react";
import BigNumberCell from "../table-cell/big-number-cell";
import GenericCell from "../table-cell/generic-cell";
import NumberCell from "../table-cell/number-cell";
Expand Down Expand Up @@ -42,17 +44,93 @@ function determineCellType(value: unknown) {
return undefined;
}

export default function tableResultCellRenderer({
y,
x,
state,
header,
isFocus,
}: OptimizeTableCellRenderProps<TableHeaderMetadata>) {
function CloudflareKvValue({
props,
}: {
props: OptimizeTableCellRenderProps<TableHeaderMetadata>;
}) {
const { y, x, state, header, isFocus } = props;

const value = useMemo(() => {
const rawBuffer = state.getValue(y, x);
let buffer = new ArrayBuffer();

if (rawBuffer instanceof ArrayBuffer) {
buffer = rawBuffer;
} else if (rawBuffer instanceof Uint8Array) {
buffer = rawBuffer.buffer as ArrayBuffer;
} else if (rawBuffer instanceof Array) {
buffer = new Uint8Array(rawBuffer).buffer;
}

return deserializeV8(buffer);
}, [y, x, state]);

let displayValue: string | null = "";

if (value.value !== undefined) {
if (typeof value.value === "string") {
displayValue = value.value;
} else if (value.value === null) {
displayValue = null;
} else if (typeof value.value === "object") {
// Protect from circular references
try {
displayValue = JSON.stringify(value.value, null);
} catch (e) {
if (e instanceof Error) {
value.error = e.message;
} else {
value.error = String(e);
}
}
} else {
displayValue = String(value.value);
}
}

if (value.error) {
return (
<div className="h-[35px] px-2 font-mono leading-[35px] text-red-500!">
Error: {value.error}
</div>
);
}

return (
<TextCell
header={header}
state={state}
editor={detectTextEditorType(displayValue)}
editMode={false}
value={displayValue}
valueType={ColumnType.TEXT}
focus={isFocus}
onChange={(newValue) => {
state.changeValue(y, x, newValue);
}}
/>
);
}

export default function tableResultCellRenderer(
props: OptimizeTableCellRenderProps<TableHeaderMetadata>
) {
const { y, x, state, header, isFocus } = props;

const editMode = isFocus && state.isInEditMode();
const value = state.getValue(y, x);

const valueType = determineCellType(value);

// Check if it is Cloudflare KV type
if (
header.metadata?.from?.table === "_cf_KV" &&
header.metadata?.from?.column === "value"
) {
return <CloudflareKvValue props={props} />;
}

switch (valueType ?? header.metadata.type) {
case ColumnType.INTEGER:
return (
Expand Down
9 changes: 3 additions & 6 deletions src/components/listview/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,8 @@ import {
} from "@/components/ui/context-menu";
import { OpenContextMenuList } from "@/core/channel-builtin";
import { cn } from "@/lib/utils";
import {
LucideChevronDown,
LucideChevronRight,
LucideIcon,
} from "lucide-react";
import { Icon } from "@phosphor-icons/react";
import { LucideChevronDown, LucideChevronRight } from "lucide-react";
import React, {
Dispatch,
Fragment,
Expand All @@ -24,7 +21,7 @@ import HighlightText from "../ui/highlight-text";
export interface ListViewItem<T = unknown> {
key: string;
name: string;
icon: LucideIcon;
icon: Icon;
iconColor?: string;
iconBadgeColor?: string;
data: T;
Expand Down
11 changes: 11 additions & 0 deletions src/lib/build-table-result.ts
Original file line number Diff line number Diff line change
Expand Up @@ -207,6 +207,16 @@ export function pipeVirtualColumnAsReadOnly(
}
}

export function pipeCloudflareSpecialTable(
headers: OptimizeTableHeaderProps<TableHeaderMetadata>[]
) {
for (const header of headers) {
if (header.metadata.from?.table === "_cf_KV") {
header.setting.readonly = true;
}
}
}

export function pipeCalculateInitialSize(
headers: OptimizeTableHeaderProps<TableHeaderMetadata>[],
{ result }: BuildTableResultProps
Expand Down Expand Up @@ -293,6 +303,7 @@ export function buildTableResultHeader(
pipeAttachColumnViaSchemas(headers, props);
pipeEditableTable(headers, props);
pipeVirtualColumnAsReadOnly(headers);
pipeCloudflareSpecialTable(headers);
pipeCalculateInitialSize(headers, props);
pipeColumnIcon(headers);

Expand Down
79 changes: 79 additions & 0 deletions src/lib/v8-derialization/deserialize.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
import { deserializeV8 } from ".";

function p(hex: string) {
const buffer = new Uint8Array(hex.length / 2);
for (let i = 0; i < hex.length; i += 2) {
buffer[i / 2] = parseInt(hex.substring(i, i + 2), 16);
}

return deserializeV8(buffer.buffer);
}

describe("V8 Deserialization", () => {
it("positive small number", () => {
expect(p("FF0F4906").value).toBe(3);
});

it("negative number", () => {
expect(p("FF0F4905").value).toBe(-3);
});

it("positive large number", () => {
expect(p("FF0F4994B0BEDF01").value).toBe(234343434);
});

it("string", () => {
expect(p("FF0F220B68656C6C6F20776F726C64").value).toBe("hello world");
});

it("unicode string", () => {
expect(p("FF0F6308604F7D59164E4C75").value).toBe("你好世界");
});

it("true", () => {
expect(p("FF0F54").value).toBe(true);
});

it("false", () => {
expect(p("FF0F46").value).toBe(false);
});

it("null", () => {
expect(p("FF0F30").value).toBe(null);
});

it("double", () => {
expect(p("FF0F4E1F85EB51B81E0940").value).toBeCloseTo(3.14);
});

it("big number", () => {
expect(p("FF0F5A10D20A1FEB8CA954AB").value).toBe(12345678901234567890n);
});

it("array", () => {
expect(p("FF0F4103220568656C6C6F2205776F726C64490A240003").value).toEqual([
"hello",
"world",
5,
]);
});

it("object", () => {
expect(
p("FF0F6F220568656C6C6F2205776F726C6422066E756D626572490A7B02").value
).toEqual({ hello: "world", number: 5 });
});

it("object with undefined", () => {
expect(
p(
"FF0F6F220568656C6C6F2205776F726C6422036172724103490249044906240003220275645F7B03"
).value
).toEqual({ hello: "world", arr: [1, 2, 3], ud: undefined });
});

it("date", () => {
const date = new Date(1743508780000);
expect(p("FF0F4400003E8B135F7942").value).toEqual(date);
});
});
Loading