mirror of
https://github.com/appwrite/console.git
synced 2026-06-06 19:27:48 +00:00
Merge branch 'mgn-dv' of https://github.com/appwrite/console into mgn-dv
This commit is contained in:
@@ -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>
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user