From 85729e9728ebaafc42c32b71dcde201be85c691f Mon Sep 17 00:00:00 2001 From: tofublock Date: Fri, 13 Dec 2024 20:39:42 +0100 Subject: [PATCH] Feature/trail table view (#138) * add "Table" * add trails table view * style table header * add author image, adjust table layout * tooltip for author image * i18n for author tooltip * trail sorting by duration and author * remove sort by elevation loss, add sort by duration instead * improve table layout * fix meilisearch migration * revert removing elevation_loss from sortOptions * add badge for public trails * hide public badge on small screens * adds share info button to trail_table --- ...436_update_sortable_attributes_duration.go | 36 ++++ .../lib/components/trail/trail_list.svelte | 112 +++++++---- .../lib/components/trail/trail_table.svelte | 181 ++++++++++++++++++ web/src/lib/i18n/locales/de.json | 1 + web/src/lib/i18n/locales/en.json | 1 + web/src/lib/i18n/locales/fr.json | 1 + web/src/lib/i18n/locales/hu.json | 1 + web/src/lib/i18n/locales/it.json | 1 + web/src/lib/i18n/locales/nl.json | 1 + web/src/lib/i18n/locales/pl.json | 1 + web/src/lib/i18n/locales/pt.json | 1 + web/src/lib/i18n/locales/zh.json | 1 + 12 files changed, 299 insertions(+), 39 deletions(-) create mode 100644 db/migrations/1733513436_update_sortable_attributes_duration.go create mode 100644 web/src/lib/components/trail/trail_table.svelte diff --git a/db/migrations/1733513436_update_sortable_attributes_duration.go b/db/migrations/1733513436_update_sortable_attributes_duration.go new file mode 100644 index 00000000..dbabc773 --- /dev/null +++ b/db/migrations/1733513436_update_sortable_attributes_duration.go @@ -0,0 +1,36 @@ +package migrations + +import ( + "os" + + "github.com/meilisearch/meilisearch-go" + "github.com/pocketbase/dbx" + + m "github.com/pocketbase/pocketbase/migrations" +) + +func init() { + client := meilisearch.New(os.Getenv("MEILI_URL"), meilisearch.WithAPIKey(os.Getenv("MEILI_MASTER_KEY"))) + + m.Register(func(db dbx.Builder) error { + _, err := client.Index("trails").UpdateSortableAttributes(&[]string{ + "created", "date", "difficulty", "distance", "elevation_gain", "elevation_loss", "name", "duration", "author", + }) + + if err != nil { + return err + } + + return nil + }, func(db dbx.Builder) error { + _, err := client.Index("trails").UpdateSortableAttributes(&[]string{ + "created", "date", "difficulty", "distance", "elevation_gain", "elevation_loss", "name", + }) + + if err != nil { + return err + } + + return nil + }) +} diff --git a/web/src/lib/components/trail/trail_list.svelte b/web/src/lib/components/trail/trail_list.svelte index e01e2866..fb6afe43 100644 --- a/web/src/lib/components/trail/trail_list.svelte +++ b/web/src/lib/components/trail/trail_list.svelte @@ -7,6 +7,7 @@ import EmptyStateSearch from "../empty_states/empty_state_search.svelte"; import TrailCard from "./trail_card.svelte"; import TrailListItem from "./trail_list_item.svelte"; + import TrailTable from "./trail_table.svelte"; import SkeletonCard from "../base/skeleton_card.svelte"; import SkeletonListItem from "../base/skeleton_list_item.svelte"; @@ -22,6 +23,7 @@ const displayOptions: SelectItem[] = [ { text: $_("card", { values: { n: 2 } }), value: "cards" }, { text: $_("list", { values: { n: 1 } }), value: "list" }, + { text: $_("table"), value: "table" }, ]; let selectedDisplayOption = displayOptions[0].value; @@ -29,13 +31,14 @@ let dispatch = createEventDispatcher(); const sortOptions: SelectItem[] = [ - { text: $_("alphabetical"), value: "name" }, - { text: $_("creation-date"), value: "created" }, - { text: $_("date"), value: "date" }, - { text: $_("difficulty"), value: "difficulty" }, + { text: $_("name"), value: "name" }, { text: $_("distance"), value: "distance" }, + { text: $_("duration"), value: "duration" }, + { text: $_("difficulty"), value: "difficulty" }, { text: $_("elevation-gain"), value: "elevation_gain" }, { text: $_("elevation-loss"), value: "elevation_loss" }, + { text: $_("creation-date"), value: "created" }, + { text: $_("date"), value: "date" }, ]; onMount(() => { @@ -81,6 +84,19 @@ localStorage.setItem("sort_order", filter.sortOrder); dispatch("update", filter); } + + function handleSortUpdate(sort: any) { + if (!filter) { + return; + } + if (sort.detail === filter.sort) { + setSortOrder(); + } else { + filter.sort = sort.detail; + filter.sortOrder = "+"; + setSort(); + } + }
@@ -94,21 +110,23 @@
{#if filter}
-

{$_("sort")}

-
- - -
+ {#if selectedDisplayOption !== "table"} +

{$_("sort")}

+
+ + +
+ {/if}
{/if}
@@ -124,35 +142,51 @@
{#if loading} - {#each { length: 12 } as _, index} - {#if selectedDisplayOption === "cards"} + {#if selectedDisplayOption === "table"} + + {:else} + {#each { length: 12 } as _, index} + {#if selectedDisplayOption === "cards"}
- {:else} - - {/if} - {/each} + {:else if selectedDisplayOption === "list"} + + {/if} + {/each} + {/if} {:else} {#if trails.length == 0}
{/if} - {#each trails as trail} - - {#if selectedDisplayOption === "cards"} - - {:else} - - {/if} - - {/each} + {#if selectedDisplayOption === "table"} + option.value !== "elevation_loss", + )} + {filter} + on:sort={(sort) => handleSortUpdate(sort)} + > + {:else} + {#each trails as trail} + + {#if selectedDisplayOption === "cards"} + + {:else} + + {/if} + + {/each} + {/if} {/if}
+ import type { Trail, TrailFilter } from "$lib/models/trail"; + import { + formatDistance, + formatElevation, + formatTimeHHMM, + } from "$lib/util/format_util"; + import { _ } from "svelte-i18n"; + import type { SelectItem } from "../base/select.svelte"; + import { goto } from "$app/navigation"; + import { createEventDispatcher } from "svelte"; + import { getFileURL } from "$lib/util/file_util"; + import ShareInfo from "../share_info.svelte"; + + export let tableHeader: SelectItem[]; + export let trails: Trail[] | null = null; + export let filter: TrailFilter | null = null; + + let dispatch = createEventDispatcher(); + + function getColumnWidth(columnValue: string): string { + switch (columnValue) { + case "name": + return "w-[25%]"; + case "distance": + return "w-[15%]"; + case "duration": + case "difficulty": + return "w-[11%]"; + case "elevation_gain": + case "created": + case "date": + return "w-[5%]"; + default: + return ""; + } + } + + +
+ + + + {#each tableHeader as column} + + {/each} + + + + {#if trails} + {#each trails as trail} + goto(`/trail/view/${trail.id}`)} + > + + + + + + + + + {/each} + {:else} + {#each { length: 12 } as _, index} + + {#each { length: tableHeader.length } as _, index} + + {/each} + + {/each} + {/if} + +
dispatch("sort", column.value)} + > +
+ {column.text} + {#if filter && filter.sort === column.value} + + {/if} +
+
+
+ {trail.name} +
+
+ {#if trail.expand && trail.expand.author} +
+ avatar +
+ {/if} +
+ {#if trail.public} +
+ +
+ {/if} + {#if trail.expand?.trail_share_via_trail?.length} + + {/if} +
+
+
+ {formatDistance(trail.distance)} + + {formatTimeHHMM(trail.duration)} + + {trail.difficulty} + + {formatElevation(trail.elevation_gain)} + + {#if trail.created} + {new Date(trail.created).toLocaleDateString( + undefined, + { + month: "2-digit", + day: "2-digit", + year: "numeric", + timeZone: "UTC", + }, + )} + {/if} + + {#if trail.date} + {new Date(trail.date).toLocaleDateString( + undefined, + { + month: "2-digit", + day: "2-digit", + year: "numeric", + timeZone: "UTC", + }, + )} + {/if} +
+
+ + diff --git a/web/src/lib/i18n/locales/de.json b/web/src/lib/i18n/locales/de.json index 9d83a43f..379d2382 100644 --- a/web/src/lib/i18n/locales/de.json +++ b/web/src/lib/i18n/locales/de.json @@ -213,6 +213,7 @@ "speed": "Geschwindigkeit", "stop-drawing": "Zeichnen beenden", "summit-book": "Gipfelbuch", + "table": "Tabelle", "text": "Text", "tilesets": "Tilesets", "trail": "{n, plural, =1 {Route} other {Routen}}", diff --git a/web/src/lib/i18n/locales/en.json b/web/src/lib/i18n/locales/en.json index f123e1ac..5cba1568 100644 --- a/web/src/lib/i18n/locales/en.json +++ b/web/src/lib/i18n/locales/en.json @@ -213,6 +213,7 @@ "speed": "Speed", "stop-drawing": "Stop drawing", "summit-book": "Summit Book", + "table": "Table", "text": "Text", "tilesets": "Custom tilesets", "trail": "{n, plural, =1 {Trail} other {Trails}}", diff --git a/web/src/lib/i18n/locales/fr.json b/web/src/lib/i18n/locales/fr.json index d46be9c5..990be316 100644 --- a/web/src/lib/i18n/locales/fr.json +++ b/web/src/lib/i18n/locales/fr.json @@ -213,6 +213,7 @@ "speed": "", "stop-drawing": "", "summit-book": "Livre du sommet", + "table": "Tableau", "text": "Texte", "tilesets": "", "trail": "{n, plural, =1 {Itinéraire} other {Itinéraires}}", diff --git a/web/src/lib/i18n/locales/hu.json b/web/src/lib/i18n/locales/hu.json index b8566547..6f8b0345 100644 --- a/web/src/lib/i18n/locales/hu.json +++ b/web/src/lib/i18n/locales/hu.json @@ -213,6 +213,7 @@ "speed": "", "stop-drawing": "", "summit-book": "Csúcspont könyv", + "table": "Táblázat", "text": "Szöveg", "tilesets": "", "trail": "{n, plural, =1 {Útvonal} other {Útvonalak}}", diff --git a/web/src/lib/i18n/locales/it.json b/web/src/lib/i18n/locales/it.json index 99c00365..8c0b7ec9 100644 --- a/web/src/lib/i18n/locales/it.json +++ b/web/src/lib/i18n/locales/it.json @@ -213,6 +213,7 @@ "speed": "Velocità", "stop-drawing": "Smettere di disegnare", "summit-book": "Libro di vetta", + "table": "Tavolo", "text": "Testo", "tilesets": "", "trail": "{n, plural, =1 {Percorso} other {Percorsi}}", diff --git a/web/src/lib/i18n/locales/nl.json b/web/src/lib/i18n/locales/nl.json index 7c36eb0c..a998dcd2 100644 --- a/web/src/lib/i18n/locales/nl.json +++ b/web/src/lib/i18n/locales/nl.json @@ -213,6 +213,7 @@ "speed": "", "stop-drawing": "", "summit-book": "Bergtopboek", + "table": "Tabel", "text": "Tekst", "tilesets": "", "trail": "{n, plural, =1 {Wandelroute} other {Wandelroutes}}", diff --git a/web/src/lib/i18n/locales/pl.json b/web/src/lib/i18n/locales/pl.json index 18341fce..4692e8a3 100644 --- a/web/src/lib/i18n/locales/pl.json +++ b/web/src/lib/i18n/locales/pl.json @@ -213,6 +213,7 @@ "speed": "", "stop-drawing": "", "summit-book": "Logbook", + "table": "Tabela", "text": "Tekst", "tilesets": "", "trail": "{n, plural, =1 {Szlak} other {Szlaki}}", diff --git a/web/src/lib/i18n/locales/pt.json b/web/src/lib/i18n/locales/pt.json index 6a571ce4..a5952fed 100644 --- a/web/src/lib/i18n/locales/pt.json +++ b/web/src/lib/i18n/locales/pt.json @@ -213,6 +213,7 @@ "speed": "", "stop-drawing": "", "summit-book": "Livro da cimeira", + "table": "Tabela", "text": "Texto", "tilesets": "", "trail": "{n, plural, =1 {Percurso} other {Percursos}}", diff --git a/web/src/lib/i18n/locales/zh.json b/web/src/lib/i18n/locales/zh.json index 0e1641b0..5437d725 100644 --- a/web/src/lib/i18n/locales/zh.json +++ b/web/src/lib/i18n/locales/zh.json @@ -213,6 +213,7 @@ "speed": "", "stop-drawing": "", "summit-book": "详细日程", + "table": "表格", "text": "文本", "tilesets": "", "trail": "{n, plural, =1 {路线} other {路线}}",