From e7e449a079dba2c58018a516707617d50a3bbead Mon Sep 17 00:00:00 2001 From: log101 Date: Thu, 18 Jul 2024 14:02:45 +0300 Subject: [PATCH] feat: refactor locked content component --- src/components/LockedContent/geolocation.ts | 63 ++++++ src/components/LockedContent/middleware.ts | 11 + src/components/LockedContent/svg.ts | 31 +++ src/components/LockedContent/templates.ts | 12 +- src/components/locked-content-lit.ts | 233 +++++--------------- 5 files changed, 168 insertions(+), 182 deletions(-) create mode 100644 src/components/LockedContent/geolocation.ts create mode 100644 src/components/LockedContent/middleware.ts create mode 100644 src/components/LockedContent/svg.ts diff --git a/src/components/LockedContent/geolocation.ts b/src/components/LockedContent/geolocation.ts new file mode 100644 index 0000000..9fc6ec7 --- /dev/null +++ b/src/components/LockedContent/geolocation.ts @@ -0,0 +1,63 @@ +import Toastify from "toastify-js"; +import L from "leaflet"; + +// This callback will be fired on geolocation error +function errorCallback(err: GeolocationPositionError) { + let errorMessage; + // Show toast accoring to the error state + switch (err.code) { + case GeolocationPositionError.PERMISSION_DENIED: + errorMessage = + "Konum izni alınamadı, lütfen tarayıcınızın ve cihazınızın gizlilik ayarlarını kontrol edin."; + break; + case GeolocationPositionError.POSITION_UNAVAILABLE: + errorMessage = + "Konumunuz tespit edilemedi, lütfen biraz sonra tekrar deneyiniz."; + break; + case GeolocationPositionError.TIMEOUT: + errorMessage = + "Konum isteği zaman aşımına uğradı, lütfen sayfayı yenileyip tekrar deneyiniz."; + break; + default: + errorMessage = + "Konum izni alınamadı, lütfen tarayıcınızın ve cihazınızın gizlilik ayarlarını kontrol edin."; + break; + } + + Toastify({ + text: errorMessage, + duration: 3000, + gravity: "top", + position: "center", + stopOnFocus: true, + style: { + background: "black", + borderRadius: "6px", + margin: "16px", + }, + onClick: function () {}, + }).showToast(); +} + +function calculateDistance( + currentPosition: GeolocationPosition, + targetPosition: L.LatLngTuple +) { + const pos = { + lat: currentPosition.coords.latitude, + lng: currentPosition.coords.longitude, + }; + + // Get target position in latitudes and longitudes + const targetLatLng = L.latLng(targetPosition); + + // Get current position in latitudes and longitudes + const currentLatLng = L.latLng(pos); + + // Calculate the distance between target and current position in meters + const betweenMeters = currentLatLng.distanceTo(targetLatLng); + + return betweenMeters; +} + +export { errorCallback, calculateDistance }; diff --git a/src/components/LockedContent/middleware.ts b/src/components/LockedContent/middleware.ts new file mode 100644 index 0000000..2c00837 --- /dev/null +++ b/src/components/LockedContent/middleware.ts @@ -0,0 +1,11 @@ +// This counter is shown at the bottom of the page and incremented +// each time "show content" button is clicked +function incrementUnlockCounter(id: string | undefined) { + if (id) { + fetch(`http://localhost:3000/api/location/increment/${id}`, { + method: "PATCH", + }); + } +} + +export { incrementUnlockCounter }; diff --git a/src/components/LockedContent/svg.ts b/src/components/LockedContent/svg.ts new file mode 100644 index 0000000..f3ab932 --- /dev/null +++ b/src/components/LockedContent/svg.ts @@ -0,0 +1,31 @@ +import { html } from "lit"; + +// Locked lock icon +const lockSVG = html` + +`; + +// Unlocked lock icon +const unlockSVG = html` + + + +`; + +export { lockSVG, unlockSVG }; diff --git a/src/components/LockedContent/templates.ts b/src/components/LockedContent/templates.ts index a824f10..a81ff21 100644 --- a/src/components/LockedContent/templates.ts +++ b/src/components/LockedContent/templates.ts @@ -1,4 +1,5 @@ import { html, type TemplateResult } from "lit"; +import { lockSVG, unlockSVG } from "./svg"; // This template is shown when user hasn't give geolocation permission yet // When user click the button user is asked for geolocation permission @@ -29,13 +30,13 @@ function permissionButtonTemplate(onClickHandler: () => void) { } // This template is shown when user has given permission but has not arrived yet -function lockedButtonTemplate(icon: TemplateResult<1>, proximityText: string) { +function lockedButtonTemplate(proximityText: string | undefined) { return html`
@@ -51,17 +52,14 @@ function lockedButtonTemplate(icon: TemplateResult<1>, proximityText: string) { // This template is shown when user has arrived to the target location // When user click the button counter at the bottom of the page is incremented // and image is revealed -function unlockedButtonTemplate( - icon: TemplateResult<1>, - onClickHandler: () => void -) { +function unlockedButtonTemplate(onClickHandler: () => void) { return html`
diff --git a/src/components/locked-content-lit.ts b/src/components/locked-content-lit.ts index 5a85317..3564daf 100644 --- a/src/components/locked-content-lit.ts +++ b/src/components/locked-content-lit.ts @@ -1,12 +1,27 @@ +// Lit imports import { LitElement, html, nothing, unsafeCSS, type CSSResultGroup } from "lit"; import { customElement, property, state } from "lit/decorators.js"; -import L, { type LatLngTuple } from "leaflet"; -import Toastify from "toastify-js"; +// Leaflet +import { type LatLngTuple } from "leaflet"; +// Styles import globalStyles from "@/styles/globals.css?inline"; import lockedContentStyles from "../styles/locked-content.css?inline"; +// Templates +import { + lockedButtonTemplate, + permissionButtonTemplate, + unlockedButtonTemplate, +} from "./LockedContent/templates"; + +// Geolocation utils +import { calculateDistance, errorCallback } from "./LockedContent/geolocation"; +import { incrementUnlockCounter } from "./LockedContent/middleware"; + +// LockedContent is a custom element watching user location and blurring +// given image until user has arrived a certain position @customElement("locked-content-lit") export class LockedContent extends LitElement { // Constants @@ -37,212 +52,82 @@ export class LockedContent extends LitElement { @state() protected _arrived = false; @state() - protected _targetProximityText?: string; + protected _distanceText?: string; @state() protected _watchId?: number; - // Locked lock icon - lockSVG = html` - - `; - - // Unlocked lock icon - unlockSVG = html` - - - - `; - // This callback will be fired when geolocation info is available successCallback(position: GeolocationPosition) { // Set hasGeolocationPermission state true to change the template if (!this._hasGeolocationPermission) this._hasGeolocationPermission = true; - const pos = { - lat: position.coords.latitude, - lng: position.coords.longitude, - }; - - // targetPosition attribute must be set for geolocation feature to work + // Target position must be set if (!this.targetPosition) return; - // Get target position in latitudes and longitudes - const targetLatLng = L.latLng(this.targetPosition); - - // Get current position in latitudes and longitudes - const currentLatLng = L.latLng(pos); - // Calculate the distance between target and current position in meters - const betweenMeters = currentLatLng.distanceTo(targetLatLng); + const distance = calculateDistance(position, this.targetPosition); + // Update the text based on the distance + this._updateDistanceText(distance); + + this._checkArrived(distance); + } + + private _updateDistanceText(distance: number) { // Update the proximity text according to the distance remaining - if (betweenMeters > 1000) { - this._targetProximityText = `${(betweenMeters / 1000).toFixed()} KM`; - } else if (betweenMeters > 100) { - this._targetProximityText = `${betweenMeters.toFixed(0)} M`; - } else { - // If target is close less then 100 meters user has arrived to target location - if (this._watchId) { - // Stop watching location - navigator.geolocation.clearWatch(this._watchId); - // Update state to reveal the image - this._arrived = true; - } + if (distance > 1000) { + this._distanceText = `${(distance / 1000).toFixed()} KM`; + } else if (distance > 100) { + this._distanceText = `${distance.toFixed(0)} M`; } } - // This callback will be fired on geolocation error - errorCallback(err: GeolocationPositionError) { - let errorMessage; - // Show toast accoring to the error state - switch (err.code) { - case GeolocationPositionError.PERMISSION_DENIED: - errorMessage = - "Konum izni alınamadı, lütfen tarayıcınızın ve cihazınızın gizlilik ayarlarını kontrol edin."; - break; - case GeolocationPositionError.POSITION_UNAVAILABLE: - errorMessage = - "Konumunuz tespit edilemedi, lütfen biraz sonra tekrar deneyiniz."; - break; - case GeolocationPositionError.TIMEOUT: - errorMessage = - "Konum isteği zaman aşımına uğradı, lütfen sayfayı yenileyip tekrar deneyiniz."; - break; - default: - errorMessage = - "Konum izni alınamadı, lütfen tarayıcınızın ve cihazınızın gizlilik ayarlarını kontrol edin."; - break; + private _checkArrived(distance: number) { + // If target is close less then 100 meters user has arrived to target location + if (distance < 100) { + if (this._watchId) { + // Stop watching location + navigator.geolocation.clearWatch(this._watchId); + } + // Update state to reveal the image + this._arrived = true; } - - Toastify({ - text: errorMessage, - duration: 3000, - gravity: "top", - position: "center", - stopOnFocus: true, - style: { - background: "black", - borderRadius: "6px", - margin: "16px", - }, - onClick: function () {}, - }).showToast(); } // This template is shown when user hasn't give geolocation permission yet // When user click the button user is asked for geolocation permission - permissionButtonTemplate() { - return html` -
- -
-
-

- Ne kadar yaklaştığını görmek için aşağıdaki butona bas. -

- -
-
-
- `; - } + private _permissionButtonTemplate = () => + permissionButtonTemplate(this._startWatchingLocation); // This template is shown when user has given permission but has not arrived yet - lockedButtonTemplate() { - return html`
- -
-
-

- İçeriği görmek için konuma gitmelisin! Kalan mesafe: - ${this._targetProximityText} -

-
-
-
`; - } + private _lockedButtonTemplate = () => + lockedButtonTemplate(this._distanceText); // This template is shown when user has arrived to the target location // When user click the button counter at the bottom of the page is incremented // and image is revealed - unlockedButtonTemplate() { - return html`
- - -
-
-

İçeriği görmek için butona bas!

-
-
-
`; - } + private _unlockedButtonTemplate = () => + unlockedButtonTemplate(() => { + incrementUnlockCounter(this.imageId); + this._unlocked = true; + }); // Start watching user location, if user has not given permission yet // this will ask the user for permission and update the watch id private _startWatchingLocation() { + // User is already being watched no need to + // watch position + if (this._watchId) return; + const id = navigator.geolocation.watchPosition( this.successCallback.bind(this), - this.errorCallback.bind(this), + errorCallback, this.geolocationOptions ); this._watchId = id; } - // This counter is shown at the bottom of the page and incremented - // each time "show content" button is clicked - private async _incrementUnlockCounter(id: string | undefined) { - if (id) { - fetch(`http://localhost:3000/api/location/increment/${id}`, { - method: "PATCH", - }); - } - } - connectedCallback(): void { super.connectedCallback(); @@ -253,13 +138,11 @@ export class LockedContent extends LitElement { .then((permissionStatus) => { switch (permissionStatus.state) { case "granted": - this._hasGeolocationPermission = true; this._startWatchingLocation(); break; case "denied": case "prompt": default: - this._hasGeolocationPermission = false; break; } }); @@ -274,11 +157,11 @@ export class LockedContent extends LitElement { // 3 - Arrived to target position // 4 - User did not give geolocation permission if (this._arrived) { - buttonTemplate = this.unlockedButtonTemplate.bind(this); + buttonTemplate = this._unlockedButtonTemplate; } else if (this._hasGeolocationPermission) { - buttonTemplate = this.lockedButtonTemplate.bind(this); + buttonTemplate = this._lockedButtonTemplate; } else { - buttonTemplate = this.permissionButtonTemplate.bind(this); + buttonTemplate = this._permissionButtonTemplate; } return html` @@ -289,7 +172,7 @@ export class LockedContent extends LitElement { ${this._unlocked ? nothing : buttonTemplate()}