Tutorials improvement (#2384)

* fix(frontend): fix tutorials

* fix(frontend): simplify

* fix(frontend): simplify

* fix(frontend): fix skip all

* fix(frontend): fix toggle

* fix(frontend): simplify
This commit is contained in:
Faton Ramadani
2023-10-04 14:28:37 +02:00
committed by GitHub
parent 0cb70d975b
commit 65bacdcd7d
12 changed files with 251 additions and 152 deletions
+29 -1
View File
@@ -14,6 +14,7 @@
copilotInfo,
enterpriseLicense,
hubScripts,
tutorialsToDo,
userStore,
workspaceStore
} from '$lib/stores'
@@ -56,6 +57,9 @@
import FlowCopilotInputsModal from './copilot/FlowCopilotInputsModal.svelte'
import FlowBuilderTutorials from './FlowBuilderTutorials.svelte'
import FlowTutorials from './FlowTutorials.svelte'
import { tainted } from './tutorials/utils'
export let initialPath: string = ''
export let selectedId: string | undefined
export let initialArgs: Record<string, any> = {}
@@ -847,6 +851,18 @@
$: $copilotCurrentStepStore === undefined && blurCopilot()
let renderCount = 0
let flowTutorials: FlowTutorials | undefined = undefined
$: if (!tainted($flowStore) && loading === false) {
const urlParams = new URLSearchParams(window.location.search)
const tutorial = urlParams.get('tutorial')
if (tutorial) {
flowTutorials?.runTutorialById(tutorial)
} else if ($tutorialsToDo.includes(0)) {
flowTutorials?.runTutorialById('action')
}
}
</script>
<svelte:window on:keydown={onKeyDown} />
@@ -983,7 +999,12 @@
<!-- metadata -->
{#if $flowStateStore}
<FlowEditor {loading} />
<FlowEditor
{loading}
on:reload={() => {
renderCount += 1
}}
/>
{:else}
<CenteredPage>Loading...</CenteredPage>
{/if}
@@ -992,3 +1013,10 @@
Flow Builder not available to operators
{/if}
{/key}
<FlowTutorials
bind:this={flowTutorials}
on:reload={() => {
renderCount += 1
}}
/>
@@ -3,81 +3,110 @@
import ButtonDropdown from './common/button/ButtonDropdown.svelte'
import Button from './common/button/Button.svelte'
import FlowBuilderTutorialSimpleFlow from './tutorials/FlowBuilderTutorialSimpleFlow.svelte'
import FlowBuilderTutorialsForLoop from './tutorials/FlowBuilderTutorialsForLoop.svelte'
import FlowBranchOne from './tutorials/FlowBranchOne.svelte'
import FlowBranchAll from './tutorials/FlowBranchAll.svelte'
import MenuItem from './common/menu/MenuItem.svelte'
import { classNames } from '$lib/utils'
import { resetAllTodos, skipAllTodos } from '$lib/tutorialUtils'
import { tutorialsToDo } from '$lib/stores'
import { getContext } from 'svelte'
import type { FlowEditorContext } from './flows/types'
import ConfirmationModal from './common/confirmationModal/ConfirmationModal.svelte'
import TutorialItem from './tutorials/TutorialItem.svelte'
import FlowTutorials from './FlowTutorials.svelte'
const { flowStore } = getContext<FlowEditorContext>('FlowEditorContext')
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()
}
let targetTutorial: string | undefined = undefined
let flowTutorials: FlowTutorials | undefined = undefined
</script>
{#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()
}}
>
<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}
<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">
<TutorialItem
on:click={() => flowTutorials?.runTutorialById('action')}
label="Simple flow tutorial"
index={0}
id="flow-builder-tutorial-action"
/>
<FlowBuilderTutorialSimpleFlow
bind:this={flowBuilderTutorialSimpleFlow}
shouldRenderButton={false}
<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" />
<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'
)}
>
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>
<FlowTutorials
bind:this={flowTutorials}
on:error={({ detail }) => {
targetTutorial = detail.detail
}}
on:skipAll={() => {
skipAllTodos()
}}
on:reload
/>
<ConfirmationModal
open={targetTutorial !== undefined}
title="Tutorial error"
confirmationText="Open new tab"
on:canceled={() => {
targetTutorial = undefined
}}
on:confirmed={async () => {
window.open(`/flows/add?tutorial=${targetTutorial}&nodraft=true`, '_blank')
}}
>
<div class="flex flex-col w-full space-y-4">
<span> You need to create a new flow before starting the tutorial.</span>
</div>
</ConfirmationModal>
<style global>
.driver-popover-title {
@apply leading-6 text-primary text-base;
@@ -0,0 +1,44 @@
<script lang="ts">
import { skipAllTodos } from '$lib/tutorialUtils'
import FlowBranchAll from './tutorials/FlowBranchAll.svelte'
import FlowBranchOne from './tutorials/FlowBranchOne.svelte'
import FlowBuilderTutorialSimpleFlow from './tutorials/FlowBuilderTutorialSimpleFlow.svelte'
import FlowBuilderTutorialsForLoop from './tutorials/FlowBuilderTutorialsForLoop.svelte'
let flowBuilderTutorialSimpleFlow: FlowBuilderTutorialSimpleFlow | undefined = undefined
let flowBuilderTutorialsForLoop: FlowBuilderTutorialsForLoop | undefined = undefined
let flowBranchOne: FlowBranchOne | undefined = undefined
let flowBranchAll: FlowBranchAll | undefined = undefined
export function runTutorialById(id: string) {
console.log(id, flowBranchOne)
if (id === 'forloop') {
flowBuilderTutorialsForLoop?.runTutorial()
} else if (id === 'branchone') {
flowBranchOne?.runTutorial()
} else if (id === 'branchall') {
flowBranchAll?.runTutorial()
} else if (id === 'action') {
flowBuilderTutorialSimpleFlow?.runTutorial()
}
}
function skipAll() {
skipAllTodos()
}
</script>
<FlowBuilderTutorialSimpleFlow
bind:this={flowBuilderTutorialSimpleFlow}
on:error
on:skipAll={skipAll}
on:reload
/>
<FlowBuilderTutorialsForLoop
bind:this={flowBuilderTutorialsForLoop}
on:error
on:skipAll={skipAll}
on:reload
/>
<FlowBranchOne bind:this={flowBranchOne} on:error on:skipAll={skipAll} on:reload />
<FlowBranchAll bind:this={flowBranchAll} on:error on:skipAll={skipAll} on:reload />
@@ -35,7 +35,7 @@
{/each}
</div>
{:else if $flowStore.value.modules}
<FlowModuleSchemaMap bind:modules={$flowStore.value.modules} />
<FlowModuleSchemaMap bind:modules={$flowStore.value.modules} on:reload />
{/if}
</div>
</Pane>
@@ -209,7 +209,7 @@
</Slider>
</div>
</TabContent>
<TabContent value="settings-schedule" class="p-4">
<TabContent value="settings-schedule" class="p-4 h-full overflow-scroll">
<Alert
type="info"
title="Primary Schedule"
@@ -23,16 +23,14 @@
import type { FlowCopilotContext } from '$lib/components/copilot/flow'
import { fade } from 'svelte/transition'
import { tutorialsToDo } from '$lib/stores'
import FlowBuilderTutorialsForLoop from '$lib/components/tutorials/FlowBuilderTutorialsForLoop.svelte'
import FlowBranchOne from '$lib/components/tutorials/FlowBranchOne.svelte'
import FlowBranchAll from '$lib/components/tutorials/FlowBranchAll.svelte'
import FlowTutorials from '$lib/components/FlowTutorials.svelte'
import { tainted } from '$lib/components/tutorials/utils'
export let modules: FlowModule[] | undefined
export let sidebarSize: number | undefined = undefined
let flowBuilderTutorialsForLoop: FlowBuilderTutorialsForLoop | undefined = undefined
let flowBranchOne: FlowBranchOne | undefined = undefined
let flowBranchAll: FlowBranchAll | undefined = undefined
let flowTutorials: FlowTutorials | undefined = undefined
const { selectedId, moving, history, flowStateStore, flowStore } =
getContext<FlowEditorContext>('FlowEditorContext')
@@ -214,12 +212,29 @@
}
}}
on:insert={async ({ detail }) => {
if ($tutorialsToDo.includes(1) && detail.detail == 'forloop') {
flowBuilderTutorialsForLoop?.runTutorial()
} else if ($tutorialsToDo.includes(2) && detail.detail == 'branchone') {
flowBranchOne?.runTutorial()
} else if ($tutorialsToDo.includes(3) && detail.detail == 'branchall') {
flowBranchAll?.runTutorial()
const svg = document.getElementsByClassName('driver-overlay driver-overlay-animated')
const isTainted = tainted($flowStore)
if (
$tutorialsToDo.includes(1) &&
detail.detail == 'forloop' &&
svg.length === 0 &&
!isTainted
) {
flowTutorials?.runTutorialById('forloop')
} else if (
$tutorialsToDo.includes(2) &&
detail.detail == 'branchone' &&
svg.length === 0 &&
!isTainted
) {
flowTutorials?.runTutorialById('branchone')
} else if (
$tutorialsToDo.includes(3) &&
detail.detail == 'branchall' &&
svg.length === 0 &&
!isTainted
) {
flowTutorials?.runTutorialById('branchall')
} else {
if (detail.modules) {
await tick()
@@ -270,6 +285,4 @@
</div>
</div>
<FlowBuilderTutorialsForLoop bind:this={flowBuilderTutorialsForLoop} shouldRenderButton={false} />
<FlowBranchOne bind:this={flowBranchOne} shouldRenderButton={false} />
<FlowBranchAll bind:this={flowBranchAll} shouldRenderButton={false} />
<FlowTutorials bind:this={flowTutorials} on:reload />
@@ -3,18 +3,20 @@
import 'driver.js/dist/driver.css'
import { createEventDispatcher, getContext } from 'svelte'
import type { FlowEditorContext } from '../flows/types'
import TutorialItem from './TutorialItem.svelte'
import { clickButtonBySelector, triggerAddFlowStep, selectFlowStepKind } from './utils'
import { clickButtonBySelector, triggerAddFlowStep, selectFlowStepKind, tainted } from './utils'
import { updateProgress } from '$lib/tutorialUtils'
import { RawScript } from '$lib/gen'
export let shouldRenderButton: boolean = true
const { flowStore } = getContext<FlowEditorContext>('FlowEditorContext')
const dispatch = createEventDispatcher()
export function runTutorial() {
if (tainted($flowStore)) {
dispatch('error', { detail: 'branchall' })
return
}
const branchAllTutorial = driver({
showProgress: true,
allowClose: true,
@@ -96,6 +98,7 @@
description: "Let's pick branch all",
onNextClick: () => {
selectFlowStepKind(6)
setTimeout(() => {
branchAllTutorial.moveNext()
})
@@ -216,12 +219,3 @@
branchAllTutorial.drive()
}
</script>
{#if shouldRenderButton}
<TutorialItem
on:click={() => runTutorial()}
label="Branch all tutorial"
index={3}
id="flow-builder-tutorial-branchall"
/>
{/if}
@@ -3,22 +3,25 @@
import 'driver.js/dist/driver.css'
import { createEventDispatcher, getContext } from 'svelte'
import type { FlowEditorContext } from '../flows/types'
import TutorialItem from './TutorialItem.svelte'
import {
clickButtonBySelector,
setInputBySelector,
triggerAddFlowStep,
selectFlowStepKind
selectFlowStepKind,
tainted
} from './utils'
import { updateProgress } from '$lib/tutorialUtils'
import { RawScript } from '$lib/gen'
export let shouldRenderButton: boolean = true
const { flowStore } = getContext<FlowEditorContext>('FlowEditorContext')
const dispatch = createEventDispatcher()
export function runTutorial() {
if (tainted($flowStore)) {
dispatch('error', { detail: 'branchone' })
return
}
const branchOneTutorial = driver({
showProgress: true,
allowClose: true,
@@ -308,12 +311,3 @@
branchOneTutorial.drive()
}
</script>
{#if shouldRenderButton}
<TutorialItem
on:click={() => runTutorial()}
label="Branch one tutorial"
index={2}
id="flow-builder-tutorial-branchone"
/>
{/if}
@@ -1,25 +1,28 @@
<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 { updateProgress } from '$lib/tutorialUtils'
import TutorialItem from './TutorialItem.svelte'
import {
clickButtonBySelector,
selectFlowStepKind,
setInputBySelector,
tainted,
triggerAddFlowStep
} from './utils'
export let shouldRenderButton: boolean = true
const { flowStore } = getContext<FlowEditorContext>('FlowEditorContext')
const dispatch = createEventDispatcher()
const queue: string[] = []
export function runTutorial() {
if (tainted($flowStore)) {
dispatch('error', { detail: 'action' })
return
}
const simpleFlowTutorial = driver({
showProgress: true,
allowClose: true,
@@ -247,7 +250,7 @@
$flowStore = $flowStore
dispatch('reload')
tick().then(() => {
setTimeout(() => {
simpleFlowTutorial.moveNext()
})
}
@@ -312,12 +315,3 @@
simpleFlowTutorial.drive()
}
</script>
{#if shouldRenderButton}
<TutorialItem
on:click={() => runTutorial()}
label="Simple flow tutorial"
index={0}
id="flow-builder-tutorial-action"
/>
{/if}
@@ -3,25 +3,28 @@
import 'driver.js/dist/driver.css'
import { createEventDispatcher, getContext } from 'svelte'
import type { FlowEditorContext } from '../flows/types'
import TutorialItem from './TutorialItem.svelte'
import { emptyFlowModuleState } from '../flows/utils'
import {
clickButtonBySelector,
setInputBySelector,
triggerAddFlowStep,
selectFlowStepKind,
selectOptionsBySelector
selectOptionsBySelector,
tainted
} from './utils'
import { updateProgress } from '$lib/tutorialUtils'
export let shouldRenderButton: boolean = true
const { flowStore, selectedId, flowStateStore } =
getContext<FlowEditorContext>('FlowEditorContext')
const dispatch = createEventDispatcher()
export function runTutorial() {
if (tainted($flowStore)) {
dispatch('error', { detail: 'forloop' })
return
}
const forloopTutorial = driver({
showProgress: true,
allowClose: true,
@@ -405,12 +408,3 @@
forloopTutorial.drive()
}
</script>
{#if shouldRenderButton}
<TutorialItem
on:click={() => runTutorial()}
label="For loops tutorial"
index={1}
id="flow-builder-tutorial-forloops"
/>
{/if}
@@ -7,21 +7,24 @@
export let label: string
export let index: number
export let shouldRenderButton: boolean = true
export let id: string
</script>
<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 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}
@@ -1,3 +1,5 @@
import type { Flow } from '$lib/gen'
export function setInputBySelector(selector: string, value: string) {
const input = document.querySelector(selector) as HTMLInputElement
@@ -41,3 +43,7 @@ export function selectOptionsBySelector(selector: string, value: string) {
select.dispatchEvent(new Event('change', { bubbles: true }))
}
}
export function tainted(flow: Flow) {
return flow.value.modules.length > 0 || Object.keys(flow?.schema?.properties).length > 0
}