Compare commits

..

18 Commits

Author SHA1 Message Date
d91e74f61e correction bande blanche cover etape 2025-08-26 16:50:43 +02:00
c5c1456e80 display bad ressources 2025-06-26 16:13:32 +02:00
c2174b27c0 display badly filled partenaires 2025-06-26 15:49:00 +02:00
6f53afa8bf correction footer modale 2025-06-26 15:35:49 +02:00
ac51fc987c ajout du readme dans src 2025-06-26 15:28:44 +02:00
f44fbd8d06 rerefactor du fetching de contenus une mielleure ux au load des modales (description dans le readme) 2025-06-26 15:26:18 +02:00
e85851bd4d MODIF CONFIGS DRUPAL centre de ressources: tri par étape + ressources mis en avant 2025-05-22 00:51:02 +02:00
bfb39a0259 filtre ressources par étape + triées par ordre chrono 2025-05-20 20:07:03 +02:00
e7a5e163ff réajout du field type de ressource 2025-04-14 16:03:11 +02:00
95e1b02760 suppression du field type de ressource qui bug en prod 2025-04-14 14:24:40 +02:00
b12af44825 test ci-cd 2025-04-07 19:33:55 +02:00
d90aa2726b test ci-cd 2025-04-07 18:35:29 +02:00
7387ce7f50 EXPORT CONFIG ! fin du centre de ressource 2025-04-02 01:06:39 +02:00
7792005403 test ci/cd 2025-03-28 18:16:12 +01:00
b538e754d2 test ci/cd 2025-03-28 16:05:00 +01:00
d805ef35b1 avancées centre de ressources 2025-03-28 15:10:03 +01:00
a1916e3219 avancées centre de ressource 2025-03-26 00:28:41 +01:00
9093caa557 MODIFS CONFIGS : fetch ressources content 2025-03-11 23:53:31 +01:00
57 changed files with 1698 additions and 479 deletions

View File

@ -0,0 +1,11 @@
features:
node_user_picture: false
comment_user_picture: true
comment_user_verification: true
favicon: 0
logo:
use_default: 0
path: 'public://favicon-caravane_1.png'
favicon:
use_default: 0
path: ''

View File

@ -1,4 +1,4 @@
uuid: b0c240c8-beea-4be3-96b8-6543b6734b25 uuid: 39f683b9-ea9f-4e84-8dec-c30162c19b72
langcode: fr langcode: fr
status: true status: true
dependencies: { } dependencies: { }
@ -12,6 +12,6 @@ context:
fallback: fallback:
language: '' language: ''
menu: menu:
path: /edit/partenaire path: /edit/partenaires
weight: 0 weight: 0
description: '' description: ''

View File

@ -1,9 +1,9 @@
uuid: 86c7ea1e-a93e-4ddf-9887-e1f302a99ea9 uuid: 67828fd5-674e-4e52-9f2f-3457e06c0baa
langcode: fr langcode: fr
status: true status: true
dependencies: { } dependencies: { }
id: intro_partenaires id: intro_ressource
label: 'Intro partenaires' label: 'intro ressource'
token: false token: false
context: context:
show_warning: true show_warning: true
@ -12,6 +12,6 @@ context:
fallback: fallback:
language: '' language: ''
menu: menu:
path: /edit/partenaires path: /edit/ressource
weight: 0 weight: 0
description: '' description: ''

View File

@ -1,4 +1,4 @@
uuid: c8c782e1-e597-4e4d-ac96-65142dbadedb uuid: 3d095235-90fe-4b5f-ae32-ef10c26c1f47
langcode: fr langcode: fr
status: true status: true
dependencies: dependencies:

View File

@ -1,24 +0,0 @@
uuid: 03e534f2-ceae-4f84-89f2-a0f9997f8a9d
langcode: fr
status: true
dependencies:
config:
- config_pages.type.intro_partenaires
- field.field.config_pages.intro_partenaires.field_intro
module:
- text
id: config_pages.intro_partenaires.default
targetEntityType: config_pages
bundle: intro_partenaires
mode: default
content:
field_intro:
type: text_textarea
weight: 0
region: content
settings:
rows: 5
placeholder: ''
third_party_settings: { }
hidden:
label: true

View File

@ -0,0 +1,33 @@
uuid: c5c09811-6a65-4cff-9936-78dc896b9adf
langcode: fr
status: true
dependencies:
config:
- config_pages.type.intro_ressource
- field.field.config_pages.intro_ressource.field_intro
- field.field.config_pages.intro_ressource.field_titre
module:
- text
id: config_pages.intro_ressource.default
targetEntityType: config_pages
bundle: intro_ressource
mode: default
content:
field_intro:
type: text_textarea
weight: 1
region: content
settings:
rows: 5
placeholder: ''
third_party_settings: { }
field_titre:
type: string_textfield
weight: 0
region: content
settings:
size: 60
placeholder: ''
third_party_settings: { }
hidden:
label: true

View File

@ -7,6 +7,7 @@ dependencies:
- field.field.node.ressource.field_date_ressource - field.field.node.ressource.field_date_ressource
- field.field.node.ressource.field_etape - field.field.node.ressource.field_etape
- field.field.node.ressource.field_introduction - field.field.node.ressource.field_introduction
- field.field.node.ressource.field_mis_en_avant
- field.field.node.ressource.field_parties_ressource - field.field.node.ressource.field_parties_ressource
- field.field.node.ressource.field_thematiques - field.field.node.ressource.field_thematiques
- field.field.node.ressource.field_type_de_ressource - field.field.node.ressource.field_type_de_ressource
@ -22,11 +23,12 @@ third_party_settings:
field_group: field_group:
group_details: group_details:
children: children:
- field_type_de_ressource
- field_thematiques - field_thematiques
- field_etape - field_etape
- status - status
- path - path
- field_mis_en_avant
- field_type_de_ressource
label: Details label: Details
region: content region: content
parent_name: '' parent_name: ''
@ -112,7 +114,7 @@ content:
third_party_settings: { } third_party_settings: { }
field_etape: field_etape:
type: entity_reference_autocomplete type: entity_reference_autocomplete
weight: 5 weight: 28
region: content region: content
settings: settings:
match_operator: CONTAINS match_operator: CONTAINS
@ -128,6 +130,13 @@ content:
rows: 5 rows: 5
placeholder: '' placeholder: ''
third_party_settings: { } third_party_settings: { }
field_mis_en_avant:
type: boolean_checkbox
weight: 31
region: content
settings:
display_label: true
third_party_settings: { }
field_parties_ressource: field_parties_ressource:
type: paragraphs type: paragraphs
weight: 5 weight: 5
@ -148,7 +157,7 @@ content:
third_party_settings: { } third_party_settings: { }
field_thematiques: field_thematiques:
type: autocomplete_deluxe type: autocomplete_deluxe
weight: 4 weight: 27
region: content region: content
settings: settings:
match_operator: CONTAINS match_operator: CONTAINS
@ -164,24 +173,20 @@ content:
no_empty_message: 'No terms could be found. Please type in order to add a new term.' no_empty_message: 'No terms could be found. Please type in order to add a new term.'
third_party_settings: { } third_party_settings: { }
field_type_de_ressource: field_type_de_ressource:
type: entity_reference_autocomplete type: options_select
weight: 3 weight: 32
region: content region: content
settings: settings: { }
match_operator: CONTAINS
match_limit: 10
size: 60
placeholder: ''
third_party_settings: { } third_party_settings: { }
path: path:
type: path type: path
weight: 7 weight: 30
region: content region: content
settings: { } settings: { }
third_party_settings: { } third_party_settings: { }
status: status:
type: boolean_checkbox type: boolean_checkbox
weight: 6 weight: 29
region: content region: content
settings: settings:
display_label: true display_label: true

View File

@ -1,4 +1,4 @@
uuid: a79b589c-3286-425b-b508-38744f9ebeb1 uuid: cad2e853-7706-4477-a3cc-d810dcd76c96
langcode: fr langcode: fr
status: true status: true
dependencies: dependencies:
@ -18,7 +18,7 @@ content:
label: visually_hidden label: visually_hidden
settings: { } settings: { }
third_party_settings: { } third_party_settings: { }
weight: 1 weight: 0
region: content region: content
field_titre: field_titre:
type: string type: string
@ -26,7 +26,7 @@ content:
settings: settings:
link_to_entity: false link_to_entity: false
third_party_settings: { } third_party_settings: { }
weight: 0 weight: 1
region: content region: content
hidden: hidden:
search_api_excerpt: true search_api_excerpt: true

View File

@ -1,23 +0,0 @@
uuid: ac925def-bc83-4add-a5c9-88bc02e771b5
langcode: fr
status: true
dependencies:
config:
- config_pages.type.intro_partenaires
- field.field.config_pages.intro_partenaires.field_intro
module:
- text
id: config_pages.intro_partenaires.default
targetEntityType: config_pages
bundle: intro_partenaires
mode: default
content:
field_intro:
type: text_default
label: visually_hidden
settings: { }
third_party_settings: { }
weight: 0
region: content
hidden:
search_api_excerpt: true

View File

@ -0,0 +1,32 @@
uuid: 342c8cdf-4085-4e41-941f-b503beb0ad1f
langcode: fr
status: true
dependencies:
config:
- config_pages.type.intro_ressource
- field.field.config_pages.intro_ressource.field_intro
- field.field.config_pages.intro_ressource.field_titre
module:
- text
id: config_pages.intro_ressource.default
targetEntityType: config_pages
bundle: intro_ressource
mode: default
content:
field_intro:
type: text_default
label: visually_hidden
settings: { }
third_party_settings: { }
weight: 0
region: content
field_titre:
type: string
label: above
settings:
link_to_entity: false
third_party_settings: { }
weight: 0
region: content
hidden:
search_api_excerpt: true

View File

@ -7,6 +7,7 @@ dependencies:
- field.field.node.ressource.field_date_ressource - field.field.node.ressource.field_date_ressource
- field.field.node.ressource.field_etape - field.field.node.ressource.field_etape
- field.field.node.ressource.field_introduction - field.field.node.ressource.field_introduction
- field.field.node.ressource.field_mis_en_avant
- field.field.node.ressource.field_parties_ressource - field.field.node.ressource.field_parties_ressource
- field.field.node.ressource.field_thematiques - field.field.node.ressource.field_thematiques
- field.field.node.ressource.field_type_de_ressource - field.field.node.ressource.field_type_de_ressource
@ -14,6 +15,7 @@ dependencies:
module: module:
- datetime - datetime
- entity_reference_revisions - entity_reference_revisions
- options
- text - text
- user - user
id: node.ressource.default id: node.ressource.default
@ -53,6 +55,16 @@ content:
third_party_settings: { } third_party_settings: { }
weight: 107 weight: 107
region: content region: content
field_mis_en_avant:
type: boolean
label: above
settings:
format: default
format_custom_false: ''
format_custom_true: ''
third_party_settings: { }
weight: 109
region: content
field_parties_ressource: field_parties_ressource:
type: entity_reference_revisions_entity_view type: entity_reference_revisions_entity_view
label: above label: above
@ -71,12 +83,11 @@ content:
weight: 4 weight: 4
region: content region: content
field_type_de_ressource: field_type_de_ressource:
type: entity_reference_label type: list_default
label: above label: above
settings: settings: { }
link: true
third_party_settings: { } third_party_settings: { }
weight: 102 weight: 108
region: content region: content
links: links:
settings: { } settings: { }

View File

@ -8,6 +8,7 @@ dependencies:
- field.field.node.ressource.field_date_ressource - field.field.node.ressource.field_date_ressource
- field.field.node.ressource.field_etape - field.field.node.ressource.field_etape
- field.field.node.ressource.field_introduction - field.field.node.ressource.field_introduction
- field.field.node.ressource.field_mis_en_avant
- field.field.node.ressource.field_parties_ressource - field.field.node.ressource.field_parties_ressource
- field.field.node.ressource.field_thematiques - field.field.node.ressource.field_thematiques
- field.field.node.ressource.field_type_de_ressource - field.field.node.ressource.field_type_de_ressource
@ -29,6 +30,7 @@ hidden:
field_date_ressource: true field_date_ressource: true
field_etape: true field_etape: true
field_introduction: true field_introduction: true
field_mis_en_avant: true
field_parties_ressource: true field_parties_ressource: true
field_thematiques: true field_thematiques: true
field_type_de_ressource: true field_type_de_ressource: true

View File

@ -1,4 +1,4 @@
uuid: 3035b470-e4d9-4807-acd6-24a09525f537 uuid: 884f84e5-faeb-4719-a083-32cb215fd311
langcode: fr langcode: fr
status: true status: true
dependencies: dependencies:

View File

@ -1,4 +1,4 @@
uuid: bbddf173-169d-4977-ba3f-d1ec9f5aa452 uuid: 00096414-fab5-4bed-ac99-b819ad041ed9
langcode: fr langcode: fr
status: true status: true
dependencies: dependencies:

View File

@ -1,17 +1,17 @@
uuid: 52fa2fc6-d3ca-4fef-b91b-06be4145e8e3 uuid: e669e2a0-7ad6-4744-9bcf-efd428aa8070
langcode: fr langcode: fr
status: true status: true
dependencies: dependencies:
config: config:
- config_pages.type.intro_partenaires - config_pages.type.intro_ressource
- field.storage.config_pages.field_intro - field.storage.config_pages.field_intro
- filter.format.wysiwyg - filter.format.wysiwyg
module: module:
- text - text
id: config_pages.intro_partenaires.field_intro id: config_pages.intro_ressource.field_intro
field_name: field_intro field_name: field_intro
entity_type: config_pages entity_type: config_pages
bundle: intro_partenaires bundle: intro_ressource
label: Intro label: Intro
description: '' description: ''
required: false required: false

View File

@ -0,0 +1,19 @@
uuid: 1a5ca231-32de-4adc-b066-22a8377bf323
langcode: fr
status: true
dependencies:
config:
- config_pages.type.intro_ressource
- field.storage.config_pages.field_titre
id: config_pages.intro_ressource.field_titre
field_name: field_titre
entity_type: config_pages
bundle: intro_ressource
label: titre
description: ''
required: false
translatable: false
default_value: { }
default_value_callback: ''
settings: { }
field_type: string

View File

@ -9,7 +9,7 @@ id: node.ressource.field_autheurice
field_name: field_autheurice field_name: field_autheurice
entity_type: node entity_type: node
bundle: ressource bundle: ressource
label: Autheur⋅ice label: Auteur⋅ice
description: '' description: ''
required: false required: false
translatable: false translatable: false

View File

@ -0,0 +1,23 @@
uuid: 3e824533-8b04-4c3c-acb4-cdf87f40e281
langcode: fr
status: true
dependencies:
config:
- field.storage.node.field_mis_en_avant
- node.type.ressource
id: node.ressource.field_mis_en_avant
field_name: field_mis_en_avant
entity_type: node
bundle: ressource
label: 'mis en avant'
description: ''
required: false
translatable: false
default_value:
-
value: 0
default_value_callback: ''
settings:
on_label: Activé
off_label: Désactivé
field_type: boolean

View File

@ -1,29 +1,21 @@
uuid: 2c08a730-15f1-48fb-99b5-9b3abb866321 uuid: e439dc6f-3fc5-40cb-b615-f95c5a22a6f9
langcode: fr langcode: fr
status: true status: true
dependencies: dependencies:
config: config:
- field.storage.node.field_type_de_ressource - field.storage.node.field_type_de_ressource
- node.type.ressource - node.type.ressource
- taxonomy.vocabulary.type_de_ressource module:
- options
id: node.ressource.field_type_de_ressource id: node.ressource.field_type_de_ressource
field_name: field_type_de_ressource field_name: field_type_de_ressource
entity_type: node entity_type: node
bundle: ressource bundle: ressource
label: 'Type de ressource' label: 'Type de ressource'
description: '' description: ''
required: false required: true
translatable: false translatable: false
default_value: { } default_value: { }
default_value_callback: '' default_value_callback: ''
settings: settings: { }
handler: 'default:taxonomy_term' field_type: list_string
handler_settings:
target_bundles:
type_de_ressource: type_de_ressource
sort:
field: name
direction: asc
auto_create: false
auto_create_bundle: ''
field_type: entity_reference

View File

@ -0,0 +1,18 @@
uuid: e92021e0-c606-4e50-b042-012920aa6341
langcode: fr
status: true
dependencies:
module:
- node
id: node.field_mis_en_avant
field_name: field_mis_en_avant
entity_type: node
type: boolean
settings: { }
module: core
locked: false
cardinality: 1
translatable: true
indexes: { }
persist_with_no_fields: false
custom_storage: false

View File

@ -1,17 +1,30 @@
uuid: 9dedf9d1-5a9a-4c71-9896-4d4f2b2b5387 uuid: be6b9eb9-d827-4adc-9106-e7c31b94aff6
langcode: fr langcode: fr
status: true status: true
dependencies: dependencies:
module: module:
- node - node
- taxonomy - options
id: node.field_type_de_ressource id: node.field_type_de_ressource
field_name: field_type_de_ressource field_name: field_type_de_ressource
entity_type: node entity_type: node
type: entity_reference type: list_string
settings: settings:
target_type: taxonomy_term allowed_values:
module: core -
value: cartes_blanches
label: 'Cartes blanches'
-
value: reportages
label: Reportages
-
value: videos
label: Videos
-
value: documents
label: Documents
allowed_values_function: ''
module: options
locked: false locked: false
cardinality: 1 cardinality: 1
translatable: true translatable: true

View File

@ -4,7 +4,7 @@ favicon:
mimetype: image/vnd.microsoft.icon mimetype: image/vnd.microsoft.icon
path: '' path: ''
url: '' url: ''
use_default: true use_default: false
features: features:
comment_user_picture: true comment_user_picture: true
comment_user_verification: true comment_user_verification: true

View File

@ -20,4 +20,5 @@ permissions:
- 'restful get rest_menu_item' - 'restful get rest_menu_item'
- 'view intro_gouvernance config page entity' - 'view intro_gouvernance config page entity'
- 'view intro_partenaire config page entity' - 'view intro_partenaire config page entity'
- 'view intro_ressource config page entity'
- 'view media' - 'view media'

View File

@ -200,7 +200,7 @@ display:
type: normal type: normal
title: 'Les partenaires' title: 'Les partenaires'
description: '' description: ''
weight: -48 weight: -47
expanded: false expanded: false
menu_name: main menu_name: main
parent: '' parent: ''

4
readme.md Normal file
View File

@ -0,0 +1,4 @@
## Routing flow
![routing flow 1](https://nextcloud.val.kiwi/apps/files_sharing/publicpreview/rCJPJgEPrkSNJAC?file=/&fileId=279057&x=3456&y=2304&a=true&etag=9d32d9bd832255ab6f8597c956be97ac)
![routing flow 2](https://nextcloud.val.kiwi/apps/files_sharing/publicpreview/CpebG9MZAaKCwe3?file=/&fileId=279060&x=3456&y=2304&a=true&etag=819047a9275df96001669680c6df154f)

View File

@ -1,6 +1,6 @@
import { initVueContentModale } from './utils/vue-setup'; import { initVueContentModale } from './utils/vue-setup';
import { processClickableElements } from './utils/process-clickable-elements'; import { processClickableElements } from './utils/process-clickable-elements';
import { handleReactiveness, setMenuToggle, setHamburgerWhenLogged } from './utils/layout-setup'; import { handleReactiveness, setMenuToggle, setRightSectionsWhenLogged } from './utils/layout-setup';
import { initFirstLoadRouting, handleClickableElements, handleBrowserNavigation } from './utils/handle-navigation'; import { initFirstLoadRouting, handleClickableElements, handleBrowserNavigation } from './utils/handle-navigation';
import { setupMapStore, preloadEtapesTiles } from './utils/map-setup'; import { setupMapStore, preloadEtapesTiles } from './utils/map-setup';
@ -12,16 +12,16 @@ import '../scss/main.scss'
(function ($, Drupal, drupalSettings) { (function ($, Drupal, drupalSettings) {
const CaravaneTheme = function () { const CaravaneTheme = function () {
function init () { function init () {
// console.log('DrupalSettings', drupalSettings); // console.log('DrupalSettings', drupalSettings);
const baseUrl = window.location.protocol + "//" + window.location.host; const baseUrl = window.location.protocol + "//" + window.location.host;
const siteName = document.querySelector('#site_name').innerText; const siteName = document.querySelector('#site_name').innerText;
const { store, mapStore, router, route } = initVueContentModale(); const router = initVueContentModale();
handleReactiveness(); handleReactiveness();
setMenuToggle(); setMenuToggle();
setHamburgerWhenLogged(drupalSettings); setRightSectionsWhenLogged(drupalSettings);
// https://www.drupal.org/docs/extending-drupal/contributed-modules/contributed-module-documentation/leaflet/leaflet-api // https://www.drupal.org/docs/extending-drupal/contributed-modules/contributed-module-documentation/leaflet/leaflet-api
@ -43,22 +43,21 @@ import '../scss/main.scss'
} = processClickableElements(); } = processClickableElements();
const clickableElements = [...etapeListLinks, ...generalListLinks, logoLink, ...mapIcons, mapContainer]; const clickableElements = [...etapeListLinks, ...generalListLinks, logoLink, ...mapIcons, mapContainer];
setupMapStore(mapStore, map, settings); setupMapStore(map, settings);
// preloadEtapesTiles(mapStore, map); // preloadEtapesTiles(map);
initFirstLoadRouting(store, router, baseUrl, siteName); initFirstLoadRouting(router, baseUrl, siteName);
handleClickableElements(clickableElements, store, router, baseUrl, siteName, mapStore); handleClickableElements(clickableElements, router, baseUrl, siteName);
window.addEventListener("popstate", () => { window.addEventListener("popstate", () => {
handleBrowserNavigation(store, baseUrl, siteName, mapStore); handleBrowserNavigation(baseUrl, siteName);
}); });
}); });
} }
} }
} }
init() init()
} }
CaravaneTheme() CaravaneTheme()

View File

@ -1,181 +1,68 @@
// query et traitement des contenus // query et traitement des contenus
import { defineStore } from 'pinia'; import { defineStore } from 'pinia';
import REST from '../api/rest-axios';
import { useLayoutStore } from './layout';
import { findContentByPath } from '../utils/content/findContentByPath'; import { findContentByPath } from '../utils/content/findContentByPath';
import { import { fetchSingletonFullContent, fetchSingletonPartialContent } from '../utils/content/fetchSingleton';
getCleanDate, import { fetchMultipleFullContent, fetchMultiplePartialContent } from '../utils/content/fetchMultiple';
fetchFromRelationships,
getRelatedEtape,
} from '../utils/content/contentFetchUtils';
import {
getCarteSensible,
getTitreTexte,
getChiffresCles,
getDiaporama,
getEntretien,
getVideos,
} from '../utils/content/cleanParties';
import {
getPartenaires,
getGouvernance,
} from '../utils/content/multiItemPages';
export const useContentStore = defineStore('content', { export const useContentStore = defineStore('content', {
state: () => ({ state: () => ({
contentType: '', contentType: '',
rawContent: {},
pageTitle: '', pageTitle: '',
content: { content: {},
contentTitle: '', partialLoading: false,
coordinates: {},
adresse: {},
etape_number: '',
couleur: '',
dates: {},
previous: {},
next: {},
vignette: {},
parties: [],
liens: [],
pieces_jointes: [],
intro: '',
partenaires: [],
gouvernances: [],
},
loading: false, loading: false,
error: null, error: null,
}), }),
actions: { actions: {
async fetchContentData(path) { // pages etape, statiques et ressource ont un seul item par page (singuliers)
// pages gouvernance (contact), ressources et partenaire ont plusieurs items par pages (multiples)
async fetchPartialContentData(path) {
this.resetStore(false); this.resetStore(false);
const contentTypes = ['etape', 'static', 'gouvernance', 'partenaire']; const contentTypes = ['etape', 'static', 'gouvernance', 'partenaire', 'ressource'];
try { try {
const { contentType, rawContent } = await findContentByPath(contentTypes, path); const { contentType, rawContent } = await findContentByPath(contentTypes, path);
this.contentType = contentType; this.contentType = contentType;
this.rawContent = rawContent;
if (this.contentType !== 'gouvernance' && this.contentType !== 'partenaire') { if (
const vignettePromise = fetchFromRelationships('field_vignette', rawContent.relationships); this.contentType === 'etape'
const partiesPromise = fetchFromRelationships('field_parties', rawContent.relationships); || this.contentType === 'static'
|| this.contentType === 'ressourceItem'
let previousEtapePromise, nextEtapePromise; ) {
let { pageTitle, partialContent } = fetchSingletonPartialContent(this.contentType, this.rawContent);
if (contentType === 'etape') { this.pageTitle = pageTitle;
// related étapes this.content = partialContent;
previousEtapePromise = getRelatedEtape('previous', path);
nextEtapePromise = getRelatedEtape('next', path);
// coordinates
this.content.coordinates = {
lat: rawContent.attributes.field_geofield.lat,
lon: rawContent.attributes.field_geofield.lon,
};
// adresse
this.content.adresse = rawContent.attributes.field_adresse;
// étape number
this.content.etape_number = rawContent.attributes.field_arret_numero;
// couleur
this.content.couleur = rawContent.attributes.field_couleur;
// dates
this.content.dates = {
start: getCleanDate(rawContent.attributes.field_dates.value),
end: getCleanDate(rawContent.attributes.field_dates.end_value),
}
}
// pageTitle
for (let tag of rawContent.attributes.metatag) {
if (tag.tag === "meta") {
this.pageTitle = tag.attributes.content;
break;
}
}
// contentTitle
this.content.contentTitle = rawContent.attributes.title;
const [vignetteData, partiesData] = await Promise.all([vignettePromise, partiesPromise]);
if (vignetteData) {
this.content.vignette = {
url: {
original: vignetteData.attributes.uri.url,
small: vignetteData.attributes.image_style_uri.content_small,
medium: vignetteData.attributes.image_style_uri.content_medium,
large: vignetteData.attributes.image_style_uri.content_large,
},
alt: rawContent.relationships.field_vignette.data.meta.alt
};
}
if (partiesData) {
const partiesPromises = partiesData.map(async (partie) => {
const partieType = partie.type.replace(/^paragraph--/, "");
let partieContent = { type: partieType };
switch (partieType) {
case 'carte_sensible':
partieContent.carteSensible = await getCarteSensible(partie);
break;
case 'titre_texte':
const { titre, texte } = await getTitreTexte(partie);
partieContent.titre = titre;
partieContent.texte = texte;
break;
case 'chiffres_cles':
partieContent.chiffresCles = await getChiffresCles(partie);
break;
case 'diaporama':
partieContent.diaporama = await getDiaporama(partie);
break;
case 'entretien':
partieContent.entretien = await getEntretien(partie);
break;
case 'exergue':
partieContent.exergue = partie.attributes.field_texte_exergue.value;
break;
case 'video':
partieContent.videos = getVideos(partie);
break;
}
return partieContent;
});
this.content.parties = await Promise.all(partiesPromises);
}
// related étapes
if (contentType === 'etape') {
const [prevContent, nextContent] = await Promise.all([previousEtapePromise, nextEtapePromise]);
this.content.previous = prevContent;
this.content.next = nextContent;
}
} else { } else {
// pages gouvernance (contact) et partenaire let { pageTitle, partialContent } = await fetchMultiplePartialContent(this.contentType);
// ont plusieurs items par pages this.pageTitle = pageTitle;
const intro = await REST.get(`/jsonapi/config_pages/intro_${this.contentType}/`); this.content = partialContent;
const introContent = intro.data.data[0];
this.pageTitle =
`${introContent.attributes.field_titre} ${introContent.attributes.metatag.find(tag => tag.tag === "meta")?.attributes.content}`;
this.content.contentTitle = introContent.attributes.field_titre;
this.content.intro = introContent.attributes.field_intro?.value;
let multiItemPageArray = [];
if (this.contentType === 'partenaire') {
multiItemPageArray = await getPartenaires(rawContent);
} else if (this.contentType === 'gouvernance') {
multiItemPageArray = await getGouvernance(rawContent);
}
this.content[`${this.contentType}s`] = multiItemPageArray;
} }
} catch (error) { } catch(error) {
this.error = 'Failed to fetch data'; this.error = 'Failed to fetch partial data';
console.error('Issue with getNodeData', error); console.error('Issue with fetchPartialContentData', error);
} finally {
this.partialLoading = false;
}
},
async fetchFullContentData(path) {
try {
if (
this.contentType === 'etape'
|| this.contentType === 'static'
|| this.contentType === 'ressourceItem'
) {
this.content = { ...this.content, ...await fetchSingletonFullContent(this.contentType, this.rawContent, path) };
} else {
this.content = { ...this.content, ...await fetchMultipleFullContent(this.contentType, this.rawContent) };
}
} catch(error) {
this.error = 'Failed to fetch full data';
console.error('Issue with fetchFullContentData', error);
} finally { } finally {
this.loading = false; this.loading = false;
} }
@ -184,8 +71,10 @@ export const useContentStore = defineStore('content', {
this.contentType = ''; this.contentType = '';
this.pageTitle = ''; this.pageTitle = '';
this.content = {}; this.content = {};
this.partialLoading = !forFrontDisplay;
this.loading = !forFrontDisplay; this.loading = !forFrontDisplay;
this.error = null; this.error = null;
useLayoutStore().hideEtapeList(false);
} }
}, },
}); });

View File

@ -46,6 +46,30 @@ export const useLayoutStore = defineStore('layout', {
this.toggleEtapeListScroll(isIntersecting, listeEtape, column, headerRect.height, animationToggleRect.top); this.toggleEtapeListScroll(isIntersecting, listeEtape, column, headerRect.height, animationToggleRect.top);
}, },
hideEtapeList(souldListHide) {
const etapeList = document.querySelector(window.innerWidth >= this.minDesktopWidth ? '#etapes-liste' : '.layout__region--third');
const animationButton = document.querySelector('#animation-toggle');
const listContainer = etapeList.parentNode;
if (souldListHide) {
listContainer.style.minWidth = '30vw';
etapeList.style.opacity = '0';
setTimeout(() => {
etapeList.style.display = 'none';
}, 300);
if (window.innerWidth >= this.minDesktopWidth) {
animationButton.style.display = 'none';
}
} else {
listContainer.style.minWidth = 'unset';
etapeList.style.display = 'block';
setTimeout(() => {
etapeList.style.opacity = '1';
}, 10);
if (window.innerWidth >= this.minDesktopWidth) {
animationButton.style.display = 'block';
}
}
},
toggleEtapeListScroll(isIntersecting, listeEtape, column, headerHeight, animationToggleTop) { toggleEtapeListScroll(isIntersecting, listeEtape, column, headerHeight, animationToggleTop) {
if (isIntersecting && !this.isEtapeListeScrollable if (isIntersecting && !this.isEtapeListeScrollable
|| !isIntersecting && this.isEtapeListeScrollable) { || !isIntersecting && this.isEtapeListeScrollable) {
@ -104,6 +128,14 @@ export const useLayoutStore = defineStore('layout', {
setHeaderPosition(currentPageIsIndex) { setHeaderPosition(currentPageIsIndex) {
const header = document.querySelector('.layout-container > header'); const header = document.querySelector('.layout-container > header');
header.style.position = currentPageIsIndex ? 'fixed' : 'relative'; header.style.position = currentPageIsIndex ? 'fixed' : 'relative';
} },
toggleModaleTransition(shouldModaleTransition, enterDelay) {
if (shouldModaleTransition) {
document.documentElement.style.setProperty('--modale-enter-delay', `${enterDelay}s`);
} else {
document.documentElement.style.setProperty('margin-top', '0');
document.documentElement.style.setProperty('transition', 'none');
}
},
}, },
}) })

View File

@ -16,6 +16,39 @@ export const useMapStore = defineStore('mapState', {
animationDuration: 3, animationDuration: 3,
}), }),
actions: { actions: {
handleMapMovement(isModaleEtape, wasModaleEtape, lat = this.defaultMapCenter.lat, lon = this.defaultMapCenter.lng) {
if (this.animationsAreEnabled) {
if (isModaleEtape) {
if (!wasModaleEtape) {
// national -> détail
useLayoutStore().toggleModaleTransition(true, this.animationDuration);
this.zoomToPlace(lat, lon);
} else {
// détail -> détail
useLayoutStore().toggleModaleTransition(true, this.animationDuration);
this.zoomToPlace(lat, lon);
}
} else {
if (wasModaleEtape) {
// détail -> national
useLayoutStore().toggleModaleTransition(true, this.animationDuration);
this.resetMap();
} else {
// national -> national
useLayoutStore().toggleModaleTransition(true, 0);
}
}
} else {
if (isModaleEtape) {
// ? -> détail
this.zoomToPlace(lat, lon);
} else {
// ? -> national
this.resetMap();
}
useLayoutStore().toggleModaleTransition(false);
}
},
zoomToPlace(lat, long) { zoomToPlace(lat, long) {
if (useLayoutStore().isDesktop) long = long - 0.03; if (useLayoutStore().isDesktop) long = long - 0.03;
this.map.flyTo( this.map.flyTo(
@ -23,21 +56,15 @@ export const useMapStore = defineStore('mapState', {
this.maxZoom, this.maxZoom,
{ animate: this.animationsAreEnabled, duration: this.animationDuration }); { animate: this.animationsAreEnabled, duration: this.animationDuration });
this.currentZoom = this.maxZoom; this.currentZoom = this.maxZoom;
this.lockMap();
}, },
resetMap(animate = this.animationsAreEnabled, duration = this.animationDuration) { resetMap(animate = this.animationsAreEnabled, duration = this.animationDuration) {
this.map.flyTo( this.map.flyTo(
this.defaultMapCenter, this.defaultMapCenter,
useLayoutStore().isDesktop ? this.defaultZoomDesktop : this.defaultZoomMobile, useLayoutStore().isDesktop ? this.defaultZoomDesktop : this.defaultZoomMobile,
{ animate, duration }); { animate, duration });
this.currentZoom = useLayoutStore().isDesktop ? this.defaultZoomDesktop : this.defaultZoomMobile; this.currentZoom = useLayoutStore().isDesktop ? this.defaultZoomDesktop : this.defaultZoomMobile;
this.unlockMap();
}, },
lockMap() { lockMap() {
setTimeout(() => {
this.map.options.minZoom = this.currentZoom;
this.map.options.maxZoom = this.currentZoom;
}, this.animationDuration * 1000 + 100);
this.map.dragging.disable(); this.map.dragging.disable();
this.map.touchZoom.disable(); this.map.touchZoom.disable();
this.map.doubleClickZoom.disable(); this.map.doubleClickZoom.disable();

View File

@ -1,5 +1,5 @@
import REST from '../../api/rest-axios'; import REST from '../../api/rest-axios';
import { fetchFromRelationships } from './contentFetchUtils'; import { fetchFromRelationships, getCleanDate } from './contentFetchUtils';
export async function getCarteSensible(partie) { export async function getCarteSensible(partie) {
@ -131,4 +131,46 @@ export function getVideos(partie) {
videos.push(videoUrl); videos.push(videoUrl);
} }
return videos; return videos;
}
export async function getDocument(partie) {
// const documentFetch = await fetchFromRelationships('field_document', partie.relationships);
const uuid = partie.relationships.field_document.data?.id;
const documentFetch = await REST.get(`/jsonapi/file/file/${uuid}`);
const url = documentFetch.data.data?.attributes.uri.url;
const titre = partie.attributes.field_titre;
const sousTitre = partie.attributes.field_sous_titre;
const date = partie.attributes.field_date ? getCleanDate(partie.attributes.field_date) : null;
const auteurice = partie.relationships.field_autheurice_s;
const description = partie.relationships.field_document.data?.meta.description;
const vignetteFetch = await REST.get(`/jsonapi/file/file/${partie.relationships.field_vignette.data?.id}`);
const vignette = { url: vignetteFetch.data.data?.attributes.image_style_uri.content_small, alt: partie.relationships.field_vignette.data?.meta.alt };
return { url, titre, sousTitre, date, auteurice, description, vignette };
}
export async function getGallerie(partie) {
const gallerieFetch = await fetchFromRelationships('field_gallerie', partie.relationships);
if (gallerieFetch) {
const titre = gallerieFetch.attributes.title;
const introduction = gallerieFetch.attributes.body?.processed;
const imagesFetch = await fetchFromRelationships('field_images', gallerieFetch.relationships);
let images = [];
imagesFetch.forEach((image, index) => {
images.push({
url: {
original: image.attributes.uri.url,
small: image.attributes.image_style_uri.content_small,
medium: image.attributes.image_style_uri.content_medium,
large: image.attributes.image_style_uri.content_large,
},
alt: gallerieFetch.relationships.field_images.data[index].meta.alt,
});
});
return { titre, introduction, images };
}
} }

View File

@ -3,7 +3,7 @@ import REST from '../../api/rest-axios';
export async function fetchFromRelationships(field, relationships) { export async function fetchFromRelationships(field, relationships) {
field = relationships[field] ? field : `${field}_static`; field = relationships[field] ? field : `${field}_static`;
if (relationships[field].links) { if (relationships[field]?.links) {
const contentLink = relationships[field].links.related.href; const contentLink = relationships[field].links.related.href;
return REST.get(contentLink) return REST.get(contentLink)
.then(contentFetch => contentFetch.data.data) .then(contentFetch => contentFetch.data.data)
@ -60,9 +60,91 @@ export async function getRelatedEtape(direction, path) {
if (etape.attributes.metatag.some(tag => if (etape.attributes.metatag.some(tag =>
tag.tag === "link" && tag.attributes.href === path tag.tag === "link" && tag.attributes.href === path
)) { )) {
const relatedEtapeIndex = direction === 'next' ? index + 1 : index - 1; const relatedEtapeIndex = direction ? (direction === 'next' ? index + 1 : index - 1) : index;
return getRelatedEtapeContent(allEtapesData.data.data[relatedEtapeIndex]); return getRelatedEtapeContent(allEtapesData.data.data[relatedEtapeIndex]);
} }
} }
}); });
}
export async function getRessourceItemCard(item) {
try {
const ressourceFetch = await REST.get(item.links.self.href);
const partiesFetch = await REST.get(item.relationships.field_parties_ressource?.links.related.href);
const parties = partiesFetch.data.data;
const vignettePartie = parties.find(partie => partie.type !== "paragraph--titre_texte");
let vignette = null;
if (vignettePartie) {
let alt;
switch (vignettePartie.type) {
case 'paragraph--diaporama':
alt = vignettePartie.relationships.field_diaporama?.data[0]?.meta.alt;
const diaporamaFetch = await REST.get(vignettePartie.relationships.field_diaporama?.links.related.href);
vignette = {
url: diaporamaFetch.data.data[0].attributes.image_style_uri?.content_small,
alt
};
break;
case 'paragraph--video':
const videoId = vignettePartie.attributes.field_videos[0]?.split('?v=')[1];
vignette = {
url: `https://img.youtube.com/vi/${videoId}/0.jpg`,
alt: item.attributes.title
};
break;
case 'paragraph--galleries':
const gallerieFetch = await REST.get(vignettePartie.relationships.field_gallerie?.links.related.href);
const galleryAlt = gallerieFetch.data.data.relationships.field_images?.data[0].meta.alt;
const gallerieImageFetch = await REST.get(gallerieFetch.data.data.relationships.field_images?.links.related.href);
vignette = {
url: gallerieImageFetch.data.data[0].attributes.image_style_uri?.content_small,
alt: galleryAlt
};
break;
case 'paragraph--document':
alt = vignettePartie.relationships.field_vignette?.data?.meta.alt;
const documentFetch = await REST.get(vignettePartie.relationships.field_vignette?.links.related.href);
vignette = {
url: documentFetch.data.data?.attributes.image_style_uri?.content_small,
alt
};
break;
default:
vignette = null;
}
}
const relatedEtape = await REST.get(item.relationships.field_etape.links.related.href);
// console.log(item);
return {
ressourceType: item.attributes.field_type_de_ressource,
title: item.attributes.title,
auteurice: item.attributes.field_autheurice,
promoted: item.attributes.field_mis_en_avant,
date: item.attributes.field_date_ressource ? getCleanDate(item.attributes.field_date_ressource) : null,
url: ressourceFetch.data.data.attributes.metatag.find(tag => tag.tag === "link")?.attributes.href,
relatedEtape: relatedEtape.data.data?.attributes.title,
vignette
};
} catch (error) {
console.error('Error fetching resource:', error);
return null;
}
}
export async function getRelatedRessources(etapeId) {
const ressources = await REST.get(`/jsonapi/node/ressource/`);
const ressourcesRelatedToEtape = ressources.data.data.filter(ressource => ressource.relationships.field_etape.data?.id === etapeId);
const ressourcesRelatedPromises = ressourcesRelatedToEtape.map(ressource => getRessourceItemCard(ressource));
return await Promise.all(ressourcesRelatedPromises);
} }

View File

@ -0,0 +1,43 @@
import REST from '../../api/rest-axios';
import { useLayoutStore } from '../../stores/layout';
import { getPartenaires, getGouvernance, getRessources } from './multiItemPages';
export async function fetchMultiplePartialContent(contentType) {
let partialContent = {};
let pageTitle = '';
const intro = await REST.get(`/jsonapi/config_pages/intro_${contentType}/`);
const introContent = intro.data.data[0];
pageTitle =
`${introContent.attributes.field_titre} ${introContent.attributes.metatag.find(tag => tag.tag === "meta")?.attributes.content}`;
partialContent.contentTitle = introContent.attributes.field_titre;
partialContent.intro = introContent.attributes.field_intro?.value;
return { pageTitle, partialContent };
}
export async function fetchMultipleFullContent(contentType, rawContent) {
let content = {};
let multiItemPageArray = [];
switch (contentType) {
case 'ressource':
multiItemPageArray = await getRessources(rawContent);
content.ressourceTypes = new Set(multiItemPageArray.map(item => item.ressourceType));
useLayoutStore().hideEtapeList(true);
break;
case 'partenaire':
multiItemPageArray = await getPartenaires(rawContent);
break;
case 'gouvernance':
multiItemPageArray = await getGouvernance(rawContent);
break;
}
content[`${contentType}s`] = multiItemPageArray;
return content;
}

View File

@ -0,0 +1,153 @@
import REST from '../../api/rest-axios';
import { getCleanDate, fetchFromRelationships, getRelatedEtape, getRelatedRessources } from './contentFetchUtils';
import { getCarteSensible, getTitreTexte, getChiffresCles, getDiaporama, getEntretien, getVideos, getDocument, getGallerie } from './cleanParties';
import { useLayoutStore } from '../../stores/layout';
export function fetchSingletonPartialContent(contentType, rawContent) {
let partialContent = {};
let pageTitle = rawContent.attributes.metatag.find(tag => tag.tag === "meta")?.attributes.content;
partialContent.contentTitle = rawContent.attributes.title;
if (contentType === 'etape') {
partialContent.coordinates = {
lat: rawContent.attributes.field_geofield.lat,
lon: rawContent.attributes.field_geofield.lon,
};
partialContent.adresse = rawContent.attributes.field_adresse;
partialContent.etape_number = rawContent.attributes.field_arret_numero;
partialContent.couleur = rawContent.attributes.field_couleur;
partialContent.dates = {
start: getCleanDate(rawContent.attributes.field_dates.value),
end: getCleanDate(rawContent.attributes.field_dates.end_value),
}
}
if (contentType === 'ressourceItem') {
partialContent.ressourceType = rawContent.attributes.field_type_de_ressource;
partialContent.auteurice = rawContent.attributes.field_autheurice;
partialContent.date = rawContent.attributes.field_date_ressource ? getCleanDate(rawContent.attributes.field_date_ressource) : null;
}
return { pageTitle, partialContent };
}
export async function fetchSingletonFullContent(contentType, rawContent, path) {
let content = {};
const vignettePromise = fetchFromRelationships('field_vignette', rawContent.relationships);
const partiesPromise = fetchFromRelationships(contentType === 'ressourceItem' ? 'field_parties_ressource' : 'field_parties', rawContent.relationships);
let previousEtapePromise, nextEtapePromise;
if (contentType === 'etape') {
previousEtapePromise = getRelatedEtape('previous', path);
nextEtapePromise = getRelatedEtape('next', path);
content.relatedRessources = await getRelatedRessources(rawContent.id);
}
if (contentType === 'ressourceItem') {
content.introduction = rawContent.attributes.field_introduction?.processed;
if (rawContent.relationships.field_etape.data) {
const relatedEtapeFetch = fetchFromRelationships('field_etape', rawContent.relationships);
const relatedEtape = await Promise.resolve(relatedEtapeFetch);
const relatedEtapeUrl = relatedEtape.attributes.metatag.find(tag => tag.tag === "link")?.attributes.href;
content.relatedEtape = await getRelatedEtape('', relatedEtapeUrl);
}
useLayoutStore().hideEtapeList(true);
}
const [vignetteData, partiesData] = await Promise.all([vignettePromise, partiesPromise]);
if (vignetteData) {
content.vignette = {
url: {
original: vignetteData.attributes.uri.url,
small: vignetteData.attributes.image_style_uri.content_small,
medium: vignetteData.attributes.image_style_uri.content_medium,
large: vignetteData.attributes.image_style_uri.content_large,
},
alt: rawContent.relationships.field_vignette.data.meta.alt
};
}
if (partiesData) {
const partiesPromises = partiesData.map(async (partie) => {
const partieType = partie.type.replace(/^paragraph--/, "");
let partieContent = { type: partieType };
switch (partieType) {
case 'carte_sensible':
partieContent.carteSensible = await getCarteSensible(partie);
break;
case 'titre_texte':
const { titre, texte } = await getTitreTexte(partie);
partieContent.titre = titre;
partieContent.texte = texte;
break;
case 'chiffres_cles':
partieContent.chiffresCles = await getChiffresCles(partie);
break;
case 'diaporama':
partieContent.diaporama = await getDiaporama(partie);
break;
case 'entretien':
partieContent.entretien = await getEntretien(partie);
break;
case 'exergue':
partieContent.exergue = partie.attributes.field_texte_exergue.value;
break;
case 'video':
partieContent.videos = getVideos(partie);
break;
case 'document':
partieContent.document = await getDocument(partie);
break;
case 'galleries':
partieContent.gallerie = await getGallerie(partie);
break;
}
return partieContent;
});
// liens
if (rawContent.attributes.field_liens?.length) {
content.liens = [];
for (let lien of rawContent.attributes.field_liens) {
content.liens.push({
title: lien.title,
url: lien.uri,
});
}
}
// pièces jointes
if (rawContent.relationships.field_pieces_jointes?.data.length) {
content.pieces_jointes = [];
for (let pieceJointe of rawContent.relationships.field_pieces_jointes.data) {
if (pieceJointe.meta.display) {
const uuid = pieceJointe.id;
const response = await REST.get(`/jsonapi/file/file/${uuid}`);
content.pieces_jointes.push({
title: pieceJointe.meta.description,
url: response.data.data.attributes.uri.url,
});
}
}
}
content.parties = await Promise.all(partiesPromises);
}
// related étapes
if (contentType === 'etape') {
const [prevContent, nextContent] = await Promise.all([previousEtapePromise, nextEtapePromise]);
content.previous = prevContent;
content.next = nextContent;
}
return content;
}

View File

@ -10,15 +10,15 @@ export async function findContentByPath(contentTypes, path) {
) )
); );
if (content) { if (content) {
return { return {
contentType: type, contentType: content.type === 'node--ressource' ? 'ressourceItem' : type,
rawContent: content, rawContent: content,
}; };
} }
// Handle special case for governance/partners (multiple items per page) // Handle special case for gouvernance, ressources, partenaires (multiple items per page)
const pageRequested = window.location.href.split('/').pop().replace(/s?$/, ''); let pageRequested = window.location.href.split('/').pop().replace(/s?$/, '');
if (type === pageRequested if (type === pageRequested
|| (type === 'gouvernance' && pageRequested === 'contact') || (type === 'gouvernance' && pageRequested === 'contact')
) { ) {

View File

@ -1,19 +1,20 @@
import REST from '../../api/rest-axios'; import REST from '../../api/rest-axios';
import { getRessourceItemCard } from './contentFetchUtils';
export async function getPartenaires(rawContent) { export async function getPartenaires(rawContent) {
const logoPromises = rawContent.map(item => const logoPromises = rawContent.map(item =>
REST.get(item.relationships.field_logo.links.related.href) REST.get(item.relationships.field_logo?.links.related.href)
.then(logoFetch => ({ .then(logoFetch => ({
title: item.attributes.title, title: item.attributes?.title,
description: item.attributes.body.value, description: item.attributes?.body?.value,
weight: item.attributes.field_poid, weight: item.attributes?.field_poid,
link_url: item.attributes.field_lien.uri, link_url: item.attributes?.field_lien?.uri,
logo_alt: item.relationships.field_logo.data.meta.alt, logo_alt: item.relationships?.field_logo?.data?.meta.alt,
logo_url: { logo_url: {
original: logoFetch.data.data.attributes.uri.url, original: logoFetch.data.data?.attributes.uri.url,
small: logoFetch.data.data.attributes.image_style_uri.content_small, small: logoFetch.data.data?.attributes.image_style_uri.content_small,
medium: logoFetch.data.data.attributes.image_style_uri.content_medium, medium: logoFetch.data.data?.attributes.image_style_uri.content_medium,
large: logoFetch.data.data.attributes.image_style_uri.content_large, large: logoFetch.data.data?.attributes.image_style_uri.content_large,
} }
})) }))
); );
@ -23,32 +24,39 @@ export async function getPartenaires(rawContent) {
export async function getGouvernance(rawContent) { export async function getGouvernance(rawContent) {
const itemPromises = rawContent.map(item => const itemPromises = rawContent.map(item =>
REST.get(item.relationships.field_personne_s.links.related.href) REST.get(item.relationships.field_personne_s?.links.related.href)
.then(async personnesFetch => { .then(async personnesFetch => {
const portraitPromises = personnesFetch.data.data.map(personne => const portraitPromises = personnesFetch.data.data.map(personne =>
REST.get(personne.relationships.field_portrait.links.related.href) REST.get(personne.relationships.field_portrait?.links.related.href)
.then(portraitFetch => ({ .then(portraitFetch => ({
nom: personne.attributes.field_nom, nom: personne.attributes.field_nom,
prenom: personne.attributes.field_prenom, prenom: personne.attributes.field_prenom,
description: personne.attributes.field_description, description: personne.attributes.field_description,
photo_meta: personne.relationships.field_portrait.data?.meta.alt, photo_meta: personne.relationships.field_portrait.data?.meta.alt,
photo_url: portraitFetch.data.data ? { photo_url: portraitFetch.data.data ? {
original: portraitFetch.data.data.attributes.uri.url, original: portraitFetch.data.data?.attributes.uri.url,
small: portraitFetch.data.data.attributes.image_style_uri.content_small, small: portraitFetch.data.data?.attributes.image_style_uri.content_small,
medium: portraitFetch.data.data.attributes.image_style_uri.content_medium, medium: portraitFetch.data.data?.attributes.image_style_uri.content_medium,
large: portraitFetch.data.data.attributes.image_style_uri.content_large, large: portraitFetch.data.data?.attributes.image_style_uri.content_large,
} : null } : null
})) }))
); );
return Promise.all(portraitPromises) return Promise.all(portraitPromises)
.then(personnes => ({ .then(personnes => ({
title: item.attributes.title, title: item.attributes?.title,
weight: item.attributes.field_poid, weight: item.attributes?.field_poid,
personnes personnes
})); }));
}) })
); );
return await Promise.all(itemPromises); return await Promise.all(itemPromises);
}
export async function getRessources(rawContent) {
const ressourcesPromises = rawContent.map(item => getRessourceItemCard(item));
return await Promise.all(ressourcesPromises);
} }

View File

@ -1,23 +1,27 @@
import { setActiveNavItem } from "./set-active-nav-item"; import { setActiveNavItem } from "./set-active-nav-item";
import { useContentStore } from "../stores/content";
import { useMapStore } from "../stores/map";
import { useLayoutStore } from '../stores/layout'; import { useLayoutStore } from '../stores/layout';
export async function initFirstLoadRouting(store, router, baseUrl, siteName) { export async function initFirstLoadRouting(router, baseUrl, siteName) {
const store = useContentStore();
const decoupled_origin = JSON.parse(window.localStorage.getItem('decoupled_origin')); const decoupled_origin = JSON.parse(window.localStorage.getItem('decoupled_origin'));
if(decoupled_origin) { if(decoupled_origin) {
router.push(decoupled_origin.url); router.push(decoupled_origin.url);
await store.fetchContentData(baseUrl + decoupled_origin.url); await store.fetchPartialContentData(baseUrl + decoupled_origin.url);
window.localStorage.removeItem("decoupled_origin"); window.localStorage.removeItem("decoupled_origin");
document.title = store.pageTitle; document.title = store.pageTitle;
setActiveNavItem(store.contentType, decoupled_origin.url); setActiveNavItem(store.contentType, decoupled_origin.url);
useLayoutStore().setHeaderPosition(false); useLayoutStore().setHeaderPosition(false);
await store.fetchFullContentData(baseUrl + decoupled_origin.url);
} else { } else {
document.title = siteName; document.title = siteName;
useLayoutStore().setHeaderPosition(true); useLayoutStore().setHeaderPosition(true);
} }
} }
export function handleClickableElements(clickableElements, store, router, baseUrl, siteName, mapStore) { export function handleClickableElements(clickableElements, router, baseUrl, siteName) {
for (const link of clickableElements) { for (const link of clickableElements) {
let href = link.href || link.dataset.href; let href = link.href || link.dataset.href;
if (href.startsWith(baseUrl)) href = href.replace(baseUrl, ''); if (href.startsWith(baseUrl)) href = href.replace(baseUrl, '');
@ -25,26 +29,29 @@ export function handleClickableElements(clickableElements, store, router, baseUr
link.onclick = async function (e) { link.onclick = async function (e) {
if (href !== window.location.pathname) { if (href !== window.location.pathname) {
router.push(href); router.push(href);
pageChange(href, store, siteName, mapStore, baseUrl); pageChange(href, siteName, baseUrl);
} }
} }
} }
} }
export async function handleBrowserNavigation(store, baseUrl, siteName, mapStore) { export async function handleBrowserNavigation(baseUrl, siteName) {
let href = window.location.pathname; let href = window.location.pathname;
if (href.startsWith(baseUrl)) href = href.replace(baseUrl, ''); if (href.startsWith(baseUrl)) href = href.replace(baseUrl, '');
pageChange(href, store, siteName, mapStore, baseUrl) pageChange(href, siteName, baseUrl)
} }
export async function pageChange(href, store, siteName, mapStore, baseUrl) { export async function pageChange(href, siteName, baseUrl) {
const store = useContentStore();
const mapStore = useMapStore();
if (href === '/') { if (href === '/') {
store.resetStore(true); store.resetStore(true);
document.title = siteName; document.title = siteName;
mapStore.resetMap(); mapStore.resetMap();
useLayoutStore().setHeaderPosition(true); useLayoutStore().setHeaderPosition(true);
} else { } else {
await store.fetchContentData(baseUrl + href); await store.fetchPartialContentData(baseUrl + href);
document.title = store.pageTitle; document.title = store.pageTitle;
useLayoutStore().setHeaderPosition(false); useLayoutStore().setHeaderPosition(false);
} }
@ -53,4 +60,8 @@ export async function pageChange(href, store, siteName, mapStore, baseUrl) {
const listeEtape = document.querySelector('#etapes-liste'); const listeEtape = document.querySelector('#etapes-liste');
const animationToggle = document.querySelector('#animation-toggle'); const animationToggle = document.querySelector('#animation-toggle');
if (!useLayoutStore().isDesktop) useLayoutStore().collapseEtapeListe(listeEtape, animationToggle); if (!useLayoutStore().isDesktop) useLayoutStore().collapseEtapeListe(listeEtape, animationToggle);
if (href !== '/') {
await store.fetchFullContentData(baseUrl + href);
}
} }

View File

@ -56,7 +56,7 @@ export function setMenuToggle() {
layoutStore.setUpHamburgerToggle(menuBurger, menuContainer); layoutStore.setUpHamburgerToggle(menuBurger, menuContainer);
} }
export function setHamburgerWhenLogged(drupalSettings) { export function setRightSectionsWhenLogged(drupalSettings) {
if (drupalSettings.user.uid != 0) { if (drupalSettings.user.uid != 0) {
const menuBurger = document.querySelector('#hamburger'); const menuBurger = document.querySelector('#hamburger');
const menuTitle = document.querySelector('#menu-title'); const menuTitle = document.querySelector('#menu-title');
@ -65,7 +65,11 @@ export function setHamburgerWhenLogged(drupalSettings) {
const headerTop = header.getBoundingClientRect().top; const headerTop = header.getBoundingClientRect().top;
menuTitle.style.top = `${headerTop}px`; menuTitle.style.top = `${headerTop}px`;
menuBurger.style.top = `${headerTop}px`; menuContainer.style.paddingTop = `${headerTop + 10}px`;
menuContainer.style.paddingTop = `${headerTop}px`; menuBurger.style.top = `${headerTop + 2}px`;
const etapesListContainer = document.querySelector('.block-region-third');
etapesListContainer.style.paddingTop = `50px`;
} }
} }

View File

@ -1,7 +1,10 @@
import { useLayoutStore } from '../stores/layout'; import { useLayoutStore } from '../stores/layout';
import { useMapStore } from '../stores/map';
import REST from '../api/rest-axios'; import REST from '../api/rest-axios';
export function setupMapStore(mapStore, map, settings) { export function setupMapStore(map, settings) {
const mapStore = useMapStore();
mapStore.map = map; mapStore.map = map;
mapStore.defaultMapCenter = map.getCenter(); mapStore.defaultMapCenter = map.getCenter();
mapStore.maxZoom = settings.settings.maxZoom; mapStore.maxZoom = settings.settings.maxZoom;
@ -13,7 +16,9 @@ export function setupMapStore(mapStore, map, settings) {
// not working // not working
// may or may not rework on it later // may or may not rework on it later
export async function preloadEtapesTiles(mapStore, map) { export async function preloadEtapesTiles(map) {
const mapStore = useMapStore();
function waitForEvent(el, eventName) { function waitForEvent(el, eventName) {
return new Promise((resolve) => { return new Promise((resolve) => {
el.once(eventName, resolve); el.once(eventName, resolve);

View File

@ -11,13 +11,19 @@ export function setActiveNavItem(contentType, href) {
} }
if (href === '/' || href === '') { if (href === '/' || href === '') {
staticNavItems[0].classList.add('is-active'); staticNavItems[1].classList.add('is-active');
for (let item of etapeNavItems) { for (let item of etapeNavItems) {
item.closest('li').classList.remove('inactive'); item.closest('li').classList.remove('inactive');
} }
} else { } else {
if (contentType === 'static') {
if (
contentType === 'static'
|| contentType === 'partenaire'
|| contentType === 'gouvernance'
|| contentType === 'ressource'
) {
for (let item of staticNavItems) { for (let item of staticNavItems) {
if (item.getAttribute('href') === href) { if (item.getAttribute('href') === href) {
item.classList.add('is-active'); item.classList.add('is-active');
@ -26,9 +32,12 @@ export function setActiveNavItem(contentType, href) {
} else if (contentType === 'etape') { } else if (contentType === 'etape') {
for (let item of etapeNavItems) { for (let item of etapeNavItems) {
if (item.getAttribute('href') === href) { if (item.getAttribute('href') === href) {
item.closest('li').classList.remove('inactive'); item.closest('li').classList.remove('inactive');
document.querySelector('#etapes-liste').scrollTo(0, item.closest('li').offsetTop);
} }
} }
} else if (contentType === "ressourceItem") {
staticNavItems[2].classList.add('is-active');
} }
} }

View File

@ -6,9 +6,6 @@ import AnimationToggle from '../vuejs/AnimationToggle.vue';
import VueImageZoomer from 'vue-image-zoomer'; import VueImageZoomer from 'vue-image-zoomer';
import 'vue-image-zoomer/dist/style.css'; import 'vue-image-zoomer/dist/style.css';
import { useContentStore } from '../stores/content';
import { useMapStore } from '../stores/map';
export function initVueContentModale() { export function initVueContentModale() {
const pinia = createPinia(); const pinia = createPinia();
@ -16,14 +13,12 @@ export function initVueContentModale() {
.use(pinia) .use(pinia)
.use(router) .use(router)
.use(VueImageZoomer); .use(VueImageZoomer);
const store = useContentStore();
const mapStore = useMapStore();
app.mount('#content-modale'); app.mount('#content-modale');
const animationToggle = createApp(AnimationToggle) const animationToggle = createApp(AnimationToggle)
.use(pinia) .use(pinia)
.mount('#animation-toggle'); .mount('#animation-toggle');
return { store, mapStore, router }; return router;
} }

View File

@ -3,19 +3,24 @@
:enter-active-class="animationsAreEnabled ? 'v-enter-active' : 'no-transition'" :enter-active-class="animationsAreEnabled ? 'v-enter-active' : 'no-transition'"
:leave-active-class="animationsAreEnabled ? 'v-leave-active' : 'no-transition'" :leave-active-class="animationsAreEnabled ? 'v-leave-active' : 'no-transition'"
> >
<div v-if="!loading && ( <div v-if="!partialLoading && contentType != ''">
contentType === 'etape' <div
|| contentType === 'static' class="content-wrapper"
|| contentType === 'gouvernance' :class="
|| contentType === 'partenaire' contentType === 'ressource' || contentType === 'ressourceItem'
)"> ? 'ressource' : ''"
<div class="content-wrapper"> >
<ModaleHeader <ModaleHeader
:loading="loading"
:contentType="contentType" :contentType="contentType"
:content="content" :content="content"
:couleur="content.couleur || brandColor" /> :couleur="content.couleur || brandColor" />
<main> <main>
<div v-for="partie in content.parties" class="partie"> <RessourceItemHeader
v-if="contentType === 'ressourceItem'"
:content="content"
:couleur="brandColor" />
<div v-if="!loading" v-for="partie in content.parties" class="partie">
<ModaleCarteSensible <ModaleCarteSensible
v-if="partie.type === 'carte_sensible'" v-if="partie.type === 'carte_sensible'"
:partie="partie" /> :partie="partie" />
@ -42,31 +47,52 @@
<ModaleVideos <ModaleVideos
v-if="partie.type === 'video'" v-if="partie.type === 'video'"
:partie="partie" /> :partie="partie" />
<ModaleGallerie
v-if="partie.type === 'galleries'"
:partie="partie"
:couleur="content.couleur || brandColor" />
<ModaleDocument
v-if="partie.type === 'document'"
:partie="partie"
:couleur="content.couleur || brandColor" />
</div> </div>
<EquipeContent <div class="content-loading" v-else>
<div></div>
<p>Chargement du contenu...</p>
</div>
<template v-if="!loading">
<EquipeContent
v-if="contentType === 'gouvernance'" v-if="contentType === 'gouvernance'"
:content="content" :content="content"
:couleur="content.couleur || brandColor" /> :couleur="brandColor" />
<PartenairesContent <PartenairesContent
v-if="contentType === 'partenaire'" v-if="contentType === 'partenaire'"
:content="content" /> :content="content" />
<CentreDeRessource
v-if="contentType === 'ressource'"
:content="content"
:couleur="brandColor" />
<RelatedRessources
v-if="contentType === 'etape' && content.relatedRessources?.length"
:relatedRessources="content.relatedRessources"
:couleur="content.couleur || brandColor" />
</template>
</main> </main>
<PiecesJointes <PiecesJointes
v-if="content.pieces_jointes || content.liens"
:content="content" :content="content"
:couleur="content.couleur || brandColor" :couleur="content.couleur || brandColor" />
/>
<ModaleFooter <ModaleFooter
:contentType="contentType" :contentType="contentType"
:content="content" :content="content"
:couleur="content.couleur || brandColor" :couleur="content.couleur || brandColor" />
/>
</div> </div>
</div> </div>
</Transition> </Transition>
</template> </template>
<script setup> <script setup>
import { watch, onMounted } from 'vue'; import { watch, onMounted, nextTick } from 'vue';
import { storeToRefs } from 'pinia'; import { storeToRefs } from 'pinia';
import { useContentStore } from '../stores/content'; import { useContentStore } from '../stores/content';
@ -76,7 +102,10 @@ import ModaleHeader from './components/ModaleHeader.vue';
import ModaleFooter from './components/ModaleFooter.vue'; import ModaleFooter from './components/ModaleFooter.vue';
import EquipeContent from './components/EquipeContent.vue'; import EquipeContent from './components/EquipeContent.vue';
import PartenairesContent from './components/PartenairesContent.vue'; import PartenairesContent from './components/PartenairesContent.vue';
import CentreDeRessource from './components/CentreDeRessource.vue';
import RessourceItemHeader from './components/RessourceItemHeader.vue';
import PiecesJointes from './components/PiecesJointes.vue'; import PiecesJointes from './components/PiecesJointes.vue';
import RelatedRessources from './components/RelatedRessources.vue';
import ModaleCarteSensible from './components/parties/ModaleCarteSensible.vue'; import ModaleCarteSensible from './components/parties/ModaleCarteSensible.vue';
import ModaleTitreTexte from './components/parties/ModaleTitreTexte.vue'; import ModaleTitreTexte from './components/parties/ModaleTitreTexte.vue';
@ -85,6 +114,8 @@ import ModaleDiaporama from './components/parties/ModaleDiaporama.vue';
import ModaleEntretien from './components/parties/ModaleEntretien.vue'; import ModaleEntretien from './components/parties/ModaleEntretien.vue';
import ModaleExergue from './components/parties/ModaleExergue.vue'; import ModaleExergue from './components/parties/ModaleExergue.vue';
import ModaleVideos from './components/parties/ModaleVideos.vue'; import ModaleVideos from './components/parties/ModaleVideos.vue';
import ModaleGallerie from './components/parties/ModaleGallerie.vue';
import ModaleDocument from './components/parties/ModaleDocument.vue';
const store = useContentStore(); const store = useContentStore();
const mapState = useMapStore(); const mapState = useMapStore();
@ -92,96 +123,56 @@ const mapState = useMapStore();
const { const {
contentType, contentType,
content, content,
partialLoading,
loading, loading,
error, error,
} = storeToRefs(store); } = storeToRefs(store);
const { defaultMapCenter, animationDuration, animationsAreEnabled } = storeToRefs(mapState); const { animationsAreEnabled } = storeToRefs(mapState);
let isModaleEtape, wasModaleEtape; let isModaleEtape, wasModaleEtape;
const brandColor = "#80c8bf"; const brandColor = "#80c8bf";
const handleColorChange = () => { onMounted(() => {
watch( nextTick(() => {
() => content.value.couleur, watch(
() => { () => content.value.couleur,
() => {
if (contentType.value === 'etape' && content.value.couleur) { if (contentType.value === 'etape' && content.value.couleur) {
document.documentElement.style.setProperty('--etape-couleur', content.value.couleur || brandColor); document.documentElement.style.setProperty('--etape-couleur', content.value.couleur || brandColor);
} }
} }
); );
}; isModaleEtape = contentType.value === 'etape';
wasModaleEtape = isModaleEtape;
const handleMapMovement = () => { watch(
watch( () => partialLoading.value,
() => loading.value, () => {
() => { if (!partialLoading.value) {
if (!loading.value) {
console.log('loading done');
isModaleEtape = contentType.value === 'etape'; isModaleEtape = contentType.value === 'etape';
mapState.handleMapMovement(
// Define helper functions in variables isModaleEtape,
const disableModaleTransition = () => { wasModaleEtape,
document.documentElement.style.setProperty('margin-top', '0'); content.value.coordinates?.lat,
document.documentElement.style.setProperty('transition', 'none'); content.value.coordinates?.lon
} );
const setModaleTransition = (enterDelay) => {
document.documentElement.style.setProperty('--modale-enter-delay', `${enterDelay}s`);
};
const zoomToContentPlace = () => {
mapState.zoomToPlace(
content.value.coordinates.lat ? content.value.coordinates.lat : defaultMapCenter.value.lat,
content.value.coordinates.lon ? content.value.coordinates.lon : defaultMapCenter.value.lng
);
};
if (animationsAreEnabled.value) {
if (isModaleEtape) {
if (!wasModaleEtape) {
// national -> détail
setModaleTransition(animationDuration.value);
zoomToContentPlace();
} else {
// détail -> détail
setModaleTransition(animationDuration.value);
zoomToContentPlace();
}
} else {
if (wasModaleEtape) {
// détail -> national
setModaleTransition(animationDuration.value);
mapState.resetMap();
} else {
// national -> national
setModaleTransition(0);
}
}
} else {
if (isModaleEtape) {
zoomToContentPlace();
} else {
mapState.resetMap();
}
disableModaleTransition();
}
scrollTo(0, 0); scrollTo(0, 0);
wasModaleEtape = isModaleEtape; wasModaleEtape = isModaleEtape;
} }
}, }
); );
}; watch(
() => contentType.value,
onMounted(() => { () => {
console.log('modale mounted'); if (contentType.value === '') {
isModaleEtape = contentType.value === 'etape'; mapState.unlockMap();
wasModaleEtape = isModaleEtape; } else {
handleColorChange(); mapState.lockMap();
handleMapMovement(); }
}
);
});
}); });
</script> </script>

View File

@ -0,0 +1,162 @@
<template>
<div id="centre-de-ressource" :style="{ '--couleur': couleur }">
<div class="filters">
<input type="text" v-model="searchQuery" placeholder="Rechercher..." class="search-bar">
<select v-model="selectedType">
<option value="">Tous les types</option>
<option v-for="type in content.ressourceTypes" :key="type" :value="type">
{{ type.replace(/_/g, ' ').replace(/^\w/, char => char.toUpperCase()) }}
</option>
</select>
<select v-model="selectedEtape">
<option value="">Toutes les étapes</option>
<option v-for="etape in allRelatedEtapes" :key="etape" :value="etape">
{{ etape }}
</option>
</select>
</div>
<div v-if="content.intro" v-html="content.intro" class="intro"></div>
<template v-for="(type, typeIndex) in filteredTypes" :key="type">
<div class="type-section" v-if="ressourcesToDisplay[type] && ressourcesToDisplay[type].length > 0">
<h3>{{ type.replace(/_/g, ' ').replace(/^\w/, char => char.toUpperCase()) }}</h3>
<div class="ressource-list">
<TransitionGroup name="itemFade" tag="div" appear>
<div class="ressource-item"
v-for="(ressource, ressourceIndex) in ressourcesToDisplay[type]"
:key="`${type}-${ressourceIndex}`"
:style="{ '--index': ressourceIndex - visibleItemsPerSection }">
<RessourceCard :ressource="ressource" :index="`${typeIndex}-${ressourceIndex}`" />
</div>
</TransitionGroup>
</div>
<div class="button-container">
<div v-if="ressourcesByType(type).length > visibleItemsPerSection
&& ressourcesToDisplay[type].length === visibleItemsPerSection"
class="ressource-button"
@click="loadMore(type)">
Voir plus ↓
</div>
<div v-if="ressourcesToDisplay[type].length > visibleItemsPerSection"
class="ressource-button"
@click="showLess(type)">
Voir moins ↑
</div>
</div>
</div>
</template>
</div>
</template>
<script setup>
import RessourceCard from './RessourceCard.vue';
import useParseDate from '../composables/useParseDates';
import { ref, computed, watch, nextTick } from 'vue';
const props = defineProps({
content: Object,
couleur: String,
});
const searchQuery = ref('');
const selectedType = ref('');
const ressourcesToDisplay = ref({});
const visibleItemsPerSection = 4;
const selectedEtape = ref('');
const allRelatedEtapes = new Set();
props.content.ressources.forEach(ressource => {
if (ressource.relatedEtape) {
allRelatedEtapes.add(ressource.relatedEtape);
}
})
const filteredTypes = computed(() => {
return selectedType.value ? [selectedType.value] : props.content.ressourceTypes;
});
const ressourcesByType = (type) => {
return props.content.ressources
.filter(ressource => !selectedEtape.value || ressource.relatedEtape === selectedEtape.value)
.filter(ressource => ressource.ressourceType === type)
.filter(ressource =>
searchQuery.value === '' ||
ressource.title.toLowerCase().includes(searchQuery.value.toLowerCase())
).sort((a, b) => {
if (a.promoted && !b.promoted) return -1;
if (!a.promoted && b.promoted) return 1;
const dateA = useParseDate(a.date);
const dateB = useParseDate(b.date);
return dateB - dateA; // descending order (newest first)
});
};
const initializeRessources = (type) => {
ressourcesToDisplay.value[type] = ressourcesByType(type).slice(0, visibleItemsPerSection);
};
props.content.ressourceTypes.forEach(type => initializeRessources(type));
const loadMore = (type) => {
const currentLength = ressourcesToDisplay.value[type].length;
if (currentLength < ressourcesByType(type).length) {
ressourcesToDisplay.value[type] = ressourcesByType(type).slice(0, currentLength + visibleItemsPerSection);
}
};
const showLess = (type) => {
ressourcesToDisplay.value[type] = ressourcesByType(type).slice(0, visibleItemsPerSection);
};
watch(searchQuery, () => {
props.content.ressourceTypes.forEach(type => {
initializeRessources(type);
});
});
watch(ressourcesToDisplay.value, async () => {
await nextTick();
document.querySelectorAll('.ressource-item > div').forEach(el => {
const card = el.__vueParentComponent.exposed;
if (card && card.setClickableElements) {
card.setClickableElements();
}
});
}, { deep: true });
watch(selectedType, async () => {
await nextTick();
document.querySelectorAll('.ressource-item > div').forEach(el => {
const card = el.__vueParentComponent.exposed;
if (card && card.setClickableElements) {
card.setClickableElements();
}
});
});
watch(selectedEtape, () => {
props.content.ressourceTypes.forEach(type => {
initializeRessources(type);
});
});
</script>
<style lang="scss" scoped>
.itemFade-enter-active, .itemFade-leave-active {
transition: all 0.3s ease !important;
transition-delay: calc(0.1s * var(--index)) !important;
}
.itemFade-enter-from, .itemFade-leave-to {
opacity: 0;
transform: translateY(-20px) !important;
}
.itemFade-enter-to, .itemFade-leave-from {
opacity: 1;
transform: translateY(0px);
}
</style>

View File

@ -0,0 +1,34 @@
<template>
<div class="card" :class="direction ? (direction === 'previous' ? 'previous' : 'next') : 'solo'" :data-href="relatedContent.url">
<div class="icon" :style="{ backgroundColor: relatedContent.couleur }"></div>
<div class="card-content">
<div class="infos">
<div class="titre">{{ relatedContent.title }} <span>({{ relatedContent.postalCode.slice(0, 2) }})</span></div>
<div class="date">Du {{ relatedContent.dates.start.d }} {{ relatedContent.dates.start.m }}<br>au {{ relatedContent.dates.end.d }} {{ relatedContent.dates.end.m }} {{ relatedContent.dates.end.y }}</div>
</div>
<div class="vignette">
<img :src="relatedContent.vignette.url.small" :alt="relatedContent.vignette.alt">
</div>
</div>
</div>
</template>
<script setup>
const props = defineProps({
relatedContent: Object,
direction: String,
});
import { onMounted } from 'vue';
import router from '../../router/router';
import { handleClickableElements } from '../../utils/handle-navigation.js';
const siteName = document.querySelector('#site_name').innerText;
onMounted(() => {
const relatedEtapesCards = document.querySelectorAll('.card');
const baseUrl = window.location.protocol + "//" + window.location.host;
handleClickableElements(relatedEtapesCards, router, baseUrl, siteName);
});
</script>

View File

@ -16,6 +16,7 @@
:navigation="true" :navigation="true"
:pagination="true" :pagination="true"
:initialSlide="currentSlideIndex" :initialSlide="currentSlideIndex"
:keyboard="{ enabled: true }"
:injectStyles="[` :injectStyles="[`
.swiper-button-next, .swiper-button-prev { .swiper-button-next, .swiper-button-prev {
color: white; color: white;

View File

@ -1,53 +1,24 @@
<template> <template>
<footer> <footer :class="contentType === 'ressourceItem' ? 'footer-ressource' : ''">
<div class="brand-pattern pattern-bottom" :style="{ backgroundColor: couleur }"> <div class="brand-pattern pattern-bottom" :style="{ backgroundColor: couleur }">
<div class="pattern"></div> <div class="pattern"></div>
</div> </div>
<div v-if="contentType === 'etape' && (content.previous || content.next)" class="related-etape-links"> <div v-if="contentType === 'etape' && (content.previous || content.next)" class="related-etape-links">
<div v-if="content.previous" class="card previous" :data-href="content.previous.url"> <EtapeCard v-if="content.previous" :relatedContent="content.previous" :direction="'previous'"/>
<div class="icon" :style="{ backgroundColor: content.previous.couleur }"></div> <EtapeCard v-if="content.next" :relatedContent="content.next" :direction="'next'"/>
<div class="card-content"> </div>
<div class="infos"> <div v-if="contentType === 'ressourceItem' && content.relatedEtape" >
<div class="titre">{{ content.previous.title }} <span>({{ content.previous.postalCode.slice(0, 2) }})</span></div> <div class="related-etape-label" :style="{ backgroundColor: couleur }">Étape de la ressource</div>
<div class="date">Du {{ content.previous.dates.start.d }} {{ content.previous.dates.start.m }}<br>au {{ content.previous.dates.end.d }} {{ content.previous.dates.end.m }} {{ content.previous.dates.end.y }}</div> <div class="related-etape-links">
</div> <EtapeCard :relatedContent="content.relatedEtape" :direction="''" />
<div class="vignette">
<img :src="content.previous.vignette.url.small" :alt="content.previous.vignette.alt">
</div>
</div>
</div>
<div v-if="content.next" class="card next" :data-href="content.next.url">
<div class="icon" :style="{ backgroundColor: content.next.couleur }"></div>
<div class="card-content">
<div class="infos">
<div class="titre">{{ content.next.title }} <span>({{ content.next.postalCode.slice(0, 2) }})</span></div>
<div class="date">Du {{ content.next.dates.start.d }} {{ content.next.dates.start.m }}<br>au {{ content.next.dates.end.d }} {{ content.next.dates.end.m }} {{ content.next.dates.end.y }}</div>
</div>
<div class="vignette">
<img :src="content.next.vignette.url.small" :alt="content.next.vignette.alt">
</div>
</div>
</div> </div>
</div> </div>
</footer> </footer>
</template> </template>
<script setup> <script setup>
import { onMounted } from 'vue'; import EtapeCard from './EtapeCard.vue';
import router from '../../router/router';
import { useContentStore } from '../../stores/content';
import { useMapStore } from '../../stores/map';
import { handleClickableElements } from '../../utils/handle-navigation.js';
const brandColor = "#80c8bf";
const store = useContentStore();
const mapStore = useMapStore();
const siteName = document.querySelector('#site_name').innerText;
const props = defineProps({ const props = defineProps({
contentType: String, contentType: String,
@ -55,10 +26,4 @@ const props = defineProps({
couleur: String, couleur: String,
map: Object, map: Object,
}); });
onMounted(() => {
const relatedEtapesCards = document.querySelectorAll('.card');
const baseUrl = window.location.protocol + "//" + window.location.host;
handleClickableElements(relatedEtapesCards, store, router, baseUrl, siteName, mapStore);
});
</script> </script>

View File

@ -1,7 +1,7 @@
<template> <template>
<header :class="{ 'not-etape': contentType !== 'etape' }"> <header :class="{ 'not-etape': contentType !== 'etape' }">
<div class="cover"> <div v-if="contentType === 'etape'" class="cover" :style="{ backgroundColor: `${couleur.substring(0, 7)}99` }">
<img v-if="content.vignette" :src="content.vignette.url.medium" :alt="content.vignette.alt"> <img v-if="!loading && content.vignette" :src="content.vignette.url.medium" :alt="content.vignette.alt">
</div> </div>
<div v-if="contentType === 'etape' && content.dates" class="cartouche" :style="{ backgroundColor: couleur }"> <div v-if="contentType === 'etape' && content.dates" class="cartouche" :style="{ backgroundColor: couleur }">
<p>Étape n°{{content.etape_number}}</p> <p>Étape n°{{content.etape_number}}</p>
@ -14,7 +14,7 @@
<div class="locality"> <div class="locality">
<div class="top-triangle" v-if="contentType === 'etape'"></div> <div class="top-triangle" v-if="contentType === 'etape'"></div>
<div class="locality-title"> <div class="locality-title">
<h1>{{content.contentTitle}} <em v-if="content.adresse">({{ content.adresse.postal_code.slice(0, 2) }})</em></h1> <h1>{{ contentType === 'ressourceItem' ? 'Centre de ressources' : content.contentTitle }} <em v-if="content.adresse">({{ content.adresse.postal_code.slice(0, 2) }})</em></h1>
</div> </div>
</div> </div>
</div> </div>
@ -23,6 +23,7 @@
<script setup> <script setup>
const props = defineProps({ const props = defineProps({
loading: Boolean,
contentType: String, contentType: String,
content: Object, content: Object,
couleur: String, couleur: String,

View File

@ -4,13 +4,13 @@
<p v-html="content.intro"></p> <p v-html="content.intro"></p>
</div> </div>
<div v-for="partenaire in [...content.partenaires].sort((a, b) => a.weight - b.weight)" class="partenaire"> <div v-for="partenaire in [...content.partenaires].sort((a, b) => a.weight - b.weight)" class="partenaire">
<figure> <figure v-if="partenaire.logo_url && partenaire.link_url">
<a :href="partenaire.link_url" target="_blank"> <a :href="partenaire.link_url" target="_blank">
<img :src="partenaire.logo_url.small" :alt="partenaire.logo_alt"> <img :src="partenaire.logo_url.small" :alt="partenaire.logo_alt">
</a> </a>
</figure> </figure>
<div class="title"><p v-html="partenaire.title"></p></div> <div class="title"><p v-html="partenaire.title || ''"></p></div>
<div class="description"><p v-html="partenaire.description"></p></div> <div class="description"><p v-html="partenaire.description || ''"></p></div>
</div> </div>
</div> </div>
</template> </template>

View File

@ -0,0 +1,23 @@
<template>
<div class="partie related-ressources">
<h3>
<p :style="{ background: `linear-gradient(transparent 70%, ${couleur} 70%)` }">
Ressources liées
</p>
</h3>
<div class="ressource-list sm-ressource-list">
<div class="ressource-item" v-for="(relatedRessource, index) in relatedRessources">
<RessourceCard :ressource="relatedRessource" :index="index" />
</div>
</div>
</div>
</template>
<script setup>
import RessourceCard from './RessourceCard.vue';
const props = defineProps({
relatedRessources: Object,
couleur: String,
});
</script>

View File

@ -0,0 +1,43 @@
<template>
<div
:data-href="ressource.url"
:id="`ressource-${index}`"
:class="ressource.promoted ? 'promoted' : ''">
<figure>
<img :src="ressource?.vignette.url" :alt="ressource?.vignette.alt" />
</figure>
<div>
<h4>{{ ressource.title }}</h4>
<p v-if="ressource.date">Le {{ ressource?.date.d }} {{ ressource?.date.m }} {{ ressource?.date.y }}</p>
<p v-if="ressource.auteurice">Par {{ ressource?.auteurice }}</p>
</div>
</div>
</template>
<script setup>
import { onMounted } from 'vue';
import router from '../../router/router';
import { handleClickableElements } from '../../utils/handle-navigation.js';
const siteName = document.querySelector('#site_name').innerText;
let relatedItemCards, baseUrl;
onMounted(() => {
baseUrl = window.location.protocol + "//" + window.location.host;
setClickableElements();
});
const setClickableElements = () => {
relatedItemCards = document.querySelector(`#ressource-${props.index}`);
handleClickableElements([relatedItemCards], router, baseUrl, siteName);
}
defineExpose({
setClickableElements,
});
const props = defineProps({
ressource: Object,
index: String,
});
</script>

View File

@ -0,0 +1,60 @@
<template>
<header id="ressource-item-header">
<div class="retour">
<p data-href="/ressources"> Retour au centre de ressources</p>
</div>
<div class="type">{{ content?.displayedType }}</div>
<div class="title">
<h2
:style="{ background: `linear-gradient(transparent 70%, ${couleur} 70%)` }">
{{ content?.contentTitle }}
</h2>
</div>
<div v-if="content.auteurice && content.date" class="meta">
Par {{ content?.auteurice }}, le {{ content?.date.d }} {{ content?.date.m }} {{ content?.date.y }}
</div>
</header>
</template>
<script setup>
import { onMounted } from 'vue';
import router from '../../router/router';
import { handleClickableElements } from '../../utils/handle-navigation.js';
const siteName = document.querySelector('#site_name')?.innerText;
const props = defineProps({
content: Object,
couleur: String,
});
function setDisplayedType() {
const ressourceType = props.content?.ressourceType;
switch (ressourceType) {
case 'cartes_blanches':
props.content.displayedType = 'Carte blanche';
break;
case 'documents':
props.content.displayedType = 'Document';
break;
case 'galleries':
props.content.displayedType = 'Galerie';
break;
case 'videos':
props.content.displayedType = 'Vidéo';
break;
case 'reportages':
props.content.displayedType = 'Reportage';
break;
}
}
onMounted(() => {
const backToRessourcesLink = document.querySelectorAll('.retour > p');
const baseUrl = window.location.protocol + "//" + window.location.host;
handleClickableElements(backToRessourcesLink, router, baseUrl, siteName);
setDisplayedType();
});
</script>

View File

@ -59,7 +59,7 @@ const handleImageClick = (event) => {
const swiperEl = img.closest('swiper-container'); const swiperEl = img.closest('swiper-container');
swiperEl.querySelectorAll('swiper-slide').forEach((slide) => { swiperEl.querySelectorAll('swiper-slide').forEach((slide) => {
const img = slide.querySelector('img'); const img = slide.querySelector('img');
const altText = slide.querySelector('figcaption')?.textContent || ''; const altText = slide.querySelector('figcaption')?.textContent || '';
Object.values(props.partie.diaporama).forEach((image) => { Object.values(props.partie.diaporama).forEach((image) => {
if (image.url.medium === img.src) { if (image.url.medium === img.src) {
swiperMedia.push({ src: image.url.large, alt: altText }); swiperMedia.push({ src: image.url.large, alt: altText });

View File

@ -0,0 +1,50 @@
<template>
<div class="document"
:style="{ '--couleur': couleur }">
<div
class="intro"
v-html="partie.document.description">
</div>
<div class="document-grid">
<figure>
<a :href="partie.document.url" target="_blank">
<img :src="partie.document.vignette.url" :alt="partie.document.vignette.alt" />
</a>
</figure>
<div class="infos">
<p>{{ partie.document.titre }}</p>
<p>{{ partie.document.sousTitre }}</p>
<p>{{ partie.document.auteurice }}</p>
<p>{{ partie.document.date?.m }} {{ partie.document.date?.y }}</p>
</div>
<div class="download">
<a :href="partie.document.url" target="_blank">Télécharger le document</a>
</div>
</div>
</div>
</template>
<script setup>
const props = defineProps({
partie: Object,
couleur: String,
});
</script>
<style lang="scss" scoped>
.intro {
position: relative;
padding-left: 1.8rem;
margin: 3rem 0;
&::before {
content: '';
display: block;
position: absolute;
background-color: var(--couleur);
width: 0.8rem;
height: 100%;
margin-right: 1rem;
left: 0;
}
}
</style>

View File

@ -0,0 +1,84 @@
<template>
<div class="gallerie">
<h3><p :style="{ background: `linear-gradient(transparent 70%, ${couleur} 70%)` }">{{ partie.gallerie.titre }}</p></h3>
<div class="intro" v-html="partie.gallerie.introduction"></div>
<div class="images-grid">
<figure v-for="image in partie.gallerie.images">
<img :src="image.url.medium" :alt="image.alt" @click="handleImageClick">
<figcaption class="caption">{{ image.alt }}</figcaption>
</figure>
</div>
</div>
<ImageModale
:isOpen="isModaleOpen"
:image="currentImage"
:swiperContent="swiperPopupContent"
@close="closeImageModale" />
</template>
<script setup>
import { onMounted } from 'vue';
import { useImageModal } from '../../composables/useImageModale';
import ImageModale from '../ImageModale.vue';
// WebComponent
// https://swiperjs.com/element
import { register } from 'swiper/element/bundle';
register();
const props = defineProps({
partie: Object,
couleur: String,
});
const {
isModaleOpen,
currentImage,
swiperPopupContent,
openImageModale,
closeImageModale
} = useImageModal();
const handleImageClick = (event) => {
const clickedImg = event.target;
if (clickedImg.tagName === 'IMG') {
let swiperMedia = [];
const imgGrid = clickedImg.closest('.images-grid');
imgGrid.querySelectorAll('figure').forEach((figure) => {
const img = figure.querySelector('img');
const altText = figure.querySelector('figcaption')?.textContent || '';
Object.values(props.partie.gallerie.images).forEach((image) => {
if (img.src === image.url.medium) {
swiperMedia.push({ src: image.url.large, alt: altText });
}
});
});
openImageModale(clickedImg.src, clickedImg.alt, swiperMedia);
}
}
onMounted(() => {
setVerticalImages();
document.documentElement.style.setProperty('--etape-couleur', props.couleur);
});
function setVerticalImages() {
const images = document.querySelectorAll('.images-grid figure img');
images.forEach((img) => {
if (img.naturalHeight > img.naturalWidth) {
img.classList.add('vertical');
img.closest('figure').querySelector('figcaption').style.textAlign = 'center';
}
});
}
</script>
<style lang="scss" scoped>
:root {
--swiper-navigation-color: #1a1918; /* cf main.scss */
--swiper-pagination-color: var(--etape-couleur);
--swiper-navigation-top-offset: calc(100% - 1.5rem);
--swiper-navigation-sides-offset: 5vw; /* cf main.scss */
}
</style>

View File

@ -9,7 +9,7 @@ export function useImageModal() {
const hamburger = document.querySelector('#hamburger'); const hamburger = document.querySelector('#hamburger');
const menu = document.querySelector('#menu'); const menu = document.querySelector('#menu');
const openImageModale = (src, alt, swiperMedia) => { const openImageModale = (src, alt, swiperMedia) => {
currentImage.value = { src, alt }; currentImage.value = { src, alt };
swiperPopupContent.value = swiperMedia || []; swiperPopupContent.value = swiperMedia || [];
isModaleOpen.value = true; isModaleOpen.value = true;

View File

@ -0,0 +1,26 @@
const frenchMonthMap = {
janvier: 0,
février: 1,
mars: 2,
avril: 3,
mai: 4,
juin: 5,
juillet: 6,
août: 7,
septembre: 8,
octobre: 9,
novembre: 10,
décembre: 11
};
export default function parseDate(dateObj) {
if (!dateObj || !frenchMonthMap[dateObj.m?.toLowerCase()]) return new Date(0);
const day = Number(dateObj.d);
const month = frenchMonthMap[dateObj.m.toLowerCase()];
const year = Number(dateObj.y);
if (isNaN(day) || isNaN(month) || isNaN(year)) return new Date(0);
return new Date(year, month, day);
}

View File

@ -0,0 +1 @@
<svg width="24" height="24" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><style>.spinner_P7sC{transform-origin:center;animation:spinner_svv2 .75s infinite linear}@keyframes spinner_svv2{100%{transform:rotate(360deg)}}</style><path d="M10.14,1.16a11,11,0,0,0-9,8.92A1.59,1.59,0,0,0,2.46,12,1.52,1.52,0,0,0,4.11,10.7a8,8,0,0,1,6.66-6.61A1.42,1.42,0,0,0,12,2.69h0A1.57,1.57,0,0,0,10.14,1.16Z" class="spinner_P7sC"/></svg>

After

Width:  |  Height:  |  Size: 428 B

View File

@ -754,6 +754,7 @@ body{
} }
} }
> #content-modale { > #content-modale {
pointer-events: none;
padding-bottom: 20vh; padding-bottom: 20vh;
@media screen and (min-width: $desktop-min-width) { @media screen and (min-width: $desktop-min-width) {
z-index: 6; z-index: 6;
@ -761,6 +762,7 @@ body{
> div:not(.image-viewer-wrapper, .image-modale) { > div:not(.image-viewer-wrapper, .image-modale) {
padding-bottom: 5vh; padding-bottom: 5vh;
> .content-wrapper { > .content-wrapper {
pointer-events: all;
left: 1.5vw; left: 1.5vw;
width: calc($modale-width-mobile); width: calc($modale-width-mobile);
top: 15vh; top: 15vh;
@ -768,11 +770,29 @@ body{
position: relative; position: relative;
background-color: white; background-color: white;
font-size: $labeur-font-size-mobile; font-size: $labeur-font-size-mobile;
//padding-bottom: $modale-bottom-padding; &.ressource {
left: 5vw;
}
@media screen and (min-width: $desktop-min-width) { @media screen and (min-width: $desktop-min-width) {
font-size: $labeur-font-size-desktop; font-size: $labeur-font-size-desktop;
width: $modale-width-desktop; width: $modale-width-desktop;
} }
&.ressource {
@media screen and (min-width: $tablet-min-width) {
left: 8vw;
width: 84vw;
.locality-title {
width: 42vw !important;
margin-left: 21vw !important;
}
}
@media screen and (min-width: $desktop-min-width) {
.locality-title {
width: 30vw;
margin-left: 27vw;
}
}
}
img { img {
width: 100%; width: 100%;
height: auto; height: auto;
@ -788,6 +808,7 @@ body{
grid-template-rows: auto auto auto auto; grid-template-rows: auto auto auto auto;
> .cover { > .cover {
grid-row: 1 / span 1; grid-row: 1 / span 1;
min-height: 150px;
max-height: 60vh; max-height: 60vh;
display: flex; display: flex;
justify-content: center; justify-content: center;
@ -905,12 +926,41 @@ body{
} }
} }
> main { > main {
min-height: 30vh;
z-index: 1; z-index: 1;
position: relative; position: relative;
width: 100%; width: 100%;
padding: 0 $modale-x-padding; padding: 0 $modale-x-padding;
padding-bottom: 5vh; padding-bottom: 5vh;
box-sizing: border-box; box-sizing: border-box;
&:has(#ressource-item-header) {
// padding-right: 50%;
margin-bottom: 3rem;
@media screen and (min-width: $desktop-min-width) {
margin-bottom: 6rem;
padding: 0 calc($modale-x-padding * 4);
}
}
.content-loading {
width: 100%;
text-align: center;
padding-top: 2rem;
display: flex;
flex-direction: column;
align-items: center;
> div {
display: block;
width: 24px;
height: 24px;
background-image: url(/themes/custom/caravane/assets/pictograms/90-ring.svg);
background-size: 24px;
background-size: no-repeat;
margin-bottom: 1rem;
}
> p {
display: block;
}
}
> .partie, > .partie,
> #equipe { > #equipe {
width: 100%; width: 100%;
@ -938,7 +988,9 @@ body{
} }
.partie-title, .partie-title,
> .chiffres-cles, > .chiffres-cles,
> .entretien { > .entretien,
> .gallerie,
&.related-ressources {
> h3 { > h3 {
position: relative; position: relative;
display: inline-block; display: inline-block;
@ -1075,6 +1127,99 @@ body{
> .videos iframe { > .videos iframe {
margin: 2rem 0; margin: 2rem 0;
} }
> .gallerie {
> .intro {
margin-bottom: 2rem;
}
> .images-grid {
margin-top: 3rem;
display: grid;
grid-template-columns: 1fr 1fr;
column-gap: 5rem;
row-gap: 2rem;
@media screen and (min-width: $desktop-min-width) {
grid-template-columns: repeat(3, 1fr);
}
}
figure {
margin: 0;
> img {
cursor: pointer;
transform: scale(1);
transition: transform 0.2s ease-in-out;
&:hover {
transform: scale(1.02);
}
&.vertical {
box-sizing: border-box;
padding: 0 25%;
}
}
}
}
> .document {
.download {
> a {
color: $main-color;
text-decoration: none;
font-size: $sm-font-size-mobile;
background-color: $brand-color;
font-weight: bold;
padding: 0.5rem 1rem;
border-radius: 1rem;
display: inline-block;
transform: scale(1);
transition: transform 0.2s ease-in-out;
&:hover {
transform: scale(1.02);
}
@media screen and (min-width: $desktop-min-width) {
font-size: $sm-font-size-desktop;
}
}
}
> .document-grid {
display: grid;
grid-template-columns: 1fr 1fr;
grid-template-rows: auto auto;
column-gap: 2rem;
> figure {
grid-column: 1 / 2;
grid-row: 1 / 3;
margin: 0;
background-color: var(--couleur);
img {
padding: 0.6rem;
box-sizing: border-box;
width: 100%;
height: auto;
object-fit: cover;
transform: scale(1);
transition: transform 0.2s ease-in-out;
&:hover {
transform: scale(1.02);
}
@media screen and (min-width: $desktop-min-width) {
padding: 2rem;
}
}
}
> .infos {
> p {
margin: 0;
margin-bottom: 0.8rem;
&:first-of-type {
font-weight: bold;
}
}
}
> .download {
align-self: end;
justify-self: end;
}
}
}
} }
.caption { .caption {
font-size: $sm-font-size-mobile; font-size: $sm-font-size-mobile;
@ -1157,9 +1302,117 @@ body{
margin-top: 10vh; margin-top: 10vh;
} }
} }
#centre-de-ressource {
.filters {
margin: 3rem 0;
margin-top: 5rem;
> .search-bar{
margin-right: 2rem;
border: solid 1px var(--couleur);
padding: 0.5rem 1rem;
border-radius: 1rem;
font-size: $sm-font-size-mobile;
font-family: 'Marianne', sans-serif;
}
> select {
max-width: 20%;
margin-right: 2rem;
appearance: none;
border: solid 1px var(--couleur);
padding: 0.5rem 1rem;
font-size: $sm-font-size-mobile;
font-family: 'Marianne', sans-serif;
border-radius: 1rem;
background-color: white;
background: url("data:image/svg+xml,<svg height='10px' width='10px' viewBox='0 0 16 16' fill='rgba(128, 200, 191)' xmlns='http://www.w3.org/2000/svg'><path d='M7.247 11.14 2.451 5.658C1.885 5.013 2.345 4 3.204 4h9.592a1 1 0 0 1 .753 1.659l-4.796 5.48a1 1 0 0 1-1.506 0z'/></svg>") no-repeat;
background-position: calc(100% - 1rem) center !important;
padding-right: 2.5rem !important;
}
}
> .intro {
font-size: $sm-font-size-mobile;
margin-bottom: 4rem;
@media screen and (min-width: $desktop-min-width) {
font-size: $sm-font-size-desktop;
width: 66%;
}
}
> .type-section {
> h3 {
display: inline-block;
font-size: $l-font-size-mobile;
font-family: 'Joost', sans-serif;
margin: 0;
margin-bottom: 2rem;
z-index: 1;
position: relative;
padding: 0 0.5rem;
background: linear-gradient(transparent 70%, $brand-color 70%);
@media screen and (min-width: $desktop-min-width) {
font-size: $l-font-size-desktop;
}
}
> .button-container {
display: flex;
justify-content: center;
> .ressource-button {
display: inline;
font-size: $sm-font-size-mobile;
background-color: $brand-color;
padding: 0.5rem 1.5rem;
border-radius: 1rem;
cursor: pointer;
transition: transform 0.2s ease-in-out;
transform: scale(1);
&:hover {
transform: scale(0.95);
}
@media screen and (min-width: $desktop-min-width) {
font-size: $sm-font-size-desktop;
}
}
}
}
}
> #ressource-item-header {
> .retour {
margin-bottom: 1.5rem;
> p {
display: inline-block;
cursor: pointer;
font-size: $sm-font-size-mobile;
@media screen and (min-width: $desktop-min-width) {
font-size: $sm-font-size-desktop;
}
}
}
> .type,
> .meta {
text-align: center;
font-size: $m-font-size-mobile;
font-family: 'Joost', sans-serif;
@media screen and (min-width: $desktop-min-width) {
font-size: $m-font-size-desktop;
}
}
> .title {
width: 100%;
text-align: center;
margin-bottom: 1.5rem;
> h2 {
display: inline;
margin-top: 1rem;
font-size: $xl-font-size-mobile;
font-family: 'Joost', sans-serif;
@media screen and (min-width: $desktop-min-width) {
font-size: $xl-font-size-desktop;
}
}
}
}
} }
> .pieces-jointes { > .pieces-jointes {
z-index: 1; z-index: 0;
position: relative; position: relative;
padding: 0 $modale-x-padding; padding: 0 $modale-x-padding;
box-sizing: border-box; box-sizing: border-box;
@ -1214,7 +1467,10 @@ body{
} }
> footer { > footer {
position: relative; position: relative;
z-index: 0; z-index: -1;
&.footer-ressource {
overflow: hidden;
}
.pattern-bottom { .pattern-bottom {
mask-image: linear-gradient(to top, rgba(0,0,0,1), rgba(0,0,0,0)); mask-image: linear-gradient(to top, rgba(0,0,0,1), rgba(0,0,0,0));
height: $modale-bottom-padding; height: $modale-bottom-padding;
@ -1225,15 +1481,33 @@ body{
background-size: 300px; background-size: 300px;
background-size: repeat; background-size: repeat;
} }
> div:has(.related-etape-label) {
z-index: 1;
position: relative;
.related-etape-label {
display: inline-block;
padding: 0.5rem 1rem;
padding-left: calc($modale-x-padding / 2);
font-size: $sm-font-size-mobile;
margin-bottom: 1.5rem;
@media screen and (min-width: $desktop-min-width) {
margin-bottom: 0;
font-size: $sm-font-size-desktop;
}
}
}
.related-etape-links { .related-etape-links {
position: absolute;
//bottom: calc(($modale-bottom-padding / 2) * -1); //bottom: calc(($modale-bottom-padding / 2) * -1);
width: 100%;
box-sizing: border-box; box-sizing: border-box;
padding: 0 calc($modale-x-padding / 2); padding: 0 calc($modale-x-padding / 2);
display: grid; display: grid;
grid-template-rows: 1fr 1fr;
grid-template-columns: 1fr; grid-template-columns: 1fr;
width: 75%;
&:not(:has(.solo)) {
width: 100%;
position: absolute;
grid-template-rows: 1fr 1fr;
}
@media screen and (min-width: $desktop-min-width) { @media screen and (min-width: $desktop-min-width) {
grid-template-columns: 1fr 1fr; grid-template-columns: 1fr 1fr;
margin-top: 2.5rem; margin-top: 2.5rem;
@ -1323,6 +1597,84 @@ body{
} }
} }
} }
.ressource-list > div:not(.ressource-item),
.ressource-list.sm-ressource-list {
display: grid;
grid-template-columns: 1fr;
align-items: start;
gap: 2rem;
margin-bottom: 2.5rem;
@media screen and (min-width: $tablet-min-width) {
grid-template-columns: repeat(2, 1fr);
}
@media screen and (min-width: $desktop-min-width) {
grid-template-columns: repeat(4, 1fr);
}
&.sm-ressource-list {
margin-top: 2rem;
grid-template-columns: repeat(2, 1fr);
}
> .ressource-item > div {
display: flex;
gap: 0.8rem;
@media screen and (min-width: $desktop-min-width) {
gap: 1.2rem;
}
cursor: pointer;
transform: scale(1);
transition: transform 0.2s ease-in-out;
&.promoted {
&::after {
content: '';
position: absolute;
top: -10px;
left: -10px;
width: 20px !important;
height: 20px !important;
background-color: $brand-color;
mask-image: url("/themes/custom/caravane/assets/pictograms/hexagone.svg");
mask-size: contain;
mask-repeat: no-repeat;
}
}
&:hover {
transform: scale(1.05);
}
> figure {
width: 40%;
margin: 0;
}
> div {
width: 50%;
> h4 {
font-size: $m-font-size-mobile;
font-family: 'Joost', sans-serif;
margin: 0;
margin-bottom: 0.5rem;
@media screen and (min-width: $desktop-min-width) {
font-size: $m-font-size-desktop;
}
}
> p {
margin: 0;
font-size: $sm-font-size-mobile;
@media screen and (min-width: $desktop-min-width) {
font-size: $sm-font-size-desktop;
}
}
}
}
}
} }
> #animation-toggle { > #animation-toggle {
transition: opacity 0.3s ease-out; transition: opacity 0.3s ease-out;
@ -1416,4 +1768,14 @@ body{
} }
} }
} }
&.toolbar-fixed {
#content-modale .content-wrapper {
top: 20vh !important;
}
&.toolbar-vertical.toolbar-tray-open {
#content-modale .content-wrapper {
left: 15vw !important;
}
}
}
} }