diff --git a/.version b/.version
index 7b5655c811..3a05135cd8 100644
--- a/.version
+++ b/.version
@@ -1 +1 @@
-2.35.4
+2.36.0
diff --git a/frontend/.version b/frontend/.version
index 7b5655c811..3a05135cd8 100644
--- a/frontend/.version
+++ b/frontend/.version
@@ -1 +1 @@
-2.35.4
+2.36.0
diff --git a/frontend/assets/translations/en.json b/frontend/assets/translations/en.json
index d02f5fa139..f73c59f4ca 100644
--- a/frontend/assets/translations/en.json
+++ b/frontend/assets/translations/en.json
@@ -706,7 +706,7 @@
"columnType": "Column type",
"columnName": "Column name",
"overflow": "Overflow",
- "key": "key",
+ "key": "Key",
"textColor": "Text color",
"validation": "Validation",
"regex": "Regex",
@@ -716,8 +716,8 @@
"values": "Values",
"labels": "Labels",
"cellBgColor": "Cell background color",
- "dateDisplayformat": "Date display format",
- "dateParseformat": "Date parse format",
+ "dateDisplayformat": "Date format",
+ "dateParseformat": "Date",
"showTime": "show time",
"makeEditable": "make editable",
"buttonText": "Button text",
@@ -727,7 +727,10 @@
"addColumn": "Add column",
"addNewColumn": "Add new column",
"noActionMessage": "This table doesn't have any action buttons",
- "horizontalAlignment": "horizontal alignment"
+ "horizontalAlignment":"Horizontal alignment",
+ "textAlignment":"Text alignment",
+ "deciamalPlaces":"Decimal Places",
+ "imageFit":"Image fit"
},
"Button": {
"displayName": "Button",
@@ -947,6 +950,7 @@
"maxWidthOfCanvas": "Max width of canvas",
"maxHeightOfCanvas": "Max height of canvas",
"backgroundColorOfCanvas": "Canvas background",
+ "appMode": "App mode",
"exportApp": "Export app"
},
"Back": {
diff --git a/frontend/package-lock.json b/frontend/package-lock.json
index 5b7a3116b9..ed5bdede1f 100644
--- a/frontend/package-lock.json
+++ b/frontend/package-lock.json
@@ -67,7 +67,7 @@
"react-circular-progressbar": "^2.1.0",
"react-color": "^2.19.3",
"react-copy-to-clipboard": "^5.1.0",
- "react-datepicker": "^4.10.0",
+ "react-datepicker": "^4.25.0",
"react-dates": "^21.8.0",
"react-datetime": "^3.2.0",
"react-dnd": "^16.0.1",
@@ -36427,9 +36427,9 @@
}
},
"node_modules/plotly.js-dist-min": {
- "version": "2.29.1",
- "resolved": "https://registry.npmjs.org/plotly.js-dist-min/-/plotly.js-dist-min-2.29.1.tgz",
- "integrity": "sha512-YvqX5TISWsJVTDIaUh2Qgt9uhLl0bWcQhO2rLPF0/hIY9BlinFa1JwSO2jFKcEmG0AJXSo4DnVdgKpsk9/8Apg=="
+ "version": "2.30.1",
+ "resolved": "https://registry.npmjs.org/plotly.js-dist-min/-/plotly.js-dist-min-2.30.1.tgz",
+ "integrity": "sha512-xWLni6EpBDwG/EdjCBTu+GK1Asuqswgh+YC77t0UAxa5OFFgpjYkVuvozZmkWqT9TKpvzjxkK4LLGv5Rn+yo7Q=="
},
"node_modules/point-in-polygon": {
"version": "1.1.0",
@@ -37237,8 +37237,9 @@
}
},
"node_modules/react-datepicker": {
- "version": "4.24.0",
- "license": "MIT",
+ "version": "4.25.0",
+ "resolved": "https://registry.npmjs.org/react-datepicker/-/react-datepicker-4.25.0.tgz",
+ "integrity": "sha512-zB7CSi44SJ0sqo8hUQ3BF1saE/knn7u25qEMTO1CQGofY1VAKahO8k9drZtp0cfW1DMfoYLR3uSY1/uMvbEzbg==",
"dependencies": {
"@popperjs/core": "^2.11.8",
"classnames": "^2.2.6",
diff --git a/frontend/package.json b/frontend/package.json
index 42b905a89c..dc4f945ff3 100644
--- a/frontend/package.json
+++ b/frontend/package.json
@@ -62,7 +62,7 @@
"react-circular-progressbar": "^2.1.0",
"react-color": "^2.19.3",
"react-copy-to-clipboard": "^5.1.0",
- "react-datepicker": "^4.10.0",
+ "react-datepicker": "^4.25.0",
"react-dates": "^21.8.0",
"react-datetime": "^3.2.0",
"react-dnd": "^16.0.1",
diff --git a/frontend/src/App/App.jsx b/frontend/src/App/App.jsx
index f5261122e3..39d719732d 100644
--- a/frontend/src/App/App.jsx
+++ b/frontend/src/App/App.jsx
@@ -31,23 +31,28 @@ import 'react-tooltip/dist/react-tooltip.css';
import { getWorkspaceIdOrSlugFromURL } from '@/_helpers/routes';
import ErrorPage from '@/_components/ErrorComponents/ErrorPage';
import WorkspaceConstants from '@/WorkspaceConstants';
-import { useAppDataStore } from '@/_stores/appDataStore';
+import { useSuperStore } from '../_stores/superStore';
+import { ModuleContext } from '../_contexts/ModuleContext';
+import cx from 'classnames';
+import useAppDarkMode from '@/_hooks/useAppDarkMode';
import { ManageOrgUsers } from '@/ManageOrgUsers';
import { ManageGroupPermissions } from '@/ManageGroupPermissions';
import OrganizationLogin from '@/_components/OrganizationLogin/OrganizationLogin';
import { ManageOrgVars } from '@/ManageOrgVars';
const AppWrapper = (props) => {
+ const { isAppDarkMode } = useAppDarkMode();
return (
| + |
{
+ const nonEditableContent = (isTruthyValue) => {
+ return isTruthyValue ? (
+
+ {isEditable ? editableContent(isEditable, value, onChange) : nonEditableContent(value)}
+
+ );
+};
diff --git a/frontend/src/Editor/Components/Table/CustomDatePickerHeader.jsx b/frontend/src/Editor/Components/Table/CustomDatePickerHeader.jsx
new file mode 100644
index 0000000000..d072e9f1d8
--- /dev/null
+++ b/frontend/src/Editor/Components/Table/CustomDatePickerHeader.jsx
@@ -0,0 +1,93 @@
+import React from 'react';
+import SolidIcon from '@/_ui/Icon/SolidIcons';
+import { getMonth, getYear } from 'date-fns';
+import { range } from 'lodash';
+
+const CustomDatePickerHeader = ({
+ date,
+ changeYear,
+ changeMonth,
+ decreaseMonth,
+ increaseMonth,
+ prevMonthButtonDisabled,
+ nextMonthButtonDisabled,
+}) => {
+ const months = [
+ 'January',
+ 'February',
+ 'March',
+ 'April',
+ 'May',
+ 'June',
+ 'July',
+ 'August',
+ 'September',
+ 'October',
+ 'November',
+ 'December',
+ ];
+
+ const years = range(1990, getYear(new Date()) + 1, 1);
+
+ return (
+ <>
+
+
+
+ >
+ );
+};
+
+export default CustomDatePickerHeader;
diff --git a/frontend/src/Editor/Components/Table/CustomDropdown.jsx b/frontend/src/Editor/Components/Table/CustomDropdown.jsx
new file mode 100644
index 0000000000..2478e6638f
--- /dev/null
+++ b/frontend/src/Editor/Components/Table/CustomDropdown.jsx
@@ -0,0 +1,165 @@
+import React, { useState, useEffect } from 'react';
+import SelectSearch from 'react-select-search';
+import { useTranslation } from 'react-i18next';
+import OverlayTrigger from 'react-bootstrap/OverlayTrigger';
+
+export const CustomDropdown = ({
+ options,
+ value,
+ multiple,
+ onChange,
+ isEditable,
+ width,
+ contentWrap,
+ autoHeight,
+ darkMode,
+}) => {
+ const [showOverlay, setShowOverlay] = useState(false);
+ const [hovered, setHovered] = useState(false);
+
+ const elem = document.querySelector(
+ '.table-custom-select-badge-badges .select-search-container.select-search-is-multiple .select-search-value > div'
+ );
+
+ useEffect(() => {
+ if (hovered) {
+ setShowOverlay(true);
+ } else {
+ setShowOverlay(false);
+ }
+ }, [hovered]);
+
+ const checkForValidValue = (value) => {
+ // value for badge should be ['premitive values'] and not [{}]
+ if (!Array.isArray(value)) {
+ return [];
+ }
+ const nonPremitiveValueExits = value.find((singleValue) => typeof singleValue === 'object');
+ return nonPremitiveValueExits ? [] : value;
+ };
+
+ value = multiple ? checkForValidValue(value) : value;
+ const { t } = useTranslation();
+
+ function renderValue(valueProps) {
+ if (!isEditable && valueProps) {
+ const stringifyValue = String(valueProps.value);
+ const arrayOfValueProps = stringifyValue.includes(',') ? stringifyValue.split(', ') : stringifyValue.split(' ');
+ return (
+
+
+
+
+
+
+
+ {arrayOfValueProps.map((value, index) => (
+
+ {value}
+
+ ))}
+
+ );
+ } else if (valueProps) {
+ const stringifyValue = String(valueProps.value);
+ const arrayOfValueProps = stringifyValue.includes(',') ? stringifyValue.split(', ') : stringifyValue.split(' ');
+ return (
+
+ {arrayOfValueProps.map((value, index) => (
+
+ {value}
+
+ ))}
+
+ );
+ }
+ }
+ const getOverlay = (value, containerWidth, options) => {
+ const labels = Array.isArray(value)
+ ? value.map((value) => {
+ const option = options.find((option) => option.value === value);
+ if (option) {
+ return option.name;
+ }
+ })
+ : [];
+ return Array.isArray(labels) ? (
+
+ {labels?.map((label) => {
+ return (
+
+ {label}
+
+ );
+ })}
+
+ ) : (
+
+ );
+ };
+
+ return (
+ {
+ if (!hovered) setHovered(true);
+ }}
+ onMouseLeave={() => setHovered(false)}
+ >
+
+
-
+ )
+ }
+ trigger={isMulti && !isFocused && valueContainerHeight > containerHeight && ['hover', 'focus']} //container width -24 -16 gives that select container size
+ rootClose={true}
+ >
+
+
+
+ );
+};
+
+const CustomMenuList = ({ optionsLoadingState, children, selectProps, inputRef, ...props }) => {
+ const { onInputChange, inputValue, onMenuInputFocus } = selectProps;
+
+ return (
+ e.stopPropagation()}>
+
+ );
+};
+
+const CustomMultiSelectOption = ({ innerRef, innerProps, children, isSelected, ...props }) => {
+ return (
+
+ {!inputValue && (
+
+
+
+
+ ) : (
+ children
+ )}
+
+
+
+
+ {props.isMulti ? (
+
+ );
+};
+
+const MultiValueRemove = (props) => {
+ const { innerProps } = props;
+ return ;
+};
+const CustomMultiValueContainer = (props) => {
+ return (
+
+
+ )}
+ {children}
+
+ {props.children}
+
+ );
+};
+
+const getOverlay = (value, containerWidth, darkMode) => {
+ const getLabel = (option) => {
+ if (option?.hasOwnProperty('label')) {
+ return option.label;
+ } else if (isString(option)) {
+ return option;
+ } else return '';
+ };
+
+ return Array.isArray(value) ? (
+
+ {value?.map((option) => {
+ return (
+
+ {getLabel(option)}
+
+ );
+ })}
+
+ ) : (
+
+ );
+};
+
+const DropdownIndicator = (props) => {
+ return (
+
+ {/* Your custom SVG */}
+ {props.selectProps.menuIsOpen ? (
+
);
};
diff --git a/frontend/src/Editor/Components/Table/Datepicker.jsx b/frontend/src/Editor/Components/Table/Datepicker.jsx
index 4138bea58a..bc12fe3d8e 100644
--- a/frontend/src/Editor/Components/Table/Datepicker.jsx
+++ b/frontend/src/Editor/Components/Table/Datepicker.jsx
@@ -1,25 +1,76 @@
-import React from 'react';
-import Datetime from 'react-datetime';
+import React, { forwardRef, useEffect, useRef } from 'react';
import moment from 'moment-timezone';
-import 'react-datetime/css/react-datetime.css';
-import '@/_styles/custom.scss';
+import DatePickerComponent from 'react-datepicker';
+import CustomDatePickerHeader from './CustomDatePickerHeader';
+import 'react-datepicker/dist/react-datepicker.css';
+import './datepicker.scss';
+import cx from 'classnames';
+import SolidIcon from '@/_ui/Icon/SolidIcons';
-const getDate = (value, parseDateFormat, displayFormat, timeZoneValue, timeZoneDisplay) => {
+const DISABLED_DATE_FORMAT = 'MM/DD/YYYY';
+
+const TjDatepicker = forwardRef(({ value, onClick, styles, dateInputRef, readOnly }, ref) => {
+ return (
+
+ {
+ e.stopPropagation();
+ }}
+ className={cx('table-column-datepicker-input text-truncate', {
+ 'pointer-events-none': readOnly,
+ })}
+ value={value}
+ onClick={onClick}
+ ref={dateInputRef}
+ style={styles}
+ />
+ {!readOnly && (
+
+
+ );
+});
+
+export const getDateTimeFormat = (
+ dateDisplayFormat,
+ isTimeChecked,
+ isTwentyFourHrFormatEnabled,
+ isDateSelectionEnabled
+) => {
+ const timeFormat = isTwentyFourHrFormatEnabled ? 'HH:mm' : 'LT';
+ if (isTimeChecked && !isDateSelectionEnabled) {
+ return timeFormat;
+ }
+ return isTimeChecked ? `${dateDisplayFormat} ${timeFormat}` : dateDisplayFormat;
+};
+
+const getDate = ({
+ value,
+ parseDateFormat,
+ timeZoneValue,
+ timeZoneDisplay,
+ unixTimestamp,
+ parseInUnixTimestamp,
+ isTimeChecked,
+}) => {
+ let momentObj = null;
if (value) {
- const dateString = value;
- if (timeZoneValue && timeZoneDisplay) {
- let momentString = moment
- .tz(dateString, parseDateFormat, timeZoneValue)
- .tz(timeZoneDisplay)
- .format(displayFormat);
- return momentString;
+ if (parseInUnixTimestamp && unixTimestamp) {
+ momentObj = unixTimestamp === 'seconds' ? moment.unix(value) : moment(parseInt(value));
+ } else if (isTimeChecked && timeZoneValue && timeZoneDisplay) {
+ momentObj = moment.tz(value, parseDateFormat, timeZoneValue).tz(timeZoneDisplay);
} else {
- const momentObj = moment(dateString, parseDateFormat);
- const momentString = momentObj.format(displayFormat);
- return momentString;
+ momentObj = moment(value, parseDateFormat);
}
}
- return '';
+ return momentObj?.isValid() ? momentObj.toDate() : null;
};
export const Datepicker = function Datepicker({
@@ -27,68 +78,151 @@ export const Datepicker = function Datepicker({
onChange,
readOnly,
isTimeChecked,
- tableRef,
dateDisplayFormat, //?Display date format
parseDateFormat, //?Parse date format
timeZoneValue,
timeZoneDisplay,
+ isDateSelectionEnabled,
+ isTwentyFourHrFormatEnabled,
+ disabledDates,
+ unixTimestamp = 'seconds',
+ parseInUnixTimestamp,
+ cellStyles,
+ darkMode,
}) {
- const [date, setDate] = React.useState(() =>
- getDate(value, parseDateFormat, dateDisplayFormat, timeZoneValue, timeZoneDisplay)
- );
+ const [date, setDate] = React.useState(null);
+ const [excludedDates, setExcludedDates] = React.useState([]);
const pickerRef = React.useRef();
- const dateChange = (event) => {
- const value = event._isAMomentObject ? event.format() : event;
- let selectedDateFormat = isTimeChecked ? `${dateDisplayFormat} LT` : dateDisplayFormat;
- const dateString = moment(value).format(selectedDateFormat);
- setDate(() => dateString);
- };
-
- React.useEffect(() => {
- let selectedDateFormat = isTimeChecked ? `${dateDisplayFormat} LT` : dateDisplayFormat;
- const dateString = getDate(value, parseDateFormat, selectedDateFormat, timeZoneValue, timeZoneDisplay);
- setDate(() => dateString);
- // eslint-disable-next-line react-hooks/exhaustive-deps
- }, [isTimeChecked, readOnly, dateDisplayFormat]);
-
- const onDatepickerClose = () => {
- onChange(date);
- };
-
- let inputProps = {
- disabled: !readOnly,
- };
-
- const calculatePosition = () => {
- const dropdown = pickerRef.current && pickerRef.current.querySelectorAll('.rdtPicker')[0];
- if (dropdown && tableRef.current) {
- const tablePos = tableRef.current.getBoundingClientRect();
- const dropDownPos = pickerRef.current.getBoundingClientRect();
- const left = dropDownPos.left - tablePos.left;
- const top = dropDownPos.bottom - tablePos.top;
- dropdown.style.left = `${left}px`;
- dropdown.style.top = `${top}px`;
+ const handleDateChange = (date) => {
+ let value = date;
+ if (parseInUnixTimestamp && unixTimestamp) {
+ value = moment(date).unix();
+ }
+ const _date = getDate({
+ value,
+ parseDateFormat: getDateTimeFormat(
+ parseDateFormat,
+ isTimeChecked,
+ isTwentyFourHrFormatEnabled,
+ isDateSelectionEnabled
+ ),
+ dateDisplayFormat,
+ timeZoneValue,
+ timeZoneDisplay,
+ unixTimestamp,
+ parseInUnixTimestamp,
+ isTimeChecked,
+ });
+ setDate(_date);
+ if (parseInUnixTimestamp && unixTimestamp) {
+ onChange(moment(_date).unix());
+ } else {
+ onChange(computeDateString(_date));
}
};
+ useEffect(() => {
+ const date = getDate({
+ value,
+ parseDateFormat: getDateTimeFormat(
+ parseDateFormat,
+ isTimeChecked,
+ isTwentyFourHrFormatEnabled,
+ isDateSelectionEnabled
+ ),
+ dateDisplayFormat,
+ timeZoneValue,
+ timeZoneDisplay,
+ unixTimestamp,
+ parseInUnixTimestamp,
+ isTimeChecked,
+ });
+ setDate(date);
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ }, [
+ JSON.stringify(
+ value,
+ parseDateFormat,
+ dateDisplayFormat,
+ timeZoneValue,
+ timeZoneDisplay,
+ unixTimestamp,
+ isTimeChecked,
+ isTwentyFourHrFormatEnabled,
+ parseInUnixTimestamp
+ ),
+ ]);
+
+ const dateInputRef = useRef(null); // Create a ref
+
+ const computeDateString = (_date) => {
+ if (_date === null && !value) return ''; // If there is no value in table data, return empty string to display
+ if (!isDateSelectionEnabled && !isTimeChecked) return '';
+ if (_date === null) return 'Invalid date';
+
+ const timeFormat = isTwentyFourHrFormatEnabled ? 'HH:mm' : 'LT';
+ const selectedDateFormat = isTimeChecked ? `${dateDisplayFormat} ${timeFormat}` : dateDisplayFormat;
+
+ if (isDateSelectionEnabled) {
+ if (isTimeChecked && parseInUnixTimestamp && unixTimestamp) {
+ return timeZoneDisplay
+ ? moment(_date).tz(timeZoneDisplay).format(selectedDateFormat)
+ : moment(_date).format(selectedDateFormat);
+ }
+ if (isTimeChecked && timeZoneValue && timeZoneDisplay) {
+ return moment.tz(_date, parseDateFormat, timeZoneValue).tz(timeZoneDisplay).format(selectedDateFormat);
+ }
+ return moment(_date).format(selectedDateFormat);
+ }
+
+ if (!isDateSelectionEnabled && isTimeChecked) {
+ return moment(_date).format(timeFormat);
+ }
+ };
+
+ useEffect(() => {
+ if (Array.isArray(disabledDates) && disabledDates.length > 0) {
+ const _exluded = [];
+ disabledDates?.map((item) => {
+ if (moment(item, DISABLED_DATE_FORMAT).isValid()) {
+ _exluded.push(moment(item, DISABLED_DATE_FORMAT).toDate());
+ }
+ });
+ setExcludedDates(_exluded);
+ }
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ }, [disabledDates]);
+
return (
-
);
diff --git a/frontend/src/Editor/Components/Table/Filter.jsx b/frontend/src/Editor/Components/Table/Filter.jsx
index fd1664606f..c158d852b7 100644
--- a/frontend/src/Editor/Components/Table/Filter.jsx
+++ b/frontend/src/Editor/Components/Table/Filter.jsx
@@ -172,7 +172,7 @@ export function Filter(props) {
}
return (
-
+ @@ -207,6 +207,7 @@ export function Filter(props) { className={`${darkMode ? 'select-search-dark' : 'select-search'} mb-0`} styles={selectStyles('100%')} useCustomStyles={true} + darkMode={darkMode} />
@@ -222,6 +223,7 @@ export function Filter(props) {
styles={selectStyles('100%')}
dataCy={`select-coloumn-dropdown-${index ?? ''}`}
useCustomStyles={true}
+ darkMode={darkMode}
/>
diff --git a/frontend/src/Editor/Components/Table/GenerateEachCellValue.jsx b/frontend/src/Editor/Components/Table/GenerateEachCellValue.jsx
index 485fb773e7..62419b512f 100644
--- a/frontend/src/Editor/Components/Table/GenerateEachCellValue.jsx
+++ b/frontend/src/Editor/Components/Table/GenerateEachCellValue.jsx
@@ -1,7 +1,10 @@
-import React, { useEffect, useRef } from 'react';
+import React, { useEffect, useRef, useState } from 'react';
import _ from 'lodash';
import { validateWidget } from '@/_helpers/utils';
import { useMounted } from '@/_hooks/use-mount';
+import OverlayTrigger from 'react-bootstrap/OverlayTrigger';
+import DOMPurify from 'dompurify';
+import NullRenderer from './NullRenderer/NullRenderer';
export default function GenerateEachCellValue({
cellValue,
@@ -15,13 +18,66 @@ export default function GenerateEachCellValue({
cellTextColor,
cell,
currentState,
+ darkMode,
+ cellWidth,
+ isCellValueChanged,
+ setIsCellValueChanged,
}) {
const mounted = useMounted();
const updateCellValue = useRef();
const isTabKeyPressed = useRef(false);
+ const cellRef = useRef(null);
+
const [showHighlightedCells, setHighlighterCells] = React.useState(globalFilter ? true : false);
+ const [isNullCellClicked, setIsNullCellClicked] = React.useState(false);
const columnTypeAllowToRenderMarkElement = ['text', 'string', 'default', 'number', undefined];
+ const ref = useRef();
+ const [showOverlay, setShowOverlay] = useState(false);
+ const [hovered, setHovered] = useState(false);
+
+ useEffect(() => {
+ if (hovered) {
+ setShowOverlay(true);
+ } else {
+ setShowOverlay(false);
+ }
+ }, [hovered]);
+
+ const _showOverlay =
+ ref?.current &&
+ (ref?.current?.clientWidth < ref?.current?.children[0]?.offsetWidth ||
+ ref?.current?.clientHeight < ref?.current?.children[0]?.offsetHeight);
+
+ const handleCellClick = () => {
+ setIsNullCellClicked(true);
+ if (isEditable && columnTypeAllowToRenderMarkElement.includes(columnType)) {
+ setHighlighterCells(false);
+ }
+ };
+
+ const handleCellBlur = (e) => {
+ e.stopPropagation();
+ if (isTabKeyPressed.current) {
+ isTabKeyPressed.current = false;
+ return;
+ } else {
+ updateCellValue.current = e.target.value;
+ if (!showHighlightedCells && updateCellValue.current === cellValue) {
+ updateCellValue.current = null;
+ setHighlighterCells(true);
+ }
+ }
+ };
+
+ const handleKeyUp = (e) => {
+ if (e.key === 'Tab') {
+ isTabKeyPressed.current = true;
+ setHighlighterCells(false);
+ }
+ };
+
let validationData = {};
+
if (cell.column.isEditable && showHighlightedCells) {
if (cell.column.columnType === 'number') {
validationData = {
@@ -68,10 +124,27 @@ export default function GenerateEachCellValue({
if (mounted && _.isEmpty(rowChangeSet)) {
setHighlighterCells(true);
}
- //In the dependency array to ingnore linting warning, added mounted but it's not working out, any way to avoid ingnoring dependency array
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [rowData, rowChangeSet]);
+ const getOverlay = () => {
+ return (
+ setHovered(true)}
+ onMouseLeave={() => setHovered(false)}
+ style={{ whiteSpace: 'pre-wrap', color: 'var(--text-primary)' }}
+ >
+
+
+ );
+ };
+
let htmlElement = cellValue;
if (cellValue?.toString()?.toLowerCase().includes(globalFilter?.toLowerCase())) {
if (globalFilter) {
@@ -86,59 +159,106 @@ export default function GenerateEachCellValue({
.replace(new RegExp(`(${normReq.join('|')})`, 'gi'), (match) => `${match}`);
}
}
- return (
- {
- if (isEditable && columnTypeAllowToRenderMarkElement.includes(columnType)) {
- setHighlighterCells(false);
- }
- }}
- onBlur={(e) => {
- e.stopPropagation();
- if (isTabKeyPressed.current) {
- isTabKeyPressed.current = false;
- return;
- } else {
- updateCellValue.current = e.target.value;
- //removing _.isEmpty(rowChangeSet) flag from if statement at the end
- if (!showHighlightedCells && updateCellValue.current === cellValue) {
- updateCellValue.current = null;
- setHighlighterCells(true);
- }
- }
- }}
- onKeyUp={(e) => {
- if (e.key === 'Tab') {
- isTabKeyPressed.current = true;
- setHighlighterCells(false);
- }
- }}
- className={`w-100 h-100 ${columnType === 'selector' && 'd-flex align-items-center justify-content-center'}`}
- >
- {!isColumnTypeAction && columnTypeAllowToRenderMarkElement.includes(columnType) && showHighlightedCells ? (
-
+
+ const _renderCellWhenHighlighted = () => {
+ return (
+ {
+ if (!hovered) setHovered(true);
+ }}
+ onMouseOut={() => setHovered(false)}
+ >
+ {cellValue === null ? (
+
+ );
+ };
+
+ const _renderNullCell = () => {
+ if (isEditable) {
+ if (!isNullCellClicked && !updateCellValue.current) {
+ return
- {validationData.validationError}
+
+ )}
+
+ {validationData.validationError}
+
+ {!isColumnTypeAction && columnTypeAllowToRenderMarkElement.includes(columnType) && showHighlightedCells ? (
+ }
+ trigger={_showOverlay && ['hover']}
+ rootClose={true}
+ show={_showOverlay && showOverlay}
+ >
+ {_renderCellWhenHighlighted()}
+
+ ) : cellValue === null ? (
+ _renderNullCell()
) : (
cellRender
)}
diff --git a/frontend/src/Editor/Components/Table/GlobalFilter.jsx b/frontend/src/Editor/Components/Table/GlobalFilter.jsx
index 92f4a1d8fe..4296a1787e 100644
--- a/frontend/src/Editor/Components/Table/GlobalFilter.jsx
+++ b/frontend/src/Editor/Components/Table/GlobalFilter.jsx
@@ -24,14 +24,14 @@ export const GlobalFilter = ({
return (
-
{
setGlobalFilter(undefined);
setValue('');
onComponentOptionChanged(component, 'searchText', '');
}}
>
-
+
);
diff --git a/frontend/src/Editor/Components/Table/NullRenderer/NullRenderer.jsx b/frontend/src/Editor/Components/Table/NullRenderer/NullRenderer.jsx
new file mode 100644
index 0000000000..09def4f993
--- /dev/null
+++ b/frontend/src/Editor/Components/Table/NullRenderer/NullRenderer.jsx
@@ -0,0 +1,13 @@
+import React from 'react';
+import './nullRenderer.scss';
+import classNames from 'classnames';
+
+const NullRenderer = ({ darkMode }) => {
+ return (
+
+ NULL
+
+ );
+};
+
+export default NullRenderer;
diff --git a/frontend/src/Editor/Components/Table/NullRenderer/nullRenderer.scss b/frontend/src/Editor/Components/Table/NullRenderer/nullRenderer.scss
new file mode 100644
index 0000000000..ac423d85a2
--- /dev/null
+++ b/frontend/src/Editor/Components/Table/NullRenderer/nullRenderer.scss
@@ -0,0 +1,17 @@
+.null-renderer-text {
+ font-size: 12px;
+ line-height: 18px;
+ color: var(--text-primary);
+ background-color: var(--surfaces-surface-03);
+ padding: 2px 6px 2px 6px;
+ border-radius: 6px;
+ font-weight: 500;
+}
+
+.table-editor-component-row {
+ &:hover {
+ .null-renderer-text {
+ background-color: var(--surfaces-surface-01) !important
+ }
+ }
+}
\ No newline at end of file
diff --git a/frontend/src/Editor/Components/Table/Pagination.jsx b/frontend/src/Editor/Components/Table/Pagination.jsx
index 0de949c4d9..c2a519c711 100644
--- a/frontend/src/Editor/Components/Table/Pagination.jsx
+++ b/frontend/src/Editor/Components/Table/Pagination.jsx
@@ -78,7 +78,7 @@ export const Pagination = function Pagination({
cursor: pageIndex === 1 ? 'not-allowed' : 'pointer',
}}
leftIcon="cheveronleftdouble"
- fill={`var(--slate12)`}
+ fill={`var(--icons-default)`}
iconWidth="14"
size="md"
disabled={pageIndex === 1}
@@ -103,7 +103,7 @@ export const Pagination = function Pagination({
cursor: pageIndex === 1 || !enablePrevButton ? 'not-allowed' : 'pointer',
}}
leftIcon="cheveronleft"
- fill={`var(--slate12)`}
+ fill={`var(--icons-default)`}
iconWidth="14"
size="md"
disabled={pageIndex === 1 || !enablePrevButton}
@@ -155,7 +155,7 @@ export const Pagination = function Pagination({
cursor: (!autoCanNextPage && !serverSide) || !enableNextButton ? 'not-allowed' : 'pointer',
}}
leftIcon="cheveronright"
- fill={`var(--slate12)`}
+ fill={`var(--icons-default)`}
iconWidth="14"
size="md"
disabled={(!autoCanNextPage && !serverSide) || !enableNextButton}
@@ -180,7 +180,7 @@ export const Pagination = function Pagination({
cursor: !autoCanNextPage && !serverSide ? 'not-allowed' : 'pointer',
}}
leftIcon="cheveronrightdouble"
- fill={`var(--slate12)`}
+ fill={`var(--icons-default)`}
iconWidth="14"
size="md"
onClick={(event) => {
diff --git a/frontend/src/Editor/Components/Table/Radio.jsx b/frontend/src/Editor/Components/Table/Radio.jsx
index 747c54ff92..56b35512b1 100644
--- a/frontend/src/Editor/Components/Table/Radio.jsx
+++ b/frontend/src/Editor/Components/Table/Radio.jsx
@@ -1,30 +1,82 @@
-import React from 'react';
+import React, { useState, useEffect } from 'react';
+import OverlayTrigger from 'react-bootstrap/OverlayTrigger';
-export const Radio = ({ options, value, onChange, readOnly }) => {
+export const Radio = ({ options, value, onChange, readOnly, containerWidth, darkMode }) => {
value = value === undefined ? [] : value;
options = Array.isArray(options) ? options : [];
- return (
-
- }
+ trigger={_showOverlay && ['hover', 'focus']}
+ rootClose={true}
+ show={_showOverlay && showOverlay && !isEditing}
+ >
+ {!isEditable ? (
+
- {options.map((option, index) => (
-
- ))}
+ const elem = document.querySelector('.table-radio-column-list');
+
+ const [showOverlay, setShowOverlay] = useState(false);
+ const [hovered, setHovered] = useState(false);
+
+ useEffect(() => {
+ if (hovered) {
+ setShowOverlay(true);
+ } else {
+ setShowOverlay(false);
+ }
+ }, [hovered]);
+
+ const renderOptions = (options) => {
+ return options.map((option, index) => (
+
+ ));
+ };
+
+ const getOverlay = (options, containerWidth) => {
+ return Array.isArray(options) ? (
+
+ ) : (
+
+ );
+ };
+
+ return (
+ setHovered(true)}
+ onMouseLeave={() => setHovered(false)}
+ >
+ {renderOptions(options)}
- {
+ if (!hovered) setHovered(true);
+ }}
+ onMouseOut={() => setHovered(false)}
+ >
+
+ {renderOptions(options)}
+ {
+ setIsEditing(false);
+ if (cellValue !== e.target.textContent) {
+ handleCellValueChange(cell.row.index, column.key || column.name, e.target.textContent, cell.row.original);
+ }
+ }}
+ onKeyDown={(e) => {
+ if (e.key === 'Enter') {
+ if (cellValue !== e.target.textContent) {
+ handleCellValueChange(cell.row.index, column.key || column.name, e.target.textContent, cell.row.original);
+ }
+ }
+ }}
+ onFocus={(e) => {
+ setIsEditing(true);
+ e.stopPropagation();
+ }}
+ >
+ {cellValue}
+
+ );
+
+ const getOverlay = () => {
+ return (
+ setHovered(true)}
+ onMouseLeave={() => setHovered(false)}
+ style={{ color: 'var(--text-primary)' }}
+ >
+
+ {cellValue}
+
+
+ );
+ };
+
+ const _showOverlay =
+ ref?.current &&
+ (ref?.current?.clientWidth < ref?.current?.children[0]?.offsetWidth ||
+ ref?.current?.clientHeight < ref?.current?.children[0]?.offsetHeight);
+
+ return (
+ <>
+ {
+ if (!hovered) setHovered(true);
+ }}
+ onMouseLeave={() => {
+ setHovered(false);
+ }}
+ ref={ref}
+ >
+
+ {cellValue}
+
+
+ ) : (
+
+
+ )}
+
+ >
+ );
+};
+
+export default String;
diff --git a/frontend/src/Editor/Components/Table/Table.jsx b/frontend/src/Editor/Components/Table/Table.jsx
index 19cb66c7ef..1986aedcc7 100644
--- a/frontend/src/Editor/Components/Table/Table.jsx
+++ b/frontend/src/Editor/Components/Table/Table.jsx
@@ -54,6 +54,8 @@ import Skeleton, { SkeletonTheme } from 'react-loading-skeleton';
import { OverlayTriggerComponent } from './OverlayTriggerComponent';
// eslint-disable-next-line import/no-unresolved
import { diff } from 'deep-object-diff';
+import { isRowInValid } from '../tableUtils';
+import moment from 'moment';
// utilityForNestedNewRow function is used to construct nested object while adding or updating new row when '.' is present in column key for adding new row
const utilityForNestedNewRow = (row) => {
@@ -132,9 +134,15 @@ export function Table({
showAddNewRowButton,
allowSelection,
enablePagination,
+ maxRowHeight,
+ autoHeight,
selectRowOnCellEdit,
+ contentWrapProperty,
+ boxShadow,
+ maxRowHeightValue,
+ borderColor,
+ isMaxRowHeightAuto,
} = loadPropertiesAndStyles(properties, styles, darkMode, component);
-
const updatedDataReference = useRef([]);
const preSelectRow = useRef(false);
const { events: allAppEvents } = useAppInfo();
@@ -171,11 +179,12 @@ export function Table({
const [tableDetails, dispatch] = useReducer(reducer, initialState());
const [hoverAdded, setHoverAdded] = useState(false);
const [generatedColumn, setGeneratedColumn] = useState([]);
+ const [isCellValueChanged, setIsCellValueChanged] = useState(false);
+
const mergeToTableDetails = (payload) => dispatch(reducerActions.mergeToTableDetails(payload));
const mergeToFilterDetails = (payload) => dispatch(reducerActions.mergeToFilterDetails(payload));
const mergeToAddNewRowsDetails = (payload) => dispatch(reducerActions.mergeToAddNewRowsDetails(payload));
const mounted = useMounted();
-
const [resizingColumnId, setResizingColumnId] = useState(null);
const prevDataFromProps = useRef();
@@ -231,6 +240,8 @@ export function Table({
function handleExistingRowCellValueChange(index, key, value, rowData) {
const changeSet = tableDetails.changeSet;
+ setIsCellValueChanged(true);
+
const dataUpdates = tableDetails.dataUpdates || [];
const clonedTableData = _.cloneDeep(tableData);
@@ -268,6 +279,7 @@ export function Table({
}, [JSON.stringify(tableDetails)]);
function handleNewRowCellValueChange(index, key, value, rowData) {
+ setIsCellValueChanged(true);
const changeSet = copyOfTableDetails.current.addNewRowsDetails.newRowsChangeSet || {};
const dataUpdates = copyOfTableDetails.current.addNewRowsDetails.newRowsDataUpdates || {};
let obj = changeSet ? changeSet[index] || {} : {};
@@ -344,6 +356,10 @@ export function Table({
}
}
+ function getExportFileName() {
+ return `${component?.name}_${moment().format('DD-MM-YYYY_HH-mm')}`;
+ }
+
function onPageIndexChanged(page) {
onComponentOptionChanged(component, 'pageIndex', page).then(() => {
onEvent('onPageChanged', tableEvents, { component });
@@ -424,6 +440,9 @@ export function Table({
t,
darkMode,
tableColumnEvents: tableColumnEvents,
+ cellSize: cellSize,
+ maxRowHeightValue: maxRowHeightValue,
+ isMaxRowHeightAuto: isMaxRowHeightAuto,
});
columnData = useMemo(
@@ -517,6 +536,7 @@ export function Table({
);
const textWrapActions = (id) => {
+ //should we remove this
let wrapOption = tableDetails.columnProperties?.find((item) => {
return item?.id == id;
});
@@ -633,6 +653,7 @@ export function Table({
pageCount: -1,
manualPagination: false,
getExportFileBlob,
+ getExportFileName,
disableSortBy: !enabledSort,
manualSortBy: serverSideSort,
stateReducer: (newState, action, prevState) => {
@@ -677,7 +698,7 @@ export function Table({
},
Cell: ({ row }) => {
return (
- {
+ if (!hovered) setHovered(true);
+ }}
+ onMouseLeave={() => setHovered(false)}
+ className={`${!isValid ? 'is-invalid h-100' : ''} ${isEditing ? 'h-100 content-editing' : ''} h-100`}
+ >
+ {_renderString()}
+
+ {validationError}
+
+ {
onComponentClick(id, component, event);
@@ -1091,7 +1116,7 @@ export function Table({
className={`tj-text-xsm ${tableDetails.filterDetails.filtersVisible && 'always-active-btn'}`}
customStyles={{ minWidth: '32px' }}
leftIcon="filter"
- fill={`var(--slate11)`}
+ fill={`var(--icons-default)`}
iconWidth="16"
onClick={(e) => {
if (tableDetails?.filterDetails?.filtersVisible) {
@@ -1160,7 +1185,9 @@ export function Table({
{...getTableProps()}
className={`table table-vcenter table-nowrap ${tableType} ${darkMode && 'table-dark'} ${
tableDetails.addNewRowsDetails.addingNewRows && 'disabled'
- } ${!loadingState && page.length !== 0 && 'h-100'}`}
+ } ${!loadingState && page.length !== 0 && 'h-100'} ${
+ state?.columnResizing?.isResizingColumn ? 'table-resizing' : ''
+ }`}
style={computedStyles}
>
@@ -1285,15 +1312,17 @@ export function Table({
`}
>
- {column.columnType !== 'selector' && isEditable && (
-
+ >
+
+
);
}}
@@ -1372,6 +1408,28 @@ export function Table({
{page.map((row, index) => {
prepareRow(row);
+ let rowProps = { ...row.getRowProps() };
+ const contentWrap = resolveReferences(contentWrapProperty, currentState);
+ const isMaxRowHeightAuto = maxRowHeight === 'auto';
+ rowProps.style.minHeight = cellSize === 'condensed' ? '39px' : '45px'; // 1px is removed to accomodate 1px border-bottom
+ let cellMaxHeight;
+ let cellHeight;
+ if (contentWrap) {
+ cellMaxHeight = isMaxRowHeightAuto
+ ? 'fit-content'
+ : resolveReferences(maxRowHeightValue, currentState) + 'px';
+ rowProps.style.maxHeight = cellMaxHeight;
+ } else {
+ cellMaxHeight = cellSize === 'condensed' ? 40 : 46;
+ cellHeight = cellSize === 'condensed' ? 40 : 46;
+ rowProps.style.maxHeight = cellMaxHeight + 'px';
+ rowProps.style.height = cellHeight + 'px';
+ }
+ const showInvalidError = row.cells.some((cell) => isRowInValid(cell, currentState, changeSet));
+ if (showInvalidError) {
+ rowProps.style.maxHeight = 'fit-content';
+ rowProps.style.height = '';
+ }
return (
setHovered(true)}
+ onMouseLeave={() => setHovered(false)}
+ >
+ {value?.map((tag, index) => {
+ return (
+
+ {renderTag(tag)}
+
+ );
+ })}
+
+ ) : (
+
+ );
+ };
+
return (
- {
- e.stopPropagation();
- if (e.key === 'Tab' && !readOnly) {
- setShowForm(true);
- }
- }}
+
+ {
+ if (!hovered) setHovered(true);
+ }}
+ onMouseOut={() => setHovered(false)}
+ >
+ {/* Container for + button */}
+ {!showForm && !readOnly && (
+
+
);
};
diff --git a/frontend/src/Editor/Components/Table/Text.jsx b/frontend/src/Editor/Components/Table/Text.jsx
new file mode 100644
index 0000000000..8ead00ab0a
--- /dev/null
+++ b/frontend/src/Editor/Components/Table/Text.jsx
@@ -0,0 +1,180 @@
+import React, { useState, useEffect, useRef } from 'react';
+import { validateWidget, determineJustifyContentValue } from '@/_helpers/utils';
+import DOMPurify from 'dompurify';
+import OverlayTrigger from 'react-bootstrap/OverlayTrigger';
+
+const Text = ({
+ isEditable,
+ darkMode,
+ handleCellValueChange,
+ cellTextColor,
+ cellValue,
+ column,
+ currentState,
+ containerWidth,
+ cell,
+ horizontalAlignment,
+ isMaxRowHeightAuto,
+ cellSize,
+ maxRowHeightValue,
+}) => {
+ const validationData = validateWidget({
+ validationObject: {
+ minLength: {
+ value: column.minLength,
+ },
+ maxLength: {
+ value: column.maxLength,
+ },
+ customRule: {
+ value: column.customRule,
+ },
+ },
+ widgetValue: cellValue,
+ currentState,
+ customResolveObjects: { cellValue },
+ });
+ const { isValid, validationError } = validationData;
+ const ref = useRef();
+ const nonEditableCellValueRef = useRef();
+ const [showOverlay, setShowOverlay] = useState(false);
+ const [hovered, setHovered] = useState(false);
+ const cellStyles = {
+ color: cellTextColor ?? 'inherit',
+ };
+ const [isEditing, setIsEditing] = useState(false);
+
+ useEffect(() => {
+ if (hovered) {
+ setShowOverlay(true);
+ } else {
+ setShowOverlay(false);
+ }
+ }, [hovered]);
+
+ const _renderTextArea = () => (
+
+
+ setShowForm(true)}>
+ {'+'}
+
+
+
+ )}
+ {/* Container for renderTags */}
+ {!showForm && (
+
+ {value.map((item, index) => (
+
+ {renderTag(item)}
+
+ ))}
+
+ )}
+ {/* Input element */}
+ {showForm && (
+
+ addTag(e.target.value)}
+ onKeyDown={handleFormKeyDown}
+ />
+
+ )}
+ {
+ setIsEditing(false);
+ if (isEditable && cellValue !== e.target.textContent) {
+ const div = e.target;
+ let content = div.innerHTML;
+ handleCellValueChange(cell.row.index, column.key || column.name, content, cell.row.original);
+ }
+ }}
+ dangerouslySetInnerHTML={{ __html: DOMPurify.sanitize(cellValue) }}
+ onKeyDown={(e) => {
+ e.persist();
+ if (e.key === 'Enter' && !e.shiftKey && isEditable) {
+ const div = e.target;
+ let content = div.innerHTML;
+ handleCellValueChange(cell.row.index, column.key || column.name, content, cell.row.original);
+ }
+ }}
+ onFocus={(e) => {
+ setIsEditing(true);
+ // setShowOverlay(false);
+ e.stopPropagation();
+ }}
+ />
+ );
+
+ const _renderNonEditableData = () => (
+ }
+ trigger={_showOverlay && ['hover']}
+ rootClose={true}
+ show={_showOverlay && showOverlay && !isEditing}
+ >
+
+
+ {cellValue}
+
+
+ );
+
+ const getOverlay = () => {
+ return (
+ setHovered(true)}
+ onMouseLeave={() => setHovered(false)}
+ style={{ whiteSpace: 'pre-wrap', color: 'var(--text-primary)' }}
+ >
+
+
+ );
+ };
+
+ const _showOverlay = isEditable
+ ? ref.current && ref.current?.parentElement?.clientHeight < ref.current?.scrollHeight
+ : ref.current &&
+ (ref.current?.parentElement?.clientWidth < nonEditableCellValueRef.current?.clientWidth ||
+ ref.current?.parentElement?.clientHeight < ref.current?.scrollHeight);
+
+ return (
+ <>
+
+
+
+ >
+ );
+};
+
+export default Text;
diff --git a/frontend/src/Editor/Components/Table/Timepicker.jsx b/frontend/src/Editor/Components/Table/Timepicker.jsx
new file mode 100644
index 0000000000..eca29e1314
--- /dev/null
+++ b/frontend/src/Editor/Components/Table/Timepicker.jsx
@@ -0,0 +1,24 @@
+import React from 'react';
+import moment from 'moment';
+import TimePickerComponent from '@/ToolJetUI/Timepicker/Timepicker';
+
+export const TimePicker = ({ value, onChange, component }) => {
+ const currentDay = moment().format('YYYY-MM-DD');
+ const dateTimeString = `${currentDay} ${value}`;
+ const dateObject = moment(dateTimeString, 'YYYY-MM-DD hh:mm').toDate();
+
+ return (
+ {
+ if (!hovered) setHovered(true);
+ }}
+ onMouseLeave={() => setHovered(false)}
+ ref={ref}
+ className={`${!isValid ? 'is-invalid h-100' : ''} ${isEditing ? 'h-100 content-editing' : ''}`}
+ >
+ {!isEditable ? _renderNonEditableData() : _renderTextArea()}
+
+ {isEditable && {validationError} }
+
+
+ );
+};
diff --git a/frontend/src/Editor/Components/Table/columns/autogenerateColumns.js b/frontend/src/Editor/Components/Table/columns/autogenerateColumns.js
index 1ef59967b1..489fb4a1c7 100644
--- a/frontend/src/Editor/Components/Table/columns/autogenerateColumns.js
+++ b/frontend/src/Editor/Components/Table/columns/autogenerateColumns.js
@@ -77,7 +77,7 @@ export default function autogenerateColumns(
// mapping the keys of first row with one level of nested elements.
const keysOfTableData = generateColumnKeys(firstRow, generateNestedColumns);
- const keysOfExistingColumns = existingColumns.map((column) => column.key || column.name);
+ const keysOfExistingColumns = existingColumns.map((column) => column?.key || column?.name);
const keysFromWhichNewColumnsShouldBeGenerated = _.difference(keysOfTableData, [
...keysOfExistingColumns,
@@ -90,8 +90,8 @@ export default function autogenerateColumns(
]);
const keysOfExistingColumnsThatNeedToPersist = existingColumns
- .filter((column) => !column.autogenerated || keysOfTableData.includes(column.key || column.name))
- .map((column) => column.key || column.name);
+ .filter((column) => !column?.autogenerated || keysOfTableData.includes(column.key || column.name))
+ .map((column) => column?.key || column?.name);
const generatedColumns = keysAndDataTypesToGenerateNewColumns.map(([key, dataType]) => ({
id: uuidv4(),
@@ -103,7 +103,7 @@ export default function autogenerateColumns(
const finalKeys = [...keysFromWhichNewColumnsShouldBeGenerated, ...keysOfExistingColumnsThatNeedToPersist];
const finalColumns = [...existingColumns, ...generatedColumns].filter((column) =>
- finalKeys.includes(column.key || column.name)
+ finalKeys.includes(column?.key || column?.name)
);
setTimeout(() => setProperty('columns', finalColumns), 10);
@@ -112,9 +112,10 @@ export default function autogenerateColumns(
const dataTypeToColumnTypeMapping = {
string: 'string',
number: 'number',
+ boolean: 'boolean',
};
const convertDataTypeToColumnType = (dataType) => {
if (Object.keys(dataTypeToColumnTypeMapping).includes(dataType)) return dataTypeToColumnTypeMapping[dataType];
- else return 'default';
+ else return 'string';
};
diff --git a/frontend/src/Editor/Components/Table/columns/index.jsx b/frontend/src/Editor/Components/Table/columns/index.jsx
index f8189ba141..18ef65ec41 100644
--- a/frontend/src/Editor/Components/Table/columns/index.jsx
+++ b/frontend/src/Editor/Components/Table/columns/index.jsx
@@ -1,14 +1,19 @@
import React from 'react';
import _ from 'lodash';
import SelectSearch from 'react-select-search';
-import { resolveReferences, validateWidget, determineJustifyContentValue } from '@/_helpers/utils';
-import { CustomSelect } from '../CustomSelect';
+import { resolveReferences, validateWidget, determineJustifyContentValue, validateDates } from '@/_helpers/utils';
+import { CustomDropdown } from '../CustomDropdown';
import { Tags } from '../Tags';
import { Radio } from '../Radio';
import { Toggle } from '../Toggle';
import { Datepicker } from '../Datepicker';
import { Link } from '../Link';
import moment from 'moment';
+import { Boolean } from '../Boolean';
+import { CustomSelect } from '../CustomSelect';
+import SolidIcon from '@/_ui/Icon/SolidIcons';
+import Text from '../Text';
+import String from '../String';
export default function generateColumnsData({
columnProperties,
@@ -27,14 +32,15 @@ export default function generateColumnsData({
t,
darkMode,
tableColumnEvents,
+ cellSize,
+ maxRowHeightValue,
}) {
return columnProperties.map((column) => {
if (!column) return;
+ const columnSize = columnSizes[column?.id] || columnSizes[column?.name] || column.columnSize;
- const columnSize = columnSizes[column?.id] || columnSizes[column?.name];
const columnType = column?.columnType;
let sortType = 'alphanumeric';
-
const columnOptions = {};
if (
columnType === 'dropdown' ||
@@ -54,16 +60,35 @@ export default function generateColumnsData({
});
}
}
+ if (columnType === 'select' || columnType === 'newMultiSelect') {
+ columnOptions.selectOptions = [];
+ const useDynamicOptions = resolveReferences(column?.useDynamicOptions, currentState);
+ if (useDynamicOptions) {
+ const dynamicOptions = resolveReferences(column?.dynamicOptions || [], currentState);
+ columnOptions.selectOptions = Array.isArray(dynamicOptions) ? dynamicOptions : [];
+ } else {
+ const options = column?.options ?? [];
+ columnOptions.selectOptions =
+ options?.map((option) => ({
+ label: option.label,
+ value: option.value,
+ })) ?? [];
+ }
+ }
if (columnType === 'datepicker') {
column.isTimeChecked = column.isTimeChecked ? column.isTimeChecked : false;
column.dateFormat = column.dateFormat ? column.dateFormat : 'DD/MM/YYYY';
column.parseDateFormat = column.parseDateFormat ?? column.dateFormat; //backwards compatibility
+ column.isDateSelectionEnabled = column.isDateSelectionEnabled ?? true;
+
sortType = (firstDate, secondDate) => {
const columnKey = column.key || column.name;
// Return -1 if second date is higher, 1 if first date is higher
if (secondDate?.original[columnKey] === '') {
return 1;
- } else if (firstDate?.original[columnKey] === '') return -1;
+ } else if (firstDate?.original[columnKey] === '') {
+ return -1;
+ }
const parsedFirstDate = moment(firstDate?.original[columnKey], column.parseDateFormat);
const parsedSecondDate = moment(secondDate?.original[columnKey], column.parseDateFormat);
@@ -75,7 +100,6 @@ export default function generateColumnsData({
}
};
}
-
const width = columnSize || defaultColumn.width;
return {
id: column.id,
@@ -98,11 +122,19 @@ export default function generateColumnsData({
sortType,
columnVisibility: column?.columnVisibility ?? true,
horizontalAlignment: column?.horizontalAlignment ?? 'left',
- Cell: function ({ cell, isEditable, newRowsChangeSet = null, horizontalAlignment }) {
+ Cell: function ({
+ cell,
+ isEditable,
+ newRowsChangeSet = null,
+ horizontalAlignment,
+ cellTextColor = '',
+ contentWrap = true,
+ autoHeight = true,
+ isMaxRowHeightAuto,
+ }) {
const updatedChangeSet = newRowsChangeSet === null ? changeSet : newRowsChangeSet;
const rowChangeSet = updatedChangeSet ? updatedChangeSet[cell.row.index] : null;
let cellValue = rowChangeSet ? rowChangeSet[column.key || column.name] ?? cell.value : cell.value;
-
const rowData = tableData?.[cell?.row?.index];
if (
cell.row.index === 0 &&
@@ -113,104 +145,133 @@ export default function generateColumnsData({
customResolvables[id] = { ...variablesExposedForPreview[id], rowData };
exposeToCodeHinter((prevState) => ({ ...prevState, ...customResolvables }));
}
- cellValue = cellValue === undefined || cellValue === null ? '' : cellValue;
-
+ cellValue = cellValue === undefined ? '' : cellValue;
switch (columnType) {
case 'string':
case undefined:
case 'default': {
- const textColor = resolveReferences(column.textColor, currentState, '', { cellValue, rowData });
-
- const cellStyles = {
- color: textColor ?? '',
- };
-
- if (isEditable) {
- const validationData = validateWidget({
- validationObject: {
- regex: {
- value: column.regex,
- },
- minLength: {
- value: column.minLength,
- },
- maxLength: {
- value: column.maxLength,
- },
- customRule: {
- value: column.customRule,
- },
- },
- widgetValue: cellValue,
- currentState,
- customResolveObjects: { cellValue },
- });
-
- const { isValid, validationError } = validationData;
- const cellStyles = {
- color: textColor ?? '',
- };
-
- return (
-
- {
- if (e.key === 'Enter') {
- if (e.target.defaultValue !== e.target.value) {
- handleCellValueChange(
- cell.row.index,
- column.key || column.name,
- e.target.value,
- cell.row.original
- );
- }
- }
- }}
- onBlur={(e) => {
- if (e.target.defaultValue !== e.target.value) {
- handleCellValueChange(
- cell.row.index,
- column.key || column.name,
- e.target.value,
- cell.row.original
- );
- }
- }}
- className={`form-control-plaintext form-control-plaintext-sm ${!isValid ? 'is-invalid' : ''}`}
- defaultValue={cellValue}
- onFocus={(e) => e.stopPropagation()}
- />
-
- );
- }
+ const cellTextColor = resolveReferences(column.textColor, currentState, '', { cellValue, rowData });
return (
- {validationError}
-
- {String(cellValue)}
-
+
+ //
+ // );
+ // }
+ // return (
+ // {
+ // if (cellValue !== e.target.textContent) {
+ // handleCellValueChange(
+ // cell.row.index,
+ // column.key || column.name,
+ // e.target.textContent,
+ // cell.row.original
+ // );
+ // }
+ // }}
+ // onKeyDown={(e) => {
+ // if (e.key === 'Enter') {
+ // if (cellValue !== e.target.textContent) {
+ // handleCellValueChange(
+ // cell.row.index,
+ // column.key || column.name,
+ // e.target.textContent,
+ // cell.row.original
+ // );
+ // }
+ // }
+ // }}
+ // onFocus={(e) => e.stopPropagation()}
+ // >
+ // {cellValue}
+ //
+ // {validationError}
+ //
+ // {String(cellValue)}
+ //
+ // );
}
case 'number': {
const textColor = resolveReferences(column.textColor, currentState, '', { cellValue, rowData });
const cellStyles = {
color: textColor ?? '',
+ overflow: 'hidden',
};
if (isEditable) {
const validationData = validateWidget({
validationObject: {
minValue: {
- value: column.minValue,
+ value: column?.minValue,
},
maxValue: {
- value: column.maxValue,
+ value: column?.maxValue,
+ },
+ regex: {
+ value: column?.regex,
+ },
+ customRule: {
+ value: column?.customRule,
},
},
widgetValue: cellValue,
@@ -223,18 +284,62 @@ export default function generateColumnsData({
color: textColor ?? '',
};
+ const handleIncrement = (e) => {
+ e.preventDefault(); // Prevent the default button behavior (form submission, page reload)
+
+ const newValue = (cellValue || 0) + 1;
+ if (!isNaN(newValue)) {
+ handleCellValueChange(cell.row.index, column.key || column.name, Number(newValue), cell.row.original);
+ }
+ };
+
+ const handleDecrement = (e) => {
+ e.preventDefault();
+ const newValue = (cellValue || 0) - 1;
+ if (!isNaN(newValue)) {
+ handleCellValueChange(cell.row.index, column.key || column.name, Number(newValue), cell.row.original);
+ }
+ };
+
+ const allowedDecimalPlaces = column?.decimalPlaces ?? null;
+ const removingExcessDecimalPlaces = (cellValue, allowedDecimalPlaces) => {
+ allowedDecimalPlaces = resolveReferences(allowedDecimalPlaces, currentState);
+ if (cellValue?.toString()?.includes('.')) {
+ const splittedCellValue = cellValue?.toString()?.split('.');
+ const decimalPlacesUnderLimit = splittedCellValue[1]
+ .split('')
+ .splice(0, allowedDecimalPlaces)
+ .join('');
+ cellValue = Number(`${splittedCellValue[0]}.${decimalPlacesUnderLimit}`);
+ }
+ return cellValue;
+ };
+ cellValue = allowedDecimalPlaces
+ ? removingExcessDecimalPlaces(cellValue, allowedDecimalPlaces)
+ : cellValue;
+
return (
-
+
);
}
- case 'text': {
+ case 'text':
return (
-
+ //
{
if (e.key === 'Enter') {
if (e.target.defaultValue !== e.target.value) {
+ const value = allowedDecimalPlaces
+ ? removingExcessDecimalPlaces(e.target.value, allowedDecimalPlaces)
+ : e.target.value;
handleCellValueChange(
cell.row.index,
column.key || column.name,
- Number(e.target.value),
+ Number(value),
cell.row.original
);
}
@@ -242,19 +347,50 @@ export default function generateColumnsData({
}}
onBlur={(e) => {
if (e.target.defaultValue !== e.target.value) {
+ const value = allowedDecimalPlaces
+ ? removingExcessDecimalPlaces(e.target.value, allowedDecimalPlaces)
+ : e.target.value;
handleCellValueChange(
cell.row.index,
column.key || column.name,
- Number(e.target.value),
+ Number(value),
cell.row.original
);
}
}}
onFocus={(e) => e.stopPropagation()}
- className={`form-control-plaintext form-control-plaintext-sm ${!isValid ? 'is-invalid' : ''}`}
+ className={`table-column-type-input-element input-number h-100 ${!isValid ? 'is-invalid' : ''}`}
defaultValue={cellValue}
/>
-
);
}
@@ -269,31 +405,29 @@ export default function generateColumnsData({
{validationError}
+
+
+ handleIncrement(e)}>
+
+ handleDecrement(e)}>
+
+ {validationError}
+
);
- }
- case 'dropdown': {
+ case 'dropdown':
+ case 'select':
+ case 'newMultiSelect': {
const validationData = validateWidget({
validationObject: {
regex: {
@@ -315,22 +449,54 @@ export default function generateColumnsData({
});
const { isValid, validationError } = validationData;
-
return (
-
- {validationError}
+
+ {columnType === 'dropdown' && (
+
);
}
@@ -357,11 +523,11 @@ export default function generateColumnsData({
case 'badges': {
return (
{validationError}
-
);
}
case 'tags': {
return (
-
-
+
+
);
@@ -445,29 +613,112 @@ export default function generateColumnsData({
);
}
case 'datepicker': {
+ const textColor = resolveReferences(column.textColor, currentState, '', { cellValue, rowData });
+ const isTimeChecked = resolveReferences(column?.isTimeChecked, currentState);
+ const isTwentyFourHrFormatEnabled = resolveReferences(column?.isTwentyFourHrFormatEnabled, currentState);
+ const disabledDates = resolveReferences(column?.disabledDates, currentState);
+ const parseInUnixTimestamp = resolveReferences(column?.parseInUnixTimestamp, currentState);
+ const isDateSelectionEnabled = resolveReferences(column?.isDateSelectionEnabled, currentState);
+ const cellStyles = {
+ color: textColor ?? '',
+ };
+ const validationData = validateDates({
+ validationObject: {
+ minDate: {
+ value: isDateSelectionEnabled ? column.minDate : undefined,
+ },
+ maxDate: {
+ value: isDateSelectionEnabled ? column.maxDate : undefined,
+ },
+ minTime: {
+ value: isTimeChecked ? column.minTime : undefined,
+ },
+ maxTime: {
+ value: isTimeChecked ? column.maxTime : undefined,
+ },
+ parseDateFormat: {
+ value: column.parseDateFormat,
+ },
+ isTwentyFourHrFormatEnabled: {
+ value: isTwentyFourHrFormatEnabled,
+ },
+ isDateSelectionEnabled: {
+ value: isDateSelectionEnabled,
+ },
+ customRule: {
+ value: column.customRule,
+ },
+ },
+ widgetValue: cellValue,
+ currentState,
+ customResolveObjects: { cellValue },
+ });
+
+ const { isValid, validationError } = validationData;
return (
-
{cellValue && (
@@ -417,6 +584,7 @@ export default function generateColumnsData({
onChange={(value) => {
handleCellValueChange(cell.row.index, column.key || column.name, value, cell.row.original);
}}
+ containerWidth={width}
/>
+ {validationError}
+ )}
-
+
+
+ );
+ }
+ case 'boolean': {
+ return (
+
+
);
}
diff --git a/frontend/src/Editor/Components/Table/datepicker.scss b/frontend/src/Editor/Components/Table/datepicker.scss
new file mode 100644
index 0000000000..d09a6f59c6
--- /dev/null
+++ b/frontend/src/Editor/Components/Table/datepicker.scss
@@ -0,0 +1,418 @@
+.tj-datepicker-widget {
+ &.react-datepicker-popper {
+ z-index: 3;
+ }
+ border-radius: 6px !important;
+ border: 1px solid var(--slate5) !important;
+ box-shadow: 0px 32px 64px -12px rgba(16, 24, 40, 0.14);
+ width: 250px;
+ padding: 0px;
+}
+
+
+
+.tj-timepicker-widget {
+ &.react-datepicker-popper {
+ background-color: unset;
+ z-index: 3;
+ }
+
+ .react-datepicker__time-container {
+ right: 0px
+ }
+
+}
+
+.react-datepicker__header {
+ background-color: var(--surfaces-surface-01);
+ padding: 6px 0px;
+ border: none;
+}
+
+.tj-datepicker-widget-left {
+ position: absolute;
+ left: 10px;
+}
+
+.tj-datepicker-widget-right {
+ position: absolute;
+ right: 10px;
+}
+
+.tj-datepicker-widget-arrows {
+ box-shadow: 0px 1px 0px 0px #0000000B;
+ border: 1px solid var(--borders-default);
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ padding: 4px;
+ background-color: var(--surfaces-surface-01) !important;
+}
+
+.react-datepicker__navigation-icon--previous {
+ right: 0px;
+ top: 3px;
+}
+
+.react-datepicker__navigation-icon--next {
+ left: 0px;
+ top: 3px;
+}
+
+.react-datepicker {
+ width: 250px;
+ border: none;
+ background-color: var(--surfaces-surface-01);
+}
+
+.dark-theme {
+ .react-datepicker {
+ background-color: #232e3c;
+ }
+
+}
+
+
+.react-datepicker__month {
+ margin: 6px
+}
+
+.react-datepicker__navigation--next {
+ right: 14px;
+}
+
+.react-datepicker__navigation--previous {
+ left: 14px;
+}
+
+.react-datepicker__week,
+.react-datepicker__day-names {
+ display: flex;
+ justify-content: space-between;
+}
+
+.react-datepicker__day-names {
+ .react-datepicker__day-name {
+ color: var(--text-placeholder)
+ }
+}
+
+.react-datepicker__day--selected {
+ border-radius: 8px;
+ background-color: var(--primary-brand) !important;
+ color: var(--surfaces-surface-01) !important
+}
+
+.react-datepicker__navigation-icon::before {
+ border-color: #000;
+ border-width: 1px 1px 0 0;
+}
+
+.react-datepicker__day {
+ font-size: 12px;
+ font-style: normal;
+ font-weight: 500;
+ color: var(--text-primary);
+
+ &:hover {
+ background-color: var(--interactive-overlays-fill-hover);
+ border-radius: 8px;
+ }
+}
+
+.react-datepicker__day--today {
+ border: 1px solid var(--primary-brand);
+ border-radius: 8px;
+ background-color: var(--surfaces-surface-01);
+}
+
+.react-datepicker__day--keyboard-selected {
+ background-color: var(--surfaces-surface-01);
+}
+
+.react-datepicker-time__input {
+
+ input {
+ width: 205px !important;
+ height: 32px !important;
+ border-radius: 6px;
+ background: #FFF;
+ box-shadow: 0px 32px 64px -12px rgba(16, 24, 40, 0.14);
+ border: 1px solid var(--tj-text-input-widget-border-default);
+ padding-left: 10px;
+
+ }
+}
+
+.datepicker-widget {
+ .input-field {
+ min-height: 32px;
+ padding: 0;
+ padding-left: 2px;
+ }
+}
+
+input[type="time"] {
+ position: relative;
+}
+
+input[type="time"]::-webkit-calendar-picker-indicator {
+ display: block;
+ top: 0;
+ right: 0;
+ height: 100%;
+ width: 100%;
+ position: absolute;
+ background: transparent;
+}
+
+.react-datepicker-popper,
+.tj-datepicker-widget {
+ background-color: var(--surfaces-surface-01);
+}
+
+.react-datepicker__month-container {
+ float: inherit;
+}
+
+.react-datepicker__time-container .react-datepicker__time {
+ color: var(--text-primary);
+ background: var(--surfaces-surface-01);
+}
+
+.dark-theme {
+
+ .react-datepicker-popper,
+ .tj-datepicker-widget {
+ background-color: #232e3c;
+ }
+
+ .react-datepicker__time-container,
+ .react-datepicker__time-container .react-datepicker__time {
+ background-color: #232e3c;
+ color: #fff
+ }
+
+ .react-datepicker__header {
+ background-color: #232e3c;
+ color: #fff
+ }
+
+ .react-datepicker__month-container {
+ background-color: #232e3c;
+ color: #fff;
+
+ .react-datepicker__month {
+ background-color: #232e3c;
+
+ .react-datepicker__day {
+ color: #fff
+ }
+ }
+
+ }
+
+
+ li.react-datepicker__time-list-item:hover {
+ background-color: #CCD1D533 !important;
+ }
+
+ select {
+ color: #fff
+ }
+}
+
+
+.react-datepicker-popper[data-placement^=bottom] {
+ padding-top: 0px !important;
+ margin-top: 12px;
+}
+
+.react-datepicker__day {
+ margin: 0px;
+}
+
+.react-datepicker__week {
+ padding: 4px;
+}
+
+.react-datepicker__day-name,
+.react-datepicker__day,
+.react-datepicker__time-name {
+ margin: 0px;
+ // width: 32px;
+}
+
+.react-datepicker__day-names {
+ margin: 0rem 0.4rem;
+ height: 24px;
+ font-size: 12px;
+ font-style: normal;
+ font-weight: 400;
+ line-height: 20px;
+}
+
+.tj-datepicker-widget-month-selector, .tj-datepicker-widget-year-selector {
+ appearance: none;
+ -moz-appearance: none;
+ -webkit-appearance: none;
+ padding-right: 1em;
+ /* Add some padding on the right to create space for custom arrow */
+ background-image: url('data:image/svg+xml;utf8,');
+ /* Add a custom arrow (you can use your own SVG) */
+ background-repeat: no-repeat;
+ background-position: right center;
+ border: none;
+ /* Remove the default border */
+ padding: 8px;
+ /* Adjust padding as needed */
+ cursor: pointer;
+ /* Add pointer cursor for better usability */
+ background: none;
+ padding: 0px;
+ height: 24px;
+ text-align: center;
+ // margin-right: 6px !important;
+ color: var(--text-primary);
+ font-weight: 500;
+
+}
+
+/* Optional: Style when select is focused */
+select:focus {
+ outline: none;
+ /* Remove default focus outline */
+ // box-shadow: 0 0 5px rgba(0, 0, 0, 0.3);
+ /* Add a subtle box-shadow when focused */
+}
+
+.table-column-datepicker-input {
+ border: 1px solid transparent;
+ background: inherit;
+ text-align: left;
+ width: 100%;
+}
+
+.table-dark {
+ .table-column-datepicker-input {
+ color: #fff
+ }
+}
+
+.react-datepicker-wrapper {
+ width: 100%;
+}
+
+.react-time-picker__wrapper {
+ border: none !important;
+}
+
+
+.timepicker-wrapper {
+
+ .react-datepicker-wrapper {
+ border: none !important;
+ }
+}
+
+.react-datepicker__triangle {
+ display: none;
+}
+
+.react-datepicker__time-container {
+ position: absolute;
+ right: -86px;
+ top: 0;
+ height: 271.12px;
+ background-color: #fff;
+ border: 1px solid var(--slate5);
+ box-shadow: 0px 32px 64px -12px rgba(16, 24, 40, 0.14);
+ border-radius: 0.3rem;
+
+ .react-datepicker__header {
+ padding-top: 13px;
+ padding-bottom: 7px;
+ border-bottom: 1px solid var(--slate7)
+ }
+
+ .react-datepicker__time-box {
+ width: 75px;
+ margin: 0px;
+ }
+
+ .react-datepicker__time-list {
+ // padding: 4px 6px;
+
+ }
+
+ .react-datepicker__time-list-item {
+ height: 28px !important;
+ color: var(--text-primary) !important;
+ font-weight: 400 !important;
+ padding: 0px !important;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ }
+
+ .react-datepicker__time-list-item--selected {
+ border-radius: 8px;
+ background-color: #4368E3 !important;
+ color: var(--surfaces-surface-01) !important
+ }
+}
+
+.react-datepicker-time__header {
+ color: #6A727C;
+ font-weight: 400;
+}
+
+.react-datepicker--time-only {
+ width: 66px;
+}
+
+.tj-datepicker-widget-hidden {
+ height: 0px;
+
+ .react-datepicker--time-only {
+ top: -16px;
+ }
+}
+
+.table-column-datepicker-input-container {
+ position: relative;
+}
+
+.table-column-datepicker-input-icon {
+ position: absolute;
+ top: 50%;
+ right: 1rem;
+ transform: translateY(-50%)
+}
+
+.tj-datepicker-widget-month-selector,
+.tj-datepicker-widget-year-selector {
+ &:hover {
+ background-color: var(--interactive-overlays-fill-hover);
+ border-radius: 6px;
+ }
+}
+
+.react-datepicker__day--outside-month {
+ color: var(--text-disabled);
+}
+
+.react-datepicker__day--excluded {
+ color: var(--text-disabled);
+ pointer-events: none;
+}
+
+td {
+ &.isEditable {
+ &:hover, &:focus-within {
+ .table-column-datepicker-input {
+ padding-right: 40px;
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/frontend/src/Editor/Components/Table/load-properties-and-styles.js b/frontend/src/Editor/Components/Table/load-properties-and-styles.js
index ce4f8ab5b3..a140b068b0 100644
--- a/frontend/src/Editor/Components/Table/load-properties-and-styles.js
+++ b/frontend/src/Editor/Components/Table/load-properties-and-styles.js
@@ -57,10 +57,10 @@ export default function loadPropertiesAndStyles(properties, styles, darkMode, co
const borderRadius = styles.borderRadius ?? 0;
- const widgetVisibility = styles?.visibility ?? true;
+ const widgetVisibility = properties?.visibility ?? true;
const parsedWidgetVisibility = widgetVisibility;
- const disabledState = styles?.disabledState ?? false;
+ const disabledState = properties?.disabledState ?? false;
const parsedDisabledState = disabledState;
const actionButtonRadius = styles.actionButtonRadius ? parseFloat(styles.actionButtonRadius) : 0;
@@ -76,7 +76,12 @@ export default function loadPropertiesAndStyles(properties, styles, darkMode, co
const showAddNewRowButton = properties?.showAddNewRowButton ?? true;
const allowSelection = properties?.allowSelection ?? (showBulkSelector || highlightSelectedRow) ? true : false;
const defaultSelectedRow = properties?.defaultSelectedRow ?? { id: 1 };
+ const maxRowHeight = styles?.maxRowHeight ?? 'auto';
+ const maxRowHeightValue = styles?.maxRowHeightValue ?? 80;
+ const boxShadow = styles?.boxShadow;
const selectRowOnCellEdit = properties?.selectRowOnCellEdit ?? true;
+ const contentWrapProperty = styles?.contentWrap ?? true;
+ const borderColor = styles?.borderColor ?? 'var(--borders-weak-disabled)';
return {
color,
@@ -109,6 +114,11 @@ export default function loadPropertiesAndStyles(properties, styles, darkMode, co
defaultSelectedRow,
showAddNewRowButton,
allowSelection,
+ maxRowHeight,
+ maxRowHeightValue,
selectRowOnCellEdit,
+ contentWrapProperty,
+ boxShadow,
+ borderColor,
};
}
diff --git a/frontend/src/Editor/Components/Text.jsx b/frontend/src/Editor/Components/Text.jsx
index 26d8e6f631..61473ba8eb 100644
--- a/frontend/src/Editor/Components/Text.jsx
+++ b/frontend/src/Editor/Components/Text.jsx
@@ -1,5 +1,6 @@
import React, { useState, useEffect } from 'react';
import DOMPurify from 'dompurify';
+// eslint-disable-next-line import/no-unresolved
import Markdown from 'react-markdown';
import './text.scss';
import Loader from '@/ToolJetUI/Loader/Loader';
diff --git a/frontend/src/Editor/Components/TextInput.jsx b/frontend/src/Editor/Components/TextInput.jsx
index c729baa3eb..add215f628 100644
--- a/frontend/src/Editor/Components/TextInput.jsx
+++ b/frontend/src/Editor/Components/TextInput.jsx
@@ -58,18 +58,33 @@ export const TextInput = function TextInput({
const computedStyles = {
height: height == 36 ? (padding == 'default' ? '36px' : '40px') : padding == 'default' ? height : height + 4,
borderRadius: `${borderRadius}px`,
- color: darkMode && textColor === '#11181C' ? '#ECEDEE' : textColor,
+ color: textColor !== '#1B1F24' ? textColor : disable || loading ? 'var(--text-disabled)' : 'var(--text-primary)',
borderColor: isFocused
- ? accentColor
- : ['#D7DBDF'].includes(borderColor)
- ? darkMode
- ? '#6D757D7A'
- : '#6A727C47'
- : borderColor,
+ ? accentColor != '4368E3'
+ ? accentColor
+ : 'var(--primary-accent-strong)'
+ : borderColor != '#CCD1D5'
+ ? borderColor
+ : disable || loading
+ ? '1px solid var(--borders-disabled-on-white)'
+ : 'var(--borders-default)',
'--tblr-input-border-color-darker': tinycolor(borderColor).darken(24).toString(),
- backgroundColor: darkMode && ['#fff'].includes(backgroundColor) ? '#313538' : backgroundColor,
+ backgroundColor:
+ backgroundColor != '#fff'
+ ? backgroundColor
+ : disable || loading
+ ? darkMode
+ ? 'var(--surfaces-app-bg-default)'
+ : 'var(--surfaces-surface-03)'
+ : 'var(--surfaces-surface-01)',
boxShadow: boxShadow,
- padding: styles.iconVisibility ? '8px 10px 8px 29px' : '8px 10px 8px 10px',
+ padding: styles.iconVisibility
+ ? height < 20
+ ? '0px 10px 0px 29px'
+ : '8px 10px 8px 29px'
+ : height < 20
+ ? '0px 10px'
+ : '8px 10px',
overflow: 'hidden',
textOverflow: 'ellipsis',
};
@@ -238,8 +253,7 @@ export const TextInput = function TextInput({
const renderInput = () => (
<>
0 && width > 0) || (auto && width == 0 && label && label?.length != 0)
? `${labelWidth + 11}px`
- : '11px', //23 :: is 10 px inside the input + 1 px border + 12px margin right
+ : '11px', //11 :: is 10 px inside the input + 1 px border + 12px margin right
position: 'absolute',
top: `${
defaultAlignment === 'side'
@@ -289,7 +303,7 @@ export const TextInput = function TextInput({
: '50%'
}`,
transform: ' translateY(-50%)',
- color: iconColor,
+ color: iconColor !== '#CFD3D859' ? iconColor : 'var(--icons-weak-disabled)',
zIndex: 3,
}}
stroke={1.5}
@@ -300,7 +314,7 @@ export const TextInput = function TextInput({
ref={textInputRef}
className={`tj-text-input-widget ${
!isValid && showValidationError ? 'is-invalid' : ''
- } validation-without-icon ${darkMode && 'dark-theme-placeholder'}`}
+ } validation-without-icon`}
onKeyUp={(e) => {
if (e.key === 'Enter') {
setValue(e.target.value);
@@ -338,11 +352,13 @@ export const TextInput = function TextInput({
{showValidationError && visibility && (
{showValidationError && validationError}
diff --git a/frontend/src/Editor/Components/tableUtils.js b/frontend/src/Editor/Components/tableUtils.js
new file mode 100644
index 0000000000..eed5da6ab2
--- /dev/null
+++ b/frontend/src/Editor/Components/tableUtils.js
@@ -0,0 +1,79 @@
+import { validateWidget, validateDates } from '@/_helpers/utils';
+
+export const isRowInValid = (cell, currentState, changeSet) => {
+ const rowChangeSet = changeSet ? changeSet[cell.row.index] : null;
+ const key = cell.column.key || cell.column.Header;
+ const cellValue = rowChangeSet ? rowChangeSet[key] || cell.value : cell.value;
+ let validationData = {};
+ if (cell.column.isEditable) {
+ if (cell.column.columnType === 'number') {
+ validationData = {
+ ...validateWidget({
+ validationObject: {
+ minValue: {
+ value: cell.column.minValue,
+ },
+ maxValue: {
+ value: cell.column.maxValue,
+ },
+ },
+ widgetValue: cellValue,
+ currentState,
+ customResolveObjects: { cellValue },
+ }),
+ };
+ }
+ if (['string', undefined, 'default', 'text'].includes(cell.column.columnType)) {
+ validationData = {
+ ...validateWidget({
+ validationObject: {
+ regex: {
+ value: cell.column.regex,
+ },
+ minLength: {
+ value: cell.column.minLength,
+ },
+ maxLength: {
+ value: cell.column.maxLength,
+ },
+ customRule: {
+ value: cell.column.customRule,
+ },
+ },
+ widgetValue: cellValue,
+ currentState,
+ customResolveObjects: { cellValue: cellValue },
+ }),
+ };
+ }
+ }
+
+ if (cell.column.columnType === 'datepicker') {
+ validationData = {
+ ...validateDates({
+ validationObject: {
+ minDate: {
+ value: cell.column.minDate,
+ },
+ maxDate: {
+ value: cell.column.maxDate,
+ },
+ minTime: {
+ value: cell.column.minTime,
+ },
+ maxTime: {
+ value: cell.column.maxTime,
+ },
+ parseDateFormat: {
+ value: cell.column.parseDateFormat,
+ },
+ },
+ widgetValue: cellValue,
+ currentState,
+ customResolveObjects: { cellValue },
+ }),
+ };
+ }
+
+ return validationData.isValid === false;
+};
diff --git a/frontend/src/Editor/Container.jsx b/frontend/src/Editor/Container.jsx
index 4e5e339e06..1fe5b52b83 100644
--- a/frontend/src/Editor/Container.jsx
+++ b/frontend/src/Editor/Container.jsx
@@ -1,5 +1,5 @@
/* eslint-disable import/no-named-as-default */
-import React, { useCallback, useState, useEffect, useRef, useMemo } from 'react';
+import React, { useCallback, useState, useEffect, useRef, useMemo, useContext } from 'react';
import cx from 'classnames';
import { useDrop, useDragLayer } from 'react-dnd';
import { ItemTypes } from './ItemTypes';
@@ -17,8 +17,10 @@ import { addComponents, addNewWidgetToTheEditor } from '@/_helpers/appUtils';
import { useCurrentState } from '@/_stores/currentStateStore';
import { useAppVersionStore } from '@/_stores/appVersionStore';
import { useEditorStore } from '@/_stores/editorStore';
+import { useSuperStore } from '@/_stores/superStore';
import { useAppInfo } from '@/_stores/appDataStore';
import { shallow } from 'zustand/shallow';
+import { ModuleContext } from '../_contexts/ModuleContext';
import _ from 'lodash';
// eslint-disable-next-line import/no-unresolved
import { diff } from 'deep-object-diff';
@@ -42,16 +44,19 @@ export const Container = ({
zoomLevel,
removeComponent,
deviceWindowWidth,
- darkMode,
socket,
handleUndo,
handleRedo,
sideBarDebugger,
currentPageId,
+ darkMode,
}) => {
// Dont update first time to skip
// redundant save on app definition load
const firstUpdate = useRef(true);
+
+ const moduleName = useContext(ModuleContext);
+
const { showComments, currentLayout } = useEditorStore(
(state) => ({
showComments: state?.showComments,
@@ -341,7 +346,8 @@ export const Container = ({
let newBoxes = { ...boxes };
- for (const selectedComponent of useEditorStore.getState().selectedComponents) {
+ for (const selectedComponent of useSuperStore.getState().modules[moduleName].useEditorStore.getState()
+ .selectedComponents) {
newBoxes = produce(newBoxes, (draft) => {
if (draft[selectedComponent.id]) {
const topOffset = draft[selectedComponent.id].layouts[currentLayout].top;
diff --git a/frontend/src/Editor/DataSourceManager/DataSourceManager.jsx b/frontend/src/Editor/DataSourceManager/DataSourceManager.jsx
index 22f79e415b..d06384a7bd 100644
--- a/frontend/src/Editor/DataSourceManager/DataSourceManager.jsx
+++ b/frontend/src/Editor/DataSourceManager/DataSourceManager.jsx
@@ -21,14 +21,17 @@ import { withTranslation, useTranslation } from 'react-i18next';
import { camelizeKeys, decamelizeKeys } from 'humps';
import { ButtonSolid } from '@/_ui/AppButton/AppButton';
import SolidIcon from '@/_ui/Icon/SolidIcons';
-import { useAppVersionStore } from '@/_stores/appVersionStore';
import { ConfirmDialog } from '@/_components';
import { deepEqual } from '../../_helpers/utils';
import { shallow } from 'zustand/shallow';
import { useDataSourcesStore } from '../../_stores/dataSourcesStore';
import { withRouter } from '@/_hoc/withRouter';
+import { useSuperStore } from '@/_stores/superStore';
+import { ModuleContext } from '@/_contexts/ModuleContext';
class DataSourceManagerComponent extends React.Component {
+ static contextType = ModuleContext;
+
constructor(props) {
super(props);
@@ -38,7 +41,6 @@ class DataSourceManagerComponent extends React.Component {
let options = {};
let dataSourceMeta = {};
let datasourceName = '';
-
if (props.selectedDataSource) {
selectedDataSource = props.selectedDataSource;
options = selectedDataSource.options;
@@ -197,7 +199,8 @@ class DataSourceManagerComponent extends React.Component {
const name = selectedDataSource.name;
const kind = selectedDataSource.kind;
const pluginId = selectedDataSourcePluginId;
- const appVersionId = useAppVersionStore?.getState()?.editingVersion?.id;
+ const appVersionId = useSuperStore.getState().modules[this.context].useAppVersionStore?.getState()
+ ?.editingVersion?.id;
const currentEnvironment = this.props.currentEnvironment?.id;
const scope = this.state?.scope || selectedDataSource?.scope;
diff --git a/frontend/src/Editor/DraggableBox.jsx b/frontend/src/Editor/DraggableBox.jsx
index bed9b7601b..f50c4c19a2 100644
--- a/frontend/src/Editor/DraggableBox.jsx
+++ b/frontend/src/Editor/DraggableBox.jsx
@@ -303,7 +303,7 @@ export const DraggableBox = React.memo(
setDragging(false);
onDragStop(e, id, direction, currentLayout, layoutData);
}}
- cancel={`div.table-responsive.jet-data-table, div.calendar-widget, div.text-input, .textarea, .map-widget, .range-slider, .kanban-container, div.real-canvas`}
+ cancel={`div.table-responsive.jet-data-table, div.calendar-widget, div.text-input, .textarea, .map-widget, .range-slider, .kanban-container, div.real-canvas, .overlay-cell-table`}
onResizeStop={(e, direction, ref, d, position) => {
setResizing(false);
onResizeStop(id, e, direction, ref, d, position);
diff --git a/frontend/src/Editor/Editor.jsx b/frontend/src/Editor/Editor.jsx
index cc904708a5..6b8eb2ac13 100644
--- a/frontend/src/Editor/Editor.jsx
+++ b/frontend/src/Editor/Editor.jsx
@@ -1,4 +1,4 @@
-import React, { useEffect, useLayoutEffect, useRef, useState } from 'react';
+import React, { useEffect, useLayoutEffect, useRef, useState, useContext } from 'react';
import {
appService,
authenticationService,
@@ -32,6 +32,7 @@ import {
buildComponentMetaDefinition,
} from '@/_helpers/appUtils';
import { Confirm } from './Viewer/Confirm';
+// eslint-disable-next-line import/no-unresolved
import { Tooltip as ReactTooltip } from 'react-tooltip';
import CommentNotifications from './CommentNotifications';
import { WidgetManager } from './WidgetManager';
@@ -54,7 +55,6 @@ import { ReleasedVersionError } from './AppVersionsManager/ReleasedVersionError'
import { useDataSourcesStore } from '@/_stores/dataSourcesStore';
import { useDataQueriesStore } from '@/_stores/dataQueriesStore';
import { useAppVersionStore, useAppVersionActions, useAppVersionState } from '@/_stores/appVersionStore';
-import { useQueryPanelStore } from '@/_stores/queryPanelStore';
import { useCurrentStateStore, useCurrentState, getCurrentState } from '@/_stores/currentStateStore';
import { computeAppDiff, computeComponentPropertyDiff, isParamFromTableColumn, resetAllStores } from '@/_stores/utils';
import { setCookie } from '@/_helpers/cookie';
@@ -64,11 +64,14 @@ import { useMounted } from '@/_hooks/use-mount';
import EditorSelecto from './EditorSelecto';
// eslint-disable-next-line import/no-unresolved
import { diff } from 'deep-object-diff';
-
+import useAppDarkMode from '@/_hooks/useAppDarkMode';
import useDebouncedArrowKeyPress from '@/_hooks/useDebouncedArrowKeyPress';
import { getQueryParams } from '@/_helpers/routes';
import RightSidebarTabManager from './RightSidebarTabManager';
import { shallow } from 'zustand/shallow';
+import { ModuleContext } from '../_contexts/ModuleContext';
+import { useSuperStore } from '../_stores/superStore';
+import cx from 'classnames';
setAutoFreeze(false);
enablePatches();
@@ -76,6 +79,7 @@ enablePatches();
const decimalToHex = (alpha) => (alpha === 0 ? '00' : Math.round(255 * alpha).toString(16));
const EditorComponent = (props) => {
+ const moduleName = useContext(ModuleContext);
const { socket } = createWebsocketConnection(props?.params?.id);
const mounted = useMounted();
@@ -170,6 +174,7 @@ const EditorComponent = (props) => {
const [showPageDeletionConfirmation, setShowPageDeletionConfirmation] = useState(null);
const [isDeletingPage, setIsDeletingPage] = useState(false);
+ const { isAppDarkMode, appMode, onAppModeChange } = useAppDarkMode();
const [undoStack, setUndoStack] = useState([]);
const [redoStack, setRedoStack] = useState([]);
@@ -214,18 +219,21 @@ const EditorComponent = (props) => {
updateState({
currentUser: appUserDetails,
});
- useCurrentStateStore.getState().actions.setCurrentState({
- globals: {
- ...currentState.globals,
- theme: { name: props?.darkMode ? 'dark' : 'light' },
- urlparams: JSON.parse(JSON.stringify(queryString.parse(props.location.search))),
- currentUser: userVars,
- /* Constant value.it will only change for viewer */
- mode: {
- value: 'edit',
+ useSuperStore
+ .getState()
+ .modules[moduleName].useCurrentStateStore.getState()
+ .actions.setCurrentState({
+ globals: {
+ ...currentState.globals,
+ theme: { name: props?.darkMode ? 'dark' : 'light' },
+ urlparams: JSON.parse(JSON.stringify(queryString.parse(props.location.search))),
+ currentUser: userVars,
+ /* Constant value.it will only change for viewer */
+ mode: {
+ value: 'edit',
+ },
},
- },
- });
+ });
}
});
@@ -237,8 +245,9 @@ const EditorComponent = (props) => {
socket && socket?.close();
subscription.unsubscribe();
if (config.ENABLE_MULTIPLAYER_EDITING) props?.provider?.disconnect();
- useEditorStore.getState().actions.setIsEditorActive(false);
+ useSuperStore.getState().modules[moduleName].useEditorStore.getState().actions.setIsEditorActive(false);
prevAppDefinition.current = null;
+ props.setEditorOrViewer('');
};
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
@@ -255,11 +264,11 @@ const EditorComponent = (props) => {
if (mounted && didAppDefinitionChanged && currentPageId) {
const components = appDefinition?.pages[currentPageId]?.components || {};
- computeComponentState(components);
+ computeComponentState(components, moduleName);
if (appDiffOptions?.skipAutoSave === true) return;
- if (useEditorStore.getState().isUpdatingEditorStateInProcess) {
+ if (useSuperStore.getState().modules[moduleName].useEditorStore.getState().isUpdatingEditorStateInProcess) {
autoSave();
}
}
@@ -269,7 +278,7 @@ const EditorComponent = (props) => {
useEffect(
() => {
const components = appDefinition?.pages?.[currentPageId]?.components || {};
- computeComponentState(components);
+ computeComponentState(components, moduleName);
},
// eslint-disable-next-line react-hooks/exhaustive-deps
[currentPageId]
@@ -296,7 +305,7 @@ const EditorComponent = (props) => {
useEffect(() => {
if (mounted) {
- useCurrentStateStore.getState().actions.setCurrentState({
+ useSuperStore.getState().modules[moduleName].useCurrentStateStore.getState().actions.setCurrentState({
layout: currentLayout,
});
}
@@ -321,12 +330,14 @@ const EditorComponent = (props) => {
const getEditorRef = () => {
const editorRef = {
- appDefinition: useEditorStore.getState().appDefinition,
- queryConfirmationList: useEditorStore.getState().queryConfirmationList,
+ appDefinition: useSuperStore.getState().modules[moduleName].useEditorStore.getState().appDefinition,
+ queryConfirmationList: useSuperStore.getState().modules[moduleName].useEditorStore.getState()
+ .queryConfirmationList,
updateQueryConfirmationList: updateQueryConfirmationList,
navigate: props.navigate,
switchPage: switchPage,
- currentPageId: useEditorStore.getState().currentPageId,
+ currentPageId: useSuperStore.getState().modules[moduleName].useEditorStore.getState().currentPageId,
+ moduleName,
};
return editorRef;
};
@@ -356,7 +367,7 @@ const EditorComponent = (props) => {
}
});
- useCurrentStateStore.getState().actions.setCurrentState({
+ useSuperStore.getState().modules[moduleName].useCurrentStateStore.getState().actions.setCurrentState({
server: server_variables,
client: client_variables,
});
@@ -372,7 +383,7 @@ const EditorComponent = (props) => {
orgConstants[constant.name] = constantValue;
});
- useCurrentStateStore.getState().actions.setCurrentState({
+ useSuperStore.getState().modules[moduleName].useCurrentStateStore.getState().actions.setCurrentState({
constants: orgConstants,
});
});
@@ -449,7 +460,7 @@ const EditorComponent = (props) => {
const $componentDidMount = async () => {
window.addEventListener('message', handleMessage);
-
+ props.setEditorOrViewer('editor');
await fetchApp(props.params.pageHandle, true);
await fetchApps(0);
await fetchOrgEnvironmentVariables();
@@ -471,18 +482,22 @@ const EditorComponent = (props) => {
};
const fetchDataQueries = async (id, selectFirstQuery = false, runQueriesOnAppLoad = false) => {
- await useDataQueriesStore
+ await useSuperStore
.getState()
- .actions.fetchDataQueries(id, selectFirstQuery, runQueriesOnAppLoad, getEditorRef());
+ .modules[moduleName].useDataQueriesStore.getState()
+ .actions.fetchDataQueries(id, selectFirstQuery, runQueriesOnAppLoad, getEditorRef(), moduleName);
};
const fetchDataSources = (id) => {
- useDataSourcesStore.getState().actions.fetchDataSources(id);
+ useSuperStore.getState().modules[moduleName].useDataSourcesStore.getState().actions.fetchDataSources(id);
};
const fetchGlobalDataSources = () => {
const { current_organization_id: organizationId } = currentUser;
- useDataSourcesStore.getState().actions.fetchGlobalDataSources(organizationId);
+ useSuperStore
+ .getState()
+ .modules[moduleName].useDataSourcesStore.getState()
+ .actions.fetchGlobalDataSources(organizationId);
};
const onVersionDelete = () => {
@@ -555,18 +570,21 @@ const EditorComponent = (props) => {
const computeCanvasContainerHeight = () => {
// 45 = (height of header)
// 85 = (the height of the query panel header when minimised) + (height of header)
- return `calc(${100}% - ${Math.max(useQueryPanelStore.getState().queryPanelHeight + 45, 85)}px)`;
+ return `calc(${100}% - ${Math.max(
+ useSuperStore.getState().modules[moduleName].useQueryPanelStore.getState().queryPanelHeight + 45,
+ 85
+ )}px)`;
};
const handleQueryPaneDragging = (bool) => setIsQueryPaneDragging(bool);
const handleQueryPaneExpanding = (bool) => setIsQueryPaneExpanded(bool);
const handleOnComponentOptionChanged = (component, optionName, value) => {
- return onComponentOptionChanged(component, optionName, value);
+ return onComponentOptionChanged(moduleName, component, optionName, value);
};
const handleOnComponentOptionsChanged = (component, options) => {
- return onComponentOptionsChanged(component, options);
+ return onComponentOptionsChanged(moduleName, component, options);
};
const handleComponentClick = (id, component) => {
@@ -577,21 +595,26 @@ const EditorComponent = (props) => {
const sideBarDebugger = {
error: (data) => {
- debuggerActions.error(data);
+ debuggerActions.error(data, moduleName);
},
flush: () => {
- debuggerActions.flush();
+ debuggerActions.flush(moduleName);
},
- generateErrorLogs: (errors) => debuggerActions.generateErrorLogs(errors),
+ generateErrorLogs: (errors) => debuggerActions.generateErrorLogs(errors, moduleName),
};
const changeDarkMode = (newMode) => {
- useCurrentStateStore.getState().actions.setCurrentState({
- globals: {
- ...currentState.globals,
- theme: { name: newMode ? 'dark' : 'light' },
- },
- });
+ if (appMode === 'auto') {
+ useSuperStore
+ .getState()
+ .modules[moduleName].useCurrentStateStore.getState()
+ .actions.setCurrentState({
+ globals: {
+ ...currentState.globals,
+ theme: { name: newMode ? 'dark' : 'light' },
+ },
+ });
+ }
props.switchDarkMode(newMode);
};
@@ -606,7 +629,10 @@ const EditorComponent = (props) => {
};
const setSelectedComponent = (id, component, multiSelect = false) => {
- const isAlreadySelected = useEditorStore.getState()?.selectedComponents.find((component) => component.id === id);
+ const isAlreadySelected = useSuperStore
+ .getState()
+ .modules[moduleName].useEditorStore.getState()
+ ?.selectedComponents.find((component) => component.id === id);
if (!isAlreadySelected) {
setSelectedComponents([{ id, component }], multiSelect);
@@ -614,7 +640,10 @@ const EditorComponent = (props) => {
};
const onVersionRelease = (versionId) => {
- useAppVersionStore.getState().actions.updateReleasedVersionId(versionId);
+ useSuperStore
+ .getState()
+ .modules[moduleName].useAppVersionStore.getState()
+ .actions.updateReleasedVersionId(versionId);
if (socket instanceof WebSocket && socket?.readyState === WebSocket.OPEN) {
socket.send(
@@ -627,9 +656,11 @@ const EditorComponent = (props) => {
};
const computeCanvasBackgroundColor = () => {
- const { canvasBackgroundColor } = appDefinition?.globalSettings ?? '#edeff5';
+ const canvasBackgroundColor = appDefinition?.globalSettings?.canvasBackgroundColor
+ ? appDefinition?.globalSettings?.canvasBackgroundColor
+ : '#edeff5';
if (['#2f3c4c', '#edeff5'].includes(canvasBackgroundColor)) {
- return props.darkMode ? '#2f3c4c' : '#edeff5';
+ return isAppDarkMode ? '#2f3c4c' : '#edeff5';
}
return canvasBackgroundColor;
};
@@ -666,9 +697,16 @@ const EditorComponent = (props) => {
const callBack = async (data, startingPageHandle, versionSwitched = false) => {
setWindowTitle({ page: pageTitles.EDITOR, appName: data.name });
- useAppVersionStore.getState().actions.updateEditingVersion(data.editing_version);
+ useSuperStore
+ .getState()
+ .modules[moduleName].useAppVersionStore.getState()
+ .actions.updateEditingVersion(data.editing_version);
+
if (!releasedVersionId || !versionSwitched) {
- useAppVersionStore.getState().actions.updateReleasedVersionId(data.current_version_id);
+ useSuperStore
+ .getState()
+ .modules[moduleName].useAppVersionStore.getState()
+ .actions.updateReleasedVersionId(data.current_version_id);
}
const appVersions = await appEnvironmentService.getVersionsByEnvironment(data?.id);
@@ -704,8 +742,9 @@ const EditorComponent = (props) => {
};
setCurrentPageId(homePageId);
+ onAppModeChange(appJson?.globalSettings?.appMode);
- useCurrentStateStore.getState().actions.setCurrentState({
+ useSuperStore.getState().modules[moduleName].useCurrentStateStore.getState().actions.setCurrentState({
page: currentpageData,
});
@@ -723,7 +762,10 @@ const EditorComponent = (props) => {
});
}
- await useDataSourcesStore.getState().actions.fetchGlobalDataSources(data?.organization_id);
+ await useSuperStore
+ .getState()
+ .modules[moduleName].useDataSourcesStore.getState()
+ .actions.fetchGlobalDataSources(data?.organization_id);
await fetchDataSources(data.editing_version?.id);
await fetchDataQueries(data.editing_version?.id, true, true);
const currentPageEvents = data.events.filter((event) => event.target === 'page' && event.sourceId === homePageId);
@@ -779,11 +821,13 @@ const EditorComponent = (props) => {
});
}
let updatedAppDefinition;
- const copyOfAppDefinition = JSON.parse(JSON.stringify(useEditorStore.getState().appDefinition));
+ const copyOfAppDefinition = JSON.parse(
+ JSON.stringify(useSuperStore.getState().modules[moduleName].useEditorStore.getState().appDefinition)
+ );
if (opts?.skipYmapUpdate && opts?.currentSessionId !== currentSessionId) {
updatedAppDefinition = produce(copyOfAppDefinition, (draft) => {
- const _currentPageId = useEditorStore.getState().currentPageId;
+ const _currentPageId = useSuperStore.getState().modules[moduleName].useEditorStore.getState().currentPageId;
if (opts?.componentDeleting) {
const currentPageComponentIds = Object.keys(copyOfAppDefinition.pages[_currentPageId]?.components);
const newComponentIds = Object.keys(newDefinition.pages[_currentPageId]?.components);
@@ -921,7 +965,7 @@ const EditorComponent = (props) => {
};
const saveEditingVersion = (isUserSwitchedVersion = false) => {
- const editingVersion = useAppVersionStore.getState().editingVersion;
+ const editingVersion = useSuperStore.getState().modules[moduleName].useAppVersionStore.getState().editingVersion;
if (isVersionReleased && !isUserSwitchedVersion) {
updateEditorState({
isUpdatingEditorStateInProcess: false,
@@ -951,7 +995,10 @@ const EditorComponent = (props) => {
...editingVersion,
...{ definition: appDefinition },
};
- useAppVersionStore.getState().actions.updateEditingVersion(_editingVersion);
+ useSuperStore
+ .getState()
+ .modules[moduleName].useAppVersionStore.getState()
+ .actions.updateEditingVersion(_editingVersion);
if (config.ENABLE_MULTIPLAYER_EDITING) {
props.ymap?.set('appDef', {
@@ -1124,7 +1171,10 @@ const EditorComponent = (props) => {
const componentDefinitionChanged = (componentDefinition, props) => {
if (isVersionReleased) {
- useAppVersionStore.getState().actions.enableReleasedVersionPopupState();
+ useSuperStore
+ .getState()
+ .modules[moduleName].useAppVersionStore.getState()
+ .actions.enableReleasedVersionPopupState();
return;
}
@@ -1182,7 +1232,10 @@ const EditorComponent = (props) => {
componentDeleted: true,
});
} else {
- useAppVersionStore.getState().actions.enableReleasedVersionPopupState();
+ useSuperStore
+ .getState()
+ .modules[moduleName].useAppVersionStore.getState()
+ .actions.enableReleasedVersionPopupState();
}
};
@@ -1190,7 +1243,9 @@ const EditorComponent = (props) => {
const gridWidth = (1 * 100) / 43; // width of the canvas grid in percentage
const _appDefinition = _.cloneDeep(appDefinition);
let newComponents = _appDefinition?.pages[currentPageId].components;
- const selectedComponents = useEditorStore.getState()?.selectedComponents;
+ const selectedComponents = useSuperStore
+ .getState()
+ .modules[moduleName].useEditorStore.getState()?.selectedComponents;
for (const selectedComponent of selectedComponents) {
let top = newComponents[selectedComponent.id].layouts[currentLayout].top;
@@ -1222,7 +1277,7 @@ const EditorComponent = (props) => {
const copyComponents = () =>
cloneComponents(
- useEditorStore.getState()?.selectedComponents,
+ useSuperStore.getState().modules[moduleName].useEditorStore.getState()?.selectedComponents,
appDefinition,
currentPageId,
appDefinitionChanged,
@@ -1231,13 +1286,16 @@ const EditorComponent = (props) => {
const cutComponents = () => {
if (isVersionReleased) {
- useAppVersionStore.getState().actions.enableReleasedVersionPopupState();
+ useSuperStore
+ .getState()
+ .modules[moduleName].useAppVersionStore.getState()
+ .actions.enableReleasedVersionPopupState();
return;
}
cloneComponents(
- useEditorStore.getState()?.selectedComponents,
+ useSuperStore.getState().modules[moduleName].useEditorStore.getState()?.selectedComponents,
appDefinition,
currentPageId,
appDefinitionChanged,
@@ -1248,11 +1306,14 @@ const EditorComponent = (props) => {
const cloningComponents = () => {
if (isVersionReleased) {
- useAppVersionStore.getState().actions.enableReleasedVersionPopupState();
+ useSuperStore
+ .getState()
+ .modules[moduleName].useAppVersionStore.getState()
+ .actions.enableReleasedVersionPopupState();
return;
}
cloneComponents(
- useEditorStore.getState()?.selectedComponents,
+ useSuperStore.getState().modules[moduleName].useEditorStore.getState()?.selectedComponents,
appDefinition,
currentPageId,
appDefinitionChanged,
@@ -1262,7 +1323,7 @@ const EditorComponent = (props) => {
};
const handleEditorEscapeKeyPress = () => {
- if (useEditorStore.getState()?.selectedComponents?.length > 0) {
+ if (useSuperStore.getState().modules[moduleName].useEditorStore.getState()?.selectedComponents?.length > 0) {
updateEditorState({
selectedComponents: [],
});
@@ -1270,7 +1331,9 @@ const EditorComponent = (props) => {
};
const removeComponents = () => {
- const selectedComponents = useEditorStore.getState()?.selectedComponents;
+ const selectedComponents = useSuperStore
+ .getState()
+ .modules[moduleName].useEditorStore.getState()?.selectedComponents;
if (!isVersionReleased && selectedComponents?.length > 1) {
let newDefinition = cloneDeep(appDefinition);
@@ -1286,7 +1349,10 @@ const EditorComponent = (props) => {
});
}
} else if (isVersionReleased) {
- useAppVersionStore.getState().actions.enableReleasedVersionPopupState();
+ useSuperStore
+ .getState()
+ .modules[moduleName].useAppVersionStore.getState()
+ .actions.enableReleasedVersionPopupState();
}
};
@@ -1369,11 +1435,14 @@ const EditorComponent = (props) => {
const globals = {
...currentState.globals,
};
- useCurrentStateStore.getState().actions.setCurrentState({ globals, page });
+ useSuperStore
+ .getState()
+ .modules[moduleName].useCurrentStateStore.getState()
+ .actions.setCurrentState({ globals, page });
};
const navigateToPage = (queryParams = [], handle) => {
- const appId = useAppDataStore.getState()?.appId;
+ const appId = useSuperStore.getState().modules[moduleName].useAppDataStore.getState()?.appId;
const queryParamsString = queryParams.map(([key, value]) => `${key}=${value}`).join('&');
props?.navigate(`/${getWorkspaceId()}/apps/${slug ?? appId}/${handle}?${queryParamsString}`, {
@@ -1385,9 +1454,9 @@ const EditorComponent = (props) => {
const switchPage = (pageId, queryParams = []) => {
// This are fetched from store to handle runQueriesOnAppLoad
- const currentPageId = useEditorStore.getState().currentPageId;
- const appDefinition = useEditorStore.getState().appDefinition;
- const pageHandle = getCurrentState().pageHandle;
+ const currentPageId = useSuperStore.getState().modules[moduleName].useEditorStore.getState().currentPageId;
+ const appDefinition = useSuperStore.getState().modules[moduleName].useEditorStore.getState().appDefinition;
+ const pageHandle = getCurrentState(moduleName).pageHandle;
if (currentPageId === pageId && pageHandle === appDefinition?.pages[pageId]?.handle) {
return;
@@ -1410,7 +1479,10 @@ const EditorComponent = (props) => {
...currentState.globals,
urlparams: JSON.parse(JSON.stringify(queryString.parse(queryParamsString))),
};
- useCurrentStateStore.getState().actions.setCurrentState({ globals, page });
+ useSuperStore
+ .getState()
+ .modules[moduleName].useCurrentStateStore.getState()
+ .actions.setCurrentState({ globals, page });
setCurrentPageId(pageId);
@@ -1639,7 +1711,7 @@ const EditorComponent = (props) => {
const handleCanvasContainerMouseUp = (e) => {
if (
['real-canvas', 'modal'].includes(e.target.className) &&
- useEditorStore.getState()?.selectedComponents?.length
+ useSuperStore.getState().modules[moduleName].useEditorStore.getState()?.selectedComponents?.length
) {
setSelectedComponents(EMPTY_ARRAY);
}
@@ -1649,7 +1721,7 @@ const EditorComponent = (props) => {
if (isLoading) {
return (
-
+
@@ -1673,7 +1745,7 @@ const EditorComponent = (props) => {
);
}
return (
-
-
+
@@ -1868,7 +1944,7 @@ const EditorComponent = (props) => {
/>
{
)}
{defaultComponentStateComputed && (
- <>
+
+
diff --git a/frontend/src/Editor/EditorSelecto.jsx b/frontend/src/Editor/EditorSelecto.jsx
index 0f3bd3433d..2a7d177f07 100644
--- a/frontend/src/Editor/EditorSelecto.jsx
+++ b/frontend/src/Editor/EditorSelecto.jsx
@@ -1,7 +1,9 @@
-import React, { useCallback, memo } from 'react';
+import React, { useCallback, memo, useContext } from 'react';
import Selecto from 'react-selecto';
import { useEditorStore, EMPTY_ARRAY } from '@/_stores/editorStore';
import { shallow } from 'zustand/shallow';
+import { useSuperStore } from '../_stores/superStore';
+import { ModuleContext } from '../_contexts/ModuleContext';
const EditorSelecto = ({
selectionRef,
@@ -20,11 +22,19 @@ const EditorSelecto = ({
shallow
);
+ const moduleName = useContext(ModuleContext);
+
const onAreaSelectionStart = useCallback(
(e) => {
- const isMultiSelect = e.inputEvent.shiftKey || useEditorStore.getState().selectedComponents.length > 0;
+ const isMultiSelect =
+ e.inputEvent.shiftKey ||
+ useSuperStore.getState().modules[moduleName].useEditorStore.getState().selectedComponents.length > 0;
setSelectionInProgress(true);
- setSelectedComponents([...(isMultiSelect ? useEditorStore.getState().selectedComponents : EMPTY_ARRAY)]);
+ setSelectedComponents([
+ ...(isMultiSelect
+ ? useSuperStore.getState().modules[moduleName].useEditorStore.getState().selectedComponents
+ : EMPTY_ARRAY),
+ ]);
},
[setSelectionInProgress, setSelectedComponents]
);
@@ -33,7 +43,7 @@ const EditorSelecto = ({
e.added.forEach((el) => {
el.classList.add('resizer-select');
});
- if (useEditorStore.getState().selectionInProgress) {
+ if (useSuperStore.getState().modules[moduleName].useEditorStore.getState().selectionInProgress) {
e.removed.forEach((el) => {
el.classList.remove('resizer-select');
});
@@ -68,7 +78,8 @@ const EditorSelecto = ({
(e) => {
if (selectionDragRef.current) {
e.stop();
- useEditorStore.getState().selectionInProgress && setSelectionInProgress(false);
+ useSuperStore.getState().modules[moduleName].useEditorStore.getState().selectionInProgress &&
+ setSelectionInProgress(false);
}
},
[setSelectionInProgress, selectionDragRef]
@@ -76,7 +87,8 @@ const EditorSelecto = ({
const onAreaSelectionDragEnd = () => {
selectionDragRef.current = false;
- useEditorStore.getState().selectionInProgress && setSelectionInProgress(false);
+ useSuperStore.getState().modules[moduleName].useEditorStore.getState().selectionInProgress &&
+ setSelectionInProgress(false);
};
return (
diff --git a/frontend/src/Editor/Header/AppModeToggle.jsx b/frontend/src/Editor/Header/AppModeToggle.jsx
new file mode 100644
index 0000000000..f66a196287
--- /dev/null
+++ b/frontend/src/Editor/Header/AppModeToggle.jsx
@@ -0,0 +1,56 @@
+import React, { useContext } from 'react';
+import ToggleGroup from '@/ToolJetUI/SwitchGroup/ToggleGroup';
+import ToggleGroupItem from '@/ToolJetUI/SwitchGroup/ToggleGroupItem';
+import { useTranslation } from 'react-i18next';
+import useAppDarkMode from '@/_hooks/useAppDarkMode';
+import { useSuperStore } from '@/_stores/superStore';
+import { ModuleContext } from '@/_contexts/ModuleContext';
+// import { ModuleContext } from '../_contexts/ModuleContext';
+
+const APP_MODES = [
+ { label: 'Auto', value: 'auto' },
+ { label: 'Light', value: 'light' },
+ { label: 'Dark', value: 'dark' },
+];
+
+const AppModeToggle = ({ globalSettingsChanged }) => {
+ const moduleName = useContext(ModuleContext);
+ const { onAppModeChange, appMode } = useAppDarkMode();
+ const { t } = useTranslation();
+
+ return (
+
+
)}
+ {t('leftSidebar.Settings.appMode', 'App mode')}
+
+ );
+};
+
+export default AppModeToggle;
diff --git a/frontend/src/Editor/Header/GlobalSettings.jsx b/frontend/src/Editor/Header/GlobalSettings.jsx
index cb0a95cb29..170c6183bd 100644
--- a/frontend/src/Editor/Header/GlobalSettings.jsx
+++ b/frontend/src/Editor/Header/GlobalSettings.jsx
@@ -15,6 +15,7 @@ import { useAppVersionStore } from '@/_stores/appVersionStore';
import { shallow } from 'zustand/shallow';
import { ButtonSolid } from '@/_ui/AppButton/AppButton';
import { useAppDataActions, useAppInfo } from '@/_stores/appDataStore';
+import AppModeToggle from './AppModeToggle';
export const GlobalSettings = ({
globalSettings,
@@ -42,7 +43,6 @@ export const GlobalSettings = ({
}),
shallow
);
-
const { app, slug: oldSlug } = useAppInfo();
const coverStyles = {
@@ -148,7 +148,7 @@ export const GlobalSettings = ({
/>
)}
+
+
-
-
+
+
Export app
diff --git a/frontend/src/Editor/Header/HeaderActions.jsx b/frontend/src/Editor/Header/HeaderActions.jsx
index 314f7a3569..6b8535252b 100644
--- a/frontend/src/Editor/Header/HeaderActions.jsx
+++ b/frontend/src/Editor/Header/HeaderActions.jsx
@@ -13,8 +13,8 @@ function HeaderActions({
showToggleLayoutBtn,
showUndoRedoBtn,
showFullWidth,
+ darkMode,
}) {
- const darkMode = localStorage.getItem('darkMode') === 'true';
const { currentLayout, toggleCurrentLayout } = useEditorStore(
(state) => ({
currentLayout: state.currentLayout,
diff --git a/frontend/src/Editor/Header/index.js b/frontend/src/Editor/Header/index.js
index 4d9751f207..d155e1b884 100644
--- a/frontend/src/Editor/Header/index.js
+++ b/frontend/src/Editor/Header/index.js
@@ -81,7 +81,7 @@ export default function EditorHeader({
const shouldRenderReleaseButton = !!app?.id;
return (
-
+
@@ -116,6 +116,7 @@ export default function EditorHeader({
handleRedo={handleRedo}
showToggleLayoutBtn
showUndoRedoBtn
+ darkMode={darkMode}
/>
@@ -153,6 +154,7 @@ export default function EditorHeader({
setAppDefinitionFromVersion={setAppDefinitionFromVersion}
onVersionDelete={onVersionDelete}
isPublic={isPublic ?? false}
+ darkMode={darkMode}
/>
)}
diff --git a/frontend/src/Editor/Inspector/Components/DefaultComponent.jsx b/frontend/src/Editor/Inspector/Components/DefaultComponent.jsx
index eff2444311..702745372e 100644
--- a/frontend/src/Editor/Inspector/Components/DefaultComponent.jsx
+++ b/frontend/src/Editor/Inspector/Components/DefaultComponent.jsx
@@ -86,7 +86,7 @@ export const baseComponentProperties = (
'Additional Actions': Object.keys(AllComponents).filter(
(component) => !SHOW_ADDITIONAL_ACTIONS.includes(component)
),
- General: ['Modal', 'TextInput', 'PasswordInput', 'NumberInput', 'Text'],
+ General: ['Modal', 'TextInput', 'PasswordInput', 'NumberInput', 'Text', 'Table'],
Layout: [],
};
if (component.component.component === 'Listview') {
diff --git a/frontend/src/Editor/Inspector/Components/Table/ColumnManager/ColumnPopover.jsx b/frontend/src/Editor/Inspector/Components/Table/ColumnManager/ColumnPopover.jsx
new file mode 100644
index 0000000000..de1da44195
--- /dev/null
+++ b/frontend/src/Editor/Inspector/Components/Table/ColumnManager/ColumnPopover.jsx
@@ -0,0 +1,109 @@
+import React, { useState } from 'react';
+import Popover from 'react-bootstrap/Popover';
+import { StylesTabElements } from './StylesTabElements';
+import { PropertiesTabElements } from './PropertiesTabElements';
+
+export const ColumnPopoverContent = ({
+ column,
+ index,
+ darkMode,
+ currentState,
+ onColumnItemChange,
+ getPopoverFieldSource,
+ setColumnPopoverRootCloseBlocker,
+ component,
+ props,
+ columnEventChanged,
+ handleEventManagerPopoverCallback,
+}) => {
+ const [activeTab, setActiveTab] = useState('propertiesTab');
+
+ const timeZoneOptions = [
+ { name: 'UTC', value: 'Etc/UTC' },
+ { name: '-12:00', value: 'Etc/GMT+12' },
+ { name: '-11:00', value: 'Etc/GMT+11' },
+ { name: '-10:00', value: 'Pacific/Honolulu' },
+ { name: '-09:00', value: 'America/Anchorage' },
+ { name: '-08:00', value: 'America/Santa_Isabel' },
+ { name: '-07:00', value: 'America/Chihuahua' },
+ { name: '-06:00', value: 'America/Guatemala' },
+ { name: '-05:00', value: 'America/Bogota' },
+ { name: '-04:00', value: 'America/Halifax' },
+ { name: '-03:30', value: 'America/St_Johns' },
+ { name: '-03:00', value: 'America/Sao_Paulo' },
+ { name: '-02:00', value: 'Etc/GMT+2' },
+ { name: '-01:00', value: 'Atlantic/Cape_Verde' },
+ { name: '+00:00', value: 'UTC' },
+ { name: '+01:00', value: 'Europe/Berlin' },
+ { name: '+02:00', value: 'Africa/Gaborone' },
+ { name: '+03:00', value: 'Asia/Baghdad' },
+ { name: '+04:00', value: 'Asia/Muscat' },
+ { name: '+04:30', value: 'Asia/Kabul' },
+ { name: '+05:00', value: 'Asia/Tashkent' },
+ { name: '+05:30', value: 'Asia/Colombo' },
+ { name: '+05:45', value: 'Asia/Kathmandu' },
+ { name: '+06:00', value: 'Asia/Almaty' },
+ { name: '+06:30', value: 'Asia/Yangon' },
+ { name: '+07:00', value: 'Asia/Bangkok' },
+ { name: '+08:00', value: 'Asia/Makassar' },
+ { name: '+09:00', value: 'Asia/Seoul' },
+ { name: '+09:30', value: 'Australia/Darwin' },
+ { name: '+10:00', value: 'Pacific/Chuuk' },
+ { name: '+11:00', value: 'Pacific/Pohnpei' },
+ { name: '+12:00', value: 'Etc/GMT-12' },
+ { name: '+13:00', value: 'Pacific/Auckland' },
+ ];
+
+ return (
+ <>
+
+
+ {
+ if (activeTab !== 'propertiesTab') setActiveTab('propertiesTab');
+ }}
+ >
+ Properties
+
+ {
+ if (activeTab !== 'stylesTab') setActiveTab('stylesTab');
+ }}
+ >
+ Styles
+
+
+
+
+
+ {resolveReferences(column?.isDateSelectionEnabled, currentState) && (
+ e.stopPropagation()}
+ >
+
+ )}
+
+
+
+
+
+
+ {isDateDisplayFormatFxOn ? (
+
+
+ >
+ ),
+ },
+ {
+ title: 'Parse format',
+ children: (
+ <>
+
+
+ {resolveReferences(column?.isTimeChecked, currentState) && (
+ <>
+ {!isDateDisplayFormatFxOn && (
+ e.stopPropagation()} style={{ padding: '0px 12px' }}>
+
+
+ )}
+
+
+
+ e.stopPropagation()}
+ style={{ padding: '0px 12px' }}
+ >
+
+
+ >
+ )}
+
+
+ ) : (
+
+
+
+
+ {resolveReferences(column?.isDateSelectionEnabled, currentState) && (
+
+ )}
+ >
+ ),
+ }
+ );
+
+ return
+
+ )}
+ {resolveReferences(column?.isTimeChecked, currentState) && (
+ <>
+ {!isParseDateFormatFxOn && (
+
+
+
+
+ {isParseDateFormatFxOn ? (
+ e.stopPropagation()}>
+
+
+ )}
+
+
+
+ >
+ )}
+
+
+ );
+};
+
+export const DeprecatedColumnTooltip = ({ columnType, children }) => {
+ const deprecatedColumnType = DEPRECATED_COLUMN_TYPES.find((ct) => ct.value === columnType);
+ return (
+ Deprecating column type
+ {`This column type is deprecated and will be removed in a future update. We recommend using the new ${columnLabel} when creating applications moving forward.`}
+
+
+
+ );
+};
+
+export default DeprecatedColumnTypeMsg;
diff --git a/frontend/src/Editor/Inspector/Components/Table/ColumnManager/PropertiesTabElements.jsx b/frontend/src/Editor/Inspector/Components/Table/ColumnManager/PropertiesTabElements.jsx
new file mode 100644
index 0000000000..876d8fdfdd
--- /dev/null
+++ b/frontend/src/Editor/Inspector/Components/Table/ColumnManager/PropertiesTabElements.jsx
@@ -0,0 +1,326 @@
+import React from 'react';
+import { resolveReferences } from '@/_helpers/utils';
+import { useTranslation } from 'react-i18next';
+import { CodeHinter } from '../../../../CodeBuilder/CodeHinter';
+import { EventManager } from '../../../EventManager';
+import { ProgramaticallyHandleProperties } from '../ProgramaticallyHandleProperties';
+import { OptionsList } from '../SelectOptionsList/OptionsList';
+import { ValidationProperties } from './ValidationProperties';
+import DatepickerProperties from './DatepickerProperties';
+import { Option } from '@/Editor/CodeBuilder/Elements/Select';
+import DeprecatedColumnTypeMsg from './DeprecatedColumnTypeMsg';
+import CustomSelect from '@/_ui/Select';
+import defaultStyles from '@/_ui/Select/styles';
+import SolidIcon from '@/_ui/Icon/SolidIcons';
+
+export const PropertiesTabElements = ({
+ column,
+ index,
+ darkMode,
+ currentState,
+ onColumnItemChange,
+ getPopoverFieldSource,
+ setColumnPopoverRootCloseBlocker,
+ component,
+ props,
+ columnEventChanged,
+ timeZoneOptions,
+ handleEventManagerPopoverCallback,
+}) => {
+ const { t } = useTranslation();
+
+ const customStylesForSelect = {
+ ...defaultStyles(darkMode, '100%'),
+ };
+
+ return (
+ <>
+ {column.columnType && e.stopPropagation()}>
+
+
+
+
+
+
+
+
+
+
+
+
+ {column.columnType === 'toggle' && (
+
+
+ )}
+ {(column.columnType === 'dropdown' ||
+ column.columnType === 'multiselect' ||
+ column.columnType === 'badge' ||
+ column.columnType === 'badges' ||
+ column.columnType === 'radio') && (
+
+
+ )}
+ {column.columnType === 'link' && (
+ <>
+
+
+
+
+
+
+
+
+
+
+
+
+ >
+ )}
+ {column.columnType === 'number' && (
+
+
+
+
+
+ )}
+ {!['image', 'link'].includes(column.columnType) && (
+
+
+ )}
+
+
+ {resolveReferences(column?.isEditable, currentState) && (
+
+
+
+ {['select', 'newMultiSelect', 'datepicker'].includes(column.columnType) &&
+
+ } + {column.columnType === 'datepicker' && ( +
+
+ )}
+ {['select', 'newMultiSelect'].includes(column.columnType) && (
+
+ {/* Your custom SVG */}
+ {props.selectProps.menuIsOpen ? (
+
+ );
+};
diff --git a/frontend/src/Editor/Inspector/Components/Table/ColumnManager/StylesTabElements.jsx b/frontend/src/Editor/Inspector/Components/Table/ColumnManager/StylesTabElements.jsx
new file mode 100644
index 0000000000..abfc7ec581
--- /dev/null
+++ b/frontend/src/Editor/Inspector/Components/Table/ColumnManager/StylesTabElements.jsx
@@ -0,0 +1,214 @@
+import React from 'react';
+import { useTranslation } from 'react-i18next';
+import { CodeHinter } from '../../../../CodeBuilder/CodeHinter';
+import { Color } from '../../../Elements/Color';
+import ToggleGroup from '@/ToolJetUI/SwitchGroup/ToggleGroup';
+import ToggleGroupItem from '@/ToolJetUI/SwitchGroup/ToggleGroupItem';
+import AlignLeft from '@/_ui/Icon/solidIcons/AlignLeft';
+import AlignCenter from '@/_ui/Icon/solidIcons/AlignCenter';
+import AlignRight from '@/_ui/Icon/solidIcons/AlignRight';
+import { ProgramaticallyHandleProperties } from '../ProgramaticallyHandleProperties';
+import { Select } from '@/Editor/CodeBuilder/Elements/Select';
+
+export const StylesTabElements = ({
+ column,
+ index,
+ darkMode,
+ currentState,
+ onColumnItemChange,
+ getPopoverFieldSource,
+ component,
+}) => {
+ const { t } = useTranslation();
+ return (
+ <>
+
+
+
+ {column.columnType === 'toggle' && (
+
+
+ )}
+ {column.columnType === 'image' && (
+ <>
+
+
+
+
+
+
+
+
+ >
+ )}
+ {column.columnType === 'boolean' && (
+
+
+ )}
+
+ {['string', 'default', undefined, 'number', 'boolean', 'select', 'text', 'newMultiSelect', 'datepicker'].includes(
+ column.columnType
+ ) && (
+ <>
+ {column.columnType !== 'boolean' && (
+
+
+
+
+
+
+ )}
+
+
+ >
+ )}
+
+ {column.columnType === 'link' && (
+ <>
+
+
+
+
+
+
+ >
+ )}
+ >
+ );
+};
diff --git a/frontend/src/Editor/Inspector/Components/Table/ColumnManager/ValidationProperties.jsx b/frontend/src/Editor/Inspector/Components/Table/ColumnManager/ValidationProperties.jsx
new file mode 100644
index 0000000000..da9ff70245
--- /dev/null
+++ b/frontend/src/Editor/Inspector/Components/Table/ColumnManager/ValidationProperties.jsx
@@ -0,0 +1,252 @@
+import React from 'react';
+import { useTranslation } from 'react-i18next';
+import { CodeHinter } from '../../../../CodeBuilder/CodeHinter';
+import ReactDatePicker from 'react-datepicker';
+import moment from 'moment';
+import Timepicker from '@/ToolJetUI/Timepicker/Timepicker';
+import CustomDatePickerHeader from '@/Editor/Components/Table/CustomDatePickerHeader';
+import { resolveReferences } from '../../../../../_helpers/utils';
+
+const getDate = (date, format) => {
+ const dateMomentInstance = date && moment(date, format);
+ if (dateMomentInstance && dateMomentInstance.isValid()) {
+ return dateMomentInstance.toDate();
+ } else {
+ return null;
+ }
+};
+
+export const ValidationProperties = ({
+ column,
+ index,
+ darkMode,
+ currentState,
+ onColumnItemChange,
+ getPopoverFieldSource,
+ setColumnPopoverRootCloseBlocker,
+}) => {
+ const { t } = useTranslation();
+ const columnType = column.columnType;
+
+ const getValidationList = (columnType) => {
+ switch (columnType) {
+ case 'string':
+ case undefined:
+ case 'default':
+ case 'text': {
+ const properties = [];
+ if (column.columnType !== 'text') {
+ properties.push({
+ property: 'regex',
+ dateCy: 'input-and-label-regex',
+ label: 'Regex',
+ placeholder: `${/^[\w\s\d]+$/i}`,
+ });
+ }
+ properties.push(
+ [
+ {
+ property: 'minLength',
+ dateCy: 'input-and-label-min-length',
+ label: 'Min length',
+ placeholder: 'Enter min length',
+ },
+ {
+ property: 'maxLength',
+ dateCy: 'input-and-label-max-length',
+ label: 'Max length',
+ placeholder: 'Enter max length',
+ },
+ ],
+
+ {
+ property: 'customRule',
+ dateCy: 'input-and-label-custom-rule',
+ label: 'Custom rule',
+ placeholder: 'eg. {{ 1 < 2 }}',
+ }
+ );
+
+ return properties;
+ }
+ case 'number':
+ return [
+ { property: 'regex', dateCy: 'input-and-label-regex', label: 'Regex', placeholder: `${/^[\w\s\d]+$/i}` },
+ [
+ {
+ property: 'minValue',
+ dateCy: 'input-and-label-min-value',
+ label: 'Min value',
+ placeholder: 'Enter min value',
+ },
+ {
+ property: 'maxValue',
+ dateCy: 'input-and-label-max-value',
+ label: 'Max value',
+ placeholder: 'Enter max value',
+ },
+ ],
+ {
+ property: 'customRule',
+ dateCy: 'input-and-label-custom-rule',
+ label: 'Custom rule',
+ placeholder: 'eg. {{ 1 < 2 }}',
+ },
+ ];
+ case 'dropdown':
+ case 'select':
+ case 'newMultiSelect':
+ return [
+ {
+ property: 'customRule',
+ dateCy: 'input-and-label-custom-rule',
+ label: 'Custom rule',
+ placeholder: 'eg. {{ 1 < 2 }}',
+ },
+ ];
+ case 'datepicker': {
+ const isTimeChecked = resolveReferences(column?.isTimeChecked);
+ let properties = [];
+ properties.push([
+ {
+ property: 'minDate',
+ dateCy: 'input-and-label-min-date',
+ label: 'Minimum date',
+ placeholder: 'MM/DD/YYYY',
+ fieldType: 'datepicker',
+ },
+ {
+ property: 'maxDate',
+ dateCy: 'input-and-label-max-date',
+ label: 'Maximum date',
+ placeholder: 'MM/DD/YYYY',
+ fieldType: 'datepicker',
+ },
+ ]);
+
+ if (isTimeChecked) {
+ properties.push([
+ {
+ property: 'minTime',
+ dateCy: 'input-and-label-min-time',
+ label: 'Minimum time',
+ placeholder: 'HH:mm',
+ fieldType: 'timepicker',
+ },
+ {
+ property: 'maxTime',
+ dateCy: 'input-and-label-max-time',
+ label: 'Maximum time',
+ placeholder: 'HH:mm',
+ fieldType: 'timepicker',
+ },
+ ]);
+ }
+ properties.push({
+ property: 'disabledDates',
+ dateCy: 'input-and-label-custom-rule',
+ label: 'Disabled dates',
+ placeholder: '{{[]}}',
+ });
+
+ properties.push({
+ property: 'customRule',
+ dateCy: 'input-and-label-custom-rule',
+ label: 'Custom rule',
+ placeholder: 'eg. {{ 1 < 2 }}',
+ });
+
+ return properties;
+ }
+
+ default:
+ return [];
+ }
+ };
+ const validationsList = getValidationList(columnType);
+
+ if (validationsList.length < 1) {
+ return '';
+ }
+
+ const renderAsPerFieldType = (validation) => {
+ switch (validation.fieldType) {
+ case 'datepicker':
+ return (
+
+
+
+
+
+
+ );
+ case 'timepicker':
+ return (
+
+
+
+ );
+ default:
+ return (
+
+
+
+ );
+ }
+ };
+
+ return (
+
+
+ );
+};
diff --git a/frontend/src/Editor/Inspector/Components/Table/ProgramaticallyHandleProperties.jsx b/frontend/src/Editor/Inspector/Components/Table/ProgramaticallyHandleProperties.jsx
index 9b1ff4af85..2dc2ae4dee 100644
--- a/frontend/src/Editor/Inspector/Components/Table/ProgramaticallyHandleProperties.jsx
+++ b/frontend/src/Editor/Inspector/Components/Table/ProgramaticallyHandleProperties.jsx
@@ -26,6 +26,30 @@ export const ProgramaticallyHandleProperties = ({
return props.columnVisibility;
case 'linkTarget':
return props.linkTarget;
+ case 'isAllColumnsEditable':
+ return props?.isAllColumnsEditable;
+ case 'underlineColor':
+ return props.underlineColor;
+ case 'linkColor':
+ return props.linkColor;
+ case 'useDynamicOptions':
+ return props?.useDynamicOptions;
+ case 'makeDefaultOption':
+ return props?.[index]?.makeDefaultOption;
+ case 'textColor':
+ return props?.textColor;
+ case 'cellBackgroundColor':
+ return props?.cellBackgroundColor;
+ case 'optionsLoadingState':
+ return props?.optionsLoadingState;
+ case 'isTimeChecked':
+ return props?.isTimeChecked;
+ case 'isTwentyFourHrFormatEnabled':
+ return props?.isTwentyFourHrFormatEnabled;
+ case 'parseInUnixTimestamp':
+ return props?.parseInUnixTimestamp;
+ case 'isDateSelectionEnabled':
+ return props?.isDateSelectionEnabled;
default:
return;
}
@@ -36,16 +60,41 @@ export const ProgramaticallyHandleProperties = ({
return definitionObj?.value ?? `{{true}}`;
}
if (property === 'linkTarget') {
- return definitionObj?.value ?? '_blank';
+ const value = definitionObj?.value;
+ if (value === '_self' || value === '{{false}}' || value === '') {
+ return '{{false}}';
+ }
+ return value || '{{true}}';
+ }
+ if (property === 'cellBackgroundColor') {
+ return definitionObj?.value ?? '';
+ }
+ if (property === 'textColor') {
+ return definitionObj?.value ?? '#11181C';
+ }
+ if (property === 'underlineColor') {
+ return definitionObj?.value ?? '#4368E3';
+ }
+ if (property === 'underline') {
+ return definitionObj?.value ?? 'hover';
+ }
+ if (property === 'linkColor') {
+ return definitionObj?.value ?? '#1B1F24';
}
return definitionObj?.value ?? `{{false}}`;
};
const value = getValueBasedOnProperty(property, props);
- const param = { name: property };
- const definition = { value, fxActive: props.fxActive };
- const initialValue = getInitialValue(property, definition);
-
+ const param = { name: property === 'makeDefaultOption' ? `options::${property}` : property };
+ let definition;
+ let initialValue;
+ if (Array.isArray(props)) {
+ definition = { value, fxActive: props?.[index]?.fxActive };
+ initialValue = getInitialValue(property, definition);
+ } else {
+ definition = { value, fxActive: props.fxActive };
+ initialValue = getInitialValue(property, definition);
+ }
const options = {};
const calcFxActiveState = (props, property) => {
@@ -94,7 +143,7 @@ export const ProgramaticallyHandleProperties = ({
};
return (
-
+ {validationsList.map((validation) => {
+ if (Array.isArray(validation)) {
+ return (
+
+
+ {validation.map((validation) => {
+ {
+ return renderAsPerFieldType(validation);
+ }
+ })}
+
+ );
+ } else {
+ return renderAsPerFieldType(validation);
+ }
+ })}
+ e.stopPropagation()}>
+ e.stopPropagation()}>
e.stopPropagation()}>
+
+
+ e.stopPropagation()}>
+
+
+
+
+ ),
+ });
+
+ return
+
+ ) : (
+
+ {column?.options?.map((option, optionIndex) => {
+ const resolvedItemName = option.label;
+ return (
+
+ );
+ }}
+
+
+ );
+ }}
+
+
+
+ {column?.options?.length === 0 &&
+
+
+
-
-
-
-
-
- {(column.columnType === 'string' || column.columnType === undefined || column.columnType === 'default') && (
-
-
-
- )}
-
-
-
-
-
-
-
-
-
-
-
-
- {(column.columnType === 'string' || column.columnType === undefined || column.columnType === 'default') && (
-
-
- )}
-
- {column.columnType === 'number' && resolveReferences(column.isEditable, this.state.currentState) && (
-
-
-
-
-
-
-
- {resolveReferences(column.isEditable, this.state.currentState) && (
-
-
- )}
-
- {this.props.t('widget.Table.validation', 'Validation')}
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- )}
-
- {column.columnType === 'toggle' && (
-
- {this.props.t('widget.Table.validation', 'Validation')}
-
-
-
-
-
-
-
-
-
- )}
-
- {(column.columnType === 'dropdown' ||
- column.columnType === 'multiselect' ||
- column.columnType === 'badge' ||
- column.columnType === 'badges' ||
- column.columnType === 'radio') && (
-
-
-
-
- )}
-
- {column.columnType === 'dropdown' && (
- <>
- {resolveReferences(column.isEditable, this.state.currentState) && (
-
-
-
-
-
-
-
-
- )}
- >
- )}
-
- {column.columnType === 'datepicker' && (
-
- {this.props.t('widget.Table.validation', 'Validation')}
-
-
-
-
-
-
-
- )}
- {column.columnType === 'image' && (
- <>
-
-
-
-
- {
- e.stopPropagation();
- this.onColumnItemChange(index, 'parseDateFormat', e.target.value);
- }}
- defaultValue={column.parseDateFormat}
- placeholder={'DD-MM-YYYY'}
- />
-
-
-
-
-
-
-
-
-
-
- {
- this.onColumnItemChange(index, 'isTimeChecked', !column.isTimeChecked);
- }}
- checked={column.isTimeChecked}
- />
-
- {this.props.t('widget.Table.showTime', 'show time')}
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- >
- )}
- {column.columnType === 'link' && (
-
-
- )}
-
- {!['image', 'link'].includes(column.columnType) && (
-
+
@@ -808,7 +245,7 @@ class TableComponent extends React.Component {
value={action.buttonText}
/>
-
+
@@ -927,7 +364,13 @@ class TableComponent extends React.Component {
addNewColumn = () => {
const columns = this.props.component.component.definition.properties.columns;
const newValue = columns.value;
- newValue.push({ name: this.generateNewColumnName(columns.value), id: uuidv4(), fxActiveFields: [] });
+ newValue.push({
+ name: this.generateNewColumnName(columns.value),
+ id: uuidv4(),
+ isEditable: this.state?.isAllColumnsEditable,
+ fxActiveFields: [],
+ columnType: 'string',
+ });
this.props.paramUpdated({ name: 'columns' }, 'value', newValue, 'properties', true);
};
@@ -948,12 +391,39 @@ class TableComponent extends React.Component {
onColumnItemChange = (index, item, value) => {
const columns = this.props.component.component.definition.properties.columns;
const column = columns.value[index];
+ const isAllColumnsEditable = this.state.isAllColumnsEditable;
+ if (item === 'columnType' && (value === 'select' || value === 'newMultiSelect')) {
+ column?.options?.length > 0 && column.options.forEach((option) => unset(option, 'makeDefaultOption'));
+ column.defaultOptionsList = [];
+ }
column[item] = value;
const newColumns = columns.value;
newColumns[index] = column;
+ if (NON_EDITABLE_COLUMNS.includes(newColumns[index].columnType)) {
+ newColumns[index].isEditable = '{{false}}';
+ }
+
+ if (item === 'columnType' && !NON_EDITABLE_COLUMNS.includes(value) && isAllColumnsEditable) {
+ newColumns[index].isEditable = '{{true}}';
+ }
+
this.props.paramUpdated({ name: 'columns' }, 'value', newColumns, 'properties', true);
+
+ // When any of the column is not editable, we need to disable "make all columns editable" toggle
+ if (item === 'isEditable' && !resolveReferences(value) && isAllColumnsEditable) {
+ this.setState({ isAllColumnsEditable: false });
+ }
+ // Check if all columns are editable and also if we have disabled "make all columns editable" toggle, if yes then enable it
+ if (item === 'isEditable' && resolveReferences(value) && !isAllColumnsEditable) {
+ const _isAllColumnsEditable = newColumns
+ .filter((column) => !NON_EDITABLE_COLUMNS.includes(column.columnType))
+ .every((column) => resolveReferences(column.isEditable));
+ if (_isAllColumnsEditable) {
+ this.setState({ isAllColumnsEditable: true });
+ }
+ }
};
getItemStyle = (isDragging, draggableStyle) => ({
@@ -995,9 +465,33 @@ class TableComponent extends React.Component {
getPopoverFieldSource = (column, field) =>
`component/${this.props.component.component.name}/${column ?? 'default'}::${field}`;
+ handleMakeAllColumnsEditable = (value) => {
+ const columns = resolveReferences(
+ this.props.component.component.definition.properties.columns,
+ this.props.currentState
+ );
+
+ this.setState({ isAllColumnsEditable: resolveReferences(value) });
+
+ const newValue = columns.value.map((column) => ({
+ ...column,
+ isEditable: !NON_EDITABLE_COLUMNS.includes(column.columnType) ? value : '{{false}}',
+ }));
+
+ this.props.paramUpdated({ name: 'columns' }, 'value', newValue, 'properties', true);
+ };
+
+ duplicateColumn = (index) => {
+ const columns = this.props.component.component.definition.properties?.columns ?? [];
+ const newColumns = columns.value;
+ let columnToBeDuplicated = newColumns?.[index];
+ columnToBeDuplicated = { ...columnToBeDuplicated, id: uuidv4() };
+ newColumns.push(columnToBeDuplicated);
+ this.props.paramUpdated({ name: 'columns' }, 'value', newColumns, 'properties', true);
+ };
+
render() {
const { dataQueries, component, paramUpdated, componentMeta, components, currentState, darkMode } = this.props;
-
const columns = component.component.definition.properties.columns;
const actions = component.component.definition.properties.actions || { value: [] };
if (!component.component.definition.properties.displaySearchBox)
@@ -1032,6 +526,7 @@ class TableComponent extends React.Component {
? resolveReferences(component.component.definition.properties.allowSelection?.value, currentState)
: resolveReferences(component.component.definition.properties.highlightSelectedRow.value, currentState) ||
resolveReferences(component.component.definition.properties.showBulkSelector.value, currentState);
+
const renderCustomElement = (param, paramType = 'properties') => {
return renderElement(component, componentMeta, paramUpdated, dataQueries, param, paramType, currentState);
};
@@ -1068,9 +563,53 @@ class TableComponent extends React.Component {
>
+
{columns.value.map((item, index) => {
const resolvedItemName = resolveReferences(item.name, this.state.currentState);
+ const isEditable = resolveReferences(item.isEditable, this.state.currentState);
+ const columnVisibility = item?.columnVisibility ?? true;
+ const getSecondaryText = (text) => {
+ switch (text) {
+ case undefined:
+ return '';
+ case 'string':
+ return 'String';
+ case 'default':
+ return 'Default';
+ case 'number':
+ return 'Number';
+ case 'text':
+ return 'Text';
+ case 'badge':
+ return 'Badge';
+ case 'badges':
+ return 'Badges';
+ case 'tags':
+ return 'Tags';
+ case 'dropdown':
+ return 'Dropdown';
+ case 'link':
+ return 'Link';
+ case 'radio':
+ return 'Radio';
+ case 'multiselect':
+ return 'Multiselect deprecated';
+ case 'toggle':
+ return 'Toggle';
+ case 'datepicker':
+ return 'Datepicker';
+ case 'image':
+ return 'Image';
+ case 'boolean':
+ return 'Boolean';
+ case 'select':
+ return 'Select';
+ case 'newMultiSelect':
+ return 'Multiselect';
+ default:
+ capitalize(text ?? '');
+ }
+ };
return (
+
+
{columns?.value?.length === 0 &&
+
)}
@@ -1176,6 +747,8 @@ class TableComponent extends React.Component {
'hideColumnSelectorButton',
'loadingState',
'showBulkUpdateActions',
+ 'visibility',
+ 'disabledState',
];
items.push({
diff --git a/frontend/src/Editor/Inspector/Elements/Color.jsx b/frontend/src/Editor/Inspector/Elements/Color.jsx
index c92cb27cdb..661898079b 100644
--- a/frontend/src/Editor/Inspector/Elements/Color.jsx
+++ b/frontend/src/Editor/Inspector/Elements/Color.jsx
@@ -2,7 +2,15 @@ import React, { useState } from 'react';
import { SketchPicker } from 'react-color';
import { ToolTip } from './Components/ToolTip';
-export const Color = ({ param, definition, onChange, paramType, componentMeta, cyLabel }) => {
+export const Color = ({
+ param,
+ definition,
+ onChange,
+ paramType,
+ componentMeta,
+ cyLabel,
+ shouldFlexDirectionBeRow = false,
+}) => {
const [showPicker, setShowPicker] = useState(false);
const coverStyles = {
@@ -38,8 +46,10 @@ export const Color = ({ param, definition, onChange, paramType, componentMeta, c
};
return (
-
-
-
+
);
diff --git a/frontend/src/Editor/ManageAppUsers.jsx b/frontend/src/Editor/ManageAppUsers.jsx
index 8c0d8f32cb..129da02bde 100644
--- a/frontend/src/Editor/ManageAppUsers.jsx
+++ b/frontend/src/Editor/ManageAppUsers.jsx
@@ -13,9 +13,12 @@ import SolidIcon from '@/_ui/Icon/SolidIcons';
import cx from 'classnames';
import { ToolTip } from '@/_components/ToolTip';
import { TOOLTIP_MESSAGES } from '@/_helpers/constants';
-import { useAppDataStore } from '@/_stores/appDataStore';
+import { useSuperStore } from '../_stores/superStore';
+import { ModuleContext } from '../_contexts/ModuleContext';
class ManageAppUsersComponent extends React.Component {
+ static contextType = ModuleContext;
+
constructor(props) {
super(props);
this.isUserAdmin = authenticationService.currentSessionValue?.admin;
@@ -109,7 +112,10 @@ class ManageAppUsersComponent extends React.Component {
ischangingVisibility: true,
});
- useAppDataStore.getState().actions.updateState({ isPublic: newState });
+ useSuperStore
+ .getState()
+ .modules[this.context].useAppDataStore.getState()
+ .actions.updateState({ isPublic: newState });
// eslint-disable-next-line no-unused-vars
appsService
@@ -165,7 +171,10 @@ class ManageAppUsersComponent extends React.Component {
});
replaceEditorURL(value, this.props.pageHandle);
- useAppDataStore.getState().actions.updateState({ slug: value });
+ useSuperStore
+ .getState()
+ .modules[this.context].useAppDataStore.getState()
+ .actions.updateState({ slug: value });
})
.catch(({ error }) => {
this.setState({
diff --git a/frontend/src/Editor/QueryManager/Components/QueryManagerHeader.jsx b/frontend/src/Editor/QueryManager/Components/QueryManagerHeader.jsx
index dce7260229..bb8c05ab28 100644
--- a/frontend/src/Editor/QueryManager/Components/QueryManagerHeader.jsx
+++ b/frontend/src/Editor/QueryManager/Components/QueryManagerHeader.jsx
@@ -21,10 +21,12 @@ import { shallow } from 'zustand/shallow';
import { Tooltip } from 'react-tooltip';
import { Button } from 'react-bootstrap';
import { cloneDeep } from 'lodash';
+import { useModuleName } from '@/_contexts/ModuleContext';
import ParameterList from './ParameterList';
export const QueryManagerHeader = forwardRef(({ darkMode, options, editorRef, setOptions }, ref) => {
+ const moduleName = useModuleName();
const { renameQuery } = useDataQueriesActions();
const selectedQuery = useSelectedQuery();
const selectedDataSource = useSelectedDataSource();
@@ -57,7 +59,7 @@ export const QueryManagerHeader = forwardRef(({ darkMode, options, editorRef, se
return false;
}
- const isNewQueryNameAlreadyExists = checkExistingQueryName(newName);
+ const isNewQueryNameAlreadyExists = checkExistingQueryName(newName, moduleName);
if (isNewQueryNameAlreadyExists) {
toast.error('Query name already exists');
return false;
diff --git a/frontend/src/Editor/QueryPanel/QueryCard.jsx b/frontend/src/Editor/QueryPanel/QueryCard.jsx
index 1b954f693c..efa32645a4 100644
--- a/frontend/src/Editor/QueryPanel/QueryCard.jsx
+++ b/frontend/src/Editor/QueryPanel/QueryCard.jsx
@@ -10,8 +10,10 @@ import { shallow } from 'zustand/shallow';
import Copy from '@/_ui/Icon/solidIcons/Copy';
import DataSourceIcon from '../QueryManager/Components/DataSourceIcon';
import { isQueryRunnable } from '@/_helpers/utils';
+import { useModuleName } from '@/_contexts/ModuleContext';
export const QueryCard = ({ dataQuery, darkMode = false, editorRef, appId }) => {
+ const moduleName = useModuleName();
const selectedQuery = useSelectedQuery();
const { isDeletingQueryInProcess } = useDataQueriesStore();
const { deleteDataQueries, renameQuery, duplicateQuery } = useDataQueriesActions();
@@ -42,7 +44,7 @@ export const QueryCard = ({ dataQuery, darkMode = false, editorRef, appId }) =>
if (name === newName) {
return setRenamingQuery(false);
}
- const isNewQueryNameAlreadyExists = checkExistingQueryName(newName);
+ const isNewQueryNameAlreadyExists = checkExistingQueryName(newName, moduleName);
if (newName && !isNewQueryNameAlreadyExists) {
renameQuery(dataQuery?.id, newName, editorRef);
setRenamingQuery(false);
diff --git a/frontend/src/Editor/QueryPanel/QueryDataPane.jsx b/frontend/src/Editor/QueryPanel/QueryDataPane.jsx
index 990f813af7..7c8fe55c4d 100644
--- a/frontend/src/Editor/QueryPanel/QueryDataPane.jsx
+++ b/frontend/src/Editor/QueryPanel/QueryDataPane.jsx
@@ -61,7 +61,7 @@ export const QueryDataPane = ({ darkMode, fetchDataQueries, editorRef, appId, to
const filterQueries = (value, queries) => {
if (value) {
- const fuse = new Fuse(queries, { keys: ['name'] });
+ const fuse = new Fuse(queries, { keys: ['name'], shouldSort: true, threshold: 0.3 });
const results = fuse.search(value);
let filterDataQueries = [];
results.every((result) => {
diff --git a/frontend/src/Editor/QueryPanel/QueryPanel.jsx b/frontend/src/Editor/QueryPanel/QueryPanel.jsx
index 9f102c6f1d..61f29c84fc 100644
--- a/frontend/src/Editor/QueryPanel/QueryPanel.jsx
+++ b/frontend/src/Editor/QueryPanel/QueryPanel.jsx
@@ -1,15 +1,17 @@
-import React, { useState, useRef, useCallback, useEffect } from 'react';
+import React, { useState, useRef, useCallback, useEffect, useContext } from 'react';
import { useEventListener } from '@/_hooks/use-event-listener';
import { Tooltip } from 'react-tooltip';
import { QueryDataPane } from './QueryDataPane';
import QueryManager from '../QueryManager/QueryManager';
-
import useWindowResize from '@/_hooks/useWindowResize';
-import { useQueryPanelStore, useQueryPanelActions } from '@/_stores/queryPanelStore';
+import { useQueryPanelActions } from '@/_stores/queryPanelStore';
import { useDataQueriesStore, useDataQueries } from '@/_stores/dataQueriesStore';
import Maximize from '@/_ui/Icon/solidIcons/Maximize';
import { cloneDeep, isEmpty, isEqual } from 'lodash';
import { ButtonSolid } from '@/_ui/AppButton/AppButton';
+import { ModuleContext } from '../../_contexts/ModuleContext';
+import { useSuperStore } from '../../_stores/superStore';
+import cx from 'classnames';
const QueryPanel = ({
dataQueriesChanged,
@@ -22,6 +24,7 @@ const QueryPanel = ({
onQueryPaneDragging,
handleQueryPaneExpanding,
}) => {
+ const moduleName = useContext(ModuleContext);
const { updateQueryPanelHeight } = useQueryPanelActions();
const dataQueries = useDataQueries();
const queryManagerPreferences = useRef(JSON.parse(localStorage.getItem('queryManagerPreferences')) ?? {});
@@ -37,26 +40,28 @@ const QueryPanel = ({
const [windowSize, isWindowResizing] = useWindowResize();
useEffect(() => {
- const queryPanelStoreListner = useQueryPanelStore.subscribe(({ selectedQuery }, prevState) => {
- if (isEmpty(prevState?.selectedQuery) || isEmpty(selectedQuery)) {
- return;
- }
+ const queryPanelStoreListner = useSuperStore
+ .getState()
+ .modules[moduleName].useQueryPanelStore.subscribe(({ selectedQuery }, prevState) => {
+ if (isEmpty(prevState?.selectedQuery) || isEmpty(selectedQuery)) {
+ return;
+ }
- if (prevState?.selectedQuery?.id !== selectedQuery.id) {
- return;
- }
+ if (prevState?.selectedQuery?.id !== selectedQuery.id) {
+ return;
+ }
- //removing updated_at since this value changes whenever the data is updated in the BE
- const formattedQuery = cloneDeep(selectedQuery);
- delete formattedQuery.updated_at;
+ //removing updated_at since this value changes whenever the data is updated in the BE
+ const formattedQuery = cloneDeep(selectedQuery);
+ delete formattedQuery.updated_at;
- const formattedPrevQuery = cloneDeep(prevState?.selectedQuery || {});
- delete formattedPrevQuery.updated_at;
+ const formattedPrevQuery = cloneDeep(prevState?.selectedQuery || {});
+ delete formattedPrevQuery.updated_at;
- if (!isEqual(formattedQuery, formattedPrevQuery)) {
- useDataQueriesStore.getState().actions.saveData(selectedQuery);
- }
- });
+ if (!isEqual(formattedQuery, formattedPrevQuery)) {
+ useSuperStore.getState().modules[moduleName].useDataQueriesStore.getState().actions.saveData(selectedQuery);
+ }
+ });
return queryPanelStoreListner;
}, []);
@@ -145,7 +150,7 @@ const QueryPanel = ({
}, []);
return (
- <>
+
@@ -70,7 +80,7 @@ export const Color = ({ param, definition, onChange, paramType, componentMeta, c
boxShadow: `0px 1px 2px 0px rgba(16, 24, 40, 0.05)`,
}}
>
-
+
diff --git a/frontend/src/Editor/Inspector/Inspector.jsx b/frontend/src/Editor/Inspector/Inspector.jsx
index 936f2014aa..7a0175a30a 100644
--- a/frontend/src/Editor/Inspector/Inspector.jsx
+++ b/frontend/src/Editor/Inspector/Inspector.jsx
@@ -53,6 +53,8 @@ const INSPECTOR_HEADER_OPTIONS = [
},
];
+const NEW_REVAMPED_COMPONENTS = ['Text', 'TextInput', 'PasswordInput', 'NumberInput', 'Table'];
+
export const Inspector = ({
componentDefinitionChanged,
allComponents,
@@ -83,7 +85,7 @@ export const Inspector = ({
const [inputRef, setInputFocus] = useFocus();
const [showHeaderActionsMenu, setShowHeaderActionsMenu] = useState(false);
- const shouldAddBoxShadow = ['TextInput', 'PasswordInput', 'NumberInput', 'Text'];
+ const isRevampedComponent = NEW_REVAMPED_COMPONENTS.includes(component.component.component);
const { isVersionReleased } = useAppVersionStore(
(state) => ({
@@ -198,6 +200,22 @@ export const Inspector = ({
} else {
allParams[param.name] = value;
}
+
+ if (
+ component.component.component === 'Table' &&
+ param.name === 'contentWrap' &&
+ !resolveReferences(value, currentState) &&
+ newDefinition.properties.columns.value.some((item) => item.columnType === 'image' && item.height !== '')
+ ) {
+ const updatedColumns = newDefinition.properties.columns.value.map((item) => {
+ return item.columnType === 'image' ? { ...item, height: '' } : item; // Create a new object for image columns
+ });
+
+ // Update the columns value with the updated columns
+ newDefinition.properties.columns.value = updatedColumns;
+ isParamFromTableColumn = true;
+ }
+
newDefinition[paramType] = allParams;
newComponent.component.definition = newDefinition;
componentDefinitionChanged(newComponent, {
@@ -312,15 +330,7 @@ export const Inspector = ({
);
const stylesTab = (
{definition.value}
-
+
);
@@ -468,18 +478,22 @@ const widgetsWithStyleConditions = {
},
],
},
+ Table: {
+ conditions: [
+ {
+ definition: 'styles',
+ property: 'contentWrap',
+ conditionStyles: ['maxRowHeight', 'autoHeight'],
+ type: 'toggle',
+ },
+ ],
+ },
};
-const styleGroupedComponentTypes = ['TextInput', 'NumberInput', 'PasswordInput'];
const RenderStyleOptions = ({ componentMeta, component, paramUpdated, dataQueries, currentState, allComponents }) => {
// Initialize an object to group properties by "accordian"
const groupedProperties = {};
- if (
- component.component.component === 'TextInput' ||
- component.component.component === 'PasswordInput' ||
- component.component.component === 'NumberInput' ||
- component.component.component === 'Text'
- ) {
+ if (NEW_REVAMPED_COMPONENTS.includes(component.component.component)) {
// Iterate over the properties in componentMeta.styles
for (const key in componentMeta.styles) {
const property = componentMeta.styles[key];
@@ -496,12 +510,7 @@ const RenderStyleOptions = ({ componentMeta, component, paramUpdated, dataQuerie
}
return Object.keys(
- component.component.component === 'TextInput' ||
- component.component.component === 'PasswordInput' ||
- component.component.component === 'NumberInput' ||
- component.component.component === 'Text'
- ? groupedProperties
- : componentMeta.styles
+ NEW_REVAMPED_COMPONENTS.includes(component.component.component) ? groupedProperties : componentMeta.styles
).map((style) => {
const conditionWidget = widgetsWithStyleConditions[component.component.component] ?? null;
const condition = conditionWidget?.conditions.find((condition) => condition.property) ?? {};
@@ -525,12 +534,7 @@ const RenderStyleOptions = ({ componentMeta, component, paramUpdated, dataQuerie
const items = [];
- if (
- component.component.component === 'TextInput' ||
- component.component.component === 'PasswordInput' ||
- component.component.component === 'NumberInput' ||
- component.component.component === 'Text'
- ) {
+ if (NEW_REVAMPED_COMPONENTS.includes(component.component.component)) {
items.push({
title: `${style}`,
children: Object.entries(groupedProperties[style]).map(([key, value]) => ({
@@ -566,7 +570,14 @@ const RenderStyleOptions = ({ componentMeta, component, paramUpdated, dataQuerie
const resolveConditionalStyle = (definition, condition, currentState) => {
const conditionExistsInDefinition = definition[condition] ?? false;
if (conditionExistsInDefinition) {
- return resolveReferences(definition[condition]?.value ?? false, currentState);
+ switch (condition) {
+ case 'cellSize': {
+ const cellSize = resolveReferences(definition[condition]?.value ?? false, currentState) === 'hugContent';
+ return cellSize;
+ }
+ default:
+ return resolveReferences(definition[condition]?.value ?? false, currentState);
+ }
}
};
diff --git a/frontend/src/Editor/Inspector/Utils.js b/frontend/src/Editor/Inspector/Utils.js
index 56b36741e3..aad23c71c6 100644
--- a/frontend/src/Editor/Inspector/Utils.js
+++ b/frontend/src/Editor/Inspector/Utils.js
@@ -43,19 +43,40 @@ export function renderCustomStyles(
componentConfig.component == 'Listview' ||
componentConfig.component == 'TextInput' ||
componentConfig.component == 'NumberInput' ||
- componentConfig.component == 'PasswordInput'
+ componentConfig.component == 'PasswordInput' ||
+ componentConfig.component == 'Table'
) {
const paramTypeConfig = componentMeta[paramType] || {};
const paramConfig = paramTypeConfig[param] || {};
const { conditionallyRender = null } = paramConfig;
- if (conditionallyRender) {
- const { key, value } = conditionallyRender;
- if (paramTypeDefinition?.[key] ?? value) {
- const resolvedValue = paramTypeDefinition?.[key] && resolveReferences(paramTypeDefinition?.[key], currentState);
+ const getResolvedValue = (key) => {
+ return paramTypeDefinition?.[key] && resolveReferences(paramTypeDefinition?.[key], currentState);
+ };
- if (resolvedValue?.value !== value) {
- return;
+ const utilFuncForMultipleChecks = (conditionallyRender) => {
+ return conditionallyRender.reduce((acc, condition) => {
+ const { key, value } = condition;
+ if (paramTypeDefinition?.[key] ?? value) {
+ const resolvedValue = getResolvedValue(key);
+ acc.push(resolvedValue?.value !== value);
+ }
+ return acc;
+ }, []);
+ };
+
+ if (conditionallyRender) {
+ const isConditionallyRenderArray = Array.isArray(conditionallyRender);
+
+ if (isConditionallyRenderArray && utilFuncForMultipleChecks(conditionallyRender).includes(true)) {
+ return;
+ } else {
+ const { key, value } = conditionallyRender;
+ if (paramTypeDefinition?.[key] ?? value) {
+ const resolvedValue = getResolvedValue(key);
+ if (resolvedValue?.value !== value) {
+ return;
+ }
}
}
}
@@ -135,7 +156,7 @@ export function renderElement(
componentMeta={componentMeta}
darkMode={darkMode}
componentName={component.component.name || null}
- type={meta.type}
+ type={meta?.type}
fxActive={definition.fxActive ?? false}
onFxPress={(active) => {
paramUpdated({ name: param, ...component.component.properties[param] }, 'fxActive', active, paramType);
diff --git a/frontend/src/Editor/LeftSidebar/SidebarDebugger/useDebugger.js b/frontend/src/Editor/LeftSidebar/SidebarDebugger/useDebugger.js
index 75f9124fa4..024b3ad6fe 100644
--- a/frontend/src/Editor/LeftSidebar/SidebarDebugger/useDebugger.js
+++ b/frontend/src/Editor/LeftSidebar/SidebarDebugger/useDebugger.js
@@ -1,5 +1,6 @@
import { useState, useEffect } from 'react';
import { useCurrentStateStore } from '@/_stores/currentStateStore';
+import { useModuleName } from '@/_contexts/ModuleContext';
import { shallow } from 'zustand/shallow';
import { debuggerActions } from '@/_helpers/appUtils';
import { flow } from 'lodash';
@@ -11,6 +12,8 @@ const useDebugger = ({ currentPageId, isDebuggerOpen }) => {
const [unReadErrorCount, setUnReadErrorCount] = useState({ read: 0, unread: 0 });
const [allLog, setAllLog] = useState([]);
+ const moduleName = useModuleName();
+
const { errors, succededQuery } = useCurrentStateStore(
(state) => ({
errors: state.errors,
@@ -43,7 +46,7 @@ const useDebugger = ({ currentPageId, isDebuggerOpen }) => {
(arr) => arr.filter(([key, value]) => value.data?.status),
Object.fromEntries,
])(errors);
- const newErrorLogs = debuggerActions.generateErrorLogs(newError);
+ const newErrorLogs = debuggerActions.generateErrorLogs(newError, moduleName);
const newPageLevelErrorLogs = newErrorLogs.filter((error) => error.strace === 'page_level');
const newAppLevelErrorLogs = newErrorLogs.filter((error) => error.strace === 'app_level');
if (newErrorLogs) {
@@ -64,19 +67,19 @@ const useDebugger = ({ currentPageId, isDebuggerOpen }) => {
};
});
}
- debuggerActions.flush();
+ debuggerActions.flush(moduleName);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [JSON.stringify({ errors })]);
useEffect(() => {
- const successQueryLogs = debuggerActions.generateQuerySuccessLogs(succededQuery);
+ const successQueryLogs = debuggerActions.generateQuerySuccessLogs(succededQuery, moduleName);
if (successQueryLogs?.length) {
setAllLog((prevLogs) => {
const temp = [...successQueryLogs, ...prevLogs];
const sortedDatesDesc = temp.sort((a, b) => moment(b.timestamp).diff(moment(a.timestamp)));
return sortedDatesDesc;
});
- debuggerActions.flushAllLog();
+ debuggerActions.flushAllLog(moduleName);
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [JSON.stringify({ succededQuery })]);
diff --git a/frontend/src/Editor/LeftSidebar/SidebarInspector.jsx b/frontend/src/Editor/LeftSidebar/SidebarInspector.jsx
index 0edb6c8bf6..7233fcbf72 100644
--- a/frontend/src/Editor/LeftSidebar/SidebarInspector.jsx
+++ b/frontend/src/Editor/LeftSidebar/SidebarInspector.jsx
@@ -3,7 +3,6 @@ import { HeaderSection } from '@/_ui/LeftSidebar';
import JSONTreeViewer from '@/_ui/JSONTreeViewer';
import _ from 'lodash';
import { toast } from 'react-hot-toast';
-import { getSvgIcon } from '@/_helpers/appUtils';
import Icon from '@/_ui/Icon/solidIcons/index';
import { useGlobalDataSources } from '@/_stores/dataSourcesStore';
import { useDataQueries } from '@/_stores/dataQueriesStore';
@@ -12,6 +11,7 @@ import { useAppVersionStore } from '@/_stores/appVersionStore';
import { shallow } from 'zustand/shallow';
import { ButtonSolid } from '@/_ui/AppButton/AppButton';
import { useEditorStore } from '@/_stores/editorStore';
+import DataSourceIcon from '@/Editor/QueryManager/Components/DataSourceIcon';
const staticDataSources = [
{ kind: 'tooljetdb', id: 'null', name: 'Tooljet Database' },
@@ -104,13 +104,10 @@ export const LeftSidebarInspector = ({
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [currentState, JSON.stringify(dataQueries)]);
- const queryIcons = Object.entries(currentState['queries']).map(([key, value]) => {
+ const queryIcons = dataQueries.map((query) => {
const allDs = [...staticDataSources, ...dataSources];
-
- const icon = allDs.find((ds) => ds.kind === value.kind);
- const iconFile = icon?.plugin?.iconFile?.data ?? undefined;
- const Icon = () => getSvgIcon(icon?.kind, 16, 16, iconFile ?? undefined);
- return { iconName: key, jsx: () =>
+
- {/*
+
-
+ {showDarkModeToggle && (
+
+
+ )}
);
};
diff --git a/frontend/src/Editor/Viewer/MobileHeader.jsx b/frontend/src/Editor/Viewer/MobileHeader.jsx
index 662e349749..129cf8b3ce 100644
--- a/frontend/src/Editor/Viewer/MobileHeader.jsx
+++ b/frontend/src/Editor/Viewer/MobileHeader.jsx
@@ -1,4 +1,4 @@
-import React from 'react';
+import React, { useMemo } from 'react';
import _, { isEmpty } from 'lodash';
// eslint-disable-next-line import/no-unresolved
import LogoIcon from '@assets/images/rocket.svg';
@@ -11,6 +11,7 @@ import classNames from 'classnames';
import { useAppVersionStore } from '@/_stores/appVersionStore';
import PreviewSettings from './PreviewSettings';
import MobileNavigationMenu from './MobileNavigationMenu';
+import { useEditorStore } from '@/_stores/editorStore';
const MobileHeader = ({
showHeader,
@@ -30,6 +31,12 @@ const MobileHeader = ({
}),
shallow
);
+ const { showDarkModeToggle } = useEditorStore(
+ (state) => ({
+ showDarkModeToggle: state.appMode === 'auto',
+ }),
+ shallow
+ );
// Fetch the version parameter from the query string
const searchParams = new URLSearchParams(window.location.search);
@@ -67,6 +74,7 @@ const MobileHeader = ({
darkMode={darkMode}
changeDarkMode={changeDarkMode}
showHeader={showHeader}
+ showDarkModeToggle={showDarkModeToggle}
/>
);
@@ -80,6 +88,7 @@ const MobileHeader = ({
);
const _renderDarkModeBtn = (args) => {
+ if (!showDarkModeToggle) return null;
const styles = args?.styles ?? {};
return (
{
+const MobileNavigationMenu = ({ pages, switchPage, currentPageId, darkMode, changeDarkMode, showDarkModeToggle }) => {
const [hamburgerMenuOpen, setHamburgerMenuOpen] = useState(false);
const handlepageSwitch = (pageId) => {
setHamburgerMenuOpen(false);
@@ -102,22 +102,24 @@ const MobileNavigationMenu = ({ pages, switchPage, currentPageId, darkMode, chan
- -
-
-
+ )}
>
);
diff --git a/frontend/src/Editor/Viewer/PreviewSettings.jsx b/frontend/src/Editor/Viewer/PreviewSettings.jsx
index 3ec6acbf29..1035472e09 100644
--- a/frontend/src/Editor/Viewer/PreviewSettings.jsx
+++ b/frontend/src/Editor/Viewer/PreviewSettings.jsx
@@ -32,6 +32,7 @@ const PreviewSettings = ({ isMobileLayout, setAppDefinitionFromVersion, showHead
onVersionDelete={noop}
isEditable={false}
isViewer
+ darkMode={darkMode}
/>
)}
@@ -98,12 +99,13 @@ const PreviewSettings = ({ isMobileLayout, setAppDefinitionFromVersion, showHead
onVersionDelete={noop}
isEditable={false}
isViewer
+ darkMode={darkMode}
/>
)}
+
- +
+
+
layout
-
)}
diff --git a/frontend/src/Editor/Viewer/viewer.scss b/frontend/src/Editor/Viewer/viewer.scss
index 7d25e623df..6c3e03c088 100644
--- a/frontend/src/Editor/Viewer/viewer.scss
+++ b/frontend/src/Editor/Viewer/viewer.scss
@@ -123,7 +123,7 @@
.canvas-container::-webkit-scrollbar {
- width: 0;
+ // width: 0;
background: transparent;
}
diff --git a/frontend/src/Editor/WidgetManager.jsx b/frontend/src/Editor/WidgetManager.jsx
index a8ed78fb5d..1bf3c515dc 100644
--- a/frontend/src/Editor/WidgetManager.jsx
+++ b/frontend/src/Editor/WidgetManager.jsx
@@ -27,7 +27,7 @@ export const WidgetManager = function WidgetManager({ componentTypes, zoomLevel,
function filterComponents(value) {
if (value !== '') {
- const fuse = new Fuse(componentTypes, { keys: ['component'] });
+ const fuse = new Fuse(componentTypes, { keys: ['displayName'], shouldSort: true, threshold: 0.4 });
const results = fuse.search(value);
setFilteredComponents(results.map((result) => result.item));
} else {
@@ -81,7 +81,7 @@ export const WidgetManager = function WidgetManager({ componentTypes, zoomLevel,
const otherSection = { title: t('widgetManager.others', 'others'), items: [] };
const allWidgets = [];
- const commonItems = ['Table', 'Chart', 'Button', 'Text', 'Datepicker'];
+ const commonItems = ['Table', 'Button', 'Text', 'TextInput', 'Datepicker', 'Form'];
const formItems = [
'Form',
'TextInput',
diff --git a/frontend/src/Editor/WidgetManager/widgetConfig.js b/frontend/src/Editor/WidgetManager/widgetConfig.js
index fc2c6b77ee..4212985baa 100644
--- a/frontend/src/Editor/WidgetManager/widgetConfig.js
+++ b/frontend/src/Editor/WidgetManager/widgetConfig.js
@@ -33,106 +33,6 @@ export const widgets = [
columns: {
type: 'array',
displayName: 'Table Columns',
- // validation: {
- // schema: {
- // type: 'array',
- // element: {
- // type: 'union',
- // schemas: [
- // {
- // type: 'object',
- // object: {
- // columnType: { type: 'string' },
- // name: { type: 'string' },
- // textWrap: { type: 'string' },
- // key: { type: 'union', schemas: [{ type: 'string' }, { type: 'number' }] },
- // textColor: { type: 'string' },
- // regex: { type: 'string' },
- // minLength: { type: 'union', schemas: [{ type: 'string' }, { type: 'number' }] },
- // maxLength: { type: 'union', schemas: [{ type: 'string' }, { type: 'number' }] },
- // customRule: { type: 'string' },
- // },
- // },
- // {
- // type: 'object',
- // object: {
- // columnType: { type: 'string' },
- // name: { type: 'string' },
- // key: { type: 'union', schemas: [{ type: 'string' }, { type: 'number' }] },
- // },
- // isEditable: { type: 'boolean' },
- // },
- // {
- // type: 'object',
- // object: {
- // columnType: { type: 'string' },
- // name: { type: 'string' },
- // activeColor: { type: 'string' },
- // isEditable: { type: 'boolean' },
- // },
- // },
- // {
- // type: 'object',
- // object: {
- // columnType: { type: 'string' },
- // name: { type: 'string' },
- // key: { type: 'union', schemas: [{ type: 'string' }, { type: 'number' }] },
- // values: {
- // type: 'union',
- // schemas: [
- // { type: 'array', element: { type: 'string' } },
- // { type: 'array', element: { type: 'number' } },
- // ],
- // },
- // labels: {
- // type: 'union',
- // schemas: [
- // { type: 'array', element: { type: 'string' } },
- // { type: 'array', element: { type: 'number' } },
- // ],
- // },
- // },
- // isEditable: { type: 'boolean' },
- // },
- // {
- // type: 'object',
- // object: {
- // columnType: { type: 'string' },
- // name: { type: 'string' },
- // key: { type: 'union', schemas: [{ type: 'string' }, { type: 'number' }] },
- // values: {
- // type: 'union',
- // schemas: [
- // { type: 'array', element: { type: 'string' } },
- // { type: 'array', element: { type: 'number' } },
- // ],
- // },
- // labels: {
- // type: 'union',
- // schemas: [
- // { type: 'array', element: { type: 'string' } },
- // { type: 'array', element: { type: 'number' } },
- // ],
- // },
- // },
- // isEditable: { type: 'boolean' },
- // },
- // {
- // type: 'object',
- // object: {
- // columnType: { type: 'string' },
- // name: { type: 'string' },
- // key: { type: 'union', schemas: [{ type: 'string' }, { type: 'number' }] },
- // dateFormat: { type: 'string' },
- // parseDateFormat: { type: 'string' },
- // isTimeChecked: { type: 'boolean' },
- // isEditable: { type: 'boolean' },
- // },
- // },
- // ],
- // },
- // },
- // },
},
useDynamicColumn: {
type: 'toggle',
@@ -221,7 +121,6 @@ export const widgets = [
{ displayName: 'Client side', value: 'clientSide' },
{ displayName: 'Server side', value: 'serverSide' },
],
- // defaultValue: 'clientSide',
validation: {
schema: { type: 'boolean' },
},
@@ -236,7 +135,6 @@ export const widgets = [
{ displayName: 'Client side', value: 'clientSide' },
{ displayName: 'Server side', value: 'serverSide' },
],
- // defaultValue: 'clientSide',
},
serverSideFilter: {
type: 'clientServerSwitch',
@@ -337,13 +235,27 @@ export const widgets = [
schema: { type: 'boolean' },
},
},
+ visibility: {
+ type: 'toggle',
+ displayName: 'Visibility',
+ validation: {
+ schema: { type: 'boolean' },
+ },
+ },
+ disabledState: {
+ type: 'toggle',
+ displayName: 'Disable',
+ validation: {
+ schema: { type: 'boolean' },
+ },
+ },
},
others: {
showOnDesktop: { type: 'toggle', displayName: 'Show on desktop ' },
showOnMobile: { type: 'toggle', displayName: 'Show on mobile' },
},
defaultSize: {
- width: 28.86,
+ width: 35,
height: 456,
},
events: {
@@ -365,57 +277,112 @@ export const widgets = [
validation: {
schema: { type: 'string' },
},
- },
- actionButtonRadius: {
- type: 'code',
- displayName: 'Action button radius',
- validation: {
- schema: { type: 'union', schemas: [{ type: 'string' }, { type: 'boolean' }] },
- },
+ accordian: 'Data',
},
tableType: {
type: 'select',
- displayName: 'Table type',
+ displayName: 'Row style',
options: [
- { name: 'Bordered', value: 'table-bordered' },
{ name: 'Regular', value: 'table-classic' },
+ { name: 'Bordered', value: 'table-bordered' },
{ name: 'Striped', value: 'table-striped' },
],
validation: {
schema: { type: 'string' },
},
+ accordian: 'Data',
},
cellSize: {
type: 'select',
- displayName: 'Cell size',
+ displayName: 'Cell height',
options: [
- { name: 'Condensed', value: 'condensed' },
{ name: 'Regular', value: 'regular' },
+ { name: 'Condensed', value: 'condensed' },
],
validation: {
schema: { type: 'string' },
},
+ accordian: 'Data',
+ },
+ contentWrap: {
+ type: 'toggle',
+ showLabel: false,
+ toggleLabel: 'Content wrap',
+ validation: {
+ schema: { type: 'union', schemas: [{ type: 'string' }, { type: 'boolean' }] },
+ },
+ accordian: 'Data',
+ },
+ maxRowHeight: {
+ type: 'switch',
+ displayName: 'Max row height',
+ validation: { schema: { type: 'string' } },
+ accordian: 'Data',
+ options: [
+ { displayName: 'Auto', value: 'auto' },
+ { displayName: 'Custom', value: 'custom' },
+ ],
+ conditionallyRender: {
+ key: 'contentWrap',
+ value: true,
+ },
+ },
+ maxRowHeightValue: {
+ type: 'tableRowHeightInput',
+ showLabel: false,
+ validation: {
+ schema: { type: 'union', schemas: [{ type: 'string' }, { type: 'boolean' }] },
+ },
+ accordian: 'Data',
+ conditionallyRender: [
+ {
+ key: 'maxRowHeight',
+ value: 'custom',
+ },
+ {
+ key: 'contentWrap',
+ value: true,
+ },
+ ],
+ },
+ actionButtonRadius: {
+ type: 'numberInput',
+ displayName: 'Button radius',
+ validation: {
+ schema: { type: 'union', schemas: [{ type: 'string' }, { type: 'boolean' }] },
+ },
+ accordian: 'Action button',
},
borderRadius: {
- type: 'code',
+ type: 'numberInput',
displayName: 'Border radius',
- validation: {
- schema: { type: 'union', schemas: [{ type: 'string' }, { type: 'number' }] },
- },
+ validation: { schema: { type: 'union', schemas: [{ type: 'string' }, { type: 'number' }] } },
+ accordian: 'Container',
},
- visibility: {
- type: 'toggle',
- displayName: 'Visibility',
+ borderColor: {
+ type: 'color',
+ displayName: 'Border',
validation: {
- schema: { type: 'boolean' },
+ schema: { type: 'string' },
+ defaultValue: false,
},
+ accordian: 'Container',
},
- disabledState: {
- type: 'toggle',
- displayName: 'Disable',
- validation: {
- schema: { type: 'boolean' },
- },
+ boxShadow: {
+ type: 'boxShadow',
+ displayName: 'Box Shadow',
+ validation: { schema: { type: 'union', schemas: [{ type: 'string' }, { type: 'number' }] } },
+ accordian: 'Container',
+ },
+ padding: {
+ type: 'switch',
+ displayName: 'Padding',
+ validation: { schema: { type: 'union', schemas: [{ type: 'string' }, { type: 'number' }] } },
+ options: [
+ { displayName: 'Default', value: 'default' },
+ { displayName: 'None', value: 'none' },
+ ],
+ accordian: 'Container',
},
},
exposedVariables: {
@@ -505,7 +472,7 @@ export const widgets = [
loadingState: { value: '{{false}}' },
data: {
value:
- "{{ [ \n\t\t{ id: 1, name: 'Sarah', email: 'sarah@example.com'}, \n\t\t{ id: 2, name: 'Lisa', email: 'lisa@example.com'}, \n\t\t{ id: 3, name: 'Sam', email: 'sam@example.com'}, \n\t\t{ id: 4, name: 'Jon', email: 'jon@example.com'} \n] }}",
+ "{{ [ \n\t\t{ id: 1, name: 'Olivia Nguyen', email: 'olivia.nguyen@example.com', date: '15/05/2022', mobile_number: 9876543210, interest: ['Reading', 'Traveling','Photography'], photo: 'https://reqres.in/img/faces/7-image.jpg' }, \n\t\t{ id: 2, name: 'Liam Patel', email: 'liam.patel@example.com', date: '20/09/2021', mobile_number: 8765432109, interest: ['Cooking','Gardening','Hiking'], photo: 'https://reqres.in/img/faces/5-image.jpg' }, \n\t\t{ id: 3, name: 'Sophia Reyes', email: 'sophia.reyes@example.com', date: '01/01/2023', mobile_number: 7654321098, interest: ['Music','Dancing','Crafting'], photo: 'https://reqres.in/img/faces/3-image.jpg' }, \n\t\t{ id: 4, name: 'Jacob Hernandez', email: 'jacob.hernandez@example.com', date: '10/11/2022', mobile_number: 6543210987, interest: ['Reading', 'Traveling', 'Volunteering'], photo: 'https://reqres.in/img/faces/1-image.jpg' }, \n\t\t{ id: 5, name: 'William Sanchez', email: 'william.sanchez@example.com', date: '07/01/2021', mobile_number: 4321098765, interest: ['Music', 'Dancing', 'Hiking'], photo: 'https://reqres.in/img/faces/4-image.jpg' }, \n\t\t{ id: 6, name: 'Ethan Morales', email: 'ethan.morales@example.com', date: '05/11/2021', mobile_number: 2109876543, interest: ['Cooking', 'Traveling', 'Photography'], photo: 'https://reqres.in/img/faces/6-image.jpg' }, \n\t\t{ id: 7, name: 'Mia Tiana', email: 'mia.tiana@example.com', date: '21/11/2022', mobile_number: 1098705217, interest: ['Music', 'Gardening', 'Hiking'], photo: 'https://reqres.in/img/faces/2-image.jpg' }, \n\t\t{ id: 8, name: 'Lucas Ramirez', email: 'lucas.ramirez@example.com', date: '31/03/2023', mobile_number: 9876543210, interest: ['Reading', 'Dancing', 'Crafting'], photo: 'https://reqres.in/img/faces/9-image.jpg' }, \n\t\t{ id: 9, name: 'Alexander Vela', email: 'alexander.vela@example.com', date: '07/09/2022', mobile_number: 7654321098, interest: ['Music','Gardening','Photography'], photo: 'https://reqres.in/img/faces/8-image.jpg' }, \n\t\t{ id: 10, name: 'Michael Reyes', email: 'michael.reyes@example.com', date: '25/12/2021', mobile_number: 5432109876, interest: ['Cooking','Crafting','Volunteering'], photo: 'https://reqres.in/img/faces/10-image.jpg' } \n] }}",
},
useDynamicColumn: { value: '{{false}}' },
columnData: {
@@ -524,6 +491,7 @@ export const widgets = [
showDownloadButton: { value: '{{true}}' },
showFilterButton: { value: '{{true}}' },
autogenerateColumns: { value: true, generateNestedColumns: true },
+ isAllColumnsEditable: { value: '{{false}}' },
columns: {
value: [
{
@@ -531,18 +499,106 @@ export const widgets = [
id: 'e3ecbf7fa52c4d7210a93edb8f43776267a489bad52bd108be9588f790126737',
autogenerated: true,
fxActiveFields: [],
+ columnSize: 30,
+ columnType: 'string',
+ },
+ {
+ name: 'photo',
+ key: 'photo',
+ id: 'f23b7d134b2e490ea41e3bb8eeb8c8e37472af243bf6b70d5af294482097e3a2',
+ autogenerated: true,
+ fxActiveFields: [],
+ columnType: 'image',
+ objectFit: 'contain',
+ borderRadius: '100',
+ columnSize: 70,
},
{
name: 'name',
id: '5d2a3744a006388aadd012fcc15cc0dbcb5f9130e0fbb64c558561c97118754a',
autogenerated: true,
fxActiveFields: [],
+ columnSize: 130,
+ columnType: 'string',
},
{
name: 'email',
id: 'afc9a5091750a1bd4760e38760de3b4be11a43452ae8ae07ce2eebc569fe9a7f',
autogenerated: true,
fxActiveFields: [],
+ columnSize: 230,
+ columnType: 'string',
+ },
+ {
+ name: 'date',
+ id: '27b75c8af9d34d1eaa1f9bb7f8f9f7b0abf1823e799748c8bb57e74f53b2c1dc',
+ autogenerated: true,
+ fxActiveFields: [],
+ columnType: 'datepicker',
+ isTimeChecked: false,
+ dateFormat: 'DD/MM/YYYY',
+ parseDateFormat: 'DD/MM/YYYY',
+ isDateSelectionEnabled: true,
+ columnSize: 130,
+ },
+ {
+ name: 'mobile_number',
+ id: '9c2e3c40572a4aefb8e179ee39a0e1ac9dc2b2e6634be56e1c05be13c3d1de56',
+ autogenerated: true,
+ fxActiveFields: [],
+ columnType: 'number',
+ columnSize: 140,
+ },
+ {
+ name: 'interest',
+ key: 'interest',
+ id: 'f23b7d134b2e490ea41e3bb8eeb8c8e37472af243bf6b70d5af294482097e3a1',
+ autogenerated: true,
+ fxActiveFields: [],
+ columnType: 'newMultiSelect',
+ columnSize: 300,
+ options: [
+ {
+ label: 'Reading',
+ value: 'Reading',
+ },
+ {
+ label: 'Traveling',
+ value: 'Traveling',
+ },
+ {
+ label: 'Photography',
+ value: 'Photography',
+ },
+ {
+ label: 'Music',
+ value: 'Music',
+ },
+ {
+ label: 'Cooking',
+ value: 'Cooking',
+ },
+ {
+ label: 'Crafting',
+ value: 'Crafting',
+ },
+ {
+ label: 'Voluntering',
+ value: 'Voluntering',
+ },
+ {
+ label: 'Garndening',
+ value: 'Garndening',
+ },
+ {
+ label: 'Dancing',
+ value: 'Dancing',
+ },
+ {
+ label: 'Hiking',
+ value: 'Hiking',
+ },
+ ],
},
],
},
@@ -556,16 +612,21 @@ export const widgets = [
defaultSelectedRow: { value: '{{{"id":1}}}' },
showAddNewRowButton: { value: '{{true}}' },
allowSelection: { value: '{{true}}' },
+ visibility: { value: '{{true}}' },
+ disabledState: { value: '{{false}}' },
},
events: [],
styles: {
textColor: { value: '#000' },
actionButtonRadius: { value: '0' },
- visibility: { value: '{{true}}' },
- disabledState: { value: '{{false}}' },
cellSize: { value: 'regular' },
- borderRadius: { value: '4' },
+ borderRadius: { value: '8' },
tableType: { value: 'table-classic' },
+ maxRowHeight: { value: 'auto' },
+ maxRowHeightValue: { value: '80px' },
+ contentWrap: { value: '{{true}}' },
+ boxShadow: { value: '0px 0px 0px 0px #00000090' },
+ padding: { value: 'default' },
},
},
},
@@ -707,6 +768,7 @@ export const widgets = [
borderRadius: { value: '{{4}}' },
borderColor: { value: '#375FCF' },
disabledState: { value: '{{false}}' },
+ padding: { value: 'default' },
},
},
},
@@ -1556,7 +1618,6 @@ export const widgets = [
value: 'side',
},
},
-
backgroundColor: {
type: 'color',
displayName: 'Background',
@@ -1700,17 +1761,17 @@ export const widgets = [
},
events: [],
styles: {
- textColor: { value: '#11181C' },
- borderColor: { value: '#6A727C47' },
+ textColor: { value: '#1B1F24' },
+ borderColor: { value: '#CCD1D5' },
accentColor: { value: '#4368E3' },
- errTextColor: { value: '#DB4324' },
+ errTextColor: { value: '#D72D39' },
borderRadius: { value: '{{6}}' },
backgroundColor: { value: '#fff' },
- iconColor: { value: '#C1C8CD' },
+ iconColor: { value: '#CFD3D859' },
direction: { value: 'left' },
width: { value: '{{33}}' },
alignment: { value: 'side' },
- color: { value: '#11181C' },
+ color: { value: '#1B1F24' },
auto: { value: '{{true}}' },
padding: { value: 'default' },
boxShadow: { value: '0px 0px 0px 0px #00000040' },
@@ -1989,15 +2050,16 @@ export const widgets = [
styles: {
borderRadius: { value: '{{6}}' },
backgroundColor: { value: '#fff' },
- borderColor: { value: '#6A727C47' },
+ borderColor: { value: '#CCD1D5' },
accentColor: { value: '#4368E3' },
- errTextColor: { value: '#DB4324' },
- textColor: { value: '#232e3c' },
- iconColor: { value: '#C1C8CD' },
+ errTextColor: { value: '#D72D39' },
+ textColor: { value: '#1B1F24' },
+ color: { value: '#1B1F24' },
+
+ iconColor: { value: '#CFD3D859' },
direction: { value: 'left' },
width: { value: '{{33}}' },
alignment: { value: 'side' },
- color: { value: '#11181C' },
auto: { value: '{{true}}' },
padding: { value: 'default' },
boxShadow: { value: '0px 0px 0px 0px #00000040' },
@@ -2273,15 +2335,15 @@ export const widgets = [
styles: {
borderRadius: { value: '{{6}}' },
backgroundColor: { value: '#fff' },
- borderColor: { value: '#6A727C47' },
+ borderColor: { value: '#CCD1D5' },
accentColor: { value: '#4368E3' },
- errTextColor: { value: '#DB4324' },
- textColor: { value: '#11181C' },
- iconColor: { value: '#C1C8CD' },
+ errTextColor: { value: '#D72D39' },
+ textColor: { value: '#1B1F24' },
+ iconColor: { value: '#CFD3D859' },
direction: { value: 'left' },
width: { value: '{{33}}' },
alignment: { value: 'side' },
- color: { value: '#11181C' },
+ color: { value: '#1B1F24' },
auto: { value: '{{true}}' },
padding: { value: 'default' },
boxShadow: { value: '0px 0px 0px 0px #00000040' },
diff --git a/frontend/src/HomePage/HomePage.jsx b/frontend/src/HomePage/HomePage.jsx
index 3b6d6bdaf5..30aaac8e83 100644
--- a/frontend/src/HomePage/HomePage.jsx
+++ b/frontend/src/HomePage/HomePage.jsx
@@ -66,6 +66,7 @@ class HomePageComponent extends React.Component {
showTemplateLibraryModal: false,
app: {},
showCreateAppModal: false,
+ showCreateModuleModal: false,
showCreateAppFromTemplateModal: false,
showImportAppModal: false,
showCloneAppModal: false,
@@ -135,11 +136,11 @@ class HomePageComponent extends React.Component {
this.fetchFolders();
};
- createApp = async (appName) => {
+ createApp = async (appName, type) => {
let _self = this;
_self.setState({ creatingApp: true });
try {
- const data = await appsService.createApp({ icon: sample(iconList), name: appName });
+ const data = await appsService.createApp({ icon: sample(iconList), name: appName, type });
const workspaceId = getWorkspaceId();
_self.props.navigate(`/${workspaceId}/apps/${data.id}`);
toast.success('App created successfully!');
@@ -544,11 +545,11 @@ class HomePageComponent extends React.Component {
};
openCreateAppModal = () => {
- this.setState({ showCreateAppModal: true });
+ this.setState({ showCreateAppModal: true, showCreateModuleModal: true });
};
closeCreateAppModal = () => {
- this.setState({ showCreateAppModal: false });
+ this.setState({ showCreateAppModal: false, showCreateModuleModal: false });
};
render() {
@@ -572,6 +573,7 @@ class HomePageComponent extends React.Component {
appToBeDeleted,
app,
showCreateAppModal,
+ showCreateModuleModal,
showImportAppModal,
fileContent,
fileName,
@@ -581,13 +583,13 @@ class HomePageComponent extends React.Component {
return (
- {showCreateAppModal && (
+ {(showCreateAppModal || showCreateModuleModal) && (
+
diff --git a/frontend/src/ToolJetUI/List/list.scss b/frontend/src/ToolJetUI/List/list.scss
index 3253fb8b10..43fc26ffeb 100644
--- a/frontend/src/ToolJetUI/List/list.scss
+++ b/frontend/src/ToolJetUI/List/list.scss
@@ -75,8 +75,41 @@ button:focus:not(:focus-visible) {
}
}
-.list-menu-option-btn {
+.list-menu-option-btn{
padding: 0px !important;
background-color: var(--base);
border: 1px solid transparent;
+}
+.delete-icon-btn{
+ padding: 2px !important;
+ display: flex;
+ border: 1px solid var(--tomato7);
+ background-color: var(--base);
+}
+.copy-column-icon{
+ padding: 2px !important;
+ display: flex;
+ border: 1px solid var(--borders-default);
+ background-color: var(--surfaces-surface-01);
+ box-shadow: 0px 1px 0px 0px var(--elevation-000-box-shadow);
+ &:hover{
+ background: var(--borders-strong) !important;
+ border: 1px solid var(--borders-strong) !important;
+ }
+ &:focus{
+ background-color: var(--surfaces-surface-01) !important;
+ border: 1px solid var(--borders-default) !important;
+ box-shadow: 0px 0px 0px 4px var(--interactive-overlays-focus-outline) 0px 0px 0px 2px var(--surfaces-surface-01) !important;
+ }
+ &:active{
+ background-color: var(--borders-strong) !important;
+ border: 1px solid var(--borders-strong) !important;
+ }
+}
+
+.list-item-deprecated-column-type {
+ margin-left: 8px;
+ svg {
+ margin-bottom: 2px
+ }
}
\ No newline at end of file
diff --git a/frontend/src/ToolJetUI/Timepicker/Timepicker.jsx b/frontend/src/ToolJetUI/Timepicker/Timepicker.jsx
new file mode 100644
index 0000000000..65a3c9b2dd
--- /dev/null
+++ b/frontend/src/ToolJetUI/Timepicker/Timepicker.jsx
@@ -0,0 +1,29 @@
+import React from 'react';
+// eslint-disable-next-line import/no-unresolved
+import DatePickerComponent from 'react-datepicker';
+import './timepicker.scss';
+import cx from 'classnames';
+
+const Timepicker = ({ timeFormat, onChange, selected, maxTime, minTime, darkMode, ...props }) => {
+ return (
+
+
+ );
+};
+
+export default Timepicker;
diff --git a/frontend/src/ToolJetUI/Timepicker/timepicker.scss b/frontend/src/ToolJetUI/Timepicker/timepicker.scss
new file mode 100644
index 0000000000..8d940ae21a
--- /dev/null
+++ b/frontend/src/ToolJetUI/Timepicker/timepicker.scss
@@ -0,0 +1,23 @@
+.tj-timepicker {
+ input {
+ background-color: var(--base);
+ border: 1px solid var(--slate7) !important;
+ }
+}
+
+
+.tj-timepicker-popper {
+ height: 28px !important;
+ border: 1px solid var(--tj-text-input-widget-border-default);
+ padding: 10px;
+ background-color: transparent;
+ color: var(--text-primary);
+
+ .react-datepicker__time-container {
+ right: 0px;
+ .react-datepicker__time {
+ background-color: var(--base);
+ }
+ }
+}
+
diff --git a/frontend/src/_components/DarkModeToggle.jsx b/frontend/src/_components/DarkModeToggle.jsx
index d797fa7a17..ae7fa16fd3 100644
--- a/frontend/src/_components/DarkModeToggle.jsx
+++ b/frontend/src/_components/DarkModeToggle.jsx
@@ -3,6 +3,7 @@ import { useSpring, animated } from 'react-spring';
import OverlayTrigger from 'react-bootstrap/OverlayTrigger';
import Tooltip from 'react-bootstrap/Tooltip';
import { useTranslation } from 'react-i18next';
+import classnames from 'classnames';
export const DarkModeToggle = function DarkModeToggle({
darkMode = false,
@@ -13,6 +14,7 @@ export const DarkModeToggle = function DarkModeToggle({
const toggleDarkMode = () => {
switchDarkMode(!darkMode);
};
+
const { t } = useTranslation();
const properties = {
sun: {
@@ -51,6 +53,7 @@ export const DarkModeToggle = function DarkModeToggle({
{
+const Accordion = ({ items, className = '' }) => {
return (
-
+
{items.map(({ title, isOpen, children }, index) => {
// eslint-disable-next-line react/no-children-prop
return |