Various fix (#2032)
* fix(frontend): Before legacy code + add confirmation modal when removing a user + fix folder stats * fix(frontend): Add Date type narrowing * fix(frontend): Add missing fullscreen in ScriptEditorDrawer * fix(frontend): Check if we have un-saved changes before navigating to the homepage * fix(frontend): correctly use the keyed each * fix(frontend): add DateTimeInput * fix(frontend): add DateTimeInput * fix(frontend): add DateTimeInput * fix(frontend): code cleanup * fix(frontend): Set default time to 00:00
This commit is contained in:
@@ -26,6 +26,7 @@
|
||||
import { twMerge } from 'tailwind-merge'
|
||||
import ArgEnum from './ArgEnum.svelte'
|
||||
import ArrayTypeNarrowing from './ArrayTypeNarrowing.svelte'
|
||||
import DateTimeInput from './DateTimeInput.svelte'
|
||||
|
||||
export let label: string = ''
|
||||
export let value: any
|
||||
@@ -379,7 +380,7 @@
|
||||
<ArgEnum {defaultValue} {valid} {customValue} {disabled} bind:value {enum_} {autofocus} />
|
||||
</div>
|
||||
{:else if inputCat == 'date'}
|
||||
<input {autofocus} class="inline-block" type="datetime-local" bind:value />
|
||||
<DateTimeInput {autofocus} bind:value />
|
||||
{:else if inputCat == 'sql' || inputCat == 'yaml'}
|
||||
<div class="border my-1 mb-4 w-full border-gray-400">
|
||||
<SimpleEditor
|
||||
|
||||
22
frontend/src/lib/components/DateTimeInput.svelte
Normal file
22
frontend/src/lib/components/DateTimeInput.svelte
Normal file
@@ -0,0 +1,22 @@
|
||||
<script lang="ts">
|
||||
export let value: string | undefined = undefined
|
||||
|
||||
export let autofocus: boolean = false
|
||||
|
||||
let dateFromValue = value ? new Date(value) : undefined
|
||||
let date = dateFromValue ? dateFromValue.toISOString().split('T')[0] : undefined
|
||||
let time = dateFromValue ? dateFromValue.toISOString().split('T')[1] : '00:00'
|
||||
|
||||
$: {
|
||||
if (date && time) {
|
||||
let newDate = new Date(`${date}T${time}`)
|
||||
value = newDate.toISOString()
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="flex flex-row gap-1 items-center w-full">
|
||||
<!-- 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" />
|
||||
</div>
|
||||
@@ -17,6 +17,7 @@
|
||||
import { X } from 'lucide-svelte'
|
||||
import LightweightResourcePicker from './LightweightResourcePicker.svelte'
|
||||
import LightweightObjectResourceInput from './LightweightObjectResourceInput.svelte'
|
||||
import DateTimeInput from './DateTimeInput.svelte'
|
||||
|
||||
export let css: ComponentCustomCSS<'schemaformcomponent'> | undefined = undefined
|
||||
export let label: string = ''
|
||||
@@ -311,7 +312,7 @@
|
||||
{/each}
|
||||
</select>
|
||||
{:else if inputCat == 'date'}
|
||||
<input class="inline-block" type="datetime-local" bind:value />
|
||||
<DateTimeInput bind:value />
|
||||
{:else if inputCat == 'base64'}
|
||||
<input
|
||||
type="file"
|
||||
|
||||
@@ -19,9 +19,9 @@
|
||||
'uuid',
|
||||
'ipv4',
|
||||
'yaml',
|
||||
'sql'
|
||||
'sql',
|
||||
// 'time',
|
||||
// 'date',
|
||||
'date-time'
|
||||
// 'duration',
|
||||
// 'ipv6',
|
||||
// 'jsonpointer'
|
||||
|
||||
@@ -1,68 +1,38 @@
|
||||
<script lang="ts">
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
// A table suitable if you can pass data as a list of row objects
|
||||
import Cell from './table/Cell.svelte'
|
||||
import DataTable from './table/DataTable.svelte'
|
||||
import Head from './table/Head.svelte'
|
||||
|
||||
export let headers: string[] | undefined
|
||||
export let data: any[] | undefined // Object containing the data
|
||||
export let keys: string[]
|
||||
export let defaultText: string = 'No data to display'
|
||||
export let paginated = false
|
||||
export let twTextSize: string = 'text-sm md:text-base'
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
</script>
|
||||
|
||||
<div class="mt-2 flex flex-col {$$props.class}">
|
||||
<div class="inline-block min-w-full align-middle">
|
||||
<table class="min-w-full divide-y table-auto">
|
||||
<thead>
|
||||
<tr class={twTextSize}>
|
||||
{#if headers}
|
||||
{#each headers as header, i}
|
||||
<th
|
||||
class="py-3.5 text-left text-sm font-semibold text-primary capitalize {i == 0
|
||||
? 'sm:pl-6 md:pl-0 pl-4 pr-3'
|
||||
: 'px-3 '}">{header}</th
|
||||
>
|
||||
{/each}
|
||||
{/if}
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="divide-y">
|
||||
{#if data && keys && data.length > 0}
|
||||
{#each data as row}
|
||||
<tr>
|
||||
{#each keys as key, i}
|
||||
<td
|
||||
class="py-2 text-sm text-secondary break-words {i == 0
|
||||
? 'pl-4 pr-3 sm:pl-6 md:pl-0 font-semibold'
|
||||
: 'px-3'} {twTextSize}"
|
||||
>
|
||||
{row[key] ?? ''}</td
|
||||
>
|
||||
{/each}
|
||||
</tr>
|
||||
<div class="mt-2">
|
||||
<DataTable>
|
||||
<Head>
|
||||
<tr>
|
||||
{#if headers}
|
||||
{#each headers as header, i}
|
||||
<Cell first={i == 0} last={i == headers.length - 1} head>{header}</Cell>
|
||||
{/each}
|
||||
{:else if data}
|
||||
<tr>{defaultText}</tr>
|
||||
{:else}
|
||||
<tr>Loading...</tr>
|
||||
{/if}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</tr>
|
||||
</Head>
|
||||
<tbody class="divide-y">
|
||||
{#if data && keys && data.length > 0}
|
||||
{#each data as row}
|
||||
<tr>
|
||||
{#each keys as key, i}
|
||||
<Cell first={i == 0} last={i == keys.length - 1} class="w-1/3 whitespace-pre-wrap">
|
||||
{row[key] ?? ''}
|
||||
</Cell>
|
||||
{/each}
|
||||
</tr>
|
||||
{/each}
|
||||
{:else}
|
||||
<tr>Loading...</tr>
|
||||
{/if}
|
||||
</tbody>
|
||||
</DataTable>
|
||||
</div>
|
||||
{#if paginated}
|
||||
<div class="flex flex-row-reverse text-tertiary">
|
||||
<button
|
||||
on:click={() => {
|
||||
dispatch('next')
|
||||
}}>Next</button
|
||||
>
|
||||
<button
|
||||
class="mx-2"
|
||||
on:click={() => {
|
||||
dispatch('next')
|
||||
}}>Previous</button
|
||||
>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
@@ -63,7 +63,7 @@
|
||||
{...btn.buttonProps}
|
||||
startIcon={{ icon: btn.buttonProps.startIcon }}
|
||||
on:click={btn.buttonProps.onClick}
|
||||
btnClasses="hidden md:block"
|
||||
btnClasses="hidden md:flex items-center gap-1"
|
||||
>
|
||||
{btn.label}
|
||||
</Button>
|
||||
@@ -72,7 +72,7 @@
|
||||
startIcon={{ icon: btn.buttonProps.startIcon }}
|
||||
on:click={btn.buttonProps.onClick}
|
||||
iconOnly
|
||||
btnClasses="block md:hidden"
|
||||
btnClasses="flex md:hidden items-center gap-1"
|
||||
>
|
||||
{btn.label}
|
||||
</Button>
|
||||
|
||||
@@ -80,6 +80,7 @@
|
||||
title="Script Editor"
|
||||
noPadding
|
||||
forceOverflowVisible
|
||||
fullScreen
|
||||
on:close={() => {
|
||||
scriptEditorDrawer.closeDrawer()
|
||||
}}
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
import SearchItems from '../SearchItems.svelte'
|
||||
import Cell from '../table/Cell.svelte'
|
||||
import Row from '../table/Row.svelte'
|
||||
import ConfirmationModal from '../common/confirmationModal/ConfirmationModal.svelte'
|
||||
|
||||
let users: User[] | undefined = undefined
|
||||
let invites: WorkspaceInvite[] = []
|
||||
@@ -59,6 +60,8 @@
|
||||
}
|
||||
}
|
||||
|
||||
let deleteConfirmedCallback: (() => void) | undefined = undefined
|
||||
|
||||
async function removeAllInvitesFromDomain() {
|
||||
await Promise.all(
|
||||
invites
|
||||
@@ -221,13 +224,15 @@
|
||||
btnClasses="text-red-500"
|
||||
size="xs"
|
||||
spacingSize="xs2"
|
||||
on:click={async () => {
|
||||
await UserService.deleteUser({
|
||||
workspace: $workspaceStore ?? '',
|
||||
username
|
||||
})
|
||||
sendUserToast('User removed')
|
||||
listUsers()
|
||||
on:click={() => {
|
||||
deleteConfirmedCallback = async () => {
|
||||
await UserService.deleteUser({
|
||||
workspace: $workspaceStore ?? '',
|
||||
username
|
||||
})
|
||||
sendUserToast('User removed')
|
||||
listUsers()
|
||||
}
|
||||
}}
|
||||
>
|
||||
Remove
|
||||
@@ -360,8 +365,10 @@
|
||||
})
|
||||
loadSettings()
|
||||
listInvites()
|
||||
}}>Unset auto-invite from {auto_invite_domain} domain</Button
|
||||
}}
|
||||
>
|
||||
Unset auto-invite from {auto_invite_domain} domain
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
@@ -369,3 +376,22 @@
|
||||
{#if !allowedAutoDomain}
|
||||
<div class="text-red-400 text-xs mb-2">{domain} domain not allowed for auto-invite</div>
|
||||
{/if}
|
||||
|
||||
<ConfirmationModal
|
||||
open={Boolean(deleteConfirmedCallback)}
|
||||
title="Remove user"
|
||||
confirmationText="Remove"
|
||||
on:canceled={() => {
|
||||
deleteConfirmedCallback = undefined
|
||||
}}
|
||||
on:confirmed={() => {
|
||||
if (deleteConfirmedCallback) {
|
||||
deleteConfirmedCallback()
|
||||
}
|
||||
deleteConfirmedCallback = undefined
|
||||
}}
|
||||
>
|
||||
<div class="flex flex-col w-full space-y-4">
|
||||
<span>Are you sure you want to remove ?</span>
|
||||
</div>
|
||||
</ConfirmationModal>
|
||||
|
||||
@@ -6,8 +6,49 @@
|
||||
import Menu from '../common/menu/Menu.svelte'
|
||||
import { faCog, faPlus } from '@fortawesome/free-solid-svg-icons'
|
||||
import { Icon } from 'svelte-awesome'
|
||||
|
||||
import { goto } from '$app/navigation'
|
||||
import { dirtyStore } from '../common/confirmationModal/dirtyStore'
|
||||
import { page } from '$app/stores'
|
||||
export let isCollapsed: boolean = false
|
||||
|
||||
function waitForNextUpdate(store) {
|
||||
return new Promise((resolve) => {
|
||||
let firstEmission = true
|
||||
store.subscribe((value) => {
|
||||
if (firstEmission) {
|
||||
firstEmission = false
|
||||
return
|
||||
}
|
||||
resolve(value)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
async function toggleSwitchWorkspace(id: string) {
|
||||
if ($workspaceStore === id) {
|
||||
return
|
||||
}
|
||||
|
||||
const editPages = ['/scripts/edit/', '/flows/edit/', '/apps/edit/']
|
||||
const isOnEditPage = editPages.some((editPage) => $page.route.id?.includes(editPage) ?? false)
|
||||
|
||||
// Check if we have unsaved changes
|
||||
const wasDirty = $dirtyStore
|
||||
// Try to go to the home page
|
||||
|
||||
// If we weren't dirty, we can directly switch workspaces
|
||||
if (!wasDirty && !isOnEditPage) {
|
||||
switchWorkspace(id)
|
||||
} else {
|
||||
await goto('/')
|
||||
|
||||
const shouldSwitch = await waitForNextUpdate(dirtyStore)
|
||||
|
||||
if (shouldSwitch) {
|
||||
switchWorkspace(id)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<Menu placement="bottom-start" let:close>
|
||||
@@ -36,11 +77,8 @@
|
||||
{$workspaceStore === workspace.id
|
||||
? 'cursor-default bg-surface-selected'
|
||||
: 'cursor-pointer hover:bg-surface-hover'}"
|
||||
on:click={() => {
|
||||
if ($workspaceStore === workspace.id) {
|
||||
return
|
||||
}
|
||||
switchWorkspace(workspace.id)
|
||||
on:click={async () => {
|
||||
await toggleSwitchWorkspace(workspace.id)
|
||||
close()
|
||||
}}
|
||||
>
|
||||
|
||||
@@ -127,7 +127,7 @@
|
||||
</tr>
|
||||
{/if}
|
||||
|
||||
{#each folders as { name, extra_perms, owners, canWrite }}
|
||||
{#each folders as { name, extra_perms, owners, canWrite } (name)}
|
||||
<Row
|
||||
hoverable
|
||||
on:click={() => {
|
||||
|
||||
@@ -109,7 +109,7 @@
|
||||
</tr>
|
||||
{/each}
|
||||
{:else}
|
||||
{#each groups as { name, summary, extra_perms, canWrite }}
|
||||
{#each groups as { name, summary, extra_perms, canWrite } (name)}
|
||||
<tr>
|
||||
<td class="!px-0 text-center">
|
||||
<SharedBadge {canWrite} extraPerms={extra_perms} />
|
||||
|
||||
@@ -366,10 +366,9 @@
|
||||
{/each}
|
||||
{:else}
|
||||
<TableSimple
|
||||
headers={['name', 'example of value', 'description']}
|
||||
headers={['Name', 'Example of value', 'Description']}
|
||||
data={contextualVariables}
|
||||
keys={['name', 'value', 'description']}
|
||||
twTextSize="text-sm"
|
||||
/>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user