Pexels Photos

You can use customization of Polotno's Side Panel to add your own panels with your own assets.

How to integrate photos from Pexels.com API?

As the first step, you need to set up your own backend proxy into pexels.com API. To get started, please use pexels.com documentation.

Here how it may look using Node.js:

module.exports = async (req, res) => {
  var i = req.url.indexOf('?');
  var query = req.url.substr(i + 1);

  // if no query provided, let's return a list of curated photos
  const searchPrefix = req.query.query ? 'search/' : '/curated';

  const r = await fetch(
    `https://api.pexels.com/v1/${searchPrefix}?query=${encodeURIComponent(
      req.query.query
    )}&page=${req.query.page}&per_page=${req.query.per_page}`,
    {
      headers: {
        Authorization:
          'YOUR_API_KEY_FROM_PEXELS',
      },
    }
  );

  if (r.status !== 200) {
    return res.status(r.status).send(await r.text());
  }

  let result = await r.json();

  res.status(r.status).json(result);
};

After you created your own API end-point, you can create a new side panel add show it in Polotno Editor. Here is example of a section that will use created server API:

import React from "react";
import { InputGroup } from "@blueprintjs/core";
import { ImagesGrid } from "polotno/side-panel/images-grid";
import { getImageSize } from "polotno/utils/image";
import { SectionTab } from "polotno/side-panel";
import { useInfiniteAPI } from "polotno/utils/use-api";
import { t } from "polotno/utils/l10n";
import { getCrop } from "polotno/utils/image";
import SiPexels from "@meronex/icons/si/SiPexels";

// this is a demo key just for that project
// (!) please don't use it in your projects
// to create your own API key please go here: https://polotno.com/login
const key = "nFA5H9elEytDyPyvKL7T";

// use Polotno API proxy into Pexels
// WARNING: don't use on production! Use your own proxy and Pexles API key
const API = "https://api.polotno.com/api";
const getPexelsAPI = ({ query, page }) =>
  `${API}/get-pexels?query=${query}&per_page=20&page=${page}&KEY=${key}`;

export const PexelsPanel = ({ store }) => {
  const { setQuery, loadMore, isReachingEnd, data, isLoading, error } =
    useInfiniteAPI({
      defaultQuery: "",
      getAPI: ({ page, query }) => getPexelsAPI({ page, query }),
      getSize: (lastResponse) =>
        lastResponse.total_results / lastResponse.per_page,
    });

  return (
    <div style={{ height: "100%", display: "flex", flexDirection: "column" }}>
      <InputGroup
        leftIcon="search"
        placeholder={t("sidePanel.searchPlaceholder")}
        onChange={(e) => {
          setQuery(e.target.value);
        }}
        type="search"
        style={{
          marginBottom: "20px",
        }}
      />
      <p style={{ textAlign: "center" }}>
        Photos by{" "}
        <a href="https://www.pexels.com/" target="_blank">
          Pexels
        </a>
      </p>
      <ImagesGrid
        images={data
          ?.map((item) => item.photos)
          .flat()
          .filter(Boolean)}
        getPreview={(image) => image.src.medium}
        onSelect={async (image, pos, element) => {
          // get url of image
          const src = image.src.large;

          // if we dropped image into svg element, le't apply mask for it
          if (element && element.type === "svg" && element.contentEditable) {
            element.set({ maskSrc: src });
            return;
          }

          // get image size
          const { width, height } = await getImageSize(src);

          // if we dropped into another image, let's just recalucate crop and apply new image
          if (element && element.type === "image" && element.contentEditable) {
            const crop = getCrop(element, {
              width,
              height,
            });
            element.set({ src, ...crop });
            return;
          }

          // otherwise let's create new image
          const x = (pos?.x || store.width / 2) - width / 2;
          const y = (pos?.y || store.height / 2) - height / 2;
          store.activePage?.addElement({
            type: "image",
            src,
            width,
            height,
            x,
            y,
          });
        }}
        isLoading={isLoading}
        error={error}
        loadMore={!isReachingEnd && loadMore}
        getCredit={(image) => (
          <span>
            Photo by{" "}
            <a href={image.photographer_url} target="_blank">
              {image.photographer}
            </a>{" "}
            on{" "}
            <a
              href="https://pexels.com/?utm_source=polotno&utm_medium=referral"
              target="_blank"
            >
              Pexels
            </a>
          </span>
        )}
      />
    </div>
  );
};

// define the new custom section
export const PexelsSection = {
  name: "pexels",
  Tab: (props) => (
    <SectionTab name="Pexels" {...props}>
      <SiPexels />
    </SectionTab>
  ),
  // we need observer to update component automatically on any store changes
  Panel: PexelsPanel,
};

When you created a new section in the side panel, you can pass into <SidePanel /> component:

import { DEFAULT_SECTIONS } from "polotno/side-panel";
import { PexelsSection } from "./pexels-section";

const sections = [PexelsSection, ...DEFAULT_SECTIONS];

// in render:
<SidePanel store={store} sections={sections} defaultSection="pexels" />

News, updates and promos – be the first to get 'em

News, updates and promos – be the first to get 'em