mirror of
https://github.com/windmill-labs/windmill.git
synced 2026-08-21 16:02:28 +00:00
feat(front): Add a confirmation modal (#634)
* feat(front): Add a confirmation modal * feat(front): Simplify styles * feat(front): Remove code duplication * feat(front): Add shortcut + add Alert component
This commit is contained in:
@@ -0,0 +1,63 @@
|
||||
<script lang="ts">
|
||||
import { classNames } from '$lib/utils'
|
||||
import {
|
||||
faCheckCircle,
|
||||
faInfoCircle,
|
||||
faWarning,
|
||||
type IconDefinition
|
||||
} from '@fortawesome/free-solid-svg-icons'
|
||||
import Icon from 'svelte-awesome'
|
||||
import type { AlertType } from './model'
|
||||
|
||||
export let type: AlertType = 'info'
|
||||
export let title: string
|
||||
|
||||
const icons: Record<AlertType, IconDefinition> = {
|
||||
info: faInfoCircle,
|
||||
warning: faWarning,
|
||||
error: faWarning,
|
||||
success: faCheckCircle
|
||||
}
|
||||
|
||||
const classes: Record<AlertType, Record<string, string>> = {
|
||||
info: {
|
||||
bgClass: 'bg-blue-50',
|
||||
iconClass: 'text-blue-500',
|
||||
titleClass: 'text-blue-800',
|
||||
descriptionClass: 'text-blue-700'
|
||||
},
|
||||
warning: {
|
||||
bgClass: 'bg-yellow-50',
|
||||
iconClass: 'text-yellow-500',
|
||||
titleClass: 'text-yellow-800',
|
||||
descriptionClass: 'text-yellow-700'
|
||||
},
|
||||
error: {
|
||||
bgClass: 'bg-red-50',
|
||||
iconClass: 'text-red-500',
|
||||
titleClass: 'text-red-800',
|
||||
descriptionClass: 'text-red-700'
|
||||
},
|
||||
success: {
|
||||
bgClass: 'bg-green-50',
|
||||
iconClass: 'text-green-500',
|
||||
titleClass: 'text-green-800',
|
||||
descriptionClass: 'text-green-700'
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class={classNames('rounded-md p-4', classes[type].bgClass)}>
|
||||
<div class="flex">
|
||||
<div class="flex h-8 w-8 items-center justify-center rounded-full">
|
||||
<Icon data={icons[type]} class={classes[type].iconClass} />
|
||||
</div>
|
||||
|
||||
<div class="ml-2 w-full">
|
||||
<span class={classNames('text-sm font-medium', classes[type].titleClass)}>{title}</span>
|
||||
<div class={classNames('mt-2 text-sm', classes[type].descriptionClass)}>
|
||||
<slot />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1 @@
|
||||
export type AlertType = 'success' | 'error' | 'warning' | 'info'
|
||||
@@ -0,0 +1,59 @@
|
||||
<script lang="ts">
|
||||
import { classNames } from '$lib/utils'
|
||||
import { faWarning } from '@fortawesome/free-solid-svg-icons'
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
import Icon from 'svelte-awesome'
|
||||
import { fade } from 'svelte/transition'
|
||||
import Button from '../button/Button.svelte'
|
||||
|
||||
export let title: string
|
||||
export let confirmationText: string
|
||||
|
||||
export let open: boolean = false
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
</script>
|
||||
|
||||
{#if open}
|
||||
<div transition:fade={{ duration: 100 }} class={'relative z-50'} role="dialog">
|
||||
<div
|
||||
class={classNames(
|
||||
'fixed inset-0 bg-gray-500 bg-opacity-75 transition-opacity',
|
||||
open ? 'ease-out duration-300 opacity-100' : 'ease-in duration-200 opacity-0'
|
||||
)}
|
||||
/>
|
||||
|
||||
<div class="fixed inset-0 z-10 overflow-y-auto">
|
||||
<div class="flex min-h-full items-center justify-center p-4">
|
||||
<div
|
||||
class={classNames(
|
||||
'relative transform overflow-hidden rounded-lg bg-white px-4 pt-5 pb-4 text-left shadow-xl transition-all sm:my-8 sm:w-full sm:max-w-lg sm:p-6',
|
||||
open
|
||||
? 'ease-out duration-300 opacity-100 translate-y-0 sm:scale-100'
|
||||
: 'ease-in duration-200 opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95'
|
||||
)}
|
||||
>
|
||||
<div class="flex">
|
||||
<div class="flex h-12 w-12 items-center justify-center rounded-full bg-red-100">
|
||||
<Icon data={faWarning} class="text-red-500" />
|
||||
</div>
|
||||
<div class="ml-4 text-left flex-1">
|
||||
<h3 class="text-lg font-medium text-gray-900">
|
||||
{title}
|
||||
</h3>
|
||||
<div class="mt-2 text-sm text-gray-500">
|
||||
<slot />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex items-center space-x-2 flex-row-reverse space-x-reverse mt-4">
|
||||
<Button on:click={() => dispatch('confirmed')} color="red" size="sm">
|
||||
{confirmationText}
|
||||
</Button>
|
||||
<Button on:click={() => dispatch('canceled')} color="light" size="sm">Cancel</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
@@ -7,13 +7,16 @@
|
||||
import { createEventDispatcher, getContext } from 'svelte'
|
||||
import Icon from 'svelte-awesome'
|
||||
import type { FlowEditorContext } from '../types'
|
||||
import RemoveStepConfirmationModal from './RemoveStepConfirmationModal.svelte'
|
||||
|
||||
export let module: FlowModule
|
||||
|
||||
let confirmationModalOpen = false
|
||||
|
||||
$: shouldPick = isEmptyFlowModule(module)
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
const { selectedId } = getContext<FlowEditorContext>('FlowEditorContext')
|
||||
const { selectedId, select } = getContext<FlowEditorContext>('FlowEditorContext')
|
||||
</script>
|
||||
|
||||
<div class="flex flex-row space-x-2" on:click|stopPropagation={() => undefined}>
|
||||
@@ -33,11 +36,26 @@
|
||||
<Button
|
||||
size="xs"
|
||||
color="alternative"
|
||||
on:click={() => {
|
||||
dispatch('delete')
|
||||
on:click={(event) => {
|
||||
if (event.shiftKey || shouldPick) {
|
||||
dispatch('delete')
|
||||
select('settings')
|
||||
} else {
|
||||
confirmationModalOpen = true
|
||||
}
|
||||
}}
|
||||
>
|
||||
<Icon data={faTrashAlt} class="mr-2" />
|
||||
{$selectedId.includes('failure') ? 'Delete error handler' : 'Remove step'}
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<RemoveStepConfirmationModal
|
||||
bind:open={confirmationModalOpen}
|
||||
on:confirmed={() => {
|
||||
dispatch('delete')
|
||||
setTimeout(() => {
|
||||
select('settings')
|
||||
})
|
||||
}}
|
||||
/>
|
||||
|
||||
@@ -43,7 +43,6 @@
|
||||
bind:flowModule={$flowStore.value.modules[parentIndex]}
|
||||
bind:flowModuleState={$flowStateStore.modules[parentIndex]}
|
||||
on:delete={() => {
|
||||
select('settings')
|
||||
$flowStateStore.modules.splice(parentIndex, 1)
|
||||
$flowStateStore = $flowStateStore
|
||||
$flowStore.value.modules.splice(parentIndex, 1)
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
<script lang="ts">
|
||||
import Alert from '$lib/components/common/alert/Alert.svelte'
|
||||
import Badge from '$lib/components/common/badge/Badge.svelte'
|
||||
import ConfirmationModal from '$lib/components/common/confirmationModal/ConfirmationModal.svelte'
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
|
||||
export let open: boolean
|
||||
const dispatch = createEventDispatcher()
|
||||
</script>
|
||||
|
||||
<ConfirmationModal
|
||||
{open}
|
||||
title="Remove step"
|
||||
confirmationText="Remove"
|
||||
on:canceled={() => {
|
||||
open = false
|
||||
dispatch('canceled')
|
||||
}}
|
||||
on:confirmed={() => {
|
||||
open = false
|
||||
dispatch('confirmed')
|
||||
}}
|
||||
>
|
||||
<div class="flex flex-col w-full space-y-4">
|
||||
<span>Are you sure you want to remove this step?</span>
|
||||
<Alert type="info" title="Bypass confirmation">
|
||||
<div>
|
||||
You can press
|
||||
<Badge color="dark-gray">SHIFT</Badge>
|
||||
while removing a step to bypass confirmation.
|
||||
</div>
|
||||
</Alert>
|
||||
</div>
|
||||
</ConfirmationModal>
|
||||
@@ -49,7 +49,7 @@
|
||||
</div>
|
||||
|
||||
{#if $flowStore.value.failure_module}
|
||||
<input
|
||||
<textarea
|
||||
bind:value={$flowStore.value.failure_module.summary}
|
||||
placeholder="Error handler summary"
|
||||
/>
|
||||
|
||||
@@ -44,7 +44,7 @@
|
||||
{#if deletable}
|
||||
<button
|
||||
type="button"
|
||||
on:click={() => dispatch('delete')}
|
||||
on:click={(event) => dispatch('delete', { event })}
|
||||
class="text-gray-900 bg-white border border-gray-300 focus:outline-none hover:bg-gray-100 focus:ring-4 focus:ring-gray-200 font-medium rounded-sm text-xs px-2 py-1"
|
||||
>
|
||||
<Icon data={faTrashAlt} scale={0.8} />
|
||||
|
||||
@@ -4,12 +4,13 @@
|
||||
import FlowModuleSchemaItem from './FlowModuleSchemaItem.svelte'
|
||||
import Icon from 'svelte-awesome'
|
||||
import { faPen, faPlus, faSliders } from '@fortawesome/free-solid-svg-icons'
|
||||
import { emptyFlowModuleState } from '$lib/components/flows/flowStateUtils'
|
||||
import { emptyFlowModuleState, isEmptyFlowModule } from '$lib/components/flows/flowStateUtils'
|
||||
import { classNames, emptyModule } from '$lib/utils'
|
||||
import type { FlowModuleState } from '../flowState'
|
||||
import type { FlowModule } from '$lib/gen'
|
||||
|
||||
import FlowErrorHandlerItem from './FlowErrorHandlerItem.svelte'
|
||||
import RemoveStepConfirmationModal from '../content/RemoveStepConfirmationModal.svelte'
|
||||
|
||||
export let prefix: string | undefined = undefined
|
||||
export let modules: FlowModule[]
|
||||
@@ -33,6 +34,8 @@
|
||||
moduleStates = moduleStates
|
||||
modules = modules
|
||||
}
|
||||
let indexToRemove: number | undefined = undefined
|
||||
$: confirmationModalOpen = indexToRemove !== undefined
|
||||
</script>
|
||||
|
||||
<div class="flex flex-col justify-between h-full">
|
||||
@@ -116,7 +119,13 @@
|
||||
isLast={index === modules.length - 1}
|
||||
selected={$selectedId === [prefix, String(index)].filter(Boolean).join('-')}
|
||||
deletable
|
||||
on:delete={() => removeAtIndex(index)}
|
||||
on:delete={(event) => {
|
||||
if (event.detail.event.shiftKey || isEmptyFlowModule(mod)) {
|
||||
removeAtIndex(index)
|
||||
} else {
|
||||
indexToRemove = index
|
||||
}
|
||||
}}
|
||||
>
|
||||
<div slot="icon">
|
||||
<span>{index + 1}</span>
|
||||
@@ -148,3 +157,13 @@
|
||||
<FlowErrorHandlerItem />
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<RemoveStepConfirmationModal
|
||||
bind:open={confirmationModalOpen}
|
||||
on:confirmed={() => {
|
||||
if (indexToRemove !== undefined) {
|
||||
removeAtIndex(indexToRemove)
|
||||
indexToRemove = undefined
|
||||
}
|
||||
}}
|
||||
/>
|
||||
|
||||
Reference in New Issue
Block a user