merge upstream

This commit is contained in:
Scott Chacon 2023-08-03 08:28:31 +02:00 committed by GitButler
commit f760857e8b
84 changed files with 331 additions and 368 deletions

View File

@ -85,11 +85,11 @@ const withLog: FetchMiddleware = (fetch) => async (url, options) => {
} }
}; };
export default ( export function getCloudApiClient(
{ fetch: realFetch }: { fetch: typeof window.fetch } = { { fetch: realFetch }: { fetch: typeof window.fetch } = {
fetch: window.fetch fetch: window.fetch
} }
) => { ) {
const fetch = fetchWith(realFetch, withRequestId, withLog); const fetch = fetchWith(realFetch, withRequestId, withLog);
return { return {
login: { login: {
@ -278,4 +278,4 @@ export default (
}).then(parseResponseJSON) }).then(parseResponseJSON)
} }
}; };
}; }

View File

@ -1,2 +1 @@
export { default as Api } from './api';
export type { User, LoginToken, Project } from './api'; export type { User, LoginToken, Project } from './api';

View File

@ -20,7 +20,7 @@ export function subscribe(
const stores: Record<string, WritableLoadable<Activity[]>> = {}; const stores: Record<string, WritableLoadable<Activity[]>> = {};
export function Activities(params: { projectId: string }) { export function getActivitiesStore(params: { projectId: string }) {
if (stores[params.projectId]) return stores[params.projectId]; if (stores[params.projectId]) return stores[params.projectId];
const store = asyncWritable([], () => list(params)); const store = asyncWritable([], () => list(params));

View File

@ -1,6 +1,7 @@
import { invoke } from '$lib/ipc'; import { invoke } from '$lib/ipc';
import { asyncWritable, type WritableLoadable } from '@square/svelte-store'; import { asyncWritable, type WritableLoadable } from '@square/svelte-store';
import { sessions, git } from '$lib/api'; import * as activities from './activities';
import * as sessions from '../ipc/sessions';
const list = (params: { projectId: string; contextLines?: number }) => const list = (params: { projectId: string; contextLines?: number }) =>
invoke<Record<string, string>>('git_wd_diff', { invoke<Record<string, string>>('git_wd_diff', {
@ -10,10 +11,10 @@ const list = (params: { projectId: string; contextLines?: number }) =>
const stores: Record<string, WritableLoadable<Record<string, string>>> = {}; const stores: Record<string, WritableLoadable<Record<string, string>>> = {};
export function Diffs(params: { projectId: string }) { export function getDiffsStore(params: { projectId: string }) {
if (stores[params.projectId]) return stores[params.projectId]; if (stores[params.projectId]) return stores[params.projectId];
const store = asyncWritable([], () => list(params)); const store = asyncWritable([], () => list(params));
git.activities.subscribe(params, ({ projectId }) => list({ projectId }).then(store.set)); activities.subscribe(params, ({ projectId }) => list({ projectId }).then(store.set));
sessions.subscribe(params, () => list(params).then(store.set)); sessions.subscribe(params, () => list(params).then(store.set));
stores[params.projectId] = store; stores[params.projectId] = store;
return store; return store;

View File

@ -16,7 +16,7 @@ export function subscribe(
const stores: Record<string, WritableLoadable<string>> = {}; const stores: Record<string, WritableLoadable<string>> = {};
export function Head(params: { projectId: string }) { export function getHeadStore(params: { projectId: string }) {
if (stores[params.projectId]) return stores[params.projectId]; if (stores[params.projectId]) return stores[params.projectId];
const store = asyncWritable([], () => const store = asyncWritable([], () =>
get(params).then((head) => head.replace('refs/heads/', '')) get(params).then((head) => head.replace('refs/heads/', ''))

View File

@ -1,11 +1,4 @@
export * as statuses from './statuses';
export { Status } from './statuses';
export * as activities from './activities';
export type { Activity } from './activities'; export type { Activity } from './activities';
export * as heads from './heads';
export * as diffs from './diffs';
export * as indexes from './indexes';
export * as fetches from './fetches';
import { invoke } from '$lib/ipc'; import { invoke } from '$lib/ipc';

View File

@ -2,7 +2,7 @@ import { invoke } from '$lib/ipc';
import { asyncWritable, type WritableLoadable } from '@square/svelte-store'; import { asyncWritable, type WritableLoadable } from '@square/svelte-store';
import * as indexes from './indexes'; import * as indexes from './indexes';
import * as activities from './activities'; import * as activities from './activities';
import * as sessions from '../sessions'; import * as sessions from '../ipc/sessions';
type FileStatus = 'added' | 'modified' | 'deleted' | 'renamed' | 'typeChange' | 'other'; type FileStatus = 'added' | 'modified' | 'deleted' | 'renamed' | 'typeChange' | 'other';
@ -11,13 +11,11 @@ export type Status =
| { unstaged: FileStatus } | { unstaged: FileStatus }
| { staged: FileStatus; unstaged: FileStatus }; | { staged: FileStatus; unstaged: FileStatus };
export namespace Status { export function isStaged(status: Status): status is { staged: FileStatus } {
export function isStaged(status: Status): status is { staged: FileStatus } {
return 'staged' in status && status.staged !== null; return 'staged' in status && status.staged !== null;
} }
export function isUnstaged(status: Status): status is { unstaged: FileStatus } { export function isUnstaged(status: Status): status is { unstaged: FileStatus } {
return 'unstaged' in status && status.unstaged !== null; return 'unstaged' in status && status.unstaged !== null;
}
} }
export function list(params: { projectId: string }) { export function list(params: { projectId: string }) {
@ -26,7 +24,7 @@ export function list(params: { projectId: string }) {
const stores: Record<string, WritableLoadable<Record<string, Status>>> = {}; const stores: Record<string, WritableLoadable<Record<string, Status>>> = {};
export function Statuses(params: { projectId: string }) { export function getStatusStore(params: { projectId: string }) {
if (stores[params.projectId]) return stores[params.projectId]; if (stores[params.projectId]) return stores[params.projectId];
const store = asyncWritable([], () => list(params)); const store = asyncWritable([], () => list(params));
sessions.subscribe(params, () => list(params).then(store.set)); sessions.subscribe(params, () => list(params).then(store.set));

View File

@ -1,2 +0,0 @@
export * from './ipc';
export { Api as CloudApi, type User, type LoginToken } from './cloud';

View File

@ -5,14 +5,12 @@ export type OperationInsert = { insert: [number, string] };
export type Operation = OperationDelete | OperationInsert; export type Operation = OperationDelete | OperationInsert;
export namespace Operation { export function isDelete(operation: Operation): operation is OperationDelete {
export function isDelete(operation: Operation): operation is OperationDelete {
return 'delete' in operation; return 'delete' in operation;
} }
export function isInsert(operation: Operation): operation is OperationInsert { export function isInsert(operation: Operation): operation is OperationInsert {
return 'insert' in operation; return 'insert' in operation;
}
} }
export type Delta = { timestampMs: number; operations: Operation[] }; export type Delta = { timestampMs: number; operations: Operation[] };

View File

@ -1,19 +1,3 @@
export * as git from './git';
export { Status, type Activity } from './git';
export * as deltas from './deltas';
export { type Delta, Operation } from './deltas';
export * as sessions from './sessions';
export { Session } from './sessions';
export * as users from './users';
export * as projects from './projects';
export type { Project } from './projects';
export * as searchResults from './search';
export { type SearchResult } from './search';
export * as files from './files';
export * as zip from './zip';
export * as bookmarks from './bookmarks';
export type { Bookmark } from './bookmarks';
import { invoke } from '$lib/ipc'; import { invoke } from '$lib/ipc';
export function deleteAllData() { export function deleteAllData() {

View File

@ -38,7 +38,7 @@ export function del(params: { id: string }) {
const store = asyncWritable([], list); const store = asyncWritable([], list);
export function Project({ id }: { id: string }) { export function getProjectStore({ id }: { id: string }) {
return { return {
...derived(store, (projects) => projects?.find((p) => p.id === id)), ...derived(store, (projects) => projects?.find((p) => p.id === id)),
update: (params: Partial<Pick<Project, 'title' | 'description' | 'api'>>) => update: (params: Partial<Pick<Project, 'title' | 'description' | 'api'>>) =>
@ -56,13 +56,11 @@ export function Project({ id }: { id: string }) {
}; };
} }
export function Projects() { export const projectsStore = {
return {
...store, ...store,
add: (params: { path: string }) => add: (params: { path: string }) =>
add(params).then((project) => { add(params).then((project) => {
store.update((projects) => [...projects, project]); store.update((projects) => [...projects, project]);
return project; return project;
}) })
}; };
}

View File

@ -42,7 +42,8 @@ export function subscribe(
const stores: Record<string, WritableLoadable<Session[]>> = {}; const stores: Record<string, WritableLoadable<Session[]>> = {};
export function Sessions(params: { projectId: string }) { // TODO: Figure out why this extra store exists.
export function getSessionStore(params: { projectId: string }) {
if (params.projectId in stores) return stores[params.projectId]; if (params.projectId in stores) return stores[params.projectId];
const store = asyncWritable([], () => list(params)); const store = asyncWritable([], () => list(params));

View File

@ -1,11 +1,11 @@
import type { User } from '$lib/api'; import type { User } from '../cloud/api';
import { invoke } from '$lib/ipc'; import { invoke } from '$lib/ipc';
export async function get() { export async function get() {
return invoke<User | null>('get_user'); return invoke<User | null>('get_user');
} }
export function set(params: { user: User }) { export async function set(params: { user: User }) {
invoke<void>('set_user', params); invoke<void>('set_user', params);
} }

View File

@ -1,6 +1,6 @@
<script lang="ts"> <script lang="ts">
import tinykeys from 'tinykeys'; import tinykeys from 'tinykeys';
import type { Project } from '$lib/api'; import type { Project } from '$lib/api/ipc/projects';
import { derived, readable, writable, type Readable } from '@square/svelte-store'; import { derived, readable, writable, type Readable } from '@square/svelte-store';
import { Overlay } from '$lib/components'; import { Overlay } from '$lib/components';
import listAvailableCommands, { Action, type Group } from './commands'; import listAvailableCommands, { Action, type Group } from './commands';

View File

@ -1,4 +1,5 @@
import { type Project, git } from '$lib/api'; import type { Project } from '$lib/api/ipc/projects';
import { matchFiles } from '$lib/api/git';
import { events } from '$lib'; import { events } from '$lib';
import { import {
IconGitCommit, IconGitCommit,
@ -180,7 +181,7 @@ const fileGroup = ({
description: 'type part of a file name', description: 'type part of a file name',
commands: [] commands: []
} }
: git.matchFiles({ projectId: project.id, matchPattern: input }).then((files) => ({ : matchFiles({ projectId: project.id, matchPattern: input }).then((files) => ({
title: 'Files', title: 'Files',
description: files.length === 0 ? `no files containing '${input}'` : '', description: files.length === 0 ? `no files containing '${input}'` : '',
commands: files.map((file) => ({ commands: files.map((file) => ({

View File

@ -1,6 +1,6 @@
<script lang="ts"> <script lang="ts">
import { type Delta, Operation } from '$lib/api'; import { isInsert, type Delta, isDelete } from '$lib/api/ipc/deltas';
import { Differ } from '$lib/components'; import Differ from './Differ';
import { line } from '$lib/diff'; import { line } from '$lib/diff';
export let doc: string; export let doc: string;
@ -13,12 +13,12 @@
const operations = deltas.flatMap((delta) => delta.operations); const operations = deltas.flatMap((delta) => delta.operations);
operations.forEach((operation) => { operations.forEach((operation) => {
if (Operation.isInsert(operation)) { if (isInsert(operation)) {
text = text =
text.slice(0, operation.insert[0]) + text.slice(0, operation.insert[0]) +
operation.insert[1] + operation.insert[1] +
text.slice(operation.insert[0]); text.slice(operation.insert[0]);
} else if (Operation.isDelete(operation)) { } else if (isDelete(operation)) {
text = text =
text.slice(0, operation.delete[0]) + text.slice(0, operation.delete[0]) +
text.slice(operation.delete[0] + operation.delete[1]); text.slice(operation.delete[0] + operation.delete[1]);

View File

@ -1,13 +1,14 @@
<script lang="ts"> <script lang="ts">
import { type LoginToken, CloudApi } from '$lib/api'; import { getCloudApiClient, type LoginToken } from '$lib/api/cloud/api';
import { toasts, stores } from '$lib'; import { toasts } from '$lib';
import { userStore } from '$lib/stores/user';
import { derived, writable } from '@square/svelte-store'; import { derived, writable } from '@square/svelte-store';
import { open } from '@tauri-apps/api/shell'; import { open } from '@tauri-apps/api/shell';
import Button from './Button'; import Button from './Button';
import { goto } from '$app/navigation'; import { goto } from '$app/navigation';
const cloud = CloudApi(); const cloud = getCloudApiClient();
const user = stores.user; const user = userStore;
export let width: 'basic' | 'full-width' = 'basic'; export let width: 'basic' | 'full-width' = 'basic';

View File

@ -1,6 +1,6 @@
<script lang="ts"> <script lang="ts">
import { collapse } from '$lib/paths'; import { collapse } from '$lib/paths';
import { Status } from '$lib/api'; import { isStaged, isUnstaged, type Status } from '$lib/api/git/statuses';
export let statuses: Record<string, Status>; export let statuses: Record<string, Status>;
</script> </script>
@ -27,12 +27,12 @@
<li class="flex w-full gap-2"> <li class="flex w-full gap-2">
<div class="flex w-[3ch] justify-between font-semibold"> <div class="flex w-[3ch] justify-between font-semibold">
<span> <span>
{#if Status.isStaged(status)} {#if isStaged(status)}
{status.staged.slice(0, 1).toUpperCase()} {status.staged.slice(0, 1).toUpperCase()}
{/if} {/if}
</span> </span>
<span> <span>
{#if Status.isUnstaged(status)} {#if isUnstaged(status)}
{status.unstaged.slice(0, 1).toUpperCase()} {status.unstaged.slice(0, 1).toUpperCase()}
{/if} {/if}
</span> </span>

View File

@ -1,4 +1,3 @@
export * as api from './api';
export * as toasts from './toasts'; export * as toasts from './toasts';
export { Toaster } from './toasts'; export { Toaster } from './toasts';
export * as week from './week'; export * as week from './week';
@ -6,4 +5,3 @@ export * as uisessions from './uisessions';
export * as events from './events'; export * as events from './events';
export * as hotkeys from './hotkeys'; export * as hotkeys from './hotkeys';
export * as diff from './diff'; export * as diff from './diff';
export * as stores from './stores';

View File

@ -1,6 +1,6 @@
import posthog from 'posthog-js'; import posthog from 'posthog-js';
import { PUBLIC_POSTHOG_API_KEY } from '$env/static/public'; import { PUBLIC_POSTHOG_API_KEY } from '$env/static/public';
import type { User } from '$lib/api'; import type { User } from './api/cloud/api';
import { getVersion, getName } from '@tauri-apps/api/app'; import { getVersion, getName } from '@tauri-apps/api/app';
interface PostHogClient { interface PostHogClient {

View File

@ -1,5 +1,5 @@
import { setUser } from '@sentry/sveltekit'; import { setUser } from '@sentry/sveltekit';
import type { User } from '$lib/api'; import type { User } from './api/cloud/api';
export default () => { export default () => {
return { return {

View File

@ -1,10 +1,10 @@
import { writable, type Loadable, derived, Loaded } from 'svelte-loadable-store'; import { writable, type Loadable, derived, Loaded } from 'svelte-loadable-store';
import { bookmarks, type Bookmark } from '$lib/api'; import * as bookmarks from '$lib/api/ipc/bookmarks';
import { get as getValue, type Readable } from '@square/svelte-store'; import { get as getValue, type Readable } from '@square/svelte-store';
const stores: Record<string, Readable<Loadable<Bookmark[]>>> = {}; const stores: Record<string, Readable<Loadable<bookmarks.Bookmark[]>>> = {};
export function list(params: { projectId: string }) { export function getBookmarksStore(params: { projectId: string }) {
if (params.projectId in stores) return stores[params.projectId]; if (params.projectId in stores) return stores[params.projectId];
const store = writable(bookmarks.list(params), (set) => { const store = writable(bookmarks.list(params), (set) => {
@ -23,11 +23,11 @@ export function list(params: { projectId: string }) {
}; };
}); });
stores[params.projectId] = store; stores[params.projectId] = store;
return store as Readable<Loadable<Bookmark[]>>; return store as Readable<Loadable<bookmarks.Bookmark[]>>;
} }
export function get(params: { projectId: string; timestampMs: number }) { export function getBookmark(params: { projectId: string; timestampMs: number }) {
return derived(list({ projectId: params.projectId }), (bookmarks) => return derived(getBookmarksStore({ projectId: params.projectId }), (bookmarks) =>
bookmarks.find((b) => b.timestampMs === params.timestampMs) bookmarks.find((b) => b.timestampMs === params.timestampMs)
); );
} }

View File

@ -1,10 +1,10 @@
import { writable, type Loadable, Loaded } from 'svelte-loadable-store'; import { writable, type Loadable, Loaded } from 'svelte-loadable-store';
import { deltas, type Delta } from '$lib/api'; import * as deltas from '$lib/api/ipc/deltas';
import { get, type Readable } from '@square/svelte-store'; import { get, type Readable } from '@square/svelte-store';
const stores: Record<string, Readable<Loadable<Record<string, Delta[]>>>> = {}; const stores: Record<string, Readable<Loadable<Record<string, deltas.Delta[]>>>> = {};
export default (params: { projectId: string; sessionId: string }) => { export function getDeltasStore(params: { projectId: string; sessionId: string }) {
const key = `${params.projectId}/${params.sessionId}`; const key = `${params.projectId}/${params.sessionId}`;
if (key in stores) return stores[key]; if (key in stores) return stores[key];
@ -29,5 +29,5 @@ export default (params: { projectId: string; sessionId: string }) => {
}; };
}); });
stores[key] = store; stores[key] = store;
return store as Readable<Loadable<Record<string, Delta[]>>>; return store as Readable<Loadable<Record<string, deltas.Delta[]>>>;
}; }

View File

@ -1,10 +1,10 @@
import { writable, type Loadable, Loaded } from 'svelte-loadable-store'; import { writable, type Loadable, Loaded } from 'svelte-loadable-store';
import { files } from '$lib/api'; import * as files from '$lib/api/ipc/files';
import { get, type Readable } from '@square/svelte-store'; import { get, type Readable } from '@square/svelte-store';
const stores: Record<string, Readable<Loadable<Record<string, string>>>> = {}; const stores: Record<string, Readable<Loadable<Record<string, string>>>> = {};
export default (params: { projectId: string; sessionId: string }) => { export function getFilesStore(params: { projectId: string; sessionId: string }) {
const key = `${params.projectId}/${params.sessionId}`; const key = `${params.projectId}/${params.sessionId}`;
if (key in stores) return stores[key]; if (key in stores) return stores[key];
@ -28,4 +28,4 @@ export default (params: { projectId: string; sessionId: string }) => {
}); });
stores[key] = store; stores[key] = store;
return store as Readable<Loadable<Record<string, string>>>; return store as Readable<Loadable<Record<string, string>>>;
}; }

View File

@ -1,5 +0,0 @@
export { default as user } from './user';
export * as bookmarks from './bookmarks';
export { default as deltas } from './deltas';
export { default as sessions } from './sessions';
export { default as files } from './files';

View File

@ -1,10 +1,10 @@
import { derived, writable, type Loadable, Loaded } from 'svelte-loadable-store'; import { derived, writable, type Loadable, Loaded } from 'svelte-loadable-store';
import { sessions, type Session } from '$lib/api'; import * as sessions from '$lib/api/ipc/sessions';
import { get, type Readable } from '@square/svelte-store'; import { get, type Readable } from '@square/svelte-store';
const stores: Record<string, Readable<Loadable<Session[]>>> = {}; const stores: Record<string, Readable<Loadable<sessions.Session[]>>> = {};
export default (params: { projectId: string }) => { export function getSessionStore(params: { projectId: string }) {
if (params.projectId in stores) return stores[params.projectId]; if (params.projectId in stores) return stores[params.projectId];
const store = derived( const store = derived(
@ -12,7 +12,7 @@ export default (params: { projectId: string }) => {
const unsubscribe = sessions.subscribe(params, ({ session }) => { const unsubscribe = sessions.subscribe(params, ({ session }) => {
const oldValue = get(store); const oldValue = get(store);
if (oldValue.isLoading) { if (oldValue.isLoading) {
sessions.list(params).then(set); sessions.list(params).then((x) => set(x));
} else if (Loaded.isError(oldValue)) { } else if (Loaded.isError(oldValue)) {
sessions.list(params).then(set); sessions.list(params).then(set);
} else { } else {
@ -33,5 +33,5 @@ export default (params: { projectId: string }) => {
(sessions) => sessions.sort((a, b) => a.meta.startTimestampMs - b.meta.startTimestampMs) (sessions) => sessions.sort((a, b) => a.meta.startTimestampMs - b.meta.startTimestampMs)
); );
stores[params.projectId] = store; stores[params.projectId] = store;
return store as Readable<Loadable<Session[]>>; return store;
}; }

View File

@ -1,7 +1,7 @@
import { users } from '$lib/api'; import * as users from '$lib/api/ipc/users';
import { asyncWritable } from '@square/svelte-store'; import { asyncWritable } from '@square/svelte-store';
const store = asyncWritable([], users.get, async (user) => { export const userStore = asyncWritable([], users.get, async (user) => {
if (user === null) { if (user === null) {
await users.delete(); await users.delete();
} else { } else {
@ -9,5 +9,3 @@ const store = asyncWritable([], users.get, async (user) => {
} }
return user; return user;
}); });
export default store;

View File

@ -1,6 +1,7 @@
import { CloudApi } from '$lib/api'; import { getCloudApiClient } from './api/cloud/api';
import lscache from 'lscache'; import lscache from 'lscache';
const cloud = CloudApi();
const cloud = getCloudApiClient();
export async function summarizeHunk(diff: string): Promise<string> { export async function summarizeHunk(diff: string): Promise<string> {
const diffHash = hash(diff); const diffHash = hash(diff);

View File

@ -1,4 +1,5 @@
import type { Session, Delta } from '$lib/api'; import type { Delta } from './api/ipc/deltas';
import type { Session } from './api/ipc/sessions';
export type UISession = { export type UISession = {
session: Session; session: Session;

View File

@ -1,12 +1,13 @@
import { writable, type Loadable, Loaded } from 'svelte-loadable-store'; import { writable, type Loadable, Loaded } from 'svelte-loadable-store';
import type { Session, Delta } from '$lib/api'; import type { Session } from '$lib/api/ipc/sessions';
import { Operation } from '$lib/api'; import { getSessionStore } from '$lib/stores/sessions';
import type { Readable } from '@square/svelte-store'; import type { Readable } from '@square/svelte-store';
import { deltas, git } from '$lib/api/ipc'; import * as fetches from '$lib/api/git/fetches';
import { stores } from '$lib'; import * as deltas from '$lib/api/ipc/deltas';
import { BaseBranch, Branch, BranchData } from './types'; import { BaseBranch, Branch, BranchData } from './types';
import { plainToInstance } from 'class-transformer'; import { plainToInstance } from 'class-transformer';
import { invoke } from '$lib/ipc'; import { invoke } from '$lib/ipc';
import { isDelete, isInsert } from '$lib/api/ipc/deltas';
export interface Refreshable { export interface Refreshable {
refresh(): Promise<void | object>; refresh(): Promise<void | object>;
@ -24,7 +25,7 @@ export class BranchStoresCache {
} }
const writableStore = writable(listVirtualBranches({ projectId }), (set) => { const writableStore = writable(listVirtualBranches({ projectId }), (set) => {
return stores.sessions({ projectId }).subscribe((sessions) => { return getSessionStore({ projectId }).subscribe((sessions) => {
if (sessions.isLoading) return; if (sessions.isLoading) return;
if (Loaded.isError(sessions)) return; if (Loaded.isError(sessions)) return;
const lastSession = sessions.value.at(-1); const lastSession = sessions.value.at(-1);
@ -70,7 +71,7 @@ export class BranchStoresCache {
return cachedStore; return cachedStore;
} }
const writableStore = writable(getRemoteBranchesData({ projectId }), (set) => { const writableStore = writable(getRemoteBranchesData({ projectId }), (set) => {
git.fetches.subscribe({ projectId }, () => { fetches.subscribe({ projectId }, () => {
getRemoteBranchesData({ projectId }).then(set); getRemoteBranchesData({ projectId }).then(set);
}); });
}); });
@ -91,7 +92,7 @@ export class BranchStoresCache {
return cachedStore; return cachedStore;
} }
const writableStore = writable(getBaseBranchData({ projectId }), (set) => { const writableStore = writable(getBaseBranchData({ projectId }), (set) => {
git.fetches.subscribe({ projectId }, () => { fetches.subscribe({ projectId }, () => {
getBaseBranchData({ projectId }).then(set); getBaseBranchData({ projectId }).then(set);
}); });
}); });
@ -133,7 +134,7 @@ async function branchesWithFileContent(projectId: string, sessionId: string, bra
sessionId: sessionId, sessionId: sessionId,
paths: filePaths paths: filePaths
}); });
const sessionDeltas = await invoke<Record<string, Delta[]>>('list_deltas', { const sessionDeltas = await invoke<Record<string, deltas.Delta[]>>('list_deltas', {
projectId: projectId, projectId: projectId,
sessionId: sessionId, sessionId: sessionId,
paths: filePaths paths: filePaths
@ -141,22 +142,22 @@ async function branchesWithFileContent(projectId: string, sessionId: string, bra
const branchesWithContnent = branches.map((branch) => { const branchesWithContnent = branches.map((branch) => {
branch.files.map((file) => { branch.files.map((file) => {
const contentAtSessionStart = sessionFiles[file.path]; const contentAtSessionStart = sessionFiles[file.path];
const deltas = sessionDeltas[file.path]; const ds = sessionDeltas[file.path];
file.content = applyDeltas(contentAtSessionStart, deltas); file.content = applyDeltas(contentAtSessionStart, ds);
}); });
return branch; return branch;
}); });
return branchesWithContnent; return branchesWithContnent;
} }
function applyDeltas(text: string, deltas: Delta[]) { function applyDeltas(text: string, ds: deltas.Delta[]) {
if (!deltas) return text; if (!ds) return text;
const operations = deltas.flatMap((delta) => delta.operations); const operations = ds.flatMap((delta) => delta.operations);
operations.forEach((operation) => { operations.forEach((operation) => {
if (Operation.isInsert(operation)) { if (isInsert(operation)) {
text = text =
text.slice(0, operation.insert[0]) + operation.insert[1] + text.slice(operation.insert[0]); text.slice(0, operation.insert[0]) + operation.insert[1] + text.slice(operation.insert[0]);
} else if (Operation.isDelete(operation)) { } else if (isDelete(operation)) {
text = text =
text.slice(0, operation.delete[0]) + text.slice(operation.delete[0] + operation.delete[1]); text.slice(0, operation.delete[0]) + text.slice(operation.delete[0] + operation.delete[1]);
} }

View File

@ -1,25 +0,0 @@
/**
* Virtual Branch feature package (experiment)
*
* There are three interesting data types coming from the rust IPC api:
* - Branch, representing a virtual branch
* - BranchData, representing a remote branch
* - BaseBranch, representing a target base remote branch
*
* The three types are obtained as reactive stores from the BranchStoresCache's methods:
* - getVirtualBranchStore - List of Branch (virtual branches)
* - getRemoteBranchStore - List of BranchData (remote branches)
* - getBaseBranchStore - BaseBranch (single target branch)
*
* BranchController is a class where all virtual branch operations are performed
* This class gets the three stores injected at construction so that any related updates can be peformed
*
* Note to self:
*
* - Create the BranchStoresCacheat the top level (where projects are listed),
* so that it can take advantage of caching, making project navigation quicker.
* - Create the BranchController at the level of a specific project and inject it to components that need it.
*/
export { Branch, File, Hunk, Commit, BranchData, BaseBranch } from './types';
export { BranchStoresCache } from './branchStoresCache';
export { BranchController } from './branchController';

View File

@ -2,7 +2,8 @@
import '../styles/main.postcss'; import '../styles/main.postcss';
import { open } from '@tauri-apps/api/dialog'; import { open } from '@tauri-apps/api/dialog';
import { toasts, Toaster, events, hotkeys, stores } from '$lib'; import { toasts, Toaster, events, hotkeys } from '$lib';
import { userStore } from '$lib/stores/user';
import type { LayoutData } from './$types'; import type { LayoutData } from './$types';
import { Link, Tooltip } from '$lib/components'; import { Link, Tooltip } from '$lib/components';
import { IconEmail } from '$lib/icons'; import { IconEmail } from '$lib/icons';
@ -20,7 +21,7 @@
export let data: LayoutData; export let data: LayoutData;
const { posthog, projects, sentry, cloud } = data; const { posthog, projects, sentry, cloud } = data;
const user = stores.user; const user = userStore;
const userSettings = loadUserSettings(); const userSettings = loadUserSettings();
initTheme(userSettings); initTheme(userSettings);

View File

@ -1,16 +1,17 @@
import type { LayoutLoad } from './$types'; import type { LayoutLoad } from './$types';
import { api } from '$lib'; import { getCloudApiClient } from '$lib/api/cloud/api';
import { projectsStore } from '$lib/api/ipc/projects';
import Posthog from '$lib/posthog'; import Posthog from '$lib/posthog';
import Sentry from '$lib/sentry'; import Sentry from '$lib/sentry';
import { BranchStoresCache } from '$lib/vbranches'; import { BranchStoresCache } from '$lib/vbranches/branchStoresCache';
export const ssr = false; export const ssr = false;
export const prerender = false; export const prerender = false;
export const csr = true; export const csr = true;
export const load: LayoutLoad = ({ fetch: realFetch }: { fetch: typeof fetch }) => ({ export const load: LayoutLoad = ({ fetch: realFetch }: { fetch: typeof fetch }) => ({
projects: api.projects.Projects(), projects: projectsStore,
cloud: api.CloudApi({ fetch: realFetch }), cloud: getCloudApiClient({ fetch: realFetch }),
branchStoresCache: new BranchStoresCache(), branchStoresCache: new BranchStoresCache(),
posthog: Posthog(), posthog: Posthog(),
sentry: Sentry() sentry: Sentry()

View File

@ -1,5 +1,5 @@
<script lang="ts"> <script lang="ts">
import type { Project } from '$lib/api'; import type { Project } from '$lib/api/ipc/projects';
import { IconHome } from '$lib/icons'; import { IconHome } from '$lib/icons';
import { goto } from '$app/navigation'; import { goto } from '$app/navigation';

View File

@ -4,28 +4,29 @@
import { asyncDerived } from '@square/svelte-store'; import { asyncDerived } from '@square/svelte-store';
import { compareDesc, formatDistanceToNow } from 'date-fns'; import { compareDesc, formatDistanceToNow } from 'date-fns';
import { IconFolder, IconLoading } from '$lib/icons'; import { IconFolder, IconLoading } from '$lib/icons';
import { toasts, api, stores } from '$lib'; import type { getCloudApiClient } from '$lib/api/cloud/api';
import { userStore } from '$lib/stores/user';
import { getProjectStore, projectsStore } from '$lib/api/ipc/projects';
import * as toasts from '$lib/toasts';
import IconFolderPlus from '$lib/icons/IconFolderPlus.svelte'; import IconFolderPlus from '$lib/icons/IconFolderPlus.svelte';
import { goto } from '$app/navigation'; import { goto } from '$app/navigation';
export let projects: ReturnType<typeof api.projects.Projects>; export let projects: typeof projectsStore;
export let cloud: ReturnType<typeof api.CloudApi>; export let cloud: ReturnType<typeof getCloudApiClient>;
const user = stores.user; const cloudProjects = asyncDerived(userStore, async (user) =>
const cloudProjects = asyncDerived(user, async (user) =>
user ? await cloud.projects.list(user.access_token) : [] user ? await cloud.projects.list(user.access_token) : []
); );
let selectedRepositoryId: string | null = null; let selectedRepositoryId: string | null = null;
let project: ReturnType<typeof api.projects.Project> | undefined; let project: ReturnType<typeof getProjectStore> | undefined;
export async function show(id: string) { export async function show(id: string) {
await user.load(); await userStore.load();
await cloudProjects.load(); await cloudProjects.load();
if ($user === null) return; if ($userStore === null) return;
project = api.projects.Project({ id }); project = getProjectStore({ id });
modal.show(); modal.show();
} }
@ -42,8 +43,8 @@
await project await project
.update({ api: { ...existingCloudProject, sync: true } }) .update({ api: { ...existingCloudProject, sync: true } })
.then(() => toasts.success(`Project linked`)); .then(() => toasts.success(`Project linked`));
} else if (selectedRepositoryId === null && $user && project && $project) { } else if (selectedRepositoryId === null && $userStore && project && $project) {
const cloudProject = await cloud.projects.create($user?.access_token, { const cloudProject = await cloud.projects.create($userStore?.access_token, {
name: $project.title, name: $project.title,
description: $project.description, description: $project.description,
uid: $project.id uid: $project.id
@ -112,7 +113,7 @@
</button> </button>
{#each $cloudProjects {#each $cloudProjects
// filter out projects that are already linked // filter out projects that are already linked
.map( (project) => ({ ...project, disabled: $projects?.some((p) => p?.api?.repository_id === project.repository_id) }) ) .map( (project) => ({ ...project, disabled: $projectsStore?.some((p) => p?.api?.repository_id === project.repository_id) }) )
// sort by last updated // sort by last updated
.sort((a, b) => compareDesc(new Date(a.updated_at), new Date(b.updated_at))) .sort((a, b) => compareDesc(new Date(a.updated_at), new Date(b.updated_at)))
// sort by name // sort by name

View File

@ -1,11 +1,12 @@
<script lang="ts"> <script lang="ts">
import { toasts, api } from '$lib'; import { toasts } from '$lib';
import * as zip from '$lib/api/ipc/zip';
import { Button, Checkbox, Modal } from '$lib/components'; import { Button, Checkbox, Modal } from '$lib/components';
import { page } from '$app/stores'; import { page } from '$app/stores';
import type { User } from '$lib/api'; import type { User, getCloudApiClient } from '$lib/api/cloud/api';
export let user: User | null; export let user: User | null;
export let cloud: ReturnType<typeof api.CloudApi>; export let cloud: ReturnType<typeof getCloudApiClient>;
export function show() { export function show() {
modal.show(); modal.show();
@ -42,12 +43,12 @@
const email = user?.email ?? emailInputValue; const email = user?.email ?? emailInputValue;
toasts.promise( toasts.promise(
Promise.all([ Promise.all([
sendLogs ? api.zip.logs().then((path) => readZipFile(path, 'logs.zip')) : undefined, sendLogs ? zip.logs().then((path) => readZipFile(path, 'logs.zip')) : undefined,
sendProjectData sendProjectData
? api.zip.gitbutlerData({ projectId }).then((path) => readZipFile(path, 'data.zip')) ? zip.gitbutlerData({ projectId }).then((path) => readZipFile(path, 'data.zip'))
: undefined, : undefined,
sendProjectRepository sendProjectRepository
? api.zip.projectData({ projectId }).then((path) => readZipFile(path, 'project.zip')) ? zip.projectData({ projectId }).then((path) => readZipFile(path, 'project.zip'))
: undefined : undefined
]).then(async ([logs, data, repo]) => ]).then(async ([logs, data, repo]) =>
cloud.feedback.create(user?.access_token, { cloud.feedback.create(user?.access_token, {

View File

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

View File

@ -7,23 +7,24 @@
import { Button, Statuses, Tooltip } from '$lib/components'; import { Button, Statuses, Tooltip } from '$lib/components';
import { goto } from '$app/navigation'; import { goto } from '$app/navigation';
import Chat from './Chat.svelte'; import Chat from './Chat.svelte';
import { Loaded } from 'svelte-loadable-store';
export let data: PageData; export let data: PageData;
$: project = derived(data.project, (project) => project); const { project, statuses, sessions, head } = data;
$: statuses = derived(data.statuses, (statuses) => statuses);
$: sessions = derived(data.sessions, (sessions) => sessions);
$: head = derived(data.head, (head) => head);
$: recentSessions = derived( $: recentSessions = derived(
sessions, sessions,
(sessions) => { (item) => {
const lastFourDaysOfSessions = sessions?.filter( if (Loaded.isValue(item)) {
(session) => session.meta.startTimestampMs >= getTime(subDays(new Date(), 4)) const lastFourDaysOfSessions = item.value?.filter(
(result) => result.meta.startTimestampMs >= getTime(subDays(new Date(), 4))
); );
if (lastFourDaysOfSessions?.length >= 4) return lastFourDaysOfSessions; if (lastFourDaysOfSessions?.length >= 4) return lastFourDaysOfSessions;
return sessions return item.value
?.slice(0, 4) ?.slice(0, 4)
.sort((a, b) => b.meta.startTimestampMs - a.meta.startTimestampMs); .sort((a, b) => b.meta.startTimestampMs - a.meta.startTimestampMs);
}
return [];
}, },
[] []
); );

View File

@ -1,6 +1,6 @@
import type { PageLoad } from './$types'; import type { PageLoad } from './$types';
import { git } from '$lib/api'; import { getActivitiesStore } from '$lib/api/git/activities';
export const load: PageLoad = async ({ params }) => ({ export const load: PageLoad = async ({ params }) => ({
activity: git.activities.Activities({ projectId: params.projectId }) activity: getActivitiesStore({ projectId: params.projectId })
}); });

View File

@ -1,13 +1,14 @@
<script lang="ts"> <script lang="ts">
import { Button } from '$lib/components'; import { Button } from '$lib/components';
import { CloudApi, type Project } from '$lib/api'; import { getCloudApiClient } from '$lib/api/cloud/api';
import { stores } from '$lib'; import { userStore } from '$lib/stores/user';
import type { Project } from '$lib/api/ipc/projects';
import { marked } from 'marked'; import { marked } from 'marked';
import { IconAISparkles } from '$lib/icons'; import { IconAISparkles } from '$lib/icons';
import { onMount } from 'svelte'; import { onMount } from 'svelte';
const cloud = CloudApi(); const cloud = getCloudApiClient();
const user = stores.user; const user = userStore;
export let project: Project; export let project: Project;

View File

@ -1,5 +1,5 @@
<script lang="ts"> <script lang="ts">
import type { Delta } from '$lib/api'; import type { Delta } from '$lib/api/ipc/deltas';
import { fillBuckets, type Bucket } from './histogram'; import { fillBuckets, type Bucket } from './histogram';
export let deltas: Delta[]; export let deltas: Delta[];

View File

@ -1,6 +1,6 @@
<script lang="ts"> <script lang="ts">
import { format, startOfDay } from 'date-fns'; import { format, startOfDay } from 'date-fns';
import type { Delta } from '$lib/api'; import type { Delta } from '$lib/api/ipc/deltas';
import { generateBuckets } from './histogram'; import { generateBuckets } from './histogram';
import { derived, Loaded } from 'svelte-loadable-store'; import { derived, Loaded } from 'svelte-loadable-store';
import FileActivity from './FileActivity.svelte'; import FileActivity from './FileActivity.svelte';
@ -8,13 +8,13 @@
import { Link } from '$lib/components'; import { Link } from '$lib/components';
import { IconRewind, IconPlayerPlayFilled, IconLoading, IconSparkle } from '$lib/icons'; import { IconRewind, IconPlayerPlayFilled, IconLoading, IconSparkle } from '$lib/icons';
import { collapse } from '$lib/paths'; import { collapse } from '$lib/paths';
import type { Session } from '$lib/api'; import type { Session } from '$lib/api/ipc/sessions';
import { stores } from '$lib'; import { getDeltasStore } from '$lib/stores/deltas';
export let sessions: Session[]; export let sessions: Session[];
$: sessionDeltas = (sessions ?? []).map(({ id, projectId }) => $: sessionDeltas = (sessions ?? []).map(({ id, projectId }) =>
stores.deltas({ sessionId: id, projectId }) getDeltasStore({ sessionId: id, projectId })
); );
$: deltasByDate = derived(sessionDeltas, (sessionDeltas) => $: deltasByDate = derived(sessionDeltas, (sessionDeltas) =>

View File

@ -1,6 +1,9 @@
<script lang="ts"> <script lang="ts">
import { toasts } from '$lib'; import { toasts } from '$lib';
import { Status, type Project, git, type CloudApi, type User } from '$lib/api'; import type { Project } from '$lib/api/ipc/projects';
import type { User, getCloudApiClient } from '$lib/api/cloud/api';
import { isUnstaged, type Status } from '$lib/api/git/statuses';
import { commit, stage } from '$lib/api/git';
import { Button, Overlay, Link } from '$lib/components'; import { Button, Overlay, Link } from '$lib/components';
import { IconGitBranch, IconSparkle } from '$lib/icons'; import { IconGitBranch, IconSparkle } from '$lib/icons';
import { Stats } from '$lib/components'; import { Stats } from '$lib/components';
@ -14,7 +17,7 @@
export let statuses: Record<string, Status>; export let statuses: Record<string, Status>;
export let diffs: Record<string, string>; export let diffs: Record<string, string>;
export let user: User; export let user: User;
export let cloud: ReturnType<typeof CloudApi>; export let cloud: ReturnType<typeof getCloudApiClient>;
let summary = ''; let summary = '';
let description = ''; let description = '';
@ -23,10 +26,10 @@
const stageAll = async () => { const stageAll = async () => {
const paths = Object.entries(statuses) const paths = Object.entries(statuses)
.filter((entry) => Status.isUnstaged(entry[1])) .filter((entry) => isUnstaged(entry[1]))
.map(([path]) => path); .map(([path]) => path);
if (paths.length === 0) return; if (paths.length === 0) return;
await git.stage({ await stage({
projectId: project.id, projectId: project.id,
paths paths
}); });
@ -66,8 +69,7 @@
isCommitting = true; isCommitting = true;
await stageAll(); await stageAll();
git commit({
.commit({
projectId: project.id, projectId: project.id,
message: description.length > 0 ? `${summary}\n\n${description}` : summary, message: description.length > 0 ? `${summary}\n\n${description}` : summary,
push: false push: false

View File

@ -3,13 +3,15 @@
import { Button, Checkbox, DiffContext } from '$lib/components'; import { Button, Checkbox, DiffContext } from '$lib/components';
import { collapse } from '$lib/paths'; import { collapse } from '$lib/paths';
import { derived, writable } from '@square/svelte-store'; import { derived, writable } from '@square/svelte-store';
import { git, Status } from '$lib/api'; import { isStaged, isUnstaged } from '$lib/api/git/statuses';
import { userStore } from '$lib/stores/user';
import { commit, stage, unstage } from '$lib/api/git';
import DiffViewer from './DiffViewer.svelte'; import DiffViewer from './DiffViewer.svelte';
import { page } from '$app/stores'; import { page } from '$app/stores';
import { error, success } from '$lib/toasts'; import { error, success } from '$lib/toasts';
import { fly } from 'svelte/transition'; import { fly } from 'svelte/transition';
import { Modal } from '$lib/components'; import { Modal } from '$lib/components';
import { hotkeys, stores } from '$lib'; import { hotkeys } from '$lib';
import { IconChevronDown, IconChevronUp } from '$lib/icons'; import { IconChevronDown, IconChevronUp } from '$lib/icons';
import { onMount } from 'svelte'; import { onMount } from 'svelte';
import { unsubscribe } from '$lib/utils'; import { unsubscribe } from '$lib/utils';
@ -17,19 +19,19 @@
export let data: PageData; export let data: PageData;
let { statuses, diffs, cloud, project } = data; let { statuses, diffs, cloud, project } = data;
const user = stores.user; const user = userStore;
let fullContext = false; let fullContext = false;
let context = 3; let context = 3;
const stagedFiles = derived(statuses, (statuses) => const stagedFiles = derived(statuses, (statuses) =>
Object.entries(statuses ?? {}) Object.entries(statuses ?? {})
.filter((status) => Status.isStaged(status[1])) .filter((status) => isStaged(status[1]))
.map(([path]) => path) .map(([path]) => path)
); );
const unstagedFiles = derived(statuses, (statuses) => const unstagedFiles = derived(statuses, (statuses) =>
Object.entries(statuses ?? {}) Object.entries(statuses ?? {})
.filter((status) => Status.isUnstaged(status[1])) .filter((status) => isUnstaged(status[1]))
.map(([path]) => path) .map(([path]) => path)
); );
const allFiles = derived(statuses, (statuses) => const allFiles = derived(statuses, (statuses) =>
@ -97,8 +99,7 @@
const description = formData.get('description') as string; const description = formData.get('description') as string;
isCommitting = true; isCommitting = true;
git commit({
.commit({
projectId: $page.params.projectId, projectId: $page.params.projectId,
message: description.length > 0 ? `${summary}\n\n${description}` : summary, message: description.length > 0 ? `${summary}\n\n${description}` : summary,
push: false push: false
@ -123,9 +124,7 @@
if ($user === null) return; if ($user === null) return;
const partialDiff = Object.fromEntries( const partialDiff = Object.fromEntries(
Object.entries($diffs ?? {}).filter( Object.entries($diffs ?? {}).filter(([key]) => $statuses[key] && isStaged($statuses[key]))
([key]) => $statuses[key] && Status.isStaged($statuses[key])
)
); );
const diff = Object.values(partialDiff).join('\n').slice(0, 5000); const diff = Object.values(partialDiff).join('\n').slice(0, 5000);
@ -158,21 +157,17 @@
const onGroupCheckboxClick = (e: Event) => { const onGroupCheckboxClick = (e: Event) => {
const target = e.target as HTMLInputElement; const target = e.target as HTMLInputElement;
if (target.checked) { if (target.checked) {
git stage({
.stage({
projectId: $page.params.projectId, projectId: $page.params.projectId,
paths: $unstagedFiles paths: $unstagedFiles
}) }).catch(() => {
.catch(() => {
error('Failed to stage files'); error('Failed to stage files');
}); });
} else { } else {
git unstage({
.unstage({
projectId: $page.params.projectId, projectId: $page.params.projectId,
paths: $stagedFiles paths: $stagedFiles
}) }).catch(() => {
.catch(() => {
error('Failed to unstage files'); error('Failed to unstage files');
}); });
} }
@ -210,10 +205,10 @@
// an git // an git
statuses.subscribe((statuses) => statuses.subscribe((statuses) =>
Object.entries(statuses ?? {}).forEach(([file, status]) => { Object.entries(statuses ?? {}).forEach(([file, status]) => {
const isStagedAdded = Status.isStaged(status) && status.staged === 'added'; const isStagedAdded = isStaged(status) && status.staged === 'added';
const isUnstagedDeleted = Status.isUnstaged(status) && status.unstaged === 'deleted'; const isUnstagedDeleted = isUnstaged(status) && status.unstaged === 'deleted';
if (isStagedAdded && isUnstagedDeleted) if (isStagedAdded && isUnstagedDeleted)
git.stage({ projectId: $page.params.projectId, paths: [file] }); stage({ projectId: $page.params.projectId, paths: [file] });
}) })
); );
@ -288,20 +283,18 @@
class="file-changed-item mx-1 mt-1 flex select-text items-center gap-2 rounded bg-card-default px-1 py-1" class="file-changed-item mx-1 mt-1 flex select-text items-center gap-2 rounded bg-card-default px-1 py-1"
> >
<Checkbox <Checkbox
checked={Status.isStaged(status)} checked={isStaged(status)}
name="path" name="path"
disabled={isCommitting || isGeneratingCommitMessage} disabled={isCommitting || isGeneratingCommitMessage}
value={path} value={path}
on:click={() => { on:click={() => {
Status.isStaged(status) isStaged(status)
? git ? unstage({ projectId: $page.params.projectId, paths: [path] }).catch(
.unstage({ projectId: $page.params.projectId, paths: [path] }) () => {
.catch(() => {
error('Failed to unstage file'); error('Failed to unstage file');
}) }
: git )
.stage({ projectId: $page.params.projectId, paths: [path] }) : stage({ projectId: $page.params.projectId, paths: [path] }).catch(() => {
.catch(() => {
error('Failed to stage file'); error('Failed to stage file');
}); });
}} }}

View File

@ -1,17 +1,18 @@
<script lang="ts"> <script lang="ts">
import { page } from '$app/stores'; import { page } from '$app/stores';
import { stores, api } from '$lib'; import { getSessionStore } from '$lib/stores/sessions';
import * as deltas from '$lib/api/ipc/deltas';
import { format } from 'date-fns'; import { format } from 'date-fns';
import { derived, Loaded } from 'svelte-loadable-store'; import { derived, Loaded } from 'svelte-loadable-store';
const sessions = stores.sessions({ projectId: $page.params.projectId }); const sessions = getSessionStore({ projectId: $page.params.projectId });
$: fileFilter = $page.url.searchParams.get('file'); $: fileFilter = $page.url.searchParams.get('file');
const dates = derived(sessions, async (sessions) => { const dates = derived(sessions, async (sessions) => {
const sessionDeltas = await Promise.all( const sessionDeltas = await Promise.all(
sessions.map((session) => sessions.map((session) =>
api.deltas.list({ deltas.list({
projectId: $page.params.projectId, projectId: $page.params.projectId,
sessionId: session.id, sessionId: session.id,
paths: fileFilter ? [fileFilter] : undefined paths: fileFilter ? [fileFilter] : undefined

View File

@ -1,11 +1,11 @@
import { error, redirect } from '@sveltejs/kit'; import { error, redirect } from '@sveltejs/kit';
import { format, compareDesc } from 'date-fns'; import { format, compareDesc } from 'date-fns';
import type { PageLoad } from './$types'; import type { PageLoad } from './$types';
import { stores } from '$lib'; import { getSessionStore } from '$lib/stores/sessions';
import { promisify } from 'svelte-loadable-store'; import { promisify } from 'svelte-loadable-store';
export const load: PageLoad = async ({ url, params }) => { export const load: PageLoad = async ({ url, params }) => {
const sessions = await promisify(stores.sessions({ projectId: params.projectId })); const sessions = await promisify(getSessionStore({ projectId: params.projectId }));
const latestDate = sessions const latestDate = sessions
.map((session) => session.meta.startTimestampMs) .map((session) => session.meta.startTimestampMs)
.sort(compareDesc) .sort(compareDesc)

View File

@ -4,12 +4,16 @@
import { derived, Loaded } from 'svelte-loadable-store'; import { derived, Loaded } from 'svelte-loadable-store';
import { format } from 'date-fns'; import { format } from 'date-fns';
import { onMount } from 'svelte'; import { onMount } from 'svelte';
import { api, events, hotkeys, stores } from '$lib'; import { events, hotkeys } from '$lib';
import BookmarkModal from './BookmarkModal.svelte'; import BookmarkModal from './BookmarkModal.svelte';
import { unsubscribe } from '$lib/utils'; import { unsubscribe } from '$lib/utils';
import SessionsList from './SessionsList.svelte'; import SessionsList from './SessionsList.svelte';
import SessionNavigations from './SessionNavigations.svelte'; import SessionNavigations from './SessionNavigations.svelte';
import { IconLoading } from '$lib/icons'; import { IconLoading } from '$lib/icons';
import { getSessionStore } from '$lib/stores/sessions';
import { getDeltasStore } from '$lib/stores/deltas';
import { getFilesStore } from '$lib/stores/files';
import * as bookmarks from '$lib/api/ipc/bookmarks';
export let data: LayoutData; export let data: LayoutData;
const { currentFilepath, currentTimestamp } = data; const { currentFilepath, currentTimestamp } = data;
@ -17,7 +21,7 @@
const filter = derived(page, (page) => page.url.searchParams.get('file')); const filter = derived(page, (page) => page.url.searchParams.get('file'));
const projectId = derived(page, (page) => page.params.projectId); const projectId = derived(page, (page) => page.params.projectId);
$: sessions = stores.sessions({ projectId: $page.params.projectId }); $: sessions = getSessionStore({ projectId: $page.params.projectId });
$: dateSessions = derived([sessions, page], ([sessions, page]) => $: dateSessions = derived([sessions, page], ([sessions, page]) =>
sessions sessions
.filter((session) => format(session.meta.startTimestampMs, 'yyyy-MM-dd') === page.params.date) .filter((session) => format(session.meta.startTimestampMs, 'yyyy-MM-dd') === page.params.date)
@ -27,12 +31,12 @@
$: richSessions = derived([dateSessions, projectId, filter], ([sessions, projectId, filter]) => $: richSessions = derived([dateSessions, projectId, filter], ([sessions, projectId, filter]) =>
sessions.map((session) => ({ sessions.map((session) => ({
...session, ...session,
deltas: derived(stores.deltas({ projectId: projectId, sessionId: session.id }), (deltas) => deltas: derived(getDeltasStore({ projectId: projectId, sessionId: session.id }), (deltas) =>
Object.fromEntries( Object.fromEntries(
Object.entries(deltas).filter(([path]) => (filter ? path === filter : true)) Object.entries(deltas).filter(([path]) => (filter ? path === filter : true))
) )
), ),
files: derived(stores.files({ projectId, sessionId: session.id }), (files) => files: derived(getFilesStore({ projectId, sessionId: session.id }), (files) =>
Object.fromEntries( Object.fromEntries(
Object.entries(files).filter(([path]) => (filter ? path === filter : true)) Object.entries(files).filter(([path]) => (filter ? path === filter : true))
) )
@ -54,7 +58,7 @@
events.on('openBookmarkModal', () => bookmarkModal?.show($currentTimestamp)), events.on('openBookmarkModal', () => bookmarkModal?.show($currentTimestamp)),
hotkeys.on('Meta+Shift+D', () => bookmarkModal?.show($currentTimestamp)), hotkeys.on('Meta+Shift+D', () => bookmarkModal?.show($currentTimestamp)),
hotkeys.on('D', async () => { hotkeys.on('D', async () => {
const existing = await api.bookmarks.list({ const existing = await bookmarks.list({
projectId: $page.params.projectId, projectId: $page.params.projectId,
range: { range: {
start: $currentTimestamp, start: $currentTimestamp,
@ -73,7 +77,7 @@
...existing[0], ...existing[0],
deleted: !existing[0].deleted deleted: !existing[0].deleted
}; };
api.bookmarks.upsert(newBookmark); bookmarks.upsert(newBookmark);
}) })
) )
); );

View File

@ -1,11 +1,11 @@
import { redirect, error } from '@sveltejs/kit'; import { redirect, error } from '@sveltejs/kit';
import { format } from 'date-fns'; import { format } from 'date-fns';
import type { PageLoad } from './$types'; import type { PageLoad } from './$types';
import { stores } from '$lib'; import { getSessionStore } from '$lib/stores/sessions';
import { promisify } from 'svelte-loadable-store'; import { promisify } from 'svelte-loadable-store';
export const load: PageLoad = async ({ params, url }) => { export const load: PageLoad = async ({ params, url }) => {
const sessions = await promisify(stores.sessions({ projectId: params.projectId })); const sessions = await promisify(getSessionStore({ projectId: params.projectId }));
const dateSessions = sessions.filter( const dateSessions = sessions.filter(
(session) => format(session.meta.startTimestampMs, 'yyyy-MM-dd') === params.date (session) => format(session.meta.startTimestampMs, 'yyyy-MM-dd') === params.date
); );

View File

@ -1,5 +1,6 @@
<script lang="ts"> <script lang="ts">
import { toasts, api } from '$lib'; import { toasts } from '$lib';
import * as bookmarks from '$lib/api/ipc/bookmarks';
import { Button, Modal } from '$lib/components'; import { Button, Modal } from '$lib/components';
import { IconBookmarkFilled } from '$lib/icons'; import { IconBookmarkFilled } from '$lib/icons';
@ -16,7 +17,7 @@
export async function show(ts: number) { export async function show(ts: number) {
reset(); reset();
timestampMs = ts; timestampMs = ts;
const existing = await api.bookmarks.list({ const existing = await bookmarks.list({
projectId, projectId,
range: { range: {
start: ts, start: ts,
@ -35,7 +36,7 @@
Promise.resolve() Promise.resolve()
.then(() => (isCreating = true)) .then(() => (isCreating = true))
.then(() => .then(() =>
api.bookmarks.upsert({ bookmarks.upsert({
projectId, projectId,
note, note,
timestampMs: timestampMs ?? Date.now(), timestampMs: timestampMs ?? Date.now(),

View File

@ -1,9 +1,10 @@
<script lang="ts"> <script lang="ts">
import { Operation, type Delta, type Session } from '$lib/api'; import type { Session } from '$lib/api/ipc/sessions';
import { isInsert, type Delta, isDelete } from '$lib/api/ipc/deltas';
import { page } from '$app/stores'; import { page } from '$app/stores';
import { collapse } from '$lib/paths'; import { collapse } from '$lib/paths';
import { derived } from '@square/svelte-store'; import { derived } from '@square/svelte-store';
import { stores } from '$lib'; import { getBookmarksStore } from '$lib/stores/bookmarks';
import { IconBookmarkFilled } from '$lib/icons'; import { IconBookmarkFilled } from '$lib/icons';
import { line } from '$lib/diff'; import { line } from '$lib/diff';
import { Stats } from '$lib/components'; import { Stats } from '$lib/components';
@ -19,12 +20,12 @@
const operations = deltas.flatMap((delta) => delta.operations); const operations = deltas.flatMap((delta) => delta.operations);
operations.forEach((operation) => { operations.forEach((operation) => {
if (Operation.isInsert(operation)) { if (isInsert(operation)) {
text = text =
text.slice(0, operation.insert[0]) + text.slice(0, operation.insert[0]) +
operation.insert[1] + operation.insert[1] +
text.slice(operation.insert[0]); text.slice(operation.insert[0]);
} else if (Operation.isDelete(operation)) { } else if (isDelete(operation)) {
text = text =
text.slice(0, operation.delete[0]) + text.slice(0, operation.delete[0]) +
text.slice(operation.delete[0] + operation.delete[1]); text.slice(operation.delete[0] + operation.delete[1]);
@ -51,7 +52,7 @@
}) })
.reduce((a, b) => [a[0] + b[0], a[1] + b[1]], [0, 0]); .reduce((a, b) => [a[0] + b[0], a[1] + b[1]], [0, 0]);
$: bookmarks = derived(stores.bookmarks.list({ projectId: session.projectId }), (bookmarks) => { $: bookmarksStore = derived(getBookmarksStore({ projectId: session.projectId }), (bookmarks) => {
if (bookmarks.isLoading) return []; if (bookmarks.isLoading) return [];
if (Loaded.isError(bookmarks)) return []; if (Loaded.isError(bookmarks)) return [];
const timestamps = Object.values(deltas ?? {}).flatMap((deltas) => const timestamps = Object.values(deltas ?? {}).flatMap((deltas) =>
@ -110,8 +111,8 @@
class:bg-card-active={isCurrent} class:bg-card-active={isCurrent}
class="session-card relative rounded border-[0.5px] border-gb-700 text-zinc-300 shadow-md transition-colors duration-200 ease-in-out hover:bg-card-active" class="session-card relative rounded border-[0.5px] border-gb-700 text-zinc-300 shadow-md transition-colors duration-200 ease-in-out hover:bg-card-active"
> >
{#await bookmarks.load() then} {#await bookmarksStore.load() then}
{#if $bookmarks?.length > 0} {#if $bookmarksStore?.length > 0}
<div class="absolute right-5 top-0 flex gap-2 overflow-hidden text-bookmark-selected"> <div class="absolute right-5 top-0 flex gap-2 overflow-hidden text-bookmark-selected">
<IconBookmarkFilled class="-mt-1 h-4 w-4" /> <IconBookmarkFilled class="-mt-1 h-4 w-4" />
</div> </div>

View File

@ -4,7 +4,8 @@
import { page } from '$app/stores'; import { page } from '$app/stores';
import { hotkeys } from '$lib'; import { hotkeys } from '$lib';
import type { Delta, Session } from '$lib/api'; import type { Session } from '$lib/api/ipc/sessions';
import type { Delta } from '$lib/api/ipc/deltas';
import { unsubscribe } from '$lib/utils'; import { unsubscribe } from '$lib/utils';
import type { Readable } from '@square/svelte-store'; import type { Readable } from '@square/svelte-store';
import { onMount } from 'svelte'; import { onMount } from 'svelte';

View File

@ -1,5 +1,6 @@
<script lang="ts"> <script lang="ts">
import type { Delta, Session } from '$lib/api'; import type { Session } from '$lib/api/ipc/sessions';
import type { Delta } from '$lib/api/ipc/deltas';
import type { Readable } from '@square/svelte-store'; import type { Readable } from '@square/svelte-store';
import { Loaded, type Loadable } from 'svelte-loadable-store'; import { Loaded, type Loadable } from 'svelte-loadable-store';
import { derived } from 'svelte-loadable-store'; import { derived } from 'svelte-loadable-store';

View File

@ -5,12 +5,15 @@
import { get, writable } from '@square/svelte-store'; import { get, writable } from '@square/svelte-store';
import { derived, Loaded } from 'svelte-loadable-store'; import { derived, Loaded } from 'svelte-loadable-store';
import { format } from 'date-fns'; import { format } from 'date-fns';
import { stores } from '$lib';
import Playback from './Playback.svelte'; import Playback from './Playback.svelte';
import type { Frame as FrameType } from './frame'; import type { Frame as FrameType } from './frame';
import Frame from './Frame.svelte'; import Frame from './Frame.svelte';
import Info from './Info.svelte'; import Info from './Info.svelte';
import type { Delta } from '$lib/api'; import type { Delta } from '$lib/api/ipc/deltas';
import { getSessionStore } from '$lib/stores/sessions';
import { getDeltasStore } from '$lib/stores/deltas';
import { getFilesStore } from '$lib/stores/files';
import { getBookmarksStore } from '$lib/stores/bookmarks';
export let data: PageData; export let data: PageData;
const { currentFilepath, currentTimestamp, currentSessionId } = data; const { currentFilepath, currentTimestamp, currentSessionId } = data;
@ -26,24 +29,23 @@
const filter = derived(page, (page) => page.url.searchParams.get('file')); const filter = derived(page, (page) => page.url.searchParams.get('file'));
const projectId = derived(page, (page) => page.params.projectId); const projectId = derived(page, (page) => page.params.projectId);
$: bookmarks = stores.bookmarks.list({ projectId: $page.params.projectId }); $: bookmarks = getBookmarksStore({ projectId: $page.params.projectId });
$: sessions = stores.sessions({ projectId: $page.params.projectId }); $: sessions = getSessionStore({ projectId: $page.params.projectId });
$: dateSessions = derived([sessions, page], ([sessions, page]) => $: dateSessions = derived([sessions, page], async ([sessions, page]) =>
sessions?.filter( sessions?.filter(
(session) => format(session.meta.startTimestampMs, 'yyyy-MM-dd') === page.params.date (session) => format(session.meta.startTimestampMs, 'yyyy-MM-dd') === page.params.date
) )
); );
$: richSessions = derived([dateSessions, filter, projectId], ([sessions, filter, projectId]) => $: richSessions = derived([dateSessions, filter, projectId], ([sessions, filter, projectId]) =>
sessions.map((session) => ({ sessions.map((session) => ({
...session, ...session,
deltas: derived(stores.deltas({ projectId: projectId, sessionId: session.id }), (deltas) => deltas: derived(getDeltasStore({ projectId: projectId, sessionId: session.id }), (deltas) =>
Object.entries(deltas) Object.entries(deltas)
.filter(([path]) => (filter ? path === filter : true)) .filter(([path]) => (filter ? path === filter : true))
.flatMap(([path, deltas]) => deltas.map((delta) => [path, delta] as [string, Delta])) .flatMap(([path, deltas]) => deltas.map((delta) => [path, delta] as [string, Delta]))
.sort((a, b) => a[1].timestampMs - b[1].timestampMs) .sort((a, b) => a[1].timestampMs - b[1].timestampMs)
), ),
files: derived(stores.files({ projectId, sessionId: session.id }), (files) => files: derived(getFilesStore({ projectId, sessionId: session.id }), (files) =>
Object.fromEntries( Object.fromEntries(
Object.entries(files).filter(([path]) => (filter ? path === filter : true)) Object.entries(files).filter(([path]) => (filter ? path === filter : true))
) )

View File

@ -1,5 +1,6 @@
<script lang="ts"> <script lang="ts">
import type { Delta, Session } from '$lib/api'; import type { Session } from '$lib/api/ipc/sessions';
import type { Delta } from '$lib/api/ipc/deltas';
import type { Frame } from './frame'; import type { Frame } from './frame';
import { DeltasViewer } from '$lib/components'; import { DeltasViewer } from '$lib/components';
import type { Readable } from '@square/svelte-store'; import type { Readable } from '@square/svelte-store';

View File

@ -1,20 +1,22 @@
<script lang="ts"> <script lang="ts">
import { stores, api, events } from '$lib'; import { events } from '$lib';
import { collapse } from '$lib/paths'; import { collapse } from '$lib/paths';
import { IconBookmark, IconBookmarkFilled } from '$lib/icons'; import { IconBookmark, IconBookmarkFilled } from '$lib/icons';
import { format } from 'date-fns'; import { format } from 'date-fns';
import { page } from '$app/stores'; import { page } from '$app/stores';
import { Loaded } from 'svelte-loadable-store'; import { Loaded } from 'svelte-loadable-store';
import * as bookmarks from '$lib/api/ipc/bookmarks';
import { getBookmark } from '$lib/stores/bookmarks';
export let timestampMs: number; export let timestampMs: number;
export let filename: string; export let filename: string;
$: bookmark = stores.bookmarks.get({ projectId: $page.params.projectId, timestampMs }); $: bookmark = getBookmark({ projectId: $page.params.projectId, timestampMs });
const toggleBookmark = () => { const toggleBookmark = () => {
if ($bookmark.isLoading) return; if ($bookmark.isLoading) return;
if (Loaded.isError($bookmark)) return; if (Loaded.isError($bookmark)) return;
api.bookmarks.upsert( bookmarks.upsert(
!$bookmark.value !$bookmark.value
? { ? {
projectId: $page.params.projectId, projectId: $page.params.projectId,

View File

@ -1,5 +1,5 @@
<script lang="ts"> <script lang="ts">
import type { Delta } from '$lib/api'; import type { Delta } from '$lib/api/ipc/deltas';
import { IconPlayerPauseFilled, IconPlayerPlayFilled } from '$lib/icons'; import { IconPlayerPauseFilled, IconPlayerPlayFilled } from '$lib/icons';
import { DiffContext } from '$lib/components'; import { DiffContext } from '$lib/components';
import { unsubscribe } from '$lib/utils'; import { unsubscribe } from '$lib/utils';

View File

@ -1,5 +1,6 @@
<script lang="ts"> <script lang="ts">
import type { Bookmark, Delta } from '$lib/api'; import type { Delta } from '$lib/api/ipc/deltas';
import type { Bookmark } from '$lib/api/ipc/bookmarks';
import { derived, Loaded, type Loadable } from 'svelte-loadable-store'; import { derived, Loaded, type Loadable } from 'svelte-loadable-store';
import type { Readable } from '@square/svelte-store'; import type { Readable } from '@square/svelte-store';
import { ModuleChapters, ModuleMarkers, type Marker } from './slider'; import { ModuleChapters, ModuleMarkers, type Marker } from './slider';

View File

@ -1,4 +1,4 @@
import type { Delta } from '$lib/api'; import type { Delta } from '$lib/api/ipc/deltas';
export type Frame = { export type Frame = {
sessionId: string; sessionId: string;

View File

@ -1,13 +1,15 @@
<script lang="ts"> <script lang="ts">
import type { PageData } from './$types'; import type { PageData } from './$types';
import { IconChevronLeft, IconChevronRight, IconLoading } from '$lib/icons'; import { IconChevronLeft, IconChevronRight, IconLoading } from '$lib/icons';
import { files, deltas, searchResults, type SearchResult } from '$lib/api';
import { asyncDerived } from '@square/svelte-store'; import { asyncDerived } from '@square/svelte-store';
import { format, formatDistanceToNow } from 'date-fns'; import { format, formatDistanceToNow } from 'date-fns';
import { DeltasViewer } from '$lib/components'; import { DeltasViewer } from '$lib/components';
import { page } from '$app/stores'; import { page } from '$app/stores';
import { derived } from '@square/svelte-store'; import { derived } from '@square/svelte-store';
import { goto } from '$app/navigation'; import { goto } from '$app/navigation';
import * as search from '$lib/api/ipc/search';
import * as files from '$lib/api/ipc/files';
import * as deltas from '$lib/api/ipc/deltas';
export let data: PageData; export let data: PageData;
const { project } = data; const { project } = data;
@ -19,7 +21,12 @@
const openNextPage = () => goto(`?q=${$query}&offset=${$offset + limit}`); const openNextPage = () => goto(`?q=${$query}&offset=${$offset + limit}`);
const openPrevPage = () => goto(`?q=${$query}&offset=${$offset - limit}`); const openPrevPage = () => goto(`?q=${$query}&offset=${$offset - limit}`);
const fetchResultData = async ({ sessionId, projectId, filePath, index }: SearchResult) => { const fetchResultData = async ({
sessionId,
projectId,
filePath,
index
}: search.SearchResult) => {
const [doc, dd] = await Promise.all([ const [doc, dd] = await Promise.all([
files.list({ projectId, sessionId, paths: [filePath] }).then((r) => r[filePath] ?? ''), files.list({ projectId, sessionId, paths: [filePath] }).then((r) => r[filePath] ?? ''),
deltas deltas
@ -42,7 +49,7 @@
[query, project, offset], [query, project, offset],
async ([query, project, offset]) => { async ([query, project, offset]) => {
if (!query || !project) return { page: [], total: 0, haveNext: false, havePrev: false }; if (!query || !project) return { page: [], total: 0, haveNext: false, havePrev: false };
const results = await searchResults.list({ projectId: project.id, query, limit, offset }); const results = await search.list({ projectId: project.id, query, limit, offset });
return { return {
page: await Promise.all(results.page.map(fetchResultData)), page: await Promise.all(results.page.map(fetchResultData)),
haveNext: offset + limit < results.total, haveNext: offset + limit < results.total,

View File

@ -3,7 +3,7 @@
import ResizeObserver from 'svelte-resize-observer'; import ResizeObserver from 'svelte-resize-observer';
import setupTerminal from './terminal'; import setupTerminal from './terminal';
import 'xterm/css/xterm.css'; import 'xterm/css/xterm.css';
import type { Project } from '$lib/api'; import type { Project } from '$lib/api/ipc/projects';
import { debounce } from '$lib/utils'; import { debounce } from '$lib/utils';
import { Button, Statuses } from '$lib/components'; import { Button, Statuses } from '$lib/components';

View File

@ -1,4 +1,4 @@
import type { Project } from '$lib/api'; import type { Project } from '$lib/api/ipc/projects';
import { Terminal } from 'xterm'; import { Terminal } from 'xterm';
import { CanvasAddon } from 'xterm-addon-canvas'; import { CanvasAddon } from 'xterm-addon-canvas';
import { WebglAddon } from 'xterm-addon-webgl'; import { WebglAddon } from 'xterm-addon-webgl';

View File

@ -3,10 +3,9 @@
import Tray from './Tray.svelte'; import Tray from './Tray.svelte';
import type { PageData } from './$types'; import type { PageData } from './$types';
import { Button } from '$lib/components'; import { Button } from '$lib/components';
import { BranchController } from '$lib/vbranches'; import { BRANCH_CONTROLLER_KEY, BranchController } from '$lib/vbranches/branchController';
import { Loaded } from 'svelte-loadable-store'; import { Loaded } from 'svelte-loadable-store';
import { getContext, setContext } from 'svelte'; import { getContext, setContext } from 'svelte';
import { BRANCH_CONTROLLER_KEY } from '$lib/vbranches/branchController';
import { SETTINGS_CONTEXT, type SettingsStore } from '$lib/userSettings'; import { SETTINGS_CONTEXT, type SettingsStore } from '$lib/userSettings';
import BottomPanel from './BottomPanel.svelte'; import BottomPanel from './BottomPanel.svelte';
import UpstreamBranchLane from './UpstreamBranchLane.svelte'; import UpstreamBranchLane from './UpstreamBranchLane.svelte';

View File

@ -1,7 +1,6 @@
import type { PageLoadEvent } from './$types'; import type { PageLoadEvent } from './$types';
import { invoke } from '$lib/ipc'; import { invoke } from '$lib/ipc';
import { api } from '$lib'; import { getProjectStore, type Project } from '$lib/api/ipc/projects';
import type { Project } from '$lib/api';
import type { Loadable } from '@square/svelte-store'; import type { Loadable } from '@square/svelte-store';
async function getRemoteBranches(params: { projectId: string }) { async function getRemoteBranches(params: { projectId: string }) {
@ -11,7 +10,7 @@ async function getRemoteBranches(params: { projectId: string }) {
export async function load({ parent, params }: PageLoadEvent) { export async function load({ parent, params }: PageLoadEvent) {
const projectId = params.projectId; const projectId = params.projectId;
const remoteBranchNames = await getRemoteBranches({ projectId }); const remoteBranchNames = await getRemoteBranches({ projectId });
const project = api.projects.Project({ id: params.projectId }); const project = getProjectStore({ id: params.projectId });
const { branchStoresCache } = await parent(); const { branchStoresCache } = await parent();
const vbranchStore = branchStoresCache.getVirtualBranchStore(projectId); const vbranchStore = branchStoresCache.getVirtualBranchStore(projectId);

View File

@ -1,20 +1,18 @@
<script lang="ts" async="true"> <script lang="ts" async="true">
import Lane from './BranchLane.svelte'; import Lane from './BranchLane.svelte';
import NewBranchDropZone from './NewBranchDropZone.svelte'; import NewBranchDropZone from './NewBranchDropZone.svelte';
import type { Branch, BaseBranch } from '$lib/vbranches'; import type { BaseBranch, Branch } from '$lib/vbranches/types';
import { dzHighlight } from './dropZone'; import { dzHighlight } from './dropZone';
import type { BranchController } from '$lib/vbranches'; import { BRANCH_CONTROLLER_KEY, BranchController } from '$lib/vbranches/branchController';
import { getContext } from 'svelte'; import { getContext } from 'svelte';
import { BRANCH_CONTROLLER_KEY } from '$lib/vbranches/branchController'; import type { getCloudApiClient } from '$lib/api/cloud/api';
import type { CloudApi } from '$lib/api';
import { Link } from '$lib/components'; import { Link } from '$lib/components';
export let projectId: string; export let projectId: string;
export let projectPath: string; export let projectPath: string;
export let branches: Branch[]; export let branches: Branch[];
export let base: BaseBranch | undefined; export let base: BaseBranch | undefined;
export let cloudEnabled: boolean; export let cloudEnabled: boolean;
export let cloud: ReturnType<typeof CloudApi>; export let cloud: ReturnType<typeof getCloudApiClient>;
const branchController = getContext<BranchController>(BRANCH_CONTROLLER_KEY); const branchController = getContext<BranchController>(BRANCH_CONTROLLER_KEY);

View File

@ -1,7 +1,7 @@
<script lang="ts"> <script lang="ts">
import { slide } from 'svelte/transition'; import { slide } from 'svelte/transition';
import { IconTriangleUp, IconTriangleDown } from '$lib/icons'; import { IconTriangleUp, IconTriangleDown } from '$lib/icons';
import type { BaseBranch } from '$lib/vbranches'; import type { BaseBranch } from '$lib/vbranches/types';
import { formatDistanceToNow } from 'date-fns'; import { formatDistanceToNow } from 'date-fns';
import type { SettingsStore } from '$lib/userSettings'; import type { SettingsStore } from '$lib/userSettings';

View File

@ -1,6 +1,7 @@
<script lang="ts"> <script lang="ts">
import { toasts, stores } from '$lib'; import { toasts } from '$lib';
import type { Commit, File, BaseBranch } from '$lib/vbranches'; import { userStore } from '$lib/stores/user';
import type { BaseBranch, Commit, File } from '$lib/vbranches/types';
import { getContext, onMount } from 'svelte'; import { getContext, onMount } from 'svelte';
import { IconAISparkles } from '$lib/icons'; import { IconAISparkles } from '$lib/icons';
import { Button, Link, Tooltip } from '$lib/components'; import { Button, Link, Tooltip } from '$lib/components';
@ -10,15 +11,14 @@
import PopupMenu from '../../../lib/components/PopupMenu/PopupMenu.svelte'; import PopupMenu from '../../../lib/components/PopupMenu/PopupMenu.svelte';
import PopupMenuItem from '../../../lib/components/PopupMenu/PopupMenuItem.svelte'; import PopupMenuItem from '../../../lib/components/PopupMenu/PopupMenuItem.svelte';
import { dzHighlight } from './dropZone'; import { dzHighlight } from './dropZone';
import type { BranchController } from '$lib/vbranches'; import { BRANCH_CONTROLLER_KEY, BranchController } from '$lib/vbranches/branchController';
import { BRANCH_CONTROLLER_KEY } from '$lib/vbranches/branchController';
import FileCardNext from './FileCardNext.svelte'; import FileCardNext from './FileCardNext.svelte';
import { slide } from 'svelte/transition'; import { slide } from 'svelte/transition';
import { quintOut } from 'svelte/easing'; import { quintOut } from 'svelte/easing';
import { crossfade, fade } from 'svelte/transition'; import { crossfade, fade } from 'svelte/transition';
import { flip } from 'svelte/animate'; import { flip } from 'svelte/animate';
import { invoke } from '@tauri-apps/api/tauri'; import { invoke } from '@tauri-apps/api/tauri';
import type { CloudApi } from '$lib/api'; import type { getCloudApiClient } from '$lib/api/cloud/api';
import Scrollbar from '$lib/components/Scrollbar.svelte'; import Scrollbar from '$lib/components/Scrollbar.svelte';
import IconNewBadge from '$lib/icons/IconNewBadge.svelte'; import IconNewBadge from '$lib/icons/IconNewBadge.svelte';
@ -50,11 +50,11 @@
export let conflicted: boolean; export let conflicted: boolean;
export let base: BaseBranch | undefined; export let base: BaseBranch | undefined;
export let cloudEnabled: boolean; export let cloudEnabled: boolean;
export let cloud: ReturnType<typeof CloudApi>; export let cloud: ReturnType<typeof getCloudApiClient>;
export let upstream: string | undefined; export let upstream: string | undefined;
const branchController = getContext<BranchController>(BRANCH_CONTROLLER_KEY); const branchController = getContext<BranchController>(BRANCH_CONTROLLER_KEY);
const user = stores.user; const user = userStore;
let commitMessage: string; let commitMessage: string;
$: remoteCommits = commits.filter((c) => c.isRemote); $: remoteCommits = commits.filter((c) => c.isRemote);

View File

@ -1,6 +1,6 @@
<script lang="ts"> <script lang="ts">
import { formatDistanceToNow } from 'date-fns'; import { formatDistanceToNow } from 'date-fns';
import type { Commit } from '$lib/vbranches'; import type { Commit } from '$lib/vbranches/types';
export let commit: Commit; export let commit: Commit;
export let url: string | undefined = undefined; export let url: string | undefined = undefined;

View File

@ -1,7 +1,7 @@
<script lang="ts"> <script lang="ts">
import { createEventDispatcher } from 'svelte'; import { createEventDispatcher } from 'svelte';
import { formatDistanceToNow } from 'date-fns'; import { formatDistanceToNow } from 'date-fns';
import type { BranchController, Hunk } from '$lib/vbranches'; import type { Hunk } from '$lib/vbranches/types';
import HunkDiffViewer from './HunkDiffViewer.svelte'; import HunkDiffViewer from './HunkDiffViewer.svelte';
import { summarizeHunk } from '$lib/summaries'; import { summarizeHunk } from '$lib/summaries';
import { IconTriangleUp, IconTriangleDown } from '$lib/icons'; import { IconTriangleUp, IconTriangleDown } from '$lib/icons';
@ -11,7 +11,7 @@
import { SETTINGS_CONTEXT, type SettingsStore } from '$lib/userSettings'; import { SETTINGS_CONTEXT, type SettingsStore } from '$lib/userSettings';
import { getContext } from 'svelte'; import { getContext } from 'svelte';
import { dzTrigger } from './dropZone'; import { dzTrigger } from './dropZone';
import { BRANCH_CONTROLLER_KEY } from '$lib/vbranches/branchController'; import { BRANCH_CONTROLLER_KEY, BranchController } from '$lib/vbranches/branchController';
const userSettings = getContext<SettingsStore>(SETTINGS_CONTEXT); const userSettings = getContext<SettingsStore>(SETTINGS_CONTEXT);
const branchController = getContext<BranchController>(BRANCH_CONTROLLER_KEY); const branchController = getContext<BranchController>(BRANCH_CONTROLLER_KEY);

View File

@ -2,7 +2,7 @@
import { ContentSection, HunkSection, parseFileSections } from './fileSections'; import { ContentSection, HunkSection, parseFileSections } from './fileSections';
import { createEventDispatcher } from 'svelte'; import { createEventDispatcher } from 'svelte';
import { open } from '@tauri-apps/api/shell'; import { open } from '@tauri-apps/api/shell';
import type { File } from '$lib/vbranches'; import type { File } from '$lib/vbranches/types';
import RenderedLine from './RenderedLine.svelte'; import RenderedLine from './RenderedLine.svelte';
import { import {
IconTriangleUp, IconTriangleUp,
@ -11,8 +11,7 @@
IconExpandUp, IconExpandUp,
IconExpandDown IconExpandDown
} from '$lib/icons'; } from '$lib/icons';
import type { BranchController } from '$lib/vbranches'; import { BRANCH_CONTROLLER_KEY, BranchController } from '$lib/vbranches/branchController';
import { BRANCH_CONTROLLER_KEY } from '$lib/vbranches/branchController';
import { getContext } from 'svelte'; import { getContext } from 'svelte';
import { dzTrigger } from './dropZone'; import { dzTrigger } from './dropZone';
import IconExpandUpDownSlim from '$lib/icons/IconExpandUpDownSlim.svelte'; import IconExpandUpDownSlim from '$lib/icons/IconExpandUpDownSlim.svelte';

View File

@ -1,9 +1,8 @@
<script lang="ts"> <script lang="ts">
import { Button } from '$lib/components'; import { Button } from '$lib/components';
import { dzHighlight } from './dropZone'; import { dzHighlight } from './dropZone';
import type { BranchController } from '$lib/vbranches'; import { BRANCH_CONTROLLER_KEY, BranchController } from '$lib/vbranches/branchController';
import { getContext } from 'svelte'; import { getContext } from 'svelte';
import { BRANCH_CONTROLLER_KEY } from '$lib/vbranches/branchController';
const branchController = getContext<BranchController>(BRANCH_CONTROLLER_KEY); const branchController = getContext<BranchController>(BRANCH_CONTROLLER_KEY);

View File

@ -1,6 +1,6 @@
<script lang="ts"> <script lang="ts">
import { Button, Checkbox, Link, Modal } from '$lib/components'; import { Button, Checkbox, Link, Modal } from '$lib/components';
import type { Branch, BranchData } from '$lib/vbranches'; import type { Branch, BranchData } from '$lib/vbranches/types';
import { formatDistanceToNow } from 'date-fns'; import { formatDistanceToNow } from 'date-fns';
import { IconGitBranch, IconRemote } from '$lib/icons'; import { IconGitBranch, IconRemote } from '$lib/icons';
import { IconTriangleDown, IconTriangleUp } from '$lib/icons'; import { IconTriangleDown, IconTriangleUp } from '$lib/icons';
@ -8,9 +8,8 @@
import PopupMenu from '$lib/components/PopupMenu/PopupMenu.svelte'; import PopupMenu from '$lib/components/PopupMenu/PopupMenu.svelte';
import PopupMenuItem from '$lib/components/PopupMenu/PopupMenuItem.svelte'; import PopupMenuItem from '$lib/components/PopupMenu/PopupMenuItem.svelte';
import { SETTINGS_CONTEXT, type SettingsStore } from '$lib/userSettings'; import { SETTINGS_CONTEXT, type SettingsStore } from '$lib/userSettings';
import type { BranchController } from '$lib/vbranches';
import { getContext } from 'svelte'; import { getContext } from 'svelte';
import { BRANCH_CONTROLLER_KEY } from '$lib/vbranches/branchController'; import { BRANCH_CONTROLLER_KEY, BranchController } from '$lib/vbranches/branchController';
import Tooltip from '$lib/components/Tooltip/Tooltip.svelte'; import Tooltip from '$lib/components/Tooltip/Tooltip.svelte';
import Scrollbar from '$lib/components/Scrollbar.svelte'; import Scrollbar from '$lib/components/Scrollbar.svelte';
import IconMeatballMenu from '$lib/icons/IconMeatballMenu.svelte'; import IconMeatballMenu from '$lib/icons/IconMeatballMenu.svelte';

View File

@ -1,10 +1,9 @@
<script lang="ts"> <script lang="ts">
import { IconBranch, IconRefresh, IconGithub } from '$lib/icons'; import { IconBranch, IconRefresh, IconGithub } from '$lib/icons';
import { Button, Modal, Tooltip } from '$lib/components'; import { Button, Modal, Tooltip } from '$lib/components';
import type { BaseBranch } from '$lib/vbranches'; import type { BaseBranch } from '$lib/vbranches/types';
import CommitCard from './CommitCard.svelte'; import CommitCard from './CommitCard.svelte';
import type { BranchController } from '$lib/vbranches'; import { BRANCH_CONTROLLER_KEY, BranchController } from '$lib/vbranches/branchController';
import { BRANCH_CONTROLLER_KEY } from '$lib/vbranches/branchController';
import { getContext } from 'svelte'; import { getContext } from 'svelte';
import Scrollbar from '$lib/components/Scrollbar.svelte'; import Scrollbar from '$lib/components/Scrollbar.svelte';

View File

@ -1,4 +1,4 @@
import type { File } from '$lib/vbranches'; import type { File } from '$lib/vbranches/types';
const TRUE_KEY = '1'; const TRUE_KEY = '1';
const FALSE_KEY = '0'; const FALSE_KEY = '0';

View File

@ -1,5 +1,5 @@
import { expect, test } from 'vitest'; import { expect, test } from 'vitest';
import type { File, Hunk } from '$lib/vbranches'; import type { File, Hunk } from '$lib/vbranches/types';
import { parseHunkSection, parseFileSections, SectionType } from './fileSections'; import { parseHunkSection, parseFileSections, SectionType } from './fileSections';
import type { ContentSection, HunkSection } from './fileSections'; import type { ContentSection, HunkSection } from './fileSections';

View File

@ -1,4 +1,4 @@
import type { File, Hunk } from '$lib/vbranches'; import type { File, Hunk } from '$lib/vbranches/types';
import { plainToInstance } from 'class-transformer'; import { plainToInstance } from 'class-transformer';
export type Line = { export type Line = {

View File

@ -1,4 +1,4 @@
import { Branch, File, type Hunk } from '$lib/vbranches'; import { Branch, File, type Hunk } from '$lib/vbranches/types';
import { plainToInstance } from 'class-transformer'; import { plainToInstance } from 'class-transformer';
let branchCounter = 0; let branchCounter = 0;

View File

@ -1,15 +1,17 @@
<script lang="ts"> <script lang="ts">
import { Button, Modal } from '$lib/components'; import { Button, Modal } from '$lib/components';
import { toasts, api, stores } from '$lib'; import { toasts } from '$lib';
import { userStore } from '$lib/stores/user';
import { goto } from '$app/navigation'; import { goto } from '$app/navigation';
import CloudForm from './CloudForm.svelte'; import CloudForm from './CloudForm.svelte';
import DetailsForm from './DetailsForm.svelte'; import DetailsForm from './DetailsForm.svelte';
import type { Project } from '$lib/api'; import * as projects from '$lib/api/ipc/projects';
import { projectsStore } from '$lib/api/ipc/projects';
import type { PageData } from './$types'; import type { PageData } from './$types';
export let data: PageData; export let data: PageData;
const { projects, project, cloud } = data; const { project, cloud } = data;
const user = stores.user; const user = userStore;
let deleteConfirmationModal: Modal; let deleteConfirmationModal: Modal;
let isDeleting = false; let isDeleting = false;
@ -17,19 +19,19 @@
const onDeleteClicked = () => const onDeleteClicked = () =>
Promise.resolve() Promise.resolve()
.then(() => (isDeleting = true)) .then(() => (isDeleting = true))
.then(() => api.projects.del({ id: $project?.id })) .then(() => projects.del({ id: $project?.id }))
.then(() => deleteConfirmationModal.close()) .then(() => deleteConfirmationModal.close())
.catch((e) => { .catch((e) => {
console.error(e); console.error(e);
toasts.error('Failed to delete project'); toasts.error('Failed to delete project');
}) })
.then(() => goto('/')) .then(() => goto('/'))
.then(() => projects.update((projects) => projects.filter((p) => p.id !== $project?.id))) .then(() => projectsStore.update((projects) => projects.filter((p) => p.id !== $project?.id)))
.then(() => toasts.success('Project deleted')) .then(() => toasts.success('Project deleted'))
.finally(() => (isDeleting = false)); .finally(() => (isDeleting = false));
const onCloudUpdated = (e: { detail: Project }) => project.update({ ...e.detail }); const onCloudUpdated = (e: { detail: projects.Project }) => project.update({ ...e.detail });
const onDetailsUpdated = async (e: { detail: Project }) => { const onDetailsUpdated = async (e: { detail: projects.Project }) => {
const api = const api =
$user && e.detail.api $user && e.detail.api
? await cloud.projects.update($user?.access_token, e.detail.api.repository_id, { ? await cloud.projects.update($user?.access_token, e.detail.api.repository_id, {

View File

@ -1,7 +1,6 @@
import type { PageLoadEvent } from './$types'; import type { PageLoadEvent } from './$types';
import { invoke } from '$lib/ipc'; import { invoke } from '$lib/ipc';
import { api } from '$lib'; import { getProjectStore, type Project } from '$lib/api/ipc/projects';
import type { Project } from '$lib/api';
import type { Loadable } from '@square/svelte-store'; import type { Loadable } from '@square/svelte-store';
async function getRemoteBranches(params: { projectId: string }) { async function getRemoteBranches(params: { projectId: string }) {
@ -11,7 +10,7 @@ async function getRemoteBranches(params: { projectId: string }) {
export async function load({ parent, params }: PageLoadEvent) { export async function load({ parent, params }: PageLoadEvent) {
const projectId = params.projectId; const projectId = params.projectId;
const remoteBranchNames = await getRemoteBranches({ projectId }); const remoteBranchNames = await getRemoteBranches({ projectId });
const project = api.projects.Project({ id: params.projectId }); const project = getProjectStore({ id: params.projectId });
const { branchStoresCache } = await parent(); const { branchStoresCache } = await parent();
const vbranchStore = branchStoresCache.getVirtualBranchStore(projectId); const vbranchStore = branchStoresCache.getVirtualBranchStore(projectId);

View File

@ -1,12 +1,14 @@
<script lang="ts"> <script lang="ts">
import { stores, toasts } from '$lib'; import { toasts } from '$lib';
import { CloudApi, type Project } from '$lib/api'; import { getCloudApiClient } from '$lib/api/cloud/api';
import type { Project } from '$lib/api/ipc/projects';
import { userStore } from '$lib/stores/user';
import { Login, Checkbox } from '$lib/components'; import { Login, Checkbox } from '$lib/components';
import { createEventDispatcher, onMount } from 'svelte'; import { createEventDispatcher, onMount } from 'svelte';
export let project: Project; export let project: Project;
const user = stores.user; const user = userStore;
const cloud = CloudApi(); const cloud = getCloudApiClient();
const dispatch = createEventDispatcher<{ const dispatch = createEventDispatcher<{
updated: Project; updated: Project;

View File

@ -1,5 +1,5 @@
<script lang="ts"> <script lang="ts">
import type { Project } from '$lib/api'; import type { Project } from '$lib/api/ipc/projects';
import { debounce } from '$lib/utils'; import { debounce } from '$lib/utils';
import { createEventDispatcher } from 'svelte'; import { createEventDispatcher } from 'svelte';

View File

@ -1,8 +1,9 @@
<script lang="ts"> <script lang="ts">
import { Button, Modal, Login, Link } from '$lib/components'; import { Button, Modal, Login, Link } from '$lib/components';
import type { PageData } from './$types'; import type { PageData } from './$types';
import { stores, toasts } from '$lib'; import { toasts } from '$lib';
import { deleteAllData } from '$lib/api'; import { deleteAllData } from '$lib/api/ipc';
import { userStore } from '$lib/stores/user';
import { goto } from '$app/navigation'; import { goto } from '$app/navigation';
import ThemeSelector from '../ThemeSelector.svelte'; import ThemeSelector from '../ThemeSelector.svelte';
import { getContext } from 'svelte'; import { getContext } from 'svelte';
@ -13,7 +14,7 @@
const { cloud } = data; const { cloud } = data;
const userSettings = getContext<SettingsStore>(SETTINGS_CONTEXT); const userSettings = getContext<SettingsStore>(SETTINGS_CONTEXT);
const user = stores.user; const user = userStore;
$: saving = false; $: saving = false;

View File

@ -111,7 +111,7 @@ const config = {
600: '#C19206', 600: '#C19206',
700: '#987105', 700: '#987105',
800: '#6F5004', 800: '#6F5004',
900: '#713F12', 900: '#713F12'
}, },
red: { red: {
400: '#F87171', 400: '#F87171',
@ -142,7 +142,7 @@ const config = {
600: '#5852A0', 600: '#5852A0',
700: '#443D7A', 700: '#443D7A',
800: '#302854', 800: '#302854',
900: '#1C142E', 900: '#1C142E'
}, },
orange: { orange: {
50: '#FEF6E4', 50: '#FEF6E4',
@ -154,7 +154,7 @@ const config = {
600: '#FA8E4B', 600: '#FA8E4B',
700: '#CD6E02', 700: '#CD6E02',
800: '#A55602', 800: '#A55602',
900: '#7D3F02', 900: '#7D3F02'
}, },
zinc: { zinc: {
50: '#fafafa', 50: '#fafafa',