mirror of
https://github.com/appwrite/console.git
synced 2026-06-06 19:27:48 +00:00
feat: function overview and templates
This commit is contained in:
@@ -1,15 +1,15 @@
|
||||
<script>
|
||||
<script lang="ts">
|
||||
import { Card, Layout, Typography } from '@appwrite.io/pink-svelte';
|
||||
export let href;
|
||||
export let href: string;
|
||||
</script>
|
||||
|
||||
<Card.Link class="card" {href}>
|
||||
<Layout.Stack height="calc(182 / 16 * 1rem)" justifyContent="space-between">
|
||||
<Layout.Stack direction="row">
|
||||
<Layout.Stack gap="xs">
|
||||
<Typography.Caption color="--fgcolor-neutral-tertiary"
|
||||
<Typography.Caption variant="400" color="--fgcolor-neutral-tertiary"
|
||||
><slot name="eyebrow" /></Typography.Caption>
|
||||
<Typography.Title size="s"><slot name="title" /></Typography.Title>
|
||||
<Typography.Title size="s" truncate><slot name="title" /></Typography.Title>
|
||||
<div>
|
||||
<slot name="subtitle" />
|
||||
</div>
|
||||
@@ -22,7 +22,7 @@
|
||||
<Layout.Stack direction="row">
|
||||
<slot />
|
||||
</Layout.Stack>
|
||||
<Layout.Stack direction="row" justifyContent="flex-end">
|
||||
<Layout.Stack direction="row" justifyContent="flex-end" alignItems="center">
|
||||
<slot name="icons" />
|
||||
</Layout.Stack>
|
||||
</Layout.Stack>
|
||||
|
||||
@@ -36,6 +36,6 @@
|
||||
<Layout.Stack direction="row" alignItems="center" inline>
|
||||
<InputSelect id="rows" {options} bind:value={limit} on:change={limitChange} />
|
||||
<p class="text" style:white-space="nowrap">
|
||||
{name} per page. Total results: {sum >= 5000 ? `${sum}+` : sum}
|
||||
{name} per page. Total: {sum >= 5000 ? `${sum}+` : sum}
|
||||
</p>
|
||||
</Layout.Stack>
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
} plan`;
|
||||
export let disabled: boolean;
|
||||
export let buttonText: string;
|
||||
export let buttonMethod: () => void | Promise<void>;
|
||||
export let buttonMethod: () => void | Promise<void> = () => {};
|
||||
export let buttonHref: string = null;
|
||||
export let buttonEvent: string = buttonText?.toLocaleLowerCase();
|
||||
export let buttonEventData: Record<string, unknown> = {};
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
SvgIcon
|
||||
} from '$lib/components';
|
||||
import { toLocaleDateTime } from '$lib/helpers/date';
|
||||
import { Container, ContainerHeader } from '$lib/layout';
|
||||
import { Container } from '$lib/layout';
|
||||
import { isServiceLimited } from '$lib/stores/billing';
|
||||
import { organization } from '$lib/stores/organization';
|
||||
import { wizard } from '$lib/stores/wizard';
|
||||
@@ -21,9 +21,11 @@
|
||||
import { functionsList } from './store';
|
||||
import { canWriteFunctions } from '$lib/stores/roles';
|
||||
import type { Models } from '@appwrite.io/console';
|
||||
import { Tooltip } from '@appwrite.io/pink-svelte';
|
||||
import { IconPlus } from '@appwrite.io/pink-icons-svelte';
|
||||
import { Icon, Layout, Tooltip, Typography } from '@appwrite.io/pink-svelte';
|
||||
import { IconClock, IconPlus } from '@appwrite.io/pink-icons-svelte';
|
||||
import { goto } from '$app/navigation';
|
||||
import { Button } from '$lib/elements/forms';
|
||||
import Avatar from '$lib/components/avatar.svelte';
|
||||
|
||||
export let data;
|
||||
|
||||
@@ -73,13 +75,12 @@
|
||||
</script>
|
||||
|
||||
<Container>
|
||||
<ContainerHeader
|
||||
title="Functions"
|
||||
buttonText={$canWriteFunctions ? 'Create function' : ''}
|
||||
buttonEvent="create_function"
|
||||
buttonEventData={{ source: 'functions_overview' }}
|
||||
buttonHref={`${base}/project-${project}/functions/create-function`}
|
||||
total={data.functions.total} />
|
||||
<Layout.Stack direction="row" justifyContent="flex-end">
|
||||
<Button href={`${base}/project-${project}/functions/create-function`}>
|
||||
<Icon icon={IconPlus} slot="start" />
|
||||
Create function
|
||||
</Button>
|
||||
</Layout.Stack>
|
||||
|
||||
{#if data.functions.total}
|
||||
<CardContainer
|
||||
@@ -91,23 +92,21 @@
|
||||
{#each data.functions.functions as func}
|
||||
<GridItem1 href={`${base}/project-${project}/functions/function-${func.$id}`}>
|
||||
<svelte:fragment slot="title">
|
||||
<div class="u-flex u-gap-16 u-cross-center">
|
||||
<div class="avatar is-medium">
|
||||
<SvgIcon name={func.runtime.split('-')[0]}></SvgIcon>
|
||||
</div>
|
||||
<span class="text">{func.name}</span>
|
||||
</div>
|
||||
<Layout.Stack gap="l" alignItems="center" direction="row" inline>
|
||||
<Avatar alt={func.name} size="m">
|
||||
<SvgIcon name={func.runtime.split('-')[0]} />
|
||||
</Avatar>
|
||||
{func.name}
|
||||
</Layout.Stack>
|
||||
</svelte:fragment>
|
||||
<svelte:fragment slot="icons">
|
||||
{#if func.schedule}
|
||||
<li>
|
||||
<Tooltip>
|
||||
<span class="icon-clock" aria-hidden="true" />
|
||||
<span slot="tooltip"
|
||||
>{`Next execution:
|
||||
{#if !func.schedule}
|
||||
<Tooltip>
|
||||
<Icon icon={IconClock} size="s" />
|
||||
<span slot="tooltip"
|
||||
>{`Next execution:
|
||||
${getNextScheduledExecution(func)}`}</span>
|
||||
</Tooltip>
|
||||
</li>
|
||||
</Tooltip>
|
||||
{/if}
|
||||
</svelte:fragment>
|
||||
<Id value={func.$id} event="function">{func.$id}</Id>
|
||||
|
||||
@@ -2,32 +2,42 @@
|
||||
import { goto } from '$app/navigation';
|
||||
import { base } from '$app/paths';
|
||||
import { page } from '$app/stores';
|
||||
import {
|
||||
Collapsible,
|
||||
CollapsibleItem,
|
||||
EmptySearch,
|
||||
Pagination,
|
||||
SvgIcon
|
||||
} from '$lib/components';
|
||||
import { EmptySearch, SvgIcon } from '$lib/components';
|
||||
import { Button, InputSearch } from '$lib/elements/forms';
|
||||
import { Container, ContainerButton } from '$lib/layout';
|
||||
import { app } from '$lib/stores/app';
|
||||
import { isServiceLimited } from '$lib/stores/billing';
|
||||
import { organization } from '$lib/stores/organization';
|
||||
import { canWriteFunctions } from '$lib/stores/roles';
|
||||
import type { Models } from '@appwrite.io/console';
|
||||
import { functionsList } from '../store';
|
||||
import { debounce } from '$lib/helpers/debounce';
|
||||
import { Tooltip, Typography } from '@appwrite.io/pink-svelte';
|
||||
import {
|
||||
Accordion,
|
||||
AvatarGroup,
|
||||
Layout,
|
||||
Selector,
|
||||
Tooltip,
|
||||
Typography,
|
||||
Card as PinkCard
|
||||
} from '@appwrite.io/pink-svelte';
|
||||
import { capitalize } from '$lib/helpers/string';
|
||||
import Card from '$lib/components/card.svelte';
|
||||
import Link from '$lib/elements/link.svelte';
|
||||
import Avatar from '$lib/components/avatar.svelte';
|
||||
import PaginationWithLimit from '$lib/components/paginationWithLimit.svelte';
|
||||
|
||||
export let data;
|
||||
|
||||
function applyFilter(filter: string, value: string, event: Event) {
|
||||
const add = (event.target as EventTarget & HTMLInputElement).checked;
|
||||
function applyFilter(filter: string, value: string, event: CustomEvent) {
|
||||
const target = new URL($page.url);
|
||||
if (add) {
|
||||
target.searchParams.append(filter, value);
|
||||
if (event?.detail) {
|
||||
if (
|
||||
!target.searchParams
|
||||
.getAll(filter)
|
||||
.some((n) => n.toLowerCase() === value.toLowerCase())
|
||||
) {
|
||||
target.searchParams.append(filter, value);
|
||||
}
|
||||
} else {
|
||||
const previous = target.searchParams
|
||||
.getAll(filter)
|
||||
@@ -121,155 +131,143 @@
|
||||
</script>
|
||||
|
||||
<Container>
|
||||
<div class="u-flex u-gap-8 u-cross-center">
|
||||
<Typography.Title>Templates</Typography.Title>
|
||||
<div class="tag eyebrow-heading-3">
|
||||
<span class="text u-x-small">Experimental</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="grid-300px-1fr u-margin-block-start-24">
|
||||
<section>
|
||||
<Layout.GridFraction start={1} end={3} gap="xxl">
|
||||
<Layout.Stack gap="xl">
|
||||
<InputSearch
|
||||
placeholder="Search templates"
|
||||
value={$page.url.searchParams.get('search')}
|
||||
on:clear={clearSearch}
|
||||
on:change={applySearch} />
|
||||
<div class="u-margin-block-start-24">
|
||||
<Collapsible>
|
||||
<CollapsibleItem>
|
||||
<svelte:fragment slot="title">Use case</svelte:fragment>
|
||||
<ul class="form-list u-row-gap-16">
|
||||
{#each [...data.useCases] as useCase}
|
||||
<li class="form-item">
|
||||
<label class="u-flex u-cross-center u-gap-16">
|
||||
<input
|
||||
type="checkbox"
|
||||
class="is-small"
|
||||
checked={isChecked(useCase)}
|
||||
on:change={(e) => applyFilter('useCase', useCase, e)} />
|
||||
<span class="u-trim-1">{capitalize(useCase)}</span>
|
||||
</label>
|
||||
</li>
|
||||
{/each}
|
||||
</ul>
|
||||
</CollapsibleItem>
|
||||
<CollapsibleItem>
|
||||
<svelte:fragment slot="title">Runtime</svelte:fragment>
|
||||
<ul class="form-list u-row-gap-16">
|
||||
{#each [...data.runtimes] as runtime}
|
||||
{@const icon = getIconFromRuntime(runtime)}
|
||||
<li class="form-item">
|
||||
<label class="u-flex u-cross-center u-gap-16">
|
||||
<input
|
||||
type="checkbox"
|
||||
class="is-small"
|
||||
checked={$page.url.searchParams
|
||||
.getAll('runtime')
|
||||
.includes(runtime)}
|
||||
on:change={(e) => applyFilter('runtime', runtime, e)} />
|
||||
<div
|
||||
class="u-flex u-cross-center u-gap-8 functions-avatar-holder">
|
||||
<div class="avatar is-size-x-small">
|
||||
<SvgIcon name={icon} iconSize="small" />
|
||||
</div>
|
||||
<div class="u-trim-1 u-capitalize">
|
||||
{runtime?.split('-')?.join(' ')}
|
||||
</div>
|
||||
</div>
|
||||
</label>
|
||||
</li>
|
||||
{/each}
|
||||
</ul>
|
||||
</CollapsibleItem>
|
||||
</Collapsible>
|
||||
</div>
|
||||
<Layout.Stack>
|
||||
<Accordion title="Use case">
|
||||
<Layout.Stack>
|
||||
{#each [...data.useCases] as useCase}
|
||||
<Layout.Stack direction="row" gap="s">
|
||||
<Selector.Checkbox
|
||||
id={useCase}
|
||||
size="s"
|
||||
label={useCase === 'ai'
|
||||
? useCase.toUpperCase()
|
||||
: capitalize(useCase)}
|
||||
checked={isChecked(useCase)}
|
||||
on:change={(e) => {
|
||||
console.log(useCase, e);
|
||||
applyFilter('useCase', useCase, e);
|
||||
}} />
|
||||
</Layout.Stack>
|
||||
{/each}
|
||||
</Layout.Stack>
|
||||
</Accordion>
|
||||
<Accordion title="Runtime">
|
||||
<Layout.Stack>
|
||||
{#each [...data.runtimes] as runtime}
|
||||
<!-- {@const icon = getIconFromRuntime(runtime)} -->
|
||||
<Layout.Stack direction="row" gap="s">
|
||||
<Selector.Checkbox
|
||||
id={runtime}
|
||||
size="s"
|
||||
label={runtime?.split('-')?.join(' ')}
|
||||
checked={$page.url.searchParams
|
||||
.getAll('runtime')
|
||||
.includes(runtime)}
|
||||
on:change={(e) => applyFilter('runtime', runtime, e)} />
|
||||
</Layout.Stack>
|
||||
{/each}
|
||||
</Layout.Stack>
|
||||
</Accordion>
|
||||
</Layout.Stack>
|
||||
|
||||
<section class="card u-margin-block-start-24">
|
||||
<h4 class="body-text-1 u-bold">Contribute</h4>
|
||||
<p class="u-margin-block-start-16">
|
||||
Have an idea for a function template? View our <a
|
||||
class="link"
|
||||
href="https://github.com/appwrite/templates/blob/main/CONTRIBUTING.md"
|
||||
target="_blank">contribution guidelines</a
|
||||
>.
|
||||
</p>
|
||||
</section>
|
||||
</section>
|
||||
<section>
|
||||
<Card isTile radius="s" padding="xs">
|
||||
<Layout.Stack gap="xxxs">
|
||||
<Typography.Text variant="m-500" color="--fgcolor-neutral-primary">
|
||||
Contribute
|
||||
</Typography.Text>
|
||||
<Typography.Text variant="m-400">
|
||||
Got a function template idea? <Link
|
||||
variant="muted"
|
||||
external
|
||||
href="https://github.com/appwrite/templates/blob/main/CONTRIBUTING.md"
|
||||
>View the contribution guidelines.
|
||||
</Link>
|
||||
</Typography.Text>
|
||||
</Layout.Stack>
|
||||
</Card>
|
||||
</Layout.Stack>
|
||||
<Layout.Stack gap="l">
|
||||
{#if data.templates.length > 0}
|
||||
<ul
|
||||
class="grid-box"
|
||||
style="--grid-item-size:22rem; --grid-item-size-small-screens:19rem">
|
||||
<Layout.Grid columns={2} columnsXL={3} columnsXS={1}>
|
||||
{#each data.templates as template}
|
||||
{@const baseRuntimes = getBaseRuntimes(template.runtimes)}
|
||||
{@const displayed = baseRuntimes.slice(0, 2)}
|
||||
{@const hidden = baseRuntimes.slice(1, -1)}
|
||||
<li>
|
||||
<article class="card u-min-height-100-percent">
|
||||
<div class="u-flex u-gap-16 u-cross-center u-main-space-between">
|
||||
<h2
|
||||
class="body-text-1 u-bold u-trim-1 functions-template-title">
|
||||
<PinkCard.Base radius="m" padding="xs">
|
||||
<Layout.Stack height="100%" justifyContent="space-between" gap="xl">
|
||||
<Layout.Stack gap="xxxs">
|
||||
<Typography.Text
|
||||
variant="m-500"
|
||||
color="--fgcolor-neutral-primary">
|
||||
{template.name}
|
||||
</h2>
|
||||
<ul class="avatars-group is-with-border">
|
||||
</Typography.Text>
|
||||
|
||||
<Typography.Text variant="m-400">
|
||||
{template.tagline}
|
||||
</Typography.Text>
|
||||
</Layout.Stack>
|
||||
|
||||
<Layout.Stack
|
||||
direction="row"
|
||||
justifyContent="space-between"
|
||||
alignItems="center">
|
||||
<AvatarGroup>
|
||||
{#each displayed as runtime}
|
||||
{@const icon = getIconFromRuntime(runtime.name)}
|
||||
{#if icon}
|
||||
<li
|
||||
class="avatars-group-item functions-avatar-holder">
|
||||
<div class="avatar is-size-small">
|
||||
<img
|
||||
style:--p-text-size="20px"
|
||||
src={`${base}/icons/${$app.themeInUse}/color/${icon}.svg`}
|
||||
alt={icon}
|
||||
aria-hidden="true"
|
||||
aria-label={icon} />
|
||||
</div>
|
||||
</li>
|
||||
<Avatar alt={runtime.name} size="xs">
|
||||
<SvgIcon name={icon} iconSize="small" />
|
||||
</Avatar>
|
||||
{/if}
|
||||
{/each}
|
||||
{#if hidden.length}
|
||||
<li class="avatars-group-item functions-avatar-holder">
|
||||
<Tooltip>
|
||||
<div class="avatar is-size-small">
|
||||
<Tooltip>
|
||||
<Avatar alt="hidden runtime number" size="xs">
|
||||
<span style:font-size="10px">
|
||||
+{hidden.length}
|
||||
</div>
|
||||
<span slot="tooltip"
|
||||
>{hidden
|
||||
.map((n) => n.name)
|
||||
.join(', ')}</span>
|
||||
</Tooltip>
|
||||
</li>
|
||||
</span>
|
||||
</Avatar>
|
||||
<span slot="tooltip"
|
||||
>{hidden.map((n) => n.name).join(', ')}</span>
|
||||
</Tooltip>
|
||||
{/if}
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<p class="u-margin-block-start-20 u-trim-2 u-break-word">
|
||||
{template.tagline}
|
||||
</p>
|
||||
|
||||
<div class="u-flex u-gap-16 u-main-end u-margin-block-start-24">
|
||||
<Button
|
||||
href={`${base}/project-${$page.params.project}/functions/templates/template-${template.id}`}
|
||||
text>
|
||||
<span class="text">View details</span>
|
||||
</Button>
|
||||
{#if $canWriteFunctions}
|
||||
<ContainerButton
|
||||
title="functions"
|
||||
disabled={buttonDisabled}
|
||||
buttonType="secondary"
|
||||
buttonMethod={() => connectTemplate(template)}
|
||||
showIcon={false}
|
||||
buttonText="Create function"
|
||||
buttonEventData={{ source: 'functions_template' }}
|
||||
buttonEvent="create_function" />
|
||||
{/if}
|
||||
</div>
|
||||
</article>
|
||||
</li>
|
||||
</AvatarGroup>
|
||||
<Layout.Stack
|
||||
direction="row"
|
||||
gap="s"
|
||||
alignItems="center"
|
||||
inline>
|
||||
<Button
|
||||
href={`${base}/project-${$page.params.project}/functions/templates/template-${template.id}`}
|
||||
text>
|
||||
<span class="text">Details</span>
|
||||
</Button>
|
||||
{#if $canWriteFunctions}
|
||||
<ContainerButton
|
||||
title="functions"
|
||||
disabled={buttonDisabled}
|
||||
buttonType="secondary"
|
||||
buttonHref={`${base}/project-${$page.params.project}/functions/create-function/template-${template.id}`}
|
||||
showIcon={false}
|
||||
buttonText="Create"
|
||||
buttonEventData={{
|
||||
source: 'functions_template'
|
||||
}}
|
||||
buttonEvent="create_function" />
|
||||
{/if}
|
||||
</Layout.Stack>
|
||||
</Layout.Stack>
|
||||
</Layout.Stack>
|
||||
</PinkCard.Base>
|
||||
{/each}
|
||||
</ul>
|
||||
</Layout.Grid>
|
||||
{:else}
|
||||
<EmptySearch hidePagination>
|
||||
<div class="common-section">
|
||||
@@ -283,12 +281,13 @@
|
||||
</div>
|
||||
</EmptySearch>
|
||||
{/if}
|
||||
<div class="u-flex u-margin-block-start-32 u-main-space-between u-cross-center">
|
||||
<p class="text">Total templates: {data.sum}</p>
|
||||
<Pagination limit={data.limit} offset={data.offset} sum={data.sum} />
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
<PaginationWithLimit
|
||||
name="Templates"
|
||||
limit={data.limit}
|
||||
offset={data.offset}
|
||||
total={data.sum} />
|
||||
</Layout.Stack>
|
||||
</Layout.GridFraction>
|
||||
</Container>
|
||||
|
||||
<style>
|
||||
|
||||
+65
-61
@@ -1,6 +1,5 @@
|
||||
<script lang="ts">
|
||||
import { Card, Collapsible } from '$lib/components';
|
||||
import { Pill } from '$lib/elements';
|
||||
import { Card } from '$lib/components';
|
||||
import { Button } from '$lib/elements/forms';
|
||||
import { Container, ContainerButton } from '$lib/layout';
|
||||
import { isCloud } from '$lib/system';
|
||||
@@ -12,71 +11,76 @@
|
||||
import { organization } from '$lib/stores/organization';
|
||||
import { functionsList } from '../../store';
|
||||
import { canWriteFunctions } from '$lib/stores/roles';
|
||||
import { Typography } from '@appwrite.io/pink-svelte';
|
||||
import {
|
||||
Layout,
|
||||
Typography,
|
||||
Card as PinkCard,
|
||||
Image,
|
||||
Badge,
|
||||
Divider
|
||||
} from '@appwrite.io/pink-svelte';
|
||||
import { base } from '$app/paths';
|
||||
import { page } from '$app/stores';
|
||||
import { capitalize } from '$lib/helpers/string';
|
||||
|
||||
$: buttonDisabled =
|
||||
isCloud && isServiceLimited('functions', $organization?.billingPlan, $functionsList?.total);
|
||||
</script>
|
||||
|
||||
<Container>
|
||||
<div class="grid-300px-1fr">
|
||||
<section>
|
||||
<Collapsible>
|
||||
<li class="collapsible-item-divider collapsible-item">
|
||||
<h3 class="u-flex u-gap-16 body-text-2 u-bold u-padding-block-12">
|
||||
Use cases
|
||||
<span class="inline-tag">{$template.useCases.length}</span>
|
||||
</h3>
|
||||
<div class="collapsible-content u-flex u-flex-wrap u-gap-8">
|
||||
{#each $template.useCases as useCase}
|
||||
<Pill>{useCase}</Pill>
|
||||
{/each}
|
||||
</div>
|
||||
</li>
|
||||
<li class="collapsible-item-divider collapsible-item">
|
||||
<h3 class="u-flex u-gap-16 body-text-2 u-bold u-padding-block-12">
|
||||
Runtimes
|
||||
<span class="inline-tag">{$template.runtimes.length}</span>
|
||||
</h3>
|
||||
<div class="collapsible-content u-flex u-flex-wrap u-gap-8">
|
||||
{#each $template.runtimes as runtime}
|
||||
<Pill>{runtime.name}</Pill>
|
||||
{/each}
|
||||
</div>
|
||||
</li>
|
||||
<li class="collapsible-item-divider collapsible-item">
|
||||
<section class="card u-margin-block-start-24">
|
||||
<h4 class="body-text-1 u-bold">Published by</h4>
|
||||
<img
|
||||
class="u-margin-block-start-8"
|
||||
src={$app.themeInUse === 'dark' ? AppwriteLogoDark : AppwriteLogoLight}
|
||||
width="120"
|
||||
height="22"
|
||||
alt="Appwrite" />
|
||||
</section>
|
||||
</li>
|
||||
</Collapsible>
|
||||
</section>
|
||||
<section>
|
||||
<Card>
|
||||
<Typography.Title size="s">
|
||||
<span class="u-flex u-cross-center u-gap-16 functions-avatar-holder">
|
||||
<div class="avatar is-size-small">
|
||||
<span
|
||||
style:--p-text-size="20px"
|
||||
class={$template.icon}
|
||||
aria-hidden="true" />
|
||||
</div>
|
||||
{$template.name}
|
||||
</span>
|
||||
</Typography.Title>
|
||||
<p class="u-margin-block-start-24">{$template.tagline}</p>
|
||||
<Layout.GridFraction start={1} end={3} gap="xxl">
|
||||
<div>
|
||||
<PinkCard.Base radius="s" padding="s">
|
||||
<Layout.Stack gap="s">
|
||||
<Typography.Text color="--fgcolor-neutral-tertiary"
|
||||
>Published by</Typography.Text>
|
||||
<Image
|
||||
fit="contain"
|
||||
src={$app.themeInUse === 'dark' ? AppwriteLogoDark : AppwriteLogoLight}
|
||||
width={120}
|
||||
height={22}
|
||||
alt="Appwrite" />
|
||||
</Layout.Stack>
|
||||
</PinkCard.Base>
|
||||
</div>
|
||||
<Card radius="m" padding="s">
|
||||
<Layout.Stack gap="xl">
|
||||
<Layout.Stack gap="l">
|
||||
<Layout.Stack gap="xxs">
|
||||
<Typography.Text variant="m-400" color="--fgcolor-neutral-tertiary">
|
||||
About
|
||||
</Typography.Text>
|
||||
<Typography.Text variant="m-400" color="--fgcolor-neutral-primary">
|
||||
{$template.tagline}
|
||||
</Typography.Text>
|
||||
</Layout.Stack>
|
||||
<Layout.Stack gap="xxs">
|
||||
<Typography.Text variant="m-400" color="--fgcolor-neutral-tertiary">
|
||||
Use cases
|
||||
</Typography.Text>
|
||||
<Layout.Stack direction="row" gap="xs" wrap="wrap">
|
||||
{#each $template.useCases as useCase}
|
||||
<Badge variant="secondary" size="s" content={capitalize(useCase)} />
|
||||
{/each}
|
||||
</Layout.Stack>
|
||||
</Layout.Stack>
|
||||
<Layout.Stack gap="xxs">
|
||||
<Typography.Text variant="m-400" color="--fgcolor-neutral-tertiary">
|
||||
Runtimes
|
||||
</Typography.Text>
|
||||
<Layout.Stack direction="row" gap="xs" wrap="wrap">
|
||||
{#each $template.runtimes as runtime}
|
||||
<Badge variant="secondary" size="s" content={runtime.name} />
|
||||
{/each}
|
||||
</Layout.Stack>
|
||||
</Layout.Stack>
|
||||
</Layout.Stack>
|
||||
|
||||
<div class="u-flex u-gap-16 u-main-end u-margin-block-start-24 u-flex-wrap">
|
||||
<Divider />
|
||||
|
||||
<Layout.Stack direction="row" gap="s" justifyContent="flex-end">
|
||||
<Button
|
||||
text
|
||||
secondary
|
||||
href={`https://github.com/${$template.providerOwner}/${$template.providerRepositoryId}`}
|
||||
external>
|
||||
View source
|
||||
@@ -91,10 +95,10 @@
|
||||
buttonText="Create function"
|
||||
buttonEvent="create_function" />
|
||||
{/if}
|
||||
</div>
|
||||
</Card>
|
||||
</section>
|
||||
</div>
|
||||
</Layout.Stack>
|
||||
</Layout.Stack>
|
||||
</Card>
|
||||
</Layout.GridFraction>
|
||||
</Container>
|
||||
|
||||
<style>
|
||||
|
||||
Reference in New Issue
Block a user