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

This commit is contained in:
Torsten Dittmann
2025-04-03 14:09:14 +02:00
10 changed files with 307 additions and 18 deletions
+1
View File
@@ -29,6 +29,7 @@
"@popperjs/core": "^2.11.8",
"@sentry/sveltekit": "^8.38.0",
"@stripe/stripe-js": "^3.5.0",
"@xterm/xterm": "^5.5.0",
"ai": "^2.2.37",
"analytics": "^0.8.16",
"cron-parser": "^4.9.0",
+8
View File
@@ -32,6 +32,9 @@ importers:
'@stripe/stripe-js':
specifier: ^3.5.0
version: 3.5.0
'@xterm/xterm':
specifier: ^5.5.0
version: 5.5.0
ai:
specifier: ^2.2.37
version: 2.2.37(react@18.3.1)(solid-js@1.9.5)(svelte@5.25.3)(vue@3.5.13(typescript@5.8.2))
@@ -1439,6 +1442,9 @@ packages:
'@vue/shared@3.5.13':
resolution: {integrity: sha512-/hnE/qP5ZoGpol0a5mDi45bOd7t3tjYJBjsgCsivow7D48cJeV5l05RD82lPqi7gRiphZM37rnhW1l6ZoCNNnQ==}
'@xterm/xterm@5.5.0':
resolution: {integrity: sha512-hqJHYaQb5OptNunnyAnkHyM8aCjZ1MEIDTQu1iIbbTD/xops91NB5yq1ZK/dC2JDbVWtF23zUtl9JE2NqwT87A==}
acorn-import-attributes@1.9.5:
resolution: {integrity: sha512-n02Vykv5uA3eHGM/Z2dQrcD56kL8TyDb2p1+0P83PClMnC/nc+anbQRhIOWnSq4Ke/KvDPrY3C9hDtC/A3eHnQ==}
peerDependencies:
@@ -5007,6 +5013,8 @@ snapshots:
'@vue/shared@3.5.13': {}
'@xterm/xterm@5.5.0': {}
acorn-import-attributes@1.9.5(acorn@8.14.1):
dependencies:
acorn: 8.14.1
+6 -12
View File
@@ -3,9 +3,10 @@
import { IconArrowUp, IconChevronLeft, IconPaperClip } from '@appwrite.io/pink-icons-svelte';
export let showChat;
export let width;
</script>
<section class="chat" class:is-visible={showChat}>
<section class="chat" style:width={showChat ? `${width}px` : 0} class:is-visible={showChat}>
<Card.Base>
<div class="chat-content">
<Layout.Stack height="calc(100vh - 120px)" justifyContent="space-between">
@@ -49,7 +50,8 @@
</div>
</Card.Base>
</section>
<div class="chat-placeholder" class:is-visible={showChat}></div>
<div class="chat-placeholder" class:is-visible={showChat} style:width={showChat ? `${width}px` : 0}>
</div>
<style lang="scss">
.chat {
@@ -57,19 +59,11 @@
position: fixed;
top: 48px;
overflow: hidden;
transition: width 0.3s ease-in-out;
&.is-visible {
width: 400px;
}
//transition: width 0.1s ease-in-out;
}
.chat-placeholder {
width: 0;
transition: width 0.3s ease-in-out;
&.is-visible {
width: 600px;
}
flex-shrink: 0;
}
.chat-content {
+1
View File
@@ -82,3 +82,4 @@ 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';
export { default as Terminal } from './studio/terminal.svelte';
+28
View File
@@ -0,0 +1,28 @@
<script>
// const xtermPkg = require('@xterm/xterm');
// const { Terminal } = xtermPkg;
// import { onMount } from 'svelte';
// import '@xterm/xterm/css/xterm.css';
//
// let terminalContainer;
export let height;
//
// onMount(() => {
// const term = new Terminal();
// term.open(terminalContainer);
// term.write('Hello from xterm.js!\r\n');
// });
</script>
<div class="terminal-container" style:height={`${height}px`}>
<!-- <div bind:this={terminalContainer}></div>-->
</div>
<style>
.terminal-container {
padding: var(--space-3);
border-radius: var(--border-radius-xsmall);
background-color: black;
overflow-y: scroll;
}
</style>
+73 -2
View File
@@ -28,6 +28,36 @@
});
$: showChat = !showChat ? $page.url.pathname.endsWith('builder') : showChat;
let resizer;
let resizerLeftPosition = 500;
let chatWidth = resizerLeftPosition - 52;
let isResizing = false;
function startResize(event) {
isResizing = true;
window.addEventListener('mousemove', resize);
window.addEventListener('mouseup', stopResize);
document.body.style.userSelect = 'none';
document.getElementById('preview-iframe').style.pointerEvents = 'none';
}
function resize(event) {
if (!isResizing) return;
if (resizer) {
resizerLeftPosition = event.clientX;
chatWidth = event.clientX - 52;
}
}
function stopResize() {
isResizing = false;
window.removeEventListener('mousemove', resize);
window.removeEventListener('mouseup', stopResize);
document.body.style.userSelect = '';
document.getElementById('preview-iframe').style.pointerEvents = '';
}
</script>
<main>
@@ -39,9 +69,17 @@
</Layout.Stack>
</header>
<div class="studio-content" class:project-sidebar={hasProjectSidebar}>
<Layout.Stack direction="row">
<Layout.Stack direction="row" gap="l">
{#if hasProjectSidebar}
<Chat bind:showChat />
<Chat bind:showChat bind:width={chatWidth} />
{#if showChat}
<div
class="resizer"
style:left={`${resizerLeftPosition}px`}
bind:this={resizer}
on:mousedown={startResize}>
</div>
{/if}
{/if}
<Card.Base>
@@ -87,4 +125,37 @@
margin-left: 52px;
}
}
.resizer {
width: 7px;
margin-inline: 10px;
position: absolute;
height: calc(100vh - 82px);
margin-block-start: 6px;
cursor: col-resize;
margin-inline-start: 8px;
&::before {
content: '';
position: absolute;
height: 100%;
width: 1px;
background-color: var(--border-neutral);
}
&::after {
content: '';
position: absolute;
height: 100%;
width: 2px;
margin-left: -1px;
background-color: var(--border-neutral-strong);
opacity: 0;
transition: opacity 0.3s ease-in-out;
}
&:hover::after {
opacity: 1;
}
}
</style>
+39
View File
@@ -0,0 +1,39 @@
type Profile = {
hasChatLayout: boolean;
hasAuth: boolean;
hasDatabases: boolean;
hasFunctions: boolean;
hasMessages: boolean;
hasStorage: boolean;
hasSites: boolean;
};
export const ConsoleCloudProfile: Profile = {
hasChatLayout: false,
hasAuth: true,
hasDatabases: true,
hasFunctions: true,
hasMessages: true,
hasStorage: true,
hasSites: true
};
export const ConsoleSelfhostedProfile: Profile = {
hasChatLayout: false,
hasAuth: true,
hasDatabases: true,
hasFunctions: true,
hasMessages: true,
hasStorage: true,
hasSites: true
};
export const StudioProfile: Profile = {
hasChatLayout: true,
hasAuth: true,
hasDatabases: true,
hasFunctions: false,
hasMessages: true,
hasStorage: true,
hasSites: true
};
+12
View File
@@ -1,5 +1,6 @@
import { env } from '$env/dynamic/public';
import { dev } from '$app/environment';
import { ConsoleCloudProfile, ConsoleSelfhostedProfile, StudioProfile } from '$lib/profiles';
export const enum Mode {
CLOUD = 'cloud',
@@ -26,6 +27,16 @@ export const ENV = {
TEST: !!import.meta.env?.VITEST
};
function getProfile() {
if (PROFILE === Profile.STUDIO) {
return StudioProfile;
} else if (MODE === Mode.CLOUD) {
return ConsoleCloudProfile;
} else {
return ConsoleSelfhostedProfile;
}
}
export const MODE = VARS.CONSOLE_MODE === Mode.CLOUD ? Mode.CLOUD : Mode.SELF_HOSTED;
export const PROFILE = VARS.PROJECT_PROFILE === Profile.CONSOLE ? Profile.CONSOLE : Profile.STUDIO;
export const isCloud = MODE === Mode.CLOUD;
@@ -35,3 +46,4 @@ export const isDev = ENV.DEV;
export const isProd = ENV.PROD;
export const hasStripePublicKey = !!VARS.PUBLIC_STRIPE_KEY;
export const GRACE_PERIOD_OVERRIDE = false;
export const consoleProfile = getProfile();
+2 -2
View File
@@ -33,7 +33,7 @@
import { openMigrationWizard } from './(migration-wizard)';
import { project } from './project-[project]/store';
import { feedback } from '$lib/stores/feedback';
import { hasStripePublicKey, isCloud, isStudio, VARS } from '$lib/system';
import { consoleProfile, hasStripePublicKey, isCloud, isStudio, VARS } from '$lib/system';
import { stripe } from '$lib/stores/stripe';
import MobileSupportModal from './wizard/support/mobileSupportModal.svelte';
import { showSupportModal } from './wizard/support/store';
@@ -330,7 +330,7 @@
</script>
<CommandCenter />
{#if isStudio}
{#if consoleProfile.hasChatLayout}
<ShellStudio bind:loadedProjects><slot /></ShellStudio>
{:else}
<Shell
@@ -1,5 +1,140 @@
<script lang="ts">
import { Layout, Card, Typography, Divider } from '@appwrite.io/pink-svelte';
import { Layout, Card, Typography, Divider, Icon } from '@appwrite.io/pink-svelte';
import { isTabSelected } from '$lib/helpers/load';
import { page } from '$app/state';
import { Tab, Tabs, Terminal } from '$lib/components';
import { base } from '$app/paths';
import {
IconChevronDoubleUp,
IconDeviceMobile,
IconExternalLink,
IconRefresh,
IconTerminal
} from '@appwrite.io/pink-icons-svelte';
import { InputText } from '$lib/elements/forms/index.js';
const frameUrl = 'https://app.posthog.com/embedded/fEjuk1e61iQjYiRjODPDjpbsyhIfpg';
const projectId = page.params.project;
const path = `${base}/project-${projectId}/builder`;
const tabs = [
{
href: path,
title: 'Preview',
event: 'preview',
hasChildren: true
},
{
href: `${path}/code`,
title: 'Code',
event: 'code',
hasChildren: true
}
];
let terminalOpen;
let asideRef;
let isResizing = false;
let terminalHeight = 300;
function startResize(event) {
isResizing = true;
window.addEventListener('mousemove', resize);
window.addEventListener('mouseup', stopResize);
document.body.style.userSelect = 'none';
document.getElementById('preview-iframe').style.pointerEvents = 'none';
}
function resize(event) {
if (!isResizing) return;
terminalHeight =
asideRef.getBoundingClientRect().y -
event.clientY +
asideRef.getBoundingClientRect().height -
32;
}
function stopResize() {
isResizing = false;
window.removeEventListener('mousemove', resize);
window.removeEventListener('mouseup', stopResize);
document.body.style.userSelect = '';
document.getElementById('preview-iframe').style.pointerEvents = '';
}
</script>
<Layout.Stack direction="row">Iframe</Layout.Stack>
<Layout.Stack direction="column" height="calc(100vh - 120px)" gap="none">
<Layout.Stack direction="column">
<Tabs>
{#each tabs as tab}
<Tab
href={tab.href}
selected={isTabSelected(tab, page.url.pathname, path, tabs)}
event={tab.event}>
{tab.title}
</Tab>
{/each}
</Tabs>
<Divider />
<Layout.Stack direction="row" alignItems="center">
<Icon icon={IconRefresh} color="--fgcolor-neutral-tertiary" />
<InputText id="previewUrl" value={frameUrl} />
<Icon icon={IconDeviceMobile} color="--fgcolor-neutral-tertiary" />
<Icon icon={IconExternalLink} color="--fgcolor-neutral-tertiary" />
</Layout.Stack>
<div></div>
</Layout.Stack>
<div class="iframe-container">
<iframe src={frameUrl} id="preview-iframe"></iframe>
</div>
<div></div>
{#if terminalOpen}<div class="terminal-slider" on:mousedown={startResize}></div>
{/if}
<aside bind:this={asideRef}>
<details bind:open={terminalOpen}>
<summary>
<Layout.Stack direction="row" justify="space-between">
<Layout.Stack direction="row" alignItems="center" gap="xs">
<Icon icon={IconTerminal} color="--fgcolor-neutral-tertiary" />
<Typography.Text>Terminal</Typography.Text>
</Layout.Stack>
<Icon icon={IconChevronDoubleUp} color="--fgcolor-neutral-tertiary" />
</Layout.Stack>
</summary>
<Terminal bind:height={terminalHeight}></Terminal>
</details>
</aside>
</Layout.Stack>
<style>
aside {
background-color: var(--bgcolor-neutral-default);
width: calc(100% + 2 * var(--space-9));
margin-inline-start: calc(-1 * var(--space-9));
margin-block-end: calc(-1 * var(--space-9));
padding: var(--space-3);
border-bottom-left-radius: var(--border-radius-m);
border-bottom-right-radius: var(--border-radius-m);
}
summary {
cursor: pointer;
}
.iframe-container {
flex-grow: 1;
}
iframe {
border: none;
width: calc(100% + 2 * var(--space-9));
height: 100%;
margin-inline-start: calc(-1 * var(--space-9));
}
.terminal-slider {
height: 4px;
background-color: var(--bgcolor-neutral-default);
width: calc(100% + 2 * var(--space-9));
margin-inline-start: calc(-1 * var(--space-9));
cursor: row-resize;
}
</style>