mirror of
https://github.com/windmill-labs/windmill.git
synced 2026-08-21 08:02:26 +00:00
feat(frontend): Fix component synchro (#1038)
* feat(frontend): Fix component synchro * feat(frontend): Fix isObject
This commit is contained in:
@@ -130,7 +130,7 @@
|
||||
})
|
||||
|
||||
if (previewJobUpdates.new_logs) {
|
||||
job.logs = (job.logs ?? '').concat(previewJobUpdates.new_logs)
|
||||
job.logs = (job?.logs ?? '').concat(previewJobUpdates.new_logs)
|
||||
}
|
||||
if ((previewJobUpdates.running ?? false) || (previewJobUpdates.completed ?? false)) {
|
||||
job = await JobService.getJob({ workspace: workspace!, id })
|
||||
|
||||
@@ -49,7 +49,8 @@
|
||||
|
||||
const options = {
|
||||
responsive: true,
|
||||
animation: false
|
||||
animation: false,
|
||||
maintainAspectRatio: false
|
||||
}
|
||||
|
||||
$: data = {
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
let result: string = ''
|
||||
</script>
|
||||
|
||||
<RunnableWrapper bind:componentInput {id} bind:result autoRefresh={false}>
|
||||
<RunnableWrapper bind:componentInput {id} bind:result>
|
||||
<AlignWrapper {horizontalAlignment} {verticalAlignment}>
|
||||
{#if result === ''}
|
||||
<div class="text-gray-400 bg-gray-100 flex justify-center items-center h-full w-full">
|
||||
|
||||
@@ -32,18 +32,13 @@
|
||||
<InputValue input={configuration.maxDate} bind:value={maxValue} />
|
||||
|
||||
<AlignWrapper {verticalAlignment}>
|
||||
<!-- svelte-ignore a11y-label-has-associated-control -->
|
||||
<label class="w-full">
|
||||
<div>
|
||||
{labelValue}
|
||||
</div>
|
||||
<input
|
||||
type={inputType}
|
||||
bind:this={input}
|
||||
on:input={handleInput}
|
||||
min={minValue}
|
||||
max={maxValue}
|
||||
placeholder="Type..."
|
||||
/>
|
||||
</label>
|
||||
<input
|
||||
type={inputType}
|
||||
bind:this={input}
|
||||
on:input={handleInput}
|
||||
min={minValue}
|
||||
max={maxValue}
|
||||
placeholder="Type..."
|
||||
class="h-full"
|
||||
/>
|
||||
</AlignWrapper>
|
||||
|
||||
@@ -36,7 +36,7 @@
|
||||
let testIsLoading = false
|
||||
let runnableInputValues: Record<string, any> = {}
|
||||
|
||||
$: mergedArgs = { ...args, ...extraQueryParams, ...runnableInputValues }
|
||||
$: mergedArgs = { ...extraQueryParams, ...runnableInputValues, ...args }
|
||||
|
||||
function setStaticInputsToArgs() {
|
||||
Object.entries(inputs ?? {}).forEach(([key, value]) => {
|
||||
@@ -51,9 +51,13 @@
|
||||
$: inputs && setStaticInputsToArgs()
|
||||
|
||||
function argMergedArgsValid(mergedArgs: Record<string, any>, testJobLoader) {
|
||||
if (!inputs) {
|
||||
return false
|
||||
}
|
||||
|
||||
if (
|
||||
Object.keys(inputs ?? {}).filter((k) => inputs[k].type !== 'user').length !==
|
||||
Object.keys(runnableInputValues).length
|
||||
Object.keys(inputs).length !==
|
||||
Object.keys(mergedArgs).length - Object.keys(extraQueryParams).length
|
||||
) {
|
||||
return false
|
||||
}
|
||||
@@ -211,22 +215,24 @@
|
||||
bind:this={testJobLoader}
|
||||
/>
|
||||
|
||||
{#if schemaStripped !== undefined && (autoRefresh || forceSchemaDisplay)}
|
||||
<SchemaForm schema={schemaStripped} bind:args {isValid} {disabledArgs} shouldHideNoInputs />
|
||||
{/if}
|
||||
|
||||
{#if !runnable && autoRefresh}
|
||||
<Alert type="warning" size="xs" class="mt-2" title="Missing runnable">
|
||||
Please select a runnable
|
||||
</Alert>
|
||||
{:else if autoRefresh === true}
|
||||
{#if isValid}
|
||||
<slot />
|
||||
{:else}
|
||||
<Alert type="warning" size="xs" class="mt-2" title="Missing inputs">
|
||||
Please fill in all the inputs
|
||||
</Alert>
|
||||
<div class="h-full flex flex-col">
|
||||
{#if schemaStripped !== undefined && (autoRefresh || forceSchemaDisplay)}
|
||||
<SchemaForm schema={schemaStripped} bind:args {isValid} {disabledArgs} shouldHideNoInputs />
|
||||
{/if}
|
||||
{:else}
|
||||
<slot />
|
||||
{/if}
|
||||
|
||||
{#if !runnable && autoRefresh}
|
||||
<Alert type="warning" size="xs" class="mt-2" title="Missing runnable">
|
||||
Please select a runnable
|
||||
</Alert>
|
||||
{:else if autoRefresh === true}
|
||||
{#if isValid}
|
||||
<slot />
|
||||
{:else}
|
||||
<Alert type="warning" size="xs" class="mt-2" title="Missing inputs">
|
||||
Please fill in all the inputs
|
||||
</Alert>
|
||||
{/if}
|
||||
{:else}
|
||||
<slot />
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
$: outputs = $worldStore?.outputsById[id] as {
|
||||
result: Output<number | null>
|
||||
}
|
||||
$: if(value || !value) {
|
||||
$: if (value || !value) {
|
||||
// Disallow 'e' character in numbers
|
||||
// if(value && value.toString().includes('e')) {
|
||||
// value = +value.toString().replaceAll('e', '')
|
||||
@@ -32,18 +32,12 @@
|
||||
<InputValue input={configuration.label} bind:value={labelValue} />
|
||||
|
||||
<AlignWrapper {verticalAlignment}>
|
||||
<!-- svelte-ignore a11y-label-has-associated-control -->
|
||||
<label class="w-full">
|
||||
<div>
|
||||
{labelValue}
|
||||
</div>
|
||||
<DebouncedInput
|
||||
bind:value={value}
|
||||
debounceDelay={300}
|
||||
type="number"
|
||||
inputmode="numeric"
|
||||
pattern="\d*"
|
||||
placeholder="Type..."
|
||||
/>
|
||||
</label>
|
||||
<input
|
||||
bind:value
|
||||
type="number"
|
||||
inputmode="numeric"
|
||||
pattern="\d*"
|
||||
placeholder="Type..."
|
||||
class="h-full"
|
||||
/>
|
||||
</AlignWrapper>
|
||||
|
||||
@@ -17,12 +17,12 @@
|
||||
let label: string
|
||||
let items: string[]
|
||||
let itemKey: string
|
||||
|
||||
|
||||
$: outputs = $worldStore?.outputsById[id] as {
|
||||
result: Output<string | undefined>
|
||||
}
|
||||
|
||||
function onChange({detail}: CustomEvent) {
|
||||
function onChange({ detail }: CustomEvent) {
|
||||
outputs?.result.set(detail?.[itemKey] || undefined)
|
||||
}
|
||||
</script>
|
||||
@@ -32,20 +32,5 @@
|
||||
<InputValue input={configuration.itemKey} bind:value={itemKey} />
|
||||
|
||||
<AlignWrapper {horizontalAlignment} {verticalAlignment}>
|
||||
<!-- svelte-ignore a11y-label-has-associated-control -->
|
||||
<label class="block w-full">
|
||||
<div>
|
||||
{label}
|
||||
</div>
|
||||
<div>
|
||||
<Select
|
||||
on:clear={onChange}
|
||||
on:change={onChange}
|
||||
|
||||
{items}
|
||||
class="w-full"
|
||||
placeholder="Select an item"
|
||||
/>
|
||||
</div>
|
||||
</label>
|
||||
<Select on:clear={onChange} on:change={onChange} {items} placeholder="Select an item" />
|
||||
</AlignWrapper>
|
||||
|
||||
@@ -8,11 +8,12 @@
|
||||
import { writable } from 'svelte/store'
|
||||
import { createSvelteTable, flexRender, type TableOptions } from '@tanstack/svelte-table'
|
||||
import AppButton from '../buttons/AppButton.svelte'
|
||||
import { classNames } from '$lib/utils'
|
||||
import { classNames, isObject } from '$lib/utils'
|
||||
import DebouncedInput from '../helpers/DebouncedInput.svelte'
|
||||
import AppTableFooter from './AppTableFooter.svelte'
|
||||
import RefreshButton from '../helpers/RefreshButton.svelte'
|
||||
import { tableOptions } from './tableOptions'
|
||||
import Alert from '$lib/components/common/alert/Alert.svelte'
|
||||
|
||||
export let id: string
|
||||
export let componentInput: AppInput | undefined
|
||||
@@ -102,79 +103,85 @@
|
||||
<InputValue input={configuration.search} bind:value={search} />
|
||||
<InputValue input={configuration.pagination} bind:value={pagination} />
|
||||
|
||||
<RunnableWrapper bind:componentInput {id} bind:result {extraQueryParams} autoRefresh={false}>
|
||||
<div class="border border-gray-300 shadow-sm divide-y divide-gray-300 flex flex-col h-full">
|
||||
<div class="py-2 px-4">
|
||||
<div class="flex justify-between items-center">
|
||||
<RefreshButton componentId={id} />
|
||||
{#if search !== 'Disabled'}
|
||||
<div>
|
||||
<DebouncedInput placeholder="Search..." bind:value={searchValue} />
|
||||
</div>
|
||||
{/if}
|
||||
<RunnableWrapper bind:componentInput {id} bind:result {extraQueryParams}>
|
||||
{#if Array.isArray(result) && result.every(isObject)}
|
||||
<div class="border border-gray-300 shadow-sm divide-y divide-gray-300 flex flex-col h-full">
|
||||
<div class="py-2 px-4">
|
||||
<div class="flex justify-between items-center">
|
||||
<RefreshButton componentId={id} />
|
||||
{#if search !== 'Disabled'}
|
||||
<div>
|
||||
<DebouncedInput placeholder="Search..." bind:value={searchValue} />
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="overflow-auto flex-1 w-full">
|
||||
<table class="divide-y divide-gray-300 w-full border-b border-b-gray-200">
|
||||
<thead class="bg-gray-50 text-left">
|
||||
{#each $table.getHeaderGroups() as headerGroup}
|
||||
<tr class="divide-x">
|
||||
{#each headerGroup.headers as header}
|
||||
<th class="px-4 py-4 text-sm font-semibold">
|
||||
{#if !header.isPlaceholder}
|
||||
<div class="overflow-auto flex-1 w-full">
|
||||
<table class="divide-y divide-gray-300 w-full border-b border-b-gray-200">
|
||||
<thead class="bg-gray-50 text-left">
|
||||
{#each $table.getHeaderGroups() as headerGroup}
|
||||
<tr class="divide-x">
|
||||
{#each headerGroup.headers as header}
|
||||
<th class="px-4 py-4 text-sm font-semibold">
|
||||
{#if !header.isPlaceholder}
|
||||
<svelte:component
|
||||
this={flexRender(header.column.columnDef.header, header.getContext())}
|
||||
/>
|
||||
{/if}
|
||||
</th>
|
||||
{/each}
|
||||
{#if actionButtons.length > 0}
|
||||
<th class="px-4 py-4 text-sm font-semibold">Actions</th>
|
||||
{/if}
|
||||
</tr>
|
||||
{/each}
|
||||
</thead>
|
||||
<tbody class="divide-y divide-gray-200 bg-white ">
|
||||
{#each $table.getRowModel().rows as row, rowIndex (row.id)}
|
||||
<tr
|
||||
class={classNames(
|
||||
selectedRowIndex === rowIndex
|
||||
? 'bg-blue-100 hover:bg-blue-200'
|
||||
: 'hover:bg-blue-50',
|
||||
'divide-x',
|
||||
'border-b w-full',
|
||||
selectedRowIndex === rowIndex
|
||||
? 'divide-blue-200 hover:divide-blue-300'
|
||||
: 'divide-gray-200'
|
||||
)}
|
||||
on:click={() => toggleRow(row, rowIndex)}
|
||||
>
|
||||
{#each row.getVisibleCells() as cell, index (index)}
|
||||
<td class="p-4 whitespace-nowrap text-xs text-gray-900">
|
||||
<svelte:component
|
||||
this={flexRender(header.column.columnDef.header, header.getContext())}
|
||||
this={flexRender(cell.column.columnDef.cell, cell.getContext())}
|
||||
/>
|
||||
{/if}
|
||||
</th>
|
||||
{/each}
|
||||
{#if actionButtons.length > 0}
|
||||
<th class="px-4 py-4 text-sm font-semibold">Actions</th>
|
||||
{/if}
|
||||
</tr>
|
||||
{/each}
|
||||
</thead>
|
||||
<tbody class="divide-y divide-gray-200 bg-white ">
|
||||
{#each $table.getRowModel().rows as row, rowIndex (row.id)}
|
||||
<tr
|
||||
class={classNames(
|
||||
selectedRowIndex === rowIndex
|
||||
? 'bg-blue-100 hover:bg-blue-200'
|
||||
: 'hover:bg-blue-50',
|
||||
'divide-x',
|
||||
'border-b w-full',
|
||||
selectedRowIndex === rowIndex
|
||||
? 'divide-blue-200 hover:divide-blue-300'
|
||||
: 'divide-gray-200'
|
||||
)}
|
||||
on:click={() => toggleRow(row, rowIndex)}
|
||||
>
|
||||
{#each row.getVisibleCells() as cell, index (index)}
|
||||
<td class="p-4 whitespace-nowrap text-xs text-gray-900">
|
||||
<svelte:component
|
||||
this={flexRender(cell.column.columnDef.cell, cell.getContext())}
|
||||
/>
|
||||
</td>
|
||||
{/each}
|
||||
</td>
|
||||
{/each}
|
||||
|
||||
{#if actionButtons.length > 0}
|
||||
<td class="flex flex-row gap-2 p-4">
|
||||
{#each actionButtons as props, actionIndex (actionIndex)}
|
||||
<AppButton
|
||||
{...props}
|
||||
extraQueryParams={{ row }}
|
||||
bind:componentInput={props.componentInput}
|
||||
bind:staticOutputs={$staticOutputsStore[props.id]}
|
||||
/>
|
||||
{/each}
|
||||
</td>
|
||||
{/if}
|
||||
</tr>
|
||||
{/each}
|
||||
</tbody>
|
||||
</table>
|
||||
{#if actionButtons.length > 0}
|
||||
<td class="flex flex-row gap-2 p-4">
|
||||
{#each actionButtons as props, actionIndex (actionIndex)}
|
||||
<AppButton
|
||||
{...props}
|
||||
extraQueryParams={{ row }}
|
||||
bind:componentInput={props.componentInput}
|
||||
bind:staticOutputs={$staticOutputsStore[props.id]}
|
||||
/>
|
||||
{/each}
|
||||
</td>
|
||||
{/if}
|
||||
</tr>
|
||||
{/each}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<AppTableFooter paginationEnabled={pagination} {result} {table} />
|
||||
</div>
|
||||
|
||||
<AppTableFooter paginationEnabled={pagination} {result} {table} />
|
||||
</div>
|
||||
{:else}
|
||||
<Alert title="Parsing issues" type="error" size="xs">
|
||||
The result should be an array of objects
|
||||
</Alert>
|
||||
{/if}
|
||||
</RunnableWrapper>
|
||||
|
||||
@@ -28,16 +28,11 @@
|
||||
<InputValue input={configuration.label} bind:value={labelValue} />
|
||||
|
||||
<AlignWrapper {verticalAlignment}>
|
||||
<!-- svelte-ignore a11y-label-has-associated-control -->
|
||||
<label class="w-full">
|
||||
<div>
|
||||
{labelValue}
|
||||
</div>
|
||||
<input
|
||||
type={inputType}
|
||||
bind:this={input}
|
||||
on:input={handleInput}
|
||||
placeholder="Type..."
|
||||
/>
|
||||
</label>
|
||||
<input
|
||||
type={inputType}
|
||||
bind:this={input}
|
||||
on:input={handleInput}
|
||||
placeholder="Type..."
|
||||
class="h-full"
|
||||
/>
|
||||
</AlignWrapper>
|
||||
|
||||
@@ -4,17 +4,9 @@
|
||||
import Grid from 'svelte-grid'
|
||||
import ComponentEditor from './ComponentEditor.svelte'
|
||||
import { classNames } from '$lib/utils'
|
||||
import {
|
||||
columnConfiguration,
|
||||
disableDrag,
|
||||
enableDrag,
|
||||
gridColumns,
|
||||
isFixed,
|
||||
toggleFixed
|
||||
} from '../gridUtils'
|
||||
import { columnConfiguration, disableDrag, enableDrag, isFixed, toggleFixed } from '../gridUtils'
|
||||
import { Alert } from '$lib/components/common'
|
||||
import { fly } from 'svelte/transition'
|
||||
import gridHelp from 'svelte-grid/build/helper/index.mjs'
|
||||
|
||||
import Button from '$lib/components/common/button/Button.svelte'
|
||||
import RecomputeAllComponents from './RecomputeAllComponents.svelte'
|
||||
@@ -92,16 +84,17 @@
|
||||
<ComponentEditor
|
||||
bind:component={gridComponent.data}
|
||||
selected={$selectedComponent === dataItem.data.id}
|
||||
locked={isFixed(gridComponent)}
|
||||
on:delete={() => removeGridElement(gridComponent.data)}
|
||||
on:lock={() => {
|
||||
gridComponent = toggleFixed(gridComponent)
|
||||
}}
|
||||
locked={isFixed(gridComponent)}
|
||||
/>
|
||||
</div>
|
||||
{/if}
|
||||
{/each}
|
||||
</Grid>
|
||||
|
||||
{#if $connectingInput.opened}
|
||||
<div
|
||||
class="fixed top-32 z-10 flex justify-center items-center"
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
// Dimensions key formula: <mobile width>:<mobile height>-<desktop width>:<desktop height>
|
||||
const dimensions: Record<`${number}:${number}-${number}:${number}`, AppComponent['type'][]> = {
|
||||
'1:1-3:1': ['buttoncomponent', 'textcomponent', 'checkboxcomponent'],
|
||||
'1:2-3:2': ['textinputcomponent', 'numberinputcomponent', 'selectcomponent'],
|
||||
'1:2-2:1': ['textinputcomponent', 'numberinputcomponent', 'selectcomponent'],
|
||||
'2:2-6:4': ['displaycomponent'],
|
||||
'2:3-6:4': ['formcomponent'],
|
||||
'2:4-6:4': ['barchartcomponent', 'piechartcomponent'],
|
||||
@@ -33,6 +33,21 @@
|
||||
return { w: +size[0], h: +size[1] }
|
||||
}
|
||||
|
||||
function getMaxDimensionsByComponent(componentType: AppComponent['type'], column: number): Size {
|
||||
if (
|
||||
[
|
||||
'textinputcomponent',
|
||||
'numberinputcomponent',
|
||||
'selectcomponent',
|
||||
'dateinputcomponent',
|
||||
'passwordinputcomponent'
|
||||
].includes(componentType)
|
||||
) {
|
||||
return { w: column, h: 1 }
|
||||
}
|
||||
return { w: column, h: 12 }
|
||||
}
|
||||
|
||||
function addComponent(appComponent: AppComponent) {
|
||||
const grid = $app.grid ?? []
|
||||
const id = getNextId(grid.map((gridItem) => gridItem.data.id))
|
||||
@@ -56,7 +71,7 @@
|
||||
|
||||
gridColumns.forEach((column) => {
|
||||
const min = getMinDimensionsByComponent(appComponent.type, column)
|
||||
const max = { w: 12, h: 12 }
|
||||
const max = getMaxDimensionsByComponent(appComponent.type, column)
|
||||
|
||||
newItem[column] = { ...newComponent, min, max, w: min.w, h: min.h }
|
||||
const position = gridHelp.findSpace(newItem, grid, column)
|
||||
|
||||
@@ -9,15 +9,7 @@ const inputs: ComponentSet = {
|
||||
id: 'textinputcomponent',
|
||||
type: 'textinputcomponent',
|
||||
componentInput: undefined,
|
||||
configuration: {
|
||||
label: {
|
||||
type: 'static',
|
||||
visible: false,
|
||||
value: 'Label',
|
||||
fieldType: 'textarea',
|
||||
defaultValue: 'Label'
|
||||
}
|
||||
},
|
||||
configuration: {},
|
||||
card: false
|
||||
},
|
||||
{
|
||||
@@ -25,15 +17,7 @@ const inputs: ComponentSet = {
|
||||
id: 'passwordinputcomponent',
|
||||
type: 'passwordinputcomponent',
|
||||
componentInput: undefined,
|
||||
configuration: {
|
||||
label: {
|
||||
type: 'static',
|
||||
visible: false,
|
||||
value: 'Label',
|
||||
fieldType: 'textarea',
|
||||
defaultValue: 'Label'
|
||||
}
|
||||
},
|
||||
configuration: {},
|
||||
card: false
|
||||
},
|
||||
{
|
||||
@@ -41,15 +25,7 @@ const inputs: ComponentSet = {
|
||||
id: 'numberinputcomponent',
|
||||
type: 'numberinputcomponent',
|
||||
componentInput: undefined,
|
||||
configuration: {
|
||||
label: {
|
||||
type: 'static',
|
||||
visible: false,
|
||||
value: 'Label',
|
||||
fieldType: 'textarea',
|
||||
defaultValue: 'Label'
|
||||
}
|
||||
},
|
||||
configuration: {},
|
||||
card: false
|
||||
},
|
||||
{
|
||||
@@ -58,13 +34,6 @@ const inputs: ComponentSet = {
|
||||
type: 'dateinputcomponent',
|
||||
componentInput: undefined,
|
||||
configuration: {
|
||||
label: {
|
||||
type: 'static',
|
||||
visible: false,
|
||||
value: 'Title',
|
||||
fieldType: 'textarea',
|
||||
defaultValue: 'Title'
|
||||
},
|
||||
minDate: {
|
||||
type: 'static',
|
||||
visible: false,
|
||||
@@ -78,7 +47,7 @@ const inputs: ComponentSet = {
|
||||
value: '',
|
||||
fieldType: 'date',
|
||||
defaultValue: ''
|
||||
},
|
||||
}
|
||||
},
|
||||
card: false,
|
||||
softWrap: true
|
||||
@@ -105,13 +74,6 @@ const inputs: ComponentSet = {
|
||||
type: 'selectcomponent',
|
||||
componentInput: undefined,
|
||||
configuration: {
|
||||
label: {
|
||||
type: 'static',
|
||||
visible: false,
|
||||
value: 'Label',
|
||||
fieldType: 'textarea',
|
||||
defaultValue: 'Label'
|
||||
},
|
||||
items: {
|
||||
type: 'static',
|
||||
fieldType: 'array',
|
||||
|
||||
+2
-2
@@ -64,8 +64,8 @@
|
||||
const { componentInput } = gridItem.data
|
||||
|
||||
if (
|
||||
componentInput.type === 'runnable' &&
|
||||
componentInput.runnable?.type === 'runnableByName'
|
||||
componentInput?.type === 'runnable' &&
|
||||
componentInput?.runnable?.type === 'runnableByName'
|
||||
) {
|
||||
acc.push(componentInput.runnable.name)
|
||||
}
|
||||
|
||||
@@ -61,7 +61,6 @@ export interface BaseAppComponent extends Partial<Aligned> {
|
||||
* *For example when the component has a popup like `Select`*
|
||||
*/
|
||||
softWrap?: boolean
|
||||
// TODO: add min/max width/height
|
||||
}
|
||||
|
||||
export type AppComponent = BaseAppComponent &
|
||||
|
||||
+27
-30
@@ -1,6 +1,14 @@
|
||||
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
|
||||
import { goto } from '$app/navigation'
|
||||
import { FlowService, FolderService, Script, ScriptService, type Flow, type FlowModule, type User } from '$lib/gen'
|
||||
import {
|
||||
FlowService,
|
||||
FolderService,
|
||||
Script,
|
||||
ScriptService,
|
||||
type Flow,
|
||||
type FlowModule,
|
||||
type User
|
||||
} from '$lib/gen'
|
||||
import { toast } from '@zerodevx/svelte-toast'
|
||||
import type { Schema, SupportedLanguage } from './common'
|
||||
import { hubScripts, workspaceStore, type UserExt } from './stores'
|
||||
@@ -60,12 +68,13 @@ export function displayDate(dateString: string | undefined, displaySecond = fals
|
||||
if (date.toString() === 'Invalid Date') {
|
||||
return ''
|
||||
} else {
|
||||
return `${date.getFullYear()}/${date.getMonth() + 1
|
||||
}/${date.getDate()} at ${date.toLocaleTimeString([], {
|
||||
hour: '2-digit',
|
||||
minute: '2-digit',
|
||||
second: displaySecond ? '2-digit' : undefined
|
||||
})}`
|
||||
return `${date.getFullYear()}/${
|
||||
date.getMonth() + 1
|
||||
}/${date.getDate()} at ${date.toLocaleTimeString([], {
|
||||
hour: '2-digit',
|
||||
minute: '2-digit',
|
||||
second: displaySecond ? '2-digit' : undefined
|
||||
})}`
|
||||
}
|
||||
}
|
||||
|
||||
@@ -175,11 +184,7 @@ export function removeItemAll<T>(arr: T[], value: T) {
|
||||
return arr
|
||||
}
|
||||
|
||||
export async function isOwner(
|
||||
path: string,
|
||||
user: UserExt,
|
||||
workspace: string
|
||||
): Promise<boolean> {
|
||||
export async function isOwner(path: string, user: UserExt, workspace: string): Promise<boolean> {
|
||||
if (isObviousOwner(path, user)) {
|
||||
return true
|
||||
} else if (path.startsWith('f/')) {
|
||||
@@ -191,11 +196,7 @@ export async function isOwner(
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export function isObviousOwner(
|
||||
path: string,
|
||||
user?: UserExt
|
||||
): boolean {
|
||||
export function isObviousOwner(path: string, user?: UserExt): boolean {
|
||||
if (!user) {
|
||||
return false
|
||||
}
|
||||
@@ -206,14 +207,10 @@ export function isObviousOwner(
|
||||
if (path.startsWith(userOwner)) {
|
||||
return true
|
||||
}
|
||||
if (
|
||||
user.pgroups.findIndex((x) => path.startsWith(x)) != -1
|
||||
) {
|
||||
if (user.pgroups.findIndex((x) => path.startsWith(x)) != -1) {
|
||||
return true
|
||||
}
|
||||
if (
|
||||
user.folders.findIndex((x) => path.startsWith('f/' + x)) != -1
|
||||
) {
|
||||
if (user.folders.findIndex((x) => path.startsWith('f/' + x)) != -1) {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
@@ -235,14 +232,10 @@ export function canWrite(
|
||||
if (keys.includes(userOwner) && extra_perms[userOwner]) {
|
||||
return true
|
||||
}
|
||||
if (
|
||||
user.pgroups.findIndex((x) => keys.includes(x) && extra_perms[x]) != -1
|
||||
) {
|
||||
if (user.pgroups.findIndex((x) => keys.includes(x) && extra_perms[x]) != -1) {
|
||||
return true
|
||||
}
|
||||
if (
|
||||
user.folders.findIndex((x) => path.startsWith('f/' + x)) != -1
|
||||
) {
|
||||
if (user.folders.findIndex((x) => path.startsWith('f/' + x)) != -1) {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
@@ -378,7 +371,7 @@ export function mapUserToUserExt(user: User): UserExt {
|
||||
return {
|
||||
...user,
|
||||
groups: user.groups!,
|
||||
pgroups: user.groups!.map((x) => `g/${x}`),
|
||||
pgroups: user.groups!.map((x) => `g/${x}`)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -631,3 +624,7 @@ export function capitalize(word: string): string {
|
||||
export function isCloudHosted(): boolean {
|
||||
return get(page).url.hostname == 'app.windmill.dev'
|
||||
}
|
||||
|
||||
export function isObject(obj: any) {
|
||||
return typeof obj === 'object'
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user