From 4e6ea29d0592ce89f9885cda9777084993a0f385 Mon Sep 17 00:00:00 2001 From: dakur Date: Fri, 24 Jan 2025 21:21:38 +0100 Subject: [PATCH] wip --- .../scripts/administrativeUnitsMap/Filters.ts | 46 ---------- .../administrativeUnitsMap/InfoWindow.ts | 64 -------------- .../src/scripts/administrativeUnitsMap/Map.ts | 85 ------------------- .../scripts/administrativeUnitsMap/index.ts | 49 ----------- .../scripts/administrativeUnitsMap/types.ts | 17 ---- .../scripts/administrativeUnitsMap/utils.ts | 60 ------------- functions.php | 4 +- gulpfile.babel.js | 6 -- header.php | 7 ++ scripts/administrativeUnitsMap.js | 60 +++++++++++++ 10 files changed, 69 insertions(+), 329 deletions(-) delete mode 100644 frontend/src/scripts/administrativeUnitsMap/Filters.ts delete mode 100644 frontend/src/scripts/administrativeUnitsMap/InfoWindow.ts delete mode 100644 frontend/src/scripts/administrativeUnitsMap/Map.ts delete mode 100644 frontend/src/scripts/administrativeUnitsMap/index.ts delete mode 100644 frontend/src/scripts/administrativeUnitsMap/types.ts delete mode 100644 frontend/src/scripts/administrativeUnitsMap/utils.ts create mode 100644 scripts/administrativeUnitsMap.js diff --git a/frontend/src/scripts/administrativeUnitsMap/Filters.ts b/frontend/src/scripts/administrativeUnitsMap/Filters.ts deleted file mode 100644 index a8388a8..0000000 --- a/frontend/src/scripts/administrativeUnitsMap/Filters.ts +++ /dev/null @@ -1,46 +0,0 @@ -import Map from './Map'; - -export default class Filters { - private /* const */ ACTIVE_FILTER_ITEM_CSS_CLASS_SELECTOR = 'administrativeUnitsMap__filter--active'; - - public constructor( - private mapInstance: Map, - private items: NodeListOf, - ) { - this.attachListeners(); - } - - - public displayAll() - { - this.updateFilterActiveState(document.getElementById('mapa-vse')!); // map exists so this should be present as well - this.mapInstance.displayLayer(); - } - - public displayLayer(item: HTMLElement) - { - this.updateFilterActiveState(item); - this.mapInstance.displayLayer(item.dataset.slug); - } - - - private attachListeners(): void - { - this.items.forEach(item => - item.addEventListener('click', ev => { - window.history.pushState(null, '', item.children[0].getAttribute('href')); - ev.preventDefault(); - - this.displayLayer(item as HTMLElement); - })); - } - - private updateFilterActiveState(activeItem: HTMLElement): void - { - this.items.forEach(item => - item.classList.remove(this.ACTIVE_FILTER_ITEM_CSS_CLASS_SELECTOR)); - - activeItem.classList.add(this.ACTIVE_FILTER_ITEM_CSS_CLASS_SELECTOR); - } - -} diff --git a/frontend/src/scripts/administrativeUnitsMap/InfoWindow.ts b/frontend/src/scripts/administrativeUnitsMap/InfoWindow.ts deleted file mode 100644 index 72b7c45..0000000 --- a/frontend/src/scripts/administrativeUnitsMap/InfoWindow.ts +++ /dev/null @@ -1,64 +0,0 @@ -import {resolveUnitTitle} from './utils'; -import {OrganizationalUnit} from './types'; - - -export class InfoWindow -{ - infoWindow: google.maps.InfoWindow; - - public constructor( - private readonly mapInstance: google.maps.Map, - ) - { - // We want only one instance of InfoWindow due to closing previous opened windows - this.infoWindow = new google.maps.InfoWindow(); - } - - public display(unit: OrganizationalUnit, marker: google.maps.Marker): void - { - this.infoWindow.close(); // Close previously opened infowindow - this.infoWindow.setContent(InfoWindow.buildContent(unit)); // set content of info window - this.infoWindow.open(this.mapInstance, marker); // open window, which was clicked by user - } - - private static buildContent(unit: OrganizationalUnit): HTMLDivElement - { - const containerEl = document.createElement('div'); - containerEl.id = 'infowindow'; - containerEl.classList.add('administrativeUnitsMap__infoWindow') - - if (unit.image !== null) { - const imageContainerEl = containerEl.appendChild(document.createElement('div')); - imageContainerEl.classList.add('administrativeUnitsMap__infoWindowImageContainer'); - const imageEl = imageContainerEl.appendChild(document.createElement('img')); - imageEl.classList.add('administrativeUnitsMap__infoWindowImage'); - imageEl.src = unit.image; - } - - const contentEl = containerEl.appendChild(document.createElement('div')); - const metaEl = contentEl.appendChild(document.createElement('div')); - - metaEl.innerHTML = resolveUnitTitle(unit); - metaEl.innerHTML += `
Adresa: ${unit.address}`; - - if (unit.chairman !== null) { - metaEl.innerHTML += `
Předseda: ${unit.chairman}`; - } - - if (unit.website !== null ) { - metaEl.innerHTML += `
Web: ${unit.website}` - } - - if (unit.email !== null) { - metaEl.innerHTML += `
E-mail: ${unit.email}`; - } - - if (unit.description !== null) { - const descriptionEl = contentEl.appendChild(document.createElement('p')); - descriptionEl.classList.add('administrativeUnitsMap__infoWindowDescription') - descriptionEl.innerHTML += unit.description; - } - - return containerEl; - } -} diff --git a/frontend/src/scripts/administrativeUnitsMap/Map.ts b/frontend/src/scripts/administrativeUnitsMap/Map.ts deleted file mode 100644 index d5e5825..0000000 --- a/frontend/src/scripts/administrativeUnitsMap/Map.ts +++ /dev/null @@ -1,85 +0,0 @@ -import {InfoWindow} from './InfoWindow'; -import {resolveIconFileName, resolveUnitTitle, resolveUnitTypeSlug} from './utils'; -import {OverlappingMarkerSpiderfier} from 'ts-overlapping-marker-spiderfier'; -import {OrganizationalUnit} from './types'; - -export default class Map { - map: google.maps.Map; - mapLayers: google.maps.MVCObject; - infoWindow: InfoWindow; - markerCluster: OverlappingMarkerSpiderfier; - slugs: Array; - organizationalUnits: Array; - - public constructor(mapElement: HTMLElement) - { - // Maps Google map to place where we want display our map - this.map = new google.maps.Map(mapElement); - - // For layers by type, @source: https://stackoverflow.com/a/23036174 - this.mapLayers = new google.maps.MVCObject(); - - this.infoWindow = new InfoWindow(this.map); - - this.markerCluster = new OverlappingMarkerSpiderfier(this.map); - - this.slugs = []; - - const organizationalUnitsInJSON = mapElement.getAttribute('data-organizationalUnits'); - if (organizationalUnitsInJSON === null) { - throw new Error('Organizational units has not been passed.'); - } - this.organizationalUnits = JSON.parse(organizationalUnitsInJSON); - - this.placeMarkers(); - this.centerAndZoom(); - } - - - // place markers and set layers by type for filter - public placeMarkers(): void - { - this.organizationalUnits.forEach(unit => { - const slug = resolveUnitTypeSlug(unit); - - if (typeof slug !== 'undefined' && this.slugs.indexOf(slug) === -1) { - this.slugs.push(slug); - this.mapLayers.set(slug, this.map); - } - - this.placeMarker(unit); - }); - } - - // Inspiration from: https://stackoverflow.com/a/30013345 - private placeMarker(unit: OrganizationalUnit): void - { - // make marker and set option - const marker = new google.maps.Marker({ - position: {lat: unit.lat, lng: unit.lng}, - map: this.map, - title: resolveUnitTitle(unit), - icon: `https://brontosaurus.cz/wp-content/uploads/2024/12/${resolveIconFileName(unit)}`, - }); - - // Bind marker.map to the specific slug property of unitTypesLayers which is set and unset below [1] - marker.bindTo('map', this.mapLayers, resolveUnitTypeSlug(unit)); - - // Add spider listener for opening and closing info window - this.markerCluster.addMarker(marker, () => { - this.infoWindow.display(unit, marker); - }); - } - - public displayLayer(filter: string|null = null): void - { - this.slugs.forEach(slug => - this.mapLayers.set(slug, slug === filter || filter === null ? this.map : null)); - } - - public centerAndZoom(): void - { - this.map.setCenter(new google.maps.LatLng(49.7437572, 15.3386383)); // Czechia geographic center, see https://en.mapy.cz/s/gupehogeha - this.map.setZoom(7); - } -} diff --git a/frontend/src/scripts/administrativeUnitsMap/index.ts b/frontend/src/scripts/administrativeUnitsMap/index.ts deleted file mode 100644 index 5fc7aac..0000000 --- a/frontend/src/scripts/administrativeUnitsMap/index.ts +++ /dev/null @@ -1,49 +0,0 @@ -// @ts-ignore -const loadGoogleMapsApi = require('load-google-maps-api'); // must be this way, because of exporting using `export =` -import Map from './Map'; -import Filters from './Filters'; - -document.addEventListener('DOMContentLoaded', async () => { - try { - await loadGoogleMapsApi({key: 'AIzaSyDsxejbWcsI1eb4eoQJq47Eq9qxvSCMXSc'}); - initialize(); - - } catch (e) { - console.error(e); - } -}); - -// Initialize and add the mapInstance -function initialize(): void -{ - const mapEl = document.getElementById('map'); - if (mapEl === null) { // don't do anything if no map - return; - } - - const map = new Map(mapEl); // initialize map - const filters = new Filters(map, document.querySelectorAll('.administrativeUnitsMap__filter')); // initialize filters (listen to click events) - - // custom behavior for about-structure page - // ideally, administrativeUnitsMap should export API, but there's no time play with it now - const unitBaseLinkEl = document.getElementById('about-structure-unit-base-link'); - if (unitBaseLinkEl !== null) { - unitBaseLinkEl.addEventListener('click', _ => // listen to base unit link as well - filters.displayLayer(document.getElementById('mapa-zakladni-clanky')!)); // map exists so this should be present as well - } - - window.addEventListener('load', () => { // finally once the page is loaded, check if a layer filter should be activated - const hash = window.location.hash.substring(1); - - const selectedFilterLinkEl = hash !== '' - ? document.querySelector(`.administrativeUnitsMap__filters #${hash}`) - : null; - - if (selectedFilterLinkEl !== null) { // no filtering element with given hash found => do not filter - filters.displayLayer(selectedFilterLinkEl); - - } else { - filters.displayAll(); - } - }); -} diff --git a/frontend/src/scripts/administrativeUnitsMap/types.ts b/frontend/src/scripts/administrativeUnitsMap/types.ts deleted file mode 100644 index 8f7723d..0000000 --- a/frontend/src/scripts/administrativeUnitsMap/types.ts +++ /dev/null @@ -1,17 +0,0 @@ -export interface OrganizationalUnit -{ - name: string; - description: string|null, - image: string|null, - lat: number; - lng: number; - address: string; - chairman: string|null; - website: string|null; - email: string|null; - isOfTypeClub: boolean; - isOfTypeBase: boolean; - isOfTypeRegional: boolean; - isOfTypeOffice: boolean; - isOfTypeChildren: boolean; -} diff --git a/frontend/src/scripts/administrativeUnitsMap/utils.ts b/frontend/src/scripts/administrativeUnitsMap/utils.ts deleted file mode 100644 index 5119ac1..0000000 --- a/frontend/src/scripts/administrativeUnitsMap/utils.ts +++ /dev/null @@ -1,60 +0,0 @@ -import {OrganizationalUnit} from './types'; - -export function resolveUnitTypeSlug(unit: OrganizationalUnit): string | undefined -{ - if (unit.isOfTypeClub) { - return 'club'; - - } else if (unit.isOfTypeBase) { - return 'base'; - - } else if (unit.isOfTypeRegional) { - return 'regional'; - - } else if (unit.isOfTypeOffice) { - return 'office'; - - } else if (unit.isOfTypeChildren) { - return 'children'; - - } else { // no option selected, fall back to Google Maps default marker - return; - } -} - -export function resolveIconFileName(unit: OrganizationalUnit): string -{ - return `icon-marker-${resolveUnitTypeSlug(unit)}.svg`; -} - -export function resolveUnitTitle(unit: OrganizationalUnit): string -{ - const type = resolveUnitTypeLabel(unit); - if (type === null) { - return unit.name; - } - - return `${unit.name} – ${type}`; -} - -function resolveUnitTypeLabel(unit: OrganizationalUnit): string|null -{ - switch (true) { - case unit.isOfTypeClub: - return 'klub'; - - case unit.isOfTypeBase: - return 'základní článek'; - - case unit.isOfTypeRegional: - return 'regionální centrum'; - - case unit.isOfTypeOffice: - return 'ústředí'; - - case unit.isOfTypeChildren: - return 'dětský oddíl'; - } - - return null; -} diff --git a/functions.php b/functions.php index ba1ae45..e428ca9 100644 --- a/functions.php +++ b/functions.php @@ -86,8 +86,8 @@ Assets::staticScript('lazyLoad', $theme); Assets::staticScript('menuHandler', $theme); Assets::staticScript('lightbox', $theme); + Assets::staticScript('administrativeUnitsMap', $theme); Assets::script('references', $theme); - Assets::script('administrativeUnitsMap', $theme); Assets::style('style', $theme); }); @@ -149,7 +149,7 @@ function hb_administrative_units_map(string $administrationUnitsInJson, bool $ha
diff --git a/gulpfile.babel.js b/gulpfile.babel.js index ff37389..b02e10b 100644 --- a/gulpfile.babel.js +++ b/gulpfile.babel.js @@ -17,12 +17,6 @@ const paths = { const buildScriptsTask = (cb) => { buildScripts(cb, [ - { - distFileName: 'administrativeUnitsMap.js', - distPath: paths.scripts.global.dist, // folder to save the compiled js file into - sourceFileName: 'index.ts', - sourcePath: paths.scripts.global.src + '/administrativeUnitsMap', - }, { distFileName: 'references.js', distPath: paths.scripts.global.dist, // folder to save the compiled js file into diff --git a/header.php b/header.php index ded0868..e631874 100644 --- a/header.php +++ b/header.php @@ -25,6 +25,13 @@ + + diff --git a/scripts/administrativeUnitsMap.js b/scripts/administrativeUnitsMap.js new file mode 100644 index 0000000..a812775 --- /dev/null +++ b/scripts/administrativeUnitsMap.js @@ -0,0 +1,60 @@ +/** + * administrative unit schema: +{ + name: string; + description: string|null, + image: string|null, + lat: number; + lng: number; + address: string; + chairman: string|null; + website: string|null; + email: string|null; + isOfTypeClub: boolean; + isOfTypeBase: boolean; + isOfTypeRegional: boolean; + isOfTypeOffice: boolean; + isOfTypeChildren: boolean; +} +*/ + +document.addEventListener('DOMContentLoaded', async function () { + const { Map } = await google.maps.importLibrary("maps"); + const { AdvancedMarkerElement, PinElement } = await google.maps.importLibrary("marker"); + + const mapEl = document.getElementById("map"); + const administrativeUnits = JSON.parse(mapEl.getAttribute('data-administrativeUnits')); + + const map = new Map(mapEl, { + center: { lat: 49.000, lng: 16.000 }, + zoom: 8, + mapId: "b80d048e42b74f71", + }); + + // todo bulk add? + for (const unit of administrativeUnits) { + const color = resolveColor(unit); + const pinEl = new PinElement({ + background: color, + glyphColor: color, + }); + const marker = new AdvancedMarkerElement({ + map: map, + position: { lat: unit.lat, lng: unit.lng }, + title: unit.name, + content: pinEl.element, + }); + } + + map.centerAndZoom(); +}); + +function resolveColor(unit) { + // todo true colors + if (unit.isOfTypeClub) return "violet"; + if (unit.isOfTypeBase) return "orange"; + if (unit.isOfTypeRegional) return "blue"; + if (unit.isOfTypeOffice) return "var(--hb-colors-emphasizing)"; + if (unit.isOfTypeChildren) return "pink"; + throw new Error("Unsupported unit type"); +}