update item position, item parents, focus item on profile load an open cluster

This commit is contained in:
Anton Tranelis 2024-03-25 01:06:47 +01:00
parent c77972a3be
commit 8484379113
13 changed files with 262 additions and 141 deletions

View File

@ -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>

View File

@ -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) =>

View File

@ -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)

View File

@ -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();
}

View File

@ -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>
:

View File

@ -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>
)
}

View File

@ -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 ?

View File

@ -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='&copy; <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) ?

View 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;
}

View 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;
}

View File

@ -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;
}

View File

@ -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">

View File

@ -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;