feat(frontend): chartjs wizard (#2532)
* feat(frontend): chartjs wizard * feat(frontend): fix indentation * feat(frontend): done * feat(frontend): remove console.log * feat(frontend): fix dark mode + remove legacy components * feat(frontend): reorder component list * feat(frontend): fix dark mode
This commit is contained in:
@@ -0,0 +1,159 @@
|
||||
<script lang="ts">
|
||||
import { Chart } from 'svelte-chartjs'
|
||||
import { Chart as ChartJS, registerables, type ChartOptions } from 'chart.js'
|
||||
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 } 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'
|
||||
|
||||
export let id: string
|
||||
export let componentInput: AppInput | undefined
|
||||
export let configuration: RichConfigurations
|
||||
export let initializing: boolean | undefined = undefined
|
||||
export let customCss: ComponentCustomCSS<'piechartcomponent'> | undefined = undefined
|
||||
export let render: boolean
|
||||
export let datasets: RichConfiguration | undefined
|
||||
export let xData: RichConfiguration | undefined
|
||||
|
||||
type Dataset = {
|
||||
value: RichConfiguration
|
||||
name: string
|
||||
}
|
||||
|
||||
let resolvedDatasets: Dataset[]
|
||||
let resolvedDatasetsValues: Array<number[]> = []
|
||||
let resolvedXData: any[] = []
|
||||
|
||||
const { app, worldStore, darkMode } = getContext<AppViewerContext>('AppViewerContext')
|
||||
|
||||
const outputs = initOutput($worldStore, id, {
|
||||
result: undefined,
|
||||
loading: false
|
||||
})
|
||||
|
||||
ChartJS.register(...registerables)
|
||||
|
||||
let result: undefined = undefined
|
||||
|
||||
const resolvedConfig = initConfig(
|
||||
components['chartjscomponent'].initialData.configuration,
|
||||
configuration
|
||||
)
|
||||
|
||||
function hasScales() {
|
||||
const type = resolvedConfig?.type as string
|
||||
return type === 'bar' || type === 'line' || type === 'scatter' || type === 'bubble'
|
||||
}
|
||||
|
||||
$: options = {
|
||||
responsive: true,
|
||||
animation: false,
|
||||
maintainAspectRatio: false,
|
||||
...(resolvedConfig.options ?? {}),
|
||||
plugins: {
|
||||
legend: {
|
||||
labels: {
|
||||
color: $darkMode ? '#fff' : '#000'
|
||||
}
|
||||
}
|
||||
},
|
||||
...(hasScales()
|
||||
? {
|
||||
scales: {
|
||||
y: {
|
||||
ticks: { color: $darkMode ? '#eee' : '#333' },
|
||||
grid: { color: $darkMode ? '#555' : '#ddd' }
|
||||
},
|
||||
x: {
|
||||
ticks: { color: $darkMode ? '#eee' : '#333' },
|
||||
grid: { color: $darkMode ? '#555' : '#ddd' }
|
||||
}
|
||||
}
|
||||
}
|
||||
: {})
|
||||
} as ChartOptions
|
||||
|
||||
$: data =
|
||||
datasets && xData && resolvedDatasets
|
||||
? {
|
||||
labels: resolvedXData,
|
||||
datasets: resolvedDatasets.map((d, index) => {
|
||||
return {
|
||||
label: d.name,
|
||||
data: resolvedDatasetsValues[index]
|
||||
}
|
||||
})
|
||||
}
|
||||
: result
|
||||
|
||||
let css = initCss($app.css?.chartjscomponent, customCss)
|
||||
</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}
|
||||
bind:resolvedConfig={resolvedDatasetsValues[index]}
|
||||
configuration={resolvedDataset.value}
|
||||
/>
|
||||
{/each}
|
||||
{/if}
|
||||
|
||||
{#each Object.keys(components['chartjscomponentv2'].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-chartjs')}
|
||||
style={css?.container?.style ?? ''}
|
||||
>
|
||||
{#if data && resolvedConfig.type}
|
||||
{#key resolvedConfig.type}
|
||||
{#key options}
|
||||
<Chart type={resolvedConfig.type} {data} {options} />
|
||||
{/key}
|
||||
{/key}
|
||||
{/if}
|
||||
</div>
|
||||
</RunnableWrapper>
|
||||
@@ -55,6 +55,7 @@
|
||||
import AppLogsComponent from '../../components/display/AppLogsComponent.svelte'
|
||||
import AppFlowStatusComponent from '../../components/display/AppFlowStatusComponent.svelte'
|
||||
import AppChartJs from '../../components/display/AppChartJs.svelte'
|
||||
import AppChartJsV2 from '../../components/display/AppChartJsV2.svelte'
|
||||
import AppQuillEditor from '../../components/inputs/AppQuillEditor.svelte'
|
||||
import AppList from '../../components/layout/AppList.svelte'
|
||||
import AppJobIdLogComponent from '../../components/display/AppJobIdLogComponent.svelte'
|
||||
@@ -645,6 +646,17 @@
|
||||
componentInput={component.componentInput}
|
||||
{render}
|
||||
/>
|
||||
{:else if component.type === 'chartjscomponentv2'}
|
||||
<AppChartJsV2
|
||||
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 === 'carousellistcomponent'}
|
||||
<AppCarouselList
|
||||
id={component.id}
|
||||
|
||||
@@ -92,6 +92,10 @@ export type RunFormComponent = BaseComponent<'runformcomponent'>
|
||||
export type BarChartComponent = BaseComponent<'barchartcomponent'>
|
||||
export type PieChartComponent = BaseComponent<'piechartcomponent'>
|
||||
export type ChartJsComponent = BaseComponent<'chartjscomponent'>
|
||||
export type ChartJsComponentV2 = BaseComponent<'chartjscomponentv2'> & {
|
||||
xData: RichConfiguration | undefined
|
||||
datasets: RichConfiguration | undefined
|
||||
}
|
||||
|
||||
export type ScatterChartComponent = BaseComponent<'scatterchartcomponent'>
|
||||
|
||||
@@ -208,6 +212,7 @@ export type TypedComponent =
|
||||
| ChartJsComponent
|
||||
| CarouselListComponent
|
||||
| PlotlyComponentV2
|
||||
| ChartJsComponentV2
|
||||
|
||||
export type AppComponent = BaseAppComponent & TypedComponent
|
||||
|
||||
@@ -1112,6 +1117,33 @@ export const components = {
|
||||
}
|
||||
}
|
||||
},
|
||||
chartjscomponentv2: {
|
||||
name: 'ChartJs',
|
||||
icon: PieChart,
|
||||
documentationLink: `${documentationBaseUrl}/chartjs`,
|
||||
dims: '2:8-6:8' as AppComponentDimensions,
|
||||
customCss: {
|
||||
container: { class: '', style: '' }
|
||||
},
|
||||
initialData: {
|
||||
configuration: {
|
||||
type: {
|
||||
type: 'static',
|
||||
|
||||
fieldType: 'select',
|
||||
selectOptions: selectOptions.chartTypeOptions,
|
||||
value: 'pie'
|
||||
},
|
||||
options: {
|
||||
type: 'static',
|
||||
fieldType: 'object',
|
||||
value: {},
|
||||
tooltip: 'ChartJs options object'
|
||||
}
|
||||
},
|
||||
componentInput: undefined
|
||||
}
|
||||
},
|
||||
barchartcomponent: {
|
||||
name: 'Bar/Line Chart',
|
||||
icon: BarChart4,
|
||||
|
||||
@@ -74,15 +74,7 @@ const tables: ComponentSet = {
|
||||
|
||||
const charts: ComponentSet = {
|
||||
title: 'Charts',
|
||||
components: [
|
||||
'barchartcomponent',
|
||||
'piechartcomponent',
|
||||
'vegalitecomponent',
|
||||
'plotlycomponentv2',
|
||||
'scatterchartcomponent',
|
||||
'timeseriescomponent',
|
||||
'chartjscomponent'
|
||||
]
|
||||
components: ['plotlycomponentv2', 'chartjscomponentv2', 'vegalitecomponent']
|
||||
} as const
|
||||
|
||||
export const COMPONENT_SETS = [layout, tabs, buttons, inputs, tables, display, charts] as const
|
||||
|
||||
@@ -670,6 +670,9 @@ export const quickStyleProperties: Record<
|
||||
chartjscomponent: {
|
||||
container: containerDefaultProps
|
||||
},
|
||||
chartjscomponentv2: {
|
||||
container: containerDefaultProps
|
||||
},
|
||||
vegalitecomponent: {},
|
||||
containercomponent: {
|
||||
container: containerDefaultProps
|
||||
|
||||
@@ -66,6 +66,16 @@
|
||||
.toString(16)
|
||||
.padEnd(6, '0')}`
|
||||
})
|
||||
} else if (subFieldType === 'chartjs') {
|
||||
value.push({
|
||||
value: {
|
||||
type: 'static',
|
||||
fieldType: 'array',
|
||||
subFieldType: 'number',
|
||||
value: [2, 4, 5, 6]
|
||||
},
|
||||
name: 'New dataset'
|
||||
})
|
||||
}
|
||||
} else {
|
||||
value.push('')
|
||||
@@ -181,6 +191,7 @@
|
||||
subFieldType={raw ? 'object' : subFieldType}
|
||||
bind:componentInput
|
||||
bind:value={item.value}
|
||||
on:remove={() => deleteElementByType(index)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -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={`Plotly configuration`}
|
||||
tooltip="The configuration is divided into two parts: X-axis data and an array of datasets. Each dataset hold the data fot he Y-axis and the configuration for the plot (type, color, 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>
|
||||
@@ -4,6 +4,7 @@
|
||||
import ToggleButtonGroup from '$lib/components/common/toggleButton-v2/ToggleButtonGroup.svelte'
|
||||
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 type { InputConnectionEval } from '../../inputType'
|
||||
@@ -17,7 +18,7 @@
|
||||
|
||||
onMount(() => {
|
||||
if (
|
||||
component.type === 'plotlycomponentv2' &&
|
||||
(component.type === 'plotlycomponentv2' || component.type === 'chartjscomponentv2') &&
|
||||
component.componentInput === undefined &&
|
||||
component.datasets === undefined
|
||||
) {
|
||||
@@ -37,30 +38,80 @@
|
||||
color: string
|
||||
}
|
||||
|
||||
function resolveConfiguration(
|
||||
x: RichConfiguration | undefined,
|
||||
connections: InputConnectionEval[]
|
||||
): string | undefined {
|
||||
if (!x) return undefined
|
||||
if (x.type === 'evalv2') {
|
||||
connections.push(...x.connections)
|
||||
return x.expr
|
||||
}
|
||||
if (x.type === 'static') return `[${x.value}]`
|
||||
return undefined
|
||||
}
|
||||
|
||||
function handleSelected(selected: string) {
|
||||
if (component.type !== 'plotlycomponentv2') return
|
||||
if (component.type === 'plotlycomponentv2') {
|
||||
if (selected === 'ui-editor') {
|
||||
convertToUIEditorCallback = () => {
|
||||
component.componentInput = undefined
|
||||
setUpUIEditor()
|
||||
}
|
||||
|
||||
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') {
|
||||
convertToJson()
|
||||
}
|
||||
} else if (component.type === 'chartjscomponentv2') {
|
||||
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') {
|
||||
convertToJson(component)
|
||||
setTimeout(() => {
|
||||
const activeElement = document.activeElement as HTMLElement
|
||||
activeElement?.blur()
|
||||
document.body.focus()
|
||||
})
|
||||
} else if (selected === 'json') {
|
||||
convertChartJSToJson()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function setUpUIEditor() {
|
||||
if (component.type !== 'plotlycomponentv2') return
|
||||
if (component.type === 'plotlycomponentv2') {
|
||||
component.datasets = createPlotlyComponentDataset()
|
||||
component.xData = createXData()
|
||||
} else if (component.type === 'chartjscomponentv2') {
|
||||
component.datasets = createChartjsComponentDataset()
|
||||
component.xData = createXData()
|
||||
}
|
||||
}
|
||||
|
||||
component.datasets = createPlotlyComponentDataset()
|
||||
component.xData = createXData()
|
||||
function createChartjsComponentDataset(): RichConfiguration {
|
||||
return {
|
||||
type: 'static',
|
||||
fieldType: 'array',
|
||||
subFieldType: 'chartjs',
|
||||
value: [
|
||||
{
|
||||
value: {
|
||||
type: 'static',
|
||||
fieldType: 'array',
|
||||
subFieldType: 'number',
|
||||
value: [25, 25, 50]
|
||||
},
|
||||
name: 'Dataset 1'
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
function createPlotlyComponentDataset(): RichConfiguration {
|
||||
@@ -95,20 +146,11 @@
|
||||
}
|
||||
}
|
||||
|
||||
function resolveConfiguration(
|
||||
x: RichConfiguration | undefined,
|
||||
connections: InputConnectionEval[]
|
||||
): string | undefined {
|
||||
if (!x) return undefined
|
||||
if (x.type === 'evalv2') {
|
||||
connections.push(...x.connections)
|
||||
return x.expr
|
||||
function convertToJson() {
|
||||
if (component.type !== 'plotlycomponentv2') {
|
||||
return
|
||||
}
|
||||
if (x.type === 'static') return `[${x.value}]`
|
||||
return undefined
|
||||
}
|
||||
|
||||
function convertToJson(component: any) {
|
||||
const connections: InputConnectionEval[] = []
|
||||
const xDataResolved = resolveConfiguration(component.xData, connections)
|
||||
|
||||
@@ -133,6 +175,42 @@
|
||||
component.xData = undefined
|
||||
}
|
||||
|
||||
function convertChartJSToJson() {
|
||||
if (component.type !== 'chartjscomponentv2') {
|
||||
return
|
||||
}
|
||||
|
||||
const connections: InputConnectionEval[] = []
|
||||
const xDataResolved = resolveConfiguration(component.xData, connections)
|
||||
|
||||
if (component.datasets === undefined || component.datasets.type !== 'static') return
|
||||
|
||||
const datasets = component.datasets?.value
|
||||
|
||||
component.componentInput = {
|
||||
type: 'evalv2',
|
||||
fieldType: 'object',
|
||||
noStatic: true,
|
||||
expr: `({
|
||||
\t"labels": ${xDataResolved},
|
||||
\t"datasets": [\n${
|
||||
datasets
|
||||
.map(
|
||||
(rawDataset: Dataset) => `\t\t{
|
||||
\t\t\tdata:${resolveConfiguration(rawDataset.value, connections)},
|
||||
\t\t\tlabel: "${rawDataset.name}"
|
||||
\t\t}`
|
||||
)
|
||||
.join(',\n') || ''
|
||||
}\n\t]
|
||||
})`,
|
||||
connections: connections.filter(Boolean)
|
||||
}
|
||||
|
||||
component.datasets = undefined
|
||||
component.xData = undefined
|
||||
}
|
||||
|
||||
function datasetToJson(
|
||||
dataset: Dataset,
|
||||
xDataExpr: string | undefined,
|
||||
@@ -151,7 +229,7 @@
|
||||
}
|
||||
</script>
|
||||
|
||||
{#if component.type === 'plotlycomponentv2'}
|
||||
{#if component.type === 'plotlycomponentv2' || component.type === 'chartjscomponentv2'}
|
||||
<div class="p-2">
|
||||
<ToggleButtonGroup
|
||||
bind:selected
|
||||
@@ -174,11 +252,19 @@
|
||||
|
||||
{#if selected === 'ui-editor'}
|
||||
{#key renderCount}
|
||||
<PlotlyRichEditor
|
||||
id={component.id}
|
||||
bind:datasets={component.datasets}
|
||||
bind:xData={component.xData}
|
||||
/>
|
||||
{#if component.type === 'plotlycomponentv2'}
|
||||
<PlotlyRichEditor
|
||||
id={component.id}
|
||||
bind:datasets={component.datasets}
|
||||
bind:xData={component.xData}
|
||||
/>
|
||||
{:else if component.type === 'chartjscomponentv2'}
|
||||
<ChartJSRichEditor
|
||||
id={component.id}
|
||||
bind:datasets={component.datasets}
|
||||
bind:xData={component.xData}
|
||||
/>
|
||||
{/if}
|
||||
{/key}
|
||||
{:else}
|
||||
<slot />
|
||||
|
||||
@@ -15,4 +15,4 @@
|
||||
$: fakeComponentInput && (value = fakeComponentInput.value)
|
||||
</script>
|
||||
|
||||
<StaticInputEditor fieldType={subFieldType} bind:componentInput={fakeComponentInput} />
|
||||
<StaticInputEditor fieldType={subFieldType} bind:componentInput={fakeComponentInput} on:remove />
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
import AgGridWizard from '$lib/components/wizards/AgGridWizard.svelte'
|
||||
import TableColumnWizard from '$lib/components/wizards/TableColumnWizard.svelte'
|
||||
import PlotlyWizard from '$lib/components/wizards/PlotlyWizard.svelte'
|
||||
import ChartJSWizard from '$lib/components/wizards/ChartJSWizard.svelte'
|
||||
|
||||
export let componentInput: StaticInput<any> | undefined
|
||||
export let fieldType: InputType | undefined = undefined
|
||||
@@ -171,7 +172,7 @@
|
||||
placeholder="Dataset name"
|
||||
/>
|
||||
<div class="absolute top-1 right-1">
|
||||
<PlotlyWizard bind:value={componentInput.value}>
|
||||
<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">
|
||||
@@ -183,6 +184,27 @@
|
||||
</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
|
||||
|
||||
@@ -25,6 +25,7 @@ export type InputType =
|
||||
| 'ag-grid'
|
||||
| 'table-column'
|
||||
| 'plotly'
|
||||
| 'chartjs'
|
||||
|
||||
// Connection to an output of another component
|
||||
// defined by the id of the component and the path of the output
|
||||
@@ -192,6 +193,7 @@ export type AppInput =
|
||||
| AppInputSpec<'array', object[], 'ag-grid'>
|
||||
| AppInputSpec<'array', object[], 'table-column'>
|
||||
| AppInputSpec<'array', object[], 'plotly'>
|
||||
| AppInputSpec<'array', object[], 'chartjs'>
|
||||
|
||||
export type RowAppInput = Extract<AppInput, { type: 'row' }>
|
||||
export type StaticAppInput = Extract<AppInput, { type: 'static' }>
|
||||
|
||||
64
frontend/src/lib/components/wizards/ChartJSWizard.svelte
Normal file
64
frontend/src/lib/components/wizards/ChartJSWizard.svelte
Normal file
@@ -0,0 +1,64 @@
|
||||
<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 InputsSpecEditor from '../apps/editor/settingsPanel/InputsSpecEditor.svelte'
|
||||
import type { AppViewerContext, RichConfiguration } from '../apps/types'
|
||||
|
||||
const { selectedComponent } = getContext<AppViewerContext>('AppViewerContext')
|
||||
|
||||
type Dataset = {
|
||||
value: RichConfiguration
|
||||
name: string
|
||||
}
|
||||
|
||||
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>
|
||||
|
||||
<InputsSpecEditor
|
||||
key={'Data'}
|
||||
bind:componentInput={value.value}
|
||||
id={$selectedComponent?.[0] ?? ''}
|
||||
userInputEnabled={false}
|
||||
shouldCapitalize={true}
|
||||
resourceOnly={false}
|
||||
fieldType={value.value?.['fieldType']}
|
||||
subFieldType={value.value?.['subFieldType']}
|
||||
format={value.value?.['format']}
|
||||
selectOptions={value.value?.['selectOptions']}
|
||||
tooltip={value.value?.['tooltip']}
|
||||
fileUpload={value.value?.['fileUpload']}
|
||||
placeholder={value.value?.['placeholder']}
|
||||
customTitle={value.value?.['customTitle']}
|
||||
displayType={false}
|
||||
/>
|
||||
|
||||
<Button color="red" size="xs" on:click={removeDataset}>Remove dataset</Button>
|
||||
</div>
|
||||
{/if}
|
||||
</Popup>
|
||||
Reference in New Issue
Block a user