mirror of
https://github.com/gpxstudio/gpx.studio.git
synced 2026-11-01 12:17:18 +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
@@ -15,9 +15,9 @@ export async function load({ params }) {
|
||||
const { language } = params;
|
||||
|
||||
const guideTitles: Record<string, string> = {};
|
||||
for (let guide of Object.keys(guides)) {
|
||||
for (const guide of Object.keys(guides)) {
|
||||
guideTitles[guide] = (await getModule(language, guide)).metadata.title;
|
||||
for (let subguide of guides[guide]) {
|
||||
for (const subguide of guides[guide]) {
|
||||
guideTitles[`${guide}/${subguide}`] = (
|
||||
await getModule(language, `${guide}/${subguide}`)
|
||||
).metadata.title;
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
<script lang="ts">
|
||||
import '../app.css';
|
||||
import { ModeWatcher } from 'mode-watcher';
|
||||
import { i18n } from '$lib/i18n.svelte';
|
||||
import { i18n } from '$lib/i18n/i18n.svelte';
|
||||
import { page } from '$app/state';
|
||||
import Nav from '$lib/components/Nav.svelte';
|
||||
import Footer from '$lib/components/Footer.svelte';
|
||||
import { onMount, type Snippet } from 'svelte';
|
||||
import { convertOldEmbeddingOptions } from '$lib/components/embedding/embedding';
|
||||
import { base } from '$app/paths';
|
||||
import { languages } from '$lib/languages';
|
||||
import { resolve } from '$app/paths';
|
||||
import { languages } from '$lib/i18n/languages';
|
||||
import { browser } from '$app/environment';
|
||||
import { goto } from '$app/navigation';
|
||||
import { getURLForLanguage } from '$lib/utils';
|
||||
@@ -31,7 +31,7 @@
|
||||
let folders = page.url.pathname.split('/');
|
||||
let locale =
|
||||
folders.indexOf('l') >= 0 ? (folders[folders.indexOf('l') + 1] ?? 'en') : 'en';
|
||||
window.location.href = `${getURLForLanguage(locale, '/embed')}?options=${encodeURIComponent(JSON.stringify(convertOldEmbeddingOptions(page.url.searchParams)))}`;
|
||||
window.location.href = `${resolve(getURLForLanguage(locale, '/embed'))}?options=${encodeURIComponent(JSON.stringify(convertOldEmbeddingOptions(page.url.searchParams)))}`;
|
||||
}
|
||||
});
|
||||
|
||||
@@ -40,10 +40,10 @@
|
||||
if (page.params.language) {
|
||||
let lang = page.params.language.replace('/', '');
|
||||
if (i18n.lang !== lang) {
|
||||
if (languages.hasOwnProperty(lang)) {
|
||||
if (Object.hasOwn(languages, lang)) {
|
||||
i18n.lang = lang;
|
||||
} else if (browser) {
|
||||
goto(`${base}/404`);
|
||||
goto(resolve('/404'));
|
||||
}
|
||||
}
|
||||
} else if (i18n.lang !== 'en') {
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
<script lang="ts">
|
||||
import { resolve } from '$app/paths';
|
||||
import { Button } from '$lib/components/ui/button';
|
||||
import Logo from '$lib/components/Logo.svelte';
|
||||
import { getURLForLanguage } from '$lib/utils';
|
||||
import { House, Map, BookOpenText } from '@lucide/svelte';
|
||||
import { i18n } from '$lib/i18n.svelte';
|
||||
import { i18n } from '$lib/i18n/i18n.svelte';
|
||||
</script>
|
||||
|
||||
<div class="grow px-12 my-12 flex flex-col items-center justify-center gap-6">
|
||||
@@ -12,7 +13,7 @@
|
||||
<Logo class="h-40 my-3 animate-spin" style="animation-duration: 20000ms" iconOnly={true} />
|
||||
<div class="w-full flex flex-row flex-wrap gap-3 justify-center">
|
||||
<Button
|
||||
href={getURLForLanguage(i18n.lang, '/')}
|
||||
href={resolve(getURLForLanguage(i18n.lang, '/'))}
|
||||
class="text-base w-1/4 min-w-fit rounded-full"
|
||||
>
|
||||
<House size="18" />
|
||||
@@ -20,14 +21,14 @@
|
||||
</Button>
|
||||
<Button
|
||||
data-sveltekit-reload
|
||||
href={getURLForLanguage(i18n.lang, '/app')}
|
||||
href={resolve(getURLForLanguage(i18n.lang, '/app'))}
|
||||
class="text-base w-1/4 min-w-fit rounded-full"
|
||||
>
|
||||
<Map size="18" />
|
||||
{i18n._('homepage.app')}
|
||||
</Button>
|
||||
<Button
|
||||
href={getURLForLanguage(i18n.lang, '/help')}
|
||||
href={resolve(getURLForLanguage(i18n.lang, '/help'))}
|
||||
class="text-base w-1/4 min-w-fit rounded-full"
|
||||
>
|
||||
<BookOpenText size="18" />
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { languages } from '$lib/languages';
|
||||
import { languages } from '$lib/i18n/languages';
|
||||
import type { EntryGenerator } from './$types';
|
||||
|
||||
export const entries: EntryGenerator = () => {
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
<script lang="ts">
|
||||
import { resolve } from '$app/paths';
|
||||
import { Button } from '$lib/components/ui/button';
|
||||
import ElevationProfile from '$lib/components/elevation-profile/ElevationProfile.svelte';
|
||||
import GPXStatistics from '$lib/components/GPXStatistics.svelte';
|
||||
@@ -16,16 +17,16 @@
|
||||
Languages,
|
||||
ExternalLink,
|
||||
} from '@lucide/svelte';
|
||||
import { i18n } from '$lib/i18n.svelte';
|
||||
import { i18n } from '$lib/i18n/i18n.svelte';
|
||||
import { exampleStatistics } from '$lib/data/example';
|
||||
import { getURLForLanguage } from '$lib/utils';
|
||||
import { exampleGPXFile } from '$lib/assets/example';
|
||||
import { writable } from 'svelte/store';
|
||||
import Scissors from '$lib/components/toolbar/tools/scissors/Scissors.svelte';
|
||||
import { currentTool, Tool } from '$lib/components/toolbar/tools';
|
||||
import { onDestroy, onMount } from 'svelte';
|
||||
|
||||
let gpxStatistics = writable(exampleGPXFile.getStatistics());
|
||||
let slicedGPXStatistics = writable(undefined);
|
||||
let statistics = writable(exampleStatistics);
|
||||
let slicedStatistics = writable(undefined);
|
||||
let hoveredPoint = writable(null);
|
||||
let additionalDatasets = writable(['speed', 'atemp']);
|
||||
let elevationFill = writable(undefined);
|
||||
@@ -58,7 +59,7 @@
|
||||
<div class="w-full flex flex-row xs:justify-center gap-3">
|
||||
<Button
|
||||
data-sveltekit-reload
|
||||
href={getURLForLanguage(i18n.lang, '/app')}
|
||||
href={resolve(getURLForLanguage(i18n.lang, '/app'))}
|
||||
class="w-1/3 min-w-fit"
|
||||
>
|
||||
<Map size="18" />
|
||||
@@ -66,7 +67,7 @@
|
||||
</Button>
|
||||
<Button
|
||||
variant="secondary"
|
||||
href={getURLForLanguage(i18n.lang, '/help')}
|
||||
href={resolve(getURLForLanguage(i18n.lang, '/help'))}
|
||||
class="w-1/3 min-w-fit"
|
||||
>
|
||||
<BookOpenText size="18" />
|
||||
@@ -74,7 +75,10 @@
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
<a href={getURLForLanguage(i18n.lang, '/app')} aria-label="Link to the web app.">
|
||||
<a
|
||||
href={resolve(getURLForLanguage(i18n.lang, '/app'))}
|
||||
aria-label="Link to the web app."
|
||||
>
|
||||
<enhanced:img
|
||||
src="/src/lib/assets/img/docs/getting-started/interface.webp"
|
||||
alt="The gpx.studio interface."
|
||||
@@ -138,18 +142,14 @@
|
||||
>
|
||||
<div class="grow">
|
||||
<ElevationProfile
|
||||
{gpxStatistics}
|
||||
{slicedGPXStatistics}
|
||||
{statistics}
|
||||
{slicedStatistics}
|
||||
{hoveredPoint}
|
||||
{additionalDatasets}
|
||||
{elevationFill}
|
||||
/>
|
||||
</div>
|
||||
<GPXStatistics
|
||||
{gpxStatistics}
|
||||
{slicedGPXStatistics}
|
||||
orientation={'horizontal'}
|
||||
/>
|
||||
<GPXStatistics {statistics} {slicedStatistics} orientation="horizontal" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="grid md:grid-rows-subgrid md:row-start-3 md:row-end-5 gap-4">
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { languages } from '$lib/languages';
|
||||
import { languages } from '$lib/i18n/languages';
|
||||
import type { EntryGenerator } from './$types';
|
||||
|
||||
export const entries: EntryGenerator = () => {
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
<script lang="ts">
|
||||
import { resolve } from '$app/paths';
|
||||
import GPXLayers from '$lib/components/map/gpx-layer/GPXLayers.svelte';
|
||||
import ElevationProfile from '$lib/components/elevation-profile/ElevationProfile.svelte';
|
||||
import FileList from '$lib/components/file-list/FileList.svelte';
|
||||
import Explorer from '$lib/components/explorer/Explorer.svelte';
|
||||
import GPXStatistics from '$lib/components/GPXStatistics.svelte';
|
||||
import Map from '$lib/components/map/Map.svelte';
|
||||
import Menu from '$lib/components/Menu.svelte';
|
||||
@@ -11,57 +13,100 @@
|
||||
import CoordinatesPopup from '$lib/components/map/CoordinatesPopup.svelte';
|
||||
import Resizer from '$lib/components/Resizer.svelte';
|
||||
import { Toaster } from '$lib/components/ui/sonner';
|
||||
import { i18n } from '$lib/i18n.svelte';
|
||||
import { settings } from '$lib/logic/settings';
|
||||
import { loadFiles } from '$lib/logic/file-actions';
|
||||
import { onDestroy, onMount } from 'svelte';
|
||||
import * as AlertDialog from '$lib/components/ui/alert-dialog';
|
||||
import { i18n } from '$lib/i18n/i18n.svelte';
|
||||
import { settings } from '$lib/state/settings';
|
||||
import { loadFiles } from '$lib/actions/file-actions';
|
||||
import { onMount } from 'svelte';
|
||||
import { page } from '$app/state';
|
||||
import { gpxStatistics, hoveredPoint, slicedGPXStatistics } from '$lib/logic/statistics';
|
||||
import { goto } from '$app/navigation';
|
||||
import { get } from 'svelte/store';
|
||||
import { getURLForLanguage } from '$lib/utils';
|
||||
import { inDesktopApp } from '$lib/engine/backend';
|
||||
import { openLinksInBrowser } from '$lib/desktop/links';
|
||||
import { hoveredPoint, slicedStatistics } from '$lib/state/selection-statistics';
|
||||
import { engine } from '$lib/engine';
|
||||
import { boundsManager } from '$lib/state/bounds';
|
||||
import { getURLForGoogleDriveFile } from '$lib/components/embedding/embedding';
|
||||
import { db } from '$lib/db';
|
||||
import { fileStateCollection } from '$lib/logic/file-state';
|
||||
|
||||
const outdated = engine.outdated;
|
||||
const workspace = engine.workspace;
|
||||
|
||||
const {
|
||||
treeFileView,
|
||||
elevationProfile,
|
||||
bottomPanelSize,
|
||||
rightPanelSize,
|
||||
explorerPanelSize,
|
||||
additionalDatasets,
|
||||
elevationFill,
|
||||
} = settings;
|
||||
|
||||
let bottomPanelWidth: number | undefined = $state();
|
||||
// the file list and the explorer share the right panel, half of it each until it is resized
|
||||
let rightPanelHeight = $state(0);
|
||||
let explorerHeight = $derived(
|
||||
$treeFileView
|
||||
? Math.min(
|
||||
Math.max($explorerPanelSize > 0 ? $explorerPanelSize : rightPanelHeight / 2, 60),
|
||||
Math.max(rightPanelHeight - 80, 60)
|
||||
)
|
||||
: rightPanelHeight
|
||||
);
|
||||
let bottomPanelOrientation = $derived(
|
||||
bottomPanelWidth && bottomPanelWidth >= 540 && $elevationProfile ? 'horizontal' : 'vertical'
|
||||
);
|
||||
|
||||
onMount(async () => {
|
||||
settings.connectToDatabase(db);
|
||||
fileStateCollection.connectToDatabase(db).then(() => {
|
||||
let files: string[] = JSON.parse(page.url.searchParams.get('files') || '[]');
|
||||
let ids: string[] = JSON.parse(page.url.searchParams.get('ids') || '[]');
|
||||
let urls: string[] = files.concat(ids.map(getURLForGoogleDriveFile));
|
||||
// the language of the page, which is in its address (none for English)
|
||||
let language = $derived(page.params.language ?? 'en');
|
||||
// whether the language that was chosen last was gone back to (the desktop app)
|
||||
let languageRestored = $state(false);
|
||||
|
||||
if (urls.length > 0) {
|
||||
let downloads: Promise<File | null>[] = [];
|
||||
urls.forEach((url) => {
|
||||
downloads.push(
|
||||
fetch(url)
|
||||
.then((response) => response.blob())
|
||||
.then((blob) => new File([blob], url.split('/').pop() ?? ''))
|
||||
);
|
||||
});
|
||||
|
||||
Promise.all(downloads).then((files) => {
|
||||
loadFiles(files.filter((file) => file !== null));
|
||||
});
|
||||
}
|
||||
});
|
||||
$effect(() => {
|
||||
if (languageRestored) {
|
||||
settings.language.set(language);
|
||||
}
|
||||
});
|
||||
|
||||
onDestroy(() => {
|
||||
settings.disconnectFromDatabase();
|
||||
fileStateCollection.disconnectFromDatabase();
|
||||
onMount(async () => {
|
||||
// files that the system opens with the app while it runs
|
||||
engine.onFilesOpened((ids) => boundsManager.fitBoundsOnLoad(ids));
|
||||
settings.connect(await engine.openStorage());
|
||||
|
||||
if (inDesktopApp()) {
|
||||
openLinksInBrowser();
|
||||
// the window of the desktop app always opens the same page, in English: it goes to the
|
||||
// language that was chosen last
|
||||
const saved = get(settings.language);
|
||||
if (saved !== undefined && saved !== language) {
|
||||
await goto(
|
||||
resolve(
|
||||
`${getURLForLanguage(saved, page.url.pathname)}${page.url.search}${page.url.hash}`
|
||||
),
|
||||
{ replaceState: true }
|
||||
);
|
||||
}
|
||||
languageRestored = true;
|
||||
}
|
||||
|
||||
let files: string[] = JSON.parse(page.url.searchParams.get('files') || '[]');
|
||||
let ids: string[] = JSON.parse(page.url.searchParams.get('ids') || '[]');
|
||||
let urls: string[] = files.concat(ids.map(getURLForGoogleDriveFile));
|
||||
|
||||
if (urls.length > 0) {
|
||||
let downloads: Promise<File | null>[] = [];
|
||||
urls.forEach((url) => {
|
||||
downloads.push(
|
||||
fetch(url)
|
||||
.then((response) => response.blob())
|
||||
.then((blob) => new File([blob], url.split('/').pop() ?? ''))
|
||||
);
|
||||
});
|
||||
|
||||
Promise.all(downloads).then((files) => {
|
||||
loadFiles(files.filter((file) => file !== null));
|
||||
});
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
@@ -118,7 +163,7 @@
|
||||
<CoordinatesPopup />
|
||||
<Toaster richColors />
|
||||
{#if !$treeFileView}
|
||||
<div class="h-10 -translate-y-10 w-full pointer-events-none absolute z-30">
|
||||
<div class="h-10 w-full pointer-events-none absolute bottom-0 left-0 z-30">
|
||||
<FileList orientation="horizontal" />
|
||||
</div>
|
||||
{/if}
|
||||
@@ -139,14 +184,14 @@
|
||||
style={$elevationProfile ? `height: ${$bottomPanelSize}px` : ''}
|
||||
>
|
||||
<GPXStatistics
|
||||
{gpxStatistics}
|
||||
{slicedGPXStatistics}
|
||||
statistics={engine.statistics}
|
||||
{slicedStatistics}
|
||||
orientation={bottomPanelOrientation == 'horizontal' ? 'vertical' : 'horizontal'}
|
||||
/>
|
||||
{#if $elevationProfile}
|
||||
<ElevationProfile
|
||||
{gpxStatistics}
|
||||
{slicedGPXStatistics}
|
||||
statistics={engine.statistics}
|
||||
{slicedStatistics}
|
||||
{hoveredPoint}
|
||||
{additionalDatasets}
|
||||
{elevationFill}
|
||||
@@ -154,12 +199,60 @@
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
{#if $treeFileView}
|
||||
{#if $treeFileView || $workspace?.folder}
|
||||
<Resizer orientation="col" bind:after={$rightPanelSize} minAfter={100} maxAfter={400} />
|
||||
<FileList orientation="vertical" recursive={true} style="width: {$rightPanelSize}px" />
|
||||
<div
|
||||
bind:clientHeight={rightPanelHeight}
|
||||
class="flex flex-col h-full min-h-0 shrink-0"
|
||||
style="width: {$rightPanelSize}px"
|
||||
>
|
||||
{#if $treeFileView}
|
||||
{#if $workspace}
|
||||
<div class="px-2 py-1 text-sm font-semibold shrink-0">
|
||||
{i18n._('menu.workspace')}
|
||||
</div>
|
||||
{/if}
|
||||
<!-- a row, as the file list was: its scroll area is as high as this, whatever its content -->
|
||||
<div class="grow min-h-0 flex flex-row">
|
||||
<FileList
|
||||
orientation="vertical"
|
||||
recursive={true}
|
||||
style="width: {$rightPanelSize}px"
|
||||
/>
|
||||
</div>
|
||||
{/if}
|
||||
<!-- the open folder (the desktop app) under the files that are open -->
|
||||
{#if $workspace?.folder}
|
||||
{#if $treeFileView}
|
||||
<Resizer
|
||||
orientation="row"
|
||||
bind:after={() => explorerHeight, (height) => ($explorerPanelSize = height)}
|
||||
minAfter={60}
|
||||
maxAfter={Math.max(rightPanelHeight - 80, 60)}
|
||||
/>
|
||||
{/if}
|
||||
<div class="flex flex-col min-h-0 shrink-0" style="height: {explorerHeight}px">
|
||||
<Explorer />
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<AlertDialog.Root open={$outdated}>
|
||||
<AlertDialog.Content escapeKeydownBehavior="ignore" interactOutsideBehavior="ignore">
|
||||
<AlertDialog.Header>
|
||||
<AlertDialog.Title>{i18n._('outdated.title')}</AlertDialog.Title>
|
||||
<AlertDialog.Description>{i18n._('outdated.description')}</AlertDialog.Description>
|
||||
</AlertDialog.Header>
|
||||
<AlertDialog.Footer>
|
||||
<AlertDialog.Action onclick={() => location.reload()}>
|
||||
{i18n._('outdated.reload')}
|
||||
</AlertDialog.Action>
|
||||
</AlertDialog.Footer>
|
||||
</AlertDialog.Content>
|
||||
</AlertDialog.Root>
|
||||
|
||||
<style lang="postcss">
|
||||
@reference "tailwindcss";
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { languages } from '$lib/languages';
|
||||
import { languages } from '$lib/i18n/languages';
|
||||
import type { EntryGenerator } from './$types';
|
||||
|
||||
export const entries: EntryGenerator = () => {
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
<script lang="ts">
|
||||
import { resolve } from '$app/paths';
|
||||
import { Button } from '$lib/components/ui/button';
|
||||
import AlgoliaDocSearch from '$lib/components/AlgoliaDocSearch.svelte';
|
||||
import { getURLForLanguage } from '$lib/utils';
|
||||
import { i18n } from '$lib/i18n.svelte';
|
||||
import { i18n } from '$lib/i18n/i18n.svelte';
|
||||
import { page } from '$app/state';
|
||||
import { guides } from '$lib/components/docs/docs';
|
||||
import type { Snippet } from 'svelte';
|
||||
@@ -24,10 +25,10 @@
|
||||
<div class="mb-2">
|
||||
<AlgoliaDocSearch />
|
||||
</div>
|
||||
{#each Object.keys(guides) as guide}
|
||||
{#each Object.keys(guides) as guide (guide)}
|
||||
<Button
|
||||
variant="link"
|
||||
href={getURLForLanguage(i18n.lang, `/help/${guide}`)}
|
||||
href={resolve(getURLForLanguage(i18n.lang, `/help/${guide}`))}
|
||||
class="min-h-5 h-fit p-0 w-fit border-none 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'
|
||||
@@ -35,10 +36,10 @@
|
||||
>
|
||||
{data.guideTitles[guide]}
|
||||
</Button>
|
||||
{#each guides[guide] as subGuide}
|
||||
{#each guides[guide] as subGuide (subGuide)}
|
||||
<Button
|
||||
variant="link"
|
||||
href={getURLForLanguage(i18n.lang, `/help/${guide}/${subGuide}`)}
|
||||
href={resolve(getURLForLanguage(i18n.lang, `/help/${guide}/${subGuide}`))}
|
||||
class="min-h-5 h-fit p-0 w-fit border-none 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
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { languages } from '$lib/languages';
|
||||
import { languages } from '$lib/i18n/languages';
|
||||
import type { EntryGenerator } from './$types';
|
||||
|
||||
export const entries: EntryGenerator = () => {
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
<script lang="ts">
|
||||
import { resolve } from '$app/paths';
|
||||
import { Button } from '$lib/components/ui/button';
|
||||
import { getURLForLanguage } from '$lib/utils';
|
||||
import { i18n } from '$lib/i18n.svelte';
|
||||
import { i18n } from '$lib/i18n/i18n.svelte';
|
||||
import { guides, guideIcons } from '$lib/components/docs/docs';
|
||||
|
||||
let {
|
||||
@@ -14,10 +15,10 @@
|
||||
</script>
|
||||
|
||||
<div class="grid grid-cols-1 sm:grid-cols-2 gap-3">
|
||||
{#each Object.keys(guides) as guide}
|
||||
{#each Object.keys(guides) as guide (guide)}
|
||||
<Button
|
||||
variant="outline"
|
||||
href={getURLForLanguage(i18n.lang, `/help/${guide}`)}
|
||||
href={resolve(getURLForLanguage(i18n.lang, `/help/${guide}`))}
|
||||
class="min-h-36 h-full pt-6 pb-3 px-0"
|
||||
>
|
||||
<div class="flex flex-col w-full">
|
||||
@@ -28,10 +29,12 @@
|
||||
{data.guideTitles[guide]}
|
||||
</div>
|
||||
<div class="flex flex-row justify-center flex-wrap gap-x-6 px-6">
|
||||
{#each guides[guide] as subGuide}
|
||||
{#each guides[guide] as subGuide (subGuide)}
|
||||
<Button
|
||||
variant="link"
|
||||
href={getURLForLanguage(i18n.lang, `/help/${guide}/${subGuide}`)}
|
||||
href={resolve(
|
||||
getURLForLanguage(i18n.lang, `/help/${guide}/${subGuide}`)
|
||||
)}
|
||||
class="min-h-8 h-fit min-w-24 px-0 py-1 text-muted-foreground text-base text-center whitespace-normal"
|
||||
>
|
||||
{#if typeof guideIcons[subGuide] === 'string'}
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
import { languages } from '$lib/languages';
|
||||
import { languages } from '$lib/i18n/languages';
|
||||
import { guides } from '$lib/components/docs/docs';
|
||||
import type { EntryGenerator } from './$types';
|
||||
|
||||
export const entries: EntryGenerator = () => {
|
||||
let entries = [];
|
||||
for (let lang of Object.keys(languages)) {
|
||||
for (let guide of Object.keys(guides)) {
|
||||
const entries = [];
|
||||
for (const lang of Object.keys(languages)) {
|
||||
for (const guide of Object.keys(guides)) {
|
||||
entries.push({
|
||||
language: lang == 'en' ? '' : lang,
|
||||
guide,
|
||||
});
|
||||
for (let subguide of guides[guide]) {
|
||||
for (const subguide of guides[guide]) {
|
||||
entries.push({
|
||||
language: lang == 'en' ? '' : lang,
|
||||
guide: `${guide}/${subguide}`,
|
||||
|
||||
@@ -1,16 +1,21 @@
|
||||
<script lang="ts">
|
||||
import { resolve } from '$app/paths';
|
||||
import { page } from '$app/state';
|
||||
import { getNextGuide, getPreviousGuide } from '$lib/components/docs/docs';
|
||||
import DocsContainer from '$lib/components/docs/DocsContainer.svelte';
|
||||
import { Button } from '$lib/components/ui/button';
|
||||
import { getURLForLanguage } from '$lib/utils';
|
||||
import { ChevronLeft, ChevronRight, PenLine, CornerDownRight } from '@lucide/svelte';
|
||||
import { i18n } from '$lib/i18n.svelte';
|
||||
import { i18n } from '$lib/i18n/i18n.svelte';
|
||||
import type { Component } from 'svelte';
|
||||
|
||||
let { data }: { data: { guideModule: any; guideTitles: Record<string, string> } } = $props();
|
||||
let {
|
||||
data,
|
||||
}: { data: { guideModule: { default: Component }; guideTitles: Record<string, string> } } =
|
||||
$props();
|
||||
|
||||
let previousGuide = $derived(getPreviousGuide(page.params.guide));
|
||||
let nextGuide = $derived(getNextGuide(page.params.guide));
|
||||
let previousGuide = $derived(getPreviousGuide(page.params.guide ?? ''));
|
||||
let nextGuide = $derived(getNextGuide(page.params.guide ?? ''));
|
||||
</script>
|
||||
|
||||
<div class="markdown flex flex-col gap-3">
|
||||
@@ -22,7 +27,7 @@
|
||||
<Button
|
||||
variant="outline"
|
||||
class="mr-auto"
|
||||
href={getURLForLanguage(i18n.lang, `/help/${previousGuide}`)}
|
||||
href={resolve(getURLForLanguage(i18n.lang, `/help/${previousGuide}`))}
|
||||
>
|
||||
<ChevronLeft size="14" class="mt-0.5" />
|
||||
{data.guideTitles[previousGuide]}
|
||||
@@ -32,7 +37,7 @@
|
||||
<Button
|
||||
variant="outline"
|
||||
class="ml-auto"
|
||||
href={getURLForLanguage(i18n.lang, `/help/${nextGuide}`)}
|
||||
href={resolve(getURLForLanguage(i18n.lang, `/help/${nextGuide}`))}
|
||||
>
|
||||
{data.guideTitles[nextGuide]}
|
||||
<ChevronRight size="14" class="mt-0.5" />
|
||||
|
||||
Reference in new issue
Block a user