pages
This commit is contained in:
@@ -1,122 +1,38 @@
|
||||
<script lang="ts">
|
||||
import type { MessageResponseBodyData } from '$lib/types/dtos/message/MessageResponseBodyData';
|
||||
import type { MinibusserviceResponseBody } from '$lib/types/dtos/MinibusserviceResponseBody';
|
||||
import Button from './button/Button.svelte';
|
||||
import Link from './link/Link.svelte';
|
||||
import MapPoint from './MapPoint.svelte';
|
||||
import { toast } from './toast/toast';
|
||||
|
||||
let buttonDisabled = false;
|
||||
|
||||
let name = '';
|
||||
let email = '';
|
||||
let phone = '';
|
||||
let message = '';
|
||||
let posting = false;
|
||||
|
||||
async function sendMessage() {
|
||||
if (!(name && email && message)) {
|
||||
toast.warning('Alle påkrevde felt må fyllest inn');
|
||||
return;
|
||||
}
|
||||
|
||||
posting = true;
|
||||
buttonDisabled = true;
|
||||
|
||||
try {
|
||||
let res = await fetch('/api/v1/message', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify({
|
||||
name,
|
||||
email,
|
||||
phone,
|
||||
message
|
||||
})
|
||||
});
|
||||
|
||||
let data: MinibusserviceResponseBody<MessageResponseBodyData> = await res.json();
|
||||
if (res.ok) {
|
||||
toast.success(data.message);
|
||||
} else {
|
||||
console.error(data.message);
|
||||
toast.error(data.message);
|
||||
}
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
buttonDisabled = false;
|
||||
toast.error('En feil oppstod under sending av meldingen');
|
||||
} finally {
|
||||
posting = false;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="mt-2 flex flex-col gap-8 md:flex-row md:text-lg">
|
||||
<div class="flex flex-1 flex-col">
|
||||
<p class="block pt-2 font-medium opacity-70 md:hidden">
|
||||
Spørsmål?<br />Kontakt oss via tlf, e-post eller via skjemaet under
|
||||
</p>
|
||||
<p class="hidden pt-2 font-medium opacity-70 md:block">
|
||||
Spørsmål?<br />Kontakt oss via tlf, e-post eller via skjemaet til høyre
|
||||
</p>
|
||||
<Link class="w-fit font-bold text-accent hover:underline" href="tel:45256161">
|
||||
Tlf: +47 45 25 61 61
|
||||
</Link>
|
||||
<Link class="w-fit font-bold text-accent hover:underline" href="mailto:minibusstur@hotmail.com">
|
||||
E-post: minibusstur@hotmail.com
|
||||
</Link>
|
||||
|
||||
<MapPoint
|
||||
class="card mt-5 min-h-96 grow overflow-hidden rounded-md border-2"
|
||||
coordinates={[
|
||||
{
|
||||
latitude: 62.48303957042255,
|
||||
longitude: 6.8108274964451745
|
||||
}
|
||||
]}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<form
|
||||
on:submit|preventDefault={sendMessage}
|
||||
class="flex flex-1 flex-col gap-4 pt-4 text-xl md:pt-0"
|
||||
>
|
||||
<label>
|
||||
<span class="text-xl font-semibold">
|
||||
<span class="text-accent"> * </span>
|
||||
Navn
|
||||
</span>
|
||||
<input required bind:value={name} placeholder="Navn" class="bg-primary-100" type="text" />
|
||||
</label>
|
||||
<label>
|
||||
<span class="text-xl font-semibold">
|
||||
<span class="text-accent"> * </span>
|
||||
E-post
|
||||
</span>
|
||||
<input
|
||||
required
|
||||
bind:value={email}
|
||||
placeholder="E-postadresse"
|
||||
class="bg-primary-100"
|
||||
type="email"
|
||||
/>
|
||||
</label>
|
||||
<label>
|
||||
<span class="text-xl font-semibold">Telefonnummer</span>
|
||||
<input bind:value={phone} placeholder="Telefonnummer" class="bg-primary-100" type="tel" />
|
||||
</label>
|
||||
<label>
|
||||
<span class="text-xl font-semibold">
|
||||
<span class="text-accent"> * </span>
|
||||
Melding
|
||||
</span>
|
||||
<textarea required bind:value={message} class="h-60 bg-primary-100" placeholder="Melding" />
|
||||
</label>
|
||||
<div>
|
||||
<Button class="w-full" disabled={buttonDisabled} loading={posting} type="submit">Send</Button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<script lang="ts">
|
||||
import Link from "./link/Link.svelte";
|
||||
import MapPoint from "./MapPoint.svelte";
|
||||
</script>
|
||||
|
||||
<div class="mt-2 flex flex-col gap-8 md:flex-row md:text-lg">
|
||||
<div class="flex flex-1 flex-col">
|
||||
<p class="block pt-2 font-medium opacity-70 md:hidden">
|
||||
Spørsmål?<br />Kontakt oss via tlf, e-post eller via skjemaet under
|
||||
</p>
|
||||
<p class="hidden pt-2 font-medium opacity-70 md:block">
|
||||
Spørsmål?<br />Kontakt oss via tlf, e-post eller via skjemaet til
|
||||
høyre
|
||||
</p>
|
||||
<Link
|
||||
class="w-fit font-bold text-accent hover:underline"
|
||||
href="tel:45256161"
|
||||
>
|
||||
Tlf: +47 45 25 61 61
|
||||
</Link>
|
||||
<Link
|
||||
class="w-fit font-bold text-accent hover:underline"
|
||||
href="mailto:minibusstur@hotmail.com"
|
||||
>
|
||||
E-post: minibusstur@hotmail.com
|
||||
</Link>
|
||||
|
||||
<MapPoint
|
||||
class="card mt-5 min-h-96 grow overflow-hidden rounded-md border-2"
|
||||
coordinates={[
|
||||
{
|
||||
latitude: 62.48303957042255,
|
||||
longitude: 6.8108274964451745,
|
||||
},
|
||||
]}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,79 +1,86 @@
|
||||
<script lang="ts">
|
||||
import { onDestroy, onMount } from 'svelte';
|
||||
import 'leaflet/dist/leaflet.css';
|
||||
import { env } from '$env/dynamic/public';
|
||||
import { twMerge } from 'tailwind-merge';
|
||||
|
||||
interface MarkerPoint {
|
||||
latitude: number;
|
||||
longitude: number;
|
||||
tooltip?: string;
|
||||
}
|
||||
|
||||
export let coordinates: MarkerPoint[];
|
||||
|
||||
let mapElement: HTMLDivElement;
|
||||
let map: L.Map | null = null;
|
||||
let markers: L.Marker<any>[] = [];
|
||||
var icon: L.Icon;
|
||||
let L: typeof import('leaflet/index');
|
||||
|
||||
onMount(async () => {
|
||||
L = await import('leaflet');
|
||||
icon = L.icon({
|
||||
iconUrl: '/map_marker.png',
|
||||
|
||||
iconSize: [32, 32],
|
||||
iconAnchor: [16, 32],
|
||||
popupAnchor: [-3, -76]
|
||||
});
|
||||
|
||||
map = L.map(mapElement);
|
||||
|
||||
if (coordinates.length >= 1) {
|
||||
map.setView([coordinates[0].latitude, coordinates[0].longitude], 15);
|
||||
}
|
||||
|
||||
L.tileLayer(env.PUBLIC_TILE_SERVER_URL, {
|
||||
maxZoom: 18,
|
||||
attribution:
|
||||
'Map data © <a href="https://www.openstreetmap.org/">OpenStreetMap</a> contributors, <a href="https://creativecommons.org/licenses/by-sa/2.0/">CC-BY-SA</a>',
|
||||
id: 'base'
|
||||
}).addTo(map);
|
||||
|
||||
updateMarkers();
|
||||
});
|
||||
|
||||
onDestroy(async () => {
|
||||
map?.remove();
|
||||
});
|
||||
|
||||
function addMarker(point: MarkerPoint) {
|
||||
let marker = L.marker([point.latitude, point.longitude], { icon: icon });
|
||||
|
||||
if (point.tooltip)
|
||||
marker.bindTooltip(point.tooltip, {
|
||||
direction: 'bottom'
|
||||
});
|
||||
|
||||
markers.push(marker);
|
||||
map?.addLayer(marker);
|
||||
}
|
||||
|
||||
function updateMarkers() {
|
||||
if (L && map) {
|
||||
markers.forEach((point) => map?.removeLayer(point));
|
||||
markers = [];
|
||||
|
||||
coordinates.forEach(addMarker);
|
||||
|
||||
map?.flyToBounds(L.featureGroup(markers).getBounds(), {
|
||||
duration: 1
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
$: coordinates && updateMarkers();
|
||||
</script>
|
||||
|
||||
<div bind:this={mapElement} class={twMerge('z-0 h-full w-full', $$restProps['class'])} />
|
||||
<script lang="ts">
|
||||
import { onDestroy, onMount } from "svelte";
|
||||
import "leaflet/dist/leaflet.css";
|
||||
import { twMerge } from "tailwind-merge";
|
||||
|
||||
interface MarkerPoint {
|
||||
latitude: number;
|
||||
longitude: number;
|
||||
tooltip?: string;
|
||||
}
|
||||
|
||||
export let coordinates: MarkerPoint[];
|
||||
|
||||
let mapElement: HTMLDivElement;
|
||||
let map: L.Map | null = null;
|
||||
let markers: L.Marker<any>[] = [];
|
||||
var icon: L.Icon;
|
||||
let L: typeof import("leaflet/index");
|
||||
|
||||
onMount(async () => {
|
||||
L = await import("leaflet");
|
||||
icon = L.icon({
|
||||
iconUrl: "/map_marker.png",
|
||||
|
||||
iconSize: [32, 32],
|
||||
iconAnchor: [16, 32],
|
||||
popupAnchor: [-3, -76],
|
||||
});
|
||||
|
||||
map = L.map(mapElement);
|
||||
|
||||
if (coordinates.length >= 1) {
|
||||
map.setView(
|
||||
[coordinates[0].latitude, coordinates[0].longitude],
|
||||
15,
|
||||
);
|
||||
}
|
||||
|
||||
L.tileLayer("https://tile.openstreetmap.org/{z}/{x}/{y}.png", {
|
||||
maxZoom: 18,
|
||||
attribution:
|
||||
'© <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors',
|
||||
id: "base",
|
||||
}).addTo(map);
|
||||
|
||||
updateMarkers();
|
||||
});
|
||||
|
||||
onDestroy(async () => {
|
||||
map?.remove();
|
||||
});
|
||||
|
||||
function addMarker(point: MarkerPoint) {
|
||||
let marker = L.marker([point.latitude, point.longitude], {
|
||||
icon: icon,
|
||||
});
|
||||
|
||||
if (point.tooltip)
|
||||
marker.bindTooltip(point.tooltip, {
|
||||
direction: "bottom",
|
||||
});
|
||||
|
||||
markers.push(marker);
|
||||
map?.addLayer(marker);
|
||||
}
|
||||
|
||||
function updateMarkers() {
|
||||
if (L && map) {
|
||||
markers.forEach((point) => map?.removeLayer(point));
|
||||
markers = [];
|
||||
|
||||
coordinates.forEach(addMarker);
|
||||
|
||||
map?.flyToBounds(L.featureGroup(markers).getBounds(), {
|
||||
duration: 1,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
$: coordinates && updateMarkers();
|
||||
</script>
|
||||
|
||||
<div
|
||||
bind:this={mapElement}
|
||||
class={twMerge("z-0 h-full w-full", $$restProps["class"])}
|
||||
></div>
|
||||
|
||||
@@ -1,41 +1,42 @@
|
||||
<script lang="ts">
|
||||
import { page } from '$app/stores';
|
||||
import Button from '$lib/components/button/Button.svelte';
|
||||
import { twMerge } from 'tailwind-merge';
|
||||
|
||||
export let maxItems: number;
|
||||
|
||||
let pageParam = $page.url.searchParams.get('page');
|
||||
let currentPage = 1;
|
||||
if (pageParam) {
|
||||
let pageParamAsNumber = Number(pageParam);
|
||||
if (pageParamAsNumber) {
|
||||
currentPage = pageParamAsNumber;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<section class={twMerge('mt-8 flex justify-center gap-5', $$restProps['class'])}>
|
||||
{#if currentPage > 1}
|
||||
<a
|
||||
on:click={() =>
|
||||
setTimeout(() => {
|
||||
currentPage = currentPage - 1;
|
||||
}, 10)}
|
||||
href={`?page=${currentPage - 1}`}
|
||||
>
|
||||
<Button>Forrige</Button>
|
||||
</a>
|
||||
{/if}
|
||||
{#if (currentPage + 1) * 10 - 10 < maxItems}
|
||||
<a
|
||||
on:click={() =>
|
||||
setTimeout(() => {
|
||||
currentPage = currentPage + 1;
|
||||
}, 10)}
|
||||
href={`?page=${currentPage + 1}`}
|
||||
>
|
||||
<Button>Neste</Button>
|
||||
</a>
|
||||
{/if}
|
||||
</section>
|
||||
<script lang="ts">
|
||||
import Button from "./button/Button.svelte";
|
||||
import { twMerge } from "tailwind-merge";
|
||||
|
||||
export let maxItems: number;
|
||||
|
||||
let pageParam = $page.url.searchParams.get("page");
|
||||
let currentPage = 1;
|
||||
if (pageParam) {
|
||||
let pageParamAsNumber = Number(pageParam);
|
||||
if (pageParamAsNumber) {
|
||||
currentPage = pageParamAsNumber;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<section
|
||||
class={twMerge("mt-8 flex justify-center gap-5", $$restProps["class"])}
|
||||
>
|
||||
{#if currentPage > 1}
|
||||
<a
|
||||
on:click={() =>
|
||||
setTimeout(() => {
|
||||
currentPage = currentPage - 1;
|
||||
}, 10)}
|
||||
href={`?page=${currentPage - 1}`}
|
||||
>
|
||||
<Button>Forrige</Button>
|
||||
</a>
|
||||
{/if}
|
||||
{#if (currentPage + 1) * 10 - 10 < maxItems}
|
||||
<a
|
||||
on:click={() =>
|
||||
setTimeout(() => {
|
||||
currentPage = currentPage + 1;
|
||||
}, 10)}
|
||||
href={`?page=${currentPage + 1}`}
|
||||
>
|
||||
<Button>Neste</Button>
|
||||
</a>
|
||||
{/if}
|
||||
</section>
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
import { onMount, type Component, type Snippet } from "svelte";
|
||||
|
||||
type RouteInfo = {
|
||||
path: string;
|
||||
pattern: RegExp;
|
||||
paramNames: string[];
|
||||
component: Component<{}>;
|
||||
@@ -44,7 +45,16 @@
|
||||
});
|
||||
|
||||
function normalizePath(path: string): string {
|
||||
return path.replace(/\/+$/, "");
|
||||
return path.replace(/\/+$/, "").trim();
|
||||
}
|
||||
|
||||
function createRoutePattern(path: string): string {
|
||||
const normalizedPath = normalizePath(path);
|
||||
const escapedPath = normalizedPath.replace(/[.*+?^${}()|\\/]/g, "\\$&");
|
||||
const pathPattern = escapedPath.replace(/\[([^\]]+)\]/g, "([^/]+)");
|
||||
const regexPattern = `^${pathPattern}\\/?$`;
|
||||
|
||||
return regexPattern;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -55,17 +65,13 @@
|
||||
console.warn(`Route already registered for path: '${path}'.`);
|
||||
}
|
||||
|
||||
const params = Array.from(
|
||||
path.matchAll(/\[([^\]]+)\]/g).map((x) => x[1]),
|
||||
const params = Array.from(path.matchAll(/\[([^\]]+)\]/g)).map(
|
||||
(x) => x[1],
|
||||
);
|
||||
|
||||
const normalizedPath = normalizePath(path);
|
||||
const escapedPath = normalizedPath.replace(/[.*+?^${}()|\\/]/g, "\\$&");
|
||||
const pathPattern = escapedPath.replace(/\[([^\]]+)\]/g, "([^/]+)");
|
||||
const regexPattern = `^${pathPattern}\\/?$`;
|
||||
|
||||
routes.set(path, {
|
||||
pattern: new RegExp(regexPattern),
|
||||
pattern: new RegExp(createRoutePattern(path)),
|
||||
path: path,
|
||||
paramNames: params,
|
||||
component: component,
|
||||
});
|
||||
@@ -75,6 +81,19 @@
|
||||
export class MissingRouteError extends RouteError {}
|
||||
export class RouteFormatError extends RouteError {}
|
||||
|
||||
export function isCurrentPath(path: string): boolean {
|
||||
if (!currentRoute) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const match = path.match(currentRoute.pattern);
|
||||
if (!match) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the value of a route parameter.
|
||||
*/
|
||||
@@ -178,8 +197,9 @@
|
||||
function makeProxy(target: any) {
|
||||
return new Proxy(target, {
|
||||
apply: (target, thisArg, argArray) => {
|
||||
target.apply(thisArg, argArray);
|
||||
const result = target.apply(thisArg, argArray);
|
||||
refresh();
|
||||
return result;
|
||||
},
|
||||
});
|
||||
}
|
||||
@@ -197,19 +217,17 @@
|
||||
return;
|
||||
}
|
||||
|
||||
if (e.target instanceof HTMLAnchorElement) {
|
||||
if (e.target.target === "_blank") {
|
||||
return;
|
||||
}
|
||||
const anchor = (e.target as Element)?.closest("a");
|
||||
if (!(anchor instanceof HTMLAnchorElement)) return;
|
||||
if (anchor.target === "_blank") return;
|
||||
|
||||
const targetUrl = new URL(e.target.href, document.baseURI);
|
||||
const documentUrl = new URL(document.baseURI);
|
||||
const targetUrl = new URL(anchor.href, document.baseURI);
|
||||
const documentUrl = new URL(document.baseURI);
|
||||
|
||||
if (targetUrl.origin == documentUrl.origin) {
|
||||
e.preventDefault();
|
||||
history.pushState({}, "", targetUrl);
|
||||
currentUrl = targetUrl;
|
||||
}
|
||||
if (targetUrl.origin === documentUrl.origin) {
|
||||
e.preventDefault();
|
||||
history.pushState({}, "", targetUrl);
|
||||
currentUrl = targetUrl;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,58 +1,68 @@
|
||||
<script lang="ts">
|
||||
import { toast, toasts } from '$lib/components/toast/toast';
|
||||
import { fade } from 'svelte/transition';
|
||||
import InfoIcon from '../icons/severity/InfoIcon.svelte';
|
||||
import WarningIcon from '../icons/severity/WarningIcon.svelte';
|
||||
import ErrorIcon from '../icons/severity/ErrorIcon.svelte';
|
||||
import SuccessIcon from '../icons/severity/SuccessIcon.svelte';
|
||||
</script>
|
||||
|
||||
{#if $toasts.length >= 1}
|
||||
<div class="fixed bottom-0 right-0 z-50 flex w-full flex-col gap-2 p-4 text-white sm:w-96">
|
||||
{#each $toasts as toastItem}
|
||||
<div
|
||||
class:bg-red-500={toastItem.type === 'error'}
|
||||
class:bg-green-500={toastItem.type === 'success'}
|
||||
class:bg-orange-500={toastItem.type === 'warning'}
|
||||
class:bg-blue-500={toastItem.type === 'info'}
|
||||
class="card flex items-center gap-3 border-contrast-900 px-4 py-3 shadow-md dark:border-contrast-100"
|
||||
transition:fade={{ duration: 200 }}
|
||||
>
|
||||
<div
|
||||
class:text-red-900={toastItem.type === 'error'}
|
||||
class:text-green-900={toastItem.type === 'success'}
|
||||
class:text-orange-900={toastItem.type === 'warning'}
|
||||
class:text-blue-900={toastItem.type === 'info'}
|
||||
class="shrink-0"
|
||||
>
|
||||
{#if toastItem.type === 'info'}
|
||||
<InfoIcon class="w-7"></InfoIcon>
|
||||
{:else if toastItem.type === 'warning'}
|
||||
<WarningIcon class="w-7"></WarningIcon>
|
||||
{:else if toastItem.type === 'error'}
|
||||
<ErrorIcon class="w-7"></ErrorIcon>
|
||||
{:else if toastItem.type === 'success'}
|
||||
<SuccessIcon class="w-7"></SuccessIcon>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<p class="grow">
|
||||
{toastItem.text}
|
||||
</p>
|
||||
|
||||
<button on:click={() => toast.dismiss(toastItem.id)} class="shrink-0">
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke-width="1.5"
|
||||
stroke="currentColor"
|
||||
class="w-6"
|
||||
>
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M6 18L18 6M6 6l12 12" />
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
{/if}
|
||||
<script lang="ts">
|
||||
import { toast, toasts } from "./toast";
|
||||
import { fade } from "svelte/transition";
|
||||
import InfoIcon from "../icons/severity/InfoIcon.svelte";
|
||||
import WarningIcon from "../icons/severity/WarningIcon.svelte";
|
||||
import ErrorIcon from "../icons/severity/ErrorIcon.svelte";
|
||||
import SuccessIcon from "../icons/severity/SuccessIcon.svelte";
|
||||
</script>
|
||||
|
||||
{#if $toasts.length >= 1}
|
||||
<div
|
||||
class="fixed bottom-0 right-0 z-50 flex w-full flex-col gap-2 p-4 text-white sm:w-96"
|
||||
>
|
||||
{#each $toasts as toastItem}
|
||||
<div
|
||||
class:bg-red-500={toastItem.type === "error"}
|
||||
class:bg-green-500={toastItem.type === "success"}
|
||||
class:bg-orange-500={toastItem.type === "warning"}
|
||||
class:bg-blue-500={toastItem.type === "info"}
|
||||
class="card flex items-center gap-3 border-contrast-900 px-4 py-3 shadow-md dark:border-contrast-100"
|
||||
transition:fade={{ duration: 200 }}
|
||||
>
|
||||
<div
|
||||
class:text-red-900={toastItem.type === "error"}
|
||||
class:text-green-900={toastItem.type === "success"}
|
||||
class:text-orange-900={toastItem.type === "warning"}
|
||||
class:text-blue-900={toastItem.type === "info"}
|
||||
class="shrink-0"
|
||||
>
|
||||
{#if toastItem.type === "info"}
|
||||
<InfoIcon class="w-7"></InfoIcon>
|
||||
{:else if toastItem.type === "warning"}
|
||||
<WarningIcon class="w-7"></WarningIcon>
|
||||
{:else if toastItem.type === "error"}
|
||||
<ErrorIcon class="w-7"></ErrorIcon>
|
||||
{:else if toastItem.type === "success"}
|
||||
<SuccessIcon class="w-7"></SuccessIcon>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<p class="grow">
|
||||
{toastItem.text}
|
||||
</p>
|
||||
|
||||
<button
|
||||
on:click={() => toast.dismiss(toastItem.id)}
|
||||
class="shrink-0"
|
||||
aria-label="dismiss"
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke-width="1.5"
|
||||
stroke="currentColor"
|
||||
class="w-6"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
d="M6 18L18 6M6 6l12 12"
|
||||
/>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
99
src/lib/global/routes.ts
Normal file
99
src/lib/global/routes.ts
Normal file
@@ -0,0 +1,99 @@
|
||||
import homeIcon from "../../assets/icons/home_icon.svg";
|
||||
import contactIcon from "../../assets/icons/contact_icon.svg";
|
||||
import busIcon from "../../assets/icons/bus_icon.svg";
|
||||
|
||||
type Route = {
|
||||
url: string;
|
||||
text: string;
|
||||
icon: string | null;
|
||||
target: string | null;
|
||||
};
|
||||
|
||||
export class Routes {
|
||||
static home: Route = {
|
||||
url: "/",
|
||||
text: "Hjem",
|
||||
icon: homeIcon,
|
||||
target: null,
|
||||
};
|
||||
|
||||
static bus: Route = {
|
||||
url: "/bus",
|
||||
text: "Buss",
|
||||
icon: busIcon,
|
||||
target: null,
|
||||
};
|
||||
|
||||
static taxi: Route = {
|
||||
url: "/taxi",
|
||||
text: "Taxi",
|
||||
icon: null,
|
||||
target: null,
|
||||
};
|
||||
|
||||
static accessibility: Route = {
|
||||
url: "/accessibility",
|
||||
text: "Tilgjengelighetsfunksjoner",
|
||||
icon: null,
|
||||
target: null,
|
||||
};
|
||||
|
||||
static contact: Route = {
|
||||
url: "/contact",
|
||||
text: "Ta kontakt",
|
||||
icon: contactIcon,
|
||||
target: null,
|
||||
};
|
||||
|
||||
static facebook: Route = {
|
||||
url: "https://www.facebook.com/MinibusserviceSteneAS/",
|
||||
text: "Facebook",
|
||||
icon: null,
|
||||
target: "_blank",
|
||||
};
|
||||
|
||||
static email: Route = {
|
||||
url: "mailto:minibusstur@hotmail.com",
|
||||
text: "E-post",
|
||||
icon: null,
|
||||
target: null,
|
||||
};
|
||||
|
||||
static phone: Route = {
|
||||
url: "tel:45256161",
|
||||
text: "Telefon",
|
||||
icon: null,
|
||||
target: null,
|
||||
};
|
||||
|
||||
static topbarRoutes: Route[] = [Routes.home, Routes.bus, Routes.contact];
|
||||
|
||||
static sidebarRoutes: Route[] = [
|
||||
Routes.home,
|
||||
Routes.bus,
|
||||
Routes.taxi,
|
||||
Routes.accessibility,
|
||||
Routes.contact,
|
||||
];
|
||||
|
||||
static footerRoutes: { text: string; routes: Route[] }[] = [
|
||||
{
|
||||
text: "HJELP",
|
||||
routes: [Routes.contact],
|
||||
},
|
||||
{
|
||||
text: "SIDER",
|
||||
routes: [
|
||||
Routes.home,
|
||||
Routes.bus,
|
||||
Routes.taxi,
|
||||
Routes.accessibility,
|
||||
Routes.contact,
|
||||
],
|
||||
},
|
||||
{
|
||||
text: "MINIBUSSERVICE",
|
||||
routes: [Routes.facebook, Routes.email, Routes.phone],
|
||||
},
|
||||
];
|
||||
}
|
||||
@@ -1,25 +1,22 @@
|
||||
import { writable } from "svelte/store";
|
||||
import { browser } from "$app/environment";
|
||||
|
||||
function createThemeToggler() {
|
||||
let defaultValue = false;
|
||||
if (browser) {
|
||||
let savedValue = localStorage.getItem("dark_theme");
|
||||
let savedValue = localStorage.getItem("dark_theme");
|
||||
|
||||
if (savedValue) {
|
||||
defaultValue = JSON.parse(savedValue);
|
||||
} else if (
|
||||
window.matchMedia &&
|
||||
window.matchMedia("(prefers-color-scheme: dark)").matches
|
||||
) {
|
||||
defaultValue = true;
|
||||
}
|
||||
if (savedValue) {
|
||||
defaultValue = JSON.parse(savedValue);
|
||||
} else if (
|
||||
window.matchMedia &&
|
||||
window.matchMedia("(prefers-color-scheme: dark)").matches
|
||||
) {
|
||||
defaultValue = true;
|
||||
}
|
||||
|
||||
if (defaultValue) {
|
||||
document.documentElement.classList.add("dark");
|
||||
} else {
|
||||
document.documentElement.classList.remove("dark");
|
||||
}
|
||||
if (defaultValue) {
|
||||
document.documentElement.style.colorScheme = "dark";
|
||||
} else {
|
||||
document.documentElement.style.colorScheme = "light";
|
||||
}
|
||||
|
||||
const { subscribe, update } = writable(defaultValue);
|
||||
@@ -28,15 +25,13 @@ function createThemeToggler() {
|
||||
subscribe,
|
||||
set: (value: boolean) => {
|
||||
update(() => value);
|
||||
if (browser) {
|
||||
if (value) {
|
||||
document.documentElement.classList.add("dark");
|
||||
} else {
|
||||
document.documentElement.classList.remove("dark");
|
||||
}
|
||||
|
||||
localStorage.setItem("dark_theme", JSON.stringify(value));
|
||||
if (value) {
|
||||
document.documentElement.style.colorScheme = "dark";
|
||||
} else {
|
||||
document.documentElement.style.colorScheme = "light";
|
||||
}
|
||||
|
||||
localStorage.setItem("dark_theme", JSON.stringify(value));
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user