feat(grid): format JSON values in cell editor
This commit is contained in:
1 parent
cb7682e932
commit
f7f39f124c
6 files changed
+72
-9
No files matched your search
@@ -103,6 +103,8 @@ import { cellImagePreviewUrl } from "@/lib/cellImageUrl";
|
|||||||
import {
|
import {
|
||||||
cellDetailEditorText,
|
cellDetailEditorText,
|
||||||
defaultCellDetailTab,
|
defaultCellDetailTab,
|
||||||
|
formatJsonText,
|
||||||
|
isJsonColumnType,
|
||||||
valueEditorActions,
|
valueEditorActions,
|
||||||
visibleCellDetailTabs,
|
visibleCellDetailTabs,
|
||||||
type CellDetailTab,
|
type CellDetailTab,
|
||||||
@@ -1774,7 +1776,10 @@ watch(activeCellDetailTab, (tab) => {
|
|||||||
|
|
||||||
const activeValueEditorActions = computed(() => {
|
const activeValueEditorActions = computed(() => {
|
||||||
const detail = activeCellDetail.value;
|
const detail = activeCellDetail.value;
|
||||||
return valueEditorActions({ canSetNull: !!detail?.isEditable && detail.value !== null });
|
return valueEditorActions({
|
||||||
|
canSetNull: !!detail?.isEditable && detail.value !== null,
|
||||||
|
canFormatJson: !!detail?.isEditable && isJsonColumnType(detail?.type),
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
const detailEditValue = ref("");
|
const detailEditValue = ref("");
|
||||||
@@ -1798,7 +1803,7 @@ function closeCellDetails() {
|
|||||||
function startDetailEdit() {
|
function startDetailEdit() {
|
||||||
const detail = activeCellDetail.value;
|
const detail = activeCellDetail.value;
|
||||||
if (!detail || !detail.isEditable) return;
|
if (!detail || !detail.isEditable) return;
|
||||||
detailEditValue.value = cellDetailEditorText(detail.value);
|
detailEditValue.value = cellDetailEditorText(detail.value, detail.type);
|
||||||
isEditingDetail.value = true;
|
isEditingDetail.value = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1842,7 +1847,7 @@ function cancelDetailEdit() {
|
|||||||
function cancelValueEditorEdit() {
|
function cancelValueEditorEdit() {
|
||||||
const detail = activeCellDetail.value;
|
const detail = activeCellDetail.value;
|
||||||
if (!detail || !detail.isEditable) return;
|
if (!detail || !detail.isEditable) return;
|
||||||
detailEditValue.value = cellDetailEditorText(detail.value);
|
detailEditValue.value = cellDetailEditorText(detail.value, detail.type);
|
||||||
isEditingDetail.value = true;
|
isEditingDetail.value = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1873,7 +1878,7 @@ function restoreDetailOriginalValue() {
|
|||||||
dirtyRows.value = new Map(dirtyRows.value);
|
dirtyRows.value = new Map(dirtyRows.value);
|
||||||
}
|
}
|
||||||
|
|
||||||
detailEditValue.value = cellDetailEditorText(restoredValue);
|
detailEditValue.value = cellDetailEditorText(restoredValue, detail.type);
|
||||||
isEditingDetail.value = activeCellDetailTab.value === "valueEditor";
|
isEditingDetail.value = activeCellDetailTab.value === "valueEditor";
|
||||||
detailCell.value = { ...detailCell.value! };
|
detailCell.value = { ...detailCell.value! };
|
||||||
}
|
}
|
||||||
@@ -1884,6 +1889,12 @@ function setValueEditorNull() {
|
|||||||
isEditingDetail.value = activeCellDetailTab.value === "valueEditor";
|
isEditingDetail.value = activeCellDetailTab.value === "valueEditor";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function formatValueEditorJson() {
|
||||||
|
const detail = activeCellDetail.value;
|
||||||
|
if (!detail || !isJsonColumnType(detail.type)) return;
|
||||||
|
detailEditValue.value = formatJsonText(detailEditValue.value) ?? detailEditValue.value;
|
||||||
|
}
|
||||||
|
|
||||||
function setDetailNull() {
|
function setDetailNull() {
|
||||||
const detail = activeCellDetail.value;
|
const detail = activeCellDetail.value;
|
||||||
if (!detail || !detail.isEditable) return;
|
if (!detail || !detail.isEditable) return;
|
||||||
@@ -4267,6 +4278,16 @@ defineExpose({
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex gap-1 mt-2 shrink-0">
|
<div class="flex gap-1 mt-2 shrink-0">
|
||||||
|
<Button
|
||||||
|
v-if="activeValueEditorActions.includes('formatJson')"
|
||||||
|
variant="outline"
|
||||||
|
size="sm"
|
||||||
|
class="h-6 text-xs"
|
||||||
|
@mousedown.prevent
|
||||||
|
@click="formatValueEditorJson"
|
||||||
|
>
|
||||||
|
{{ t("grid.formatJson") }}
|
||||||
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
v-if="activeValueEditorActions.includes('setNull')"
|
v-if="activeValueEditorActions.includes('setNull')"
|
||||||
variant="outline"
|
variant="outline"
|
||||||
|
|||||||
@@ -374,6 +374,7 @@ export default {
|
|||||||
rawValue: "Raw Value",
|
rawValue: "Raw Value",
|
||||||
copyValue: "Copy Value",
|
copyValue: "Copy Value",
|
||||||
editValue: "Edit Value",
|
editValue: "Edit Value",
|
||||||
|
formatJson: "Format JSON",
|
||||||
setNull: "Set NULL",
|
setNull: "Set NULL",
|
||||||
restoreOriginalValue: "Restore Original",
|
restoreOriginalValue: "Restore Original",
|
||||||
copyColumnName: "Copy Column Name",
|
copyColumnName: "Copy Column Name",
|
||||||
|
|||||||
@@ -342,6 +342,7 @@ export default {
|
|||||||
rawValue: "Valor sin procesar",
|
rawValue: "Valor sin procesar",
|
||||||
copyValue: "Copiar valor",
|
copyValue: "Copiar valor",
|
||||||
editValue: "Editar valor",
|
editValue: "Editar valor",
|
||||||
|
formatJson: "Formatear JSON",
|
||||||
setNull: "Establecer NULL",
|
setNull: "Establecer NULL",
|
||||||
restoreOriginalValue: "Restaurar original",
|
restoreOriginalValue: "Restaurar original",
|
||||||
copyColumnName: "Copiar nombre de columna",
|
copyColumnName: "Copiar nombre de columna",
|
||||||
|
|||||||
@@ -371,6 +371,7 @@ export default {
|
|||||||
rawValue: "原始值",
|
rawValue: "原始值",
|
||||||
copyValue: "复制值",
|
copyValue: "复制值",
|
||||||
editValue: "编辑值",
|
editValue: "编辑值",
|
||||||
|
formatJson: "格式化 JSON",
|
||||||
setNull: "设为 NULL",
|
setNull: "设为 NULL",
|
||||||
restoreOriginalValue: "恢复原值",
|
restoreOriginalValue: "恢复原值",
|
||||||
copyColumnName: "复制列名",
|
copyColumnName: "复制列名",
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
export type CellDetailTab = "details" | "valueEditor";
|
export type CellDetailTab = "details" | "valueEditor";
|
||||||
export type ValueEditorAction = "setNull" | "restoreOriginal";
|
export type ValueEditorAction = "formatJson" | "setNull" | "restoreOriginal";
|
||||||
|
|
||||||
export interface CellDetailPresentationOptions {
|
export interface CellDetailPresentationOptions {
|
||||||
isEditable: boolean;
|
isEditable: boolean;
|
||||||
@@ -17,12 +17,38 @@ export function visibleCellDetailTabs(options: CellDetailPresentationOptions): C
|
|||||||
return tabs;
|
return tabs;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function cellDetailEditorText(value: unknown): string {
|
export function cellDetailEditorText(value: unknown, columnType?: string): string {
|
||||||
if (value === null) return "";
|
if (value === null) return "";
|
||||||
|
if (isJsonColumnType(columnType)) {
|
||||||
|
const text = typeof value === "object" ? JSON.stringify(value) : String(value);
|
||||||
|
return formatJsonText(text) ?? text;
|
||||||
|
}
|
||||||
if (typeof value === "object") return JSON.stringify(value);
|
if (typeof value === "object") return JSON.stringify(value);
|
||||||
return String(value);
|
return String(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function valueEditorActions(options: { canSetNull: boolean }): ValueEditorAction[] {
|
export function valueEditorActions(options: { canSetNull: boolean; canFormatJson?: boolean }): ValueEditorAction[] {
|
||||||
return options.canSetNull ? ["setNull", "restoreOriginal"] : ["restoreOriginal"];
|
const actions: ValueEditorAction[] = [];
|
||||||
|
if (options.canFormatJson) actions.push("formatJson");
|
||||||
|
if (options.canSetNull) actions.push("setNull");
|
||||||
|
actions.push("restoreOriginal");
|
||||||
|
return actions;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function isJsonColumnType(columnType: string | undefined): boolean {
|
||||||
|
const base = (columnType ?? "")
|
||||||
|
.trim()
|
||||||
|
.toLowerCase()
|
||||||
|
.split(/[(:\s]/)[0];
|
||||||
|
return base === "json" || base === "jsonb";
|
||||||
|
}
|
||||||
|
|
||||||
|
export function formatJsonText(text: string): string | undefined {
|
||||||
|
const trimmed = text.trim();
|
||||||
|
if (!trimmed) return undefined;
|
||||||
|
try {
|
||||||
|
return JSON.stringify(JSON.parse(trimmed), null, 2);
|
||||||
|
} catch {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -33,7 +33,20 @@ test("cell detail value editor restores the original value text on cancel", () =
|
|||||||
assert.equal(cellDetailEditorText("already text"), "already text");
|
assert.equal(cellDetailEditorText("already text"), "already text");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test("cell detail value editor formats json typed string values", () => {
|
||||||
|
assert.equal(
|
||||||
|
cellDetailEditorText('{"nested":true,"items":[1,2]}', "jsonb"),
|
||||||
|
'{\n "nested": true,\n "items": [\n 1,\n 2\n ]\n}',
|
||||||
|
);
|
||||||
|
assert.equal(cellDetailEditorText('{"nested":true}', "varchar"), '{"nested":true}');
|
||||||
|
assert.equal(cellDetailEditorText("{invalid", "json"), "{invalid");
|
||||||
|
});
|
||||||
|
|
||||||
test("cell detail value editor uses cell actions instead of confirm and cancel", () => {
|
test("cell detail value editor uses cell actions instead of confirm and cancel", () => {
|
||||||
assert.deepEqual(valueEditorActions({ canSetNull: true }), ["setNull", "restoreOriginal"]);
|
assert.deepEqual(valueEditorActions({ canSetNull: true, canFormatJson: true }), [
|
||||||
|
"formatJson",
|
||||||
|
"setNull",
|
||||||
|
"restoreOriginal",
|
||||||
|
]);
|
||||||
assert.deepEqual(valueEditorActions({ canSetNull: false }), ["restoreOriginal"]);
|
assert.deepEqual(valueEditorActions({ canSetNull: false }), ["restoreOriginal"]);
|
||||||
});
|
});
|
||||||
Reference in new issue
Block a user