You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
212 lines
6.2 KiB
212 lines
6.2 KiB
import React, {useCallback, useEffect, useState} from 'react';
|
|
import * as ReactDOM from 'react-dom/client';
|
|
import {
|
|
SidebarButtonToggleStyle,
|
|
SidebarCloseButtonStyle, SidebarDataOptionsStyle,
|
|
SidebarHeaderStyle,
|
|
SidebarStyle,
|
|
} from "./data-options.style.js";
|
|
import {isClickOutside, isEscHit} from "../responsive-button/ResponsiveButton.jsx";
|
|
import {DesignPreview} from "./DesignPreview.jsx";
|
|
|
|
function DataOptions(props = {}) {
|
|
props.rootAttributes = props.rootAttributes ?? {};
|
|
|
|
const initialState = {
|
|
dataName: 'default',
|
|
data: {},
|
|
dataText: '{}',
|
|
dataOptions: [],
|
|
designPreview: [],
|
|
jsonError: false,
|
|
};
|
|
|
|
const [state, setState] = useState(initialState);
|
|
const [previewOption, setPreviewOption] = useState(getDesignPreviewImage(state.dataName, state.designPreview));
|
|
const updateState = (update) => setState(Object.assign({}, state, update));
|
|
|
|
const [sidebarOpen, setSidebarOpen] = useState(false);
|
|
|
|
useEffect(async () => {
|
|
await syncDataOptions(state.dataName);
|
|
}, []);
|
|
|
|
const handleCloseSidebarEscEvent = useCallback((e) => {
|
|
if (isEscHit(e)) {
|
|
(() => {
|
|
closeSidebar()
|
|
})();
|
|
}
|
|
}, []);
|
|
|
|
useEffect(() => {
|
|
document.addEventListener("keydown", handleCloseSidebarEscEvent);
|
|
|
|
// Unsubscribe from ESC listener.
|
|
return () => {
|
|
document.removeEventListener("keydown", handleCloseSidebarEscEvent);
|
|
}
|
|
}, [handleCloseSidebarEscEvent]);
|
|
|
|
useEffect(() => {
|
|
window.addEventListener('message', responsiveModeChangesHandler);
|
|
return () => window.removeEventListener('message', responsiveModeChangesHandler);
|
|
}, [state]);
|
|
|
|
useEffect(() => {
|
|
setPreviewOption(getDesignPreviewImage(state.dataName, state.designPreview));
|
|
}, [state.designPreview]);
|
|
|
|
const handleBlur = async (e) => await isClickOutside(e) ? closeSidebar() : null;
|
|
|
|
return <div style={{display: 'flex', gap: '1rem', alignItems: 'center'}}>
|
|
<SidebarButtonToggleStyle onClick={() => openSidebar()} title="Open a Sidebar with Data Options"/>
|
|
|
|
<DesignPreview previewOption={previewOption}/>
|
|
|
|
<SidebarStyle className={sidebarOpen ? 'active sidebar-active' : ''} tabIndex='0' onBlur={handleBlur}>
|
|
<SidebarHeaderStyle>
|
|
<SidebarCloseButtonStyle onClick={() => closeSidebar()}></SidebarCloseButtonStyle>
|
|
</SidebarHeaderStyle>
|
|
|
|
{state.dataOptions && !!state.dataOptions.length &&
|
|
<SidebarDataOptionsStyle>
|
|
<label htmlFor="data-options">Data Options</label>
|
|
|
|
<select name="data" id="data-options" onChange={(e) => changeDataOption(e)} value={state.dataName}>
|
|
{state.dataOptions.map((item) => {
|
|
const isSelected = state.dataName === item;
|
|
return <option value={item} selected={isSelected}>{item}</option>
|
|
})}
|
|
</select>
|
|
</SidebarDataOptionsStyle>
|
|
}
|
|
|
|
{state.data &&
|
|
<textarea value={state.dataText} onChange={dataOptionUpdate}/>
|
|
}
|
|
|
|
{state.jsonError &&
|
|
<p className={'alert'}>Invalid JSON entered.</p>
|
|
}
|
|
|
|
<button className='btn btn--secondary' onClick={(e) => copyToClipboard(e, state.data)}>Copy to Clipboard</button>
|
|
</SidebarStyle>
|
|
</div>;
|
|
|
|
//
|
|
// Functions
|
|
//
|
|
|
|
function dataOptionUpdate(e) {
|
|
let data;
|
|
|
|
try {
|
|
data = JSON.parse(e.target.value);
|
|
} catch (err) {
|
|
updateState({dataText: e.target.value, jsonError: true});
|
|
return;
|
|
}
|
|
|
|
updateState({dataText: e.target.value, data, jsonError: false});
|
|
updateIframe(data);
|
|
}
|
|
|
|
function openSidebar() {
|
|
setSidebarOpen(true);
|
|
setTimeout(() => document.querySelector('.sidebar-active').focus());
|
|
}
|
|
|
|
function closeSidebar() {
|
|
setSidebarOpen(false);
|
|
}
|
|
|
|
async function changeDataOption(e) {
|
|
const dataName = e.target.value;
|
|
await syncDataOptions(dataName);
|
|
}
|
|
|
|
async function fetchDataOptions(name = 'default') {
|
|
const queryParameters = new URLSearchParams({name});
|
|
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);
|
|
let size = window.responsiveState.breakpoint;
|
|
if (size === '100%') {
|
|
size = window.innerWidth;
|
|
}
|
|
|
|
return dataOptions.find((item) => {
|
|
return size >= item.widthDimension;
|
|
});
|
|
}
|
|
|
|
function responsiveModeChangesHandler(e) {
|
|
if (e.data !== 'responsiveUpdate') {
|
|
return;
|
|
}
|
|
|
|
setPreviewOption(getDesignPreviewImage(state.dataName, state.designPreview));
|
|
}
|
|
|
|
async function syncDataOptions(dataName) {
|
|
const dataOptions = await fetchDataOptions(dataName);
|
|
updateIframe(dataOptions.data);
|
|
|
|
const newState = Object.assign({jsonError: false},
|
|
dataOptions,
|
|
{dataName},
|
|
{dataText: JSON.stringify(dataOptions.data, null, 2)},
|
|
);
|
|
updateState(newState);
|
|
}
|
|
|
|
function updateIframe(data) {
|
|
const previewIframe = props.rootAttributes.previewFrame;
|
|
previewIframe.contentWindow.postMessage('dataUpdate:' + JSON.stringify(data || {}), '*');
|
|
}
|
|
}
|
|
|
|
function copyToClipboard(e, context) {
|
|
e.stopPropagation();
|
|
|
|
if (typeof context !== 'string') {
|
|
context = JSON.stringify(context, null, 2);
|
|
}
|
|
|
|
if (navigator.clipboard && window.isSecureContext) {
|
|
// This case will not work on non-https pages.
|
|
return navigator.clipboard.writeText(context);
|
|
} else {
|
|
let textArea = document.createElement("textarea");
|
|
textArea.value = context;
|
|
textArea.style.position = "fixed";
|
|
textArea.style.left = "-999999px";
|
|
textArea.style.top = "-999999px";
|
|
document.body.appendChild(textArea);
|
|
// textArea.focus();
|
|
textArea.select();
|
|
return new Promise((res, rej) => {
|
|
document.execCommand('copy') ? res() : rej();
|
|
textArea.remove();
|
|
e.target.focus()
|
|
});
|
|
}
|
|
}
|
|
|
|
export function setupDataOptions(rootAttributes) {
|
|
// INIT
|
|
const wrapper = document.createElement('div');
|
|
document.querySelector('.page_toolbar__left').prepend(wrapper)
|
|
|
|
const root = ReactDOM.createRoot(wrapper);
|
|
const html = (<DataOptions rootAttributes={rootAttributes}/>);
|
|
root.render(html);
|
|
}
|
|
|