mirror of
https://github.com/DeNNiiInc/dbgate.git
synced 2026-04-19 17:46:00 +00:00
chart initial import
This commit is contained in:
34
packages/web/src/charts/ChartCore.svelte
Normal file
34
packages/web/src/charts/ChartCore.svelte
Normal file
@@ -0,0 +1,34 @@
|
||||
<script lang="ts">
|
||||
import { onMount, afterUpdate, onDestroy } from 'svelte';
|
||||
import Chart from 'chart.js';
|
||||
|
||||
export let data;
|
||||
export let type = 'line';
|
||||
export let options = {};
|
||||
export let plugins = {};
|
||||
|
||||
let chart = null;
|
||||
let domChart;
|
||||
|
||||
onMount(() => {
|
||||
chart = new Chart(domChart, {
|
||||
type,
|
||||
data,
|
||||
options,
|
||||
plugins,
|
||||
});
|
||||
});
|
||||
afterUpdate(() => {
|
||||
if (!chart) return;
|
||||
chart.data = data;
|
||||
chart.type = type;
|
||||
chart.options = options;
|
||||
chart.plugins = plugins;
|
||||
chart.update();
|
||||
});
|
||||
onDestroy(() => {
|
||||
chart = null;
|
||||
});
|
||||
</script>
|
||||
|
||||
<canvas bind:this={domChart} {...$$restProps} />
|
||||
169
packages/web/src/charts/ChartEditor.svelte
Normal file
169
packages/web/src/charts/ChartEditor.svelte
Normal file
@@ -0,0 +1,169 @@
|
||||
<script lang="ts">
|
||||
import AboutModal from '../modals/AboutModal.svelte';
|
||||
import { presetPrimaryColors } from '@ant-design/colors';
|
||||
import { startCase } from 'lodash';
|
||||
import FormProviderCore from '../forms/FormProviderCore.svelte';
|
||||
import HorizontalSplitter from '../elements/HorizontalSplitter.svelte';
|
||||
import WidgetColumnBar from '../widgets/WidgetColumnBar.svelte';
|
||||
import WidgetColumnBarItem from '../widgets/WidgetColumnBarItem.svelte';
|
||||
import ManagerInnerContainer from '../elements/ManagerInnerContainer.svelte';
|
||||
import FormSelectField from '../forms/FormSelectField.svelte';
|
||||
import FormTextField from '../forms/FormTextField.svelte';
|
||||
import FormCheckboxField from '../forms/FormCheckboxField.svelte';
|
||||
import { writable } from 'svelte/store';
|
||||
import FormFieldTemplateTiny from '../forms/FormFieldTemplateTiny.svelte';
|
||||
import createRef from '../utility/createRef';
|
||||
import { getConnectionInfo } from '../utility/metadataLoaders';
|
||||
import { findEngineDriver } from 'dbgate-tools';
|
||||
import { extensions } from '../stores';
|
||||
import { loadChartData, loadChartStructure } from './chartDataLoader';
|
||||
import DataChart from './DataChart.svelte';
|
||||
import _ from 'lodash';
|
||||
|
||||
export let data;
|
||||
export let config;
|
||||
export let setConfig;
|
||||
export let conid;
|
||||
export let database;
|
||||
export let sql;
|
||||
|
||||
let availableColumnNames = [];
|
||||
let error = null;
|
||||
let loadedData = null;
|
||||
|
||||
const getDriver = async () => {
|
||||
const conn = await getConnectionInfo({ conid });
|
||||
if (!conn) return;
|
||||
const driver = findEngineDriver(conn, $extensions);
|
||||
return driver;
|
||||
};
|
||||
|
||||
const handleLoadColumns = async () => {
|
||||
const driver = await getDriver();
|
||||
if (!driver) return;
|
||||
try {
|
||||
const columns = await loadChartStructure(driver, conid, database, sql);
|
||||
availableColumnNames = columns;
|
||||
} catch (err) {
|
||||
error = err.message;
|
||||
}
|
||||
};
|
||||
|
||||
const handleLoadData = async () => {
|
||||
const driver = await getDriver();
|
||||
if (!driver) return;
|
||||
const loaded = await loadChartData(driver, conid, database, sql, config);
|
||||
if (!loaded) return;
|
||||
const { columns, rows } = loaded;
|
||||
loadedData = {
|
||||
structure: columns,
|
||||
rows,
|
||||
};
|
||||
};
|
||||
|
||||
const configStore = writable(config);
|
||||
const changingRef = createRef(false);
|
||||
$: {
|
||||
if (!changingRef.get()) {
|
||||
$configStore = config;
|
||||
}
|
||||
}
|
||||
// $: {
|
||||
// changingRef.set(true);
|
||||
// setConfig($configStore);
|
||||
// changingRef.set(false);
|
||||
// }
|
||||
$: {
|
||||
$extensions;
|
||||
if (sql && conid && database) {
|
||||
handleLoadColumns();
|
||||
}
|
||||
}
|
||||
$: {
|
||||
if (data) {
|
||||
availableColumnNames = data.structure.columns.map(x => x.columnName);
|
||||
}
|
||||
}
|
||||
$: {
|
||||
if (config.labelColumn && sql && conid && database) {
|
||||
handleLoadData();
|
||||
}
|
||||
}
|
||||
|
||||
let managerSize;
|
||||
</script>
|
||||
|
||||
<FormProviderCore values={configStore} template={FormFieldTemplateTiny}>
|
||||
<HorizontalSplitter initialValue="300px" bind:size={managerSize}>
|
||||
<div class="left" slot="1">
|
||||
<WidgetColumnBar>
|
||||
<WidgetColumnBarItem title="Style" name="style" height="40%">
|
||||
<ManagerInnerContainer width={managerSize}>
|
||||
<FormSelectField
|
||||
label="Chart type"
|
||||
name="chartType"
|
||||
isNative
|
||||
options={[
|
||||
{ value: 'bar', label: 'Bar' },
|
||||
{ value: 'line', label: 'Line' },
|
||||
{ value: 'pie', label: 'Pie' },
|
||||
{ value: 'polarArea', label: 'Polar area' },
|
||||
]}
|
||||
/>
|
||||
<FormTextField label="Color set" name="colorSeed" />
|
||||
<FormSelectField
|
||||
label="Truncate from"
|
||||
name="truncateFrom"
|
||||
isNative
|
||||
options={[
|
||||
{ value: 'begin', label: 'Begin' },
|
||||
{ value: 'end', label: 'End (most recent data for datetime)' },
|
||||
]}
|
||||
/>
|
||||
<FormTextField label="Truncate limit" name="truncateLimit" />
|
||||
<FormCheckboxField label="Show relative values" name="showRelativeValues" />
|
||||
</ManagerInnerContainer>
|
||||
</WidgetColumnBarItem>
|
||||
<WidgetColumnBarItem title="Data" name="data">
|
||||
<ManagerInnerContainer width={managerSize}>
|
||||
{#if availableColumnNames.length > 0}
|
||||
<FormSelectField
|
||||
label="Label column"
|
||||
name="labelColumn"
|
||||
isNative
|
||||
options={[{ value: '' }, ...availableColumnNames.map(col => ({ value: col, label: col }))]}
|
||||
/>
|
||||
{/if}
|
||||
|
||||
{#each availableColumnNames as col (col)}
|
||||
<FormCheckboxField label={col} name={`dataColumn_${col}`} />
|
||||
{#if config[`dataColumn_${col}`]}
|
||||
<FormSelectField
|
||||
label="Color"
|
||||
name={`dataColumnColor_${col}`}
|
||||
isNative
|
||||
options={[
|
||||
{ value: '', label: 'Random' },
|
||||
..._.keys(presetPrimaryColors).map(color => ({ value: color, label: _.startCase(color) })),
|
||||
]}
|
||||
/>
|
||||
{/if}
|
||||
{/each}
|
||||
</ManagerInnerContainer>
|
||||
</WidgetColumnBarItem>
|
||||
</WidgetColumnBar>
|
||||
</div>
|
||||
|
||||
<svelte:fragment slot="2">
|
||||
<DataChart data={data || loadedData} />
|
||||
</svelte:fragment>
|
||||
</HorizontalSplitter>
|
||||
</FormProviderCore>
|
||||
|
||||
<style>
|
||||
.left {
|
||||
background-color: var(--theme-bg-0);
|
||||
display: flex;
|
||||
flex: 1;
|
||||
}
|
||||
</style>
|
||||
137
packages/web/src/charts/DataChart.svelte
Normal file
137
packages/web/src/charts/DataChart.svelte
Normal file
@@ -0,0 +1,137 @@
|
||||
<script lang="ts" context="module">
|
||||
function getTimeAxis(labels) {
|
||||
const res = [];
|
||||
for (const label of labels) {
|
||||
const parsed = moment(label);
|
||||
if (!parsed.isValid()) return null;
|
||||
const iso = parsed.toISOString();
|
||||
if (iso < '1850-01-01T00:00:00' || iso > '2150-01-01T00:00:00') return null;
|
||||
res.push(parsed);
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
function getLabels(labelValues, timeAxis, chartType) {
|
||||
if (!timeAxis) return labelValues;
|
||||
if (chartType === 'line') return timeAxis.map(x => x.toDate());
|
||||
return timeAxis.map(x => x.format('D. M. YYYY'));
|
||||
}
|
||||
|
||||
function getOptions(timeAxis, chartType) {
|
||||
if (timeAxis && chartType === 'line') {
|
||||
return {
|
||||
scales: {
|
||||
xAxes: [
|
||||
{
|
||||
type: 'time',
|
||||
distribution: 'linear',
|
||||
|
||||
time: {
|
||||
tooltipFormat: 'D. M. YYYY HH:mm',
|
||||
displayFormats: {
|
||||
millisecond: 'HH:mm:ss.SSS',
|
||||
second: 'HH:mm:ss',
|
||||
minute: 'HH:mm',
|
||||
hour: 'D.M hA',
|
||||
day: 'D. M.',
|
||||
week: 'D. M. YYYY',
|
||||
month: 'MM-YYYY',
|
||||
quarter: '[Q]Q - YYYY',
|
||||
year: 'YYYY',
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
function createChartData(freeData, labelColumn, dataColumns, colorSeed, chartType, dataColumnColors) {
|
||||
if (!freeData || !labelColumn || !dataColumns || !freeData.rows || dataColumns.length == 0) return [{}, {}];
|
||||
const colors = randomcolor({
|
||||
count: _.max([freeData.rows.length, dataColumns.length, 1]),
|
||||
seed: colorSeed,
|
||||
});
|
||||
let backgroundColor = null;
|
||||
let borderColor = null;
|
||||
const labelValues = freeData.rows.map(x => x[labelColumn]);
|
||||
const timeAxis = getTimeAxis(labelValues);
|
||||
const labels = getLabels(labelValues, timeAxis, chartType);
|
||||
const res = {
|
||||
labels,
|
||||
datasets: dataColumns.map((dataColumn, columnIndex) => {
|
||||
if (chartType == 'line' || chartType == 'bar') {
|
||||
const color = dataColumnColors[dataColumn];
|
||||
if (color) {
|
||||
backgroundColor = presetPalettes[color][4] + '80';
|
||||
borderColor = presetPalettes[color][7];
|
||||
} else {
|
||||
backgroundColor = colors[columnIndex] + '80';
|
||||
borderColor = colors[columnIndex];
|
||||
}
|
||||
} else {
|
||||
backgroundColor = colors;
|
||||
}
|
||||
|
||||
return {
|
||||
label: dataColumn,
|
||||
data: freeData.rows.map(row => row[dataColumn]),
|
||||
backgroundColor,
|
||||
borderColor,
|
||||
borderWidth: 1,
|
||||
};
|
||||
}),
|
||||
};
|
||||
|
||||
const options = getOptions(timeAxis, chartType);
|
||||
return [res, options];
|
||||
}
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
import _ from 'lodash';
|
||||
import randomcolor from 'randomcolor';
|
||||
import moment from 'moment';
|
||||
import ChartCore from './ChartCore.svelte';
|
||||
import { getFormContext } from '../forms/FormProviderCore.svelte';
|
||||
import { generate, presetPalettes, presetDarkPalettes, presetPrimaryColors } from '@ant-design/colors';
|
||||
import { extractDataColumnColors, extractDataColumns } from './chartDataLoader';
|
||||
|
||||
export let data;
|
||||
|
||||
const { values } = getFormContext();
|
||||
|
||||
let clientWidth;
|
||||
let clientHeight;
|
||||
|
||||
$: dataColumns = extractDataColumns(values);
|
||||
$: dataColumnColors = extractDataColumnColors(values, dataColumns);
|
||||
|
||||
$: chartData = createChartData(
|
||||
data,
|
||||
$values.labelColumn,
|
||||
dataColumns,
|
||||
$values.colorSeed || '5',
|
||||
$values.chartType,
|
||||
dataColumnColors
|
||||
);
|
||||
</script>
|
||||
|
||||
<div class="wrapper" bind:clientWidth bind:clientHeight>
|
||||
<ChartCore
|
||||
width={clientWidth}
|
||||
height={clientHeight}
|
||||
data={chartData[0]}
|
||||
type={$values.chartType}
|
||||
options={chartData[1]}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.wrapper {
|
||||
flex: 1;
|
||||
overflow: hidden;
|
||||
}
|
||||
</style>
|
||||
122
packages/web/src/charts/chartDataLoader.ts
Normal file
122
packages/web/src/charts/chartDataLoader.ts
Normal file
@@ -0,0 +1,122 @@
|
||||
import { dumpSqlSelect, Select } from 'dbgate-sqltree';
|
||||
import { EngineDriver } from 'dbgate-types';
|
||||
import axiosInstance from '../utility/axiosInstance';
|
||||
import _ from 'lodash';
|
||||
|
||||
export async function loadChartStructure(driver: EngineDriver, conid, database, sql) {
|
||||
const select: Select = {
|
||||
commandType: 'select',
|
||||
selectAll: true,
|
||||
topRecords: 1,
|
||||
from: {
|
||||
subQueryString: sql,
|
||||
alias: 'subq',
|
||||
},
|
||||
};
|
||||
|
||||
const dmp = driver.createDumper();
|
||||
dumpSqlSelect(dmp, select);
|
||||
const resp = await axiosInstance.post('database-connections/query-data', { conid, database, sql: dmp.s });
|
||||
if (resp.data.errorMessage) throw new Error(resp.data.errorMessage);
|
||||
return resp.data.columns.map(x => x.columnName);
|
||||
}
|
||||
|
||||
export async function loadChartData(driver: EngineDriver, conid, database, sql, config) {
|
||||
const dataColumns = extractDataColumns(config);
|
||||
const { labelColumn, truncateFrom, truncateLimit, showRelativeValues } = config;
|
||||
if (!labelColumn || !dataColumns || dataColumns.length == 0) return null;
|
||||
|
||||
const select: Select = {
|
||||
commandType: 'select',
|
||||
|
||||
columns: [
|
||||
{
|
||||
exprType: 'column',
|
||||
source: { alias: 'subq' },
|
||||
columnName: labelColumn,
|
||||
alias: labelColumn,
|
||||
},
|
||||
// @ts-ignore
|
||||
...dataColumns.map(columnName => ({
|
||||
exprType: 'call',
|
||||
func: 'SUM',
|
||||
args: [
|
||||
{
|
||||
exprType: 'column',
|
||||
columnName,
|
||||
source: { alias: 'subq' },
|
||||
},
|
||||
],
|
||||
alias: columnName,
|
||||
})),
|
||||
],
|
||||
topRecords: truncateLimit || 100,
|
||||
from: {
|
||||
subQueryString: sql,
|
||||
alias: 'subq',
|
||||
},
|
||||
groupBy: [
|
||||
{
|
||||
exprType: 'column',
|
||||
source: { alias: 'subq' },
|
||||
columnName: labelColumn,
|
||||
},
|
||||
],
|
||||
orderBy: [
|
||||
{
|
||||
exprType: 'column',
|
||||
source: { alias: 'subq' },
|
||||
columnName: labelColumn,
|
||||
direction: truncateFrom == 'end' ? 'DESC' : 'ASC',
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const dmp = driver.createDumper();
|
||||
dumpSqlSelect(dmp, select);
|
||||
const resp = await axiosInstance.post('database-connections/query-data', { conid, database, sql: dmp.s });
|
||||
let { rows, columns } = resp.data;
|
||||
if (truncateFrom == 'end' && rows) {
|
||||
rows = _.reverse([...rows]);
|
||||
}
|
||||
if (showRelativeValues) {
|
||||
const maxValues = dataColumns.map(col => _.max(rows.map(row => row[col])));
|
||||
for (const [col, max] of _.zip(dataColumns, maxValues)) {
|
||||
if (!max) continue;
|
||||
if (!_.isNumber(max)) continue;
|
||||
if (!(max > 0)) continue;
|
||||
rows = rows.map(row => ({
|
||||
...row,
|
||||
[col]: (row[col] / max) * 100,
|
||||
}));
|
||||
// columns = columns.map((x) => {
|
||||
// if (x.columnName == col) {
|
||||
// return { columnName: `${col} %` };
|
||||
// }
|
||||
// return x;
|
||||
// });
|
||||
}
|
||||
}
|
||||
return {
|
||||
columns,
|
||||
rows,
|
||||
};
|
||||
}
|
||||
|
||||
export function extractDataColumns(values) {
|
||||
const dataColumns = [];
|
||||
for (const key in values) {
|
||||
if (key.startsWith('dataColumn_') && values[key]) {
|
||||
dataColumns.push(key.substring('dataColumn_'.length));
|
||||
}
|
||||
}
|
||||
return dataColumns;
|
||||
}
|
||||
export function extractDataColumnColors(values, dataColumns) {
|
||||
const res = {};
|
||||
for (const column of dataColumns) {
|
||||
const color = values[`dataColumnColor_${column}`];
|
||||
if (color) res[column] = color;
|
||||
}
|
||||
return res;
|
||||
}
|
||||
@@ -12,11 +12,13 @@
|
||||
import DesignerReference from './DesignerReference.svelte';
|
||||
import { writable } from 'svelte/store';
|
||||
import { tick } from 'svelte';
|
||||
import contextMenu from '../utility/contextMenu';
|
||||
|
||||
export let value;
|
||||
export let onChange;
|
||||
export let conid;
|
||||
export let database;
|
||||
// export let menu;
|
||||
|
||||
let domCanvas;
|
||||
|
||||
|
||||
40
packages/web/src/forms/FormFieldTemplateTiny.svelte
Normal file
40
packages/web/src/forms/FormFieldTemplateTiny.svelte
Normal file
@@ -0,0 +1,40 @@
|
||||
<script lang="ts">
|
||||
export let type;
|
||||
export let label;
|
||||
export let noMargin;
|
||||
export let disabled = false;
|
||||
export let labelProps: any = {};
|
||||
</script>
|
||||
|
||||
<div class="row">
|
||||
{#if type == 'checkbox'}
|
||||
<slot />
|
||||
<span {...labelProps} on:click={labelProps.onClick} class:disabled>{label}</span>
|
||||
{:else}
|
||||
<div class="label" {...labelProps} on:click={labelProps.onClick}>
|
||||
<div class="label">
|
||||
<span {...labelProps} on:click={labelProps.onClick} class:disabled>{label}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="value">
|
||||
<slot />
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.row {
|
||||
margin: 5px;
|
||||
}
|
||||
.label {
|
||||
margin-bottom: 3px;
|
||||
color: var(--theme-font-3);
|
||||
}
|
||||
.disabled {
|
||||
color: var(--theme-font-3);
|
||||
}
|
||||
.value {
|
||||
margin-left: 15px;
|
||||
margin-top: 3px;
|
||||
}
|
||||
</style>
|
||||
@@ -1,6 +1,6 @@
|
||||
<script lang="ts">
|
||||
import { writable } from 'svelte/store';
|
||||
import FormFieldTemplateLarge from '../modals/FormFieldTemplateLarge.svelte';
|
||||
import FormFieldTemplateLarge from './FormFieldTemplateLarge.svelte';
|
||||
import FormProviderCore from './FormProviderCore.svelte';
|
||||
|
||||
export let initialValues = {};
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
import FormFieldTemplateLarge from '../modals/FormFieldTemplateLarge.svelte';
|
||||
import FormFieldTemplateLarge from './FormFieldTemplateLarge.svelte';
|
||||
import createRef from '../utility/createRef';
|
||||
|
||||
import keycodes from '../utility/keycodes';
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
import { writable } from 'svelte/store';
|
||||
import FormArgumentList from '../forms/FormArgumentList.svelte';
|
||||
import FormProviderCore from '../forms/FormProviderCore.svelte';
|
||||
import FormFieldTemplateRow from '../modals/FormFieldTemplateRow.svelte';
|
||||
import FormFieldTemplateRow from '../forms/FormFieldTemplateRow.svelte';
|
||||
|
||||
export let args = [];
|
||||
const macroValues = getContext('macroValues');
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
import ConnectionModalDriverFields from './ConnectionModalDriverFields.svelte';
|
||||
import ConnectionModalSshTunnelFields from './ConnectionModalSshTunnelFields.svelte';
|
||||
import ConnectionModalSslFields from './ConnectionModalSslFields.svelte';
|
||||
import FormFieldTemplateLarge from './FormFieldTemplateLarge.svelte';
|
||||
import FormFieldTemplateLarge from '../forms/FormFieldTemplateLarge.svelte';
|
||||
|
||||
import ModalBase from './ModalBase.svelte';
|
||||
import { closeCurrentModal, closeModal } from './modalTools';
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
import FormProvider from '../forms/FormProvider.svelte';
|
||||
import FormSubmit from '../forms/FormSubmit.svelte';
|
||||
import FormTextFieldRaw from '../forms/FormTextFieldRaw.svelte';
|
||||
import FormFieldTemplateLarge from './FormFieldTemplateLarge.svelte';
|
||||
import FormFieldTemplateLarge from '../forms/FormFieldTemplateLarge.svelte';
|
||||
import SetFilterModal_Select from './SetFilterModal_Select.svelte';
|
||||
import ModalBase from './ModalBase.svelte';
|
||||
import { closeCurrentModal } from './modalTools';
|
||||
|
||||
56
packages/web/src/tabs/ChartTab.svelte
Normal file
56
packages/web/src/tabs/ChartTab.svelte
Normal file
@@ -0,0 +1,56 @@
|
||||
<script lang="ts">
|
||||
import _ from 'lodash';
|
||||
import ChartEditor from '../charts/ChartEditor.svelte';
|
||||
|
||||
import ErrorInfo from '../elements/ErrorInfo.svelte';
|
||||
|
||||
import LoadingInfo from '../elements/LoadingInfo.svelte';
|
||||
|
||||
import useEditorData from '../query/useEditorData';
|
||||
import createUndoReducer from '../utility/createUndoReducer';
|
||||
|
||||
export let tabid;
|
||||
export let conid;
|
||||
export let database;
|
||||
|
||||
export function getData() {
|
||||
return $editorState.value || '';
|
||||
}
|
||||
|
||||
const { editorState, editorValue, setEditorData } = useEditorData({
|
||||
tabid,
|
||||
onInitialData: value => {
|
||||
dispatchModel({ type: 'reset', value });
|
||||
},
|
||||
});
|
||||
|
||||
const [modelState, dispatchModel] = createUndoReducer({
|
||||
tables: [],
|
||||
references: [],
|
||||
columns: [],
|
||||
});
|
||||
|
||||
$: setEditorData($modelState.value);
|
||||
|
||||
const setConfig = config =>
|
||||
// @ts-ignore
|
||||
dispatchModel({
|
||||
type: 'compute',
|
||||
compute: v => ({ ...v, config: _.isFunction(config) ? config(v.config) : config }),
|
||||
});
|
||||
</script>
|
||||
|
||||
{#if $editorState.isLoading}
|
||||
<LoadingInfo wrapper message="Loading data" />
|
||||
{:else if $editorState.errorMessage}
|
||||
<ErrorInfo message={$editorState.errorMessage} />
|
||||
{:else}
|
||||
<ChartEditor
|
||||
data={$modelState.value && $modelState.value.data}
|
||||
config={$modelState.value ? $modelState.value.config || {} : {}}
|
||||
{setConfig}
|
||||
sql={$modelState.value && $modelState.value.sql}
|
||||
{conid}
|
||||
{database}
|
||||
/>
|
||||
{/if}
|
||||
@@ -7,7 +7,7 @@ import * as ShellTab from './ShellTab.svelte';
|
||||
import * as ArchiveFileTab from './ArchiveFileTab.svelte';
|
||||
import * as FreeTableTab from './FreeTableTab.svelte';
|
||||
// import PluginTab from './PluginTab';
|
||||
// import ChartTab from './ChartTab';
|
||||
import * as ChartTab from './ChartTab.svelte';
|
||||
import * as MarkdownEditorTab from './MarkdownEditorTab.svelte';
|
||||
// import MarkdownViewTab from './MarkdownViewTab';
|
||||
// import MarkdownPreviewTab from './MarkdownPreviewTab';
|
||||
@@ -24,7 +24,7 @@ export default {
|
||||
ArchiveFileTab,
|
||||
FreeTableTab,
|
||||
// PluginTab,
|
||||
// ChartTab,
|
||||
ChartTab,
|
||||
MarkdownEditorTab,
|
||||
// MarkdownViewTab,
|
||||
// MarkdownPreviewTab,
|
||||
|
||||
Reference in New Issue
Block a user