merged main

This commit is contained in:
Anton Tranelis 2025-04-22 01:12:15 +01:00
commit 6977b5c76f
7 changed files with 200 additions and 19 deletions

View File

@ -7,21 +7,46 @@ import { useSetPermissionData, useSetPermissionApi, useSetAdminRole } from './ho
import type { ItemsApi } from '#types/ItemsApi'
import type { Permission } from '#types/Permission'
/**
* @category Types
*/
export interface PermissionsProps {
data?: Permission[]
api?: ItemsApi<Permission>
adminRole?: string
}
export type { Permission } from '#types/Permission'
export type { ItemsApi } from '#types/ItemsApi'
/**
* This Components injects Permissions comming from an {@link ItemsApi | `API`}
* ```tsx
* <Permissions api={itemsApiInstance} adminRole="8141dee8-8e10-48d0-baf1-680aea271298" />
* ```
* or from on {@link Permission| `Array`}
* ```tsx
* <Permissions data={permissions} adminRole="8141dee8-8e10-48d0-baf1-680aea271298" />
* ```
* Can be child of {@link AppShell | `AppShell`}
* ```tsx
* <AppShell>
* ...
* <Permissions api={itemsApiInstance} adminRole="8141dee8-8e10-48d0-baf1-680aea271298" />
* </AppShell>
* ```
* Or child of {@link UtopiaMap | `UtopiaMap`}
* ```tsx
* <UtopiaMap>
* ...
* <Permissions api={itemsApiInstance} adminRole="8141dee8-8e10-48d0-baf1-680aea271298" />
* </UtopiaMap>
* ```
* @category Map
*/
export function Permissions({ data, api, adminRole }: PermissionsProps) {
export function Permissions({
data,
api,
adminRole,
}: {
/** Array with all the permissions inside */
data?: Permission[]
/** API to fetch all the permissions from a server */
api?: ItemsApi<Permission>
/** UUID of the admin role which has always all the permissions */
adminRole?: string
}) {
const setPermissionData = useSetPermissionData()
const setPermissionApi = useSetPermissionApi()
const setAdminRole = useSetAdminRole()

View File

@ -8,6 +8,28 @@ import type { ItemsApi } from '#types/ItemsApi'
import type { Tag } from '#types/Tag'
/**
* This Components injects Tags comming from an {@link ItemsApi | `API`}
* ```tsx
* <Tags api={tagsApi} />
* ```
* or from on {@link Tag| `Array`}
* ```tsx
* <Tags data={tags} />
* ```
* Can be child of {@link AppShell | `AppShell`}
* ```tsx
* <AppShell>
* ...
* <Tags api={tagsApi} />
* </AppShell>
* ```
* Or child of {@link UtopiaMap | `UtopiaMap`}
* ```tsx
* <UtopiaMap>
* ...
* <Tags api={tagsApi} />
* </UtopiaMap>
* ```
* @category Map
*/
export function Tags({ data, api }: { data?: Tag[]; api?: ItemsApi<Tag> }) {

View File

@ -5,9 +5,40 @@ import { ContextWrapper } from '#components/AppShell/ContextWrapper'
import { UtopiaMapInner } from './UtopiaMapInner'
import type { UtopiaMapProps } from '#types/UtopiaMapProps'
import type { GeoJsonObject } from 'geojson'
/**
* This component creates the map.
* ```tsx
* <UtopiaMap center={[50.6, 9.5]} zoom={5} height="100dvh" width="100dvw" />
* ```
* You can define its {@link Layer | `Layers`} as supcomponents.
* ```tsx
* <UtopiaMap center={[50.6, 15.5]} zoom={5} height="100dvh" width="100dvw">
* <Layer
* name="events"
* markerIcon="calendar"
* markerShape="square"
* markerDefaultColor="#700"
* data={events}
* />
* <Layer
* name="places"
* markerIcon="point"
* markerShape="circle"
* markerDefaultColor="#007"
* data={places}
* />
* </UtopiaMap>
* ```
* You can also pass {@link Tags | `Tags`} or {@link Permissions | `Permissions`} as subcomponents.
* ```tsx
* <UtopiaMap center={[50.6, 15.5]} zoom={5} height="100dvh" width="100dvw">
* ...
* <Tags data={tags} />
* <Permissions data={permissions} />
* </UtopiaMap>
* ```
* @category Map
*/
function UtopiaMap({
@ -22,9 +53,33 @@ function UtopiaMap({
showLayerControl = true,
showThemeControl = false,
defaultTheme,
infoText,
donationWidget,
}: UtopiaMapProps) {
}: {
/** height of the map (default '500px') */
height?: string
/** width of the map (default '100%') */
width?: string
/** initial centered position of the map (default [50.6, 9.5]) */
center?: [number, number]
/** initial zoom level of the map (default 10) */
zoom?: number
/** React child-components */
children?: React.ReactNode
/** GeoJSON to display on the map */
geo?: GeoJsonObject
/** show the filter control widget (default false) */
showFilterControl?: boolean
/** show the gratitude control widget (default false) */
showLayerControl?: boolean
/** show the layer control widget (default true) */
showGratitudeControl?: boolean
/** show a widget to switch the theme */
showThemeControl: boolean
/** the defaut theme */
defaultTheme: string
/** ask to donate to the Utopia Project OpenCollective campaign (default false) */
donationWidget?: boolean
}) {
return (
<ContextWrapper>
<MapContainer
@ -39,7 +94,6 @@ function UtopiaMap({
showFilterControl={showFilterControl}
showGratitudeControl={showGratitudeControl}
showLayerControl={showLayerControl}
infoText={infoText}
donationWidget={donationWidget}
showThemeControl={showThemeControl}
defaultTheme={defaultTheme}

View File

@ -36,8 +36,7 @@ import { TextView } from './Subcomponents/ItemPopupComponents/TextView'
import { SelectPosition } from './Subcomponents/SelectPosition'
import type { ItemFormPopupProps } from '#types/ItemFormPopupProps'
import type { UtopiaMapProps } from '#types/UtopiaMapProps'
import type { Feature, Geometry as GeoJSONGeometry } from 'geojson'
import type { Feature, Geometry as GeoJSONGeometry, GeoJsonObject } from 'geojson'
export function UtopiaMapInner({
children,
@ -48,7 +47,16 @@ export function UtopiaMapInner({
showThemeControl = false,
defaultTheme = '',
donationWidget,
}: UtopiaMapProps) {
}: {
children?: React.ReactNode
geo?: GeoJsonObject
showFilterControl?: boolean
showLayerControl?: boolean
showGratitudeControl?: boolean
donationWidget?: boolean
showThemeControl?: boolean
defaultTheme?: string
}) {
const selectNewItemPosition = useSelectPosition()
const setSelectNewItemPosition = useSetSelectPosition()
const setClusterRef = useSetClusterRef()

View File

@ -0,0 +1,44 @@
import { describe, it, expect, vi } from 'vitest'
import { linkItem } from './itemFunctions'
const toastErrorMock: (t: string) => void = vi.fn()
const toastSuccessMock: (t: string) => void = vi.fn()
vi.mock('react-toastify', () => ({
toast: {
error: (t: string) => toastErrorMock(t),
success: (t: string) => toastSuccessMock(t),
},
}))
describe('linkItem', () => {
const id = 'some-id'
let updateApi: () => void = vi.fn()
const item = { layer: { api: { updateItem: () => updateApi() } } }
const updateItem = vi.fn()
beforeEach(() => {
updateApi = vi.fn()
vi.clearAllMocks()
})
describe('api rejects', () => {
it('toasts an error', async () => {
updateApi = vi.fn().mockRejectedValue('autsch')
await linkItem(id, item, updateItem)
expect(toastErrorMock).toHaveBeenCalledWith('autsch')
expect(updateItem).not.toHaveBeenCalled()
expect(toastSuccessMock).not.toHaveBeenCalled()
})
})
describe('api resolves', () => {
it('toasts success and calls updateItem()', async () => {
await linkItem(id, item, updateItem)
expect(toastErrorMock).not.toHaveBeenCalled()
expect(updateItem).toHaveBeenCalledTimes(1)
expect(toastSuccessMock).toHaveBeenCalledWith('Item linked')
})
})
})

31
src/types/Tag.d.ts vendored
View File

@ -1,4 +1,35 @@
/**
* Tags are used to tag items within the app and the map and to filter by keywords. Every tag has a color.
* @example
* ```ts
* export const tags: Tag[] = [
* {
* "id": "e19f46a7-77a4-4a50-99a2-a942dce843a3",
* "name": "nature",
* "color": "#9bc53d"
* },
* {
* "id": "2c2099a6-23ac-4308-b91c-86eefeff3a1d",
* "name": "utopia",
* "color": "#c3423f"
* },
* {
* "id": "48b2de97-2b9e-432b-b230-7bdc9a5fb6c0",
* "name": "map",
* "color": "#5bc0eb"
* },
* {
* "id": "c88f52e6-357b-45fb-a171-9c2b1dceeb8e",
* "name": "food",
* "color": "#6761a8"
* },
* {
* "id": "8928cb92-a3c1-4d83-9495-c2eb4fac0bbe",
* "name": "permaculture",
* "color": "#44344f"
* },
*];
```
* @category Types
*/
export interface Tag {

View File

@ -16,9 +16,6 @@ export default defineConfig({
reporter: ['html', 'json-summary'],
thresholds: {
lines: 1,
functions: 56,
branches: 58,
statements: 1,
},
},
},