Skip to content

Commit

Permalink
Merge pull request #5833 from gitbutlerapp/Start-to-introduce-loadabl…
Browse files Browse the repository at this point in the history
…e-data-type

Introduce LoadableData type for organizations
  • Loading branch information
Caleb-T-Owens authored Jan 9, 2025
2 parents fa67ead + 71717da commit 8eec4ce
Show file tree
Hide file tree
Showing 46 changed files with 580 additions and 272 deletions.
2 changes: 1 addition & 1 deletion apps/desktop/src/lib/ai/butlerClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import {
import { ModelKind, type AIClient, type AIEvalOptions, type Prompt } from '$lib/ai/types';
import { andThenAsync, ok, wrapAsync, type Result } from '$lib/result';
import { stringStreamGenerator } from '$lib/utils/promise';
import type { HttpClient } from '@gitbutler/shared/httpClient';
import type { HttpClient } from '@gitbutler/shared/network/httpClient';

function splitPromptMessagesIfNecessary(
modelKind: ModelKind,
Expand Down
2 changes: 1 addition & 1 deletion apps/desktop/src/lib/ai/service.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ import {
import { buildFailureFromAny, ok, unwrap, type Result } from '$lib/result';
import { TokenMemoryService } from '$lib/stores/tokenMemoryService';
import { Hunk } from '$lib/vbranches/types';
import { HttpClient } from '@gitbutler/shared/httpClient';
import { HttpClient } from '@gitbutler/shared/network/httpClient';
import { plainToInstance } from 'class-transformer';
import { get } from 'svelte/store';
import { expect, test, describe, vi } from 'vitest';
Expand Down
2 changes: 1 addition & 1 deletion apps/desktop/src/lib/ai/service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ import { get } from 'svelte/store';
import type { GitConfigService } from '$lib/backend/gitConfigService';
import type { SecretsService } from '$lib/secrets/secretsService';
import type { TokenMemoryService } from '$lib/stores/tokenMemoryService';
import type { HttpClient } from '@gitbutler/shared/httpClient';
import type { HttpClient } from '@gitbutler/shared/network/httpClient';

const maxDiffLengthLimitForAPI = 5000;
const prDescriptionTokenLimit = 4096;
Expand Down
9 changes: 6 additions & 3 deletions apps/desktop/src/lib/backend/projectCloudSync.svelte.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { registerInterest } from '@gitbutler/shared/interest/registerInterestFun
import { projectsSelectors } from '@gitbutler/shared/organizations/projectsSlice';
import { readableToReactive } from '@gitbutler/shared/reactiveUtils.svelte';
import type { ProjectService, ProjectsService } from '$lib/backend/projects';
import type { HttpClient } from '@gitbutler/shared/httpClient';
import type { HttpClient } from '@gitbutler/shared/network/httpClient';
import type { ProjectService as CloudProjectService } from '@gitbutler/shared/organizations/projectService';
import type { AppProjectsState } from '@gitbutler/shared/redux/store.svelte';

Expand All @@ -25,14 +25,17 @@ export function projectCloudSync(
registerInterest(cloudProjectInterest);
});

const cloudProject = $derived(
const loadableCloudProject = $derived(
project.current?.api
? projectsSelectors.selectById(appState.projects, project.current.api.repository_id)
: undefined
);

$effect(() => {
if (!project.current?.api || !cloudProject) return;
if (!project.current?.api || !loadableCloudProject || loadableCloudProject.status !== 'found')
return;

const cloudProject = loadableCloudProject.value;
const persistedProjectUpdatedAt = new Date(project.current.api.updated_at).getTime();
const cloudProjectUpdatedAt = new Date(cloudProject.updatedAt).getTime();
if (persistedProjectUpdatedAt >= cloudProjectUpdatedAt) return;
Expand Down
2 changes: 1 addition & 1 deletion apps/desktop/src/lib/backend/projects.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { persisted } from '@gitbutler/shared/persisted';
import { open } from '@tauri-apps/plugin-dialog';
import { plainToInstance } from 'class-transformer';
import { derived, get, writable, type Readable } from 'svelte/store';
import type { HttpClient } from '@gitbutler/shared/httpClient';
import type { HttpClient } from '@gitbutler/shared/network/httpClient';
import { goto } from '$app/navigation';

export type KeyType = 'gitCredentialsHelper' | 'local' | 'systemExecutable';
Expand Down
2 changes: 1 addition & 1 deletion apps/desktop/src/lib/components/ShareIssueModal.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
import { User } from '$lib/stores/user';
import * as toasts from '$lib/utils/toasts';
import { getContext, getContextStore } from '@gitbutler/shared/context';
import { HttpClient } from '@gitbutler/shared/httpClient';
import { HttpClient } from '@gitbutler/shared/network/httpClient';
import Button from '@gitbutler/ui/Button.svelte';
import Checkbox from '@gitbutler/ui/Checkbox.svelte';
import Modal from '@gitbutler/ui/Modal.svelte';
Expand Down
8 changes: 4 additions & 4 deletions apps/desktop/src/lib/feeds/CreatePost.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -73,13 +73,13 @@
// Post creation
let newPostContent = $derived(persisted('', `postContent--${feedIdentity}--${replyTo}`));
function createPost() {
if (!feedIdentity?.current) return;
if (!parentProject?.current) return;
if (feedIdentity?.current.status !== 'found') return;
if (parentProject?.current?.status !== 'found') return;
feedService.createPost(
$newPostContent,
parentProject.current.repositoryId,
feedIdentity.current,
parentProject.current.value.repositoryId,
feedIdentity.current.value,
replyTo,
picture
);
Expand Down
17 changes: 11 additions & 6 deletions apps/desktop/src/lib/feeds/Post.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import { postsSelectors } from '@gitbutler/shared/feeds/postsSlice';
import { FeedService } from '@gitbutler/shared/feeds/service';
import { registerInterestInView } from '@gitbutler/shared/interest/registerInterestFunction.svelte';
import Loading from '@gitbutler/shared/network/Loading.svelte';
import { AppState } from '@gitbutler/shared/redux/store.svelte';
import { UserService } from '@gitbutler/shared/users/userService';
import Button from '@gitbutler/ui/Button.svelte';
Expand Down Expand Up @@ -42,12 +43,16 @@
<div bind:this={postCardRef}>
<SectionCard>
<div class="author">
<Avatar
size="medium"
tooltip={author.current?.name || 'Unknown'}
srcUrl={author.current?.avatarUrl || ''}
/>
<p>{author.current?.name}</p>
<Loading loadable={author?.current}>
{#snippet children(author)}
<Avatar
size="medium"
tooltip={author.name || 'Unknown'}
srcUrl={author.avatarUrl || ''}
/>
<p>{author.name}</p>
{/snippet}
</Loading>
</div>

<Markdown content={post.content} />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import * as toasts from '$lib/utils/toasts';
import { getContext, getContextStore } from '@gitbutler/shared/context';
import RegisterInterest from '@gitbutler/shared/interest/RegisterInterest.svelte';
import Loading from '@gitbutler/shared/network/Loading.svelte';
import { OrganizationService } from '@gitbutler/shared/organizations/organizationService';
import { organizationsSelectors } from '@gitbutler/shared/organizations/organizationsSlice';
import { ProjectService as CloudProjectService } from '@gitbutler/shared/organizations/projectService';
Expand Down Expand Up @@ -138,36 +139,44 @@
{/snippet}
</Section>

{#if !cloudProject?.parentProjectRepositoryId}
<Section>
{#snippet title()}
Link your project with an organization
{/snippet}

<RegisterInterest interest={usersOrganizationsInterest} />

<div>
{#each usersOrganizations as organization, index}
<SectionCard
roundedBottom={index === usersOrganizations.length - 1}
roundedTop={index === 0}
orientation="row"
centerAlign
>
{#snippet children()}
<h5 class="text-15 text-bold flex-grow">{organization.name || organization.slug}</h5>
{/snippet}
{#snippet actions()}
<ProjectConnectModal
organizationSlug={organization.slug}
projectRepositoryId={cloudProject.repositoryId}
/>
{/snippet}
</SectionCard>
{/each}
</div>
</Section>
{/if}
<Loading loadable={cloudProject}>
{#snippet children(cloudProject)}
<Section>
{#snippet title()}
Link your project with an organization
{/snippet}

<RegisterInterest interest={usersOrganizationsInterest} />

<div>
{#each usersOrganizations as loadableOrganization, index}
<SectionCard
roundedBottom={index === usersOrganizations.length - 1}
roundedTop={index === 0}
orientation="row"
centerAlign
>
{#snippet children()}
<Loading loadable={loadableOrganization}>
{#snippet children(organization)}
<h5 class="text-15 text-bold flex-grow">
{organization.name || organization.slug}
</h5>
{/snippet}
</Loading>
{/snippet}
{#snippet actions()}
<ProjectConnectModal
organizationSlug={loadableOrganization.id}
projectRepositoryId={cloudProject.repositoryId}
/>
{/snippet}
</SectionCard>
{/each}
</div>
</Section>
{/snippet}
</Loading>
{:else if !$project?.api?.repository_id}
<Section>
<Button onclick={createProject}>Create Gitbutler Project</Button>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
<script lang="ts">
import { getContext } from '@gitbutler/shared/context';
import RegisterInterest from '@gitbutler/shared/interest/RegisterInterest.svelte';
import Loading from '@gitbutler/shared/network/Loading.svelte';
import { OrganizationService } from '@gitbutler/shared/organizations/organizationService';
import { organizationsSelectors } from '@gitbutler/shared/organizations/organizationsSlice';
import { ProjectService } from '@gitbutler/shared/organizations/projectService';
Expand All @@ -26,53 +27,59 @@
);
const projectInterest = $derived(projectsService.getProjectInterest(projectRepositoryId));
const organization = $derived(
const chosenOrganization = $derived(
organizationsSelectors.selectById(appState.organizations, organizationSlug)
);
const project = $derived(projectsSelectors.selectById(appState.projects, projectRepositoryId));
const organizationProjects = $derived(
organization?.projectRepositoryIds?.map((repositoryId) => ({
project: projectsSelectors.selectById(appState.projects, repositoryId),
projectInterest: projectsService.getProjectInterest(repositoryId)
})) || []
const targetProject = $derived(
projectsSelectors.selectById(appState.projects, projectRepositoryId)
);
const organizationProjects = $derived.by(() => {
if (chosenOrganization?.status !== 'found') return [];
return (
chosenOrganization.value.projectRepositoryIds?.map((repositoryId) => ({
project: projectsSelectors.selectById(appState.projects, repositoryId),
interest: projectsService.getProjectInterest(repositoryId)
})) || []
);
});
function connectToOrganization(projectSlug?: string) {
if (!project || !organization) return;
if (targetProject?.status !== 'found' || chosenOrganization?.status !== 'found') return;
projectsService.connectProjectToOrganization(
project.repositoryId,
organization.slug,
targetProject.value.repositoryId,
chosenOrganization.value.slug,
projectSlug
);
}
const title = $derived.by(() => {
if (targetProject?.status !== 'found' || chosenOrganization?.status !== 'found') return;
return `Join ${targetProject.value.name} into ${chosenOrganization.value.name}`;
});
let modal = $state<Modal>();
</script>

<Modal bind:this={modal} title={`Join ${project?.name} into ${organization?.name}`}>
<Modal bind:this={modal} {title}>
<RegisterInterest interest={organizationInterest} />
<RegisterInterest interest={projectInterest} />

{#if organization}
{#each organizationProjects as { project, projectInterest }, index}
<RegisterInterest interest={projectInterest} />

<SectionCard
roundedTop={index === 0}
roundedBottom={index === organizationProjects.length - 1}
>
{#if project}
<h5>{project.name}</h5>

<Button onclick={() => connectToOrganization(project.slug)}>Connect</Button>
{:else}
<p>Loading...</p>
{/if}
</SectionCard>
{/each}
{/if}
{#each organizationProjects as { project: organizationProject, interest }, index}
<RegisterInterest {interest} />

<SectionCard roundedTop={index === 0} roundedBottom={index === organizationProjects.length - 1}>
<Loading loadable={organizationProject}>
{#snippet children(organizationProject)}
<h5>{organizationProject.name}</h5>

<Button onclick={() => connectToOrganization(organizationProject.slug)}>Connect</Button>
{/snippet}
</Loading>
</SectionCard>
{/each}

<Button onclick={() => connectToOrganization()}>Create organization project</Button>
</Modal>
Expand Down
2 changes: 1 addition & 1 deletion apps/desktop/src/lib/stores/user.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { showError } from '$lib/notifications/toasts';
import { copyToClipboard } from '$lib/utils/clipboard';
import { sleep } from '$lib/utils/sleep';
import { openExternalUrl } from '$lib/utils/url';
import { type HttpClient } from '@gitbutler/shared/httpClient';
import { type HttpClient } from '@gitbutler/shared/network/httpClient';
import { plainToInstance } from 'class-transformer';
import { derived, writable } from 'svelte/store';
import type { PostHogWrapper } from '$lib/analytics/posthog';
Expand Down
2 changes: 1 addition & 1 deletion apps/desktop/src/routes/+layout.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@
import * as events from '$lib/utils/events';
import { unsubscribe } from '$lib/utils/unsubscribe';
import { FeedService } from '@gitbutler/shared/feeds/service';
import { HttpClient } from '@gitbutler/shared/httpClient';
import { HttpClient } from '@gitbutler/shared/network/httpClient';
import { OrganizationService } from '@gitbutler/shared/organizations/organizationService';
import { ProjectService as CloudProjectService } from '@gitbutler/shared/organizations/projectService';
import { AppDispatch, AppState } from '@gitbutler/shared/redux/store.svelte';
Expand Down
2 changes: 1 addition & 1 deletion apps/desktop/src/routes/+layout.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import { RemotesService } from '$lib/remotes/service';
import { RustSecretService } from '$lib/secrets/secretsService';
import { TokenMemoryService } from '$lib/stores/tokenMemoryService';
import { UserService } from '$lib/stores/user';
import { HttpClient } from '@gitbutler/shared/httpClient';
import { HttpClient } from '@gitbutler/shared/network/httpClient';
import { LineManagerFactory } from '@gitbutler/ui/commitLines/lineManager';
import { LineManagerFactory as StackingLineManagerFactory } from '@gitbutler/ui/commitLines/lineManager';
import lscache from 'lscache';
Expand Down
2 changes: 1 addition & 1 deletion apps/desktop/src/routes/[projectId]/+layout.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@
import { VirtualBranchService } from '$lib/vbranches/virtualBranch';
import { CloudBranchesService } from '@gitbutler/shared/cloud/stacks/service';
import { getContext } from '@gitbutler/shared/context';
import { HttpClient } from '@gitbutler/shared/httpClient';
import { HttpClient } from '@gitbutler/shared/network/httpClient';
import { ProjectService as CloudProjectService } from '@gitbutler/shared/organizations/projectService';
import { AppState } from '@gitbutler/shared/redux/store.svelte';
import { DesktopRoutesService, getRoutesService } from '@gitbutler/shared/sharedRoutes';
Expand Down
17 changes: 12 additions & 5 deletions apps/desktop/src/routes/[projectId]/feed/+page.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -22,18 +22,25 @@
: undefined
);
const feed = $derived(getFeed(appState, feedService, feedIdentity?.current));
const feed = $derived.by(() => {
if (feedIdentity?.current.status !== 'found') return;
return getFeed(appState, feedService, feedIdentity?.current.value);
});
// Infinite scrolling
const lastPost = $derived(getFeedLastPost(appState, feedService, feed.current));
const lastPost = $derived(getFeedLastPost(appState, feedService, feed?.current));
let lastElement = $state<HTMLElement | undefined>();
$effect(() => {
if (!lastElement) return;
const observer = new IntersectionObserver((entries) => {
if (entries[0]?.isIntersecting && lastPost.current?.createdAt && feedIdentity?.current) {
feedService.getFeedPage(feedIdentity.current, lastPost.current.createdAt);
if (
entries[0]?.isIntersecting &&
lastPost.current?.createdAt &&
feedIdentity?.current.status === 'found'
) {
feedService.getFeedPage(feedIdentity.current.value, lastPost.current.createdAt);
}
});
Expand All @@ -51,7 +58,7 @@

<hr />

{#if feed.current}
{#if feed?.current}
{#each feed.current.postIds as postId, index (postId)}
<div class="bleep-container">
{#if index < feed.current.postIds.length - 1 && lastPost.current && feedIdentity?.current}
Expand Down
Loading

0 comments on commit 8eec4ce

Please sign in to comment.