konulu-konum/src/components/locked-content.ts

152 lines
4.7 KiB
TypeScript

// Lit imports
import { LitElement, html, nothing, unsafeCSS, type CSSResultGroup } from "lit";
import { customElement, property, state } from "lit/decorators.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,
permissionDeniedButtonTemplate,
unlockedButtonTemplate,
} from "./LockedContent/templates";
// Geolocation utils
import { calculateDistance } from "./LockedContent/geolocation";
import { incrementUnlockCounter } from "./LockedContent/serverUtils";
// LockedContent is a custom element watching user location and blurring
// given image until user has arrived a certain position
@customElement("locked-content")
export class LockedContent extends LitElement {
// Constants
geolocationOptions = {
enableHighAccuracy: true,
timeout: 15000,
maximumAge: 0,
};
// Tailwind and custom styles
static styles: CSSResultGroup | undefined = [
unsafeCSS(globalStyles),
unsafeCSS(lockedContentStyles),
];
// Components properties/attributes, no accessor attribute disables detecting
// changes as these are readonly attributes there is no need to attach setters
@property({ noAccessor: true }) readonly imageId?: string;
@property({ noAccessor: true }) readonly imageURL?: string;
@property({ type: Object, noAccessor: true })
readonly targetPosition?: LatLngTuple;
@property({ type: Object })
currentPosition?: LatLngTuple;
// Reactive states, template is rendered according to this states
@state()
protected _unlocked = false;
@state()
protected _arrived = false;
@state()
protected _distanceText?: string;
private _updateDistanceText(distance: number) {
// Update the proximity text according to the distance remaining
if (distance > 1000) {
this._distanceText = `${(distance / 1000).toFixed()} KM`;
} else if (distance > 100) {
this._distanceText = `${distance.toFixed(0)} M`;
}
}
private _checkArrived(distance: number) {
// If target is close less then 100 meters user has arrived to target location
if (distance < 100) {
// Update state to reveal the image
this._arrived = true;
}
}
private _dispatchAskPermissionEvent() {
const event = new Event("askpermission", { bubbles: true, composed: true });
this.dispatchEvent(event);
}
// This template is shown when user hasn't give geolocation permission yet
// When user click the button user is asked for geolocation permission
private _permissionButtonTemplate = () =>
permissionButtonTemplate(this._dispatchAskPermissionEvent);
// This template is shown when user has given permission but has not arrived yet
private _lockedButtonTemplate = () => {
// Target position must be set
if (!this.targetPosition || !this.currentPosition) return;
// Calculate the distance between target and current position in meters
const distance = calculateDistance(
this.currentPosition,
this.targetPosition
);
// Update the text based on the distance
this._updateDistanceText(distance);
this._checkArrived(distance);
return 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
private _unlockedButtonTemplate = () =>
unlockedButtonTemplate(() => {
incrementUnlockCounter(this.imageId);
this._unlocked = true;
});
connectedCallback(): void {
super.connectedCallback();
}
render() {
let buttonTemplate;
// Determine which template to show, there are 3 states:
// 1 - No current location provided
// 2 - Current location given but has no arrived to target position yet
// 3 - Arrived to target position
// 4 - User did not give geolocation permission
if (this._arrived) {
buttonTemplate = this._unlockedButtonTemplate;
} else if (this.currentPosition) {
buttonTemplate = this._lockedButtonTemplate;
} else if (!this.currentPosition) {
buttonTemplate = this._permissionButtonTemplate;
} else {
buttonTemplate = permissionDeniedButtonTemplate;
}
return html`
<div
class="w-full h-[475px] overflow-hidden border border-zinc-200 shadow-sm p-4 rounded"
>
<div class="flex flex-col justify-center items-center image-wrapper">
<img
id="content"
src="${this.imageURL}"
class="h-[450px] ${this._unlocked ? "" : "blur-2xl"}"
/>
${this._unlocked ? nothing : buttonTemplate()}
</div>
</div>
`;
}
}