diff --git a/app/src/pages/MapContainer.tsx b/app/src/pages/MapContainer.tsx index a49be37f..b30c1cb7 100644 --- a/app/src/pages/MapContainer.tsx +++ b/app/src/pages/MapContainer.tsx @@ -110,6 +110,7 @@ function MapContainer({ layers, map }: { layers: LayerProps[]; map: any }) { public_edit_items={layer.public_edit_items} listed={layer.listed} api={apis.find((api) => api.id === layer.id)?.api} + item_default_name={layer.item_default_name} > {layer.itemType.show_start_end && } diff --git a/backend/directus-config/development/snapshot/fields/layers/item_default_name.json b/backend/directus-config/development/snapshot/fields/layers/item_default_name.json new file mode 100644 index 00000000..011e4bfb --- /dev/null +++ b/backend/directus-config/development/snapshot/fields/layers/item_default_name.json @@ -0,0 +1,43 @@ +{ + "collection": "layers", + "field": "item_default_name", + "type": "string", + "meta": { + "collection": "layers", + "conditions": null, + "display": null, + "display_options": null, + "field": "item_default_name", + "group": null, + "hidden": false, + "interface": "input", + "note": null, + "options": null, + "readonly": false, + "required": false, + "sort": 16, + "special": null, + "translations": null, + "validation": null, + "validation_message": null, + "width": "half" + }, + "schema": { + "name": "item_default_name", + "table": "layers", + "data_type": "character varying", + "default_value": "item", + "max_length": 255, + "numeric_precision": null, + "numeric_scale": null, + "is_nullable": true, + "is_unique": false, + "is_indexed": false, + "is_primary_key": false, + "is_generated": false, + "generation_expression": null, + "has_auto_increment": false, + "foreign_key_table": null, + "foreign_key_column": null + } +} diff --git a/backend/directus-config/development/snapshot/fields/layers/item_presets.json b/backend/directus-config/development/snapshot/fields/layers/item_presets.json index b365ccb1..8d1c2617 100644 --- a/backend/directus-config/development/snapshot/fields/layers/item_presets.json +++ b/backend/directus-config/development/snapshot/fields/layers/item_presets.json @@ -18,7 +18,7 @@ }, "readonly": false, "required": false, - "sort": 16, + "sort": 17, "special": [ "cast-json" ], diff --git a/backend/directus-config/development/snapshot/fields/layers/notifications.json b/backend/directus-config/development/snapshot/fields/layers/notifications.json index 7d174468..b02104d2 100644 --- a/backend/directus-config/development/snapshot/fields/layers/notifications.json +++ b/backend/directus-config/development/snapshot/fields/layers/notifications.json @@ -15,7 +15,7 @@ "options": {}, "readonly": false, "required": false, - "sort": 17, + "sort": 18, "special": [ "m2m" ], diff --git a/lib/src/Components/Item/PopupView.tsx b/lib/src/Components/Item/PopupView.tsx index 6eea6e43..7c096262 100644 --- a/lib/src/Components/Item/PopupView.tsx +++ b/lib/src/Components/Item/PopupView.tsx @@ -173,7 +173,7 @@ export const PopupView = ({ children }: { children?: React.ReactNode }) => { - {item.name} + {item.name || item.layer?.item_default_name} diff --git a/lib/src/Components/Map/Layer.tsx b/lib/src/Components/Map/Layer.tsx index 7239a7b3..48921443 100644 --- a/lib/src/Components/Map/Layer.tsx +++ b/lib/src/Components/Map/Layer.tsx @@ -35,6 +35,8 @@ export const Layer = ({ // eslint-disable-next-line camelcase public_edit_items, listed = true, + // eslint-disable-next-line camelcase + item_default_name = 'item', }: LayerProps) => { const setItemsApi = useSetItemsApi() const setItemsData = useSetItemsData() @@ -65,6 +67,8 @@ export const Layer = ({ // eslint-disable-next-line camelcase public_edit_items, listed, + // eslint-disable-next-line camelcase + item_default_name, }) api && setItemsApi({ @@ -86,6 +90,8 @@ export const Layer = ({ // eslint-disable-next-line camelcase public_edit_items, listed, + // eslint-disable-next-line camelcase + item_default_name, }) // eslint-disable-next-line react-hooks/exhaustive-deps }, [data, api]) diff --git a/lib/src/Components/Map/Subcomponents/ItemPopupComponents/HeaderView.tsx b/lib/src/Components/Map/Subcomponents/ItemPopupComponents/HeaderView.tsx index d31f3cd3..4d340157 100644 --- a/lib/src/Components/Map/Subcomponents/ItemPopupComponents/HeaderView.tsx +++ b/lib/src/Components/Map/Subcomponents/ItemPopupComponents/HeaderView.tsx @@ -60,7 +60,7 @@ export function HeaderView({ const avatar = (item?.image && appState.assetsApi.url + item.image + '?width=160&heigth=160') || item?.image_external - const title = item?.name + const title = item?.name ?? item?.layer?.item_default_name const subtitle = item?.subname const [address] = useState('') diff --git a/lib/src/Components/Map/Subcomponents/ItemPopupComponents/TextView.tsx b/lib/src/Components/Map/Subcomponents/ItemPopupComponents/TextView.tsx index 93fce564..03076bed 100644 --- a/lib/src/Components/Map/Subcomponents/ItemPopupComponents/TextView.tsx +++ b/lib/src/Components/Map/Subcomponents/ItemPopupComponents/TextView.tsx @@ -28,7 +28,7 @@ export const TextView = ({ }: { item?: Item itemId?: string - text?: string + text?: string | null truncate?: boolean rawText?: string }) => { @@ -44,7 +44,14 @@ export const TextView = ({ if (rawText) { innerText = replacedText = rawText - } else if (text) { + } else if (text === undefined) { + // Field was omitted by backend (no permission) + innerText = replacedText = `Login to see this ${item?.layer?.item_default_name ?? 'item'}` + } else if (text === null || text === '') { + // Field is not set or empty - show nothing + innerText = '' + } else { + // Field has a value innerText = text } diff --git a/lib/src/Components/Map/hooks/useItems.tsx b/lib/src/Components/Map/hooks/useItems.tsx index 0fb1af78..b15fdceb 100644 --- a/lib/src/Components/Map/hooks/useItems.tsx +++ b/lib/src/Components/Map/hooks/useItems.tsx @@ -5,9 +5,19 @@ /* eslint-disable @typescript-eslint/restrict-template-expressions */ /* eslint-disable @typescript-eslint/no-misused-promises */ -import { useCallback, useReducer, createContext, useContext, useState } from 'react' +import { + useCallback, + useReducer, + createContext, + useContext, + useState, + useEffect, + useRef, +} from 'react' import { toast } from 'react-toastify' +import { useAuth } from '#components/Auth/useAuth' + import { useAddLayer } from './useLayers' import type { Item } from '#types/Item' @@ -18,6 +28,7 @@ type ActionType = | { type: 'UPDATE'; item: Item } | { type: 'REMOVE'; item: Item } | { type: 'RESET'; layer: LayerProps } + | { type: 'CLEAR_ALL' } type UseItemManagerResult = ReturnType @@ -43,8 +54,10 @@ function useItemsManager(initialItems: Item[]): { allItemsLoaded: boolean } { const addLayer = useAddLayer() + const { user } = useAuth() const [allItemsLoaded, setallItemsLoaded] = useState(false) + const layersRef = useRef([]) const [items, dispatch] = useReducer((state: Item[], action: ActionType) => { switch (action.type) { @@ -65,6 +78,8 @@ function useItemsManager(initialItems: Item[]): { return state.filter((item) => item !== action.item) case 'RESET': return state.filter((item) => item.layer?.name !== action.layer.name) + case 'CLEAR_ALL': + return [] default: throw new Error() } @@ -72,6 +87,7 @@ function useItemsManager(initialItems: Item[]): { const setItemsApi = useCallback(async (layer: LayerProps) => { addLayer(layer) + layersRef.current.push(layer) const result = await toast.promise(layer.api!.getItems(), { pending: `loading ${layer.name} ...`, success: `${layer.name} loaded`, @@ -127,6 +143,38 @@ function useItemsManager(initialItems: Item[]): { }) }, []) + const reloadAllItems = useCallback(async () => { + dispatch({ type: 'CLEAR_ALL' }) + setallItemsLoaded(false) + + for (const layer of layersRef.current) { + if (layer.api) { + const result = await toast.promise(layer.api.getItems(), { + pending: `loading ${layer.name} ...`, + success: `${layer.name} loaded`, + error: { + render({ data }) { + return `${data}` + }, + }, + }) + result.map((item) => { + dispatch({ type: 'ADD', item: { ...item, layer } }) + return null + }) + } + } + + setallItemsLoaded(true) + }, []) + + useEffect(() => { + if (layersRef.current.length > 0) { + void reloadAllItems() + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [user?.id]) + return { items, updateItem, diff --git a/lib/src/Components/Profile/Subcomponents/GalleryView.tsx b/lib/src/Components/Profile/Subcomponents/GalleryView.tsx index 18c0d4a3..4d8b7094 100644 --- a/lib/src/Components/Profile/Subcomponents/GalleryView.tsx +++ b/lib/src/Components/Profile/Subcomponents/GalleryView.tsx @@ -24,6 +24,7 @@ export const GalleryView = ({ item }: { item: Item }) => { const appState = useAppState() const images = item.gallery?.flatMap((g, index) => { + if (!g.directus_files_id) return [] const file = g.directus_files_id if (typeof file === 'string') return [] const { id, type, width, height } = file diff --git a/lib/src/Components/Profile/Subcomponents/ProfileTextView.tsx b/lib/src/Components/Profile/Subcomponents/ProfileTextView.tsx index 2adbb3e1..ea62c6d7 100644 --- a/lib/src/Components/Profile/Subcomponents/ProfileTextView.tsx +++ b/lib/src/Components/Profile/Subcomponents/ProfileTextView.tsx @@ -17,15 +17,14 @@ export const ProfileTextView = ({ }) => { const text = get(item, dataField) - const parsedText = typeof text !== 'string' ? '' : text + // undefined = no permission, null = not set, string = value exists + const shouldShowHeading = !(hideWhenEmpty && (text === '' || text === null)) return (
- {!(text === '' && hideWhenEmpty) && ( -

{heading}

- )} + {shouldShowHeading &&

{heading}

}
- +
) diff --git a/lib/src/types/LayerProps.d.ts b/lib/src/types/LayerProps.d.ts index 40aa6196..8fd0cf6e 100644 --- a/lib/src/types/LayerProps.d.ts +++ b/lib/src/types/LayerProps.d.ts @@ -25,4 +25,5 @@ export interface LayerProps { public_edit_items?: boolean listed?: boolean item_presets?: Record + item_default_name?: string }