feat: template flows (WIP)

This commit is contained in:
Arman
2025-03-27 17:20:10 +01:00
parent 334af2fb79
commit 6fe23a8515
18 changed files with 217 additions and 68 deletions
+1 -1
View File
@@ -19,7 +19,7 @@
"e2e:ui": "playwright test tests/e2e --ui"
},
"dependencies": {
"@appwrite.io/console": "https://pkg.pr.new/appwrite/appwrite/@appwrite.io/console@8db247c",
"@appwrite.io/console": "https://pkg.pr.new/appwrite/appwrite/@appwrite.io/console@cfe6b8d",
"@appwrite.io/pink-icons": "0.25.0",
"@appwrite.io/pink-icons-svelte": "https://pkg.pr.new/appwrite/pink/@appwrite.io/pink-icons-svelte@4a5aab5",
"@appwrite.io/pink-legacy": "^1.0.3",
+5 -5
View File
@@ -9,8 +9,8 @@ importers:
.:
dependencies:
'@appwrite.io/console':
specifier: https://pkg.pr.new/appwrite/appwrite/@appwrite.io/console@8db247c
version: https://pkg.pr.new/appwrite/appwrite/@appwrite.io/console@8db247c
specifier: https://pkg.pr.new/appwrite/appwrite/@appwrite.io/console@cfe6b8d
version: https://pkg.pr.new/appwrite/appwrite/@appwrite.io/console@cfe6b8d
'@appwrite.io/pink-icons':
specifier: 0.25.0
version: 0.25.0
@@ -211,8 +211,8 @@ packages:
'@analytics/type-utils@0.6.2':
resolution: {integrity: sha512-TD+xbmsBLyYy/IxFimW/YL/9L2IEnM7/EoV9Aeh56U64Ify8o27HJcKjo38XY9Tcn0uOq1AX3thkKgvtWvwFQg==}
'@appwrite.io/console@https://pkg.pr.new/appwrite/appwrite/@appwrite.io/console@8db247c':
resolution: {tarball: https://pkg.pr.new/appwrite/appwrite/@appwrite.io/console@8db247c}
'@appwrite.io/console@https://pkg.pr.new/appwrite/appwrite/@appwrite.io/console@cfe6b8d':
resolution: {tarball: https://pkg.pr.new/appwrite/appwrite/@appwrite.io/console@cfe6b8d}
version: 1.2.1
'@appwrite.io/pink-icons-svelte@https://pkg.pr.new/appwrite/pink/@appwrite.io/pink-icons-svelte@4a5aab5':
@@ -4241,7 +4241,7 @@ snapshots:
'@analytics/type-utils@0.6.2': {}
'@appwrite.io/console@https://pkg.pr.new/appwrite/appwrite/@appwrite.io/console@8db247c': {}
'@appwrite.io/console@https://pkg.pr.new/appwrite/appwrite/@appwrite.io/console@cfe6b8d': {}
'@appwrite.io/pink-icons-svelte@https://pkg.pr.new/appwrite/pink/@appwrite.io/pink-icons-svelte@4a5aab5(svelte@4.2.19)':
dependencies:
+1 -1
View File
@@ -18,8 +18,8 @@
import { IconLockClosed } from '@appwrite.io/pink-icons-svelte';
import ConnectGit from './connectGit.svelte';
import SvgIcon from '../svgIcon.svelte';
import { getFrameworkIcon } from '$routes/(console)/project-[project]/sites/store';
import { VCSDetectionType, type Models } from '@appwrite.io/console';
import { getFrameworkIcon } from '$lib/stores/sites';
const dispatch = createEventDispatcher();
+1
View File
@@ -85,6 +85,7 @@ export const sdk = {
assistant: new Assistant(clientConsole),
billing: new Billing(clientConsole),
sources: new Sources(clientConsole),
sites: new Sites(clientConsole),
domains: new Domains(clientConsole)
},
get forProject() {
+35
View File
@@ -0,0 +1,35 @@
export function getFrameworkIcon(framework: string) {
switch (true) {
case framework.toLocaleLowerCase().includes('sveltekit'):
return 'svelte';
case framework.toLocaleLowerCase().includes('nuxt'):
return 'nuxt';
case framework.toLocaleLowerCase().includes('vue'):
return 'vue';
case framework.toLocaleLowerCase().includes('react'):
return 'react';
case framework.toLocaleLowerCase().includes('angular'):
return 'angular';
case framework.toLocaleLowerCase().includes('svelte'):
return 'svelte';
case framework.toLocaleLowerCase().includes('next'):
return 'nextjs';
case framework.toLocaleLowerCase().includes('astro'):
return 'astro';
case framework.toLocaleLowerCase().includes('remix'):
return 'remix';
case framework.toLocaleLowerCase().includes('flutter'):
return 'flutter';
case framework.toLocaleLowerCase().includes('analog'):
return 'analog';
case framework.toLocaleLowerCase().includes('vite'):
return 'vite';
case framework.toLocaleLowerCase().includes('lynx'):
return 'lynx';
case framework.toLocaleLowerCase().includes('other'):
return 'empty';
default:
return 'empty';
}
}
@@ -7,7 +7,7 @@ export const columns = writable<Column[]>([
id: 'requestPath',
title: 'Path',
type: 'string',
width: { min: 100, max: 200 },
width: { min: 200 },
format: 'string'
},
@@ -4,7 +4,7 @@
import { IconGithub, IconGitBranch } from '@appwrite.io/pink-icons-svelte';
import { consoleVariables } from '$routes/(console)/store';
import type { Models } from '@appwrite.io/console';
import { getFrameworkIcon } from '../store';
import { getFrameworkIcon } from '$lib/stores/sites';
export let framework: Partial<Models.Framework>;
export let repositoryName: string;
@@ -24,7 +24,6 @@
IconPencil
} from '@appwrite.io/pink-icons-svelte';
import type { Models } from '@appwrite.io/console';
import { getFrameworkIcon } from '../store';
import { iconPath } from '$lib/stores/app';
import VariableEditorModal from './variableEditorModal.svelte';
import SecretVariableModal from './secretVariableModal.svelte';
@@ -33,6 +32,7 @@
import DeleteVariableModal from './deleteVariableModal.svelte';
import UpdateVariableModal from './updateVariableModal.svelte';
import { Click, trackEvent } from '$lib/actions/analytics';
import { getFrameworkIcon } from '$lib/stores/sites';
export let frameworks: Models.Framework[];
export let selectedFramework: Models.Framework;
@@ -6,12 +6,12 @@
import Button from '$lib/elements/forms/button.svelte';
import Aside from '../aside.svelte';
import Logs from '../../(components)/logs.svelte';
import { getFrameworkIcon } from '../../store';
import { Copy, SvgIcon } from '$lib/components';
import { sdk } from '$lib/stores/sdk';
import { invalidate } from '$app/navigation';
import { onMount } from 'svelte';
import { Dependencies } from '$lib/constants';
import { getFrameworkIcon } from '$lib/stores/sites';
export let data;
@@ -9,7 +9,7 @@
import { Card, Layout, Accordion, Selector, Tooltip } from '@appwrite.io/pink-svelte';
import { capitalize } from '$lib/helpers/string';
import { app } from '$lib/stores/app.js';
import { getFrameworkIcon } from '../../store.js';
import { getFrameworkIcon } from '$lib/stores/sites.js';
export let data;
@@ -32,11 +32,11 @@
Repositories,
RepositoryBehaviour
} from '$lib/components/git';
import { getFrameworkIcon } from '../../../store';
import { app, iconPath } from '$lib/stores/app';
import { consoleVariables } from '$routes/(console)/store';
import { connectGitHub } from '$lib/stores/git';
import Domain from '../../domain.svelte';
import { getFrameworkIcon } from '$lib/stores/sites';
export let data;
@@ -4,7 +4,7 @@
import { timeFromNow } from '$lib/helpers/date';
import type { Models } from '@appwrite.io/console';
import { Card, Icon, Layout, Popover, Tooltip, Typography } from '@appwrite.io/pink-svelte';
import { generateSiteDeploymentDesc, getFrameworkIcon } from './store';
import { generateSiteDeploymentDesc } from './store';
import { SvgIcon } from '$lib/components';
import { app } from '$lib/stores/app';
import { getApiEndpoint } from '$lib/stores/sdk';
@@ -13,6 +13,7 @@
import { capitalize } from '$lib/helpers/string';
import { IconExclamation } from '@appwrite.io/pink-icons-svelte';
import { Link } from '$lib/elements';
import { getFrameworkIcon } from '$lib/stores/sites';
export let siteList: Models.SiteList;
@@ -7,7 +7,7 @@ export const columns = writable<Column[]>([
id: 'requestPath',
title: 'Path',
type: 'string',
width: { min: 500, max: 900 },
width: { min: 500 },
format: 'string'
},
@@ -9,10 +9,10 @@
import { Adapter, BuildRuntime, Framework, type Models } from '@appwrite.io/console';
import { Card, Fieldset, Icon, InlineCode, Layout, Tooltip } from '@appwrite.io/pink-svelte';
import { iconPath } from '$lib/stores/app';
import { getFrameworkIcon } from '../../store';
import { Link } from '$lib/elements';
import { IconInfo } from '@appwrite.io/pink-icons-svelte';
import { adapterDataList } from './store';
import { getFrameworkIcon } from '$lib/stores/sites';
export let site: Models.Site;
export let frameworks: Models.Framework[];
@@ -16,42 +16,6 @@ export const columns = writable<Column[]>([
{ id: '$createdAt', title: 'Created', type: 'datetime', width: { min: 120 } }
]);
export function getFrameworkIcon(framework: string) {
switch (true) {
case framework.toLocaleLowerCase().includes('sveltekit'):
return 'svelte';
case framework.toLocaleLowerCase().includes('nuxt'):
return 'nuxt';
case framework.toLocaleLowerCase().includes('vue'):
return 'vue';
case framework.toLocaleLowerCase().includes('react'):
return 'react';
case framework.toLocaleLowerCase().includes('angular'):
return 'angular';
case framework.toLocaleLowerCase().includes('svelte'):
return 'svelte';
case framework.toLocaleLowerCase().includes('next'):
return 'nextjs';
case framework.toLocaleLowerCase().includes('astro'):
return 'astro';
case framework.toLocaleLowerCase().includes('remix'):
return 'remix';
case framework.toLocaleLowerCase().includes('flutter'):
return 'flutter';
case framework.toLocaleLowerCase().includes('analog'):
return 'analog';
case framework.toLocaleLowerCase().includes('vite'):
return 'vite';
case framework.toLocaleLowerCase().includes('lynx'):
return 'lynx';
case framework.toLocaleLowerCase().includes('other'):
return 'empty';
default:
return 'empty';
}
}
export function generateSiteDeploymentDesc(site: Models.Site) {
if (site.latestDeploymentStatus === 'building') {
return `Deployment building ${timer(site.latestDeploymentCreatedAt)}`;
@@ -3,7 +3,7 @@
import { page } from '$app/stores';
import { timeFromNow, toLocaleDateTime } from '$lib/helpers/date';
import { Avatar, Icon, Layout, Popover, Table, Typography } from '@appwrite.io/pink-svelte';
import { columns, getFrameworkIcon } from './store';
import { columns } from './store';
import type { Models } from '@appwrite.io/console';
import SitesActionMenu from './sitesActionMenu.svelte';
import AddCollaboratorModal from './(components)/addCollaboratorModal.svelte';
@@ -13,6 +13,7 @@
import { Link } from '$lib/elements';
import { timer } from '$lib/helpers/timeConversion';
import DualTimeView from '$lib/components/dualTimeView.svelte';
import { getFrameworkIcon } from '$lib/stores/sites';
export let siteList: Models.SiteList;
@@ -1,16 +1,135 @@
<script lang="ts">
import { base } from '$app/paths';
import { SvgIcon } from '$lib/components/index.js';
import { Button, InputSelect } from '$lib/elements/forms';
import { app } from '$lib/stores/app';
import { loading } from '$routes/store';
import { Typography } from '@appwrite.io/pink-svelte';
import type { Organization } from '$lib/stores/organization';
import { sdk } from '$lib/stores/sdk';
import { getFrameworkIcon } from '$lib/stores/sites.js';
import { OAuthProvider, Query, type Models } from '@appwrite.io/console';
import { IconGithub } from '@appwrite.io/pink-icons-svelte';
import { Card, Divider, Icon, Image, Layout, Typography } from '@appwrite.io/pink-svelte';
import { onMount } from 'svelte';
loading.set(false);
export let data;
let projects: Models.ProjectList;
let selectedOrg: Organization = data?.organizations?.total
? data.organizations.teams[0]
: undefined;
onMount(async () => {
if (data.organizations) {
projects = await sdk.forConsole.projects.list([
Query.equal('teamId', selectedOrg.$id),
Query.orderDesc('')
]);
}
});
export function isSiteTemplate(
template: Models.TemplateFunction | Models.TemplateSite,
product: 'site' | 'function'
): template is Models.TemplateSite {
return product === 'site';
}
function onGithubLogin() {
sdk.forConsole.account.createOAuth2Session(
OAuthProvider.Github,
window.location.origin,
window.location.origin,
['read:user', 'user:email']
);
}
</script>
<svelte:head>
<title>Deploy {data.template.name} - Appwrite</title>
</svelte:head>
<div class="auth-bg">
<section>
<div class="console-container">
<slot />
</div>
<section class="console-container">
<Card.Base padding="s" radius="l">
<Layout.Stack gap="xl">
<Layout.Stack gap="l">
<Typography.Title size="l">
Deploy {data.product}
</Typography.Title>
<Card.Base variant="secondary" padding="xxxs" radius="s">
<Layout.GridFraction start={4} end={5}>
{#if isSiteTemplate(data.template, data.product)}
{@const framework = data.template.frameworks[0]}
<Image
border
radius="s"
ratio="16/9"
style=" align-self: start"
src={$app.themeInUse === 'dark'
? data.template.screenshotDark ||
`${base}/images/sites/screenshot-placeholder-dark.svg`
: data.template.screenshotLight ||
`${base}/images/sites/screenshot-placeholder-light.svg`}
alt="Screenshot" />
<Layout.Stack gap="xl">
<Layout.Stack gap="xxs">
<Typography.Text
variant="m-500"
color="--fgcolor-neutral-primary">
{data.template.name}
</Typography.Text>
<Typography.Text variant="m-500">
{data.template.tagline}
</Typography.Text>
</Layout.Stack>
{@const frameworkIcon = getFrameworkIcon(framework.key)}
<Layout.Stack gap="xxs" alignItems="center" direction="row">
{#if frameworkIcon}
<SvgIcon
iconSize="small"
size={16}
name={frameworkIcon} />
{/if}
<Typography.Text
variant="m-500"
color="--fgcolor-neutral-primary">
{framework.name}
</Typography.Text>
</Layout.Stack>
</Layout.Stack>
{/if}
</Layout.GridFraction>
</Card.Base>
{#if data.account}
<InputSelect
id="organization"
label="Organization"
placeholder="Select an organization"
options={data.organizations.teams.map((o) => ({
label: o.name,
value: o.$id
}))}
bind:value={selectedOrg.$id} />
<Divider />
<Button secondary fullWidth on:click={onGithubLogin}>
<Icon slot="start" icon={IconGithub} />
<span class="text">Sign in with GitHub</span>
</Button>
{:else}
<Typography.Text variant="m-500">
Sign in to deploy a site from GitHub.
</Typography.Text>
<Divider />
<Button secondary fullWidth on:click={onGithubLogin}>
<Icon slot="start" icon={IconGithub} />
<span class="text">Sign in with GitHub</span>
</Button>
{/if}
</Layout.Stack>
</Layout.Stack>
</Card.Base>
</section>
<footer>
<Typography.Eyebrow color="--fgcolor-neutral-secondary">POWERED BY</Typography.Eyebrow>
@@ -48,6 +167,8 @@
flex: 1;
display: flex;
align-items: center;
width: 100%;
max-width: 592px;
}
footer {
padding: 2rem 1rem;
@@ -1,14 +1,40 @@
import { base } from '$app/paths';
import { sdk } from '$lib/stores/sdk.js';
import { isCloud } from '$lib/system.js';
import type { Models } from '@appwrite.io/console';
import { redirect } from '@sveltejs/kit';
import { error } from '@sveltejs/kit';
export const load = async ({ parent, url, params }) => {
//TODO: enable before release
// if (!isCloud) redirect(303, '/');
export const load = async ({ parent, url }) => {
const { account } = await parent();
// const params = new URLSearchParams(window.location.search);
if (account) {
redirect(303, `${base}/templates-[template]?type=${url.searchParams.get('type')}`);
} else {
return {};
if (!url.searchParams.has('type')) {
error(404, 'Type is not optional');
}
const product = url.searchParams.get('type');
let template: Models.TemplateFunction | Models.TemplateSite;
switch (product) {
case 'function':
template = await sdk.forConsole.functions.getTemplate(params.template);
break;
case 'site':
template = await sdk.forConsole.sites.getTemplate(params.template);
break;
default:
error(404, 'Type is not valid');
}
if (account)
return {
account,
template: template,
product,
organizations: account?.$id
? await sdk.forConsole.billing.listOrganization()
: undefined
};
};