mirror of
https://github.com/gitbutlerapp/gitbutler.git
synced 2024-12-19 15:41:31 +03:00
Make it possible to view remote commits using existing components
- we need a union type rather than using File | RemoteFile everywhere
This commit is contained in:
parent
a69b6d9279
commit
f5428dcec7
@ -4,8 +4,9 @@
|
|||||||
import Modal from '$lib/components/Modal.svelte';
|
import Modal from '$lib/components/Modal.svelte';
|
||||||
import { projectMergeUpstreamWarningDismissed } from '$lib/config/config';
|
import { projectMergeUpstreamWarningDismissed } from '$lib/config/config';
|
||||||
import { tooltip } from '$lib/utils/tooltip';
|
import { tooltip } from '$lib/utils/tooltip';
|
||||||
|
import { writable } from 'svelte/store';
|
||||||
import type { BranchController } from '$lib/vbranches/branchController';
|
import type { BranchController } from '$lib/vbranches/branchController';
|
||||||
import type { BaseBranch } from '$lib/vbranches/types';
|
import type { BaseBranch, File, RemoteFile } from '$lib/vbranches/types';
|
||||||
|
|
||||||
export let base: BaseBranch;
|
export let base: BaseBranch;
|
||||||
export let projectId: string;
|
export let projectId: string;
|
||||||
@ -15,6 +16,7 @@
|
|||||||
const mergeUpstreamWarningDismissed = projectMergeUpstreamWarningDismissed(
|
const mergeUpstreamWarningDismissed = projectMergeUpstreamWarningDismissed(
|
||||||
branchController.projectId
|
branchController.projectId
|
||||||
);
|
);
|
||||||
|
const selectedFiles = writable<(File | RemoteFile)[]>([]);
|
||||||
|
|
||||||
let updateTargetModal: Modal;
|
let updateTargetModal: Modal;
|
||||||
let mergeUpstreamWarningDismissedCheckbox = false;
|
let mergeUpstreamWarningDismissedCheckbox = false;
|
||||||
@ -52,6 +54,7 @@
|
|||||||
<CommitCard
|
<CommitCard
|
||||||
{commit}
|
{commit}
|
||||||
{projectId}
|
{projectId}
|
||||||
|
{selectedFiles}
|
||||||
commitUrl={base.commitUrl(commit.id)}
|
commitUrl={base.commitUrl(commit.id)}
|
||||||
{projectPath}
|
{projectPath}
|
||||||
{branchController}
|
{branchController}
|
||||||
@ -76,6 +79,7 @@
|
|||||||
<CommitCard
|
<CommitCard
|
||||||
{commit}
|
{commit}
|
||||||
{projectId}
|
{projectId}
|
||||||
|
{selectedFiles}
|
||||||
commitUrl={base.commitUrl(commit.id)}
|
commitUrl={base.commitUrl(commit.id)}
|
||||||
{projectPath}
|
{projectPath}
|
||||||
{branchController}
|
{branchController}
|
||||||
|
@ -28,7 +28,7 @@
|
|||||||
import type { BranchService } from '$lib/branches/service';
|
import type { BranchService } from '$lib/branches/service';
|
||||||
import type { GitHubService } from '$lib/github/service';
|
import type { GitHubService } from '$lib/github/service';
|
||||||
import type { BranchController } from '$lib/vbranches/branchController';
|
import type { BranchController } from '$lib/vbranches/branchController';
|
||||||
import type { BaseBranch, Branch, File } from '$lib/vbranches/types';
|
import type { BaseBranch, Branch, File, RemoteFile } from '$lib/vbranches/types';
|
||||||
|
|
||||||
export let branch: Branch;
|
export let branch: Branch;
|
||||||
export let isUnapplied = false;
|
export let isUnapplied = false;
|
||||||
@ -245,6 +245,7 @@
|
|||||||
{branchService}
|
{branchService}
|
||||||
{branchCount}
|
{branchCount}
|
||||||
{isUnapplied}
|
{isUnapplied}
|
||||||
|
{selectedFiles}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -4,7 +4,8 @@
|
|||||||
import type { BranchService } from '$lib/branches/service';
|
import type { BranchService } from '$lib/branches/service';
|
||||||
import type { GitHubService } from '$lib/github/service';
|
import type { GitHubService } from '$lib/github/service';
|
||||||
import type { BranchController } from '$lib/vbranches/branchController';
|
import type { BranchController } from '$lib/vbranches/branchController';
|
||||||
import type { BaseBranch, Branch } from '$lib/vbranches/types';
|
import type { BaseBranch, Branch, File, RemoteFile } from '$lib/vbranches/types';
|
||||||
|
import type { Writable } from 'svelte/store';
|
||||||
|
|
||||||
export let project: Project;
|
export let project: Project;
|
||||||
export let branch: Branch;
|
export let branch: Branch;
|
||||||
@ -12,6 +13,7 @@
|
|||||||
export let githubService: GitHubService;
|
export let githubService: GitHubService;
|
||||||
export let branchController: BranchController;
|
export let branchController: BranchController;
|
||||||
export let branchService: BranchService;
|
export let branchService: BranchService;
|
||||||
|
export let selectedFiles: Writable<(File | RemoteFile)[]>;
|
||||||
export let isUnapplied: boolean;
|
export let isUnapplied: boolean;
|
||||||
export let branchCount: number;
|
export let branchCount: number;
|
||||||
</script>
|
</script>
|
||||||
@ -26,6 +28,7 @@
|
|||||||
{branchCount}
|
{branchCount}
|
||||||
{githubService}
|
{githubService}
|
||||||
{isUnapplied}
|
{isUnapplied}
|
||||||
|
{selectedFiles}
|
||||||
type="upstream"
|
type="upstream"
|
||||||
/>
|
/>
|
||||||
<CommitList
|
<CommitList
|
||||||
@ -36,6 +39,7 @@
|
|||||||
{branchService}
|
{branchService}
|
||||||
{githubService}
|
{githubService}
|
||||||
{isUnapplied}
|
{isUnapplied}
|
||||||
|
{selectedFiles}
|
||||||
type="local"
|
type="local"
|
||||||
/>
|
/>
|
||||||
<CommitList
|
<CommitList
|
||||||
@ -46,6 +50,7 @@
|
|||||||
{branchService}
|
{branchService}
|
||||||
{githubService}
|
{githubService}
|
||||||
{isUnapplied}
|
{isUnapplied}
|
||||||
|
{selectedFiles}
|
||||||
type="remote"
|
type="remote"
|
||||||
/>
|
/>
|
||||||
<CommitList
|
<CommitList
|
||||||
@ -56,6 +61,7 @@
|
|||||||
{branchService}
|
{branchService}
|
||||||
{githubService}
|
{githubService}
|
||||||
{isUnapplied}
|
{isUnapplied}
|
||||||
|
{selectedFiles}
|
||||||
type="integrated"
|
type="integrated"
|
||||||
/>
|
/>
|
||||||
{/if}
|
{/if}
|
||||||
|
@ -1,10 +1,7 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
|
import BranchFilesHeader from './BranchFilesHeader.svelte';
|
||||||
import BranchFilesList from './BranchFilesList.svelte';
|
import BranchFilesList from './BranchFilesList.svelte';
|
||||||
import FileTree from './FileTree.svelte';
|
import FileTree from './FileTree.svelte';
|
||||||
import Badge from '$lib/components/Badge.svelte';
|
|
||||||
import Checkbox from '$lib/components/Checkbox.svelte';
|
|
||||||
import Segment from '$lib/components/SegmentControl/Segment.svelte';
|
|
||||||
import SegmentedControl from '$lib/components/SegmentControl/SegmentedControl.svelte';
|
|
||||||
import { filesToFileTree } from '$lib/vbranches/filetree';
|
import { filesToFileTree } from '$lib/vbranches/filetree';
|
||||||
import type { Ownership } from '$lib/vbranches/ownership';
|
import type { Ownership } from '$lib/vbranches/ownership';
|
||||||
import type { Branch, File } from '$lib/vbranches/types';
|
import type { Branch, File } from '$lib/vbranches/types';
|
||||||
@ -17,39 +14,6 @@
|
|||||||
export let showCheckboxes = false;
|
export let showCheckboxes = false;
|
||||||
|
|
||||||
let selectedListMode: string;
|
let selectedListMode: string;
|
||||||
|
|
||||||
let headerElement: HTMLDivElement;
|
|
||||||
|
|
||||||
function isAllChecked(selectedOwnership: Ownership): boolean {
|
|
||||||
return branch.files.every((f) =>
|
|
||||||
f.hunks.every((h) => selectedOwnership.containsHunk(f.id, h.id))
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
$: checked = isAllChecked($selectedOwnership);
|
|
||||||
|
|
||||||
function isIndeterminate(selectedOwnership: Ownership): boolean {
|
|
||||||
if (branch.files.length <= 1) return false;
|
|
||||||
|
|
||||||
let file = branch.files[0];
|
|
||||||
let prev = selectedOwnership.containsHunk(file.id, ...file.hunkIds);
|
|
||||||
for (let i = 1; i < branch.files.length; i++) {
|
|
||||||
file = branch.files[i];
|
|
||||||
const contained = selectedOwnership.containsHunk(file.id, ...file.hunkIds);
|
|
||||||
if (contained != prev) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
$: indeterminate = isIndeterminate($selectedOwnership);
|
|
||||||
|
|
||||||
function selectAll(selectedOwnership: Writable<Ownership>, files: File[]) {
|
|
||||||
files.forEach((f) =>
|
|
||||||
selectedOwnership.update((ownership) => ownership.addHunk(f.id, ...f.hunks.map((h) => h.id)))
|
|
||||||
);
|
|
||||||
}
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{#if branch.active && branch.conflicted}
|
{#if branch.active && branch.conflicted}
|
||||||
@ -64,38 +28,21 @@
|
|||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
<div class="branch-files" class:isUnapplied>
|
<div class="branch-files" class:isUnapplied>
|
||||||
<div class="header" bind:this={headerElement}>
|
<div class="branch-files__header">
|
||||||
<div class="header__left">
|
<BranchFilesHeader
|
||||||
{#if showCheckboxes && selectedListMode == 'list' && branch.files.length > 1}
|
files={branch.files}
|
||||||
<Checkbox
|
{selectedOwnership}
|
||||||
small
|
{showCheckboxes}
|
||||||
{checked}
|
bind:selectedListMode
|
||||||
{indeterminate}
|
|
||||||
on:change={(e) => {
|
|
||||||
if (e.detail) {
|
|
||||||
selectAll(selectedOwnership, branch.files);
|
|
||||||
} else {
|
|
||||||
selectedOwnership.update((ownership) => ownership.clear());
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
/>
|
/>
|
||||||
{/if}
|
|
||||||
<div class="header__title text-base-13 text-semibold">
|
|
||||||
<span>Changes</span>
|
|
||||||
<Badge count={branch.files.length} />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<SegmentedControl bind:selected={selectedListMode} selectedIndex={0}>
|
|
||||||
<Segment id="list" icon="list-view" />
|
|
||||||
<Segment id="tree" icon="tree-view" />
|
|
||||||
</SegmentedControl>
|
|
||||||
</div>
|
</div>
|
||||||
{#if branch.files.length > 0}
|
{#if branch.files.length > 0}
|
||||||
<div class="scroll-container">
|
<div class="files-padding">
|
||||||
<!-- TODO: This is an experiment in file sorting. Accept or reject! -->
|
|
||||||
{#if selectedListMode == 'list'}
|
{#if selectedListMode == 'list'}
|
||||||
<BranchFilesList
|
<BranchFilesList
|
||||||
{branch}
|
allowMultiple
|
||||||
|
branchId={branch.id}
|
||||||
|
files={branch.files}
|
||||||
{selectedOwnership}
|
{selectedOwnership}
|
||||||
{selectedFiles}
|
{selectedFiles}
|
||||||
{showCheckboxes}
|
{showCheckboxes}
|
||||||
@ -103,6 +50,7 @@
|
|||||||
/>
|
/>
|
||||||
{:else}
|
{:else}
|
||||||
<FileTree
|
<FileTree
|
||||||
|
allowMultiple
|
||||||
node={filesToFileTree(branch.files)}
|
node={filesToFileTree(branch.files)}
|
||||||
{showCheckboxes}
|
{showCheckboxes}
|
||||||
branchId={branch.id}
|
branchId={branch.id}
|
||||||
@ -121,37 +69,21 @@
|
|||||||
flex: 1;
|
flex: 1;
|
||||||
background: var(--clr-theme-container-light);
|
background: var(--clr-theme-container-light);
|
||||||
border-radius: var(--radius-m) var(--radius-m) 0 0;
|
border-radius: var(--radius-m) var(--radius-m) 0 0;
|
||||||
|
|
||||||
&.isUnapplied {
|
&.isUnapplied {
|
||||||
border-radius: var(--radius-m);
|
border-radius: var(--radius-m);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.scroll-container {
|
.branch-files__header {
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: var(--space-2);
|
|
||||||
padding-top: 0;
|
|
||||||
padding-left: var(--space-12);
|
|
||||||
padding-right: var(--space-12);
|
|
||||||
padding-bottom: var(--space-16);
|
|
||||||
}
|
|
||||||
.header {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: space-between;
|
|
||||||
padding-top: var(--space-12);
|
padding-top: var(--space-12);
|
||||||
padding-bottom: var(--space-12);
|
padding-bottom: var(--space-12);
|
||||||
padding-left: var(--space-20);
|
padding-left: var(--space-20);
|
||||||
padding-right: var(--space-12);
|
padding-right: var(--space-12);
|
||||||
border-color: var(--clr-theme-container-outline-light);
|
|
||||||
}
|
}
|
||||||
.header__title {
|
.files-padding {
|
||||||
display: flex;
|
padding-top: 0;
|
||||||
align-items: center;
|
padding-bottom: var(--space-12);
|
||||||
gap: var(--space-4);
|
padding-left: var(--space-12);
|
||||||
color: var(--clr-theme-scale-ntrl-0);
|
padding-right: var(--space-12);
|
||||||
}
|
|
||||||
.header__left {
|
|
||||||
display: flex;
|
|
||||||
gap: var(--space-10);
|
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
88
gitbutler-ui/src/lib/components/BranchFilesHeader.svelte
Normal file
88
gitbutler-ui/src/lib/components/BranchFilesHeader.svelte
Normal file
@ -0,0 +1,88 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import Badge from '$lib/components/Badge.svelte';
|
||||||
|
import Checkbox from '$lib/components/Checkbox.svelte';
|
||||||
|
import Segment from '$lib/components/SegmentControl/Segment.svelte';
|
||||||
|
import SegmentedControl from '$lib/components/SegmentControl/SegmentedControl.svelte';
|
||||||
|
import type { Ownership } from '$lib/vbranches/ownership';
|
||||||
|
import type { File, RemoteFile } from '$lib/vbranches/types';
|
||||||
|
import type { Writable } from 'svelte/store';
|
||||||
|
|
||||||
|
export let files: (File | RemoteFile)[];
|
||||||
|
export let selectedOwnership: Writable<Ownership>;
|
||||||
|
export let showCheckboxes = false;
|
||||||
|
|
||||||
|
export let selectedListMode: string;
|
||||||
|
|
||||||
|
function selectAll(selectedOwnership: Writable<Ownership>, files: (File | RemoteFile)[]) {
|
||||||
|
files.forEach((f) =>
|
||||||
|
selectedOwnership.update((ownership) => ownership.addHunk(f.id, ...f.hunks.map((h) => h.id)))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function isAllChecked(selectedOwnership: Ownership): boolean {
|
||||||
|
return files.every((f) => f.hunks.every((h) => selectedOwnership.containsHunk(f.id, h.id)));
|
||||||
|
}
|
||||||
|
|
||||||
|
function isIndeterminate(selectedOwnership: Ownership): boolean {
|
||||||
|
if (files.length <= 1) return false;
|
||||||
|
|
||||||
|
let file = files[0];
|
||||||
|
let prev = selectedOwnership.containsHunk(file.id, ...file.hunkIds);
|
||||||
|
for (let i = 1; i < files.length; i++) {
|
||||||
|
file = files[i];
|
||||||
|
const contained = selectedOwnership.containsHunk(file.id, ...file.hunkIds);
|
||||||
|
if (contained != prev) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
$: indeterminate = isIndeterminate($selectedOwnership);
|
||||||
|
$: checked = isAllChecked($selectedOwnership);
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class="header">
|
||||||
|
<div class="header__left">
|
||||||
|
{#if showCheckboxes && selectedListMode == 'list' && files.length > 1}
|
||||||
|
<Checkbox
|
||||||
|
small
|
||||||
|
{checked}
|
||||||
|
{indeterminate}
|
||||||
|
on:change={(e) => {
|
||||||
|
if (e.detail) {
|
||||||
|
selectAll(selectedOwnership, files);
|
||||||
|
} else {
|
||||||
|
selectedOwnership.update((ownership) => ownership.clear());
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
{/if}
|
||||||
|
<div class="header__title text-base-13 text-semibold">
|
||||||
|
<span>Changes</span>
|
||||||
|
<Badge count={files.length} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<SegmentedControl bind:selected={selectedListMode} selectedIndex={0}>
|
||||||
|
<Segment id="list" icon="list-view" />
|
||||||
|
<Segment id="tree" icon="tree-view" />
|
||||||
|
</SegmentedControl>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style lang="postcss">
|
||||||
|
.header {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
}
|
||||||
|
.header__title {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: var(--space-4);
|
||||||
|
color: var(--clr-theme-scale-ntrl-0);
|
||||||
|
}
|
||||||
|
.header__left {
|
||||||
|
display: flex;
|
||||||
|
gap: var(--space-10);
|
||||||
|
}
|
||||||
|
</style>
|
@ -2,21 +2,25 @@
|
|||||||
import FileListItem from './FileListItem.svelte';
|
import FileListItem from './FileListItem.svelte';
|
||||||
import { sortLikeFileTree } from '$lib/vbranches/filetree';
|
import { sortLikeFileTree } from '$lib/vbranches/filetree';
|
||||||
import type { Ownership } from '$lib/vbranches/ownership';
|
import type { Ownership } from '$lib/vbranches/ownership';
|
||||||
import type { Branch, File } from '$lib/vbranches/types';
|
import type { File, RemoteFile } from '$lib/vbranches/types';
|
||||||
import type { Writable } from 'svelte/store';
|
import type { Writable } from 'svelte/store';
|
||||||
|
|
||||||
export let branch: Branch;
|
export let branchId: string;
|
||||||
|
export let files: (File | RemoteFile)[];
|
||||||
export let selectedOwnership: Writable<Ownership>;
|
export let selectedOwnership: Writable<Ownership>;
|
||||||
export let isUnapplied = false;
|
export let isUnapplied = false;
|
||||||
export let showCheckboxes = false;
|
export let showCheckboxes = false;
|
||||||
export let selectedFiles: Writable<File[]>;
|
export let selectedFiles: Writable<(File | RemoteFile)[]>;
|
||||||
|
export let allowMultiple = false;
|
||||||
|
|
||||||
|
$: console.log(selectedFiles);
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{#each sortLikeFileTree(branch.files) as file (file.id)}
|
{#each sortLikeFileTree(files) as file (file.id)}
|
||||||
<FileListItem
|
<FileListItem
|
||||||
{file}
|
{file}
|
||||||
{isUnapplied}
|
{isUnapplied}
|
||||||
branchId={branch.id}
|
{branchId}
|
||||||
{selectedOwnership}
|
{selectedOwnership}
|
||||||
showCheckbox={showCheckboxes}
|
showCheckbox={showCheckboxes}
|
||||||
{selectedFiles}
|
{selectedFiles}
|
||||||
@ -26,7 +30,7 @@
|
|||||||
selectedFiles.update((fileIds) => fileIds.filter((f) => f.id != file.id));
|
selectedFiles.update((fileIds) => fileIds.filter((f) => f.id != file.id));
|
||||||
} else if (isAlreadySelected) {
|
} else if (isAlreadySelected) {
|
||||||
$selectedFiles = [];
|
$selectedFiles = [];
|
||||||
} else if (e.shiftKey) {
|
} else if (e.shiftKey && allowMultiple) {
|
||||||
selectedFiles.update((files) => [file, ...files]);
|
selectedFiles.update((files) => [file, ...files]);
|
||||||
} else {
|
} else {
|
||||||
$selectedFiles = [file];
|
$selectedFiles = [file];
|
||||||
|
@ -2,13 +2,13 @@
|
|||||||
import BranchCard from './BranchCard.svelte';
|
import BranchCard from './BranchCard.svelte';
|
||||||
import FileCard from './FileCard.svelte';
|
import FileCard from './FileCard.svelte';
|
||||||
import { Ownership } from '$lib/vbranches/ownership';
|
import { Ownership } from '$lib/vbranches/ownership';
|
||||||
|
import { RemoteFile, type BaseBranch, type Branch, type File } from '$lib/vbranches/types';
|
||||||
import { writable, type Writable } from 'svelte/store';
|
import { writable, type Writable } from 'svelte/store';
|
||||||
import type { User, getCloudApiClient } from '$lib/backend/cloud';
|
import type { User, getCloudApiClient } from '$lib/backend/cloud';
|
||||||
import type { Project } from '$lib/backend/projects';
|
import type { Project } from '$lib/backend/projects';
|
||||||
import type { BranchService } from '$lib/branches/service';
|
import type { BranchService } from '$lib/branches/service';
|
||||||
import type { GitHubService } from '$lib/github/service';
|
import type { GitHubService } from '$lib/github/service';
|
||||||
import type { BranchController } from '$lib/vbranches/branchController';
|
import type { BranchController } from '$lib/vbranches/branchController';
|
||||||
import type { BaseBranch, Branch, File } from '$lib/vbranches/types';
|
|
||||||
|
|
||||||
export let branch: Branch;
|
export let branch: Branch;
|
||||||
export let isUnapplied = false;
|
export let isUnapplied = false;
|
||||||
@ -29,8 +29,11 @@
|
|||||||
|
|
||||||
let commitBoxOpen: Writable<boolean>;
|
let commitBoxOpen: Writable<boolean>;
|
||||||
|
|
||||||
function setSelected(files: File[], branch: Branch) {
|
function setSelected(files: (File | RemoteFile)[], branch: Branch) {
|
||||||
if (files.length == 0) return undefined;
|
if (files.length == 0) return undefined;
|
||||||
|
if (files.length == 1 && files[0] instanceof RemoteFile) return files[0];
|
||||||
|
|
||||||
|
// If regular file selected but not found in branch files then clear selection.
|
||||||
const match = branch.files?.find((f) => files[0].id == f.id);
|
const match = branch.files?.find((f) => files[0].id == f.id);
|
||||||
if (!match) $selectedFiles = [];
|
if (!match) $selectedFiles = [];
|
||||||
return match;
|
return match;
|
||||||
|
@ -1,5 +1,8 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
|
import BranchFilesHeader from './BranchFilesHeader.svelte';
|
||||||
|
import BranchFilesList from './BranchFilesList.svelte';
|
||||||
import FileDiff from './FileDiff.svelte';
|
import FileDiff from './FileDiff.svelte';
|
||||||
|
import FileTree from './FileTree.svelte';
|
||||||
import Button from '$lib/components/Button.svelte';
|
import Button from '$lib/components/Button.svelte';
|
||||||
import Modal from '$lib/components/Modal.svelte';
|
import Modal from '$lib/components/Modal.svelte';
|
||||||
import Tag from '$lib/components/Tag.svelte';
|
import Tag from '$lib/components/Tag.svelte';
|
||||||
@ -7,9 +10,12 @@
|
|||||||
import { draggable } from '$lib/dragging/draggable';
|
import { draggable } from '$lib/dragging/draggable';
|
||||||
import { draggableCommit, nonDraggable } from '$lib/dragging/draggables';
|
import { draggableCommit, nonDraggable } from '$lib/dragging/draggables';
|
||||||
import { getVSIFileIcon } from '$lib/ext-icons';
|
import { getVSIFileIcon } from '$lib/ext-icons';
|
||||||
|
import { filesToFileTree } from '$lib/vbranches/filetree';
|
||||||
|
import { Ownership } from '$lib/vbranches/ownership';
|
||||||
import { listRemoteCommitFiles } from '$lib/vbranches/remoteCommits';
|
import { listRemoteCommitFiles } from '$lib/vbranches/remoteCommits';
|
||||||
import { RemoteCommit, Commit, RemoteFile } from '$lib/vbranches/types';
|
import { File, RemoteCommit, Commit, RemoteFile } from '$lib/vbranches/types';
|
||||||
import { open } from '@tauri-apps/api/shell';
|
import { open } from '@tauri-apps/api/shell';
|
||||||
|
import { writable, type Writable } from 'svelte/store';
|
||||||
import type { ContentSection, HunkSection } from '$lib/utils/fileSections';
|
import type { ContentSection, HunkSection } from '$lib/utils/fileSections';
|
||||||
import type { BranchController } from '$lib/vbranches/branchController';
|
import type { BranchController } from '$lib/vbranches/branchController';
|
||||||
|
|
||||||
@ -21,8 +27,13 @@
|
|||||||
export let isUnapplied = false;
|
export let isUnapplied = false;
|
||||||
export let branchController: BranchController;
|
export let branchController: BranchController;
|
||||||
export let projectPath: string;
|
export let projectPath: string;
|
||||||
|
export let selectedFiles: Writable<(File | RemoteFile)[]>;
|
||||||
|
|
||||||
|
const selectedOwnership = writable(Ownership.default());
|
||||||
|
|
||||||
let previewCommitModal: Modal;
|
let previewCommitModal: Modal;
|
||||||
|
let showFiles = false;
|
||||||
|
let selectedListMode: string;
|
||||||
|
|
||||||
let entries: [RemoteFile, (ContentSection | HunkSection)[]][] = [];
|
let entries: [RemoteFile, (ContentSection | HunkSection)[]][] = [];
|
||||||
let isLoading = false;
|
let isLoading = false;
|
||||||
@ -33,22 +44,22 @@
|
|||||||
isLoading = false;
|
isLoading = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$: files = entries.map((entry) => entry[0]);
|
||||||
|
|
||||||
function onClick() {
|
function onClick() {
|
||||||
loadEntries();
|
loadEntries();
|
||||||
previewCommitModal.show();
|
showFiles = !showFiles;
|
||||||
|
// previewCommitModal.show();
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
on:click={onClick}
|
|
||||||
on:keyup={onClick}
|
|
||||||
use:draggable={commit instanceof Commit
|
use:draggable={commit instanceof Commit
|
||||||
? draggableCommit(commit.branchId, commit)
|
? draggableCommit(commit.branchId, commit)
|
||||||
: nonDraggable()}
|
: nonDraggable()}
|
||||||
role="button"
|
|
||||||
tabindex="0"
|
|
||||||
>
|
>
|
||||||
<div class="commit__card" class:is-head-commit={isHeadCommit}>
|
<div class="commit__card" class:is-head-commit={isHeadCommit}>
|
||||||
|
<div on:click={onClick} on:keyup={onClick} role="button" tabindex="0">
|
||||||
<div class="commit__header">
|
<div class="commit__header">
|
||||||
<span class="commit__description text-base-12 truncate">
|
<span class="commit__description text-base-12 truncate">
|
||||||
{commit.description}
|
{commit.description}
|
||||||
@ -85,6 +96,37 @@
|
|||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
{#if showFiles}
|
||||||
|
<div class="commit__files-header">
|
||||||
|
<BranchFilesHeader
|
||||||
|
{files}
|
||||||
|
{selectedOwnership}
|
||||||
|
showCheckboxes={false}
|
||||||
|
bind:selectedListMode
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="commit__files">
|
||||||
|
{#if selectedListMode == 'list'}
|
||||||
|
<BranchFilesList
|
||||||
|
branchId="blah"
|
||||||
|
{files}
|
||||||
|
{selectedOwnership}
|
||||||
|
{selectedFiles}
|
||||||
|
{isUnapplied}
|
||||||
|
/>
|
||||||
|
{:else}
|
||||||
|
<FileTree
|
||||||
|
node={filesToFileTree(files)}
|
||||||
|
branchId="blah"
|
||||||
|
isRoot={true}
|
||||||
|
{selectedOwnership}
|
||||||
|
{selectedFiles}
|
||||||
|
{isUnapplied}
|
||||||
|
/>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Modal
|
<Modal
|
||||||
@ -168,7 +210,6 @@
|
|||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
cursor: default;
|
cursor: default;
|
||||||
gap: var(--space-10);
|
gap: var(--space-10);
|
||||||
padding: var(--space-12);
|
|
||||||
border-radius: var(--space-6);
|
border-radius: var(--space-6);
|
||||||
background-color: var(--clr-theme-container-light);
|
background-color: var(--clr-theme-container-light);
|
||||||
border: 1px solid var(--clr-theme-container-outline-light);
|
border: 1px solid var(--clr-theme-container-outline-light);
|
||||||
@ -183,6 +224,7 @@
|
|||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: var(--space-8);
|
gap: var(--space-8);
|
||||||
|
padding: var(--space-12) var(--space-12) 0 var(--space-12);
|
||||||
}
|
}
|
||||||
|
|
||||||
.commit__description {
|
.commit__description {
|
||||||
@ -197,6 +239,22 @@
|
|||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: var(--space-8);
|
gap: var(--space-8);
|
||||||
|
padding: 0 var(--space-12) var(--space-12) var(--space-12);
|
||||||
|
}
|
||||||
|
|
||||||
|
.commit__files {
|
||||||
|
padding-top: 0;
|
||||||
|
padding-left: var(--space-12);
|
||||||
|
padding-right: var(--space-12);
|
||||||
|
padding-bottom: var(--space-12);
|
||||||
|
}
|
||||||
|
|
||||||
|
.commit__files-header {
|
||||||
|
border-top: 1px solid var(--clr-theme-container-outline-light);
|
||||||
|
padding-top: var(--space-12);
|
||||||
|
padding-bottom: var(--space-12);
|
||||||
|
padding-left: var(--space-20);
|
||||||
|
padding-right: var(--space-12);
|
||||||
}
|
}
|
||||||
|
|
||||||
.commit__author {
|
.commit__author {
|
||||||
|
@ -6,7 +6,8 @@
|
|||||||
import type { BranchService } from '$lib/branches/service';
|
import type { BranchService } from '$lib/branches/service';
|
||||||
import type { GitHubService } from '$lib/github/service';
|
import type { GitHubService } from '$lib/github/service';
|
||||||
import type { BranchController } from '$lib/vbranches/branchController';
|
import type { BranchController } from '$lib/vbranches/branchController';
|
||||||
import type { BaseBranch, Branch, CommitStatus } from '$lib/vbranches/types';
|
import type { BaseBranch, Branch, CommitStatus, File, RemoteFile } from '$lib/vbranches/types';
|
||||||
|
import type { Writable } from 'svelte/store';
|
||||||
|
|
||||||
export let branch: Branch;
|
export let branch: Branch;
|
||||||
export let base: BaseBranch | undefined | null;
|
export let base: BaseBranch | undefined | null;
|
||||||
@ -15,6 +16,7 @@
|
|||||||
export let type: CommitStatus;
|
export let type: CommitStatus;
|
||||||
export let githubService: GitHubService;
|
export let githubService: GitHubService;
|
||||||
export let branchService: BranchService;
|
export let branchService: BranchService;
|
||||||
|
export let selectedFiles: Writable<(File | RemoteFile)[]>;
|
||||||
export let isUnapplied: boolean;
|
export let isUnapplied: boolean;
|
||||||
export let branchCount: number = 0;
|
export let branchCount: number = 0;
|
||||||
|
|
||||||
@ -47,6 +49,7 @@
|
|||||||
{base}
|
{base}
|
||||||
{project}
|
{project}
|
||||||
{isUnapplied}
|
{isUnapplied}
|
||||||
|
{selectedFiles}
|
||||||
isChained={idx != commits.length - 1}
|
isChained={idx != commits.length - 1}
|
||||||
isHeadCommit={commit.id === headCommit?.id}
|
isHeadCommit={commit.id === headCommit?.id}
|
||||||
/>
|
/>
|
||||||
|
@ -11,8 +11,15 @@
|
|||||||
} from '$lib/dragging/draggables';
|
} from '$lib/dragging/draggables';
|
||||||
import { dropzone } from '$lib/dragging/dropzone';
|
import { dropzone } from '$lib/dragging/dropzone';
|
||||||
import { filesToOwnership } from '$lib/vbranches/ownership';
|
import { filesToOwnership } from '$lib/vbranches/ownership';
|
||||||
import { RemoteCommit, type BaseBranch, type Branch, type Commit } from '$lib/vbranches/types';
|
import {
|
||||||
import { get } from 'svelte/store';
|
RemoteCommit,
|
||||||
|
type BaseBranch,
|
||||||
|
type Branch,
|
||||||
|
type Commit,
|
||||||
|
type File,
|
||||||
|
RemoteFile
|
||||||
|
} from '$lib/vbranches/types';
|
||||||
|
import { get, type Writable } from 'svelte/store';
|
||||||
import type { Project } from '$lib/backend/projects';
|
import type { Project } from '$lib/backend/projects';
|
||||||
import type { BranchController } from '$lib/vbranches/branchController';
|
import type { BranchController } from '$lib/vbranches/branchController';
|
||||||
|
|
||||||
@ -24,6 +31,7 @@
|
|||||||
export let isChained: boolean;
|
export let isChained: boolean;
|
||||||
export let isUnapplied = false;
|
export let isUnapplied = false;
|
||||||
export let branchController: BranchController;
|
export let branchController: BranchController;
|
||||||
|
export let selectedFiles: Writable<(File | RemoteFile)[]>;
|
||||||
|
|
||||||
function acceptAmend(commit: Commit | RemoteCommit) {
|
function acceptAmend(commit: Commit | RemoteCommit) {
|
||||||
if (commit instanceof RemoteCommit) {
|
if (commit instanceof RemoteCommit) {
|
||||||
@ -139,6 +147,7 @@
|
|||||||
{resetHeadCommit}
|
{resetHeadCommit}
|
||||||
{isUnapplied}
|
{isUnapplied}
|
||||||
{branchController}
|
{branchController}
|
||||||
|
{selectedFiles}
|
||||||
projectPath={project.path}
|
projectPath={project.path}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
@ -12,12 +12,12 @@
|
|||||||
import { slide } from 'svelte/transition';
|
import { slide } from 'svelte/transition';
|
||||||
import type { BranchController } from '$lib/vbranches/branchController';
|
import type { BranchController } from '$lib/vbranches/branchController';
|
||||||
import type { Ownership } from '$lib/vbranches/ownership';
|
import type { Ownership } from '$lib/vbranches/ownership';
|
||||||
import type { File } from '$lib/vbranches/types';
|
import type { File, RemoteFile } from '$lib/vbranches/types';
|
||||||
import type { Writable } from 'svelte/store';
|
import type { Writable } from 'svelte/store';
|
||||||
|
|
||||||
export let projectId: string;
|
export let projectId: string;
|
||||||
export let branchId: string;
|
export let branchId: string;
|
||||||
export let file: File;
|
export let file: File | RemoteFile;
|
||||||
export let conflicted: boolean;
|
export let conflicted: boolean;
|
||||||
export let projectPath: string | undefined;
|
export let projectPath: string | undefined;
|
||||||
export let branchController: BranchController;
|
export let branchController: BranchController;
|
||||||
@ -35,7 +35,7 @@
|
|||||||
|
|
||||||
let sections: (HunkSection | ContentSection)[] = [];
|
let sections: (HunkSection | ContentSection)[] = [];
|
||||||
|
|
||||||
function parseFile(file: File) {
|
function parseFile(file: File | RemoteFile) {
|
||||||
// When we toggle expansion status on sections we need to assign
|
// When we toggle expansion status on sections we need to assign
|
||||||
// `sections = sections` to redraw, and why we do not use a reactive
|
// `sections = sections` to redraw, and why we do not use a reactive
|
||||||
// variable.
|
// variable.
|
||||||
|
@ -6,9 +6,9 @@
|
|||||||
import { computeFileStatus } from '$lib/utils/fileStatus';
|
import { computeFileStatus } from '$lib/utils/fileStatus';
|
||||||
import { computeAddedRemovedByFiles } from '$lib/utils/metrics';
|
import { computeAddedRemovedByFiles } from '$lib/utils/metrics';
|
||||||
import { createEventDispatcher } from 'svelte';
|
import { createEventDispatcher } from 'svelte';
|
||||||
import type { File } from '$lib/vbranches/types';
|
import type { File, RemoteFile } from '$lib/vbranches/types';
|
||||||
|
|
||||||
export let file: File;
|
export let file: File | RemoteFile;
|
||||||
export let isFileLocked: boolean;
|
export let isFileLocked: boolean;
|
||||||
|
|
||||||
const dispatch = createEventDispatcher<{ close: void }>();
|
const dispatch = createEventDispatcher<{ close: void }>();
|
||||||
|
@ -5,16 +5,16 @@
|
|||||||
import { draggableFile } from '$lib/dragging/draggables';
|
import { draggableFile } from '$lib/dragging/draggables';
|
||||||
import { getVSIFileIcon } from '$lib/ext-icons';
|
import { getVSIFileIcon } from '$lib/ext-icons';
|
||||||
import type { Ownership } from '$lib/vbranches/ownership';
|
import type { Ownership } from '$lib/vbranches/ownership';
|
||||||
import type { File } from '$lib/vbranches/types';
|
import type { File, RemoteFile } from '$lib/vbranches/types';
|
||||||
import type { Writable } from 'svelte/store';
|
import type { Writable } from 'svelte/store';
|
||||||
|
|
||||||
export let branchId: string;
|
export let branchId: string;
|
||||||
export let file: File;
|
export let file: File | RemoteFile;
|
||||||
export let isUnapplied: boolean;
|
export let isUnapplied: boolean;
|
||||||
export let selected: boolean;
|
export let selected: boolean;
|
||||||
export let showCheckbox: boolean = false;
|
export let showCheckbox: boolean = false;
|
||||||
export let selectedOwnership: Writable<Ownership>;
|
export let selectedOwnership: Writable<Ownership>;
|
||||||
export let selectedFiles: Writable<File[]>;
|
export let selectedFiles: Writable<(File | RemoteFile)[]>;
|
||||||
|
|
||||||
let checked = false;
|
let checked = false;
|
||||||
let indeterminate = false;
|
let indeterminate = false;
|
||||||
|
@ -2,9 +2,9 @@
|
|||||||
import FileStatusCircle from './FileStatusCircle.svelte';
|
import FileStatusCircle from './FileStatusCircle.svelte';
|
||||||
import Icon from '$lib/components/Icon.svelte';
|
import Icon from '$lib/components/Icon.svelte';
|
||||||
import { computeFileStatus } from '$lib/utils/fileStatus';
|
import { computeFileStatus } from '$lib/utils/fileStatus';
|
||||||
import type { File } from '$lib/vbranches/types';
|
import type { File, RemoteFile } from '$lib/vbranches/types';
|
||||||
|
|
||||||
export let file: File;
|
export let file: File | RemoteFile;
|
||||||
$: isLocked = file.hunks.some((h) => h.locked);
|
$: isLocked = file.hunks.some((h) => h.locked);
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
@ -7,7 +7,7 @@
|
|||||||
import TreeListFolder from './TreeListFolder.svelte';
|
import TreeListFolder from './TreeListFolder.svelte';
|
||||||
import type { TreeNode } from '$lib/vbranches/filetree';
|
import type { TreeNode } from '$lib/vbranches/filetree';
|
||||||
import type { Ownership } from '$lib/vbranches/ownership';
|
import type { Ownership } from '$lib/vbranches/ownership';
|
||||||
import type { File } from '$lib/vbranches/types';
|
import type { File, RemoteFile } from '$lib/vbranches/types';
|
||||||
import type { Writable } from 'svelte/store';
|
import type { Writable } from 'svelte/store';
|
||||||
|
|
||||||
export let expanded = true;
|
export let expanded = true;
|
||||||
@ -15,9 +15,10 @@
|
|||||||
export let isRoot = false;
|
export let isRoot = false;
|
||||||
export let showCheckboxes = false;
|
export let showCheckboxes = false;
|
||||||
export let selectedOwnership: Writable<Ownership>;
|
export let selectedOwnership: Writable<Ownership>;
|
||||||
export let selectedFiles: Writable<File[]>;
|
export let selectedFiles: Writable<(File | RemoteFile)[]>;
|
||||||
export let branchId: string;
|
export let branchId: string;
|
||||||
export let isUnapplied: boolean;
|
export let isUnapplied: boolean;
|
||||||
|
export let allowMultiple = false;
|
||||||
|
|
||||||
function isNodeChecked(selectedOwnership: Ownership, node: TreeNode): boolean {
|
function isNodeChecked(selectedOwnership: Ownership, node: TreeNode): boolean {
|
||||||
if (node.file) {
|
if (node.file) {
|
||||||
@ -91,12 +92,13 @@
|
|||||||
{selectedFiles}
|
{selectedFiles}
|
||||||
showCheckbox={showCheckboxes}
|
showCheckbox={showCheckboxes}
|
||||||
on:click={(e) => {
|
on:click={(e) => {
|
||||||
|
e.stopPropagation();
|
||||||
const isAlreadySelected = $selectedFiles.includes(file);
|
const isAlreadySelected = $selectedFiles.includes(file);
|
||||||
if (isAlreadySelected && e.shiftKey) {
|
if (isAlreadySelected && e.shiftKey) {
|
||||||
selectedFiles.update((fileIds) => fileIds.filter((f) => f.id != file.id));
|
selectedFiles.update((fileIds) => fileIds.filter((f) => f.id != file.id));
|
||||||
} else if (isAlreadySelected) {
|
} else if (isAlreadySelected) {
|
||||||
$selectedFiles = [];
|
$selectedFiles = [];
|
||||||
} else if (e.shiftKey) {
|
} else if (e.shiftKey && allowMultiple) {
|
||||||
selectedFiles.update((files) => [file, ...files]);
|
selectedFiles.update((files) => [file, ...files]);
|
||||||
} else {
|
} else {
|
||||||
$selectedFiles = [file];
|
$selectedFiles = [file];
|
||||||
|
@ -2,12 +2,15 @@
|
|||||||
import Button from '$lib/components/Button.svelte';
|
import Button from '$lib/components/Button.svelte';
|
||||||
import CommitCard from '$lib/components/CommitCard.svelte';
|
import CommitCard from '$lib/components/CommitCard.svelte';
|
||||||
import type { BranchController } from '$lib/vbranches/branchController';
|
import type { BranchController } from '$lib/vbranches/branchController';
|
||||||
import type { RemoteBranch } from '$lib/vbranches/types';
|
import type { File, RemoteBranch, RemoteFile } from '$lib/vbranches/types';
|
||||||
|
import { writable } from 'svelte/store';
|
||||||
|
|
||||||
export let branch: RemoteBranch | undefined;
|
export let branch: RemoteBranch | undefined;
|
||||||
export let projectId: string;
|
export let projectId: string;
|
||||||
export let projectPath: string;
|
export let projectPath: string;
|
||||||
export let branchController: BranchController;
|
export let branchController: BranchController;
|
||||||
|
|
||||||
|
const selectedFiles = writable<(File | RemoteFile)[]>([]);
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{#if branch != undefined}
|
{#if branch != undefined}
|
||||||
@ -24,7 +27,7 @@
|
|||||||
{#if branch.commits && branch.commits.length > 0}
|
{#if branch.commits && branch.commits.length > 0}
|
||||||
<div class="flex w-full flex-col gap-y-2">
|
<div class="flex w-full flex-col gap-y-2">
|
||||||
{#each branch.commits as commit}
|
{#each branch.commits as commit}
|
||||||
<CommitCard {commit} {projectId} {branchController} {projectPath} />
|
<CommitCard {commit} {projectId} {branchController} {projectPath} {selectedFiles} />
|
||||||
{/each}
|
{/each}
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
@ -5,16 +5,16 @@
|
|||||||
import { draggableFile } from '$lib/dragging/draggables';
|
import { draggableFile } from '$lib/dragging/draggables';
|
||||||
import { getVSIFileIcon } from '$lib/ext-icons';
|
import { getVSIFileIcon } from '$lib/ext-icons';
|
||||||
import type { Ownership } from '$lib/vbranches/ownership';
|
import type { Ownership } from '$lib/vbranches/ownership';
|
||||||
import type { File } from '$lib/vbranches/types';
|
import type { File, RemoteFile } from '$lib/vbranches/types';
|
||||||
import type { Writable } from 'svelte/store';
|
import type { Writable } from 'svelte/store';
|
||||||
|
|
||||||
export let branchId: string;
|
export let branchId: string;
|
||||||
export let file: File;
|
export let file: File | RemoteFile;
|
||||||
export let selected: boolean;
|
export let selected: boolean;
|
||||||
export let isUnapplied: boolean;
|
export let isUnapplied: boolean;
|
||||||
export let showCheckbox: boolean = false;
|
export let showCheckbox: boolean = false;
|
||||||
export let selectedOwnership: Writable<Ownership>;
|
export let selectedOwnership: Writable<Ownership>;
|
||||||
export let selectedFiles: Writable<File[]>;
|
export let selectedFiles: Writable<(File | RemoteFile)[]>;
|
||||||
|
|
||||||
let checked = false;
|
let checked = false;
|
||||||
let indeterminate = false;
|
let indeterminate = false;
|
||||||
@ -103,16 +103,19 @@
|
|||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: var(--space-10);
|
gap: var(--space-10);
|
||||||
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
.name-wrapper {
|
.name-wrapper {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: var(--space-6);
|
gap: var(--space-6);
|
||||||
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
.name {
|
.name {
|
||||||
color: var(--clr-theme-scale-ntrl-0);
|
color: var(--clr-theme-scale-ntrl-0);
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
.selected {
|
.selected {
|
||||||
background-color: var(--clr-theme-scale-pop-80);
|
background-color: var(--clr-theme-scale-pop-80);
|
||||||
|
@ -1,77 +0,0 @@
|
|||||||
<script lang="ts">
|
|
||||||
import CommitCard from './CommitCard.svelte';
|
|
||||||
import Button from '$lib/components/Button.svelte';
|
|
||||||
import { draggable } from '$lib/dragging/draggable';
|
|
||||||
import { draggableRemoteCommit } from '$lib/dragging/draggables';
|
|
||||||
import type { BranchController } from '$lib/vbranches/branchController';
|
|
||||||
import type { BaseBranch, RemoteBranch } from '$lib/vbranches/types';
|
|
||||||
|
|
||||||
export let branchId: string;
|
|
||||||
export let projectId: string;
|
|
||||||
export let projectPath: string;
|
|
||||||
export let branchCount: number;
|
|
||||||
export let upstream: RemoteBranch | undefined;
|
|
||||||
export let branchController: BranchController;
|
|
||||||
export let base: BaseBranch | undefined | null;
|
|
||||||
|
|
||||||
let upstreamCommitsShown = false;
|
|
||||||
|
|
||||||
$: if (upstreamCommitsShown && upstream?.commits.length === 0) {
|
|
||||||
upstreamCommitsShown = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
function merge() {
|
|
||||||
branchController.mergeUpstream(branchId);
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
{#if upstream}
|
|
||||||
<div class="bg-zinc-300 p-2 dark:bg-zinc-800">
|
|
||||||
<div class="flex flex-row justify-between">
|
|
||||||
<div class="p-1 text-purple-700">
|
|
||||||
{upstream.commits.length}
|
|
||||||
upstream {upstream.commits.length > 1 ? 'commits' : 'commit'}
|
|
||||||
</div>
|
|
||||||
<Button
|
|
||||||
kind="outlined"
|
|
||||||
color="primary"
|
|
||||||
on:click={() => (upstreamCommitsShown = !upstreamCommitsShown)}
|
|
||||||
>
|
|
||||||
<span class="purple">
|
|
||||||
{#if !upstreamCommitsShown}
|
|
||||||
View
|
|
||||||
{:else}
|
|
||||||
Cancel
|
|
||||||
{/if}
|
|
||||||
</span>
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{#if upstreamCommitsShown}
|
|
||||||
<div
|
|
||||||
class="flex w-full flex-col gap-1 border-t border-light-400 bg-light-300 p-2 dark:border-dark-400 dark:bg-dark-800"
|
|
||||||
id="upstreamCommits"
|
|
||||||
>
|
|
||||||
{#each upstream.commits as commit (commit.id)}
|
|
||||||
<div use:draggable={draggableRemoteCommit(branchId, commit)}>
|
|
||||||
<CommitCard
|
|
||||||
{commit}
|
|
||||||
{projectId}
|
|
||||||
commitUrl={base?.commitUrl(commit.id)}
|
|
||||||
{branchController}
|
|
||||||
{projectPath}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
{/each}
|
|
||||||
<div class="flex justify-end p-2">
|
|
||||||
{#if branchCount > 1}
|
|
||||||
<div class="px-2 text-sm">
|
|
||||||
You have {branchCount} active branches. To merge upstream work, we will unapply all other
|
|
||||||
branches.
|
|
||||||
</div>
|
|
||||||
{/if}
|
|
||||||
<Button color="primary" on:click={merge}>Merge</Button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{/if}
|
|
||||||
{/if}
|
|
@ -1,4 +1,4 @@
|
|||||||
import type { Commit, File, Hunk, RemoteCommit } from '../vbranches/types';
|
import type { Commit, File, Hunk, RemoteCommit, RemoteFile } from '../vbranches/types';
|
||||||
import type { Writable } from 'svelte/store';
|
import type { Writable } from 'svelte/store';
|
||||||
|
|
||||||
export function nonDraggable() {
|
export function nonDraggable() {
|
||||||
@ -23,11 +23,15 @@ export function isDraggableHunk(obj: any): obj is DraggableHunk {
|
|||||||
|
|
||||||
export type DraggableFile = {
|
export type DraggableFile = {
|
||||||
branchId: string;
|
branchId: string;
|
||||||
files: Writable<File[]>;
|
files: Writable<(File | RemoteFile)[]>;
|
||||||
current: File;
|
current: File;
|
||||||
};
|
};
|
||||||
|
|
||||||
export function draggableFile(branchId: string, current: File, files: Writable<File[]>) {
|
export function draggableFile(
|
||||||
|
branchId: string,
|
||||||
|
current: File | RemoteFile,
|
||||||
|
files: Writable<(File | RemoteFile)[]>
|
||||||
|
) {
|
||||||
return { data: { branchId, current, files } };
|
return { data: { branchId, current, files } };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,8 +1,12 @@
|
|||||||
import type { File } from '$lib/vbranches/types';
|
import { RemoteFile, type File } from '$lib/vbranches/types';
|
||||||
|
|
||||||
export type FileStatus = 'A' | 'M' | 'D';
|
export type FileStatus = 'A' | 'M' | 'D';
|
||||||
|
|
||||||
export function computeFileStatus(file: File): FileStatus {
|
export function computeFileStatus(file: File | RemoteFile): FileStatus {
|
||||||
|
if (file instanceof RemoteFile) {
|
||||||
|
// TODO: How do we compute this for remote files?
|
||||||
|
return 'M';
|
||||||
|
}
|
||||||
if (file.hunks.length == 1) {
|
if (file.hunks.length == 1) {
|
||||||
const changeType = file.hunks[0].changeType;
|
const changeType = file.hunks[0].changeType;
|
||||||
if (changeType == 'added') {
|
if (changeType == 'added') {
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import { HunkSection, type ContentSection } from './fileSections';
|
import { HunkSection, type ContentSection } from './fileSections';
|
||||||
import type { File } from '$lib/vbranches/types';
|
import type { File, RemoteFile } from '$lib/vbranches/types';
|
||||||
|
|
||||||
export function computeAddedRemovedByFiles(...files: File[]) {
|
export function computeAddedRemovedByFiles(...files: (File | RemoteFile)[]) {
|
||||||
return files
|
return files
|
||||||
.flatMap((f) => f.hunks)
|
.flatMap((f) => f.hunks)
|
||||||
.map((h) => h.diff.split('\n'))
|
.map((h) => h.diff.split('\n'))
|
||||||
|
13
gitbutler-ui/src/lib/utils/string.ts
Normal file
13
gitbutler-ui/src/lib/utils/string.ts
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
export function hashCode(s: string) {
|
||||||
|
let hash = 0;
|
||||||
|
let chr;
|
||||||
|
let i;
|
||||||
|
|
||||||
|
if (s.length === 0) return hash.toString();
|
||||||
|
for (i = 0; i < s.length; i++) {
|
||||||
|
chr = s.charCodeAt(i);
|
||||||
|
hash = (hash << 5) - hash + chr;
|
||||||
|
hash |= 0; // Convert to 32bit integer
|
||||||
|
}
|
||||||
|
return hash.toString();
|
||||||
|
}
|
@ -4,11 +4,11 @@
|
|||||||
* This module provides support for tranforming a list of files into a
|
* This module provides support for tranforming a list of files into a
|
||||||
* hirerarchical structure for easy rendering.
|
* hirerarchical structure for easy rendering.
|
||||||
*/
|
*/
|
||||||
import type { File } from './types';
|
import type { File, RemoteFile } from './types';
|
||||||
|
|
||||||
export interface TreeNode {
|
export interface TreeNode {
|
||||||
name: string;
|
name: string;
|
||||||
file?: File;
|
file?: File | RemoteFile;
|
||||||
children: TreeNode[];
|
children: TreeNode[];
|
||||||
parent?: TreeNode;
|
parent?: TreeNode;
|
||||||
}
|
}
|
||||||
@ -40,7 +40,7 @@ export function sortChildren(node: TreeNode) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function filesToFileTree(files: File[]): TreeNode {
|
export function filesToFileTree(files: (File | RemoteFile)[]): TreeNode {
|
||||||
const acc: TreeNode = { name: 'root', children: [] };
|
const acc: TreeNode = { name: 'root', children: [] };
|
||||||
files.forEach((f) => {
|
files.forEach((f) => {
|
||||||
const pathParts = f.path.split('/');
|
const pathParts = f.path.split('/');
|
||||||
@ -51,8 +51,8 @@ export function filesToFileTree(files: File[]): TreeNode {
|
|||||||
return acc;
|
return acc;
|
||||||
}
|
}
|
||||||
|
|
||||||
function fileTreeToList(node: TreeNode): File[] {
|
function fileTreeToList(node: TreeNode): (File | RemoteFile)[] {
|
||||||
const list: File[] = [];
|
const list: (File | RemoteFile)[] = [];
|
||||||
if (node.file) list.push(node.file);
|
if (node.file) list.push(node.file);
|
||||||
node.children.forEach((child) => {
|
node.children.forEach((child) => {
|
||||||
list.push(...fileTreeToList(child));
|
list.push(...fileTreeToList(child));
|
||||||
@ -61,6 +61,6 @@ function fileTreeToList(node: TreeNode): File[] {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Sorts a file list the same way it is sorted in a file tree
|
// Sorts a file list the same way it is sorted in a file tree
|
||||||
export function sortLikeFileTree(files: File[]): File[] {
|
export function sortLikeFileTree(files: (File | RemoteFile)[]): (File | RemoteFile)[] {
|
||||||
return fileTreeToList(filesToFileTree(files));
|
return fileTreeToList(filesToFileTree(files));
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import type { Branch, File } from './types';
|
import type { Branch, File, RemoteFile } from './types';
|
||||||
|
|
||||||
export function filesToOwnership(files: File[]) {
|
export function filesToOwnership(files: (File | RemoteFile)[]) {
|
||||||
return files.map((f) => `${f.path}:${f.hunks.map(({ id }) => id).join(',')}`).join('\n');
|
return files.map((f) => `${f.path}:${f.hunks.map(({ id }) => id).join(',')}`).join('\n');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import 'reflect-metadata';
|
import 'reflect-metadata';
|
||||||
import { Type, Transform } from 'class-transformer';
|
import { Type, Transform } from 'class-transformer';
|
||||||
|
import { hashCode } from '$lib/utils/string';
|
||||||
|
|
||||||
export type ChangeType =
|
export type ChangeType =
|
||||||
/// Entry does not exist in old version
|
/// Entry does not exist in old version
|
||||||
@ -130,6 +131,14 @@ export class RemoteCommit {
|
|||||||
|
|
||||||
export class RemoteHunk {
|
export class RemoteHunk {
|
||||||
diff!: string;
|
diff!: string;
|
||||||
|
|
||||||
|
get id(): string {
|
||||||
|
return hashCode(this.diff);
|
||||||
|
}
|
||||||
|
|
||||||
|
get locked() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class RemoteFile {
|
export class RemoteFile {
|
||||||
@ -137,6 +146,34 @@ export class RemoteFile {
|
|||||||
@Type(() => RemoteHunk)
|
@Type(() => RemoteHunk)
|
||||||
hunks!: RemoteHunk[];
|
hunks!: RemoteHunk[];
|
||||||
binary!: boolean;
|
binary!: boolean;
|
||||||
|
|
||||||
|
get id(): string {
|
||||||
|
return this.path;
|
||||||
|
}
|
||||||
|
|
||||||
|
get filename(): string {
|
||||||
|
return this.path.replace(/^.*[\\/]/, '');
|
||||||
|
}
|
||||||
|
|
||||||
|
get justpath() {
|
||||||
|
return this.path.split('/').slice(0, -1).join('/');
|
||||||
|
}
|
||||||
|
|
||||||
|
get locked() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
get large() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
get conflicted() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
get hunkIds() {
|
||||||
|
return this.hunks.map((h) => h.id);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Author {
|
export interface Author {
|
||||||
|
Loading…
Reference in New Issue
Block a user