mirror of
https://github.com/gitbutlerapp/gitbutler.git
synced 2024-12-23 01:22:12 +03:00
Replace svelte-dnd-action with native drag & drop
This commit is a bit of a relief, we now have less than 1/10th the amount of code powering drag & drop. Future work includes: - extracting commonalities into a Svelte action - applying css to new drop zone without using sibling selector - styling
This commit is contained in:
parent
983d58061a
commit
2b19031a36
@ -82,7 +82,6 @@
|
||||
"reflect-metadata": "^0.1.13",
|
||||
"svelte": "~3.55.1",
|
||||
"svelte-check": "^3.0.1",
|
||||
"svelte-dnd-action": "github:gitbutlerapp/svelte-dnd-action#9912712394c26b971ef067674bdc871550940636",
|
||||
"svelte-floating-ui": "^1.5.2",
|
||||
"svelte-french-toast": "^1.0.3",
|
||||
"svelte-loadable-store": "^1.2.3",
|
||||
|
@ -193,9 +193,6 @@ devDependencies:
|
||||
svelte-check:
|
||||
specifier: ^3.0.1
|
||||
version: 3.0.3(postcss-load-config@4.0.1)(postcss@8.4.21)(svelte@3.55.1)
|
||||
svelte-dnd-action:
|
||||
specifier: github:gitbutlerapp/svelte-dnd-action#9912712394c26b971ef067674bdc871550940636
|
||||
version: github.com/gitbutlerapp/svelte-dnd-action/9912712394c26b971ef067674bdc871550940636(svelte@3.55.1)
|
||||
svelte-floating-ui:
|
||||
specifier: ^1.5.2
|
||||
version: 1.5.2
|
||||
@ -5462,17 +5459,6 @@ packages:
|
||||
engines: {node: '>=12.20'}
|
||||
dev: true
|
||||
|
||||
github.com/gitbutlerapp/svelte-dnd-action/9912712394c26b971ef067674bdc871550940636(svelte@3.55.1):
|
||||
resolution: {tarball: https://codeload.github.com/gitbutlerapp/svelte-dnd-action/tar.gz/9912712394c26b971ef067674bdc871550940636}
|
||||
id: github.com/gitbutlerapp/svelte-dnd-action/9912712394c26b971ef067674bdc871550940636
|
||||
name: svelte-dnd-action
|
||||
version: 0.9.22
|
||||
peerDependencies:
|
||||
svelte: '>=3.23.0'
|
||||
dependencies:
|
||||
svelte: 3.55.1
|
||||
dev: true
|
||||
|
||||
github.com/tauri-apps/tauri-plugin-log/21921031d74f871180381317a338559f588ad8e9:
|
||||
resolution: {tarball: https://codeload.github.com/tauri-apps/tauri-plugin-log/tar.gz/21921031d74f871180381317a338559f588ad8e9}
|
||||
name: tauri-plugin-log-api
|
||||
|
@ -938,6 +938,7 @@ fn create_window(handle: &tauri::AppHandle) -> tauri::Result<tauri::Window> {
|
||||
tauri::WindowBuilder::new(handle, "main", tauri::WindowUrl::App("index.html".into()))
|
||||
.resizable(true)
|
||||
.title(app_title)
|
||||
.disable_file_drop_handler()
|
||||
.min_inner_size(600.0, 300.0)
|
||||
.inner_size(800.0, 600.0)
|
||||
.build()
|
||||
@ -952,6 +953,7 @@ fn create_window(handle: &tauri::AppHandle) -> tauri::Result<tauri::Window> {
|
||||
.min_inner_size(1024.0, 600.0)
|
||||
.inner_size(1024.0, 600.0)
|
||||
.hidden_title(true)
|
||||
.disable_file_drop_handler()
|
||||
.title_bar_style(tauri::TitleBarStyle::Overlay)
|
||||
.build()
|
||||
}
|
||||
|
@ -316,3 +316,36 @@ input[type='checkbox'].large {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
}
|
||||
|
||||
/* drag & drop */
|
||||
|
||||
.drag-zone-hover * {
|
||||
@apply pointer-events-none;
|
||||
}
|
||||
.drag-zone-marker {
|
||||
@apply border-green-450 bg-green-200 dark:bg-green-470;
|
||||
}
|
||||
.drag-zone-active.drag-zone-hover .drag-zone-marker,
|
||||
.drag-zone-active + #new-branch-dz.drag-zone-hover .drag-zone-marker {
|
||||
@apply bg-green-300 dark:bg-green-460;
|
||||
}
|
||||
.drag-zone-hover {
|
||||
@apply border-green-500;
|
||||
}
|
||||
.drag-zone-active .no-changes {
|
||||
@apply hidden;
|
||||
}
|
||||
.drag-zone-active .drag-zone-marker {
|
||||
@apply block;
|
||||
}
|
||||
|
||||
/* drag & drop ugly stuff */
|
||||
#new-branch-dz.new-branch-active {
|
||||
@apply visible flex;
|
||||
}
|
||||
.drag-zone-active + #new-branch-dz .call-to-action {
|
||||
@apply hidden;
|
||||
}
|
||||
.drag-zone-active + #new-branch-dz .drag-zone-marker {
|
||||
@apply block;
|
||||
}
|
||||
|
@ -1,7 +1,5 @@
|
||||
<script lang="ts" async="true">
|
||||
import { dndzone } from 'svelte-dnd-action';
|
||||
import Lane from './BranchLane.svelte';
|
||||
import type { DndEvent } from 'svelte-dnd-action/typings';
|
||||
import NewBranchDropZone from './NewBranchDropZone.svelte';
|
||||
import type { Branch } from '$lib/api/ipc/vbranches';
|
||||
import type { VirtualBranchOperations } from './vbranches';
|
||||
@ -10,25 +8,12 @@
|
||||
export let projectPath: string;
|
||||
export let branches: Branch[];
|
||||
export let virtualBranches: VirtualBranchOperations;
|
||||
let dragged: any;
|
||||
let dropZone: HTMLDivElement;
|
||||
let priorPosition = 0;
|
||||
let dropPosition = 0;
|
||||
|
||||
const newBranchClass = 'new-branch-active';
|
||||
|
||||
function ensureBranchOrder() {
|
||||
branches.forEach((branch, i) => {
|
||||
if (branch.order !== i) {
|
||||
virtualBranches.updateBranchOrder(branch.id, i);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function handleDndEvent(e: CustomEvent<DndEvent<Branch>>) {
|
||||
branches = e.detail.items;
|
||||
|
||||
if (e.type == 'finalize') {
|
||||
branches = branches.filter((branch) => branch.active);
|
||||
ensureBranchOrder();
|
||||
}
|
||||
}
|
||||
const hoverClass = 'drag-zone-hover';
|
||||
|
||||
function handleEmpty() {
|
||||
const emptyIndex = branches.findIndex((item) => !item.files || item.files.length == 0);
|
||||
@ -40,22 +25,64 @@
|
||||
</script>
|
||||
|
||||
<div
|
||||
bind:this={dropZone}
|
||||
id="branch-lanes"
|
||||
class="flex max-w-full flex-shrink flex-grow snap-x items-start overflow-x-auto overflow-y-hidden bg-light-200 px-2 dark:bg-dark-1000"
|
||||
use:dndzone={{
|
||||
items: branches,
|
||||
types: ['branch'],
|
||||
receives: ['branch'],
|
||||
dropTargetClassMap: {
|
||||
file: [newBranchClass],
|
||||
hunk: [newBranchClass]
|
||||
on:dragenter={(e) => {
|
||||
if (!e.dataTransfer?.types.includes('text/branch')) {
|
||||
return;
|
||||
}
|
||||
dropZone.classList.add(hoverClass);
|
||||
}}
|
||||
on:dragend={(e) => {
|
||||
if (!e.dataTransfer?.types.includes('text/branch')) {
|
||||
return;
|
||||
}
|
||||
dropZone.classList.remove(hoverClass);
|
||||
}}
|
||||
on:dragover={(e) => {
|
||||
if (!e.dataTransfer?.types.includes('text/branch')) {
|
||||
return;
|
||||
}
|
||||
e.preventDefault(); // Only when text/branch
|
||||
const children = [...e.currentTarget.children];
|
||||
dropPosition = 0;
|
||||
for (let i = 0; i < children.length; i++) {
|
||||
const pos = children[i].getBoundingClientRect();
|
||||
if (e.clientX > pos.left + pos.width) {
|
||||
dropPosition = i + 1; // Note that this is declared in the <script>
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
const idx = children.indexOf(dragged);
|
||||
if (idx != dropPosition) {
|
||||
idx >= dropPosition
|
||||
? children[dropPosition].before(dragged)
|
||||
: children[dropPosition].after(dragged);
|
||||
}
|
||||
}}
|
||||
on:drop={(e) => {
|
||||
dropZone.classList.remove(hoverClass);
|
||||
if (priorPosition != dropPosition) {
|
||||
const el = branches.splice(priorPosition, 1);
|
||||
branches.splice(dropPosition, 0, ...el);
|
||||
branches.forEach((branch, i) => {
|
||||
if (branch.order !== i) {
|
||||
virtualBranches.updateBranchOrder(branch.id, i);
|
||||
}
|
||||
});
|
||||
}
|
||||
}}
|
||||
on:consider={handleDndEvent}
|
||||
on:finalize={handleDndEvent}
|
||||
>
|
||||
{#each branches.filter((c) => c.active) as { id, name, files, commits, upstream, description, order } (id)}
|
||||
<Lane
|
||||
on:dragstart={(e) => {
|
||||
if (!e.dataTransfer) return;
|
||||
e.dataTransfer.setData('text/branch', id);
|
||||
dragged = e.currentTarget;
|
||||
priorPosition = Array.from(dropZone.children).indexOf(dragged);
|
||||
}}
|
||||
{name}
|
||||
commitMessage={description}
|
||||
{files}
|
||||
@ -73,7 +100,7 @@
|
||||
</div>
|
||||
|
||||
<style lang="postcss">
|
||||
:global(#branch-lanes.new-branch-active [data-dnd-ignore]) {
|
||||
@apply visible flex;
|
||||
:global(.drag-zone-hover *) {
|
||||
@apply pointer-events-none;
|
||||
}
|
||||
</style>
|
||||
|
@ -1,9 +1,10 @@
|
||||
<script lang="ts" context="module">
|
||||
const zones = new Set<HTMLDivElement>();
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
import { dndzone } from 'svelte-dnd-action';
|
||||
import type { DndEvent } from 'svelte-dnd-action/typings';
|
||||
import { Commit, File, Hunk } from '$lib/api/ipc/vbranches';
|
||||
import type { Commit, File, Hunk } from '$lib/api/ipc/vbranches';
|
||||
import { createEventDispatcher, onMount } from 'svelte';
|
||||
import { createFile } from './helpers';
|
||||
import FileCard from './FileCard.svelte';
|
||||
import { IconBranch } from '$lib/icons';
|
||||
import { Button } from '$lib/components';
|
||||
@ -39,26 +40,15 @@
|
||||
let isPushing = false;
|
||||
let popupMenu: PopupMenu;
|
||||
let meatballButton: HTMLButtonElement;
|
||||
let dropZone: HTMLDivElement;
|
||||
|
||||
function handleDndEvent(e: CustomEvent<DndEvent<File | Hunk>>) {
|
||||
const newItems = e.detail.items;
|
||||
const fileItems = newItems.filter((item) => item instanceof File) as File[];
|
||||
const hoverClass = 'drag-zone-hover';
|
||||
const hunkType = 'text/hunk';
|
||||
|
||||
console.log('lane: handleDndEvent', e.type, e.detail.items);
|
||||
|
||||
const hunkItems = newItems.filter((item) => item instanceof Hunk) as Hunk[];
|
||||
hunkItems.forEach((hunk) => {
|
||||
const file = files.find((f) => f.hunks.find((h) => h.id == hunk.id));
|
||||
if (file) {
|
||||
file.hunks.push(hunk);
|
||||
} else {
|
||||
fileItems.push(createFile(hunk.filePath, hunk));
|
||||
}
|
||||
});
|
||||
|
||||
files = fileItems.filter((file) => file.hunks && file.hunks.length > 0);
|
||||
if (e.type === 'finalize') updateBranchOwnership();
|
||||
}
|
||||
onMount(() => {
|
||||
zones.add(dropZone);
|
||||
return () => zones.delete(dropZone);
|
||||
});
|
||||
|
||||
function updateBranchOwnership() {
|
||||
const ownership = files
|
||||
@ -133,12 +123,53 @@
|
||||
console.log('branch name change:', name);
|
||||
virtualBranches.updateBranchName(branchId, name);
|
||||
}
|
||||
|
||||
function isChildOf(child: any, parent: HTMLElement): boolean {
|
||||
if (parent === child) return false;
|
||||
if (!child.parentElement) return false;
|
||||
if (child.parentElement == parent) return true;
|
||||
return isChildOf(child.parentElement, parent);
|
||||
}
|
||||
</script>
|
||||
|
||||
<div
|
||||
class="flex max-h-full min-w-[24rem] max-w-[120ch] shrink-0 snap-center flex-col overflow-y-auto py-2 px-3 transition-width dark:text-dark-100"
|
||||
draggable="true"
|
||||
bind:this={dropZone}
|
||||
class="flex max-h-full min-w-[24rem] max-w-[120ch] shrink-0 snap-center flex-col overflow-y-auto bg-light-200 py-2 px-3 transition-width dark:bg-dark-1000 dark:text-dark-100"
|
||||
class:w-full={maximized}
|
||||
class:w-96={!maximized}
|
||||
on:dragstart
|
||||
on:dragenter={(e) => {
|
||||
if (!e.dataTransfer?.types.includes(hunkType)) {
|
||||
return;
|
||||
}
|
||||
dropZone.classList.add(hoverClass);
|
||||
}}
|
||||
on:dragleave|stopPropagation={(e) => {
|
||||
if (!e.dataTransfer?.types.includes(hunkType)) {
|
||||
return;
|
||||
}
|
||||
if (!isChildOf(e.target, dropZone)) {
|
||||
dropZone.classList.remove(hoverClass);
|
||||
}
|
||||
}}
|
||||
on:dragover|stopPropagation={(e) => {
|
||||
if (e.dataTransfer?.types.includes(hunkType)) e.preventDefault();
|
||||
}}
|
||||
on:dragend={(e) => {
|
||||
dropZone.classList.remove(hoverClass);
|
||||
}}
|
||||
on:drop|stopPropagation={(e) => {
|
||||
if (!e.dataTransfer) {
|
||||
return;
|
||||
}
|
||||
dropZone.classList.remove(hoverClass);
|
||||
const data = e.dataTransfer.getData(hunkType);
|
||||
const ownership = files
|
||||
.map((file) => file.id + ':' + file.hunks.map((hunk) => hunk.id).join(','))
|
||||
.join('\n');
|
||||
virtualBranches.updateBranchOwnership(branchId, (data + '\n' + ownership).trim());
|
||||
}}
|
||||
>
|
||||
<div
|
||||
class="mb-2 flex w-full shrink-0 items-center gap-x-2 rounded-lg bg-light-200 text-lg font-bold text-light-900 dark:bg-dark-1000 dark:font-normal dark:text-dark-100"
|
||||
@ -194,39 +225,29 @@
|
||||
class="flex flex-col rounded bg-white p-2 shadow-lg dark:border dark:border-dark-600 dark:bg-dark-800"
|
||||
>
|
||||
<div class="mb-2 flex items-center">
|
||||
{#if files.filter((x) => x.hunks).length > 0}
|
||||
<textarea
|
||||
bind:value={commitMessage}
|
||||
class="shrink-0 flex-grow cursor-text resize-none overflow-x-auto overflow-y-auto rounded border border-white bg-white p-2 text-dark-700 outline-none hover:border-light-400 focus:border-light-400 focus:ring-0 dark:border-dark-500 dark:bg-dark-700 dark:text-light-400 dark:hover:border-dark-300 dark:focus:border-dark-300"
|
||||
placeholder="Your commit message here..."
|
||||
rows={messageRows}
|
||||
/>
|
||||
{/if}
|
||||
<textarea
|
||||
bind:value={commitMessage}
|
||||
class="shrink-0 flex-grow cursor-text resize-none overflow-x-auto overflow-y-auto rounded border border-white bg-white p-2 text-dark-700 outline-none hover:border-light-400 focus:border-light-400 focus:ring-0 dark:border-dark-500 dark:bg-dark-700 dark:text-light-400 dark:hover:border-dark-300 dark:focus:border-dark-300"
|
||||
placeholder="Your commit message here..."
|
||||
rows={messageRows}
|
||||
/>
|
||||
</div>
|
||||
<div class="mb-4 text-right">
|
||||
{#if files.filter((x) => x.hunks).length > 0}
|
||||
<Button
|
||||
height="small"
|
||||
color="purple"
|
||||
on:click={() => {
|
||||
commit();
|
||||
}}>Commit</Button
|
||||
>
|
||||
{/if}
|
||||
<Button
|
||||
height="small"
|
||||
color="purple"
|
||||
on:click={() => {
|
||||
commit();
|
||||
}}>Commit</Button
|
||||
>
|
||||
</div>
|
||||
<div
|
||||
class="flex flex-shrink flex-col gap-y-2"
|
||||
use:dndzone={{
|
||||
items: files,
|
||||
zoneTabIndex: -1,
|
||||
types: ['file'],
|
||||
receives: ['file', 'hunk']
|
||||
}}
|
||||
on:consider={handleDndEvent}
|
||||
on:finalize={handleDndEvent}
|
||||
>
|
||||
{#each files.filter((x) => x.hunks) as file (file.id)}
|
||||
<div class="flex flex-shrink flex-col gap-y-2">
|
||||
<div class="drag-zone-marker hidden rounded-lg border p-6">
|
||||
Drop here to add to virtual branch
|
||||
</div>
|
||||
{#each files as file (file.id)}
|
||||
<FileCard
|
||||
id={file.id}
|
||||
filepath={file.path}
|
||||
expanded={file.expanded}
|
||||
hunks={file.hunks}
|
||||
@ -238,11 +259,21 @@
|
||||
setExpandedWithCache(file, e.detail);
|
||||
expandFromCache();
|
||||
}}
|
||||
on:drag={(e) => {
|
||||
zones.forEach((zone) => {
|
||||
if (zone != dropZone) {
|
||||
e.detail
|
||||
? zone.classList.add('drag-zone-active')
|
||||
: zone.classList.remove('drag-zone-active');
|
||||
}
|
||||
});
|
||||
}}
|
||||
{projectPath}
|
||||
/>
|
||||
{/each}
|
||||
{#if files.filter((x) => x.hunks).length == 0}
|
||||
<div class="p-3 pt-0">No uncommitted work on this branch.</div>
|
||||
{#if files.length == 0}
|
||||
<!-- attention: these markers have custom css at the bottom of thise file -->
|
||||
<div class="no-changes p-2" data-dnd-ignore>No uncommitted work on this branch.</div>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
|
@ -1,8 +1,6 @@
|
||||
<script lang="ts">
|
||||
import { createEventDispatcher } from 'svelte';
|
||||
import { dndzone } from 'svelte-dnd-action';
|
||||
import { formatDistanceToNow } from 'date-fns';
|
||||
import type { DndEvent } from 'svelte-dnd-action/typings';
|
||||
import type { Hunk } from '$lib/api/ipc/vbranches';
|
||||
import HunkDiffViewer from './HunkDiffViewer.svelte';
|
||||
import { summarizeHunk } from '$lib/summaries';
|
||||
@ -11,6 +9,7 @@
|
||||
import PopupMenu from '$lib/components/PopupMenu/PopupMenu.svelte';
|
||||
import PopupMenuItem from '$lib/components/PopupMenu/PopupMenuItem.svelte';
|
||||
|
||||
export let id: string;
|
||||
export let projectPath: string;
|
||||
export let filepath: string;
|
||||
export let hunks: Hunk[];
|
||||
@ -19,16 +18,12 @@
|
||||
const dispatch = createEventDispatcher<{
|
||||
expanded: boolean;
|
||||
update: Hunk[];
|
||||
drag: boolean;
|
||||
}>();
|
||||
export let expanded: boolean | undefined;
|
||||
|
||||
let popupMenu: PopupMenu;
|
||||
|
||||
function handleDndEvent(e: CustomEvent<DndEvent<Hunk>>) {
|
||||
hunks = e.detail.items;
|
||||
if (e.type == 'finalize') dispatch('update', e.detail.items);
|
||||
}
|
||||
|
||||
function hunkSize(hunk: string): number[] {
|
||||
const linesAdded = hunk.split('\n').filter((line) => line.startsWith('+')).length;
|
||||
const linesRemoved = hunk.split('\n').filter((line) => line.startsWith('-')).length;
|
||||
@ -53,9 +48,19 @@
|
||||
</script>
|
||||
|
||||
<div
|
||||
draggable="true"
|
||||
on:dragstart|stopPropagation={(e) => {
|
||||
if (!e.dataTransfer) return;
|
||||
e.dataTransfer.setData('text/hunk', id + ':' + hunks.map((h) => h.id).join(','));
|
||||
dispatch('drag', true);
|
||||
return true;
|
||||
}}
|
||||
on:dragend|stopPropagation={(e) => {
|
||||
dispatch('drag', false);
|
||||
}}
|
||||
class="changed-file flex w-full flex-col justify-center gap-2 rounded-lg border border-light-300 bg-light-50 text-light-900 dark:border-dark-400 dark:bg-dark-700 dark:text-light-300"
|
||||
>
|
||||
<div class="flex items-center px-2 pt-2">
|
||||
<div class="items-cente flex px-2 pt-2">
|
||||
<div class="flex-grow overflow-hidden text-ellipsis whitespace-nowrap " title={filepath}>
|
||||
{@html boldenFilename(filepath)}
|
||||
</div>
|
||||
@ -75,23 +80,22 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="hunk-change-container flex flex-col gap-2 rounded px-2 pb-2"
|
||||
use:dndzone={{
|
||||
items: hunks,
|
||||
zoneTabIndex: -1,
|
||||
autoAriaDisabled: true,
|
||||
types: ['hunk', filepath],
|
||||
receives: [filepath]
|
||||
}}
|
||||
on:consider={handleDndEvent}
|
||||
on:finalize={handleDndEvent}
|
||||
>
|
||||
<div class="hunk-change-container flex flex-col gap-2 rounded px-2 pb-2">
|
||||
{#if expanded}
|
||||
{#each hunks || [] as hunk (hunk.id)}
|
||||
<div
|
||||
class="changed-hunk flex w-full flex-col rounded-lg border border-light-200 bg-white dark:border-dark-400 dark:bg-dark-900"
|
||||
draggable="true"
|
||||
on:dragstart|stopPropagation={(e) => {
|
||||
if (!e.dataTransfer) return;
|
||||
e.dataTransfer.setData('text/hunk', id + ':' + hunk.id);
|
||||
dispatch('drag', true);
|
||||
return false;
|
||||
}}
|
||||
on:dragend|stopPropagation={(e) => {
|
||||
dispatch('drag', false);
|
||||
}}
|
||||
on:contextmenu|preventDefault={(e) => popupMenu.openByMouse(e, hunk)}
|
||||
class="changed-hunk flex w-full flex-col rounded-lg border border-light-200 bg-white dark:border-dark-400 dark:bg-dark-900"
|
||||
>
|
||||
<div class="truncate whitespace-normal p-2">
|
||||
{#await summarizeHunk(hunk.diff) then description}
|
||||
|
@ -1,72 +1,58 @@
|
||||
<script lang="ts">
|
||||
import { dndzone } from 'svelte-dnd-action';
|
||||
import { Branch, File, Hunk } from '$lib/api/ipc/vbranches';
|
||||
import type { DndEvent } from 'svelte-dnd-action/typings';
|
||||
import { createBranch, createFile } from './helpers';
|
||||
import type { Branch } from '$lib/api/ipc/vbranches';
|
||||
import { Button } from '$lib/components';
|
||||
import type { VirtualBranchOperations } from './vbranches';
|
||||
|
||||
export let virtualBranches: VirtualBranchOperations;
|
||||
let items: Branch[] = [];
|
||||
let dropZone: HTMLDivElement;
|
||||
|
||||
function handleNewVirtualBranch() {
|
||||
virtualBranches.createBranch({});
|
||||
}
|
||||
|
||||
function handleDndFinalize(e: CustomEvent<DndEvent<Branch | File | Hunk>>) {
|
||||
console.log('new dropzone: handleDndFinalize', e.type, e.detail.items);
|
||||
const newItems = e.detail.items;
|
||||
const branchItems = newItems.filter((item) => item instanceof Branch) as Branch[];
|
||||
|
||||
const hunkItems = newItems.filter((item) => item instanceof Hunk) as Hunk[];
|
||||
for (const hunk of hunkItems) {
|
||||
branchItems.push(createBranch(createFile(hunk.filePath, hunk)));
|
||||
}
|
||||
|
||||
const fileItems = newItems.filter((item) => item instanceof File) as File[];
|
||||
for (const file of fileItems) {
|
||||
branchItems.push(createBranch(file));
|
||||
}
|
||||
|
||||
if (e.type == 'finalize') {
|
||||
const ownership = branchItems[0].files
|
||||
.map((file) => file.id + ':' + file.hunks.map((hunk) => hunk.id).join(','))
|
||||
.join('\n');
|
||||
|
||||
virtualBranches.createBranch({ ownership });
|
||||
items = [];
|
||||
return;
|
||||
}
|
||||
items = branchItems;
|
||||
function isChildOf(child: any, parent: HTMLElement): boolean {
|
||||
if (parent === child) return false;
|
||||
if (!child.parentElement) return false;
|
||||
if (child.parentElement == parent) return true;
|
||||
return isChildOf(child.parentElement, parent);
|
||||
}
|
||||
</script>
|
||||
|
||||
<div
|
||||
id="new-branch-dz"
|
||||
class="ml-4 mt-16 flex h-40 w-[22.5rem] shrink-0 items-center rounded-lg border border-dashed border-light-600 px-8 py-10"
|
||||
use:dndzone={{
|
||||
items: items,
|
||||
types: ['new-branch'],
|
||||
receives: ['file', 'hunk'],
|
||||
dropTargetClassMap: {
|
||||
file: ['new-branch-active'],
|
||||
hunk: ['new-branch-active']
|
||||
class="h-42 ml-4 mt-16 flex w-[22.5rem] shrink-0 justify-center text-center text-light-800 dark:text-dark-100"
|
||||
bind:this={dropZone}
|
||||
on:dragover|stopPropagation={(e) => {
|
||||
if (e.dataTransfer?.types.includes('text/hunk')) e.preventDefault();
|
||||
dropZone.classList.add('drag-zone-hover');
|
||||
}}
|
||||
on:dragleave|stopPropagation={(e) => {
|
||||
if (!isChildOf(e.target, dropZone)) {
|
||||
dropZone.classList.remove('drag-zone-hover');
|
||||
}
|
||||
}}
|
||||
on:finalize={handleDndFinalize}
|
||||
on:drop|stopPropagation={(e) => {
|
||||
if (!e.dataTransfer) {
|
||||
return;
|
||||
}
|
||||
dropZone.classList.remove('drag-zone-hover');
|
||||
const ownership = e.dataTransfer.getData('text/hunk');
|
||||
virtualBranches.createBranch({ ownership });
|
||||
}}
|
||||
>
|
||||
<div
|
||||
class="flex flex-col items-center gap-y-3 self-center p-8 text-center text-lg text-light-800 dark:text-dark-100"
|
||||
>
|
||||
<p>Drag changes or click button to create new virtual branch</p>
|
||||
<Button color="purple" height="small" on:click={handleNewVirtualBranch}
|
||||
>New virtual branch</Button
|
||||
>
|
||||
<div class="bg-green-300" />
|
||||
<div class="call-to-action flex-grow rounded-lg border border-dashed border-light-600 p-8">
|
||||
<div class="flex flex-col items-center gap-y-3 self-center p-2">
|
||||
<p>Drag changes or click button to create new virtual branch</p>
|
||||
<Button color="purple" height="small" on:click={handleNewVirtualBranch}
|
||||
>New virtual branch</Button
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
<div class="drag-zone-marker hidden flex-grow rounded-lg border border-green-450 p-8">
|
||||
<div class="flex flex-col items-center gap-y-3 self-center p-2">
|
||||
<p>Drop here to add to virtual branch</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style lang="postcss">
|
||||
:global(#new-branch-dz.new-branch-active) {
|
||||
@apply visible flex;
|
||||
}
|
||||
</style>
|
||||
|
@ -108,7 +108,12 @@ const config = {
|
||||
900: '#7c2d12'
|
||||
},
|
||||
green: {
|
||||
200: '#AFEDB1',
|
||||
300: '#6BE66D',
|
||||
400: '#4ade80',
|
||||
450: '#40C341',
|
||||
460: '#346E45',
|
||||
470: '#314D39',
|
||||
500: '#22c55e',
|
||||
600: '#16a34a',
|
||||
700: '#15803d',
|
||||
|
Loading…
Reference in New Issue
Block a user