Compare commits
1 Commits
feat/apped
...
rf/flowSta
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d0448ac838 |
@@ -25,7 +25,7 @@
|
||||
import { deepEqual } from 'fast-equals'
|
||||
import FlowTimeline from './FlowTimeline.svelte'
|
||||
import { dfs } from './flows/dfs'
|
||||
import { writable, type Writable } from 'svelte/store'
|
||||
import { get, writable, type Unsubscriber, type Writable } from 'svelte/store'
|
||||
import Alert from './common/alert/Alert.svelte'
|
||||
import FlowGraphViewerStep from './FlowGraphViewerStep.svelte'
|
||||
import FlowGraphV2 from './graph/FlowGraphV2.svelte'
|
||||
@@ -66,10 +66,7 @@
|
||||
|
||||
export let globalModuleStates: Writable<Record<string, GraphModuleState>>[]
|
||||
export let globalDurationStatuses: Writable<Record<string, DurationStatus>>[]
|
||||
export let globalRefreshes: Record<
|
||||
string,
|
||||
(loopJob: { index: number; job: string }) => Promise<void>
|
||||
> = {}
|
||||
|
||||
export let childFlow: boolean = false
|
||||
export let reducedPolling = false
|
||||
|
||||
@@ -79,6 +76,7 @@
|
||||
export let prefix: string | undefined = undefined
|
||||
export let subflowParentsGlobalModuleStates: Writable<Record<string, GraphModuleState>>[] = []
|
||||
export let subflowParentsDurationStatuses: Writable<Record<string, DurationStatus>>[] = []
|
||||
export let isForloopSelected = false
|
||||
|
||||
let jobResults: any[] =
|
||||
flowJobIds?.flowJobs?.map((x, id) => `iter #${id + 1} not loaded by frontend yet`) ?? []
|
||||
@@ -110,6 +108,16 @@
|
||||
if (keepType && (x[key]?.type == 'Success' || x[key]?.type == 'Failure')) {
|
||||
newValue.type = x[key].type
|
||||
}
|
||||
|
||||
if (x[key]?.selectedForLoopSetManually) {
|
||||
newValue.selectedForLoopSetManually = true
|
||||
newValue.selectedForloopIndex = x[key].selectedForloopIndex
|
||||
newValue.selectedForloop = x[key].selectedForloop
|
||||
} else if (x[key]?.selectedForloopIndex != undefined && newValue.selectedForloopIndex == undefined) {
|
||||
newValue.selectedForloopIndex = x[key].selectedForloopIndex
|
||||
newValue.selectedForloop = x[key].selectedForloop
|
||||
}
|
||||
|
||||
x[key] = newValue
|
||||
return x
|
||||
})
|
||||
@@ -212,6 +220,7 @@
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function updateInnerModules() {
|
||||
if ($localModuleStates) {
|
||||
innerModules.forEach((mod, i) => {
|
||||
@@ -256,6 +265,8 @@
|
||||
},
|
||||
true
|
||||
)
|
||||
} else if (isForloopSelected) {
|
||||
setModuleState(mod.id ?? '', {}, true)
|
||||
}
|
||||
if (mod.branch_chosen) {
|
||||
setModuleState(
|
||||
@@ -284,63 +295,32 @@
|
||||
}
|
||||
}
|
||||
|
||||
let recursiveRefresh: Record<string, (boolean) => Promise<void>> = {}
|
||||
|
||||
export async function refresh(
|
||||
root: boolean,
|
||||
loopJob: { index: number; job: string } | undefined
|
||||
) {
|
||||
let modId = flowJobIds?.moduleId
|
||||
|
||||
if (!loopJob) {
|
||||
loopJob = {
|
||||
index: $localModuleStates[modId ?? '']?.selectedForloopIndex ?? 0,
|
||||
job: $localModuleStates[modId ?? '']?.selectedForloop ?? ''
|
||||
}
|
||||
}
|
||||
|
||||
let last = root ? undefined : flowJobIds?.flowJobs?.[flowJobIds?.flowJobs.length - 1]
|
||||
|
||||
// console.log(innerModule, modId)
|
||||
|
||||
Object.entries(recursiveRefresh).forEach(([key, v]) => {
|
||||
if (modId) {
|
||||
if ((root && key == loopJob?.job) || key == last) {
|
||||
v(false)
|
||||
}
|
||||
} else {
|
||||
v(false)
|
||||
}
|
||||
})
|
||||
|
||||
let njob = flowJobIds
|
||||
? root && modId
|
||||
? storedListJobs?.[loopJob.job]
|
||||
: storedListJobs?.[flowJobIds.length - 1]
|
||||
: job
|
||||
|
||||
if (njob) {
|
||||
dispatch('jobsLoaded', { job: njob, force: true })
|
||||
}
|
||||
}
|
||||
$: isForloopSelected && globalModuleStates && loadJobInProgress()
|
||||
|
||||
async function getNewJob(jobId: string, initialJob: Job | undefined) {
|
||||
if (jobId == initialJob?.id &&
|
||||
initialJob?.id != undefined &&
|
||||
initialJob?.type === 'CompletedJob') {
|
||||
return initialJob;
|
||||
} else {
|
||||
return await JobService.getJob({
|
||||
workspace: workspaceId ?? $workspaceStore ?? '',
|
||||
id: jobId ?? '',
|
||||
noLogs: true
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
let errorCount = 0
|
||||
let notAnonynmous = false
|
||||
async function loadJobInProgress() {
|
||||
dispatch('start')
|
||||
if (jobId != '00000000-0000-0000-0000-000000000000') {
|
||||
try {
|
||||
const newJob =
|
||||
jobId == initialJob?.id &&
|
||||
initialJob?.id != undefined &&
|
||||
initialJob?.type === 'CompletedJob'
|
||||
? initialJob
|
||||
: await JobService.getJob({
|
||||
workspace: workspaceId ?? $workspaceStore ?? '',
|
||||
id: jobId ?? '',
|
||||
noLogs: true
|
||||
})
|
||||
if (!deepEqual(job, newJob)) {
|
||||
const newJob = await getNewJob(jobId, initialJob)
|
||||
if (!deepEqual(job, newJob) || isForloopSelected) {
|
||||
job = newJob
|
||||
job?.flow_status && updateStatus(job?.flow_status)
|
||||
dispatch('jobsLoaded', { job, force: false })
|
||||
@@ -366,6 +346,10 @@
|
||||
}
|
||||
|
||||
let destroyed = false
|
||||
|
||||
|
||||
|
||||
|
||||
async function updateJobId() {
|
||||
if (jobId !== job?.id) {
|
||||
$localModuleStates = {}
|
||||
@@ -396,30 +380,30 @@
|
||||
$localDurationStatuses = {}
|
||||
}
|
||||
await loadJobInProgress()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$: jobId && updateJobId()
|
||||
|
||||
$: isListJob = flowJobIds != undefined && Array.isArray(flowJobIds?.flowJobs)
|
||||
|
||||
$: flowJobIds?.moduleId && onFlowJobFlowStatus()
|
||||
|
||||
function onFlowJobFlowStatus() {
|
||||
if (globalRefreshes) {
|
||||
let modId = flowJobIds?.moduleId
|
||||
if (modId) {
|
||||
globalRefreshes[buildSubflowKey(modId, prefix)] = async (loopJob) => {
|
||||
setIteration(loopJob.index, loopJob.job, false, modId ?? '')
|
||||
refresh(true, loopJob)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let forloop_selected = get(globalModuleStates?.[globalModuleStates?.length - 1])?.[flowJobIds?.moduleId ?? '']?.selectedForloop
|
||||
|
||||
let sub: Unsubscriber | undefined = undefined
|
||||
if (flowJobIds?.moduleId) {
|
||||
sub = globalModuleStates?.[globalModuleStates?.length - 1].subscribe((x) => {
|
||||
forloop_selected = x[flowJobIds?.moduleId ?? '']?.selectedForloop
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
onDestroy(() => {
|
||||
destroyed = true
|
||||
timeout && clearTimeout(timeout)
|
||||
sub?.()
|
||||
|
||||
})
|
||||
|
||||
$: selected = isListJob ? 'sequence' : 'graph'
|
||||
@@ -500,46 +484,44 @@
|
||||
}
|
||||
}
|
||||
|
||||
function setIteration(j: number, id: string, clicked: boolean, modId: string) {
|
||||
if (modId) {
|
||||
if (!$localModuleStates?.[modId]) {
|
||||
$localModuleStates[modId] = {
|
||||
type: 'InProgress',
|
||||
args: undefined
|
||||
}
|
||||
}
|
||||
let state = $localModuleStates?.[modId]
|
||||
|
||||
if (state) {
|
||||
if (state.selectedForloop == id && clicked) {
|
||||
setModuleState(
|
||||
modId,
|
||||
{
|
||||
selectedForloop: undefined,
|
||||
selectedForloopIndex: -1
|
||||
},
|
||||
false,
|
||||
true
|
||||
)
|
||||
} else {
|
||||
setModuleState(
|
||||
modId,
|
||||
{
|
||||
|
||||
|
||||
function setIteration(j: number, id: string, selectedManually: boolean | undefined, clicked: boolean, modId: string, isForloop: boolean) {
|
||||
if (modId) {
|
||||
globalModuleStates?.[globalModuleStates?.length - 1]?.update((topLevelModuleStates) => {
|
||||
let state = topLevelModuleStates?.[modId]
|
||||
if (state) {
|
||||
let manualOnce = state.selectedForLoopSetManually
|
||||
if (state?.selectedForloop == id && clicked) {
|
||||
topLevelModuleStates[modId] = {
|
||||
...(topLevelModuleStates[modId] ?? {}),
|
||||
selectedForloop: undefined,
|
||||
selectedForloopIndex: -1,
|
||||
selectedForLoopSetManually: false
|
||||
}
|
||||
} else if (clicked || (!manualOnce && (state == undefined || !isForloop || j >= (state.selectedForloopIndex ?? -1)) ) || selectedManually){
|
||||
let setManually = clicked || selectedManually || manualOnce
|
||||
topLevelModuleStates[modId] = {
|
||||
...(topLevelModuleStates[modId] ?? {}),
|
||||
selectedForloop: id,
|
||||
selectedForloopIndex: j
|
||||
},
|
||||
false,
|
||||
true
|
||||
)
|
||||
clicked && refresh(true, undefined)
|
||||
selectedForloopIndex: j,
|
||||
selectedForLoopSetManually: setManually ?? false
|
||||
}
|
||||
// clicked && callGlobRefresh(modId, {index: j, job: id, selectedManually: setManually ?? false})
|
||||
}
|
||||
}
|
||||
}
|
||||
return topLevelModuleStates
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
function innerJobLoaded(jobLoaded: Job, j: number, clicked: boolean, force: boolean) {
|
||||
let modId = flowJobIds?.moduleId
|
||||
if (modId) {
|
||||
setIteration(j, jobLoaded.id, clicked, modId)
|
||||
console.log(j, jobLoaded.id)
|
||||
setIteration(j, jobLoaded.id, clicked, clicked, modId, innerModule?.type == 'forloopflow' || innerModule?.type == 'whileloopflow')
|
||||
|
||||
if ($flowStateStore && $flowStateStore?.[modId] == undefined) {
|
||||
$flowStateStore[modId] = {
|
||||
@@ -572,48 +554,46 @@
|
||||
let job_id = jobLoaded.id
|
||||
initializeByJob(modId)
|
||||
|
||||
if (jobLoaded.type == 'QueuedJob') {
|
||||
if ($localModuleStates[modId]?.selectedForloopIndex == j) {
|
||||
setModuleState(
|
||||
modId,
|
||||
{
|
||||
started_at,
|
||||
logs: jobLoaded.logs,
|
||||
job_id,
|
||||
args: jobLoaded.args,
|
||||
flow_jobs: flowJobIds?.flowJobs,
|
||||
flow_jobs_success: flowJobIds?.flowJobsSuccess,
|
||||
iteration_total: flowJobIds?.length,
|
||||
duration_ms: undefined
|
||||
},
|
||||
force,
|
||||
true
|
||||
)
|
||||
let v: Partial<GraphModuleState> = {
|
||||
started_at,
|
||||
flow_jobs: flowJobIds?.flowJobs,
|
||||
flow_jobs_success: flowJobIds?.flowJobsSuccess,
|
||||
iteration_total: flowJobIds?.length,
|
||||
duration_ms: undefined
|
||||
}
|
||||
|
||||
let currentIndex = $localModuleStates[modId]?.selectedForloopIndex == j
|
||||
if (currentIndex) {
|
||||
v.logs = jobLoaded.logs
|
||||
v.args = jobLoaded.args
|
||||
v.job_id = jobLoaded.id
|
||||
}
|
||||
|
||||
if (jobLoaded.type == 'QueuedJob') {
|
||||
if (started_at && $localModuleStates[modId]?.type != 'InProgress') {
|
||||
v.type = 'InProgress'
|
||||
}
|
||||
} else if (jobLoaded.type == 'CompletedJob') {
|
||||
v.flow_jobs_results = jobResults
|
||||
if (currentIndex) {
|
||||
v.result = jobLoaded.result
|
||||
}
|
||||
}
|
||||
setModuleState(
|
||||
modId,
|
||||
v,
|
||||
force,
|
||||
true
|
||||
)
|
||||
|
||||
if (jobLoaded.type == 'QueuedJob') {
|
||||
|
||||
setDurationStatusByJob(modId, job_id, {
|
||||
created_at,
|
||||
started_at
|
||||
})
|
||||
} else if (jobLoaded.type == 'CompletedJob') {
|
||||
if ($localModuleStates[modId]?.selectedForloopIndex == j) {
|
||||
setModuleState(
|
||||
modId,
|
||||
{
|
||||
started_at,
|
||||
args: jobLoaded.args,
|
||||
result: jobLoaded.result,
|
||||
flow_jobs_results: jobResults,
|
||||
job_id,
|
||||
flow_jobs: flowJobIds?.flowJobs,
|
||||
flow_jobs_success: flowJobIds?.flowJobsSuccess,
|
||||
iteration_total: flowJobIds?.length,
|
||||
duration_ms: undefined,
|
||||
isListJob: true
|
||||
},
|
||||
force,
|
||||
true
|
||||
)
|
||||
}
|
||||
|
||||
setDurationStatusByJob(modId, job_id, {
|
||||
created_at,
|
||||
started_at,
|
||||
@@ -702,6 +682,8 @@
|
||||
|
||||
return rec(ids, undefined)
|
||||
}
|
||||
|
||||
|
||||
</script>
|
||||
|
||||
{#if notAnonynmous}
|
||||
@@ -826,8 +808,6 @@
|
||||
<div class="{selected != 'sequence' ? 'hidden' : ''} max-w-7xl mx-auto">
|
||||
{#if isListJob}
|
||||
{@const sliceFrom = $localDurationStatuses[flowJobIds?.moduleId ?? '']?.iteration_from ?? 0}
|
||||
{@const forloop_selected =
|
||||
$localModuleStates?.[flowJobIds?.moduleId ?? '']?.selectedForloop}
|
||||
<h3 class="text-md leading-6 font-bold text-tertiary border-b mb-4">
|
||||
Subflows ({flowJobIds?.flowJobs.length})
|
||||
</h3>
|
||||
@@ -865,20 +845,21 @@
|
||||
</Button>
|
||||
{/if}
|
||||
{#if j >= sliceFrom || forloop_selected == loopJobId}
|
||||
{@const forloopIsSelected = forloop_selected == loopJobId || (innerModule?.type != 'forloopflow' && innerModule?.type != 'whileloopflow')}
|
||||
<!-- <LogId id={loopJobId} /> -->
|
||||
<div class="border p-6" class:hidden={forloop_selected != loopJobId}>
|
||||
<svelte:self
|
||||
bind:refresh={recursiveRefresh[loopJobId]}
|
||||
{globalRefreshes}
|
||||
{childFlow}
|
||||
job={storedListJobs[j]}
|
||||
globalModuleStates={[localModuleStates, ...globalModuleStates]}
|
||||
initialJob={storedListJobs[j]}
|
||||
globalModuleStates={forloopIsSelected ? [localModuleStates, ...globalModuleStates] : []}
|
||||
globalDurationStatuses={[localDurationStatuses, ...globalDurationStatuses]}
|
||||
{prefix}
|
||||
{subflowParentsGlobalModuleStates}
|
||||
{subflowParentsDurationStatuses}
|
||||
render={forloop_selected == loopJobId && selected == 'sequence' && render}
|
||||
reducedPolling={flowJobIds?.flowJobs.length && flowJobIds?.flowJobs.length > 20}
|
||||
isForloopSelected={forloop_selected == loopJobId && (innerModule?.type == 'forloopflow' || innerModule?.type == 'whileloopflow')}
|
||||
reducedPolling={reducedPolling || (flowJobIds?.flowJobs.length && flowJobIds?.flowJobs.length > 20)}
|
||||
{workspaceId}
|
||||
jobId={loopJobId}
|
||||
on:jobsLoaded={(e) => {
|
||||
@@ -949,8 +930,6 @@
|
||||
<!-- <LogId id={loopJobId} /> -->
|
||||
<div class="border p-6" class:hidden={retry_selected != failedRetry}>
|
||||
<svelte:self
|
||||
{globalRefreshes}
|
||||
bind:refresh={recursiveRefresh[failedRetry]}
|
||||
{childFlow}
|
||||
globalModuleStates={[localModuleStates, ...globalModuleStates]}
|
||||
globalDurationStatuses={[localDurationStatuses, ...globalDurationStatuses]}
|
||||
@@ -958,7 +937,7 @@
|
||||
{subflowParentsGlobalModuleStates}
|
||||
{subflowParentsDurationStatuses}
|
||||
render={failedRetry == retry_selected && render}
|
||||
reducedPolling={false}
|
||||
{reducedPolling}
|
||||
{workspaceId}
|
||||
jobId={failedRetry}
|
||||
/>
|
||||
@@ -968,8 +947,6 @@
|
||||
{#if ['InProgress', 'Success', 'Failure'].includes(mod.type)}
|
||||
{#if job.raw_flow?.modules[i]?.value.type == 'flow'}
|
||||
<svelte:self
|
||||
{globalRefreshes}
|
||||
bind:refresh={recursiveRefresh[mod.job ?? '']}
|
||||
globalModuleStates={[]}
|
||||
globalDurationStatuses={[]}
|
||||
prefix={buildPrefix(prefix, mod.id ?? '')}
|
||||
@@ -986,6 +963,7 @@
|
||||
render={selected == 'sequence' && render}
|
||||
{workspaceId}
|
||||
jobId={mod.job}
|
||||
{reducedPolling}
|
||||
childFlow
|
||||
on:jobsLoaded={(e) => {
|
||||
let { force, job } = e.detail
|
||||
@@ -996,8 +974,7 @@
|
||||
<div class="text-secondary">no subflow (empty loop?)</div>
|
||||
{:else}
|
||||
<svelte:self
|
||||
{globalRefreshes}
|
||||
bind:refresh={recursiveRefresh[mod.job ?? '']}
|
||||
|
||||
{childFlow}
|
||||
globalModuleStates={[localModuleStates, ...globalModuleStates]}
|
||||
globalDurationStatuses={[localDurationStatuses, ...globalDurationStatuses]}
|
||||
@@ -1007,6 +984,7 @@
|
||||
{subflowParentsGlobalModuleStates}
|
||||
{subflowParentsDurationStatuses}
|
||||
jobId={mod.job}
|
||||
{reducedPolling}
|
||||
innerModule={mod.flow_jobs ? job.raw_flow?.modules[i]?.value : undefined}
|
||||
flowJobIds={mod.flow_jobs
|
||||
? {
|
||||
@@ -1090,13 +1068,21 @@
|
||||
}}
|
||||
on:selectedIteration={(e) => {
|
||||
let detail = e.detail
|
||||
if (detail.manuallySet) {
|
||||
$localModuleStates[detail.moduleId] = {
|
||||
...$localModuleStates[detail.moduleId],
|
||||
selectedForloop: detail.id,
|
||||
selectedForloopIndex: detail.index,
|
||||
selectedForLoopSetManually: true
|
||||
}
|
||||
|
||||
setModuleState(detail.moduleId, {
|
||||
selectedForloop: detail.id,
|
||||
selectedForloopIndex: detail.index
|
||||
})
|
||||
globalRefreshes[detail.moduleId]?.({ job: detail.id, index: detail.index })
|
||||
// console.log('selectedIteration', prefix, detail.moduleId, globalRefreshes)
|
||||
|
||||
} else {
|
||||
$localModuleStates[detail.moduleId] = {
|
||||
...$localModuleStates[detail.moduleId],
|
||||
selectedForLoopSetManually: false
|
||||
}
|
||||
}
|
||||
}}
|
||||
modules={job.raw_flow?.modules ?? []}
|
||||
failureModule={job.raw_flow?.failure_module}
|
||||
|
||||
@@ -16,13 +16,10 @@
|
||||
import { offset, flip, shift } from 'svelte-floating-ui/dom'
|
||||
import ResolveStyle from '../helpers/ResolveStyle.svelte'
|
||||
import MultiSelect from '$lib/components/multiselect/MultiSelect.svelte'
|
||||
import type { ObjectOption } from '../../../multiselect/types'
|
||||
import { parseConfigOptions } from './utils'
|
||||
import { deepEqual } from 'fast-equals'
|
||||
|
||||
export let id: string
|
||||
export let configuration: RichConfigurations
|
||||
export let customCss: ComponentCustomCSS<'multiselectcomponent'> | undefined = undefined
|
||||
export let id: string
|
||||
export let render: boolean
|
||||
export let verticalAlignment: 'top' | 'center' | 'bottom' | undefined = undefined
|
||||
|
||||
@@ -33,83 +30,49 @@
|
||||
|
||||
const { app, worldStore, selectedComponent, componentControl } =
|
||||
getContext<AppViewerContext>('AppViewerContext')
|
||||
let items: string[]
|
||||
|
||||
const resolvedConfig = initConfig(
|
||||
components['multiselectcomponentv2'].initialData.configuration,
|
||||
components['multiselectcomponent'].initialData.configuration,
|
||||
configuration
|
||||
)
|
||||
const outputs = initOutput($worldStore, id, { result: [] as any[] })
|
||||
let options: ObjectOption[] = []
|
||||
let selectedOptions: ObjectOption[] = [...new Set(findOptionsByValue(outputs?.result.peak()))]
|
||||
|
||||
let css = initCss($app.css?.multiselectcomponent, customCss)
|
||||
let outerDiv: HTMLDivElement | undefined = undefined
|
||||
let portalRef: HTMLDivElement | undefined = undefined
|
||||
let w = 0
|
||||
let open: boolean = false
|
||||
const outputs = initOutput($worldStore, id, {
|
||||
result: [] as string[]
|
||||
})
|
||||
|
||||
$: if (resolvedConfig.items) {
|
||||
options = parseConfigOptions(resolvedConfig.items)
|
||||
}
|
||||
let value: string[] | undefined = [...new Set(outputs?.result.peak())] as string[]
|
||||
|
||||
$componentControl[id] = {
|
||||
setValue(newValues: any[]) {
|
||||
selectOptionsByValue(newValues)
|
||||
setValue(nvalue: string[]) {
|
||||
value = [...new Set(nvalue)]
|
||||
outputs?.result.set([...(value ?? [])])
|
||||
}
|
||||
}
|
||||
|
||||
$: if (resolvedConfig.defaultItems) {
|
||||
selectOptionsByValue(resolvedConfig.defaultItems)
|
||||
}
|
||||
$: resolvedConfig.items && handleItems()
|
||||
|
||||
$: outerDiv &&
|
||||
portalRef &&
|
||||
css?.multiselect?.style &&
|
||||
setOuterDivStyle(outerDiv, portalRef, css?.multiselect?.style)
|
||||
|
||||
$: if (render && portalRef && outerDiv && options?.length > 0) {
|
||||
tick().then(() => {
|
||||
moveOptionsToPortal()
|
||||
})
|
||||
}
|
||||
|
||||
function findOptionsByValue(values: any[]) {
|
||||
if (!Array.isArray(values)) {
|
||||
return []
|
||||
function handleItems() {
|
||||
if (Array.isArray(resolvedConfig.items)) {
|
||||
items = resolvedConfig.items?.map((label) => {
|
||||
return typeof label === 'string' ? label : `NOT_STRING`
|
||||
})
|
||||
}
|
||||
return options?.filter((item) => {
|
||||
let itemValue = item.value
|
||||
try {
|
||||
if (typeof itemValue == 'string') {
|
||||
itemValue = JSON.parse(itemValue)
|
||||
}
|
||||
} catch (_) {}
|
||||
return values.some((value) => deepEqual(itemValue, value))
|
||||
}) as ObjectOption[]
|
||||
}
|
||||
|
||||
function selectOptionsByValue(values: any[]) {
|
||||
if (!Array.isArray(values)) {
|
||||
outputs?.result.set([])
|
||||
$: resolvedConfig.defaultItems && handleDefaultItems()
|
||||
|
||||
function handleDefaultItems() {
|
||||
if (Array.isArray(resolvedConfig.defaultItems)) {
|
||||
const nvalue = resolvedConfig.defaultItems?.map((label) => {
|
||||
return typeof label === 'string' ? label : `NOT_STRING`
|
||||
})
|
||||
value = [...new Set(nvalue)]
|
||||
outputs?.result.set([...(value ?? [])])
|
||||
}
|
||||
|
||||
selectedOptions = findOptionsByValue(values)
|
||||
outputs?.result.set([...toValues(selectedOptions)])
|
||||
}
|
||||
|
||||
function toValues(options: ObjectOption[] | undefined) {
|
||||
if (!options || !Array.isArray(options)) {
|
||||
return []
|
||||
}
|
||||
|
||||
return options.map((o) => {
|
||||
let val = o.value
|
||||
try {
|
||||
if (typeof val === 'string') val = JSON.parse(val)
|
||||
} catch (_) {}
|
||||
return val
|
||||
})
|
||||
}
|
||||
let css = initCss($app.css?.multiselectcomponent, customCss)
|
||||
|
||||
function setOuterDivStyle(outerDiv: HTMLDivElement, portalRef: HTMLDivElement, style: string) {
|
||||
outerDiv.setAttribute('style', style)
|
||||
@@ -118,6 +81,14 @@
|
||||
ul?.setAttribute('style', extractCustomProperties(style))
|
||||
}
|
||||
|
||||
$: outerDiv &&
|
||||
portalRef &&
|
||||
css?.multiselect?.style &&
|
||||
setOuterDivStyle(outerDiv, portalRef, css?.multiselect?.style)
|
||||
|
||||
let outerDiv: HTMLDivElement | undefined = undefined
|
||||
let portalRef: HTMLDivElement | undefined = undefined
|
||||
|
||||
function moveOptionsToPortal() {
|
||||
// Find ul element with class 'options' within the outerDiv
|
||||
const ul = outerDiv?.querySelector('.options')
|
||||
@@ -127,6 +98,14 @@
|
||||
portalRef?.appendChild(ul)
|
||||
}
|
||||
}
|
||||
|
||||
$: if (render && portalRef && outerDiv && items?.length > 0) {
|
||||
tick().then(() => {
|
||||
moveOptionsToPortal()
|
||||
})
|
||||
}
|
||||
let w = 0
|
||||
let open: boolean = false
|
||||
</script>
|
||||
|
||||
{#each Object.keys(components['multiselectcomponent'].initialData.configuration) as key (key)}
|
||||
@@ -149,6 +128,7 @@
|
||||
{/each}
|
||||
|
||||
<InitializeComponent {id} />
|
||||
|
||||
<AlignWrapper {render} hFull {verticalAlignment}>
|
||||
<div
|
||||
class="w-full app-editor-input"
|
||||
@@ -163,7 +143,7 @@
|
||||
use:floatingRef
|
||||
bind:clientWidth={w}
|
||||
>
|
||||
{#if !selectedOptions || Array.isArray(selectedOptions)}
|
||||
{#if !value || Array.isArray(value)}
|
||||
<MultiSelect
|
||||
bind:outerDiv
|
||||
outerDivClass={`${resolvedConfig.allowOverflow ? '' : 'h-full'}`}
|
||||
@@ -171,19 +151,15 @@
|
||||
--sms-border={'none'}
|
||||
--sms-min-height={'32px'}
|
||||
--sms-focus-border={'none'}
|
||||
bind:selected={selectedOptions}
|
||||
{options}
|
||||
bind:selected={value}
|
||||
options={items}
|
||||
placeholder={resolvedConfig.placeholder}
|
||||
allowUserOptions={resolvedConfig.create}
|
||||
key={(option) => {
|
||||
if (typeof option == 'string' || typeof option == 'number') return option
|
||||
return option.value
|
||||
}}
|
||||
on:change={(event) => {
|
||||
if (event?.detail?.type === 'removeAll') {
|
||||
outputs?.result.set([])
|
||||
} else {
|
||||
outputs?.result.set([...toValues(selectedOptions)])
|
||||
outputs?.result.set([...(value ?? [])])
|
||||
}
|
||||
}}
|
||||
on:open={() => {
|
||||
@@ -205,10 +181,9 @@
|
||||
e.target?.['parentElement']?.dispatchEvent(newe)
|
||||
}}
|
||||
>
|
||||
{@html option.label === '' ? ` ` : option.label}
|
||||
{option}
|
||||
</div>
|
||||
</MultiSelect>
|
||||
|
||||
<Portal name="app-multiselect-v2">
|
||||
<div use:floatingContent class="z5000" hidden={!open}>
|
||||
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
||||
@@ -222,7 +197,7 @@
|
||||
</div>
|
||||
</Portal>
|
||||
{:else}
|
||||
Value {selectedOptions} is not an array
|
||||
Value {value} is not an array
|
||||
{/if}
|
||||
</div>
|
||||
</AlignWrapper>
|
||||
|
||||
@@ -1,48 +0,0 @@
|
||||
import {
|
||||
type ObjectOption,
|
||||
isObjectOptionArray,
|
||||
isStringArray
|
||||
} from '$lib/components/multiselect/types'
|
||||
import type { StaticAppInput } from '../../inputType'
|
||||
|
||||
export function parseConfigOptions(resolvedConfigItems: StaticAppInput) {
|
||||
if (!resolvedConfigItems) {
|
||||
return []
|
||||
}
|
||||
if (isObjectOptionArray(resolvedConfigItems)) {
|
||||
return parseLabeledItems(resolvedConfigItems)
|
||||
} else if (isStringArray(resolvedConfigItems)) {
|
||||
return parseStringItems(resolvedConfigItems)
|
||||
}
|
||||
return []
|
||||
}
|
||||
export function parseLabeledItems(resolvedConfigItems: ObjectOption[]) {
|
||||
return resolvedConfigItems?.map((item: ObjectOption) => {
|
||||
if (!item || typeof item !== 'object') {
|
||||
console.error(
|
||||
'When labeled, MultiSelect component items should be an array of { label: string, value: string }.'
|
||||
)
|
||||
return {
|
||||
label: 'not object',
|
||||
value: 'not object'
|
||||
}
|
||||
}
|
||||
return {
|
||||
label: item?.label ?? 'undefined',
|
||||
value:
|
||||
typeof item?.value === 'object' ? JSON.stringify(item.value) : item?.value ?? 'undefined'
|
||||
}
|
||||
})
|
||||
}
|
||||
export function parseStringItems(resolvedConfigItems: string[]): ObjectOption[] {
|
||||
return resolvedConfigItems?.map((option: string) => {
|
||||
if (option === null || option === undefined || typeof option !== 'string') {
|
||||
console.error('When not labeled, MultiSelect component items should be an array of strings.')
|
||||
return { label: 'not string', value: 'not string' }
|
||||
}
|
||||
if (option === '') {
|
||||
return { label: 'empty string', value: 'empty string' }
|
||||
}
|
||||
return { label: option, value: option }
|
||||
})
|
||||
}
|
||||
@@ -2079,8 +2079,7 @@ This is a paragraph.
|
||||
value: [
|
||||
{ value: 'foo', label: 'Foo' },
|
||||
{ value: 'bar', label: 'Bar' }
|
||||
],
|
||||
hasLabeledMode: true
|
||||
]
|
||||
} as StaticAppInput,
|
||||
create: {
|
||||
type: 'static',
|
||||
@@ -2200,15 +2199,14 @@ This is a paragraph.
|
||||
items: {
|
||||
type: 'static',
|
||||
fieldType: 'array',
|
||||
subFieldType: 'simplestringselect',
|
||||
value: ['Foo', 'Bar'],
|
||||
hasLabeledMode: true
|
||||
subFieldType: 'text',
|
||||
value: ['Foo', 'Bar']
|
||||
} as StaticAppInput,
|
||||
defaultItems: {
|
||||
type: 'static',
|
||||
fieldType: 'array',
|
||||
subFieldType: 'simplestringselect',
|
||||
value: undefined
|
||||
subFieldType: 'text',
|
||||
value: []
|
||||
} as StaticAppInput,
|
||||
placeholder: {
|
||||
type: 'static',
|
||||
|
||||
@@ -9,30 +9,15 @@
|
||||
import Toggle from '$lib/components/Toggle.svelte'
|
||||
import QuickAddColumn from './QuickAddColumn.svelte'
|
||||
import RefreshDatabaseStudioTable from './RefreshDatabaseStudioTable.svelte'
|
||||
import { isObjectOptionArray, isStringArray } from '$lib/components/multiselect/types'
|
||||
|
||||
export let componentInput: StaticInput<any[]> & {
|
||||
loading?: boolean
|
||||
expr?: string
|
||||
isLabeled?: boolean
|
||||
}
|
||||
export let componentInput: StaticInput<any[]> & { loading?: boolean }
|
||||
export let subFieldType: InputType | undefined = undefined
|
||||
export let selectOptions: StaticOptions['selectOptions'] | undefined = undefined
|
||||
export let id: string | undefined
|
||||
export let hasLabeledMode: boolean = false
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
const flipDurationMs = 200
|
||||
|
||||
let items = getItems(componentInput)
|
||||
let raw: boolean = false
|
||||
let labeled: boolean = subFieldType === 'labeledselect'
|
||||
|
||||
$: subFieldType === 'db-explorer' && clearTableOnComponentReset(componentInput?.value)
|
||||
|
||||
$: labeled !== undefined && handleLabeledChange()
|
||||
$: items !== undefined && handleItemsChange()
|
||||
|
||||
function addElementByType() {
|
||||
if (!Array.isArray(componentInput.value)) {
|
||||
componentInput.value = []
|
||||
@@ -47,13 +32,12 @@
|
||||
} else if (subFieldType === 'object') {
|
||||
value.push({})
|
||||
} else if (subFieldType === 'labeledresource' || subFieldType === 'labeledselect') {
|
||||
value.push({ label: 'label', value: 'value' })
|
||||
value.push({ value: 'value', label: 'label' })
|
||||
} else if (subFieldType === 'tab-select') {
|
||||
value.push({ id: '', index: 0 })
|
||||
} else if (
|
||||
subFieldType === 'text' ||
|
||||
subFieldType === 'textarea' ||
|
||||
subFieldType === 'simplestringselect' ||
|
||||
// TODO: Add support for these types
|
||||
subFieldType === 'date' ||
|
||||
subFieldType === 'time' ||
|
||||
@@ -209,6 +193,8 @@
|
||||
componentInput.value = reorderedValues
|
||||
}
|
||||
|
||||
let items = getItems(componentInput)
|
||||
|
||||
function getItems(componentInput: StaticInput<any[]> & { loading?: boolean }) {
|
||||
return (Array.isArray(componentInput.value) ? componentInput.value : [])
|
||||
.filter((x) => x != undefined)
|
||||
@@ -223,30 +209,15 @@
|
||||
}
|
||||
}
|
||||
|
||||
$: subFieldType === 'db-explorer' && clearTableOnComponentReset(componentInput?.value)
|
||||
|
||||
$: items != undefined && handleItemsChange()
|
||||
|
||||
function handleItemsChange() {
|
||||
const newComponentInput = { ...componentInput }
|
||||
newComponentInput.value = items.map((item) => item.value).filter((item) => item != undefined)
|
||||
newComponentInput.expr = JSON.stringify(newComponentInput.value)
|
||||
newComponentInput.isLabeled = labeled
|
||||
|
||||
dispatch('componentInputChange', newComponentInput)
|
||||
}
|
||||
|
||||
/** Transforms items string[] to ObjectOption] and vice versa. Mutating items will fire handleItemsChange. */
|
||||
function handleLabeledChange() {
|
||||
if (labeled && isStringArray(items.map((item) => item.value))) {
|
||||
for (let i = 0; i < items.length; i++) {
|
||||
const itemValue = items[i].value
|
||||
items[i].value = { label: itemValue, value: itemValue }
|
||||
}
|
||||
} else if (!labeled && isObjectOptionArray(items.map((item) => item.value))) {
|
||||
for (let i = 0; i < items.length; i++) {
|
||||
const itemValue = items[i].value
|
||||
items[i].value = itemValue.label
|
||||
}
|
||||
}
|
||||
componentInput.value = items.map((item) => item.value).filter((item) => item != undefined)
|
||||
}
|
||||
|
||||
let raw: boolean = false
|
||||
// let mounted = false
|
||||
|
||||
// $: if (componentInput.value && mounted) {
|
||||
@@ -272,6 +243,7 @@
|
||||
{#if Array.isArray(items) && componentInput.value}
|
||||
<div class="flex flex-row items-center justify-between">
|
||||
<div class="text-xs text-tertiary font-semibold">{pluralize(items.length, 'item')}</div>
|
||||
|
||||
{#if subFieldType === 'ag-grid' || subFieldType === 'table-column'}
|
||||
<Toggle
|
||||
options={{
|
||||
@@ -281,16 +253,6 @@
|
||||
bind:checked={raw}
|
||||
/>
|
||||
{/if}
|
||||
{#if hasLabeledMode}
|
||||
<Toggle
|
||||
options={{
|
||||
right: 'w/ label'
|
||||
}}
|
||||
size="xs"
|
||||
bind:checked={labeled}
|
||||
on:change={handleLabeledChange}
|
||||
/>
|
||||
{/if}
|
||||
</div>
|
||||
<section
|
||||
use:dragHandleZone={{
|
||||
@@ -309,7 +271,7 @@
|
||||
<div class="grow min-w-0">
|
||||
<SubTypeEditor
|
||||
{id}
|
||||
subFieldType={raw ? 'object' : labeled ? 'labeledselect' : subFieldType}
|
||||
subFieldType={raw ? 'object' : subFieldType}
|
||||
bind:componentInput
|
||||
bind:value={item.value}
|
||||
on:remove={() => deleteElementByType(index)}
|
||||
|
||||
@@ -43,7 +43,6 @@
|
||||
export let documentationLink: string | undefined = undefined
|
||||
export let markdownTooltip: string | undefined = undefined
|
||||
export let securedContext = false
|
||||
export let hasLabeledMode = false
|
||||
|
||||
const { connectingInput, app } = getContext<AppViewerContext>('AppViewerContext')
|
||||
|
||||
@@ -172,7 +171,6 @@
|
||||
{selectOptions}
|
||||
{format}
|
||||
{placeholder}
|
||||
{hasLabeledMode}
|
||||
bind:componentInput
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -81,7 +81,6 @@
|
||||
{recomputeOnInputChanged}
|
||||
{showOnDemandOnlyToggle}
|
||||
{securedContext}
|
||||
hasLabeledMode={meta?.['hasLabeledMode']}
|
||||
/>
|
||||
{#if deletable}
|
||||
<div class="flex flex-row-reverse -mt-4">
|
||||
|
||||
@@ -30,9 +30,6 @@
|
||||
editor?.setCode(code)
|
||||
}
|
||||
|
||||
let fullscreen = false
|
||||
let focus = false
|
||||
|
||||
$: extraLib =
|
||||
componentInput?.expr && $worldStore
|
||||
? buildExtraLib($worldStore?.outputsById ?? {}, acceptSelf ? '' : id, $state, false)
|
||||
@@ -55,6 +52,9 @@
|
||||
inferDeps(code, $worldStore.outputsById, componentInput, app)
|
||||
}
|
||||
}
|
||||
|
||||
let fullscreen = false
|
||||
let focus = false
|
||||
</script>
|
||||
|
||||
{#if componentInput?.type === 'evalv2'}
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
import ArrayStaticInputEditor from '../ArrayStaticInputEditor.svelte'
|
||||
import ResourcePicker from '$lib/components/ResourcePicker.svelte'
|
||||
import JsonEditor from './JsonEditor.svelte'
|
||||
import { getContext, onMount } from 'svelte'
|
||||
import { getContext } from 'svelte'
|
||||
import type { AppViewerContext } from '$lib/components/apps/types'
|
||||
import IconSelectInput from './IconSelectInput.svelte'
|
||||
import ColorInput from './ColorInput.svelte'
|
||||
@@ -24,49 +24,17 @@
|
||||
import EditableSchemaDrawer from '$lib/components/schema/EditableSchemaDrawer.svelte'
|
||||
import AppPicker from '$lib/components/wizards/AppPicker.svelte'
|
||||
|
||||
export let componentInput: (StaticInput<any> & { isLabeled?: boolean }) | undefined
|
||||
export let componentInput: StaticInput<any> | undefined
|
||||
export let fieldType: InputType | undefined = undefined
|
||||
export let subFieldType: InputType | undefined = undefined
|
||||
export let selectOptions: StaticOptions['selectOptions'] | undefined = undefined
|
||||
export let placeholder: string | undefined = undefined
|
||||
export let format: string | undefined = undefined
|
||||
export let id: string | undefined
|
||||
export let hasLabeledMode: boolean = false
|
||||
|
||||
const { onchange } = getContext<AppViewerContext>('AppViewerContext')
|
||||
|
||||
$: componentInput && onchange?.()
|
||||
|
||||
onMount(() => {
|
||||
/** preserve the `labeled` toggling when switching from eval to static */
|
||||
if (componentInput !== undefined) {
|
||||
handleComponentInputChange(componentInput)
|
||||
}
|
||||
})
|
||||
/**
|
||||
* Every components: switching from `static` to `eval` preserves the user data
|
||||
*
|
||||
* Multiselect: toggling `labeled` needs to mutate input value and input fieldType.
|
||||
* */
|
||||
function handleComponentInputChange(newComponentInput: StaticInput<any>) {
|
||||
componentInput = { ...componentInput, ...newComponentInput }
|
||||
|
||||
if (componentInput?.isLabeled) {
|
||||
if (fieldType === 'array' && subFieldType === 'simplestringselect') {
|
||||
subFieldType = 'labeledselect'
|
||||
}
|
||||
if (fieldType === 'simplestringselect') {
|
||||
fieldType = 'labeledselect'
|
||||
}
|
||||
} else if (componentInput?.isLabeled === false) {
|
||||
if (fieldType === 'array' && subFieldType === 'labeledselect') {
|
||||
subFieldType = 'simplestringselect'
|
||||
}
|
||||
if (fieldType === 'labeledselect') {
|
||||
fieldType = 'simplestringselect'
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
{#key subFieldType}
|
||||
@@ -165,7 +133,7 @@
|
||||
{/if}
|
||||
{:else if fieldType === 'color'}
|
||||
<ColorInput bind:value={componentInput.value} />
|
||||
{:else if fieldType === 'object' || fieldType == 'labeledselect' || fieldType === 'simplestringselect'}
|
||||
{:else if fieldType === 'object' || fieldType == 'labeledselect'}
|
||||
{#if format?.startsWith('resource-') && (componentInput.value == undefined || typeof componentInput.value == 'string')}
|
||||
<ResourcePicker
|
||||
initialValue={componentInput.value?.split('$res:')?.[1] || ''}
|
||||
@@ -194,14 +162,7 @@
|
||||
</div>
|
||||
{/if}
|
||||
{:else if fieldType === 'array'}
|
||||
<ArrayStaticInputEditor
|
||||
{id}
|
||||
{subFieldType}
|
||||
bind:componentInput
|
||||
on:deleteArrayItem
|
||||
on:componentInputChange={(e) => handleComponentInputChange(e.detail)}
|
||||
{hasLabeledMode}
|
||||
/>
|
||||
<ArrayStaticInputEditor {id} {subFieldType} bind:componentInput on:deleteArrayItem />
|
||||
{:else if fieldType === 'schema'}
|
||||
<div class="w-full">
|
||||
<EditableSchemaDrawer bind:schema={componentInput.value} />
|
||||
|
||||
@@ -20,7 +20,6 @@ export type InputType =
|
||||
| 'any'
|
||||
| 'labeledresource'
|
||||
| 'labeledselect'
|
||||
| 'simplestringselect'
|
||||
| 'tab-select'
|
||||
| 'schema'
|
||||
| 'ag-grid'
|
||||
@@ -176,7 +175,6 @@ type InputConfiguration<T extends InputType, V extends InputType> = {
|
||||
noStatic?: boolean
|
||||
onDemandOnly?: boolean
|
||||
hideRefreshButton?: boolean
|
||||
isLabeled?: boolean
|
||||
}
|
||||
|
||||
export type StaticOptions = {
|
||||
@@ -209,7 +207,6 @@ export type AppInput =
|
||||
| (AppInputSpec<'array', string[], 'select'> & StaticOptions)
|
||||
| AppInputSpec<'array', object[], 'labeledresource'>
|
||||
| AppInputSpec<'array', object[], 'labeledselect'>
|
||||
| AppInputSpec<'array', string[], 'simplestringselect'>
|
||||
| AppInputSpec<'labeledselect', object>
|
||||
| AppInputSpec<'labeledresource', object>
|
||||
| AppInputSpec<'array', object[], 'tab-select'>
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
})
|
||||
</script>
|
||||
|
||||
<Menu let:open as="div" class="relative hover:z-50 flex w-full h-8">
|
||||
<Menu let:open as="div" class="relative hover:z-50 flex w-full h-8">
|
||||
<ResolveOpen {open} on:open on:close />
|
||||
<div use:floatingRef class="w-full">
|
||||
<MenuButton class={twMerge('w-full', justifyEnd ? 'flex justify-end' : '')}>
|
||||
|
||||
@@ -217,7 +217,7 @@
|
||||
{#if ['script', 'trigger', 'approval', 'preprocessor', 'failure'].includes(selectedKind)}
|
||||
{#if (preFilter === 'all' && owners.length > 0) || preFilter === 'workspace'}
|
||||
{#if preFilter !== 'workspace'}
|
||||
<div class="pb-0 text-2xs font-light text-secondary ml-2">Workspace Folders</div>
|
||||
<div class="pb-0 text-2xs font-light text-secondary ml-2">Folders</div>
|
||||
{/if}
|
||||
|
||||
{#if owners.length > 0}
|
||||
|
||||
@@ -1,15 +1,16 @@
|
||||
<script lang="ts">
|
||||
import Menu from '$lib/components/common/menu/MenuV2.svelte'
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
import { ListFilter } from 'lucide-svelte'
|
||||
import { ListFilter, Lock, LockOpen } from 'lucide-svelte'
|
||||
import { MenuItem } from '@rgossiaux/svelte-headlessui'
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
|
||||
export let index: number
|
||||
export let id: string
|
||||
export let flowJobs: string[] | undefined
|
||||
export let flowJobsSuccess: (boolean | undefined)[] | undefined
|
||||
export let selected: number
|
||||
export let selectedManually: boolean | undefined
|
||||
|
||||
let filter: number | undefined = undefined
|
||||
function onKeydown(event: KeyboardEvent) {
|
||||
@@ -21,16 +22,34 @@
|
||||
filter > 0
|
||||
) {
|
||||
event.preventDefault()
|
||||
dispatch('selectedIteration', { index: filter - 1, id: flowJobs[filter - 1] })
|
||||
dispatch('selectedIteration', { index: filter - 1, id: flowJobs[filter - 1], manuallySet: true })
|
||||
}
|
||||
}
|
||||
|
||||
let buttonHover = false
|
||||
</script>
|
||||
|
||||
<Menu maxHeight={300}>
|
||||
{#if selectedManually}
|
||||
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
||||
<!-- svelte-ignore a11y-no-static-element-interactions -->
|
||||
<div title="Iteration picked from new subflow runs automatically" on:mouseenter={() => (buttonHover = true)} on:mouseleave={() => (buttonHover = false)} on:click={(e) => {
|
||||
buttonHover = false
|
||||
dispatch('selectedIteration', { manuallySet: false })}
|
||||
}
|
||||
class="absolute top-1.5 right-12 cursor-pointer">
|
||||
{#if buttonHover}
|
||||
<LockOpen class="text-primary" size={14} />
|
||||
{:else}
|
||||
<Lock class="text-primary" size={12} />
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<Menu maxHeight={300}>
|
||||
<div slot="trigger">
|
||||
<button
|
||||
title="Pick an iteration"
|
||||
id={`flow-editor-iteration picker-${index}`}
|
||||
id={`flow-editor-iteration picker-${id}`}
|
||||
type="button"
|
||||
class=" text-xs bg-surface border-[1px] border-gray-300 dark:border-gray-500 focus:outline-none
|
||||
hover:bg-surface-hover focus:ring-4 focus:ring-surface-selected font-medium rounded-sm w-[40px] gap-1 h-[20px]
|
||||
@@ -42,6 +61,7 @@
|
||||
<ListFilter size={15} />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<MenuItem disabled>
|
||||
<input type="number" bind:value={filter} on:keydown={onKeydown} />
|
||||
</MenuItem>
|
||||
@@ -56,7 +76,7 @@
|
||||
: ''}"
|
||||
on:pointerdown={() => {
|
||||
//close()
|
||||
dispatch('selectedIteration', { index: idx, id })
|
||||
dispatch('selectedIteration', { index: idx, id, manuallySet: true })
|
||||
}}
|
||||
role="menuitem"
|
||||
tabindex="-1"
|
||||
|
||||
@@ -17,16 +17,14 @@
|
||||
export let insertable: boolean
|
||||
export let annotation: string | undefined = undefined
|
||||
export let bgColor: string = ''
|
||||
export let modules: FlowModule[]
|
||||
export let moving: string | undefined = undefined
|
||||
export let duration_ms: number | undefined = undefined
|
||||
|
||||
export let retries: number | undefined = undefined
|
||||
export let flowJobs:
|
||||
| { flowJobs: string[]; selected: number; flowJobsSuccess: (boolean | undefined)[] }
|
||||
| { flowJobs: string[]; selected: number; flowJobsSuccess: (boolean | undefined)[], selectedManually: boolean | undefined }
|
||||
| undefined
|
||||
|
||||
$: idx = modules.findIndex((m) => m.id === mod.id)
|
||||
|
||||
const { selectedId } = getContext<{ selectedId: Writable<string> }>('FlowGraphContext')
|
||||
const dispatch = createEventDispatcher<{
|
||||
@@ -83,13 +81,14 @@
|
||||
{#if flowJobs && !insertable && (mod.value.type === 'forloopflow' || mod.value.type === 'whileloopflow')}
|
||||
<div class="absolute right-8 z-50 -top-5">
|
||||
<FlowJobsMenu
|
||||
id={mod.id}
|
||||
on:selectedIteration={(e) => {
|
||||
dispatch('selectedIteration', e.detail)
|
||||
}}
|
||||
flowJobsSuccess={flowJobs.flowJobsSuccess}
|
||||
flowJobs={flowJobs.flowJobs}
|
||||
selected={flowJobs.selected}
|
||||
index={idx}
|
||||
selectedManually={flowJobs.selectedManually}
|
||||
/>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
@@ -53,6 +53,7 @@ export type GraphModuleState = {
|
||||
parent_module?: string
|
||||
selectedForloop?: string
|
||||
selectedForloopIndex?: number
|
||||
selectedForLoopSetManually?: boolean
|
||||
flow_jobs_success?: (boolean | undefined)[]
|
||||
flow_jobs?: string[]
|
||||
iteration_total?: number
|
||||
|
||||
@@ -26,6 +26,7 @@
|
||||
'branchone',
|
||||
data.flowModuleStates?.[data.id]
|
||||
)
|
||||
|
||||
</script>
|
||||
|
||||
<NodeWrapper let:darkMode offset={data.offset}>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<script lang="ts">
|
||||
import VirtualItem from '$lib/components/flows/map/VirtualItem.svelte'
|
||||
import NodeWrapper from './NodeWrapper.svelte'
|
||||
import type { FlowModule } from '$lib/gen'
|
||||
import type { FlowModule, FlowStatusModule } from '$lib/gen'
|
||||
import { getStateColor } from '../../util'
|
||||
import type { GraphModuleState } from '../../model'
|
||||
import type { GraphEventHandlers } from '../../graphBuilder'
|
||||
@@ -26,8 +26,18 @@
|
||||
if (!inputJson || typeof inputJson !== 'object' || !inputJson.iter) return {}
|
||||
return { iter: inputJson.iter }
|
||||
}
|
||||
|
||||
function computeStatus(state: GraphModuleState | undefined): FlowStatusModule["type"] | undefined {
|
||||
if (state?.type == 'InProgress' || state?.type == 'Success' || state?.type == 'Failure') {
|
||||
let r = state?.flow_jobs_success?.[state?.selectedForloopIndex ?? 0]
|
||||
if (r == undefined) return 'InProgress'
|
||||
return r ? 'Success' : 'InProgress'
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
|
||||
|
||||
<NodeWrapper let:darkMode offset={data.offset}>
|
||||
<VirtualItem
|
||||
label={data.simplifiedTriggerView ? 'For each new event' : 'Do one iteration'}
|
||||
@@ -36,7 +46,7 @@
|
||||
id={data.id}
|
||||
hideId
|
||||
bgColor={getStateColor(undefined, darkMode)}
|
||||
borderColor={getStateColor(data.flowModuleStates?.[data.id]?.type, darkMode)}
|
||||
borderColor={getStateColor(computeStatus(data.flowModuleStates?.[data.id]), darkMode)}
|
||||
on:select={(e) => {
|
||||
data?.eventHandlers?.select(e.detail)
|
||||
}}
|
||||
|
||||
@@ -38,13 +38,15 @@
|
||||
? {
|
||||
flowJobs: state?.flow_jobs,
|
||||
selected: state?.selectedForloopIndex ?? -1,
|
||||
selectedManually: state?.selectedForLoopSetManually,
|
||||
flowJobsSuccess: state?.flow_jobs_success
|
||||
}
|
||||
: (undefined as any)
|
||||
|
||||
let selectedIteration = -1
|
||||
</script>
|
||||
|
||||
|
||||
|
||||
<NodeWrapper offset={data.offset} let:darkMode>
|
||||
{#if data.module.value.type == 'flow'}
|
||||
<button
|
||||
@@ -65,12 +67,11 @@
|
||||
annotation={flowJobs &&
|
||||
(data.module.value.type === 'forloopflow' || data.module.value.type === 'whileloopflow')
|
||||
? 'Iteration: ' +
|
||||
(selectedIteration >= 0 ? selectedIteration : state?.flow_jobs?.length) +
|
||||
((state?.selectedForloopIndex ?? 0) >= 0 ? (state?.selectedForloopIndex ?? 0) + 1 : state?.flow_jobs?.length) +
|
||||
'/' +
|
||||
(state?.iteration_total ?? '?')
|
||||
: ''}
|
||||
bgColor={getStateColor(type, darkMode, true, state?.skipped)}
|
||||
modules={data.modules ?? []}
|
||||
moving={data.moving}
|
||||
duration_ms={state?.duration_ms}
|
||||
retries={data.retries}
|
||||
@@ -94,7 +95,6 @@
|
||||
data.eventHandlers.select(e.detail)
|
||||
}}
|
||||
on:selectedIteration={(e) => {
|
||||
selectedIteration = e.detail.index + 1
|
||||
data.eventHandlers.selectedIteration(e.detail, data.module.id)
|
||||
}}
|
||||
/>
|
||||
|
||||
@@ -45,30 +45,3 @@ export type MultiSelectEvents = {
|
||||
touchmove: TouchEvent
|
||||
touchstart: TouchEvent
|
||||
}
|
||||
|
||||
export function isObjectOption(item: any): item is ObjectOption {
|
||||
if (!item || item !== Object(item)) {
|
||||
return false
|
||||
}
|
||||
if (item.label === undefined || item.label === null) {
|
||||
return false
|
||||
}
|
||||
if (typeof item.label !== 'string' && typeof item.label !== 'number') {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
export function isObjectOptionArray(arr: any): arr is ObjectOption[] {
|
||||
if (!arr || !Array.isArray(arr)) {
|
||||
return false
|
||||
}
|
||||
return arr.every((item) => isObjectOption(item))
|
||||
}
|
||||
|
||||
export function isStringArray(arr: any): arr is string[] {
|
||||
if (!arr || !Array.isArray(arr)) {
|
||||
return false
|
||||
}
|
||||
return arr.every((item) => typeof item === 'string')
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user