mirror of
https://github.com/gpxstudio/gpx.studio.git
synced 2025-09-02 16:52:31 +00:00
optional parameter for language, instead of rest parameter
This commit is contained in:
23
website/src/routes/[[language]]/+layout.svelte
Normal file
23
website/src/routes/[[language]]/+layout.svelte
Normal file
@@ -0,0 +1,23 @@
|
||||
<script lang="ts">
|
||||
import { locale, _ } from 'svelte-i18n';
|
||||
import { page } from '$app/stores';
|
||||
import { languages } from '$lib/languages';
|
||||
import { browser } from '$app/environment';
|
||||
import { goto } from '$app/navigation';
|
||||
import { base } from '$app/paths';
|
||||
|
||||
$: if ($page.params.language === '' && $locale !== 'en') {
|
||||
locale.set('en');
|
||||
} else if ($page.params.language) {
|
||||
let lang = $page.params.language.replace('/', '');
|
||||
if ($locale !== lang) {
|
||||
if (languages.hasOwnProperty(lang)) {
|
||||
locale.set(lang);
|
||||
} else if (browser) {
|
||||
goto(`${base}/404`);
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<slot />
|
256
website/src/routes/[[language]]/+page.svelte
Normal file
256
website/src/routes/[[language]]/+page.svelte
Normal file
@@ -0,0 +1,256 @@
|
||||
<script lang="ts">
|
||||
import { Button } from '$lib/components/ui/button';
|
||||
import DocsLoader from '$lib/components/docs/DocsLoader.svelte';
|
||||
import Logo from '$lib/components/Logo.svelte';
|
||||
import ElevationProfile from '$lib/components/ElevationProfile.svelte';
|
||||
import GPXStatistics from '$lib/components/GPXStatistics.svelte';
|
||||
import Routing from '$lib/components/toolbar/tools/routing/Routing.svelte';
|
||||
import {
|
||||
BookOpenText,
|
||||
Heart,
|
||||
LineChart,
|
||||
Map,
|
||||
PencilRuler,
|
||||
PenLine,
|
||||
Route,
|
||||
Scale
|
||||
} from 'lucide-svelte';
|
||||
import { _, locale } from 'svelte-i18n';
|
||||
import { getURLForLanguage } from '$lib/utils';
|
||||
import { exampleGPXFile } from '$lib/assets/example';
|
||||
import { writable } from 'svelte/store';
|
||||
import Toolbar from '$lib/components/toolbar/Toolbar.svelte';
|
||||
import { currentTool, Tool } from '$lib/stores';
|
||||
import { onDestroy, onMount } from 'svelte';
|
||||
import routingScreenshot from '$lib/assets/img/home/routing.png?enhanced';
|
||||
import mapboxOutdoorsMap from '$lib/assets/img/home/mapbox-outdoors.png?enhanced';
|
||||
import mapboxSatelliteMap from '$lib/assets/img/home/mapbox-satellite.png?enhanced';
|
||||
import ignMap from '$lib/assets/img/home/ign.png?enhanced';
|
||||
import cyclosmMap from '$lib/assets/img/home/cyclosm.png?enhanced';
|
||||
import waymarkedMap from '$lib/assets/img/home/waymarked.png?enhanced';
|
||||
import mapScreenshot from '$lib/assets/img/home/map.png?enhanced';
|
||||
|
||||
let gpxStatistics = writable(exampleGPXFile.getStatistics());
|
||||
let slicedGPXStatistics = writable(undefined);
|
||||
let additionalDatasets = writable(['speed', 'atemp']);
|
||||
let elevationFill = writable<'slope' | 'surface' | undefined>(undefined);
|
||||
|
||||
onMount(() => {
|
||||
$currentTool = Tool.SCISSORS;
|
||||
});
|
||||
|
||||
$: $currentTool, ($currentTool = Tool.SCISSORS);
|
||||
|
||||
onDestroy(() => {
|
||||
$currentTool = null;
|
||||
});
|
||||
</script>
|
||||
|
||||
<div class="space-y-24 my-24">
|
||||
<div class="-mt-12 sm:mt-0">
|
||||
<div class="px-12 w-full flex flex-col items-center">
|
||||
<div class="flex flex-col gap-6 items-center max-w-3xl">
|
||||
<div class="text-4xl sm:text-6xl font-black text-center">{$_('metadata.app_title')}</div>
|
||||
<div class="text-lg sm:text-xl text-muted-foreground text-center">
|
||||
{$_('metadata.description')}
|
||||
</div>
|
||||
<div class="w-full flex flex-row justify-center gap-3">
|
||||
<Button href={getURLForLanguage($locale, '/app')} class="w-1/3 min-w-fit">
|
||||
<Map size="18" class="mr-1.5" />
|
||||
{$_('homepage.app')}
|
||||
</Button>
|
||||
<Button
|
||||
variant="secondary"
|
||||
href={getURLForLanguage($locale, '/help')}
|
||||
class="w-1/3 min-w-fit"
|
||||
>
|
||||
<BookOpenText size="18" class="mr-1.5" />
|
||||
<span>{$_('menu.help')}</span>
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="relative overflow-hidden">
|
||||
<enhanced:img
|
||||
src={routingScreenshot}
|
||||
alt="Screenshot of the gpx.studio map in 3D."
|
||||
class="w-full min-w-[1200px] ml-[20%] -translate-x-[20%]"
|
||||
/>
|
||||
<div
|
||||
class="absolute top-0 left-0 w-full h-full bg-gradient-to-b from-background via-transparent to-background"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="px-12 sm:px-24 w-full flex flex-col items-center">
|
||||
<div class="flex flex-col md:flex-row gap-x-12 gap-y-6 items-center justify-between max-w-5xl">
|
||||
<div class="markdown text-center">
|
||||
<h1>
|
||||
<Route size="24" class="mr-1 inline-block align-baseline" />
|
||||
{$_('homepage.route_planning')}
|
||||
</h1>
|
||||
<p class="text-muted-foreground">{$_('homepage.route_planning_description')}</p>
|
||||
</div>
|
||||
<div class="p-3 w-fit rounded-md border shadow-xl md:shrink-0">
|
||||
<Routing minimizable={false} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="px-12 sm:px-24 w-full flex flex-col items-center">
|
||||
<div class="flex flex-col md:flex-row gap-x-12 gap-y-6 items-center justify-between max-w-5xl">
|
||||
<div class="markdown text-center md:hidden">
|
||||
<h1>
|
||||
<PencilRuler size="24" class="mr-1 inline-block align-baseline" />
|
||||
{$_('homepage.file_processing')}
|
||||
</h1>
|
||||
<p class="text-muted-foreground">{$_('homepage.file_processing_description')}</p>
|
||||
</div>
|
||||
<div class="relative md:shrink-0 max-w-[400px]">
|
||||
<Toolbar />
|
||||
</div>
|
||||
<div class="markdown text-center hidden md:block">
|
||||
<h1>
|
||||
<PencilRuler size="24" class="mr-1 inline-block align-baseline" />
|
||||
{$_('homepage.file_processing')}
|
||||
</h1>
|
||||
<p class="text-muted-foreground">{$_('homepage.file_processing_description')}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="px-12 sm:px-24 w-full flex flex-col items-center">
|
||||
<div
|
||||
class="markdown flex flex-col md:flex-row gap-x-12 gap-y-6 items-center justify-between max-w-5xl"
|
||||
>
|
||||
<div class="markdown text-center">
|
||||
<h1>
|
||||
<Map size="24" class="mr-1 inline-block align-baseline" />
|
||||
{$_('homepage.maps')}
|
||||
</h1>
|
||||
<p class="text-muted-foreground">{$_('homepage.maps_description')}</p>
|
||||
</div>
|
||||
<div
|
||||
class="relative h-60 xs:h-80 aspect-square rounded-2xl shadow-xl overflow-hidden overflow-clip"
|
||||
>
|
||||
<enhanced:img
|
||||
src={mapboxOutdoorsMap}
|
||||
alt="Mapbox Outdoors map screenshot."
|
||||
class="absolute"
|
||||
style="clip-path: inset(0 50% 50% 0);"
|
||||
/>
|
||||
<enhanced:img
|
||||
src={mapboxSatelliteMap}
|
||||
alt="Mapbox Satellite map screenshot."
|
||||
class="absolute"
|
||||
style="clip-path: inset(0 0 50% 50%);"
|
||||
/>
|
||||
<enhanced:img
|
||||
src={ignMap}
|
||||
alt="IGN map screenshot."
|
||||
class="absolute"
|
||||
style="clip-path: inset(50% 50% 0 0);"
|
||||
/>
|
||||
<enhanced:img
|
||||
src={cyclosmMap}
|
||||
alt="CyclOSM map screenshot."
|
||||
class="absolute"
|
||||
style="clip-path: inset(50% 0 0 50%);"
|
||||
/>
|
||||
<enhanced:img src={waymarkedMap} alt="Waymarked Trails map screenshot." class="absolute" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="px-8 md:px-12">
|
||||
<div class="markdown text-center px-4 md:px-12">
|
||||
<h1>
|
||||
<LineChart size="24" class="mr-1 inline-block align-baseline" />
|
||||
{$_('homepage.data_visualization')}
|
||||
</h1>
|
||||
<p class="text-muted-foreground mb-6">{$_('homepage.data_visualization_description')}</p>
|
||||
</div>
|
||||
<div class="h-48 w-full">
|
||||
<ElevationProfile
|
||||
{gpxStatistics}
|
||||
{slicedGPXStatistics}
|
||||
additionalDatasets={$additionalDatasets}
|
||||
elevationFill={$elevationFill}
|
||||
panelSize={200}
|
||||
/>
|
||||
</div>
|
||||
<div class="flex flex-col items-center">
|
||||
<div class="h-10 w-fit">
|
||||
<GPXStatistics
|
||||
{gpxStatistics}
|
||||
{slicedGPXStatistics}
|
||||
panelSize={192}
|
||||
orientation={'horizontal'}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="px-12 sm:px-24 w-full flex flex-col items-center">
|
||||
<div class="flex flex-col md:flex-row gap-x-12 gap-y-6 items-center justify-between max-w-5xl">
|
||||
<div class="markdown text-center md:hidden">
|
||||
<h1>
|
||||
<Scale size="24" class="mr-1 inline-block align-baseline" />
|
||||
{$_('homepage.identity')}
|
||||
</h1>
|
||||
<p class="text-muted-foreground">{$_('homepage.identity_description')}</p>
|
||||
</div>
|
||||
<a href="https://github.com/gpxstudio/gpx.studio" target="_blank">
|
||||
<Logo class="h-32" company="github" />
|
||||
</a>
|
||||
<div class="markdown text-center hidden md:block">
|
||||
<h1>
|
||||
<Scale size="24" class="mr-1 inline-block align-baseline" />
|
||||
{$_('homepage.identity')}
|
||||
</h1>
|
||||
<p class="text-muted-foreground">{$_('homepage.identity_description')}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="px-12 w-full">
|
||||
<div class="w-full max-w-7xl mx-auto rounded-2xl shadow-xl overflow-hidden overflow-clip">
|
||||
<enhanced:img
|
||||
src={mapScreenshot}
|
||||
alt="Screenshot of the gpx.studio map in 3D."
|
||||
class="min-w-[800px] ml-[15%] -translate-x-[15%]"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="px-12 md:px-24 flex flex-row flex-wrap lg:flex-nowrap items-center justify-center -space-y-0.5 lg:-space-x-0.5"
|
||||
>
|
||||
<div
|
||||
class="grow max-w-xl flex flex-col items-center gap-6 p-8 border rounded-2xl shadow-xl -rotate-1 lg:rotate-1"
|
||||
>
|
||||
<DocsLoader path="home/funding.mdx" />
|
||||
<Button href="https://ko-fi.com/gpxstudio" target="_blank" class="text-base">
|
||||
<Heart size="16" class="mr-1" fill="rgb(var(--support))" color="rgb(var(--support))" />
|
||||
<span>{$_('homepage.support_button')}</span>
|
||||
</Button>
|
||||
</div>
|
||||
<div
|
||||
class="grow max-w-lg mx-6 h-fit bg-background flex flex-col items-center gap-6 p-8 border rounded-2xl shadow-xl rotate-1 lg:-rotate-1"
|
||||
>
|
||||
<DocsLoader path="home/translation.mdx" />
|
||||
<Button href="https://crowdin.com/project/gpxstudio" target="_blank" class="text-base">
|
||||
<PenLine size="16" class="mr-1" />
|
||||
<span>{$_('homepage.contribute')}</span>
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="px-12 md:px-24 flex flex-col items-center">
|
||||
<div
|
||||
class="max-w-4xl flex flex-col lg:flex-row items-center justify-center gap-x-12 gap-y-6 p-6 border rounded-2xl shadow-xl bg-muted"
|
||||
>
|
||||
<div class="shrink-0 flex flex-col sm:flex-row lg:flex-col items-center gap-x-4 gap-y-2">
|
||||
<div class="text-lg font-semibold text-muted-foreground">
|
||||
❤️ {$_('homepage.supported_by')}
|
||||
</div>
|
||||
<a href="https://www.mapbox.com/" target="_blank">
|
||||
<Logo company="mapbox" class="w-60" />
|
||||
</a>
|
||||
</div>
|
||||
<DocsLoader path="home/mapbox.mdx" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
121
website/src/routes/[[language]]/app/+page.svelte
Normal file
121
website/src/routes/[[language]]/app/+page.svelte
Normal file
@@ -0,0 +1,121 @@
|
||||
<script lang="ts">
|
||||
import GPXLayers from '$lib/components/gpx-layer/GPXLayers.svelte';
|
||||
import ElevationProfile from '$lib/components/ElevationProfile.svelte';
|
||||
import FileList from '$lib/components/file-list/FileList.svelte';
|
||||
import GPXStatistics from '$lib/components/GPXStatistics.svelte';
|
||||
import Map from '$lib/components/Map.svelte';
|
||||
import Menu from '$lib/components/Menu.svelte';
|
||||
import Toolbar from '$lib/components/toolbar/Toolbar.svelte';
|
||||
import StreetViewControl from '$lib/components/street-view-control/StreetViewControl.svelte';
|
||||
import LayerControl from '$lib/components/layer-control/LayerControl.svelte';
|
||||
import Resizer from '$lib/components/Resizer.svelte';
|
||||
import { Toaster } from '$lib/components/ui/sonner';
|
||||
|
||||
import { observeFilesFromDatabase, settings } from '$lib/db';
|
||||
import { gpxStatistics, loadFiles, slicedGPXStatistics } from '$lib/stores';
|
||||
import { onMount } from 'svelte';
|
||||
import { page } from '$app/stores';
|
||||
import { languages } from '$lib/languages';
|
||||
import { getURLForLanguage } from '$lib/utils';
|
||||
import Welcome from '$lib/components/Welcome.svelte';
|
||||
|
||||
const {
|
||||
verticalFileView,
|
||||
elevationProfile,
|
||||
bottomPanelSize,
|
||||
rightPanelSize,
|
||||
additionalDatasets,
|
||||
elevationFill
|
||||
} = settings;
|
||||
|
||||
onMount(() => {
|
||||
observeFilesFromDatabase();
|
||||
|
||||
let files = JSON.parse($page.url.searchParams.get('files') || '[]');
|
||||
|
||||
if (files.length > 0) {
|
||||
let downloads: Promise<File | null>[] = [];
|
||||
files.forEach((url) => {
|
||||
downloads.push(
|
||||
fetch(url)
|
||||
.then((response) => response.blob())
|
||||
.then((blob) => new File([blob], url.split('/').pop()))
|
||||
);
|
||||
});
|
||||
|
||||
Promise.all(downloads).then((files) => {
|
||||
files = files.filter((file) => file !== null);
|
||||
loadFiles(files);
|
||||
});
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<div class="fixed flex flex-row w-screen h-screen h-dvh">
|
||||
<div class="flex flex-col grow h-full min-w-0">
|
||||
<div class="grow relative">
|
||||
<Menu />
|
||||
<div
|
||||
class="absolute top-0 bottom-0 left-0 z-40 flex flex-col justify-center pointer-events-none"
|
||||
>
|
||||
<Toolbar />
|
||||
</div>
|
||||
<Map class="h-full {$verticalFileView ? '' : 'horizontal'}" />
|
||||
<StreetViewControl />
|
||||
<LayerControl />
|
||||
<GPXLayers />
|
||||
<Toaster richColors />
|
||||
{#if !$verticalFileView}
|
||||
<div class="h-10 -translate-y-10 w-full pointer-events-none absolute z-30">
|
||||
<FileList orientation="horizontal" />
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
{#if $elevationProfile}
|
||||
<Resizer orientation="row" bind:after={$bottomPanelSize} minAfter={100} maxAfter={300} />
|
||||
{/if}
|
||||
<div
|
||||
class="{$elevationProfile ? '' : 'h-10'} flex flex-row gap-2 px-2 sm:px-4"
|
||||
style={$elevationProfile ? `height: ${$bottomPanelSize}px` : ''}
|
||||
>
|
||||
<GPXStatistics
|
||||
{gpxStatistics}
|
||||
{slicedGPXStatistics}
|
||||
panelSize={$bottomPanelSize}
|
||||
orientation={$elevationProfile ? 'vertical' : 'horizontal'}
|
||||
/>
|
||||
{#if $elevationProfile}
|
||||
<ElevationProfile
|
||||
{gpxStatistics}
|
||||
{slicedGPXStatistics}
|
||||
bind:additionalDatasets={$additionalDatasets}
|
||||
bind:elevationFill={$elevationFill}
|
||||
panelSize={$bottomPanelSize}
|
||||
class="py-2"
|
||||
/>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
{#if $verticalFileView}
|
||||
<Resizer orientation="col" bind:after={$rightPanelSize} minAfter={100} maxAfter={400} />
|
||||
<FileList orientation="vertical" recursive={true} style="width: {$rightPanelSize}px" />
|
||||
{/if}
|
||||
<Welcome />
|
||||
</div>
|
||||
|
||||
<!-- hidden links for svelte crawling -->
|
||||
<div class="hidden">
|
||||
{#each Object.entries(languages) as [lang, label]}
|
||||
<a href={getURLForLanguage(lang, '/embed')}>
|
||||
{label}
|
||||
</a>
|
||||
{/each}
|
||||
</div>
|
||||
|
||||
<style lang="postcss">
|
||||
div :global(.toaster.group) {
|
||||
@apply absolute;
|
||||
@apply right-2;
|
||||
--offset: 50px !important;
|
||||
}
|
||||
</style>
|
27
website/src/routes/[[language]]/embed/+page.svelte
Normal file
27
website/src/routes/[[language]]/embed/+page.svelte
Normal file
@@ -0,0 +1,27 @@
|
||||
<script lang="ts">
|
||||
import { page } from '$app/stores';
|
||||
import { onMount } from 'svelte';
|
||||
import Embedding from '$lib/components/embedding/Embedding.svelte';
|
||||
import {
|
||||
getMergedEmbeddingOptions,
|
||||
type EmbeddingOptions
|
||||
} from '$lib/components/embedding/Embedding';
|
||||
|
||||
let embeddingOptions: EmbeddingOptions | undefined = undefined;
|
||||
|
||||
onMount(() => {
|
||||
let options = $page.url.searchParams.get('options');
|
||||
if (options === null) {
|
||||
return;
|
||||
}
|
||||
options = JSON.parse(options);
|
||||
if (options === null) {
|
||||
return;
|
||||
}
|
||||
embeddingOptions = getMergedEmbeddingOptions(options);
|
||||
});
|
||||
</script>
|
||||
|
||||
{#if embeddingOptions}
|
||||
<Embedding options={embeddingOptions} hash={$page.url.hash} />
|
||||
{/if}
|
41
website/src/routes/[[language]]/help/+layout.svelte
Normal file
41
website/src/routes/[[language]]/help/+layout.svelte
Normal file
@@ -0,0 +1,41 @@
|
||||
<script lang="ts">
|
||||
import { Button } from '$lib/components/ui/button';
|
||||
import { getURLForLanguage } from '$lib/utils';
|
||||
import { locale } from 'svelte-i18n';
|
||||
import DocsLoader from '$lib/components/docs/DocsLoader.svelte';
|
||||
import { page } from '$app/stores';
|
||||
import { guides } from '$lib/components/docs/docs';
|
||||
</script>
|
||||
|
||||
<div class="px-12 pt-6 pb-12 flex flex-row gap-24">
|
||||
<div class="hidden md:flex flex-col gap-2 w-40 sticky top-[108px] self-start shrink-0">
|
||||
{#each Object.keys(guides) as guide}
|
||||
<Button
|
||||
variant="link"
|
||||
href={getURLForLanguage($locale, `/help/${guide}`)}
|
||||
class="h-fit p-0 w-fit text-muted-foreground hover:text-foreground hover:no-underline font-normal hover:font-semibold items-start whitespace-normal {$page
|
||||
.params.guide === guide
|
||||
? 'font-semibold text-foreground'
|
||||
: ''}"
|
||||
>
|
||||
<DocsLoader path={`${guide}.mdx`} titleOnly={true} />
|
||||
</Button>
|
||||
{#each guides[guide] as subGuide}
|
||||
<Button
|
||||
variant="link"
|
||||
href={getURLForLanguage($locale, `/help/${guide}/${subGuide}`)}
|
||||
class="h-fit p-0 w-fit text-muted-foreground hover:text-foreground hover:no-underline font-normal hover:font-semibold items-start whitespace-normal ml-3 {$page
|
||||
.params.guide ===
|
||||
guide + '/' + subGuide
|
||||
? 'font-semibold text-foreground'
|
||||
: ''}"
|
||||
>
|
||||
<DocsLoader path={`${guide}/${subGuide}.mdx`} titleOnly={true} />
|
||||
</Button>
|
||||
{/each}
|
||||
{/each}
|
||||
</div>
|
||||
<div class="grow">
|
||||
<slot />
|
||||
</div>
|
||||
</div>
|
38
website/src/routes/[[language]]/help/+page.svelte
Normal file
38
website/src/routes/[[language]]/help/+page.svelte
Normal file
@@ -0,0 +1,38 @@
|
||||
<script>
|
||||
import { Button } from '$lib/components/ui/button';
|
||||
import { getURLForLanguage } from '$lib/utils';
|
||||
import { locale } from 'svelte-i18n';
|
||||
import DocsLoader from '$lib/components/docs/DocsLoader.svelte';
|
||||
import { guides, guideIcons } from '$lib/components/docs/docs';
|
||||
</script>
|
||||
|
||||
<div class="grid grid-cols-1 sm:grid-cols-2 gap-6">
|
||||
{#each Object.keys(guides) as guide}
|
||||
<Button
|
||||
variant="outline"
|
||||
href={getURLForLanguage($locale, `/help/${guide}`)}
|
||||
class="h-full pt-6 pb-3 px-0"
|
||||
>
|
||||
<div class="flex flex-col w-full">
|
||||
<div class="text-center text-5xl">
|
||||
{guideIcons[guide]}
|
||||
</div>
|
||||
<div class="text-2xl text-center my-3 w-full whitespace-normal px-6">
|
||||
<DocsLoader path={`${guide}.mdx`} titleOnly={true} />
|
||||
</div>
|
||||
<div class="flex flex-row justify-center flex-wrap gap-x-6 px-6">
|
||||
{#each guides[guide] as subGuide}
|
||||
<Button
|
||||
variant="link"
|
||||
href={getURLForLanguage($locale, `/help/${guide}/${subGuide}`)}
|
||||
class="h-fit px-0 py-1 text-muted-foreground text-base text-center whitespace-normal"
|
||||
>
|
||||
<svelte:component this={guideIcons[subGuide]} size="16" class="mr-1 shrink-0" />
|
||||
<DocsLoader path={`${guide}/${subGuide}.mdx`} titleOnly={true} />
|
||||
</Button>
|
||||
{/each}
|
||||
</div>
|
||||
</div>
|
||||
</Button>
|
||||
{/each}
|
||||
</div>
|
36
website/src/routes/[[language]]/help/[...guide]/+page.svelte
Normal file
36
website/src/routes/[[language]]/help/[...guide]/+page.svelte
Normal file
@@ -0,0 +1,36 @@
|
||||
<script lang="ts">
|
||||
import { page } from '$app/stores';
|
||||
import { getNextGuide, getPreviousGuide } from '$lib/components/docs/docs';
|
||||
import DocsLoader from '$lib/components/docs/DocsLoader.svelte';
|
||||
import { Button } from '$lib/components/ui/button';
|
||||
import { getURLForLanguage } from '$lib/utils';
|
||||
import { ChevronLeft, ChevronRight } from 'lucide-svelte';
|
||||
|
||||
$: previousGuide = getPreviousGuide($page.params.guide);
|
||||
$: nextGuide = getNextGuide($page.params.guide);
|
||||
</script>
|
||||
|
||||
<DocsLoader path="{$page.params.guide}.mdx" />
|
||||
|
||||
<div class="flex flex-row flex-wrap gap-3 pt-6">
|
||||
{#if previousGuide}
|
||||
<Button
|
||||
variant="outline"
|
||||
class="mr-auto"
|
||||
href={getURLForLanguage(undefined, `/help/${previousGuide}`)}
|
||||
>
|
||||
<ChevronLeft size="14" class="mr-1 mt-0.5" />
|
||||
<DocsLoader path="{previousGuide}.mdx" titleOnly={true} />
|
||||
</Button>
|
||||
{/if}
|
||||
{#if nextGuide}
|
||||
<Button
|
||||
variant="outline"
|
||||
class="ml-auto"
|
||||
href={getURLForLanguage(undefined, `/help/${nextGuide}`)}
|
||||
>
|
||||
<DocsLoader path="{nextGuide}.mdx" titleOnly={true} />
|
||||
<ChevronRight size="14" class="ml-1 mt-0.5" />
|
||||
</Button>
|
||||
{/if}
|
||||
</div>
|
Reference in New Issue
Block a user