mirror of
https://github.com/gitbutlerapp/gitbutler.git
synced 2024-12-28 20:15:20 +03:00
Merge branch 'master' into keyboard-shortcuts
This commit is contained in:
parent
c3273c10fe
commit
b8c60adc64
@ -19,7 +19,7 @@
|
||||
createRemoteCommitsContextStore
|
||||
} from '$lib/vbranches/contexts';
|
||||
import { FileIdSelection } from '$lib/vbranches/fileIdSelection';
|
||||
import { Ownership } from '$lib/vbranches/ownership';
|
||||
import { SelectedOwnership } from '$lib/vbranches/ownership';
|
||||
import { RemoteFile, VirtualBranch } from '$lib/vbranches/types';
|
||||
import lscache from 'lscache';
|
||||
import { setContext } from 'svelte';
|
||||
@ -55,12 +55,15 @@
|
||||
|
||||
// BRANCH
|
||||
const branchStore = createContextStore(VirtualBranch, branch);
|
||||
const ownershipStore = createContextStore(Ownership, Ownership.fromBranch(branch));
|
||||
const selectedOwnershipStore = createContextStore(
|
||||
SelectedOwnership,
|
||||
SelectedOwnership.fromBranch(branch)
|
||||
);
|
||||
const branchFiles = writable(branch.files);
|
||||
|
||||
$effect(() => {
|
||||
branchStore.set(branch);
|
||||
ownershipStore.set(Ownership.fromBranch(branch));
|
||||
selectedOwnershipStore.update((o) => o?.update(branch));
|
||||
branchFiles.set(branch.files);
|
||||
});
|
||||
|
||||
|
@ -4,7 +4,7 @@
|
||||
import { getContext, getContextStore } from '$lib/utils/context';
|
||||
import { intersectionObserver } from '$lib/utils/intersectionObserver';
|
||||
import { BranchController } from '$lib/vbranches/branchController';
|
||||
import { Ownership } from '$lib/vbranches/ownership';
|
||||
import { SelectedOwnership } from '$lib/vbranches/ownership';
|
||||
import { VirtualBranch } from '$lib/vbranches/types';
|
||||
import Button from '@gitbutler/ui/Button.svelte';
|
||||
import { slideFade } from '@gitbutler/ui/utils/transitions';
|
||||
@ -15,7 +15,7 @@
|
||||
export let hasSectionsAfter: boolean;
|
||||
|
||||
const branchController = getContext(BranchController);
|
||||
const selectedOwnership = getContextStore(Ownership);
|
||||
const selectedOwnership = getContextStore(SelectedOwnership);
|
||||
const branch = getContextStore(VirtualBranch);
|
||||
|
||||
const runCommitHooks = projectRunCommitHooks(projectId);
|
||||
@ -93,7 +93,8 @@
|
||||
outline={!$expanded}
|
||||
grow
|
||||
loading={isCommitting}
|
||||
disabled={(isCommitting || !commitMessageValid || $selectedOwnership.isEmpty()) && $expanded}
|
||||
disabled={(isCommitting || !commitMessageValid || $selectedOwnership.nothingSelected()) &&
|
||||
$expanded}
|
||||
id="commit-to-branch"
|
||||
onclick={() => {
|
||||
if ($expanded) {
|
||||
|
@ -19,7 +19,7 @@
|
||||
import { KeyName } from '$lib/utils/hotkeys';
|
||||
import { resizeObserver } from '$lib/utils/resizeObserver';
|
||||
import { isWhiteSpaceString } from '$lib/utils/string';
|
||||
import { Ownership } from '$lib/vbranches/ownership';
|
||||
import { SelectedOwnership } from '$lib/vbranches/ownership';
|
||||
import { VirtualBranch, LocalFile } from '$lib/vbranches/types';
|
||||
import Checkbox from '@gitbutler/ui/Checkbox.svelte';
|
||||
import Icon from '@gitbutler/ui/Icon.svelte';
|
||||
@ -35,7 +35,7 @@
|
||||
export let cancel: () => void;
|
||||
|
||||
const user = getContextStore(User);
|
||||
const selectedOwnership = getContextStore(Ownership);
|
||||
const selectedOwnership = getContextStore(SelectedOwnership);
|
||||
const aiService = getContext(AIService);
|
||||
const branch = getContextStore(VirtualBranch);
|
||||
const project = getContext(Project);
|
||||
@ -73,7 +73,7 @@
|
||||
|
||||
async function generateCommitMessage(files: LocalFile[]) {
|
||||
const hunks = files.flatMap((f) =>
|
||||
f.hunks.filter((h) => $selectedOwnership.contains(f.id, h.id))
|
||||
f.hunks.filter((h) => $selectedOwnership.isSelected(f.id, h.id))
|
||||
);
|
||||
// Branches get their names generated only if there are at least 4 lines of code
|
||||
// If the change is a 'one-liner', the branch name is either left as "virtual branch"
|
||||
|
@ -1,6 +1,6 @@
|
||||
<script lang="ts">
|
||||
import { maybeGetContextStore } from '$lib/utils/context';
|
||||
import { Ownership } from '$lib/vbranches/ownership';
|
||||
import { SelectedOwnership } from '$lib/vbranches/ownership';
|
||||
import Badge from '@gitbutler/ui/Badge.svelte';
|
||||
import Checkbox from '@gitbutler/ui/Checkbox.svelte';
|
||||
import type { AnyFile } from '$lib/vbranches/types';
|
||||
@ -10,27 +10,30 @@
|
||||
export let files: AnyFile[];
|
||||
export let showCheckboxes = false;
|
||||
|
||||
const selectedOwnership: Writable<Ownership> | undefined = maybeGetContextStore(Ownership);
|
||||
const selectedOwnership: Writable<SelectedOwnership> | undefined =
|
||||
maybeGetContextStore(SelectedOwnership);
|
||||
|
||||
function selectAll(files: AnyFile[]) {
|
||||
if (!selectedOwnership) return;
|
||||
files.forEach((f) => selectedOwnership.update((ownership) => ownership.add(f.id, ...f.hunks)));
|
||||
files.forEach((f) =>
|
||||
selectedOwnership.update((ownership) => ownership.select(f.id, ...f.hunks))
|
||||
);
|
||||
}
|
||||
|
||||
function isAllChecked(selectedOwnership: Ownership | undefined): boolean {
|
||||
function isAllChecked(selectedOwnership: SelectedOwnership | undefined): boolean {
|
||||
if (!selectedOwnership) return false;
|
||||
return files.every((f) => f.hunks.every((h) => selectedOwnership.contains(f.id, h.id)));
|
||||
return files.every((f) => f.hunks.every((h) => selectedOwnership.isSelected(f.id, h.id)));
|
||||
}
|
||||
|
||||
function isIndeterminate(selectedOwnership: Ownership | undefined): boolean {
|
||||
function isIndeterminate(selectedOwnership: SelectedOwnership | undefined): boolean {
|
||||
if (!selectedOwnership) return false;
|
||||
if (files.length <= 1) return false;
|
||||
|
||||
let file = files[0] as AnyFile;
|
||||
let prev = selectedOwnership.contains(file.id, ...file.hunkIds);
|
||||
let prev = selectedOwnership.isSelected(file.id, ...file.hunkIds);
|
||||
for (let i = 1; i < files.length; i++) {
|
||||
file = files[i] as AnyFile;
|
||||
const contained = selectedOwnership.contains(file.id, ...file.hunkIds);
|
||||
const contained = selectedOwnership.isSelected(file.id, ...file.hunkIds);
|
||||
if (contained !== prev) {
|
||||
return true;
|
||||
}
|
||||
@ -49,12 +52,13 @@
|
||||
small
|
||||
{checked}
|
||||
{indeterminate}
|
||||
style={indeterminate ? 'neutral' : 'default'}
|
||||
onchange={(e: Event & { currentTarget: EventTarget & HTMLInputElement; }) => {
|
||||
const isChecked = e.currentTarget.checked;
|
||||
if (isChecked) {
|
||||
selectAll(files);
|
||||
} else {
|
||||
selectedOwnership?.update((ownership) => ownership.clear());
|
||||
selectedOwnership?.update((ownership) => ownership.clearSelection());
|
||||
}
|
||||
}}
|
||||
/>
|
||||
|
@ -3,6 +3,7 @@
|
||||
import FileListItem from './FileListItem.svelte';
|
||||
import LazyloadContainer from '$lib/shared/LazyloadContainer.svelte';
|
||||
import TextBox from '$lib/shared/TextBox.svelte';
|
||||
import { chunk } from '$lib/utils/array';
|
||||
import { copyToClipboard } from '$lib/utils/clipboard';
|
||||
import { getContext } from '$lib/utils/context';
|
||||
import { KeyName } from '$lib/utils/hotkeys';
|
||||
@ -15,50 +16,44 @@
|
||||
import type { AnyFile } from '$lib/vbranches/types';
|
||||
import type { Writable } from 'svelte/store';
|
||||
|
||||
export let files: AnyFile[];
|
||||
export let isUnapplied = false;
|
||||
export let showCheckboxes = false;
|
||||
export let allowMultiple = false;
|
||||
export let readonly = false;
|
||||
export let commitDialogExpanded: Writable<boolean> | undefined = undefined;
|
||||
const MERGE_DIFF_COMMAND = 'git diff-tree --cc ';
|
||||
|
||||
interface Props {
|
||||
files: AnyFile[];
|
||||
isUnapplied?: boolean;
|
||||
showCheckboxes?: boolean;
|
||||
allowMultiple?: boolean;
|
||||
readonly?: boolean;
|
||||
commitDialogExpanded: Writable<boolean> | undefined;
|
||||
}
|
||||
|
||||
const {
|
||||
files,
|
||||
isUnapplied = false,
|
||||
showCheckboxes = false,
|
||||
allowMultiple = false,
|
||||
readonly = false,
|
||||
commitDialogExpanded = undefined
|
||||
}: Props = $props();
|
||||
|
||||
const fileIdSelection = getContext(FileIdSelection);
|
||||
const commit = getCommitStore();
|
||||
|
||||
function chunk<T>(arr: T[], size: number) {
|
||||
return Array.from({ length: Math.ceil(arr.length / size) }, (_v, i) =>
|
||||
arr.slice(i * size, i * size + size)
|
||||
);
|
||||
}
|
||||
let chunkedFiles: AnyFile[][] = $derived(chunk(sortLikeFileTree(files), 100));
|
||||
let currentDisplayIndex = $state(0);
|
||||
let displayedFiles: AnyFile[] = $derived(chunkedFiles.slice(0, currentDisplayIndex + 1).flat());
|
||||
|
||||
let chunkedFiles: AnyFile[][] = [];
|
||||
let displayedFiles: AnyFile[] = [];
|
||||
let currentDisplayIndex = 0;
|
||||
|
||||
function setFiles(files: AnyFile[]) {
|
||||
chunkedFiles = chunk(sortLikeFileTree(files), 100);
|
||||
displayedFiles = chunkedFiles[0] || [];
|
||||
currentDisplayIndex = 0;
|
||||
}
|
||||
|
||||
// Make sure we display when the file list is reset
|
||||
$: setFiles(files);
|
||||
|
||||
function startCommit() {
|
||||
function startCommit() {
|
||||
if (commitDialogExpanded === undefined) return;
|
||||
if (!$commitDialogExpanded) {
|
||||
$commitDialogExpanded = true;
|
||||
}
|
||||
}
|
||||
|
||||
export function loadMore() {
|
||||
function loadMore() {
|
||||
if (currentDisplayIndex + 1 >= chunkedFiles.length) return;
|
||||
|
||||
currentDisplayIndex += 1;
|
||||
const currentChunkedFiles = chunkedFiles[currentDisplayIndex] ?? [];
|
||||
displayedFiles = [...displayedFiles, ...currentChunkedFiles];
|
||||
}
|
||||
let mergeDiffCommand = 'git diff-tree --cc ';
|
||||
</script>
|
||||
|
||||
{#if !$commit?.isMergeCommit()}
|
||||
@ -70,12 +65,12 @@
|
||||
GitHub, or run the following command in your project directory:
|
||||
</p>
|
||||
<div class="command">
|
||||
<TextBox value={mergeDiffCommand + $commit.id.slice(0, 7)} wide readonly />
|
||||
<TextBox value={MERGE_DIFF_COMMAND + $commit.id.slice(0, 7)} wide readonly />
|
||||
<Button
|
||||
icon="copy"
|
||||
style="ghost"
|
||||
outline
|
||||
onmousedown={() => copyToClipboard(mergeDiffCommand + $commit.id.slice(0, 7))}
|
||||
onmousedown={() => copyToClipboard(MERGE_DIFF_COMMAND + $commit.id.slice(0, 7))}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -2,12 +2,13 @@
|
||||
import FileContextMenu from './FileContextMenu.svelte';
|
||||
import { draggableChips } from '$lib/dragging/draggable';
|
||||
import { DraggableFile } from '$lib/dragging/draggables';
|
||||
import { itemsSatisfy } from '$lib/utils/array';
|
||||
import { getContext, maybeGetContextStore } from '$lib/utils/context';
|
||||
import { computeFileStatus } from '$lib/utils/fileStatus';
|
||||
import { getLocalCommits, getLocalAndRemoteCommits } from '$lib/vbranches/contexts';
|
||||
import { getCommitStore } from '$lib/vbranches/contexts';
|
||||
import { FileIdSelection } from '$lib/vbranches/fileIdSelection';
|
||||
import { Ownership } from '$lib/vbranches/ownership';
|
||||
import { SelectedOwnership } from '$lib/vbranches/ownership';
|
||||
import { getLockText } from '$lib/vbranches/tooltip';
|
||||
import { VirtualBranch, type AnyFile, LocalFile } from '$lib/vbranches/types';
|
||||
import FileListItem from '@gitbutler/ui/file/FileListItem.svelte';
|
||||
@ -28,7 +29,8 @@
|
||||
$props();
|
||||
|
||||
const branch = maybeGetContextStore(VirtualBranch);
|
||||
const selectedOwnership: Writable<Ownership> | undefined = maybeGetContextStore(Ownership);
|
||||
const selectedOwnership: Writable<SelectedOwnership> | undefined =
|
||||
maybeGetContextStore(SelectedOwnership);
|
||||
const fileIdSelection = getContext(FileIdSelection);
|
||||
const commit = getCommitStore();
|
||||
|
||||
@ -45,27 +47,20 @@
|
||||
const selectedFiles = fileIdSelection.files;
|
||||
|
||||
let contextMenu: FileContextMenu;
|
||||
let lastCheckboxDetail = true;
|
||||
|
||||
let draggableEl: HTMLDivElement | undefined = $state();
|
||||
let checked = $state(false);
|
||||
let indeterminate = $state(false);
|
||||
|
||||
const draggable = !readonly && !isUnapplied;
|
||||
|
||||
$effect(() => {
|
||||
if (!lastCheckboxDetail) {
|
||||
selectedOwnership?.update((ownership) => {
|
||||
file.hunks.forEach((h) => ownership.remove(file.id, h.id));
|
||||
return ownership;
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
$effect(() => {
|
||||
if (file && $selectedOwnership) {
|
||||
checked =
|
||||
file.hunks.every((hunk) => $selectedOwnership?.contains(file.id, hunk.id)) &&
|
||||
lastCheckboxDetail;
|
||||
const hunksContained = itemsSatisfy(file.hunks, (h) =>
|
||||
$selectedOwnership?.isSelected(file.id, h.id)
|
||||
);
|
||||
checked = hunksContained === 'all';
|
||||
indeterminate = hunksContained === 'some';
|
||||
}
|
||||
});
|
||||
|
||||
@ -101,6 +96,7 @@
|
||||
{selected}
|
||||
{showCheckbox}
|
||||
{checked}
|
||||
{indeterminate}
|
||||
{draggable}
|
||||
{onclick}
|
||||
{onkeydown}
|
||||
@ -108,12 +104,11 @@
|
||||
{lockText}
|
||||
oncheck={(e) => {
|
||||
const isChecked = e.currentTarget.checked;
|
||||
lastCheckboxDetail = isChecked;
|
||||
selectedOwnership?.update((ownership) => {
|
||||
if (isChecked) {
|
||||
file.hunks.forEach((h) => ownership.add(file.id, h));
|
||||
file.hunks.forEach((h) => ownership.select(file.id, h));
|
||||
} else {
|
||||
file.hunks.forEach((h) => ownership.remove(file.id, h.id));
|
||||
file.hunks.forEach((h) => ownership.ignore(file.id, h.id));
|
||||
}
|
||||
return ownership;
|
||||
});
|
||||
@ -123,14 +118,14 @@
|
||||
if (isChecked) {
|
||||
files.forEach((f) => {
|
||||
selectedOwnership?.update((ownership) => {
|
||||
f.hunks.forEach((h) => ownership.add(f.id, h));
|
||||
f.hunks.forEach((h) => ownership.select(f.id, h));
|
||||
return ownership;
|
||||
});
|
||||
});
|
||||
} else {
|
||||
files.forEach((f) => {
|
||||
selectedOwnership?.update((ownership) => {
|
||||
f.hunks.forEach((h) => ownership.remove(f.id, h.id));
|
||||
f.hunks.forEach((h) => ownership.ignore(f.id, h.id));
|
||||
return ownership;
|
||||
});
|
||||
});
|
||||
|
@ -4,9 +4,15 @@
|
||||
import ScrollableContainer from '$lib/scroll/ScrollableContainer.svelte';
|
||||
import { create } from '$lib/utils/codeHighlight';
|
||||
import { maybeGetContextStore } from '$lib/utils/context';
|
||||
import { type ContentSection, SectionType, type Line } from '$lib/utils/fileSections';
|
||||
import { Ownership } from '$lib/vbranches/ownership';
|
||||
import {
|
||||
type ContentSection,
|
||||
SectionType,
|
||||
type Line,
|
||||
CountColumnSide
|
||||
} from '$lib/utils/fileSections';
|
||||
import { SelectedOwnership } from '$lib/vbranches/ownership';
|
||||
import { type Hunk } from '$lib/vbranches/types';
|
||||
import Checkbox from '@gitbutler/ui/Checkbox.svelte';
|
||||
import Icon from '@gitbutler/ui/Icon.svelte';
|
||||
import diff_match_patch from 'diff-match-patch';
|
||||
import type { Writable } from 'svelte/store';
|
||||
@ -52,9 +58,12 @@
|
||||
const WHITESPACE_REGEX = /\s/;
|
||||
const NUMBER_COLUMN_WIDTH_PX = minWidth * 20;
|
||||
|
||||
const selectedOwnership: Writable<Ownership> | undefined = maybeGetContextStore(Ownership);
|
||||
const selectedOwnership: Writable<SelectedOwnership> | undefined =
|
||||
maybeGetContextStore(SelectedOwnership);
|
||||
|
||||
const selected = $derived($selectedOwnership?.contains(hunk.filePath, hunk.id) ?? false);
|
||||
let tableWidth = $state<number>(0);
|
||||
|
||||
const selected = $derived($selectedOwnership?.isSelected(hunk.filePath, hunk.id) ?? false);
|
||||
let isSelected = $derived(selectable && selected);
|
||||
|
||||
const inlineUnifiedDiffs = featureInlineUnifiedDiffs();
|
||||
@ -87,7 +96,8 @@
|
||||
afterLineNumber: line.afterLineNumber,
|
||||
tokens: toTokens(line.content),
|
||||
type: section.sectionType,
|
||||
size: line.content.length
|
||||
size: line.content.length,
|
||||
isLast: false
|
||||
};
|
||||
});
|
||||
}
|
||||
@ -129,14 +139,16 @@
|
||||
afterLineNumber: oldLine.afterLineNumber,
|
||||
tokens: [] as string[],
|
||||
type: prevSection.sectionType,
|
||||
size: oldLine.content.length
|
||||
size: oldLine.content.length,
|
||||
isLast: false
|
||||
};
|
||||
const nextSectionRow = {
|
||||
beforeLineNumber: newLine.beforeLineNumber,
|
||||
afterLineNumber: newLine.afterLineNumber,
|
||||
tokens: [] as string[],
|
||||
type: nextSection.sectionType,
|
||||
size: newLine.content.length
|
||||
size: newLine.content.length,
|
||||
isLast: false
|
||||
};
|
||||
|
||||
const diff = charDiff(oldLine.content, newLine.content);
|
||||
@ -181,7 +193,8 @@
|
||||
afterLineNumber: newLine.afterLineNumber,
|
||||
tokens: [] as string[],
|
||||
type: nextSection.sectionType,
|
||||
size: newLine.content.length
|
||||
size: newLine.content.length,
|
||||
isLast: false
|
||||
};
|
||||
|
||||
const diff = charDiff(oldLine.content, newLine.content);
|
||||
@ -209,7 +222,7 @@
|
||||
}
|
||||
|
||||
function generateRows(subsections: ContentSection[]) {
|
||||
return subsections.reduce((acc, nextSection, i) => {
|
||||
const rows = subsections.reduce((acc, nextSection, i) => {
|
||||
const prevSection = subsections[i - 1];
|
||||
|
||||
// Filter out section for which we don't need to compute word diffs
|
||||
@ -254,59 +267,130 @@
|
||||
return acc;
|
||||
}
|
||||
}, [] as Row[]);
|
||||
|
||||
const last = rows.at(-1);
|
||||
if (last) {
|
||||
last.isLast = true;
|
||||
}
|
||||
|
||||
return rows;
|
||||
}
|
||||
|
||||
const renderRows = $derived(generateRows(subsections));
|
||||
|
||||
interface DiffHunkLineInfo {
|
||||
beforLineStart: number;
|
||||
beforeLineCount: number;
|
||||
afterLineStart: number;
|
||||
afterLineCount: number;
|
||||
}
|
||||
|
||||
function getHunkLineInfo(subsections: ContentSection[]): DiffHunkLineInfo {
|
||||
const firstSection = subsections[0];
|
||||
const lastSection = subsections.at(-1);
|
||||
|
||||
const beforLineStart = firstSection?.lines[0]?.beforeLineNumber ?? 0;
|
||||
const beforeLineEnd = lastSection?.lines?.at(-1)?.beforeLineNumber ?? 0;
|
||||
const beforeLineCount = beforeLineEnd - beforLineStart + 1;
|
||||
|
||||
const afterLineStart = firstSection?.lines[0]?.afterLineNumber ?? 0;
|
||||
const afterLineEnd = lastSection?.lines?.at(-1)?.afterLineNumber ?? 0;
|
||||
const afterLineCount = afterLineEnd - afterLineStart + 1;
|
||||
|
||||
return {
|
||||
beforLineStart,
|
||||
beforeLineCount,
|
||||
afterLineStart,
|
||||
afterLineCount
|
||||
};
|
||||
}
|
||||
|
||||
const hunkLineInfo = $derived(getHunkLineInfo(subsections));
|
||||
</script>
|
||||
|
||||
{#snippet countColumn(count: number | undefined, lineType: SectionType)}
|
||||
{#snippet countColumn(row: Row, side: CountColumnSide)}
|
||||
<td
|
||||
class="table__numberColumn"
|
||||
class:diff-line-deletion={lineType === SectionType.RemovedLines}
|
||||
class:diff-line-addition={lineType === SectionType.AddedLines}
|
||||
class:diff-line-deletion={row.type === SectionType.RemovedLines}
|
||||
class:diff-line-addition={row.type === SectionType.AddedLines}
|
||||
style="--number-col-width: {NUMBER_COLUMN_WIDTH_PX}px;"
|
||||
align="center"
|
||||
class:is-last={row.isLast}
|
||||
class:is-before={side === CountColumnSide.Before}
|
||||
class:selected={isSelected}
|
||||
onclick={() => {
|
||||
selectable && handleSelected(hunk, !isSelected);
|
||||
}}
|
||||
>
|
||||
{count}
|
||||
{side === CountColumnSide.Before ? row.beforeLineNumber : row.afterLineNumber}
|
||||
</td>
|
||||
{/snippet}
|
||||
|
||||
<div
|
||||
bind:clientWidth={tableWidth}
|
||||
class="table__wrapper hide-native-scrollbar"
|
||||
style="--tab-size: {tabSize}; --cursor: {draggingDisabled ? 'default' : 'grab'}"
|
||||
>
|
||||
<ScrollableContainer horz padding={{ left: NUMBER_COLUMN_WIDTH_PX * 2 + 2 }}>
|
||||
{#if !draggingDisabled}
|
||||
<div class="table__drag-handle">
|
||||
<Icon name="draggable-narrow" />
|
||||
</div>
|
||||
{/if}
|
||||
<table data-hunk-id={hunk.id} class="table__section">
|
||||
<thead>
|
||||
<tr>
|
||||
<th
|
||||
class="table__checkbox-container"
|
||||
class:selected={isSelected}
|
||||
colspan={2}
|
||||
onclick={() => {
|
||||
selectable && handleSelected(hunk, !isSelected);
|
||||
}}
|
||||
>
|
||||
<div class="table__checkbox">
|
||||
<Checkbox
|
||||
checked={isSelected}
|
||||
style="blue"
|
||||
onclick={() => {
|
||||
selectable && handleSelected(hunk, !isSelected);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
class="table__title"
|
||||
style="--number-col-width: {NUMBER_COLUMN_WIDTH_PX}px; --table-width: {tableWidth}px"
|
||||
>
|
||||
<p class="table__title-content text-12">
|
||||
{`@@ -${hunkLineInfo.beforLineStart},${hunkLineInfo.beforeLineCount} +${hunkLineInfo.afterLineStart},${hunkLineInfo.afterLineCount} @@`}
|
||||
</p>
|
||||
{#if !draggingDisabled}
|
||||
<div class="table__drag-handle">
|
||||
<Icon name="draggable-narrow" />
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
</th>
|
||||
<th class="table__title-container"> </th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{#each renderRows as line}
|
||||
{#each renderRows as row}
|
||||
<tr data-no-drag>
|
||||
{@render countColumn(line.beforeLineNumber, line.type)}
|
||||
{@render countColumn(line.afterLineNumber, line.type)}
|
||||
{@render countColumn(row, CountColumnSide.Before)}
|
||||
{@render countColumn(row, CountColumnSide.After)}
|
||||
<td
|
||||
{onclick}
|
||||
class="table__textContent"
|
||||
style="--tab-size: {tabSize};"
|
||||
class:readonly
|
||||
data-no-drag
|
||||
class:diff-line-deletion={line.type === SectionType.RemovedLines}
|
||||
class:diff-line-addition={line.type === SectionType.AddedLines}
|
||||
class:diff-line-deletion={row.type === SectionType.RemovedLines}
|
||||
class:diff-line-addition={row.type === SectionType.AddedLines}
|
||||
class:is-last={row.isLast}
|
||||
oncontextmenu={(event) => {
|
||||
const lineNumber = (line.beforeLineNumber
|
||||
? line.beforeLineNumber
|
||||
: line.afterLineNumber) as number;
|
||||
const lineNumber = (row.beforeLineNumber
|
||||
? row.beforeLineNumber
|
||||
: row.afterLineNumber) as number;
|
||||
handleLineContextMenu({ event, hunk, lineNumber, subsection: subsections[0] as ContentSection });
|
||||
}}
|
||||
>
|
||||
{@html line.tokens.join('')}
|
||||
{@html row.tokens.join('')}
|
||||
</td>
|
||||
</tr>
|
||||
{/each}
|
||||
@ -317,7 +401,6 @@
|
||||
|
||||
<style>
|
||||
.table__wrapper {
|
||||
border: 1px solid var(--clr-border-2);
|
||||
border-radius: var(--radius-s);
|
||||
background-color: var(--clr-diff-line-bg);
|
||||
overflow-x: auto;
|
||||
@ -330,16 +413,12 @@
|
||||
}
|
||||
|
||||
.table__drag-handle {
|
||||
position: absolute;
|
||||
box-sizing: border-box;
|
||||
cursor: grab;
|
||||
top: 6px;
|
||||
right: 6px;
|
||||
background-color: var(--clr-bg-1);
|
||||
border: 1px solid var(--clr-border-2);
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
padding: 4px 2px;
|
||||
border-radius: var(--radius-s);
|
||||
opacity: 0;
|
||||
transform: translateY(10%) translateX(-10%) scale(0.9);
|
||||
@ -350,10 +429,72 @@
|
||||
transform 0.2s;
|
||||
}
|
||||
|
||||
table,
|
||||
.table__section {
|
||||
border-spacing: 0;
|
||||
width: 100%;
|
||||
font-family: monospace;
|
||||
border-collapse: separate;
|
||||
border-spacing: 0;
|
||||
}
|
||||
|
||||
thead {
|
||||
width: 100%;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
th,
|
||||
td,
|
||||
tr {
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
table thead th {
|
||||
top: 0;
|
||||
left: 0;
|
||||
position: sticky;
|
||||
}
|
||||
|
||||
.table__checkbox-container {
|
||||
/* border: 1px solid var(--clr-border-2); */
|
||||
z-index: var(--z-lifted);
|
||||
box-shadow: inset 0 0 0 1px var(--clr-border-2);
|
||||
background-color: var(--clr-diff-count-bg);
|
||||
border-top-left-radius: var(--radius-s);
|
||||
box-sizing: border-box;
|
||||
|
||||
&.selected {
|
||||
background-color: var(--clr-diff-selected-count-bg);
|
||||
border-color: var(--clr-diff-selected-count-border);
|
||||
box-shadow: inset 0 0 0 1px var(--clr-diff-selected-count-border);
|
||||
}
|
||||
}
|
||||
|
||||
.table__checkbox {
|
||||
padding: 4px 6px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.table__title {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: calc(var(--number-col-width) * 2);
|
||||
width: calc(var(--table-width) - var(--number-col-width) * 2);
|
||||
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
border-top: 1px solid var(--clr-border-2);
|
||||
border-right: 1px solid var(--clr-border-2);
|
||||
border-bottom: 1px solid var(--clr-border-2);
|
||||
border-top-right-radius: var(--radius-s);
|
||||
}
|
||||
|
||||
.table__title-content {
|
||||
padding: 4px 6px;
|
||||
text-wrap: nowrap;
|
||||
color: var(--clr-text-2);
|
||||
}
|
||||
|
||||
.table__numberColumn {
|
||||
@ -390,6 +531,19 @@
|
||||
background-color: var(--clr-diff-selected-count-bg);
|
||||
box-shadow: inset -1px 0 0 0 var(--clr-diff-selected-count-border);
|
||||
color: var(--clr-diff-selected-count-text);
|
||||
border-color: var(--clr-diff-selected-count-border);
|
||||
}
|
||||
|
||||
&.is-last {
|
||||
border-bottom-width: 1px;
|
||||
}
|
||||
|
||||
&.is-before {
|
||||
border-left-width: 1px;
|
||||
}
|
||||
|
||||
&.is-before.is-last {
|
||||
border-bottom-left-radius: var(--radius-s);
|
||||
}
|
||||
}
|
||||
|
||||
@ -397,9 +551,22 @@
|
||||
width: var(--number-col-width);
|
||||
min-width: var(--number-col-width);
|
||||
left: 0px;
|
||||
|
||||
&.diff-line-addition {
|
||||
box-shadow: inset -1px 0 0 0 var(--clr-diff-addition-count-border);
|
||||
}
|
||||
|
||||
&.diff-line-deletion {
|
||||
box-shadow: inset -1px 0 0 0 var(--clr-diff-deletion-count-border);
|
||||
}
|
||||
|
||||
&.selected {
|
||||
box-shadow: inset -1px 0 0 0 var(--clr-diff-selected-count-border);
|
||||
}
|
||||
}
|
||||
|
||||
.table__textContent {
|
||||
z-index: var(--z-lifted);
|
||||
width: 100%;
|
||||
font-size: 12px;
|
||||
padding-left: 4px;
|
||||
@ -408,5 +575,12 @@
|
||||
white-space: pre;
|
||||
user-select: text;
|
||||
cursor: text;
|
||||
|
||||
border-right: 1px solid var(--clr-border-2);
|
||||
|
||||
&.is-last {
|
||||
box-shadow: inset 0 -1px 0 0 var(--clr-border-2);
|
||||
border-bottom-right-radius: var(--radius-s);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
@ -8,7 +8,7 @@
|
||||
import LargeDiffMessage from '$lib/shared/LargeDiffMessage.svelte';
|
||||
import { getContext, getContextStoreBySymbol, maybeGetContextStore } from '$lib/utils/context';
|
||||
import { type HunkSection } from '$lib/utils/fileSections';
|
||||
import { Ownership } from '$lib/vbranches/ownership';
|
||||
import { SelectedOwnership } from '$lib/vbranches/ownership';
|
||||
import { VirtualBranch, type Hunk } from '$lib/vbranches/types';
|
||||
import type { Writable } from 'svelte/store';
|
||||
|
||||
@ -36,7 +36,8 @@
|
||||
readonly = false
|
||||
}: Props = $props();
|
||||
|
||||
const selectedOwnership: Writable<Ownership> | undefined = maybeGetContextStore(Ownership);
|
||||
const selectedOwnership: Writable<SelectedOwnership> | undefined =
|
||||
maybeGetContextStore(SelectedOwnership);
|
||||
const userSettings = getContextStoreBySymbol<Settings>(SETTINGS);
|
||||
const branch = maybeGetContextStore(VirtualBranch);
|
||||
const project = getContext(Project);
|
||||
@ -49,9 +50,9 @@
|
||||
function onHunkSelected(hunk: Hunk, isSelected: boolean) {
|
||||
if (!selectedOwnership) return;
|
||||
if (isSelected) {
|
||||
selectedOwnership.update((ownership) => ownership.add(hunk.filePath, hunk));
|
||||
selectedOwnership.update((ownership) => ownership.select(hunk.filePath, hunk));
|
||||
} else {
|
||||
selectedOwnership.update((ownership) => ownership.remove(hunk.filePath, hunk.id));
|
||||
selectedOwnership.update((ownership) => ownership.ignore(hunk.filePath, hunk.id));
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
@ -6,6 +6,7 @@ export interface Row {
|
||||
tokens: string[];
|
||||
type: SectionType;
|
||||
size: number;
|
||||
isLast: boolean;
|
||||
}
|
||||
|
||||
export enum Operation {
|
||||
|
@ -12,7 +12,7 @@
|
||||
*/
|
||||
|
||||
import LazyloadContainer from '$lib/shared/LazyloadContainer.svelte';
|
||||
import { chunk } from '$lib/utils/chunk';
|
||||
import { chunk } from '$lib/utils/array';
|
||||
import { type Snippet } from 'svelte';
|
||||
|
||||
interface Props {
|
||||
|
30
apps/desktop/src/lib/utils/array.ts
Normal file
30
apps/desktop/src/lib/utils/array.ts
Normal file
@ -0,0 +1,30 @@
|
||||
type ItemsSatisfyResult = 'all' | 'some' | 'none';
|
||||
|
||||
export function itemsSatisfy<T>(arr: T[], predicate: (item: T) => boolean): ItemsSatisfyResult {
|
||||
let satisfyCount = 0;
|
||||
let offenseCount = 0;
|
||||
for (const item of arr) {
|
||||
if (predicate(item)) {
|
||||
satisfyCount++;
|
||||
continue;
|
||||
}
|
||||
|
||||
offenseCount++;
|
||||
}
|
||||
|
||||
if (satisfyCount === 0) {
|
||||
return 'none';
|
||||
}
|
||||
|
||||
if (offenseCount === 0) {
|
||||
return 'all';
|
||||
}
|
||||
|
||||
return 'some';
|
||||
}
|
||||
|
||||
export function chunk<T>(arr: T[], size: number) {
|
||||
return Array.from({ length: Math.ceil(arr.length / size) }, (_v, i) =>
|
||||
arr.slice(i * size, i * size + size)
|
||||
);
|
||||
}
|
@ -1,5 +0,0 @@
|
||||
export function chunk<T>(arr: T[], size: number) {
|
||||
return Array.from({ length: Math.ceil(arr.length / size) }, (_v, i) =>
|
||||
arr.slice(i * size, i * size + size)
|
||||
);
|
||||
}
|
@ -21,6 +21,11 @@ export enum SectionType {
|
||||
Context
|
||||
}
|
||||
|
||||
export enum CountColumnSide {
|
||||
Before,
|
||||
After
|
||||
}
|
||||
|
||||
export class HunkSection {
|
||||
hunk!: Hunk;
|
||||
header!: HunkHeader;
|
||||
|
@ -23,48 +23,149 @@ export type FilePath = string;
|
||||
export type HunkClaims = Map<HunkId, AnyHunk>;
|
||||
export type FileClaims = Map<FilePath, HunkClaims>;
|
||||
|
||||
export class Ownership {
|
||||
function branchFilesToClaims(files: AnyFile[]): FileClaims {
|
||||
const selection = new Map<FilePath, HunkClaims>();
|
||||
for (const file of files) {
|
||||
const existingFile = selection.get(file.id);
|
||||
if (existingFile) {
|
||||
file.hunks.forEach((hunk) => existingFile.set(hunk.id, hunk));
|
||||
continue;
|
||||
}
|
||||
|
||||
selection.set(
|
||||
file.id,
|
||||
file.hunks.reduce((acc, hunk) => {
|
||||
return acc.set(hunk.id, hunk);
|
||||
}, new Map<string, AnyHunk>())
|
||||
);
|
||||
}
|
||||
|
||||
return selection;
|
||||
}
|
||||
|
||||
function selectAddedClaims(
|
||||
branch: VirtualBranch,
|
||||
previousState: SelectedOwnershipState,
|
||||
selection: Map<string, HunkClaims>
|
||||
) {
|
||||
for (const file of branch.files) {
|
||||
const existingFile = previousState.claims.get(file.id);
|
||||
|
||||
if (!existingFile) {
|
||||
// Select newly added files
|
||||
selection.set(
|
||||
file.id,
|
||||
file.hunks.reduce((acc, hunk) => {
|
||||
return acc.set(hunk.id, hunk);
|
||||
}, new Map<string, AnyHunk>())
|
||||
);
|
||||
continue;
|
||||
}
|
||||
|
||||
for (const hunk of file.hunks) {
|
||||
const existingHunk = existingFile.get(hunk.id);
|
||||
if (!existingHunk) {
|
||||
// Select newly added hunks
|
||||
const existingFile = selection.get(file.id);
|
||||
if (existingFile) {
|
||||
existingFile.set(hunk.id, hunk);
|
||||
} else {
|
||||
selection.set(file.id, new Map([[hunk.id, hunk]]));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function ignoreRemovedClaims(
|
||||
previousState: SelectedOwnershipState,
|
||||
branch: VirtualBranch,
|
||||
selection: Map<string, HunkClaims>
|
||||
) {
|
||||
for (const [fileId, hunkClaims] of previousState.selection.entries()) {
|
||||
const branchFile = branch.files.find((f) => f.id === fileId);
|
||||
if (branchFile) {
|
||||
for (const hunkId of hunkClaims.keys()) {
|
||||
const branchHunk = branchFile.hunks.find((h) => h.id === hunkId);
|
||||
if (branchHunk) {
|
||||
// Re-select hunks that are still present in the branch
|
||||
const existingFile = selection.get(fileId);
|
||||
if (existingFile) {
|
||||
existingFile.set(hunkId, branchHunk);
|
||||
} else {
|
||||
selection.set(fileId, new Map([[hunkId, branchHunk]]));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
interface SelectedOwnershipState {
|
||||
claims: FileClaims;
|
||||
selection: FileClaims;
|
||||
}
|
||||
|
||||
function getState(
|
||||
branch: VirtualBranch,
|
||||
previousState?: SelectedOwnershipState
|
||||
): SelectedOwnershipState {
|
||||
const claims = branchFilesToClaims(branch.files);
|
||||
|
||||
if (previousState !== undefined) {
|
||||
const selection = new Map<FilePath, HunkClaims>();
|
||||
selectAddedClaims(branch, previousState, selection);
|
||||
ignoreRemovedClaims(previousState, branch, selection);
|
||||
|
||||
return { selection, claims };
|
||||
}
|
||||
|
||||
return { selection: claims, claims };
|
||||
}
|
||||
|
||||
export class SelectedOwnership {
|
||||
private claims: FileClaims;
|
||||
private selection: FileClaims;
|
||||
|
||||
constructor(state: SelectedOwnershipState) {
|
||||
this.claims = state.claims;
|
||||
this.selection = state.selection;
|
||||
}
|
||||
|
||||
static fromBranch(branch: VirtualBranch) {
|
||||
const files = branch.files.reduce((acc, file) => {
|
||||
const existing = acc.get(file.id);
|
||||
if (existing) {
|
||||
file.hunks.forEach((hunk) => existing.set(hunk.id, hunk));
|
||||
} else {
|
||||
acc.set(
|
||||
file.id,
|
||||
file.hunks.reduce((acc2, hunk) => {
|
||||
return acc2.set(hunk.id, hunk);
|
||||
}, new Map<string, AnyHunk>())
|
||||
);
|
||||
}
|
||||
return acc;
|
||||
}, new Map<FilePath, Map<HunkId, AnyHunk>>());
|
||||
const ownership = new Ownership(files);
|
||||
const state = getState(branch);
|
||||
const ownership = new SelectedOwnership(state);
|
||||
return ownership;
|
||||
}
|
||||
|
||||
constructor(files: FileClaims) {
|
||||
this.claims = files;
|
||||
update(branch: VirtualBranch) {
|
||||
const { selection, claims } = getState(branch, {
|
||||
claims: this.claims,
|
||||
selection: this.selection
|
||||
});
|
||||
|
||||
this.claims = claims;
|
||||
this.selection = selection;
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
remove(fileId: string, ...hunkIds: string[]) {
|
||||
const claims = this.claims;
|
||||
if (!claims) return this;
|
||||
ignore(fileId: string, ...hunkIds: string[]) {
|
||||
const selection = this.selection;
|
||||
if (!selection) return this;
|
||||
hunkIds.forEach((hunkId) => {
|
||||
claims.get(fileId)?.delete(hunkId);
|
||||
if (claims.get(fileId)?.size === 0) claims.delete(fileId);
|
||||
selection.get(fileId)?.delete(hunkId);
|
||||
if (selection.get(fileId)?.size === 0) selection.delete(fileId);
|
||||
});
|
||||
return this;
|
||||
}
|
||||
|
||||
add(fileId: string, ...items: AnyHunk[]) {
|
||||
const claim = this.claims.get(fileId);
|
||||
if (claim) {
|
||||
items.forEach((hunk) => claim.set(hunk.id, hunk));
|
||||
select(fileId: string, ...items: AnyHunk[]) {
|
||||
const selectedFile = this.selection.get(fileId);
|
||||
if (selectedFile) {
|
||||
items.forEach((hunk) => selectedFile.set(hunk.id, hunk));
|
||||
} else {
|
||||
this.claims.set(
|
||||
this.selection.set(
|
||||
fileId,
|
||||
items.reduce((acc, hunk) => {
|
||||
return acc.set(hunk.id, hunk);
|
||||
@ -74,17 +175,17 @@ export class Ownership {
|
||||
return this;
|
||||
}
|
||||
|
||||
contains(fileId: string, ...hunkIds: string[]): boolean {
|
||||
return hunkIds.every((hunkId) => !!this.claims.get(fileId)?.has(hunkId));
|
||||
isSelected(fileId: string, ...hunkIds: string[]): boolean {
|
||||
return hunkIds.every((hunkId) => !!this.selection.get(fileId)?.has(hunkId));
|
||||
}
|
||||
|
||||
clear() {
|
||||
this.claims.clear();
|
||||
clearSelection() {
|
||||
this.selection.clear();
|
||||
return this;
|
||||
}
|
||||
|
||||
toString() {
|
||||
return Array.from(this.claims.entries())
|
||||
return Array.from(this.selection.entries())
|
||||
.map(
|
||||
([fileId, hunkMap]) =>
|
||||
fileId +
|
||||
@ -98,7 +199,7 @@ export class Ownership {
|
||||
.join('\n');
|
||||
}
|
||||
|
||||
isEmpty() {
|
||||
return this.claims.size === 0;
|
||||
nothingSelected() {
|
||||
return this.selection.size === 0;
|
||||
}
|
||||
}
|
||||
|
@ -1,4 +1,5 @@
|
||||
<script lang="ts" module>
|
||||
export type CheckboxStyle = 'default' | 'blue' | 'neutral';
|
||||
export interface CheckboxProps {
|
||||
name?: string;
|
||||
small?: boolean;
|
||||
@ -6,6 +7,7 @@
|
||||
checked?: boolean;
|
||||
value?: string;
|
||||
indeterminate?: boolean;
|
||||
style?: CheckboxStyle;
|
||||
onclick?: (e: MouseEvent) => void;
|
||||
onchange?: (
|
||||
e: Event & {
|
||||
@ -25,6 +27,7 @@
|
||||
checked = $bindable(),
|
||||
value = '',
|
||||
indeterminate = false,
|
||||
style = 'default',
|
||||
onclick,
|
||||
onchange
|
||||
}: CheckboxProps = $props();
|
||||
@ -46,7 +49,7 @@
|
||||
onchange?.(e);
|
||||
}}
|
||||
type="checkbox"
|
||||
class="checkbox"
|
||||
class={`checkbox ${style}`}
|
||||
class:small
|
||||
{value}
|
||||
id={name}
|
||||
@ -90,23 +93,19 @@
|
||||
border-color: none;
|
||||
}
|
||||
|
||||
&:indeterminate {
|
||||
background-color: var(--clr-bg-2);
|
||||
/* indeterminate */
|
||||
|
||||
&::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
width: 50%;
|
||||
height: 2px;
|
||||
background-color: var(--clr-scale-ntrl-30);
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
}
|
||||
&:indeterminate::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
width: 50%;
|
||||
height: 2px;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
}
|
||||
|
||||
/* checked */
|
||||
&:checked {
|
||||
&.default:indeterminate {
|
||||
background-color: var(--clr-theme-pop-element);
|
||||
box-shadow: inset 0 0 0 1px var(--clr-theme-pop-element);
|
||||
|
||||
@ -114,6 +113,38 @@
|
||||
background-color: var(--clr-theme-pop-element-hover);
|
||||
}
|
||||
|
||||
&::before {
|
||||
background-color: white;
|
||||
}
|
||||
}
|
||||
|
||||
&.neutral:indeterminate {
|
||||
background-color: var(--clr-bg-2);
|
||||
|
||||
&:hover {
|
||||
background-color: var(--clr-bg-3);
|
||||
}
|
||||
|
||||
&::before {
|
||||
background-color: var(--clr-scale-ntrl-30);
|
||||
}
|
||||
}
|
||||
|
||||
&.blue:indeterminate {
|
||||
background-color: var(--clr-diff-selected-checkbox);
|
||||
box-shadow: inset 0 0 0 1px var(--clr-diff-selected-checkbox);
|
||||
|
||||
&:hover {
|
||||
background-color: var(--clr-diff-selected-checkbox-hover);
|
||||
}
|
||||
|
||||
&::before {
|
||||
background-color: white;
|
||||
}
|
||||
}
|
||||
|
||||
/* checked */
|
||||
&:checked {
|
||||
&:disabled {
|
||||
pointer-events: none;
|
||||
opacity: 0.4;
|
||||
@ -127,6 +158,33 @@
|
||||
}
|
||||
}
|
||||
|
||||
&.default:checked {
|
||||
background-color: var(--clr-theme-pop-element);
|
||||
box-shadow: inset 0 0 0 1px var(--clr-theme-pop-element);
|
||||
|
||||
&:hover {
|
||||
background-color: var(--clr-theme-pop-element-hover);
|
||||
}
|
||||
}
|
||||
|
||||
&.neutral:checked {
|
||||
background-color: var(--clr-bg-2);
|
||||
box-shadow: inset 0 0 0 1px var(--clr-scale-ntrl-30);
|
||||
|
||||
&:hover {
|
||||
background-color: var(--clr-bg-3);
|
||||
}
|
||||
}
|
||||
|
||||
&.blue:checked {
|
||||
background-color: var(--clr-diff-selected-checkbox);
|
||||
box-shadow: inset 0 0 0 1px var(--clr-diff-selected-checkbox);
|
||||
|
||||
&:hover {
|
||||
background-color: var(--clr-diff-selected-checkbox-hover);
|
||||
}
|
||||
}
|
||||
|
||||
&::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
|
@ -293,7 +293,7 @@
|
||||
},
|
||||
"50": {
|
||||
"$type": "color",
|
||||
"$value": "#48a8a3",
|
||||
"$value": "#3cb4ae",
|
||||
"$description": "",
|
||||
"$extensions": {
|
||||
"mode": {},
|
||||
@ -309,7 +309,7 @@
|
||||
},
|
||||
"60": {
|
||||
"$type": "color",
|
||||
"$value": "#97cecb",
|
||||
"$value": "#8fd6d2",
|
||||
"$description": "",
|
||||
"$extensions": {
|
||||
"mode": {},
|
||||
@ -325,7 +325,7 @@
|
||||
},
|
||||
"70": {
|
||||
"$type": "color",
|
||||
"$value": "#c6e7e5",
|
||||
"$value": "#c1ebe9",
|
||||
"$description": "",
|
||||
"$extensions": {
|
||||
"mode": {},
|
||||
@ -341,7 +341,7 @@
|
||||
},
|
||||
"80": {
|
||||
"$type": "color",
|
||||
"$value": "#daf1f0",
|
||||
"$value": "#d7f4f2",
|
||||
"$description": "",
|
||||
"$extensions": {
|
||||
"mode": {},
|
||||
@ -357,7 +357,7 @@
|
||||
},
|
||||
"90": {
|
||||
"$type": "color",
|
||||
"$value": "#e9f7f6",
|
||||
"$value": "#e7f8f7",
|
||||
"$description": "",
|
||||
"$extensions": {
|
||||
"mode": {},
|
||||
@ -373,7 +373,7 @@
|
||||
},
|
||||
"95": {
|
||||
"$type": "color",
|
||||
"$value": "#f4fbfa",
|
||||
"$value": "#f3fcfb",
|
||||
"$description": "",
|
||||
"$extensions": {
|
||||
"mode": {},
|
||||
@ -859,7 +859,7 @@
|
||||
},
|
||||
"70": {
|
||||
"$type": "color",
|
||||
"$value": "#bef4da",
|
||||
"$value": "#c2f0da",
|
||||
"$description": "",
|
||||
"$extensions": {
|
||||
"mode": {},
|
||||
@ -875,7 +875,7 @@
|
||||
},
|
||||
"80": {
|
||||
"$type": "color",
|
||||
"$value": "#d0f7e5",
|
||||
"$value": "#d2f4e4",
|
||||
"$description": "",
|
||||
"$extensions": {
|
||||
"mode": {},
|
||||
@ -891,7 +891,7 @@
|
||||
},
|
||||
"90": {
|
||||
"$type": "color",
|
||||
"$value": "#e5faf0",
|
||||
"$value": "#e7f9f0",
|
||||
"$description": "",
|
||||
"$extensions": {
|
||||
"mode": {},
|
||||
@ -3652,12 +3652,12 @@
|
||||
"selected": {
|
||||
"count-bg": {
|
||||
"$type": "color",
|
||||
"$value": "#378bf2",
|
||||
"$value": "#e1eeff",
|
||||
"$description": "",
|
||||
"$extensions": {
|
||||
"mode": {
|
||||
"light": "#378bf2",
|
||||
"dark": "#044289"
|
||||
"light": "#e1eeff",
|
||||
"dark": "#133161"
|
||||
},
|
||||
"figma": {
|
||||
"variableId": "VariableID:3935:251",
|
||||
@ -3671,12 +3671,12 @@
|
||||
},
|
||||
"count-border": {
|
||||
"$type": "color",
|
||||
"$value": "#265dd4",
|
||||
"$value": "#9dc3f5",
|
||||
"$description": "",
|
||||
"$extensions": {
|
||||
"mode": {
|
||||
"light": "#265dd4",
|
||||
"dark": "#005cc5"
|
||||
"light": "#9dc3f5",
|
||||
"dark": "#2464ae"
|
||||
},
|
||||
"figma": {
|
||||
"variableId": "VariableID:3935:253",
|
||||
@ -3690,12 +3690,12 @@
|
||||
},
|
||||
"count-text": {
|
||||
"$type": "color",
|
||||
"$value": "#ffffff",
|
||||
"$value": "#6187dc",
|
||||
"$description": "",
|
||||
"$extensions": {
|
||||
"mode": {
|
||||
"light": "#ffffff",
|
||||
"dark": "#d6e8ff"
|
||||
"light": "#6187dc",
|
||||
"dark": "#6aaeff"
|
||||
},
|
||||
"figma": {
|
||||
"variableId": "VariableID:3935:254",
|
||||
@ -3706,6 +3706,44 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"checkbox": {
|
||||
"$type": "color",
|
||||
"$value": "#378bf2",
|
||||
"$description": "",
|
||||
"$extensions": {
|
||||
"mode": {
|
||||
"light": "#378bf2",
|
||||
"dark": "#2581f3"
|
||||
},
|
||||
"figma": {
|
||||
"variableId": "VariableID:4469:4018",
|
||||
"collection": {
|
||||
"id": "VariableCollectionId:8:1868",
|
||||
"name": "clr",
|
||||
"defaultModeId": "8:5"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"checkbox-hover": {
|
||||
"$type": "color",
|
||||
"$value": "#196dd4",
|
||||
"$description": "",
|
||||
"$extensions": {
|
||||
"mode": {
|
||||
"light": "#196dd4",
|
||||
"dark": "#1165cc"
|
||||
},
|
||||
"figma": {
|
||||
"variableId": "VariableID:4499:8368",
|
||||
"collection": {
|
||||
"id": "VariableCollectionId:8:1868",
|
||||
"name": "clr",
|
||||
"defaultModeId": "8:5"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"count-text": {
|
||||
@ -4137,7 +4175,7 @@
|
||||
"useDTCGKeys": true,
|
||||
"colorMode": "hex",
|
||||
"variableCollections": ["clr-core", "clr", "size", "radius"],
|
||||
"createdAt": "2024-08-31T23:16:13.487Z"
|
||||
"createdAt": "2024-09-10T12:38:43.089Z"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -18,6 +18,7 @@
|
||||
clickable?: boolean;
|
||||
showCheckbox?: boolean;
|
||||
checked?: boolean;
|
||||
indeterminate?: boolean;
|
||||
conflicted?: boolean;
|
||||
locked?: boolean;
|
||||
lockText?: string;
|
||||
@ -44,6 +45,7 @@
|
||||
clickable = true,
|
||||
showCheckbox = false,
|
||||
checked = $bindable(),
|
||||
indeterminate,
|
||||
conflicted,
|
||||
locked,
|
||||
lockText,
|
||||
@ -81,7 +83,7 @@
|
||||
}}
|
||||
>
|
||||
{#if showCheckbox}
|
||||
<Checkbox small {checked} onchange={oncheck} />
|
||||
<Checkbox small {checked} {indeterminate} onchange={oncheck} />
|
||||
{/if}
|
||||
<div class="info">
|
||||
<FileIcon {fileName} size={14} />
|
||||
|
@ -21,12 +21,12 @@
|
||||
--clr-core-pop-20: color(srgb 0.10980392156862745 0.32941176470588235 0.3176470588235294);
|
||||
--clr-core-pop-30: color(srgb 0.1450980392156863 0.43529411764705883 0.4196078431372549);
|
||||
--clr-core-pop-40: color(srgb 0.16470588235294117 0.5725490196078431 0.5529411764705883);
|
||||
--clr-core-pop-50: color(srgb 0.2823529411764706 0.6588235294117647 0.6392156862745098);
|
||||
--clr-core-pop-60: color(srgb 0.592156862745098 0.807843137254902 0.796078431372549);
|
||||
--clr-core-pop-70: color(srgb 0.7764705882352941 0.9058823529411765 0.8980392156862745);
|
||||
--clr-core-pop-80: color(srgb 0.8549019607843137 0.9450980392156862 0.9411764705882353);
|
||||
--clr-core-pop-90: color(srgb 0.9137254901960784 0.9686274509803922 0.9647058823529412);
|
||||
--clr-core-pop-95: color(srgb 0.9568627450980393 0.984313725490196 0.9803921568627451);
|
||||
--clr-core-pop-50: color(srgb 0.23529411764705882 0.7058823529411765 0.6823529411764706);
|
||||
--clr-core-pop-60: color(srgb 0.5607843137254902 0.8392156862745098 0.8235294117647058);
|
||||
--clr-core-pop-70: color(srgb 0.7568627450980392 0.9215686274509803 0.9137254901960784);
|
||||
--clr-core-pop-80: color(srgb 0.8431372549019608 0.9568627450980393 0.9490196078431372);
|
||||
--clr-core-pop-90: color(srgb 0.9058823529411765 0.9725490196078431 0.9686274509803922);
|
||||
--clr-core-pop-95: color(srgb 0.9529411764705882 0.9882352941176471 0.984313725490196);
|
||||
--clr-core-err-5: color(srgb 0.14901960784313725 0.050980392156862744 0.058823529411764705);
|
||||
--clr-core-err-10: color(srgb 0.2980392156862745 0.10196078431372549 0.12156862745098039);
|
||||
--clr-core-err-20: color(srgb 0.4196078431372549 0.1411764705882353 0.16862745098039217);
|
||||
@ -56,9 +56,9 @@
|
||||
--clr-core-succ-40: color(srgb 0.23529411764705882 0.6039215686274509 0.43529411764705883);
|
||||
--clr-core-succ-50: color(srgb 0.2901960784313726 0.7098039215686275 0.5098039215686274);
|
||||
--clr-core-succ-60: color(srgb 0.5725490196078431 0.8666666666666667 0.7294117647058823);
|
||||
--clr-core-succ-70: color(srgb 0.7450980392156863 0.9568627450980393 0.8549019607843137);
|
||||
--clr-core-succ-80: color(srgb 0.8156862745098039 0.9686274509803922 0.8980392156862745);
|
||||
--clr-core-succ-90: color(srgb 0.8980392156862745 0.9803921568627451 0.9411764705882353);
|
||||
--clr-core-succ-70: color(srgb 0.7607843137254902 0.9411764705882353 0.8549019607843137);
|
||||
--clr-core-succ-80: color(srgb 0.8235294117647058 0.9568627450980393 0.8941176470588236);
|
||||
--clr-core-succ-90: color(srgb 0.9058823529411765 0.9764705882352941 0.9411764705882353);
|
||||
--clr-core-succ-95: color(srgb 0.9647058823529412 0.9882352941176471 0.984313725490196);
|
||||
--clr-core-purp-5: color(srgb 0.1568627450980392 0.11372549019607843 0.26666666666666666);
|
||||
--clr-core-purp-10: color(srgb 0.24705882352941178 0.17254901960784313 0.40784313725490196);
|
||||
@ -203,13 +203,19 @@
|
||||
--clr-diff-line-bg: var(--clr-bg-1);
|
||||
--clr-diff-count-bg: color(srgb 0.9686274509803922 0.9686274509803922 0.9647058823529412);
|
||||
--clr-diff-count-border: var(--clr-border-2);
|
||||
--clr-diff-selected-count-bg: color(
|
||||
--clr-diff-selected-count-bg: color(srgb 0.8823529411764706 0.9333333333333333 1);
|
||||
--clr-diff-selected-count-border: color(
|
||||
srgb 0.615686274509804 0.7647058823529411 0.9607843137254902
|
||||
);
|
||||
--clr-diff-selected-count-text: color(
|
||||
srgb 0.3803921568627451 0.5294117647058824 0.8627450980392157
|
||||
);
|
||||
--clr-diff-selected-checkbox: color(
|
||||
srgb 0.21568627450980393 0.5450980392156862 0.9490196078431372
|
||||
);
|
||||
--clr-diff-selected-count-border: color(
|
||||
srgb 0.14901960784313725 0.36470588235294116 0.8313725490196079
|
||||
--clr-diff-selected-checkbox-hover: color(
|
||||
srgb 0.09803921568627451 0.42745098039215684 0.8313725490196079
|
||||
);
|
||||
--clr-diff-selected-count-text: color(srgb 1 1 1);
|
||||
--clr-diff-count-text: var(--clr-text-3);
|
||||
--clr-diff-deletion-line-bg: color(srgb 1 0.9411764705882353 0.9490196078431372);
|
||||
--clr-diff-deletion-line-highlight: color(
|
||||
@ -386,10 +392,16 @@
|
||||
--clr-diff-count-bg: color(srgb 0.18823529411764706 0.17254901960784313 0.16862745098039217);
|
||||
--clr-diff-count-border: var(--clr-border-2);
|
||||
--clr-diff-selected-count-bg: color(
|
||||
srgb 0.01568627450980392 0.25882352941176473 0.5372549019607843
|
||||
srgb 0.07450980392156863 0.19215686274509805 0.3803921568627451
|
||||
);
|
||||
--clr-diff-selected-count-border: color(srgb 0 0.3607843137254902 0.7725490196078432);
|
||||
--clr-diff-selected-count-text: color(srgb 0.8392156862745098 0.9098039215686274 1);
|
||||
--clr-diff-selected-count-border: color(
|
||||
srgb 0.1411764705882353 0.39215686274509803 0.6823529411764706
|
||||
);
|
||||
--clr-diff-selected-count-text: color(srgb 0.41568627450980394 0.6823529411764706 1);
|
||||
--clr-diff-selected-checkbox: color(
|
||||
srgb 0.1450980392156863 0.5058823529411764 0.9529411764705882
|
||||
);
|
||||
--clr-diff-selected-checkbox-hover: color(srgb 0.06666666666666667 0.396078431372549 0.8);
|
||||
--clr-diff-count-text: var(--clr-text-3);
|
||||
--clr-diff-deletion-line-bg: color(
|
||||
srgb 0.23529411764705882 0.07450980392156863 0.10588235294117647
|
||||
|
Loading…
Reference in New Issue
Block a user