Merge branch 'main' into select-position-on-index-page

This commit is contained in:
Anton Tranelis 2025-06-11 12:40:21 +02:00 committed by GitHub
commit 81c3b46648
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
12 changed files with 154 additions and 124 deletions

View File

@ -9,6 +9,7 @@ export interface StartEndInputProps {
labelStyle?: string labelStyle?: string
updateStartValue?: (value: string) => void updateStartValue?: (value: string) => void
updateEndValue?: (value: string) => void updateEndValue?: (value: string) => void
containerStyle?: string
} }
/** /**
@ -20,9 +21,10 @@ export const PopupStartEndInput = ({
labelStyle, labelStyle,
updateStartValue, updateStartValue,
updateEndValue, updateEndValue,
containerStyle,
}: StartEndInputProps) => { }: StartEndInputProps) => {
return ( return (
<div className='tw:grid tw:grid-cols-2 tw:gap-2'> <div className={`tw:grid tw:grid-cols-2 tw:gap-2 ${containerStyle ?? ''}`}>
<TextInput <TextInput
type='date' type='date'
placeholder='start' placeholder='start'

View File

@ -53,6 +53,13 @@ export const TextView = ({
if (innerText) replacedText = fixUrls(innerText) if (innerText) replacedText = fixUrls(innerText)
if (replacedText) {
replacedText = replacedText.replace(
/(?<!\]?\()(?<!<)https?:\/\/[^\s)]+(?!\))(?!>)/g,
(url) => `[${url.replace(/https?:\/\/w{3}\./gi, '')}](${url})`,
)
}
if (replacedText) { if (replacedText) {
replacedText = replacedText.replace(mailRegex, (url) => { replacedText = replacedText.replace(mailRegex, (url) => {
return `[${url}](mailto:${url})` return `[${url}](mailto:${url})`

View File

@ -67,6 +67,7 @@ describe('GalleryForm', () => {
id: '1', id: '1',
width: 800, width: 800,
height: 600, height: 600,
type: 'image/jpeg',
}, },
}, },
{ {
@ -74,6 +75,7 @@ describe('GalleryForm', () => {
id: '2', id: '2',
width: 1024, width: 1024,
height: 768, height: 768,
type: 'image/jpeg',
}, },
}, },
] ]

View File

@ -42,6 +42,7 @@ export const GalleryForm = ({ state, setState }: Props) => {
height, height,
asset: await appState.assetsApi.upload(compressedFile, file.name), asset: await appState.assetsApi.upload(compressedFile, file.name),
name: file.name, name: file.name,
type: file.type,
} }
}) })
@ -56,6 +57,7 @@ export const GalleryForm = ({ state, setState }: Props) => {
id: upload.asset.id, id: upload.asset.id,
width: upload.width, width: upload.width,
height: upload.height, height: upload.height,
type: upload.type,
}, },
}, },
], ],
@ -68,6 +70,7 @@ export const GalleryForm = ({ state, setState }: Props) => {
onDrop: upload, onDrop: upload,
accept: { accept: {
'image/jpeg': [], 'image/jpeg': [],
'image/png': [],
}, },
}) })

View File

@ -6,15 +6,28 @@ import { useAppState } from '#components/AppShell/hooks/useAppState'
import type { Item } from '#types/Item' import type { Item } from '#types/Item'
const extensionMap = new Map([
['image/jpeg', '.jpg'],
['image/png', '.png'],
])
const getExtension = (type: string) => {
const extension = extensionMap.get(type)
if (extension) return extension
throw new Error(`Unsupported file type: ${type}`)
}
export const GalleryView = ({ item }: { item: Item }) => { export const GalleryView = ({ item }: { item: Item }) => {
const [index, setIndex] = useState(-1) const [index, setIndex] = useState(-1)
const appState = useAppState() const appState = useAppState()
const images = const images =
item.gallery?.map((i, j) => ({ item.gallery?.map(({ directus_files_id: { id, type, width, height } }, index) => ({
src: appState.assetsApi.url + `${i.directus_files_id.id}.jpg`, src: `${appState.assetsApi.url}${id}${getExtension(type)}`,
width: i.directus_files_id.width, width,
height: i.directus_files_id.height, height,
index: j, index,
})) ?? [] })) ?? []
if (images.length > 0) if (images.length > 0)

View File

@ -21,18 +21,16 @@ export function PlusButton({
return ( return (
<> <>
{hasUserPermission(collection, 'create', undefined, layer) && ( {hasUserPermission(collection, 'create', undefined, layer) && (
<div className='tw:dropdown tw:dropdown-top tw:dropdown-end tw:dropdown-hover tw:z-3000 tw:absolute tw:right-4 tw:bottom-4'> <button
<button tabIndex={0}
tabIndex={0} className='tw:btn tw:btn-circle tw:shadow tw:z-3000 tw:absolute tw:right-4 tw:bottom-4'
className='tw:z-500 tw:btn tw:btn-circle tw:shadow' onClick={() => {
onClick={() => { triggerAction()
triggerAction() }}
}} style={{ backgroundColor: color, color: '#fff' }}
style={{ backgroundColor: color, color: '#fff' }} >
> <PlusIcon className='tw:w-5 tw:h-5 tw:stroke-[2.5]' />
<PlusIcon className='tw:w-5 tw:h-5 tw:stroke-[2.5]' /> </button>
</button>
</div>
)} )}
</> </>
) )

View File

@ -69,7 +69,7 @@ exports[`GalleryForm > with previous images > renders 1`] = `
tabindex="0" tabindex="0"
> >
<input <input
accept="image/jpeg" accept="image/jpeg,image/png"
data-testid="gallery-upload-input" data-testid="gallery-upload-input"
multiple="" multiple=""
style="border: 0px; clip: rect(0, 0, 0, 0); clip-path: inset(50%); height: 1px; margin: 0px -1px -1px 0px; overflow: hidden; padding: 0px; position: absolute; width: 1px; white-space: nowrap;" style="border: 0px; clip: rect(0, 0, 0, 0); clip-path: inset(50%); height: 1px; margin: 0px -1px -1px 0px; overflow: hidden; padding: 0px; position: absolute; width: 1px; white-space: nowrap;"
@ -149,7 +149,7 @@ exports[`GalleryForm > with uploading images > renders 1`] = `
tabindex="0" tabindex="0"
> >
<input <input
accept="image/jpeg" accept="image/jpeg,image/png"
data-testid="gallery-upload-input" data-testid="gallery-upload-input"
multiple="" multiple=""
style="border: 0px; clip: rect(0, 0, 0, 0); clip-path: inset(50%); height: 1px; margin: 0px -1px -1px 0px; overflow: hidden; padding: 0px; position: absolute; width: 1px; white-space: nowrap;" style="border: 0px; clip: rect(0, 0, 0, 0); clip-path: inset(50%); height: 1px; margin: 0px -1px -1px 0px; overflow: hidden; padding: 0px; position: absolute; width: 1px; white-space: nowrap;"
@ -193,7 +193,7 @@ exports[`GalleryForm > without previous images > renders 1`] = `
tabindex="0" tabindex="0"
> >
<input <input
accept="image/jpeg" accept="image/jpeg,image/png"
data-testid="gallery-upload-input" data-testid="gallery-upload-input"
multiple="" multiple=""
style="border: 0px; clip: rect(0, 0, 0, 0); clip-path: inset(50%); height: 1px; margin: 0px -1px -1px 0px; overflow: hidden; padding: 0px; position: absolute; width: 1px; white-space: nowrap;" style="border: 0px; clip: rect(0, 0, 0, 0); clip-path: inset(50%); height: 1px; margin: 0px -1px -1px 0px; overflow: hidden; padding: 0px; position: absolute; width: 1px; white-space: nowrap;"

View File

@ -8,7 +8,7 @@ import { useEffect, useRef, useState } from 'react'
import { toast } from 'react-toastify' import { toast } from 'react-toastify'
import { useAuth } from '#components/Auth/useAuth' import { useAuth } from '#components/Auth/useAuth'
import { TextInput, TextAreaInput } from '#components/Input' import { TextInput } from '#components/Input'
import { useFilterTags } from '#components/Map/hooks/useFilter' import { useFilterTags } from '#components/Map/hooks/useFilter'
import { useAddItem, useItems, useRemoveItem } from '#components/Map/hooks/useItems' import { useAddItem, useItems, useRemoveItem } from '#components/Map/hooks/useItems'
import { useLayers } from '#components/Map/hooks/useLayers' import { useLayers } from '#components/Map/hooks/useLayers'
@ -33,15 +33,13 @@ export const OverlayItemsIndexPage = ({
url, url,
layerName, layerName,
parameterField, parameterField,
plusButton = true,
}: { }: {
layerName: string layerName: string
url: string url: string
parameterField?: string parameterField?: string
plusButton?: boolean
}) => { }) => {
const [loading, setLoading] = useState<boolean>(false) const [loading, setLoading] = useState<boolean>(false)
const [addItemPopupType, setAddItemPopupType] = useState<string>('') const [addItemPopupOpen, setAddItemPopupOpen] = useState<boolean>(false)
// eslint-disable-next-line @typescript-eslint/no-unused-vars // eslint-disable-next-line @typescript-eslint/no-unused-vars
const parameterFieldDummy = parameterField const parameterFieldDummy = parameterField
@ -52,12 +50,10 @@ export const OverlayItemsIndexPage = ({
} }
useEffect(() => { useEffect(() => {
scroll() if (addItemPopupOpen) {
}, [addItemPopupType]) scroll()
}
useEffect(() => { }, [addItemPopupOpen])
setAddItemPopupType('')
}, [layerName])
const tags = useTags() const tags = useTags()
const addTag = useAddTag() const addTag = useAddTag()
@ -106,7 +102,7 @@ export const OverlayItemsIndexPage = ({
} }
addItem({ ...formItem, user_created: user ?? undefined, id: uuid, layer, public_edit: !user }) addItem({ ...formItem, user_created: user ?? undefined, id: uuid, layer, public_edit: !user })
setLoading(false) setLoading(false)
setAddItemPopupType('') setAddItemPopupOpen(false)
} }
const deleteItem = async (item: Item) => { const deleteItem = async (item: Item) => {
@ -174,15 +170,18 @@ export const OverlayItemsIndexPage = ({
/> />
</div> </div>
))} ))}
{addItemPopupType === 'place' && ( {addItemPopupOpen && (
<form ref={tabRef} autoComplete='off' onSubmit={(e) => submitNewItem(e)}> <form ref={tabRef} autoComplete='off' onSubmit={(e) => submitNewItem(e)}>
<div className='tw:cursor-pointer tw:break-inside-avoid card tw:border-[1px] tw:border-base-300 card-body tw:shadow-xl tw:bg-base-100 tw:text-base-content tw:p-6 tw:mb-10'> <div className='tw:cursor-pointer tw:break-inside-avoid card tw:border-[1px] tw:border-base-300 card-body tw:shadow-xl tw:bg-base-100 tw:text-base-content tw:p-6 tw:mb-10'>
<label <label
className='btn btn-sm tw:rounded-2xl btn-circle btn-ghost tw:hover:bg-transparent tw:absolute tw:right-0 tw:top-0 tw:text-gray-600' className='tw:btn tw:btn-sm tw:rounded-2xl tw:btn-circle tw:btn-ghost tw:hover:bg-transparent tw:absolute tw:right-0 tw:top-0 tw:text-gray-600'
onClick={() => setAddItemPopupType('')} onClick={() => setAddItemPopupOpen(false)}
> >
<p className='tw:text-center'></p> <p className='tw:text-center'></p>
</label> </label>
<div className='tw:flex tw:justify-center tw:mb-4'>
<b className='tw:text-xl tw:text-center tw:font-bold'>{layer?.menuText}</b>
</div>
<TextInput <TextInput
type='text' type='text'
placeholder='Name' placeholder='Name'
@ -190,13 +189,9 @@ export const OverlayItemsIndexPage = ({
defaultValue={''} defaultValue={''}
inputStyle='' inputStyle=''
/> />
{layer?.itemType.show_start_end_input && <PopupStartEndInput />} {layer?.itemType.show_start_end_input && (
<TextAreaInput <PopupStartEndInput containerStyle='tw:mt-3' showLabels={false} />
placeholder='Text' )}
dataField='text'
defaultValue={''}
inputStyle='tw:h-40 tw:mt-5'
/>
<div className='tw:flex tw:justify-center'> <div className='tw:flex tw:justify-center'>
<button <button
className={ className={
@ -217,11 +212,11 @@ export const OverlayItemsIndexPage = ({
</div> </div>
</MapOverlayPage> </MapOverlayPage>
{plusButton && ( {!layer?.userProfileLayer && (
<PlusButton <PlusButton
layer={layer} layer={layer}
triggerAction={() => { triggerAction={() => {
setAddItemPopupType('place') setAddItemPopupOpen(true)
scroll() scroll()
}} }}
color={'#777'} color={'#777'}

View File

@ -0,0 +1,3 @@
.yarl__portal_open {
z-index: 10050;
}

View File

@ -1,98 +1,103 @@
.leaflet-control-attribution { .leaflet-control-attribution {
display: none; display: none;
} }
.leaflet-control-locate { .leaflet-control-locate {
display: none; display: none;
} }
.leaflet-data-marker { .leaflet-data-marker {
background: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACMAAAAQCAYAAACcN8ZaAAAB3klEQVR42s3U4UdDURzG8czMXJnJ1Vwzc6VJZjaZJdlMlpQsKdmUFNOUspRSSqUolfQfr+fF98Vx5mwv9qbDx7LdznnO7/7Omej3+/+Ga0QMUYkhbvBgmhzCQxwxibIGrGEF8CQhU+LLtKQkQNqScUgjxRxTBIxbgfgD/BgnhM8kM5KTeclLQYqGkkMRBckzR8ic/mAgd5BAZplsUaqyIg2sDtHg2brUZJk5SmwopErJUWE8SpmTMhNvya60Zd/SNrR4bkeaskG4uiwRZk6yrJEYFibGAxn+scECHTmTnuVCzvmty3PHciB7bGKN6lQkzysPqIrHmpFhYbKUtckC1/Ioz4ZHuZdbuSLYiRxRpSZVWXZVxAzC0R4Ik5SQsu6w8yd5l2/5kg95I9SdXMoZQfYIUjeqEUrgOkXGPeN4TYRhxy8E+ZUf+eS7B7miIoeybVSjKDnm8u3+gH3pDTYwu1igATvs/pXqvBKiR4i2bNJfi1ZfUAnjgrOG8wY2quNzBKuU/ZS+uSFEl5O0xRGuUIlZCcw7xG5QPkeHYUSNV5WXGou2sC3rBC0LjenqCXGO0WEiTJa0Lr4KixdHBrDGuGGiRqCUpFk8pGIpQtCU7p4YPwxYxEMCk1aAMQZh8Ac8PfbIzYPJOwAAAABJRU5ErkJggg==') no-repeat; background: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACMAAAAQCAYAAACcN8ZaAAAB3klEQVR42s3U4UdDURzG8czMXJnJ1Vwzc6VJZjaZJdlMlpQsKdmUFNOUspRSSqUolfQfr+fF98Vx5mwv9qbDx7LdznnO7/7Omej3+/+Ga0QMUYkhbvBgmhzCQxwxibIGrGEF8CQhU+LLtKQkQNqScUgjxRxTBIxbgfgD/BgnhM8kM5KTeclLQYqGkkMRBckzR8ic/mAgd5BAZplsUaqyIg2sDtHg2brUZJk5SmwopErJUWE8SpmTMhNvya60Zd/SNrR4bkeaskG4uiwRZk6yrJEYFibGAxn+scECHTmTnuVCzvmty3PHciB7bGKN6lQkzysPqIrHmpFhYbKUtckC1/Ioz4ZHuZdbuSLYiRxRpSZVWXZVxAzC0R4Ik5SQsu6w8yd5l2/5kg95I9SdXMoZQfYIUjeqEUrgOkXGPeN4TYRhxy8E+ZUf+eS7B7miIoeybVSjKDnm8u3+gH3pDTYwu1igATvs/pXqvBKiR4i2bNJfi1ZfUAnjgrOG8wY2quNzBKuU/ZS+uSFEl5O0xRGuUIlZCcw7xG5QPkeHYUSNV5WXGou2sC3rBC0LjenqCXGO0WEiTJa0Lr4KixdHBrDGuGGiRqCUpFk8pGIpQtCU7p4YPwxYxEMCk1aAMQZh8Ac8PfbIzYPJOwAAAABJRU5ErkJggg==') no-repeat;
background-position: 6px 32px; background-position: 6px 32px;
} }
.leaflet-container { .leaflet-container {
cursor: inherit; cursor: inherit;
} }
.leaflet-popup-scrolled { .leaflet-popup-scrolled {
overflow-x: hidden; overflow-x: hidden;
} }
.leaflet-popup-content-wrapper, .leaflet-popup-tip{ .leaflet-popup-content-wrapper,
background-color: var(--color-base-100); .leaflet-popup-tip {
color: var(--color-base-content); background-color: var(--color-base-100);
border-radius: var(--radius-box); color: var(--color-base-content);
} border-radius: var(--radius-box);
}
.leaflet-popup-tip-container, .leaflet-popup-tip{ .leaflet-popup-tip-container,
border-radius: 0; .leaflet-popup-tip {
} border-radius: 0;
}
.leaflet-tooltip { .leaflet-tooltip {
background-color: var(--color-base-100); background-color: var(--color-base-100);
color: var(--color-base-content); color: var(--color-base-content);
border-width: 0px; border-width: 0px;
} }
.leaflet-tooltip { .leaflet-tooltip {
border-radius: var(--radius-box); border-radius: var(--radius-box);
transition: opacity 500ms; transition: opacity 500ms;
transition-delay: 50ms; transition-delay: 50ms;
} }
.leaflet-tooltip::before { .leaflet-tooltip::before {
border-top-color: var(--color-base-100); border-top-color: var(--color-base-100);
} }
.leaflet-container { .leaflet-container {
text-align: left; text-align: left;
background-image: url("data:image/svg+xml,%3Csvg width='40' height='40' stroke='%23000' viewBox='0 0 24 24' xmlns='http://www.w3.org/2000/svg'%3E%3Cstyle%3E.spinner_V8m1%7Btransform-origin:center;animation:spinner_zKoa 2s linear infinite%7D.spinner_V8m1 circle%7Bstroke-linecap:round;animation:spinner_YpZS 1.5s ease-out infinite%7D%40keyframes spinner_zKoa%7B100%25%7Btransform:rotate(360deg)%7D%7D%40keyframes spinner_YpZS%7B0%25%7Bstroke-dasharray:0 150;stroke-dashoffset:0%7D47.5%25%7Bstroke-dasharray:42 150;stroke-dashoffset:-16%7D95%25%2C100%25%7Bstroke-dasharray:42 150;stroke-dashoffset:-59%7D%7D%3C%2Fstyle%3E%3Cg class='spinner_V8m1'%3E%3Ccircle cx='12' cy='12' r='9.5' fill='none' stroke-width='3'%3E%3C%2Fcircle%3E%3C%2Fg%3E%3C%2Fsvg%3E"); background-image: url("data:image/svg+xml,%3Csvg width='40' height='40' stroke='%23000' viewBox='0 0 24 24' xmlns='http://www.w3.org/2000/svg'%3E%3Cstyle%3E.spinner_V8m1%7Btransform-origin:center;animation:spinner_zKoa 2s linear infinite%7D.spinner_V8m1 circle%7Bstroke-linecap:round;animation:spinner_YpZS 1.5s ease-out infinite%7D%40keyframes spinner_zKoa%7B100%25%7Btransform:rotate(360deg)%7D%7D%40keyframes spinner_YpZS%7B0%25%7Bstroke-dasharray:0 150;stroke-dashoffset:0%7D47.5%25%7Bstroke-dasharray:42 150;stroke-dashoffset:-16%7D95%25%2C100%25%7Bstroke-dasharray:42 150;stroke-dashoffset:-59%7D%7D%3C%2Fstyle%3E%3Cg class='spinner_V8m1'%3E%3Ccircle cx='12' cy='12' r='9.5' fill='none' stroke-width='3'%3E%3C%2Fcircle%3E%3C%2Fg%3E%3C%2Fsvg%3E");
background-repeat: no-repeat; background-repeat: no-repeat;
background-attachment: fixed; background-attachment: fixed;
background-position: 50% 80%; background-position: 50% 80%;
} }
.leaflet-popup-close-button span { .leaflet-popup-close-button span {
color: var(--color-base-content); color: var(--color-base-content);
opacity: 50%; opacity: 50%;
} }
.leaflet-top { .leaflet-top {
top: 6em top: 6em
} }
.leaflet-left { .leaflet-left {
left: 0.5em left: 0.5em
} }
.leaflet-control-zoom { .leaflet-control-zoom {
border-radius: var(--radius-box); border-radius: var(--radius-box);
color: var(--color-base-content); color: var(--color-base-content);
background-color: var(--color-base-100); background-color: var(--color-base-100);
width: calc(var(--tw-spacing) * 10); width: calc(var(--tw-spacing) * 10);
border: 2px solid var(--color-base-300) !important; border: 2px solid var(--color-base-300) !important;
box-shadow: 0 20px 25px -5px rgb(0 0 0 / 0.1), 0 8px 10px -6px rgb(0 0 0 / 0.1) !important; box-shadow: 0 20px 25px -5px rgb(0 0 0 / 0.1), 0 8px 10px -6px rgb(0 0 0 / 0.1) !important;
} }
.leaflet-control-zoom-in { .leaflet-control-zoom-in {
border-top-left-radius: var(--radius-box) !important; border-top-left-radius: var(--radius-box) !important;
border-top-right-radius: var(--radius-box) !important; border-top-right-radius: var(--radius-box) !important;
color: var(--color-base-content) !important; color: var(--color-base-content) !important;
background-color: var(--color-base-100) !important; background-color: var(--color-base-100) !important;
width: calc(var(--tw-spacing) * 9) !important; width: calc(var(--tw-spacing) * 9) !important;
border-bottom: 1px solid var(--color-base-300) !important; border-bottom: 1px solid var(--color-base-300) !important;
height: calc(var(--tw-spacing) * 9) !important; height: calc(var(--tw-spacing) * 9) !important;
line-height: 40px !important; line-height: 40px !important;
} }
.leaflet-control-zoom-out { .leaflet-control-zoom-out {
border-bottom-left-radius: var(--radius-box) !important; border-bottom-left-radius: var(--radius-box) !important;
border-bottom-right-radius: var(--radius-box) !important; border-bottom-right-radius: var(--radius-box) !important;
color: var(--color-base-content) !important; color: var(--color-base-content) !important;
background-color: var(--color-base-100) !important; background-color: var(--color-base-100) !important;
width: calc(var(--tw-spacing) * 9) !important; width: calc(var(--tw-spacing) * 9) !important;
height: calc(var(--tw-spacing) * 9) !important; height: calc(var(--tw-spacing) * 9) !important;
line-height: 40px !important; line-height: 40px !important;
} }
.leaflet-popup-content p {
margin: calc(var(--tw-spacing) * 1) !important
}

View File

@ -15,3 +15,4 @@ import '#assets/css/leaflet.css'
import '#assets/css/color-picker.css' import '#assets/css/color-picker.css'
import '#assets/css/markdown.css' import '#assets/css/markdown.css'
import '#assets/css/tiptap.css' import '#assets/css/tiptap.css'
import '#assets/css/gallery.css'

1
src/types/Item.d.ts vendored
View File

@ -12,6 +12,7 @@ interface GalleryItem {
id: string id: string
width: number width: number
height: number height: number
type: string
} }
} }