mirror of
https://github.com/appwrite/console.git
synced 2026-06-06 19:27:48 +00:00
Merge remote-tracking branch 'origin/main' into feat-project-changes
This commit is contained in:
+1
-1
@@ -1,5 +1,5 @@
|
||||
PUBLIC_CONSOLE_MODE=self-hosted
|
||||
PUBLIC_CONSOLE_FEATURE_FLAGS=
|
||||
PUBLIC_CONSOLE_FEATURE_FLAGS=sites,csv-import,attribute-encrypt,index-lengths
|
||||
PUBLIC_APPWRITE_MULTI_REGION=false
|
||||
PUBLIC_APPWRITE_ENDPOINT=http://localhost/v1
|
||||
PUBLIC_STRIPE_KEY=
|
||||
|
||||
@@ -79,7 +79,7 @@ jobs:
|
||||
labels: ${{ steps.meta.outputs.labels }}
|
||||
build-args: |
|
||||
"PUBLIC_CONSOLE_MODE=cloud"
|
||||
"PUBLIC_CONSOLE_FEATURE_FLAGS=sites,csv-import"
|
||||
"PUBLIC_CONSOLE_FEATURE_FLAGS=sites,csv-import,attribute-encrypt,index-lengths"
|
||||
"PUBLIC_APPWRITE_MULTI_REGION=true"
|
||||
"PUBLIC_GROWTH_ENDPOINT=${{ secrets.PUBLIC_GROWTH_ENDPOINT }}"
|
||||
"PUBLIC_STRIPE_KEY=${{ secrets.PUBLIC_STRIPE_KEY_STAGE }}"
|
||||
@@ -118,7 +118,7 @@ jobs:
|
||||
build-args: |
|
||||
"PUBLIC_CONSOLE_MODE=self-hosted"
|
||||
"PUBLIC_APPWRITE_MULTI_REGION=false"
|
||||
"PUBLIC_CONSOLE_FEATURE_FLAGS=sites,csv-import"
|
||||
"PUBLIC_CONSOLE_FEATURE_FLAGS=sites,csv-import,attribute-encrypt,index-lengths"
|
||||
"PUBLIC_GROWTH_ENDPOINT=${{ secrets.PUBLIC_GROWTH_ENDPOINT }}"
|
||||
|
||||
publish-cloud-no-regions:
|
||||
@@ -156,6 +156,6 @@ jobs:
|
||||
build-args: |
|
||||
"PUBLIC_CONSOLE_MODE=cloud"
|
||||
"PUBLIC_APPWRITE_MULTI_REGION=false"
|
||||
"PUBLIC_CONSOLE_FEATURE_FLAGS=sites,csv-import"
|
||||
"PUBLIC_CONSOLE_FEATURE_FLAGS=sites,csv-import,attribute-encrypt,index-lengths"
|
||||
"PUBLIC_STRIPE_KEY=${{ secrets.PUBLIC_STRIPE_KEY_STAGE }}"
|
||||
"PUBLIC_GROWTH_ENDPOINT=${{ secrets.PUBLIC_GROWTH_ENDPOINT }}"
|
||||
|
||||
+2
-2
@@ -22,11 +22,11 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@ai-sdk/svelte": "^1.1.24",
|
||||
"@appwrite.io/console": "https://pkg.pr.new/appwrite-labs/cloud/@appwrite.io/console@a5e5564",
|
||||
"@appwrite.io/console": "^1.9.0",
|
||||
"@appwrite.io/pink-icons": "0.25.0",
|
||||
"@appwrite.io/pink-icons-svelte": "^2.0.0-RC.1",
|
||||
"@appwrite.io/pink-legacy": "^1.0.3",
|
||||
"@appwrite.io/pink-svelte": "https://pkg.vc/-/@appwrite/@appwrite.io/pink-svelte@40bae6b",
|
||||
"@appwrite.io/pink-svelte": "https://pkg.pr.new/appwrite/pink/@appwrite.io/pink-svelte@ee1b778",
|
||||
"@popperjs/core": "^2.11.8",
|
||||
"@sentry/sveltekit": "^8.38.0",
|
||||
"@stripe/stripe-js": "^3.5.0",
|
||||
|
||||
Generated
+19
-19
@@ -12,8 +12,8 @@ importers:
|
||||
specifier: ^1.1.24
|
||||
version: 1.1.24(svelte@5.25.3)(zod@3.24.3)
|
||||
'@appwrite.io/console':
|
||||
specifier: https://pkg.pr.new/appwrite-labs/cloud/@appwrite.io/console@a5e5564
|
||||
version: https://pkg.pr.new/appwrite-labs/cloud/@appwrite.io/console@a5e5564
|
||||
specifier: ^1.9.0
|
||||
version: 1.9.0
|
||||
'@appwrite.io/pink-icons':
|
||||
specifier: 0.25.0
|
||||
version: 0.25.0
|
||||
@@ -24,8 +24,8 @@ importers:
|
||||
specifier: ^1.0.3
|
||||
version: 1.0.3
|
||||
'@appwrite.io/pink-svelte':
|
||||
specifier: https://pkg.vc/-/@appwrite/@appwrite.io/pink-svelte@40bae6b
|
||||
version: https://pkg.vc/-/@appwrite/%40appwrite.io%2Fpink-svelte@40bae6b(svelte@5.25.3)
|
||||
specifier: https://pkg.pr.new/appwrite/pink/@appwrite.io/pink-svelte@ee1b778
|
||||
version: https://pkg.pr.new/appwrite/pink/@appwrite.io/pink-svelte@ee1b778(svelte@5.25.3)
|
||||
'@popperjs/core':
|
||||
specifier: ^2.11.8
|
||||
version: 2.11.8
|
||||
@@ -257,12 +257,12 @@ packages:
|
||||
'@analytics/type-utils@0.6.2':
|
||||
resolution: {integrity: sha512-TD+xbmsBLyYy/IxFimW/YL/9L2IEnM7/EoV9Aeh56U64Ify8o27HJcKjo38XY9Tcn0uOq1AX3thkKgvtWvwFQg==}
|
||||
|
||||
'@appwrite.io/console@https://pkg.pr.new/appwrite-labs/cloud/@appwrite.io/console@a5e5564':
|
||||
resolution: {tarball: https://pkg.pr.new/appwrite-labs/cloud/@appwrite.io/console@a5e5564}
|
||||
version: 1.8.0
|
||||
'@appwrite.io/console@1.9.0':
|
||||
resolution: {integrity: sha512-g8+zfdBF8mz7tRUER4CGVe5FHWQVLp8TlY/UOGCZ2TgUF1qnpNu/DhiQgph8uF+QZ9jDKCLAAZlP5//9t7ckWA==}
|
||||
|
||||
'@appwrite.io/pink-icons-svelte@2.0.0-RC.1':
|
||||
resolution: {integrity: sha512-iLFlV55hj8mGuAbmxJGenxN5RaZMmVT4GJb9dv/MP1xBAtYibFq7JvBcxm18qV2KU8c31Rntf+Ub4GL7HwqTYg==}
|
||||
'@appwrite.io/pink-icons-svelte@https://pkg.pr.new/appwrite/pink/@appwrite.io/pink-icons-svelte@ee1b7788cd3f877c9aa1b6487976bd63a6196870':
|
||||
resolution: {tarball: https://pkg.pr.new/appwrite/pink/@appwrite.io/pink-icons-svelte@ee1b7788cd3f877c9aa1b6487976bd63a6196870}
|
||||
version: 2.0.0-RC.1
|
||||
peerDependencies:
|
||||
svelte: ^4.0.0
|
||||
|
||||
@@ -281,8 +281,8 @@ packages:
|
||||
'@appwrite.io/pink-legacy@1.0.3':
|
||||
resolution: {integrity: sha512-GGde5fmPhs+s6/3aFeMPc/kKADG/gTFkYQSy6oBN8pK0y0XNCLrZZgBv+EBbdhwdtqVEWXa0X85Mv9w7jcIlwQ==}
|
||||
|
||||
'@appwrite.io/pink-svelte@https://pkg.vc/-/@appwrite/%40appwrite.io%2Fpink-svelte@40bae6b':
|
||||
resolution: {tarball: https://pkg.vc/-/@appwrite/%40appwrite.io%2Fpink-svelte@40bae6b}
|
||||
'@appwrite.io/pink-svelte@https://pkg.pr.new/appwrite/pink/@appwrite.io/pink-svelte@ee1b778':
|
||||
resolution: {tarball: https://pkg.pr.new/appwrite/pink/@appwrite.io/pink-svelte@ee1b778}
|
||||
version: 2.0.0-RC.2
|
||||
peerDependencies:
|
||||
svelte: ^4.0.0
|
||||
@@ -1340,8 +1340,8 @@ packages:
|
||||
'@types/prop-types@15.7.14':
|
||||
resolution: {integrity: sha512-gNMvNH49DJ7OJYv+KAKn0Xp45p8PLl6zo2YnvDIbTd4J6MER2BmWN49TG7n9LvkyihINxeKW8+3bfS2yDC9dzQ==}
|
||||
|
||||
'@types/react@18.3.22':
|
||||
resolution: {integrity: sha512-vUhG0YmQZ7kL/tmKLrD3g5zXbXXreZXB3pmROW8bg3CnLnpjkRVwUlLne7Ufa2r9yJ8+/6B73RzhAek5TBKh2Q==}
|
||||
'@types/react@18.3.23':
|
||||
resolution: {integrity: sha512-/LDXMQh55EzZQ0uVAZmKKhfENivEvWz6E+EYzh+/MCjMhNsotd+ZHhBGIjFDTi6+fz0OhQQQLbTgdQIxxCsC0w==}
|
||||
|
||||
'@types/remarkable@2.0.8':
|
||||
resolution: {integrity: sha512-eKXqPZfpQl1kOADjdKchHrp2gwn9qMnGXhH/AtZe0UrklzhGJkawJo/Y/D0AlWcdWoWamFNIum8+/nkAISQVGg==}
|
||||
@@ -3635,9 +3635,9 @@ snapshots:
|
||||
|
||||
'@analytics/type-utils@0.6.2': {}
|
||||
|
||||
'@appwrite.io/console@https://pkg.pr.new/appwrite-labs/cloud/@appwrite.io/console@a5e5564': {}
|
||||
'@appwrite.io/console@1.9.0': {}
|
||||
|
||||
'@appwrite.io/pink-icons-svelte@2.0.0-RC.1(svelte@5.25.3)':
|
||||
'@appwrite.io/pink-icons-svelte@https://pkg.pr.new/appwrite/pink/@appwrite.io/pink-icons-svelte@ee1b7788cd3f877c9aa1b6487976bd63a6196870(svelte@5.25.3)':
|
||||
dependencies:
|
||||
svelte: 5.25.3
|
||||
|
||||
@@ -3654,9 +3654,9 @@ snapshots:
|
||||
'@appwrite.io/pink-icons': 1.0.0
|
||||
the-new-css-reset: 1.11.3
|
||||
|
||||
'@appwrite.io/pink-svelte@https://pkg.vc/-/@appwrite/%40appwrite.io%2Fpink-svelte@40bae6b(svelte@5.25.3)':
|
||||
'@appwrite.io/pink-svelte@https://pkg.pr.new/appwrite/pink/@appwrite.io/pink-svelte@ee1b778(svelte@5.25.3)':
|
||||
dependencies:
|
||||
'@appwrite.io/pink-icons-svelte': 2.0.0-RC.1(svelte@5.25.3)
|
||||
'@appwrite.io/pink-icons-svelte': https://pkg.pr.new/appwrite/pink/@appwrite.io/pink-icons-svelte@ee1b7788cd3f877c9aa1b6487976bd63a6196870(svelte@5.25.3)
|
||||
'@floating-ui/dom': 1.6.13
|
||||
'@melt-ui/pp': 0.3.2(@melt-ui/svelte@0.86.6(svelte@5.25.3))(svelte@5.25.3)
|
||||
'@melt-ui/svelte': 0.86.6(svelte@5.25.3)
|
||||
@@ -4791,7 +4791,7 @@ snapshots:
|
||||
|
||||
'@types/prop-types@15.7.14': {}
|
||||
|
||||
'@types/react@18.3.22':
|
||||
'@types/react@18.3.23':
|
||||
dependencies:
|
||||
'@types/prop-types': 15.7.14
|
||||
csstype: 3.1.3
|
||||
@@ -6752,7 +6752,7 @@ snapshots:
|
||||
|
||||
svelte-motion@0.12.2(svelte@5.25.3):
|
||||
dependencies:
|
||||
'@types/react': 18.3.22
|
||||
'@types/react': 18.3.23
|
||||
framesync: 6.1.2
|
||||
popmotion: 11.0.5
|
||||
style-value-types: 5.1.2
|
||||
|
||||
@@ -1,10 +1,13 @@
|
||||
<script lang="ts">
|
||||
import { base } from '$app/paths';
|
||||
import { isTabletViewport } from '$lib/stores/viewport';
|
||||
import { createEventDispatcher, onMount, onDestroy } from 'svelte';
|
||||
import { Button } from '$lib/elements/forms';
|
||||
import { Icon } from '@appwrite.io/pink-svelte';
|
||||
import { IconX } from '@appwrite.io/pink-icons-svelte';
|
||||
import { isTabletViewport } from '$lib/stores/viewport';
|
||||
import PinkBackground from '$lib/images/pink-background.svg';
|
||||
import { createEventDispatcher, onMount, onDestroy } from 'svelte';
|
||||
|
||||
export let variant: 'gradient' | 'image' = 'gradient';
|
||||
|
||||
let container: HTMLElement;
|
||||
const dispatch = createEventDispatcher();
|
||||
@@ -19,8 +22,7 @@
|
||||
const alertHeight = container?.getBoundingClientRect()?.height || 0;
|
||||
const { header, sidebar, content } = queryLayoutElements();
|
||||
const headerHeight = header?.getBoundingClientRect().height || 0;
|
||||
const offset = alertHeight + (!isTabletViewport && header ? headerHeight : 0);
|
||||
|
||||
const offset = alertHeight + (!$isTabletViewport && header ? headerHeight : 0);
|
||||
if (header) header.style.top = `${alertHeight}px`;
|
||||
if (sidebar) {
|
||||
sidebar.style.top = `${offset}px`;
|
||||
@@ -35,23 +37,32 @@
|
||||
|
||||
<svelte:window on:resize={setNavigationHeight} />
|
||||
|
||||
<div bind:this={container} class="top-banner alert is-action is-action-and-top-sticky">
|
||||
<div class="top-banner-bg">
|
||||
<div class="top-banner-bg-1">
|
||||
<img
|
||||
src={`${base}/images/top-banner/bg-pink-desktop.svg`}
|
||||
width="1283"
|
||||
height="1278"
|
||||
alt="" />
|
||||
<div
|
||||
bind:this={container}
|
||||
class:darker={variant === 'image'}
|
||||
class="top-banner alert is-action is-action-and-top-sticky">
|
||||
{#if variant === 'gradient'}
|
||||
<div class="top-banner-bg">
|
||||
<div class="top-banner-bg-1">
|
||||
<img
|
||||
src={`${base}/images/top-banner/bg-pink-desktop.svg`}
|
||||
width="1283"
|
||||
height="1278"
|
||||
alt="" />
|
||||
</div>
|
||||
<div class="top-banner-bg-2">
|
||||
<img
|
||||
src={`${base}/images/top-banner/bg-mint-desktop.svg`}
|
||||
width="1051"
|
||||
height="1271"
|
||||
alt="" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="top-banner-bg-2">
|
||||
<img
|
||||
src={`${base}/images/top-banner/bg-mint-desktop.svg`}
|
||||
width="1051"
|
||||
height="1271"
|
||||
alt="" />
|
||||
{:else}
|
||||
<div class="centered-image-only">
|
||||
<img src={PinkBackground} width="1283" height="1278" alt="" />
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<div class="top-banner-content u-color-text-primary">
|
||||
<slot />
|
||||
@@ -62,17 +73,39 @@
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
<style lang="scss">
|
||||
.alert {
|
||||
top: 0;
|
||||
width: 100%;
|
||||
z-index: 100;
|
||||
position: fixed;
|
||||
padding: 0.8rem;
|
||||
|
||||
&.darker {
|
||||
background: var(--bgcolor-neutral-default);
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
:global(.top-banner-button.position) {
|
||||
.centered-image-only {
|
||||
top: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
padding-left: 25vw;
|
||||
position: absolute;
|
||||
border-bottom: 1px solid var(--border-neutral);
|
||||
|
||||
@media (max-width: 768px) {
|
||||
& img {
|
||||
max-width: 100vw;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
:global(.top-banner-button.position) {
|
||||
z-index: 1;
|
||||
@media (max-width: 768px) {
|
||||
position: absolute;
|
||||
padding-block-start: 1rem;
|
||||
padding-block-end: 3.7625rem;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
<script lang="ts">
|
||||
import { BillingPlan } from '$lib/constants';
|
||||
import { BASE_BILLING_PLANS, BillingPlan } from '$lib/constants';
|
||||
import { formatCurrency } from '$lib/helpers/numbers';
|
||||
import { plansInfo, type Tier, tierFree, tierPro, tierScale } from '$lib/stores/billing';
|
||||
import { organization } from '$lib/stores/organization';
|
||||
import { currentPlan, organization } from '$lib/stores/organization';
|
||||
import { Badge, Layout, Typography } from '@appwrite.io/pink-svelte';
|
||||
import { LabelCard } from '..';
|
||||
|
||||
@@ -14,6 +14,8 @@
|
||||
$: freePlan = $plansInfo.get(BillingPlan.FREE);
|
||||
$: proPlan = $plansInfo.get(BillingPlan.PRO);
|
||||
$: scalePlan = $plansInfo.get(BillingPlan.SCALE);
|
||||
|
||||
$: isBasePlan = BASE_BILLING_PLANS.includes($currentPlan?.$id);
|
||||
</script>
|
||||
|
||||
<Layout.Stack>
|
||||
@@ -72,4 +74,25 @@
|
||||
{formatCurrency(scalePlan?.price ?? 0)} per month + usage
|
||||
</Typography.Text>
|
||||
</LabelCard>
|
||||
{#if $currentPlan && !isBasePlan}
|
||||
<LabelCard
|
||||
name="plan"
|
||||
bind:group={billingPlan}
|
||||
value={$currentPlan.$id}
|
||||
title={$currentPlan.name}>
|
||||
<svelte:fragment slot="action">
|
||||
{#if $organization?.billingPlan === $currentPlan.$id && !isNewOrg}
|
||||
<Badge variant="secondary" size="xs" content="Current plan" />
|
||||
{/if}
|
||||
</svelte:fragment>
|
||||
<Typography.Caption variant="400">
|
||||
{$currentPlan.desc}
|
||||
</Typography.Caption>
|
||||
<Typography.Text>
|
||||
{@const isZeroPrice = ($currentPlan?.price ?? 0) <= 0}
|
||||
{@const price = formatCurrency($currentPlan?.price ?? 0)}
|
||||
{isZeroPrice ? price : `${price} per month + usage`}
|
||||
</Typography.Text>
|
||||
</LabelCard>
|
||||
{/if}
|
||||
</Layout.Stack>
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
import { isCloud } from '$lib/system';
|
||||
import { goto } from '$app/navigation';
|
||||
import { base } from '$app/paths';
|
||||
import { newOrgModal } from '$lib/stores/organization';
|
||||
import { currentPlan, newOrgModal } from '$lib/stores/organization';
|
||||
import { Click, trackEvent } from '$lib/actions/analytics';
|
||||
import { page } from '$app/stores';
|
||||
|
||||
@@ -182,6 +182,14 @@
|
||||
projectsBottomSheetOpen = false;
|
||||
}
|
||||
}
|
||||
|
||||
$: correctPlanName =
|
||||
// the plan names are hardcoded in some cases and are not available locally,
|
||||
// so we rely on the plan's source of truth - `$currentPlan`
|
||||
$currentPlan &&
|
||||
$currentPlan?.name.toLocaleLowerCase() !== selectedOrg?.tierName.toLocaleLowerCase()
|
||||
? $currentPlan.name
|
||||
: selectedOrg?.tierName; // fallback
|
||||
</script>
|
||||
|
||||
<svelte:window on:resize={onResize} />
|
||||
@@ -195,9 +203,9 @@
|
||||
aria-label="Open organizations tab">
|
||||
<span class="orgName">{selectedOrg?.name ?? 'Organization'}</span>
|
||||
<span class="not-mobile"
|
||||
>{#if selectedOrg?.tierName}<Badge
|
||||
>{#if correctPlanName}<Badge
|
||||
variant="secondary"
|
||||
content={selectedOrg?.tierName} />{/if}</span>
|
||||
content={correctPlanName} />{/if}</span>
|
||||
<Icon icon={IconChevronDown} size="s" color="--fgcolor-neutral-secondary" />
|
||||
</button>
|
||||
{:else}
|
||||
@@ -211,7 +219,7 @@
|
||||
<span class="orgName" class:noProjects={!selectedProject}
|
||||
>{selectedOrg?.name ?? 'Organization'}</span>
|
||||
<span class="not-mobile"
|
||||
><Badge variant="secondary" content={selectedOrg?.tierName ?? ''} /></span>
|
||||
><Badge variant="secondary" content={correctPlanName ?? ''} /></span>
|
||||
<Icon icon={IconChevronDown} size="s" color="--fgcolor-neutral-secondary" />
|
||||
</button>
|
||||
{/if}
|
||||
|
||||
@@ -18,6 +18,19 @@
|
||||
children: Snippet<[toggle: () => void, selectedColumnsNumber: number]>;
|
||||
} = $props();
|
||||
|
||||
let maxHeight = $state('none');
|
||||
let containerRef;
|
||||
|
||||
const calcMaxHeight = () => {
|
||||
if (containerRef) {
|
||||
// get parent row element for correct top position
|
||||
const parent = containerRef.parentElement.parentElement;
|
||||
const { top } = parent.getBoundingClientRect();
|
||||
|
||||
maxHeight = `${window.innerHeight - top - 48}px`;
|
||||
}
|
||||
};
|
||||
|
||||
onMount(async () => {
|
||||
if (isCustomCollection) {
|
||||
const prefs = preferences.getCustomCollectionColumns(page.params.collection);
|
||||
@@ -50,6 +63,8 @@
|
||||
preferences.setColumns(columns);
|
||||
}
|
||||
});
|
||||
|
||||
calcMaxHeight();
|
||||
});
|
||||
|
||||
let selectedColumnsNumber = $derived(
|
||||
@@ -61,29 +76,32 @@
|
||||
);
|
||||
</script>
|
||||
|
||||
<svelte:window on:resize={calcMaxHeight} />
|
||||
{#if $columns?.length}
|
||||
<Popover let:toggle placement="bottom-end" padding="none">
|
||||
{@render children(toggle, selectedColumnsNumber)}
|
||||
<svelte:fragment slot="tooltip">
|
||||
<ActionMenu.Root>
|
||||
{#each $columns as column}
|
||||
{#if !column?.exclude}
|
||||
<ActionMenu.Item.Button
|
||||
on:click={() => (column.hide = !column.hide)}
|
||||
disabled={allowNoColumns
|
||||
? false
|
||||
: selectedColumnsNumber <= 1 && column.hide !== true}>
|
||||
<Layout.Stack direction="row" gap="s">
|
||||
<Selector.Checkbox
|
||||
checked={!column.hide}
|
||||
size="s"
|
||||
on:click={() => (column.hide = !column.hide)} />
|
||||
{column.title}
|
||||
</Layout.Stack>
|
||||
</ActionMenu.Item.Button>
|
||||
{/if}
|
||||
{/each}
|
||||
</ActionMenu.Root>
|
||||
<div style:max-height={maxHeight} style:overflow="scroll" bind:this={containerRef}>
|
||||
<ActionMenu.Root>
|
||||
{#each $columns as column}
|
||||
{#if !column?.exclude}
|
||||
<ActionMenu.Item.Button
|
||||
on:click={() => (column.hide = !column.hide)}
|
||||
disabled={allowNoColumns
|
||||
? false
|
||||
: selectedColumnsNumber <= 1 && column.hide !== true}>
|
||||
<Layout.Stack direction="row" gap="s">
|
||||
<Selector.Checkbox
|
||||
checked={!column.hide}
|
||||
size="s"
|
||||
on:click={() => (column.hide = !column.hide)} />
|
||||
{column.title}
|
||||
</Layout.Stack>
|
||||
</ActionMenu.Item.Button>
|
||||
{/if}
|
||||
{/each}
|
||||
</ActionMenu.Root>
|
||||
</div>
|
||||
</svelte:fragment>
|
||||
</Popover>
|
||||
{/if}
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
import { page } from '$app/state';
|
||||
import Card from '../card.svelte';
|
||||
import SkeletonRepoList from './skeletonRepoList.svelte';
|
||||
import { untrack } from 'svelte';
|
||||
import { onMount, untrack } from 'svelte';
|
||||
|
||||
let {
|
||||
action = $bindable('select'),
|
||||
@@ -46,6 +46,11 @@
|
||||
let search = $state('');
|
||||
let selectedInstallation = $state(null);
|
||||
let isLoadingRepositories = $state(null);
|
||||
let installationsMap = $state(null);
|
||||
|
||||
onMount(() => {
|
||||
loadInstallations();
|
||||
});
|
||||
|
||||
async function loadInstallations() {
|
||||
if (installationList) {
|
||||
@@ -59,7 +64,7 @@
|
||||
)
|
||||
);
|
||||
}
|
||||
return installationList.installations;
|
||||
installationsMap = installationList.installations;
|
||||
} else {
|
||||
const { installations } = await sdk
|
||||
.forProject(page.params.region, page.params.project)
|
||||
@@ -70,7 +75,7 @@
|
||||
}
|
||||
installation.set(installations.find((entry) => entry.$id === selectedInstallation));
|
||||
}
|
||||
return installations;
|
||||
installationsMap = installations;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -123,7 +128,7 @@
|
||||
{#if hasInstallations}
|
||||
<Layout.Stack>
|
||||
<Layout.Stack gap="s">
|
||||
{#await loadInstallations()}
|
||||
{#if !installationsMap}
|
||||
<Layout.Stack direction="row">
|
||||
<InputSelect
|
||||
disabled
|
||||
@@ -137,12 +142,12 @@
|
||||
value={null} />
|
||||
<InputSearch placeholder="Search repositories" disabled />
|
||||
</Layout.Stack>
|
||||
{:then installations}
|
||||
{:else}
|
||||
<Layout.Stack direction={$isSmallViewport ? 'column' : 'row'}>
|
||||
<InputSelect
|
||||
id="installation"
|
||||
options={[
|
||||
...installations.map((entry) => {
|
||||
...installationsMap.map((entry) => {
|
||||
return {
|
||||
label: entry.organization,
|
||||
value: entry.$id
|
||||
@@ -160,7 +165,7 @@
|
||||
}
|
||||
search = '';
|
||||
installation.set(
|
||||
installations.find((entry) => entry.$id === selectedInstallation)
|
||||
installationsMap.find((entry) => entry.$id === selectedInstallation)
|
||||
);
|
||||
|
||||
isLoadingRepositories = true;
|
||||
@@ -174,7 +179,7 @@
|
||||
bind:value={search}
|
||||
disabled={!search && !$repositories?.repositories?.length} />
|
||||
</Layout.Stack>
|
||||
{/await}
|
||||
{/if}
|
||||
</Layout.Stack>
|
||||
{#if selectedInstallation}
|
||||
<!-- manual installation change -->
|
||||
|
||||
@@ -211,6 +211,7 @@
|
||||
<Popover let:toggle let:showing>
|
||||
<button
|
||||
type="button"
|
||||
style:cursor="pointer"
|
||||
on:click|preventDefault={(e) => {
|
||||
toggle(e);
|
||||
shouldAnimateThemeToggle = false;
|
||||
@@ -221,7 +222,7 @@
|
||||
style:user-select="none">
|
||||
<Avatar size="s" src={avatar} />
|
||||
</button>
|
||||
<svelte:fragment slot="tooltip" let:toggle>
|
||||
<svelte:fragment slot="tooltip">
|
||||
<ActionMenu.Root noPadding>
|
||||
<Layout.Stack gap="xxs">
|
||||
<div
|
||||
@@ -229,12 +230,11 @@
|
||||
style:padding-inline-end="8px"
|
||||
style:padding-block="4px">
|
||||
<Typography.Text variant="m-500">
|
||||
{$user.email}
|
||||
{$user?.email}
|
||||
</Typography.Text>
|
||||
</div>
|
||||
<ActionMenu.Item.Anchor
|
||||
trailingIcon={IconUser}
|
||||
on:click={toggle}
|
||||
size="l"
|
||||
href={`${base}/account`}>
|
||||
Account</ActionMenu.Item.Anchor>
|
||||
|
||||
@@ -530,6 +530,8 @@ export enum BillingPlan {
|
||||
ENTERPRISE = 'ent-1'
|
||||
}
|
||||
|
||||
export const BASE_BILLING_PLANS: string[] = [BillingPlan.FREE, BillingPlan.PRO, BillingPlan.SCALE];
|
||||
|
||||
export const feedbackDowngradeOptions = [
|
||||
{
|
||||
value: 'availableFeatures',
|
||||
|
||||
+3
-1
@@ -20,5 +20,7 @@ function isFlagEnabled(name: string) {
|
||||
|
||||
export const flags = {
|
||||
showSites: isFlagEnabled('sites'),
|
||||
showCsvImport: isFlagEnabled('csv-import')
|
||||
showCsvImport: isFlagEnabled('csv-import'),
|
||||
showAttributeEncrypt: isFlagEnabled('attribute-encrypt'),
|
||||
showIndexLengths: isFlagEnabled('index-lengths')
|
||||
};
|
||||
|
||||
@@ -44,6 +44,7 @@ export type Column = PinkColumn & {
|
||||
format?: string;
|
||||
exclude?: boolean;
|
||||
elements?: string[] | { value: string | number; label: string }[];
|
||||
encrypt?: boolean;
|
||||
};
|
||||
|
||||
export function isValueOfStringEnum<T extends Record<string, string>>(
|
||||
|
||||
@@ -0,0 +1,76 @@
|
||||
<svg width="481" height="44" viewBox="0 0 481 44" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g style="mix-blend-mode:screen" opacity="0.25" filter="url(#filter0_f_2804_2539)">
|
||||
<circle cx="242.19" cy="52.7439" r="110.744" fill="url(#paint0_radial_2804_2539)"/>
|
||||
</g>
|
||||
<g filter="url(#filter1_f_2804_2539)">
|
||||
<ellipse cx="240.479" cy="56.2318" rx="40.0747" ry="119.968" transform="rotate(66.0796 240.479 56.2318)" fill="url(#paint1_radial_2804_2539)" fill-opacity="0.4"/>
|
||||
</g>
|
||||
<g filter="url(#filter2_f_2804_2539)">
|
||||
<ellipse cx="228.238" cy="51.0802" rx="23.6296" ry="70.7377" transform="rotate(66.0796 228.238 51.0802)" fill="url(#paint2_radial_2804_2539)" fill-opacity="0.5"/>
|
||||
</g>
|
||||
<g filter="url(#filter3_f_2804_2539)">
|
||||
<path d="M191.132 89.2277C140.692 116.602 92.3886 125.135 83.2442 108.286C74.0998 91.4362 107.577 55.5854 158.017 28.2107C208.458 0.835917 256.761 -7.69658 265.906 9.15282C275.05 26.0022 241.573 61.853 191.132 89.2277Z" fill="url(#paint3_radial_2804_2539)" fill-opacity="0.5"/>
|
||||
</g>
|
||||
<g filter="url(#filter4_f_2804_2539)">
|
||||
<ellipse cx="128.998" cy="25.9598" rx="16.2007" ry="66.1069" transform="rotate(66.0796 128.998 25.9598)" fill="url(#paint4_radial_2804_2539)" fill-opacity="0.05"/>
|
||||
</g>
|
||||
<g filter="url(#filter5_f_2804_2539)">
|
||||
<path d="M238.738 55.4355C188.298 82.8103 145.088 100.728 142.227 95.4567C139.366 90.1851 177.937 63.72 228.378 36.3452C278.818 8.97044 322.028 -8.94768 324.889 -3.67603C327.75 1.59561 289.179 28.0608 238.738 55.4355Z" fill="url(#paint5_radial_2804_2539)" fill-opacity="0.35"/>
|
||||
</g>
|
||||
<defs>
|
||||
<filter id="filter0_f_2804_2539" x="70.3866" y="-119.06" width="343.607" height="343.607" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
|
||||
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
|
||||
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
|
||||
<feGaussianBlur stdDeviation="30.5299" result="effect1_foregroundBlur_2804_2539"/>
|
||||
</filter>
|
||||
<filter id="filter1_f_2804_2539" x="0.195038" y="-134.073" width="480.567" height="380.61" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
|
||||
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
|
||||
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
|
||||
<feGaussianBlur stdDeviation="64.7038" result="effect1_foregroundBlur_2804_2539"/>
|
||||
</filter>
|
||||
<filter id="filter2_f_2804_2539" x="62.8613" y="-84.8274" width="330.753" height="271.815" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
|
||||
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
|
||||
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
|
||||
<feGaussianBlur stdDeviation="50" result="effect1_foregroundBlur_2804_2539"/>
|
||||
</filter>
|
||||
<filter id="filter3_f_2804_2539" x="49.7393" y="-31.4919" width="249.671" height="180.422" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
|
||||
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
|
||||
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
|
||||
<feGaussianBlur stdDeviation="16" result="effect1_foregroundBlur_2804_2539"/>
|
||||
</filter>
|
||||
<filter id="filter4_f_2804_2539" x="47.2728" y="-25.6037" width="163.45" height="103.127" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
|
||||
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
|
||||
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
|
||||
<feGaussianBlur stdDeviation="10.4674" result="effect1_foregroundBlur_2804_2539"/>
|
||||
</filter>
|
||||
<filter id="filter5_f_2804_2539" x="110.078" y="-36.5962" width="246.96" height="164.973" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
|
||||
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
|
||||
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
|
||||
<feGaussianBlur stdDeviation="16" result="effect1_foregroundBlur_2804_2539"/>
|
||||
</filter>
|
||||
<radialGradient id="paint0_radial_2804_2539" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(242.19 52.7439) rotate(90) scale(110.744)">
|
||||
<stop stop-color="#FD366E"/>
|
||||
<stop offset="1" stop-color="#FD366E" stop-opacity="0"/>
|
||||
</radialGradient>
|
||||
<radialGradient id="paint1_radial_2804_2539" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(240.479 56.2318) rotate(90) scale(119.968 40.0747)">
|
||||
<stop stop-color="#FE9567"/>
|
||||
<stop offset="1" stop-color="#F02E65" stop-opacity="0"/>
|
||||
</radialGradient>
|
||||
<radialGradient id="paint2_radial_2804_2539" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(228.238 51.0802) rotate(90) scale(70.7377 23.6296)">
|
||||
<stop stop-color="#FFEAE1"/>
|
||||
<stop offset="1" stop-color="#F02E65" stop-opacity="0"/>
|
||||
</radialGradient>
|
||||
<radialGradient id="paint3_radial_2804_2539" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(174.575 58.7192) rotate(151.511) scale(103.914 34.7119)">
|
||||
<stop stop-color="#F02E65"/>
|
||||
<stop offset="1" stop-color="#F02E65" stop-opacity="0"/>
|
||||
</radialGradient>
|
||||
<radialGradient id="paint4_radial_2804_2539" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(128.998 25.9598) rotate(90) scale(66.1069 16.2007)">
|
||||
<stop stop-color="#FFEAE1"/>
|
||||
<stop offset="1" stop-color="#F02E65" stop-opacity="0"/>
|
||||
</radialGradient>
|
||||
<radialGradient id="paint5_radial_2804_2539" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(233.558 45.8904) rotate(151.511) scale(103.914 10.8603)">
|
||||
<stop stop-color="#F02E65"/>
|
||||
<stop offset="1" stop-color="#F02E65" stop-opacity="0"/>
|
||||
</radialGradient>
|
||||
</defs>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 5.4 KiB |
@@ -17,7 +17,7 @@
|
||||
|
||||
let {
|
||||
columns,
|
||||
view = View.Table,
|
||||
view = $bindable(View.Table),
|
||||
hideView = false,
|
||||
hideColumns = false,
|
||||
hasSearch = false,
|
||||
|
||||
@@ -311,6 +311,7 @@ export type Plan = {
|
||||
teams: number;
|
||||
projects: number;
|
||||
databases: number;
|
||||
databasesAllowEncrypt: boolean;
|
||||
buckets: number;
|
||||
fileSize: number;
|
||||
functions: number;
|
||||
|
||||
@@ -11,7 +11,7 @@ import PaymentAuthRequired from '$lib/components/billing/alerts/paymentAuthRequi
|
||||
import PaymentMandate from '$lib/components/billing/alerts/paymentMandate.svelte';
|
||||
import { BillingPlan, NEW_DEV_PRO_UPGRADE_COUPON } from '$lib/constants';
|
||||
import { cachedStore } from '$lib/helpers/cache';
|
||||
import { sizeToBytes, type Size } from '$lib/helpers/sizeConvertion';
|
||||
import { type Size, sizeToBytes } from '$lib/helpers/sizeConvertion';
|
||||
import type {
|
||||
AddressesList,
|
||||
Aggregation,
|
||||
@@ -28,13 +28,19 @@ import { Query } from '@appwrite.io/console';
|
||||
import { derived, get, writable } from 'svelte/store';
|
||||
import { headerAlert } from './headerAlert';
|
||||
import { addNotification, notifications } from './notifications';
|
||||
import { organization, type Organization, type OrganizationError } from './organization';
|
||||
import {
|
||||
currentPlan,
|
||||
organization,
|
||||
type Organization,
|
||||
type OrganizationError
|
||||
} from './organization';
|
||||
import { canSeeBilling } from './roles';
|
||||
import { sdk } from './sdk';
|
||||
import { user } from './user';
|
||||
import BudgetLimitAlert from '$routes/(console)/organization-[organization]/budgetLimitAlert.svelte';
|
||||
import TeamReadonlyAlert from '$routes/(console)/organization-[organization]/teamReadonlyAlert.svelte';
|
||||
import ProjectsLimit from '$lib/components/billing/alerts/projectsLimit.svelte';
|
||||
import EnterpriseTrial from '$routes/(console)/organization-[organization]/enterpriseTrial.svelte';
|
||||
|
||||
export type Tier = 'tier-0' | 'tier-1' | 'tier-2' | 'auto-1' | 'cont-1' | 'ent-1';
|
||||
|
||||
@@ -269,6 +275,32 @@ export function isServiceLimited(serviceId: PlanServices, plan: Tier, total: num
|
||||
return isLimited && total >= limit && !hasUsageFees;
|
||||
}
|
||||
|
||||
export function checkForEnterpriseTrial(org: Organization) {
|
||||
if (!org || !org.billingNextInvoiceDate) return;
|
||||
if (calculateEnterpriseTrial(org) > 0) {
|
||||
headerAlert.add({
|
||||
id: 'teamEnterpriseTrial',
|
||||
component: EnterpriseTrial,
|
||||
show: true,
|
||||
importance: 11
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export function calculateEnterpriseTrial(org: Organization) {
|
||||
const endDate = new Date(org.billingNextInvoiceDate);
|
||||
const startDate = new Date(org.billingCurrentInvoiceDate);
|
||||
const today = new Date();
|
||||
|
||||
let diffCycle = endDate.getTime() - startDate.getTime();
|
||||
diffCycle = Math.ceil(diffCycle / (1000 * 60 * 60 * 24));
|
||||
if (diffCycle === 14) {
|
||||
const remaining = endDate.getTime() - today.getTime();
|
||||
return Math.ceil(remaining / (1000 * 60 * 60 * 24));
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
export function calculateTrialDay(org: Organization) {
|
||||
if (org?.billingPlan === BillingPlan.FREE) return false;
|
||||
const endDate = new Date(org?.billingStartDate);
|
||||
@@ -342,7 +374,7 @@ export async function checkForUsageLimit(org: Organization) {
|
||||
];
|
||||
|
||||
const members = org.total;
|
||||
const plan = get(plansInfo)?.get(org.billingPlan);
|
||||
const plan = get(currentPlan);
|
||||
const membersOverflow =
|
||||
members > plan.addons.seats.limit ? members - (plan.addons.seats.limit || members) : 0;
|
||||
|
||||
|
||||
@@ -13,6 +13,10 @@ export type Account = Models.User<
|
||||
>;
|
||||
|
||||
export const user = derived(page, ($page) => {
|
||||
if (browser) sessionStorage.setItem('account', JSON.stringify($page.data.account));
|
||||
return $page.data.account as Account;
|
||||
if ($page.data?.account) {
|
||||
if (browser) {
|
||||
sessionStorage.setItem('account', JSON.stringify($page.data.account));
|
||||
}
|
||||
return $page.data.account as Account;
|
||||
} else return null;
|
||||
});
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
import Create from './createOrganization.svelte';
|
||||
import {
|
||||
calculateTrialDay,
|
||||
checkForEnterpriseTrial,
|
||||
checkForMandate,
|
||||
checkForMarkedForDeletion,
|
||||
checkForMissingPaymentMethod,
|
||||
@@ -306,6 +307,7 @@
|
||||
if (isCloud) {
|
||||
currentOrganizationId = org.$id;
|
||||
checkForProjectsLimit(org, data.projects?.length || 0);
|
||||
checkForEnterpriseTrial(org);
|
||||
await checkForUsageLimit(org);
|
||||
checkForMarkedForDeletion(org);
|
||||
await checkForNewDevUpgradePro(org);
|
||||
|
||||
@@ -0,0 +1,69 @@
|
||||
<script lang="ts">
|
||||
import { page } from '$app/state';
|
||||
import { Button } from '$lib/elements/forms';
|
||||
import { organization } from '$lib/stores/organization';
|
||||
import { calculateEnterpriseTrial, hideBillingHeaderRoutes } from '$lib/stores/billing';
|
||||
import { base } from '$app/paths';
|
||||
import GradientBanner from '$lib/components/billing/gradientBanner.svelte';
|
||||
import { Badge, Typography } from '@appwrite.io/pink-svelte';
|
||||
import { activeHeaderAlert } from '../store';
|
||||
|
||||
$: upgradeUrl = `${base}/organization-${$organization?.$id}/change-plan`;
|
||||
$: remainingDays = calculateEnterpriseTrial($organization);
|
||||
|
||||
let show = true;
|
||||
|
||||
function handleClose() {
|
||||
show = false;
|
||||
const now = new Date().getTime();
|
||||
localStorage.setItem($activeHeaderAlert.id, now.toString());
|
||||
}
|
||||
</script>
|
||||
|
||||
{#if $organization?.$id && remainingDays > 0 && !hideBillingHeaderRoutes.includes(page.url.pathname) && show}
|
||||
<GradientBanner variant="image" on:close={handleClose}>
|
||||
<div class="banner-fullwidth-grid">
|
||||
<Typography.Text color="--fgcolor-neutral-primary">
|
||||
Your enterprise trial expires in <Badge
|
||||
size="xs"
|
||||
variant="secondary"
|
||||
content={remainingDays.toString()} /> days
|
||||
</Typography.Text>
|
||||
|
||||
<Button size="xs" secondary fullWidthMobile href={upgradeUrl}>Upgrade</Button>
|
||||
</div>
|
||||
</GradientBanner>
|
||||
{/if}
|
||||
|
||||
<style lang="scss">
|
||||
.banner-fullwidth-grid {
|
||||
width: 100vw;
|
||||
display: grid;
|
||||
padding: 0 3rem;
|
||||
align-items: center;
|
||||
grid-template-columns: 1fr auto 1fr;
|
||||
|
||||
> :global(:first-child) {
|
||||
grid-column: 2;
|
||||
justify-self: center;
|
||||
|
||||
@media (max-width: 768px) {
|
||||
grid-column: unset;
|
||||
}
|
||||
}
|
||||
|
||||
> :global(:last-child) {
|
||||
grid-column: 3;
|
||||
justify-self: end;
|
||||
margin-inline-end: 4px;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
gap: 12px;
|
||||
width: 100%;
|
||||
display: flex;
|
||||
padding: unset;
|
||||
flex-direction: column;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
+15
-21
@@ -1,37 +1,31 @@
|
||||
<script lang="ts">
|
||||
import { Empty, PaginationWithLimit, SearchQuery, ViewSelector } from '$lib/components';
|
||||
import { Empty, PaginationWithLimit } from '$lib/components';
|
||||
import { Button } from '$lib/elements/forms';
|
||||
import { Container } from '$lib/layout';
|
||||
import { Container, ResponsiveContainerHeader } from '$lib/layout';
|
||||
import { columns, showCreate } from './store';
|
||||
import Table from './table.svelte';
|
||||
import Grid from './grid.svelte';
|
||||
import type { PageData } from './$types';
|
||||
import { canWriteCollections } from '$lib/stores/roles';
|
||||
import { Icon, Layout } from '@appwrite.io/pink-svelte';
|
||||
import { Icon } from '@appwrite.io/pink-svelte';
|
||||
import { IconPlus } from '@appwrite.io/pink-icons-svelte';
|
||||
|
||||
export let data: PageData;
|
||||
</script>
|
||||
|
||||
<Container>
|
||||
<Layout.Stack direction="row" justifyContent="space-between">
|
||||
<Layout.Stack direction="row" alignItems="center">
|
||||
<SearchQuery placeholder="Search by name or ID" />
|
||||
</Layout.Stack>
|
||||
<Layout.Stack direction="row" alignItems="center" justifyContent="flex-end">
|
||||
<ViewSelector
|
||||
{columns}
|
||||
view={data.view}
|
||||
hideColumns={!data.collections.total}
|
||||
hideView={!data.collections.total} />
|
||||
{#if $canWriteCollections}
|
||||
<Button on:click={() => ($showCreate = true)} event="create_collection">
|
||||
<Icon icon={IconPlus} slot="start" size="s" />
|
||||
Create collection
|
||||
</Button>
|
||||
{/if}
|
||||
</Layout.Stack>
|
||||
</Layout.Stack>
|
||||
<ResponsiveContainerHeader
|
||||
bind:view={data.view}
|
||||
{columns}
|
||||
hasSearch
|
||||
searchPlaceholder="Search by name or ID">
|
||||
{#if $canWriteCollections}
|
||||
<Button on:click={() => ($showCreate = true)} event="create_collection">
|
||||
<Icon icon={IconPlus} slot="start" size="s" />
|
||||
Create collection
|
||||
</Button>
|
||||
{/if}
|
||||
</ResponsiveContainerHeader>
|
||||
|
||||
{#if data.collections.total}
|
||||
{#if data.view === 'grid'}
|
||||
|
||||
+35
-20
@@ -13,10 +13,11 @@
|
||||
Link,
|
||||
Popover,
|
||||
Table,
|
||||
Tooltip,
|
||||
Typography
|
||||
} from '@appwrite.io/pink-svelte';
|
||||
import Create from '../createAttribute.svelte';
|
||||
import { isRelationship } from '../document-[document]/attributes/store';
|
||||
import { isRelationship, isString } from '../document-[document]/attributes/store';
|
||||
import FailedModal from '../failedModal.svelte';
|
||||
import CreateIndex from '../indexes/createIndex.svelte';
|
||||
import { attributes, type Attributes, isCsvImportInProgress } from '../store';
|
||||
@@ -33,7 +34,8 @@
|
||||
IconPlus,
|
||||
IconSwitchHorizontal,
|
||||
IconTrash,
|
||||
IconViewList
|
||||
IconViewList,
|
||||
IconLockClosed
|
||||
} from '@appwrite.io/pink-icons-svelte';
|
||||
import type { ComponentProps } from 'svelte';
|
||||
import { Click, trackEvent } from '$lib/actions/analytics';
|
||||
@@ -112,25 +114,38 @@
|
||||
{:else}
|
||||
<Icon icon={option.icon} size="s" />
|
||||
{/if}
|
||||
<span class="text u-trim-1" data-private>{attribute.key}</span>
|
||||
{#if attribute.status !== 'available'}
|
||||
<Badge
|
||||
size="s"
|
||||
variant="secondary"
|
||||
content={attribute.status}
|
||||
type={getAttributeStatusBadge(attribute.status)} />
|
||||
{#if attribute.error}
|
||||
<Link.Button
|
||||
variant="muted"
|
||||
on:click={(e) => {
|
||||
e.preventDefault();
|
||||
error = attribute.error;
|
||||
showFailed = true;
|
||||
}}>Details</Link.Button>
|
||||
<Layout.Stack direction="row" alignItems="center" gap="s">
|
||||
<Layout.Stack inline direction="row" alignItems="center" gap="xxs">
|
||||
<span class="text u-trim-1" data-private>{attribute.key}</span>
|
||||
{#if isString(attribute) && attribute.encrypt}
|
||||
<Tooltip>
|
||||
<Icon
|
||||
size="s"
|
||||
icon={IconLockClosed}
|
||||
color="--fgcolor-neutral-tertiary" />
|
||||
<div slot="tooltip">Encrypted</div>
|
||||
</Tooltip>
|
||||
{/if}
|
||||
</Layout.Stack>
|
||||
{#if attribute.status !== 'available'}
|
||||
<Badge
|
||||
size="s"
|
||||
variant="secondary"
|
||||
content={attribute.status}
|
||||
type={getAttributeStatusBadge(attribute.status)} />
|
||||
{#if attribute.error}
|
||||
<Link.Button
|
||||
variant="muted"
|
||||
on:click={(e) => {
|
||||
e.preventDefault();
|
||||
error = attribute.error;
|
||||
showFailed = true;
|
||||
}}>Details</Link.Button>
|
||||
{/if}
|
||||
{:else if attribute.required}
|
||||
<Badge size="xs" variant="secondary" content="required" />
|
||||
{/if}
|
||||
{:else if attribute.required}
|
||||
<Badge size="s" variant="secondary" content="required" />
|
||||
{/if}
|
||||
</Layout.Stack>
|
||||
</Layout.Stack>
|
||||
</Table.Cell>
|
||||
<Table.Cell column="type" {root}>
|
||||
|
||||
+1
-2
@@ -100,5 +100,4 @@
|
||||
label="Array"
|
||||
bind:checked={data.array}
|
||||
disabled={data.required || editing}
|
||||
description="Indicate whether this attribute should act as an array, with the default value set as an empty
|
||||
array." />
|
||||
description="Indicate whether this attribute is an array. Defaults to an empty array." />
|
||||
|
||||
+1
-2
@@ -92,5 +92,4 @@
|
||||
label="Array"
|
||||
bind:checked={data.array}
|
||||
disabled={data.required || editing}
|
||||
description="Indicate whether this attribute should act as an array, with the default value set as an empty
|
||||
array." />
|
||||
description="Indicate whether this attribute is an array. Defaults to an empty array." />
|
||||
|
||||
+1
-2
@@ -93,5 +93,4 @@
|
||||
label="Array"
|
||||
bind:checked={data.array}
|
||||
disabled={data.required || editing}
|
||||
description="Indicate whether this attribute should act as an array, with the default value set as an empty
|
||||
array." />
|
||||
description="Indicate whether this attribute is an array. Defaults to an empty array." />
|
||||
|
||||
+1
-2
@@ -124,5 +124,4 @@
|
||||
label="Array"
|
||||
bind:checked={data.array}
|
||||
disabled={data.required || editing}
|
||||
description="Indicate whether this attribute should act as an array, with the default value set as an empty
|
||||
array." />
|
||||
description="Indicate whether this attribute is an array. Defaults to an empty array." />
|
||||
|
||||
+1
-2
@@ -122,5 +122,4 @@
|
||||
label="Array"
|
||||
bind:checked={data.array}
|
||||
disabled={data.required || editing}
|
||||
description="Indicate whether this attribute should act as an array, with the default value set as an empty
|
||||
array." />
|
||||
description="Indicate whether this attribute is an array. Defaults to an empty array." />
|
||||
|
||||
+1
-2
@@ -120,5 +120,4 @@
|
||||
label="Array"
|
||||
bind:checked={data.array}
|
||||
disabled={data.required || editing}
|
||||
description="Indicate whether this attribute should act as an array, with the default value set as an empty
|
||||
array." />
|
||||
description="Indicate whether this attribute is an array. Defaults to an empty array." />
|
||||
|
||||
+1
-2
@@ -92,5 +92,4 @@
|
||||
label="Array"
|
||||
bind:checked={data.array}
|
||||
disabled={data.required || editing}
|
||||
description="Indicate whether this attribute should act as an array, with the default value set as an empty
|
||||
array." />
|
||||
description="Indicate whether this attribute is an array. Defaults to an empty array." />
|
||||
|
||||
+98
-27
@@ -18,7 +18,8 @@
|
||||
data.size,
|
||||
data.required,
|
||||
data.default,
|
||||
data.array
|
||||
data.array,
|
||||
data.encrypt
|
||||
);
|
||||
}
|
||||
export async function updateString(
|
||||
@@ -42,20 +43,28 @@
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
import { Selector } from '@appwrite.io/pink-svelte';
|
||||
import { isCloud } from '$lib/system';
|
||||
import { upgradeURL } from '$lib/stores/billing';
|
||||
import { currentPlan } from '$lib/stores/organization';
|
||||
import { createConservative } from '$lib/helpers/stores';
|
||||
import { ActionMenu, Selector } from '@appwrite.io/pink-svelte';
|
||||
import { InputNumber, InputText, InputTextarea } from '$lib/elements/forms';
|
||||
import { Popover, Layout, Tag, Typography, Link } from '@appwrite.io/pink-svelte';
|
||||
import { flags } from '$lib/flags';
|
||||
|
||||
export let data: Partial<Models.AttributeString> = {
|
||||
required: false,
|
||||
size: 0,
|
||||
default: null,
|
||||
array: false
|
||||
array: false,
|
||||
encrypt: false
|
||||
};
|
||||
|
||||
export let editing = false;
|
||||
|
||||
let savedDefault = data.default;
|
||||
|
||||
const showEncrypt = flags.showAttributeEncrypt(page.data);
|
||||
|
||||
function handleDefaultState(hideDefault: boolean) {
|
||||
if (hideDefault) {
|
||||
savedDefault = data.default;
|
||||
@@ -73,48 +82,110 @@
|
||||
array: false,
|
||||
...data
|
||||
});
|
||||
|
||||
$: listen(data);
|
||||
|
||||
$: handleDefaultState($required || $array);
|
||||
|
||||
// Check plan on cloud, always allow on self-hosted
|
||||
$: supportsStringEncryption = isCloud ? $currentPlan?.databasesAllowEncrypt : true;
|
||||
</script>
|
||||
|
||||
<InputNumber
|
||||
id="size"
|
||||
label="Size"
|
||||
required
|
||||
placeholder="Enter size"
|
||||
bind:value={data.size}
|
||||
required={true} />
|
||||
{#if data.size >= 50}
|
||||
<InputTextarea
|
||||
id="default"
|
||||
label="Default"
|
||||
placeholder="Enter string"
|
||||
disabled={data.required || data.array}
|
||||
nullable={!data.required && !data.array}
|
||||
maxlength={data.size}
|
||||
bind:value={data.default} />
|
||||
{:else}
|
||||
<InputText
|
||||
id="default"
|
||||
label="Default"
|
||||
placeholder="Enter string"
|
||||
disabled={data.required || data.array}
|
||||
nullable={!data.required && !data.array}
|
||||
maxlength={data.size}
|
||||
bind:value={data.default} />
|
||||
{/if}
|
||||
min={supportsStringEncryption && data.encrypt ? 150 : undefined}
|
||||
helper={supportsStringEncryption && data.encrypt
|
||||
? 'Encrypted string attributes require a minimum size of 150.'
|
||||
: undefined} />
|
||||
|
||||
<svelte:component
|
||||
this={data.size >= 50 ? InputTextarea : InputText}
|
||||
id="default"
|
||||
label="Default"
|
||||
placeholder="Enter string"
|
||||
maxlength={data.size}
|
||||
bind:value={data.default}
|
||||
disabled={data.required || data.array}
|
||||
nullable={!data.required && !data.array} />
|
||||
|
||||
<Selector.Checkbox
|
||||
size="s"
|
||||
id="required"
|
||||
label="Required"
|
||||
bind:checked={data.required}
|
||||
disabled={data.array}
|
||||
description="Indicate whether this attribute is required" />
|
||||
description="Indicate whether this attribute is required." />
|
||||
|
||||
<Selector.Checkbox
|
||||
size="s"
|
||||
id="array"
|
||||
label="Array"
|
||||
bind:checked={data.array}
|
||||
disabled={data.required || editing}
|
||||
description="Indicate whether this attribute should act as an array, with the default value set as an empty
|
||||
array." />
|
||||
description="Indicate whether this attribute is an array. Defaults to an empty array." />
|
||||
|
||||
{#if showEncrypt}
|
||||
<Layout.Stack gap="xs" direction="column">
|
||||
<div class="popover-holder" class:disabled-checkbox={!supportsStringEncryption || editing}>
|
||||
<Layout.Stack inline gap="s" alignItems="flex-start" direction="row">
|
||||
<Popover let:toggle placement="bottom-start">
|
||||
<Selector.Checkbox
|
||||
size="s"
|
||||
id="encrypt"
|
||||
bind:checked={data.encrypt}
|
||||
disabled={!supportsStringEncryption || editing} />
|
||||
|
||||
<Layout.Stack gap="xxs" direction="column">
|
||||
<button
|
||||
type="button"
|
||||
class="u-cursor-pointer"
|
||||
on:click={(e) => {
|
||||
if (!supportsStringEncryption) {
|
||||
toggle(e);
|
||||
} else {
|
||||
data.encrypt = !data.encrypt;
|
||||
}
|
||||
}}>
|
||||
<Layout.Stack inline gap="xxs" direction="row" alignItems="center">
|
||||
<Typography.Text variant="m-500">Encrypted</Typography.Text>
|
||||
{#if !supportsStringEncryption}
|
||||
<Tag variant="default" size="xs" on:click={toggle}>Pro</Tag>
|
||||
{/if}
|
||||
</Layout.Stack>
|
||||
</button>
|
||||
<Typography.Text color="--fgcolor-neutral-tertiary">
|
||||
Indicate whether this attribute is encrypted. Encrypted attributes
|
||||
cannot be queried.
|
||||
</Typography.Text>
|
||||
</Layout.Stack>
|
||||
|
||||
<ActionMenu.Root width="180px" slot="tooltip">
|
||||
<Typography.Text variant="m-500">
|
||||
Available on Pro plan. <Link.Anchor href={$upgradeURL}
|
||||
>Upgrade</Link.Anchor>
|
||||
to enable encrypted attributes.
|
||||
</Typography.Text>
|
||||
</ActionMenu.Root>
|
||||
</Popover>
|
||||
</Layout.Stack>
|
||||
</div>
|
||||
</Layout.Stack>
|
||||
{/if}
|
||||
|
||||
<style lang="scss">
|
||||
.popover-holder {
|
||||
& :global([role='tooltip']) {
|
||||
margin-top: 4px;
|
||||
left: 8rem !important;
|
||||
}
|
||||
|
||||
// no cursor when disabled
|
||||
&.disabled-checkbox :global(button) {
|
||||
cursor: unset !important;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
+1
-2
@@ -93,5 +93,4 @@
|
||||
label="Array"
|
||||
bind:checked={data.array}
|
||||
disabled={data.required || editing}
|
||||
description="Indicate whether this attribute should act as an array, with the default value set as an empty
|
||||
array." />
|
||||
description="Indicate whether this attribute is an array. Defaults to an empty array." />
|
||||
|
||||
+1
-2
@@ -20,7 +20,6 @@
|
||||
const databaseId = page.params.database;
|
||||
const collectionId = page.params.collection;
|
||||
const documentId = page.params.document;
|
||||
const editing = true;
|
||||
|
||||
function initWork() {
|
||||
const prohibitedKeys = [
|
||||
@@ -124,7 +123,7 @@
|
||||
<svelte:fragment slot="title">{label}</svelte:fragment>
|
||||
|
||||
<svelte:fragment slot="aside">
|
||||
<AttributeItem {attribute} bind:formValues={$work} {label} {editing} />
|
||||
<AttributeItem {attribute} bind:formValues={$work} {label} editing />
|
||||
</svelte:fragment>
|
||||
|
||||
<svelte:fragment slot="actions">
|
||||
|
||||
+169
-107
@@ -1,24 +1,22 @@
|
||||
<script lang="ts">
|
||||
import { page } from '$app/state';
|
||||
import { PaginationInline } from '$lib/components';
|
||||
import { Button } from '$lib/elements/forms';
|
||||
import { Button, InputSelect } from '$lib/elements/forms';
|
||||
import { preferences } from '$lib/stores/preferences';
|
||||
import { sdk } from '$lib/stores/sdk';
|
||||
import { Query, type Models } from '@appwrite.io/console';
|
||||
import { onMount } from 'svelte';
|
||||
import { doc } from '../store';
|
||||
import { isRelationshipToMany } from './store';
|
||||
import { IconPlus, IconX } from '@appwrite.io/pink-icons-svelte';
|
||||
import { Icon, Layout, Typography } from '@appwrite.io/pink-svelte';
|
||||
import { IconPlus } from '@appwrite.io/pink-icons-svelte';
|
||||
import InputSelect from '$lib/elements/forms/inputSelect.svelte';
|
||||
|
||||
export let id: string;
|
||||
export let label: string;
|
||||
|
||||
export let editing = false;
|
||||
export let value: string | string[];
|
||||
export let attribute: Models.AttributeRelationship;
|
||||
export let optionalText: string | undefined = undefined;
|
||||
export let editing = false;
|
||||
|
||||
const databaseId = page.params.database;
|
||||
|
||||
@@ -28,6 +26,7 @@
|
||||
let relatedList: string[] = [];
|
||||
let singleRel: string;
|
||||
let showInput = false;
|
||||
let newItemValue: string = '';
|
||||
let limit = 10;
|
||||
let offset = 0;
|
||||
|
||||
@@ -39,6 +38,7 @@
|
||||
singleRel = value as string;
|
||||
}
|
||||
}
|
||||
|
||||
documentList = await getDocuments();
|
||||
|
||||
if (editing && $doc?.[attribute.key]) {
|
||||
@@ -59,20 +59,48 @@
|
||||
});
|
||||
|
||||
async function getDocuments(search: string = null) {
|
||||
if (search) {
|
||||
const documents = await sdk
|
||||
.forProject(page.params.region, page.params.project)
|
||||
.databases.listDocuments(databaseId, attribute.relatedCollection, [
|
||||
Query.startsWith('$id', search),
|
||||
Query.orderDesc('')
|
||||
]);
|
||||
return documents;
|
||||
const queries = search ? [Query.startsWith('$id', search), Query.orderDesc('')] : [];
|
||||
return await sdk
|
||||
.forProject(page.params.region, page.params.project)
|
||||
.databases.listDocuments(databaseId, attribute.relatedCollection, queries);
|
||||
}
|
||||
|
||||
function getAvailableOptions(excludeIndex?: number) {
|
||||
return options?.filter((option) => {
|
||||
const otherItems =
|
||||
excludeIndex !== undefined
|
||||
? relatedList.filter((_, idx) => idx !== excludeIndex)
|
||||
: relatedList;
|
||||
return !otherItems.includes(option.value);
|
||||
});
|
||||
}
|
||||
|
||||
function updateRelatedList() {
|
||||
relatedList = relatedList;
|
||||
value = relatedList;
|
||||
}
|
||||
|
||||
function removeItem(index: number) {
|
||||
if (relatedList.length === 1) {
|
||||
relatedList[index] = '';
|
||||
} else {
|
||||
const documents = await sdk
|
||||
.forProject(page.params.region, page.params.project)
|
||||
.databases.listDocuments(databaseId, attribute.relatedCollection);
|
||||
return documents;
|
||||
relatedList.splice(index, 1);
|
||||
}
|
||||
updateRelatedList();
|
||||
}
|
||||
|
||||
function addNewItem() {
|
||||
if (newItemValue) {
|
||||
relatedList = [...relatedList, newItemValue];
|
||||
value = relatedList;
|
||||
newItemValue = '';
|
||||
showInput = false;
|
||||
}
|
||||
}
|
||||
|
||||
function cancelAddItem() {
|
||||
newItemValue = '';
|
||||
showInput = false;
|
||||
}
|
||||
|
||||
//Reactive statements
|
||||
@@ -84,106 +112,140 @@
|
||||
.reverse()
|
||||
.slice(offset, offset + limit)
|
||||
: [];
|
||||
$: total = relatedList?.length ?? 0;
|
||||
|
||||
$: totalCount = relatedList?.length ?? 0;
|
||||
|
||||
$: options =
|
||||
documentList?.documents?.map((n) => {
|
||||
const data = displayNames.filter((name) => name !== '$id').map((name) => n?.[name]);
|
||||
return {
|
||||
value: n.$id,
|
||||
label: n.$id,
|
||||
data
|
||||
};
|
||||
return { value: n.$id, label: n.$id, data };
|
||||
}) ?? [];
|
||||
|
||||
$: hasItems = totalCount > 0;
|
||||
$: showTopAddButton = !editing && totalCount === 0 && !showInput;
|
||||
$: showBottomAddButton =
|
||||
(!editing && hasItems && !showInput) ||
|
||||
(editing && hasItems && relatedList.every((item) => item) && !showInput);
|
||||
$: showEmptyInput = editing && totalCount === 0 && !showInput;
|
||||
</script>
|
||||
|
||||
{#if isRelationshipToMany(attribute)}
|
||||
<div class="u-width-full-line">
|
||||
<Layout.Stack direction="row" alignContent="space-between">
|
||||
<Layout.Stack gap="xxs" direction="row" alignItems="center">
|
||||
<Typography.Text variant="m-500">{label}</Typography.Text>
|
||||
<Typography.Text variant="m-400" color="--fgcolor-neutral-tertiary">
|
||||
{optionalText}
|
||||
</Typography.Text>
|
||||
<Layout.Stack gap="xxl">
|
||||
<Layout.Stack gap="m">
|
||||
<Layout.Stack direction="row" alignContent="space-between">
|
||||
<Layout.Stack gap="xxs" direction="row" alignItems="center">
|
||||
<Typography.Text variant="m-500">{label}</Typography.Text>
|
||||
<Typography.Text variant="m-400" color="--fgcolor-neutral-tertiary">
|
||||
{optionalText}
|
||||
</Typography.Text>
|
||||
</Layout.Stack>
|
||||
|
||||
{#if showTopAddButton}
|
||||
<Button secondary on:click={() => (showInput = true)}>
|
||||
<Icon icon={IconPlus} slot="start" size="s" />
|
||||
Add item
|
||||
</Button>
|
||||
{/if}
|
||||
</Layout.Stack>
|
||||
{#if editing || total === 0}
|
||||
<Button
|
||||
secondary
|
||||
on:click={() => {
|
||||
showInput = true;
|
||||
}}>
|
||||
<Icon icon={IconPlus} slot="start" size="s" />
|
||||
Add item
|
||||
</Button>
|
||||
|
||||
<Layout.Stack gap="m">
|
||||
<!-- Empty input for editing mode when no items exist -->
|
||||
{#if showEmptyInput}
|
||||
<Layout.Stack direction="row">
|
||||
<InputSelect
|
||||
{id}
|
||||
required
|
||||
{options}
|
||||
bind:value={relatedList[0]}
|
||||
placeholder={`Select ${attribute.key}`}
|
||||
on:change={() => {
|
||||
if (!relatedList[0]) relatedList = [''];
|
||||
updateRelatedList();
|
||||
}} />
|
||||
</Layout.Stack>
|
||||
{/if}
|
||||
|
||||
<!-- Existing items in editing mode -->
|
||||
{#if editing && hasItems}
|
||||
{#each paginatedItems as _, paginatedIndex}
|
||||
{@const actualIndex = offset + paginatedIndex}
|
||||
<Layout.Stack direction="row">
|
||||
<InputSelect
|
||||
{id}
|
||||
required
|
||||
options={getAvailableOptions(actualIndex)}
|
||||
bind:value={relatedList[actualIndex]}
|
||||
placeholder={`Select ${attribute.key}`}
|
||||
on:change={updateRelatedList} />
|
||||
{#if relatedList[actualIndex]}
|
||||
<div style:padding-block-start="0.5rem">
|
||||
<Button
|
||||
icon
|
||||
extraCompact
|
||||
on:click={() => removeItem(actualIndex)}>
|
||||
<Icon icon={IconX} size="s" />
|
||||
</Button>
|
||||
</div>
|
||||
{/if}
|
||||
</Layout.Stack>
|
||||
{/each}
|
||||
{/if}
|
||||
|
||||
<!-- Existing items in creation mode -->
|
||||
{#if !editing && hasItems}
|
||||
{#each relatedList as item, i}
|
||||
<Layout.Stack direction="row">
|
||||
<InputSelect
|
||||
{id}
|
||||
required
|
||||
options={getAvailableOptions(i)}
|
||||
bind:value={item}
|
||||
on:change={updateRelatedList} />
|
||||
<div style:padding-block-start="0.5rem">
|
||||
<Button
|
||||
icon
|
||||
extraCompact
|
||||
ariaLabel={`Delete item ${i}`}
|
||||
on:click={() => {
|
||||
relatedList.splice(i, 1);
|
||||
updateRelatedList();
|
||||
}}>
|
||||
<Icon icon={IconX} size="s" />
|
||||
</Button>
|
||||
</div>
|
||||
</Layout.Stack>
|
||||
{/each}
|
||||
{/if}
|
||||
|
||||
<!-- Input for adding new items -->
|
||||
{#if showInput}
|
||||
<Layout.Stack direction="row">
|
||||
<InputSelect
|
||||
{id}
|
||||
required
|
||||
placeholder={`Select ${attribute.key}`}
|
||||
bind:value={newItemValue}
|
||||
options={getAvailableOptions()}
|
||||
on:change={addNewItem} />
|
||||
<div style:padding-block-start="0.5rem">
|
||||
<Button icon extraCompact on:click={cancelAddItem}>
|
||||
<Icon icon={IconX} size="s" />
|
||||
</Button>
|
||||
</div>
|
||||
</Layout.Stack>
|
||||
{/if}
|
||||
</Layout.Stack>
|
||||
|
||||
{#if showBottomAddButton}
|
||||
<Layout.Stack direction="row" alignContent="flex-start">
|
||||
<Button extraCompact on:click={() => (showInput = true)}>
|
||||
<Icon icon={IconPlus} slot="start" size="s" />
|
||||
Add item
|
||||
</Button>
|
||||
</Layout.Stack>
|
||||
{/if}
|
||||
</Layout.Stack>
|
||||
<ul class="u-flex-vertical u-gap-4 u-margin-block-start-4">
|
||||
{#if !editing && relatedList?.length}
|
||||
{#each relatedList as item, i}
|
||||
<InputSelect {id} required bind:value={item} {options} />
|
||||
<Button
|
||||
extraCompact
|
||||
ariaLabel={`Delete item ${i}`}
|
||||
on:click={() => {
|
||||
relatedList.splice(i, 1);
|
||||
relatedList = relatedList;
|
||||
value = relatedList;
|
||||
}}>
|
||||
<span class="icon-x" aria-hidden="true"></span>
|
||||
</Button>
|
||||
{/each}
|
||||
{/if}
|
||||
|
||||
{#if showInput}
|
||||
<InputSelect
|
||||
{id}
|
||||
label="Rel"
|
||||
required
|
||||
placeholder={`Select ${attribute.key}`}
|
||||
bind:value={relatedList[total]}
|
||||
options={options?.filter((n) => !relatedList.includes(n.value))}
|
||||
on:change={() => {
|
||||
value = relatedList;
|
||||
showInput = false;
|
||||
}} />
|
||||
<Button extraCompact ariaLabel={`Hide input`} on:click={() => (showInput = false)}>
|
||||
<span class="icon-x" aria-hidden="true"></span>
|
||||
</Button>
|
||||
{/if}
|
||||
{#if paginatedItems && editing}
|
||||
{#each paginatedItems as item, i}
|
||||
<InputSelect {id} label="Rel" required bind:value={item} {options} />
|
||||
<Button
|
||||
extraCompact
|
||||
ariaLabel={`Delete item ${i}`}
|
||||
on:click={() => {
|
||||
relatedList.splice(i, 1);
|
||||
relatedList = relatedList;
|
||||
value = relatedList;
|
||||
}}>
|
||||
<span class="icon-x" aria-hidden="true"></span>
|
||||
</Button>
|
||||
{/each}
|
||||
{/if}
|
||||
</ul>
|
||||
{#if editing}
|
||||
<div class="u-flex u-margin-block-start-32 u-main-space-between">
|
||||
<p class="text">Total results: {total}</p>
|
||||
<PaginationInline {limit} bind:offset {total} hidePages />
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
{#if total > 0 && !editing}
|
||||
<Button
|
||||
extraCompact
|
||||
disabled={showInput}
|
||||
on:click={() => {
|
||||
showInput = true;
|
||||
}}>
|
||||
<Icon icon={IconPlus} slot="start" size="s" />
|
||||
Add item
|
||||
</Button>
|
||||
{/if}
|
||||
</div>
|
||||
</Layout.Stack>
|
||||
{:else}
|
||||
<InputSelect
|
||||
{id}
|
||||
|
||||
+5
@@ -15,3 +15,8 @@ export function isRelationship(attribute: Attributes): attribute is Models.Attri
|
||||
if (!attribute) return false;
|
||||
return attribute?.type === 'relationship';
|
||||
}
|
||||
|
||||
export function isString(attribute: Attributes): attribute is Models.AttributeString {
|
||||
if (!attribute) return false;
|
||||
return attribute?.type === 'string';
|
||||
}
|
||||
|
||||
+6
-1
@@ -79,13 +79,15 @@
|
||||
{ id: 'type' },
|
||||
{ id: 'attributes' },
|
||||
{ id: 'orders' },
|
||||
{ id: 'lengths' },
|
||||
{ id: 'actions', width: 40 }
|
||||
]}>
|
||||
<svelte:fragment slot="header" let:root>
|
||||
<Table.Header.Cell column="key" {root}>Key</Table.Header.Cell>
|
||||
<Table.Header.Cell column="type" {root}>Type</Table.Header.Cell>
|
||||
<Table.Header.Cell column="attributes" {root}>Attributes</Table.Header.Cell>
|
||||
<Table.Header.Cell column="orders" {root}>Asc/Desc</Table.Header.Cell>
|
||||
<Table.Header.Cell column="orders" {root}>Orders</Table.Header.Cell>
|
||||
<Table.Header.Cell column="lengths" {root}>Lengths</Table.Header.Cell>
|
||||
<Table.Header.Cell column="actions" {root} />
|
||||
</svelte:fragment>
|
||||
{#each data.collection.indexes as index}
|
||||
@@ -118,6 +120,9 @@
|
||||
<Table.Cell column="orders" {root}>
|
||||
{index.orders}
|
||||
</Table.Cell>
|
||||
<Table.Cell column="lengths" {root}>
|
||||
{index.lengths}
|
||||
</Table.Cell>
|
||||
<Table.Cell column="actions" {root}>
|
||||
<Popover let:toggle padding="none" placement="bottom-end">
|
||||
<Button text icon ariaLabel="more options" on:click={toggle}>
|
||||
|
||||
+30
-16
@@ -5,7 +5,7 @@
|
||||
import { Submit, trackError, trackEvent } from '$lib/actions/analytics';
|
||||
import { Modal } from '$lib/components';
|
||||
import { Dependencies } from '$lib/constants';
|
||||
import { Button, InputSelect, InputText } from '$lib/elements/forms';
|
||||
import { Button, InputNumber, InputSelect, InputText } from '$lib/elements/forms';
|
||||
import { remove } from '$lib/helpers/array';
|
||||
import { addNotification } from '$lib/stores/notifications';
|
||||
import { sdk } from '$lib/stores/sdk';
|
||||
@@ -14,6 +14,7 @@
|
||||
import { type Attributes, collection, indexes } from '../store';
|
||||
import { Icon, Layout } from '@appwrite.io/pink-svelte';
|
||||
import { IconPlus } from '@appwrite.io/pink-icons-svelte';
|
||||
import { flags } from '$lib/flags';
|
||||
|
||||
export let showCreateIndex = false;
|
||||
export let externalAttribute: Attributes = null;
|
||||
@@ -36,7 +37,9 @@
|
||||
label: attribute.key
|
||||
}));
|
||||
|
||||
let attributeList = [{ value: '', order: '' }];
|
||||
let attributeList = [{ value: '', order: '', length: null }];
|
||||
|
||||
const showLengths = flags.showIndexLengths(page.data);
|
||||
|
||||
function generateIndexKey() {
|
||||
let indexKeys = $indexes.map((index) => index.key);
|
||||
@@ -51,8 +54,8 @@
|
||||
|
||||
function initialize() {
|
||||
attributeList = externalAttribute
|
||||
? [{ value: externalAttribute.key, order: 'ASC' }]
|
||||
: [{ value: '', order: 'ASC' }];
|
||||
? [{ value: externalAttribute.key, order: 'ASC', length: null }]
|
||||
: [{ value: '', order: 'ASC', length: null }];
|
||||
selectedType = IndexType.Key;
|
||||
key = `index_${$indexes.length + 1}`;
|
||||
}
|
||||
@@ -78,8 +81,10 @@
|
||||
key,
|
||||
selectedType,
|
||||
attributeList.map((a) => a.value),
|
||||
attributeList.map((a) => a.order)
|
||||
attributeList.map((a) => a.order),
|
||||
attributeList.map((a) => (a.length ? Number(a.length) : null))
|
||||
);
|
||||
|
||||
await Promise.allSettled([
|
||||
invalidate(Dependencies.COLLECTION),
|
||||
invalidate(Dependencies.DATABASE)
|
||||
@@ -105,7 +110,7 @@
|
||||
if (addAttributeDisabled) return;
|
||||
|
||||
// We assign instead of pushing to trigger Svelte's reactivity
|
||||
attributeList = [...attributeList, { value: '', order: '' }];
|
||||
attributeList = [...attributeList, { value: '', order: '', length: null }];
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -134,17 +139,26 @@
|
||||
label={i === 0 ? 'Attribute' : undefined}
|
||||
placeholder="Select Attribute"
|
||||
bind:value={attribute.value} />
|
||||
|
||||
<InputSelect
|
||||
options={[
|
||||
{ value: 'ASC', label: 'ASC' },
|
||||
{ value: 'DESC', label: 'DESC' }
|
||||
]}
|
||||
required
|
||||
id={`order-${i}`}
|
||||
label={i === 0 ? 'Order' : undefined}
|
||||
bind:value={attribute.order}
|
||||
placeholder="Select Order" />
|
||||
|
||||
<Layout.Stack direction="row" alignItems="flex-end" gap="xs">
|
||||
<InputSelect
|
||||
options={[
|
||||
{ value: 'ASC', label: 'ASC' },
|
||||
{ value: 'DESC', label: 'DESC' }
|
||||
]}
|
||||
required
|
||||
id={`order-${i}`}
|
||||
label={i === 0 ? 'Order' : undefined}
|
||||
bind:value={attribute.order}
|
||||
placeholder="Select Order" />
|
||||
{#if selectedType === IndexType.Key && showLengths}
|
||||
<InputNumber
|
||||
id={`length-${i}`}
|
||||
label={i === 0 ? 'Length' : undefined}
|
||||
placeholder="Enter Length"
|
||||
bind:value={attribute.length} />
|
||||
{/if}
|
||||
<Button
|
||||
icon
|
||||
compact
|
||||
|
||||
+12
@@ -3,9 +3,13 @@
|
||||
import { InputText } from '$lib/elements/forms';
|
||||
import { Layout } from '@appwrite.io/pink-svelte';
|
||||
import type { Models } from '@appwrite.io/console';
|
||||
import { page } from '$app/state';
|
||||
import { flags } from '$lib/flags';
|
||||
|
||||
export let showOverview = false;
|
||||
export let selectedIndex: Models.Index = null;
|
||||
|
||||
const showLengths = flags.showIndexLengths(page.data);
|
||||
</script>
|
||||
|
||||
<Modal title="Overview" bind:show={showOverview}>
|
||||
@@ -39,6 +43,14 @@
|
||||
id={`value-${selectedIndex.orders[i]}`}
|
||||
value={selectedIndex.orders[i]}
|
||||
readonly />
|
||||
{#if showLengths}
|
||||
<InputText
|
||||
required
|
||||
label={i === 0 ? 'Length' : ''}
|
||||
id={`value-${selectedIndex.lengths[i]}`}
|
||||
value={selectedIndex.lengths[i]?.toString() ?? null}
|
||||
readonly />
|
||||
{/if}
|
||||
</Layout.Stack>
|
||||
{/each}
|
||||
{/if}
|
||||
|
||||
+85
-67
@@ -12,7 +12,11 @@
|
||||
import type { Models } from '@appwrite.io/console';
|
||||
import { afterUpdate, onMount } from 'svelte';
|
||||
import type { PageData } from './$types';
|
||||
import { isRelationship, isRelationshipToMany } from './document-[document]/attributes/store';
|
||||
import {
|
||||
isRelationship,
|
||||
isRelationshipToMany,
|
||||
isString
|
||||
} from './document-[document]/attributes/store';
|
||||
import RelationshipsModal from './relationshipsModal.svelte';
|
||||
import { attributes, collection, columns } from './store';
|
||||
import type { ColumnType } from '$lib/helpers/types';
|
||||
@@ -23,11 +27,12 @@
|
||||
Link,
|
||||
Badge,
|
||||
FloatingActionBar,
|
||||
InteractiveText,
|
||||
Typography
|
||||
} from '@appwrite.io/pink-svelte';
|
||||
import { toLocaleDateTime } from '$lib/helpers/date';
|
||||
import DualTimeView from '$lib/components/dualTimeView.svelte';
|
||||
|
||||
import { flags } from '$lib/flags';
|
||||
export let data: PageData;
|
||||
|
||||
const databaseId = page.params.database;
|
||||
@@ -163,6 +168,8 @@
|
||||
) as Models.AttributeRelationship[];
|
||||
|
||||
let checked = false;
|
||||
|
||||
const showEncrypt = flags.showAttributeEncrypt(data);
|
||||
</script>
|
||||
|
||||
<Table.Root
|
||||
@@ -198,77 +205,88 @@
|
||||
|
||||
{#each $columns as { id } (id)}
|
||||
{@const attr = $attributes.find((n) => n.key === id)}
|
||||
<Table.Cell column={id} {root}>
|
||||
{#if isRelationship(attr)}
|
||||
{@const args = displayNames?.[attr.relatedCollection] ?? ['$id']}
|
||||
{#if !isRelationshipToMany(attr)}
|
||||
{#if document[id]}
|
||||
{@const related = document[id]}
|
||||
<Link.Button
|
||||
variant="muted"
|
||||
on:click={(e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
goto(
|
||||
`${base}/project-${page.params.region}-${page.params.project}/databases/database-${databaseId}/collection-${attr.relatedCollection}/document-${related.$id}`
|
||||
);
|
||||
}}>
|
||||
{#each args as arg, i}
|
||||
{#if arg !== undefined}
|
||||
{#if i}
|
||||
|
|
||||
{#if attr}
|
||||
<Table.Cell column={id} {root}>
|
||||
{#if isRelationship(attr)}
|
||||
{@const args = displayNames?.[attr.relatedCollection] ?? ['$id']}
|
||||
{#if !isRelationshipToMany(attr)}
|
||||
{#if document[id]}
|
||||
{@const related = document[id]}
|
||||
<Link.Button
|
||||
variant="muted"
|
||||
on:click={(e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
goto(
|
||||
`${base}/project-${page.params.region}-${page.params.project}/databases/database-${databaseId}/collection-${attr.relatedCollection}/document-${related.$id}`
|
||||
);
|
||||
}}>
|
||||
{#each args as arg, i}
|
||||
{#if arg !== undefined}
|
||||
{#if i}
|
||||
|
|
||||
{/if}
|
||||
<span class="text" data-private>
|
||||
{related?.[arg]}
|
||||
</span>
|
||||
{/if}
|
||||
<span class="text" data-private>
|
||||
{related?.[arg]}
|
||||
</span>
|
||||
{/if}
|
||||
{/each}
|
||||
</Link.Button>
|
||||
{/each}
|
||||
</Link.Button>
|
||||
{:else}
|
||||
<span class="text">n/a</span>
|
||||
{/if}
|
||||
{:else}
|
||||
<span class="text">n/a</span>
|
||||
{@const itemsNum = document[id]?.length}
|
||||
<Button.Button
|
||||
variant="extra-compact"
|
||||
disabled={!itemsNum}
|
||||
badge={itemsNum ?? 0}
|
||||
on:click={(e) => {
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
relationshipData = document[id];
|
||||
showRelationships = true;
|
||||
selectedRelationship = attr;
|
||||
}}>
|
||||
Items
|
||||
</Button.Button>
|
||||
{/if}
|
||||
{:else}
|
||||
{@const itemsNum = document[id]?.length}
|
||||
<Button.Button
|
||||
variant="extra-compact"
|
||||
disabled={!itemsNum}
|
||||
badge={itemsNum ?? 0}
|
||||
on:click={(e) => {
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
relationshipData = document[id];
|
||||
showRelationships = true;
|
||||
selectedRelationship = attr;
|
||||
}}>
|
||||
Items
|
||||
</Button.Button>
|
||||
{/if}
|
||||
{:else}
|
||||
{@const formatted = formatColumn(document[id])}
|
||||
{@const isDatetimeAttribute = attr.type === 'datetime'}
|
||||
{#if isDatetimeAttribute}
|
||||
<DualTimeView time={formatted.whole}>
|
||||
<span slot="title">Timestamp</span>
|
||||
{toLocaleDateTime(formatted.whole, true, 'UTC')}
|
||||
</DualTimeView>
|
||||
{:else if formatted.truncated}
|
||||
<Tooltip placement="bottom" disabled={!formatted.truncated}>
|
||||
{@const datetime = document[id]}
|
||||
{@const formatted = formatColumn(document[id])}
|
||||
{@const isDatetimeAttribute = attr.type === 'datetime'}
|
||||
{#if isDatetimeAttribute}
|
||||
<DualTimeView time={datetime}>
|
||||
<span slot="title">Timestamp</span>
|
||||
{toLocaleDateTime(datetime, true)}
|
||||
</DualTimeView>
|
||||
{:else if isString(attr) && attr.encrypt && showEncrypt}
|
||||
<button on:click={(e) => e.preventDefault()}>
|
||||
<InteractiveText
|
||||
copy={false}
|
||||
variant="secret"
|
||||
isVisible={false}
|
||||
text={formatted.value} />
|
||||
</button>
|
||||
{:else if formatted.truncated}
|
||||
<Tooltip placement="bottom" disabled={!formatted.truncated}>
|
||||
<Typography.Text truncate>{formatted.value}</Typography.Text>
|
||||
<span
|
||||
let:showing
|
||||
slot="tooltip"
|
||||
style:white-space="pre-wrap"
|
||||
style:word-break="break-all">
|
||||
{#if showing}
|
||||
{formatted.whole}
|
||||
{/if}
|
||||
</span>
|
||||
</Tooltip>
|
||||
{:else}
|
||||
<Typography.Text truncate>{formatted.value}</Typography.Text>
|
||||
<span
|
||||
let:showing
|
||||
slot="tooltip"
|
||||
style:white-space="pre-wrap"
|
||||
style:word-break="break-all">
|
||||
{#if showing}
|
||||
{formatted.whole}
|
||||
{/if}
|
||||
</span>
|
||||
</Tooltip>
|
||||
{:else}
|
||||
<Typography.Text truncate>{formatted.value}</Typography.Text>
|
||||
{/if}
|
||||
{/if}
|
||||
{/if}
|
||||
</Table.Cell>
|
||||
</Table.Cell>
|
||||
{/if}
|
||||
{/each}
|
||||
<Table.Cell column="$created" {root}>
|
||||
<DualTimeView time={document.$createdAt} />
|
||||
|
||||
+8
-2
@@ -1,6 +1,6 @@
|
||||
<script lang="ts">
|
||||
import { Card, SvgIcon } from '$lib/components';
|
||||
import { Icon, Layout, Typography } from '@appwrite.io/pink-svelte';
|
||||
import { Icon, Layout, Skeleton, Typography } from '@appwrite.io/pink-svelte';
|
||||
import { IconGithub, IconGitBranch } from '@appwrite.io/pink-icons-svelte';
|
||||
import type { Models, Runtime } from '@appwrite.io/console';
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
export let rootDir: string = undefined;
|
||||
export let showGitData = true;
|
||||
export let runtimes: Models.RuntimeList;
|
||||
export let loading = false;
|
||||
|
||||
$: selectedRuntime = runtimes?.runtimes.find((r) => r.$id === runtime);
|
||||
</script>
|
||||
@@ -18,7 +19,7 @@
|
||||
<Layout.Stack gap="xl">
|
||||
<slot />
|
||||
<Layout.Stack gap="l">
|
||||
{#if selectedRuntime?.name}
|
||||
{#if selectedRuntime?.name && !loading}
|
||||
<Layout.Stack gap="xxxs">
|
||||
<Typography.Caption variant="400">Runtime</Typography.Caption>
|
||||
<Layout.Stack gap="xxs" alignItems="center" direction="row">
|
||||
@@ -30,6 +31,11 @@
|
||||
</Typography.Text>
|
||||
</Layout.Stack>
|
||||
</Layout.Stack>
|
||||
{:else if loading}
|
||||
<Layout.Stack gap="xs">
|
||||
<Skeleton variant="line" width={100} height={16} />
|
||||
<Skeleton variant="line" width="100%" height={20} />
|
||||
</Layout.Stack>
|
||||
{/if}
|
||||
{#if showGitData}
|
||||
{#if repositoryName}
|
||||
|
||||
+29
-14
@@ -7,7 +7,7 @@
|
||||
import { organization } from '$lib/stores/organization';
|
||||
import { isCloud } from '$lib/system';
|
||||
import { IconPencil } from '@appwrite.io/pink-icons-svelte';
|
||||
import { Fieldset, Icon, Input, Layout, Tag } from '@appwrite.io/pink-svelte';
|
||||
import { Fieldset, Icon, Input, Layout, Skeleton, Tag } from '@appwrite.io/pink-svelte';
|
||||
import type { ComponentType } from 'svelte';
|
||||
|
||||
export let name: string;
|
||||
@@ -25,6 +25,7 @@
|
||||
value: string;
|
||||
label: string;
|
||||
}[] = [];
|
||||
export let loading = false;
|
||||
|
||||
let showCustomId = false;
|
||||
</script>
|
||||
@@ -51,13 +52,20 @@
|
||||
{/if}
|
||||
</Layout.Stack>
|
||||
{#key runtime}
|
||||
<InputSelect
|
||||
id="runtime"
|
||||
label="Runtime"
|
||||
placeholder="Select runtime"
|
||||
bind:value={runtime}
|
||||
required
|
||||
{options} />
|
||||
{#if loading}
|
||||
<Layout.Stack gap="xs">
|
||||
<Skeleton variant="line" width={100} height={20} />
|
||||
<Skeleton variant="line" width="100%" height={32} />
|
||||
</Layout.Stack>
|
||||
{:else}
|
||||
<InputSelect
|
||||
id="runtime"
|
||||
label="Runtime"
|
||||
placeholder="Select runtime"
|
||||
bind:value={runtime}
|
||||
required
|
||||
{options} />
|
||||
{/if}
|
||||
{/key}
|
||||
|
||||
{#if isCloud}
|
||||
@@ -79,12 +87,19 @@
|
||||
</Layout.Stack>
|
||||
{/if}
|
||||
{#if showEntrypoint}
|
||||
<InputText
|
||||
label="Entrypoint"
|
||||
id="entrypoint"
|
||||
bind:value={entrypoint}
|
||||
required
|
||||
placeholder="Enter entrypoint" />
|
||||
{#if loading}
|
||||
<Layout.Stack gap="xs">
|
||||
<Skeleton variant="line" width={100} height={20} />
|
||||
<Skeleton variant="line" width="100%" height={32} />
|
||||
</Layout.Stack>
|
||||
{:else}
|
||||
<InputText
|
||||
label="Entrypoint"
|
||||
id="entrypoint"
|
||||
bind:value={entrypoint}
|
||||
required
|
||||
placeholder="Enter entrypoint" />
|
||||
{/if}
|
||||
{/if}
|
||||
</Layout.Stack>
|
||||
</Fieldset>
|
||||
|
||||
+33
-3
@@ -10,7 +10,7 @@
|
||||
import { installation, repository } from '$lib/stores/vcs';
|
||||
import { Layout } from '@appwrite.io/pink-svelte';
|
||||
import { writable } from 'svelte/store';
|
||||
import { ID, Runtime, VCSDeploymentType } from '@appwrite.io/console';
|
||||
import { ID, Runtime, VCSDeploymentType, VCSDetectionType } from '@appwrite.io/console';
|
||||
import type { Models } from '@appwrite.io/console';
|
||||
import { onMount } from 'svelte';
|
||||
import Details from '../(components)/details.svelte';
|
||||
@@ -57,12 +57,40 @@
|
||||
let silentMode = false;
|
||||
let specification = specificationOptions[0].value;
|
||||
|
||||
let detectingRuntime = true;
|
||||
|
||||
onMount(async () => {
|
||||
installation.set(data.installation);
|
||||
repository.set(data.repository);
|
||||
name = data.repository.name;
|
||||
|
||||
await detectRuntime();
|
||||
});
|
||||
|
||||
async function detectRuntime() {
|
||||
try {
|
||||
detectingRuntime = true;
|
||||
|
||||
const detections = (await sdk
|
||||
.forProject(page.params.regionn, page.params.project)
|
||||
.vcs.createRepositoryDetection(
|
||||
data.installation.$id,
|
||||
page.params.repository,
|
||||
VCSDetectionType.Runtime
|
||||
)) as unknown as Models.DetectionRuntime; /* SDK return type is wrong atm */
|
||||
|
||||
entrypoint = detections.entrypoint;
|
||||
buildCommand = detections.commands;
|
||||
runtime = detections.runtime as Runtime;
|
||||
|
||||
trackEvent(Submit.FrameworkDetect, { runtime, source: 'repository' });
|
||||
} catch (error) {
|
||||
trackError(error, Submit.FrameworkDetect);
|
||||
} finally {
|
||||
detectingRuntime = false;
|
||||
}
|
||||
}
|
||||
|
||||
async function create() {
|
||||
try {
|
||||
const func = await sdk
|
||||
@@ -154,7 +182,8 @@
|
||||
bind:specification
|
||||
{specificationOptions}
|
||||
options={runtimeOptions}
|
||||
showEntrypoint />
|
||||
showEntrypoint
|
||||
loading={detectingRuntime} />
|
||||
|
||||
<ProductionBranchFieldset
|
||||
bind:branch
|
||||
@@ -173,7 +202,8 @@
|
||||
runtimes={data.runtimesList}
|
||||
repositoryName={data.repository.name}
|
||||
{branch}
|
||||
{rootDir} />
|
||||
{rootDir}
|
||||
loading={detectingRuntime} />
|
||||
</svelte:fragment>
|
||||
|
||||
<svelte:fragment slot="footer">
|
||||
|
||||
+8
-8
@@ -3,17 +3,17 @@ import { sdk } from '$lib/stores/sdk';
|
||||
export const load = async ({ parent, params, url }) => {
|
||||
const { installations, runtimesList, specificationsList } = await parent();
|
||||
|
||||
const [repository] = await Promise.all([
|
||||
sdk
|
||||
.forProject(params.region, params.project)
|
||||
.vcs.getRepository(url.searchParams.get('installation'), params.repository)
|
||||
]);
|
||||
const repository = await sdk
|
||||
.forProject(params.region, params.project)
|
||||
.vcs.getRepository(url.searchParams.get('installation'), params.repository);
|
||||
|
||||
const installation = installations.installations.find(
|
||||
(installation) => installation.$id === url.searchParams.get('installation')
|
||||
);
|
||||
|
||||
return {
|
||||
installation,
|
||||
installations,
|
||||
installation: installations.installations.find(
|
||||
(installation) => installation.$id === url.searchParams.get('installation')
|
||||
),
|
||||
repository,
|
||||
runtimesList,
|
||||
specificationsList
|
||||
|
||||
@@ -1,19 +1,11 @@
|
||||
<script lang="ts">
|
||||
import { base } from '$app/paths';
|
||||
import { page } from '$app/state';
|
||||
import {
|
||||
Empty,
|
||||
EmptyFilter,
|
||||
EmptySearch,
|
||||
Id,
|
||||
PaginationWithLimit,
|
||||
SearchQuery,
|
||||
ViewSelector
|
||||
} from '$lib/components';
|
||||
import { Filters, hasPageQueries } from '$lib/components/filters';
|
||||
import { Empty, EmptyFilter, EmptySearch, Id, PaginationWithLimit } from '$lib/components';
|
||||
import { hasPageQueries } from '$lib/components/filters';
|
||||
import { Button } from '$lib/elements/forms';
|
||||
import { toLocaleDateTime } from '$lib/helpers/date';
|
||||
import { Container } from '$lib/layout';
|
||||
import { Container, ResponsiveContainerHeader } from '$lib/layout';
|
||||
import { MessagingProviderType } from '@appwrite.io/console';
|
||||
import CreateMessageDropdown from './createMessageDropdown.svelte';
|
||||
import FailedModal from './failedModal.svelte';
|
||||
@@ -105,18 +97,18 @@
|
||||
</script>
|
||||
|
||||
<Container>
|
||||
<Layout.Stack direction="row" justifyContent="space-between">
|
||||
<Layout.Stack direction="row" alignItems="center">
|
||||
<SearchQuery placeholder="Search by description, type, status, or ID" />
|
||||
</Layout.Stack>
|
||||
<Layout.Stack direction="row" alignItems="center" justifyContent="flex-end">
|
||||
<Filters query={data.query} {columns} analyticsSource="messaging_messages" />
|
||||
<ViewSelector view={data.view} {columns} hideView />
|
||||
{#if $canWriteMessages}
|
||||
<CreateMessageDropdown />
|
||||
{/if}
|
||||
</Layout.Stack>
|
||||
</Layout.Stack>
|
||||
<ResponsiveContainerHeader
|
||||
{columns}
|
||||
bind:view={data.view}
|
||||
hideView
|
||||
hasFilters
|
||||
hasSearch
|
||||
analyticsSource="messaging_messages"
|
||||
searchPlaceholder="Search by description, type, status, or ID">
|
||||
{#if $canWriteMessages}
|
||||
<CreateMessageDropdown />
|
||||
{/if}
|
||||
</ResponsiveContainerHeader>
|
||||
|
||||
{#if data.messages.total}
|
||||
<Table.Root
|
||||
|
||||
@@ -1,27 +1,20 @@
|
||||
<script lang="ts">
|
||||
import { page } from '$app/state';
|
||||
import { Button } from '$lib/elements/forms';
|
||||
import {
|
||||
Empty,
|
||||
EmptySearch,
|
||||
SearchQuery,
|
||||
PaginationWithLimit,
|
||||
EmptyFilter,
|
||||
ViewSelector
|
||||
} from '$lib/components';
|
||||
import { Empty, EmptySearch, PaginationWithLimit, EmptyFilter } from '$lib/components';
|
||||
import Create from './create.svelte';
|
||||
import { goto } from '$app/navigation';
|
||||
import { Container } from '$lib/layout';
|
||||
import { Container, ResponsiveContainerHeader } from '$lib/layout';
|
||||
import { base } from '$app/paths';
|
||||
import type { Models } from '@appwrite.io/console';
|
||||
import type { PageData } from './$types';
|
||||
import { showCreate } from './store';
|
||||
import { Filters, hasPageQueries } from '$lib/components/filters';
|
||||
import { hasPageQueries } from '$lib/components/filters';
|
||||
import Table from './table.svelte';
|
||||
import type { Column } from '$lib/helpers/types';
|
||||
import { writable } from 'svelte/store';
|
||||
import { canWriteTopics } from '$lib/stores/roles';
|
||||
import { Icon, Layout } from '@appwrite.io/pink-svelte';
|
||||
import { Icon } from '@appwrite.io/pink-svelte';
|
||||
import { View } from '$lib/helpers/load';
|
||||
import { IconPlus } from '@appwrite.io/pink-icons-svelte';
|
||||
import { Click, trackEvent } from '$lib/actions/analytics';
|
||||
@@ -72,26 +65,26 @@
|
||||
</script>
|
||||
|
||||
<Container>
|
||||
<Layout.Stack direction="row" justifyContent="space-between">
|
||||
<Layout.Stack direction="row" alignItems="center">
|
||||
<SearchQuery placeholder="Search by name or ID" />
|
||||
</Layout.Stack>
|
||||
<Layout.Stack direction="row" alignItems="center" justifyContent="flex-end">
|
||||
<Filters query={data.query} {columns} analyticsSource="messaging_topics_filter" />
|
||||
<ViewSelector view={View.Table} {columns} hideView />
|
||||
{#if $canWriteTopics}
|
||||
<Button
|
||||
on:click={() => {
|
||||
$showCreate = true;
|
||||
trackEvent(Click.MessagingTopicCreateClick);
|
||||
}}
|
||||
event="create_topic">
|
||||
<Icon icon={IconPlus} slot="start" size="s" />
|
||||
Create topic
|
||||
</Button>
|
||||
{/if}
|
||||
</Layout.Stack>
|
||||
</Layout.Stack>
|
||||
<ResponsiveContainerHeader
|
||||
{columns}
|
||||
view={View.Table}
|
||||
hideView
|
||||
hasFilters
|
||||
hasSearch
|
||||
analyticsSource="messaging_topics_filter"
|
||||
searchPlaceholder="Search by name or ID">
|
||||
{#if $canWriteTopics}
|
||||
<Button
|
||||
on:click={() => {
|
||||
$showCreate = true;
|
||||
trackEvent(Click.MessagingTopicCreateClick);
|
||||
}}
|
||||
event="create_topic">
|
||||
<Icon icon={IconPlus} slot="start" size="s" />
|
||||
Create topic
|
||||
</Button>
|
||||
{/if}
|
||||
</ResponsiveContainerHeader>
|
||||
|
||||
{#if data.topics.total}
|
||||
<Table columns={$columns} {data} />
|
||||
|
||||
+1
-1
@@ -98,7 +98,7 @@
|
||||
<div class="u-flex u-gap-16">
|
||||
<div class="u-cross-child-center u-line-height-1-5">
|
||||
<h6 class="u-bold">{$platform.name}</h6>
|
||||
<p>{$platform.hostname}</p>
|
||||
<p>{$platform.hostname || $platform.key}</p>
|
||||
</div>
|
||||
</div>
|
||||
</Box>
|
||||
|
||||
+57
-56
@@ -91,11 +91,11 @@ const val APPWRITE_PUBLIC_ENDPOINT = "${sdk.forProject(page.params.region, page.
|
||||
});
|
||||
</script>
|
||||
|
||||
<Wizard title="Add Android platform" bind:showExitModal confirmExit>
|
||||
<Form onSubmit={createAndroidPlatform}>
|
||||
<Layout.Stack gap="xxl">
|
||||
<!-- Step One -->
|
||||
{#if !isPlatformCreated}
|
||||
<Wizard title="Add Android platform" bind:showExitModal confirmExit={!isPlatformCreated}>
|
||||
<Layout.Stack gap="xxl">
|
||||
<!-- Step One -->
|
||||
{#if !isPlatformCreated}
|
||||
<Form onSubmit={createAndroidPlatform}>
|
||||
<Fieldset legend="Details">
|
||||
<Layout.Stack gap="l" alignItems="flex-end">
|
||||
<Layout.Stack gap="s">
|
||||
@@ -136,59 +136,60 @@ const val APPWRITE_PUBLIC_ENDPOINT = "${sdk.forProject(page.params.region, page.
|
||||
</Button>
|
||||
</Layout.Stack>
|
||||
</Fieldset>
|
||||
{:else}
|
||||
<Layout.Stack gap="xxl">
|
||||
<Card padding="s" radius="s">
|
||||
<Layout.Stack
|
||||
direction="row"
|
||||
justifyContent="space-between"
|
||||
alignItems="center"
|
||||
gap="xs">
|
||||
<Layout.Stack direction="row" alignItems="center" gap="s">
|
||||
<Icon size="m" icon={IconAndroid} />
|
||||
<Typography.Text variant="m-400" color="--fgcolor-neutral-primary">
|
||||
{$createPlatform.name} ({$createPlatform.key})
|
||||
</Typography.Text>
|
||||
</Layout.Stack>
|
||||
</Form>
|
||||
{:else}
|
||||
<Layout.Stack gap="xxl">
|
||||
<Card padding="s" radius="s">
|
||||
<Layout.Stack
|
||||
direction="row"
|
||||
justifyContent="space-between"
|
||||
alignItems="center"
|
||||
gap="xs">
|
||||
<Layout.Stack direction="row" alignItems="center" gap="s">
|
||||
<Icon size="m" icon={IconAndroid} />
|
||||
<Typography.Text variant="m-400" color="--fgcolor-neutral-primary">
|
||||
{$createPlatform.name} ({$createPlatform.key})
|
||||
</Typography.Text>
|
||||
</Layout.Stack>
|
||||
</Card>
|
||||
</Layout.Stack>
|
||||
{/if}
|
||||
|
||||
<!-- Step Two -->
|
||||
{#if isPlatformCreated}
|
||||
<Fieldset legend="Clone starter">
|
||||
<Layout.Stack gap="l">
|
||||
<Typography.Text variant="m-500">
|
||||
1. Clone the starter kit from GitHub using the terminal or Android
|
||||
Studio.
|
||||
</Typography.Text>
|
||||
|
||||
<!-- Temporary fix: Remove this div once Code splitting issue with stack spacing is resolved -->
|
||||
<div class="pink2-code-margin-fix">
|
||||
<Code lang="bash" lineNumbers code={gitCloneCode} />
|
||||
</div>
|
||||
|
||||
<Typography.Text variant="m-500"
|
||||
>2. Open the file <InlineCode
|
||||
size="s"
|
||||
code="data/repository/AppwriteRepository.kt" /> and update the configuration
|
||||
settings.</Typography.Text>
|
||||
|
||||
<!-- Temporary fix: Remove this div once Code splitting issue with stack spacing is resolved -->
|
||||
<div class="pink2-code-margin-fix">
|
||||
<Code lang="kotlin" lineNumbers code={updateConfigCode} />
|
||||
</div>
|
||||
|
||||
<Typography.Text variant="m-500"
|
||||
>3. Run the app on a connected device or emulator, then click the <InlineCode
|
||||
size="s"
|
||||
code="Send a ping" /> button to verify the setup.</Typography.Text>
|
||||
</Layout.Stack>
|
||||
</Fieldset>
|
||||
{/if}
|
||||
</Layout.Stack>
|
||||
</Form>
|
||||
</Card>
|
||||
</Layout.Stack>
|
||||
{/if}
|
||||
|
||||
<!-- Step Two -->
|
||||
{#if isPlatformCreated}
|
||||
<Fieldset legend="Clone starter">
|
||||
<Layout.Stack gap="l">
|
||||
<Typography.Text variant="m-500">
|
||||
1. If you're starting a new project, you can clone our starter kit from
|
||||
GitHub using the terminal, VSCode or Android Studio.
|
||||
</Typography.Text>
|
||||
|
||||
<!-- Temporary fix: Remove this div once Code splitting issue with stack spacing is resolved -->
|
||||
<div class="pink2-code-margin-fix">
|
||||
<Code lang="bash" lineNumbers code={gitCloneCode} />
|
||||
</div>
|
||||
|
||||
<Typography.Text variant="m-500"
|
||||
>2. Open the file <InlineCode
|
||||
size="s"
|
||||
code="data/repository/AppwriteRepository.kt" /> and update the configuration
|
||||
settings.</Typography.Text>
|
||||
|
||||
<!-- Temporary fix: Remove this div once Code splitting issue with stack spacing is resolved -->
|
||||
<div class="pink2-code-margin-fix">
|
||||
<Code lang="kotlin" lineNumbers code={updateConfigCode} />
|
||||
</div>
|
||||
|
||||
<Typography.Text variant="m-500"
|
||||
>3. Run the app on a connected device or emulator, then click the <InlineCode
|
||||
size="s"
|
||||
code="Send a ping" /> button to verify the setup.</Typography.Text>
|
||||
</Layout.Stack>
|
||||
</Fieldset>
|
||||
{/if}
|
||||
</Layout.Stack>
|
||||
|
||||
<svelte:fragment slot="aside">
|
||||
<Card padding="l" class="responsive-padding">
|
||||
<Layout.Stack gap="xxl">
|
||||
|
||||
+109
-104
@@ -102,116 +102,121 @@ APPWRITE_PUBLIC_ENDPOINT: "${sdk.forProject(page.params.region, page.params.proj
|
||||
});
|
||||
</script>
|
||||
|
||||
<Wizard title="Add Apple platform" bind:showExitModal confirmExit>
|
||||
<Form onSubmit={createApplePlatform}>
|
||||
<Layout.Stack gap="xxl">
|
||||
<!-- Step One -->
|
||||
<Layout.Grid gap="l" rowGap="l" columns={4} columnsXS={2}>
|
||||
{#each Object.entries(platforms) as [key, value]}
|
||||
<Pink2Card.Selector
|
||||
{value}
|
||||
id={key}
|
||||
title={key}
|
||||
imageRadius="s"
|
||||
name="framework"
|
||||
bind:group={platform}
|
||||
disabled={isCreatingPlatform || isPlatformCreated} />
|
||||
{/each}
|
||||
</Layout.Grid>
|
||||
<Wizard title="Add Apple platform" bind:showExitModal confirmExit={!isPlatformCreated}>
|
||||
<Layout.Stack gap="xxl">
|
||||
<Form onSubmit={createApplePlatform}>
|
||||
<Layout.Stack gap="xxl">
|
||||
<!-- Step One -->
|
||||
<Layout.Grid gap="l" rowGap="l" columns={4} columnsXS={2}>
|
||||
{#each Object.entries(platforms) as [key, value]}
|
||||
<Pink2Card.Selector
|
||||
{value}
|
||||
id={key}
|
||||
title={key}
|
||||
imageRadius="s"
|
||||
name="framework"
|
||||
bind:group={platform}
|
||||
disabled={isCreatingPlatform || isPlatformCreated} />
|
||||
{/each}
|
||||
</Layout.Grid>
|
||||
|
||||
<!-- Step Two -->
|
||||
{#if !isPlatformCreated}
|
||||
<Fieldset legend="Details">
|
||||
<Layout.Stack gap="l" alignItems="flex-end">
|
||||
<Layout.Stack gap="s">
|
||||
<InputText
|
||||
id="name"
|
||||
label="Name"
|
||||
placeholder="My Apple App"
|
||||
required
|
||||
bind:value={$createPlatform.name} />
|
||||
<!-- Step Two -->
|
||||
{#if !isPlatformCreated}
|
||||
<Fieldset legend="Details">
|
||||
<Layout.Stack gap="l" alignItems="flex-end">
|
||||
<Layout.Stack gap="s">
|
||||
<InputText
|
||||
id="name"
|
||||
label="Name"
|
||||
placeholder="My Apple App"
|
||||
required
|
||||
bind:value={$createPlatform.name} />
|
||||
|
||||
<!-- Tooltips on InputText don't work as of now -->
|
||||
<InputText
|
||||
id="hostname"
|
||||
label="Bundle ID"
|
||||
placeholder="com.company.appname"
|
||||
required
|
||||
bind:value={$createPlatform.key}>
|
||||
<Tooltip slot="info" maxWidth="15rem">
|
||||
<Icon icon={IconInfo} size="s" />
|
||||
<Typography.Caption variant="400" slot="tooltip">
|
||||
You can find your Bundle Identifier in the General tab for
|
||||
your app's primary target in Xcode.
|
||||
</Typography.Caption>
|
||||
</Tooltip>
|
||||
</InputText>
|
||||
</Layout.Stack>
|
||||
|
||||
<Button
|
||||
fullWidthMobile
|
||||
size="s"
|
||||
submit
|
||||
forceShowLoader
|
||||
submissionLoader={isCreatingPlatform}
|
||||
disabled={!platform ||
|
||||
!$createPlatform.name ||
|
||||
!$createPlatform.key ||
|
||||
isCreatingPlatform}>
|
||||
Create platform
|
||||
</Button>
|
||||
</Layout.Stack>
|
||||
</Fieldset>
|
||||
{:else}
|
||||
<Layout.Stack gap="xxl">
|
||||
<Card padding="s" radius="s">
|
||||
<Layout.Stack
|
||||
direction="row"
|
||||
justifyContent="space-between"
|
||||
alignItems="center"
|
||||
gap="xs">
|
||||
<Layout.Stack direction="row" alignItems="center" gap="s">
|
||||
<Icon size="m" icon={IconApple} />
|
||||
<Typography.Text variant="m-400" color="--fgcolor-neutral-primary">
|
||||
{$createPlatform.name} ({$createPlatform.key})
|
||||
</Typography.Text>
|
||||
<!-- Tooltips on InputText don't work as of now -->
|
||||
<InputText
|
||||
id="hostname"
|
||||
label="Bundle ID"
|
||||
placeholder="com.company.appname"
|
||||
required
|
||||
bind:value={$createPlatform.key}>
|
||||
<Tooltip slot="info" maxWidth="15rem">
|
||||
<Icon icon={IconInfo} size="s" />
|
||||
<Typography.Caption variant="400" slot="tooltip">
|
||||
You can find your Bundle Identifier in the General tab
|
||||
for your app's primary target in Xcode.
|
||||
</Typography.Caption>
|
||||
</Tooltip>
|
||||
</InputText>
|
||||
</Layout.Stack>
|
||||
</Layout.Stack>
|
||||
</Card>
|
||||
</Layout.Stack>
|
||||
{/if}
|
||||
|
||||
<!-- Step Three -->
|
||||
{#if isPlatformCreated}
|
||||
<Fieldset legend="Clone starter">
|
||||
<Layout.Stack gap="l">
|
||||
<Typography.Text variant="m-500">
|
||||
1. Clone the starter kit from GitHub using the terminal or XCode.
|
||||
</Typography.Text>
|
||||
|
||||
<!-- Temporary fix: Remove this div once Code splitting issue with stack spacing is resolved -->
|
||||
<div class="pink2-code-margin-fix">
|
||||
<Code lang="bash" lineNumbers code={gitCloneCode} />
|
||||
</div>
|
||||
|
||||
<Typography.Text variant="m-500"
|
||||
>2. Open the file <InlineCode size="s" code="Sources/Config.plist" /> and
|
||||
update the configuration settings.</Typography.Text>
|
||||
|
||||
<!-- Temporary fix: Remove this div once Code splitting issue with stack spacing is resolved -->
|
||||
<div class="pink2-code-margin-fix">
|
||||
<Code lang="plaintext" lineNumbers code={updateConfigCode} />
|
||||
</div>
|
||||
|
||||
<Typography.Text variant="m-500"
|
||||
>3. Run the app on a connected device or simulator, then click the <InlineCode
|
||||
<Button
|
||||
fullWidthMobile
|
||||
size="s"
|
||||
code="Send a ping" /> button to verify the setup.</Typography.Text>
|
||||
submit
|
||||
forceShowLoader
|
||||
submissionLoader={isCreatingPlatform}
|
||||
disabled={!platform ||
|
||||
!$createPlatform.name ||
|
||||
!$createPlatform.key ||
|
||||
isCreatingPlatform}>
|
||||
Create platform
|
||||
</Button>
|
||||
</Layout.Stack>
|
||||
</Fieldset>
|
||||
{:else}
|
||||
<Layout.Stack gap="xxl">
|
||||
<Card padding="s" radius="s">
|
||||
<Layout.Stack
|
||||
direction="row"
|
||||
justifyContent="space-between"
|
||||
alignItems="center"
|
||||
gap="xs">
|
||||
<Layout.Stack direction="row" alignItems="center" gap="s">
|
||||
<Icon size="m" icon={IconApple} />
|
||||
<Typography.Text
|
||||
variant="m-400"
|
||||
color="--fgcolor-neutral-primary">
|
||||
{$createPlatform.name} ({$createPlatform.key})
|
||||
</Typography.Text>
|
||||
</Layout.Stack>
|
||||
</Layout.Stack>
|
||||
</Card>
|
||||
</Layout.Stack>
|
||||
</Fieldset>
|
||||
{/if}
|
||||
</Layout.Stack>
|
||||
</Form>
|
||||
{/if}
|
||||
</Layout.Stack>
|
||||
</Form>
|
||||
|
||||
<!-- Step Three -->
|
||||
{#if isPlatformCreated}
|
||||
<Fieldset legend="Clone starter">
|
||||
<Layout.Stack gap="l">
|
||||
<Typography.Text variant="m-500">
|
||||
1. If you're starting a new project, you can clone our starter kit from
|
||||
GitHub using the terminal or XCode.
|
||||
</Typography.Text>
|
||||
|
||||
<!-- Temporary fix: Remove this div once Code splitting issue with stack spacing is resolved -->
|
||||
<div class="pink2-code-margin-fix">
|
||||
<Code lang="bash" lineNumbers code={gitCloneCode} />
|
||||
</div>
|
||||
|
||||
<Typography.Text variant="m-500"
|
||||
>2. Open the file <InlineCode size="s" code="Sources/Config.plist" /> and update
|
||||
the configuration settings.</Typography.Text>
|
||||
|
||||
<!-- Temporary fix: Remove this div once Code splitting issue with stack spacing is resolved -->
|
||||
<div class="pink2-code-margin-fix">
|
||||
<Code lang="plaintext" lineNumbers code={updateConfigCode} />
|
||||
</div>
|
||||
|
||||
<Typography.Text variant="m-500"
|
||||
>3. Run the app on a connected device or simulator, then click the <InlineCode
|
||||
size="s"
|
||||
code="Send a ping" /> button to verify the setup.</Typography.Text>
|
||||
</Layout.Stack>
|
||||
</Fieldset>
|
||||
{/if}
|
||||
</Layout.Stack>
|
||||
<svelte:fragment slot="aside">
|
||||
<Card padding="l" class="responsive-padding">
|
||||
<Layout.Stack gap="xxl">
|
||||
|
||||
+124
-120
@@ -161,133 +161,137 @@ static const String APPWRITE_PUBLIC_ENDPOINT = "${sdk.forProject(page.params.reg
|
||||
});
|
||||
</script>
|
||||
|
||||
<Wizard title="Add Flutter platform" bind:showExitModal confirmExit>
|
||||
<Form onSubmit={createFlutterPlatform}>
|
||||
<Layout.Stack gap="xxl">
|
||||
<!-- Step One -->
|
||||
<Layout.Grid gap="l" rowGap="l" columns={3} columnsXS={2} columnsXXS={1}>
|
||||
{#each Object.entries(platforms) as [key, value]}
|
||||
<Pink2Card.Selector
|
||||
{value}
|
||||
id={key}
|
||||
title={key}
|
||||
imageRadius="s"
|
||||
name="framework"
|
||||
bind:group={platform}
|
||||
disabled={isCreatingPlatform || isPlatformCreated} />
|
||||
{/each}
|
||||
</Layout.Grid>
|
||||
<Wizard title="Add Flutter platform" bind:showExitModal confirmExit={!isPlatformCreated}>
|
||||
<Layout.Stack gap="xxl">
|
||||
<Form onSubmit={createFlutterPlatform}>
|
||||
<Layout.Stack gap="xxl">
|
||||
<!-- Step One -->
|
||||
<Layout.Grid gap="l" rowGap="l" columns={3} columnsXS={2} columnsXXS={1}>
|
||||
{#each Object.entries(platforms) as [key, value]}
|
||||
<Pink2Card.Selector
|
||||
{value}
|
||||
id={key}
|
||||
title={key}
|
||||
imageRadius="s"
|
||||
name="framework"
|
||||
bind:group={platform}
|
||||
disabled={isCreatingPlatform || isPlatformCreated} />
|
||||
{/each}
|
||||
</Layout.Grid>
|
||||
|
||||
<!-- Step Two -->
|
||||
{#if !isPlatformCreated}
|
||||
<Fieldset legend="Details">
|
||||
<Layout.Stack gap="l" alignItems="flex-end">
|
||||
<Layout.Stack gap="s">
|
||||
<InputText
|
||||
id="name"
|
||||
label="Name"
|
||||
placeholder={placeholder[platform].name}
|
||||
required
|
||||
bind:value={$createPlatform.name} />
|
||||
|
||||
<!-- Tooltips on InputText don't work as of now -->
|
||||
{#if platform === PlatformType.Flutterweb}
|
||||
<!-- Step Two -->
|
||||
{#if !isPlatformCreated}
|
||||
<Fieldset legend="Details">
|
||||
<Layout.Stack gap="l" alignItems="flex-end">
|
||||
<Layout.Stack gap="s">
|
||||
<InputText
|
||||
id="hostname"
|
||||
label={hostname[platform]}
|
||||
placeholder={placeholder[platform].hostname}
|
||||
id="name"
|
||||
label="Name"
|
||||
placeholder={placeholder[platform].name}
|
||||
required
|
||||
bind:value={$createPlatform.hostname}>
|
||||
<Tooltip slot="info" maxWidth="15rem">
|
||||
<Icon icon={IconInfo} size="s" />
|
||||
<Typography.Caption variant="400" slot="tooltip">
|
||||
{placeholder[platform].tooltip}
|
||||
</Typography.Caption>
|
||||
</Tooltip>
|
||||
</InputText>
|
||||
{:else}
|
||||
<InputText
|
||||
id="key"
|
||||
label={hostname[platform]}
|
||||
placeholder={placeholder[platform].hostname}
|
||||
required
|
||||
bind:value={$createPlatform.key}>
|
||||
<Tooltip slot="info" maxWidth="15rem">
|
||||
<Icon icon={IconInfo} size="s" />
|
||||
<Typography.Caption variant="400" slot="tooltip">
|
||||
{placeholder[platform].tooltip}
|
||||
</Typography.Caption>
|
||||
</Tooltip>
|
||||
</InputText>
|
||||
{/if}
|
||||
</Layout.Stack>
|
||||
bind:value={$createPlatform.name} />
|
||||
|
||||
<Button
|
||||
fullWidthMobile
|
||||
size="s"
|
||||
submit
|
||||
forceShowLoader
|
||||
submissionLoader={isCreatingPlatform}
|
||||
disabled={!platform ||
|
||||
!$createPlatform.name ||
|
||||
(!$createPlatform.key && !$createPlatform.hostname) ||
|
||||
isCreatingPlatform}>
|
||||
Create platform
|
||||
</Button>
|
||||
</Layout.Stack>
|
||||
</Fieldset>
|
||||
{:else}
|
||||
<Layout.Stack gap="xxl">
|
||||
<Card padding="s" radius="s">
|
||||
<Layout.Stack
|
||||
direction="row"
|
||||
justifyContent="space-between"
|
||||
alignItems="center"
|
||||
gap="xs">
|
||||
<Layout.Stack direction="row" alignItems="center" gap="s">
|
||||
<Icon size="m" icon={IconFlutter} />
|
||||
<Typography.Text variant="m-400" color="--fgcolor-neutral-primary">
|
||||
{$createPlatform.name} ({$createPlatform.key})
|
||||
</Typography.Text>
|
||||
<!-- Tooltips on InputText don't work as of now -->
|
||||
{#if platform === PlatformType.Flutterweb}
|
||||
<InputText
|
||||
id="hostname"
|
||||
label={hostname[platform]}
|
||||
placeholder={placeholder[platform].hostname}
|
||||
required
|
||||
bind:value={$createPlatform.hostname}>
|
||||
<Tooltip slot="info" maxWidth="15rem">
|
||||
<Icon icon={IconInfo} size="s" />
|
||||
<Typography.Caption variant="400" slot="tooltip">
|
||||
{placeholder[platform].tooltip}
|
||||
</Typography.Caption>
|
||||
</Tooltip>
|
||||
</InputText>
|
||||
{:else}
|
||||
<InputText
|
||||
id="key"
|
||||
label={hostname[platform]}
|
||||
placeholder={placeholder[platform].hostname}
|
||||
required
|
||||
bind:value={$createPlatform.key}>
|
||||
<Tooltip slot="info" maxWidth="15rem">
|
||||
<Icon icon={IconInfo} size="s" />
|
||||
<Typography.Caption variant="400" slot="tooltip">
|
||||
{placeholder[platform].tooltip}
|
||||
</Typography.Caption>
|
||||
</Tooltip>
|
||||
</InputText>
|
||||
{/if}
|
||||
</Layout.Stack>
|
||||
|
||||
<Button
|
||||
fullWidthMobile
|
||||
size="s"
|
||||
submit
|
||||
forceShowLoader
|
||||
submissionLoader={isCreatingPlatform}
|
||||
disabled={!platform ||
|
||||
!$createPlatform.name ||
|
||||
(!$createPlatform.key && !$createPlatform.hostname) ||
|
||||
isCreatingPlatform}>
|
||||
Create platform
|
||||
</Button>
|
||||
</Layout.Stack>
|
||||
</Card>
|
||||
</Layout.Stack>
|
||||
{/if}
|
||||
|
||||
<!-- Step Three -->
|
||||
{#if isPlatformCreated}
|
||||
<Fieldset legend="Clone starter">
|
||||
<Layout.Stack gap="l">
|
||||
<Typography.Text variant="m-500">
|
||||
1. Clone the starter kit from GitHub using the terminal, Android Studio
|
||||
or VSCode.
|
||||
</Typography.Text>
|
||||
|
||||
<!-- Temporary fix: Remove this div once Code splitting issue with stack spacing is resolved -->
|
||||
<div class="pink2-code-margin-fix">
|
||||
<Code lang="bash" lineNumbers code={gitCloneCode} />
|
||||
</div>
|
||||
|
||||
<Typography.Text variant="m-500"
|
||||
>2. Open the file <InlineCode
|
||||
size="s"
|
||||
code="lib/constants/appwrite.dart" /> and update the configuration settings.</Typography.Text>
|
||||
|
||||
<!-- Temporary fix: Remove this div once Code splitting issue with stack spacing is resolved -->
|
||||
<div class="pink2-code-margin-fix">
|
||||
<Code lang="dart" lineNumbers code={updateConfigCode} />
|
||||
</div>
|
||||
|
||||
<Typography.Text variant="m-500"
|
||||
>3. Run the app on a connected device or simulator, then click the <InlineCode
|
||||
size="s"
|
||||
code="Send a ping" /> button to verify the setup.</Typography.Text>
|
||||
</Fieldset>
|
||||
{:else}
|
||||
<Layout.Stack gap="xxl">
|
||||
<Card padding="s" radius="s">
|
||||
<Layout.Stack
|
||||
direction="row"
|
||||
justifyContent="space-between"
|
||||
alignItems="center"
|
||||
gap="xs">
|
||||
<Layout.Stack direction="row" alignItems="center" gap="s">
|
||||
<Icon size="m" icon={IconFlutter} />
|
||||
<Typography.Text
|
||||
variant="m-400"
|
||||
color="--fgcolor-neutral-primary">
|
||||
{$createPlatform.name} ({$createPlatform.hostname ||
|
||||
$createPlatform.key})
|
||||
</Typography.Text>
|
||||
</Layout.Stack>
|
||||
</Layout.Stack>
|
||||
</Card>
|
||||
</Layout.Stack>
|
||||
</Fieldset>
|
||||
{/if}
|
||||
</Layout.Stack>
|
||||
</Form>
|
||||
{/if}
|
||||
</Layout.Stack>
|
||||
</Form>
|
||||
|
||||
<!-- Step Three -->
|
||||
{#if isPlatformCreated}
|
||||
<Fieldset legend="Clone starter">
|
||||
<Layout.Stack gap="l">
|
||||
<Typography.Text variant="m-500">
|
||||
1. If you're starting a new project, you can clone our starter kit from
|
||||
GitHub using the terminal, VSCode or Android Studio.
|
||||
</Typography.Text>
|
||||
|
||||
<!-- Temporary fix: Remove this div once Code splitting issue with stack spacing is resolved -->
|
||||
<div class="pink2-code-margin-fix">
|
||||
<Code lang="bash" lineNumbers code={gitCloneCode} />
|
||||
</div>
|
||||
|
||||
<Typography.Text variant="m-500"
|
||||
>2. Open the file <InlineCode size="s" code="lib/constants/appwrite.dart" />
|
||||
and update the configuration settings.</Typography.Text>
|
||||
|
||||
<!-- Temporary fix: Remove this div once Code splitting issue with stack spacing is resolved -->
|
||||
<div class="pink2-code-margin-fix">
|
||||
<Code lang="dart" lineNumbers code={updateConfigCode} />
|
||||
</div>
|
||||
|
||||
<Typography.Text variant="m-500"
|
||||
>3. Run the app on a connected device or simulator, then click the <InlineCode
|
||||
size="s"
|
||||
code="Send a ping" /> button to verify the setup.</Typography.Text>
|
||||
</Layout.Stack>
|
||||
</Fieldset>
|
||||
{/if}
|
||||
</Layout.Stack>
|
||||
<svelte:fragment slot="aside">
|
||||
<Card padding="l" class="responsive-padding">
|
||||
<Layout.Stack gap="xxl">
|
||||
|
||||
+113
-109
@@ -25,7 +25,6 @@
|
||||
import ConnectionLine from './components/ConnectionLine.svelte';
|
||||
import OnboardingPlatformCard from './components/OnboardingPlatformCard.svelte';
|
||||
import { PlatformType } from '@appwrite.io/console';
|
||||
import { isCloud } from '$lib/system';
|
||||
|
||||
let showExitModal = false;
|
||||
let isPlatformCreated = false;
|
||||
@@ -36,10 +35,8 @@
|
||||
const gitCloneCode =
|
||||
'\ngit clone https://github.com/appwrite/starter-for-react-native\ncd starter-for-react-native\n';
|
||||
|
||||
const updateConfigCode = isCloud
|
||||
? `const APPWRITE_PROJECT_ID = "${projectId}";`
|
||||
: `const APPWRITE_PROJECT_ID = "${projectId}";
|
||||
const APPWRITE_PUBLIC_ENDPOINT = "${sdk.forProject(page.params.region, page.params.project).client.config.endpoint}";
|
||||
const updateConfigCode = `const EXPO_PUBLIC_APPWRITE_PROJECT_ID = "${projectId}";
|
||||
const EXPO_PUBLIC_APPWRITE_ENDPOINT = "${sdk.forProject(page.params.region, page.params.project).client.config.endpoint}";
|
||||
`;
|
||||
|
||||
export let platform: PlatformType = PlatformType.Reactnativeandroid;
|
||||
@@ -129,116 +126,123 @@ const APPWRITE_PUBLIC_ENDPOINT = "${sdk.forProject(page.params.region, page.para
|
||||
});
|
||||
</script>
|
||||
|
||||
<Wizard title="Add React Native platform" bind:showExitModal confirmExit>
|
||||
<Form onSubmit={createReactNativePlatform}>
|
||||
<Layout.Stack gap="xxl">
|
||||
<!-- Step One -->
|
||||
<Layout.Stack gap="l" direction="row">
|
||||
{#each Object.entries(platforms) as [key, value]}
|
||||
<Pink2Card.Selector
|
||||
{value}
|
||||
id={key}
|
||||
title={key}
|
||||
imageRadius="s"
|
||||
name="framework"
|
||||
bind:group={platform}
|
||||
disabled={isCreatingPlatform || isPlatformCreated} />
|
||||
{/each}
|
||||
</Layout.Stack>
|
||||
|
||||
<!-- Step Two -->
|
||||
{#if !isPlatformCreated}
|
||||
<Fieldset legend="Details">
|
||||
<Layout.Stack gap="l" alignItems="flex-end">
|
||||
<Layout.Stack gap="s">
|
||||
<InputText
|
||||
id="name"
|
||||
label="Name"
|
||||
placeholder={placeholder[platform].name}
|
||||
required
|
||||
bind:value={$createPlatform.name} />
|
||||
|
||||
<!-- Tooltips on InputText don't work as of now -->
|
||||
<InputText
|
||||
id="hostname"
|
||||
label={hostname[platform]}
|
||||
placeholder={placeholder[platform].hostname}
|
||||
required
|
||||
bind:value={$createPlatform.key}>
|
||||
<Tooltip slot="info" maxWidth="15rem">
|
||||
<Icon icon={IconInfo} size="s" />
|
||||
<Typography.Caption variant="400" slot="tooltip">
|
||||
{placeholder[platform].tooltip}
|
||||
</Typography.Caption>
|
||||
</Tooltip>
|
||||
</InputText>
|
||||
</Layout.Stack>
|
||||
|
||||
<Button
|
||||
fullWidthMobile
|
||||
size="s"
|
||||
submit
|
||||
forceShowLoader
|
||||
submissionLoader={isCreatingPlatform}
|
||||
disabled={!platform ||
|
||||
!$createPlatform.name ||
|
||||
!$createPlatform.key ||
|
||||
isCreatingPlatform}>
|
||||
Create platform
|
||||
</Button>
|
||||
</Layout.Stack>
|
||||
</Fieldset>
|
||||
{:else}
|
||||
<Layout.Stack gap="xxl">
|
||||
<Card padding="s" radius="s">
|
||||
<Layout.Stack
|
||||
direction="row"
|
||||
justifyContent="space-between"
|
||||
alignItems="center"
|
||||
gap="xs">
|
||||
<Layout.Stack direction="row" alignItems="center" gap="s">
|
||||
<Icon size="m" icon={IconReact} />
|
||||
<Typography.Text variant="m-400" color="--fgcolor-neutral-primary">
|
||||
{$createPlatform.name} ({$createPlatform.key})
|
||||
</Typography.Text>
|
||||
</Layout.Stack>
|
||||
</Layout.Stack>
|
||||
</Card>
|
||||
<Wizard title="Add React Native platform" bind:showExitModal confirmExit={!isPlatformCreated}>
|
||||
<Layout.Stack gap="xxl">
|
||||
<Form onSubmit={createReactNativePlatform}>
|
||||
<Layout.Stack gap="xxl">
|
||||
<!-- Step One -->
|
||||
<Layout.Stack gap="l" direction="row">
|
||||
{#each Object.entries(platforms) as [key, value]}
|
||||
<Pink2Card.Selector
|
||||
{value}
|
||||
id={key}
|
||||
title={key}
|
||||
imageRadius="s"
|
||||
name="framework"
|
||||
bind:group={platform}
|
||||
disabled={isCreatingPlatform || isPlatformCreated} />
|
||||
{/each}
|
||||
</Layout.Stack>
|
||||
{/if}
|
||||
|
||||
<!-- Step Three -->
|
||||
{#if isPlatformCreated}
|
||||
<Fieldset legend="Clone starter">
|
||||
<Layout.Stack gap="l">
|
||||
<Typography.Text variant="m-500">
|
||||
1. Clone the starter kit from GitHub using the terminal or VSCode.
|
||||
</Typography.Text>
|
||||
<!-- Step Two -->
|
||||
{#if !isPlatformCreated}
|
||||
<Fieldset legend="Details">
|
||||
<Layout.Stack gap="l" alignItems="flex-end">
|
||||
<Layout.Stack gap="s">
|
||||
<InputText
|
||||
id="name"
|
||||
label="Name"
|
||||
placeholder={placeholder[platform].name}
|
||||
required
|
||||
bind:value={$createPlatform.name} />
|
||||
|
||||
<!-- Temporary fix: Remove this div once Code splitting issue with stack spacing is resolved -->
|
||||
<div class="pink2-code-margin-fix">
|
||||
<Code lang="bash" lineNumbers code={gitCloneCode} />
|
||||
</div>
|
||||
<!-- Tooltips on InputText don't work as of now -->
|
||||
<InputText
|
||||
id="hostname"
|
||||
label={hostname[platform]}
|
||||
placeholder={placeholder[platform].hostname}
|
||||
required
|
||||
bind:value={$createPlatform.key}>
|
||||
<Tooltip slot="info" maxWidth="15rem">
|
||||
<Icon icon={IconInfo} size="s" />
|
||||
<Typography.Caption variant="400" slot="tooltip">
|
||||
{placeholder[platform].tooltip}
|
||||
</Typography.Caption>
|
||||
</Tooltip>
|
||||
</InputText>
|
||||
</Layout.Stack>
|
||||
|
||||
<Typography.Text variant="m-500"
|
||||
>2. Open the file <InlineCode
|
||||
<Button
|
||||
fullWidthMobile
|
||||
size="s"
|
||||
code="lib/constants/appwrite.js" /> and update the configuration settings.</Typography.Text>
|
||||
|
||||
<!-- Temporary fix: Remove this div once Code splitting issue with stack spacing is resolved -->
|
||||
<div class="pink2-code-margin-fix">
|
||||
<Code lang="javascript" lineNumbers code={updateConfigCode} />
|
||||
</div>
|
||||
|
||||
<Typography.Text variant="m-500"
|
||||
>3. Run the app on a connected device or simulator, then click the <InlineCode
|
||||
size="s"
|
||||
code="Send a ping" /> button to verify the setup.</Typography.Text>
|
||||
submit
|
||||
forceShowLoader
|
||||
submissionLoader={isCreatingPlatform}
|
||||
disabled={!platform ||
|
||||
!$createPlatform.name ||
|
||||
!$createPlatform.key ||
|
||||
isCreatingPlatform}>
|
||||
Create platform
|
||||
</Button>
|
||||
</Layout.Stack>
|
||||
</Fieldset>
|
||||
{:else}
|
||||
<Layout.Stack gap="xxl">
|
||||
<Card padding="s" radius="s">
|
||||
<Layout.Stack
|
||||
direction="row"
|
||||
justifyContent="space-between"
|
||||
alignItems="center"
|
||||
gap="xs">
|
||||
<Layout.Stack direction="row" alignItems="center" gap="s">
|
||||
<Icon size="m" icon={IconReact} />
|
||||
<Typography.Text
|
||||
variant="m-400"
|
||||
color="--fgcolor-neutral-primary">
|
||||
{$createPlatform.name} ({$createPlatform.key})
|
||||
</Typography.Text>
|
||||
</Layout.Stack>
|
||||
</Layout.Stack>
|
||||
</Card>
|
||||
</Layout.Stack>
|
||||
</Fieldset>
|
||||
{/if}
|
||||
</Layout.Stack>
|
||||
</Form>
|
||||
{/if}
|
||||
</Layout.Stack>
|
||||
</Form>
|
||||
|
||||
<!-- Step Three -->
|
||||
{#if isPlatformCreated}
|
||||
<Fieldset legend="Clone starter">
|
||||
<Layout.Stack gap="l">
|
||||
<Typography.Text variant="m-500">
|
||||
1. If you're starting a new project, you can clone our starter kit from
|
||||
GitHub using the terminal or VSCode.
|
||||
</Typography.Text>
|
||||
|
||||
<!-- Temporary fix: Remove this div once Code splitting issue with stack spacing is resolved -->
|
||||
<div class="pink2-code-margin-fix">
|
||||
<Code lang="bash" lineNumbers code={gitCloneCode} />
|
||||
</div>
|
||||
|
||||
<Typography.Text variant="m-500"
|
||||
>2. Add your Appwrite credentials to <InlineCode
|
||||
size="s"
|
||||
code=".env.example" /> then rename it to <InlineCode
|
||||
size="s"
|
||||
code=".env" /> if needed.</Typography.Text>
|
||||
|
||||
<!-- Temporary fix: Remove this div once Code splitting issue with stack spacing is resolved -->
|
||||
<div class="pink2-code-margin-fix">
|
||||
<Code lang="javascript" lineNumbers code={updateConfigCode} />
|
||||
</div>
|
||||
|
||||
<Typography.Text variant="m-500"
|
||||
>3. Run the app on a connected device or simulator, then click the <InlineCode
|
||||
size="s"
|
||||
code="Send a ping" /> button to verify the setup.</Typography.Text>
|
||||
</Layout.Stack>
|
||||
</Fieldset>
|
||||
{/if}
|
||||
</Layout.Stack>
|
||||
<svelte:fragment slot="aside">
|
||||
<Card padding="l" class="responsive-padding">
|
||||
<Layout.Stack gap="xxl">
|
||||
|
||||
+136
-141
@@ -199,155 +199,150 @@ ${prefix}APPWRITE_ENDPOINT = "${sdk.forProject(page.params.region, page.params.p
|
||||
});
|
||||
</script>
|
||||
|
||||
<Wizard title="Add web platform" bind:showExitModal>
|
||||
<Form onSubmit={createWebPlatform}>
|
||||
<Layout.Stack gap="xxl">
|
||||
<!-- Step One -->
|
||||
{#if !isPlatformCreated || isChangingFramework}
|
||||
<Fieldset legend="Type">
|
||||
<Layout.Stack>
|
||||
<div class="frameworks">
|
||||
{#each frameworks as framework}
|
||||
<Card.Selector
|
||||
bind:group={selectedFrameworkKey}
|
||||
name="framework"
|
||||
id={framework.key}
|
||||
value={framework.key}
|
||||
title={framework.label}
|
||||
icon={framework.icon}
|
||||
imageRadius="s" />
|
||||
{/each}
|
||||
</div>
|
||||
<Wizard title="Add web platform" bind:showExitModal confirmExit={!isPlatformCreated}>
|
||||
<Layout.Stack gap="xxl">
|
||||
<!-- Step One -->
|
||||
{#if !isPlatformCreated || isChangingFramework}
|
||||
<Form onSubmit={createWebPlatform}>
|
||||
<Layout.Stack>
|
||||
<Fieldset legend="Type">
|
||||
<Layout.Stack>
|
||||
<div class="frameworks">
|
||||
{#each frameworks as framework}
|
||||
<Card.Selector
|
||||
bind:group={selectedFrameworkKey}
|
||||
name="framework"
|
||||
id={framework.key}
|
||||
value={framework.key}
|
||||
title={framework.label}
|
||||
icon={framework.icon}
|
||||
imageRadius="s" />
|
||||
{/each}
|
||||
</div>
|
||||
<Layout.Stack direction="row" justifyContent="flex-end">
|
||||
{#if isChangingFramework}
|
||||
<Button
|
||||
disabled={!selectedFramework}
|
||||
on:click={() => (isChangingFramework = false)}>
|
||||
Save</Button>
|
||||
{/if}
|
||||
</Layout.Stack>
|
||||
</Layout.Stack>
|
||||
</Fieldset>
|
||||
{#if !isChangingFramework}
|
||||
<Fieldset legend="Details">
|
||||
<InputText
|
||||
id="hostname"
|
||||
label="Hostname"
|
||||
placeholder="localhost"
|
||||
error={hostnameError && 'Please enter a valid hostname'}
|
||||
bind:value={hostname}>
|
||||
<Tooltip slot="info">
|
||||
<Icon icon={IconInfo} size="s" />
|
||||
<span slot="tooltip">
|
||||
The hostname that your website will use to interact with the
|
||||
Appwrite APIs in production or development environments. No
|
||||
protocol or port number required.
|
||||
</span>
|
||||
</Tooltip>
|
||||
</InputText></Fieldset>
|
||||
<Layout.Stack direction="row" justifyContent="flex-end">
|
||||
{#if isChangingFramework}
|
||||
<Button
|
||||
disabled={!selectedFramework}
|
||||
on:click={() => (isChangingFramework = false)}>
|
||||
Save</Button>
|
||||
{/if}
|
||||
<Button submit disabled={!selectedFramework}>Create platform</Button>
|
||||
</Layout.Stack>
|
||||
{/if}
|
||||
</Layout.Stack>
|
||||
</Form>
|
||||
{:else}
|
||||
<Card.Base padding="s"
|
||||
><Layout.Stack direction="row" justifyContent="space-between" alignItems="center">
|
||||
<Layout.Stack gap="xxs" direction="row">
|
||||
<Icon icon={selectedFramework.smallIcon} />
|
||||
<Typography.Text variant="m-500">{selectedFramework.label}</Typography.Text>
|
||||
</Layout.Stack>
|
||||
</Fieldset>
|
||||
{#if !isChangingFramework}
|
||||
<Fieldset legend="Details">
|
||||
<InputText
|
||||
id="hostname"
|
||||
label="Hostname"
|
||||
placeholder="localhost"
|
||||
error={hostnameError && 'Please enter a valid hostname'}
|
||||
bind:value={hostname}>
|
||||
<Tooltip slot="info">
|
||||
<Icon icon={IconInfo} size="s" />
|
||||
<span slot="tooltip">
|
||||
The hostname that your website will use to interact with the
|
||||
Appwrite APIs in production or development environments. No
|
||||
protocol or port number required.
|
||||
</span>
|
||||
</Tooltip>
|
||||
</InputText></Fieldset>
|
||||
<Layout.Stack direction="row" justifyContent="flex-end"
|
||||
><Button submit disabled={!selectedFramework}>Create platform</Button
|
||||
></Layout.Stack>
|
||||
{/if}
|
||||
{:else}
|
||||
<Card.Base padding="s"
|
||||
><Layout.Stack
|
||||
direction="row"
|
||||
justifyContent="space-between"
|
||||
alignItems="center">
|
||||
<Layout.Stack gap="xxs" direction="row">
|
||||
<Icon icon={selectedFramework.smallIcon} />
|
||||
<Typography.Text variant="m-500"
|
||||
>{selectedFramework.label}</Typography.Text>
|
||||
</Layout.Stack>
|
||||
<Button
|
||||
size="s"
|
||||
secondary
|
||||
on:click={() => {
|
||||
isChangingFramework = true;
|
||||
}}>Change</Button>
|
||||
</Layout.Stack></Card.Base>
|
||||
{/if}
|
||||
<Button
|
||||
size="s"
|
||||
secondary
|
||||
on:click={() => {
|
||||
isChangingFramework = true;
|
||||
}}>Change</Button>
|
||||
</Layout.Stack></Card.Base>
|
||||
{/if}
|
||||
|
||||
<!-- Step Three -->
|
||||
{#if isPlatformCreated && !isChangingFramework}
|
||||
<Fieldset legend="Clone starter">
|
||||
<Layout.Stack gap="l">
|
||||
<Typography.Text variant="m-500">
|
||||
1. Clone the starter kit from GitHub using the terminal or VSCode.
|
||||
</Typography.Text>
|
||||
<!-- Step Three -->
|
||||
{#if isPlatformCreated && !isChangingFramework}
|
||||
<Fieldset legend="Clone starter">
|
||||
<Layout.Stack gap="l">
|
||||
<Typography.Text variant="m-500">
|
||||
1. If you're starting a new project, you can clone our starter kit from
|
||||
GitHub using the terminal or VSCode.
|
||||
</Typography.Text>
|
||||
|
||||
<!-- Temporary fix: Remove this div once Code splitting issue with stack spacing is resolved -->
|
||||
<div class="pink2-code-margin-fix">
|
||||
<Code
|
||||
lang="bash"
|
||||
lineNumbers
|
||||
code={`\ngit clone https://github.com/appwrite/starter-for-${selectedFramework.key}\ncd starter-for-${selectedFramework.key}`} />
|
||||
</div>
|
||||
|
||||
{#if selectedFramework.key === 'angular'}
|
||||
<Typography.Text variant="m-500"
|
||||
>2. Change <InlineCode
|
||||
size="s"
|
||||
code="src/environments/environment.ts" />
|
||||
to reflect the values below:</Typography.Text>
|
||||
{:else}
|
||||
<Typography.Text variant="m-500"
|
||||
>2. Rename <InlineCode size="s" code=".env.example" /> into <InlineCode
|
||||
size="s"
|
||||
code=".env" /> and update the values.</Typography.Text>
|
||||
{/if}
|
||||
|
||||
<!-- Temporary fix: Remove this div once Code splitting issue with stack spacing is resolved -->
|
||||
<div class="pink2-code-margin-fix">
|
||||
<Code
|
||||
lang="bash"
|
||||
lineNumbers
|
||||
code={selectedFramework.updateConfigCode} />
|
||||
</div>
|
||||
<!-- Temporary fix: Remove this div once Code splitting issue with stack spacing is resolved -->
|
||||
<div class="pink2-code-margin-fix">
|
||||
<Code
|
||||
lang="bash"
|
||||
lineNumbers
|
||||
code={`\ngit clone https://github.com/appwrite/starter-for-${selectedFramework.key}\ncd starter-for-${selectedFramework.key}`} />
|
||||
</div>
|
||||
|
||||
{#if selectedFramework.key === 'angular'}
|
||||
<Typography.Text variant="m-500"
|
||||
>3. Install project dependencies</Typography.Text>
|
||||
|
||||
<!-- Temporary fix: Remove this div once Code splitting issue with stack spacing is resolved -->
|
||||
<div class="pink2-code-margin-fix">
|
||||
<Code lang="bash" lineNumbers code={'npm install'} />
|
||||
</div>
|
||||
|
||||
<Typography.Text variant="m-500"
|
||||
>4. Run the app, then click the <InlineCode
|
||||
>2. Change <InlineCode
|
||||
size="s"
|
||||
code="Send a ping" /> button to verify the setup.</Typography.Text>
|
||||
<!-- Temporary fix: Remove this div once Code splitting issue with stack spacing is resolved -->
|
||||
<div class="pink2-code-margin-fix">
|
||||
<Code lang="bash" lineNumbers code={selectedFramework.runCommand} />
|
||||
</div>
|
||||
</Layout.Stack>
|
||||
</Fieldset>
|
||||
<Card.Base padding="s"
|
||||
><Layout.Stack direction="row" justifyContent="space-between"
|
||||
><Layout.Stack direction="row" alignItems="center">
|
||||
<Icon
|
||||
icon={IconInfo}
|
||||
color="--fgcolor-neutral-tertiary" /><Typography.Text
|
||||
variant="m-500"
|
||||
color="--fgcolor-neutral-primary">
|
||||
Demo app runs on http://localhost:{selectedFramework.portNumber}</Typography.Text
|
||||
></Layout.Stack>
|
||||
<Button
|
||||
external
|
||||
secondary
|
||||
href={`http://localhost:${selectedFramework.portNumber}`}
|
||||
><Layout.Stack direction="row" gap="xs"
|
||||
>Open <Icon
|
||||
icon={IconExternalLink}
|
||||
color="--fgcolor-neutral-tertiary" /></Layout.Stack
|
||||
></Button
|
||||
></Layout.Stack
|
||||
></Card.Base>
|
||||
{/if}
|
||||
</Layout.Stack>
|
||||
</Form>
|
||||
code="src/environments/environment.ts" />
|
||||
to reflect the values below:</Typography.Text>
|
||||
{:else}
|
||||
<Typography.Text variant="m-500"
|
||||
>2. Add your Appwrite credentials to <InlineCode
|
||||
size="s"
|
||||
code=".env.example" /> then rename it to <InlineCode
|
||||
size="s"
|
||||
code=".env" /> if needed.</Typography.Text>
|
||||
{/if}
|
||||
|
||||
<!-- Temporary fix: Remove this div once Code splitting issue with stack spacing is resolved -->
|
||||
<div class="pink2-code-margin-fix">
|
||||
<Code lang="bash" lineNumbers code={selectedFramework.updateConfigCode} />
|
||||
</div>
|
||||
|
||||
<Typography.Text variant="m-500"
|
||||
>3. Install project dependencies</Typography.Text>
|
||||
|
||||
<!-- Temporary fix: Remove this div once Code splitting issue with stack spacing is resolved -->
|
||||
<div class="pink2-code-margin-fix">
|
||||
<Code lang="bash" lineNumbers code={'npm install'} />
|
||||
</div>
|
||||
|
||||
<Typography.Text variant="m-500"
|
||||
>4. Run the app, then click the <InlineCode size="s" code="Send a ping" /> button
|
||||
to verify the setup.</Typography.Text>
|
||||
<!-- Temporary fix: Remove this div once Code splitting issue with stack spacing is resolved -->
|
||||
<div class="pink2-code-margin-fix">
|
||||
<Code lang="bash" lineNumbers code={selectedFramework.runCommand} />
|
||||
</div>
|
||||
</Layout.Stack>
|
||||
</Fieldset>
|
||||
<Card.Base padding="s"
|
||||
><Layout.Stack direction="row" justifyContent="space-between"
|
||||
><Layout.Stack direction="row" alignItems="center">
|
||||
<Icon icon={IconInfo} color="--fgcolor-neutral-tertiary" /><Typography.Text
|
||||
variant="m-500"
|
||||
color="--fgcolor-neutral-primary">
|
||||
Demo app runs on http://localhost:{selectedFramework.portNumber}</Typography.Text
|
||||
></Layout.Stack>
|
||||
<Button
|
||||
external
|
||||
secondary
|
||||
href={`http://localhost:${selectedFramework.portNumber}`}
|
||||
><Layout.Stack direction="row" gap="xs"
|
||||
>Open <Icon
|
||||
icon={IconExternalLink}
|
||||
color="--fgcolor-neutral-tertiary" /></Layout.Stack
|
||||
></Button
|
||||
></Layout.Stack
|
||||
></Card.Base>
|
||||
{/if}
|
||||
</Layout.Stack>
|
||||
<svelte:fragment slot="aside">
|
||||
<Card.Base>
|
||||
<Layout.Stack gap="xxl">
|
||||
|
||||
+8
-2
@@ -34,8 +34,14 @@
|
||||
$: dbWrites = data.usage.databasesWrites;
|
||||
|
||||
$: legendData = [
|
||||
{ name: 'Reads', value: data.usage.databasesReadsTotal },
|
||||
{ name: 'Writes', value: data.usage.databasesWritesTotal }
|
||||
{
|
||||
name: 'Reads',
|
||||
value: data.usage.databasesReads.reduce((sum, item) => sum + item.value, 0)
|
||||
},
|
||||
{
|
||||
name: 'Writes',
|
||||
value: data.usage.databasesWrites.reduce((sum, item) => sum + item.value, 0)
|
||||
}
|
||||
];
|
||||
|
||||
const tier = data?.currentInvoice?.plan ?? $organization?.billingPlan;
|
||||
|
||||
+1
-1
@@ -62,7 +62,7 @@
|
||||
.forProject(page.params.region, page.params.project)
|
||||
.vcs.createRepositoryDetection(
|
||||
$installation.$id,
|
||||
data.repository.id,
|
||||
page.params.repository,
|
||||
VCSDetectionType.Framework,
|
||||
rootDir
|
||||
);
|
||||
|
||||
+9
-8
@@ -5,11 +5,14 @@ import { buildVerboseDomain } from '../../store';
|
||||
export const load = async ({ parent, params, url }) => {
|
||||
const { installations, frameworks, project, organization, regionalConsoleVariables } =
|
||||
await parent();
|
||||
const [repository] = await Promise.all([
|
||||
sdk
|
||||
.forProject(params.region, params.project)
|
||||
.vcs.getRepository(url.searchParams.get('installation'), params.repository)
|
||||
]);
|
||||
|
||||
const repository = await sdk
|
||||
.forProject(params.region, params.project)
|
||||
.vcs.getRepository(url.searchParams.get('installation'), params.repository);
|
||||
|
||||
const installation = installations.installations.find(
|
||||
(installation) => installation.$id === url.searchParams.get('installation')
|
||||
);
|
||||
|
||||
const domain = await buildVerboseDomain(
|
||||
regionalConsoleVariables._APP_DOMAIN_SITES,
|
||||
@@ -21,9 +24,7 @@ export const load = async ({ parent, params, url }) => {
|
||||
|
||||
return {
|
||||
installations,
|
||||
installation: installations.installations.find(
|
||||
(installation) => installation.$id === url.searchParams.get('installation')
|
||||
),
|
||||
installation,
|
||||
repository,
|
||||
frameworks,
|
||||
domain
|
||||
|
||||
Reference in New Issue
Block a user