Files
dbx/apps/desktop/src/lib/cellDetailPresentation.ts
T
DengQingNian 47ddbd5ce7 feat(ui): 支持预览postgres的Geometry数据为图形
为 cell detail 面板中的 PostGIS geometry/geography 列添加基于 Canvas 的几何图形预览弹窗
2026-06-05 12:41:19 +08:00

99 lines
3.1 KiB
TypeScript

export type CellDetailTab = "details" | "valueEditor";
export type ValueEditorAction = "formatJson" | "setNull" | "restoreOriginal";
export const CELL_DETAIL_JSON_FORMAT_MAX_LENGTH = 50_000;
export interface CellDetailPresentationOptions {
isEditable: boolean;
}
export interface LinkedCellDetailOptions {
isOpen: boolean;
isEditing: boolean;
selectedCell: { rowIndex: number; visibleColIndex: number } | null;
actualColumnIndex: (visibleColIndex: number) => number;
}
export interface CellDetailTarget {
rowIndex: number;
col: number;
}
export function defaultCellDetailTab(): CellDetailTab {
return "details";
}
export function visibleCellDetailTabs(options: CellDetailPresentationOptions): CellDetailTab[] {
const tabs: CellDetailTab[] = ["details"];
if (options.isEditable) {
tabs.push("valueEditor");
}
return tabs;
}
export function cellDetailEditorText(value: unknown, _columnType?: string): string {
if (value === null) return "";
return cellDetailRawEditorText(value);
}
function cellDetailRawEditorText(value: unknown): string {
if (typeof value === "object") return JSON.stringify(value);
return String(value);
}
export function valueEditorActions(options: { canSetNull: boolean; canFormatJson?: boolean }): ValueEditorAction[] {
const actions: ValueEditorAction[] = [];
if (options.canFormatJson) actions.push("formatJson");
if (options.canSetNull) actions.push("setNull");
actions.push("restoreOriginal");
return actions;
}
export function linkedCellDetailTarget(options: LinkedCellDetailOptions): CellDetailTarget | null {
if (!options.isOpen || options.isEditing || !options.selectedCell) return null;
return {
rowIndex: options.selectedCell.rowIndex,
col: options.actualColumnIndex(options.selectedCell.visibleColIndex),
};
}
export function isJsonColumnType(columnType: string | undefined): boolean {
const base = (columnType ?? "")
.trim()
.toLowerCase()
.split(/[(:\s]/)[0];
return base === "json" || base === "jsonb";
}
export function isGeometryColumnType(columnType: string | undefined): boolean {
const base = (columnType ?? "")
.trim()
.toLowerCase()
.split(/[(:\s]/)[0];
return base === "geometry" || base === "geography";
}
export function canFormatCellDetailJson(value: unknown, columnType?: string): boolean {
if (value === null || value === undefined) return false;
const text = cellDetailRawEditorText(value);
if (text.length > CELL_DETAIL_JSON_FORMAT_MAX_LENGTH) return false;
if (isJsonColumnType(columnType)) return !!formatJsonText(text);
return typeof value === "string" && looksLikeJsonContainer(text) && !!formatJsonText(text);
}
export function formatJsonText(text: string): string | undefined {
const trimmed = text.trim();
if (!trimmed) return undefined;
if (trimmed.length > CELL_DETAIL_JSON_FORMAT_MAX_LENGTH) return undefined;
try {
return JSON.stringify(JSON.parse(trimmed), null, 2);
} catch {
return undefined;
}
}
function looksLikeJsonContainer(text: string): boolean {
const trimmed = text.trim();
return trimmed.startsWith("{") || trimmed.startsWith("[");
}