Merge branch 'main' into mapbox-vector-tiles

This commit is contained in:
Anton Tranelis 2025-10-09 15:56:45 +02:00 committed by GitHub
commit ff43dc2a4e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
18 changed files with 221 additions and 21 deletions

View File

@ -40,7 +40,7 @@ jobs:
sudo chmod 777 -R ./data
docker compose -f docker-compose.yml up -d
sleep 5
cd backend && ./seed.sh
cd backend && ./push.sh && ./seed.sh
working-directory: ${{env.WORKING_DIRECTORY}}
#build-development:

View File

@ -113,6 +113,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}
>
<PopupView>
{layer.itemType.show_start_end && <StartEndView></StartEndView>}

View File

@ -6,6 +6,7 @@ To run the backend you can simply execute
To fill in all required data execute the following commands in order:
```
cd backend
./push.sh
./seed.sh
```
@ -22,6 +23,8 @@ npx directus-sync pull \
--directus-password admin123
```
You can run `./pull.sh` to run this command and modify it via `export PROJECT=...` for a different project configuration.
## Push Data from Harddrive to Docker
To push local changes or to seed directus use the following command
@ -33,6 +36,8 @@ npx directus-sync push \
--directus-password admin123
```
You can run `./push.sh` to run this command and modify it via `export PROJECT=...` for a different project configuration.
## Seed Data for local development
In order to seed the development data, run the script `backend/seed.sh`.

View File

@ -0,0 +1,49 @@
{
"collection": "Themes",
"meta": {
"insert_order": 1,
"create": true,
"update": true,
"delete": true,
"preserve_ids": true,
"ignore_on_update": []
},
"data": [
{
"_sync_id": "theme-light",
"theme": "light"
},
{
"_sync_id": "theme-dark",
"theme": "dark"
},
{
"_sync_id": "theme-valentine",
"theme": "valentine"
},
{
"_sync_id": "theme-retro",
"theme": "retro"
},
{
"_sync_id": "theme-aqua",
"theme": "aqua"
},
{
"_sync_id": "theme-cyberpunk",
"theme": "cyberpunk"
},
{
"_sync_id": "theme-caramellatte",
"theme": "caramellatte"
},
{
"_sync_id": "theme-abyss",
"theme": "abyss"
},
{
"_sync_id": "theme-silk",
"theme": "silk"
}
]
}

View File

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

View File

@ -18,7 +18,7 @@
},
"readonly": false,
"required": false,
"sort": 16,
"sort": 17,
"special": [
"cast-json"
],

View File

@ -15,7 +15,7 @@
"options": {},
"readonly": false,
"required": false,
"sort": 17,
"sort": 18,
"special": [
"m2m"
],

24
backend/pull.sh Executable file
View File

@ -0,0 +1,24 @@
#!/bin/sh
# base setup
SCRIPT_PATH=$(realpath $0)
SCRIPT_DIR=$(dirname $SCRIPT_PATH)
DIRECTUS_URL="${DIRECTUS_URL:-http://localhost:8055}"
DIRECTUS_EMAIL="${DIRECTUS_EMAIL:-admin@it4c.dev}"
DIRECTUS_PASSWORD="${DIRECTUS_PASSWORD:-admin123}"
PGPASSWORD="${PGPASSWORD:-'directus'}"
PGUSER="${PGUSER:-'directus'}"
PGDATABASE="${PGDATABASE:-'directus'}"
PROJECT_NAME="${PROJECT:-development}"
PROJECT_FOLDER=$SCRIPT_DIR/directus-config/$PROJECT_NAME
echo "Pull collections"
npx directus-sync@3.4.0 pull \
--dump-path $PROJECT_FOLDER \
--directus-url $DIRECTUS_URL \
--directus-email $DIRECTUS_EMAIL \
--directus-password $DIRECTUS_PASSWORD \
|| exit 1

24
backend/push.sh Executable file
View File

@ -0,0 +1,24 @@
#!/bin/sh
# base setup
SCRIPT_PATH=$(realpath $0)
SCRIPT_DIR=$(dirname $SCRIPT_PATH)
DIRECTUS_URL="${DIRECTUS_URL:-http://localhost:8055}"
DIRECTUS_EMAIL="${DIRECTUS_EMAIL:-admin@it4c.dev}"
DIRECTUS_PASSWORD="${DIRECTUS_PASSWORD:-admin123}"
PGPASSWORD="${PGPASSWORD:-'directus'}"
PGUSER="${PGUSER:-'directus'}"
PGDATABASE="${PGDATABASE:-'directus'}"
PROJECT_NAME="${PROJECT:-development}"
PROJECT_FOLDER=$SCRIPT_DIR/directus-config/$PROJECT_NAME
echo "Push collections"
npx directus-sync@3.4.0 push \
--dump-path $PROJECT_FOLDER \
--directus-url $DIRECTUS_URL \
--directus-email $DIRECTUS_EMAIL \
--directus-password $DIRECTUS_PASSWORD \
|| exit 1

View File

@ -15,14 +15,6 @@ PGDATABASE="${PGDATABASE:-'directus'}"
PROJECT_NAME="${PROJECT:-development}"
PROJECT_FOLDER=$SCRIPT_DIR/directus-config/$PROJECT_NAME
echo "Sync collections"
npx directus-sync@3.4.0 push \
--dump-path $PROJECT_FOLDER \
--directus-url $DIRECTUS_URL \
--directus-email $DIRECTUS_EMAIL \
--directus-password $DIRECTUS_PASSWORD \
|| exit 1
echo "Seed data"
npx directus-sync@3.4.0 seed push \
--seed-path $PROJECT_FOLDER/seed \

View File

@ -173,7 +173,7 @@ export const PopupView = ({ children }: { children?: React.ReactNode }) => {
</ItemViewPopup>
<Tooltip offset={[0, -38]} direction='top'>
{item.name}
{item.name || item.layer?.item_default_name}
</Tooltip>
</Marker>
</TemplateItemContext.Provider>

View File

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

View File

@ -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<string>('')

View File

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

View File

@ -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<typeof useItemsManager>
@ -43,8 +54,10 @@ function useItemsManager(initialItems: Item[]): {
allItemsLoaded: boolean
} {
const addLayer = useAddLayer()
const { user } = useAuth()
const [allItemsLoaded, setallItemsLoaded] = useState<boolean>(false)
const layersRef = useRef<LayerProps[]>([])
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,

View File

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

View File

@ -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 (
<div className='tw:my-10 tw:mt-2 tw:px-6'>
{!(text === '' && hideWhenEmpty) && (
<h2 className='tw:text-lg tw:font-semibold'>{heading}</h2>
)}
{shouldShowHeading && <h2 className='tw:text-lg tw:font-semibold'>{heading}</h2>}
<div className='tw:mt-2 tw:text-sm'>
<TextView itemId={item.id} rawText={parsedText} />
<TextView item={item} text={text as string | null | undefined} itemId={item.id} />
</div>
</div>
)

View File

@ -25,4 +25,5 @@ export interface LayerProps {
public_edit_items?: boolean
listed?: boolean
item_presets?: Record<string, unknown>
item_default_name?: string
}