changed the color sort to use hue so that when custom colors are introduced it will sort in a way that is logically consistent.

This commit is contained in:
Kenneth Brewer 2026-04-28 10:03:38 -04:00
parent b043016299
commit a09d945b41
5 changed files with 75 additions and 29 deletions

View File

@ -167,11 +167,19 @@ export default class MapsController {
vine.object({
name: vine.string().trim().minLength(1).maxLength(255).optional(),
color: vine.string().trim().maxLength(20).optional(),
longitude: vine.number().min(-180).max(180).optional(),
latitude: vine.number().min(-90).max(90).optional(),
notes: vine.string().trim().nullable().optional(),
marker_type: vine.string().trim().maxLength(20).optional(),
})
)
)
if (payload.name !== undefined) marker.name = payload.name
if (payload.color !== undefined) marker.color = payload.color
if (payload.longitude !== undefined) marker.longitude = payload.longitude
if (payload.latitude !== undefined) marker.latitude = payload.latitude
if (payload.notes !== undefined) marker.notes = payload.notes
if (payload.marker_type !== undefined) marker.marker_type = payload.marker_type
await marker.save()
return marker
}

View File

@ -16,7 +16,7 @@ import MarkerPin from './MarkerPin'
import MarkerPanel from './MarkerPanel'
import ViewMapMarkerPopup from './ViewMapMarkerPopup'
import MapMarkerFormPopup from './MapMarkerFormPopup'
import ScaleUnitControl from './ScaleUnitControl'
import ScaleUnitToggle from './ScaleUnitToggle'
type ScaleUnit = 'imperial' | 'metric'
@ -94,7 +94,7 @@ export default function MapComponent() {
<NavigationControl style={{ marginTop: '110px', marginRight: '36px' }} />
<FullscreenControl style={{ marginTop: '30px', marginRight: '36px' }} />
<ScaleControl position="bottom-left" maxWidth={150} unit={scaleUnit} />
<ScaleUnitControl scaleUnit={scaleUnit} onChange={handleScaleUnitChange} />
<ScaleUnitToggle scaleUnit={scaleUnit} onChange={handleScaleUnitChange} />
{markers.map((marker) => (
<Marker
@ -136,14 +136,6 @@ export default function MapComponent() {
/>
)}
{selectedMarker && editingMarkerId !== selectedMarker.id && (
<ViewMapMarkerPopup
marker={selectedMarker}
onClose={() => setSelectedMarkerId(null)}
onEdit={() => setEditingMarkerId(selectedMarker.id)}
/>
)}
{selectedMarker && editingMarkerId === selectedMarker.id && (
<MapMarkerFormPopup
longitude={selectedMarker.longitude}

View File

@ -12,17 +12,42 @@ interface MarkerPanelProps {
selectedMarkerId: number | null
}
const getColorSortValue = (color: string) => {
const preset = PIN_COLORS.find((pinColor) => pinColor.id === color)
if (preset) return preset.label
return color.replace('#', '').toLowerCase()
}
type SortField = 'name' | 'color'
type SortDirection = 'asc' | 'desc'
const normalizeColorHex = (color: string) => {
const preset = PIN_COLORS.find((pinColor) => pinColor.id === color)
return preset?.hex ?? color
}
const getHueSortValue = (color: string) => {
const hex = normalizeColorHex(color).replace('#', '')
if (!/^[0-9a-fA-F]{6}$/.test(hex)) return 0
const r = parseInt(hex.slice(0, 2), 16) / 255
const g = parseInt(hex.slice(2, 4), 16) / 255
const b = parseInt(hex.slice(4, 6), 16) / 255
const max = Math.max(r, g, b)
const min = Math.min(r, g, b)
const delta = max - min
if (delta === 0) return 0
let hue = 0
if (max === r) {
hue = ((g - b) / delta) % 6
} else if (max === g) {
hue = (b - r) / delta + 2
} else {
hue = (r - g) / delta + 4
}
return Math.round(hue * 60 + 360) % 360
}
export default function MarkerPanel({
markers,
onDelete,
@ -35,6 +60,15 @@ export default function MarkerPanel({
const [sortField, setSortField] = useState<SortField>('name')
const [sortDirection, setSortDirection] = useState<SortDirection>('asc')
const sortDirectionLabel =
sortField === 'name'
? sortDirection === 'asc'
? 'A → Z'
: 'Z → A'
: sortDirection === 'asc'
? 'Hue ↑'
: 'Hue ↓'
const visibleMarkers = useMemo(() => {
const query = searchQuery.trim().toLowerCase()
@ -44,12 +78,10 @@ export default function MarkerPanel({
return marker.name.toLowerCase().includes(query)
})
.sort((a, b) => {
const aValue = sortField === 'name' ? a.name : getColorSortValue(a.color)
const bValue = sortField === 'name' ? b.name : getColorSortValue(b.color)
const result = aValue.localeCompare(bValue, undefined, {
sensitivity: 'base',
})
const result =
sortField === 'name'
? a.name.localeCompare(b.name, undefined, { sensitivity: 'base' })
: getHueSortValue(a.color) - getHueSortValue(b.color)
return sortDirection === 'asc' ? result : -result
})
@ -116,7 +148,7 @@ export default function MarkerPanel({
className="flex-1 rounded border border-border-default bg-surface-primary px-2 py-1 text-xs text-text-primary focus:border-desert-green focus:outline-none"
>
<option value="name">Sort by name</option>
<option value="color">Sort by color</option>
<option value="color">Sort by hue</option>
</select>
<button
@ -124,7 +156,7 @@ export default function MarkerPanel({
onClick={() => setSortDirection((prev) => (prev === 'asc' ? 'desc' : 'asc'))}
className="rounded border border-border-default px-2 py-1 text-xs text-text-secondary transition-colors hover:bg-surface-secondary"
>
{sortDirection === 'asc' ? 'A → Z' : 'Z → A'}
{sortDirectionLabel}
</button>
</div>
</div>
@ -154,7 +186,7 @@ export default function MarkerPanel({
size={16}
className="shrink-0"
style={{
color: PIN_COLORS.find((color) => color.id === marker.color)?.hex ?? '#a84a12',
color: normalizeColorHex(marker.color),
}}
/>

View File

@ -5,7 +5,7 @@ type ScaleUnitControlProps = {
onChange: (unit: ScaleUnit) => void
}
export default function ScaleUnitControl({ scaleUnit, onChange }: ScaleUnitControlProps) {
export default function ScaleUnitToggle({ scaleUnit, onChange }: ScaleUnitControlProps) {
return (
<div style={{ position: 'absolute', bottom: '30px', left: '10px', zIndex: 2 }}>
<div

View File

@ -28,7 +28,21 @@ export default function ViewMapMarkerPopup({
{marker.notes && (
<div className="mt-1 text-xs text-gray-500">
<ReactMarkdown remarkPlugins={[remarkGfm]}>
<ReactMarkdown
remarkPlugins={[remarkGfm]}
components={{
a: ({ href, children }) => (
<a
href={href}
target="_blank"
rel="noopener noreferrer"
className="text-blue-600 underline hover:text-blue-700"
>
{children}
</a>
),
}}
>
{marker.notes}
</ReactMarkdown>
</div>