Added PreviewDesign functionality - for perfect pixel test.

This commit is contained in:
2022-10-15 13:30:49 +03:00
parent dfcd9681bc
commit 44dc99c641
10 changed files with 422 additions and 14 deletions
@@ -12,7 +12,7 @@ import {DesignPreview} from "./DesignPreview.jsx";
function DataOptions(props = {}) {
props.rootAttributes = props.rootAttributes ?? {};
const initialState = {dataName: 'default', data: {}, dataOptions: []};
const initialState = {dataName: 'default', data: {}, dataOptions: [], designPreview: []};
const [state, setState] = useState(initialState);
const updateState = (update) => setState(Object.assign({}, state, update));
@@ -41,10 +41,12 @@ function DataOptions(props = {}) {
}, [handleCloseSidebarEscEvent]);
const handleBlur = async (e) => await isClickOutside(e) ? closeSidebar() : null;
const previewOption = getDesignPreviewImage(state.dataName, state.designPreview);
return <div style={{display: 'flex', gap: '1rem', alignItems: 'center'}}>
<SidebarButtonToggleStyle onClick={() => openSidebar()} title="Open a Sidebar with Data Options"/>
<DesignPreview/>
<DesignPreview previewOption={previewOption}/>
<SidebarStyle className={sidebarOpen ? 'active sidebar-active' : ''} tabIndex='0' onBlur={handleBlur}>
<SidebarHeaderStyle>
@@ -92,6 +94,18 @@ function DataOptions(props = {}) {
const response = await fetch(`/data?${queryParameters}`);
return await response.json();
}
function getDesignPreviewImage(currentDataName, designPreviewOptions = []) {
const dataOptions = designPreviewOptions.filter((item) => {
return item.dataSource === currentDataName;
});
dataOptions.sort((a, b) => b.widthDimension - a.widthDimension);
return dataOptions.find((item) => {
return item.widthDimension === 414;
});
}
}
export function setupDataOptions(rootAttributes) {
@@ -1,5 +1,120 @@
import React, {useCallback, useEffect, useState} from 'react';
import React, {createRef, useCallback, useEffect, useState} from 'react';
import {PreviewButtonStyle, PreviewStyle} from "./design-preview.style.js";
export function DesignPreview(props = {}) {
return <span style={{color: '#999'}}>Preview</span>
let startDragPosition = {x: 0, y: 0};
export function DesignPreview({previewOption = {widthDimension: 0}}) {
const reference = createRef();
const [active, setActive] = useState(false);
const [position, setPosition] = useState({x: getInitialXPosition(previewOption), y: 0});
const [opacity, setOpacity] = useState(100);
const keyDownHandler = useCallback(
function (e) {
const step = e.shiftKey ? 10 : 1;
if (e.key === 'ArrowUp') {
updatePosition({y: step * -1}, true)
} else if (e.key === 'ArrowDown') {
updatePosition({y: step}, true)
} else if (e.key === 'ArrowLeft') {
updatePosition({x: step * -1}, true)
} else if (e.key === 'ArrowRight') {
updatePosition({x: step}, true)
}
}, [position]);
useEffect(() => {
document.addEventListener('keydown', keyDownHandler);
return () => {
document.removeEventListener('keydown', keyDownHandler)
}
}, [keyDownHandler]);
if (!previewOption) {
return;
}
const disabled = !previewOption.url;
const classNames = [];
if (active) {
classNames.push('active');
}
if (disabled) {
classNames.push('disabled');
}
const handleOpacityChange = (e) => {
setOpacity(e.target.value);
}
return <>
<PreviewButtonStyle onClick={() => toggle()} className={classNames.join(' ')} disabled={disabled}/>
{active &&
<>
<input type="range" value={opacity} min="0" max="100" onChange={handleOpacityChange}/>
<PreviewStyle onMouseUp={e => stop(e)} style={{opacity: opacity / 100}}>
<img src={previewOption.url} alt="" ref={reference}
onMouseDown={e => start(e)}
draggable={false}
style={{top: position.y + 'px', left: position.x + 'px'}}
/>
</PreviewStyle>
</>
}
</>
function start(e) {
startDragPosition.x = e.clientX;
startDragPosition.y = e.clientY;
document.body.addEventListener('mousemove', handler);
}
function stop(e) {
const target = reference.current;
updatePosition({x: target.offsetLeft, y: target.offsetTop});
document.body.removeEventListener('mousemove', handler);
}
function handler(e) {
const target = reference.current;
if (target) {
const updatedPosition = {
x: target.offsetLeft - (startDragPosition.x - e.clientX),
y: target.offsetTop - (startDragPosition.y - e.clientY),
};
target.style.left = updatedPosition.x + 'px';
target.style.top = updatedPosition.y + 'px';
startDragPosition.x = e.clientX;
startDragPosition.y = e.clientY;
}
}
function toggle() {
setActive(!active);
}
function updatePosition({x, y}, relative = false) {
x = typeof x === 'undefined' ? position.x : relative ? position.x + x : x;
y = typeof y === 'undefined' ? position.y : relative ? position.y + y : y;
setPosition({x, y})
}
function getInitialXPosition(previewOption) {
if (!previewOption) {
return 0;
}
return window.innerWidth / 2 - previewOption.widthDimension / 2;
}
}
@@ -0,0 +1,60 @@
import styled from "styled-components";
export const PreviewButtonStyle = styled.button`
--size: 1.5rem;
cursor: pointer;
border: 0;
background-image: url("/scripts/dist/toolbar/images/icon-preview.svg");
background-repeat: no-repeat;
background-size: calc(var(--size) - 0.15rem);
background-position: center center;
background-color: initial;
font-size: 1px;
color: rgba(0, 0, 0, 0);
line-height: 1;
display: block;
width: var(--size);
height: var(--size);
border-radius: 0.25rem;
outline: none;
&.active {
background-image: url("/scripts/dist/toolbar/images/icon-preview-disabled.svg");
}
&.disabled {
opacity: 0.25;
cursor: default;
}
`;
export const PreviewStyle = styled.div`
position: fixed;
top: 3rem;
bottom: 0;
left: 0;
right: 0;
background-color: rgba(0, 0, 0, 0.1);
display: flex;
justify-content: center;
overflow: hidden;
align-items: baseline;
img {
width: auto;
height: auto;
display: block;
cursor: move;
position: absolute;
}
`;
export const PreviewImageStyle = styled.div`
height: 100%;
width: 100%;
background-repeat: no-repeat;
background-position: top;
cursor: move;
`;