mirror of
https://github.com/DeNNiiInc/dbgate.git
synced 2026-04-29 08:43:57 +00:00
free table editor
This commit is contained in:
50
packages/web/src/freetable/ColumnManagerRow.svelte
Normal file
50
packages/web/src/freetable/ColumnManagerRow.svelte
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import FontIcon from '../icons/FontIcon.svelte';
|
||||||
|
|
||||||
|
export let column;
|
||||||
|
export let onEdit;
|
||||||
|
export let onRemove;
|
||||||
|
export let onUp;
|
||||||
|
export let onDown;
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
<div class="name">{column.columnName}</div>
|
||||||
|
<div class="nowrap">
|
||||||
|
<span class="icon" on:click={onEdit}>
|
||||||
|
<FontIcon icon="icon edit" />
|
||||||
|
</span>
|
||||||
|
<span class="icon" on:click={onRemove}>
|
||||||
|
<FontIcon icon="icon delete" />
|
||||||
|
</span>
|
||||||
|
<span class="icon" on:click={onUp}>
|
||||||
|
<FontIcon icon="icon arrow-up" />
|
||||||
|
</span>
|
||||||
|
<span class="icon" on:click={onDown}>
|
||||||
|
<FontIcon icon="icon arrow-down" />
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.row {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
.row:hover {
|
||||||
|
background-color: var(--theme-bg-selected);
|
||||||
|
}
|
||||||
|
.name {
|
||||||
|
white-space: nowrap;
|
||||||
|
margin: 5px;
|
||||||
|
}
|
||||||
|
.icon {
|
||||||
|
position: relative;
|
||||||
|
top: 5px;
|
||||||
|
padding: 5px;
|
||||||
|
}
|
||||||
|
.icon:hover {
|
||||||
|
background-color: var(--theme-bg-3);
|
||||||
|
}
|
||||||
|
</style>
|
||||||
55
packages/web/src/freetable/ColumnNameEditor.svelte
Normal file
55
packages/web/src/freetable/ColumnNameEditor.svelte
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import { onMount } from 'svelte';
|
||||||
|
import keycodes from '../utility/keycodes';
|
||||||
|
|
||||||
|
export let onEnter;
|
||||||
|
export let onBlur = undefined;
|
||||||
|
export let focusOnCreate = false;
|
||||||
|
export let blurOnEnter = false;
|
||||||
|
export let existingNames;
|
||||||
|
export let defaultValue = '';
|
||||||
|
|
||||||
|
let domEditor;
|
||||||
|
let value = defaultValue || '';
|
||||||
|
$: isError = value && existingNames && existingNames.includes(value);
|
||||||
|
|
||||||
|
const handleKeyDown = event => {
|
||||||
|
if (value && event.keyCode == keycodes.enter && !isError) {
|
||||||
|
onEnter(value);
|
||||||
|
value = '';
|
||||||
|
if (blurOnEnter) domEditor.blur();
|
||||||
|
}
|
||||||
|
if (event.keyCode == keycodes.escape) {
|
||||||
|
value = '';
|
||||||
|
domEditor.blur();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const handleBlur = () => {
|
||||||
|
if (value && !isError) {
|
||||||
|
onEnter(value);
|
||||||
|
value = '';
|
||||||
|
}
|
||||||
|
if (onBlur) onBlur();
|
||||||
|
};
|
||||||
|
if (focusOnCreate) onMount(() => domEditor.focus());
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
{...$$restProps}
|
||||||
|
bind:value
|
||||||
|
bind:this={domEditor}
|
||||||
|
on:keydown={handleKeyDown}
|
||||||
|
on:blur={handleBlur}
|
||||||
|
class:isError
|
||||||
|
/>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
input {
|
||||||
|
width: calc(100% - 10px);
|
||||||
|
}
|
||||||
|
|
||||||
|
input.isError {
|
||||||
|
background: var(--theme-bg-red);
|
||||||
|
}
|
||||||
|
</style>
|
||||||
82
packages/web/src/freetable/FreeTableColumnEditor.svelte
Normal file
82
packages/web/src/freetable/FreeTableColumnEditor.svelte
Normal file
@@ -0,0 +1,82 @@
|
|||||||
|
<script context="module">
|
||||||
|
function dispatchChangeColumns(props, func, rowFunc = null) {
|
||||||
|
const { modelState, dispatchModel } = props;
|
||||||
|
const model = modelState.value;
|
||||||
|
|
||||||
|
dispatchModel({
|
||||||
|
type: 'set',
|
||||||
|
value: {
|
||||||
|
rows: rowFunc ? model.rows.map(rowFunc) : model.rows,
|
||||||
|
structure: {
|
||||||
|
...model.structure,
|
||||||
|
columns: func(model.structure.columns),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function exchange(array, i1, i2) {
|
||||||
|
const i1r = (i1 + array.length) % array.length;
|
||||||
|
const i2r = (i2 + array.length) % array.length;
|
||||||
|
const res = [...array];
|
||||||
|
[res[i1r], res[i2r]] = [res[i2r], res[i1r]];
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import _ from 'lodash';
|
||||||
|
import ManagerInnerContainer from '../elements/ManagerInnerContainer.svelte';
|
||||||
|
import ColumnManagerRow from './ColumnManagerRow.svelte';
|
||||||
|
import ColumnNameEditor from './ColumnNameEditor.svelte';
|
||||||
|
|
||||||
|
export let modelState;
|
||||||
|
export let dispatchModel;
|
||||||
|
export let managerSize;
|
||||||
|
|
||||||
|
let editingColumn = null;
|
||||||
|
|
||||||
|
$: structure = modelState.value.structure;
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<ManagerInnerContainer width={managerSize}>
|
||||||
|
{#each structure.columns as column, index}
|
||||||
|
{#if index == editingColumn}
|
||||||
|
<ColumnNameEditor
|
||||||
|
defaultValue={column.columnName}
|
||||||
|
onEnter={columnName => {
|
||||||
|
dispatchChangeColumns(
|
||||||
|
$$props,
|
||||||
|
cols => cols.map((col, i) => (index == i ? { columnName } : col)),
|
||||||
|
row => _.mapKeys(row, (v, k) => (k == column.columnName ? columnName : k))
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
onBlur={() => (editingColumn = null)}
|
||||||
|
focusOnCreate
|
||||||
|
blurOnEnter
|
||||||
|
existingNames={structure.columns.map(x => x.columnName)}
|
||||||
|
/>
|
||||||
|
{:else}
|
||||||
|
<ColumnManagerRow
|
||||||
|
{column}
|
||||||
|
onEdit={() => (editingColumn = index)}
|
||||||
|
onRemove={() => {
|
||||||
|
dispatchChangeColumns($$props, cols => cols.filter((c, i) => i != index));
|
||||||
|
}}
|
||||||
|
onUp={() => {
|
||||||
|
dispatchChangeColumns($$props, cols => exchange(cols, index, index - 1));
|
||||||
|
}}
|
||||||
|
onDown={() => {
|
||||||
|
dispatchChangeColumns($$props, cols => exchange(cols, index, index + 1));
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
{/if}
|
||||||
|
{/each}
|
||||||
|
<ColumnNameEditor
|
||||||
|
onEnter={columnName => {
|
||||||
|
dispatchChangeColumns($$props, cols => [...cols, { columnName }]);
|
||||||
|
}}
|
||||||
|
placeholder="New column"
|
||||||
|
existingNames={structure.columns.map(x => x.columnName)}
|
||||||
|
/>
|
||||||
|
</ManagerInnerContainer>
|
||||||
72
packages/web/src/freetable/FreeTableGrid.svelte
Normal file
72
packages/web/src/freetable/FreeTableGrid.svelte
Normal file
@@ -0,0 +1,72 @@
|
|||||||
|
<script lang="ts" context="module">
|
||||||
|
function extractMacroValuesForMacro(macroValues, macro) {
|
||||||
|
if (!macro) return {};
|
||||||
|
return {
|
||||||
|
..._.fromPairs((macro.args || []).filter(x => x.default != null).map(x => [x.name, x.default])),
|
||||||
|
..._.mapKeys(macroValues, (v, k) => k.replace(/^.*#/, '')),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import HorizontalSplitter from '../elements/HorizontalSplitter.svelte';
|
||||||
|
import VerticalSplitter from '../elements/VerticalSplitter.svelte';
|
||||||
|
import WidgetColumnBar from '../widgets/WidgetColumnBar.svelte';
|
||||||
|
import WidgetColumnBarItem from '../widgets/WidgetColumnBarItem.svelte';
|
||||||
|
import ColumnManager from '../datagrid/ColumnManager.svelte';
|
||||||
|
import ReferenceManager from '../datagrid/ReferenceManager.svelte';
|
||||||
|
import FreeTableGridCore from './FreeTableGridCore.svelte';
|
||||||
|
import FreeTableColumnEditor from './FreeTableColumnEditor.svelte';
|
||||||
|
|
||||||
|
let managerSize;
|
||||||
|
let selectedMacro;
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<HorizontalSplitter initialValue="300px" bind:size={managerSize}>
|
||||||
|
<div class="left" slot="1">
|
||||||
|
<WidgetColumnBar>
|
||||||
|
<WidgetColumnBarItem title="Columns" name="columns" height="40%">
|
||||||
|
<FreeTableColumnEditor {...$$props} {managerSize} />
|
||||||
|
</WidgetColumnBarItem>
|
||||||
|
|
||||||
|
<!-- <WidgetColumnBarItem title="Macros" name="macros">
|
||||||
|
<MacroManager {...$$props} {managerSize} />
|
||||||
|
</WidgetColumnBarItem> -->
|
||||||
|
</WidgetColumnBar>
|
||||||
|
</div>
|
||||||
|
<div class="grid" slot="2">
|
||||||
|
<VerticalSplitter initialValue="70%">
|
||||||
|
<svelte:fragment slot="1">
|
||||||
|
<FreeTableGridCore {...$$props} />
|
||||||
|
</svelte:fragment>
|
||||||
|
|
||||||
|
<!-- macroPreview={selectedMacro}
|
||||||
|
macroValues={extractMacroValuesForMacro(macroValues, selectedMacro)}
|
||||||
|
onSelectionChanged={setSelectedCells}
|
||||||
|
{setSelectedMacro} -->
|
||||||
|
|
||||||
|
<!-- {#if selectedMacro}
|
||||||
|
<MacroDetail
|
||||||
|
{selectedMacro}
|
||||||
|
{setSelectedMacro}
|
||||||
|
onChangeValues={setMacroValues}
|
||||||
|
{macroValues}
|
||||||
|
onExecute={handleExecuteMacro}
|
||||||
|
/>
|
||||||
|
{/if} -->
|
||||||
|
</VerticalSplitter>
|
||||||
|
</div>
|
||||||
|
</HorizontalSplitter>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.left {
|
||||||
|
display: flex;
|
||||||
|
flex: 1;
|
||||||
|
background-color: var(--theme-bg-0);
|
||||||
|
}
|
||||||
|
|
||||||
|
.grid {
|
||||||
|
position: relative;
|
||||||
|
flex-grow: 1;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
39
packages/web/src/freetable/FreeTableGridCore.svelte
Normal file
39
packages/web/src/freetable/FreeTableGridCore.svelte
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import { createGridCache, FreeTableGridDisplay } from 'dbgate-datalib';
|
||||||
|
import { writable } from 'svelte/store';
|
||||||
|
import uuidv1 from 'uuid/v1';
|
||||||
|
|
||||||
|
import DataGridCore from '../datagrid/DataGridCore.svelte';
|
||||||
|
import ImportExportModal from '../modals/ImportExportModal.svelte';
|
||||||
|
import { showModal } from '../modals/modalTools';
|
||||||
|
import axiosInstance from '../utility/axiosInstance';
|
||||||
|
import FreeTableGrider from './FreeTableGrider';
|
||||||
|
import MacroPreviewGrider from './MacroPreviewGrider';
|
||||||
|
|
||||||
|
export let macroPreview;
|
||||||
|
export let modelState;
|
||||||
|
export let dispatchModel;
|
||||||
|
export let macroValues;
|
||||||
|
export let config;
|
||||||
|
export let setConfig;
|
||||||
|
|
||||||
|
let selectedCells = [];
|
||||||
|
const cache = writable(createGridCache());
|
||||||
|
|
||||||
|
$: grider = macroPreview
|
||||||
|
? new MacroPreviewGrider(modelState.value, macroPreview, macroValues, selectedCells)
|
||||||
|
: new FreeTableGrider(modelState, dispatchModel);
|
||||||
|
$: display = new FreeTableGridDisplay(grider.model || modelState.value, config, setConfig, $cache, cache.update);
|
||||||
|
|
||||||
|
async function exportGrid() {
|
||||||
|
const jslid = uuidv1();
|
||||||
|
await axiosInstance.post('jsldata/save-free-table', { jslid, data: modelState.value });
|
||||||
|
const initialValues: any = {};
|
||||||
|
initialValues.sourceStorageType = 'jsldata';
|
||||||
|
initialValues.sourceJslId = jslid;
|
||||||
|
initialValues.sourceList = ['editor-data'];
|
||||||
|
showModal(ImportExportModal, { initialValues: initialValues });
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<DataGridCore {...$$props} {grider} {display} frameSelection={!!macroPreview} {exportGrid} onExportGrid={exportGrid} />
|
||||||
85
packages/web/src/freetable/FreeTableGrider.ts
Normal file
85
packages/web/src/freetable/FreeTableGrider.ts
Normal file
@@ -0,0 +1,85 @@
|
|||||||
|
import { FreeTableModel } from 'dbgate-datalib';
|
||||||
|
import Grider, { GriderRowStatus } from '../datagrid/Grider';
|
||||||
|
|
||||||
|
export default class FreeTableGrider extends Grider {
|
||||||
|
public model: FreeTableModel;
|
||||||
|
private batchModel: FreeTableModel;
|
||||||
|
|
||||||
|
constructor(public modelState, public dispatchModel) {
|
||||||
|
super();
|
||||||
|
this.model = modelState && modelState.value;
|
||||||
|
}
|
||||||
|
getRowData(index: any) {
|
||||||
|
return this.model.rows[index];
|
||||||
|
}
|
||||||
|
get rowCount() {
|
||||||
|
return this.model.rows.length;
|
||||||
|
}
|
||||||
|
get currentModel(): FreeTableModel {
|
||||||
|
return this.batchModel || this.model;
|
||||||
|
}
|
||||||
|
set currentModel(value) {
|
||||||
|
if (this.batchModel) this.batchModel = value;
|
||||||
|
else this.dispatchModel({ type: 'set', value });
|
||||||
|
}
|
||||||
|
setCellValue(index: number, uniqueName: string, value: any) {
|
||||||
|
const model = this.currentModel;
|
||||||
|
if (model.rows[index])
|
||||||
|
this.currentModel = {
|
||||||
|
...model,
|
||||||
|
rows: model.rows.map((row, i) => (index == i ? { ...row, [uniqueName]: value } : row)),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
get editable() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
get canInsert() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
get allowSave() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
insertRow(): number {
|
||||||
|
const model = this.currentModel;
|
||||||
|
this.currentModel = {
|
||||||
|
...model,
|
||||||
|
rows: [...model.rows, {}],
|
||||||
|
};
|
||||||
|
return this.currentModel.rows.length - 1;
|
||||||
|
}
|
||||||
|
deleteRow(index: number) {
|
||||||
|
const model = this.currentModel;
|
||||||
|
this.currentModel = {
|
||||||
|
...model,
|
||||||
|
rows: model.rows.filter((row, i) => index != i),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
beginUpdate() {
|
||||||
|
this.batchModel = this.model;
|
||||||
|
}
|
||||||
|
endUpdate() {
|
||||||
|
if (this.model != this.batchModel) {
|
||||||
|
this.dispatchModel({ type: 'set', value: this.batchModel });
|
||||||
|
this.batchModel = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// static factory({ modelState, dispatchModel }): FreeTableGrider {
|
||||||
|
// return new FreeTableGrider(modelState, dispatchModel);
|
||||||
|
// }
|
||||||
|
// static factoryDeps({ modelState, dispatchModel }) {
|
||||||
|
// return [modelState, dispatchModel];
|
||||||
|
// }
|
||||||
|
undo() {
|
||||||
|
this.dispatchModel({ type: 'undo' });
|
||||||
|
}
|
||||||
|
redo() {
|
||||||
|
this.dispatchModel({ type: 'redo' });
|
||||||
|
}
|
||||||
|
get canUndo() {
|
||||||
|
return this.modelState.canUndo;
|
||||||
|
}
|
||||||
|
get canRedo() {
|
||||||
|
return this.modelState.canRedo;
|
||||||
|
}
|
||||||
|
}
|
||||||
40
packages/web/src/freetable/MacroPreviewGrider.ts
Normal file
40
packages/web/src/freetable/MacroPreviewGrider.ts
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
import { FreeTableModel, MacroDefinition, MacroSelectedCell, runMacro } from 'dbgate-datalib';
|
||||||
|
import Grider, { GriderRowStatus } from '../datagrid/Grider';
|
||||||
|
import _ from 'lodash';
|
||||||
|
|
||||||
|
function convertToSet(row, field) {
|
||||||
|
if (!row) return null;
|
||||||
|
if (!row[field]) return null;
|
||||||
|
if (_.isSet(row[field])) return row[field];
|
||||||
|
return new Set(row[field]);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default class MacroPreviewGrider extends Grider {
|
||||||
|
model: FreeTableModel;
|
||||||
|
_errors: string[] = [];
|
||||||
|
constructor(model: FreeTableModel, macro: MacroDefinition, macroArgs: {}, selectedCells: MacroSelectedCell[]) {
|
||||||
|
super();
|
||||||
|
this.model = runMacro(macro, macroArgs, model, true, selectedCells, this._errors);
|
||||||
|
}
|
||||||
|
|
||||||
|
get errors() {
|
||||||
|
return this._errors;
|
||||||
|
}
|
||||||
|
|
||||||
|
getRowStatus(index): GriderRowStatus {
|
||||||
|
const row = this.model.rows[index];
|
||||||
|
return {
|
||||||
|
status: (row && row.__rowStatus) || 'regular',
|
||||||
|
modifiedFields: convertToSet(row, '__modifiedFields'),
|
||||||
|
insertedFields: convertToSet(row, '__insertedFields'),
|
||||||
|
deletedFields: convertToSet(row, '__deletedFields'),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
getRowData(index: any) {
|
||||||
|
return this.model.rows[index];
|
||||||
|
}
|
||||||
|
get rowCount() {
|
||||||
|
return this.model.rows.length;
|
||||||
|
}
|
||||||
|
}
|
||||||
273
packages/web/src/freetable/macros.js
Normal file
273
packages/web/src/freetable/macros.js
Normal file
@@ -0,0 +1,273 @@
|
|||||||
|
const macros = [
|
||||||
|
{
|
||||||
|
title: 'Remove diacritics',
|
||||||
|
name: 'removeDiacritics',
|
||||||
|
group: 'Text',
|
||||||
|
description: 'Removes diacritics from selected cells',
|
||||||
|
type: 'transformValue',
|
||||||
|
code: `return modules.lodash.deburr(value)`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Search & replace text',
|
||||||
|
name: 'stringReplace',
|
||||||
|
group: 'Text',
|
||||||
|
description: 'Search & replace text or regular expression',
|
||||||
|
type: 'transformValue',
|
||||||
|
args: [
|
||||||
|
{
|
||||||
|
type: 'text',
|
||||||
|
label: 'Find',
|
||||||
|
name: 'find',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'text',
|
||||||
|
label: 'Replace with',
|
||||||
|
name: 'replace',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'checkbox',
|
||||||
|
label: 'Case sensitive',
|
||||||
|
name: 'caseSensitive',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'checkbox',
|
||||||
|
label: 'Regular expression',
|
||||||
|
name: 'isRegex',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
code: `
|
||||||
|
const rtext = args.isRegex ? args.find : modules.lodash.escapeRegExp(args.find);
|
||||||
|
const rflags = args.caseSensitive ? 'g' : 'ig';
|
||||||
|
return value ? value.toString().replace(new RegExp(rtext, rflags), args.replace || '') : value
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Change text case',
|
||||||
|
name: 'changeTextCase',
|
||||||
|
group: 'Text',
|
||||||
|
description: 'Uppercase, lowercase and other case functions',
|
||||||
|
type: 'transformValue',
|
||||||
|
args: [
|
||||||
|
{
|
||||||
|
type: 'select',
|
||||||
|
options: ['toUpper', 'toLower', 'lowerCase', 'upperCase', 'kebabCase', 'snakeCase', 'camelCase', 'startCase'],
|
||||||
|
label: 'Type',
|
||||||
|
name: 'type',
|
||||||
|
default: 'toUpper',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
code: `return modules.lodash[args.type](value)`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Row index',
|
||||||
|
name: 'rowIndex',
|
||||||
|
group: 'Tools',
|
||||||
|
description: 'Index of row from 1 (autoincrement)',
|
||||||
|
type: 'transformValue',
|
||||||
|
code: `return rowIndex + 1`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Generate UUID',
|
||||||
|
name: 'uuidv1',
|
||||||
|
group: 'Tools',
|
||||||
|
description: 'Generate unique identifier',
|
||||||
|
type: 'transformValue',
|
||||||
|
args: [
|
||||||
|
{
|
||||||
|
type: 'select',
|
||||||
|
options: [
|
||||||
|
{ value: 'uuidv1', name: 'V1 - from timestamp' },
|
||||||
|
{ value: 'uuidv4', name: 'V4 - random generated' },
|
||||||
|
],
|
||||||
|
label: 'Version',
|
||||||
|
name: 'version',
|
||||||
|
default: 'uuidv1',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
code: `return modules[args.version]()`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Current date',
|
||||||
|
name: 'currentDate',
|
||||||
|
group: 'Tools',
|
||||||
|
description: 'Gets current date',
|
||||||
|
type: 'transformValue',
|
||||||
|
args: [
|
||||||
|
{
|
||||||
|
type: 'text',
|
||||||
|
label: 'Format',
|
||||||
|
name: 'format',
|
||||||
|
default: 'YYYY-MM-DD HH:mm:ss',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
code: `return modules.moment().format(args.format)`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Duplicate rows',
|
||||||
|
name: 'duplicateRows',
|
||||||
|
group: 'Tools',
|
||||||
|
description: 'Duplicate selected rows',
|
||||||
|
type: 'transformRows',
|
||||||
|
code: `
|
||||||
|
const selectedRowIndexes = modules.lodash.uniq(selectedCells.map(x => x.row));
|
||||||
|
const selectedRows = modules.lodash.groupBy(selectedCells, 'row');
|
||||||
|
const maxIndex = modules.lodash.max(selectedRowIndexes);
|
||||||
|
return [
|
||||||
|
...rows.slice(0, maxIndex + 1),
|
||||||
|
...selectedRowIndexes.map(index => ({
|
||||||
|
...modules.lodash.pick(rows[index], selectedRows[index].map(x => x.column)),
|
||||||
|
__rowStatus: 'inserted',
|
||||||
|
})),
|
||||||
|
...rows.slice(maxIndex + 1),
|
||||||
|
]
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Delete empty rows',
|
||||||
|
name: 'deleteEmptyRows',
|
||||||
|
group: 'Tools',
|
||||||
|
description: 'Delete empty rows - rows with all values null or empty string',
|
||||||
|
type: 'transformRows',
|
||||||
|
code: `
|
||||||
|
return rows.map(row => {
|
||||||
|
if (cols.find(col => row[col])) return row;
|
||||||
|
return {
|
||||||
|
...row,
|
||||||
|
__rowStatus: 'deleted',
|
||||||
|
};
|
||||||
|
})
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Duplicate columns',
|
||||||
|
name: 'duplicateColumns',
|
||||||
|
group: 'Tools',
|
||||||
|
description: 'Duplicate selected columns',
|
||||||
|
type: 'transformData',
|
||||||
|
code: `
|
||||||
|
const selectedColumnNames = modules.lodash.uniq(selectedCells.map(x => x.column));
|
||||||
|
const selectedRowIndexes = modules.lodash.uniq(selectedCells.map(x => x.row));
|
||||||
|
const addedColumnNames = selectedColumnNames.map(col => (args.prefix || '') + col + (args.postfix || ''));
|
||||||
|
const resultRows = rows.map((row, rowIndex) => ({
|
||||||
|
...row,
|
||||||
|
...(selectedRowIndexes.includes(rowIndex) ? modules.lodash.fromPairs(selectedColumnNames.map(col => [(args.prefix || '') + col + (args.postfix || ''), row[col]])) : {}),
|
||||||
|
__insertedFields: addedColumnNames,
|
||||||
|
}));
|
||||||
|
const resultCols = [
|
||||||
|
...cols,
|
||||||
|
...addedColumnNames,
|
||||||
|
];
|
||||||
|
return {
|
||||||
|
rows: resultRows,
|
||||||
|
cols: resultCols,
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
args: [
|
||||||
|
{
|
||||||
|
type: 'text',
|
||||||
|
label: 'Prefix',
|
||||||
|
name: 'prefix',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'text',
|
||||||
|
label: 'Postfix',
|
||||||
|
name: 'postfix',
|
||||||
|
default: '_copy',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Extract date fields',
|
||||||
|
name: 'extractDateFields',
|
||||||
|
group: 'Tools',
|
||||||
|
description: 'Extract yaear, month, day and other date/time fields from selection and adds it as new columns',
|
||||||
|
type: 'transformData',
|
||||||
|
code: `
|
||||||
|
const selectedColumnNames = modules.lodash.uniq(selectedCells.map(x => x.column));
|
||||||
|
const selectedRowIndexes = modules.lodash.uniq(selectedCells.map(x => x.row));
|
||||||
|
const addedColumnNames = modules.lodash.compact([args.year, args.month, args.day, args.hour, args.minute, args.second]);
|
||||||
|
const selectedRows = modules.lodash.groupBy(selectedCells, 'row');
|
||||||
|
const resultRows = rows.map((row, rowIndex) => {
|
||||||
|
if (!selectedRowIndexes.includes(rowIndex)) return {
|
||||||
|
...row,
|
||||||
|
__insertedFields: addedColumnNames,
|
||||||
|
};
|
||||||
|
let mom = null;
|
||||||
|
for(const cell of selectedRows[rowIndex]) {
|
||||||
|
const m = modules.moment(row[cell.column]);
|
||||||
|
if (m.isValid()) {
|
||||||
|
mom = m;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!mom) return {
|
||||||
|
...row,
|
||||||
|
__insertedFields: addedColumnNames,
|
||||||
|
};
|
||||||
|
|
||||||
|
const fields = {
|
||||||
|
[args.year]: mom.year(),
|
||||||
|
[args.month]: mom.month() + 1,
|
||||||
|
[args.day]: mom.day(),
|
||||||
|
[args.hour]: mom.hour(),
|
||||||
|
[args.minute]: mom.minute(),
|
||||||
|
[args.second]: mom.second(),
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
...row,
|
||||||
|
...modules.lodash.pick(fields, addedColumnNames),
|
||||||
|
__insertedFields: addedColumnNames,
|
||||||
|
}
|
||||||
|
});
|
||||||
|
const resultCols = [
|
||||||
|
...cols,
|
||||||
|
...addedColumnNames,
|
||||||
|
];
|
||||||
|
return {
|
||||||
|
rows: resultRows,
|
||||||
|
cols: resultCols,
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
args: [
|
||||||
|
{
|
||||||
|
type: 'text',
|
||||||
|
label: 'Year name',
|
||||||
|
name: 'year',
|
||||||
|
default: 'year',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'text',
|
||||||
|
label: 'Month name',
|
||||||
|
name: 'month',
|
||||||
|
default: 'month',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'text',
|
||||||
|
label: 'Day name',
|
||||||
|
name: 'day',
|
||||||
|
default: 'day',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'text',
|
||||||
|
label: 'Hour name',
|
||||||
|
name: 'hour',
|
||||||
|
default: 'hour',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'text',
|
||||||
|
label: 'Minute name',
|
||||||
|
name: 'minute',
|
||||||
|
default: 'minute',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'text',
|
||||||
|
label: 'Second name',
|
||||||
|
name: 'second',
|
||||||
|
default: 'second',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
export default macros;
|
||||||
@@ -19,7 +19,7 @@ function getParsedLocalStorage(key) {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function useEditorData({ tabid, reloadToken = 0, loadFromArgs = null }) {
|
export default function useEditorData({ tabid, reloadToken = 0, loadFromArgs = null, onInitialData = null }) {
|
||||||
const localStorageKey = `tabdata_editor_${tabid}`;
|
const localStorageKey = `tabdata_editor_${tabid}`;
|
||||||
let changeCounter = 0;
|
let changeCounter = 0;
|
||||||
let savedCounter = 0;
|
let savedCounter = 0;
|
||||||
@@ -32,7 +32,6 @@ export default function useEditorData({ tabid, reloadToken = 0, loadFromArgs = n
|
|||||||
|
|
||||||
const editorValue = derived(editorState, $state => $state.value);
|
const editorValue = derived(editorState, $state => $state.value);
|
||||||
|
|
||||||
let initialData = null;
|
|
||||||
let value = null;
|
let value = null;
|
||||||
|
|
||||||
// const valueRef = React.useRef(null);
|
// const valueRef = React.useRef(null);
|
||||||
@@ -49,8 +48,8 @@ export default function useEditorData({ tabid, reloadToken = 0, loadFromArgs = n
|
|||||||
...x,
|
...x,
|
||||||
value: init,
|
value: init,
|
||||||
}));
|
}));
|
||||||
|
if (onInitialData) onInitialData(init);
|
||||||
value = init;
|
value = init;
|
||||||
initialData = init;
|
|
||||||
// mark as not saved
|
// mark as not saved
|
||||||
changeCounter += 1;
|
changeCounter += 1;
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
@@ -68,11 +67,11 @@ export default function useEditorData({ tabid, reloadToken = 0, loadFromArgs = n
|
|||||||
...x,
|
...x,
|
||||||
value: initFallback,
|
value: initFallback,
|
||||||
}));
|
}));
|
||||||
|
if (onInitialData) onInitialData(initFallback);
|
||||||
value = initFallback;
|
value = initFallback;
|
||||||
// move to local forage
|
// move to local forage
|
||||||
await localforage.setItem(localStorageKey, initFallback);
|
await localforage.setItem(localStorageKey, initFallback);
|
||||||
localStorage.removeItem(localStorageKey);
|
localStorage.removeItem(localStorageKey);
|
||||||
initialData = initFallback;
|
|
||||||
} else {
|
} else {
|
||||||
const init = await localforage.getItem(localStorageKey);
|
const init = await localforage.getItem(localStorageKey);
|
||||||
if (init) {
|
if (init) {
|
||||||
@@ -80,8 +79,8 @@ export default function useEditorData({ tabid, reloadToken = 0, loadFromArgs = n
|
|||||||
...x,
|
...x,
|
||||||
value: init,
|
value: init,
|
||||||
}));
|
}));
|
||||||
|
if (onInitialData) onInitialData(init);
|
||||||
value = init;
|
value = init;
|
||||||
initialData = init;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -140,7 +139,6 @@ export default function useEditorData({ tabid, reloadToken = 0, loadFromArgs = n
|
|||||||
editorState,
|
editorState,
|
||||||
editorValue,
|
editorValue,
|
||||||
setEditorData,
|
setEditorData,
|
||||||
initialData,
|
|
||||||
saveToStorage,
|
saveToStorage,
|
||||||
saveToStorageSync,
|
saveToStorageSync,
|
||||||
initialLoad,
|
initialLoad,
|
||||||
|
|||||||
49
packages/web/src/tabs/FreeTableTab.svelte
Normal file
49
packages/web/src/tabs/FreeTableTab.svelte
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import { createFreeTableModel } from 'dbgate-datalib';
|
||||||
|
import ErrorInfo from '../elements/ErrorInfo.svelte';
|
||||||
|
import LoadingInfo from '../elements/LoadingInfo.svelte';
|
||||||
|
|
||||||
|
import FreeTableGrid from '../freetable/FreeTableGrid.svelte';
|
||||||
|
import useEditorData from '../query/useEditorData';
|
||||||
|
import axiosInstance from '../utility/axiosInstance';
|
||||||
|
import createUndoReducer from '../utility/createUndoReducer';
|
||||||
|
import useGridConfig from '../utility/useGridConfig';
|
||||||
|
|
||||||
|
export let tabid;
|
||||||
|
export let initialArgs;
|
||||||
|
|
||||||
|
const config = useGridConfig(tabid);
|
||||||
|
const [modelState, dispatchModel] = createUndoReducer(createFreeTableModel());
|
||||||
|
|
||||||
|
const { setEditorData, editorState } = useEditorData({
|
||||||
|
tabid,
|
||||||
|
loadFromArgs:
|
||||||
|
initialArgs && initialArgs.functionName
|
||||||
|
? () => axiosInstance.post('runners/load-reader', initialArgs).then(x => x.data)
|
||||||
|
: null,
|
||||||
|
onInitialData: value => {
|
||||||
|
dispatchModel({ type: 'reset', value });
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
$: isLoading = $editorState.isLoading;
|
||||||
|
$: errorMessage = $editorState.errorMessage;
|
||||||
|
|
||||||
|
$: setEditorData($modelState.value);
|
||||||
|
|
||||||
|
function handleSave() {}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
{#if isLoading}
|
||||||
|
<LoadingInfo wrapper message="Loading data" />
|
||||||
|
{:else if errorMessage}
|
||||||
|
<ErrorInfo message={errorMessage} />
|
||||||
|
{:else}
|
||||||
|
<FreeTableGrid
|
||||||
|
config={$config}
|
||||||
|
setConfig={config.update}
|
||||||
|
modelState={$modelState}
|
||||||
|
{dispatchModel}
|
||||||
|
onSave={handleSave}
|
||||||
|
/>
|
||||||
|
{/if}
|
||||||
@@ -5,7 +5,7 @@ import * as QueryTab from './QueryTab.svelte';
|
|||||||
import * as ShellTab from './ShellTab.svelte';
|
import * as ShellTab from './ShellTab.svelte';
|
||||||
// import InfoPageTab from './InfoPageTab';
|
// import InfoPageTab from './InfoPageTab';
|
||||||
import * as ArchiveFileTab from './ArchiveFileTab.svelte';
|
import * as ArchiveFileTab from './ArchiveFileTab.svelte';
|
||||||
// import FreeTableTab from './FreeTableTab';
|
import * as FreeTableTab from './FreeTableTab.svelte';
|
||||||
// import PluginTab from './PluginTab';
|
// import PluginTab from './PluginTab';
|
||||||
// import ChartTab from './ChartTab';
|
// import ChartTab from './ChartTab';
|
||||||
import * as MarkdownEditorTab from './MarkdownEditorTab.svelte';
|
import * as MarkdownEditorTab from './MarkdownEditorTab.svelte';
|
||||||
@@ -22,7 +22,7 @@ export default {
|
|||||||
// InfoPageTab,
|
// InfoPageTab,
|
||||||
ShellTab,
|
ShellTab,
|
||||||
ArchiveFileTab,
|
ArchiveFileTab,
|
||||||
// FreeTableTab,
|
FreeTableTab,
|
||||||
// PluginTab,
|
// PluginTab,
|
||||||
// ChartTab,
|
// ChartTab,
|
||||||
MarkdownEditorTab,
|
MarkdownEditorTab,
|
||||||
|
|||||||
@@ -348,10 +348,10 @@ export function useArchiveFiles(args) {
|
|||||||
return useCore(archiveFilesLoader, args);
|
return useCore(archiveFilesLoader, args);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getArchiveFolders(args={}) {
|
export function getArchiveFolders(args = {}) {
|
||||||
return getCore(archiveFoldersLoader, args);
|
return getCore(archiveFoldersLoader, args);
|
||||||
}
|
}
|
||||||
export function useArchiveFolders(args={}) {
|
export function useArchiveFolders(args = {}) {
|
||||||
return useCore(archiveFoldersLoader, args);
|
return useCore(archiveFoldersLoader, args);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user