mirror of
https://github.com/gitbutlerapp/gitbutler.git
synced 2024-11-23 03:26:36 +03:00
Simplify drag & drop
- use classes for hunks, files, etc - add class-transformer dependency - simplify helper functions for readable code
This commit is contained in:
parent
45be9746a4
commit
d0c2707cae
@ -51,6 +51,7 @@
|
||||
"@typescript-eslint/eslint-plugin": "^5.59.7",
|
||||
"@typescript-eslint/parser": "^5.59.7",
|
||||
"autoprefixer": "^10.4.7",
|
||||
"class-transformer": "^0.5.1",
|
||||
"date-fns": "^2.29.3",
|
||||
"diff-match-patch": "^1.0.5",
|
||||
"eslint": "^8.41.0",
|
||||
@ -70,9 +71,10 @@
|
||||
"prettier": "^2.8.0",
|
||||
"prettier-plugin-svelte": "^2.8.1",
|
||||
"prettier-plugin-tailwindcss": "^0.3.0",
|
||||
"reflect-metadata": "^0.1.13",
|
||||
"svelte": "~3.55.1",
|
||||
"svelte-check": "^3.0.1",
|
||||
"svelte-dnd-action": "github:gitbutlerapp/svelte-dnd-action#fbe6a192bc99dd69d2d1946fefb2df9cea7b2de9",
|
||||
"svelte-dnd-action": "github:gitbutlerapp/svelte-dnd-action#8acea8787f2172dd89ff0118bbb40bfcd5e7bf00",
|
||||
"svelte-floating-ui": "^1.5.2",
|
||||
"svelte-french-toast": "^1.0.3",
|
||||
"svelte-loadable-store": "^1.2.3",
|
||||
|
@ -106,6 +106,9 @@ devDependencies:
|
||||
autoprefixer:
|
||||
specifier: ^10.4.7
|
||||
version: 10.4.13(postcss@8.4.21)
|
||||
class-transformer:
|
||||
specifier: ^0.5.1
|
||||
version: 0.5.1
|
||||
date-fns:
|
||||
specifier: ^2.29.3
|
||||
version: 2.29.3
|
||||
@ -163,6 +166,9 @@ devDependencies:
|
||||
prettier-plugin-tailwindcss:
|
||||
specifier: ^0.3.0
|
||||
version: 0.3.0(prettier-plugin-svelte@2.9.0)(prettier@2.8.4)
|
||||
reflect-metadata:
|
||||
specifier: ^0.1.13
|
||||
version: 0.1.13
|
||||
svelte:
|
||||
specifier: ~3.55.1
|
||||
version: 3.55.1
|
||||
@ -170,8 +176,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: github:gitbutlerapp/svelte-dnd-action#fbe6a192bc99dd69d2d1946fefb2df9cea7b2de9
|
||||
version: github.com/gitbutlerapp/svelte-dnd-action/fbe6a192bc99dd69d2d1946fefb2df9cea7b2de9(svelte@3.55.1)
|
||||
specifier: github:gitbutlerapp/svelte-dnd-action#8acea8787f2172dd89ff0118bbb40bfcd5e7bf00
|
||||
version: github.com/gitbutlerapp/svelte-dnd-action/8acea8787f2172dd89ff0118bbb40bfcd5e7bf00(svelte@3.55.1)
|
||||
svelte-floating-ui:
|
||||
specifier: ^1.5.2
|
||||
version: 1.5.2
|
||||
@ -192,10 +198,10 @@ devDependencies:
|
||||
version: 3.2.4(postcss@8.4.21)
|
||||
tauri-plugin-log-api:
|
||||
specifier: github:tauri-apps/tauri-plugin-log
|
||||
version: github.com/tauri-apps/tauri-plugin-log/5e14c2cad7335a4284a6caad81d8cf37dd675a27
|
||||
version: github.com/tauri-apps/tauri-plugin-log/21921031d74f871180381317a338559f588ad8e9
|
||||
tauri-plugin-websocket-api:
|
||||
specifier: github:tauri-apps/tauri-plugin-websocket
|
||||
version: github.com/tauri-apps/tauri-plugin-websocket/c77fa23243388bdeff6ca1b09f78b71b62ef01da
|
||||
version: github.com/tauri-apps/tauri-plugin-websocket/9633f601a828600bcf11208daaf2003b38350eaa
|
||||
tinykeys:
|
||||
specifier: ^1.4.0
|
||||
version: 1.4.0
|
||||
@ -1707,6 +1713,10 @@ packages:
|
||||
fsevents: 2.3.2
|
||||
dev: true
|
||||
|
||||
/class-transformer@0.5.1:
|
||||
resolution: {integrity: sha512-SQa1Ws6hUbfC98vKGxZH3KFY0Y1lm5Zm0SY8XX9zbK7FJCyVEac3ATW0RIpwzW+oOfmHE5PMPufDG9hCfoEOMw==}
|
||||
dev: true
|
||||
|
||||
/color-convert@2.0.1:
|
||||
resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==}
|
||||
engines: {node: '>=7.0.0'}
|
||||
@ -3405,6 +3415,10 @@ packages:
|
||||
picomatch: 2.3.1
|
||||
dev: true
|
||||
|
||||
/reflect-metadata@0.1.13:
|
||||
resolution: {integrity: sha512-Ts1Y/anZELhSsjMcU605fU9RE4Oi3p5ORujwbIKXfWa+0Zxs510Qrmrce5/Jowq3cHSZSJqBjypxmHarc+vEWg==}
|
||||
dev: true
|
||||
|
||||
/requires-port@1.0.0:
|
||||
resolution: {integrity: sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==}
|
||||
dev: true
|
||||
@ -4266,9 +4280,9 @@ 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
|
||||
github.com/gitbutlerapp/svelte-dnd-action/8acea8787f2172dd89ff0118bbb40bfcd5e7bf00(svelte@3.55.1):
|
||||
resolution: {tarball: https://codeload.github.com/gitbutlerapp/svelte-dnd-action/tar.gz/8acea8787f2172dd89ff0118bbb40bfcd5e7bf00}
|
||||
id: github.com/gitbutlerapp/svelte-dnd-action/8acea8787f2172dd89ff0118bbb40bfcd5e7bf00
|
||||
name: svelte-dnd-action
|
||||
version: 0.9.22
|
||||
peerDependencies:
|
||||
@ -4277,16 +4291,16 @@ packages:
|
||||
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}
|
||||
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
|
||||
version: 0.0.0
|
||||
dependencies:
|
||||
'@tauri-apps/api': 1.3.0
|
||||
dev: true
|
||||
|
||||
github.com/tauri-apps/tauri-plugin-websocket/c77fa23243388bdeff6ca1b09f78b71b62ef01da:
|
||||
resolution: {tarball: https://codeload.github.com/tauri-apps/tauri-plugin-websocket/tar.gz/c77fa23243388bdeff6ca1b09f78b71b62ef01da}
|
||||
github.com/tauri-apps/tauri-plugin-websocket/9633f601a828600bcf11208daaf2003b38350eaa:
|
||||
resolution: {tarball: https://codeload.github.com/tauri-apps/tauri-plugin-websocket/tar.gz/9633f601a828600bcf11208daaf2003b38350eaa}
|
||||
name: tauri-plugin-websocket-api
|
||||
version: 0.0.0
|
||||
dependencies:
|
||||
|
@ -1,5 +1,6 @@
|
||||
import type { PageLoad } from './$types';
|
||||
import type { Branch, File } from './types';
|
||||
import { plainToInstance } from 'class-transformer';
|
||||
import { Branch, File } from './types';
|
||||
|
||||
export const load: PageLoad = async () => {
|
||||
const testdata_file = await (
|
||||
@ -26,16 +27,19 @@ export const load: PageLoad = async () => {
|
||||
);
|
||||
let branches = test_branches as Branch[];
|
||||
|
||||
branches = branches.map((column) => ({
|
||||
...column,
|
||||
commits: column.commits.map((commit) => ({
|
||||
...commit,
|
||||
files: commit.files.map((file) => ({
|
||||
...file,
|
||||
hunks: file.hunks.sort((a, b) => b.modifiedAt.getTime() - a.modifiedAt.getTime())
|
||||
branches = plainToInstance(
|
||||
Branch,
|
||||
branches.map((column) => ({
|
||||
...column,
|
||||
commits: column.commits.map((commit) => ({
|
||||
...commit,
|
||||
files: commit.files.map((file) => ({
|
||||
...file,
|
||||
hunks: file.hunks.sort((a, b) => b.modifiedAt.getTime() - a.modifiedAt.getTime())
|
||||
}))
|
||||
}))
|
||||
}))
|
||||
}));
|
||||
);
|
||||
|
||||
return {
|
||||
branchData: branches
|
||||
|
@ -2,7 +2,7 @@
|
||||
import { flip } from 'svelte/animate';
|
||||
import { dndzone } from 'svelte-dnd-action';
|
||||
import Lane from './BranchLane.svelte';
|
||||
import type { Branch, Commit, File, Hunk } from './types';
|
||||
import { Branch, Commit, File, Hunk } from './types';
|
||||
import type { DndEvent } from 'svelte-dnd-action/typings';
|
||||
import { createBranch, createCommit, createFile } from './helpers';
|
||||
|
||||
@ -10,49 +10,25 @@
|
||||
|
||||
const flipDurationMs = 300;
|
||||
|
||||
function handleDndEvent(
|
||||
e: CustomEvent<DndEvent<Branch | Commit | File | Hunk>>,
|
||||
isFinal: boolean
|
||||
) {
|
||||
const branchItems = e.detail.items.filter((item) => item.kind == 'branch') as Branch[];
|
||||
const commitItems = e.detail.items.filter((item) => item.kind == 'commit') as Commit[];
|
||||
const fileItems = e.detail.items.filter((item) => item.kind == 'file') as File[];
|
||||
const hunkItems = e.detail.items.filter((item) => item.kind == 'hunk') as Hunk[];
|
||||
function handleDndEvent(e: CustomEvent<DndEvent<Branch | Commit | File | Hunk>>) {
|
||||
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({
|
||||
commits: [
|
||||
createCommit({
|
||||
files: [
|
||||
createFile({
|
||||
hunks: [{ ...hunk, isDndShadowItem: !isFinal }],
|
||||
isShadow: false,
|
||||
filePath: hunk.filePath
|
||||
})
|
||||
],
|
||||
isShadow: false
|
||||
})
|
||||
]
|
||||
})
|
||||
);
|
||||
branchItems.push(createBranch(createCommit(createFile(hunk.filePath, hunk))));
|
||||
}
|
||||
|
||||
const fileItems = newItems.filter((item) => item instanceof File) as File[];
|
||||
for (const file of fileItems) {
|
||||
branchItems.push(
|
||||
createBranch({
|
||||
commits: [
|
||||
createCommit({ files: [{ ...file, isDndShadowItem: !isFinal }], isShadow: false })
|
||||
]
|
||||
})
|
||||
);
|
||||
branchItems.push(createBranch(createCommit(file)));
|
||||
}
|
||||
|
||||
const commitItems = newItems.filter((item) => item instanceof Commit) as Commit[];
|
||||
for (const commit of commitItems) {
|
||||
branchItems.push(
|
||||
createBranch({
|
||||
commits: [commit]
|
||||
})
|
||||
);
|
||||
branchItems.push(createBranch(commit));
|
||||
}
|
||||
|
||||
branches = branchItems.filter((commit) => commit.active);
|
||||
console.log(branches);
|
||||
}
|
||||
@ -73,10 +49,10 @@
|
||||
types: ['branch'],
|
||||
receives: ['branch', 'commit', 'file', 'hunk']
|
||||
}}
|
||||
on:consider={(e) => handleDndEvent(e, false)}
|
||||
on:finalize={(e) => handleDndEvent(e, true)}
|
||||
on:consider={handleDndEvent}
|
||||
on:finalize={handleDndEvent}
|
||||
>
|
||||
{#each branches.filter((c) => c.active) as { id, name, commits }, idx (id)}
|
||||
{#each branches.filter((c) => c.active) as { id, name, commits } (id)}
|
||||
<div
|
||||
class="flex h-full w-96 border border-zinc-700 bg-zinc-900/50 p-4"
|
||||
animate:flip={{ duration: flipDurationMs }}
|
||||
|
@ -2,7 +2,7 @@
|
||||
import { flip } from 'svelte/animate';
|
||||
import { dndzone } from 'svelte-dnd-action';
|
||||
import type { DndEvent } from 'svelte-dnd-action/typings';
|
||||
import type { Commit, File, Hunk } from './types';
|
||||
import { Commit, File, Hunk } from './types';
|
||||
import CommitGroup from './CommitGroup.svelte';
|
||||
import { createEventDispatcher } from 'svelte';
|
||||
import { createCommit, createFile } from './helpers';
|
||||
@ -13,31 +13,20 @@
|
||||
const flipDurationMs = 150;
|
||||
const dispatch = createEventDispatcher();
|
||||
|
||||
function handleDndEvent(e: CustomEvent<DndEvent<Commit | File | Hunk>>, isFinal: boolean) {
|
||||
const commitItems = e.detail.items.filter((item) => item.kind == 'commit') as Commit[];
|
||||
const fileItems = e.detail.items.filter((item) => item.kind == 'file') as File[];
|
||||
const hunkItems = e.detail.items.filter((item) => item.kind == 'hunk') as Hunk[];
|
||||
function handleDndEvent(e: CustomEvent<DndEvent<Commit | File | Hunk>>) {
|
||||
const newItems = e.detail.items;
|
||||
const commitItems = newItems.filter((item) => item instanceof Commit) as Commit[];
|
||||
|
||||
// Merge hunks into existing files, or create new where none exist
|
||||
const hunkItems = newItems.filter((item) => item instanceof Hunk) as Hunk[];
|
||||
for (const hunk of hunkItems) {
|
||||
commitItems.push(
|
||||
createCommit({
|
||||
files: [
|
||||
createFile({
|
||||
hunks: [{ ...hunk, isDndShadowItem: !isFinal }],
|
||||
isShadow: false,
|
||||
filePath: hunk.filePath
|
||||
})
|
||||
],
|
||||
isShadow: false
|
||||
})
|
||||
);
|
||||
commitItems.push(createCommit(createFile(hunk.filePath, hunk)));
|
||||
}
|
||||
|
||||
const fileItems = newItems.filter((item) => item instanceof File) as File[];
|
||||
for (const file of fileItems) {
|
||||
commitItems.push(
|
||||
createCommit({ files: [{ ...file, isDndShadowItem: true }], isShadow: false })
|
||||
);
|
||||
commitItems.push(createCommit(file));
|
||||
}
|
||||
|
||||
commits = commitItems.filter((commit) => commit.files && commit.files.length > 0);
|
||||
|
||||
if (e.type == 'finalize' && (!commits || commits.length == 0)) {
|
||||
@ -69,8 +58,8 @@
|
||||
types: ['commit'],
|
||||
receives: ['commit', 'file', 'hunk']
|
||||
}}
|
||||
on:consider={(e) => handleDndEvent(e, false)}
|
||||
on:finalize={(e) => handleDndEvent(e, true)}
|
||||
on:consider={handleDndEvent}
|
||||
on:finalize={handleDndEvent}
|
||||
>
|
||||
{#each commits.filter((x) => x.files) as { id, description, files }, idx (id)}
|
||||
<div class="w-full" animate:flip={{ duration: flipDurationMs }}>
|
||||
|
@ -2,37 +2,32 @@
|
||||
import { flip } from 'svelte/animate';
|
||||
import { dndzone } from 'svelte-dnd-action';
|
||||
import type { DndEvent } from 'svelte-dnd-action/typings';
|
||||
import type { File, Hunk } from './types';
|
||||
import { File, Hunk } from './types';
|
||||
import FileCard from './FileCard.svelte';
|
||||
import { createEventDispatcher } from 'svelte';
|
||||
import { createFile } from './helpers';
|
||||
|
||||
export let description: string;
|
||||
export let description: string | undefined;
|
||||
export let id: string;
|
||||
export let files: File[];
|
||||
|
||||
const flipDurationMs = 150;
|
||||
const dispatch = createEventDispatcher();
|
||||
|
||||
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[];
|
||||
function handleDndEvent(e: CustomEvent<DndEvent<File | Hunk>>) {
|
||||
const newItems = e.detail.items;
|
||||
const fileItems = newItems.filter((item) => item instanceof File) as File[];
|
||||
|
||||
// Merge hunks into existing files, or create new where none exist
|
||||
const hunkItems = newItems.filter((item) => item instanceof Hunk) as Hunk[];
|
||||
for (const hunk of hunkItems) {
|
||||
const file = fileItems.find((file) => file.path == hunk.filePath);
|
||||
if (file) {
|
||||
file.hunks.push(hunk);
|
||||
} else {
|
||||
fileItems.push(
|
||||
createFile({
|
||||
filePath: hunk.filePath,
|
||||
hunks: [{ ...hunk, isDndShadowItem: !isFinal }],
|
||||
isShadow: false
|
||||
})
|
||||
);
|
||||
fileItems.push(createFile(hunk.filePath, hunk));
|
||||
}
|
||||
}
|
||||
|
||||
files = fileItems.filter((file) => file.hunks && file.hunks.length > 0);
|
||||
|
||||
if (e.type == 'finalize' && (!files || files.length == 0)) {
|
||||
@ -63,8 +58,8 @@
|
||||
types: ['file'],
|
||||
receives: ['file', 'hunk']
|
||||
}}
|
||||
on:consider={(e) => handleDndEvent(e, false)}
|
||||
on:finalize={(e) => handleDndEvent(e, true)}
|
||||
on:consider={handleDndEvent}
|
||||
on:finalize={handleDndEvent}
|
||||
>
|
||||
{#each files.filter((x) => x.hunks) as file, idx (file.id)}
|
||||
<div class="w-full" animate:flip={{ duration: flipDurationMs }}>
|
||||
|
@ -1,38 +1,37 @@
|
||||
import type { Branch, Commit, File, Hunk } from './types';
|
||||
import { Branch, Commit, File, type Hunk } from './types';
|
||||
import { plainToInstance } from 'class-transformer';
|
||||
|
||||
let fileCounter = 0;
|
||||
let commitCounter = 0;
|
||||
let branchCounter = 0;
|
||||
|
||||
export function createFile(args: { hunks: [Hunk]; filePath: string; isShadow: boolean }): File {
|
||||
export function createFile(path: string, hunk: Hunk): File {
|
||||
fileCounter++;
|
||||
return {
|
||||
return plainToInstance(File, {
|
||||
id: `file-${fileCounter}`,
|
||||
path: args.filePath,
|
||||
path: path,
|
||||
kind: 'file',
|
||||
hunks: args.hunks,
|
||||
isDndShadowItem: args.isShadow
|
||||
};
|
||||
hunks: [hunk]
|
||||
});
|
||||
}
|
||||
|
||||
export function createCommit(args: { files: File[]; isShadow: boolean }): Commit {
|
||||
export function createCommit(file: File): Commit {
|
||||
commitCounter++;
|
||||
return {
|
||||
return plainToInstance(Commit, {
|
||||
id: `commit-${commitCounter}`,
|
||||
description: `New commit # ${commitCounter}`,
|
||||
kind: 'commit',
|
||||
files: args.files,
|
||||
isDndShadowItem: args.isShadow
|
||||
};
|
||||
files: [file]
|
||||
});
|
||||
}
|
||||
|
||||
export function createBranch(args: { commits: Commit[] }): Branch {
|
||||
export function createBranch(commit: Commit): Branch {
|
||||
branchCounter++;
|
||||
return {
|
||||
return plainToInstance(Branch, {
|
||||
id: `branch-${branchCounter}`,
|
||||
name: `new branch ${branchCounter}`,
|
||||
active: true,
|
||||
kind: 'branch',
|
||||
commits: args.commits
|
||||
};
|
||||
commits: [commit]
|
||||
});
|
||||
}
|
||||
|
@ -1,35 +1,35 @@
|
||||
export type Hunk = {
|
||||
id: string;
|
||||
name: string;
|
||||
modifiedAt: Date;
|
||||
diff: string;
|
||||
kind: string;
|
||||
filePath: string;
|
||||
isDndShadowItem?: boolean;
|
||||
};
|
||||
import { Type } from 'class-transformer';
|
||||
import 'reflect-metadata';
|
||||
|
||||
export type File = {
|
||||
id: string;
|
||||
path: string;
|
||||
hunks: Hunk[];
|
||||
kind: string;
|
||||
class DndItem {
|
||||
id!: string;
|
||||
kind!: string;
|
||||
isDndShadowItem?: boolean;
|
||||
};
|
||||
}
|
||||
|
||||
export type Commit = {
|
||||
id: string;
|
||||
description: string;
|
||||
export class Hunk extends DndItem {
|
||||
name!: string;
|
||||
diff!: string;
|
||||
modifiedAt!: Date;
|
||||
filePath!: string;
|
||||
}
|
||||
|
||||
export class File extends DndItem {
|
||||
path!: string;
|
||||
@Type(() => Hunk)
|
||||
hunks!: Hunk[];
|
||||
}
|
||||
|
||||
export class Commit extends DndItem {
|
||||
description?: string;
|
||||
committedAt?: Date;
|
||||
files: File[];
|
||||
kind: string;
|
||||
isDndShadowItem?: boolean;
|
||||
};
|
||||
@Type(() => File)
|
||||
files!: File[];
|
||||
}
|
||||
|
||||
export type Branch = {
|
||||
id: string;
|
||||
name: string;
|
||||
active: boolean;
|
||||
commits: Commit[];
|
||||
kind: string;
|
||||
isDndShadowItem?: boolean;
|
||||
};
|
||||
export class Branch extends DndItem {
|
||||
name!: string;
|
||||
active!: boolean;
|
||||
@Type(() => Commit)
|
||||
commits!: Commit[];
|
||||
}
|
||||
|
@ -8,6 +8,7 @@
|
||||
"resolveJsonModule": true,
|
||||
"skipLibCheck": true,
|
||||
"sourceMap": true,
|
||||
"strict": true
|
||||
"strict": true,
|
||||
"experimentalDecorators": true
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user