From 5f5283f406af5d66430c47cfc56c99fd1d9ad2fa Mon Sep 17 00:00:00 2001 From: Bastyen Date: Fri, 10 Nov 2023 16:05:11 +0100 Subject: [PATCH] improve touristic content --- src/components.d.ts | 55 +++++ src/components/grw-app/grw-app.scss | 8 +- src/components/grw-app/grw-app.tsx | 143 ++++++++++--- src/components/grw-app/readme.md | 16 +- src/components/grw-filter/readme.md | 4 +- src/components/grw-filters/grw-filters.tsx | 2 +- src/components/grw-filters/readme.md | 42 ++++ src/components/grw-map/grw-map.scss | 10 +- src/components/grw-map/grw-map.tsx | 92 +++++++-- .../grw-touristic-content-detail.scss | 190 ++++++++++++++++++ .../grw-touristic-content-detail.tsx | 174 ++++++++++++++++ .../grw-touristic-content-detail/readme.md | 38 ++++ .../grw-touristic-content.scss | 31 +++ .../grw-touristic-content.tsx | 8 +- .../grw-touristic-content/readme.md | 13 +- .../grw-trek-detail/grw-trek-detail.tsx | 16 +- src/i18n/i18n.ts | 30 ++- src/store/grw-touristic-content-provider.tsx | 66 ++++++ src/store/grw-trek-provider.tsx | 2 +- src/store/store.ts | 3 + src/types/types.ts | 14 +- 21 files changed, 876 insertions(+), 81 deletions(-) create mode 100644 src/components/grw-filters/readme.md create mode 100644 src/components/grw-touristic-content-detail/grw-touristic-content-detail.scss create mode 100644 src/components/grw-touristic-content-detail/grw-touristic-content-detail.tsx create mode 100644 src/components/grw-touristic-content-detail/readme.md create mode 100644 src/store/grw-touristic-content-provider.tsx diff --git a/src/components.d.ts b/src/components.d.ts index abb5c1d4..78cb0611 100644 --- a/src/components.d.ts +++ b/src/components.d.ts @@ -90,6 +90,22 @@ export namespace Components { interface GrwTouristicContent { "touristicContent": TouristicContent; } + interface GrwTouristicContentDetail { + "colorBackground": string; + "colorOnPrimaryContainer": string; + "colorOnSecondaryContainer": string; + "colorOnSurface": string; + "colorPrimaryApp": string; + "colorPrimaryContainer": string; + "colorSecondaryContainer": string; + "colorSurfaceContainerLow": string; + "isLargeView": boolean; + } + interface GrwTouristicContentProvider { + "api": string; + "languages": string; + "touristicContentId": string; + } interface GrwTrekCard { "colorOnSecondaryContainer": string; "colorOnSurface": string; @@ -154,6 +170,10 @@ export interface GrwMapCustomEvent extends CustomEvent { detail: T; target: HTMLGrwMapElement; } +export interface GrwTouristicContentCustomEvent extends CustomEvent { + detail: T; + target: HTMLGrwTouristicContentElement; +} export interface GrwTrekCardCustomEvent extends CustomEvent { detail: T; target: HTMLGrwTrekCardElement; @@ -223,6 +243,18 @@ declare global { prototype: HTMLGrwTouristicContentElement; new (): HTMLGrwTouristicContentElement; }; + interface HTMLGrwTouristicContentDetailElement extends Components.GrwTouristicContentDetail, HTMLStencilElement { + } + var HTMLGrwTouristicContentDetailElement: { + prototype: HTMLGrwTouristicContentDetailElement; + new (): HTMLGrwTouristicContentDetailElement; + }; + interface HTMLGrwTouristicContentProviderElement extends Components.GrwTouristicContentProvider, HTMLStencilElement { + } + var HTMLGrwTouristicContentProviderElement: { + prototype: HTMLGrwTouristicContentProviderElement; + new (): HTMLGrwTouristicContentProviderElement; + }; interface HTMLGrwTrekCardElement extends Components.GrwTrekCard, HTMLStencilElement { } var HTMLGrwTrekCardElement: { @@ -264,6 +296,8 @@ declare global { "grw-select-language": HTMLGrwSelectLanguageElement; "grw-sensitive-area-detail": HTMLGrwSensitiveAreaDetailElement; "grw-touristic-content": HTMLGrwTouristicContentElement; + "grw-touristic-content-detail": HTMLGrwTouristicContentDetailElement; + "grw-touristic-content-provider": HTMLGrwTouristicContentProviderElement; "grw-trek-card": HTMLGrwTrekCardElement; "grw-trek-detail": HTMLGrwTrekDetailElement; "grw-trek-provider": HTMLGrwTrekProviderElement; @@ -355,8 +389,25 @@ declare namespace LocalJSX { "sensitiveArea"?: SensitiveArea; } interface GrwTouristicContent { + "onTouristicContentCardPress"?: (event: GrwTouristicContentCustomEvent) => void; "touristicContent"?: TouristicContent; } + interface GrwTouristicContentDetail { + "colorBackground"?: string; + "colorOnPrimaryContainer"?: string; + "colorOnSecondaryContainer"?: string; + "colorOnSurface"?: string; + "colorPrimaryApp"?: string; + "colorPrimaryContainer"?: string; + "colorSecondaryContainer"?: string; + "colorSurfaceContainerLow"?: string; + "isLargeView"?: boolean; + } + interface GrwTouristicContentProvider { + "api"?: string; + "languages"?: string; + "touristicContentId"?: string; + } interface GrwTrekCard { "colorOnSecondaryContainer"?: string; "colorOnSurface"?: string; @@ -430,6 +481,8 @@ declare namespace LocalJSX { "grw-select-language": GrwSelectLanguage; "grw-sensitive-area-detail": GrwSensitiveAreaDetail; "grw-touristic-content": GrwTouristicContent; + "grw-touristic-content-detail": GrwTouristicContentDetail; + "grw-touristic-content-provider": GrwTouristicContentProvider; "grw-trek-card": GrwTrekCard; "grw-trek-detail": GrwTrekDetail; "grw-trek-provider": GrwTrekProvider; @@ -451,6 +504,8 @@ declare module "@stencil/core" { "grw-select-language": LocalJSX.GrwSelectLanguage & JSXBase.HTMLAttributes; "grw-sensitive-area-detail": LocalJSX.GrwSensitiveAreaDetail & JSXBase.HTMLAttributes; "grw-touristic-content": LocalJSX.GrwTouristicContent & JSXBase.HTMLAttributes; + "grw-touristic-content-detail": LocalJSX.GrwTouristicContentDetail & JSXBase.HTMLAttributes; + "grw-touristic-content-provider": LocalJSX.GrwTouristicContentProvider & JSXBase.HTMLAttributes; "grw-trek-card": LocalJSX.GrwTrekCard & JSXBase.HTMLAttributes; "grw-trek-detail": LocalJSX.GrwTrekDetail & JSXBase.HTMLAttributes; "grw-trek-provider": LocalJSX.GrwTrekProvider & JSXBase.HTMLAttributes; diff --git a/src/components/grw-app/grw-app.scss b/src/components/grw-app/grw-app.scss index bb6c0213..3ee5d15c 100644 --- a/src/components/grw-app/grw-app.scss +++ b/src/components/grw-app/grw-app.scss @@ -145,8 +145,10 @@ grw-app { .app-treks-list-container, .app-trek-detail-container, +.app-touristic-content-detail-container, .large-view-app-treks-list-container, .large-view-app-trek-detail-container { + max-width: 100%; height: 100%; display: flex; flex-direction: column; @@ -204,7 +206,8 @@ grw-app { width: 100%; } -grw-treks-list { +grw-treks-list, +grw-touristic-content-detail { max-height: calc(100vh - 64px); } @@ -222,7 +225,8 @@ grw-trek-detail { } .large-view-app-treks-list-container, -.large-view-app-trek-detail-container { +.large-view-app-trek-detail-container, +.large-view-app-touristic-content-detail-container { width: 50%; } diff --git a/src/components/grw-app/grw-app.tsx b/src/components/grw-app/grw-app.tsx index e77fefbc..2ab79487 100644 --- a/src/components/grw-app/grw-app.tsx +++ b/src/components/grw-app/grw-app.tsx @@ -10,11 +10,14 @@ import state, { onChange, reset } from 'store/store'; export class GrwApp { @Element() appElement: HTMLElement; @State() showTrek = false; + @State() showTouristicContent = false; @State() showTreksMap = false; @State() showTrekMap = false; + @State() showTouristicContentMap = false; @State() showFilters = false; @State() isLargeView = false; @State() currentTrekId: number; + @State() currentTouristicContentId: number; @Prop() appWidth: string = '100%'; @Prop() appHeight: string = '100vh'; @Prop() api: string; @@ -60,6 +63,9 @@ export class GrwApp { @Listen('trekCardPress', { target: 'window' }) onTrekCardPress(event: CustomEvent) { + this.currentTouristicContentId = null; + this.showTouristicContent = false; + state.currentTouristicContent = null; const parentTrek = state.parentTrekId ? state.parentTrekId : state.currentTrekSteps ? this.currentTrekId : null; state.currentTrek = null; this.currentTrekId = event.detail; @@ -68,7 +74,7 @@ export class GrwApp { const url = new URL(window.location.toString()); url.searchParams.set('trek', this.currentTrekId.toString()); if (state.currentTrekSteps) { - url.searchParams.set('parentTrek', parentTrek.toString()); + url.searchParams.set('parenttrek', parentTrek.toString()); } window.history.pushState({}, '', url); } @@ -81,7 +87,21 @@ export class GrwApp { this.showTrekMap = false; const url = new URL(window.location.toString()); url.searchParams.set('trek', this.currentTrekId.toString()); - url.searchParams.delete('parentTrek'); + url.searchParams.delete('parenttrek'); + window.history.pushState({}, '', url); + } + + @Listen('touristicContentCardPress', { target: 'window' }) + onTouristicContentCardPress(event: CustomEvent) { + state.currentTrek = null; + this.currentTrekId = null; + this.showTrek = false; + this.currentTouristicContentId = event.detail; + this.showTouristicContent = true; + this.showTouristicContentMap = false; + const url = new URL(window.location.toString()); + url.searchParams.delete('trek'); + url.searchParams.set('touristiccontent', this.currentTouristicContentId.toString()); window.history.pushState({}, '', url); } @@ -105,12 +125,17 @@ export class GrwApp { componentWillLoad() { const url = new URL(window.location.toString()); const trekParam = url.searchParams.get('trek'); - const parentTrekId = url.searchParams.get('parentTrek'); + const parentTrekId = url.searchParams.get('parenttrek'); + const touristicContentParam = url.searchParams.get('touristiccontent'); if (trekParam) { - window.history.replaceState({ isInitialHistoryWithTrek: true }, '', url); + window.history.replaceState({ isInitialHistoryWithDetails: true }, '', url); state.parentTrekId = parentTrekId ? Number(parentTrekId) : null; this.currentTrekId = Number(trekParam); - this.showTrek = !this.showTrek; + this.showTrek = true; + } else if (touristicContentParam) { + window.history.replaceState({ isInitialHistoryWithDetails: true }, '', url); + this.currentTouristicContentId = Number(touristicContentParam); + this.showTouristicContent = true; } } @@ -119,21 +144,26 @@ export class GrwApp { if (state.trekNetworkError) { const urlRedirect = new URL(window.location.toString()); urlRedirect.searchParams.delete('trek'); - urlRedirect.searchParams.delete('parentTrek'); + urlRedirect.searchParams.delete('parenttrek'); window.history.replaceState({}, '', urlRedirect); - this.onTrekDetailsClose(); + this.onDetailsClose(); } }); window.addEventListener('popstate', this.handlePopStateBind, false); this.handleView(); } - onTrekDetailsClose() { + onDetailsClose() { + this.currentTouristicContentId = null; + this.showTouristicContent = false; + this.showTouristicContentMap = false; + state.currentTouristicContent = null; + state.currentTrek = null; state.currentTrekSteps = null; state.parentTrekId = null; this.currentTrekId = null; - this.showTrek = !this.showTrek; + this.showTrek = false; } handleView() { @@ -147,10 +177,11 @@ export class GrwApp { } handleBackButton() { - if (window.history.state && window.history.state.isInitialHistoryWithTrek) { - this.onTrekDetailsClose(); + if (window.history.state && window.history.state.isInitialHistoryWithDetails) { + this.onDetailsClose(); const url = new URL(window.location.toString()); url.searchParams.delete('trek'); + url.searchParams.delete('touristiccontent'); window.history.pushState({}, '', url); } else { window.history.back(); @@ -160,23 +191,56 @@ export class GrwApp { handlePopState() { const url = new URL(window.location.toString()); const trekParam = Number(url.searchParams.get('trek')); + const touristicContentParam = url.searchParams.get('touristiccontent'); if (trekParam && this.currentTrekId !== trekParam) { + this.currentTouristicContentId = null; + this.showTouristicContent = false; state.currentTrek = null; this.currentTrekId = trekParam; this.showTrek = true; - } else if (!trekParam) { - this.onTrekDetailsClose(); + } else if (!trekParam && !touristicContentParam) { + this.onDetailsClose(); } } handleShowMap() { if (this.currentTrekId) { this.showTrekMap = !this.showTrekMap; + } else if (this.showTouristicContent) { + this.showTouristicContentMap = !this.showTouristicContentMap; } else { this.showTreksMap = !this.showTreksMap; } } + getMapVisibilityIconButton() { + let mapIconButton: string; + + if (this.showTrek) { + mapIconButton = this.showTrekMap ? 'summarize' : 'map'; + } else if (this.showTouristicContent) { + mapIconButton = this.showTouristicContentMap ? 'summarize' : 'map'; + } else { + mapIconButton = this.showTreksMap ? 'list' : 'map'; + } + + return mapIconButton; + } + + getMapVisibilityLabelButton() { + let mapLabelButton: string; + + if (this.showTrek) { + mapLabelButton = this.showTrekMap ? translate[state.language].showDetails : translate[state.language].showRoute; + } else if (this.showTouristicContent) { + mapLabelButton = this.showTouristicContentMap ? translate[state.language].showDetails : translate[state.language].showMap; + } else { + mapLabelButton = this.showTreksMap ? translate[state.language].showList : translate[state.language].showMap; + } + + return mapLabelButton; + } + disconnectedCallback() { reset(); window.removeEventListener('popstate', this.handlePopStateBind); @@ -205,12 +269,12 @@ export class GrwApp { '--app-height': this.appHeight, }} > - {!state.currentTreks && !state.currentTrek && ( + {!state.currentTreks && !state.currentTrek && !state.currentTouristicContent && (
)} - {!this.showTrek && !state.treks && ( + {!this.showTrek && !this.showTouristicContent && !state.treks && ( )} - {(state.currentTreks || state.currentTrek) && ( + {this.showTouristicContent && this.currentTouristicContentId && ( + + )} + {(state.currentTreks || state.currentTrek || state.currentTouristicContent) && (
- {!this.showTrek ? ( + {!this.showTrek && !this.showTouristicContent ? (
@@ -260,7 +327,7 @@ export class GrwApp {
- {this.showTrek && !state.currentTrek && ( + {((this.showTrek && !state.currentTrek) || (this.showTouristicContent && !state.currentTouristicContent)) && (
@@ -298,12 +365,34 @@ export class GrwApp { >
)} + {this.showTouristicContent && ( +
+ +
+ )}
- {(!this.showTrek || (this.showTrek && state.currentTrek)) && ( + {((!this.showTrek && !this.showTouristicContent) || (this.showTrek && state.currentTrek) || (this.showTouristicContent && state.currentTouristicContent)) && (
)} diff --git a/src/components/grw-app/readme.md b/src/components/grw-app/readme.md index 7799eb79..ce96af7a 100644 --- a/src/components/grw-app/readme.md +++ b/src/components/grw-app/readme.md @@ -46,41 +46,39 @@ | `zoom` | `zoom` | | `number` | `undefined` | -## Events - -| Event | Description | Type | -| ------------- | ----------- | ------------------ | -| `resetFilter` | | `CustomEvent` | - - ## Dependencies ### Depends on - [grw-treks-provider](../../store) - [grw-trek-provider](../../store) +- [grw-touristic-content-provider](../../store) - [grw-search](../grw-search) - [grw-treks-list](../grw-treks-list) - [grw-trek-detail](../grw-trek-detail) +- [grw-touristic-content-detail](../grw-touristic-content-detail) - [grw-map](../grw-map) -- [grw-filter](../grw-filter) +- [grw-filters](../grw-filters) ### Graph ```mermaid graph TD; grw-app --> grw-treks-provider grw-app --> grw-trek-provider + grw-app --> grw-touristic-content-provider grw-app --> grw-search grw-app --> grw-treks-list grw-app --> grw-trek-detail + grw-app --> grw-touristic-content-detail grw-app --> grw-map - grw-app --> grw-filter + grw-app --> grw-filters grw-treks-list --> grw-trek-card grw-trek-detail --> grw-trek-card grw-trek-detail --> grw-sensitive-area-detail grw-trek-detail --> grw-information-desk grw-trek-detail --> grw-poi grw-trek-detail --> grw-touristic-content + grw-filters --> grw-filter style grw-app fill:#f9f,stroke:#333,stroke-width:4px ``` diff --git a/src/components/grw-filter/readme.md b/src/components/grw-filter/readme.md index 8441d84a..49231da8 100644 --- a/src/components/grw-filter/readme.md +++ b/src/components/grw-filter/readme.md @@ -19,12 +19,12 @@ ### Used by - - [grw-app](../grw-app) + - [grw-filters](../grw-filters) ### Graph ```mermaid graph TD; - grw-app --> grw-filter + grw-filters --> grw-filter style grw-filter fill:#f9f,stroke:#333,stroke-width:4px ``` diff --git a/src/components/grw-filters/grw-filters.tsx b/src/components/grw-filters/grw-filters.tsx index ec87a0b5..5a2ff59b 100644 --- a/src/components/grw-filters/grw-filters.tsx +++ b/src/components/grw-filters/grw-filters.tsx @@ -162,7 +162,7 @@ export class GrwFilters { )} {state['cities'].length > 0 && (
- +
)}
diff --git a/src/components/grw-filters/readme.md b/src/components/grw-filters/readme.md new file mode 100644 index 00000000..1a669411 --- /dev/null +++ b/src/components/grw-filters/readme.md @@ -0,0 +1,42 @@ +# grw-filters + + + + + + +## Properties + +| Property | Attribute | Description | Type | Default | +| --------------- | --------- | ----------- | ---------- | ----------- | +| `handleFilters` | -- | | `Function` | `undefined` | + + +## Events + +| Event | Description | Type | +| ------------- | ----------- | ------------------ | +| `resetFilter` | | `CustomEvent` | + + +## Dependencies + +### Used by + + - [grw-app](../grw-app) + +### Depends on + +- [grw-filter](../grw-filter) + +### Graph +```mermaid +graph TD; + grw-filters --> grw-filter + grw-app --> grw-filters + style grw-filters fill:#f9f,stroke:#333,stroke-width:4px +``` + +---------------------------------------------- + +*Built with [StencilJS](https://stenciljs.com/)* diff --git a/src/components/grw-map/grw-map.scss b/src/components/grw-map/grw-map.scss index eaa20937..5646b98f 100644 --- a/src/components/grw-map/grw-map.scss +++ b/src/components/grw-map/grw-map.scss @@ -92,10 +92,12 @@ .trek-marker, .step-marker, +.touristic-content-marker, .selected-trek-marker, .selected-step-marker { .trek-marker-container, - .step-marker-container { + .step-marker-container, + .touristic-content-marker-container { display: flex; justify-content: center; align-items: center; @@ -115,7 +117,8 @@ width: 24px !important; } } - .trek-marker-container { + .trek-marker-container, + .touristic-content-marker-container { background-color: var(--color-surface); } .step-marker-container { @@ -126,7 +129,8 @@ .selected-trek-marker, .selected-step-marker { .trek-marker-container, - .step-marker-container { + .step-marker-container, + .touristic-content-marker-container { width: 64px; height: 64px; img, diff --git a/src/components/grw-map/grw-map.tsx b/src/components/grw-map/grw-map.tsx index 8d64f0b6..802c7638 100644 --- a/src/components/grw-map/grw-map.tsx +++ b/src/components/grw-map/grw-map.tsx @@ -17,6 +17,8 @@ import { getTrekGeometry } from 'services/treks.service'; export class GrwMap { mapRef: HTMLElement; elevationRef: HTMLElement; + maxZoom = 19; + @Event() trekCardPress: EventEmitter; @State() mapIsReady = false; @Prop() nameLayer: string; @@ -47,12 +49,13 @@ export class GrwMap { currentStepsLayer: L.GeoJSON; selectedCurrentTrekLayer: L.GeoJSON; selectedCurrentStepLayer: L.GeoJSON; - currentPointsReferenceLayer: L.GeoJSON; + currentReferencePointsLayer: L.GeoJSON; currentParkingLayer: L.GeoJSON; currentSensitiveAreasLayer: L.GeoJSON; currentPoisLayer: L.GeoJSON; currentInformationDesksLayer: L.GeoJSON; currentToutisticContentsLayer: L.GeoJSON; + currentToutisticContentLayer: L.GeoJSON; elevationControl: L.Control.Layers; layersControl: L.Control.Layers; userLayersState: { @@ -78,8 +81,8 @@ export class GrwMap { @Listen('descriptionIsInViewport', { target: 'window' }) descriptionIsInViewport(event: CustomEvent) { - if (this.currentPointsReferenceLayer) { - this.handleLayerVisibility(event.detail, this.currentPointsReferenceLayer); + if (this.currentReferencePointsLayer) { + this.handleLayerVisibility(event.detail, this.currentReferencePointsLayer); } } @@ -163,7 +166,7 @@ export class GrwMap { this.tileLayer.push({ key: `${nameLayers[index]}`, value: L.tileLayer(urlLayer, { - maxZoom: 19, + maxZoom: this.maxZoom, attribution: `${ attributionLayers[index] && attributionLayers[index] !== '' ? attributionLayers[index].concat(' | ') : '' }Powered by Geotrek`, @@ -207,6 +210,8 @@ export class GrwMap { this.addTrek(); } else if (state.currentTreks) { this.addTreks(); + } else if (state.currentTouristicContent) { + this.addTouristicContent(); } onChange('currentTreks', () => { @@ -223,7 +228,21 @@ export class GrwMap { if (state.currentTrek) { this.removeTreks(); this.addTrek(); - } else { + } else if (state.currentTreks) { + this.removeTrek(); + this.addTreks(); + } + }); + + onChange('currentTouristicContent', () => { + if (state.currentTouristicContent) { + this.removeTrek(); + this.removeTreks(); + this.addTouristicContent(); + } else if (state.currentTrek) { + this.removeTreks(); + this.addTrek(); + } else if (state.currentTrek) { this.removeTrek(); this.addTreks(); } @@ -553,19 +572,19 @@ export class GrwMap { } if (state.currentTrek.points_reference) { - const currentPointsReferenceFeatureCollection: FeatureCollection = { + const currentReferencePointsFeatureCollection: FeatureCollection = { type: 'FeatureCollection', features: [], }; - currentPointsReferenceFeatureCollection.features.push({ + currentReferencePointsFeatureCollection.features.push({ type: 'Feature', properties: {}, geometry: state.currentTrek.points_reference, }); let index = 0; - this.currentPointsReferenceLayer = L.geoJSON(currentPointsReferenceFeatureCollection, { + this.currentReferencePointsLayer = L.geoJSON(currentReferencePointsFeatureCollection, { pointToLayer: (_geoJsonPoint, latlng) => { index += 1; return L.marker(latlng, { @@ -697,8 +716,8 @@ export class GrwMap { if (this.currentStepsLayer) { overlays[translate[state.language].layers.steps] = this.currentStepsLayer; } - if (this.currentPointsReferenceLayer) { - overlays[translate[state.language].layers.pointsReference] = this.currentPointsReferenceLayer; + if (this.currentReferencePointsLayer) { + overlays[translate[state.language].layers.referencePoints] = this.currentReferencePointsLayer; } if (this.currentParkingLayer) { overlays[translate[state.language].layers.parking] = this.currentParkingLayer; @@ -960,9 +979,9 @@ export class GrwMap { this.currentInformationDesksLayer = null; } - if (this.currentPointsReferenceLayer) { - this.map.removeLayer(this.currentPointsReferenceLayer); - this.currentPointsReferenceLayer = null; + if (this.currentReferencePointsLayer) { + this.map.removeLayer(this.currentReferencePointsLayer); + this.currentReferencePointsLayer = null; } if (this.currentStepsLayer) { @@ -1006,6 +1025,53 @@ export class GrwMap { } } + addTouristicContent() { + const touristicContentsFeatureCollection: FeatureCollection = { + type: 'FeatureCollection', + features: [], + }; + + if (state.currentTouristicContent) { + touristicContentsFeatureCollection.features.push({ + type: 'Feature', + geometry: { type: 'Point', coordinates: state.currentTouristicContent.geometry.coordinates }, + properties: { + id: state.currentTouristicContent.id, + name: state.currentTouristicContent.name, + practice: state.touristicContentCategories.find(touristicContentCategory => touristicContentCategory.id === state.currentTouristicContent.category).pictogram, + }, + }); + } + + if (!this.currentToutisticContentLayer) { + this.currentToutisticContentLayer = L.geoJSON(touristicContentsFeatureCollection, { + pointToLayer: (geoJsonPoint, latlng) => + L.marker(latlng, { + icon: L.divIcon({ + html: geoJsonPoint.properties.practice + ? `
` + : `
`, + className: 'touristic-content-marker', + iconSize: 32, + iconAnchor: [18, 0], + } as any), + autoPanOnFocus: false, + } as any), + }); + this.map.addLayer(this.currentToutisticContentLayer); + } + + this.map.setView([state.currentTouristicContent.geometry.coordinates[1], state.currentTouristicContent.geometry.coordinates[0]], this.maxZoom); + !this.mapIsReady && (this.mapIsReady = !this.mapIsReady); + } + + removeTouristicContent() { + if (this.currentToutisticContentLayer) { + this.map.removeLayer(this.currentToutisticContentLayer); + this.currentToutisticContentLayer = null; + } + } + render() { const layersImageSrc = getAssetPath(`${Build.isDev ? '/' : ''}assets/layers.svg`); const contractImageSrc = getAssetPath(`${Build.isDev ? '/' : ''}assets/contract.svg`); diff --git a/src/components/grw-touristic-content-detail/grw-touristic-content-detail.scss b/src/components/grw-touristic-content-detail/grw-touristic-content-detail.scss new file mode 100644 index 00000000..eb99f36c --- /dev/null +++ b/src/components/grw-touristic-content-detail/grw-touristic-content-detail.scss @@ -0,0 +1,190 @@ +@use '~swiper/swiper.scss'; +@use '~swiper/modules/navigation/navigation.scss'; +@use '~swiper/modules/pagination/pagination.scss'; + +.swiper { + z-index: 0; +} + +.material-symbols { + font-family: 'Material Symbols Outlined'; +} + +.material-symbols-outlined { + font-variation-settings: 'FILL' 0, 'wght' 400, 'GRAD' 0, 'opsz' 48; +} + +:host { + --swiper-navigation-size: 24px; + --swiper-navigation-color: var(--color-on-primary-container); + --swiper-pagination-color: var(--color-primary-container); + --swiper-pagination-bullet-size: 12px; + .swiper-button-prev, + .swiper-button-next { + background-color: var(--color-primary-container); + padding: 4px; + width: 32px; + height: 32px; + border-radius: 32px; + box-shadow: var(--elevation-1); + &:hover { + box-shadow: var(--elevation-2); + } + } +} + +grw-touristic-content-detail { + overflow: hidden; +} + +.touristic-content-detail-container { + font-family: 'Roboto'; + color: var(--color-on-surface); + display: flex; + flex-direction: column; + height: 100%; + max-height: 100%; + overflow-y: auto; + .touristic-content-category-container { + display: flex; + align-items: center; + padding: 0px 24px; + margin: 8px 0px; + .touristic-content-category-name { + display: -webkit-box; + -webkit-box-orient: vertical; + -webkit-line-clamp: 1; + overflow: hidden; + } + img { + font-size: 24px; + width: 24px; + height: 24px; + margin-right: 8px; + background-color: var(--color-primary-app); + } + } + .name { + color: var(--color-on-surface); + font-size: 22px; + line-height: 28px; + font-weight: 400; + padding: 0px 24px; + margin: 8px 0px; + } + .image, + .default-poi-img { + cursor: pointer; + width: 100%; + height: 100%; + object-fit: cover; + } + .default-poi-img { + background-color: var(--color-primary-container); + object-fit: contain; + } + .description-teaser, + .description-container, + .useful-information-container, + .contact-container { + padding: 0px 24px 0px 24px; + } + .description-container, + .useful-information-container, + .contact-container { + font-size: 16px; + line-height: 24px; + font-weight: 400; + a { + color: var(--color-primary-app); + } + } + .description-title, + .useful-information-title, + .contact-title { + color: var(--color-on-surface); + font-size: 22px; + line-height: 28px; + font-weight: 400; + font-weight: bold; + margin-bottom: 16px; + } +} + +.detail-bottom-space { + min-height: var(--detail-bottom-space-height); + height: var(--detail-bottom-space-height); + width: 100%; + background-color: var(--color-background); +} + +.images-container { + display: flex; + max-width: 100%; + max-height: 400px; +} + +.divider { + margin: 16px; + border-top: 1px solid var(--color-outline); +} + +.cities-container { + display: flex; + align-items: center; + padding: 24px 24px 0px 24px; + .cities-title { + color: var(--color-on-surface); + font-weight: bold; + white-space: nowrap; + margin-right: 8px; + } +} + +.downloads-container { + padding: 0px 24px 0px 24px; + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + .download-title { + color: var(--color-on-surface); + font-size: 16px; + line-height: 24px; + font-weight: 500; + } + .links-container { + display: flex; + flex-wrap: wrap; + a { + margin-top: 8px; + background-color: var(--color-primary-container); + color: var(--color-on-primary-container); + border-radius: 8px; + padding: 8px 16px; + box-shadow: var(--elevation-1); + height: 32px; + gap: 8px; + box-sizing: border-box; + text-align: start; + vertical-align: middle; + display: flex; + flex-direction: row; + align-items: center; + justify-content: center; + position: relative; + text-decoration: none; + &:nth-child(2) { + margin: 8px 12px 0px 12px; + } + &:hover { + box-shadow: var(--elevation-2); + } + } + } +} + +.email-container, +.website-container { + display: flex; +} diff --git a/src/components/grw-touristic-content-detail/grw-touristic-content-detail.tsx b/src/components/grw-touristic-content-detail/grw-touristic-content-detail.tsx new file mode 100644 index 00000000..aa9f5356 --- /dev/null +++ b/src/components/grw-touristic-content-detail/grw-touristic-content-detail.tsx @@ -0,0 +1,174 @@ +import { Build, Component, Host, Prop, State, getAssetPath, h } from '@stencil/core'; +import { translate } from 'i18n/i18n'; +import state from 'store/store'; +import Swiper, { FreeMode, Keyboard, Mousewheel, Navigation, Pagination } from 'swiper'; + +@Component({ + tag: 'grw-touristic-content-detail', + styleUrl: 'grw-touristic-content-detail.scss', + shadow: true, +}) +export class GrwTouristicContentDetail { + swiperImages?: Swiper; + swiperImagesRef?: HTMLDivElement; + prevElImagesRef?: HTMLDivElement; + nextElImagesRef?: HTMLDivElement; + paginationElImagesRef?: HTMLDivElement; + presentationRef?: HTMLDivElement; + + @State() displayFullscreen = false; + @Prop() colorPrimaryApp = '#6b0030'; + @Prop() colorOnSurface = '#49454e'; + @Prop() colorPrimaryContainer = '#eaddff'; + @Prop() colorOnPrimaryContainer = '#21005e'; + @Prop() colorSecondaryContainer = '#e8def8'; + @Prop() colorOnSecondaryContainer = '#1d192b'; + @Prop() colorSurfaceContainerLow = '#f7f2fa'; + @Prop() colorBackground = '#fef7ff'; + @Prop() isLargeView = false; + + descriptionRef?: HTMLDivElement; + touristicContentDetailContainerRef?: HTMLElement; + + componentDidLoad() { + this.swiperImages = new Swiper(this.swiperImagesRef, { + modules: [Navigation, Pagination, Keyboard, FreeMode, Mousewheel], + navigation: { + prevEl: this.prevElImagesRef, + nextEl: this.nextElImagesRef, + }, + pagination: { el: this.paginationElImagesRef }, + allowTouchMove: false, + keyboard: false, + }); + this.swiperImagesRef.onfullscreenchange = () => { + this.displayFullscreen = !this.displayFullscreen; + this.displayFullscreen ? this.swiperImages.keyboard.enable() : this.swiperImages.keyboard.disable(); + }; + } + + handleFullscreen() { + if (state.currentTouristicContent.attachments && state.currentTouristicContent.attachments[0] && state.currentTouristicContent.attachments[0].url) { + this.swiperImagesRef.requestFullscreen(); + } + } + + render() { + const defaultImageSrc = getAssetPath(`${Build.isDev ? '/' : ''}assets/default-image.svg`); + const touristicContentCategory = + state.touristicContentCategories && + state.currentTouristicContent && + state.touristicContentCategories.find(touristicContentCategory => touristicContentCategory.id === state.currentTouristicContent.category); + const cities = state.currentTouristicContent && state.currentTouristicContent.cities.map(currentCity => state.cities.find(city => city.id === currentCity)?.name); + return ( + + {state.currentTouristicContent && ( +
(this.touristicContentDetailContainerRef = el)}> +
(this.presentationRef = el)}> +
(this.swiperImagesRef = el)}> +
+ {state.currentTouristicContent.attachments.length > 0 ? ( + state.currentTouristicContent.attachments + .filter(attachment => attachment.type === 'image') + .map(attachment => ( +
+ this.handleFullscreen()} /> +
+ )) + ) : ( +
+ +
+ )} +
+
(this.paginationElImagesRef = el)}>
+
(this.prevElImagesRef = el)}>
+
(this.nextElImagesRef = el)}>
+
+
+ +
{state.currentTouristicContent.name}
+
+
+
{translate[state.language].download}
+ +
+ {state.currentTouristicContent.description_teaser && ( +
+
+
+
+ )} +
+ {state.currentTouristicContent.practical_info && ( +
(this.descriptionRef = el)}> +
{translate[state.language].description}
+
+
+ )} + {cities && cities.length > 0 && ( +
+
{translate[state.language].city} : 
+
+
+ )} + {state.currentTouristicContent.practical_info && ( +
+
+
+
{translate[state.language].usefulInformation} : 
+
+
+
+ )} + {(state.currentTouristicContent.contact || state.currentTouristicContent.email || state.currentTouristicContent.website) && ( +
+
+ +
+
{translate[state.language].contact} : 
+ {state.currentTouristicContent.contact &&
} + {state.currentTouristicContent.email && ( + + )} + {state.currentTouristicContent.website && ( +
+
{translate[state.language].website} : 
+
+
+ )} +
+
+ )} +
+
+ )} +
+ ); + } +} diff --git a/src/components/grw-touristic-content-detail/readme.md b/src/components/grw-touristic-content-detail/readme.md new file mode 100644 index 00000000..3329dd7a --- /dev/null +++ b/src/components/grw-touristic-content-detail/readme.md @@ -0,0 +1,38 @@ +# grw-touristic-content-detail + + + + + + +## Properties + +| Property | Attribute | Description | Type | Default | +| --------------------------- | ------------------------------ | ----------- | --------- | ----------- | +| `colorBackground` | `color-background` | | `string` | `'#fef7ff'` | +| `colorOnPrimaryContainer` | `color-on-primary-container` | | `string` | `'#21005e'` | +| `colorOnSecondaryContainer` | `color-on-secondary-container` | | `string` | `'#1d192b'` | +| `colorOnSurface` | `color-on-surface` | | `string` | `'#49454e'` | +| `colorPrimaryApp` | `color-primary-app` | | `string` | `'#6b0030'` | +| `colorPrimaryContainer` | `color-primary-container` | | `string` | `'#eaddff'` | +| `colorSecondaryContainer` | `color-secondary-container` | | `string` | `'#e8def8'` | +| `colorSurfaceContainerLow` | `color-surface-container-low` | | `string` | `'#f7f2fa'` | +| `isLargeView` | `is-large-view` | | `boolean` | `false` | + + +## Dependencies + +### Used by + + - [grw-app](../grw-app) + +### Graph +```mermaid +graph TD; + grw-app --> grw-touristic-content-detail + style grw-touristic-content-detail fill:#f9f,stroke:#333,stroke-width:4px +``` + +---------------------------------------------- + +*Built with [StencilJS](https://stenciljs.com/)* diff --git a/src/components/grw-touristic-content/grw-touristic-content.scss b/src/components/grw-touristic-content/grw-touristic-content.scss index 94656a66..08405de7 100644 --- a/src/components/grw-touristic-content/grw-touristic-content.scss +++ b/src/components/grw-touristic-content/grw-touristic-content.scss @@ -101,3 +101,34 @@ line-height: 24px; } } + +.touristic-content-more-detail-container { + padding: 0px 12px 12px 12px; + .more-details-button { + cursor: pointer; + background-color: var(--color-primary-container); + color: var(--color-on-primary-container); + border-radius: 8px; + padding: 8px 16px; + box-shadow: var(--elevation-1); + width: 100%; + height: 32px; + gap: 8px; + box-sizing: border-box; + text-align: start; + vertical-align: middle; + display: flex; + flex-direction: row; + align-items: center; + justify-content: center; + position: relative; + border: none; + font-size: 14px; + &:hover { + box-shadow: var(--elevation-2); + } + span { + font-size: 18px; + } + } +} diff --git a/src/components/grw-touristic-content/grw-touristic-content.tsx b/src/components/grw-touristic-content/grw-touristic-content.tsx index 833d8518..71b64c73 100644 --- a/src/components/grw-touristic-content/grw-touristic-content.tsx +++ b/src/components/grw-touristic-content/grw-touristic-content.tsx @@ -1,4 +1,4 @@ -import { Build, Component, Host, Prop, State, getAssetPath, h } from '@stencil/core'; +import { Build, Component, Host, getAssetPath, h, State, Prop, Event, EventEmitter } from '@stencil/core'; import state from 'store/store'; import Swiper, { Keyboard, Navigation, Pagination } from 'swiper'; import { TouristicContent } from 'types/types'; @@ -9,6 +9,7 @@ import { TouristicContent } from 'types/types'; shadow: true, }) export class GrwTouristicContent { + @Event() touristicContentCardPress: EventEmitter; swiperTouristicContent?: Swiper; swiperTouristicContentRef?: HTMLDivElement; prevElTouristicContentRef?: HTMLDivElement; @@ -83,6 +84,11 @@ export class GrwTouristicContent {
{this.touristicContent.name}
+
+ +
); } diff --git a/src/components/grw-touristic-content/readme.md b/src/components/grw-touristic-content/readme.md index 75bf9420..fe40bbb3 100644 --- a/src/components/grw-touristic-content/readme.md +++ b/src/components/grw-touristic-content/readme.md @@ -7,9 +7,16 @@ ## Properties -| Property | Attribute | Description | Type | Default | -| ------------------ | --------- | ----------- | --------------------------------------------------------------------------------------------------------------------------- | ----------- | -| `touristicContent` | -- | | `{ id: number; name: string; attachments: Attachments; description_teaser: string; category: number; geometry: Geometry; }` | `undefined` | +| Property | Attribute | Description | Type | Default | +| ------------------ | --------- | ----------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------- | +| `touristicContent` | -- | | `{ id: number; name: string; attachments: Attachments; description?: string; description_teaser?: string; practical_info?: string; category: number; geometry: Point; cities?: string[]; source?: number[]; pdf?: string; contact?: string; email?: string; website?: string; }` | `undefined` | + + +## Events + +| Event | Description | Type | +| --------------------------- | ----------- | --------------------- | +| `touristicContentCardPress` | | `CustomEvent` | ## Dependencies diff --git a/src/components/grw-trek-detail/grw-trek-detail.tsx b/src/components/grw-trek-detail/grw-trek-detail.tsx index 904fe6d5..96188d93 100644 --- a/src/components/grw-trek-detail/grw-trek-detail.tsx +++ b/src/components/grw-trek-detail/grw-trek-detail.tsx @@ -545,13 +545,15 @@ export class GrwTrekDetail {
{this.currentTrek.description_teaser &&
} {this.currentTrek.ambiance &&
} -
{state.parentTrekId && state.parentTrek && this.currentTrek.id !== state.parentTrekId && ( -
- +
+
+
+ +
)} {state.currentTrekSteps && ( @@ -603,7 +605,7 @@ export class GrwTrekDetail { )} {this.currentTrek.cities && this.currentTrek.cities.length > 0 && (
-
{translate[state.language].cities} : 
+
{translate[state.language].crossedCities} : 
)} diff --git a/src/i18n/i18n.ts b/src/i18n/i18n.ts index 6696603d..5e067c4e 100644 --- a/src/i18n/i18n.ts +++ b/src/i18n/i18n.ts @@ -10,6 +10,7 @@ interface Translation { showMap: string; showRoute: string; showDetails: string; + download: string; downloads: string; description: string; departure: string; @@ -33,7 +34,8 @@ interface Translation { pois: Function; touristicContents: Function; sources: string; - cities: string; + city: string; + crossedCities: string; options: { presentation: string; description: string; @@ -45,7 +47,7 @@ interface Translation { touristicContents: string; }; layers: { - pointsReference: string; + referencePoints: string; parking: string; sensitiveArea: string; informationPlaces: string; @@ -62,6 +64,10 @@ interface Translation { ok: string; location: string; steps: string; + usefulInformation: string; + contact: string; + email: string; + website: string; } interface AvailableTranslations { @@ -82,6 +88,7 @@ export const translate: AvailableTranslations = { showMap: 'Voir la carte', showRoute: "Voir l'itinéraire", showDetails: 'Voir la fiche', + download: 'Téléchargement', downloads: 'Téléchargements', description: 'Description', departure: 'Départ', @@ -105,7 +112,8 @@ export const translate: AvailableTranslations = { pois: (poisLength: number) => `Les ${poisLength} patrimoines à découvrir`, touristicContents: (touristicContentsLength: number) => `À proximité (${touristicContentsLength})`, sources: 'Sources', - cities: 'Communes traversées', + city: 'Commune', + crossedCities: 'Communes traversées', options: { presentation: 'Présentation', description: 'Description', @@ -117,7 +125,7 @@ export const translate: AvailableTranslations = { touristicContents: 'À proximité', }, layers: { - pointsReference: 'Points de référence', + referencePoints: 'Points de référence', parking: 'Parking conseillé', sensitiveArea: 'Zones de sensibilité environnementale', informationPlaces: 'Lieux de renseignement', @@ -134,6 +142,10 @@ export const translate: AvailableTranslations = { ok: 'Ok', location: 'Localisation', steps: 'étapes', + usefulInformation: 'Informations pratiques', + contact: 'Contact', + email: 'Email', + website: 'Site web', }, en: { filter: 'Filter', @@ -147,6 +159,7 @@ export const translate: AvailableTranslations = { showMap: 'Show the map', showRoute: 'Show the route', showDetails: 'Show the details', + download: 'Download', downloads: 'Downloads', description: 'Description', departure: 'Departure', @@ -170,7 +183,8 @@ export const translate: AvailableTranslations = { pois: (poisLength: number) => `${poisLength} points of interest`, touristicContents: (touristicContentsLength: number) => `Near (${touristicContentsLength})`, sources: 'Sources', - cities: 'Cities crossed', + city: 'City', + crossedCities: 'Cities crossed', options: { presentation: 'Presentation', description: 'Description', @@ -182,7 +196,7 @@ export const translate: AvailableTranslations = { touristicContents: 'Near', }, layers: { - pointsReference: 'Reference points', + referencePoints: 'Reference points', parking: 'Parking', sensitiveArea: 'Sensitive areas', informationPlaces: 'Information places', @@ -198,6 +212,10 @@ export const translate: AvailableTranslations = { erase: 'Erase', ok: 'Ok', location: 'Location', + usefulInformation: 'Useful information', steps: 'steps', + contact: 'Contact', + email: 'Email', + website: 'Website', }, }; diff --git a/src/store/grw-touristic-content-provider.tsx b/src/store/grw-touristic-content-provider.tsx new file mode 100644 index 00000000..47a31fbd --- /dev/null +++ b/src/store/grw-touristic-content-provider.tsx @@ -0,0 +1,66 @@ +import { Build, Component, h, Host, Prop } from '@stencil/core'; +import state from 'store/store'; + +@Component({ + tag: 'grw-touristic-content-provider', + shadow: true, +}) +export class GrwTouristicContentProvider { + @Prop() languages = 'fr'; + @Prop() api: string; + @Prop() touristicContentId: string; + controller = new AbortController(); + signal = this.controller.signal; + init: RequestInit = { cache: Build.isDev ? 'force-cache' : 'default', signal: this.signal }; + + connectedCallback() { + if (!state.api) { + state.api = this.api; + state.languages = this.languages.split(','); + state.language = state.languages[0]; + } + this.handleTouristicContent(); + } + + handleTouristicContent() { + const requests = []; + requests.push(!state.cities ? fetch(`${state.api}city/?language=${state.language}&fields=id,name&published=true&page_size=999`, this.init) : new Response('null')); + requests.push( + !state.touristicContentCategories + ? fetch(`${state.api}touristiccontent_category/?language=${state.language}&published=true&fields=id,label,pictogram&page_size=999`, this.init) + : new Response('null'), + ); + + Promise.all([ + ...requests, + fetch( + `${state.api}touristiccontent/${this.touristicContentId}/?language=${state.language}&published=true&fields=id,name,attachments,description,description_teaser,category,geometry,cities,pdf,practical_info,contact,email,website`, + this.init, + ), + ]) + .then(responses => Promise.all(responses.map(response => response.json()))) + .then(async ([cities, touristicContentCategory, touristicContent]) => { + state.trekNetworkError = false; + + if (cities) { + state.cities = cities.results; + } + if (touristicContentCategory) { + state.touristicContentCategories = touristicContentCategory.results; + } + state.currentTouristicContent = touristicContent; + }) + .catch(() => { + state.trekNetworkError = true; + }); + } + + disconnectedCallback() { + this.controller.abort(); + state.trekNetworkError = false; + } + + render() { + return ; + } +} diff --git a/src/store/grw-trek-provider.tsx b/src/store/grw-trek-provider.tsx index 1b13a65e..49d4cba3 100644 --- a/src/store/grw-trek-provider.tsx +++ b/src/store/grw-trek-provider.tsx @@ -56,7 +56,7 @@ export class GrwTrekProvider { this.init, ), fetch( - `${state.api}touristiccontent/?language=${state.language}&near_trek=${this.trekId}&published=true&fields=id,name,attachments,description_teaser,category,geometry&page_size=999`, + `${state.api}touristiccontent/?language=${state.language}&near_trek=${this.trekId}&published=true&fields=id,name,attachments,category,geometry&page_size=999`, this.init, ), fetch(`${state.api}touristiccontent_category/?language=${state.language}&published=true&fields=id,label,pictogram&page_size=999`, this.init), diff --git a/src/store/store.ts b/src/store/store.ts index ba9cc5c7..a6516880 100644 --- a/src/store/store.ts +++ b/src/store/store.ts @@ -22,6 +22,7 @@ import { Districts, TouristicContents, TouristicContentCategories, + TouristicContent, } from 'types/types'; const { state, onChange, reset } = createStore<{ @@ -63,6 +64,7 @@ const { state, onChange, reset } = createStore<{ selectedStepId: number; touristicContents: TouristicContents; touristicContentCategories: TouristicContentCategories; + currentTouristicContent: TouristicContent; }>({ api: null, languages: null, @@ -102,6 +104,7 @@ const { state, onChange, reset } = createStore<{ selectedStepId: null, touristicContents: null, touristicContentCategories: null, + currentTouristicContent: null, }); export { onChange, reset }; diff --git a/src/types/types.ts b/src/types/types.ts index ca5c2dd9..6b954828 100644 --- a/src/types/types.ts +++ b/src/types/types.ts @@ -1,4 +1,4 @@ -import { LineString, Geometry, Position, MultiPoint } from 'geojson'; +import { LineString, Geometry, Position, MultiPoint, Point } from 'geojson'; export type Treks = Trek[]; @@ -232,9 +232,17 @@ export type TouristicContent = { id: number; name: string; attachments: Attachments; - description_teaser: string; + description?: string; + description_teaser?: string; + practical_info?: string; category: number; - geometry: Geometry; + geometry: Point; + cities?: string[]; + source?: number[]; + pdf?: string; + contact?: string; + email?: string; + website?: string; }; export type TouristicContents = TouristicContent[];