Merge branch 'main' into lazy-loading-items-index

This commit is contained in:
Anton Tranelis 2026-01-06 10:52:28 +01:00 committed by GitHub
commit 5a59265852
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
26 changed files with 438 additions and 605 deletions

View File

@ -27,6 +27,10 @@ updates:
- "@tiptap/pm"
- "@tiptap/react"
- "@tiptap/starter-kit"
vitest-ecosystem:
patterns:
- "vitest"
- "@vitest/*"
labels:
- "dependencies"
- "javascript"

View File

@ -21,8 +21,8 @@
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-rnd": "^10.4.1",
"react-router-dom": "^6.23.0",
"utopia-ui": "^3.0.111",
"react-router-dom": "^7.11.0",
"utopia-ui": "^3.0.112",
"vite-tsconfig-paths": "^5.1.4"
},
"devDependencies": {

View File

@ -11,6 +11,17 @@ import { directusClient } from './directus'
import type { MyCollections } from './directus'
import type { ItemsApi } from 'utopia-ui'
// Fields to request when fetching items to include all relational data
const ITEM_FIELDS = [
'*',
'secrets.*',
'to.*',
'relations.*',
'user_created.*',
'markerIcon.*',
{ offers: ['*'], needs: ['*'], gallery: ['*.*'] } as any,
]
export class itemsApi<T> implements ItemsApi<T> {
collectionName: keyof MyCollections
filter: any
@ -43,15 +54,7 @@ export class itemsApi<T> implements ItemsApi<T> {
try {
const result = await directusClient.request<T[]>(
readItems(this.collectionName as never, {
fields: [
'*',
'secrets.*',
'to.*',
'relations.*',
'user_created.*',
'markerIcon.*',
{ offers: ['*'], needs: ['*'], gallery: ['*.*'] } as any,
],
fields: ITEM_FIELDS,
filter: this.filter,
limit: -1,
}),
@ -70,7 +73,11 @@ export class itemsApi<T> implements ItemsApi<T> {
async getItem(id: string): Promise<T> {
try {
const result = await directusClient.request(readItem(this.collectionName as never, id))
const result = await directusClient.request(
readItem(this.collectionName as never, id, {
fields: ITEM_FIELDS,
}),
)
return result as T
} catch (error: any) {
console.log(error)
@ -82,13 +89,18 @@ export class itemsApi<T> implements ItemsApi<T> {
async createItem(item: T & { id?: string }): Promise<T> {
try {
const result = await directusClient.request(
createItem(this.collectionName, {
...item,
...(this.customParameter && this.customParameter),
...(this.layerId && { layer: this.layerId }),
...(this.layerId && { layer: this.layerId }),
...(this.mapId && { map: this.mapId }),
}),
createItem(
this.collectionName,
{
...item,
...(this.customParameter && this.customParameter),
...(this.layerId && { layer: this.layerId }),
...(this.mapId && { map: this.mapId }),
},
{
fields: ITEM_FIELDS,
},
),
)
return result as T
} catch (error: any) {
@ -100,7 +112,11 @@ export class itemsApi<T> implements ItemsApi<T> {
async updateItem(item: T & { id?: string }): Promise<T> {
try {
const result = await directusClient.request(updateItem(this.collectionName, item.id!, item))
const result = await directusClient.request(
updateItem(this.collectionName, item.id!, item, {
fields: ITEM_FIELDS,
}),
)
return result as T
} catch (error: any) {
console.log(error)

View File

@ -1,6 +1,6 @@
{
"name": "utopia-ui",
"version": "3.0.111",
"version": "3.0.112",
"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/",
@ -58,7 +58,7 @@
"@types/react": "^18.2.0",
"@types/react-dom": "^18.0.5",
"@vitejs/plugin-react": "^4.3.4",
"@vitest/coverage-v8": "^3.0.5",
"@vitest/coverage-v8": "^4.0.16",
"cypress": "^15.7.1",
"daisyui": "^5.5.14",
"eslint": "^9.39.2",
@ -79,24 +79,24 @@
"prettier": "^3.7.4",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"rollup": "^4.53.5",
"rollup": "^4.54.0",
"rollup-plugin-dts": "^6.3.0",
"rollup-plugin-postcss": "^4.0.2",
"rollup-plugin-svg": "^2.0.0",
"tailwindcss": "^4.1.18",
"typedoc": "^0.27.6",
"typedoc-plugin-coverage": "^3.4.1",
"typedoc-plugin-missing-exports": "^3.1.0",
"typedoc": "^0.28.15",
"typedoc-plugin-coverage": "^4.0.2",
"typedoc-plugin-missing-exports": "^4.1.2",
"typescript": "^5.9.3",
"typescript-eslint": "^8.9.0",
"vite": "^7.3.0",
"vite-plugin-svgr": "^4.3.0",
"vitest": "^3.0.5"
"vitest": "^4.0.16"
},
"peerDependencies": {
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-router-dom": "^6.23.0"
"react-router-dom": "^7.10.1"
},
"dependencies": {
"@heroicons/react": "^2.0.17",
@ -126,12 +126,12 @@
"react-leaflet": "^4.2.1",
"react-leaflet-cluster": "^3.1.1",
"react-markdown": "^9.0.1",
"react-photo-album": "^3.3.0",
"react-photo-album": "^3.4.0",
"react-qr-code": "^2.0.16",
"react-toastify": "^9.1.3",
"remark-breaks": "^4.0.0",
"tiptap-markdown": "^0.9.0",
"yet-another-react-lightbox": "^3.27.0"
"yet-another-react-lightbox": "^3.28.0"
},
"imports": {
"#assets/*": "./src/assets/*",

View File

@ -45,12 +45,12 @@ export const UserControl = () => {
const handleEdit = () => {
if (!myProfile?.layer) {
navigate(userProfile.id ? `/edit-item/${userProfile.id}` : '#')
void navigate(userProfile.id ? `/edit-item/${userProfile.id}` : '#')
return
}
if (myProfile.layer.itemType.small_form_edit && myProfile.position) {
navigate('/')
void navigate('/')
// Wait for navigation to complete before setting popup
setTimeout(() => {
if (myProfile.position && myProfile.layer) {
@ -65,7 +65,7 @@ export const UserControl = () => {
}
}, 100)
} else {
navigate(userProfile.id ? `/edit-item/${userProfile.id}` : '#')
void navigate(userProfile.id ? `/edit-item/${userProfile.id}` : '#')
}
}
const avatar: string | undefined =

View File

@ -51,9 +51,9 @@ export function LoginPage({ inviteApi, showRequestPassword }: Props) {
invitingProfileId = await redeemInvite(inviteCode)
}
if (invitingProfileId) {
navigate(`/item/${invitingProfileId}`)
void navigate(`/item/${invitingProfileId}`)
} else {
navigate('/')
void navigate('/')
}
}, [navigate, redeemInvite])

View File

@ -20,7 +20,7 @@ export function RequestPasswordPage({ resetUrl }: { resetUrl: string }) {
await toast.promise(requestPasswordReset(email, resetUrl), {
success: {
render() {
navigate('/')
void navigate('/')
return 'Check your mailbox'
},
// other options

View File

@ -22,7 +22,7 @@ export function SetNewPasswordPage() {
await toast.promise(passwordReset(token, password), {
success: {
render() {
navigate('/')
void navigate('/')
return 'New password set'
},
},

View File

@ -25,7 +25,7 @@ export function SignupPage() {
await toast.promise(register({ email, password }, userName), {
success: {
render({ data }) {
navigate('/')
void navigate('/')
return `Hi ${data?.first_name ? data.first_name : 'Traveler'}`
},
// other options

View File

@ -14,7 +14,7 @@ export const GratitudeControl = () => {
<div
className='tw:card-body tw:hover:bg-slate-300 tw:card tw:p-2 tw:h-10 tw:w-10 tw:transition-all tw:duration-300 tw:hover:cursor-pointer'
onClick={() => {
navigate('/select-user')
void navigate('/select-user')
}}
>
<HeartIcon className='tw:stroke-[2.5]' />

View File

@ -195,7 +195,7 @@ export const LocateControl = (): React.JSX.Element => {
}
// Navigate to the profile to show the popup
navigate(`/${result.id}`)
void navigate(`/${result.id}`)
// Clean up and reset state
setFoundLocation(null)

View File

@ -73,11 +73,13 @@ export function EditMenu({
className='tw:text-base-content! tw:tooltip tw:tooltip-top tw:cursor-pointer'
data-tip='Edit'
onClick={(e) => {
item.layer?.customEditLink
? navigate(
`${item.layer.customEditLink}${item.layer.customEditParameter ? `/${item.id}${params.toString() ? '?' + params.toString() : ''}` : ''}`,
)
: editCallback(e)
if (item.layer?.customEditLink) {
void navigate(
`${item.layer.customEditLink}${item.layer.customEditParameter ? `/${item.id}${params.toString() ? '?' + params.toString() : ''}` : ''}`,
)
} else {
editCallback(e)
}
}}
>
<PencilIcon className='tw:h-5 tw:w-5' />

View File

@ -81,7 +81,7 @@ export const ItemViewPopup = forwardRef((props: ItemViewPopupProps, ref: any) =>
setLoading(false)
map.closePopup()
removeItemFromUrl()
navigate('/')
void navigate('/')
}
return (
@ -99,7 +99,7 @@ export const ItemViewPopup = forwardRef((props: ItemViewPopupProps, ref: any) =>
setPositionCallback={() => {
map.closePopup()
setSelectPosition(props.item)
navigate('/')
void navigate('/')
}}
loading={loading}
/>

View File

@ -117,7 +117,7 @@ function useFilterManager(initialTags: Tag[]): {
params.set('layers', visibleNames.join(','))
}
navigate(`${location.pathname}?${params.toString()}`, { replace: true })
void navigate(`${location.pathname}?${params.toString()}`, { replace: true })
}, [visibleLayers, allLayers, navigate])
const [visibleGroupTypes, dispatchGroupTypes] = useReducer(
@ -152,8 +152,8 @@ function useFilterManager(initialTags: Tag[]): {
params.set('tags', `${urlTags || ''}${urlTags ? ';' : ''}${tag.name}`)
}
if (windowDimensions.width < 786 && location.pathname.split('/').length > 2)
navigate('/' + (params ? `?${params}` : ''))
else navigate(location.pathname + (params ? `?${params}` : ''))
void navigate('/' + (params ? `?${params}` : ''))
else void navigate(location.pathname + (params ? `?${params}` : ''))
dispatchTags({
type: 'ADD_TAG',
@ -177,10 +177,10 @@ function useFilterManager(initialTags: Tag[]): {
})
if (newUrlTags !== '') {
params.set('tags', newUrlTags)
navigate(location.pathname + (params ? `?${params}` : ''))
void navigate(location.pathname + (params ? `?${params}` : ''))
} else {
params.delete('tags')
navigate(location.pathname + (params ? `?${params}` : ''))
void navigate(location.pathname + (params ? `?${params}` : ''))
}
dispatchTags({

View File

@ -39,10 +39,10 @@ export function InvitePage({ inviteApi }: Props) {
if (invitingProfileId) {
toast.success('Invite redeemed successfully!')
navigate(`/item/${invitingProfileId}`)
void navigate(`/item/${invitingProfileId}`)
} else {
toast.error('Failed to redeem invite')
navigate('/')
void navigate('/')
}
}
@ -55,7 +55,7 @@ export function InvitePage({ inviteApi }: Props) {
localStorage.setItem('inviteCode', id)
// Redirect to login page
navigate('/login')
void navigate('/login')
}
}, [
id,

View File

@ -187,12 +187,12 @@ export function ProfileView({ attestationApi }: { attestationApi?: ItemsApi<any>
})
}}
editCallback={() => {
navigate('/edit-item/' + item.id)
void navigate('/edit-item/' + item.id)
}}
setPositionCallback={() => {
map.closePopup()
setSelectPosition(item)
navigate('/')
void navigate('/')
}}
big
truncateSubname={false}

View File

@ -133,7 +133,7 @@ export const TabsForm = ({
key={i.id}
className='tw:cursor-pointer tw:card tw:bg-base-200 tw:border-[1px] tw:border-base-300 tw:card-body tw:shadow-xl tw:text-base-content tw:mx-4 tw:p-6 tw:mb-4'
onClick={() => {
navigate('/item/' + i.id)
void navigate('/item/' + i.id)
}}
>
<LinkedItemsHeaderView

View File

@ -286,7 +286,7 @@ export const TabsView = ({
key={i.id}
className='tw:cursor-pointer tw:card tw:bg-base-200 tw:border-[1px] tw:border-base-300 tw:card-body tw:shadow-xl tw:text-base-content tw:p-6 tw:mr-4 tw:mb-4'
onClick={() => {
navigate('/item/' + i.id)
void navigate('/item/' + i.id)
}}
>
<LinkedItemsHeaderView

View File

@ -48,7 +48,7 @@ export function UserSettings() {
},
})
.then(() => {
navigate('/')
void navigate('/')
})
.catch((e) => {
throw e

View File

@ -71,7 +71,7 @@ export const AttestationForm = ({ api }: { api?: ItemsApi<unknown> }) => {
},
)
.then(() => {
navigate(
void navigate(
'/item/' +
// eslint-disable-next-line @typescript-eslint/restrict-plus-operands
items.find(

View File

@ -35,7 +35,7 @@ export const ItemCard = ({
}
if (i.layer.itemType.small_form_edit && i.position) {
navigate('/')
void navigate('/')
// Wait for navigation to complete before setting popup
setTimeout(() => {
if (i.position && i.layer) {
@ -49,7 +49,7 @@ export const ItemCard = ({
}
}, 100)
} else {
navigate('/edit-item/' + i.id)
void navigate('/edit-item/' + i.id)
}
}
@ -60,8 +60,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.size > 0 ? `?${params.toString()}` : ''))
else navigate(url + i.id + (params.size > 0 ? `?${params.toString()}` : ''))
void navigate('/' + i.id + (params.size > 0 ? `?${params.toString()}` : ''))
else void navigate(url + i.id + (params.size > 0 ? `?${params.toString()}` : ''))
}}
>
<HeaderView
@ -74,7 +74,7 @@ export const ItemCard = ({
setPositionCallback={() => {
map.closePopup()
setSelectPosition(i)
navigate('/')
void navigate('/')
}}
deleteCallback={() => {
deleteCallback(i)

View File

@ -17,7 +17,7 @@ export function MapOverlayPage({
card?: boolean
}) {
const closeScreen = () => {
navigate(`/${window.location.search ? window.location.search : ''}`)
void navigate(`/${window.location.search ? window.location.search : ''}`)
}
const navigate = useNavigate()

View File

@ -70,7 +70,7 @@ export const MarketView = () => {
{groupAndCount(offers).map((o) => (
<TagView
onClick={() => {
navigate(`/?tags=${o.object.name}`)
void navigate(`/?tags=${o.object.name}`)
}}
key={o.object.id}
tag={o.object}
@ -85,7 +85,7 @@ export const MarketView = () => {
{groupAndCount(needs).map((o) => (
<TagView
onClick={() => {
navigate(`/?tags=${o.object.name}`)
void navigate(`/?tags=${o.object.name}`)
}}
key={o.object.id}
tag={o.object}

View File

@ -39,7 +39,7 @@ export const Tabs: React.FC<TabsProps> = ({ items, setUrlParams }: TabsProps) =>
setUrlParams(params)
const newUrl = location.pathname + '?' + params.toString()
navigate(newUrl, { replace: false })
void navigate(newUrl, { replace: false })
},
[location.pathname, location.search, navigate, setUrlParams],
)

View File

@ -10,7 +10,6 @@ export default defineConfig({
environment: 'happy-dom',
setupFiles: ['setupTest.ts'],
coverage: {
all: true,
include: ['src/**/*.{js,jsx,ts,tsx}'],
exclude: [...configDefaults.exclude, 'src/**/*.cy.tsx'],
reporter: ['html', 'json-summary'],

880
package-lock.json generated

File diff suppressed because it is too large Load Diff