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

This commit is contained in:
Torsten Dittmann
2025-05-13 16:11:41 +02:00
6 changed files with 258 additions and 25 deletions
+212
View File
@@ -0,0 +1,212 @@
<script lang="ts">
import { createMenubar, melt } from '@melt-ui/svelte';
import { Badge, Icon, type SheetMenu, ActionMenu, Card } from '@appwrite.io/pink-svelte';
import {
IconChevronDown,
IconChevronRight,
IconPlus,
IconPlusSm
} from '@appwrite.io/pink-icons-svelte';
import { BottomSheet } from '$lib/components';
import { isSmallViewport } from '$lib/stores/viewport';
import { isCloud, isStudio } from '$lib/system';
import { goto } from '$app/navigation';
import { base } from '$app/paths';
import { newOrgModal } from '$lib/stores/organization';
import { Click, trackEvent } from '$lib/actions/analytics';
import { page } from '$app/stores';
import type { Artifact } from '$lib/sdk/imagine';
import type { SubMenu } from '$lib/components/bottom-sheet';
type Item = {
name: string;
isActive: boolean;
onClick?: () => void;
href?: string;
icon?: ComponentType;
};
const {
elements: { menubar },
builders: { createMenu }
} = createMenubar({ closeOnItemClick: true });
const {
elements: { trigger: triggerItems, menu: menuItems }
} = createMenu();
let { items = [] }: { items: Item[] } = $props();
let selectedItem = $derived.by(() => {
return items.find((item) => item.isActive);
});
let bottomSheetOpen = $state(false);
let bottomSheetOptions: SubMenu = $derived.by(() => {
return {
top: {
items: items.map((item) => ({
name: item.name,
href: item.href,
onClick: item.onClick,
leadingIcon: item.icon
}))
},
bottom: undefined
};
});
function onResize() {
if (bottomSheetOpen && !$isSmallViewport) {
bottomSheetOpen = false;
}
}
</script>
<svelte:window on:resize={onResize} />
<div use:melt={$menubar} data-align="center">
{#if !$isSmallViewport}
<button
type="button"
class="trigger-wrapper"
use:melt={$triggerItems}
aria-label="Open tab">
<div class="trigger-content">
<span class="orgName">{selectedItem?.name ?? 'Artifact'}</span>
<Icon icon={IconChevronDown} size="s" color="--fgcolor-neutral-secondary" />
</div>
</button>
{:else}
<button
type="button"
class="trigger-wrapper"
on:click={() => {
bottomSheetOpen = true;
}}
aria-label="Open tab">
<span class="orgName" class:noProjects={!selectedItem}
>{selectedItem?.name ?? 'Artifact'}</span>
<Icon icon={IconChevronDown} size="s" color="--fgcolor-neutral-secondary" />
</button>
{/if}
<div class="menu" use:melt={$menuItems}>
<Card.Base padding="xxxs" shadow={true}>
{#each items as item}
<ActionMenu.Root>
{#if item.href}
<ActionMenu.Item.Anchor href={item.href}
>{item.name}</ActionMenu.Item.Anchor>
{:else if item.onClick}
<ActionMenu.Item.Button on:click={item.onClick} leadingIcon={item.icon}
>{item.name}</ActionMenu.Item.Button>
{/if}</ActionMenu.Root>
{/each}
</Card.Base>
</div>
</div>
<BottomSheet.Menu bind:isOpen={bottomSheetOpen} menu={bottomSheetOptions}></BottomSheet.Menu>
<style lang="scss">
.menu {
min-width: 244px;
z-index: 20;
}
.trigger-content {
display: flex;
align-items: center;
gap: var(--gap-s);
padding-inline: var(--space-4);
border-radius: var(--border-radius-xs);
flex-direction: row;
&:hover {
background: var(--overlay-neutral-hover, rgba(25, 25, 28, 0.03));
}
}
.not-mobile {
display: none;
@media (min-width: 768px) {
display: block;
}
}
.subMenu {
min-width: 244px;
margin-inline: -4px;
margin-block: -4px;
}
.orgName,
.projectName {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
color: var(--fgcolor-neutral-secondary);
@media (min-width: 390px) {
max-width: 95px;
}
@media (min-width: 400px) {
max-width: 105px;
}
@media (min-width: 800px) {
max-width: 125px;
}
@media (min-width: 1024px) {
max-width: 150px;
}
}
.noProjects {
max-width: 150px;
}
.trigger-wrapper {
min-width: 244px;
display: inline-flex;
align-items: center;
justify-content: center;
padding: var(--space-1, 2px) var(--space-2, 4px) var(--space-1, 2px) var(--space-3, 6px);
gap: var(--space-2, 4px);
margin: 0 var(--space-5, 10px) 0 var(--space-5, 10px);
transition: color 0.2s ease;
color: var(--fgcolor-neutral-primary, #2d2d31);
border-radius: var(--corner-radius-medium, 8px);
cursor: pointer;
/* Body text/level 2 Regular */
font-family: Inter;
font-size: 14px;
font-style: normal;
font-weight: 400;
line-height: 150%; /* 21px */
}
.trigger:focus-visible {
z-index: 30;
box-shadow:
var(--shadow-offsetx-0, 0px) var(--shadow-offsety-0, 0px) 0 2px
var(--bgcolor-neutral-default, #fafafb),
0 0 0 4px var(--border-focus, #818186);
}
.separator {
height: 1px;
margin-block: 2px;
margin-inline-start: calc(var(--base-4) * -1);
width: calc(100% + var(--base-8));
background-color: var(--border-neutral);
}
.breadcrumb-separator {
color: var(--fgcolor-neutral-tertiary, #97979b);
}
</style>
-12
View File
@@ -62,20 +62,8 @@
}
} = createMenu();
const {
elements: {
trigger: triggerArtifacts,
menu: menuArtifacts,
item: itemArtifacts,
separator: separatorArtifacts
}
} = createMenu();
export let organizations: Organization[] = [];
$: displayArtifacts = (
$page.url.pathname.includes('artifact') && isStudio ? $page.data.artifacts.artifacts : []
) as Artifact[];
$: selectedOrg = organizations.find((organization) => organization.isSelected);
$: selectedProject = selectedOrg?.projects.find((project) => project.isSelected);
+1
View File
@@ -83,3 +83,4 @@ export { default as Confirm } from './confirm.svelte';
export { default as UsageCard } from './usageCard.svelte';
export { default as ViewToggle } from './viewToggle.svelte';
export { default as Terminal } from './studio/terminal.svelte';
export { default as ActionDropdown } from './actionDropdown.svelte';
+13
View File
@@ -0,0 +1,13 @@
import { sdk } from '$lib/stores/sdk';
import { ID } from '@appwrite.io/console';
import { goto, invalidate } from '$app/navigation';
import { base } from '$app/paths';
import { page } from '$app/state';
import { Dependencies } from '$lib/constants';
export async function createArtifact() {
const artifact = await sdk.forProject.imagine.create(ID.unique());
await goto(`${base}/project-${page.params.project}/studio/artifact-${artifact.$id}`);
invalidate(Dependencies.ARTIFACTS);
}
@@ -8,15 +8,9 @@
import { ID } from '@appwrite.io/console';
import { goto, invalidate } from '$app/navigation';
import { Dependencies } from '$lib/constants';
import { createArtifact } from '$lib/helpers/artifact.js';
const { data } = $props();
async function createArtifact() {
const artifact = await sdk.forProject.imagine.create(ID.unique());
await goto(`${base}/project-${page.params.project}/studio/artifact-${artifact.$id}`);
invalidate(Dependencies.ARTIFACTS);
}
</script>
<Layout.Stack>
@@ -9,7 +9,7 @@
} from '@appwrite.io/pink-svelte';
import { isTabSelected } from '$lib/helpers/load';
import { page } from '$app/state';
import { Tab, Tabs, Terminal } from '$lib/components';
import { ActionDropdown, Tab, Tabs, Terminal } from '$lib/components';
import { base } from '$app/paths';
import { isSmallViewport } from '$lib/stores/viewport';
import {
@@ -19,7 +19,7 @@
IconChevronDown,
IconPlusSm,
IconTerminal,
IconWifi
IconPlus
} from '@appwrite.io/pink-icons-svelte';
import { previewFrameRef } from '$routes/(console)/project-[project]/store';
import {
@@ -42,6 +42,7 @@
import { filesystem } from '$lib/components/editor/filesystem';
import { InputSelect } from '$lib/elements/forms/index.js';
import { SvelteMap } from 'svelte/reactivity';
import { createArtifact } from '$lib/helpers/artifact';
const { children } = $props();
@@ -149,8 +150,34 @@
terminals.set(symbol, createSynapse(endpoint, page.params.artifact));
currentTerminal = symbol;
}
let artifacts = $derived.by(() => {
const mappedArtifacts = page.data.artifacts?.artifacts
? page.data.artifacts.artifacts.map((artifact) => {
return {
name: artifact.name,
isActive: page.params.artifact === artifact.$id,
href: `${base}/project-${page.params.project}/studio/artifact-${artifact.$id}`
};
})
: [];
mappedArtifacts.push({
name: 'Create artifact',
isActive: false,
onClick: createArtifact,
icon: IconPlus
});
return mappedArtifacts;
});
</script>
{#snippet artifactSelector()}
{#if page.data.artifacts?.artifacts}
<ActionDropdown items={artifacts} />
{/if}
{/snippet}
<Layout.Stack
direction="column"
height={$isSmallViewport ? 'calc(100vh - 154px)' : 'calc(100vh - 88px)'}
@@ -159,8 +186,7 @@
{#if $isSmallViewport}
<Layout.Stack direction="row" gap="none" justifyContent="center">
<div>
<ActionMenu.Item.Button trailingIcon={IconChevronDown}
>Dashboard</ActionMenu.Item.Button>
{@render artifactSelector()}
</div>
</Layout.Stack>
<div class="divider-wrapper-artifacts">
@@ -189,8 +215,7 @@
</Tabs>
</Layout.Stack>
{#if !$isSmallViewport}
<ActionMenu.Item.Button trailingIcon={IconChevronDown}
>Dashboard</ActionMenu.Item.Button>
{@render artifactSelector()}
{/if}
<Layout.Stack gap="s" direction="row" alignItems="center" inline>