Add terminal screen

This commit is contained in:
ernstmul
2025-04-03 13:55:19 +02:00
parent 093a98a109
commit 85c1b376da
6 changed files with 177 additions and 2 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))
@@ -1424,6 +1427,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:
@@ -4816,6 +4822,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
+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>
+2
View File
@@ -39,6 +39,7 @@
window.addEventListener('mousemove', resize);
window.addEventListener('mouseup', stopResize);
document.body.style.userSelect = 'none';
document.getElementById('preview-iframe').style.pointerEvents = 'none';
}
function resize(event) {
@@ -55,6 +56,7 @@
window.removeEventListener('mousemove', resize);
window.removeEventListener('mouseup', stopResize);
document.body.style.userSelect = '';
document.getElementById('preview-iframe').style.pointerEvents = '';
}
</script>
@@ -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>