mirror of
https://github.com/utopia-os/utopia-ui.git
synced 2026-01-20 20:01:18 +00:00
Merge remote-tracking branch 'origin/main' into initial-e2e-test-setup
This commit is contained in:
commit
7633a0a3a8
2
.github/workflows/test.unit.yml
vendored
2
.github/workflows/test.unit.yml
vendored
@ -17,8 +17,6 @@ jobs:
|
||||
unit:
|
||||
- '.github/workflows/**/*'
|
||||
- '**/*'
|
||||
- '!**/*.md'
|
||||
predicate-quantifier: 'every'
|
||||
|
||||
unit:
|
||||
if: needs.files-changed.outputs.unit == 'true'
|
||||
|
||||
@ -8,6 +8,7 @@ We'll use **Vite** to create an empty React app named **"1-static-map"**:
|
||||
|
||||
```shell
|
||||
npm create vite@latest 1-static-map -- --template react-ts
|
||||
```
|
||||
|
||||
Next, we navigate into our project folder and install the [utopia-ui](https://github.com/utopia-os/utopia-ui) package:
|
||||
|
||||
@ -38,7 +39,8 @@ To see our **first map app**, we start the development server:
|
||||
|
||||
```shell
|
||||
npm run dev
|
||||
```
|
||||
|
||||
Now, we can open the project in the browser and explore our interactive map! 😊
|
||||
Now, we can open [localhost:5173](http://localhost:5173/) in the browser and explore our interactive map! 😊
|
||||
|
||||
➡️ In [Example 2](../2-static-layers/), we'll add **static data** to our map.
|
||||
|
||||
16397
package-lock.json
generated
16397
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "utopia-ui",
|
||||
"version": "3.0.74",
|
||||
"version": "3.0.78",
|
||||
"description": "Reuseable React Components to build mapping apps for real life communities and networks",
|
||||
"repository": "https://github.com/utopia-os/utopia-ui",
|
||||
"homepage": "https://utopia-os.org/",
|
||||
@ -103,6 +103,7 @@
|
||||
"radash": "^12.1.0",
|
||||
"react-colorful": "^5.6.1",
|
||||
"react-image-crop": "^10.1.8",
|
||||
"react-inlinesvg": "^4.2.0",
|
||||
"react-leaflet": "^4.2.1",
|
||||
"react-leaflet-cluster": "^2.1.0",
|
||||
"react-markdown": "^9.0.1",
|
||||
@ -110,7 +111,6 @@
|
||||
"react-router-dom": "^6.16.0",
|
||||
"react-toastify": "^9.1.3",
|
||||
"remark-breaks": "^4.0.0",
|
||||
"tw-elements": "^1.0.0",
|
||||
"yet-another-react-lightbox": "^3.21.7"
|
||||
},
|
||||
"imports": {
|
||||
|
||||
@ -76,6 +76,7 @@ export default [
|
||||
'leaflet.locatecontrol/dist/L.Control.Locate.css',
|
||||
'yet-another-react-lightbox',
|
||||
'react-photo-album',
|
||||
'react-inlinesvg',
|
||||
],
|
||||
},
|
||||
{
|
||||
|
||||
@ -23,7 +23,7 @@ export function AppShell({
|
||||
<div className='tw-flex tw-flex-col tw-h-full'>
|
||||
<SetAppState assetsApi={assetsApi} />
|
||||
<NavBar appName={appName}></NavBar>
|
||||
<div id='app-content' className='tw-flex-grow'>
|
||||
<div id='app-content' className='tw-flex'>
|
||||
{children}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -1,3 +1,5 @@
|
||||
import { useAppState } from './hooks/useAppState'
|
||||
|
||||
interface ContentProps {
|
||||
children?: React.ReactNode
|
||||
}
|
||||
@ -6,8 +8,12 @@ interface ContentProps {
|
||||
* @category AppShell
|
||||
*/
|
||||
export function Content({ children }: ContentProps) {
|
||||
const appState = useAppState()
|
||||
|
||||
return (
|
||||
<div className='tw-flex tw-flex-col tw-w-full tw-h-full tw-bg-base-200 tw-relative'>
|
||||
<div
|
||||
className={`${appState.sideBarOpen && !appState.sideBarSlim ? 'tw-ml-48' : appState.sideBarOpen && appState.sideBarSlim ? 'tw-ml-14' : ''} tw-flex tw-flex-col tw-w-full tw-h-full tw-bg-base-200 tw-relative tw-transition-all tw-duration-300`}
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
)
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
|
||||
import { useContext, createContext } from 'react'
|
||||
import { BrowserRouter as Router, useLocation } from 'react-router-dom'
|
||||
import { BrowserRouter as Router, useInRouterContext } from 'react-router-dom'
|
||||
import { ToastContainer } from 'react-toastify'
|
||||
|
||||
import { QuestsProvider } from '#components/Gaming/hooks/useQuests'
|
||||
@ -15,49 +15,40 @@ import { TagsProvider } from '#components/Map/hooks/useTags'
|
||||
|
||||
import { AppStateProvider } from './hooks/useAppState'
|
||||
|
||||
import type { CloseButtonProps } from 'react-toastify'
|
||||
|
||||
// Helper context to determine if the ContextWrapper is already present.
|
||||
const ContextCheckContext = createContext(false)
|
||||
|
||||
const CloseButton = ({ closeToast }: CloseButtonProps) => (
|
||||
<button
|
||||
className='tw-btn tw-btn-sm tw-btn-circle tw-btn-ghost tw-absolute tw-right-2 tw-top-2 focus:tw-outline-none'
|
||||
onClick={closeToast}
|
||||
>
|
||||
✕
|
||||
</button>
|
||||
)
|
||||
|
||||
export const ContextWrapper = ({ children }: { children: React.ReactNode }) => {
|
||||
const isWrapped = useContext(ContextCheckContext)
|
||||
|
||||
// Check if we are already inside a Router
|
||||
let location
|
||||
try {
|
||||
// eslint-disable-next-line react-hooks/rules-of-hooks
|
||||
location = useLocation()
|
||||
// eslint-disable-next-line no-catch-all/no-catch-all
|
||||
} catch (e) {
|
||||
location = null
|
||||
}
|
||||
const isInsideRouter = useInRouterContext()
|
||||
|
||||
// Case 1: Only the Router is missing, but ContextWrapper is already provided
|
||||
if (!location && isWrapped) {
|
||||
return <Router>{children}</Router>
|
||||
}
|
||||
let returnValue = children
|
||||
|
||||
// Case 2: Neither Router nor ContextWrapper is present
|
||||
if (!location && !isWrapped) {
|
||||
return (
|
||||
<Router>
|
||||
<ContextCheckContext.Provider value={true}>
|
||||
<Wrappers>{children}</Wrappers>
|
||||
</ContextCheckContext.Provider>
|
||||
</Router>
|
||||
)
|
||||
}
|
||||
|
||||
// Case 3: Only ContextWrapper is missing
|
||||
if (location && !isWrapped) {
|
||||
return (
|
||||
if (!isWrapped) {
|
||||
returnValue = (
|
||||
<ContextCheckContext.Provider value={true}>
|
||||
<Wrappers>{children}</Wrappers>
|
||||
<Wrappers>{returnValue}</Wrappers>
|
||||
</ContextCheckContext.Provider>
|
||||
)
|
||||
}
|
||||
|
||||
// Case 4: Both Router and ContextWrapper are already present
|
||||
return children
|
||||
if (!isInsideRouter) {
|
||||
returnValue = <Router>{returnValue}</Router>
|
||||
}
|
||||
|
||||
return returnValue
|
||||
}
|
||||
|
||||
// eslint-disable-next-line react/prop-types
|
||||
@ -87,6 +78,7 @@ export const Wrappers = ({ children }) => {
|
||||
draggable
|
||||
pauseOnHover
|
||||
theme='light'
|
||||
closeButton={CloseButton}
|
||||
/>
|
||||
{children}
|
||||
</QuestsProvider>
|
||||
|
||||
@ -8,6 +8,8 @@ import { toast } from 'react-toastify'
|
||||
import { useAuth } from '#components/Auth/useAuth'
|
||||
import { useItems } from '#components/Map/hooks/useItems'
|
||||
|
||||
import { useAppState, useSetAppState } from './hooks/useAppState'
|
||||
|
||||
import type { Item } from '#types/Item'
|
||||
|
||||
export default function NavBar({ appName }: { appName: string }) {
|
||||
@ -16,6 +18,13 @@ export default function NavBar({ appName }: { appName: string }) {
|
||||
const [userProfile, setUserProfile] = useState<Item>({} as Item)
|
||||
const items = useItems()
|
||||
|
||||
const appState = useAppState()
|
||||
const setAppState = useSetAppState()
|
||||
|
||||
const toggleSidebar = () => {
|
||||
setAppState({ sideBarOpen: !appState.sideBarOpen })
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
const profile =
|
||||
user && items.find((i) => i.user_created?.id === user.id && i.layer?.userProfileLayer)
|
||||
@ -24,8 +33,6 @@ export default function NavBar({ appName }: { appName: string }) {
|
||||
: setUserProfile({ id: crypto.randomUUID(), name: user?.first_name ?? '', text: '' })
|
||||
}, [user, items])
|
||||
|
||||
// useEffect(() => {}, [userProfile])
|
||||
|
||||
const nameRef = useRef<HTMLHeadingElement>(null)
|
||||
const [nameWidth, setNameWidth] = useState<number>(0)
|
||||
const location = useLocation()
|
||||
@ -65,10 +72,9 @@ export default function NavBar({ appName }: { appName: string }) {
|
||||
<div className='tw-navbar tw-bg-base-100 tw-z-[9998] tw-shadow-xl tw-relative'>
|
||||
<button
|
||||
className='tw-btn tw-btn-square tw-btn-ghost'
|
||||
data-te-sidenav-toggle-ref
|
||||
data-te-target='#sidenav'
|
||||
aria-controls='#sidenav'
|
||||
aria-haspopup='true'
|
||||
onClick={() => toggleSidebar()}
|
||||
>
|
||||
<Bars3Icon className='tw-inline-block tw-w-5 tw-h-5' />
|
||||
</button>
|
||||
@ -103,7 +109,7 @@ export default function NavBar({ appName }: { appName: string }) {
|
||||
{userProfile.image && (
|
||||
<div className='tw-avatar'>
|
||||
<div className='tw-w-10 tw-rounded-full'>
|
||||
<img src={'https://api.utopia-lab.org/assets/' + userProfile.image} />
|
||||
<img src={appState.assetsApi.url + userProfile.image} />
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
@ -1,8 +1,8 @@
|
||||
import ChevronRightIcon from '@heroicons/react/24/outline/ChevronRightIcon'
|
||||
import { useRef, useState, useEffect } from 'react'
|
||||
import { useState, useEffect } from 'react'
|
||||
import { NavLink, useLocation } from 'react-router-dom'
|
||||
import { Sidenav, initTE } from 'tw-elements'
|
||||
|
||||
import { useAppState, useSetAppState } from './hooks/useAppState'
|
||||
import SidebarSubmenu from './SidebarSubmenu'
|
||||
|
||||
export interface Route {
|
||||
@ -13,40 +13,12 @@ export interface Route {
|
||||
blank?: boolean
|
||||
}
|
||||
|
||||
interface SidenavType extends HTMLElement {
|
||||
toggleSlim(): void
|
||||
toggle(): void
|
||||
}
|
||||
|
||||
/**
|
||||
* @category AppShell
|
||||
*/
|
||||
export function SideBar({ routes, bottomRoutes }: { routes: Route[]; bottomRoutes?: Route[] }) {
|
||||
// prevent react18 from calling useEffect twice
|
||||
const init = useRef(false)
|
||||
|
||||
const location = useLocation()
|
||||
|
||||
const [instance, setInstance] = useState<SidenavType>()
|
||||
const [slim, setSlim] = useState<boolean>(false)
|
||||
|
||||
const toggleSlim = () => {
|
||||
setSlim(!slim)
|
||||
instance?.toggleSlim()
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if (!init.current) {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-call
|
||||
initTE({ Sidenav })
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-call
|
||||
const instance = Sidenav.getInstance(document.getElementById('sidenav')) as SidenavType
|
||||
setInstance(instance)
|
||||
instance.toggleSlim()
|
||||
init.current = true
|
||||
}
|
||||
}, [])
|
||||
|
||||
const [embedded, setEmbedded] = useState<boolean>(true)
|
||||
|
||||
useEffect(() => {
|
||||
@ -57,18 +29,25 @@ export function SideBar({ routes, bottomRoutes }: { routes: Route[]; bottomRoute
|
||||
|
||||
const params = new URLSearchParams(window.location.search)
|
||||
|
||||
const appState = useAppState()
|
||||
const setAppState = useSetAppState()
|
||||
|
||||
const toggleSidebarOpen = () => {
|
||||
setAppState({ sideBarOpen: !appState.sideBarOpen })
|
||||
}
|
||||
|
||||
const toggleSidebarSlim = () => {
|
||||
setAppState({ sideBarSlim: !appState.sideBarSlim })
|
||||
}
|
||||
|
||||
return (
|
||||
<nav
|
||||
id='sidenav'
|
||||
className={`group tw-fixed tw-left-0 ${embedded ? 'tw-mt-0 tw-h-[100dvh]' : 'tw-mt-16 tw-h-[calc(100dvh-64px)]'} tw-top-0 tw-z-[10035] tw--translate-x-full tw-overflow-hidden tw-shadow-xl data-[te-sidenav-slim='true']:tw-hidden data-[te-sidenav-slim-collapsed='true']:tw-w-[56px] data-[te-sidenav-slim='true']:tw-w-[56px] data-[te-sidenav-hidden='false']:tw-translate-x-0 dark:tw-bg-zinc-800 [&[data-te-sidenav-slim-collapsed='true'][data-te-sidenav-slim='false']]:tw-hidden [&[data-te-sidenav-slim-collapsed='true'][data-te-sidenav-slim='true']]:[display:unset]`}
|
||||
data-te-sidenav-init
|
||||
data-te-sidenav-hidden='true'
|
||||
data-te-sidenav-mode='side'
|
||||
data-te-sidenav-slim='true'
|
||||
data-te-sidenav-content='#app-content'
|
||||
data-te-sidenav-slim-collapsed='true'
|
||||
data-te-sidenav-slim-width='56'
|
||||
data-te-sidenav-width='180'
|
||||
className={`${appState.sideBarOpen ? 'tw-translate-x-0' : '-tw-translate-x-full'}
|
||||
${appState.sideBarSlim ? 'tw-w-14' : 'tw-w-48'}
|
||||
${embedded ? 'tw-mt-0 tw-h-[100dvh]' : 'tw-mt-16 tw-h-[calc(100dvh-64px)]'}
|
||||
tw-fixed tw-left-0 tw-transition-all tw-duration-300 tw-top-0 tw-z-[10035]
|
||||
tw-overflow-hidden tw-shadow-xl dark:tw-bg-zinc-800`}
|
||||
>
|
||||
<div
|
||||
className={`tw-flex tw-flex-col ${embedded ? 'tw-h-full' : 'tw-h-[calc(100dvh-64px)]'}`}
|
||||
@ -92,12 +71,12 @@ export function SideBar({ routes, bottomRoutes }: { routes: Route[]; bottomRoute
|
||||
`${isActive ? 'tw-font-semibold tw-bg-base-200 !tw-rounded-none' : 'tw-font-normal !tw-rounded-none'}`
|
||||
}
|
||||
onClick={() => {
|
||||
if (screen.width < 640 && !slim) instance?.toggle()
|
||||
if (screen.width < 640 && !appState.sideBarSlim) toggleSidebarOpen()
|
||||
}}
|
||||
>
|
||||
{route.icon}
|
||||
<span
|
||||
className="group-[&[data-te-sidenav-slim-collapsed='true']]:data-[te-sidenav-slim='false']:tw-hidden"
|
||||
className={`${appState.sideBarSlim ? 'tw-hidden' : ''}`}
|
||||
data-te-sidenav-slim='false'
|
||||
>
|
||||
{route.name}
|
||||
@ -140,12 +119,12 @@ export function SideBar({ routes, bottomRoutes }: { routes: Route[]; bottomRoute
|
||||
`${isActive ? 'tw-font-semibold tw-bg-base-200 !tw-rounded-none' : 'tw-font-normal !tw-rounded-none'}`
|
||||
}
|
||||
onClick={() => {
|
||||
if (screen.width < 640 && !slim) instance?.toggle()
|
||||
if (screen.width < 640 && !appState.sideBarSlim) toggleSidebarOpen()
|
||||
}}
|
||||
>
|
||||
{route.icon}
|
||||
<span
|
||||
className="group-[&[data-te-sidenav-slim-collapsed='true']]:data-[te-sidenav-slim='false']:tw-hidden"
|
||||
className={`${appState.sideBarSlim ? 'tw-hidden' : ''}`}
|
||||
data-te-sidenav-slim='false'
|
||||
>
|
||||
{route.name}
|
||||
@ -167,9 +146,9 @@ export function SideBar({ routes, bottomRoutes }: { routes: Route[]; bottomRoute
|
||||
<ChevronRightIcon
|
||||
className={
|
||||
'tw-w-5 tw-h-5 tw-mb-4 tw-mr-4 tw-cursor-pointer tw-float-right tw-delay-400 tw-duration-500 tw-transition-all ' +
|
||||
(!slim ? 'tw-rotate-180' : '')
|
||||
(!appState.sideBarSlim ? 'tw-rotate-180' : '')
|
||||
}
|
||||
onClick={() => toggleSlim()}
|
||||
onClick={() => toggleSidebarSlim()}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -32,13 +32,7 @@ function SidebarSubmenu({
|
||||
<div className='flex-col'>
|
||||
{/** Route header */}
|
||||
<div className='w-full' onClick={() => setIsExpanded(!isExpanded)}>
|
||||
{icon}{' '}
|
||||
<span
|
||||
className="group-[&[data-te-sidenav-slim-collapsed='true']]:data-[te-sidenav-slim='false']:hidden"
|
||||
data-te-sidenav-slim='false'
|
||||
>
|
||||
{name}{' '}
|
||||
</span>
|
||||
{icon} <span>{name} </span>
|
||||
<ChevronDownIcon
|
||||
className={
|
||||
'w-5 h-5 mt-1 float-right delay-400 duration-500 transition-all ' +
|
||||
@ -48,7 +42,7 @@ function SidebarSubmenu({
|
||||
</div>
|
||||
|
||||
{/** Submenu list */}
|
||||
<div className={' w-full data-[te-collapse-show]:!hidden ' + (isExpanded ? '' : 'hidden')}>
|
||||
<div className={' w-full' + (isExpanded ? '' : 'hidden')}>
|
||||
<ul className={'menu menu-compact'}>
|
||||
{submenu?.map((m, k) => {
|
||||
return (
|
||||
|
||||
@ -6,14 +6,16 @@ import type { AssetsApi } from '#types/AssetsApi'
|
||||
|
||||
interface AppState {
|
||||
assetsApi: AssetsApi
|
||||
userType: string
|
||||
sideBarOpen: boolean
|
||||
sideBarSlim: boolean
|
||||
}
|
||||
|
||||
type UseAppManagerResult = ReturnType<typeof useAppManager>
|
||||
|
||||
const initialAppState: AppState = {
|
||||
assetsApi: {} as AssetsApi,
|
||||
userType: '',
|
||||
sideBarOpen: false,
|
||||
sideBarSlim: false,
|
||||
}
|
||||
|
||||
const AppContext = createContext<UseAppManagerResult>({
|
||||
|
||||
@ -1,42 +0,0 @@
|
||||
/* eslint-disable react/prop-types */
|
||||
|
||||
/* eslint-disable @typescript-eslint/no-empty-function */
|
||||
import { useCallback, useState, createContext, useContext } from 'react'
|
||||
|
||||
import type { AssetsApi } from '#types/AssetsApi'
|
||||
|
||||
type UseAssetManagerResult = ReturnType<typeof useAssetsManager>
|
||||
|
||||
const AssetContext = createContext<UseAssetManagerResult>({
|
||||
api: {} as AssetsApi,
|
||||
setAssetsApi: () => {},
|
||||
})
|
||||
|
||||
function useAssetsManager(): {
|
||||
api: AssetsApi
|
||||
setAssetsApi: (api: AssetsApi) => void
|
||||
} {
|
||||
const [api, setApi] = useState<AssetsApi>({} as AssetsApi)
|
||||
|
||||
const setAssetsApi = useCallback((api: AssetsApi) => {
|
||||
setApi(api)
|
||||
}, [])
|
||||
|
||||
return { api, setAssetsApi }
|
||||
}
|
||||
|
||||
export const AssetsProvider: React.FunctionComponent<{
|
||||
children?: React.ReactNode
|
||||
}> = ({ children }) => (
|
||||
<AssetContext.Provider value={useAssetsManager()}>{children}</AssetContext.Provider>
|
||||
)
|
||||
|
||||
export const useAssetApi = (): AssetsApi => {
|
||||
const { api } = useContext(AssetContext)
|
||||
return api
|
||||
}
|
||||
|
||||
export const useSetAssetApi = (): UseAssetManagerResult['setAssetsApi'] => {
|
||||
const { setAssetsApi } = useContext(AssetContext)
|
||||
return setAssetsApi
|
||||
}
|
||||
@ -1,3 +1,4 @@
|
||||
export * from './AppShell'
|
||||
export { SideBar } from './SideBar'
|
||||
export { Content } from './Content'
|
||||
export { default as SVG } from 'react-inlinesvg'
|
||||
|
||||
@ -16,9 +16,10 @@ const ComboBoxInput = ({ id, options, value, onValueChange }: ComboBoxProps) =>
|
||||
id={id}
|
||||
className='tw-form-select tw-block tw-w-full tw-py-2 tw-px-4 tw-border tw-border-gray-300 rounded-md tw-shadow-sm tw-text-sm focus:tw-outline-none focus:tw-ring-indigo-500 focus:tw-border-indigo-500 sm:tw-text-sm'
|
||||
onChange={handleChange}
|
||||
defaultValue={value}
|
||||
>
|
||||
{options.map((o) => (
|
||||
<option value={o} key={o} selected={o === value}>
|
||||
<option value={o} key={o}>
|
||||
{o}
|
||||
</option>
|
||||
))}
|
||||
|
||||
@ -1,5 +1,7 @@
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
/* eslint-disable @typescript-eslint/no-non-null-assertion */
|
||||
import SVG from 'react-inlinesvg'
|
||||
|
||||
import PlusSVG from '#assets/plus.svg'
|
||||
import { useLayers } from '#components/Map/hooks/useLayers'
|
||||
import { useHasUserPermission } from '#components/Map/hooks/usePermissions'
|
||||
@ -31,7 +33,7 @@ export default function AddButton({
|
||||
{canAddItems() ? (
|
||||
<div className='tw-dropdown tw-dropdown-top tw-dropdown-end tw-dropdown-hover tw-z-500 tw-absolute tw-right-4 tw-bottom-4'>
|
||||
<label tabIndex={0} className='tw-z-500 tw-btn tw-btn-circle tw-shadow tw-bg-base-100'>
|
||||
<img src={PlusSVG} alt='Layers' className='tw-h-5 tw-w-5' />
|
||||
<SVG src={PlusSVG} className='tw-h-5 tw-w-5' />
|
||||
</label>
|
||||
<ul tabIndex={0} className='tw-dropdown-content tw-pr-1 tw-list-none'>
|
||||
{layers.map(
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
import { useState } from 'react'
|
||||
import SVG from 'react-inlinesvg'
|
||||
|
||||
import LayerSVG from '#assets/layer.svg'
|
||||
import { useIsLayerVisible, useToggleVisibleLayer } from '#components/Map/hooks/useFilter'
|
||||
@ -56,7 +57,7 @@ export function LayerControl() {
|
||||
setOpen(true)
|
||||
}}
|
||||
>
|
||||
<img src={LayerSVG} alt='Layers' />
|
||||
<SVG src={LayerSVG} />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@ -6,13 +6,13 @@
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-call */
|
||||
import { control } from 'leaflet'
|
||||
import { useEffect, useRef, useState } from 'react'
|
||||
import SVG from 'react-inlinesvg'
|
||||
import { useMap, useMapEvents } from 'react-leaflet'
|
||||
|
||||
import TargetSVG from '#assets/target.svg'
|
||||
|
||||
// eslint-disable-next-line import/no-unassigned-import
|
||||
import 'leaflet.locatecontrol'
|
||||
import 'leaflet.locatecontrol/dist/L.Control.Locate.css'
|
||||
|
||||
// Converts leaflet.locatecontrol to a React Component
|
||||
export const LocateControl = () => {
|
||||
@ -59,9 +59,8 @@ export const LocateControl = () => {
|
||||
{loading ? (
|
||||
<span className='tw-loading tw-loading-spinner tw-loading-md tw-mt-1'></span>
|
||||
) : (
|
||||
<img
|
||||
<SVG
|
||||
src={TargetSVG}
|
||||
alt='x'
|
||||
className='tw-mt-1 tw-p-[1px]'
|
||||
style={{ fill: `${active ? '#fc8702' : 'currentColor'}` }}
|
||||
/>
|
||||
|
||||
@ -15,6 +15,7 @@ import MagnifyingGlassIcon from '@heroicons/react/24/outline/MagnifyingGlassIcon
|
||||
import axios from 'axios'
|
||||
import { LatLng, LatLngBounds, marker } from 'leaflet'
|
||||
import { useEffect, useRef, useState } from 'react'
|
||||
import SVG from 'react-inlinesvg'
|
||||
import { useMap, useMapEvents } from 'react-leaflet'
|
||||
import { useLocation, useNavigate } from 'react-router-dom'
|
||||
|
||||
@ -169,7 +170,7 @@ export const SearchControl = () => {
|
||||
{itemsResults.slice(0, 5).map((item) => (
|
||||
<div
|
||||
key={item.id}
|
||||
className='tw-cursor-pointer hover:tw-font-bold'
|
||||
className='tw-cursor-pointer hover:tw-font-bold tw-flex tw-flex-row'
|
||||
onClick={() => {
|
||||
const marker = Object.entries(leafletRefs).find((r) => r[1].item === item)?.[1]
|
||||
.marker
|
||||
@ -182,18 +183,25 @@ export const SearchControl = () => {
|
||||
}
|
||||
}}
|
||||
>
|
||||
<div className='tw-flex tw-flex-row'>
|
||||
<img
|
||||
src={item.layer?.menuIcon}
|
||||
className='tw-text-current tw-w-5 tw-mr-2 tw-mt-0'
|
||||
{item.layer?.menuIcon ? (
|
||||
<SVG
|
||||
src={item.layer.menuIcon}
|
||||
className='tw-text-current tw-mr-2 tw-mt-0 tw-w-5'
|
||||
preProcessor={(code: string): string => {
|
||||
code = code.replace(/fill=".*?"/g, 'fill="currentColor"')
|
||||
code = code.replace(/stroke=".*?"/g, 'stroke="currentColor"')
|
||||
return code
|
||||
}}
|
||||
/>
|
||||
<div>
|
||||
<div className='tw-text-sm tw-overflow-hidden tw-text-ellipsis tw-whitespace-nowrap tw-max-w-[17rem]'>
|
||||
{item.name}
|
||||
</div>
|
||||
<div className='tw-text-xs tw-overflow-hidden tw-text-ellipsis tw-whitespace-nowrap tw-max-w-[17rem]'>
|
||||
{item.text}
|
||||
</div>
|
||||
) : (
|
||||
<div className='tw-w-5' />
|
||||
)}
|
||||
<div>
|
||||
<div className='tw-text-sm tw-overflow-hidden tw-text-ellipsis tw-whitespace-nowrap tw-max-w-[17rem]'>
|
||||
{item.name}
|
||||
</div>
|
||||
<div className='tw-text-xs tw-overflow-hidden tw-text-ellipsis tw-whitespace-nowrap tw-max-w-[17rem]'>
|
||||
{item.text}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -236,7 +244,7 @@ export const SearchControl = () => {
|
||||
hide()
|
||||
}}
|
||||
>
|
||||
<MagnifyingGlassIcon className='tw-text-current tw-mr-2 tw-mt-0 tw-w-4' />
|
||||
<MagnifyingGlassIcon className='tw-text-current tw-mr-2 tw-mt-0 tw-w-5' />
|
||||
<div>
|
||||
<div className='tw-text-sm tw-overflow-hidden tw-text-ellipsis tw-whitespace-nowrap tw-max-w-[17rem]'>
|
||||
{geo?.properties.name ? geo?.properties.name : value}
|
||||
|
||||
@ -83,18 +83,13 @@ export function ItemFormPopup(props: ItemFormPopupProps) {
|
||||
toast.error(error.toString())
|
||||
}
|
||||
if (success) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log(props.item)
|
||||
|
||||
updateItem({ ...props.item, ...formItem })
|
||||
toast.success('Item updated')
|
||||
}
|
||||
setSpinner(false)
|
||||
map.closePopup()
|
||||
} else {
|
||||
const item = items.find(
|
||||
(i) => i.user_created?.id === user?.id && i.layer?.id === props.layer.id,
|
||||
)
|
||||
const item = items.find((i) => i.user_created?.id === user?.id && i.layer === props.layer)
|
||||
|
||||
const uuid = crypto.randomUUID()
|
||||
let success = false
|
||||
|
||||
@ -12,7 +12,8 @@
|
||||
import EllipsisVerticalIcon from '@heroicons/react/16/solid/EllipsisVerticalIcon'
|
||||
import PencilIcon from '@heroicons/react/24/solid/PencilIcon'
|
||||
import TrashIcon from '@heroicons/react/24/solid/TrashIcon'
|
||||
import { useState, useEffect } from 'react'
|
||||
import { useState } from 'react'
|
||||
import SVG from 'react-inlinesvg'
|
||||
import { useNavigate } from 'react-router-dom'
|
||||
|
||||
import TargetDotSVG from '#assets/targetDot.svg'
|
||||
@ -56,10 +57,6 @@ export function HeaderView({
|
||||
|
||||
const [imageLoaded, setImageLoaded] = useState(false)
|
||||
|
||||
useEffect(() => {
|
||||
setImageLoaded(false)
|
||||
}, [item])
|
||||
|
||||
const avatar =
|
||||
item.image &&
|
||||
appState.assetsApi.url + item.image + `${big ? '?width=160&heigth=160' : '?width=80&heigth=80'}`
|
||||
@ -163,7 +160,7 @@ export function HeaderView({
|
||||
className='!tw-text-base-content tw-cursor-pointer'
|
||||
onClick={setPositionCallback}
|
||||
>
|
||||
<img src={TargetDotSVG} alt='Position' className='tw-w-5 tw-h-5' />
|
||||
<SVG src={TargetDotSVG} className='tw-w-5 tw-h-5' />
|
||||
</a>
|
||||
</li>
|
||||
)}
|
||||
|
||||
@ -1,188 +0,0 @@
|
||||
.leaflet-container {
|
||||
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-repeat: no-repeat;
|
||||
background-attachment: fixed;
|
||||
background-position: 50% 80%;
|
||||
}
|
||||
|
||||
input {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
textarea {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.leaflet-control-attribution {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.leaflet-control-locate {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.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-position: 6px 32px;
|
||||
}
|
||||
|
||||
.crosshair-cursor-enabled {
|
||||
cursor: crosshair;
|
||||
}
|
||||
|
||||
.leaflet-container {
|
||||
cursor: inherit;
|
||||
}
|
||||
|
||||
.calendar-icon {
|
||||
position: relative;
|
||||
top: -35px;
|
||||
left: 10px;
|
||||
width: 13px;
|
||||
}
|
||||
|
||||
.user-icon {
|
||||
position: relative;
|
||||
top: -36px;
|
||||
left: 10px;
|
||||
width: 13px;
|
||||
}
|
||||
|
||||
.circle-icon {
|
||||
position: relative;
|
||||
top: -33px;
|
||||
left: 10px;
|
||||
width: 13px;
|
||||
}
|
||||
|
||||
.fire-icon {
|
||||
position: relative;
|
||||
top: -36px;
|
||||
left: 10px;
|
||||
width: 13px;
|
||||
}
|
||||
|
||||
.tree-icon {
|
||||
position: relative;
|
||||
top: -38px;
|
||||
left: 4px;
|
||||
width: 24px;
|
||||
}
|
||||
|
||||
.music-icon {
|
||||
position: relative;
|
||||
top: -35px;
|
||||
left: 4px;
|
||||
width: 24px;
|
||||
}
|
||||
|
||||
.quest-icon {
|
||||
position: relative;
|
||||
top: -34px;
|
||||
left: 4px;
|
||||
width: 24px;
|
||||
}
|
||||
|
||||
.drum-icon {
|
||||
position: relative;
|
||||
top: -38px;
|
||||
left: 4px;
|
||||
width: 24px;
|
||||
}
|
||||
|
||||
|
||||
.compass-icon {
|
||||
position: relative;
|
||||
top: -36.5px;
|
||||
left: 4px;
|
||||
width: 24px;
|
||||
}
|
||||
|
||||
.group-icon {
|
||||
position: relative;
|
||||
top: -37px;
|
||||
left: 4px;
|
||||
width: 24px;
|
||||
}
|
||||
|
||||
.liebevoll-jetzt-icon{
|
||||
position: relative;
|
||||
top: -35px;
|
||||
left: 4px;
|
||||
width: 24px;
|
||||
}
|
||||
|
||||
.staff-snake-icon {
|
||||
position: relative;
|
||||
top: -35px;
|
||||
left: 4px;
|
||||
width: 24px;
|
||||
}
|
||||
|
||||
.flower-icon {
|
||||
position: relative;
|
||||
top: -35px;
|
||||
left: 4px;
|
||||
width: 24px;
|
||||
}
|
||||
|
||||
.network-icon {
|
||||
position: relative;
|
||||
top: -35px;
|
||||
left: 4px;
|
||||
width: 24px;
|
||||
}
|
||||
|
||||
.shop-icon {
|
||||
position: relative;
|
||||
top: -34px;
|
||||
left: 4px;
|
||||
width: 24px;
|
||||
}
|
||||
|
||||
.plant-icon {
|
||||
position: relative;
|
||||
top: -34px;
|
||||
left: 4px;
|
||||
width: 24px;
|
||||
}
|
||||
|
||||
.circle-dot-icon {
|
||||
position: relative;
|
||||
top: -36px;
|
||||
left: 4px;
|
||||
width: 24px;
|
||||
}
|
||||
|
||||
.leaflet-popup-scrolled {
|
||||
overflow-x: hidden;
|
||||
}
|
||||
|
||||
.leaflet-popup-content-wrapper, .leaflet-popup-tip{
|
||||
background-color: theme('colors.base-100');
|
||||
color: theme('colors.base-content');
|
||||
|
||||
}
|
||||
|
||||
.leaflet-tooltip {
|
||||
background-color: theme('colors.base-100');
|
||||
color: theme('colors.base-content');
|
||||
border-width: 0px;
|
||||
}
|
||||
|
||||
.leaflet-tooltip {
|
||||
border-radius: 1em;
|
||||
transition: opacity 500ms;
|
||||
transition-delay: 50ms;
|
||||
|
||||
}
|
||||
|
||||
.leaflet-tooltip-top::before {
|
||||
border-top-color: theme('colors.base-100');
|
||||
}
|
||||
|
||||
.marker-cluster span {
|
||||
color: #000;
|
||||
}
|
||||
@ -6,7 +6,6 @@ import { ContextWrapper } from '#components/AppShell/ContextWrapper'
|
||||
import { UtopiaMapInner } from './UtopiaMapInner'
|
||||
|
||||
import type { UtopiaMapProps } from '#types/UtopiaMapProps'
|
||||
import 'react-toastify/dist/ReactToastify.css'
|
||||
|
||||
/**
|
||||
* @category Map
|
||||
|
||||
@ -8,11 +8,9 @@
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-argument */
|
||||
import { Children, cloneElement, isValidElement, useEffect, useRef, useState } from 'react'
|
||||
import { TileLayer, useMapEvents, GeoJSON, useMap } from 'react-leaflet'
|
||||
import 'leaflet/dist/leaflet.css'
|
||||
import MarkerClusterGroup from 'react-leaflet-cluster'
|
||||
import { Outlet, useLocation } from 'react-router-dom'
|
||||
import { toast } from 'react-toastify'
|
||||
import './UtopiaMap.css'
|
||||
|
||||
import { containsUUID } from '#utils/ContainsUUID'
|
||||
|
||||
@ -77,10 +75,12 @@ export function UtopiaMapInner({
|
||||
<div>
|
||||
<TextView
|
||||
itemId=''
|
||||
rawText={'Support us building free opensource maps and help us grow 🌱☀️'}
|
||||
rawText={
|
||||
'Support us building free opensource maps for communities and help us grow 🌱☀️'
|
||||
}
|
||||
/>
|
||||
<a href='https://opencollective.com/utopia-project'>
|
||||
<div className='tw-btn tw-btn-sm tw-float-right'>Donate</div>
|
||||
<div className='tw-btn tw-btn-sm tw-float-right tw-btn-primary'>Donate</div>
|
||||
</a>
|
||||
</div>
|
||||
</>,
|
||||
|
||||
@ -1,10 +0,0 @@
|
||||
/* eslint-disable @typescript-eslint/no-unused-vars */
|
||||
|
||||
import { useMap } from 'react-leaflet'
|
||||
|
||||
export const setItemLocation = () => {
|
||||
// eslint-disable-next-line react-hooks/rules-of-hooks
|
||||
const map = useMap()
|
||||
|
||||
return <div></div>
|
||||
}
|
||||
@ -5,7 +5,6 @@ import { useEffect, useState } from 'react'
|
||||
import { useLocation, useNavigate } from 'react-router-dom'
|
||||
import { toast } from 'react-toastify'
|
||||
|
||||
import { useAppState } from '#components/AppShell/hooks/useAppState'
|
||||
import { useAuth } from '#components/Auth/useAuth'
|
||||
import { useItems, useUpdateItem, useAddItem } from '#components/Map/hooks/useItems'
|
||||
import { useLayers } from '#components/Map/hooks/useLayers'
|
||||
@ -62,7 +61,6 @@ export function ProfileForm() {
|
||||
const hasUserPermission = useHasUserPermission()
|
||||
const getItemTags = useGetItemTags()
|
||||
const items = useItems()
|
||||
const appState = useAppState()
|
||||
|
||||
const [urlParams, setUrlParams] = useState(new URLSearchParams(location.search))
|
||||
|
||||
@ -146,8 +144,12 @@ export function ProfileForm() {
|
||||
const [template, setTemplate] = useState<string>('')
|
||||
|
||||
useEffect(() => {
|
||||
setTemplate(item.layer?.itemType.template ?? appState.userType)
|
||||
}, [appState.userType, item])
|
||||
if (item.layer?.itemType.template) setTemplate(item.layer?.itemType.template)
|
||||
else {
|
||||
const userLayer = layers.find((l) => l.userProfileLayer === true)
|
||||
if (userLayer) setTemplate(userLayer.itemType.template)
|
||||
}
|
||||
}, [item, layers])
|
||||
|
||||
return (
|
||||
<>
|
||||
|
||||
@ -12,9 +12,9 @@ import { useEffect, useState } from 'react'
|
||||
import { useMap } from 'react-leaflet'
|
||||
import { useLocation, useNavigate } from 'react-router-dom'
|
||||
|
||||
import { useAppState } from '#components/AppShell/hooks/useAppState'
|
||||
import { useClusterRef } from '#components/Map/hooks/useClusterRef'
|
||||
import { useItems, useRemoveItem, useUpdateItem } from '#components/Map/hooks/useItems'
|
||||
import { useLayers } from '#components/Map/hooks/useLayers'
|
||||
import { useLeafletRefs } from '#components/Map/hooks/useLeafletRefs'
|
||||
import { useHasUserPermission } from '#components/Map/hooks/usePermissions'
|
||||
import { useSelectPosition, useSetSelectPosition } from '#components/Map/hooks/useSelectPosition'
|
||||
@ -57,7 +57,7 @@ export function ProfileView({ attestationApi }: { attestationApi?: ItemsApi<any>
|
||||
const setSelectPosition = useSetSelectPosition()
|
||||
const clusterRef = useClusterRef()
|
||||
const leafletRefs = useLeafletRefs()
|
||||
const appState = useAppState()
|
||||
const layers = useLayers()
|
||||
|
||||
const [attestations, setAttestations] = useState<any[]>([])
|
||||
|
||||
@ -162,8 +162,12 @@ export function ProfileView({ attestationApi }: { attestationApi?: ItemsApi<any>
|
||||
}, [selectPosition])
|
||||
|
||||
useEffect(() => {
|
||||
setTemplate(item?.layer?.itemType.template ?? appState.userType)
|
||||
}, [appState.userType, item])
|
||||
if (item?.layer?.itemType.template) setTemplate(item.layer.itemType.template)
|
||||
else {
|
||||
const userLayer = layers.find((l) => l.userProfileLayer === true)
|
||||
if (userLayer) setTemplate(userLayer.itemType.template)
|
||||
}
|
||||
}, [item, layers])
|
||||
|
||||
return (
|
||||
<>
|
||||
|
||||
@ -8,7 +8,6 @@ import { ReactCrop, centerCrop, makeAspectCrop } from 'react-image-crop'
|
||||
|
||||
import UserSVG from '#assets/user.svg'
|
||||
import { useAppState } from '#components/AppShell/hooks/useAppState'
|
||||
import 'react-image-crop/dist/ReactCrop.css'
|
||||
import DialogModal from '#components/Templates/DialogModal'
|
||||
|
||||
import type { Crop } from 'react-image-crop'
|
||||
|
||||
@ -5,7 +5,6 @@
|
||||
import { useCallback, useEffect, useRef, useState } from 'react'
|
||||
import { HexColorPicker } from 'react-colorful'
|
||||
|
||||
import './ColorPicker.css'
|
||||
import useClickOutside from '#components/Profile/hooks/useClickOutside'
|
||||
|
||||
// eslint-disable-next-line react/prop-types
|
||||
|
||||
@ -1,8 +1,6 @@
|
||||
import { useState } from 'react'
|
||||
import { RowsPhotoAlbum } from 'react-photo-album'
|
||||
import ReactLightbox from 'yet-another-react-lightbox'
|
||||
import 'yet-another-react-lightbox/styles.css'
|
||||
import 'react-photo-album/rows.css'
|
||||
|
||||
import { useAppState } from '#components/AppShell/hooks/useAppState'
|
||||
|
||||
|
||||
@ -51,9 +51,7 @@ export const TabsView = ({
|
||||
const items = useItems()
|
||||
const appState = useAppState()
|
||||
const getUserProfile = (id: string) => {
|
||||
return items.find(
|
||||
(i) => i.user_created?.id === id && i.layer?.itemType.name === appState.userType,
|
||||
)
|
||||
return items.find((i) => i.user_created?.id === id && i.layer?.userProfileLayer)
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
@ -152,29 +150,41 @@ export const TabsView = ({
|
||||
</div>
|
||||
</td>
|
||||
<td>
|
||||
<Link to={'/item/' + getUserProfile(a.user_created.id)?.id}>
|
||||
<div className='flex items-center gap-3'>
|
||||
<div className='tw-avatar'>
|
||||
<div className='tw-mask tw-rounded-full h-8 w-8 tw-mr-2'>
|
||||
<img
|
||||
src={
|
||||
appState.assetsApi.url +
|
||||
getUserProfile(a.user_created.id)?.image
|
||||
}
|
||||
alt='Avatar'
|
||||
/>
|
||||
{getUserProfile(a.user_created.id) ? (
|
||||
<Link to={'/item/' + getUserProfile(a.user_created.id)?.id}>
|
||||
<div className='flex items-center gap-3'>
|
||||
<div className='tw-avatar'>
|
||||
<div className='tw-mask tw-rounded-full tw-h-8 tw-w-8 tw-mr-2'>
|
||||
{getUserProfile(a.user_created.id)?.image && (
|
||||
<img
|
||||
src={
|
||||
appState.assetsApi.url +
|
||||
getUserProfile(a.user_created.id)?.image
|
||||
}
|
||||
alt='Avatar'
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<div className='font-bold'>
|
||||
{getUserProfile(a.user_created.id)?.name ??
|
||||
a.user_created.first_name}{' '}
|
||||
</div>
|
||||
<div className='tw-text-xs opacity-50 tw-text-zinc-500'>
|
||||
{timeAgo(a.date_created)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<div className='font-bold'>
|
||||
{getUserProfile(a.user_created.id)?.name}
|
||||
</div>
|
||||
<div className='tw-text-xs opacity-50 tw-text-zinc-500'>
|
||||
{timeAgo(a.date_created)}
|
||||
</div>
|
||||
</Link>
|
||||
) : (
|
||||
<div>
|
||||
<div className='font-bold'>{a.user_created.first_name} </div>
|
||||
<div className='tw-text-xs opacity-50 tw-text-zinc-500'>
|
||||
{timeAgo(a.date_created)}
|
||||
</div>
|
||||
</div>
|
||||
</Link>
|
||||
)}
|
||||
</td>
|
||||
</tr>
|
||||
))}
|
||||
|
||||
@ -1,6 +1,3 @@
|
||||
/* eslint-disable @typescript-eslint/no-non-null-assertion */
|
||||
/* eslint-disable @typescript-eslint/no-unnecessary-condition */
|
||||
/* eslint-disable @typescript-eslint/prefer-optional-chain */
|
||||
import { useState } from 'react'
|
||||
|
||||
import { timeAgo } from '#utils/TimeAgo'
|
||||
@ -18,7 +15,8 @@ export const DateUserInfo = ({ item }: { item: Item }) => {
|
||||
<p
|
||||
className={'tw-italic tw-min-h-[21px] !tw-my-0 tw-text-gray-500'}
|
||||
onClick={() => setInfoExpanded(false)}
|
||||
>{`${item.date_updated && item.date_updated !== item.date_created ? 'updated' : 'posted'} ${item && item.user_created && item.user_created.first_name ? `by ${item.user_created.first_name}` : ''} ${item.date_updated ? timeAgo(item.date_updated) : timeAgo(item.date_created!)}`}</p>
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
>{`${item.date_updated && item.date_updated !== item.date_created ? 'updated' : 'posted'} ${item.user_created?.first_name ? `by ${item.user_created.first_name}` : ''} ${item.date_updated ? timeAgo(item.date_updated) : timeAgo(item.date_created!)}`}</p>
|
||||
) : (
|
||||
<p
|
||||
className='!tw-my-0 tw-min-h-[21px] tw-font-bold tw-cursor-pointer tw-text-gray-500'
|
||||
|
||||
@ -1,4 +1,3 @@
|
||||
/* eslint-disable @typescript-eslint/restrict-template-expressions */
|
||||
import { useState } from 'react'
|
||||
|
||||
interface Props {
|
||||
@ -110,7 +109,7 @@ export const EmojiPicker = ({
|
||||
<button
|
||||
key={emoji}
|
||||
onClick={() => selectEmoji(emoji)}
|
||||
className={`tw-cursor-pointer tw-text-2xl tw-p-2 hover:tw-bg-base-200 tw-rounded-md ${emoji === selectedEmoji && 'tw-bg-base-300'}`}
|
||||
className={`tw-cursor-pointer tw-text-2xl tw-p-2 hover:tw-bg-base-200 tw-rounded-md ${emoji === selectedEmoji ? 'tw-bg-base-300' : ''}`}
|
||||
>
|
||||
{emoji}
|
||||
</button>
|
||||
@ -121,7 +120,7 @@ export const EmojiPicker = ({
|
||||
{shapes.map((shape) => (
|
||||
<div
|
||||
key={shape}
|
||||
className={`tw-cursor-pointer hover:tw-bg-base-200 tw-rounded-md tw-p-2 ${shape === selectedShape && 'tw-bg-base-300'}`}
|
||||
className={`tw-cursor-pointer hover:tw-bg-base-200 tw-rounded-md tw-p-2 ${shape === selectedShape ? 'tw-bg-base-300' : ''}`}
|
||||
onClick={() => selectShape(shape)}
|
||||
>
|
||||
<div className={`tw-h-12 tw-mask tw-mask-${shape} tw-bg-neutral-content`}></div>
|
||||
@ -133,7 +132,7 @@ export const EmojiPicker = ({
|
||||
{colors.map((color) => (
|
||||
<div
|
||||
key={color}
|
||||
className={`tw-cursor-pointer hover:tw-bg-base-200 tw-rounded-md tw-p-2 tw-flex tw-justify-center tw-items-center ${color === selectedColor && 'tw-bg-base-300'}`}
|
||||
className={`tw-cursor-pointer hover:tw-bg-base-200 tw-rounded-md tw-p-2 tw-flex tw-justify-center tw-items-center ${color === selectedColor ? 'tw-bg-base-300' : ''}`}
|
||||
onClick={() => selectColor(color)}
|
||||
>
|
||||
<div className={`tw-h-8 tw-w-8 tw-rounded-full tw-bg-[${color}]`}></div>
|
||||
|
||||
@ -1,8 +1,3 @@
|
||||
/* eslint-disable @typescript-eslint/no-unnecessary-condition */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-call */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-return */
|
||||
/* eslint-disable @typescript-eslint/restrict-template-expressions */
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
import { useNavigate } from 'react-router-dom'
|
||||
|
||||
import { StartEndView, TextView } from '#components/Map'
|
||||
@ -22,7 +17,7 @@ export const ItemCard = ({
|
||||
i: Item
|
||||
loading: boolean
|
||||
url: string
|
||||
deleteCallback: any
|
||||
deleteCallback: (item: Item) => void
|
||||
}) => {
|
||||
const navigate = useNavigate()
|
||||
const windowDimensions = useWindowDimensions()
|
||||
@ -34,8 +29,8 @@ export const ItemCard = ({
|
||||
// We could have an onClick callback instead
|
||||
const params = new URLSearchParams(window.location.search)
|
||||
if (windowDimensions.width < 786 && i.position)
|
||||
navigate('/' + i.id + `${params ? `?${params}` : ''}`)
|
||||
else navigate(url + i.id + `${params ? `?${params}` : ''}`)
|
||||
navigate('/' + i.id + `${params.size > 0 ? `?${params.toString()}` : ''}`)
|
||||
else navigate(url + i.id + `${params.size > 0 ? `?${params.toString()}` : ''}`)
|
||||
}}
|
||||
>
|
||||
<HeaderView
|
||||
|
||||
@ -1,4 +1,3 @@
|
||||
/* eslint-disable @typescript-eslint/restrict-template-expressions */
|
||||
import { DomEvent } from 'leaflet'
|
||||
import { createRef, useEffect } from 'react'
|
||||
import { useNavigate } from 'react-router-dom'
|
||||
@ -39,14 +38,14 @@ export function MapOverlayPage({
|
||||
}, [overlayRef, backdropRef])
|
||||
|
||||
return (
|
||||
<div className={`tw-absolute tw-h-full tw-w-full tw-m-auto ${backdrop && 'tw-z-[2000]'}`}>
|
||||
<div className={`tw-absolute tw-h-full tw-w-full tw-m-auto ${backdrop ? 'tw-z-[2000]' : ''}`}>
|
||||
<div
|
||||
ref={backdropRef}
|
||||
className={`${backdrop && 'tw-backdrop-brightness-75'} tw-h-full tw-w-full tw-grid tw-place-items-center tw-m-auto`}
|
||||
className={`${backdrop ? 'tw-backdrop-brightness-75' : ''} tw-h-full tw-w-full tw-grid tw-place-items-center tw-m-auto`}
|
||||
>
|
||||
<div
|
||||
ref={overlayRef}
|
||||
className={`${card && 'tw-card tw-card-body'} tw-shadow-xl tw-bg-base-100 tw-p-6 ${className && className} ${!backdrop && 'tw-z-[2000]'} tw-absolute tw-top-0 tw-bottom-0 tw-right-0 tw-left-0 tw-m-auto`}
|
||||
className={`${card ? 'tw-card tw-card-body' : ''} tw-shadow-xl tw-bg-base-100 tw-p-6 ${className ?? ''} ${backdrop ? '' : 'tw-z-[2000]'} tw-absolute tw-top-0 tw-bottom-0 tw-right-0 tw-left-0 tw-m-auto`}
|
||||
>
|
||||
{children}
|
||||
<button
|
||||
|
||||
@ -1,10 +1,9 @@
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
/* eslint-disable @typescript-eslint/prefer-optional-chain */
|
||||
/* eslint-disable @typescript-eslint/no-misused-promises */
|
||||
/* eslint-disable @typescript-eslint/no-non-null-assertion */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-call */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-argument */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-call */
|
||||
import { useEffect, useRef, useState } from 'react'
|
||||
import { toast } from 'react-toastify'
|
||||
|
||||
@ -73,16 +72,16 @@ export const OverlayItemsIndexPage = ({
|
||||
|
||||
const layer = layers.find((l) => l.name === layerName)
|
||||
|
||||
const submitNewItem = async (evt: any) => {
|
||||
const submitNewItem = async (evt: React.FormEvent<HTMLFormElement>) => {
|
||||
evt.preventDefault()
|
||||
const formItem: Item = {} as Item
|
||||
Array.from(evt.target).forEach((input: HTMLInputElement) => {
|
||||
Array.from(evt.target as any).forEach((input: HTMLInputElement) => {
|
||||
if (input.name) {
|
||||
formItem[input.name] = input.value
|
||||
}
|
||||
})
|
||||
setLoading(true)
|
||||
formItem.text &&
|
||||
if (formItem.text) {
|
||||
formItem.text
|
||||
.toLocaleLowerCase()
|
||||
.match(hashTagRegex)
|
||||
@ -92,6 +91,7 @@ export const OverlayItemsIndexPage = ({
|
||||
}
|
||||
return null
|
||||
})
|
||||
}
|
||||
const uuid = crypto.randomUUID()
|
||||
let success = false
|
||||
try {
|
||||
@ -109,7 +109,7 @@ export const OverlayItemsIndexPage = ({
|
||||
setAddItemPopupType('')
|
||||
}
|
||||
|
||||
const deleteItem = async (item) => {
|
||||
const deleteItem = async (item: Item) => {
|
||||
setLoading(true)
|
||||
let success = false
|
||||
try {
|
||||
|
||||
@ -1,5 +1,3 @@
|
||||
/* eslint-disable @typescript-eslint/restrict-template-expressions */
|
||||
|
||||
import { decodeTag } from '#utils/FormatTags'
|
||||
|
||||
import type { Tag } from '#types/Tag'
|
||||
@ -21,7 +19,7 @@ export const TagView = ({
|
||||
<div
|
||||
key={tag.name}
|
||||
onClick={onClick}
|
||||
className={`tw-flex tw-items-center tw-flex-row tw-rounded-2xl tw-text-white tw-p-2 tw-px-4 tw-shadow-xl tw-card tw-mt-3 tw-mr-4 tw-cursor-pointer tw-w-fit ${heighlight && 'tw-border-4 tw-border-base-200 tw-shadow-lg'}`}
|
||||
className={`tw-flex tw-items-center tw-flex-row tw-rounded-2xl tw-text-white tw-p-2 tw-px-4 tw-shadow-xl tw-card tw-mt-3 tw-mr-4 tw-cursor-pointer tw-w-fit ${heighlight ? 'tw-border-4 tw-border-base-200 tw-shadow-lg' : ''}`}
|
||||
style={{ backgroundColor: tag.color ? tag.color : '#666' }}
|
||||
>
|
||||
<b>{decodeTag(tag.name)}</b>
|
||||
|
||||
@ -1,5 +1,3 @@
|
||||
/* eslint-disable @typescript-eslint/prefer-nullish-coalescing */
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
import Subtitle from '#components/Typography/Subtitle'
|
||||
|
||||
interface TitleCardProps {
|
||||
@ -8,7 +6,7 @@ interface TitleCardProps {
|
||||
children?: React.ReactNode
|
||||
topMargin?: string
|
||||
className?: string
|
||||
TopSideButtons?: any
|
||||
TopSideButtons?: React.ReactNode
|
||||
}
|
||||
|
||||
/**
|
||||
@ -26,9 +24,9 @@ export function TitleCard({
|
||||
<div
|
||||
className={
|
||||
'tw-card tw-w-full tw-p-6 tw-bg-base-100 tw-shadow-xl tw-h-fit tw-mb-4 ' +
|
||||
(className || '') +
|
||||
(className ?? '') +
|
||||
' ' +
|
||||
(topMargin || 'tw-mt-6')
|
||||
(topMargin ?? 'tw-mt-6')
|
||||
}
|
||||
>
|
||||
{!hideTitle && (
|
||||
|
||||
@ -1,7 +1,4 @@
|
||||
/* eslint-disable @typescript-eslint/restrict-template-expressions */
|
||||
|
||||
// eslint-disable-next-line react/prop-types
|
||||
function ErrorText({ styleClass, children }) {
|
||||
function ErrorText({ styleClass, children }: { styleClass: string; children: React.ReactNode }) {
|
||||
return <p className={`tw-text-center tw-text-error ${styleClass}`}>{children}</p>
|
||||
}
|
||||
|
||||
|
||||
@ -1,8 +1,3 @@
|
||||
/* eslint-disable @typescript-eslint/restrict-template-expressions */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-return */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-call */
|
||||
const goldenRatioConjugate = 0.618033988749895
|
||||
|
||||
export const randomColor = () => {
|
||||
@ -53,7 +48,7 @@ function hsvToHex(h: number, s: number, v: number) {
|
||||
return rgbToHex(Math.round(r * 255), Math.round(g * 255), Math.round(b * 255))
|
||||
}
|
||||
|
||||
const rgbToHex = (r, g, b) =>
|
||||
const rgbToHex = (r: number, g: number, b: number) =>
|
||||
'#' +
|
||||
[r, g, b]
|
||||
.map((x) => {
|
||||
|
||||
@ -1,20 +1,20 @@
|
||||
.picker {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.swatch {
|
||||
}
|
||||
|
||||
.swatch {
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
border-radius: 8px;
|
||||
border: 3px solid #fff;
|
||||
box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.1), inset 0 0 0 1px rgba(0, 0, 0, 0.1);
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.popover {
|
||||
}
|
||||
|
||||
.popover {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 36px;
|
||||
border-radius: 9px;
|
||||
box-shadow: 0 6px 12px rgba(0, 0, 0, 0.15);
|
||||
}
|
||||
}
|
||||
18
src/assets/css/custom-file-upload.css
Normal file
18
src/assets/css/custom-file-upload.css
Normal file
@ -0,0 +1,18 @@
|
||||
.custom-file-upload {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
input[type="file"] {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.custom-file-upload:hover .button {
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
.custom-file-upload .button {
|
||||
transition: .5s ease;
|
||||
opacity: 0;
|
||||
position: absolute;
|
||||
transform: translate(8px, 8px);
|
||||
}
|
||||
51
src/assets/css/leaflet.css
Normal file
51
src/assets/css/leaflet.css
Normal file
@ -0,0 +1,51 @@
|
||||
.leaflet-control-attribution {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.leaflet-control-locate {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.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-position: 6px 32px;
|
||||
}
|
||||
|
||||
.leaflet-container {
|
||||
cursor: inherit;
|
||||
}
|
||||
|
||||
.leaflet-popup-scrolled {
|
||||
overflow-x: hidden;
|
||||
}
|
||||
|
||||
.leaflet-popup-content-wrapper, .leaflet-popup-tip{
|
||||
background-color: theme('colors.base-100');
|
||||
color: theme('colors.base-content');
|
||||
|
||||
}
|
||||
|
||||
.leaflet-tooltip {
|
||||
background-color: theme('colors.base-100');
|
||||
color: theme('colors.base-content');
|
||||
border-width: 0px;
|
||||
}
|
||||
|
||||
.leaflet-tooltip {
|
||||
border-radius: 1em;
|
||||
transition: opacity 500ms;
|
||||
transition-delay: 50ms;
|
||||
|
||||
}
|
||||
|
||||
.leaflet-tooltip-top::before {
|
||||
border-top-color: theme('colors.base-100');
|
||||
}
|
||||
|
||||
.leaflet-container {
|
||||
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-repeat: no-repeat;
|
||||
background-attachment: fixed;
|
||||
background-position: 50% 80%;
|
||||
}
|
||||
118
src/assets/css/marker-icons.css
Normal file
118
src/assets/css/marker-icons.css
Normal file
@ -0,0 +1,118 @@
|
||||
.calendar-icon {
|
||||
position: relative;
|
||||
top: -35px;
|
||||
left: 10px;
|
||||
width: 13px;
|
||||
}
|
||||
|
||||
.user-icon {
|
||||
position: relative;
|
||||
top: -36px;
|
||||
left: 10px;
|
||||
width: 13px;
|
||||
}
|
||||
|
||||
.circle-icon {
|
||||
position: relative;
|
||||
top: -33px;
|
||||
left: 10px;
|
||||
width: 13px;
|
||||
}
|
||||
|
||||
.fire-icon {
|
||||
position: relative;
|
||||
top: -36px;
|
||||
left: 10px;
|
||||
width: 13px;
|
||||
}
|
||||
|
||||
.tree-icon {
|
||||
position: relative;
|
||||
top: -38px;
|
||||
left: 4px;
|
||||
width: 24px;
|
||||
}
|
||||
|
||||
.music-icon {
|
||||
position: relative;
|
||||
top: -35px;
|
||||
left: 4px;
|
||||
width: 24px;
|
||||
}
|
||||
|
||||
.quest-icon {
|
||||
position: relative;
|
||||
top: -34px;
|
||||
left: 4px;
|
||||
width: 24px;
|
||||
}
|
||||
|
||||
.drum-icon {
|
||||
position: relative;
|
||||
top: -38px;
|
||||
left: 4px;
|
||||
width: 24px;
|
||||
}
|
||||
|
||||
.compass-icon {
|
||||
position: relative;
|
||||
top: -36.5px;
|
||||
left: 4px;
|
||||
width: 24px;
|
||||
}
|
||||
|
||||
.group-icon {
|
||||
position: relative;
|
||||
top: -37px;
|
||||
left: 4px;
|
||||
width: 24px;
|
||||
}
|
||||
|
||||
.liebevoll-jetzt-icon{
|
||||
position: relative;
|
||||
top: -35px;
|
||||
left: 4px;
|
||||
width: 24px;
|
||||
}
|
||||
|
||||
.staff-snake-icon {
|
||||
position: relative;
|
||||
top: -35px;
|
||||
left: 4px;
|
||||
width: 24px;
|
||||
}
|
||||
|
||||
.flower-icon {
|
||||
position: relative;
|
||||
top: -35px;
|
||||
left: 4px;
|
||||
width: 24px;
|
||||
}
|
||||
|
||||
.network-icon {
|
||||
position: relative;
|
||||
top: -35px;
|
||||
left: 4px;
|
||||
width: 24px;
|
||||
}
|
||||
|
||||
.shop-icon {
|
||||
position: relative;
|
||||
top: -34px;
|
||||
left: 4px;
|
||||
width: 24px;
|
||||
}
|
||||
|
||||
.plant-icon {
|
||||
position: relative;
|
||||
top: -34px;
|
||||
left: 4px;
|
||||
width: 24px;
|
||||
}
|
||||
|
||||
.circle-dot-icon {
|
||||
position: relative;
|
||||
top: -36px;
|
||||
left: 4px;
|
||||
width: 24px;
|
||||
}
|
||||
27
src/assets/css/masonry.css
Normal file
27
src/assets/css/masonry.css
Normal file
@ -0,0 +1,27 @@
|
||||
.masonry {
|
||||
column-count: 1;
|
||||
column-gap: 1.5rem;
|
||||
}
|
||||
|
||||
.masonry-item {
|
||||
break-inside: avoid;
|
||||
margin-bottom: 1.5rem;
|
||||
}
|
||||
|
||||
@media (min-width: 640px) {
|
||||
.masonry {
|
||||
column-count: 2;
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 1024px) {
|
||||
.masonry {
|
||||
column-count: 3;
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 1536px) {
|
||||
.masonry {
|
||||
column-count: 4;
|
||||
}
|
||||
}
|
||||
23
src/assets/css/misc.css
Normal file
23
src/assets/css/misc.css
Normal file
@ -0,0 +1,23 @@
|
||||
.fade {
|
||||
mask-image: linear-gradient(180deg, transparent, #000 1%, #000 99%, transparent);
|
||||
}
|
||||
|
||||
.placeholder-center::placeholder {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
input {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
textarea {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.crosshair-cursor-enabled {
|
||||
cursor: crosshair;
|
||||
}
|
||||
|
||||
.marker-cluster span {
|
||||
color: #000;
|
||||
}
|
||||
24
src/assets/css/tailwind.css
Normal file
24
src/assets/css/tailwind.css
Normal file
@ -0,0 +1,24 @@
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
|
||||
.tw-modal {
|
||||
z-index: 1200 !important;
|
||||
}
|
||||
|
||||
.tw-menu li a {
|
||||
border-radius: 10px;
|
||||
}
|
||||
|
||||
.tw-modal {
|
||||
z-index: 1200 !important;
|
||||
max-height: 100dvh;
|
||||
}
|
||||
|
||||
.tw-modal-box {
|
||||
max-height: calc(100dvh - 2em);
|
||||
}
|
||||
|
||||
.tw-tab-content .container {
|
||||
height: 100%;
|
||||
}
|
||||
26
src/assets/css/toastify.css
Normal file
26
src/assets/css/toastify.css
Normal file
@ -0,0 +1,26 @@
|
||||
:root {
|
||||
--toastify-color-info: theme('colors.info');
|
||||
--toastify-color-success: theme('colors.success');
|
||||
--toastify-color-warning: theme('colors.warning');
|
||||
--toastify-color-error: theme('colors.error');
|
||||
}
|
||||
|
||||
.Toastify__toast {
|
||||
border-radius: 1rem;
|
||||
--tw-shadow: 0 20px 25px -5px rgb(0 0 0 / 0.1), 0 8px 10px -6px rgb(0 0 0 / 0.1);
|
||||
--tw-shadow-colored: 0 20px 25px -5px var(--tw-shadow-color), 0 8px 10px -6px var(--tw-shadow-color);
|
||||
box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow);
|
||||
margin-left: 1rem;
|
||||
margin-right: 1rem;
|
||||
margin-bottom: 1rem;
|
||||
background-color: theme('colors.base-100');
|
||||
color: theme('colors.base-content');
|
||||
}
|
||||
|
||||
.Toastify__toast-container {
|
||||
z-index: 2001 !important;
|
||||
}
|
||||
|
||||
.Toastify__toast-container--top-right {
|
||||
top: 4.75em !important;
|
||||
}
|
||||
@ -5,4 +5,4 @@
|
||||
xmlns='http://www.w3.org/2000/svg'
|
||||
>
|
||||
<path d='M30 14.75h-2.824c-0.608-5.219-4.707-9.318-9.874-9.921l-0.053-0.005v-2.824c0-0.69-0.56-1.25-1.25-1.25s-1.25 0.56-1.25 1.25v0 2.824c-5.219 0.608-9.318 4.707-9.921 9.874l-0.005 0.053h-2.824c-0.69 0-1.25 0.56-1.25 1.25s0.56 1.25 1.25 1.25v0h2.824c0.608 5.219 4.707 9.318 9.874 9.921l0.053 0.005v2.824c0 0.69 0.56 1.25 1.25 1.25s1.25-0.56 1.25-1.25v0-2.824c5.219-0.608 9.318-4.707 9.921-9.874l0.005-0.053h2.824c0.69 0 1.25-0.56 1.25-1.25s-0.56-1.25-1.25-1.25v0zM17.25 24.624v-2.624c0-0.69-0.56-1.25-1.25-1.25s-1.25 0.56-1.25 1.25v0 2.624c-3.821-0.57-6.803-3.553-7.368-7.326l-0.006-0.048h2.624c0.69 0 1.25-0.56 1.25-1.25s-0.56-1.25-1.25-1.25v0h-2.624c0.57-3.821 3.553-6.804 7.326-7.368l0.048-0.006v2.624c0 0.69 0.56 1.25 1.25 1.25s1.25-0.56 1.25-1.25v0-2.624c3.821 0.57 6.803 3.553 7.368 7.326l0.006 0.048h-2.624c-0.69 0-1.25 0.56-1.25 1.25s0.56 1.25 1.25 1.25v0h2.624c-0.571 3.821-3.553 6.803-7.326 7.368l-0.048 0.006z'></path>
|
||||
</svg>
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 1.0 KiB After Width: | Height: | Size: 1.0 KiB |
15
src/css.tsx
Normal file
15
src/css.tsx
Normal file
@ -0,0 +1,15 @@
|
||||
import 'leaflet/dist/leaflet.css'
|
||||
import 'leaflet.locatecontrol/dist/L.Control.Locate.css'
|
||||
import 'react-image-crop/dist/ReactCrop.css'
|
||||
import 'react-photo-album/rows.css'
|
||||
import 'react-toastify/dist/ReactToastify.css'
|
||||
import 'yet-another-react-lightbox/styles.css'
|
||||
|
||||
import '#assets/css/tailwind.css'
|
||||
import '#assets/css/masonry.css'
|
||||
import '#assets/css/toastify.css'
|
||||
import '#assets/css/custom-file-upload.css'
|
||||
import '#assets/css/misc.css'
|
||||
import '#assets/css/marker-icons.css'
|
||||
import '#assets/css/leaflet.css'
|
||||
import '#assets/css/color-picker.css'
|
||||
105
src/index.css
105
src/index.css
@ -1,105 +0,0 @@
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
|
||||
.fade {
|
||||
mask-image: linear-gradient(180deg, transparent, #000 1%, #000 99%, transparent);
|
||||
}
|
||||
|
||||
.tw-modal {
|
||||
z-index: 1200 !important;
|
||||
}
|
||||
|
||||
.tw-menu li a {
|
||||
border-radius: 10px;
|
||||
}
|
||||
|
||||
.tw-modal {
|
||||
z-index: 1200 !important;
|
||||
max-height: 100dvh;
|
||||
}
|
||||
|
||||
.tw-modal-box {
|
||||
max-height: calc(100dvh - 2em);
|
||||
}
|
||||
|
||||
.Toastify__toast {
|
||||
border-radius: 1rem;
|
||||
--tw-shadow: 0 20px 25px -5px rgb(0 0 0 / 0.1), 0 8px 10px -6px rgb(0 0 0 / 0.1);
|
||||
--tw-shadow-colored: 0 20px 25px -5px var(--tw-shadow-color), 0 8px 10px -6px var(--tw-shadow-color);
|
||||
box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow);
|
||||
margin-left: 1rem;
|
||||
margin-right: 1rem;
|
||||
margin-bottom: 1rem;
|
||||
background-color: theme('colors.base-100');
|
||||
color: theme('colors.base-content');
|
||||
}
|
||||
|
||||
.Toastify__toast-container {
|
||||
z-index: 2001 !important;
|
||||
}
|
||||
|
||||
.Toastify__toast-container--top-right {
|
||||
top: 4.75em !important;
|
||||
}
|
||||
|
||||
:root {
|
||||
|
||||
--toastify-color-info: theme('colors.info');
|
||||
--toastify-color-success: theme('colors.success');
|
||||
--toastify-color-warning: theme('colors.warning');
|
||||
--toastify-color-error: theme('colors.error');
|
||||
|
||||
}
|
||||
|
||||
.placeholder-center::placeholder {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.custom-file-upload {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
input[type="file"] {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.custom-file-upload:hover .button {
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
.custom-file-upload .button {
|
||||
transition: .5s ease;
|
||||
opacity: 0;
|
||||
position: absolute;
|
||||
transform: translate(8px, 8px);
|
||||
|
||||
}
|
||||
|
||||
.tw-tab-content .container {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.masonry {
|
||||
column-count: 1;
|
||||
column-gap: 1.5rem;
|
||||
}
|
||||
.masonry-item {
|
||||
break-inside: avoid;
|
||||
margin-bottom: 1.5rem;
|
||||
}
|
||||
@media (min-width: 640px) {
|
||||
.masonry {
|
||||
column-count: 2;
|
||||
}
|
||||
}
|
||||
@media (min-width: 1024px) {
|
||||
.masonry {
|
||||
column-count: 3;
|
||||
}
|
||||
}
|
||||
@media (min-width: 1536px) {
|
||||
.masonry {
|
||||
column-count: 4;
|
||||
}
|
||||
}
|
||||
@ -1,4 +1,5 @@
|
||||
import './index.css'
|
||||
// eslint-disable-next-line import/no-unassigned-import
|
||||
import './css'
|
||||
|
||||
export * from './Components/Map'
|
||||
export * from './Components/AppShell'
|
||||
|
||||
@ -64,8 +64,8 @@ module.exports = {
|
||||
pulseGrow: 'pulseGrow 2s ease-in-out infinite',
|
||||
},
|
||||
},
|
||||
// eslint-disable-next-line import/no-commonjs, import/extensions
|
||||
plugins: [require('daisyui'), require('tw-elements/dist/plugin.cjs')],
|
||||
// eslint-disable-next-line import/no-commonjs
|
||||
plugins: [require('daisyui')],
|
||||
daisyui: {
|
||||
themes: [
|
||||
'light',
|
||||
|
||||
@ -16,8 +16,8 @@ export default defineConfig({
|
||||
reporter: ['html', 'json-summary'],
|
||||
thresholds: {
|
||||
lines: 1,
|
||||
functions: 59,
|
||||
branches: 62,
|
||||
functions: 56,
|
||||
branches: 58,
|
||||
statements: 1,
|
||||
},
|
||||
},
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user