diff --git a/gpx/src/gpx.ts b/gpx/src/gpx.ts index ac97f916..d5fc971f 100644 --- a/gpx/src/gpx.ts +++ b/gpx/src/gpx.ts @@ -176,43 +176,28 @@ export class GPXFile extends GPXTreeNode{ } // Producers - replaceTracks(start: number, end: number, tracks: Track[]) { - return produce(this, (draft) => { + replaceTracks(start: number, end: number, tracks: Track[]): [GPXFile, Track[]] { + let removed = []; + let result = produce(this, (draft) => { let og = getOriginal(draft); // Read as much as possible from the original object because it is faster let trk = og.trk.slice(); - trk.splice(start, end - start + 1, ...tracks); + removed = trk.splice(start, end - start + 1, ...tracks); draft.trk = freeze(trk); // Pre-freeze the array, faster as well }); + return [result, removed]; } - moveTracks(indices: number[], dest: number) { - return produce(this, (draft) => { + replaceTrackSegments(trackIndex: number, start: number, end: number, segments: TrackSegment[]): [GPXFile, TrackSegment[]] { + let removed = []; + let result = produce(this, (draft) => { let og = getOriginal(draft); // Read as much as possible from the original object because it is faster let trk = og.trk.slice(); - let tracks = indices.map((index) => trk[index]); - indices.sort((a, b) => b - a); - indices.forEach((index) => trk.splice(index, 1)); - trk.splice(dest, 0, ...tracks); - draft.trk = freeze(trk); // Pre-freeze the array, faster as well - }); - } - - replaceTrackSegments(trackIndex: number, start: number, end: number, segments: TrackSegment[]) { - return produce(this, (draft) => { - let og = getOriginal(draft); // Read as much as possible from the original object because it is faster - let trk = og.trk.slice(); - trk[trackIndex] = trk[trackIndex].replaceTrackSegments(start, end, segments); - draft.trk = freeze(trk); // Pre-freeze the array, faster as well - }); - } - - moveTrackSegments(trackIndex: number, indices: number[], dest: number) { - return produce(this, (draft) => { - let og = getOriginal(draft); // Read as much as possible from the original object because it is faster - let trk = og.trk.slice(); - trk[trackIndex] = trk[trackIndex].moveTrackSegments(indices, dest); + let [result, rmv] = trk[trackIndex].replaceTrackSegments(start, end, segments); + trk[trackIndex] = result; + removed = rmv; draft.trk = freeze(trk); // Pre-freeze the array, faster as well }); + return [result, removed]; } replaceTrackPoints(trackIndex: number, segmentIndex: number, start: number, end: number, points: TrackPoint[]) { @@ -224,25 +209,15 @@ export class GPXFile extends GPXTreeNode{ }); } - replaceWaypoints(start: number, end: number, waypoints: Waypoint[]) { - return produce(this, (draft) => { + replaceWaypoints(start: number, end: number, waypoints: Waypoint[]): [GPXFile, Waypoint[]] { + let removed = []; + let result = produce(this, (draft) => { let og = getOriginal(draft); // Read as much as possible from the original object because it is faster let wpt = og.wpt.slice(); - wpt.splice(start, end - start + 1, ...waypoints); - draft.wpt = freeze(wpt); // Pre-freeze the array, faster as well - }); - } - - moveWaypoints(indices: number[], dest: number) { - return produce(this, (draft) => { - let og = getOriginal(draft); // Read as much as possible from the original object because it is faster - let wpt = og.wpt.slice(); - let waypoints = indices.map((index) => wpt[index]); - indices.sort((a, b) => b - a); - indices.forEach((index) => wpt.splice(index, 1)); - wpt.splice(dest, 0, ...waypoints); + removed = wpt.splice(start, end - start + 1, ...waypoints); draft.wpt = freeze(wpt); // Pre-freeze the array, faster as well }); + return [result, removed]; } reverse() { @@ -350,25 +325,15 @@ export class Track extends GPXTreeNode { } // Producers - replaceTrackSegments(start: number, end: number, segments: TrackSegment[]) { - return produce(this, (draft) => { + replaceTrackSegments(start: number, end: number, segments: TrackSegment[]): [Track, TrackSegment[]] { + let removed = []; + let result = produce(this, (draft) => { let og = getOriginal(draft); // Read as much as possible from the original object because it is faster let trkseg = og.trkseg.slice(); - trkseg.splice(start, end - start + 1, ...segments); - draft.trkseg = freeze(trkseg); // Pre-freeze the array, faster as well - }); - } - - moveTrackSegments(indices: number[], dest: number) { - return produce(this, (draft) => { - let og = getOriginal(draft); // Read as much as possible from the original object because it is faster - let trkseg = og.trkseg.slice(); - let segments = indices.map((index) => trkseg[index]); - indices.sort((a, b) => b - a); - indices.forEach((index) => trkseg.splice(index, 1)); - trkseg.splice(dest, 0, ...segments); + removed = trkseg.splice(start, end - start + 1, ...segments); draft.trkseg = freeze(trkseg); // Pre-freeze the array, faster as well }); + return [result, removed]; } replaceTrackPoints(segmentIndex: number, start: number, end: number, points: TrackPoint[]) { diff --git a/website/src/lib/components/file-list/FileList.ts b/website/src/lib/components/file-list/FileList.ts index b2cb9e51..ec85675a 100644 --- a/website/src/lib/components/file-list/FileList.ts +++ b/website/src/lib/components/file-list/FileList.ts @@ -1,134 +1,8 @@ -export class SelectionTreeType { - item: ListItem; - selected: boolean; - children: { - [key: string | number]: SelectionTreeType - }; - size: number = 0; - - constructor(item: ListItem) { - this.item = item; - this.selected = false; - this.children = {}; - } - - clear() { - this.selected = false; - for (let key in this.children) { - this.children[key].clear(); - } - this.size = 0; - } - - _setOrToggle(item: ListItem, value?: boolean) { - if (item.level === this.item.level) { - let newSelected = value === undefined ? !this.selected : value; - if (this.selected !== newSelected) { - this.selected = newSelected; - this.size += this.selected ? 1 : -1; - } - } else { - let id = item.getIdAtLevel(this.item.level); - if (id !== undefined) { - if (!this.children.hasOwnProperty(id)) { - this.children[id] = new SelectionTreeType(this.item.extend(id)); - } - this.size -= this.children[id].size; - this.children[id]._setOrToggle(item, value); - this.size += this.children[id].size; - } - } - } - - set(item: ListItem, value: boolean) { - this._setOrToggle(item, value); - } - - toggle(item: ListItem) { - this._setOrToggle(item); - } - - has(item: ListItem): boolean { - if (item.level === this.item.level) { - return this.selected; - } else { - let id = item.getIdAtLevel(this.item.level); - if (id !== undefined) { - if (this.children.hasOwnProperty(id)) { - return this.children[id].has(item); - } - } - } - return false; - } - - hasAnyParent(item: ListItem, self: boolean = true): boolean { - if (this.selected && this.item.level <= item.level && (self || this.item.level < item.level)) { - return this.selected; - } - let id = item.getIdAtLevel(this.item.level); - if (id !== undefined) { - if (this.children.hasOwnProperty(id)) { - return this.children[id].hasAnyParent(item, self); - } - } - return false; - } - - hasAnyChildren(item: ListItem, self: boolean = true, ignoreIds?: (string | number)[]): boolean { - if (this.selected && this.item.level >= item.level && (self || this.item.level > item.level)) { - return this.selected; - } - let id = item.getIdAtLevel(this.item.level); - if (id !== undefined) { - if (ignoreIds === undefined || ignoreIds.indexOf(id) === -1) { - if (this.children.hasOwnProperty(id)) { - return this.children[id].hasAnyChildren(item, self, ignoreIds); - } - } - } else { - for (let key in this.children) { - if (ignoreIds === undefined || ignoreIds.indexOf(key) === -1) { - if (this.children[key].hasAnyChildren(item, self, ignoreIds)) { - return true; - } - } - } - } - return false; - } - - getSelected(selection?: ListItem[]): ListItem[] { - if (selection === undefined) { - selection = []; - } - if (this.selected) { - selection.push(this.item); - } - for (let key in this.children) { - this.children[key].getSelected(selection); - } - return selection; - } - - forEach(callback: (item: ListItem) => void) { - if (this.selected) { - callback(this.item); - } - for (let key in this.children) { - this.children[key].forEach(callback); - } - } - - getChild(id: string | number): SelectionTreeType | undefined { - return this.children[id]; - } - - deleteChild(id: string | number) { - this.size -= this.children[id].size; - delete this.children[id]; - } -}; +import { dbUtils, fileObservers } from "$lib/db"; +import { castDraft } from "immer"; +import { Track, TrackSegment, Waypoint } from "gpx"; +import { selection } from "./Selection"; +import { get } from "svelte/store"; export enum ListLevel { ROOT, @@ -363,3 +237,85 @@ export class ListWaypointItem extends ListItem { 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(); + } +} + +export function moveItems(fromParent: ListItem, toParent: ListItem, fromItems: ListItem[], toItems: ListItem[]) { + sortItems(fromItems, true); + sortItems(toItems, false); + + let toFileObserver = get(fileObservers).get(toParent.getFileId()); + let first = true; + toFileObserver?.subscribe(() => { // Update selection when the target file has been updated + if (first) first = false; + else { + selection.update(($selection) => { + $selection.clear(); + toItems.forEach((item) => { + $selection.set(item, true); + }); + return $selection; + }); + } + }); + + dbUtils.applyEachToFiles([fromParent.getFileId(), toParent.getFileId()], [ + (file, context: (Track | TrackSegment | Waypoint[] | Waypoint)[]) => { + let newFile = file; + fromItems.forEach((item) => { + if (item instanceof ListTrackItem) { + let [result, removed] = newFile.replaceTracks(item.getTrackIndex(), item.getTrackIndex(), []); + newFile = castDraft(result); + context.push(...removed); + } else if (item instanceof ListTrackSegmentItem) { + let [result, removed] = newFile.replaceTrackSegments(item.getTrackIndex(), item.getSegmentIndex(), item.getSegmentIndex(), []); + newFile = castDraft(result); + context.push(...removed); + } else if (item instanceof ListWaypointsItem) { + let [result, removed] = newFile.replaceWaypoints(0, newFile.wpt.length - 1, []); + newFile = castDraft(result); + context.push(removed); + } else if (item instanceof ListWaypointItem) { + let [result, removed] = newFile.replaceWaypoints(item.getWaypointIndex(), item.getWaypointIndex(), []); + newFile = castDraft(result); + context.push(...removed); + } + }); + context.reverse(); + return newFile; + }, + (file, context: (Track | TrackSegment | Waypoint[] | Waypoint)[]) => { + let newFile = file; + toItems.forEach((item, i) => { + if (item instanceof ListTrackItem && context[i] instanceof Track) { + let [result, _removed] = newFile.replaceTracks(item.getTrackIndex(), item.getTrackIndex() - 1, [context[i]]); + newFile = castDraft(result); + } else if (item instanceof ListTrackSegmentItem && context[i] instanceof TrackSegment) { + let [result, _removed] = newFile.replaceTrackSegments(item.getTrackIndex(), item.getSegmentIndex(), item.getSegmentIndex() - 1, [context[i]]); + newFile = castDraft(result); + } else if (item instanceof ListWaypointsItem && Array.isArray(context[i]) && context[i].length > 0 && context[i][0] instanceof Waypoint) { + let [result, _removed] = newFile.replaceWaypoints(0, -1, context[i]); + newFile = castDraft(result); + } else if (item instanceof ListWaypointItem && context[i] instanceof Waypoint) { + let [result, _removed] = newFile.replaceWaypoints(item.getWaypointIndex(), item.getWaypointIndex() - 1, [context[i]]); + newFile = castDraft(result); + } + }); + return newFile; + } + ], []); +} diff --git a/website/src/lib/components/file-list/FileListNodeContent.svelte b/website/src/lib/components/file-list/FileListNodeContent.svelte index 41156f51..f153c611 100644 --- a/website/src/lib/components/file-list/FileListNodeContent.svelte +++ b/website/src/lib/components/file-list/FileListNodeContent.svelte @@ -2,12 +2,12 @@ import { GPXFile, Track, Waypoint, type AnyGPXTreeElement, type GPXTreeElement } from 'gpx'; import { afterUpdate, getContext, onDestroy, onMount } from 'svelte'; import Sortable from 'sortablejs/Sortable'; - import { dbUtils, fileObservers, settings, type GPXFileWithStatistics } from '$lib/db'; + import { fileObservers, settings, type GPXFileWithStatistics } from '$lib/db'; import { get, type Readable } from 'svelte/store'; import FileListNodeStore from './FileListNodeStore.svelte'; import FileListNode from './FileListNode.svelte'; import FileListNodeLabel from './FileListNodeLabel.svelte'; - import { ListLevel, ListTrackItem, type ListItem } from './FileList'; + import { ListLevel, moveItems, type ListItem } from './FileList'; import { selection } from './Selection'; import { _ } from 'svelte-i18n'; @@ -123,47 +123,35 @@ } else { let fromItem = Sortable.get(e.from)._item; let toItem = Sortable.get(e.to)._item; - let oldIndices = - e.oldIndicies.length > 0 ? e.oldIndicies.map((i) => i.index) : [e.oldIndex]; - let newIndices = - e.newIndicies.length > 0 ? e.newIndicies.map((i) => i.index) : [e.newIndex]; - oldIndices.sort((a, b) => a - b); - newIndices.sort((a, b) => a - b); - let oldItems = oldIndices.map((i) => item.extend(i)); - let newItems = newIndices.map((i) => item.extend(i)); + if (item === toItem) { + // Event is triggered on source and destination list, only handle it once + let fromItems = []; + let toItems = []; - if (fromItem === toItem) { - if (sortableLevel === ListLevel.TRACK) { - dbUtils.applyToFile(item.getFileId(), (draft) => - draft.moveTracks(oldIndices, newIndices[0]) - ); - } else if (item instanceof ListTrackItem) { - dbUtils.applyToFile(item.getFileId(), (draft) => - draft.moveTrackSegments(item.getTrackIndex(), oldIndices, newIndices[0]) - ); - } else if (sortableLevel === ListLevel.WAYPOINT) { - dbUtils.applyToFile(item.getFileId(), (draft) => - draft.moveWaypoints(oldIndices, newIndices[0]) - ); + if (waypointRoot) { + fromItems = [fromItem.extend('waypoints')]; + toItems = [toItem.extend('waypoints')]; + } else { + let oldIndices = + e.oldIndicies.length > 0 ? e.oldIndicies.map((i) => i.index) : [e.oldIndex]; + let newIndices = + e.newIndicies.length > 0 ? e.newIndicies.map((i) => i.index) : [e.newIndex]; + oldIndices.sort((a, b) => a - b); + newIndices.sort((a, b) => a - b); + + fromItems = oldIndices.map((i) => fromItem.extend(i)); + toItems = newIndices.map((i) => toItem.extend(i)); } - selection.update(($selection) => { - $selection.clear(); - newItems.forEach((newItem) => { - console.log('newItem', newItem); - $selection.set(newItem, true); - }); - return $selection; - }); - } else if (item === toItem) { - // Move between lists - console.log('Move between lists'); + + moveItems(fromItem, toItem, fromItems, toItems); } } } }); Object.defineProperty(sortable, '_item', { - value: item + value: item, + writable: true }); selection.set(get(selection)); }); diff --git a/website/src/lib/components/file-list/Selection.ts b/website/src/lib/components/file-list/Selection.ts index 651f3a21..5f4e8969 100644 --- a/website/src/lib/components/file-list/Selection.ts +++ b/website/src/lib/components/file-list/Selection.ts @@ -1,7 +1,139 @@ import { get, writable } from "svelte/store"; -import { ListFileItem, ListItem, ListRootItem, ListTrackItem, ListTrackSegmentItem, ListWaypointItem, SelectionTreeType, type ListLevel } from "./FileList"; +import { ListFileItem, ListItem, ListRootItem, ListTrackItem, ListTrackSegmentItem, ListWaypointItem, type ListLevel, sortItems } from "./FileList"; import { fileObservers, settings } from "$lib/db"; +export class SelectionTreeType { + item: ListItem; + selected: boolean; + children: { + [key: string | number]: SelectionTreeType + }; + size: number = 0; + + constructor(item: ListItem) { + this.item = item; + this.selected = false; + this.children = {}; + } + + clear() { + this.selected = false; + for (let key in this.children) { + this.children[key].clear(); + } + this.size = 0; + } + + _setOrToggle(item: ListItem, value?: boolean) { + if (item.level === this.item.level) { + let newSelected = value === undefined ? !this.selected : value; + if (this.selected !== newSelected) { + this.selected = newSelected; + this.size += this.selected ? 1 : -1; + } + } else { + let id = item.getIdAtLevel(this.item.level); + if (id !== undefined) { + if (!this.children.hasOwnProperty(id)) { + this.children[id] = new SelectionTreeType(this.item.extend(id)); + } + this.size -= this.children[id].size; + this.children[id]._setOrToggle(item, value); + this.size += this.children[id].size; + } + } + } + + set(item: ListItem, value: boolean) { + this._setOrToggle(item, value); + } + + toggle(item: ListItem) { + this._setOrToggle(item); + } + + has(item: ListItem): boolean { + if (item.level === this.item.level) { + return this.selected; + } else { + let id = item.getIdAtLevel(this.item.level); + if (id !== undefined) { + if (this.children.hasOwnProperty(id)) { + return this.children[id].has(item); + } + } + } + return false; + } + + hasAnyParent(item: ListItem, self: boolean = true): boolean { + if (this.selected && this.item.level <= item.level && (self || this.item.level < item.level)) { + return this.selected; + } + let id = item.getIdAtLevel(this.item.level); + if (id !== undefined) { + if (this.children.hasOwnProperty(id)) { + return this.children[id].hasAnyParent(item, self); + } + } + return false; + } + + hasAnyChildren(item: ListItem, self: boolean = true, ignoreIds?: (string | number)[]): boolean { + if (this.selected && this.item.level >= item.level && (self || this.item.level > item.level)) { + return this.selected; + } + let id = item.getIdAtLevel(this.item.level); + if (id !== undefined) { + if (ignoreIds === undefined || ignoreIds.indexOf(id) === -1) { + if (this.children.hasOwnProperty(id)) { + return this.children[id].hasAnyChildren(item, self, ignoreIds); + } + } + } else { + for (let key in this.children) { + if (ignoreIds === undefined || ignoreIds.indexOf(key) === -1) { + if (this.children[key].hasAnyChildren(item, self, ignoreIds)) { + return true; + } + } + } + } + return false; + } + + getSelected(selection?: ListItem[]): ListItem[] { + if (selection === undefined) { + selection = []; + } + if (this.selected) { + selection.push(this.item); + } + for (let key in this.children) { + this.children[key].getSelected(selection); + } + return selection; + } + + forEach(callback: (item: ListItem) => void) { + if (this.selected) { + callback(this.item); + } + for (let key in this.children) { + this.children[key].forEach(callback); + } + } + + getChild(id: string | number): SelectionTreeType | undefined { + return this.children[id]; + } + + deleteChild(id: string | number) { + this.size -= this.children[id].size; + delete this.children[id]; + } +}; + export const selection = writable(new SelectionTreeType(new ListRootItem())); export function selectItem(item: ListItem) { @@ -80,19 +212,7 @@ export function applyToOrderedSelectedItemsFromFile(callback: (fileId: string, l }); if (items.length > 0) { - if (reverse) { - items.sort((a, b) => { // Process the items in reverse order to avoid index conflicts - if (a instanceof ListTrackItem && b instanceof ListTrackItem) { - return b.getTrackIndex() - a.getTrackIndex(); - } else if (a instanceof ListTrackSegmentItem && b instanceof ListTrackSegmentItem) { - return b.getSegmentIndex() - a.getSegmentIndex(); - } else if (a instanceof ListWaypointItem && b instanceof ListWaypointItem) { - return b.getWaypointIndex() - a.getWaypointIndex(); - } - return b.level - a.level; - }); - } - + sortItems(items, reverse); callback(fileId, level, items); } }); diff --git a/website/src/lib/db.ts b/website/src/lib/db.ts index 3ab7d7d1..6985d5df 100644 --- a/website/src/lib/db.ts +++ b/website/src/lib/db.ts @@ -291,6 +291,22 @@ function applyToFiles(fileIds: string[], callback: (file: WritableDraft return commitFileStateChange(newFileState, patch); } +// Helper function to apply different callbacks to multiple files +function applyEachToFiles(fileIds: string[], callbacks: ((file: WritableDraft, context?: any) => GPXFile)[], context?: any) { + const [newFileState, patch, inversePatch] = produceWithPatches(fileState, (draft) => { + fileIds.forEach((fileId, index) => { + let file = draft.get(fileId); + if (file) { + draft.set(fileId, castDraft(callbacks[index](file, context))); + } + }); + }); + + storePatches(patch, inversePatch); + + return commitFileStateChange(newFileState, patch); +} + const MAX_PATCHES = 100; // Store the new patches in the database async function storePatches(patch: Patch[], inversePatch: Patch[]) { @@ -362,6 +378,9 @@ export const dbUtils = { applyToFiles: (ids: string[], callback: (file: WritableDraft) => GPXFile) => { applyToFiles(ids, callback); }, + applyEachToFiles: (ids: string[], callbacks: ((file: WritableDraft, context?: any) => GPXFile)[], context?: any) => { + applyEachToFiles(ids, callbacks, context); + }, applyToSelection: (callback: (file: WritableDraft) => AnyGPXTreeElement) => { if (get(selection).size === 0) { return; @@ -408,18 +427,21 @@ export const dbUtils = { } else if (level === ListLevel.TRACK) { for (let item of items) { let trackIndex = (item as ListTrackItem).getTrackIndex(); - newFile = newFile.replaceTracks(trackIndex + 1, trackIndex, [file.trk[trackIndex].clone()]); + let [result, _removed] = newFile.replaceTracks(trackIndex + 1, trackIndex, [file.trk[trackIndex].clone()]); + newFile = result; } } else if (level === ListLevel.SEGMENT) { for (let item of items) { let trackIndex = (item as ListTrackSegmentItem).getTrackIndex(); let segmentIndex = (item as ListTrackSegmentItem).getSegmentIndex(); - newFile = newFile.replaceTrackSegments(trackIndex, segmentIndex + 1, segmentIndex, [file.trk[trackIndex].trkseg[segmentIndex].clone()]); + let [result, _removed] = newFile.replaceTrackSegments(trackIndex, segmentIndex + 1, segmentIndex, [file.trk[trackIndex].trkseg[segmentIndex].clone()]); + newFile = result; } } else if (level === ListLevel.WAYPOINT) { for (let item of items) { let waypointIndex = (item as ListWaypointItem).getWaypointIndex(); - newFile = newFile.replaceWaypoints(waypointIndex + 1, waypointIndex, [file.wpt[waypointIndex].clone()]); + let [result, _removed] = newFile.replaceWaypoints(waypointIndex + 1, waypointIndex, [file.wpt[waypointIndex].clone()]); + newFile = result; } } draft.set(newFile._data.id, freeze(newFile)); @@ -470,20 +492,24 @@ export const dbUtils = { if (level === ListLevel.TRACK) { for (let item of items) { let trackIndex = (item as ListTrackItem).getTrackIndex(); - newFile = newFile.replaceTracks(trackIndex, trackIndex, []); + let [result, _removed] = newFile.replaceTracks(trackIndex, trackIndex, []); + newFile = result; } } else if (level === ListLevel.SEGMENT) { for (let item of items) { let trackIndex = (item as ListTrackSegmentItem).getTrackIndex(); let segmentIndex = (item as ListTrackSegmentItem).getSegmentIndex(); - newFile = newFile.replaceTrackSegments(trackIndex, segmentIndex, segmentIndex, []); + let [result, _removed] = newFile.replaceTrackSegments(trackIndex, segmentIndex, segmentIndex, []); + newFile = result; } } else if (level === ListLevel.WAYPOINTS) { - newFile = newFile.replaceWaypoints(0, newFile.wpt.length - 1, []); + let [result, _removed] = newFile.replaceWaypoints(0, newFile.wpt.length - 1, []); + newFile = result; } else if (level === ListLevel.WAYPOINT) { for (let item of items) { let waypointIndex = (item as ListWaypointItem).getWaypointIndex(); - newFile = newFile.replaceWaypoints(waypointIndex, waypointIndex, []); + let [result, _removed] = newFile.replaceWaypoints(waypointIndex, waypointIndex, []); + newFile = result; } } draft.set(newFile._data.id, freeze(newFile));