diff --git a/.vscode/tasks.json b/.vscode/tasks.json
new file mode 100644
index 00000000..5f56fc9a
--- /dev/null
+++ b/.vscode/tasks.json
@@ -0,0 +1,16 @@
+{
+ "version": "2.0.0",
+ "tasks": [
+ {
+ "type": "npm",
+ "script": "dev",
+ "path": "web",
+ "problemMatcher": [
+ "$vite"
+ ],
+ "label": "npm: dev - web",
+ "detail": "vite dev",
+ "isBackground": true
+ }
+ ]
+}
\ No newline at end of file
diff --git a/db/pb_migrations/1706529850_updated_trails.js b/db/pb_migrations/1706529850_updated_trails.js
new file mode 100644
index 00000000..9746cbf1
--- /dev/null
+++ b/db/pb_migrations/1706529850_updated_trails.js
@@ -0,0 +1,58 @@
+///
+migrate((db) => {
+ const dao = new Dao(db)
+ const collection = dao.findCollectionByNameOrId("e864strfxo14pm4")
+
+ // remove
+ collection.schema.removeField("jjiidvbm")
+
+ // add
+ collection.schema.addField(new SchemaField({
+ "system": false,
+ "id": "mcqce8l9",
+ "name": "thumbnail",
+ "type": "text",
+ "required": false,
+ "presentable": false,
+ "unique": false,
+ "options": {
+ "min": null,
+ "max": null,
+ "pattern": ""
+ }
+ }))
+
+ return dao.saveCollection(collection)
+}, (db) => {
+ const dao = new Dao(db)
+ const collection = dao.findCollectionByNameOrId("e864strfxo14pm4")
+
+ // add
+ collection.schema.addField(new SchemaField({
+ "system": false,
+ "id": "jjiidvbm",
+ "name": "thumbnail",
+ "type": "file",
+ "required": false,
+ "presentable": false,
+ "unique": false,
+ "options": {
+ "mimeTypes": [
+ "image/png",
+ "image/vnd.mozilla.apng",
+ "image/jpeg",
+ "image/webp",
+ "image/svg+xml"
+ ],
+ "thumbs": [],
+ "maxSelect": 1,
+ "maxSize": 5242880,
+ "protected": false
+ }
+ }))
+
+ // remove
+ collection.schema.removeField("mcqce8l9")
+
+ return dao.saveCollection(collection)
+})
diff --git a/db/pb_migrations/1706533600_updated_waypoints.js b/db/pb_migrations/1706533600_updated_waypoints.js
new file mode 100644
index 00000000..4796117f
--- /dev/null
+++ b/db/pb_migrations/1706533600_updated_waypoints.js
@@ -0,0 +1,76 @@
+///
+migrate((db) => {
+ const dao = new Dao(db)
+ const collection = dao.findCollectionByNameOrId("goeo2ubp103rzp9")
+
+ // update
+ collection.schema.addField(new SchemaField({
+ "system": false,
+ "id": "ygotgxzy",
+ "name": "lat",
+ "type": "number",
+ "required": false,
+ "presentable": false,
+ "unique": false,
+ "options": {
+ "min": null,
+ "max": null,
+ "noDecimal": false
+ }
+ }))
+
+ // update
+ collection.schema.addField(new SchemaField({
+ "system": false,
+ "id": "q0ygnxd2",
+ "name": "lon",
+ "type": "number",
+ "required": false,
+ "presentable": false,
+ "unique": false,
+ "options": {
+ "min": null,
+ "max": null,
+ "noDecimal": false
+ }
+ }))
+
+ return dao.saveCollection(collection)
+}, (db) => {
+ const dao = new Dao(db)
+ const collection = dao.findCollectionByNameOrId("goeo2ubp103rzp9")
+
+ // update
+ collection.schema.addField(new SchemaField({
+ "system": false,
+ "id": "ygotgxzy",
+ "name": "lat",
+ "type": "number",
+ "required": false,
+ "presentable": false,
+ "unique": false,
+ "options": {
+ "min": 0,
+ "max": null,
+ "noDecimal": false
+ }
+ }))
+
+ // update
+ collection.schema.addField(new SchemaField({
+ "system": false,
+ "id": "q0ygnxd2",
+ "name": "lon",
+ "type": "number",
+ "required": false,
+ "presentable": false,
+ "unique": false,
+ "options": {
+ "min": 0,
+ "max": null,
+ "noDecimal": false
+ }
+ }))
+
+ return dao.saveCollection(collection)
+})
diff --git a/db/pb_migrations/1706548284_updated_trails.js b/db/pb_migrations/1706548284_updated_trails.js
new file mode 100644
index 00000000..382cc817
--- /dev/null
+++ b/db/pb_migrations/1706548284_updated_trails.js
@@ -0,0 +1,16 @@
+///
+migrate((db) => {
+ const dao = new Dao(db)
+ const collection = dao.findCollectionByNameOrId("e864strfxo14pm4")
+
+ collection.createRule = ""
+
+ return dao.saveCollection(collection)
+}, (db) => {
+ const dao = new Dao(db)
+ const collection = dao.findCollectionByNameOrId("e864strfxo14pm4")
+
+ collection.createRule = null
+
+ return dao.saveCollection(collection)
+})
diff --git a/db/pb_migrations/1706551417_updated_trails.js b/db/pb_migrations/1706551417_updated_trails.js
new file mode 100644
index 00000000..093cb6b7
--- /dev/null
+++ b/db/pb_migrations/1706551417_updated_trails.js
@@ -0,0 +1,84 @@
+///
+migrate((db) => {
+ const dao = new Dao(db)
+ const collection = dao.findCollectionByNameOrId("e864strfxo14pm4")
+
+ // update
+ collection.schema.addField(new SchemaField({
+ "system": false,
+ "id": "e1lwowvd",
+ "name": "summit_logs",
+ "type": "relation",
+ "required": false,
+ "presentable": false,
+ "unique": false,
+ "options": {
+ "collectionId": "dd2l9a4vxpy2ni8",
+ "cascadeDelete": true,
+ "minSelect": null,
+ "maxSelect": null,
+ "displayFields": null
+ }
+ }))
+
+ // update
+ collection.schema.addField(new SchemaField({
+ "system": false,
+ "id": "ppq2sist",
+ "name": "waypoints",
+ "type": "relation",
+ "required": false,
+ "presentable": false,
+ "unique": false,
+ "options": {
+ "collectionId": "goeo2ubp103rzp9",
+ "cascadeDelete": true,
+ "minSelect": null,
+ "maxSelect": null,
+ "displayFields": null
+ }
+ }))
+
+ return dao.saveCollection(collection)
+}, (db) => {
+ const dao = new Dao(db)
+ const collection = dao.findCollectionByNameOrId("e864strfxo14pm4")
+
+ // update
+ collection.schema.addField(new SchemaField({
+ "system": false,
+ "id": "e1lwowvd",
+ "name": "summit_logs",
+ "type": "relation",
+ "required": false,
+ "presentable": false,
+ "unique": false,
+ "options": {
+ "collectionId": "dd2l9a4vxpy2ni8",
+ "cascadeDelete": false,
+ "minSelect": null,
+ "maxSelect": null,
+ "displayFields": null
+ }
+ }))
+
+ // update
+ collection.schema.addField(new SchemaField({
+ "system": false,
+ "id": "ppq2sist",
+ "name": "waypoints",
+ "type": "relation",
+ "required": false,
+ "presentable": false,
+ "unique": false,
+ "options": {
+ "collectionId": "goeo2ubp103rzp9",
+ "cascadeDelete": false,
+ "minSelect": null,
+ "maxSelect": null,
+ "displayFields": null
+ }
+ }))
+
+ return dao.saveCollection(collection)
+})
diff --git a/db/pb_migrations/1706552026_updated_waypoints.js b/db/pb_migrations/1706552026_updated_waypoints.js
new file mode 100644
index 00000000..21c37336
--- /dev/null
+++ b/db/pb_migrations/1706552026_updated_waypoints.js
@@ -0,0 +1,16 @@
+///
+migrate((db) => {
+ const dao = new Dao(db)
+ const collection = dao.findCollectionByNameOrId("goeo2ubp103rzp9")
+
+ collection.createRule = ""
+
+ return dao.saveCollection(collection)
+}, (db) => {
+ const dao = new Dao(db)
+ const collection = dao.findCollectionByNameOrId("goeo2ubp103rzp9")
+
+ collection.createRule = null
+
+ return dao.saveCollection(collection)
+})
diff --git a/db/pb_migrations/1706552830_updated_trails.js b/db/pb_migrations/1706552830_updated_trails.js
new file mode 100644
index 00000000..97b890aa
--- /dev/null
+++ b/db/pb_migrations/1706552830_updated_trails.js
@@ -0,0 +1,84 @@
+///
+migrate((db) => {
+ const dao = new Dao(db)
+ const collection = dao.findCollectionByNameOrId("e864strfxo14pm4")
+
+ // update
+ collection.schema.addField(new SchemaField({
+ "system": false,
+ "id": "e1lwowvd",
+ "name": "summit_logs",
+ "type": "relation",
+ "required": false,
+ "presentable": false,
+ "unique": false,
+ "options": {
+ "collectionId": "dd2l9a4vxpy2ni8",
+ "cascadeDelete": false,
+ "minSelect": null,
+ "maxSelect": null,
+ "displayFields": null
+ }
+ }))
+
+ // update
+ collection.schema.addField(new SchemaField({
+ "system": false,
+ "id": "ppq2sist",
+ "name": "waypoints",
+ "type": "relation",
+ "required": false,
+ "presentable": false,
+ "unique": false,
+ "options": {
+ "collectionId": "goeo2ubp103rzp9",
+ "cascadeDelete": false,
+ "minSelect": null,
+ "maxSelect": null,
+ "displayFields": null
+ }
+ }))
+
+ return dao.saveCollection(collection)
+}, (db) => {
+ const dao = new Dao(db)
+ const collection = dao.findCollectionByNameOrId("e864strfxo14pm4")
+
+ // update
+ collection.schema.addField(new SchemaField({
+ "system": false,
+ "id": "e1lwowvd",
+ "name": "summit_logs",
+ "type": "relation",
+ "required": false,
+ "presentable": false,
+ "unique": false,
+ "options": {
+ "collectionId": "dd2l9a4vxpy2ni8",
+ "cascadeDelete": true,
+ "minSelect": null,
+ "maxSelect": null,
+ "displayFields": null
+ }
+ }))
+
+ // update
+ collection.schema.addField(new SchemaField({
+ "system": false,
+ "id": "ppq2sist",
+ "name": "waypoints",
+ "type": "relation",
+ "required": false,
+ "presentable": false,
+ "unique": false,
+ "options": {
+ "collectionId": "goeo2ubp103rzp9",
+ "cascadeDelete": true,
+ "minSelect": null,
+ "maxSelect": null,
+ "displayFields": null
+ }
+ }))
+
+ return dao.saveCollection(collection)
+})
diff --git a/db/pb_migrations/1706552880_updated_summit_log.js b/db/pb_migrations/1706552880_updated_summit_log.js
new file mode 100644
index 00000000..24e98f51
--- /dev/null
+++ b/db/pb_migrations/1706552880_updated_summit_log.js
@@ -0,0 +1,16 @@
+///
+migrate((db) => {
+ const dao = new Dao(db)
+ const collection = dao.findCollectionByNameOrId("dd2l9a4vxpy2ni8")
+
+ collection.createRule = ""
+
+ return dao.saveCollection(collection)
+}, (db) => {
+ const dao = new Dao(db)
+ const collection = dao.findCollectionByNameOrId("dd2l9a4vxpy2ni8")
+
+ collection.createRule = null
+
+ return dao.saveCollection(collection)
+})
diff --git a/db/pb_migrations/1706552897_updated_summit_log.js b/db/pb_migrations/1706552897_updated_summit_log.js
new file mode 100644
index 00000000..f3c7e51b
--- /dev/null
+++ b/db/pb_migrations/1706552897_updated_summit_log.js
@@ -0,0 +1,16 @@
+///
+migrate((db) => {
+ const dao = new Dao(db)
+ const collection = dao.findCollectionByNameOrId("dd2l9a4vxpy2ni8")
+
+ collection.name = "summit_logs"
+
+ return dao.saveCollection(collection)
+}, (db) => {
+ const dao = new Dao(db)
+ const collection = dao.findCollectionByNameOrId("dd2l9a4vxpy2ni8")
+
+ collection.name = "summit_log"
+
+ return dao.saveCollection(collection)
+})
diff --git a/db/pb_migrations/1706553833_updated_trails.js b/db/pb_migrations/1706553833_updated_trails.js
new file mode 100644
index 00000000..36368481
--- /dev/null
+++ b/db/pb_migrations/1706553833_updated_trails.js
@@ -0,0 +1,16 @@
+///
+migrate((db) => {
+ const dao = new Dao(db)
+ const collection = dao.findCollectionByNameOrId("e864strfxo14pm4")
+
+ collection.updateRule = ""
+
+ return dao.saveCollection(collection)
+}, (db) => {
+ const dao = new Dao(db)
+ const collection = dao.findCollectionByNameOrId("e864strfxo14pm4")
+
+ collection.updateRule = null
+
+ return dao.saveCollection(collection)
+})
diff --git a/db/pb_migrations/1706557823_updated_trails.js b/db/pb_migrations/1706557823_updated_trails.js
new file mode 100644
index 00000000..cd4ae5f8
--- /dev/null
+++ b/db/pb_migrations/1706557823_updated_trails.js
@@ -0,0 +1,16 @@
+///
+migrate((db) => {
+ const dao = new Dao(db)
+ const collection = dao.findCollectionByNameOrId("e864strfxo14pm4")
+
+ collection.deleteRule = ""
+
+ return dao.saveCollection(collection)
+}, (db) => {
+ const dao = new Dao(db)
+ const collection = dao.findCollectionByNameOrId("e864strfxo14pm4")
+
+ collection.deleteRule = null
+
+ return dao.saveCollection(collection)
+})
diff --git a/db/pb_migrations/1706558119_updated_waypoints.js b/db/pb_migrations/1706558119_updated_waypoints.js
new file mode 100644
index 00000000..2427c307
--- /dev/null
+++ b/db/pb_migrations/1706558119_updated_waypoints.js
@@ -0,0 +1,18 @@
+///
+migrate((db) => {
+ const dao = new Dao(db)
+ const collection = dao.findCollectionByNameOrId("goeo2ubp103rzp9")
+
+ collection.updateRule = ""
+ collection.deleteRule = ""
+
+ return dao.saveCollection(collection)
+}, (db) => {
+ const dao = new Dao(db)
+ const collection = dao.findCollectionByNameOrId("goeo2ubp103rzp9")
+
+ collection.updateRule = null
+ collection.deleteRule = null
+
+ return dao.saveCollection(collection)
+})
diff --git a/db/pb_migrations/1706558125_updated_summit_logs.js b/db/pb_migrations/1706558125_updated_summit_logs.js
new file mode 100644
index 00000000..2d993407
--- /dev/null
+++ b/db/pb_migrations/1706558125_updated_summit_logs.js
@@ -0,0 +1,18 @@
+///
+migrate((db) => {
+ const dao = new Dao(db)
+ const collection = dao.findCollectionByNameOrId("dd2l9a4vxpy2ni8")
+
+ collection.updateRule = ""
+ collection.deleteRule = ""
+
+ return dao.saveCollection(collection)
+}, (db) => {
+ const dao = new Dao(db)
+ const collection = dao.findCollectionByNameOrId("dd2l9a4vxpy2ni8")
+
+ collection.updateRule = null
+ collection.deleteRule = null
+
+ return dao.saveCollection(collection)
+})
diff --git a/web/package-lock.json b/web/package-lock.json
index 35d8b5a6..02e64839 100644
--- a/web/package-lock.json
+++ b/web/package-lock.json
@@ -10,12 +10,13 @@
"dependencies": {
"@fortawesome/fontawesome-free": "^6.5.1",
"@types/leaflet.awesome-markers": "^2.0.28",
+ "crypto-random-string": "^5.0.0",
+ "date-fns": "^3.3.1",
"leaflet": "^1.9.4",
"leaflet-gpx": "^1.7.0",
"leaflet.awesome-markers": "^2.0.5",
"photoswipe": "^5.4.3",
"pocketbase": "^0.21.0",
- "svelte-forms-lib": "^2.0.1",
"yup": "^1.3.3"
},
"devDependencies": {
@@ -1341,6 +1342,20 @@
"node": ">= 8"
}
},
+ "node_modules/crypto-random-string": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/crypto-random-string/-/crypto-random-string-5.0.0.tgz",
+ "integrity": "sha512-KWjTXWwxFd6a94m5CdRGW/t82Tr8DoBc9dNnPCAbFI1EBweN6v1tv8y4Y1m7ndkp/nkIBRxUxAzpaBnR2k3bcQ==",
+ "dependencies": {
+ "type-fest": "^2.12.2"
+ },
+ "engines": {
+ "node": ">=14.16"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
"node_modules/css-tree": {
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/css-tree/-/css-tree-2.3.1.tgz",
@@ -1366,6 +1381,15 @@
"node": ">=4"
}
},
+ "node_modules/date-fns": {
+ "version": "3.3.1",
+ "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-3.3.1.tgz",
+ "integrity": "sha512-y8e109LYGgoQDveiEBD3DYXKba1jWf5BA8YU1FL5Tvm0BTdEfy54WLCwnuYWZNnzzvALy/QQ4Hov+Q9RVRv+Zw==",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/kossnocorp"
+ }
+ },
"node_modules/debug": {
"version": "4.3.4",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
@@ -1408,6 +1432,7 @@
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz",
"integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==",
+ "dev": true,
"engines": {
"node": ">=6"
}
@@ -3131,14 +3156,6 @@
"svelte": "^3.55.0 || ^4.0.0-next.0 || ^4.0.0 || ^5.0.0-next.0"
}
},
- "node_modules/svelte-forms-lib": {
- "version": "2.0.1",
- "resolved": "https://registry.npmjs.org/svelte-forms-lib/-/svelte-forms-lib-2.0.1.tgz",
- "integrity": "sha512-kwbJ3ynsepsrrJyAMrvSc0Lj/myc9vfI2DL8OKxgArZimrNYsRh1gENYhvrcKEI3BiZrv8q3VFfmGo/GMyk7Zg==",
- "dependencies": {
- "dequal": "^2.0.2"
- }
- },
"node_modules/svelte-hmr": {
"version": "0.15.3",
"resolved": "https://registry.npmjs.org/svelte-hmr/-/svelte-hmr-0.15.3.tgz",
diff --git a/web/package.json b/web/package.json
index d5fd10a9..c55f8fd0 100644
--- a/web/package.json
+++ b/web/package.json
@@ -33,12 +33,13 @@
"dependencies": {
"@fortawesome/fontawesome-free": "^6.5.1",
"@types/leaflet.awesome-markers": "^2.0.28",
+ "crypto-random-string": "^5.0.0",
+ "date-fns": "^3.3.1",
"leaflet": "^1.9.4",
"leaflet-gpx": "^1.7.0",
"leaflet.awesome-markers": "^2.0.5",
"photoswipe": "^5.4.3",
"pocketbase": "^0.21.0",
- "svelte-forms-lib": "^2.0.1",
"yup": "^1.3.3"
}
}
diff --git a/web/src/app.d.ts b/web/src/app.d.ts
index 743f07b2..a8fd724c 100644
--- a/web/src/app.d.ts
+++ b/web/src/app.d.ts
@@ -1,3 +1,6 @@
+import PocketBase from 'pocketbase';
+
+
// See https://kit.svelte.dev/docs/types#app
// for information about these interfaces
declare global {
@@ -7,7 +10,10 @@ declare global {
// interface PageData {}
// interface PageState {}
// interface Platform {}
+ interface Locals {
+ pb: PocketBase
+ }
}
}
-export {};
+export { };
diff --git a/web/src/components.css b/web/src/components.css
index fd737969..4fe67d7f 100644
--- a/web/src/components.css
+++ b/web/src/components.css
@@ -3,17 +3,21 @@
@import 'tailwindcss/components';
.btn-primary {
- @apply text-white rounded-lg px-4 py-2 bg-primary font-semibold transition-all hover:bg-gray-900 focus:ring-4 ring-gray-400
+ @apply h-10 text-white rounded-lg px-4 py-2 bg-primary font-semibold transition-all hover:bg-gray-900 focus:ring-4 ring-gray-400
}
.btn-large {
- @apply py-3
+ @apply h-12 leading-8
}
.btn-secondary {
@apply rounded-lg px-4 py-2 border border-primary font-medium transition-all hover:bg-gray-100 focus:ring-4 ring-gray-200
}
+.btn-disabled {
+ @apply bg-gray-500 hover:bg-gray-500
+}
+
.tab {
@apply p-4 rounded-t-lg
}
@@ -28,4 +32,63 @@
.tab-active:hover {
@apply bg-primary
+}
+
+.tooltip {
+ position: relative;
+}
+
+.tooltip:before,
+.tooltip:after {
+ display: block;
+ opacity: 0;
+ pointer-events: none;
+ position: absolute;
+
+ transform: translate3d(0, -10px, 0);
+ transition: all .15s ease-in-out;
+}
+
+.tooltip:before {
+ background: rgba(36, 39, 52, 0.75);
+ border-radius: 4px;
+ color: #fff;
+ content: attr(data-title);
+ font-size: 14px;
+ padding: 6px 10px;
+ top: 24px;
+ white-space: nowrap;
+}
+
+.tooltip:hover:after,
+.tooltip:hover:before {
+ opacity: 1;
+ transform: translate3d(0, 0, 0);
+}
+
+.spinner {
+ display: inline-block;
+ width: 32px;
+ height: 32px;
+}
+
+.spinner:after {
+ content: " ";
+ display: block;
+ width: 32px;
+ height: 32px;
+ border-radius: 50%;
+ border: 6px solid #fff;
+ border-color: #fff transparent #fff transparent;
+ animation: spinner 1.2s linear infinite;
+}
+
+@keyframes spinner {
+ 0% {
+ transform: rotate(0deg);
+ }
+
+ 100% {
+ transform: rotate(360deg);
+ }
}
\ No newline at end of file
diff --git a/web/src/hooks.server.ts b/web/src/hooks.server.ts
new file mode 100644
index 00000000..2d3fd75a
--- /dev/null
+++ b/web/src/hooks.server.ts
@@ -0,0 +1,23 @@
+import PocketBase from 'pocketbase';
+
+export async function handle({ event, resolve }) {
+ event.locals.pb = new PocketBase('http://127.0.0.1:8090');
+
+ // load the store data from the request cookie string
+ event.locals.pb.authStore.loadFromCookie(event.request.headers.get('cookie') || '');
+
+ try {
+ // get an up-to-date auth store state by verifying and refreshing the loaded auth model (if any)
+ event.locals.pb.authStore.isValid && await event.locals.pb.collection('users').authRefresh();
+ } catch (_) {
+ // clear the auth store on failed refresh
+ event.locals.pb.authStore.clear();
+ }
+
+ const response = await resolve(event);
+
+ // send back the default 'pb_auth' cookie to the client with the latest store state
+ response.headers.append('set-cookie', event.locals.pb.authStore.exportToCookie());
+
+ return response;
+}
\ No newline at end of file
diff --git a/web/src/lib/components/base/button.svelte b/web/src/lib/components/base/button.svelte
new file mode 100644
index 00000000..7e1c343d
--- /dev/null
+++ b/web/src/lib/components/base/button.svelte
@@ -0,0 +1,31 @@
+
+
+
diff --git a/web/src/lib/components/base/datepicker.svelte b/web/src/lib/components/base/datepicker.svelte
new file mode 100644
index 00000000..e48ebd13
--- /dev/null
+++ b/web/src/lib/components/base/datepicker.svelte
@@ -0,0 +1,29 @@
+
+
+
+ {#if label.length}
+
+ {label}
+
+ {/if}
+
+ 0}
+ class:bg-red-50={error.length > 0}
+ type="date"
+ bind:value
+ on:change
+ />
+
+
+
+ {error}
+
+
diff --git a/web/src/lib/components/base/dropdown.svelte b/web/src/lib/components/base/dropdown.svelte
index 9abe57ef..5a7040ed 100644
--- a/web/src/lib/components/base/dropdown.svelte
+++ b/web/src/lib/components/base/dropdown.svelte
@@ -7,7 +7,9 @@
let isOpen = false;
- function toggleMenu() {
+ function toggleMenu(e: MouseEvent) {
+ e.stopPropagation();
+ e.preventDefault();
isOpen = !isOpen;
}
@@ -15,8 +17,8 @@
isOpen = false;
}
- function handleItemClick(item: { text: string, value: any }) {
- dispatch("change", item);
+ function handleItemClick(item: { text: string; value: any }) {
+ dispatch("change", item);
closeMenu();
}
@@ -27,6 +29,7 @@
@@ -42,8 +45,8 @@
diff --git a/web/src/lib/components/base/select.svelte b/web/src/lib/components/base/select.svelte
index 720c5692..1f634e3d 100644
--- a/web/src/lib/components/base/select.svelte
+++ b/web/src/lib/components/base/select.svelte
@@ -1,4 +1,5 @@
+
+
+ {#if isThumbnail}
+
+ {/if}
+
+
+
+
+
diff --git a/web/src/lib/components/summit_log/summit_log_card.svelte b/web/src/lib/components/summit_log/summit_log_card.svelte
new file mode 100644
index 00000000..9473d69e
--- /dev/null
+++ b/web/src/lib/components/summit_log/summit_log_card.svelte
@@ -0,0 +1,24 @@
+
+
+
+
+
{format(log.date, 'dd.MM.yyyy')}
+
+ {#if mode == "edit"}
+
+ {/if}
+
+
{log.text}
+
diff --git a/web/src/lib/components/summit_log/summit_log_modal.svelte b/web/src/lib/components/summit_log/summit_log_modal.svelte
new file mode 100644
index 00000000..70ed07ab
--- /dev/null
+++ b/web/src/lib/components/summit_log/summit_log_modal.svelte
@@ -0,0 +1,67 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/web/src/lib/components/summit_log_card.svelte b/web/src/lib/components/summit_log_card.svelte
deleted file mode 100644
index 07894f08..00000000
--- a/web/src/lib/components/summit_log_card.svelte
+++ /dev/null
@@ -1,11 +0,0 @@
-
-
-
-
{formatISODate(log.date)}
- {log.text}
-
diff --git a/web/src/lib/components/trail_card.svelte b/web/src/lib/components/trail/trail_card.svelte
similarity index 65%
rename from web/src/lib/components/trail_card.svelte
rename to web/src/lib/components/trail/trail_card.svelte
index eef71f3d..cbad64b4 100644
--- a/web/src/lib/components/trail_card.svelte
+++ b/web/src/lib/components/trail/trail_card.svelte
@@ -2,17 +2,26 @@
import type { Trail } from "$lib/models/trail";
import { getFileURL } from "$lib/util/file_util";
import { formatMeters, formatTimeHHMM } from "$lib/util/format_util";
+ import Dropdown from "../base/dropdown.svelte";
export let trail: Trail;
+
+ const dropdownItems = [
+ { text: "Edit", value: "edit" },
+ { text: "Delete", value: "delete" },
+ ];
-
-

+
+
+

+
-
{trail.name}
+
+
{trail.name}
+
+
{trail.location}
diff --git a/web/src/lib/components/waypoint/waypoint_modal.svelte b/web/src/lib/components/waypoint/waypoint_modal.svelte
index c6741dce..76aa7031 100644
--- a/web/src/lib/components/waypoint/waypoint_modal.svelte
+++ b/web/src/lib/components/waypoint/waypoint_modal.svelte
@@ -2,11 +2,12 @@
import { Waypoint, waypointSchema } from "$lib/models/waypoint";
import { createEventDispatcher } from "svelte";
- import { createForm } from "svelte-forms-lib";
+ import { createForm } from "$lib/vendor/svelte-form-lib/index";
import Modal from "../base/modal.svelte";
import TextField from "../base/text_field.svelte";
import Textarea from "../base/textarea.svelte";
import { waypoint } from "$lib/stores/waypoint_store";
+ import { util } from "$lib/vendor/svelte-form-lib/util";
export let openModal: (() => void) | undefined = undefined;
export let closeModal: (() => void) | undefined = undefined;
@@ -21,7 +22,7 @@
closeModal!();
},
});
- $: form.set($waypoint);
+ $: form.set(util.cloneDeep($waypoint));
({
+ id: string().optional(),
+ date: date().transform((value, originalValue, context) => {
+ if (context.isType(value)) return value;
+ return parse(originalValue, 'dd.MM.yyyy', new Date());
+ }).required('Required').typeError('Invalid Date'),
+ text: string().optional(),
+});
+
+
+export { SummitLog, summitLogSchema }
\ No newline at end of file
diff --git a/web/src/lib/models/trail.ts b/web/src/lib/models/trail.ts
index 63f0b278..5d57cc09 100644
--- a/web/src/lib/models/trail.ts
+++ b/web/src/lib/models/trail.ts
@@ -1,3 +1,4 @@
+import { array, number, object, string } from "yup";
import type { Category } from "./category";
import type { SummitLog } from "./summit_log";
import type { Waypoint } from "./waypoint";
@@ -20,38 +21,57 @@ class Trail {
tags?: string[];
description?: string;
+ _photoFiles: File[]
+
constructor(name: string,
- id?: string,
- location?: string,
- distance?: number,
- elevation_gain?: number,
- duration?: number,
- thumbnail?: string,
- photos?: string[],
- gpx?: string,
- category?: Category,
- waypoints?: Waypoint[],
- summit_logs?: SummitLog[],
- tags?: string[],
- description?: string
- ) {
- this.id = id;
- this.name = name;
- this.location = location;
- this.distance = distance;
- this.elevation_gain = elevation_gain;
- this.duration = duration;
- this.thumbnail = thumbnail;
- this.photos = photos ?? [];
- this.gpx = gpx;
- this.expand = {
- category: category,
- waypoints: waypoints ?? [],
- summit_logs: summit_logs ?? []
+ params?: {
+ id?: string,
+ location?: string,
+ distance?: number,
+ elevation_gain?: number,
+ duration?: number,
+ thumbnail?: string,
+ photos?: string[],
+ gpx?: string,
+ category?: Category,
+ waypoints?: Waypoint[],
+ summit_logs?: SummitLog[],
+ tags?: string[],
+ description?: string
}
- this.tags = tags ?? []
- this.description = description ?? "";
+
+ ) {
+ this.id = params?.id;
+ this.name = name;
+ this.location = params?.location;
+ this.distance = params?.distance;
+ this.elevation_gain = params?.elevation_gain;
+ this.duration = params?.duration;
+ this.thumbnail = params?.thumbnail;
+ this.photos = params?.photos ?? [];
+ this.gpx = params?.gpx;
+ this.expand = {
+ category: params?.category,
+ waypoints: params?.waypoints ?? [],
+ summit_logs: params?.summit_logs ?? []
+ }
+ this.tags = params?.tags ?? []
+ this.description = params?.description ?? "";
+ this._photoFiles = [];
}
}
-export { Trail };
+const trailSchema = object({
+ id: string().optional(),
+ name: string().required("Required"),
+ location: string().optional(),
+ distance: number().optional(),
+ elevation_gain: number().optional(),
+ duration: number().optional(),
+ thumbnail: string().optional(),
+ photos: array(string()).optional(),
+ gpx: string().optional(),
+ description: string().optional()
+});
+
+export { Trail, trailSchema };
diff --git a/web/src/lib/models/waypoint.ts b/web/src/lib/models/waypoint.ts
index 8cbc986c..61775c7e 100644
--- a/web/src/lib/models/waypoint.ts
+++ b/web/src/lib/models/waypoint.ts
@@ -25,8 +25,8 @@ const waypointSchema = object({
id: string().optional(),
name: string().optional(),
description: string().optional(),
- lat: number().min(0).required('Required').typeError('Invalid latitude'),
- lon: number().min(0).required('Required').typeError('Invalid longitude'),
+ lat: number().required('Required').typeError('Invalid latitude'),
+ lon: number().required('Required').typeError('Invalid longitude'),
icon: string().optional()
});
diff --git a/web/src/lib/stores/summit_log_store.ts b/web/src/lib/stores/summit_log_store.ts
new file mode 100644
index 00000000..8d07da5f
--- /dev/null
+++ b/web/src/lib/stores/summit_log_store.ts
@@ -0,0 +1,21 @@
+import { pb } from "$lib/constants";
+import { SummitLog } from "$lib/models/summit_log";
+import { writable, type Writable } from "svelte/store";
+
+export const summitLog: Writable = writable(new SummitLog(new Date().toISOString()));
+
+export async function summit_logs_create(bodyParams?: { [key: string]: any; } | FormData) {
+ const model = await pb
+ .collection("summit_logs")
+ .create(bodyParams);
+
+ return model;
+}
+
+export async function summit_logs_delete(id: string) {
+ const success = await pb
+ .collection("summit_logs_delete")
+ .delete(id);
+
+ return success;
+}
\ No newline at end of file
diff --git a/web/src/lib/stores/trail_store.ts b/web/src/lib/stores/trail_store.ts
index c262f170..e1a7dc36 100644
--- a/web/src/lib/stores/trail_store.ts
+++ b/web/src/lib/stores/trail_store.ts
@@ -23,6 +23,31 @@ export async function trails_show(id: string, loadGPX?: boolean) {
trail.set(response);
}
+export async function trails_create(bodyParams?: { [key: string]: any; } | FormData) {
+ const model = await pb
+ .collection("trails")
+ .create(bodyParams);
+
+ return model;
+}
+
+export async function trails_update(id: string, bodyParams?: { [key: string]: any; } | FormData) {
+ const model = await pb
+ .collection("trails")
+ .update(id, bodyParams);
+
+ return model;
+}
+
+
+export async function trails_delete(id: string) {
+ const success = await pb
+ .collection("trails")
+ .delete(id);
+
+ return success;
+}
+
async function fetchGPX(trail: Trail) {
if (!trail.gpx) {
return "";
diff --git a/web/src/lib/stores/waypoint_store.ts b/web/src/lib/stores/waypoint_store.ts
index 85236133..919798e4 100644
--- a/web/src/lib/stores/waypoint_store.ts
+++ b/web/src/lib/stores/waypoint_store.ts
@@ -1,4 +1,22 @@
+import { pb } from "$lib/constants";
import { Waypoint } from "$lib/models/waypoint";
import { writable, type Writable } from "svelte/store";
export const waypoint: Writable = writable(new Waypoint(0, 0));
+
+export async function waypoints_create(bodyParams?: { [key: string]: any; } | FormData) {
+
+ const model = await pb
+ .collection("waypoints")
+ .create(bodyParams);
+
+ return model;
+}
+
+export async function waypoints_delete(id: string) {
+ const success = await pb
+ .collection("waypoints")
+ .delete(id);
+
+ return success;
+}
\ No newline at end of file
diff --git a/web/src/lib/util/format_util.ts b/web/src/lib/util/format_util.ts
index 4e10d8b6..afc72a4b 100644
--- a/web/src/lib/util/format_util.ts
+++ b/web/src/lib/util/format_util.ts
@@ -1,7 +1,6 @@
-import type { Waypoint } from "$lib/models/waypoint";
export function formatTimeHHMM(minutes?: number) {
- if(!minutes) {
+ if (!minutes) {
return "-";
}
const m = minutes % 60;
@@ -12,7 +11,7 @@ export function formatTimeHHMM(minutes?: number) {
}
export function formatMeters(meters?: number) {
- if(!meters) {
+ if (!meters) {
return "-";
}
if (meters % 1 === 0) {
@@ -22,16 +21,3 @@ export function formatMeters(meters?: number) {
}
}
-
-export function formatISODate(isoTimestamp?: string) {
- if(!isoTimestamp) {
- return "-"
- }
- const date = new Date(isoTimestamp);
-
- const day = date.getDate().toString().padStart(2, '0');
- const month = (date.getMonth() + 1).toString().padStart(2, '0'); // Month is zero-based
- const year = date.getFullYear();
-
- return `${day}.${month}.${year}`;
-}
\ No newline at end of file
diff --git a/web/src/lib/vendor/svelte-form-lib/create-form.js b/web/src/lib/vendor/svelte-form-lib/create-form.js
new file mode 100644
index 00000000..b88c0691
--- /dev/null
+++ b/web/src/lib/vendor/svelte-form-lib/create-form.js
@@ -0,0 +1,255 @@
+import {derived, writable, get} from 'svelte/store';
+import {util} from './util';
+
+const NO_ERROR = '';
+const IS_TOUCHED = true;
+
+function isCheckbox(element) {
+ return element.getAttribute && element.getAttribute('type') === 'checkbox';
+}
+
+function isFileInput(element) {
+ return element.getAttribute && element.getAttribute('type') === 'file';
+}
+
+function resolveValue(element) {
+ if (isFileInput(element)) {
+ return element.files;
+ } else if (isCheckbox(element)) {
+ return element.checked;
+ } else {
+ return element.value;
+ }
+}
+
+export const createForm = (config) => {
+ let initialValues = config.initialValues || {};
+
+ const validationSchema = config.validationSchema;
+ const validateFunction = config.validate;
+ const onSubmit = config.onSubmit;
+
+ const getInitial = {
+ values: () => util.cloneDeep(initialValues),
+ errors: () =>
+ validationSchema
+ ? util.getErrorsFromSchema(initialValues, validationSchema.fields)
+ : util.assignDeep(initialValues, NO_ERROR),
+ touched: () => util.assignDeep(initialValues, !IS_TOUCHED),
+ };
+
+ const form = writable(getInitial.values());
+ const errors = writable(getInitial.errors());
+ const touched = writable(getInitial.touched());
+
+ const isSubmitting = writable(false);
+ const isValidating = writable(false);
+
+ const isValid = derived(errors, ($errors) => {
+ const noErrors = util
+ .getValues($errors)
+ .every((field) => field === NO_ERROR);
+ return noErrors;
+ });
+
+ const modified = derived(form, ($form) => {
+ const object = util.assignDeep($form, false);
+
+ for (let key in $form) {
+ object[key] = !util.deepEqual($form[key], initialValues[key]);
+ }
+
+ return object;
+ });
+
+ const isModified = derived(modified, ($modified) => {
+ return util.getValues($modified).includes(true);
+ });
+
+ function validateField(field) {
+ return util
+ .subscribeOnce(form)
+ .then((values) => validateFieldValue(field, values[field]));
+ }
+
+ function validateFieldValue(field, value) {
+ updateTouched(field, true);
+
+ if (validationSchema) {
+ isValidating.set(true);
+
+ return validationSchema
+ .validateAt(field, get(form))
+ .then(() => util.update(errors, field, ''))
+ .catch((error) => util.update(errors, field, error.message))
+ .finally(() => {
+ isValidating.set(false);
+ });
+ }
+
+ if (validateFunction) {
+ isValidating.set(true);
+ return Promise.resolve()
+ .then(() => validateFunction({[field]: value}))
+ .then((errs) =>
+ util.update(errors, field, !util.isNullish(errs) ? errs[field] : ''),
+ )
+ .finally(() => {
+ isValidating.set(false);
+ });
+ }
+
+ return Promise.resolve();
+ }
+
+ function updateValidateField(field, value) {
+ updateField(field, value);
+ return validateFieldValue(field, value);
+ }
+
+ function handleChange(event) {
+ const element = event.target;
+ const field = element.name || element.id;
+ const value = resolveValue(element);
+
+ return updateValidateField(field, value);
+ }
+
+ function handleSubmit(event) {
+ if (event && event.preventDefault) {
+ event.preventDefault();
+ }
+
+ isSubmitting.set(true);
+
+ return util.subscribeOnce(form).then((values) => {
+ if (typeof validateFunction === 'function') {
+ isValidating.set(true);
+
+ return Promise.resolve()
+ .then(() => validateFunction(values))
+ .then((error) => {
+ if (util.isNullish(error) || util.getValues(error).length === 0) {
+ return clearErrorsAndSubmit(values);
+ } else {
+ errors.set(error);
+ isSubmitting.set(false);
+ }
+ })
+ .finally(() => isValidating.set(false));
+ }
+
+ if (validationSchema) {
+ isValidating.set(true);
+
+ return (
+ validationSchema
+ .validate(values, {abortEarly: false})
+ .then(() => clearErrorsAndSubmit(values))
+ // eslint-disable-next-line unicorn/catch-error-name
+ .catch((yupErrors) => {
+ if (yupErrors && yupErrors.inner) {
+ const updatedErrors = getInitial.errors();
+
+ yupErrors.inner.map((error) =>
+ util.set(updatedErrors, error.path, error.message),
+ );
+
+ errors.set(updatedErrors);
+ }
+ isSubmitting.set(false);
+ })
+ .finally(() => isValidating.set(false))
+ );
+ }
+
+ return clearErrorsAndSubmit(values);
+ });
+ }
+
+ function handleReset() {
+ form.set(getInitial.values());
+ errors.set(getInitial.errors());
+ touched.set(getInitial.touched());
+ }
+
+ function clearErrorsAndSubmit(values) {
+ return Promise.resolve()
+ .then(() => errors.set(getInitial.errors()))
+ .then(() => onSubmit(values, form, errors))
+ .finally(() => isSubmitting.set(false));
+ }
+
+ /**
+ * Handler to imperatively update the value of a form field
+ */
+ function updateField(field, value) {
+ util.update(form, field, value);
+ }
+
+ /**
+ * Handler to imperatively update the touched value of a form field
+ */
+ function updateTouched(field, value) {
+ util.update(touched, field, value);
+ }
+
+ /**
+ * Update the initial values and reset form. Used to dynamically display new form values
+ */
+ function updateInitialValues(newValues) {
+ initialValues = newValues;
+
+ handleReset();
+ }
+
+ return {
+ form,
+ errors,
+ touched,
+ modified,
+ isValid,
+ isSubmitting,
+ isValidating,
+ isModified,
+ handleChange,
+ handleSubmit,
+ handleReset,
+ updateField,
+ updateValidateField,
+ updateTouched,
+ validateField,
+ updateInitialValues,
+ state: derived(
+ [
+ form,
+ errors,
+ touched,
+ modified,
+ isValid,
+ isValidating,
+ isSubmitting,
+ isModified,
+ ],
+ ([
+ $form,
+ $errors,
+ $touched,
+ $modified,
+ $isValid,
+ $isValidating,
+ $isSubmitting,
+ $isModified,
+ ]) => ({
+ form: $form,
+ errors: $errors,
+ touched: $touched,
+ modified: $modified,
+ isValid: $isValid,
+ isSubmitting: $isSubmitting,
+ isValidating: $isValidating,
+ isModified: $isModified,
+ }),
+ ),
+ };
+};
diff --git a/web/src/lib/vendor/svelte-form-lib/index.d.ts b/web/src/lib/vendor/svelte-form-lib/index.d.ts
new file mode 100644
index 00000000..83a75f6a
--- /dev/null
+++ b/web/src/lib/vendor/svelte-form-lib/index.d.ts
@@ -0,0 +1,102 @@
+///
+import type {SvelteComponentTyped} from 'svelte';
+import type {Readable, Writable} from 'svelte/store';
+import type {ObjectSchema} from 'yup';
+
+export type FormProps> = {
+ context?: FormState;
+ initialValues?: Inf;
+ onSubmit?: ((values: Inf) => any) | ((values: Inf) => Promise);
+ validate?: (values: Inf) => any | undefined;
+ validationSchema?: ObjectSchema;
+} & svelte.JSX.HTMLAttributes;
+
+type FieldProperties = {
+ name: string;
+ type?: string;
+ value?: string;
+} & svelte.JSX.HTMLProps;
+
+type SelectProperties = {
+ name: string;
+} & svelte.JSX.HTMLProps;
+
+type ErrorProperties = {
+ name: string;
+} & svelte.JSX.HTMLProps;
+
+type TextareaProperties = {
+ name: string;
+} & svelte.JSX.HTMLProps;
+
+type FormState> = {
+ form: Writable;
+ errors: Writable>;
+ touched: Writable>;
+ modified: Readable>;
+ isValid: Readable;
+ isSubmitting: Writable;
+ isValidating: Writable;
+ isModified: Readable;
+ updateField: (field: keyof Inf, value: any) => void;
+ updateValidateField: (field: keyof Inf, value: any) => void;
+ updateTouched: (field: keyof Inf, value: any) => void;
+ validateField: (field: keyof Inf) => Promise;
+ updateInitialValues: (newValues: Inf) => void;
+ handleReset: () => void;
+ state: Readable<{
+ form: Inf;
+ errors: Record;
+ touched: Record;
+ modified: Record;
+ isValid: boolean;
+ isSubmitting: boolean;
+ isValidating: boolean;
+ isModified: boolean;
+ }>;
+ handleChange: (event: Event) => any;
+ handleSubmit: (event: Event) => any;
+};
+
+declare function createForm>(formProperties: {
+ initialValues: Inf;
+ onSubmit: (values: Inf) => any | Promise;
+ validate?: (values: Inf) => any | undefined;
+ validationSchema?: ObjectSchema;
+}): FormState;
+
+declare class Form extends SvelteComponentTyped<
+ FormProps,
+ Record,
+ {
+ default: FormState;
+ }
+> {}
+
+declare class Field extends SvelteComponentTyped<
+ FieldProperties,
+ Record,
+ Record
+> {}
+
+declare class Textarea extends SvelteComponentTyped<
+ TextareaProperties,
+ Record,
+ Record
+> {}
+
+declare class Select extends SvelteComponentTyped<
+ SelectProperties,
+ Record,
+ {default: any}
+> {}
+
+declare class ErrorMessage extends SvelteComponentTyped<
+ ErrorProperties,
+ Record,
+ {default: any}
+> {}
+
+declare const key: {};
+
+export {createForm, key, Form, Field, Select, ErrorMessage, Textarea};
diff --git a/web/src/lib/vendor/svelte-form-lib/index.js b/web/src/lib/vendor/svelte-form-lib/index.js
new file mode 100644
index 00000000..db96af5b
--- /dev/null
+++ b/web/src/lib/vendor/svelte-form-lib/index.js
@@ -0,0 +1 @@
+export {createForm} from './create-form';
\ No newline at end of file
diff --git a/web/src/lib/vendor/svelte-form-lib/util.js b/web/src/lib/vendor/svelte-form-lib/util.js
new file mode 100644
index 00000000..32f6f27f
--- /dev/null
+++ b/web/src/lib/vendor/svelte-form-lib/util.js
@@ -0,0 +1,135 @@
+import { dequal as isEqual } from 'dequal/lite';
+
+function subscribeOnce(observable) {
+ return new Promise((resolve) => {
+ observable.subscribe(resolve)(); // immediately invoke to unsubscribe
+ });
+}
+
+function update(object, path, value) {
+ object.update((o) => {
+ set(o, path, value);
+ return o;
+ });
+}
+
+function cloneDeep(object) {
+ try {
+ return JSON.parse(JSON.stringify(object));
+
+ } catch (e) {
+ return object;
+ }
+}
+
+function isNullish(value) {
+ return value === undefined || value === null;
+}
+
+function isEmpty(object) {
+ return isNullish(object) || Object.keys(object).length <= 0;
+}
+
+function getValues(object) {
+ let results = [];
+
+ for (const [, value] of Object.entries(object)) {
+ const values = typeof value === 'object' ? getValues(value) : [value];
+ results = [...results, ...values];
+ }
+
+ return results;
+}
+
+// TODO: refactor this so as not to rely directly on yup's API
+// This should use dependency injection, with a default callback which may assume
+// yup as the validation schema
+function getErrorsFromSchema(initialValues, schema, errors = {}) {
+ for (const key in schema) {
+ switch (true) {
+ case schema[key].type === 'object' && !isEmpty(schema[key].fields): {
+ errors[key] = getErrorsFromSchema(
+ initialValues[key],
+ schema[key].fields,
+ { ...errors[key] },
+ );
+ break;
+ }
+
+ case schema[key].type === 'array': {
+ const values =
+ initialValues && initialValues[key] ? initialValues[key] : [];
+ errors[key] = values.map((value) => {
+ const innerError = getErrorsFromSchema(
+ value,
+ schema[key].innerType.fields,
+ { ...errors[key] },
+ );
+
+ return Object.keys(innerError).length > 0 ? innerError : '';
+ });
+ break;
+ }
+
+ default: {
+ errors[key] = '';
+ }
+ }
+ }
+
+ return errors;
+}
+
+const deepEqual = isEqual;
+
+function assignDeep(object, value) {
+ if (Array.isArray(object)) {
+ return object.map((o) => assignDeep(o, value));
+ }
+ const copy = {};
+ for (const key in object) {
+ copy[key] =
+ typeof object[key] === 'object' && !isNullish(object[key]) ? assignDeep(object[key], value) : value;
+ }
+ return copy;
+}
+
+function set(object, path, value) {
+ if (new Object(object) !== object) return object;
+
+ if (!Array.isArray(path)) {
+ path = path.toString().match(/[^.[\]]+/g) || [];
+ }
+
+ const result = path
+ .slice(0, -1)
+ // TODO: replace this reduce with something more readable
+ // eslint-disable-next-line unicorn/no-array-reduce
+ .reduce(
+ (accumulator, key, index) =>
+ new Object(accumulator[key]) === accumulator[key]
+ ? accumulator[key]
+ : (accumulator[key] =
+ Math.trunc(Math.abs(path[index + 1])) === +path[index + 1]
+ ? []
+ : {}),
+ object,
+ );
+
+ result[path[path.length - 1]] = value;
+
+ return object;
+}
+
+export const util = {
+ assignDeep,
+ cloneDeep,
+ deepEqual,
+ getErrorsFromSchema,
+ getValues,
+ isEmpty,
+ isNullish,
+ set,
+ subscribeOnce,
+ update,
+};
diff --git a/web/src/routes/+page.svelte b/web/src/routes/+page.svelte
index 871b0730..1014f5e4 100644
--- a/web/src/routes/+page.svelte
+++ b/web/src/routes/+page.svelte
@@ -1,8 +1,37 @@
diff --git a/web/src/routes/+page.ts b/web/src/routes/+page.ts
index 35061744..04c515e4 100644
--- a/web/src/routes/+page.ts
+++ b/web/src/routes/+page.ts
@@ -1,8 +1,8 @@
import { categories_index } from "$lib/stores/category_store";
import { trails_index } from "$lib/stores/trail_store";
-import type { Load } from "@sveltejs/kit";
+import type { ServerLoad } from "@sveltejs/kit";
-export const load: Load = async ({ params }) => {
+export const load: ServerLoad = async ({ params, locals }) => {
await trails_index()
await categories_index()
};
\ No newline at end of file
diff --git a/web/src/routes/new/+page.svelte b/web/src/routes/new/+page.svelte
index bb9c0bd3..6fc7446f 100644
--- a/web/src/routes/new/+page.svelte
+++ b/web/src/routes/new/+page.svelte
@@ -1,29 +1,80 @@
-
+
Waypoints
- {#each $trail.expand.waypoints as waypoint, i}
+ {#each $form.expand.waypoints as waypoint, i}
- openMarkerPopup(waypoint)}>
{/each}
-
+
+ Photos
+
+
+ Summit Book
+
+ {#each $form.expand.summit_logs as log, i}
+ -
+ handleSummitLogMenuClick(log, i, e)}
+ >
+
+ {/each}
+
+
+
+
-
+