feat(frontend): App carousel (#1956)

* feat(frontend): app carousel

* carousel wip

* feat(frontend): add carousel component

* feat(frontend): add slide handler

* feat(frontend): Fix styling issues

* feat(frontend): Update subgrid focus on page change

* feat(frontend): fix subgrid issues + add carousel list

* feat(frontend): fix carousel width issues

* feat(frontend): fix build

* feat(frontend): fix build

* feat(frontend): revert changes

* feat(frontend): add missing tooltips

* feat(frontend): remove autoplay

* feat(frontend): add missing tooltip + add select for timings

* feat(frontend): simplify carousel

* feat(frontend): fix render when data changes

---------

Co-authored-by: Ruben Fiszel <ruben@windmill.dev>
This commit is contained in:
Faton Ramadani
2023-07-31 17:56:38 +02:00
committed by GitHub
parent b4268b1db1
commit 43cb5a985e
9 changed files with 292 additions and 4 deletions
+67
View File
@@ -33,6 +33,7 @@
"openai": "^4.0.0-beta.4",
"quill": "^1.3.7",
"svelte-autosize": "^1.0.1",
"svelte-carousel": "^1.0.25",
"svelte-chartjs": "^3.1.0",
"svelte-dnd-action": "^0.9.22",
"svelte-exmarkdown": "^2.1.0",
@@ -3497,6 +3498,16 @@
"integrity": "sha512-/pjZsA1b4RPHbeWZQn66SWS8nZZWLQQ23oE3Eam7aroEFGEvwKAsJfZ9ytiEMycfzXWpca4FA9QIOehf7PocBQ==",
"dev": true
},
"node_modules/easy-reactive": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/easy-reactive/-/easy-reactive-1.0.4.tgz",
"integrity": "sha512-dFVouegPctMf6XT5si+sRByBWUQE30n99TW0Y45Y7mccmNsbSl4uXA2sBGI8zCDZqXjhb8ouA3wVVhSSGpXk5Q==",
"dependencies": {
"lodash.clonedeep": "^4.5.0",
"lodash.get": "^4.4.2",
"lodash.isequal": "^4.5.0"
}
},
"node_modules/electron-to-chromium": {
"version": "1.4.348",
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.348.tgz",
@@ -5136,11 +5147,26 @@
"integrity": "sha512-aVx8ztPv7/2ULbArGJ2Y42bG1mEQ5mGjpdvrbJcJFU3TbYybe+QlLS4pst9zV52ymy2in1KpFPiZnAOATxD4+Q==",
"dev": true
},
"node_modules/lodash.clonedeep": {
"version": "4.5.0",
"resolved": "https://registry.npmjs.org/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz",
"integrity": "sha512-H5ZhCF25riFd9uB5UCkVKo61m3S/xZk1x4wA6yp/L3RFP6Z/eHH1ymQcGLo7J3GMPfm0V/7m1tryHuGVxpqEBQ=="
},
"node_modules/lodash.debounce": {
"version": "4.0.8",
"resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz",
"integrity": "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow=="
},
"node_modules/lodash.get": {
"version": "4.4.2",
"resolved": "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz",
"integrity": "sha512-z+Uw/vLuy6gQe8cfaFWD7p0wVv8fJl3mbzXh33RS+0oW2wvUqiRXiQ69gLWSLpgB5/6sU+r6BlQR0MBILadqTQ=="
},
"node_modules/lodash.isequal": {
"version": "4.5.0",
"resolved": "https://registry.npmjs.org/lodash.isequal/-/lodash.isequal-4.5.0.tgz",
"integrity": "sha512-pDo3lu8Jhfjqls6GkMgpahsF9kCyayhgykjyLMNFTKWrpVdAQtYyB4muAMWozBB4ig/dtWAmsMxLEI8wuz+DYQ=="
},
"node_modules/lodash.isplainobject": {
"version": "4.0.6",
"resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz",
@@ -8656,6 +8682,14 @@
"colord": "^2.9.3"
}
},
"node_modules/svelte-carousel": {
"version": "1.0.25",
"resolved": "https://registry.npmjs.org/svelte-carousel/-/svelte-carousel-1.0.25.tgz",
"integrity": "sha512-vR/AbGYwlh3fgWaB5PJg+iqZFvoVCJq4JWdmlXvPtdG9TLlKJWBcoiKlMZt3MnFF7AqNW46tOH/O+uVMWQzyWQ==",
"dependencies": {
"easy-reactive": "1.0.4"
}
},
"node_modules/svelte-chartjs": {
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/svelte-chartjs/-/svelte-chartjs-3.1.2.tgz",
@@ -12305,6 +12339,16 @@
"integrity": "sha512-/pjZsA1b4RPHbeWZQn66SWS8nZZWLQQ23oE3Eam7aroEFGEvwKAsJfZ9ytiEMycfzXWpca4FA9QIOehf7PocBQ==",
"dev": true
},
"easy-reactive": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/easy-reactive/-/easy-reactive-1.0.4.tgz",
"integrity": "sha512-dFVouegPctMf6XT5si+sRByBWUQE30n99TW0Y45Y7mccmNsbSl4uXA2sBGI8zCDZqXjhb8ouA3wVVhSSGpXk5Q==",
"requires": {
"lodash.clonedeep": "^4.5.0",
"lodash.get": "^4.4.2",
"lodash.isequal": "^4.5.0"
}
},
"electron-to-chromium": {
"version": "1.4.348",
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.348.tgz",
@@ -13532,11 +13576,26 @@
"integrity": "sha512-aVx8ztPv7/2ULbArGJ2Y42bG1mEQ5mGjpdvrbJcJFU3TbYybe+QlLS4pst9zV52ymy2in1KpFPiZnAOATxD4+Q==",
"dev": true
},
"lodash.clonedeep": {
"version": "4.5.0",
"resolved": "https://registry.npmjs.org/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz",
"integrity": "sha512-H5ZhCF25riFd9uB5UCkVKo61m3S/xZk1x4wA6yp/L3RFP6Z/eHH1ymQcGLo7J3GMPfm0V/7m1tryHuGVxpqEBQ=="
},
"lodash.debounce": {
"version": "4.0.8",
"resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz",
"integrity": "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow=="
},
"lodash.get": {
"version": "4.4.2",
"resolved": "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz",
"integrity": "sha512-z+Uw/vLuy6gQe8cfaFWD7p0wVv8fJl3mbzXh33RS+0oW2wvUqiRXiQ69gLWSLpgB5/6sU+r6BlQR0MBILadqTQ=="
},
"lodash.isequal": {
"version": "4.5.0",
"resolved": "https://registry.npmjs.org/lodash.isequal/-/lodash.isequal-4.5.0.tgz",
"integrity": "sha512-pDo3lu8Jhfjqls6GkMgpahsF9kCyayhgykjyLMNFTKWrpVdAQtYyB4muAMWozBB4ig/dtWAmsMxLEI8wuz+DYQ=="
},
"lodash.isplainobject": {
"version": "4.0.6",
"resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz",
@@ -15964,6 +16023,14 @@
"colord": "^2.9.3"
}
},
"svelte-carousel": {
"version": "1.0.25",
"resolved": "https://registry.npmjs.org/svelte-carousel/-/svelte-carousel-1.0.25.tgz",
"integrity": "sha512-vR/AbGYwlh3fgWaB5PJg+iqZFvoVCJq4JWdmlXvPtdG9TLlKJWBcoiKlMZt3MnFF7AqNW46tOH/O+uVMWQzyWQ==",
"requires": {
"easy-reactive": "1.0.4"
}
},
"svelte-chartjs": {
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/svelte-chartjs/-/svelte-chartjs-3.1.2.tgz",
+1
View File
@@ -96,6 +96,7 @@
"openai": "^4.0.0-beta.4",
"quill": "^1.3.7",
"svelte-autosize": "^1.0.1",
"svelte-carousel": "^1.0.25",
"svelte-chartjs": "^3.1.0",
"svelte-dnd-action": "^0.9.22",
"svelte-exmarkdown": "^2.1.0",
@@ -0,0 +1,176 @@
<script lang="ts">
import { getContext } from 'svelte'
import { initConfig, initOutput } from '../../editor/appUtils'
import SubGridEditor from '../../editor/SubGridEditor.svelte'
import type { AppViewerContext, ComponentCustomCSS, RichConfigurations } from '../../types'
import { concatCustomCss } from '../../utils'
import InitializeComponent from '../helpers/InitializeComponent.svelte'
import type { AppInput } from '../../inputType'
import ResolveConfig from '../helpers/ResolveConfig.svelte'
import { components } from '../../editor/component'
import RunnableWrapper from '../helpers/RunnableWrapper.svelte'
import ListWrapper from '../layout/ListWrapper.svelte'
import Carousel from 'svelte-carousel'
import { ArrowLeftCircle, ArrowRightCircle } from 'lucide-svelte'
import { Button } from '$lib/components/common'
export let id: string
export let componentInput: AppInput | undefined
export let configuration: RichConfigurations
export let customCss: ComponentCustomCSS<'containercomponent'> | undefined = undefined
export let render: boolean
export let initializing: boolean | undefined
export let componentContainerHeight: number
const { app, focusedGrid, selectedComponent, worldStore, connectingInput } =
getContext<AppViewerContext>('AppViewerContext')
const outputs = initOutput($worldStore, id, {
result: undefined,
loading: false,
inputs: {}
})
const resolvedConfig = initConfig(
components['carousellistcomponent'].initialData.configuration,
configuration
)
function onFocus() {
$focusedGrid = {
parentComponentId: id,
subGridIndex: 0
}
}
$: css = concatCustomCss($app.css?.containercomponent, customCss)
let result: any[] | undefined = undefined
let inputs = {}
let carousel: Carousel
$: $selectedComponent?.includes(id) &&
$focusedGrid === undefined &&
($focusedGrid = {
parentComponentId: id,
subGridIndex: 0
})
</script>
{#each Object.keys(components['carousellistcomponent'].initialData.configuration) as key (key)}
<ResolveConfig
{id}
{key}
bind:resolvedConfig={resolvedConfig[key]}
configuration={configuration[key]}
/>
{/each}
<InitializeComponent {id} />
<RunnableWrapper
render={true}
{outputs}
autoRefresh
{componentInput}
{id}
bind:initializing
bind:result
>
<div class="w-full flex flex-wrap overflow-auto divide-y max-h-full">
{#if $app.subgrids?.[`${id}-0`]}
{#if Array.isArray(result) && result.length > 0}
{#key result}
<Carousel
particlesToShow={1}
particlesToScroll={1}
autoplay={false}
autoplayProgressVisible={false}
timingFunction={resolvedConfig.timingFunction}
dots={true}
arrows={true}
swiping={false}
bind:this={carousel}
let:currentPageIndex
on:pageChange={(event) => {
$focusedGrid = {
parentComponentId: id,
subGridIndex: event.detail
}
}}
>
<div slot="prev" class="h-full flex justify-center flex-col p-2">
<div>
<Button
color="light"
on:click={() => {
const pagesCount = result?.length ?? 0
if (currentPageIndex > 0) {
carousel.goTo(currentPageIndex - 1)
} else {
carousel.goTo(pagesCount - 1)
}
}}
>
<ArrowLeftCircle size={16} />
</Button>
</div>
</div>
<div slot="next" class="h-full flex justify-center flex-col p-2">
<div>
<Button
color="light"
on:click={() => {
const pagesCount = result?.length ?? 0
if (currentPageIndex < pagesCount - 1) {
carousel.goTo(currentPageIndex + 1)
} else {
carousel.goTo(0)
}
}}
>
<ArrowRightCircle size={16} />
</Button>
</div>
</div>
{#each result ?? [] as value, index}
<div class="overflow-auto w-full">
<ListWrapper
on:inputsChange={() => {
outputs?.inputs.set(inputs, true)
}}
bind:inputs
{value}
{index}
>
<SubGridEditor
{id}
visible={render}
class={css?.container?.class}
style={css?.container?.style}
subGridId={`${id}-0`}
containerHeight={componentContainerHeight - 40}
on:focus={() => {
if (!$connectingInput.opened) {
$selectedComponent = [id]
}
onFocus()
}}
/>
</ListWrapper>
</div>
{/each}
</Carousel>
{/key}
{:else}
<ListWrapper disabled value={undefined} index={0}>
<SubGridEditor visible={false} {id} subGridId={`${id}-0`} />
</ListWrapper>
{#if !Array.isArray(result)}
<div class="text-center text-tertiary">Input data is not an array</div>
{/if}
{/if}
{/if}
</div>
</RunnableWrapper>
@@ -54,6 +54,7 @@
import AppList from '../../components/layout/AppList.svelte'
import AppJobIdLogComponent from '../../components/display/AppJobIdLogComponent.svelte'
import AppJobIdFlowStatus from '../../components/display/AppJobIdFlowStatus.svelte'
import AppCarouselList from '../../components/display/AppCarouselList.svelte'
export let component: AppComponent
export let selected: boolean
@@ -600,6 +601,15 @@
componentInput={component.componentInput}
{render}
/>
{:else if component.type === 'carousellistcomponent'}
<AppCarouselList
id={component.id}
configuration={component.configuration}
componentInput={component.componentInput}
{componentContainerHeight}
{render}
bind:initializing
/>
{/if}
</div>
</div>
@@ -137,6 +137,8 @@ export type Schemaformcomponent = BaseComponent<'schemaformcomponent'>
export type SelectTabComponent = BaseComponent<'selecttabcomponent'>
export type SelectStepComponent = BaseComponent<'selectstepcomponent'>
export type CarouselListComponent = BaseComponent<'carousellistcomponent'>
export type TypedComponent =
| DisplayComponent
| LogComponent
@@ -192,6 +194,7 @@ export type TypedComponent =
| SelectStepComponent
| DownloadComponent
| ChartJsComponent
| CarouselListComponent
export type AppComponent = BaseAppComponent & TypedComponent
@@ -281,6 +284,7 @@ export const selectOptions = {
'radar',
'scatter'
] as ChartType[],
animationTimingFunctionOptions: ['linear', 'ease', 'ease-in', 'ease-out', 'ease-in-out'],
prose: ['sm', 'Default', 'lg', 'xl', '2xl']
}
const labels = {
@@ -1920,6 +1924,34 @@ This is a paragraph.
tabs: ['First', 'Second'] as string[]
}
},
carousellistcomponent: {
name: 'Carousel List',
icon: ListIcon,
documentationLink: `${documentationBaseUrl}#list`,
dims: '3:8-12:8' as AppComponentDimensions,
customCss: {
container: { class: '', style: '' }
},
initialData: {
configuration: {
timingFunction: {
fieldType: 'select',
type: 'static',
onlyStatic: true,
selectOptions: selectOptions.animationTimingFunctionOptions,
value: 'linear',
tooltip: 'See https://developer.mozilla.org/en-US/docs/Web/CSS/animation-timing-function'
}
},
componentInput: {
type: 'static',
fieldType: 'array',
subFieldType: 'object',
value: [{ foo: 1 }, { foo: 2 }, { foo: 3 }] as object[]
},
numberOfSubgrids: 1
}
},
iconcomponent: {
name: 'Icon',
icon: Smile,
@@ -17,7 +17,8 @@ const layout: ComponentSet = {
'verticalsplitpanescomponent',
'horizontalsplitpanescomponent',
'modalcomponent',
'steppercomponent'
'steppercomponent',
'carousellistcomponent'
]
} as const
@@ -643,6 +643,9 @@ export const quickStyleProperties: Record<
header: [...containerDefaultProps, typographyGrouping],
container: containerDefaultProps
},
carousellistcomponent: {
container: containerDefaultProps
},
flowstatuscomponent: {
header: [...containerDefaultProps, typographyGrouping],
container: containerDefaultProps
@@ -18,7 +18,7 @@
export let canDisableTabs: boolean = false
// Migration code
$: if (tabs.length !== disabledTabs?.length && canDisableTabs) {
$: if (tabs?.length !== disabledTabs?.length && canDisableTabs) {
disabledTabs = Array(tabs.length).fill({
type: 'static',
value: false,
@@ -247,8 +247,6 @@ export function concatCustomCss<T extends Record<string, ComponentCssProperty>>(
const compStyle = v?.style?.trim() || ''
const compEnding = compStyle?.endsWith(';') || !compStyle ? ' ' : ';'
console.log(key, v)
return [
key,
{