diff --git a/app/src/App.tsx b/app/src/App.tsx index 29d01af9..34ff2df0 100644 --- a/app/src/App.tsx +++ b/app/src/App.tsx @@ -56,6 +56,7 @@ import MapContainer from './pages/MapContainer' import { getBottomRoutes, routes } from './routes/sidebar' import { config } from './config' import { InviteApi } from './api/inviteApi' +import { MapPinIcon } from '@heroicons/react/24/solid' const userApi = new UserApi() const inviteApi = new InviteApi(userApi) @@ -139,14 +140,19 @@ function App() { ?.filter((l: LayerProps) => l.listed) .map((l: LayerProps) => ({ path: '/' + l.name, // url - icon: ( + icon: l.markerIcon?.image ? ( code.replace(/stroke=".*?"/g, 'stroke="currentColor"') } /> + ) : ( + ), name: l.name, // name that appear in Sidebar color: l.menuColor, diff --git a/backend/directus-config/development/snapshot/fields/layers/markerIcon.json b/backend/directus-config/development/snapshot/fields/layers/markerIcon.json index 43eda564..0ece7a8d 100644 --- a/backend/directus-config/development/snapshot/fields/layers/markerIcon.json +++ b/backend/directus-config/development/snapshot/fields/layers/markerIcon.json @@ -16,7 +16,7 @@ "template": "{{id}}" }, "readonly": false, - "required": true, + "required": false, "sort": 2, "special": [ "m2o" diff --git a/backend/directus-config/development/snapshot/fields/layers/markerShape.json b/backend/directus-config/development/snapshot/fields/layers/markerShape.json index 29c7339d..61224a31 100644 --- a/backend/directus-config/development/snapshot/fields/layers/markerShape.json +++ b/backend/directus-config/development/snapshot/fields/layers/markerShape.json @@ -37,7 +37,7 @@ "name": "markerShape", "table": "layers", "data_type": "character varying", - "default_value": null, + "default_value": "circle", "max_length": 255, "numeric_precision": null, "numeric_scale": null, diff --git a/backend/directus-config/development/snapshot/fields/marker_icons/image_outline.json b/backend/directus-config/development/snapshot/fields/marker_icons/image_outline.json deleted file mode 100644 index 960ceaca..00000000 --- a/backend/directus-config/development/snapshot/fields/marker_icons/image_outline.json +++ /dev/null @@ -1,47 +0,0 @@ -{ - "collection": "marker_icons", - "field": "image_outline", - "type": "uuid", - "meta": { - "collection": "marker_icons", - "conditions": null, - "display": null, - "display_options": null, - "field": "image_outline", - "group": null, - "hidden": false, - "interface": "file-image", - "note": null, - "options": { - "folder": "f255d3a7-8ecc-4ee0-b584-dee753317415" - }, - "readonly": false, - "required": false, - "sort": 4, - "special": [ - "file" - ], - "translations": null, - "validation": null, - "validation_message": null, - "width": "half" - }, - "schema": { - "name": "image_outline", - "table": "marker_icons", - "data_type": "uuid", - "default_value": null, - "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": "directus_files", - "foreign_key_column": "id" - } -} diff --git a/backend/directus-config/development/snapshot/fields/marker_icons/size_outline.json b/backend/directus-config/development/snapshot/fields/marker_icons/size_outline.json deleted file mode 100644 index d6a9bf58..00000000 --- a/backend/directus-config/development/snapshot/fields/marker_icons/size_outline.json +++ /dev/null @@ -1,43 +0,0 @@ -{ - "collection": "marker_icons", - "field": "size_outline", - "type": "decimal", - "meta": { - "collection": "marker_icons", - "conditions": null, - "display": null, - "display_options": null, - "field": "size_outline", - "group": null, - "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": "size_outline", - "table": "marker_icons", - "data_type": "numeric", - "default_value": null, - "max_length": null, - "numeric_precision": 10, - "numeric_scale": 5, - "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 - } -} diff --git a/backend/directus-config/development/snapshot/relations/marker_icons/image_outline.json b/backend/directus-config/development/snapshot/relations/marker_icons/image_outline.json deleted file mode 100644 index 51689393..00000000 --- a/backend/directus-config/development/snapshot/relations/marker_icons/image_outline.json +++ /dev/null @@ -1,25 +0,0 @@ -{ - "collection": "marker_icons", - "field": "image_outline", - "related_collection": "directus_files", - "meta": { - "junction_field": null, - "many_collection": "marker_icons", - "many_field": "image_outline", - "one_allowed_collections": null, - "one_collection": "directus_files", - "one_collection_field": null, - "one_deselect_action": "nullify", - "one_field": null, - "sort_field": null - }, - "schema": { - "table": "marker_icons", - "column": "image_outline", - "foreign_key_table": "directus_files", - "foreign_key_column": "id", - "constraint_name": "marker_icons_image_outline_foreign", - "on_update": "NO ACTION", - "on_delete": "SET NULL" - } -} diff --git a/lib/src/Components/AppShell/SideBar.tsx b/lib/src/Components/AppShell/SideBar.tsx index b1f958b4..4c7d6799 100644 --- a/lib/src/Components/AppShell/SideBar.tsx +++ b/lib/src/Components/AppShell/SideBar.tsx @@ -64,7 +64,7 @@ export function SideBar({ routes, bottomRoutes }: { routes: Route[]; bottomRoute }} >
{route.icon} diff --git a/lib/src/Components/Map/Layer.tsx b/lib/src/Components/Map/Layer.tsx index 48921443..3ffba5cb 100644 --- a/lib/src/Components/Map/Layer.tsx +++ b/lib/src/Components/Map/Layer.tsx @@ -41,6 +41,9 @@ export const Layer = ({ const setItemsApi = useSetItemsApi() const setItemsData = useSetItemsData() + // Ensure markerShape has a valid value, default to 'circle' if null or empty + const normalizedMarkerShape = markerShape || 'circle' + const addTag = useAddTag() const [newTagsToAdd] = useState([]) const [tagsReady] = useState(false) @@ -55,7 +58,7 @@ export const Layer = ({ menuText, menuColor, markerIcon, - markerShape, + markerShape: normalizedMarkerShape, markerDefaultColor, markerDefaultColor2, api, @@ -79,7 +82,7 @@ export const Layer = ({ menuText, menuColor, markerIcon, - markerShape, + markerShape: normalizedMarkerShape, markerDefaultColor, markerDefaultColor2, api, @@ -116,7 +119,7 @@ export const Layer = ({ name, markerDefaultColor, markerDefaultColor2, - markerShape, + markerShape: normalizedMarkerShape, markerIcon, menuText, }} diff --git a/lib/src/Components/Map/Subcomponents/AddButton.tsx b/lib/src/Components/Map/Subcomponents/AddButton.tsx index 334fddb3..ab0e744b 100644 --- a/lib/src/Components/Map/Subcomponents/AddButton.tsx +++ b/lib/src/Components/Map/Subcomponents/AddButton.tsx @@ -1,5 +1,6 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ /* eslint-disable @typescript-eslint/no-non-null-assertion */ +import { MapPinIcon } from '@heroicons/react/24/solid' import { useState } from 'react' import SVG from 'react-inlinesvg' @@ -94,13 +95,18 @@ export default function AddButton({ e.preventDefault() }} > - + {layer.markerIcon?.image ? ( + + ) : ( + + )}
diff --git a/lib/src/Components/Map/Subcomponents/Controls/SearchControl.tsx b/lib/src/Components/Map/Subcomponents/Controls/SearchControl.tsx index 8f50b056..44cc200f 100644 --- a/lib/src/Components/Map/Subcomponents/Controls/SearchControl.tsx +++ b/lib/src/Components/Map/Subcomponents/Controls/SearchControl.tsx @@ -11,7 +11,7 @@ /* eslint-disable @typescript-eslint/no-unsafe-member-access */ /* eslint-disable @typescript-eslint/no-unsafe-call */ import FlagIcon from '@heroicons/react/24/outline/FlagIcon' -import MagnifyingGlassIcon from '@heroicons/react/24/outline/MagnifyingGlassIcon' +import MapPinIcon from '@heroicons/react/24/outline/MapPinIcon' import axios from 'axios' import { LatLng, LatLngBounds, marker } from 'leaflet' import { useRef, useState } from 'react' @@ -167,56 +167,85 @@ export const SearchControl = () => { {itemsResults.length > 0 && tagsResults.length > 0 && (
)} - {itemsResults.slice(0, 5).map((item) => ( -
{ - const marker = Object.entries(leafletRefs).find((r) => r[1].item === item)?.[1] - .marker - if (marker) { - navigate(`/${item.id}?${new URLSearchParams(window.location.search)}`) - } else { - navigate( - 'item/' + item.id + '?' + new URLSearchParams(window.location.search), - ) - } - }} - > - {item.layer?.markerIcon.image ? ( -
- { - code = code.replace(/fill=".*?"/g, 'fill="currentColor"') - code = code.replace(/stroke=".*?"/g, 'stroke="currentColor"') - return code - }} + {itemsResults.slice(0, 5).map((item) => { + // Calculate color using the same logic as PopupView + const itemTags = + item.text + ?.match(/#[^\s#]+/g) + ?.map((tag) => + tags.find((t) => t.name.toLowerCase() === tag.slice(1).toLowerCase()), + ) + .filter(Boolean) ?? [] + + let color1 = item.layer?.markerDefaultColor ?? '#777' + if (item.color) { + color1 = item.color + } else if (itemTags[0]) { + color1 = itemTags[0].color + } + + return ( +
{ + const marker = Object.entries(leafletRefs).find( + (r) => r[1].item === item, + )?.[1].marker + if (marker) { + navigate(`/${item.id}?${new URLSearchParams(window.location.search)}`) + } else { + navigate( + 'item/' + item.id + '?' + new URLSearchParams(window.location.search), + ) + } + }} + > + {item.layer?.markerIcon?.image ? ( +
+ { + code = code.replace(/fill=".*?"/g, 'fill="currentColor"') + code = code.replace(/stroke=".*?"/g, 'stroke="currentColor"') + return code + }} + /> +
+ ) : ( +
-
- ) : ( -
- )} -
-
- {item.name} + )} +
+
+ {item.name} +
+
+ {item.text} +
-
- ))} + ) + })} {Array.from(geoResults).length > 0 && (itemsResults.length > 0 || tagsResults.length > 0) && (
)} {Array.from(geoResults).map((geo) => (
{ @@ -249,19 +278,21 @@ export const SearchControl = () => { hide() }} > - -
+
+ +
+
{geo?.properties.name ? geo?.properties.name : value}
{geo?.properties?.city && `${capitalizeFirstLetter(geo?.properties?.city)}, `}{' '} @@ -281,7 +312,7 @@ export const SearchControl = () => { ))} {isGeoCoordinate(value) && (
{ marker( @@ -306,19 +337,21 @@ export const SearchControl = () => { ) }} > - -
+
+ +
+
{value}
{'Coordiante'} diff --git a/lib/src/Components/Map/Subcomponents/Controls/TagsControl.tsx b/lib/src/Components/Map/Subcomponents/Controls/TagsControl.tsx index d793cccf..6335cc84 100644 --- a/lib/src/Components/Map/Subcomponents/Controls/TagsControl.tsx +++ b/lib/src/Components/Map/Subcomponents/Controls/TagsControl.tsx @@ -6,7 +6,7 @@ export const TagsControl = () => { const removeFilterTag = useRemoveFilterTag() return ( -
+
{filterTags.map((tag) => (
{ export const useShareLogic = (item?: Item) => { const shareUrl = window.location.href const shareTitle = item?.name ?? 'Utopia Map Item' - const inviteLink = item?.secrets - ? `${window.location.origin}/invite/${item.secrets[0].secret}` - : shareUrl + const inviteLink = + item?.secrets && item.secrets.length > 0 + ? `${window.location.origin}/invite/${item.secrets[0].secret}` + : shareUrl const copyLink = () => { navigator.clipboard diff --git a/lib/src/Components/Map/Subcomponents/ItemPopupComponents/HeaderView/index.tsx b/lib/src/Components/Map/Subcomponents/ItemPopupComponents/HeaderView/index.tsx index 1befab1c..94d49262 100644 --- a/lib/src/Components/Map/Subcomponents/ItemPopupComponents/HeaderView/index.tsx +++ b/lib/src/Components/Map/Subcomponents/ItemPopupComponents/HeaderView/index.tsx @@ -85,7 +85,9 @@ export function HeaderView({ onConfirm={deleteCallback ?? (() => undefined)} /> - setQrModalOpen(false)} /> + {showQrButton && ( + setQrModalOpen(false)} /> + )} ) } diff --git a/lib/src/Components/Map/Subcomponents/ItemPopupComponents/TextView.tsx b/lib/src/Components/Map/Subcomponents/ItemPopupComponents/TextView.tsx index 03076bed..67968ff2 100644 --- a/lib/src/Components/Map/Subcomponents/ItemPopupComponents/TextView.tsx +++ b/lib/src/Components/Map/Subcomponents/ItemPopupComponents/TextView.tsx @@ -5,6 +5,7 @@ /* eslint-disable @typescript-eslint/no-unsafe-member-access */ /* eslint-disable @typescript-eslint/no-unsafe-call */ import Markdown from 'react-markdown' +import { Link as RouterLink } from 'react-router-dom' import remarkBreaks from 'remark-breaks' import { useAddFilterTag } from '#components/Map/hooks/useFilter' @@ -46,7 +47,9 @@ export const TextView = ({ innerText = replacedText = rawText } else if (text === undefined) { // Field was omitted by backend (no permission) - innerText = replacedText = `Login to see this ${item?.layer?.item_default_name ?? 'item'}` + innerText = replacedText = `[Login](/login) to see this ${ + item?.layer?.item_default_name ?? 'item' + }` } else if (text === null || text === '') { // Field is not set or empty - show nothing innerText = '' @@ -127,7 +130,12 @@ export const TextView = ({ else return children } - // Default: Link + // Internal Link (React Router) + if (href.startsWith('/')) { + return {children} + } + + // External Link return ( {children} diff --git a/lib/src/Components/Map/UtopiaMap.tsx b/lib/src/Components/Map/UtopiaMap.tsx index 44d87faf..51b202f7 100644 --- a/lib/src/Components/Map/UtopiaMap.tsx +++ b/lib/src/Components/Map/UtopiaMap.tsx @@ -133,6 +133,7 @@ function UtopiaMap({ maplibreStyle={maplibreStyle} zoomOffset={zoomOffset} tileSize={tileSize} + showZoomControl={showZoomControl} > {children} diff --git a/lib/src/Components/Map/UtopiaMapInner.tsx b/lib/src/Components/Map/UtopiaMapInner.tsx index b25f9b13..aa6786cb 100644 --- a/lib/src/Components/Map/UtopiaMapInner.tsx +++ b/lib/src/Components/Map/UtopiaMapInner.tsx @@ -64,6 +64,7 @@ export function UtopiaMapInner({ maplibreStyle, zoomOffset = 0, tileSize = 256, + showZoomControl, }: { children?: React.ReactNode geo?: GeoJsonObject @@ -81,6 +82,7 @@ export function UtopiaMapInner({ maplibreStyle?: string zoomOffset?: number tileSize?: number + showZoomControl?: boolean }) { const selectNewItemPosition = useSelectPosition() const setSelectNewItemPosition = useSetSelectPosition() @@ -285,7 +287,9 @@ export function UtopiaMapInner({ - +
+ +
{showFullscreenControl && } diff --git a/lib/src/Utils/MarkerIconFactory.ts b/lib/src/Utils/MarkerIconFactory.ts index a1984cc4..dc03b3d5 100644 --- a/lib/src/Utils/MarkerIconFactory.ts +++ b/lib/src/Utils/MarkerIconFactory.ts @@ -40,7 +40,7 @@ const MarkerIconFactory = ( ) => { if (icon) return divIcon({ - html: `
${createSvg(shape, color1, color2)}
`, + html: `
${createSvg(shape, color1, color2)}
`, iconAnchor: [17, 40], popupAnchor: [0, -40], iconSize: new Point(40, 46), diff --git a/lib/src/types/LayerProps.d.ts b/lib/src/types/LayerProps.d.ts index 8fd0cf6e..d7fe65d3 100644 --- a/lib/src/types/LayerProps.d.ts +++ b/lib/src/types/LayerProps.d.ts @@ -13,8 +13,8 @@ export interface LayerProps { name: string menuColor: string menuText: string - markerIcon: MarkerIcon - markerShape: string + markerIcon?: MarkerIcon + markerShape?: string markerDefaultColor: string markerDefaultColor2?: string api?: ItemsApi