mirror of
https://github.com/appwrite/console.git
synced 2026-06-06 19:27:48 +00:00
feat: quick filter refactor PoC
This commit is contained in:
@@ -0,0 +1,120 @@
|
||||
<script lang="ts">
|
||||
import { capitalize } from '$lib/helpers/string';
|
||||
import { IconChevronRight } from '@appwrite.io/pink-icons-svelte';
|
||||
import { ActionMenu, Card, Layout, Selector } from '@appwrite.io/pink-svelte';
|
||||
import { createMenubar, melt } from '@melt-ui/svelte';
|
||||
import { createEventDispatcher } from 'svelte';
|
||||
import type { ValidOperators } from './store';
|
||||
|
||||
type FilterData = {
|
||||
title: string;
|
||||
id: string;
|
||||
array: boolean;
|
||||
show: boolean;
|
||||
tag: string;
|
||||
operator: ValidOperators;
|
||||
options: { value: string; label: string; checked: boolean }[];
|
||||
};
|
||||
|
||||
export let filter: FilterData;
|
||||
export let variant: 'checkbox' | 'radio' = 'checkbox';
|
||||
|
||||
const {
|
||||
builders: { createMenu }
|
||||
} = createMenubar();
|
||||
|
||||
const {
|
||||
elements: { item: item, separator: separator },
|
||||
builders: { createSubmenu: createSubmenu, createMenuRadioGroup, createCheckboxItem }
|
||||
} = createMenu();
|
||||
|
||||
const {
|
||||
elements: { checkboxItem: checkboxItem }
|
||||
} = createCheckboxItem();
|
||||
|
||||
const {
|
||||
elements: { radioGroup: radioGroup }
|
||||
} = createMenuRadioGroup({});
|
||||
|
||||
const {
|
||||
elements: { subMenu: subMenu, subTrigger: subTrigger }
|
||||
} = createSubmenu();
|
||||
|
||||
const dispatch = createEventDispatcher();
|
||||
</script>
|
||||
|
||||
<div use:melt={$subTrigger}>
|
||||
<ActionMenu.Root>
|
||||
<ActionMenu.Item.Button trailingIcon={IconChevronRight}
|
||||
>{filter.title}</ActionMenu.Item.Button>
|
||||
</ActionMenu.Root>
|
||||
</div>
|
||||
|
||||
<div class="menu subMenu" use:melt={$subMenu}>
|
||||
<Card.Base padding="xxxs">
|
||||
<div use:melt={$radioGroup}>
|
||||
{#each filter.options as option (option.value + option.checked)}
|
||||
{#if variant === 'radio'}
|
||||
<div use:melt={$item}>
|
||||
<ActionMenu.Root>
|
||||
<ActionMenu.Item.Button
|
||||
on:click={() => {
|
||||
option.checked = !option.checked;
|
||||
dispatch('add', {
|
||||
value: filter?.array
|
||||
? null
|
||||
: option.checked
|
||||
? option.value
|
||||
: null
|
||||
});
|
||||
}}>
|
||||
{capitalize(option.label)}
|
||||
</ActionMenu.Item.Button>
|
||||
</ActionMenu.Root>
|
||||
</div>
|
||||
{:else}
|
||||
<div use:melt={$checkboxItem}>
|
||||
<ActionMenu.Root>
|
||||
<ActionMenu.Item.Button
|
||||
on:click={() => {
|
||||
option.checked = !option.checked;
|
||||
dispatch('add', {
|
||||
value: filter?.array
|
||||
? null
|
||||
: option.checked
|
||||
? option.value
|
||||
: null
|
||||
});
|
||||
}}>
|
||||
<Layout.Stack direction="row" gap="s">
|
||||
<Selector.Checkbox bind:checked={option.checked} />
|
||||
{capitalize(option.label)}
|
||||
</Layout.Stack>
|
||||
</ActionMenu.Item.Button>
|
||||
</ActionMenu.Root>
|
||||
</div>
|
||||
{/if}
|
||||
{/each}
|
||||
{#if filter.options.some((option) => option.checked)}
|
||||
<div class="separator" use:melt={$separator} />
|
||||
<div use:melt={$item}>
|
||||
<ActionMenu.Root>
|
||||
<ActionMenu.Item.Button
|
||||
on:click={() => {
|
||||
dispatch('clear');
|
||||
}}>
|
||||
Clear all
|
||||
</ActionMenu.Item.Button>
|
||||
</ActionMenu.Root>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
</Card.Base>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.menu {
|
||||
min-width: 244px;
|
||||
z-index: 20;
|
||||
}
|
||||
</style>
|
||||
+10
-9
@@ -16,11 +16,11 @@
|
||||
$: totalSize = humanFileSize((deployment?.buildSize ?? 0) + (deployment?.sourceSize ?? 0));
|
||||
</script>
|
||||
|
||||
<Card isTile padding="s" radius="m" {variant}>
|
||||
<Layout.Stack gap="l">
|
||||
<Card isTile padding="m" radius="m" {variant}>
|
||||
<Layout.Stack gap="xxl">
|
||||
<Layout.GridFraction start={4} end={6}>
|
||||
<Layout.Stack direction="row">
|
||||
<Layout.Stack gap="xs">
|
||||
<Layout.Stack>
|
||||
<Layout.Stack direction="row" alignItems="center" gap="s">
|
||||
{#if activeDeployment}
|
||||
<Typography.Title
|
||||
size="s"
|
||||
@@ -28,10 +28,11 @@
|
||||
color="--fgcolor-neutral-primary">
|
||||
Active deployment
|
||||
</Typography.Title>
|
||||
|
||||
<Id value={deployment.$id}>
|
||||
{deployment.$id}
|
||||
</Id>
|
||||
<div>
|
||||
<Id value={deployment.$id}>
|
||||
{deployment.$id}
|
||||
</Id>
|
||||
</div>
|
||||
{:else}
|
||||
<Typography.Title
|
||||
size="s"
|
||||
@@ -127,7 +128,7 @@
|
||||
|
||||
{#if $$slots.footer}
|
||||
<span
|
||||
style="margin-left: calc(-1* var(--space-7));margin-right: calc(-1* var(--space-7));width:auto;">
|
||||
style="margin-left: calc(-1* var(--space-9));margin-right: calc(-1* var(--space-9));width:auto;">
|
||||
<Divider />
|
||||
</span>
|
||||
<Layout.Stack direction="row-reverse">
|
||||
|
||||
+45
-130
@@ -1,18 +1,13 @@
|
||||
<script lang="ts">
|
||||
import { invalidate } from '$app/navigation';
|
||||
import { Alert, EmptySearch, PaginationWithLimit, ViewSelector } from '$lib/components';
|
||||
import { BillingPlan, Dependencies } from '$lib/constants';
|
||||
import { goto, invalidate } from '$app/navigation';
|
||||
import { Alert, Empty, EmptySearch, PaginationWithLimit, ViewSelector } from '$lib/components';
|
||||
import { Dependencies } from '$lib/constants';
|
||||
import { Button } from '$lib/elements/forms';
|
||||
import { hoursToDays } from '$lib/helpers/date';
|
||||
import { Container, ContainerHeader } from '$lib/layout';
|
||||
import { Container } from '$lib/layout';
|
||||
import { sdk } from '$lib/stores/sdk';
|
||||
import { onMount } from 'svelte';
|
||||
import { func } from '../store';
|
||||
import { organization } from '$lib/stores/organization';
|
||||
import { getServiceLimit, showUsageRatesModal } from '$lib/stores/billing';
|
||||
import { project } from '$routes/(console)/project-[project]/store';
|
||||
import Create from '../(components)/createActionMenu.svelte';
|
||||
import { abbreviateNumber } from '$lib/helpers/numbers';
|
||||
import { base } from '$app/paths';
|
||||
import { Filters, queries } from '$lib/components/filters';
|
||||
import { writable } from 'svelte/store';
|
||||
@@ -20,13 +15,11 @@
|
||||
import { View } from '$lib/helpers/load';
|
||||
import Table from './table.svelte';
|
||||
import QuickFilters from './quickFilters.svelte';
|
||||
import { Pill } from '$lib/elements';
|
||||
import { tags } from '$lib/components/filters/store';
|
||||
import { Layout } from '@appwrite.io/pink-svelte';
|
||||
|
||||
export let data;
|
||||
|
||||
const logs = getServiceLimit('logs');
|
||||
|
||||
const columns = writable<Column[]>([
|
||||
{ id: '$id', title: 'Execution ID', type: 'string', show: true, width: 150 },
|
||||
{
|
||||
@@ -160,116 +153,43 @@
|
||||
</script>
|
||||
|
||||
<Container>
|
||||
<ContainerHeader title="Executions">
|
||||
<svelte:fragment slot="tooltip" let:tier let:limit let:upgradeMethod>
|
||||
<p class="u-bold">The {tier} plan has limits</p>
|
||||
<ul>
|
||||
<li>
|
||||
{abbreviateNumber(limit)} function executions
|
||||
</li>
|
||||
<li>
|
||||
{hoursToDays(logs)} of logs
|
||||
</li>
|
||||
</ul>
|
||||
{#if $organization?.billingPlan === BillingPlan.FREE}
|
||||
<p class="text">
|
||||
<button class="link" type="button" on:click|preventDefault={upgradeMethod}
|
||||
>Upgrade</button>
|
||||
to increase your resource limits.
|
||||
</p>
|
||||
{:else}
|
||||
<p class="text">
|
||||
After this amount, <button
|
||||
class="link"
|
||||
type="button"
|
||||
on:click|preventDefault={() => ($showUsageRatesModal = true)}
|
||||
>usage fees will apply</button
|
||||
>.
|
||||
</p>
|
||||
{/if}
|
||||
</svelte:fragment>
|
||||
</ContainerHeader>
|
||||
<div class="u-flex u-main-space-between is-not-mobile u-margin-block-start-16">
|
||||
<div class="u-flex u-gap-8 u-cross-center u-flex-wrap">
|
||||
<QuickFilters {columns} />
|
||||
<Filters
|
||||
query={data.query}
|
||||
{columns}
|
||||
let:disabled
|
||||
let:toggle
|
||||
clearOnClick
|
||||
analyticsSource="executions_filters">
|
||||
<div class="u-flex u-gap-4">
|
||||
<Button
|
||||
text
|
||||
on:click={toggle}
|
||||
{disabled}
|
||||
ariaLabel="open filter"
|
||||
noMargin={!$tags?.length}>
|
||||
<span class="icon-filter-line" />
|
||||
<span class="text">More filters</span>
|
||||
</Button>
|
||||
{#if $tags?.length}
|
||||
<div
|
||||
style="flex-basis: 1px; background-color: hsl(var(--border)); width: 1px;">
|
||||
</div>
|
||||
<Button text on:click={clearAll}>Clear All</Button>
|
||||
{/if}
|
||||
<Layout.Stack direction="row" alignItems="center" justifyContent="space-between">
|
||||
<QuickFilters {columns} />
|
||||
<!-- <Filters
|
||||
query={data.query}
|
||||
{columns}
|
||||
let:disabled
|
||||
let:toggle
|
||||
clearOnClick
|
||||
analyticsSource="executions_filters">
|
||||
<div class="u-flex u-gap-4">
|
||||
<Button
|
||||
text
|
||||
on:click={toggle}
|
||||
{disabled}
|
||||
ariaLabel="open filter"
|
||||
noMargin={!$tags?.length}>
|
||||
<span class="icon-filter-line" />
|
||||
<span class="text">More filters</span>
|
||||
</Button>
|
||||
{#if $tags?.length}
|
||||
<div
|
||||
style="flex-basis: 1px; background-color: hsl(var(--border)); width: 1px;">
|
||||
</div>
|
||||
</Filters>
|
||||
</div>
|
||||
<div class="u-flex u-gap-16">
|
||||
<ViewSelector view={View.Table} {columns} hideView allowNoColumns hideText />
|
||||
<Button text on:click={clearAll}>Clear All</Button>
|
||||
{/if}
|
||||
</div>
|
||||
</Filters> -->
|
||||
<Layout.Stack gap="s" inline direction="row" alignItems="center">
|
||||
<ViewSelector view={View.Table} {columns} hideView allowNoColumns />
|
||||
<Button
|
||||
event="execute_function"
|
||||
href={`${base}/project-${$project.$id}/functions/function-${$func.$id}/executions/execute-function`}
|
||||
disabled={!$func.$id || !$func?.deployment}>
|
||||
<span class="text">Execute</span>
|
||||
Execute
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="is-only-mobile">
|
||||
<div class="u-flex u-main-space-between u-margin-block-start-16">
|
||||
<Button
|
||||
text
|
||||
on:click={() => (showMobileFilters = !showMobileFilters)}
|
||||
ariaLabel="toggle filters">
|
||||
<span class="icon-filter-line" />
|
||||
<span class="text">Filters</span>
|
||||
</Button>
|
||||
|
||||
<div class=" u-flex u-gap-16">
|
||||
<ViewSelector view={View.Table} {columns} hideView allowNoColumns />
|
||||
<Button
|
||||
event="execute_function"
|
||||
href={`${base}/project-${$project.$id}/functions/function-${$func.$id}/executions/execute-function`}
|
||||
disabled={!$func.$id || !$func?.deployment}>
|
||||
<span class="text">Execute</span>
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class:u-hide={!showMobileFilters}
|
||||
class:u-flex={showMobileFilters}
|
||||
class=" u-gap-8 u-flex-wrap u-margin-block-start-16">
|
||||
<QuickFilters {columns} />
|
||||
|
||||
<Filters query={data.query} {columns} clearOnClick analyticsSource="executions_filters">
|
||||
<svelte:fragment slot="mobile" let:disabled let:toggle>
|
||||
<Pill
|
||||
button
|
||||
on:click={toggle}
|
||||
{disabled}
|
||||
class="u-flex u-gap-4 u-cross-center"
|
||||
style="--p-tag-content-height: auto">
|
||||
<!-- <span class="icon-filter-line" /> -->
|
||||
<span class="text">More filters</span>
|
||||
<span class="icon-cheveron-down" />
|
||||
</Pill>
|
||||
</svelte:fragment>
|
||||
</Filters>
|
||||
</div>
|
||||
</div>
|
||||
</Layout.Stack>
|
||||
</Layout.Stack>
|
||||
|
||||
{#if !$func.logging}
|
||||
<div class="common-section">
|
||||
@@ -310,19 +230,14 @@
|
||||
</div>
|
||||
</EmptySearch>
|
||||
{:else}
|
||||
<EmptySearch>
|
||||
<div class="u-text-center">
|
||||
<p class="text u-line-height-1-5">
|
||||
You have no execution logs. Create and activate a deployment to see it here.
|
||||
</p>
|
||||
<p class="text u-line-height-1-5">Need a hand? Learn more in our documentation.</p>
|
||||
</div>
|
||||
<div class="u-flex u-gap-16">
|
||||
<Button text external href="https://appwrite.io/docs/products/functions/execution">
|
||||
Documentation
|
||||
</Button>
|
||||
<Create secondary />
|
||||
</div>
|
||||
</EmptySearch>
|
||||
<Empty
|
||||
single
|
||||
target="execution"
|
||||
href="https://appwrite.io/docs/products/functions/execution"
|
||||
on:click={() =>
|
||||
goto(
|
||||
`${base}/project-${$project.$id}/functions/function-${$func.$id}/executions/execute-function`
|
||||
)}>
|
||||
</Empty>
|
||||
{/if}
|
||||
</Container>
|
||||
|
||||
+67
-79
@@ -2,6 +2,7 @@
|
||||
import { afterNavigate } from '$app/navigation';
|
||||
import { Submit, trackEvent } from '$lib/actions/analytics';
|
||||
import { DropList, DropListItem } from '$lib/components';
|
||||
import MenuItem from '$lib/components/filters/subMenu.svelte';
|
||||
import {
|
||||
addFilter,
|
||||
queries,
|
||||
@@ -13,7 +14,11 @@
|
||||
} from '$lib/components/filters/store';
|
||||
import { Pill, SelectSearchCheckbox } from '$lib/elements';
|
||||
import type { Column } from '$lib/helpers/types';
|
||||
import { Card, Icon } from '@appwrite.io/pink-svelte';
|
||||
import { melt, createMenubar } from '@melt-ui/svelte';
|
||||
import { type Writable } from 'svelte/store';
|
||||
import { Button } from '$lib/elements/forms';
|
||||
import { IconFilterLine } from '@appwrite.io/pink-icons-svelte';
|
||||
|
||||
export let columns: Writable<Column[]>;
|
||||
|
||||
@@ -181,103 +186,86 @@
|
||||
addFilter($columns, colId, ValidOperators.GreaterThanOrEqual, isoValue.toISOString());
|
||||
addFilter($columns, colId, ValidOperators.LessThanOrEqual, now.toISOString());
|
||||
}
|
||||
|
||||
const {
|
||||
elements: { menubar },
|
||||
builders: { createMenu }
|
||||
} = createMenubar();
|
||||
|
||||
const {
|
||||
elements: { trigger: trigger, menu: menu }
|
||||
} = createMenu();
|
||||
</script>
|
||||
|
||||
{#each [statusFilter, triggerFilter, methodFilter] as filter}
|
||||
<DropList bind:show={filter.show} noArrow class="u-margin-block-start-16">
|
||||
<Pill
|
||||
button
|
||||
on:click={() => (filter.show = !filter.show)}
|
||||
event="apply_quick_filter"
|
||||
eventData={{ source: 'function_execution', column: filter.title }}>
|
||||
{#key filter.tag}
|
||||
<span use:tagFormat>
|
||||
{filter?.tag ?? filter.title}
|
||||
</span>
|
||||
{/key}
|
||||
<span class:icon-cheveron-down={!filter.show} class:icon-cheveron-up={filter.show}>
|
||||
</span>
|
||||
</Pill>
|
||||
<svelte:fragment slot="list">
|
||||
{#each filter.options as option (option.value + option.checked)}
|
||||
<SelectSearchCheckbox
|
||||
padding={8}
|
||||
bind:value={option.checked}
|
||||
on:click={() => {
|
||||
option.checked = !option.checked;
|
||||
<div use:melt={$menubar}>
|
||||
<div use:melt={$trigger}>
|
||||
{#if $tags.length}
|
||||
<Button secondary badge={`${$tags.length}`}>
|
||||
<Icon icon={IconFilterLine} slot="start" />
|
||||
Filters
|
||||
</Button>
|
||||
{:else}
|
||||
<Button secondary>
|
||||
<Icon icon={IconFilterLine} slot="start" />
|
||||
Filters
|
||||
</Button>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<div class="menu" use:melt={$menu}>
|
||||
<Card.Base padding="xxxs" shadow={true}>
|
||||
{#each [statusFilter, triggerFilter, methodFilter] as filter}
|
||||
<MenuItem
|
||||
{filter}
|
||||
on:add={(e) => {
|
||||
console.log('test');
|
||||
addFilterAndApply(
|
||||
filter.id,
|
||||
filter.title,
|
||||
filter.operator,
|
||||
filter?.array ? null : option.checked ? option.value : null,
|
||||
e.detail.value,
|
||||
filter?.array
|
||||
? (filter.options
|
||||
.filter((opt) => opt.checked)
|
||||
.map((opt) => opt.value) ?? [])
|
||||
: []
|
||||
);
|
||||
}}>
|
||||
{option.label}
|
||||
</SelectSearchCheckbox>
|
||||
{/each}
|
||||
{#if filter.options.some((option) => option.checked)}
|
||||
<DropListItem
|
||||
padding={8}
|
||||
on:click={() => {
|
||||
filter.show = false;
|
||||
}}
|
||||
on:clear={() => {
|
||||
filter.tag = null;
|
||||
addFilterAndApply(filter.id, filter.title, filter.operator, null, []);
|
||||
}}>
|
||||
Clear selection
|
||||
</DropListItem>
|
||||
{/if}
|
||||
</svelte:fragment>
|
||||
</DropList>
|
||||
{/each}
|
||||
{#each [statusCodeFilter, createdAtFilter] as filter}
|
||||
<DropList bind:show={filter.show} noArrow class="u-margin-block-start-16">
|
||||
<Pill
|
||||
button
|
||||
on:click={() => (filter.show = !filter.show)}
|
||||
event="apply_quick_filter"
|
||||
eventData={{ source: 'function_execution', column: filter.title }}>
|
||||
{#key filter.tag}
|
||||
<span use:tagFormat>
|
||||
{filter?.tag ?? filter.title}
|
||||
</span>
|
||||
{/key}
|
||||
<span class:icon-cheveron-down={!filter.show} class:icon-cheveron-up={filter.show}>
|
||||
</span>
|
||||
</Pill>
|
||||
<svelte:fragment slot="list">
|
||||
{#each filter.options as option (option.value + option.checked)}
|
||||
<DropListItem
|
||||
padding={8}
|
||||
on:click={() => {
|
||||
filter.show = false;
|
||||
|
||||
}} />
|
||||
{/each}
|
||||
{#each [statusCodeFilter, createdAtFilter] as filter}
|
||||
<MenuItem
|
||||
variant="radio"
|
||||
{filter}
|
||||
on:add={(e) => {
|
||||
console.log('test');
|
||||
addFilterAndApply(
|
||||
filter.id,
|
||||
filter.title,
|
||||
filter.operator,
|
||||
filter?.array ? null : option.value,
|
||||
[]
|
||||
e.detail.value,
|
||||
filter?.array
|
||||
? (filter.options
|
||||
.filter((opt) => opt.checked)
|
||||
.map((opt) => opt.value) ?? [])
|
||||
: []
|
||||
);
|
||||
}}>
|
||||
{option.label}
|
||||
</DropListItem>
|
||||
{/each}
|
||||
{#if filter?.tag}
|
||||
<DropListItem
|
||||
padding={8}
|
||||
on:click={() => {
|
||||
filter.show = false;
|
||||
}}
|
||||
on:clear={() => {
|
||||
filter.tag = null;
|
||||
addFilterAndApply(filter.id, filter.title, filter.operator, null, []);
|
||||
}}>
|
||||
Clear selection
|
||||
</DropListItem>
|
||||
{/if}
|
||||
</svelte:fragment>
|
||||
</DropList>
|
||||
{/each}
|
||||
}} />
|
||||
{/each}
|
||||
</Card.Base>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.menu {
|
||||
min-width: 244px;
|
||||
z-index: 20;
|
||||
}
|
||||
</style>
|
||||
|
||||
+4
-2
@@ -17,11 +17,13 @@
|
||||
Card as PinkCard,
|
||||
Image,
|
||||
Badge,
|
||||
Divider
|
||||
Divider,
|
||||
Icon
|
||||
} from '@appwrite.io/pink-svelte';
|
||||
import { base } from '$app/paths';
|
||||
import { page } from '$app/stores';
|
||||
import { capitalize } from '$lib/helpers/string';
|
||||
import { IconExternalLink } from '@appwrite.io/pink-icons-svelte';
|
||||
|
||||
$: buttonDisabled =
|
||||
isCloud && isServiceLimited('functions', $organization?.billingPlan, $functionsList?.total);
|
||||
@@ -84,7 +86,7 @@
|
||||
href={`https://github.com/${$template.providerOwner}/${$template.providerRepositoryId}`}
|
||||
external>
|
||||
View source
|
||||
<span class="icon-external-link" />
|
||||
<Icon icon={IconExternalLink} size="s" slot="end" />
|
||||
</Button>
|
||||
{#if $canWriteFunctions}
|
||||
<ContainerButton
|
||||
|
||||
Reference in New Issue
Block a user