mirror of
https://github.com/ToolJet/ToolJet
synced 2026-04-21 21:47:17 +00:00
* Add routes for multi-page apps * Modify Editor, Viewer and Inspector to accept new app structure * Show a page selector on left side bar * Align component deletion logic with new app schema * Make subcontainer work with multi-page apps * Load components state properly in viewer * Use UUID instead of handle for pages * Display sidebar on viewer to switch pages * Add proper URL suffixing for pages in viewer * Add action to switch page * Revert translation file back to its pre-existing linting * Fix bug that caused modal to not open/close * Add support for query params in page switch * Fix the issue that caused navigation to fail while accessed via slug * Add missing SwitchPage file * Add support for page level variables * Add migration to convert existing apps to new schema * Add rollback for converting multi-page definitions back to single-page * Fix migration for multi-page apps * Adapt import/export service for multi-pages * [improvements] Multi-page applications (#4755) * UI updates for page selector popup card * delete page * delete page check: if only one page exits * switch to home page if the selected page is removed * adds and switch to new page * updating page name * updates to home page and starting page * handle updating the home page when home page is deleted * search box for filtering pages and minor style updates for the page handler card * header search box style fixes * for creating a new page, page handle needs to be unique * seperating into smaller components * updated pinned icon for page selector styles and settinf styles * Leftsidebar header ui component * handle dark theme * page handle ui and dark theme fixes for page menu * page handler edit modal * pinned state and update pinned state for menu options triggered * dark theme fixes for edit modal * handle on update should not be empty or prev * page handler updater * added loading state for saving * handles cancels * fixes slug ui * fixes crash for older app versions * updates the query params when handle gets an update * update homePage to homePageId * removes console.log * go back to the popover for modal close * fixes: Difficult to select page * fixes: Difficult to select the three-dot menu * fixes: on visiting the root url, navigate to homepage on viewer * adds tooltip for url * updates the page selector sidebar with sync with query manager * refactor and cleanup * refactor and cleanup * Compute component state when page is switched * modal should not close on click outside * disable save button if there is not change in the page handle input * should show/hide page menu when hovered * page icon * updates delete icon for disabled state * query manager should always be on top of page selector * checks if homePage key exists in pages def * updates page handler menu * updates the clear icon * page handler menu position * page handler menu position * handle icon * alert msg * global settings handler for updating viewer page navigation * show/hode page navigation for viewer * info text for toggle * Multipages:with sortable list [DnD] (#4783) * applied sortable list * on sort updates the definitions * fixies: app crash for dnd * viwer: canvas width should be 100% when navigation drawer is disbaled * fixes: homepage/startpage reload * clean up Co-authored-by: Sherfin Shamsudeen <sherfin94@gmail.com> * Multipage UI viewer (#4801) * new ui changes for viewer pages * fixes postions for debugger and datasources popover * removes console.log * Multipage : hide page and unhide page feature (#4803) * adds: ability to hide pages * hides pages in viewer * unhide page * hide icon * allow accessing hidden pages from url * add: duplicate page (#4802) * add: duplicate page * do not copy the same references from the original page * page name and page handler should be unique for duplicate pages too * Add support for on-page-load events * Add icon from page settings menu item * Convert existing templates to multi-page schema * error logs for page level and app level errors (#4842) * Adapt comments feature for multi-pages * [Bugfix] multipage - page menu interactions (#4844) * fixes: menu popup interaction * fixes: on modal input focus, we switch the page * Adapt multi-player to multi-pages * Add editingPageId to ymap * Log self, others and editor props in real-time avatar generation * Save editing page id to appDef * Add editingPageId to presence in RealtimeCursors * adds no results ui for empty search results (#4869) * page icon updated (#4870) * fixes:Version switching crashes if the target version does not contain the current page (#4868) * Remove unnecessary setting of editingPageId on ymap * Remove unnecessary console.log * [Bugfix] Multipages: widget inspector event popover unmounts (#4887) * introduced a local state for events * cleaned up inspector.jsx * fixes: table widget inspector event accordion * Do not run switchPage twice when viewer is loaded * Preview should open the currently editing page * Properly place navigation and canvas in viewer * Update app definition whenever event manager changes are made * Add support for browser back and forward button in multi-pages * Rename handleBackButton to handlePageSwitchingBasedOnURLparam * Add support for cut/copy/paste and clone * Fix the crash caused by boxShadow * Add support for background colors in viewer in multi-pages * Run queries to be run on load on viewer, in multi-pages * Fix issue that caused inspector popovers to collapse * resolves workspace vars in viewer mode (#4892) * Multipage : Navigation for Mobile-ui (#4814) * refactored to components * burger menu for mobile ui * merge conflict fix for hidden pages * hamburger menu positioned in the header * viewer header reafctored * viewer mobile page manu styles * handles dark theme * mobile menu with dark mode toggle in the footer * components are moved to page level, handle for mobile layout * style fixes * removing unwanted code block * dark theme fixes * style fixes * fixes: events are sortable (#4895) * fixes: events are sortable * Remove uneccesarily repeated call of setEvents in EventManager Co-authored-by: Sherfin Shamsudeen <sherfin94@gmail.com> * renamed settings to Event handlers (#4898) * updates the page setting title to Page Events * temp commit * Add support for setting max width in percentage * fixes: paramUpdates for boxes: 🙌🏻 * [Bugfix] Multipage - viewer canvas dark theme (#4897) * fixes: darktheme bg for viewer canvas * reverts canvas size * Fix for inspector bouncing back to previous values * resolves pages variables in pythong and js transformation (#4905) * csa support to event manager for pages (#4907) * Add support for setting canvas width in percentages * Persist page level variables across page switches * latest definitions is merged with the current appdef (#4914) * latest definitions is merged with the current appdef * mutating the local obj * cleanup * iterate through pages for new versions are created Co-authored-by: Arpit <arpitnath42@gmail.com>
325 lines
9.7 KiB
JavaScript
325 lines
9.7 KiB
JavaScript
/* eslint-disable react-hooks/exhaustive-deps */
|
|
import React, { useEffect, useState } from 'react';
|
|
import cx from 'classnames';
|
|
import { useDrag } from 'react-dnd';
|
|
import { ItemTypes } from './ItemTypes';
|
|
import { getEmptyImage } from 'react-dnd-html5-backend';
|
|
import { Box } from './Box';
|
|
import { ConfigHandle } from './ConfigHandle';
|
|
import { Rnd } from 'react-rnd';
|
|
import { resolveWidgetFieldValue } from '@/_helpers/utils';
|
|
import ErrorBoundary from './ErrorBoundary';
|
|
|
|
const resizerClasses = {
|
|
topRight: 'top-right',
|
|
bottomRight: 'bottom-right',
|
|
bottomLeft: 'bottom-left',
|
|
topLeft: 'top-left',
|
|
};
|
|
|
|
const resizerStyles = {
|
|
topRight: {
|
|
width: '8px',
|
|
height: '8px',
|
|
right: '-4px',
|
|
top: '-4px',
|
|
},
|
|
bottomRight: {
|
|
width: '8px',
|
|
height: '8px',
|
|
right: '-4px',
|
|
bottom: '-4px',
|
|
},
|
|
bottomLeft: {
|
|
width: '8px',
|
|
height: '8px',
|
|
left: '-4px',
|
|
bottom: '-4px',
|
|
},
|
|
topLeft: {
|
|
width: '8px',
|
|
height: '8px',
|
|
left: '-4px',
|
|
top: '-4px',
|
|
},
|
|
};
|
|
|
|
function computeWidth(currentLayoutOptions) {
|
|
return `${currentLayoutOptions?.width}%`;
|
|
}
|
|
|
|
function getStyles(isDragging, isSelectedComponent) {
|
|
return {
|
|
position: 'absolute',
|
|
zIndex: isSelectedComponent ? 2 : 1,
|
|
// IE fallback: hide the real node using CSS when dragging
|
|
// because IE will ignore our custom "empty image" drag preview.
|
|
opacity: isDragging ? 0 : 1,
|
|
};
|
|
}
|
|
|
|
export const DraggableBox = function DraggableBox({
|
|
id,
|
|
className,
|
|
mode,
|
|
title,
|
|
_left,
|
|
_top,
|
|
parent,
|
|
allComponents,
|
|
component,
|
|
index,
|
|
inCanvas,
|
|
onEvent,
|
|
onComponentClick,
|
|
currentState,
|
|
onComponentOptionChanged,
|
|
onComponentOptionsChanged,
|
|
onResizeStop,
|
|
onDragStop,
|
|
paramUpdated,
|
|
resizingStatusChanged,
|
|
zoomLevel,
|
|
containerProps,
|
|
setSelectedComponent,
|
|
removeComponent,
|
|
currentLayout,
|
|
layouts,
|
|
_deviceWindowWidth,
|
|
isSelectedComponent,
|
|
draggingStatusChanged,
|
|
darkMode,
|
|
canvasWidth,
|
|
readOnly,
|
|
customResolvables,
|
|
parentId,
|
|
hoveredComponent,
|
|
onComponentHover,
|
|
sideBarDebugger,
|
|
isMultipleComponentsSelected,
|
|
dataQueries,
|
|
}) {
|
|
const [isResizing, setResizing] = useState(false);
|
|
const [isDragging2, setDragging] = useState(false);
|
|
const [canDrag, setCanDrag] = useState(true);
|
|
const [mouseOver, setMouseOver] = useState(false);
|
|
|
|
useEffect(() => {
|
|
setMouseOver(hoveredComponent === id);
|
|
}, [hoveredComponent]);
|
|
|
|
const [{ isDragging }, drag, preview] = useDrag(
|
|
() => ({
|
|
type: ItemTypes.BOX,
|
|
item: {
|
|
id,
|
|
title,
|
|
component,
|
|
zoomLevel,
|
|
parent,
|
|
layouts,
|
|
canvasWidth,
|
|
currentLayout,
|
|
},
|
|
collect: (monitor) => ({
|
|
isDragging: monitor.isDragging(),
|
|
}),
|
|
}),
|
|
[id, title, component, index, zoomLevel, parent, layouts, currentLayout, canvasWidth]
|
|
);
|
|
|
|
useEffect(() => {
|
|
preview(getEmptyImage(), { captureDraggingState: true });
|
|
}, [isDragging]);
|
|
|
|
useEffect(() => {
|
|
if (resizingStatusChanged) {
|
|
resizingStatusChanged(isResizing);
|
|
}
|
|
}, [isResizing]);
|
|
|
|
useEffect(() => {
|
|
if (draggingStatusChanged) {
|
|
draggingStatusChanged(isDragging2);
|
|
}
|
|
|
|
if (isDragging2 && !isSelectedComponent) {
|
|
setSelectedComponent(id, component);
|
|
}
|
|
}, [isDragging2]);
|
|
|
|
const style = {
|
|
display: 'inline-block',
|
|
alignItems: 'center',
|
|
justifyContent: 'center',
|
|
padding: '0px',
|
|
};
|
|
|
|
let _refProps = {};
|
|
|
|
if (mode === 'edit' && canDrag) {
|
|
_refProps = {
|
|
ref: drag,
|
|
};
|
|
}
|
|
|
|
function changeCanDrag(newState) {
|
|
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, currentLayout]);
|
|
|
|
const gridWidth = canvasWidth / 43;
|
|
const width = (canvasWidth * currentLayoutOptions.width) / 43;
|
|
|
|
const configWidgetHandlerForModalComponent =
|
|
!isSelectedComponent &&
|
|
component.component === 'Modal' &&
|
|
resolveWidgetFieldValue(component.definition.properties.useDefaultButton, currentState)?.value === false;
|
|
|
|
return (
|
|
<div
|
|
className={inCanvas ? '' : 'col-md-4 text-center align-items-center clearfix mb-2'}
|
|
style={!inCanvas ? {} : { width: computeWidth() }}
|
|
>
|
|
{inCanvas ? (
|
|
<div
|
|
className={cx(`draggable-box widget-${id}`, {
|
|
[className]: !!className,
|
|
'draggable-box-in-editor': mode === 'edit',
|
|
})}
|
|
onMouseEnter={(e) => {
|
|
if (e.currentTarget.className.includes(`widget-${id}`)) {
|
|
onComponentHover?.(id);
|
|
e.stopPropagation();
|
|
}
|
|
}}
|
|
onMouseLeave={() => onComponentHover?.(false)}
|
|
style={getStyles(isDragging, isSelectedComponent)}
|
|
>
|
|
<Rnd
|
|
style={{ ...style }}
|
|
resizeGrid={[gridWidth, 10]}
|
|
dragGrid={[gridWidth, 10]}
|
|
size={{
|
|
width: width,
|
|
height: currentLayoutOptions.height,
|
|
}}
|
|
position={{
|
|
x: currentLayoutOptions ? (currentLayoutOptions.left * canvasWidth) / 100 : 0,
|
|
y: currentLayoutOptions ? currentLayoutOptions.top : 0,
|
|
}}
|
|
defaultSize={{}}
|
|
className={`resizer ${
|
|
mouseOver || isResizing || isDragging2 || isSelectedComponent ? 'resizer-active' : ''
|
|
} `}
|
|
onResize={() => setResizing(true)}
|
|
onDrag={(e) => {
|
|
e.preventDefault();
|
|
e.stopImmediatePropagation();
|
|
if (!isDragging2) {
|
|
setDragging(true);
|
|
}
|
|
}}
|
|
resizeHandleClasses={isSelectedComponent || mouseOver ? resizerClasses : {}}
|
|
resizeHandleStyles={resizerStyles}
|
|
enableResizing={mode === 'edit' && !readOnly}
|
|
disableDragging={mode !== 'edit' || readOnly}
|
|
onDragStop={(e, direction) => {
|
|
setDragging(false);
|
|
onDragStop(e, id, direction, currentLayout, currentLayoutOptions);
|
|
}}
|
|
cancel={`div.table-responsive.jet-data-table, div.calendar-widget, div.text-input, .textarea, .map-widget, .range-slider, .kanban-container`}
|
|
onDragStart={(e) => e.stopPropagation()}
|
|
onResizeStop={(e, direction, ref, d, position) => {
|
|
setResizing(false);
|
|
onResizeStop(id, e, direction, ref, d, position);
|
|
}}
|
|
bounds={parent !== undefined ? `#canvas-${parent}` : '.real-canvas'}
|
|
widgetId={id}
|
|
>
|
|
<div ref={preview} role="DraggableBox" style={isResizing ? { opacity: 0.5 } : { opacity: 1 }}>
|
|
{mode === 'edit' &&
|
|
!readOnly &&
|
|
(configWidgetHandlerForModalComponent || mouseOver || isSelectedComponent) &&
|
|
!isResizing && (
|
|
<ConfigHandle
|
|
id={id}
|
|
removeComponent={removeComponent}
|
|
component={component}
|
|
position={currentLayoutOptions.top < 15 ? 'bottom' : 'top'}
|
|
widgetTop={currentLayoutOptions.top}
|
|
widgetHeight={currentLayoutOptions.height}
|
|
isMultipleComponentsSelected={isMultipleComponentsSelected}
|
|
configWidgetHandlerForModalComponent={configWidgetHandlerForModalComponent}
|
|
/>
|
|
)}
|
|
<ErrorBoundary showFallback={mode === 'edit'}>
|
|
<Box
|
|
component={component}
|
|
id={id}
|
|
width={width}
|
|
height={currentLayoutOptions.height - 4}
|
|
mode={mode}
|
|
changeCanDrag={changeCanDrag}
|
|
inCanvas={inCanvas}
|
|
paramUpdated={paramUpdated}
|
|
onEvent={onEvent}
|
|
onComponentOptionChanged={onComponentOptionChanged}
|
|
onComponentOptionsChanged={onComponentOptionsChanged}
|
|
onComponentClick={onComponentClick}
|
|
currentState={currentState}
|
|
containerProps={containerProps}
|
|
darkMode={darkMode}
|
|
removeComponent={removeComponent}
|
|
canvasWidth={canvasWidth}
|
|
readOnly={readOnly}
|
|
customResolvables={customResolvables}
|
|
parentId={parentId}
|
|
allComponents={allComponents}
|
|
sideBarDebugger={sideBarDebugger}
|
|
dataQueries={dataQueries}
|
|
/>
|
|
</ErrorBoundary>
|
|
</div>
|
|
</Rnd>
|
|
</div>
|
|
) : (
|
|
<div ref={drag} role="DraggableBox" className="draggable-box" style={{ height: '100%' }}>
|
|
<ErrorBoundary showFallback={mode === 'edit'}>
|
|
<Box
|
|
component={component}
|
|
id={id}
|
|
mode={mode}
|
|
inCanvas={inCanvas}
|
|
onEvent={onEvent}
|
|
paramUpdated={paramUpdated}
|
|
onComponentOptionChanged={onComponentOptionChanged}
|
|
onComponentOptionsChanged={onComponentOptionsChanged}
|
|
onComponentClick={onComponentClick}
|
|
currentState={currentState}
|
|
darkMode={darkMode}
|
|
removeComponent={removeComponent}
|
|
sideBarDebugger={sideBarDebugger}
|
|
customResolvables={customResolvables}
|
|
containerProps={containerProps}
|
|
/>
|
|
</ErrorBoundary>
|
|
</div>
|
|
)}
|
|
</div>
|
|
);
|
|
};
|