mirror of
https://github.com/utopia-os/utopia-ui.git
synced 2025-12-13 07:46:10 +00:00
update item position, item parents, focus item on profile load an open cluster
This commit is contained in:
parent
c77972a3be
commit
8484379113
@ -13,7 +13,8 @@ import { FilterProvider } from '../Map/hooks/useFilter'
|
||||
import { ItemsProvider } from '../Map/hooks/useItems'
|
||||
import { LayersProvider } from '../Map/hooks/useLayers'
|
||||
import { LeafletRefsProvider } from '../Map/hooks/useLeafletRefs'
|
||||
import { SelectPositionProvider } from '../Map/hooks/useSetItemPosition'
|
||||
import { SelectPositionProvider } from '../Map/hooks/useSelectPosition'
|
||||
import { ClusterRefProvider } from '../Map/hooks/useClusterRef'
|
||||
|
||||
export function AppShell({ appName, nameWidth, children, assetsApi }: { appName: string, nameWidth?: number, children: React.ReactNode, assetsApi: AssetsApi }) {
|
||||
|
||||
@ -32,23 +33,25 @@ export function AppShell({ appName, nameWidth, children, assetsApi }: { appName:
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<BrowserRouter>
|
||||
<AssetsProvider>
|
||||
<SetAssetsApi assetsApi={assetsApi}></SetAssetsApi>
|
||||
<QuestsProvider initialOpen={true}>
|
||||
<ToastContainer position="top-right"
|
||||
autoClose={2000}
|
||||
hideProgressBar
|
||||
newestOnTop={false}
|
||||
closeOnClick
|
||||
rtl={false}
|
||||
pauseOnFocusLoss
|
||||
draggable
|
||||
pauseOnHover
|
||||
theme="light" />
|
||||
<NavBar appName={appName} nameWidth={nameWidth}></NavBar>
|
||||
<div id="app-content" className="tw-flex tw-!pl-[77px]">
|
||||
{children}
|
||||
</div>
|
||||
</QuestsProvider>
|
||||
<ClusterRefProvider>
|
||||
<SetAssetsApi assetsApi={assetsApi}></SetAssetsApi>
|
||||
<QuestsProvider initialOpen={true}>
|
||||
<ToastContainer position="top-right"
|
||||
autoClose={2000}
|
||||
hideProgressBar
|
||||
newestOnTop={false}
|
||||
closeOnClick
|
||||
rtl={false}
|
||||
pauseOnFocusLoss
|
||||
draggable
|
||||
pauseOnHover
|
||||
theme="light" />
|
||||
<NavBar appName={appName} nameWidth={nameWidth}></NavBar>
|
||||
<div id="app-content" className="tw-flex tw-!pl-[77px]">
|
||||
{children}
|
||||
</div>
|
||||
</QuestsProvider>
|
||||
</ClusterRefProvider>
|
||||
</AssetsProvider>
|
||||
</BrowserRouter>
|
||||
</QueryClientProvider>
|
||||
|
||||
@ -6,7 +6,7 @@ import { ItemViewPopup } from './Subcomponents/ItemViewPopup'
|
||||
import { useAllItemsLoaded, useItems, useSetItemsApi, useSetItemsData } from './hooks/useItems'
|
||||
import { useEffect, useState } from 'react'
|
||||
import { ItemFormPopup } from './Subcomponents/ItemFormPopup'
|
||||
import { useFilterTags, useIsLayerVisible } from './hooks/useFilter'
|
||||
import { useFilterTags, useIsLayerVisible, useResetFilterTags } from './hooks/useFilter'
|
||||
import { useAddTag, useAllTagsLoaded, useGetItemTags, useTags } from './hooks/useTags'
|
||||
import { useAddMarker, useAddPopup, useLeafletRefs } from './hooks/useLeafletRefs'
|
||||
import { Popup } from 'leaflet'
|
||||
@ -15,6 +15,7 @@ import { getValue } from '../../Utils/GetValue'
|
||||
import { hashTagRegex } from '../../Utils/HashTagRegex'
|
||||
import { randomColor } from '../../Utils/RandomColor'
|
||||
import { encodeTag } from '../../Utils/FormatTags'
|
||||
import { useSetMarkerClicked } from './hooks/useSelectPosition'
|
||||
|
||||
export const Layer = ({
|
||||
data,
|
||||
@ -27,6 +28,7 @@ export const Layer = ({
|
||||
markerShape = 'circle',
|
||||
markerDefaultColor = '#777',
|
||||
api,
|
||||
itemType,
|
||||
itemNameField = 'name',
|
||||
itemTextField = 'text',
|
||||
itemAvatarField,
|
||||
@ -53,12 +55,15 @@ export const Layer = ({
|
||||
const addMarker = useAddMarker();
|
||||
const addPopup = useAddPopup();
|
||||
const leafletRefs = useLeafletRefs();
|
||||
const resetFilterTags = useResetFilterTags();
|
||||
|
||||
let location = useLocation();
|
||||
const location = useLocation();
|
||||
|
||||
const allTagsLoaded = useAllTagsLoaded();
|
||||
const allItemsLoaded = useAllItemsLoaded();
|
||||
|
||||
const setMarkerClicked = useSetMarkerClicked();
|
||||
|
||||
const tags = useTags();
|
||||
const addTag = useAddTag();
|
||||
const [newTagsToAdd, setNewTagsToAdd] = useState<Tag[]>([]);
|
||||
@ -71,16 +76,16 @@ export const Layer = ({
|
||||
|
||||
|
||||
useEffect(() => {
|
||||
data && setItemsData({ data, children, name, menuIcon, menuText, menuColor, markerIcon, markerShape, markerDefaultColor, api, itemNameField, itemTextField, itemAvatarField, itemColorField, itemOwnerField, itemTagsField, itemOffersField, itemNeedsField, onlyOnePerOwner, customEditLink, setItemFormPopup, itemFormPopup, clusterRef });
|
||||
api && setItemsApi({ data, children, name, menuIcon, menuText, menuColor, markerIcon, markerShape, markerDefaultColor, api, itemNameField, itemTextField, itemAvatarField, itemColorField, itemOwnerField, itemTagsField, itemOffersField, itemNeedsField, onlyOnePerOwner, customEditLink, setItemFormPopup, itemFormPopup, clusterRef });
|
||||
data && setItemsData({ data, children, name, menuIcon, menuText, menuColor, markerIcon, markerShape, markerDefaultColor, api, itemType, itemNameField, itemTextField, itemAvatarField, itemColorField, itemOwnerField, itemTagsField, itemOffersField, itemNeedsField, onlyOnePerOwner, customEditLink, setItemFormPopup, itemFormPopup, clusterRef });
|
||||
api && setItemsApi({ data, children, name, menuIcon, menuText, menuColor, markerIcon, markerShape, markerDefaultColor, api, itemType, itemNameField, itemTextField, itemAvatarField, itemColorField, itemOwnerField, itemTagsField, itemOffersField, itemNeedsField, onlyOnePerOwner, customEditLink, setItemFormPopup, itemFormPopup, clusterRef });
|
||||
}, [data, api])
|
||||
|
||||
useMapEvents({
|
||||
popupopen: (e) => {
|
||||
const item = Object.entries(leafletRefs).find(r => r[1].popup == e.popup)?.[1].item;
|
||||
if (item?.layer?.name == name && window.location.pathname.split("/")[2] != item.id) {
|
||||
let params = new URLSearchParams(window.location.search);
|
||||
window.history.pushState({}, "", `/${name}/${item.id}`+ `${params.toString() !== "" ? `?${params}` : ""}`)
|
||||
let params = new URLSearchParams(window.location.search);
|
||||
window.history.pushState({}, "", `/${name}/${item.id}` + `${params.toString() !== "" ? `?${params}` : ""}`)
|
||||
let title = "";
|
||||
if (item.name) title = item.name;
|
||||
else if (item.layer?.itemNameField) title = getValue(item, item.layer.itemNameField);
|
||||
@ -98,8 +103,9 @@ export const Layer = ({
|
||||
if (window.location.pathname.split("/")[2]) {
|
||||
const id = window.location.pathname.split("/")[2]
|
||||
const marker = leafletRefs[id]?.marker;
|
||||
if (marker && marker != null) {
|
||||
marker !== null && clusterRef?.current?.zoomToShowLayer(marker, () => {
|
||||
resetFilterTags();
|
||||
if (marker && filterTags.length == 0) {
|
||||
marker !== null && clusterRef?.zoomToShowLayer(marker, () => {
|
||||
marker.openPopup();
|
||||
});
|
||||
const item = leafletRefs[id]?.item;
|
||||
@ -113,7 +119,6 @@ export const Layer = ({
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
@ -157,7 +162,7 @@ export const Layer = ({
|
||||
}
|
||||
|
||||
|
||||
if (allTagsLoaded && allItemsLoaded) {
|
||||
if (allTagsLoaded && allItemsLoaded) {
|
||||
item[itemTextField].match(hashTagRegex)?.map(tag => {
|
||||
if ((!tags.find((t) => t.name.toLocaleLowerCase() === tag.slice(1).toLocaleLowerCase())) && !newTagsToAdd.find((t) => t.name.toLocaleLowerCase() === tag.slice(1).toLocaleLowerCase())) {
|
||||
const newTag = { id: crypto.randomUUID(), name: tag.slice(1), color: randomColor() };
|
||||
@ -188,7 +193,13 @@ export const Layer = ({
|
||||
<Marker ref={(r) => {
|
||||
if (!(item.id in leafletRefs && leafletRefs[item.id].marker == r))
|
||||
r && addMarker(item, r);
|
||||
}} icon={MarkerIconFactory(markerShape, color1, color2, markerIcon)} key={item.id} position={[latitude, longitude]}>
|
||||
}}
|
||||
eventHandlers={{
|
||||
click: () => {
|
||||
setMarkerClicked(item)
|
||||
},
|
||||
}}
|
||||
icon={MarkerIconFactory(markerShape, color1, color2, markerIcon)} key={item.id} position={[latitude, longitude]}>
|
||||
{
|
||||
(children && React.Children.toArray(children).some(child => React.isValidElement(child) && child.props.__TYPE === "ItemView") ?
|
||||
React.Children.toArray(children).map((child) =>
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import * as React from 'react'
|
||||
import { useAddFilterTag, useFilterTags, useResetFilterTags, useSetSearchPhrase } from '../../hooks/useFilter'
|
||||
import { useAddFilterTag, useFilterTags, useResetFilterTags } from '../../hooks/useFilter'
|
||||
import useWindowDimensions from '../../hooks/useWindowDimension';
|
||||
import axios from 'axios';
|
||||
import { useRef, useState } from 'react';
|
||||
@ -15,10 +15,11 @@ import * as L from 'leaflet';
|
||||
import MarkerIconFactory from '../../../../Utils/MarkerIconFactory';
|
||||
import { decodeTag } from '../../../../Utils/FormatTags';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { useClusterRef } from '../../hooks/useClusterRef';
|
||||
|
||||
|
||||
|
||||
export const SearchControl = ({ clusterRef }) => {
|
||||
export const SearchControl = () => {
|
||||
|
||||
const windowDimensions = useWindowDimensions();
|
||||
const [popupOpen, setPopupOpen] = useState(false);
|
||||
@ -36,6 +37,7 @@ export const SearchControl = ({ clusterRef }) => {
|
||||
const addFilterTag = useAddFilterTag();
|
||||
const resetFilterTags = useResetFilterTags();
|
||||
const filterTags = useFilterTags();
|
||||
const clusterRef = useClusterRef();
|
||||
|
||||
useMapEvents({
|
||||
popupopen: () => {
|
||||
@ -115,17 +117,9 @@ export const SearchControl = ({ clusterRef }) => {
|
||||
<div key={item.id} className='tw-cursor-pointer hover:tw-font-bold' onClick={() => {
|
||||
const marker = Object.entries(leafletRefs).find(r => r[1].item == item)?.[1].marker;
|
||||
if(marker){
|
||||
if (filterTags.length > 0) {
|
||||
marker !== null && window.history.pushState({}, "", `/${item.layer.name}/${item.id}`)
|
||||
resetFilterTags();
|
||||
hide();
|
||||
}
|
||||
else {
|
||||
marker !== null && clusterRef?.current?.zoomToShowLayer(marker, () => {
|
||||
marker?.openPopup();
|
||||
hide();
|
||||
});
|
||||
}
|
||||
navigate(`/${item.layer.name}/${item.id}`)
|
||||
|
||||
|
||||
}
|
||||
else {
|
||||
navigate("item/"+item.id)
|
||||
|
||||
@ -96,7 +96,7 @@ export function ItemFormPopup(props: ItemFormPopupProps) {
|
||||
const item = items.find(item => item.layer == props.layer && item.user_created?.id == user?.id);
|
||||
item && removeItem(item);
|
||||
}
|
||||
addItem({...formItem, id: uuid, layer: props.layer, user_created: user});
|
||||
addItem({...formItem, id: uuid, layer: props.layer, user_created: user, type: props.layer.itemType });
|
||||
toast.success("New item created");
|
||||
resetFilterTags();
|
||||
}
|
||||
|
||||
@ -5,16 +5,17 @@ import { getValue } from "../../../../Utils/GetValue";
|
||||
import { useAssetApi } from '../../../AppShell/hooks/useAssets'
|
||||
import DialogModal from "../../../Templates/DialogModal";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import { useSetSelectPosition } from "../../hooks/useSetItemPosition";
|
||||
import { useMap } from "react-leaflet";
|
||||
|
||||
|
||||
|
||||
|
||||
export function HeaderView({ item, api, editCallback, deleteCallback, itemNameField, itemAvatarField, loading, hideMenu = false, big = false, updatePosition = false }: {
|
||||
export function HeaderView({ item, api, editCallback, deleteCallback, setPositionCallback, itemNameField, itemAvatarField, loading, hideMenu = false, big = false }: {
|
||||
item: Item,
|
||||
api?: ItemsApi<any>,
|
||||
editCallback?: any,
|
||||
deleteCallback?: any,
|
||||
setPositionCallback?: any,
|
||||
itemNameField?: string,
|
||||
itemAvatarField?: string,
|
||||
loading?: boolean,
|
||||
@ -29,7 +30,6 @@ export function HeaderView({ item, api, editCallback, deleteCallback, itemNameFi
|
||||
const hasUserPermission = useHasUserPermission();
|
||||
const navigate = useNavigate();
|
||||
const assetsApi = useAssetApi();
|
||||
const setSelectPosition = useSetSelectPosition();
|
||||
|
||||
const avatar = itemAvatarField && getValue(item, itemAvatarField) ? assetsApi.url + getValue(item, itemAvatarField) + `${big ? "?width=160&heigth=160": "?width=80&heigth=80"}` : item.layer?.itemAvatarField && item && getValue(item, item.layer?.itemAvatarField) && assetsApi.url + getValue(item, item.layer?.itemAvatarField) + `${big ? "?width=160&heigth=160": "?width=80&heigth=80"}`;
|
||||
const title = itemNameField ? getValue(item, itemNameField) : item.layer?.itemNameField && item && getValue(item, item.layer?.itemNameField);
|
||||
@ -63,21 +63,21 @@ export function HeaderView({ item, api, editCallback, deleteCallback, itemNameFi
|
||||
</svg>
|
||||
</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)) || item.layer?.customEditLink) && <li>
|
||||
<a className="!tw-text-base-content tw-cursor-pointer" onClick={() => item.layer?.customEditLink? navigate(item.layer.customEditLink) : editCallback}>
|
||||
{((api?.updateItem && hasUserPermission(api.collectionName!, "update", item)) || item.layer?.customEditLink) && editCallback && <li>
|
||||
<a className="!tw-text-base-content tw-cursor-pointer" onClick={() => item.layer?.customEditLink? navigate(item.layer.customEditLink) : editCallback()}>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" className="tw-h-5 tw-w-5" viewBox="0 0 20 20" fill="currentColor">
|
||||
<path d="M13.586 3.586a2 2 0 112.828 2.828l-.793.793-2.828-2.828.793-.793zM11.379 5.793L3 14.172V17h2.828l8.38-8.379-2.83-2.828z" />
|
||||
</svg>
|
||||
</a>
|
||||
</li>}
|
||||
{((api?.updateItem && hasUserPermission(api.collectionName!, "update", item))) && updatePosition &&<li>
|
||||
<a className="!tw-text-base-content tw-cursor-pointer" onClick={() => {setSelectPosition(item), navigate("/")}}>
|
||||
{((api?.updateItem && hasUserPermission(api.collectionName!, "update", item))) && setPositionCallback &&<li>
|
||||
<a className="!tw-text-base-content tw-cursor-pointer" onClick={setPositionCallback}>
|
||||
<svg stroke="currentColor" fill="currentColor" strokeWidth="0" viewBox="0 0 512 512" className="tw-w-5 tw-h-5" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M256 0c17.7 0 32 14.3 32 32V42.4c93.7 13.9 167.7 88 181.6 181.6H480c17.7 0 32 14.3 32 32s-14.3 32-32 32H469.6c-13.9 93.7-88 167.7-181.6 181.6V480c0 17.7-14.3 32-32 32s-32-14.3-32-32V469.6C130.3 455.7 56.3 381.7 42.4 288H32c-17.7 0-32-14.3-32-32s14.3-32 32-32H42.4C56.3 130.3 130.3 56.3 224 42.4V32c0-17.7 14.3-32 32-32zM107.4 288c12.5 58.3 58.4 104.1 116.6 116.6V384c0-17.7 14.3-32 32-32s32 14.3 32 32v20.6c58.3-12.5 104.1-58.4 116.6-116.6H384c-17.7 0-32-14.3-32-32s14.3-32 32-32h20.6C392.1 165.7 346.3 119.9 288 107.4V128c0 17.7-14.3 32-32 32s-32-14.3-32-32V107.4C165.7 119.9 119.9 165.7 107.4 224H128c17.7 0 32 14.3 32 32s-14.3 32-32 32H107.4zM256 224a32 32 0 1 1 0 64 32 32 0 1 1 0-64z"></path>
|
||||
</svg>
|
||||
</a>
|
||||
</li>}
|
||||
{api?.deleteItem && hasUserPermission(api.collectionName!, "delete", item) && <li>
|
||||
{api?.deleteItem && hasUserPermission(api.collectionName!, "delete", item) && deleteCallback &&<li>
|
||||
<a className='tw-cursor-pointer !tw-text-error' onClick={openDeleteModal}>
|
||||
{loading ? <span className="tw-loading tw-loading-spinner tw-loading-sm"></span>
|
||||
:
|
||||
|
||||
@ -4,8 +4,11 @@ import { getValue } from '../../../../Utils/GetValue'
|
||||
import { Item } from '../../../../types'
|
||||
|
||||
export const PopupButton = ({url, parameterField, text, colorField, item} : {url: string, parameterField?: string, text: string, colorField?: string, item? : Item}) => {
|
||||
let params = new URLSearchParams(window.location.search);
|
||||
|
||||
|
||||
return (
|
||||
<Link to={`${url}/${parameterField? getValue(item,parameterField):``}`}><button style={{backgroundColor: `${colorField && getValue(item,colorField)? getValue(item,colorField) : item?.layer?.markerDefaultColor}`}} className="tw-btn tw-text-white tw-btn-sm tw-float-right tw-mt-1">{text}</button></Link>
|
||||
<Link to={`${url}/${parameterField? getValue(item,parameterField):``}?${params}`}><button style={{backgroundColor: `${colorField && getValue(item,colorField)? getValue(item,colorField) : item?.layer?.markerDefaultColor}`}} className="tw-btn tw-text-white tw-btn-sm tw-float-right tw-mt-1">{text}</button></Link>
|
||||
|
||||
)
|
||||
}
|
||||
|
||||
@ -10,6 +10,7 @@ import { LatLng } from 'leaflet'
|
||||
import { useNavigate } from 'react-router-dom'
|
||||
import { useRemoveItem } from '../hooks/useItems'
|
||||
import { toast } from 'react-toastify'
|
||||
import { useSetSelectPosition } from '../hooks/useSelectPosition'
|
||||
|
||||
|
||||
export interface ItemViewPopupProps {
|
||||
@ -26,6 +27,7 @@ export const ItemViewPopup = React.forwardRef((props: ItemViewPopupProps, ref: a
|
||||
const [loading, setLoading] = React.useState<boolean>(false);
|
||||
const removeItem = useRemoveItem();
|
||||
const navigate = useNavigate();
|
||||
const setSelectPosition = useSetSelectPosition();
|
||||
|
||||
|
||||
const [infoExpanded, setInfoExpanded] = useState<Boolean>(false);
|
||||
@ -61,7 +63,7 @@ export const ItemViewPopup = React.forwardRef((props: ItemViewPopupProps, ref: a
|
||||
return (
|
||||
<LeafletPopup ref={ref} maxHeight={377} minWidth={275} maxWidth={275} autoPanPadding={[20, 80]}>
|
||||
<div className='tw-bg-base-100 tw-text-base-content'>
|
||||
<HeaderView api={props.item.layer?.api} item={props.item} editCallback={handleEdit} deleteCallback={handleDelete} updatePosition/>
|
||||
<HeaderView api={props.item.layer?.api} item={props.item} editCallback={handleEdit} deleteCallback={handleDelete} setPositionCallback={()=>{map.closePopup();setSelectPosition(props.item); navigate("/")}} loading={loading} />
|
||||
<div className='tw-overflow-y-auto tw-overflow-x-hidden tw-max-h-64 fade'>
|
||||
{props.children ?
|
||||
|
||||
|
||||
@ -14,9 +14,10 @@ import { QuestControl } from "./Subcomponents/Controls/QuestControl";
|
||||
import { Control } from "./Subcomponents/Controls/Control";
|
||||
import { Outlet, useLocation, useNavigate } from "react-router-dom";
|
||||
import { TagsControl } from "./Subcomponents/Controls/TagsControl";
|
||||
import { useSelectPosition, useSetSelectPosition } from "./hooks/useSetItemPosition";
|
||||
import { useSelectPosition, useSetSelectPosition } from "./hooks/useSelectPosition";
|
||||
import { useUpdateItem } from "./hooks/useItems";
|
||||
import { toast } from "react-toastify";
|
||||
import { useClusterRef, useSetClusterRef } from "./hooks/useClusterRef";
|
||||
|
||||
|
||||
export interface MapEventListenerProps {
|
||||
@ -44,15 +45,14 @@ function UtopiaMap({
|
||||
window.history.pushState({}, "", `/` + `${params.toString() !== "" ? `?${params}` : ""}`)
|
||||
document.title = document.title.split("-")[0];
|
||||
document.querySelector('meta[property="og:title"]')?.setAttribute("content", document.title);
|
||||
document.querySelector('meta[property="og:description"]')?.setAttribute("content", `${document.querySelector('meta[name="description"]')?.getAttribute("content")}`);
|
||||
|
||||
document.querySelector('meta[property="og:description"]')?.setAttribute("content", `${document.querySelector('meta[name="description"]')?.getAttribute("content")}`);
|
||||
console.log(e.latlng.lat + ',' + e.latlng.lng);
|
||||
if (selectNewItemPosition != null) {
|
||||
if ('menuIcon' in selectNewItemPosition) {
|
||||
props.setItemFormPopup({ layer: props.selectNewItemPosition, position: e.latlng })
|
||||
props.setSelectNewItemPosition(null)
|
||||
}
|
||||
if ('position' in selectNewItemPosition) {
|
||||
if ('text' in selectNewItemPosition) {
|
||||
const position = new Geometry(e.latlng.lng,e.latlng.lat);
|
||||
itemUpdate({...selectNewItemPosition as Item, position: position })
|
||||
setSelectNewItemPosition(null);
|
||||
@ -61,14 +61,13 @@ function UtopiaMap({
|
||||
},
|
||||
moveend: (e) => {
|
||||
console.log(e);
|
||||
}
|
||||
},
|
||||
|
||||
})
|
||||
return null
|
||||
}
|
||||
|
||||
const itemUpdate = async (updatedItem: Item) => {
|
||||
console.log(updatedItem);
|
||||
console.log(updatedItem?.layer?.api?.updateItem!);
|
||||
let success = false;
|
||||
try {
|
||||
await updatedItem?.layer?.api?.updateItem!({id: updatedItem.id, position: updatedItem.position })
|
||||
@ -85,10 +84,11 @@ function UtopiaMap({
|
||||
|
||||
const selectNewItemPosition = useSelectPosition();
|
||||
const setSelectNewItemPosition = useSetSelectPosition();
|
||||
const clusterRef = React.useRef();
|
||||
const location = useLocation();
|
||||
const updateItem = useUpdateItem();
|
||||
const navigate = useNavigate();
|
||||
const setClusterRef = useSetClusterRef();
|
||||
const clusterRef = useClusterRef();
|
||||
|
||||
const [itemFormPopup, setItemFormPopup] = useState<ItemFormPopupProps | null>(null);
|
||||
|
||||
@ -100,13 +100,11 @@ function UtopiaMap({
|
||||
|
||||
return (
|
||||
<>
|
||||
|
||||
|
||||
<div className={(selectNewItemPosition != null ? "crosshair-cursor-enabled" : undefined)}>
|
||||
<MapContainer ref={mapDivRef} style={{ height: height, width: width }} center={new LatLng(center[0], center[1])} zoom={zoom} zoomControl={false} maxZoom={19}>
|
||||
<Outlet></Outlet>
|
||||
<Control position='topLeft' zIndex="1000">
|
||||
<SearchControl clusterRef={clusterRef} />
|
||||
<SearchControl />
|
||||
<TagsControl />
|
||||
</Control>
|
||||
<Control position='bottomLeft' zIndex="999">
|
||||
@ -117,7 +115,7 @@ function UtopiaMap({
|
||||
maxZoom={19}
|
||||
attribution='© <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'
|
||||
url="https://tile.osmand.net/hd/{z}/{x}/{y}.png" />
|
||||
<MarkerClusterGroup ref={clusterRef} showCoverageOnHover chunkedLoading maxClusterRadius={50} removeOutsideVisibleBounds={false}>
|
||||
<MarkerClusterGroup ref={(r)=> setClusterRef(r)} showCoverageOnHover chunkedLoading maxClusterRadius={50} removeOutsideVisibleBounds={false}>
|
||||
{
|
||||
React.Children.toArray(children).map((child) =>
|
||||
React.isValidElement<{ setItemFormPopup: React.Dispatch<React.SetStateAction<ItemFormPopupProps>>, itemFormPopup: ItemFormPopupProps | null, clusterRef: React.MutableRefObject<undefined> }>(child) ?
|
||||
|
||||
40
src/Components/Map/hooks/useClusterRef.tsx
Normal file
40
src/Components/Map/hooks/useClusterRef.tsx
Normal file
@ -0,0 +1,40 @@
|
||||
import { createContext, useContext, useState } from "react";
|
||||
|
||||
type UseClusterRefManagerResult = ReturnType<typeof useClusterRefManager>;
|
||||
|
||||
const ClusterRefContext = createContext<UseClusterRefManagerResult>({
|
||||
clusterRef: {} as React.MutableRefObject<undefined>,
|
||||
setClusterRef: () => { },
|
||||
});
|
||||
|
||||
function useClusterRefManager(): {
|
||||
clusterRef: any
|
||||
setClusterRef: React.Dispatch<React.SetStateAction<React.MutableRefObject<undefined>>>;
|
||||
} {
|
||||
const [clusterRef, setClusterRef] = useState<React.MutableRefObject<undefined>>({} as React.MutableRefObject<undefined>);
|
||||
|
||||
return { clusterRef, setClusterRef };
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
export const ClusterRefProvider: React.FunctionComponent<{
|
||||
children?: React.ReactNode
|
||||
}> = ({ children }) => (
|
||||
<ClusterRefContext.Provider value={useClusterRefManager()}>
|
||||
{children}
|
||||
</ClusterRefContext.Provider>
|
||||
);
|
||||
|
||||
export const useClusterRef = (): any=> {
|
||||
const { clusterRef } = useContext(ClusterRefContext);
|
||||
return clusterRef;
|
||||
};
|
||||
|
||||
export const useSetClusterRef = (): UseClusterRefManagerResult["setClusterRef"] => {
|
||||
const { setClusterRef } = useContext(ClusterRefContext);
|
||||
return setClusterRef;
|
||||
}
|
||||
|
||||
107
src/Components/Map/hooks/useSelectPosition.tsx
Normal file
107
src/Components/Map/hooks/useSelectPosition.tsx
Normal file
@ -0,0 +1,107 @@
|
||||
import { createContext, useContext, useEffect, useState } from "react";
|
||||
import { Item, LayerProps } from '../../../types';
|
||||
import { useUpdateItem } from "./useItems";
|
||||
import { toast } from "react-toastify";
|
||||
import { useHasUserPermission } from "./usePermissions";
|
||||
|
||||
type UseSelectPositionManagerResult = ReturnType<typeof useSelectPositionManager>;
|
||||
|
||||
const SelectPositionContext = createContext<UseSelectPositionManagerResult>({
|
||||
selectPosition: null,
|
||||
setSelectPosition: () => { },
|
||||
setMarkerClicked: () => { },
|
||||
});
|
||||
|
||||
function useSelectPositionManager(): {
|
||||
selectPosition: Item | LayerProps | null;
|
||||
setSelectPosition: React.Dispatch<React.SetStateAction<Item | LayerProps | null>>;
|
||||
setMarkerClicked: React.Dispatch<React.SetStateAction<Item>>;
|
||||
} {
|
||||
const [selectPosition, setSelectPosition] = useState<LayerProps | null | Item>(null);
|
||||
const [markerClicked, setMarkerClicked] = useState<Item>();
|
||||
const updateItem = useUpdateItem();
|
||||
const hasUserPermission = useHasUserPermission();
|
||||
|
||||
|
||||
useEffect(() => {
|
||||
if (selectPosition && markerClicked && 'text' in selectPosition) {
|
||||
itemUpdate({ ...selectPosition, parent: markerClicked.id })
|
||||
}
|
||||
}, [markerClicked])
|
||||
|
||||
const itemUpdate = async (updatedItem: Item) => {
|
||||
if (markerClicked?.layer?.api?.collectionName && hasUserPermission(markerClicked?.layer?.api?.collectionName, "update", markerClicked)) {
|
||||
let success = false;
|
||||
try {
|
||||
await updatedItem?.layer?.api?.updateItem!({ id: updatedItem.id, parent: updatedItem.parent, position: null })
|
||||
success = true;
|
||||
} catch (error) {
|
||||
toast.error(error.toString());
|
||||
}
|
||||
if (success) {
|
||||
await updateItem({ ...updatedItem, parent: updatedItem.parent, position: undefined })
|
||||
await linkItem(updatedItem.id);
|
||||
toast.success("Item position updated");
|
||||
setSelectPosition(null);
|
||||
}
|
||||
}
|
||||
else {
|
||||
setSelectPosition(null);
|
||||
toast.error("you don't have permission to add items to " + markerClicked?.name);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
const linkItem = async (id: string) => {
|
||||
if (markerClicked) {
|
||||
let new_relations = markerClicked.relations || [];
|
||||
console.log(new_relations);
|
||||
console.log(id);
|
||||
|
||||
|
||||
if (!new_relations.some(r => r.related_items_id == id)) {
|
||||
new_relations?.push({ items_id: markerClicked.id, related_items_id: id })
|
||||
const updatedItem = { id: markerClicked.id, relations: new_relations }
|
||||
|
||||
let success = false;
|
||||
try {
|
||||
await markerClicked?.layer?.api?.updateItem!(updatedItem)
|
||||
success = true;
|
||||
} catch (error) {
|
||||
toast.error(error.toString());
|
||||
}
|
||||
if (success) {
|
||||
updateItem({ ...markerClicked, relations: new_relations })
|
||||
toast.success("Item linked");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return { selectPosition, setSelectPosition, setMarkerClicked };
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
export const SelectPositionProvider: React.FunctionComponent<{
|
||||
children?: React.ReactNode
|
||||
}> = ({ children }) => (
|
||||
<SelectPositionContext.Provider value={useSelectPositionManager()}>
|
||||
{children}
|
||||
</SelectPositionContext.Provider>
|
||||
);
|
||||
|
||||
export const useSelectPosition = (): Item | LayerProps | null => {
|
||||
const { selectPosition } = useContext(SelectPositionContext);
|
||||
return selectPosition;
|
||||
};
|
||||
|
||||
export const useSetSelectPosition = (): UseSelectPositionManagerResult["setSelectPosition"] => {
|
||||
const { setSelectPosition } = useContext(SelectPositionContext);
|
||||
return setSelectPosition;
|
||||
}
|
||||
|
||||
export const useSetMarkerClicked = (): UseSelectPositionManagerResult["setMarkerClicked"] => {
|
||||
const { setMarkerClicked } = useContext(SelectPositionContext);
|
||||
return setMarkerClicked;
|
||||
}
|
||||
@ -1,35 +0,0 @@
|
||||
import { createContext, useContext, useState } from "react";
|
||||
import { Item, LayerProps } from '../../../types';
|
||||
|
||||
type UseSelectPositionManagerResult = ReturnType<typeof useSelectPositionManager>;
|
||||
|
||||
const SelectPositionContext = createContext<UseSelectPositionManagerResult>({
|
||||
selectPosition: null,
|
||||
setSelectPosition: () => { },
|
||||
});
|
||||
|
||||
function useSelectPositionManager(): {
|
||||
selectPosition: Item | LayerProps | null;
|
||||
setSelectPosition: React.Dispatch<React.SetStateAction< Item | LayerProps | null>>;
|
||||
} {
|
||||
const [selectPosition, setSelectPosition] = useState<LayerProps | null | Item>(null);
|
||||
return { selectPosition, setSelectPosition };
|
||||
}
|
||||
|
||||
export const SelectPositionProvider: React.FunctionComponent<{
|
||||
children?: React.ReactNode
|
||||
}> = ({ children }) => (
|
||||
<SelectPositionContext.Provider value={useSelectPositionManager()}>
|
||||
{children}
|
||||
</SelectPositionContext.Provider>
|
||||
);
|
||||
|
||||
export const useSelectPosition = (): Item | LayerProps | null => {
|
||||
const { selectPosition } = useContext(SelectPositionContext);
|
||||
return selectPosition;
|
||||
};
|
||||
|
||||
export const useSetSelectPosition = (): UseSelectPositionManagerResult["setSelectPosition"] => {
|
||||
const { setSelectPosition } = useContext(SelectPositionContext);
|
||||
return setSelectPosition;
|
||||
}
|
||||
@ -8,7 +8,7 @@ import { LatLng } from 'leaflet';
|
||||
import { PopupStartEndInput, StartEndView, TextView } from '../Map';
|
||||
import useWindowDimensions from '../Map/hooks/useWindowDimension';
|
||||
import { useAddTag, useTags } from '../Map/hooks/useTags';
|
||||
import { useResetFilterTags } from '../Map/hooks/useFilter';
|
||||
import { useFilterTags, useResetFilterTags } from '../Map/hooks/useFilter';
|
||||
import { useHasUserPermission } from '../Map/hooks/usePermissions';
|
||||
import { TextAreaInput, TextInput } from '../Input';
|
||||
import { hashTagRegex } from '../../Utils/HashTagRegex';
|
||||
@ -19,44 +19,38 @@ import { useLayers } from '../Map/hooks/useLayers';
|
||||
import { ActionButton } from './ActionsButton';
|
||||
import { LinkedItemsHeaderView } from './LinkedItemsHeaderView';
|
||||
import { HeaderView } from '../Map/Subcomponents/ItemPopupComponents/HeaderView';
|
||||
import { useSelectPosition } from '../Map/hooks/useSetItemPosition';
|
||||
import { useSelectPosition, useSetSelectPosition } from '../Map/hooks/useSelectPosition';
|
||||
import { useClusterRef } from '../Map/hooks/useClusterRef';
|
||||
import { useLeafletRefs } from '../Map/hooks/useLeafletRefs';
|
||||
|
||||
export function OverlayItemProfile() {
|
||||
|
||||
const [updatePermission, setUpdatePermission] = useState<boolean>(false);
|
||||
const [relations, setRelations] = useState<Array<Item>>([]);
|
||||
const [activeTab, setActiveTab] = useState<number>(1);
|
||||
const [addItemPopupType, setAddItemPopupType] = useState<string>("");
|
||||
const [loading, setLoading] = useState<boolean>(false);
|
||||
|
||||
const location = useLocation();
|
||||
const items = useItems();
|
||||
const updateItem = useUpdateItem();
|
||||
const [item, setItem] = useState<Item>({} as Item)
|
||||
const map = useMap();
|
||||
const windowDimension = useWindowDimensions();
|
||||
|
||||
const [updatePermission, setUpdatePermission] = useState<boolean>(false);
|
||||
|
||||
const layers = useLayers();
|
||||
const selectPosition = useSelectPosition();
|
||||
|
||||
const removeItem = useRemoveItem();
|
||||
|
||||
const tags = useTags();
|
||||
|
||||
const navigate = useNavigate();
|
||||
|
||||
const [relations, setRelations] = useState<Array<Item>>([]);
|
||||
|
||||
const [activeTab, setActiveTab] = useState<number>(1);
|
||||
|
||||
const [addItemPopupType, setAddItemPopupType] = useState<string>("");
|
||||
|
||||
const [loading, setLoading] = useState<boolean>(false);
|
||||
|
||||
const addTag = useAddTag();
|
||||
const resetFilterTags = useResetFilterTags();
|
||||
|
||||
const filterTags = useFilterTags();
|
||||
const addItem = useAddItem();
|
||||
const { user } = useAuth();
|
||||
|
||||
const hasUserPermission = useHasUserPermission();
|
||||
const setSelectPosition = useSetSelectPosition();
|
||||
const clusterRef = useClusterRef();
|
||||
const leafletRefs = useLeafletRefs();
|
||||
|
||||
const tabRef = useRef<HTMLFormElement>(null);
|
||||
|
||||
@ -68,11 +62,6 @@ export function OverlayItemProfile() {
|
||||
scroll();
|
||||
}, [addItemPopupType])
|
||||
|
||||
useEffect(() => {
|
||||
console.log(addItemPopupType);
|
||||
|
||||
}, [addItemPopupType])
|
||||
|
||||
|
||||
const updateActiveTab = (id: number) => {
|
||||
setActiveTab(id);
|
||||
@ -85,18 +74,31 @@ export function OverlayItemProfile() {
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
|
||||
const itemId = location.pathname.split("/")[2];
|
||||
const item = items.find(i => i.id === itemId);
|
||||
item && setItem(item);
|
||||
const bounds = map.getBounds();
|
||||
const x = bounds.getEast() - bounds.getWest()
|
||||
if (windowDimension.width > 768)
|
||||
if (item?.position && item?.position.coordinates[0])
|
||||
map.setView(new LatLng(item?.position.coordinates[1]!, item?.position.coordinates[0]! + x / 4))
|
||||
resetFilterTags();
|
||||
if (item && filterTags.length == 0) {
|
||||
if(item.position) {
|
||||
const marker = Object.entries(leafletRefs).find(r => r[1].item == item)?.[1].marker;
|
||||
marker && clusterRef?.zoomToShowLayer(marker, () => {
|
||||
const bounds = map.getBounds();
|
||||
const x = bounds.getEast() - bounds.getWest()
|
||||
map.setView(new LatLng(item?.position?.coordinates[1]!, item?.position?.coordinates[0]! + x / 4), undefined, {duration: 1})}
|
||||
);
|
||||
}
|
||||
else {
|
||||
const parent = items.find(i => i.id == item.parent);
|
||||
const marker = Object.entries(leafletRefs).find(r => r[1].item == parent)?.[1].marker;
|
||||
marker && clusterRef?.zoomToShowLayer(marker, () => {
|
||||
const bounds = map.getBounds();
|
||||
const x = bounds.getEast() - bounds.getWest()
|
||||
map.setView(new LatLng(parent?.position?.coordinates[1]!, parent?.position?.coordinates[0]! + x / 4), undefined, {duration: 1})}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}, [location, items, activeTab])
|
||||
}, [items, activeTab, leafletRefs])
|
||||
|
||||
|
||||
useEffect(() => {
|
||||
@ -121,8 +123,6 @@ export function OverlayItemProfile() {
|
||||
}, [item])
|
||||
|
||||
|
||||
const [selecting, setSelecting] = useState<boolean>(false);
|
||||
|
||||
useEffect(() => {
|
||||
selectPosition && map.closePopup();
|
||||
}, [selectPosition])
|
||||
@ -233,17 +233,13 @@ export function OverlayItemProfile() {
|
||||
|
||||
return (
|
||||
<>
|
||||
{item &&
|
||||
{item &&
|
||||
<MapOverlayPage className={`tw-mx-4 tw-mt-4 tw-max-h-[calc(100dvh-96px)] tw-h-[calc(100dvh-96px)] md:tw-w-[calc(50%-32px)] tw-w-[calc(100%-32px)] tw-min-w-80 tw-max-w-3xl !tw-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'}`}>
|
||||
|
||||
<>
|
||||
<HeaderView api={item.layer?.api} item={item} deleteCallback={handleDelete} editCallback={() => navigate("/edit-item/" + item.id)} big updatePosition/>
|
||||
|
||||
|
||||
|
||||
<HeaderView api={item.layer?.api} item={item} deleteCallback={handleDelete} editCallback={() => navigate("/edit-item/" + item.id)} setPositionCallback={()=>{map.closePopup();setSelectPosition(item); navigate("/")}} big />
|
||||
|
||||
<div className='tw-h-full'>
|
||||
|
||||
<div role="tablist" className="tw-tabs tw-tabs-lifted tw-mt-2 tw-mb-2">
|
||||
<input type="radio" name="my_tabs_2" role="tab" className={`tw-tab [--tab-border-color:var(--fallback-bc,oklch(var(--bc)/0.2))]`} aria-label="Info" checked={activeTab == 1 && true} onChange={() => updateActiveTab(1)} />
|
||||
<div role="tabpanel" className="tw-tab-content tw-bg-base-100 tw-rounded-box tw-h-[calc(100dvh-280px)] tw-overflow-y-auto fade tw-pt-2 tw-pb-4 tw-mb-4 tw-overflow-x-hidden">
|
||||
|
||||
@ -21,6 +21,7 @@ export interface LayerProps {
|
||||
markerShape: string,
|
||||
markerDefaultColor: string,
|
||||
api?: ItemsApi<any>,
|
||||
itemType: string,
|
||||
itemNameField?: string,
|
||||
itemTextField?: string,
|
||||
itemAvatarField?: string,
|
||||
@ -35,7 +36,7 @@ export interface LayerProps {
|
||||
customEditLink?: string,
|
||||
setItemFormPopup?: React.Dispatch<React.SetStateAction<ItemFormPopupProps | null>>,
|
||||
itemFormPopup?: ItemFormPopupProps | null,
|
||||
clusterRef?: React.MutableRefObject<any>
|
||||
clusterRef?: any
|
||||
}
|
||||
|
||||
export class Item {
|
||||
@ -51,6 +52,7 @@ export class Item {
|
||||
tags?: string[];
|
||||
layer?: LayerProps;
|
||||
relations?: Relation[];
|
||||
parent?:string;
|
||||
[key: string]: any;
|
||||
constructor(id:string,name:string,text:string,position:Geometry, layer?: LayerProps, api?: ItemsApi<any>){
|
||||
this.id = id;
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user