storage commands

This commit is contained in:
tglide
2023-07-25 12:15:04 +01:00
parent 92d3bafd19
commit 0b21fe415e
23 changed files with 309 additions and 67 deletions
+2 -5
View File
@@ -24,7 +24,6 @@
<script lang="ts">
import { dev } from '$app/environment';
import { afterNavigate } from '$app/navigation';
import { portal } from '$lib/actions/portal';
import { last } from '$lib/helpers/array';
import { debounce } from '$lib/helpers/debounce';
@@ -61,10 +60,6 @@
}
}
afterNavigate(() => {
clearSubPanels();
});
const ctx = setCommandCenterCtx({
isInitialPanel: true,
open: false
@@ -95,6 +90,8 @@
}
$commandCenterKeyDownHandler(e);
};
$: console.log($subPanels);
</script>
<svelte:window on:mousedown={handleBlur} on:keydown={handleKeydown} />
+5 -2
View File
@@ -25,7 +25,9 @@ const groups = [
'indexes',
'documents',
'teams',
'security'
'security',
'buckets',
'files'
] as const;
export type CommandGroup = (typeof groups)[number];
@@ -39,6 +41,7 @@ type BaseCommand = {
icon?: string;
rank?: number;
nested?: boolean;
keepOpen?: boolean;
};
type KeyedCommand = BaseCommand & {
@@ -152,7 +155,7 @@ export const commandCenterKeyDownHandler = derived(
const rankAndExecute = debounce(() => {
const command = getHighestPriorityCommand();
command.callback();
command?.callback();
reset.immediate();
}, 200);
@@ -0,0 +1,9 @@
<script lang="ts">
import { initSearcher } from '../commands';
import { bucketSearcher } from '../searchers';
import Template from './template.svelte';
const { search, results } = initSearcher(bucketSearcher);
</script>
<Template options={$results} bind:search={$search} />
@@ -0,0 +1,9 @@
<script lang="ts">
import { initSearcher } from '../commands';
import { fileSearcher } from '../searchers';
import Template from './template.svelte';
const { search, results } = initSearcher(fileSearcher);
</script>
<Template options={$results} bind:search={$search} />
+12
View File
@@ -66,3 +66,15 @@ export const FunctionsPanel: SubPanel = {
name: 'Functions',
component: Functions
};
import Buckets from './buckets.svelte';
export const BucketsPanel: SubPanel = {
name: 'Buckets',
component: Buckets
};
import Files from './files.svelte';
export const FilesPanel: SubPanel = {
name: 'Files',
component: Files
};
+8 -20
View File
@@ -1,5 +1,5 @@
<script lang="ts">
import { commandGroupRanks, type CommandGroup } from '../commands';
import { commandGroupRanks, type CommandGroup, type Command } from '../commands';
// This is the template for all panels used in the command center.
// Use this component when you want to create a new panel.
@@ -10,18 +10,7 @@
import { clearSubPanels, popSubPanel, subPanels } from '../subPanels';
import { quadOut } from 'svelte/easing';
import { crossfade } from 'svelte/transition';
type BaseOption = {
callback: () => void;
group?: string;
rank?: number;
nested?: boolean;
icon?: string;
label?: string;
};
type Option = $$Generic<BaseOption>;
type Option = $$Generic<Command>;
export let options: Option[] | null = null;
export let search = '';
export let searchPlaceholder = 'Search...';
@@ -31,10 +20,10 @@
let selected = 0;
let contentEl: HTMLElement;
function triggerOption(option: Option) {
async function triggerOption(option: Option) {
const prevPanels = $subPanels.length;
option.callback();
if (prevPanels === $subPanels.length && clearOnCallback) {
if (prevPanels === $subPanels.length && clearOnCallback && !option.keepOpen) {
clearSubPanels();
}
}
@@ -106,11 +95,6 @@
selected = 0;
}
const [send, receive] = crossfade({
duration: 100,
easing: quadOut
});
const commandCenterCtx = getCommandCenterCtx();
let cardEl: HTMLElement;
@@ -237,10 +221,14 @@
class:scale-up={$commandCenterCtx.isInitialPanel && $commandCenterCtx.open}>
<div class="search-wrapper">
{#each breadcrumbs as crumb, i}
{@const isLast = i === breadcrumbs.length - 1}
<button class="crumb" on:click={() => handleCrumbClick(i)}>
<span>{crumb}</span>
<i class="icon-x" />
</button>
{#if !isLast}
<span style="opacity: 50%">/</span>
{/if}
{/each}
<slot name="search">
@@ -0,0 +1,81 @@
import { goto } from '$app/navigation';
import { sdk } from '$lib/stores/sdk';
import { project } from '$routes/console/project-[project]/store';
import { Query, type Models } from '@appwrite.io/console';
import { get } from 'svelte/store';
import type { Command, Searcher } from '../commands';
import { addSubPanel } from '../subPanels';
import { FilesPanel } from '../panels';
const getBucketCommand = (bucket: Models.Bucket, projectId: string) => {
return {
label: `${bucket.name}`,
callback() {
goto(`/console/project-${projectId}/storage/bucket-${bucket.$id}`);
},
group: 'buckets',
icon: 'folder'
} satisfies Command;
};
export const bucketSearcher = (async (query: string) => {
const { buckets } = await sdk.forProject.storage.listBuckets([Query.orderDesc('$createdAt')]);
const $project = get(project);
const filtered = buckets.filter((bucket) => bucket.name.includes(query));
if (filtered.length === 1) {
const bucket = filtered[0];
return [
getBucketCommand(bucket, $project.$id),
{
label: 'Find files',
async callback() {
await goto(`/console/project-${$project.$id}/storage/bucket-${bucket.$id}`);
addSubPanel(FilesPanel);
},
group: 'buckets',
nested: true,
icon: 'search',
keepOpen: true
},
{
label: 'Permissions',
async callback() {
await goto(
`/console/project-${$project.$id}/storage/bucket-${bucket.$id}/settings#permissions`
);
scrollBy({ top: -100 });
},
group: 'buckets',
nested: true,
icon: 'key'
},
{
label: 'Extensions',
async callback() {
await goto(
`/console/project-${$project.$id}/storage/bucket-${bucket.$id}/settings#extensions`
);
},
group: 'buckets',
nested: true,
icon: 'puzzle'
},
{
label: 'File Security',
async callback() {
await goto(
`/console/project-${$project.$id}/storage/bucket-${bucket.$id}/settings#file-security`
);
scrollBy({ top: -100 });
},
group: 'buckets',
nested: true,
icon: 'lock-closed'
}
];
}
return filtered.map((bucket) => getBucketCommand(bucket, $project.$id));
}) satisfies Searcher;
+27
View File
@@ -0,0 +1,27 @@
import { sdk } from '$lib/stores/sdk';
import { get } from 'svelte/store';
import type { Searcher } from '../commands';
import { bucket } from '$routes/console/project-[project]/storage/bucket-[bucket]/store';
import { Query } from '@appwrite.io/console';
import { goto } from '$app/navigation';
import { project } from '$routes/console/project-[project]/store';
export const fileSearcher = (async (query: string) => {
const $bucket = get(bucket);
const $project = get(project);
const { files } = await sdk.forProject.storage.listFiles(
$bucket.$id,
[Query.orderDesc('$createdAt')],
query || undefined
);
return files.map((file) => ({
label: file.name,
callback: () => {
goto(`/console/project-${$project.$id}/storage/bucket-${$bucket.$id}/file-${file.$id}`);
},
icon: 'document',
group: 'files'
}));
}) satisfies Searcher;
+2
View File
@@ -5,3 +5,5 @@ export * from './collections';
export * from './projects';
export * from './teams';
export * from './functions';
export * from './buckets';
export * from './files';
+1 -1
View File
@@ -17,7 +17,7 @@ const getUserCommand = (user: Models.User<Models.Preferences>, projectId: string
} satisfies Command);
export const userSearcher = (async (query: string) => {
const { users } = await sdk.forProject.users.list([], query);
const { users } = await sdk.forProject.users.list([], query || undefined);
const projectId = get(project).$id;
if (users.length === 1) {
@@ -13,6 +13,7 @@
} from '$lib/commandCenter';
import {
bucketSearcher,
dbSearcher,
functionsSearcher,
teamSearcher,
@@ -23,7 +24,8 @@
UsersPanel,
TeamsPanel,
DatabasesPanel,
FunctionsPanel
FunctionsPanel,
BucketsPanel
} from '$lib/commandCenter/panels';
onMount(async () => {
@@ -122,12 +124,19 @@
group: 'functions',
icon: 'search',
keys: ['f', 'f']
},
{
label: 'Find Buckets',
callback: () => {
addSubPanel(BucketsPanel);
},
group: 'buckets',
icon: 'search',
keys: ['f', 'b']
}
]);
$registerSearchers(userSearcher, teamSearcher, dbSearcher, functionsSearcher);
$updateCommandGroupRanks({ functions: 10 });
$registerSearchers(userSearcher, teamSearcher, dbSearcher, functionsSearcher, bucketSearcher);
</script>
<slot />
@@ -123,7 +123,7 @@
]);
// To prioritize the groups!
$: $updateCommandGroupRanks({ users: 300, teams: 200, security: 100 });
$: $updateCommandGroupRanks({ users: 300, teams: 200, security: 100, navigation: 50 });
</script>
<svelte:head>
@@ -17,7 +17,7 @@
}
]);
$: $updateCommandGroupRanks({ databases: 100 });
$: $updateCommandGroupRanks({ databases: 200, navigation: 100 });
</script>
<svelte:head>
@@ -85,7 +85,7 @@
$registerSearchers(collectionsSearcher);
$: $updateCommandGroupRanks((p) => ({ ...p, collections: 10 }));
$: $updateCommandGroupRanks({ collections: 10 });
</script>
<svelte:head>
@@ -147,13 +147,12 @@
}
]);
$updateCommandGroupRanks((p) => ({
...p,
$updateCommandGroupRanks({
attributes: 1000,
documents: 900,
collections: 800,
indexes: 700
}));
});
</script>
<svelte:head>
@@ -80,10 +80,9 @@
}
]);
$: $updateCommandGroupRanks((prev) => ({
...prev,
$: $updateCommandGroupRanks({
integrations: 10
}));
});
</script>
<svelte:head>
@@ -71,11 +71,10 @@
}
]);
$: $updateCommandGroupRanks((prev) => ({
...prev,
$updateCommandGroupRanks({
domains: 20,
webhooks: 10
}));
});
</script>
<svelte:head>
@@ -13,8 +13,6 @@
} from '$lib/elements/table';
import Table from '$lib/elements/table/table.svelte';
import { isSameDay, toLocaleDate } from '$lib/helpers/date';
import { parseIfString } from '$lib/helpers/object';
import { capitalize } from '$lib/helpers/string';
import { Container } from '$lib/layout';
import { sdk } from '$lib/stores/sdk';
import { isSelfHosted } from '$lib/system';
@@ -71,10 +69,7 @@
}
]);
$: $updateCommandGroupRanks((prev) => ({
...prev,
migrations: 100
}));
$updateCommandGroupRanks({ migrations: 100 });
const getCurrentEndpoint = () => {
// Remove subpaths and query strings from the current URL. Add a /v1 suffix
@@ -39,11 +39,7 @@
const projectId = $page.params.project;
$: $updateCommandGroupRanks((prev) => ({
...prev,
webhooks: 20,
domains: 10
}));
$: $updateCommandGroupRanks({ webhooks: 20, domains: 10 });
</script>
<svelte:head>
@@ -15,7 +15,8 @@
$showCreateBucket = true;
},
keys: $page.url.pathname.endsWith('storage') ? ['c'] : ['c', 'b'],
icon: 'plus'
icon: 'plus',
group: 'buckets'
},
{
label: 'Go to usage',
@@ -23,12 +24,14 @@
goto(`/console/project-${$project.$id}/storage/usage`);
},
keys: ['g', 'u'],
disabled: $page.url.pathname.endsWith('usage'),
group: 'storage'
disabled:
$page.url.pathname.endsWith('usage') || $page.url.pathname.includes('bucket-'),
group: 'navigation',
rank: 10
}
]);
$: $updateCommandGroupRanks((prev) => ({ ...prev, storage: 10 }));
$: $updateCommandGroupRanks({ buckets: 200, files: 150, navigation: 100 });
</script>
<svelte:head>
@@ -1,3 +1,109 @@
<script lang="ts">
import { goto } from '$app/navigation';
import { page } from '$app/stores';
import {
addSubPanel,
registerCommands,
registerSearchers,
updateCommandGroupRanks
} from '$lib/commandCenter';
import { FilesPanel } from '$lib/commandCenter/panels';
import { fileSearcher } from '$lib/commandCenter/searchers';
import { project } from '../../store';
import { showCreateFile } from './+page.svelte';
import { bucket } from './store';
$: $registerCommands([
{
label: 'Search files',
callback() {
addSubPanel(FilesPanel);
},
keys: ['f', 'l'],
group: 'files',
icon: 'search'
},
{
label: 'Create file',
async callback() {
if (!$page.url.pathname.endsWith($bucket.$id)) {
goto(`/console/project-${$project.$id}/storage/bucket-${$bucket.$id}`);
}
showCreateFile();
},
keys: $page.url.pathname.endsWith($bucket.$id) ? ['c'] : ['c', 'f'],
group: 'files',
icon: 'plus'
},
{
label: 'Permissions',
async callback() {
await goto(
`/console/project-${$project.$id}/storage/bucket-${$bucket.$id}/settings#permissions`
);
scrollBy({ top: -100 });
},
group: 'buckets',
icon: 'key'
},
{
label: 'Extensions',
async callback() {
await goto(
`/console/project-${$project.$id}/storage/bucket-${$bucket.$id}/settings#extensions`
);
},
group: 'buckets',
icon: 'puzzle'
},
{
label: 'File Security',
async callback() {
await goto(
`/console/project-${$project.$id}/storage/bucket-${$bucket.$id}/settings#file-security`
);
scrollBy({ top: -100 });
},
group: 'buckets',
icon: 'lock-closed'
},
{
label: 'Go to files',
callback() {
goto(`/console/project-${$project.$id}/storage/bucket-${$bucket.$id}`);
},
disabled: $page.url.pathname.endsWith($bucket.$id),
keys: ['g', 'f'],
group: 'navigation',
rank: 10
},
{
label: 'Go to usage',
callback() {
goto(`/console/project-${$project.$id}/storage/bucket-${$bucket.$id}/usage`);
},
disabled: $page.url.pathname.endsWith('usage'),
keys: ['g', 'u'],
group: 'navigation',
rank: 10
},
{
label: 'Go to settings',
callback() {
goto(`/console/project-${$project.$id}/storage/bucket-${$bucket.$id}/settings`);
},
disabled: $page.url.pathname.endsWith('settings'),
keys: ['g', 's'],
group: 'navigation',
rank: 10
}
]);
$: $updateCommandGroupRanks({ files: 300 });
$registerSearchers(fileSearcher);
</script>
<svelte:head>
<title>Bucket - Appwrite</title>
</svelte:head>
@@ -1,3 +1,11 @@
<script lang="ts" context="module">
let showCreate = writable(false);
export const showCreateFile = () => {
showCreate.set(true);
};
</script>
<script lang="ts">
import { page } from '$app/stores';
import { sdk } from '$lib/stores/sdk';
@@ -36,10 +44,10 @@
import { invalidate } from '$app/navigation';
import { Dependencies } from '$lib/constants';
import { Submit, trackEvent, trackError } from '$lib/actions/analytics';
import { writable } from 'svelte/store';
export let data: PageData;
let showCreate = false;
let showDelete = false;
let showDropdown = [];
let selectedFile: Models.File = null;
@@ -50,7 +58,7 @@
sdk.forProject.storage.getFilePreview(bucketId, fileId, 32, 32).toString() + '&mode=admin';
async function fileCreated() {
showCreate = false;
$showCreate = false;
await invalidate(Dependencies.FILES);
}
@@ -78,7 +86,7 @@
<Container>
<SearchQuery search={data.search} placeholder="Search by filename">
<Button on:click={() => (showCreate = true)} event="create_file">
<Button on:click={() => ($showCreate = true)} event="create_file">
<span class="icon-plus" aria-hidden="true" /> <span class="text">Create file</span>
</Button>
</SearchQuery>
@@ -211,11 +219,11 @@
single
href="https://appwrite.io/docs/storage#createFile"
target="file"
on:click={() => (showCreate = true)} />
on:click={() => ($showCreate = true)} />
{/if}
</Container>
<Create bind:showCreate on:created={fileCreated} />
<Create bind:showCreate={$showCreate} on:created={fileCreated} />
{#if selectedFile}
<Delete file={selectedFile} bind:showDelete on:deleted={fileDeleted} />
{/if}
@@ -258,7 +258,7 @@
<Form onSubmit={updatePermissions}>
<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 buckets and files. For more information, check out
the <a
@@ -282,7 +282,7 @@
<Form onSubmit={updateFileSecurity}>
<CardGrid>
<Heading tag="h6" size="7">File Security</Heading>
<Heading tag="h6" size="7" id="file-security">File Security</Heading>
<svelte:fragment slot="aside">
<FormList>
<InputSwitch
@@ -406,7 +406,7 @@
<Form onSubmit={updateAllowedExtensions}>
<CardGrid>
<Heading tag="h6" size="7">File Extensions</Heading>
<Heading tag="h6" size="7" id="extensions">File Extensions</Heading>
<p class="text">
Allowed file extensions. A maximum of 100 file extensions can be added. Leave
blank to allow all file types.