'use client'; import React from 'react'; import { isAlive } from 'mobx-state-tree'; import { InputGroup } from '@blueprintjs/core'; import { Search } from '@blueprintjs/icons'; import { ImagesGrid } from './images-grid'; import { svgToURL } from '../utils/svg'; import { figureToSvg, TYPES } from '../utils/figure-to-svg'; import styled from '../utils/styled'; import { t as s } from '../utils/l10n'; import { useInfiniteAPI } from '../utils/use-api'; import { URLS } from '../utils/api'; import { StoreType } from '../model/store'; const downloadNounproject = async (id: string) => { const res = await fetch(URLS.nounProjectDownload(id)); const text = await res.text(); return await svgToURL(text); }; const DarkInvertContainer = styled('div')` height: 100%; overflow: hidden; .bp5-dark & img { filter: invert(1); } `; export const NounprojectPanel = ({ store, query }: { store: StoreType; query: string }) => { const { data, isLoading, loadMore, setQuery, hasMore } = useInfiniteAPI({ defaultQuery: query, getAPI: ({ page, query: q }: any) => URLS.nounProjectList({ query: q, page, limit: 50 }), getSize: (d: any) => d.pagesNumber, }); React.useEffect(() => { setQuery(query); }, [query]); return React.createElement( DarkInvertContainer, null, React.createElement(ImagesGrid, { shadowEnabled: false, images: data?.map((d: any) => d.icons).flat(), getPreview: (item: any) => item.preview_url_84, isLoading, onSelect: async (item: any, pos: any, el: any) => { if (el && el.type === 'image' && (el as any).contentEditable) { const src = await downloadNounproject(item.id); el.set({ clipSrc: src }); return; } if (el && el.type === 'video' && (el as any).contentEditable) { const src = await downloadNounproject(item.id); el.set({ clipSrc: src }); return; } store.history.transaction(async () => { const x = ((pos?.x) || store.width / 2) - 100; const y = ((pos?.y) || store.height / 2) - 100; const node = store.activePage?.addElement({ type: 'svg', width: 200, height: 200, x, y }); const src = await downloadNounproject(item.id); if (isAlive(node)) await node.set({ src }); }); }, rowsNumber: 4, loadMore: hasMore && loadMore, }) ); }; const lineStyles = [ { preview: svgToURL(``), data: {} }, { preview: svgToURL(``), data: { dash: [4, 2] } }, { preview: svgToURL(``), data: { dash: [1, 1] } }, { preview: svgToURL(``), data: { endHead: 'arrow' } }, { preview: svgToURL(``), data: { startHead: 'circle', endHead: 'triangle' } }, { preview: svgToURL(``), data: { startHead: 'square', endHead: 'bar', dash: [2, 1] } }, ]; let defaultShapeColor = 'rgba(191, 191, 191, 100)'; export const setDefaultColor = (color: string) => { defaultShapeColor = color; }; const LinesGrid = ({ store }: { store: StoreType }) => React.createElement(ImagesGrid, { shadowEnabled: false, rowsNumber: 3, images: lineStyles, getPreview: (item: any) => item.preview, itemHeight: 50, isLoading: false, onSelect: async (item: any, pos: any) => { const w = store.activePage.computedWidth / 3; store.activePage.addElement(Object.assign({ type: 'line', x: pos ? pos.x : store.activePage.computedWidth / 2 - w / 2, y: pos ? pos.y : store.activePage.computedHeight / 2, color: defaultShapeColor, width: w, }, item.data)); }, }); const shapeTypes = Object.keys(TYPES); const baseShapes = [{ width: 300, height: 300, fill: 'rgba(191, 191, 191, 100)', stroke: '#0c0c0c', strokeWidth: 0, url: '' }]; const shapes: any[] = []; shapeTypes.forEach((subType) => { baseShapes.forEach((base) => { shapes.push(Object.assign({ subType }, base)); }); }); shapes.forEach((shape) => { shape.url = svgToURL(figureToSvg(shape)); }); const ShapesGridContainer = styled('div')` height: 220px; `; const ShapesGrid = ({ store }: { store: StoreType }) => { const rows = Math.ceil(shapes.length / 4) || 1; return React.createElement( ShapesGridContainer, { style: { height: 110 * rows + 'px' } }, React.createElement(ImagesGrid, { shadowEnabled: false, rowsNumber: 4, images: shapes, getPreview: (item: any) => item.url, isLoading: false, itemHeight: 100, onSelect: async (item: any, pos: any, el: any) => { if (el && el.type === 'image' && (el as any).contentEditable) { el.set({ clipSrc: item.url }); return; } if (el && el.type === 'video' && (el as any).contentEditable) { el.set({ clipSrc: item.url }); return; } const ratio = (store.activePage.computedWidth + store.activePage.computedHeight) / 2160; const w = item.width * ratio; const h = item.height * ratio; const x = ((pos?.x) || store.activePage.computedWidth / 2) - w / 2; const y = ((pos?.y) || store.activePage.computedHeight / 2) - h / 2; store.activePage?.addElement(Object.assign(Object.assign({ type: 'figure' }, item), { x, y, width: w, height: h, fill: defaultShapeColor })); }, }) ); }; const LinesContainer = styled('div')` height: 220px; `; export const Shapes = ({ store }: { store: StoreType }) => React.createElement( 'div', { style: { display: 'flex', flexDirection: 'column', height: '100%', overflow: 'auto' } }, React.createElement('p', null, s('sidePanel.lines')), React.createElement(LinesContainer, null, React.createElement(LinesGrid, { store })), React.createElement('p', null, s('sidePanel.shapes')), React.createElement(ShapesGrid, { store }) ); export const ElementsPanel = ({ store }: { store: StoreType }) => { const timerRef = React.useRef(); const [inputValue, setInputValue] = React.useState(''); const [query, setQuery] = React.useState(inputValue); React.useEffect(() => { timerRef.current = setTimeout(() => { setQuery(inputValue); }, 500); return () => { clearTimeout(timerRef.current); }; }, [inputValue]); const hasQuery = !!query; return React.createElement( 'div', { style: { height: '100%', display: 'flex', flexDirection: 'column' } }, React.createElement(InputGroup, { leftIcon: React.createElement(Search, null), placeholder: s('sidePanel.searchPlaceholder'), onChange: (e: any) => { setInputValue(e.target.value); }, style: { marginBottom: '20px' }, type: 'search', }), hasQuery && React.createElement(NounprojectPanel, { query, store }), !hasQuery && React.createElement(Shapes, { store }) ); };