From e315c4d58a5a3ffe83864e959c32e1108808279b Mon Sep 17 00:00:00 2001 From: Christian Beutel <> Date: Sat, 14 Dec 2024 17:42:00 +0100 Subject: [PATCH] adds activity timeline --- .../1734186355_created_activities.go | 182 ++++++ .../1734187284_updated_activities.go | 503 +++++++++++++++++ .../1734191497_updated_activities.go | 531 ++++++++++++++++++ web/src/lib/components/base/tabs.svelte | 4 +- .../components/profile/activity_card.svelte | 93 +++ .../{profile => settings}/email_modal.svelte | 0 .../password_modal.svelte | 0 .../components/trail/trail_info_panel.svelte | 2 +- .../lib/components/trail/trail_table.svelte | 2 +- web/src/lib/models/activity.ts | 17 + web/src/lib/stores/activity_store.ts | 27 + web/src/routes/api/v1/activity/+server.ts | 18 + web/src/routes/lists/+page.svelte | 2 +- web/src/routes/profile/[id]/+layout.server.ts | 19 +- web/src/routes/profile/[id]/+page.svelte | 47 +- web/src/routes/profile/[id]/+page.ts | 4 +- .../routes/profile/[id]/trails/+page.svelte | 5 +- web/src/routes/settings/account/+page.svelte | 4 +- web/src/routes/trail/view/[id]/+page.svelte | 7 +- 19 files changed, 1452 insertions(+), 15 deletions(-) create mode 100644 db/migrations/1734186355_created_activities.go create mode 100644 db/migrations/1734187284_updated_activities.go create mode 100644 db/migrations/1734191497_updated_activities.go create mode 100644 web/src/lib/components/profile/activity_card.svelte rename web/src/lib/components/{profile => settings}/email_modal.svelte (100%) rename web/src/lib/components/{profile => settings}/password_modal.svelte (100%) create mode 100644 web/src/lib/models/activity.ts create mode 100644 web/src/lib/stores/activity_store.ts create mode 100644 web/src/routes/api/v1/activity/+server.ts diff --git a/db/migrations/1734186355_created_activities.go b/db/migrations/1734186355_created_activities.go new file mode 100644 index 00000000..f0851bea --- /dev/null +++ b/db/migrations/1734186355_created_activities.go @@ -0,0 +1,182 @@ +package migrations + +import ( + "encoding/json" + + "github.com/pocketbase/dbx" + "github.com/pocketbase/pocketbase/daos" + m "github.com/pocketbase/pocketbase/migrations" + "github.com/pocketbase/pocketbase/models" +) + +func init() { + m.Register(func(db dbx.Builder) error { + jsonData := `{ + "id": "t9lphichi5xwyeu", + "created": "2024-12-14 14:25:55.563Z", + "updated": "2024-12-14 14:25:55.563Z", + "name": "activities", + "type": "view", + "system": false, + "schema": [ + { + "system": false, + "id": "zegqj8bt", + "name": "date", + "type": "json", + "required": false, + "presentable": false, + "unique": false, + "options": { + "maxSize": 1 + } + }, + { + "system": false, + "id": "mztv5od5", + "name": "name", + "type": "json", + "required": false, + "presentable": false, + "unique": false, + "options": { + "maxSize": 1 + } + }, + { + "system": false, + "id": "4hq14mc5", + "name": "description", + "type": "json", + "required": false, + "presentable": false, + "unique": false, + "options": { + "maxSize": 1 + } + }, + { + "system": false, + "id": "hbyuxehw", + "name": "gpx", + "type": "json", + "required": false, + "presentable": false, + "unique": false, + "options": { + "maxSize": 1 + } + }, + { + "system": false, + "id": "syp8ya96", + "name": "author", + "type": "json", + "required": false, + "presentable": false, + "unique": false, + "options": { + "maxSize": 1 + } + }, + { + "system": false, + "id": "rrfp5omm", + "name": "photos", + "type": "json", + "required": false, + "presentable": false, + "unique": false, + "options": { + "maxSize": 1 + } + }, + { + "system": false, + "id": "tacveduk", + "name": "distance", + "type": "json", + "required": false, + "presentable": false, + "unique": false, + "options": { + "maxSize": 1 + } + }, + { + "system": false, + "id": "ydhldat1", + "name": "duration", + "type": "json", + "required": false, + "presentable": false, + "unique": false, + "options": { + "maxSize": 1 + } + }, + { + "system": false, + "id": "9gg24ge8", + "name": "elevation_gain", + "type": "json", + "required": false, + "presentable": false, + "unique": false, + "options": { + "maxSize": 1 + } + }, + { + "system": false, + "id": "ujsn6lqc", + "name": "elevation_loss", + "type": "json", + "required": false, + "presentable": false, + "unique": false, + "options": { + "maxSize": 1 + } + }, + { + "system": false, + "id": "nvvrulvj", + "name": "type", + "type": "json", + "required": false, + "presentable": false, + "unique": false, + "options": { + "maxSize": 1 + } + } + ], + "indexes": [], + "listRule": null, + "viewRule": null, + "createRule": null, + "updateRule": null, + "deleteRule": null, + "options": { + "query": "SELECT id,date,name,description,gpx,author,photos,distance,duration,elevation_gain,elevation_loss, type \nFROM (\n SELECT summit_logs.id,summit_logs.date,trails.name,text as description,summit_logs.gpx,summit_logs.author,summit_logs.photos,summit_logs.distance,summit_logs.duration,summit_logs.elevation_gain,summit_logs.elevation_loss,\"summit_log\" as type \n FROM summit_logs\n LEFT JOIN trails ON summit_logs.id IN (\n SELECT value\n FROM json_each(trails.summit_logs)\n )\n UNION\n SELECT id,date,name,description,gpx,author,photos,distance,duration,elevation_gain,elevation_loss, \"trail\" as type \n FROM trails\n) ORDER BY date DESC" + } + }` + + collection := &models.Collection{} + if err := json.Unmarshal([]byte(jsonData), &collection); err != nil { + return err + } + + return daos.New(db).SaveCollection(collection) + }, func(db dbx.Builder) error { + dao := daos.New(db); + + collection, err := dao.FindCollectionByNameOrId("t9lphichi5xwyeu") + if err != nil { + return err + } + + return dao.DeleteCollection(collection) + }) +} diff --git a/db/migrations/1734187284_updated_activities.go b/db/migrations/1734187284_updated_activities.go new file mode 100644 index 00000000..b2db73a0 --- /dev/null +++ b/db/migrations/1734187284_updated_activities.go @@ -0,0 +1,503 @@ +package migrations + +import ( + "encoding/json" + + "github.com/pocketbase/dbx" + "github.com/pocketbase/pocketbase/daos" + m "github.com/pocketbase/pocketbase/migrations" + "github.com/pocketbase/pocketbase/models/schema" + "github.com/pocketbase/pocketbase/tools/types" +) + +func init() { + m.Register(func(db dbx.Builder) error { + dao := daos.New(db); + + collection, err := dao.FindCollectionByNameOrId("t9lphichi5xwyeu") + if err != nil { + return err + } + + collection.ListRule = types.Pointer("") + + collection.ViewRule = types.Pointer("") + + // remove + collection.Schema.RemoveField("zegqj8bt") + + // remove + collection.Schema.RemoveField("mztv5od5") + + // remove + collection.Schema.RemoveField("4hq14mc5") + + // remove + collection.Schema.RemoveField("hbyuxehw") + + // remove + collection.Schema.RemoveField("syp8ya96") + + // remove + collection.Schema.RemoveField("rrfp5omm") + + // remove + collection.Schema.RemoveField("tacveduk") + + // remove + collection.Schema.RemoveField("ydhldat1") + + // remove + collection.Schema.RemoveField("9gg24ge8") + + // remove + collection.Schema.RemoveField("ujsn6lqc") + + // remove + collection.Schema.RemoveField("nvvrulvj") + + // add + new_date := &schema.SchemaField{} + if err := json.Unmarshal([]byte(`{ + "system": false, + "id": "qejtfjom", + "name": "date", + "type": "json", + "required": false, + "presentable": false, + "unique": false, + "options": { + "maxSize": 1 + } + }`), new_date); err != nil { + return err + } + collection.Schema.AddField(new_date) + + // add + new_name := &schema.SchemaField{} + if err := json.Unmarshal([]byte(`{ + "system": false, + "id": "nskezkjx", + "name": "name", + "type": "json", + "required": false, + "presentable": false, + "unique": false, + "options": { + "maxSize": 1 + } + }`), new_name); err != nil { + return err + } + collection.Schema.AddField(new_name) + + // add + new_description := &schema.SchemaField{} + if err := json.Unmarshal([]byte(`{ + "system": false, + "id": "viedej4h", + "name": "description", + "type": "json", + "required": false, + "presentable": false, + "unique": false, + "options": { + "maxSize": 1 + } + }`), new_description); err != nil { + return err + } + collection.Schema.AddField(new_description) + + // add + new_gpx := &schema.SchemaField{} + if err := json.Unmarshal([]byte(`{ + "system": false, + "id": "vljhhvdy", + "name": "gpx", + "type": "json", + "required": false, + "presentable": false, + "unique": false, + "options": { + "maxSize": 1 + } + }`), new_gpx); err != nil { + return err + } + collection.Schema.AddField(new_gpx) + + // add + new_author := &schema.SchemaField{} + if err := json.Unmarshal([]byte(`{ + "system": false, + "id": "z1uaeqzu", + "name": "author", + "type": "json", + "required": false, + "presentable": false, + "unique": false, + "options": { + "maxSize": 1 + } + }`), new_author); err != nil { + return err + } + collection.Schema.AddField(new_author) + + // add + new_photos := &schema.SchemaField{} + if err := json.Unmarshal([]byte(`{ + "system": false, + "id": "9nnpyhlq", + "name": "photos", + "type": "json", + "required": false, + "presentable": false, + "unique": false, + "options": { + "maxSize": 1 + } + }`), new_photos); err != nil { + return err + } + collection.Schema.AddField(new_photos) + + // add + new_distance := &schema.SchemaField{} + if err := json.Unmarshal([]byte(`{ + "system": false, + "id": "7kzcclzu", + "name": "distance", + "type": "json", + "required": false, + "presentable": false, + "unique": false, + "options": { + "maxSize": 1 + } + }`), new_distance); err != nil { + return err + } + collection.Schema.AddField(new_distance) + + // add + new_duration := &schema.SchemaField{} + if err := json.Unmarshal([]byte(`{ + "system": false, + "id": "gwpsktbu", + "name": "duration", + "type": "json", + "required": false, + "presentable": false, + "unique": false, + "options": { + "maxSize": 1 + } + }`), new_duration); err != nil { + return err + } + collection.Schema.AddField(new_duration) + + // add + new_elevation_gain := &schema.SchemaField{} + if err := json.Unmarshal([]byte(`{ + "system": false, + "id": "5tze9ybp", + "name": "elevation_gain", + "type": "json", + "required": false, + "presentable": false, + "unique": false, + "options": { + "maxSize": 1 + } + }`), new_elevation_gain); err != nil { + return err + } + collection.Schema.AddField(new_elevation_gain) + + // add + new_elevation_loss := &schema.SchemaField{} + if err := json.Unmarshal([]byte(`{ + "system": false, + "id": "xkuz8ixp", + "name": "elevation_loss", + "type": "json", + "required": false, + "presentable": false, + "unique": false, + "options": { + "maxSize": 1 + } + }`), new_elevation_loss); err != nil { + return err + } + collection.Schema.AddField(new_elevation_loss) + + // add + new_type := &schema.SchemaField{} + if err := json.Unmarshal([]byte(`{ + "system": false, + "id": "soylsusn", + "name": "type", + "type": "json", + "required": false, + "presentable": false, + "unique": false, + "options": { + "maxSize": 1 + } + }`), new_type); err != nil { + return err + } + collection.Schema.AddField(new_type) + + return dao.SaveCollection(collection) + }, func(db dbx.Builder) error { + dao := daos.New(db); + + collection, err := dao.FindCollectionByNameOrId("t9lphichi5xwyeu") + if err != nil { + return err + } + + collection.ListRule = nil + + collection.ViewRule = nil + + // add + del_date := &schema.SchemaField{} + if err := json.Unmarshal([]byte(`{ + "system": false, + "id": "zegqj8bt", + "name": "date", + "type": "json", + "required": false, + "presentable": false, + "unique": false, + "options": { + "maxSize": 1 + } + }`), del_date); err != nil { + return err + } + collection.Schema.AddField(del_date) + + // add + del_name := &schema.SchemaField{} + if err := json.Unmarshal([]byte(`{ + "system": false, + "id": "mztv5od5", + "name": "name", + "type": "json", + "required": false, + "presentable": false, + "unique": false, + "options": { + "maxSize": 1 + } + }`), del_name); err != nil { + return err + } + collection.Schema.AddField(del_name) + + // add + del_description := &schema.SchemaField{} + if err := json.Unmarshal([]byte(`{ + "system": false, + "id": "4hq14mc5", + "name": "description", + "type": "json", + "required": false, + "presentable": false, + "unique": false, + "options": { + "maxSize": 1 + } + }`), del_description); err != nil { + return err + } + collection.Schema.AddField(del_description) + + // add + del_gpx := &schema.SchemaField{} + if err := json.Unmarshal([]byte(`{ + "system": false, + "id": "hbyuxehw", + "name": "gpx", + "type": "json", + "required": false, + "presentable": false, + "unique": false, + "options": { + "maxSize": 1 + } + }`), del_gpx); err != nil { + return err + } + collection.Schema.AddField(del_gpx) + + // add + del_author := &schema.SchemaField{} + if err := json.Unmarshal([]byte(`{ + "system": false, + "id": "syp8ya96", + "name": "author", + "type": "json", + "required": false, + "presentable": false, + "unique": false, + "options": { + "maxSize": 1 + } + }`), del_author); err != nil { + return err + } + collection.Schema.AddField(del_author) + + // add + del_photos := &schema.SchemaField{} + if err := json.Unmarshal([]byte(`{ + "system": false, + "id": "rrfp5omm", + "name": "photos", + "type": "json", + "required": false, + "presentable": false, + "unique": false, + "options": { + "maxSize": 1 + } + }`), del_photos); err != nil { + return err + } + collection.Schema.AddField(del_photos) + + // add + del_distance := &schema.SchemaField{} + if err := json.Unmarshal([]byte(`{ + "system": false, + "id": "tacveduk", + "name": "distance", + "type": "json", + "required": false, + "presentable": false, + "unique": false, + "options": { + "maxSize": 1 + } + }`), del_distance); err != nil { + return err + } + collection.Schema.AddField(del_distance) + + // add + del_duration := &schema.SchemaField{} + if err := json.Unmarshal([]byte(`{ + "system": false, + "id": "ydhldat1", + "name": "duration", + "type": "json", + "required": false, + "presentable": false, + "unique": false, + "options": { + "maxSize": 1 + } + }`), del_duration); err != nil { + return err + } + collection.Schema.AddField(del_duration) + + // add + del_elevation_gain := &schema.SchemaField{} + if err := json.Unmarshal([]byte(`{ + "system": false, + "id": "9gg24ge8", + "name": "elevation_gain", + "type": "json", + "required": false, + "presentable": false, + "unique": false, + "options": { + "maxSize": 1 + } + }`), del_elevation_gain); err != nil { + return err + } + collection.Schema.AddField(del_elevation_gain) + + // add + del_elevation_loss := &schema.SchemaField{} + if err := json.Unmarshal([]byte(`{ + "system": false, + "id": "ujsn6lqc", + "name": "elevation_loss", + "type": "json", + "required": false, + "presentable": false, + "unique": false, + "options": { + "maxSize": 1 + } + }`), del_elevation_loss); err != nil { + return err + } + collection.Schema.AddField(del_elevation_loss) + + // add + del_type := &schema.SchemaField{} + if err := json.Unmarshal([]byte(`{ + "system": false, + "id": "nvvrulvj", + "name": "type", + "type": "json", + "required": false, + "presentable": false, + "unique": false, + "options": { + "maxSize": 1 + } + }`), del_type); err != nil { + return err + } + collection.Schema.AddField(del_type) + + // remove + collection.Schema.RemoveField("qejtfjom") + + // remove + collection.Schema.RemoveField("nskezkjx") + + // remove + collection.Schema.RemoveField("viedej4h") + + // remove + collection.Schema.RemoveField("vljhhvdy") + + // remove + collection.Schema.RemoveField("z1uaeqzu") + + // remove + collection.Schema.RemoveField("9nnpyhlq") + + // remove + collection.Schema.RemoveField("7kzcclzu") + + // remove + collection.Schema.RemoveField("gwpsktbu") + + // remove + collection.Schema.RemoveField("5tze9ybp") + + // remove + collection.Schema.RemoveField("xkuz8ixp") + + // remove + collection.Schema.RemoveField("soylsusn") + + return dao.SaveCollection(collection) + }) +} diff --git a/db/migrations/1734191497_updated_activities.go b/db/migrations/1734191497_updated_activities.go new file mode 100644 index 00000000..086add57 --- /dev/null +++ b/db/migrations/1734191497_updated_activities.go @@ -0,0 +1,531 @@ +package migrations + +import ( + "encoding/json" + + "github.com/pocketbase/dbx" + "github.com/pocketbase/pocketbase/daos" + m "github.com/pocketbase/pocketbase/migrations" + "github.com/pocketbase/pocketbase/models/schema" +) + +func init() { + m.Register(func(db dbx.Builder) error { + dao := daos.New(db); + + collection, err := dao.FindCollectionByNameOrId("t9lphichi5xwyeu") + if err != nil { + return err + } + + options := map[string]any{} + if err := json.Unmarshal([]byte(`{ + "query": "SELECT id,trail_id,date,name,description,gpx,author,photos,distance,duration,elevation_gain,elevation_loss, type \nFROM (\n SELECT summit_logs.id,trails.id as trail_id,summit_logs.date,trails.name,text as description,summit_logs.gpx,summit_logs.author,summit_logs.photos,summit_logs.distance,summit_logs.duration,summit_logs.elevation_gain,summit_logs.elevation_loss,\"summit_log\" as type \n FROM summit_logs\n LEFT JOIN trails ON summit_logs.id IN (\n SELECT value\n FROM json_each(trails.summit_logs)\n )\n UNION\n SELECT id,id as trail_id,date,name,description,gpx,author,photos,distance,duration,elevation_gain,elevation_loss, \"trail\" as type \n FROM trails\n) ORDER BY date DESC" + }`), &options); err != nil { + return err + } + collection.SetOptions(options) + + // remove + collection.Schema.RemoveField("qejtfjom") + + // remove + collection.Schema.RemoveField("nskezkjx") + + // remove + collection.Schema.RemoveField("viedej4h") + + // remove + collection.Schema.RemoveField("vljhhvdy") + + // remove + collection.Schema.RemoveField("z1uaeqzu") + + // remove + collection.Schema.RemoveField("9nnpyhlq") + + // remove + collection.Schema.RemoveField("7kzcclzu") + + // remove + collection.Schema.RemoveField("gwpsktbu") + + // remove + collection.Schema.RemoveField("5tze9ybp") + + // remove + collection.Schema.RemoveField("xkuz8ixp") + + // remove + collection.Schema.RemoveField("soylsusn") + + // add + new_trail_id := &schema.SchemaField{} + if err := json.Unmarshal([]byte(`{ + "system": false, + "id": "xbcmb4ax", + "name": "trail_id", + "type": "json", + "required": false, + "presentable": false, + "unique": false, + "options": { + "maxSize": 1 + } + }`), new_trail_id); err != nil { + return err + } + collection.Schema.AddField(new_trail_id) + + // add + new_date := &schema.SchemaField{} + if err := json.Unmarshal([]byte(`{ + "system": false, + "id": "elenpjfu", + "name": "date", + "type": "json", + "required": false, + "presentable": false, + "unique": false, + "options": { + "maxSize": 1 + } + }`), new_date); err != nil { + return err + } + collection.Schema.AddField(new_date) + + // add + new_name := &schema.SchemaField{} + if err := json.Unmarshal([]byte(`{ + "system": false, + "id": "gvcm04wv", + "name": "name", + "type": "json", + "required": false, + "presentable": false, + "unique": false, + "options": { + "maxSize": 1 + } + }`), new_name); err != nil { + return err + } + collection.Schema.AddField(new_name) + + // add + new_description := &schema.SchemaField{} + if err := json.Unmarshal([]byte(`{ + "system": false, + "id": "lzg9ifgi", + "name": "description", + "type": "json", + "required": false, + "presentable": false, + "unique": false, + "options": { + "maxSize": 1 + } + }`), new_description); err != nil { + return err + } + collection.Schema.AddField(new_description) + + // add + new_gpx := &schema.SchemaField{} + if err := json.Unmarshal([]byte(`{ + "system": false, + "id": "rzqpztsp", + "name": "gpx", + "type": "json", + "required": false, + "presentable": false, + "unique": false, + "options": { + "maxSize": 1 + } + }`), new_gpx); err != nil { + return err + } + collection.Schema.AddField(new_gpx) + + // add + new_author := &schema.SchemaField{} + if err := json.Unmarshal([]byte(`{ + "system": false, + "id": "oqzupzz2", + "name": "author", + "type": "json", + "required": false, + "presentable": false, + "unique": false, + "options": { + "maxSize": 1 + } + }`), new_author); err != nil { + return err + } + collection.Schema.AddField(new_author) + + // add + new_photos := &schema.SchemaField{} + if err := json.Unmarshal([]byte(`{ + "system": false, + "id": "xmzmcgjm", + "name": "photos", + "type": "json", + "required": false, + "presentable": false, + "unique": false, + "options": { + "maxSize": 1 + } + }`), new_photos); err != nil { + return err + } + collection.Schema.AddField(new_photos) + + // add + new_distance := &schema.SchemaField{} + if err := json.Unmarshal([]byte(`{ + "system": false, + "id": "rslhnvby", + "name": "distance", + "type": "json", + "required": false, + "presentable": false, + "unique": false, + "options": { + "maxSize": 1 + } + }`), new_distance); err != nil { + return err + } + collection.Schema.AddField(new_distance) + + // add + new_duration := &schema.SchemaField{} + if err := json.Unmarshal([]byte(`{ + "system": false, + "id": "xgfy26z9", + "name": "duration", + "type": "json", + "required": false, + "presentable": false, + "unique": false, + "options": { + "maxSize": 1 + } + }`), new_duration); err != nil { + return err + } + collection.Schema.AddField(new_duration) + + // add + new_elevation_gain := &schema.SchemaField{} + if err := json.Unmarshal([]byte(`{ + "system": false, + "id": "iuhfh4vo", + "name": "elevation_gain", + "type": "json", + "required": false, + "presentable": false, + "unique": false, + "options": { + "maxSize": 1 + } + }`), new_elevation_gain); err != nil { + return err + } + collection.Schema.AddField(new_elevation_gain) + + // add + new_elevation_loss := &schema.SchemaField{} + if err := json.Unmarshal([]byte(`{ + "system": false, + "id": "hoauojgi", + "name": "elevation_loss", + "type": "json", + "required": false, + "presentable": false, + "unique": false, + "options": { + "maxSize": 1 + } + }`), new_elevation_loss); err != nil { + return err + } + collection.Schema.AddField(new_elevation_loss) + + // add + new_type := &schema.SchemaField{} + if err := json.Unmarshal([]byte(`{ + "system": false, + "id": "7b25xfwx", + "name": "type", + "type": "json", + "required": false, + "presentable": false, + "unique": false, + "options": { + "maxSize": 1 + } + }`), new_type); err != nil { + return err + } + collection.Schema.AddField(new_type) + + return dao.SaveCollection(collection) + }, func(db dbx.Builder) error { + dao := daos.New(db); + + collection, err := dao.FindCollectionByNameOrId("t9lphichi5xwyeu") + if err != nil { + return err + } + + options := map[string]any{} + if err := json.Unmarshal([]byte(`{ + "query": "SELECT id,date,name,description,gpx,author,photos,distance,duration,elevation_gain,elevation_loss, type \nFROM (\n SELECT summit_logs.id,summit_logs.date,trails.name,text as description,summit_logs.gpx,summit_logs.author,summit_logs.photos,summit_logs.distance,summit_logs.duration,summit_logs.elevation_gain,summit_logs.elevation_loss,\"summit_log\" as type \n FROM summit_logs\n LEFT JOIN trails ON summit_logs.id IN (\n SELECT value\n FROM json_each(trails.summit_logs)\n )\n UNION\n SELECT id,date,name,description,gpx,author,photos,distance,duration,elevation_gain,elevation_loss, \"trail\" as type \n FROM trails\n) ORDER BY date DESC" + }`), &options); err != nil { + return err + } + collection.SetOptions(options) + + // add + del_date := &schema.SchemaField{} + if err := json.Unmarshal([]byte(`{ + "system": false, + "id": "qejtfjom", + "name": "date", + "type": "json", + "required": false, + "presentable": false, + "unique": false, + "options": { + "maxSize": 1 + } + }`), del_date); err != nil { + return err + } + collection.Schema.AddField(del_date) + + // add + del_name := &schema.SchemaField{} + if err := json.Unmarshal([]byte(`{ + "system": false, + "id": "nskezkjx", + "name": "name", + "type": "json", + "required": false, + "presentable": false, + "unique": false, + "options": { + "maxSize": 1 + } + }`), del_name); err != nil { + return err + } + collection.Schema.AddField(del_name) + + // add + del_description := &schema.SchemaField{} + if err := json.Unmarshal([]byte(`{ + "system": false, + "id": "viedej4h", + "name": "description", + "type": "json", + "required": false, + "presentable": false, + "unique": false, + "options": { + "maxSize": 1 + } + }`), del_description); err != nil { + return err + } + collection.Schema.AddField(del_description) + + // add + del_gpx := &schema.SchemaField{} + if err := json.Unmarshal([]byte(`{ + "system": false, + "id": "vljhhvdy", + "name": "gpx", + "type": "json", + "required": false, + "presentable": false, + "unique": false, + "options": { + "maxSize": 1 + } + }`), del_gpx); err != nil { + return err + } + collection.Schema.AddField(del_gpx) + + // add + del_author := &schema.SchemaField{} + if err := json.Unmarshal([]byte(`{ + "system": false, + "id": "z1uaeqzu", + "name": "author", + "type": "json", + "required": false, + "presentable": false, + "unique": false, + "options": { + "maxSize": 1 + } + }`), del_author); err != nil { + return err + } + collection.Schema.AddField(del_author) + + // add + del_photos := &schema.SchemaField{} + if err := json.Unmarshal([]byte(`{ + "system": false, + "id": "9nnpyhlq", + "name": "photos", + "type": "json", + "required": false, + "presentable": false, + "unique": false, + "options": { + "maxSize": 1 + } + }`), del_photos); err != nil { + return err + } + collection.Schema.AddField(del_photos) + + // add + del_distance := &schema.SchemaField{} + if err := json.Unmarshal([]byte(`{ + "system": false, + "id": "7kzcclzu", + "name": "distance", + "type": "json", + "required": false, + "presentable": false, + "unique": false, + "options": { + "maxSize": 1 + } + }`), del_distance); err != nil { + return err + } + collection.Schema.AddField(del_distance) + + // add + del_duration := &schema.SchemaField{} + if err := json.Unmarshal([]byte(`{ + "system": false, + "id": "gwpsktbu", + "name": "duration", + "type": "json", + "required": false, + "presentable": false, + "unique": false, + "options": { + "maxSize": 1 + } + }`), del_duration); err != nil { + return err + } + collection.Schema.AddField(del_duration) + + // add + del_elevation_gain := &schema.SchemaField{} + if err := json.Unmarshal([]byte(`{ + "system": false, + "id": "5tze9ybp", + "name": "elevation_gain", + "type": "json", + "required": false, + "presentable": false, + "unique": false, + "options": { + "maxSize": 1 + } + }`), del_elevation_gain); err != nil { + return err + } + collection.Schema.AddField(del_elevation_gain) + + // add + del_elevation_loss := &schema.SchemaField{} + if err := json.Unmarshal([]byte(`{ + "system": false, + "id": "xkuz8ixp", + "name": "elevation_loss", + "type": "json", + "required": false, + "presentable": false, + "unique": false, + "options": { + "maxSize": 1 + } + }`), del_elevation_loss); err != nil { + return err + } + collection.Schema.AddField(del_elevation_loss) + + // add + del_type := &schema.SchemaField{} + if err := json.Unmarshal([]byte(`{ + "system": false, + "id": "soylsusn", + "name": "type", + "type": "json", + "required": false, + "presentable": false, + "unique": false, + "options": { + "maxSize": 1 + } + }`), del_type); err != nil { + return err + } + collection.Schema.AddField(del_type) + + // remove + collection.Schema.RemoveField("xbcmb4ax") + + // remove + collection.Schema.RemoveField("elenpjfu") + + // remove + collection.Schema.RemoveField("gvcm04wv") + + // remove + collection.Schema.RemoveField("lzg9ifgi") + + // remove + collection.Schema.RemoveField("rzqpztsp") + + // remove + collection.Schema.RemoveField("oqzupzz2") + + // remove + collection.Schema.RemoveField("xmzmcgjm") + + // remove + collection.Schema.RemoveField("rslhnvby") + + // remove + collection.Schema.RemoveField("xgfy26z9") + + // remove + collection.Schema.RemoveField("iuhfh4vo") + + // remove + collection.Schema.RemoveField("hoauojgi") + + // remove + collection.Schema.RemoveField("7b25xfwx") + + return dao.SaveCollection(collection) + }) +} diff --git a/web/src/lib/components/base/tabs.svelte b/web/src/lib/components/base/tabs.svelte index 27d4bbb4..b90cbd31 100644 --- a/web/src/lib/components/base/tabs.svelte +++ b/web/src/lib/components/base/tabs.svelte @@ -18,7 +18,7 @@ }); onMount(() => { - switchTabs(0); + switchTabs(activeTab ?? 0); }); function switchTabs(index: number) { @@ -26,7 +26,7 @@ const childElement = tabs?.children[index + 1] as HTMLElement; const newWidth = childElement?.getBoundingClientRect().width ?? 0; - const newPosition = childElement.offsetLeft; + const newPosition = childElement?.offsetLeft; indicatorWidth.set(newWidth); indicatorPosition.set(newPosition); diff --git a/web/src/lib/components/profile/activity_card.svelte b/web/src/lib/components/profile/activity_card.svelte new file mode 100644 index 00000000..5c0d2bd6 --- /dev/null +++ b/web/src/lib/components/profile/activity_card.svelte @@ -0,0 +1,93 @@ + + +
+
+ avatar +
+ {user.username} + {activity.type === "trail" ? "planned" : "completed"} + a trail +

+ {new Date(activity.date).toLocaleDateString(undefined, { + month: "long", + day: "2-digit", + year: "numeric", + timeZone: "UTC", + })} +

+
+
+

{activity.name}

+
+ {formatDistance( + activity.distance, + )} + {formatTimeHHMM( + activity.duration, + )} + {formatElevation( + activity.elevation_gain, + )} + {formatElevation( + activity.elevation_loss, + )} +
+ {#if activity.photos.length} +
+ {#each activity.photos as photo, i} + 2} + src={getFileURL( + { collectionId: activity.type + "s", id: activity.id }, + photo, + )} + alt="" + /> + {/each} +
+ {/if} + {#if activity.description.length} +

+ {!fullDescription + ? activity.description?.substring(0, 100) + : activity.description} + {#if (activity.description?.length ?? 0) > 100 && !fullDescription} + + {/if} +

+ {/if} +
diff --git a/web/src/lib/components/profile/email_modal.svelte b/web/src/lib/components/settings/email_modal.svelte similarity index 100% rename from web/src/lib/components/profile/email_modal.svelte rename to web/src/lib/components/settings/email_modal.svelte diff --git a/web/src/lib/components/profile/password_modal.svelte b/web/src/lib/components/settings/password_modal.svelte similarity index 100% rename from web/src/lib/components/profile/password_modal.svelte rename to web/src/lib/components/settings/password_modal.svelte diff --git a/web/src/lib/components/trail/trail_info_panel.svelte b/web/src/lib/components/trail/trail_info_panel.svelte index 4c487053..f1016426 100644 --- a/web/src/lib/components/trail/trail_info_panel.svelte +++ b/web/src/lib/components/trail/trail_info_panel.svelte @@ -37,6 +37,7 @@ export let trail: Trail; export let mode: "overview" | "map" | "list" = "map"; export let markers: M.Marker[] = []; + export let activeTab = 0; const tabs = [ $_("description"), @@ -49,7 +50,6 @@ const trailIsShared = (trail.expand?.trail_share_via_trail?.length ?? 0) > 0; - let activeTab = 0; let thumbnail = trail.photos.length ? getFileURL(trail, trail.photos[trail.thumbnail]) diff --git a/web/src/lib/components/trail/trail_table.svelte b/web/src/lib/components/trail/trail_table.svelte index 9d3937dd..d753c383 100644 --- a/web/src/lib/components/trail/trail_table.svelte +++ b/web/src/lib/components/trail/trail_table.svelte @@ -38,7 +38,7 @@
diff --git a/web/src/lib/models/activity.ts b/web/src/lib/models/activity.ts new file mode 100644 index 00000000..8791a1c6 --- /dev/null +++ b/web/src/lib/models/activity.ts @@ -0,0 +1,17 @@ +interface Activity { + id: string; + trail_id: string; + name: string, + description: string; + date: string; + gpx: string; + photos: string[]; + distance: number; + duration: number; + elevation_gain: number; + elevation_loss: number; + type: "trail" | "summit_log" + author: string; +} + +export { type Activity } \ No newline at end of file diff --git a/web/src/lib/stores/activity_store.ts b/web/src/lib/stores/activity_store.ts new file mode 100644 index 00000000..b51c79e4 --- /dev/null +++ b/web/src/lib/stores/activity_store.ts @@ -0,0 +1,27 @@ +import type { Activity } from "$lib/models/activity"; +import { ClientResponseError, type ListResult } from "pocketbase"; + +let activities: Activity[] = []; + +export async function activities_index(author: string, page: number = 1, perPage: number = 10, f: (url: RequestInfo | URL, config?: RequestInit) => Promise = fetch) { + const r = await f('/api/v1/activity?' + new URLSearchParams({ + "per-page": perPage.toString(), + page: page.toString(), + filter: `author="${author}"` + }), { + method: 'GET', + }) + + if (!r.ok) { + throw new ClientResponseError(await r.json()) + } + + const fetchedActivities: ListResult = await r.json(); + + const result = page > 1 ? [...activities, ...fetchedActivities.items] : fetchedActivities.items + + activities = result; + + return { ...fetchedActivities, items: result }; + +} \ No newline at end of file diff --git a/web/src/routes/api/v1/activity/+server.ts b/web/src/routes/api/v1/activity/+server.ts new file mode 100644 index 00000000..8412f200 --- /dev/null +++ b/web/src/routes/api/v1/activity/+server.ts @@ -0,0 +1,18 @@ +import type { Activity } from "$lib/models/activity"; +import { pb } from "$lib/pocketbase"; +import { error, json, type RequestEvent } from "@sveltejs/kit"; + +export async function GET(event: RequestEvent) { + const page = event.url.searchParams.get("page") ?? "0"; + const perPage = event.url.searchParams.get("per-page") ?? "10"; + const filter = event.url.searchParams.get("filter") ?? ""; + + try { + const r = await pb.collection('activities') + .getList(parseInt(page), parseInt(perPage), { filter: filter ?? "" }) + return json(r) + } catch (e: any) { + throw error(e.status, e); + } +} + diff --git a/web/src/routes/lists/+page.svelte b/web/src/routes/lists/+page.svelte index 293e1e27..151c378d 100644 --- a/web/src/routes/lists/+page.svelte +++ b/web/src/routes/lists/+page.svelte @@ -143,7 +143,7 @@ mapWithElevation.unHighlightTrail(trail.id!); } - async function onListScroll(e: any) { + async function onListScroll(e: Event) { const container = e.target as HTMLDivElement; const scrollTop = container.scrollTop; const scrollHeight = container.scrollHeight; diff --git a/web/src/routes/profile/[id]/+layout.server.ts b/web/src/routes/profile/[id]/+layout.server.ts index 1d174a56..4601e800 100644 --- a/web/src/routes/profile/[id]/+layout.server.ts +++ b/web/src/routes/profile/[id]/+layout.server.ts @@ -1,15 +1,26 @@ import { users_show } from "$lib/stores/user_store"; import { error, type ServerLoad } from "@sveltejs/kit"; +import { ClientResponseError } from "pocketbase"; export const load: ServerLoad = async ({ params, locals, fetch }) => { - if(!params.id) { + if (!params.id) { error(404, "Not found") } - const user = await users_show(params.id, fetch); + try { + const user = await users_show(params.id, fetch); - const isOwnProfile = params.id === locals.user?.id; + const isOwnProfile = params.id === locals.user?.id; + + return { user, isOwnProfile } + } catch (e) { + if (e instanceof ClientResponseError && e.status == 404) { + error(404, { + message: 'Not found' + }); + } + throw e + } - return { user, isOwnProfile } }; \ No newline at end of file diff --git a/web/src/routes/profile/[id]/+page.svelte b/web/src/routes/profile/[id]/+page.svelte index 84467109..6f2bf6d7 100644 --- a/web/src/routes/profile/[id]/+page.svelte +++ b/web/src/routes/profile/[id]/+page.svelte @@ -1,9 +1,43 @@ + + + + {$_("profile")} | wanderer + +

@@ -58,7 +92,18 @@ {/each}

-
+

Timeline

+ + {#each data.activities.items as activity} + + + + {/each}
diff --git a/web/src/routes/profile/[id]/+page.ts b/web/src/routes/profile/[id]/+page.ts index 0ccf257b..a0fcfd37 100644 --- a/web/src/routes/profile/[id]/+page.ts +++ b/web/src/routes/profile/[id]/+page.ts @@ -1,8 +1,10 @@ +import { activities_index } from "$lib/stores/activity_store"; import { lists_index } from "$lib/stores/list_store"; import { type Load } from "@sveltejs/kit"; export const load: Load = async ({ params, fetch }) => { const lists = await lists_index({ q: "", author: params.id, sort: "created", sortOrder: "-" }, 1, -1, fetch) - return { lists: lists.items } + const activities = await activities_index(params.id!, 1, 10, fetch); + return { lists: lists.items, activities: activities } }; \ No newline at end of file diff --git a/web/src/routes/profile/[id]/trails/+page.svelte b/web/src/routes/profile/[id]/trails/+page.svelte index e815a3f0..6e2ccd82 100644 --- a/web/src/routes/profile/[id]/trails/+page.svelte +++ b/web/src/routes/profile/[id]/trails/+page.svelte @@ -1,6 +1,7 @@ - + + {$_("profile")} | wanderer + - + {$trail.name} | {$_("trail", { values: { n: 1 } })} | wanderer