mirror of
https://github.com/windmill-labs/windmill.git
synced 2026-08-26 00:01:37 +00:00
9c28bbfd69
* stash
* ui nits
* Fix contenteditable feedback look (duplicate typing)
* fix right icon wrong position with placeholder
* user editor in Path editor takes correct width
* nits
* nit
* chore: remove assets-operator changes (moved to separate PR)
These files were mistakenly included in this PR and belong in a dedicated PR
("Allow assets page to operators").
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* chore: remove sidebar assets-operator change (moved to separate PR)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix disabled
* border nit
* Fix disabled styling
* Apply suggestion from @cubic-dev-ai[bot]
Co-authored-by: cubic-dev-ai[bot] <191113872+cubic-dev-ai[bot]@users.noreply.github.com>
* nit
* Update frontend/src/lib/components/text_input/TextInput.svelte
Co-authored-by: cubic-dev-ai[bot] <191113872+cubic-dev-ai[bot]@users.noreply.github.com>
* Fix disabled tabindex and aria-disabled on contenteditable Select
The useContentEditable branch had an unconditional tabindex="0", keeping
a disabled Select in the tab order, and was missing aria-disabled.
Mirror the TextInput div branch.
Co-authored-by: Diego Imbert <diegoimbert@users.noreply.github.com>
* fix: drop obsolete hideFullPath prop from EditorHeader Path usage
* invalidate autocomplete paths on deploy
* nit pixel
* use Badge in auto complete
* nit prevent default
* fix(autocomplete): don't let stale fetch clobber forced refresh
A non-forced fetchWorkspacePaths() that started before invalidateWorkspacePaths()
could still resolve afterward, overwrite the cache, and clear forceNextFetch —
making the post-deploy refresh a no-op. Only write back from the promise that
is still the current pending one, and only clear the force flag when the
completing fetch was itself forced.
* refactor(path): drop unreachable 'group' branch in owner-kind setter
The Select only offers user/folder, so the 'group' branch was dead. Leave a
short note pointing at validateName which still accepts 'group' for
forward-compat.
* fix(path): respect disableEditing on owner-kind selector
Other path-editor controls disable on (disabled || disableEditing); the
owner-kind Select only checked `disabled`, so read-only users (trigger
editors with !can_write) could still toggle User/Folder and mutate the
bound path. Reuse the existing nameDisabled flag.
* Revert "fix(autocomplete): don't let stale fetch clobber forced refresh"
This reverts commit 6649975714.
---------
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-authored-by: cubic-dev-ai[bot] <191113872+cubic-dev-ai[bot]@users.noreply.github.com>
Co-authored-by: claude[bot] <41898282+claude[bot]@users.noreply.github.com>
Co-authored-by: Diego Imbert <diegoimbert@users.noreply.github.com>
219 lines
5.8 KiB
Svelte
219 lines
5.8 KiB
Svelte
<script lang="ts">
|
|
import { emptyString, isOwner } from '$lib/utils'
|
|
import { Alert, Button } from '$lib/components/common'
|
|
import Popover from '$lib/components/meltComponents/Popover.svelte'
|
|
import TextInput from '$lib/components/text_input/TextInput.svelte'
|
|
import Path from '$lib/components/Path.svelte'
|
|
import { userStore, workspaceStore } from '$lib/stores'
|
|
import { sendUserToast } from '$lib/toast'
|
|
import { updateItemPathAndSummary, checkFlowOnBehalfOf } from './moveRenameManager'
|
|
import Label from './Label.svelte'
|
|
import LabelsInput from './LabelsInput.svelte'
|
|
import Badge from './common/badge/Badge.svelte'
|
|
|
|
interface Props {
|
|
summary?: string
|
|
path?: string
|
|
labels?: string[] | undefined
|
|
editable?: boolean
|
|
onSaved?: (newPath: string) => void
|
|
kind?: 'flow' | 'script'
|
|
}
|
|
|
|
let {
|
|
summary = $bindable(''),
|
|
path = $bindable(''),
|
|
labels = $bindable(),
|
|
editable = false,
|
|
onSaved,
|
|
kind = 'flow'
|
|
}: Props = $props()
|
|
|
|
let editSummary = $state('')
|
|
let editPath = $state('')
|
|
let dirtyPath = $state(false)
|
|
let popoverOpen = $state(false)
|
|
let own = $state(false)
|
|
let onBehalfOfEmail = $state<string | undefined>(undefined)
|
|
let summaryInput: ReturnType<typeof TextInput> | undefined = $state()
|
|
let labelsDirty = $state(false)
|
|
let hasChanges = $derived(editSummary !== (summary ?? '') || (own && dirtyPath) || labelsDirty)
|
|
|
|
$effect(() => {
|
|
if (popoverOpen && onSaved) {
|
|
editSummary = summary ?? ''
|
|
editPath = path ?? ''
|
|
labelsDirty = false
|
|
own = isOwner(path ?? '', $userStore, $workspaceStore)
|
|
onBehalfOfEmail = undefined
|
|
if (kind === 'flow' && $workspaceStore && path) {
|
|
checkFlowOnBehalfOf($workspaceStore, path).then((email) => {
|
|
onBehalfOfEmail = email
|
|
})
|
|
}
|
|
}
|
|
})
|
|
|
|
async function save(close: () => void) {
|
|
const initialPath = path ?? ''
|
|
const newPath = own ? editPath : initialPath
|
|
|
|
try {
|
|
await updateItemPathAndSummary({
|
|
workspace: $workspaceStore!,
|
|
kind,
|
|
initialPath,
|
|
newPath,
|
|
newSummary: editSummary,
|
|
labels
|
|
})
|
|
sendUserToast(`${kind === 'flow' ? 'Flow' : 'Script'} updated`)
|
|
labelsDirty = false
|
|
close()
|
|
onSaved?.(newPath)
|
|
} catch (e: any) {
|
|
sendUserToast(`Could not update ${kind}: ${e.body ?? e.message}`, true)
|
|
}
|
|
}
|
|
</script>
|
|
|
|
{#if editable || onSaved}
|
|
<Popover
|
|
class="min-w-0 max-w-full"
|
|
placement="bottom-start"
|
|
contentClasses="p-4"
|
|
usePointerDownOutside
|
|
excludeSelectors=".drawer"
|
|
disableFocusTrap
|
|
openFocus={() => {
|
|
summaryInput?.focus()
|
|
return null
|
|
}}
|
|
bind:isOpen={popoverOpen}
|
|
>
|
|
{#snippet trigger()}
|
|
<div
|
|
class={'min-w-0 truncate flex flex-col items-start px-2 py-1 rounded-md transition-colors cursor-pointer hover:bg-surface-hover'}
|
|
>
|
|
<span class="text-2xs leading-tight text-tertiary font-mono font-normal truncate max-w-full"
|
|
>{path}</span
|
|
>
|
|
<div class="flex items-center gap-3 max-w-full">
|
|
<span
|
|
class="text-sm font-semibold truncate {emptyString(summary)
|
|
? 'text-tertiary italic font-normal'
|
|
: 'text-emphasis'}"
|
|
>
|
|
{emptyString(summary) ? 'Add a summary...' : summary}
|
|
</span>
|
|
{#if labels?.length}
|
|
<div class="flex items-center gap-0.5">
|
|
{#each labels as label}
|
|
<Badge color="blue" verySmall class="px-1" title="Label: {label}">{label}</Badge>
|
|
{/each}
|
|
</div>
|
|
{/if}
|
|
</div>
|
|
</div>
|
|
{/snippet}
|
|
{#snippet content({ close })}
|
|
<div class="flex flex-col gap-6 w-[480px]">
|
|
{#if onSaved}
|
|
<Label label="Summary">
|
|
<TextInput
|
|
bind:this={summaryInput}
|
|
inputProps={{
|
|
type: 'text',
|
|
placeholder: 'Short summary',
|
|
onkeydown: (e) => {
|
|
if (e.key === 'Enter') {
|
|
save(close)
|
|
}
|
|
}
|
|
}}
|
|
bind:value={editSummary}
|
|
/>
|
|
</Label>
|
|
<LabelsInput
|
|
bind:labels
|
|
class="-mt-4"
|
|
onchange={() => {
|
|
labelsDirty = true
|
|
}}
|
|
/>
|
|
<Label label="Path">
|
|
{#if own}
|
|
<Path
|
|
autofocus={false}
|
|
bind:path={editPath}
|
|
bind:dirty={dirtyPath}
|
|
initialPath={path ?? ''}
|
|
namePlaceholder={kind}
|
|
{kind}
|
|
size="sm"
|
|
drawerOffset={4000}
|
|
/>
|
|
{:else}
|
|
<span class="text-xs font-mono text-secondary">{path}</span>
|
|
<p class="text-2xs text-tertiary mt-1">Only the owner can change the path</p>
|
|
{/if}
|
|
</Label>
|
|
{#if onBehalfOfEmail}
|
|
<Alert type="info" title="Run on behalf of" size="xs">
|
|
This flow will be redeployed on behalf of you ({$userStore?.email}) instead of {onBehalfOfEmail}
|
|
</Alert>
|
|
{/if}
|
|
<Button
|
|
size="xs"
|
|
variant="accent"
|
|
disabled={!hasChanges}
|
|
title="Save summary and path"
|
|
onclick={() => save(close)}
|
|
>
|
|
Save
|
|
</Button>
|
|
{:else}
|
|
<label class="block text-primary">
|
|
<div class="pb-1 text-xs font-semibold text-emphasis">Summary</div>
|
|
<TextInput
|
|
bind:this={summaryInput}
|
|
inputProps={{
|
|
type: 'text',
|
|
placeholder: 'Short summary',
|
|
onkeydown: (e) => {
|
|
if (e.key === 'Enter') {
|
|
close()
|
|
}
|
|
}
|
|
}}
|
|
bind:value={summary}
|
|
/>
|
|
</label>
|
|
<div class="block text-primary">
|
|
<div class="pb-1 text-xs font-semibold text-emphasis">Path</div>
|
|
<Path
|
|
autofocus={false}
|
|
bind:path
|
|
bind:dirty={dirtyPath}
|
|
initialPath={path ?? ''}
|
|
namePlaceholder={kind}
|
|
{kind}
|
|
size="sm"
|
|
drawerOffset={4000}
|
|
/>
|
|
</div>
|
|
{/if}
|
|
</div>
|
|
{/snippet}
|
|
</Popover>
|
|
{:else}
|
|
<div class="min-w-0 truncate flex flex-col px-2">
|
|
{#if !emptyString(summary)}
|
|
<span class="text-[10px] leading-tight text-tertiary font-mono truncate">{path}</span>
|
|
{/if}
|
|
<span class="text-sm font-semibold text-emphasis truncate">
|
|
{emptyString(summary) ? (path ?? '') : summary}
|
|
</span>
|
|
</div>
|
|
{/if}
|