feat(grid): add data grid quick entry
This commit is contained in:
1 parent
074cef8358
commit
3e7cf30ce5
23 files changed
+1679
-186
No files matched your search
@@ -369,6 +369,7 @@ async function previewDocumentChanges(changes: DocumentGridChanges): Promise<str
|
||||
const customSaveHandler = computed<CustomSaveHandler>(() => ({
|
||||
save: gridSave,
|
||||
preview: previewDocumentChanges,
|
||||
supportsInsert: true,
|
||||
}));
|
||||
|
||||
function stopDocumentLoadingTimer() {
|
||||
|
||||
@@ -203,6 +203,7 @@ const debugLogDownloaded = ref(false);
|
||||
const editShowColumnCommentsInHeader = ref(settingsStore.editorSettings.showColumnCommentsInHeader);
|
||||
const editShowColumnTypesInHeader = ref(settingsStore.editorSettings.showColumnTypesInHeader);
|
||||
const editCompactColumnHeaderActions = ref(settingsStore.editorSettings.compactColumnHeaderActions);
|
||||
const editDataGridQuickEntry = ref(settingsStore.editorSettings.dataGridQuickEntry);
|
||||
const editInfiniteScroll = ref(settingsStore.editorSettings.infiniteScroll);
|
||||
const editInfiniteScrollMaxRows = ref(settingsStore.editorSettings.infiniteScrollMaxRows);
|
||||
const editTableColumnTemplateRows = ref<TableColumnTemplateGridRow[]>(tableColumnTemplateRowsFromSettings(settingsStore.editorSettings.tableColumnTemplateFields));
|
||||
@@ -477,6 +478,7 @@ watch(
|
||||
editShowColumnCommentsInHeader.value = settingsStore.editorSettings.showColumnCommentsInHeader;
|
||||
editShowColumnTypesInHeader.value = settingsStore.editorSettings.showColumnTypesInHeader;
|
||||
editCompactColumnHeaderActions.value = settingsStore.editorSettings.compactColumnHeaderActions;
|
||||
editDataGridQuickEntry.value = settingsStore.editorSettings.dataGridQuickEntry;
|
||||
editInfiniteScroll.value = settingsStore.editorSettings.infiniteScroll;
|
||||
editInfiniteScrollMaxRows.value = settingsStore.editorSettings.infiniteScrollMaxRows;
|
||||
editTableColumnTemplateRows.value = tableColumnTemplateRowsFromSettings(settingsStore.editorSettings.tableColumnTemplateFields);
|
||||
@@ -540,6 +542,7 @@ function hasChanges(): boolean {
|
||||
editShowColumnCommentsInHeader.value !== settingsStore.editorSettings.showColumnCommentsInHeader ||
|
||||
editShowColumnTypesInHeader.value !== settingsStore.editorSettings.showColumnTypesInHeader ||
|
||||
editCompactColumnHeaderActions.value !== settingsStore.editorSettings.compactColumnHeaderActions ||
|
||||
editDataGridQuickEntry.value !== settingsStore.editorSettings.dataGridQuickEntry ||
|
||||
editInfiniteScroll.value !== settingsStore.editorSettings.infiniteScroll ||
|
||||
editInfiniteScrollMaxRows.value !== settingsStore.editorSettings.infiniteScrollMaxRows ||
|
||||
JSON.stringify(normalizedEditTableColumnTemplateFields.value) !== JSON.stringify(settingsStore.editorSettings.tableColumnTemplateFields) ||
|
||||
@@ -584,6 +587,7 @@ async function persistSettings() {
|
||||
showColumnCommentsInHeader: editShowColumnCommentsInHeader.value,
|
||||
showColumnTypesInHeader: editShowColumnTypesInHeader.value,
|
||||
compactColumnHeaderActions: editCompactColumnHeaderActions.value,
|
||||
dataGridQuickEntry: editDataGridQuickEntry.value,
|
||||
infiniteScroll: editInfiniteScroll.value,
|
||||
infiniteScrollMaxRows: editInfiniteScrollMaxRows.value,
|
||||
tableColumnTemplateFields: normalizedEditTableColumnTemplateFields.value,
|
||||
@@ -670,6 +674,7 @@ function resetDefaultsForTab(tab: SettingsCategory) {
|
||||
editShowColumnCommentsInHeader.value = DEFAULT_EDITOR_SETTINGS.showColumnCommentsInHeader;
|
||||
editShowColumnTypesInHeader.value = DEFAULT_EDITOR_SETTINGS.showColumnTypesInHeader;
|
||||
editCompactColumnHeaderActions.value = DEFAULT_EDITOR_SETTINGS.compactColumnHeaderActions;
|
||||
editDataGridQuickEntry.value = DEFAULT_EDITOR_SETTINGS.dataGridQuickEntry;
|
||||
editInfiniteScroll.value = DEFAULT_EDITOR_SETTINGS.infiniteScroll;
|
||||
editInfiniteScrollMaxRows.value = DEFAULT_EDITOR_SETTINGS.infiniteScrollMaxRows;
|
||||
editTableColumnTemplateRows.value = tableColumnTemplateRowsFromSettings(DEFAULT_EDITOR_SETTINGS.tableColumnTemplateFields);
|
||||
@@ -708,6 +713,7 @@ function resetAllDefaults() {
|
||||
editShowColumnCommentsInHeader.value = DEFAULT_EDITOR_SETTINGS.showColumnCommentsInHeader;
|
||||
editShowColumnTypesInHeader.value = DEFAULT_EDITOR_SETTINGS.showColumnTypesInHeader;
|
||||
editCompactColumnHeaderActions.value = DEFAULT_EDITOR_SETTINGS.compactColumnHeaderActions;
|
||||
editDataGridQuickEntry.value = DEFAULT_EDITOR_SETTINGS.dataGridQuickEntry;
|
||||
editInfiniteScroll.value = DEFAULT_EDITOR_SETTINGS.infiniteScroll;
|
||||
editInfiniteScrollMaxRows.value = DEFAULT_EDITOR_SETTINGS.infiniteScrollMaxRows;
|
||||
editTableColumnTemplateRows.value = tableColumnTemplateRowsFromSettings(DEFAULT_EDITOR_SETTINGS.tableColumnTemplateFields);
|
||||
@@ -2323,6 +2329,17 @@ watch(
|
||||
</div>
|
||||
<Switch id="compact-column-header-actions" v-model="editCompactColumnHeaderActions" />
|
||||
</div>
|
||||
<div class="flex items-center justify-between gap-4 rounded-md border bg-muted/20 px-3 py-2">
|
||||
<div class="space-y-1">
|
||||
<Label for="data-grid-quick-entry">
|
||||
{{ t("settings.dataGridQuickEntry") }}
|
||||
</Label>
|
||||
<p class="text-xs text-muted-foreground">
|
||||
{{ t("settings.dataGridQuickEntryDescription") }}
|
||||
</p>
|
||||
</div>
|
||||
<Switch id="data-grid-quick-entry" v-model="editDataGridQuickEntry" />
|
||||
</div>
|
||||
<div class="flex items-center justify-between gap-4 rounded-md border bg-muted/20 px-3 py-2">
|
||||
<div class="space-y-1">
|
||||
<Label for="infinite-scroll">
|
||||
|
||||
@@ -92,7 +92,7 @@ import type { BuildSingleColumnAlterSqlOptions } from "@/lib/tableStructureEdito
|
||||
import { buildTableSelectSql, quoteTableIdentifier } from "@/lib/tableSelectSql";
|
||||
import { uuid } from "@/lib/utils";
|
||||
import { resolveHeaderColumnType } from "@/lib/dataGridColumnType";
|
||||
import { canEditExistingTableRows, canUseKeylessRowPredicate, hiveTablePropertiesIndicateTransactional, isClickHouseExistingRowReadonlyColumn, isHiddenGridColumn, isTdengineExistingRowReadonlyColumn, usesSyntheticRowIdKey } from "@/lib/tableEditing";
|
||||
import { canEditExistingTableRows, canInsertTableRows, canUseKeylessRowPredicate, hiveTablePropertiesIndicateTransactional, isClickHouseExistingRowReadonlyColumn, isHiddenGridColumn, isTdengineExistingRowReadonlyColumn, usesSyntheticRowIdKey } from "@/lib/tableEditing";
|
||||
import { buildDataGridColumnDistinctValuesSql, buildDataGridContextFilterCondition, buildDataGridCountSql, buildHiveTablePropertiesSql, type DataGridContextFilterMode } from "@/lib/dataGridSql";
|
||||
import {
|
||||
buildVisibleTransposeRows,
|
||||
@@ -106,7 +106,7 @@ import {
|
||||
transposeScrollLeftForRecord,
|
||||
visibleTransposeRecordWindow,
|
||||
} from "@/lib/dataGridTranspose";
|
||||
import { matchesRowStatusFilter, type RowStatus, type RowStatusFilter } from "@/lib/gridRowStatus";
|
||||
import { canApplyGridSelectionValue, canDeleteGridRowItem, canEditGridCellDetail, matchesRowStatusFilter, shouldShowQuickEntryDraftRow, type RowStatus, type RowStatusFilter } from "@/lib/gridRowStatus";
|
||||
import { displayCellValue, type CellValue } from "@/lib/cellValue";
|
||||
import { getApplicablePreviewActions } from "@/lib/resultPreviewRegistry";
|
||||
import "@/lib/previewHandlers/geometryMapPreview";
|
||||
@@ -2710,7 +2710,13 @@ type DataGridTableMeta = NonNullable<typeof props.tableMeta>;
|
||||
const hiveTableTransactional = ref<boolean | undefined>(undefined);
|
||||
const canEditExistingRows = computed(() => !!props.customSaveHandler || canEditExistingTableRows(props.databaseType, hiveTableTransactional.value, props.tableMeta?.primaryKeys ?? []));
|
||||
const customReadonlyColumns = computed(() => new Set((props.customSaveHandler?.readonlyColumns ?? []).map((column) => column.toLowerCase())));
|
||||
const canInsertRows = computed(() => !props.customSaveHandler || props.customSaveHandler.canInsert !== false);
|
||||
const hasDataGridSaveTarget = computed(() => !!props.tableMeta || !!props.customSaveHandler);
|
||||
const hasDataGridInsertTarget = computed(() => {
|
||||
const handler = props.customSaveHandler;
|
||||
if (handler) return handler.supportsInsert === true || handler.canInsert === true;
|
||||
return !!props.tableMeta && canInsertTableRows(props.databaseType);
|
||||
});
|
||||
const canInsertRows = computed(() => !!props.editable && hasDataGridInsertTarget.value);
|
||||
const canDeleteRows = computed(() => !props.customSaveHandler || props.customSaveHandler.canDelete !== false);
|
||||
watch(
|
||||
() => [props.databaseType, props.connectionId, props.database, props.tableMeta?.schema, props.tableMeta?.tableName],
|
||||
@@ -2936,6 +2942,7 @@ interface RowItem {
|
||||
newIndex?: number;
|
||||
data: CellValue[];
|
||||
isNew: boolean;
|
||||
isDraft?: boolean;
|
||||
isDeleted: boolean;
|
||||
isDirtyCol: boolean[];
|
||||
status: RowStatus;
|
||||
@@ -2957,6 +2964,14 @@ type DisplayRowRef =
|
||||
isNew: true;
|
||||
isDeleted: false;
|
||||
status: RowStatus;
|
||||
}
|
||||
| {
|
||||
id: number;
|
||||
displayIndex: number;
|
||||
isNew: false;
|
||||
isDraft: true;
|
||||
isDeleted: false;
|
||||
status: RowStatus;
|
||||
};
|
||||
|
||||
const editor = useDataGridEditor({
|
||||
@@ -2976,6 +2991,7 @@ const editor = useDataGridEditor({
|
||||
currentWhereInput: computed(() => currentWhereInput()),
|
||||
orderByInput,
|
||||
rowStatusFilter,
|
||||
dataGridQuickEntryEnabled: computed(() => settingsStore.editorSettings.dataGridQuickEntry),
|
||||
initialEditColumn: firstVisibleColumnIndex,
|
||||
getRowItem,
|
||||
pageSize,
|
||||
@@ -2991,6 +3007,8 @@ const {
|
||||
dirtyRows,
|
||||
newRows,
|
||||
deletedRows,
|
||||
quickEntryDraftRow,
|
||||
quickEntryDraftRowId,
|
||||
pendingChangesVersion,
|
||||
pendingChangeCount,
|
||||
hasPendingChanges,
|
||||
@@ -3001,6 +3019,7 @@ const {
|
||||
exitTransaction,
|
||||
startEdit,
|
||||
commitEdit,
|
||||
commitEditAndMaybeAutoSave,
|
||||
commitEditFromBlur,
|
||||
applyCellValue,
|
||||
restoreCellValue,
|
||||
@@ -3023,6 +3042,8 @@ const {
|
||||
undoPendingChange,
|
||||
redoPendingChange,
|
||||
rowDataWithChanges,
|
||||
ensureQuickEntryDraftRow,
|
||||
isSavingNewRow,
|
||||
canEditColumn,
|
||||
resetGridVerticalScroll,
|
||||
getResetScrollAfterResult,
|
||||
@@ -3032,6 +3053,7 @@ const {
|
||||
isPreviewLoading,
|
||||
previewChanges,
|
||||
} = editor;
|
||||
const pendingQuickEntryDraftCellFocus = ref<{ rowId: number; col: number } | null>(null);
|
||||
|
||||
const showSqlPreview = ref(false);
|
||||
const previewSqlText = ref("");
|
||||
@@ -3090,34 +3112,45 @@ const saveActionMode = computed(() =>
|
||||
const saveToolbarState = computed(() =>
|
||||
dataGridSaveToolbarState({
|
||||
editable: props.editable,
|
||||
hasSaveTarget: !!props.tableMeta || !!props.customSaveHandler,
|
||||
hasSaveTarget: hasDataGridSaveTarget.value,
|
||||
hasPendingChanges: hasPendingChanges.value,
|
||||
isSaving: isSaving.value,
|
||||
}),
|
||||
);
|
||||
const hasSearchBarSlot = computed(() => !!slots["search-bar"]);
|
||||
const quickEntryEnabled = computed(() => settingsStore.editorSettings.dataGridQuickEntry);
|
||||
const showQuickEntryDraftRow = computed(() =>
|
||||
shouldShowQuickEntryDraftRow({
|
||||
editable: !!props.editable,
|
||||
hasInsertTarget: hasDataGridInsertTarget.value,
|
||||
quickEntryEnabled: quickEntryEnabled.value,
|
||||
rowStatusFilter: rowStatusFilter.value,
|
||||
hasPendingChanges: hasPendingChanges.value,
|
||||
}),
|
||||
);
|
||||
const showDataGridTopbar = computed(
|
||||
() =>
|
||||
(useTransaction.value && !!props.editable && (!!props.tableMeta || !!props.customSaveHandler)) ||
|
||||
(useTransaction.value && !!props.editable && hasDataGridSaveTarget.value) ||
|
||||
hasLocalColumnFilters.value ||
|
||||
canShowWhereSearch.value ||
|
||||
hasSearchBarSlot.value ||
|
||||
showQueryEditReadyBadge.value ||
|
||||
props.context !== "results" ||
|
||||
(!!props.editable && (!!props.tableMeta || !!props.customSaveHandler)) ||
|
||||
(!!props.editable && hasDataGridSaveTarget.value) ||
|
||||
transactionActive.value ||
|
||||
saveToolbarState.value.showActions,
|
||||
);
|
||||
|
||||
function canEditRowItem(item: RowItem | undefined): boolean {
|
||||
return !!props.editable && !!item && !item.isDeleted && (item.isNew || canEditExistingRows.value);
|
||||
return !!props.editable && !!item && !item.isDeleted && (item.isNew || item.isDraft || canEditExistingRows.value);
|
||||
}
|
||||
|
||||
function canEditCellItem(item: RowItem | undefined, columnIndex: number): boolean {
|
||||
if (!canEditRowItem(item) || !canEditColumn(columnIndex)) return false;
|
||||
if (isSavingNewRow(item)) return false;
|
||||
const column = props.result.columns[columnIndex] ?? "";
|
||||
if (customReadonlyColumns.value.has(column.toLowerCase())) return false;
|
||||
if (!item?.isNew) {
|
||||
if (!item?.isNew && !item?.isDraft) {
|
||||
const sourceColumn = props.sourceColumns?.[columnIndex] ?? column;
|
||||
if (isClickHouseExistingRowReadonlyColumn(props.databaseType, sourceColumn, props.tableMeta?.primaryKeys ?? [], props.tableMeta?.columns ?? [])) return false;
|
||||
if (isTdengineExistingRowReadonlyColumn(props.databaseType, column, props.tableMeta?.columns ?? [])) return false;
|
||||
@@ -3217,7 +3250,15 @@ function isDecimalColumnType(dataType: string): boolean {
|
||||
}
|
||||
|
||||
function canDeleteRowItem(item: RowItem | undefined): boolean {
|
||||
return !!props.editable && canDeleteRows.value && !!item && !item.isDeleted && (item.isNew || canEditExistingRows.value);
|
||||
if (!item) return false;
|
||||
return canDeleteGridRowItem({
|
||||
editable: !!props.editable && canDeleteRows.value,
|
||||
isDraft: !!item.isDraft,
|
||||
isDeleted: item.isDeleted,
|
||||
isNew: item.isNew,
|
||||
canEditExistingRows: canEditExistingRows.value,
|
||||
isSavingNewRow: isSavingNewRow(item),
|
||||
});
|
||||
}
|
||||
|
||||
function resetInfiniteScrollState() {
|
||||
@@ -3292,7 +3333,8 @@ const displayRowRefs = computed<DisplayRowRef[]>(() => {
|
||||
const dirty = dirtyRows.value.get(sourceIndex);
|
||||
const isDeleted = deletedRows.value.has(sourceIndex);
|
||||
const status: RowStatus = isDeleted ? "deleted" : dirty?.size ? "edited" : "clean";
|
||||
if (matchesRowStatusFilter(status, rowStatusFilter.value)) {
|
||||
const isActiveEditingRow = quickEntryEnabled.value && editingCell.value?.rowId === sourceIndex;
|
||||
if (matchesRowStatusFilter(status, rowStatusFilter.value) || isActiveEditingRow) {
|
||||
refs.push({ id: sourceIndex, displayIndex: refs.length, sourceIndex, isNew: false, isDeleted, status });
|
||||
}
|
||||
}
|
||||
@@ -3309,6 +3351,17 @@ const displayRowRefs = computed<DisplayRowRef[]>(() => {
|
||||
status,
|
||||
});
|
||||
});
|
||||
if (showQuickEntryDraftRow.value) {
|
||||
ensureQuickEntryDraftRow();
|
||||
refs.push({
|
||||
id: quickEntryDraftRowId,
|
||||
displayIndex: refs.length,
|
||||
isNew: false,
|
||||
isDraft: true,
|
||||
isDeleted: false,
|
||||
status: "draft",
|
||||
});
|
||||
}
|
||||
return refs;
|
||||
});
|
||||
|
||||
@@ -3322,6 +3375,14 @@ function rowItemFromDisplayRef(ref: DisplayRowRef): RowItem {
|
||||
isDirtyCol: cleanDirtyColumns.value,
|
||||
};
|
||||
}
|
||||
if (!("sourceIndex" in ref)) {
|
||||
ensureQuickEntryDraftRow();
|
||||
return {
|
||||
...ref,
|
||||
data: quickEntryDraftRow.value,
|
||||
isDirtyCol: cleanDirtyColumns.value,
|
||||
};
|
||||
}
|
||||
const row = props.result.rows[ref.sourceIndex] ?? [];
|
||||
const dirty = dirtyRows.value.get(ref.sourceIndex);
|
||||
return {
|
||||
@@ -3606,40 +3667,57 @@ function cellDetailButtonVisible(rowIndex: number, actualColIdx: number) {
|
||||
|
||||
function affectedRowIds(): number[] {
|
||||
if (hasRowSelection.value && selectedRowCount.value > 0) {
|
||||
return [...selectedRowIds.value];
|
||||
return [...selectedRowIds.value].filter((rowId) => !getRowItem(rowId)?.isDraft);
|
||||
}
|
||||
const range = selectedRange.value;
|
||||
if (range && range.startRow !== range.endRow) {
|
||||
return displayRowRefs.value.slice(range.startRow, range.endRow + 1).map((ref) => ref.id);
|
||||
return displayRowRefs.value
|
||||
.slice(range.startRow, range.endRow + 1)
|
||||
.filter((ref) => !("isDraft" in ref && ref.isDraft))
|
||||
.map((ref) => ref.id);
|
||||
}
|
||||
return [];
|
||||
}
|
||||
|
||||
function deletableRowIds(rowIds: number[]): number[] {
|
||||
return rowIds.filter((rowId) => canDeleteRowItem(getRowItem(rowId)));
|
||||
}
|
||||
|
||||
function exportSelectedRowsCsv() {
|
||||
return exportCsv(affectedRowIds());
|
||||
const rowIds = affectedRowIds();
|
||||
if (rowIds.length === 0) return;
|
||||
return exportCsv(rowIds);
|
||||
}
|
||||
|
||||
function exportSelectedRowsXlsx() {
|
||||
return exportXlsx(affectedRowIds());
|
||||
const rowIds = affectedRowIds();
|
||||
if (rowIds.length === 0) return;
|
||||
return exportXlsx(rowIds);
|
||||
}
|
||||
|
||||
function exportSelectedRowsJson() {
|
||||
return exportJson(affectedRowIds());
|
||||
const rowIds = affectedRowIds();
|
||||
if (rowIds.length === 0) return;
|
||||
return exportJson(rowIds);
|
||||
}
|
||||
|
||||
function exportSelectedRowsMarkdown() {
|
||||
return exportMarkdown(affectedRowIds());
|
||||
const rowIds = affectedRowIds();
|
||||
if (rowIds.length === 0) return;
|
||||
return exportMarkdown(rowIds);
|
||||
}
|
||||
|
||||
function exportSelectedRowsSql() {
|
||||
return exportSql(affectedRowIds());
|
||||
const rowIds = affectedRowIds();
|
||||
if (rowIds.length === 0) return;
|
||||
return exportSql(rowIds);
|
||||
}
|
||||
|
||||
function executePreviewAction(action: { execute: (ctx: any) => any }) {
|
||||
const config = action.execute({
|
||||
result: props.result,
|
||||
selectedRowIds: affectedRowIds(),
|
||||
displayRowRefs: displayRowRefs.value,
|
||||
displayRowRefs: displayRowRefs.value.filter((ref) => "sourceIndex" in ref),
|
||||
});
|
||||
if (config) {
|
||||
previewDialogConfig.value = config;
|
||||
@@ -3685,7 +3763,7 @@ function cellDetailFor(rowIndex: number, columnIndex: number): DataGridCellDetai
|
||||
resultColumnTypes: props.result.column_types,
|
||||
commentByColumn: columnCommentMap.value,
|
||||
displayValue: (value, index) => formatCellCached(value, index),
|
||||
isEditable: canEditCellItem(item, columnIndex),
|
||||
isEditable: canEditGridCellDetail({ canEditCell: canEditCellItem(item, columnIndex), isDraft: !!item.isDraft }),
|
||||
});
|
||||
}
|
||||
|
||||
@@ -3713,7 +3791,7 @@ const rowDetail = computed(() => {
|
||||
resultColumnTypes: props.result.column_types,
|
||||
commentByColumn: columnCommentMap.value,
|
||||
displayValue: (value, index) => formatCellCached(value, index),
|
||||
isEditableColumn: (columnIndex) => canEditCellItem(item, columnIndex),
|
||||
isEditableColumn: (columnIndex) => canEditGridCellDetail({ canEditCell: canEditCellItem(item, columnIndex), isDraft: !!item.isDraft }),
|
||||
});
|
||||
});
|
||||
|
||||
@@ -3721,12 +3799,14 @@ const columnDetail = computed(() => {
|
||||
if (columnDetailDialogColumnIndex.value === null) return null;
|
||||
const columnIndex = columnDetailDialogColumnIndex.value;
|
||||
return buildDataGridColumnDetail({
|
||||
rows: displayItems.value.map((item) => ({
|
||||
rowIndex: item.displayIndex,
|
||||
rowId: item.id,
|
||||
row: item.data,
|
||||
isEditable: canEditCellItem(item, columnIndex),
|
||||
})),
|
||||
rows: displayItems.value
|
||||
.filter((item) => !item.isDraft)
|
||||
.map((item) => ({
|
||||
rowIndex: item.displayIndex,
|
||||
rowId: item.id,
|
||||
row: item.data,
|
||||
isEditable: canEditGridCellDetail({ canEditCell: canEditCellItem(item, columnIndex), isDraft: !!item.isDraft }),
|
||||
})),
|
||||
columns: props.result.columns,
|
||||
columnIndex,
|
||||
typeByColumn: columnTypeMap.value,
|
||||
@@ -4386,6 +4466,16 @@ function formatCellCached(value: CellValue, columnIndex?: number): string {
|
||||
return rememberPrimitiveCellFormat(key, formatCell(value, columnIndex));
|
||||
}
|
||||
|
||||
function rowNumberText(item: RowItem | undefined): string {
|
||||
if (!item) return "";
|
||||
if (item.isDraft) return "*";
|
||||
return String(infiniteScrollEnabled.value ? item.displayIndex + 1 : item.displayIndex + 1 + (currentPage.value - 1) * pageSize.value);
|
||||
}
|
||||
|
||||
function draftCellPlaceholder(item: RowItem | undefined, columnIndex: number): string | null {
|
||||
return item?.isDraft && item.data[columnIndex] === null ? t("grid.quickEntryDraftPlaceholder") : null;
|
||||
}
|
||||
|
||||
watch(() => [props.result.columns.join("\u0000"), columnFormatterSignatures.value.join("\u0000")], clearCellFormatCache);
|
||||
|
||||
function quoteIdent(name: string): string {
|
||||
@@ -4402,6 +4492,9 @@ function isNull(value: unknown): boolean {
|
||||
}
|
||||
|
||||
function rowNumberStatusClass(item: RowItem): string {
|
||||
if (item.status === "draft") {
|
||||
return "border-muted-foreground/20 bg-muted/20 font-semibold text-muted-foreground";
|
||||
}
|
||||
if (item.status === "new") {
|
||||
return "border-emerald-500/40 bg-emerald-500/15 font-semibold text-emerald-700 dark:text-emerald-300";
|
||||
}
|
||||
@@ -4692,10 +4785,15 @@ function clearCanvasDetailHover(event?: MouseEvent) {
|
||||
|
||||
function onCanvasMouseDown(event: MouseEvent) {
|
||||
if (event.button !== 0) return;
|
||||
commitHiddenCanvasEditBeforeCellInteraction();
|
||||
const hit = canvasHitTest(event);
|
||||
if (!hit) return;
|
||||
if (!hit) {
|
||||
commitHiddenCanvasEditBeforeCellInteraction();
|
||||
return;
|
||||
}
|
||||
const item = displayItemAt(hit.rowIndex);
|
||||
const actualColIdx = hit.rowNumber ? undefined : visibleColumnIndexes.value[hit.visibleColIdx];
|
||||
if (item && actualColIdx !== undefined) prepareDataCellMouseDown(item, actualColIdx);
|
||||
commitHiddenCanvasEditBeforeCellInteraction();
|
||||
if (!item) return;
|
||||
if (hit.rowNumber) {
|
||||
handleRowClick(item.displayIndex, item.id, event);
|
||||
@@ -4780,7 +4878,7 @@ function canvasEditingCellIsVisible() {
|
||||
function commitHiddenCanvasEditBeforeCellInteraction() {
|
||||
if (!editingCell.value || !useCanvasGridRows.value) return;
|
||||
if (canvasEditingCellIsVisible()) return;
|
||||
commitEdit();
|
||||
void commitEditFromCellBlur();
|
||||
}
|
||||
|
||||
const canvasEditingCell = computed(() => {
|
||||
@@ -4881,6 +4979,7 @@ function drawCanvasGrid() {
|
||||
searchMatchKeys: searchMatchSet.value,
|
||||
currentSearchMatch: currentSearchMatch.value,
|
||||
formatCell: formatCellCached,
|
||||
draftCellPlaceholder: t("grid.quickEntryDraftPlaceholder"),
|
||||
isRowActive,
|
||||
rowCellsUseSelectionVisual,
|
||||
cellIsSelected,
|
||||
@@ -5299,6 +5398,7 @@ async function pasteClipboardIntoSelection() {
|
||||
|
||||
function pasteTextIntoSelection(text: string): boolean {
|
||||
const rows = parseClipboardTable(text);
|
||||
const allowDraftSelectionValue = selectedRangeTargetsOnlyDraftRow();
|
||||
|
||||
if (rows.length === 1 && rows[0]?.length === 1 && fillSelectionWithValue(rows[0][0])) {
|
||||
toast(t("grid.pasted"));
|
||||
@@ -5314,7 +5414,7 @@ function pasteTextIntoSelection(text: string): boolean {
|
||||
row.forEach((value, colOffset) => {
|
||||
const visibleCol = start.colIndex + colOffset;
|
||||
if (visibleCol >= visibleColumns.value.length) return;
|
||||
applied = applyVisibleCellValue(item, visibleCol, value) || applied;
|
||||
applied = applyVisibleSelectedCellValue(item, visibleCol, value, allowDraftSelectionValue) || applied;
|
||||
});
|
||||
});
|
||||
if (applied) toast(t("grid.pasted"));
|
||||
@@ -5351,15 +5451,28 @@ function applyVisibleCellValue(item: RowItem, visibleCol: number, value: string
|
||||
return true;
|
||||
}
|
||||
|
||||
function applyVisibleSelectedCellValue(item: RowItem, visibleCol: number, value: string | null, allowDraft = selectedRangeTargetsOnlyDraftRow()): boolean {
|
||||
if (!canApplyGridSelectionValue({ isDraft: !!item.isDraft, allowDraft })) return false;
|
||||
return applyVisibleCellValue(item, visibleCol, value);
|
||||
}
|
||||
|
||||
function selectedRangeTargetsOnlyDraftRow(): boolean {
|
||||
const range = selectedRange.value;
|
||||
if (!range) return false;
|
||||
if (range.startRow !== range.endRow) return false;
|
||||
return displayItemAt(range.startRow)?.isDraft === true;
|
||||
}
|
||||
|
||||
function fillSelectionWithValue(value: string | null): boolean {
|
||||
const range = selectedRange.value;
|
||||
let applied = false;
|
||||
const allowDraftSelectionValue = selectedRangeTargetsOnlyDraftRow();
|
||||
if (range) {
|
||||
for (let rowIndex = range.startRow; rowIndex <= range.endRow; rowIndex++) {
|
||||
const item = displayItemAt(rowIndex);
|
||||
if (!item) continue;
|
||||
for (let visibleCol = range.startCol; visibleCol <= range.endCol; visibleCol++) {
|
||||
applied = applyVisibleCellValue(item, visibleCol, value) || applied;
|
||||
applied = applyVisibleSelectedCellValue(item, visibleCol, value, allowDraftSelectionValue) || applied;
|
||||
}
|
||||
}
|
||||
return applied;
|
||||
@@ -5372,7 +5485,7 @@ function fillSelectionWithValue(value: string | null): boolean {
|
||||
const item = displayItemAt(rowIndex);
|
||||
if (!item) continue;
|
||||
for (const visibleCol of visibleColumnIndexes) {
|
||||
applied = applyVisibleCellValue(item, visibleCol, value) || applied;
|
||||
applied = applyVisibleSelectedCellValue(item, visibleCol, value) || applied;
|
||||
}
|
||||
}
|
||||
return applied;
|
||||
@@ -5420,11 +5533,12 @@ function cutSelection() {
|
||||
if (!props.editable || !selectedRange.value) return;
|
||||
copySelectionTsv();
|
||||
const range = selectedRange.value;
|
||||
const allowDraftSelectionValue = selectedRangeTargetsOnlyDraftRow();
|
||||
for (let rowIndex = range.startRow; rowIndex <= range.endRow; rowIndex++) {
|
||||
const item = displayItemAt(rowIndex);
|
||||
if (!item) continue;
|
||||
for (let visibleCol = range.startCol; visibleCol <= range.endCol; visibleCol++) {
|
||||
applyCellValue(item.id, actualColumnIndex(visibleCol), null);
|
||||
applyVisibleSelectedCellValue(item, visibleCol, null, allowDraftSelectionValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -5566,7 +5680,8 @@ function selectedOrCurrentRowIds(): number[] {
|
||||
}
|
||||
|
||||
function copyCurrentRow(): boolean {
|
||||
const rowIds = selectedOrCurrentRowIds();
|
||||
if (!canInsertRows.value) return false;
|
||||
const rowIds = selectedOrCurrentRowIds().filter((rowId) => !getRowItem(rowId)?.isDraft);
|
||||
if (rowIds.length === 0) return false;
|
||||
if (rowIds.length === 1) {
|
||||
cloneRow(rowIds[0]);
|
||||
@@ -5580,19 +5695,46 @@ function deleteCurrentRow(): boolean {
|
||||
const rowIds = selectedOrCurrentRowIds();
|
||||
if (rowIds.length === 0) return false;
|
||||
|
||||
const deletableRowIds = rowIds.filter((rowId) => canDeleteRowItem(getRowItem(rowId)));
|
||||
if (deletableRowIds.length === 0) return false;
|
||||
if (deletableRowIds.length === 1) {
|
||||
requestDeleteRow(deletableRowIds[0]);
|
||||
const targetRowIds = deletableRowIds(rowIds);
|
||||
if (targetRowIds.length === 0) return false;
|
||||
if (targetRowIds.length === 1) {
|
||||
requestDeleteRow(targetRowIds[0]);
|
||||
return true;
|
||||
}
|
||||
requestDeleteRows(deletableRowIds);
|
||||
requestDeleteRows(targetRowIds);
|
||||
return true;
|
||||
}
|
||||
|
||||
function commitGridEdit() {
|
||||
commitEdit();
|
||||
nextTick(() => gridRef.value?.focus({ preventScroll: true }));
|
||||
void commitEditAndMaybeAutoSave().finally(() => nextTick(() => gridRef.value?.focus({ preventScroll: true })));
|
||||
}
|
||||
|
||||
async function commitEditFromCellBlur() {
|
||||
const target = pendingQuickEntryDraftCellFocus.value;
|
||||
pendingQuickEntryDraftCellFocus.value = null;
|
||||
if (target && editingCell.value?.rowId === quickEntryDraftRowId && target.rowId === quickEntryDraftRowId) {
|
||||
await commitEditFromBlur({ promoteDraft: false });
|
||||
nextTick(() => {
|
||||
const item = getRowItem(target.rowId);
|
||||
if (item && canEditCellItem(item, target.col)) startEdit(target.rowId, target.col);
|
||||
});
|
||||
return;
|
||||
}
|
||||
await commitEditFromBlur();
|
||||
}
|
||||
|
||||
function prepareDataCellMouseDown(item: RowItem, actualColIdx: number) {
|
||||
const editing = editingCell.value;
|
||||
if (editing?.rowId === quickEntryDraftRowId && item.isDraft && item.id === quickEntryDraftRowId && editing.col !== actualColIdx) {
|
||||
pendingQuickEntryDraftCellFocus.value = { rowId: item.id, col: actualColIdx };
|
||||
} else {
|
||||
pendingQuickEntryDraftCellFocus.value = null;
|
||||
}
|
||||
}
|
||||
|
||||
function prepareTransposeCellMouseDown(rowIndex: number, actualColIdx: number) {
|
||||
const item = displayItemAt(rowIndex);
|
||||
if (item) prepareDataCellMouseDown(item, actualColIdx);
|
||||
}
|
||||
|
||||
async function saveGridChangesFromShortcut() {
|
||||
@@ -5662,7 +5804,7 @@ async function onGridKeydown(event: KeyboardEvent) {
|
||||
return;
|
||||
}
|
||||
if ((event.metaKey || event.ctrlKey) && !event.shiftKey && event.key.toLowerCase() === "n") {
|
||||
if (props.editable && (props.tableMeta || props.customSaveHandler) && canInsertRows.value) {
|
||||
if (props.editable && hasDataGridInsertTarget.value && canInsertRows.value) {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
addRow();
|
||||
@@ -7130,7 +7272,7 @@ const gridContextMenuItems = computed<ContextMenuItem[]>(() => {
|
||||
if (props.editable && contextRowItem.value) {
|
||||
const labels = rowActionLabels();
|
||||
items.push({ label: "", separator: true });
|
||||
if (canInsertRows.value) {
|
||||
if (canInsertRows.value && !contextRowItem.value.isDraft) {
|
||||
items.push({
|
||||
label: labels.clone,
|
||||
action: () => (isMultiRow.value ? cloneRows(affectedRowIds()) : cloneRow(contextRowItem.value!.id)),
|
||||
@@ -7146,7 +7288,7 @@ const gridContextMenuItems = computed<ContextMenuItem[]>(() => {
|
||||
} else if (canDeleteRowItem(contextRowItem.value)) {
|
||||
items.push({
|
||||
label: labels.delete,
|
||||
action: () => (isMultiRow.value ? requestDeleteRows(affectedRowIds()) : requestDeleteRow(contextRowItem.value!.id)),
|
||||
action: () => (isMultiRow.value ? requestDeleteRows(deletableRowIds(affectedRowIds())) : requestDeleteRow(contextRowItem.value!.id)),
|
||||
icon: Trash2,
|
||||
variant: "destructive" as const,
|
||||
});
|
||||
@@ -7194,7 +7336,7 @@ const gridContextMenuItems = computed<ContextMenuItem[]>(() => {
|
||||
"
|
||||
>
|
||||
<div class="data-grid-topbar flex items-stretch relative" :class="{ 'data-grid-topbar--compact': compactDataGridToolbar }">
|
||||
<div v-if="useTransaction && editable && (tableMeta || customSaveHandler)" class="flex items-center px-2 py-0.5 border-r shrink-0">
|
||||
<div v-if="useTransaction && editable && hasDataGridSaveTarget" class="flex items-center px-2 py-0.5 border-r shrink-0">
|
||||
<Select :model-value="rowStatusFilter" @update:model-value="(value: any) => setRowStatusFilter(String(value))">
|
||||
<SelectTrigger class="h-5 max-w-28 border-0 bg-transparent px-0 py-0 text-xs font-medium text-foreground/70 shadow-none focus-visible:ring-0 data-[state=open]:text-foreground [&_svg]:size-3">
|
||||
<SelectValue :placeholder="t('grid.filterRows')" />
|
||||
@@ -7219,7 +7361,7 @@ const gridContextMenuItems = computed<ContextMenuItem[]>(() => {
|
||||
</template>
|
||||
<template v-if="canShowWhereSearch">
|
||||
<div ref="searchSplitContainerRef" class="flex flex-1 min-w-0">
|
||||
<div class="flex flex-1 items-center gap-1 px-2 py-0.5 min-w-0 relative" :class="{ 'border-l': useTransaction && editable && (tableMeta || customSaveHandler) }" :style="whereSearchPaneStyle">
|
||||
<div class="flex flex-1 items-center gap-1 px-2 py-0.5 min-w-0 relative" :class="{ 'border-l': useTransaction && editable && hasDataGridSaveTarget }" :style="whereSearchPaneStyle">
|
||||
<Popover v-model:open="filterBuilderOpen">
|
||||
<PopoverTrigger as-child>
|
||||
<button
|
||||
@@ -7542,7 +7684,7 @@ const gridContextMenuItems = computed<ContextMenuItem[]>(() => {
|
||||
</TooltipTrigger>
|
||||
<TooltipContent side="bottom">{{ t("grid.goToColumn") }}</TooltipContent>
|
||||
</Tooltip>
|
||||
<Tooltip v-if="editable && (tableMeta || customSaveHandler) && canInsertRows">
|
||||
<Tooltip v-if="editable && hasDataGridInsertTarget && canInsertRows">
|
||||
<TooltipTrigger as-child>
|
||||
<Button variant="ghost" size="sm" :class="['data-grid-topbar-action-button h-5 shrink-0 text-xs px-1.5', compactDataGridToolbar ? 'data-grid-topbar-action-button--compact' : '']" @click="addRow">
|
||||
<Plus class="data-grid-topbar-action-icon w-3 h-3" />
|
||||
@@ -7719,7 +7861,7 @@ const gridContextMenuItems = computed<ContextMenuItem[]>(() => {
|
||||
@click="selectTransposeRecord(recordIndex, $event)"
|
||||
@contextmenu="selectTransposeRecord(recordIndex, $event)"
|
||||
>
|
||||
{{ recordIndex + 1 }}
|
||||
{{ rowNumberText(displayItems[recordIndex]) }}
|
||||
<div class="absolute right-0 top-0 bottom-0 w-1.5 cursor-col-resize hover:bg-primary/30" @mousedown.stop="onTransposeRecordResizeStart(recordIndex, $event)" @dblclick.stop="autoFitTransposeRecord(recordIndex)" />
|
||||
</div>
|
||||
<div class="shrink-0" :style="{ width: `${transposeAfterSpacerWidth}px` }" />
|
||||
@@ -7749,6 +7891,7 @@ const gridContextMenuItems = computed<ContextMenuItem[]>(() => {
|
||||
}"
|
||||
:style="{ width: `${getTransposeRecordWidth(cell.recordIndex)}px` }"
|
||||
:title="cell.display"
|
||||
@mousedown="prepareTransposeCellMouseDown(cell.recordIndex, cell.valueIndex)"
|
||||
@click="selectTransposeCell(cell.recordIndex, cell.valueIndex, $event)"
|
||||
@mouseenter="onTransposeCellMouseenter(cell.recordIndex, cell.valueIndex)"
|
||||
@mouseleave="onCellMouseleave(cell.recordIndex, cell.valueIndex)"
|
||||
@@ -7768,7 +7911,7 @@ const gridContextMenuItems = computed<ContextMenuItem[]>(() => {
|
||||
autocorrect="off"
|
||||
spellcheck="false"
|
||||
class="cell-edit-input cell-edit-input--expanded absolute left-0 top-0 min-h-full bg-background px-1.5 py-1 leading-[18px] outline-none z-10"
|
||||
@blur="commitEditFromBlur"
|
||||
@blur="commitEditFromCellBlur"
|
||||
@click.stop
|
||||
@focus="onCellEditTextareaInput"
|
||||
@input="onCellEditTextareaInput"
|
||||
@@ -7783,7 +7926,7 @@ const gridContextMenuItems = computed<ContextMenuItem[]>(() => {
|
||||
autocorrect="off"
|
||||
spellcheck="false"
|
||||
class="cell-edit-input absolute inset-0 bg-background border-2 border-primary px-1.5 py-0 leading-[26px] outline-none z-10"
|
||||
@blur="commitEditFromBlur"
|
||||
@blur="commitEditFromCellBlur"
|
||||
@click.stop
|
||||
@input="onCellEditTextareaInput"
|
||||
@keydown.stop="onCellEditKeydown"
|
||||
@@ -7791,7 +7934,10 @@ const gridContextMenuItems = computed<ContextMenuItem[]>(() => {
|
||||
/>
|
||||
</template>
|
||||
<template v-else>
|
||||
{{ cell.display }}
|
||||
<template v-if="draftCellPlaceholder(displayItems[cell.recordIndex], cell.valueIndex)">
|
||||
<span class="text-muted-foreground/70 italic">{{ draftCellPlaceholder(displayItems[cell.recordIndex], cell.valueIndex) }}</span>
|
||||
</template>
|
||||
<template v-else>{{ cell.display }}</template>
|
||||
<div v-if="cellDetailButtonVisible(cell.recordIndex, cell.valueIndex)" class="absolute right-2 top-0.5 flex items-center gap-1">
|
||||
<LightDropdownMenu
|
||||
v-if="canQuickDownloadCellValue(cell.recordIndex, cell.valueIndex)"
|
||||
@@ -8226,7 +8372,7 @@ const gridContextMenuItems = computed<ContextMenuItem[]>(() => {
|
||||
autocorrect="off"
|
||||
spellcheck="false"
|
||||
class="cell-edit-input cell-edit-input--expanded absolute left-0 top-0 min-h-full bg-background px-2.5 py-1 leading-[18px] outline-none z-10"
|
||||
@blur="commitEditFromBlur"
|
||||
@blur="commitEditFromCellBlur"
|
||||
@click.stop
|
||||
@focus="onCellEditTextareaInput"
|
||||
@input="onCellEditTextareaInput"
|
||||
@@ -8241,7 +8387,7 @@ const gridContextMenuItems = computed<ContextMenuItem[]>(() => {
|
||||
autocorrect="off"
|
||||
spellcheck="false"
|
||||
class="cell-edit-input absolute inset-0 bg-background border-2 border-primary px-2.5 py-0 leading-[22px] outline-none z-10"
|
||||
@blur="commitEditFromBlur"
|
||||
@blur="commitEditFromCellBlur"
|
||||
@click.stop
|
||||
@input="onCellEditTextareaInput"
|
||||
@keydown.stop="onCellEditKeydown"
|
||||
@@ -8302,7 +8448,8 @@ const gridContextMenuItems = computed<ContextMenuItem[]>(() => {
|
||||
:class="{
|
||||
'bg-destructive/5 opacity-70': item.isDeleted,
|
||||
'bg-primary/5': item.isNew && !isRowActive(item.displayIndex),
|
||||
'bg-muted/30': !item.isNew && !item.isDeleted && !isRowActive(item.displayIndex) && item.displayIndex % 2 === 1,
|
||||
'bg-muted/20': item.isDraft && !isRowActive(item.displayIndex),
|
||||
'bg-muted/30': !item.isNew && !item.isDraft && !item.isDeleted && !isRowActive(item.displayIndex) && item.displayIndex % 2 === 1,
|
||||
'active-row': isRowActive(item.displayIndex) && !item.isDeleted,
|
||||
'relative z-20 overflow-visible': editingCell?.rowId === item.id,
|
||||
}"
|
||||
@@ -8315,7 +8462,7 @@ const gridContextMenuItems = computed<ContextMenuItem[]>(() => {
|
||||
@dblclick.stop="toggleTranspose(item.displayIndex)"
|
||||
@contextmenu="onRowContext(item.id, item.displayIndex)"
|
||||
>
|
||||
{{ infiniteScrollEnabled ? item.displayIndex + 1 : item.displayIndex + 1 + (currentPage - 1) * pageSize }}
|
||||
{{ rowNumberText(item) }}
|
||||
</div>
|
||||
<div class="shrink-0" :style="{ width: `${horizontalColumnWindow.beforeWidth}px` }" />
|
||||
<div
|
||||
@@ -8338,7 +8485,10 @@ const gridContextMenuItems = computed<ContextMenuItem[]>(() => {
|
||||
'overflow-visible z-20 border-r-transparent': editingCell?.rowId === item.id && editingCell?.col === col.actualColIdx,
|
||||
'overflow-hidden': !(editingCell?.rowId === item.id && editingCell?.col === col.actualColIdx),
|
||||
}"
|
||||
@mousedown="handleDataCellMousedown(item.displayIndex, col.visibleColIdx, item.id, $event)"
|
||||
@mousedown="
|
||||
prepareDataCellMouseDown(item, col.actualColIdx);
|
||||
handleDataCellMousedown(item.displayIndex, col.visibleColIdx, item.id, $event);
|
||||
"
|
||||
@mouseenter="onCellMouseenter(item.displayIndex, col.visibleColIdx, col.actualColIdx)"
|
||||
@mouseleave="onCellMouseleave(item.displayIndex, col.actualColIdx)"
|
||||
@dblclick="canEditCellItem(item, col.actualColIdx) && startDomCellEdit(item.id, col.actualColIdx, formatCellCached(item.data[col.actualColIdx], col.actualColIdx), $event)"
|
||||
@@ -8358,7 +8508,7 @@ const gridContextMenuItems = computed<ContextMenuItem[]>(() => {
|
||||
autocorrect="off"
|
||||
spellcheck="false"
|
||||
class="cell-edit-input cell-edit-input--expanded absolute left-0 top-0 min-h-full bg-background px-2.5 py-1 leading-[18px] outline-none z-10"
|
||||
@blur="commitEditFromBlur"
|
||||
@blur="commitEditFromCellBlur"
|
||||
@click.stop
|
||||
@focus="onCellEditTextareaInput"
|
||||
@input="onCellEditTextareaInput"
|
||||
@@ -8373,7 +8523,7 @@ const gridContextMenuItems = computed<ContextMenuItem[]>(() => {
|
||||
autocorrect="off"
|
||||
spellcheck="false"
|
||||
class="cell-edit-input absolute inset-0 bg-background border-2 border-primary px-2.5 py-0 leading-[22px] outline-none z-10"
|
||||
@blur="commitEditFromBlur"
|
||||
@blur="commitEditFromCellBlur"
|
||||
@click.stop
|
||||
@input="onCellEditTextareaInput"
|
||||
@keydown.stop="onCellEditKeydown"
|
||||
@@ -8381,7 +8531,10 @@ const gridContextMenuItems = computed<ContextMenuItem[]>(() => {
|
||||
/>
|
||||
</template>
|
||||
<template v-else>
|
||||
{{ formatCellCached(item.data[col.actualColIdx], col.actualColIdx) }}
|
||||
<template v-if="draftCellPlaceholder(item, col.actualColIdx)">
|
||||
<span class="text-muted-foreground/70 italic">{{ draftCellPlaceholder(item, col.actualColIdx) }}</span>
|
||||
</template>
|
||||
<template v-else>{{ formatCellCached(item.data[col.actualColIdx], col.actualColIdx) }}</template>
|
||||
<div v-if="cellDetailButtonVisible(item.displayIndex, col.actualColIdx)" class="absolute right-2 top-0.5 flex items-center gap-1">
|
||||
<LightDropdownMenu
|
||||
v-if="canQuickDownloadCellValue(item.displayIndex, col.actualColIdx)"
|
||||
@@ -8871,7 +9024,7 @@ const gridContextMenuItems = computed<ContextMenuItem[]>(() => {
|
||||
<span v-if="!hasData" class="shrink-0">{{ t("grid.rowsAffected", { count: result.affected_rows }) }}</span>
|
||||
<span class="shrink-0">{{ result.execution_time_ms }}ms</span>
|
||||
|
||||
<template v-if="editable && (tableMeta || customSaveHandler)">
|
||||
<template v-if="editable && hasDataGridSaveTarget">
|
||||
<span v-if="hasPendingChanges" class="shrink-0 text-foreground">
|
||||
{{ t("grid.pendingChanges", { count: pendingChangeCount }) }}
|
||||
</span>
|
||||
|
||||
@@ -348,7 +348,7 @@ const mongoQueryResultSaveHandler = computed<CustomSaveHandler | undefined>(() =
|
||||
return stmts;
|
||||
};
|
||||
|
||||
return { save, preview, canInsert: false, canDelete: false, readonlyColumns: [target.idColumn], targetLabel: target.collection };
|
||||
return { save, preview, canInsert: false, canDelete: false, supportsInsert: false, readonlyColumns: [target.idColumn], targetLabel: target.collection };
|
||||
});
|
||||
const resultsPaneOpen = ref(false);
|
||||
const resultsPaneSize = ref(Number(safeLocalStorageGet("dbx-results-pane-size")) || DEFAULT_QUERY_RESULTS_PANE_SIZE);
|
||||
|
||||
Reference in new issue
Block a user