Side panel
Pexels photos
Integrate Pexels photos into a custom Side Panel section
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?
First, set up your own backend proxy to the Pexels API. See the official docs: https://www.pexels.com/api
Example 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 creating your API endpoint, create a new Side Panel section and show it in Polotno Editor. Example section using the proxy:
import React from 'react';
import { InputGroup } from '@blueprintjs/core';
import { ImagesGrid } from 'polotno/side-panel/images-grid';
import { getImageSize, getCrop } from 'polotno/utils/image';
import { SectionTab } from 'polotno/side-panel';
import { useInfiniteAPI } from 'polotno/utils/use-api';
import { t } from 'polotno/utils/l10n';
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 Pexels API key
const API = 'https://api.polotno.com/api';
const getPexelsAPI = ({ query, page }: { query: string; page: number }) =>
`${API}/get-pexels?query=${query}&per_page=20&page=${page}&KEY=${key}`;
export const PexelsPanel = ({ store }: { store: any }) => {
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: React.ChangeEvent<HTMLInputElement>) => {
setQuery(e.target.value);
}}
type="search"
style={{ marginBottom: '20px' }}
/>
<p style={{ textAlign: 'center' }}>
Photos by{' '}
<a href="https://www.pexels.com/" target="_blank" rel="noreferrer">
Pexels
</a>
</p>
<ImagesGrid
images={data?.map((item: any) => item.photos).flat().filter(Boolean)}
getPreview={(image: any) => image.src.medium}
onSelect={async (image: any, pos?: { x: number; y: number }, element?: any) => {
// get url of image
const src = image.src.large;
// if we dropped image into svg element, 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 dropped into another image, recalculate crop and apply new image
if (element && element.type === 'image' && element.contentEditable) {
const crop = getCrop(element, { width, height });
element.set({ src, ...crop });
return;
}
// otherwise 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: any) => (
<span>
Photo by{' '}
<a href={image.photographer_url} target="_blank" rel="noreferrer">
{image.photographer}
</a>{' '}
on{' '}
<a
href="https://pexels.com/?utm_source=polotno&utm_medium=referral"
target="_blank"
rel="noreferrer"
>
Pexels
</a>
</span>
)}
/>
</div>
);
};
// define the new custom section
export const PexelsSection = {
name: 'pexels',
Tab: (props: any) => (
<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, pass it into <SidePanel />:
import { SidePanel, 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" />