Merge branch '1.4.x' into fix-sentence-case

This commit is contained in:
Torsten Dittmann
2023-08-28 11:37:53 +02:00
committed by GitHub
251 changed files with 9636 additions and 2581 deletions
+7325 -1322
View File
File diff suppressed because it is too large Load Diff
+5 -3
View File
@@ -20,15 +20,16 @@
"dependencies": {
"@analytics/google-analytics": "^1.0.5",
"@analytics/google-tag-manager": "^0.5.3",
"@appwrite.io/console": "npm:matej-appwrite-console@7.1.126",
"@appwrite.io/pink": "0.1.0-next.3",
"@appwrite.io/pink-icons": "^0.1.0-next.3",
"@appwrite.io/console": "npm:matej-appwrite-console@7.1.128",
"@appwrite.io/pink": "0.1.0-next.7",
"@appwrite.io/pink-icons": "^0.1.0-next.7",
"@popperjs/core": "^2.11.6",
"@sentry/svelte": "^7.44.2",
"@sentry/tracing": "^7.44.2",
"ai": "^2.1.15",
"analytics": "^0.8.1",
"dayjs": "^1.11.9",
"deep-equal": "^2.2.2",
"dotenv": "^16.0.3",
"echarts": "^5.4.1",
"logrocket": "^3.0.1",
@@ -48,6 +49,7 @@
"@testing-library/jest-dom": "^5.16.5",
"@testing-library/svelte": "^3.2.2",
"@testing-library/user-event": "^14.4.3",
"@types/deep-equal": "^1.0.1",
"@types/prismjs": "^1.26.0",
"@typescript-eslint/eslint-plugin": "^5.56.0",
"@typescript-eslint/parser": "^5.56.0",
+20
View File
@@ -0,0 +1,20 @@
import type { Action } from 'svelte/action';
export type MultiActionArray = Array<(node: HTMLElement) => ReturnType<Action>>;
export function multiAction(node: HTMLElement, arr: MultiActionArray) {
const destroyFns = arr.map((fn) => {
const actionReturn = fn(node);
return actionReturn
? actionReturn.destroy
: () => {
/* noop */
};
});
return {
destroy() {
destroyFns.forEach((fn) => fn());
}
};
}
+18 -2
View File
@@ -33,6 +33,9 @@
import { commandCenterKeyDownHandler, disableCommands, registerCommands } from './commands';
import { RootPanel } from './panels';
import { addSubPanel, clearSubPanels, subPanels } from './subPanels';
import { addNotification } from '$lib/stores/notifications';
let debugOverlayEnabled = false;
$: $registerCommands([
{
@@ -40,6 +43,20 @@
keys: ['k'],
ctrl: true,
forceEnable: true
},
{
label: 'Toggle debug overlay',
callback: () => {
debugOverlayEnabled = !debugOverlayEnabled;
addNotification({
title: 'Debug overlay',
message: debugOverlayEnabled ? 'Enabled' : 'Disabled',
type: 'info'
});
},
keys: ['d', 'o'],
group: 'misc',
disabled: !dev
}
]);
@@ -100,7 +117,7 @@
</div>
{/if}
{#if dev}
{#if dev && debugOverlayEnabled}
<div class="debug-keys" use:portal>
{#each keys as key, i (i)}
<kbd class="kbd" transition:fade|local={{ duration: 150 }}>
@@ -126,7 +143,6 @@
left: 50%;
transform: translateX(-50%);
padding: 0.5rem;
// background-color: hsl(var(--color-neutral-500) / 0.5);
z-index: 9999;
display: flex;
+16 -11
View File
@@ -31,7 +31,8 @@ const groups = [
'security',
'buckets',
'files',
'misc'
'misc',
'settings'
] as const;
export type CommandGroup = (typeof groups)[number];
@@ -57,8 +58,8 @@ type KeyedCommand = BaseCommand & {
alt?: boolean;
};
function isKeyedCommand(command: Command): command is KeyedCommand {
return 'keys' in command;
export function isKeyedCommand(command: Command): command is KeyedCommand {
return 'keys' in command && Array.isArray((command as KeyedCommand).keys);
}
export type Command = KeyedCommand | BaseCommand;
@@ -83,7 +84,7 @@ function isInputEvent(event: KeyboardEvent) {
function getCommandRank(command: KeyedCommand) {
const { keys, ctrl: meta, shift, alt } = command;
const modifiers = [meta, shift, alt].filter(Boolean).length;
return keys.length + modifiers * 10;
return (keys?.length || 0) + modifiers * 10;
}
function hasDisputing(command: KeyedCommand, allCommands: Command[]) {
@@ -95,7 +96,7 @@ function hasDisputing(command: KeyedCommand, allCommands: Command[]) {
return false;
}
const keysString = command.keys.join('+');
const otherKeysString = otherCommand.keys.join('+');
const otherKeysString = otherCommand?.keys?.join('+');
const cmdRank = getCommandRank(command);
const otherCmdRank = getCommandRank(otherCommand);
@@ -195,8 +196,10 @@ export const commandCenterKeyDownHandler = derived(
const isShiftPressed = shift ? event.shiftKey : !event.shiftKey;
const isAltPressed = alt ? event.altKey : !event.altKey;
const commandKeyCodes = keys.map((key) => key.toUpperCase().charCodeAt(0));
const allKeysPressed = recentKeyCodes.join('').includes(commandKeyCodes.join(''));
const commandKeyCodes = keys?.map((key) => key.toUpperCase().charCodeAt(0));
const allKeysPressed = commandKeyCodes
? recentKeyCodes.join('').includes(commandKeyCodes.join(''))
: false;
if (allKeysPressed && isMetaPressed && isShiftPressed && isAltPressed) {
event.preventDefault();
@@ -288,10 +291,12 @@ export const commandGroupRanks = derived(groupRanksMap, ($groupRankTransformatio
const initialRanks = {
...Object.fromEntries(groups.map((group) => [group, 0])),
ungrouped: 9999,
databases: 3,
users: 2,
teams: 1,
navigation: -10,
databases: 50,
users: 40,
teams: 30,
projects: 20,
organizations: 10,
navigation: 0,
help: -20,
misc: -30
} as CommandGroupRanks;
+71 -36
View File
@@ -1,13 +1,12 @@
<script lang="ts">
import Template from './template.svelte';
import { AvatarInitials, Code } from '$lib/components';
import { AvatarInitials, Code, LoadingDots, SvgIcon } from '$lib/components';
import { user } from '$lib/stores/user';
import { useCompletion } from 'ai/svelte';
import { subPanels } from '../subPanels';
import { isLanguage, type Language } from '$lib/components/code.svelte';
import CoolerAppwrite from '$lib/images/appwrite-cooler.svg';
import { VARS } from '$lib/system';
const endpoint = VARS.APPWRITE_ENDPOINT ?? `${globalThis?.location?.origin}/v1`;
@@ -62,7 +61,9 @@
answer.push({
type: 'code',
value: nextCodeMatch[2],
value: nextCodeMatch[2].startsWith('\n')
? nextCodeMatch[2].slice(1)
: nextCodeMatch[2],
language: isLanguage(language) ? language : 'js'
});
@@ -82,6 +83,11 @@
}
$: answer = parseCompletion($completion);
function getInitials(name: string) {
const [first, last] = name.split(' ');
return `${first?.[0] ?? ''}${last?.[0] ?? ''}`;
}
</script>
<Template
@@ -98,9 +104,14 @@
};
})}
clearOnCallback={false}
fullheight
--command-panel-max-height="40rem">
<div slot="search" />
on:keydown={(e) => {
e.detail.cancel();
}}
--min-height="40rem"
--max-height="52.5rem">
<div slot="search">
<span class="experimental border-gradient">EXPERIMENTAL</span>
</div>
<div slot="option" let:option class="u-flex u-cross-center u-gap-8">
<i class="icon-question-mark-circle" />
@@ -109,20 +120,28 @@
{#if $isLoading || answer}
<div class="content">
<div class="u-flex u-gap-8">
<div class="u-flex u-gap-8 u-cross-center">
<div class="avatar is-size-x-small">{getInitials($user.name)}</div>
<p class="u-opacity-75">{$input}</p>
</div>
<div class="u-flex u-gap-8 u-margin-block-start-24">
<div class="logo">
<img src={CoolerAppwrite} alt="Appwrite logo" />
<SvgIcon name="sparkles" type="color" />
</div>
<div class="answer">
{#if $isLoading && !$completion}
<p>...</p>
<LoadingDots />
{:else}
{#each answer as part}
{#if part.type === 'text'}
<p>{part.value}</p>
<p>{part.value.trimStart()}</p>
{:else if part.type === 'code'}
{#key part.value}
<Code language={part.language} code={part.value} />
<div
class="u-margin-block-start-8"
style="margin-block-end: 1rem;">
<Code language={part.language} code={part.value} noMargin />
</div>
{/key}
{/if}
{/each}
@@ -174,8 +193,16 @@
</Template>
<style lang="scss">
:global(.theme-dark) .content {
--logo-bg: #282a3b;
}
:global(.theme-light) .content {
--logo-bg: #f2f2f8;
}
.content {
overflow-y: auto;
overflow: auto;
padding: 1rem;
.logo {
@@ -187,7 +214,7 @@
flex-shrink: 0;
border-radius: 0.25rem;
background: #282a3b;
background: var(--logo-bg);
}
.answer {
@@ -197,29 +224,6 @@
white-space: pre-wrap;
}
}
h2 {
color: hsl(var(--color-neutral-70));
}
.examples {
display: flex;
flex-direction: column;
li {
padding: 0.59375rem 0.5rem;
button {
&:hover {
opacity: 0.75;
}
i {
color: hsl(var(--color-neutral-70));
}
}
}
}
}
.footer {
@@ -229,4 +233,35 @@
background-color: hsl(var(--color-neutral-150));
}
}
.experimental {
display: flex;
padding: 0.09375rem 0.25rem;
align-items: center;
color: var(--light-neutrals-30, #e8e9f0);
text-align: center;
font-family: Inter;
font-size: 0.625rem;
font-style: normal;
font-weight: 500;
line-height: 150%; /* 0.9375rem */
letter-spacing: 0.075rem;
text-transform: uppercase;
background: rgba(240, 46, 101, 0.24);
--border-gradient: linear-gradient(
to bottom,
rgba(240, 46, 101, 0.48) 0%,
rgba(240, 46, 101, 0) 150%
)
border-box;
--border-size: 0.03rem;
--border-radius: 0.25rem;
border-radius: var(--border-radius);
}
:global(.theme-light) .experimental {
color: rgba(240, 46, 101, 1);
}
</style>
+2 -2
View File
@@ -4,7 +4,7 @@
<script lang="ts">
import { debounce } from '$lib/helpers/debounce';
import { isMac } from '$lib/helpers/platform';
import { commands, searchers, type Command } from '../commands';
import { commands, searchers, type Command, isKeyedCommand } from '../commands';
import Template from './template.svelte';
let search = '';
@@ -69,7 +69,7 @@
{#if hasAlt(command)}
<kbd class="kbd"> {isMac() ? '⌥' : 'Alt'} </kbd>
{/if}
{#if 'keys' in command}
{#if isKeyedCommand(command)}
{#each command.keys as key, i}
{@const hasNext = command.keys.length - 1 !== i}
+85 -20
View File
@@ -4,13 +4,13 @@
// This is the template for all panels used in the command center.
// Use this component when you want to create a new panel.
import { tick } from 'svelte';
import { createEventDispatcher, tick } from 'svelte';
import { getCommandCenterCtx } from '../commandCenter.svelte';
import { clearSubPanels, popSubPanel, subPanels } from '../subPanels';
type Option = $$Generic<Command>;
type Option = $$Generic<Omit<Command, 'group'> & { group?: string }>;
export let options: Option[] | null = null;
export let search = '';
export let searchPlaceholder = 'Search...';
@@ -18,6 +18,7 @@
export let clearOnCallback = true;
let selected = 0;
let usingKeyboard = false;
let contentEl: HTMLElement;
async function triggerOption(option: Option) {
@@ -28,8 +29,25 @@
}
}
const dispatch = createEventDispatcher<{
keydown: {
originalEvent: KeyboardEvent;
cancel: () => void;
key: string;
};
}>();
function handleKeyDown(event: KeyboardEvent) {
if (!open) return;
usingKeyboard = true;
let canceled = false;
dispatch('keydown', {
originalEvent: event,
cancel: () => (canceled = true),
key: event.key
});
if (canceled) return;
if (options) {
if (event.key === 'ArrowDown') {
@@ -90,8 +108,9 @@
}
}
$: {
options;
$: if (selected > options?.length - 1) {
selected = options?.length - 1;
} else if (usingKeyboard && selected < 0 && options?.length) {
selected = 0;
}
@@ -173,13 +192,30 @@
triggerOption(option);
};
const getOptionFocusHandler = (option: IndexedOption) => () => {
selected = option.index;
const getOptionFocusHandler =
(option: IndexedOption, hover = false) =>
() => {
selected = option.index;
usingKeyboard = hover ? false : usingKeyboard;
};
const getOptionBlurHandler = () => () => {
selected = -1;
usingKeyboard = false;
};
const castOption = (option: IndexedOption) => option as Option;
$: breadcrumbs = $subPanels.filter((panel) => panel.name !== 'root').map((panel) => panel.name);
function getBreadcrumbs(subPanels: typeof $subPanels) {
return subPanels.filter((panel) => panel.name !== 'root').map((panel) => panel.name);
}
let breadcrumbs = getBreadcrumbs($subPanels);
// Avoid clearing subpanels before the closing transition is finished
$: if ($subPanels.length) {
breadcrumbs = getBreadcrumbs($subPanels);
}
const handleCrumbClick = (index: number) => {
if (index === breadcrumbs.length - 1) {
@@ -262,7 +298,8 @@
<button
class="option"
on:click={getOptionClickHandler(item)}
on:mouseover={getOptionFocusHandler(item)}
on:mouseover={getOptionFocusHandler(item, true)}
on:mouseleave={getOptionBlurHandler()}
on:focus={getOptionFocusHandler(item)}>
<slot name="option" option={castOption(item)}>
<div class="u-flex u-gap-8 u-cross-center">
@@ -350,6 +387,9 @@
--result-bg: hsl(var(--color-neutral-10));
--footer-bg: linear-gradient(180deg, #fff 0%, #e8e9f0 100%);
--icon-color: hsl(var(--color-neutral-50));
--label-color: hsl(var(--color-neutral-100));
}
:global(.theme-dark) .card {
@@ -364,23 +404,28 @@
--result-bg: hsl(var(--color-neutral-200));
--footer-bg: linear-gradient(180deg, #1b1b28 0%, #282a3b 100%);
--icon-color: hsl(var(--color-neutral-70));
--label-color: hsl(var(--color-neutral-30));
}
// Elements
.card {
display: flex;
flex-direction: column;
width: var(--command-panel-width, 42.5rem);
max-width: 100%;
max-height: var(--command-panel-max-height, 32rem);
overflow: hidden;
padding: 0;
position: absolute;
top: clamp(128px, 15vh, 400px);
--top: clamp(64px, 10vh, 400px);
top: var(--top);
left: 50%;
translate: -50%;
display: flex;
flex-direction: column;
width: var(--width, 42.5rem);
max-width: 100%;
min-height: var(--min-height);
max-height: min(calc(100vh - var(--top) - 4rem), var(--max-height, 32rem));
overflow: hidden;
padding: 0;
border-radius: 0.5rem;
border: 1px solid var(--cmd-center-border);
background: var(--cmd-center-bg);
@@ -388,7 +433,7 @@
backdrop-filter: blur(6px);
&.fullheight {
height: var(--command-panel-max-height, 32rem);
height: var(--max-height, 32rem);
}
:global(.kbd) {
@@ -458,6 +503,8 @@
position: relative;
z-index: 10;
font-size: 10px !important;
&:not(:first-child) {
margin-block-start: 1rem;
}
@@ -470,17 +517,35 @@
position: absolute;
inset: 0;
background-color: var(--result-bg);
border-radius: 0.75rem;
border-radius: 0.5rem;
translate: 0 -1px;
}
.option {
padding: 0.5rem 0.75rem;
padding: 0.5rem 9.5px;
font-size: 14px;
position: relative;
z-index: 10;
width: 100%;
box-shadow: none !important;
color: var(--label-color);
:global(i[class^='icon-']) {
font-size: 1rem !important;
width: 1rem !important;
height: 1rem !important;
color: var(--icon-color);
position: relative;
}
:global(i[class^='icon-']::before) {
position: absolute;
top: 50%;
left: 50%;
translate: -50% -50%;
}
}
&.nested {
+2 -1
View File
@@ -4,8 +4,9 @@
export let name: string;
export let size: number;
export let background: string | undefined = undefined;
$: src = sdk.forConsole.avatars.getInitials(name, size * 2, size * 2).toString();
$: src = sdk.forConsole.avatars.getInitials(name, size * 2, size * 2, background).toString();
</script>
<Avatar {name} {size} {src} />
+6 -2
View File
@@ -1,4 +1,8 @@
<div class="box">
<script lang="ts">
import { Box } from '.';
</script>
<Box>
<div class="u-flex u-gap-16">
<slot name="image" />
<div class="u-cross-child-center u-line-height-1-5">
@@ -6,4 +10,4 @@
<slot />
</div>
</div>
</div>
</Box>
+3 -2
View File
@@ -2,10 +2,11 @@
import { Card } from './';
export let danger = false;
export let hideOverflow = false;
</script>
<Card {danger}>
<div class="common-section grid-1-2">
<div class="common-section grid-1-2" class:hideOverflow>
<div class="grid-1-2-col-1 u-flex u-flex-vertical u-gap-16">
<slot />
</div>
@@ -21,7 +22,7 @@
</Card>
<style lang="scss">
.grid-1-2 > * {
.hideOverflow > * {
width: 100%;
overflow: hidden;
}
+10 -2
View File
@@ -29,6 +29,7 @@
export let withLineNumbers = false;
export let withCopy = false;
export let noMargin = false;
export let allowScroll = false;
Prism.plugins.customClass.prefix('prism-');
@@ -56,8 +57,10 @@
</Copy>
{/if}
</div>
<pre class={`language-${language}`} class:line-numbers={withLineNumbers}><code
>{code}</code></pre>
<pre
class:with-scroll={allowScroll}
class={`language-${language}`}
class:line-numbers={withLineNumbers}><code>{code}</code></pre>
</section>
<style lang="scss" global>
@@ -76,6 +79,11 @@
}
}
.with-scroll {
height: 100%;
overflow: auto;
}
code,
pre {
&[class*='language-'] {
+8 -5
View File
@@ -4,11 +4,12 @@
import Dark from '$lib/images/search-dark.svg';
import PaginationInline from './paginationInline.svelte';
export let hidePagination = false;
export let hidePages = false;
</script>
<article class="card u-grid u-cross-center u-width-full-line common-section">
<div class="u-flex u-flex-vertical u-cross-center u-gap-24">
<div class="u-flex u-flex-vertical u-cross-center u-gap-24 u-overflow-hidden">
{#if $app.themeInUse === 'dark'}
<img src={Dark} alt="create" aria-hidden="true" />
{:else}
@@ -18,7 +19,9 @@
</div>
</article>
<div class="u-flex u-margin-block-start-32 u-main-space-between">
<p class="text">Total results: 0</p>
<PaginationInline limit={1} offset={0} sum={0} {hidePages} />
</div>
{#if !hidePagination}
<div class="u-flex u-margin-block-start-32 u-main-space-between">
<p class="text">Total results: 0</p>
<PaginationInline limit={1} offset={0} sum={0} {hidePages} />
</div>
{/if}
+1 -2
View File
@@ -245,8 +245,7 @@
}
</script>
<Modal bind:show onSubmit={create} size="big">
<svelte:fragment slot="header">Create event</svelte:fragment>
<Modal title="Create event" bind:show onSubmit={create} size="big">
<slot />
<div>
<p class="u-text">Choose a service</p>
@@ -21,6 +21,8 @@
border-radius: 0.5rem;
padding: 0.75rem 1rem;
width: clamp(0px, calc(100vw - 4rem), 32.5rem);
}
:global(.theme-dark) .floating-action-bar {
+1
View File
@@ -59,3 +59,4 @@ export { default as EyebrowHeading } from './eyebrowHeading.svelte';
export { default as SvgIcon } from './svgIcon.svelte';
export { default as MigrationBox } from './migrationBox.svelte';
export { default as FloatingActionBar } from './floatingActionBar.svelte';
export { default as LoadingDots } from './loadingDots.svelte';
+6 -2
View File
@@ -7,6 +7,8 @@
export let icon: string = null;
export let fullHeight = true;
export let borderRadius: 'xsmall' | 'small' | 'medium' | 'large' = 'small';
export let backgroundColor: string = null;
export let backgroundColorHover: string = null;
enum Radius {
xsmall = '--border-radius-xsmall',
@@ -20,8 +22,10 @@
class="card is-allow-focus u-cursor-pointer"
class:u-height-100-percent={fullHeight}
style:--card-padding={`${padding}rem`}
style:--card-border-radius={`var(${Radius[borderRadius]})`}>
<div class="u-flex u-gap-16">
style:--card-border-radius={`var(${Radius[borderRadius]})`}
style:--p-card-bg-color-default={backgroundColor}
style:--p-card-bg-color-hover={backgroundColorHover}>
<div class="u-flex u-gap-8">
<input
class="is-small u-margin-block-start-2"
type="radio"
+56
View File
@@ -0,0 +1,56 @@
<!-- Loading dots -->
<script>
let className = '';
export { className as class };
</script>
<div class="loading-dots {className}">
<div class="dot" />
<div class="dot" />
<div class="dot" />
</div>
<style>
.loading-dots {
display: flex;
align-items: center;
justify-content: center;
width: 100%;
height: 100%;
gap: 0.25rem;
padding-inline: 0.5rem;
}
.dot {
width: 0.25rem;
height: 0.25rem;
border-radius: 50%;
background-color: hsl(var(--p-body-text-color));
animation: loading-dots 1s infinite ease-in-out;
}
.dot:nth-child(1) {
animation-delay: 0.1s;
}
.dot:nth-child(2) {
animation-delay: 0.2s;
}
.dot:nth-child(3) {
animation-delay: 0.3s;
}
@keyframes loading-dots {
0% {
transform: translateY(0.25rem);
}
25% {
transform: translateY(0rem);
}
50%,
100% {
transform: translateY(0.25rem);
}
}
</style>
+11 -1
View File
@@ -15,6 +15,8 @@
export let onSubmit: (e: SubmitEvent) => Promise<void> | void = function () {
return;
};
export let title = '';
export let description = '';
let dialog: HTMLDialogElement;
let alert: HTMLElement;
@@ -102,8 +104,11 @@
<span class={`icon-${icon}`} aria-hidden="true" />
</div>
{/if}
<h4 class="modal-title heading-level-5">
<slot name="header" />
<slot name="title">
{title}
</slot>
</h4>
</div>
{#if closable}
@@ -122,6 +127,11 @@
</button>
{/if}
</div>
<p>
<slot name="description">
{description}
</slot>
</p>
</header>
<div class="modal-content">
{#if error}
+1 -2
View File
@@ -25,8 +25,7 @@
$: disabled = !value || $groups.has(value);
</script>
<Modal bind:show on:close={reset} onSubmit={create}>
<svelte:fragment slot="header">Custom permission</svelte:fragment>
<Modal title="Custom permission" bind:show on:close={reset} onSubmit={create}>
<p class="text">
Custom permissions allow you to grant access to specific users or teams using their ID and
role.
+1 -2
View File
@@ -61,8 +61,7 @@
}
</script>
<Modal bind:show onSubmit={create} on:close={reset} size="big">
<svelte:fragment slot="header">Select teams</svelte:fragment>
<Modal title="Select teams" bind:show onSubmit={create} on:close={reset} size="big">
<p class="text">
Grant access to any member of a specific team. To grant access to team members with specific
roles, you will need to set a <button
+1 -2
View File
@@ -61,8 +61,7 @@
}
</script>
<Modal bind:show onSubmit={create} on:close={reset} size="big">
<svelte:fragment slot="header">Select users</svelte:fragment>
<Modal title="Select users" bind:show onSubmit={create} on:close={reset} size="big">
<p class="text">Grant access to any authenticated or anonymous user.</p>
<InputSearch
autofocus
+3 -1
View File
@@ -13,7 +13,8 @@
class="secondary-tabs-button"
class:u-width-full-line={fullWidth}
class:u-text-center={center}
disabled>
disabled
type="button">
<span class="text"><slot /></span>
</button>
{:else}
@@ -30,6 +31,7 @@
class="secondary-tabs-button"
class:u-width-full-line={fullWidth}
class:u-text-center={center}
type="button"
{disabled}
on:click>
<span class="text"><slot /></span>
+6 -2
View File
@@ -3,6 +3,7 @@
import { getContext, hasContext } from 'svelte';
import { readable } from 'svelte/store';
import type { FormContext } from './form.svelte';
import { multiAction, type MultiActionArray } from '$lib/actions/multi-actions';
export let submit = false;
export let secondary = false;
@@ -19,6 +20,7 @@
export let event: string = null;
let classes: string = undefined;
export { classes as class };
export let actions: MultiActionArray = [];
const isSubmitting = hasContext('form')
? getContext<FormContext>('form').isSubmitting
@@ -59,7 +61,8 @@
target={external ? '_blank' : ''}
rel={external ? 'noopener noreferrer' : ''}
class={resolvedClasses}
aria-label={ariaLabel}>
aria-label={ariaLabel}
use:multiAction={actions}>
<slot />
</a>
{:else}
@@ -69,7 +72,8 @@
disabled={internalDisabled}
class={resolvedClasses}
aria-label={ariaLabel}
type={submit === false ? 'button' : undefined}>
type={submit ? 'submit' : 'button'}
use:multiAction={actions}>
<slot />
</button>
{/if}
+1 -1
View File
@@ -11,7 +11,7 @@
export let required = false;
export let disabled = false;
let element: HTMLInputElement;
export let element: HTMLInputElement | undefined = undefined;
let error: string;
const handleInvalid = (event: Event) => {
+23 -19
View File
@@ -40,25 +40,29 @@
on:invalid={handleInvalid} />
<div class="choice-item-content">
<div class="u-flex u-cross-center u-gap-4">
<h6 class:u-hide={!showLabel} class="choice-item-title">
{label}
</h6>
{#if tooltip}
<button class="tooltip" aria-label="variables info">
<span
class="icon-info"
aria-hidden="true"
style="font-size: var(--icon-size-small)" />
<span class="tooltip-popup" role="tooltip">
<p class="text">
{tooltip}
</p>
</span>
</button>
{/if}
</div>
{#if $$slots}
{#if (label && showLabel) || tooltip}
<div class="u-flex u-gap-4">
{#if label}
<h6 class:u-hide={!showLabel} class="choice-item-title">
{label}
</h6>
{/if}
{#if tooltip}
<button type="button" class="tooltip" aria-label="variables info">
<span
class="icon-info"
aria-hidden="true"
style="font-size: var(--icon-size-small)" />
<span class="tooltip-popup" role="tooltip">
<p class="text">
{tooltip}
</p>
</span>
</button>
{/if}
</div>
{/if}
{#if $$slots.default}
<p class="choice-item-paragraph"><slot /></p>
{/if}
</div>
+1 -1
View File
@@ -28,7 +28,7 @@
{/if}
{#if tooltip}
<button class="tooltip" aria-label="input tooltip">
<button type="button" on:click|preventDefault class="tooltip" aria-label="input tooltip">
<span class="icon-info" aria-hidden="true" style="font-size: var(--icon-size-small)" />
<span class="tooltip-popup" role="tooltip">
<p class="text">
+3 -1
View File
@@ -3,11 +3,13 @@
export let onlyDesktop = false;
export let width: number = null;
export let showOverflow = false;
let className = '';
export { className as class };
</script>
<div
style={width ? `--p-col-width:${width?.toString()}` : ''}
class="table-col"
class="table-col {className}"
class:u-overflow-visible={showOverflow}
class:is-only-desktop={onlyDesktop}
data-title={title}
+26 -15
View File
@@ -6,24 +6,35 @@
export let id: string;
export let selectedIds: string[] = [];
let el: HTMLInputElement;
const handleClick = (e: Event) => {
// Prevent the link from being followed
e.preventDefault();
if (!isHTMLInputElement(el)) return;
selectedIds = toggle(selectedIds, id);
// Hack to make sure the checkbox is checked, independent of the
// preventDefault() call above
window.setTimeout(() => {
el.checked = selectedIds.includes(id);
});
};
</script>
<TableCell>
<TableCell class="u-position-relative">
<div class="touch-area" on:click={handleClick} on:keypress={handleClick} />
<InputCheckbox
bind:element={el}
id="select-{id}"
value={selectedIds.includes(id)}
on:click={(e) => {
// Prevent the link from being followed
e.preventDefault();
const el = e.currentTarget;
if (!isHTMLInputElement(el)) return;
selectedIds = toggle(selectedIds, id);
// Hack to make sure the checkbox is checked, independent of the
// preventDefault() call above
window.setTimeout(() => {
el.checked = selectedIds.includes(id);
});
}} />
on:click={handleClick} />
</TableCell>
<style lang="scss">
.touch-area {
position: absolute;
inset: 0;
}
</style>
+7 -1
View File
@@ -55,7 +55,7 @@ export function empty(arr: unknown[]): boolean {
export function at(array: readonly [], index: number): undefined;
export function at<T>(array: readonly T[], index: number): T;
export function at<T>(array: readonly T[] | [], index: number): T | undefined {
const len = array.length;
const len = array?.length;
if (!len) return undefined;
if (index < 0) index += len;
@@ -96,3 +96,9 @@ export function toggle<T>(arr: T[], elem: T): T[] {
arr.push(elem);
return arr;
}
// TODO: metric type is wrong
export function total(set: Array<number>): number {
if (!set) return 0;
return set.reduce((prev, curr) => prev + curr, 0);
}
+6
View File
@@ -0,0 +1,6 @@
// Easier wrapper over mutation observer, which takes an element, a callback, and returns an unsubscribe fn
export function observeElement(el: HTMLElement, callback: MutationCallback): () => void {
const observer = new MutationObserver(callback);
observer.observe(el, { childList: true, subtree: true });
return () => observer.disconnect();
}
+2 -27
View File
@@ -8,33 +8,8 @@ export function objectEntries<T extends object>(obj: T) {
return Object.entries(obj) as Array<[keyof T, T[keyof T]]>;
}
/**
* Recursively compares two objects to see if they are equal. Returns true if they are equal, false otherwise.
* @param obj1 the first object
* @param obj2 the second object
* @returns true if the objects are equal, false otherwise
*/
export function deepEqual<T>(obj1: T, obj2: T): boolean {
if (obj1 === obj2) return true;
if (typeof obj1 !== 'object' || typeof obj2 !== 'object' || obj1 === null || obj2 === null)
return false;
const keys1 = Object.keys(obj1);
const keys2 = Object.keys(obj2);
if (keys1.length !== keys2.length) return false;
for (const key of keys1) {
if (
!keys2.includes(key) ||
!Object.prototype.hasOwnProperty.call(obj2, key) ||
!deepEqual(obj1[key], obj2[key])
)
return false;
}
return true;
export function isRegExp(value: unknown): value is RegExp {
return value instanceof RegExp;
}
/**
+38
View File
@@ -0,0 +1,38 @@
import { writable, type Writable } from 'svelte/store';
import { deepClone, objectEntries } from './object';
/**
* Given an object `obj`, returns an object of writable stores for each key in that object.
*
* Also returns a listen method that accepts an object of the same shape as `obj` and updates
* the stores with the values from that object if they are different.
*
* @export
*/
export function createConservative<Obj extends Record<string, any>>(obj: Obj) {
const stores = Object.fromEntries(
objectEntries(obj).map(([key, value]) => [key, writable(value)])
) as {
[K in keyof Obj]: Writable<Obj[K]>;
};
const history = deepClone(obj);
function listen(input: Obj) {
objectEntries(input).forEach(([key, value]) => {
if (!(key in stores) || !(key in history)) {
return;
}
const curr = history[key];
if (curr !== value) {
stores[key].set(value);
history[key] = value;
}
});
}
return {
stores,
listen
};
}
+8
View File
@@ -40,3 +40,11 @@ export function camelize(str: string): string {
return firstChar.toLowerCase();
});
}
const formatter = Intl.NumberFormat('en', {
notation: 'compact'
});
export function formatNum(number: number): string {
return formatter.format(number);
}
+3
View File
@@ -9,3 +9,6 @@ export function isHTMLElement(el: unknown): el is HTMLElement {
export function isHTMLInputElement(el: unknown): el is HTMLInputElement {
return el instanceof HTMLInputElement;
}
// eslint-disable-next-line @typescript-eslint/ban-types
export type Prettify<T> = T & {};
+1 -1
View File
@@ -1,6 +1,6 @@
<script lang="ts">
export let overlapCover = false;
export let size: 'small' | 'medium' | 'large' | 'xl' = null;
export let size: 'small' | 'medium' | 'large' | 'xl' | 'xxl' | 'xxxl' = null;
$: style = size
? `--p-container-max-size: var(--container-max-size, var(--container-size-${size}))`
+17 -4
View File
@@ -3,10 +3,12 @@
import { base } from '$app/paths';
import { page } from '$app/stores';
import { Submit, trackEvent } from '$lib/actions/analytics';
import { tooltip } from '$lib/actions/tooltip';
import { toggleCommandCenter } from '$lib/commandCenter/commandCenter.svelte';
import { AvatarInitials, DropList, DropListItem, DropListLink } from '$lib/components';
import { Feedback } from '$lib/components/feedback';
import Button from '$lib/elements/forms/button.svelte';
import { isMac } from '$lib/helpers/platform';
import AppwriteLogo from '$lib/images/appwrite-gray-light.svg';
import DarkMode from '$lib/images/mode/dark-mode.svg';
import LightMode from '$lib/images/mode/light-mode.svg';
@@ -95,7 +97,18 @@
class="button is-small is-text">
<span class="text">Support</span>
</a>
<Button text class="is-small" on:click={toggleCommandCenter}>
<Button
actions={[
(node) => {
return tooltip(node, {
content: isMac() ? '⌘ + K' : 'Ctrl + K',
placement: 'bottom'
});
}
]}
text
class="is-small"
on:click={toggleCommandCenter}>
<i class="icon-search" />
</Button>
</nav>
@@ -160,7 +173,7 @@
</section>
<section class="drop-section">
<ul class="u-flex u-gap-12">
<li>
<li class="u-stretch">
<label class="image-radio">
<img src={LightMode} alt="light mode" />
<input
@@ -175,7 +188,7 @@
value="light" />
</label>
</li>
<li>
<li class="u-stretch">
<label class="image-radio">
<img src={DarkMode} alt="dark mode" />
<input
@@ -190,7 +203,7 @@
value="dark" />
</label>
</li>
<li>
<li class="u-stretch">
<label class="image-radio">
<img src={SystemMode} alt="system mode" />
<input
+50 -41
View File
@@ -53,6 +53,7 @@
$: if (execution?.errors) {
selectedResponse = 'errors';
}
$: host = execution?.requestHeaders?.find((header) => header.name === 'host')?.value;
</script>
<svelte:window on:keydown={handleKeydown} />
@@ -74,49 +75,57 @@
</header>
<div class="cover-frame-content u-flex u-flex-vertical">
<div class="u-flex u-gap-16">
<div class="avatar is-size-large">
<SvgIcon
size={56}
type="color"
name={func.runtime.split('-')[0]}
iconSize="large" />
<div class="grid-1-2">
<div class="grid-1-2-col-1">
<div class="u-flex u-gap-16">
<div class="avatar is-size-large">
<SvgIcon
size={56}
type="color"
name={func.runtime.split('-')[0]}
iconSize="large" />
</div>
<div class="u-grid-equal-row-size u-gap-4 u-line-height-1">
<h2 class="body-text-2 u-bold">Execution ID:</h2>
<Id value={execution.$id}>{execution.$id}</Id>
</div>
</div>
</div>
<div>
<h2 class="body-text-2 u-bold">Execution ID:</h2>
<Id value={execution.$id}>{execution.$id}</Id>
</div>
<ul>
<li class="text">
<b>Duration: </b>
<time>
{calculateTime(execution.duration)}
</time>
</li>
<li class="text">
<b>Created at:</b>
<time>
{toLocaleDateTime(execution.$createdAt)}
</time>
</li>
{#if execution?.requestHeaders?.host}
<div class="grid-1-2-col-2 u-flex u-main-space-between">
<ul class="u-grid-equal-row-size u-gap-4 u-line-height-1">
<li class="text">
<b>Host</b>
<span>
{execution.requestHeaders.host}
</span>
<b>Duration: </b>
<time>
{calculateTime(execution.duration)}
</time>
</li>
{/if}
</ul>
<div class="status u-margin-inline-start-auto">
<Pill
warning={execution.status === 'waiting'}
danger={execution.status === 'failed'}
success={execution.status === 'completed' || execution.status === 'ready'}
info={execution.status === 'processing' || execution.status === 'building'}>
{execution.status}
</Pill>
<li class="text">
<b>Created at:</b>
<time>
{toLocaleDateTime(execution.$createdAt)}
</time>
</li>
{#if host}
<li class="text">
<b>Host:</b>
<span>
{host}
</span>
</li>
{/if}
</ul>
<div class="status u-margin-inline-start-auto">
<Pill
warning={execution.status === 'waiting'}
danger={execution.status === 'failed'}
success={execution.status === 'completed' ||
execution.status === 'ready'}
info={execution.status === 'processing' ||
execution.status === 'building'}>
{execution.status}
</Pill>
</div>
</div>
</div>
@@ -258,7 +267,7 @@
</p>
{/if}
</div>
<div class="grid-1-2-col-2 u-flex u-flex-vertical u-gap-16">
<div class="grid-1-2-col-2 u-flex u-flex-vertical u-gap-16 u-min-width-0">
<Heading tag="h3" size="6">Response</Heading>
<div class="u-sep-block-end">
<Tabs>
-5
View File
@@ -1,5 +1,4 @@
<script lang="ts">
import { beforeNavigate } from '$app/navigation';
import { base } from '$app/paths';
import { page } from '$app/stores';
import { trackEvent } from '$lib/actions/analytics';
@@ -37,10 +36,6 @@
isOpen = false;
wizard.start(Create);
}
beforeNavigate(() => {
wizard.hide();
});
</script>
<svelte:window on:keydown={handleKeyDown} />
+3 -1
View File
@@ -30,7 +30,9 @@
* Cancel navigation when wizard is open and triggered by popstate
*/
beforeNavigate((n) => {
if (!$wizard.show || !$wizard.cover) return;
const external = n.to.url.hostname !== globalThis.location.hostname;
if (external) return;
if (!($wizard.show || $wizard.cover)) return;
if (n.type === 'popstate') {
n.cancel();
}
+3 -1
View File
@@ -146,7 +146,9 @@
class:is-cloud={isCloud}
style:--url={`url(${AppwriteCloudBg})`}
style:--url-mobile={`url(${AppwriteCloudBgMobile})`}>
<div class="container u-flex u-flex-vertical u-cross-center" class:cloud-contents={isCloud}>
<div
class="container u-flex u-flex-vertical u-cross-center u-main-center"
class:cloud-contents={isCloud}>
{#if isCloud}
<a class="mobile-logo is-only-mobile" href={user ? '/console' : '/'}>
<img
+82 -1
View File
@@ -13,10 +13,12 @@
<script lang="ts">
import { Container } from '$lib/layout';
import { BarChart } from '$lib/charts';
import { BarChart, LineChart } from '$lib/charts';
import { Card, SecondaryTabs, SecondaryTabsItem, Heading } from '$lib/components';
import { Colors } from '$lib/charts/config';
import type { Models } from '@appwrite.io/console';
import { page } from '$app/stores';
import { Tiles } from '../components/index.js';
type MetricMetadata = {
title: string;
@@ -25,7 +27,16 @@
export let title: string;
export let count: Models.Metric[];
export let created: Models.Metric[];
export let read: Models.Metric[];
export let updated: Models.Metric[];
export let deleted: Models.Metric[];
export let countMetadata: MetricMetadata;
export let createdMetadata: MetricMetadata;
export let readMetadata: MetricMetadata;
export let updatedMetadata: MetricMetadata;
export let deletedMetadata: MetricMetadata;
export let path: string = null;
</script>
@@ -62,6 +73,76 @@
</div>
{/if}
</Card>
<Tiles>
<Card isTile>
{#if created}
<Heading tag="h6" size="6">{total(created)}</Heading>
<p>{createdMetadata.title}</p>
<div class="u-margin-block-start-16" />
<div class="chart-container">
<LineChart
series={[
{
name: createdMetadata.legend,
data: [...created.map((e) => [e.date, e.value])],
color: Colors.Secondary
}
]} />
</div>
{/if}
</Card>
<Card isTile>
{#if read}
<Heading tag="h6" size="6">{total(read)}</Heading>
<p>{readMetadata.title}</p>
<div class="u-margin-block-start-16" />
<div class="chart-container">
<LineChart
series={[
{
name: readMetadata.legend,
data: [...read.map((e) => [e.date, e.value])],
color: Colors.Tertiary
}
]} />
</div>
{/if}
</Card>
<Card isTile>
{#if updated}
<Heading tag="h6" size="6">{total(updated)}</Heading>
<p>{updatedMetadata.title}</p>
<div class="u-margin-block-start-16" />
<div class="chart-container">
<LineChart
series={[
{
name: updatedMetadata.legend,
data: [...updated.map((e) => [e.date, e.value])],
color: Colors.Quaternary
}
]} />
</div>
{/if}
</Card>
<Card isTile>
{#if deleted}
<Heading tag="h6" size="6">{total(deleted)}</Heading>
<p>{deletedMetadata.title}</p>
<div class="u-margin-block-start-16" />
<div class="chart-container">
<LineChart
series={[
{
name: deletedMetadata.legend,
data: [...deleted.map((e) => [e.date, e.value])],
color: Colors.Quinary
}
]} />
</div>
{/if}
</Card>
</Tiles>
</Container>
<style lang="scss">
+9 -10
View File
@@ -26,7 +26,6 @@
const dispatch = createEventDispatcher();
let currentStep = 1;
let showExitModal = false;
function handleKeydown(event: KeyboardEvent) {
@@ -55,8 +54,8 @@
function handleStepClick(e: CustomEvent<number>) {
const step = e.detail;
if (step < currentStep) {
currentStep = step;
if (step < $wizard.step) {
$wizard.step = step;
}
}
@@ -79,12 +78,12 @@
dispatch('finish');
} else {
trackEvent('wizard_next');
currentStep++;
$wizard.step++;
}
}
$: sortedSteps = [...steps].sort(([a], [b]) => (a > b ? 1 : -1));
$: isLastStep = currentStep === steps.size;
$: isLastStep = $wizard.step === steps.size;
</script>
<svelte:window on:keydown={handleKeydown} />
@@ -114,7 +113,7 @@
text: label,
optional
}))}
{currentStep} />
currentStep={$wizard.step} />
</aside>
<div class="wizard-media">
{#if $wizard.media}
@@ -124,24 +123,24 @@
<div class="wizard-main">
<Form noStyle onSubmit={submit}>
{#each sortedSteps as [step, { component }]}
{#if currentStep === step}
{#if $wizard.step === step}
<svelte:component this={component} />
{/if}
{/each}
<div class="form-footer">
<div class="u-flex u-main-end u-gap-12">
{#if !isLastStep && sortedSteps[currentStep - 1][1].optional}
{#if !isLastStep && sortedSteps[$wizard.step - 1]?.[1]?.optional}
<Button text on:click={() => dispatch('finish')}>
Skip optional steps
</Button>
{/if}
{#if currentStep === 1}
{#if $wizard.step === 1}
<Button secondary on:click={handleExit}>Cancel</Button>
{:else}
<Button
secondary
on:click={() => currentStep--}
on:click={() => $wizard.step--}
on:click={() => trackEvent('wizard_back')}>Back</Button>
{/if}
+1 -2
View File
@@ -11,8 +11,7 @@
}
</script>
<Modal bind:show onSubmit={handleSubmit} icon="exclamation" state="warning">
<svelte:fragment slot="header">Exit Process</svelte:fragment>
<Modal title="Exit Process" bind:show onSubmit={handleSubmit} icon="exclamation" state="warning">
<p>
Are you sure you want to exit from <slot />? All data will be deleted. This action is
irreversible.
+1 -1
View File
@@ -33,12 +33,12 @@
</script>
<Modal
title="Delete domain"
bind:show={showDelete}
onSubmit={deleteDomain}
icon="exclamation"
state="warning"
headerDivider={false}>
<svelte:fragment slot="header">Delete domain</svelte:fragment>
{#if selectedDomain}
<p data-private>
Are you sure you want to delete <b>{selectedDomain.domain}</b>? You will no longer be
+19 -10
View File
@@ -85,7 +85,7 @@
<span class="icon-external-link" aria-hidden="true" />
</span>
</TableCellLink>
<TableCell title="Status">
<TableCell title="Verification">
{#if domain.status === 'created'}
<div class="u-flex u-gap-8 u-cross-center">
<Pill danger>
@@ -94,7 +94,7 @@
aria-hidden="true" />
<span class="u-text">Failed</span>
</Pill>
<button on:click={() => openRetry(domain)}>
<button type="button" on:click={() => openRetry(domain)}>
<span class="link">Retry</span>
</button>
</div>
@@ -107,13 +107,18 @@
</Pill>
{/if}
</TableCell>
<TableCell title="Name">
<TableCell title="Cartificate">
{#if domain.status === 'unverified'}
<div class="u-flex u-gap-8 u-cross-center">
<span
class="icon-x-circle u-color-text-danger"
aria-hidden="true" />
<p class="text">Failed</p>
<Pill danger>
<span
class="icon-exclamation-circle u-color-text-danger"
aria-hidden="true" />
<span class="u-text">Failed</span>
</Pill>
<button type="button" on:click={() => openRetry(domain)}>
<span class="link">Retry</span>
</button>
</div>
{:else if domain.status === 'verified'}
<div class="u-flex u-gap-8 u-cross-center">
@@ -152,7 +157,9 @@
</Button>
<svelte:fragment slot="list">
<DropListItem icon="refresh" on:click={() => openRetry(domain, i)}>
Retry
{domain.status === 'unverfied'
? 'Retry generation'
: 'Retry verification'}
</DropListItem>
<DropListItem
icon="trash"
@@ -179,8 +186,10 @@
{/if}
<Delete bind:showDelete bind:selectedDomain {dependency} />
<Modal bind:show={showRetry} headerDivider={false} bind:error={retryError}>
<svelte:fragment slot="header">Retry verification</svelte:fragment>
<Modal bind:show={showRetry} headerDivider={false} bind:error={retryError} size="big">
<svelte:fragment slot="title">
Retry {$domain.status === 'unverfied' ? 'certificate generation' : 'verification'}
</svelte:fragment>
<Retry on:error={(e) => (retryError = e.detail)} />
<svelte:fragment slot="footer">
<Button text on:click={() => (showRetry = false)}>Close</Button>
+59 -23
View File
@@ -4,7 +4,7 @@
import { domain } from './store';
import CnameTable from './cnameTable.svelte';
import { createEventDispatcher } from 'svelte';
import { Box } from '$lib/components';
import { Box, Code, Trim } from '$lib/components';
import { invalidate } from '$app/navigation';
import { Dependencies } from '$lib/constants';
import { addNotification } from '$lib/stores/notifications';
@@ -20,7 +20,10 @@
$domain = await sdk.forProject.proxy.updateRuleVerification($domain.$id);
invalidate(Dependencies.FUNCTION_DOMAINS);
addNotification({
message: 'Domain has been verified successfully',
message:
$domain.status === 'unverfied'
? 'Domain certificate has been generated successfully'
: 'Domain has been verified successfully',
type: 'success'
});
trackEvent(Submit.DomainUpdateVerification);
@@ -33,24 +36,57 @@
}
</script>
<Box radius="small">
<div class="u-flex u-gap-8 u-cross-center">
<span class="icon-exclamation-circle u-color-text-danger" aria-hidden="true" />
<p class="u-stretch">Verification failed</p>
<Button secondary on:click={retry} disabled={retrying}>
{#if retrying}
<div class="loader u-text-color-gray" />
{:else}
Retry
{/if}
</Button>
</div>
<p class="text u-margin-block-start-24">
In order to continue, set the following record on your DNS provider. Find a list of domain
providers and their DNS settings in our documentation. Changes may take time to be
effective.
</p>
<div class="u-margin-block-start-24">
<CnameTable />
</div>
</Box>
{#if $domain.status === 'created'}
<Box radius="small">
<div class="u-flex u-gap-8 u-cross-center">
<span class="icon-exclamation-circle u-color-text-danger" aria-hidden="true" />
<p class="u-stretch">Verification failed</p>
<Button secondary on:click={retry} disabled={retrying}>
{#if retrying}
<div class="loader u-text-color-gray" />
{:else}
Retry
{/if}
</Button>
</div>
<p class="text u-margin-block-start-24">
In order to continue, set the following record on your DNS provider. DNS records may
take up to 48 hours to propagate. Please retry over the next 48 hours, but if
verification still fails, please <a
href="https://appwrite.io/support"
target="_blank"
rel="noopener noreferrer">contact support</a
>.
</p>
<div class="u-margin-block-start-24">
<CnameTable />
</div>
</Box>
{:else if $domain.status === 'unverified'}
<Trim alternativeTrim><b>{$domain.domain}</b></Trim>
<Box radius="small">
<div class="u-flex u-gap-8 u-cross-center">
<span class="icon-exclamation-circle u-color-text-danger" aria-hidden="true" />
<p class="u-stretch">Generation failed</p>
<Button secondary on:click={retry} disabled={retrying}>
{#if retrying}
<div class="loader u-text-color-gray" />
{:else}
Retry
{/if}
</Button>
</div>
<p class="text u-margin-block-start-24">
In order to continue, set the following record on your DNS provider. DNS records may
take up to 48 hours to propagate. Please retry over the next 48 hours, but if
verification still fails, please <a
href="https://appwrite.io/support"
target="_blank"
rel="noopener noreferrer">contact support</a
>.
</p>
{#if $domain?.logs}
<Code language="sh" withCopy code={$domain.logs} />
{/if}
</Box>
{/if}
+3 -2
View File
@@ -3,6 +3,7 @@
import { domain } from './store';
import Retry from './retry.svelte';
import { addNotification } from '$lib/stores/notifications';
import { Box } from '$lib/components';
function onRetryError(event: CustomEvent<string>) {
addNotification({
@@ -25,7 +26,7 @@
<p class="u-stretch">Domain verified</p>
</div>
{/if}
<div class="box">
<Box>
<div class="u-flex u-gap-8 u-cross-center">
{#if $domain.status === 'verifying'}
<div
@@ -42,6 +43,6 @@
</p>
{/if}
</div>
</div>
</Box>
</div>
</WizardStep>
+9 -5
View File
@@ -2,21 +2,23 @@ import { getProjectId } from '$lib/helpers/project';
import { VARS } from '$lib/system';
import {
Account,
Assistant,
Avatars,
Client,
Console,
Databases,
Functions,
Health,
Locale,
Migrations,
Projects,
Project,
Project as ProjectApi,
Projects,
Proxy,
Storage,
Teams,
Users,
Project as ProjectApi,
Vcs,
Proxy
Vcs
} from '@appwrite.io/console';
const endpoint = VARS.APPWRITE_ENDPOINT ?? `${globalThis?.location?.origin}/v1`;
@@ -63,7 +65,9 @@ export const sdk = {
projects: new Projects(clientConsole),
teams: new Teams(clientConsole),
users: new Users(clientConsole),
migrations: new Migrations(clientConsole)
migrations: new Migrations(clientConsole),
console: new Console(clientConsole),
assistant: new Assistant(clientConsole)
},
get forProject() {
const projectId = getProjectId();
+18 -2
View File
@@ -9,6 +9,7 @@ export type WizardStore = {
cover?: typeof SvelteComponent;
interceptor?: () => Promise<void>;
nextDisabled: boolean;
step: number;
};
function createWizardStore() {
@@ -18,7 +19,8 @@ function createWizardStore() {
cover: null,
interceptor: null,
media: null,
nextDisabled: false
nextDisabled: false,
step: 1
});
return {
@@ -30,6 +32,7 @@ function createWizardStore() {
n.component = component;
n.interceptor = null;
n.media = media;
n.step = 1;
n.cover = null;
trackEvent('wizard_start');
return n;
@@ -52,6 +55,7 @@ function createWizardStore() {
n.component = null;
n.interceptor = null;
n.media = null;
n.step = 1;
n.cover = null;
return n;
}),
@@ -59,7 +63,19 @@ function createWizardStore() {
update((n) => {
n.cover = component;
return n;
})
}),
updateStep: (cb: (prevStep: number) => number) => {
update((n) => {
n.step = cb(n.step);
return n;
});
},
setStep: (step: number) => {
update((n) => {
n.step = step;
return n;
});
}
};
}
@@ -14,7 +14,7 @@
id={appwriteVariable.name}
label={appwriteVariable.name}
placeholder={appwriteVariable.placeholder ?? 'Enter value'}
required={appwriteVariable.required}
required={appwriteVariable.required && !$templateConfig.generateKey}
bind:value={$templateConfig.appwriteApiKey}
disabled={!!$templateConfig.generateKey} />
<Helper type="neutral">
@@ -1,7 +1,7 @@
<script lang="ts">
import { base } from '$app/paths';
import { page } from '$app/stores';
import { EmptySearch, PaginationInline } from '$lib/components';
import { EmptySearch } from '$lib/components';
import { Button, InputSearch, InputSelect } from '$lib/elements/forms';
import { timeFromNow } from '$lib/helpers/date';
import { app } from '$lib/stores/app';
@@ -12,9 +12,11 @@
const dispatch = createEventDispatcher();
export let callbackState: Record<string, string> = null;
export let selectedRepository: string = null;
export let hasInstallations = false;
export let action: 'button' | 'select' = 'select';
let offset = 0;
const limit = 5;
@@ -57,7 +59,11 @@
function connectGitHub() {
const redirect = new URL($page.url);
redirect.searchParams.append('github-installed', 'true');
if (callbackState) {
Object.keys(callbackState).forEach((key) => {
redirect.searchParams.append(key, callbackState[key]);
});
}
const target = new URL(`${sdk.forProject.client.config.endpoint}/vcs/github/authorize`);
target.searchParams.set('projectId', $page.params.project);
target.searchParams.set('success', redirect.toString());
@@ -173,10 +179,10 @@
{/if}
{/each}
</ul>
<div class="u-flex u-margin-block-start-32 u-main-space-between">
<!-- <div class="u-flex u-margin-block-start-32 u-main-space-between">
<p class="text">Total results: {response?.length}</p>
<PaginationInline {limit} bind:offset sum={response?.length} />
</div>
</div> -->
{:else if search}
<EmptySearch hidePages>
<div class="common-section">
@@ -18,13 +18,13 @@
$func.$id,
$func.name,
$func.runtime,
$func.entrypoint || undefined,
$func.execute || undefined,
$func.events || undefined,
$func.schedule || undefined,
$func.timeout || undefined,
$func.enabled || undefined,
$func.logging || undefined,
$func.entrypoint || undefined,
$func.commands || undefined,
$installation.$id,
$repository.id,
@@ -59,11 +59,11 @@
const stepsComponents: WizardStepsType = new Map();
stepsComponents.set(1, {
label: 'Select repository',
label: 'Repository',
component: SelectRepository
});
stepsComponents.set(2, {
label: 'Git configuration',
label: 'Git',
component: GitConfiguration
});
</script>
+22 -13
View File
@@ -19,7 +19,7 @@
variables,
repositoryBehaviour: 'new',
repositoryName: template.id,
repositoryPrivate: false,
repositoryPrivate: true,
repositoryId: null
});
wizard.start(CreateTemplate);
@@ -28,7 +28,7 @@
<script lang="ts">
import { base } from '$app/paths';
import { AvatarGroup, Heading } from '$lib/components';
import { AvatarGroup, Box, Heading } from '$lib/components';
import WizardCover from '$lib/layout/wizardCover.svelte';
import { app } from '$lib/stores/app';
import { wizard } from '$lib/stores/wizard';
@@ -46,9 +46,9 @@
let selectedRepository: string;
const quickStart = marketplace.find((template) => template.id === 'starter');
const templates = marketplace.filter((template) => template.id !== 'starter').slice(0, 3);
const templates = marketplace.filter((template) => template.id !== 'starter').slice(0, 2);
function connect(event: CustomEvent<Models.Repository>) {
function connect(event: CustomEvent<Models.ProviderRepository>) {
repository.set(event.detail);
wizard.start(CreateGit);
}
@@ -60,7 +60,7 @@
</script>
<WizardCover>
<svelte:fragment slot="title">Create function</svelte:fragment>
<svelte:fragment slot="title">Create Function</svelte:fragment>
<div class="wizard-container container">
<div class="grid-1-1 u-gap-24">
<div>
@@ -74,6 +74,10 @@
bind:hasInstallations
bind:selectedRepository
action="button"
callbackState={{
from: 'github',
to: 'cover'
}}
on:connect={connect} />
</div>
</div>
@@ -82,8 +86,11 @@
class="link"
on:click={() => wizard.start(CreateManual)}>manually</button>
or using the CLI.
<a href="https://appwrite.io/docs/functions-deploy" target="_blank" rel="noopener noreferrer" class="link"
>Learn more</a
<a
href="https://appwrite.io/docs/functions-deploy"
target="_blank"
rel="noopener noreferrer"
class="link">Learn more</a
>.
</p>
</div>
@@ -122,23 +129,25 @@
}.svg`}
alt={runtime.name} />
</div>
<div class="body-text-2">{runtimeDetail.name}</div>
<div class="body-text-2">
{runtimeDetail.name}
</div>
</button>
</li>
{/each}
{#if quickStart.runtimes.length < 6}
<li>
<div
class="box u-width-full-line u-flex u-cross-center u-gap-8"
style:--box-padding="1rem"
style:--box-border-radius="var(--border-radius-small)">
<Box
class="u-width-full-line u-flex u-cross-center u-gap-8"
padding={16}
radius="small">
<AvatarGroup
icons={['dotnet', 'deno']}
total={4}
avatarSize="small"
bordered />
</div>
</Box>
</li>
{/if}
</ul>
+3 -3
View File
@@ -20,13 +20,13 @@
$createFunction.$id || ID.unique(),
$createFunction.name,
$createFunction.runtime,
$createFunction.entrypoint,
$createFunction.execute || undefined,
undefined,
undefined,
undefined,
undefined,
undefined,
$createFunction.entrypoint,
$createFunction.commands || undefined,
$installation.$id,
$repository.id,
@@ -67,11 +67,11 @@
const stepsComponents: WizardStepsType = new Map();
stepsComponents.set(1, {
label: 'Git configuration',
label: 'Git',
component: GitConfiguration
});
stepsComponents.set(2, {
label: 'Function configuration',
label: 'Configuration',
component: FunctionConfiguration
});
stepsComponents.set(3, {
@@ -26,13 +26,13 @@
$createFunction.$id || ID.unique(),
$createFunction.name,
$createFunction.runtime,
$createFunction.entrypoint,
$createFunction.execute || undefined,
undefined,
undefined,
undefined,
undefined,
undefined,
$createFunction.entrypoint,
$createFunction.commands || undefined
);
await sdk.forProject.functions.createDeployment(
@@ -36,13 +36,13 @@
$templateConfig.$id || ID.unique(),
$templateConfig.name,
$templateConfig.runtime,
runtimeDetail.entrypoint,
$template.permissions || undefined,
$template.events || undefined,
$template.cron || undefined,
$template.timeout || undefined,
undefined,
undefined,
runtimeDetail.entrypoint,
runtimeDetail.commands || undefined,
$installation.$id,
$repository.id,
@@ -77,6 +77,7 @@
wizard.hide();
templateConfig.set(null);
template.set(null);
installation.set(null);
}
const stepsComponents: WizardStepsType = new Map();
@@ -7,7 +7,7 @@
</script>
<WizardStep>
<svelte:fragment slot="title">Function configuration</svelte:fragment>
<svelte:fragment slot="title">Configuration</svelte:fragment>
<svelte:fragment slot="subtitle">
Set your deployment configuration and any build commands here.
</svelte:fragment>
@@ -8,12 +8,18 @@
import { WizardStep } from '$lib/layout';
import { addNotification } from '$lib/stores/notifications';
import { sdk } from '$lib/stores/sdk';
import { onMount } from 'svelte';
import Repositories from '../components/repositories.svelte';
import { installation, repository, templateConfig } from '../store';
import { installation, repository, template, templateConfig } from '../store';
import { Box } from '$lib/components';
let selectedInstallationId;
let hasInstallations;
let selectedRepository;
let selectedInstallationId: string;
let hasInstallations: boolean;
let selectedRepository: string;
onMount(() => {
$templateConfig.repositoryPrivate = true;
});
async function beforeSubmit() {
if (!hasInstallations || !$installation) {
@@ -41,7 +47,19 @@
function connectGitHub() {
const redirect = new URL($page.url);
redirect.searchParams.append('github-installed', 'true');
const callbackState = {
from: 'github',
to: 'template',
step: '4',
template: $template.id,
templateConfig: JSON.stringify($templateConfig)
};
if (callbackState) {
Object.keys(callbackState).forEach((key) => {
redirect.searchParams.append(key, callbackState[key]);
});
}
const target = new URL(`${sdk.forProject.client.config.endpoint}/vcs/github/authorize`);
target.searchParams.set('projectId', $page.params.project);
target.searchParams.set('success', redirect.toString());
@@ -70,7 +88,7 @@
</script>
<WizardStep {beforeSubmit}>
<svelte:fragment slot="title">Select repository</svelte:fragment>
<svelte:fragment slot="title">Repository</svelte:fragment>
<svelte:fragment slot="subtitle">
Select a Git repository that will trigger your function deployments when updated.
</svelte:fragment>
@@ -107,7 +125,7 @@
<div class="u-flex u-cross-center u-flex-vertical u-gap-16">
<Button href={connectGitHub().toString()} fullWidth secondary>
<span class="icon-github" aria-hidden="true" />
<span class="text">Continue with GitHub</span>
<span class="text">GitHub</span>
</Button>
<Button disabled fullWidth secondary>
<span class="icon-gitlab" aria-hidden="true" />
@@ -123,36 +141,33 @@
</Button>
</div>
{/if}
{#if $installation}
<Box radius="small" class="u-margin-block-start-20">
<div class="u-flex u-gap-16">
<div class="avatar is-size-x-small">
<span class={getProviderIcon($installation.provider)} />
</div>
<div class="u-cross-child-center u-line-height-1-5">
<h6 class="u-bold u-trim-1">
{$installation.organization}/{$templateConfig.repositoryName}
</h6>
</div>
</div>
<div class="u-margin-block-start-24">
<FormList>
<InputText
id="repositoryName"
label="Repository name"
placeholder="my-repository"
bind:value={$templateConfig.repositoryName} />
<InputChoice
id="repositoryPrivate"
label="Keep repository private"
bind:value={$templateConfig.repositoryPrivate} />
</FormList>
</div>
</Box>
{/if}
{/await}
{#if $installation}
<div
class="box u-margin-block-start-20"
style:--box-border-radius="var(--border-radius-small)">
<div class="u-flex u-gap-16">
<div class="avatar is-size-x-small">
<span class={getProviderIcon($installation.provider)} />
</div>
<div class="u-cross-child-center u-line-height-1-5">
<h6 class="u-bold u-trim-1">
{$installation.organization}/{$templateConfig.repositoryName}
</h6>
</div>
</div>
<div class="u-margin-block-start-24">
<FormList>
<InputText
id="repositoryName"
label="Repository name"
placeholder="my-repository"
bind:value={$templateConfig.repositoryName} />
<InputChoice
id="repositoryPrivate"
label="Keep repository private"
bind:value={$templateConfig.repositoryPrivate} />
</FormList>
</div>
</div>
{/if}
{/if}
</WizardStep>
@@ -21,7 +21,7 @@
</script>
<WizardStep>
<svelte:fragment slot="title">Function details</svelte:fragment>
<svelte:fragment slot="title">Details</svelte:fragment>
<svelte:fragment slot="subtitle">Create and deploy your function manually.</svelte:fragment>
<FormList>
<InputText
@@ -51,7 +51,7 @@
</script>
<WizardStep>
<svelte:fragment slot="title">Function configuration</svelte:fragment>
<svelte:fragment slot="title">Configuration</svelte:fragment>
<svelte:fragment slot="subtitle">
Set your deployment configuration and any build commands here.
</svelte:fragment>
@@ -1,4 +1,5 @@
<script lang="ts">
import { Box } from '$lib/components';
import { FormList, InputChoice, InputText } from '$lib/elements/forms';
import InputSelectSearch from '$lib/elements/forms/inputSelectSearch.svelte';
import { WizardStep } from '$lib/layout';
@@ -33,12 +34,12 @@
</script>
<WizardStep>
<svelte:fragment slot="title">Git configuration</svelte:fragment>
<svelte:fragment slot="title">Git</svelte:fragment>
<svelte:fragment slot="subtitle">
Configure the Git repository that will trigger your function deployments when updated.
</svelte:fragment>
<div class="box" style:--box-border-radius="var(--border-radius-small)">
<Box radius="small">
<div class="u-flex u-gap-16">
<div class="avatar is-size-x-small">
<span class={getProviderIcon($repository.provider)} />
@@ -93,10 +94,10 @@
</FormList>
</div>
{/await}
</div>
</Box>
<p class="text u-margin-block-start-8">
View your configuration in <a
href={$repository.html_url}
Visit your repository on <a
href={`https://github.com/${$repository.organization}/${$repository.name}`}
target="_blank"
rel="noopener noreferrer"
class="link">GitHub</a
@@ -11,18 +11,28 @@
</script>
<WizardStep {beforeSubmit}>
<svelte:fragment slot="title">Repository behaviour</svelte:fragment>
<svelte:fragment slot="title">Connect</svelte:fragment>
<svelte:fragment slot="subtitle">
Connect function to a new repository or to an existing one within a selected Git
organization.
</svelte:fragment>
<ul class="u-flex u-flex-vertical u-gap-24">
<LabelCard name="test" value="new" bind:group={$templateConfig.repositoryBehaviour}>
<ul class="u-flex u-flex-vertical u-gap-16">
<LabelCard
name="behaviour"
value="new"
backgroundColor="var(--color-neutral-5)"
backgroundColorHover="var(--color-neutral-10)"
bind:group={$templateConfig.repositoryBehaviour}>
<svelte:fragment slot="title">Create a new repository</svelte:fragment>
Clone the template and create a new repository in your selected organization.
</LabelCard>
<LabelCard name="test" value="new" bind:group={$templateConfig.repositoryBehaviour}>
<LabelCard
name="behaviour"
value="existing"
backgroundColor="var(--color-neutral-5)"
backgroundColorHover="var(--color-neutral-10)"
bind:group={$templateConfig.repositoryBehaviour}>
<svelte:fragment slot="title">Add to existing repository</svelte:fragment>
Clone the template to an existing repository in your selected organization.
</LabelCard>
@@ -2,8 +2,8 @@
import { WizardStep } from '$lib/layout';
import Repositories from '../components/repositories.svelte';
let hasInstallations;
let selectedRepository;
let hasInstallations: boolean;
let selectedRepository: string;
async function beforeSubmit() {
if (!hasInstallations) {
throw new Error('Please connect a Git provider');
@@ -15,7 +15,7 @@
</script>
<WizardStep {beforeSubmit}>
<svelte:fragment slot="title">Select repository</svelte:fragment>
<svelte:fragment slot="title">Repository</svelte:fragment>
<svelte:fragment slot="subtitle">
Select a Git repository that will trigger your function deployments when updated.
</svelte:fragment>
@@ -51,9 +51,10 @@
label="Runtime"
id="runtime"
placeholder="Select runtime"
bind:value={$templateConfig.runtime}
required
disabled={options.length <= 1}
{options}
required />
bind:value={$templateConfig.runtime} />
{/await}
</FormList>
@@ -11,7 +11,10 @@
if (!variable.required) {
continue;
}
if (variable.name === 'APPWRITE_API_KEY') {
if ($templateConfig.appwriteApiKey || $templateConfig.generateKey) continue;
else throw new Error(`Please set ${variable.name} variable or generate it.`);
}
if (!$templateConfig.variables[variable.name]) {
throw new Error(`Please set ${variable.name} variable.`);
}
@@ -23,7 +26,7 @@
</script>
<WizardStep {beforeSubmit}>
<svelte:fragment slot="title">Environment variables</svelte:fragment>
<svelte:fragment slot="title">Variables</svelte:fragment>
<svelte:fragment slot="subtitle">
Edit the values of the environment variables that will be passed to your function at
runtime.
+21
View File
@@ -185,4 +185,25 @@
background: var(--separator-color);
}
}
.border-gradient {
position: relative;
}
.border-gradient::before {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
border-radius: var(--border-radius);
border: var(--border-size) solid transparent;
background: var(--border-gradient) border-box;
mask: linear-gradient(#fff 0 0) padding-box, linear-gradient(#fff 0 0);
-webkit-mask: linear-gradient(#fff 0 0) padding-box, linear-gradient(#fff 0 0);
-webkit-mask-composite: destination-out;
mask-composite: exclude;
pointer-events: none;
}
</style>
+1 -2
View File
@@ -199,8 +199,7 @@
</div>
</div>
<Modal bind:show={showEmbedCode}>
<svelte:fragment slot="header">Get Embed Code</svelte:fragment>
<Modal title="Get Embed Code" bind:show={showEmbedCode}>
<div class="u-overflow-hidden">
<Code language="html" code={embedCode} noMargin />
</div>
@@ -1,9 +1,9 @@
<script lang="ts">
import { EyebrowHeading } from '$lib/components';
import { Alert, Box, EyebrowHeading } from '$lib/components';
import { Button } from '$lib/elements/forms';
import { deepMap } from '$lib/helpers/object';
import type { WritableValue } from '$lib/helpers/types';
import { sdk } from '$lib/stores/sdk';
import { sdk, type getSdkForProject } from '$lib/stores/sdk';
import { onMount } from 'svelte';
@@ -14,11 +14,11 @@
providerResources,
resourcesToMigrationForm
} from '$lib/stores/migration';
import { addNotification } from '$lib/stores/notifications';
import { wizard } from '$lib/stores/wizard';
export let formData: ReturnType<typeof createMigrationFormStore>;
export let provider: ReturnType<typeof createMigrationProviderStore>;
export let projectSdk: ReturnType<typeof getSdkForProject>;
type ValueOf<T> = T[keyof T];
type FormData = WritableValue<typeof formData>;
@@ -80,12 +80,14 @@
$: version = report?.version || '0.0.0';
let isOpen = false;
let error = false;
onMount(async () => {
isOpen = true;
try {
switch ($provider.provider) {
case 'appwrite': {
const res = await sdk.forProject.migrations.getAppwriteReport(
const res = await projectSdk.migrations.getAppwriteReport(
providerResources.appwrite,
$provider.endpoint,
$provider.projectID,
@@ -95,7 +97,7 @@
break;
}
case 'supabase': {
const res = await sdk.forProject.migrations.getSupabaseReport(
const res = await projectSdk.migrations.getSupabaseReport(
providerResources.supabase,
$provider.endpoint,
$provider.apiKey,
@@ -118,7 +120,7 @@
report = res;
} else if ($provider.serviceAccount) {
// Manual auth
const res = await sdk.forProject.migrations.getFirebaseReport(
const res = await projectSdk.migrations.getFirebaseReport(
providerResources.firebase,
$provider.serviceAccount
);
@@ -128,7 +130,7 @@
break;
}
case 'nhost': {
const res = await sdk.forProject.migrations.getNHostReport(
const res = await projectSdk.migrations.getNHostReport(
providerResources.nhost,
$provider.subdomain,
$provider.region,
@@ -142,10 +144,7 @@
}
} catch (e) {
if (!isOpen) return;
addNotification({
message: e.message,
type: 'error'
});
error = true;
}
return () => {
@@ -154,11 +153,10 @@
});
$: resources = providerResources[$provider.provider];
$: wizard.setNextDisabled(!report);
</script>
<div class="box" style:border-radius="0.5rem">
<Box radius="small">
<div class="u-flex u-flex-vertical u-gap-16">
<EyebrowHeading class="eyebrow" tag="h3" size={3}>Good to know</EyebrowHeading>
<div class="u-flex u-gap-16">
@@ -204,7 +202,46 @@
</div>
{/if}
</div>
</div>
</Box>
{#if report && !isVersionAtLeast(version, '1.4.0')}
<div class="u-margin-block-start-24">
<Alert
type="warning"
isStandalone
buttons={[
{
name: 'Learn more',
method() {
wizard.updateStep((p) => p - 1);
}
}
]}>
<svelte:fragment slot="title">Functions not available for import</svelte:fragment>
To migrate your functions, update the version of the Appwrite instance you're importing from
to a version newer than 1.4
</Alert>
</div>
{/if}
{#if error}
<div class="u-margin-block-start-24">
<Alert
type="error"
isStandalone
buttons={[
{
name: 'Edit credentials',
method() {
wizard.updateStep((p) => p - 1);
}
}
]}>
<svelte:fragment slot="title">Request failed</svelte:fragment>
Please check if your credentials are filled in correctly in the previous step
</Alert>
</div>
{/if}
<ul class="buttons-list u-margin-block-start-32 u-main-end">
<li class="buttons-list-item">
@@ -216,18 +253,22 @@
</li>
</ul>
<ul class="u-flex u-flex-vertical u-gap-32 u-margin-block-start-16">
<ul class="u-flex u-flex-vertical u-margin-block-start-16">
{#if resources.includes('user')}
<li class="checkbox-field">
<input
type="checkbox"
bind:checked={$formData.users.root}
on:change={handleInputChange('users.root')} />
<div class="u-flex u-gap-4">
<div class="u-flex u-gap-4 u-cross-center">
<span class="u-bold">Users</span>
{#if $provider.provider !== 'firebase'}
<span class="inline-tag">{report?.user ?? '...'}</span>
{#if report?.user !== undefined}
<span class="inline-tag">{report.user}</span>
{:else if !error}
<span class="loader is-small u-margin-inline-start-4" />
{/if}
{/if}
</div>
<div />
@@ -240,10 +281,14 @@
type="checkbox"
bind:checked={$formData.users.teams}
on:change={handleInputChange('users.teams')} />
<div class="u-flex u-gap-4">
<div class="u-flex u-gap-4 u-cross-center">
<span class="u-bold">Include teams</span>
{#if $provider.provider === 'firebase'}
<span class="inline-tag">{report?.team ?? '...'}</span>
{#if report?.team !== undefined}
<span class="inline-tag">{report.team}</span>
{:else if !error}
<span class="loader is-small u-margin-inline-start-4" />
{/if}
{/if}
</div>
<div />
@@ -260,10 +305,14 @@
type="checkbox"
bind:checked={$formData.databases.root}
on:change={handleInputChange('databases.root')} />
<div class="u-flex u-gap-4">
<div class="u-flex u-gap-4 u-cross-center">
<span class="u-bold">Databases</span>
{#if $provider.provider !== 'firebase'}
<span class="inline-tag">{report?.database ?? '...'}</span>
{#if report?.database !== undefined}
<span class="inline-tag">{report.database}</span>
{:else if !error}
<span class="loader is-small u-margin-inline-start-4" />
{/if}
{/if}
</div>
<div />
@@ -276,10 +325,14 @@
type="checkbox"
bind:checked={$formData.databases.documents}
on:change={handleInputChange('databases.documents')} />
<div class="u-flex u-gap-4">
<div class="u-flex u-gap-4 u-cross-center">
<span class="u-bold">Include documents</span>
{#if $provider.provider !== 'firebase'}
<span class="inline-tag">{report?.document ?? '...'}</span>
{#if report?.document !== undefined}
<span class="inline-tag">{report.document}</span>
{:else if !error}
<span class="loader is-small u-margin-inline-start-4" />
{/if}
{/if}
</div>
<div />
@@ -296,10 +349,14 @@
type="checkbox"
bind:checked={$formData.functions.root}
on:change={handleInputChange('functions.root')} />
<div class="u-flex u-gap-4">
<div class="u-flex u-gap-4 u-cross-center">
<span class="u-bold">Functions</span>
{#if $provider.provider !== 'firebase'}
<span class="inline-tag">{report?.function ?? '...'}</span>
{#if report?.function !== undefined}
<span class="inline-tag">{report.function}</span>
{:else if !error}
<span class="loader is-small u-margin-inline-start-4" />
{/if}
{/if}
</div>
<div />
@@ -341,26 +398,28 @@
type="checkbox"
bind:checked={$formData.storage.root}
on:change={handleInputChange('storage.root')} />
<div class="u-flex u-gap-4">
<div class="u-flex u-gap-4 u-cross-center">
<span class="u-bold">Storage</span>
{#if $provider.provider !== 'firebase'}
<span class="inline-tag">
{report?.size ? `${report.size.toFixed(2)}MB` : '...'}
</span>
{#if report?.size !== undefined}
<span class="inline-tag">{`${report.size.toFixed(2)}MB`}</span>
{:else if !error}
<span class="loader is-small u-margin-inline-start-4" />
{/if}
{/if}
</div>
<div />
<span>
<p>
Import all buckets
{#if $provider.provider !== 'firebase'}
<span class="inline-tag">{report?.bucket ?? '...'}</span>
{#if $provider.provider !== 'firebase' && report?.bucket}
<span class="inline-tag">{report.bucket}</span>
{/if}
and files
{#if $provider.provider !== 'firebase'}
<span class="inline-tag">{report?.file ?? '...'}</span>
{#if $provider.provider !== 'firebase' && report?.file}
<span class="inline-tag">{report.file}</span>
{/if}
</span>
</p>
</li>
{/if}
</ul>
@@ -398,10 +457,10 @@
grid-template-columns: auto 1fr;
gap: 0.25rem 1rem;
align-items: center;
padding-block-end: 1rem;
ul {
grid-column-start: 2;
padding-block-end: 2rem;
li {
margin-block-start: 1rem;
@@ -1,11 +1,12 @@
<script lang="ts">
import { WizardStep } from '$lib/layout';
import { getSdkForProject } from '$lib/stores/sdk';
import { formData, provider } from '.';
import { formData, provider, selectedProject } from '.';
import ResourceForm from './resource-form.svelte';
</script>
<WizardStep>
<svelte:fragment slot="title">Select data</svelte:fragment>
<ResourceForm {provider} {formData} />
<ResourceForm {provider} {formData} projectSdk={getSdkForProject($selectedProject)} />
</WizardStep>
+125 -45
View File
@@ -17,20 +17,53 @@
import { goto } from '$app/navigation';
import { CommandCenter, registerCommands, registerSearchers } from '$lib/commandCenter';
import { AIPanel, OrganizationsPanel, ProjectsPanel } from '$lib/commandCenter/panels';
import { AIPanel } from '$lib/commandCenter/panels';
import { orgSearcher, projectsSearcher } from '$lib/commandCenter/searchers';
import { addSubPanel } from '$lib/commandCenter/subPanels';
import { addNotification } from '$lib/stores/notifications';
import { openMigrationWizard } from './(migration-wizard)';
import { project } from './project-[project]/store';
import { sdk } from '$lib/stores/sdk';
function kebabToSentenceCase(str: string) {
return str
.split('-')
.map((word) => word[0].toUpperCase() + word.slice(1))
.join(' ');
}
let isAssistantEnabled = false;
onMount(async () => {
const vars = await sdk.forConsole.console.variables();
isAssistantEnabled = vars._APP_ASSISTANT_ENABLED === true;
});
$: isOnSettingsLayout = $project?.$id
? $page.url.pathname.includes(`project-${$project.$id}/settings`)
: false;
$: $registerCommands([
{
label: 'Ask AI',
label: 'Go to projects',
callback: () => {
goto('/console');
},
keys: ['g', 'p'],
group: 'navigation',
disabled:
$page.url.pathname.includes('/console/organization-') &&
!$page.url.pathname.endsWith('/members') &&
!$page.url.pathname.endsWith('/settings'),
rank: -1
},
{
label: 'Ask the AI',
callback: () => {
addSubPanel(AIPanel);
},
keys: ['a', 'i'],
icon: 'light-bulb'
icon: 'sparkles',
disabled: !isAssistantEnabled
},
{
label: 'Go to account',
@@ -38,25 +71,8 @@
goto('/console/account');
},
keys: ['i'],
group: 'navigation'
},
{
label: 'Find an organization',
callback: () => {
addSubPanel(OrganizationsPanel);
},
group: 'organizations',
icon: 'search',
keys: ['f', 'o']
},
{
label: 'Find a project',
callback: () => {
addSubPanel(ProjectsPanel);
},
keys: ['f', 'p'],
group: 'projects',
icon: 'search'
group: 'navigation',
rank: -2
},
{
label: 'Create new organization',
@@ -66,14 +82,6 @@
keys: ['c', 'o'],
group: 'organizations'
},
{
label: 'Go to home',
callback: () => {
goto('/console');
},
keys: ['h'],
group: 'navigation'
},
{
label: 'Open documentation',
callback: () => {
@@ -106,24 +114,96 @@
group: 'help',
icon: 'discord'
},
...(['auto', 'dark', 'light'] as const).map((theme) => {
return {
label: `Set theme to ${theme}`,
callback: () => {
$app.theme = theme;
addNotification({
title: 'Theme changed',
message: `Theme changed to ${$app.theme}`,
type: 'success'
});
},
group: 'misc',
icon: 'switch-horizontal',
keys: ['t', theme[0]]
} as const;
}),
// Auth
...[
'users-limit',
'session-length',
'sessions-limit',
'password-history',
'password-dictionary',
'personal-data'
].map(
(heading) =>
({
label: kebabToSentenceCase(heading),
async callback() {
await goto(`/console/project-${$project.$id}/auth/security#${heading}`);
scrollBy({ top: -100 });
},
group: 'security',
icon: 'pencil'
} as const)
),
// Settings
{
label: 'Toggle theme',
label: 'Go to settings overview',
keys: isOnSettingsLayout ? ['g', 'o'] : undefined,
callback: () => {
if ($app.theme === 'auto') {
$app.theme = 'light';
} else if ($app.theme === 'light') {
$app.theme = 'dark';
} else {
$app.theme = 'auto';
}
addNotification({
title: 'Theme changed',
message: `Theme changed to ${$app.theme}`,
type: 'success'
});
goto(`/console/project-${$project.$id}/settings`);
},
group: 'misc',
icon: 'switch-horizontal'
disabled: isOnSettingsLayout && $page.url.pathname.endsWith('settings'),
group: isOnSettingsLayout ? 'navigation' : 'settings',
rank: isOnSettingsLayout ? 40 : -1
},
{
label: 'Go to custom domains',
keys: isOnSettingsLayout ? ['g', 'd'] : undefined,
callback: () => {
goto(`/console/project-${$project.$id}/settings/domains`);
},
disabled: isOnSettingsLayout && $page.url.pathname.includes('domains'),
group: isOnSettingsLayout ? 'navigation' : 'settings',
rank: isOnSettingsLayout ? 30 : -1
},
{
label: 'Go to webhooks',
keys: isOnSettingsLayout ? ['g', 'w'] : undefined,
callback: () => {
goto(`/console/project-${$project.$id}/settings/webhooks`);
},
disabled: isOnSettingsLayout && $page.url.pathname.includes('webhooks'),
group: isOnSettingsLayout ? 'navigation' : 'settings',
rank: isOnSettingsLayout ? 20 : -1
},
{
label: 'Go to migrations',
keys: isOnSettingsLayout ? ['g', 'm'] : undefined,
callback: () => {
goto(`/console/project-${$project.$id}/settings/migrations`);
},
disabled: isOnSettingsLayout && $page.url.pathname.includes('migrations'),
group: isOnSettingsLayout ? 'navigation' : 'settings',
rank: isOnSettingsLayout ? 10 : -1
},
{
label: 'Go to SMTP settings',
keys: isOnSettingsLayout ? ['g', 's'] : undefined,
callback: () => {
goto(`/console/project-${$project.$id}/settings/smtp`);
},
disabled: isOnSettingsLayout && $page.url.pathname.includes('smtp'),
group: isOnSettingsLayout ? 'navigation' : 'settings',
rank: -1
}
]);
let isOpen = false;
-13
View File
@@ -1,13 +0,0 @@
import { base } from '$app/paths';
import { redirect } from '@sveltejs/kit';
import type { PageLoad } from './$types';
export const load: PageLoad = async ({ parent }) => {
const { organizations } = await parent();
if (organizations.total) {
const teamId = localStorage.getItem('organization') ?? organizations.teams[0].$id;
throw redirect(303, `${base}/console/organization-${teamId}`);
} else {
throw redirect(303, `${base}/console/onboarding`);
}
};
+1 -1
View File
@@ -30,12 +30,12 @@
</script>
<Modal
title="Delete account"
bind:show={showDelete}
onSubmit={deleteAccount}
icon="exclamation"
state="warning"
headerDivider={false}>
<svelte:fragment slot="header">Delete account</svelte:fragment>
<p>Are you sure you want to delete your account?</p>
<svelte:fragment slot="footer">
<Button text on:click={() => (showDelete = false)}>Cancel</Button>
+1 -2
View File
@@ -42,8 +42,7 @@
}
</script>
<Modal {error} onSubmit={create} size="big" bind:show>
<svelte:fragment slot="header">Create new organization</svelte:fragment>
<Modal title="Create new organization" {error} onSubmit={create} size="big" bind:show>
<FormList>
<InputText
id="organization-name"
@@ -1,13 +1,39 @@
<script lang="ts">
import { goto } from '$app/navigation';
import { page } from '$app/stores';
import { registerCommands } from '$lib/commandCenter';
import { newMemberModal, newOrgModal } from '$lib/stores/organization';
import { requestedMigration } from '$routes/store';
import { openMigrationWizard } from '../(migration-wizard)';
import Create from '../createOrganization.svelte';
import CreateMember from './createMember.svelte';
export let data;
$: if ($requestedMigration) {
openMigrationWizard();
}
$: $registerCommands([
{
label: 'Go to members',
callback: () => {
goto(`/console/organization-${data.organization.$id}/members`);
},
keys: ['g', 'm'],
disabled: $page.url.pathname.endsWith('/members'),
group: 'navigation'
},
{
label: 'Go to settings',
callback: () => {
goto(`/console/organization-${data.organization.$id}/settings`);
},
keys: ['g', 's'],
disabled: $page.url.pathname.endsWith('/settings'),
group: 'navigation'
}
]);
</script>
<svelte:head>
@@ -49,8 +49,7 @@
}
</script>
<Modal {error} size="big" bind:show={showCreate} onSubmit={create}>
<svelte:fragment slot="header">Invite Member</svelte:fragment>
<Modal title="Invite Member" {error} size="big" bind:show={showCreate} onSubmit={create}>
<FormList>
<InputEmail
required
@@ -44,8 +44,7 @@
}
</script>
<Modal {error} onSubmit={create} size="big" bind:show>
<svelte:fragment slot="header">Create project</svelte:fragment>
<Modal title="Create project" {error} onSubmit={create} size="big" bind:show>
<FormList>
<InputText id="name" label="Name" bind:value={name} required autofocus={true} />
{#if !showCustomId}
@@ -49,7 +49,7 @@
icon="exclamation"
state="warning"
headerDivider={false}>
<svelte:fragment slot="header">
<svelte:fragment slot="title">
{isUser ? 'Leave organization' : 'Delete member'}
</svelte:fragment>
<p data-private>
@@ -37,12 +37,12 @@
</script>
<Modal
title="Delete organization"
onSubmit={deleteOrg}
bind:show={showDelete}
icon="exclamation"
state="warning"
headerDivider={false}>
<svelte:fragment slot="header">Delete organization</svelte:fragment>
<p>
Are you sure you want to delete <b>{$organization.name}</b>? All projects ({$organization.total})
and data associated with this organization will be deleted. This action is irreversible.
@@ -5,15 +5,8 @@
import { project, stats } from './store';
import { goto } from '$app/navigation';
import { addSubPanel, registerCommands, registerSearchers } from '$lib/commandCenter';
import { registerCommands, registerSearchers } from '$lib/commandCenter';
import {
BucketsPanel,
DatabasesPanel,
FunctionsPanel,
TeamsPanel,
UsersPanel
} from '$lib/commandCenter/panels';
import {
bucketSearcher,
dbSearcher,
@@ -35,24 +28,7 @@
$: $registerCommands([
{
label: 'Go to overview',
callback: () => {
goto(`/console/project-${$project.$id}`);
},
keys: ['o'],
group: 'navigation'
},
{
label: 'Go to auth',
callback: () => {
goto(`/console/project-${$project.$id}/auth`);
},
keys: ['a'],
group: 'navigation'
},
{
label: 'Go to databases',
label: 'Go to Databases',
callback: () => {
goto(`/console/project-${$project.$id}/databases`);
},
@@ -60,7 +36,15 @@
group: 'navigation'
},
{
label: 'Go to functions',
label: 'Go to Auth',
callback: () => {
goto(`/console/project-${$project.$id}/auth`);
},
keys: ['a'],
group: 'navigation'
},
{
label: 'Go to Functions',
callback: () => {
goto(`/console/project-${$project.$id}/functions`);
},
@@ -68,7 +52,7 @@
group: 'navigation'
},
{
label: 'Go to storage',
label: 'Go to Storage',
callback: () => {
goto(`/console/project-${$project.$id}/storage`);
},
@@ -84,50 +68,12 @@
group: 'navigation'
},
{
label: 'Find users',
label: 'Go to overview',
callback: () => {
addSubPanel(UsersPanel);
goto(`/console/project-${$project.$id}`);
},
group: 'users',
icon: 'search',
keys: ['f', 'u'],
rank: 10
},
{
label: 'Find teams',
callback: () => {
addSubPanel(TeamsPanel);
},
group: 'teams',
icon: 'search',
keys: ['f', 't']
},
{
label: 'Find databases',
callback: () => {
addSubPanel(DatabasesPanel);
},
group: 'databases',
icon: 'search',
keys: ['f', 'd']
},
{
label: 'Find functions',
callback: () => {
addSubPanel(FunctionsPanel);
},
group: 'functions',
icon: 'search',
keys: ['f', 'f']
},
{
label: 'Find buckets',
callback: () => {
addSubPanel(BucketsPanel);
},
group: 'buckets',
icon: 'search',
keys: ['f', 'b']
keys: ['o'],
group: 'navigation'
}
]);
@@ -74,51 +74,6 @@
group: 'navigation',
rank: 1,
disabled: $page.url.pathname.endsWith('settings')
},
{
label: 'Users limit',
async callback() {
await goto(`/console/project-${$project.$id}/auth/security#users-limit`);
scrollBy({ top: -100 });
},
group: 'security',
icon: 'pencil'
},
{
label: 'Session length',
async callback() {
await goto(`/console/project-${$project.$id}/auth/security#session-length`);
scrollBy({ top: -100 });
},
group: 'security',
icon: 'pencil'
},
{
label: 'Sessions limit',
async callback() {
await goto(`/console/project-${$project.$id}/auth/security#sessions-limit`);
scrollBy({ top: -100 });
},
group: 'security',
icon: 'pencil'
},
{
label: 'Password history',
async callback() {
await goto(`/console/project-${$project.$id}/auth/security#password-history`);
scrollBy({ top: -100 });
},
group: 'security',
icon: 'pencil'
},
{
label: 'Password dictionary',
async callback() {
await goto(`/console/project-${$project.$id}/auth/security#password-dictionary`);
scrollBy({ top: -100 });
},
group: 'security',
icon: 'pencil'
}
]);
@@ -38,7 +38,7 @@
</script>
<Modal {error} onSubmit={update} size="big" show on:close>
<svelte:fragment slot="header">{provider.name} OAuth2 Settings</svelte:fragment>
<svelte:fragment slot="title">{provider.name} OAuth2 Settings</svelte:fragment>
<FormList>
<p>
To use {provider.name} authentication in your application, first fill in this form. For more
@@ -39,7 +39,7 @@
</script>
<Modal {error} size="big" show onSubmit={update} on:close>
<svelte:fragment slot="header">{provider.name} OAuth2 Settings</svelte:fragment>
<svelte:fragment slot="title">{provider.name} OAuth2 Settings</svelte:fragment>
<FormList>
<p>
To use {provider.name} authentication in your application, first fill in this form. For more
@@ -39,7 +39,7 @@
</script>
<Modal {error} size="big" show onSubmit={update} on:close>
<svelte:fragment slot="header">{provider.name} OAuth2 Settings</svelte:fragment>
<svelte:fragment slot="title">{provider.name} OAuth2 Settings</svelte:fragment>
<FormList>
<p>
To use {provider.name} authentication in your application, first fill in this form. For more
@@ -41,8 +41,7 @@
}
</script>
<Modal {error} size="big" bind:show={showCreate} onSubmit={create}>
<svelte:fragment slot="header">Create team</svelte:fragment>
<Modal title="Create team" {error} size="big" bind:show={showCreate} onSubmit={create}>
<FormList>
<InputText
id="name"
@@ -58,8 +58,7 @@
}
</script>
<Modal {error} size="big" bind:show={showCreate} onSubmit={create}>
<svelte:fragment slot="header">Create user</svelte:fragment>
<Modal title="Create user" {error} size="big" bind:show={showCreate} onSubmit={create}>
<FormList>
<InputText
id="name"
@@ -37,7 +37,7 @@
</script>
<Modal {error} size="big" show onSubmit={update} on:close>
<svelte:fragment slot="header">{provider.name} OAuth2 Settings</svelte:fragment>
<svelte:fragment slot="title">{provider.name} OAuth2 Settings</svelte:fragment>
<FormList>
<p>
To use {provider.name} authentication in your application, first fill in this form. For more
@@ -35,7 +35,7 @@
</script>
<Modal {error} size="big" show onSubmit={update} on:close>
<svelte:fragment slot="header">{provider.name} OAuth2 Settings</svelte:fragment>
<svelte:fragment slot="title">{provider.name} OAuth2 Settings</svelte:fragment>
<FormList>
<p>
To use {provider.name} authentication in your application, first fill in this form. For more
@@ -35,7 +35,7 @@
</script>
<Modal {error} size="big" show onSubmit={update} on:close>
<svelte:fragment slot="header">{provider.name} OAuth2 Settings</svelte:fragment>
<svelte:fragment slot="title">{provider.name} OAuth2 Settings</svelte:fragment>
<FormList>
<p>
To use {provider.name} authentication in your application, first fill in this form. For more
@@ -38,7 +38,7 @@
</script>
<Modal {error} onSubmit={update} size="big" show on:close>
<svelte:fragment slot="header">{provider.name} OAuth2 Settings</svelte:fragment>
<svelte:fragment slot="title">{provider.name} OAuth2 Settings</svelte:fragment>
<FormList>
<p>
To use {provider.name} authentication in your application, first fill in this form. For more
@@ -61,7 +61,7 @@
</script>
<Modal {error} onSubmit={update} size="big" show on:close>
<svelte:fragment slot="header">{provider.name} OAuth2 Settings</svelte:fragment>
<svelte:fragment slot="title">{provider.name} OAuth2 Settings</svelte:fragment>
<FormList>
<p>
To use {provider.name} authentication in your application, first fill in this form. For more
@@ -43,7 +43,7 @@
</script>
<Modal {error} onSubmit={update} size="big" show on:close>
<svelte:fragment slot="header">{provider.name} OAuth2 Settings</svelte:fragment>
<svelte:fragment slot="title">{provider.name} OAuth2 Settings</svelte:fragment>
<FormList>
<p>
To use {provider.name} authentication in your application, first fill in this form. For more
@@ -35,7 +35,7 @@
<Form onSubmit={updatePersonalDataCheck}>
<CardGrid>
<Heading tag="h2" size="7">Personal data</Heading>
<Heading tag="h2" size="7" id="personal-data">Personal data</Heading>
<svelte:fragment slot="aside">
<FormList>
<InputSwitch
@@ -44,7 +44,7 @@
label="Disallow personal data" />
</FormList>
<p class="text">
Do now allow passwords that contain any part of the user's personal data. This
Do not allow passwords that contain any part of the user's personal data. This
includes the user's <code>name</code>, <code>email</code>, or <code>phone</code>.
</p>
</svelte:fragment>
@@ -44,8 +44,7 @@
}
</script>
<Modal {error} onSubmit={create} size="big" bind:show={showCreate}>
<svelte:fragment slot="header">Create membership</svelte:fragment>
<Modal title="Create membership" {error} onSubmit={create} size="big" bind:show={showCreate}>
<FormList>
<InputEmail
id="email"
@@ -38,12 +38,12 @@
</script>
<Modal
title="Delete Member"
bind:show={showDelete}
onSubmit={deleteMembership}
icon="exclamation"
state="warning"
headerDivider={false}>
<svelte:fragment slot="header">Delete Member</svelte:fragment>
<p data-private>
Are you sure you want to delete <b>{selectedMembership.userName}</b> from '{selectedMembership.teamName}'?
</p>
@@ -29,12 +29,12 @@
</script>
<Modal
title="Delete team"
bind:show={showDelete}
onSubmit={deleteTeam}
icon="exclamation"
state="warning"
headerDivider={false}>
<svelte:fragment slot="header">Delete team</svelte:fragment>
<p data-private>
Are you sure you want to delete <b>{team.name}</b>?
</p>
@@ -35,12 +35,12 @@
import EmailMagicUrlTemplate from './emailMagicUrlTemplate.svelte';
import EmailRecoveryTemplate from './emailRecoveryTemplate.svelte';
import EmailInviteTemplate from './emailInviteTemplate.svelte';
import SmsVerificationTemplate from './smsVerificationTemplate.svelte';
import SmsLoginTemplate from './smsLoginTemplate.svelte';
import { baseEmailTemplate, baseSmsTemplate, emailTemplate, smsTemplate } from './store';
// import SmsVerificationTemplate from './smsVerificationTemplate.svelte';
// import SmsLoginTemplate from './smsLoginTemplate.svelte';
// import { baseEmailTemplate, baseSmsTemplate, emailTemplate, smsTemplate } from './store';
import { baseEmailTemplate, emailTemplate } from './store';
import { Button } from '$lib/elements/forms';
export let data;
const projectId = $page.params.project;
let emailOpen = 'verification';
@@ -49,14 +49,14 @@
$: emailResetPassword = emailOpen === 'recovery';
$: emailInviteUser = emailOpen === 'invitation';
let smsOpen = 'verification';
$: smsVerificationOpen = smsOpen === 'verification';
$: smsLoginOpen = smsOpen === 'login';
$: smsInvitationOpen = smsOpen === 'invitation';
// let smsOpen = 'verification';
// $: smsVerificationOpen = smsOpen === 'verification';
// $: smsLoginOpen = smsOpen === 'login';
// $: smsInvitationOpen = smsOpen === 'invitation';
onMount(async () => {
openEmail('verification');
openSms('verification');
// openSms('verification');
});
async function openEmail(type: string) {
@@ -65,11 +65,11 @@
$baseEmailTemplate = { ...$emailTemplate };
}
async function openSms(type: string) {
type === smsOpen ? (smsOpen = null) : (smsOpen = type);
$smsTemplate = await loadSmsTemplate(projectId, type, 'en');
$baseSmsTemplate = { ...$smsTemplate };
}
// async function openSms(type: string) {
// type === smsOpen ? (smsOpen = null) : (smsOpen = type);
// $smsTemplate = await loadSmsTemplate(projectId, type, 'en');
// $baseSmsTemplate = { ...$smsTemplate };
// }
</script>
<Container>
@@ -88,7 +88,7 @@
type="info"
buttons={[
{
name: 'Add SMTP server',
name: 'SMTP settings',
method: () => {
goto(`${base}/console/project-${$project.$id}/settings/smtp`);
}
@@ -107,11 +107,10 @@
<Heading size="7" tag="h3">Email templates</Heading>
<p class="text">
Use templates to send and process account management emails. <a
href="https://appwrite.io/docs"
href="https://appwrite.io/docs/email-and-sms-templates"
class="link">
Learn more about email templates.
</a>
<!-- TODO Docs link -->
</p>
<svelte:fragment slot="aside">
@@ -129,7 +128,7 @@
Send a verification email to users that sign in with their email and
password.
</p>
<EmailVerificationTemplate localeCodes={data.localeCodes} />
<EmailVerificationTemplate />
</CollapsibleItem>
<CollapsibleItem
bind:open={emailMagicSessionOpen}
@@ -139,7 +138,7 @@
}}>
<svelte:fragment slot="title">Magic URL</svelte:fragment>
<p class="text">Send an email to users that sign in with a magic URL.</p>
<EmailMagicUrlTemplate localeCodes={data.localeCodes} />
<EmailMagicUrlTemplate />
</CollapsibleItem>
<CollapsibleItem
bind:open={emailResetPassword}
@@ -149,7 +148,7 @@
}}>
<svelte:fragment slot="title">Reset password</svelte:fragment>
<p class="text">Send a recovery email to users that forget their password.</p>
<EmailRecoveryTemplate localeCodes={data.localeCodes} />
<EmailRecoveryTemplate />
</CollapsibleItem>
<CollapsibleItem
bind:open={emailInviteUser}
@@ -159,25 +158,24 @@
}}>
<svelte:fragment slot="title">Invite user</svelte:fragment>
<p class="text">Send an invitation email to become a member of your project.</p>
<EmailInviteTemplate localeCodes={data.localeCodes} />
<EmailInviteTemplate />
</CollapsibleItem>
</Collapsible>
</svelte:fragment>
<svelte:fragment slot="actions">
<Button href={`${base}/console/project-${$project.$id}/settings/smtp`} secondary>
Add SMTP server
SMTP settings
</Button>
</svelte:fragment>
</CardGrid>
<CardGrid>
<!-- <CardGrid>
<Heading size="7" tag="h3">SMS templates</Heading>
<p class="text">
Use templates to send and process account management mobile messages. <a
href="https://appwrite.io/docs"
class="link">
Learn more about SMS templates</a
>. <!-- TODO Docs link -->
</p>
<svelte:fragment slot="aside">
@@ -192,7 +190,7 @@
<p class="text">
Send a verification SMS to users that sign in with their phone
</p>
<SmsVerificationTemplate localeCodes={data.localeCodes} />
<SmsVerificationTemplate />
</CollapsibleItem>
<CollapsibleItem
bind:open={smsLoginOpen}
@@ -204,7 +202,7 @@
<p class="text">
Send a one-time passcode to users' mobile phones to allow them to sign in.
</p>
<SmsLoginTemplate localeCodes={data.localeCodes} />
<SmsLoginTemplate />
</CollapsibleItem>
<CollapsibleItem
bind:open={smsInvitationOpen}
@@ -214,9 +212,9 @@
}}>
<svelte:fragment slot="title">Invitation</svelte:fragment>
<p class="text">Send an invitation SMS to become a member of your project.</p>
<SmsLoginTemplate localeCodes={data.localeCodes} />
<SmsLoginTemplate />
</CollapsibleItem>
</Collapsible>
</svelte:fragment>
</CardGrid>
</CardGrid>-->
</Container>
@@ -1,7 +1,6 @@
<script lang="ts">
import EmailTemplate from './emailTemplate.svelte';
import LocaleOptions from './localeOptions.svelte';
import type { Models } from '@appwrite.io/console';
import { loadEmailTemplate } from './+page.svelte';
import { page } from '$app/stores';
import { baseEmailTemplate, emailTemplate } from './store';
@@ -9,8 +8,6 @@
import { Id } from '$lib/components';
import { Submit, trackError, trackEvent } from '$lib/actions/analytics';
export let localeCodes: Models.LocaleCode[];
const projectId = $page.params.project;
let locale = 'en';
let loading = false;
@@ -38,7 +35,7 @@
</script>
<div class="boxes-wrapper u-margin-block-start-16">
<LocaleOptions {localeCodes} on:select={onLocaleChange} bind:value={locale} />
<LocaleOptions on:select={onLocaleChange} bind:value={locale} />
<EmailTemplate bind:loading>
<Id value={'{{team}}'}>{'{{team}}'}</Id>
<Id value={'{{user}}'}>{'{{user}}'}</Id>
@@ -2,14 +2,12 @@
import { loadEmailTemplate } from './+page.svelte';
import EmailTemplate from './emailTemplate.svelte';
import LocaleOptions from './localeOptions.svelte';
import type { Models } from '@appwrite.io/console';
import { baseEmailTemplate, emailTemplate } from './store';
import { page } from '$app/stores';
import { addNotification } from '$lib/stores/notifications';
import { Id } from '$lib/components';
import { Submit, trackError, trackEvent } from '$lib/actions/analytics';
export let localeCodes: Models.LocaleCode[];
const projectId = $page.params.project;
let locale = 'en';
@@ -39,7 +37,7 @@
</script>
<div class="boxes-wrapper u-margin-block-start-16">
<LocaleOptions {localeCodes} on:select={onLocaleChange} bind:value={locale} />
<LocaleOptions on:select={onLocaleChange} bind:value={locale} />
<EmailTemplate bind:loading>
<Id value={'{{team}}'}>{'{{team}}'}</Id>
<Id value={'{{user}}'}>{'{{user}}'}</Id>
@@ -1,7 +1,6 @@
<script lang="ts">
import EmailTemplate from './emailTemplate.svelte';
import LocaleOptions from './localeOptions.svelte';
import type { Models } from '@appwrite.io/console';
import { baseEmailTemplate, emailTemplate } from './store';
import { loadEmailTemplate } from './+page.svelte';
import { page } from '$app/stores';
@@ -9,7 +8,6 @@
import { Id } from '$lib/components';
import { Submit, trackError, trackEvent } from '$lib/actions/analytics';
export let localeCodes: Models.LocaleCode[];
const projectId = $page.params.project;
let locale = 'en';
@@ -38,7 +36,7 @@
</script>
<div class="boxes-wrapper u-margin-block-start-16">
<LocaleOptions {localeCodes} on:select={onLocaleChange} bind:value={locale} />
<LocaleOptions on:select={onLocaleChange} bind:value={locale} />
<EmailTemplate bind:loading>
<Id value={'{{team}}'}>{'{{team}}'}</Id>
<Id value={'{{user}}'}>{'{{user}}'}</Id>

Some files were not shown because too many files have changed in this diff Show More