mirror of
https://github.com/utopia-os/utopia-ui.git
synced 2026-03-01 12:44:17 +00:00
Merge branch 'main' into layout-color-enhancements
This commit is contained in:
commit
0999220aa5
@ -86,7 +86,11 @@ function MapContainer({ layers, map }: { layers: LayerProps[]; map: any }) {
|
||||
expandLayerControl={map.expand_layer_control}
|
||||
tileServerUrl={map.tile_server_url}
|
||||
tileServerAttribution={map.tile_server_attribution}
|
||||
tilesType={map.tiles_type}
|
||||
maplibreStyle={map.maplibre_style}
|
||||
showFullscreenControl={map.show_fullscreen_control}
|
||||
zoomOffset={map.zoom_offset}
|
||||
tileSize={map.tile_size}
|
||||
>
|
||||
{layers &&
|
||||
apis &&
|
||||
@ -120,7 +124,7 @@ function MapContainer({ layers, map }: { layers: LayerProps[]; map: any }) {
|
||||
parameterField={
|
||||
layer.itemType.custom_profile_url ? 'extended.external_profile_id' : 'id'
|
||||
}
|
||||
text={layer.itemType.botton_label ?? 'Profile'}
|
||||
text={layer.itemType.button_label ?? 'Profile'}
|
||||
target={layer.itemType.custom_profile_url ? '_blank' : '_self'}
|
||||
/>
|
||||
)}
|
||||
|
||||
@ -22,10 +22,21 @@ export default defineConfig({
|
||||
plugins: [react(), tailwindcss(), tsConfigPaths()],
|
||||
build: {
|
||||
sourcemap: true,
|
||||
modulePreload: {
|
||||
// Don't preload maplibre chunks - only load when actually needed
|
||||
resolveDependencies: (_filename, deps) => {
|
||||
return deps.filter((dep) => !dep.includes('maplibre'))
|
||||
},
|
||||
},
|
||||
rollupOptions: {
|
||||
output: {
|
||||
manualChunks: (id) => {
|
||||
if (id.includes('lib/src')) {
|
||||
// Handle lib source (dev) or dist (prod)
|
||||
if (id.includes('lib/src') || id.includes('lib/dist')) {
|
||||
// Separate chunk for MapLibre components
|
||||
if (id.includes('MapLibre')) {
|
||||
return 'maplibre-layer'
|
||||
}
|
||||
return 'utopia-ui'
|
||||
}
|
||||
if (id.includes('node_modules')) {
|
||||
@ -38,6 +49,10 @@ export default defineConfig({
|
||||
if (id.includes('leaflet')) {
|
||||
return 'leaflet'
|
||||
}
|
||||
// Separate chunk for maplibre-gl
|
||||
if (id.includes('maplibre-gl')) {
|
||||
return 'maplibre-gl'
|
||||
}
|
||||
return 'vendor'
|
||||
}
|
||||
},
|
||||
|
||||
@ -60,6 +60,8 @@
|
||||
"public_registration_role": null,
|
||||
"public_registration_email_filter": null,
|
||||
"visual_editor_urls": null,
|
||||
"accepted_terms": true,
|
||||
"project_id": "0199aa52-4dd7-7293-984a-f2af93b5f8fd",
|
||||
"_syncId": "55f04445-0c26-4201-ab9c-d6e0fbadf6bf"
|
||||
}
|
||||
]
|
||||
|
||||
@ -0,0 +1,46 @@
|
||||
{
|
||||
"collection": "maps",
|
||||
"field": "maplibre",
|
||||
"type": "alias",
|
||||
"meta": {
|
||||
"collection": "maps",
|
||||
"conditions": [
|
||||
{
|
||||
"hidden": false,
|
||||
"name": "Show when maplibre tiles selected",
|
||||
"options": null,
|
||||
"rule": {
|
||||
"_and": [
|
||||
{
|
||||
"tiles_type": {
|
||||
"_eq": "maplibre"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
],
|
||||
"display": null,
|
||||
"display_options": null,
|
||||
"field": "maplibre",
|
||||
"group": "tile_server",
|
||||
"hidden": true,
|
||||
"interface": "group-detail",
|
||||
"note": "Configuration for MapLibre GL vector tiles",
|
||||
"options": {
|
||||
"start": "open"
|
||||
},
|
||||
"readonly": false,
|
||||
"required": false,
|
||||
"sort": 3,
|
||||
"special": [
|
||||
"alias",
|
||||
"no-data",
|
||||
"group"
|
||||
],
|
||||
"translations": null,
|
||||
"validation": null,
|
||||
"validation_message": null,
|
||||
"width": "full"
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,45 @@
|
||||
{
|
||||
"collection": "maps",
|
||||
"field": "maplibre_style",
|
||||
"type": "string",
|
||||
"meta": {
|
||||
"collection": "maps",
|
||||
"conditions": null,
|
||||
"display": null,
|
||||
"display_options": null,
|
||||
"field": "maplibre_style",
|
||||
"group": "maplibre",
|
||||
"hidden": false,
|
||||
"interface": "input",
|
||||
"note": "MapLibre style URL (default: OpenFreeMap Liberty style)",
|
||||
"options": {
|
||||
"placeholder": "https://tiles.openfreemap.org/styles/liberty"
|
||||
},
|
||||
"readonly": false,
|
||||
"required": false,
|
||||
"sort": 1,
|
||||
"special": null,
|
||||
"translations": null,
|
||||
"validation": null,
|
||||
"validation_message": null,
|
||||
"width": "full"
|
||||
},
|
||||
"schema": {
|
||||
"name": "maplibre_style",
|
||||
"table": "maps",
|
||||
"data_type": "character varying",
|
||||
"default_value": null,
|
||||
"max_length": 255,
|
||||
"numeric_precision": null,
|
||||
"numeric_scale": null,
|
||||
"is_nullable": true,
|
||||
"is_unique": false,
|
||||
"is_indexed": false,
|
||||
"is_primary_key": false,
|
||||
"is_generated": false,
|
||||
"generation_expression": null,
|
||||
"has_auto_increment": false,
|
||||
"foreign_key_table": null,
|
||||
"foreign_key_column": null
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,46 @@
|
||||
{
|
||||
"collection": "maps",
|
||||
"field": "raster_tiles",
|
||||
"type": "alias",
|
||||
"meta": {
|
||||
"collection": "maps",
|
||||
"conditions": [
|
||||
{
|
||||
"hidden": false,
|
||||
"name": "Show when raster tiles selected",
|
||||
"options": null,
|
||||
"rule": {
|
||||
"_and": [
|
||||
{
|
||||
"tiles_type": {
|
||||
"_eq": "raster"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
],
|
||||
"display": null,
|
||||
"display_options": null,
|
||||
"field": "raster_tiles",
|
||||
"group": "tile_server",
|
||||
"hidden": true,
|
||||
"interface": "group-detail",
|
||||
"note": "Configuration for raster tile layers",
|
||||
"options": {
|
||||
"start": "open"
|
||||
},
|
||||
"readonly": false,
|
||||
"required": false,
|
||||
"sort": 2,
|
||||
"special": [
|
||||
"alias",
|
||||
"no-data",
|
||||
"group"
|
||||
],
|
||||
"translations": null,
|
||||
"validation": null,
|
||||
"validation_message": null,
|
||||
"width": "full"
|
||||
}
|
||||
}
|
||||
@ -11,11 +11,13 @@
|
||||
"group": "tile_server",
|
||||
"hidden": false,
|
||||
"interface": "input",
|
||||
"note": null,
|
||||
"options": null,
|
||||
"note": "Attribution text for raster tiles",
|
||||
"options": {
|
||||
"placeholder": "© <a href=\"https://www.openstreetmap.org/copyright\">OpenStreetMap</a>"
|
||||
},
|
||||
"readonly": false,
|
||||
"required": false,
|
||||
"sort": 2,
|
||||
"sort": 4,
|
||||
"special": null,
|
||||
"translations": null,
|
||||
"validation": null,
|
||||
|
||||
@ -8,11 +8,13 @@
|
||||
"display": null,
|
||||
"display_options": null,
|
||||
"field": "tile_server_url",
|
||||
"group": "tile_server",
|
||||
"group": "raster_tiles",
|
||||
"hidden": false,
|
||||
"interface": "input",
|
||||
"note": null,
|
||||
"options": null,
|
||||
"note": "Raster tile server URL template (e.g., https://tile.osmand.net/hd/{z}/{x}/{y}.png)",
|
||||
"options": {
|
||||
"placeholder": "https://tile.osmand.net/hd/{z}/{x}/{y}.png"
|
||||
},
|
||||
"readonly": false,
|
||||
"required": false,
|
||||
"sort": 1,
|
||||
|
||||
@ -0,0 +1,43 @@
|
||||
{
|
||||
"collection": "maps",
|
||||
"field": "tile_size",
|
||||
"type": "integer",
|
||||
"meta": {
|
||||
"collection": "maps",
|
||||
"conditions": null,
|
||||
"display": null,
|
||||
"display_options": null,
|
||||
"field": "tile_size",
|
||||
"group": "raster_tiles",
|
||||
"hidden": false,
|
||||
"interface": "input",
|
||||
"note": null,
|
||||
"options": null,
|
||||
"readonly": false,
|
||||
"required": false,
|
||||
"sort": 2,
|
||||
"special": null,
|
||||
"translations": null,
|
||||
"validation": null,
|
||||
"validation_message": null,
|
||||
"width": "half"
|
||||
},
|
||||
"schema": {
|
||||
"name": "tile_size",
|
||||
"table": "maps",
|
||||
"data_type": "integer",
|
||||
"default_value": 256,
|
||||
"max_length": null,
|
||||
"numeric_precision": 32,
|
||||
"numeric_scale": 0,
|
||||
"is_nullable": true,
|
||||
"is_unique": false,
|
||||
"is_indexed": false,
|
||||
"is_primary_key": false,
|
||||
"is_generated": false,
|
||||
"generation_expression": null,
|
||||
"has_auto_increment": false,
|
||||
"foreign_key_table": null,
|
||||
"foreign_key_column": null
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,54 @@
|
||||
{
|
||||
"collection": "maps",
|
||||
"field": "tiles_type",
|
||||
"type": "string",
|
||||
"meta": {
|
||||
"collection": "maps",
|
||||
"conditions": null,
|
||||
"display": null,
|
||||
"display_options": null,
|
||||
"field": "tiles_type",
|
||||
"group": "tile_server",
|
||||
"hidden": false,
|
||||
"interface": "select-dropdown",
|
||||
"note": "Choose between raster tiles or vector tiles (MapLibre GL)",
|
||||
"options": {
|
||||
"choices": [
|
||||
{
|
||||
"text": "Raster Tiles",
|
||||
"value": "raster"
|
||||
},
|
||||
{
|
||||
"text": "Vector Tiles (MapLibre GL)",
|
||||
"value": "maplibre"
|
||||
}
|
||||
]
|
||||
},
|
||||
"readonly": false,
|
||||
"required": false,
|
||||
"sort": 1,
|
||||
"special": null,
|
||||
"translations": null,
|
||||
"validation": null,
|
||||
"validation_message": null,
|
||||
"width": "full"
|
||||
},
|
||||
"schema": {
|
||||
"name": "tiles_type",
|
||||
"table": "maps",
|
||||
"data_type": "character varying",
|
||||
"default_value": "raster",
|
||||
"max_length": 255,
|
||||
"numeric_precision": null,
|
||||
"numeric_scale": null,
|
||||
"is_nullable": true,
|
||||
"is_unique": false,
|
||||
"is_indexed": false,
|
||||
"is_primary_key": false,
|
||||
"is_generated": false,
|
||||
"generation_expression": null,
|
||||
"has_auto_increment": false,
|
||||
"foreign_key_table": null,
|
||||
"foreign_key_column": null
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,45 @@
|
||||
{
|
||||
"collection": "maps",
|
||||
"field": "zoom_offset",
|
||||
"type": "integer",
|
||||
"meta": {
|
||||
"collection": "maps",
|
||||
"conditions": null,
|
||||
"display": null,
|
||||
"display_options": null,
|
||||
"field": "zoom_offset",
|
||||
"group": "raster_tiles",
|
||||
"hidden": false,
|
||||
"interface": "input",
|
||||
"note": null,
|
||||
"options": {
|
||||
"min": 0
|
||||
},
|
||||
"readonly": false,
|
||||
"required": false,
|
||||
"sort": 3,
|
||||
"special": null,
|
||||
"translations": null,
|
||||
"validation": null,
|
||||
"validation_message": null,
|
||||
"width": "half"
|
||||
},
|
||||
"schema": {
|
||||
"name": "zoom_offset",
|
||||
"table": "maps",
|
||||
"data_type": "integer",
|
||||
"default_value": -1,
|
||||
"max_length": null,
|
||||
"numeric_precision": 32,
|
||||
"numeric_scale": 0,
|
||||
"is_nullable": true,
|
||||
"is_unique": false,
|
||||
"is_indexed": false,
|
||||
"is_primary_key": false,
|
||||
"is_generated": false,
|
||||
"generation_expression": null,
|
||||
"has_auto_increment": false,
|
||||
"foreign_key_table": null,
|
||||
"foreign_key_column": null
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,32 @@
|
||||
{
|
||||
"collection": "types",
|
||||
"field": "Header",
|
||||
"type": "alias",
|
||||
"meta": {
|
||||
"collection": "types",
|
||||
"conditions": null,
|
||||
"display": null,
|
||||
"display_options": null,
|
||||
"field": "Header",
|
||||
"group": null,
|
||||
"hidden": false,
|
||||
"interface": "group-detail",
|
||||
"note": null,
|
||||
"options": {
|
||||
"headerIcon": "credit_card",
|
||||
"start": "closed"
|
||||
},
|
||||
"readonly": false,
|
||||
"required": false,
|
||||
"sort": 7,
|
||||
"special": [
|
||||
"alias",
|
||||
"no-data",
|
||||
"group"
|
||||
],
|
||||
"translations": null,
|
||||
"validation": null,
|
||||
"validation_message": null,
|
||||
"width": "full"
|
||||
}
|
||||
}
|
||||
@ -13,11 +13,12 @@
|
||||
"interface": "group-detail",
|
||||
"note": null,
|
||||
"options": {
|
||||
"headerIcon": "lab_profile"
|
||||
"headerIcon": "lab_profile",
|
||||
"start": "closed"
|
||||
},
|
||||
"readonly": false,
|
||||
"required": false,
|
||||
"sort": 9,
|
||||
"sort": 10,
|
||||
"special": [
|
||||
"alias",
|
||||
"no-data",
|
||||
|
||||
@ -0,0 +1,59 @@
|
||||
{
|
||||
"collection": "types",
|
||||
"field": "cta_button_label",
|
||||
"type": "string",
|
||||
"meta": {
|
||||
"collection": "types",
|
||||
"conditions": [
|
||||
{
|
||||
"hidden": true,
|
||||
"name": "show cta button",
|
||||
"readonly": false,
|
||||
"required": false,
|
||||
"rule": {
|
||||
"_and": [
|
||||
{
|
||||
"show_cta_button": {
|
||||
"_eq": false
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
],
|
||||
"display": null,
|
||||
"display_options": null,
|
||||
"field": "cta_button_label",
|
||||
"group": "header_elements",
|
||||
"hidden": false,
|
||||
"interface": "input",
|
||||
"note": null,
|
||||
"options": null,
|
||||
"readonly": false,
|
||||
"required": false,
|
||||
"sort": 5,
|
||||
"special": null,
|
||||
"translations": null,
|
||||
"validation": null,
|
||||
"validation_message": null,
|
||||
"width": "half"
|
||||
},
|
||||
"schema": {
|
||||
"name": "cta_button_label",
|
||||
"table": "types",
|
||||
"data_type": "character varying",
|
||||
"default_value": null,
|
||||
"max_length": 255,
|
||||
"numeric_precision": null,
|
||||
"numeric_scale": null,
|
||||
"is_nullable": true,
|
||||
"is_unique": false,
|
||||
"is_indexed": false,
|
||||
"is_primary_key": false,
|
||||
"is_generated": false,
|
||||
"generation_expression": null,
|
||||
"has_auto_increment": false,
|
||||
"foreign_key_table": null,
|
||||
"foreign_key_column": null
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,29 @@
|
||||
{
|
||||
"collection": "types",
|
||||
"field": "header_elements",
|
||||
"type": "alias",
|
||||
"meta": {
|
||||
"collection": "types",
|
||||
"conditions": null,
|
||||
"display": null,
|
||||
"display_options": null,
|
||||
"field": "header_elements",
|
||||
"group": "Header",
|
||||
"hidden": false,
|
||||
"interface": "group-raw",
|
||||
"note": null,
|
||||
"options": null,
|
||||
"readonly": false,
|
||||
"required": false,
|
||||
"sort": 3,
|
||||
"special": [
|
||||
"alias",
|
||||
"no-data",
|
||||
"group"
|
||||
],
|
||||
"translations": null,
|
||||
"validation": null,
|
||||
"validation_message": null,
|
||||
"width": "full"
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,45 @@
|
||||
{
|
||||
"collection": "types",
|
||||
"field": "show_cta_button",
|
||||
"type": "boolean",
|
||||
"meta": {
|
||||
"collection": "types",
|
||||
"conditions": null,
|
||||
"display": null,
|
||||
"display_options": null,
|
||||
"field": "show_cta_button",
|
||||
"group": "header_elements",
|
||||
"hidden": false,
|
||||
"interface": "boolean",
|
||||
"note": null,
|
||||
"options": null,
|
||||
"readonly": false,
|
||||
"required": false,
|
||||
"sort": 4,
|
||||
"special": [
|
||||
"cast-boolean"
|
||||
],
|
||||
"translations": null,
|
||||
"validation": null,
|
||||
"validation_message": null,
|
||||
"width": "half"
|
||||
},
|
||||
"schema": {
|
||||
"name": "show_cta_button",
|
||||
"table": "types",
|
||||
"data_type": "boolean",
|
||||
"default_value": false,
|
||||
"max_length": null,
|
||||
"numeric_precision": null,
|
||||
"numeric_scale": null,
|
||||
"is_nullable": true,
|
||||
"is_unique": false,
|
||||
"is_indexed": false,
|
||||
"is_primary_key": false,
|
||||
"is_generated": false,
|
||||
"generation_expression": null,
|
||||
"has_auto_increment": false,
|
||||
"foreign_key_table": null,
|
||||
"foreign_key_column": null
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,45 @@
|
||||
{
|
||||
"collection": "types",
|
||||
"field": "show_navigation_button",
|
||||
"type": "boolean",
|
||||
"meta": {
|
||||
"collection": "types",
|
||||
"conditions": null,
|
||||
"display": null,
|
||||
"display_options": null,
|
||||
"field": "show_navigation_button",
|
||||
"group": "header_elements",
|
||||
"hidden": false,
|
||||
"interface": "boolean",
|
||||
"note": null,
|
||||
"options": null,
|
||||
"readonly": false,
|
||||
"required": false,
|
||||
"sort": 2,
|
||||
"special": [
|
||||
"cast-boolean"
|
||||
],
|
||||
"translations": null,
|
||||
"validation": null,
|
||||
"validation_message": null,
|
||||
"width": "half"
|
||||
},
|
||||
"schema": {
|
||||
"name": "show_navigation_button",
|
||||
"table": "types",
|
||||
"data_type": "boolean",
|
||||
"default_value": false,
|
||||
"max_length": null,
|
||||
"numeric_precision": null,
|
||||
"numeric_scale": null,
|
||||
"is_nullable": true,
|
||||
"is_unique": false,
|
||||
"is_indexed": false,
|
||||
"is_primary_key": false,
|
||||
"is_generated": false,
|
||||
"generation_expression": null,
|
||||
"has_auto_increment": false,
|
||||
"foreign_key_table": null,
|
||||
"foreign_key_column": null
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,45 @@
|
||||
{
|
||||
"collection": "types",
|
||||
"field": "show_qr_button",
|
||||
"type": "boolean",
|
||||
"meta": {
|
||||
"collection": "types",
|
||||
"conditions": null,
|
||||
"display": null,
|
||||
"display_options": null,
|
||||
"field": "show_qr_button",
|
||||
"group": "header_elements",
|
||||
"hidden": false,
|
||||
"interface": "boolean",
|
||||
"note": null,
|
||||
"options": null,
|
||||
"readonly": false,
|
||||
"required": false,
|
||||
"sort": 1,
|
||||
"special": [
|
||||
"cast-boolean"
|
||||
],
|
||||
"translations": null,
|
||||
"validation": null,
|
||||
"validation_message": null,
|
||||
"width": "half"
|
||||
},
|
||||
"schema": {
|
||||
"name": "show_qr_button",
|
||||
"table": "types",
|
||||
"data_type": "boolean",
|
||||
"default_value": false,
|
||||
"max_length": null,
|
||||
"numeric_precision": null,
|
||||
"numeric_scale": null,
|
||||
"is_nullable": true,
|
||||
"is_unique": false,
|
||||
"is_indexed": false,
|
||||
"is_primary_key": false,
|
||||
"is_generated": false,
|
||||
"generation_expression": null,
|
||||
"has_auto_increment": false,
|
||||
"foreign_key_table": null,
|
||||
"foreign_key_column": null
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,45 @@
|
||||
{
|
||||
"collection": "types",
|
||||
"field": "show_share_button",
|
||||
"type": "boolean",
|
||||
"meta": {
|
||||
"collection": "types",
|
||||
"conditions": null,
|
||||
"display": null,
|
||||
"display_options": null,
|
||||
"field": "show_share_button",
|
||||
"group": "header_elements",
|
||||
"hidden": false,
|
||||
"interface": "boolean",
|
||||
"note": null,
|
||||
"options": null,
|
||||
"readonly": false,
|
||||
"required": false,
|
||||
"sort": 3,
|
||||
"special": [
|
||||
"cast-boolean"
|
||||
],
|
||||
"translations": null,
|
||||
"validation": null,
|
||||
"validation_message": null,
|
||||
"width": "half"
|
||||
},
|
||||
"schema": {
|
||||
"name": "show_share_button",
|
||||
"table": "types",
|
||||
"data_type": "boolean",
|
||||
"default_value": false,
|
||||
"max_length": null,
|
||||
"numeric_precision": null,
|
||||
"numeric_scale": null,
|
||||
"is_nullable": true,
|
||||
"is_unique": false,
|
||||
"is_indexed": false,
|
||||
"is_primary_key": false,
|
||||
"is_generated": false,
|
||||
"generation_expression": null,
|
||||
"has_auto_increment": false,
|
||||
"foreign_key_table": null,
|
||||
"foreign_key_column": null
|
||||
}
|
||||
}
|
||||
@ -13,11 +13,12 @@
|
||||
"interface": "group-detail",
|
||||
"note": null,
|
||||
"options": {
|
||||
"headerIcon": "edit_square"
|
||||
"headerIcon": "edit_square",
|
||||
"start": "closed"
|
||||
},
|
||||
"readonly": false,
|
||||
"required": false,
|
||||
"sort": 8,
|
||||
"sort": 9,
|
||||
"special": [
|
||||
"alias",
|
||||
"no-data",
|
||||
|
||||
@ -13,11 +13,12 @@
|
||||
"interface": "group-detail",
|
||||
"note": null,
|
||||
"options": {
|
||||
"headerIcon": "wysiwyg"
|
||||
"headerIcon": "wysiwyg",
|
||||
"start": "closed"
|
||||
},
|
||||
"readonly": false,
|
||||
"required": false,
|
||||
"sort": 7,
|
||||
"sort": 8,
|
||||
"special": [
|
||||
"alias",
|
||||
"no-data",
|
||||
|
||||
@ -0,0 +1,74 @@
|
||||
{
|
||||
"collection": "types",
|
||||
"field": "subtitle_label",
|
||||
"type": "string",
|
||||
"meta": {
|
||||
"collection": "types",
|
||||
"conditions": [
|
||||
{
|
||||
"hidden": false,
|
||||
"name": "subtitle=custom",
|
||||
"readonly": false,
|
||||
"required": true,
|
||||
"rule": {
|
||||
"_and": [
|
||||
{
|
||||
"subtitle_mode": {
|
||||
"_eq": "custom"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"hidden": true,
|
||||
"name": "subtitle != custom",
|
||||
"readonly": true,
|
||||
"required": false,
|
||||
"rule": {
|
||||
"_and": [
|
||||
{
|
||||
"subtitle_mode": {
|
||||
"_neq": "custom"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
],
|
||||
"display": null,
|
||||
"display_options": null,
|
||||
"field": "subtitle_label",
|
||||
"group": "Header",
|
||||
"hidden": false,
|
||||
"interface": "input",
|
||||
"note": null,
|
||||
"options": null,
|
||||
"readonly": false,
|
||||
"required": false,
|
||||
"sort": 2,
|
||||
"special": null,
|
||||
"translations": null,
|
||||
"validation": null,
|
||||
"validation_message": null,
|
||||
"width": "half"
|
||||
},
|
||||
"schema": {
|
||||
"name": "subtitle_label",
|
||||
"table": "types",
|
||||
"data_type": "character varying",
|
||||
"default_value": "Subname",
|
||||
"max_length": 255,
|
||||
"numeric_precision": null,
|
||||
"numeric_scale": null,
|
||||
"is_nullable": true,
|
||||
"is_unique": false,
|
||||
"is_indexed": false,
|
||||
"is_primary_key": false,
|
||||
"is_generated": false,
|
||||
"generation_expression": null,
|
||||
"has_auto_increment": false,
|
||||
"foreign_key_table": null,
|
||||
"foreign_key_column": null
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,58 @@
|
||||
{
|
||||
"collection": "types",
|
||||
"field": "subtitle_mode",
|
||||
"type": "string",
|
||||
"meta": {
|
||||
"collection": "types",
|
||||
"conditions": null,
|
||||
"display": null,
|
||||
"display_options": null,
|
||||
"field": "subtitle_mode",
|
||||
"group": "Header",
|
||||
"hidden": false,
|
||||
"interface": "select-dropdown",
|
||||
"note": null,
|
||||
"options": {
|
||||
"choices": [
|
||||
{
|
||||
"text": "address",
|
||||
"value": "address"
|
||||
},
|
||||
{
|
||||
"text": "custom",
|
||||
"value": "custom"
|
||||
},
|
||||
{
|
||||
"text": "none",
|
||||
"value": "none"
|
||||
}
|
||||
]
|
||||
},
|
||||
"readonly": false,
|
||||
"required": false,
|
||||
"sort": 1,
|
||||
"special": null,
|
||||
"translations": null,
|
||||
"validation": null,
|
||||
"validation_message": null,
|
||||
"width": "half"
|
||||
},
|
||||
"schema": {
|
||||
"name": "subtitle_mode",
|
||||
"table": "types",
|
||||
"data_type": "character varying",
|
||||
"default_value": "address",
|
||||
"max_length": 255,
|
||||
"numeric_precision": null,
|
||||
"numeric_scale": null,
|
||||
"is_nullable": true,
|
||||
"is_unique": false,
|
||||
"is_indexed": false,
|
||||
"is_primary_key": false,
|
||||
"is_generated": false,
|
||||
"generation_expression": null,
|
||||
"has_auto_increment": false,
|
||||
"foreign_key_table": null,
|
||||
"foreign_key_column": null
|
||||
}
|
||||
}
|
||||
@ -102,6 +102,7 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@heroicons/react": "^2.0.17",
|
||||
"@maplibre/maplibre-gl-leaflet": "^0.1.3",
|
||||
"@tanstack/react-query": "^5.17.8",
|
||||
"@tiptap/core": "^3.6.5",
|
||||
"@tiptap/extension-bubble-menu": "^3.6.5",
|
||||
@ -119,6 +120,7 @@
|
||||
"date-fns": "^3.3.1",
|
||||
"leaflet": "^1.9.4",
|
||||
"leaflet.locatecontrol": "^0.79.0",
|
||||
"maplibre-gl": "^5.9.0",
|
||||
"radash": "^12.1.0",
|
||||
"react-colorful": "^5.6.1",
|
||||
"react-dropzone": "^14.3.8",
|
||||
|
||||
@ -173,7 +173,7 @@ export const PopupView = ({ children }: { children?: React.ReactNode }) => {
|
||||
</ItemViewPopup>
|
||||
|
||||
<Tooltip offset={[0, -38]} direction='top'>
|
||||
{item.name || item.layer?.item_default_name}
|
||||
{item.name ?? item.layer?.item_default_name}
|
||||
</Tooltip>
|
||||
</Marker>
|
||||
</TemplateItemContext.Provider>
|
||||
|
||||
@ -79,7 +79,7 @@ export const SearchControl = () => {
|
||||
items.filter((item) => {
|
||||
return (
|
||||
value.length > 2 &&
|
||||
((item.layer?.listed && item.name.toLowerCase().includes(value.toLowerCase())) ||
|
||||
((item.layer?.listed && item.name?.toLowerCase().includes(value.toLowerCase())) ||
|
||||
item.text?.toLowerCase().includes(value.toLowerCase()))
|
||||
)
|
||||
}),
|
||||
|
||||
@ -146,7 +146,7 @@ export function ItemFormPopup(props: Props) {
|
||||
(i) => i.user_created?.id === user?.id && i.layer === popupForm.layer,
|
||||
)
|
||||
|
||||
const itemName = formItem.name || user?.first_name
|
||||
const itemName = formItem.name ?? user?.first_name
|
||||
if (!itemName) {
|
||||
toast.error('Name must be defined')
|
||||
return false
|
||||
|
||||
@ -1,222 +1 @@
|
||||
/* eslint-disable @typescript-eslint/no-unnecessary-condition */
|
||||
/* eslint-disable @typescript-eslint/no-non-null-asserted-optional-chain */
|
||||
/* eslint-disable @typescript-eslint/no-misused-promises */
|
||||
/* eslint-disable @typescript-eslint/require-await */
|
||||
/* eslint-disable @typescript-eslint/restrict-template-expressions */
|
||||
/* eslint-disable @typescript-eslint/restrict-plus-operands */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-call */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-return */
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
/* eslint-disable @typescript-eslint/no-non-null-assertion */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
|
||||
import EllipsisVerticalIcon from '@heroicons/react/16/solid/EllipsisVerticalIcon'
|
||||
import PencilIcon from '@heroicons/react/24/solid/PencilIcon'
|
||||
import TrashIcon from '@heroicons/react/24/solid/TrashIcon'
|
||||
import { useState } from 'react'
|
||||
import SVG from 'react-inlinesvg'
|
||||
import { useNavigate } from 'react-router-dom'
|
||||
|
||||
import TargetDotSVG from '#assets/targetDot.svg'
|
||||
import { useAppState } from '#components/AppShell/hooks/useAppState'
|
||||
import { useHasUserPermission } from '#components/Map/hooks/usePermissions'
|
||||
import DialogModal from '#components/Templates/DialogModal'
|
||||
|
||||
import type { Item } from '#types/Item'
|
||||
import type { ItemsApi } from '#types/ItemsApi'
|
||||
|
||||
export function HeaderView({
|
||||
item,
|
||||
api,
|
||||
editCallback,
|
||||
deleteCallback,
|
||||
setPositionCallback,
|
||||
loading,
|
||||
hideMenu = false,
|
||||
big = false,
|
||||
truncateSubname = true,
|
||||
hideSubname = false,
|
||||
showAddress = false,
|
||||
}: {
|
||||
item?: Item
|
||||
api?: ItemsApi<any>
|
||||
editCallback?: any
|
||||
deleteCallback?: any
|
||||
setPositionCallback?: any
|
||||
loading?: boolean
|
||||
hideMenu?: boolean
|
||||
big?: boolean
|
||||
hideSubname?: boolean
|
||||
truncateSubname?: boolean
|
||||
showAddress?: boolean
|
||||
}) {
|
||||
const [modalOpen, setModalOpen] = useState<boolean>(false)
|
||||
|
||||
const hasUserPermission = useHasUserPermission()
|
||||
const navigate = useNavigate()
|
||||
const appState = useAppState()
|
||||
|
||||
const [imageLoaded, setImageLoaded] = useState(false)
|
||||
|
||||
const avatar =
|
||||
(item?.image && appState.assetsApi.url + item.image + '?width=160&heigth=160') ||
|
||||
item?.image_external
|
||||
const title = item?.name ?? item?.layer?.item_default_name
|
||||
const subtitle = item?.subname
|
||||
|
||||
const [address] = useState<string>('')
|
||||
|
||||
const params = new URLSearchParams(window.location.search)
|
||||
|
||||
const openDeleteModal = async (event: React.MouseEvent<HTMLElement>) => {
|
||||
setModalOpen(true)
|
||||
event.stopPropagation()
|
||||
}
|
||||
if (!item) return null
|
||||
return (
|
||||
<>
|
||||
<div className='tw:flex tw:flex-row'>
|
||||
<div className={'tw:grow tw:max-w-[calc(100%-60px)] }'}>
|
||||
<div className='tw:flex tw:items-center'>
|
||||
{avatar && (
|
||||
<div className='tw:avatar'>
|
||||
<div
|
||||
className={`${
|
||||
big ? 'tw:w-20' : 'tw:w-10'
|
||||
} tw:inline tw:items-center tw:justify-center overflow-hidden`}
|
||||
>
|
||||
<img
|
||||
className={'tw:w-full tw:h-full tw:object-cover tw:rounded-full'}
|
||||
src={avatar}
|
||||
alt={item.name + ' logo'}
|
||||
onLoad={() => setImageLoaded(true)}
|
||||
onError={() => setImageLoaded(false)}
|
||||
style={{ display: imageLoaded ? 'block' : 'none' }}
|
||||
/>
|
||||
{!imageLoaded && (
|
||||
<div className='tw:w-full tw:h-full tw:bg-gray-200 tw:rounded-full' />
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
<div className={`${avatar ? 'tw:ml-2' : ''} tw:overflow-hidden`}>
|
||||
<div
|
||||
className={`${big ? 'tw:xl:text-3xl tw:text-2xl' : 'tw:text-xl'} tw:font-semibold tw:truncate`}
|
||||
title={title}
|
||||
data-cy='profile-title'
|
||||
>
|
||||
{title}
|
||||
</div>
|
||||
{showAddress && address && !hideSubname && (
|
||||
<div className={`tw:text-xs tw:text-gray-500 ${truncateSubname && 'tw:truncate'}`}>
|
||||
{address}
|
||||
</div>
|
||||
)}
|
||||
{subtitle && !hideSubname && (
|
||||
<div className={`tw:text-xs tw:opacity-50 ${truncateSubname && 'tw:truncate'}`}>
|
||||
{subtitle}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div onClick={(e) => e.stopPropagation()} className={`${big ? 'tw:mt-5' : 'tw:mt-1'}`}>
|
||||
{(api?.deleteItem || item.layer?.api?.updateItem) &&
|
||||
(hasUserPermission(api?.collectionName!, 'delete', item) ||
|
||||
hasUserPermission(api?.collectionName!, 'update', item)) &&
|
||||
!hideMenu && (
|
||||
<div className='tw:dropdown tw:dropdown-bottom'>
|
||||
<label
|
||||
tabIndex={0}
|
||||
className='tw:bg-base-100 tw:btn tw:m-1 tw:leading-3 tw:border-none tw:min-h-0 tw:h-6'
|
||||
>
|
||||
<EllipsisVerticalIcon className='tw:h-5 tw:w-5' />
|
||||
</label>
|
||||
<ul
|
||||
tabIndex={0}
|
||||
className='tw:dropdown-content tw:menu tw:p-2 tw:shadow tw:bg-base-100 tw:rounded-box tw:z-1000'
|
||||
>
|
||||
{api?.updateItem &&
|
||||
hasUserPermission(api.collectionName!, 'update', item) &&
|
||||
editCallback && (
|
||||
<li>
|
||||
<a
|
||||
className='tw:text-base-content! tw:tooltip tw:tooltip-right tw:cursor-pointer'
|
||||
data-tip='Edit'
|
||||
onClick={(e) =>
|
||||
item.layer?.customEditLink
|
||||
? navigate(
|
||||
`${item.layer.customEditLink}${item.layer.customEditParameter ? `/${item.id}${params && '?' + params}` : ''} `,
|
||||
)
|
||||
: editCallback(e)
|
||||
}
|
||||
>
|
||||
<PencilIcon className='tw:h-5 tw:w-5' />
|
||||
</a>
|
||||
</li>
|
||||
)}
|
||||
{api?.updateItem &&
|
||||
hasUserPermission(api.collectionName!, 'update', item) &&
|
||||
setPositionCallback && (
|
||||
<li>
|
||||
<a
|
||||
className='tw:text-base-content! tw:tooltip tw:tooltip-right tw:cursor-pointer'
|
||||
data-tip='Set position'
|
||||
onClick={setPositionCallback}
|
||||
>
|
||||
<SVG src={TargetDotSVG} className='tw:w-5 tw:h-5' />
|
||||
</a>
|
||||
</li>
|
||||
)}
|
||||
{api?.deleteItem &&
|
||||
hasUserPermission(api.collectionName!, 'delete', item) &&
|
||||
deleteCallback && (
|
||||
<li>
|
||||
<a
|
||||
className='tw:text-error! tw:tooltip tw:tooltip-right tw:cursor-pointer'
|
||||
data-tip='Delete'
|
||||
onClick={openDeleteModal}
|
||||
>
|
||||
{loading ? (
|
||||
<span className='tw:loading tw:loading-spinner tw:loading-sm'></span>
|
||||
) : (
|
||||
<TrashIcon className='tw:h-5 tw:w-5' />
|
||||
)}
|
||||
</a>
|
||||
</li>
|
||||
)}
|
||||
</ul>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<DialogModal
|
||||
isOpened={modalOpen}
|
||||
title='Are you sure?'
|
||||
showCloseButton={false}
|
||||
onClose={() => setModalOpen(false)}
|
||||
>
|
||||
<div onClick={(e) => e.stopPropagation()}>
|
||||
<span>
|
||||
Do you want to delete <b>{item.name}</b>?
|
||||
</span>
|
||||
<div className='tw:grid'>
|
||||
<div className='tw:flex tw:justify-between'>
|
||||
<label
|
||||
className='tw:btn tw:mt-4 tw:btn-error'
|
||||
onClick={(e) => {
|
||||
deleteCallback(e)
|
||||
setModalOpen(false)
|
||||
}}
|
||||
>
|
||||
Yes
|
||||
</label>
|
||||
<label className='tw:btn tw:mt-4' onClick={() => setModalOpen(false)}>
|
||||
No
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</DialogModal>
|
||||
</>
|
||||
)
|
||||
}
|
||||
export { HeaderView } from './HeaderView/index'
|
||||
|
||||
@ -0,0 +1,42 @@
|
||||
import { LuNavigation } from 'react-icons/lu'
|
||||
|
||||
import { useMyProfile } from '#components/Map/hooks/useMyProfile'
|
||||
|
||||
import { useNavigationUrl } from './hooks'
|
||||
import { ShareButton } from './ShareButton'
|
||||
|
||||
import type { Item } from '#types/Item'
|
||||
|
||||
interface ActionButtonsProps {
|
||||
item: Item
|
||||
}
|
||||
|
||||
export function ActionButtons({ item }: ActionButtonsProps) {
|
||||
const myProfile = useMyProfile()
|
||||
const { getNavigationUrl, isMobile, isIOS } = useNavigationUrl(
|
||||
item.position?.coordinates as [number, number] | undefined,
|
||||
)
|
||||
|
||||
const showNavigationButton = item.layer?.itemType.show_navigation_button ?? true
|
||||
const showShareButton = item.layer?.itemType.show_share_button ?? true
|
||||
const isOtherProfile = myProfile.myProfile?.id !== item.id
|
||||
|
||||
return (
|
||||
<>
|
||||
{item.position?.coordinates && isOtherProfile && showNavigationButton && (
|
||||
<a
|
||||
href={getNavigationUrl()}
|
||||
target='_blank'
|
||||
data-tip='Navigate'
|
||||
rel='noopener noreferrer'
|
||||
className='tw:btn tw:mr-2 tw:px-3 tw:tooltip tw:tooltip-top'
|
||||
style={{ color: 'inherit' }}
|
||||
title={`Navigate with ${isMobile ? 'default navigation app' : isIOS ? 'Apple Maps' : 'Google Maps'}`}
|
||||
>
|
||||
<LuNavigation className='tw:h-4 tw:w-4' />
|
||||
</a>
|
||||
)}
|
||||
{isOtherProfile && showShareButton && <ShareButton item={item} />}
|
||||
</>
|
||||
)
|
||||
}
|
||||
@ -0,0 +1,46 @@
|
||||
import { FaPlus } from 'react-icons/fa6'
|
||||
|
||||
import { useMyProfile } from '#components/Map/hooks/useMyProfile'
|
||||
import { useGetItemTags } from '#components/Map/hooks/useTags'
|
||||
|
||||
import type { Item } from '#types/Item'
|
||||
|
||||
interface ConnectionStatusProps {
|
||||
item: Item
|
||||
}
|
||||
|
||||
export function ConnectionStatus({ item }: ConnectionStatusProps) {
|
||||
const myProfile = useMyProfile()
|
||||
const getItemTags = useGetItemTags()
|
||||
|
||||
if (myProfile.myProfile?.id === item.id) {
|
||||
return null
|
||||
}
|
||||
|
||||
const isConnected = item.relations?.some(
|
||||
(r) =>
|
||||
r.type === item.layer?.itemType.cta_relation &&
|
||||
r.related_items_id === myProfile.myProfile?.id,
|
||||
)
|
||||
|
||||
if (!item.layer?.itemType.show_cta_button) {
|
||||
return null
|
||||
}
|
||||
|
||||
if (isConnected) {
|
||||
return <p className='tw:flex tw:items-center tw:mr-2'>✅ Connected</p>
|
||||
}
|
||||
|
||||
const tags = getItemTags(item)
|
||||
return (
|
||||
<button
|
||||
style={{
|
||||
backgroundColor: `${item.color ?? (tags[0]?.color ? tags[0].color : item.layer.markerDefaultColor || '#000')}`,
|
||||
}}
|
||||
className='tw:btn tw:text-white tw:mr-2 tw:tooltip tw:tooltip-top '
|
||||
data-tip={'Connect'}
|
||||
>
|
||||
<FaPlus className='tw:w-5' /> Connect
|
||||
</button>
|
||||
)
|
||||
}
|
||||
@ -0,0 +1,37 @@
|
||||
import DialogModal from '#components/Templates/DialogModal'
|
||||
|
||||
import type { Item } from '#types/Item'
|
||||
|
||||
interface DeleteModalProps {
|
||||
item: Item
|
||||
isOpen: boolean
|
||||
onClose: () => void
|
||||
onConfirm: (e: React.MouseEvent) => void
|
||||
}
|
||||
|
||||
export function DeleteModal({ item, isOpen, onClose, onConfirm }: DeleteModalProps) {
|
||||
const handleConfirm = (e: React.MouseEvent) => {
|
||||
onConfirm(e)
|
||||
onClose()
|
||||
}
|
||||
|
||||
return (
|
||||
<DialogModal isOpened={isOpen} title='Are you sure?' showCloseButton={false} onClose={onClose}>
|
||||
<div onClick={(e) => e.stopPropagation()}>
|
||||
<span>
|
||||
Do you want to delete <b>{item.name}</b>?
|
||||
</span>
|
||||
<div className='tw:grid'>
|
||||
<div className='tw:flex tw:justify-between'>
|
||||
<label className='tw:btn tw:mt-4 tw:btn-error' onClick={handleConfirm}>
|
||||
Yes
|
||||
</label>
|
||||
<label className='tw:btn tw:mt-4' onClick={onClose}>
|
||||
No
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</DialogModal>
|
||||
)
|
||||
}
|
||||
@ -0,0 +1,112 @@
|
||||
import EllipsisVerticalIcon from '@heroicons/react/16/solid/EllipsisVerticalIcon'
|
||||
import PencilIcon from '@heroicons/react/24/solid/PencilIcon'
|
||||
import TrashIcon from '@heroicons/react/24/solid/TrashIcon'
|
||||
import SVG from 'react-inlinesvg'
|
||||
import { useNavigate } from 'react-router-dom'
|
||||
|
||||
import TargetDotSVG from '#assets/targetDot.svg'
|
||||
import { useHasUserPermission } from '#components/Map/hooks/usePermissions'
|
||||
|
||||
import type { Item } from '#types/Item'
|
||||
import type { ItemsApi } from '#types/ItemsApi'
|
||||
|
||||
interface EditMenuProps {
|
||||
item: Item
|
||||
api?: ItemsApi<unknown>
|
||||
editCallback?: (e: React.MouseEvent) => void
|
||||
deleteCallback?: (e: React.MouseEvent) => void
|
||||
setPositionCallback?: () => void
|
||||
loading?: boolean
|
||||
hideMenu?: boolean
|
||||
big?: boolean
|
||||
onDeleteModalOpen: () => void
|
||||
}
|
||||
|
||||
export function EditMenu({
|
||||
item,
|
||||
api,
|
||||
editCallback,
|
||||
deleteCallback,
|
||||
setPositionCallback,
|
||||
loading = false,
|
||||
hideMenu = false,
|
||||
big = false,
|
||||
onDeleteModalOpen,
|
||||
}: EditMenuProps) {
|
||||
const hasUserPermission = useHasUserPermission()
|
||||
const navigate = useNavigate()
|
||||
|
||||
const params = new URLSearchParams(window.location.search)
|
||||
|
||||
const handleDeleteClick = (event: React.MouseEvent<HTMLElement>) => {
|
||||
onDeleteModalOpen()
|
||||
event.stopPropagation()
|
||||
}
|
||||
|
||||
if (hideMenu) return null
|
||||
|
||||
const hasDeletePermission =
|
||||
api?.deleteItem && api.collectionName && hasUserPermission(api.collectionName, 'delete', item)
|
||||
const hasUpdatePermission =
|
||||
api?.updateItem && api.collectionName && hasUserPermission(api.collectionName, 'update', item)
|
||||
|
||||
if (!hasDeletePermission && !hasUpdatePermission) return null
|
||||
|
||||
return (
|
||||
<div onClick={(e) => e.stopPropagation()} className={`${big ? 'tw:mt-5' : 'tw:mt-1'}`}>
|
||||
<div className='tw:dropdown tw:dropdown-bottom tw:dropdown-center'>
|
||||
<label tabIndex={0} className='tw:btn tw:btn-ghost tw:px-2.5'>
|
||||
<EllipsisVerticalIcon className='tw:h-5 tw:w-5' />
|
||||
</label>
|
||||
<ul
|
||||
tabIndex={0}
|
||||
className='tw:dropdown-content tw:menu tw:p-2 tw:shadow tw:bg-base-100 tw:rounded-box tw:z-1000'
|
||||
>
|
||||
{hasUpdatePermission && editCallback && (
|
||||
<li>
|
||||
<a
|
||||
className='tw:text-base-content! tw:tooltip tw:tooltip-top tw:cursor-pointer'
|
||||
data-tip='Edit'
|
||||
onClick={(e) =>
|
||||
item.layer?.customEditLink
|
||||
? navigate(
|
||||
`${item.layer.customEditLink}${item.layer.customEditParameter ? `/${item.id}${params.toString() ? '?' + params.toString() : ''}` : ''}`,
|
||||
)
|
||||
: editCallback(e)
|
||||
}
|
||||
>
|
||||
<PencilIcon className='tw:h-5 tw:w-5' />
|
||||
</a>
|
||||
</li>
|
||||
)}
|
||||
{hasUpdatePermission && setPositionCallback && (
|
||||
<li>
|
||||
<a
|
||||
className='tw:text-base-content! tw:tooltip tw:tooltip-top tw:cursor-pointer'
|
||||
data-tip='Set position'
|
||||
onClick={setPositionCallback}
|
||||
>
|
||||
<SVG src={TargetDotSVG} className='tw:w-5 tw:h-5' />
|
||||
</a>
|
||||
</li>
|
||||
)}
|
||||
{hasDeletePermission && deleteCallback && (
|
||||
<li>
|
||||
<a
|
||||
className='tw:text-error! tw:tooltip tw:tooltip-top tw:cursor-pointer'
|
||||
data-tip='Delete'
|
||||
onClick={handleDeleteClick}
|
||||
>
|
||||
{loading ? (
|
||||
<span className='tw:loading tw:loading-spinner tw:loading-sm'></span>
|
||||
) : (
|
||||
<TrashIcon className='tw:h-5 tw:w-5' />
|
||||
)}
|
||||
</a>
|
||||
</li>
|
||||
)}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@ -0,0 +1,73 @@
|
||||
import { QrCodeIcon } from '@heroicons/react/24/solid'
|
||||
import { useState } from 'react'
|
||||
|
||||
import { useAppState } from '#components/AppShell/hooks/useAppState'
|
||||
|
||||
import type { Item } from '#types/Item'
|
||||
|
||||
interface ItemAvatarProps {
|
||||
item: Item
|
||||
big?: boolean
|
||||
extraLarge?: boolean
|
||||
showQrButton?: boolean
|
||||
onQrClick?: () => void
|
||||
}
|
||||
|
||||
export function ItemAvatar({
|
||||
item,
|
||||
big = false,
|
||||
extraLarge = false,
|
||||
showQrButton = false,
|
||||
onQrClick,
|
||||
}: ItemAvatarProps) {
|
||||
const appState = useAppState()
|
||||
const [imageLoaded, setImageLoaded] = useState(false)
|
||||
|
||||
const imageSize = extraLarge ? 320 : 160
|
||||
const avatar =
|
||||
(item.image &&
|
||||
appState.assetsApi.url + item.image + `?width=${imageSize}&height=${imageSize}`) ??
|
||||
item.image_external
|
||||
|
||||
const hasAvatar = !!avatar
|
||||
|
||||
// If no avatar but QR button should be shown, show only the QR button
|
||||
if (!hasAvatar && showQrButton) {
|
||||
return (
|
||||
<button onClick={onQrClick} className='tw:btn tw:btn-lg tw:p-3 tw:mr-2' title='QR-Code'>
|
||||
<QrCodeIcon className='tw:h-6 tw:w-6' />
|
||||
</button>
|
||||
)
|
||||
}
|
||||
|
||||
if (!hasAvatar) return null
|
||||
|
||||
const avatarSize = extraLarge ? 'tw:w-32' : big ? 'tw:w-16' : 'tw:w-10'
|
||||
|
||||
return (
|
||||
<div className='tw:avatar tw:relative'>
|
||||
<div
|
||||
className={`${avatarSize} tw:inline tw:items-center tw:justify-center tw:overflow-visible`}
|
||||
>
|
||||
<img
|
||||
className='tw:w-full tw:h-full tw:object-cover tw:rounded-full tw:border-white'
|
||||
src={avatar}
|
||||
alt={(item.name ?? '') + ' logo'}
|
||||
onLoad={() => setImageLoaded(true)}
|
||||
onError={() => setImageLoaded(false)}
|
||||
style={{ display: imageLoaded ? 'block' : 'none' }}
|
||||
/>
|
||||
{!imageLoaded && <div className='tw:w-full tw:h-full tw:bg-gray-200 tw:rounded-full' />}
|
||||
</div>
|
||||
{showQrButton && (
|
||||
<button
|
||||
onClick={onQrClick}
|
||||
className='tw:btn tw:p-1 tw:btn-sm tw:absolute tw:bottom-[-6px] tw:right-[-6px]'
|
||||
title='QR-Code'
|
||||
>
|
||||
<QrCodeIcon className='tw:h-5 tw:w-5' />
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@ -0,0 +1,132 @@
|
||||
import { MapPinIcon } from '@heroicons/react/24/solid'
|
||||
import { useEffect, useRef, useState } from 'react'
|
||||
|
||||
import { useGeoDistance } from '#components/Map/hooks/useGeoDistance'
|
||||
import { useReverseGeocode } from '#components/Map/hooks/useReverseGeocode'
|
||||
|
||||
import { useFormatDistance } from './hooks'
|
||||
|
||||
import type { Item } from '#types/Item'
|
||||
|
||||
interface ItemTitleProps {
|
||||
item: Item
|
||||
big?: boolean
|
||||
truncateSubname?: boolean
|
||||
subtitleMode?: 'address' | 'custom' | 'none'
|
||||
hasAvatar?: boolean
|
||||
}
|
||||
|
||||
export function ItemTitle({
|
||||
item,
|
||||
big = false,
|
||||
truncateSubname = true,
|
||||
subtitleMode = 'address',
|
||||
hasAvatar = false,
|
||||
}: ItemTitleProps) {
|
||||
const { distance } = useGeoDistance(item.position ?? undefined)
|
||||
const { formatDistance } = useFormatDistance()
|
||||
const titleRef = useRef<HTMLDivElement>(null)
|
||||
const containerRef = useRef<HTMLDivElement>(null)
|
||||
const [fontSize, setFontSize] = useState<string>('tw:text-xl')
|
||||
|
||||
const { address } = useReverseGeocode(
|
||||
item.position?.coordinates as [number, number] | undefined,
|
||||
subtitleMode === 'address',
|
||||
'municipality',
|
||||
)
|
||||
|
||||
const title = item.name ?? item.layer?.item_default_name
|
||||
const subtitle = item.subname
|
||||
|
||||
useEffect(() => {
|
||||
if (!containerRef.current || !title) {
|
||||
return
|
||||
}
|
||||
|
||||
const calculateFontSize = () => {
|
||||
const container = containerRef.current
|
||||
if (!container) return
|
||||
|
||||
const containerWidth = container.offsetWidth
|
||||
|
||||
// Create temporary element to measure text width
|
||||
const measureElement = document.createElement('span')
|
||||
measureElement.style.position = 'absolute'
|
||||
measureElement.style.visibility = 'hidden'
|
||||
measureElement.style.whiteSpace = 'nowrap'
|
||||
measureElement.style.fontWeight = '700' // font-bold
|
||||
measureElement.textContent = title
|
||||
document.body.appendChild(measureElement)
|
||||
|
||||
// Measure at different font sizes - include larger sizes only if big is true
|
||||
const fontSizes = big
|
||||
? [
|
||||
{ class: 'tw:text-2xl', pixels: 24 },
|
||||
{ class: 'tw:text-xl', pixels: 20 },
|
||||
{ class: 'tw:text-lg', pixels: 18 },
|
||||
]
|
||||
: [
|
||||
{ class: 'tw:text-xl', pixels: 20 },
|
||||
{ class: 'tw:text-lg', pixels: 18 },
|
||||
]
|
||||
|
||||
let selectedSize = 'tw:text-lg'
|
||||
|
||||
for (const size of fontSizes) {
|
||||
measureElement.style.fontSize = `${size.pixels}px`
|
||||
const textWidth = measureElement.offsetWidth
|
||||
|
||||
if (textWidth <= containerWidth) {
|
||||
selectedSize = size.class
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
document.body.removeChild(measureElement)
|
||||
setFontSize(selectedSize)
|
||||
}
|
||||
|
||||
// Initial calculation
|
||||
calculateFontSize()
|
||||
|
||||
// Watch for container size changes
|
||||
const resizeObserver = new ResizeObserver(calculateFontSize)
|
||||
resizeObserver.observe(containerRef.current)
|
||||
|
||||
return () => {
|
||||
resizeObserver.disconnect()
|
||||
}
|
||||
}, [title, big])
|
||||
|
||||
return (
|
||||
<div
|
||||
ref={containerRef}
|
||||
className={`${hasAvatar ? 'tw:ml-3' : ''} tw:overflow-hidden tw:flex-1 tw:min-w-0 `}
|
||||
>
|
||||
<div
|
||||
ref={titleRef}
|
||||
className={`${fontSize} tw:font-bold ${!big ? 'tw:truncate' : ''}`}
|
||||
title={title}
|
||||
data-cy='profile-title'
|
||||
>
|
||||
{title}
|
||||
</div>
|
||||
{subtitleMode === 'address' && address && (
|
||||
<div className='tw:text-sm tw:flex tw:items-center tw:text-gray-500 tw:w-full'>
|
||||
<MapPinIcon className='tw:w-4 tw:mr-1 tw:flex-shrink-0' />
|
||||
<span title={address} className='tw:truncate'>
|
||||
{address}
|
||||
{distance && distance >= 0.1 ? ` (${formatDistance(distance) ?? ''})` : ''}
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
{subtitleMode === 'custom' && subtitle && (
|
||||
<div
|
||||
className={`tw:text-sm tw:opacity-50 tw:items-center ${truncateSubname ? 'tw:truncate' : ''}`}
|
||||
>
|
||||
{subtitle}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@ -0,0 +1,44 @@
|
||||
import QRCode from 'react-qr-code'
|
||||
|
||||
import DialogModal from '#components/Templates/DialogModal'
|
||||
|
||||
import { useShareLogic } from './hooks'
|
||||
import { ItemAvatar } from './ItemAvatar'
|
||||
import { ShareButton } from './ShareButton'
|
||||
|
||||
import type { Item } from '#types/Item'
|
||||
|
||||
interface QRModalProps {
|
||||
item: Item
|
||||
isOpen: boolean
|
||||
onClose: () => void
|
||||
}
|
||||
|
||||
export function QRModal({ item, isOpen, onClose }: QRModalProps) {
|
||||
const { inviteLink } = useShareLogic(item)
|
||||
|
||||
return (
|
||||
<DialogModal
|
||||
isOpened={isOpen}
|
||||
showCloseButton={true}
|
||||
onClose={onClose}
|
||||
className='tw:w-[calc(100vw-2rem)] tw:!max-w-96'
|
||||
>
|
||||
<div onClick={(e) => e.stopPropagation()} className='tw:text-center tw:p-4'>
|
||||
<p className='tw:text-xl tw:font-bold'>Share your Profile to expand your Network!</p>
|
||||
|
||||
<div className='tw:flex tw:flex-col tw:items-center tw:gap-4 tw:my-8'>
|
||||
<ItemAvatar item={item} extraLarge={true} />
|
||||
<div className='tw:p-8 tw:mt-4 tw:rounded-lg tw:inline-block tw:border-base-300 tw:bg-base-200 tw:border-1'>
|
||||
<QRCode value={inviteLink} size={164} />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className='tw:flex tw:items-center tw:gap-2 tw:w-full tw:border-base-300 tw:border-1 tw:rounded-selector tw:p-2'>
|
||||
<span className='tw:text-sm tw:truncate tw:flex-1 tw:min-w-0'>{inviteLink}</span>
|
||||
<ShareButton item={item} dropdownDirection='up' />
|
||||
</div>
|
||||
</div>
|
||||
</DialogModal>
|
||||
)
|
||||
}
|
||||
@ -0,0 +1,168 @@
|
||||
import { ShareIcon } from '@heroicons/react/24/solid'
|
||||
import { useRef } from 'react'
|
||||
|
||||
import ChevronSVG from '#assets/chevron.svg'
|
||||
import ClipboardSVG from '#assets/share/clipboard.svg'
|
||||
import FacebookSVG from '#assets/share/facebook.svg'
|
||||
import LinkedinSVG from '#assets/share/linkedin.svg'
|
||||
import TelegramSVG from '#assets/share/telegram.svg'
|
||||
import TwitterSVG from '#assets/share/twitter.svg'
|
||||
import WhatsappSVG from '#assets/share/whatsapp.svg'
|
||||
import XingSVG from '#assets/share/xing.svg'
|
||||
|
||||
import { useShareLogic } from './hooks'
|
||||
|
||||
import type { Item } from '#types/Item'
|
||||
import type { PlatformConfig, SharePlatformConfigs } from './types'
|
||||
|
||||
interface ShareButtonProps {
|
||||
item: Item
|
||||
dropdownDirection?: 'up' | 'down'
|
||||
}
|
||||
|
||||
export function ShareButton({ item, dropdownDirection = 'down' }: ShareButtonProps) {
|
||||
const { shareUrl, shareTitle, copyLink, getShareUrl } = useShareLogic(item)
|
||||
const detailsRef = useRef<HTMLDetailsElement>(null)
|
||||
|
||||
const closeDropdown = () => {
|
||||
if (detailsRef.current) {
|
||||
detailsRef.current.open = false
|
||||
}
|
||||
}
|
||||
|
||||
const handleCopyLink = () => {
|
||||
copyLink()
|
||||
closeDropdown()
|
||||
}
|
||||
|
||||
const canUseNativeShare =
|
||||
typeof navigator !== 'undefined' && typeof navigator.share !== 'undefined'
|
||||
|
||||
const handleNativeShare = () => {
|
||||
void navigator
|
||||
.share({
|
||||
title: shareTitle,
|
||||
url: shareUrl,
|
||||
})
|
||||
.then(closeDropdown)
|
||||
.catch(() => {
|
||||
// User cancelled or error occurred - ignore
|
||||
})
|
||||
}
|
||||
|
||||
const platformConfigs: SharePlatformConfigs = {
|
||||
facebook: {
|
||||
shareUrl: 'https://www.facebook.com/sharer/sharer.php?u={url}',
|
||||
icon: <img src={FacebookSVG} alt='Facebook' className='tw:w-4 tw:h-4' />,
|
||||
label: 'Facebook',
|
||||
bgColor: '#3b5998',
|
||||
},
|
||||
twitter: {
|
||||
shareUrl: 'https://twitter.com/intent/tweet?text={title}:%20{url}',
|
||||
icon: <img src={TwitterSVG} alt='Twitter' className='tw:w-4 tw:h-4' />,
|
||||
label: 'Twitter',
|
||||
bgColor: '#55acee',
|
||||
},
|
||||
linkedin: {
|
||||
shareUrl: 'http://www.linkedin.com/shareArticle?mini=true&url={url}&title={title}',
|
||||
icon: <img src={LinkedinSVG} alt='Linkedin' className='tw:w-4 tw:h-4' />,
|
||||
label: 'LinkedIn',
|
||||
bgColor: '#4875b4',
|
||||
},
|
||||
whatsapp: {
|
||||
shareUrl: 'https://api.whatsapp.com/send?text={title}%20{url}',
|
||||
icon: <img src={WhatsappSVG} alt='Whatsapp' className='tw:w-4 tw:h-4' />,
|
||||
label: 'WhatsApp',
|
||||
bgColor: '#25D366',
|
||||
},
|
||||
telegram: {
|
||||
shareUrl: 'https://t.me/share/url?url={url}&text={title}',
|
||||
icon: <img src={TelegramSVG} alt='Telegram' className='tw:w-4 tw:h-4' />,
|
||||
label: 'Telegram',
|
||||
bgColor: '#0088cc',
|
||||
},
|
||||
xing: {
|
||||
shareUrl: 'https://www.xing-share.com/app/user?op=share;sc_p=xing-share;url={url}',
|
||||
icon: <img src={XingSVG} alt='Xing' className='tw:w-4 tw:h-4' />,
|
||||
label: 'Xing',
|
||||
bgColor: '#026466',
|
||||
},
|
||||
}
|
||||
|
||||
const dropdownClass = dropdownDirection === 'up' ? 'tw:dropdown-top' : ''
|
||||
|
||||
// If native share is available, render a simple button instead of dropdown
|
||||
if (canUseNativeShare) {
|
||||
return (
|
||||
<button
|
||||
onClick={handleNativeShare}
|
||||
className='tw:btn tw:px-3 tw:tooltip tw:tooltip-top'
|
||||
data-tip='Share'
|
||||
>
|
||||
<ShareIcon className='tw:w-4 tw:h-4' />
|
||||
</button>
|
||||
)
|
||||
}
|
||||
|
||||
// Otherwise, render the dropdown with manual share options
|
||||
return (
|
||||
<details ref={detailsRef} className={`tw:dropdown tw:dropdown-end ${dropdownClass}`}>
|
||||
<summary className='tw:btn tw:px-3 tw:tooltip tw:tooltip-top' data-tip='Share'>
|
||||
<ShareIcon className='tw:w-4 tw:h-4' />
|
||||
</summary>
|
||||
<ul className='tw:dropdown-content tw:menu tw:bg-base-100 tw:rounded-box tw:z-[1] tw:p-2 tw:shadow-sm'>
|
||||
<li>
|
||||
<a
|
||||
onClick={handleCopyLink}
|
||||
className='tw:flex tw:items-center tw:gap-3'
|
||||
style={{ color: 'inherit' }}
|
||||
>
|
||||
<div
|
||||
className='tw:w-6 tw:h-6 tw:rounded-full tw:flex tw:items-center tw:justify-center'
|
||||
style={{ backgroundColor: '#888' }}
|
||||
>
|
||||
<img src={ClipboardSVG} className='tw:w-3 tw:h-3' alt='Copy' />
|
||||
</div>
|
||||
Copy Link
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a
|
||||
href={`mailto:?subject=${encodeURIComponent(shareTitle)}&body=${encodeURIComponent(shareUrl)}`}
|
||||
onClick={closeDropdown}
|
||||
className='tw:flex tw:items-center tw:gap-3'
|
||||
style={{ color: 'inherit' }}
|
||||
>
|
||||
<div
|
||||
className='tw:w-6 tw:h-6 tw:rounded-full tw:flex tw:items-center tw:justify-center tw:text-white'
|
||||
style={{ backgroundColor: '#444' }}
|
||||
>
|
||||
<img src={ChevronSVG} className='tw:w-3 tw:h-3' alt='Email' />
|
||||
</div>
|
||||
Email
|
||||
</a>
|
||||
</li>
|
||||
{Object.entries(platformConfigs).map(([platform, config]) => (
|
||||
<li key={platform}>
|
||||
<a
|
||||
href={getShareUrl(platform as keyof SharePlatformConfigs, platformConfigs)}
|
||||
target='_blank'
|
||||
rel='noopener noreferrer'
|
||||
onClick={closeDropdown}
|
||||
className='tw:flex tw:items-center tw:gap-3'
|
||||
style={{ color: 'inherit' }}
|
||||
>
|
||||
<div
|
||||
className='tw:w-6 tw:h-6 tw:rounded-full tw:flex tw:items-center tw:justify-center'
|
||||
style={{ backgroundColor: (config as PlatformConfig).bgColor }}
|
||||
>
|
||||
{(config as PlatformConfig).icon}
|
||||
</div>
|
||||
{(config as PlatformConfig).label}
|
||||
</a>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</details>
|
||||
)
|
||||
}
|
||||
@ -0,0 +1,77 @@
|
||||
import { toast } from 'react-toastify'
|
||||
|
||||
import type { Item } from '#types/Item'
|
||||
import type { SharePlatformConfigs } from './types'
|
||||
|
||||
export const useNavigationUrl = (coordinates?: [number, number]) => {
|
||||
const isIOS = /iPad|iPhone|iPod/.test(navigator.userAgent)
|
||||
const isMobile = /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(
|
||||
navigator.userAgent,
|
||||
)
|
||||
|
||||
const getNavigationUrl = () => {
|
||||
if (!coordinates) return ''
|
||||
|
||||
const [longitude, latitude] = coordinates
|
||||
|
||||
if (isIOS) {
|
||||
return `https://maps.apple.com/?daddr=${latitude},${longitude}`
|
||||
} else if (isMobile) {
|
||||
return `geo:${latitude},${longitude}`
|
||||
} else {
|
||||
return `https://www.google.com/maps/dir/?api=1&destination=${latitude},${longitude}`
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
getNavigationUrl,
|
||||
isMobile,
|
||||
isIOS,
|
||||
}
|
||||
}
|
||||
|
||||
export const useShareLogic = (item?: Item) => {
|
||||
const shareUrl = window.location.href
|
||||
const shareTitle = item?.name ?? 'Utopia Map Item'
|
||||
const inviteLink = shareUrl
|
||||
|
||||
const copyLink = () => {
|
||||
navigator.clipboard
|
||||
.writeText(inviteLink)
|
||||
.then(() => {
|
||||
toast.success('Link copied to clipboard')
|
||||
return null
|
||||
})
|
||||
.catch(() => {
|
||||
toast.error('Error copying link')
|
||||
})
|
||||
}
|
||||
|
||||
const getShareUrl = (
|
||||
platform: keyof SharePlatformConfigs,
|
||||
platformConfigs: SharePlatformConfigs,
|
||||
) => {
|
||||
// eslint-disable-next-line security/detect-object-injection
|
||||
const config = platformConfigs[platform]
|
||||
return config.shareUrl
|
||||
.replace('{url}', encodeURIComponent(shareUrl))
|
||||
.replace('{title}', encodeURIComponent(shareTitle))
|
||||
}
|
||||
|
||||
return {
|
||||
shareUrl,
|
||||
shareTitle,
|
||||
inviteLink,
|
||||
copyLink,
|
||||
getShareUrl,
|
||||
}
|
||||
}
|
||||
|
||||
export const useFormatDistance = () => {
|
||||
const formatDistance = (dist: number | null): string | null => {
|
||||
if (!dist) return null
|
||||
return dist < 10 ? `${dist.toFixed(1)} km` : `${Math.round(dist)} km`
|
||||
}
|
||||
|
||||
return { formatDistance }
|
||||
}
|
||||
@ -0,0 +1,91 @@
|
||||
import { useState } from 'react'
|
||||
|
||||
import { useMyProfile } from '#components/Map/hooks/useMyProfile'
|
||||
|
||||
import { ActionButtons } from './ActionButtons'
|
||||
import { ConnectionStatus } from './ConnectionStatus'
|
||||
import { DeleteModal } from './DeleteModal'
|
||||
import { EditMenu } from './EditMenu'
|
||||
import { ItemAvatar } from './ItemAvatar'
|
||||
import { ItemTitle } from './ItemTitle'
|
||||
import { QRModal } from './QRModal'
|
||||
|
||||
import type { HeaderViewProps } from './types'
|
||||
|
||||
export function HeaderView({
|
||||
item,
|
||||
api,
|
||||
editCallback,
|
||||
deleteCallback,
|
||||
setPositionCallback,
|
||||
loading,
|
||||
hideMenu = false,
|
||||
big = false,
|
||||
truncateSubname = true,
|
||||
showAddress = true,
|
||||
}: HeaderViewProps) {
|
||||
const [modalOpen, setModalOpen] = useState<boolean>(false)
|
||||
const [qrModalOpen, setQrModalOpen] = useState<boolean>(false)
|
||||
const myProfile = useMyProfile()
|
||||
|
||||
if (!item) return null
|
||||
|
||||
const hasAvatar = !!(item.image ?? item.image_external)
|
||||
const isMyProfile = myProfile.myProfile?.id === item.id
|
||||
const showQrButton = big && isMyProfile && (item.layer?.itemType.show_qr_button ?? true)
|
||||
const subtitleMode = item.layer?.itemType.subtitle_mode ?? (showAddress ? 'address' : 'none')
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className='tw:flex tw:flex-row'>
|
||||
<div className={'tw:grow tw:flex tw:flex-1 tw:min-w-0'}>
|
||||
<div className='tw:flex tw:flex-1 tw:min-w-0 tw:items-center'>
|
||||
<ItemAvatar
|
||||
item={item}
|
||||
big={big}
|
||||
showQrButton={showQrButton}
|
||||
onQrClick={() => setQrModalOpen(true)}
|
||||
/>
|
||||
<ItemTitle
|
||||
item={item}
|
||||
big={big}
|
||||
truncateSubname={truncateSubname}
|
||||
subtitleMode={subtitleMode}
|
||||
hasAvatar={hasAvatar}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<EditMenu
|
||||
item={item}
|
||||
api={api}
|
||||
editCallback={editCallback}
|
||||
deleteCallback={deleteCallback}
|
||||
setPositionCallback={setPositionCallback}
|
||||
loading={loading}
|
||||
hideMenu={hideMenu}
|
||||
big={big}
|
||||
onDeleteModalOpen={() => setModalOpen(true)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{big && (
|
||||
<div className='tw:flex tw:row tw:mt-2 '>
|
||||
<div className='tw:grow'></div>
|
||||
<div className='tw:flex'>
|
||||
<ConnectionStatus item={item} />
|
||||
<ActionButtons item={item} />
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<DeleteModal
|
||||
item={item}
|
||||
isOpen={modalOpen}
|
||||
onClose={() => setModalOpen(false)}
|
||||
onConfirm={deleteCallback ?? (() => undefined)}
|
||||
/>
|
||||
|
||||
<QRModal item={item} isOpen={qrModalOpen} onClose={() => setQrModalOpen(false)} />
|
||||
</>
|
||||
)
|
||||
}
|
||||
@ -0,0 +1,31 @@
|
||||
import type { Item } from '#types/Item'
|
||||
import type { ItemsApi } from '#types/ItemsApi'
|
||||
|
||||
export interface HeaderViewProps {
|
||||
item?: Item
|
||||
api?: ItemsApi<unknown>
|
||||
editCallback?: (e: React.MouseEvent) => void
|
||||
deleteCallback?: (e: React.MouseEvent) => void
|
||||
setPositionCallback?: () => void
|
||||
loading?: boolean
|
||||
hideMenu?: boolean
|
||||
big?: boolean
|
||||
truncateSubname?: boolean
|
||||
showAddress?: boolean
|
||||
}
|
||||
|
||||
export interface PlatformConfig {
|
||||
shareUrl: string
|
||||
icon: JSX.Element
|
||||
label: string
|
||||
bgColor: string
|
||||
}
|
||||
|
||||
export interface SharePlatformConfigs {
|
||||
facebook: PlatformConfig
|
||||
twitter: PlatformConfig
|
||||
linkedin: PlatformConfig
|
||||
whatsapp: PlatformConfig
|
||||
telegram: PlatformConfig
|
||||
xing: PlatformConfig
|
||||
}
|
||||
@ -8,25 +8,19 @@ import type { Item } from '#types/Item'
|
||||
*/
|
||||
export const StartEndView = ({ item }: { item?: Item }) => {
|
||||
return (
|
||||
<div className='tw:flex tw:flex-row tw:mb-4 tw:mt-1'>
|
||||
<div className='tw:basis-2/5 tw:flex tw:flex-row'>
|
||||
<CalendarIcon className='tw:h-4 tw:w-4 tw:mr-2' />
|
||||
<time
|
||||
className='tw:align-middle'
|
||||
dateTime={item && item.start ? item.start.substring(0, 10) : ''}
|
||||
>
|
||||
<div className='tw:flex tw:flex-row tw:mb-2.5 tw:mt-2.5 tw:bg-base-200 tw:px-3 tw:py-2.5 tw:rounded-selector tw:w-full'>
|
||||
<div className='tw:basis-2/5 tw:flex tw:flex-row tw:items-center tw:font-bold'>
|
||||
<CalendarIcon className='tw:h-5 tw:w-5 tw:mr-2' />
|
||||
<time dateTime={item && item.start ? item.start.substring(0, 10) : ''}>
|
||||
{item && item.start ? new Date(item.start).toLocaleDateString() : ''}
|
||||
</time>
|
||||
</div>
|
||||
<div className='tw:basis-1/5 tw:place-content-center'>
|
||||
<div className='tw:basis-1/5 tw:flex tw:items-center tw:justify-center'>
|
||||
<span>-</span>
|
||||
</div>
|
||||
<div className='tw:basis-2/5 tw:flex tw:flex-row'>
|
||||
<CalendarIcon className='tw:h-4 tw:w-4 tw:mr-2' />
|
||||
<time
|
||||
className='tw:align-middle'
|
||||
dateTime={item && item.end ? item.end.substring(0, 10) : ''}
|
||||
>
|
||||
<div className='tw:basis-2/5 tw:flex tw:flex-row tw:items-center tw:font-bold'>
|
||||
<CalendarIcon className='tw:h-5 tw:w-5 tw:mr-2' />
|
||||
<time dateTime={item && item.end ? item.end.substring(0, 10) : ''}>
|
||||
{item && item.end ? new Date(item.end).toLocaleDateString() : ''}
|
||||
</time>
|
||||
</div>
|
||||
|
||||
@ -92,7 +92,11 @@ export const ItemViewPopup = forwardRef((props: ItemViewPopupProps, ref: any) =>
|
||||
api={props.item.layer?.api}
|
||||
item={props.item}
|
||||
editCallback={handleEdit}
|
||||
deleteCallback={handleDelete}
|
||||
deleteCallback={(e: React.MouseEvent<HTMLElement>) => {
|
||||
handleDelete(e).catch(() => {
|
||||
// Error handling is already in handleDelete
|
||||
})
|
||||
}}
|
||||
setPositionCallback={() => {
|
||||
map.closePopup()
|
||||
setSelectPosition(props.item)
|
||||
|
||||
53
lib/src/Components/Map/Subcomponents/MapLibreLayer.tsx
Normal file
53
lib/src/Components/Map/Subcomponents/MapLibreLayer.tsx
Normal file
@ -0,0 +1,53 @@
|
||||
/* eslint-disable import/no-unassigned-import */
|
||||
import L from 'leaflet'
|
||||
import { useEffect } from 'react'
|
||||
import { useMap } from 'react-leaflet'
|
||||
|
||||
import '@maplibre/maplibre-gl-leaflet'
|
||||
import 'maplibre-gl/dist/maplibre-gl.css'
|
||||
|
||||
declare module 'leaflet' {
|
||||
interface MapLibreGLOptions {
|
||||
style: string
|
||||
attribution?: string
|
||||
}
|
||||
|
||||
interface MapLibreGLLayer extends Layer {
|
||||
addTo(map: Map): this
|
||||
}
|
||||
|
||||
function maplibreGL(options: MapLibreGLOptions): MapLibreGLLayer
|
||||
}
|
||||
|
||||
/**
|
||||
* MapLibreLayer component for rendering vector tiles with MapLibre GL
|
||||
* Integrates MapLibre GL with Leaflet using the maplibre-gl-leaflet bridge
|
||||
*
|
||||
* @param styleUrl - URL to the MapLibre style JSON (default: OpenFreeMap Liberty style)
|
||||
* @param attribution - Attribution text for the map tiles
|
||||
*/
|
||||
export function MapLibreLayer({
|
||||
styleUrl = 'https://tiles.openfreemap.org/styles/liberty',
|
||||
attribution = '© <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a>',
|
||||
}: {
|
||||
styleUrl?: string
|
||||
attribution?: string
|
||||
}) {
|
||||
const map = useMap()
|
||||
|
||||
useEffect(() => {
|
||||
const mapLibreLayer = L.maplibreGL({
|
||||
style: styleUrl,
|
||||
attribution,
|
||||
})
|
||||
|
||||
mapLibreLayer.addTo(map)
|
||||
|
||||
// Cleanup function to remove layer when component unmounts
|
||||
return () => {
|
||||
map.removeLayer(mapLibreLayer)
|
||||
}
|
||||
}, [map, styleUrl, attribution])
|
||||
|
||||
return null
|
||||
}
|
||||
@ -59,6 +59,10 @@ function UtopiaMap({
|
||||
expandLayerControl,
|
||||
tileServerUrl,
|
||||
tileServerAttribution,
|
||||
tilesType = 'raster',
|
||||
maplibreStyle,
|
||||
zoomOffset,
|
||||
tileSize,
|
||||
}: {
|
||||
/** height of the map (default '500px') */
|
||||
height?: string
|
||||
@ -94,6 +98,14 @@ function UtopiaMap({
|
||||
tileServerUrl?: string
|
||||
/** configure a custom tile server attribution */
|
||||
tileServerAttribution?: string
|
||||
/** tiles type: 'raster' or 'maplibre' (default 'raster') */
|
||||
tilesType?: 'raster' | 'maplibre'
|
||||
/** MapLibre style URL for vector tiles (default: OpenFreeMap Liberty) */
|
||||
maplibreStyle?: string
|
||||
/** zoom offset which is needed for some raster tile provider (eg. Mapbox) */
|
||||
zoomOffset?: number
|
||||
/** tile size (default 256) */
|
||||
tileSize?: number
|
||||
}) {
|
||||
return (
|
||||
<ContextWrapper>
|
||||
@ -117,6 +129,10 @@ function UtopiaMap({
|
||||
expandLayerControl={expandLayerControl}
|
||||
tileServerUrl={tileServerUrl}
|
||||
tileServerAttribution={tileServerAttribution}
|
||||
tilesType={tilesType}
|
||||
maplibreStyle={maplibreStyle}
|
||||
zoomOffset={zoomOffset}
|
||||
tileSize={tileSize}
|
||||
>
|
||||
{children}
|
||||
</UtopiaMapInner>
|
||||
|
||||
@ -42,7 +42,7 @@ import { LayerControl } from './Subcomponents/Controls/LayerControl'
|
||||
import { SearchControl } from './Subcomponents/Controls/SearchControl'
|
||||
import { TagsControl } from './Subcomponents/Controls/TagsControl'
|
||||
import { TextView } from './Subcomponents/ItemPopupComponents/TextView'
|
||||
import { SelectPositionToast } from './Subcomponents/SelectPositionToast'
|
||||
import { MapLibreLayer } from './Subcomponents/MapLibreLayer'
|
||||
|
||||
import type { Feature, Geometry as GeoJSONGeometry, GeoJsonObject } from 'geojson'
|
||||
|
||||
@ -59,6 +59,10 @@ export function UtopiaMapInner({
|
||||
expandLayerControl,
|
||||
tileServerUrl,
|
||||
tileServerAttribution,
|
||||
tilesType,
|
||||
maplibreStyle,
|
||||
zoomOffset = 0,
|
||||
tileSize = 256,
|
||||
}: {
|
||||
children?: React.ReactNode
|
||||
geo?: GeoJsonObject
|
||||
@ -72,6 +76,10 @@ export function UtopiaMapInner({
|
||||
expandLayerControl?: boolean
|
||||
tileServerUrl?: string
|
||||
tileServerAttribution?: string
|
||||
tilesType?: 'raster' | 'maplibre'
|
||||
maplibreStyle?: string
|
||||
zoomOffset?: number
|
||||
tileSize?: number
|
||||
}) {
|
||||
const selectNewItemPosition = useSelectPosition()
|
||||
const setSelectNewItemPosition = useSetSelectPosition()
|
||||
@ -179,7 +187,7 @@ export function UtopiaMapInner({
|
||||
document.title = `${document.title.split('-')[0]} - ${title}`
|
||||
document
|
||||
.querySelector('meta[property="og:title"]')
|
||||
?.setAttribute('content', ref.item.name)
|
||||
?.setAttribute('content', ref.item.name ?? '')
|
||||
document
|
||||
.querySelector('meta[property="og:description"]')
|
||||
?.setAttribute('content', ref.item.text ?? '')
|
||||
@ -284,14 +292,20 @@ export function UtopiaMapInner({
|
||||
{showLayerControl && <LayerControl expandLayerControl={expandLayerControl ?? false} />}
|
||||
{showGratitudeControl && <GratitudeControl />}
|
||||
</Control>
|
||||
<TileLayer
|
||||
maxZoom={19}
|
||||
attribution={
|
||||
tileServerAttribution ??
|
||||
'© <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a>'
|
||||
}
|
||||
url={tileServerUrl ?? 'https://tile.osmand.net/hd/{z}/{x}/{y}.png'}
|
||||
/>
|
||||
{tilesType === 'raster' ? (
|
||||
<TileLayer
|
||||
maxZoom={19}
|
||||
tileSize={tileSize}
|
||||
zoomOffset={zoomOffset}
|
||||
attribution={
|
||||
tileServerAttribution ??
|
||||
'© <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a>'
|
||||
}
|
||||
url={tileServerUrl ?? 'https://tile.osmand.net/hd/{z}/{x}/{y}.png'}
|
||||
/>
|
||||
) : (
|
||||
<MapLibreLayer styleUrl={maplibreStyle} attribution={tileServerAttribution} />
|
||||
)}
|
||||
<MarkerClusterGroup
|
||||
ref={(r) => setClusterRef(r as any)}
|
||||
showCoverageOnHover
|
||||
|
||||
56
lib/src/Components/Map/hooks/useGeoDistance.tsx
Normal file
56
lib/src/Components/Map/hooks/useGeoDistance.tsx
Normal file
@ -0,0 +1,56 @@
|
||||
import { useState, useEffect } from 'react'
|
||||
|
||||
import { useMyProfile } from './useMyProfile'
|
||||
|
||||
import type { Point } from 'geojson'
|
||||
|
||||
const getDistance = (lat1: number, lon1: number, lat2: number, lon2: number): number => {
|
||||
const R = 6371 // Earth's radius in km
|
||||
const dLat = ((lat2 - lat1) * Math.PI) / 180
|
||||
const dLon = ((lon2 - lon1) * Math.PI) / 180
|
||||
const a =
|
||||
Math.sin(dLat / 2) * Math.sin(dLat / 2) +
|
||||
Math.cos((lat1 * Math.PI) / 180) *
|
||||
Math.cos((lat2 * Math.PI) / 180) *
|
||||
Math.sin(dLon / 2) *
|
||||
Math.sin(dLon / 2)
|
||||
const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a))
|
||||
return R * c // Distance in km
|
||||
}
|
||||
|
||||
export const useGeoDistance = (targetPoint?: Point) => {
|
||||
const [distance, setDistance] = useState<number | null>(null)
|
||||
const [error, setError] = useState<string | null>(null)
|
||||
const { myProfile, isMyProfileLoaded } = useMyProfile()
|
||||
|
||||
useEffect(() => {
|
||||
setError(null)
|
||||
setDistance(null)
|
||||
|
||||
if (!isMyProfileLoaded) {
|
||||
return
|
||||
}
|
||||
|
||||
if (!myProfile?.position || !targetPoint) {
|
||||
setError('Missing location data')
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
const userGeoJson = myProfile.position
|
||||
const [userLon, userLat] = userGeoJson.coordinates
|
||||
const [targetLon, targetLat] = targetPoint.coordinates
|
||||
|
||||
const dist = getDistance(userLat, userLon, targetLat, targetLon)
|
||||
setDistance(dist)
|
||||
} catch (err) {
|
||||
if (err instanceof Error) {
|
||||
setError(err.message)
|
||||
} else {
|
||||
throw err
|
||||
}
|
||||
}
|
||||
}, [myProfile, isMyProfileLoaded, targetPoint])
|
||||
|
||||
return { distance, error, userLocation: myProfile?.position as Point | undefined }
|
||||
}
|
||||
108
lib/src/Components/Map/hooks/useReverseGeocode.ts
Normal file
108
lib/src/Components/Map/hooks/useReverseGeocode.ts
Normal file
@ -0,0 +1,108 @@
|
||||
import { useState, useEffect } from 'react'
|
||||
|
||||
interface GeocodeResult {
|
||||
street?: string
|
||||
housenumber?: string
|
||||
postcode?: string
|
||||
city?: string
|
||||
town?: string
|
||||
village?: string
|
||||
district?: string
|
||||
suburb?: string
|
||||
neighbourhood?: string
|
||||
state?: string
|
||||
country?: string
|
||||
}
|
||||
|
||||
interface GeocodeFeature {
|
||||
properties: GeocodeResult
|
||||
}
|
||||
|
||||
interface GeocodeResponse {
|
||||
features?: GeocodeFeature[]
|
||||
}
|
||||
|
||||
export function useReverseGeocode(
|
||||
coordinates?: [number, number] | null,
|
||||
enabled = true,
|
||||
accuracy: 'municipality' | 'street' | 'house_number' = 'municipality',
|
||||
) {
|
||||
const [address, setAddress] = useState('')
|
||||
const [loading, setLoading] = useState(false)
|
||||
const [error, setError] = useState<string | null>(null)
|
||||
|
||||
useEffect(() => {
|
||||
if (!enabled || !coordinates) {
|
||||
setAddress('')
|
||||
return
|
||||
}
|
||||
|
||||
const [longitude, latitude] = coordinates
|
||||
if (!Number.isFinite(latitude) || !Number.isFinite(longitude)) {
|
||||
return
|
||||
}
|
||||
|
||||
const reverseGeocode = async () => {
|
||||
setLoading(true)
|
||||
setError(null)
|
||||
|
||||
try {
|
||||
const response = await fetch(
|
||||
`https://photon.komoot.io/reverse?lat=${latitude}&lon=${longitude}&lang=de&limit=1`,
|
||||
)
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error('Geocoding request failed')
|
||||
}
|
||||
|
||||
const data = (await response.json()) as GeocodeResponse
|
||||
|
||||
if (data.features && data.features.length > 0) {
|
||||
const props = data.features[0].properties
|
||||
const municipality = props.city ?? props.town ?? props.village
|
||||
|
||||
let addressString = ''
|
||||
|
||||
switch (accuracy) {
|
||||
case 'municipality':
|
||||
addressString = municipality ?? ''
|
||||
break
|
||||
case 'street':
|
||||
if (props.street && municipality) {
|
||||
addressString = `${props.street}, ${municipality}`
|
||||
} else {
|
||||
addressString = municipality ?? ''
|
||||
}
|
||||
break
|
||||
case 'house_number':
|
||||
if (props.street && props.housenumber && municipality) {
|
||||
addressString = `${props.street} ${props.housenumber}, ${municipality}`
|
||||
} else if (props.street && municipality) {
|
||||
addressString = `${props.street}, ${municipality}`
|
||||
} else {
|
||||
addressString = municipality ?? ''
|
||||
}
|
||||
break
|
||||
}
|
||||
|
||||
setAddress(addressString)
|
||||
} else {
|
||||
setAddress('')
|
||||
}
|
||||
} catch (err) {
|
||||
if (err instanceof Error) {
|
||||
setError(err.message)
|
||||
setAddress('')
|
||||
} else {
|
||||
throw err
|
||||
}
|
||||
} finally {
|
||||
setLoading(false)
|
||||
}
|
||||
}
|
||||
|
||||
void reverseGeocode()
|
||||
}, [coordinates, enabled, accuracy])
|
||||
|
||||
return { address, loading, error }
|
||||
}
|
||||
@ -3,7 +3,6 @@
|
||||
|
||||
/* eslint-disable @typescript-eslint/no-non-null-assertion */
|
||||
/* eslint-disable @typescript-eslint/no-non-null-asserted-optional-chain */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-argument */
|
||||
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-call */
|
||||
@ -175,14 +174,18 @@ export function ProfileView({ attestationApi }: { attestationApi?: ItemsApi<any>
|
||||
<MapOverlayPage
|
||||
key={item.id}
|
||||
data-cy='profile-view'
|
||||
className={`tw:p-0! tw:overflow-scroll tw:m-4! tw:md:w-[calc(50%-32px)] tw:w-[calc(100%-32px)] tw:min-w-80 tw:max-w-3xl tw:left-0! tw:sm:left-auto! tw:top-0 tw:bottom-0 tw:transition-opacity tw:duration-500 ${!selectPosition ? 'tw:opacity-100 tw:pointer-events-auto' : 'tw:opacity-0 tw:pointer-events-none'} tw:max-h-[1000px]`}
|
||||
className={`tw:@container tw:overflow-hidden tw:p-0! tw:m-4! tw:md:w-[calc(50%-32px)] tw:w-[calc(100%-32px)] tw:min-w-80 tw:max-w-3xl tw:left-0! tw:sm:left-auto! tw:top-0 tw:bottom-0 tw:transition-opacity tw:duration-500 ${!selectPosition ? 'tw:opacity-100 tw:pointer-events-auto' : 'tw:opacity-0 tw:pointer-events-none'} tw:max-h-[1000px]`}
|
||||
>
|
||||
<>
|
||||
<div className={'tw:px-6 tw:pt-6'} data-cy='profile-header'>
|
||||
<HeaderView
|
||||
api={item.layer?.api}
|
||||
item={item}
|
||||
deleteCallback={(e) => handleDelete(e, item, setLoading, removeItem, map, navigate)}
|
||||
deleteCallback={(e: React.MouseEvent<HTMLElement>) => {
|
||||
handleDelete(e, item, setLoading, removeItem, map, navigate).catch(() => {
|
||||
// Error handling is already in handleDelete
|
||||
})
|
||||
}}
|
||||
editCallback={() => navigate('/edit-item/' + item.id)}
|
||||
setPositionCallback={() => {
|
||||
map.closePopup()
|
||||
|
||||
@ -105,7 +105,7 @@ export function ActionButton({
|
||||
.filter((item) => {
|
||||
return search === ''
|
||||
? item
|
||||
: item.name.toLowerCase().includes(search.toLowerCase())
|
||||
: item.name?.toLowerCase().includes(search.toLowerCase())
|
||||
})
|
||||
.map((i) => (
|
||||
<div
|
||||
|
||||
@ -38,7 +38,9 @@ export const FormHeader = ({ item, state, setState }: Props) => {
|
||||
}
|
||||
className={'tw:-left-6 tw:top-14 tw:-mr-6'}
|
||||
/>
|
||||
<div className='tw:grow tw:mr-4 tw:pt-1'>
|
||||
<div
|
||||
className={`tw:grow tw:mr-4 ${item.layer?.itemType.subtitle_mode === 'custom' ? 'tw:pt-1' : 'tw:flex tw:items-center'}`}
|
||||
>
|
||||
<TextInput
|
||||
placeholder='Name'
|
||||
defaultValue={item.name ? item.name : ''}
|
||||
@ -51,19 +53,21 @@ export const FormHeader = ({ item, state, setState }: Props) => {
|
||||
containerStyle='tw:grow tw:px-4'
|
||||
inputStyle='tw:input-md'
|
||||
/>
|
||||
<TextInput
|
||||
placeholder='Subtitle'
|
||||
required={false}
|
||||
defaultValue={item.subname ? item.subname : ''}
|
||||
updateFormValue={(v) =>
|
||||
setState((prevState) => ({
|
||||
...prevState,
|
||||
subname: v,
|
||||
}))
|
||||
}
|
||||
containerStyle='tw:grow tw:px-4 tw:mt-1'
|
||||
inputStyle='tw:input-sm'
|
||||
/>
|
||||
{item.layer?.itemType.subtitle_mode === 'custom' && (
|
||||
<TextInput
|
||||
placeholder={item.layer.itemType.subtitle_label}
|
||||
required={false}
|
||||
defaultValue={item.subname ? item.subname : ''}
|
||||
updateFormValue={(v) =>
|
||||
setState((prevState) => ({
|
||||
...prevState,
|
||||
subname: v,
|
||||
}))
|
||||
}
|
||||
containerStyle='tw:grow tw:px-4 tw:mt-1'
|
||||
inputStyle='tw:input-sm'
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -35,7 +35,7 @@ export const GroupSubHeaderView = ({
|
||||
? shareBaseUrl + item.slug
|
||||
: window.location.protocol + '//' + window.location.host + '/item/' + item.id
|
||||
}
|
||||
title={item.name}
|
||||
title={item.name ?? ''}
|
||||
platforms={platforms}
|
||||
/>
|
||||
</div>
|
||||
|
||||
@ -40,7 +40,7 @@ export function LinkedItemsHeaderView({
|
||||
<img
|
||||
className={'tw:w-10 tw:inline tw:rounded-full'}
|
||||
src={avatar}
|
||||
alt={item.name + ' logo'}
|
||||
alt={(item.name ?? '') + ' logo'}
|
||||
/>
|
||||
)}
|
||||
<div className={`${avatar ? 'tw:ml-2' : ''} tw:overflow-hidden`}>
|
||||
|
||||
@ -4,7 +4,7 @@ import type { Item } from '#types/Item'
|
||||
|
||||
export const ProfileStartEndView = ({ item }: { item: Item }) => {
|
||||
return (
|
||||
<div className='tw:mt-2 tw:px-6 tw:max-w-xs'>
|
||||
<div className='tw:mt-2 tw:px-6'>
|
||||
<StartEndView item={item}></StartEndView>
|
||||
</div>
|
||||
)
|
||||
|
||||
@ -9,7 +9,7 @@ const isClickInsideRectangle = (e: MouseEvent, element: HTMLElement) => {
|
||||
}
|
||||
|
||||
interface Props {
|
||||
title: string
|
||||
title?: string
|
||||
isOpened: boolean
|
||||
onClose: () => void
|
||||
children: React.ReactNode
|
||||
@ -52,7 +52,9 @@ const DialogModal = ({
|
||||
}
|
||||
>
|
||||
<div className='tw:card-body tw:p-2'>
|
||||
<h2 className='tw:text-2xl tw:font-semibold tw:mb-2 tw:text-center'>{title}</h2>
|
||||
{title && (
|
||||
<h2 className='tw:text-2xl tw:font-semibold tw:mb-2 tw:text-center'>{title}</h2>
|
||||
)}
|
||||
|
||||
{children}
|
||||
{showCloseButton && (
|
||||
|
||||
2
lib/src/types/Item.d.ts
vendored
2
lib/src/types/Item.d.ts
vendored
@ -27,7 +27,7 @@ interface ItemSecret {
|
||||
*/
|
||||
export interface Item {
|
||||
id: string
|
||||
name: string
|
||||
name?: string
|
||||
text?: string
|
||||
data?: string
|
||||
position?: Point | null
|
||||
|
||||
10
lib/src/types/ItemType.d.ts
vendored
10
lib/src/types/ItemType.d.ts
vendored
@ -18,7 +18,15 @@ export interface ItemType {
|
||||
questlog: boolean
|
||||
custom_profile_url?: string
|
||||
small_form_edit?: boolean
|
||||
botton_label?: string
|
||||
button_label?: string
|
||||
text_input_label?: string
|
||||
show_header_view_in_form?: boolean
|
||||
cta_button_label?: string
|
||||
subtitle_mode?: 'address' | 'custom' | 'none'
|
||||
subtitle_label?: string
|
||||
cta_relation?: string
|
||||
show_cta_button?: boolean
|
||||
show_qr_button?: boolean
|
||||
show_navigation_button?: boolean
|
||||
show_share_button?: boolean
|
||||
}
|
||||
|
||||
272
package-lock.json
generated
272
package-lock.json
generated
@ -67,6 +67,7 @@
|
||||
"license": "GPL-3.0-only",
|
||||
"dependencies": {
|
||||
"@heroicons/react": "^2.0.17",
|
||||
"@maplibre/maplibre-gl-leaflet": "^0.1.3",
|
||||
"@tanstack/react-query": "^5.17.8",
|
||||
"@tiptap/core": "^3.6.5",
|
||||
"@tiptap/extension-bubble-menu": "^3.6.5",
|
||||
@ -84,6 +85,7 @@
|
||||
"date-fns": "^3.3.1",
|
||||
"leaflet": "^1.9.4",
|
||||
"leaflet.locatecontrol": "^0.79.0",
|
||||
"maplibre-gl": "^5.9.0",
|
||||
"radash": "^12.1.0",
|
||||
"react-colorful": "^5.6.1",
|
||||
"react-dropzone": "^14.3.8",
|
||||
@ -2670,6 +2672,123 @@
|
||||
"@jridgewell/sourcemap-codec": "^1.4.14"
|
||||
}
|
||||
},
|
||||
"node_modules/@mapbox/geojson-rewind": {
|
||||
"version": "0.5.2",
|
||||
"resolved": "https://registry.npmjs.org/@mapbox/geojson-rewind/-/geojson-rewind-0.5.2.tgz",
|
||||
"integrity": "sha512-tJaT+RbYGJYStt7wI3cq4Nl4SXxG8W7JDG5DMJu97V25RnbNg3QtQtf+KD+VLjNpWKYsRvXDNmNrBgEETr1ifA==",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"get-stream": "^6.0.1",
|
||||
"minimist": "^1.2.6"
|
||||
},
|
||||
"bin": {
|
||||
"geojson-rewind": "geojson-rewind"
|
||||
}
|
||||
},
|
||||
"node_modules/@mapbox/geojson-rewind/node_modules/get-stream": {
|
||||
"version": "6.0.1",
|
||||
"resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz",
|
||||
"integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/@mapbox/jsonlint-lines-primitives": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@mapbox/jsonlint-lines-primitives/-/jsonlint-lines-primitives-2.0.2.tgz",
|
||||
"integrity": "sha512-rY0o9A5ECsTQRVhv7tL/OyDpGAoUB4tTvLiW1DSzQGq4bvTPhNw1VpSNjDJc5GFZ2XuyOtSWSVN05qOtcD71qQ==",
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/@mapbox/point-geometry": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@mapbox/point-geometry/-/point-geometry-1.1.0.tgz",
|
||||
"integrity": "sha512-YGcBz1cg4ATXDCM/71L9xveh4dynfGmcLDqufR+nQQy3fKwsAZsWd/x4621/6uJaeB9mwOHE6hPeDgXz9uViUQ==",
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/@mapbox/tiny-sdf": {
|
||||
"version": "2.0.7",
|
||||
"resolved": "https://registry.npmjs.org/@mapbox/tiny-sdf/-/tiny-sdf-2.0.7.tgz",
|
||||
"integrity": "sha512-25gQLQMcpivjOSA40g3gO6qgiFPDpWRoMfd+G/GoppPIeP6JDaMMkMrEJnMZhKyyS6iKwVt5YKu02vCUyJM3Ug==",
|
||||
"license": "BSD-2-Clause"
|
||||
},
|
||||
"node_modules/@mapbox/unitbezier": {
|
||||
"version": "0.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@mapbox/unitbezier/-/unitbezier-0.0.1.tgz",
|
||||
"integrity": "sha512-nMkuDXFv60aBr9soUG5q+GvZYL+2KZHVvsqFCzqnkGEf46U2fvmytHaEVc1/YZbiLn8X+eR3QzX1+dwDO1lxlw==",
|
||||
"license": "BSD-2-Clause"
|
||||
},
|
||||
"node_modules/@mapbox/vector-tile": {
|
||||
"version": "2.0.4",
|
||||
"resolved": "https://registry.npmjs.org/@mapbox/vector-tile/-/vector-tile-2.0.4.tgz",
|
||||
"integrity": "sha512-AkOLcbgGTdXScosBWwmmD7cDlvOjkg/DetGva26pIRiZPdeJYjYKarIlb4uxVzi6bwHO6EWH82eZ5Nuv4T5DUg==",
|
||||
"license": "BSD-3-Clause",
|
||||
"dependencies": {
|
||||
"@mapbox/point-geometry": "~1.1.0",
|
||||
"@types/geojson": "^7946.0.16",
|
||||
"pbf": "^4.0.1"
|
||||
}
|
||||
},
|
||||
"node_modules/@mapbox/whoots-js": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@mapbox/whoots-js/-/whoots-js-3.1.0.tgz",
|
||||
"integrity": "sha512-Es6WcD0nO5l+2BOQS4uLfNPYQaNDfbot3X1XUoloz+x0mPDS3eeORZJl06HXjwBG1fOGwCRnzK88LMdxKRrd6Q==",
|
||||
"license": "ISC",
|
||||
"engines": {
|
||||
"node": ">=6.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@maplibre/maplibre-gl-leaflet": {
|
||||
"version": "0.1.3",
|
||||
"resolved": "https://registry.npmjs.org/@maplibre/maplibre-gl-leaflet/-/maplibre-gl-leaflet-0.1.3.tgz",
|
||||
"integrity": "sha512-9+hp1PSJcxuuj5/Zta9zbQ8+ZvN4doWXPtlY7ikNtUZY1VbkamY0uTqzHp9kxRPqpgeKGrI7MjzXvwzU88wWCw==",
|
||||
"license": "ISC",
|
||||
"peerDependencies": {
|
||||
"@types/leaflet": "^1.9.0",
|
||||
"leaflet": "^1.9.3",
|
||||
"maplibre-gl": "^2.4.0 || ^3.3.1 || ^4.3.2 || ^5.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@maplibre/maplibre-gl-style-spec": {
|
||||
"version": "24.2.0",
|
||||
"resolved": "https://registry.npmjs.org/@maplibre/maplibre-gl-style-spec/-/maplibre-gl-style-spec-24.2.0.tgz",
|
||||
"integrity": "sha512-cE80g83fRcBbZbQC70siOUxUK6YJ/5ZkClDZbmm+hzrUbv+J6yntkMmcpdz9DbOrWOM7FHKR5rruc6Q/hWx5cA==",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"@mapbox/jsonlint-lines-primitives": "~2.0.2",
|
||||
"@mapbox/unitbezier": "^0.0.1",
|
||||
"json-stringify-pretty-compact": "^4.0.0",
|
||||
"minimist": "^1.2.8",
|
||||
"quickselect": "^3.0.0",
|
||||
"rw": "^1.3.3",
|
||||
"tinyqueue": "^3.0.0"
|
||||
},
|
||||
"bin": {
|
||||
"gl-style-format": "dist/gl-style-format.mjs",
|
||||
"gl-style-migrate": "dist/gl-style-migrate.mjs",
|
||||
"gl-style-validate": "dist/gl-style-validate.mjs"
|
||||
}
|
||||
},
|
||||
"node_modules/@maplibre/vt-pbf": {
|
||||
"version": "4.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@maplibre/vt-pbf/-/vt-pbf-4.0.3.tgz",
|
||||
"integrity": "sha512-YsW99BwnT+ukJRkseBcLuZHfITB4puJoxnqPVjo72rhW/TaawVYsgQHcqWLzTxqknttYoDpgyERzWSa/XrETdA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@mapbox/point-geometry": "^1.1.0",
|
||||
"@mapbox/vector-tile": "^2.0.4",
|
||||
"@types/geojson-vt": "3.2.5",
|
||||
"@types/supercluster": "^7.1.3",
|
||||
"geojson-vt": "^4.0.2",
|
||||
"pbf": "^4.0.1",
|
||||
"supercluster": "^8.0.1"
|
||||
}
|
||||
},
|
||||
"node_modules/@napi-rs/wasm-runtime": {
|
||||
"version": "0.2.12",
|
||||
"resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-0.2.12.tgz",
|
||||
@ -4497,6 +4616,15 @@
|
||||
"integrity": "sha512-6C8nqWur3j98U6+lXDfTUWIfgvZU+EumvpHKcYjujKH7woYyLj2sUmff0tRhrqM7BohUw7Pz3ZB1jj2gW9Fvmg==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/geojson-vt": {
|
||||
"version": "3.2.5",
|
||||
"resolved": "https://registry.npmjs.org/@types/geojson-vt/-/geojson-vt-3.2.5.tgz",
|
||||
"integrity": "sha512-qDO7wqtprzlpe8FfQ//ClPV9xiuoh2nkIgiouIptON9w5jvD/fA4szvP9GBlDVdJ5dldAl0kX/sy3URbWwLx0g==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/geojson": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/hast": {
|
||||
"version": "3.0.4",
|
||||
"resolved": "https://registry.npmjs.org/@types/hast/-/hast-3.0.4.tgz",
|
||||
@ -4524,7 +4652,6 @@
|
||||
"version": "1.9.20",
|
||||
"resolved": "https://registry.npmjs.org/@types/leaflet/-/leaflet-1.9.20.tgz",
|
||||
"integrity": "sha512-rooalPMlk61LCaLOvBF2VIf9M47HgMQqi5xQ9QRi7c8PkdIe0WrIi5IxXUXQjAdL0c+vcQ01mYWbthzmp9GHWw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/geojson": "*"
|
||||
@ -4640,6 +4767,15 @@
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/supercluster": {
|
||||
"version": "7.1.3",
|
||||
"resolved": "https://registry.npmjs.org/@types/supercluster/-/supercluster-7.1.3.tgz",
|
||||
"integrity": "sha512-Z0pOY34GDFl3Q6hUFYf3HkTwKEE02e7QgtJppBt+beEAxnyOpJua+voGFvxINBHa06GwLFFym7gRPY2SiKIfIA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/geojson": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/trusted-types": {
|
||||
"version": "2.0.7",
|
||||
"resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.7.tgz",
|
||||
@ -7232,6 +7368,12 @@
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/earcut": {
|
||||
"version": "3.0.2",
|
||||
"resolved": "https://registry.npmjs.org/earcut/-/earcut-3.0.2.tgz",
|
||||
"integrity": "sha512-X7hshQbLyMJ/3RPhyObLARM2sNxxmRALLKx1+NVFFnQ9gKzmCrxm9+uLIAdBcvc8FNLpctqlQ2V6AE92Ol9UDQ==",
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/eastasianwidth": {
|
||||
"version": "0.2.0",
|
||||
"resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz",
|
||||
@ -8804,6 +8946,12 @@
|
||||
"node": ">=6.9.0"
|
||||
}
|
||||
},
|
||||
"node_modules/geojson-vt": {
|
||||
"version": "4.0.2",
|
||||
"resolved": "https://registry.npmjs.org/geojson-vt/-/geojson-vt-4.0.2.tgz",
|
||||
"integrity": "sha512-AV9ROqlNqoZEIJGfm1ncNjEXfkz2hdFlZf0qkVfmkwdKa8vj7H16YUOT81rJw1rdFhyEDlN2Tds91p/glzbl5A==",
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/get-intrinsic": {
|
||||
"version": "1.3.0",
|
||||
"resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz",
|
||||
@ -8915,6 +9063,12 @@
|
||||
"assert-plus": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/gl-matrix": {
|
||||
"version": "3.4.4",
|
||||
"resolved": "https://registry.npmjs.org/gl-matrix/-/gl-matrix-3.4.4.tgz",
|
||||
"integrity": "sha512-latSnyDNt/8zYUB6VIJ6PCh2jBjJX6gnDsoCZ7LyW7GkqrD51EWwa9qCoGixj8YqBtETQK/xY7OmpTF8xz1DdQ==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/glob": {
|
||||
"version": "7.2.3",
|
||||
"resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz",
|
||||
@ -10268,6 +10422,12 @@
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/json-stringify-pretty-compact": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/json-stringify-pretty-compact/-/json-stringify-pretty-compact-4.0.0.tgz",
|
||||
"integrity": "sha512-3CNZ2DnrpByG9Nqj6Xo8vqbjT4F6N+tb4Gb28ESAZjYZ5yqvmc56J+/kuIwkaAMOyblTQhUW7PxMkUb8Q36N3Q==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/json-stringify-safe": {
|
||||
"version": "5.0.1",
|
||||
"resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz",
|
||||
@ -10350,6 +10510,12 @@
|
||||
"node": ">=4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/kdbush": {
|
||||
"version": "4.0.2",
|
||||
"resolved": "https://registry.npmjs.org/kdbush/-/kdbush-4.0.2.tgz",
|
||||
"integrity": "sha512-WbCVYJ27Sz8zi9Q7Q0xHC+05iwkm3Znipc2XTlrnJbsHMYktW4hPhXUE8Ys1engBrvffoSCqbil1JQAa7clRpA==",
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/keyv": {
|
||||
"version": "4.5.4",
|
||||
"resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz",
|
||||
@ -10958,6 +11124,43 @@
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/maplibre-gl": {
|
||||
"version": "5.9.0",
|
||||
"resolved": "https://registry.npmjs.org/maplibre-gl/-/maplibre-gl-5.9.0.tgz",
|
||||
"integrity": "sha512-YxW9glb/YrDXGDhqy1u+aG113+L86ttAUpTd6sCkGHyUKMXOX8qbGHJQVqxOczy+4CtRKnqcCfSura2MzB0nQA==",
|
||||
"license": "BSD-3-Clause",
|
||||
"dependencies": {
|
||||
"@mapbox/geojson-rewind": "^0.5.2",
|
||||
"@mapbox/jsonlint-lines-primitives": "^2.0.2",
|
||||
"@mapbox/point-geometry": "^1.1.0",
|
||||
"@mapbox/tiny-sdf": "^2.0.7",
|
||||
"@mapbox/unitbezier": "^0.0.1",
|
||||
"@mapbox/vector-tile": "^2.0.4",
|
||||
"@mapbox/whoots-js": "^3.1.0",
|
||||
"@maplibre/maplibre-gl-style-spec": "^24.2.0",
|
||||
"@maplibre/vt-pbf": "^4.0.3",
|
||||
"@types/geojson": "^7946.0.16",
|
||||
"@types/geojson-vt": "3.2.5",
|
||||
"@types/supercluster": "^7.1.3",
|
||||
"earcut": "^3.0.2",
|
||||
"geojson-vt": "^4.0.2",
|
||||
"gl-matrix": "^3.4.4",
|
||||
"kdbush": "^4.0.2",
|
||||
"murmurhash-js": "^1.0.0",
|
||||
"pbf": "^4.0.1",
|
||||
"potpack": "^2.1.0",
|
||||
"quickselect": "^3.0.0",
|
||||
"supercluster": "^8.0.1",
|
||||
"tinyqueue": "^3.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=16.14.0",
|
||||
"npm": ">=8.1.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/maplibre/maplibre-gl-js?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/markdown-it": {
|
||||
"version": "14.1.0",
|
||||
"resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-14.1.0.tgz",
|
||||
@ -11742,7 +11945,6 @@
|
||||
"version": "1.2.8",
|
||||
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz",
|
||||
"integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
@ -11775,6 +11977,12 @@
|
||||
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/murmurhash-js": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/murmurhash-js/-/murmurhash-js-1.0.0.tgz",
|
||||
"integrity": "sha512-TvmkNhkv8yct0SVBSy+o8wYzXjE4Zz3PCesbfs8HiCXXdcTuocApFv11UWlNFWKYsP2okqrhb7JNlSm9InBhIw==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/nanoid": {
|
||||
"version": "3.3.11",
|
||||
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz",
|
||||
@ -12317,6 +12525,18 @@
|
||||
"node": ">= 14.16"
|
||||
}
|
||||
},
|
||||
"node_modules/pbf": {
|
||||
"version": "4.0.1",
|
||||
"resolved": "https://registry.npmjs.org/pbf/-/pbf-4.0.1.tgz",
|
||||
"integrity": "sha512-SuLdBvS42z33m8ejRbInMapQe8n0D3vN/Xd5fmWM3tufNgRQFBpaW2YVJxQZV4iPNqb0vEFvssMEo5w9c6BTIA==",
|
||||
"license": "BSD-3-Clause",
|
||||
"dependencies": {
|
||||
"resolve-protobuf-schema": "^2.1.0"
|
||||
},
|
||||
"bin": {
|
||||
"pbf": "bin/pbf"
|
||||
}
|
||||
},
|
||||
"node_modules/pend": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz",
|
||||
@ -13001,6 +13221,12 @@
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/potpack": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/potpack/-/potpack-2.1.0.tgz",
|
||||
"integrity": "sha512-pcaShQc1Shq0y+E7GqJqvZj8DTthWV1KeHGdi0Z6IAin2Oi3JnLCOfwnCo84qc+HAp52wT9nK9H7FAJp5a44GQ==",
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/prelude-ls": {
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz",
|
||||
@ -13325,6 +13551,12 @@
|
||||
"prosemirror-transform": "^1.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/protocol-buffers-schema": {
|
||||
"version": "3.6.0",
|
||||
"resolved": "https://registry.npmjs.org/protocol-buffers-schema/-/protocol-buffers-schema-3.6.0.tgz",
|
||||
"integrity": "sha512-TdDRD+/QNdrCGCE7v8340QyuXd4kIWIgapsE2+n/SaGiSSbomYl4TjHlvIoCWRpE7wFt02EpB35VVA2ImcBVqw==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/proxy-from-env": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
|
||||
@ -13404,6 +13636,12 @@
|
||||
],
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/quickselect": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/quickselect/-/quickselect-3.0.0.tgz",
|
||||
"integrity": "sha512-XdjUArbK4Bm5fLLvlm5KpTFOiOThgfWWI4axAZDWg4E/0mKdZyI9tNEfds27qCi1ze/vwTR16kvmmGhRra3c2g==",
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/radash": {
|
||||
"version": "12.1.1",
|
||||
"resolved": "https://registry.npmjs.org/radash/-/radash-12.1.1.tgz",
|
||||
@ -13944,6 +14182,15 @@
|
||||
"url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/resolve-protobuf-schema": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/resolve-protobuf-schema/-/resolve-protobuf-schema-2.1.0.tgz",
|
||||
"integrity": "sha512-kI5ffTiZWmJaS/huM8wZfEMer1eRd7oJQhDuxeCLe3t7N7mX3z94CN0xPxBQxFYQTSNz9T0i+v6inKqSdK8xrQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"protocol-buffers-schema": "^3.3.1"
|
||||
}
|
||||
},
|
||||
"node_modules/restore-cursor": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-3.1.0.tgz",
|
||||
@ -14173,6 +14420,12 @@
|
||||
"queue-microtask": "^1.2.2"
|
||||
}
|
||||
},
|
||||
"node_modules/rw": {
|
||||
"version": "1.3.3",
|
||||
"resolved": "https://registry.npmjs.org/rw/-/rw-1.3.3.tgz",
|
||||
"integrity": "sha512-PdhdWy89SiZogBLaw42zdeqtRJ//zFd2PgQavcICDUgJT5oW10QCRKbJ6bg4r0/UY2M6BWd5tkxuGFRvCkgfHQ==",
|
||||
"license": "BSD-3-Clause"
|
||||
},
|
||||
"node_modules/rxjs": {
|
||||
"version": "7.8.2",
|
||||
"resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.2.tgz",
|
||||
@ -14947,6 +15200,15 @@
|
||||
"postcss": "^8.2.15"
|
||||
}
|
||||
},
|
||||
"node_modules/supercluster": {
|
||||
"version": "8.0.1",
|
||||
"resolved": "https://registry.npmjs.org/supercluster/-/supercluster-8.0.1.tgz",
|
||||
"integrity": "sha512-IiOea5kJ9iqzD2t7QJq/cREyLHTtSmUT6gQsweojg9WH2sYJqZK9SswTu6jrscO6D1G5v5vYZ9ru/eq85lXeZQ==",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"kdbush": "^4.0.2"
|
||||
}
|
||||
},
|
||||
"node_modules/supports-color": {
|
||||
"version": "8.1.1",
|
||||
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz",
|
||||
@ -15269,6 +15531,12 @@
|
||||
"node": "^18.0.0 || >=20.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/tinyqueue": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/tinyqueue/-/tinyqueue-3.0.0.tgz",
|
||||
"integrity": "sha512-gRa9gwYU3ECmQYv3lslts5hxuIa90veaEcxDYuu3QGOIAEM2mOZkVHp48ANJuu1CURtRdHKUBY5Lm1tHV+sD4g==",
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/tinyrainbow": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-2.0.0.tgz",
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user