Merge branch 'mgn-dv' of https://github.com/appwrite/console into mgn-dv

This commit is contained in:
Torsten Dittmann
2025-04-02 22:59:53 +02:00
22 changed files with 226 additions and 279 deletions
+98
View File
@@ -0,0 +1,98 @@
<script>
import { Divider, Card, Typography, Layout, Button, Icon } from '@appwrite.io/pink-svelte';
import { IconArrowUp, IconChevronLeft, IconPaperClip } from '@appwrite.io/pink-icons-svelte';
export let showChat;
</script>
<section class="chat" class:is-visible={showChat}>
<Card.Base>
<div class="chat-content">
<Layout.Stack height="calc(100vh - 120px)" justifyContent="space-between">
<Layout.Stack gap="s">
<Layout.Stack
direction="row"
justifyContent="space-between"
alignItems="center">
<Typography.Text>Chat</Typography.Text>
<Button.Button
icon
variant="secondary"
size="s"
on:click={() => {
showChat = false;
}}
><Icon
icon={IconChevronLeft}
color="--fgcolor-neutral-tertiary" /></Button.Button>
</Layout.Stack>
<Divider />
<!-- CHAT CONTENT -->
</Layout.Stack>
<div class="input">
<textarea placeholder="Reply..."></textarea>
<div class="options">
<Layout.Stack direction="row" justifyContent="flex-end">
<Button.Button icon variant="secondary" size="s"
><Icon
icon={IconPaperClip}
color="--fgcolor-neutral-tertiary" /></Button.Button>
<Button.Button icon variant="secondary" size="s"
><Icon
icon={IconArrowUp}
color="--fgcolor-neutral-tertiary" /></Button.Button>
</Layout.Stack>
</div>
</div>
</Layout.Stack>
</div>
</Card.Base>
</section>
<div class="chat-placeholder" class:is-visible={showChat}></div>
<style lang="scss">
.chat {
width: 0;
position: fixed;
top: 48px;
overflow: hidden;
transition: width 0.3s ease-in-out;
&.is-visible {
width: 400px;
}
}
.chat-placeholder {
width: 0;
transition: width 0.3s ease-in-out;
&.is-visible {
width: 600px;
}
}
.chat-content {
opacity: 0;
}
.is-visible .chat-content {
transition: opacity 0.3s ease-in-out 0.2s;
opacity: 1;
}
.input {
margin-inline: calc(-1 * var(--space-9));
margin-block-end: calc(-1 * var(--space-9));
border-top: 1px solid var(--border-neutral);
border-radius: var(--border-radius-m);
padding: var(--space-6);
textarea {
width: 100%;
min-height: 100px;
overflow: hidden;
resize: none;
}
}
</style>
+1
View File
@@ -81,3 +81,4 @@ export { default as Sidebar } from './sidebar.svelte';
export { default as BottomSheet } from './bottom-sheet/index';
export { default as Confirm } from './confirm.svelte';
export { default as UsageCard } from './usageCard.svelte';
export { default as Chat } from './chat.svelte';
+30 -20
View File
@@ -5,35 +5,45 @@
IconCog,
IconDatabase,
IconFolder,
IconSparkles,
IconTemplate,
IconUserGroup
} from '@appwrite.io/pink-icons-svelte';
import { base } from '$app/paths';
export let project;
export let showChat;
</script>
<nav style:--icon-fill="var(--fgcolor-neutral-tertiary)">
<Layout.Stack gap="xs">
<ActionMenu.Item.Anchor href={`${base}/project-${project.$id}`} leadingIcon={IconTemplate}
></ActionMenu.Item.Anchor>
<Divider />
<ActionMenu.Item.Anchor
href={`${base}/project-${project.$id}/auth`}
leadingIcon={IconUserGroup}></ActionMenu.Item.Anchor>
<ActionMenu.Item.Anchor
href={`${base}/project-${project.$id}/databases`}
leadingIcon={IconDatabase}></ActionMenu.Item.Anchor>
<ActionMenu.Item.Anchor
href={`${base}/project-${project.$id}/messaging`}
leadingIcon={IconAnnotation}></ActionMenu.Item.Anchor>
<ActionMenu.Item.Anchor
href={`${base}/project-${project.$id}/storage`}
leadingIcon={IconFolder}></ActionMenu.Item.Anchor>
<Divider />
<ActionMenu.Item.Anchor
href={`${base}/project-${project.$id}/settings`}
leadingIcon={IconCog}></ActionMenu.Item.Anchor>
<Layout.Stack direction="column" justifyContent="space-between" height="100%">
<Layout.Stack gap="xs">
<ActionMenu.Item.Anchor
href={`${base}/project-${project.$id}`}
leadingIcon={IconTemplate}></ActionMenu.Item.Anchor>
<Divider />
<ActionMenu.Item.Anchor
href={`${base}/project-${project.$id}/auth`}
leadingIcon={IconUserGroup}></ActionMenu.Item.Anchor>
<ActionMenu.Item.Anchor
href={`${base}/project-${project.$id}/databases`}
leadingIcon={IconDatabase}></ActionMenu.Item.Anchor>
<ActionMenu.Item.Anchor
href={`${base}/project-${project.$id}/messaging`}
leadingIcon={IconAnnotation}></ActionMenu.Item.Anchor>
<ActionMenu.Item.Anchor
href={`${base}/project-${project.$id}/storage`}
leadingIcon={IconFolder}></ActionMenu.Item.Anchor>
<Divider />
<ActionMenu.Item.Anchor
href={`${base}/project-${project.$id}/settings`}
leadingIcon={IconCog}></ActionMenu.Item.Anchor>
</Layout.Stack>
<ActionMenu.Item.Button
leadingIcon={IconSparkles}
on:click={() => {
showChat = !showChat;
}}></ActionMenu.Item.Button>
</Layout.Stack>
</nav>
+19 -8
View File
@@ -1,5 +1,6 @@
<script lang="ts">
import { Layout } from '@appwrite.io/pink-svelte';
import { isStudio } from '$lib/system';
export let size: 'small' | 'medium' | 'large' | 'xl' = null;
export let blocksize = '152px';
@@ -9,16 +10,21 @@
: '';
</script>
<div class="top-cover-console" style:block-size={blocksize}>
<div class="cover-container" {style}>
<Layout.Stack direction="row" alignItems="center">
<slot name="header" />
</Layout.Stack>
<slot />
{#if isStudio}
<slot name="header" />
<slot />
{:else}
<div class="top-cover-console" class:no-padding={isStudio} style:block-size={blocksize}>
<div class="cover-container" {style}>
<Layout.Stack direction="row" alignItems="center">
<slot name="header" />
</Layout.Stack>
<slot />
</div>
</div>
</div>
{/if}
<style>
<style lang="scss">
.top-cover-console {
container-type: inline-size;
padding-block-start: var(--base-32);
@@ -27,6 +33,11 @@
background: var(--bgcolor-neutral-primary, #1d1d21);
margin-left: -190px;
padding-left: 190px;
&.no-padding {
margin-left: 0;
padding-left: 0;
}
}
.cover-container {
position: relative;
+21 -4
View File
@@ -1,6 +1,6 @@
<script lang="ts">
import { AvatarInitials, Breadcrumbs } from '$lib/components/index.js';
import { Avatar, Layout } from '@appwrite.io/pink-svelte';
import { AvatarInitials, Breadcrumbs, Chat } from '$lib/components/index.js';
import { Avatar, Divider, Layout, Card, Typography } from '@appwrite.io/pink-svelte';
import { page } from '$app/stores';
import { organization, organizationList } from '$lib/stores/organization';
import { user } from '$lib/stores/user';
@@ -26,6 +26,8 @@
projects: loadedProjects
};
});
$: showChat = !showChat ? $page.url.pathname.endsWith('builder') : showChat;
</script>
<main>
@@ -36,9 +38,24 @@
<AvatarInitials name={$user?.name ?? ''} size="s" />
</Layout.Stack>
</header>
<div class="studio-content" class:project-sidebar={hasProjectSidebar}><slot /></div>
<div class="studio-content" class:project-sidebar={hasProjectSidebar}>
<Layout.Stack direction="row">
{#if hasProjectSidebar}
<Chat bind:showChat />
{/if}
<Card.Base>
<Layout.Stack>
{#if $page.data?.header}
<svelte:component this={$page.data.header} />
{/if}
<slot />
</Layout.Stack>
</Card.Base>
</Layout.Stack>
</div>
{#if hasProjectSidebar}
<SidebarProject project={$page.data.project} />
<SidebarProject project={$page.data.project} bind:showChat />
{:else}
<SidebarOrganization organization={$page.data.organization} />
{/if}
@@ -144,30 +144,28 @@
</script>
{#if isStudio}
<Card.Base>
<Layout.Stack>
<Layout.Stack direction="row" justifyContent="space-between" alignItems="center">
<Typography.Text variant="m-500" color="--fgcolor-neutral-secondary"
>Projects</Typography.Text>
<Button size="s">Create project</Button>
</Layout.Stack>
<Divider />
<Layout.Grid gap="l" columnsS={3} columns={4} columnsL={4} columnsXL={5}>
<!-- TODO: this now maps over projects, but probably will be more like artifacts (and location might be different-->
{#each data.projects.projects as project}
<a href={`${base}/project-${project.$id}`}
><Card.Media
src="https://picsum.photos/260/150"
alt=""
description={project.description}
title={project.name}></Card.Media
></a>
{/each}
</Layout.Grid>
<Layout.Stack>
<Layout.Stack direction="row" justifyContent="space-between" alignItems="center">
<Typography.Text variant="m-500" color="--fgcolor-neutral-secondary"
>Projects</Typography.Text>
<Button size="s">Create project</Button>
</Layout.Stack>
</Card.Base>
<Divider />
<Layout.Grid gap="l" columnsS={3} columns={4} columnsL={4} columnsXL={5}>
<!-- TODO: this now maps over projects, but probably will be more like artifacts (and location might be different-->
{#each data.projects.projects as project}
<a href={`${base}/project-${project.$id}`}
><Card.Media
src="https://picsum.photos/260/150"
alt=""
description={project.description}
title={project.name}></Card.Media
></a>
{/each}
</Layout.Grid>
</Layout.Stack>
{:else}
<Container>
<div class="u-flex u-gap-12 common-section u-main-space-between">
@@ -16,7 +16,7 @@
isBilling,
isOwner
} from '$lib/stores/roles';
import { GRACE_PERIOD_OVERRIDE, isCloud } from '$lib/system';
import { GRACE_PERIOD_OVERRIDE, isCloud, isStudio } from '$lib/system';
import { IconGithub, IconPlus } from '@appwrite.io/pink-icons-svelte';
import { Icon, Tooltip, Typography, Layout, Badge } from '@appwrite.io/pink-svelte';
@@ -76,7 +76,7 @@
].filter((tab) => !tab.disabled);
</script>
{#if $organization?.$id}
{#if $organization?.$id && !isStudio}
<Cover>
<svelte:fragment slot="header">
<span class="u-flex u-cross-center u-gap-8 u-min-width-0">
@@ -2,8 +2,12 @@ import { redirect } from '@sveltejs/kit';
import { base } from '$app/paths';
import type { PageLoad } from './$types';
import { hasOnboardingDismissed } from '$lib/helpers/onboarding';
import { isStudio } from '$lib/system';
export const load: PageLoad = async ({ params }) => {
if (isStudio) {
redirect(302, `${base}/project-${params.project}/builder`);
}
if (hasOnboardingDismissed(params.project)) {
redirect(302, `${base}/project-${params.project}/overview`);
}
@@ -6,6 +6,7 @@
import { Cover } from '$lib/layout';
import { canWriteProjects } from '$lib/stores/roles';
import { Typography } from '@appwrite.io/pink-svelte';
import { isStudio } from '$lib/system';
const projectId = page.params.project;
const path = `${base}/project-${projectId}/auth`;
@@ -52,7 +53,8 @@
<Cover>
<svelte:fragment slot="header">
<Typography.Title color="--fgcolor-neutral-primary" size="xl">Auth</Typography.Title>
<Typography.Title color="--fgcolor-neutral-primary" size={isStudio ? 's' : 'xl'}
>Auth</Typography.Title>
</svelte:fragment>
<Tabs>
{#each tabs as tab}
@@ -0,0 +1,5 @@
<script lang="ts">
import { Layout, Card, Typography, Divider } from '@appwrite.io/pink-svelte';
</script>
<Layout.Stack direction="row">Iframe</Layout.Stack>
@@ -5,6 +5,7 @@
import { isTabSelected } from '$lib/helpers/load';
import { Cover } from '$lib/layout';
import { Typography } from '@appwrite.io/pink-svelte';
import { isStudio } from '$lib/system';
const projectId = page.params.project;
const path = `${base}/project-${projectId}/databases`;
@@ -26,7 +27,8 @@
<Cover>
<svelte:fragment slot="header">
<Typography.Title color="--fgcolor-neutral-primary" size="xl">Databases</Typography.Title>
<Typography.Title color="--fgcolor-neutral-primary" size={isStudio ? 's' : 'xl'}
>Databases</Typography.Title>
</svelte:fragment>
<Tabs>
{#each tabs as tab}
@@ -5,6 +5,7 @@
import { isTabSelected } from '$lib/helpers/load';
import { Cover } from '$lib/layout';
import { Typography } from '@appwrite.io/pink-svelte';
import { isStudio } from '$lib/system';
$: projectId = page.params.project;
$: path = `${base}/project-${projectId}/functions`;
@@ -30,7 +31,8 @@
<Cover>
<svelte:fragment slot="header">
<Typography.Title color="--fgcolor-neutral-primary" size="xl">Functions</Typography.Title>
<Typography.Title color="--fgcolor-neutral-primary" size={isStudio ? 's' : 'xl'}
>Functions</Typography.Title>
</svelte:fragment>
<Tabs>
@@ -5,6 +5,7 @@
import { isTabSelected } from '$lib/helpers/load';
import { Cover } from '$lib/layout';
import { Typography } from '@appwrite.io/pink-svelte';
import { isStudio } from '$lib/system';
const projectId = page.params.project;
const path = `${base}/project-${projectId}/messaging`;
@@ -31,7 +32,8 @@
<Cover>
<svelte:fragment slot="header">
<Typography.Title color="--fgcolor-neutral-primary" size="xl">Messaging</Typography.Title>
<Typography.Title color="--fgcolor-neutral-primary" size={isStudio ? 's' : 'xl'}
>Messaging</Typography.Title>
</svelte:fragment>
<Tabs>
{#each tabs as tab}
@@ -11,12 +11,13 @@
import { isSmallViewport } from '$lib/stores/viewport';
import { Dependencies } from '$lib/constants';
import { trackEvent } from '$lib/actions/analytics';
import { isStudio } from '$lib/system';
</script>
{#if !page.url.pathname.includes('get-started')}
<Cover>
<svelte:fragment slot="header">
<Typography.Title color="--fgcolor-neutral-primary" size="xl">
<Typography.Title color="--fgcolor-neutral-primary" size={isStudio ? 's' : 'xl'}>
{$project?.name}
</Typography.Title>
<Id value={$project.$id}>{$project.$id}</Id>
@@ -6,7 +6,7 @@
import type { TabElement } from '$lib/helpers/load';
import { Cover } from '$lib/layout';
import { canWriteProjects } from '$lib/stores/roles';
import { isCloud } from '$lib/system';
import { isCloud, isStudio } from '$lib/system';
import { Typography } from '@appwrite.io/pink-svelte';
const projectId = page.params.project;
@@ -51,7 +51,8 @@
<Cover>
<svelte:fragment slot="header">
<Typography.Title color="--fgcolor-neutral-primary" size="xl">Settings</Typography.Title>
<Typography.Title color="--fgcolor-neutral-primary" size={isStudio ? 's' : 'xl'}
>Settings</Typography.Title>
</svelte:fragment>
<Tabs>
@@ -5,6 +5,7 @@
import { isTabSelected } from '$lib/helpers/load';
import { Cover } from '$lib/layout';
import { Typography } from '@appwrite.io/pink-svelte';
import { isStudio } from '$lib/system';
$: projectId = page.params.project;
$: path = `${base}/project-${projectId}/sites`;
@@ -25,7 +26,8 @@
<Cover>
<svelte:fragment slot="header">
<Typography.Title color="--fgcolor-neutral-primary" size="xl">Sites</Typography.Title>
<Typography.Title color="--fgcolor-neutral-primary" size={isStudio ? 's' : 'xl'}
>Sites</Typography.Title>
</svelte:fragment>
<Tabs variant="primary" let:root>
@@ -5,6 +5,7 @@
import { isTabSelected } from '$lib/helpers/load';
import { Cover } from '$lib/layout';
import { Typography } from '@appwrite.io/pink-svelte';
import { isStudio } from '$lib/system';
const projectId = page.params.project;
const path = `${base}/project-${projectId}/storage`;
@@ -26,7 +27,8 @@
<Cover>
<svelte:fragment slot="header">
<Typography.Title color="--fgcolor-neutral-primary" size="xl">Storage</Typography.Title>
<Typography.Title color="--fgcolor-neutral-primary" size={isStudio ? 's' : 'xl'}
>Storage</Typography.Title>
</svelte:fragment>
<Tabs>
{#each tabs as tab}
-80
View File
@@ -1,80 +0,0 @@
<script lang="ts">
import { Avatar, Layout } from '@appwrite.io/pink-svelte';
import { AvatarInitials, Breadcrumbs } from '$lib/components';
import { page } from '$app/stores';
import { BillingPlan } from '$lib/constants';
import { isCloud } from '$lib/system';
import { tierToPlan } from '$lib/stores/billing';
import { organization, organizationList } from '$lib/stores/organization';
import { user } from '$lib/stores/user';
import SidebarOrganization from '$lib/components/studio/sidebarOrganization.svelte';
import SidebarProject from '$lib/components/studio/sidebarProject.svelte';
$: hasProjectSidebar = $page.url.pathname.startsWith('/studio/proj');
$: loadedProjects = $page.data.projects.map((project) => {
return {
name: project?.name,
$id: project.$id,
isSelected: $page.data.currentProjectId === project.$id,
platformCount: project.platforms.length,
pingCount: project.pingCount
};
});
$: organizations = $organizationList.teams.map((org) => {
const billingPlan = org['billingPlan'];
return {
name: org.name,
$id: org.$id,
showUpgrade: billingPlan === BillingPlan.FREE,
tierName: isCloud ? (tierToPlan(billingPlan)?.name ?? '') : null,
isSelected: $page.data.currentOrganization?.$id === org.$id,
projects: loadedProjects
};
});
</script>
<main>
<Layout.Stack>
<header>
<Layout.Stack direction="row" justifyContent="space-between" alignItems="center">
<Breadcrumbs {organizations} />
<AvatarInitials name={$user?.name ?? ''} size="s" />
</Layout.Stack>
</header>
<div class="studio-content" class:project-sidebar={hasProjectSidebar}><slot /></div>
{#if hasProjectSidebar}
<SidebarProject />
{:else}
<SidebarOrganization organization={$page.data.currentOrganization} />
{/if}
</Layout.Stack>
</main>
<style lang="scss">
main {
min-height: 100vh;
background-color: var(--bgcolor-neutral-default);
}
header {
position: fixed;
top: 0;
width: 100%;
padding: var(--space-4);
}
.studio-content {
min-height: calc(100vh - 48px);
margin-top: 48px;
width: calc(100vw - 200px);
margin-left: 200px;
padding-right: var(--space-4);
&.project-sidebar {
width: calc(100vw - 52px);
margin-left: 52px;
}
}
</style>
-70
View File
@@ -1,70 +0,0 @@
import { Dependencies } from '$lib/constants';
import type { Plan } from '$lib/sdk/billing';
import type { Tier } from '$lib/stores/billing';
import { sdk } from '$lib/stores/sdk';
import { isCloud } from '$lib/system';
import type { LayoutLoad } from './$types';
import { Query } from '@appwrite.io/console';
export const load: LayoutLoad = async ({ params, fetch, depends, parent }) => {
await parent();
depends(Dependencies.RUNTIMES);
depends(Dependencies.CONSOLE_VARIABLES);
depends(Dependencies.ORGANIZATION);
const prefs = await sdk.forConsole.account.getPrefs();
const { endpoint, project } = sdk.forConsole.client.config;
const versionPromise = fetch(`${endpoint}/health/version`, {
headers: {
'X-Appwrite-Project': project
}
}).then((response) => response.json() as { version?: string });
const variablesPromise = sdk.forConsole.console.variables();
const [data, variables] = await Promise.all([versionPromise, variablesPromise]);
let plansInfo = new Map<Tier, Plan>();
if (isCloud) {
const plansArray = await sdk.forConsole.billing.getPlansInfo();
plansInfo = plansArray.plans.reduce((map, plan) => {
map.set(plan.$id as Tier, plan);
return map;
}, new Map<Tier, Plan>());
}
const organizations = !isCloud
? await sdk.forConsole.teams.list()
: await sdk.forConsole.billing.listOrganization();
let projects = [];
let currentOrgId = params.organization ? params.organization : prefs.organization;
if (!currentOrgId && organizations.teams.length > 0) {
currentOrgId = organizations.teams[0].$id;
}
if (currentOrgId) {
const orgProjects = await sdk.forConsole.projects.list([
Query.equal('teamId', currentOrgId),
Query.limit(100),
Query.orderDesc('$updatedAt')
]);
projects = orgProjects.projects.length > 0 ? orgProjects.projects : [];
}
const selectedOrganizationId =
params && params.organization ? params.organization : prefs.organization;
return {
consoleVariables: variables,
version: data?.version ?? null,
plansInfo,
roles: [],
scopes: [],
projects: projects,
currentProjectId: params.project ?? '',
organizations: organizations,
currentOrganization: organizations.teams.find((org) => org.$id === selectedOrganizationId)
};
};
@@ -1,30 +0,0 @@
<script lang="ts">
import { Card, Typography, Layout, Divider, Button } from '@appwrite.io/pink-svelte';
import { base } from '$app/paths';
export let data;
</script>
<Card.Base>
<Layout.Stack>
<Layout.Stack direction="row" justifyContent="space-between" alignItems="center">
<Typography.Text variant="m-500" color="--fgcolor-neutral-secondary"
>Projects</Typography.Text>
<Button.Button size="s">Create project</Button.Button>
</Layout.Stack>
<Divider />
<Layout.Grid gap="l" columnsS={3} columns={4} columnsL={4} columnsXL={5}>
{#each data.projects as project}
<a href={`${base}/proj-${project.$id}`}
><Card.Media
src="https://picsum.photos/260/150"
alt=""
description={project.description}
title={project.name}></Card.Media
></a>
{/each}
</Layout.Grid>
</Layout.Stack>
</Card.Base>
@@ -1,30 +0,0 @@
<script lang="ts">
import { Layout, Card, Typography, Divider } from '@appwrite.io/pink-svelte';
</script>
<Layout.Stack direction="row">
<section class="chat">
<Card.Base>
<Layout.Stack height="calc(100vh - 120px)">
<Typography.Text>Chat</Typography.Text>
<Divider />
<Typography.Text>
Phasellus a ultricies nulla. Proin tristique aliquam dui. Phasellus porta
sodales justo, et feugiat ligula pulvinar sed. Morbi et nulla hendrerit,
pellentesque est sodales, euismod magna. Suspendisse vel massa arcu. Nulla
ultrices sit amet metus ac euismod. Curabitur ultrices aliquet libero, a
pharetra orci tristique in. Etiam egestas fermentum justo ut semper. Sed
vulputate eros erat, ac sollicitudin nulla condimentum volutpat. Sed vulputate
est ante.
</Typography.Text>
</Layout.Stack>
</Card.Base>
</section>
<Card.Base>Iframe</Card.Base>
</Layout.Stack>
<style>
.chat {
width: 600px;
}
</style>
+2 -3
View File
@@ -41,9 +41,8 @@ export const load: PageLoad = async ({ parent, url }) => {
} else {
redirect(
303,
isStudio
? `${base}/org-${teamId}${url.search}`
: `${base}/organization-${teamId}${url.search}`
`${base}/organization-${teamId}${url.search}`
);
}
} else {