mirror of
https://github.com/Crosstalk-Solutions/project-nomad.git
synced 2026-05-23 04:45:06 +02:00
Addressed further pr comments about the api, onSave async, and adding a comment about rehypeRaw
This commit is contained in:
parent
aad4d2af7c
commit
2f00db362d
|
|
@ -137,9 +137,11 @@ export default function MapComponent() {
|
|||
<MapMarkerFormPopup
|
||||
longitude={placingMarker.lng}
|
||||
latitude={placingMarker.lat}
|
||||
onDirtyChange={setHasUnsavedMarkerChanges}
|
||||
onSave={async ({ name, notes, color }) => {
|
||||
await addMarker(name, placingMarker.lng, placingMarker.lat, color, notes || undefined)
|
||||
setPlacingMarker(null)
|
||||
setHasUnsavedMarkerChanges(false)
|
||||
}}
|
||||
onCancel={() => {
|
||||
if (!confirmDiscardMarkerChanges()) return
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import {useCallback, useEffect, useLayoutEffect, useRef, useState} from 'react'
|
||||
import { useCallback, useEffect, useLayoutEffect, useRef, useState } from 'react'
|
||||
import { Popup } from 'react-map-gl/maplibre'
|
||||
|
||||
import { PIN_COLORS } from '~/hooks/useMapMarkers'
|
||||
|
|
@ -18,7 +18,7 @@ type MapMarkerFormPopupProps = {
|
|||
name: string
|
||||
notes: string
|
||||
color: PinColorId
|
||||
}) => void
|
||||
}) => Promise<void> | void
|
||||
onCancel: () => void
|
||||
onDirtyChange?: (dirty: boolean) => void
|
||||
}
|
||||
|
|
@ -34,6 +34,7 @@ export default function MapMarkerFormPopup({
|
|||
const [name, setName] = useState(initialMarker?.name ?? '')
|
||||
const [notes, setNotes] = useState(initialMarker?.notes ?? '')
|
||||
const [color, setColor] = useState<PinColorId>(initialMarker?.color ?? 'orange')
|
||||
const [isSaving, setIsSaving] = useState(false)
|
||||
|
||||
const textareaRef = useRef<HTMLTextAreaElement | null>(null)
|
||||
|
||||
|
|
@ -49,10 +50,6 @@ export default function MapMarkerFormPopup({
|
|||
resizeTextarea()
|
||||
}, [resizeTextarea])
|
||||
|
||||
useLayoutEffect(() => {
|
||||
resizeTextarea()
|
||||
}, [])
|
||||
|
||||
const isDirty =
|
||||
name !== (initialMarker?.name ?? '') ||
|
||||
notes !== (initialMarker?.notes ?? '') ||
|
||||
|
|
@ -62,15 +59,21 @@ export default function MapMarkerFormPopup({
|
|||
onDirtyChange?.(isDirty)
|
||||
}, [isDirty, onDirtyChange])
|
||||
|
||||
const handleSave = () => {
|
||||
if (!name.trim()) return
|
||||
const handleSave = async () => {
|
||||
if (!name.trim() || isSaving) return
|
||||
|
||||
onSave({
|
||||
id: initialMarker?.id,
|
||||
name: name.trim(),
|
||||
notes: notes.trim(),
|
||||
color,
|
||||
})
|
||||
try {
|
||||
setIsSaving(true)
|
||||
|
||||
await onSave({
|
||||
id: initialMarker?.id,
|
||||
name: name.trim(),
|
||||
notes: notes.trim(),
|
||||
color,
|
||||
})
|
||||
} finally {
|
||||
setIsSaving(false)
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
|
|
@ -136,7 +139,8 @@ export default function MapMarkerFormPopup({
|
|||
<button
|
||||
type="button"
|
||||
onClick={onCancel}
|
||||
className="text-xs text-gray-500 hover:text-gray-700 px-2 py-1 rounded transition-colors"
|
||||
disabled={isSaving}
|
||||
className="text-xs text-gray-500 hover:text-gray-700 px-2 py-1 rounded transition-colors disabled:opacity-40"
|
||||
>
|
||||
Cancel
|
||||
</button>
|
||||
|
|
@ -144,10 +148,10 @@ export default function MapMarkerFormPopup({
|
|||
<button
|
||||
type="button"
|
||||
onClick={handleSave}
|
||||
disabled={!name.trim()}
|
||||
disabled={!name.trim() || isSaving}
|
||||
className="text-xs bg-[#424420] text-white rounded px-2.5 py-1 hover:bg-[#525530] disabled:opacity-40 transition-colors"
|
||||
>
|
||||
Save
|
||||
{isSaving ? 'Saving...' : 'Save'}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -29,6 +29,8 @@ export default function ViewMapMarkerPopup({
|
|||
|
||||
{marker.notes && (
|
||||
<div className="mt-1 max-w-[240px] break-all whitespace-pre-wrap text-xs text-gray-500">
|
||||
{/* react-markdown is intentionally used without rehypeRaw.
|
||||
Do not enable raw HTML rendering unless notes are sanitized first. */}
|
||||
<ReactMarkdown
|
||||
remarkPlugins={[remarkGfm]}
|
||||
components={{
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ import { catchInternal } from './util'
|
|||
import { NomadChatResponse, NomadInstalledModel, NomadOllamaModel, OllamaChatRequest } from '../../types/ollama'
|
||||
import BenchmarkResult from '#models/benchmark_result'
|
||||
import { BenchmarkType, RunBenchmarkResponse, SubmitBenchmarkResponse, UpdateBuilderTagResponse } from '../../types/benchmark'
|
||||
import type { MapMarkerResponse } from '../../types/maps'
|
||||
|
||||
class API {
|
||||
private client: AxiosInstance
|
||||
|
|
@ -580,27 +581,30 @@ class API {
|
|||
|
||||
async listMapMarkers() {
|
||||
return catchInternal(async () => {
|
||||
const response = await this.client.get<
|
||||
Array<{ id: number; name: string; longitude: number; latitude: number; color: string; created_at: string }>
|
||||
>('/maps/markers')
|
||||
const response = await this.client.get<MapMarkerResponse[]>('/maps/markers')
|
||||
return response.data
|
||||
})()
|
||||
}
|
||||
|
||||
async createMapMarker(data: { name: string; longitude: number; latitude: number; color?: string }) {
|
||||
async createMapMarker(data: {
|
||||
name: string
|
||||
notes?: string | null
|
||||
longitude: number
|
||||
latitude: number
|
||||
color?: string
|
||||
}) {
|
||||
return catchInternal(async () => {
|
||||
const response = await this.client.post<
|
||||
{ id: number; name: string; longitude: number; latitude: number; color: string; created_at: string }
|
||||
>('/maps/markers', data)
|
||||
const response = await this.client.post<MapMarkerResponse>('/maps/markers', data)
|
||||
return response.data
|
||||
})()
|
||||
}
|
||||
|
||||
async updateMapMarker(id: number, data: { name?: string; color?: string }) {
|
||||
async updateMapMarker(
|
||||
id: number,
|
||||
data: { name?: string; notes?: string | null; color?: string }
|
||||
) {
|
||||
return catchInternal(async () => {
|
||||
const response = await this.client.patch<
|
||||
{ id: number; name: string; longitude: number; latitude: number; color: string }
|
||||
>(`/maps/markers/${id}`, data)
|
||||
const response = await this.client.patch<MapMarkerResponse>(`/maps/markers/${id}`, data)
|
||||
return response.data
|
||||
})()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -21,3 +21,17 @@ export type MapLayer = {
|
|||
'source-layer'?: string
|
||||
[key: string]: any
|
||||
}
|
||||
|
||||
export type MapMarkerResponse = {
|
||||
id: number
|
||||
name: string
|
||||
longitude: number
|
||||
latitude: number
|
||||
color: string
|
||||
notes?: string | null
|
||||
marker_type?: string
|
||||
route_id?: string | null
|
||||
route_order?: number | null
|
||||
created_at: string
|
||||
updated_at?: string
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user