more work on print view
This commit is contained in:
@@ -16,9 +16,7 @@
|
||||
<title>{$trail.name} | {$_("map")} | wanderer</title>
|
||||
</svelte:head>
|
||||
<main class="grid grid-cols-1 md:grid-cols-[458px_1fr] gap-x-1 gap-y-4">
|
||||
<div
|
||||
id="trail-details"
|
||||
>
|
||||
<div id="trail-details">
|
||||
<TrailInfoPanel trail={$trail} {markers}></TrailInfoPanel>
|
||||
</div>
|
||||
<MapWithElevation trail={$trail} bind:markers></MapWithElevation>
|
||||
|
||||
262
web/src/routes/map/trail/[id]/print/+page.svelte
Normal file
262
web/src/routes/map/trail/[id]/print/+page.svelte
Normal file
@@ -0,0 +1,262 @@
|
||||
<script lang="ts">
|
||||
import { page } from "$app/stores";
|
||||
import Button from "$lib/components/base/button.svelte";
|
||||
import Select from "$lib/components/base/select.svelte";
|
||||
import MapWithElevation from "$lib/components/trail/map_with_elevation.svelte";
|
||||
import { trail } from "$lib/stores/trail_store";
|
||||
import {
|
||||
calculatePixelPerMeter,
|
||||
calculateScaleFactor,
|
||||
} from "$lib/util/leaflet_util";
|
||||
import { createRect, createText } from "$lib/util/svg_util";
|
||||
import "$lib/vendor/leaflet-elevation/src/index.css";
|
||||
import leafletImage from "$lib/vendor/leaflet-image/leaflet-image.js";
|
||||
import type { Map } from "leaflet";
|
||||
import "leaflet.awesome-markers/dist/leaflet.awesome-markers.css";
|
||||
import "leaflet/dist/leaflet.css";
|
||||
import QrCodeWithLogo from "qrcode-with-logos";
|
||||
import { onMount, tick } from "svelte";
|
||||
import { currentUser } from "$lib/stores/user_store";
|
||||
import {
|
||||
formatDistance,
|
||||
formatElevation,
|
||||
formatTimeHHMM,
|
||||
} from "$lib/util/format_util";
|
||||
import LogoTextLight from "$lib/components/logo/logo_text_light.svelte";
|
||||
import LogoText from "$lib/components/logo/logo_text.svelte";
|
||||
|
||||
let map: Map;
|
||||
|
||||
const paperSizes: { text: string; value: keyof typeof paperDimensions }[] =
|
||||
[
|
||||
{ text: "A4", value: "a4" },
|
||||
{ text: "Letter", value: "letter" },
|
||||
];
|
||||
const paperDimensions = {
|
||||
a4: {
|
||||
width: "21cm",
|
||||
height: "29.7cm",
|
||||
},
|
||||
letter: {
|
||||
width: "21.59cm",
|
||||
height: "27.94cm",
|
||||
},
|
||||
};
|
||||
let selectedPaperSize = paperSizes[0].value;
|
||||
|
||||
const orientations: { text: string; value: "portrait" | "landscape" }[] = [
|
||||
{ text: "Portrait", value: "portrait" },
|
||||
{ text: "Landscape", value: "landscape" },
|
||||
];
|
||||
let selectedOrientation: "portrait" | "landscape" = orientations[0].value;
|
||||
|
||||
let scale = 1;
|
||||
|
||||
onMount(() => {
|
||||
let qrcode = new QrCodeWithLogo({
|
||||
content: $page.url.href.replace("/print", ""),
|
||||
image: document.getElementById("qrcode") as HTMLImageElement,
|
||||
logo: {
|
||||
src: "/favicon.png",
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
function print() {
|
||||
leafletImage(map, function (err: string, canvas: HTMLCanvasElement) {
|
||||
var img = document.createElement("img");
|
||||
var dimensions = map.getSize();
|
||||
img.width = dimensions.x;
|
||||
img.height = dimensions.y;
|
||||
img.src = canvas.toDataURL();
|
||||
|
||||
document.getElementById("images")!.innerHTML = "";
|
||||
document.getElementById("images")!.appendChild(img);
|
||||
});
|
||||
}
|
||||
|
||||
async function updateMapSize() {
|
||||
if (!map) {
|
||||
return;
|
||||
}
|
||||
await tick();
|
||||
map.invalidateSize();
|
||||
updateScale(map);
|
||||
}
|
||||
|
||||
async function updateScale(map: Map) {
|
||||
const svg = document.getElementById("ruler") as HTMLElement;
|
||||
if (!svg) {
|
||||
return;
|
||||
}
|
||||
const svgRect = svg.getBoundingClientRect();
|
||||
const height = svgRect.height;
|
||||
const width = svgRect.width;
|
||||
|
||||
svg.replaceChildren();
|
||||
|
||||
let oneUnitInPixels = calculatePixelPerMeter(
|
||||
map,
|
||||
$currentUser && $currentUser.unit == "imperial" ? 1609.34 : 1000,
|
||||
);
|
||||
let unitsInRuler = width / oneUnitInPixels;
|
||||
|
||||
function createRulerSegment(
|
||||
x: number,
|
||||
width: number,
|
||||
label: string,
|
||||
fill: string,
|
||||
) {
|
||||
const bar = createRect(x, height / 2, width - 4, 6, fill);
|
||||
const tick = createRect(x + width - 4, height / 2, 4, 6, "#000");
|
||||
const text = createText(
|
||||
label,
|
||||
x + width - 5 * label.length,
|
||||
height / 2 + 20,
|
||||
"0.75rem",
|
||||
);
|
||||
svg.appendChild(bar);
|
||||
svg.appendChild(tick);
|
||||
svg.append(text);
|
||||
}
|
||||
let multiplier = 1;
|
||||
|
||||
if (unitsInRuler > 500) {
|
||||
multiplier = 250;
|
||||
} else if (unitsInRuler > 150) {
|
||||
multiplier = 50;
|
||||
} else if (unitsInRuler > 25) {
|
||||
multiplier = 10;
|
||||
} else if (unitsInRuler > 15) {
|
||||
multiplier = 5;
|
||||
} else if (unitsInRuler > 5) {
|
||||
multiplier = 2;
|
||||
} else if (unitsInRuler > 1.5) {
|
||||
multiplier = 1;
|
||||
} else if (unitsInRuler > 1) {
|
||||
multiplier = 0.5;
|
||||
} else if (unitsInRuler > 0.2) {
|
||||
multiplier = 0.1;
|
||||
} else if (unitsInRuler > 0) {
|
||||
multiplier = 0.05;
|
||||
}
|
||||
const segmentCount = Math.floor(unitsInRuler / multiplier);
|
||||
|
||||
svg.appendChild(createText("0", 0, height / 2 + 20, "0.75rem"));
|
||||
for (let i = 0; i < segmentCount; i++) {
|
||||
createRulerSegment(
|
||||
oneUnitInPixels * i * multiplier,
|
||||
oneUnitInPixels * multiplier,
|
||||
`${multiplier < 1 ? ((i + 1) * multiplier).toFixed(1) : (i + 1) * multiplier}`,
|
||||
i % 2 == 0 ? "#7c7c7c" : "#5d5d5d",
|
||||
);
|
||||
}
|
||||
|
||||
svg.appendChild(
|
||||
createText(
|
||||
$currentUser && $currentUser.unit == "imperial" ? "MI" : "KM",
|
||||
segmentCount * multiplier * oneUnitInPixels - 20,
|
||||
height / 2 - 7,
|
||||
),
|
||||
);
|
||||
|
||||
scale = calculateScaleFactor(map);
|
||||
}
|
||||
</script>
|
||||
|
||||
<main class="grid grid-cols-1 md:grid-cols-[400px_1fr] gap-x-1 gap-y-4">
|
||||
<div
|
||||
class="print-details px-6 space-y-4 border border-input-border rounded-3xl"
|
||||
>
|
||||
<div id="images"></div>
|
||||
<h1 class="text-4xl font-bold">Print Trail</h1>
|
||||
<Select
|
||||
bind:value={selectedPaperSize}
|
||||
items={paperSizes}
|
||||
label="Paper size"
|
||||
on:change={updateMapSize}
|
||||
></Select>
|
||||
<Select
|
||||
bind:value={selectedOrientation}
|
||||
items={orientations}
|
||||
label="Orientation"
|
||||
on:change={updateMapSize}
|
||||
></Select>
|
||||
|
||||
<Button primary={true} on:click={print}>Print!</Button>
|
||||
</div>
|
||||
|
||||
<div class="paper-container overflow-scroll">
|
||||
<div
|
||||
class="paper flex flex-col mx-auto shadow-xl mb-8 p-8 bg-white"
|
||||
style="width: {paperDimensions[selectedPaperSize][
|
||||
selectedOrientation == 'portrait' ? 'width' : 'height'
|
||||
]}; height: {paperDimensions[selectedPaperSize][
|
||||
selectedOrientation == 'portrait' ? 'height' : 'width'
|
||||
]}"
|
||||
>
|
||||
<div class="basis-full">
|
||||
<MapWithElevation
|
||||
trail={$trail}
|
||||
options={{
|
||||
theme: "gray-theme",
|
||||
slope: false,
|
||||
speed: false,
|
||||
legend: false,
|
||||
time: false,
|
||||
hotline: false,
|
||||
height: 150,
|
||||
}}
|
||||
on:zoomend={(e) => updateScale(e.detail)}
|
||||
bind:map
|
||||
>
|
||||
<img
|
||||
class="w-[100px] h-[100px]"
|
||||
id="qrcode"
|
||||
alt="QR Code"
|
||||
/>
|
||||
<div class="basis-full mx-4">
|
||||
<svg width="100%" height="50" id="ruler"> </svg>
|
||||
<span class="text-sm"
|
||||
>Scale:   1 : {Math.round(scale)}</span
|
||||
>
|
||||
</div>
|
||||
</MapWithElevation>
|
||||
</div>
|
||||
<div class="pt-4 -mt-4 border-t border-t-input-border z-10">
|
||||
<div class="float-right"><LogoText></LogoText></div>
|
||||
|
||||
<h4 class="font-semibold">{$trail.name}</h4>
|
||||
<h5>
|
||||
<i class="fa fa-location-dot mr-2"></i>{$trail.location ||
|
||||
"-"}
|
||||
</h5>
|
||||
<div
|
||||
class="flex mt-1 gap-4 text-sm text-gray-500 whitespace-nowrap"
|
||||
>
|
||||
<span
|
||||
><i class="fa fa-left-right mr-2"></i>{formatDistance(
|
||||
$trail.distance,
|
||||
)}</span
|
||||
>
|
||||
<span
|
||||
><i class="fa fa-up-down mr-2"></i>{formatElevation(
|
||||
$trail.elevation_gain,
|
||||
)}</span
|
||||
>
|
||||
<span
|
||||
><i class="fa fa-clock mr-2"></i>{formatTimeHHMM(
|
||||
$trail.duration,
|
||||
)}</span
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<style>
|
||||
:global(#map-container) {
|
||||
height: 100%;
|
||||
}
|
||||
</style>
|
||||
19
web/src/routes/map/trail/[id]/print/+page.ts
Normal file
19
web/src/routes/map/trail/[id]/print/+page.ts
Normal file
@@ -0,0 +1,19 @@
|
||||
import { lists_index } from "$lib/stores/list_store";
|
||||
import { trails_show } from "$lib/stores/trail_store";
|
||||
import { error, type Load } from "@sveltejs/kit";
|
||||
import { ClientResponseError } from "pocketbase";
|
||||
|
||||
export const load: Load = async ({ params, fetch }) => {
|
||||
try {
|
||||
await trails_show(params.id!, true, fetch)
|
||||
} catch (e) {
|
||||
if (e instanceof ClientResponseError && e.status == 404) {
|
||||
error(404, {
|
||||
message: 'Not found'
|
||||
});
|
||||
}
|
||||
console.log(e);
|
||||
|
||||
}
|
||||
await lists_index(undefined, fetch);
|
||||
};
|
||||
Reference in New Issue
Block a user