From 9de3ac321c758ff62aee9ce6c7e4d8e4f5145a85 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alexandre=20Bult=C3=A9?= Date: Wed, 2 Sep 2026 12:04:08 +0200 Subject: [PATCH 01/15] feat(datasets): enable new explorer --- configs/ecospheres/config.yaml | 2 + package.json | 2 +- pnpm-lock.yaml | 120 ++++++------------ .../datasets/NewResourceExplorer.vue | 47 +++++++ .../datasets/ResourcesTabContent.vue | 70 ++++++++++ .../views/indicators/IndicatorDetailView.vue | 4 +- src/main.ts | 1 + src/model/config.ts | 1 + src/router/index.ts | 6 + src/router/utils.ts | 1 + src/typings/route-meta.d.ts | 3 + src/utils/newExplorer.ts | 29 +++++ src/views/datasets/DatasetDetailView.vue | 4 +- 13 files changed, 205 insertions(+), 85 deletions(-) create mode 100644 src/components/datasets/NewResourceExplorer.vue create mode 100644 src/components/datasets/ResourcesTabContent.vue create mode 100644 src/utils/newExplorer.ts diff --git a/configs/ecospheres/config.yaml b/configs/ecospheres/config.yaml index 5408106d2..aa573e6fd 100644 --- a/configs/ecospheres/config.yaml +++ b/configs/ecospheres/config.yaml @@ -189,6 +189,8 @@ website: - CSW-DCAT - CSW-ISO-19139 show_extended_information_panel: true + # Spike: new @datagouv/components-next ResourceExplorer, opt-in via a banner toggle + new_explorer_enabled: true # TODO: better place for that? topics: can_add_topics: diff --git a/package.json b/package.json index 7c9b261a7..407e65eb4 100644 --- a/package.json +++ b/package.json @@ -30,7 +30,7 @@ "type-check:cypress": "tsc --noEmit --project cypress/tsconfig.json" }, "dependencies": { - "@datagouv/components-next": "1.3.0", + "@datagouv/components-next": "1.0.2-dev.193", "@gouvfr/dsfr": "~1.14.4", "@gouvfr/dsfr-chart": "^2.0.4", "@gouvminint/vue-dsfr": "^8.15.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index bdc44b6eb..c9d51819a 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -9,8 +9,8 @@ importers: .: dependencies: '@datagouv/components-next': - specifier: 1.3.0 - version: 1.3.0(core-js@3.47.0)(echarts@6.1.0)(resize-detector@0.3.0)(supports-color@8.1.1)(vue-router@4.6.4(vue@3.5.30(typescript@5.7.3)))(vue@3.5.30(typescript@5.7.3)) + specifier: 1.0.2-dev.193 + version: 1.0.2-dev.193(core-js@3.47.0)(echarts@6.1.0)(resize-detector@0.3.0)(supports-color@8.1.1)(vue-router@4.6.4(vue@3.5.30(typescript@5.7.3)))(vue@3.5.30(typescript@5.7.3)) '@gouvfr/dsfr': specifier: ~1.14.4 version: 1.14.4 @@ -415,8 +415,8 @@ packages: '@cypress/xvfb@1.2.4': resolution: {integrity: sha512-skbBzPggOVYCbnGgV+0dmBdW/s77ZkAOXIC1knS8NagwDjBrNC1LuXtQJeiN6l+m7lzmHtaoUw/ctJKdqkG57Q==} - '@datagouv/components-next@1.3.0': - resolution: {integrity: sha512-Y5/Gev4bOCGIURJpF9eXB8pAPIEyQ0co8FLglLSy24HYNixlyXgfTwF8PA4+lSkZn+xrQ9sbimQ64XEdmvWy9Q==} + '@datagouv/components-next@1.0.2-dev.193': + resolution: {integrity: sha512-TNAyHzP4ybMnMVHgThw90NYe56al1o5ZJa2o9QCe+4TSrgkKiwcSFLOsWXU/qZj0pi6xg5rkQdH/yWZnEndSUA==} engines: {node: '>= 20.0.0', npm: use-pnpm-instead, pnpm: '>= 10.0.0', yarn: use-pnpm-instead} peerDependencies: echarts: ^6.0.0 @@ -617,17 +617,19 @@ packages: resolution: {integrity: sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@floating-ui/core@1.7.5': - resolution: {integrity: sha512-1Ih4WTWyw0+lKyFMcBHGbb5U5FtuHJuujoyyr5zTaWS5EYMeT6Jb2AuDeftsCsEuchO+mM2ij5+q9crhydzLhQ==} + '@floating-ui/core@1.8.0': + resolution: {integrity: sha512-0CIZ5itps/8x7BG8dEIhs53BvCUH2PCoogtakwRTut+Arm58sJooJ0AuZhLw2HJYIR5cMLNPBSS728sPho2khQ==} - '@floating-ui/dom@1.7.6': - resolution: {integrity: sha512-9gZSAI5XM36880PPMm//9dfiEngYoC6Am2izES1FF406YFsjvyBMmeJ2g4SAju3xWwtuynNRFL2s9hgxpLI5SQ==} + '@floating-ui/dom@1.8.0': + resolution: {integrity: sha512-yXSrzeHZBTZadLOlfyhCkJHNeLJnHRnRInwdZ40L7ZiaAtrBwoYlsDrX3v5zB1Utk7CLfzcOVnVVWoXEky7Ceg==} - '@floating-ui/utils@0.2.11': - resolution: {integrity: sha512-RiB/yIh78pcIxl6lLMG0CgBXAZ2Y0eVHqMPYugu+9U0AeT6YBeiJpf7lbdJNIugFP5SIjwNRgo4DhR1Qxi26Gg==} + '@floating-ui/utils@0.2.12': + resolution: {integrity: sha512-HpCo8tmWzLVad5s2d19EhAz5zqrrQ6s69qd6moPMQvkOuSwDT1YgRfWSVuc4ennqrgv3OHppiOGMQ7oC13yIww==} - '@floating-ui/vue@1.1.11': - resolution: {integrity: sha512-HzHKCNVxnGS35r9fCHBc3+uCnjw9IWIlCPL683cGgM9Kgj2BiAl8x1mS7vtvP6F9S/e/q4O6MApwSHj8hNLGfw==} + '@floating-ui/vue@2.0.1': + resolution: {integrity: sha512-043dyEKD9TDSfz080YH2vobubgg+NIo/9Fw4RAl7sEMpD8Am1DNrrSTl/GpMBjyGHoD/FbJY2QLawY+HSQsEIA==} + peerDependencies: + vue: '>=3.3.0' '@gouvfr/dsfr-chart@2.0.4': resolution: {integrity: sha512-/JoW6R7ystExxvuLvHacR0ZWcL1PpDkUyG/niF8jgqRaksWgb31EpJGQjmgaHR29nrANdyvoRb1DEQAL00XZ1A==} @@ -1623,6 +1625,7 @@ packages: '@xmldom/xmldom@0.9.10': resolution: {integrity: sha512-A9gOqLdi6cV4ibazAjcQufGj0B1y/vDqYrcuP6d/6x8P27gRS8643Dj9o1dEKtB6O7fwxb2FgBmJS2mX7gpvdw==} engines: {node: '>=14.6'} + deprecated: this version has critical issues, please update to the latest version '@zarrita/storage@0.2.0': resolution: {integrity: sha512-855ZXqtnds7spnT8vNvD+MXa3QExP1m2GqShe8yt7uZXHnQLgJHgkpVwFjE1B0KDDRO0ki09hmk6OboTaIfPsQ==} @@ -2333,6 +2336,7 @@ packages: eslint@9.39.1: resolution: {integrity: sha512-BhHmn2yNOFA9H9JmmIVKJmd288g9hrVRDkdoIgRCRuSySRUHH7r/DI6aAXW9T1WwUuY3DFgrcaqB+deURBLR5g==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + deprecated: This version is no longer supported. Please see https://eslint.org/version-support for other options. hasBin: true peerDependencies: jiti: '*' @@ -2571,10 +2575,6 @@ packages: resolution: {integrity: sha512-TC0NKVHcJZ9QvkEDyuMB327lT0xQIcZoIE3aSNOHN4Bkt1TcF+D/zAMQTl2AVTAYdrLAqFl6bhTWn5r7VJvkpw==} engines: {node: '>=20'} - geotiff@2.1.3: - resolution: {integrity: sha512-PT6uoF5a1+kbC3tHmZSUsLHBp2QJlHasxxxxPW47QIY1VBKpFB+FcDvX+MxER6UzgLQZ0xDzJ9s48B9JbOCTqA==} - engines: {node: '>=10.19'} - geotiff@3.0.5: resolution: {integrity: sha512-OWcL9S9+yDZ6iAlXMt32T1iwUApJM8UiD47xbm6ZP1h33d10fqkPs14EG/ttT5EnefpZSx3G15iDFC5FxUNUwA==} engines: {node: '>=10.19'} @@ -3567,9 +3567,6 @@ packages: peerDependencies: ol: '*' - ol@10.7.0: - resolution: {integrity: sha512-122U5gamPqNgLpLOkogFJhgpywvd/5en2kETIDW+Ubfi9lPnZ0G9HWRdG+CX0oP8od2d6u6ky3eewIYYlrVczw==} - ol@10.9.0: resolution: {integrity: sha512-svbbgVQUmEHaKpLQ8kRySojs59Brvgl2zYIrqG9eQNXGfsbi55rQasZIDpwpQzDL6OlzrUb0H4hQaiX9wDoGmA==} @@ -4414,9 +4411,6 @@ packages: unist-util-visit@4.1.2: resolution: {integrity: sha512-MSd8OUGISqHdVvfY9TPhyK2VdUrPgxkUtWSuMHF6XAAFuL4LokseigBnZtPnJMu+FbynTkFNnFlyjxpVKujMRg==} - unist-util-visit@5.0.0: - resolution: {integrity: sha512-MR04uvD+07cwl/yhVuVWAtw+3GOR/knlL55Nd/wAdblk27GCVt3lqpTivy/tkJcZoNPzTwS1Y+KMojlLDhoTzg==} - unist-util-visit@5.1.0: resolution: {integrity: sha512-m+vIdyeCOpdr/QeQCu2EzxX/ohgS8KbnPDgFni4dQsfSCtpz8UqDyY5GjRru8PDKuYn7Fq19j1CQ+nJSsGKOzg==} @@ -4882,9 +4876,6 @@ packages: zrender@6.1.0: resolution: {integrity: sha512-oEGMDB6pOP2S6OwRR4PdVv610zrjnA3Bh+JnSG12fYJlBKjtNAoEb5fSUoCOOINlH96I2fU38/A2UpRKs67xYQ==} - zstddec@0.1.0: - resolution: {integrity: sha512-w2NTI8+3l3eeltKAdK8QpiLo/flRAr2p8AGeakfMZOXBxOg9HIu4LVDxBi81sYgVhFhdJjv1OrB5ssI8uFPoLg==} - zstddec@0.2.0: resolution: {integrity: sha512-oyPnDa1X5c13+Y7mA/FDMNJrn4S8UNBe0KCqtDmor40Re7ALrPN6npFwyYVRRh+PqozZQdeg23QtbcamZnG5rA==} @@ -5189,9 +5180,9 @@ snapshots: transitivePeerDependencies: - supports-color - '@datagouv/components-next@1.3.0(core-js@3.47.0)(echarts@6.1.0)(resize-detector@0.3.0)(supports-color@8.1.1)(vue-router@4.6.4(vue@3.5.30(typescript@5.7.3)))(vue@3.5.30(typescript@5.7.3))': + '@datagouv/components-next@1.0.2-dev.193(core-js@3.47.0)(echarts@6.1.0)(resize-detector@0.3.0)(supports-color@8.1.1)(vue-router@4.6.4(vue@3.5.30(typescript@5.7.3)))(vue@3.5.30(typescript@5.7.3))': dependencies: - '@floating-ui/vue': 1.1.11(vue@3.5.30(typescript@5.7.3)) + '@floating-ui/vue': 2.0.1(vue@3.5.30(typescript@5.7.3)) '@headlessui/vue': 1.7.23(vue@3.5.30(typescript@5.7.3)) '@remixicon/vue': 4.9.0(vue@3.5.30(typescript@5.7.3)) '@types/hast': 3.0.4 @@ -5234,7 +5225,6 @@ snapshots: transitivePeerDependencies: - '@nuxt/kit' - '@nuxt/schema' - - '@vue/composition-api' - core-js - encoding - nuxt @@ -5365,25 +5355,22 @@ snapshots: '@eslint/core': 0.17.0 levn: 0.4.1 - '@floating-ui/core@1.7.5': + '@floating-ui/core@1.8.0': dependencies: - '@floating-ui/utils': 0.2.11 + '@floating-ui/utils': 0.2.12 - '@floating-ui/dom@1.7.6': + '@floating-ui/dom@1.8.0': dependencies: - '@floating-ui/core': 1.7.5 - '@floating-ui/utils': 0.2.11 + '@floating-ui/core': 1.8.0 + '@floating-ui/utils': 0.2.12 - '@floating-ui/utils@0.2.11': {} + '@floating-ui/utils@0.2.12': {} - '@floating-ui/vue@1.1.11(vue@3.5.30(typescript@5.7.3))': + '@floating-ui/vue@2.0.1(vue@3.5.30(typescript@5.7.3))': dependencies: - '@floating-ui/dom': 1.7.6 - '@floating-ui/utils': 0.2.11 - vue-demi: 0.14.10(vue@3.5.30(typescript@5.7.3)) - transitivePeerDependencies: - - '@vue/composition-api' - - vue + '@floating-ui/dom': 1.8.0 + '@floating-ui/utils': 0.2.12 + vue: 3.5.30(typescript@5.7.3) '@gouvfr/dsfr-chart@2.0.4': {} @@ -7460,9 +7447,9 @@ snapshots: geoportal-access-lib: 3.4.6 loglevel: 1.9.2 marked: 16.4.2 - ol: 10.7.0 - ol-contextmenu: 5.5.0(ol@10.7.0) - ol-mapbox-style: 12.6.1(ol@10.7.0) + ol: 10.9.0 + ol-contextmenu: 5.5.0(ol@10.9.0) + ol-mapbox-style: 12.6.1(ol@10.9.0) proj4: 2.15.0 sortablejs: 1.15.3 typescript: 5.7.3 @@ -7480,17 +7467,6 @@ snapshots: transitivePeerDependencies: - encoding - geotiff@2.1.3: - dependencies: - '@petamoriken/float16': 3.9.3 - lerc: 3.0.0 - pako: 2.1.0 - parse-headers: 2.0.6 - quick-lru: 6.1.2 - web-worker: 1.5.0 - xml-utils: 1.10.2 - zstddec: 0.1.0 - geotiff@3.0.5: dependencies: '@petamoriken/float16': 3.9.3 @@ -7653,7 +7629,7 @@ snapshots: mdast-util-to-hast: 13.2.1 parse5: 7.3.0 unist-util-position: 5.0.0 - unist-util-visit: 5.0.0 + unist-util-visit: 5.1.0 vfile: 6.0.3 web-namespaces: 2.0.1 zwitch: 2.0.4 @@ -8348,7 +8324,7 @@ snapshots: micromark-util-sanitize-uri: 2.0.1 trim-lines: 3.0.1 unist-util-position: 5.0.0 - unist-util-visit: 5.0.0 + unist-util-visit: 5.1.0 vfile: 6.0.3 mdast-util-to-markdown@0.6.5: @@ -8369,7 +8345,7 @@ snapshots: mdast-util-to-string: 4.0.0 micromark-util-classify-character: 2.0.1 micromark-util-decode-string: 2.0.1 - unist-util-visit: 5.0.0 + unist-util-visit: 5.1.0 zwitch: 2.0.4 mdast-util-to-string@2.0.0: {} @@ -8764,24 +8740,16 @@ snapshots: node-fetch-native: 1.6.7 ufo: 1.6.1 - ol-contextmenu@5.5.0(ol@10.7.0): + ol-contextmenu@5.5.0(ol@10.9.0): dependencies: - ol: 10.7.0 + ol: 10.9.0 tiny-emitter: 2.1.0 - ol-mapbox-style@12.6.1(ol@10.7.0): + ol-mapbox-style@12.6.1(ol@10.9.0): dependencies: '@maplibre/maplibre-gl-style-spec': 23.3.0 mapbox-to-css-font: 3.2.0 - ol: 10.7.0 - - ol@10.7.0: - dependencies: - '@types/rbush': 4.0.0 - earcut: 3.0.2 - geotiff: 2.1.3 - pbf: 4.0.1 - rbush: 4.0.1 + ol: 10.9.0 ol@10.9.0: dependencies: @@ -9086,7 +9054,7 @@ snapshots: '@types/hast': 3.0.4 hast-util-to-text: 4.0.2 lowlight: 3.3.0 - unist-util-visit: 5.0.0 + unist-util-visit: 5.1.0 vfile: 6.0.3 rehype-raw@7.0.0: @@ -9106,7 +9074,7 @@ snapshots: github-slugger: 2.0.0 hast-util-heading-rank: 3.0.0 hast-util-to-string: 3.0.1 - unist-util-visit: 5.0.0 + unist-util-visit: 5.1.0 rehype-stringify@10.0.1: dependencies: @@ -9748,12 +9716,6 @@ snapshots: unist-util-is: 5.2.1 unist-util-visit-parents: 5.1.3 - unist-util-visit@5.0.0: - dependencies: - '@types/unist': 3.0.3 - unist-util-is: 6.0.1 - unist-util-visit-parents: 6.0.2 - unist-util-visit@5.1.0: dependencies: '@types/unist': 3.0.3 @@ -10206,8 +10168,6 @@ snapshots: dependencies: tslib: 2.3.0 - zstddec@0.1.0: {} - zstddec@0.2.0: {} zwitch@1.0.5: {} diff --git a/src/components/datasets/NewResourceExplorer.vue b/src/components/datasets/NewResourceExplorer.vue new file mode 100644 index 000000000..d934b6a84 --- /dev/null +++ b/src/components/datasets/NewResourceExplorer.vue @@ -0,0 +1,47 @@ + + + diff --git a/src/components/datasets/ResourcesTabContent.vue b/src/components/datasets/ResourcesTabContent.vue new file mode 100644 index 000000000..386c4f9e7 --- /dev/null +++ b/src/components/datasets/ResourcesTabContent.vue @@ -0,0 +1,70 @@ + + + diff --git a/src/custom/ecospheres/views/indicators/IndicatorDetailView.vue b/src/custom/ecospheres/views/indicators/IndicatorDetailView.vue index 8e97afd1c..f64348cf2 100644 --- a/src/custom/ecospheres/views/indicators/IndicatorDetailView.vue +++ b/src/custom/ecospheres/views/indicators/IndicatorDetailView.vue @@ -9,7 +9,7 @@ import DatasetAddToTopicModal from '@/components/datasets/DatasetAddToTopicModal import DatasetDataservicesList from '@/components/datasets/DatasetDataservicesList.vue' import DatasetReusesList from '@/components/datasets/DatasetReusesList.vue' import DatasetSidebar from '@/components/datasets/DatasetSidebar.vue' -import ResourcesList from '@/components/datasets/ResourcesList.vue' +import ResourcesTabContent from '@/components/datasets/ResourcesTabContent.vue' import config from '@/config' import IndicatorVizChart from '@/custom/ecospheres/components/indicators/viz/IndicatorVizChart.vue' import { useRouteParamsAsString } from '@/router/utils' @@ -157,7 +157,7 @@ onMounted(() => { > - diff --git a/src/main.ts b/src/main.ts index 88ae960f9..638115223 100644 --- a/src/main.ts +++ b/src/main.ts @@ -59,6 +59,7 @@ routerPromise app.use(datagouv, { name: 'data.gouv.fr', baseUrl: config.datagouvfr.base_url, + trustedDomains: ['data.gouv.fr'], // hostnames allowed in Access-Control-Allow-Origin for resource preview CORS checks (includes subdomains, e.g. demo.data.gouv.fr) apiBase: config.datagouvfr.base_url, tabularApiUrl: config.datagouvfr.tabular_api_url, tabularAllowRemote: true, diff --git a/src/model/config.ts b/src/model/config.ts index fb5146b29..fa6c874f5 100644 --- a/src/model/config.ts +++ b/src/model/config.ts @@ -125,6 +125,7 @@ export type DatasetsConf = { } | null harvest_backends_quality_warning: string[] show_extended_information_panel: boolean + new_explorer_enabled?: boolean } interface Logo { diff --git a/src/router/index.ts b/src/router/index.ts index 1d7f9d8f9..26b64870d 100644 --- a/src/router/index.ts +++ b/src/router/index.ts @@ -129,6 +129,12 @@ const routerPromise = siteRoutesPromise.then((siteRoutes) => { if (to.meta.searchConfig && from.meta.searchConfig) { return false } + // Preserve scroll on dataset detail pages (datasets, indicators, ...): their + // ResourceExplorer navigates resources/tabs via router.replace on the same + // route, which isn't a page navigation. + if (to.path === from.path && to.meta.objectType === 'datasets') { + return false + } if (savedPosition !== null) { return savedPosition } else { diff --git a/src/router/utils.ts b/src/router/utils.ts index 4fea65bd2..702def168 100644 --- a/src/router/utils.ts +++ b/src/router/utils.ts @@ -269,6 +269,7 @@ export const useGlobalSearchPageRoutes = ({ component: detailsViewComponent ?? defaultDetailsViews[objectType], meta: { pageKey, + objectType, descriptionComponent, cardComponent, datasetCardComponent diff --git a/src/typings/route-meta.d.ts b/src/typings/route-meta.d.ts index da2692bb6..71803e68f 100644 --- a/src/typings/route-meta.d.ts +++ b/src/typings/route-meta.d.ts @@ -14,6 +14,9 @@ declare module 'vue-router' { datasetCardComponent?: () => Promise<{ default: Component }> descriptionComponent?: () => Promise<{ default: Component }> pageKey?: string + // Detail page's object type (e.g. to scope behaviour to dataset detail pages, + // regardless of pageKey - datasets, indicators, ...) + objectType?: PageObjectType // GlobalSearch-specific searchType?: PageObjectType searchConfig?: GlobalSearchConfig diff --git a/src/utils/newExplorer.ts b/src/utils/newExplorer.ts new file mode 100644 index 000000000..3ae4c1c95 --- /dev/null +++ b/src/utils/newExplorer.ts @@ -0,0 +1,29 @@ +import { ref } from 'vue' + +// Feature flag for the new resource navigation, shared across consumers and persisted in a cookie (mirrors cdata's own useNewExplorer). + +const COOKIE_NAME = 'new_explorer' +const COOKIE_MAX_AGE = 60 * 60 * 24 * 7 // 7 days + +function readCookie(name: string): string | null { + const match = document.cookie.match(new RegExp(`(?:^|; )${name}=([^;]*)`)) + return match ? decodeURIComponent(match[1]) : null +} + +function writeCookie(name: string, value: string | null) { + document.cookie = + value === null + ? `${name}=; path=/; max-age=0` + : `${name}=${encodeURIComponent(value)}; path=/; max-age=${COOKIE_MAX_AGE}` +} + +const enabled = ref(readCookie(COOKIE_NAME) === '1') + +export function useNewExplorer() { + function setEnabled(value: boolean) { + enabled.value = value + writeCookie(COOKIE_NAME, value ? '1' : null) + } + + return { enabled, setEnabled } +} diff --git a/src/views/datasets/DatasetDetailView.vue b/src/views/datasets/DatasetDetailView.vue index 54fde172f..c7a5a9bae 100644 --- a/src/views/datasets/DatasetDetailView.vue +++ b/src/views/datasets/DatasetDetailView.vue @@ -10,7 +10,7 @@ import DatasetInformationPanel from '@/components/datasets/DatasetInformationPan import DatasetReusesList from '@/components/datasets/DatasetReusesList.vue' import DatasetSidebar from '@/components/datasets/DatasetSidebar.vue' import ExtendedInformationPanel from '@/components/datasets/ExtendedInformationPanel.vue' -import ResourcesList from '@/components/datasets/ResourcesList.vue' +import ResourcesTabContent from '@/components/datasets/ResourcesTabContent.vue' import config from '@/config' import { useCurrentPageConf, useRouteParamsAsString } from '@/router/utils' import { useDatasetStore } from '@/store/DatasetStore' @@ -212,7 +212,7 @@ onMounted(() => { > - +
Date: Wed, 2 Sep 2026 12:16:32 +0200 Subject: [PATCH 02/15] add fullscreen --- configs/ecospheres/config.yaml | 2 +- src/components/datasets/NewResourceExplorer.vue | 15 ++++++++++----- src/components/datasets/ResourcesTabContent.vue | 6 ++---- src/router/index.ts | 4 +--- src/typings/route-meta.d.ts | 3 +-- 5 files changed, 15 insertions(+), 15 deletions(-) diff --git a/configs/ecospheres/config.yaml b/configs/ecospheres/config.yaml index aa573e6fd..a7579dc19 100644 --- a/configs/ecospheres/config.yaml +++ b/configs/ecospheres/config.yaml @@ -189,7 +189,7 @@ website: - CSW-DCAT - CSW-ISO-19139 show_extended_information_panel: true - # Spike: new @datagouv/components-next ResourceExplorer, opt-in via a banner toggle + # new @datagouv/components-next ResourceExplorer, opt-in via a banner toggle new_explorer_enabled: true # TODO: better place for that? topics: diff --git a/src/components/datasets/NewResourceExplorer.vue b/src/components/datasets/NewResourceExplorer.vue index d934b6a84..654deab66 100644 --- a/src/components/datasets/NewResourceExplorer.vue +++ b/src/components/datasets/NewResourceExplorer.vue @@ -1,12 +1,14 @@ + + From 3a3e44d3aaf3eea3ac2ed28c6844f90ab74a9cc7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alexandre=20Bult=C3=A9?= Date: Thu, 3 Sep 2026 09:50:10 +0200 Subject: [PATCH 07/15] comments --- src/components/datasets/NewResourceExplorer.vue | 7 ++----- src/views/datasets/DatasetExploreView.vue | 6 ++---- 2 files changed, 4 insertions(+), 9 deletions(-) diff --git a/src/components/datasets/NewResourceExplorer.vue b/src/components/datasets/NewResourceExplorer.vue index be8764e9f..e02cfe026 100644 --- a/src/components/datasets/NewResourceExplorer.vue +++ b/src/components/datasets/NewResourceExplorer.vue @@ -15,8 +15,7 @@ const props = defineProps({ type: Object as () => DatasetV2WithFullObject, required: true }, - // Route name to come back to when exiting the fullscreen explorer (datasets_detail, - // indicators_detail, ...) - both share object_type: datasets but have distinct pages. + // Route to return to on exit: datasets_detail and indicators_detail share object_type but are distinct pages. fromRouteName: { type: String, default: 'datasets_detail' @@ -27,9 +26,7 @@ const props = defineProps({ // @ts-expect-error dataset prop is typed as DatasetV2, not DatasetV2WithFullObject const datasetForExplorer: DatasetV2 = props.dataset -// Absolute URL, not a router-relative path: AppLink (used by the button ResourceExplorer -// renders this through) prepends `/${locale}` to any relative string href, since cdata's -// own routes are locale-prefixed - ours aren't, so that would 404 the link. +// Absolute URL, not a relative path: AppLink prepends `/${locale}` to relative hrefs, which would 404 here since our routes aren't locale-prefixed. const router = useRouter() const exploreTo = (resource: Resource) => `${window.location.origin}${ diff --git a/src/views/datasets/DatasetExploreView.vue b/src/views/datasets/DatasetExploreView.vue index 5d6729704..29f8d20ec 100644 --- a/src/views/datasets/DatasetExploreView.vue +++ b/src/views/datasets/DatasetExploreView.vue @@ -9,8 +9,7 @@ import BlankState from '@/components/BlankState.vue' import { useDatasetStore } from '@/store/DatasetStore' import { useMeta } from '@/utils/seo' -// Plain useRoute, not useRouteParamsAsString: exitTo must stay reactive to -// resource_id, which ResourceExplorer rewrites via router.replace on every switch. +// Plain useRoute, not useRouteParamsAsString: exitTo must stay reactive to resource_id, rewritten by ResourceExplorer on every switch. const route = useRoute() const itemId = route.params.item_id as string @@ -22,8 +21,7 @@ const datasetForExplorer = computed( () => dataset.value as unknown as DatasetV2 | undefined ) -// Carries the visitor back to wherever they came from (datasets or indicators), on the -// resource they were viewing - the origin route name and resource_id travel in via query. +// Origin route name and resource_id travel in via query, to carry the visitor back to where they were. const exitTo = computed(() => { const fromRouteName = typeof route.query.from === 'string' ? route.query.from : 'datasets_detail' From 0f6d90e6d475a22740decaaf83a0d27a74f4e4b0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alexandre=20Bult=C3=A9?= Date: Thu, 3 Sep 2026 09:57:11 +0200 Subject: [PATCH 08/15] rename all the thingz --- configs/ecospheres/config.yaml | 4 +-- ...ourceExplorer.vue => ResourceExplorer.vue} | 6 ++-- .../datasets/ResourcesTabContent.vue | 30 +++++++++---------- src/model/config.ts | 2 +- src/router/index.ts | 6 ++-- src/utils/explorer.ts | 21 +++++++++++++ src/utils/newExplorer.ts | 21 ------------- src/views/datasets/DatasetDetailView.vue | 8 ++--- 8 files changed, 49 insertions(+), 49 deletions(-) rename src/components/datasets/{NewResourceExplorer.vue => ResourceExplorer.vue} (88%) create mode 100644 src/utils/explorer.ts delete mode 100644 src/utils/newExplorer.ts diff --git a/configs/ecospheres/config.yaml b/configs/ecospheres/config.yaml index a7579dc19..e7ed1e077 100644 --- a/configs/ecospheres/config.yaml +++ b/configs/ecospheres/config.yaml @@ -189,8 +189,8 @@ website: - CSW-DCAT - CSW-ISO-19139 show_extended_information_panel: true - # new @datagouv/components-next ResourceExplorer, opt-in via a banner toggle - new_explorer_enabled: true + # @datagouv/components-next ResourceExplorer, opt-in via a banner toggle + resources_explorer_enabled: true # TODO: better place for that? topics: can_add_topics: diff --git a/src/components/datasets/NewResourceExplorer.vue b/src/components/datasets/ResourceExplorer.vue similarity index 88% rename from src/components/datasets/NewResourceExplorer.vue rename to src/components/datasets/ResourceExplorer.vue index e02cfe026..ede0e3c53 100644 --- a/src/components/datasets/NewResourceExplorer.vue +++ b/src/components/datasets/ResourceExplorer.vue @@ -4,7 +4,7 @@ import type { DatasetV2WithFullObject, Resource } from '@datagouv/components-next' -import { ResourceExplorer } from '@datagouv/components-next' +import { ResourceExplorer as DatagouvfrResourceExplorer } from '@datagouv/components-next' import { onErrorCaptured, ref } from 'vue' import { useRouter } from 'vue-router' @@ -26,8 +26,8 @@ const props = defineProps({ // @ts-expect-error dataset prop is typed as DatasetV2, not DatasetV2WithFullObject const datasetForExplorer: DatasetV2 = props.dataset -// Absolute URL, not a relative path: AppLink prepends `/${locale}` to relative hrefs, which would 404 here since our routes aren't locale-prefixed. const router = useRouter() +// Absolute URL needed for upstream component. const exploreTo = (resource: Resource) => `${window.location.origin}${ router.resolve({ @@ -52,7 +52,7 @@ onErrorCaptured(() => { message="Une erreur est survenue lors du chargement des ressources." /> - - diff --git a/src/model/config.ts b/src/model/config.ts index fa6c874f5..c0f31ce3f 100644 --- a/src/model/config.ts +++ b/src/model/config.ts @@ -125,7 +125,7 @@ export type DatasetsConf = { } | null harvest_backends_quality_warning: string[] show_extended_information_panel: boolean - new_explorer_enabled?: boolean + resources_explorer_enabled?: boolean } interface Logo { diff --git a/src/router/index.ts b/src/router/index.ts index 8a099c0a6..4a9d5624b 100644 --- a/src/router/index.ts +++ b/src/router/index.ts @@ -2,7 +2,7 @@ import { createRouter, createWebHistory, type RouteRecordRaw } from 'vue-router' import config from '@/config' import type { StaticPageConfig } from '@/model/config' -import { useNewExplorer } from '@/utils/newExplorer' +import { useExplorer } from '@/utils/explorer' import NotFoundView from '@/views/NotFoundView.vue' import StaticPageView from '@/views/StaticPageView.vue' @@ -17,7 +17,7 @@ const defaultRoutes: RouteRecordRaw[] = [ }, component: async () => await import('@/views/HomeView.vue') }, - // fullscreen resource explorer, opt-in per site (see useNewExplorer) + // fullscreen resource explorer, opt-in per site (see useExplorer) { path: '/explore/:item_id', name: 'explore', @@ -29,7 +29,7 @@ const defaultRoutes: RouteRecordRaw[] = [ component: async () => await import('@/views/datasets/DatasetExploreView.vue'), beforeEnter: (to) => { - const { eligible } = useNewExplorer() + const { eligible } = useExplorer() if (!eligible.value) { return { name: 'datasets_detail', diff --git a/src/utils/explorer.ts b/src/utils/explorer.ts new file mode 100644 index 000000000..89d7cd778 --- /dev/null +++ b/src/utils/explorer.ts @@ -0,0 +1,21 @@ +import { useStorage } from '@vueuse/core' +import { computed } from 'vue' + +import { useDatasetsConf } from '@/utils/config' + +// Feature flag for the resource explorer, shared across consumers and persisted in localStorage. +const stored = useStorage('resources_explorer_enabled', false) + +export function useExplorer() { + // Whether this site opted into the explorer at all, independent of the user's own toggle. + const eligible = computed( + () => useDatasetsConf().resources_explorer_enabled === true + ) + const enabled = computed(() => eligible.value && stored.value) + + return { + eligible, + enabled, + setEnabled: (value: boolean) => (stored.value = value) + } +} diff --git a/src/utils/newExplorer.ts b/src/utils/newExplorer.ts deleted file mode 100644 index 7b5fc8a60..000000000 --- a/src/utils/newExplorer.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { useStorage } from '@vueuse/core' -import { computed } from 'vue' - -import { useDatasetsConf } from '@/utils/config' - -// Feature flag for the new resource navigation, shared across consumers and persisted in localStorage. -const stored = useStorage('new_explorer', false) - -export function useNewExplorer() { - // Whether this site opted into the new explorer at all, independent of the user's own toggle. - const eligible = computed( - () => useDatasetsConf().new_explorer_enabled === true - ) - const enabled = computed(() => eligible.value && stored.value) - - return { - eligible, - enabled, - setEnabled: (value: boolean) => (stored.value = value) - } -} diff --git a/src/views/datasets/DatasetDetailView.vue b/src/views/datasets/DatasetDetailView.vue index ab52d0ae5..3e2527910 100644 --- a/src/views/datasets/DatasetDetailView.vue +++ b/src/views/datasets/DatasetDetailView.vue @@ -18,8 +18,8 @@ import { useResourceStore } from '@/store/ResourceStore' import { useUserStore } from '@/store/UserStore' import { descriptionFromMarkdown } from '@/utils' import { useDatasetsConf, usePageConf } from '@/utils/config' +import { useExplorer } from '@/utils/explorer' import { useLabels } from '@/utils/labels' -import { useNewExplorer } from '@/utils/newExplorer' import type { OgcLayerInfo } from '@/utils/ogcServices' import { fetchAllOgcResources } from '@/utils/ogcServices' import { openInQgis } from '@/utils/qgis' @@ -62,7 +62,7 @@ const topicPageKey = datasetsConf.add_to_topic?.page const topicPageConf = topicPageKey ? usePageConf(topicPageKey) : null const labels = useLabels(pageConf.labels) const topicLabels = topicPageConf ? useLabels(topicPageConf.labels) : null -const { enabled: newExplorerEnabled } = useNewExplorer() +const { enabled: explorerEnabled } = useExplorer() const canEdit = computed(() => { return pageConf.editable && userStore.hasEditPermissions(dataset.value) @@ -159,7 +159,7 @@ onMounted(() => {
@@ -180,7 +180,7 @@ onMounted(() => { /> Date: Thu, 3 Sep 2026 10:00:48 +0200 Subject: [PATCH 09/15] useResourceExplorer --- src/components/datasets/ResourceExplorer.vue | 4 +-- .../datasets/ResourcesTabContent.vue | 30 +++++++++++-------- src/router/index.ts | 6 ++-- src/utils/explorer.ts | 2 +- src/views/datasets/DatasetDetailView.vue | 8 ++--- 5 files changed, 27 insertions(+), 23 deletions(-) diff --git a/src/components/datasets/ResourceExplorer.vue b/src/components/datasets/ResourceExplorer.vue index ede0e3c53..15a559983 100644 --- a/src/components/datasets/ResourceExplorer.vue +++ b/src/components/datasets/ResourceExplorer.vue @@ -15,14 +15,14 @@ const props = defineProps({ type: Object as () => DatasetV2WithFullObject, required: true }, - // Route to return to on exit: datasets_detail and indicators_detail share object_type but are distinct pages. + // Route name to return to on exit from full screen mode. fromRouteName: { type: String, default: 'datasets_detail' } }) -// FIXME: ResourceExplorer should accept DatasetV2WithFullObject — same upstream bug as ResourceAccordion (see ResourcesList.vue) +// FIXME: ResourceExplorer should accept DatasetV2WithFullObject; same upstream bug as ResourceAccordion. // @ts-expect-error dataset prop is typed as DatasetV2, not DatasetV2WithFullObject const datasetForExplorer: DatasetV2 = props.dataset diff --git a/src/components/datasets/ResourcesTabContent.vue b/src/components/datasets/ResourcesTabContent.vue index c3abf705c..582cc59e8 100644 --- a/src/components/datasets/ResourcesTabContent.vue +++ b/src/components/datasets/ResourcesTabContent.vue @@ -5,7 +5,7 @@ import { useRoute, useRouter } from 'vue-router' import ResourceExplorer from '@/components/datasets/ResourceExplorer.vue' import ResourcesList from '@/components/datasets/ResourcesList.vue' -import { useExplorer } from '@/utils/explorer' +import { useResourceExplorer } from '@/utils/explorer' defineProps({ dataset: { @@ -25,15 +25,15 @@ defineProps({ const route = useRoute() const router = useRouter() const { - eligible: explorerEligible, - enabled: explorerEnabled, - setEnabled: setExplorerEnabled -} = useExplorer() + eligible: resourceExplorerEligible, + enabled: resourceExplorerEnabled, + setEnabled: setResourceExplorerEnabled +} = useResourceExplorer() // Drops ?resource_id when reverting: it doesn't carry the same meaning on the old navigation. -function toggleExplorer() { - const enable = !explorerEnabled.value - setExplorerEnabled(enable) +function toggleResourceExplorer() { + const enable = !resourceExplorerEnabled.value + setResourceExplorerEnabled(enable) if (!enable && route.query.resource_id) { const { resource_id, ...query } = route.query router.replace({ query }) @@ -43,19 +43,23 @@ function toggleExplorer() { Date: Thu, 3 Sep 2026 10:03:05 +0200 Subject: [PATCH 10/15] chrome -> fullscreen --- src/App.vue | 8 ++++---- src/router/index.ts | 2 +- src/typings/route-meta.d.ts | 4 ++-- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/App.vue b/src/App.vue index 86275da26..280b5d1de 100644 --- a/src/App.vue +++ b/src/App.vue @@ -122,13 +122,13 @@ provide(AccessibilityPropertiesKey, setAccessibilityProperties)
@@ -136,7 +136,7 @@ provide(AccessibilityPropertiesKey, setAccessibilityProperties) Date: Thu, 3 Sep 2026 10:35:22 +0200 Subject: [PATCH 11/15] skiplinks for fullscreen --- src/App.vue | 1 + src/views/datasets/DatasetExploreView.vue | 36 +++++++++++++++++++++-- 2 files changed, 34 insertions(+), 3 deletions(-) diff --git a/src/App.vue b/src/App.vue index 280b5d1de..634432905 100644 --- a/src/App.vue +++ b/src/App.vue @@ -121,6 +121,7 @@ provide(AccessibilityPropertiesKey, setAccessibilityProperties)