mirror of
https://github.com/gpxstudio/gpx.studio.git
synced 2026-10-16 08:19:59 +00:00
progress
This commit is contained in:
1 parent
606a0e974a
commit
67a4c090bd
107 files changed
+3474
-3948
No files matched your search
@@ -3,8 +3,7 @@
|
||||
import FileListNodeStore from './FileListNodeStore.svelte';
|
||||
import FileListNode from './FileListNode.svelte';
|
||||
import FileListNodeContent from './FileListNodeContent.svelte';
|
||||
import { ListLevel } from './file-list';
|
||||
import { allowedMoves, dragging, SortableFileList } from './sortable-file-list';
|
||||
import { allowedMoves, dragging, SortableFileList, type ListLevel } from './sortable-file-list';
|
||||
import { engine, type FileState } from '$lib/engine';
|
||||
import type { FileTreeNode } from '$lib/selection-helpers';
|
||||
|
||||
@@ -26,14 +25,14 @@
|
||||
let container: HTMLElement;
|
||||
let sortableLevel: ListLevel =
|
||||
node === null
|
||||
? ListLevel.FILE
|
||||
? 'file'
|
||||
: node.type === 'file'
|
||||
? waypointRoot
|
||||
? ListLevel.WAYPOINTS
|
||||
: ListLevel.TRACK
|
||||
? 'waypoints'
|
||||
: 'track'
|
||||
: node.type === 'waypoints'
|
||||
? ListLevel.WAYPOINT
|
||||
: ListLevel.SEGMENT;
|
||||
? 'waypoint'
|
||||
: 'segment';
|
||||
let orientation = getContext<'vertical' | 'horizontal'>('orientation');
|
||||
|
||||
let canDrop = $derived($dragging !== null && allowedMoves[$dragging].includes(sortableLevel));
|
||||
|
||||
@@ -18,7 +18,6 @@
|
||||
Scissors,
|
||||
FileStack,
|
||||
} from '@lucide/svelte';
|
||||
import { ListLevel } from './file-list';
|
||||
import { getContext } from 'svelte';
|
||||
import { i18n } from '$lib/i18n.svelte';
|
||||
import MetadataDialog from '$lib/components/file-list/metadata/MetadataDialog.svelte';
|
||||
@@ -56,14 +55,7 @@
|
||||
let orientation = getContext<'vertical' | 'horizontal'>('orientation');
|
||||
let embedding = getContext<boolean>('embedding');
|
||||
|
||||
const levels = {
|
||||
file: ListLevel.FILE,
|
||||
track: ListLevel.TRACK,
|
||||
segment: ListLevel.SEGMENT,
|
||||
waypoints: ListLevel.WAYPOINTS,
|
||||
waypoint: ListLevel.WAYPOINT,
|
||||
};
|
||||
let level = $derived(levels[node.type]);
|
||||
let level = $derived(node.type);
|
||||
|
||||
let singleSelection = $derived(selectionSize($selection) === 1);
|
||||
|
||||
@@ -128,7 +120,7 @@
|
||||
<MetadataDialog bind:open={openEditMetadata} {fileState} {node} />
|
||||
<StyleDialog bind:open={openEditStyle} {fileState} {node} />
|
||||
{/if}
|
||||
{#if level === ListLevel.FILE || level === ListLevel.TRACK}
|
||||
{#if level === 'file' || level === 'track'}
|
||||
<div
|
||||
class="absolute {orientation === 'vertical'
|
||||
? 'top-0 bottom-0 right-0 w-1'
|
||||
@@ -178,9 +170,9 @@
|
||||
}
|
||||
}}
|
||||
>
|
||||
{#if level === ListLevel.SEGMENT}
|
||||
{#if level === 'segment'}
|
||||
<Waypoints size="16" class="mx-1 shrink-0" />
|
||||
{:else if level === ListLevel.WAYPOINT}
|
||||
{:else if level === 'waypoint'}
|
||||
{#if symbolKey && symbols[symbolKey].icon}
|
||||
{@const SymbolIcon = symbols[symbolKey].icon}
|
||||
<SymbolIcon size="16" class="mx-1 shrink-0" />
|
||||
@@ -250,7 +242,7 @@
|
||||
<ContextMenu.Separator />
|
||||
{/if}
|
||||
{/if}
|
||||
{#if level !== ListLevel.WAYPOINTS}
|
||||
{#if level !== 'waypoints'}
|
||||
<ContextMenu.Item onclick={() => engine.selectAll()}>
|
||||
<FileStack size="16" />
|
||||
{i18n._('menu.select_all')}
|
||||
|
||||
@@ -1,300 +0,0 @@
|
||||
export enum ListLevel {
|
||||
ROOT,
|
||||
FILE,
|
||||
TRACK,
|
||||
SEGMENT,
|
||||
WAYPOINTS,
|
||||
WAYPOINT,
|
||||
}
|
||||
|
||||
export abstract class ListItem {
|
||||
[x: string]: any;
|
||||
level: ListLevel;
|
||||
|
||||
constructor(level: ListLevel) {
|
||||
this.level = level;
|
||||
}
|
||||
|
||||
abstract getId(): string | number;
|
||||
abstract getFullId(): string;
|
||||
abstract getIdAtLevel(level: ListLevel): string | number | undefined;
|
||||
abstract getFileId(): string;
|
||||
abstract getParent(): ListItem;
|
||||
abstract extend(id: string | number): ListItem;
|
||||
}
|
||||
|
||||
export class ListRootItem extends ListItem {
|
||||
constructor() {
|
||||
super(ListLevel.ROOT);
|
||||
}
|
||||
|
||||
getId(): string {
|
||||
return 'root';
|
||||
}
|
||||
|
||||
getFullId(): string {
|
||||
return 'root';
|
||||
}
|
||||
|
||||
getIdAtLevel(level: ListLevel): string | number | undefined {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
getFileId(): string {
|
||||
return '';
|
||||
}
|
||||
|
||||
getParent(): ListItem {
|
||||
return this;
|
||||
}
|
||||
|
||||
extend(id: string): ListFileItem {
|
||||
return new ListFileItem(id);
|
||||
}
|
||||
}
|
||||
|
||||
export class ListFileItem extends ListItem {
|
||||
fileId: string;
|
||||
|
||||
constructor(fileId: string) {
|
||||
super(ListLevel.FILE);
|
||||
this.fileId = fileId;
|
||||
}
|
||||
|
||||
getId(): string {
|
||||
return this.fileId;
|
||||
}
|
||||
|
||||
getFullId(): string {
|
||||
return this.fileId;
|
||||
}
|
||||
|
||||
getIdAtLevel(level: ListLevel): string | number | undefined {
|
||||
switch (level) {
|
||||
case ListLevel.ROOT:
|
||||
return this.fileId;
|
||||
default:
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
getFileId(): string {
|
||||
return this.fileId;
|
||||
}
|
||||
|
||||
getParent(): ListItem {
|
||||
return new ListRootItem();
|
||||
}
|
||||
|
||||
extend(id: number | 'waypoints'): ListTrackItem | ListWaypointsItem {
|
||||
if (id === 'waypoints') {
|
||||
return new ListWaypointsItem(this.fileId);
|
||||
} else {
|
||||
return new ListTrackItem(this.fileId, id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export class ListTrackItem extends ListItem {
|
||||
fileId: string;
|
||||
trackIndex: number;
|
||||
|
||||
constructor(fileId: string, trackIndex: number) {
|
||||
super(ListLevel.TRACK);
|
||||
this.fileId = fileId;
|
||||
this.trackIndex = trackIndex;
|
||||
}
|
||||
|
||||
getId(): number {
|
||||
return this.trackIndex;
|
||||
}
|
||||
|
||||
getFullId(): string {
|
||||
return `${this.fileId}-track-${this.trackIndex}`;
|
||||
}
|
||||
|
||||
getIdAtLevel(level: ListLevel): string | number | undefined {
|
||||
switch (level) {
|
||||
case ListLevel.ROOT:
|
||||
return this.fileId;
|
||||
case ListLevel.FILE:
|
||||
return this.trackIndex;
|
||||
default:
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
getFileId(): string {
|
||||
return this.fileId;
|
||||
}
|
||||
|
||||
getTrackIndex(): number {
|
||||
return this.trackIndex;
|
||||
}
|
||||
|
||||
getParent(): ListItem {
|
||||
return new ListFileItem(this.fileId);
|
||||
}
|
||||
|
||||
extend(id: number): ListTrackSegmentItem {
|
||||
return new ListTrackSegmentItem(this.fileId, this.trackIndex, id);
|
||||
}
|
||||
}
|
||||
|
||||
export class ListTrackSegmentItem extends ListItem {
|
||||
fileId: string;
|
||||
trackIndex: number;
|
||||
segmentIndex: number;
|
||||
|
||||
constructor(fileId: string, trackIndex: number, segmentIndex: number) {
|
||||
super(ListLevel.SEGMENT);
|
||||
this.fileId = fileId;
|
||||
this.trackIndex = trackIndex;
|
||||
this.segmentIndex = segmentIndex;
|
||||
}
|
||||
|
||||
getId(): number {
|
||||
return this.segmentIndex;
|
||||
}
|
||||
|
||||
getFullId(): string {
|
||||
return `${this.fileId}-track-${this.trackIndex}--${this.segmentIndex}`;
|
||||
}
|
||||
|
||||
getIdAtLevel(level: ListLevel): string | number | undefined {
|
||||
switch (level) {
|
||||
case ListLevel.ROOT:
|
||||
return this.fileId;
|
||||
case ListLevel.FILE:
|
||||
return this.trackIndex;
|
||||
case ListLevel.TRACK:
|
||||
return this.segmentIndex;
|
||||
default:
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
getFileId(): string {
|
||||
return this.fileId;
|
||||
}
|
||||
|
||||
getTrackIndex(): number {
|
||||
return this.trackIndex;
|
||||
}
|
||||
|
||||
getSegmentIndex(): number {
|
||||
return this.segmentIndex;
|
||||
}
|
||||
|
||||
getParent(): ListItem {
|
||||
return new ListTrackItem(this.fileId, this.trackIndex);
|
||||
}
|
||||
|
||||
extend(): ListTrackSegmentItem {
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
export class ListWaypointsItem extends ListItem {
|
||||
fileId: string;
|
||||
|
||||
constructor(fileId: string) {
|
||||
super(ListLevel.WAYPOINTS);
|
||||
this.fileId = fileId;
|
||||
}
|
||||
|
||||
getId(): string {
|
||||
return 'waypoints';
|
||||
}
|
||||
|
||||
getFullId(): string {
|
||||
return `${this.fileId}-waypoints`;
|
||||
}
|
||||
|
||||
getIdAtLevel(level: ListLevel): string | number | undefined {
|
||||
switch (level) {
|
||||
case ListLevel.ROOT:
|
||||
return this.fileId;
|
||||
case ListLevel.FILE:
|
||||
return 'waypoints';
|
||||
default:
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
getFileId(): string {
|
||||
return this.fileId;
|
||||
}
|
||||
|
||||
getParent(): ListItem {
|
||||
return new ListFileItem(this.fileId);
|
||||
}
|
||||
|
||||
extend(id: number): ListWaypointItem {
|
||||
return new ListWaypointItem(this.fileId, id);
|
||||
}
|
||||
}
|
||||
|
||||
export class ListWaypointItem extends ListItem {
|
||||
fileId: string;
|
||||
waypointIndex: number;
|
||||
|
||||
constructor(fileId: string, waypointIndex: number) {
|
||||
super(ListLevel.WAYPOINT);
|
||||
this.fileId = fileId;
|
||||
this.waypointIndex = waypointIndex;
|
||||
}
|
||||
|
||||
getId(): number {
|
||||
return this.waypointIndex;
|
||||
}
|
||||
|
||||
getFullId(): string {
|
||||
return `${this.fileId}-waypoint-${this.waypointIndex}`;
|
||||
}
|
||||
|
||||
getIdAtLevel(level: ListLevel): string | number | undefined {
|
||||
switch (level) {
|
||||
case ListLevel.ROOT:
|
||||
return this.fileId;
|
||||
case ListLevel.FILE:
|
||||
return 'waypoints';
|
||||
case ListLevel.WAYPOINTS:
|
||||
return this.waypointIndex;
|
||||
default:
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
getFileId(): string {
|
||||
return this.fileId;
|
||||
}
|
||||
|
||||
getWaypointIndex(): number {
|
||||
return this.waypointIndex;
|
||||
}
|
||||
|
||||
getParent(): ListItem {
|
||||
return new ListWaypointsItem(this.fileId);
|
||||
}
|
||||
|
||||
extend(): ListWaypointItem {
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
export function sortItems(items: ListItem[], reverse: boolean = false) {
|
||||
items.sort((a, b) => {
|
||||
if (a instanceof ListTrackItem && b instanceof ListTrackItem) {
|
||||
return a.getTrackIndex() - b.getTrackIndex();
|
||||
} else if (a instanceof ListTrackSegmentItem && b instanceof ListTrackSegmentItem) {
|
||||
return a.getSegmentIndex() - b.getSegmentIndex();
|
||||
} else if (a instanceof ListWaypointItem && b instanceof ListWaypointItem) {
|
||||
return a.getWaypointIndex() - b.getWaypointIndex();
|
||||
}
|
||||
return a.level - b.level;
|
||||
});
|
||||
if (reverse) {
|
||||
items.reverse();
|
||||
}
|
||||
}
|
||||
@@ -1,18 +1,19 @@
|
||||
import { isMac } from '$lib/utils';
|
||||
import Sortable, { type Direction } from 'sortablejs/Sortable';
|
||||
import { ListLevel } from './file-list';
|
||||
import { get, writable } from 'svelte/store';
|
||||
import { tick } from 'svelte';
|
||||
import { engine, type MoveTarget, type Selection } from '$lib/engine';
|
||||
import { isSelected, type FileTreeNode } from '$lib/selection-helpers';
|
||||
|
||||
/** What a list holds: the files, the tracks of a file, the segments of a track, or the waypoints. */
|
||||
export type ListLevel = 'file' | 'track' | 'segment' | 'waypoints' | 'waypoint';
|
||||
|
||||
export const allowedMoves: Record<ListLevel, ListLevel[]> = {
|
||||
[ListLevel.ROOT]: [],
|
||||
[ListLevel.FILE]: [ListLevel.FILE],
|
||||
[ListLevel.TRACK]: [ListLevel.FILE, ListLevel.TRACK],
|
||||
[ListLevel.SEGMENT]: [ListLevel.FILE, ListLevel.TRACK, ListLevel.SEGMENT],
|
||||
[ListLevel.WAYPOINTS]: [ListLevel.WAYPOINTS],
|
||||
[ListLevel.WAYPOINT]: [ListLevel.WAYPOINTS, ListLevel.WAYPOINT],
|
||||
file: ['file'],
|
||||
track: ['file', 'track'],
|
||||
segment: ['file', 'track', 'segment'],
|
||||
waypoints: ['waypoints'],
|
||||
waypoint: ['waypoints', 'waypoint'],
|
||||
};
|
||||
|
||||
export const dragging = writable<ListLevel | null>(null);
|
||||
@@ -84,11 +85,11 @@ export class SortableFileList {
|
||||
childNode(id: string): FileTreeNode | undefined {
|
||||
const parent = this._parent;
|
||||
switch (this._sortableLevel) {
|
||||
case ListLevel.FILE:
|
||||
case 'file':
|
||||
return { type: 'file', fileId: id };
|
||||
case ListLevel.TRACK:
|
||||
case 'track':
|
||||
return parent ? { type: 'track', fileId: parent.fileId, trackId: id } : undefined;
|
||||
case ListLevel.SEGMENT:
|
||||
case 'segment':
|
||||
return parent?.type === 'track'
|
||||
? {
|
||||
type: 'segment',
|
||||
@@ -97,9 +98,9 @@ export class SortableFileList {
|
||||
segmentId: id,
|
||||
}
|
||||
: undefined;
|
||||
case ListLevel.WAYPOINTS:
|
||||
case 'waypoints':
|
||||
return parent ? { type: 'waypoints', fileId: parent.fileId } : undefined;
|
||||
case ListLevel.WAYPOINT:
|
||||
case 'waypoint':
|
||||
return parent
|
||||
? { type: 'waypoint', fileId: parent.fileId, waypointId: id }
|
||||
: undefined;
|
||||
@@ -110,11 +111,11 @@ export class SortableFileList {
|
||||
private selectionOf(ids: string[]): Selection | undefined {
|
||||
const parent = this._parent;
|
||||
switch (this._sortableLevel) {
|
||||
case ListLevel.FILE:
|
||||
case 'file':
|
||||
return { type: 'file', fileIds: ids };
|
||||
case ListLevel.TRACK:
|
||||
case 'track':
|
||||
return parent ? { type: 'track', fileId: parent.fileId, trackIds: ids } : undefined;
|
||||
case ListLevel.SEGMENT:
|
||||
case 'segment':
|
||||
return parent?.type === 'track'
|
||||
? {
|
||||
type: 'segment',
|
||||
@@ -123,9 +124,9 @@ export class SortableFileList {
|
||||
segmentIds: ids,
|
||||
}
|
||||
: undefined;
|
||||
case ListLevel.WAYPOINTS:
|
||||
case 'waypoints':
|
||||
return parent ? { type: 'waypoints', fileId: parent.fileId } : undefined;
|
||||
case ListLevel.WAYPOINT:
|
||||
case 'waypoint':
|
||||
return parent
|
||||
? { type: 'waypoint', fileId: parent.fileId, waypointIds: ids }
|
||||
: undefined;
|
||||
@@ -136,20 +137,20 @@ export class SortableFileList {
|
||||
private targetAt(index: number): MoveTarget | undefined {
|
||||
const parent = this._parent;
|
||||
switch (this._sortableLevel) {
|
||||
case ListLevel.FILE:
|
||||
case 'file':
|
||||
return { type: 'files', index };
|
||||
case ListLevel.TRACK:
|
||||
case 'track':
|
||||
return parent ? { type: 'tracks', fileId: parent.fileId, index } : undefined;
|
||||
case ListLevel.SEGMENT:
|
||||
case 'segment':
|
||||
return parent?.type === 'track'
|
||||
? { type: 'segments', fileId: parent.fileId, trackId: parent.trackId, index }
|
||||
: undefined;
|
||||
case ListLevel.WAYPOINTS:
|
||||
case 'waypoints':
|
||||
// the list holds the node standing for the waypoints: they go after the others
|
||||
return parent
|
||||
? { type: 'waypoints', fileId: parent.fileId, index: Infinity }
|
||||
: undefined;
|
||||
case ListLevel.WAYPOINT:
|
||||
case 'waypoint':
|
||||
return parent ? { type: 'waypoints', fileId: parent.fileId, index } : undefined;
|
||||
}
|
||||
}
|
||||
@@ -161,7 +162,7 @@ export class SortableFileList {
|
||||
return;
|
||||
}
|
||||
|
||||
if (from === to && this._sortableLevel === ListLevel.FILE) {
|
||||
if (from === to && this._sortableLevel === 'file') {
|
||||
// the order of the files is the one of the list
|
||||
this.updateToFileOrder();
|
||||
return;
|
||||
@@ -169,7 +170,7 @@ export class SortableFileList {
|
||||
|
||||
// The event is triggered on the source and on the destination list: handle it once.
|
||||
// Nothing to move in the list of the single node of the waypoints.
|
||||
if (to !== this || (from === to && this._sortableLevel === ListLevel.WAYPOINTS)) {
|
||||
if (to !== this || (from === to && this._sortableLevel === 'waypoints')) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -245,19 +246,19 @@ export class SortableFileList {
|
||||
engine.select([]);
|
||||
} else {
|
||||
switch (this._sortableLevel) {
|
||||
case ListLevel.FILE:
|
||||
case 'file':
|
||||
engine.select(selected);
|
||||
break;
|
||||
case ListLevel.TRACK:
|
||||
case 'track':
|
||||
engine.selectTracks(parent!.fileId, selected);
|
||||
break;
|
||||
case ListLevel.SEGMENT:
|
||||
case 'segment':
|
||||
engine.selectSegments(parent!.fileId, (parent as any).trackId, selected);
|
||||
break;
|
||||
case ListLevel.WAYPOINTS:
|
||||
case 'waypoints':
|
||||
engine.selectWaypointGroup(parent!.fileId);
|
||||
break;
|
||||
case ListLevel.WAYPOINT:
|
||||
case 'waypoint':
|
||||
engine.selectWaypoints(parent!.fileId, selected);
|
||||
break;
|
||||
}
|
||||
@@ -266,7 +267,7 @@ export class SortableFileList {
|
||||
}
|
||||
|
||||
updateFromFileOrder() {
|
||||
if (!this._sortable || this._sortableLevel !== ListLevel.FILE) {
|
||||
if (!this._sortable || this._sortableLevel !== 'file') {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -282,7 +283,7 @@ export class SortableFileList {
|
||||
}
|
||||
|
||||
updateToFileOrder() {
|
||||
if (!this._sortable || this._sortableLevel !== ListLevel.FILE) {
|
||||
if (!this._sortable || this._sortableLevel !== 'file') {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -304,7 +305,7 @@ export class SortableFileList {
|
||||
if (element instanceof HTMLElement) {
|
||||
let attr = element.getAttribute('data-id');
|
||||
if (attr) {
|
||||
if (this._sortableLevel === ListLevel.FILE && !files.has(attr)) {
|
||||
if (this._sortableLevel === 'file' && !files.has(attr)) {
|
||||
element.remove();
|
||||
} else {
|
||||
this._elements[attr] = element;
|
||||
|
||||
Reference in new issue
Block a user