mirror of
https://github.com/gitbutlerapp/gitbutler.git
synced 2024-12-23 09:33:01 +03:00
Update drag & drop to match forked library
- svelte-dnd-action now installed from gitbutlerapp fork - dnd zones can have multiple types and require receivable types - dropped items that do not match zone have to be wrapped in handlers
This commit is contained in:
parent
d21d7e4909
commit
dbcb391252
@ -72,7 +72,7 @@
|
||||
"prettier-plugin-tailwindcss": "^0.3.0",
|
||||
"svelte": "~3.55.1",
|
||||
"svelte-check": "^3.0.1",
|
||||
"svelte-dnd-action": "^0.9.22",
|
||||
"svelte-dnd-action": "github:gitbutlerapp/svelte-dnd-action#fbe6a192bc99dd69d2d1946fefb2df9cea7b2de9",
|
||||
"svelte-floating-ui": "^1.5.2",
|
||||
"svelte-french-toast": "^1.0.3",
|
||||
"svelte-loadable-store": "^1.2.3",
|
||||
|
@ -170,8 +170,8 @@ devDependencies:
|
||||
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: ^0.9.22
|
||||
version: 0.9.22(svelte@3.55.1)
|
||||
specifier: github:gitbutlerapp/svelte-dnd-action#fbe6a192bc99dd69d2d1946fefb2df9cea7b2de9
|
||||
version: github.com/gitbutlerapp/svelte-dnd-action/fbe6a192bc99dd69d2d1946fefb2df9cea7b2de9(svelte@3.55.1)
|
||||
svelte-floating-ui:
|
||||
specifier: ^1.5.2
|
||||
version: 1.5.2
|
||||
@ -3686,14 +3686,6 @@ packages:
|
||||
- sugarss
|
||||
dev: true
|
||||
|
||||
/svelte-dnd-action@0.9.22(svelte@3.55.1):
|
||||
resolution: {integrity: sha512-lOQJsNLM1QWv5mdxIkCVtk6k4lHCtLgfE59y8rs7iOM6erchbLC9hMEFYSveZz7biJV0mpg7yDSs4bj/RT/YkA==}
|
||||
peerDependencies:
|
||||
svelte: '>=3.23.0'
|
||||
dependencies:
|
||||
svelte: 3.55.1
|
||||
dev: true
|
||||
|
||||
/svelte-eslint-parser@0.29.0(svelte@3.55.1):
|
||||
resolution: {integrity: sha512-2uzOw9vRpSO3fo6NkbH7UynfCopQbMz/7LO9KT05YPvkB0uuFvFHex8+Ccv3gSrxHRvKS7FwJmV4H8WNWIzgWQ==}
|
||||
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
|
||||
@ -4274,6 +4266,17 @@ packages:
|
||||
engines: {node: '>=10'}
|
||||
dev: true
|
||||
|
||||
github.com/gitbutlerapp/svelte-dnd-action/fbe6a192bc99dd69d2d1946fefb2df9cea7b2de9(svelte@3.55.1):
|
||||
resolution: {tarball: https://codeload.github.com/gitbutlerapp/svelte-dnd-action/tar.gz/fbe6a192bc99dd69d2d1946fefb2df9cea7b2de9}
|
||||
id: github.com/gitbutlerapp/svelte-dnd-action/fbe6a192bc99dd69d2d1946fefb2df9cea7b2de9
|
||||
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/5e14c2cad7335a4284a6caad81d8cf37dd675a27:
|
||||
resolution: {tarball: https://codeload.github.com/tauri-apps/tauri-plugin-log/tar.gz/5e14c2cad7335a4284a6caad81d8cf37dd675a27}
|
||||
name: tauri-plugin-log-api
|
||||
|
2
src/global.d.ts
vendored
2
src/global.d.ts
vendored
@ -1,5 +1,5 @@
|
||||
declare type Item = import('svelte-dnd-action').Item;
|
||||
declare type DndEvent<ItemType = Item> = import('svelte-dnd-action').DndEvent<ItemType>;
|
||||
declare type DndEvent<ItemType = Item> = import('svelte-dnd-action/typings').DndEvent<ItemType>;
|
||||
declare namespace svelte.JSX {
|
||||
interface HTMLAttributes<T> {
|
||||
onconsider?: (event: CustomEvent<DndEvent<ItemType>> & { target: EventTarget & T }) => void;
|
||||
|
@ -5,8 +5,6 @@
|
||||
|
||||
export let data: PageData;
|
||||
let columnsData = data.columnsData;
|
||||
|
||||
$: console.log(columnsData);
|
||||
</script>
|
||||
|
||||
<div class="flex h-full">
|
||||
|
@ -9,31 +9,40 @@ export const load: PageLoad = wrapLoadWithSentry(async () => {
|
||||
id: 'c1',
|
||||
name: 'feature-1',
|
||||
active: true,
|
||||
kind: 'lane',
|
||||
files: [
|
||||
{
|
||||
id: 'f1',
|
||||
path: 'src/foo.py',
|
||||
kind: 'file',
|
||||
hunks: [
|
||||
{
|
||||
id: 'h1',
|
||||
name: 'foo-hunk-1',
|
||||
modified: subMinutes(new Date(), 5)
|
||||
kind: 'hunk',
|
||||
modified: subMinutes(new Date(), 5),
|
||||
filePath: 'src/foo.py'
|
||||
},
|
||||
{
|
||||
id: 'h2',
|
||||
name: 'foo-hunk-2',
|
||||
modified: subSeconds(new Date(), 15)
|
||||
kind: 'hunk',
|
||||
modified: subSeconds(new Date(), 15),
|
||||
filePath: 'src/foo.py'
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 'f2',
|
||||
path: 'src/bar.py',
|
||||
kind: 'file',
|
||||
hunks: [
|
||||
{
|
||||
id: 'h3',
|
||||
name: 'bar-hunk-1',
|
||||
modified: subHours(new Date(), 2)
|
||||
kind: 'hunk',
|
||||
modified: subHours(new Date(), 2),
|
||||
filePath: 'src/bar.py'
|
||||
}
|
||||
]
|
||||
}
|
||||
@ -43,15 +52,19 @@ export const load: PageLoad = wrapLoadWithSentry(async () => {
|
||||
id: 'c2',
|
||||
name: 'bugfix',
|
||||
active: true,
|
||||
kind: 'lane',
|
||||
files: [
|
||||
{
|
||||
id: 'f3',
|
||||
path: 'src/foo.py',
|
||||
kind: 'file',
|
||||
hunks: [
|
||||
{
|
||||
id: 'h4',
|
||||
name: 'foo-hunk-3',
|
||||
modified: subMinutes(new Date(), 32)
|
||||
kind: 'hunk',
|
||||
modified: subMinutes(new Date(), 32),
|
||||
filePath: 'src/foo.py'
|
||||
}
|
||||
]
|
||||
}
|
||||
@ -61,15 +74,19 @@ export const load: PageLoad = wrapLoadWithSentry(async () => {
|
||||
id: 'c3',
|
||||
name: 'stashed-things',
|
||||
active: false,
|
||||
kind: 'lane',
|
||||
files: [
|
||||
{
|
||||
id: 'f4',
|
||||
path: 'src/bar.py',
|
||||
kind: 'file',
|
||||
hunks: [
|
||||
{
|
||||
id: 'h5',
|
||||
name: 'bar-hunk-2',
|
||||
modified: subHours(new Date(), 1)
|
||||
kind: 'hunk',
|
||||
modified: subHours(new Date(), 1),
|
||||
filePath: 'src/bar.py'
|
||||
}
|
||||
]
|
||||
}
|
||||
|
@ -1,19 +1,32 @@
|
||||
<script lang="ts">
|
||||
import { flip } from 'svelte/animate';
|
||||
import { dndzone } from 'svelte-dnd-action';
|
||||
import type { BranchLane } from './board';
|
||||
import Lane from './Lane.svelte';
|
||||
import type { BranchLane, File, Hunk } from './board';
|
||||
import type { DndEvent } from 'svelte-dnd-action/typings';
|
||||
|
||||
export let columns: BranchLane[];
|
||||
|
||||
const flipDurationMs = 300;
|
||||
|
||||
function handleDndEvent(
|
||||
e: CustomEvent<DndEvent<BranchLane | File | Hunk>> & { target: HTMLElement }
|
||||
) {
|
||||
columns = e.detail.items.filter((item) => item.kind == 'lane') as BranchLane[];
|
||||
// TODO: Create lanes out of dropped files/hunks
|
||||
}
|
||||
</script>
|
||||
|
||||
<section
|
||||
class="flex gap-x-4 p-4"
|
||||
use:dndzone={{ items: columns, flipDurationMs, type: 'column' }}
|
||||
on:consider={(e) => (columns = e.detail.items)}
|
||||
on:finalize={(e) => (columns = e.detail.items)}
|
||||
use:dndzone={{
|
||||
items: columns,
|
||||
flipDurationMs,
|
||||
types: ['lane'],
|
||||
receives: ['lane', 'file', 'hunk']
|
||||
}}
|
||||
on:consider={handleDndEvent}
|
||||
on:finalize={handleDndEvent}
|
||||
>
|
||||
{#each columns.filter((c) => c.active) as { id, name, files }, idx (id)}
|
||||
<div
|
||||
|
@ -1,14 +1,22 @@
|
||||
<script lang="ts">
|
||||
import { flip } from 'svelte/animate';
|
||||
import { createEventDispatcher } from 'svelte';
|
||||
import { dndzone } from 'svelte-dnd-action';
|
||||
import type { File, Hunk } from './board';
|
||||
const flipDurationMs = 150;
|
||||
import animateHeight from './animation';
|
||||
import { flip } from 'svelte/animate';
|
||||
import { formatDistanceToNow, compareDesc } from 'date-fns';
|
||||
import animateHeight from './animation';
|
||||
import type { DndEvent } from 'svelte-dnd-action/typings';
|
||||
import type { File, Hunk } from './board';
|
||||
|
||||
export let file: File;
|
||||
|
||||
function sortAndUpdateHunks(e: { detail: { items: Hunk[] } }) {
|
||||
const dispatch = createEventDispatcher();
|
||||
const flipDurationMs = 150;
|
||||
|
||||
function handleDndEvent(e: CustomEvent<DndEvent<Hunk>>) {
|
||||
if (!file.hunks || file.hunks.length == 0) {
|
||||
dispatch('empty');
|
||||
return;
|
||||
}
|
||||
e.detail.items.sort((itemA, itemB) => compareDesc(itemA.modified, itemB.modified));
|
||||
file.hunks = e.detail.items;
|
||||
}
|
||||
@ -28,13 +36,14 @@
|
||||
items: file.hunks,
|
||||
flipDurationMs,
|
||||
zoneTabIndex: -1,
|
||||
type: file.path,
|
||||
autoAriaDisabled: true
|
||||
autoAriaDisabled: true,
|
||||
types: ['hunk', file.path],
|
||||
receives: [file.path]
|
||||
}}
|
||||
on:consider={sortAndUpdateHunks}
|
||||
on:finalize={sortAndUpdateHunks}
|
||||
on:consider={handleDndEvent}
|
||||
on:finalize={handleDndEvent}
|
||||
>
|
||||
{#each file.hunks as hunk (hunk.id)}
|
||||
{#each file.hunks || [] as hunk (hunk.id)}
|
||||
<div
|
||||
animate:flip={{ duration: flipDurationMs }}
|
||||
class="w-full rounded border border-zinc-500 bg-zinc-600 p-1"
|
||||
|
@ -1,13 +1,51 @@
|
||||
<script lang="ts">
|
||||
import { flip } from 'svelte/animate';
|
||||
import { dndzone } from 'svelte-dnd-action';
|
||||
import type { File } from './board';
|
||||
import type { DndEvent } from 'svelte-dnd-action/typings';
|
||||
import type { File, Hunk } from './board';
|
||||
import FileCard from './FileCard.svelte';
|
||||
|
||||
export let name: string;
|
||||
export let files: File[];
|
||||
|
||||
const flipDurationMs = 150;
|
||||
|
||||
function handleDndEvent(e: CustomEvent<DndEvent<File | Hunk>>, isFinal: boolean) {
|
||||
const fileItems = e.detail.items.filter((item) => item.kind == 'file') as File[];
|
||||
const hunkItems = e.detail.items.filter((item) => item.kind == 'hunk') as Hunk[];
|
||||
|
||||
// Merge hunks into existing files, or create new where none exist
|
||||
for (const hunk of hunkItems) {
|
||||
const file = fileItems.find((file) => file.path == hunk.filePath);
|
||||
if (file) {
|
||||
file.hunks.push(hunk);
|
||||
} else {
|
||||
fileItems.push({
|
||||
id: `${Date.now()}-${hunk.id}`,
|
||||
path: hunk.filePath,
|
||||
kind: 'file',
|
||||
hunks: [
|
||||
{
|
||||
id: hunk.id,
|
||||
filePath: hunk.filePath,
|
||||
kind: hunk.kind,
|
||||
modified: hunk.modified,
|
||||
name: hunk.name,
|
||||
isDndShadowItem: !isFinal
|
||||
}
|
||||
]
|
||||
});
|
||||
}
|
||||
}
|
||||
files = fileItems.filter((file) => file.hunks && file.hunks.length > 0);
|
||||
}
|
||||
|
||||
function handleEmptyFile() {
|
||||
const emptyIndex = files.findIndex((item) => !item.hunks || item.hunks.length == 0);
|
||||
if (emptyIndex != -1) {
|
||||
files.splice(emptyIndex, 1);
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="flex h-full w-full flex-col">
|
||||
@ -16,13 +54,19 @@
|
||||
</div>
|
||||
<div
|
||||
class="flex flex-grow flex-col gap-y-4"
|
||||
use:dndzone={{ items: files, flipDurationMs, zoneTabIndex: -1 }}
|
||||
on:consider={(e) => (files = e.detail.items)}
|
||||
on:finalize={(e) => (files = e.detail.items)}
|
||||
use:dndzone={{
|
||||
items: files,
|
||||
flipDurationMs,
|
||||
zoneTabIndex: -1,
|
||||
types: ['file'],
|
||||
receives: ['file', 'hunk']
|
||||
}}
|
||||
on:consider={(e) => handleDndEvent(e, false)}
|
||||
on:finalize={(e) => handleDndEvent(e, true)}
|
||||
>
|
||||
{#each files as file, idx (file.id)}
|
||||
{#each files.filter((x) => x.hunks) as file, idx (file.id)}
|
||||
<div animate:flip={{ duration: flipDurationMs }}>
|
||||
<FileCard bind:file />
|
||||
<FileCard bind:file on:empty={handleEmptyFile} />
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
|
@ -2,12 +2,17 @@ export type Hunk = {
|
||||
id: string;
|
||||
name: string;
|
||||
modified: Date;
|
||||
kind: string;
|
||||
filePath: string;
|
||||
isDndShadowItem?: boolean;
|
||||
};
|
||||
|
||||
export type File = {
|
||||
id: string;
|
||||
path: string;
|
||||
hunks: Hunk[];
|
||||
kind: string;
|
||||
isDndShadowItem?: boolean;
|
||||
};
|
||||
|
||||
export type BranchLane = {
|
||||
@ -15,4 +20,5 @@ export type BranchLane = {
|
||||
name: string;
|
||||
active: boolean;
|
||||
files: File[];
|
||||
kind: string;
|
||||
};
|
||||
|
Loading…
Reference in New Issue
Block a user