Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Make the route loading more obvious. #260

Merged
merged 9 commits into from
Jul 12, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
72 changes: 63 additions & 9 deletions src/lib/draw/route/RouteMode.svelte
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
<script lang="ts">
import type { LineString } from "geojson";
import init from "route-snapper";
import { fetchWithProgress } from "route-snapper/lib.js";
import { onMount } from "svelte";
import { onMount, tick } from "svelte";
import type { FeatureWithProps } from "../../../maplibre_helpers";
import { currentMode, map } from "../../../stores";
import type { Mode } from "../../../types";
Expand All @@ -16,10 +15,14 @@
export let changeMode: (m: Mode) => void;
export let url: string;

let progress: HTMLDivElement;
export let routeTool: RouteTool;
export let eventHandler: EventHandler;

let progress: number = 0;
let routeToolReady = false;
$: downloadComplete = progress >= 100;
let failedToLoadRouteTool = false;

// While the new feature is being drawn, remember its last valid version
let unsavedFeature: { value: FeatureWithProps<LineString> | null } = {
value: null,
Expand All @@ -45,11 +48,15 @@

console.log(`Grabbing ${url}`);
try {
const graphBytes = await fetchWithProgress(url, progress);
routeTool = new RouteTool($map, graphBytes);
const graphBytes = await fetchWithProgress(
url,
(percentLoaded) => (progress = percentLoaded)
);
routeTool = new RouteTool($map, graphBytes, routeToolInitialised);
} catch (err) {
console.log(`Route tool broke: ${err}`);
progress.innerHTML = "Failed to load";
failedToLoadRouteTool = true;

return;
}

Expand All @@ -61,11 +68,58 @@
changeMode
);
});

async function fetchWithProgress(url, setProgress: (number) => void) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
async function fetchWithProgress(url, setProgress: (number) => void) {
// setProgress will generally be called with a number between 0 and 100, but it may exceed this for gzipped files.
// The server must send back a Content-Length header.
async function fetchWithProgress(url: string, setProgress: (number) => void) {

const response = await fetch(url);
const reader = response.body.getReader();

const contentLength = parseInt(response.headers.get("Content-Length"));

let receivedLength = 0;
let chunks = [];
while (true) {
const { done, value } = await reader.read();
if (done) {
break;
}

chunks.push(value);
receivedLength += value.length;

const percent = (100.0 * receivedLength) / contentLength;
setProgress(percent);
}

let allChunks = new Uint8Array(receivedLength);
let position = 0;
for (let chunk of chunks) {
allChunks.set(chunk, position);
position += chunk.length;
}

return allChunks;
}

function routeToolInitialised() {
progress = 100;
routeToolReady = true;
}
</script>

{#if !routeTool}
<!-- TODO the text should be fixed, and the progress bar float -->
<div bind:this={progress}>Route tool loading...</div>
{#if !routeToolReady && !failedToLoadRouteTool && !downloadComplete}
<label for="route-loading">Route tool loading</label>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Elsewhere we've been doing <label>Route tool loading <progress value={progress} />, the second form mentioned by https://developer.mozilla.org/en-US/docs/Web/HTML/Element/label, to avoid making up an ID. Not too opinionated about it here

<progress id="route-loading" value={progress} />
{:else if downloadComplete && !routeToolReady && !failedToLoadRouteTool}
<label for="route-unpacking">Route data unpacking</label>
<progress id="route-unpacking" />
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I really like the default animation of bouncing back and forth!

So the strategy is:

  1. Show a 0-100 progress bar while we're downloading, up to the Content-Length of the .gzip file
  2. We'll exceed 100%, because the browser is unpacking and passing along more than that Content-Length (and we have no way of knowing the uncompressed size, unless we store and send back a second header somehow)
  3. As soon as we hit 100%, we switch to the bouncey animation and say unpacking. We might still be loading from the network for some of this, but it's fine.

{:else if failedToLoadRouteTool}
<p>Failed to load</p>
{:else if $currentMode == thisMode}
<RouteControls {routeTool} extendRoute />
{/if}

<style>
progress {
width: 100%;
}
</style>
7 changes: 6 additions & 1 deletion src/lib/draw/route/route_tool.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,11 @@ export class RouteTool {
) => void)[];
eventListenersFailure: (() => void)[];

constructor(map: Map, graphBytes: Uint8Array) {
constructor(
map: Map,
graphBytes: Uint8Array,
initialisedCallback: () => { void }
) {
this.map = map;
console.time("Deserialize and setup JsRouteSnapper");
this.inner = new JsRouteSnapper(graphBytes);
Expand Down Expand Up @@ -81,6 +85,7 @@ export class RouteTool {
color: "black",
opacity: 0.5,
});
initialisedCallback();
}

onMouseMove = (e: MapMouseEvent) => {
Expand Down