feat: user searcher

This commit is contained in:
tglide
2023-07-20 14:36:59 +01:00
parent b9c4393c31
commit 7c03f05615
7 changed files with 168 additions and 95 deletions
+72 -32
View File
@@ -1,24 +1,28 @@
import { debounce } from '$lib/helpers/debounce';
import { isMac } from '$lib/helpers/platform';
import { onMount } from 'svelte';
import { derived, writable, type Updater } from 'svelte/store';
// Stores
export type CommandGroup =
| 'ungrouped'
| 'navigation'
| 'projects'
| 'organizations'
| 'auth'
| 'help'
| 'account'
| 'platforms'
| 'databases'
| 'functions'
| 'storage'
| 'domains'
| 'webhooks'
| 'integrations'
| 'migrations';
const groups = [
'ungrouped',
'navigation',
'projects',
'organizations',
'auth',
'help',
'account',
'platforms',
'databases',
'functions',
'storage',
'domains',
'webhooks',
'integrations',
'migrations',
'users'
] as const;
export type CommandGroup = (typeof groups)[number];
type BaseCommand = {
callback: () => void;
@@ -251,24 +255,60 @@ export const updateCommandGroupRanks = {
};
export const commandGroupRanks = derived(groupRankTransformations, ($groupRankTransformations) => {
const initialRanks: CommandGroupRanks = {
const initialRanks = {
...Object.fromEntries(groups.map((group) => [group, 0])),
ungrouped: 9999,
domains: 0,
webhooks: 0,
navigation: 1,
projects: 0,
account: 0,
organizations: 0,
auth: 0,
platforms: 0,
databases: 0,
functions: 0,
storage: 0,
integrations: 0,
migrations: 0,
help: -1
};
} as CommandGroupRanks;
const transformations = Array.from($groupRankTransformations.values());
return transformations.reduce((prev, curr) => curr(prev), initialRanks);
});
export type Searcher = (query: string) => Promise<Command[]>;
const searchersMap = writable<Map<string, Searcher>>(new Map());
export const registerSearcher = {
subscribe(runner: (cb: (searcher: Searcher) => void) => void) {
const uuid = crypto.randomUUID();
runner((searcher: Searcher) => {
searchersMap.update((curr) => {
curr.set(uuid, searcher);
return curr;
});
});
return () => {
searchersMap.update((curr) => {
curr.delete(uuid);
return curr;
});
};
}
};
export const searchers = derived(searchersMap, ($searchersMap) => {
return Array.from($searchersMap.values());
});
export const useSearcher = (searcher: Searcher) => {
const search = writable('');
const results = writable<Command[]>([]);
const searcherDebounced = debounce(async (query: string) => {
results.set(await searcher(query));
}, 500);
onMount(() => {
searcherDebounced.immediate('');
return search.subscribe((query) => {
searcherDebounced(query);
});
});
return {
search,
results
};
};
+7 -46
View File
@@ -2,58 +2,19 @@
This is the root command panel. It precedes all other command panels.
-->
<script lang="ts">
import { commands, type Command } from '../commands';
import { isMac } from '$lib/helpers/platform';
import Template from './template.svelte';
import { sdk } from '$lib/stores/sdk';
import { Query } from '@appwrite.io/console';
import { goto } from '$app/navigation';
import { project } from '$routes/console/project-[project]/store';
import { database } from '$routes/console/project-[project]/databases/database-[database]/store';
import { debounce } from '$lib/helpers/debounce';
import { isMac } from '$lib/helpers/platform';
import { commands, searchers, type Command } from '../commands';
import Template from './template.svelte';
let search = '';
let searchResults: Omit<Command, 'keys'>[] = [];
const executeSearch = debounce(async (s: string) => {
const newResults: typeof searchResults = [];
const { databases } = await sdk.forProject.databases.list([Query.limit(20)]);
newResults.push(
...databases
.filter((db) => db.name.includes(s))
.map((db) => {
return {
label: `Go to ${db.name} database`,
callback: () => {
goto(`/console/project-${$project.$id}/databases/database-${db.$id}`);
},
group: 'databases'
} as const;
})
);
if ($database?.$id) {
const { collections } = await sdk.forProject.databases.listCollections($database.$id, [
Query.limit(20)
]);
newResults.push(
...collections
.filter((collection) => collection.name.includes(s))
.map((collection) => {
return {
label: `Go to ${collection.name} collection`,
callback: () => {
goto(
`/console/project-${$project.$id}/databases/database-${$database.$id}/collection-${collection.$id}`
);
},
group: 'databases'
} as const;
})
);
}
searchResults = [...newResults];
$searchers.forEach(async (searcher) => {
const results = await searcher(s);
searchResults = [...searchResults, ...results];
});
}, 500);
$: {
@@ -0,0 +1,22 @@
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 dbSearcher = (async (query: string) => {
const { databases } = await sdk.forProject.databases.list();
return databases
.filter((db) => db.name.toLowerCase().includes(query.toLowerCase()))
.map(
(db) =>
({
group: 'databases',
label: db.name,
callback: () => {
goto(`/console/project-${get(project).$id}/databases/database-${db.$id}`);
}
} as const)
);
}) satisfies Searcher;
+2
View File
@@ -0,0 +1,2 @@
export * from './databases';
export * from './users';
+49
View File
@@ -0,0 +1,49 @@
import { goto } from '$app/navigation';
import { sdk } from '$lib/stores/sdk';
import { project } from '$routes/console/project-[project]/store';
import { get } from 'svelte/store';
import type { Command, Searcher } from '../commands';
import type { Models } from '@appwrite.io/console';
const getUserCommand = (user: Models.User<Models.Preferences>, projectId: string) =>
({
label: user.name,
callback: () => {
goto(`/console/project-${projectId}/auth/user-${user.$id}`);
},
group: 'users',
icon: 'user-circle'
} satisfies Command);
export const userSearcher = (async (query: string) => {
const { users } = await sdk.forProject.users.list([], query);
const projectId = get(project).$id;
if (users.length === 1) {
return [
getUserCommand(users[0], projectId),
{
label: 'Go to activity',
callback: () => {
goto(`/console/project-${projectId}/auth/user-${users[0].$id}/activity`);
},
group: 'users'
},
{
label: 'Go to sessions',
callback: () => {
goto(`/console/project-${projectId}/auth/user-${users[0].$id}/sessions`);
},
group: 'users'
},
{
label: 'Go to memberships',
callback: () => {
goto(`/console/project-${projectId}/auth/user-${users[0].$id}/memberships`);
},
group: 'users'
}
];
}
return users.map((user) => getUserCommand(user, projectId));
}) satisfies Searcher;
@@ -5,9 +5,10 @@
import { project, stats } from './store';
import { goto } from '$app/navigation';
import { registerCommands } from '$lib/commandCenter';
import { registerCommands, registerSearcher } from '$lib/commandCenter';
import { MigrationBox } from '$lib/components';
import { dbSearcher, userSearcher } from '$lib/commandCenter/searchers';
onMount(async () => {
return sdk.forConsole.client.subscribe(['project', 'console'], (response) => {
@@ -70,6 +71,9 @@
group: 'navigation'
}
]);
$registerSearcher(dbSearcher);
$registerSearcher(userSearcher);
</script>
<slot />
@@ -1,10 +1,5 @@
<script lang="ts">
import { invalidate } from '$app/navigation';
import {
PUBLIC_MOCK_APIKEY,
PUBLIC_MOCK_ENDPOINT,
PUBLIC_MOCK_PROJECTID
} from '$env/static/public';
import { registerCommands, updateCommandGroupRanks } from '$lib/commandCenter';
import { Arrow, CardGrid, Heading } from '$lib/components';
import { Dependencies } from '$lib/constants';
@@ -116,23 +111,23 @@
undefined
);
// const migrationData = {
// endpoint: currEndpoint,
// projectId: $project.$id,
// apiKey: secret
// };
const migrationData = {
endpoint: PUBLIC_MOCK_ENDPOINT,
projectId: PUBLIC_MOCK_PROJECTID,
apiKey: PUBLIC_MOCK_APIKEY
endpoint: currEndpoint,
projectId: $project.$id,
apiKey: secret
};
window.location.href = `http://localhost:3000/?migrate=${encodeURIComponent(
window.location.href = `https://cloud.appwrite.io/?migrate=${encodeURIComponent(
JSON.stringify(migrationData)
)}`;
// window.location.href = `https://cloud.appwrite.io/?migrate=${encodeURIComponent(
// const migrationData = {
// endpoint: PUBLIC_MOCK_ENDPOINT,
// projectId: PUBLIC_MOCK_PROJECTID,
// apiKey: PUBLIC_MOCK_APIKEY
// };
// window.location.href = `http://localhost:3000/?migrate=${encodeURIComponent(
// JSON.stringify(migrationData)
// )}`;
};