diff --git a/packages/app/package.json b/packages/app/package.json index d82eecb7..6caa4d42 100644 --- a/packages/app/package.json +++ b/packages/app/package.json @@ -21,6 +21,7 @@ "dependencies": { "@aiui/core": "workspace:*", "@tanstack/vue-virtual": "^3.13.19", + "leaflet": "^1.9.4", "markdown-it": "^14.1.1", "pdfjs-dist": "^5.5.207", "pinia": "^3.0.4", @@ -32,6 +33,7 @@ "@eslint/js": "^10.0.1", "@playwright/test": "^1.49.0", "@tailwindcss/vite": "^4.2.1", + "@types/leaflet": "^1.9.21", "@types/markdown-it": "^14.1.2", "@vitejs/plugin-vue": "^6.0.4", "duck-duck-scrape": "^2.2.7", diff --git a/packages/app/src/components/chat/ChatMessage.vue b/packages/app/src/components/chat/ChatMessage.vue index 19698349..e2e347cc 100644 --- a/packages/app/src/components/chat/ChatMessage.vue +++ b/packages/app/src/components/chat/ChatMessage.vue @@ -242,6 +242,13 @@ > View all {{ inlinePlaces.length }} places → + + View on map → + props.message.role === 'user') diff --git a/packages/app/src/components/content/ContentPanel.vue b/packages/app/src/components/content/ContentPanel.vue index 11cf495e..67aa918a 100644 --- a/packages/app/src/components/content/ContentPanel.vue +++ b/packages/app/src/components/content/ContentPanel.vue @@ -99,6 +99,11 @@ :title="pdfUrl.title" @back="closePdfViewer" /> + - !!(selectedFilm.value || selectedBook.value || selectedTVSeries.value || selectedSong.value || selectedPodcast.value || selectedArticle.value || selectedDesignSystemItem.value || longFormArticle.value || pdfUrl.value) + !!(selectedFilm.value || selectedBook.value || selectedTVSeries.value || selectedSong.value || selectedPodcast.value || selectedArticle.value || selectedDesignSystemItem.value || longFormArticle.value || pdfUrl.value || mapPlaces.value.length > 0) ) const windowWidth = ref(window.innerWidth) diff --git a/packages/app/src/components/renderers/MapRenderer.vue b/packages/app/src/components/renderers/MapRenderer.vue new file mode 100644 index 00000000..610aa505 --- /dev/null +++ b/packages/app/src/components/renderers/MapRenderer.vue @@ -0,0 +1,139 @@ + + + + + + + + + + {{ places.length }} place{{ places.length === 1 ? '' : 's' }} + + + + + + + + + + + + + + diff --git a/packages/app/src/composables/useContentPanel.ts b/packages/app/src/composables/useContentPanel.ts index b125dde5..4009f32a 100644 --- a/packages/app/src/composables/useContentPanel.ts +++ b/packages/app/src/composables/useContentPanel.ts @@ -52,6 +52,7 @@ const availableTabs = ref([]) const selectedDesignSystemItem = ref(null) const longFormArticle = ref<{ content: string; title?: string } | null>(null) const pdfUrl = ref<{ url: string; title?: string } | null>(null) +const mapPlaces = ref([]) export interface DesignSystemItem { id: string @@ -275,6 +276,7 @@ export function useContentPanel() { selectedDesignSystemItem.value = null longFormArticle.value = null pdfUrl.value = null + mapPlaces.value = [] } function openFilmDetail(film: Film) { clearAllSelections(); selectedFilm.value = film } @@ -301,6 +303,9 @@ export function useContentPanel() { function openPdfViewer(url: string, title?: string) { clearAllSelections(); pdfUrl.value = { url, title }; panelOpen.value = true } function closePdfViewer() { pdfUrl.value = null } + function openMapView(places: Place[]) { clearAllSelections(); mapPlaces.value = places; panelOpen.value = true } + function closeMapView() { mapPlaces.value = [] } + function openMagazineSectionDetail(section: MagazineSection, index: number) { clearAllSelections() selectedMagazineSection.value = section @@ -451,6 +456,9 @@ export function useContentPanel() { pdfUrl, openPdfViewer, closePdfViewer, + mapPlaces, + openMapView, + closeMapView, enterDesignSystemMode, closePanel, showAllFilms, diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 769e13bf..c92b6b3f 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -27,6 +27,9 @@ importers: '@tanstack/vue-virtual': specifier: ^3.13.19 version: 3.13.19(vue@3.5.29(typescript@5.8.3)) + leaflet: + specifier: ^1.9.4 + version: 1.9.4 markdown-it: specifier: ^14.1.1 version: 14.1.1 @@ -55,6 +58,9 @@ importers: '@tailwindcss/vite': specifier: ^4.2.1 version: 4.2.1(vite@7.3.1(@types/node@25.3.3)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2)) + '@types/leaflet': + specifier: ^1.9.21 + version: 1.9.21 '@types/markdown-it': specifier: ^14.1.2 version: 14.1.2 @@ -1267,9 +1273,15 @@ packages: '@types/estree@1.0.8': resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==} + '@types/geojson@7946.0.16': + resolution: {integrity: sha512-6C8nqWur3j98U6+lXDfTUWIfgvZU+EumvpHKcYjujKH7woYyLj2sUmff0tRhrqM7BohUw7Pz3ZB1jj2gW9Fvmg==} + '@types/json-schema@7.0.15': resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==} + '@types/leaflet@1.9.21': + resolution: {integrity: sha512-TbAd9DaPGSnzp6QvtYngntMZgcRk+igFELwR2N99XZn7RXUdKgsXMR+28bUO0rPsWp8MIu/f47luLIQuSLYv/w==} + '@types/linkify-it@5.0.0': resolution: {integrity: sha512-sVDA58zAw4eWAffKOaQH5/5j3XeayukzDk+ewSsnv3p4yJEZHCCzMDiZM8e0OUrRvmpGZ85jf4yDHkHsgBNr9Q==} @@ -2162,6 +2174,9 @@ packages: keyv@4.5.4: resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==} + leaflet@1.9.4: + resolution: {integrity: sha512-nxS1ynzJOmOlHp+iL3FyWqK89GtNL8U8rvlMOsQdTTssxZwCXh8N2NB3GDQOL+YR3XnWyZAxwQixURb+FA74PA==} + leven@3.1.0: resolution: {integrity: sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==} engines: {node: '>=6'} @@ -4215,8 +4230,14 @@ snapshots: '@types/estree@1.0.8': {} + '@types/geojson@7946.0.16': {} + '@types/json-schema@7.0.15': {} + '@types/leaflet@1.9.21': + dependencies: + '@types/geojson': 7946.0.16 + '@types/linkify-it@5.0.0': {} '@types/markdown-it@14.1.2': @@ -5276,6 +5297,8 @@ snapshots: dependencies: json-buffer: 3.0.1 + leaflet@1.9.4: {} + leven@3.1.0: {} levn@0.4.1: