From ec33723bdc315d40afcf8ea33db5b6eec51c469f Mon Sep 17 00:00:00 2001 From: Christian Beutel <> Date: Sun, 4 Feb 2024 22:48:04 +0100 Subject: [PATCH] adds trails page --- .../1707047370_updated_waypoints.js | 18 ++ web/package-lock.json | 6 + web/package.json | 1 + .../lib/components/base/double_slider.svelte | 54 ++++ web/src/lib/components/base/dropdown.svelte | 2 +- .../lib/components/base/radio_group.svelte | 41 +++ web/src/lib/components/base/search.svelte | 110 ++++++++ web/src/lib/components/base/slider.svelte | 52 ++++ web/src/lib/components/base/text_field.svelte | 8 +- web/src/lib/components/nav_bar.svelte | 2 +- .../lib/components/trail/trail_card.svelte | 7 +- web/src/lib/models/trail.ts | 20 +- web/src/lib/stores/trail_store.ts | 4 +- web/src/lib/util/country_code_util.ts | 247 ++++++++++++++++++ web/src/lib/util/format_util.ts | 2 +- web/src/routes/+page.svelte | 73 ++++-- web/src/routes/trail/edit/[id]/+page.svelte | 12 +- web/src/routes/trails/+page.svelte | 162 ++++++++++++ web/src/routes/trails/+page.ts | 8 + 19 files changed, 799 insertions(+), 30 deletions(-) create mode 100644 db/pb_migrations/1707047370_updated_waypoints.js create mode 100644 web/src/lib/components/base/double_slider.svelte create mode 100644 web/src/lib/components/base/radio_group.svelte create mode 100644 web/src/lib/components/base/search.svelte create mode 100644 web/src/lib/components/base/slider.svelte create mode 100644 web/src/lib/util/country_code_util.ts create mode 100644 web/src/routes/trails/+page.svelte create mode 100644 web/src/routes/trails/+page.ts diff --git a/db/pb_migrations/1707047370_updated_waypoints.js b/db/pb_migrations/1707047370_updated_waypoints.js new file mode 100644 index 00000000..39b1b45b --- /dev/null +++ b/db/pb_migrations/1707047370_updated_waypoints.js @@ -0,0 +1,18 @@ +/// +migrate((db) => { + const dao = new Dao(db) + const collection = dao.findCollectionByNameOrId("goeo2ubp103rzp9") + + collection.updateRule = "@request.auth.id != \"\" && @collection.trails.waypoints.id ?= id && (@collection.trails.author = @request.auth.id)" + collection.deleteRule = "@request.auth.id != \"\" && @collection.trails.waypoints.id ?= id && (@collection.trails.author = @request.auth.id)" + + return dao.saveCollection(collection) +}, (db) => { + const dao = new Dao(db) + const collection = dao.findCollectionByNameOrId("goeo2ubp103rzp9") + + collection.updateRule = "@request.auth.id != \"\" && @collection.trails.waypoints ?= id && (@collection.trails.author = @request.auth.id)" + collection.deleteRule = "@request.auth.id != \"\" && @collection.trails.waypoints ?= id && (@collection.trails.author = @request.auth.id)" + + return dao.saveCollection(collection) +}) diff --git a/web/package-lock.json b/web/package-lock.json index 55e7f205..5bcdbfc3 100644 --- a/web/package-lock.json +++ b/web/package-lock.json @@ -16,6 +16,7 @@ "leaflet-gpx": "^1.7.0", "leaflet.awesome-markers": "^2.0.5", "meilisearch": "^0.37.0", + "nouislider": "^15.7.1", "photoswipe": "^5.4.3", "pocketbase": "^0.21.0", "yup": "^1.3.3" @@ -2247,6 +2248,11 @@ "node": ">=0.10.0" } }, + "node_modules/nouislider": { + "version": "15.7.1", + "resolved": "https://registry.npmjs.org/nouislider/-/nouislider-15.7.1.tgz", + "integrity": "sha512-5N7C1ru/i8y3dg9+Z6ilj6+m1EfabvOoaRa7ztpxBSKKRZso4vA52DGSbBJjw5XLtFr/LZ9SgGAXqyVtlVHO5w==" + }, "node_modules/npm-run-path": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-5.2.0.tgz", diff --git a/web/package.json b/web/package.json index 68e86445..31da8b41 100644 --- a/web/package.json +++ b/web/package.json @@ -39,6 +39,7 @@ "leaflet-gpx": "^1.7.0", "leaflet.awesome-markers": "^2.0.5", "meilisearch": "^0.37.0", + "nouislider": "^15.7.1", "photoswipe": "^5.4.3", "pocketbase": "^0.21.0", "yup": "^1.3.3" diff --git a/web/src/lib/components/base/double_slider.svelte b/web/src/lib/components/base/double_slider.svelte new file mode 100644 index 00000000..baad92dd --- /dev/null +++ b/web/src/lib/components/base/double_slider.svelte @@ -0,0 +1,54 @@ + + +
+ + diff --git a/web/src/lib/components/base/dropdown.svelte b/web/src/lib/components/base/dropdown.svelte index cafa6287..2a8cf09c 100644 --- a/web/src/lib/components/base/dropdown.svelte +++ b/web/src/lib/components/base/dropdown.svelte @@ -30,7 +30,7 @@ diff --git a/web/src/lib/components/base/radio_group.svelte b/web/src/lib/components/base/radio_group.svelte new file mode 100644 index 00000000..755c077d --- /dev/null +++ b/web/src/lib/components/base/radio_group.svelte @@ -0,0 +1,41 @@ + + + + +{#each items as item, i} +
+ handleRadioChange(i)} + /> + +
+{/each} diff --git a/web/src/lib/components/base/search.svelte b/web/src/lib/components/base/search.svelte new file mode 100644 index 00000000..d6e9befb --- /dev/null +++ b/web/src/lib/components/base/search.svelte @@ -0,0 +1,110 @@ + + + + +
+ + + + {#if value.length > 0} + + {/if} + (searching = true)} + > + + {#if dropDownOpen} + + {/if} +
diff --git a/web/src/lib/components/base/slider.svelte b/web/src/lib/components/base/slider.svelte new file mode 100644 index 00000000..8d2f7d82 --- /dev/null +++ b/web/src/lib/components/base/slider.svelte @@ -0,0 +1,52 @@ + + +
+ + diff --git a/web/src/lib/components/base/text_field.svelte b/web/src/lib/components/base/text_field.svelte index d1f1b1af..bd92c2d3 100644 --- a/web/src/lib/components/base/text_field.svelte +++ b/web/src/lib/components/base/text_field.svelte @@ -6,8 +6,8 @@ export let error: string = ""; export let icon: string = ""; export let extraClasses: string = ""; - export let type: "text" | "password" = "text"; - + export let type: "text" | "password" | "search" = "text"; + export let autocomplete: "on" | "off" = "on" function typeAction(node: HTMLInputElement) { node.type = type; } @@ -28,9 +28,13 @@ class="bg-gray-50 border rounded-md p-3 transition-colors focus:border-primary focus:outline-none focus:ring-0 w-full {extraClasses}" class:border-red-400={error.length > 0} class:bg-red-50={error.length > 0} + {autocomplete} use:typeAction bind:value on:change + on:input + on:focusin + on:focusout {placeholder} /> diff --git a/web/src/lib/components/nav_bar.svelte b/web/src/lib/components/nav_bar.svelte index 748987e4..90ca0e8e 100644 --- a/web/src/lib/components/nav_bar.svelte +++ b/web/src/lib/components/nav_bar.svelte @@ -21,7 +21,7 @@ {#if $currentUser} - Trails + Trails Map Categories Favorites diff --git a/web/src/lib/components/trail/trail_card.svelte b/web/src/lib/components/trail/trail_card.svelte index 592c493d..299105e4 100644 --- a/web/src/lib/components/trail/trail_card.svelte +++ b/web/src/lib/components/trail/trail_card.svelte @@ -5,6 +5,7 @@ import Dropdown from "../base/dropdown.svelte"; export let trail: Trail; + export let mode: "show" | "edit" = "show"; const dropdownItems = [ { text: "Edit", value: "edit" }, @@ -12,15 +13,15 @@ ]; -
-
+
+

{trail.name}

- {#if $currentUser && $currentUser.id == trail.author} + {#if $currentUser && $currentUser.id == trail.author && mode == "edit"} {/if}
diff --git a/web/src/lib/models/trail.ts b/web/src/lib/models/trail.ts index 4149ff04..810ff7e4 100644 --- a/web/src/lib/models/trail.ts +++ b/web/src/lib/models/trail.ts @@ -85,4 +85,22 @@ const trailSchema = object({ description: string().optional() }); -export { Trail, trailSchema }; +interface TrailFilter { + q: string, + category: string[], + near: { + lat?: number, + lon?: number, + distance: number + } + distanceMin: number, + distanceMax: number, + eleavationGainMin: number; + elevationGainMax: number; + completed?: boolean; +} + +export { Trail, trailSchema }; + +export type { TrailFilter }; + diff --git a/web/src/lib/stores/trail_store.ts b/web/src/lib/stores/trail_store.ts index 3ba72cf1..1df608c8 100644 --- a/web/src/lib/stores/trail_store.ts +++ b/web/src/lib/stores/trail_store.ts @@ -90,7 +90,7 @@ export async function trails_create(trail: Trail, formData: { [key: string]: any model = await pb .collection("trails") - .update(model.id!, { thumbnail: thumbnail }); + .update(model.id!, { thumbnail: thumbnail }, { expand: "category" }); index_trail(model); @@ -190,6 +190,8 @@ export async function trails_delete(trail: Trail) { } } + ms.index('trails').deleteDocument(trail.id!) + const success = await pb .collection("trails") .delete(trail.id!); diff --git a/web/src/lib/util/country_code_util.ts b/web/src/lib/util/country_code_util.ts new file mode 100644 index 00000000..08bff98b --- /dev/null +++ b/web/src/lib/util/country_code_util.ts @@ -0,0 +1,247 @@ +export const country_codes = { + AF: 'Afghanistan', + AX: 'Aland Islands', + AL: 'Albania', + DZ: 'Algeria', + AS: 'American Samoa', + AD: 'Andorra', + AO: 'Angola', + AI: 'Anguilla', + AQ: 'Antarctica', + AG: 'Antigua And Barbuda', + AR: 'Argentina', + AM: 'Armenia', + AW: 'Aruba', + AU: 'Australia', + AT: 'Austria', + AZ: 'Azerbaijan', + BS: 'Bahamas', + BH: 'Bahrain', + BD: 'Bangladesh', + BB: 'Barbados', + BY: 'Belarus', + BE: 'Belgium', + BZ: 'Belize', + BJ: 'Benin', + BM: 'Bermuda', + BT: 'Bhutan', + BO: 'Bolivia', + BA: 'Bosnia And Herzegovina', + BW: 'Botswana', + BV: 'Bouvet Island', + BR: 'Brazil', + IO: 'British Indian Ocean Territory', + BN: 'Brunei Darussalam', + BG: 'Bulgaria', + BF: 'Burkina Faso', + BI: 'Burundi', + KH: 'Cambodia', + CM: 'Cameroon', + CA: 'Canada', + CV: 'Cape Verde', + KY: 'Cayman Islands', + CF: 'Central African Republic', + TD: 'Chad', + CL: 'Chile', + CN: 'China', + CX: 'Christmas Island', + CC: 'Cocos (Keeling) Islands', + CO: 'Colombia', + KM: 'Comoros', + CG: 'Congo', + CD: 'Congo, Democratic Republic', + CK: 'Cook Islands', + CR: 'Costa Rica', + CI: 'Cote D\'Ivoire', + HR: 'Croatia', + CU: 'Cuba', + CY: 'Cyprus', + CZ: 'Czech Republic', + DK: 'Denmark', + DJ: 'Djibouti', + DM: 'Dominica', + DO: 'Dominican Republic', + EC: 'Ecuador', + EG: 'Egypt', + SV: 'El Salvador', + GQ: 'Equatorial Guinea', + ER: 'Eritrea', + EE: 'Estonia', + ET: 'Ethiopia', + FK: 'Falkland Islands (Malvinas)', + FO: 'Faroe Islands', + FJ: 'Fiji', + FI: 'Finland', + FR: 'France', + GF: 'French Guiana', + PF: 'French Polynesia', + TF: 'French Southern Territories', + GA: 'Gabon', + GM: 'Gambia', + GE: 'Georgia', + DE: 'Germany', + GH: 'Ghana', + GI: 'Gibraltar', + GR: 'Greece', + GL: 'Greenland', + GD: 'Grenada', + GP: 'Guadeloupe', + GU: 'Guam', + GT: 'Guatemala', + GG: 'Guernsey', + GN: 'Guinea', + GW: 'Guinea-Bissau', + GY: 'Guyana', + HT: 'Haiti', + HM: 'Heard Island & Mcdonald Islands', + VA: 'Holy See (Vatican City State)', + HN: 'Honduras', + HK: 'Hong Kong', + HU: 'Hungary', + IS: 'Iceland', + IN: 'India', + ID: 'Indonesia', + IR: 'Iran, Islamic Republic Of', + IQ: 'Iraq', + IE: 'Ireland', + IM: 'Isle Of Man', + IL: 'Israel', + IT: 'Italy', + JM: 'Jamaica', + JP: 'Japan', + JE: 'Jersey', + JO: 'Jordan', + KZ: 'Kazakhstan', + KE: 'Kenya', + KI: 'Kiribati', + KR: 'Korea', + KW: 'Kuwait', + KG: 'Kyrgyzstan', + LA: 'Lao People\'s Democratic Republic', + LV: 'Latvia', + LB: 'Lebanon', + LS: 'Lesotho', + LR: 'Liberia', + LY: 'Libyan Arab Jamahiriya', + LI: 'Liechtenstein', + LT: 'Lithuania', + LU: 'Luxembourg', + MO: 'Macao', + MK: 'Macedonia', + MG: 'Madagascar', + MW: 'Malawi', + MY: 'Malaysia', + MV: 'Maldives', + ML: 'Mali', + MT: 'Malta', + MH: 'Marshall Islands', + MQ: 'Martinique', + MR: 'Mauritania', + MU: 'Mauritius', + YT: 'Mayotte', + MX: 'Mexico', + FM: 'Micronesia, Federated States Of', + MD: 'Moldova', + MC: 'Monaco', + MN: 'Mongolia', + ME: 'Montenegro', + MS: 'Montserrat', + MA: 'Morocco', + MZ: 'Mozambique', + MM: 'Myanmar', + NA: 'Namibia', + NR: 'Nauru', + NP: 'Nepal', + NL: 'Netherlands', + AN: 'Netherlands Antilles', + NC: 'New Caledonia', + NZ: 'New Zealand', + NI: 'Nicaragua', + NE: 'Niger', + NG: 'Nigeria', + NU: 'Niue', + NF: 'Norfolk Island', + MP: 'Northern Mariana Islands', + NO: 'Norway', + OM: 'Oman', + PK: 'Pakistan', + PW: 'Palau', + PS: 'Palestinian Territory, Occupied', + PA: 'Panama', + PG: 'Papua New Guinea', + PY: 'Paraguay', + PE: 'Peru', + PH: 'Philippines', + PN: 'Pitcairn', + PL: 'Poland', + PT: 'Portugal', + PR: 'Puerto Rico', + QA: 'Qatar', + RE: 'Reunion', + RO: 'Romania', + RU: 'Russian Federation', + RW: 'Rwanda', + BL: 'Saint Barthelemy', + SH: 'Saint Helena', + KN: 'Saint Kitts And Nevis', + LC: 'Saint Lucia', + MF: 'Saint Martin', + PM: 'Saint Pierre And Miquelon', + VC: 'Saint Vincent And Grenadines', + WS: 'Samoa', + SM: 'San Marino', + ST: 'Sao Tome And Principe', + SA: 'Saudi Arabia', + SN: 'Senegal', + RS: 'Serbia', + SC: 'Seychelles', + SL: 'Sierra Leone', + SG: 'Singapore', + SK: 'Slovakia', + SI: 'Slovenia', + SB: 'Solomon Islands', + SO: 'Somalia', + ZA: 'South Africa', + GS: 'South Georgia And Sandwich Isl.', + ES: 'Spain', + LK: 'Sri Lanka', + SD: 'Sudan', + SR: 'Suriname', + SJ: 'Svalbard And Jan Mayen', + SZ: 'Swaziland', + SE: 'Sweden', + CH: 'Switzerland', + SY: 'Syrian Arab Republic', + TW: 'Taiwan', + TJ: 'Tajikistan', + TZ: 'Tanzania', + TH: 'Thailand', + TL: 'Timor-Leste', + TG: 'Togo', + TK: 'Tokelau', + TO: 'Tonga', + TT: 'Trinidad And Tobago', + TN: 'Tunisia', + TR: 'Turkey', + TM: 'Turkmenistan', + TC: 'Turks And Caicos Islands', + TV: 'Tuvalu', + UG: 'Uganda', + UA: 'Ukraine', + AE: 'United Arab Emirates', + GB: 'United Kingdom', + US: 'United States', + UM: 'United States Outlying Islands', + UY: 'Uruguay', + UZ: 'Uzbekistan', + VU: 'Vanuatu', + VE: 'Venezuela', + VN: 'Viet Nam', + VG: 'Virgin Islands, British', + VI: 'Virgin Islands, U.S.', + WF: 'Wallis And Futuna', + EH: 'Western Sahara', + YE: 'Yemen', + ZM: 'Zambia', + ZW: 'Zimbabwe' +} \ 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 afc72a4b..460a2497 100644 --- a/web/src/lib/util/format_util.ts +++ b/web/src/lib/util/format_util.ts @@ -11,7 +11,7 @@ export function formatTimeHHMM(minutes?: number) { } export function formatMeters(meters?: number) { - if (!meters) { + if (meters === undefined) { return "-"; } if (meters % 1 === 0) { diff --git a/web/src/routes/+page.svelte b/web/src/routes/+page.svelte index 4c4965a2..7cb76575 100644 --- a/web/src/routes/+page.svelte +++ b/web/src/routes/+page.svelte @@ -1,17 +1,22 @@
-
- - - - -
+ search(e.detail)} + on:click={(e) => handleSearchClick(e.detail)} + large={true} + placeholder="Search trails..." + items={searchDropdownItems} + >
-

{$currentUser ? 'Your' : 'Explore'} trails

+

+ {$currentUser ? "Your" : "Explore"} trails +

handleDropdownClick(trail, e.detail)} > diff --git a/web/src/routes/trail/edit/[id]/+page.svelte b/web/src/routes/trail/edit/[id]/+page.svelte index 55e6c8e0..ff43ff35 100644 --- a/web/src/routes/trail/edit/[id]/+page.svelte +++ b/web/src/routes/trail/edit/[id]/+page.svelte @@ -194,11 +194,13 @@ reader.onload = async function (e) { trail.set(new Trail("")); await addGPXLayer(e.target?.result as string); - console.log(await - ms.index("cities500").search("", { - filter: [`_geoRadius(${$form.lat}, ${$form.lon}, 100000000000)`], - }), - ); + const closestCity = (await ms.index("cities500").search("", { + filter: [`_geoRadius(${$form.lat}, ${$form.lon}, 10000)`], + sort: [`_geoPoint(${$form.lat}, ${$form.lon}):asc`], + limit: 1, + })).hits[0]; + + $form.location = closestCity.name; }; } diff --git a/web/src/routes/trails/+page.svelte b/web/src/routes/trails/+page.svelte new file mode 100644 index 00000000..89b5135e --- /dev/null +++ b/web/src/routes/trails/+page.svelte @@ -0,0 +1,162 @@ + + +
+
+ +
+

Category

+ {#each $categories as category} +
+ + +
+ {/each} +
+

Near

+
+ searchCities(e.detail)} + on:click={(e) => handleSearchClick(e.detail)} + > +
+ +

+ Radius: + {formatMeters(filter.near.distance)} +

+
+

Distance

+ +
+ {formatMeters(filter.distanceMin)} + {formatMeters(filter.distanceMax)} +
+
+

Elevation Gain

+ +
+ {formatMeters(filter.eleavationGainMin)} + {formatMeters(filter.elevationGainMax)} +
+
+

Completed

+ setCompletedFilter(e.detail)} + > +
+
+ {#each $trails as trail} + + + {/each} +
+
diff --git a/web/src/routes/trails/+page.ts b/web/src/routes/trails/+page.ts new file mode 100644 index 00000000..04c515e4 --- /dev/null +++ b/web/src/routes/trails/+page.ts @@ -0,0 +1,8 @@ +import { categories_index } from "$lib/stores/category_store"; +import { trails_index } from "$lib/stores/trail_store"; +import type { ServerLoad } from "@sveltejs/kit"; + +export const load: ServerLoad = async ({ params, locals }) => { + await trails_index() + await categories_index() +}; \ No newline at end of file