global project stores

This commit is contained in:
Nikita Galaiko 2023-05-15 15:57:15 +02:00
parent 4afa457baa
commit 1f1455019c
16 changed files with 113 additions and 92 deletions

View File

@ -1,6 +1,5 @@
import { invoke } from '$lib/ipc';
import type { Project as CloudProject } from '$lib/api/cloud';
import { asyncWritable, derived } from '@square/svelte-store';
export type Project = {
id: string;
@ -22,31 +21,5 @@ export const update = (params: {
export const add = (params: { path: string }) => invoke<Project>('add_project', params);
export const del = (params: { id: string }) => invoke('delete_project', params);
const store = asyncWritable([], list);
export const Project = ({ id }: { id: string }) => ({
...derived(store, (projects) => projects?.find((p) => p.id === id)),
update: (params: Partial<Pick<Project, 'title' | 'description' | 'api'>>) =>
update({
project: {
id,
...params
}
}).then((project) => {
store.update((projects) => projects.map((p) => (p.id === project.id ? project : p)));
return project;
}),
delete: () =>
del({ id }).then(() => store.update((projects) => projects.filter((p) => p.id !== id)))
});
export const Projects = () => ({
...store,
add: (params: { path: string }) =>
add(params).then((project) => {
store.update((projects) => [...projects, project]);
return project;
})
});
const del = (params: { id: string }) => invoke('delete_project', params);
export { del as delete };

View File

@ -1 +1,3 @@
export { default as user } from './user';
export { default as projects } from './projects';
export { default as project } from './project';

24
src/lib/stores/project.ts Normal file
View File

@ -0,0 +1,24 @@
import { api } from '$lib';
import { derived } from '@square/svelte-store';
import type { Project } from '$lib/api';
import projects from './projects';
export default ({ id }: { id: string }) => ({
...derived(projects, (projects) => projects?.find((p) => p.id === id)),
update: (params: Partial<Pick<Project, 'title' | 'description' | 'api'>>) =>
api.projects
.update({
project: {
id,
...params
}
})
.then((project) => {
projects.update((projects) => projects.map((p) => (p.id === project.id ? project : p)));
return project;
}),
delete: () =>
api.projects
.delete({ id })
.then(() => projects.update((projects) => projects.filter((p) => p.id !== id)))
});

View File

@ -0,0 +1,13 @@
import { api } from '$lib';
import { asyncWritable } from '@square/svelte-store';
const projects = asyncWritable([], api.projects.list);
export default {
...projects,
add: (params: { path: string }) =>
api.projects.add(params).then((project) => {
projects.update((projects) => [...projects, project]);
return project;
})
};

View File

@ -6,7 +6,6 @@
import type { LayoutData } from './$types';
import { Link, CommandPalette } from '$lib/components';
import { page } from '$app/stores';
import { derived } from '@square/svelte-store';
import { onMount } from 'svelte';
import { goto } from '$app/navigation';
import { unsubscribe } from '$lib/utils';
@ -16,13 +15,12 @@
import ShareIssueModal from './ShareIssueModal.svelte';
export let data: LayoutData;
const { posthog, projects, sentry, cloud } = data;
const { posthog, sentry, cloud } = data;
const projects = stores.projects;
const user = stores.user;
const project = derived([page, projects], ([page, projects]) =>
projects?.find((project) => project.id === page.params.projectId)
);
$: project = stores.project({ id: $page.params.projectId });
let commandPalette: CommandPalette;
let linkProjectModal: LinkProjectModal;
@ -98,7 +96,7 @@
<CommandPalette bind:this={commandPalette} {projects} {project} />
{/await}
<LinkProjectModal bind:this={linkProjectModal} {cloud} {projects} />
<LinkProjectModal bind:this={linkProjectModal} {cloud} />
<ShareIssueModal bind:this={shareIssueModal} user={$user} {cloud} />
<ShareIssueModal bind:this={shareIssueModal} {cloud} />
</div>

View File

@ -11,7 +11,6 @@ export const csr = true;
export const load: LayoutLoad = wrapLoadWithSentry(({ fetch }) => {
log.setup();
return {
projects: api.projects.Projects(),
cloud: api.CloudApi({ fetch }),
posthog: Posthog(),
sentry: Sentry()

View File

@ -1,11 +1,8 @@
<script lang="ts">
import type { LayoutData } from './$types';
import { Button, Tooltip } from '$lib/components';
import { events } from '$lib';
import { events, stores } from '$lib';
export let data: LayoutData;
const { projects } = data;
const projects = stores.projects;
</script>
<div class="h-full w-full p-8">

View File

@ -6,10 +6,10 @@
import { IconFolder, IconLoading } from '$lib/icons';
import { toasts, api, stores } from '$lib';
export let projects: ReturnType<typeof api.projects.Projects>;
export let cloud: ReturnType<typeof api.CloudApi>;
const user = stores.user;
const projects = stores.projects;
const cloudProjects = asyncDerived(user, async (user) =>
user ? await cloud.projects.list(user.access_token) : []
@ -17,10 +17,10 @@
let selectedRepositoryId: string | null = null;
let project: ReturnType<typeof api.projects.Project> | undefined;
let project: ReturnType<typeof stores.project> | undefined;
export const show = async (id: string) => {
project = api.projects.Project({ id });
project = stores.project({ id });
modal.show();
};

View File

@ -1,14 +1,14 @@
<script lang="ts">
import { toasts, api } from '$lib';
import { toasts, api, stores } from '$lib';
import { Button, Checkbox, Modal } from '$lib/components';
import { page } from '$app/stores';
import type { User } from '$lib/api';
export let user: User | null;
export let cloud: ReturnType<typeof api.CloudApi>;
export const show = () => modal.show();
const user = stores.user;
let modal: Modal;
let comments = '';
@ -53,8 +53,8 @@
])
)
.then(async ([logs, data, repo]) =>
cloud.feedback.create(user?.access_token, {
email: user?.email ?? email,
cloud.feedback.create($user?.access_token, {
email: $user?.email ?? email,
message: comments,
logs,
data,

View File

@ -12,8 +12,9 @@
import { events, hotkeys, stores } from '$lib';
export let data: LayoutData;
const { cloud, project, head, statuses, diffs } = data;
const { cloud, head, statuses, diffs } = data;
$: project = stores.project({ id: $page.params.projectId });
const user = stores.user;
let query: string;
@ -31,12 +32,12 @@
events.on('openQuickCommitModal', () => quickCommitModal?.show()),
hotkeys.on('C', () => events.emit('openQuickCommitModal')),
hotkeys.on('Meta+Shift+C', () => goto(`/projects/${$project.id}/commit/`)),
hotkeys.on('Meta+T', () => goto(`/projects/${$project.id}/terminal/`)),
hotkeys.on('Meta+P', () => goto(`/projects/${$project.id}/`)),
hotkeys.on('Meta+Shift+,', () => goto(`/projects/${$project.id}/settings/`)),
hotkeys.on('Meta+R', () => goto(`/projects/${$project.id}/player/`)),
hotkeys.on('a i p', () => goto(`/projects/${$project.id}/aiplayground/`))
hotkeys.on('Meta+Shift+C', () => $project && goto(`/projects/${$project.id}/commit/`)),
hotkeys.on('Meta+T', () => $project && goto(`/projects/${$project.id}/terminal/`)),
hotkeys.on('Meta+P', () => $project && goto(`/projects/${$project.id}/`)),
hotkeys.on('Meta+Shift+,', () => $project && goto(`/projects/${$project.id}/settings/`)),
hotkeys.on('Meta+R', () => $project && goto(`/projects/${$project.id}/player/`)),
hotkeys.on('a i p', () => $project && goto(`/projects/${$project.id}/aiplayground/`))
)
);
</script>
@ -87,7 +88,7 @@
<li>
<Tooltip label="Terminal">
<Button
on:click={() => goto(`/projects/${$project.id}/terminal`)}
on:click={() => $project && goto(`/projects/${$project.id}/terminal`)}
kind="plain"
icon={IconTerminal}
/>
@ -96,7 +97,7 @@
<li>
<Tooltip label="Project settings">
<Button
on:click={() => goto(`/projects/${$project.id}/settings`)}
on:click={() => $project && goto(`/projects/${$project.id}/settings`)}
kind="plain"
icon={IconSettings}
/>
@ -147,7 +148,7 @@
</div>
{#await Promise.all([diffs.load(), user.load(), project.load(), statuses.load()]) then}
{#if $user}
{#if $user && $project}
<QuickCommitModal
bind:this={quickCommitModal}
user={$user}

View File

@ -1,19 +1,13 @@
import { api } from '$lib';
import { error } from '@sveltejs/kit';
import type { LayoutLoad } from './$types';
import type { Loadable } from '@square/svelte-store';
import type { Project } from '$lib/api';
export const prerender = false;
export const load: LayoutLoad = async ({ params }) => {
const project = api.projects.Project({ id: params.projectId });
if ((await project.load()) === undefined) throw error(404, new Error('Project not found'));
return {
head: api.git.heads.Head({ projectId: params.projectId }),
statuses: api.git.statuses.Statuses({ projectId: params.projectId }),
sessions: api.sessions.Sessions({ projectId: params.projectId }),
diffs: api.git.diffs.Diffs({ projectId: params.projectId }),
project: project as Loadable<Project> & Pick<typeof project, 'update' | 'delete'>
diffs: api.git.diffs.Diffs({ projectId: params.projectId })
};
};

View File

@ -6,10 +6,12 @@
import FileSummaries from './FileSummaries.svelte';
import { Button, Statuses, Tooltip } from '$lib/components';
import { goto } from '$app/navigation';
import { stores } from '$lib';
import { page } from '$app/stores';
export let data: PageData;
$: activity = derived(data.activity, (activity) => activity);
$: project = derived(data.project, (project) => project);
$: project = stores.project({ id: $page.params.projectId });
$: statuses = derived(data.statuses, (statuses) => statuses);
$: sessions = derived(data.sessions, (sessions) => sessions);
$: head = derived(data.head, (head) => head);

View File

@ -15,8 +15,9 @@
import { unsubscribe } from '$lib/utils';
export let data: PageData;
let { statuses, diffs, cloud, project } = data;
let { statuses, diffs, cloud } = data;
$: project = stores.project({ id: $page.params.projectId });
const user = stores.user;
let fullContext = false;

View File

@ -1,5 +1,4 @@
<script lang="ts">
import type { PageData } from './$types';
import { IconChevronLeft, IconChevronRight, IconLoading } from '$lib/icons';
import { files, deltas, searchResults, type SearchResult } from '$lib/api';
import { asyncDerived } from '@square/svelte-store';
@ -8,9 +7,9 @@
import { page } from '$app/stores';
import { derived } from '@square/svelte-store';
import { goto } from '$app/navigation';
import { stores } from '$lib';
export let data: PageData;
const { project } = data;
$: project = stores.project({ id: $page.params.projectId });
const limit = 10;
const query = derived(page, (page) => page.url.searchParams.get('q'));

View File

@ -7,19 +7,22 @@
import CloudForm from './CloudForm.svelte';
import DetailsForm from './DetailsForm.svelte';
import type { Project } from '$lib/api';
import { page } from '$app/stores';
export let data: PageData;
const { projects, project, cloud } = data;
const { cloud } = data;
const projects = stores.projects;
const user = stores.user;
$: project = stores.project({ id: $page.params.projectId });
let deleteConfirmationModal: Modal;
let isDeleting = false;
const onDeleteClicked = () =>
const onDeleteClicked = () => {
Promise.resolve()
.then(() => (isDeleting = true))
.then(() => api.projects.del({ id: $project?.id }))
.then(() => $project && api.projects.delete({ id: $project.id }))
.then(() => deleteConfirmationModal.close())
.catch((e) => {
log.error(e);
@ -29,6 +32,7 @@
.then(() => projects.update((projects) => projects.filter((p) => p.id !== $project?.id)))
.then(() => toasts.success('Project deleted'))
.finally(() => (isDeleting = false));
};
const onCloudUpdated = (e: { detail: Project }) => project.update({ ...e.detail });
const onDetailsUpdated = async (e: { detail: Project }) => {
@ -58,8 +62,10 @@
</div>
<hr class="border-zinc-600" />
{#await project.load() then}
<CloudForm project={$project} on:updated={onCloudUpdated} />
<DetailsForm project={$project} on:updated={onDetailsUpdated} />
{#if $project}
<CloudForm project={$project} on:updated={onCloudUpdated} />
<DetailsForm project={$project} on:updated={onDetailsUpdated} />
{/if}
{/await}
<hr class="border-zinc-600" />
@ -104,16 +110,20 @@
</div>
</div>
<Modal bind:this={deleteConfirmationModal} title="Delete {$project.title}?">
<p>
Are you sure you want to delete the project,
<span class="font-bold text-white">{$project.title}</span>? This cant be undone.
</p>
{#await project.load() then}
{#if $project}
<Modal bind:this={deleteConfirmationModal} title="Delete {$project.title}?">
<p>
Are you sure you want to delete the project,
<span class="font-bold text-white">{$project.title}</span>? This cant be undone.
</p>
<svelte:fragment slot="controls" let:close>
<Button kind="outlined" on:click={close}>Cancel</Button>
<Button color="destructive" loading={isDeleting} on:click={onDeleteClicked}>
Delete project
</Button>
</svelte:fragment>
</Modal>
<svelte:fragment slot="controls" let:close>
<Button kind="outlined" on:click={close}>Cancel</Button>
<Button color="destructive" loading={isDeleting} on:click={onDeleteClicked}>
Delete project
</Button>
</svelte:fragment>
</Modal>
{/if}
{/await}

View File

@ -5,10 +5,14 @@
import 'xterm/css/xterm.css';
import type { Project } from '$lib/api';
import { debounce } from '$lib/utils';
import { page } from '$app/stores';
import { Button, Statuses } from '$lib/components';
import { stores } from '$lib';
export let data: LayoutData;
const { project, statuses } = data;
const { statuses } = data;
$: project = stores.project({ id: $page.params.projectId });
type Unpromisify<T> = T extends Promise<infer U> ? U : T;
let term: Unpromisify<ReturnType<typeof setupTerminal>> | undefined;
@ -47,8 +51,12 @@
<div class="main-content-container h-full">
<div class="flex h-full w-full flex-row">
<div class="h-full w-full" use:terminal={{ project: $project }} />
<ResizeObserver on:resize={handleTerminalResize} />
{#await project.load() then}
{#if $project}
<div class="h-full w-full" use:terminal={{ project: $project }} />
<ResizeObserver on:resize={handleTerminalResize} />
{/if}
{/await}
</div>
</div>
</div>