mirror of
https://github.com/windmill-labs/windmill.git
synced 2026-08-23 08:00:45 +00:00
fix: improve date picker across app
This commit is contained in:
@@ -568,7 +568,7 @@
|
||||
/>
|
||||
</div>
|
||||
{:else if inputCat == 'date'}
|
||||
<DateTimeInput {autofocus} bind:value />
|
||||
<DateTimeInput useDropdown {autofocus} bind:value />
|
||||
{:else if inputCat == 'sql' || inputCat == 'yaml'}
|
||||
<div class="border my-1 mb-4 w-full border-primary">
|
||||
<SimpleEditor
|
||||
|
||||
@@ -1,27 +1,94 @@
|
||||
<script lang="ts">
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
import { Button } from './common'
|
||||
|
||||
export let value: string | undefined = undefined
|
||||
|
||||
export let autofocus: boolean = false
|
||||
export let useDropdown: boolean = false
|
||||
|
||||
let dateFromValue: Date | undefined = value ? new Date(value) : undefined
|
||||
let date: string | undefined = undefined
|
||||
let time: string | undefined = undefined
|
||||
function parseValue(value: string | undefined = undefined) {
|
||||
let dateFromValue: Date | undefined = value ? new Date(value) : undefined
|
||||
|
||||
let date = isValidDate(dateFromValue) ? dateFromValue!.toISOString().split('T')[0] : undefined
|
||||
let time = isValidDate(dateFromValue) ? dateFromValue!.toISOString().split('T')[1] : '00:00'
|
||||
date = isValidDate(dateFromValue) ? dateFromValue!.toISOString().split('T')[0] : undefined
|
||||
time = isValidDate(dateFromValue)
|
||||
? `${dateFromValue!.getHours().toString().padStart(2, '0')}:${dateFromValue!
|
||||
.getMinutes()
|
||||
.toString()
|
||||
.padStart(2, '0')}`
|
||||
: undefined
|
||||
}
|
||||
|
||||
$: {
|
||||
if (date && time) {
|
||||
$: parseValue(value)
|
||||
|
||||
let initialDate = date
|
||||
let initialTime = time
|
||||
|
||||
function parseDateAndTime(date: string | undefined, time: string | undefined) {
|
||||
if (date && time && (initialDate != date || initialTime != time)) {
|
||||
let newDate = new Date(`${date}T${time}`)
|
||||
value = newDate.toISOString()
|
||||
dispatch('change', value)
|
||||
}
|
||||
}
|
||||
|
||||
$: parseDateAndTime(date, time)
|
||||
|
||||
function isValidDate(d: Date | undefined): boolean {
|
||||
return d instanceof Date && !isNaN(d as any)
|
||||
}
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
|
||||
function setTimeLater(mins: number) {
|
||||
let newDate = new Date()
|
||||
newDate.setMinutes(newDate.getMinutes() + mins)
|
||||
value = newDate.toISOString()
|
||||
dispatch('change', value)
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="flex flex-row gap-1 items-center w-full">
|
||||
<div class="flex flex-row gap-1 items-center w-full" id="droptarget">
|
||||
<!-- svelte-ignore a11y-autofocus -->
|
||||
<input type="date" bind:value={date} {autofocus} class="!w-3/4" />
|
||||
<input type="time" bind:value={time} class="!w-1/4" />
|
||||
<input type="time" bind:value={time} class="!w-1/4 min-w-[100px]" />
|
||||
<Button
|
||||
variant="border"
|
||||
color="light"
|
||||
size="xs"
|
||||
portalTarget="#droptarget"
|
||||
dropdownItems={useDropdown
|
||||
? [
|
||||
{
|
||||
label: 'In 15 minutes',
|
||||
onClick: () => {
|
||||
setTimeLater(15)
|
||||
}
|
||||
},
|
||||
{
|
||||
label: 'In 1 hour',
|
||||
onClick: () => {
|
||||
setTimeLater(60)
|
||||
}
|
||||
},
|
||||
{
|
||||
label: 'Tomorrow',
|
||||
onClick: () => {
|
||||
setTimeLater(60 * 24)
|
||||
}
|
||||
},
|
||||
{
|
||||
label: 'In 1 week',
|
||||
onClick: () => {
|
||||
setTimeLater(7 * 60 * 24)
|
||||
}
|
||||
}
|
||||
]
|
||||
: undefined}
|
||||
on:click={() => {
|
||||
setTimeLater(0)
|
||||
}}>now</Button
|
||||
>
|
||||
</div>
|
||||
|
||||
@@ -75,7 +75,6 @@
|
||||
enabled: true,
|
||||
modifierKey: 'ctrl' as 'ctrl',
|
||||
onPanComplete: ({ chart }) => {
|
||||
console.log(chart.scales.x.min)
|
||||
dispatch('zoom', {
|
||||
min: addSeconds(new Date(chart.scales.x.min), -1),
|
||||
max: addSeconds(new Date(chart.scales.x.max), 1)
|
||||
|
||||
@@ -6,8 +6,8 @@
|
||||
import Tooltip from './Tooltip.svelte'
|
||||
import { userStore, workerTags } from '$lib/stores'
|
||||
import { Button } from './common'
|
||||
import { getToday } from '$lib/utils'
|
||||
import { WorkerService } from '$lib/gen'
|
||||
import DateTimeInput from './DateTimeInput.svelte'
|
||||
|
||||
export let runnable:
|
||||
| {
|
||||
@@ -50,13 +50,12 @@
|
||||
<div class="flex flex-row items-end">
|
||||
<div class="w-max md:w-2/3 mt-2 mb-1">
|
||||
<label for="run-time" />
|
||||
<input
|
||||
class="inline-block"
|
||||
type="datetime-local"
|
||||
id="run-time"
|
||||
name="run-scheduled-time"
|
||||
bind:value={scheduledForStr}
|
||||
min={getToday().toISOString().slice(0, 16)}
|
||||
<DateTimeInput
|
||||
value={scheduledForStr}
|
||||
on:change={(e) => {
|
||||
console.log('e.detail', e.detail)
|
||||
scheduledForStr = e.detail
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<Button
|
||||
|
||||
@@ -29,7 +29,7 @@
|
||||
export let title: string | undefined = undefined
|
||||
export let style: string = ''
|
||||
export let download: string | undefined = undefined
|
||||
|
||||
export let portalTarget: string | undefined = undefined
|
||||
export let startIcon: ButtonType.Icon | undefined = undefined
|
||||
export let endIcon: ButtonType.Icon | undefined = undefined
|
||||
|
||||
@@ -147,7 +147,7 @@
|
||||
</script>
|
||||
|
||||
<div
|
||||
class="{dropdownItems && variant === 'contained'
|
||||
class="{dropdownItems && dropdownItems.length > 0 && variant === 'contained'
|
||||
? colorVariants[color].divider
|
||||
: ''} {wrapperClasses} flex flex-row"
|
||||
style={wrapperStyle}
|
||||
@@ -226,11 +226,11 @@
|
||||
</button>
|
||||
{/if}
|
||||
|
||||
{#if dropdownItems}
|
||||
{#if dropdownItems && dropdownItems.length > 0}
|
||||
<div
|
||||
class={twMerge(buttonClass, 'rounded-r-md rounded-l-none m-0 p-0 h-auto !w-10 border-l-0')}
|
||||
>
|
||||
<ButtonDropdown>
|
||||
<ButtonDropdown target={portalTarget}>
|
||||
<svelte:fragment slot="items">
|
||||
{#each computeDropdowns() ?? [] as item}
|
||||
<MenuItem on:click={item.onClick} href={item.href}>
|
||||
|
||||
@@ -1,36 +1,20 @@
|
||||
<script lang="ts">
|
||||
import { ArrowRight, Calendar } from 'lucide-svelte'
|
||||
import { Calendar } from 'lucide-svelte'
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
import { Button, Popup } from '..'
|
||||
import { Popup } from '..'
|
||||
import type { Placement } from '@floating-ui/core'
|
||||
import DateTimeInput from '$lib/components/DateTimeInput.svelte'
|
||||
|
||||
export let date: string | undefined
|
||||
export let label: string
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
let value = date
|
||||
let input: HTMLInputElement
|
||||
|
||||
$: if (date && input) {
|
||||
input.value = new Date(date).toISOString().slice(0, 16)
|
||||
}
|
||||
|
||||
function save() {
|
||||
console.log(value, date, label)
|
||||
dispatch('change', value)
|
||||
input.blur()
|
||||
}
|
||||
|
||||
function bg(e: KeyboardEvent) {
|
||||
if (e.key === 'Tab') {
|
||||
e.stopPropagation()
|
||||
}
|
||||
}
|
||||
|
||||
export let placement: Placement = 'top-end'
|
||||
</script>
|
||||
|
||||
<Popup floatingConfig={{ placement: placement, strategy: 'absolute' }} let:close>
|
||||
<Popup floatingConfig={{ placement: placement, strategy: 'absolute' }}>
|
||||
<svelte:fragment slot="button">
|
||||
<button
|
||||
title="Open calendar picker"
|
||||
@@ -44,30 +28,17 @@
|
||||
</button>
|
||||
</svelte:fragment>
|
||||
|
||||
<!-- svelte-ignore a11y-label-has-associated-control -->
|
||||
<label class="block text-primary">
|
||||
<div class="pb-1 text-sm text-secondary">{label}</div>
|
||||
<div class="flex w-full">
|
||||
<input
|
||||
type="datetime-local"
|
||||
bind:value
|
||||
class="!w-auto grow"
|
||||
bind:this={input}
|
||||
on:keydown={bg}
|
||||
/>
|
||||
<Button
|
||||
size="xs"
|
||||
color="dark"
|
||||
buttonType="button"
|
||||
btnClasses="!p-1 !w-[34px] !ml-1"
|
||||
aria-label="Save ID"
|
||||
disabled={!value}
|
||||
on:click={() => {
|
||||
save()
|
||||
close(null)
|
||||
<DateTimeInput
|
||||
value={date}
|
||||
on:change={(e) => {
|
||||
date = e.detail
|
||||
dispatch('change', date)
|
||||
}}
|
||||
>
|
||||
<ArrowRight size={18} />
|
||||
</Button>
|
||||
/>
|
||||
</div>
|
||||
</label>
|
||||
</Popup>
|
||||
|
||||
@@ -345,14 +345,16 @@
|
||||
on:click={async () => (cancelAllJobs = true)}>Cancel All</Button
|
||||
>
|
||||
</div>
|
||||
<div class="flex flex-row gap-1 w-full max-w-xl">
|
||||
<div class="flex flex-row gap-1 w-full max-w-lg">
|
||||
<div class="relative w-full">
|
||||
<div class="flex gap-1 relative w-full">
|
||||
<span class="text-xs absolute -top-4">Min datetime</span>
|
||||
|
||||
<input
|
||||
type="text"
|
||||
value={minTs ?? 'zoom x axis to set min (drag with ctrl)'}
|
||||
value={minTs
|
||||
? new Date(minTs).toLocaleString()
|
||||
: 'zoom x axis to set min (drag with ctrl)'}
|
||||
disabled
|
||||
/>
|
||||
|
||||
@@ -368,7 +370,11 @@
|
||||
<div class="relative w-full">
|
||||
<div class="flex gap-1 relative w-full">
|
||||
<span class="text-xs absolute -top-4">Max datetime</span>
|
||||
<input type="text" value={maxTs ?? 'zoom x axis to set max'} disabled />
|
||||
<input
|
||||
type="text"
|
||||
value={maxTs ? new Date(maxTs).toLocaleString() : 'zoom x axis to set max'}
|
||||
disabled
|
||||
/>
|
||||
<CalendarPicker
|
||||
date={maxTs}
|
||||
label="Max datetimes"
|
||||
@@ -507,14 +513,16 @@
|
||||
on:click={async () => (cancelAllJobs = true)}>Cancel All</Button
|
||||
>
|
||||
</div>
|
||||
<div class="flex flex-row gap-1 w-full max-w-xl items-center">
|
||||
<div class="flex flex-row gap-1 w-full max-w-lg items-center">
|
||||
<div class="relative w-full">
|
||||
<div class="flex gap-1 relative w-full">
|
||||
<span class="text-xs absolute -top-4">Min datetime</span>
|
||||
|
||||
<input
|
||||
type="text"
|
||||
value={minTs ?? 'zoom x axis to set min (drag with ctrl)'}
|
||||
value={minTs
|
||||
? new Date(minTs).toLocaleString()
|
||||
: 'zoom x axis to set min (drag with ctrl)'}
|
||||
disabled
|
||||
/>
|
||||
|
||||
@@ -530,7 +538,11 @@
|
||||
<div class="relative w-full">
|
||||
<div class="flex gap-1 relative w-full">
|
||||
<span class="text-xs absolute -top-4">Max datetime</span>
|
||||
<input type="text" value={maxTs ?? 'zoom x axis to set max'} disabled />
|
||||
<input
|
||||
type="text"
|
||||
value={maxTs ? new Date(maxTs).toLocaleString() : 'zoom x axis to set max'}
|
||||
disabled
|
||||
/>
|
||||
<CalendarPicker
|
||||
date={maxTs}
|
||||
label="Max datetimes"
|
||||
|
||||
@@ -20,20 +20,31 @@
|
||||
|
||||
let scriptBuilder: ScriptBuilder | undefined = undefined
|
||||
|
||||
function decodeStateAndHandleError(state) {
|
||||
try {
|
||||
return decodeState(state)
|
||||
} catch (e) {
|
||||
console.error('Error decoding state', e)
|
||||
return defaultScript
|
||||
}
|
||||
}
|
||||
|
||||
function defaultScript() {
|
||||
return {
|
||||
hash: '',
|
||||
path: path ?? '',
|
||||
summary: '',
|
||||
content: '',
|
||||
schema: schema,
|
||||
is_template: false,
|
||||
extra_perms: {},
|
||||
language: 'deno',
|
||||
kind: Script.kind.SCRIPT
|
||||
}
|
||||
}
|
||||
|
||||
let script: NewScript =
|
||||
!path && initialState != undefined
|
||||
? decodeState(initialState)
|
||||
: {
|
||||
hash: '',
|
||||
path: path ?? '',
|
||||
summary: '',
|
||||
content: '',
|
||||
schema: schema,
|
||||
is_template: false,
|
||||
extra_perms: {},
|
||||
language: 'deno',
|
||||
kind: Script.kind.SCRIPT
|
||||
}
|
||||
!path && initialState != undefined ? decodeStateAndHandleError(initialState) : defaultScript()
|
||||
|
||||
async function loadTemplate(): Promise<void> {
|
||||
if (templatePath) {
|
||||
|
||||
Reference in New Issue
Block a user