From 820cdc903bd66e8588366868aea6bfdb75fe372a Mon Sep 17 00:00:00 2001 From: log101 Date: Fri, 26 Jul 2024 21:23:56 +0300 Subject: [PATCH] refactor: add utility classes for dom access --- src/components/LockedContent/domUtils.ts | 46 ++++++ src/components/LockedContent/geolocation.ts | 82 ++++++++++- src/scripts/initMap.ts | 2 +- src/scripts/lockedContent.ts | 148 +++++--------------- tsconfig.json | 1 + 5 files changed, 164 insertions(+), 115 deletions(-) create mode 100644 src/components/LockedContent/domUtils.ts diff --git a/src/components/LockedContent/domUtils.ts b/src/components/LockedContent/domUtils.ts new file mode 100644 index 0000000..3372f9e --- /dev/null +++ b/src/components/LockedContent/domUtils.ts @@ -0,0 +1,46 @@ +import { incrementUnlockCounter } from "./serverUtils"; + +function updateText(elemId: string, text: string) { + const elem = document.getElementById(elemId); + if (elem) elem.innerText = text; + else console.error("Element could not be found!"); +} + +function toggleClass(elemId: string, className: string) { + const elem = document.getElementById(elemId); + if (elem) elem.classList.toggle(className); + else console.error("Element could not be found!"); +} + +function removeClasses(elemId: string, ...inputs: string[]) { + const elem = document.getElementById(elemId); + if (elem) elem.classList.remove(...inputs); + else console.error("Element could not be found!"); +} + +function addClasses(elemId: string, ...inputs: string[]) { + const elem = document.getElementById(elemId); + if (elem) elem.classList.add(...inputs); + else console.error("Element could not be found!"); +} + +function removeElement(elemId: string) { + const elem = document.getElementById(elemId); + if (elem) elem.remove(); + else console.error("Element could not be found!"); +} + +function revealContent() { + incrementUnlockCounter(document.URL.slice(-12)); + removeClasses("content", "blur-2xl"); + removeElement("unlock-button-container"); +} + +export { + addClasses, + removeClasses, + removeElement, + toggleClass, + updateText, + revealContent, +}; diff --git a/src/components/LockedContent/geolocation.ts b/src/components/LockedContent/geolocation.ts index 396aaa3..48f2dcb 100644 --- a/src/components/LockedContent/geolocation.ts +++ b/src/components/LockedContent/geolocation.ts @@ -1,5 +1,68 @@ import Toastify from "toastify-js"; -import L from "leaflet"; +import L, { type LatLngTuple } from "leaflet"; +import { + addClasses, + removeClasses, + removeElement, + revealContent, + toggleClass, + updateText, +} from "./domUtils"; +import { mapLocationSuccessCallback } from "@/scripts/initMap"; + +// Update the elements according to distance remaining +function locationSuccessCallback( + position: GeolocationPosition, + targetPosition: LatLngTuple +) { + const newPosition = position.coords; + + // Calculate the distance remaining + const distance = calculateDistance( + [newPosition.latitude, newPosition.longitude], + targetPosition + ); + + // If user has arrived to destination + if (distance < 100) { + // Change the description texts + updateText("button-text", "İçeriği Göster"); + updateText("locked-content-description", "İçeriği görmek için butona bas!"); + + // Swap the icon + toggleClass("lock-icon", "hidden"); + toggleClass("unlock-icon", "hidden"); + + // Tansform the unlock button + removeClasses("unlock-content-button", "bg-primary", "hover:bg-primary/90"); + addClasses( + "unlock-content-button", + "bg-indigo-600", + "hover:bg-indigo-700", + "hover:animate-none", + "border-2", + "border-indigo-800" + ); + + // Wait for transition to finish then add animation + setTimeout(() => { + removeClasses("unlock-content-button", "duration-1000"); + addClasses("unlock-content-button", "animate-pulse"); + }, 800); + + // Reveal image when the unlock button is clicked + const unlockButton = document.getElementById("unlock-content-button"); + unlockButton?.addEventListener("click", revealContent); + } else { + const distanceText = generateDistanceText(distance); + updateText("locked-content-description", `Kalan mesafe: ${distanceText}`); + } + + removeElement("location-permission-button"); + + // Update leaflet controls + mapLocationSuccessCallback(position); +} // This callback will be fired on geolocation error function errorCallback(err: GeolocationPositionError) { @@ -55,4 +118,19 @@ function calculateDistance( return betweenMeters; } -export { errorCallback, calculateDistance }; +// Generates a human readable destination text according to +// distance remaining +function generateDistanceText(distance: number) { + if (distance > 1000) { + return `${(distance / 1000).toFixed()} KM`; + } else if (distance > 100) { + return `${distance.toFixed(0)} M`; + } +} + +export { + errorCallback, + locationSuccessCallback, + calculateDistance, + generateDistanceText, +}; diff --git a/src/scripts/initMap.ts b/src/scripts/initMap.ts index bd1f7b2..d5a3c6f 100644 --- a/src/scripts/initMap.ts +++ b/src/scripts/initMap.ts @@ -33,7 +33,7 @@ L.tileLayer("https://tile.openstreetmap.org/{z}/{x}/{y}.png", { let currentLocationMarker: L.Marker; -export function onLocationSuccess(position: GeolocationPosition) { +export function mapLocationSuccessCallback(position: GeolocationPosition) { const currentPos = { lat: position.coords.latitude, lng: position.coords.longitude, diff --git a/src/scripts/lockedContent.ts b/src/scripts/lockedContent.ts index c3a154a..d62cb6b 100644 --- a/src/scripts/lockedContent.ts +++ b/src/scripts/lockedContent.ts @@ -1,132 +1,58 @@ import { - calculateDistance, errorCallback, + locationSuccessCallback, } from "@/components/LockedContent/geolocation"; -import { incrementUnlockCounter } from "@/components/LockedContent/serverUtils"; -import { onLocationSuccess } from "@/scripts/initMap"; +import type { LatLngTuple } from "leaflet"; + +// Geolocation watch id to avoid duplicate watchPosition calls let watchId: number; -const targetLocation = document.getElementById("locked-content-container") - ?.dataset.targetPosition; - -// This element display the distance between the user and the -// target if geolocation permission is granted -const descriptionElement = document.getElementById( - "locked-content-description" -); +// DOM ELEMENTS const locationPermissionButton = document.getElementById( "location-permission-button" ); + const unlockButton = document.getElementById("unlock-content-button"); -const unlockIcon = document.getElementById("unlock-icon"); -const lockIcon = document.getElementById("lock-icon"); -const buttonText = document.getElementById("button-text"); -const description = document.getElementById("locked-content-description"); - -function updateText(elemId: string, text: string) { - const elem = document.getElementById(elemId); - if (elem) elem.innerText = text; - else console.error("Element could not be found!"); -} - -function toggleClass(elemId: string, className: string) { - const elem = document.getElementById(elemId); - if (elem) elem.classList.toggle(className); - else console.error("Element could not be found!"); -} - -if (locationPermissionButton) - locationPermissionButton.addEventListener("click", startWatchingLocation); - -// Generates a human readable destination text according to -// distance remaining -function generateDistanceText(distance: number) { - if (distance > 1000) { - return `${(distance / 1000).toFixed()} KM`; - } else if (distance > 100) { - return `${distance.toFixed(0)} M`; - } -} - -// Update the elements according to distance remaining -function updateCurrentLocation(position: GeolocationPosition) { - const newPosition = position.coords; - - if (!targetLocation) return; - // Calculate the distance remaining - const distance = calculateDistance( - [newPosition.latitude, newPosition.longitude], - JSON.parse(targetLocation) - ); - - if (distance < 100) { - // If user has arrived to destination - // Transform locked button to reveal button - updateText("button-text", "İçeriği Göster"); - toggleClass("lock-icon", "hidden"); - toggleClass("unlock-icon", "hidden"); - updateText("locked-content-description", "İçeriği görmek için butona bas!"); - unlockButton?.classList.remove("bg-primary", "hover:bg-primary/90"); - unlockButton?.classList.add( - "bg-indigo-600", - "hover:bg-indigo-700", - "hover:animate-none", - "border-2", - "border-indigo-800" - ); - setTimeout(() => { - unlockButton?.classList.remove("duration-1000"); - unlockButton?.classList.add("animate-pulse"); - }, 800); - - unlockButton?.addEventListener("click", () => { - const image = document.getElementById("content"); - const unlockButtonContainer = document.getElementById( - "unlock-button-container" - ); - incrementUnlockCounter(document.URL.slice(-12)); - if (image) image.classList.remove("blur-2xl"); - if (unlockButtonContainer) unlockButtonContainer.remove(); - }); - } else { - const distanceText = generateDistanceText(distance); - - if (descriptionElement) - descriptionElement.innerText = `Kalan mesafe: ${distanceText}`; - } - - if (locationPermissionButton) { - locationPermissionButton.remove(); - } - - // Update leaflet controls - onLocationSuccess(position); -} - -function startWatchingLocation() { - if (!watchId) { - watchId = window.navigator.geolocation.watchPosition( - updateCurrentLocation, - errorCallback - ); - } -} const lockedContentContainer = document.getElementById( "locked-content-container" ); -if (lockedContentContainer) - lockedContentContainer.addEventListener( - "askpermission", - startWatchingLocation - ); +// These elements MUST be defined +// Throw error if they are not found +if (!locationPermissionButton || !lockedContentContainer || !unlockButton) { + throw new Error("Element not found!"); +} +// EVENT LISTENERS +locationPermissionButton.addEventListener("click", startWatchingLocation); + +lockedContentContainer.addEventListener("askpermission", startWatchingLocation); + +const targetPositionString = lockedContentContainer.dataset.targetPosition; + +// TARGET_POSITION is required to calculate distance +if (!targetPositionString) throw new Error("Target position is not provided!"); + +const TARGET_POSITION = JSON.parse(targetPositionString) as LatLngTuple; + +// Call Geolocation API to start watching user location +function startWatchingLocation() { + if (!watchId) { + watchId = window.navigator.geolocation.watchPosition( + position => locationSuccessCallback(position, TARGET_POSITION), + errorCallback + ); + } +} + +// When the web page is loaded, check if user has given geolocation +// permission before navigator.permissions.query({ name: "geolocation" }).then(permissionStatus => { switch (permissionStatus.state) { case "granted": watchId = window.navigator.geolocation.watchPosition( - updateCurrentLocation, + position => locationSuccessCallback(position, TARGET_POSITION), errorCallback ); break; @@ -136,5 +62,3 @@ navigator.permissions.query({ name: "geolocation" }).then(permissionStatus => { break; } }); - -export { startWatchingLocation }; diff --git a/tsconfig.json b/tsconfig.json index 7ec5a42..a6834ed 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -7,6 +7,7 @@ "paths": { "@/*": ["./src/*"] }, + "noUnusedLocals": true, "experimentalDecorators": true, "useDefineForClassFields": false, }