adds settings collection
This commit is contained in:
130
db/migrations/1713016468_created_settings.go
Normal file
130
db/migrations/1713016468_created_settings.go
Normal file
@@ -0,0 +1,130 @@
|
|||||||
|
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": "uavt73rsqcn1n13",
|
||||||
|
"created": "2024-04-13 13:54:26.023Z",
|
||||||
|
"updated": "2024-04-13 13:54:26.023Z",
|
||||||
|
"name": "settings",
|
||||||
|
"type": "base",
|
||||||
|
"system": false,
|
||||||
|
"schema": [
|
||||||
|
{
|
||||||
|
"system": false,
|
||||||
|
"id": "0sepzvkh",
|
||||||
|
"name": "language",
|
||||||
|
"type": "select",
|
||||||
|
"required": false,
|
||||||
|
"presentable": false,
|
||||||
|
"unique": false,
|
||||||
|
"options": {
|
||||||
|
"maxSelect": 1,
|
||||||
|
"values": [
|
||||||
|
"en",
|
||||||
|
"de",
|
||||||
|
"fr",
|
||||||
|
"hu",
|
||||||
|
"nl",
|
||||||
|
"pl",
|
||||||
|
"pt",
|
||||||
|
"zh"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"system": false,
|
||||||
|
"id": "zwg1jl0d",
|
||||||
|
"name": "unit",
|
||||||
|
"type": "select",
|
||||||
|
"required": false,
|
||||||
|
"presentable": false,
|
||||||
|
"unique": false,
|
||||||
|
"options": {
|
||||||
|
"maxSelect": 1,
|
||||||
|
"values": [
|
||||||
|
"metric",
|
||||||
|
"imperial"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"system": false,
|
||||||
|
"id": "jo1zcsbu",
|
||||||
|
"name": "mapFocus",
|
||||||
|
"type": "select",
|
||||||
|
"required": false,
|
||||||
|
"presentable": false,
|
||||||
|
"unique": false,
|
||||||
|
"options": {
|
||||||
|
"maxSelect": 1,
|
||||||
|
"values": [
|
||||||
|
"trails",
|
||||||
|
"location"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"system": false,
|
||||||
|
"id": "ufhepjxo",
|
||||||
|
"name": "location",
|
||||||
|
"type": "json",
|
||||||
|
"required": false,
|
||||||
|
"presentable": false,
|
||||||
|
"unique": false,
|
||||||
|
"options": {
|
||||||
|
"maxSize": 2000000
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"system": false,
|
||||||
|
"id": "5uip7a4p",
|
||||||
|
"name": "user",
|
||||||
|
"type": "relation",
|
||||||
|
"required": false,
|
||||||
|
"presentable": false,
|
||||||
|
"unique": false,
|
||||||
|
"options": {
|
||||||
|
"collectionId": "_pb_users_auth_",
|
||||||
|
"cascadeDelete": true,
|
||||||
|
"minSelect": null,
|
||||||
|
"maxSelect": 1,
|
||||||
|
"displayFields": null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"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("uavt73rsqcn1n13")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return dao.DeleteCollection(collection)
|
||||||
|
})
|
||||||
|
}
|
||||||
162
db/migrations/1713018113_updated_users.go
Normal file
162
db/migrations/1713018113_updated_users.go
Normal file
@@ -0,0 +1,162 @@
|
|||||||
|
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"
|
||||||
|
"github.com/pocketbase/pocketbase/models/schema"
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
m.Register(func(db dbx.Builder) error {
|
||||||
|
dao := daos.New(db)
|
||||||
|
|
||||||
|
collection, err := dao.FindCollectionByNameOrId("_pb_users_auth_")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// remove
|
||||||
|
collection.Schema.RemoveField("wjofulpg")
|
||||||
|
|
||||||
|
// remove
|
||||||
|
collection.Schema.RemoveField("t1wlsqyp")
|
||||||
|
|
||||||
|
// remove
|
||||||
|
collection.Schema.RemoveField("fhxhln9g")
|
||||||
|
|
||||||
|
// remove
|
||||||
|
collection.Schema.RemoveField("wosrk4ue")
|
||||||
|
|
||||||
|
query := dao.RecordQuery("_pb_users_auth_")
|
||||||
|
|
||||||
|
users := []*models.Record{}
|
||||||
|
if err := query.All(&users); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
settingsCollection, err := dao.FindCollectionByNameOrId("settings")
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, user := range users {
|
||||||
|
settings := models.NewRecord(settingsCollection)
|
||||||
|
language := user.Get("language")
|
||||||
|
unit := user.Get("unit")
|
||||||
|
location := user.Get("location")
|
||||||
|
settings.Set("language", language)
|
||||||
|
settings.Set("unit", unit)
|
||||||
|
settings.Set("location", location)
|
||||||
|
settings.Set("user", user.Id)
|
||||||
|
settings.Set("mapFocus", "trails")
|
||||||
|
if err := dao.SaveRecord(settings); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return dao.SaveCollection(collection)
|
||||||
|
}, func(db dbx.Builder) error {
|
||||||
|
dao := daos.New(db)
|
||||||
|
|
||||||
|
collection, err := dao.FindCollectionByNameOrId("_pb_users_auth_")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// add
|
||||||
|
del_unit := &schema.SchemaField{}
|
||||||
|
if err := json.Unmarshal([]byte(`{
|
||||||
|
"system": false,
|
||||||
|
"id": "wjofulpg",
|
||||||
|
"name": "unit",
|
||||||
|
"type": "select",
|
||||||
|
"required": false,
|
||||||
|
"presentable": false,
|
||||||
|
"unique": false,
|
||||||
|
"options": {
|
||||||
|
"maxSelect": 1,
|
||||||
|
"values": [
|
||||||
|
"metric",
|
||||||
|
"imperial"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}`), del_unit); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
collection.Schema.AddField(del_unit)
|
||||||
|
|
||||||
|
// add
|
||||||
|
del_language := &schema.SchemaField{}
|
||||||
|
if err := json.Unmarshal([]byte(`{
|
||||||
|
"system": false,
|
||||||
|
"id": "t1wlsqyp",
|
||||||
|
"name": "language",
|
||||||
|
"type": "select",
|
||||||
|
"required": false,
|
||||||
|
"presentable": false,
|
||||||
|
"unique": false,
|
||||||
|
"options": {
|
||||||
|
"maxSelect": 1,
|
||||||
|
"values": [
|
||||||
|
"en",
|
||||||
|
"de",
|
||||||
|
"fr",
|
||||||
|
"hu",
|
||||||
|
"nl",
|
||||||
|
"pl",
|
||||||
|
"pt",
|
||||||
|
"zh"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}`), del_language); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
collection.Schema.AddField(del_language)
|
||||||
|
|
||||||
|
// add
|
||||||
|
del_mapView := &schema.SchemaField{}
|
||||||
|
if err := json.Unmarshal([]byte(`{
|
||||||
|
"system": false,
|
||||||
|
"id": "fhxhln9g",
|
||||||
|
"name": "mapView",
|
||||||
|
"type": "select",
|
||||||
|
"required": false,
|
||||||
|
"presentable": false,
|
||||||
|
"unique": false,
|
||||||
|
"options": {
|
||||||
|
"maxSelect": 1,
|
||||||
|
"values": [
|
||||||
|
"location",
|
||||||
|
"trails"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}`), del_mapView); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
collection.Schema.AddField(del_mapView)
|
||||||
|
|
||||||
|
// add
|
||||||
|
del_location := &schema.SchemaField{}
|
||||||
|
if err := json.Unmarshal([]byte(`{
|
||||||
|
"system": false,
|
||||||
|
"id": "wosrk4ue",
|
||||||
|
"name": "location",
|
||||||
|
"type": "json",
|
||||||
|
"required": false,
|
||||||
|
"presentable": false,
|
||||||
|
"unique": false,
|
||||||
|
"options": {
|
||||||
|
"maxSize": 2000000
|
||||||
|
}
|
||||||
|
}`), del_location); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
collection.Schema.AddField(del_location)
|
||||||
|
|
||||||
|
return dao.SaveCollection(collection)
|
||||||
|
})
|
||||||
|
}
|
||||||
46
db/migrations/1713023959_updated_settings.go
Normal file
46
db/migrations/1713023959_updated_settings.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("uavt73rsqcn1n13")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
collection.ListRule = types.Pointer("user = @request.auth.id")
|
||||||
|
|
||||||
|
collection.ViewRule = types.Pointer("user = @request.auth.id")
|
||||||
|
|
||||||
|
collection.CreateRule = types.Pointer("")
|
||||||
|
|
||||||
|
collection.UpdateRule = types.Pointer("user = @request.auth.id")
|
||||||
|
|
||||||
|
return dao.SaveCollection(collection)
|
||||||
|
}, func(db dbx.Builder) error {
|
||||||
|
dao := daos.New(db);
|
||||||
|
|
||||||
|
collection, err := dao.FindCollectionByNameOrId("uavt73rsqcn1n13")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
collection.ListRule = nil
|
||||||
|
|
||||||
|
collection.ViewRule = nil
|
||||||
|
|
||||||
|
collection.CreateRule = nil
|
||||||
|
|
||||||
|
collection.UpdateRule = nil
|
||||||
|
|
||||||
|
return dao.SaveCollection(collection)
|
||||||
|
})
|
||||||
|
}
|
||||||
98
db/migrations/1713027641_created_trails_bounding_box.go
Normal file
98
db/migrations/1713027641_created_trails_bounding_box.go
Normal file
@@ -0,0 +1,98 @@
|
|||||||
|
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": "urytyc428mwlbqq",
|
||||||
|
"created": "2024-04-13 17:00:41.541Z",
|
||||||
|
"updated": "2024-04-13 17:00:41.541Z",
|
||||||
|
"name": "trails_bounding_box",
|
||||||
|
"type": "view",
|
||||||
|
"system": false,
|
||||||
|
"schema": [
|
||||||
|
{
|
||||||
|
"system": false,
|
||||||
|
"id": "iyhsoisl",
|
||||||
|
"name": "max_lat",
|
||||||
|
"type": "json",
|
||||||
|
"required": false,
|
||||||
|
"presentable": false,
|
||||||
|
"unique": false,
|
||||||
|
"options": {
|
||||||
|
"maxSize": 1
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"system": false,
|
||||||
|
"id": "kx2qfztr",
|
||||||
|
"name": "max_lon",
|
||||||
|
"type": "json",
|
||||||
|
"required": false,
|
||||||
|
"presentable": false,
|
||||||
|
"unique": false,
|
||||||
|
"options": {
|
||||||
|
"maxSize": 1
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"system": false,
|
||||||
|
"id": "z4qsnjeb",
|
||||||
|
"name": "min_lat",
|
||||||
|
"type": "json",
|
||||||
|
"required": false,
|
||||||
|
"presentable": false,
|
||||||
|
"unique": false,
|
||||||
|
"options": {
|
||||||
|
"maxSize": 1
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"system": false,
|
||||||
|
"id": "p66xomdb",
|
||||||
|
"name": "min_lon",
|
||||||
|
"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 users.id, COALESCE(MAX(trails.lat), 0) AS max_lat, COALESCE(MAX(trails.lon), 0) AS max_lon, COALESCE(MIN(trails.lat), 0) AS min_lat, COALESCE(MIN(trails.lon), 0) AS min_lon FROM users LEFT JOIN trails ON users.id = trails.author GROUP BY users.id;"
|
||||||
|
}
|
||||||
|
}`
|
||||||
|
|
||||||
|
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("urytyc428mwlbqq")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return dao.DeleteCollection(collection)
|
||||||
|
})
|
||||||
|
}
|
||||||
205
db/migrations/1713028155_updated_trails_bounding_box.go
Normal file
205
db/migrations/1713028155_updated_trails_bounding_box.go
Normal file
@@ -0,0 +1,205 @@
|
|||||||
|
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("urytyc428mwlbqq")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
collection.ViewRule = types.Pointer("@request.auth.id = id")
|
||||||
|
|
||||||
|
// remove
|
||||||
|
collection.Schema.RemoveField("iyhsoisl")
|
||||||
|
|
||||||
|
// remove
|
||||||
|
collection.Schema.RemoveField("kx2qfztr")
|
||||||
|
|
||||||
|
// remove
|
||||||
|
collection.Schema.RemoveField("z4qsnjeb")
|
||||||
|
|
||||||
|
// remove
|
||||||
|
collection.Schema.RemoveField("p66xomdb")
|
||||||
|
|
||||||
|
// add
|
||||||
|
new_max_lat := &schema.SchemaField{}
|
||||||
|
if err := json.Unmarshal([]byte(`{
|
||||||
|
"system": false,
|
||||||
|
"id": "osfey1yx",
|
||||||
|
"name": "max_lat",
|
||||||
|
"type": "json",
|
||||||
|
"required": false,
|
||||||
|
"presentable": false,
|
||||||
|
"unique": false,
|
||||||
|
"options": {
|
||||||
|
"maxSize": 1
|
||||||
|
}
|
||||||
|
}`), new_max_lat); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
collection.Schema.AddField(new_max_lat)
|
||||||
|
|
||||||
|
// add
|
||||||
|
new_max_lon := &schema.SchemaField{}
|
||||||
|
if err := json.Unmarshal([]byte(`{
|
||||||
|
"system": false,
|
||||||
|
"id": "eohslzky",
|
||||||
|
"name": "max_lon",
|
||||||
|
"type": "json",
|
||||||
|
"required": false,
|
||||||
|
"presentable": false,
|
||||||
|
"unique": false,
|
||||||
|
"options": {
|
||||||
|
"maxSize": 1
|
||||||
|
}
|
||||||
|
}`), new_max_lon); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
collection.Schema.AddField(new_max_lon)
|
||||||
|
|
||||||
|
// add
|
||||||
|
new_min_lat := &schema.SchemaField{}
|
||||||
|
if err := json.Unmarshal([]byte(`{
|
||||||
|
"system": false,
|
||||||
|
"id": "kigvankd",
|
||||||
|
"name": "min_lat",
|
||||||
|
"type": "json",
|
||||||
|
"required": false,
|
||||||
|
"presentable": false,
|
||||||
|
"unique": false,
|
||||||
|
"options": {
|
||||||
|
"maxSize": 1
|
||||||
|
}
|
||||||
|
}`), new_min_lat); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
collection.Schema.AddField(new_min_lat)
|
||||||
|
|
||||||
|
// add
|
||||||
|
new_min_lon := &schema.SchemaField{}
|
||||||
|
if err := json.Unmarshal([]byte(`{
|
||||||
|
"system": false,
|
||||||
|
"id": "ifnks9mg",
|
||||||
|
"name": "min_lon",
|
||||||
|
"type": "json",
|
||||||
|
"required": false,
|
||||||
|
"presentable": false,
|
||||||
|
"unique": false,
|
||||||
|
"options": {
|
||||||
|
"maxSize": 1
|
||||||
|
}
|
||||||
|
}`), new_min_lon); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
collection.Schema.AddField(new_min_lon)
|
||||||
|
|
||||||
|
return dao.SaveCollection(collection)
|
||||||
|
}, func(db dbx.Builder) error {
|
||||||
|
dao := daos.New(db);
|
||||||
|
|
||||||
|
collection, err := dao.FindCollectionByNameOrId("urytyc428mwlbqq")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
collection.ViewRule = nil
|
||||||
|
|
||||||
|
// add
|
||||||
|
del_max_lat := &schema.SchemaField{}
|
||||||
|
if err := json.Unmarshal([]byte(`{
|
||||||
|
"system": false,
|
||||||
|
"id": "iyhsoisl",
|
||||||
|
"name": "max_lat",
|
||||||
|
"type": "json",
|
||||||
|
"required": false,
|
||||||
|
"presentable": false,
|
||||||
|
"unique": false,
|
||||||
|
"options": {
|
||||||
|
"maxSize": 1
|
||||||
|
}
|
||||||
|
}`), del_max_lat); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
collection.Schema.AddField(del_max_lat)
|
||||||
|
|
||||||
|
// add
|
||||||
|
del_max_lon := &schema.SchemaField{}
|
||||||
|
if err := json.Unmarshal([]byte(`{
|
||||||
|
"system": false,
|
||||||
|
"id": "kx2qfztr",
|
||||||
|
"name": "max_lon",
|
||||||
|
"type": "json",
|
||||||
|
"required": false,
|
||||||
|
"presentable": false,
|
||||||
|
"unique": false,
|
||||||
|
"options": {
|
||||||
|
"maxSize": 1
|
||||||
|
}
|
||||||
|
}`), del_max_lon); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
collection.Schema.AddField(del_max_lon)
|
||||||
|
|
||||||
|
// add
|
||||||
|
del_min_lat := &schema.SchemaField{}
|
||||||
|
if err := json.Unmarshal([]byte(`{
|
||||||
|
"system": false,
|
||||||
|
"id": "z4qsnjeb",
|
||||||
|
"name": "min_lat",
|
||||||
|
"type": "json",
|
||||||
|
"required": false,
|
||||||
|
"presentable": false,
|
||||||
|
"unique": false,
|
||||||
|
"options": {
|
||||||
|
"maxSize": 1
|
||||||
|
}
|
||||||
|
}`), del_min_lat); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
collection.Schema.AddField(del_min_lat)
|
||||||
|
|
||||||
|
// add
|
||||||
|
del_min_lon := &schema.SchemaField{}
|
||||||
|
if err := json.Unmarshal([]byte(`{
|
||||||
|
"system": false,
|
||||||
|
"id": "p66xomdb",
|
||||||
|
"name": "min_lon",
|
||||||
|
"type": "json",
|
||||||
|
"required": false,
|
||||||
|
"presentable": false,
|
||||||
|
"unique": false,
|
||||||
|
"options": {
|
||||||
|
"maxSize": 1
|
||||||
|
}
|
||||||
|
}`), del_min_lon); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
collection.Schema.AddField(del_min_lon)
|
||||||
|
|
||||||
|
// remove
|
||||||
|
collection.Schema.RemoveField("osfey1yx")
|
||||||
|
|
||||||
|
// remove
|
||||||
|
collection.Schema.RemoveField("eohslzky")
|
||||||
|
|
||||||
|
// remove
|
||||||
|
collection.Schema.RemoveField("kigvankd")
|
||||||
|
|
||||||
|
// remove
|
||||||
|
collection.Schema.RemoveField("ifnks9mg")
|
||||||
|
|
||||||
|
return dao.SaveCollection(collection)
|
||||||
|
})
|
||||||
|
}
|
||||||
10
web/package-lock.json
generated
10
web/package-lock.json
generated
@@ -26,7 +26,7 @@
|
|||||||
"pdfkit": "^0.15.0",
|
"pdfkit": "^0.15.0",
|
||||||
"photoswipe": "^5.4.3",
|
"photoswipe": "^5.4.3",
|
||||||
"pocketbase": "^0.21.0",
|
"pocketbase": "^0.21.0",
|
||||||
"qrcode-with-logos": "^1.0.5",
|
"qrcode": "^1.4.4",
|
||||||
"svelte-i18n": "^4.0.0",
|
"svelte-i18n": "^4.0.0",
|
||||||
"three": "^0.161.0",
|
"three": "^0.161.0",
|
||||||
"yup": "^1.3.3"
|
"yup": "^1.3.3"
|
||||||
@@ -4356,14 +4356,6 @@
|
|||||||
"node": ">=10.13.0"
|
"node": ">=10.13.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/qrcode-with-logos": {
|
|
||||||
"version": "1.0.5",
|
|
||||||
"resolved": "https://registry.npmjs.org/qrcode-with-logos/-/qrcode-with-logos-1.0.5.tgz",
|
|
||||||
"integrity": "sha512-ZHynBWdidxv57T5Lw8KBGpM6c/bwrL9buITPOrtX0PaoV0AgKkqHifdO+rKV7jMSiR/EcQWpvHI9ZarZonDq6g==",
|
|
||||||
"dependencies": {
|
|
||||||
"qrcode": "^1.4.4"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/querystringify": {
|
"node_modules/querystringify": {
|
||||||
"version": "2.2.0",
|
"version": "2.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.2.0.tgz",
|
||||||
|
|||||||
@@ -51,7 +51,7 @@
|
|||||||
"pdfkit": "^0.15.0",
|
"pdfkit": "^0.15.0",
|
||||||
"photoswipe": "^5.4.3",
|
"photoswipe": "^5.4.3",
|
||||||
"pocketbase": "^0.21.0",
|
"pocketbase": "^0.21.0",
|
||||||
"qrcode-with-logos": "^1.0.5",
|
"qrcode": "^1.4.4",
|
||||||
"svelte-i18n": "^4.0.0",
|
"svelte-i18n": "^4.0.0",
|
||||||
"three": "^0.161.0",
|
"three": "^0.161.0",
|
||||||
"yup": "^1.3.3"
|
"yup": "^1.3.3"
|
||||||
|
|||||||
4
web/src/app.d.ts
vendored
4
web/src/app.d.ts
vendored
@@ -14,7 +14,9 @@ declare global {
|
|||||||
interface Locals {
|
interface Locals {
|
||||||
pb: PocketBase
|
pb: PocketBase
|
||||||
ms: MeiliSearch
|
ms: MeiliSearch
|
||||||
user: AuthModel | null
|
user: AuthModel | null,
|
||||||
|
settings: Settings | null
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,8 @@
|
|||||||
|
import type { User } from '$lib/models/user'
|
||||||
import { pb } from '$lib/pocketbase'
|
import { pb } from '$lib/pocketbase'
|
||||||
import { currentUser, type User } from '$lib/stores/user_store'
|
import { settings_show } from '$lib/stores/settings_store';
|
||||||
|
import { currentUser } from '$lib/stores/user_store'
|
||||||
|
import { get } from 'svelte/store';
|
||||||
|
|
||||||
pb.authStore.loadFromCookie(document.cookie)
|
pb.authStore.loadFromCookie(document.cookie)
|
||||||
pb.authStore.onChange(() => {
|
pb.authStore.onChange(() => {
|
||||||
|
|||||||
@@ -1,11 +1,13 @@
|
|||||||
import { env } from '$env/dynamic/private'
|
import { env } from '$env/dynamic/private'
|
||||||
import { env as envPub } from '$env/dynamic/public'
|
import { env as envPub } from '$env/dynamic/public'
|
||||||
|
import type { Settings } from '$lib/models/settings'
|
||||||
|
|
||||||
import { pb } from '$lib/pocketbase'
|
import { pb } from '$lib/pocketbase'
|
||||||
import { isRouteProtected } from '$lib/util/authorization_util'
|
import { isRouteProtected } from '$lib/util/authorization_util'
|
||||||
import { redirect, type Handle } from '@sveltejs/kit'
|
import { redirect, type Handle } from '@sveltejs/kit'
|
||||||
import { MeiliSearch } from 'meilisearch'
|
import { MeiliSearch } from 'meilisearch'
|
||||||
import { locale } from 'svelte-i18n'
|
import { locale } from 'svelte-i18n'
|
||||||
|
import { get } from 'svelte/store'
|
||||||
|
|
||||||
export const handle: Handle = async ({ event, resolve }) => {
|
export const handle: Handle = async ({ event, resolve }) => {
|
||||||
// load the store data from the request cookie string
|
// load the store data from the request cookie string
|
||||||
@@ -34,8 +36,10 @@ export const handle: Handle = async ({ event, resolve }) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let meiliApiKey: string = "";
|
let meiliApiKey: string = "";
|
||||||
|
let settings: Settings | undefined;
|
||||||
if (pb.authStore.model) {
|
if (pb.authStore.model) {
|
||||||
meiliApiKey = pb.authStore.model.token
|
meiliApiKey = pb.authStore.model.token
|
||||||
|
settings = await pb.collection('settings').getFirstListItem<Settings>(`user="${pb.authStore.model.id}"`)
|
||||||
} else {
|
} else {
|
||||||
const r = await event.fetch(pb.buildUrl("/public/search/token"));
|
const r = await event.fetch(pb.buildUrl("/public/search/token"));
|
||||||
const response = await r.json();
|
const response = await r.json();
|
||||||
@@ -46,11 +50,15 @@ export const handle: Handle = async ({ event, resolve }) => {
|
|||||||
event.locals.ms = ms
|
event.locals.ms = ms
|
||||||
event.locals.pb = pb
|
event.locals.pb = pb
|
||||||
event.locals.user = pb.authStore.model
|
event.locals.user = pb.authStore.model
|
||||||
|
event.locals.settings = settings
|
||||||
|
|
||||||
const lang = pb.authStore.model?.language ?? event.request.headers.get('accept-language')?.split(',')[0]
|
const lang = settings?.language ?? event.request.headers.get('accept-language')?.split(',')[0]
|
||||||
|
|
||||||
if (lang) {
|
if (lang) {
|
||||||
locale.set(lang)
|
locale.set(lang)
|
||||||
|
if (pb.authStore.model) {
|
||||||
|
pb.authStore.model!.language = lang;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const response = await resolve(event)
|
const response = await resolve(event)
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
|
import { page } from "$app/stores";
|
||||||
import type { Trail } from "$lib/models/trail";
|
import type { Trail } from "$lib/models/trail";
|
||||||
import { currentUser } from "$lib/stores/user_store";
|
|
||||||
import { createMarkerFromWaypoint } from "$lib/util/leaflet_util";
|
import { createMarkerFromWaypoint } from "$lib/util/leaflet_util";
|
||||||
import type AutoGraticule from "$lib/vendor/leaflet-graticule/leaflet-auto-graticule";
|
import type AutoGraticule from "$lib/vendor/leaflet-graticule/leaflet-auto-graticule";
|
||||||
import type { Map, Marker } from "leaflet";
|
import type { Map, Marker } from "leaflet";
|
||||||
@@ -52,7 +52,7 @@
|
|||||||
closeBtn: false,
|
closeBtn: false,
|
||||||
followMarker: true,
|
followMarker: true,
|
||||||
autofitBounds: true,
|
autofitBounds: true,
|
||||||
imperial: $currentUser?.unit == "imperial" ?? false,
|
imperial: $page.data.settings?.unit == "imperial" ?? false,
|
||||||
reverseCoords: false,
|
reverseCoords: false,
|
||||||
acceleration: false,
|
acceleration: false,
|
||||||
slope: true,
|
slope: true,
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
|
import { page } from "$app/stores";
|
||||||
import type { Trail } from "$lib/models/trail";
|
import type { Trail } from "$lib/models/trail";
|
||||||
import { getFileURL } from "$lib/util/file_util";
|
import { getFileURL } from "$lib/util/file_util";
|
||||||
import {
|
import {
|
||||||
@@ -64,12 +65,12 @@
|
|||||||
<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
|
||||||
><i class="fa fa-left-right mr-2"></i>{formatDistance(
|
><i class="fa fa-left-right mr-2"></i>{formatDistance(
|
||||||
trail.distance,
|
trail.distance
|
||||||
)}</span
|
)}</span
|
||||||
>
|
>
|
||||||
<span
|
<span
|
||||||
><i class="fa fa-up-down mr-2"></i>{formatElevation(
|
><i class="fa fa-up-down mr-2"></i>{formatElevation(
|
||||||
trail.elevation_gain,
|
trail.elevation_gain
|
||||||
)}</span
|
)}</span
|
||||||
>
|
>
|
||||||
<span
|
<span
|
||||||
|
|||||||
@@ -12,6 +12,7 @@
|
|||||||
import Search, { type SearchItem } from "../base/search.svelte";
|
import Search, { type SearchItem } from "../base/search.svelte";
|
||||||
import Slider from "../base/slider.svelte";
|
import Slider from "../base/slider.svelte";
|
||||||
import type { SelectItem } from "../base/select.svelte";
|
import type { SelectItem } from "../base/select.svelte";
|
||||||
|
import { page } from "$app/stores";
|
||||||
|
|
||||||
export let categories: Category[];
|
export let categories: Category[];
|
||||||
export let filterExpanded: boolean = true;
|
export let filterExpanded: boolean = true;
|
||||||
|
|||||||
@@ -31,6 +31,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 { page } from "$app/stores";
|
||||||
|
|
||||||
export let trail: Trail;
|
export let trail: Trail;
|
||||||
export let mode: "overview" | "map" = "map";
|
export let mode: "overview" | "map" = "map";
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
|
import { page } from "$app/stores";
|
||||||
import type { Trail } from "$lib/models/trail";
|
import type { Trail } from "$lib/models/trail";
|
||||||
import { getFileURL } from "$lib/util/file_util";
|
import { getFileURL } from "$lib/util/file_util";
|
||||||
import {
|
import {
|
||||||
@@ -53,12 +54,12 @@
|
|||||||
<div class="flex mt-1 gap-4 text-sm text-gray-500">
|
<div class="flex mt-1 gap-4 text-sm text-gray-500">
|
||||||
<span
|
<span
|
||||||
><i class="fa fa-left-right mr-2"></i>{formatDistance(
|
><i class="fa fa-left-right mr-2"></i>{formatDistance(
|
||||||
trail.distance,
|
trail.distance
|
||||||
)}</span
|
)}</span
|
||||||
>
|
>
|
||||||
<span
|
<span
|
||||||
><i class="fa fa-up-down mr-2"></i>{formatElevation(
|
><i class="fa fa-up-down mr-2"></i>{formatElevation(
|
||||||
trail.elevation_gain,
|
trail.elevation_gain
|
||||||
)}</span
|
)}</span
|
||||||
>
|
>
|
||||||
<span
|
<span
|
||||||
|
|||||||
28
web/src/lib/models/settings.ts
Normal file
28
web/src/lib/models/settings.ts
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
|
||||||
|
class Settings {
|
||||||
|
id?: string;
|
||||||
|
unit?: "metric" | "imperial";
|
||||||
|
language?: "en" | "de" | "fr" | "hu" | "nl" | "pl" | "pt" | "zh";
|
||||||
|
mapFocus?: "trails" | "location";
|
||||||
|
location?: { name: string, lat: number, lon: number };
|
||||||
|
user?: string;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
unit: "metric" | "imperial",
|
||||||
|
language: "en" | "de" | "fr" | "hu" | "nl" | "pl" | "pt" | "zh",
|
||||||
|
mapFocus: "trails" | "location",
|
||||||
|
user: string,
|
||||||
|
params?: {
|
||||||
|
location: { name: string, lat: number, lon: number }
|
||||||
|
}
|
||||||
|
) {
|
||||||
|
this.unit = unit;
|
||||||
|
this.language = language;
|
||||||
|
this.mapFocus = mapFocus;
|
||||||
|
this.user = user;
|
||||||
|
this.location = params?.location;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export { Settings };
|
||||||
@@ -115,6 +115,13 @@ interface TrailFilterValues {
|
|||||||
max_duration: number,
|
max_duration: number,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface TrailBoundingBox {
|
||||||
|
max_lat: number,
|
||||||
|
min_lat: number,
|
||||||
|
max_lon: number,
|
||||||
|
min_lon: number,
|
||||||
|
}
|
||||||
|
|
||||||
export { Trail };
|
export { Trail };
|
||||||
|
|
||||||
export type { TrailFilter, TrailFilterValues };
|
export type { TrailFilter, TrailFilterValues, TrailBoundingBox };
|
||||||
10
web/src/lib/models/user.ts
Normal file
10
web/src/lib/models/user.ts
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
import type { Settings } from "./settings";
|
||||||
|
|
||||||
|
export type User = {
|
||||||
|
id: string,
|
||||||
|
username?: string,
|
||||||
|
email?: string,
|
||||||
|
password: string,
|
||||||
|
avatar?: string;
|
||||||
|
language?: string;
|
||||||
|
}
|
||||||
55
web/src/lib/stores/settings_store.ts
Normal file
55
web/src/lib/stores/settings_store.ts
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
import { invalidateAll } from "$app/navigation";
|
||||||
|
import { Settings } from "$lib/models/settings";
|
||||||
|
import { ClientResponseError } from "pocketbase";
|
||||||
|
|
||||||
|
export async function settings_show(userId: string, f: (url: RequestInfo | URL, config?: RequestInit) => Promise<Response> = fetch) {
|
||||||
|
const r = await f('/api/v1/settings/' + userId, {
|
||||||
|
method: 'GET',
|
||||||
|
})
|
||||||
|
|
||||||
|
const response = await r.json();
|
||||||
|
if (r.ok) {
|
||||||
|
return response;
|
||||||
|
} else {
|
||||||
|
throw new ClientResponseError(response)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function settings_create(settings: Settings) {
|
||||||
|
const r = await fetch('/api/v1/settings', {
|
||||||
|
method: 'PUT',
|
||||||
|
body: JSON.stringify(settings),
|
||||||
|
})
|
||||||
|
|
||||||
|
if (r.ok) {
|
||||||
|
return await r.json();
|
||||||
|
} else {
|
||||||
|
throw new ClientResponseError(await r.json())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function settings_update(settings: Settings) {
|
||||||
|
const r = await fetch('/api/v1/settings/' + settings.id, {
|
||||||
|
method: 'POST',
|
||||||
|
body: JSON.stringify(settings),
|
||||||
|
})
|
||||||
|
|
||||||
|
if (r.ok) {
|
||||||
|
await invalidateAll()
|
||||||
|
return await r.json();
|
||||||
|
} else {
|
||||||
|
throw new ClientResponseError(await r.json())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function settings_delete(settings: Settings) {
|
||||||
|
const r = await fetch('/api/v1/settings/' + settings.id, {
|
||||||
|
method: 'DELETE',
|
||||||
|
})
|
||||||
|
|
||||||
|
if (r.ok) {
|
||||||
|
return await r.json();
|
||||||
|
} else {
|
||||||
|
throw new ClientResponseError(await r.json())
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -370,6 +370,18 @@ export async function trails_get_filter_values(f: (url: RequestInfo | URL, confi
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function trails_get_bounding_box(f: (url: RequestInfo | URL, config?: RequestInit) => Promise<Response> = fetch): Promise<TrailFilterValues> {
|
||||||
|
const r = await f('/api/v1/trail/bounding-box', {
|
||||||
|
method: 'GET',
|
||||||
|
})
|
||||||
|
|
||||||
|
if (r.ok) {
|
||||||
|
return await r.json();
|
||||||
|
} else {
|
||||||
|
throw new ClientResponseError(await r.json())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export async function fetchGPX(trail: Trail, f: (url: RequestInfo | URL, config?: RequestInit) => Promise<Response> = fetch) {
|
export async function fetchGPX(trail: Trail, f: (url: RequestInfo | URL, config?: RequestInit) => Promise<Response> = fetch) {
|
||||||
if (!trail.gpx) {
|
if (!trail.gpx) {
|
||||||
return "";
|
return "";
|
||||||
|
|||||||
@@ -1,33 +1,26 @@
|
|||||||
|
import { Settings } from "$lib/models/settings";
|
||||||
|
import type { User } from "$lib/models/user";
|
||||||
import { pb } from "$lib/pocketbase";
|
import { pb } from "$lib/pocketbase";
|
||||||
import { ClientResponseError, type AuthMethodsList } from "pocketbase";
|
import { ClientResponseError, type AuthMethodsList } from "pocketbase";
|
||||||
import { writable, type Writable } from "svelte/store";
|
import { writable, type Writable } from "svelte/store";
|
||||||
|
import { settings_create } from "./settings_store";
|
||||||
export type User = {
|
|
||||||
id: string,
|
|
||||||
username?: string,
|
|
||||||
email?: string,
|
|
||||||
password: string,
|
|
||||||
avatar?: string;
|
|
||||||
unit?: "metric" | "imperial";
|
|
||||||
language?: "en" | "de" | "fr" | "hu" | "nl" | "pl" | "pt" | "zh" ;
|
|
||||||
location?: { name: string, lat: number, lon: number }
|
|
||||||
}
|
|
||||||
|
|
||||||
export const currentUser: Writable<User | null> = writable<User | null>()
|
export const currentUser: Writable<User | null> = writable<User | null>()
|
||||||
|
|
||||||
export async function users_create(user: User) {
|
export async function users_create(user: User) {
|
||||||
user.unit = "metric";
|
let r = await fetch('/api/v1/user', {
|
||||||
const r = await fetch('/api/v1/user', {
|
|
||||||
method: 'PUT',
|
method: 'PUT',
|
||||||
body: JSON.stringify({ ...user, passwordConfirm: user.password })
|
body: JSON.stringify({ ...user, passwordConfirm: user.password })
|
||||||
})
|
})
|
||||||
|
|
||||||
if (r.ok) {
|
if (!r.ok) {
|
||||||
return await r.json();
|
|
||||||
} else {
|
|
||||||
throw new ClientResponseError(await r.json())
|
throw new ClientResponseError(await r.json())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const createdUser: User = await r.json();
|
||||||
|
|
||||||
|
const settings = new Settings("metric", "en", "trails", createdUser.id!)
|
||||||
|
await settings_create(settings);
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function users_auth_methods(f: (url: RequestInfo | URL, config?: RequestInit) => Promise<Response> = fetch): Promise<AuthMethodsList> {
|
export async function users_auth_methods(f: (url: RequestInfo | URL, config?: RequestInit) => Promise<Response> = fetch): Promise<AuthMethodsList> {
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import { page } from "$app/stores";
|
||||||
import { currentUser } from "$lib/stores/user_store";
|
import { currentUser } from "$lib/stores/user_store";
|
||||||
import { get } from "svelte/store";
|
import { get } from "svelte/store";
|
||||||
|
|
||||||
@@ -17,7 +18,7 @@ export function formatDistance(meters?: number) {
|
|||||||
return "-";
|
return "-";
|
||||||
}
|
}
|
||||||
|
|
||||||
const unit = get(currentUser)?.unit ?? "metric";
|
const unit = get(page).data.settings?.unit ?? "metric";
|
||||||
|
|
||||||
if (unit == "metric") {
|
if (unit == "metric") {
|
||||||
if (meters >= 1000) {
|
if (meters >= 1000) {
|
||||||
@@ -38,7 +39,7 @@ export function formatElevation(meters?: number) {
|
|||||||
return "-";
|
return "-";
|
||||||
}
|
}
|
||||||
|
|
||||||
const unit = get(currentUser)?.unit ?? "metric";
|
const unit = get(page).data.settings?.unit ?? "metric";
|
||||||
|
|
||||||
if (unit == "metric") {
|
if (unit == "metric") {
|
||||||
return `${Math.round(meters)} m`
|
return `${Math.round(meters)} m`
|
||||||
|
|||||||
366
web/src/lib/vendor/qr-code-with-logos/index.js
vendored
Normal file
366
web/src/lib/vendor/qr-code-with-logos/index.js
vendored
Normal file
@@ -0,0 +1,366 @@
|
|||||||
|
import QRCode from "qrcode";
|
||||||
|
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
/*! *****************************************************************************
|
||||||
|
Copyright (c) Microsoft Corporation.
|
||||||
|
|
||||||
|
Permission to use, copy, modify, and/or distribute this software for any
|
||||||
|
purpose with or without fee is hereby granted.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
||||||
|
REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
||||||
|
AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
||||||
|
INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
||||||
|
LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
||||||
|
OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||||
|
PERFORMANCE OF THIS SOFTWARE.
|
||||||
|
***************************************************************************** */
|
||||||
|
|
||||||
|
function __awaiter(thisArg, _arguments, P, generator) {
|
||||||
|
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
||||||
|
return new (P || (P = Promise))(function (resolve, reject) {
|
||||||
|
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
||||||
|
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
||||||
|
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
||||||
|
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function __generator(thisArg, body) {
|
||||||
|
var _ = { label: 0, sent: function () { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g;
|
||||||
|
return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function () { return this; }), g;
|
||||||
|
function verb(n) { return function (v) { return step([n, v]); }; }
|
||||||
|
function step(op) {
|
||||||
|
if (f) throw new TypeError("Generator is already executing.");
|
||||||
|
while (_) try {
|
||||||
|
if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
|
||||||
|
if (y = 0, t) op = [op[0] & 2, t.value];
|
||||||
|
switch (op[0]) {
|
||||||
|
case 0: case 1: t = op; break;
|
||||||
|
case 4: _.label++; return { value: op[1], done: false };
|
||||||
|
case 5: _.label++; y = op[1]; op = [0]; continue;
|
||||||
|
case 7: op = _.ops.pop(); _.trys.pop(); continue;
|
||||||
|
default:
|
||||||
|
if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
|
||||||
|
if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
|
||||||
|
if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
|
||||||
|
if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
|
||||||
|
if (t[2]) _.ops.pop();
|
||||||
|
_.trys.pop(); continue;
|
||||||
|
}
|
||||||
|
op = body.call(thisArg, _);
|
||||||
|
} catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
|
||||||
|
if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* @Author: super
|
||||||
|
* @Date: 2019-06-27 16:29:43
|
||||||
|
* @Last Modified by: super
|
||||||
|
* @Last Modified time: 2019-06-27 17:46:21
|
||||||
|
*/
|
||||||
|
/**
|
||||||
|
* promisify promise化,使得promisify(func).then()更加方便,不用每次都構造 promise
|
||||||
|
* Making Promise more convenient, without having to construct a promise every time
|
||||||
|
* @param f {function} 異步函數
|
||||||
|
*/
|
||||||
|
var promisify = function (f) {
|
||||||
|
return function () {
|
||||||
|
var args = Array.prototype.slice.call(arguments);
|
||||||
|
return new Promise(function (resolve, reject) {
|
||||||
|
args.push(function (err, result) {
|
||||||
|
if (err)
|
||||||
|
reject(err);
|
||||||
|
else
|
||||||
|
resolve(result);
|
||||||
|
});
|
||||||
|
f.apply(null, args);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
};
|
||||||
|
/**
|
||||||
|
* 判斷是否是函數
|
||||||
|
* Determine if it is a function
|
||||||
|
* @param o {function} 函數
|
||||||
|
*/
|
||||||
|
function isFunction(o) {
|
||||||
|
return typeof o === "function";
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* 判斷是不是字符串
|
||||||
|
* Determine if it is a string
|
||||||
|
* @param o {string} 字符串
|
||||||
|
*/
|
||||||
|
function isString(o) {
|
||||||
|
return typeof o === "string";
|
||||||
|
}
|
||||||
|
|
||||||
|
// @ts-ignore
|
||||||
|
// import QRCode from "qrcode"
|
||||||
|
var toCanvas$1 = promisify(QRCode.toCanvas);
|
||||||
|
var renderQrCode = function (_a) {
|
||||||
|
var canvas = _a.canvas, content = _a.content, _b = _a.width, width = _b === void 0 ? 0 : _b, _c = _a.nodeQrCodeOptions, nodeQrCodeOptions = _c === void 0 ? {} : _c;
|
||||||
|
// 容错率,默认对内容少的二维码采用高容错率,内容多的二维码采用低容错率
|
||||||
|
// according to the content length to choose different errorCorrectionLevel
|
||||||
|
nodeQrCodeOptions.errorCorrectionLevel =
|
||||||
|
nodeQrCodeOptions.errorCorrectionLevel || getErrorCorrectionLevel(content);
|
||||||
|
return getOriginWidth(content, nodeQrCodeOptions).then(function (_width) {
|
||||||
|
// 得到原始比例后还原至设定值,再放大4倍以获取高清图
|
||||||
|
// Restore to the set value according to the original ratio, and then zoom in 4 times to get the HD image.
|
||||||
|
nodeQrCodeOptions.scale = width === 0 ? undefined : (width / _width) * 4;
|
||||||
|
// @ts-ignore
|
||||||
|
return toCanvas$1(canvas, content, nodeQrCodeOptions);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
// 得到原QrCode的大小,以便缩放得到正确的QrCode大小
|
||||||
|
// Get the size of the original QrCode
|
||||||
|
var getOriginWidth = function (content, nodeQrCodeOption) {
|
||||||
|
var _canvas = document.createElement("canvas");
|
||||||
|
// @ts-ignore
|
||||||
|
return toCanvas$1(_canvas, content, nodeQrCodeOption).then(function () { return _canvas.width; });
|
||||||
|
};
|
||||||
|
// 对于内容少的QrCode,增大容错率
|
||||||
|
// Increase the fault tolerance for QrCode with less content
|
||||||
|
var getErrorCorrectionLevel = function (content) {
|
||||||
|
if (content.length > 36) {
|
||||||
|
return "M";
|
||||||
|
}
|
||||||
|
else if (content.length > 16) {
|
||||||
|
return "Q";
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return "H";
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
var drawLogo = function (_a) {
|
||||||
|
var canvas = _a.canvas, logo = _a.logo;
|
||||||
|
if (!logo)
|
||||||
|
return Promise.resolve();
|
||||||
|
if (logo === '')
|
||||||
|
return Promise.resolve();
|
||||||
|
var canvasWidth = canvas.width;
|
||||||
|
if (isString(logo)) {
|
||||||
|
logo = { src: logo };
|
||||||
|
}
|
||||||
|
var _b = logo, _c = _b.logoSize, logoSize = _c === void 0 ? 0.15 : _c, _d = _b.borderColor, borderColor = _d === void 0 ? "#ffffff" : _d, _e = _b.bgColor, bgColor = _e === void 0 ? borderColor || "#ffffff" : _e, _f = _b.borderSize, borderSize = _f === void 0 ? 0.05 : _f, crossOrigin = _b.crossOrigin, _g = _b.borderRadius, borderRadius = _g === void 0 ? 8 : _g, _h = _b.logoRadius, logoRadius = _h === void 0 ? 0 : _h;
|
||||||
|
var logoSrc = typeof logo === "string" ? logo : logo.src;
|
||||||
|
var logoWidth = canvasWidth * logoSize;
|
||||||
|
var logoXY = (canvasWidth * (1 - logoSize)) / 2;
|
||||||
|
var logoBgWidth = canvasWidth * (logoSize + borderSize);
|
||||||
|
var logoBgXY = (canvasWidth * (1 - logoSize - borderSize)) / 2;
|
||||||
|
var ctx = canvas.getContext("2d");
|
||||||
|
// logo 底色, draw logo background color
|
||||||
|
canvasRoundRect(ctx)(logoBgXY, logoBgXY, logoBgWidth, logoBgWidth, borderRadius);
|
||||||
|
ctx.fillStyle = bgColor;
|
||||||
|
ctx.fill();
|
||||||
|
// logo
|
||||||
|
var image = new Image();
|
||||||
|
image.setAttribute("crossOrigin", crossOrigin || "anonymous");
|
||||||
|
image.src = logoSrc;
|
||||||
|
// 使用image绘制可以避免某些跨域情况
|
||||||
|
// Use image drawing to avoid some cross-domain situations
|
||||||
|
var drawLogoWithImage = function (image) {
|
||||||
|
ctx.drawImage(image, logoXY, logoXY, logoWidth, logoWidth);
|
||||||
|
};
|
||||||
|
// 使用canvas绘制以获得更多的功能
|
||||||
|
// Use canvas to draw more features, such as borderRadius
|
||||||
|
var drawLogoWithCanvas = function (image) {
|
||||||
|
var canvasImage = document.createElement("canvas");
|
||||||
|
canvasImage.width = logoXY + logoWidth;
|
||||||
|
canvasImage.height = logoXY + logoWidth;
|
||||||
|
canvasImage
|
||||||
|
.getContext("2d")
|
||||||
|
.drawImage(image, logoXY, logoXY, logoWidth, logoWidth);
|
||||||
|
canvasRoundRect(ctx)(logoXY, logoXY, logoWidth, logoWidth, logoRadius);
|
||||||
|
// @ts-ignore
|
||||||
|
ctx.fillStyle = ctx.createPattern(canvasImage, "no-repeat");
|
||||||
|
ctx.fill();
|
||||||
|
};
|
||||||
|
// 将 logo绘制到 canvas上
|
||||||
|
// Draw the logo on the canvas
|
||||||
|
return new Promise(function (resolve, reject) {
|
||||||
|
image.onload = function () {
|
||||||
|
logoRadius ? drawLogoWithCanvas(image) : drawLogoWithImage(image);
|
||||||
|
resolve();
|
||||||
|
};
|
||||||
|
image.onerror = function () {
|
||||||
|
reject('logo load fail!');
|
||||||
|
};
|
||||||
|
});
|
||||||
|
};
|
||||||
|
// draw radius
|
||||||
|
var canvasRoundRect = function (ctx) {
|
||||||
|
return function (x, y, w, h, r) {
|
||||||
|
var minSize = Math.min(w, h);
|
||||||
|
if (r > minSize / 2) {
|
||||||
|
r = minSize / 2;
|
||||||
|
}
|
||||||
|
ctx.beginPath();
|
||||||
|
ctx.moveTo(x + r, y);
|
||||||
|
ctx.arcTo(x + w, y, x + w, y + h, r);
|
||||||
|
ctx.arcTo(x + w, y + h, x, y + h, r);
|
||||||
|
ctx.arcTo(x, y + h, x, y, r);
|
||||||
|
ctx.arcTo(x, y, x + w, y, r);
|
||||||
|
ctx.closePath();
|
||||||
|
return ctx;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
* @Author: super
|
||||||
|
* @Date: 2019-06-27 16:29:34
|
||||||
|
* @Last Modified by: super
|
||||||
|
* @Last Modified time: 2019-06-27 16:47:22
|
||||||
|
*/
|
||||||
|
var toCanvas = function (options) {
|
||||||
|
return renderQrCode(options).then(function () { return drawLogo(options); });
|
||||||
|
};
|
||||||
|
|
||||||
|
var toImage = function (options, instance) {
|
||||||
|
return __awaiter(this, void 0, void 0, function () {
|
||||||
|
var canvas, image, _a, downloadName, download, startDownload;
|
||||||
|
return __generator(this, function (_b) {
|
||||||
|
switch (_b.label) {
|
||||||
|
case 0:
|
||||||
|
canvas = options.canvas;
|
||||||
|
if (options.logo) {
|
||||||
|
if (isString(options.logo)) {
|
||||||
|
options.logo = { src: options.logo };
|
||||||
|
}
|
||||||
|
options.logo.crossOrigin = 'Anonymous';
|
||||||
|
}
|
||||||
|
if (!!instance.ifCanvasDrawed) return [3 /*break*/, 2];
|
||||||
|
return [4 /*yield*/, toCanvas(options)];
|
||||||
|
case 1:
|
||||||
|
_b.sent();
|
||||||
|
_b.label = 2;
|
||||||
|
case 2:
|
||||||
|
image = options.image, _a = options.downloadName, downloadName = _a === void 0 ? 'qr-code' : _a;
|
||||||
|
download = options.download;
|
||||||
|
if (canvas.toDataURL()) {
|
||||||
|
image.src = canvas.toDataURL();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
throw new Error('Can not get the canvas DataURL');
|
||||||
|
}
|
||||||
|
instance.ifImageCreated = true;
|
||||||
|
if (download !== true && !isFunction(download)) {
|
||||||
|
return [2 /*return*/];
|
||||||
|
}
|
||||||
|
download = download === true ? function (start) { return start(); } : download;
|
||||||
|
startDownload = function () {
|
||||||
|
return saveImage(image, downloadName);
|
||||||
|
};
|
||||||
|
if (download) {
|
||||||
|
return [2 /*return*/, download(startDownload)];
|
||||||
|
}
|
||||||
|
return [2 /*return*/, Promise.resolve()];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
/**save image */
|
||||||
|
var saveImage = function (image, name) {
|
||||||
|
return new Promise(function (resolve, reject) {
|
||||||
|
try {
|
||||||
|
var dataURL = image.src;
|
||||||
|
var link = document.createElement('a');
|
||||||
|
link.download = name;
|
||||||
|
link.href = dataURL;
|
||||||
|
link.dispatchEvent(new MouseEvent('click'));
|
||||||
|
resolve(true);
|
||||||
|
}
|
||||||
|
catch (err) {
|
||||||
|
reject(err);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
var version = "1.0.5";
|
||||||
|
|
||||||
|
/*
|
||||||
|
* @Author: super
|
||||||
|
* @Date: 2019-06-27 16:29:31
|
||||||
|
* @Last Modified by: suporka
|
||||||
|
* @Last Modified time: 2020-03-04 12:24:50
|
||||||
|
*/
|
||||||
|
var QrCodeWithLogo = /** @class */ (function () {
|
||||||
|
function QrCodeWithLogo(option) {
|
||||||
|
this.ifCanvasDrawed = false;
|
||||||
|
this.ifImageCreated = false;
|
||||||
|
this.defaultOption = {
|
||||||
|
canvas: undefined,
|
||||||
|
image: undefined,
|
||||||
|
content: ''
|
||||||
|
};
|
||||||
|
this.option = Object.assign(this.defaultOption, option);
|
||||||
|
if (!this.option.canvas)
|
||||||
|
this.option.canvas = document.createElement("canvas");
|
||||||
|
if (!this.option.image)
|
||||||
|
this.option.image = document.createElement("img");
|
||||||
|
this.toCanvas().then(this.toImage.bind(this));
|
||||||
|
}
|
||||||
|
QrCodeWithLogo.prototype.toCanvas = function () {
|
||||||
|
var _this = this;
|
||||||
|
return toCanvas.call(this, this.option).then(function () {
|
||||||
|
_this.ifCanvasDrawed = true;
|
||||||
|
return Promise.resolve();
|
||||||
|
});
|
||||||
|
};
|
||||||
|
QrCodeWithLogo.prototype.toImage = function () {
|
||||||
|
return toImage(this.option, this);
|
||||||
|
};
|
||||||
|
QrCodeWithLogo.prototype.downloadImage = function (name) {
|
||||||
|
if (name === void 0) { name = 'qrcode.png'; }
|
||||||
|
return __awaiter(this, void 0, void 0, function () {
|
||||||
|
return __generator(this, function (_a) {
|
||||||
|
switch (_a.label) {
|
||||||
|
case 0:
|
||||||
|
if (!!this.ifImageCreated) return [3 /*break*/, 2];
|
||||||
|
return [4 /*yield*/, this.toImage()];
|
||||||
|
case 1:
|
||||||
|
_a.sent();
|
||||||
|
_a.label = 2;
|
||||||
|
case 2: return [2 /*return*/, saveImage(this.option.image, name)];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
QrCodeWithLogo.prototype.getImage = function () {
|
||||||
|
return __awaiter(this, void 0, Promise, function () {
|
||||||
|
return __generator(this, function (_a) {
|
||||||
|
switch (_a.label) {
|
||||||
|
case 0:
|
||||||
|
if (!!this.ifImageCreated) return [3 /*break*/, 2];
|
||||||
|
return [4 /*yield*/, this.toImage()];
|
||||||
|
case 1:
|
||||||
|
_a.sent();
|
||||||
|
_a.label = 2;
|
||||||
|
case 2: return [2 /*return*/, this.option.image];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
QrCodeWithLogo.prototype.getCanvas = function () {
|
||||||
|
return __awaiter(this, void 0, Promise, function () {
|
||||||
|
return __generator(this, function (_a) {
|
||||||
|
switch (_a.label) {
|
||||||
|
case 0:
|
||||||
|
if (!!this.ifCanvasDrawed) return [3 /*break*/, 2];
|
||||||
|
return [4 /*yield*/, this.toCanvas()];
|
||||||
|
case 1:
|
||||||
|
_a.sent();
|
||||||
|
_a.label = 2;
|
||||||
|
case 2: return [2 /*return*/, this.option.canvas];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
QrCodeWithLogo.version = version;
|
||||||
|
return QrCodeWithLogo;
|
||||||
|
}());
|
||||||
|
|
||||||
|
export default QrCodeWithLogo;
|
||||||
7
web/src/routes/+layout.server.ts
Normal file
7
web/src/routes/+layout.server.ts
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
// +layout.ts
|
||||||
|
import '$lib/i18n';
|
||||||
|
import type { LayoutServerLoad } from './$types';
|
||||||
|
|
||||||
|
export const load: LayoutServerLoad = async ({ locals }) => {
|
||||||
|
return { settings: locals.settings }
|
||||||
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
<script>
|
<script lang="ts">
|
||||||
import { beforeNavigate, goto } from "$app/navigation";
|
import { beforeNavigate, goto } from "$app/navigation";
|
||||||
import Toast from "$lib/components/base/toast.svelte";
|
import Toast from "$lib/components/base/toast.svelte";
|
||||||
import Footer from "$lib/components/footer.svelte";
|
import Footer from "$lib/components/footer.svelte";
|
||||||
@@ -13,7 +13,7 @@
|
|||||||
beforeNavigate((n) => {
|
beforeNavigate((n) => {
|
||||||
if (!$currentUser && isRouteProtected(n.to?.url?.pathname ?? "")) {
|
if (!$currentUser && isRouteProtected(n.to?.url?.pathname ?? "")) {
|
||||||
n.cancel();
|
n.cancel();
|
||||||
goto("/login?r="+n.to?.url?.pathname);
|
goto("/login?r=" + n.to?.url?.pathname);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
14
web/src/routes/api/v1/settings/+server.ts
Normal file
14
web/src/routes/api/v1/settings/+server.ts
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
import type { Settings } from '$lib/models/settings';
|
||||||
|
import { pb } from '$lib/pocketbase';
|
||||||
|
import { error, json, type RequestEvent } from '@sveltejs/kit';
|
||||||
|
|
||||||
|
export async function PUT(event: RequestEvent) {
|
||||||
|
const data = await event.request.json();
|
||||||
|
|
||||||
|
try {
|
||||||
|
const r = await pb.collection('settings').create<Settings>(data)
|
||||||
|
return json(r);
|
||||||
|
} catch (e: any) {
|
||||||
|
throw error(e.status, e)
|
||||||
|
}
|
||||||
|
}
|
||||||
32
web/src/routes/api/v1/settings/[id]/+server.ts
Normal file
32
web/src/routes/api/v1/settings/[id]/+server.ts
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
import type { Settings } from "$lib/models/settings";
|
||||||
|
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('settings').getFirstListItem<Settings>(`user="${event.params.id}"`)
|
||||||
|
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 await pb.collection("settings").update<Settings>(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('settings').delete(event.params.id as string)
|
||||||
|
return json({ 'acknowledged': r });
|
||||||
|
} catch (e: any) {
|
||||||
|
throw error(e.status, e)
|
||||||
|
}
|
||||||
|
}
|
||||||
20
web/src/routes/api/v1/trail/bounding-box/+server.ts
Normal file
20
web/src/routes/api/v1/trail/bounding-box/+server.ts
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
import { type TrailBoundingBox, type TrailFilterValues } from '$lib/models/trail';
|
||||||
|
import { pb } from '$lib/pocketbase';
|
||||||
|
import { error, json, type RequestEvent } from '@sveltejs/kit';
|
||||||
|
|
||||||
|
export async function GET(event: RequestEvent) {
|
||||||
|
if (!pb.authStore.model) {
|
||||||
|
return json({
|
||||||
|
max_lat: 0,
|
||||||
|
min_lat: 0,
|
||||||
|
max_lon: 0,
|
||||||
|
min_lon: 0
|
||||||
|
});
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
const r = await pb.collection('trails_bounding_box').getOne<TrailBoundingBox>(pb.authStore.model!.id)
|
||||||
|
return json(r)
|
||||||
|
} catch (e: any) {
|
||||||
|
throw error(e.status || 500, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -8,7 +8,12 @@
|
|||||||
import EmptyStateSearch from "$lib/components/empty_states/empty_state_search.svelte";
|
import EmptyStateSearch from "$lib/components/empty_states/empty_state_search.svelte";
|
||||||
import TrailCard from "$lib/components/trail/trail_card.svelte";
|
import TrailCard from "$lib/components/trail/trail_card.svelte";
|
||||||
import TrailFilterPanel from "$lib/components/trail/trail_filter_panel.svelte";
|
import TrailFilterPanel from "$lib/components/trail/trail_filter_panel.svelte";
|
||||||
import type { Trail, TrailFilter } from "$lib/models/trail";
|
import type { Settings } from "$lib/models/settings";
|
||||||
|
import type {
|
||||||
|
Trail,
|
||||||
|
TrailBoundingBox,
|
||||||
|
TrailFilter,
|
||||||
|
} from "$lib/models/trail";
|
||||||
import { categories } from "$lib/stores/category_store";
|
import { categories } from "$lib/stores/category_store";
|
||||||
import {
|
import {
|
||||||
trails,
|
trails,
|
||||||
@@ -48,6 +53,8 @@
|
|||||||
let showMap: boolean = true;
|
let showMap: boolean = true;
|
||||||
|
|
||||||
const filter: TrailFilter = $page.data.filter;
|
const filter: TrailFilter = $page.data.filter;
|
||||||
|
const maxBoundingBox: TrailBoundingBox = $page.data.boundingBox;
|
||||||
|
const settings: Settings = $page.data.settings;
|
||||||
|
|
||||||
onMount(async () => {
|
onMount(async () => {
|
||||||
L = (await import("leaflet")).default;
|
L = (await import("leaflet")).default;
|
||||||
@@ -90,11 +97,18 @@
|
|||||||
];
|
];
|
||||||
|
|
||||||
map.fitBounds(boundingBox);
|
map.fitBounds(boundingBox);
|
||||||
} else if ($currentUser && $currentUser.location) {
|
} else if (settings && settings.mapFocus == "trails") {
|
||||||
map.setView(
|
const boundingBox: LatLngBoundsExpression = [
|
||||||
[$currentUser.location.lat, $currentUser.location.lon],
|
[maxBoundingBox.max_lat, maxBoundingBox.min_lon],
|
||||||
12,
|
[maxBoundingBox.min_lat, maxBoundingBox.max_lon],
|
||||||
);
|
];
|
||||||
|
map.fitBounds(boundingBox);
|
||||||
|
} else if (
|
||||||
|
settings &&
|
||||||
|
settings.mapFocus == "location" &&
|
||||||
|
settings.location
|
||||||
|
) {
|
||||||
|
map.setView([settings.location.lat, settings.location.lon], 12);
|
||||||
} else {
|
} else {
|
||||||
navigator.geolocation.getCurrentPosition(
|
navigator.geolocation.getCurrentPosition(
|
||||||
(position) => {
|
(position) => {
|
||||||
|
|||||||
@@ -1,9 +1,10 @@
|
|||||||
import type { TrailFilter } from "$lib/models/trail";
|
import type { TrailFilter } from "$lib/models/trail";
|
||||||
import { categories_index } from "$lib/stores/category_store";
|
import { categories_index } from "$lib/stores/category_store";
|
||||||
import { trails, trails_get_filter_values } from "$lib/stores/trail_store";
|
import { trails, trails_get_bounding_box, trails_get_filter_values } from "$lib/stores/trail_store";
|
||||||
import type { ServerLoad } from "@sveltejs/kit";
|
import type { ServerLoad } from "@sveltejs/kit";
|
||||||
|
|
||||||
export const load: ServerLoad = async ({ params, locals, fetch }) => {
|
export const load: ServerLoad = async ({ params, locals, fetch }) => {
|
||||||
|
const boundingBox = await trails_get_bounding_box(fetch);
|
||||||
const filterValues = await trails_get_filter_values(fetch);
|
const filterValues = await trails_get_filter_values(fetch);
|
||||||
|
|
||||||
const filter: TrailFilter = {
|
const filter: TrailFilter = {
|
||||||
@@ -27,5 +28,5 @@ export const load: ServerLoad = async ({ params, locals, fetch }) => {
|
|||||||
|
|
||||||
trails.set([])
|
trails.set([])
|
||||||
|
|
||||||
return { filter: filter }
|
return { filter: filter, boundingBox: boundingBox }
|
||||||
};
|
};
|
||||||
@@ -4,13 +4,12 @@
|
|||||||
import "$lib/assets/fonts/IBMPlexSans-SemiBold-bold";
|
import "$lib/assets/fonts/IBMPlexSans-SemiBold-bold";
|
||||||
import "$lib/assets/fonts/fa-solid-900-normal";
|
import "$lib/assets/fonts/fa-solid-900-normal";
|
||||||
import Button from "$lib/components/base/button.svelte";
|
import Button from "$lib/components/base/button.svelte";
|
||||||
import Select, {
|
import Select from "$lib/components/base/select.svelte";
|
||||||
type SelectItem,
|
|
||||||
} from "$lib/components/base/select.svelte";
|
|
||||||
import LogoText from "$lib/components/logo/logo_text.svelte";
|
import LogoText from "$lib/components/logo/logo_text.svelte";
|
||||||
import MapWithElevation from "$lib/components/trail/map_with_elevation.svelte";
|
import MapWithElevation from "$lib/components/trail/map_with_elevation.svelte";
|
||||||
|
import type { Settings } from "$lib/models/settings";
|
||||||
|
import { show_toast } from "$lib/stores/toast_store";
|
||||||
import { trail } from "$lib/stores/trail_store";
|
import { trail } from "$lib/stores/trail_store";
|
||||||
import { currentUser } from "$lib/stores/user_store";
|
|
||||||
import {
|
import {
|
||||||
formatDistance,
|
formatDistance,
|
||||||
formatElevation,
|
formatElevation,
|
||||||
@@ -22,21 +21,22 @@
|
|||||||
} from "$lib/util/leaflet_util";
|
} from "$lib/util/leaflet_util";
|
||||||
import { createRect, createText } from "$lib/util/svg_util";
|
import { createRect, createText } from "$lib/util/svg_util";
|
||||||
import "$lib/vendor/leaflet-elevation/src/index.css";
|
import "$lib/vendor/leaflet-elevation/src/index.css";
|
||||||
|
import type AutoGraticule from "$lib/vendor/leaflet-graticule/leaflet-auto-graticule";
|
||||||
import leafletImage from "$lib/vendor/leaflet-image/leaflet-image.js";
|
import leafletImage from "$lib/vendor/leaflet-image/leaflet-image.js";
|
||||||
|
import { Canvg } from "canvg";
|
||||||
import { jsPDF } from "jspdf";
|
import { jsPDF } from "jspdf";
|
||||||
import type { Map } from "leaflet";
|
import type { Map } from "leaflet";
|
||||||
import "leaflet.awesome-markers/dist/leaflet.awesome-markers.css";
|
import "leaflet.awesome-markers/dist/leaflet.awesome-markers.css";
|
||||||
import "leaflet/dist/leaflet.css";
|
import "leaflet/dist/leaflet.css";
|
||||||
import QrCodeWithLogo from "qrcode-with-logos";
|
import QrCodeWithLogo from "$lib/vendor/qr-code-with-logos/index";
|
||||||
import { onMount, tick } from "svelte";
|
import { onMount, tick } from "svelte";
|
||||||
import { _ } from "svelte-i18n";
|
import { _ } from "svelte-i18n";
|
||||||
import { Canvg } from "canvg";
|
|
||||||
import type AutoGraticule from "$lib/vendor/leaflet-graticule/leaflet-auto-graticule";
|
|
||||||
import { show_toast } from "$lib/stores/toast_store";
|
|
||||||
|
|
||||||
let map: Map;
|
let map: Map;
|
||||||
let graticule: AutoGraticule;
|
let graticule: AutoGraticule;
|
||||||
|
|
||||||
|
const settings: Settings = $page.data.settings;
|
||||||
|
|
||||||
const paperSizes: { text: string; value: keyof typeof paperDimensions }[] =
|
const paperSizes: { text: string; value: keyof typeof paperDimensions }[] =
|
||||||
[
|
[
|
||||||
{ text: "A4", value: "a4" },
|
{ text: "A4", value: "a4" },
|
||||||
@@ -375,7 +375,7 @@
|
|||||||
|
|
||||||
let oneUnitInPixels = calculatePixelPerMeter(
|
let oneUnitInPixels = calculatePixelPerMeter(
|
||||||
map,
|
map,
|
||||||
$currentUser && $currentUser.unit == "imperial" ? 1609.34 : 1000,
|
settings && settings.unit == "imperial" ? 1609.34 : 1000,
|
||||||
);
|
);
|
||||||
let unitsInRuler = width / oneUnitInPixels;
|
let unitsInRuler = width / oneUnitInPixels;
|
||||||
|
|
||||||
@@ -430,7 +430,7 @@
|
|||||||
|
|
||||||
svg.appendChild(
|
svg.appendChild(
|
||||||
createText(
|
createText(
|
||||||
$currentUser && $currentUser.unit == "imperial" ? "MI" : "KM",
|
settings && settings.unit == "imperial" ? "MI" : "KM",
|
||||||
segmentCount * multiplier * oneUnitInPixels - 20,
|
segmentCount * multiplier * oneUnitInPixels - 20,
|
||||||
height / 2 - 7,
|
height / 2 - 7,
|
||||||
),
|
),
|
||||||
@@ -535,7 +535,9 @@
|
|||||||
>
|
>
|
||||||
<span
|
<span
|
||||||
><i class="fa fa-left-right mr-2"
|
><i class="fa fa-left-right mr-2"
|
||||||
></i>{formatDistance($trail.distance)}</span
|
></i>{formatDistance(
|
||||||
|
$trail.distance,
|
||||||
|
)}</span
|
||||||
>
|
>
|
||||||
<span
|
<span
|
||||||
><i class="fa fa-up-down mr-2"
|
><i class="fa fa-up-down mr-2"
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { goto } from "$app/navigation";
|
import { goto } from "$app/navigation";
|
||||||
|
import { page } from "$app/stores";
|
||||||
import RadioGroup, {
|
import RadioGroup, {
|
||||||
type RadioItem,
|
type RadioItem,
|
||||||
} from "$lib/components/base/radio_group.svelte";
|
} from "$lib/components/base/radio_group.svelte";
|
||||||
@@ -10,6 +11,7 @@
|
|||||||
type SelectItem,
|
type SelectItem,
|
||||||
} from "$lib/components/base/select.svelte";
|
} from "$lib/components/base/select.svelte";
|
||||||
import ConfirmModal from "$lib/components/confirm_modal.svelte";
|
import ConfirmModal from "$lib/components/confirm_modal.svelte";
|
||||||
|
import { settings_update } from "$lib/stores/settings_store";
|
||||||
import {
|
import {
|
||||||
currentUser,
|
currentUser,
|
||||||
logout,
|
logout,
|
||||||
@@ -21,6 +23,8 @@
|
|||||||
import { onMount } from "svelte";
|
import { onMount } from "svelte";
|
||||||
import { _, locale } from "svelte-i18n";
|
import { _, locale } from "svelte-i18n";
|
||||||
|
|
||||||
|
const settings = $page.data.settings;
|
||||||
|
|
||||||
const languages: SelectItem[] = [
|
const languages: SelectItem[] = [
|
||||||
{ text: $_("chinese"), value: "zh" },
|
{ text: $_("chinese"), value: "zh" },
|
||||||
{ text: $_("german"), value: "de" },
|
{ text: $_("german"), value: "de" },
|
||||||
@@ -32,12 +36,18 @@
|
|||||||
{ text: $_("portuguese"), value: "pt" },
|
{ text: $_("portuguese"), value: "pt" },
|
||||||
];
|
];
|
||||||
|
|
||||||
|
const mapFocus: SelectItem[] = [
|
||||||
|
{ text: $_("trail", { values: { n: 2 } }), value: "trails" },
|
||||||
|
{ text: $_("location"), value: "location" },
|
||||||
|
];
|
||||||
|
|
||||||
const units: RadioItem[] = [
|
const units: RadioItem[] = [
|
||||||
{ text: $_("metric"), value: "metric" },
|
{ text: $_("metric"), value: "metric" },
|
||||||
{ text: $_("imperial"), value: "imperial" },
|
{ text: $_("imperial"), value: "imperial" },
|
||||||
];
|
];
|
||||||
|
|
||||||
let selectedLanguage = "en";
|
let selectedLanguage = "en";
|
||||||
|
let selectedMapFocus = "trails";
|
||||||
|
|
||||||
let searchDropdownItems: SearchItem[] = [];
|
let searchDropdownItems: SearchItem[] = [];
|
||||||
let citySearchQuery: string = "";
|
let citySearchQuery: string = "";
|
||||||
@@ -45,8 +55,9 @@
|
|||||||
let openConfirmModal: () => void;
|
let openConfirmModal: () => void;
|
||||||
|
|
||||||
onMount(() => {
|
onMount(() => {
|
||||||
citySearchQuery = $currentUser?.location?.name ?? "";
|
citySearchQuery = settings?.location?.name ?? "";
|
||||||
selectedLanguage = $currentUser?.language || "en";
|
selectedLanguage = settings?.language || "en";
|
||||||
|
selectedMapFocus = settings?.mapFocus ?? "trails";
|
||||||
});
|
});
|
||||||
|
|
||||||
async function searchCities(q: string) {
|
async function searchCities(q: string) {
|
||||||
@@ -67,8 +78,8 @@
|
|||||||
|
|
||||||
async function handleSearchClick(item: SearchItem) {
|
async function handleSearchClick(item: SearchItem) {
|
||||||
citySearchQuery = item.text;
|
citySearchQuery = item.text;
|
||||||
await users_update({
|
await settings_update({
|
||||||
id: $currentUser!.id,
|
id: settings?.id!,
|
||||||
location: {
|
location: {
|
||||||
name: item.value.name,
|
name: item.value.name,
|
||||||
lat: item.value.lat,
|
lat: item.value.lat,
|
||||||
@@ -90,8 +101,8 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function handleUnitSelection(e: RadioItem) {
|
async function handleUnitSelection(e: RadioItem) {
|
||||||
await users_update({
|
await settings_update({
|
||||||
id: $currentUser!.id,
|
id: settings!.id,
|
||||||
unit: e.value as "imperial" | "metric",
|
unit: e.value as "imperial" | "metric",
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -100,12 +111,19 @@
|
|||||||
value: "en" | "de" | "fr" | "hu" | "nl" | "pl" | "pt",
|
value: "en" | "de" | "fr" | "hu" | "nl" | "pl" | "pt",
|
||||||
) {
|
) {
|
||||||
locale.set(value);
|
locale.set(value);
|
||||||
await users_update({
|
await settings_update({
|
||||||
id: $currentUser!.id,
|
id: settings!.id,
|
||||||
language: value,
|
language: value,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function handleMapFocusSelection(value: "trails" | "location") {
|
||||||
|
await settings_update({
|
||||||
|
id: settings!.id,
|
||||||
|
mapFocus: value,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
async function deleteAccount() {
|
async function deleteAccount() {
|
||||||
await users_delete($currentUser!);
|
await users_delete($currentUser!);
|
||||||
logout();
|
logout();
|
||||||
@@ -156,17 +174,6 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="space-y-6 rounded-xl p-4">
|
<div class="space-y-6 rounded-xl p-4">
|
||||||
<h3 class="text-2xl font-semibold">{$_("settings")}</h3>
|
<h3 class="text-2xl font-semibold">{$_("settings")}</h3>
|
||||||
|
|
||||||
<div>
|
|
||||||
<h5 class="font-medium mb-1">{$_("default-location")}</h5>
|
|
||||||
<Search
|
|
||||||
items={searchDropdownItems}
|
|
||||||
placeholder="{$_('search-cities')}..."
|
|
||||||
bind:value={citySearchQuery}
|
|
||||||
on:update={(e) => searchCities(e.detail)}
|
|
||||||
on:click={(e) => handleSearchClick(e.detail)}
|
|
||||||
></Search>
|
|
||||||
</div>
|
|
||||||
<div>
|
<div>
|
||||||
<h5 class="font-medium mb-1">{$_("language")}</h5>
|
<h5 class="font-medium mb-1">{$_("language")}</h5>
|
||||||
<Select
|
<Select
|
||||||
@@ -180,10 +187,30 @@
|
|||||||
<RadioGroup
|
<RadioGroup
|
||||||
name="unit"
|
name="unit"
|
||||||
items={units}
|
items={units}
|
||||||
selected={$currentUser.unit == "metric" ? 0 : 1}
|
selected={settings?.unit == "metric" ? 0 : 1}
|
||||||
on:change={(e) => handleUnitSelection(e.detail)}
|
on:change={(e) => handleUnitSelection(e.detail)}
|
||||||
></RadioGroup>
|
></RadioGroup>
|
||||||
</div>
|
</div>
|
||||||
|
<div>
|
||||||
|
<h5 class="font-medium mb-1">Focus map on</h5>
|
||||||
|
|
||||||
|
<Select
|
||||||
|
items={mapFocus}
|
||||||
|
bind:value={selectedMapFocus}
|
||||||
|
on:change={(e) => handleMapFocusSelection(e.detail)}
|
||||||
|
></Select>
|
||||||
|
{#if selectedMapFocus == "location"}
|
||||||
|
<div class="mt-3">
|
||||||
|
<Search
|
||||||
|
items={searchDropdownItems}
|
||||||
|
placeholder="{$_('search-cities')}..."
|
||||||
|
bind:value={citySearchQuery}
|
||||||
|
on:update={(e) => searchCities(e.detail)}
|
||||||
|
on:click={(e) => handleSearchClick(e.detail)}
|
||||||
|
></Search>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
<hr class="border-input-border" />
|
<hr class="border-input-border" />
|
||||||
<div class="space-y-4">
|
<div class="space-y-4">
|
||||||
<h4 class="text-xl text-red-400">{$_("danger-zone")}</h4>
|
<h4 class="text-xl text-red-400">{$_("danger-zone")}</h4>
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
|
import { page } from "$app/stores";
|
||||||
import Button from "$lib/components/base/button.svelte";
|
import Button from "$lib/components/base/button.svelte";
|
||||||
import Datepicker from "$lib/components/base/datepicker.svelte";
|
import Datepicker from "$lib/components/base/datepicker.svelte";
|
||||||
import Select from "$lib/components/base/select.svelte";
|
import Select from "$lib/components/base/select.svelte";
|
||||||
|
|||||||
Reference in New Issue
Block a user