Compare commits
6 Commits
component-
...
tutorials
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b76426ba83 | ||
|
|
1cb2a177c6 | ||
|
|
db87577a1b | ||
|
|
c219eb0ee9 | ||
|
|
bfe5b56c99 | ||
|
|
4453690521 |
42
frontend/package-lock.json
generated
42
frontend/package-lock.json
generated
@@ -64,6 +64,7 @@
|
||||
"postcss-load-config": "^4.0.1",
|
||||
"prettier": "^2.8.3",
|
||||
"prettier-plugin-svelte": "^2.9.0",
|
||||
"shepherd.js": "^10.0.1",
|
||||
"simple-svelte-autocomplete": "^2.5.1",
|
||||
"stylelint-config-recommended": "^9.0.0",
|
||||
"svelte": "^3.55.1",
|
||||
@@ -6653,6 +6654,24 @@
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/shepherd.js": {
|
||||
"version": "10.0.1",
|
||||
"resolved": "https://registry.npmjs.org/shepherd.js/-/shepherd.js-10.0.1.tgz",
|
||||
"integrity": "sha512-R32v4b4b0N1gK/vxvRtdhty+SBZlBPcPTSYCwcaAQvFd0n6Xki7cRH6Sx0oD9WB/HCkqCNM1msyIyylXmq635w==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@popperjs/core": "^2.11.5",
|
||||
"deepmerge": "^4.2.2",
|
||||
"smoothscroll-polyfill": "^0.4.4"
|
||||
},
|
||||
"engines": {
|
||||
"node": "12.* || 14.* || >= 16"
|
||||
},
|
||||
"funding": {
|
||||
"type": "individual",
|
||||
"url": "https://github.com/sponsors/rwwagner90"
|
||||
}
|
||||
},
|
||||
"node_modules/signal-exit": {
|
||||
"version": "3.0.7",
|
||||
"resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz",
|
||||
@@ -6739,6 +6758,12 @@
|
||||
"url": "https://github.com/chalk/slice-ansi?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/smoothscroll-polyfill": {
|
||||
"version": "0.4.4",
|
||||
"resolved": "https://registry.npmjs.org/smoothscroll-polyfill/-/smoothscroll-polyfill-0.4.4.tgz",
|
||||
"integrity": "sha512-TK5ZA9U5RqCwMpfoMq/l1mrH0JAR7y7KRvOBx0n2869aLxch+gT9GhN3yUfjiw+d/DiF1mKo14+hd62JyMmoBg==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/sorcery": {
|
||||
"version": "0.11.0",
|
||||
"resolved": "https://registry.npmjs.org/sorcery/-/sorcery-0.11.0.tgz",
|
||||
@@ -12989,6 +13014,17 @@
|
||||
"integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==",
|
||||
"dev": true
|
||||
},
|
||||
"shepherd.js": {
|
||||
"version": "10.0.1",
|
||||
"resolved": "https://registry.npmjs.org/shepherd.js/-/shepherd.js-10.0.1.tgz",
|
||||
"integrity": "sha512-R32v4b4b0N1gK/vxvRtdhty+SBZlBPcPTSYCwcaAQvFd0n6Xki7cRH6Sx0oD9WB/HCkqCNM1msyIyylXmq635w==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@popperjs/core": "^2.11.5",
|
||||
"deepmerge": "^4.2.2",
|
||||
"smoothscroll-polyfill": "^0.4.4"
|
||||
}
|
||||
},
|
||||
"signal-exit": {
|
||||
"version": "3.0.7",
|
||||
"resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz",
|
||||
@@ -13049,6 +13085,12 @@
|
||||
"is-fullwidth-code-point": "^3.0.0"
|
||||
}
|
||||
},
|
||||
"smoothscroll-polyfill": {
|
||||
"version": "0.4.4",
|
||||
"resolved": "https://registry.npmjs.org/smoothscroll-polyfill/-/smoothscroll-polyfill-0.4.4.tgz",
|
||||
"integrity": "sha512-TK5ZA9U5RqCwMpfoMq/l1mrH0JAR7y7KRvOBx0n2869aLxch+gT9GhN3yUfjiw+d/DiF1mKo14+hd62JyMmoBg==",
|
||||
"dev": true
|
||||
},
|
||||
"sorcery": {
|
||||
"version": "0.11.0",
|
||||
"resolved": "https://registry.npmjs.org/sorcery/-/sorcery-0.11.0.tgz",
|
||||
|
||||
@@ -43,6 +43,7 @@
|
||||
"postcss-load-config": "^4.0.1",
|
||||
"prettier": "^2.8.3",
|
||||
"prettier-plugin-svelte": "^2.9.0",
|
||||
"shepherd.js": "^10.0.1",
|
||||
"simple-svelte-autocomplete": "^2.5.1",
|
||||
"stylelint-config-recommended": "^9.0.0",
|
||||
"svelte": "^3.55.1",
|
||||
|
||||
@@ -232,8 +232,14 @@
|
||||
<Badge color={validCode ? 'green' : 'red'} class="min-w-[60px] mr-3">
|
||||
{validCode ? 'Valid' : 'Invalid'}
|
||||
</Badge>
|
||||
<div class="flex items-center divide-x">
|
||||
<Popover notClickable placement="bottom" disapperTimoout={0} class="pr-1" disablePopup={!iconOnly}>
|
||||
<div id="script-tutorial-3" class="flex items-center divide-x">
|
||||
<Popover
|
||||
notClickable
|
||||
placement="bottom"
|
||||
disapperTimoout={0}
|
||||
class="pr-1"
|
||||
disablePopup={!iconOnly}
|
||||
>
|
||||
<Button
|
||||
color="light"
|
||||
btnClasses="!font-medium !h-full"
|
||||
@@ -245,11 +251,15 @@
|
||||
>
|
||||
+Context Var
|
||||
</Button>
|
||||
<svelte:fragment slot="text">
|
||||
Add context variable
|
||||
</svelte:fragment>
|
||||
<svelte:fragment slot="text">Add context variable</svelte:fragment>
|
||||
</Popover>
|
||||
<Popover notClickable placement="bottom" disapperTimoout={0} class="px-1" disablePopup={!iconOnly}>
|
||||
<Popover
|
||||
notClickable
|
||||
placement="bottom"
|
||||
disapperTimoout={0}
|
||||
class="px-1"
|
||||
disablePopup={!iconOnly}
|
||||
>
|
||||
<Button
|
||||
color="light"
|
||||
btnClasses="!font-medium !h-full"
|
||||
@@ -261,11 +271,15 @@
|
||||
>
|
||||
+Variable
|
||||
</Button>
|
||||
<svelte:fragment slot="text">
|
||||
Add variable
|
||||
</svelte:fragment>
|
||||
<svelte:fragment slot="text">Add variable</svelte:fragment>
|
||||
</Popover>
|
||||
<Popover notClickable placement="bottom" disapperTimoout={0} class="px-1" disablePopup={!iconOnly}>
|
||||
<Popover
|
||||
notClickable
|
||||
placement="bottom"
|
||||
disapperTimoout={0}
|
||||
class="px-1"
|
||||
disablePopup={!iconOnly}
|
||||
>
|
||||
<Button
|
||||
btnClasses="!font-medium !h-full"
|
||||
size="xs"
|
||||
@@ -277,11 +291,15 @@
|
||||
>
|
||||
+Resource
|
||||
</Button>
|
||||
<svelte:fragment slot="text">
|
||||
Add resource
|
||||
</svelte:fragment>
|
||||
<svelte:fragment slot="text">Add resource</svelte:fragment>
|
||||
</Popover>
|
||||
<Popover notClickable placement="bottom" disapperTimoout={0} class="px-1" disablePopup={!iconOnly}>
|
||||
<Popover
|
||||
notClickable
|
||||
placement="bottom"
|
||||
disapperTimoout={0}
|
||||
class="px-1"
|
||||
disablePopup={!iconOnly}
|
||||
>
|
||||
<Button
|
||||
btnClasses="!font-medium !h-full"
|
||||
size="xs"
|
||||
@@ -293,11 +311,15 @@
|
||||
>
|
||||
Reset
|
||||
</Button>
|
||||
<svelte:fragment slot="text">
|
||||
Reset
|
||||
</svelte:fragment>
|
||||
<svelte:fragment slot="text">Reset</svelte:fragment>
|
||||
</Popover>
|
||||
<Popover notClickable placement="bottom" disapperTimoout={0} class="px-1" disablePopup={!iconOnly}>
|
||||
<Popover
|
||||
notClickable
|
||||
placement="bottom"
|
||||
disapperTimoout={0}
|
||||
class="px-1"
|
||||
disablePopup={!iconOnly}
|
||||
>
|
||||
<Button
|
||||
btnClasses="!font-medium !h-full"
|
||||
size="xs"
|
||||
@@ -320,11 +342,15 @@
|
||||
{/if}
|
||||
</span>
|
||||
</Button>
|
||||
<svelte:fragment slot="text">
|
||||
Reload assistant
|
||||
</svelte:fragment>
|
||||
<svelte:fragment slot="text">Reload assistant</svelte:fragment>
|
||||
</Popover>
|
||||
<Popover notClickable placement="bottom" disapperTimoout={0} class="px-1" disablePopup={!iconOnly}>
|
||||
<Popover
|
||||
notClickable
|
||||
placement="bottom"
|
||||
disapperTimoout={0}
|
||||
class="px-1"
|
||||
disablePopup={!iconOnly}
|
||||
>
|
||||
<Button
|
||||
btnClasses="!font-medium"
|
||||
size="xs"
|
||||
@@ -342,7 +368,13 @@
|
||||
</Popover>
|
||||
</div>
|
||||
</div>
|
||||
<Popover notClickable placement="bottom" disapperTimoout={0} class="px-1" disablePopup={!iconOnly}>
|
||||
<Popover
|
||||
notClickable
|
||||
placement="bottom"
|
||||
disapperTimoout={0}
|
||||
class="px-1"
|
||||
disablePopup={!iconOnly}
|
||||
>
|
||||
<Button
|
||||
btnClasses="!font-medium"
|
||||
size="xs"
|
||||
@@ -354,9 +386,7 @@
|
||||
>
|
||||
Script
|
||||
</Button>
|
||||
<svelte:fragment slot="text">
|
||||
Script
|
||||
</svelte:fragment>
|
||||
<svelte:fragment slot="text">Script</svelte:fragment>
|
||||
</Popover>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -20,6 +20,7 @@
|
||||
import { loadFlowSchedule, type Schedule } from './flows/scheduleUtils'
|
||||
import type { FlowEditorContext } from './flows/types'
|
||||
import { cleanInputs } from './flows/utils'
|
||||
import { Tour } from './tutorial'
|
||||
|
||||
export let initialPath: string = ''
|
||||
export let selectedId: string | undefined
|
||||
@@ -27,6 +28,7 @@
|
||||
export let loading = false
|
||||
export let flowStore: Writable<Flow>
|
||||
export let flowStateStore: Writable<FlowState>
|
||||
export let tour = false
|
||||
|
||||
async function createSchedule(path: string) {
|
||||
const { cron, args, enabled } = $scheduleStore
|
||||
@@ -246,6 +248,10 @@
|
||||
|
||||
<svelte:window on:keydown={onKeyDown} />
|
||||
|
||||
{#if tour}
|
||||
<Tour tutorial="flow" />
|
||||
{/if}
|
||||
|
||||
{#if !$userStore?.operator}
|
||||
<ScriptEditorDrawer bind:this={$scriptEditorDrawer} />
|
||||
|
||||
|
||||
@@ -346,6 +346,7 @@
|
||||
lang={script.language}
|
||||
{initialArgs}
|
||||
{kind}
|
||||
tour
|
||||
/>
|
||||
{:else if step === 3}
|
||||
<CenteredPage>
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
import { Button, Kbd } from './common'
|
||||
import SplitPanesWrapper from './splitPanes/SplitPanesWrapper.svelte'
|
||||
import WindmillIcon from './icons/WindmillIcon.svelte'
|
||||
import { Tour } from './tutorial'
|
||||
|
||||
// Exported
|
||||
export let schema: Schema = emptySchema()
|
||||
@@ -27,6 +28,7 @@
|
||||
export let initialArgs: Record<string, any> = {}
|
||||
export let fixedOverflowWidgets = true
|
||||
export let noSyncFromGithub = false
|
||||
export let tour = false
|
||||
|
||||
let websocketAlive = { pyright: false, black: false, deno: false, go: false }
|
||||
|
||||
@@ -114,6 +116,10 @@
|
||||
|
||||
<svelte:window on:keydown={onKeyDown} />
|
||||
|
||||
{#if tour}
|
||||
<Tour tutorial="script" />
|
||||
{/if}
|
||||
|
||||
<div class="border-b-2 shadow-sm px-1 pr-4" bind:clientWidth={width}>
|
||||
<div class="flex justify-between space-x-2">
|
||||
<EditorBar
|
||||
@@ -146,6 +152,7 @@
|
||||
<Splitpanes class="!overflow-visible">
|
||||
<Pane size={60} minSize={10} class="!overflow-visible">
|
||||
<div
|
||||
id="script-tutorial-1"
|
||||
class="pl-2 h-full !overflow-visible"
|
||||
on:mouseleave={() => {
|
||||
inferSchema(code)
|
||||
@@ -180,7 +187,7 @@
|
||||
</Pane>
|
||||
<Pane size={40} minSize={10}>
|
||||
<div class="flex flex-col h-full">
|
||||
<div class="px-2 w-full border-b py-1">
|
||||
<div id="script-tutorial-4" class="px-2 w-full border-b py-1">
|
||||
{#if testIsLoading}
|
||||
<Button on:click={testJobLoader?.cancelJob} btnClasses="w-full" color="red" size="xs">
|
||||
<WindmillIcon
|
||||
@@ -211,7 +218,7 @@
|
||||
</div>
|
||||
<Splitpanes horizontal class="!max-h-[calc(100%-43px)]">
|
||||
<Pane size={33}>
|
||||
<div class="px-2">
|
||||
<div id="script-tutorial-2" class="px-2">
|
||||
<div class="break-words relative font-sans">
|
||||
<SchemaForm compact {schema} bind:args bind:isValid />
|
||||
</div>
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
const dispatch = createEventDispatcher()
|
||||
</script>
|
||||
|
||||
<span class="{$$props.class} z-auto">
|
||||
<span class="{$$props.class ?? ''} z-auto">
|
||||
<label
|
||||
for={id}
|
||||
class="inline-flex items-center mt-2 duration-200 {disabled
|
||||
|
||||
@@ -37,6 +37,7 @@
|
||||
import { page } from '$app/stores'
|
||||
import CssSettings from './componentsPanel/CssSettings.svelte'
|
||||
import { initHistory } from '$lib/history'
|
||||
import { Tour } from '../../tutorial'
|
||||
import ComponentNavigation from './component/ComponentNavigation.svelte'
|
||||
import ItemPicker from '$lib/components/ItemPicker.svelte'
|
||||
import VariableEditor from '$lib/components/VariableEditor.svelte'
|
||||
@@ -47,6 +48,7 @@
|
||||
export let policy: Policy
|
||||
export let summary: string
|
||||
export let fromHub: boolean = false
|
||||
export let tour: boolean = false
|
||||
|
||||
const appStore = writable<App>(app)
|
||||
const worldStore = writable<World | undefined>(undefined)
|
||||
@@ -152,6 +154,10 @@
|
||||
|
||||
<svelte:window on:hashchange={hashchange} />
|
||||
|
||||
{#if tour}
|
||||
<Tour tutorial="app" />
|
||||
{/if}
|
||||
|
||||
{#if $connectingInput.opened}
|
||||
<div
|
||||
class="absolute w-full h-screen bg-black border-2 bg-opacity-25 z-20 flex justify-center items-center"
|
||||
@@ -196,6 +202,7 @@
|
||||
$selectedComponent = undefined
|
||||
$focusedGrid = undefined
|
||||
}}
|
||||
id="app-tutorial-3"
|
||||
class={twMerge(
|
||||
'bg-gray-100 h-full w-full',
|
||||
$appStore.css?.['app']?.['viewer']?.class
|
||||
@@ -221,7 +228,7 @@
|
||||
</div>
|
||||
</Pane>
|
||||
<Pane size={$connectingInput?.opened ? 0 : 30}>
|
||||
<div class="relative h-full w-full">
|
||||
<div id="app-tutorial-4" class="relative h-full w-full">
|
||||
<InlineScriptsPanel />
|
||||
</div>
|
||||
</Pane>
|
||||
|
||||
@@ -65,7 +65,11 @@
|
||||
No components found
|
||||
</div>
|
||||
{:else}
|
||||
<div in:fade|local={{ duration: 50, delay: 50 }} out:fade|local={{ duration: 50 }}>
|
||||
<div
|
||||
id="app-tutorial-1"
|
||||
in:fade|local={{ duration: 50, delay: 50 }}
|
||||
out:fade|local={{ duration: 50 }}
|
||||
>
|
||||
{#each componentsFiltered as { title, components }, index (index)}
|
||||
{#if components.length}
|
||||
<div transition:slide|local={{ duration: 300 }}>
|
||||
|
||||
@@ -78,7 +78,7 @@
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
<div class="relative p-2">
|
||||
<div id="app-tutorial-2" class="relative p-2">
|
||||
{#each filteredPanels as [componentId, outputs] (componentId)}
|
||||
<div
|
||||
animate:flip={{ duration: 300 }}
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
}[kind]
|
||||
</script>
|
||||
|
||||
<div
|
||||
<li
|
||||
class="hover:bg-gray-50 w-full inline-flex items-center p-4 gap-4 first-of-type:!border-t-0
|
||||
first-of-type:rounded-t-md last-of-type:rounded-b-md {color}"
|
||||
>
|
||||
@@ -61,4 +61,4 @@ first-of-type:rounded-t-md last-of-type:rounded-b-md {color}"
|
||||
/>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
</li>
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
export { c as class }
|
||||
export let wrapperClass = ''
|
||||
export let style = ''
|
||||
export let id = ''
|
||||
|
||||
$: selected && updateSelected()
|
||||
|
||||
@@ -42,6 +43,7 @@
|
||||
<div
|
||||
class={twMerge('border-b border-gray-200 flex flex-row whitespace-nowrap scrollbar-hidden', c)}
|
||||
{style}
|
||||
{id}
|
||||
>
|
||||
<slot {selected} />
|
||||
</div>
|
||||
|
||||
@@ -29,7 +29,7 @@
|
||||
<div class="h-full overflow-hidden">
|
||||
<FlowCard title="Settings">
|
||||
<div class="h-full flex-1">
|
||||
<Tabs bind:selected={$selectedId}>
|
||||
<Tabs bind:selected={$selectedId} id="flow-tutorial-3">
|
||||
<Tab value="settings-metadata">Metadata</Tab>
|
||||
<Tab value="settings-schedule">Schedule</Tab>
|
||||
<Tab value="settings-same-worker">Shared Directory</Tab>
|
||||
|
||||
@@ -35,7 +35,7 @@
|
||||
let is_owner = false
|
||||
</script>
|
||||
|
||||
<div class="flex flex-row-reverse justify-between items-center gap-x-2">
|
||||
<div id="flow-tutorial-4" class="flex flex-row-reverse justify-between items-center gap-x-2">
|
||||
<Button
|
||||
on:click={() => {
|
||||
previewMode = 'whole'
|
||||
|
||||
@@ -133,7 +133,7 @@
|
||||
<FlowConstantsItem />
|
||||
</div>
|
||||
|
||||
<div class="flex-auto grow" bind:clientHeight={minHeight}>
|
||||
<div id="flow-tutorial-1" class="flex-auto grow" bind:clientHeight={minHeight}>
|
||||
<FlowGraph
|
||||
insertable
|
||||
scroll
|
||||
|
||||
@@ -84,6 +84,7 @@
|
||||
|
||||
{#if insertable && modules && (label != 'Input' || modules.length == 0)}
|
||||
<div
|
||||
id="flow-tutorial-2"
|
||||
class="{openMenu ? 'z-10' : ''} w-7 absolute {whereInsert == 'after'
|
||||
? 'top-12'
|
||||
: '-top-10'} left-[50%] right-[50%] -translate-x-1/2"
|
||||
|
||||
@@ -245,69 +245,74 @@
|
||||
/>
|
||||
|
||||
<CenteredPage>
|
||||
<div class="flex flex-wrap gap-2 items-center justify-between w-full">
|
||||
<div class="flex justify-start">
|
||||
<ToggleButtonGroup bind:selected={itemKind}>
|
||||
<ToggleButton light position="left" value="all" size="sm">All</ToggleButton>
|
||||
<ToggleButton light position="center" value="script" size="sm">
|
||||
<div class="flex gap-1 items-center">
|
||||
<Code2 size={16} />
|
||||
Scripts
|
||||
</div>
|
||||
</ToggleButton>
|
||||
<ToggleButton light position="center" value="flow" size="sm">
|
||||
<div class="flex gap-1 items-center">
|
||||
<Icon data={faBarsStaggered} scale={0.8} class="mr-1" />
|
||||
Flows
|
||||
</div>
|
||||
</ToggleButton>
|
||||
<ToggleButton light position="right" value="app" size="sm">
|
||||
<div class="flex gap-1 items-center">
|
||||
<LayoutDashboard size={16} />
|
||||
Apps
|
||||
</div>
|
||||
</ToggleButton>
|
||||
</ToggleButtonGroup>
|
||||
<div id="welcome-tutorial-1">
|
||||
<div class="flex flex-wrap gap-2 items-center justify-between w-full">
|
||||
<div class="flex justify-start">
|
||||
<ToggleButtonGroup bind:selected={itemKind}>
|
||||
<ToggleButton light position="left" value="all" size="sm">All</ToggleButton>
|
||||
<ToggleButton light position="center" value="script" size="sm">
|
||||
<div class="flex gap-1 items-center">
|
||||
<Code2 size={16} />
|
||||
Scripts
|
||||
</div>
|
||||
</ToggleButton>
|
||||
<ToggleButton light position="center" value="flow" size="sm">
|
||||
<div class="flex gap-1 items-center">
|
||||
<Icon data={faBarsStaggered} scale={0.8} class="mr-1" />
|
||||
Flows
|
||||
</div>
|
||||
</ToggleButton>
|
||||
<ToggleButton light position="right" value="app" size="sm">
|
||||
<div class="flex gap-1 items-center">
|
||||
<LayoutDashboard size={16} />
|
||||
Apps
|
||||
</div>
|
||||
</ToggleButton>
|
||||
</ToggleButtonGroup>
|
||||
</div>
|
||||
<div class="relative text-gray-600 grow min-w-[100px]">
|
||||
<!-- svelte-ignore a11y-autofocus -->
|
||||
<input
|
||||
autofocus
|
||||
placeholder="Search Scripts, Flows & Apps"
|
||||
bind:value={filter}
|
||||
class="bg-white !h-10 !px-4 !pr-10 !rounded-lg text-sm focus:outline-none"
|
||||
/>
|
||||
<button type="submit" class="absolute right-0 top-0 mt-3 mr-4">
|
||||
<svg
|
||||
class="h-4 w-4 fill-current"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||
version="1.1"
|
||||
id="Capa_1"
|
||||
x="0px"
|
||||
y="0px"
|
||||
viewBox="0 0 56.966 56.966"
|
||||
style="enable-background:new 0 0 56.966 56.966;"
|
||||
xml:space="preserve"
|
||||
width="512px"
|
||||
height="512px"
|
||||
>
|
||||
<path
|
||||
d="M55.146,51.887L41.588,37.786c3.486-4.144,5.396-9.358,5.396-14.786c0-12.682-10.318-23-23-23s-23,10.318-23,23 s10.318,23,23,23c4.761,0,9.298-1.436,13.177-4.162l13.661,14.208c0.571,0.593,1.339,0.92,2.162,0.92 c0.779,0,1.518-0.297,2.079-0.837C56.255,54.982,56.293,53.08,55.146,51.887z M23.984,6c9.374,0,17,7.626,17,17s-7.626,17-17,17 s-17-7.626-17-17S14.61,6,23.984,6z"
|
||||
/>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="relative text-gray-600 grow min-w-[100px]">
|
||||
<!-- svelte-ignore a11y-autofocus -->
|
||||
<input
|
||||
autofocus
|
||||
placeholder="Search Scripts, Flows & Apps"
|
||||
bind:value={filter}
|
||||
class="bg-white !h-10 !px-4 !pr-10 !rounded-lg text-sm focus:outline-none"
|
||||
/>
|
||||
<button type="submit" class="absolute right-0 top-0 mt-3 mr-4">
|
||||
<svg
|
||||
class="h-4 w-4 fill-current"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||
version="1.1"
|
||||
id="Capa_1"
|
||||
x="0px"
|
||||
y="0px"
|
||||
viewBox="0 0 56.966 56.966"
|
||||
style="enable-background:new 0 0 56.966 56.966;"
|
||||
xml:space="preserve"
|
||||
width="512px"
|
||||
height="512px"
|
||||
>
|
||||
<path
|
||||
d="M55.146,51.887L41.588,37.786c3.486-4.144,5.396-9.358,5.396-14.786c0-12.682-10.318-23-23-23s-23,10.318-23,23 s10.318,23,23,23c4.761,0,9.298-1.436,13.177-4.162l13.661,14.208c0.571,0.593,1.339,0.92,2.162,0.92 c0.779,0,1.518-0.297,2.079-0.837C56.255,54.982,56.293,53.08,55.146,51.887z M23.984,6c9.374,0,17,7.626,17,17s-7.626,17-17,17 s-17-7.626-17-17S14.61,6,23.984,6z"
|
||||
/>
|
||||
</svg>
|
||||
</button>
|
||||
<div class="relative flex justify-between items-start">
|
||||
<ListFilters bind:selectedFilter={ownerFilter} filters={owners} />
|
||||
{#if !loading}
|
||||
<Toggle
|
||||
size="xs"
|
||||
bind:checked={archived}
|
||||
options={{ right: 'Show archived' }}
|
||||
class="pt-2"
|
||||
textClass="whitespace-nowrap"
|
||||
/>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
<div class="relative">
|
||||
<ListFilters bind:selectedFilter={ownerFilter} filters={owners} />
|
||||
{#if !loading}
|
||||
<div class="absolute -bottom-2 right-0 bg-white/90">
|
||||
<Toggle size="xs" bind:checked={archived} options={{ right: 'Show archived' }} /></div
|
||||
>
|
||||
{/if}
|
||||
</div>
|
||||
<div>
|
||||
{#if filteredItems == undefined}
|
||||
<div class="mt-4" />
|
||||
@@ -318,7 +323,7 @@
|
||||
{:else if filteredItems.length === 0}
|
||||
<NoItemFound />
|
||||
{:else}
|
||||
<div class="border rounded-md divide-y divide-gray-200">
|
||||
<ul class="border rounded-md divide-y divide-gray-200">
|
||||
<!-- <VirtualList {items} let:item bind:start bind:end> -->
|
||||
{#each (items ?? []).slice(0, nbDisplayed) as item, i (item.type + '/' + item.path + (item.summary ?? ''))}
|
||||
{#if item.type == 'script'}
|
||||
@@ -354,7 +359,7 @@
|
||||
{/if}
|
||||
{/each}
|
||||
<!-- </VirtualList> -->
|
||||
</div>
|
||||
</ul>
|
||||
{#if items && items?.length > 30}
|
||||
<span class="text-xs"
|
||||
>{nbDisplayed} items out of {items.length}
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
export let icon: IconDefinition
|
||||
export let isCollapsed: boolean
|
||||
export let disabled: boolean = false
|
||||
export let id: string = ''
|
||||
|
||||
let isSelected = false
|
||||
|
||||
@@ -33,6 +34,7 @@
|
||||
$$props.class
|
||||
)}
|
||||
target={href.includes('http') ? '_blank' : null}
|
||||
{id}
|
||||
>
|
||||
<Icon
|
||||
data={icon}
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
|
||||
const mainMenuLinks = [
|
||||
{ label: 'Home', href: '/', icon: faHomeAlt },
|
||||
{ label: 'Runs', href: '/runs', icon: faPlay },
|
||||
{ label: 'Runs', href: '/runs', icon: faPlay, id: 'welcome-tutorial-4' },
|
||||
{ label: 'Variables', href: '/variables', icon: faDollarSign, disabled: $userStore?.operator },
|
||||
{ label: 'Resources', href: '/resources', icon: faCubes, disabled: $userStore?.operator }
|
||||
]
|
||||
|
||||
143
frontend/src/lib/components/tutorial/Tour.svelte
Normal file
143
frontend/src/lib/components/tutorial/Tour.svelte
Normal file
@@ -0,0 +1,143 @@
|
||||
<script lang="ts">
|
||||
import Shepherd from 'shepherd.js'
|
||||
import { steps, tourStore, type TutorialName } from './'
|
||||
|
||||
export let tutorial: TutorialName
|
||||
/**
|
||||
* If the index of a step is added to a tour, the custom footer will not be added
|
||||
* (ie. the steps won't be shown and the buttons won't be positioned)
|
||||
*/
|
||||
const noPageNumber: Partial<Record<TutorialName, number[]>> = {
|
||||
welcome: [0]
|
||||
}
|
||||
let tour: Shepherd.Tour | undefined
|
||||
|
||||
$: $tourStore.includes(tutorial) && initiate()
|
||||
|
||||
function initiate() {
|
||||
if (!tourStore.isTourActive(tutorial)) {
|
||||
return
|
||||
}
|
||||
tour?.cancel()
|
||||
tour = new Shepherd.Tour({
|
||||
tourName: tutorial,
|
||||
useModalOverlay: true,
|
||||
defaultStepOptions: {
|
||||
classes: 'max-w-[460px] shadow-xl bg-white border rounded px-6 py-4 z-[1100]',
|
||||
scrollTo: true
|
||||
}
|
||||
})
|
||||
tour.addSteps(steps[tutorial](tour))
|
||||
tour.steps.forEach((step, i) => {
|
||||
if (noPageNumber[tutorial]?.includes(i)) {
|
||||
return
|
||||
}
|
||||
step.on('show', () => {
|
||||
const currentStep = tour?.getCurrentStep()
|
||||
const currentStepElement = currentStep?.getElement()
|
||||
const header = currentStepElement?.querySelector('.shepherd-header')
|
||||
if (currentStep && currentStepElement && header) {
|
||||
const progress = document.createElement('span')
|
||||
progress.className = 'shepherd-progress text-sm text-gray-500 pr-2'
|
||||
progress.innerText = `${(tour?.steps.indexOf(currentStep) ?? 0) + 1}/${
|
||||
tour?.steps.length
|
||||
}`
|
||||
header.prepend(progress)
|
||||
}
|
||||
})
|
||||
})
|
||||
;['complete', 'cancel'].forEach((event) => {
|
||||
tour?.on(event, () => {
|
||||
tourStore.markTourAsDone(tutorial)
|
||||
})
|
||||
})
|
||||
tour.start()
|
||||
}
|
||||
</script>
|
||||
|
||||
<style global>
|
||||
.shepherd-footer {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.shepherd-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.shepherd-button {
|
||||
padding: 4px 12px;
|
||||
border-radius: 4px;
|
||||
transition-duration: 200ms;
|
||||
}
|
||||
|
||||
/* Simple primary button */
|
||||
.shepherd-button:not(.shepherd-button-secondary.special) {
|
||||
color: #3b82f6;
|
||||
}
|
||||
|
||||
.shepherd-button:not(.shepherd-button-secondary.special):hover,
|
||||
.shepherd-button:not(.shepherd-button-secondary.special):focus {
|
||||
background-color: #eff6ff;
|
||||
}
|
||||
|
||||
/* Simple secondary button */
|
||||
.shepherd-button.shepherd-button-secondary:not(.special) {
|
||||
color: #6b7280;
|
||||
}
|
||||
|
||||
.shepherd-button.shepherd-button-secondary:not(.special):hover,
|
||||
.shepherd-button.shepherd-button-secondary:not(.special):focus {
|
||||
background-color: #f3f4f6;
|
||||
}
|
||||
|
||||
/* Special primary button */
|
||||
.shepherd-button.special:not(.shepherd-button-secondary) {
|
||||
background-color: #3b82f6;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.shepherd-button.special:not(.shepherd-button-secondary):hover,
|
||||
.shepherd-button.special:not(.shepherd-button-secondary):focus {
|
||||
background-color: #2563eb;
|
||||
}
|
||||
|
||||
/* Special secondary button */
|
||||
.shepherd-button.special.shepherd-button-secondary {
|
||||
background-color: #eff6ff;
|
||||
color: #3b82f6;
|
||||
}
|
||||
|
||||
.shepherd-button.special.shepherd-button-secondary:hover,
|
||||
.shepherd-button.special.shepherd-button-secondary:focus {
|
||||
background-color: #dbeafe;
|
||||
}
|
||||
|
||||
.shepherd-cancel-icon {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
border-radius: 16px;
|
||||
background-color: #fff;
|
||||
transition-duration: 200ms;
|
||||
}
|
||||
|
||||
.shepherd-cancel-icon:hover,
|
||||
.shepherd-cancel-icon:focus {
|
||||
background-color: #ededed;
|
||||
}
|
||||
|
||||
.shepherd-highlighted-element {
|
||||
box-shadow: 0 0 0 6px #93c4fdbb;
|
||||
background-color: #93c4fd3a;
|
||||
position: relative;
|
||||
z-index: 1090;
|
||||
}
|
||||
</style>
|
||||
113
frontend/src/lib/components/tutorial/app-steps.ts
Normal file
113
frontend/src/lib/components/tutorial/app-steps.ts
Normal file
@@ -0,0 +1,113 @@
|
||||
import type Shepherd from 'shepherd.js'
|
||||
|
||||
export default (tour: Shepherd.Tour) => {
|
||||
const steps: object[] | Shepherd.Step[] = [
|
||||
{
|
||||
id: 'welcome',
|
||||
text: `<div class="text-xl font-bold mb-6">Windmill App Editor</div>
|
||||
<p class="mb-6">Build your own UI quick and easy with drag-and-drop components. Connect your scripts and data to them and deploy a fully functioning application.</p>`,
|
||||
buttons: [
|
||||
{
|
||||
text: 'See by myself',
|
||||
action: tour.cancel,
|
||||
secondary: true,
|
||||
classes: 'special'
|
||||
},
|
||||
{
|
||||
text: 'Give me an overview',
|
||||
action: tour.next,
|
||||
classes: 'special'
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 'components',
|
||||
text: `<div class="text-xl font-bold mb-2">Components</div>
|
||||
<p class="mb-2">Click to add and move them around by dragging. Configure the inputs, settings and styling, even with Tailwind classes.</p>`,
|
||||
cancelIcon: {
|
||||
enabled: true
|
||||
},
|
||||
attachTo: { element: '#app-tutorial-1', on: 'left-start' },
|
||||
highlightClass: 'shepherd-highlighted-element',
|
||||
scrollTo: false,
|
||||
buttons: [
|
||||
{
|
||||
text: 'Back',
|
||||
action: tour.back,
|
||||
secondary: true
|
||||
},
|
||||
{
|
||||
text: 'Next',
|
||||
action: tour.next
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 'outputs',
|
||||
text: `<div class="text-xl font-bold mb-2">Outputs</div>
|
||||
<p class="mb-2">Each component has their outputs, which can be used to easily hook into the app state.</p>`,
|
||||
cancelIcon: {
|
||||
enabled: true
|
||||
},
|
||||
attachTo: { element: '#app-tutorial-2', on: 'right-start' },
|
||||
highlightClass: 'shepherd-highlighted-element',
|
||||
scrollTo: false,
|
||||
buttons: [
|
||||
{
|
||||
text: 'Back',
|
||||
action: tour.back,
|
||||
secondary: true
|
||||
},
|
||||
{
|
||||
text: 'Next',
|
||||
action: tour.next
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 'parameters',
|
||||
text: `<div class="text-xl font-bold mb-2">Canvas</div>
|
||||
<p class="mb-2">It allows you to position, scale and group the components.</p>`,
|
||||
cancelIcon: {
|
||||
enabled: true
|
||||
},
|
||||
attachTo: { element: '#app-tutorial-3', on: 'bottom' },
|
||||
highlightClass: 'shepherd-highlighted-element',
|
||||
scrollTo: false,
|
||||
buttons: [
|
||||
{
|
||||
text: 'Back',
|
||||
action: tour.back,
|
||||
secondary: true
|
||||
},
|
||||
{
|
||||
text: 'Next',
|
||||
action: tour.next
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 'test',
|
||||
text: `<div class="text-xl font-bold mb-2">Runnable editor</div>
|
||||
<p class="mb-2">Here you can create, edit and manage the scripts of your app.</p>`,
|
||||
cancelIcon: {
|
||||
enabled: true
|
||||
},
|
||||
attachTo: { element: '#app-tutorial-4', on: 'top-start' },
|
||||
highlightClass: 'shepherd-highlighted-element',
|
||||
scrollTo: false,
|
||||
buttons: [
|
||||
{
|
||||
text: 'Back',
|
||||
action: tour.back,
|
||||
secondary: true
|
||||
},
|
||||
{
|
||||
text: 'Finish',
|
||||
action: tour.complete
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
return steps
|
||||
}
|
||||
113
frontend/src/lib/components/tutorial/flow-steps.ts
Normal file
113
frontend/src/lib/components/tutorial/flow-steps.ts
Normal file
@@ -0,0 +1,113 @@
|
||||
import type Shepherd from 'shepherd.js'
|
||||
|
||||
export default (tour: Shepherd.Tour) => {
|
||||
const steps: object[] | Shepherd.Step[] = [
|
||||
{
|
||||
id: 'welcome',
|
||||
text: `<div class="text-xl font-bold mb-6">Windmill Flow Editor</div>
|
||||
<p class="mb-6">Build complex workflows from your scripts, configure them and get a clear visualization.</p>`,
|
||||
buttons: [
|
||||
{
|
||||
text: 'See by myself',
|
||||
action: tour.cancel,
|
||||
secondary: true,
|
||||
classes: 'special'
|
||||
},
|
||||
{
|
||||
text: 'Give me an overview',
|
||||
action: tour.next,
|
||||
classes: 'special'
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 'graph',
|
||||
text: `<div class="text-xl font-bold mb-2">Flow graph</div>
|
||||
<p class="mb-2">Know how your flow is structured at a glimpse.</p>`,
|
||||
cancelIcon: {
|
||||
enabled: true
|
||||
},
|
||||
attachTo: { element: '#flow-tutorial-1', on: 'right-start' },
|
||||
highlightClass: 'shepherd-highlighted-element',
|
||||
scrollTo: false,
|
||||
buttons: [
|
||||
{
|
||||
text: 'Back',
|
||||
action: tour.back,
|
||||
secondary: true
|
||||
},
|
||||
{
|
||||
text: 'Next',
|
||||
action: tour.next
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 'modules',
|
||||
text: `<div class="text-xl font-bold mb-2">Insert modules</div>
|
||||
<p class="mb-2">Start building by adding modules. A module can be a simple script, a loop or a branching for example.</p>`,
|
||||
cancelIcon: {
|
||||
enabled: true
|
||||
},
|
||||
attachTo: { element: '#flow-tutorial-2 button', on: 'right-start' },
|
||||
highlightClass: 'shepherd-highlighted-element',
|
||||
scrollTo: false,
|
||||
buttons: [
|
||||
{
|
||||
text: 'Back',
|
||||
action: tour.back,
|
||||
secondary: true
|
||||
},
|
||||
{
|
||||
text: 'Next',
|
||||
action: tour.next
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 'settings',
|
||||
text: `<div class="text-xl font-bold mb-2">Settings</div>
|
||||
<p class="mb-2">Configure schedules, authorizations, sharing parameters and many more.</p>`,
|
||||
cancelIcon: {
|
||||
enabled: true
|
||||
},
|
||||
attachTo: { element: '#flow-tutorial-3', on: 'bottom-start' },
|
||||
highlightClass: 'shepherd-highlighted-element',
|
||||
scrollTo: false,
|
||||
buttons: [
|
||||
{
|
||||
text: 'Back',
|
||||
action: tour.back,
|
||||
secondary: true
|
||||
},
|
||||
{
|
||||
text: 'Next',
|
||||
action: tour.next
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 'test',
|
||||
text: `<div class="text-xl font-bold mb-2">Test flow</div>
|
||||
<p class="mb-2">Time to try your work! Test the flow and iterate as much as you want.</p>`,
|
||||
cancelIcon: {
|
||||
enabled: true
|
||||
},
|
||||
attachTo: { element: '#flow-tutorial-4', on: 'left-start' },
|
||||
highlightClass: 'shepherd-highlighted-element',
|
||||
scrollTo: false,
|
||||
buttons: [
|
||||
{
|
||||
text: 'Back',
|
||||
action: tour.back,
|
||||
secondary: true
|
||||
},
|
||||
{
|
||||
text: 'Finish',
|
||||
action: tour.complete
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
return steps
|
||||
}
|
||||
51
frontend/src/lib/components/tutorial/index.ts
Normal file
51
frontend/src/lib/components/tutorial/index.ts
Normal file
@@ -0,0 +1,51 @@
|
||||
import { browser } from '$app/environment'
|
||||
import type Shepherd from 'shepherd.js'
|
||||
import welcome from './welcome-steps'
|
||||
import script from './script-steps'
|
||||
import flow from './flow-steps'
|
||||
import app from './app-steps'
|
||||
import { writable } from 'svelte/store'
|
||||
|
||||
export { default as Tour } from './Tour.svelte'
|
||||
|
||||
export const TUTORIAL_NAMES = ['welcome', 'script', 'flow', 'app'] as const
|
||||
export type TutorialName = (typeof TUTORIAL_NAMES)[number]
|
||||
|
||||
export const steps: Record<TutorialName, (tour: Shepherd.Tour) => object[] | Shepherd.Step[]> = {
|
||||
welcome,
|
||||
script,
|
||||
flow,
|
||||
app
|
||||
}
|
||||
|
||||
const STORAGE_KEY = 'tutorials-to-show' as const
|
||||
const store = writable<Readonly<TutorialName[]>>([])
|
||||
export const tourStore = {
|
||||
subscribe: store.subscribe,
|
||||
initiateTours: () => {
|
||||
if (!browser) {
|
||||
return
|
||||
}
|
||||
window.localStorage.setItem(STORAGE_KEY, JSON.stringify(TUTORIAL_NAMES))
|
||||
store.set(TUTORIAL_NAMES)
|
||||
},
|
||||
isTourActive: (name: TutorialName) => {
|
||||
if (!browser) {
|
||||
return false
|
||||
}
|
||||
const toursToShow = JSON.parse(
|
||||
window.localStorage.getItem(STORAGE_KEY) || '[]'
|
||||
) as TutorialName[]
|
||||
return toursToShow.includes(name)
|
||||
},
|
||||
markTourAsDone: (name: TutorialName) => {
|
||||
if (!browser) {
|
||||
return
|
||||
}
|
||||
const toursToShow = (
|
||||
JSON.parse(window.localStorage.getItem(STORAGE_KEY) || '[]') as TutorialName[]
|
||||
).filter((t) => t !== name)
|
||||
window.localStorage.setItem(STORAGE_KEY, JSON.stringify(toursToShow))
|
||||
store.set(toursToShow)
|
||||
}
|
||||
}
|
||||
113
frontend/src/lib/components/tutorial/script-steps.ts
Normal file
113
frontend/src/lib/components/tutorial/script-steps.ts
Normal file
@@ -0,0 +1,113 @@
|
||||
import type Shepherd from 'shepherd.js'
|
||||
|
||||
export default (tour: Shepherd.Tour) => {
|
||||
const steps: object[] | Shepherd.Step[] = [
|
||||
{
|
||||
id: 'welcome',
|
||||
text: `<div class="text-xl font-bold mb-6">Windmill Script Builder</div>
|
||||
<p class="mb-6">Build scripts with our own web editor, then use them anywhere in your flows, apps or as standalone computations with their auto-generated UI.</p>`,
|
||||
buttons: [
|
||||
{
|
||||
text: 'See by myself',
|
||||
action: tour.cancel,
|
||||
secondary: true,
|
||||
classes: 'special'
|
||||
},
|
||||
{
|
||||
text: 'Give me an overview',
|
||||
action: tour.next,
|
||||
classes: 'special'
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 'editor',
|
||||
text: `<div class="text-xl font-bold mb-2">Web editor</div>
|
||||
<p class="mb-2">Code directly from the web editor.</p>`,
|
||||
cancelIcon: {
|
||||
enabled: true
|
||||
},
|
||||
attachTo: { element: '#script-tutorial-1', on: 'right-start' },
|
||||
highlightClass: 'shepherd-highlighted-element',
|
||||
scrollTo: false,
|
||||
buttons: [
|
||||
{
|
||||
text: 'Back',
|
||||
action: tour.back,
|
||||
secondary: true
|
||||
},
|
||||
{
|
||||
text: 'Next',
|
||||
action: tour.next
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 'preview',
|
||||
text: `<div class="text-xl font-bold mb-2">UI preview</div>
|
||||
<p class="mb-2">Preview the user interface generated from the signature of the script.</p>`,
|
||||
cancelIcon: {
|
||||
enabled: true
|
||||
},
|
||||
attachTo: { element: '#script-tutorial-2', on: 'left-start' },
|
||||
highlightClass: 'shepherd-highlighted-element',
|
||||
scrollTo: false,
|
||||
buttons: [
|
||||
{
|
||||
text: 'Back',
|
||||
action: tour.back,
|
||||
secondary: true
|
||||
},
|
||||
{
|
||||
text: 'Next',
|
||||
action: tour.next
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 'parameters',
|
||||
text: `<div class="text-xl font-bold mb-2">Parameters</div>
|
||||
<p class="mb-2">Add variables, resources and configure your script.</p>`,
|
||||
cancelIcon: {
|
||||
enabled: true
|
||||
},
|
||||
attachTo: { element: '#script-tutorial-3', on: 'bottom-start' },
|
||||
highlightClass: 'shepherd-highlighted-element',
|
||||
scrollTo: false,
|
||||
buttons: [
|
||||
{
|
||||
text: 'Back',
|
||||
action: tour.back,
|
||||
secondary: true
|
||||
},
|
||||
{
|
||||
text: 'Next',
|
||||
action: tour.next
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 'test',
|
||||
text: `<div class="text-xl font-bold mb-2">Test script</div>
|
||||
<p class="mb-2">Time to try your work! Test the script and iterate as much as you want.</p>`,
|
||||
cancelIcon: {
|
||||
enabled: true
|
||||
},
|
||||
attachTo: { element: '#script-tutorial-4', on: 'left-start' },
|
||||
highlightClass: 'shepherd-highlighted-element',
|
||||
scrollTo: false,
|
||||
buttons: [
|
||||
{
|
||||
text: 'Back',
|
||||
action: tour.back,
|
||||
secondary: true
|
||||
},
|
||||
{
|
||||
text: 'Finish',
|
||||
action: tour.complete
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
return steps
|
||||
}
|
||||
114
frontend/src/lib/components/tutorial/welcome-steps.ts
Normal file
114
frontend/src/lib/components/tutorial/welcome-steps.ts
Normal file
@@ -0,0 +1,114 @@
|
||||
import type Shepherd from 'shepherd.js'
|
||||
|
||||
export default (tour: Shepherd.Tour) => {
|
||||
const steps: object[] | Shepherd.Step[] = [
|
||||
{
|
||||
id: 'welcome',
|
||||
text: `<div class="text-xl font-bold mb-6">Welcome to Windmill Cloud App!</div>
|
||||
<p class="mb-6">Thank your for your interest in Windmill. Let us guide you through the home screen in strictly less than a minute.</p>`,
|
||||
buttons: [
|
||||
{
|
||||
text: 'See by myself',
|
||||
action: tour.cancel,
|
||||
secondary: true,
|
||||
classes: 'special'
|
||||
},
|
||||
{
|
||||
text: 'Give me an overview',
|
||||
action: tour.next,
|
||||
classes: 'special'
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 'workspace',
|
||||
text: `<div class="text-xl font-bold mb-2">Workspace</div>
|
||||
<p>You can see all the scripts, flows and apps here.</p>
|
||||
<p class="mb-2">Use the search and filters to find just what you are looking for.</p>`,
|
||||
cancelIcon: {
|
||||
enabled: true
|
||||
},
|
||||
attachTo: { element: '#welcome-tutorial-1', on: 'bottom-start' },
|
||||
highlightClass: 'shepherd-highlighted-element',
|
||||
scrollTo: false,
|
||||
buttons: [
|
||||
{
|
||||
text: 'Back',
|
||||
action: tour.back,
|
||||
secondary: true
|
||||
},
|
||||
{
|
||||
text: 'Next',
|
||||
action: tour.next
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 'create',
|
||||
text: `<div class="text-xl font-bold mb-2">Create</div>
|
||||
<p class="mb-2">Build your own tools in minutes.</p>`,
|
||||
cancelIcon: {
|
||||
enabled: true
|
||||
},
|
||||
attachTo: { element: '#welcome-tutorial-2', on: 'bottom-end' },
|
||||
highlightClass: 'shepherd-highlighted-element',
|
||||
scrollTo: false,
|
||||
buttons: [
|
||||
{
|
||||
text: 'Back',
|
||||
action: tour.back,
|
||||
secondary: true
|
||||
},
|
||||
{
|
||||
text: 'Next',
|
||||
action: tour.next
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 'inspiration',
|
||||
text: `<div class="text-xl font-bold mb-2">Take inspiration</div>
|
||||
<p class="mb-2">Explore templates built by the community and import them directly from <a href="https://hub.windmill.dev" target="_blank">Windmill Hub</a>.</p>`,
|
||||
cancelIcon: {
|
||||
enabled: true
|
||||
},
|
||||
attachTo: { element: '#welcome-tutorial-3', on: 'bottom-start' },
|
||||
highlightClass: 'shepherd-highlighted-element',
|
||||
scrollTo: false,
|
||||
buttons: [
|
||||
{
|
||||
text: 'Back',
|
||||
action: tour.back,
|
||||
secondary: true
|
||||
},
|
||||
{
|
||||
text: 'Next',
|
||||
action: tour.next
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 'monitor',
|
||||
text: `<div class="text-xl font-bold mb-2">Monitor</div>
|
||||
<p class="mb-2">Keep an eye an all past and scheduled executions of scripts and flows.</p>`,
|
||||
cancelIcon: {
|
||||
enabled: true
|
||||
},
|
||||
attachTo: { element: '#welcome-tutorial-4-wrapper #welcome-tutorial-4', on: 'right-start' },
|
||||
highlightClass: 'shepherd-highlighted-element',
|
||||
scrollTo: false,
|
||||
buttons: [
|
||||
{
|
||||
text: 'Back',
|
||||
action: tour.back,
|
||||
secondary: true
|
||||
},
|
||||
{
|
||||
text: 'Finish',
|
||||
action: tour.complete
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
return steps
|
||||
}
|
||||
@@ -202,7 +202,10 @@
|
||||
isCollapsed ? 'md:w-12' : 'md:w-40'
|
||||
)}
|
||||
>
|
||||
<div class="flex-1 flex flex-col min-h-0 h-screen shadow-lg bg-[#2e3440]">
|
||||
<div
|
||||
id="welcome-tutorial-4-wrapper"
|
||||
class="flex-1 flex flex-col min-h-0 h-screen shadow-lg bg-[#2e3440]"
|
||||
>
|
||||
<button
|
||||
on:click={() => {
|
||||
goto('/')
|
||||
|
||||
@@ -20,6 +20,8 @@
|
||||
import AppPreview from '$lib/components/apps/editor/AppPreview.svelte'
|
||||
import { writable } from 'svelte/store'
|
||||
import type { EditorBreakpoint } from '$lib/components/apps/types'
|
||||
import { Tour, tourStore } from '../../../lib/components/tutorial'
|
||||
import { onMount } from 'svelte'
|
||||
|
||||
type Tab = 'hubscripts' | 'hubflows' | 'hubapps' | 'workspace'
|
||||
|
||||
@@ -60,8 +62,12 @@
|
||||
appViewerApp = hub
|
||||
appViewer.openDrawer?.()
|
||||
}
|
||||
|
||||
onMount(tourStore.initiateTours)
|
||||
</script>
|
||||
|
||||
<Tour tutorial="welcome" />
|
||||
|
||||
<Drawer bind:this={codeViewer} size="900px">
|
||||
<DrawerContent title={codeViewerObj?.summary ?? ''} on:close={codeViewer.closeDrawer}>
|
||||
<svelte:fragment slot="actions">
|
||||
@@ -181,7 +187,7 @@
|
||||
</Alert>
|
||||
{/if}
|
||||
<PageHeader title="Home">
|
||||
<div class="flex flex-row gap-4 flex-wrap justify-end items-center">
|
||||
<div id="welcome-tutorial-2" class="flex flex-row gap-4 flex-wrap justify-end items-center">
|
||||
{#if !$userStore?.operator}
|
||||
<span class="text-sm text-gray-500">Create a new:</span>
|
||||
<CreateActionsScript />
|
||||
@@ -192,7 +198,7 @@
|
||||
</PageHeader>
|
||||
|
||||
{#if !$userStore?.operator}
|
||||
<div class="w-full overflow-auto scrollbar-hidden">
|
||||
<div id="welcome-tutorial-3" class="w-full">
|
||||
<Tabs bind:selected={tab}>
|
||||
<Tab size="md" value="workspace">
|
||||
<div class="flex gap-2 items-center my-1">
|
||||
|
||||
@@ -84,7 +84,7 @@
|
||||
{#if value}
|
||||
<div class="h-screen">
|
||||
{#key value}
|
||||
<AppEditor {summary} app={value} path={''} {policy} fromHub={hubId != null} />
|
||||
<AppEditor {summary} app={value} path={''} {policy} fromHub={hubId != null} tour />
|
||||
{/key}
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
@@ -89,4 +89,4 @@
|
||||
$dirtyStore = true
|
||||
</script>
|
||||
|
||||
<FlowBuilder {flowStore} {flowStateStore} {selectedId} {loading} />
|
||||
<FlowBuilder {flowStore} {flowStateStore} {selectedId} {loading} tour />
|
||||
|
||||
Reference in New Issue
Block a user