diff --git a/src/Components/Map/Subcomponents/AddButton.tsx b/src/Components/Map/Subcomponents/AddButton.tsx index 30de95fb..daad12c9 100644 --- a/src/Components/Map/Subcomponents/AddButton.tsx +++ b/src/Components/Map/Subcomponents/AddButton.tsx @@ -3,7 +3,7 @@ import { useLayers } from '../hooks/useLayers' import { useHasUserPermission } from '../hooks/usePermissions'; -export default function AddButton({ setSelectNewItemPosition }: { setSelectNewItemPosition: React.Dispatch> }) { +export default function AddButton({ triggerAction }: { triggerAction: React.Dispatch> }) { const layers = useLayers(); const hasUserPermission = useHasUserPermission(); @@ -35,7 +35,7 @@ export default function AddButton({ setSelectNewItemPosition }: { setSelectNewIt diff --git a/src/Components/Map/Subcomponents/ItemPopupComponents/HeaderView.tsx b/src/Components/Map/Subcomponents/ItemPopupComponents/HeaderView.tsx index cd5998ee..d6b5b370 100644 --- a/src/Components/Map/Subcomponents/ItemPopupComponents/HeaderView.tsx +++ b/src/Components/Map/Subcomponents/ItemPopupComponents/HeaderView.tsx @@ -15,9 +15,10 @@ import { useNavigate } from "react-router-dom"; -export function HeaderView({ item, setItemFormPopup }: { +export function HeaderView({ item, setItemFormPopup, hideMenu=false }: { item: Item, - setItemFormPopup?: React.Dispatch> + setItemFormPopup?: React.Dispatch>, + hideMenu?: boolean }) { @@ -68,7 +69,7 @@ export function HeaderView({ item, setItemFormPopup }: { const openEditPopup = (event: React.MouseEvent) => { event.stopPropagation(); map.closePopup(); - if (setItemFormPopup) + if (setItemFormPopup && item.position) setItemFormPopup({ position: new LatLng(item.position.coordinates[1], item.position.coordinates[0]), layer: item.layer!, item: item, setItemFormPopup: setItemFormPopup }) } @@ -79,7 +80,7 @@ export function HeaderView({ item, setItemFormPopup }: {
{ avatar ? -
+
: @@ -92,7 +93,8 @@ export function HeaderView({ item, setItemFormPopup }: {
{(item.layer?.api?.deleteItem || item.layer?.api?.updateItem) && ((user && owner?.id === user.id) || owner == undefined) - && (hasUserPermission(item.layer.api?.collectionName!, "delete") || hasUserPermission(item.layer.api?.collectionName!, "update")) && + && (hasUserPermission(item.layer.api?.collectionName!, "delete") || hasUserPermission(item.layer.api?.collectionName!, "update")) + && !hideMenu &&
(setModalOpen(false)) }> - Do you want to delte {item.name}? + Do you want to delete {item.name}?
diff --git a/src/Components/Map/Subcomponents/ItemPopupComponents/TextView.tsx b/src/Components/Map/Subcomponents/ItemPopupComponents/TextView.tsx index 3d8ddbec..e31cb043 100644 --- a/src/Components/Map/Subcomponents/ItemPopupComponents/TextView.tsx +++ b/src/Components/Map/Subcomponents/ItemPopupComponents/TextView.tsx @@ -11,10 +11,7 @@ import { decodeTag } from '../../../../Utils/FormatTags'; export const TextView = ({ item, truncate = false, itemTextField}: { item?: Item, truncate?: boolean,itemTextField?: string }) => { const tags = useTags(); - const addFilterTag = useAddFilterTag(); - - console.log(item); - + const addFilterTag = useAddFilterTag(); let text = ""; diff --git a/src/Components/Map/UtopiaMap.tsx b/src/Components/Map/UtopiaMap.tsx index 2b4bdd7b..ba63ef8e 100644 --- a/src/Components/Map/UtopiaMap.tsx +++ b/src/Components/Map/UtopiaMap.tsx @@ -89,7 +89,7 @@ function UtopiaMap({
- + @@ -100,6 +100,7 @@ function UtopiaMap({ @@ -112,7 +113,7 @@ function UtopiaMap({ - + {selectNewItemPosition != null &&
diff --git a/src/Components/Profile/AvatarWidget.tsx b/src/Components/Profile/AvatarWidget.tsx new file mode 100644 index 00000000..00af6f91 --- /dev/null +++ b/src/Components/Profile/AvatarWidget.tsx @@ -0,0 +1,161 @@ +import * as React from "react"; +import { useState } from "react"; +import ReactCrop, { Crop, centerCrop, makeAspectCrop } from 'react-image-crop'; +import { useAssetApi } from '../AppShell/hooks/useAssets'; +import DialogModal from "../Templates/DialogModal"; + + +export const AvatarWidget = ({avatar, setAvatar}:{avatar:string, setAvatar : React.Dispatch>}) => { + + + const [crop, setCrop] = useState(); + const [image, setImage] = useState(""); + const [cropModalOpen, setCropModalOpen] = useState(false); + const [cropping, setCropping] = useState(false); + + const assetsApi = useAssetApi(); + + + + const imgRef = React.useRef(null) + + const onImageChange = (event) => { + if (event.target.files && event.target.files[0]) { + setImage(URL.createObjectURL(event.target.files[0])); + } + setCropModalOpen(true); + } + + function onImageLoad(e: React.SyntheticEvent) { + const { width, height } = e.currentTarget + + setCrop(centerAspectCrop(width, height, 1)) + } + + + // This is to demonstate how to make and center a % aspect crop + // which is a bit trickier so we use some helper functions. + function centerAspectCrop( + mediaWidth: number, + mediaHeight: number, + aspect: number, + ) { + return centerCrop( + makeAspectCrop( + { + unit: 'px', + width: mediaWidth / 2, + }, + aspect, + mediaWidth, + mediaHeight, + ), + mediaWidth, + mediaHeight, + ) + } + + async function renderCrop() { + // get the image element + const image = imgRef.current; + if (crop && image) { + + const scaleX = image.naturalWidth / image.width + const scaleY = image.naturalHeight / image.height + + // create a canvas element to draw the cropped image + const canvas = new OffscreenCanvas( + crop.width * scaleX, + crop.height * scaleY, + ) + const ctx = canvas.getContext("2d"); + const pixelRatio = window.devicePixelRatio; + canvas.width = crop.width * pixelRatio * scaleX; + canvas.height = crop.height * pixelRatio * scaleY; + + if (ctx) { + ctx.setTransform(pixelRatio, 0, 0, pixelRatio, 0, 0); + + ctx.drawImage( + image, + crop.x * scaleX, + crop.y * scaleY, + crop.width * scaleX, + crop.height * scaleY, + 0, + 0, + crop.width * scaleX, + crop.height * scaleY + ); + } + const blob = await canvas.convertToBlob(); + await resizeBlob(blob); + setCropping(false); + setImage(""); + } + } + + async function resizeBlob(blob) { + var img = new Image(); + img.src = URL.createObjectURL(blob); + await img.decode(); + const canvas = new OffscreenCanvas( + 400, + 400 + ) + var ctx = canvas.getContext("2d"); + ctx?.drawImage(img, 0, 0, 400, 400); + const resizedBlob = await canvas.convertToBlob() + const asset = await assetsApi.upload(resizedBlob, "test"); + setAvatar(asset.id) + } + + + return ( + <> + {!cropping ? + + + :
+ +
+ + } + { + setCropModalOpen(false); + setImage(""); + }}> + setCrop(c)} aspect={1} > + + + + + + ) +} diff --git a/src/Components/Profile/ColorPicker.tsx b/src/Components/Profile/ColorPicker.tsx index 45c86a24..a35c07ee 100644 --- a/src/Components/Profile/ColorPicker.tsx +++ b/src/Components/Profile/ColorPicker.tsx @@ -39,10 +39,11 @@ export const ColorPicker = ({ color, onChange, className }) => { /> {isOpen && ( -
- +
+ toggle(false)}/>
)}
); }; + diff --git a/src/Components/Profile/OverlayItemProfile.tsx b/src/Components/Profile/OverlayItemProfile.tsx new file mode 100644 index 00000000..1744246a --- /dev/null +++ b/src/Components/Profile/OverlayItemProfile.tsx @@ -0,0 +1,266 @@ +import * as React from 'react' +import { MapOverlayPage, TitleCard } from '../Templates' +import { useAddItem, useItems, useRemoveItem, useUpdateItem } from '../Map/hooks/useItems' +import { useLocation, useNavigate } from 'react-router-dom' +import { useEffect, useRef, useState } from 'react'; +import { Item, Tag, UserItem } from '../../types'; +import { getValue } from '../../Utils/GetValue'; +import { useMap } from 'react-leaflet'; +import { LatLng } from 'leaflet'; +import { PopupStartEndInput, StartEndView, TextView } from '../Map'; +import useWindowDimensions from '../Map/hooks/useWindowDimension'; +import { useAddTag, useTags } from '../Map/hooks/useTags'; +import { useAddFilterTag, useResetFilterTags } from '../Map/hooks/useFilter'; +import { HeaderView } from '../Map/Subcomponents/ItemPopupComponents/HeaderView'; +import { useHasUserPermission } from '../Map/hooks/usePermissions'; +import PlusButton from './PlusButton'; +import { TextAreaInput, TextInput } from '../Input'; +import { hashTagRegex } from '../../Utils/HashTagRegex'; +import { randomColor } from '../../Utils/RandomColor'; +import { toast } from 'react-toastify'; +import { useAuth } from '../Auth'; +import { useLayers } from '../Map/hooks/useLayers'; + +export function OverlayItemProfile() { + + const location = useLocation(); + const items = useItems(); + const updateItem = useUpdateItem(); + const [item, setItem] = useState({} as Item) + const map = useMap(); + const windowDimension = useWindowDimensions(); + + const layers = useLayers(); + + + const tags = useTags(); + + const navigate = useNavigate(); + + const [owner, setOwner] = useState(); + const [offers, setOffers] = useState>([]); + const [needs, setNeeds] = useState>([]); + const [relations, setRelations] = useState>([]); + + const [activeTab, setActiveTab] = useState(1); + + const [addItemPopupType, setAddItemPopupType] = useState(""); + + const [loading, setLoading] = useState(false); + + const addTag = useAddTag(); + const resetFilterTags = useResetFilterTags(); + + const addItem = useAddItem(); + const { user } = useAuth(); + + const hasUserPermission = useHasUserPermission(); + + const tabRef = useRef(null); + + function scroll() { + tabRef.current?.scrollTo(0, 800); + } + + 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)) + }, [location, items, activeTab]) + + useEffect(() => { + setActiveTab(1); + }, [location]) + + useEffect(() => { + setOffers([]); + setNeeds([]); + setRelations([]); + setOwner(undefined); + item?.layer?.itemOwnerField && setOwner(getValue(item, item.layer?.itemOwnerField)); + item.layer?.itemOffersField && getValue(item, item.layer.itemOffersField).map(o => { + const tag = tags.find(t => t.id === o.tags_id); + tag && setOffers(current => [...current, tag]) + }) + item.layer?.itemNeedsField && getValue(item, item.layer.itemNeedsField).map(n => { + const tag = tags.find(t => t.id === n.tags_id); + tag && setNeeds(current => [...current, tag]) + }) + item.relations?.map(r => { + const item = items.find(i => i.id == r.related_items_id) + item && setRelations(current => [...current, item]) + }) + }, [item]) + + const submitNewItem = async (evt: any, type: string) => { + evt.preventDefault(); + const formItem: Item = {} as Item; + Array.from(evt.target).forEach((input: HTMLInputElement) => { + if (input.name) { + formItem[input.name] = input.value; + } + }); + setLoading(true); + formItem.text && formItem.text.toLocaleLowerCase().match(hashTagRegex)?.map(tag => { + if (!tags.find((t) => t.name.toLocaleLowerCase() === tag.slice(1).toLocaleLowerCase())) { + addTag({ id: crypto.randomUUID(), name: tag.slice(1), color: randomColor() }) + } + }); + const uuid = crypto.randomUUID(); + let success = false; + try { + await item?.layer?.api?.createItem!({ ...formItem, id: uuid, type: type }); + await linkItem(uuid); + success = true; + } catch (error) { + toast.error(error.toString()); + } + if (success) { + addItem({ ...formItem, id: uuid, type: type, layer: item?.layer, user_created: user }); + toast.success("New item created"); + resetFilterTags(); + } + setLoading(false); + setAddItemPopupType(""); + } + + const linkItem = async (id: string) => { + let new_relations = item.relations; + new_relations?.push({ items_id: item.id, related_items_id: id }) + + const updatedItem = { id: item.id, relations: new_relations } + + await item?.layer?.api?.updateItem!(updatedItem) + + updateItem({ ...item, relations: new_relations }) + } + + return ( + + {item && + <> +
+
+

{item.layer?.itemAvatarField && getValue(item, item.layer.itemAvatarField) && } {item.layer?.itemNameField && getValue(item, item.layer.itemNameField)}

+
+ {(item.layer?.api?.updateItem && hasUserPermission(item.layer.api?.collectionName!, "update")) ? + navigate("/edit-item/" + item.id)}> + + + + : "" + } +
+ + + + +
+ +
+ setActiveTab(1)} /> +
+ +
+ + setActiveTab(2)} /> +
+
+
+ {relations && relations.map(i => { + if (i.type == 'project') return ( + +
navigate('/item/' + i.id)}> + +
+ +
+
+ + ) + else return null + })} + {addItemPopupType == "project" ? +
submitNewItem(e, addItemPopupType)} > + +
+ + + +
+ +
+
+
: <> + } + { setAddItemPopupType("project"); scroll() }} color={item.color}> + +
+
+
+ + setActiveTab(3)} /> +
+
+
+ {relations && relations.map(i => { + if (i.type == 'event') return ( + +
navigate('/item/' + i.id)}> + +
+ + +
+
+ + ) + else return null + })} + {addItemPopupType == "event" ? +
submitNewItem(e, addItemPopupType)} > + +
+ + + + +
+ +
+
+
: <> + } + { setAddItemPopupType("event"); scroll() }} color={item.color}> + +
+
+
+ setActiveTab(4)} /> +
+
+
+ + + + +
+ + } +
+ ) +} + + diff --git a/src/Components/Profile/OverlayItemProfileSettings.tsx b/src/Components/Profile/OverlayItemProfileSettings.tsx new file mode 100644 index 00000000..a51fdb86 --- /dev/null +++ b/src/Components/Profile/OverlayItemProfileSettings.tsx @@ -0,0 +1,145 @@ +import * as React from 'react' +import { useItems, useUpdateItem } from '../Map/hooks/useItems' +import { useEffect, useState } from 'react'; +import { getValue } from '../../Utils/GetValue'; +import { toast } from 'react-toastify'; +import { useAuth } from '../Auth'; +import { TextInput, TextAreaInput } from '../Input'; +import { ColorPicker } from './ColorPicker'; +import DialogModal from '../Templates/DialogModal'; +import { hashTagRegex } from '../../Utils/HashTagRegex'; +import { useAddTag, useTags } from '../Map/hooks/useTags'; +import { randomColor } from '../../Utils/RandomColor'; +import { useLocation, useNavigate } from 'react-router-dom'; +import { Geometry, Item, Tag, UserItem } from '../../types'; +import { MapOverlayPage } from '../Templates'; +import { TagsWidget } from './TagsWidget'; +import { decodeTag, encodeTag } from '../../Utils/FormatTags'; +import { AvatarWidget } from './AvatarWidget'; + + +export function OverlayItemProfileSettings() { + + const [id, setId] = useState(""); + const [name, setName] = useState(""); + const [text, setText] = useState(""); + const [image, setImage] = useState(""); + const [color, setColor] = useState(""); + + + const [activeTab, setActiveTab] = useState(1); + const [loading, setLoading] = useState(false); + + + + + const updateItem = useUpdateItem(); + + const location = useLocation(); + const items = useItems(); + const [item, setItem] = useState({} as Item) + + const tags = useTags(); + const addTag = useAddTag(); + const navigate = useNavigate(); + + + + useEffect(() => { + const itemId = location.pathname.split("/")[2]; + const item = items.find(i => i.id === itemId); + item && setItem(item); + + }, [location, items, activeTab]) + + React.useEffect(() => { + if (item.layer?.itemColorField) setColor(getValue(item, item.layer?.itemColorField)); + else setColor(item.layer?.markerDefaultColor || "#000") + + setId(item?.id ? item.id : ""); + setName(item?.name ? item.name : ""); + setText(item?.text ? item.text : ""); + setImage(item?.image ? item?.image : ""); + }, [item]) + + + const onUpdateItem = async () => { + let changedItem = {} as Item; + + console.log(item.position); + + + + changedItem = { id: id, name: name, text: text, color: color, position: item.position, ...image.length > 10 && { image: image }}; + // update profile item in current state + //const item = items.find(i => i.layer?.itemOwnerField && getValue(i, i.layer?.itemOwnerField).id === id); + + + // if (item && item.layer && item.layer.itemOwnerField) item[item.layer.itemOwnerField] = {... changedUser, offers: offer_state, needs: needs_state}; + // add new hashtags from profile text + text.toLocaleLowerCase().match(hashTagRegex)?.map(tag => { + if (!tags.find((t) => t.name.toLocaleLowerCase() === tag.slice(1).toLocaleLowerCase())) { + addTag({ id: crypto.randomUUID(), name: encodeTag(tag.slice(1).toLocaleLowerCase()), color: randomColor()}) + } + }); + + setLoading(true); + + item?.layer?.api?.updateItem && toast.promise( + item?.layer?.api?.updateItem(changedItem), + { + pending: 'updating Item ...', + success: 'Item updated', + error: { + render({ data }) { + return `${data}` + }, + }, + }) + .then(() => item && updateItem({...item, ...changedItem})) + .then(() => { + setLoading(false); + navigate("/item/"+item.id)}); + } + + + return ( + <> + +
+
+ + + setName(v)} containerStyle='tw-grow tw-ml-6 tw-my-auto ' /> +
+ + +
+ setActiveTab(1)} /> +
+ {console.log(v);setText(v)}} containerStyle='tw-h-full' inputStyle='tw-h-full tw-border-t-0 tw-rounded-tl-none' /> +
+ + setActiveTab(2)} /> +
+
+ + setActiveTab(3)} /> +
+
+ + setActiveTab(4)} /> +
+
+
+ +
+ +
+ +
+ + ) +} + + diff --git a/src/Components/Profile/OverlayProfile.tsx b/src/Components/Profile/OverlayProfile.tsx index 43703e98..22e2cb34 100644 --- a/src/Components/Profile/OverlayProfile.tsx +++ b/src/Components/Profile/OverlayProfile.tsx @@ -46,7 +46,7 @@ export function OverlayProfile() { const bounds = map.getBounds(); const x = bounds.getEast() - bounds.getWest() if (windowDimension.width > 768) - if (item?.position.coordinates[0]) + if (item?.position?.coordinates[0]) map.setView(new LatLng(item?.position.coordinates[1]!, item?.position.coordinates[0]! + x / 4)) }, [location, items, activeTab]) @@ -75,7 +75,7 @@ export function OverlayProfile() { <>
-

{item.layer?.itemAvatarField && getValue(item, item.layer.itemAvatarField) && } {item.layer?.itemNameField && getValue(item, item.layer.itemNameField)}

+

{item.layer?.itemAvatarField && getValue(item, item.layer.itemAvatarField) && } {item.layer?.itemNameField && getValue(item, item.layer.itemNameField)}

{owner?.id === user?.id && owner?.id ? navigate("/profile-settings")}> diff --git a/src/Components/Profile/OverlayProfileSettings.tsx b/src/Components/Profile/OverlayProfileSettings.tsx index 14f8d808..5ee529b6 100644 --- a/src/Components/Profile/OverlayProfileSettings.tsx +++ b/src/Components/Profile/OverlayProfileSettings.tsx @@ -2,13 +2,10 @@ import * as React from 'react' import { useItems, useUpdateItem } from '../Map/hooks/useItems' import { useState } from 'react'; import { getValue } from '../../Utils/GetValue'; -import ReactCrop, { Crop, centerCrop, makeAspectCrop } from 'react-image-crop'; import { toast } from 'react-toastify'; -import { useAssetApi } from '../AppShell/hooks/useAssets'; import { useAuth } from '../Auth'; import { TextInput, TextAreaInput } from '../Input'; import { ColorPicker } from './ColorPicker'; -import DialogModal from '../Templates/DialogModal'; import { hashTagRegex } from '../../Utils/HashTagRegex'; import { useAddTag, useTags } from '../Map/hooks/useTags'; import { randomColor } from '../../Utils/RandomColor'; @@ -17,11 +14,12 @@ import { Tag, UserItem } from '../../types'; import { MapOverlayPage } from '../Templates'; import { TagsWidget } from './TagsWidget'; import { decodeTag, encodeTag } from '../../Utils/FormatTags'; +import { AvatarWidget } from './AvatarWidget'; export function OverlayProfileSettings() { - const { user, updateUser, loading, token } = useAuth(); + const { user, updateUser, loading } = useAuth(); const [id, setId] = useState(""); const [name, setName] = useState(""); @@ -35,12 +33,7 @@ export function OverlayProfileSettings() { const [activeTab, setActiveTab] = useState(1); - const [crop, setCrop] = useState(); - const [image, setImage] = useState(""); - const [cropModalOpen, setCropModalOpen] = useState(false); - const [cropping, setCropping] = useState(false); - const assetsApi = useAssetApi(); const items = useItems(); const updateItem = useUpdateItem(); @@ -67,98 +60,6 @@ export function OverlayProfileSettings() { setContact(user?.contact ? user.contact : ""); }, [user]) - const imgRef = React.useRef(null) - - const onImageChange = (event) => { - if (event.target.files && event.target.files[0]) { - setImage(URL.createObjectURL(event.target.files[0])); - } - setCropModalOpen(true); - } - - function onImageLoad(e: React.SyntheticEvent) { - const { width, height } = e.currentTarget - - setCrop(centerAspectCrop(width, height, 1)) - } - - - // This is to demonstate how to make and center a % aspect crop - // which is a bit trickier so we use some helper functions. - function centerAspectCrop( - mediaWidth: number, - mediaHeight: number, - aspect: number, - ) { - return centerCrop( - makeAspectCrop( - { - unit: 'px', - width: mediaWidth / 2, - }, - aspect, - mediaWidth, - mediaHeight, - ), - mediaWidth, - mediaHeight, - ) - } - - async function renderCrop() { - // get the image element - const image = imgRef.current; - if (crop && image) { - - const scaleX = image.naturalWidth / image.width - const scaleY = image.naturalHeight / image.height - - // create a canvas element to draw the cropped image - const canvas = new OffscreenCanvas( - crop.width * scaleX, - crop.height * scaleY, - ) - const ctx = canvas.getContext("2d"); - const pixelRatio = window.devicePixelRatio; - canvas.width = crop.width * pixelRatio * scaleX; - canvas.height = crop.height * pixelRatio * scaleY; - - if (ctx) { - ctx.setTransform(pixelRatio, 0, 0, pixelRatio, 0, 0); - - ctx.drawImage( - image, - crop.x * scaleX, - crop.y * scaleY, - crop.width * scaleX, - crop.height * scaleY, - 0, - 0, - crop.width * scaleX, - crop.height * scaleY - ); - } - const blob = await canvas.convertToBlob(); - await resizeBlob(blob); - setCropping(false); - setImage(""); - } - } - - async function resizeBlob(blob) { - var img = new Image(); - img.src = URL.createObjectURL(blob); - await img.decode(); - const canvas = new OffscreenCanvas( - 400, - 400 - ) - var ctx = canvas.getContext("2d"); - ctx?.drawImage(img, 0, 0, 400, 400); - const resizedBlob = await canvas.convertToBlob() - const asset = await assetsApi.upload(resizedBlob, "test"); - setAvatar(asset.id) - } const onUpdateUser = async () => { let changedUser = {} as UserItem; @@ -228,33 +129,7 @@ export function OverlayProfileSettings() {
- {!cropping ? - - - :
- -
- - } + setName(v)} containerStyle='tw-grow tw-ml-6 tw-my-auto ' />
@@ -289,22 +164,6 @@ export function OverlayProfileSettings() {
- { - setCropModalOpen(false); - setImage(""); - }}> - setCrop(c)} aspect={1} > - - - - ) } diff --git a/src/Components/Profile/PlusButton.tsx b/src/Components/Profile/PlusButton.tsx new file mode 100644 index 00000000..fd44b1ab --- /dev/null +++ b/src/Components/Profile/PlusButton.tsx @@ -0,0 +1,23 @@ +import { useLayers } from "../Map/hooks/useLayers"; +import { useHasUserPermission } from "../Map/hooks/usePermissions"; + +export default function PlusButton({ triggerAction, color }: { triggerAction: any, color: string }) { + + const hasUserPermission = useHasUserPermission(); + + + + return ( + <>{hasUserPermission("items", "create") && +
+ +
+ } + + + ) +} diff --git a/src/Components/Profile/index.tsx b/src/Components/Profile/index.tsx index 4e23a23d..2a60779c 100644 --- a/src/Components/Profile/index.tsx +++ b/src/Components/Profile/index.tsx @@ -2,4 +2,6 @@ export {UserSettings} from './UserSettings' export {ProfileSettings} from './ProfileSettings' export {OverlayProfile} from './OverlayProfile' export {OverlayProfileSettings} from './OverlayProfileSettings' -export {OverlayUserSettings} from './OverlayUserSettings' \ No newline at end of file +export {OverlayUserSettings} from './OverlayUserSettings' +export {OverlayItemProfile} from './OverlayItemProfile' +export {OverlayItemProfileSettings} from './OverlayItemProfileSettings' \ No newline at end of file diff --git a/src/index.tsx b/src/index.tsx index 7ccec09d..f1feb529 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -1,7 +1,7 @@ export { UtopiaMap, Layer, Tags, Permissions, ItemForm, ItemView, PopupTextAreaInput, PopupStartEndInput, PopupTextInput, PopupButton, TextView, StartEndView } from './Components/Map'; export {AppShell, Content, SideBar} from "./Components/AppShell" export {AuthProvider, useAuth, LoginPage, SignupPage, RequestPasswordPage, SetNewPasswordPage} from "./Components/Auth" -export {UserSettings, ProfileSettings, OverlayProfile, OverlayProfileSettings, OverlayUserSettings} from './Components/Profile' +export {UserSettings, ProfileSettings, OverlayProfile, OverlayProfileSettings, OverlayUserSettings, OverlayItemProfile, OverlayItemProfileSettings} from './Components/Profile' export {Quests, Modal} from './Components/Gaming' export {TitleCard, CardPage, MapOverlayPage, CircleLayout, MoonCalendar, ItemsIndexPage, ItemViewPage} from './Components/Templates' export {TextInput, TextAreaInput, SelectBox} from './Components/Input' diff --git a/src/types.ts b/src/types.ts index 29863ce8..2a1e05c7 100644 --- a/src/types.ts +++ b/src/types.ts @@ -42,7 +42,7 @@ export class Item { id: string ; name: string; text: string; - position: Geometry; + position?: Geometry; date_created?: string; date_updated?: string | null; start?: string; @@ -50,6 +50,7 @@ export class Item { api?: ItemsApi; tags?: string[]; layer?: LayerProps; + relations?: Relation[]; [key: string]: any; constructor(id:string,name:string,text:string,position:Geometry, layer?: LayerProps, api?: ItemsApi){ this.id = id; @@ -123,10 +124,15 @@ export type Profile = { export type Permission = { id?: string; - role: string;S + role: string; collection: string; action: PermissionAction } export type PermissionAction = "create"|"read"|"update"|"delete"; + +export type Relation = { + related_items_id: string; + [key: string]: any; +} \ No newline at end of file