mirror of
https://github.com/DeNNiiInc/dbgate.git
synced 2026-04-19 23:35:59 +00:00
open JSON array as tabular view
This commit is contained in:
@@ -3,6 +3,7 @@ import { EngineDriver, ViewInfo, ColumnInfo } from 'dbgate-types';
|
|||||||
import { GridDisplay, ChangeCacheFunc, ChangeConfigFunc } from './GridDisplay';
|
import { GridDisplay, ChangeCacheFunc, ChangeConfigFunc } from './GridDisplay';
|
||||||
import { GridConfig, GridCache } from './GridConfig';
|
import { GridConfig, GridCache } from './GridConfig';
|
||||||
import { FreeTableModel } from './FreeTableModel';
|
import { FreeTableModel } from './FreeTableModel';
|
||||||
|
import { analyseCollectionDisplayColumns } from '.';
|
||||||
|
|
||||||
export class FreeTableGridDisplay extends GridDisplay {
|
export class FreeTableGridDisplay extends GridDisplay {
|
||||||
constructor(
|
constructor(
|
||||||
@@ -13,7 +14,9 @@ export class FreeTableGridDisplay extends GridDisplay {
|
|||||||
setCache: ChangeCacheFunc
|
setCache: ChangeCacheFunc
|
||||||
) {
|
) {
|
||||||
super(config, setConfig, cache, setCache);
|
super(config, setConfig, cache, setCache);
|
||||||
this.columns = this.getDisplayColumns(model);
|
this.columns = model?.structure?.__isDynamicStructure
|
||||||
|
? analyseCollectionDisplayColumns(model?.rows, this)
|
||||||
|
: this.getDisplayColumns(model);
|
||||||
this.filterable = false;
|
this.filterable = false;
|
||||||
this.sortable = false;
|
this.sortable = false;
|
||||||
}
|
}
|
||||||
|
|||||||
1
packages/types/dbinfo.d.ts
vendored
1
packages/types/dbinfo.d.ts
vendored
@@ -80,6 +80,7 @@ export interface TableInfo extends DatabaseObjectInfo {
|
|||||||
checks?: CheckInfo[];
|
checks?: CheckInfo[];
|
||||||
preloadedRows?: any[];
|
preloadedRows?: any[];
|
||||||
preloadedRowsKey?: string[];
|
preloadedRowsKey?: string[];
|
||||||
|
__isDynamicStructure?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface CollectionInfo extends DatabaseObjectInfo {}
|
export interface CollectionInfo extends DatabaseObjectInfo {}
|
||||||
|
|||||||
@@ -14,7 +14,6 @@
|
|||||||
export let isJsonView = false;
|
export let isJsonView = false;
|
||||||
|
|
||||||
let filter;
|
let filter;
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<SearchBoxWrapper>
|
<SearchBoxWrapper>
|
||||||
|
|||||||
@@ -147,19 +147,34 @@
|
|||||||
<HorizontalSplitter initialValue="300px" bind:size={managerSize} hideFirst={$collapsedLeftColumnStore}>
|
<HorizontalSplitter initialValue="300px" bind:size={managerSize} hideFirst={$collapsedLeftColumnStore}>
|
||||||
<div class="left" slot="1">
|
<div class="left" slot="1">
|
||||||
<WidgetColumnBar>
|
<WidgetColumnBar>
|
||||||
<WidgetColumnBarItem title="Columns" name="columns" height="45%" skip={freeTableColumn || isFormView}>
|
<WidgetColumnBarItem
|
||||||
|
title="Columns"
|
||||||
|
name="columns"
|
||||||
|
height="45%"
|
||||||
|
show={(!freeTableColumn || isDynamicStructure) && !isFormView}
|
||||||
|
>
|
||||||
<ColumnManager {...$$props} {managerSize} {isJsonView} />
|
<ColumnManager {...$$props} {managerSize} {isJsonView} />
|
||||||
</WidgetColumnBarItem>
|
</WidgetColumnBarItem>
|
||||||
|
|
||||||
<WidgetColumnBarItem title="Filters" name="jsonFilters" height="30%" skip={!isDynamicStructure}>
|
<WidgetColumnBarItem
|
||||||
|
title="Filters"
|
||||||
|
name="jsonFilters"
|
||||||
|
height="30%"
|
||||||
|
skip={!isDynamicStructure || !display?.filterable}
|
||||||
|
>
|
||||||
<JsonViewFilters {...$$props} {managerSize} />
|
<JsonViewFilters {...$$props} {managerSize} />
|
||||||
</WidgetColumnBarItem>
|
</WidgetColumnBarItem>
|
||||||
|
|
||||||
<WidgetColumnBarItem title="Columns" name="freeColumns" height="40%" skip={!freeTableColumn}>
|
<WidgetColumnBarItem
|
||||||
|
title="Columns"
|
||||||
|
name="freeColumns"
|
||||||
|
height="40%"
|
||||||
|
show={freeTableColumn && !isDynamicStructure}
|
||||||
|
>
|
||||||
<FreeTableColumnEditor {...$$props} {managerSize} />
|
<FreeTableColumnEditor {...$$props} {managerSize} />
|
||||||
</WidgetColumnBarItem>
|
</WidgetColumnBarItem>
|
||||||
|
|
||||||
<WidgetColumnBarItem title="Filters" name="filters" height="30%" skip={!isFormView}>
|
<WidgetColumnBarItem title="Filters" name="filters" height="30%" show={isFormView}>
|
||||||
<FormViewFilters {...$$props} {managerSize} driver={formDisplay?.driver} />
|
<FormViewFilters {...$$props} {managerSize} driver={formDisplay?.driver} />
|
||||||
</WidgetColumnBarItem>
|
</WidgetColumnBarItem>
|
||||||
|
|
||||||
@@ -168,12 +183,12 @@
|
|||||||
name="references"
|
name="references"
|
||||||
height="30%"
|
height="30%"
|
||||||
collapsed={isDetailView}
|
collapsed={isDetailView}
|
||||||
skip={!showReferences || !display?.hasReferences}
|
show={showReferences && display?.hasReferences}
|
||||||
>
|
>
|
||||||
<ReferenceManager {...$$props} {managerSize} />
|
<ReferenceManager {...$$props} {managerSize} />
|
||||||
</WidgetColumnBarItem>
|
</WidgetColumnBarItem>
|
||||||
|
|
||||||
<WidgetColumnBarItem title="Macros" name="macros" skip={!showMacros} collapsed>
|
<WidgetColumnBarItem title="Macros" name="macros" show={showMacros} collapsed>
|
||||||
<MacroManager {...$$props} {managerSize} />
|
<MacroManager {...$$props} {managerSize} />
|
||||||
</WidgetColumnBarItem>
|
</WidgetColumnBarItem>
|
||||||
</WidgetColumnBar>
|
</WidgetColumnBar>
|
||||||
|
|||||||
@@ -25,4 +25,5 @@
|
|||||||
label="Array({value.length})"
|
label="Array({value.length})"
|
||||||
bracketOpen="["
|
bracketOpen="["
|
||||||
bracketClose="]"
|
bracketClose="]"
|
||||||
|
elementValue={value}
|
||||||
/>
|
/>
|
||||||
@@ -10,9 +10,11 @@
|
|||||||
export let getValue = key => key;
|
export let getValue = key => key;
|
||||||
export let getPreviewValue = getValue;
|
export let getPreviewValue = getValue;
|
||||||
export let expanded = false, expandable = true;
|
export let expanded = false, expandable = true;
|
||||||
|
export let elementValue = null;
|
||||||
|
|
||||||
const context = getContext('json-tree-context-key');
|
const context = getContext('json-tree-context-key');
|
||||||
setContext('json-tree-context-key', { ...context, colon })
|
setContext('json-tree-context-key', { ...context, colon })
|
||||||
|
const elementData=getContext('json-tree-element-data');
|
||||||
|
|
||||||
$: slicedKeys = expanded ? keys: previewKeys.slice(0, 5);
|
$: slicedKeys = expanded ? keys: previewKeys.slice(0, 5);
|
||||||
|
|
||||||
@@ -28,6 +30,12 @@
|
|||||||
expanded = true;
|
expanded = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let domElement;
|
||||||
|
|
||||||
|
$: if (domElement && elementData && elementValue) {
|
||||||
|
elementData.set(domElement, elementValue)
|
||||||
|
}
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
<style>
|
<style>
|
||||||
label {
|
label {
|
||||||
@@ -51,7 +59,7 @@
|
|||||||
position: relative;
|
position: relative;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
<li class:indent={isParentExpanded}>
|
<li class:indent={isParentExpanded} class:jsonValueHolder={!!elementValue} bind:this={domElement}>
|
||||||
<label>
|
<label>
|
||||||
{#if expandable && isParentExpanded}
|
{#if expandable && isParentExpanded}
|
||||||
<JSONArrow on:click={toggleExpand} {expanded} />
|
<JSONArrow on:click={toggleExpand} {expanded} />
|
||||||
|
|||||||
@@ -2,6 +2,8 @@
|
|||||||
import JSONNode from './JSONNode.svelte';
|
import JSONNode from './JSONNode.svelte';
|
||||||
import { setContext } from 'svelte';
|
import { setContext } from 'svelte';
|
||||||
import contextMenu, { getContextMenu } from '../utility/contextMenu';
|
import contextMenu, { getContextMenu } from '../utility/contextMenu';
|
||||||
|
import openNewTab from '../utility/openNewTab';
|
||||||
|
import _ from 'lodash';
|
||||||
|
|
||||||
setContext('json-tree-context-key', {});
|
setContext('json-tree-context-key', {});
|
||||||
|
|
||||||
@@ -15,10 +17,45 @@
|
|||||||
export let isInserted;
|
export let isInserted;
|
||||||
export let isModified;
|
export let isModified;
|
||||||
|
|
||||||
|
const elementData = new WeakMap();
|
||||||
|
|
||||||
|
if (elementData) {
|
||||||
|
setContext('json-tree-element-data', elementData);
|
||||||
|
}
|
||||||
|
|
||||||
const parentMenu = getContextMenu();
|
const parentMenu = getContextMenu();
|
||||||
|
|
||||||
|
function getElementMenu({ targetElement }) {
|
||||||
|
if (!targetElement) return null;
|
||||||
|
const closest = targetElement.closest('.jsonValueHolder');
|
||||||
|
if (!closest) return;
|
||||||
|
const value = elementData.get(closest);
|
||||||
|
|
||||||
|
if (value && _.isArray(value)) {
|
||||||
|
return {
|
||||||
|
text: 'Open as data sheet',
|
||||||
|
onClick: () => {
|
||||||
|
openNewTab(
|
||||||
|
{
|
||||||
|
title: 'Data #',
|
||||||
|
icon: 'img free-table',
|
||||||
|
tabComponent: 'FreeTableTab',
|
||||||
|
props: {},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
editor: {
|
||||||
|
rows: value,
|
||||||
|
structure: { __isDynamicStructure: true, columns: [] },
|
||||||
|
},
|
||||||
|
}
|
||||||
|
);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<ul use:contextMenu={[parentMenu, menu]} class:isDeleted class:isInserted class:isModified>
|
<ul use:contextMenu={[parentMenu, menu, getElementMenu]} class:isDeleted class:isInserted class:isModified>
|
||||||
<JSONNode {key} {value} isParentExpanded={true} isParentArray={false} {expanded} {labelOverride} />
|
<JSONNode {key} {value} isParentExpanded={true} isParentArray={false} {expanded} {labelOverride} />
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
|
|||||||
@@ -9,6 +9,7 @@
|
|||||||
left={$currentDropDownMenu.left}
|
left={$currentDropDownMenu.left}
|
||||||
top={$currentDropDownMenu.top}
|
top={$currentDropDownMenu.top}
|
||||||
items={$currentDropDownMenu.items}
|
items={$currentDropDownMenu.items}
|
||||||
|
targetElement={$currentDropDownMenu.targetElement}
|
||||||
on:close={() => ($currentDropDownMenu = null)}
|
on:close={() => ($currentDropDownMenu = null)}
|
||||||
/>
|
/>
|
||||||
{/if}
|
{/if}
|
||||||
|
|||||||
@@ -53,7 +53,6 @@
|
|||||||
}
|
}
|
||||||
return item;
|
return item;
|
||||||
}
|
}
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
@@ -69,6 +68,7 @@
|
|||||||
export let top;
|
export let top;
|
||||||
export let left;
|
export let left;
|
||||||
export let onCloseParent;
|
export let onCloseParent;
|
||||||
|
export let targetElement;
|
||||||
|
|
||||||
let element;
|
let element;
|
||||||
|
|
||||||
@@ -104,7 +104,7 @@
|
|||||||
submenuOffset = hoverOffset;
|
submenuOffset = hoverOffset;
|
||||||
}, 500);
|
}, 500);
|
||||||
|
|
||||||
$: extracted = extractMenuItems(items);
|
$: extracted = extractMenuItems(items, { targetElement });
|
||||||
$: compacted = _.compact(extracted.map(x => mapItem(x, $commandsCustomized)));
|
$: compacted = _.compact(extracted.map(x => mapItem(x, $commandsCustomized)));
|
||||||
$: filtered = compacted.filter(x => !x.disabled || !x.hideDisabled);
|
$: filtered = compacted.filter(x => !x.disabled || !x.hideDisabled);
|
||||||
|
|
||||||
@@ -121,7 +121,6 @@
|
|||||||
document.removeEventListener('mousedown', handleClickOutside, true);
|
document.removeEventListener('mousedown', handleClickOutside, true);
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<ul class="dropDownMenuMarker" style={`left: ${left}px; top: ${top}px`} bind:this={element}>
|
<ul class="dropDownMenuMarker" style={`left: ${left}px; top: ${top}px`} bind:this={element}>
|
||||||
@@ -217,5 +216,4 @@
|
|||||||
position: relative;
|
position: relative;
|
||||||
left: 15px;
|
left: 15px;
|
||||||
}
|
}
|
||||||
|
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -119,7 +119,7 @@
|
|||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
const left = e.pageX;
|
const left = e.pageX;
|
||||||
const top = e.pageY;
|
const top = e.pageY;
|
||||||
currentDropDownMenu.set({ left, top, items: _.isFunction(menu) ? menu() : menu });
|
currentDropDownMenu.set({ left, top, items: menu, targetElement: e.target });
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleKeyDown = (data, hash, keyString, keyCode, event) => {
|
const handleKeyDown = (data, hash, keyString, keyCode, event) => {
|
||||||
|
|||||||
@@ -16,7 +16,7 @@
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { createFreeTableModel, runMacro } from 'dbgate-datalib';
|
import { createFreeTableModel, FreeTableGridDisplay, runMacro } from 'dbgate-datalib';
|
||||||
import { setContext } from 'svelte';
|
import { setContext } from 'svelte';
|
||||||
import { writable } from 'svelte/store';
|
import { writable } from 'svelte/store';
|
||||||
import registerCommand from '../commands/registerCommand';
|
import registerCommand from '../commands/registerCommand';
|
||||||
@@ -87,6 +87,9 @@
|
|||||||
setContext('collapsedLeftColumnStore', collapsedLeftColumnStore);
|
setContext('collapsedLeftColumnStore', collapsedLeftColumnStore);
|
||||||
|
|
||||||
registerMenu({ command: 'freeTable.save', tag: 'save' });
|
registerMenu({ command: 'freeTable.save', tag: 'save' });
|
||||||
|
|
||||||
|
// display is overridden in FreeTableGridCore, this is because of column manager
|
||||||
|
$: display = new FreeTableGridDisplay($modelState.value, $config, config.update, null, null);
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{#if isLoading}
|
{#if isLoading}
|
||||||
@@ -104,5 +107,7 @@
|
|||||||
freeTableColumn
|
freeTableColumn
|
||||||
showMacros
|
showMacros
|
||||||
onRunMacro={handleRunMacro}
|
onRunMacro={handleRunMacro}
|
||||||
|
isDynamicStructure={$modelState.value?.structure?.__isDynamicStructure}
|
||||||
|
{display}
|
||||||
/>
|
/>
|
||||||
{/if}
|
{/if}
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ export default function contextMenu(node, items = []) {
|
|||||||
if (items) {
|
if (items) {
|
||||||
const left = e.pageX;
|
const left = e.pageX;
|
||||||
const top = e.pageY;
|
const top = e.pageY;
|
||||||
currentDropDownMenu.set({ left, top, items });
|
currentDropDownMenu.set({ left, top, items, targetElement: e.target });
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -31,12 +31,12 @@ export default function contextMenu(node, items = []) {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function doExtractMenuItems(menu, res) {
|
function doExtractMenuItems(menu, res, options) {
|
||||||
if (_.isFunction(menu)) {
|
if (_.isFunction(menu)) {
|
||||||
doExtractMenuItems(menu(), res);
|
doExtractMenuItems(menu(options), res, options);
|
||||||
} else if (_.isArray(menu)) {
|
} else if (_.isArray(menu)) {
|
||||||
for (const item of menu) {
|
for (const item of menu) {
|
||||||
doExtractMenuItems(item, res);
|
doExtractMenuItems(item, res, options);
|
||||||
}
|
}
|
||||||
} else if (_.isPlainObject(menu) && !menu._skip) {
|
} else if (_.isPlainObject(menu) && !menu._skip) {
|
||||||
res.push(menu);
|
res.push(menu);
|
||||||
@@ -77,9 +77,9 @@ function processTags(items) {
|
|||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function extractMenuItems(menu) {
|
export function extractMenuItems(menu, options = null) {
|
||||||
let res = [];
|
let res = [];
|
||||||
doExtractMenuItems(menu, res);
|
doExtractMenuItems(menu, res, options);
|
||||||
res = processTags(res);
|
res = processTags(res);
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,6 +11,7 @@
|
|||||||
export let title;
|
export let title;
|
||||||
export let name;
|
export let name;
|
||||||
export let skip = false;
|
export let skip = false;
|
||||||
|
export let show = true;
|
||||||
export let height = null;
|
export let height = null;
|
||||||
export let collapsed = null;
|
export let collapsed = null;
|
||||||
|
|
||||||
@@ -27,12 +28,12 @@
|
|||||||
{
|
{
|
||||||
collapsed,
|
collapsed,
|
||||||
height,
|
height,
|
||||||
skip,
|
skip: skip || !show,
|
||||||
},
|
},
|
||||||
dynamicProps
|
dynamicProps
|
||||||
);
|
);
|
||||||
|
|
||||||
$: updateWidgetItemDefinition(widgetItemIndex, { collapsed: !visible, height, skip });
|
$: updateWidgetItemDefinition(widgetItemIndex, { collapsed: !visible, height, skip: skip || !show });
|
||||||
|
|
||||||
$: setInitialSize(height, $widgetColumnBarHeight);
|
$: setInitialSize(height, $widgetColumnBarHeight);
|
||||||
|
|
||||||
@@ -46,7 +47,7 @@
|
|||||||
let visible = !collapsed;
|
let visible = !collapsed;
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{#if !skip}
|
{#if !skip && show}
|
||||||
<WidgetTitle on:click={() => (visible = !visible)}>{title}</WidgetTitle>
|
<WidgetTitle on:click={() => (visible = !visible)}>{title}</WidgetTitle>
|
||||||
|
|
||||||
{#if visible}
|
{#if visible}
|
||||||
|
|||||||
Reference in New Issue
Block a user