mirror of
https://github.com/windmill-labs/windmill.git
synced 2026-09-23 00:02:41 +00:00
* Remove $$props.field usage * Rename slots to ensure no hyphen * _props * _trigger * OnSelectedIteration type correct capitalization * rename _content * Remove afterUpdate * Migrate everything to svelte 5 * array bind * Fix popover * type never * nit fixes * Fixed many trivial errors * onClick * Fix errors * use let: * nit typing * fix: wrap state_referenced_locally vars with untrack() Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * Add untrack import * Fix all syntax errors due to untrack migration * Fix undefined errors * Fix more undefined errors * untrack(() => initialOpen) * svelte-ignore * Fix state_descriptors_fixed error in Chart.svelte Use $state.snapshot() to pass plain copies of data/options to Chart.js instead of $state proxies. Chart.js's listenArrayEvents tries to define property descriptors on data arrays, which Svelte 5 proxies reject. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * nit typing * Merge issue * Fix "path is not set" error in resource picker / editor * Fix InputTransformForm error when rerunning some flows * fix npm run check --------- Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
158 lines
3.6 KiB
Svelte
158 lines
3.6 KiB
Svelte
<script lang="ts">
|
|
import { driver, type Driver, type DriveStep } from 'driver.js'
|
|
import { createEventDispatcher, mount } from 'svelte'
|
|
import { updateProgress } from '$lib/tutorialUtils'
|
|
import { ignoredTutorials } from './ignoredTutorials'
|
|
import SkipTutorials from './SkipTutorials.svelte'
|
|
import TutorialControls from './TutorialControls.svelte'
|
|
import TutorialInner from './TutorialInner.svelte'
|
|
import { isCurrentlyInTutorial } from '$lib/stores'
|
|
|
|
|
|
type Options = {
|
|
indexToInsertAt?: number
|
|
skipStepsCount?: number
|
|
}
|
|
|
|
interface Props {
|
|
index?: number;
|
|
name?: string;
|
|
tainted?: boolean;
|
|
onDestroyed?: (() => void) | undefined;
|
|
getSteps?: (driver: Driver, options?: Options | undefined) => DriveStep[];
|
|
}
|
|
|
|
let {
|
|
index = 0,
|
|
name = 'action',
|
|
tainted = false,
|
|
onDestroyed = undefined,
|
|
getSteps = () => []
|
|
}: Props = $props();
|
|
|
|
let totalSteps = 0
|
|
let tutorial: Driver | undefined = $state(undefined)
|
|
const dispatch = createEventDispatcher()
|
|
|
|
// Render controls needs to be exposed so steps that have a custom render can call it
|
|
export function renderControls({ config, state }) {
|
|
const popoverContent = document.querySelector('#driver-popover-content')
|
|
popoverContent?.addEventListener('pointerdown', (event) => {
|
|
event.stopPropagation()
|
|
})
|
|
|
|
const popoverDescription = document.querySelector('#driver-popover-description')
|
|
|
|
if (!tutorial) {
|
|
return
|
|
}
|
|
|
|
if (state.activeIndex == 0) {
|
|
const div = document.createElement('div')
|
|
|
|
mount(SkipTutorials, {
|
|
target: div,
|
|
events: {
|
|
skipAll: () => {
|
|
dispatch('skipAll')
|
|
tutorial?.destroy()
|
|
},
|
|
skipThis: () => {
|
|
updateProgress(index)
|
|
tutorial?.destroy()
|
|
}
|
|
}
|
|
})
|
|
|
|
if (popoverDescription) {
|
|
popoverDescription.appendChild(div)
|
|
}
|
|
}
|
|
|
|
const controls = document.createElement('div')
|
|
|
|
mount(TutorialControls, {
|
|
target: controls,
|
|
props: {
|
|
activeIndex: state.activeIndex,
|
|
totalSteps
|
|
},
|
|
events: {
|
|
next: () => {
|
|
const step = tutorial?.getActiveStep()
|
|
|
|
if (step) {
|
|
if (tutorial?.getActiveStep()?.popover?.onNextClick) {
|
|
const activeElement = tutorial?.getActiveElement()
|
|
tutorial?.getActiveStep()?.popover?.onNextClick?.(activeElement, step, {
|
|
config,
|
|
state,
|
|
driver: tutorial
|
|
})
|
|
} else {
|
|
tutorial?.moveNext()
|
|
}
|
|
}
|
|
},
|
|
previous: () => {
|
|
const step = tutorial?.getActiveStep()
|
|
|
|
if (step) {
|
|
if (tutorial?.getActiveStep()?.popover?.onPrevClick) {
|
|
const activeElement = tutorial?.getActiveElement()
|
|
tutorial?.getActiveStep()?.popover?.onPrevClick?.(activeElement, step, {
|
|
config,
|
|
state,
|
|
driver: tutorial
|
|
})
|
|
} else {
|
|
tutorial?.movePrevious()
|
|
}
|
|
}
|
|
}
|
|
}
|
|
})
|
|
|
|
if (popoverDescription) {
|
|
popoverDescription.appendChild(controls)
|
|
}
|
|
}
|
|
|
|
export const runTutorial = (options?: Options | undefined) => {
|
|
if (tainted) {
|
|
dispatch('error', { detail: name })
|
|
return
|
|
}
|
|
isCurrentlyInTutorial.val = true
|
|
|
|
tutorial = driver({
|
|
allowClose: true,
|
|
disableActiveInteraction: true,
|
|
showButtons: ['close'],
|
|
showProgress: false,
|
|
overlayColor: 'rgba(0, 0, 0, 0.8)',
|
|
onPopoverRender: (popover, { config, state }) => {
|
|
renderControls({ config, state })
|
|
},
|
|
onDestroyed: () => {
|
|
onDestroyed?.()
|
|
if (!tutorial?.hasNextStep()) {
|
|
$ignoredTutorials = Array.from(new Set([...$ignoredTutorials, index]))
|
|
}
|
|
isCurrentlyInTutorial.val = false
|
|
}
|
|
})
|
|
|
|
const steps = getSteps(tutorial, options)
|
|
|
|
totalSteps = steps.length
|
|
|
|
tutorial.setSteps(steps)
|
|
tutorial.drive()
|
|
}
|
|
</script>
|
|
|
|
{#if tutorial}
|
|
<TutorialInner />
|
|
{/if}
|