elninotech/uppload

View on GitHub
src/helpers/search.ts

Summary

Maintainability
A
35 mins
Test Coverage
import { UpploadService } from "../service";
import {
  IHandlersParams,
  IServiceTemplateParams,
  IUpploadFile,
} from "./interfaces";
import { cachedFetch, imageUrlToBlob } from "./http";
import { safeListen } from "./elements";
import { colorSVG } from "./assets";
import { blobToUpploadFile } from "./files";

const generateFileName = (
  file: IUpploadFile,
  service: string,
  query?: string | null
) => {
  file.name = `${query || `${service}-import`}-${Math.random()
    .toString(36)
    .slice(2)}.jpg`;
  file.type = "image/jpeg";
  return file;
};

export class SearchBaseClass<ImageResult = any> extends UpploadService {
  apiKey: string;
  results: ImageResult[] = [];
  loading = false;
  poweredByUrl: string;
  popularEndpoint: string;
  searchEndpoint: (apiKey: string, query: string) => string;
  getButton: (image: ImageResult) => string;
  getPopularResults: (response: any) => ImageResult[];
  getSearchResults: (response: any) => ImageResult[];
  noRecolor = false;
  fetchSettings?: RequestInit;

  constructor({
    apiKey,
    name,
    icon,
    color,
    poweredByUrl,
    popularEndpoint,
    searchEndpoint,
    getButton,
    getPopularResults,
    getSearchResults,
    noRecolor,
    fetchSettings,
  }: {
    name: string;
    icon: string;
    color: string;
    apiKey: string;
    poweredByUrl: string;
    popularEndpoint: (apiKey: string) => string;
    searchEndpoint: (apiKey: string, query: string) => string;
    getButton: (image: ImageResult) => string;
    getPopularResults: (response: any) => ImageResult[];
    getSearchResults: (response: any) => ImageResult[];
    noRecolor?: boolean;
    fetchSettings?: (apiKey: string) => RequestInit;
  }) {
    super();
    this.name = name;
    this.icon = icon;
    this.color = color;
    this.apiKey = apiKey;
    this.noRecolor = !!noRecolor;
    this.poweredByUrl = poweredByUrl;
    this.popularEndpoint = popularEndpoint(this.apiKey);
    this.searchEndpoint = searchEndpoint;
    this.getButton = getButton;
    this.getPopularResults = getPopularResults;
    this.getSearchResults = getSearchResults;
    if (fetchSettings) this.fetchSettings = fetchSettings(this.apiKey);
    if (this.popularEndpoint)
      cachedFetch<any>(this.popularEndpoint, this.fetchSettings)
        .then(photos => {
          this.results = this.getPopularResults(photos);
        })
        .catch(() => {});
  }

  updateImages(params: IHandlersParams) {
    const imagesContainer =
      params.uppload.container.querySelector(".search-images");
    if (imagesContainer) {
      imagesContainer.innerHTML = `
        ${this.results.map(result => this.getButton(result)).join("\n")}
      `;
    }
  }

  update(params: IHandlersParams) {
    this.updateImages(params);
    if (params) this.handlers(params);
    const loader = params.uppload.container.querySelector(
      ".search-loader"
    ) as HTMLDivElement;
    const container = params.uppload.container.querySelector(
      ".search-container"
    ) as HTMLDivElement;
    if (container) container.style.display = this.loading ? "none" : "";
    if (loader) loader.style.display = this.loading ? "flex" : "none";
  }

  template = ({ translate }: IServiceTemplateParams): string => {
    return `
      <div class="search-container"><form class="search-search-form">
      <div class="service-icon">${colorSVG(this.icon, this)}</div>
      <label><span>${
        translate(`services.${this.name}.label`) ||
        translate("services.search.label")
      }</span>
        <input class="search-search-input" type="search" placeholder="${translate(
          `services.search.placeholder`
        )}" required></label>
        <button type="submit" style="background: ${this.color}">${translate(
      `services.search.button`,
      translate(`services.${this.name}.title`)
    )}</button>
      </form>
      <div class="search-images"></div>
      <p class="search-footer">${translate(
        "services.search.imagesPoweredBy",
        `<a href="${this.poweredByUrl}" target="_blank">${translate(
          `services.${this.name}.title`
        )}</a>`
      )}</p></div>
      <button class="need-help-link"><span>${translate(
        "needHelp"
      )}</span aria-hidden="true"><span>?</span></button>
      <div class="uppload-loader search-loader">
        <div></div>
        <p>${translate(
          "fetching",
          translate(`services.${this.name}.title`)
        )}</p>
      </div>
    `;
  };

  handlers = (params: IHandlersParams) => {
    const form = params.uppload.container.querySelector(
      `.search-search-form`
    ) as HTMLFormElement | null;
    if (form) {
      safeListen(form, "submit", event => {
        const input = params.uppload.container.querySelector(
          `.search-search-input`
        ) as HTMLInputElement | null;
        if (input) {
          const query = input.value;
          cachedFetch<any>(
            this.searchEndpoint(this.apiKey, query),
            this.fetchSettings
          )
            .then(json => {
              this.results = this.getSearchResults(json);
              this.update(params);
            })
            .catch(() => params.handle(new Error("errors.unable_to_search")));
        }
        event.preventDefault();
        return false;
      });
    }
    this.updateImages(params);
    const imageButtons = params.uppload.container.querySelectorAll(
      ".search-images button"
    );
    imageButtons.forEach(image => {
      safeListen(image, "click", () => {
        const url = image.getAttribute("data-full-url");
        this.loading = true;
        this.update(params);
        if (url)
          imageUrlToBlob(url)
            .then(blob =>
              params.next(
                generateFileName(
                  blobToUpploadFile(blob),
                  this.name,
                  image.getAttribute("aria-label")
                )
              )
            )
            .catch(() => params.handle("errors.response_not_ok"))
            .then(() => (this.loading = false));
      });
    });
    const helpButton =
      params.uppload.container.querySelector(".need-help-link");
    if (helpButton)
      safeListen(helpButton, "click", () =>
        params.showHelp(`/services/search/${this.name}`)
      );
  };
}