mirror of
https://github.com/windmill-labs/windmill.git
synced 2026-08-21 00:02:23 +00:00
Fix app tutorials (#6728)
* Fix tutorial basic * fix other tutorials * nit fix bug with button shrinking * tutorial works backwards * nit delete field on prev * remove empty app duplication and magic code * fix norefreshbar auto binding to false, making app dirty * fix and improve app tutorial * fix background runnable tutorial scroll * fix connection tutorial * mistake * isCurrentlyInTutorial global state * disable component navigation when in tutorial * ci
This commit is contained in:
@@ -166,7 +166,13 @@
|
||||
<div class="flex text-2xs gap-8 items-center">
|
||||
<div class="py-2 pr-2 text-secondary flex gap-1 items-center">
|
||||
Hide bar on view
|
||||
<Toggle size="xs" bind:checked={$app.norefreshbar} />
|
||||
<Toggle
|
||||
size="xs"
|
||||
bind:checked={
|
||||
() => $app.norefreshbar ?? false,
|
||||
(v) => ($app.norefreshbar !== undefined || v) && ($app.norefreshbar = v)
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
{policy.on_behalf_of ? `Author ${policy.on_behalf_of_email}` : ''}
|
||||
|
||||
@@ -13,6 +13,8 @@ import {
|
||||
ccomponents,
|
||||
components,
|
||||
getRecommendedDimensionsByComponent,
|
||||
presets,
|
||||
processDimension,
|
||||
type AppComponent,
|
||||
type BaseComponent,
|
||||
type InitialAppComponent,
|
||||
@@ -36,17 +38,18 @@ import { sendUserToast } from '$lib/toast'
|
||||
import { getNextId } from '$lib/components/flows/idUtils'
|
||||
import { enterpriseLicense } from '$lib/stores'
|
||||
import gridHelp from '../svelte-grid/utils/helper'
|
||||
import { DEFAULT_THEME } from './componentsPanel/themeUtils'
|
||||
|
||||
type GridItemLocation =
|
||||
| {
|
||||
type: 'grid'
|
||||
gridItemIndex: number
|
||||
}
|
||||
type: 'grid'
|
||||
gridItemIndex: number
|
||||
}
|
||||
| {
|
||||
type: 'subgrid'
|
||||
subgridItemIndex: number
|
||||
subgridKey: string
|
||||
}
|
||||
type: 'subgrid'
|
||||
subgridItemIndex: number
|
||||
subgridKey: string
|
||||
}
|
||||
interface GridItemWithLocation {
|
||||
location: GridItemLocation
|
||||
item: GridItem
|
||||
@@ -187,7 +190,7 @@ export function selectId(
|
||||
selectedComponent: Writable<string[] | undefined>,
|
||||
app: App
|
||||
) {
|
||||
; (document?.activeElement as HTMLElement)?.blur()
|
||||
;(document?.activeElement as HTMLElement)?.blur()
|
||||
if (e.shiftKey) {
|
||||
selectedComponent.update((old) => {
|
||||
if (old && old?.[0]) {
|
||||
@@ -492,11 +495,11 @@ export function appComponentFromType<T extends keyof typeof components>(
|
||||
xData:
|
||||
type === 'plotlycomponentv2' || type === 'chartjscomponentv2'
|
||||
? {
|
||||
type: 'evalv2',
|
||||
fieldType: 'array',
|
||||
expr: '[1, 2, 3, 4]',
|
||||
connections: []
|
||||
}
|
||||
type: 'evalv2',
|
||||
fieldType: 'array',
|
||||
expr: '[1, 2, 3, 4]',
|
||||
connections: []
|
||||
}
|
||||
: undefined,
|
||||
...(extra ?? {})
|
||||
}
|
||||
@@ -845,33 +848,33 @@ export type InitConfig<
|
||||
| EvalAppInput
|
||||
| EvalV2AppInput
|
||||
| {
|
||||
type: 'oneOf'
|
||||
selected: string
|
||||
configuration: Record<
|
||||
string,
|
||||
Record<string, StaticAppInput | EvalAppInput | EvalV2AppInput>
|
||||
>
|
||||
}
|
||||
type: 'oneOf'
|
||||
selected: string
|
||||
configuration: Record<
|
||||
string,
|
||||
Record<string, StaticAppInput | EvalAppInput | EvalV2AppInput>
|
||||
>
|
||||
}
|
||||
>
|
||||
> = {
|
||||
[Property in keyof T]: T[Property] extends StaticAppInput
|
||||
[Property in keyof T]: T[Property] extends StaticAppInput
|
||||
? T[Property]['value'] | undefined
|
||||
: T[Property] extends { type: 'oneOf' }
|
||||
? {
|
||||
type: 'oneOf'
|
||||
selected: keyof T[Property]['configuration']
|
||||
configuration: {
|
||||
[Choice in keyof T[Property]['configuration']]: {
|
||||
[IT in keyof T[Property]['configuration'][Choice]]: T[Property]['configuration'][Choice][IT] extends StaticAppInput
|
||||
? T[Property]['configuration'][Choice][IT] extends StaticAppInputOnDemand
|
||||
? () => Promise<T[Property]['configuration'][Choice][IT]['value'] | undefined>
|
||||
: T[Property]['configuration'][Choice][IT]['value'] | undefined
|
||||
: undefined
|
||||
? {
|
||||
type: 'oneOf'
|
||||
selected: keyof T[Property]['configuration']
|
||||
configuration: {
|
||||
[Choice in keyof T[Property]['configuration']]: {
|
||||
[IT in keyof T[Property]['configuration'][Choice]]: T[Property]['configuration'][Choice][IT] extends StaticAppInput
|
||||
? T[Property]['configuration'][Choice][IT] extends StaticAppInputOnDemand
|
||||
? () => Promise<T[Property]['configuration'][Choice][IT]['value'] | undefined>
|
||||
: T[Property]['configuration'][Choice][IT]['value'] | undefined
|
||||
: undefined
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
: undefined
|
||||
}
|
||||
: undefined
|
||||
}
|
||||
|
||||
export function initConfig<
|
||||
T extends Record<
|
||||
@@ -880,13 +883,13 @@ export function initConfig<
|
||||
| EvalAppInput
|
||||
| EvalV2AppInput
|
||||
| {
|
||||
type: 'oneOf'
|
||||
selected: string
|
||||
configuration: Record<
|
||||
string,
|
||||
Record<string, StaticAppInput | EvalAppInput | EvalV2AppInput>
|
||||
>
|
||||
}
|
||||
type: 'oneOf'
|
||||
selected: string
|
||||
configuration: Record<
|
||||
string,
|
||||
Record<string, StaticAppInput | EvalAppInput | EvalV2AppInput>
|
||||
>
|
||||
}
|
||||
>
|
||||
>(
|
||||
r: T,
|
||||
@@ -894,13 +897,13 @@ export function initConfig<
|
||||
string,
|
||||
| StaticAppInput
|
||||
| {
|
||||
type: 'oneOf'
|
||||
selected: string
|
||||
configuration: Record<
|
||||
string,
|
||||
Record<string, StaticAppInput | EvalAppInput | EvalV2AppInput | boolean>
|
||||
>
|
||||
}
|
||||
type: 'oneOf'
|
||||
selected: string
|
||||
configuration: Record<
|
||||
string,
|
||||
Record<string, StaticAppInput | EvalAppInput | EvalV2AppInput | boolean>
|
||||
>
|
||||
}
|
||||
| any
|
||||
>
|
||||
): InitConfig<T> {
|
||||
@@ -910,31 +913,31 @@ export function initConfig<
|
||||
Object.entries(r).map(([key, value]) =>
|
||||
value.type == 'static'
|
||||
? [
|
||||
key,
|
||||
configuration?.[key]?.type == 'static' ? configuration?.[key]?.['value'] : undefined
|
||||
]
|
||||
key,
|
||||
configuration?.[key]?.type == 'static' ? configuration?.[key]?.['value'] : undefined
|
||||
]
|
||||
: value.type == 'oneOf'
|
||||
? [
|
||||
key,
|
||||
{
|
||||
selected: value.selected,
|
||||
type: 'oneOf',
|
||||
configuration: Object.fromEntries(
|
||||
Object.entries(value.configuration).map(([choice, config]) => {
|
||||
const conf = initConfig(
|
||||
config,
|
||||
configuration?.[key]?.configuration?.[choice]
|
||||
)
|
||||
Object.entries(config).forEach(([innerKey, innerValue]) => {
|
||||
if (innerValue.type === 'static' && !(innerKey in conf)) {
|
||||
conf[innerKey] = innerValue.value
|
||||
}
|
||||
key,
|
||||
{
|
||||
selected: value.selected,
|
||||
type: 'oneOf',
|
||||
configuration: Object.fromEntries(
|
||||
Object.entries(value.configuration).map(([choice, config]) => {
|
||||
const conf = initConfig(
|
||||
config,
|
||||
configuration?.[key]?.configuration?.[choice]
|
||||
)
|
||||
Object.entries(config).forEach(([innerKey, innerValue]) => {
|
||||
if (innerValue.type === 'static' && !(innerKey in conf)) {
|
||||
conf[innerKey] = innerValue.value
|
||||
}
|
||||
})
|
||||
return [choice, conf]
|
||||
})
|
||||
return [choice, conf]
|
||||
})
|
||||
)
|
||||
}
|
||||
]
|
||||
)
|
||||
}
|
||||
]
|
||||
: [key, undefined]
|
||||
)
|
||||
) as any
|
||||
@@ -1395,3 +1398,45 @@ export function animateTo(start: number, end: number, onUpdate: (newValue: numbe
|
||||
function easeInOut(t: number) {
|
||||
return t < 0.5 ? 2 * t * t : -1 + (4 - 2 * t) * t
|
||||
}
|
||||
|
||||
export function emptyApp(): App {
|
||||
let value: App = {
|
||||
grid: [],
|
||||
fullscreen: false,
|
||||
unusedInlineScripts: [],
|
||||
hiddenInlineScripts: [],
|
||||
theme: {
|
||||
type: 'path',
|
||||
path: DEFAULT_THEME
|
||||
}
|
||||
}
|
||||
const preset = presets['topbarcomponent']
|
||||
|
||||
const id = insertNewGridItem(
|
||||
value,
|
||||
appComponentFromType(preset.targetComponent, preset.configuration, undefined, {
|
||||
customCss: {
|
||||
container: {
|
||||
class: '!p-0' as any,
|
||||
style: ''
|
||||
}
|
||||
}
|
||||
}) as (id: string) => AppComponent,
|
||||
undefined,
|
||||
undefined,
|
||||
'topbar',
|
||||
{ x: 0, y: 0 },
|
||||
{
|
||||
3: processDimension(preset.dims, 3),
|
||||
12: processDimension(preset.dims, 12)
|
||||
},
|
||||
true,
|
||||
true
|
||||
)
|
||||
|
||||
setUpTopBarComponentContent(id, value)
|
||||
|
||||
value.hideLegacyTopBar = true
|
||||
value.mobileViewOnSmallerScreens = false
|
||||
return value
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
left
|
||||
} from './componentCallbacks.svelte'
|
||||
import type { AppEditorContext, AppViewerContext } from '../../types'
|
||||
import { isCurrentlyInTutorial } from '$lib/stores'
|
||||
|
||||
const { history, movingcomponents, jobsDrawerOpen, runnableJobEditorPanel } =
|
||||
getContext<AppEditorContext>('AppEditorContext') as AppEditorContext
|
||||
@@ -33,7 +34,8 @@
|
||||
if (
|
||||
(typeof classes === 'string' && classes.includes('inputarea')) ||
|
||||
['INPUT', 'TEXTAREA'].includes(document.activeElement?.tagName!) ||
|
||||
$runnableJobEditorPanel.focused
|
||||
$runnableJobEditorPanel.focused ||
|
||||
isCurrentlyInTutorial.val
|
||||
) {
|
||||
return
|
||||
}
|
||||
|
||||
@@ -52,7 +52,7 @@
|
||||
|
||||
{#if render && object != undefined && Object.keys(object).length > 0}
|
||||
{#if $hasResult[componentId] || $search == ''}
|
||||
<div class="pl-2 !cursor-pointer" data-connection-button>
|
||||
<div class="pl-2 !cursor-pointer component-output-viewer-{componentId}" data-connection-button>
|
||||
<ObjectViewer
|
||||
json={filtered}
|
||||
on:select
|
||||
|
||||
@@ -11,14 +11,14 @@
|
||||
|
||||
import { Building, GitFork, Globe2 } from 'lucide-svelte'
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
import { fly } from 'svelte/transition'
|
||||
import { defaultCode } from '../component'
|
||||
import WorkspaceScriptList from '../settingsPanel/mainInput/WorkspaceScriptList.svelte'
|
||||
import RunnableSelector from '../settingsPanel/mainInput/RunnableSelector.svelte'
|
||||
import { defaultScripts } from '$lib/stores'
|
||||
import { defaultScripts, isCurrentlyInTutorial } from '$lib/stores'
|
||||
import DefaultScripts from '$lib/components/DefaultScripts.svelte'
|
||||
import type { Preview } from '$lib/gen'
|
||||
import type { InlineScript } from '../../types'
|
||||
import { twMerge } from 'tailwind-merge'
|
||||
|
||||
interface Props {
|
||||
componentType?: string | undefined
|
||||
@@ -133,8 +133,10 @@
|
||||
</Drawer>
|
||||
|
||||
<div
|
||||
class="flex flex-col px-4 gap-2 text-sm"
|
||||
in:fly={{ duration: 50 }}
|
||||
class={twMerge(
|
||||
'flex flex-col px-4 gap-2 text-sm',
|
||||
isCurrentlyInTutorial.val ? 'h-full overflow-y-clip' : ''
|
||||
)}
|
||||
id="app-editor-empty-runnable"
|
||||
>
|
||||
<div class="mt-2 flex justify-between gap-4" id="app-editor-runnable-header">
|
||||
|
||||
@@ -193,6 +193,7 @@
|
||||
{openConnection}
|
||||
isOpen={!!$connectingInput.opened}
|
||||
btnWrapperClasses={'h-6 w-8 opacity-0 group-hover:opacity-100 transition-opacity'}
|
||||
id="schema-plug-{key}"
|
||||
/>
|
||||
<ToggleButtonGroup
|
||||
class="h-6"
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
export let openConnection: () => void
|
||||
export let closeConnection: () => void
|
||||
export let btnWrapperClasses = ''
|
||||
export let id: string | undefined = undefined
|
||||
|
||||
let selected = false
|
||||
|
||||
@@ -83,7 +84,7 @@
|
||||
color="light"
|
||||
title="Connect"
|
||||
on:click={() => handleConnect(true)}
|
||||
id="schema-plug"
|
||||
{id}
|
||||
wrapperClasses={twMerge(btnWrapperClasses, selected ? 'opacity-100' : '')}
|
||||
btnClasses="p-0"
|
||||
>
|
||||
|
||||
@@ -6,10 +6,12 @@
|
||||
import SkipTutorials from './SkipTutorials.svelte'
|
||||
import TutorialControls from './TutorialControls.svelte'
|
||||
import TutorialInner from './TutorialInner.svelte'
|
||||
import { isCurrentlyInTutorial } from '$lib/stores'
|
||||
|
||||
export let index: number = 0
|
||||
export let name: string = 'action'
|
||||
export let tainted: boolean = false
|
||||
export let onDestroyed: (() => void) | undefined = undefined
|
||||
|
||||
type Options = {
|
||||
indexToInsertAt?: number
|
||||
@@ -111,6 +113,7 @@
|
||||
dispatch('error', { detail: name })
|
||||
return
|
||||
}
|
||||
isCurrentlyInTutorial.val = true
|
||||
|
||||
tutorial = driver({
|
||||
allowClose: true,
|
||||
@@ -122,9 +125,11 @@
|
||||
renderControls({ config, state })
|
||||
},
|
||||
onDestroyed: () => {
|
||||
onDestroyed?.()
|
||||
if (!tutorial?.hasNextStep()) {
|
||||
$ignoredTutorials = Array.from(new Set([...$ignoredTutorials, index]))
|
||||
}
|
||||
isCurrentlyInTutorial.val = false
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
@@ -13,14 +13,15 @@
|
||||
updateInlineRunnableCode
|
||||
} from '../utils'
|
||||
import { updateProgress } from '$lib/tutorialUtils'
|
||||
import { type DriveStep } from 'driver.js'
|
||||
import { wait } from '$lib/utils'
|
||||
|
||||
export let name: string
|
||||
export let index: number
|
||||
|
||||
let tutorial: Tutorial | undefined = undefined
|
||||
|
||||
const { app, selectedComponent, focusedGrid, connectingInput } =
|
||||
getContext<AppViewerContext>('AppViewerContext')
|
||||
const { app, selectedComponent, focusedGrid } = getContext<AppViewerContext>('AppViewerContext')
|
||||
const { history } = getContext<AppEditorContext>('AppEditorContext')
|
||||
|
||||
export function runTutorial() {
|
||||
@@ -49,7 +50,7 @@
|
||||
on:skipAll
|
||||
tainted={isAppTainted($app)}
|
||||
getSteps={(driver) => {
|
||||
const steps = [
|
||||
const steps: DriveStep[] = [
|
||||
{
|
||||
popover: {
|
||||
title: 'App editor tutorial',
|
||||
@@ -112,7 +113,7 @@
|
||||
popover: {
|
||||
title: 'Component input',
|
||||
description:
|
||||
'There are several ways to set the input of a component. It can be static, the result of a JS expression, connected to the output of another component, or the result of a inline runnable. Here we will create an inline runnable that will convert the text to uppercase.',
|
||||
'There are several ways to set the input of a component. It can be static, the result of a JS expression, connected to the output of another component, or the result of an inline runnable. Here we will create an inline runnable that will convert the text to uppercase.',
|
||||
onNextClick: () => {
|
||||
clickFirstButtonBySelector('#component-input')
|
||||
setTimeout(() => {
|
||||
@@ -143,9 +144,7 @@
|
||||
description: "Let's create an inline script.",
|
||||
onNextClick: () => {
|
||||
clickButtonBySelector('#app-editor-create-inline-script')
|
||||
setTimeout(() => {
|
||||
driver.moveNext()
|
||||
})
|
||||
setTimeout(() => driver.moveNext())
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -155,7 +154,7 @@
|
||||
popover: {
|
||||
title: 'Choose a language',
|
||||
description:
|
||||
'You can choose the language of your runnable. They are two type of runnables: frontend and backend.'
|
||||
'You can choose the language of your runnable. There are two type of runnables: frontend and backend.'
|
||||
}
|
||||
},
|
||||
|
||||
@@ -177,87 +176,71 @@
|
||||
},
|
||||
{
|
||||
element: '#create-deno-script',
|
||||
onHighlighted: () => {
|
||||
document.querySelector('#schema-plug-x')?.parentElement?.classList.remove('opacity-0')
|
||||
},
|
||||
popover: {
|
||||
title: 'Create a deno script',
|
||||
description:
|
||||
"Let's create a simple deno script. For the sake of this tutorial, we will create a script that converts the text to uppercase.",
|
||||
onNextClick: () => {
|
||||
onNextClick: async () => {
|
||||
clickButtonBySelector('#create-deno-script')
|
||||
setTimeout(() => {
|
||||
if ($selectedComponent?.[0]) {
|
||||
updateInlineRunnableCode(
|
||||
$app,
|
||||
$selectedComponent[0],
|
||||
`export async function main(x: string) {
|
||||
return x?.toLocaleUpperCase();
|
||||
}
|
||||
`
|
||||
)
|
||||
}
|
||||
await wait(50)
|
||||
if ($selectedComponent?.[0]) {
|
||||
updateInlineRunnableCode(
|
||||
$app,
|
||||
$selectedComponent[0],
|
||||
'export function main(x: string) {\n return x?.toLocaleUpperCase();\n}'
|
||||
)
|
||||
}
|
||||
|
||||
driver.moveNext()
|
||||
})
|
||||
driver.moveNext()
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
element: '#schema-plug',
|
||||
element: '#schema-plug-x',
|
||||
onHighlighted: () => {
|
||||
document.querySelector('#schema-plug-x')?.parentElement?.classList.remove('opacity-0')
|
||||
},
|
||||
popover: {
|
||||
title: 'Connect the function input',
|
||||
description:
|
||||
"The function we created has an string input 'x'. We can connect the output of the text component to it.",
|
||||
onNextClick: () => {
|
||||
clickButtonBySelector('#schema-plug')
|
||||
clickButtonBySelector('#schema-plug-x')
|
||||
setTimeout(() => {
|
||||
driver.moveNext()
|
||||
})
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
{
|
||||
element: '#connect-output-d',
|
||||
element: '#connect-output-a',
|
||||
popover: {
|
||||
title: 'Select the output',
|
||||
description: ' ',
|
||||
description: 'Open the output selector of the text input component.',
|
||||
onNextClick: () => {
|
||||
$connectingInput.opened = false
|
||||
$connectingInput.input = undefined
|
||||
|
||||
clickButtonBySelector('#connect-output-a')
|
||||
setTimeout(() => {
|
||||
driver.moveNext()
|
||||
})
|
||||
},
|
||||
onPopoverRender: (popover, opts) => {
|
||||
const wrapper = document.createElement('div')
|
||||
wrapper.classList.add('flex', 'flex-col', 'gap-2', 'w-full', 'items-start')
|
||||
|
||||
const p1 = document.createElement('p')
|
||||
p1.innerText =
|
||||
'You can now select the output in the output menu. Click on the little red button to open the menu.'
|
||||
|
||||
const id = document.createElement('div')
|
||||
id.innerHTML = `<button class="bg-red-500/70 border border-red-600 px-1 py-0.5" title="Outputs" aria-label="Open output"><svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide-icon lucide lucide-plug-2 "><path d="M9 2v6"></path><path d="M15 2v6"></path><path d="M12 17v5"></path><path d="M5 8h14"></path><path d="M6 11V8h12v3a6 6 0 1 1-12 0v0Z"></path></svg></button>`
|
||||
|
||||
const p2 = document.createElement('p')
|
||||
p2.innerText =
|
||||
'Once opened, you can select the output you want to connect to. Here we will connect the result output of the text component to the input "x" of the inline runnable.'
|
||||
|
||||
const objectViewer = document.createElement('div')
|
||||
objectViewer.innerHTML = `<div class="rounded-lg shadow-md border p-4 bg-surface"><span class="s-UNyBDXJ1E286"> <ul class="w-full pl-2 border-none s-UNyBDXJ1E286"><li class="s-UNyBDXJ1E286"><button class="whitespace-nowrap s-UNyBDXJ1E286"><span class="key border font-semibold rounded px-1 hover:bg-surface-hover text-2xs text-secondary s-UNyBDXJ1E286">result</span> :</button> <button class="val rounded px-1 hover:bg-blue-100 string s-UNyBDXJ1E286"><span title="" class="text-2xs s-UNyBDXJ1E286">""</span></button></li> </ul> </span> <span class="border border-blue-600 rounded px-1 cursor-pointer hover:bg-gray-200 s-UNyBDXJ1E286 hidden">{...}</span> </div>`
|
||||
|
||||
wrapper.appendChild(p1)
|
||||
wrapper.appendChild(id)
|
||||
wrapper.appendChild(p2)
|
||||
wrapper.appendChild(objectViewer)
|
||||
|
||||
popover.description.appendChild(wrapper)
|
||||
|
||||
tutorial?.renderControls(opts)
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
{
|
||||
element: '.component-output-viewer-a li *:has(> button[title="result"])',
|
||||
popover: {
|
||||
title: 'Select the output',
|
||||
description: "Let's select the result of the text input component.",
|
||||
onNextClick: () => {
|
||||
setTimeout(async () => {
|
||||
clickButtonBySelector('.component-output-viewer-a li button[title="result"]')
|
||||
driver.moveNext()
|
||||
})
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
element: '.wm-app-viewer',
|
||||
popover: {
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
<script lang="ts">
|
||||
import { updateProgress } from '$lib/tutorialUtils'
|
||||
import { type DriveStep } from 'driver.js'
|
||||
import Tutorial from '../Tutorial.svelte'
|
||||
import { clickButtonBySelector } from '../utils'
|
||||
|
||||
@@ -20,7 +21,7 @@
|
||||
on:error
|
||||
on:skipAll
|
||||
getSteps={(driver, options) => {
|
||||
const steps = [
|
||||
const steps: DriveStep[] = [
|
||||
{
|
||||
element: '#app-editor-runnable-panel',
|
||||
popover: {
|
||||
@@ -37,10 +38,7 @@
|
||||
'Click here to create a runnable. Runnables are scripts that can be executed in the background. You can add as many runnables as you want.',
|
||||
onNextClick: () => {
|
||||
clickButtonBySelector('#create-background-runnable')
|
||||
|
||||
setTimeout(() => {
|
||||
driver.moveNext()
|
||||
})
|
||||
setTimeout(() => driver.moveNext())
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
@@ -68,12 +68,12 @@
|
||||
}
|
||||
},
|
||||
{
|
||||
element: '#plug',
|
||||
element: '[data-connection-button] button[title="Connect"]',
|
||||
popover: {
|
||||
title: 'Connect the text component',
|
||||
description: 'Click on the plug icon to connect the text component',
|
||||
onNextClick: () => {
|
||||
clickButtonBySelector('#plug')
|
||||
clickButtonBySelector('[data-connection-button] button[title="Connect"]')
|
||||
setTimeout(() => {
|
||||
driver.moveNext()
|
||||
})
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import type { FlowModule, OpenFlow } from '$lib/gen'
|
||||
import { deepEqual } from 'fast-equals'
|
||||
import { findGridItem } from '../apps/editor/appUtils'
|
||||
import { emptyApp, findGridItem } from '../apps/editor/appUtils'
|
||||
import type { App } from '../apps/types'
|
||||
|
||||
export function setInputBySelector(selector: string, value: string) {
|
||||
@@ -52,155 +52,6 @@ export function isFlowTainted(flow: OpenFlow) {
|
||||
)
|
||||
}
|
||||
|
||||
const emptyApp = {
|
||||
grid: [
|
||||
{
|
||||
'3': {
|
||||
fixed: false,
|
||||
x: 0,
|
||||
y: 0,
|
||||
fullHeight: false,
|
||||
w: 6,
|
||||
h: 2
|
||||
},
|
||||
'12': {
|
||||
fixed: false,
|
||||
x: 0,
|
||||
y: 0,
|
||||
fullHeight: false,
|
||||
w: 12,
|
||||
h: 2
|
||||
},
|
||||
data: {
|
||||
type: 'containercomponent',
|
||||
configuration: {},
|
||||
customCss: {
|
||||
container: {
|
||||
class: '!p-0',
|
||||
style: ''
|
||||
}
|
||||
},
|
||||
numberOfSubgrids: 1,
|
||||
id: 'a'
|
||||
},
|
||||
id: 'a'
|
||||
}
|
||||
],
|
||||
fullscreen: false,
|
||||
unusedInlineScripts: [],
|
||||
hiddenInlineScripts: [],
|
||||
theme: {
|
||||
type: 'path',
|
||||
path: 'f/app_themes/theme_0'
|
||||
},
|
||||
subgrids: {
|
||||
'a-0': [
|
||||
{
|
||||
'3': {
|
||||
fixed: false,
|
||||
x: 0,
|
||||
y: 0,
|
||||
fullHeight: false,
|
||||
w: 6,
|
||||
h: 1
|
||||
},
|
||||
'12': {
|
||||
fixed: false,
|
||||
x: 0,
|
||||
y: 0,
|
||||
fullHeight: false,
|
||||
w: 6,
|
||||
h: 1
|
||||
},
|
||||
data: {
|
||||
type: 'textcomponent',
|
||||
configuration: {
|
||||
style: {
|
||||
type: 'static',
|
||||
value: 'Body'
|
||||
},
|
||||
copyButton: {
|
||||
type: 'static',
|
||||
value: false
|
||||
},
|
||||
tooltip: {
|
||||
type: 'evalv2',
|
||||
value: '',
|
||||
fieldType: 'text',
|
||||
expr: '`Author: ${ctx.author}`',
|
||||
connections: [
|
||||
{
|
||||
componentId: 'ctx',
|
||||
id: 'author'
|
||||
}
|
||||
]
|
||||
},
|
||||
disableNoText: {
|
||||
type: 'static',
|
||||
value: true,
|
||||
fieldType: 'boolean'
|
||||
}
|
||||
},
|
||||
componentInput: {
|
||||
type: 'templatev2',
|
||||
fieldType: 'template',
|
||||
eval: '${ctx.summary}',
|
||||
connections: [
|
||||
{
|
||||
id: 'summary',
|
||||
componentId: 'ctx'
|
||||
}
|
||||
]
|
||||
},
|
||||
customCss: {
|
||||
text: {
|
||||
class: 'text-xl font-semibold whitespace-nowrap truncate',
|
||||
style: ''
|
||||
},
|
||||
container: {
|
||||
class: '',
|
||||
style: ''
|
||||
}
|
||||
},
|
||||
horizontalAlignment: 'left',
|
||||
verticalAlignment: 'center',
|
||||
id: 'b'
|
||||
},
|
||||
id: 'b'
|
||||
},
|
||||
{
|
||||
'3': {
|
||||
fixed: false,
|
||||
x: 0,
|
||||
y: 1,
|
||||
fullHeight: false,
|
||||
w: 3,
|
||||
h: 1
|
||||
},
|
||||
'12': {
|
||||
fixed: false,
|
||||
x: 6,
|
||||
y: 0,
|
||||
fullHeight: false,
|
||||
w: 6,
|
||||
h: 1
|
||||
},
|
||||
data: {
|
||||
type: 'recomputeallcomponent',
|
||||
configuration: {},
|
||||
menuItems: [],
|
||||
horizontalAlignment: 'right',
|
||||
verticalAlignment: 'center',
|
||||
id: 'c'
|
||||
},
|
||||
id: 'c'
|
||||
}
|
||||
]
|
||||
},
|
||||
hideLegacyTopBar: true,
|
||||
norefreshbar: false
|
||||
}
|
||||
|
||||
export function isAppTainted(app: App) {
|
||||
if (app.hideLegacyTopBar === true) {
|
||||
// An empty app should have only have a topbar and no hidden inline scripts
|
||||
@@ -215,7 +66,7 @@ export function isAppTainted(app: App) {
|
||||
}
|
||||
|
||||
// Check if the current app is different from an empty app
|
||||
return !deepEqual(app, emptyApp)
|
||||
return !deepEqual(app, emptyApp())
|
||||
} else {
|
||||
// For older apps,
|
||||
return !(app.grid?.length === 0 && app.hiddenInlineScripts?.length === 0)
|
||||
@@ -247,8 +98,6 @@ export function updateFlowModuleById(
|
||||
}
|
||||
|
||||
dfs(flow.value.modules)
|
||||
|
||||
flow = flow
|
||||
}
|
||||
|
||||
export function updateBackgroundRunnableCode(app: App, index: number, newCode: string) {
|
||||
@@ -256,13 +105,10 @@ export function updateBackgroundRunnableCode(app: App, index: number, newCode: s
|
||||
if (script.type === 'runnableByName' && script.inlineScript) {
|
||||
script.inlineScript.content = newCode
|
||||
}
|
||||
|
||||
app = app
|
||||
}
|
||||
|
||||
export function updateInlineRunnableCode(app: App, componentId: string, newCode: string) {
|
||||
const gridItem = findGridItem(app, componentId)
|
||||
|
||||
if (gridItem?.data.componentInput?.type === 'runnable') {
|
||||
if (
|
||||
gridItem.data.componentInput.runnable?.type === 'runnableByName' &&
|
||||
@@ -271,8 +117,6 @@ export function updateInlineRunnableCode(app: App, componentId: string, newCode:
|
||||
gridItem.data.componentInput.runnable.inlineScript.content = newCode
|
||||
}
|
||||
}
|
||||
|
||||
app = app
|
||||
}
|
||||
|
||||
export function connectComponentSourceToOutput(app: App, componentId: string, targetId: string) {
|
||||
@@ -292,8 +136,6 @@ export function connectComponentSourceToOutput(app: App, componentId: string, ta
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
app = app
|
||||
}
|
||||
|
||||
export function connectInlineRunnableInputToComponentOutput(
|
||||
|
||||
@@ -12,8 +12,9 @@ import {
|
||||
type WorkspaceDefaultScripts,
|
||||
WorkspaceService
|
||||
} from './gen'
|
||||
import { getLocalSetting } from './utils'
|
||||
import { getLocalSetting, type StateStore } from './utils'
|
||||
import { workspaceAIClients } from './components/copilot/lib'
|
||||
import { createState } from './svelte5Utils.svelte'
|
||||
|
||||
export interface UserExt {
|
||||
email: string
|
||||
@@ -277,6 +278,8 @@ export const workspaceColor: Readable<string | null | undefined> = derived(
|
||||
}
|
||||
)
|
||||
|
||||
export const isCurrentlyInTutorial: StateStore<boolean> = createState({ val: false })
|
||||
|
||||
export function getFlatTableNamesFromSchema(dbSchema: DBSchema | undefined): string[] {
|
||||
const schema = dbSchema?.schema ?? {}
|
||||
const tableNames: string[] = []
|
||||
|
||||
@@ -13,16 +13,7 @@
|
||||
import { goto } from '$lib/navigation'
|
||||
import { sendUserToast } from '$lib/toast'
|
||||
import { DEFAULT_THEME } from '$lib/components/apps/editor/componentsPanel/themeUtils'
|
||||
import {
|
||||
presets,
|
||||
processDimension,
|
||||
type AppComponent
|
||||
} from '$lib/components/apps/editor/component'
|
||||
import {
|
||||
appComponentFromType,
|
||||
insertNewGridItem,
|
||||
setUpTopBarComponentContent
|
||||
} from '$lib/components/apps/editor/appUtils'
|
||||
import { emptyApp } from '$lib/components/apps/editor/appUtils'
|
||||
|
||||
let nodraft = $page.url.searchParams.get('nodraft')
|
||||
const hubId = $page.url.searchParams.get('hub')
|
||||
@@ -118,36 +109,7 @@
|
||||
])
|
||||
value = decodeState(appState)
|
||||
} else {
|
||||
const preset = presets['topbarcomponent']
|
||||
|
||||
const id = insertNewGridItem(
|
||||
value,
|
||||
appComponentFromType(preset.targetComponent, preset.configuration, undefined, {
|
||||
customCss: {
|
||||
container: {
|
||||
class: '!p-0' as any,
|
||||
style: ''
|
||||
}
|
||||
}
|
||||
}) as (id: string) => AppComponent,
|
||||
undefined,
|
||||
undefined,
|
||||
'topbar',
|
||||
{ x: 0, y: 0 },
|
||||
{
|
||||
3: processDimension(preset.dims, 3),
|
||||
12: processDimension(preset.dims, 12)
|
||||
},
|
||||
true,
|
||||
true
|
||||
)
|
||||
|
||||
setUpTopBarComponentContent(id, value)
|
||||
|
||||
value.hideLegacyTopBar = true
|
||||
value.mobileViewOnSmallerScreens = false
|
||||
|
||||
value = value
|
||||
value = emptyApp()
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
Reference in New Issue
Block a user