HabitatMap/AirCasting

View on GitHub
app/javascript/react/components/Map/Markers/customMarkerOverlay.ts

Summary

Maintainability
B
4 hrs
Test Coverage
export class CustomMarkerOverlay extends google.maps.OverlayView {
  private div: HTMLElement | null = null;
  private position: google.maps.LatLng;
  private color: string;
  private isSelected: boolean;
  private shouldPulse: boolean;

  constructor(
    position: google.maps.LatLng,
    color: string,
    isSelected: boolean,
    shouldPulse: boolean
  ) {
    super();
    this.position = position;
    this.color = color;
    this.isSelected = isSelected;
    this.shouldPulse = shouldPulse;
  }

  public getIsSelected(): boolean {
    return this.isSelected;
  }

  public getShouldPulse(): boolean {
    return this.shouldPulse;
  }

  public getColor(): string {
    return this.color;
  }

  public setColor(color: string): void {
    this.color = color;
    this.update();
  }

  onAdd() {
    this.div = document.createElement("div");
    this.div.style.position = "absolute";
    this.div.style.transform = "translate(-50%, -50%)";
    this.div.style.pointerEvents = "none";
    this.div.style.cursor = "none";

    this.applyStyles();

    const panes = this.getPanes();
    panes && panes.overlayLayer.appendChild(this.div);
  }

  setIsSelected(isSelected: boolean) {
    this.isSelected = isSelected;
    this.update();
  }

  setShouldPulse(shouldPulse: boolean) {
    this.shouldPulse = shouldPulse;
    this.update();
  }

  update() {
    if (this.div) {
      this.applyStyles();
    }
  }

  draw() {
    if (!this.div) return;
    const overlayProjection = this.getProjection();
    const pos = overlayProjection.fromLatLngToDivPixel(this.position);
    if (pos) {
      this.div.style.left = `${pos.x}px`;
      this.div.style.top = `${pos.y}px`;
    }
  }

  onRemove() {
    if (this.div && this.div.parentNode) {
      this.div.parentNode.removeChild(this.div);
      this.div = null;
    }
  }

  private applyStyles() {
    if (!this.div) return;

    const size = this.isSelected ? 44 : 36;
    const blurValue = this.isSelected ? 0 : 3;
    const opacityValue = this.isSelected ? 0.4 : 0.8;

    this.div.style.width = `${size}px`;
    this.div.style.height = `${size}px`;
    this.div.style.borderRadius = "50%";
    this.div.style.backgroundColor = this.color;
    this.div.style.opacity = `${opacityValue}`;
    this.div.style.filter = `blur(${blurValue}px)`;
    this.div.style.transition = "transform 0.3s ease-out";

    if (this.shouldPulse) {
      this.div.style.animation = `pulse-animation 2s infinite`;
    } else {
      this.div.style.animation = "";
    }
  }
}

const styleSheetId = "custom-marker-overlay-styles";
if (!document.getElementById(styleSheetId)) {
  const styleSheet = document.createElement("style");
  styleSheet.type = "text/css";
  styleSheet.id = styleSheetId;
  styleSheet.innerText = `
    @keyframes pulse-animation {
      0% {
        transform: translate(-50%, -50%) scale(1);
        opacity: 1;
      }
      50% {
        transform: translate(-50%, -50%) scale(1.6);
        opacity: 0.8;
      }
      100% {
        transform: translate(-50%, -50%) scale(1);
        opacity: 1;
      }
    }
  `;
  document.head.appendChild(styleSheet);
}