mirror of
https://github.com/gpxstudio/gpx.studio.git
synced 2026-11-01 20:19:39 +00:00
Migration to new Rust-based engine (#401)
* wasm first steps * parsing start * parsing continued * progress * progress * progress * progress * progress * progress * progress * progress * fixes * reorganize * change type * fix * progress * refactoring * progress * progress * progress * keep style info * progress * progress * remove (copy) in name * progress * merge methods into 1 * splice * progress * progress * progress * cleaning * progress * progress * progress * visibility * progress * progress * progress * progress * progress * fixes * progress * fix markers * fix * progress * progress * progress * progress * progress * progress * small fix * use engine info * fix popups * small fix * progress * progress * progress * clean tool * handle self closing elements * reduce data exchanges across boundary * copy on wasm side to avoid any data corruption * speed fix * routing * refactoring * reverse * reverse * crop * split * merge * progress * time tool * fix bounds for waypoints * reduce * fix reactivity * handle corrupt time data * export * progress * remove gpx js package * remove todo * update readme * cleaning * progress * fix link fields * remove unused fields * refactoring and tests * import and export of routes, ref #192 * per-chunk categories storage * cross-tab sync * outdated state * refactoring * reorder methods * refactoring * fix * improve flow * renaming * progress * progress * fix * progress * per file history * keep original flag * use history variant * fix * progress * file explorer * progress * fix * sticky folders * improved sticky folders * fix sortable * fix sortable * fix * fix tab size * no file delete on desktop * fix routing on other file * fix sorting * remember language * open links in browser * commit missing changes * avoid selecting on resize * clarify filename * explorer actions * fix folder styling * drop anywhere in folder * cleaning * fixes * remove focus-ring * full screen * small fix * consistent highlight color * improved preview * performance improvements * sparser data transfers * sparser data transfers * show popup once data is loaded * fix routing control size * fix * sparse desktop build * remove custom ign styles * use new endpoint for openhikingmap * strip native menu * reorder context menu items * route full support * route - track conversion * create route file action * route export, filter out anchors at high zoom level * clarify name handling * move hide & center to view * routes continued * update docs * fix power format, closes #378 * lint fixes * fix display of additional data with gaps, closes #361 * fix linting issues * fix import modules * fix multidrag * change base to resolve * fix type errors * fix layer typings * fix last type error * each keys * improve typings * deploy to cloudflare --------- Co-authored-by: vcoppe <vcoppe@hexaly.com>
This commit is contained in:
1 parent
6a0d434371
commit
8fe7d8d64d
484 files changed
+53949
-41152
No files matched your search
@@ -0,0 +1,175 @@
|
||||
import type { FileStructure } from 'gpx-wasm';
|
||||
|
||||
/**
|
||||
* Visibility the user set explicitly, by element id (file, route, track, segment, waypoint, or the
|
||||
* waypoints of the file as a whole, see `waypointsKey`): `false` hides, `true` shows. An element
|
||||
* without entry follows its parent, and everything is shown by default.
|
||||
*/
|
||||
export type Visibility = ReadonlyMap<string, boolean>;
|
||||
|
||||
/** What is needed to tell whether the elements of a file are hidden (a `FileState` fits). */
|
||||
export type VisibilityState = { structure: FileStructure; visibility: Visibility };
|
||||
|
||||
/** Id to hide or show all the waypoints of a file at once. */
|
||||
export function waypointsKey(fileId: string): string {
|
||||
return `${fileId}:waypoints`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Ids from the file down to `id` in the file tree (file, routes, tracks, segments / waypoints
|
||||
* group).
|
||||
*/
|
||||
function pathTo(structure: FileStructure, id: string): string[] {
|
||||
const fileId = structure.id;
|
||||
if (id === fileId) {
|
||||
return [fileId];
|
||||
}
|
||||
if (id === waypointsKey(fileId)) {
|
||||
return [fileId, id];
|
||||
}
|
||||
if (structure.waypoints.some((waypoint) => waypoint.id === id)) {
|
||||
return [fileId, waypointsKey(fileId), id];
|
||||
}
|
||||
if (structure.routes.some((route) => route.id === id)) {
|
||||
return [fileId, id];
|
||||
}
|
||||
for (const track of structure.tracks) {
|
||||
if (track.id === id) {
|
||||
return [fileId, id];
|
||||
}
|
||||
if (track.segments.some((segment) => segment.id === id)) {
|
||||
return [fileId, track.id, id];
|
||||
}
|
||||
}
|
||||
return [];
|
||||
}
|
||||
|
||||
function children(structure: FileStructure, id: string): string[] {
|
||||
if (id === structure.id) {
|
||||
return [
|
||||
waypointsKey(id),
|
||||
...structure.routes.map((route) => route.id),
|
||||
...structure.tracks.map((track) => track.id),
|
||||
];
|
||||
}
|
||||
if (id === waypointsKey(structure.id)) {
|
||||
return structure.waypoints.map((waypoint) => waypoint.id);
|
||||
}
|
||||
return structure.tracks.find((track) => track.id === id)?.segments.map((s) => s.id) ?? [];
|
||||
}
|
||||
|
||||
function descendants(structure: FileStructure, id: string): string[] {
|
||||
return children(structure, id).flatMap((child) => [child, ...descendants(structure, child)]);
|
||||
}
|
||||
|
||||
/** The closest explicit visibility on a path (from the file down): whether it is hidden. */
|
||||
function followsHidden(visibility: Visibility, path: string[]): boolean {
|
||||
for (let i = path.length - 1; i >= 0; i--) {
|
||||
const shown = visibility.get(path[i]);
|
||||
if (shown !== undefined) {
|
||||
return !shown;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether an element is hidden: its closest explicit ancestor (or itself) is hidden, and none
|
||||
* of its descendants was shown. A file whose only visible segment was shown on its own is not
|
||||
* hidden, its other segments are. Unknown ids are not hidden.
|
||||
*/
|
||||
export function isHidden({ structure, visibility }: VisibilityState, id: string): boolean {
|
||||
return (
|
||||
followsHidden(visibility, pathTo(structure, id)) &&
|
||||
!descendants(structure, id).some((descendant) => visibility.get(descendant) === true)
|
||||
);
|
||||
}
|
||||
|
||||
// Leaf elements have no descendants: no need to look at the structure.
|
||||
|
||||
/** Whether a segment is hidden: `parentId` is its route (which is a leaf), or its track. */
|
||||
export function isSegmentHidden(
|
||||
{ structure, visibility }: VisibilityState,
|
||||
parentId: string,
|
||||
segmentId: string
|
||||
): boolean {
|
||||
return followsHidden(visibility, [structure.id, parentId, segmentId]);
|
||||
}
|
||||
|
||||
export function isWaypointHidden(
|
||||
{ structure, visibility }: VisibilityState,
|
||||
waypointId: string
|
||||
): boolean {
|
||||
return followsHidden(visibility, [structure.id, waypointsKey(structure.id), waypointId]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Hides or shows elements of a file, and returns the new visibility. What was set explicitly
|
||||
* below these elements is reset, so that hiding a file then showing one of its segments leaves
|
||||
* only that segment visible, and hiding the file again hides all of it.
|
||||
*/
|
||||
export function setHidden(
|
||||
structure: FileStructure,
|
||||
visibility: Visibility,
|
||||
ids: string[],
|
||||
hidden: boolean
|
||||
): Visibility {
|
||||
const next = new Map(visibility);
|
||||
ids.forEach((id) => {
|
||||
descendants(structure, id).forEach((child) => next.delete(child));
|
||||
next.set(id, !hidden);
|
||||
});
|
||||
return next;
|
||||
}
|
||||
|
||||
/** Name under which the visibility of the files is kept in the storage. */
|
||||
export const VISIBILITY_KEY = 'visibility';
|
||||
|
||||
/**
|
||||
* The visibility of the files as JSON, to keep it: the explicit visibility of each file by id,
|
||||
* leaving out the files that `keep` refuses (the ones that are gone) and the ones that have none.
|
||||
* `undefined` when there is nothing to keep.
|
||||
*/
|
||||
export function stringifyVisibility(
|
||||
files: ReadonlyMap<string, Visibility>,
|
||||
keep: (fileId: string) => boolean
|
||||
): string | undefined {
|
||||
const data: Record<string, Record<string, boolean>> = {};
|
||||
files.forEach((visibility, fileId) => {
|
||||
if (visibility.size > 0 && keep(fileId)) {
|
||||
data[fileId] = Object.fromEntries(visibility);
|
||||
}
|
||||
});
|
||||
return Object.keys(data).length > 0 ? JSON.stringify(data) : undefined;
|
||||
}
|
||||
|
||||
/** What `stringifyVisibility` gave back. What does not make sense is left out. */
|
||||
export function parseVisibility(json: string | undefined): Map<string, Visibility> {
|
||||
const files = new Map<string, Visibility>();
|
||||
if (json === undefined) {
|
||||
return files;
|
||||
}
|
||||
try {
|
||||
const data: unknown = JSON.parse(json);
|
||||
if (typeof data !== 'object' || data === null) {
|
||||
return files;
|
||||
}
|
||||
for (const [fileId, entries] of Object.entries(data)) {
|
||||
if (typeof entries !== 'object' || entries === null) {
|
||||
continue;
|
||||
}
|
||||
const visibility = new Map<string, boolean>();
|
||||
for (const [id, shown] of Object.entries(entries)) {
|
||||
if (typeof shown === 'boolean') {
|
||||
visibility.set(id, shown);
|
||||
}
|
||||
}
|
||||
if (visibility.size > 0) {
|
||||
files.set(fileId, visibility);
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
// not something that was kept
|
||||
}
|
||||
return files;
|
||||
}
|
||||
Reference in new issue
Block a user