routes continued

This commit is contained in:
vcoppe committed 2026-10-28 18:14:08 +01:00
1 parent 603b73b99d
commit 68eacc76d1
20 files changed
+307 -85

No files matched your search

-49
View File
@@ -1,49 +0,0 @@
import { readable } from 'svelte/store';
import { engine, FileStateCollectionObserver, type FileState, type Selection } from '$lib/engine';
import { isSelectionHidden } from '$lib/engine/selection-helpers';
/**
* Whether all the selected elements are hidden. It follows the selection and, through the
* observer of the files, the state of every file (hiding, editing).
*/
export const allHidden = readable(false, (set) => {
let selection: Selection = { type: 'empty' };
const states = new Map<string, FileState>();
const unsubscribes = new Map<string, () => void>();
const update = () => set(isSelectionHidden(selection, states));
const unsubscribeSelection = engine.selection.subscribe(($selection) => {
selection = $selection;
update();
});
const observer = new FileStateCollectionObserver(
(files) => {
files.forEach((store, fileId) => {
unsubscribes.set(
fileId,
store.subscribe(($file) => {
states.set(fileId, $file);
update();
})
);
});
},
(fileId) => {
unsubscribes.get(fileId)?.();
unsubscribes.delete(fileId);
states.delete(fileId);
update();
},
() => {
unsubscribes.forEach((unsubscribe) => unsubscribe());
unsubscribes.clear();
states.clear();
}
);
return () => {
unsubscribeSelection();
observer.destroy();
};
});
+2 -1
View File
@@ -374,7 +374,8 @@ class Engine {
/**
* Converts the selected routes to tracks, or the selected tracks or segments to routes (one
* per segment), which are selected. Resolves to `false` when there is nothing to convert.
* per segment), which are selected. Selected files with routes only (or tracks only) have all
* of them converted. Resolves to `false` when there is nothing to convert.
*/
convert() {
return this.run((b) => b.convert());
@@ -5,6 +5,7 @@ import type { VisibilityState } from '$lib/engine/file-visibility';
import {
coversSegments,
elementId,
fileConversion,
hasSelectionWithin,
isCovered,
isInClipboard,
@@ -274,4 +275,41 @@ describe('selection helpers', () => {
assert.equal(isSelectionHidden(files('F1', 'F2'), states('F1')), false);
});
});
describe('fileConversion', () => {
const segment = (id: string) => ({ id, rev: id, length: 2 });
const structure = (routes: string[], tracks: string[]): { structure: FileStructure } => ({
structure: {
id: 'F',
name: 'file',
waypointsRev: 'rev',
waypoints: [],
routes: routes.map((id) => ({ id, segment: segment(`${id}-S`) })),
tracks: tracks.map((id) => ({ id, segments: [segment(`${id}-S`)] })),
},
});
const states = new Map([
['routes', structure(['R1', 'R2'], [])],
['tracks', structure([], ['T1'])],
['both', structure(['R1'], ['T1'])],
['none', structure([], [])],
]);
it('converts files with tracks only to routes, and routes only to tracks', () => {
assert.equal(fileConversion(files('tracks'), states), 'route');
assert.equal(fileConversion(files('routes'), states), 'track');
});
it('does not convert files with both, neither, or a mix of the two', () => {
assert.equal(fileConversion(files('both'), states), undefined);
assert.equal(fileConversion(files('none'), states), undefined);
assert.equal(fileConversion(files('routes', 'tracks'), states), undefined);
assert.equal(fileConversion(files('unknown'), states), undefined);
});
it('only converts files', () => {
assert.equal(fileConversion(empty, states), undefined);
assert.equal(fileConversion(tracks('T1'), states), undefined);
});
});
});
@@ -247,3 +247,34 @@ export function isSelectionHidden(
})
);
}
/**
* What the selected files convert to: `'route'` when they all have tracks only (with segments),
* `'track'` when they all have routes only, `undefined` otherwise (other selections, files with
* both or neither, or a mix of the two). `states` holds the files by id.
*/
export function fileConversion(
selection: Selection,
states: ReadonlyMap<string, { structure: FileStructure }>
): 'route' | 'track' | undefined {
if (selection.type !== 'file' || selection.fileIds.length === 0) {
return undefined;
}
const targets = new Set(
selection.fileIds.map((fileId) => {
const structure = states.get(fileId)?.structure;
if (structure === undefined) {
return undefined;
} else if (structure.routes.length > 0 && structure.tracks.length === 0) {
return 'track' as const;
} else if (
structure.routes.length === 0 &&
structure.tracks.some((track) => track.segments.length > 0)
) {
return 'route' as const;
}
return undefined;
})
);
return targets.size === 1 ? targets.values().next().value : undefined;
}
@@ -0,0 +1,63 @@
import { readable, type Readable } from 'svelte/store';
import { engine, FileStateCollectionObserver, type FileState, type Selection } from '$lib/engine';
import { fileConversion, isSelectionHidden } from '$lib/engine/selection-helpers';
/**
* A value computed from the selection and the states of the files. It follows the selection and,
* through the observer of the files, the state of every file (hiding, editing).
*/
function fromSelectionAndFiles<T>(
initial: T,
compute: (selection: Selection, states: ReadonlyMap<string, FileState>) => T
): Readable<T> {
return readable(initial, (set) => {
let selection: Selection = { type: 'empty' };
const states = new Map<string, FileState>();
const unsubscribes = new Map<string, () => void>();
const update = () => set(compute(selection, states));
const unsubscribeSelection = engine.selection.subscribe(($selection) => {
selection = $selection;
update();
});
const observer = new FileStateCollectionObserver(
(files) => {
files.forEach((store, fileId) => {
unsubscribes.set(
fileId,
store.subscribe(($file) => {
states.set(fileId, $file);
update();
})
);
});
},
(fileId) => {
unsubscribes.get(fileId)?.();
unsubscribes.delete(fileId);
states.delete(fileId);
update();
},
() => {
unsubscribes.forEach((unsubscribe) => unsubscribe());
unsubscribes.clear();
states.clear();
}
);
return () => {
unsubscribeSelection();
observer.destroy();
};
});
}
/** Whether all the selected elements are hidden. */
export const allHidden = fromSelectionAndFiles(false, isSelectionHidden);
/** What the selected files convert to, see `fileConversion`. */
export const selectedFilesConversion = fromSelectionAndFiles<'route' | 'track' | undefined>(
undefined,
fileConversion
);