Merge pull request #897 from appwrite/fix-command-center

chore: add missing commands
This commit is contained in:
Torsten Dittmann
2024-03-06 12:13:57 +01:00
committed by GitHub
18 changed files with 385 additions and 83 deletions
+25 -4
View File
@@ -5,6 +5,7 @@ import { onMount } from 'svelte';
import { derived, writable } from 'svelte/store';
import { nanoid } from 'nanoid/non-secure';
import { trackEvent } from '$lib/actions/analytics';
import { omit } from '$lib/helpers/omit';
const groups = [
'ungrouped',
@@ -18,6 +19,9 @@ const groups = [
'databases',
'functions',
'messaging',
'messages',
'providers',
'topics',
'storage',
'domains',
'webhooks',
@@ -45,6 +49,7 @@ type BaseCommand = {
forceEnable?: boolean;
group?: CommandGroup;
icon?: string;
image?: string;
rank?: number;
nested?: boolean;
keepOpen?: boolean;
@@ -70,7 +75,23 @@ export const disabledMap = writable<Map<string, boolean>>(new Map());
// Derived stores
export const commands = derived(commandMap, ($commandMap) => {
return Array.from($commandMap.values()).flat();
const res: Command[] = [];
const keys = new Set<string>();
const allCommands = Array.from($commandMap.values()).flat().toReversed();
for (const command of allCommands) {
if (isKeyedCommand(command) && !command.disabled) {
const keysString = command.keys.join('+');
if (keys.has(keysString)) {
res.push(omit(command, 'keys'));
continue;
}
keys.add(keysString);
}
res.push(command);
}
return res;
});
const commandsEnabled = derived(disabledMap, ($disabledMap) => {
@@ -110,9 +131,9 @@ function hasDisputing(command: KeyedCommand, allCommands: Command[]) {
}
export const commandCenterKeyDownHandler = derived(
[commandMap, commandsEnabled, wizard],
([$commandMap, enabled, $wizard]) => {
const commandsArr = Array.from($commandMap.values()).flat();
[commands, commandsEnabled, wizard],
([$commands, enabled, $wizard]) => {
const commandsArr = $commands;
let recentKeyCodes: number[] = [];
let validCommands: KeyedCommand[] = [];
@@ -0,0 +1,73 @@
<script lang="ts">
import { providers } from '$routes/console/project-[project]/messaging/providers/store';
import {
messageParams,
providerType,
targetsById
} from '$routes/console/project-[project]/messaging/wizard/store';
import { MessagingProviderType } from '@appwrite.io/console';
import Template from './template.svelte';
import { wizard } from '$lib/stores/wizard';
import Create from '$routes/console/project-[project]/messaging/create.svelte';
import { topicsById } from '$routes/console/project-[project]/messaging/store';
let search = '';
let options = Object.entries(providers).map(([type, option]) => {
return {
label: option.name,
icon: option.icon,
callback() {
if (
type !== MessagingProviderType.Email &&
type !== MessagingProviderType.Sms &&
type !== MessagingProviderType.Push
)
return;
$providerType = type;
$topicsById = {};
$targetsById = {};
const common = {
topics: [],
users: [],
targets: []
};
switch (type) {
case MessagingProviderType.Email:
$messageParams[$providerType] = {
...common,
subject: '',
content: ''
};
break;
case MessagingProviderType.Sms:
$messageParams[$providerType] = {
...common,
content: ''
};
break;
case MessagingProviderType.Push:
$messageParams[$providerType] = {
...common,
title: '',
body: '',
data: [['', '']]
};
break;
}
wizard.start(Create);
}
};
});
$: filteredOptions = options.filter((option) => {
return option.label.toLowerCase().includes(search.toLowerCase());
});
</script>
<Template options={filteredOptions} bind:search>
<div class="u-flex u-cross-center u-gap-8" slot="option" let:option>
<i class="icon-{option.icon}" />
<span>{option.label}</span>
</div>
</Template>
+6
View File
@@ -78,3 +78,9 @@ export const FilesPanel: SubPanel = {
name: 'Files',
component: Files
};
import CreateMessage from './createMessage.svelte';
export const CreateMessagePanel: SubPanel = {
name: 'Create Message',
component: CreateMessage
};
+9 -1
View File
@@ -2,6 +2,8 @@
This is the root command panel. It precedes all other command panels.
-->
<script lang="ts">
import { base } from '$app/paths';
import { app } from '$lib/stores/app';
import { debounce } from '$lib/helpers/debounce';
import { isMac } from '$lib/helpers/platform';
import { commands, searchers, type Command, isKeyedCommand } from '../commands';
@@ -54,7 +56,13 @@
<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'}" />
{#if command.image}
<img
src={`${base}/icons/${$app.themeInUse}/color/${command.image}.svg`}
alt={command.label} />
{:else}
<i class="icon-{command.icon ?? 'arrow-sm-right'}" />
{/if}
<span>
{command.label}
</span>
@@ -0,0 +1,52 @@
import { goto } from '$app/navigation';
import { project } from '$routes/console/project-[project]/store';
import { get } from 'svelte/store';
import { type Searcher } from '../commands';
import { sdk } from '$lib/stores/sdk';
import { MessagingProviderType } from '@appwrite.io/console';
const getLabel = (message) => {
switch (message.providerType) {
case MessagingProviderType.Push:
return message.data.title;
case MessagingProviderType.Sms:
return message.data.content;
case MessagingProviderType.Email:
return message.data.subject;
default:
return 'null';
}
};
const getIcon = (message) => {
switch (message.providerType) {
case MessagingProviderType.Push:
return 'device-mobile';
case MessagingProviderType.Sms:
return 'annotation';
case MessagingProviderType.Email:
return 'mail';
default:
return 'send';
}
};
export const messagesSearcher = (async (query: string) => {
const { messages } = await sdk.forProject.messaging.listMessages([], query || undefined);
const projectId = get(project).$id;
return messages
.filter((message) => getLabel(message).toLowerCase().includes(query.toLowerCase()))
.map(
(message) =>
({
group: 'messages',
label: getLabel(message),
callback: () => {
goto(`/console/project-${projectId}/messaging/message-${message.$id}`);
},
icon: getIcon(message)
}) as const
);
}) satisfies Searcher;
@@ -0,0 +1,33 @@
import { goto } from '$app/navigation';
import { project } from '$routes/console/project-[project]/store';
import { get } from 'svelte/store';
import type { Searcher } from '../commands';
import { sdk } from '$lib/stores/sdk';
import { getProviderDisplayNameAndIcon } from '$routes/console/project-[project]/messaging/provider.svelte';
const getIcon = (provider: string) => {
const { icon } = getProviderDisplayNameAndIcon(provider);
return icon;
};
export const providersSearcher = (async (query: string) => {
const { providers } = await sdk.forProject.messaging.listProviders([], query || undefined);
const projectId = get(project).$id;
return providers
.filter((provider) => provider.name.toLowerCase().includes(query.toLowerCase()))
.map(
(provider) =>
({
group: 'providers',
label: provider.name,
callback: () => {
goto(
`/console/project-${projectId}/messaging/providers/provider-${provider.$id}`
);
},
image: getIcon(provider.provider)
}) as const
);
}) satisfies Searcher;
+25
View File
@@ -0,0 +1,25 @@
import { goto } from '$app/navigation';
import { project } from '$routes/console/project-[project]/store';
import { get } from 'svelte/store';
import type { Searcher } from '../commands';
import { sdk } from '$lib/stores/sdk';
export const topicsSearcher = (async (query: string) => {
const { topics } = await sdk.forProject.messaging.listTopics([], query || undefined);
const projectId = get(project).$id;
return topics
.filter((topic) => topic.name.toLowerCase().includes(query.toLowerCase()))
.map(
(topic) =>
({
group: 'topics',
label: topic.name,
callback: () => {
goto(`/console/project-${projectId}/messaging/topics/topic-${topic.$id}`);
},
icon: 'send'
}) as const
);
}) satisfies Searcher;
+7
View File
@@ -0,0 +1,7 @@
export function omit<T, K extends keyof T>(obj: T, ...keys: K[]): Omit<T, K> {
const ret = { ...obj };
for (const key of keys) {
delete ret[key];
}
return ret;
}
-21
View File
@@ -1,29 +1,8 @@
<script>
import { addSubPanel, registerCommands } from '$lib/commandCenter';
import { TeamsPanel, UsersPanel } from '$lib/commandCenter/panels';
import { Container } from '$lib/layout';
import { loading } from '../store';
loading.set(false);
$registerCommands([
{
label: 'Find users',
callback: () => {
addSubPanel(UsersPanel);
},
group: 'users',
rank: -1
},
{
label: 'Find teams',
callback: () => {
addSubPanel(TeamsPanel);
},
group: 'teams',
rank: -1
}
]);
</script>
<Container>
+1 -10
View File
@@ -56,7 +56,7 @@
$: $registerCommands([
{
label: 'Go to projects',
label: 'Go to Projects',
callback: () => {
goto('/console');
},
@@ -77,15 +77,6 @@
icon: 'sparkles',
disabled: !isAssistantEnabled
},
{
label: 'Go to account',
callback: () => {
goto('/console/account');
},
keys: ['i'],
group: 'navigation',
rank: -2
},
{
label: 'Create new organization',
callback: () => {
@@ -15,6 +15,7 @@
userSearcher
} from '$lib/commandCenter/searchers';
import { MigrationBox } from '$lib/components';
import { page } from '$app/stores';
onMount(() => {
return sdk.forConsole.client.subscribe(['project', 'console'], (response) => {
@@ -27,20 +28,20 @@
});
$: $registerCommands([
{
label: 'Go to Databases',
callback: () => {
goto(`/console/project-${$project.$id}/databases`);
},
keys: ['d'],
group: 'navigation'
},
{
label: 'Go to Auth',
callback: () => {
goto(`/console/project-${$project.$id}/auth`);
},
keys: ['a'],
keys: ['g', 'a'],
group: 'navigation'
},
{
label: 'Go to Databases',
callback: () => {
goto(`/console/project-${$project.$id}/databases`);
},
keys: ['g', 'd'],
group: 'navigation'
},
{
@@ -48,7 +49,16 @@
callback: () => {
goto(`/console/project-${$project.$id}/functions`);
},
keys: ['f'],
keys: ['g', 'f'],
group: 'navigation'
},
{
label: 'Go to Messaging',
callback: () => {
goto(`/console/project-${$project.$id}/messaging`);
},
keys: ['g', 'm'],
disabled: $page.url.pathname.endsWith('messaging'),
group: 'navigation'
},
{
@@ -56,23 +66,23 @@
callback: () => {
goto(`/console/project-${$project.$id}/storage`);
},
keys: ['s'],
keys: ['g', 's'],
group: 'navigation'
},
{
label: 'Go to settings',
label: 'Go to Settings',
callback: () => {
goto(`/console/project-${$project.$id}/settings`);
},
keys: ['p'],
keys: ['g', 'e'],
group: 'navigation'
},
{
label: 'Go to overview',
label: 'Go to Overview',
callback: () => {
goto(`/console/project-${$project.$id}`);
},
keys: ['o'],
keys: ['g', 'o'],
group: 'navigation'
}
]);
@@ -1,7 +1,8 @@
<script lang="ts">
import { goto } from '$app/navigation';
import { page } from '$app/stores';
import { registerCommands, updateCommandGroupRanks } from '$lib/commandCenter';
import { addSubPanel, registerCommands, updateCommandGroupRanks } from '$lib/commandCenter';
import { TeamsPanel, UsersPanel } from '$lib/commandCenter/panels';
import { readOnly } from '$lib/stores/billing';
import { GRACE_PERIOD_OVERRIDE } from '$lib/system';
import { project } from '../store';
@@ -77,6 +78,22 @@
group: 'navigation',
rank: 1,
disabled: $page.url.pathname.endsWith('settings')
},
{
label: 'Find users',
callback: () => {
addSubPanel(UsersPanel);
},
group: 'users',
rank: -1
},
{
label: 'Find teams',
callback: () => {
addSubPanel(TeamsPanel);
},
group: 'teams',
rank: -1
}
]);
@@ -136,6 +136,45 @@
disabled: $page.url.pathname.endsWith('settings'),
group: 'collections'
},
{
label: 'Display Name',
async callback() {
await goto(
`/console/project-${$project.$id}/databases/database-${$database.$id}/collection-${$collection.$id}/settings#display-name`
);
},
group: 'collections',
disabled:
$page.url.pathname.endsWith('display-name') ||
$page.url.pathname.endsWith('settings'),
icon: 'eye'
},
{
label: 'Permissions',
async callback() {
await goto(
`/console/project-${$project.$id}/databases/database-${$database.$id}/collection-${$collection.$id}/settings#permissions`
);
},
group: 'collections',
disabled:
$page.url.pathname.endsWith('permissions') ||
$page.url.pathname.endsWith('settings'),
icon: 'puzzle'
},
{
label: 'Document security',
async callback() {
await goto(
`/console/project-${$project.$id}/databases/database-${$database.$id}/collection-${$collection.$id}/settings#document-security`
);
},
group: 'collections',
disabled:
$page.url.pathname.endsWith('document-security') ||
$page.url.pathname.endsWith('settings'),
icon: 'lock-closed'
},
{
label: 'Create index',
keys: $page.url.pathname.endsWith('indexes') ? ['c'] : ['c', 'i'],
@@ -55,7 +55,7 @@
<Form onSubmit={updateDisplayName}>
<CardGrid>
<Heading tag="h6" size="7">Display name</Heading>
<Heading tag="h6" size="7" id="display-name">Display name</Heading>
<p class="text">
Select string attributes as display names for your documents. The selected names will be
used as short forms to identify documents in the Appwrite console, like when creating
@@ -56,7 +56,7 @@
</script>
<CardGrid>
<Heading tag="h6" size="7">Permissions</Heading>
<Heading tag="h6" size="7" id="permissions">Permissions</Heading>
<p class="text">
Choose who can access your collection and documents. For more about <a
href="https://appwrite.io/docs/products/databases/permissions"
@@ -45,7 +45,7 @@
</script>
<CardGrid>
<Heading tag="h6" size="7">Document security</Heading>
<Heading tag="h6" size="7" id="document-security">Document security</Heading>
<svelte:fragment slot="aside">
<FormList>
<InputSwitch
@@ -1,58 +1,55 @@
<script lang="ts">
import { goto } from '$app/navigation';
import { page } from '$app/stores';
import { registerCommands, updateCommandGroupRanks } from '$lib/commandCenter';
import {
addSubPanel,
registerCommands,
registerSearchers,
updateCommandGroupRanks
} from '$lib/commandCenter';
import { CreateMessagePanel } from '$lib/commandCenter/panels';
import { messagesSearcher } from '$lib/commandCenter/searchers/messages';
import { providersSearcher } from '$lib/commandCenter/searchers/providers';
import { topicsSearcher } from '$lib/commandCenter/searchers/topics';
import { project } from '../store';
import { showCreate } from './store';
// TODO: finalize the commands
$: $registerCommands([
{
label: 'Create message',
callback: () => {
if (!$page.url.pathname.endsWith('messaging')) {
goto(`/console/project-${$project.$id}/messaging`);
}
$showCreate = true;
},
keys: $page.url.pathname.endsWith('messaging') ? ['c'] : ['c', 'm'],
callback() {
addSubPanel(CreateMessagePanel);
},
icon: 'plus',
group: 'messaging'
},
{
label: 'Go to topics',
label: 'Go to Topics',
keys: ['g', 't'],
callback() {
goto(`/console/project-${$project.$id}/messaging/topics`);
},
keys: ['g', 't'],
disabled:
$page.url.pathname.endsWith('topics') || $page.url.pathname.includes('message-'),
group: 'navigation',
rank: 10
group: 'messaging',
rank: 1
},
{
label: 'Go to providers',
label: 'Go to Providers',
keys: ['g', 'p'],
callback() {
goto(`/console/project-${$project.$id}/messaging/providers`);
},
keys: ['g', 'p'],
disabled:
$page.url.pathname.endsWith('topics') || $page.url.pathname.includes('message-'),
group: 'navigation',
rank: 10
$page.url.pathname.endsWith('providers') || $page.url.pathname.includes('message-'),
group: 'messaging',
rank: 2
}
// {
// label: 'Find messages',
// callback: () => {
// addSubPanel(BucketsPanel);
// },
// group: 'messaging',
// rank: -1
// }
]);
$: $updateCommandGroupRanks({ messaging: 200, navigation: 100 });
$registerSearchers(messagesSearcher, providersSearcher, topicsSearcher);
$: $updateCommandGroupRanks({ messaging: 400, providers: 300, topics: 200, navigation: 100 });
</script>
<svelte:head>
@@ -1,3 +1,47 @@
<script lang="ts">
import { goto } from '$app/navigation';
import { page } from '$app/stores';
import { registerCommands } from '$lib/commandCenter';
import { project } from '$routes/console/project-[project]/store';
const topicId = $page.params.topic;
$: $registerCommands([
{
label: 'Go to Subscribers',
callback() {
goto(
`/console/project-${$project.$id}/messaging/topics/topic-${topicId}/subscribers`
);
},
keys: ['g', 's'],
disabled: $page.url.pathname.endsWith('subscribers'),
group: 'topics',
rank: 1
},
{
label: 'Go to Activity',
callback() {
goto(`/console/project-${$project.$id}/messaging/topics/topic-${topicId}/activity`);
},
keys: ['g', 'a'],
disabled: $page.url.pathname.endsWith('activity'),
group: 'topics',
rank: 1
},
{
label: 'Go to Overview',
callback() {
goto(`/console/project-${$project.$id}/messaging/topics/topic-${topicId}`);
},
keys: ['g', 'o'],
disabled: $page.url.pathname.endsWith(`topic-${topicId}`),
group: 'topics',
rank: 1
}
]);
</script>
<svelte:head>
<title>Topic - Appwrite</title>
</svelte:head>