mirror of
https://github.com/appwrite/console.git
synced 2026-06-06 19:27:48 +00:00
Merge branch '1.4.x' into fix-sentence-case
This commit is contained in:
Generated
+7325
-1322
File diff suppressed because it is too large
Load Diff
+5
-3
@@ -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",
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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}
|
||||
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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} />
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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-'] {
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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>
|
||||
@@ -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}
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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) => {
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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,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}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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
|
||||
};
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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,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}))`
|
||||
|
||||
@@ -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
@@ -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>
|
||||
|
||||
@@ -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} />
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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}
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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,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>
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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`);
|
||||
}
|
||||
};
|
||||
@@ -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>
|
||||
|
||||
@@ -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
Reference in New Issue
Block a user