feat: find projects command

This commit is contained in:
tglide
2023-07-11 12:49:42 +01:00
parent cf21609c4e
commit e6363ec4a6
10 changed files with 109 additions and 59 deletions
+2 -1
View File
@@ -6,6 +6,7 @@ import { derived, writable } from 'svelte/store';
export type CommandGroup =
| 'ungrouped'
| 'navigation'
| 'projects'
| 'project'
| 'auth'
| 'help'
@@ -16,7 +17,7 @@ export type CommandGroup =
| 'storage';
export type Command = {
keys: string[];
keys?: string[];
/* Ctrl on Windows/Linux, Meta on Mac */
ctrl?: boolean;
shift?: boolean;
+4 -4
View File
@@ -1,4 +1,4 @@
import AI from './ai.svelte';
import Root from './root.svelte';
import Template from './template.svelte';
export { AI, Root, Template };
export { default as AI } from './ai.svelte';
export { default as Root } from './root.svelte';
export { default as Template } from './template.svelte';
export { default as Projects } from './projects.svelte';
@@ -0,0 +1,28 @@
<!--
This is the root command panel. It precedes all other command panels.
-->
<script lang="ts">
import { page } from '$app/stores';
import type { Models } from '@appwrite.io/console';
import Template from './template.svelte';
import { goto } from '$app/navigation';
let search = '';
$: projects = $page.data.projects as Models.ProjectList;
$: filteredProjects = projects.projects
.filter((project) => project.name.toLowerCase().includes(search.toLowerCase()))
.map((project) => {
return {
label: project.name,
callback: () => {
goto(`/console/project-${project.$id}`);
},
group: 'projects'
} as const;
});
</script>
<Template options={filteredProjects} bind:search>
<div class="option" slot="option" let:option>{option.label}</div>
</Template>
+4 -12
View File
@@ -75,7 +75,7 @@
] as Array<Command | Omit<Command, 'keys'>>;
</script>
<Template options={results} bind:search>
<Template options={results} bind:search searchPlaceholder="Search for commands or content...">
<div slot="option" class="u-flex u-main-space-between content" let:option={command}>
<div class="u-flex u-gap-8 u-cross-center">
<i class="icon-{command.icon ?? 'arrow-sm-right'}" />
@@ -85,13 +85,13 @@
</div>
<div class="u-flex u-gap-4 u-cross-center">
{#if command.ctrl}
<kbd class="kbd"> {isMac() ? '⌘' : 'ctrl'} </kbd>
<kbd class="kbd"> {isMac() ? '⌘' : 'Ctrl'} </kbd>
{/if}
{#if command.shift}
<kbd class="kbd"> {isMac() ? '⇧' : 'shift'} </kbd>
<kbd class="kbd"> {isMac() ? '⇧' : 'Shift'} </kbd>
{/if}
{#if command.alt}
<kbd class="kbd"> {isMac() ? '⌥' : 'alt'} </kbd>
<kbd class="kbd"> {isMac() ? '⌥' : 'Alt'} </kbd>
{/if}
{#if 'keys' in command}
{#each command.keys as key, i}
@@ -108,12 +108,4 @@
</div>
</div>
<svelte:fragment slot="no-options">No commands found</svelte:fragment>
<div slot="footer" class="footer u-flex u-flex u-cross-center u-main-space-between">
<div class="u-flex u-cross-center u-gap-4">
<kbd class="kbd">Enter</kbd> <span>to select and close</span>
</div>
<div class="u-flex u-cross-center u-gap-4">
<kbd class="kbd">Esc</kbd> <span>to close</span>
</div>
</div>
</Template>
+36 -19
View File
@@ -17,9 +17,11 @@
type Option = $$Generic<BaseOption>;
export let options: Option[] | null = null;
export let search = '';
export let searchPlaceholder = 'Search...';
export let fullheight = false;
let selected = 0;
let contentEl: HTMLElement;
function triggerOption(option: Option) {
const prevPanels = $subPanels.length;
@@ -60,9 +62,22 @@
tick().then(() => {
if (!cardEl) return;
const selectedEl = cardEl.querySelector('[data-selected]');
if (selectedEl) {
const resultEls = Array.from(contentEl.querySelectorAll('.result'));
const selectedEl = contentEl.querySelector('[data-selected]');
const selectedIdx = resultEls.indexOf(selectedEl);
if (selectedIdx === 0) {
contentEl.scrollTo({
top: 0,
behavior: 'smooth'
});
} else if (selectedIdx === resultEls.length - 1) {
contentEl.scrollTo({
top: contentEl.scrollHeight,
behavior: 'smooth'
});
} else if (selectedEl) {
selectedEl.scrollIntoView({
block: 'nearest',
behavior: 'smooth'
@@ -83,7 +98,7 @@
}
const [send, receive] = crossfade({
duration: 150,
duration: 100,
easing: quadOut
});
@@ -173,16 +188,13 @@
<slot name="search">
<div class="u-flex default-search u-width-full-line">
<input
type="text"
placeholder="Search for commands..."
autofocus
bind:value={search} />
<!-- svelte-ignore a11y-autofocus -->
<input type="text" placeholder={searchPlaceholder} autofocus bind:value={search} />
</div>
</slot>
</div>
<div class="content">
<div class="content" bind:this={contentEl}>
{#if groupsAndOptions}
<ul class="options">
{#each groupsAndOptions as item}
@@ -222,7 +234,17 @@
</div>
<div class="footer">
<slot name="footer" />
<slot name="footer">
<div class=" u-flex u-flex u-cross-center u-main-space-between">
<div class="u-flex u-cross-center u-gap-4">
<kbd class="kbd">Enter</kbd> <span>to select</span>
</div>
<div class="u-flex u-cross-center u-gap-4">
<kbd class="kbd">Esc</kbd>
<span>to {$subPanels.length > 1 ? 'go back' : 'close'}</span>
</div>
</div>
</slot>
</div>
</div>
@@ -347,23 +369,18 @@
.group {
color: hsl(var(--color-neutral-70));
margin-inline-start: 0.25rem;
margin-block-start: 1rem;
margin-block-end: 0.25rem;
position: relative;
z-index: 10;
&:not(:first-child) {
margin-block-start: 1rem;
}
}
.result {
transition: 150ms;
position: relative;
opacity: 0.65;
transition: 75ms cubic-bezier(0.5, 1, 0.89, 1);
&[data-selected] {
opacity: 1;
transition: 150ms cubic-bezier(0.5, 1, 0.89, 1);
}
.bg {
position: absolute;
inset: 0;
+3 -5
View File
@@ -17,9 +17,9 @@
import { goto } from '$app/navigation';
import { addSubPanel } from '$lib/commandCenter/subPanels';
import { registerCommands, CommandCenter } from '$lib/commandCenter';
import { CommandCenter, registerCommands } from '$lib/commandCenter';
import { AI } from '$lib/commandCenter/panels';
import { addSubPanel } from '$lib/commandCenter/subPanels';
$: $registerCommands([
{
@@ -38,9 +38,7 @@
callback: () => {
goto('/console/account');
},
keys: ['a'],
ctrl: true,
shift: true,
keys: ['g', 'i'],
group: 'navigation'
},
{
+5 -1
View File
@@ -1,4 +1,5 @@
import { sdk } from '$lib/stores/sdk';
import type { LayoutLoad } from './$types';
export const load: LayoutLoad = async () => {
@@ -6,6 +7,9 @@ export const load: LayoutLoad = async () => {
const data = await response.json();
return {
version: data?.version ?? null
version: data?.version ?? null,
promised: {
// We nest these so that layout doesn't have to wait for these promises to load
}
};
};
@@ -1,8 +1,8 @@
<script lang="ts">
import { goto } from '$app/navigation';
import { base } from '$app/paths';
import { page } from '$app/stores';
import { type Command, registerCommands } from '$lib/commandCenter';
import { addSubPanel, registerCommands } from '$lib/commandCenter';
import { Projects } from '$lib/commandCenter/panels';
import { CardContainer, Empty, GridItem1, Heading, PaginationWithLimit } from '$lib/components';
import { Pill } from '$lib/elements';
import { Button } from '$lib/elements/forms';
@@ -48,14 +48,17 @@
}
$: $registerCommands([
...data.projects.projects.map<Command>((project, i) => ({
label: `Project ${project.name}`,
{
label: 'Find a project',
callback: () => {
goto(`/console/project-${project.$id}`);
addSubPanel({
name: 'Projects',
component: Projects
});
},
keys: ['p', `${i + 1}`],
group: 'project'
})),
group: 'project',
icon: 'search'
},
{
label: 'Create project',
callback: () => {
@@ -58,6 +58,14 @@
},
keys: ['g', 's'],
group: 'navigation'
},
{
label: 'Go to Settings',
callback: () => {
goto(`/console/project-${$project.$id}/settings`);
},
keys: ['g', 'p'],
group: 'navigation'
}
]);
</script>
@@ -36,7 +36,6 @@
import { toLocaleDateTime } from '$lib/helpers/date';
import { app } from '$lib/stores/app';
import type { PageData } from './$types';
import { registerCommands } from '$lib/commandCenter';
export let data: PageData;
@@ -72,14 +71,14 @@
}
};
$: $registerCommands([
{
label: 'Create Web App',
callback: () => addPlatform(Platform.Web),
keys: ['c'],
icon: 'plus'
}
]);
// $: $registerCommands([
// {
// label: 'Create Web App',
// callback: () => addPlatform(Platform.Web),
// keys: ['c'],
// icon: 'plus'
// }
// ]);
</script>
<div class="common-section u-flex u-gap-12">