Files
gpx.studio/website/src/lib/components/file-list/FileList.ts

441 lines
14 KiB
TypeScript
Raw Normal View History

2024-07-04 02:17:50 +02:00
import { dbUtils, getFile } from "$lib/db";
2024-06-05 21:08:01 +02:00
import { castDraft, freeze } from "immer";
2024-06-20 15:18:21 +02:00
import { GPXFile, Track, TrackSegment, Waypoint } from "gpx";
2024-06-04 16:11:47 +02:00
import { selection } from "./Selection";
2024-06-05 21:08:01 +02:00
import { newGPXFile } from "$lib/stores";
2024-05-22 16:05:31 +02:00
2024-05-24 13:16:41 +02:00
export enum ListLevel {
ROOT,
FILE,
TRACK,
SEGMENT,
WAYPOINTS,
WAYPOINT
}
2024-05-22 16:05:31 +02:00
2024-06-20 15:18:21 +02:00
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]
};
export const allowedPastes: Record<ListLevel, ListLevel[]> = {
[ListLevel.ROOT]: [],
[ListLevel.FILE]: [ListLevel.ROOT, ListLevel.FILE],
[ListLevel.TRACK]: [ListLevel.ROOT, ListLevel.FILE, ListLevel.TRACK],
[ListLevel.SEGMENT]: [ListLevel.ROOT, ListLevel.FILE, ListLevel.TRACK, ListLevel.SEGMENT],
[ListLevel.WAYPOINTS]: [ListLevel.FILE, ListLevel.WAYPOINTS, ListLevel.WAYPOINT],
[ListLevel.WAYPOINT]: [ListLevel.FILE, ListLevel.WAYPOINTS, ListLevel.WAYPOINT]
};
2024-05-22 16:05:31 +02:00
export abstract class ListItem {
level: ListLevel;
constructor(level: ListLevel) {
this.level = level;
}
abstract getId(): string | number;
2024-06-11 19:08:46 +02:00
abstract getFullId(): string;
2024-05-22 16:05:31 +02:00
abstract getIdAtLevel(level: ListLevel): string | number | undefined;
abstract getFileId(): string;
2024-06-20 15:18:21 +02:00
abstract getParent(): ListItem;
2024-05-22 16:05:31 +02:00
abstract extend(id: string | number): ListItem;
}
export class ListRootItem extends ListItem {
constructor() {
2024-05-24 13:16:41 +02:00
super(ListLevel.ROOT);
2024-05-22 16:05:31 +02:00
}
getId(): string {
return 'root';
}
2024-06-11 19:08:46 +02:00
getFullId(): string {
return 'root';
}
2024-05-22 16:05:31 +02:00
getIdAtLevel(level: ListLevel): string | number | undefined {
return undefined;
}
getFileId(): string {
return '';
}
2024-06-20 15:18:21 +02:00
getParent(): ListItem {
return this;
}
2024-05-22 16:05:31 +02:00
extend(id: string): ListFileItem {
return new ListFileItem(id);
}
}
export class ListFileItem extends ListItem {
fileId: string;
constructor(fileId: string) {
2024-05-24 13:16:41 +02:00
super(ListLevel.FILE);
2024-05-22 16:05:31 +02:00
this.fileId = fileId;
}
getId(): string {
return this.fileId;
}
2024-06-11 19:08:46 +02:00
getFullId(): string {
return this.fileId;
}
2024-05-22 16:05:31 +02:00
getIdAtLevel(level: ListLevel): string | number | undefined {
switch (level) {
2024-05-24 13:16:41 +02:00
case ListLevel.ROOT:
2024-05-22 16:05:31 +02:00
return this.fileId;
default:
return undefined;
}
}
getFileId(): string {
return this.fileId;
}
2024-06-20 15:18:21 +02:00
getParent(): ListItem {
return new ListRootItem();
}
2024-05-22 16:05:31 +02:00
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) {
2024-05-24 13:16:41 +02:00
super(ListLevel.TRACK);
2024-05-22 16:05:31 +02:00
this.fileId = fileId;
this.trackIndex = trackIndex;
}
getId(): number {
return this.trackIndex;
}
2024-06-11 19:08:46 +02:00
getFullId(): string {
return `${this.fileId}-track-${this.trackIndex}`;
}
2024-05-22 16:05:31 +02:00
getIdAtLevel(level: ListLevel): string | number | undefined {
switch (level) {
2024-05-24 13:16:41 +02:00
case ListLevel.ROOT:
2024-05-22 16:05:31 +02:00
return this.fileId;
2024-05-24 13:16:41 +02:00
case ListLevel.FILE:
2024-05-22 16:05:31 +02:00
return this.trackIndex;
default:
return undefined;
}
}
getFileId(): string {
return this.fileId;
}
2024-05-23 14:44:07 +02:00
getTrackIndex(): number {
return this.trackIndex;
}
2024-06-20 15:18:21 +02:00
getParent(): ListItem {
return new ListFileItem(this.fileId);
}
2024-05-23 14:44:07 +02:00
extend(id: number): ListTrackSegmentItem {
return new ListTrackSegmentItem(this.fileId, this.trackIndex, id);
2024-05-22 16:05:31 +02:00
}
}
2024-05-23 14:44:07 +02:00
export class ListTrackSegmentItem extends ListItem {
2024-05-22 16:05:31 +02:00
fileId: string;
trackIndex: number;
segmentIndex: number;
constructor(fileId: string, trackIndex: number, segmentIndex: number) {
2024-05-24 13:16:41 +02:00
super(ListLevel.SEGMENT);
2024-05-22 16:05:31 +02:00
this.fileId = fileId;
this.trackIndex = trackIndex;
this.segmentIndex = segmentIndex;
}
getId(): number {
return this.segmentIndex;
}
2024-06-11 19:08:46 +02:00
getFullId(): string {
return `${this.fileId}-track-${this.trackIndex}--${this.segmentIndex}`;
}
2024-05-22 16:05:31 +02:00
getIdAtLevel(level: ListLevel): string | number | undefined {
switch (level) {
2024-05-24 13:16:41 +02:00
case ListLevel.ROOT:
2024-05-22 16:05:31 +02:00
return this.fileId;
2024-05-24 13:16:41 +02:00
case ListLevel.FILE:
2024-05-22 16:05:31 +02:00
return this.trackIndex;
2024-05-24 13:16:41 +02:00
case ListLevel.TRACK:
2024-05-22 16:05:31 +02:00
return this.segmentIndex;
default:
return undefined;
}
}
getFileId(): string {
return this.fileId;
}
2024-05-23 14:44:07 +02:00
getTrackIndex(): number {
return this.trackIndex;
}
getSegmentIndex(): number {
return this.segmentIndex;
}
2024-06-20 15:18:21 +02:00
getParent(): ListItem {
return new ListTrackItem(this.fileId, this.trackIndex);
}
2024-05-23 14:44:07 +02:00
extend(): ListTrackSegmentItem {
2024-05-22 16:05:31 +02:00
return this;
}
}
export class ListWaypointsItem extends ListItem {
fileId: string;
constructor(fileId: string) {
2024-05-24 13:16:41 +02:00
super(ListLevel.WAYPOINTS);
2024-05-22 16:05:31 +02:00
this.fileId = fileId;
}
getId(): string {
return 'waypoints';
}
2024-06-11 19:08:46 +02:00
getFullId(): string {
return `${this.fileId}-waypoints`;
}
2024-05-22 16:05:31 +02:00
getIdAtLevel(level: ListLevel): string | number | undefined {
switch (level) {
2024-05-24 13:16:41 +02:00
case ListLevel.ROOT:
2024-05-22 16:05:31 +02:00
return this.fileId;
2024-05-24 13:16:41 +02:00
case ListLevel.FILE:
2024-05-22 16:05:31 +02:00
return 'waypoints';
default:
return undefined;
}
}
getFileId(): string {
return this.fileId;
}
2024-06-20 15:18:21 +02:00
getParent(): ListItem {
return new ListFileItem(this.fileId);
}
2024-05-22 16:05:31 +02:00
extend(id: number): ListWaypointItem {
return new ListWaypointItem(this.fileId, id);
}
}
export class ListWaypointItem extends ListItem {
fileId: string;
waypointIndex: number;
constructor(fileId: string, waypointIndex: number) {
2024-05-24 13:16:41 +02:00
super(ListLevel.WAYPOINT);
2024-05-22 16:05:31 +02:00
this.fileId = fileId;
this.waypointIndex = waypointIndex;
}
getId(): number {
return this.waypointIndex;
}
2024-06-11 19:08:46 +02:00
getFullId(): string {
return `${this.fileId}-waypoint-${this.waypointIndex}`;
}
2024-05-22 16:05:31 +02:00
getIdAtLevel(level: ListLevel): string | number | undefined {
switch (level) {
2024-05-24 13:16:41 +02:00
case ListLevel.ROOT:
2024-05-22 16:05:31 +02:00
return this.fileId;
2024-05-24 13:16:41 +02:00
case ListLevel.FILE:
2024-05-22 16:05:31 +02:00
return 'waypoints';
2024-05-24 13:16:41 +02:00
case ListLevel.WAYPOINTS:
2024-05-22 16:05:31 +02:00
return this.waypointIndex;
default:
return undefined;
}
}
getFileId(): string {
return this.fileId;
}
2024-05-23 14:44:07 +02:00
getWaypointIndex(): number {
return this.waypointIndex;
}
2024-06-20 15:18:21 +02:00
getParent(): ListItem {
return new ListWaypointsItem(this.fileId);
}
2024-05-22 16:05:31 +02:00
extend(): ListWaypointItem {
return this;
}
}
2024-06-04 16:11:47 +02:00
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();
}
}
2024-06-20 15:18:21 +02:00
export function moveItems(fromParent: ListItem, toParent: ListItem, fromItems: ListItem[], toItems: ListItem[], remove: boolean = true) {
if (fromItems.length === 0) {
return;
}
sortItems(fromItems, remove && !(fromParent instanceof ListRootItem));
2024-06-04 16:11:47 +02:00
sortItems(toItems, false);
2024-06-20 15:18:21 +02:00
let context: (GPXFile | Track | TrackSegment | Waypoint[] | Waypoint)[] = [];
if (!remove || fromParent instanceof ListRootItem) {
fromItems.forEach((item) => {
let file = getFile(item.getFileId());
if (file) {
if (item instanceof ListFileItem) {
context.push(file.clone());
} else if (item instanceof ListTrackItem && item.getTrackIndex() < file.trk.length) {
context.push(file.trk[item.getTrackIndex()].clone());
} else if (item instanceof ListTrackSegmentItem && item.getTrackIndex() < file.trk.length && item.getSegmentIndex() < file.trk[item.getTrackIndex()].trkseg.length) {
context.push(file.trk[item.getTrackIndex()].trkseg[item.getSegmentIndex()].clone());
} else if (item instanceof ListWaypointsItem) {
context.push(file.wpt.map((wpt) => wpt.clone()));
} else if (item instanceof ListWaypointItem && item.getWaypointIndex() < file.wpt.length) {
context.push(file.wpt[item.getWaypointIndex()].clone());
}
}
});
}
let files = [fromParent.getFileId(), toParent.getFileId()];
let callbacks = [
(file, context: (GPXFile | Track | TrackSegment | Waypoint[] | Waypoint)[]) => {
2024-06-04 16:11:47 +02:00
fromItems.forEach((item) => {
if (item instanceof ListTrackItem) {
2024-07-04 02:17:50 +02:00
context.push(...file.replaceTracks(item.getTrackIndex(), item.getTrackIndex(), []));
2024-06-04 16:11:47 +02:00
} else if (item instanceof ListTrackSegmentItem) {
2024-07-04 02:17:50 +02:00
context.push(...file.replaceTrackSegments(item.getTrackIndex(), item.getSegmentIndex(), item.getSegmentIndex(), []));
2024-06-04 16:11:47 +02:00
} else if (item instanceof ListWaypointsItem) {
2024-07-04 02:17:50 +02:00
context.push(file.replaceWaypoints(0, newFile.wpt.length - 1, []));
2024-06-04 16:11:47 +02:00
} else if (item instanceof ListWaypointItem) {
2024-07-04 02:17:50 +02:00
context.push(...file.replaceWaypoints(item.getWaypointIndex(), item.getWaypointIndex(), []));
2024-06-04 16:11:47 +02:00
}
});
context.reverse();
},
2024-06-20 15:18:21 +02:00
(file, context: (GPXFile | Track | TrackSegment | Waypoint[] | Waypoint)[]) => {
2024-06-04 16:11:47 +02:00
toItems.forEach((item, i) => {
2024-06-05 21:08:01 +02:00
if (item instanceof ListTrackItem) {
if (context[i] instanceof Track) {
2024-07-04 02:17:50 +02:00
file.replaceTracks(item.getTrackIndex(), item.getTrackIndex() - 1, [context[i]]);
2024-06-05 21:08:01 +02:00
} else if (context[i] instanceof TrackSegment) {
2024-07-04 02:17:50 +02:00
file.replaceTracks(item.getTrackIndex(), item.getTrackIndex() - 1, [new Track({
2024-06-05 21:08:01 +02:00
trkseg: [context[i]]
})]);
}
2024-06-04 16:11:47 +02:00
} else if (item instanceof ListTrackSegmentItem && context[i] instanceof TrackSegment) {
2024-07-04 02:17:50 +02:00
file.replaceTrackSegments(item.getTrackIndex(), item.getSegmentIndex(), item.getSegmentIndex() - 1, [context[i]]);
2024-06-05 21:08:01 +02:00
} else if (item instanceof ListWaypointsItem) {
if (Array.isArray(context[i]) && context[i].length > 0 && context[i][0] instanceof Waypoint) {
2024-07-04 02:17:50 +02:00
file.replaceWaypoints(file.wpt.length, file.wpt.length - 1, context[i]);
2024-06-05 21:08:01 +02:00
} else if (context[i] instanceof Waypoint) {
2024-07-04 02:17:50 +02:00
file.replaceWaypoints(file.wpt.length, file.wpt.length - 1, [context[i]]);
2024-06-05 21:08:01 +02:00
}
2024-06-04 16:11:47 +02:00
} else if (item instanceof ListWaypointItem && context[i] instanceof Waypoint) {
2024-07-04 02:17:50 +02:00
file.replaceWaypoints(item.getWaypointIndex(), item.getWaypointIndex() - 1, [context[i]]);
2024-06-04 16:11:47 +02:00
}
});
}
2024-06-20 15:18:21 +02:00
];
if (fromParent instanceof ListRootItem) {
files = [];
callbacks = [];
} else if (!remove) {
files.splice(0, 1);
callbacks.splice(0, 1);
}
dbUtils.applyEachToFilesAndGlobal(files, callbacks, (files, context: (GPXFile | Track | TrackSegment | Waypoint[] | Waypoint)[]) => {
2024-06-05 21:08:01 +02:00
toItems.forEach((item, i) => {
if (item instanceof ListFileItem) {
2024-06-20 15:18:21 +02:00
if (context[i] instanceof GPXFile) {
let newFile = context[i];
if (remove) {
files.delete(newFile._data.id);
}
newFile._data.id = item.getFileId();
files.set(item.getFileId(), freeze(newFile));
} else if (context[i] instanceof Track) {
2024-06-05 21:08:01 +02:00
let newFile = newGPXFile();
newFile._data.id = item.getFileId();
if (context[i].name) {
newFile.metadata.name = context[i].name;
}
2024-07-04 02:17:50 +02:00
console.log(context[i]);
newFile.replaceTracks(0, 0, [context[i]])[0];
2024-06-05 21:08:01 +02:00
files.set(item.getFileId(), freeze(newFile));
} else if (context[i] instanceof TrackSegment) {
let newFile = newGPXFile();
newFile._data.id = item.getFileId();
2024-07-04 02:17:50 +02:00
newFile.replaceTracks(0, 0, [new Track({
2024-06-05 21:08:01 +02:00
trkseg: [context[i]]
})])[0];
files.set(item.getFileId(), freeze(newFile));
}
}
2024-06-05 17:19:03 +02:00
});
2024-06-20 15:18:21 +02:00
}, context);
2024-06-05 21:36:24 +02:00
selection.update(($selection) => {
$selection.clear();
toItems.forEach((item) => {
$selection.set(item, true);
});
return $selection;
});
2024-06-04 16:11:47 +02:00
}