From 38c4dc3fd4ed1a5cfea82e4589af67dd91eebc29 Mon Sep 17 00:00:00 2001 From: Kenneth Brewer Date: Wed, 6 May 2026 03:53:52 -0400 Subject: [PATCH] Added font awesome icons to the map markers --- .../components/maps/IconSelectorPopover.tsx | 62 +++++- .../inertia/components/maps/MapComponent.tsx | 14 +- admin/inertia/components/maps/MarkerPanel.tsx | 37 +++- admin/inertia/components/maps/MarkerPin.tsx | 26 ++- .../components/maps/ScaleUnitSelector.tsx | 94 +++++---- admin/package-lock.json | 180 +++++++++--------- admin/package.json | 1 + 7 files changed, 274 insertions(+), 140 deletions(-) diff --git a/admin/inertia/components/maps/IconSelectorPopover.tsx b/admin/inertia/components/maps/IconSelectorPopover.tsx index 9d47928..f031091 100644 --- a/admin/inertia/components/maps/IconSelectorPopover.tsx +++ b/admin/inertia/components/maps/IconSelectorPopover.tsx @@ -2,6 +2,8 @@ import { useMemo, useState } from 'react' import * as TablerIcons from '@tabler/icons-react' import type { IconProps } from '@tabler/icons-react' import type { ComponentType } from 'react' +import * as FontAwesomeIcons from 'react-icons/fa' +import type { IconType } from 'react-icons' const PAGE_SIZE = 48 @@ -11,7 +13,21 @@ type IconSelectorPopoverProps = { onClose: () => void } -const iconEntries = Object.entries(TablerIcons) +const normalizeIconNameForSort = (name: string) => { + return name + .replace(/^Icon/, '') // Tabler: IconHome -> Home + .replace(/^Fa/, '') // FontAwesome: FaHome -> Home + .toLowerCase() +} + +type IconEntry = { + name: string + label: string + library: 'tabler' | 'fa' + Icon: ComponentType | IconType +} + +const tablerIconEntries: IconEntry[] = Object.entries(TablerIcons) .filter(([name, value]) => { return ( name.startsWith('Icon') && @@ -20,7 +36,32 @@ const iconEntries = Object.entries(TablerIcons) (typeof value === 'function' || typeof value === 'object') ) }) - .sort(([a], [b]) => a.localeCompare(b)) as Array<[string, ComponentType]> + .map(([name, Icon]) => ({ + name: `tabler:${name}`, + label: name, + library: 'tabler' as const, + Icon: Icon as ComponentType, + })) + +const fontAwesomeIconEntries: IconEntry[] = Object.entries(FontAwesomeIcons) + .filter(([name, value]) => { + return ( + name.startsWith('Fa') && + value !== null && + (typeof value === 'function' || typeof value === 'object') + ) + }) + .map(([name, Icon]) => ({ + name: `fa:${name}`, + label: name, + library: 'fa' as const, + Icon: Icon as IconType, + })) + +const iconEntries: IconEntry[] = [ + ...tablerIconEntries, + ...fontAwesomeIconEntries, +].sort((a, b) => a.label.localeCompare(b.label)) export default function IconSelectorPopover({ selectedIcon, @@ -33,11 +74,18 @@ export default function IconSelectorPopover({ const filteredIcons = useMemo(() => { const normalizedQuery = query.trim().toLowerCase() - return iconEntries.filter(([name]) => name.toLowerCase().includes(normalizedQuery)) + return iconEntries.filter((entry) => { + const normalizedLabel = normalizeIconNameForSort(entry.label) + + return ( + entry.label.toLowerCase().includes(normalizedQuery) || + normalizedLabel.includes(normalizedQuery) || + entry.library.toLowerCase().includes(normalizedQuery) + ) + }) }, [query]) const pageCount = Math.max(1, Math.ceil(filteredIcons.length / PAGE_SIZE)) - const pagedIcons = filteredIcons.slice(page * PAGE_SIZE, page * PAGE_SIZE + PAGE_SIZE) return ( @@ -61,12 +109,12 @@ export default function IconSelectorPopover({
- {pagedIcons.map(([name, Icon]) => ( + {pagedIcons.map(({ name, label, library, Icon }) => ( + {/* Metric */} + - -
-
- ) + {/* Imperial */} + + + {/* Nautical */} + + + + + ) } diff --git a/admin/package-lock.json b/admin/package-lock.json index b742bc2..0e9fedf 100644 --- a/admin/package-lock.json +++ b/admin/package-lock.json @@ -9,94 +9,95 @@ "version": "0.0.0", "license": "ISC", "dependencies": { - "@adonisjs/auth": "^9.4.0", - "@adonisjs/core": "^6.18.0", - "@adonisjs/cors": "^2.2.1", - "@adonisjs/inertia": "^3.1.1", - "@adonisjs/lucid": "^21.8.2", - "@adonisjs/session": "^7.5.1", - "@adonisjs/shield": "^8.2.0", - "@adonisjs/static": "^1.1.1", - "@adonisjs/transmit": "^2.0.2", - "@adonisjs/transmit-client": "^1.0.0", - "@adonisjs/vite": "^4.0.0", - "@chonkiejs/core": "^0.0.7", - "@headlessui/react": "^2.2.4", - "@inertiajs/react": "^2.0.13", - "@markdoc/markdoc": "^0.5.2", - "@openzim/libzim": "^4.0.0", - "@protomaps/basemaps": "^5.7.0", - "@qdrant/js-client-rest": "^1.16.2", - "@tabler/icons-react": "^3.34.0", - "@tailwindcss/vite": "^4.1.10", - "@tanstack/react-query": "^5.81.5", - "@tanstack/react-query-devtools": "^5.83.0", - "@tanstack/react-virtual": "^3.13.12", - "@uppy/core": "^5.2.0", - "@uppy/dashboard": "^5.1.0", - "@uppy/react": "^5.1.1", - "@vinejs/vine": "^3.0.1", - "@vitejs/plugin-react": "^4.6.0", - "autoprefixer": "^10.4.21", - "axios": "^1.15.0", - "better-sqlite3": "^12.1.1", - "bullmq": "^5.65.1", - "cheerio": "^1.2.0", - "compression": "^1.8.1", - "dockerode": "^4.0.7", - "edge.js": "^6.2.1", - "fast-xml-parser": "^5.5.7", - "fuse.js": "^7.1.0", - "jszip": "^3.10.1", - "luxon": "^3.6.1", - "maplibre-gl": "^4.7.1", - "mysql2": "^3.14.1", - "ollama": "^0.6.3", - "openai": "^6.27.0", - "pdf-parse": "^2.4.5", - "pdf2pic": "^3.2.0", - "pino-pretty": "^13.0.0", - "pmtiles": "^4.4.0", - "postcss": "^8.5.6", - "react": "^19.1.0", - "react-adonis-transmit": "^1.0.1", - "react-dom": "^19.1.0", - "react-map-gl": "^8.1.0", - "react-markdown": "^10.1.0", - "reflect-metadata": "^0.2.2", - "remark-gfm": "^4.0.1", - "sharp": "^0.34.5", - "stopword": "^3.1.5", - "systeminformation": "^5.31.0", - "tailwindcss": "^4.2.1", - "tar": "^7.5.11", - "tesseract.js": "^7.0.0", - "url-join": "^5.0.0", - "yaml": "^2.8.3" + "@adonisjs/auth": "9.6.0", + "@adonisjs/core": "6.19.3", + "@adonisjs/cors": "2.2.1", + "@adonisjs/inertia": "3.1.1", + "@adonisjs/lucid": "21.8.2", + "@adonisjs/session": "7.7.1", + "@adonisjs/shield": "8.2.0", + "@adonisjs/static": "1.1.1", + "@adonisjs/transmit": "2.0.2", + "@adonisjs/transmit-client": "1.1.0", + "@adonisjs/vite": "4.0.0", + "@chonkiejs/core": "0.0.7", + "@headlessui/react": "2.2.9", + "@inertiajs/react": "2.3.13", + "@markdoc/markdoc": "0.5.4", + "@openzim/libzim": "4.0.0", + "@protomaps/basemaps": "5.7.0", + "@qdrant/js-client-rest": "1.16.2", + "@tabler/icons-react": "3.36.1", + "@tailwindcss/vite": "4.1.18", + "@tanstack/react-query": "5.90.20", + "@tanstack/react-query-devtools": "5.91.3", + "@tanstack/react-virtual": "3.13.18", + "@uppy/core": "5.2.0", + "@uppy/dashboard": "5.1.0", + "@uppy/react": "5.1.1", + "@vinejs/vine": "3.0.1", + "@vitejs/plugin-react": "4.7.0", + "autoprefixer": "10.4.24", + "axios": "1.15.0", + "better-sqlite3": "12.6.2", + "bullmq": "5.67.2", + "cheerio": "1.2.0", + "compression": "1.8.1", + "dockerode": "4.0.9", + "edge.js": "6.4.0", + "fast-xml-parser": "5.5.9", + "fuse.js": "7.1.0", + "jszip": "3.10.1", + "luxon": "3.7.2", + "maplibre-gl": "4.7.1", + "mysql2": "3.16.2", + "ollama": "0.6.3", + "openai": "6.27.0", + "pdf-parse": "2.4.5", + "pdf2pic": "3.2.0", + "pino-pretty": "13.1.3", + "pmtiles": "4.4.0", + "postcss": "8.5.6", + "react": "19.2.4", + "react-adonis-transmit": "1.0.1", + "react-dom": "19.2.4", + "react-icons": "5.6.0", + "react-map-gl": "8.1.0", + "react-markdown": "10.1.0", + "reflect-metadata": "0.2.2", + "remark-gfm": "4.0.1", + "sharp": "0.34.5", + "stopword": "3.1.5", + "systeminformation": "5.31.0", + "tailwindcss": "4.2.2", + "tar": "7.5.11", + "tesseract.js": "7.0.0", + "url-join": "5.0.0", + "yaml": "2.8.3" }, "devDependencies": { - "@adonisjs/assembler": "^7.8.2", - "@adonisjs/eslint-config": "^2.0.0", - "@adonisjs/prettier-config": "^1.4.4", - "@adonisjs/tsconfig": "^1.4.0", - "@japa/assert": "^4.0.1", - "@japa/plugin-adonisjs": "^4.0.0", - "@japa/runner": "^4.2.0", + "@adonisjs/assembler": "7.8.2", + "@adonisjs/eslint-config": "2.1.2", + "@adonisjs/prettier-config": "1.4.5", + "@adonisjs/tsconfig": "1.4.1", + "@japa/assert": "4.2.0", + "@japa/plugin-adonisjs": "4.0.0", + "@japa/runner": "4.5.0", "@swc/core": "1.11.24", - "@tanstack/eslint-plugin-query": "^5.81.2", - "@types/compression": "^1.8.1", - "@types/dockerode": "^4.0.1", - "@types/luxon": "^3.6.2", - "@types/node": "^22.15.18", - "@types/react": "^19.1.8", - "@types/react-dom": "^19.1.6", - "@types/stopword": "^2.0.3", - "eslint": "^9.26.0", - "hot-hook": "^0.4.0", - "prettier": "^3.5.3", - "ts-node-maintained": "^10.9.5", - "typescript": "~5.8.3", - "vite": "^6.4.2" + "@tanstack/eslint-plugin-query": "5.91.4", + "@types/compression": "1.8.1", + "@types/dockerode": "4.0.1", + "@types/luxon": "3.7.1", + "@types/node": "22.19.7", + "@types/react": "19.2.10", + "@types/react-dom": "19.2.3", + "@types/stopword": "2.0.3", + "eslint": "9.39.2", + "hot-hook": "0.4.0", + "prettier": "3.8.1", + "ts-node-maintained": "10.9.6", + "typescript": "5.8.3", + "vite": "6.4.2" } }, "node_modules/@adobe/css-tools": { @@ -14052,6 +14053,15 @@ "react": "^19.2.4" } }, + "node_modules/react-icons": { + "version": "5.6.0", + "resolved": "https://registry.npmjs.org/react-icons/-/react-icons-5.6.0.tgz", + "integrity": "sha512-RH93p5ki6LfOiIt0UtDyNg/cee+HLVR6cHHtW3wALfo+eOHTp8RnU2kRkI6E+H19zMIs03DyxUG/GfZMOGvmiA==", + "license": "MIT", + "peerDependencies": { + "react": "*" + } + }, "node_modules/react-is": { "version": "18.3.1", "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", diff --git a/admin/package.json b/admin/package.json index b273fd0..2b4b5f1 100644 --- a/admin/package.json +++ b/admin/package.json @@ -114,6 +114,7 @@ "react": "19.2.4", "react-adonis-transmit": "1.0.1", "react-dom": "19.2.4", + "react-icons": "^5.6.0", "react-map-gl": "8.1.0", "react-markdown": "10.1.0", "reflect-metadata": "0.2.2",