mirror of
https://github.com/ToolJet/ToolJet
synced 2026-05-23 08:58:26 +00:00
Feature: Mobile layout 🔥🔥🔥
This commit is contained in:
parent
6196051ffb
commit
03a71a6352
6 changed files with 163 additions and 42 deletions
|
|
@ -1,6 +1,6 @@
|
|||
import React, { useEffect, useState, memo } from 'react';
|
||||
|
||||
export const BoxDragPreview = memo(function BoxDragPreview({ item }) {
|
||||
export const BoxDragPreview = memo(function BoxDragPreview({ item, currentLayout }) {
|
||||
const [tickTock, setTickTock] = useState(false);
|
||||
|
||||
useEffect(
|
||||
|
|
@ -11,7 +11,8 @@ export const BoxDragPreview = memo(function BoxDragPreview({ item }) {
|
|||
[tickTock]
|
||||
);
|
||||
|
||||
let { width, height } = item;
|
||||
const layouts = item.layouts;
|
||||
let { width, height } = layouts ? item.layouts[currentLayout] : {};
|
||||
|
||||
if (item.id === undefined) {
|
||||
width = item.component.defaultSize.width;
|
||||
|
|
|
|||
|
|
@ -30,6 +30,7 @@ export const Container = ({
|
|||
appLoading,
|
||||
configHandleClicked,
|
||||
zoomLevel,
|
||||
currentLayout,
|
||||
removeComponent
|
||||
}) => {
|
||||
const components = appDefinition.components || [];
|
||||
|
|
@ -43,11 +44,11 @@ export const Container = ({
|
|||
}, [components]);
|
||||
|
||||
const moveBox = useCallback(
|
||||
(id, left, top) => {
|
||||
(id, layouts) => {
|
||||
setBoxes(
|
||||
update(boxes, {
|
||||
[id]: {
|
||||
$merge: { left, top }
|
||||
$merge: { layouts }
|
||||
}
|
||||
})
|
||||
);
|
||||
|
|
@ -86,6 +87,9 @@ export const Container = ({
|
|||
return;
|
||||
}
|
||||
|
||||
let layouts = item['layouts'];
|
||||
const currentLayoutOptions = layouts ? layouts[currentLayout] : {};
|
||||
|
||||
let componentData = {};
|
||||
let componentMeta = {};
|
||||
let id = item.id;
|
||||
|
|
@ -107,8 +111,30 @@ export const Container = ({
|
|||
}
|
||||
|
||||
componentData = item.component;
|
||||
left = Math.round(item.left + deltaX);
|
||||
top = Math.round(item.top + deltaY);
|
||||
left = Math.round(currentLayoutOptions.left + deltaX);
|
||||
top = Math.round(currentLayoutOptions.top + deltaY);
|
||||
|
||||
if (snapToGrid) {
|
||||
[left, top] = doSnapToGrid(left, top);
|
||||
}
|
||||
|
||||
let newBoxes = {
|
||||
...boxes,
|
||||
[id]: {
|
||||
...boxes[id],
|
||||
layouts: {
|
||||
...boxes[id]['layouts'],
|
||||
[currentLayout]: {
|
||||
...boxes[id]['layouts'][currentLayout],
|
||||
top: top,
|
||||
left: left,
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
setBoxes(newBoxes);
|
||||
|
||||
} else {
|
||||
// This is a new component
|
||||
componentMeta = componentTypes.find((component) => component.component === item.component.component);
|
||||
|
|
@ -124,22 +150,32 @@ export const Container = ({
|
|||
top = Math.round(currentOffset.y + (currentOffset.y * (1 - zoomLevel)) - offsetFromTopOfWindow);
|
||||
|
||||
id = uuidv4();
|
||||
}
|
||||
|
||||
if (snapToGrid) {
|
||||
[left, top] = doSnapToGrid(left, top);
|
||||
}
|
||||
|
||||
setBoxes({
|
||||
...boxes,
|
||||
[id]: {
|
||||
top: top,
|
||||
left: left,
|
||||
width: item.width > 0 ? item.width : componentMeta.defaultSize.width,
|
||||
height: item.height > 0 ? item.height : componentMeta.defaultSize.height,
|
||||
component: componentData
|
||||
if (snapToGrid) {
|
||||
[left, top] = doSnapToGrid(left, top);
|
||||
}
|
||||
});
|
||||
|
||||
setBoxes({
|
||||
...boxes,
|
||||
[id]: {
|
||||
component: componentData,
|
||||
layouts: {
|
||||
[currentLayout]: {
|
||||
top: top,
|
||||
left: left,
|
||||
width: componentMeta.defaultSize.width,
|
||||
height: componentMeta.defaultSize.height,
|
||||
},
|
||||
mobile: {
|
||||
top: 100,
|
||||
left: 0,
|
||||
width: 445,
|
||||
height: 500
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
|
@ -153,18 +189,29 @@ export const Container = ({
|
|||
|
||||
let { x, y } = position;
|
||||
|
||||
let { left, top, width, height } = boxes[id];
|
||||
const defaultData = {
|
||||
top: 100,
|
||||
left: 0,
|
||||
width: 445,
|
||||
height: 500
|
||||
};
|
||||
|
||||
let { left, top, width, height } = boxes[id]['layouts'][currentLayout] || defaultData;
|
||||
|
||||
top = y;
|
||||
left = x;
|
||||
|
||||
width = width + deltaWidth;
|
||||
height = height + deltaHeight
|
||||
height = height + deltaHeight;
|
||||
|
||||
setBoxes(
|
||||
update(boxes, {
|
||||
[id]: {
|
||||
$merge: { width, height, top, left }
|
||||
$merge: {
|
||||
layouts: {
|
||||
[currentLayout]: { width, height, top, left }
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
);
|
||||
|
|
@ -214,6 +261,7 @@ export const Container = ({
|
|||
zoomLevel={zoomLevel}
|
||||
configHandleClicked={configHandleClicked}
|
||||
removeComponent={removeComponent}
|
||||
currentLayout={currentLayout}
|
||||
containerProps={{
|
||||
mode,
|
||||
snapToGrid,
|
||||
|
|
@ -227,7 +275,8 @@ export const Container = ({
|
|||
appLoading,
|
||||
zoomLevel,
|
||||
configHandleClicked,
|
||||
removeComponent
|
||||
removeComponent,
|
||||
currentLayout
|
||||
}}
|
||||
/>
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ const layerStyles = {
|
|||
height: '100%'
|
||||
};
|
||||
|
||||
function getItemStyles(delta, item, initialOffset, currentOffset) {
|
||||
function getItemStyles(delta, item, initialOffset, currentOffset, currentLayout) {
|
||||
if (!initialOffset || !currentOffset) {
|
||||
return {
|
||||
display: 'none'
|
||||
|
|
@ -29,8 +29,10 @@ function getItemStyles(delta, item, initialOffset, currentOffset) {
|
|||
const realCanvasDelta = realCanvasBoundingRect.x - canvasContainerBoundingRect.x;
|
||||
|
||||
if (id) { // Dragging within the canvas
|
||||
x = Math.round(item.left + delta.x);
|
||||
y = Math.round(item.top + delta.y);
|
||||
|
||||
x = Math.round(item.layouts[currentLayout].left + delta.x);
|
||||
y = Math.round(item.layouts[currentLayout].top + delta.y);
|
||||
|
||||
} else { // New component being dragged from components sidebar
|
||||
const offsetFromTopOfWindow = realCanvasBoundingRect.top;
|
||||
const offsetFromLeftOfWindow = realCanvasBoundingRect.left;
|
||||
|
|
@ -50,7 +52,7 @@ function getItemStyles(delta, item, initialOffset, currentOffset) {
|
|||
WebkitTransform: transform
|
||||
};
|
||||
}
|
||||
export const CustomDragLayer = () => {
|
||||
export const CustomDragLayer = ({ currentLayout }) => {
|
||||
const {
|
||||
itemType, isDragging, item, initialOffset, currentOffset, delta
|
||||
} = useDragLayer((monitor) => ({
|
||||
|
|
@ -64,7 +66,7 @@ export const CustomDragLayer = () => {
|
|||
function renderItem() {
|
||||
switch (itemType) {
|
||||
case ItemTypes.BOX:
|
||||
return <BoxDragPreview item={item} />;
|
||||
return <BoxDragPreview item={item} currentLayout={currentLayout}/>;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
|
|
@ -76,7 +78,7 @@ export const CustomDragLayer = () => {
|
|||
|
||||
return (
|
||||
<div style={layerStyles}>
|
||||
<div style={getItemStyles(delta, item, initialOffset, currentOffset)}>
|
||||
<div style={getItemStyles(delta, item, initialOffset, currentOffset, currentLayout)}>
|
||||
{renderItem()}
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -66,7 +66,9 @@ export const DraggableBox = function DraggableBox({
|
|||
zoomLevel,
|
||||
containerProps,
|
||||
configHandleClicked,
|
||||
removeComponent
|
||||
removeComponent,
|
||||
currentLayout,
|
||||
layouts
|
||||
}) {
|
||||
const [isResizing, setResizing] = useState(false);
|
||||
const [canDrag, setCanDrag] = useState(true);
|
||||
|
|
@ -76,13 +78,13 @@ export const DraggableBox = function DraggableBox({
|
|||
() => ({
|
||||
type: ItemTypes.BOX,
|
||||
item: {
|
||||
id, left, top, width, height, title, component, zoomLevel, parent
|
||||
id, title, component, zoomLevel, parent, layouts
|
||||
},
|
||||
collect: (monitor) => ({
|
||||
isDragging: monitor.isDragging()
|
||||
})
|
||||
}),
|
||||
[id, left, top, height, width, title, component, index, zoomLevel, parent]
|
||||
[id, title, component, index, zoomLevel, parent, layouts]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
|
|
@ -114,6 +116,49 @@ export const DraggableBox = function DraggableBox({
|
|||
setCanDrag(newState);
|
||||
}
|
||||
|
||||
const defaultData = {
|
||||
top: 100,
|
||||
left: 0,
|
||||
width: 445,
|
||||
height: 500
|
||||
}
|
||||
|
||||
const layoutData = inCanvas ? layouts[currentLayout] || defaultData : defaultData;
|
||||
const [currentLayoutOptions, setCurrentLayoutOptions] = useState(layoutData);
|
||||
|
||||
useEffect(() => {
|
||||
console.log(layoutData)
|
||||
setCurrentLayoutOptions(layoutData);
|
||||
}, [layoutData.height, layoutData.width, layoutData.left, layoutData.top]);
|
||||
|
||||
// const [currentLayoutOptions, setCurrentLayoutOptions] = useState(layoutData);
|
||||
|
||||
// if(inCanvas && Object.keys(currentLayoutOptions).length === 0 ) {
|
||||
// setCurrentLayoutOptions({
|
||||
// top: 100,
|
||||
// left: 0,
|
||||
// width: 445,
|
||||
// height: 500
|
||||
// })
|
||||
// }
|
||||
|
||||
// useEffect(() => {
|
||||
// if(inCanvas) {
|
||||
|
||||
// if(Object.keys(layoutData).length === 0 ) {
|
||||
// setCurrentLayoutOptions({
|
||||
// top: 100,
|
||||
// left: 0,
|
||||
// width: 445,
|
||||
// height: 500
|
||||
// })
|
||||
// } else {
|
||||
// setCurrentLayoutOptions(layoutData);
|
||||
// }
|
||||
|
||||
// }
|
||||
// }, [layoutData]);
|
||||
|
||||
return (
|
||||
<div>
|
||||
{inCanvas ? (
|
||||
|
|
@ -126,8 +171,8 @@ export const DraggableBox = function DraggableBox({
|
|||
|
||||
<Rnd
|
||||
style={{ ...style }}
|
||||
size={{ width: width + 6, height: height + 6 }}
|
||||
position={{ x: left, y: top }}
|
||||
size={{ width: currentLayoutOptions.width + 6, height: currentLayoutOptions.height + 6}}
|
||||
position={{ x: currentLayoutOptions ? currentLayoutOptions.left : 0, y: currentLayoutOptions ? currentLayoutOptions.top : 0 }}
|
||||
defaultSize={{}}
|
||||
className={`resizer ${mouseOver ? 'resizer-active' : ''}`}
|
||||
onResize={() => setResizing(true)}
|
||||
|
|
@ -153,9 +198,9 @@ export const DraggableBox = function DraggableBox({
|
|||
<Box
|
||||
component={component}
|
||||
id={id}
|
||||
width={width}
|
||||
width={currentLayoutOptions.width}
|
||||
height={currentLayoutOptions.height}
|
||||
mode={mode}
|
||||
height={height}
|
||||
changeCanDrag={changeCanDrag}
|
||||
inCanvas={inCanvas}
|
||||
paramUpdated={paramUpdated}
|
||||
|
|
|
|||
|
|
@ -60,6 +60,7 @@ class Editor extends React.Component {
|
|||
showQueryEditor: true,
|
||||
showLeftSidebar: true,
|
||||
zoomLevel: 1.0,
|
||||
currentLayout: 'desktop',
|
||||
appDefinition: {
|
||||
components: null
|
||||
},
|
||||
|
|
@ -393,8 +394,7 @@ class Editor extends React.Component {
|
|||
currentState,
|
||||
isLoading,
|
||||
zoomLevel,
|
||||
previewLoading,
|
||||
queryPreviewData
|
||||
currentLayout,
|
||||
} = this.state;
|
||||
|
||||
const appLink = `/applications/${appId}`;
|
||||
|
|
@ -489,6 +489,27 @@ class Editor extends React.Component {
|
|||
/>
|
||||
</button>
|
||||
</div>
|
||||
<div className="layout-buttons">
|
||||
<div class="btn-group" role="group" aria-label="Basic example">
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-light"
|
||||
onClick={() => this.setState({ currentLayout: 'desktop' })}
|
||||
disabled={currentLayout === 'desktop'}
|
||||
>
|
||||
<img src="https://www.svgrepo.com/show/108320/desktop.svg" width="12" height="12" />
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-light"
|
||||
onClick={() => this.setState({ currentLayout: 'mobile' })}
|
||||
disabled={currentLayout === 'mobile'}
|
||||
>
|
||||
<img src="https://www.svgrepo.com/show/80383/touch-screen-mobile-device.svg" width="12" height="12" />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<div className="navbar-nav flex-row order-md-last">
|
||||
<div className="nav-item dropdown d-none d-md-flex me-3">
|
||||
{app
|
||||
|
|
@ -631,13 +652,14 @@ class Editor extends React.Component {
|
|||
</Resizable>
|
||||
<div className="main">
|
||||
<div className="canvas-container align-items-center" style={{ transform: `scale(${zoomLevel})` }}>
|
||||
<div className="canvas-area">
|
||||
<div className="canvas-area" style={{width: currentLayout === 'desktop' ? '1292px' : '450px'}}>
|
||||
<Container
|
||||
appDefinition={appDefinition}
|
||||
appDefinitionChanged={this.appDefinitionChanged}
|
||||
snapToGrid={true}
|
||||
mode={'edit'}
|
||||
zoomLevel={zoomLevel}
|
||||
currentLayout={currentLayout}
|
||||
appLoading={isLoading}
|
||||
onEvent={(eventName, options) => onEvent(this, eventName, options)}
|
||||
onComponentOptionChanged={(component, optionName, value) => onComponentOptionChanged(this, component, optionName, value)
|
||||
|
|
@ -653,7 +675,10 @@ class Editor extends React.Component {
|
|||
onComponentClick(this, id, component);
|
||||
}}
|
||||
/>
|
||||
<CustomDragLayer snapToGrid={true} />
|
||||
<CustomDragLayer
|
||||
snapToGrid={true}
|
||||
currentLayout={currentLayout}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
|
|
|
|||
|
|
@ -270,7 +270,6 @@ body {
|
|||
}
|
||||
|
||||
.canvas-area {
|
||||
width: 1292px;
|
||||
min-height: 2400px;
|
||||
background: #edeff5;
|
||||
margin: 0px auto;
|
||||
|
|
|
|||
Loading…
Reference in a new issue