fix(frontend): fix flow tutorials (#2383)

* fix(frontend): fix flow tutorials

* fix(frontend): restore tainted

* fix(frontend): add guide about key arrows

* fix(frontend): add how first tutorial is started

* fix(frontend): add how first tutorial is started

* fix(frontend): remove debugger

* fix(frontend): fix deployement issues
This commit is contained in:
Faton Ramadani
2023-10-04 11:12:28 +02:00
committed by GitHub
parent 33b1c752ae
commit 0cb70d975b
5 changed files with 366 additions and 342 deletions
@@ -11,58 +11,72 @@
import { classNames } from '$lib/utils'
import { resetAllTodos, skipAllTodos } from '$lib/tutorialUtils'
import { tutorialsToDo } from '$lib/stores'
import { clickButtonBySelector } from './tutorials/utils'
import { getContext } from 'svelte'
import type { FlowEditorContext } from './flows/types'
let buttonDropdown: ButtonDropdown | undefined = undefined
const { flowStore } = getContext<FlowEditorContext>('FlowEditorContext')
$: {
if (buttonDropdown && $tutorialsToDo.includes(0)) {
clickButtonBySelector('#tutorials-button')
}
let flowBuilderTutorialSimpleFlow: FlowBuilderTutorialSimpleFlow | undefined = undefined
$: tainted =
$flowStore.value.modules.length > 0 || Object.keys($flowStore?.schema?.properties).length > 0
let automaticallyRan: boolean = false
$: if (!automaticallyRan && $tutorialsToDo.includes(0)) {
automaticallyRan = true
flowBuilderTutorialSimpleFlow?.runTutorial()
}
</script>
<button on:pointerdown|stopPropagation>
<ButtonDropdown hasPadding={false} bind:this={buttonDropdown}>
<svelte:fragment slot="buttonReplacement">
<Button nonCaptureEvent size="xs" color="light" variant="border" id="tutorials-button">
<div class="flex flex-row gap-2 items-center">
<BookOpen size={16} />
Tutorials
</div>
</Button>
</svelte:fragment>
<svelte:fragment slot="items">
<FlowBuilderTutorialSimpleFlow on:reload on:skipAll={skipAllTodos} />
<FlowBuilderTutorialsForLoop on:reload on:skipAll={skipAllTodos} />
<FlowBranchOne on:reload on:skipAll={skipAllTodos} />
<FlowBranchAll on:reload on:skipAll={skipAllTodos} />
<div class="border-t border-surface-hover" />
<MenuItem
on:click={() => {
resetAllTodos()
}}
>
<div
class={classNames(
'text-primary flex flex-row items-center text-left gap-2 cursor-pointer hover:bg-surface-hover !text-xs font-semibold'
)}
{#if !tainted}
<button on:pointerdown|stopPropagation>
<ButtonDropdown hasPadding={false}>
<svelte:fragment slot="buttonReplacement">
<Button nonCaptureEvent size="xs" color="light" variant="border" id="tutorials-button">
<div class="flex flex-row gap-2 items-center">
<BookOpen size={16} />
Tutorials
</div>
</Button>
</svelte:fragment>
<svelte:fragment slot="items">
<FlowBuilderTutorialSimpleFlow on:reload on:skipAll={skipAllTodos} />
<FlowBuilderTutorialsForLoop on:reload on:skipAll={skipAllTodos} />
<FlowBranchOne on:reload on:skipAll={skipAllTodos} />
<FlowBranchAll on:reload on:skipAll={skipAllTodos} />
<div class="border-t border-surface-hover" />
<MenuItem
on:click={() => {
resetAllTodos()
}}
>
Reset tutorials
</div>
</MenuItem>
<MenuItem on:click={() => skipAllTodos()}>
<div
class={classNames(
'text-primary flex flex-row items-center text-left gap-2 cursor-pointer hover:bg-surface-hover !text-xs font-semibold'
)}
>
Skip tutorials
</div>
</MenuItem>
</svelte:fragment>
</ButtonDropdown>
</button>
<div
class={classNames(
'text-primary flex flex-row items-center text-left gap-2 cursor-pointer hover:bg-surface-hover !text-xs font-semibold'
)}
>
Reset tutorials
</div>
</MenuItem>
<MenuItem on:click={() => skipAllTodos()}>
<div
class={classNames(
'text-primary flex flex-row items-center text-left gap-2 cursor-pointer hover:bg-surface-hover !text-xs font-semibold'
)}
>
Skip tutorials
</div>
</MenuItem>
</svelte:fragment>
</ButtonDropdown>
</button>
{/if}
<FlowBuilderTutorialSimpleFlow
bind:this={flowBuilderTutorialSimpleFlow}
shouldRenderButton={false}
/>
<style global>
.driver-popover-title {
@@ -74,6 +88,6 @@
}
.driver-popover {
@apply p-6;
@apply p-6 bg-surface;
}
</style>
@@ -1,7 +1,7 @@
<script lang="ts">
import { driver } from 'driver.js'
import 'driver.js/dist/driver.css'
import { createEventDispatcher, getContext, tick } from 'svelte'
import { createEventDispatcher, getContext } from 'svelte'
import type { FlowEditorContext } from '../flows/types'
import TutorialItem from './TutorialItem.svelte'
import { clickButtonBySelector, triggerAddFlowStep, selectFlowStepKind } from './utils'
@@ -21,7 +21,7 @@
onPopoverRender: (popover, { config, state }) => {
if (state.activeIndex == 0) {
const skipThisButton = document.createElement('button')
skipThisButton.innerText = 'Skip this'
skipThisButton.innerText = 'Skip this tutorial'
skipThisButton.addEventListener('click', () => {
updateProgress(3)
@@ -33,7 +33,7 @@
)
const skipAllButton = document.createElement('button')
skipAllButton.innerText = 'Skip all'
skipAllButton.innerText = 'Skip all tutorials'
skipAllButton.addEventListener('click', () => {
dispatch('skipAll')
branchAllTutorial.destroy()
@@ -48,12 +48,13 @@
const div = document.createElement('div')
div.setAttribute('class', 'flex flex-row gap-2')
div.setAttribute('class', 'flex flex-row gap-2 justify-between w-full pt-2')
if (popoverDescription) {
div.appendChild(skipThisButton)
div.appendChild(skipAllButton)
div.appendChild(skipThisButton)
popoverDescription.appendChild(div)
}
}
@@ -61,8 +62,9 @@
steps: [
{
popover: {
title: 'Welcome in the Windmil Flow editor',
description: 'Learn how to build our first branch to be executed on a condition'
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'
}
},
{
@@ -172,7 +174,7 @@
$flowStore = $flowStore
dispatch('reload')
tick().then(() => {
setTimeout(() => {
branchAllTutorial.moveNext()
})
}
@@ -1,7 +1,7 @@
<script lang="ts">
import { driver } from 'driver.js'
import 'driver.js/dist/driver.css'
import { createEventDispatcher, getContext, tick } from 'svelte'
import { createEventDispatcher, getContext } from 'svelte'
import type { FlowEditorContext } from '../flows/types'
import TutorialItem from './TutorialItem.svelte'
import {
@@ -25,7 +25,7 @@
onPopoverRender: (popover, { config, state }) => {
if (state.activeIndex == 0) {
const skipThisButton = document.createElement('button')
skipThisButton.innerText = 'Skip this'
skipThisButton.innerText = 'Skip this tutorials'
skipThisButton.addEventListener('click', () => {
updateProgress(2)
@@ -37,7 +37,7 @@
)
const skipAllButton = document.createElement('button')
skipAllButton.innerText = 'Skip all'
skipAllButton.innerText = 'Skip all tutorials'
skipAllButton.addEventListener('click', () => {
dispatch('skipAll')
branchOneTutorial.destroy()
@@ -52,11 +52,11 @@
const div = document.createElement('div')
div.setAttribute('class', 'flex flex-row gap-2')
div.setAttribute('class', 'flex flex-row gap-2 justify-between w-full pt-2')
if (popoverDescription) {
div.appendChild(skipThisButton)
div.appendChild(skipAllButton)
div.appendChild(skipThisButton)
popoverDescription.appendChild(div)
}
@@ -65,8 +65,9 @@
steps: [
{
popover: {
title: 'Welcome in the Windmil Flow editor',
description: 'Learn how to build our first branch to be executed on a condition'
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'
}
},
@@ -213,7 +214,7 @@
$flowStore = $flowStore
dispatch('reload')
tick().then(() => {
setTimeout(() => {
branchOneTutorial.moveNext()
})
}
@@ -265,7 +266,7 @@
$flowStore = $flowStore
dispatch('reload')
tick().then(() => {
setTimeout(() => {
branchOneTutorial.moveNext()
})
}
@@ -11,307 +11,313 @@
setInputBySelector,
triggerAddFlowStep
} from './utils'
import { tutorialsToDo } from '$lib/stores'
export let shouldRenderButton: boolean = true
const { flowStore } = getContext<FlowEditorContext>('FlowEditorContext')
const dispatch = createEventDispatcher()
const queue: string[] = []
const simpleFlowTutorial = driver({
showProgress: true,
allowClose: true,
onPopoverRender: (popover, { config, state }) => {
if (state.activeIndex == 0) {
const skipThisButton = document.createElement('button')
skipThisButton.innerText = 'Skip this'
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'
)
export function runTutorial() {
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'
skipAllButton.addEventListener('click', () => {
dispatch('skipAll')
simpleFlowTutorial.destroy()
})
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'
)
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 popoverDescription = document.querySelector('#driver-popover-description')
const div = document.createElement('div')
const div = document.createElement('div')
div.setAttribute('class', 'flex flex-row gap-2')
div.setAttribute('class', 'flex flex-row gap-2 justify-between w-full pt-2')
if (popoverDescription) {
div.appendChild(skipThisButton)
div.appendChild(skipAllButton)
if (popoverDescription) {
div.appendChild(skipAllButton)
div.appendChild(skipThisButton)
popoverDescription.appendChild(div)
}
}
},
steps: [
{
popover: {
title: 'Welcome in the Windmil Flow editor',
description: 'Learn how to build powerful flows in a few steps'
popoverDescription.appendChild(div)
}
}
},
{
popover: {
title: 'Flows inputs',
description: 'Flows have inputs that can be used in the flow',
onNextClick: () => {
clickButtonBySelector('#flow-editor-virtual-Input')
setTimeout(() => {
simpleFlowTutorial.moveNext()
})
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.'
}
},
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()
})
{
popover: {
title: 'Flows inputs',
description: 'Flows have inputs that can be used in the flow',
onNextClick: () => {
clickButtonBySelector('#flow-editor-virtual-Input')
setTimeout(() => {
simpleFlowTutorial.moveNext()
})
}
},
onPrevClick: () => {
simpleFlowTutorial.moveTo(1)
debugger
$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: '#svelvet-Input'
},
element: '#flow-editor-insert-module'
},
{
popover: {
title: 'Pick an action',
description: 'Lets pick an action to add to your flow',
onNextClick: () => {
selectFlowStepKind(1)
{
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()
})
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)')
{
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')
setTimeout(() => {
simpleFlowTutorial.moveNext()
})
}
}
}
},
},
{
element: '#schema-modal-save',
popover: {
title: 'Save your property',
description: 'Click here to save your property',
onNextClick: () => {
queue.push(JSON.stringify($flowStore))
{
element: '#flow-editor-editor',
popover: {
title: 'Action editor',
description: 'Windmill provides a full code editor to write your actions'
}
},
clickButtonBySelector('#schema-modal-save')
{
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()
})
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'
},
{
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: 'Lets 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')
tick().then(() => {
simpleFlowTutorial.moveNext()
})
}
}
},
$flowStore = $flowStore
dispatch('reload')
{
element: '#flow-editor-step-input',
popover: {
title: 'Input connected!',
description: 'The input is now connected to the firstname input'
}
},
tick().then(() => {
simpleFlowTutorial.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(() => {
simpleFlowTutorial.moveNext()
})
}
}
},
{
element: '#flow-preview-content',
popover: {
title: 'Flow input',
description: 'Lets 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)
})
}
}
}
},
{
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: 'Lets 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)
})
}
}
}
]
})
$: $tutorialsToDo.includes(0) && simpleFlowTutorial.drive()
]
})
simpleFlowTutorial.drive()
}
</script>
<TutorialItem
on:click={() => simpleFlowTutorial.drive()}
label="Simple flow tutorial"
index={0}
id="flow-builder-tutorial-action"
/>
{#if shouldRenderButton}
<TutorialItem
on:click={() => runTutorial()}
label="Simple flow tutorial"
index={0}
id="flow-builder-tutorial-action"
/>
{/if}
@@ -1,7 +1,7 @@
<script lang="ts">
import { driver } from 'driver.js'
import 'driver.js/dist/driver.css'
import { createEventDispatcher, getContext, tick } from 'svelte'
import { createEventDispatcher, getContext } from 'svelte'
import type { FlowEditorContext } from '../flows/types'
import TutorialItem from './TutorialItem.svelte'
import { emptyFlowModuleState } from '../flows/utils'
@@ -28,7 +28,7 @@
onPopoverRender: (popover, { config, state }) => {
if (state.activeIndex == 0) {
const skipThisButton = document.createElement('button')
skipThisButton.innerText = 'Skip this'
skipThisButton.innerText = 'Skip this tutorials'
skipThisButton.addEventListener('click', () => {
updateProgress(1)
forloopTutorial.destroy()
@@ -39,7 +39,7 @@
)
const skipAllButton = document.createElement('button')
skipAllButton.innerText = 'Skip all'
skipAllButton.innerText = 'Skip all tutorials'
skipAllButton.addEventListener('click', () => {
dispatch('skipAll')
forloopTutorial.destroy()
@@ -54,11 +54,11 @@
const div = document.createElement('div')
div.setAttribute('class', 'flex flex-row gap-2')
div.setAttribute('class', 'flex flex-row gap-2 justify-between w-full pt-2')
if (popoverDescription) {
div.appendChild(skipThisButton)
div.appendChild(skipAllButton)
div.appendChild(skipThisButton)
popoverDescription.appendChild(div)
}
@@ -67,8 +67,9 @@
steps: [
{
popover: {
title: 'Welcome in the Windmil Flow editor',
description: 'Learn how to build our first for loop to iterate on'
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.'
}
},
@@ -201,7 +202,7 @@
dispatch('reload')
tick().then(() => {
setTimeout(() => {
forloopTutorial.moveNext()
})
}
@@ -251,7 +252,7 @@
dispatch('reload')
tick().then(() => {
setTimeout(() => {
forloopTutorial.moveNext()
})
}
@@ -267,7 +268,7 @@
$flowStore = $flowStore
dispatch('reload')
tick().then(() => {
setTimeout(() => {
forloopTutorial.moveNext()
})
}
@@ -326,7 +327,7 @@
$flowStore = $flowStore
dispatch('reload')
tick().then(() => {
setTimeout(() => {
forloopTutorial.moveNext()
})
}