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: window.fetch
}
) => {
) {
const fetch = fetchWith(realFetch, withRequestId, withLog);
return {
login: {
@ -278,4 +278,4 @@ export default (
}).then(parseResponseJSON)
}
};
};
}

View File

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

View File

@ -20,7 +20,7 @@ export function subscribe(
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];
const store = asyncWritable([], () => list(params));

View File

@ -1,6 +1,7 @@
import { invoke } from '$lib/ipc';
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 }) =>
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>>> = {};
export function Diffs(params: { projectId: string }) {
export function getDiffsStore(params: { projectId: string }) {
if (stores[params.projectId]) return stores[params.projectId];
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));
stores[params.projectId] = store;
return store;

View File

@ -16,7 +16,7 @@ export function subscribe(
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];
const store = asyncWritable([], () =>
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 * as heads from './heads';
export * as diffs from './diffs';
export * as indexes from './indexes';
export * as fetches from './fetches';
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 * as indexes from './indexes';
import * as activities from './activities';
import * as sessions from '../sessions';
import * as sessions from '../ipc/sessions';
type FileStatus = 'added' | 'modified' | 'deleted' | 'renamed' | 'typeChange' | 'other';
@ -11,13 +11,11 @@ export type Status =
| { unstaged: FileStatus }
| { staged: FileStatus; unstaged: FileStatus };
export namespace Status {
export function isStaged(status: Status): status is { staged: FileStatus } {
return 'staged' in status && status.staged !== null;
}
export function isUnstaged(status: Status): status is { unstaged: FileStatus } {
return 'unstaged' in status && status.unstaged !== null;
}
export function isStaged(status: Status): status is { staged: FileStatus } {
return 'staged' in status && status.staged !== null;
}
export function isUnstaged(status: Status): status is { unstaged: FileStatus } {
return 'unstaged' in status && status.unstaged !== null;
}
export function list(params: { projectId: string }) {
@ -26,7 +24,7 @@ export function list(params: { projectId: string }) {
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];
const store = asyncWritable([], () => list(params));
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 namespace Operation {
export function isDelete(operation: Operation): operation is OperationDelete {
return 'delete' in operation;
}
export function isDelete(operation: Operation): operation is OperationDelete {
return 'delete' in operation;
}
export function isInsert(operation: Operation): operation is OperationInsert {
return 'insert' in operation;
}
export function isInsert(operation: Operation): operation is OperationInsert {
return 'insert' in 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';
export function deleteAllData() {

View File

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

View File

@ -42,7 +42,8 @@ export function subscribe(
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];
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';
export async function get() {
return invoke<User | null>('get_user');
}
export function set(params: { user: User }) {
export async function set(params: { user: User }) {
invoke<void>('set_user', params);
}

View File

@ -1,6 +1,6 @@
<script lang="ts">
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 { Overlay } from '$lib/components';
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 {
IconGitCommit,
@ -180,7 +181,7 @@ const fileGroup = ({
description: 'type part of a file name',
commands: []
}
: git.matchFiles({ projectId: project.id, matchPattern: input }).then((files) => ({
: matchFiles({ projectId: project.id, matchPattern: input }).then((files) => ({
title: 'Files',
description: files.length === 0 ? `no files containing '${input}'` : '',
commands: files.map((file) => ({

View File

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

View File

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

View File

@ -1,6 +1,6 @@
<script lang="ts">
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>;
</script>
@ -27,12 +27,12 @@
<li class="flex w-full gap-2">
<div class="flex w-[3ch] justify-between font-semibold">
<span>
{#if Status.isStaged(status)}
{#if isStaged(status)}
{status.staged.slice(0, 1).toUpperCase()}
{/if}
</span>
<span>
{#if Status.isUnstaged(status)}
{#if isUnstaged(status)}
{status.unstaged.slice(0, 1).toUpperCase()}
{/if}
</span>

View File

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

View File

@ -1,6 +1,6 @@
import posthog from 'posthog-js';
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';
interface PostHogClient {

View File

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

View File

@ -1,10 +1,10 @@
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';
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];
const store = writable(bookmarks.list(params), (set) => {
@ -23,11 +23,11 @@ export function list(params: { projectId: string }) {
};
});
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 }) {
return derived(list({ projectId: params.projectId }), (bookmarks) =>
export function getBookmark(params: { projectId: string; timestampMs: number }) {
return derived(getBookmarksStore({ projectId: params.projectId }), (bookmarks) =>
bookmarks.find((b) => b.timestampMs === params.timestampMs)
);
}

View File

@ -1,10 +1,10 @@
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';
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}`;
if (key in stores) return stores[key];
@ -29,5 +29,5 @@ export default (params: { projectId: string; sessionId: string }) => {
};
});
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 { files } from '$lib/api';
import * as files from '$lib/api/ipc/files';
import { get, type Readable } from '@square/svelte-store';
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}`;
if (key in stores) return stores[key];
@ -28,4 +28,4 @@ export default (params: { projectId: string; sessionId: string }) => {
});
stores[key] = store;
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 { sessions, type Session } from '$lib/api';
import * as sessions from '$lib/api/ipc/sessions';
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];
const store = derived(
@ -12,7 +12,7 @@ export default (params: { projectId: string }) => {
const unsubscribe = sessions.subscribe(params, ({ session }) => {
const oldValue = get(store);
if (oldValue.isLoading) {
sessions.list(params).then(set);
sessions.list(params).then((x) => set(x));
} else if (Loaded.isError(oldValue)) {
sessions.list(params).then(set);
} else {
@ -33,5 +33,5 @@ export default (params: { projectId: string }) => {
(sessions) => sessions.sort((a, b) => a.meta.startTimestampMs - b.meta.startTimestampMs)
);
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';
const store = asyncWritable([], users.get, async (user) => {
export const userStore = asyncWritable([], users.get, async (user) => {
if (user === null) {
await users.delete();
} else {
@ -9,5 +9,3 @@ const store = asyncWritable([], users.get, async (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';
const cloud = CloudApi();
const cloud = getCloudApiClient();
export async function summarizeHunk(diff: string): Promise<string> {
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 = {
session: Session;

View File

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

View File

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

View File

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

View File

@ -4,28 +4,29 @@
import { asyncDerived } from '@square/svelte-store';
import { compareDesc, formatDistanceToNow } from 'date-fns';
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 { goto } from '$app/navigation';
export let projects: ReturnType<typeof api.projects.Projects>;
export let cloud: ReturnType<typeof api.CloudApi>;
export let projects: typeof projectsStore;
export let cloud: ReturnType<typeof getCloudApiClient>;
const user = stores.user;
const cloudProjects = asyncDerived(user, async (user) =>
const cloudProjects = asyncDerived(userStore, async (user) =>
user ? await cloud.projects.list(user.access_token) : []
);
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) {
await user.load();
await userStore.load();
await cloudProjects.load();
if ($user === null) return;
project = api.projects.Project({ id });
if ($userStore === null) return;
project = getProjectStore({ id });
modal.show();
}
@ -42,8 +43,8 @@
await project
.update({ api: { ...existingCloudProject, sync: true } })
.then(() => toasts.success(`Project linked`));
} else if (selectedRepositoryId === null && $user && project && $project) {
const cloudProject = await cloud.projects.create($user?.access_token, {
} else if (selectedRepositoryId === null && $userStore && project && $project) {
const cloudProject = await cloud.projects.create($userStore?.access_token, {
name: $project.title,
description: $project.description,
uid: $project.id
@ -112,7 +113,7 @@
</button>
{#each $cloudProjects
// 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((a, b) => compareDesc(new Date(a.updated_at), new Date(b.updated_at)))
// sort by name

View File

@ -1,11 +1,12 @@
<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 { 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 cloud: ReturnType<typeof api.CloudApi>;
export let cloud: ReturnType<typeof getCloudApiClient>;
export function show() {
modal.show();
@ -42,12 +43,12 @@
const email = user?.email ?? emailInputValue;
toasts.promise(
Promise.all([
sendLogs ? api.zip.logs().then((path) => readZipFile(path, 'logs.zip')) : undefined,
sendLogs ? zip.logs().then((path) => readZipFile(path, 'logs.zip')) : undefined,
sendProjectData
? api.zip.gitbutlerData({ projectId }).then((path) => readZipFile(path, 'data.zip'))
? zip.gitbutlerData({ projectId }).then((path) => readZipFile(path, 'data.zip'))
: undefined,
sendProjectRepository
? api.zip.projectData({ projectId }).then((path) => readZipFile(path, 'project.zip'))
? zip.projectData({ projectId }).then((path) => readZipFile(path, 'project.zip'))
: undefined
]).then(async ([logs, data, repo]) =>
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 type { LayoutLoad } from './$types';
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 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'));
return {
head: api.git.heads.Head({ projectId: params.projectId }),
statuses: api.git.statuses.Statuses({ projectId: params.projectId }),
sessions: api.sessions.Sessions({ projectId: params.projectId }),
diffs: api.git.diffs.Diffs({ projectId: params.projectId }),
head: getHeadStore({ projectId: params.projectId }),
statuses: getStatusStore({ projectId: params.projectId }),
sessions: getSessionStore({ projectId: params.projectId }),
diffs: getDiffsStore({ projectId: params.projectId }),
project: project as Loadable<Project> & Pick<typeof project, 'update' | 'delete'>
};
};

View File

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

View File

@ -1,6 +1,6 @@
import type { PageLoad } from './$types';
import { git } from '$lib/api';
import { getActivitiesStore } from '$lib/api/git/activities';
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">
import { Button } from '$lib/components';
import { CloudApi, type Project } from '$lib/api';
import { stores } from '$lib';
import { getCloudApiClient } from '$lib/api/cloud/api';
import { userStore } from '$lib/stores/user';
import type { Project } from '$lib/api/ipc/projects';
import { marked } from 'marked';
import { IconAISparkles } from '$lib/icons';
import { onMount } from 'svelte';
const cloud = CloudApi();
const user = stores.user;
const cloud = getCloudApiClient();
const user = userStore;
export let project: Project;

View File

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

View File

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

View File

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

View File

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

View File

@ -1,17 +1,18 @@
<script lang="ts">
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 { 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');
const dates = derived(sessions, async (sessions) => {
const sessionDeltas = await Promise.all(
sessions.map((session) =>
api.deltas.list({
deltas.list({
projectId: $page.params.projectId,
sessionId: session.id,
paths: fileFilter ? [fileFilter] : undefined

View File

@ -1,11 +1,11 @@
import { error, redirect } from '@sveltejs/kit';
import { format, compareDesc } from 'date-fns';
import type { PageLoad } from './$types';
import { stores } from '$lib';
import { getSessionStore } from '$lib/stores/sessions';
import { promisify } from 'svelte-loadable-store';
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
.map((session) => session.meta.startTimestampMs)
.sort(compareDesc)

View File

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

View File

@ -1,11 +1,11 @@
import { redirect, error } from '@sveltejs/kit';
import { format } from 'date-fns';
import type { PageLoad } from './$types';
import { stores } from '$lib';
import { getSessionStore } from '$lib/stores/sessions';
import { promisify } from 'svelte-loadable-store';
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(
(session) => format(session.meta.startTimestampMs, 'yyyy-MM-dd') === params.date
);

View File

@ -1,5 +1,6 @@
<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 { IconBookmarkFilled } from '$lib/icons';
@ -16,7 +17,7 @@
export async function show(ts: number) {
reset();
timestampMs = ts;
const existing = await api.bookmarks.list({
const existing = await bookmarks.list({
projectId,
range: {
start: ts,
@ -35,7 +36,7 @@
Promise.resolve()
.then(() => (isCreating = true))
.then(() =>
api.bookmarks.upsert({
bookmarks.upsert({
projectId,
note,
timestampMs: timestampMs ?? Date.now(),

View File

@ -1,9 +1,10 @@
<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 { collapse } from '$lib/paths';
import { derived } from '@square/svelte-store';
import { stores } from '$lib';
import { getBookmarksStore } from '$lib/stores/bookmarks';
import { IconBookmarkFilled } from '$lib/icons';
import { line } from '$lib/diff';
import { Stats } from '$lib/components';
@ -19,12 +20,12 @@
const operations = deltas.flatMap((delta) => delta.operations);
operations.forEach((operation) => {
if (Operation.isInsert(operation)) {
if (isInsert(operation)) {
text =
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.slice(0, operation.delete[0]) +
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]);
$: bookmarks = derived(stores.bookmarks.list({ projectId: session.projectId }), (bookmarks) => {
$: bookmarksStore = derived(getBookmarksStore({ projectId: session.projectId }), (bookmarks) => {
if (bookmarks.isLoading) return [];
if (Loaded.isError(bookmarks)) return [];
const timestamps = Object.values(deltas ?? {}).flatMap((deltas) =>
@ -110,8 +111,8 @@
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"
>
{#await bookmarks.load() then}
{#if $bookmarks?.length > 0}
{#await bookmarksStore.load() then}
{#if $bookmarksStore?.length > 0}
<div class="absolute right-5 top-0 flex gap-2 overflow-hidden text-bookmark-selected">
<IconBookmarkFilled class="-mt-1 h-4 w-4" />
</div>

View File

@ -4,7 +4,8 @@
import { page } from '$app/stores';
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 type { Readable } from '@square/svelte-store';
import { onMount } from 'svelte';

View File

@ -1,5 +1,6 @@
<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 { Loaded, type Loadable } from 'svelte-loadable-store';
import { derived } from 'svelte-loadable-store';

View File

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

View File

@ -1,5 +1,6 @@
<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 { DeltasViewer } from '$lib/components';
import type { Readable } from '@square/svelte-store';

View File

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

View File

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

View File

@ -1,5 +1,6 @@
<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 type { Readable } from '@square/svelte-store';
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 = {
sessionId: string;

View File

@ -1,13 +1,15 @@
<script lang="ts">
import type { PageData } from './$types';
import { IconChevronLeft, IconChevronRight, IconLoading } from '$lib/icons';
import { files, deltas, searchResults, type SearchResult } from '$lib/api';
import { asyncDerived } from '@square/svelte-store';
import { format, formatDistanceToNow } from 'date-fns';
import { DeltasViewer } from '$lib/components';
import { page } from '$app/stores';
import { derived } from '@square/svelte-store';
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;
const { project } = data;
@ -19,7 +21,12 @@
const openNextPage = () => 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([
files.list({ projectId, sessionId, paths: [filePath] }).then((r) => r[filePath] ?? ''),
deltas
@ -42,7 +49,7 @@
[query, project, offset],
async ([query, project, offset]) => {
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 {
page: await Promise.all(results.page.map(fetchResultData)),
haveNext: offset + limit < results.total,

View File

@ -3,7 +3,7 @@
import ResizeObserver from 'svelte-resize-observer';
import setupTerminal from './terminal';
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 { 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 { CanvasAddon } from 'xterm-addon-canvas';
import { WebglAddon } from 'xterm-addon-webgl';

View File

@ -3,10 +3,9 @@
import Tray from './Tray.svelte';
import type { PageData } from './$types';
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 { getContext, setContext } from 'svelte';
import { BRANCH_CONTROLLER_KEY } from '$lib/vbranches/branchController';
import { SETTINGS_CONTEXT, type SettingsStore } from '$lib/userSettings';
import BottomPanel from './BottomPanel.svelte';
import UpstreamBranchLane from './UpstreamBranchLane.svelte';

View File

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

View File

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

View File

@ -1,7 +1,7 @@
<script lang="ts">
import { slide } from 'svelte/transition';
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 type { SettingsStore } from '$lib/userSettings';

View File

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

View File

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

View File

@ -1,7 +1,7 @@
<script lang="ts">
import { createEventDispatcher } from 'svelte';
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 { summarizeHunk } from '$lib/summaries';
import { IconTriangleUp, IconTriangleDown } from '$lib/icons';
@ -11,7 +11,7 @@
import { SETTINGS_CONTEXT, type SettingsStore } from '$lib/userSettings';
import { getContext } from 'svelte';
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 branchController = getContext<BranchController>(BRANCH_CONTROLLER_KEY);

View File

@ -2,7 +2,7 @@
import { ContentSection, HunkSection, parseFileSections } from './fileSections';
import { createEventDispatcher } from 'svelte';
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 {
IconTriangleUp,
@ -11,8 +11,7 @@
IconExpandUp,
IconExpandDown
} from '$lib/icons';
import type { BranchController } from '$lib/vbranches';
import { BRANCH_CONTROLLER_KEY } from '$lib/vbranches/branchController';
import { BRANCH_CONTROLLER_KEY, BranchController } from '$lib/vbranches/branchController';
import { getContext } from 'svelte';
import { dzTrigger } from './dropZone';
import IconExpandUpDownSlim from '$lib/icons/IconExpandUpDownSlim.svelte';

View File

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

View File

@ -1,6 +1,6 @@
<script lang="ts">
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 { IconGitBranch, IconRemote } from '$lib/icons';
import { IconTriangleDown, IconTriangleUp } from '$lib/icons';
@ -8,9 +8,8 @@
import PopupMenu from '$lib/components/PopupMenu/PopupMenu.svelte';
import PopupMenuItem from '$lib/components/PopupMenu/PopupMenuItem.svelte';
import { SETTINGS_CONTEXT, type SettingsStore } from '$lib/userSettings';
import type { BranchController } from '$lib/vbranches';
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 Scrollbar from '$lib/components/Scrollbar.svelte';
import IconMeatballMenu from '$lib/icons/IconMeatballMenu.svelte';

View File

@ -1,10 +1,9 @@
<script lang="ts">
import { IconBranch, IconRefresh, IconGithub } from '$lib/icons';
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 type { BranchController } from '$lib/vbranches';
import { BRANCH_CONTROLLER_KEY } from '$lib/vbranches/branchController';
import { BRANCH_CONTROLLER_KEY, BranchController } from '$lib/vbranches/branchController';
import { getContext } from '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 FALSE_KEY = '0';

View File

@ -1,5 +1,5 @@
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 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';
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';
let branchCounter = 0;

View File

@ -1,15 +1,17 @@
<script lang="ts">
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 CloudForm from './CloudForm.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';
export let data: PageData;
const { projects, project, cloud } = data;
const user = stores.user;
const { project, cloud } = data;
const user = userStore;
let deleteConfirmationModal: Modal;
let isDeleting = false;
@ -17,19 +19,19 @@
const onDeleteClicked = () =>
Promise.resolve()
.then(() => (isDeleting = true))
.then(() => api.projects.del({ id: $project?.id }))
.then(() => projects.del({ id: $project?.id }))
.then(() => deleteConfirmationModal.close())
.catch((e) => {
console.error(e);
toasts.error('Failed to delete project');
})
.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'))
.finally(() => (isDeleting = false));
const onCloudUpdated = (e: { detail: Project }) => project.update({ ...e.detail });
const onDetailsUpdated = async (e: { detail: Project }) => {
const onCloudUpdated = (e: { detail: projects.Project }) => project.update({ ...e.detail });
const onDetailsUpdated = async (e: { detail: projects.Project }) => {
const api =
$user && e.detail.api
? await cloud.projects.update($user?.access_token, e.detail.api.repository_id, {

View File

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

View File

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

View File

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

View File

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

View File

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