Compare commits
55 Commits
02efe708c2
...
dev
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
08d1f1d5e4 | ||
|
|
f3bf4f0096 | ||
|
|
b4094d0a5b | ||
|
|
36122b4ac5 | ||
|
|
dd9aba3adb | ||
|
|
bd40fbae74 | ||
|
|
690cbc49cc | ||
|
|
36b16ddeef | ||
|
|
16b8988fa7 | ||
|
|
40f97b7c35 | ||
|
|
54b3113480 | ||
|
|
7e9140492a | ||
|
|
79c0aed54f | ||
|
|
cb5a74de00 | ||
|
|
31f25f346a | ||
|
|
b3a11125a5 | ||
|
|
71cdc03da5 | ||
|
|
694e73a677 | ||
|
|
5aaacccef9 | ||
|
|
f2bf043900 | ||
|
|
ba251fe407 | ||
|
|
421aa9dc69 | ||
|
|
b4a7f1353b | ||
|
|
30de3c6db5 | ||
|
|
8aafa26238 | ||
|
|
5768391305 | ||
|
|
1d204bacf2 | ||
|
|
4d1d5d48c0 | ||
|
|
5a6321535e | ||
|
|
e077de9f48 | ||
|
|
7586f03998 | ||
|
|
af8c22dcda | ||
|
|
3dce5dc617 | ||
|
|
84b90e1026 | ||
|
|
d507586eed | ||
|
|
57afaedf83 | ||
|
|
48063b9066 | ||
|
|
452d356599 | ||
|
|
25eda8041e | ||
|
|
ae4d5356eb | ||
|
|
3343bb906e | ||
|
|
7b17900160 | ||
|
|
d5f1fe1c7b | ||
|
|
553f73f992 | ||
|
|
c8cedf2e2c | ||
|
|
a751817847 | ||
|
|
d1ef12db8d | ||
|
|
43d73edf29 | ||
|
|
5a0b8c376c | ||
|
|
9743fd460e | ||
|
|
f70f92a176 | ||
|
|
1a4175446c | ||
|
|
ed6dfab4c1 | ||
|
|
6a6e1105c0 | ||
|
|
1677fe254b |
@@ -5,7 +5,7 @@
|
|||||||
|
|
||||||
[**gpx.studio**](https://gpx.studio) is an online tool for creating and editing GPX files.
|
[**gpx.studio**](https://gpx.studio) is an online tool for creating and editing GPX files.
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
This repository contains the source code of the website.
|
This repository contains the source code of the website.
|
||||||
|
|
||||||
|
|||||||
@@ -1,9 +1,8 @@
|
|||||||
{
|
{
|
||||||
"$schema": "https://shadcn-svelte.com/schema.json",
|
"$schema": "https://shadcn-svelte.com/schema.json",
|
||||||
"style": "default",
|
|
||||||
"tailwind": {
|
"tailwind": {
|
||||||
"css": "src/app.css",
|
"css": "src/app.css",
|
||||||
"baseColor": "slate"
|
"baseColor": "neutral"
|
||||||
},
|
},
|
||||||
"aliases": {
|
"aliases": {
|
||||||
"components": "$lib/components",
|
"components": "$lib/components",
|
||||||
@@ -13,5 +12,9 @@
|
|||||||
"lib": "$lib"
|
"lib": "$lib"
|
||||||
},
|
},
|
||||||
"typescript": true,
|
"typescript": true,
|
||||||
"registry": "https://shadcn-svelte.com/registry"
|
"registry": "https://shadcn-svelte.com/registry",
|
||||||
|
"style": "nova",
|
||||||
|
"iconLibrary": "lucide",
|
||||||
|
"menuColor": "default",
|
||||||
|
"menuAccent": "subtle"
|
||||||
}
|
}
|
||||||
|
|||||||
7456
website/package-lock.json
generated
@@ -14,7 +14,9 @@
|
|||||||
"format": "prettier --write . --config ../.prettierrc --ignore-path ../.prettierignore --ignore-path ./.gitignore"
|
"format": "prettier --write . --config ../.prettierrc --ignore-path ../.prettierignore --ignore-path ./.gitignore"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@lucide/svelte": "^0.544.0",
|
"@fontsource-variable/inter": "^5.2.8",
|
||||||
|
"@internationalized/date": "^3.12.0",
|
||||||
|
"@lucide/svelte": "^1.7.0",
|
||||||
"@sveltejs/adapter-static": "^3.0.8",
|
"@sveltejs/adapter-static": "^3.0.8",
|
||||||
"@sveltejs/enhanced-img": "^0.6.0",
|
"@sveltejs/enhanced-img": "^0.6.0",
|
||||||
"@sveltejs/kit": "^2.21.2",
|
"@sveltejs/kit": "^2.21.2",
|
||||||
@@ -30,7 +32,8 @@
|
|||||||
"@types/sortablejs": "^1.15.8",
|
"@types/sortablejs": "^1.15.8",
|
||||||
"@typescript-eslint/eslint-plugin": "^8.33.1",
|
"@typescript-eslint/eslint-plugin": "^8.33.1",
|
||||||
"@typescript-eslint/parser": "^8.33.1",
|
"@typescript-eslint/parser": "^8.33.1",
|
||||||
"bits-ui": "^2.14.4",
|
"bits-ui": "^2.17.2",
|
||||||
|
"clsx": "^2.1.1",
|
||||||
"eslint": "^9.28.0",
|
"eslint": "^9.28.0",
|
||||||
"eslint-config-prettier": "^10.1.5",
|
"eslint-config-prettier": "^10.1.5",
|
||||||
"eslint-plugin-svelte": "^3.9.1",
|
"eslint-plugin-svelte": "^3.9.1",
|
||||||
@@ -43,30 +46,29 @@
|
|||||||
"postcss": "^8.4.47",
|
"postcss": "^8.4.47",
|
||||||
"prettier": "^3.5.3",
|
"prettier": "^3.5.3",
|
||||||
"prettier-plugin-svelte": "^3.4.0",
|
"prettier-plugin-svelte": "^3.4.0",
|
||||||
|
"shadcn-svelte": "^1.2.7",
|
||||||
"svelte": "^5.33.18",
|
"svelte": "^5.33.18",
|
||||||
"svelte-check": "^4.0.0",
|
"svelte-check": "^4.0.0",
|
||||||
"svelte-dnd-action": "^0.9.65",
|
"svelte-dnd-action": "^0.9.65",
|
||||||
"svelte-sonner": "^1.0.5",
|
"svelte-sonner": "^1.1.0",
|
||||||
"tailwind-variants": "^3.1.1",
|
"tailwind-merge": "^3.5.0",
|
||||||
|
"tailwind-variants": "^3.2.2",
|
||||||
"tailwindcss": "^4.1.8",
|
"tailwindcss": "^4.1.8",
|
||||||
"tslib": "^2.8.1",
|
"tslib": "^2.8.1",
|
||||||
"tsx": "^4.19.1",
|
"tsx": "^4.19.1",
|
||||||
"tw-animate-css": "^1.3.4",
|
"tw-animate-css": "^1.4.0",
|
||||||
"typescript": "^5.8.3",
|
"typescript": "^5.8.3",
|
||||||
"vaul-svelte": "^1.0.0-next.7",
|
"vaul-svelte": "^1.0.0-next.7",
|
||||||
"vite": "^6.3.5",
|
"vite": "^6.3.5"
|
||||||
"vite-plugin-node-polyfills": "^0.23.0"
|
|
||||||
},
|
},
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@docsearch/js": "^3.9.0",
|
"@docsearch/js": "^3.9.0",
|
||||||
"@internationalized/date": "^3.8.2",
|
|
||||||
"@mapbox/sphericalmercator": "^2.0.1",
|
"@mapbox/sphericalmercator": "^2.0.1",
|
||||||
"@mapbox/tilebelt": "^2.0.2",
|
"@mapbox/tilebelt": "^2.0.2",
|
||||||
"@maplibre/maplibre-gl-geocoder": "^1.9.4",
|
"@maplibre/maplibre-gl-geocoder": "^1.9.4",
|
||||||
"chart.js": "^4.5.1",
|
"chart.js": "^4.5.1",
|
||||||
"chartjs-plugin-zoom": "^2.2.0",
|
"chartjs-plugin-zoom": "^2.2.0",
|
||||||
"clsx": "^2.1.1",
|
|
||||||
"dexie": "^4.0.11",
|
"dexie": "^4.0.11",
|
||||||
"file-saver": "^2.0.5",
|
"file-saver": "^2.0.5",
|
||||||
"gpx": "file:../gpx",
|
"gpx": "file:../gpx",
|
||||||
@@ -75,7 +77,6 @@
|
|||||||
"mapillary-js": "^4.1.2",
|
"mapillary-js": "^4.1.2",
|
||||||
"maplibre-gl": "^5.21.1",
|
"maplibre-gl": "^5.21.1",
|
||||||
"sanitize-html": "^2.17.0",
|
"sanitize-html": "^2.17.0",
|
||||||
"sortablejs": "^1.15.6",
|
"sortablejs": "^1.15.6"
|
||||||
"tailwind-merge": "^3.3.0"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,76 +1,93 @@
|
|||||||
@import 'tailwindcss';
|
@import 'tailwindcss';
|
||||||
@import 'tw-animate-css';
|
@import 'tw-animate-css';
|
||||||
|
@import "shadcn-svelte/tailwind.css";
|
||||||
|
@import "@fontsource-variable/inter";
|
||||||
|
|
||||||
@custom-variant dark (&:is(.dark *));
|
@custom-variant dark (&:is(.dark *));
|
||||||
|
|
||||||
:root {
|
:root {
|
||||||
--background: hsl(0 0% 100%) /* <- Wrap in HSL */;
|
--background: oklch(1 0 0);
|
||||||
--foreground: hsl(240 10% 3.9%);
|
--foreground: oklch(0.145 0 0);
|
||||||
--muted: hsl(240 4.8% 95.9%);
|
--muted: oklch(0.97 0 0);
|
||||||
--muted-foreground: hsl(240 3.8% 46.1%);
|
--muted-foreground: oklch(0.556 0 0);
|
||||||
--popover: hsl(0 0% 100%);
|
--popover: oklch(1 0 0);
|
||||||
--popover-foreground: hsl(240 10% 3.9%);
|
--popover-foreground: oklch(0.145 0 0);
|
||||||
--card: hsl(0 0% 100%);
|
--card: oklch(1 0 0);
|
||||||
--card-foreground: hsl(240 10% 3.9%);
|
--card-foreground: oklch(0.145 0 0);
|
||||||
--border: hsl(240 5.9% 90%);
|
--border: oklch(0.922 0 0);
|
||||||
--input: hsl(240 5.9% 90%);
|
--input: oklch(0.922 0 0);
|
||||||
--primary: hsl(240 5.9% 10%);
|
--primary: oklch(0.205 0 0);
|
||||||
--primary-foreground: hsl(0 0% 98%);
|
--primary-foreground: oklch(0.985 0 0);
|
||||||
--secondary: hsl(240 4.8% 95.9%);
|
--secondary: oklch(0.97 0 0);
|
||||||
--secondary-foreground: hsl(240 5.9% 10%);
|
--secondary-foreground: oklch(0.205 0 0);
|
||||||
--accent: hsl(240 4.8% 95.9%);
|
--accent: oklch(0.97 0 0);
|
||||||
--accent-foreground: hsl(240 5.9% 10%);
|
--accent-foreground: oklch(0.205 0 0);
|
||||||
--destructive: hsl(0 72.2% 50.6%);
|
--destructive: oklch(0.577 0.245 27.325);
|
||||||
--destructive-foreground: hsl(0 0% 98%);
|
--destructive-foreground: hsl(0 0% 98%);
|
||||||
--ring: hsl(240 10% 3.9%);
|
--ring: oklch(0.708 0 0);
|
||||||
--sidebar: hsl(0 0% 98%);
|
--sidebar: oklch(0.985 0 0);
|
||||||
--sidebar-foreground: hsl(240 5.3% 26.1%);
|
--sidebar-foreground: oklch(0.145 0 0);
|
||||||
--sidebar-primary: hsl(240 5.9% 10%);
|
--sidebar-primary: oklch(0.205 0 0);
|
||||||
--sidebar-primary-foreground: hsl(0 0% 98%);
|
--sidebar-primary-foreground: oklch(0.985 0 0);
|
||||||
--sidebar-accent: hsl(240 4.8% 95.9%);
|
--sidebar-accent: oklch(0.97 0 0);
|
||||||
--sidebar-accent-foreground: hsl(240 5.9% 10%);
|
--sidebar-accent-foreground: oklch(0.205 0 0);
|
||||||
--sidebar-border: hsl(220 13% 91%);
|
--sidebar-border: oklch(0.922 0 0);
|
||||||
--sidebar-ring: hsl(217.2 91.2% 59.8%);
|
--sidebar-ring: oklch(0.708 0 0);
|
||||||
|
|
||||||
--support: rgb(220 15 130);
|
--support: rgb(220 15 130);
|
||||||
--link: rgb(0 110 180);
|
--link: rgb(0 110 180);
|
||||||
--selection: hsl(240 4.8% 93%);
|
--selection: hsl(240 4.8% 93%);
|
||||||
|
|
||||||
--radius: 0.5rem;
|
--radius: 0.5rem;
|
||||||
|
|
||||||
|
--chart-1: oklch(0.87 0 0);
|
||||||
|
|
||||||
|
--chart-2: oklch(0.556 0 0);
|
||||||
|
|
||||||
|
--chart-3: oklch(0.439 0 0);
|
||||||
|
|
||||||
|
--chart-4: oklch(0.371 0 0);
|
||||||
|
|
||||||
|
--chart-5: oklch(0.269 0 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
.dark {
|
.dark {
|
||||||
--background: hsl(240 10% 3.9%);
|
--background: oklch(0.145 0 0);
|
||||||
--foreground: hsl(0 0% 98%);
|
--foreground: oklch(0.985 0 0);
|
||||||
--muted: hsl(240 3.7% 15.9%);
|
--muted: oklch(0.269 0 0);
|
||||||
--muted-foreground: hsl(240 5% 64.9%);
|
--muted-foreground: oklch(0.708 0 0);
|
||||||
--popover: hsl(240 10% 3.9%);
|
--popover: oklch(0.205 0 0);
|
||||||
--popover-foreground: hsl(0 0% 98%);
|
--popover-foreground: oklch(0.985 0 0);
|
||||||
--card: hsl(240 10% 3.9%);
|
--card: oklch(0.205 0 0);
|
||||||
--card-foreground: hsl(0 0% 98%);
|
--card-foreground: oklch(0.985 0 0);
|
||||||
--border: hsl(240 3.7% 15.9%);
|
--border: oklch(1 0 0 / 10%);
|
||||||
--input: hsl(240 3.7% 15.9%);
|
--input: oklch(1 0 0 / 15%);
|
||||||
--primary: hsl(0 0% 98%);
|
--primary: oklch(0.922 0 0);
|
||||||
--primary-foreground: hsl(240 5.9% 10%);
|
--primary-foreground: oklch(0.205 0 0);
|
||||||
--secondary: hsl(240 3.7% 15.9%);
|
--secondary: oklch(0.269 0 0);
|
||||||
--secondary-foreground: hsl(0 0% 98%);
|
--secondary-foreground: oklch(0.985 0 0);
|
||||||
--accent: hsl(240 3.7% 15.9%);
|
--accent: oklch(0.269 0 0);
|
||||||
--accent-foreground: hsl(0 0% 98%);
|
--accent-foreground: oklch(0.985 0 0);
|
||||||
--destructive: hsl(0 62.8% 30.6%);
|
--destructive: oklch(0.704 0.191 22.216);
|
||||||
--destructive-foreground: hsl(0 0% 98%);
|
--destructive-foreground: hsl(0 0% 98%);
|
||||||
--ring: hsl(240 4.9% 83.9%);
|
--ring: oklch(0.556 0 0);
|
||||||
--sidebar: hsl(240 5.9% 10%);
|
--sidebar: oklch(0.205 0 0);
|
||||||
--sidebar-foreground: hsl(240 4.8% 95.9%);
|
--sidebar-foreground: oklch(0.985 0 0);
|
||||||
--sidebar-primary: hsl(224.3 76.3% 48%);
|
--sidebar-primary: oklch(0.488 0.243 264.376);
|
||||||
--sidebar-primary-foreground: hsl(0 0% 100%);
|
--sidebar-primary-foreground: oklch(0.985 0 0);
|
||||||
--sidebar-accent: hsl(240 3.7% 15.9%);
|
--sidebar-accent: oklch(0.269 0 0);
|
||||||
--sidebar-accent-foreground: hsl(240 4.8% 95.9%);
|
--sidebar-accent-foreground: oklch(0.985 0 0);
|
||||||
--sidebar-border: hsl(240 3.7% 15.9%);
|
--sidebar-border: oklch(1 0 0 / 10%);
|
||||||
--sidebar-ring: hsl(217.2 91.2% 59.8%);
|
--sidebar-ring: oklch(0.556 0 0);
|
||||||
|
|
||||||
--support: rgb(255 110 190);
|
--support: rgb(255 110 190);
|
||||||
--link: rgb(80 190 255);
|
--link: rgb(80 190 255);
|
||||||
--selection: hsl(240 3.7% 22%);
|
--selection: hsl(240 3.7% 22%);
|
||||||
|
--chart-1: oklch(0.87 0 0);
|
||||||
|
--chart-2: oklch(0.556 0 0);
|
||||||
|
--chart-3: oklch(0.439 0 0);
|
||||||
|
--chart-4: oklch(0.371 0 0);
|
||||||
|
--chart-5: oklch(0.269 0 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
@theme inline {
|
@theme inline {
|
||||||
@@ -113,14 +130,35 @@
|
|||||||
--color-link: var(--link);
|
--color-link: var(--link);
|
||||||
|
|
||||||
--breakpoint-xs: 540px;
|
--breakpoint-xs: 540px;
|
||||||
|
|
||||||
|
--font-sans: 'Inter Variable', sans-serif;
|
||||||
|
|
||||||
|
--color-chart-5: var(--chart-5);
|
||||||
|
|
||||||
|
--color-chart-4: var(--chart-4);
|
||||||
|
|
||||||
|
--color-chart-3: var(--chart-3);
|
||||||
|
|
||||||
|
--color-chart-2: var(--chart-2);
|
||||||
|
|
||||||
|
--color-chart-1: var(--chart-1);
|
||||||
|
|
||||||
|
--radius-2xl: calc(var(--radius) * 1.8);
|
||||||
|
|
||||||
|
--radius-3xl: calc(var(--radius) * 2.2);
|
||||||
|
|
||||||
|
--radius-4xl: calc(var(--radius) * 2.6);
|
||||||
}
|
}
|
||||||
|
|
||||||
@layer base {
|
@layer base {
|
||||||
* {
|
* {
|
||||||
@apply border-border;
|
@apply border-border outline-ring/50;
|
||||||
}
|
}
|
||||||
|
|
||||||
body {
|
body {
|
||||||
@apply bg-background text-foreground;
|
@apply bg-background text-foreground;
|
||||||
}
|
}
|
||||||
|
html {
|
||||||
|
@apply font-sans;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 2.0 MiB |
BIN
website/src/lib/assets/img/docs/getting-started/interface.webp
Normal file
|
After Width: | Height: | Size: 339 KiB |
|
Before Width: | Height: | Size: 768 KiB After Width: | Height: | Size: 1.7 MiB |
|
Before Width: | Height: | Size: 596 KiB After Width: | Height: | Size: 1.9 MiB |
|
Before Width: | Height: | Size: 4.3 MiB |
|
Before Width: | Height: | Size: 5.4 MiB |
BIN
website/src/lib/assets/img/home/map-overlay.png
Normal file
|
After Width: | Height: | Size: 2.5 MiB |
|
Before Width: | Height: | Size: 2.9 MiB |
|
Before Width: | Height: | Size: 3.6 MiB |
|
Before Width: | Height: | Size: 1.5 MiB After Width: | Height: | Size: 2.8 MiB |
|
Before Width: | Height: | Size: 6.9 MiB |
|
Before Width: | Height: | Size: 448 KiB After Width: | Height: | Size: 348 KiB |
@@ -35,6 +35,28 @@ export const basemaps: { [key: string]: string | StyleSpecification } = {
|
|||||||
maptilerTopo: `https://api.maptiler.com/maps/topo-v4/style.json?key=${maptilerKeyPlaceHolder}`,
|
maptilerTopo: `https://api.maptiler.com/maps/topo-v4/style.json?key=${maptilerKeyPlaceHolder}`,
|
||||||
maptilerOutdoors: `https://api.maptiler.com/maps/outdoor-v4/style.json?key=${maptilerKeyPlaceHolder}`,
|
maptilerOutdoors: `https://api.maptiler.com/maps/outdoor-v4/style.json?key=${maptilerKeyPlaceHolder}`,
|
||||||
maptilerSatellite: `https://api.maptiler.com/maps/hybrid-v4/style.json?key=${maptilerKeyPlaceHolder}`,
|
maptilerSatellite: `https://api.maptiler.com/maps/hybrid-v4/style.json?key=${maptilerKeyPlaceHolder}`,
|
||||||
|
esriSatellite: {
|
||||||
|
version: 8,
|
||||||
|
sources: {
|
||||||
|
esriSatellite: {
|
||||||
|
type: 'raster',
|
||||||
|
tiles: [
|
||||||
|
'https://services.arcgisonline.com/arcgis/rest/services/World_Imagery/MapServer/WMTS/tile/1.0.0/World_Imagery/default/default028mm/{z}/{y}/{x}.jpg',
|
||||||
|
],
|
||||||
|
tileSize: 256,
|
||||||
|
maxzoom: 19,
|
||||||
|
attribution:
|
||||||
|
'© <a href="https://www.esri.com/" target="_blank">Esri</a>, Vantor, Earthstar Geographics, and the GIS User Community',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
layers: [
|
||||||
|
{
|
||||||
|
id: 'esriSatellite',
|
||||||
|
type: 'raster',
|
||||||
|
source: 'esriSatellite',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
openStreetMap: {
|
openStreetMap: {
|
||||||
version: 8,
|
version: 8,
|
||||||
sources: {
|
sources: {
|
||||||
@@ -781,6 +803,7 @@ export const basemapTree: LayerTreeType = {
|
|||||||
maptilerTopo: true,
|
maptilerTopo: true,
|
||||||
maptilerOutdoors: true,
|
maptilerOutdoors: true,
|
||||||
maptilerSatellite: true,
|
maptilerSatellite: true,
|
||||||
|
esriSatellite: true,
|
||||||
openStreetMap: true,
|
openStreetMap: true,
|
||||||
openTopoMap: true,
|
openTopoMap: true,
|
||||||
openHikingMap: true,
|
openHikingMap: true,
|
||||||
@@ -1006,6 +1029,7 @@ export const defaultBasemapTree: LayerTreeType = {
|
|||||||
maptilerTopo: true,
|
maptilerTopo: true,
|
||||||
maptilerOutdoors: true,
|
maptilerOutdoors: true,
|
||||||
maptilerSatellite: true,
|
maptilerSatellite: true,
|
||||||
|
esriSatellite: false,
|
||||||
openStreetMap: true,
|
openStreetMap: true,
|
||||||
openTopoMap: true,
|
openTopoMap: true,
|
||||||
openHikingMap: true,
|
openHikingMap: true,
|
||||||
|
|||||||
@@ -64,3 +64,9 @@
|
|||||||
</svelte:head>
|
</svelte:head>
|
||||||
|
|
||||||
<div id="docsearch" class={props.class ?? ''}></div>
|
<div id="docsearch" class={props.class ?? ''}></div>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
#docsearch :global(button) {
|
||||||
|
margin-left: 0px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|||||||
@@ -26,7 +26,7 @@
|
|||||||
<Tooltip.Root>
|
<Tooltip.Root>
|
||||||
<Tooltip.Trigger>
|
<Tooltip.Trigger>
|
||||||
{#snippet child({ props })}
|
{#snippet child({ props })}
|
||||||
<Button {...props} {variant} class={className} {onclick}>
|
<Button {...props} {variant} class="bg-inherit {className}" {onclick}>
|
||||||
{@render children()}
|
{@render children()}
|
||||||
</Button>
|
</Button>
|
||||||
{/snippet}
|
{/snippet}
|
||||||
|
|||||||
@@ -1,15 +1,15 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { Button } from '$lib/components/ui/button';
|
import { Button } from '$lib/components/ui/button';
|
||||||
import LanguageSelect from '$lib/components/LanguageSelect.svelte';
|
import LanguageSelect from '$lib/components/LanguageSelect.svelte';
|
||||||
|
import ModeSwitch from '$lib/components/ModeSwitch.svelte';
|
||||||
import Logo from '$lib/components/Logo.svelte';
|
import Logo from '$lib/components/Logo.svelte';
|
||||||
import { AtSign, BookOpenText, Heart, House, Map } from '@lucide/svelte';
|
import { AtSign, BookOpenText, Heart, House, Map } from '@lucide/svelte';
|
||||||
import { i18n } from '$lib/i18n.svelte';
|
import { i18n } from '$lib/i18n.svelte';
|
||||||
import { getURLForLanguage } from '$lib/utils';
|
import { getURLForLanguage } from '$lib/utils';
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<footer class="w-full">
|
<footer class="w-full px-12 py-10 border-t flex flex-col items-center">
|
||||||
<div class="mx-6 border-t">
|
<div class="w-full max-w-5xl flex flex-row flex-wrap justify-between gap-x-10 gap-y-6">
|
||||||
<div class="mx-12 py-10 flex flex-row flex-wrap justify-between gap-x-10 gap-y-6">
|
|
||||||
<div class="grow flex flex-col items-start">
|
<div class="grow flex flex-col items-start">
|
||||||
<Logo class="h-8" width="153" />
|
<Logo class="h-8" width="153" />
|
||||||
<Button
|
<Button
|
||||||
@@ -20,7 +20,10 @@
|
|||||||
>
|
>
|
||||||
MIT © 2026 gpx.studio
|
MIT © 2026 gpx.studio
|
||||||
</Button>
|
</Button>
|
||||||
<LanguageSelect class="w-40 mt-3" />
|
<div class="mt-3 flex flex-row gap-1.5">
|
||||||
|
<LanguageSelect />
|
||||||
|
<ModeSwitch />
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="grow max-w-2xl flex flex-row flex-wrap justify-between gap-x-10 gap-y-6">
|
<div class="grow max-w-2xl flex flex-row flex-wrap justify-between gap-x-10 gap-y-6">
|
||||||
<div class="flex flex-col items-start gap-1">
|
<div class="flex flex-col items-start gap-1">
|
||||||
@@ -113,5 +116,4 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
</footer>
|
</footer>
|
||||||
|
|||||||
@@ -12,16 +12,17 @@
|
|||||||
|
|
||||||
const { velocityUnits } = settings;
|
const { velocityUnits } = settings;
|
||||||
|
|
||||||
|
let panelHeight: number = $state(0);
|
||||||
|
let panelWidth: number = $state(0);
|
||||||
|
|
||||||
let {
|
let {
|
||||||
gpxStatistics,
|
gpxStatistics,
|
||||||
slicedGPXStatistics,
|
slicedGPXStatistics,
|
||||||
orientation,
|
orientation,
|
||||||
panelSize,
|
|
||||||
}: {
|
}: {
|
||||||
gpxStatistics: Readable<GPXStatisticsGroup>;
|
gpxStatistics: Readable<GPXStatisticsGroup>;
|
||||||
slicedGPXStatistics: Readable<[GPXGlobalStatistics, number, number] | undefined>;
|
slicedGPXStatistics: Readable<[GPXGlobalStatistics, number, number] | undefined>;
|
||||||
orientation: 'horizontal' | 'vertical';
|
orientation: 'horizontal' | 'vertical';
|
||||||
panelSize: number;
|
|
||||||
} = $props();
|
} = $props();
|
||||||
|
|
||||||
let statistics = $derived(
|
let statistics = $derived(
|
||||||
@@ -32,12 +33,15 @@
|
|||||||
<Card.Root
|
<Card.Root
|
||||||
class="h-full {orientation === 'vertical'
|
class="h-full {orientation === 'vertical'
|
||||||
? 'min-w-40 sm:min-w-44'
|
? 'min-w-40 sm:min-w-44'
|
||||||
: 'w-full h-10'} border-none shadow-none p-0 text-sm sm:text-base"
|
: 'w-full h-fit my-1'} ring-0 p-0 text-sm sm:text-base bg-transparent"
|
||||||
>
|
>
|
||||||
<Card.Content
|
<Card.Content class="h-full p-0">
|
||||||
class="h-full flex {orientation === 'vertical'
|
<div
|
||||||
? 'flex-col justify-center'
|
bind:clientHeight={panelHeight}
|
||||||
: 'flex-row w-full justify-evenly'} gap-4 p-0"
|
bind:clientWidth={panelWidth}
|
||||||
|
class="flex {orientation === 'vertical'
|
||||||
|
? 'flex-col h-full justify-center'
|
||||||
|
: 'flex-row w-full justify-evenly'} gap-4"
|
||||||
>
|
>
|
||||||
<Tooltip label={i18n._('quantities.distance')}>
|
<Tooltip label={i18n._('quantities.distance')}>
|
||||||
<span class="flex flex-row items-center">
|
<span class="flex flex-row items-center">
|
||||||
@@ -53,9 +57,8 @@
|
|||||||
<WithUnits value={statistics.elevation.loss} type="elevation" />
|
<WithUnits value={statistics.elevation.loss} type="elevation" />
|
||||||
</span>
|
</span>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
{#if panelSize > 120 || orientation === 'horizontal'}
|
{#if panelHeight > 120 || (orientation === 'horizontal' && panelWidth > 450)}
|
||||||
<Tooltip
|
<Tooltip
|
||||||
class={orientation === 'horizontal' ? 'hidden xs:block' : ''}
|
|
||||||
label="{$velocityUnits === 'speed'
|
label="{$velocityUnits === 'speed'
|
||||||
? i18n._('quantities.speed')
|
? i18n._('quantities.speed')
|
||||||
: i18n._('quantities.pace')} ({i18n._('quantities.moving')} / {i18n._(
|
: i18n._('quantities.pace')} ({i18n._('quantities.moving')} / {i18n._(
|
||||||
@@ -70,9 +73,8 @@
|
|||||||
</span>
|
</span>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
{/if}
|
{/if}
|
||||||
{#if panelSize > 160 || orientation === 'horizontal'}
|
{#if panelHeight > 150 || (orientation === 'horizontal' && panelWidth > 620)}
|
||||||
<Tooltip
|
<Tooltip
|
||||||
class={orientation === 'horizontal' ? 'hidden md:block' : ''}
|
|
||||||
label="{i18n._('quantities.time')} ({i18n._('quantities.moving')} / {i18n._(
|
label="{i18n._('quantities.time')} ({i18n._('quantities.moving')} / {i18n._(
|
||||||
'quantities.total'
|
'quantities.total'
|
||||||
)})"
|
)})"
|
||||||
@@ -85,5 +87,6 @@
|
|||||||
</span>
|
</span>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
{/if}
|
{/if}
|
||||||
|
</div>
|
||||||
</Card.Content>
|
</Card.Content>
|
||||||
</Card.Root>
|
</Card.Root>
|
||||||
|
|||||||
@@ -14,12 +14,12 @@
|
|||||||
} = $props();
|
} = $props();
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="text-sm bg-secondary rounded border flex flex-row items-center p-2 {className}">
|
<div class="text-[13px] bg-secondary rounded border flex flex-row items-center p-2 {className}">
|
||||||
<CircleQuestionMark size="16" class="w-4 mr-2 shrink-0 grow-0" />
|
<CircleQuestionMark size="16" class="w-4 mr-2 shrink-0 grow-0" />
|
||||||
<div>
|
<div>
|
||||||
{@render children()}
|
{@render children()}
|
||||||
{#if link}
|
{#if link}
|
||||||
<a href={link} target="_blank" class="text-sm text-link hover:underline">
|
<a href={link} target="_blank" class="text-[13px] text-link hover:underline">
|
||||||
{i18n._('menu.more')}
|
{i18n._('menu.more')}
|
||||||
</a>
|
</a>
|
||||||
{/if}
|
{/if}
|
||||||
|
|||||||
@@ -5,16 +5,10 @@
|
|||||||
import { getURLForLanguage } from '$lib/utils';
|
import { getURLForLanguage } from '$lib/utils';
|
||||||
import { Languages } from '@lucide/svelte';
|
import { Languages } from '@lucide/svelte';
|
||||||
import { i18n } from '$lib/i18n.svelte';
|
import { i18n } from '$lib/i18n.svelte';
|
||||||
|
|
||||||
let {
|
|
||||||
class: className = '',
|
|
||||||
}: {
|
|
||||||
class?: string;
|
|
||||||
} = $props();
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<Select.Root type="single" value={i18n.lang}>
|
<Select.Root type="single" value={i18n.lang}>
|
||||||
<Select.Trigger class="min-w-[180px] {className}" aria-label={i18n._('menu.language')}>
|
<Select.Trigger class="w-[180px] px-2" aria-label={i18n._('menu.language')}>
|
||||||
<Languages size="16" />
|
<Languages size="16" />
|
||||||
<span class="mr-auto">
|
<span class="mr-auto">
|
||||||
{languages[i18n.lang]}
|
{languages[i18n.lang]}
|
||||||
|
|||||||
@@ -43,6 +43,8 @@
|
|||||||
BookOpenText,
|
BookOpenText,
|
||||||
ChartArea,
|
ChartArea,
|
||||||
Maximize,
|
Maximize,
|
||||||
|
Maximize2,
|
||||||
|
Minimize2,
|
||||||
} from '@lucide/svelte';
|
} from '@lucide/svelte';
|
||||||
import { map } from '$lib/components/map/map';
|
import { map } from '$lib/components/map/map';
|
||||||
import { editMetadata } from '$lib/components/file-list/metadata/utils.svelte';
|
import { editMetadata } from '$lib/components/file-list/metadata/utils.svelte';
|
||||||
@@ -70,7 +72,7 @@
|
|||||||
import { copied, selection } from '$lib/logic/selection';
|
import { copied, selection } from '$lib/logic/selection';
|
||||||
import { allHidden } from '$lib/logic/hidden';
|
import { allHidden } from '$lib/logic/hidden';
|
||||||
import { boundsManager } from '$lib/logic/bounds';
|
import { boundsManager } from '$lib/logic/bounds';
|
||||||
import { tick } from 'svelte';
|
import { tick, onMount } from 'svelte';
|
||||||
import { allowedPastes } from '$lib/components/file-list/sortable-file-list';
|
import { allowedPastes } from '$lib/components/file-list/sortable-file-list';
|
||||||
|
|
||||||
const {
|
const {
|
||||||
@@ -105,6 +107,23 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
let layerSettingsOpen = $state(false);
|
let layerSettingsOpen = $state(false);
|
||||||
|
let fullscreen = $state(false);
|
||||||
|
|
||||||
|
function toggleFullscreen() {
|
||||||
|
if (!document.fullscreenElement) {
|
||||||
|
document.documentElement.requestFullscreen?.();
|
||||||
|
} else {
|
||||||
|
document.exitFullscreen?.();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onMount(() => {
|
||||||
|
const handler = () => {
|
||||||
|
fullscreen = document.fullscreenElement !== null;
|
||||||
|
};
|
||||||
|
document.addEventListener('fullscreenchange', handler);
|
||||||
|
return () => document.removeEventListener('fullscreenchange', handler);
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="absolute md:top-2 left-0 right-0 z-20 flex flex-row justify-center pointer-events-none">
|
<div class="absolute md:top-2 left-0 right-0 z-20 flex flex-row justify-center pointer-events-none">
|
||||||
@@ -377,6 +396,16 @@
|
|||||||
{i18n._('menu.toggle_3d')}
|
{i18n._('menu.toggle_3d')}
|
||||||
<Shortcut key={i18n._('menu.right_click_drag')} />
|
<Shortcut key={i18n._('menu.right_click_drag')} />
|
||||||
</Menubar.Item>
|
</Menubar.Item>
|
||||||
|
<Menubar.Separator />
|
||||||
|
<Menubar.CheckboxItem checked={fullscreen} onCheckedChange={toggleFullscreen}>
|
||||||
|
{#if fullscreen}
|
||||||
|
<Minimize2 size="16" />
|
||||||
|
{:else}
|
||||||
|
<Maximize2 size="16" />
|
||||||
|
{/if}
|
||||||
|
{i18n._('menu.fullscreen')}
|
||||||
|
<Shortcut key="F11" />
|
||||||
|
</Menubar.CheckboxItem>
|
||||||
</Menubar.Content>
|
</Menubar.Content>
|
||||||
</Menubar.Menu>
|
</Menubar.Menu>
|
||||||
<Menubar.Menu>
|
<Menubar.Menu>
|
||||||
@@ -389,7 +418,7 @@
|
|||||||
<Menubar.Content class="border-none">
|
<Menubar.Content class="border-none">
|
||||||
<Menubar.Sub>
|
<Menubar.Sub>
|
||||||
<Menubar.SubTrigger>
|
<Menubar.SubTrigger>
|
||||||
<Ruler size="16" class="mr-2" />{i18n._('menu.distance_units')}
|
<Ruler size="16" />{i18n._('menu.distance_units')}
|
||||||
</Menubar.SubTrigger>
|
</Menubar.SubTrigger>
|
||||||
<Menubar.SubContent>
|
<Menubar.SubContent>
|
||||||
<Menubar.RadioGroup bind:value={$distanceUnits}>
|
<Menubar.RadioGroup bind:value={$distanceUnits}>
|
||||||
@@ -407,7 +436,7 @@
|
|||||||
</Menubar.Sub>
|
</Menubar.Sub>
|
||||||
<Menubar.Sub>
|
<Menubar.Sub>
|
||||||
<Menubar.SubTrigger>
|
<Menubar.SubTrigger>
|
||||||
<Zap size="16" class="mr-2" />{i18n._('menu.velocity_units')}
|
<Zap size="16" />{i18n._('menu.velocity_units')}
|
||||||
</Menubar.SubTrigger>
|
</Menubar.SubTrigger>
|
||||||
<Menubar.SubContent>
|
<Menubar.SubContent>
|
||||||
<Menubar.RadioGroup bind:value={$velocityUnits}>
|
<Menubar.RadioGroup bind:value={$velocityUnits}>
|
||||||
@@ -422,7 +451,7 @@
|
|||||||
</Menubar.Sub>
|
</Menubar.Sub>
|
||||||
<Menubar.Sub>
|
<Menubar.Sub>
|
||||||
<Menubar.SubTrigger>
|
<Menubar.SubTrigger>
|
||||||
<Thermometer size="16" class="mr-2" />{i18n._('menu.temperature_units')}
|
<Thermometer size="16" />{i18n._('menu.temperature_units')}
|
||||||
</Menubar.SubTrigger>
|
</Menubar.SubTrigger>
|
||||||
<Menubar.SubContent>
|
<Menubar.SubContent>
|
||||||
<Menubar.RadioGroup bind:value={$temperatureUnits}>
|
<Menubar.RadioGroup bind:value={$temperatureUnits}>
|
||||||
@@ -438,7 +467,7 @@
|
|||||||
<Menubar.Separator />
|
<Menubar.Separator />
|
||||||
<Menubar.Sub>
|
<Menubar.Sub>
|
||||||
<Menubar.SubTrigger>
|
<Menubar.SubTrigger>
|
||||||
<Languages size="16" class="mr-2" />
|
<Languages size="16" />
|
||||||
{i18n._('menu.language')}
|
{i18n._('menu.language')}
|
||||||
</Menubar.SubTrigger>
|
</Menubar.SubTrigger>
|
||||||
<Menubar.SubContent>
|
<Menubar.SubContent>
|
||||||
@@ -454,9 +483,9 @@
|
|||||||
<Menubar.Sub>
|
<Menubar.Sub>
|
||||||
<Menubar.SubTrigger>
|
<Menubar.SubTrigger>
|
||||||
{#if mode.current === 'light' || !mode.current}
|
{#if mode.current === 'light' || !mode.current}
|
||||||
<Sun size="16" class="mr-2" />
|
<Sun size="16" />
|
||||||
{:else}
|
{:else}
|
||||||
<Moon size="16" class="mr-2" />
|
<Moon size="16" />
|
||||||
{/if}
|
{/if}
|
||||||
{i18n._('menu.mode')}
|
{i18n._('menu.mode')}
|
||||||
</Menubar.SubTrigger>
|
</Menubar.SubTrigger>
|
||||||
@@ -479,7 +508,7 @@
|
|||||||
<Menubar.Separator />
|
<Menubar.Separator />
|
||||||
<Menubar.Sub>
|
<Menubar.Sub>
|
||||||
<Menubar.SubTrigger>
|
<Menubar.SubTrigger>
|
||||||
<PersonStanding size="16" class="mr-2" />
|
<PersonStanding size="16" />
|
||||||
{i18n._('menu.street_view_source')}
|
{i18n._('menu.street_view_source')}
|
||||||
</Menubar.SubTrigger>
|
</Menubar.SubTrigger>
|
||||||
<Menubar.SubContent>
|
<Menubar.SubContent>
|
||||||
@@ -500,12 +529,12 @@
|
|||||||
</Menubar.Content>
|
</Menubar.Content>
|
||||||
</Menubar.Menu>
|
</Menubar.Menu>
|
||||||
</Menubar.Root>
|
</Menubar.Root>
|
||||||
<div class="h-fit flex flex-row items-center ml-1 gap-1">
|
<div class="h-fit flex flex-row items-center">
|
||||||
<Button
|
<Button
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
href="./help"
|
href="./help"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
class="cursor-default h-fit rounded-sm px-3 py-0.5"
|
class="cursor-default h-fit rounded-md px-3 py-0.5"
|
||||||
aria-label={i18n._('menu.help')}
|
aria-label={i18n._('menu.help')}
|
||||||
>
|
>
|
||||||
<BookOpenText size="18" class="md:hidden" />
|
<BookOpenText size="18" class="md:hidden" />
|
||||||
@@ -517,7 +546,7 @@
|
|||||||
variant="ghost"
|
variant="ghost"
|
||||||
href="https://opencollective.com/gpxstudio"
|
href="https://opencollective.com/gpxstudio"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
class="cursor-default h-fit rounded-sm font-bold text-support hover:text-support px-3 py-0.5"
|
class="cursor-default h-fit rounded-md font-bold text-support hover:text-support px-3 py-0.5"
|
||||||
aria-label={i18n._('menu.donate')}
|
aria-label={i18n._('menu.donate')}
|
||||||
>
|
>
|
||||||
<HeartHandshake size="18" class="md:hidden" />
|
<HeartHandshake size="18" class="md:hidden" />
|
||||||
|
|||||||
@@ -12,7 +12,7 @@
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
variant="ghost"
|
variant="outline"
|
||||||
size="icon"
|
size="icon"
|
||||||
class={className}
|
class={className}
|
||||||
onclick={() => {
|
onclick={() => {
|
||||||
|
|||||||
@@ -1,22 +1,23 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import Logo from '$lib/components/Logo.svelte';
|
import Logo from '$lib/components/Logo.svelte';
|
||||||
import { Button } from '$lib/components/ui/button';
|
import { Button } from '$lib/components/ui/button';
|
||||||
import AlgoliaDocSearch from '$lib/components/AlgoliaDocSearch.svelte';
|
|
||||||
import ModeSwitch from '$lib/components/ModeSwitch.svelte';
|
|
||||||
import { BookOpenText, House, Map } from '@lucide/svelte';
|
import { BookOpenText, House, Map } from '@lucide/svelte';
|
||||||
import { i18n } from '$lib/i18n.svelte';
|
import { i18n } from '$lib/i18n.svelte';
|
||||||
import { getURLForLanguage } from '$lib/utils';
|
import { getURLForLanguage } from '$lib/utils';
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<nav class="w-full sticky top-0 bg-background z-50">
|
<nav class="sticky top-0 w-full px-12 py-2 bg-background z-50 flex flex-col items-center border-b">
|
||||||
<div class="mx-6 py-2 flex flex-row items-center border-b gap-4 sm:gap-8">
|
<div class="w-full max-w-5xl flex flex-row items-center gap-4 sm:gap-8">
|
||||||
<a href={getURLForLanguage(i18n.lang, '/')} class="shrink-0 translate-y-0.5">
|
<a
|
||||||
<Logo class="h-8 sm:hidden" iconOnly={true} width="26" />
|
href={getURLForLanguage(i18n.lang, '/')}
|
||||||
<Logo class="h-8 hidden sm:block" width="153" />
|
class="shrink-0 translate-y-0.25 justify-self-start"
|
||||||
|
>
|
||||||
|
<Logo class="h-8 xs:hidden" iconOnly={true} width="26" />
|
||||||
|
<Logo class="h-8 hidden xs:block" width="153" />
|
||||||
</a>
|
</a>
|
||||||
<Button
|
<Button
|
||||||
variant="link"
|
variant="link"
|
||||||
class="text-base px-0 has-[>svg]:px-0"
|
class="text-base px-0 has-[>svg]:px-0 ml-auto"
|
||||||
href={getURLForLanguage(i18n.lang, '/')}
|
href={getURLForLanguage(i18n.lang, '/')}
|
||||||
>
|
>
|
||||||
<House size="18" />
|
<House size="18" />
|
||||||
@@ -39,7 +40,5 @@
|
|||||||
<BookOpenText size="18" />
|
<BookOpenText size="18" />
|
||||||
{i18n._('menu.help')}
|
{i18n._('menu.help')}
|
||||||
</Button>
|
</Button>
|
||||||
<AlgoliaDocSearch class="ml-auto" />
|
|
||||||
<ModeSwitch class="hidden xs:inline-flex" />
|
|
||||||
</div>
|
</div>
|
||||||
</nav>
|
</nav>
|
||||||
|
|||||||
@@ -35,7 +35,7 @@
|
|||||||
<Button
|
<Button
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
size="icon"
|
size="icon"
|
||||||
class="w-full flex flex-row gap-1 {side === 'right'
|
class="w-full flex flex-row gap-1 border-none {side === 'right'
|
||||||
? 'justify-between'
|
? 'justify-between'
|
||||||
: 'justify-start pl-1'} h-fit {nohover
|
: 'justify-start pl-1'} h-fit {nohover
|
||||||
? 'hover:bg-background'
|
? 'hover:bg-background'
|
||||||
@@ -62,7 +62,7 @@
|
|||||||
<Button
|
<Button
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
size="icon"
|
size="icon"
|
||||||
class="w-full flex flex-row gap-1 {side === 'right'
|
class="w-full flex flex-row gap-1 border-none {side === 'right'
|
||||||
? 'justify-between'
|
? 'justify-between'
|
||||||
: 'justify-start pl-1'} h-fit {nohover ? 'hover:bg-background' : ''}"
|
: 'justify-start pl-1'} h-fit {nohover ? 'hover:bg-background' : ''}"
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -19,7 +19,7 @@
|
|||||||
@apply text-foreground;
|
@apply text-foreground;
|
||||||
@apply text-3xl;
|
@apply text-3xl;
|
||||||
@apply font-semibold;
|
@apply font-semibold;
|
||||||
@apply mb-3 pt-6;
|
@apply mb-3;
|
||||||
}
|
}
|
||||||
|
|
||||||
:global(.markdown h2) {
|
:global(.markdown h2) {
|
||||||
|
|||||||
@@ -12,7 +12,7 @@
|
|||||||
<div class="rounded-md overflow-hidden overflow-clip shadow-xl mx-auto">
|
<div class="rounded-md overflow-hidden overflow-clip shadow-xl mx-auto">
|
||||||
{#if src === 'getting-started/interface'}
|
{#if src === 'getting-started/interface'}
|
||||||
<enhanced:img
|
<enhanced:img
|
||||||
src="/src/lib/assets/img/docs/getting-started/interface.png"
|
src="/src/lib/assets/img/docs/getting-started/interface.webp"
|
||||||
{alt}
|
{alt}
|
||||||
class="w-full max-w-3xl"
|
class="w-full max-w-3xl"
|
||||||
/>
|
/>
|
||||||
@@ -20,13 +20,13 @@
|
|||||||
<enhanced:img
|
<enhanced:img
|
||||||
src="/src/lib/assets/img/docs/tools/routing.png"
|
src="/src/lib/assets/img/docs/tools/routing.png"
|
||||||
{alt}
|
{alt}
|
||||||
class="w-full max-w-3xl"
|
class="w-full max-w-lg"
|
||||||
/>
|
/>
|
||||||
{:else if src === 'tools/split'}
|
{:else if src === 'tools/split'}
|
||||||
<enhanced:img
|
<enhanced:img
|
||||||
src="/src/lib/assets/img/docs/tools/split.png"
|
src="/src/lib/assets/img/docs/tools/split.png"
|
||||||
{alt}
|
{alt}
|
||||||
class="w-full max-w-3xl"
|
class="w-full max-w-lg"
|
||||||
/>
|
/>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -75,26 +75,24 @@
|
|||||||
label={i18n._('chart.settings')}
|
label={i18n._('chart.settings')}
|
||||||
variant="outline"
|
variant="outline"
|
||||||
side="left"
|
side="left"
|
||||||
class="w-7 h-7 p-0 flex justify-center opacity-70 hover:opacity-100 transition-opacity duration-300 hover:bg-background"
|
class="w-7 h-7 p-0 flex justify-center opacity-70 hover:opacity-100 transition-opacity duration-300 bg-background"
|
||||||
>
|
>
|
||||||
<ChartNoAxesColumn size="18" />
|
<ChartNoAxesColumn size="18" />
|
||||||
</ButtonWithTooltip>
|
</ButtonWithTooltip>
|
||||||
</Popover.Trigger>
|
</Popover.Trigger>
|
||||||
<Popover.Content
|
<Popover.Content
|
||||||
class="w-fit p-0 flex flex-col"
|
class="w-fit p-0 flex flex-col gap-0 overflow-hidden"
|
||||||
side="top"
|
side="top"
|
||||||
align="end"
|
align="end"
|
||||||
sideOffset={-32}
|
sideOffset={-32}
|
||||||
>
|
>
|
||||||
<ToggleGroup.Root
|
<ToggleGroup.Root
|
||||||
class="flex flex-col items-start gap-0 p-1 w-full border-none"
|
class="flex flex-col w-full border-none"
|
||||||
type="single"
|
type="single"
|
||||||
|
size="sm"
|
||||||
bind:value={$elevationFill}
|
bind:value={$elevationFill}
|
||||||
>
|
>
|
||||||
<ToggleGroup.Item
|
<ToggleGroup.Item value="slope" class="w-full flex flex-row justify-start">
|
||||||
class="p-0 pr-1.5 h-6 w-full gap-1.5 rounded flex justify-start data-[state=on]:bg-background data-[state=on]:hover:bg-accent hover:bg-accent hover:text-foreground"
|
|
||||||
value="slope"
|
|
||||||
>
|
|
||||||
<div class="w-6 flex justify-center items-center">
|
<div class="w-6 flex justify-center items-center">
|
||||||
{#if $elevationFill === 'slope'}
|
{#if $elevationFill === 'slope'}
|
||||||
<Circle class="size-1.5 fill-current text-current" />
|
<Circle class="size-1.5 fill-current text-current" />
|
||||||
@@ -104,9 +102,8 @@
|
|||||||
{i18n._('quantities.slope')}
|
{i18n._('quantities.slope')}
|
||||||
</ToggleGroup.Item>
|
</ToggleGroup.Item>
|
||||||
<ToggleGroup.Item
|
<ToggleGroup.Item
|
||||||
class="p-0 pr-1.5 h-6 w-full gap-1.5 rounded flex justify-start data-[state=on]:bg-background data-[state=on]:hover:bg-accent hover:bg-accent hover:text-foreground"
|
|
||||||
value="surface"
|
value="surface"
|
||||||
variant="outline"
|
class="w-full flex flex-row justify-start"
|
||||||
>
|
>
|
||||||
<div class="w-6 flex justify-center items-center">
|
<div class="w-6 flex justify-center items-center">
|
||||||
{#if $elevationFill === 'surface'}
|
{#if $elevationFill === 'surface'}
|
||||||
@@ -117,9 +114,8 @@
|
|||||||
{i18n._('quantities.surface')}
|
{i18n._('quantities.surface')}
|
||||||
</ToggleGroup.Item>
|
</ToggleGroup.Item>
|
||||||
<ToggleGroup.Item
|
<ToggleGroup.Item
|
||||||
class="p-0 pr-1.5 h-6 w-full gap-1.5 rounded flex justify-start data-[state=on]:bg-background data-[state=on]:hover:bg-accent hover:bg-accent hover:text-foreground"
|
|
||||||
value="highway"
|
value="highway"
|
||||||
variant="outline"
|
class="w-full flex flex-row justify-start"
|
||||||
>
|
>
|
||||||
<div class="w-6 flex justify-center items-center">
|
<div class="w-6 flex justify-center items-center">
|
||||||
{#if $elevationFill === 'highway'}
|
{#if $elevationFill === 'highway'}
|
||||||
@@ -132,14 +128,12 @@
|
|||||||
</ToggleGroup.Root>
|
</ToggleGroup.Root>
|
||||||
<Separator />
|
<Separator />
|
||||||
<ToggleGroup.Root
|
<ToggleGroup.Root
|
||||||
class="flex flex-col items-start gap-0 p-1"
|
class="flex flex-col gap-0"
|
||||||
type="multiple"
|
type="multiple"
|
||||||
|
size="sm"
|
||||||
bind:value={$additionalDatasets}
|
bind:value={$additionalDatasets}
|
||||||
>
|
>
|
||||||
<ToggleGroup.Item
|
<ToggleGroup.Item value="speed" class="w-full flex flex-row justify-start">
|
||||||
class="p-0 pr-1.5 h-6 w-full gap-1.5 rounded flex justify-start data-[state=on]:bg-background data-[state=on]:hover:bg-accent hover:bg-accent hover:text-foreground"
|
|
||||||
value="speed"
|
|
||||||
>
|
|
||||||
<div class="w-6 flex justify-center items-center">
|
<div class="w-6 flex justify-center items-center">
|
||||||
{#if $additionalDatasets.includes('speed')}
|
{#if $additionalDatasets.includes('speed')}
|
||||||
<Check size="14" />
|
<Check size="14" />
|
||||||
@@ -150,10 +144,7 @@
|
|||||||
? i18n._('quantities.speed')
|
? i18n._('quantities.speed')
|
||||||
: i18n._('quantities.pace')}
|
: i18n._('quantities.pace')}
|
||||||
</ToggleGroup.Item>
|
</ToggleGroup.Item>
|
||||||
<ToggleGroup.Item
|
<ToggleGroup.Item value="hr" class="w-full flex flex-row justify-start">
|
||||||
class="p-0 pr-1.5 h-6 w-full gap-1.5 rounded flex justify-start data-[state=on]:bg-background data-[state=on]:hover:bg-accent hover:bg-accent hover:text-foreground"
|
|
||||||
value="hr"
|
|
||||||
>
|
|
||||||
<div class="w-6 flex justify-center items-center">
|
<div class="w-6 flex justify-center items-center">
|
||||||
{#if $additionalDatasets.includes('hr')}
|
{#if $additionalDatasets.includes('hr')}
|
||||||
<Check size="14" />
|
<Check size="14" />
|
||||||
@@ -162,10 +153,7 @@
|
|||||||
<HeartPulse size="15" />
|
<HeartPulse size="15" />
|
||||||
{i18n._('quantities.heartrate')}
|
{i18n._('quantities.heartrate')}
|
||||||
</ToggleGroup.Item>
|
</ToggleGroup.Item>
|
||||||
<ToggleGroup.Item
|
<ToggleGroup.Item value="cad" class="w-full flex flex-row justify-start">
|
||||||
class="p-0 pr-1.5 h-6 w-full gap-1.5 rounded flex justify-start data-[state=on]:bg-background data-[state=on]:hover:bg-accent hover:bg-accent hover:text-foreground"
|
|
||||||
value="cad"
|
|
||||||
>
|
|
||||||
<div class="w-6 flex justify-center items-center">
|
<div class="w-6 flex justify-center items-center">
|
||||||
{#if $additionalDatasets.includes('cad')}
|
{#if $additionalDatasets.includes('cad')}
|
||||||
<Check size="14" />
|
<Check size="14" />
|
||||||
@@ -174,10 +162,7 @@
|
|||||||
<Orbit size="15" />
|
<Orbit size="15" />
|
||||||
{i18n._('quantities.cadence')}
|
{i18n._('quantities.cadence')}
|
||||||
</ToggleGroup.Item>
|
</ToggleGroup.Item>
|
||||||
<ToggleGroup.Item
|
<ToggleGroup.Item value="atemp" class="w-full flex flex-row justify-start">
|
||||||
class="p-0 pr-1.5 h-6 w-full gap-1.5 rounded flex justify-start data-[state=on]:bg-background data-[state=on]:hover:bg-accent hover:bg-accent hover:text-foreground"
|
|
||||||
value="atemp"
|
|
||||||
>
|
|
||||||
<div class="w-6 flex justify-center items-center">
|
<div class="w-6 flex justify-center items-center">
|
||||||
{#if $additionalDatasets.includes('atemp')}
|
{#if $additionalDatasets.includes('atemp')}
|
||||||
<Check size="14" />
|
<Check size="14" />
|
||||||
@@ -186,10 +171,7 @@
|
|||||||
<Thermometer size="15" />
|
<Thermometer size="15" />
|
||||||
{i18n._('quantities.temperature')}
|
{i18n._('quantities.temperature')}
|
||||||
</ToggleGroup.Item>
|
</ToggleGroup.Item>
|
||||||
<ToggleGroup.Item
|
<ToggleGroup.Item value="power" class="w-full flex flex-row justify-start">
|
||||||
class="p-0 pr-1.5 h-6 w-full gap-1.5 rounded flex justify-start data-[state=on]:bg-background data-[state=on]:hover:bg-accent hover:bg-accent hover:text-foreground"
|
|
||||||
value="power"
|
|
||||||
>
|
|
||||||
<div class="w-6 flex justify-center items-center">
|
<div class="w-6 flex justify-center items-center">
|
||||||
{#if $additionalDatasets.includes('power')}
|
{#if $additionalDatasets.includes('power')}
|
||||||
<Check size="14" />
|
<Check size="14" />
|
||||||
|
|||||||
@@ -117,13 +117,12 @@
|
|||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
class="{options.elevation.show ? '' : 'h-10'} flex flex-row gap-2 px-2 sm:px-4"
|
class="{options.elevation.show ? '' : 'h-10'} flex flex-row gap-2 p-2 sm:px-4"
|
||||||
style={options.elevation.show ? `height: ${options.elevation.height}px` : ''}
|
style={options.elevation.show ? `height: ${options.elevation.height}px` : ''}
|
||||||
>
|
>
|
||||||
<GPXStatistics
|
<GPXStatistics
|
||||||
{gpxStatistics}
|
{gpxStatistics}
|
||||||
{slicedGPXStatistics}
|
{slicedGPXStatistics}
|
||||||
panelSize={options.elevation.height}
|
|
||||||
orientation={options.elevation.show ? 'vertical' : 'horizontal'}
|
orientation={options.elevation.show ? 'vertical' : 'horizontal'}
|
||||||
/>
|
/>
|
||||||
{#if options.elevation.show}
|
{#if options.elevation.show}
|
||||||
|
|||||||
@@ -90,6 +90,9 @@ export function getCleanedEmbeddingOptions(
|
|||||||
delete cleanedOptions[key];
|
delete cleanedOptions[key];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (cleanedOptions['key'] && cleanedOptions['key'] === PUBLIC_MAPTILER_KEY) {
|
||||||
|
delete cleanedOptions['key'];
|
||||||
|
}
|
||||||
return cleanedOptions;
|
return cleanedOptions;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -114,10 +114,10 @@
|
|||||||
<ContextMenu.Trigger class="grow truncate">
|
<ContextMenu.Trigger class="grow truncate">
|
||||||
<Button
|
<Button
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
class="relative w-full p-0 overflow-hidden focus-visible:ring-0 focus-visible:ring-offset-0 {orientation ===
|
class="relative w-full p-0 overflow-hidden border-none focus-visible:ring-0 focus-visible:ring-offset-0 flex flex-row {orientation ===
|
||||||
'vertical'
|
'vertical'
|
||||||
? 'h-fit'
|
? 'h-7'
|
||||||
: 'h-9 px-1.5 shadow-md'} pointer-events-auto"
|
: 'h-9 px-1.5'} pointer-events-auto"
|
||||||
>
|
>
|
||||||
{#if item instanceof ListFileItem || item instanceof ListTrackItem}
|
{#if item instanceof ListFileItem || item instanceof ListTrackItem}
|
||||||
<MetadataDialog bind:open={openEditMetadata} {node} {item} />
|
<MetadataDialog bind:open={openEditMetadata} {node} {item} />
|
||||||
@@ -126,7 +126,7 @@
|
|||||||
{#if item.level === ListLevel.FILE || item.level === ListLevel.TRACK}
|
{#if item.level === ListLevel.FILE || item.level === ListLevel.TRACK}
|
||||||
<div
|
<div
|
||||||
class="absolute {orientation === 'vertical'
|
class="absolute {orientation === 'vertical'
|
||||||
? 'top-0 bottom-0 right-1 w-1'
|
? 'top-0 bottom-0 right-0 w-1'
|
||||||
: 'top-0 h-1 left-0 right-0'}"
|
: 'top-0 h-1 left-0 right-0'}"
|
||||||
style="background:linear-gradient(to {orientation === 'vertical'
|
style="background:linear-gradient(to {orientation === 'vertical'
|
||||||
? 'bottom'
|
? 'bottom'
|
||||||
@@ -139,7 +139,7 @@
|
|||||||
></div>
|
></div>
|
||||||
{/if}
|
{/if}
|
||||||
<span
|
<span
|
||||||
class="w-full text-left truncate py-1 flex flex-row items-center {hidden
|
class="grow text-left truncate ml-1 flex flex-row items-center {hidden
|
||||||
? 'text-muted-foreground'
|
? 'text-muted-foreground'
|
||||||
: ''} {$cut && $copied?.some((i) => i.getFullId() === item.getFullId())
|
: ''} {$cut && $copied?.some((i) => i.getFullId() === item.getFullId())
|
||||||
? 'text-muted-foreground'
|
? 'text-muted-foreground'
|
||||||
|
|||||||
@@ -16,7 +16,6 @@
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
size="sm"
|
|
||||||
class="justify-start {className}"
|
class="justify-start {className}"
|
||||||
variant="outline"
|
variant="outline"
|
||||||
onclick={() => {
|
onclick={() => {
|
||||||
|
|||||||
@@ -39,7 +39,6 @@
|
|||||||
/>
|
/>
|
||||||
{#if trackpoint.fileId === undefined}
|
{#if trackpoint.fileId === undefined}
|
||||||
<Button
|
<Button
|
||||||
size="sm"
|
|
||||||
variant="outline"
|
variant="outline"
|
||||||
class="justify-start"
|
class="justify-start"
|
||||||
href={`https://www.openstreetmap.org/edit?#map=${(($map?.getZoom() ?? 17) + 1).toFixed(0)}/${trackpoint.item.getLatitude().toFixed(5)}/${trackpoint.item.getLongitude().toFixed(5)}`}
|
href={`https://www.openstreetmap.org/edit?#map=${(($map?.getZoom() ?? 17) + 1).toFixed(0)}/${trackpoint.item.getLatitude().toFixed(5)}/${trackpoint.item.getLongitude().toFixed(5)}`}
|
||||||
|
|||||||
@@ -88,7 +88,6 @@
|
|||||||
<CopyCoordinates coordinates={waypoint.item.attributes} />
|
<CopyCoordinates coordinates={waypoint.item.attributes} />
|
||||||
{#if $currentTool === Tool.WAYPOINT && selected}
|
{#if $currentTool === Tool.WAYPOINT && selected}
|
||||||
<Button
|
<Button
|
||||||
class="p-1 has-[>svg]:px-2 h-8"
|
|
||||||
variant="outline"
|
variant="outline"
|
||||||
onclick={() => {
|
onclick={() => {
|
||||||
if (waypoint.fileId) {
|
if (waypoint.fileId) {
|
||||||
|
|||||||
@@ -232,16 +232,13 @@
|
|||||||
|
|
||||||
<div class="flex flex-col">
|
<div class="flex flex-col">
|
||||||
{#if $customBasemapOrder.length > 0}
|
{#if $customBasemapOrder.length > 0}
|
||||||
|
<div class="px-3 py-2">
|
||||||
<div class="flex flex-row items-center gap-1 font-semibold mb-2">
|
<div class="flex flex-row items-center gap-1 font-semibold mb-2">
|
||||||
<Map size="16" />
|
<Map size="16" />
|
||||||
{i18n._('layers.label.basemaps')}
|
{i18n._('layers.label.basemaps')}
|
||||||
<div class="grow">
|
|
||||||
<Separator />
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
{/if}
|
|
||||||
<div
|
<div
|
||||||
class="ml-1.5 flex flex-col gap-1 {$customBasemapOrder.length > 0 ? 'mb-2' : ''}"
|
class="ml-1.5 flex flex-col gap-1"
|
||||||
use:dndzone={{
|
use:dndzone={{
|
||||||
items: customBasemapItems,
|
items: customBasemapItems,
|
||||||
type: 'basemap',
|
type: 'basemap',
|
||||||
@@ -258,14 +255,17 @@
|
|||||||
onfinalize={(e) => {
|
onfinalize={(e) => {
|
||||||
customBasemapItems = e.detail.items;
|
customBasemapItems = e.detail.items;
|
||||||
$customBasemapOrder = customBasemapItems.map((item) => item.id);
|
$customBasemapOrder = customBasemapItems.map((item) => item.id);
|
||||||
$selectedBasemapTree.basemaps['custom'] = customBasemapItems.reduce((acc, item) => {
|
$selectedBasemapTree.basemaps['custom'] = customBasemapItems.reduce(
|
||||||
|
(acc, item) => {
|
||||||
acc[item.id] = true;
|
acc[item.id] = true;
|
||||||
return acc;
|
return acc;
|
||||||
}, {});
|
},
|
||||||
|
{}
|
||||||
|
);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{#each customBasemapItems as item (item.id)}
|
{#each customBasemapItems as item (item.id)}
|
||||||
<div class="flex flex-row items-center gap-2">
|
<div class="flex flex-row items-center gap-1">
|
||||||
<Move size="12" />
|
<Move size="12" />
|
||||||
<span class="grow">{item.name}</span>
|
<span class="grow">{item.name}</span>
|
||||||
<Button
|
<Button
|
||||||
@@ -287,17 +287,18 @@
|
|||||||
</div>
|
</div>
|
||||||
{/each}
|
{/each}
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
<Separator />
|
||||||
|
{/if}
|
||||||
{#if $customOverlayOrder.length > 0}
|
{#if $customOverlayOrder.length > 0}
|
||||||
|
<div class="px-3 py-2">
|
||||||
<div class="flex flex-row items-center gap-1 font-semibold mb-2">
|
<div class="flex flex-row items-center gap-1 font-semibold mb-2">
|
||||||
<Layers2 size="16" />
|
<Layers2 size="16" />
|
||||||
{i18n._('layers.label.overlays')}
|
{i18n._('layers.label.overlays')}
|
||||||
<div class="grow">
|
<div class="grow"></div>
|
||||||
<Separator />
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
{/if}
|
|
||||||
<div
|
<div
|
||||||
class="ml-1.5 flex flex-col gap-1 {$customOverlayOrder.length > 0 ? 'mb-2' : ''}"
|
class="ml-1.5 flex flex-col gap-1"
|
||||||
use:dndzone={{
|
use:dndzone={{
|
||||||
items: customOverlayItems,
|
items: customOverlayItems,
|
||||||
type: 'overlay',
|
type: 'overlay',
|
||||||
@@ -314,14 +315,17 @@
|
|||||||
onfinalize={(e) => {
|
onfinalize={(e) => {
|
||||||
customOverlayItems = e.detail.items;
|
customOverlayItems = e.detail.items;
|
||||||
$customOverlayOrder = customOverlayItems.map((item) => item.id);
|
$customOverlayOrder = customOverlayItems.map((item) => item.id);
|
||||||
$selectedOverlayTree.overlays['custom'] = customOverlayItems.reduce((acc, item) => {
|
$selectedOverlayTree.overlays['custom'] = customOverlayItems.reduce(
|
||||||
|
(acc, item) => {
|
||||||
acc[item.id] = true;
|
acc[item.id] = true;
|
||||||
return acc;
|
return acc;
|
||||||
}, {});
|
},
|
||||||
|
{}
|
||||||
|
);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{#each customOverlayItems as item (item.id)}
|
{#each customOverlayItems as item (item.id)}
|
||||||
<div class="flex flex-row items-center gap-2">
|
<div class="flex flex-row items-center gap-1">
|
||||||
<Move size="12" />
|
<Move size="12" />
|
||||||
<span class="grow">{item.name}</span>
|
<span class="grow">{item.name}</span>
|
||||||
<Button
|
<Button
|
||||||
@@ -343,9 +347,12 @@
|
|||||||
</div>
|
</div>
|
||||||
{/each}
|
{/each}
|
||||||
</div>
|
</div>
|
||||||
<Card.Root class="py-0 gap-0 shadow-none">
|
</div>
|
||||||
|
<Separator />
|
||||||
|
{/if}
|
||||||
|
<Card.Root class="py-0 gap-0 shadow-none ring-0">
|
||||||
<Card.Header class="p-3">
|
<Card.Header class="p-3">
|
||||||
<Card.Title class="text-base">
|
<Card.Title class="text-sm font-semibold">
|
||||||
{#if selectedLayerId}
|
{#if selectedLayerId}
|
||||||
{i18n._('layers.custom_layers.edit')}
|
{i18n._('layers.custom_layers.edit')}
|
||||||
{:else}
|
{:else}
|
||||||
@@ -353,7 +360,7 @@
|
|||||||
{/if}
|
{/if}
|
||||||
</Card.Title>
|
</Card.Title>
|
||||||
</Card.Header>
|
</Card.Header>
|
||||||
<Card.Content class="p-3 pt-0">
|
<Card.Content class="px-3 py-2">
|
||||||
<fieldset class="flex flex-col gap-2">
|
<fieldset class="flex flex-col gap-2">
|
||||||
<Label for="name">{i18n._('menu.metadata.name')}</Label>
|
<Label for="name">{i18n._('menu.metadata.name')}</Label>
|
||||||
<Input bind:value={name} id="name" class="h-8" />
|
<Input bind:value={name} id="name" class="h-8" />
|
||||||
@@ -410,7 +417,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</RadioGroup.Root>
|
</RadioGroup.Root>
|
||||||
{#if selectedLayerId}
|
{#if selectedLayerId}
|
||||||
<div class="mt-2 flex flex-row gap-2">
|
<div class="mt-2 flex flex-row gap-1">
|
||||||
<Button variant="outline" onclick={createLayer} class="grow">
|
<Button variant="outline" onclick={createLayer} class="grow">
|
||||||
<Save size="16" />
|
<Save size="16" />
|
||||||
{i18n._('layers.custom_layers.update')}
|
{i18n._('layers.custom_layers.update')}
|
||||||
|
|||||||
@@ -121,7 +121,7 @@
|
|||||||
<Accordion.Root class="flex flex-col" bind:value={accordionValue} type="single">
|
<Accordion.Root class="flex flex-col" bind:value={accordionValue} type="single">
|
||||||
<Accordion.Item value="layer-selection" class="flex flex-col">
|
<Accordion.Item value="layer-selection" class="flex flex-col">
|
||||||
<Accordion.Trigger>{i18n._('layers.selection')}</Accordion.Trigger>
|
<Accordion.Trigger>{i18n._('layers.selection')}</Accordion.Trigger>
|
||||||
<Accordion.Content class="grow flex flex-col border rounded">
|
<Accordion.Content class="grow flex flex-col border rounded-md mb-1.5">
|
||||||
<div class="py-2 pl-3 pr-2">
|
<div class="py-2 pl-3 pr-2">
|
||||||
<LayerTree
|
<LayerTree
|
||||||
layerTree={basemapTree}
|
layerTree={basemapTree}
|
||||||
@@ -152,7 +152,9 @@
|
|||||||
</Accordion.Item>
|
</Accordion.Item>
|
||||||
<Accordion.Item value="overlay-opacity">
|
<Accordion.Item value="overlay-opacity">
|
||||||
<Accordion.Trigger>{i18n._('layers.opacity')}</Accordion.Trigger>
|
<Accordion.Trigger>{i18n._('layers.opacity')}</Accordion.Trigger>
|
||||||
<Accordion.Content class="flex flex-col gap-3 overflow-visible">
|
<Accordion.Content
|
||||||
|
class="flex flex-col gap-3 overflow-visible border rounded-md px-3 py-2 mb-1.5"
|
||||||
|
>
|
||||||
<div class="flex flex-row gap-6 items-center">
|
<div class="flex flex-row gap-6 items-center">
|
||||||
<Label>
|
<Label>
|
||||||
{i18n._('layers.custom_layers.overlay')}
|
{i18n._('layers.custom_layers.overlay')}
|
||||||
@@ -231,10 +233,10 @@
|
|||||||
<Accordion.Item value="custom-layers">
|
<Accordion.Item value="custom-layers">
|
||||||
<Accordion.Trigger>{i18n._('layers.custom_layers.title')}</Accordion.Trigger
|
<Accordion.Trigger>{i18n._('layers.custom_layers.title')}</Accordion.Trigger
|
||||||
>
|
>
|
||||||
<Accordion.Content>
|
<Accordion.Content
|
||||||
<ScrollArea>
|
class="flex flex-col overflow-visible border rounded-md p-0 mb-1.5"
|
||||||
|
>
|
||||||
<CustomLayers />
|
<CustomLayers />
|
||||||
</ScrollArea>
|
|
||||||
</Accordion.Content>
|
</Accordion.Content>
|
||||||
</Accordion.Item>
|
</Accordion.Item>
|
||||||
<Accordion.Item value="terrain-source">
|
<Accordion.Item value="terrain-source">
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import type { LayerTreeType } from '$lib/assets/layers';
|
import type { LayerTreeType } from '$lib/assets/layers';
|
||||||
import { writable } from 'svelte/store';
|
|
||||||
|
|
||||||
export function anySelectedLayer(node: LayerTreeType) {
|
export function anySelectedLayer(node: LayerTreeType) {
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -142,6 +142,7 @@ export class MapLayerEventManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private _handleMouseMove(e: maplibregl.MapMouseEvent) {
|
private _handleMouseMove(e: maplibregl.MapMouseEvent) {
|
||||||
|
if (e.originalEvent.buttons > 0) return;
|
||||||
const featuresByLayer = this._getRenderedFeaturesByLayer(e);
|
const featuresByLayer = this._getRenderedFeaturesByLayer(e);
|
||||||
Object.keys(this._listeners).forEach((layerId) => {
|
Object.keys(this._listeners).forEach((layerId) => {
|
||||||
const features = featuresByLayer[layerId] || [];
|
const features = featuresByLayer[layerId] || [];
|
||||||
|
|||||||
@@ -54,7 +54,7 @@ export class MapLibreGLMap {
|
|||||||
zoom: 0,
|
zoom: 0,
|
||||||
hash: hash,
|
hash: hash,
|
||||||
boxZoom: false,
|
boxZoom: false,
|
||||||
maxPitch: 85,
|
maxPitch: 90,
|
||||||
});
|
});
|
||||||
this.layerEventManager = new MapLayerEventManager(map);
|
this.layerEventManager = new MapLayerEventManager(map);
|
||||||
map.addControl(
|
map.addControl(
|
||||||
|
|||||||
@@ -53,7 +53,7 @@
|
|||||||
<CustomControl class="w-[29px] h-[29px] shrink-0">
|
<CustomControl class="w-[29px] h-[29px] shrink-0">
|
||||||
<ButtonWithTooltip
|
<ButtonWithTooltip
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
class="w-full h-full"
|
class="w-full h-full border-none rounded-sm"
|
||||||
side="left"
|
side="left"
|
||||||
label={i18n._('menu.toggle_street_view')}
|
label={i18n._('menu.toggle_street_view')}
|
||||||
onclick={() => {
|
onclick={() => {
|
||||||
|
|||||||
@@ -81,13 +81,20 @@ export class StyleManager {
|
|||||||
|
|
||||||
let basemap = get(currentBasemap);
|
let basemap = get(currentBasemap);
|
||||||
const basemapInfo = basemaps[basemap] ?? custom[basemap]?.value ?? basemaps[defaultBasemap];
|
const basemapInfo = basemaps[basemap] ?? custom[basemap]?.value ?? basemaps[defaultBasemap];
|
||||||
const basemapStyle = await this.get(basemapInfo);
|
|
||||||
|
|
||||||
|
let basemapStyle = basemaps.openStreetMap as maplibregl.StyleSpecification;
|
||||||
|
try {
|
||||||
|
basemapStyle = await this.get(basemapInfo);
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e.message);
|
||||||
|
}
|
||||||
this.merge(style, basemapStyle);
|
this.merge(style, basemapStyle);
|
||||||
|
|
||||||
|
if (this._maptilerKey !== '') {
|
||||||
const terrain = this.getCurrentTerrain();
|
const terrain = this.getCurrentTerrain();
|
||||||
style.sources[terrain.source] = terrainSources[terrain.source];
|
style.sources[terrain.source] = terrainSources[terrain.source];
|
||||||
style.terrain = terrain.exaggeration > 0 ? terrain : undefined;
|
style.terrain = terrain.exaggeration > 0 ? terrain : undefined;
|
||||||
|
}
|
||||||
|
|
||||||
style.layers.push(...anchorLayers);
|
style.layers.push(...anchorLayers);
|
||||||
|
|
||||||
@@ -107,16 +114,21 @@ export class StyleManager {
|
|||||||
if (!layers[overlay]) {
|
if (!layers[overlay]) {
|
||||||
if (this._pastOverlays.has(overlay)) {
|
if (this._pastOverlays.has(overlay)) {
|
||||||
const overlayInfo = custom[overlay]?.value ?? overlays[overlay];
|
const overlayInfo = custom[overlay]?.value ?? overlays[overlay];
|
||||||
|
try {
|
||||||
const overlayStyle = await this.get(overlayInfo);
|
const overlayStyle = await this.get(overlayInfo);
|
||||||
for (let layer of overlayStyle.layers ?? []) {
|
for (let layer of overlayStyle.layers ?? []) {
|
||||||
if (map_.getLayer(layer.id)) {
|
if (map_.getLayer(layer.id)) {
|
||||||
map_.removeLayer(layer.id);
|
map_.removeLayer(layer.id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
} catch (e) {
|
||||||
|
// Should not happen
|
||||||
|
}
|
||||||
this._pastOverlays.delete(overlay);
|
this._pastOverlays.delete(overlay);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
const overlayInfo = custom[overlay]?.value ?? overlays[overlay];
|
const overlayInfo = custom[overlay]?.value ?? overlays[overlay];
|
||||||
|
try {
|
||||||
const overlayStyle = await this.get(overlayInfo);
|
const overlayStyle = await this.get(overlayInfo);
|
||||||
const opacity = overlayOpacities[overlay];
|
const opacity = overlayOpacities[overlay];
|
||||||
|
|
||||||
@@ -144,14 +156,17 @@ export class StyleManager {
|
|||||||
map_.addLayer(layer, ANCHOR_LAYER_KEY.overlays);
|
map_.addLayer(layer, ANCHOR_LAYER_KEY.overlays);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this._pastOverlays.add(overlay);
|
this._pastOverlays.add(overlay);
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e.message);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (e) {}
|
} catch (e) {}
|
||||||
}
|
}
|
||||||
|
|
||||||
updateTerrain() {
|
updateTerrain() {
|
||||||
|
if (this._maptilerKey === '') return;
|
||||||
const map_ = get(this._map);
|
const map_ = get(this._map);
|
||||||
if (!map_) return;
|
if (!map_) return;
|
||||||
|
|
||||||
@@ -178,6 +193,9 @@ export class StyleManager {
|
|||||||
styleUrl = styleUrl.replace(maptilerKeyPlaceHolder, this._maptilerKey);
|
styleUrl = styleUrl.replace(maptilerKeyPlaceHolder, this._maptilerKey);
|
||||||
}
|
}
|
||||||
const response = await fetch(styleUrl, { cache: 'force-cache' });
|
const response = await fetch(styleUrl, { cache: 'force-cache' });
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error(`HTTP error fetching style "${styleInfo}": ${response.status}`);
|
||||||
|
}
|
||||||
const style = await response.json();
|
const style = await response.json();
|
||||||
return style;
|
return style;
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -17,7 +17,7 @@
|
|||||||
<div class="flex flex-col gap-3 w-full max-w-80 {props.class ?? ''}">
|
<div class="flex flex-col gap-3 w-full max-w-80 {props.class ?? ''}">
|
||||||
<Button
|
<Button
|
||||||
variant="outline"
|
variant="outline"
|
||||||
class="whitespace-normal h-fit"
|
class="whitespace-normal h-fit min-h-8 py-1"
|
||||||
disabled={!validSelection}
|
disabled={!validSelection}
|
||||||
onclick={() => fileActions.addElevationToSelection()}
|
onclick={() => fileActions.addElevationToSelection()}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -76,7 +76,7 @@
|
|||||||
{/if}
|
{/if}
|
||||||
<Button
|
<Button
|
||||||
variant="outline"
|
variant="outline"
|
||||||
class="whitespace-normal h-fit"
|
class="whitespace-normal h-fit min-h-8 py-1"
|
||||||
disabled={(mergeType === MergeType.TRACES && !canMergeTraces) ||
|
disabled={(mergeType === MergeType.TRACES && !canMergeTraces) ||
|
||||||
(mergeType === MergeType.CONTENTS && !canMergeContents)}
|
(mergeType === MergeType.CONTENTS && !canMergeContents)}
|
||||||
onclick={() => {
|
onclick={() => {
|
||||||
|
|||||||
@@ -185,8 +185,8 @@
|
|||||||
|
|
||||||
<div class="flex flex-col gap-3 w-full max-w-80 {props.class ?? ''}">
|
<div class="flex flex-col gap-3 w-full max-w-80 {props.class ?? ''}">
|
||||||
<fieldset class="flex flex-col gap-2">
|
<fieldset class="flex flex-col gap-2">
|
||||||
<div class="flex flex-row gap-2 justify-center">
|
<div class="flex flex-row gap-1.5 justify-center">
|
||||||
<div class="flex flex-col gap-2 grow">
|
<div class="flex flex-col gap-1 grow">
|
||||||
<Label for="speed" class="flex flex-row">
|
<Label for="speed" class="flex flex-row">
|
||||||
<Zap size="16" />
|
<Zap size="16" />
|
||||||
{#if $velocityUnits === 'speed'}
|
{#if $velocityUnits === 'speed'}
|
||||||
@@ -239,7 +239,7 @@
|
|||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex flex-col gap-2 grow">
|
<div class="flex flex-col gap-1 grow">
|
||||||
<Label for="duration" class="flex flex-row">
|
<Label for="duration" class="flex flex-row">
|
||||||
<Timer size="16" />
|
<Timer size="16" />
|
||||||
{i18n._('toolbar.time.total_time')}
|
{i18n._('toolbar.time.total_time')}
|
||||||
@@ -253,11 +253,12 @@
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="flex flex-col gap-1">
|
||||||
<Label class="flex flex-row">
|
<Label class="flex flex-row">
|
||||||
<CirclePlay size="16" />
|
<CirclePlay size="16" />
|
||||||
{i18n._('toolbar.time.start')}
|
{i18n._('toolbar.time.start')}
|
||||||
</Label>
|
</Label>
|
||||||
<div class="flex flex-row gap-2">
|
<div class="flex flex-row gap-1.5">
|
||||||
<DatePicker
|
<DatePicker
|
||||||
bind:value={startDate}
|
bind:value={startDate}
|
||||||
disabled={!canUpdate}
|
disabled={!canUpdate}
|
||||||
@@ -279,11 +280,13 @@
|
|||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="flex flex-col gap-1">
|
||||||
<Label class="flex flex-row">
|
<Label class="flex flex-row">
|
||||||
<CircleStop size="16" />
|
<CircleStop size="16" />
|
||||||
{i18n._('toolbar.time.end')}
|
{i18n._('toolbar.time.end')}
|
||||||
</Label>
|
</Label>
|
||||||
<div class="flex flex-row gap-2">
|
<div class="flex flex-row gap-1.5">
|
||||||
<DatePicker
|
<DatePicker
|
||||||
bind:value={endDate}
|
bind:value={endDate}
|
||||||
disabled={!canUpdate}
|
disabled={!canUpdate}
|
||||||
@@ -305,6 +308,7 @@
|
|||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
{#if $gpxStatistics.global.time.moving === 0 || $gpxStatistics.global.time.moving === undefined}
|
{#if $gpxStatistics.global.time.moving === 0 || $gpxStatistics.global.time.moving === undefined}
|
||||||
<div class="mt-0.5 flex flex-row gap-1 items-center">
|
<div class="mt-0.5 flex flex-row gap-1 items-center">
|
||||||
<Checkbox id="artificial-time" bind:checked={artificial} disabled={!canUpdate} />
|
<Checkbox id="artificial-time" bind:checked={artificial} disabled={!canUpdate} />
|
||||||
@@ -314,11 +318,11 @@
|
|||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
</fieldset>
|
</fieldset>
|
||||||
<div class="flex flex-row gap-2 items-center">
|
<div class="flex flex-row gap-1.5 items-center">
|
||||||
<Button
|
<Button
|
||||||
variant="outline"
|
variant="outline"
|
||||||
disabled={!canUpdate}
|
disabled={!canUpdate}
|
||||||
class="grow whitespace-normal h-fit"
|
class="grow shrink whitespace-normal h-fit min-h-8 py-1"
|
||||||
onclick={() => {
|
onclick={() => {
|
||||||
let effectiveSpeed = getSpeed();
|
let effectiveSpeed = getSpeed();
|
||||||
if (
|
if (
|
||||||
|
|||||||
@@ -14,7 +14,7 @@
|
|||||||
|
|
||||||
let props: { class?: string } = $props();
|
let props: { class?: string } = $props();
|
||||||
|
|
||||||
let sliderValue = $state([50]);
|
let sliderValue = $state(50);
|
||||||
const maxTolerance = 10000;
|
const maxTolerance = 10000;
|
||||||
|
|
||||||
let validSelection = $derived(
|
let validSelection = $derived(
|
||||||
@@ -25,7 +25,7 @@
|
|||||||
|
|
||||||
$effect(() => {
|
$effect(() => {
|
||||||
tolerance.set(
|
tolerance.set(
|
||||||
minTolerance * 2 ** (sliderValue[0] / (100 / Math.log2(maxTolerance / minTolerance)))
|
minTolerance * 2 ** (sliderValue / (100 / Math.log2(maxTolerance / minTolerance)))
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -36,7 +36,7 @@
|
|||||||
|
|
||||||
<div class="flex flex-col gap-3 w-full max-w-80 {props.class ?? ''}">
|
<div class="flex flex-col gap-3 w-full max-w-80 {props.class ?? ''}">
|
||||||
<div class="p-2">
|
<div class="p-2">
|
||||||
<Slider bind:value={sliderValue} min={0} max={100} step={1} type="multiple" />
|
<Slider bind:value={sliderValue} min={0} max={100} step={1} type="single" />
|
||||||
</div>
|
</div>
|
||||||
<Label class="flex flex-row justify-between">
|
<Label class="flex flex-row justify-between">
|
||||||
<span>{i18n._('toolbar.reduce.tolerance')}</span>
|
<span>{i18n._('toolbar.reduce.tolerance')}</span>
|
||||||
|
|||||||
@@ -191,7 +191,7 @@
|
|||||||
<ButtonWithTooltip
|
<ButtonWithTooltip
|
||||||
label={i18n._('toolbar.routing.reverse.tooltip')}
|
label={i18n._('toolbar.routing.reverse.tooltip')}
|
||||||
variant="outline"
|
variant="outline"
|
||||||
class="gap-1 text-xs"
|
class="gap-1 text-xs px-1.5 py-1.5 h-fit"
|
||||||
disabled={!validSelection}
|
disabled={!validSelection}
|
||||||
onclick={fileActions.reverseSelection}
|
onclick={fileActions.reverseSelection}
|
||||||
>
|
>
|
||||||
@@ -200,7 +200,7 @@
|
|||||||
<ButtonWithTooltip
|
<ButtonWithTooltip
|
||||||
label={i18n._('toolbar.routing.route_back_to_start.tooltip')}
|
label={i18n._('toolbar.routing.route_back_to_start.tooltip')}
|
||||||
variant="outline"
|
variant="outline"
|
||||||
class="gap-1 text-xs"
|
class="gap-1 text-xs px-1.5 py-1.5 h-fit"
|
||||||
disabled={!validSelection}
|
disabled={!validSelection}
|
||||||
onclick={() => {
|
onclick={() => {
|
||||||
const selected = selection.getOrderedSelection();
|
const selected = selection.getOrderedSelection();
|
||||||
@@ -236,14 +236,14 @@
|
|||||||
<ButtonWithTooltip
|
<ButtonWithTooltip
|
||||||
label={i18n._('toolbar.routing.round_trip.tooltip')}
|
label={i18n._('toolbar.routing.round_trip.tooltip')}
|
||||||
variant="outline"
|
variant="outline"
|
||||||
class="gap-1 text-xs"
|
class="gap-1 text-xs px-1.5 py-1.5 h-fit"
|
||||||
disabled={!validSelection}
|
disabled={!validSelection}
|
||||||
onclick={fileActions.createRoundTripForSelection}
|
onclick={fileActions.createRoundTripForSelection}
|
||||||
>
|
>
|
||||||
<Repeat class="size-3" />{i18n._('toolbar.routing.round_trip.button')}
|
<Repeat class="size-3" />{i18n._('toolbar.routing.round_trip.button')}
|
||||||
</ButtonWithTooltip>
|
</ButtonWithTooltip>
|
||||||
</div>
|
</div>
|
||||||
<div class="w-full flex flex-row gap-2 items-end justify-between">
|
<div class="w-full flex flex-row gap-1 items-end justify-between">
|
||||||
<Help link={getURLForLanguage(i18n.lang, '/help/toolbar/routing')}>
|
<Help link={getURLForLanguage(i18n.lang, '/help/toolbar/routing')}>
|
||||||
{#if !validSelection}
|
{#if !validSelection}
|
||||||
{i18n._('toolbar.routing.help_no_file')}
|
{i18n._('toolbar.routing.help_no_file')}
|
||||||
|
|||||||
@@ -57,8 +57,10 @@ export class RoutingControls {
|
|||||||
|
|
||||||
updateControlsBinded: () => void = this.updateControls.bind(this);
|
updateControlsBinded: () => void = this.updateControls.bind(this);
|
||||||
appendAnchorBinded: (e: MapMouseEvent) => void = this.appendAnchor.bind(this);
|
appendAnchorBinded: (e: MapMouseEvent) => void = this.appendAnchor.bind(this);
|
||||||
|
addIntermediateAnchorBinded: (e: MapMouseEvent) => void = this.addIntermediateAnchor.bind(this);
|
||||||
|
|
||||||
draggedAnchorIndex: number | null = null;
|
draggedAnchorIndex: number | null = null;
|
||||||
|
lastDraggedAnchorEventTime: number = 0;
|
||||||
draggingStartingPosition: maplibregl.Point = new maplibregl.Point(0, 0);
|
draggingStartingPosition: maplibregl.Point = new maplibregl.Point(0, 0);
|
||||||
onMouseEnterBinded: () => void = this.onMouseEnter.bind(this);
|
onMouseEnterBinded: () => void = this.onMouseEnter.bind(this);
|
||||||
onMouseLeaveBinded: () => void = this.onMouseLeave.bind(this);
|
onMouseLeaveBinded: () => void = this.onMouseLeave.bind(this);
|
||||||
@@ -85,7 +87,7 @@ export class RoutingControls {
|
|||||||
this.file = file;
|
this.file = file;
|
||||||
for (let zoom = MIN_ANCHOR_ZOOM; zoom <= MAX_ANCHOR_ZOOM; zoom++) {
|
for (let zoom = MIN_ANCHOR_ZOOM; zoom <= MAX_ANCHOR_ZOOM; zoom++) {
|
||||||
this.layers.set(zoom, {
|
this.layers.set(zoom, {
|
||||||
id: `routing-controls-${zoom}`,
|
id: `routing-controls-${this.fileId}-${zoom}`,
|
||||||
anchors: [],
|
anchors: [],
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -133,6 +135,7 @@ export class RoutingControls {
|
|||||||
map_.on('style.load', this.updateControlsBinded);
|
map_.on('style.load', this.updateControlsBinded);
|
||||||
map_.on('click', this.appendAnchorBinded);
|
map_.on('click', this.appendAnchorBinded);
|
||||||
layerEventManager.on('mousemove', this.fileId, this.showTemporaryAnchorBinded);
|
layerEventManager.on('mousemove', this.fileId, this.showTemporaryAnchorBinded);
|
||||||
|
layerEventManager.on('click', this.fileId, this.addIntermediateAnchorBinded);
|
||||||
|
|
||||||
this.fileUnsubscribe = this.file.subscribe(this.updateControlsBinded);
|
this.fileUnsubscribe = this.file.subscribe(this.updateControlsBinded);
|
||||||
}
|
}
|
||||||
@@ -237,6 +240,7 @@ export class RoutingControls {
|
|||||||
map_?.off('style.load', this.updateControlsBinded);
|
map_?.off('style.load', this.updateControlsBinded);
|
||||||
map_?.off('click', this.appendAnchorBinded);
|
map_?.off('click', this.appendAnchorBinded);
|
||||||
layerEventManager?.off('mousemove', this.fileId, this.showTemporaryAnchorBinded);
|
layerEventManager?.off('mousemove', this.fileId, this.showTemporaryAnchorBinded);
|
||||||
|
layerEventManager?.off('click', this.fileId, this.addIntermediateAnchorBinded);
|
||||||
map_?.off('mousemove', this.updateTemporaryAnchorBinded);
|
map_?.off('mousemove', this.updateTemporaryAnchorBinded);
|
||||||
|
|
||||||
this.layers.forEach((layer) => {
|
this.layers.forEach((layer) => {
|
||||||
@@ -521,15 +525,19 @@ export class RoutingControls {
|
|||||||
if (get(streetViewEnabled) && get(streetViewSource) === 'google') {
|
if (get(streetViewEnabled) && get(streetViewSource) === 'google') {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if (
|
||||||
|
this.draggedAnchorIndex !== null ||
|
||||||
|
Date.now() - this.lastDraggedAnchorEventTime < 100
|
||||||
|
) {
|
||||||
|
// Exit if anchor is being dragged
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (
|
if (
|
||||||
e.target.queryRenderedFeatures(e.point, {
|
e.target.queryRenderedFeatures(e.point, {
|
||||||
layers: this.layers
|
layers: [this.fileId, ...[...this.layers.values()].map((layer) => layer.id)],
|
||||||
.values()
|
|
||||||
.map((layer) => layer.id)
|
|
||||||
.toArray(),
|
|
||||||
}).length
|
}).length
|
||||||
) {
|
) {
|
||||||
// Clicked on routing control, ignoring
|
// Clicked on routing control or layer, ignoring
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this.appendAnchorWithCoordinates({
|
this.appendAnchorWithCoordinates({
|
||||||
@@ -601,6 +609,15 @@ export class RoutingControls {
|
|||||||
await this.routeBetweenAnchors([lastAnchor, newAnchor], [lastAnchorPoint, newAnchorPoint]);
|
await this.routeBetweenAnchors([lastAnchor, newAnchor], [lastAnchorPoint, newAnchorPoint]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
addIntermediateAnchor(e: maplibregl.MapMouseEvent) {
|
||||||
|
e.preventDefault();
|
||||||
|
|
||||||
|
if (this.temporaryAnchor !== null) {
|
||||||
|
this.turnIntoPermanentAnchor();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
getNeighbouringAnchors(anchor: Anchor): [Anchor | null, Anchor | null] {
|
getNeighbouringAnchors(anchor: Anchor): [Anchor | null, Anchor | null] {
|
||||||
let previousAnchor: Anchor | null = null;
|
let previousAnchor: Anchor | null = null;
|
||||||
let nextAnchor: Anchor | null = null;
|
let nextAnchor: Anchor | null = null;
|
||||||
@@ -803,7 +820,7 @@ export class RoutingControls {
|
|||||||
`<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20">
|
`<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20">
|
||||||
<circle cx="10" cy="10" r="8" fill="white" stroke="black" stroke-width="2" />
|
<circle cx="10" cy="10" r="8" fill="white" stroke="black" stroke-width="2" />
|
||||||
</svg>`,
|
</svg>`,
|
||||||
_map.getCanvasContainer().offsetWidth > 1000 ? 50 : 80
|
_map.getCanvasContainer().offsetWidth > 1000 ? 56 : 80
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -821,8 +838,11 @@ export class RoutingControls {
|
|||||||
onClick(e: MapLayerMouseEvent) {
|
onClick(e: MapLayerMouseEvent) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
|
||||||
if (this.temporaryAnchor !== null) {
|
if (
|
||||||
this.turnIntoPermanentAnchor();
|
this.draggedAnchorIndex !== null ||
|
||||||
|
Date.now() - this.lastDraggedAnchorEventTime < 100
|
||||||
|
) {
|
||||||
|
// Exit if anchor is being dragged
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -911,6 +931,8 @@ export class RoutingControls {
|
|||||||
lat: e.lngLat.lat,
|
lat: e.lngLat.lat,
|
||||||
lon: e.lngLat.lng,
|
lon: e.lngLat.lng,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
this.lastDraggedAnchorEventTime = Date.now();
|
||||||
}
|
}
|
||||||
|
|
||||||
onMouseUp(e: MapLayerMouseEvent | MapLayerTouchEvent) {
|
onMouseUp(e: MapLayerMouseEvent | MapLayerTouchEvent) {
|
||||||
@@ -949,6 +971,7 @@ export class RoutingControls {
|
|||||||
}
|
}
|
||||||
|
|
||||||
this.draggedAnchorIndex = null;
|
this.draggedAnchorIndex = null;
|
||||||
|
this.lastDraggedAnchorEventTime = Date.now();
|
||||||
}
|
}
|
||||||
|
|
||||||
showTemporaryAnchor(e: MapLayerMouseEvent) {
|
showTemporaryAnchor(e: MapLayerMouseEvent) {
|
||||||
@@ -1076,7 +1099,9 @@ export class RoutingControls {
|
|||||||
if (!this.temporaryAnchor) {
|
if (!this.temporaryAnchor) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
let source = get(map)?.getSource('routing-controls-0') as GeoJSONSource | undefined;
|
let source = get(map)?.getSource(`routing-controls-${this.fileId}-0`) as
|
||||||
|
| GeoJSONSource
|
||||||
|
| undefined;
|
||||||
if (source) {
|
if (source) {
|
||||||
if (this.temporaryAnchor) {
|
if (this.temporaryAnchor) {
|
||||||
source.updateData({
|
source.updateData({
|
||||||
@@ -1091,7 +1116,9 @@ export class RoutingControls {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const map_ = get(map);
|
const map_ = get(map);
|
||||||
let source = map_?.getSource('routing-controls-0') as GeoJSONSource | undefined;
|
let source = map_?.getSource(`routing-controls-${this.fileId}-0`) as
|
||||||
|
| GeoJSONSource
|
||||||
|
| undefined;
|
||||||
if (source) {
|
if (source) {
|
||||||
if (this.temporaryAnchor) {
|
if (this.temporaryAnchor) {
|
||||||
source.updateData({
|
source.updateData({
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ export const routingProfiles: { [key: string]: RoutingProfile } = {
|
|||||||
gravel_bike: { engine: 'graphhopper', profile: 'gravelbike' },
|
gravel_bike: { engine: 'graphhopper', profile: 'gravelbike' },
|
||||||
mountain_bike: { engine: 'graphhopper', profile: 'mtb' },
|
mountain_bike: { engine: 'graphhopper', profile: 'mtb' },
|
||||||
foot: { engine: 'graphhopper', profile: 'foot' },
|
foot: { engine: 'graphhopper', profile: 'foot' },
|
||||||
motorcycle: { engine: 'graphhopper', profile: 'motorcycle' },
|
motorcycle: { engine: 'graphhopper', profile: 'motorbike' },
|
||||||
water: { engine: 'brouter', profile: 'river' },
|
water: { engine: 'brouter', profile: 'river' },
|
||||||
railway: { engine: 'brouter', profile: 'rail' },
|
railway: { engine: 'brouter', profile: 'rail' },
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -161,14 +161,17 @@
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="flex flex-col gap-3 w-full max-w-96 {props.class ?? ''}">
|
<div class="flex flex-col gap-3 w-full max-w-96 {props.class ?? ''}">
|
||||||
<fieldset class="flex flex-col gap-2">
|
<fieldset class="flex flex-col gap-1.5">
|
||||||
|
<div class="flex flex-col gap-1">
|
||||||
<Label for="name">{i18n._('menu.metadata.name')}</Label>
|
<Label for="name">{i18n._('menu.metadata.name')}</Label>
|
||||||
<Input
|
<Input
|
||||||
bind:value={name}
|
bind:value={name}
|
||||||
id="name"
|
id="name"
|
||||||
class="font-semibold h-8"
|
class="font-semibold"
|
||||||
disabled={!canCreate && !$selectedWaypoint}
|
disabled={!canCreate && !$selectedWaypoint}
|
||||||
/>
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="flex flex-col gap-1">
|
||||||
<Label for="description">{i18n._('menu.metadata.description')}</Label>
|
<Label for="description">{i18n._('menu.metadata.description')}</Label>
|
||||||
<Textarea
|
<Textarea
|
||||||
bind:value={description}
|
bind:value={description}
|
||||||
@@ -176,11 +179,12 @@
|
|||||||
disabled={!canCreate && !$selectedWaypoint}
|
disabled={!canCreate && !$selectedWaypoint}
|
||||||
class="min-h-8 h-8 py-1 px-3 text-sm"
|
class="min-h-8 h-8 py-1 px-3 text-sm"
|
||||||
/>
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="flex flex-col gap-1">
|
||||||
<Label for="symbol">{i18n._('toolbar.waypoint.icon')}</Label>
|
<Label for="symbol">{i18n._('toolbar.waypoint.icon')}</Label>
|
||||||
<Select.Root bind:value={sym} type="single">
|
<Select.Root bind:value={sym} type="single">
|
||||||
<Select.Trigger
|
<Select.Trigger
|
||||||
id="symbol"
|
id="symbol"
|
||||||
size="sm"
|
|
||||||
class="w-full"
|
class="w-full"
|
||||||
disabled={!canCreate && !$selectedWaypoint}
|
disabled={!canCreate && !$selectedWaypoint}
|
||||||
>
|
>
|
||||||
@@ -196,7 +200,7 @@
|
|||||||
{/if}
|
{/if}
|
||||||
</span>
|
</span>
|
||||||
</Select.Trigger>
|
</Select.Trigger>
|
||||||
<Select.Content class="max-h-60 overflow-y-scroll">
|
<Select.Content class="max-h-60">
|
||||||
{#each sortedSymbols as [key, symbol]}
|
{#each sortedSymbols as [key, symbol]}
|
||||||
<Select.Item value={symbol.value}>
|
<Select.Item value={symbol.value}>
|
||||||
<span>
|
<span>
|
||||||
@@ -212,6 +216,8 @@
|
|||||||
{/each}
|
{/each}
|
||||||
</Select.Content>
|
</Select.Content>
|
||||||
</Select.Root>
|
</Select.Root>
|
||||||
|
</div>
|
||||||
|
<div class="flex flex-col gap-1">
|
||||||
<Label for="link">{i18n._('toolbar.waypoint.link')}</Label>
|
<Label for="link">{i18n._('toolbar.waypoint.link')}</Label>
|
||||||
<Input
|
<Input
|
||||||
bind:value={link}
|
bind:value={link}
|
||||||
@@ -219,8 +225,9 @@
|
|||||||
class="h-8"
|
class="h-8"
|
||||||
disabled={!canCreate && !$selectedWaypoint}
|
disabled={!canCreate && !$selectedWaypoint}
|
||||||
/>
|
/>
|
||||||
<div class="flex flex-row gap-2">
|
</div>
|
||||||
<div class="grow flex flex-col gap-2">
|
<div class="flex flex-row gap-1.5">
|
||||||
|
<div class="grow flex flex-col gap-1">
|
||||||
<Label for="latitude">{i18n._('toolbar.waypoint.latitude')}</Label>
|
<Label for="latitude">{i18n._('toolbar.waypoint.latitude')}</Label>
|
||||||
<Input
|
<Input
|
||||||
bind:value={latitude}
|
bind:value={latitude}
|
||||||
@@ -233,7 +240,7 @@
|
|||||||
disabled={!canCreate && !$selectedWaypoint}
|
disabled={!canCreate && !$selectedWaypoint}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div class="grow flex flex-col gap-2">
|
<div class="grow flex flex-col gap-1">
|
||||||
<Label for="longitude">{i18n._('toolbar.waypoint.longitude')}</Label>
|
<Label for="longitude">{i18n._('toolbar.waypoint.longitude')}</Label>
|
||||||
<Input
|
<Input
|
||||||
bind:value={longitude}
|
bind:value={longitude}
|
||||||
@@ -248,11 +255,11 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</fieldset>
|
</fieldset>
|
||||||
<div class="flex flex-row gap-2 items-center">
|
<div class="flex flex-row gap-1.5 items-center">
|
||||||
<Button
|
<Button
|
||||||
variant="outline"
|
variant="outline"
|
||||||
disabled={!canCreate && !$selectedWaypoint}
|
disabled={!canCreate && !$selectedWaypoint}
|
||||||
class="grow whitespace-normal h-fit"
|
class="grow shrink h-fit min-h-8 whitespace-normal py-1"
|
||||||
onclick={createOrUpdateWaypoint}
|
onclick={createOrUpdateWaypoint}
|
||||||
>
|
>
|
||||||
{#if $selectedWaypoint}
|
{#if $selectedWaypoint}
|
||||||
|
|||||||
@@ -13,10 +13,15 @@
|
|||||||
<AccordionPrimitive.Content
|
<AccordionPrimitive.Content
|
||||||
bind:ref
|
bind:ref
|
||||||
data-slot="accordion-content"
|
data-slot="accordion-content"
|
||||||
class="data-[state=closed]:animate-accordion-up data-[state=open]:animate-accordion-down overflow-hidden text-sm"
|
class="data-open:animate-accordion-down data-closed:animate-accordion-up text-sm overflow-hidden"
|
||||||
{...restProps}
|
{...restProps}
|
||||||
>
|
>
|
||||||
<div class={cn("pb-4 pt-0", className)}>
|
<div
|
||||||
|
class={cn(
|
||||||
|
"pt-0 pb-2.5 [&_a]:hover:text-foreground [&_a]:underline [&_a]:underline-offset-3 [&_p:not(:last-child)]:mb-4",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
>
|
||||||
{@render children?.()}
|
{@render children?.()}
|
||||||
</div>
|
</div>
|
||||||
</AccordionPrimitive.Content>
|
</AccordionPrimitive.Content>
|
||||||
|
|||||||
@@ -12,6 +12,6 @@
|
|||||||
<AccordionPrimitive.Item
|
<AccordionPrimitive.Item
|
||||||
bind:ref
|
bind:ref
|
||||||
data-slot="accordion-item"
|
data-slot="accordion-item"
|
||||||
class={cn("border-b last:border-b-0", className)}
|
class={cn("not-last:border-b", className)}
|
||||||
{...restProps}
|
{...restProps}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { Accordion as AccordionPrimitive } from "bits-ui";
|
import { Accordion as AccordionPrimitive } from "bits-ui";
|
||||||
import ChevronDownIcon from "@lucide/svelte/icons/chevron-down";
|
|
||||||
import { cn, type WithoutChild } from "$lib/utils.js";
|
import { cn, type WithoutChild } from "$lib/utils.js";
|
||||||
|
import ChevronDownIcon from '@lucide/svelte/icons/chevron-down';
|
||||||
|
import ChevronUpIcon from '@lucide/svelte/icons/chevron-up';
|
||||||
|
|
||||||
let {
|
let {
|
||||||
ref = $bindable(null),
|
ref = $bindable(null),
|
||||||
@@ -19,14 +20,13 @@
|
|||||||
data-slot="accordion-trigger"
|
data-slot="accordion-trigger"
|
||||||
bind:ref
|
bind:ref
|
||||||
class={cn(
|
class={cn(
|
||||||
"focus-visible:border-ring focus-visible:ring-ring/50 flex flex-1 items-start justify-between gap-4 rounded-md py-4 text-left text-sm font-medium outline-none transition-all hover:underline focus-visible:ring-[3px] disabled:pointer-events-none disabled:opacity-50 [&[data-state=open]>svg]:rotate-180",
|
"focus-visible:ring-ring/50 focus-visible:border-ring focus-visible:after:border-ring **:data-[slot=accordion-trigger-icon]:text-muted-foreground rounded-lg py-2.5 text-left text-sm font-medium hover:underline focus-visible:ring-3 **:data-[slot=accordion-trigger-icon]:ml-auto **:data-[slot=accordion-trigger-icon]:size-4 group/accordion-trigger relative flex flex-1 items-start justify-between border border-transparent transition-all outline-none disabled:pointer-events-none disabled:opacity-50",
|
||||||
className
|
className
|
||||||
)}
|
)}
|
||||||
{...restProps}
|
{...restProps}
|
||||||
>
|
>
|
||||||
{@render children?.()}
|
{@render children?.()}
|
||||||
<ChevronDownIcon
|
<ChevronDownIcon data-slot="accordion-trigger-icon" class="cn-accordion-trigger-icon pointer-events-none shrink-0 group-aria-expanded/accordion-trigger:hidden" />
|
||||||
class="text-muted-foreground pointer-events-none size-4 shrink-0 translate-y-0.5 transition-transform duration-200"
|
<ChevronUpIcon data-slot="accordion-trigger-icon" class="cn-accordion-trigger-icon pointer-events-none hidden shrink-0 group-aria-expanded/accordion-trigger:inline" />
|
||||||
/>
|
|
||||||
</AccordionPrimitive.Trigger>
|
</AccordionPrimitive.Trigger>
|
||||||
</AccordionPrimitive.Header>
|
</AccordionPrimitive.Header>
|
||||||
|
|||||||
@@ -1,9 +1,11 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { Accordion as AccordionPrimitive } from "bits-ui";
|
import { Accordion as AccordionPrimitive } from "bits-ui";
|
||||||
|
import { cn } from "$lib/utils.js";
|
||||||
|
|
||||||
let {
|
let {
|
||||||
ref = $bindable(null),
|
ref = $bindable(null),
|
||||||
value = $bindable(),
|
value = $bindable(),
|
||||||
|
class: className,
|
||||||
...restProps
|
...restProps
|
||||||
}: AccordionPrimitive.RootProps = $props();
|
}: AccordionPrimitive.RootProps = $props();
|
||||||
</script>
|
</script>
|
||||||
@@ -12,5 +14,6 @@
|
|||||||
bind:ref
|
bind:ref
|
||||||
bind:value={value as never}
|
bind:value={value as never}
|
||||||
data-slot="accordion"
|
data-slot="accordion"
|
||||||
|
class={cn("cn-accordion flex w-full flex-col", className)}
|
||||||
{...restProps}
|
{...restProps}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -1,18 +1,27 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { AlertDialog as AlertDialogPrimitive } from "bits-ui";
|
import { AlertDialog as AlertDialogPrimitive } from "bits-ui";
|
||||||
import { buttonVariants } from "$lib/components/ui/button/index.js";
|
import {
|
||||||
|
buttonVariants,
|
||||||
|
type ButtonVariant,
|
||||||
|
type ButtonSize,
|
||||||
|
} from "$lib/components/ui/button/index.js";
|
||||||
import { cn } from "$lib/utils.js";
|
import { cn } from "$lib/utils.js";
|
||||||
|
|
||||||
let {
|
let {
|
||||||
ref = $bindable(null),
|
ref = $bindable(null),
|
||||||
class: className,
|
class: className,
|
||||||
|
variant = "default",
|
||||||
|
size = "default",
|
||||||
...restProps
|
...restProps
|
||||||
}: AlertDialogPrimitive.ActionProps = $props();
|
}: AlertDialogPrimitive.ActionProps & {
|
||||||
|
variant?: ButtonVariant;
|
||||||
|
size?: ButtonSize;
|
||||||
|
} = $props();
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<AlertDialogPrimitive.Action
|
<AlertDialogPrimitive.Action
|
||||||
bind:ref
|
bind:ref
|
||||||
data-slot="alert-dialog-action"
|
data-slot="alert-dialog-action"
|
||||||
class={cn(buttonVariants(), className)}
|
class={cn(buttonVariants({ variant, size }), "cn-alert-dialog-action", className)}
|
||||||
{...restProps}
|
{...restProps}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -1,18 +1,27 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { AlertDialog as AlertDialogPrimitive } from "bits-ui";
|
import { AlertDialog as AlertDialogPrimitive } from "bits-ui";
|
||||||
import { buttonVariants } from "$lib/components/ui/button/index.js";
|
import {
|
||||||
|
buttonVariants,
|
||||||
|
type ButtonVariant,
|
||||||
|
type ButtonSize,
|
||||||
|
} from "$lib/components/ui/button/index.js";
|
||||||
import { cn } from "$lib/utils.js";
|
import { cn } from "$lib/utils.js";
|
||||||
|
|
||||||
let {
|
let {
|
||||||
ref = $bindable(null),
|
ref = $bindable(null),
|
||||||
class: className,
|
class: className,
|
||||||
|
variant = "outline",
|
||||||
|
size = "default",
|
||||||
...restProps
|
...restProps
|
||||||
}: AlertDialogPrimitive.CancelProps = $props();
|
}: AlertDialogPrimitive.CancelProps & {
|
||||||
|
variant?: ButtonVariant;
|
||||||
|
size?: ButtonSize;
|
||||||
|
} = $props();
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<AlertDialogPrimitive.Cancel
|
<AlertDialogPrimitive.Cancel
|
||||||
bind:ref
|
bind:ref
|
||||||
data-slot="alert-dialog-cancel"
|
data-slot="alert-dialog-cancel"
|
||||||
class={cn(buttonVariants({ variant: "outline" }), className)}
|
class={cn(buttonVariants({ variant, size }), "cn-alert-dialog-cancel", className)}
|
||||||
{...restProps}
|
{...restProps}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -1,27 +1,32 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { AlertDialog as AlertDialogPrimitive } from "bits-ui";
|
import { AlertDialog as AlertDialogPrimitive } from "bits-ui";
|
||||||
|
import AlertDialogPortal from "./alert-dialog-portal.svelte";
|
||||||
import AlertDialogOverlay from "./alert-dialog-overlay.svelte";
|
import AlertDialogOverlay from "./alert-dialog-overlay.svelte";
|
||||||
import { cn, type WithoutChild, type WithoutChildrenOrChild } from "$lib/utils.js";
|
import { cn, type WithoutChild, type WithoutChildrenOrChild } from "$lib/utils.js";
|
||||||
|
import type { ComponentProps } from "svelte";
|
||||||
|
|
||||||
let {
|
let {
|
||||||
ref = $bindable(null),
|
ref = $bindable(null),
|
||||||
class: className,
|
class: className,
|
||||||
|
size = "default",
|
||||||
portalProps,
|
portalProps,
|
||||||
...restProps
|
...restProps
|
||||||
}: WithoutChild<AlertDialogPrimitive.ContentProps> & {
|
}: WithoutChild<AlertDialogPrimitive.ContentProps> & {
|
||||||
portalProps?: WithoutChildrenOrChild<AlertDialogPrimitive.PortalProps>;
|
size?: "default" | "sm";
|
||||||
|
portalProps?: WithoutChildrenOrChild<ComponentProps<typeof AlertDialogPortal>>;
|
||||||
} = $props();
|
} = $props();
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<AlertDialogPrimitive.Portal {...portalProps}>
|
<AlertDialogPortal {...portalProps}>
|
||||||
<AlertDialogOverlay />
|
<AlertDialogOverlay />
|
||||||
<AlertDialogPrimitive.Content
|
<AlertDialogPrimitive.Content
|
||||||
bind:ref
|
bind:ref
|
||||||
data-slot="alert-dialog-content"
|
data-slot="alert-dialog-content"
|
||||||
|
data-size={size}
|
||||||
class={cn(
|
class={cn(
|
||||||
"bg-background data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 fixed left-[50%] top-[50%] z-50 grid w-full max-w-[calc(100%-2rem)] translate-x-[-50%] translate-y-[-50%] gap-4 rounded-lg border p-6 shadow-lg duration-200 sm:max-w-lg",
|
"data-open:animate-in data-closed:animate-out data-closed:fade-out-0 data-open:fade-in-0 data-closed:zoom-out-95 data-open:zoom-in-95 bg-popover text-popover-foreground ring-foreground/10 gap-4 rounded-xl p-4 ring-1 duration-100 data-[size=default]:max-w-xs data-[size=sm]:max-w-xs data-[size=default]:sm:max-w-sm group/alert-dialog-content fixed top-1/2 left-1/2 z-50 grid w-full -translate-x-1/2 -translate-y-1/2 outline-none",
|
||||||
className
|
className
|
||||||
)}
|
)}
|
||||||
{...restProps}
|
{...restProps}
|
||||||
/>
|
/>
|
||||||
</AlertDialogPrimitive.Portal>
|
</AlertDialogPortal>
|
||||||
|
|||||||
@@ -12,6 +12,6 @@
|
|||||||
<AlertDialogPrimitive.Description
|
<AlertDialogPrimitive.Description
|
||||||
bind:ref
|
bind:ref
|
||||||
data-slot="alert-dialog-description"
|
data-slot="alert-dialog-description"
|
||||||
class={cn("text-muted-foreground text-sm", className)}
|
class={cn("text-muted-foreground *:[a]:hover:text-foreground text-sm text-balance md:text-pretty *:[a]:underline *:[a]:underline-offset-3", className)}
|
||||||
{...restProps}
|
{...restProps}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -13,7 +13,10 @@
|
|||||||
<div
|
<div
|
||||||
bind:this={ref}
|
bind:this={ref}
|
||||||
data-slot="alert-dialog-footer"
|
data-slot="alert-dialog-footer"
|
||||||
class={cn("flex flex-col-reverse gap-2 sm:flex-row sm:justify-end", className)}
|
class={cn(
|
||||||
|
"bg-muted/50 -mx-4 -mb-4 rounded-b-xl border-t p-4 flex flex-col-reverse gap-2 group-data-[size=sm]/alert-dialog-content:grid group-data-[size=sm]/alert-dialog-content:grid-cols-2 sm:flex-row sm:justify-end",
|
||||||
|
className
|
||||||
|
)}
|
||||||
{...restProps}
|
{...restProps}
|
||||||
>
|
>
|
||||||
{@render children?.()}
|
{@render children?.()}
|
||||||
|
|||||||
@@ -13,7 +13,7 @@
|
|||||||
<div
|
<div
|
||||||
bind:this={ref}
|
bind:this={ref}
|
||||||
data-slot="alert-dialog-header"
|
data-slot="alert-dialog-header"
|
||||||
class={cn("flex flex-col gap-2 text-center sm:text-left", className)}
|
class={cn("grid grid-rows-[auto_1fr] place-items-center gap-1.5 text-center has-data-[slot=alert-dialog-media]:grid-rows-[auto_auto_1fr] has-data-[slot=alert-dialog-media]:gap-x-4 sm:group-data-[size=default]/alert-dialog-content:place-items-start sm:group-data-[size=default]/alert-dialog-content:text-left sm:group-data-[size=default]/alert-dialog-content:has-data-[slot=alert-dialog-media]:grid-rows-[auto_1fr]", className)}
|
||||||
{...restProps}
|
{...restProps}
|
||||||
>
|
>
|
||||||
{@render children?.()}
|
{@render children?.()}
|
||||||
|
|||||||
@@ -0,0 +1,20 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import type { HTMLAttributes } from "svelte/elements";
|
||||||
|
import { cn, type WithElementRef } from "$lib/utils.js";
|
||||||
|
|
||||||
|
let {
|
||||||
|
ref = $bindable(null),
|
||||||
|
class: className,
|
||||||
|
children,
|
||||||
|
...restProps
|
||||||
|
}: WithElementRef<HTMLAttributes<HTMLDivElement>> = $props();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div
|
||||||
|
bind:this={ref}
|
||||||
|
data-slot="alert-dialog-media"
|
||||||
|
class={cn("bg-muted mb-2 inline-flex size-10 items-center justify-center rounded-md sm:group-data-[size=default]/alert-dialog-content:row-span-2 *:[svg:not([class*='size-'])]:size-6", className)}
|
||||||
|
{...restProps}
|
||||||
|
>
|
||||||
|
{@render children?.()}
|
||||||
|
</div>
|
||||||
@@ -12,9 +12,6 @@
|
|||||||
<AlertDialogPrimitive.Overlay
|
<AlertDialogPrimitive.Overlay
|
||||||
bind:ref
|
bind:ref
|
||||||
data-slot="alert-dialog-overlay"
|
data-slot="alert-dialog-overlay"
|
||||||
class={cn(
|
class={cn("data-open:animate-in data-closed:animate-out data-closed:fade-out-0 data-open:fade-in-0 bg-black/10 duration-100 supports-backdrop-filter:backdrop-blur-xs fixed inset-0 z-50", className)}
|
||||||
"data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 fixed inset-0 z-50 bg-black/50",
|
|
||||||
className
|
|
||||||
)}
|
|
||||||
{...restProps}
|
{...restProps}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -1,9 +1,7 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { AlertDialog as AlertDialogPrimitive } from "bits-ui";
|
import { AlertDialog as AlertDialogPrimitive } from "bits-ui";
|
||||||
|
|
||||||
type $$Props = AlertDialogPrimitive.PortalProps;
|
let { ...restProps }: AlertDialogPrimitive.PortalProps = $props();
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<AlertDialogPrimitive.Portal {...$$restProps}>
|
<AlertDialogPrimitive.Portal {...restProps} />
|
||||||
<slot />
|
|
||||||
</AlertDialogPrimitive.Portal>
|
|
||||||
|
|||||||
@@ -12,6 +12,6 @@
|
|||||||
<AlertDialogPrimitive.Title
|
<AlertDialogPrimitive.Title
|
||||||
bind:ref
|
bind:ref
|
||||||
data-slot="alert-dialog-title"
|
data-slot="alert-dialog-title"
|
||||||
class={cn("text-lg font-semibold", className)}
|
class={cn("text-base font-medium sm:group-data-[size=default]/alert-dialog-content:group-has-data-[slot=alert-dialog-media]/alert-dialog-content:col-start-2", className)}
|
||||||
{...restProps}
|
{...restProps}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -0,0 +1,7 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import { AlertDialog as AlertDialogPrimitive } from "bits-ui";
|
||||||
|
|
||||||
|
let { open = $bindable(false), ...restProps }: AlertDialogPrimitive.RootProps = $props();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<AlertDialogPrimitive.Root bind:open {...restProps} />
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
import { AlertDialog as AlertDialogPrimitive } from "bits-ui";
|
import Root from "./alert-dialog.svelte";
|
||||||
|
import Portal from "./alert-dialog-portal.svelte";
|
||||||
import Trigger from "./alert-dialog-trigger.svelte";
|
import Trigger from "./alert-dialog-trigger.svelte";
|
||||||
import Title from "./alert-dialog-title.svelte";
|
import Title from "./alert-dialog-title.svelte";
|
||||||
import Action from "./alert-dialog-action.svelte";
|
import Action from "./alert-dialog-action.svelte";
|
||||||
@@ -8,9 +9,7 @@ import Header from "./alert-dialog-header.svelte";
|
|||||||
import Overlay from "./alert-dialog-overlay.svelte";
|
import Overlay from "./alert-dialog-overlay.svelte";
|
||||||
import Content from "./alert-dialog-content.svelte";
|
import Content from "./alert-dialog-content.svelte";
|
||||||
import Description from "./alert-dialog-description.svelte";
|
import Description from "./alert-dialog-description.svelte";
|
||||||
|
import Media from "./alert-dialog-media.svelte";
|
||||||
const Root = AlertDialogPrimitive.Root;
|
|
||||||
const Portal = AlertDialogPrimitive.Portal;
|
|
||||||
|
|
||||||
export {
|
export {
|
||||||
Root,
|
Root,
|
||||||
@@ -24,6 +23,7 @@ export {
|
|||||||
Overlay,
|
Overlay,
|
||||||
Content,
|
Content,
|
||||||
Description,
|
Description,
|
||||||
|
Media,
|
||||||
//
|
//
|
||||||
Root as AlertDialog,
|
Root as AlertDialog,
|
||||||
Title as AlertDialogTitle,
|
Title as AlertDialogTitle,
|
||||||
@@ -36,4 +36,5 @@ export {
|
|||||||
Overlay as AlertDialogOverlay,
|
Overlay as AlertDialogOverlay,
|
||||||
Content as AlertDialogContent,
|
Content as AlertDialogContent,
|
||||||
Description as AlertDialogDescription,
|
Description as AlertDialogDescription,
|
||||||
|
Media as AlertDialogMedia,
|
||||||
};
|
};
|
||||||
|
|||||||
20
website/src/lib/components/ui/alert/alert-action.svelte
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import type { HTMLAttributes } from "svelte/elements";
|
||||||
|
import { cn, type WithElementRef } from "$lib/utils.js";
|
||||||
|
|
||||||
|
let {
|
||||||
|
ref = $bindable(null),
|
||||||
|
class: className,
|
||||||
|
children,
|
||||||
|
...restProps
|
||||||
|
}: WithElementRef<HTMLAttributes<HTMLDivElement>> = $props();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div
|
||||||
|
bind:this={ref}
|
||||||
|
data-slot="alert-action"
|
||||||
|
class={cn("absolute top-2 right-2", className)}
|
||||||
|
{...restProps}
|
||||||
|
>
|
||||||
|
{@render children?.()}
|
||||||
|
</div>
|
||||||
@@ -14,7 +14,7 @@
|
|||||||
bind:this={ref}
|
bind:this={ref}
|
||||||
data-slot="alert-description"
|
data-slot="alert-description"
|
||||||
class={cn(
|
class={cn(
|
||||||
"text-muted-foreground col-start-2 grid justify-items-start gap-1 text-sm [&_p]:leading-relaxed",
|
"text-muted-foreground text-sm text-balance md:text-pretty [&_p:not(:last-child)]:mb-4 [&_a]:hover:text-foreground [&_a]:underline [&_a]:underline-offset-3",
|
||||||
className
|
className
|
||||||
)}
|
)}
|
||||||
{...restProps}
|
{...restProps}
|
||||||
|
|||||||
@@ -13,7 +13,10 @@
|
|||||||
<div
|
<div
|
||||||
bind:this={ref}
|
bind:this={ref}
|
||||||
data-slot="alert-title"
|
data-slot="alert-title"
|
||||||
class={cn("col-start-2 line-clamp-1 min-h-4 font-medium tracking-tight", className)}
|
class={cn(
|
||||||
|
"font-medium group-has-[>svg]/alert:col-start-2 [&_a]:hover:text-foreground [&_a]:underline [&_a]:underline-offset-3",
|
||||||
|
className
|
||||||
|
)}
|
||||||
{...restProps}
|
{...restProps}
|
||||||
>
|
>
|
||||||
{@render children?.()}
|
{@render children?.()}
|
||||||
|
|||||||
@@ -2,12 +2,11 @@
|
|||||||
import { type VariantProps, tv } from "tailwind-variants";
|
import { type VariantProps, tv } from "tailwind-variants";
|
||||||
|
|
||||||
export const alertVariants = tv({
|
export const alertVariants = tv({
|
||||||
base: "relative grid w-full grid-cols-[0_1fr] items-start gap-y-0.5 rounded-lg border px-4 py-3 text-sm has-[>svg]:grid-cols-[calc(var(--spacing)*4)_1fr] has-[>svg]:gap-x-3 [&>svg]:size-4 [&>svg]:translate-y-0.5 [&>svg]:text-current",
|
base: "grid gap-0.5 rounded-lg border px-2.5 py-2 text-left text-sm has-data-[slot=alert-action]:relative has-data-[slot=alert-action]:pr-18 has-[>svg]:grid-cols-[auto_1fr] has-[>svg]:gap-x-2 *:[svg]:row-span-2 *:[svg]:translate-y-0.5 *:[svg]:text-current *:[svg:not([class*='size-'])]:size-4 group/alert relative w-full",
|
||||||
variants: {
|
variants: {
|
||||||
variant: {
|
variant: {
|
||||||
default: "bg-card text-card-foreground",
|
default: "bg-card text-card-foreground",
|
||||||
destructive:
|
destructive: "text-destructive bg-card *:data-[slot=alert-description]:text-destructive/90 *:[svg]:text-current",
|
||||||
"text-destructive bg-card *:data-[slot=alert-description]:text-destructive/90 [&>svg]:text-current",
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
defaultVariants: {
|
defaultVariants: {
|
||||||
@@ -36,9 +35,9 @@
|
|||||||
<div
|
<div
|
||||||
bind:this={ref}
|
bind:this={ref}
|
||||||
data-slot="alert"
|
data-slot="alert"
|
||||||
|
role="alert"
|
||||||
class={cn(alertVariants({ variant }), className)}
|
class={cn(alertVariants({ variant }), className)}
|
||||||
{...restProps}
|
{...restProps}
|
||||||
role="alert"
|
|
||||||
>
|
>
|
||||||
{@render children?.()}
|
{@render children?.()}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,14 +1,17 @@
|
|||||||
import Root from "./alert.svelte";
|
import Root from "./alert.svelte";
|
||||||
import Description from "./alert-description.svelte";
|
import Description from "./alert-description.svelte";
|
||||||
import Title from "./alert-title.svelte";
|
import Title from "./alert-title.svelte";
|
||||||
|
import Action from "./alert-action.svelte";
|
||||||
export { alertVariants, type AlertVariant } from "./alert.svelte";
|
export { alertVariants, type AlertVariant } from "./alert.svelte";
|
||||||
|
|
||||||
export {
|
export {
|
||||||
Root,
|
Root,
|
||||||
Description,
|
Description,
|
||||||
Title,
|
Title,
|
||||||
|
Action,
|
||||||
//
|
//
|
||||||
Root as Alert,
|
Root as Alert,
|
||||||
Description as AlertDescription,
|
Description as AlertDescription,
|
||||||
Title as AlertTitle,
|
Title as AlertTitle,
|
||||||
|
Action as AlertAction,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -4,25 +4,25 @@
|
|||||||
import { type VariantProps, tv } from "tailwind-variants";
|
import { type VariantProps, tv } from "tailwind-variants";
|
||||||
|
|
||||||
export const buttonVariants = tv({
|
export const buttonVariants = tv({
|
||||||
base: "focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive inline-flex shrink-0 items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium outline-none transition-all focus-visible:ring-[3px] disabled:pointer-events-none disabled:opacity-50 aria-disabled:pointer-events-none aria-disabled:opacity-50 [&_svg:not([class*='size-'])]:size-4 [&_svg]:pointer-events-none [&_svg]:shrink-0",
|
base: "focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive dark:aria-invalid:border-destructive/50 rounded-lg border border-transparent bg-clip-padding text-sm font-medium focus-visible:ring-3 active:not-aria-[haspopup]:translate-y-px aria-invalid:ring-3 [&_svg:not([class*='size-'])]:size-4 group/button inline-flex shrink-0 items-center justify-center whitespace-nowrap transition-all outline-none select-none disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0",
|
||||||
variants: {
|
variants: {
|
||||||
variant: {
|
variant: {
|
||||||
default: "bg-primary text-primary-foreground shadow-xs hover:bg-primary/90",
|
default: "bg-primary text-primary-foreground [a]:hover:bg-primary/80",
|
||||||
destructive:
|
outline: "border-border bg-background hover:bg-muted hover:text-foreground dark:bg-input/30 dark:border-input dark:hover:bg-input/50 aria-expanded:bg-muted aria-expanded:text-foreground",
|
||||||
"bg-destructive shadow-xs hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60 text-white",
|
secondary: "bg-secondary text-secondary-foreground hover:bg-secondary/80 aria-expanded:bg-secondary aria-expanded:text-secondary-foreground",
|
||||||
outline:
|
ghost: "hover:bg-muted hover:text-foreground dark:hover:bg-muted/50 aria-expanded:bg-muted aria-expanded:text-foreground",
|
||||||
"bg-background shadow-xs hover:bg-accent hover:text-accent-foreground dark:bg-input/30 dark:border-input dark:hover:bg-input/50 border",
|
destructive: "bg-destructive/10 hover:bg-destructive/20 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/20 text-destructive focus-visible:border-destructive/40 dark:hover:bg-destructive/30",
|
||||||
secondary: "bg-secondary text-secondary-foreground shadow-xs hover:bg-secondary/80",
|
|
||||||
ghost: "hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50",
|
|
||||||
link: "text-primary underline-offset-4 hover:underline",
|
link: "text-primary underline-offset-4 hover:underline",
|
||||||
},
|
},
|
||||||
size: {
|
size: {
|
||||||
default: "h-9 px-4 py-2 has-[>svg]:px-3",
|
default: "h-8 gap-1.5 px-2.5 has-data-[icon=inline-end]:pr-2 has-data-[icon=inline-start]:pl-2",
|
||||||
sm: "h-8 gap-1.5 rounded-md px-3 has-[>svg]:px-2.5",
|
xs: "h-6 gap-1 rounded-[min(var(--radius-md),10px)] px-2 text-xs in-data-[slot=button-group]:rounded-lg has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 [&_svg:not([class*='size-'])]:size-3",
|
||||||
lg: "h-10 rounded-md px-6 has-[>svg]:px-4",
|
sm: "h-7 gap-1 rounded-[min(var(--radius-md),12px)] px-2.5 text-[0.8rem] in-data-[slot=button-group]:rounded-lg has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 [&_svg:not([class*='size-'])]:size-3.5",
|
||||||
icon: "size-9",
|
lg: "h-9 gap-1.5 px-2.5 has-data-[icon=inline-end]:pr-2 has-data-[icon=inline-start]:pl-2",
|
||||||
"icon-sm": "size-8",
|
icon: "size-8",
|
||||||
"icon-lg": "size-10",
|
"icon-xs": "size-6 rounded-[min(var(--radius-md),10px)] in-data-[slot=button-group]:rounded-lg [&_svg:not([class*='size-'])]:size-3",
|
||||||
|
"icon-sm": "size-7 rounded-[min(var(--radius-md),12px)] in-data-[slot=button-group]:rounded-lg",
|
||||||
|
"icon-lg": "size-9",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
defaultVariants: {
|
defaultVariants: {
|
||||||
|
|||||||
@@ -12,7 +12,7 @@
|
|||||||
<CalendarPrimitive.Cell
|
<CalendarPrimitive.Cell
|
||||||
bind:ref
|
bind:ref
|
||||||
class={cn(
|
class={cn(
|
||||||
"size-(--cell-size) relative p-0 text-center text-sm focus-within:z-20 [&:first-child[data-selected]_[data-bits-day]]:rounded-l-md [&:last-child[data-selected]_[data-bits-day]]:rounded-r-md",
|
"relative size-(--cell-size) p-0 text-center text-sm focus-within:z-20 [&:first-child[data-selected]_[data-bits-day]]:rounded-s-(--cell-radius) [&:last-child[data-selected]_[data-bits-day]]:rounded-e-(--cell-radius)",
|
||||||
className
|
className
|
||||||
)}
|
)}
|
||||||
{...restProps}
|
{...restProps}
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { buttonVariants } from "$lib/components/ui/button/index.js";
|
|
||||||
import { cn } from "$lib/utils.js";
|
import { cn } from "$lib/utils.js";
|
||||||
import { Calendar as CalendarPrimitive } from "bits-ui";
|
import { Calendar as CalendarPrimitive } from "bits-ui";
|
||||||
|
|
||||||
@@ -13,18 +12,17 @@
|
|||||||
<CalendarPrimitive.Day
|
<CalendarPrimitive.Day
|
||||||
bind:ref
|
bind:ref
|
||||||
class={cn(
|
class={cn(
|
||||||
buttonVariants({ variant: "ghost" }),
|
"flex size-(--cell-size) flex-col items-center justify-center gap-1 rounded-(--cell-radius) p-0 leading-none font-normal whitespace-nowrap select-none",
|
||||||
"size-(--cell-size) flex select-none flex-col items-center justify-center gap-1 whitespace-nowrap p-0 font-normal leading-none",
|
"[&:last-child[data-selected=true]_button]:rounded-r-(--cell-radius)",
|
||||||
|
"not-data-selected:hover:bg-accent/50 not-data-selected:hover:text-accent-foreground",
|
||||||
"[&[data-today]:not([data-selected])]:bg-accent [&[data-today]:not([data-selected])]:text-accent-foreground [&[data-today][data-disabled]]:text-muted-foreground",
|
"[&[data-today]:not([data-selected])]:bg-accent [&[data-today]:not([data-selected])]:text-accent-foreground [&[data-today][data-disabled]]:text-muted-foreground",
|
||||||
"data-[selected]:bg-primary dark:data-[selected]:hover:bg-accent/50 data-[selected]:text-primary-foreground",
|
"data-[selected]:bg-primary data-[selected]:text-primary-foreground data-[selected]:hover:text-foreground",
|
||||||
// Outside months
|
// Outside months
|
||||||
"[&[data-outside-month]:not([data-selected])]:text-muted-foreground [&[data-outside-month]:not([data-selected])]:hover:text-accent-foreground",
|
"[&[data-outside-month]:not([data-selected])]:text-muted-foreground [&[data-outside-month]:not([data-selected])]:hover:text-accent-foreground",
|
||||||
// Disabled
|
// Disabled
|
||||||
"data-[disabled]:text-muted-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
|
"data-[disabled]:text-muted-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
|
||||||
// Unavailable
|
// Unavailable
|
||||||
"data-[unavailable]:text-muted-foreground data-[unavailable]:line-through",
|
"data-[unavailable]:text-muted-foreground data-[unavailable]:line-through",
|
||||||
// hover
|
|
||||||
"dark:hover:text-accent-foreground",
|
|
||||||
// focus
|
// focus
|
||||||
"focus:border-ring focus:ring-ring/50 focus:relative",
|
"focus:border-ring focus:ring-ring/50 focus:relative",
|
||||||
// inner spans
|
// inner spans
|
||||||
|
|||||||
@@ -11,6 +11,6 @@
|
|||||||
|
|
||||||
<CalendarPrimitive.Grid
|
<CalendarPrimitive.Grid
|
||||||
bind:ref
|
bind:ref
|
||||||
class={cn("mt-4 flex w-full border-collapse flex-col gap-1", className)}
|
class={cn("flex w-full border-collapse flex-col", className)}
|
||||||
{...restProps}
|
{...restProps}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -12,7 +12,7 @@
|
|||||||
<CalendarPrimitive.Header
|
<CalendarPrimitive.Header
|
||||||
bind:ref
|
bind:ref
|
||||||
class={cn(
|
class={cn(
|
||||||
"h-(--cell-size) flex w-full items-center justify-center gap-1.5 text-sm font-medium",
|
"flex h-(--cell-size) w-full items-center justify-center gap-1.5 text-sm font-medium",
|
||||||
className
|
className
|
||||||
)}
|
)}
|
||||||
{...restProps}
|
{...restProps}
|
||||||
|
|||||||
@@ -14,11 +14,15 @@
|
|||||||
|
|
||||||
<span
|
<span
|
||||||
class={cn(
|
class={cn(
|
||||||
"has-focus:border-ring border-input shadow-xs has-focus:ring-ring/50 has-focus:ring-[3px] relative flex rounded-md border",
|
"has-focus:border-ring border-input has-focus:ring-ring/50 relative flex rounded-md border shadow-xs has-focus:ring-[3px]",
|
||||||
className
|
className
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<CalendarPrimitive.MonthSelect bind:ref class="absolute inset-0 opacity-0" {...restProps}>
|
<CalendarPrimitive.MonthSelect
|
||||||
|
bind:ref
|
||||||
|
class="bg-background dark:bg-popover dark:text-popover-foreground absolute inset-0 opacity-0"
|
||||||
|
{...restProps}
|
||||||
|
>
|
||||||
{#snippet child({ props, monthItems, selectedMonthItem })}
|
{#snippet child({ props, monthItems, selectedMonthItem })}
|
||||||
<select {...props} {value} {onchange}>
|
<select {...props} {value} {onchange}>
|
||||||
{#each monthItems as monthItem (monthItem.value)}
|
{#each monthItems as monthItem (monthItem.value)}
|
||||||
@@ -33,7 +37,7 @@
|
|||||||
{/each}
|
{/each}
|
||||||
</select>
|
</select>
|
||||||
<span
|
<span
|
||||||
class="[&>svg]:text-muted-foreground flex h-8 select-none items-center gap-1 rounded-md pl-2 pr-1 text-sm font-medium [&>svg]:size-3.5"
|
class="[&>svg]:text-muted-foreground flex h-(--cell-size) items-center gap-1 rounded-md ps-2 pe-1 text-sm font-medium select-none [&>svg]:size-3.5"
|
||||||
aria-hidden="true"
|
aria-hidden="true"
|
||||||
>
|
>
|
||||||
{monthItems.find((item) => item.value === value)?.label || selectedMonthItem.label}
|
{monthItems.find((item) => item.value === value)?.label || selectedMonthItem.label}
|
||||||
|
|||||||
@@ -10,6 +10,6 @@
|
|||||||
}: WithElementRef<HTMLAttributes<HTMLElement>> = $props();
|
}: WithElementRef<HTMLAttributes<HTMLElement>> = $props();
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div {...restProps} bind:this={ref} class={cn("flex flex-col", className)}>
|
<div {...restProps} bind:this={ref} class={cn("flex w-full flex-col gap-4", className)}>
|
||||||
{@render children?.()}
|
{@render children?.()}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -23,9 +23,14 @@
|
|||||||
bind:ref
|
bind:ref
|
||||||
class={cn(
|
class={cn(
|
||||||
buttonVariants({ variant }),
|
buttonVariants({ variant }),
|
||||||
"size-(--cell-size) select-none bg-transparent p-0 disabled:opacity-50 rtl:rotate-180",
|
"size-(--cell-size) bg-transparent p-0 select-none disabled:opacity-50 rtl:rotate-180",
|
||||||
className
|
className
|
||||||
)}
|
)}
|
||||||
children={children || Fallback}
|
|
||||||
{...restProps}
|
{...restProps}
|
||||||
/>
|
>
|
||||||
|
{#if children}
|
||||||
|
{@render children?.()}
|
||||||
|
{:else}
|
||||||
|
{@render Fallback()}
|
||||||
|
{/if}
|
||||||
|
</CalendarPrimitive.NextButton>
|
||||||
|
|||||||
@@ -23,9 +23,14 @@
|
|||||||
bind:ref
|
bind:ref
|
||||||
class={cn(
|
class={cn(
|
||||||
buttonVariants({ variant }),
|
buttonVariants({ variant }),
|
||||||
"size-(--cell-size) select-none bg-transparent p-0 disabled:opacity-50 rtl:rotate-180",
|
"size-(--cell-size) bg-transparent p-0 select-none disabled:opacity-50 rtl:rotate-180",
|
||||||
className
|
className
|
||||||
)}
|
)}
|
||||||
children={children || Fallback}
|
|
||||||
{...restProps}
|
{...restProps}
|
||||||
/>
|
>
|
||||||
|
{#if children}
|
||||||
|
{@render children?.()}
|
||||||
|
{:else}
|
||||||
|
{@render Fallback()}
|
||||||
|
{/if}
|
||||||
|
</CalendarPrimitive.PrevButton>
|
||||||
|
|||||||
@@ -13,11 +13,15 @@
|
|||||||
|
|
||||||
<span
|
<span
|
||||||
class={cn(
|
class={cn(
|
||||||
"has-focus:border-ring border-input shadow-xs has-focus:ring-ring/50 has-focus:ring-[3px] relative flex rounded-md border",
|
"has-focus:border-ring border-input has-focus:ring-ring/50 relative flex rounded-md border shadow-xs has-focus:ring-[3px]",
|
||||||
className
|
className
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<CalendarPrimitive.YearSelect bind:ref class="absolute inset-0 opacity-0" {...restProps}>
|
<CalendarPrimitive.YearSelect
|
||||||
|
bind:ref
|
||||||
|
class="dark:bg-popover dark:text-popover-foreground absolute inset-0 opacity-0"
|
||||||
|
{...restProps}
|
||||||
|
>
|
||||||
{#snippet child({ props, yearItems, selectedYearItem })}
|
{#snippet child({ props, yearItems, selectedYearItem })}
|
||||||
<select {...props} {value}>
|
<select {...props} {value}>
|
||||||
{#each yearItems as yearItem (yearItem.value)}
|
{#each yearItems as yearItem (yearItem.value)}
|
||||||
@@ -32,7 +36,7 @@
|
|||||||
{/each}
|
{/each}
|
||||||
</select>
|
</select>
|
||||||
<span
|
<span
|
||||||
class="[&>svg]:text-muted-foreground flex h-8 select-none items-center gap-1 rounded-md pl-2 pr-1 text-sm font-medium [&>svg]:size-3.5"
|
class="[&>svg]:text-muted-foreground flex h-(--cell-size) items-center gap-1 rounded-md ps-2 pe-1 text-sm font-medium select-none [&>svg]:size-3.5"
|
||||||
aria-hidden="true"
|
aria-hidden="true"
|
||||||
>
|
>
|
||||||
{yearItems.find((item) => item.value === value)?.label || selectedYearItem.label}
|
{yearItems.find((item) => item.value === value)?.label || selectedYearItem.label}
|
||||||
|
|||||||
@@ -50,7 +50,7 @@ get along, so we shut typescript up by casting `value` to `never`.
|
|||||||
{weekdayFormat}
|
{weekdayFormat}
|
||||||
{disableDaysOutsideMonth}
|
{disableDaysOutsideMonth}
|
||||||
class={cn(
|
class={cn(
|
||||||
"bg-background group/calendar p-3 [--cell-size:--spacing(8)] [[data-slot=card-content]_&]:bg-transparent [[data-slot=popover-content]_&]:bg-transparent",
|
"p-2 [--cell-radius:var(--radius-md)] [--cell-size:--spacing(7)] bg-background group/calendar in-data-[slot=card-content]:bg-transparent in-data-[slot=popover-content]:bg-transparent",
|
||||||
className
|
className
|
||||||
)}
|
)}
|
||||||
{locale}
|
{locale}
|
||||||
|
|||||||
@@ -13,7 +13,10 @@
|
|||||||
<div
|
<div
|
||||||
bind:this={ref}
|
bind:this={ref}
|
||||||
data-slot="card-action"
|
data-slot="card-action"
|
||||||
class={cn("col-start-2 row-span-2 row-start-1 self-start justify-self-end", className)}
|
class={cn(
|
||||||
|
"cn-card-action col-start-2 row-span-2 row-start-1 self-start justify-self-end",
|
||||||
|
className
|
||||||
|
)}
|
||||||
{...restProps}
|
{...restProps}
|
||||||
>
|
>
|
||||||
{@render children?.()}
|
{@render children?.()}
|
||||||
|
|||||||
@@ -10,6 +10,11 @@
|
|||||||
}: WithElementRef<HTMLAttributes<HTMLDivElement>> = $props();
|
}: WithElementRef<HTMLAttributes<HTMLDivElement>> = $props();
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div bind:this={ref} data-slot="card-content" class={cn("px-6", className)} {...restProps}>
|
<div
|
||||||
|
bind:this={ref}
|
||||||
|
data-slot="card-content"
|
||||||
|
class={cn("px-4 group-data-[size=sm]/card:px-3", className)}
|
||||||
|
{...restProps}
|
||||||
|
>
|
||||||
{@render children?.()}
|
{@render children?.()}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -13,7 +13,7 @@
|
|||||||
<div
|
<div
|
||||||
bind:this={ref}
|
bind:this={ref}
|
||||||
data-slot="card-footer"
|
data-slot="card-footer"
|
||||||
class={cn("[.border-t]:pt-6 flex items-center px-6", className)}
|
class={cn("bg-muted/50 rounded-b-xl border-t p-4 group-data-[size=sm]/card:p-3 flex items-center", className)}
|
||||||
{...restProps}
|
{...restProps}
|
||||||
>
|
>
|
||||||
{@render children?.()}
|
{@render children?.()}
|
||||||
|
|||||||
@@ -14,7 +14,7 @@
|
|||||||
bind:this={ref}
|
bind:this={ref}
|
||||||
data-slot="card-header"
|
data-slot="card-header"
|
||||||
class={cn(
|
class={cn(
|
||||||
"@container/card-header has-data-[slot=card-action]:grid-cols-[1fr_auto] [.border-b]:pb-6 grid auto-rows-min grid-rows-[auto_auto] items-start gap-1.5 px-6",
|
"gap-1 rounded-t-xl px-4 group-data-[size=sm]/card:px-3 [.border-b]:pb-4 group-data-[size=sm]/card:[.border-b]:pb-3 group/card-header @container/card-header grid auto-rows-min items-start has-data-[slot=card-action]:grid-cols-[1fr_auto] has-data-[slot=card-description]:grid-rows-[auto_auto]",
|
||||||
className
|
className
|
||||||
)}
|
)}
|
||||||
{...restProps}
|
{...restProps}
|
||||||
|
|||||||
@@ -13,7 +13,7 @@
|
|||||||
<div
|
<div
|
||||||
bind:this={ref}
|
bind:this={ref}
|
||||||
data-slot="card-title"
|
data-slot="card-title"
|
||||||
class={cn("font-semibold leading-none", className)}
|
class={cn("text-base leading-snug font-medium group-data-[size=sm]/card:text-sm", className)}
|
||||||
{...restProps}
|
{...restProps}
|
||||||
>
|
>
|
||||||
{@render children?.()}
|
{@render children?.()}
|
||||||
|
|||||||
@@ -6,17 +6,16 @@
|
|||||||
ref = $bindable(null),
|
ref = $bindable(null),
|
||||||
class: className,
|
class: className,
|
||||||
children,
|
children,
|
||||||
|
size = "default",
|
||||||
...restProps
|
...restProps
|
||||||
}: WithElementRef<HTMLAttributes<HTMLDivElement>> = $props();
|
}: WithElementRef<HTMLAttributes<HTMLDivElement>> & { size?: "default" | "sm" } = $props();
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
bind:this={ref}
|
bind:this={ref}
|
||||||
data-slot="card"
|
data-slot="card"
|
||||||
class={cn(
|
data-size={size}
|
||||||
"bg-card text-card-foreground flex flex-col gap-6 rounded-xl border py-6 shadow-sm",
|
class={cn("ring-foreground/10 bg-card text-card-foreground gap-4 overflow-hidden rounded-xl py-4 text-sm ring-1 has-data-[slot=card-footer]:pb-0 has-[>img:first-child]:pt-0 data-[size=sm]:gap-3 data-[size=sm]:py-3 data-[size=sm]:has-data-[slot=card-footer]:pb-0 *:[img:first-child]:rounded-t-xl *:[img:last-child]:rounded-b-xl group/card flex flex-col", className)}
|
||||||
className
|
|
||||||
)}
|
|
||||||
{...restProps}
|
{...restProps}
|
||||||
>
|
>
|
||||||
{@render children?.()}
|
{@render children?.()}
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { Checkbox as CheckboxPrimitive } from "bits-ui";
|
import { Checkbox as CheckboxPrimitive } from "bits-ui";
|
||||||
import CheckIcon from "@lucide/svelte/icons/check";
|
|
||||||
import MinusIcon from "@lucide/svelte/icons/minus";
|
|
||||||
import { cn, type WithoutChildrenOrChild } from "$lib/utils.js";
|
import { cn, type WithoutChildrenOrChild } from "$lib/utils.js";
|
||||||
|
import CheckIcon from '@lucide/svelte/icons/check';
|
||||||
|
import MinusIcon from '@lucide/svelte/icons/minus';
|
||||||
|
|
||||||
let {
|
let {
|
||||||
ref = $bindable(null),
|
ref = $bindable(null),
|
||||||
@@ -17,7 +17,7 @@
|
|||||||
bind:ref
|
bind:ref
|
||||||
data-slot="checkbox"
|
data-slot="checkbox"
|
||||||
class={cn(
|
class={cn(
|
||||||
"border-input dark:bg-input/30 data-[state=checked]:bg-primary data-[state=checked]:text-primary-foreground dark:data-[state=checked]:bg-primary data-[state=checked]:border-primary focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive shadow-xs peer flex size-4 shrink-0 items-center justify-center rounded-[4px] border outline-none transition-shadow focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50",
|
"border-input dark:bg-input/30 data-checked:bg-primary data-checked:text-primary-foreground dark:data-checked:bg-primary data-checked:border-primary aria-invalid:aria-checked:border-primary aria-invalid:border-destructive dark:aria-invalid:border-destructive/50 focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 flex size-4 items-center justify-center rounded-[4px] border transition-colors group-has-disabled/field:opacity-50 focus-visible:ring-3 aria-invalid:ring-3 peer relative shrink-0 outline-none after:absolute after:-inset-x-3 after:-inset-y-2 disabled:cursor-not-allowed disabled:opacity-50",
|
||||||
className
|
className
|
||||||
)}
|
)}
|
||||||
bind:checked
|
bind:checked
|
||||||
@@ -25,11 +25,14 @@
|
|||||||
{...restProps}
|
{...restProps}
|
||||||
>
|
>
|
||||||
{#snippet children({ checked, indeterminate })}
|
{#snippet children({ checked, indeterminate })}
|
||||||
<div data-slot="checkbox-indicator" class="text-current transition-none">
|
<div
|
||||||
|
data-slot="checkbox-indicator"
|
||||||
|
class="[&>svg]:size-3.5 grid place-content-center text-current transition-none"
|
||||||
|
>
|
||||||
{#if checked}
|
{#if checked}
|
||||||
<CheckIcon class="size-3.5" />
|
<CheckIcon />
|
||||||
{:else if indeterminate}
|
{:else if indeterminate}
|
||||||
<MinusIcon class="size-3.5" />
|
<MinusIcon />
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
{/snippet}
|
{/snippet}
|
||||||
|
|||||||
@@ -1,17 +1,19 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { ContextMenu as ContextMenuPrimitive } from "bits-ui";
|
import { ContextMenu as ContextMenuPrimitive } from "bits-ui";
|
||||||
import CheckIcon from "@lucide/svelte/icons/check";
|
|
||||||
import { cn, type WithoutChildrenOrChild } from "$lib/utils.js";
|
import { cn, type WithoutChildrenOrChild } from "$lib/utils.js";
|
||||||
import type { Snippet } from "svelte";
|
import type { Snippet } from "svelte";
|
||||||
|
import CheckIcon from '@lucide/svelte/icons/check';
|
||||||
|
|
||||||
let {
|
let {
|
||||||
ref = $bindable(null),
|
ref = $bindable(null),
|
||||||
checked = $bindable(false),
|
checked = $bindable(false),
|
||||||
indeterminate = $bindable(false),
|
indeterminate = $bindable(false),
|
||||||
class: className,
|
class: className,
|
||||||
|
inset,
|
||||||
children: childrenProp,
|
children: childrenProp,
|
||||||
...restProps
|
...restProps
|
||||||
}: WithoutChildrenOrChild<ContextMenuPrimitive.CheckboxItemProps> & {
|
}: WithoutChildrenOrChild<ContextMenuPrimitive.CheckboxItemProps> & {
|
||||||
|
inset?: boolean;
|
||||||
children?: Snippet;
|
children?: Snippet;
|
||||||
} = $props();
|
} = $props();
|
||||||
</script>
|
</script>
|
||||||
@@ -21,16 +23,17 @@
|
|||||||
bind:checked
|
bind:checked
|
||||||
bind:indeterminate
|
bind:indeterminate
|
||||||
data-slot="context-menu-checkbox-item"
|
data-slot="context-menu-checkbox-item"
|
||||||
|
data-inset={inset}
|
||||||
class={cn(
|
class={cn(
|
||||||
"data-highlighted:bg-accent data-highlighted:text-accent-foreground outline-hidden relative flex cursor-default select-none items-center gap-2 rounded-sm py-1.5 pl-8 pr-2 text-sm data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg:not([class*='size-'])]:size-4 [&_svg]:pointer-events-none [&_svg]:shrink-0",
|
"focus:bg-accent focus:text-accent-foreground gap-1.5 rounded-md py-1 pr-8 pl-1.5 text-sm data-inset:pl-7 [&_svg:not([class*='size-'])]:size-4 relative flex cursor-default items-center outline-hidden select-none data-disabled:pointer-events-none data-disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0",
|
||||||
className
|
className
|
||||||
)}
|
)}
|
||||||
{...restProps}
|
{...restProps}
|
||||||
>
|
>
|
||||||
{#snippet children({ checked })}
|
{#snippet children({ checked })}
|
||||||
<span class="pointer-events-none absolute left-2 flex size-3.5 items-center justify-center">
|
<span class="absolute right-2 pointer-events-none">
|
||||||
{#if checked}
|
{#if checked}
|
||||||
<CheckIcon class="size-4" />
|
<CheckIcon />
|
||||||
{/if}
|
{/if}
|
||||||
</span>
|
</span>
|
||||||
{@render childrenProp?.()}
|
{@render childrenProp?.()}
|
||||||
|
|||||||
@@ -1,6 +1,9 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { ContextMenu as ContextMenuPrimitive } from "bits-ui";
|
import { ContextMenu as ContextMenuPrimitive } from "bits-ui";
|
||||||
import { cn } from "$lib/utils.js";
|
import { cn } from "$lib/utils.js";
|
||||||
|
import ContextMenuPortal from "./context-menu-portal.svelte";
|
||||||
|
import type { ComponentProps } from "svelte";
|
||||||
|
import type { WithoutChildrenOrChild } from "$lib/utils.js";
|
||||||
|
|
||||||
let {
|
let {
|
||||||
ref = $bindable(null),
|
ref = $bindable(null),
|
||||||
@@ -8,18 +11,18 @@
|
|||||||
class: className,
|
class: className,
|
||||||
...restProps
|
...restProps
|
||||||
}: ContextMenuPrimitive.ContentProps & {
|
}: ContextMenuPrimitive.ContentProps & {
|
||||||
portalProps?: ContextMenuPrimitive.PortalProps;
|
portalProps?: WithoutChildrenOrChild<ComponentProps<typeof ContextMenuPortal>>;
|
||||||
} = $props();
|
} = $props();
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<ContextMenuPrimitive.Portal {...portalProps}>
|
<ContextMenuPortal {...portalProps}>
|
||||||
<ContextMenuPrimitive.Content
|
<ContextMenuPrimitive.Content
|
||||||
bind:ref
|
bind:ref
|
||||||
data-slot="context-menu-content"
|
data-slot="context-menu-content"
|
||||||
class={cn(
|
class={cn(
|
||||||
"bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 max-h-(--bits-context-menu-content-available-height) origin-(--bits-context-menu-content-transform-origin) z-50 min-w-[8rem] overflow-y-auto overflow-x-hidden rounded-md border p-1 shadow-md",
|
"data-open:animate-in data-closed:animate-out data-closed:fade-out-0 data-open:fade-in-0 data-closed:zoom-out-95 data-open:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 ring-foreground/10 bg-popover text-popover-foreground min-w-36 rounded-lg p-1 shadow-md ring-1 duration-100 z-50 overflow-x-hidden overflow-y-auto outline-none",
|
||||||
className
|
className
|
||||||
)}
|
)}
|
||||||
{...restProps}
|
{...restProps}
|
||||||
/>
|
/>
|
||||||
</ContextMenuPrimitive.Portal>
|
</ContextMenuPortal>
|
||||||
|
|||||||
@@ -16,6 +16,6 @@
|
|||||||
bind:ref
|
bind:ref
|
||||||
data-slot="context-menu-group-heading"
|
data-slot="context-menu-group-heading"
|
||||||
data-inset={inset}
|
data-inset={inset}
|
||||||
class={cn("text-foreground px-2 py-1.5 text-sm font-medium data-[inset]:pl-8", className)}
|
class={cn("text-foreground px-2 py-1.5 text-sm font-medium data-inset:ps-8", className)}
|
||||||
{...restProps}
|
{...restProps}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -20,7 +20,7 @@
|
|||||||
data-inset={inset}
|
data-inset={inset}
|
||||||
data-variant={variant}
|
data-variant={variant}
|
||||||
class={cn(
|
class={cn(
|
||||||
"data-highlighted:bg-accent data-highlighted:text-accent-foreground data-[variant=destructive]:text-destructive data-[variant=destructive]:data-highlighted:bg-destructive/10 dark:data-[variant=destructive]:data-highlighted:bg-destructive/20 data-[variant=destructive]:data-highlighted:text-destructive data-[variant=destructive]:*:[svg]:!text-destructive [&_svg:not([class*='text-'])]:text-muted-foreground outline-hidden relative flex cursor-default select-none items-center gap-2 rounded-sm px-2 py-1.5 text-sm data-[disabled]:pointer-events-none data-[inset]:pl-8 data-[disabled]:opacity-50 [&_svg:not([class*='size-'])]:size-4 [&_svg]:pointer-events-none [&_svg]:shrink-0",
|
"focus:bg-accent focus:text-accent-foreground data-[variant=destructive]:text-destructive data-[variant=destructive]:focus:bg-destructive/10 dark:data-[variant=destructive]:focus:bg-destructive/20 data-[variant=destructive]:focus:text-destructive data-[variant=destructive]:*:[svg]:text-destructive focus:*:[svg]:text-accent-foreground gap-1.5 rounded-md px-1.5 py-1 text-sm data-inset:pl-7 [&_svg:not([class*='size-'])]:size-4 group/context-menu-item relative flex cursor-default items-center outline-hidden select-none data-disabled:pointer-events-none data-disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0",
|
||||||
className
|
className
|
||||||
)}
|
)}
|
||||||
{...restProps}
|
{...restProps}
|
||||||
|
|||||||
@@ -17,7 +17,7 @@
|
|||||||
bind:this={ref}
|
bind:this={ref}
|
||||||
data-slot="context-menu-label"
|
data-slot="context-menu-label"
|
||||||
data-inset={inset}
|
data-inset={inset}
|
||||||
class={cn("text-foreground px-2 py-1.5 text-sm font-medium data-[inset]:pl-8", className)}
|
class={cn("text-muted-foreground px-1.5 py-1 text-xs font-medium data-inset:pl-7 data-inset:pl-8", className)}
|
||||||
{...restProps}
|
{...restProps}
|
||||||
>
|
>
|
||||||
{@render children?.()}
|
{@render children?.()}
|
||||||
|
|||||||
@@ -0,0 +1,7 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import { ContextMenu as ContextMenuPrimitive } from "bits-ui";
|
||||||
|
|
||||||
|
let { ...restProps }: ContextMenuPrimitive.PortalProps = $props();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<ContextMenuPrimitive.Portal {...restProps} />
|
||||||
@@ -1,29 +1,33 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { ContextMenu as ContextMenuPrimitive } from "bits-ui";
|
import { ContextMenu as ContextMenuPrimitive } from "bits-ui";
|
||||||
import CircleIcon from "@lucide/svelte/icons/circle";
|
|
||||||
import { cn, type WithoutChild } from "$lib/utils.js";
|
import { cn, type WithoutChild } from "$lib/utils.js";
|
||||||
|
import CheckIcon from '@lucide/svelte/icons/check';
|
||||||
|
|
||||||
let {
|
let {
|
||||||
ref = $bindable(null),
|
ref = $bindable(null),
|
||||||
class: className,
|
class: className,
|
||||||
|
inset,
|
||||||
children: childrenProp,
|
children: childrenProp,
|
||||||
...restProps
|
...restProps
|
||||||
}: WithoutChild<ContextMenuPrimitive.RadioItemProps> = $props();
|
}: WithoutChild<ContextMenuPrimitive.RadioItemProps> & {
|
||||||
|
inset?: boolean;
|
||||||
|
} = $props();
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<ContextMenuPrimitive.RadioItem
|
<ContextMenuPrimitive.RadioItem
|
||||||
bind:ref
|
bind:ref
|
||||||
data-slot="context-menu-radio-item"
|
data-slot="context-menu-radio-item"
|
||||||
|
data-inset={inset}
|
||||||
class={cn(
|
class={cn(
|
||||||
"data-highlighted:bg-accent data-highlighted:text-accent-foreground outline-hidden relative flex cursor-default select-none items-center gap-2 rounded-sm py-1.5 pl-8 pr-2 text-sm data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg:not([class*='size-'])]:size-4 [&_svg]:pointer-events-none [&_svg]:shrink-0",
|
"focus:bg-accent focus:text-accent-foreground gap-1.5 rounded-md py-1 pr-8 pl-1.5 text-sm data-inset:pl-7 [&_svg:not([class*='size-'])]:size-4 relative flex cursor-default items-center outline-hidden select-none data-disabled:pointer-events-none data-disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0",
|
||||||
className
|
className
|
||||||
)}
|
)}
|
||||||
{...restProps}
|
{...restProps}
|
||||||
>
|
>
|
||||||
{#snippet children({ checked })}
|
{#snippet children({ checked })}
|
||||||
<span class="pointer-events-none absolute left-2 flex size-3.5 items-center justify-center">
|
<span class="absolute right-2 pointer-events-none">
|
||||||
{#if checked}
|
{#if checked}
|
||||||
<CircleIcon class="size-2 fill-current" />
|
<CheckIcon />
|
||||||
{/if}
|
{/if}
|
||||||
</span>
|
</span>
|
||||||
{@render childrenProp?.({ checked })}
|
{@render childrenProp?.({ checked })}
|
||||||
|
|||||||
@@ -13,7 +13,7 @@
|
|||||||
<span
|
<span
|
||||||
bind:this={ref}
|
bind:this={ref}
|
||||||
data-slot="context-menu-shortcut"
|
data-slot="context-menu-shortcut"
|
||||||
class={cn("text-muted-foreground ml-auto text-xs tracking-widest", className)}
|
class={cn("text-muted-foreground group-focus/context-menu-item:text-accent-foreground ml-auto text-xs tracking-widest", className)}
|
||||||
{...restProps}
|
{...restProps}
|
||||||
>
|
>
|
||||||
{@render children?.()}
|
{@render children?.()}
|
||||||
|
|||||||
@@ -12,9 +12,6 @@
|
|||||||
<ContextMenuPrimitive.SubContent
|
<ContextMenuPrimitive.SubContent
|
||||||
bind:ref
|
bind:ref
|
||||||
data-slot="context-menu-sub-content"
|
data-slot="context-menu-sub-content"
|
||||||
class={cn(
|
class={cn("data-open:animate-in data-closed:animate-out data-closed:fade-out-0 data-open:fade-in-0 data-closed:zoom-out-95 data-open:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 bg-popover text-popover-foreground min-w-32 rounded-lg border p-1 shadow-lg duration-100", className)}
|
||||||
"bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 origin-(--bits-context-menu-content-transform-origin) z-50 min-w-[8rem] overflow-hidden rounded-md border p-1 shadow-lg",
|
|
||||||
className
|
|
||||||
)}
|
|
||||||
{...restProps}
|
{...restProps}
|
||||||
/>
|
/>
|
||||||
|
|||||||