-
- {defaultComponentStateComputed && (
- <>
- {isLoading ? (
-
- ) : (
-
false} // function not relevant in viewer
- snapToGrid={true}
- appLoading={isLoading}
- darkMode={this.props.darkMode}
- onEvent={(eventName, options) => onEvent(this, eventName, options, 'view')}
- mode="view"
- deviceWindowWidth={deviceWindowWidth}
- currentLayout={currentLayout}
- currentState={this.state.currentState}
- selectedComponent={this.state.selectedComponent}
- onComponentClick={(id, component) => {
- this.setState({
- selectedComponent: { id, component },
- });
- onComponentClick(this, id, component, 'view');
- }}
- onComponentOptionChanged={(component, optionName, value) => {
- return onComponentOptionChanged(this, component, optionName, value);
- }}
- onComponentOptionsChanged={(component, options) =>
- onComponentOptionsChanged(this, component, options)
- }
- canvasWidth={this.getCanvasWidth()}
- dataQueries={dataQueries}
- />
- )}
- >
+
+ {appDefinition?.showViewerNavigation && (
+
)}
+
+ {defaultComponentStateComputed && (
+ <>
+ {isLoading ? (
+
+ ) : (
+
false} // function not relevant in viewer
+ snapToGrid={true}
+ appLoading={isLoading}
+ darkMode={this.props.darkMode}
+ onEvent={(eventName, options) => onEvent(this, eventName, options, 'view')}
+ mode="view"
+ deviceWindowWidth={deviceWindowWidth}
+ currentLayout={currentLayout}
+ currentState={this.state.currentState}
+ selectedComponent={this.state.selectedComponent}
+ onComponentClick={(id, component) => {
+ this.setState({
+ selectedComponent: { id, component },
+ });
+ onComponentClick(this, id, component, 'view');
+ }}
+ onComponentOptionChanged={(component, optionName, value) => {
+ return onComponentOptionChanged(this, component, optionName, value);
+ }}
+ onComponentOptionsChanged={(component, options) =>
+ onComponentOptionsChanged(this, component, options)
+ }
+ canvasWidth={this.getCanvasWidth()}
+ dataQueries={dataQueries}
+ currentPageId={this.state.currentPageId}
+ />
+ )}
+ >
+ )}
+
diff --git a/frontend/src/Editor/Viewer/Header.jsx b/frontend/src/Editor/Viewer/Header.jsx
new file mode 100644
index 0000000000..11c1c4fd69
--- /dev/null
+++ b/frontend/src/Editor/Viewer/Header.jsx
@@ -0,0 +1,13 @@
+import React from 'react';
+
+const Header = ({ children, className }) => {
+ return (
+
+ );
+};
+
+export default Header;
diff --git a/frontend/src/Editor/Viewer/ViewerNavigation.jsx b/frontend/src/Editor/Viewer/ViewerNavigation.jsx
new file mode 100644
index 0000000000..40d00c3340
--- /dev/null
+++ b/frontend/src/Editor/Viewer/ViewerNavigation.jsx
@@ -0,0 +1,213 @@
+import React from 'react';
+import _ from 'lodash';
+import { slide as Menu } from 'react-burger-menu';
+import LogoIcon from '../Icons/logo.svg';
+import { Link } from 'react-router-dom';
+import { DarkModeToggle } from '@/_components/DarkModeToggle';
+import Header from './Header';
+
+export const ViewerNavigation = ({
+ isMobileDevice,
+ canvasBackgroundColor,
+ pages,
+ currentPageId,
+ switchPage,
+ darkMode,
+}) => {
+ if (isMobileDevice) {
+ return null;
+ }
+
+ return (
+
+
+ {pages.map(
+ ([id, page]) =>
+ !page.hidden && (
+
switchPage(id)}
+ className={`viewer-page-handler cursor-pointer ${darkMode && 'dark'}`}
+ >
+
+
+ {_.truncate(page.name, { length: 22 })}
+
+
+
+ )
+ )}
+
+
+ );
+};
+
+const MobileNavigationMenu = ({ pages, switchPage, currentPageId, darkMode, changeDarkMode }) => {
+ const [hamburgerMenuOpen, setHamburgerMenuOpen] = React.useState(false);
+
+ const handlepageSwitch = (pageId) => {
+ setHamburgerMenuOpen(false);
+ switchPage(pageId);
+ };
+
+ var styles = {
+ bmBurgerButton: {
+ position: 'fixed',
+ width: '21px',
+ height: '16px',
+ right: 10,
+ top: 18,
+ },
+ bmBurgerBars: {
+ background: darkMode ? '#4C5155' : 'rgb(77, 114, 250)',
+ },
+ bmCrossButton: {
+ display: 'none',
+ },
+ bmCross: {
+ background: '#bdc3c7',
+ },
+ bmMenuWrap: {
+ height: '100%',
+ width: '100%',
+ top: 0,
+ },
+ bmMenu: {
+ background: darkMode ? '#202B37' : '#fff',
+ padding: '0',
+ },
+ bmMorphShape: {
+ fill: '#373a47',
+ },
+ bmItemList: {
+ display: 'flex',
+ flexDirection: 'column',
+ alignItems: 'center',
+ },
+ bmItem: {
+ display: 'inline-block',
+ },
+ bmOverlay: {
+ background: 'rgba(0, 0, 0, 0.3)',
+ },
+ };
+
+ return (
+ <>
+
+ >
+ );
+};
+
+const ViewerHeader = ({
+ showHeader,
+ appName,
+ changeDarkMode,
+ darkMode,
+ pages,
+ currentPageId,
+ switchPage,
+ currentLayout,
+}) => {
+ return (
+
+ );
+};
+
+const Footer = ({ darkMode, switchDarkMode }) => {
+ return (
+
+ );
+};
+
+ViewerNavigation.BurgerMenu = MobileNavigationMenu;
+ViewerNavigation.Header = ViewerHeader;
+ViewerNavigation.Footer = Footer;
diff --git a/frontend/src/Editor/WidgetManager/components.js b/frontend/src/Editor/WidgetManager/components.js
index 82c802330d..d7aa4442f0 100644
--- a/frontend/src/Editor/WidgetManager/components.js
+++ b/frontend/src/Editor/WidgetManager/components.js
@@ -16,6 +16,9 @@ const universalProps = {
others: {},
events: [],
styles: {},
+ generalStyles: {
+ boxShadow: { value: '0px 0px 0px 0px #00000040' },
+ },
},
};
diff --git a/frontend/src/_components/DarkModeToggle.jsx b/frontend/src/_components/DarkModeToggle.jsx
index bc499cef38..b3959f8233 100644
--- a/frontend/src/_components/DarkModeToggle.jsx
+++ b/frontend/src/_components/DarkModeToggle.jsx
@@ -8,6 +8,7 @@ export const DarkModeToggle = function DarkModeToggle({
darkMode = false,
switchDarkMode,
tooltipPlacement = 'bottom',
+ showText = false,
}) {
const toggleDarkMode = () => {
switchDarkMode(!darkMode);
@@ -58,46 +59,51 @@ export const DarkModeToggle = function DarkModeToggle({
}
>
-
-
-
-
-
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {showText && (
+
Switch to {!darkMode ? 'dark mode' : 'light mode'}
+ )}
+
);
};
diff --git a/frontend/src/_components/SortableList/SortableList.jsx b/frontend/src/_components/SortableList/SortableList.jsx
new file mode 100644
index 0000000000..63f44ce21a
--- /dev/null
+++ b/frontend/src/_components/SortableList/SortableList.jsx
@@ -0,0 +1,48 @@
+import React, { useMemo, useState } from 'react';
+
+import { DndContext, KeyboardSensor, PointerSensor, useSensor, useSensors } from '@dnd-kit/core';
+
+import { SortableContext, arrayMove, sortableKeyboardCoordinates } from '@dnd-kit/sortable';
+
+import { SortableItem, SortableOverlay } from './components';
+
+export function SortableList({ items, onChange, renderItem }) {
+ const [active, setActive] = useState(null);
+ const activeItem = useMemo(() => items.find((item) => item.id === active?.id), [active, items]);
+ const sensors = useSensors(
+ useSensor(PointerSensor),
+ useSensor(KeyboardSensor, {
+ coordinateGetter: sortableKeyboardCoordinates,
+ })
+ );
+
+ return (
+ {
+ setActive(active);
+ }}
+ onDragEnd={({ active, over }) => {
+ if (over && active.id !== over?.id) {
+ const activeIndex = items.findIndex(({ id }) => id === active.id);
+ const overIndex = items.findIndex(({ id }) => id === over.id);
+
+ onChange(arrayMove(items, activeIndex, overIndex));
+ }
+ setActive(null);
+ }}
+ onDragCancel={() => {
+ setActive(null);
+ }}
+ >
+
+ {items.map((item) => (
+ {renderItem(item)}
+ ))}
+
+ {activeItem ? renderItem(activeItem) : null}
+
+ );
+}
+
+SortableList.Item = SortableItem;
diff --git a/frontend/src/_components/SortableList/components/SortableItem.jsx b/frontend/src/_components/SortableList/components/SortableItem.jsx
new file mode 100644
index 0000000000..07789c46aa
--- /dev/null
+++ b/frontend/src/_components/SortableList/components/SortableItem.jsx
@@ -0,0 +1,53 @@
+import React, { createContext, useContext, useMemo } from 'react';
+
+import { useSortable } from '@dnd-kit/sortable';
+import { CSS } from '@dnd-kit/utilities';
+
+const SortableItemContext = createContext({
+ attributes: {},
+ listeners: undefined,
+ ref() {},
+});
+
+export function SortableItem({ children, id, classNames }) {
+ const { attributes, isDragging, listeners, setNodeRef, setActivatorNodeRef, transform, transition } = useSortable({
+ id,
+ });
+ const context = useMemo(
+ () => ({
+ attributes,
+ listeners,
+ ref: setActivatorNodeRef,
+ }),
+ [attributes, listeners, setActivatorNodeRef]
+ );
+ const style = {
+ opacity: isDragging ? 0.4 : undefined,
+ transform: CSS.Translate.toString(transform),
+ transition,
+ };
+
+ return (
+
+
+ {children}
+
+
+ );
+}
+
+export function DragHandle({ show = true }) {
+ const { attributes, listeners, ref } = useContext(SortableItemContext);
+
+ if (!show) {
+ return ;
+ }
+
+ return (
+
+ );
+}
diff --git a/frontend/src/_components/SortableList/components/SortableOverlay.jsx b/frontend/src/_components/SortableList/components/SortableOverlay.jsx
new file mode 100644
index 0000000000..77b2bdcf71
--- /dev/null
+++ b/frontend/src/_components/SortableList/components/SortableOverlay.jsx
@@ -0,0 +1,16 @@
+import React from 'react';
+import { DragOverlay, defaultDropAnimationSideEffects } from '@dnd-kit/core';
+
+const dropAnimationConfig = {
+ sideEffects: defaultDropAnimationSideEffects({
+ styles: {
+ active: {
+ opacity: '0.4',
+ },
+ },
+ }),
+};
+
+export function SortableOverlay({ children }) {
+ return {children};
+}
diff --git a/frontend/src/_components/SortableList/components/index.js b/frontend/src/_components/SortableList/components/index.js
new file mode 100644
index 0000000000..80b9dc0a52
--- /dev/null
+++ b/frontend/src/_components/SortableList/components/index.js
@@ -0,0 +1,4 @@
+import { SortableItem, DragHandle } from './SortableItem';
+import { SortableOverlay } from './SortableOverlay';
+
+export { SortableItem, SortableOverlay, DragHandle };
diff --git a/frontend/src/_components/SortableList/index.js b/frontend/src/_components/SortableList/index.js
new file mode 100644
index 0000000000..98b719403a
--- /dev/null
+++ b/frontend/src/_components/SortableList/index.js
@@ -0,0 +1,47 @@
+import React from 'react';
+import { SortableList } from './SortableList';
+import { DragHandle } from './components';
+
+const SortableComponent = ({ data, Element, ...restProps }) => {
+ const { onSort } = restProps;
+
+ const [items, setItems] = React.useState([]);
+
+ React.useEffect(() => {
+ setItems(data);
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ }, [JSON.stringify(data)]);
+
+ //function to check if the item in items array has changed position with respect to the original data
+ const didItemChangePosition = (originalArr, sortedArry) => {
+ return originalArr.some((item, index) => {
+ return item.id !== sortedArry[index].id;
+ });
+ };
+
+ React.useEffect(() => {
+ if (items.length > 0 && didItemChangePosition(data, items)) {
+ console.log('items changed ==>');
+ onSort(items);
+ }
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ }, [items]);
+
+ return (
+
+ (
+
+
+
+ )}
+ />
+
+ );
+};
+
+SortableComponent.DragHandle = DragHandle;
+
+export default SortableComponent;
diff --git a/frontend/src/_helpers/appUtils.js b/frontend/src/_helpers/appUtils.js
index ce39a99ca2..3e9b1f8f65 100644
--- a/frontend/src/_helpers/appUtils.js
+++ b/frontend/src/_helpers/appUtils.js
@@ -110,6 +110,7 @@ async function exceutePycode(payload, code, currentState, query, mode) {
variables = currentState['variables']
client = currentState['client']
server = currentState['server']
+ page = currentState['page']
code_to_execute = ${_code}
try:
@@ -186,7 +187,7 @@ export async function runTransformation(
if (transformationLanguage === 'javascript') {
try {
const evalFunction = Function(
- ['data', 'moment', '_', 'components', 'queries', 'globals', 'variables'],
+ ['data', 'moment', '_', 'components', 'queries', 'globals', 'variables', 'page'],
transformation
);
@@ -197,7 +198,8 @@ export async function runTransformation(
currentState.components,
currentState.queries,
currentState.globals,
- currentState.variables
+ currentState.variables,
+ currentState.page
);
} catch (err) {
console.log('Transformation failed for query: ', query.name, err);
@@ -212,7 +214,7 @@ export async function runTransformation(
}
export async function executeActionsForEventId(_ref, eventId, component, mode, customVariables) {
- const events = component.definition.events || [];
+ const events = component?.definition?.events || [];
const filteredEvents = events.filter((event) => event.eventId === eventId);
for (const event of filteredEvents) {
@@ -251,7 +253,7 @@ function showModal(_ref, modal, show) {
return Promise.resolve();
}
- const modalMeta = _ref.state.appDefinition.components[modalId];
+ const modalMeta = _ref.state.appDefinition.pages[_ref.state.currentPageId].components[modalId];
const newState = {
currentState: {
..._ref.state.currentState,
@@ -422,6 +424,37 @@ export const executeAction = (_ref, event, mode, customVariables) => {
});
}
+ case 'set-page-variable': {
+ const key = resolveReferences(event.key, _ref.state.currentState, undefined, customVariables);
+ const value = resolveReferences(event.value, _ref.state.currentState, undefined, customVariables);
+ const customPageVariables = { ..._ref.state.currentState.page.variables, [key]: value };
+
+ return _ref.setState({
+ currentState: {
+ ..._ref.state.currentState,
+ page: {
+ ..._ref.state.currentState.page,
+ variables: customPageVariables,
+ },
+ },
+ });
+ }
+
+ case 'unset-page-variable': {
+ const key = resolveReferences(event.key, _ref.state.currentState, undefined, customVariables);
+ const customPageVariables = _.omit(_ref.state.currentState.page.variables, key);
+
+ return _ref.setState({
+ currentState: {
+ ..._ref.state.currentState,
+ page: {
+ ..._ref.state.currentState.page,
+ variables: customPageVariables,
+ },
+ },
+ });
+ }
+
case 'control-component': {
const component = Object.values(_ref.state.currentState?.components ?? {}).filter(
(component) => component.id === event.componentId
@@ -434,6 +467,14 @@ export const executeAction = (_ref, event, mode, customVariables) => {
const actionPromise = action(...actionArguments.map((argument) => argument.value));
return actionPromise ?? Promise.resolve();
}
+
+ case 'switch-page': {
+ _ref.switchPage(
+ event.pageId,
+ resolveReferences(event.queryParams, _ref.state.currentState, [], customVariables)
+ );
+ return Promise.resolve();
+ }
}
}
};
@@ -444,6 +485,10 @@ export async function onEvent(_ref, eventName, options, mode = 'edit') {
const { customVariables } = options;
+ if (eventName === 'onPageLoad') {
+ await executeActionsForEventId(_ref, 'onPageLoad', { definition: { events: [options] } }, mode, customVariables);
+ }
+
if (eventName === 'onTrigger') {
const { component, queryId, queryName } = options;
_self.setState(
@@ -1042,7 +1087,9 @@ export const debuggerActions = {
key,
type: value.type,
kind: errorType !== 'transformations' ? value.kind : 'transformations',
+ page: value.page,
timestamp: moment(),
+ strace: value.strace ?? 'app_level',
};
switch (errorType) {
@@ -1097,19 +1144,22 @@ export const getComponentName = (currentState, id) => {
}
};
-const updateNewComponents = (appDefinition, newComponents, updateAppDefinition) => {
+const updateNewComponents = (pageId, appDefinition, newComponents, updateAppDefinition) => {
const newAppDefinition = JSON.parse(JSON.stringify(appDefinition));
newComponents.forEach((newComponent) => {
- newComponent.component.name = computeComponentName(newComponent.component.component, newAppDefinition.components);
- newAppDefinition.components[newComponent.id] = newComponent;
+ newComponent.component.name = computeComponentName(
+ newComponent.component.component,
+ newAppDefinition.pages[pageId].components
+ );
+ newAppDefinition.pages[pageId].components[newComponent.id] = newComponent;
});
updateAppDefinition(newAppDefinition);
};
export const cloneComponents = (_ref, updateAppDefinition, isCloning = true, isCut = false) => {
- const { selectedComponents, appDefinition } = _ref.state;
+ const { selectedComponents, appDefinition, currentPageId } = _ref.state;
if (selectedComponents.length < 1) return getSelectedText();
- const { components: allComponents } = appDefinition;
+ const { components: allComponents } = appDefinition.pages[currentPageId];
let newDefinition = _.cloneDeep(appDefinition);
let newComponents = [],
newComponentObj = {},
@@ -1135,11 +1185,11 @@ export const cloneComponents = (_ref, updateAppDefinition, isCloning = true, isC
};
}
if (isCloning) {
- addComponents(appDefinition, updateAppDefinition, undefined, newComponentObj);
+ addComponents(currentPageId, appDefinition, updateAppDefinition, undefined, newComponentObj);
toast.success('Component cloned succesfully');
} else if (isCut) {
navigator.clipboard.writeText(JSON.stringify(newComponentObj));
- removeSelectedComponent(newDefinition, selectedComponents);
+ removeSelectedComponent(currentPageId, newDefinition, selectedComponents);
updateAppDefinition(newDefinition);
} else {
navigator.clipboard.writeText(JSON.stringify(newComponentObj));
@@ -1205,14 +1255,15 @@ const updateComponentLayout = (components, parentId, isCut = false) => {
});
};
-export const addComponents = (appDefinition, appDefinitionChanged, parentId = undefined, newComponentObj) => {
+export const addComponents = (pageId, appDefinition, appDefinitionChanged, parentId = undefined, newComponentObj) => {
+ console.log({ pageId, newComponentObj });
const finalComponents = [];
let parentComponent = undefined;
const { isCloning, isCut, newComponents: pastedComponent = [] } = newComponentObj;
if (parentId) {
- const id = Object.keys(appDefinition.components).filter((key) => parentId.startsWith(key));
- parentComponent = JSON.parse(JSON.stringify(appDefinition.components[id[0]]));
+ const id = Object.keys(appDefinition.pages[pageId].components).filter((key) => parentId.startsWith(key));
+ parentComponent = JSON.parse(JSON.stringify(appDefinition.pages[pageId].components[id[0]]));
parentComponent.id = parentId;
}
@@ -1247,7 +1298,7 @@ export const addComponents = (appDefinition, appDefinitionChanged, parentId = un
buildComponents(pastedComponent, parentComponent, true);
- updateNewComponents(appDefinition, finalComponents, appDefinitionChanged);
+ updateNewComponents(pageId, appDefinition, finalComponents, appDefinitionChanged);
!isCloning && toast.success('Component pasted succesfully');
};
@@ -1340,25 +1391,25 @@ export function snapToGrid(canvasWidth, x, y) {
const snappedY = Math.round(y / 10) * 10;
return [snappedX, snappedY];
}
-export const removeSelectedComponent = (newDefinition, selectedComponents) => {
+export const removeSelectedComponent = (pageId, newDefinition, selectedComponents) => {
selectedComponents.forEach((component) => {
let childComponents = [];
- if (newDefinition.components[component.id]?.component?.component === 'Tabs') {
- childComponents = Object.keys(newDefinition.components).filter((key) =>
- newDefinition.components[key].parent?.startsWith(component.id)
+ if (newDefinition.pages[pageId].components[component.id]?.component?.component === 'Tabs') {
+ childComponents = Object.keys(newDefinition.pages[pageId].components).filter((key) =>
+ newDefinition.pages[pageId].components[key].parent?.startsWith(component.id)
);
} else {
- childComponents = Object.keys(newDefinition.components).filter(
- (key) => newDefinition.components[key].parent === component.id
+ childComponents = Object.keys(newDefinition.pages[pageId].components).filter(
+ (key) => newDefinition.pages[pageId].components[key].parent === component.id
);
}
childComponents.forEach((componentId) => {
- delete newDefinition.components[componentId];
+ delete newDefinition.pages[pageId].components[componentId];
});
- delete newDefinition.components[component.id];
+ delete newDefinition.pages[pageId].components[component.id];
});
};
diff --git a/frontend/src/_helpers/utils.js b/frontend/src/_helpers/utils.js
index a190f3b358..06ab2e6cce 100644
--- a/frontend/src/_helpers/utils.js
+++ b/frontend/src/_helpers/utils.js
@@ -56,6 +56,7 @@ function resolveCode(code, state, customObjects = {}, withError = false, reserve
'components',
'queries',
'globals',
+ 'page',
'client',
'server',
'moment',
@@ -70,6 +71,7 @@ function resolveCode(code, state, customObjects = {}, withError = false, reserve
isJsCode ? state?.components : undefined,
isJsCode ? state?.queries : undefined,
isJsCode ? state?.globals : undefined,
+ isJsCode ? state?.page : undefined,
isJsCode ? undefined : state?.client,
isJsCode ? undefined : state?.server,
moment,
@@ -470,6 +472,7 @@ export async function executeMultilineJS(
'components',
'queries',
'globals',
+ 'page',
'axios',
'variables',
'actions',
@@ -483,6 +486,7 @@ export async function executeMultilineJS(
currentState.components,
currentState.queries,
currentState.globals,
+ currentState.page,
axios,
currentState.variables,
actions
diff --git a/frontend/src/_services/comments.service.js b/frontend/src/_services/comments.service.js
index 94f31d94a2..d774d49322 100644
--- a/frontend/src/_services/comments.service.js
+++ b/frontend/src/_services/comments.service.js
@@ -36,8 +36,10 @@ function deleteComment(commentId) {
return adapter.delete(`/comments/${commentId}`);
}
-function getNotifications(appId, isResolved, appVersionsId) {
- return adapter.get(`/comments/${appId}/notifications?isResolved=${isResolved}&appVersionsId=${appVersionsId}`);
+function getNotifications(appId, isResolved, appVersionsId, pageId) {
+ return adapter.get(
+ `/comments/${appId}/notifications?isResolved=${isResolved}&appVersionsId=${appVersionsId}&pageId=${pageId}`
+ );
}
export const commentsService = {
diff --git a/frontend/src/_styles/components.scss b/frontend/src/_styles/components.scss
new file mode 100644
index 0000000000..56ce3c2709
--- /dev/null
+++ b/frontend/src/_styles/components.scss
@@ -0,0 +1,298 @@
+$base-border-radius: 6px;
+$btn-bg: #FFFFFF;
+$btn-color: #11181C;
+
+$btn-dark-bg: #121212;
+$btn-dark-color: #FFFFFF;
+
+
+
+@mixin button($bg, $color) {
+ background-color: $bg;
+ color: $color;
+ border-radius: $base-border-radius;
+ border: none;
+ font-weight: 500;
+ cursor: pointer;
+ transition: all 0.3s ease-in-out;
+ &:hover {
+ @if $bg != none {
+ background-color: darken($bg, 10%);
+ }
+ }
+}
+
+
+.base-button {
+ @include button($btn-bg, $btn-color);
+ border-radius: $base-border-radius;
+ display: flex;
+ flex-direction: row;
+ justify-content: center;
+ align-items: center;
+ padding: 4px 16px;
+ border: 1px solid #D7DBDF;
+}
+
+.base-button.dark {
+ background: $btn-dark-bg;
+ color: $btn-dark-color;
+ border-color: #11181C;
+
+ &:hover {
+ background: lighten($btn-dark-bg, 10%);
+ }
+
+ img {
+ filter: brightness(0) invert(1);
+ }
+}
+
+.unstyled-button {
+ @include button(none, inherit);
+ border: none;
+ font-size: 12px;
+ font-family: 'Roboto';
+ font-style: normal;
+ font-weight: 400;
+ line-height: 20px;
+}
+
+
+.page-handle-button-container {
+ border-radius: $base-border-radius;
+ display: flex;
+ flex-direction: row;
+ justify-content: left;
+ align-items: center;
+ padding: 6px 8px;
+ border: 1px solid #D7DBDF;
+ height: 32px;
+
+ img {
+ position: absolute;
+ right: 0 !important;
+ margin-right: 1.5rem !important;
+ filter: invert(38%) sepia(85%) saturate(5221%) hue-rotate(217deg) brightness(91%) contrast(90%);
+ }
+}
+
+.popover-dark-themed .page-handle-button-container {
+ border-color: #697177;
+ img {
+ filter: invert(95%) sepia(38%) saturate(4716%) hue-rotate(180deg) brightness(113%) contrast(102%);
+ }
+}
+
+.leftsidebar-panel-header {
+ height: 100%;
+ background-color: #F1F3F5;
+
+ .panel-header-container {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ padding: 12px 16px;
+ height: 52px;
+ border-bottom: 1px solid #E6E8EB;
+
+ .add-new-page {
+ margin-left: 4px;
+ }
+
+ }
+ .panel-search-container {
+ padding: 8px 12px;
+ border-bottom: 1px solid #E6E8EB;
+ }
+ }
+
+.leftsidebar-panel-header.dark {
+ // background-color: #202425;
+ background-color: #1F2936;
+ .panel-header-container, .panel-search-container {
+ border-color: #697177;
+ }
+}
+
+.page-selector-panel-body {
+ height: 100%;
+ padding: 12px;
+ background-color: #FFFFFF;
+
+ .page-handler {
+ height: 32px !important;
+ padding: 0;
+ margin-bottom: 6px;
+ font-weight: 500;
+
+ .card, .card-body {
+ padding: 3px;
+ height: 32px;
+ }
+
+ .card.active {
+ background: #ECEEF0;
+ }
+
+ .card.non-active-page {
+ border: none;
+ box-shadow: none;
+ }
+
+ .card:hover {
+ background: #E6EDFE !important;
+ }
+
+ .page-name-input {
+ height: 32px;
+ }
+ }
+}
+
+.page-selector-panel-body.dark {
+ background: #1F2936;
+
+ .page-handler {
+ .card {
+ background: none !important
+ }
+ .card.active {
+ background: #26292B !important;
+ }
+ .card:hover{
+ background: #2C3547 !important;
+ }
+ }
+}
+
+.left-sidebar-page-selector.dark {
+ background: #1F2936 !important;
+
+ .clear-icon {
+ filter: invert(100%);
+ }
+}
+
+#page-handler-menu.global-settings {
+ min-height: 124px;
+ padding: 6px;
+}
+
+#page-handler-menu {
+ border-radius: 4px;
+ width: 238px;
+ margin-top: 0.2rem !important;
+ margin-left: 0.5rem !important;
+ box-shadow: 0px 3px 2px rgba(0, 0, 0, 0.25);
+
+ .popover-body {
+ padding: 16px 6px 0px 6px;
+ height: 100%;
+
+ .card-body {
+ padding: 0;
+ height: 100%;
+ }
+
+ .field {
+ font-weight: 500;
+ font-size: 0.7rem;
+
+ &:hover {
+ color:#919eab;
+ }
+
+ &__danger {
+ color: #ff6666;
+ }
+ }
+ }
+}
+
+.page-icons {
+ position: relative;
+ left: 1rem;
+}
+
+.page-handler-alert {
+ background-color: #fff5f0!important;
+ border: 1px solid #FFF1E7!important;
+}
+
+.page-handle-edit-container {
+ height: 60px;
+ width: 100%;
+
+ .input-group {
+ height: 42px;
+ padding: 2px;
+ }
+
+ .input-group-text {
+ border: none;
+ background: none;
+ font-weight: 400;
+ font-size: 14px;
+ line-height: 20px;
+ padding-right: 4px;
+ }
+
+ .page-handler-input {
+ border-radius: $base-border-radius !important;
+ }
+}
+
+.page-handle-edit-modal.theme-dark {
+ background: none !important;
+
+ .input-group-text {
+ border: none !important;
+ background: none!important; ;
+ }
+}
+
+
+.page-handle-tip {
+ text-decoration: none!important;
+}
+
+.delete-btn.field__danger {
+ img {
+ filter: invert(37%) sepia(50%) saturate(2105%) hue-rotate(342deg) brightness(93%) contrast(93%);
+ }
+}
+
+.clear-icon {
+ margin-top: 6px;
+}
+
+.secondary-text {
+ color: #687076;
+}
+
+
+.DragHandle {
+ display: flex;
+ width: 12px;
+ padding: 3px;
+ align-items: center;
+ justify-content: center;
+ flex: 0 0 auto;
+ touch-action: none;
+ cursor: grab !important;
+ border-radius: 5px;
+ border: none;
+ outline: none;
+ appearance: none;
+ background-color: transparent;
+ -webkit-tap-highlight-color: transparent;
+}
+
+.DragHandle svg {
+ flex: 0 0 auto;
+ margin: auto;
+ height: 100%;
+ overflow: visible;
+ fill: #919eab;
+}
\ No newline at end of file
diff --git a/frontend/src/_styles/custom.scss b/frontend/src/_styles/custom.scss
index 96bfe2c93c..f670d6841d 100644
--- a/frontend/src/_styles/custom.scss
+++ b/frontend/src/_styles/custom.scss
@@ -68,9 +68,14 @@ div[data-disabled='true'] {
}
}
+.switch-page {
+ .css-1nfapid-container{
+ width: 100%;
+ }
+}
.loader-main-container{
width: 0px !important;
position: absolute;
bottom: 44%;
left: 46%;
-}
\ No newline at end of file
+}
diff --git a/frontend/src/_styles/left-sidebar.scss b/frontend/src/_styles/left-sidebar.scss
index e375977eb4..b25e752f93 100644
--- a/frontend/src/_styles/left-sidebar.scss
+++ b/frontend/src/_styles/left-sidebar.scss
@@ -38,7 +38,7 @@
max-height: 60%;
}
.datasources-popover {
- top: 100px;
+ top: 200px;
width: 200px;
.add-btn {
border: none;
@@ -46,7 +46,7 @@
}
}
.debugger-popover {
- top: 180px;
+ top: 270px;
cursor: pointer;
}
.global-settings-popover {
@@ -349,7 +349,6 @@
display: block;
position: absolute;
left: 0;
- margin-right: 0.5rem;
}
.clear-icon {
@@ -361,6 +360,12 @@
}
}
+.panel-search-container {
+ .search-icon {
+ margin: 0.4rem !important;
+ }
+}
+
.link-span {
text-decoration: none;
text-decoration: underline;
@@ -398,3 +403,64 @@
left: 0;
}
}
+
+.page-handler-wrapper {
+ background: transparent;
+}
+
+
+.viewer-page-handler {
+ height: 32px;
+ padding: 0;
+ // max-width: 185px;
+
+ .card {
+ background: none;
+ border: none;
+ box-shadow: none;
+ }
+ .card,.card-body {
+ padding: 3px;
+ height: 32px;
+ }
+
+ .card.active {
+ background: #fff;
+ }
+}
+
+.viewer-page-handler.dark {
+ .card.active {
+ background: #2c405c;
+ }
+}
+
+.theme-dark .viewer-page-handler .card {
+ background: transparent;
+}
+
+.mobile-header {
+ width: 100%;
+}
+
+.bm-item .pages-container {
+ width: 100% !important;
+ padding: 1rem 2rem;
+
+ .card.active {
+ width: inherit;
+ background-color: #ECEEF0;
+ color: #3e525b;
+ }
+}
+
+.bm-item .pages-container.dark {
+ .card.active {
+ background-color: #2c405c;
+ color: #fff;
+ }
+}
+
+.viewer-footer {
+ height: 48px;
+}
\ No newline at end of file
diff --git a/frontend/src/_styles/theme.scss b/frontend/src/_styles/theme.scss
index 8d70ca7658..9c0a0a0751 100644
--- a/frontend/src/_styles/theme.scss
+++ b/frontend/src/_styles/theme.scss
@@ -2,6 +2,7 @@
@import "./colors.scss";
@import "./z-index.scss";
@import "./mixins.scss";
+@import "./components.scss";
// variables
$border-radius: 4px;
@@ -360,6 +361,13 @@ button {
padding: 1rem 0rem 1rem 1rem;
}
}
+
+ .left-sidebar-page-selector {
+ .add-new-page-button-container{
+ width: 100%;
+ margin-top: 10px;
+ }
+ }
}
.editor-sidebar {
@@ -560,7 +568,7 @@ button {
}
.query-pane {
- z-index: 1;
+ z-index: 10;
height: 350px;
position: fixed;
left: 76px; //sidebar is 76px
@@ -769,6 +777,29 @@ button {
background-size: 80px 80px;
background-repeat: repeat;
}
+
+ .navigation-area {
+ background-color: #ECEEF0;
+ padding: 1rem;
+ a.page-link {
+ border-radius: 0;
+ border: 0;
+ }
+
+ a.page-link:hover {
+ color: white;
+ background-color: #4D72FA ;
+ }
+
+ a.page-link.active {
+ color: white;
+ background-color: #4D72FA ;
+ }
+ }
+
+ .navigation-area.dark {
+ background-color: #2b3546 !important;
+ }
}
}
}
@@ -3034,7 +3065,7 @@ input:focus-visible {
}
.card {
- background-color: #324156 !important;
+ background-color: #324156;
}
.card .table tbody td a {
@@ -3076,6 +3107,25 @@ input:focus-visible {
background-color: #2f3c4c;
}
+ .main .navigation-area {
+ background-color: #2f3c4c !important;
+ a.page-link {
+ border-radius: 0;
+ border: 0;
+ color: white;
+ }
+
+ a.page-link:hover {
+ color: white;
+ background-color: #4D72FA ;
+ }
+
+ a.page-link.active {
+ color: white;
+ background-color: #4D72FA ;
+ }
+ }
+
.rdtOpen .rdtPicker {
color: black;
}
@@ -3180,6 +3230,23 @@ input:focus-visible {
.text-muted {
color: #fff !important;
}
+
+ .left-sidebar-page-selector {
+ .list-group {
+ .list-group-item {
+ border: solid #1d2a39 1px;
+ color: white;
+ }
+
+ .list-group-item:hover {
+ background-color: #1F2936;
+ }
+
+ .list-group-item.active {
+ background-color: #1F2936;
+ }
+ }
+ }
}
.folder-list {
@@ -4402,9 +4469,14 @@ input[type="text"] {
.searchbox-wrapper {
margin-top: 0 !important;
+
+ .search-icon {
+ margin: 0.30rem
+ }
input {
border-radius: $border-radius !important;
+ padding-left: 1.75rem !important;
}
}
@@ -4904,7 +4976,7 @@ input[type="text"] {
// **Alert component**
.alert-component {
- border: 1px solid rgba(101, 109, 119, 0.16) !important;
+ border: 1px solid rgba(101, 109, 119, 0.16);
background: #f5f7f9;
a {
@@ -5866,6 +5938,10 @@ div#driver-page-overlay {
cursor: text;
}
+.cursor-not-allowed {
+ cursor: none;
+}
+
.bade-component {
display: inline-flex;
justify-content: center;
@@ -6930,9 +7006,33 @@ tbody {
.page-body {
height: calc(100vh - 1.25rem - 48px);
- min-height: 500px;
+ min-height : 500px;
+}
+
+.theme-dark{
+ .org-avatar:hover{
+ .avatar{
+ background: #10141A no-repeat center/cover ;
+ }
+ }
+
}
+.dark-theme-toggle-btn {
+ height: 32px;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+
+}
+.dark-theme-toggle-btn-text {
+ font-size: 14px;
+ margin: 12px;
+}
+
+.maximum-canvas-height-input-field {
+ width: 90px;
+}
//ONBOARD START---------------------------->>>>>
.onboarding-page-wrapper {
@@ -8179,4 +8279,4 @@ tbody {
}
-//ONBOARD STYLES END---------------------------->>>>>
\ No newline at end of file
+//ONBOARD STYLES END---------------------------->>>>>
diff --git a/frontend/src/_ui/LeftSidebar/Button.jsx b/frontend/src/_ui/LeftSidebar/Button.jsx
new file mode 100644
index 0000000000..ca38fc2bbe
--- /dev/null
+++ b/frontend/src/_ui/LeftSidebar/Button.jsx
@@ -0,0 +1,63 @@
+import React from 'react';
+
+const defaultDisabledStyles = {
+ color: '#C1C8CD',
+ cursor: 'not-allowed',
+ pointerEvents: 'none',
+};
+
+const Button = ({
+ children,
+ onClick,
+ darkMode,
+ size = 'sm',
+ classNames = '',
+ styles = {},
+ disabled = false,
+ isLoading = false,
+}) => {
+ const baseHeight = size === 'sm' ? 28 : 40;
+ const baseWidth = size === 'sm' ? 92 : 150;
+
+ const diabledStyles = {
+ ...defaultDisabledStyles,
+ backgroundColor: '#F1F3F5',
+ };
+
+ return (
+
+ {!isLoading && children}
+
+ );
+};
+
+const Content = ({ title = null, iconSrc = null, direction = 'left' }) => {
+ const icon = !iconSrc ? '' :
;
+ const btnTitle = !title ? '' : typeof title === 'function' ? title() : {title};
+ const content = direction === 'left' ? [icon, btnTitle] : [btnTitle, icon];
+
+ return content;
+};
+
+const UnstyledButton = ({ children, onClick, classNames = '', styles = {}, disabled = false }) => {
+ return (
+
+ {children}
+
+ );
+};
+
+Button.Content = Content;
+Button.UnstyledButton = UnstyledButton;
+
+export default Button;
diff --git a/frontend/src/_ui/LeftSidebar/Header.jsx b/frontend/src/_ui/LeftSidebar/Header.jsx
new file mode 100644
index 0000000000..feabdecaf3
--- /dev/null
+++ b/frontend/src/_ui/LeftSidebar/Header.jsx
@@ -0,0 +1,35 @@
+import React from 'react';
+import { SearchBoxComponent } from '@/_ui/Search';
+
+const Header = ({ children, darkMode }) => {
+ return {children}
;
+};
+
+const PanelHeader = ({ children, title }) => {
+ return (
+
+ );
+};
+
+const SearchContainer = ({ onChange, placeholder, placeholderIcon, callBack = null }) => {
+ return (
+
+
+
+ );
+};
+
+Header.PanelHeader = PanelHeader;
+Header.SearchBoxComponent = SearchContainer;
+
+export default Header;
diff --git a/frontend/src/_ui/LeftSidebar/index.js b/frontend/src/_ui/LeftSidebar/index.js
new file mode 100644
index 0000000000..222a59ea49
--- /dev/null
+++ b/frontend/src/_ui/LeftSidebar/index.js
@@ -0,0 +1,4 @@
+import Button from './Button';
+import HeaderSection from './Header';
+
+export { Button, HeaderSection };
diff --git a/frontend/src/_ui/Search/Search.jsx b/frontend/src/_ui/Search/Search.jsx
index c9d7b0da3b..c6779db096 100644
--- a/frontend/src/_ui/Search/Search.jsx
+++ b/frontend/src/_ui/Search/Search.jsx
@@ -2,7 +2,7 @@ import React from 'react';
import { useTranslation } from 'react-i18next';
export const SearchBox = ({ onChange, ...restProps }) => {
- const { callback, placeholder } = restProps;
+ const { callback, placeholder, placeholderIcon = null } = restProps;
const [searchText, setSearchText] = React.useState('');
const { t } = useTranslation();
@@ -32,45 +32,43 @@ export const SearchBox = ({ onChange, ...restProps }) => {
{searchText.length === 0 && (
-
-