mirror of
https://github.com/windmill-labs/windmill.git
synced 2026-09-12 08:05:44 +00:00
Ignored tutorials (#2394)
* fix(frontend): add ignored tutorials * fix(frontend): add ignored tutorials * fix(frontend): tutorial code cleanup * fix(frontend): refactor how buttons are created * fix(frontend): Correctly handled ignored tutorials
This commit is contained in:
@@ -58,6 +58,7 @@
|
||||
import FlowBuilderTutorials from './FlowBuilderTutorials.svelte'
|
||||
|
||||
import FlowTutorials from './FlowTutorials.svelte'
|
||||
import { ignoredTutorials } from './tutorials/ignoredTutorials'
|
||||
|
||||
export let initialPath: string = ''
|
||||
export let selectedId: string | undefined
|
||||
@@ -858,7 +859,7 @@
|
||||
|
||||
if (tutorial) {
|
||||
flowTutorials?.runTutorialById(tutorial)
|
||||
} else if ($tutorialsToDo.includes(0)) {
|
||||
} else if ($tutorialsToDo.includes(0) && !$ignoredTutorials.includes(0)) {
|
||||
flowTutorials?.runTutorialById('action')
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,28 +29,24 @@
|
||||
on:click={() => flowTutorials?.runTutorialById('action')}
|
||||
label="Simple flow tutorial"
|
||||
index={0}
|
||||
id="flow-builder-tutorial-action"
|
||||
/>
|
||||
|
||||
<TutorialItem
|
||||
on:click={() => flowTutorials?.runTutorialById('forloop')}
|
||||
label="For loops tutorial"
|
||||
index={1}
|
||||
id="flow-builder-tutorial-forloops"
|
||||
/>
|
||||
|
||||
<TutorialItem
|
||||
on:click={() => flowTutorials?.runTutorialById('branchone')}
|
||||
label="Branch one tutorial"
|
||||
index={2}
|
||||
id="flow-builder-tutorial-branchone"
|
||||
/>
|
||||
|
||||
<TutorialItem
|
||||
on:click={() => flowTutorials?.runTutorialById('branchall')}
|
||||
label="Branch all tutorial"
|
||||
index={3}
|
||||
id="flow-builder-tutorial-branchall"
|
||||
/>
|
||||
|
||||
<div class="border-t border-surface-hover" />
|
||||
@@ -117,6 +113,6 @@
|
||||
}
|
||||
|
||||
.driver-popover {
|
||||
@apply p-6 bg-surface;
|
||||
@apply p-6 bg-surface max-w-2xl;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -11,7 +11,6 @@
|
||||
let flowBranchAll: FlowBranchAll | undefined = undefined
|
||||
|
||||
export function runTutorialById(id: string) {
|
||||
console.log(id, flowBranchOne)
|
||||
if (id === 'forloop') {
|
||||
flowBuilderTutorialsForLoop?.runTutorial()
|
||||
} else if (id === 'branchone') {
|
||||
|
||||
@@ -26,6 +26,7 @@
|
||||
|
||||
import FlowTutorials from '$lib/components/FlowTutorials.svelte'
|
||||
import { tainted } from '$lib/components/tutorials/utils'
|
||||
import { ignoredTutorials } from '$lib/components/tutorials/ignoredTutorials'
|
||||
|
||||
export let modules: FlowModule[] | undefined
|
||||
export let sidebarSize: number | undefined = undefined
|
||||
@@ -140,6 +141,18 @@
|
||||
|
||||
const { currentStepStore: copilotCurrentStepStore } =
|
||||
getContext<FlowCopilotContext | undefined>('FlowCopilotContext') || {}
|
||||
|
||||
function shouldRunTutorial(tutorialName: string, name: string, index: number) {
|
||||
const svg = document.getElementsByClassName('driver-overlay driver-overlay-animated')
|
||||
const isTainted = tainted($flowStore)
|
||||
return (
|
||||
$tutorialsToDo.includes(index) &&
|
||||
name == tutorialName &&
|
||||
svg.length === 0 &&
|
||||
!isTainted &&
|
||||
!$ignoredTutorials.includes(index)
|
||||
)
|
||||
}
|
||||
</script>
|
||||
|
||||
<Portal>
|
||||
@@ -212,28 +225,11 @@
|
||||
}
|
||||
}}
|
||||
on:insert={async ({ detail }) => {
|
||||
const svg = document.getElementsByClassName('driver-overlay driver-overlay-animated')
|
||||
const isTainted = tainted($flowStore)
|
||||
if (
|
||||
$tutorialsToDo.includes(1) &&
|
||||
detail.detail == 'forloop' &&
|
||||
svg.length === 0 &&
|
||||
!isTainted
|
||||
) {
|
||||
if (shouldRunTutorial('forloop', detail.detail, 1)) {
|
||||
flowTutorials?.runTutorialById('forloop')
|
||||
} else if (
|
||||
$tutorialsToDo.includes(2) &&
|
||||
detail.detail == 'branchone' &&
|
||||
svg.length === 0 &&
|
||||
!isTainted
|
||||
) {
|
||||
} else if (shouldRunTutorial('branchone', detail.detail, 2)) {
|
||||
flowTutorials?.runTutorialById('branchone')
|
||||
} else if (
|
||||
$tutorialsToDo.includes(3) &&
|
||||
detail.detail == 'branchall' &&
|
||||
svg.length === 0 &&
|
||||
!isTainted
|
||||
) {
|
||||
} else if (shouldRunTutorial('branchall', detail.detail, 3)) {
|
||||
flowTutorials?.runTutorialById('branchall')
|
||||
} else {
|
||||
if (detail.modules) {
|
||||
|
||||
@@ -1,221 +1,179 @@
|
||||
<script lang="ts">
|
||||
import { driver } from 'driver.js'
|
||||
import 'driver.js/dist/driver.css'
|
||||
import { createEventDispatcher, getContext } from 'svelte'
|
||||
import type { FlowEditorContext } from '../flows/types'
|
||||
import { clickButtonBySelector, triggerAddFlowStep, selectFlowStepKind, tainted } from './utils'
|
||||
import { clickButtonBySelector, triggerAddFlowStep, selectFlowStepKind } from './utils'
|
||||
import { updateProgress } from '$lib/tutorialUtils'
|
||||
import { RawScript } from '$lib/gen'
|
||||
import Tutorial from './Tutorial.svelte'
|
||||
|
||||
const { flowStore } = getContext<FlowEditorContext>('FlowEditorContext')
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
|
||||
let tutorial: Tutorial | undefined = undefined
|
||||
|
||||
export function runTutorial() {
|
||||
if (tainted($flowStore)) {
|
||||
dispatch('error', { detail: 'branchall' })
|
||||
return
|
||||
}
|
||||
|
||||
const branchAllTutorial = driver({
|
||||
showProgress: true,
|
||||
allowClose: true,
|
||||
onPopoverRender: (popover, { config, state }) => {
|
||||
if (state.activeIndex == 0) {
|
||||
const skipThisButton = document.createElement('button')
|
||||
skipThisButton.innerText = 'Skip this tutorial'
|
||||
skipThisButton.addEventListener('click', () => {
|
||||
updateProgress(3)
|
||||
|
||||
branchAllTutorial.destroy()
|
||||
})
|
||||
skipThisButton.setAttribute(
|
||||
'class',
|
||||
'border px-2 py-1 !text-xs font-normal rounded-md hover:bg-surface-hover transition-all flex items-center'
|
||||
)
|
||||
|
||||
const skipAllButton = document.createElement('button')
|
||||
skipAllButton.innerText = 'Skip all tutorials'
|
||||
skipAllButton.addEventListener('click', () => {
|
||||
dispatch('skipAll')
|
||||
branchAllTutorial.destroy()
|
||||
})
|
||||
|
||||
skipAllButton.setAttribute(
|
||||
'class',
|
||||
'border px-2 py-1 !text-xs font-normal rounded-md hover:bg-surface-hover transition-all flex items-center'
|
||||
)
|
||||
|
||||
const popoverDescription = document.querySelector('#driver-popover-description')
|
||||
|
||||
const div = document.createElement('div')
|
||||
|
||||
div.setAttribute('class', 'flex flex-row gap-2 justify-between w-full pt-2')
|
||||
|
||||
if (popoverDescription) {
|
||||
div.appendChild(skipAllButton)
|
||||
|
||||
div.appendChild(skipThisButton)
|
||||
|
||||
popoverDescription.appendChild(div)
|
||||
}
|
||||
}
|
||||
},
|
||||
steps: [
|
||||
{
|
||||
popover: {
|
||||
title: 'Welcome to the Windmil Flow editor',
|
||||
description:
|
||||
'Learn how to build our first branch to be executed on a condition. You can use arrow keys to navigate'
|
||||
}
|
||||
},
|
||||
{
|
||||
element: '#flow-editor-add-step-0',
|
||||
popover: {
|
||||
title: 'Add a step',
|
||||
description: 'Click here to add a step to your flow',
|
||||
onNextClick: () => {
|
||||
triggerAddFlowStep(0)
|
||||
|
||||
setTimeout(() => {
|
||||
branchAllTutorial.moveNext()
|
||||
})
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
{
|
||||
popover: {
|
||||
title: 'Steps kind',
|
||||
description: "Choose the kind of step you want to add. Let's start with a simple action"
|
||||
},
|
||||
element: '#flow-editor-insert-module'
|
||||
},
|
||||
|
||||
{
|
||||
popover: {
|
||||
title: 'Insert Branch all',
|
||||
description: "Let's pick branch all",
|
||||
onNextClick: () => {
|
||||
selectFlowStepKind(6)
|
||||
|
||||
setTimeout(() => {
|
||||
branchAllTutorial.moveNext()
|
||||
})
|
||||
}
|
||||
},
|
||||
element: '#flow-editor-insert-module > div > button:nth-child(6)'
|
||||
},
|
||||
|
||||
{
|
||||
element: '#add-branch-button',
|
||||
popover: {
|
||||
title: 'Add branch',
|
||||
description: 'Click here to add a branch',
|
||||
onNextClick: () => {
|
||||
clickButtonBySelector('#add-branch-button')
|
||||
|
||||
setTimeout(() => {
|
||||
branchAllTutorial.moveNext()
|
||||
})
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
{
|
||||
element: '#flow-editor-step-input',
|
||||
popover: {
|
||||
title: 'Branch all',
|
||||
description:
|
||||
'All branches will be executed, and the result will be gathered in an array',
|
||||
|
||||
onNextClick: () => {
|
||||
if ($flowStore.value.modules[0].value.type === 'branchall') {
|
||||
$flowStore.value.modules[0].value = {
|
||||
type: 'branchall',
|
||||
branches: [
|
||||
{
|
||||
modules: [
|
||||
{
|
||||
id: 'b',
|
||||
value: {
|
||||
type: 'rawscript',
|
||||
content:
|
||||
'// import * as wmill from "npm:windmill-client@1"\n\nexport async function main(x: string) {\n return "Hello"\n}\n',
|
||||
language: RawScript.language.DENO,
|
||||
input_transforms: {
|
||||
x: {
|
||||
type: 'static'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
skip_failure: false
|
||||
},
|
||||
{
|
||||
summary: '',
|
||||
modules: [
|
||||
{
|
||||
id: 'c',
|
||||
value: {
|
||||
type: 'rawscript',
|
||||
content:
|
||||
'package inner\n\nfunc main() (interface{}, error) {\n\treturn "World", nil\n}\n',
|
||||
language: RawScript.language.GO,
|
||||
input_transforms: {}
|
||||
}
|
||||
}
|
||||
],
|
||||
skip_failure: false
|
||||
}
|
||||
],
|
||||
parallel: false
|
||||
}
|
||||
}
|
||||
|
||||
$flowStore = $flowStore
|
||||
dispatch('reload')
|
||||
|
||||
setTimeout(() => {
|
||||
branchAllTutorial.moveNext()
|
||||
})
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
{
|
||||
element: '#flow-editor-test-flow',
|
||||
popover: {
|
||||
title: 'Test your flow',
|
||||
description: 'We can now test our flow',
|
||||
onNextClick: () => {
|
||||
clickButtonBySelector('#flow-editor-test-flow')
|
||||
|
||||
setTimeout(() => {
|
||||
branchAllTutorial.moveNext()
|
||||
})
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
{
|
||||
element: '#flow-editor-test-flow-drawer',
|
||||
popover: {
|
||||
title: 'Test your flow',
|
||||
description: 'Finally we can test our flow, and view the results!',
|
||||
onNextClick: () => {
|
||||
clickButtonBySelector('#flow-editor-test-flow-drawer')
|
||||
|
||||
setTimeout(() => {
|
||||
branchAllTutorial.moveNext()
|
||||
updateProgress(3)
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
})
|
||||
branchAllTutorial.drive()
|
||||
tutorial?.runTutorial()
|
||||
}
|
||||
</script>
|
||||
|
||||
<Tutorial
|
||||
bind:this={tutorial}
|
||||
index={3}
|
||||
name="branchall"
|
||||
on:error
|
||||
on:skipAll
|
||||
getSteps={(driver) => [
|
||||
{
|
||||
popover: {
|
||||
title: 'Welcome to the Windmil Flow editor',
|
||||
description:
|
||||
'Learn how to build our first branch to be executed on a condition. You can use arrow keys to navigate'
|
||||
}
|
||||
},
|
||||
{
|
||||
element: '#flow-editor-add-step-0',
|
||||
popover: {
|
||||
title: 'Add a step',
|
||||
description: 'Click here to add a step to your flow',
|
||||
onNextClick: () => {
|
||||
triggerAddFlowStep(0)
|
||||
|
||||
setTimeout(() => {
|
||||
driver.moveNext()
|
||||
})
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
{
|
||||
popover: {
|
||||
title: 'Steps kind',
|
||||
description: "Choose the kind of step you want to add. Let's start with a simple action"
|
||||
},
|
||||
element: '#flow-editor-insert-module'
|
||||
},
|
||||
|
||||
{
|
||||
popover: {
|
||||
title: 'Insert Branch all',
|
||||
description: "Let's pick branch all",
|
||||
onNextClick: () => {
|
||||
selectFlowStepKind(6)
|
||||
|
||||
setTimeout(() => {
|
||||
driver.moveNext()
|
||||
})
|
||||
}
|
||||
},
|
||||
element: '#flow-editor-insert-module > div > button:nth-child(6)'
|
||||
},
|
||||
|
||||
{
|
||||
element: '#add-branch-button',
|
||||
popover: {
|
||||
title: 'Add branch',
|
||||
description: 'Click here to add a branch',
|
||||
onNextClick: () => {
|
||||
clickButtonBySelector('#add-branch-button')
|
||||
|
||||
setTimeout(() => {
|
||||
driver.moveNext()
|
||||
})
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
{
|
||||
element: '#flow-editor-step-input',
|
||||
popover: {
|
||||
title: 'Branch all',
|
||||
description: 'All branches will be executed, and the result will be gathered in an array',
|
||||
|
||||
onNextClick: () => {
|
||||
if ($flowStore.value.modules[0].value.type === 'branchall') {
|
||||
$flowStore.value.modules[0].value = {
|
||||
type: 'branchall',
|
||||
branches: [
|
||||
{
|
||||
modules: [
|
||||
{
|
||||
id: 'b',
|
||||
value: {
|
||||
type: 'rawscript',
|
||||
content:
|
||||
'// import * as wmill from "npm:windmill-client@1"\n\nexport async function main(x: string) {\n return "Hello"\n}\n',
|
||||
language: RawScript.language.DENO,
|
||||
input_transforms: {
|
||||
x: {
|
||||
type: 'static'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
skip_failure: false
|
||||
},
|
||||
{
|
||||
summary: '',
|
||||
modules: [
|
||||
{
|
||||
id: 'c',
|
||||
value: {
|
||||
type: 'rawscript',
|
||||
content:
|
||||
'package inner\n\nfunc main() (interface{}, error) {\n\treturn "World", nil\n}\n',
|
||||
language: RawScript.language.GO,
|
||||
input_transforms: {}
|
||||
}
|
||||
}
|
||||
],
|
||||
skip_failure: false
|
||||
}
|
||||
],
|
||||
parallel: false
|
||||
}
|
||||
}
|
||||
|
||||
$flowStore = $flowStore
|
||||
dispatch('reload')
|
||||
|
||||
setTimeout(() => {
|
||||
driver.moveNext()
|
||||
})
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
{
|
||||
element: '#flow-editor-test-flow',
|
||||
popover: {
|
||||
title: 'Test your flow',
|
||||
description: 'We can now test our flow',
|
||||
onNextClick: () => {
|
||||
clickButtonBySelector('#flow-editor-test-flow')
|
||||
|
||||
setTimeout(() => {
|
||||
driver.moveNext()
|
||||
})
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
{
|
||||
element: '#flow-editor-test-flow-drawer',
|
||||
popover: {
|
||||
title: 'Test your flow',
|
||||
description: 'Finally we can test our flow, and view the results!',
|
||||
onNextClick: () => {
|
||||
clickButtonBySelector('#flow-editor-test-flow-drawer')
|
||||
|
||||
setTimeout(() => {
|
||||
driver.moveNext()
|
||||
updateProgress(3)
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
]}
|
||||
/>
|
||||
|
||||
@@ -1,313 +1,271 @@
|
||||
<script lang="ts">
|
||||
import { driver } from 'driver.js'
|
||||
import 'driver.js/dist/driver.css'
|
||||
import { createEventDispatcher, getContext } from 'svelte'
|
||||
import type { FlowEditorContext } from '../flows/types'
|
||||
import {
|
||||
clickButtonBySelector,
|
||||
setInputBySelector,
|
||||
triggerAddFlowStep,
|
||||
selectFlowStepKind,
|
||||
tainted
|
||||
selectFlowStepKind
|
||||
} from './utils'
|
||||
import { updateProgress } from '$lib/tutorialUtils'
|
||||
import { RawScript } from '$lib/gen'
|
||||
import Tutorial from './Tutorial.svelte'
|
||||
|
||||
const { flowStore } = getContext<FlowEditorContext>('FlowEditorContext')
|
||||
const dispatch = createEventDispatcher()
|
||||
|
||||
let tutorial: Tutorial | undefined = undefined
|
||||
|
||||
export function runTutorial() {
|
||||
if (tainted($flowStore)) {
|
||||
dispatch('error', { detail: 'branchone' })
|
||||
return
|
||||
}
|
||||
tutorial?.runTutorial()
|
||||
}
|
||||
</script>
|
||||
|
||||
const branchOneTutorial = driver({
|
||||
showProgress: true,
|
||||
allowClose: true,
|
||||
onPopoverRender: (popover, { config, state }) => {
|
||||
if (state.activeIndex == 0) {
|
||||
const skipThisButton = document.createElement('button')
|
||||
skipThisButton.innerText = 'Skip this tutorials'
|
||||
skipThisButton.addEventListener('click', () => {
|
||||
updateProgress(2)
|
||||
<Tutorial
|
||||
bind:this={tutorial}
|
||||
index={2}
|
||||
name="branchone"
|
||||
on:error
|
||||
on:skipAll
|
||||
getSteps={(driver) => [
|
||||
{
|
||||
popover: {
|
||||
title: 'Welcome to the Windmil Flow editor',
|
||||
description:
|
||||
'Learn how to build our first branch to be executed on a condition. You can use arrow keys to navigate'
|
||||
}
|
||||
},
|
||||
|
||||
branchOneTutorial.destroy()
|
||||
{
|
||||
popover: {
|
||||
title: 'Flows inputs',
|
||||
description: 'Flows have inputs that can be used in the flow',
|
||||
onNextClick: () => {
|
||||
clickButtonBySelector('#flow-editor-virtual-Input')
|
||||
|
||||
setTimeout(() => {
|
||||
driver.moveNext()
|
||||
})
|
||||
skipThisButton.setAttribute(
|
||||
'class',
|
||||
'border px-2 py-1 !text-xs font-normal rounded-md hover:bg-surface-hover transition-all flex items-center'
|
||||
)
|
||||
|
||||
const skipAllButton = document.createElement('button')
|
||||
skipAllButton.innerText = 'Skip all tutorials'
|
||||
skipAllButton.addEventListener('click', () => {
|
||||
dispatch('skipAll')
|
||||
branchOneTutorial.destroy()
|
||||
})
|
||||
|
||||
skipAllButton.setAttribute(
|
||||
'class',
|
||||
'border px-2 py-1 !text-xs font-normal rounded-md hover:bg-surface-hover transition-all flex items-center'
|
||||
)
|
||||
|
||||
const popoverDescription = document.querySelector('#driver-popover-description')
|
||||
|
||||
const div = document.createElement('div')
|
||||
|
||||
div.setAttribute('class', 'flex flex-row gap-2 justify-between w-full pt-2')
|
||||
|
||||
if (popoverDescription) {
|
||||
div.appendChild(skipAllButton)
|
||||
div.appendChild(skipThisButton)
|
||||
|
||||
popoverDescription.appendChild(div)
|
||||
}
|
||||
}
|
||||
},
|
||||
steps: [
|
||||
{
|
||||
popover: {
|
||||
title: 'Welcome to the Windmil Flow editor',
|
||||
description:
|
||||
'Learn how to build our first branch to be executed on a condition. You can use arrow keys to navigate'
|
||||
element: '#svelvet-Input'
|
||||
},
|
||||
{
|
||||
element: '#flow-editor-add-property',
|
||||
popover: {
|
||||
title: 'Add a property',
|
||||
description: 'Click here to add a property to your schema',
|
||||
onNextClick: () => {
|
||||
clickButtonBySelector('#flow-editor-add-property')
|
||||
setTimeout(() => {
|
||||
driver.moveNext()
|
||||
})
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
element: '#schema-modal-name',
|
||||
popover: {
|
||||
title: 'Name your property',
|
||||
description: 'Give a name to your property. Here we will call it condition',
|
||||
onNextClick: () => {
|
||||
setInputBySelector('#schema-modal-name', 'condition')
|
||||
driver.moveNext()
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
element: '#schema-modal-type-boolean',
|
||||
popover: {
|
||||
title: 'Property type',
|
||||
description: 'Choose the type of your property. Here we will choose boolean',
|
||||
onNextClick: () => {
|
||||
clickButtonBySelector('#schema-modal-type-boolean')
|
||||
driver.moveNext()
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
element: '#schema-modal-save',
|
||||
popover: {
|
||||
title: 'Save your property',
|
||||
description: 'Click here to save your property',
|
||||
onNextClick: () => {
|
||||
clickButtonBySelector('#schema-modal-save')
|
||||
|
||||
setTimeout(() => {
|
||||
driver.moveNext()
|
||||
})
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
{
|
||||
element: '#flow-editor-add-step-0',
|
||||
popover: {
|
||||
title: 'Branch one',
|
||||
description: 'Windmill supports branches, let us add one',
|
||||
onNextClick: () => {
|
||||
triggerAddFlowStep(0)
|
||||
|
||||
setTimeout(() => {
|
||||
driver.moveNext()
|
||||
})
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
popover: {
|
||||
title: 'Steps kind',
|
||||
description: 'Choose the kind of step you want to add.'
|
||||
},
|
||||
element: '#flow-editor-insert-module'
|
||||
},
|
||||
{
|
||||
popover: {
|
||||
title: 'Insert Branch one',
|
||||
description: "Let's pick branch one",
|
||||
onNextClick: () => {
|
||||
selectFlowStepKind(5)
|
||||
|
||||
setTimeout(() => {
|
||||
driver.moveNext()
|
||||
})
|
||||
}
|
||||
},
|
||||
element: '#flow-editor-insert-module > div > button:nth-child(5)'
|
||||
},
|
||||
|
||||
{
|
||||
element: '#add-branch-button',
|
||||
popover: {
|
||||
title: 'Add branch',
|
||||
description: 'Click here to add a branch',
|
||||
onNextClick: () => {
|
||||
clickButtonBySelector('#add-branch-button')
|
||||
|
||||
setTimeout(() => {
|
||||
driver.moveNext()
|
||||
})
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
{
|
||||
element: '#flow-editor-edit-predicate',
|
||||
popover: {
|
||||
title: 'Edit predicate',
|
||||
description: 'Click here to edit the predicate of your branch',
|
||||
onNextClick: () => {
|
||||
clickButtonBySelector('#flow-editor-edit-predicate')
|
||||
|
||||
setTimeout(() => {
|
||||
driver.moveNext()
|
||||
})
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
{
|
||||
element: '.key',
|
||||
popover: {
|
||||
title: 'Connect',
|
||||
description: 'As we did before, we can connect to the iterator of the loop',
|
||||
onNextClick: () => {
|
||||
if ($flowStore.value.modules[0].value.type === 'branchone') {
|
||||
$flowStore.value.modules[0].value.branches[0].expr = 'flow_input.condition'
|
||||
}
|
||||
},
|
||||
|
||||
{
|
||||
popover: {
|
||||
title: 'Flows inputs',
|
||||
description: 'Flows have inputs that can be used in the flow',
|
||||
onNextClick: () => {
|
||||
clickButtonBySelector('#flow-editor-virtual-Input')
|
||||
$flowStore = $flowStore
|
||||
dispatch('reload')
|
||||
|
||||
setTimeout(() => {
|
||||
branchOneTutorial.moveNext()
|
||||
})
|
||||
}
|
||||
},
|
||||
element: '#svelvet-Input'
|
||||
},
|
||||
{
|
||||
element: '#flow-editor-add-property',
|
||||
popover: {
|
||||
title: 'Add a property',
|
||||
description: 'Click here to add a property to your schema',
|
||||
onNextClick: () => {
|
||||
clickButtonBySelector('#flow-editor-add-property')
|
||||
setTimeout(() => {
|
||||
branchOneTutorial.moveNext()
|
||||
})
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
element: '#schema-modal-name',
|
||||
popover: {
|
||||
title: 'Name your property',
|
||||
description: 'Give a name to your property. Here we will call it condition',
|
||||
onNextClick: () => {
|
||||
setInputBySelector('#schema-modal-name', 'condition')
|
||||
branchOneTutorial.moveNext()
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
element: '#schema-modal-type-boolean',
|
||||
popover: {
|
||||
title: 'Property type',
|
||||
description: 'Choose the type of your property. Here we will choose boolean',
|
||||
onNextClick: () => {
|
||||
clickButtonBySelector('#schema-modal-type-boolean')
|
||||
branchOneTutorial.moveNext()
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
element: '#schema-modal-save',
|
||||
popover: {
|
||||
title: 'Save your property',
|
||||
description: 'Click here to save your property',
|
||||
onNextClick: () => {
|
||||
clickButtonBySelector('#schema-modal-save')
|
||||
setTimeout(() => {
|
||||
driver.moveNext()
|
||||
})
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
setTimeout(() => {
|
||||
branchOneTutorial.moveNext()
|
||||
})
|
||||
}
|
||||
}
|
||||
},
|
||||
//flow-editor-add-step-
|
||||
|
||||
{
|
||||
element: '#flow-editor-add-step-0',
|
||||
popover: {
|
||||
title: 'Branch one',
|
||||
description: 'Windmill supports branches, let us add one',
|
||||
onNextClick: () => {
|
||||
triggerAddFlowStep(0)
|
||||
|
||||
setTimeout(() => {
|
||||
branchOneTutorial.moveNext()
|
||||
})
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
popover: {
|
||||
title: 'Steps kind',
|
||||
description: 'Choose the kind of step you want to add.'
|
||||
},
|
||||
element: '#flow-editor-insert-module'
|
||||
},
|
||||
{
|
||||
popover: {
|
||||
title: 'Insert Branch one',
|
||||
description: "Let's pick branch one",
|
||||
onNextClick: () => {
|
||||
selectFlowStepKind(5)
|
||||
|
||||
setTimeout(() => {
|
||||
branchOneTutorial.moveNext()
|
||||
})
|
||||
}
|
||||
},
|
||||
element: '#flow-editor-insert-module > div > button:nth-child(5)'
|
||||
},
|
||||
|
||||
{
|
||||
element: '#add-branch-button',
|
||||
popover: {
|
||||
title: 'Add branch',
|
||||
description: 'Click here to add a branch',
|
||||
onNextClick: () => {
|
||||
clickButtonBySelector('#add-branch-button')
|
||||
|
||||
setTimeout(() => {
|
||||
branchOneTutorial.moveNext()
|
||||
})
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
{
|
||||
element: '#flow-editor-edit-predicate',
|
||||
popover: {
|
||||
title: 'Edit predicate',
|
||||
description: 'Click here to edit the predicate of your branch',
|
||||
onNextClick: () => {
|
||||
clickButtonBySelector('#flow-editor-edit-predicate')
|
||||
|
||||
setTimeout(() => {
|
||||
branchOneTutorial.moveNext()
|
||||
})
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
{
|
||||
element: '.key',
|
||||
popover: {
|
||||
title: 'Connect',
|
||||
description: 'As we did before, we can connect to the iterator of the loop',
|
||||
onNextClick: () => {
|
||||
if ($flowStore.value.modules[0].value.type === 'branchone') {
|
||||
$flowStore.value.modules[0].value.branches[0].expr = 'flow_input.condition'
|
||||
}
|
||||
|
||||
$flowStore = $flowStore
|
||||
dispatch('reload')
|
||||
|
||||
setTimeout(() => {
|
||||
branchOneTutorial.moveNext()
|
||||
})
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
//flow-editor-add-step-
|
||||
|
||||
{
|
||||
popover: {
|
||||
title: 'Branche modules',
|
||||
description:
|
||||
'We can now add modules to each branch, one in typescript and one in python',
|
||||
onNextClick: () => {
|
||||
if ($flowStore.value.modules[0].value.type === 'branchone') {
|
||||
$flowStore.value.modules[0].value = {
|
||||
type: 'branchone',
|
||||
branches: [
|
||||
{
|
||||
popover: {
|
||||
title: 'Branche modules',
|
||||
description: 'We can now add modules to each branch, one in typescript and one in python',
|
||||
onNextClick: () => {
|
||||
if ($flowStore.value.modules[0].value.type === 'branchone') {
|
||||
$flowStore.value.modules[0].value = {
|
||||
type: 'branchone',
|
||||
branches: [
|
||||
{
|
||||
summary: '',
|
||||
expr: 'flow_input.condition',
|
||||
modules: [
|
||||
{
|
||||
summary: '',
|
||||
expr: 'flow_input.condition',
|
||||
modules: [
|
||||
{
|
||||
id: 'c',
|
||||
value: {
|
||||
type: 'rawscript',
|
||||
content:
|
||||
'export async function main() {\n return "Entered the condition!";\n}\n',
|
||||
language: RawScript.language.DENO,
|
||||
input_transforms: {}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
default: [
|
||||
{
|
||||
id: 'b',
|
||||
id: 'c',
|
||||
value: {
|
||||
type: 'rawscript',
|
||||
content: '# import wmill\n\n\ndef main():\n return "Default Branch"',
|
||||
language: RawScript.language.PYTHON3,
|
||||
content:
|
||||
'export async function main() {\n return "Entered the condition!";\n}\n',
|
||||
language: RawScript.language.DENO,
|
||||
input_transforms: {}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
$flowStore = $flowStore
|
||||
dispatch('reload')
|
||||
|
||||
setTimeout(() => {
|
||||
branchOneTutorial.moveNext()
|
||||
})
|
||||
],
|
||||
default: [
|
||||
{
|
||||
id: 'b',
|
||||
value: {
|
||||
type: 'rawscript',
|
||||
content: '# import wmill\n\n\ndef main():\n return "Default Branch"',
|
||||
language: RawScript.language.PYTHON3,
|
||||
input_transforms: {}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
$flowStore = $flowStore
|
||||
dispatch('reload')
|
||||
|
||||
{
|
||||
element: '#flow-editor-test-flow',
|
||||
popover: {
|
||||
title: 'Test your flow',
|
||||
description: 'We can now test our flow',
|
||||
onNextClick: () => {
|
||||
clickButtonBySelector('#flow-editor-test-flow')
|
||||
|
||||
setTimeout(() => {
|
||||
branchOneTutorial.moveNext()
|
||||
})
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
{
|
||||
element: '#flow-editor-test-flow-drawer',
|
||||
popover: {
|
||||
title: 'Test your flow',
|
||||
description: 'Finally we can test our flow, and view the results!',
|
||||
onNextClick: () => {
|
||||
clickButtonBySelector('#flow-editor-test-flow-drawer')
|
||||
|
||||
setTimeout(() => {
|
||||
branchOneTutorial.moveNext()
|
||||
updateProgress(2)
|
||||
})
|
||||
}
|
||||
}
|
||||
setTimeout(() => {
|
||||
driver.moveNext()
|
||||
})
|
||||
}
|
||||
]
|
||||
})
|
||||
branchOneTutorial.drive()
|
||||
}
|
||||
</script>
|
||||
}
|
||||
},
|
||||
|
||||
{
|
||||
element: '#flow-editor-test-flow',
|
||||
popover: {
|
||||
title: 'Test your flow',
|
||||
description: 'We can now test our flow',
|
||||
onNextClick: () => {
|
||||
clickButtonBySelector('#flow-editor-test-flow')
|
||||
|
||||
setTimeout(() => {
|
||||
driver.moveNext()
|
||||
})
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
{
|
||||
element: '#flow-editor-test-flow-drawer',
|
||||
popover: {
|
||||
title: 'Test your flow',
|
||||
description: 'Finally we can test our flow, and view the results!',
|
||||
onNextClick: () => {
|
||||
clickButtonBySelector('#flow-editor-test-flow-drawer')
|
||||
|
||||
setTimeout(() => {
|
||||
driver.moveNext()
|
||||
updateProgress(2)
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
]}
|
||||
/>
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
<script lang="ts">
|
||||
import { driver } from 'driver.js'
|
||||
import 'driver.js/dist/driver.css'
|
||||
import { createEventDispatcher, getContext } from 'svelte'
|
||||
import type { FlowEditorContext } from '../flows/types'
|
||||
import { updateProgress } from '$lib/tutorialUtils'
|
||||
@@ -8,310 +6,269 @@
|
||||
clickButtonBySelector,
|
||||
selectFlowStepKind,
|
||||
setInputBySelector,
|
||||
tainted,
|
||||
triggerAddFlowStep
|
||||
} from './utils'
|
||||
import Tutorial from './Tutorial.svelte'
|
||||
|
||||
const { flowStore } = getContext<FlowEditorContext>('FlowEditorContext')
|
||||
const dispatch = createEventDispatcher()
|
||||
|
||||
const queue: string[] = []
|
||||
|
||||
let tutorial: Tutorial | undefined = undefined
|
||||
|
||||
export function runTutorial() {
|
||||
if (tainted($flowStore)) {
|
||||
dispatch('error', { detail: 'action' })
|
||||
return
|
||||
}
|
||||
|
||||
const simpleFlowTutorial = driver({
|
||||
showProgress: true,
|
||||
allowClose: true,
|
||||
onPopoverRender: (popover, { config, state }) => {
|
||||
if (state.activeIndex == 0) {
|
||||
const skipThisButton = document.createElement('button')
|
||||
skipThisButton.innerText = 'Skip this tutorials'
|
||||
skipThisButton.addEventListener('click', () => {
|
||||
updateProgress(0)
|
||||
simpleFlowTutorial.destroy()
|
||||
})
|
||||
skipThisButton.setAttribute(
|
||||
'class',
|
||||
'border px-2 py-1 !text-xs font-normal rounded-md hover:bg-surface-hover transition-all flex items-center'
|
||||
)
|
||||
|
||||
const skipAllButton = document.createElement('button')
|
||||
skipAllButton.innerText = 'Skip all tutorials'
|
||||
skipAllButton.addEventListener('click', () => {
|
||||
dispatch('skipAll')
|
||||
simpleFlowTutorial.destroy()
|
||||
})
|
||||
|
||||
skipAllButton.setAttribute(
|
||||
'class',
|
||||
'border px-2 py-1 !text-xs font-normal rounded-md hover:bg-surface-hover transition-all flex items-center'
|
||||
)
|
||||
|
||||
const popoverDescription = document.querySelector('#driver-popover-description')
|
||||
|
||||
const div = document.createElement('div')
|
||||
|
||||
div.setAttribute('class', 'flex flex-row gap-2 justify-between w-full pt-2')
|
||||
|
||||
if (popoverDescription) {
|
||||
div.appendChild(skipAllButton)
|
||||
div.appendChild(skipThisButton)
|
||||
|
||||
popoverDescription.appendChild(div)
|
||||
}
|
||||
}
|
||||
},
|
||||
steps: [
|
||||
{
|
||||
popover: {
|
||||
title: 'Welcome to the Windmil Flow editor',
|
||||
description:
|
||||
'Learn how to build powerful flows in a few steps. You can use arrow keys to navigate.'
|
||||
}
|
||||
},
|
||||
{
|
||||
popover: {
|
||||
title: 'Flows inputs',
|
||||
description: 'Flows have inputs that can be used in the flow',
|
||||
onNextClick: () => {
|
||||
clickButtonBySelector('#flow-editor-virtual-Input')
|
||||
setTimeout(() => {
|
||||
simpleFlowTutorial.moveNext()
|
||||
})
|
||||
}
|
||||
},
|
||||
element: '#svelvet-Input'
|
||||
},
|
||||
{
|
||||
element: '#flow-editor-add-property',
|
||||
popover: {
|
||||
title: 'Add a property',
|
||||
description: 'Click here to add a property to your schema',
|
||||
onNextClick: () => {
|
||||
clickButtonBySelector('#flow-editor-add-property')
|
||||
|
||||
setTimeout(() => {
|
||||
simpleFlowTutorial.moveNext()
|
||||
})
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
element: '#schema-modal-name',
|
||||
popover: {
|
||||
title: 'Name your property',
|
||||
description: 'Give a name to your property. Here we will call it firstname',
|
||||
onNextClick: () => {
|
||||
setInputBySelector('#schema-modal-name', 'firstname')
|
||||
|
||||
simpleFlowTutorial.moveNext()
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
element: '#schema-modal-save',
|
||||
popover: {
|
||||
title: 'Save your property',
|
||||
description: 'Click here to save your property',
|
||||
onNextClick: () => {
|
||||
queue.push(JSON.stringify($flowStore))
|
||||
|
||||
clickButtonBySelector('#schema-modal-save')
|
||||
|
||||
setTimeout(() => {
|
||||
simpleFlowTutorial.moveNext()
|
||||
})
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
element: '#flow-editor-add-step-0',
|
||||
popover: {
|
||||
title: 'Add a step',
|
||||
description: 'Click here to add a step to your flow',
|
||||
onNextClick: () => {
|
||||
triggerAddFlowStep(0)
|
||||
|
||||
setTimeout(() => {
|
||||
simpleFlowTutorial.moveNext()
|
||||
})
|
||||
},
|
||||
|
||||
onPrevClick: () => {
|
||||
simpleFlowTutorial.moveTo(1)
|
||||
$flowStore = JSON.parse(queue.pop() || '{}')
|
||||
dispatch('reload')
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
{
|
||||
popover: {
|
||||
title: 'Steps kind',
|
||||
description: "Choose the kind of step you want to add. Let's start with a simple action"
|
||||
},
|
||||
element: '#flow-editor-insert-module'
|
||||
},
|
||||
{
|
||||
popover: {
|
||||
title: 'Pick an action',
|
||||
description: 'Let’s pick an action to add to your flow',
|
||||
onNextClick: () => {
|
||||
selectFlowStepKind(1)
|
||||
|
||||
setTimeout(() => {
|
||||
simpleFlowTutorial.moveNext()
|
||||
})
|
||||
}
|
||||
},
|
||||
element: '#flow-editor-insert-module > div > button:nth-child(1)'
|
||||
},
|
||||
{
|
||||
element: '#flow-editor-flow-inputs',
|
||||
popover: {
|
||||
title: 'Action configuration',
|
||||
description: 'An action can be inlined, imported from your workspace or the Hub.'
|
||||
}
|
||||
},
|
||||
{
|
||||
element: '#flow-editor-action-script',
|
||||
popover: {
|
||||
title: 'Supported languages',
|
||||
description: 'Windmill support the following languages/runtimes.'
|
||||
}
|
||||
},
|
||||
{
|
||||
element: '#flow-editor-action-script > button:nth-child(1)',
|
||||
popover: {
|
||||
title: 'Typescript',
|
||||
description: "Let's pick an action to add to your flow",
|
||||
onNextClick: () => {
|
||||
clickButtonBySelector(
|
||||
'#flow-editor-action-script > button > div > button:nth-child(1)'
|
||||
)
|
||||
|
||||
setTimeout(() => {
|
||||
simpleFlowTutorial.moveNext()
|
||||
})
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
{
|
||||
element: '#flow-editor-editor',
|
||||
popover: {
|
||||
title: 'Action editor',
|
||||
description: 'Windmill provides a full code editor to write your actions'
|
||||
}
|
||||
},
|
||||
|
||||
{
|
||||
element: '#flow-editor-step-input',
|
||||
popover: {
|
||||
title: 'Autogenerated schema',
|
||||
description: 'The schema and the UI is autogenerated from your code'
|
||||
}
|
||||
},
|
||||
|
||||
{
|
||||
element: '#flow-editor-plug',
|
||||
popover: {
|
||||
title: 'Connect',
|
||||
description:
|
||||
'You can provide static values or connect to other nodes result. Here we will connect to the firstname input',
|
||||
onNextClick: () => {
|
||||
clickButtonBySelector('#flow-editor-plug')
|
||||
|
||||
setTimeout(() => {
|
||||
simpleFlowTutorial.moveNext()
|
||||
})
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
element: '.key',
|
||||
popover: {
|
||||
title: 'Connection mode',
|
||||
description: 'Once you pressed the connect button, you can choose what to connect to.',
|
||||
onNextClick: () => {
|
||||
if ($flowStore.value.modules[0].value.type === 'rawscript') {
|
||||
$flowStore.value.modules[0].value.input_transforms = {
|
||||
x: {
|
||||
type: 'javascript',
|
||||
expr: 'flow_input.firstname'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$flowStore = $flowStore
|
||||
dispatch('reload')
|
||||
|
||||
setTimeout(() => {
|
||||
simpleFlowTutorial.moveNext()
|
||||
})
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
{
|
||||
element: '#flow-editor-step-input',
|
||||
popover: {
|
||||
title: 'Input connected!',
|
||||
description: 'The input is now connected to the firstname input'
|
||||
}
|
||||
},
|
||||
|
||||
{
|
||||
element: '#flow-editor-test-flow',
|
||||
popover: {
|
||||
title: 'Test your flow',
|
||||
description: 'We can now test our flow',
|
||||
onNextClick: () => {
|
||||
clickButtonBySelector('#flow-editor-test-flow')
|
||||
|
||||
setTimeout(() => {
|
||||
simpleFlowTutorial.moveNext()
|
||||
})
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
{
|
||||
element: '#flow-preview-content',
|
||||
popover: {
|
||||
title: 'Flow input',
|
||||
description: 'Let’s provide an input to our flow',
|
||||
onNextClick: () => {
|
||||
setInputBySelector('textarea.w-full', 'Hello World!')
|
||||
|
||||
setTimeout(() => {
|
||||
simpleFlowTutorial.moveNext()
|
||||
})
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
element: '#flow-editor-test-flow-drawer',
|
||||
popover: {
|
||||
title: 'Test your flow',
|
||||
description: 'Finally we can test our flow, and view the results!',
|
||||
onNextClick: () => {
|
||||
clickButtonBySelector('#flow-editor-test-flow-drawer')
|
||||
|
||||
setTimeout(() => {
|
||||
simpleFlowTutorial.moveNext()
|
||||
|
||||
updateProgress(0)
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
})
|
||||
simpleFlowTutorial.drive()
|
||||
tutorial?.runTutorial()
|
||||
}
|
||||
</script>
|
||||
|
||||
<Tutorial
|
||||
bind:this={tutorial}
|
||||
index={0}
|
||||
name="action"
|
||||
on:error
|
||||
on:skipAll
|
||||
getSteps={(driver) => [
|
||||
{
|
||||
popover: {
|
||||
title: 'Welcome to the Windmil Flow editor',
|
||||
description:
|
||||
'Learn how to build powerful flows in a few steps. You can use arrow keys to navigate.'
|
||||
}
|
||||
},
|
||||
{
|
||||
popover: {
|
||||
title: 'Flows inputs',
|
||||
description: 'Flows have inputs that can be used in the flow',
|
||||
onNextClick: () => {
|
||||
clickButtonBySelector('#flow-editor-virtual-Input')
|
||||
setTimeout(() => {
|
||||
driver.moveNext()
|
||||
})
|
||||
}
|
||||
},
|
||||
element: '#svelvet-Input'
|
||||
},
|
||||
{
|
||||
element: '#flow-editor-add-property',
|
||||
popover: {
|
||||
title: 'Add a property',
|
||||
description: 'Click here to add a property to your schema',
|
||||
onNextClick: () => {
|
||||
clickButtonBySelector('#flow-editor-add-property')
|
||||
|
||||
setTimeout(() => {
|
||||
driver.moveNext()
|
||||
})
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
element: '#schema-modal-name',
|
||||
popover: {
|
||||
title: 'Name your property',
|
||||
description: 'Give a name to your property. Here we will call it firstname',
|
||||
onNextClick: () => {
|
||||
setInputBySelector('#schema-modal-name', 'firstname')
|
||||
|
||||
driver.moveNext()
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
element: '#schema-modal-save',
|
||||
popover: {
|
||||
title: 'Save your property',
|
||||
description: 'Click here to save your property',
|
||||
onNextClick: () => {
|
||||
queue.push(JSON.stringify($flowStore))
|
||||
|
||||
clickButtonBySelector('#schema-modal-save')
|
||||
|
||||
setTimeout(() => {
|
||||
driver.moveNext()
|
||||
})
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
element: '#flow-editor-add-step-0',
|
||||
popover: {
|
||||
title: 'Add a step',
|
||||
description: 'Click here to add a step to your flow',
|
||||
onNextClick: () => {
|
||||
triggerAddFlowStep(0)
|
||||
|
||||
setTimeout(() => {
|
||||
driver.moveNext()
|
||||
})
|
||||
},
|
||||
|
||||
onPrevClick: () => {
|
||||
driver.moveTo(1)
|
||||
$flowStore = JSON.parse(queue.pop() || '{}')
|
||||
dispatch('reload')
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
{
|
||||
popover: {
|
||||
title: 'Steps kind',
|
||||
description: "Choose the kind of step you want to add. Let's start with a simple action"
|
||||
},
|
||||
element: '#flow-editor-insert-module'
|
||||
},
|
||||
{
|
||||
popover: {
|
||||
title: 'Pick an action',
|
||||
description: 'Let’s pick an action to add to your flow',
|
||||
onNextClick: () => {
|
||||
selectFlowStepKind(1)
|
||||
|
||||
setTimeout(() => {
|
||||
driver.moveNext()
|
||||
})
|
||||
}
|
||||
},
|
||||
element: '#flow-editor-insert-module > div > button:nth-child(1)'
|
||||
},
|
||||
{
|
||||
element: '#flow-editor-flow-inputs',
|
||||
popover: {
|
||||
title: 'Action configuration',
|
||||
description: 'An action can be inlined, imported from your workspace or the Hub.'
|
||||
}
|
||||
},
|
||||
{
|
||||
element: '#flow-editor-action-script',
|
||||
popover: {
|
||||
title: 'Supported languages',
|
||||
description: 'Windmill support the following languages/runtimes.'
|
||||
}
|
||||
},
|
||||
{
|
||||
element: '#flow-editor-action-script > button:nth-child(1)',
|
||||
popover: {
|
||||
title: 'Typescript',
|
||||
description: "Let's pick an action to add to your flow",
|
||||
onNextClick: () => {
|
||||
clickButtonBySelector('#flow-editor-action-script > button > div > button:nth-child(1)')
|
||||
|
||||
setTimeout(() => {
|
||||
driver.moveNext()
|
||||
})
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
{
|
||||
element: '#flow-editor-editor',
|
||||
popover: {
|
||||
title: 'Action editor',
|
||||
description: 'Windmill provides a full code editor to write your actions'
|
||||
}
|
||||
},
|
||||
|
||||
{
|
||||
element: '#flow-editor-step-input',
|
||||
popover: {
|
||||
title: 'Autogenerated schema',
|
||||
description: 'The schema and the UI is autogenerated from your code'
|
||||
}
|
||||
},
|
||||
|
||||
{
|
||||
element: '#flow-editor-plug',
|
||||
popover: {
|
||||
title: 'Connect',
|
||||
description:
|
||||
'You can provide static values or connect to other nodes result. Here we will connect to the firstname input',
|
||||
onNextClick: () => {
|
||||
clickButtonBySelector('#flow-editor-plug')
|
||||
|
||||
setTimeout(() => {
|
||||
driver.moveNext()
|
||||
})
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
element: '.key',
|
||||
popover: {
|
||||
title: 'Connection mode',
|
||||
description: 'Once you pressed the connect button, you can choose what to connect to.',
|
||||
onNextClick: () => {
|
||||
if ($flowStore.value.modules[0].value.type === 'rawscript') {
|
||||
$flowStore.value.modules[0].value.input_transforms = {
|
||||
x: {
|
||||
type: 'javascript',
|
||||
expr: 'flow_input.firstname'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$flowStore = $flowStore
|
||||
dispatch('reload')
|
||||
|
||||
setTimeout(() => {
|
||||
driver.moveNext()
|
||||
})
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
{
|
||||
element: '#flow-editor-step-input',
|
||||
popover: {
|
||||
title: 'Input connected!',
|
||||
description: 'The input is now connected to the firstname input'
|
||||
}
|
||||
},
|
||||
|
||||
{
|
||||
element: '#flow-editor-test-flow',
|
||||
popover: {
|
||||
title: 'Test your flow',
|
||||
description: 'We can now test our flow',
|
||||
onNextClick: () => {
|
||||
clickButtonBySelector('#flow-editor-test-flow')
|
||||
|
||||
setTimeout(() => {
|
||||
driver.moveNext()
|
||||
})
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
{
|
||||
element: '#flow-preview-content',
|
||||
popover: {
|
||||
title: 'Flow input',
|
||||
description: 'Let’s provide an input to our flow',
|
||||
onNextClick: () => {
|
||||
setInputBySelector('textarea.w-full', 'Hello World!')
|
||||
|
||||
setTimeout(() => {
|
||||
driver.moveNext()
|
||||
})
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
element: '#flow-editor-test-flow-drawer',
|
||||
popover: {
|
||||
title: 'Test your flow',
|
||||
description: 'Finally we can test our flow, and view the results!',
|
||||
onNextClick: () => {
|
||||
clickButtonBySelector('#flow-editor-test-flow-drawer')
|
||||
|
||||
setTimeout(() => {
|
||||
driver.moveNext()
|
||||
|
||||
updateProgress(0)
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
]}
|
||||
/>
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
<script lang="ts">
|
||||
import { driver } from 'driver.js'
|
||||
import 'driver.js/dist/driver.css'
|
||||
import { createEventDispatcher, getContext } from 'svelte'
|
||||
import type { FlowEditorContext } from '../flows/types'
|
||||
import { emptyFlowModuleState } from '../flows/utils'
|
||||
@@ -9,402 +7,363 @@
|
||||
setInputBySelector,
|
||||
triggerAddFlowStep,
|
||||
selectFlowStepKind,
|
||||
selectOptionsBySelector,
|
||||
tainted
|
||||
selectOptionsBySelector
|
||||
} from './utils'
|
||||
import { updateProgress } from '$lib/tutorialUtils'
|
||||
import Tutorial from './Tutorial.svelte'
|
||||
|
||||
const { flowStore, selectedId, flowStateStore } =
|
||||
getContext<FlowEditorContext>('FlowEditorContext')
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
|
||||
let tutorial: Tutorial | undefined = undefined
|
||||
|
||||
export function runTutorial() {
|
||||
if (tainted($flowStore)) {
|
||||
dispatch('error', { detail: 'forloop' })
|
||||
return
|
||||
}
|
||||
|
||||
const forloopTutorial = driver({
|
||||
showProgress: true,
|
||||
allowClose: true,
|
||||
onPopoverRender: (popover, { config, state }) => {
|
||||
if (state.activeIndex == 0) {
|
||||
const skipThisButton = document.createElement('button')
|
||||
skipThisButton.innerText = 'Skip this tutorials'
|
||||
skipThisButton.addEventListener('click', () => {
|
||||
updateProgress(1)
|
||||
forloopTutorial.destroy()
|
||||
})
|
||||
skipThisButton.setAttribute(
|
||||
'class',
|
||||
'border px-2 py-1 !text-xs font-normal rounded-md hover:bg-surface-hover transition-all flex items-center'
|
||||
)
|
||||
|
||||
const skipAllButton = document.createElement('button')
|
||||
skipAllButton.innerText = 'Skip all tutorials'
|
||||
skipAllButton.addEventListener('click', () => {
|
||||
dispatch('skipAll')
|
||||
forloopTutorial.destroy()
|
||||
})
|
||||
|
||||
skipAllButton.setAttribute(
|
||||
'class',
|
||||
'border px-2 py-1 !text-xs font-normal rounded-md hover:bg-surface-hover transition-all flex items-center'
|
||||
)
|
||||
|
||||
const popoverDescription = document.querySelector('#driver-popover-description')
|
||||
|
||||
const div = document.createElement('div')
|
||||
|
||||
div.setAttribute('class', 'flex flex-row gap-2 justify-between w-full pt-2')
|
||||
|
||||
if (popoverDescription) {
|
||||
div.appendChild(skipAllButton)
|
||||
div.appendChild(skipThisButton)
|
||||
|
||||
popoverDescription.appendChild(div)
|
||||
}
|
||||
}
|
||||
},
|
||||
steps: [
|
||||
{
|
||||
popover: {
|
||||
title: 'Welcome to the Windmil Flow editor',
|
||||
description:
|
||||
'Learn how to build our first for loop to iterate on. You can use arrow keys to navigate.'
|
||||
}
|
||||
},
|
||||
|
||||
{
|
||||
popover: {
|
||||
title: 'Flows inputs',
|
||||
description: 'Flows have inputs that can be used in the flow',
|
||||
onNextClick: () => {
|
||||
clickButtonBySelector('#flow-editor-virtual-Input')
|
||||
|
||||
setTimeout(() => {
|
||||
forloopTutorial.moveNext()
|
||||
})
|
||||
}
|
||||
},
|
||||
element: '#svelvet-Input'
|
||||
},
|
||||
{
|
||||
element: '#flow-editor-add-property',
|
||||
popover: {
|
||||
title: 'Add a property',
|
||||
description: 'Click here to add a property to your schema',
|
||||
onNextClick: () => {
|
||||
clickButtonBySelector('#flow-editor-add-property')
|
||||
|
||||
setTimeout(() => {
|
||||
forloopTutorial.moveNext()
|
||||
})
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
element: '#schema-modal-name',
|
||||
popover: {
|
||||
title: 'Name your property',
|
||||
description: 'Give a name to your property. Here we will call it firstname',
|
||||
onNextClick: () => {
|
||||
setInputBySelector('#schema-modal-name', 'array')
|
||||
forloopTutorial.moveNext()
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
element: '#schema-modal-type-array',
|
||||
popover: {
|
||||
title: 'Property type',
|
||||
description: 'Choose the type of your property. Here we will choose array',
|
||||
onNextClick: () => {
|
||||
clickButtonBySelector('#schema-modal-type-array')
|
||||
forloopTutorial.moveNext()
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
element: '#array-type-narrowing',
|
||||
popover: {
|
||||
title: 'Array type narrowing',
|
||||
description: 'You can narrow the type of your array. Here we will choose numbers',
|
||||
onNextClick: () => {
|
||||
selectOptionsBySelector('#array-type-narrowing', 'number')
|
||||
forloopTutorial.moveNext()
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
element: '#schema-modal-save',
|
||||
popover: {
|
||||
title: 'Save your property',
|
||||
description: 'Click here to save your property',
|
||||
onNextClick: () => {
|
||||
clickButtonBySelector('#schema-modal-save')
|
||||
|
||||
setTimeout(() => {
|
||||
forloopTutorial.moveNext()
|
||||
})
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
element: '#flow-editor-add-step-0',
|
||||
popover: {
|
||||
title: 'Add a step',
|
||||
description: 'Click here to add a step to your flow',
|
||||
onNextClick: () => {
|
||||
triggerAddFlowStep(0)
|
||||
|
||||
setTimeout(() => {
|
||||
forloopTutorial.moveNext()
|
||||
})
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
{
|
||||
popover: {
|
||||
title: 'Steps kind',
|
||||
description: "Choose the kind of step you want to add. Let's start with a simple action"
|
||||
},
|
||||
element: '#flow-editor-insert-module'
|
||||
},
|
||||
|
||||
{
|
||||
popover: {
|
||||
title: 'Insert loop',
|
||||
description: "Let's pick forloop",
|
||||
onNextClick: () => {
|
||||
selectFlowStepKind(4)
|
||||
setTimeout(() => {
|
||||
forloopTutorial.moveNext()
|
||||
})
|
||||
}
|
||||
},
|
||||
element: '#flow-editor-insert-module > div > button:nth-child(4)'
|
||||
},
|
||||
|
||||
{
|
||||
element: '#flow-editor-iterator-expression',
|
||||
popover: {
|
||||
title: 'Iterator expression',
|
||||
description:
|
||||
'The iterator expression is a javascript expression that respresents the array to iterate on. Here we will iterate on the firstname input letter by letter',
|
||||
onNextClick: () => {
|
||||
if ($flowStore.value.modules[0].value.type === 'forloopflow') {
|
||||
if ($flowStore.value.modules[0].value.iterator.type === 'javascript') {
|
||||
$flowStore.value.modules[0].value.iterator.expr = 'flow_input.array'
|
||||
}
|
||||
}
|
||||
|
||||
$flowStore = $flowStore
|
||||
|
||||
dispatch('reload')
|
||||
|
||||
setTimeout(() => {
|
||||
forloopTutorial.moveNext()
|
||||
})
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
{
|
||||
element: '#flow-editor-iterator-expression',
|
||||
popover: {
|
||||
title: 'Iterator expression',
|
||||
description:
|
||||
'We can refer to the result of previous steps using the results object: results.a',
|
||||
onNextClick: () => {
|
||||
if ($flowStore.value.modules[0].value.type === 'forloopflow') {
|
||||
$flowStore.value.modules[0].value.modules = [
|
||||
{
|
||||
id: 'b',
|
||||
value: {
|
||||
type: 'rawscript',
|
||||
content: 'def main(x: int):\n return x*2',
|
||||
// @ts-ignore
|
||||
language: 'python3',
|
||||
input_transforms: {
|
||||
x: {
|
||||
type: 'javascript',
|
||||
// @ts-ignore
|
||||
value: '',
|
||||
expr: ''
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
$flowStateStore['b'] = emptyFlowModuleState()
|
||||
|
||||
$flowStateStore['b'].schema.properties = {
|
||||
x: {
|
||||
type: 'number',
|
||||
description: '',
|
||||
default: null
|
||||
}
|
||||
}
|
||||
|
||||
$flowStore = $flowStore
|
||||
|
||||
dispatch('reload')
|
||||
|
||||
setTimeout(() => {
|
||||
forloopTutorial.moveNext()
|
||||
})
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
element: '#svelvet-c',
|
||||
popover: {
|
||||
title: 'Step of the loop',
|
||||
description: 'We added an action to the loop. Let’s configure it',
|
||||
onNextClick: () => {
|
||||
$selectedId = 'b'
|
||||
$flowStore = $flowStore
|
||||
|
||||
dispatch('reload')
|
||||
setTimeout(() => {
|
||||
forloopTutorial.moveNext()
|
||||
})
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
element: '#flow-editor-editor',
|
||||
popover: {
|
||||
title: 'Python step',
|
||||
description:
|
||||
'We can write python code in the editor. In this example we will capitalize the letter'
|
||||
}
|
||||
},
|
||||
|
||||
{
|
||||
element: '#flow-editor-step-input',
|
||||
popover: {
|
||||
title: 'flow editor',
|
||||
description: 'Description'
|
||||
}
|
||||
},
|
||||
|
||||
{
|
||||
element: '#flow-editor-plug',
|
||||
popover: {
|
||||
title: 'Input configuration',
|
||||
description:
|
||||
'UI is autogenerated from your code. Let’s connect the input to the letter input',
|
||||
onNextClick: () => {
|
||||
clickButtonBySelector('#flow-editor-plug')
|
||||
|
||||
setTimeout(() => {
|
||||
forloopTutorial.moveNext()
|
||||
})
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
element: '.key',
|
||||
popover: {
|
||||
title: 'Connect',
|
||||
description: 'As we did before, we can connect to the iterator of the loop',
|
||||
onNextClick: () => {
|
||||
if (
|
||||
$flowStore.value.modules[0].value.type === 'forloopflow' &&
|
||||
$flowStore.value.modules[0].value.modules[0].value.type === 'rawscript'
|
||||
) {
|
||||
$flowStore.value.modules[0].value.modules[0].value.input_transforms = {
|
||||
x: {
|
||||
type: 'javascript',
|
||||
expr: 'flow_input.iter.value'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$flowStore = $flowStore
|
||||
dispatch('reload')
|
||||
|
||||
setTimeout(() => {
|
||||
forloopTutorial.moveNext()
|
||||
})
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
element: '#flow-editor-step-input',
|
||||
popover: {
|
||||
title: 'Iterator',
|
||||
description:
|
||||
'Loops expose an iterator object that contains the current value of the loop and the index'
|
||||
}
|
||||
},
|
||||
{
|
||||
element: '#flow-editor-test-flow',
|
||||
popover: {
|
||||
title: 'Test your flow',
|
||||
description: 'We can now test our flow',
|
||||
onNextClick: () => {
|
||||
clickButtonBySelector('#flow-editor-test-flow')
|
||||
|
||||
setTimeout(() => {
|
||||
forloopTutorial.moveNext()
|
||||
})
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
{
|
||||
element: 'arg-input-add-item',
|
||||
popover: {
|
||||
title: 'Flow input',
|
||||
description: 'Let’s add an item to our array',
|
||||
onNextClick: () => {
|
||||
clickButtonBySelector('#arg-input-add-item')
|
||||
|
||||
setTimeout(() => {
|
||||
forloopTutorial.moveNext()
|
||||
})
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
element: 'arg-input-add-item',
|
||||
popover: {
|
||||
title: 'Flow input',
|
||||
description: 'We can set the value of the item',
|
||||
onNextClick: () => {
|
||||
setInputBySelector('#arg-input-number-array', '25')
|
||||
|
||||
setTimeout(() => {
|
||||
forloopTutorial.moveNext()
|
||||
})
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
{
|
||||
element: '#flow-editor-test-flow-drawer',
|
||||
popover: {
|
||||
title: 'Test your flow',
|
||||
description: 'Finally we can test our flow, and view the results!',
|
||||
onNextClick: () => {
|
||||
clickButtonBySelector('#flow-editor-test-flow-drawer')
|
||||
|
||||
setTimeout(() => {
|
||||
forloopTutorial.moveNext()
|
||||
updateProgress(1)
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
})
|
||||
forloopTutorial.drive()
|
||||
tutorial?.runTutorial()
|
||||
}
|
||||
</script>
|
||||
|
||||
<Tutorial
|
||||
bind:this={tutorial}
|
||||
index={1}
|
||||
name="forloop"
|
||||
on:error
|
||||
on:skipAll
|
||||
getSteps={(driver) => [
|
||||
{
|
||||
popover: {
|
||||
title: 'Welcome to the Windmil Flow editor',
|
||||
description:
|
||||
'Learn how to build our first for loop to iterate on. You can use arrow keys to navigate.'
|
||||
}
|
||||
},
|
||||
|
||||
{
|
||||
popover: {
|
||||
title: 'Flows inputs',
|
||||
description: 'Flows have inputs that can be used in the flow',
|
||||
onNextClick: () => {
|
||||
clickButtonBySelector('#flow-editor-virtual-Input')
|
||||
|
||||
setTimeout(() => {
|
||||
driver.moveNext()
|
||||
})
|
||||
}
|
||||
},
|
||||
element: '#svelvet-Input'
|
||||
},
|
||||
{
|
||||
element: '#flow-editor-add-property',
|
||||
popover: {
|
||||
title: 'Add a property',
|
||||
description: 'Click here to add a property to your schema',
|
||||
onNextClick: () => {
|
||||
clickButtonBySelector('#flow-editor-add-property')
|
||||
|
||||
setTimeout(() => {
|
||||
driver.moveNext()
|
||||
})
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
element: '#schema-modal-name',
|
||||
popover: {
|
||||
title: 'Name your property',
|
||||
description: 'Give a name to your property. Here we will call it firstname',
|
||||
onNextClick: () => {
|
||||
setInputBySelector('#schema-modal-name', 'array')
|
||||
driver.moveNext()
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
element: '#schema-modal-type-array',
|
||||
popover: {
|
||||
title: 'Property type',
|
||||
description: 'Choose the type of your property. Here we will choose array',
|
||||
onNextClick: () => {
|
||||
clickButtonBySelector('#schema-modal-type-array')
|
||||
driver.moveNext()
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
element: '#array-type-narrowing',
|
||||
popover: {
|
||||
title: 'Array type narrowing',
|
||||
description: 'You can narrow the type of your array. Here we will choose numbers',
|
||||
onNextClick: () => {
|
||||
selectOptionsBySelector('#array-type-narrowing', 'number')
|
||||
driver.moveNext()
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
element: '#schema-modal-save',
|
||||
popover: {
|
||||
title: 'Save your property',
|
||||
description: 'Click here to save your property',
|
||||
onNextClick: () => {
|
||||
clickButtonBySelector('#schema-modal-save')
|
||||
|
||||
setTimeout(() => {
|
||||
driver.moveNext()
|
||||
})
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
element: '#flow-editor-add-step-0',
|
||||
popover: {
|
||||
title: 'Add a step',
|
||||
description: 'Click here to add a step to your flow',
|
||||
onNextClick: () => {
|
||||
triggerAddFlowStep(0)
|
||||
|
||||
setTimeout(() => {
|
||||
driver.moveNext()
|
||||
})
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
{
|
||||
popover: {
|
||||
title: 'Steps kind',
|
||||
description: "Choose the kind of step you want to add. Let's start with a simple action"
|
||||
},
|
||||
element: '#flow-editor-insert-module'
|
||||
},
|
||||
|
||||
{
|
||||
popover: {
|
||||
title: 'Insert loop',
|
||||
description: "Let's pick forloop",
|
||||
onNextClick: () => {
|
||||
selectFlowStepKind(4)
|
||||
setTimeout(() => {
|
||||
driver.moveNext()
|
||||
})
|
||||
}
|
||||
},
|
||||
element: '#flow-editor-insert-module > div > button:nth-child(4)'
|
||||
},
|
||||
|
||||
{
|
||||
element: '#flow-editor-iterator-expression',
|
||||
popover: {
|
||||
title: 'Iterator expression',
|
||||
description:
|
||||
'The iterator expression is a javascript expression that respresents the array to iterate on. Here we will iterate on the firstname input letter by letter',
|
||||
onNextClick: () => {
|
||||
if ($flowStore.value.modules[0].value.type === 'forloopflow') {
|
||||
if ($flowStore.value.modules[0].value.iterator.type === 'javascript') {
|
||||
$flowStore.value.modules[0].value.iterator.expr = 'flow_input.array'
|
||||
}
|
||||
}
|
||||
|
||||
$flowStore = $flowStore
|
||||
|
||||
dispatch('reload')
|
||||
|
||||
setTimeout(() => {
|
||||
driver.moveNext()
|
||||
})
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
{
|
||||
element: '#flow-editor-iterator-expression',
|
||||
popover: {
|
||||
title: 'Iterator expression',
|
||||
description:
|
||||
'We can refer to the result of previous steps using the results object: results.a',
|
||||
onNextClick: () => {
|
||||
if ($flowStore.value.modules[0].value.type === 'forloopflow') {
|
||||
$flowStore.value.modules[0].value.modules = [
|
||||
{
|
||||
id: 'b',
|
||||
value: {
|
||||
type: 'rawscript',
|
||||
content: 'def main(x: int):\n return x*2',
|
||||
// @ts-ignore
|
||||
language: 'python3',
|
||||
input_transforms: {
|
||||
x: {
|
||||
type: 'javascript',
|
||||
// @ts-ignore
|
||||
value: '',
|
||||
expr: ''
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
$flowStateStore['b'] = emptyFlowModuleState()
|
||||
|
||||
$flowStateStore['b'].schema.properties = {
|
||||
x: {
|
||||
type: 'number',
|
||||
description: '',
|
||||
default: null
|
||||
}
|
||||
}
|
||||
|
||||
$flowStore = $flowStore
|
||||
|
||||
dispatch('reload')
|
||||
|
||||
setTimeout(() => {
|
||||
driver.moveNext()
|
||||
})
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
element: '#svelvet-c',
|
||||
popover: {
|
||||
title: 'Step of the loop',
|
||||
description: 'We added an action to the loop. Let’s configure it',
|
||||
onNextClick: () => {
|
||||
$selectedId = 'b'
|
||||
$flowStore = $flowStore
|
||||
|
||||
dispatch('reload')
|
||||
setTimeout(() => {
|
||||
driver.moveNext()
|
||||
})
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
element: '#flow-editor-editor',
|
||||
popover: {
|
||||
title: 'Python step',
|
||||
description:
|
||||
'We can write python code in the editor. In this example we will capitalize the letter'
|
||||
}
|
||||
},
|
||||
|
||||
{
|
||||
element: '#flow-editor-step-input',
|
||||
popover: {
|
||||
title: 'flow editor',
|
||||
description: 'Description'
|
||||
}
|
||||
},
|
||||
|
||||
{
|
||||
element: '#flow-editor-plug',
|
||||
popover: {
|
||||
title: 'Input configuration',
|
||||
description:
|
||||
'UI is autogenerated from your code. Let’s connect the input to the letter input',
|
||||
onNextClick: () => {
|
||||
clickButtonBySelector('#flow-editor-plug')
|
||||
|
||||
setTimeout(() => {
|
||||
driver.moveNext()
|
||||
})
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
element: '.key',
|
||||
popover: {
|
||||
title: 'Connect',
|
||||
description: 'As we did before, we can connect to the iterator of the loop',
|
||||
onNextClick: () => {
|
||||
if (
|
||||
$flowStore.value.modules[0].value.type === 'forloopflow' &&
|
||||
$flowStore.value.modules[0].value.modules[0].value.type === 'rawscript'
|
||||
) {
|
||||
$flowStore.value.modules[0].value.modules[0].value.input_transforms = {
|
||||
x: {
|
||||
type: 'javascript',
|
||||
expr: 'flow_input.iter.value'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$flowStore = $flowStore
|
||||
dispatch('reload')
|
||||
|
||||
setTimeout(() => {
|
||||
driver.moveNext()
|
||||
})
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
element: '#flow-editor-step-input',
|
||||
popover: {
|
||||
title: 'Iterator',
|
||||
description:
|
||||
'Loops expose an iterator object that contains the current value of the loop and the index'
|
||||
}
|
||||
},
|
||||
{
|
||||
element: '#flow-editor-test-flow',
|
||||
popover: {
|
||||
title: 'Test your flow',
|
||||
description: 'We can now test our flow',
|
||||
onNextClick: () => {
|
||||
clickButtonBySelector('#flow-editor-test-flow')
|
||||
|
||||
setTimeout(() => {
|
||||
driver.moveNext()
|
||||
})
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
{
|
||||
element: 'arg-input-add-item',
|
||||
popover: {
|
||||
title: 'Flow input',
|
||||
description: 'Let’s add an item to our array',
|
||||
onNextClick: () => {
|
||||
clickButtonBySelector('#arg-input-add-item')
|
||||
|
||||
setTimeout(() => {
|
||||
driver.moveNext()
|
||||
})
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
element: 'arg-input-add-item',
|
||||
popover: {
|
||||
title: 'Flow input',
|
||||
description: 'We can set the value of the item',
|
||||
onNextClick: () => {
|
||||
setInputBySelector('#arg-input-number-array', '25')
|
||||
|
||||
setTimeout(() => {
|
||||
driver.moveNext()
|
||||
})
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
{
|
||||
element: '#flow-editor-test-flow-drawer',
|
||||
popover: {
|
||||
title: 'Test your flow',
|
||||
description: 'Finally we can test our flow, and view the results!',
|
||||
onNextClick: () => {
|
||||
clickButtonBySelector('#flow-editor-test-flow-drawer')
|
||||
setTimeout(() => {
|
||||
driver.moveNext()
|
||||
updateProgress(1)
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
]}
|
||||
/>
|
||||
|
||||
@@ -0,0 +1,75 @@
|
||||
<script lang="ts">
|
||||
import { driver, type Driver, type DriveStep } from 'driver.js'
|
||||
import 'driver.js/dist/driver.css'
|
||||
import { createEventDispatcher, getContext } from 'svelte'
|
||||
import type { FlowEditorContext } from '../flows/types'
|
||||
import { tainted } from './utils'
|
||||
import { updateProgress } from '$lib/tutorialUtils'
|
||||
import { ignoredTutorials } from './ignoredTutorials'
|
||||
|
||||
export let index: number = 0
|
||||
export let name: string = 'action'
|
||||
|
||||
export let getSteps: (driver: Driver) => DriveStep[] = () => []
|
||||
|
||||
const { flowStore } = getContext<FlowEditorContext>('FlowEditorContext')
|
||||
const dispatch = createEventDispatcher()
|
||||
|
||||
function createTutorialButton(text: string, onClick: () => void) {
|
||||
const button = document.createElement('button')
|
||||
button.innerHTML = text
|
||||
button.addEventListener('click', onClick)
|
||||
button.setAttribute(
|
||||
'class',
|
||||
'border px-2 py-1 !text-xs font-normal rounded-md hover:bg-surface-hover transition-all flex items-center'
|
||||
)
|
||||
return button
|
||||
}
|
||||
|
||||
export const runTutorial = () => {
|
||||
if (tainted($flowStore)) {
|
||||
dispatch('error', { detail: name })
|
||||
return
|
||||
}
|
||||
|
||||
const tutorial = driver({
|
||||
showProgress: true,
|
||||
allowClose: true,
|
||||
onPopoverRender: (popover, { config, state }) => {
|
||||
if (state.activeIndex == 0) {
|
||||
const skipThisButton = createTutorialButton('Mark this tutorial as completed', () => {
|
||||
updateProgress(index)
|
||||
tutorial.destroy()
|
||||
})
|
||||
|
||||
const skipAllButton = createTutorialButton(
|
||||
'Mark <b>all</b> tutorials as completed',
|
||||
() => {
|
||||
dispatch('skipAll')
|
||||
tutorial.destroy()
|
||||
}
|
||||
)
|
||||
|
||||
const popoverDescription = document.querySelector('#driver-popover-description')
|
||||
const div = document.createElement('div')
|
||||
|
||||
div.setAttribute('class', 'flex flex-row gap-2 justify-between w-full pt-2')
|
||||
|
||||
if (popoverDescription) {
|
||||
div.appendChild(skipAllButton)
|
||||
div.appendChild(skipThisButton)
|
||||
popoverDescription.appendChild(div)
|
||||
}
|
||||
}
|
||||
},
|
||||
onDestroyed: () => {
|
||||
if (!tutorial.hasNextStep()) {
|
||||
$ignoredTutorials = Array.from(new Set([...$ignoredTutorials, index]))
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
tutorial.setSteps(getSteps(tutorial))
|
||||
tutorial.drive()
|
||||
}
|
||||
</script>
|
||||
@@ -7,24 +7,19 @@
|
||||
|
||||
export let label: string
|
||||
export let index: number
|
||||
export let shouldRenderButton: boolean = true
|
||||
export let id: string
|
||||
</script>
|
||||
|
||||
{#if shouldRenderButton}
|
||||
<MenuItem on:click>
|
||||
<div
|
||||
{id}
|
||||
class={classNames(
|
||||
'text-primary flex flex-row items-center text-left px-4 py-2 gap-2 cursor-pointer hover:bg-surface-hover !text-xs font-semibold'
|
||||
)}
|
||||
>
|
||||
{#if $tutorialsToDo.includes(index)}
|
||||
<Circle size={16} />
|
||||
{:else}
|
||||
<CheckCircle size={16} color="green" />
|
||||
{/if}
|
||||
{label}
|
||||
</div>
|
||||
</MenuItem>
|
||||
{/if}
|
||||
<MenuItem on:click>
|
||||
<div
|
||||
class={classNames(
|
||||
'text-primary flex flex-row items-center text-left px-4 py-2 gap-2 cursor-pointer hover:bg-surface-hover !text-xs font-semibold'
|
||||
)}
|
||||
>
|
||||
{#if $tutorialsToDo.includes(index)}
|
||||
<Circle size={16} />
|
||||
{:else}
|
||||
<CheckCircle size={16} color="green" />
|
||||
{/if}
|
||||
{label}
|
||||
</div>
|
||||
</MenuItem>
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
import { writable } from 'svelte/store'
|
||||
|
||||
export const ignoredTutorials = writable<number[]>([])
|
||||
Reference in New Issue
Block a user