adds trail share
This commit is contained in:
96
db/migrations/1726231220_created_list_share.go
Normal file
96
db/migrations/1726231220_created_list_share.go
Normal file
@@ -0,0 +1,96 @@
|
|||||||
|
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": "1kot7t9na3hi0gl",
|
||||||
|
"created": "2024-09-13 12:40:20.308Z",
|
||||||
|
"updated": "2024-09-13 12:40:20.308Z",
|
||||||
|
"name": "list_share",
|
||||||
|
"type": "base",
|
||||||
|
"system": false,
|
||||||
|
"schema": [
|
||||||
|
{
|
||||||
|
"system": false,
|
||||||
|
"id": "luqrtipy",
|
||||||
|
"name": "list",
|
||||||
|
"type": "relation",
|
||||||
|
"required": true,
|
||||||
|
"presentable": false,
|
||||||
|
"unique": false,
|
||||||
|
"options": {
|
||||||
|
"collectionId": "r6gu2ajyidy1x69",
|
||||||
|
"cascadeDelete": false,
|
||||||
|
"minSelect": null,
|
||||||
|
"maxSelect": 1,
|
||||||
|
"displayFields": null
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"system": false,
|
||||||
|
"id": "mix12kkh",
|
||||||
|
"name": "user",
|
||||||
|
"type": "relation",
|
||||||
|
"required": true,
|
||||||
|
"presentable": false,
|
||||||
|
"unique": false,
|
||||||
|
"options": {
|
||||||
|
"collectionId": "_pb_users_auth_",
|
||||||
|
"cascadeDelete": false,
|
||||||
|
"minSelect": null,
|
||||||
|
"maxSelect": 1,
|
||||||
|
"displayFields": null
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"system": false,
|
||||||
|
"id": "n9rjdx5g",
|
||||||
|
"name": "permission",
|
||||||
|
"type": "select",
|
||||||
|
"required": true,
|
||||||
|
"presentable": false,
|
||||||
|
"unique": false,
|
||||||
|
"options": {
|
||||||
|
"maxSelect": 1,
|
||||||
|
"values": [
|
||||||
|
"view",
|
||||||
|
"edit"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"indexes": [],
|
||||||
|
"listRule": null,
|
||||||
|
"viewRule": null,
|
||||||
|
"createRule": null,
|
||||||
|
"updateRule": null,
|
||||||
|
"deleteRule": null,
|
||||||
|
"options": {}
|
||||||
|
}`
|
||||||
|
|
||||||
|
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("1kot7t9na3hi0gl")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return dao.DeleteCollection(collection)
|
||||||
|
})
|
||||||
|
}
|
||||||
50
db/migrations/1726232380_updated_list_share.go
Normal file
50
db/migrations/1726232380_updated_list_share.go
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
package migrations
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/pocketbase/dbx"
|
||||||
|
"github.com/pocketbase/pocketbase/daos"
|
||||||
|
m "github.com/pocketbase/pocketbase/migrations"
|
||||||
|
"github.com/pocketbase/pocketbase/tools/types"
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
m.Register(func(db dbx.Builder) error {
|
||||||
|
dao := daos.New(db);
|
||||||
|
|
||||||
|
collection, err := dao.FindCollectionByNameOrId("1kot7t9na3hi0gl")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
collection.ListRule = types.Pointer("list.author = @request.auth.id || user = @request.auth.id")
|
||||||
|
|
||||||
|
collection.ViewRule = types.Pointer("list.author = @request.auth.id || user = @request.auth.id")
|
||||||
|
|
||||||
|
collection.CreateRule = types.Pointer("list.author = @request.auth.id")
|
||||||
|
|
||||||
|
collection.UpdateRule = types.Pointer("list.author = @request.auth.id")
|
||||||
|
|
||||||
|
collection.DeleteRule = types.Pointer("list.author = @request.auth.id")
|
||||||
|
|
||||||
|
return dao.SaveCollection(collection)
|
||||||
|
}, func(db dbx.Builder) error {
|
||||||
|
dao := daos.New(db);
|
||||||
|
|
||||||
|
collection, err := dao.FindCollectionByNameOrId("1kot7t9na3hi0gl")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
collection.ListRule = nil
|
||||||
|
|
||||||
|
collection.ViewRule = nil
|
||||||
|
|
||||||
|
collection.CreateRule = nil
|
||||||
|
|
||||||
|
collection.UpdateRule = nil
|
||||||
|
|
||||||
|
collection.DeleteRule = nil
|
||||||
|
|
||||||
|
return dao.SaveCollection(collection)
|
||||||
|
})
|
||||||
|
}
|
||||||
46
db/migrations/1726232603_updated_lists.go
Normal file
46
db/migrations/1726232603_updated_lists.go
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
package migrations
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/pocketbase/dbx"
|
||||||
|
"github.com/pocketbase/pocketbase/daos"
|
||||||
|
m "github.com/pocketbase/pocketbase/migrations"
|
||||||
|
"github.com/pocketbase/pocketbase/tools/types"
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
m.Register(func(db dbx.Builder) error {
|
||||||
|
dao := daos.New(db);
|
||||||
|
|
||||||
|
collection, err := dao.FindCollectionByNameOrId("r6gu2ajyidy1x69")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
collection.ListRule = types.Pointer("author = @request.auth.id || (@request.auth.id != \"\" && list_share_via_list.user ?= @request.auth.id)")
|
||||||
|
|
||||||
|
collection.ViewRule = types.Pointer("author = @request.auth.id || (@request.auth.id != \"\" && list_share_via_list.user ?= @request.auth.id)")
|
||||||
|
|
||||||
|
collection.CreateRule = types.Pointer("@request.auth.id != \"\" && (@request.data.author = @request.auth.id)")
|
||||||
|
|
||||||
|
collection.UpdateRule = types.Pointer("author = @request.auth.id || (@request.auth.id != \"\" && list_share_via_list.list = id && list_share_via_list.user ?= @request.auth.id && list_share_via_list.permission = \"edit\")")
|
||||||
|
|
||||||
|
return dao.SaveCollection(collection)
|
||||||
|
}, func(db dbx.Builder) error {
|
||||||
|
dao := daos.New(db);
|
||||||
|
|
||||||
|
collection, err := dao.FindCollectionByNameOrId("r6gu2ajyidy1x69")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
collection.ListRule = types.Pointer("author = @request.auth.id ")
|
||||||
|
|
||||||
|
collection.ViewRule = types.Pointer("author = @request.auth.id ")
|
||||||
|
|
||||||
|
collection.CreateRule = types.Pointer("author = @request.auth.id ")
|
||||||
|
|
||||||
|
collection.UpdateRule = types.Pointer("author = @request.auth.id ")
|
||||||
|
|
||||||
|
return dao.SaveCollection(collection)
|
||||||
|
})
|
||||||
|
}
|
||||||
300
db/migrations/1726245829_updated_trails_filter.go
Normal file
300
db/migrations/1726245829_updated_trails_filter.go
Normal file
@@ -0,0 +1,300 @@
|
|||||||
|
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("4wbv9tz5zjdrjh1")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
options := map[string]any{}
|
||||||
|
if err := json.Unmarshal([]byte(`{
|
||||||
|
"query": "SELECT users.id, COALESCE(MAX(trails.distance), 0) AS max_distance, COALESCE(MAX(trails.elevation_gain), 0) AS max_elevation_gain, COALESCE(MAX(trails.duration), 0) AS max_duration, COALESCE(MIN(trails.distance), 0) AS min_distance, COALESCE(MIN(trails.elevation_gain), 0) AS min_elevation_gain, COALESCE(MIN(trails.duration), 0) AS min_duration FROM users LEFT JOIN trails ON users.id = trails.author OR trails.public = 1 OR EXISTS (\n SELECT 1 \n FROM trail_share \n WHERE trail_share.trail = trails.id \n AND trail_share.user = users.id\n ) GROUP BY users.id;"
|
||||||
|
}`), &options); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
collection.SetOptions(options)
|
||||||
|
|
||||||
|
// remove
|
||||||
|
collection.Schema.RemoveField("6tuhuumw")
|
||||||
|
|
||||||
|
// remove
|
||||||
|
collection.Schema.RemoveField("xarhom23")
|
||||||
|
|
||||||
|
// remove
|
||||||
|
collection.Schema.RemoveField("kl9wsxwf")
|
||||||
|
|
||||||
|
// remove
|
||||||
|
collection.Schema.RemoveField("ipd1ohgc")
|
||||||
|
|
||||||
|
// remove
|
||||||
|
collection.Schema.RemoveField("o4s7acfv")
|
||||||
|
|
||||||
|
// remove
|
||||||
|
collection.Schema.RemoveField("d9lfg9vd")
|
||||||
|
|
||||||
|
// add
|
||||||
|
new_max_distance := &schema.SchemaField{}
|
||||||
|
if err := json.Unmarshal([]byte(`{
|
||||||
|
"system": false,
|
||||||
|
"id": "ixfa8u8m",
|
||||||
|
"name": "max_distance",
|
||||||
|
"type": "json",
|
||||||
|
"required": false,
|
||||||
|
"presentable": false,
|
||||||
|
"unique": false,
|
||||||
|
"options": {
|
||||||
|
"maxSize": 1
|
||||||
|
}
|
||||||
|
}`), new_max_distance); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
collection.Schema.AddField(new_max_distance)
|
||||||
|
|
||||||
|
// add
|
||||||
|
new_max_elevation_gain := &schema.SchemaField{}
|
||||||
|
if err := json.Unmarshal([]byte(`{
|
||||||
|
"system": false,
|
||||||
|
"id": "bhn0jj40",
|
||||||
|
"name": "max_elevation_gain",
|
||||||
|
"type": "json",
|
||||||
|
"required": false,
|
||||||
|
"presentable": false,
|
||||||
|
"unique": false,
|
||||||
|
"options": {
|
||||||
|
"maxSize": 1
|
||||||
|
}
|
||||||
|
}`), new_max_elevation_gain); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
collection.Schema.AddField(new_max_elevation_gain)
|
||||||
|
|
||||||
|
// add
|
||||||
|
new_max_duration := &schema.SchemaField{}
|
||||||
|
if err := json.Unmarshal([]byte(`{
|
||||||
|
"system": false,
|
||||||
|
"id": "3kujiwzh",
|
||||||
|
"name": "max_duration",
|
||||||
|
"type": "json",
|
||||||
|
"required": false,
|
||||||
|
"presentable": false,
|
||||||
|
"unique": false,
|
||||||
|
"options": {
|
||||||
|
"maxSize": 1
|
||||||
|
}
|
||||||
|
}`), new_max_duration); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
collection.Schema.AddField(new_max_duration)
|
||||||
|
|
||||||
|
// add
|
||||||
|
new_min_distance := &schema.SchemaField{}
|
||||||
|
if err := json.Unmarshal([]byte(`{
|
||||||
|
"system": false,
|
||||||
|
"id": "yf3zwzn4",
|
||||||
|
"name": "min_distance",
|
||||||
|
"type": "json",
|
||||||
|
"required": false,
|
||||||
|
"presentable": false,
|
||||||
|
"unique": false,
|
||||||
|
"options": {
|
||||||
|
"maxSize": 1
|
||||||
|
}
|
||||||
|
}`), new_min_distance); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
collection.Schema.AddField(new_min_distance)
|
||||||
|
|
||||||
|
// add
|
||||||
|
new_min_elevation_gain := &schema.SchemaField{}
|
||||||
|
if err := json.Unmarshal([]byte(`{
|
||||||
|
"system": false,
|
||||||
|
"id": "skvtkith",
|
||||||
|
"name": "min_elevation_gain",
|
||||||
|
"type": "json",
|
||||||
|
"required": false,
|
||||||
|
"presentable": false,
|
||||||
|
"unique": false,
|
||||||
|
"options": {
|
||||||
|
"maxSize": 1
|
||||||
|
}
|
||||||
|
}`), new_min_elevation_gain); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
collection.Schema.AddField(new_min_elevation_gain)
|
||||||
|
|
||||||
|
// add
|
||||||
|
new_min_duration := &schema.SchemaField{}
|
||||||
|
if err := json.Unmarshal([]byte(`{
|
||||||
|
"system": false,
|
||||||
|
"id": "gydvo1ug",
|
||||||
|
"name": "min_duration",
|
||||||
|
"type": "json",
|
||||||
|
"required": false,
|
||||||
|
"presentable": false,
|
||||||
|
"unique": false,
|
||||||
|
"options": {
|
||||||
|
"maxSize": 1
|
||||||
|
}
|
||||||
|
}`), new_min_duration); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
collection.Schema.AddField(new_min_duration)
|
||||||
|
|
||||||
|
return dao.SaveCollection(collection)
|
||||||
|
}, func(db dbx.Builder) error {
|
||||||
|
dao := daos.New(db);
|
||||||
|
|
||||||
|
collection, err := dao.FindCollectionByNameOrId("4wbv9tz5zjdrjh1")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
options := map[string]any{}
|
||||||
|
if err := json.Unmarshal([]byte(`{
|
||||||
|
"query": "SELECT users.id, COALESCE(MAX(trails.distance), 0) AS max_distance, COALESCE(MAX(trails.elevation_gain), 0) AS max_elevation_gain, COALESCE(MAX(trails.duration), 0) AS max_duration, COALESCE(MIN(trails.distance), 0) AS min_distance, COALESCE(MIN(trails.elevation_gain), 0) AS min_elevation_gain, COALESCE(MIN(trails.duration), 0) AS min_duration FROM users LEFT JOIN trails ON users.id = trails.author OR trails.public = 1 OR EXISTS (\n SELECT 1 \n FROM trail_share \n WHERE trail_share.trail = trails.id \n AND trail_share.user = users.id\n );"
|
||||||
|
}`), &options); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
collection.SetOptions(options)
|
||||||
|
|
||||||
|
// add
|
||||||
|
del_max_distance := &schema.SchemaField{}
|
||||||
|
if err := json.Unmarshal([]byte(`{
|
||||||
|
"system": false,
|
||||||
|
"id": "6tuhuumw",
|
||||||
|
"name": "max_distance",
|
||||||
|
"type": "json",
|
||||||
|
"required": false,
|
||||||
|
"presentable": false,
|
||||||
|
"unique": false,
|
||||||
|
"options": {
|
||||||
|
"maxSize": 1
|
||||||
|
}
|
||||||
|
}`), del_max_distance); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
collection.Schema.AddField(del_max_distance)
|
||||||
|
|
||||||
|
// add
|
||||||
|
del_max_elevation_gain := &schema.SchemaField{}
|
||||||
|
if err := json.Unmarshal([]byte(`{
|
||||||
|
"system": false,
|
||||||
|
"id": "xarhom23",
|
||||||
|
"name": "max_elevation_gain",
|
||||||
|
"type": "json",
|
||||||
|
"required": false,
|
||||||
|
"presentable": false,
|
||||||
|
"unique": false,
|
||||||
|
"options": {
|
||||||
|
"maxSize": 1
|
||||||
|
}
|
||||||
|
}`), del_max_elevation_gain); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
collection.Schema.AddField(del_max_elevation_gain)
|
||||||
|
|
||||||
|
// add
|
||||||
|
del_max_duration := &schema.SchemaField{}
|
||||||
|
if err := json.Unmarshal([]byte(`{
|
||||||
|
"system": false,
|
||||||
|
"id": "kl9wsxwf",
|
||||||
|
"name": "max_duration",
|
||||||
|
"type": "json",
|
||||||
|
"required": false,
|
||||||
|
"presentable": false,
|
||||||
|
"unique": false,
|
||||||
|
"options": {
|
||||||
|
"maxSize": 1
|
||||||
|
}
|
||||||
|
}`), del_max_duration); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
collection.Schema.AddField(del_max_duration)
|
||||||
|
|
||||||
|
// add
|
||||||
|
del_min_distance := &schema.SchemaField{}
|
||||||
|
if err := json.Unmarshal([]byte(`{
|
||||||
|
"system": false,
|
||||||
|
"id": "ipd1ohgc",
|
||||||
|
"name": "min_distance",
|
||||||
|
"type": "json",
|
||||||
|
"required": false,
|
||||||
|
"presentable": false,
|
||||||
|
"unique": false,
|
||||||
|
"options": {
|
||||||
|
"maxSize": 1
|
||||||
|
}
|
||||||
|
}`), del_min_distance); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
collection.Schema.AddField(del_min_distance)
|
||||||
|
|
||||||
|
// add
|
||||||
|
del_min_elevation_gain := &schema.SchemaField{}
|
||||||
|
if err := json.Unmarshal([]byte(`{
|
||||||
|
"system": false,
|
||||||
|
"id": "o4s7acfv",
|
||||||
|
"name": "min_elevation_gain",
|
||||||
|
"type": "json",
|
||||||
|
"required": false,
|
||||||
|
"presentable": false,
|
||||||
|
"unique": false,
|
||||||
|
"options": {
|
||||||
|
"maxSize": 1
|
||||||
|
}
|
||||||
|
}`), del_min_elevation_gain); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
collection.Schema.AddField(del_min_elevation_gain)
|
||||||
|
|
||||||
|
// add
|
||||||
|
del_min_duration := &schema.SchemaField{}
|
||||||
|
if err := json.Unmarshal([]byte(`{
|
||||||
|
"system": false,
|
||||||
|
"id": "d9lfg9vd",
|
||||||
|
"name": "min_duration",
|
||||||
|
"type": "json",
|
||||||
|
"required": false,
|
||||||
|
"presentable": false,
|
||||||
|
"unique": false,
|
||||||
|
"options": {
|
||||||
|
"maxSize": 1
|
||||||
|
}
|
||||||
|
}`), del_min_duration); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
collection.Schema.AddField(del_min_duration)
|
||||||
|
|
||||||
|
// remove
|
||||||
|
collection.Schema.RemoveField("ixfa8u8m")
|
||||||
|
|
||||||
|
// remove
|
||||||
|
collection.Schema.RemoveField("bhn0jj40")
|
||||||
|
|
||||||
|
// remove
|
||||||
|
collection.Schema.RemoveField("3kujiwzh")
|
||||||
|
|
||||||
|
// remove
|
||||||
|
collection.Schema.RemoveField("yf3zwzn4")
|
||||||
|
|
||||||
|
// remove
|
||||||
|
collection.Schema.RemoveField("skvtkith")
|
||||||
|
|
||||||
|
// remove
|
||||||
|
collection.Schema.RemoveField("gydvo1ug")
|
||||||
|
|
||||||
|
return dao.SaveCollection(collection)
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -8,6 +8,7 @@
|
|||||||
|
|
||||||
export let title: string = "Confirm Deletion";
|
export let title: string = "Confirm Deletion";
|
||||||
export let text: string;
|
export let text: string;
|
||||||
|
export let action: string = "delete";
|
||||||
|
|
||||||
const dispatch = createEventDispatcher();
|
const dispatch = createEventDispatcher();
|
||||||
|
|
||||||
@@ -24,7 +25,7 @@
|
|||||||
>{$_("cancel")}</button
|
>{$_("cancel")}</button
|
||||||
>
|
>
|
||||||
<button class="btn-danger" type="button" on:click={confirm} name="delete"
|
<button class="btn-danger" type="button" on:click={confirm} name="delete"
|
||||||
>{$_("delete")}</button
|
>{$_(action)}</button
|
||||||
>
|
>
|
||||||
</div></Modal
|
</div></Modal
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -8,14 +8,11 @@
|
|||||||
} from "$lib/util/format_util";
|
} from "$lib/util/format_util";
|
||||||
import Dropdown, { type DropdownItem } from "../base/dropdown.svelte";
|
import Dropdown, { type DropdownItem } from "../base/dropdown.svelte";
|
||||||
import { _ } from "svelte-i18n";
|
import { _ } from "svelte-i18n";
|
||||||
|
import ShareInfo from "../share_info.svelte";
|
||||||
|
import { currentUser } from "$lib/stores/user_store";
|
||||||
export let list: List;
|
export let list: List;
|
||||||
export let active: boolean = false;
|
export let active: boolean = false;
|
||||||
|
|
||||||
const dropdownItems: DropdownItem[] = [
|
|
||||||
{ text: $_("edit"), value: "edit" },
|
|
||||||
{ text: $_("delete"), value: "delete" },
|
|
||||||
];
|
|
||||||
|
|
||||||
$: cumulativeDistance = list.expand?.trails.reduce(
|
$: cumulativeDistance = list.expand?.trails.reduce(
|
||||||
(s, b) => s + b.distance!,
|
(s, b) => s + b.distance!,
|
||||||
0,
|
0,
|
||||||
@@ -30,6 +27,22 @@
|
|||||||
(s, b) => s + b.duration!,
|
(s, b) => s + b.duration!,
|
||||||
0,
|
0,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
$: listIsShared = (list.expand?.list_share_via_list?.length ?? 0) > 0;
|
||||||
|
|
||||||
|
$: allowEdit =
|
||||||
|
list.author == $currentUser?.id ||
|
||||||
|
list.expand?.list_share_via_list?.some((s) => s.permission == "edit");
|
||||||
|
|
||||||
|
$: dropdownItems = [
|
||||||
|
...(list.author == $currentUser?.id
|
||||||
|
? [{ text: $_("share"), value: "share" }]
|
||||||
|
: []),
|
||||||
|
...(allowEdit ? [{ text: $_("edit"), value: "edit" }] : []),
|
||||||
|
...(list.author == $currentUser?.id
|
||||||
|
? [{ text: $_("delete"), value: "delete" }]
|
||||||
|
: []),
|
||||||
|
];
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
@@ -50,11 +63,18 @@
|
|||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
<div class="self-start min-w-0 w-full transition-transform">
|
<div class="self-start min-w-0 w-full transition-transform">
|
||||||
<div class="flex justify-between items-center">
|
<div class="flex items-center gap-3">
|
||||||
<h5 class="text-xl font-semibold overflow-hidden overflow-ellipsis">
|
<h5
|
||||||
|
class="text-xl font-semibold overflow-hidden overflow-ellipsis basis-full"
|
||||||
|
>
|
||||||
{list.name}
|
{list.name}
|
||||||
</h5>
|
</h5>
|
||||||
<Dropdown items={dropdownItems} on:change></Dropdown>
|
{#if listIsShared}
|
||||||
|
<ShareInfo type="list" subject={list}></ShareInfo>
|
||||||
|
{/if}
|
||||||
|
{#if dropdownItems.length}
|
||||||
|
<Dropdown items={dropdownItems} on:change></Dropdown>
|
||||||
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
<div class="flex mt-1 gap-4 text-sm text-gray-500 whitespace-nowrap">
|
<div class="flex mt-1 gap-4 text-sm text-gray-500 whitespace-nowrap">
|
||||||
<span
|
<span
|
||||||
@@ -73,12 +93,12 @@
|
|||||||
)}</span
|
)}</span
|
||||||
>
|
>
|
||||||
</div>
|
</div>
|
||||||
<p class="text-sm text-gray-500 mb-2"
|
<p class="text-sm text-gray-500 mb-2">
|
||||||
>{list.expand?.trails.length ?? 0}
|
{list.expand?.trails.length ?? 0}
|
||||||
{$_("trail", {
|
{$_("trail", {
|
||||||
values: { n: list.expand?.trails.length ?? 0 },
|
values: { n: list.expand?.trails.length ?? 0 },
|
||||||
})}</p
|
})}
|
||||||
>
|
</p>
|
||||||
<p
|
<p
|
||||||
class="text-gray-500 text-sm mr-8 whitespace-pre-wrap {active
|
class="text-gray-500 text-sm mr-8 whitespace-pre-wrap {active
|
||||||
? ''
|
? ''
|
||||||
|
|||||||
222
web/src/lib/components/list/list_share_modal.svelte
Normal file
222
web/src/lib/components/list/list_share_modal.svelte
Normal file
@@ -0,0 +1,222 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import Modal from "$lib/components/base/modal.svelte";
|
||||||
|
import type { List } from "$lib/models/list";
|
||||||
|
import { ListShare } from "$lib/models/list_share";
|
||||||
|
import { TrailShare } from "$lib/models/trail_share";
|
||||||
|
import type { User } from "$lib/models/user";
|
||||||
|
import {
|
||||||
|
list_share_create,
|
||||||
|
list_share_delete,
|
||||||
|
list_share_index,
|
||||||
|
list_share_update,
|
||||||
|
shares,
|
||||||
|
} from "$lib/stores/list_share_store";
|
||||||
|
import { show_toast } from "$lib/stores/toast_store";
|
||||||
|
import {
|
||||||
|
trail_share_create,
|
||||||
|
trail_share_delete,
|
||||||
|
trail_share_index,
|
||||||
|
} from "$lib/stores/trail_share_store";
|
||||||
|
import { users_search } from "$lib/stores/user_store";
|
||||||
|
import { getFileURL } from "$lib/util/file_util";
|
||||||
|
import { createEventDispatcher } from "svelte";
|
||||||
|
import { _ } from "svelte-i18n";
|
||||||
|
import Button from "../base/button.svelte";
|
||||||
|
import Search, { type SearchItem } from "../base/search.svelte";
|
||||||
|
import type { SelectItem } from "../base/select.svelte";
|
||||||
|
import Select from "../base/select.svelte";
|
||||||
|
import { useFBO } from "@threlte/extras";
|
||||||
|
import { lists } from "$lib/stores/list_store";
|
||||||
|
|
||||||
|
let openModal: (() => void) | undefined = undefined;
|
||||||
|
export let closeModal: (() => void) | undefined = undefined;
|
||||||
|
export function openShareModal() {
|
||||||
|
shares.set([]);
|
||||||
|
fetchShares();
|
||||||
|
if (openModal) {
|
||||||
|
openModal();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export let list: List;
|
||||||
|
|
||||||
|
const dispatch = createEventDispatcher();
|
||||||
|
|
||||||
|
let copyButtonText = $_("copy-link");
|
||||||
|
|
||||||
|
let searchItems: SearchItem[] = [];
|
||||||
|
|
||||||
|
let sharesLoading: boolean = false;
|
||||||
|
|
||||||
|
const permissionSelectItems: SelectItem[] = [
|
||||||
|
{ text: $_("view"), value: "view" },
|
||||||
|
{ text: $_("edit"), value: "edit" },
|
||||||
|
];
|
||||||
|
|
||||||
|
async function updateUsers(q: string) {
|
||||||
|
try {
|
||||||
|
const users: User[] = await users_search(q);
|
||||||
|
searchItems = users.map((u) => ({
|
||||||
|
text: u.username!,
|
||||||
|
value: u,
|
||||||
|
icon: "user",
|
||||||
|
}));
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e);
|
||||||
|
show_toast({
|
||||||
|
type: "error",
|
||||||
|
icon: "close",
|
||||||
|
text: "Error during search",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function copyURLToClipboard() {
|
||||||
|
navigator.clipboard.writeText(window.location.href);
|
||||||
|
|
||||||
|
copyButtonText = $_("link-copied");
|
||||||
|
setTimeout(() => (copyButtonText = $_("copy-link")), 3000);
|
||||||
|
}
|
||||||
|
|
||||||
|
function close() {
|
||||||
|
searchItems = [];
|
||||||
|
dispatch("save");
|
||||||
|
closeModal!();
|
||||||
|
}
|
||||||
|
|
||||||
|
async function shareTrails(userId: string) {
|
||||||
|
const existingTrailShares = await trail_share_index({ user: userId });
|
||||||
|
const trailIds = existingTrailShares.map((s) => s.trail);
|
||||||
|
for (const trailId of list.trails ?? []) {
|
||||||
|
if (!trailIds.includes(trailId)) {
|
||||||
|
const share = new TrailShare(userId, trailId, "view");
|
||||||
|
await trail_share_create(share);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function shareList(item: SelectItem) {
|
||||||
|
const share = new ListShare(item.value.id, list.id!, "view");
|
||||||
|
await list_share_create(share);
|
||||||
|
await shareTrails(item.value.id);
|
||||||
|
fetchShares();
|
||||||
|
}
|
||||||
|
|
||||||
|
async function updateSharePermission(
|
||||||
|
share: ListShare,
|
||||||
|
permission: "view" | "edit",
|
||||||
|
) {
|
||||||
|
share.permission = permission;
|
||||||
|
await list_share_update(share);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function deleteTrailShares(userId: string) {
|
||||||
|
const existingTrailShares = await trail_share_index({ user: userId });
|
||||||
|
for (const trailId of list.trails ?? []) {
|
||||||
|
const shareToDelete = existingTrailShares.find(
|
||||||
|
(s) => s.trail == trailId,
|
||||||
|
);
|
||||||
|
if (shareToDelete) {
|
||||||
|
await trail_share_delete(shareToDelete);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function deleteShare(share: ListShare) {
|
||||||
|
await list_share_delete(share);
|
||||||
|
// await deleteTrailShares(share.user);
|
||||||
|
fetchShares();
|
||||||
|
}
|
||||||
|
|
||||||
|
async function fetchShares() {
|
||||||
|
sharesLoading = true;
|
||||||
|
const fetchedShares = await list_share_index(list.id!);
|
||||||
|
list.expand = {
|
||||||
|
trails: list.expand?.trails ?? [],
|
||||||
|
list_share_via_list: fetchedShares,
|
||||||
|
};
|
||||||
|
sharesLoading = false;
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<Modal
|
||||||
|
id="share-modal"
|
||||||
|
title={$_("share-this-list")}
|
||||||
|
size="max-w-sm overflow-visible"
|
||||||
|
bind:openModal
|
||||||
|
bind:closeModal
|
||||||
|
>
|
||||||
|
<div slot="content">
|
||||||
|
<p class="p-4 bg-amber-100 rounded-xl mb-4 text-sm text-gray-500">
|
||||||
|
{$_("list-share-warning")}
|
||||||
|
</p>
|
||||||
|
<Search
|
||||||
|
on:update={(e) => updateUsers(e.detail)}
|
||||||
|
on:click={(e) => shareList(e.detail)}
|
||||||
|
placeholder={`${$_("username")}`}
|
||||||
|
items={searchItems}
|
||||||
|
>
|
||||||
|
<img
|
||||||
|
slot="item-header"
|
||||||
|
let:item
|
||||||
|
class="rounded-full w-8 aspect-square mr-2"
|
||||||
|
src={getFileURL(item.value, item.value.avatar) ||
|
||||||
|
`https://api.dicebear.com/7.x/initials/svg?seed=${item.value.username}&backgroundType=gradientLinear`}
|
||||||
|
alt="avatar"
|
||||||
|
/>
|
||||||
|
</Search>
|
||||||
|
<h4 class="font-semibold mt-4">{$_("shared-with")}</h4>
|
||||||
|
|
||||||
|
{#if $shares.length == 0}
|
||||||
|
<p class="text-gray-500 text-center mt-2 text-sm">
|
||||||
|
{$_("list-not-shared")}
|
||||||
|
</p>
|
||||||
|
{:else}
|
||||||
|
{#each $shares as share}
|
||||||
|
{#if share.expand}
|
||||||
|
<div class="flex items-center gap-x-2 p-2">
|
||||||
|
<img
|
||||||
|
class="rounded-full w-8 aspect-square mr-2"
|
||||||
|
src={getFileURL(
|
||||||
|
share.expand.user,
|
||||||
|
share.expand.user.avatar,
|
||||||
|
) ||
|
||||||
|
`https://api.dicebear.com/7.x/initials/svg?seed=${share.expand.user.username}&backgroundType=gradientLinear`}
|
||||||
|
alt="avatar"
|
||||||
|
/>
|
||||||
|
<p>{share.expand.user.username}</p>
|
||||||
|
<span
|
||||||
|
class="basis-full text-sm text-center text-gray-500"
|
||||||
|
>{$_("can")}</span
|
||||||
|
>
|
||||||
|
<div class="shrink-0">
|
||||||
|
<Select
|
||||||
|
bind:value={share.permission}
|
||||||
|
items={permissionSelectItems}
|
||||||
|
on:change={(e) =>
|
||||||
|
updateSharePermission(share, e.detail)}
|
||||||
|
></Select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button
|
||||||
|
class="btn-icon text-red-500"
|
||||||
|
on:click={() => deleteShare(share)}
|
||||||
|
><i class="fa fa-trash"></i></button
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
{/each}
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
<div slot="footer" class="flex justify-between items-center gap-4">
|
||||||
|
<Button
|
||||||
|
secondary={true}
|
||||||
|
disabled={copyButtonText == $_("link-copied")}
|
||||||
|
on:click={copyURLToClipboard}
|
||||||
|
>
|
||||||
|
<i class="fa fa-link mr-2"></i>
|
||||||
|
{copyButtonText}
|
||||||
|
</Button>
|
||||||
|
<button class="btn-primary" on:click={close}>{$_("close")}</button>
|
||||||
|
</div></Modal
|
||||||
|
>
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
|
import type { List } from "$lib/models/list";
|
||||||
import type { Trail } from "$lib/models/trail";
|
import type { Trail } from "$lib/models/trail";
|
||||||
import type { User } from "$lib/models/user";
|
import type { User } from "$lib/models/user";
|
||||||
import { pb } from "$lib/pocketbase";
|
import { pb } from "$lib/pocketbase";
|
||||||
@@ -7,31 +8,30 @@
|
|||||||
import { _ } from "svelte-i18n";
|
import { _ } from "svelte-i18n";
|
||||||
import { fly, slide } from "svelte/transition";
|
import { fly, slide } from "svelte/transition";
|
||||||
|
|
||||||
export let trail: Trail;
|
export let subject: Trail | List;
|
||||||
export let large: boolean = false;
|
export let large: boolean = false;
|
||||||
|
export let type: "trail" | "list"
|
||||||
|
|
||||||
|
const shareData = type == "trail" ? (subject as Trail).expand.trail_share_via_trail : (subject as List).expand?.list_share_via_list
|
||||||
|
|
||||||
let showInfo: boolean = false;
|
let showInfo: boolean = false;
|
||||||
|
|
||||||
let loading: boolean = false;
|
let loading: boolean = false;
|
||||||
|
|
||||||
let infoLoaded: boolean = false;
|
let infoLoaded: boolean = false;
|
||||||
|
let subjectIsOwned: boolean = subject.author == pb.authStore.model?.id;
|
||||||
let trailIsOwned: boolean = trail.author == pb.authStore.model?.id;
|
|
||||||
|
|
||||||
let author: User;
|
let author: User;
|
||||||
|
|
||||||
async function fetchInfo() {
|
async function fetchInfo() {
|
||||||
if (!infoLoaded) {
|
if (!infoLoaded) {
|
||||||
loading = true;
|
loading = true;
|
||||||
loading = false;
|
loading = false;
|
||||||
if (trailIsOwned) {
|
if (subjectIsOwned) {
|
||||||
for (const share of trail.expand.trail_share_via_trail ?? []) {
|
for (const share of shareData ?? []) {
|
||||||
share.expand = {
|
share.expand = {
|
||||||
user: await users_show(share.user),
|
user: await users_show(share.user),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
author = await users_show(trail.author!);
|
author = await users_show(subject.author!);
|
||||||
}
|
}
|
||||||
|
|
||||||
infoLoaded = true;
|
infoLoaded = true;
|
||||||
@@ -57,9 +57,9 @@
|
|||||||
>
|
>
|
||||||
{#if loading}
|
{#if loading}
|
||||||
<div class="spinner spinner-dark"></div>
|
<div class="spinner spinner-dark"></div>
|
||||||
{:else if infoLoaded && trail.expand.trail_share_via_trail}
|
{:else if infoLoaded && shareData}
|
||||||
{#if trailIsOwned}
|
{#if subjectIsOwned}
|
||||||
{#each trail.expand.trail_share_via_trail as share}
|
{#each shareData as share}
|
||||||
{#if share.expand}
|
{#if share.expand}
|
||||||
<li>
|
<li>
|
||||||
<div class="flex items-center mr-8">
|
<div class="flex items-center mr-8">
|
||||||
@@ -109,7 +109,7 @@
|
|||||||
{$_("you-can")}
|
{$_("you-can")}
|
||||||
</p>
|
</p>
|
||||||
<p class="whitespace-nowrap">
|
<p class="whitespace-nowrap">
|
||||||
{#if trail.expand.trail_share_via_trail[0].permission == "view"}
|
{#if shareData[0].permission == "view"}
|
||||||
<i class="fa fa-eye mr-1"></i>
|
<i class="fa fa-eye mr-1"></i>
|
||||||
{$_("view")}
|
{$_("view")}
|
||||||
{:else}
|
{:else}
|
||||||
@@ -7,7 +7,7 @@
|
|||||||
formatTimeHHMM,
|
formatTimeHHMM,
|
||||||
} from "$lib/util/format_util";
|
} from "$lib/util/format_util";
|
||||||
import { _ } from "svelte-i18n";
|
import { _ } from "svelte-i18n";
|
||||||
import TrailShareInfo from "./trail_share_info.svelte";
|
import ShareInfo from "../share_info.svelte";
|
||||||
|
|
||||||
export let trail: Trail;
|
export let trail: Trail;
|
||||||
|
|
||||||
@@ -45,7 +45,7 @@
|
|||||||
</span>
|
</span>
|
||||||
{/if}
|
{/if}
|
||||||
{#if trail.expand?.trail_share_via_trail?.length}
|
{#if trail.expand?.trail_share_via_trail?.length}
|
||||||
<TrailShareInfo {trail}></TrailShareInfo>
|
<ShareInfo type="trail" subject={trail}></ShareInfo>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
|||||||
@@ -33,7 +33,7 @@
|
|||||||
import Textarea from "../base/textarea.svelte";
|
import Textarea from "../base/textarea.svelte";
|
||||||
import CommentCard from "../comment/comment_card.svelte";
|
import CommentCard from "../comment/comment_card.svelte";
|
||||||
import PhotoGallery from "../photo_gallery.svelte";
|
import PhotoGallery from "../photo_gallery.svelte";
|
||||||
import TrailShareInfo from "./trail_share_info.svelte";
|
import ShareInfo from "../share_info.svelte";
|
||||||
|
|
||||||
export let trail: Trail;
|
export let trail: Trail;
|
||||||
export let mode: "overview" | "map" = "map";
|
export let mode: "overview" | "map" = "map";
|
||||||
@@ -190,7 +190,7 @@
|
|||||||
</span>
|
</span>
|
||||||
{/if}
|
{/if}
|
||||||
{#if trailIsShared}
|
{#if trailIsShared}
|
||||||
<TrailShareInfo {trail} large={true}></TrailShareInfo>
|
<ShareInfo type="trail" subject={trail} large={true}></ShareInfo>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
|||||||
@@ -7,7 +7,7 @@
|
|||||||
formatTimeHHMM,
|
formatTimeHHMM,
|
||||||
} from "$lib/util/format_util";
|
} from "$lib/util/format_util";
|
||||||
import { _ } from "svelte-i18n";
|
import { _ } from "svelte-i18n";
|
||||||
import TrailShareInfo from "./trail_share_info.svelte";
|
import ShareInfo from "../share_info.svelte";
|
||||||
|
|
||||||
export let trail: Trail;
|
export let trail: Trail;
|
||||||
|
|
||||||
@@ -33,7 +33,7 @@
|
|||||||
</span>
|
</span>
|
||||||
{/if}
|
{/if}
|
||||||
{#if trail.expand?.trail_share_via_trail?.length}
|
{#if trail.expand?.trail_share_via_trail?.length}
|
||||||
<TrailShareInfo {trail}></TrailShareInfo>
|
<ShareInfo type="trail" subject={trail}></ShareInfo>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
{#if trail.date}
|
{#if trail.date}
|
||||||
|
|||||||
@@ -93,7 +93,7 @@
|
|||||||
|
|
||||||
async function fetchShares() {
|
async function fetchShares() {
|
||||||
sharesLoading = true;
|
sharesLoading = true;
|
||||||
await trail_share_index(trail.id!);
|
await trail_share_index({trail: trail.id!});
|
||||||
sharesLoading = false;
|
sharesLoading = false;
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -31,6 +31,8 @@
|
|||||||
"comment": "{n, plural, =1 {Kommentar} other {Kommentare}}",
|
"comment": "{n, plural, =1 {Kommentar} other {Kommentare}}",
|
||||||
"completed": "Abgeschlossen",
|
"completed": "Abgeschlossen",
|
||||||
"completion-status": "Abschlussstatus",
|
"completion-status": "Abschlussstatus",
|
||||||
|
"confirm": "",
|
||||||
|
"confirm-share": "",
|
||||||
"contribute": "Mitwirken",
|
"contribute": "Mitwirken",
|
||||||
"copy-link": "Link kopieren",
|
"copy-link": "Link kopieren",
|
||||||
"create-new-list": "Neue Liste erstellen",
|
"create-new-list": "Neue Liste erstellen",
|
||||||
@@ -71,6 +73,7 @@
|
|||||||
"error-exporting-trail": "Fehler beim Exportieren des Trails",
|
"error-exporting-trail": "Fehler beim Exportieren des Trails",
|
||||||
"error-printing-map": "Fehler beim Drucken der Karte",
|
"error-printing-map": "Fehler beim Drucken der Karte",
|
||||||
"error-reading-file": "Fehler beim Lesen der Datei",
|
"error-reading-file": "Fehler beim Lesen der Datei",
|
||||||
|
"error-saving-list": "",
|
||||||
"error-saving-trail": "Fehler bei Speichern der Route",
|
"error-saving-trail": "Fehler bei Speichern der Route",
|
||||||
"error-updating-password": "Fehler beim Aktualisieren des Passworts",
|
"error-updating-password": "Fehler beim Aktualisieren des Passworts",
|
||||||
"est-duration": "Gesch. Dauer",
|
"est-duration": "Gesch. Dauer",
|
||||||
@@ -106,7 +109,10 @@
|
|||||||
"license": "Lizenz",
|
"license": "Lizenz",
|
||||||
"link-copied": "Link kopiert",
|
"link-copied": "Link kopiert",
|
||||||
"list": "{n, plural, =1 {Liste} other {Listen}}",
|
"list": "{n, plural, =1 {Liste} other {Listen}}",
|
||||||
|
"list-not-shared": "",
|
||||||
"list-saved-successfully": "",
|
"list-saved-successfully": "",
|
||||||
|
"list-share-warning": "Sharing a list automatically shares all trails contained in it.",
|
||||||
|
"list-share-warning-update": "",
|
||||||
"location": "Standort",
|
"location": "Standort",
|
||||||
"login": "Login",
|
"login": "Login",
|
||||||
"login-details": "Login Details",
|
"login-details": "Login Details",
|
||||||
@@ -162,6 +168,7 @@
|
|||||||
"select-list": "Liste auswählen",
|
"select-list": "Liste auswählen",
|
||||||
"settings": "Einstellungen",
|
"settings": "Einstellungen",
|
||||||
"share": "Teilen",
|
"share": "Teilen",
|
||||||
|
"share-this-list": "",
|
||||||
"share-this-trail": "Diese Route teilen",
|
"share-this-trail": "Diese Route teilen",
|
||||||
"shared-by": "Geteilt von",
|
"shared-by": "Geteilt von",
|
||||||
"shared-with": "Geteilt mit",
|
"shared-with": "Geteilt mit",
|
||||||
|
|||||||
@@ -31,6 +31,8 @@
|
|||||||
"comment": "{n, plural, =1 {Comment} other {Comments}}",
|
"comment": "{n, plural, =1 {Comment} other {Comments}}",
|
||||||
"completed": "Completed",
|
"completed": "Completed",
|
||||||
"completion-status": "Completion Status",
|
"completion-status": "Completion Status",
|
||||||
|
"confirm": "Confirm",
|
||||||
|
"confirm-share": "Confirm share",
|
||||||
"contribute": "Contribute",
|
"contribute": "Contribute",
|
||||||
"copy-link": "Copy Link",
|
"copy-link": "Copy Link",
|
||||||
"create-new-list": "Create new list",
|
"create-new-list": "Create new list",
|
||||||
@@ -71,6 +73,7 @@
|
|||||||
"error-exporting-trail": "Error exporting trail",
|
"error-exporting-trail": "Error exporting trail",
|
||||||
"error-printing-map": "Error printing map",
|
"error-printing-map": "Error printing map",
|
||||||
"error-reading-file": "Error reading file",
|
"error-reading-file": "Error reading file",
|
||||||
|
"error-saving-list": "Error saving list",
|
||||||
"error-saving-trail": "Error saving trail",
|
"error-saving-trail": "Error saving trail",
|
||||||
"error-updating-password": "Error updating password",
|
"error-updating-password": "Error updating password",
|
||||||
"est-duration": "Est. duration",
|
"est-duration": "Est. duration",
|
||||||
@@ -106,7 +109,10 @@
|
|||||||
"license": "License",
|
"license": "License",
|
||||||
"link-copied": "Link copied!",
|
"link-copied": "Link copied!",
|
||||||
"list": "{n, plural, =1 {List} other {Lists}}",
|
"list": "{n, plural, =1 {List} other {Lists}}",
|
||||||
|
"list-not-shared": "Not shared with anyone",
|
||||||
"list-saved-successfully": "List saved successfully",
|
"list-saved-successfully": "List saved successfully",
|
||||||
|
"list-share-warning": "",
|
||||||
|
"list-share-warning-update": "Added trails will be shared with everyone that has access to this list.",
|
||||||
"location": "Location",
|
"location": "Location",
|
||||||
"login": "Login",
|
"login": "Login",
|
||||||
"login-details": "Login details",
|
"login-details": "Login details",
|
||||||
@@ -162,6 +168,7 @@
|
|||||||
"select-list": "Select List",
|
"select-list": "Select List",
|
||||||
"settings": "Settings",
|
"settings": "Settings",
|
||||||
"share": "Share",
|
"share": "Share",
|
||||||
|
"share-this-list": "Share this list",
|
||||||
"share-this-trail": "Share this trail",
|
"share-this-trail": "Share this trail",
|
||||||
"shared-by": "Shared by",
|
"shared-by": "Shared by",
|
||||||
"shared-with": "Shared with",
|
"shared-with": "Shared with",
|
||||||
|
|||||||
@@ -31,6 +31,8 @@
|
|||||||
"comment": "{n, plural, =1 {Commentaire} other {Commentaires}}",
|
"comment": "{n, plural, =1 {Commentaire} other {Commentaires}}",
|
||||||
"completed": "Compléter",
|
"completed": "Compléter",
|
||||||
"completion-status": "L'état d'achèvement",
|
"completion-status": "L'état d'achèvement",
|
||||||
|
"confirm": "",
|
||||||
|
"confirm-share": "",
|
||||||
"contribute": "Contribuer",
|
"contribute": "Contribuer",
|
||||||
"copy-link": "",
|
"copy-link": "",
|
||||||
"create-new-list": "Créer une nouvelle liste",
|
"create-new-list": "Créer une nouvelle liste",
|
||||||
@@ -71,6 +73,7 @@
|
|||||||
"error-exporting-trail": "",
|
"error-exporting-trail": "",
|
||||||
"error-printing-map": "Erreur d'impression de la carte",
|
"error-printing-map": "Erreur d'impression de la carte",
|
||||||
"error-reading-file": "Erreur de lecture du fichier",
|
"error-reading-file": "Erreur de lecture du fichier",
|
||||||
|
"error-saving-list": "",
|
||||||
"error-saving-trail": "",
|
"error-saving-trail": "",
|
||||||
"error-updating-password": "",
|
"error-updating-password": "",
|
||||||
"est-duration": "Temps estimé",
|
"est-duration": "Temps estimé",
|
||||||
@@ -106,7 +109,10 @@
|
|||||||
"license": "Licence",
|
"license": "Licence",
|
||||||
"link-copied": "",
|
"link-copied": "",
|
||||||
"list": "{n, plural, =1 {Liste} other {Listes}}",
|
"list": "{n, plural, =1 {Liste} other {Listes}}",
|
||||||
|
"list-not-shared": "",
|
||||||
"list-saved-successfully": "",
|
"list-saved-successfully": "",
|
||||||
|
"list-share-warning": "",
|
||||||
|
"list-share-warning-update": "",
|
||||||
"location": "Localisation",
|
"location": "Localisation",
|
||||||
"login": "Connexion",
|
"login": "Connexion",
|
||||||
"login-details": "",
|
"login-details": "",
|
||||||
@@ -162,6 +168,7 @@
|
|||||||
"select-list": "Liste de choix",
|
"select-list": "Liste de choix",
|
||||||
"settings": "Paramètres",
|
"settings": "Paramètres",
|
||||||
"share": "",
|
"share": "",
|
||||||
|
"share-this-list": "",
|
||||||
"share-this-trail": "",
|
"share-this-trail": "",
|
||||||
"shared-by": "",
|
"shared-by": "",
|
||||||
"shared-with": "",
|
"shared-with": "",
|
||||||
|
|||||||
@@ -31,6 +31,8 @@
|
|||||||
"comment": "{n, plural, =1 {Megjegyzés} other {Megjegyzés}}",
|
"comment": "{n, plural, =1 {Megjegyzés} other {Megjegyzés}}",
|
||||||
"completed": "Teljesítve",
|
"completed": "Teljesítve",
|
||||||
"completion-status": "Befejezés állapota",
|
"completion-status": "Befejezés állapota",
|
||||||
|
"confirm": "",
|
||||||
|
"confirm-share": "",
|
||||||
"contribute": "Hozzájárulás",
|
"contribute": "Hozzájárulás",
|
||||||
"copy-link": "",
|
"copy-link": "",
|
||||||
"create-new-list": "Új lista létrehozása",
|
"create-new-list": "Új lista létrehozása",
|
||||||
@@ -71,6 +73,7 @@
|
|||||||
"error-exporting-trail": "",
|
"error-exporting-trail": "",
|
||||||
"error-printing-map": "",
|
"error-printing-map": "",
|
||||||
"error-reading-file": "Hiba a fájl olvasása közben",
|
"error-reading-file": "Hiba a fájl olvasása közben",
|
||||||
|
"error-saving-list": "",
|
||||||
"error-saving-trail": "",
|
"error-saving-trail": "",
|
||||||
"error-updating-password": "",
|
"error-updating-password": "",
|
||||||
"est-duration": "Becsült időtartam",
|
"est-duration": "Becsült időtartam",
|
||||||
@@ -106,7 +109,10 @@
|
|||||||
"license": "License",
|
"license": "License",
|
||||||
"link-copied": "",
|
"link-copied": "",
|
||||||
"list": "{n, plural, =1 {Lista} other {Listák}}",
|
"list": "{n, plural, =1 {Lista} other {Listák}}",
|
||||||
|
"list-not-shared": "",
|
||||||
"list-saved-successfully": "",
|
"list-saved-successfully": "",
|
||||||
|
"list-share-warning": "",
|
||||||
|
"list-share-warning-update": "",
|
||||||
"location": "Helyszín",
|
"location": "Helyszín",
|
||||||
"login": "Bejelentkezés",
|
"login": "Bejelentkezés",
|
||||||
"login-details": "",
|
"login-details": "",
|
||||||
@@ -162,6 +168,7 @@
|
|||||||
"select-list": "Lista kiválasztása",
|
"select-list": "Lista kiválasztása",
|
||||||
"settings": "Beállítások",
|
"settings": "Beállítások",
|
||||||
"share": "",
|
"share": "",
|
||||||
|
"share-this-list": "",
|
||||||
"share-this-trail": "",
|
"share-this-trail": "",
|
||||||
"shared-by": "",
|
"shared-by": "",
|
||||||
"shared-with": "",
|
"shared-with": "",
|
||||||
|
|||||||
@@ -31,6 +31,8 @@
|
|||||||
"comment": "{n, plural, =1 {Commento} other {Commenti}}",
|
"comment": "{n, plural, =1 {Commento} other {Commenti}}",
|
||||||
"completed": "Completato",
|
"completed": "Completato",
|
||||||
"completion-status": "Stato di completamento",
|
"completion-status": "Stato di completamento",
|
||||||
|
"confirm": "",
|
||||||
|
"confirm-share": "",
|
||||||
"contribute": "Contribuisci",
|
"contribute": "Contribuisci",
|
||||||
"copy-link": "Copia link",
|
"copy-link": "Copia link",
|
||||||
"create-new-list": "Crea nuova lista",
|
"create-new-list": "Crea nuova lista",
|
||||||
@@ -71,6 +73,7 @@
|
|||||||
"error-exporting-trail": "Errore durante l'esportazione del percorso",
|
"error-exporting-trail": "Errore durante l'esportazione del percorso",
|
||||||
"error-printing-map": "Errore durante la stampa della mappa",
|
"error-printing-map": "Errore durante la stampa della mappa",
|
||||||
"error-reading-file": "Errore durante la lettura del file",
|
"error-reading-file": "Errore durante la lettura del file",
|
||||||
|
"error-saving-list": "",
|
||||||
"error-saving-trail": "",
|
"error-saving-trail": "",
|
||||||
"error-updating-password": "",
|
"error-updating-password": "",
|
||||||
"est-duration": "Durata stimata",
|
"est-duration": "Durata stimata",
|
||||||
@@ -106,7 +109,10 @@
|
|||||||
"license": "Licenza",
|
"license": "Licenza",
|
||||||
"link-copied": "Link copiato",
|
"link-copied": "Link copiato",
|
||||||
"list": "{n, plural, =1 {Lista} other {Liste}}",
|
"list": "{n, plural, =1 {Lista} other {Liste}}",
|
||||||
|
"list-not-shared": "",
|
||||||
"list-saved-successfully": "",
|
"list-saved-successfully": "",
|
||||||
|
"list-share-warning": "",
|
||||||
|
"list-share-warning-update": "",
|
||||||
"location": "Posizione",
|
"location": "Posizione",
|
||||||
"login": "Login",
|
"login": "Login",
|
||||||
"login-details": "",
|
"login-details": "",
|
||||||
@@ -162,6 +168,7 @@
|
|||||||
"select-list": "Seleziona lista",
|
"select-list": "Seleziona lista",
|
||||||
"settings": "Impostazioni",
|
"settings": "Impostazioni",
|
||||||
"share": "Condividi",
|
"share": "Condividi",
|
||||||
|
"share-this-list": "",
|
||||||
"share-this-trail": "Condividi questo percorso",
|
"share-this-trail": "Condividi questo percorso",
|
||||||
"shared-by": "Condiviso da",
|
"shared-by": "Condiviso da",
|
||||||
"shared-with": "Condiviso con",
|
"shared-with": "Condiviso con",
|
||||||
|
|||||||
@@ -31,6 +31,8 @@
|
|||||||
"comment": "{n, plural, =1 {Opmerking} other {Opmerkingen}}",
|
"comment": "{n, plural, =1 {Opmerking} other {Opmerkingen}}",
|
||||||
"completed": "Voltooid",
|
"completed": "Voltooid",
|
||||||
"completion-status": "Voltooiingsstatus",
|
"completion-status": "Voltooiingsstatus",
|
||||||
|
"confirm": "",
|
||||||
|
"confirm-share": "",
|
||||||
"contribute": "Bijdragen",
|
"contribute": "Bijdragen",
|
||||||
"copy-link": "",
|
"copy-link": "",
|
||||||
"create-new-list": "Nieuwe lijst",
|
"create-new-list": "Nieuwe lijst",
|
||||||
@@ -71,6 +73,7 @@
|
|||||||
"error-exporting-trail": "",
|
"error-exporting-trail": "",
|
||||||
"error-printing-map": "Fout bij afdrukken van kaart",
|
"error-printing-map": "Fout bij afdrukken van kaart",
|
||||||
"error-reading-file": "Het bestand kan niet worden ingelezen",
|
"error-reading-file": "Het bestand kan niet worden ingelezen",
|
||||||
|
"error-saving-list": "",
|
||||||
"error-saving-trail": "",
|
"error-saving-trail": "",
|
||||||
"error-updating-password": "",
|
"error-updating-password": "",
|
||||||
"est-duration": "Geschatte duur",
|
"est-duration": "Geschatte duur",
|
||||||
@@ -106,7 +109,10 @@
|
|||||||
"license": "Licentie",
|
"license": "Licentie",
|
||||||
"link-copied": "",
|
"link-copied": "",
|
||||||
"list": "{n, plural, =1 {Lijst} other {Lijsten}}",
|
"list": "{n, plural, =1 {Lijst} other {Lijsten}}",
|
||||||
|
"list-not-shared": "",
|
||||||
"list-saved-successfully": "",
|
"list-saved-successfully": "",
|
||||||
|
"list-share-warning": "",
|
||||||
|
"list-share-warning-update": "",
|
||||||
"location": "Locatie",
|
"location": "Locatie",
|
||||||
"login": "Inloggen",
|
"login": "Inloggen",
|
||||||
"login-details": "",
|
"login-details": "",
|
||||||
@@ -162,6 +168,7 @@
|
|||||||
"select-list": "Kies een lijst",
|
"select-list": "Kies een lijst",
|
||||||
"settings": "Instellingen",
|
"settings": "Instellingen",
|
||||||
"share": "",
|
"share": "",
|
||||||
|
"share-this-list": "",
|
||||||
"share-this-trail": "",
|
"share-this-trail": "",
|
||||||
"shared-by": "",
|
"shared-by": "",
|
||||||
"shared-with": "",
|
"shared-with": "",
|
||||||
|
|||||||
@@ -31,6 +31,8 @@
|
|||||||
"comment": "{n, plural, =1 {Komentarz} other {Komentarze}}",
|
"comment": "{n, plural, =1 {Komentarz} other {Komentarze}}",
|
||||||
"completed": "Zakończono",
|
"completed": "Zakończono",
|
||||||
"completion-status": "Stan ukończenia",
|
"completion-status": "Stan ukończenia",
|
||||||
|
"confirm": "",
|
||||||
|
"confirm-share": "",
|
||||||
"contribute": "Kontrybuuj",
|
"contribute": "Kontrybuuj",
|
||||||
"copy-link": "",
|
"copy-link": "",
|
||||||
"create-new-list": "Stwórz nową listę",
|
"create-new-list": "Stwórz nową listę",
|
||||||
@@ -71,6 +73,7 @@
|
|||||||
"error-exporting-trail": "",
|
"error-exporting-trail": "",
|
||||||
"error-printing-map": "",
|
"error-printing-map": "",
|
||||||
"error-reading-file": "Błąd wczytywania pliku",
|
"error-reading-file": "Błąd wczytywania pliku",
|
||||||
|
"error-saving-list": "",
|
||||||
"error-saving-trail": "",
|
"error-saving-trail": "",
|
||||||
"error-updating-password": "",
|
"error-updating-password": "",
|
||||||
"est-duration": "Szacowany czas",
|
"est-duration": "Szacowany czas",
|
||||||
@@ -106,7 +109,10 @@
|
|||||||
"license": "Licencja",
|
"license": "Licencja",
|
||||||
"link-copied": "",
|
"link-copied": "",
|
||||||
"list": "{n, plural, =1 {Lista} other {Listy}}",
|
"list": "{n, plural, =1 {Lista} other {Listy}}",
|
||||||
|
"list-not-shared": "",
|
||||||
"list-saved-successfully": "",
|
"list-saved-successfully": "",
|
||||||
|
"list-share-warning": "",
|
||||||
|
"list-share-warning-update": "",
|
||||||
"location": "Lokalizacja",
|
"location": "Lokalizacja",
|
||||||
"login": "Zaloguj się",
|
"login": "Zaloguj się",
|
||||||
"login-details": "",
|
"login-details": "",
|
||||||
@@ -162,6 +168,7 @@
|
|||||||
"select-list": "Wybierz Listę",
|
"select-list": "Wybierz Listę",
|
||||||
"settings": "Ustawienia",
|
"settings": "Ustawienia",
|
||||||
"share": "",
|
"share": "",
|
||||||
|
"share-this-list": "",
|
||||||
"share-this-trail": "",
|
"share-this-trail": "",
|
||||||
"shared-by": "",
|
"shared-by": "",
|
||||||
"shared-with": "",
|
"shared-with": "",
|
||||||
|
|||||||
@@ -31,6 +31,8 @@
|
|||||||
"comment": "{n, plural, =1 {Comentário} other {Comentários}}",
|
"comment": "{n, plural, =1 {Comentário} other {Comentários}}",
|
||||||
"completed": "Completada",
|
"completed": "Completada",
|
||||||
"completion-status": "Status de conclusão",
|
"completion-status": "Status de conclusão",
|
||||||
|
"confirm": "",
|
||||||
|
"confirm-share": "",
|
||||||
"contribute": "Contribuir",
|
"contribute": "Contribuir",
|
||||||
"copy-link": "",
|
"copy-link": "",
|
||||||
"create-new-list": "Criar nova lista",
|
"create-new-list": "Criar nova lista",
|
||||||
@@ -71,6 +73,7 @@
|
|||||||
"error-exporting-trail": "",
|
"error-exporting-trail": "",
|
||||||
"error-printing-map": "",
|
"error-printing-map": "",
|
||||||
"error-reading-file": "Erro ao ler o arquivo",
|
"error-reading-file": "Erro ao ler o arquivo",
|
||||||
|
"error-saving-list": "",
|
||||||
"error-saving-trail": "",
|
"error-saving-trail": "",
|
||||||
"error-updating-password": "",
|
"error-updating-password": "",
|
||||||
"est-duration": "Duração prevista",
|
"est-duration": "Duração prevista",
|
||||||
@@ -106,7 +109,10 @@
|
|||||||
"license": "Licença",
|
"license": "Licença",
|
||||||
"link-copied": "",
|
"link-copied": "",
|
||||||
"list": "{n, plural, =1 {Lista} other {Listas}}",
|
"list": "{n, plural, =1 {Lista} other {Listas}}",
|
||||||
|
"list-not-shared": "",
|
||||||
"list-saved-successfully": "",
|
"list-saved-successfully": "",
|
||||||
|
"list-share-warning": "",
|
||||||
|
"list-share-warning-update": "",
|
||||||
"location": "Localização",
|
"location": "Localização",
|
||||||
"login": "Login",
|
"login": "Login",
|
||||||
"login-details": "",
|
"login-details": "",
|
||||||
@@ -162,6 +168,7 @@
|
|||||||
"select-list": "Selecionar lista",
|
"select-list": "Selecionar lista",
|
||||||
"settings": "Definições",
|
"settings": "Definições",
|
||||||
"share": "",
|
"share": "",
|
||||||
|
"share-this-list": "",
|
||||||
"share-this-trail": "",
|
"share-this-trail": "",
|
||||||
"shared-by": "",
|
"shared-by": "",
|
||||||
"shared-with": "",
|
"shared-with": "",
|
||||||
|
|||||||
@@ -31,6 +31,8 @@
|
|||||||
"comment": "{n, plural, =1 {评论} other {评论}}",
|
"comment": "{n, plural, =1 {评论} other {评论}}",
|
||||||
"completed": "已完成",
|
"completed": "已完成",
|
||||||
"completion-status": "完成状态",
|
"completion-status": "完成状态",
|
||||||
|
"confirm": "",
|
||||||
|
"confirm-share": "",
|
||||||
"contribute": "贡献",
|
"contribute": "贡献",
|
||||||
"copy-link": "",
|
"copy-link": "",
|
||||||
"create-new-list": "创建新列表",
|
"create-new-list": "创建新列表",
|
||||||
@@ -71,6 +73,7 @@
|
|||||||
"error-exporting-trail": "",
|
"error-exporting-trail": "",
|
||||||
"error-printing-map": "",
|
"error-printing-map": "",
|
||||||
"error-reading-file": "读取文件错误",
|
"error-reading-file": "读取文件错误",
|
||||||
|
"error-saving-list": "",
|
||||||
"error-saving-trail": "",
|
"error-saving-trail": "",
|
||||||
"error-updating-password": "",
|
"error-updating-password": "",
|
||||||
"est-duration": "预计时长",
|
"est-duration": "预计时长",
|
||||||
@@ -106,7 +109,10 @@
|
|||||||
"license": "开源协议",
|
"license": "开源协议",
|
||||||
"link-copied": "",
|
"link-copied": "",
|
||||||
"list": "{n, plural, =1 {列表} other {列表}}",
|
"list": "{n, plural, =1 {列表} other {列表}}",
|
||||||
|
"list-not-shared": "",
|
||||||
"list-saved-successfully": "",
|
"list-saved-successfully": "",
|
||||||
|
"list-share-warning": "",
|
||||||
|
"list-share-warning-update": "",
|
||||||
"location": "地点",
|
"location": "地点",
|
||||||
"login": "登录",
|
"login": "登录",
|
||||||
"login-details": "",
|
"login-details": "",
|
||||||
@@ -162,6 +168,7 @@
|
|||||||
"select-list": "选择列表",
|
"select-list": "选择列表",
|
||||||
"settings": "设置",
|
"settings": "设置",
|
||||||
"share": "",
|
"share": "",
|
||||||
|
"share-this-list": "",
|
||||||
"share-this-trail": "",
|
"share-this-trail": "",
|
||||||
"shared-by": "",
|
"shared-by": "",
|
||||||
"shared-with": "",
|
"shared-with": "",
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { object, string } from "yup";
|
import { object, string } from "yup";
|
||||||
import type { Trail } from "./trail";
|
import type { Trail } from "./trail";
|
||||||
|
import type { ListShare } from "./list_share";
|
||||||
|
|
||||||
export class List {
|
export class List {
|
||||||
id?: string;
|
id?: string;
|
||||||
@@ -9,6 +10,8 @@ export class List {
|
|||||||
trails?: string[];
|
trails?: string[];
|
||||||
expand?: {
|
expand?: {
|
||||||
trails: Trail[]
|
trails: Trail[]
|
||||||
|
list_share_via_list?: ListShare[]
|
||||||
|
|
||||||
}
|
}
|
||||||
author?: string;
|
author?: string;
|
||||||
|
|
||||||
|
|||||||
17
web/src/lib/models/list_share.ts
Normal file
17
web/src/lib/models/list_share.ts
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
import type { User } from "./user";
|
||||||
|
|
||||||
|
export class ListShare {
|
||||||
|
id?: string;
|
||||||
|
user: string;
|
||||||
|
list: string;
|
||||||
|
permission: "view" | "edit"
|
||||||
|
expand?: {
|
||||||
|
user: User
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor(user: string, list: string, permission: "view" | "edit") {
|
||||||
|
this.user = user;
|
||||||
|
this.list = list;
|
||||||
|
this.permission = permission
|
||||||
|
}
|
||||||
|
}
|
||||||
57
web/src/lib/stores/list_share_store.ts
Normal file
57
web/src/lib/stores/list_share_store.ts
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
import type { ListShare } from "$lib/models/list_share";
|
||||||
|
import { ClientResponseError } from "pocketbase";
|
||||||
|
import { writable, type Writable } from "svelte/store";
|
||||||
|
|
||||||
|
export const shares: Writable<ListShare[]> = writable([])
|
||||||
|
|
||||||
|
|
||||||
|
export async function list_share_index(list: string, f: (url: RequestInfo | URL, config?: RequestInit) => Promise<Response> = fetch) {
|
||||||
|
const r = await f('/api/v1/list-share?' + new URLSearchParams({
|
||||||
|
filter: `list='${list}'`,
|
||||||
|
}), {
|
||||||
|
method: 'GET',
|
||||||
|
})
|
||||||
|
|
||||||
|
if (!r.ok) {
|
||||||
|
throw new ClientResponseError(await r.json())
|
||||||
|
}
|
||||||
|
|
||||||
|
const response: ListShare[] = await r.json();
|
||||||
|
|
||||||
|
shares.set(response);
|
||||||
|
|
||||||
|
return response;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function list_share_create(share: ListShare) {
|
||||||
|
let r = await fetch('/api/v1/list-share', {
|
||||||
|
method: 'PUT',
|
||||||
|
body: JSON.stringify(share),
|
||||||
|
})
|
||||||
|
|
||||||
|
if (!r.ok) {
|
||||||
|
throw new ClientResponseError(await r.json())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function list_share_update(share: ListShare) {
|
||||||
|
let r = await fetch('/api/v1/list-share/' + share.id, {
|
||||||
|
method: 'POST',
|
||||||
|
body: JSON.stringify(share),
|
||||||
|
})
|
||||||
|
|
||||||
|
if (!r.ok) {
|
||||||
|
throw new ClientResponseError(await r.json())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function list_share_delete(share: ListShare) {
|
||||||
|
const r = await fetch('/api/v1/list-share/' + share.id, {
|
||||||
|
method: 'DELETE',
|
||||||
|
})
|
||||||
|
|
||||||
|
if (!r.ok) {
|
||||||
|
throw new ClientResponseError(await r.json())
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -5,9 +5,9 @@ import { writable, type Writable } from "svelte/store";
|
|||||||
export const shares: Writable<TrailShare[]> = writable([])
|
export const shares: Writable<TrailShare[]> = writable([])
|
||||||
|
|
||||||
|
|
||||||
export async function trail_share_index(trail: string, f: (url: RequestInfo | URL, config?: RequestInit) => Promise<Response> = fetch) {
|
export async function trail_share_index(data: {trail?: string, user?: string}, f: (url: RequestInfo | URL, config?: RequestInit) => Promise<Response> = fetch) {
|
||||||
const r = await f('/api/v1/trail-share?' + new URLSearchParams({
|
const r = await f('/api/v1/trail-share?' + new URLSearchParams({
|
||||||
filter: `trail='${trail}'`,
|
filter: data.trail ? `trail='${data.trail}'` : data.user ? `user='${data.user}'` : '',
|
||||||
}), {
|
}), {
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
})
|
})
|
||||||
@@ -20,7 +20,7 @@ export async function trail_share_index(trail: string, f: (url: RequestInfo | UR
|
|||||||
|
|
||||||
shares.set(response);
|
shares.set(response);
|
||||||
|
|
||||||
return shares;
|
return response;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -167,7 +167,7 @@ export const GpxGroup = L.GpxGroup = L.Class.extend({
|
|||||||
_addTrack: function (track) {
|
_addTrack: function (track) {
|
||||||
if (track instanceof Object) {
|
if (track instanceof Object) {
|
||||||
this._loadGeoJSON(track);
|
this._loadGeoJSON(track);
|
||||||
} else {
|
} else if (track !== undefined) {
|
||||||
this._elevation._parseFromString(track)
|
this._elevation._parseFromString(track)
|
||||||
.then(geojson => this._loadGeoJSON(geojson, this._hashCode(track), track.split('/').pop().split('#')[0].split('?')[0]))
|
.then(geojson => this._loadGeoJSON(geojson, this._hashCode(track), track.split('/').pop().split('#')[0].split('?')[0]))
|
||||||
}
|
}
|
||||||
@@ -355,17 +355,18 @@ export const GpxGroup = L.GpxGroup = L.Class.extend({
|
|||||||
// Ensure hash is a positive integer
|
// Ensure hash is a positive integer
|
||||||
hash = Math.abs(hash);
|
hash = Math.abs(hash);
|
||||||
|
|
||||||
// Define the maximum brightness (e.g., 200 ensures the color is not too light)
|
// Define the maximum brightness to ensure the color is not too light
|
||||||
const maxBrightness = 200;
|
const maxBrightness = 250;
|
||||||
|
|
||||||
// Convert the hash to a color in a reduced RGB range to prevent light colors
|
// Convert the hash to a color in a reduced RGB range to prevent light and green colors
|
||||||
const r = (hash >> 16) & 0xFF; // Extract the red component
|
const r = (hash >> 16) & 0xFF; // Extract the red component
|
||||||
const g = (hash >> 8) & 0xFF; // Extract the green component
|
const g = (hash >> 8) & 0xFF; // Extract the green component
|
||||||
const b = hash & 0xFF; // Extract the blue component
|
const b = hash & 0xFF; // Extract the blue component
|
||||||
|
|
||||||
// Adjust the RGB values to be within the allowed range (0 to maxBrightness)
|
// Adjust the green value to avoid greenish colors
|
||||||
|
// Reducing the green component significantly to avoid strong green shades
|
||||||
|
const green = Math.floor((g / 255) * (maxBrightness * 0.5)); // Reduce green component range
|
||||||
const red = Math.floor((r / 255) * maxBrightness);
|
const red = Math.floor((r / 255) * maxBrightness);
|
||||||
const green = Math.floor((g / 255) * maxBrightness);
|
|
||||||
const blue = Math.floor((b / 255) * maxBrightness);
|
const blue = Math.floor((b / 255) * maxBrightness);
|
||||||
|
|
||||||
// Format as HEX color
|
// Format as HEX color
|
||||||
|
|||||||
36
web/src/routes/api/v1/list-share/+server.ts
Normal file
36
web/src/routes/api/v1/list-share/+server.ts
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
import type { ListShare } from '$lib/models/list_share';
|
||||||
|
import type { User } from '$lib/models/user';
|
||||||
|
import { pb } from '$lib/pocketbase';
|
||||||
|
import { error, json, type RequestEvent } from '@sveltejs/kit';
|
||||||
|
|
||||||
|
export async function GET(event: RequestEvent) {
|
||||||
|
const sort = event.url.searchParams.get('sort') ?? ""
|
||||||
|
const filter = event.url.searchParams.get("filter") ?? "";
|
||||||
|
|
||||||
|
try {
|
||||||
|
const r: ListShare[] = await pb.collection('list_share').getFullList<ListShare>({
|
||||||
|
sort: sort,
|
||||||
|
filter: filter
|
||||||
|
})
|
||||||
|
for (const share of r) {
|
||||||
|
const anonymous_user = await pb.collection('users_anonymous').getOne<User>(share.user)
|
||||||
|
share.expand = {
|
||||||
|
user: anonymous_user
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return json(r)
|
||||||
|
} catch (e: any) {
|
||||||
|
throw error(e.status, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function PUT(event: RequestEvent) {
|
||||||
|
const data = await event.request.json();
|
||||||
|
|
||||||
|
try {
|
||||||
|
const r = await pb.collection('list_share').create<ListShare>(data)
|
||||||
|
return json(r);
|
||||||
|
} catch (e: any) {
|
||||||
|
throw error(e.status, e)
|
||||||
|
}
|
||||||
|
}
|
||||||
33
web/src/routes/api/v1/list-share/[id]/+server.ts
Normal file
33
web/src/routes/api/v1/list-share/[id]/+server.ts
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
import type { ListShare } from "$lib/models/list_share";
|
||||||
|
import { pb } from "$lib/pocketbase";
|
||||||
|
import { error, json, type RequestEvent } from "@sveltejs/kit";
|
||||||
|
|
||||||
|
export async function GET(event: RequestEvent) {
|
||||||
|
try {
|
||||||
|
const r = await pb.collection('list_share')
|
||||||
|
.getOne<ListShare>(event.params.id as string)
|
||||||
|
return json(r)
|
||||||
|
} catch (e: any) {
|
||||||
|
throw error(e.status, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function POST(event: RequestEvent) {
|
||||||
|
const data = await event.request.json()
|
||||||
|
|
||||||
|
try {
|
||||||
|
const r = await pb.collection('list_share').update<ListShare>(event.params.id as string, data)
|
||||||
|
return json(r);
|
||||||
|
} catch (e: any) {
|
||||||
|
throw error(e.status, e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function DELETE(event: RequestEvent) {
|
||||||
|
try {
|
||||||
|
const r = await pb.collection('list_share').delete(event.params.id as string)
|
||||||
|
return json({ 'acknowledged': r });
|
||||||
|
} catch (e: any) {
|
||||||
|
throw error(e.status, e)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -6,7 +6,7 @@ export async function GET(event: RequestEvent) {
|
|||||||
const sort = event.url.searchParams.get('sort') ?? ""
|
const sort = event.url.searchParams.get('sort') ?? ""
|
||||||
try {
|
try {
|
||||||
const r: List[] = await pb.collection('lists').getFullList<List>({
|
const r: List[] = await pb.collection('lists').getFullList<List>({
|
||||||
expand: "trails,trails.waypoints,trails.category",
|
expand: "trails,trails.waypoints,trails.category,list_share_via_list",
|
||||||
sort: sort,
|
sort: sort,
|
||||||
})
|
})
|
||||||
return json(r)
|
return json(r)
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import { error, json, type RequestEvent } from "@sveltejs/kit";
|
|||||||
export async function GET(event: RequestEvent) {
|
export async function GET(event: RequestEvent) {
|
||||||
try {
|
try {
|
||||||
const r = await pb.collection('lists')
|
const r = await pb.collection('lists')
|
||||||
.getOne<List>(event.params.id as string, { expand: "trails,trails.waypoints,trails.category" })
|
.getOne<List>(event.params.id as string, { expand: "trails,trails.waypoints,trails.category,list_share_via_list" })
|
||||||
return json(r)
|
return json(r)
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
throw error(e.status, e);
|
throw error(e.status, e);
|
||||||
|
|||||||
@@ -5,6 +5,7 @@
|
|||||||
import ConfirmModal from "$lib/components/confirm_modal.svelte";
|
import ConfirmModal from "$lib/components/confirm_modal.svelte";
|
||||||
import ListCard from "$lib/components/list/list_card.svelte";
|
import ListCard from "$lib/components/list/list_card.svelte";
|
||||||
import ListModal from "$lib/components/list/list_modal.svelte";
|
import ListModal from "$lib/components/list/list_modal.svelte";
|
||||||
|
import ListShareModal from "$lib/components/list/list_share_modal.svelte";
|
||||||
import MapWithElevationMultiple from "$lib/components/trail/map_with_elevation_multiple.svelte";
|
import MapWithElevationMultiple from "$lib/components/trail/map_with_elevation_multiple.svelte";
|
||||||
import TrailList from "$lib/components/trail/trail_list.svelte";
|
import TrailList from "$lib/components/trail/trail_list.svelte";
|
||||||
import { List } from "$lib/models/list";
|
import { List } from "$lib/models/list";
|
||||||
@@ -28,6 +29,7 @@
|
|||||||
|
|
||||||
let openListModal: () => void;
|
let openListModal: () => void;
|
||||||
let openConfirmModal: () => void;
|
let openConfirmModal: () => void;
|
||||||
|
let openShareModal: () => void;
|
||||||
|
|
||||||
let filter: TrailFilter = $page.data.filter;
|
let filter: TrailFilter = $page.data.filter;
|
||||||
let listToBeDeleted: List | null = null;
|
let listToBeDeleted: List | null = null;
|
||||||
@@ -60,7 +62,11 @@
|
|||||||
) {
|
) {
|
||||||
const item = e.detail;
|
const item = e.detail;
|
||||||
|
|
||||||
if (item.value == "edit") {
|
if (item.value == "share") {
|
||||||
|
list.set(currentList);
|
||||||
|
await tick();
|
||||||
|
openShareModal();
|
||||||
|
} else if (item.value == "edit") {
|
||||||
goto("/lists/edit/" + currentList.id);
|
goto("/lists/edit/" + currentList.id);
|
||||||
} else if (item.value == "delete") {
|
} else if (item.value == "delete") {
|
||||||
openConfirmModal();
|
openConfirmModal();
|
||||||
@@ -91,19 +97,14 @@
|
|||||||
<svelte:head>
|
<svelte:head>
|
||||||
<title>{$_("list", { values: { n: 2 } })} | wanderer</title>
|
<title>{$_("list", { values: { n: 2 } })} | wanderer</title>
|
||||||
</svelte:head>
|
</svelte:head>
|
||||||
<main
|
<main class="grid grid-cols-1 md:grid-cols-[430px_1fr] gap-4 lg:gap-4 mx-4">
|
||||||
class="grid grid-cols-1 md:grid-cols-[430px_1fr] gap-4 lg:gap-4 mx-4"
|
|
||||||
style="height: calc(100vh - 124px)"
|
|
||||||
>
|
|
||||||
<ul
|
<ul
|
||||||
class="list-list mx-4 md:mx-auto rounded-xl border border-input-border max-w-full overflow-y-scroll"
|
class="list-list mx-4 md:mx-auto rounded-xl border border-input-border max-h-full"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="flex gap-x-4 items-center justify-between p-4 top-0 sticky bg-background z-50"
|
class="flex gap-x-4 items-center justify-between p-4 bg-background z-50 rounded-xl"
|
||||||
>
|
>
|
||||||
<a
|
<a class="btn-primary btn-large text-center" href="/lists/edit/new"
|
||||||
class="btn-primary btn-large text-center mx-4"
|
|
||||||
href="/lists/edit/new"
|
|
||||||
><i class="fa fa-plus mr-2"></i>{$_("new-list")}</a
|
><i class="fa fa-plus mr-2"></i>{$_("new-list")}</a
|
||||||
>
|
>
|
||||||
<button type="button" class="btn-icon" on:click={toggleMap}
|
<button type="button" class="btn-icon" on:click={toggleMap}
|
||||||
@@ -113,24 +114,24 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<hr class="border-separator mb-2" />
|
<hr class="border-separator mb-2" />
|
||||||
{#each $lists as item, i}
|
|
||||||
<li
|
<div class="px-4">
|
||||||
class="list-list-item"
|
{#each $lists as item, i}
|
||||||
on:click={() => setCurrentList(item)}
|
<li
|
||||||
role="presentation"
|
class="list-list-item my-1"
|
||||||
>
|
on:click={() => setCurrentList(item)}
|
||||||
<ListCard
|
role="presentation"
|
||||||
list={item}
|
>
|
||||||
on:change={(e) => handleDropdownClick(e, item)}
|
<ListCard
|
||||||
active={item.id === $list.id}
|
list={item}
|
||||||
></ListCard>
|
on:change={(e) => handleDropdownClick(e, item)}
|
||||||
{#if i != $lists.length - 1}
|
active={item.id === $list.id}
|
||||||
<hr class="border-separator my-2" />
|
></ListCard>
|
||||||
{/if}
|
</li>
|
||||||
</li>
|
{/each}
|
||||||
{/each}
|
</div>
|
||||||
</ul>
|
</ul>
|
||||||
<div class:hidden={!showMap}>
|
<div id="trail-map" class="sticky top-[62px]" class:hidden={!showMap}>
|
||||||
<MapWithElevationMultiple
|
<MapWithElevationMultiple
|
||||||
trails={$list.expand?.trails ?? []}
|
trails={$list.expand?.trails ?? []}
|
||||||
bind:map
|
bind:map
|
||||||
@@ -151,10 +152,13 @@
|
|||||||
bind:openModal={openConfirmModal}
|
bind:openModal={openConfirmModal}
|
||||||
on:confirm={deleteList}
|
on:confirm={deleteList}
|
||||||
></ConfirmModal>
|
></ConfirmModal>
|
||||||
|
<ListShareModal list={$list} bind:openShareModal></ListShareModal>
|
||||||
</main>
|
</main>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
#map {
|
@media only screen and (min-width: 768px) {
|
||||||
min-height: 600px;
|
#trail-map {
|
||||||
|
height: calc(100vh - 124px);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -12,6 +12,8 @@
|
|||||||
import Textarea from "$lib/components/base/textarea.svelte";
|
import Textarea from "$lib/components/base/textarea.svelte";
|
||||||
import MapWithElevationMultiple from "$lib/components/trail/map_with_elevation_multiple.svelte";
|
import MapWithElevationMultiple from "$lib/components/trail/map_with_elevation_multiple.svelte";
|
||||||
import type { Trail } from "$lib/models/trail.js";
|
import type { Trail } from "$lib/models/trail.js";
|
||||||
|
import { lists_create, lists_update } from "$lib/stores/list_store.js";
|
||||||
|
import { show_toast } from "$lib/stores/toast_store.js";
|
||||||
import { trails_show } from "$lib/stores/trail_store";
|
import { trails_show } from "$lib/stores/trail_store";
|
||||||
import { getFileURL } from "$lib/util/file_util.js";
|
import { getFileURL } from "$lib/util/file_util.js";
|
||||||
import {
|
import {
|
||||||
@@ -19,13 +21,16 @@
|
|||||||
formatElevation,
|
formatElevation,
|
||||||
formatTimeHHMM,
|
formatTimeHHMM,
|
||||||
} from "$lib/util/format_util";
|
} from "$lib/util/format_util";
|
||||||
import { lists_create, lists_update } from "$lib/stores/list_store.js";
|
import {
|
||||||
import { show_toast } from "$lib/stores/toast_store.js";
|
trail_share_create,
|
||||||
import { onMount } from "svelte";
|
trail_share_index,
|
||||||
|
} from "$lib/stores/trail_share_store.js";
|
||||||
|
import { TrailShare } from "$lib/models/trail_share.js";
|
||||||
|
import ConfirmModal from "$lib/components/confirm_modal.svelte";
|
||||||
|
|
||||||
export let data;
|
export let data;
|
||||||
|
|
||||||
let previewURL = "";
|
let previewURL = data.previewUrl ?? "";
|
||||||
let searchDropdownItems: SearchItem[] = [];
|
let searchDropdownItems: SearchItem[] = [];
|
||||||
|
|
||||||
let activeTrailIndex: number | null = null;
|
let activeTrailIndex: number | null = null;
|
||||||
@@ -34,6 +39,10 @@
|
|||||||
|
|
||||||
let loading: boolean = false;
|
let loading: boolean = false;
|
||||||
|
|
||||||
|
let newShares: TrailShare[] = [];
|
||||||
|
|
||||||
|
let openConfirmModal: () => void;
|
||||||
|
|
||||||
const { form, errors, handleChange, handleSubmit } = createForm<List>({
|
const { form, errors, handleChange, handleSubmit } = createForm<List>({
|
||||||
initialValues: data.list!,
|
initialValues: data.list!,
|
||||||
validationSchema: listSchema,
|
validationSchema: listSchema,
|
||||||
@@ -42,18 +51,34 @@
|
|||||||
document.getElementById("avatar") as HTMLInputElement
|
document.getElementById("avatar") as HTMLInputElement
|
||||||
).files![0];
|
).files![0];
|
||||||
loading = true;
|
loading = true;
|
||||||
if ($form.id) {
|
try {
|
||||||
await lists_update($form, avatarFile);
|
if ($form.id) {
|
||||||
} else {
|
await lists_update($form, avatarFile);
|
||||||
await lists_create($form, avatarFile);
|
await findNewTrailShares();
|
||||||
|
if (!newShares.length) {
|
||||||
|
show_toast({
|
||||||
|
type: "success",
|
||||||
|
icon: "check",
|
||||||
|
text: $_("list-saved-successfully"),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
await lists_create($form, avatarFile);
|
||||||
|
show_toast({
|
||||||
|
type: "success",
|
||||||
|
icon: "check",
|
||||||
|
text: $_("list-saved-successfully"),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
show_toast({
|
||||||
|
type: "error",
|
||||||
|
icon: "close",
|
||||||
|
text: $_("error-saving-list"),
|
||||||
|
});
|
||||||
|
} finally {
|
||||||
|
loading = false;
|
||||||
}
|
}
|
||||||
loading = false;
|
|
||||||
|
|
||||||
show_toast({
|
|
||||||
type: "success",
|
|
||||||
icon: "check",
|
|
||||||
text: $_("list-saved-successfully"),
|
|
||||||
});
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -88,14 +113,14 @@
|
|||||||
|
|
||||||
const response = await r.json();
|
const response = await r.json();
|
||||||
|
|
||||||
searchDropdownItems = response.results[0].hits.map(
|
searchDropdownItems = response.results[0].hits
|
||||||
(t: Record<string, any>) => ({
|
.filter((h: List) => !$form.trails?.includes(h.id!))
|
||||||
|
.map((t: Record<string, any>) => ({
|
||||||
text: t.name,
|
text: t.name,
|
||||||
description: `${t.location ?? "-"}`,
|
description: `${t.location ?? "-"}`,
|
||||||
value: t.id,
|
value: t.id,
|
||||||
icon: "route",
|
icon: "route",
|
||||||
}),
|
}));
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async function handleSearchClick(item: SearchItem) {
|
async function handleSearchClick(item: SearchItem) {
|
||||||
@@ -110,12 +135,57 @@
|
|||||||
(t) => t.id !== trail.id,
|
(t) => t.id !== trail.id,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function findNewTrailShares() {
|
||||||
|
const usersWithAccess: string[] = [
|
||||||
|
$form.author!,
|
||||||
|
...($form.expand?.list_share_via_list ?? []).map((s) => s.user),
|
||||||
|
];
|
||||||
|
for (const userId of usersWithAccess) {
|
||||||
|
const existingTrailShares = await trail_share_index({
|
||||||
|
user: userId,
|
||||||
|
});
|
||||||
|
for (const trail of $form.expand?.trails ?? []) {
|
||||||
|
if (trail.author == userId) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const trailShare = existingTrailShares.find(
|
||||||
|
(s) => s.trail == trail.id,
|
||||||
|
);
|
||||||
|
if (!trailShare) {
|
||||||
|
newShares.push(new TrailShare(userId, trail.id!, "view"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (newShares.length) {
|
||||||
|
openConfirmModal();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function updateTrailShares() {
|
||||||
|
for (const newShare of newShares) {
|
||||||
|
await trail_share_create(newShare);
|
||||||
|
}
|
||||||
|
newShares = [];
|
||||||
|
show_toast({
|
||||||
|
type: "success",
|
||||||
|
icon: "check",
|
||||||
|
text: $_("list-saved-successfully"),
|
||||||
|
});
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
<svelte:head>
|
||||||
|
<title
|
||||||
|
>{$form.id ? `${$form.name} | ${$_("edit")}` : $_("new-list")} | wanderer</title
|
||||||
|
>
|
||||||
|
</svelte:head>
|
||||||
<main class="grid grid-cols-1 md:grid-cols-[440px_1fr]">
|
<main class="grid grid-cols-1 md:grid-cols-[440px_1fr]">
|
||||||
<form
|
<form
|
||||||
id="list-form"
|
id="list-form"
|
||||||
class="overflow-y-auto overflow-x-hidden flex flex-col gap-4 px-8 order-1 md:order-none mt-8 md:mt-0"
|
class="flex flex-col gap-4 px-8 order-1 md:order-none mt-8 md:mt-0 overflow-y-scroll"
|
||||||
|
style="max-height: calc(100vh - 124px)"
|
||||||
on:submit={handleSubmit}
|
on:submit={handleSubmit}
|
||||||
>
|
>
|
||||||
<h2 class="text-2xl font-semibold">
|
<h2 class="text-2xl font-semibold">
|
||||||
@@ -248,10 +318,28 @@
|
|||||||
{loading}>{$_("save-list")}</Button
|
{loading}>{$_("save-list")}</Button
|
||||||
>
|
>
|
||||||
</form>
|
</form>
|
||||||
<MapWithElevationMultiple
|
<div id="trail-map" class="max-h-full">
|
||||||
trails={$form.expand?.trails ?? []}
|
<MapWithElevationMultiple
|
||||||
options={{flyToBounds: true}}
|
trails={$form.expand?.trails ?? []}
|
||||||
bind:activeTrailIndex
|
options={{ flyToBounds: true }}
|
||||||
bind:this={map}
|
bind:activeTrailIndex
|
||||||
></MapWithElevationMultiple>
|
bind:this={map}
|
||||||
|
></MapWithElevationMultiple>
|
||||||
|
</div>
|
||||||
</main>
|
</main>
|
||||||
|
|
||||||
|
<ConfirmModal
|
||||||
|
text={$_('list-share-warning-update')}
|
||||||
|
title={$_("confirm-share")}
|
||||||
|
action="confirm"
|
||||||
|
bind:openModal={openConfirmModal}
|
||||||
|
on:confirm={updateTrailShares}
|
||||||
|
></ConfirmModal>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
@media only screen and (min-width: 768px) {
|
||||||
|
#trail-map {
|
||||||
|
height: calc(100vh - 124px);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { List } from "$lib/models/list";
|
import { List } from "$lib/models/list";
|
||||||
import { lists_show } from "$lib/stores/list_store";
|
import { lists_show } from "$lib/stores/list_store";
|
||||||
|
import { getFileURL } from "$lib/util/file_util";
|
||||||
import { error, type Load } from "@sveltejs/kit";
|
import { error, type Load } from "@sveltejs/kit";
|
||||||
import { ClientResponseError } from "pocketbase";
|
import { ClientResponseError } from "pocketbase";
|
||||||
|
|
||||||
@@ -11,11 +12,13 @@ export const load: Load = async ({ params, fetch, data }) => {
|
|||||||
let list: List;
|
let list: List;
|
||||||
if (params.id === "new") {
|
if (params.id === "new") {
|
||||||
list = new List("", []);
|
list = new List("", []);
|
||||||
return { list: list }
|
return { list: list, previewUrl: "" }
|
||||||
} else {
|
} else {
|
||||||
try {
|
try {
|
||||||
list = await lists_show(params.id, fetch);
|
list = await lists_show(params.id, fetch);
|
||||||
return { list: list }
|
const previewURL = getFileURL(list, list.avatar);
|
||||||
|
|
||||||
|
return { list: list, previewUrl: previewURL }
|
||||||
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
if (e instanceof ClientResponseError) {
|
if (e instanceof ClientResponseError) {
|
||||||
|
|||||||
@@ -17,7 +17,7 @@
|
|||||||
</svelte:head>
|
</svelte:head>
|
||||||
<main class="grid grid-cols-1 md:grid-cols-[458px_1fr] gap-x-1 gap-y-4">
|
<main class="grid grid-cols-1 md:grid-cols-[458px_1fr] gap-x-1 gap-y-4">
|
||||||
<TrailInfoPanel trail={$trail} {markers}></TrailInfoPanel>
|
<TrailInfoPanel trail={$trail} {markers}></TrailInfoPanel>
|
||||||
<div id="trail-details" class=" sticky top-[62px]">
|
<div id="trail-details" class="sticky top-[62px]">
|
||||||
<MapWithElevation trail={$trail} bind:markers></MapWithElevation>
|
<MapWithElevation trail={$trail} bind:markers></MapWithElevation>
|
||||||
</div>
|
</div>
|
||||||
</main>
|
</main>
|
||||||
|
|||||||
Reference in New Issue
Block a user