mirror of
https://github.com/windmill-labs/windmill.git
synced 2026-08-18 16:02:10 +00:00
feat(frontend): AG chart (#2972)
* fix(frontend): Fix decision tree (#2928) * fix(frontend): wip * fix(frontend): wip * fix(frontend): decision tree history * fix(frontend): fix wording * feat(frontend): wip * feat(frontend): wip * feat(frontend): ag charts * feat(frontend): remove todo * feat(frontend): remove todo * feat(frontend): revert * feat(frontend): fix converstion * feat(frontend): ag charts ee * feat(frontend): fix build * feat(frontend): fix convertion to json * feat(frontend): fix naming * feat(frontend): fix id collision * feat(frontend): fix initial load * feat(frontend): fix initial load
This commit is contained in:
Generated
+15
@@ -14,6 +14,8 @@
|
||||
"@popperjs/core": "^2.11.6",
|
||||
"@redocly/json-to-json-schema": "^0.0.1",
|
||||
"@tanstack/svelte-table": "^8.9.9",
|
||||
"ag-charts-community": "^9.0.1",
|
||||
"ag-charts-enterprise": "^9.0.1",
|
||||
"ag-grid-community": "^31.0.0",
|
||||
"ag-grid-enterprise": "^31.0.0",
|
||||
"ansi_up": "^5.2.1",
|
||||
@@ -1687,6 +1689,19 @@
|
||||
"acorn": "^6.0.0 || ^7.0.0 || ^8.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/ag-charts-community": {
|
||||
"version": "9.0.1",
|
||||
"resolved": "https://registry.npmjs.org/ag-charts-community/-/ag-charts-community-9.0.1.tgz",
|
||||
"integrity": "sha512-FZy1tAWO10TzRheoM622/vJWRqldAYH3VGreQ8qirt7FB/ctIR4kjY9GPZywDJnVcb/wET3nrgwq2vIiFKhKBw=="
|
||||
},
|
||||
"node_modules/ag-charts-enterprise": {
|
||||
"version": "9.0.1",
|
||||
"resolved": "https://registry.npmjs.org/ag-charts-enterprise/-/ag-charts-enterprise-9.0.1.tgz",
|
||||
"integrity": "sha512-b8Lz6wgs/CSL1lz0PUnNod016Z5n7vus1mTM9GXhxJ3uyEYP0klwFyvgtK0Cjgc0YeFyhoVDmb38dMKIvHnUyg==",
|
||||
"dependencies": {
|
||||
"ag-charts-community": "9.0.1"
|
||||
}
|
||||
},
|
||||
"node_modules/ag-grid-community": {
|
||||
"version": "31.0.0",
|
||||
"resolved": "https://registry.npmjs.org/ag-grid-community/-/ag-grid-community-31.0.0.tgz",
|
||||
|
||||
@@ -97,6 +97,8 @@
|
||||
"@popperjs/core": "^2.11.6",
|
||||
"@redocly/json-to-json-schema": "^0.0.1",
|
||||
"@tanstack/svelte-table": "^8.9.9",
|
||||
"ag-charts-community": "^9.0.1",
|
||||
"ag-charts-enterprise": "^9.0.1",
|
||||
"ag-grid-community": "^31.0.0",
|
||||
"ag-grid-enterprise": "^31.0.0",
|
||||
"ansi_up": "^5.2.1",
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
export let componentInput: AppInput | undefined
|
||||
export let configuration: RichConfigurations
|
||||
export let initializing: boolean | undefined = undefined
|
||||
export let customCss: ComponentCustomCSS<'piechartcomponent'> | undefined = undefined
|
||||
export let customCss: ComponentCustomCSS<'chartjscomponent'> | undefined = undefined
|
||||
export let render: boolean
|
||||
|
||||
const { app, worldStore } = getContext<AppViewerContext>('AppViewerContext')
|
||||
|
||||
@@ -22,7 +22,7 @@
|
||||
export let componentInput: AppInput | undefined
|
||||
export let configuration: RichConfigurations
|
||||
export let initializing: boolean | undefined = undefined
|
||||
export let customCss: ComponentCustomCSS<'piechartcomponent'> | undefined = undefined
|
||||
export let customCss: ComponentCustomCSS<'chartjscomponent'> | undefined = undefined
|
||||
export let render: boolean
|
||||
export let datasets: RichConfiguration | undefined
|
||||
export let xData: RichConfiguration | undefined
|
||||
|
||||
@@ -0,0 +1,300 @@
|
||||
<script lang="ts">
|
||||
import RunnableWrapper from '../../helpers/RunnableWrapper.svelte'
|
||||
import type { AppInput } from '../../../inputType'
|
||||
import type {
|
||||
AppViewerContext,
|
||||
ComponentCustomCSS,
|
||||
RichConfiguration,
|
||||
RichConfigurations
|
||||
} from '../../../types'
|
||||
import { initCss } from '../../../utils'
|
||||
import { getContext, onMount } from 'svelte'
|
||||
import { initConfig, initOutput } from '../../../editor/appUtils'
|
||||
import { components } from '../../../editor/component'
|
||||
import ResolveConfig from '../../helpers/ResolveConfig.svelte'
|
||||
import { twMerge } from 'tailwind-merge'
|
||||
import ResolveStyle from '../../helpers/ResolveStyle.svelte'
|
||||
import type { AgChartOptions, AgChartInstance } from 'ag-charts-community'
|
||||
|
||||
export let id: string
|
||||
export let componentInput: AppInput | undefined
|
||||
export let configuration: RichConfigurations
|
||||
export let initializing: boolean | undefined = undefined
|
||||
export let customCss: ComponentCustomCSS<'agchartscomponent'> | undefined = undefined
|
||||
export let render: boolean
|
||||
export let datasets: RichConfiguration | undefined
|
||||
export let xData: RichConfiguration | undefined
|
||||
export let license: string | undefined = undefined
|
||||
export let ee: boolean = false
|
||||
|
||||
const { app, worldStore } = getContext<AppViewerContext>('AppViewerContext')
|
||||
|
||||
type Dataset = {
|
||||
value: RichConfiguration
|
||||
name: string
|
||||
type: 'bar' | 'line' | 'scatter' | 'area' | 'range-bar'
|
||||
}
|
||||
|
||||
let resolvedDatasets: Dataset[]
|
||||
let resolvedXData: number[] = []
|
||||
|
||||
const outputs = initOutput($worldStore, id, {
|
||||
result: undefined as
|
||||
| {
|
||||
data: any[]
|
||||
series: any[]
|
||||
}
|
||||
| undefined,
|
||||
loading: false
|
||||
})
|
||||
|
||||
let result: undefined = undefined
|
||||
|
||||
const resolvedConfig = initConfig(
|
||||
components['agchartscomponent'].initialData.configuration,
|
||||
configuration
|
||||
)
|
||||
|
||||
let css = initCss($app.css?.agchartscomponent, customCss)
|
||||
let chartInstance: AgChartInstance | undefined = undefined
|
||||
|
||||
function updateChart() {
|
||||
if (!chartInstance) {
|
||||
return
|
||||
}
|
||||
|
||||
let data = [] as any[]
|
||||
|
||||
for (let i = 0; i < resolvedXData.length; i++) {
|
||||
const o = {
|
||||
x: resolvedXData[i]
|
||||
}
|
||||
|
||||
for (let j = 0; j < resolvedDatasets.length; j++) {
|
||||
if (!resolvedDatasetsValues[j]) {
|
||||
continue
|
||||
}
|
||||
|
||||
if (resolvedDatasetsValues[j].type === 'range-bar') {
|
||||
o[`y-${j}-low`] = resolvedDatasetsValues[j].value[i]?.[0]
|
||||
o[`y-${j}-high`] = resolvedDatasetsValues[j].value[i]?.[1]
|
||||
} else {
|
||||
o[`y-${j}`] = resolvedDatasetsValues[j].value[i]
|
||||
}
|
||||
}
|
||||
|
||||
data.push(o)
|
||||
}
|
||||
|
||||
const options = {
|
||||
container: document.getElementById(`agchart-${id}`) as HTMLElement,
|
||||
data: data,
|
||||
series:
|
||||
(resolvedDatasets?.map((d, index) => {
|
||||
const type = resolvedDatasetsValues[index].type
|
||||
if (type === 'range-bar') {
|
||||
return {
|
||||
type: type,
|
||||
xKey: 'x',
|
||||
yLowKey: `y-${index}-low`,
|
||||
yHighKey: `y-${index}-high`,
|
||||
yName: d.name
|
||||
}
|
||||
} else {
|
||||
return {
|
||||
type: type,
|
||||
xKey: 'x',
|
||||
yKey: `y-${index}`,
|
||||
yName: d.name
|
||||
}
|
||||
}
|
||||
}) as any[]) ?? []
|
||||
}
|
||||
|
||||
outputs.result.set({
|
||||
data: options.data,
|
||||
series: options.series
|
||||
})
|
||||
|
||||
AgChartsInstance?.update(chartInstance, options)
|
||||
}
|
||||
|
||||
$: resolvedDatasetsValues = resolvedDatasets?.map((d) => {
|
||||
const config = initConfig(
|
||||
{
|
||||
value: {
|
||||
type: 'oneOf',
|
||||
selected: 'bar',
|
||||
labels: {
|
||||
bar: 'Bar',
|
||||
scatter: 'Scatter',
|
||||
line: 'Line',
|
||||
area: 'Area',
|
||||
['range-bar']: 'Range Bar'
|
||||
},
|
||||
configuration: {
|
||||
bar: {
|
||||
value: {
|
||||
type: 'static',
|
||||
fieldType: 'array',
|
||||
subFieldType: 'number',
|
||||
value: [25, 25, 50]
|
||||
}
|
||||
},
|
||||
scatter: {
|
||||
value: {
|
||||
type: 'static',
|
||||
fieldType: 'array',
|
||||
subFieldType: 'number',
|
||||
value: [25, 25, 50]
|
||||
}
|
||||
},
|
||||
line: {
|
||||
value: {
|
||||
type: 'static',
|
||||
fieldType: 'array',
|
||||
subFieldType: 'number',
|
||||
value: [25, 25, 50]
|
||||
}
|
||||
},
|
||||
area: {
|
||||
value: {
|
||||
type: 'static',
|
||||
fieldType: 'array',
|
||||
subFieldType: 'number',
|
||||
value: [25, 25, 50]
|
||||
}
|
||||
},
|
||||
['range-bar']: {
|
||||
value: {
|
||||
type: 'static',
|
||||
fieldType: 'array',
|
||||
subFieldType: 'number-tuple',
|
||||
value: [
|
||||
[10, 15],
|
||||
[20, 25],
|
||||
[18, 27]
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
value: d.value
|
||||
}
|
||||
)
|
||||
|
||||
return {
|
||||
type: d.value['selected'],
|
||||
// @ts-ignore
|
||||
value: config.value?.['configuration']?.[d.value['selected']].value
|
||||
}
|
||||
})
|
||||
|
||||
$: resolvedXData && resolvedDatasets && resolvedDatasetsValues && chartInstance && updateChart()
|
||||
$: result && updateChartByResult()
|
||||
|
||||
function updateChartByResult() {
|
||||
if (!result || !chartInstance) {
|
||||
return
|
||||
}
|
||||
|
||||
const options = {
|
||||
container: document.getElementById(`agchart-${id}`) as HTMLElement,
|
||||
data: result?.['data'],
|
||||
series: result?.['series']
|
||||
}
|
||||
|
||||
outputs.result.set({
|
||||
data: result?.['data'],
|
||||
series: result?.['series']
|
||||
})
|
||||
|
||||
AgChartsInstance?.update(chartInstance, options)
|
||||
}
|
||||
|
||||
let AgChartsInstance: any | undefined = undefined
|
||||
|
||||
async function loadLibrary() {
|
||||
if (ee) {
|
||||
const enterprise = await import('ag-charts-enterprise')
|
||||
AgChartsInstance = enterprise.AgCharts
|
||||
|
||||
AgChartsInstance.setLicenseKey(license)
|
||||
} else {
|
||||
const community = await import('ag-charts-community')
|
||||
AgChartsInstance = community.AgCharts
|
||||
}
|
||||
}
|
||||
|
||||
onMount(() => {
|
||||
loadLibrary().then(() => {
|
||||
try {
|
||||
// Chart Options
|
||||
const options: AgChartOptions = {
|
||||
container: document.getElementById(`agchart-${id}`) as HTMLElement,
|
||||
data: [],
|
||||
series: []
|
||||
}
|
||||
|
||||
chartInstance = AgChartsInstance?.create(options)
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
}
|
||||
})
|
||||
})
|
||||
</script>
|
||||
|
||||
{#if datasets}
|
||||
<ResolveConfig
|
||||
{id}
|
||||
key={'datasets'}
|
||||
bind:resolvedConfig={resolvedDatasets}
|
||||
configuration={datasets}
|
||||
/>
|
||||
{/if}
|
||||
|
||||
{#if xData}
|
||||
<ResolveConfig {id} key={'xData'} bind:resolvedConfig={resolvedXData} configuration={xData} />
|
||||
{/if}
|
||||
|
||||
{#if resolvedDatasets}
|
||||
{#each resolvedDatasets as resolvedDataset, index (resolvedDataset.name + index)}
|
||||
<ResolveConfig
|
||||
{id}
|
||||
key={'datasets' + index}
|
||||
extraKey={resolvedDataset.name}
|
||||
bind:resolvedConfig={resolvedDatasetsValues[index]}
|
||||
configuration={resolvedDataset.value}
|
||||
/>
|
||||
{/each}
|
||||
{/if}
|
||||
|
||||
{#each Object.keys(components['agchartscomponent'].initialData.configuration) as key (key)}
|
||||
<ResolveConfig
|
||||
{id}
|
||||
{key}
|
||||
bind:resolvedConfig={resolvedConfig[key]}
|
||||
configuration={configuration[key]}
|
||||
/>
|
||||
{/each}
|
||||
|
||||
{#each Object.keys(css ?? {}) as key (key)}
|
||||
<ResolveStyle
|
||||
{id}
|
||||
{customCss}
|
||||
{key}
|
||||
bind:css={css[key]}
|
||||
componentStyle={$app.css?.chartjscomponent}
|
||||
/>
|
||||
{/each}
|
||||
|
||||
<RunnableWrapper {outputs} {render} autoRefresh {componentInput} {id} bind:initializing bind:result>
|
||||
<div
|
||||
class={twMerge('w-full h-full', css?.container?.class, 'wm-agchart')}
|
||||
style={css?.container?.style ?? ''}
|
||||
>
|
||||
<div id={`agchart-${id}`} class="h-full w-full" />
|
||||
</div>
|
||||
</RunnableWrapper>
|
||||
@@ -66,6 +66,7 @@
|
||||
import AppStatCard from '../../components/display/AppStatCard.svelte'
|
||||
import AppMenu from '../../components/display/AppMenu.svelte'
|
||||
import AppDecisionTree from '../../components/layout/AppDecisionTree.svelte'
|
||||
import AppAgCharts from '../../components/display/charts/AppAgCharts.svelte'
|
||||
import AppDbExplorer from '../../components/display/dbtable/AppDbExplorer.svelte'
|
||||
|
||||
export let component: AppComponent
|
||||
@@ -296,6 +297,30 @@
|
||||
componentInput={component.componentInput}
|
||||
{render}
|
||||
/>
|
||||
{:else if component.type === 'agchartscomponent'}
|
||||
<AppAgCharts
|
||||
configuration={component.configuration}
|
||||
id={component.id}
|
||||
customCss={component.customCss}
|
||||
bind:initializing
|
||||
componentInput={component.componentInput}
|
||||
datasets={component.datasets}
|
||||
xData={component.xData}
|
||||
{render}
|
||||
/>
|
||||
{:else if component.type === 'agchartscomponentee'}
|
||||
<AppAgCharts
|
||||
configuration={component.configuration}
|
||||
id={component.id}
|
||||
customCss={component.customCss}
|
||||
bind:initializing
|
||||
componentInput={component.componentInput}
|
||||
datasets={component.datasets}
|
||||
xData={component.xData}
|
||||
license={component.license}
|
||||
ee={true}
|
||||
{render}
|
||||
/>
|
||||
{:else if component.type === 'tablecomponent'}
|
||||
<AppTable
|
||||
configuration={component.configuration}
|
||||
|
||||
@@ -110,6 +110,17 @@ export type ChartJsComponentV2 = BaseComponent<'chartjscomponentv2'> & {
|
||||
datasets: RichConfiguration | undefined
|
||||
}
|
||||
|
||||
export type AgChartsComponent = BaseComponent<'agchartscomponent'> & {
|
||||
xData: RichConfiguration | undefined
|
||||
datasets: RichConfiguration | undefined
|
||||
}
|
||||
|
||||
export type AgChartsComponentEe = BaseComponent<'agchartscomponentee'> & {
|
||||
license: string
|
||||
xData: RichConfiguration | undefined
|
||||
datasets: RichConfiguration | undefined
|
||||
}
|
||||
|
||||
export type ScatterChartComponent = BaseComponent<'scatterchartcomponent'>
|
||||
|
||||
export type TableComponent = BaseComponent<'tablecomponent'> & {
|
||||
@@ -253,6 +264,8 @@ export type TypedComponent =
|
||||
| StatisticCardComponent
|
||||
| MenuComponent
|
||||
| DecisionTreeComponent
|
||||
| AgChartsComponent
|
||||
| AgChartsComponentEe
|
||||
|
||||
export type AppComponent = BaseAppComponent & TypedComponent
|
||||
|
||||
@@ -630,6 +643,20 @@ const aggridcomponentconst = {
|
||||
}
|
||||
} as const
|
||||
|
||||
const agchartscomponentconst = {
|
||||
name: 'AgCharts',
|
||||
icon: BarChart4,
|
||||
documentationLink: `${documentationBaseUrl}/agcharts`,
|
||||
dims: '2:8-6:8' as AppComponentDimensions,
|
||||
customCss: {
|
||||
container: { class: '', style: '' }
|
||||
},
|
||||
initialData: {
|
||||
configuration: {},
|
||||
componentInput: undefined
|
||||
}
|
||||
} as const
|
||||
|
||||
export const components = {
|
||||
displaycomponent: {
|
||||
name: 'Rich Result',
|
||||
@@ -1232,6 +1259,9 @@ export const components = {
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
agchartscomponent: agchartscomponentconst,
|
||||
agchartscomponentee: { ...agchartscomponentconst, name: 'AgCharts EE' },
|
||||
htmlcomponent: {
|
||||
name: 'HTML',
|
||||
icon: Code2,
|
||||
|
||||
@@ -77,7 +77,13 @@ const tables: ComponentSet = {
|
||||
|
||||
const charts: ComponentSet = {
|
||||
title: 'Charts',
|
||||
components: ['plotlycomponentv2', 'chartjscomponentv2', 'vegalitecomponent']
|
||||
components: [
|
||||
'plotlycomponentv2',
|
||||
'chartjscomponentv2',
|
||||
'vegalitecomponent',
|
||||
'agchartscomponent',
|
||||
'agchartscomponentee'
|
||||
]
|
||||
} as const
|
||||
|
||||
export const COMPONENT_SETS = [layout, tabs, buttons, inputs, tables, display, charts] as const
|
||||
|
||||
@@ -548,6 +548,11 @@ export const customisationByComponent: Customisation[] = [
|
||||
selectors: [{ selector: '.wm-chartjs', comment: 'ChartJS', customCssKey: 'container' }],
|
||||
variables: []
|
||||
},
|
||||
{
|
||||
components: ['agchartscomponent'],
|
||||
selectors: [{ selector: '.wm-agchart', comment: 'AgCharts', customCssKey: 'container' }],
|
||||
variables: []
|
||||
},
|
||||
{
|
||||
components: ['timeseriescomponent'],
|
||||
selectors: [{ selector: '.wm-timeseries', comment: 'Time series', customCssKey: 'container' }],
|
||||
|
||||
@@ -668,6 +668,12 @@ export const quickStyleProperties: Record<
|
||||
piechartcomponent: {
|
||||
container: containerDefaultProps
|
||||
},
|
||||
agchartscomponent: {
|
||||
container: containerDefaultProps
|
||||
},
|
||||
agchartscomponentee: {
|
||||
container: containerDefaultProps
|
||||
},
|
||||
chartjscomponent: {
|
||||
container: containerDefaultProps
|
||||
},
|
||||
|
||||
@@ -0,0 +1,58 @@
|
||||
<script lang="ts">
|
||||
import type { RichConfiguration } from '../../types'
|
||||
import InputsSpecEditor from './InputsSpecEditor.svelte'
|
||||
import PanelSection from './common/PanelSection.svelte'
|
||||
|
||||
export let datasets: RichConfiguration | undefined = undefined
|
||||
export let xData: RichConfiguration | undefined = undefined
|
||||
export let id: string
|
||||
</script>
|
||||
|
||||
<PanelSection
|
||||
title={`AG Chart configuration`}
|
||||
tooltip="The configuration is divided into two parts: X-axis data and an array of datasets. Each dataset hold the data for the Y-axis and the configuration for the plot (type, name, etc)."
|
||||
>
|
||||
<div class="w-full flex flex-col gap-4">
|
||||
{#if xData}
|
||||
<InputsSpecEditor
|
||||
key={`X-axis data`}
|
||||
bind:componentInput={xData}
|
||||
{id}
|
||||
userInputEnabled={false}
|
||||
shouldCapitalize={true}
|
||||
resourceOnly={false}
|
||||
fieldType={xData?.['fieldType']}
|
||||
subFieldType={xData?.['subFieldType']}
|
||||
format={xData?.['format']}
|
||||
selectOptions={xData?.['selectOptions']}
|
||||
tooltip={xData?.['tooltip']}
|
||||
fileUpload={xData?.['fileUpload']}
|
||||
placeholder={xData?.['placeholder']}
|
||||
customTitle={xData?.['customTitle']}
|
||||
displayType={false}
|
||||
/>
|
||||
{/if}
|
||||
|
||||
{#if datasets}
|
||||
<InputsSpecEditor
|
||||
key={`Dataset`}
|
||||
bind:componentInput={datasets}
|
||||
{id}
|
||||
userInputEnabled={false}
|
||||
shouldCapitalize={true}
|
||||
resourceOnly={false}
|
||||
fieldType={datasets?.['fieldType']}
|
||||
subFieldType={datasets?.['subFieldType']}
|
||||
format={datasets?.['format']}
|
||||
selectOptions={datasets?.['selectOptions']}
|
||||
tooltip="For each dataset, you can specify the data for the Y-axis and the configuration for the plot (type, color, etc). If you want to have an eval for every data point, you can switch to JSON mode."
|
||||
fileUpload={datasets?.['fileUpload']}
|
||||
placeholder={datasets?.['placeholder']}
|
||||
customTitle={datasets?.['customTitle']}
|
||||
displayType={false}
|
||||
shouldFormatExpression={true}
|
||||
allowTypeChange={false}
|
||||
/>
|
||||
{/if}
|
||||
</div>
|
||||
</PanelSection>
|
||||
+65
-1
@@ -26,7 +26,8 @@
|
||||
if (subFieldType === 'boolean') {
|
||||
value.push(false)
|
||||
} else if (subFieldType === 'number') {
|
||||
value.push(0)
|
||||
value.push(1)
|
||||
value = value
|
||||
} else if (subFieldType === 'object') {
|
||||
value.push({})
|
||||
} else if (subFieldType === 'labeledresource' || subFieldType === 'labeledselect') {
|
||||
@@ -74,6 +75,69 @@
|
||||
},
|
||||
name: 'New dataset'
|
||||
})
|
||||
} else if (subFieldType === 'ag-chart') {
|
||||
value.push({
|
||||
value: {
|
||||
type: 'oneOf',
|
||||
selected: 'bar',
|
||||
labels: {
|
||||
bar: 'Bar',
|
||||
scatter: 'Scatter',
|
||||
line: 'Line',
|
||||
area: 'Area',
|
||||
'range-bar': 'Range Bar'
|
||||
},
|
||||
configuration: {
|
||||
bar: {
|
||||
value: {
|
||||
type: 'static',
|
||||
fieldType: 'array',
|
||||
subFieldType: 'number',
|
||||
value: [25, 25, 50]
|
||||
}
|
||||
},
|
||||
scatter: {
|
||||
value: {
|
||||
type: 'static',
|
||||
fieldType: 'array',
|
||||
subFieldType: 'number',
|
||||
value: [25, 25, 50]
|
||||
}
|
||||
},
|
||||
line: {
|
||||
value: {
|
||||
type: 'static',
|
||||
fieldType: 'array',
|
||||
subFieldType: 'number',
|
||||
value: [25, 25, 50]
|
||||
}
|
||||
},
|
||||
area: {
|
||||
value: {
|
||||
type: 'static',
|
||||
fieldType: 'array',
|
||||
subFieldType: 'number',
|
||||
value: [25, 25, 50]
|
||||
}
|
||||
},
|
||||
'range-bar': {
|
||||
value: {
|
||||
type: 'static',
|
||||
fieldType: 'array',
|
||||
subFieldType: 'number-tuple',
|
||||
value: [
|
||||
[10, 15],
|
||||
[20, 25],
|
||||
[18, 27]
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
name: 'New dataset'
|
||||
})
|
||||
} else if (subFieldType === 'number-tuple') {
|
||||
value.push([0, 5])
|
||||
}
|
||||
} else {
|
||||
value.push('')
|
||||
|
||||
@@ -38,6 +38,7 @@
|
||||
import ComponentPanelDataSource from './ComponentPanelDataSource.svelte'
|
||||
import MenuItems from './MenuItems.svelte'
|
||||
import DecisionTreeGraphEditor from './DecisionTreeGraphEditor.svelte'
|
||||
import GridAgChartsLicenseKe from './GridAgChartsLicenseKe.svelte'
|
||||
|
||||
export let componentSettings: { item: GridItem; parent: string | undefined } | undefined =
|
||||
undefined
|
||||
@@ -326,6 +327,8 @@
|
||||
/>
|
||||
{:else if componentSettings.item.data.type === 'aggridcomponentee'}
|
||||
<GridAgGridLicenseKey bind:license={componentSettings.item.data.license} />
|
||||
{:else if componentSettings.item.data.type === 'agchartscomponentee'}
|
||||
<GridAgChartsLicenseKe bind:license={componentSettings.item.data.license} />
|
||||
{:else if componentSettings.item.data.type === 'steppercomponent'}
|
||||
<GridTab
|
||||
bind:tabs={componentSettings.item.data.tabs}
|
||||
|
||||
+159
-5
@@ -5,8 +5,9 @@
|
||||
import ToggleButton from '$lib/components/common/toggleButton-v2/ToggleButton.svelte'
|
||||
import PlotlyRichEditor from './PlotlyRichEditor.svelte'
|
||||
import ChartJSRichEditor from './ChartJSRichEditor.svelte'
|
||||
import { onMount } from 'svelte'
|
||||
import type { RichConfiguration } from '../../types'
|
||||
import AGChartRichEditor from './AGChartRichEditor.svelte'
|
||||
import { getContext, onMount } from 'svelte'
|
||||
import type { AppViewerContext, RichConfiguration } from '../../types'
|
||||
import type { InputConnectionEval } from '../../inputType'
|
||||
import ConfirmationModal from '$lib/components/common/confirmationModal/ConfirmationModal.svelte'
|
||||
|
||||
@@ -18,7 +19,10 @@
|
||||
|
||||
onMount(() => {
|
||||
if (
|
||||
(component.type === 'plotlycomponentv2' || component.type === 'chartjscomponentv2') &&
|
||||
(component.type === 'plotlycomponentv2' ||
|
||||
component.type === 'chartjscomponentv2' ||
|
||||
component.type === 'agchartscomponent' ||
|
||||
component.type === 'agchartscomponentee') &&
|
||||
component.componentInput === undefined &&
|
||||
component.datasets === undefined
|
||||
) {
|
||||
@@ -29,6 +33,7 @@
|
||||
}
|
||||
})
|
||||
|
||||
const { worldStore } = getContext<AppViewerContext>('AppViewerContext')
|
||||
interface Dataset {
|
||||
value: any // Define more specific type if possible
|
||||
name: string
|
||||
@@ -82,9 +87,47 @@
|
||||
} else if (selected === 'json') {
|
||||
convertChartJSToJson()
|
||||
}
|
||||
} else if (isAgChartsComponent()) {
|
||||
if (selected === 'ui-editor') {
|
||||
convertToUIEditorCallback = () => {
|
||||
component.componentInput = undefined
|
||||
setUpUIEditor()
|
||||
}
|
||||
|
||||
setTimeout(() => {
|
||||
const activeElement = document.activeElement as HTMLElement
|
||||
activeElement?.blur()
|
||||
document.body.focus()
|
||||
})
|
||||
} else if (selected === 'json') {
|
||||
convertAgChartToJson()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function convertAgChartToJson() {
|
||||
if (component.type !== 'agchartscomponent' && component.type !== 'agchartscomponentee') {
|
||||
return
|
||||
}
|
||||
|
||||
const connections: InputConnectionEval[] = []
|
||||
|
||||
if (component.datasets === undefined || component.datasets.type !== 'static') return
|
||||
|
||||
const datasetsAsString = datasetToAgChartJson()
|
||||
|
||||
component.componentInput = {
|
||||
type: 'evalv2',
|
||||
fieldType: 'object',
|
||||
noStatic: true,
|
||||
expr: datasetsAsString,
|
||||
connections: connections.filter(Boolean)
|
||||
}
|
||||
|
||||
component.datasets = undefined
|
||||
component.xData = undefined
|
||||
}
|
||||
|
||||
function setUpUIEditor() {
|
||||
if (component.type === 'plotlycomponentv2') {
|
||||
component.datasets = createPlotlyComponentDataset()
|
||||
@@ -92,6 +135,81 @@
|
||||
} else if (component.type === 'chartjscomponentv2') {
|
||||
component.datasets = createChartjsComponentDataset()
|
||||
component.xData = createXData()
|
||||
} else if (component.type === 'agchartscomponent' || component.type === 'agchartscomponentee') {
|
||||
component.datasets = createAgChartsComponentDataset()
|
||||
component.xData = createXData()
|
||||
}
|
||||
}
|
||||
|
||||
function createAgChartsComponentDataset(): RichConfiguration {
|
||||
return {
|
||||
type: 'static',
|
||||
fieldType: 'array',
|
||||
subFieldType: 'ag-chart',
|
||||
|
||||
value: [
|
||||
{
|
||||
value: {
|
||||
type: 'oneOf',
|
||||
selected: 'bar',
|
||||
labels: {
|
||||
bar: 'Bar',
|
||||
scatter: 'Scatter',
|
||||
line: 'Line',
|
||||
area: 'Area',
|
||||
['range-bar']: 'Range Bar'
|
||||
},
|
||||
configuration: {
|
||||
bar: {
|
||||
value: {
|
||||
type: 'static',
|
||||
fieldType: 'array',
|
||||
subFieldType: 'number',
|
||||
value: [25, 25, 50]
|
||||
}
|
||||
},
|
||||
scatter: {
|
||||
value: {
|
||||
type: 'static',
|
||||
fieldType: 'array',
|
||||
subFieldType: 'number',
|
||||
value: [25, 25, 50]
|
||||
}
|
||||
},
|
||||
line: {
|
||||
value: {
|
||||
type: 'static',
|
||||
fieldType: 'array',
|
||||
subFieldType: 'number',
|
||||
value: [25, 25, 50]
|
||||
}
|
||||
},
|
||||
area: {
|
||||
value: {
|
||||
type: 'static',
|
||||
fieldType: 'array',
|
||||
subFieldType: 'number',
|
||||
value: [25, 25, 50]
|
||||
}
|
||||
},
|
||||
['range-bar']: {
|
||||
value: {
|
||||
type: 'static',
|
||||
fieldType: 'array',
|
||||
subFieldType: 'number-tuple',
|
||||
value: [
|
||||
[10, 15],
|
||||
[20, 25],
|
||||
[18, 27]
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
} as const,
|
||||
name: 'Dataset 1',
|
||||
type: 'bar'
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -146,8 +264,16 @@
|
||||
}
|
||||
}
|
||||
|
||||
function isAgChartsComponent(): boolean {
|
||||
return component.type === 'agchartscomponent' || component.type === 'agchartscomponentee'
|
||||
}
|
||||
|
||||
function convertToJson() {
|
||||
if (component.type !== 'plotlycomponentv2') {
|
||||
if (
|
||||
component.type !== 'plotlycomponentv2' &&
|
||||
component.type !== 'agchartscomponent' &&
|
||||
component.type !== 'agchartscomponentee'
|
||||
) {
|
||||
return
|
||||
}
|
||||
|
||||
@@ -227,9 +353,31 @@
|
||||
\t\t}
|
||||
\t}`
|
||||
}
|
||||
|
||||
function datasetToAgChartJson(): string {
|
||||
const outputs = $worldStore.outputsById[component.id]
|
||||
const result = outputs?.result.peak()
|
||||
|
||||
if (!result.data && !result.series) {
|
||||
throw new Error('Invalid result')
|
||||
}
|
||||
|
||||
return (
|
||||
'(' +
|
||||
JSON.stringify(
|
||||
{
|
||||
data: result.data,
|
||||
series: result.series
|
||||
},
|
||||
null,
|
||||
'\t'
|
||||
) +
|
||||
')'
|
||||
)
|
||||
}
|
||||
</script>
|
||||
|
||||
{#if component.type === 'plotlycomponentv2' || component.type === 'chartjscomponentv2'}
|
||||
{#if component.type === 'plotlycomponentv2' || component.type === 'chartjscomponentv2' || component.type === 'agchartscomponent' || component.type === 'agchartscomponentee'}
|
||||
<div class="p-2">
|
||||
<ToggleButtonGroup
|
||||
bind:selected
|
||||
@@ -258,6 +406,12 @@
|
||||
bind:datasets={component.datasets}
|
||||
bind:xData={component.xData}
|
||||
/>
|
||||
{:else if isAgChartsComponent()}
|
||||
<AGChartRichEditor
|
||||
id={component.id}
|
||||
bind:datasets={component.datasets}
|
||||
bind:xData={component.xData}
|
||||
/>
|
||||
{:else if component.type === 'chartjscomponentv2'}
|
||||
<ChartJSRichEditor
|
||||
id={component.id}
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
<script lang="ts">
|
||||
import Badge from '$lib/components/common/badge/Badge.svelte'
|
||||
|
||||
export let license: string
|
||||
|
||||
let valid = false
|
||||
$: license && checkLicenseKey(license)
|
||||
|
||||
async function checkLicenseKey(key: string) {
|
||||
try {
|
||||
const { AgCharts } = await import('ag-charts-enterprise')
|
||||
|
||||
// @ts-ignore
|
||||
valid = AgCharts.licenseKey
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="p-2">
|
||||
<span class="text-xs font-semibold">AgCharts EE License Key</span>
|
||||
<input type="text" bind:value={license} placeholder="AgCharts Enterprise" />
|
||||
|
||||
{#if valid}
|
||||
<Badge color="green">Valid</Badge>
|
||||
{:else}
|
||||
<Badge color="red">Invalid</Badge>
|
||||
{/if}
|
||||
</div>
|
||||
@@ -13,6 +13,7 @@
|
||||
export let id: string
|
||||
export let resourceOnly: boolean
|
||||
export let tooltip: string | undefined
|
||||
export let disabledOptions: string[] = []
|
||||
|
||||
$: {
|
||||
if (oneOf == undefined) {
|
||||
@@ -63,7 +64,7 @@
|
||||
}}
|
||||
>
|
||||
{#each Object.keys(inputSpecsConfiguration ?? {}) as choice}
|
||||
{#if !getValueOfDeprecated(inputSpecsConfiguration[choice]) || oneOf.selected === choice}
|
||||
{#if (!disabledOptions.includes(choice) && !getValueOfDeprecated(inputSpecsConfiguration[choice])) || oneOf.selected === choice}
|
||||
<option value={choice}>{labels?.[choice] ?? choice}</option>
|
||||
{/if}
|
||||
{/each}
|
||||
|
||||
+250
-205
@@ -17,7 +17,9 @@
|
||||
import TableColumnWizard from '$lib/components/wizards/TableColumnWizard.svelte'
|
||||
import PlotlyWizard from '$lib/components/wizards/PlotlyWizard.svelte'
|
||||
import ChartJSWizard from '$lib/components/wizards/ChartJSWizard.svelte'
|
||||
import AgChartWizard from '$lib/components/wizards/AgChartWizard.svelte'
|
||||
import DBExplorerWizard from '$lib/components/wizards/DBExplorerWizard.svelte'
|
||||
import Label from '$lib/components/Label.svelte'
|
||||
|
||||
export let componentInput: StaticInput<any> | undefined
|
||||
export let fieldType: InputType | undefined = undefined
|
||||
@@ -31,80 +33,35 @@
|
||||
$: componentInput && onchange?.()
|
||||
</script>
|
||||
|
||||
{#if componentInput?.type === 'static'}
|
||||
{#if fieldType === 'number' || fieldType === 'integer'}
|
||||
<input on:keydown|stopPropagation type="number" bind:value={componentInput.value} />
|
||||
{:else if fieldType === 'textarea'}
|
||||
<textarea use:autosize on:keydown|stopPropagation bind:value={componentInput.value} />
|
||||
{:else if fieldType === 'date'}
|
||||
<input on:keydown|stopPropagation type="date" bind:value={componentInput.value} />
|
||||
{:else if fieldType === 'boolean'}
|
||||
<Toggle bind:checked={componentInput.value} size="xs" />
|
||||
{:else if fieldType === 'select' && selectOptions}
|
||||
<select on:keydown|stopPropagation bind:value={componentInput.value}>
|
||||
{#each selectOptions ?? [] as option}
|
||||
{#if typeof option == 'string'}
|
||||
<option value={option}>
|
||||
{option}
|
||||
</option>
|
||||
{:else}
|
||||
<option value={option.value}>
|
||||
{option.label}
|
||||
</option>
|
||||
{/if}
|
||||
{/each}
|
||||
</select>
|
||||
{:else if fieldType === 'icon-select'}
|
||||
<IconSelectInput bind:componentInput />
|
||||
{:else if fieldType === 'tab-select'}
|
||||
<TabSelectInput bind:componentInput />
|
||||
{:else if fieldType === 'resource'}
|
||||
<ResourcePicker
|
||||
initialValue={componentInput.value?.split('$res:')?.[1] || ''}
|
||||
on:change={(e) => {
|
||||
let path = e.detail
|
||||
if (componentInput) {
|
||||
if (path) {
|
||||
componentInput.value = `$res:${path}`
|
||||
} else {
|
||||
componentInput.value = undefined
|
||||
}
|
||||
}
|
||||
}}
|
||||
showSchemaExplorer
|
||||
resourceType="postgresql"
|
||||
/>
|
||||
{:else if fieldType === 'labeledresource'}
|
||||
{#if componentInput?.value && typeof componentInput?.value == 'object' && 'label' in componentInput?.value && (componentInput.value?.['value'] == undefined || typeof componentInput.value?.['value'] == 'string')}
|
||||
<div class="flex flex-col gap-1 w-full">
|
||||
<input
|
||||
on:keydown|stopPropagation
|
||||
placeholder="Label"
|
||||
type="text"
|
||||
bind:value={componentInput.value['label']}
|
||||
/>
|
||||
<ResourcePicker
|
||||
initialValue={componentInput.value?.['value']?.split('$res:')?.[1] || ''}
|
||||
on:change={(e) => {
|
||||
let path = e.detail
|
||||
if (componentInput) {
|
||||
if (path) {
|
||||
componentInput.value['value'] = `$res:${path}`
|
||||
} else {
|
||||
componentInput.value['value'] = undefined
|
||||
}
|
||||
}
|
||||
}}
|
||||
showSchemaExplorer
|
||||
/>
|
||||
</div>
|
||||
{:else}
|
||||
Inconsistent labeled resource object
|
||||
{/if}
|
||||
{:else if fieldType === 'color'}
|
||||
<ColorInput bind:value={componentInput.value} />
|
||||
{:else if fieldType === 'object' || fieldType == 'labeledselect'}
|
||||
{#if format?.startsWith('resource-') && (componentInput.value == undefined || typeof componentInput.value == 'string')}
|
||||
{#key subFieldType}
|
||||
{#if componentInput?.type === 'static'}
|
||||
{#if fieldType === 'number' || fieldType === 'integer'}
|
||||
<input on:keydown|stopPropagation type="number" bind:value={componentInput.value} />
|
||||
{:else if fieldType === 'textarea'}
|
||||
<textarea use:autosize on:keydown|stopPropagation bind:value={componentInput.value} />
|
||||
{:else if fieldType === 'date'}
|
||||
<input on:keydown|stopPropagation type="date" bind:value={componentInput.value} />
|
||||
{:else if fieldType === 'boolean'}
|
||||
<Toggle bind:checked={componentInput.value} size="xs" />
|
||||
{:else if fieldType === 'select' && selectOptions}
|
||||
<select on:keydown|stopPropagation bind:value={componentInput.value}>
|
||||
{#each selectOptions ?? [] as option}
|
||||
{#if typeof option == 'string'}
|
||||
<option value={option}>
|
||||
{option}
|
||||
</option>
|
||||
{:else}
|
||||
<option value={option.value}>
|
||||
{option.label}
|
||||
</option>
|
||||
{/if}
|
||||
{/each}
|
||||
</select>
|
||||
{:else if fieldType === 'icon-select'}
|
||||
<IconSelectInput bind:componentInput />
|
||||
{:else if fieldType === 'tab-select'}
|
||||
<TabSelectInput bind:componentInput />
|
||||
{:else if fieldType === 'resource'}
|
||||
<ResourcePicker
|
||||
initialValue={componentInput.value?.split('$res:')?.[1] || ''}
|
||||
on:change={(e) => {
|
||||
@@ -117,142 +74,230 @@
|
||||
}
|
||||
}
|
||||
}}
|
||||
resourceType={format && format?.split('-').length > 1
|
||||
? format.substring('resource-'.length)
|
||||
: undefined}
|
||||
showSchemaExplorer
|
||||
resourceType="postgresql"
|
||||
/>
|
||||
{:else if fieldType === 'labeledresource'}
|
||||
{#if componentInput?.value && typeof componentInput?.value == 'object' && 'label' in componentInput?.value && (componentInput.value?.['value'] == undefined || typeof componentInput.value?.['value'] == 'string')}
|
||||
<div class="flex flex-col gap-1 w-full">
|
||||
<input
|
||||
on:keydown|stopPropagation
|
||||
placeholder="Label"
|
||||
type="text"
|
||||
bind:value={componentInput.value['label']}
|
||||
/>
|
||||
<ResourcePicker
|
||||
initialValue={componentInput.value?.['value']?.split('$res:')?.[1] || ''}
|
||||
on:change={(e) => {
|
||||
let path = e.detail
|
||||
if (componentInput) {
|
||||
if (path) {
|
||||
componentInput.value['value'] = `$res:${path}`
|
||||
} else {
|
||||
componentInput.value['value'] = undefined
|
||||
}
|
||||
}
|
||||
}}
|
||||
showSchemaExplorer
|
||||
/>
|
||||
</div>
|
||||
{:else}
|
||||
Inconsistent labeled resource object
|
||||
{/if}
|
||||
{:else if fieldType === 'color'}
|
||||
<ColorInput bind:value={componentInput.value} />
|
||||
{:else if fieldType === 'object' || fieldType == 'labeledselect'}
|
||||
{#if format?.startsWith('resource-') && (componentInput.value == undefined || typeof componentInput.value == 'string')}
|
||||
<ResourcePicker
|
||||
initialValue={componentInput.value?.split('$res:')?.[1] || ''}
|
||||
on:change={(e) => {
|
||||
let path = e.detail
|
||||
if (componentInput) {
|
||||
if (path) {
|
||||
componentInput.value = `$res:${path}`
|
||||
} else {
|
||||
componentInput.value = undefined
|
||||
}
|
||||
}
|
||||
}}
|
||||
resourceType={format && format?.split('-').length > 1
|
||||
? format.substring('resource-'.length)
|
||||
: undefined}
|
||||
showSchemaExplorer
|
||||
/>
|
||||
{:else}
|
||||
<div class="flex w-full flex-col">
|
||||
<JsonEditor
|
||||
small
|
||||
bind:value={componentInput.value}
|
||||
code={JSON.stringify(componentInput.value, null, 2)}
|
||||
/>
|
||||
</div>
|
||||
{/if}
|
||||
{:else if fieldType === 'array'}
|
||||
<ArrayStaticInputEditor {subFieldType} bind:componentInput on:deleteArrayItem />
|
||||
{:else if fieldType === 'schema'}
|
||||
<div class="w-full">
|
||||
<SchemaEditor bind:schema={componentInput.value} lightMode />
|
||||
</div>
|
||||
{:else if fieldType === 'ag-grid'}
|
||||
<div class="flex flex-row rounded-md bg-surface items-center h-full">
|
||||
<div class="relative w-full">
|
||||
<input
|
||||
class="text-xs px-2 border-y w-full flex flex-row items-center border-r rounded-r-md h-8"
|
||||
bind:value={componentInput.value.field}
|
||||
placeholder="Field"
|
||||
/>
|
||||
<div class="absolute top-1 right-1">
|
||||
<AgGridWizard bind:value={componentInput.value}>
|
||||
<svelte:fragment slot="trigger">
|
||||
<Button color="light" size="xs2" nonCaptureEvent={true}>
|
||||
<div class="flex flex-row items-center gap-2 text-xs font-normal">
|
||||
<Settings size={16} />
|
||||
</div>
|
||||
</Button>
|
||||
</svelte:fragment>
|
||||
</AgGridWizard>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{:else if fieldType === 'db-explorer' && componentInput.value != undefined}
|
||||
<div class="flex flex-row rounded-md bg-surface items-center h-full">
|
||||
<div class="relative w-full">
|
||||
<input
|
||||
class="text-xs px-2 border-y w-full flex flex-row items-center border-r rounded-r-md h-8"
|
||||
bind:value={componentInput.value.field}
|
||||
placeholder="Field"
|
||||
disabled
|
||||
/>
|
||||
<div class="absolute top-1 right-1">
|
||||
<DBExplorerWizard bind:value={componentInput.value}>
|
||||
<svelte:fragment slot="trigger">
|
||||
<Button color="light" size="xs2" nonCaptureEvent={true}>
|
||||
<div class="flex flex-row items-center gap-2 text-xs font-normal">
|
||||
<Settings size={16} />
|
||||
</div>
|
||||
</Button>
|
||||
</svelte:fragment>
|
||||
</DBExplorerWizard>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{:else if fieldType === 'table-column'}
|
||||
<div class="flex flex-row rounded-md bg-surface items-center h-full">
|
||||
<div class="relative w-full">
|
||||
<input
|
||||
class="text-xs px-2 border-y w-full flex flex-row items-center border-r rounded-r-md h-8"
|
||||
bind:value={componentInput.value.field}
|
||||
placeholder="Field"
|
||||
/>
|
||||
<div class="absolute top-1 right-1">
|
||||
<TableColumnWizard bind:column={componentInput.value}>
|
||||
<svelte:fragment slot="trigger">
|
||||
<Button color="light" size="xs2" nonCaptureEvent={true}>
|
||||
<div class="flex flex-row items-center gap-2 text-xs font-normal">
|
||||
<Settings size={16} />
|
||||
</div>
|
||||
</Button>
|
||||
</svelte:fragment>
|
||||
</TableColumnWizard>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{:else if fieldType === 'plotly'}
|
||||
<div class="flex flex-row rounded-md bg-surface items-center h-full">
|
||||
<div class="relative w-full">
|
||||
<input
|
||||
class="text-xs px-2 border-y w-full flex flex-row items-center border-r rounded-r-md h-8"
|
||||
bind:value={componentInput.value.name}
|
||||
placeholder="Dataset name"
|
||||
/>
|
||||
<div class="absolute top-1 right-1">
|
||||
<PlotlyWizard bind:value={componentInput.value} on:remove>
|
||||
<svelte:fragment slot="trigger">
|
||||
<Button color="light" size="xs2" nonCaptureEvent={true}>
|
||||
<div class="flex flex-row items-center gap-2 text-xs font-normal">
|
||||
<Settings size={16} />
|
||||
</div>
|
||||
</Button>
|
||||
</svelte:fragment>
|
||||
</PlotlyWizard>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{:else if fieldType === 'chartjs'}
|
||||
<div class="flex flex-row rounded-md bg-surface items-center h-full">
|
||||
<div class="relative w-full">
|
||||
<input
|
||||
class="text-xs px-2 border-y w-full flex flex-row items-center border-r rounded-r-md h-8"
|
||||
bind:value={componentInput.value.name}
|
||||
placeholder="Dataset name"
|
||||
/>
|
||||
<div class="absolute top-1 right-1">
|
||||
<ChartJSWizard bind:value={componentInput.value} on:remove>
|
||||
<svelte:fragment slot="trigger">
|
||||
<Button color="light" size="xs2" nonCaptureEvent={true}>
|
||||
<div class="flex flex-row items-center gap-2 text-xs font-normal">
|
||||
<Settings size={16} />
|
||||
</div>
|
||||
</Button>
|
||||
</svelte:fragment>
|
||||
</ChartJSWizard>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{:else if fieldType === 'ag-chart'}
|
||||
<div class="flex flex-row rounded-md bg-surface items-center h-full">
|
||||
<div class="relative w-full">
|
||||
<input
|
||||
class="text-xs px-2 border-y w-full flex flex-row items-center border-r rounded-r-md h-8"
|
||||
bind:value={componentInput.value.name}
|
||||
placeholder="Dataset name"
|
||||
/>
|
||||
|
||||
<div class="absolute top-1 right-1">
|
||||
<AgChartWizard bind:value={componentInput.value} on:remove>
|
||||
<svelte:fragment slot="trigger">
|
||||
<Button color="light" size="xs2" nonCaptureEvent={true}>
|
||||
<div class="flex flex-row items-center gap-2 text-xs font-normal">
|
||||
<Settings size={16} />
|
||||
</div>
|
||||
</Button>
|
||||
</svelte:fragment>
|
||||
</AgChartWizard>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{:else if fieldType === 'number-tuple'}
|
||||
<div class="flex flex-row rounded-md bg-surface items-center h-full">
|
||||
<div class="relative w-full flex flex-row gap-2">
|
||||
<Label label="Y Low">
|
||||
<input
|
||||
class="text-xs px-2 border-y w-full flex flex-row items-center border-r rounded-r-md h-8"
|
||||
bind:value={componentInput.value[0]}
|
||||
type="number"
|
||||
/>
|
||||
</Label>
|
||||
<Label label="Y High">
|
||||
<input
|
||||
class="text-xs px-2 border-y w-full flex flex-row items-center border-r rounded-r-md h-8"
|
||||
bind:value={componentInput.value[1]}
|
||||
type="number"
|
||||
/>
|
||||
</Label>
|
||||
</div>
|
||||
</div>
|
||||
{:else}
|
||||
<div class="flex w-full flex-col">
|
||||
<JsonEditor
|
||||
small
|
||||
<div class="flex gap-1 relative w-full">
|
||||
<textarea
|
||||
rows="1"
|
||||
use:autosize
|
||||
on:keydown|stopPropagation
|
||||
placeholder={placeholder ?? 'Static value'}
|
||||
bind:value={componentInput.value}
|
||||
code={JSON.stringify(componentInput.value, null, 2)}
|
||||
class="!pr-12"
|
||||
/>
|
||||
</div>
|
||||
{/if}
|
||||
{:else if fieldType === 'array'}
|
||||
<ArrayStaticInputEditor {subFieldType} bind:componentInput on:deleteArrayItem />
|
||||
{:else if fieldType === 'schema'}
|
||||
<div class="w-full">
|
||||
<SchemaEditor bind:schema={componentInput.value} lightMode />
|
||||
</div>
|
||||
{:else if fieldType === 'ag-grid'}
|
||||
<div class="flex flex-row rounded-md bg-surface items-center h-full">
|
||||
<div class="relative w-full">
|
||||
<input
|
||||
class="text-xs px-2 border-y w-full flex flex-row items-center border-r rounded-r-md h-8"
|
||||
bind:value={componentInput.value.field}
|
||||
placeholder="Field"
|
||||
/>
|
||||
<div class="absolute top-1 right-1">
|
||||
<AgGridWizard bind:value={componentInput.value}>
|
||||
<svelte:fragment slot="trigger">
|
||||
<Button color="light" size="xs2" nonCaptureEvent={true}>
|
||||
<div class="flex flex-row items-center gap-2 text-xs font-normal">
|
||||
<Settings size={16} />
|
||||
</div>
|
||||
</Button>
|
||||
</svelte:fragment>
|
||||
</AgGridWizard>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{:else if fieldType === 'db-explorer' && componentInput.value != undefined}
|
||||
<div class="flex flex-row rounded-md bg-surface items-center h-full">
|
||||
<div class="relative w-full">
|
||||
<input
|
||||
class="text-xs px-2 border-y w-full flex flex-row items-center border-r rounded-r-md h-8"
|
||||
bind:value={componentInput.value.field}
|
||||
placeholder="Field"
|
||||
disabled
|
||||
/>
|
||||
<div class="absolute top-1 right-1">
|
||||
<DBExplorerWizard bind:value={componentInput.value}>
|
||||
<svelte:fragment slot="trigger">
|
||||
<Button color="light" size="xs2" nonCaptureEvent={true}>
|
||||
<div class="flex flex-row items-center gap-2 text-xs font-normal">
|
||||
<Settings size={16} />
|
||||
</div>
|
||||
</Button>
|
||||
</svelte:fragment>
|
||||
</DBExplorerWizard>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{:else if fieldType === 'table-column'}
|
||||
<div class="flex flex-row rounded-md bg-surface items-center h-full">
|
||||
<div class="relative w-full">
|
||||
<input
|
||||
class="text-xs px-2 border-y w-full flex flex-row items-center border-r rounded-r-md h-8"
|
||||
bind:value={componentInput.value.field}
|
||||
placeholder="Field"
|
||||
/>
|
||||
<div class="absolute top-1 right-1">
|
||||
<TableColumnWizard bind:column={componentInput.value}>
|
||||
<svelte:fragment slot="trigger">
|
||||
<Button color="light" size="xs2" nonCaptureEvent={true}>
|
||||
<div class="flex flex-row items-center gap-2 text-xs font-normal">
|
||||
<Settings size={16} />
|
||||
</div>
|
||||
</Button>
|
||||
</svelte:fragment>
|
||||
</TableColumnWizard>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{:else if fieldType === 'plotly'}
|
||||
<div class="flex flex-row rounded-md bg-surface items-center h-full">
|
||||
<div class="relative w-full">
|
||||
<input
|
||||
class="text-xs px-2 border-y w-full flex flex-row items-center border-r rounded-r-md h-8"
|
||||
bind:value={componentInput.value.name}
|
||||
placeholder="Dataset name"
|
||||
/>
|
||||
<div class="absolute top-1 right-1">
|
||||
<PlotlyWizard bind:value={componentInput.value} on:remove>
|
||||
<svelte:fragment slot="trigger">
|
||||
<Button color="light" size="xs2" nonCaptureEvent={true}>
|
||||
<div class="flex flex-row items-center gap-2 text-xs font-normal">
|
||||
<Settings size={16} />
|
||||
</div>
|
||||
</Button>
|
||||
</svelte:fragment>
|
||||
</PlotlyWizard>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{:else if fieldType === 'chartjs'}
|
||||
<div class="flex flex-row rounded-md bg-surface items-center h-full">
|
||||
<div class="relative w-full">
|
||||
<input
|
||||
class="text-xs px-2 border-y w-full flex flex-row items-center border-r rounded-r-md h-8"
|
||||
bind:value={componentInput.value.name}
|
||||
placeholder="Dataset name"
|
||||
/>
|
||||
<div class="absolute top-1 right-1">
|
||||
<ChartJSWizard bind:value={componentInput.value} on:remove>
|
||||
<svelte:fragment slot="trigger">
|
||||
<Button color="light" size="xs2" nonCaptureEvent={true}>
|
||||
<div class="flex flex-row items-center gap-2 text-xs font-normal">
|
||||
<Settings size={16} />
|
||||
</div>
|
||||
</Button>
|
||||
</svelte:fragment>
|
||||
</ChartJSWizard>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{:else}
|
||||
<div class="flex gap-1 relative w-full">
|
||||
<textarea
|
||||
rows="1"
|
||||
use:autosize
|
||||
on:keydown|stopPropagation
|
||||
placeholder={placeholder ?? 'Static value'}
|
||||
bind:value={componentInput.value}
|
||||
class="!pr-12"
|
||||
/>
|
||||
</div>
|
||||
{/if}
|
||||
{/if}
|
||||
{/key}
|
||||
|
||||
@@ -27,9 +27,11 @@ export type InputType =
|
||||
| 'plotly'
|
||||
| 'chartjs'
|
||||
| 'DecisionTreeNode'
|
||||
| 'ag-chart'
|
||||
| 'resource'
|
||||
| 'db-explorer'
|
||||
| 'db-table'
|
||||
| 'number-tuple'
|
||||
|
||||
// Connection to an output of another component
|
||||
// defined by the id of the component and the path of the output
|
||||
@@ -201,7 +203,9 @@ export type AppInput =
|
||||
| AppInputSpec<'array', object[], 'plotly'>
|
||||
| AppInputSpec<'array', object[], 'chartjs'>
|
||||
| AppInputSpec<'array', DecisionTreeNode, 'DecisionTreeNode'>
|
||||
| AppInputSpec<'array', object[], 'ag-chart'>
|
||||
| AppInputSpec<'resource', string>
|
||||
| AppInputSpec<'array', object[], 'number-tuple'>
|
||||
|
||||
export type RowAppInput = Extract<AppInput, { type: 'row' }>
|
||||
export type StaticAppInput = Extract<AppInput, { type: 'static' }>
|
||||
|
||||
@@ -0,0 +1,68 @@
|
||||
<script lang="ts">
|
||||
import { createEventDispatcher, getContext } from 'svelte'
|
||||
import { Popup } from '../common'
|
||||
import Label from '../Label.svelte'
|
||||
import { offset, flip, shift } from 'svelte-floating-ui/dom'
|
||||
import Button from '../common/button/Button.svelte'
|
||||
import OneOfInputSpecsEditor from '../apps/editor/settingsPanel/OneOfInputSpecsEditor.svelte'
|
||||
import type { AppViewerContext, GridItem, RichConfiguration } from '../apps/types'
|
||||
import { findGridItem } from '../apps/editor/appUtils'
|
||||
|
||||
const { selectedComponent, app } = getContext<AppViewerContext>('AppViewerContext')
|
||||
|
||||
type Dataset = {
|
||||
value: RichConfiguration
|
||||
name: string
|
||||
type: 'bar' | 'scatter' | 'line' | 'area' | 'range-bar'
|
||||
}
|
||||
|
||||
let component: GridItem | undefined = undefined
|
||||
|
||||
$: if (component === undefined && $selectedComponent && $app) {
|
||||
component = findGridItem($app, $selectedComponent[0])
|
||||
}
|
||||
|
||||
$: isEE = component?.data.type === 'agchartscomponentee'
|
||||
|
||||
export let value: Dataset | undefined = undefined
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
|
||||
function removeDataset() {
|
||||
dispatch('remove')
|
||||
}
|
||||
</script>
|
||||
|
||||
<Popup
|
||||
floatingConfig={{
|
||||
strategy: 'fixed',
|
||||
placement: 'left-end',
|
||||
middleware: [offset(8), flip(), shift()]
|
||||
}}
|
||||
containerClasses="border rounded-lg shadow-lg bg-surface p-4"
|
||||
>
|
||||
<svelte:fragment slot="button">
|
||||
<slot name="trigger" />
|
||||
</svelte:fragment>
|
||||
{#if value}
|
||||
<div class="flex flex-col w-96 p-2 gap-4">
|
||||
<Label label="Name">
|
||||
<input type="text" bind:value={value.name} />
|
||||
</Label>
|
||||
|
||||
<OneOfInputSpecsEditor
|
||||
key={'Data'}
|
||||
bind:oneOf={value.value}
|
||||
id={$selectedComponent?.[0] ?? ''}
|
||||
shouldCapitalize={true}
|
||||
resourceOnly={false}
|
||||
inputSpecsConfiguration={value.value?.['configuration']}
|
||||
labels={value.value?.['labels']}
|
||||
tooltip={value.value?.['tooltip']}
|
||||
disabledOptions={isEE ? [] : ['range-bar']}
|
||||
/>
|
||||
|
||||
<Button color="red" size="xs" on:click={removeDataset}>Remove dataset</Button>
|
||||
</div>
|
||||
{/if}
|
||||
</Popup>
|
||||
@@ -1,7 +1,7 @@
|
||||
import { ScriptService, type MainArgSignature, FlowService, Script } from '$lib/gen'
|
||||
import { get, writable } from 'svelte/store'
|
||||
import type { Schema, SchemaProperty, SupportedLanguage } from './common.js'
|
||||
import { emptySchema, sendUserToast, sortObject } from './utils.js'
|
||||
import { emptySchema, sortObject } from './utils.js'
|
||||
import { tick } from 'svelte'
|
||||
import init, {
|
||||
parse_deno,
|
||||
|
||||
@@ -629,6 +629,7 @@ const config = {
|
||||
{
|
||||
display: 'block',
|
||||
fontSize: theme('fontSize.sm'),
|
||||
boxShadow: theme('boxShadow.sm'),
|
||||
width: '100%',
|
||||
padding: `${theme('spacing.1')} ${theme('spacing.2')}`,
|
||||
border: `1px solid ${theme('colors.gray.300')}`,
|
||||
|
||||
Reference in New Issue
Block a user