Merge pull request #2843 from beekeeper-studio/feat/changed-sign-json-viewer

Make changes in TableTable noticeable in JSON Viewer
This commit is contained in:
Matthew Rathbone
2025-03-21 21:18:19 -05:00
committed by GitHub
6 changed files with 91 additions and 15 deletions

View File

@@ -87,7 +87,6 @@ div.sidebar.detail-view-sidebar {
.CodeMirror.text-editor {
width: 100%;
padding-left: 0.65rem;
&, .CodeMirror-gutters {
background-color: $theme-bg !important;

View File

@@ -5,6 +5,18 @@
}
}
.CodeMirror-foldgutter {
padding-left: 1rem;
}
.CodeMirror-gutter-elt [class^=CodeMirror-foldgutter] {
padding-left: 1rem;
}
.CodeMirror-gutter-background.changed {
border-left: 2px solid $brand-warning;
}
.CodeMirror-guttermaker-subtle {
color: rgba($theme-base, 0.25);
}

View File

@@ -42,6 +42,7 @@ import {
} from "@/lib/editor/vim";
import { AppEvent } from "@/common/AppEvent";
import { keymapTypes } from "@/lib/db/types"
import { EditorMarker, LineGutter } from "@/lib/editor/utils";
interface InitializeOptions {
userKeymap?: typeof keymapTypes[number]['value']
@@ -75,6 +76,7 @@ export default {
"bookmarks",
"foldAll",
"unfoldAll",
"lineGutters",
],
data() {
return {
@@ -82,6 +84,7 @@ export default {
foundRootFold: false,
bookmarkInstances: [],
markInstances: [],
activeLineGutters: [],
wasEditorFocused: false,
firstInitialization: true,
};
@@ -176,6 +179,9 @@ export default {
bookmarks() {
this.initializeBookmarks();
},
lineGutters() {
this.initializeLineGutters();
},
foldAll() {
CodeMirror.commands.foldAll(this.editor)
},
@@ -199,14 +205,27 @@ export default {
async initialize(options: InitializeOptions = {}) {
this.destroyEditor();
const gutters = []
let foldGutter: { indicatorOpen: HTMLElement, indicatorFolded: HTMLElement };
const lineNumbers = this.lineNumbers ?? true
if (lineNumbers) {
gutters.push("CodeMirror-linenumbers")
}
if (this.foldGutter) {
gutters.push("CodeMirror-foldgutter")
}
const indicatorOpen = document.createElement("span");
indicatorOpen.classList.add("foldgutter", "btn-fab", "open-close");
indicatorOpen.classList.add("CodeMirror-foldgutter", "foldgutter", "btn-fab", "open-close");
indicatorOpen.innerHTML = `<i class="dropdown-icon material-icons">keyboard_arrow_down</i>`;
const indicatorFolded = document.createElement("span");
indicatorFolded.classList.add("foldgutter", "btn-fab", "open-close");
indicatorFolded.classList.add("CodeMirror-foldgutter", "foldgutter", "btn-fab", "open-close");
indicatorFolded.innerHTML = `<i class="dropdown-icon material-icons">keyboard_arrow_right</i>`;
foldGutter = { indicatorOpen, indicatorFolded };
const cm = CodeMirror.fromTextArea(this.$refs.editor, {
autoRefresh: true,
lineNumbers: this.lineNumbers ?? true,
@@ -219,7 +238,6 @@ export default {
[this.cmCtrlOrCmd("R")]: "replace",
[this.cmCtrlOrCmd("Shift-R")]: "replaceAll",
},
// @ts-expect-error not fully typed
options: {
closeOnBlur: false,
},
@@ -228,10 +246,10 @@ export default {
hintOptions: this.hintOptions,
keyMap: options.userKeymap,
getColumns: this.columnsGetter,
...(this.foldGutter && {
gutters: ["CodeMirror-linenumbers", { className: "CodeMirror-foldgutter", style: "width: 18px" }],
foldGutter: { indicatorOpen, indicatorFolded },
}),
gutters,
// @ts-expect-error not fully typed
foldGutter,
autoRefresh: true,
// Remove JSON root key from folding
...(this.removeJsonRootBrackets && {
foldGutter: {
@@ -361,11 +379,12 @@ export default {
this.$nextTick(() => {
this.initializeMarkers();
this.initializeBookmarks();
this.initializeLineGutters();
this.$emit("update:initialized", true);
})
},
initializeMarkers() {
const markers = this.markers || [];
const markers: EditorMarker[] = this.markers || [];
if (!this.editor) return;
// Cleanup existing bookmarks
@@ -423,6 +442,21 @@ export default {
this.bookmarkInstances.push(mark);
}
},
initializeLineGutters() {
const lineGutters = this.lineGutters || [];
if (!this.editor) return;
// Cleanup existing line gutters
this.activeLineGutters.forEach((lineGutter: LineGutter) => {
this.editor.removeLineClass(lineGutter.line, "gutter", "changed");
})
this.activeLineGutters = []
lineGutters.forEach((lineGutter: LineGutter) => {
this.editor.addLineClass(lineGutter.line, "gutter", "changed");
this.activeLineGutters.push(lineGutter)
})
},
destroyEditor() {
if (this.editor) {
this.editor

View File

@@ -63,6 +63,7 @@
:force-initizalize="reinitializeTextEditor + (reinitialize ?? 0)"
:markers="markers"
:plugins="textEditorPlugins"
:line-gutters="lineGutters"
:line-numbers="false"
/>
<div class="empty-state" v-show="empty">
@@ -91,7 +92,7 @@ import {
eachPaths,
} from "@/lib/data/detail_view";
import { mapGetters } from "vuex";
import { EditorMarker } from "@/lib/editor/utils";
import { EditorMarker, LineGutter } from "@/lib/editor/utils";
import { persistJsonFold } from "@/lib/editor/plugins/persistJsonFold";
import DetailViewSidebarUpsell from '@/components/upsell/DetailViewSidebarUpsell.vue'
import rawLog from "@bksLogger";
@@ -102,7 +103,7 @@ const log = rawLog.scope("detail-view-sidebar");
export default Vue.extend({
components: { TextEditor, DetailViewSidebarUpsell },
props: ["value", "hidden", "expandablePaths", "dataId", "title", "reinitialize"],
props: ["value", "hidden", "expandablePaths", "dataId", "title", "reinitialize", "signs"],
data() {
return {
reinitializeTextEditor: 0,
@@ -235,6 +236,19 @@ export default Vue.extend({
lines() {
return this.text?.split("\n") || [];
},
lineGutters() {
const lineGutters: LineGutter[] = []
_.forEach(this.signs, (_i, key) => {
const type = this.signs[key]
const line = findKeyPosition(this.text, [key]);
if (line === -1) {
log.warn(`Failed to sign key \`${key}\`. \`${key}\` is not found.`)
return
}
lineGutters.push({ line, type });
})
return lineGutters;
},
menuOptions() {
return [
{

View File

@@ -40,6 +40,7 @@
:hidden="!openDetailView"
:expandable-paths="expandablePaths"
:reinitialize="reinitializeDetailView"
:signs="selectedRowDataSigns"
@expandPath="expandForeignKey"
@close="toggleOpenDetailView(false)"
/>
@@ -741,7 +742,17 @@ export default Vue.extend({
label: createMenuItem("Open Column Filter"),
action: this.showColumnFilterModal,
}
}
},
/** This tells which fields have been modified */
selectedRowDataSigns() {
const signs = {}
for (const pendingUpdate of this.pendingChanges.updates) {
if (pendingUpdate.rowIndex === this.selectedRowIndex) {
signs[pendingUpdate.column] = "changed"
}
}
return signs
},
},
watch: {
@@ -1113,7 +1124,7 @@ export default Vue.extend({
},
buildPendingUpdates() {
return this.pendingChanges.updates.map((update) => {
return _.omit(update, ['key', 'oldValue', 'cell'])
return _.omit(update, ['key', 'oldValue', 'cell', 'rowIndex'])
});
},
buildPendingInserts() {
@@ -1270,7 +1281,7 @@ export default Vue.extend({
if (currentEdit) {
currentEdit.value = cell.getValue()
} else {
const payload: TableUpdate & { key: string, oldValue: any, cell: any } = {
const payload: TableUpdate & { key: string, oldValue: any, cell: any, rowIndex: number } = {
key: key,
table: this.table.name,
schema: this.table.schema,
@@ -1281,7 +1292,8 @@ export default Vue.extend({
primaryKeys,
oldValue: cell.getOldValue(),
cell: cell,
value: cell.getValue(0)
value: cell.getValue(0),
rowIndex: this.indexRowOf(cell.getRow())
}
// remove existing pending updates with identical pKey-column combo
let pendingUpdates = _.reject(this.pendingChanges.updates, { 'key': payload.key })

View File

@@ -10,6 +10,11 @@ export interface EditorMarker {
type: "error" | "highlight" | "custom"; // | "warning"
}
export interface LineGutter {
line: number;
type: "changed";
}
export const plugins = {
autoquote: CodeMirrorPlugins.registerAutoquote,
autoComplete: CodeMirrorPlugins.registerAutoComplete,