mirror of
https://github.com/ToolJet/ToolJet
synced 2026-05-23 00:48:25 +00:00
Merge branch 'main' into release/community
This commit is contained in:
commit
76300cf23c
142 changed files with 9064 additions and 2952 deletions
2
.version
2
.version
|
|
@ -1 +1 @@
|
|||
2.35.4
|
||||
2.36.0
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
2.35.4
|
||||
2.36.0
|
||||
|
|
|
|||
|
|
@ -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": {
|
||||
|
|
|
|||
|
|
@ -355,7 +355,7 @@
|
|||
"blankPage": {
|
||||
"welcomeToToolJet": "¡Bienvenido a ToolJet!",
|
||||
"getStartedCreateNewApp": "Puedes empezar creando una nueva aplicación o creando una aplicación usando una plantilla en la biblioteca de ToolJet.",
|
||||
"importApplication": "Importar una aplicación",
|
||||
"importApplication": "Importar una aplicación"
|
||||
},
|
||||
"foldersSection": {
|
||||
"allApplications": "Todas las aplicaciones",
|
||||
|
|
@ -387,11 +387,11 @@
|
|||
"templateCard": {
|
||||
"use": "Usar",
|
||||
"preview": "Prevista",
|
||||
"leadGeneretion": "Generación de líderes",
|
||||
"leadGeneretion": "Generación de líderes"
|
||||
},
|
||||
"templateLibraryModal": {
|
||||
"select": "Seleccionar plantilla",
|
||||
"createAppfromTemplate": "Crear aplicación desde plantilla",
|
||||
"createAppfromTemplate": "Crear aplicación desde plantilla"
|
||||
}
|
||||
},
|
||||
"confirmationPage": {
|
||||
|
|
@ -662,7 +662,7 @@
|
|||
"setColor": "Establecer color",
|
||||
"structure": "Estructura",
|
||||
"checkedValues": "Valores marcados",
|
||||
"expandedValues": "Valores expandidos",
|
||||
"expandedValues": "Valores expandidos"
|
||||
},
|
||||
"Table": {
|
||||
"displayName": "Tabla",
|
||||
|
|
@ -689,7 +689,7 @@
|
|||
"remove": "Eliminar",
|
||||
"addButton": "+ Agregar botón",
|
||||
"addColumn": "+ Agregar columna",
|
||||
"noActionMessage": "Esta tabla no tiene botones de acción",
|
||||
"noActionMessage": "Esta tabla no tiene botones de acción"
|
||||
},
|
||||
"Button": {
|
||||
"displayName": "Botón",
|
||||
|
|
@ -898,7 +898,7 @@
|
|||
"text": "Comentarios",
|
||||
"tip": "Habilitar comentarios",
|
||||
"commentBody": "No hay comentarios para desplegar",
|
||||
"typeComment": "Escriba su comentario aquí",
|
||||
"typeComment": "Escriba su comentario aquí"
|
||||
},
|
||||
"Settings": {
|
||||
"text": "Ajustes",
|
||||
|
|
@ -907,7 +907,7 @@
|
|||
"maintenanceMode": "Modo de mantenimiento",
|
||||
"maxWidthOfCanvas": "Ancho máximo del lienzo",
|
||||
"maxHeightOfCanvas": "Altura máxima del lienzo",
|
||||
"backgroundColorOfCanvas": "Color de fondo del lienzo",
|
||||
"backgroundColorOfCanvas": "Color de fondo del lienzo"
|
||||
},
|
||||
"Back": {
|
||||
"text": "Atrás",
|
||||
|
|
|
|||
13
frontend/package-lock.json
generated
13
frontend/package-lock.json
generated
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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 (
|
||||
<Suspense fallback={null}>
|
||||
<BrowserRouter basename={window.public_config?.SUB_PATH || '/'}>
|
||||
<AppWithRouter props={props} />
|
||||
<AppWithRouter props={props} isAppDarkMode={isAppDarkMode} />
|
||||
</BrowserRouter>
|
||||
</Suspense>
|
||||
);
|
||||
};
|
||||
|
||||
class AppComponent extends React.Component {
|
||||
static contextType = ModuleContext;
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
|
|
@ -55,6 +60,7 @@ class AppComponent extends React.Component {
|
|||
currentUser: null,
|
||||
fetchedMetadata: false,
|
||||
darkMode: localStorage.getItem('darkMode') === 'true',
|
||||
isEditorOrViewer: '',
|
||||
};
|
||||
}
|
||||
updateSidebarNAV = (val) => {
|
||||
|
|
@ -62,7 +68,7 @@ class AppComponent extends React.Component {
|
|||
};
|
||||
fetchMetadata = () => {
|
||||
tooljetService.fetchMetaData().then((data) => {
|
||||
useAppDataStore.getState().actions.setMetadata(data);
|
||||
useSuperStore.getState().modules[this.context].useAppDataStore.getState().actions.setMetadata(data);
|
||||
localStorage.setItem('currentVersion', data.installed_version);
|
||||
if (data.latest_version && lt(data.installed_version, data.latest_version) && data.version_ignored === false) {
|
||||
this.setState({ updateAvailable: true });
|
||||
|
|
@ -98,19 +104,19 @@ class AppComponent extends React.Component {
|
|||
|
||||
switchDarkMode = (newMode) => {
|
||||
this.setState({ darkMode: newMode });
|
||||
useSuperStore.getState().modules[this.context].useAppDataStore.getState().actions.updateIsTJDarkMode(newMode);
|
||||
localStorage.setItem('darkMode', newMode);
|
||||
};
|
||||
|
||||
render() {
|
||||
const { updateAvailable, darkMode } = this.state;
|
||||
|
||||
const { updateAvailable, darkMode, isEditorOrViewer } = this.state;
|
||||
let toastOptions = {
|
||||
style: {
|
||||
wordBreak: 'break-all',
|
||||
},
|
||||
};
|
||||
|
||||
if (darkMode) {
|
||||
if (isEditorOrViewer === 'viewer' ? this.props.isAppDarkMode : darkMode) {
|
||||
toastOptions = {
|
||||
className: 'toast-dark-mode',
|
||||
style: {
|
||||
|
|
@ -125,7 +131,12 @@ class AppComponent extends React.Component {
|
|||
const { updateSidebarNAV } = this;
|
||||
return (
|
||||
<>
|
||||
<div className={`main-wrapper ${darkMode ? 'theme-dark dark-theme' : ''}`} data-cy="main-wrapper">
|
||||
<div
|
||||
className={cx('main-wrapper', {
|
||||
'theme-dark dark-theme': !isEditorOrViewer && darkMode,
|
||||
})}
|
||||
data-cy="main-wrapper"
|
||||
>
|
||||
{updateAvailable && (
|
||||
<div className="alert alert-info alert-dismissible" role="alert">
|
||||
<h3 className="mb-1">Update available</h3>
|
||||
|
|
@ -181,7 +192,11 @@ class AppComponent extends React.Component {
|
|||
path="/:workspaceId/apps/:slug/:pageHandle?/*"
|
||||
element={
|
||||
<PrivateRoute>
|
||||
<AppLoader switchDarkMode={this.switchDarkMode} darkMode={darkMode} />
|
||||
<AppLoader
|
||||
switchDarkMode={this.switchDarkMode}
|
||||
darkMode={darkMode}
|
||||
setEditorOrViewer={(value) => this.setState({ isEditorOrViewer: value })}
|
||||
/>
|
||||
</PrivateRoute>
|
||||
}
|
||||
/>
|
||||
|
|
@ -199,7 +214,11 @@ class AppComponent extends React.Component {
|
|||
path="/applications/:slug/:pageHandle?"
|
||||
element={
|
||||
<PrivateRoute>
|
||||
<Viewer switchDarkMode={this.switchDarkMode} darkMode={darkMode} />
|
||||
<Viewer
|
||||
switchDarkMode={this.switchDarkMode}
|
||||
darkMode={this.props.isAppDarkMode}
|
||||
setEditorOrViewer={(value) => this.setState({ isEditorOrViewer: value })}
|
||||
/>
|
||||
</PrivateRoute>
|
||||
}
|
||||
/>
|
||||
|
|
@ -208,7 +227,11 @@ class AppComponent extends React.Component {
|
|||
path="/applications/:slug/versions/:versionId/:pageHandle?"
|
||||
element={
|
||||
<PrivateRoute>
|
||||
<Viewer switchDarkMode={this.switchDarkMode} darkMode={darkMode} />
|
||||
<Viewer
|
||||
switchDarkMode={this.switchDarkMode}
|
||||
darkMode={this.props.isAppDarkMode}
|
||||
setEditorOrViewer={(value) => this.setState({ isEditorOrViewer: value })}
|
||||
/>
|
||||
</PrivateRoute>
|
||||
}
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@ export const AppVersionsManager = function ({
|
|||
onVersionDelete,
|
||||
isEditable = true,
|
||||
isViewer,
|
||||
darkMode,
|
||||
}) {
|
||||
const [appVersionStatus, setGetAppVersionStatus] = useState(appVersionLoadingStatus.loading);
|
||||
const [deleteVersion, setDeleteVersion] = useState({
|
||||
|
|
@ -53,8 +54,6 @@ export const AppVersionsManager = function ({
|
|||
};
|
||||
}, [appVersions]);
|
||||
|
||||
const darkMode = localStorage.getItem('darkMode') === 'true';
|
||||
|
||||
const selectVersion = (id) => {
|
||||
appVersionService
|
||||
.getAppVersionData(appId, id)
|
||||
|
|
@ -173,8 +172,8 @@ export const AppVersionsManager = function ({
|
|||
value={editingVersion?.id}
|
||||
onChange={(id) => selectVersion(id)}
|
||||
{...customSelectProps}
|
||||
className={` ${darkMode && 'dark-theme'}`}
|
||||
isEditable={isEditable}
|
||||
darkMode={darkMode}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -67,6 +67,7 @@ import { EditorContext } from '@/Editor/Context/EditorContextWrapper';
|
|||
import { useTranslation } from 'react-i18next';
|
||||
import { useCurrentState } from '@/_stores/currentStateStore';
|
||||
import { useAppInfo } from '@/_stores/appDataStore';
|
||||
import { useModuleName } from '../_contexts/ModuleContext';
|
||||
import { isPDFSupported } from '@/_stores/utils';
|
||||
|
||||
export const AllComponents = {
|
||||
|
|
@ -158,10 +159,12 @@ export const Box = memo(
|
|||
isResizing,
|
||||
adjustHeightBasedOnAlignment,
|
||||
currentLayout,
|
||||
darkMode,
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
const backgroundColor = yellow ? 'yellow' : '';
|
||||
const currentState = useCurrentState();
|
||||
const moduleName = useModuleName();
|
||||
const { events } = useAppInfo();
|
||||
const shouldAddBoxShadowAndVisibility = ['TextInput', 'PasswordInput', 'NumberInput', 'Text'];
|
||||
|
||||
|
|
@ -204,7 +207,6 @@ export const Box = memo(
|
|||
? validateProperties(resolvedGeneralStyles, componentMeta.generalStyles)
|
||||
: [resolvedGeneralStyles, []];
|
||||
|
||||
const darkMode = localStorage.getItem('darkMode') === 'true';
|
||||
const { variablesExposedForPreview, exposeToCodeHinter } = useContext(EditorContext) || {};
|
||||
|
||||
let styles = {
|
||||
|
|
|
|||
|
|
@ -43,6 +43,8 @@ import { Input } from './Elements/Input';
|
|||
import { Icon } from './Elements/Icon';
|
||||
import { Visibility } from './Elements/Visibility';
|
||||
import { NumberInput } from './Elements/NumberInput';
|
||||
import TableRowHeightInput from './Elements/TableRowHeightInput';
|
||||
|
||||
import { validateProperty } from '../component-properties-validation';
|
||||
|
||||
const HIDDEN_CODE_HINTER_LABELS = ['Table data', 'Column data', 'Text Format', 'TextComponentTextInput'];
|
||||
|
|
@ -63,6 +65,7 @@ const AllElements = {
|
|||
Icon,
|
||||
Visibility,
|
||||
NumberInput,
|
||||
TableRowHeightInput,
|
||||
};
|
||||
|
||||
export function CodeHinter({
|
||||
|
|
@ -407,7 +410,15 @@ export function CodeHinter({
|
|||
|
||||
const fxBtn = () => (
|
||||
<div className="col-auto pt-0 fx-common">
|
||||
{!['Type', 'selectRowOnCellEdit', 'Select row on cell edit', ' ', 'Padding', 'Width'].includes(paramLabel) && ( //add some key if these extends
|
||||
{![
|
||||
'Type',
|
||||
'selectRowOnCellEdit',
|
||||
'Select row on cell edit',
|
||||
' ',
|
||||
'Padding',
|
||||
'Width',
|
||||
'Make all columns editable',
|
||||
].includes(paramLabel) && ( //add some key if these extends
|
||||
<FxButton
|
||||
active={codeShow}
|
||||
onPress={() => {
|
||||
|
|
|
|||
|
|
@ -29,7 +29,7 @@ export const BoxShadow = ({ value, onChange, cyLabel }) => {
|
|||
|
||||
const colorPickerStyle = {
|
||||
position: 'absolute',
|
||||
bottom: '260px',
|
||||
top: '-220px',
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
|
|
|
|||
|
|
@ -45,8 +45,9 @@ export const Color = ({ value, onChange, pickerStyle = {}, cyLabel, asBoxShadowP
|
|||
// This is fix when color picker don't have much space to open in bottom side
|
||||
{ 'inspector-color-input-popover': colorPickerPosition === 'top' }
|
||||
)}
|
||||
style={{ zIndex: 10000 }}
|
||||
>
|
||||
<Popover.Body className={!asBoxShadowPopover && 'boxshadow-picker'}>
|
||||
<Popover.Body className={!asBoxShadowPopover && 'boxshadow-picker'} style={{ padding: '0px' }}>
|
||||
<>{ColorPicker()}</>
|
||||
</Popover.Body>
|
||||
</Popover>
|
||||
|
|
|
|||
|
|
@ -2,60 +2,82 @@ import React from 'react';
|
|||
import SelectComponent from '@/_ui/Select';
|
||||
import { components } from 'react-select';
|
||||
import Check from '@/_ui/Icon/solidIcons/Check';
|
||||
import Icon from '@/_ui/Icon/solidIcons/index';
|
||||
import {
|
||||
DeprecatedColumnTooltip,
|
||||
checkIfTableColumnDeprecated,
|
||||
} from '../../Inspector/Components/Table/ColumnManager/DeprecatedColumnTypeMsg';
|
||||
|
||||
const Option = (props) => {
|
||||
export const Option = (props) => {
|
||||
const isDeprecated = checkIfTableColumnDeprecated(props.value);
|
||||
return (
|
||||
<components.Option {...props}>
|
||||
<div className="d-flex justify-content-between">
|
||||
<span>{props.label}</span>
|
||||
{props.isSelected && (
|
||||
<span>
|
||||
<Check width={'20'} fill={'#3E63DD'} />
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
<DeprecatedColumnTooltip columnType={props.value}>
|
||||
<div className="d-flex justify-content-between">
|
||||
<span>{props.label}</span>
|
||||
{props.isSelected && (
|
||||
<span>
|
||||
<Check width={'20'} fill={'#3E63DD'} />
|
||||
</span>
|
||||
)}
|
||||
{isDeprecated && (
|
||||
<span>
|
||||
<Icon name={'warning'} height={16} width={16} fill="#DB4324" />
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
</DeprecatedColumnTooltip>
|
||||
</components.Option>
|
||||
);
|
||||
};
|
||||
|
||||
const selectCustomStyles = {
|
||||
control: (base, state) => {
|
||||
return {
|
||||
...base,
|
||||
border: state.isFocused ? '1px solid #3E63DD' : '1px solid #cccccc',
|
||||
boxShadow: state.isFocused ? '0px 0px 6px #3E63DD' : 'none',
|
||||
backgroundColor: state.isFocused ? 'var(--indigo2)' : 'var(--base)',
|
||||
'&:hover': {
|
||||
border: '1px solid #3E63DD !important',
|
||||
boxShadow: '0px 0px 6px #3E63DD',
|
||||
},
|
||||
borderRadius: '6px',
|
||||
width: '144px',
|
||||
minHeight: '32px',
|
||||
};
|
||||
},
|
||||
const selectCustomStyles = (width) => {
|
||||
return {
|
||||
control: (base, state) => {
|
||||
return {
|
||||
...base,
|
||||
border: state.isFocused ? '1px solid #3E63DD' : '1px solid #cccccc',
|
||||
boxShadow: state.isFocused ? '0px 0px 6px #3E63DD' : 'none',
|
||||
backgroundColor: state.isFocused ? 'var(--indigo2)' : 'var(--base)',
|
||||
'&:hover': {
|
||||
border: '1px solid #3E63DD !important',
|
||||
boxShadow: '0px 0px 6px #3E63DD',
|
||||
},
|
||||
borderRadius: '6px',
|
||||
width: width,
|
||||
minHeight: '32px',
|
||||
color: 'var(--slate12)',
|
||||
};
|
||||
},
|
||||
|
||||
dropdownIndicator: (base) => ({
|
||||
...base,
|
||||
padding: '4px',
|
||||
}),
|
||||
menuList: (base) => ({
|
||||
...base,
|
||||
padding: '4px',
|
||||
}),
|
||||
option: (base, state) => ({
|
||||
...base,
|
||||
backgroundColor: state.isFocused ? '#F0F4FF !important' : 'white',
|
||||
color: '#11181C',
|
||||
borderRadius: '6px',
|
||||
}),
|
||||
dropdownIndicator: (base) => ({
|
||||
...base,
|
||||
padding: '4px',
|
||||
}),
|
||||
menuList: (base) => ({
|
||||
...base,
|
||||
padding: '4px',
|
||||
}),
|
||||
option: (base, state) => ({
|
||||
...base,
|
||||
backgroundColor: state.isFocused ? '#F0F4FF !important' : 'white',
|
||||
color: '#11181C',
|
||||
borderRadius: '6px',
|
||||
}),
|
||||
singleValue: (provided) => ({
|
||||
...provided,
|
||||
color: 'var(--slate12)',
|
||||
}),
|
||||
};
|
||||
};
|
||||
|
||||
export const Select = ({ value, onChange, meta }) => {
|
||||
export const Select = ({ value, onChange, meta, width = '144px' }) => {
|
||||
return (
|
||||
<div
|
||||
className="row fx-container"
|
||||
data-cy={`dropdown-${meta.displayName ? String(meta.displayName).toLowerCase().replace(/\s+/g, '-') : 'common'}`}
|
||||
data-cy={`dropdown-${
|
||||
meta?.displayName ? String(meta?.displayName).toLowerCase().replace(/\s+/g, '-') : 'common'
|
||||
}`}
|
||||
>
|
||||
<div className="field" onClick={(e) => e.stopPropagation()}>
|
||||
<SelectComponent
|
||||
|
|
@ -65,7 +87,7 @@ export const Select = ({ value, onChange, meta }) => {
|
|||
onChange={onChange}
|
||||
width={224}
|
||||
height={32}
|
||||
styles={selectCustomStyles}
|
||||
styles={selectCustomStyles(width)}
|
||||
useCustomStyles={true}
|
||||
classNamePrefix="inspector-select"
|
||||
components={{
|
||||
|
|
|
|||
|
|
@ -30,7 +30,7 @@ function Slider1({ value, onChange, component }) {
|
|||
};
|
||||
|
||||
return (
|
||||
<div className="d-flex flex-column " style={{ width: '142px', position: 'relative' }}>
|
||||
<div className="d-flex flex-column " style={{ width: '142px', marginBottom: '16px', position: 'relative' }}>
|
||||
<CustomInput
|
||||
disabled={component.component.definition.styles.auto.value}
|
||||
value={sliderValue}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,51 @@
|
|||
import React, { useEffect, useState } from 'react';
|
||||
|
||||
const MIN_TABLE_ROW_HEIGHT_CONDENSED = 39;
|
||||
const MIN_TABLE_ROW_HEIGHT_DEFAULT = 45;
|
||||
|
||||
const TableRowHeightInput = ({ value, onChange, cyLabel, staticText, component: { component } }) => {
|
||||
const [inputValue, setInputValue] = useState(value);
|
||||
|
||||
useEffect(() => {
|
||||
setInputValue(value < minValue ? minValue : value);
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [value, component?.definition?.styles?.cellSize?.value]);
|
||||
|
||||
const minValue =
|
||||
component?.definition?.styles?.cellSize?.value === 'condensed'
|
||||
? MIN_TABLE_ROW_HEIGHT_CONDENSED
|
||||
: MIN_TABLE_ROW_HEIGHT_DEFAULT;
|
||||
|
||||
const handleBlur = () => {
|
||||
const newValue = Math.max(inputValue, minValue);
|
||||
setInputValue(newValue);
|
||||
onChange(newValue);
|
||||
};
|
||||
|
||||
const handleChange = (e) => {
|
||||
setInputValue(e.target.value);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="form-text tj-number-input-element">
|
||||
<input
|
||||
style={{ width: '142px', height: '32px' }}
|
||||
data-cy={`${cyLabel}-input`}
|
||||
type="number"
|
||||
className="tj-input-element tj-text-xsm"
|
||||
value={inputValue}
|
||||
placeholder=""
|
||||
id="labelId"
|
||||
min={minValue}
|
||||
onChange={handleChange}
|
||||
onBlur={handleBlur}
|
||||
autoComplete="off"
|
||||
/>
|
||||
<label htmlFor="labelId" className="static-value tj-text-xsm">
|
||||
{staticText ? staticText : 'px'}
|
||||
</label>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default TableRowHeightInput;
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
import React from 'react';
|
||||
|
||||
export const Toggle = ({ value, onChange, cyLabel }) => {
|
||||
export const Toggle = ({ value, onChange, cyLabel, meta }) => {
|
||||
return (
|
||||
<div className="row fx-container">
|
||||
<div className="col d-flex align-items-center">
|
||||
|
|
@ -9,6 +9,14 @@ export const Toggle = ({ value, onChange, cyLabel }) => {
|
|||
className="form-check form-switch mb-0 d-flex justify-content-end"
|
||||
style={{ marginBottom: '0px', paddingLeft: '28px' }}
|
||||
>
|
||||
{meta.toggleLabel && (
|
||||
<span
|
||||
className="font-weight-400 font-size-12 d-flex align-items-center color-slate12"
|
||||
style={{ marginRight: '78px' }}
|
||||
>
|
||||
{meta.toggleLabel}
|
||||
</span>
|
||||
)}
|
||||
<input
|
||||
className="form-check-input"
|
||||
type="checkbox"
|
||||
|
|
|
|||
|
|
@ -17,4 +17,5 @@ export const TypeMapping = {
|
|||
icon: 'Icon',
|
||||
visibility: 'Visibility',
|
||||
numberInput: 'NumberInput',
|
||||
tableRowHeightInput: 'TableRowHeightInput',
|
||||
};
|
||||
|
|
|
|||
|
|
@ -121,6 +121,7 @@ export const Chart = function Chart({
|
|||
b: padding,
|
||||
t: padding,
|
||||
},
|
||||
...(chartLayout.annotations && { annotations: chartLayout.annotations }),
|
||||
barmode: barmode,
|
||||
hoverlabel: { namelength: -1 },
|
||||
};
|
||||
|
|
|
|||
|
|
@ -127,7 +127,6 @@ export const Listview = function Listview({
|
|||
key={index}
|
||||
data-cy={`${String(component.name).toLowerCase()}-row-${index}`}
|
||||
onClick={(event) => {
|
||||
event.preventDefault();
|
||||
onRecordClicked(index);
|
||||
onRowClicked(index);
|
||||
}}
|
||||
|
|
|
|||
|
|
@ -42,6 +42,7 @@ export const Modal = function Modal({
|
|||
boxShadow,
|
||||
} = styles;
|
||||
const parentRef = useRef(null);
|
||||
const isInitialRender = useRef(true);
|
||||
|
||||
const title = properties.title ?? '';
|
||||
const size = properties.size ?? 'lg';
|
||||
|
|
@ -62,14 +63,24 @@ export const Modal = function Modal({
|
|||
}, [setShowModal]);
|
||||
|
||||
useEffect(() => {
|
||||
if (isInitialRender.current) {
|
||||
isInitialRender.current = false;
|
||||
return;
|
||||
}
|
||||
const canShowModal = exposedVariables.show ?? false;
|
||||
setShowModal(exposedVariables.show ?? false);
|
||||
fireEvent(canShowModal ? 'onOpen' : 'onClose');
|
||||
setShowModal(exposedVariables.show ?? false);
|
||||
const inputRef = document?.getElementsByClassName('tj-text-input-widget')?.[0];
|
||||
inputRef?.blur();
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [exposedVariables.show]);
|
||||
|
||||
function hideModal() {
|
||||
setShowModal(false);
|
||||
setExposedVariable('show', false);
|
||||
fireEvent('onClose');
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
const handleModalOpen = () => {
|
||||
const canvasElement = document.getElementsByClassName('canvas-area')[0];
|
||||
|
|
@ -127,11 +138,6 @@ export const Modal = function Modal({
|
|||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [showModal, modalHeight]);
|
||||
|
||||
function hideModal() {
|
||||
setShowModal(false);
|
||||
setExposedVariable('show', false);
|
||||
fireEvent('onClose');
|
||||
}
|
||||
const backwardCompatibilityCheck = height == '34' || modalHeight != undefined ? true : false;
|
||||
|
||||
const customStyles = {
|
||||
|
|
|
|||
|
|
@ -39,7 +39,7 @@ export const NumberInput = function NumberInput({
|
|||
accentColor,
|
||||
} = styles;
|
||||
|
||||
const textColor = darkMode && ['#232e3c', '#000000ff'].includes(styles.textColor) ? '#fff' : styles.textColor;
|
||||
const textColor = darkMode && ['#232e3c', '#000000ff'].includes(styles.textColor) ? '#CFD3D8' : styles.textColor;
|
||||
const isMandatory = resolveReferences(component?.definition?.validation?.mandatory?.value, currentState) ?? false;
|
||||
const minValue = resolveReferences(component?.definition?.validation?.minValue?.value, currentState) ?? null;
|
||||
const maxValue = resolveReferences(component?.definition?.validation?.maxValue?.value, currentState) ?? null;
|
||||
|
|
@ -165,22 +165,34 @@ export const NumberInput = function NumberInput({
|
|||
const computedStyles = {
|
||||
height: height == 36 ? (padding == 'default' ? '36px' : '40px') : padding == 'default' ? height : height + 4,
|
||||
borderRadius: `${borderRadius}px`,
|
||||
color: darkMode && textColor === '#11181C' ? '#ECEDEE' : textColor,
|
||||
borderColor: isFocused
|
||||
? accentColor
|
||||
: ['#D7DBDF'].includes(borderColor)
|
||||
? darkMode
|
||||
? '#6D757D7A'
|
||||
: '#6A727C47'
|
||||
: borderColor,
|
||||
'--tblr-input-border-color-darker': tinycolor(borderColor).darken(24).toString(),
|
||||
backgroundColor:
|
||||
darkMode && ['#ffffff', '#ffffffff', '#fff'].includes(backgroundColor) ? '#313538' : backgroundColor,
|
||||
|
||||
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',
|
||||
color: textColor !== '#1B1F24' ? textColor : disable || loading ? 'var(--text-disabled)' : 'var(--text-primary)',
|
||||
borderColor: isFocused
|
||||
? accentColor != '4368E3'
|
||||
? accentColor
|
||||
: 'var(--primary-accent-strong)'
|
||||
: borderColor != '#CCD1D5'
|
||||
? borderColor
|
||||
: disable || loading
|
||||
? 'var(--borders-disabled-on-white-dimmed)'
|
||||
: 'var(--borders-default)',
|
||||
'--tblr-input-border-color-darker': tinycolor(borderColor).darken(24).toString(),
|
||||
backgroundColor: !['#ffffff', '#ffffffff', '#fff'].includes(backgroundColor)
|
||||
? backgroundColor
|
||||
: disable || loading
|
||||
? darkMode
|
||||
? 'var(--surfaces-app-bg-default)'
|
||||
: 'var(--surfaces-surface-03)'
|
||||
: 'var(--surfaces-surface-01)',
|
||||
};
|
||||
|
||||
const defaultAlignment = alignment === 'side' || alignment === 'top' ? alignment : 'side';
|
||||
|
|
@ -278,7 +290,6 @@ export const NumberInput = function NumberInput({
|
|||
<>
|
||||
<div
|
||||
data-cy={`label-${String(component.name).toLowerCase()}`}
|
||||
data-disabled={disable || loading}
|
||||
className={`text-input tj-number-input-widget d-flex ${
|
||||
defaultAlignment === 'top' &&
|
||||
((width != 0 && label && label?.length != 0) || (auto && width == 0 && label && label?.length != 0))
|
||||
|
|
@ -293,6 +304,7 @@ export const NumberInput = function NumberInput({
|
|||
display: !visibility ? 'none' : 'flex',
|
||||
whiteSpace: 'nowrap',
|
||||
}}
|
||||
data-disabled={disable || loading}
|
||||
>
|
||||
<Label
|
||||
label={label}
|
||||
|
|
@ -320,7 +332,7 @@ export const NumberInput = function NumberInput({
|
|||
? '11px'
|
||||
: (label?.length > 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'
|
||||
|
|
@ -330,7 +342,7 @@ export const NumberInput = function NumberInput({
|
|||
: '50%'
|
||||
}`,
|
||||
transform: ' translateY(-50%)',
|
||||
color: iconColor,
|
||||
color: iconColor !== '#CFD3D859' ? iconColor : 'var(--icons-weak-disabled)',
|
||||
zIndex: 3,
|
||||
}}
|
||||
stroke={1.5}
|
||||
|
|
@ -369,7 +381,8 @@ export const NumberInput = function NumberInput({
|
|||
<div onClick={(e) => handleIncrement(e)}>
|
||||
<SolidIcon
|
||||
width={padding == 'default' ? `${height / 2 - 1}px` : `${height / 2 + 1}px`}
|
||||
height={`${height / 2}px`}
|
||||
height={padding == 'default' ? `${height / 2 - 1}px` : `${height / 2 + 1}px`}
|
||||
fill={'var(--icons-default)'}
|
||||
style={{
|
||||
top: defaultAlignment === 'top' && label?.length > 0 && width > 0 ? '21px' : '1px',
|
||||
right:
|
||||
|
|
@ -378,19 +391,26 @@ export const NumberInput = function NumberInput({
|
|||
: alignment == 'side' && direction === 'right'
|
||||
? `${labelWidth + 1}px`
|
||||
: '1px',
|
||||
borderLeft: darkMode ? '1px solid #313538' : '1px solid #D7D7D7',
|
||||
borderBottom: darkMode ? '.5px solid #313538' : '0.5px solid #D7D7D7',
|
||||
borderLeft:
|
||||
disable || loading
|
||||
? '1px solid var(--borders-weak-disabled)'
|
||||
: '1px solid var(--borders-default)',
|
||||
borderBottom:
|
||||
disable || loading
|
||||
? '1px solid var(--borders-weak-disabled)'
|
||||
: '.5px solid var(--borders-default)',
|
||||
borderTopRightRadius: borderRadius - 1,
|
||||
backgroundColor: !darkMode ? 'white' : 'black',
|
||||
backgroundColor: 'transparent',
|
||||
zIndex: 3,
|
||||
}}
|
||||
className="numberinput-up-arrow arrow"
|
||||
name="cheveronup"
|
||||
className="numberinput-up-arrow arrow number-input-arrow"
|
||||
name="TriangleDownCenter"
|
||||
></SolidIcon>
|
||||
</div>
|
||||
|
||||
<div onClick={(e) => handleDecrement(e)}>
|
||||
<SolidIcon
|
||||
fill={'var(--icons-default)'}
|
||||
style={{
|
||||
right:
|
||||
labelWidth == 0
|
||||
|
|
@ -399,30 +419,37 @@ export const NumberInput = function NumberInput({
|
|||
? `${labelWidth + 1}px`
|
||||
: '1px',
|
||||
bottom: '1px',
|
||||
borderLeft: darkMode ? '1px solid #313538' : '1px solid #D7D7D7',
|
||||
borderTop: darkMode ? '0.5px solid #313538' : '0.5px solid #D7D7D7',
|
||||
borderLeft:
|
||||
disable || loading
|
||||
? '1px solid var(--borders-weak-disabled)'
|
||||
: '1px solid var(--borders-default)',
|
||||
borderTop:
|
||||
disable || loading
|
||||
? '1px solid var(--borders-weak-disabled)'
|
||||
: '.5px solid var(--borders-default)',
|
||||
borderBottomRightRadius: borderRadius - 1,
|
||||
backgroundColor: !darkMode ? 'white' : 'black',
|
||||
backgroundColor: 'transparent',
|
||||
zIndex: 3,
|
||||
}}
|
||||
width={padding == 'default' ? `${height / 2 - 1}px` : `${height / 2 + 1}px`}
|
||||
height={`${height / 2}px`}
|
||||
className="numberinput-down-arrow arrow"
|
||||
name="cheverondown"
|
||||
height={padding == 'default' ? `${height / 2 - 1}px` : `${height / 2 + 1}px`}
|
||||
className="numberinput-down-arrow arrow number-input-arrow"
|
||||
name="TriangleUpCenter"
|
||||
></SolidIcon>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
|
||||
{loading && <Loader style={{ ...loaderStyle }} width="16" />}
|
||||
</div>
|
||||
{showValidationError && visibility && (
|
||||
<div
|
||||
className="tj-text-sm"
|
||||
data-cy={`${String(component.name).toLowerCase()}-invalid-feedback`}
|
||||
style={{
|
||||
color: errTextColor,
|
||||
color: errTextColor !== '#D72D39' ? errTextColor : 'var(--status-error-strong)',
|
||||
textAlign: direction == 'left' && 'end',
|
||||
fontSize: '11px',
|
||||
fontWeight: '400',
|
||||
lineHeight: '16px',
|
||||
}}
|
||||
>
|
||||
{showValidationError && validationError}
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ export const Pagination = ({
|
|||
fireEvent,
|
||||
darkMode,
|
||||
dataCy,
|
||||
width,
|
||||
}) => {
|
||||
const { visibility, disabledState, boxShadow } = styles;
|
||||
const [currentPage, setCurrentPage] = useState(() => properties?.defaultPageIndex ?? 1);
|
||||
|
|
@ -64,12 +65,7 @@ export const Pagination = ({
|
|||
};
|
||||
|
||||
return (
|
||||
<div
|
||||
data-disabled={disabledState}
|
||||
className="d-flex align-items-center px-1"
|
||||
data-cy={dataCy}
|
||||
style={{ boxShadow }}
|
||||
>
|
||||
<div data-disabled={disabledState} className="d-flex align-items-center" data-cy={dataCy} style={{ boxShadow }}>
|
||||
<ul className="pagination m-0" style={computedStyles}>
|
||||
<Pagination.Operator
|
||||
operator="<<"
|
||||
|
|
@ -90,6 +86,7 @@ export const Pagination = ({
|
|||
totalPages={properties.numberOfPages}
|
||||
callback={gotoPage}
|
||||
darkMode={darkMode}
|
||||
containerWidth={width}
|
||||
/>
|
||||
<Pagination.Operator
|
||||
operator=">"
|
||||
|
|
@ -139,7 +136,6 @@ function getOperator(operator) {
|
|||
<polyline points="17 7 12 12 17 17" />
|
||||
</svg>
|
||||
);
|
||||
|
||||
case '>>':
|
||||
return (
|
||||
<svg
|
||||
|
|
@ -159,7 +155,6 @@ function getOperator(operator) {
|
|||
<polyline points="13 7 18 12 13 17" />
|
||||
</svg>
|
||||
);
|
||||
|
||||
case '<':
|
||||
return (
|
||||
<svg
|
||||
|
|
@ -178,7 +173,6 @@ function getOperator(operator) {
|
|||
<polyline points="15 6 9 12 15 18"></polyline>
|
||||
</svg>
|
||||
);
|
||||
|
||||
case '>':
|
||||
return (
|
||||
<svg
|
||||
|
|
@ -197,7 +191,6 @@ function getOperator(operator) {
|
|||
<polyline points="9 6 15 12 9 18"></polyline>
|
||||
</svg>
|
||||
);
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
|
@ -222,19 +215,100 @@ const Operator = ({ operator, currentPage, totalPages, handleOnClick, darkMode }
|
|||
);
|
||||
};
|
||||
|
||||
const PageLinks = ({ currentPage, totalPages, callback, darkMode }) => {
|
||||
return Array.from(Array(totalPages).keys()).map((index) => {
|
||||
const pageNumber = index + 1;
|
||||
return (
|
||||
<li
|
||||
key={pageNumber}
|
||||
onClick={() => callback(pageNumber)}
|
||||
className={`page-item ${currentPage === pageNumber ? 'active' : ''}`}
|
||||
>
|
||||
<a className={`page-link ${darkMode && 'text-light'}`}>{pageNumber}</a>
|
||||
</li>
|
||||
);
|
||||
});
|
||||
const PageLinks = ({ currentPage, totalPages, callback, darkMode, containerWidth }) => {
|
||||
const itemWidth = 28; // Width of each item
|
||||
|
||||
const [maxItems, setMaxItems] = useState(0); // for max items that can fit in container
|
||||
|
||||
useEffect(() => {
|
||||
if (!containerWidth || isNaN(totalPages) || totalPages <= 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Calculate the maximum number of items that can fit within the container width
|
||||
const availableWidth = containerWidth;
|
||||
const calculatedMaxItems = Math.floor(availableWidth / itemWidth);
|
||||
setMaxItems(calculatedMaxItems);
|
||||
}, [containerWidth, totalPages]);
|
||||
|
||||
const renderPageNumbers = () => {
|
||||
const pageNumbers = [];
|
||||
|
||||
// Calculate the number of page numbers that can fit, excluding arrows
|
||||
const maxPageNumbers = maxItems - 4;
|
||||
|
||||
// Calculate the starting and ending page numbers based on the current page and the total pages
|
||||
let startPage = 1;
|
||||
let endPage = totalPages;
|
||||
|
||||
// Adjust startPage and endPage if total pages exceed the maximum displayable page numbers
|
||||
if (totalPages > maxPageNumbers) {
|
||||
const leftPageNumbers = Math.ceil(maxPageNumbers / 2) - 1;
|
||||
const rightPageNumbers = maxPageNumbers - leftPageNumbers - 1;
|
||||
|
||||
startPage = currentPage - leftPageNumbers;
|
||||
endPage = currentPage + rightPageNumbers;
|
||||
|
||||
// Ensure startPage and endPage are within valid ranges
|
||||
if (startPage <= 1) {
|
||||
startPage = 1;
|
||||
endPage = maxPageNumbers;
|
||||
} else if (endPage >= totalPages) {
|
||||
endPage = totalPages;
|
||||
startPage = totalPages - maxPageNumbers + 1;
|
||||
}
|
||||
}
|
||||
|
||||
// Render page numbers
|
||||
for (let i = startPage; i <= endPage; i++) {
|
||||
pageNumbers.push(
|
||||
<li key={i} onClick={() => callback(i)} className={`page-item ${currentPage === i ? 'active' : ''}`}>
|
||||
<a className={`page-link ${darkMode && 'text-light'}`}>{i}</a>
|
||||
</li>
|
||||
);
|
||||
}
|
||||
// If total pages exceed the maximum displayable page numbers, add ellipsis
|
||||
if (totalPages > maxPageNumbers) {
|
||||
// If there is enough space, remove one number and add ellipsis
|
||||
if (endPage < totalPages) {
|
||||
pageNumbers.pop(); // Remove one number for ellipsis
|
||||
pageNumbers.pop(); // Remove one number for 1
|
||||
|
||||
pageNumbers.push(
|
||||
<li key="right-ellipsis" className="page-item">
|
||||
<span className="page-link">...</span>
|
||||
</li>
|
||||
);
|
||||
|
||||
pageNumbers.push(
|
||||
<li key={totalPages} onClick={() => callback(totalPages)} className={`page-item`}>
|
||||
<a className={`page-link ${darkMode && 'text-light'}`}>{totalPages}</a>
|
||||
</li>
|
||||
);
|
||||
}
|
||||
|
||||
// If the beginning of the pages is not visible, add ellipsis and remove one number
|
||||
if (startPage > 1) {
|
||||
pageNumbers.shift();
|
||||
pageNumbers.shift();
|
||||
|
||||
pageNumbers.unshift(
|
||||
<li key="left-ellipsis" className="page-item">
|
||||
<span className="page-link">...</span>
|
||||
</li>
|
||||
);
|
||||
pageNumbers.unshift(
|
||||
<li key={1} onClick={() => callback(1)} className={`page-item`}>
|
||||
<a className={`page-link ${darkMode && 'text-light'}`}>1</a>
|
||||
</li>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return pageNumbers;
|
||||
};
|
||||
|
||||
return <>{renderPageNumbers()}</>;
|
||||
};
|
||||
|
||||
Pagination.Operator = Operator;
|
||||
|
|
|
|||
|
|
@ -60,21 +60,28 @@ export const PasswordInput = function PasswordInput({
|
|||
const computedStyles = {
|
||||
height: height == 36 ? (padding == 'default' ? '36px' : '40px') : padding == 'default' ? height : height + 4,
|
||||
borderRadius: `${borderRadius}px`,
|
||||
color: darkMode && textColor === '#11181C' ? '#ECEDEE' : textColor,
|
||||
borderColor: isFocused
|
||||
? accentColor
|
||||
: ['#D7DBDF'].includes(borderColor)
|
||||
backgroundColor: !['#ffffff', '#fff'].includes(backgroundColor)
|
||||
? backgroundColor
|
||||
: disable || loading
|
||||
? darkMode
|
||||
? '#6D757D7A'
|
||||
: '#6A727C47'
|
||||
: borderColor,
|
||||
'--tblr-input-border-color-darker': tinycolor(borderColor).darken(24).toString(),
|
||||
backgroundColor: darkMode && ['#fff', '#ffffff'].includes(backgroundColor) ? '#313538' : backgroundColor,
|
||||
? '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',
|
||||
|
||||
overflow: 'hidden',
|
||||
textOverflow: 'ellipsis',
|
||||
color: textColor !== '#1B1F24' ? textColor : disable || loading ? 'var(--text-disabled)' : 'var(--text-primary)',
|
||||
borderColor: isFocused
|
||||
? 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(),
|
||||
};
|
||||
|
||||
const loaderStyle = {
|
||||
|
|
@ -232,7 +239,6 @@ export const PasswordInput = function PasswordInput({
|
|||
<>
|
||||
<div
|
||||
data-cy={`label-${String(component.name).toLowerCase()}`}
|
||||
data-disabled={disable || loading}
|
||||
className={`text-input d-flex ${
|
||||
defaultAlignment === 'top' &&
|
||||
((width != 0 && label && label?.length != 0) || (auto && width == 0 && label && label?.length != 0))
|
||||
|
|
@ -283,7 +289,7 @@ export const PasswordInput = function PasswordInput({
|
|||
: '50%'
|
||||
}`,
|
||||
transform: ' translateY(-50%)',
|
||||
color: iconColor,
|
||||
color: iconColor !== '#CFD3D859' ? iconColor : 'var(--icons-weak-disabled)',
|
||||
zIndex: 3,
|
||||
}}
|
||||
stroke={1.5}
|
||||
|
|
@ -319,14 +325,19 @@ export const PasswordInput = function PasswordInput({
|
|||
}}
|
||||
stroke={1.5}
|
||||
>
|
||||
<SolidIcon width={16} className="password-component-eye" name={!iconVisibility ? 'eye1' : 'eyedisable'} />
|
||||
<SolidIcon
|
||||
width={16}
|
||||
fill={'var(--icons-weak-disabled)'}
|
||||
className="password-component-eye"
|
||||
name={!iconVisibility ? 'eye1' : 'eyedisable'}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
<input
|
||||
data-cy={dataCy}
|
||||
className={`tj-text-input-widget ${
|
||||
!isValid && showValidationError ? 'is-invalid' : ''
|
||||
} validation-without-icon ${darkMode && 'dark-theme-placeholder'}`}
|
||||
} validation-without-icon `}
|
||||
ref={textInputRef}
|
||||
onKeyUp={(e) => {
|
||||
if (e.key === 'Enter') {
|
||||
|
|
@ -363,11 +374,13 @@ export const PasswordInput = function PasswordInput({
|
|||
</div>
|
||||
{showValidationError && visibility && (
|
||||
<div
|
||||
className="tj-text-sm"
|
||||
data-cy={`${String(component.name).toLowerCase()}-invalid-feedback`}
|
||||
style={{
|
||||
color: errTextColor,
|
||||
textAlign: direction === 'left' && 'end',
|
||||
color: errTextColor !== '#D72D39' ? errTextColor : 'var(--status-error-strong)',
|
||||
textAlign: direction == 'left' && 'end',
|
||||
fontSize: '11px',
|
||||
fontWeight: '400',
|
||||
lineHeight: '16px',
|
||||
}}
|
||||
>
|
||||
{showValidationError && validationError}
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import _ from 'lodash';
|
|||
import { Tooltip } from 'react-tooltip';
|
||||
import { ButtonSolid } from '@/_ui/AppButton/AppButton';
|
||||
import SolidIcon from '@/_ui/Icon/SolidIcons';
|
||||
import cx from 'classnames';
|
||||
|
||||
export function AddNewRowComponent({
|
||||
hideAddNewRowPopup,
|
||||
|
|
@ -77,7 +78,7 @@ export function AddNewRowComponent({
|
|||
<div className="table-responsive jet-data-table">
|
||||
<table
|
||||
{...getTableProps()}
|
||||
className={`table table-vcenter table-nowrap ${tableType} ${darkMode && 'dark-theme'}`}
|
||||
className={`table table-vcenter table-nowrap ${tableType} ${darkMode && 'dark-theme table-dark'}`}
|
||||
>
|
||||
<thead>
|
||||
{headerGroups.map((headerGroup, index) => {
|
||||
|
|
@ -114,7 +115,25 @@ export function AddNewRowComponent({
|
|||
let cellProps = cell.getCellProps();
|
||||
const isEditable = true;
|
||||
return (
|
||||
<td key={index} {...cellProps} style={{ ...cellProps.style }}>
|
||||
<td
|
||||
key={index}
|
||||
{...cellProps}
|
||||
style={{ ...cellProps.style, backgroundColor: 'inherit' }}
|
||||
className={cx(`table-text-align-${cell.column.horizontalAlignment} td`, {
|
||||
'has-actions': cell.column.id === 'rightActions' || cell.column.id === 'leftActions',
|
||||
'has-left-actions': cell.column.id === 'leftActions',
|
||||
'has-right-actions': cell.column.id === 'rightActions',
|
||||
'has-text': cell.column.columnType === 'text' || isEditable,
|
||||
'has-dropdown': cell.column.columnType === 'dropdown',
|
||||
'has-multiselect': cell.column.columnType === 'multiselect',
|
||||
'has-datepicker': cell.column.columnType === 'datepicker',
|
||||
'align-items-center flex-column': cell.column.columnType === 'selector',
|
||||
// [cellSize]: true,
|
||||
'selector-column': cell.column.columnType === 'selector' && cell.column.id === 'selection',
|
||||
'has-select': ['select', 'newMultiSelect'].includes(cell.column.columnType),
|
||||
isEditable: isEditable,
|
||||
})}
|
||||
>
|
||||
<div
|
||||
className={`td-container ${cell.column.columnType === 'image' && 'jet-table-image-column'} ${
|
||||
cell.column.columnType !== 'image' && 'w-100 h-100'
|
||||
|
|
|
|||
44
frontend/src/Editor/Components/Table/Boolean.jsx
Normal file
44
frontend/src/Editor/Components/Table/Boolean.jsx
Normal file
|
|
@ -0,0 +1,44 @@
|
|||
import React from 'react';
|
||||
import SolidIcon from '@/_ui/Icon/SolidIcons';
|
||||
|
||||
export const Boolean = ({ value = false, isEditable, onChange, toggleOnBg, toggleOffBg }) => {
|
||||
const nonEditableContent = (isTruthyValue) => {
|
||||
return isTruthyValue ? (
|
||||
<SolidIcon name="tick" width="24" fill={`var(--grass9)`} />
|
||||
) : (
|
||||
<SolidIcon name="remove" width="24" fill={`var(--tomato9)`} />
|
||||
);
|
||||
};
|
||||
|
||||
const getCustomBgStyles = (value, toggleOnBg, toggleOffBg) => {
|
||||
if (value && toggleOnBg) {
|
||||
return { backgroundColor: toggleOnBg };
|
||||
}
|
||||
if (!value && toggleOffBg) {
|
||||
return { backgroundColor: toggleOffBg };
|
||||
}
|
||||
return {};
|
||||
};
|
||||
|
||||
const editableContent = (isEditable, value, onChange) => {
|
||||
return (
|
||||
<label class="boolean-switch">
|
||||
<input
|
||||
type="checkbox"
|
||||
disabled={!isEditable}
|
||||
checked={value}
|
||||
onClick={() => {
|
||||
onChange(!value);
|
||||
}}
|
||||
/>
|
||||
<span class="boolean-slider round" style={getCustomBgStyles(value, toggleOnBg, toggleOffBg)}></span>
|
||||
</label>
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="w-100" style={{ lineHeight: 1 }}>
|
||||
{isEditable ? editableContent(isEditable, value, onChange) : nonEditableContent(value)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
|
@ -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 (
|
||||
<>
|
||||
<div
|
||||
style={{
|
||||
marginBottom: 10,
|
||||
marginTop: 10,
|
||||
display: 'flex',
|
||||
justifyContent: 'center',
|
||||
}}
|
||||
>
|
||||
<button
|
||||
className="tj-datepicker-widget-arrows tj-datepicker-widget-left"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
decreaseMonth();
|
||||
}}
|
||||
disabled={prevMonthButtonDisabled}
|
||||
>
|
||||
<SolidIcon name="cheveronleft" width="12" />
|
||||
</button>
|
||||
<div style={{ marginRight: '8px' }}>
|
||||
<select
|
||||
value={months[getMonth(date)]}
|
||||
onChange={({ target: { value } }) => changeMonth(months.indexOf(value))}
|
||||
className="tj-datepicker-widget-month-selector"
|
||||
>
|
||||
{months.map((option) => (
|
||||
<option key={option} value={option}>
|
||||
{option}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
<select
|
||||
value={getYear(date)}
|
||||
onChange={({ target: { value } }) => changeYear(value)}
|
||||
className="tj-datepicker-widget-year-selector"
|
||||
style={{ padding: '4px 6px' }}
|
||||
>
|
||||
{years.map((option) => (
|
||||
<option key={option} value={option}>
|
||||
{option}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<button
|
||||
className="tj-datepicker-widget-arrows tj-datepicker-widget-right "
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
increaseMonth();
|
||||
}}
|
||||
disabled={nextMonthButtonDisabled}
|
||||
>
|
||||
<SolidIcon name="cheveronright" width="12" />
|
||||
</button>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default CustomDatePickerHeader;
|
||||
165
frontend/src/Editor/Components/Table/CustomDropdown.jsx
Normal file
165
frontend/src/Editor/Components/Table/CustomDropdown.jsx
Normal file
|
|
@ -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 (
|
||||
<div>
|
||||
{arrayOfValueProps.map((value, index) => (
|
||||
<span key={index} className="badge bg-blue-lt p-2 mx-1">
|
||||
{value}
|
||||
</span>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
} else if (valueProps) {
|
||||
const stringifyValue = String(valueProps.value);
|
||||
const arrayOfValueProps = stringifyValue.includes(',') ? stringifyValue.split(', ') : stringifyValue.split(' ');
|
||||
return (
|
||||
<div>
|
||||
{arrayOfValueProps.map((value, index) => (
|
||||
<span key={index} {...valueProps} className="badge bg-blue-lt p-2 mx-1">
|
||||
{value}
|
||||
</span>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
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) ? (
|
||||
<div
|
||||
style={{
|
||||
maxWidth: containerWidth,
|
||||
width: containerWidth,
|
||||
}}
|
||||
className={`overlay-cell-table overlay-badges-table ${darkMode && 'dark-theme'}`}
|
||||
>
|
||||
{labels?.map((label) => {
|
||||
return (
|
||||
<span
|
||||
style={{
|
||||
padding: '2px 6px',
|
||||
background: 'var(--surfaces-surface-03)',
|
||||
borderRadius: '6px',
|
||||
color: 'var(--text-primary)',
|
||||
fontSize: '12px',
|
||||
wordWrap: 'break-word', // Add word-wrap property for content wrapping
|
||||
display: 'flex',
|
||||
flexWrap: 'wrap',
|
||||
overflow: 'auto',
|
||||
}}
|
||||
key={label}
|
||||
>
|
||||
{label}
|
||||
</span>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
) : (
|
||||
<div></div>
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<OverlayTrigger
|
||||
placement="bottom"
|
||||
overlay={
|
||||
multiple &&
|
||||
elem &&
|
||||
(elem?.clientHeight < elem?.scrollHeight || elem?.clientWidth < elem?.scrollWidth) &&
|
||||
getOverlay(value, width, options)
|
||||
}
|
||||
trigger={
|
||||
multiple &&
|
||||
elem &&
|
||||
(elem?.clientHeight < elem?.scrollHeight || elem?.clientWidth < elem?.scrollWidth) &&
|
||||
value?.length >= 1 && ['hover']
|
||||
}
|
||||
rootClose={true}
|
||||
show={
|
||||
multiple &&
|
||||
elem &&
|
||||
(elem?.clientHeight < elem?.scrollHeight || elem?.clientWidth < elem?.scrollWidth) &&
|
||||
value?.length >= 1 &&
|
||||
showOverlay
|
||||
}
|
||||
>
|
||||
<div
|
||||
className={`custom-select d-flex align-items-center table-custom-select-badge-badges w-100 h-100 position-relative ${
|
||||
contentWrap
|
||||
? autoHeight
|
||||
? 'content--wrap'
|
||||
: 'content--wrap-content--overflow-hidden'
|
||||
: 'content--overflow-hidden'
|
||||
}`}
|
||||
onMouseMove={() => {
|
||||
if (!hovered) setHovered(true);
|
||||
}}
|
||||
onMouseLeave={() => setHovered(false)}
|
||||
>
|
||||
<SelectSearch
|
||||
options={options}
|
||||
printOptions="on-focus"
|
||||
value={value}
|
||||
renderValue={renderValue}
|
||||
search={false}
|
||||
onChange={onChange}
|
||||
multiple={multiple}
|
||||
placeholder={t('globals.select', 'Select') + '...'}
|
||||
className={'select-search'}
|
||||
/>
|
||||
</div>
|
||||
</OverlayTrigger>
|
||||
);
|
||||
};
|
||||
|
|
@ -1,44 +1,326 @@
|
|||
import React from 'react';
|
||||
import SelectSearch from 'react-select-search';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import React, { useRef, useState, useEffect, useMemo } from 'react';
|
||||
import Select from '@/_ui/Select';
|
||||
import { components } from 'react-select';
|
||||
import defaultStyles from '@/_ui/Select/styles';
|
||||
import SolidIcon from '@/_ui/Icon/SolidIcons';
|
||||
import { Checkbox } from '@/_ui/CheckBox/CheckBox';
|
||||
import OverlayTrigger from 'react-bootstrap/OverlayTrigger';
|
||||
import { isArray, isString } from 'lodash';
|
||||
const { MenuList } = components;
|
||||
|
||||
export const CustomSelect = ({ options, value, multiple, onChange, isEditable, width }) => {
|
||||
const { t } = useTranslation();
|
||||
export const CustomSelect = ({
|
||||
options,
|
||||
value,
|
||||
onChange,
|
||||
fuzzySearch = false,
|
||||
placeholder,
|
||||
disabled,
|
||||
className,
|
||||
darkMode,
|
||||
defaultOptionsList,
|
||||
textColor = '',
|
||||
isMulti,
|
||||
containerWidth,
|
||||
optionsLoadingState = false,
|
||||
horizontalAlignment = 'left',
|
||||
isEditable,
|
||||
}) => {
|
||||
const containerRef = useRef(null);
|
||||
const inputRef = useRef(null); // Ref for the input search box
|
||||
const [isFocused, setIsFocused] = useState(false);
|
||||
useEffect(() => {
|
||||
const handleDocumentClick = (event) => {
|
||||
if (isMulti && !containerRef.current?.contains(event.target)) {
|
||||
setIsFocused(false);
|
||||
}
|
||||
};
|
||||
|
||||
function renderValue(valueProps) {
|
||||
if (!isEditable) {
|
||||
const stringifyValue = String(valueProps.value);
|
||||
const arrayOfValueProps = stringifyValue.includes(',') ? stringifyValue.split(', ') : stringifyValue.split(' ');
|
||||
return arrayOfValueProps.map((value, index) => (
|
||||
<span key={index} className="badge bg-blue-lt p-2 mx-1">
|
||||
{value}
|
||||
</span>
|
||||
));
|
||||
document.addEventListener('mousedown', handleDocumentClick);
|
||||
|
||||
return () => {
|
||||
document.removeEventListener('mousedown', handleDocumentClick);
|
||||
};
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
// Focus the input search box when the menu list is open and the component is focused
|
||||
if (isFocused && inputRef.current) {
|
||||
inputRef.current.focus();
|
||||
}
|
||||
if (valueProps) {
|
||||
const stringifyValue = String(valueProps.value);
|
||||
const arrayOfValueProps = stringifyValue.includes(',') ? stringifyValue.split(', ') : stringifyValue.split(' ');
|
||||
return arrayOfValueProps.map((value, index) => (
|
||||
<span key={index} {...valueProps} className="badge bg-blue-lt p-2 mx-1">
|
||||
{value}
|
||||
</span>
|
||||
));
|
||||
}, [isFocused]);
|
||||
|
||||
const customStyles = {
|
||||
...defaultStyles(darkMode, '100%'),
|
||||
...(isMulti && {
|
||||
multiValue: (provided) => ({
|
||||
...provided,
|
||||
display: 'inline-block', // Display selected options inline
|
||||
marginRight: '4px', // Add some space between options
|
||||
}),
|
||||
}),
|
||||
valueContainer: (provided) => ({
|
||||
...provided,
|
||||
...(isMulti && {
|
||||
marginBottom: '0',
|
||||
display: 'flex',
|
||||
flexWrap: 'no-wrap',
|
||||
overflow: 'hidden',
|
||||
flexDirection: 'row',
|
||||
}),
|
||||
justifyContent: horizontalAlignment,
|
||||
}),
|
||||
menuList: (base) => ({
|
||||
...base,
|
||||
backgroundColor: 'var(--surfaces-surface-01) ',
|
||||
color: 'var(--text-primary)',
|
||||
cursor: 'pointer',
|
||||
overflow: 'auto',
|
||||
}),
|
||||
multiValueLabel: (provided) => ({
|
||||
...provided,
|
||||
padding: '2px 6px',
|
||||
background: 'var(--surfaces-surface-03)',
|
||||
borderRadius: '6px',
|
||||
color: textColor || 'var(--text-primary)',
|
||||
fontSize: '12px',
|
||||
}),
|
||||
singleValue: (provided) => ({
|
||||
...provided,
|
||||
padding: '2px 6px',
|
||||
background: 'var(--surfaces-surface-03)',
|
||||
borderRadius: '6px',
|
||||
color: textColor || 'var(--text-primary)',
|
||||
fontSize: '12px',
|
||||
}),
|
||||
};
|
||||
const customComponents = {
|
||||
MenuList: (props) => <CustomMenuList {...props} optionsLoadingState={optionsLoadingState} inputRef={inputRef} />,
|
||||
Option: CustomMultiSelectOption,
|
||||
DropdownIndicator: isEditable ? DropdownIndicator : null,
|
||||
...(isMulti && {
|
||||
MultiValueRemove,
|
||||
MultiValueContainer: CustomMultiValueContainer,
|
||||
}),
|
||||
};
|
||||
const defaultValue = useMemo(() => {
|
||||
if (defaultOptionsList.length >= 1) {
|
||||
return !isMulti ? defaultOptionsList[defaultOptionsList.length - 1] : defaultOptionsList;
|
||||
} else {
|
||||
return isMulti ? [] : {};
|
||||
}
|
||||
}
|
||||
}, [isMulti, defaultOptionsList]);
|
||||
|
||||
const _value = useMemo(() => {
|
||||
// Return null to show default value
|
||||
if (!value) {
|
||||
return null;
|
||||
}
|
||||
if (isMulti && value?.length) {
|
||||
if (isArray(value)) {
|
||||
return options?.filter((option) =>
|
||||
value?.find((val) => {
|
||||
if (val.hasOwnProperty('value')) {
|
||||
return option.value === val.value;
|
||||
} else return option.value === val;
|
||||
})
|
||||
);
|
||||
} else {
|
||||
return []; // Return empty array to not show default value in case of wrong value to be set
|
||||
}
|
||||
} else {
|
||||
// Condition for single select
|
||||
return options?.find((option) => option.value === value) || [];
|
||||
}
|
||||
}, [options, value, isMulti]);
|
||||
|
||||
const selectContainerRef = useRef(null); // Ref for the input search box
|
||||
const containerHeight = selectContainerRef.current?.clientHeight;
|
||||
const valueContainerHeight = selectContainerRef.current?.querySelector(
|
||||
'.react-select__value-container'
|
||||
)?.clientHeight;
|
||||
|
||||
return (
|
||||
<div className="custom-select" style={{ width: width }}>
|
||||
<SelectSearch
|
||||
options={options}
|
||||
printOptions="on-focus"
|
||||
value={value}
|
||||
renderValue={renderValue}
|
||||
search={false}
|
||||
onChange={onChange}
|
||||
multiple={multiple}
|
||||
placeholder={t('globals.select', 'Select') + '...'}
|
||||
className={'select-search'}
|
||||
/>
|
||||
<OverlayTrigger
|
||||
placement="bottom"
|
||||
overlay={
|
||||
isMulti && (_value?.length || defaultValue?.length) && !isFocused ? (
|
||||
getOverlay(_value ? _value : defaultValue, containerWidth, darkMode)
|
||||
) : (
|
||||
<div></div>
|
||||
)
|
||||
}
|
||||
trigger={isMulti && !isFocused && valueContainerHeight > containerHeight && ['hover', 'focus']} //container width -24 -16 gives that select container size
|
||||
rootClose={true}
|
||||
>
|
||||
<div className="w-100 h-100 d-flex align-items-center" ref={selectContainerRef}>
|
||||
<Select
|
||||
options={options}
|
||||
hasSearch={false}
|
||||
fuzzySearch={fuzzySearch}
|
||||
isDisabled={disabled}
|
||||
className={className}
|
||||
components={customComponents}
|
||||
value={_value}
|
||||
onMenuInputFocus={() => setIsFocused(true)}
|
||||
onChange={(value) => {
|
||||
setIsFocused(false);
|
||||
if (!isMulti && value === _value?.value) {
|
||||
// for single select column type if on change value is similar to current value , then discard the current value
|
||||
onChange('');
|
||||
} else {
|
||||
onChange(value);
|
||||
}
|
||||
}}
|
||||
useCustomStyles={true}
|
||||
styles={customStyles}
|
||||
defaultValue={defaultValue}
|
||||
placeholder={placeholder}
|
||||
isMulti={isMulti}
|
||||
hideSelectedOptions={false}
|
||||
isClearable={false}
|
||||
clearIndicator={false}
|
||||
darkMode={darkMode}
|
||||
{...{
|
||||
menuIsOpen: isFocused || undefined,
|
||||
isFocused: isFocused || undefined,
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</OverlayTrigger>
|
||||
);
|
||||
};
|
||||
|
||||
const CustomMenuList = ({ optionsLoadingState, children, selectProps, inputRef, ...props }) => {
|
||||
const { onInputChange, inputValue, onMenuInputFocus } = selectProps;
|
||||
|
||||
return (
|
||||
<div className="table-select-custom-menu-list" onClick={(e) => e.stopPropagation()}>
|
||||
<div className="table-select-column-type-search-box-wrapper ">
|
||||
{!inputValue && (
|
||||
<span className="">
|
||||
<SolidIcon name="search" width="14" />
|
||||
</span>
|
||||
)}
|
||||
<input
|
||||
autoCorrect="off"
|
||||
autoComplete="off"
|
||||
spellCheck="false"
|
||||
type="text"
|
||||
value={inputValue}
|
||||
onChange={(e) =>
|
||||
onInputChange(e.currentTarget.value, {
|
||||
action: 'input-change',
|
||||
})
|
||||
}
|
||||
onMouseDown={(e) => {
|
||||
e.stopPropagation();
|
||||
e.target.focus();
|
||||
}}
|
||||
onTouchEnd={(e) => {
|
||||
e.stopPropagation();
|
||||
e.target.focus();
|
||||
}}
|
||||
onFocus={onMenuInputFocus}
|
||||
placeholder="Search..."
|
||||
className="table-select-column-type-search-box"
|
||||
ref={inputRef} // Assign the ref to the input search box
|
||||
/>
|
||||
</div>
|
||||
<MenuList {...props} selectProps={selectProps}>
|
||||
{optionsLoadingState ? (
|
||||
<div class="text-center py-4">
|
||||
<div class="spinner-border text-primary" role="status">
|
||||
<span class="sr-only"></span>
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
children
|
||||
)}
|
||||
</MenuList>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const CustomMultiSelectOption = ({ innerRef, innerProps, children, isSelected, ...props }) => {
|
||||
return (
|
||||
<div ref={innerRef} {...innerProps} className="option-wrapper d-flex">
|
||||
{props.isMulti ? (
|
||||
<Checkbox label="" isChecked={isSelected} onChange={(e) => e.stopPropagation()} key="" value={children} />
|
||||
) : (
|
||||
<div style={{ visibility: isSelected ? 'visible' : 'hidden' }}>
|
||||
<Checkbox label="" isChecked={isSelected} onChange={(e) => e.stopPropagation()} key="" value={children} />
|
||||
</div>
|
||||
)}
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const MultiValueRemove = (props) => {
|
||||
const { innerProps } = props;
|
||||
return <div {...innerProps} />;
|
||||
};
|
||||
const CustomMultiValueContainer = (props) => {
|
||||
return (
|
||||
<div
|
||||
style={{
|
||||
display: 'flex',
|
||||
flexWrap: 'wrap',
|
||||
alignItems: 'center',
|
||||
}}
|
||||
>
|
||||
{props.children}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
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) ? (
|
||||
<div
|
||||
style={{
|
||||
maxWidth: '266px',
|
||||
}}
|
||||
className={`overlay-cell-table overlay-multiselect-table ${darkMode && 'dark-theme'}`}
|
||||
>
|
||||
{value?.map((option) => {
|
||||
return (
|
||||
<span
|
||||
style={{
|
||||
padding: '2px 6px',
|
||||
background: 'var(--surfaces-surface-03)',
|
||||
borderRadius: '6px',
|
||||
color: 'var(--text-primary)',
|
||||
fontSize: '12px',
|
||||
}}
|
||||
key={getLabel(option)}
|
||||
>
|
||||
{getLabel(option)}
|
||||
</span>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
) : (
|
||||
<div></div>
|
||||
);
|
||||
};
|
||||
|
||||
const DropdownIndicator = (props) => {
|
||||
return (
|
||||
<div {...props} className="cell-icon-display">
|
||||
{/* Your custom SVG */}
|
||||
{props.selectProps.menuIsOpen ? (
|
||||
<SolidIcon name="arrowUpTriangle" width="16" height="16" fill={'#6A727C'} />
|
||||
) : (
|
||||
<SolidIcon name="arrowDownTriangle" width="16" height="16" fill={'#6A727C'} />
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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 (
|
||||
<div className="table-column-datepicker-input-container">
|
||||
<input
|
||||
onBlur={(e) => {
|
||||
e.stopPropagation();
|
||||
}}
|
||||
className={cx('table-column-datepicker-input text-truncate', {
|
||||
'pointer-events-none': readOnly,
|
||||
})}
|
||||
value={value}
|
||||
onClick={onClick}
|
||||
ref={dateInputRef}
|
||||
style={styles}
|
||||
/>
|
||||
{!readOnly && (
|
||||
<span className="cell-icon-display">
|
||||
<SolidIcon
|
||||
width="16"
|
||||
fill={'var(--borders-strong)'}
|
||||
name="calender"
|
||||
className="table-column-datepicker-input-icon"
|
||||
/>
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
||||
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 (
|
||||
<div ref={pickerRef}>
|
||||
<Datetime
|
||||
inputProps={inputProps}
|
||||
timeFormat={isTimeChecked}
|
||||
className="cell-type-datepicker"
|
||||
<DatePickerComponent
|
||||
className={`input-field form-control validation-without-icon px-2`}
|
||||
popperClassName={cx({
|
||||
'tj-timepicker-widget': !isDateSelectionEnabled && isTimeChecked,
|
||||
'tj-datepicker-widget': isDateSelectionEnabled,
|
||||
'theme-dark dark-theme': darkMode,
|
||||
})}
|
||||
selected={date}
|
||||
onChange={(date) => handleDateChange(date)}
|
||||
value={computeDateString(date)}
|
||||
dateFormat={dateDisplayFormat}
|
||||
value={date}
|
||||
onChange={dateChange}
|
||||
closeOnSelect={true}
|
||||
onClose={onDatepickerClose}
|
||||
disabled={readOnly}
|
||||
renderView={(viewMode, renderDefault) => {
|
||||
calculatePosition();
|
||||
return renderDefault();
|
||||
}}
|
||||
closeOnTab={false}
|
||||
customInput={
|
||||
<TjDatepicker dateInputRef={dateInputRef} readOnly={readOnly} styles={{ color: cellStyles.color }} />
|
||||
}
|
||||
showTimeSelect={isTimeChecked}
|
||||
showTimeSelectOnly={!isDateSelectionEnabled && isTimeChecked}
|
||||
showMonthDropdown
|
||||
showYearDropdown
|
||||
dropdownMode="select"
|
||||
excludeDates={excludedDates}
|
||||
showPopperArrow={false}
|
||||
renderCustomHeader={(headerProps) => <CustomDatePickerHeader {...headerProps} />}
|
||||
shouldCloseOnSelect
|
||||
readOnly={readOnly}
|
||||
popperProps={{ strategy: 'fixed' }}
|
||||
timeIntervals={15}
|
||||
timeFormat={isTwentyFourHrFormatEnabled ? 'HH:mm' : 'h:mm aa'}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -172,7 +172,7 @@ export function Filter(props) {
|
|||
}
|
||||
|
||||
return (
|
||||
<div className={`table-filters card ${darkMode && 'dark-theme'}`}>
|
||||
<div className={`table-filters card ${darkMode ? 'dark-theme theme-dark' : 'light-theme'}`}>
|
||||
<div className="card-header row">
|
||||
<div className="col">
|
||||
<h4 data-cy={`header-filters`} className="font-weight-normal">
|
||||
|
|
@ -207,6 +207,7 @@ export function Filter(props) {
|
|||
className={`${darkMode ? 'select-search-dark' : 'select-search'} mb-0`}
|
||||
styles={selectStyles('100%')}
|
||||
useCustomStyles={true}
|
||||
darkMode={darkMode}
|
||||
/>
|
||||
</div>
|
||||
<div data-cy={`select-operation-dropdown-${index ?? ''}`} className="col" style={{ maxWidth: '180px' }}>
|
||||
|
|
@ -222,6 +223,7 @@ export function Filter(props) {
|
|||
styles={selectStyles('100%')}
|
||||
dataCy={`select-coloumn-dropdown-${index ?? ''}`}
|
||||
useCustomStyles={true}
|
||||
darkMode={darkMode}
|
||||
/>
|
||||
</div>
|
||||
<div className="col">
|
||||
|
|
|
|||
|
|
@ -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 (
|
||||
<div
|
||||
className={`overlay-cell-table ${darkMode && 'dark-theme'}`}
|
||||
onMouseEnter={() => setHovered(true)}
|
||||
onMouseLeave={() => setHovered(false)}
|
||||
style={{ whiteSpace: 'pre-wrap', color: 'var(--text-primary)' }}
|
||||
>
|
||||
<span
|
||||
dangerouslySetInnerHTML={{ __html: DOMPurify.sanitize(cellValue) }}
|
||||
style={{
|
||||
maxWidth: cellWidth,
|
||||
}}
|
||||
></span>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
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) => `<mark>${match}</mark>`);
|
||||
}
|
||||
}
|
||||
return (
|
||||
<div
|
||||
onClick={() => {
|
||||
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 ? (
|
||||
<div className="d-flex justify-content-center flex-column w-100 h-100 generate-cell-value-component-div-wrapper">
|
||||
|
||||
const _renderCellWhenHighlighted = () => {
|
||||
return (
|
||||
<div
|
||||
className="d-flex justify-content-center flex-column w-100 h-100 generate-cell-value-component-div-wrapper"
|
||||
style={{ oveflow: 'hidden' }}
|
||||
onMouseMove={() => {
|
||||
if (!hovered) setHovered(true);
|
||||
}}
|
||||
onMouseOut={() => setHovered(false)}
|
||||
>
|
||||
{cellValue === null ? (
|
||||
<NullRenderer darkMode={darkMode} />
|
||||
) : (
|
||||
<div
|
||||
style={{
|
||||
color: cellTextColor,
|
||||
}}
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: htmlElement,
|
||||
}}
|
||||
ref={ref}
|
||||
tabIndex={0}
|
||||
className={`form-control-plaintext form-control-plaintext-sm ${columnType === 'text' && 'h-100 my-1'}`}
|
||||
></div>
|
||||
<div
|
||||
style={{
|
||||
display: cell.column.isEditable && validationData.validationError ? 'block' : 'none',
|
||||
width: '100%',
|
||||
marginTop: ' 0.25rem',
|
||||
fontSize: ' 85.7142857%',
|
||||
color: '#d63939',
|
||||
}}
|
||||
className={`table-column-type-div-element ${columnType === 'text' && 'h-100 my-1'}`}
|
||||
>
|
||||
{validationData.validationError}
|
||||
<span
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: htmlElement,
|
||||
}}
|
||||
></span>
|
||||
</div>
|
||||
)}
|
||||
<div
|
||||
style={{
|
||||
display: cell.column.isEditable && validationData.validationError ? 'block' : 'none',
|
||||
width: '100%',
|
||||
marginTop: ' 0.25rem',
|
||||
fontSize: ' 85.7142857%',
|
||||
color: '#d63939',
|
||||
}}
|
||||
>
|
||||
{validationData.validationError}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const _renderNullCell = () => {
|
||||
if (isEditable) {
|
||||
if (!isNullCellClicked && !updateCellValue.current) {
|
||||
return <NullRenderer darkMode={darkMode} />;
|
||||
} else return cellRender;
|
||||
} else {
|
||||
return <NullRenderer darkMode={darkMode} />;
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
const handleClickOutside = (event) => {
|
||||
if (cellRef.current && !cellRef.current.contains(event.target) && !isCellValueChanged) {
|
||||
// Adding setTimeout to avoid this event to be executed before input's blur event
|
||||
setTimeout(() => {
|
||||
setIsNullCellClicked(false);
|
||||
setIsCellValueChanged(false);
|
||||
}, 100);
|
||||
}
|
||||
};
|
||||
document.addEventListener('mousedown', handleClickOutside);
|
||||
return () => {
|
||||
document.removeEventListener('mousedown', handleClickOutside);
|
||||
};
|
||||
}, [isCellValueChanged, setIsCellValueChanged]);
|
||||
|
||||
useEffect(() => {
|
||||
if (isNullCellClicked) {
|
||||
const inputElement = document.getElementById(`table-input-${cell.column.id}-${cell.row.id}`);
|
||||
if (inputElement) {
|
||||
inputElement.focus();
|
||||
}
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [isNullCellClicked]);
|
||||
|
||||
return (
|
||||
<div
|
||||
onClick={handleCellClick}
|
||||
onBlur={handleCellBlur}
|
||||
onKeyUp={handleKeyUp}
|
||||
className={`w-100 h-100 ${columnType === 'selector' && 'd-flex align-items-center justify-content-center'}`}
|
||||
ref={cellRef}
|
||||
>
|
||||
{!isColumnTypeAction && columnTypeAllowToRenderMarkElement.includes(columnType) && showHighlightedCells ? (
|
||||
<OverlayTrigger
|
||||
placement="bottom"
|
||||
overlay={_showOverlay ? getOverlay() : <div></div>}
|
||||
trigger={_showOverlay && ['hover']}
|
||||
rootClose={true}
|
||||
show={_showOverlay && showOverlay}
|
||||
>
|
||||
{_renderCellWhenHighlighted()}
|
||||
</OverlayTrigger>
|
||||
) : cellValue === null ? (
|
||||
_renderNullCell()
|
||||
) : (
|
||||
cellRender
|
||||
)}
|
||||
|
|
|
|||
|
|
@ -24,14 +24,14 @@ export const GlobalFilter = ({
|
|||
|
||||
return (
|
||||
<div
|
||||
className="d-flex border align-items-center table-global-search"
|
||||
className="d-flex align-items-center table-global-search"
|
||||
style={{ padding: '0.4rem 0.6rem', borderRadius: '6px' }}
|
||||
>
|
||||
<div className="d-flex">
|
||||
<SolidIcon name="search" width="16" height="16" />
|
||||
<SolidIcon name="search" width="16" height="16" fill={'var(--icons-default)'} />
|
||||
<input
|
||||
type="text"
|
||||
className={`align-self-center bg-transparent tj-text tj-text-xsm mx-lg-1`}
|
||||
className={`align-self-center bg-transparent tj-text tj-text-sm mx-lg-1`}
|
||||
value={value || ''}
|
||||
onChange={(e) => {
|
||||
setValue(e.target.value);
|
||||
|
|
@ -45,14 +45,14 @@ export const GlobalFilter = ({
|
|||
/>
|
||||
<div
|
||||
className={`d-flex table-clear-icon align-items-center ${globalFilter ? 'visible' : 'invisible'}`}
|
||||
style={{ width: '20px', height: '20px', padding: '4px', cursor: 'pointer' }}
|
||||
style={{ cursor: 'pointer' }}
|
||||
onClick={() => {
|
||||
setGlobalFilter(undefined);
|
||||
setValue('');
|
||||
onComponentOptionChanged(component, 'searchText', '');
|
||||
}}
|
||||
>
|
||||
<SolidIcon name="remove" width="16" height="16px" fill={darkMode ? '#3E63DD' : '#3E63DD'} />
|
||||
<SolidIcon name="removerectangle" width="16" height="16" fill={'var(--icons-default)'} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,16 +1,25 @@
|
|||
import React from 'react';
|
||||
|
||||
export const Link = ({ cellValue, linkTarget }) => {
|
||||
export const Link = ({ cellValue, linkTarget, underline, underlineColor, linkColor, displayText, darkMode }) => {
|
||||
const linkTextColor =
|
||||
linkColor !== '#1B1F24' ? linkColor : darkMode && linkColor === '#1B1F24' ? '#FFFFFF' : linkColor;
|
||||
return (
|
||||
<div className="w-100">
|
||||
<div className="w-100 table-link-column">
|
||||
<a
|
||||
className={underline === 'hover' ? 'table-link-hover' : 'table-link'}
|
||||
href={cellValue}
|
||||
target={linkTarget}
|
||||
target={linkTarget == '_self' || linkTarget == false ? '_self' : '_blank'}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
}}
|
||||
style={{
|
||||
color: linkTextColor,
|
||||
textDecoration: underline === 'always' && 'underline', // Apply underline always or only on hover
|
||||
textDecorationColor: underlineColor,
|
||||
}}
|
||||
rel="noreferrer"
|
||||
>
|
||||
{cellValue}
|
||||
{displayText ? displayText : cellValue}
|
||||
</a>
|
||||
</div>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -0,0 +1,13 @@
|
|||
import React from 'react';
|
||||
import './nullRenderer.scss';
|
||||
import classNames from 'classnames';
|
||||
|
||||
const NullRenderer = ({ darkMode }) => {
|
||||
return (
|
||||
<div className="d-flex align-items-center h-100">
|
||||
<span className={classNames('null-renderer-text', { 'dark-theme': darkMode })}>NULL</span>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default NullRenderer;
|
||||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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) => {
|
||||
|
|
|
|||
|
|
@ -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 (
|
||||
<div className="radio row">
|
||||
<div>
|
||||
{options.map((option, index) => (
|
||||
<label
|
||||
key={index}
|
||||
className="form-check form-check-inline"
|
||||
onClick={() => {
|
||||
if (!readOnly) onChange(option.value);
|
||||
}}
|
||||
>
|
||||
<input
|
||||
className="form-check-input"
|
||||
type="radio"
|
||||
checked={option.value === value}
|
||||
disabled={readOnly && option.value !== value}
|
||||
/>
|
||||
<span className="form-check-label">{option.name}</span>
|
||||
</label>
|
||||
))}
|
||||
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) => (
|
||||
<label
|
||||
key={index}
|
||||
className="form-check form-check-inline"
|
||||
onClick={() => {
|
||||
if (!readOnly) onChange(option.value);
|
||||
}}
|
||||
>
|
||||
<input
|
||||
className="form-check-input"
|
||||
type="radio"
|
||||
checked={option.value === value}
|
||||
disabled={readOnly && option.value !== value}
|
||||
/>
|
||||
<span className="form-check-label">{option.name}</span>
|
||||
</label>
|
||||
));
|
||||
};
|
||||
|
||||
const getOverlay = (options, containerWidth) => {
|
||||
return Array.isArray(options) ? (
|
||||
<div
|
||||
style={{
|
||||
maxWidth: containerWidth,
|
||||
width: containerWidth,
|
||||
}}
|
||||
className={`overlay-cell-table overlay-radio-table ${darkMode && 'dark-theme'}`}
|
||||
onMouseEnter={() => setHovered(true)}
|
||||
onMouseLeave={() => setHovered(false)}
|
||||
>
|
||||
{renderOptions(options)}
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<div></div>
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<OverlayTrigger
|
||||
placement="bottom"
|
||||
overlay={
|
||||
elem &&
|
||||
(elem?.clientHeight < elem?.scrollHeight || elem?.clientWidth < elem?.scrollWidth) &&
|
||||
getOverlay(options, containerWidth)
|
||||
}
|
||||
trigger={elem && (elem?.clientHeight < elem?.scrollHeight || elem?.clientWidth < elem?.scrollWidth) && ['focus']}
|
||||
rootClose={true}
|
||||
show={elem && (elem?.clientHeight < elem?.scrollHeight || elem?.clientWidth < elem?.scrollWidth) && showOverlay}
|
||||
>
|
||||
<div
|
||||
className="table-radio-column-cell radio row h-100"
|
||||
onMouseMove={() => {
|
||||
if (!hovered) setHovered(true);
|
||||
}}
|
||||
onMouseOut={() => setHovered(false)}
|
||||
>
|
||||
<div className="table-radio-column-list">{renderOptions(options)}</div>
|
||||
</div>
|
||||
</OverlayTrigger>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
180
frontend/src/Editor/Components/Table/String.jsx
Normal file
180
frontend/src/Editor/Components/Table/String.jsx
Normal file
|
|
@ -0,0 +1,180 @@
|
|||
import React, { useState, useEffect } from 'react';
|
||||
import { validateWidget, determineJustifyContentValue } from '@/_helpers/utils';
|
||||
import OverlayTrigger from 'react-bootstrap/OverlayTrigger';
|
||||
|
||||
const String = ({
|
||||
isEditable,
|
||||
darkMode,
|
||||
handleCellValueChange,
|
||||
cellTextColor,
|
||||
cellValue,
|
||||
column,
|
||||
currentState,
|
||||
containerWidth,
|
||||
cell,
|
||||
horizontalAlignment,
|
||||
isMaxRowHeightAuto,
|
||||
cellSize,
|
||||
maxRowHeightValue,
|
||||
}) => {
|
||||
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: cellTextColor ?? 'inherit',
|
||||
};
|
||||
const ref = React.useRef(null);
|
||||
const [showOverlay, setShowOverlay] = useState(false);
|
||||
const [hovered, setHovered] = useState(false);
|
||||
const [isEditing, setIsEditing] = useState(false);
|
||||
useEffect(() => {
|
||||
if (hovered) {
|
||||
setShowOverlay(true);
|
||||
} else {
|
||||
setShowOverlay(false);
|
||||
}
|
||||
}, [hovered]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!isEditable && isEditing) {
|
||||
setIsEditing(false);
|
||||
}
|
||||
}, [isEditable]);
|
||||
|
||||
const _renderString = () => (
|
||||
<div
|
||||
ref={ref}
|
||||
contentEditable={'true'}
|
||||
className={`${!isValid ? 'is-invalid' : ''} h-100 text-container long-text-input d-flex align-items-center ${
|
||||
darkMode ? ' textarea-dark-theme' : ''
|
||||
} justify-content-${determineJustifyContentValue(horizontalAlignment)}`}
|
||||
tabIndex={-1}
|
||||
style={{
|
||||
color: cellTextColor ? cellTextColor : 'inherit',
|
||||
outline: 'none',
|
||||
border: 'none',
|
||||
background: 'inherit',
|
||||
position: 'relative',
|
||||
height: '100%',
|
||||
}}
|
||||
readOnly={!isEditable}
|
||||
onBlur={(e) => {
|
||||
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();
|
||||
}}
|
||||
>
|
||||
<span>{cellValue}</span>
|
||||
</div>
|
||||
);
|
||||
|
||||
const getOverlay = () => {
|
||||
return (
|
||||
<div
|
||||
className={`overlay-cell-table ${darkMode && 'dark-theme'}`}
|
||||
onMouseEnter={() => setHovered(true)}
|
||||
onMouseLeave={() => setHovered(false)}
|
||||
style={{ color: 'var(--text-primary)' }}
|
||||
>
|
||||
<span
|
||||
style={{
|
||||
width: `${containerWidth}px`,
|
||||
}}
|
||||
>
|
||||
{cellValue}
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const _showOverlay =
|
||||
ref?.current &&
|
||||
(ref?.current?.clientWidth < ref?.current?.children[0]?.offsetWidth ||
|
||||
ref?.current?.clientHeight < ref?.current?.children[0]?.offsetHeight);
|
||||
|
||||
return (
|
||||
<>
|
||||
<OverlayTrigger
|
||||
placement="bottom"
|
||||
overlay={_showOverlay ? getOverlay() : <div></div>}
|
||||
trigger={_showOverlay && ['hover', 'focus']}
|
||||
rootClose={true}
|
||||
show={_showOverlay && showOverlay && !isEditing}
|
||||
>
|
||||
{!isEditable ? (
|
||||
<div
|
||||
className={`d-flex align-items-center h-100 w-100 justify-content-${determineJustifyContentValue(
|
||||
horizontalAlignment
|
||||
)}`}
|
||||
style={cellStyles}
|
||||
onMouseMove={() => {
|
||||
if (!hovered) setHovered(true);
|
||||
}}
|
||||
onMouseLeave={() => {
|
||||
setHovered(false);
|
||||
}}
|
||||
ref={ref}
|
||||
>
|
||||
<span
|
||||
style={{
|
||||
maxHeight: isMaxRowHeightAuto
|
||||
? 'auto'
|
||||
: maxRowHeightValue
|
||||
? maxRowHeightValue
|
||||
: cellSize === 'condensed'
|
||||
? '39px'
|
||||
: '45px',
|
||||
}}
|
||||
>
|
||||
{cellValue}
|
||||
</span>
|
||||
</div>
|
||||
) : (
|
||||
<div className="h-100 d-flex flex-column justify-content-center position-relative">
|
||||
<div
|
||||
onMouseMove={() => {
|
||||
if (!hovered) setHovered(true);
|
||||
}}
|
||||
onMouseLeave={() => setHovered(false)}
|
||||
className={`${!isValid ? 'is-invalid h-100' : ''} ${isEditing ? 'h-100 content-editing' : ''} h-100`}
|
||||
>
|
||||
{_renderString()}
|
||||
</div>
|
||||
<div className={`${isValid ? '' : 'invalid-feedback text-truncate'} `}>{validationError}</div>
|
||||
</div>
|
||||
)}
|
||||
</OverlayTrigger>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default String;
|
||||
|
|
@ -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 (
|
||||
<div className="d-flex flex-column align-items-center">
|
||||
<div className="d-flex flex-column align-items-center justify-content-center h-100">
|
||||
<IndeterminateCheckbox {...row.getToggleRowSelectedProps()} fireEvent={fireEvent} />
|
||||
</div>
|
||||
);
|
||||
|
|
@ -705,6 +726,7 @@ export function Table({
|
|||
},
|
||||
];
|
||||
}, [JSON.stringify(state)]);
|
||||
|
||||
const getDetailsOfPreSelectedRow = () => {
|
||||
const key = Object?.keys(defaultSelectedRow)[0] ?? '';
|
||||
const value = defaultSelectedRow?.[key] ?? undefined;
|
||||
|
|
@ -836,6 +858,7 @@ export function Table({
|
|||
setPageSize(rows?.length || 10);
|
||||
}
|
||||
}, [clientSidePagination, serverSidePagination, rows, rowsPerPage]);
|
||||
|
||||
useEffect(() => {
|
||||
const pageData = page.map((row) => row.original);
|
||||
if (preSelectRow.current) {
|
||||
|
|
@ -876,6 +899,7 @@ export function Table({
|
|||
|
||||
const [paginationInternalPageIndex, setPaginationInternalPageIndex] = useState(pageIndex ?? 1);
|
||||
const [rowDetails, setRowDetails] = useState();
|
||||
|
||||
useEffect(() => {
|
||||
if (pageCount <= pageIndex) gotoPage(pageCount - 1);
|
||||
}, [pageCount]);
|
||||
|
|
@ -1055,15 +1079,16 @@ export function Table({
|
|||
<div
|
||||
data-cy={`draggable-widget-${String(component.name).toLowerCase()}`}
|
||||
data-disabled={parsedDisabledState}
|
||||
className={`card jet-table table-component ${darkMode && 'dark-theme'}`}
|
||||
className={`card jet-table table-component ${darkMode ? 'dark-theme' : 'light-theme'}`}
|
||||
style={{
|
||||
width: `100%`,
|
||||
height: `${height}px`,
|
||||
display: parsedWidgetVisibility ? '' : 'none',
|
||||
overflow: 'hidden',
|
||||
borderRadius: Number.parseFloat(borderRadius),
|
||||
boxShadow: styles.boxShadow,
|
||||
boxShadow,
|
||||
padding: '8px',
|
||||
borderColor: borderColor,
|
||||
}}
|
||||
onClick={(event) => {
|
||||
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}
|
||||
>
|
||||
<thead>
|
||||
|
|
@ -1285,15 +1312,17 @@ export function Table({
|
|||
`}
|
||||
>
|
||||
<div>
|
||||
{column.columnType !== 'selector' && isEditable && (
|
||||
<SolidIcon
|
||||
name="editable"
|
||||
width="16px"
|
||||
height="16px"
|
||||
fill={darkMode ? '#4C5155' : '#C1C8CD'}
|
||||
vievBox="0 0 16 16"
|
||||
/>
|
||||
)}
|
||||
{column.columnType !== 'selector' &&
|
||||
column.columnType !== 'image' &&
|
||||
isEditable && (
|
||||
<SolidIcon
|
||||
name="editable"
|
||||
width="16px"
|
||||
height="16px"
|
||||
fill={darkMode ? '#4C5155' : '#C1C8CD'}
|
||||
vievBox="0 0 16 16"
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
<div
|
||||
data-cy={`column-header-${String(column.exportValue)
|
||||
|
|
@ -1354,7 +1383,14 @@ export function Table({
|
|||
? ''
|
||||
: 'resizer'
|
||||
} ${column.isResizing ? 'isResizing' : ''}`}
|
||||
></div>
|
||||
>
|
||||
<div
|
||||
className="table-column-resize-handle"
|
||||
style={{
|
||||
...(column.isResizing && { display: 'block' }),
|
||||
}}
|
||||
></div>
|
||||
</div>
|
||||
</th>
|
||||
);
|
||||
}}
|
||||
|
|
@ -1372,6 +1408,28 @@ export function Table({
|
|||
<tbody {...getTableBodyProps()} style={{ color: computeFontColor() }}>
|
||||
{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 (
|
||||
<tr
|
||||
key={index}
|
||||
|
|
@ -1385,7 +1443,7 @@ export function Table({
|
|||
? 'selected'
|
||||
: ''
|
||||
}`}
|
||||
{...row.getRowProps()}
|
||||
{...rowProps}
|
||||
onClick={async (e) => {
|
||||
e.stopPropagation();
|
||||
// toggleRowSelected will triggered useRededcuer function in useTable and in result will get the selectedFlatRows consisting row which are selected
|
||||
|
|
@ -1438,19 +1496,26 @@ export function Table({
|
|||
) {
|
||||
cellProps.style.flex = '1 1 auto';
|
||||
}
|
||||
//should we remove this
|
||||
const wrapAction = textWrapActions(cell.column.id);
|
||||
const rowChangeSet = changeSet ? changeSet[cell.row.index] : null;
|
||||
const cellValue = rowChangeSet ? rowChangeSet[cell.column.name] || cell.value : cell.value;
|
||||
const rowData = tableData[cell.row.index];
|
||||
const cellBackgroundColor = resolveReferences(
|
||||
cell.column?.cellBackgroundColor,
|
||||
currentState,
|
||||
'',
|
||||
{
|
||||
cellValue,
|
||||
rowData,
|
||||
}
|
||||
);
|
||||
const cellBackgroundColor = ![
|
||||
'dropdown',
|
||||
'badge',
|
||||
'badges',
|
||||
'tags',
|
||||
'radio',
|
||||
'link',
|
||||
'multiselect',
|
||||
'toggle',
|
||||
].includes(cell?.column?.columnType)
|
||||
? resolveReferences(cell.column?.cellBackgroundColor, currentState, '', {
|
||||
cellValue,
|
||||
rowData,
|
||||
})
|
||||
: '';
|
||||
const cellTextColor = resolveReferences(cell.column?.textColor, currentState, '', {
|
||||
cellValue,
|
||||
rowData,
|
||||
|
|
@ -1477,22 +1542,34 @@ export function Table({
|
|||
cell.column.id === 'rightActions' || cell.column.id === 'leftActions' ? cell.column.id : ''
|
||||
)}${String(cellValue ?? '').toLocaleLowerCase()}-cell-${index}`}
|
||||
className={cx(
|
||||
`table-text-align-${cell.column.horizontalAlignment} ${
|
||||
wrapAction ? wrapAction : cell?.column?.Header === 'Actions' ? '' : 'wrap'
|
||||
}-wrapper td`,
|
||||
`table-text-align-${cell.column.horizontalAlignment}
|
||||
${cell?.column?.Header !== 'Actions' && (contentWrap ? 'wrap-wrapper' : '')}
|
||||
td`,
|
||||
{
|
||||
'has-actions': cell.column.id === 'rightActions' || cell.column.id === 'leftActions',
|
||||
'has-left-actions': cell.column.id === 'leftActions',
|
||||
'has-right-actions': cell.column.id === 'rightActions',
|
||||
'has-text': cell.column.columnType === 'text' || isEditable,
|
||||
'has-number': cell.column.columnType === 'number',
|
||||
'has-dropdown': cell.column.columnType === 'dropdown',
|
||||
'has-multiselect': cell.column.columnType === 'multiselect',
|
||||
'has-datepicker': cell.column.columnType === 'datepicker',
|
||||
'align-items-center flex-column': cell.column.columnType === 'selector',
|
||||
'has-badge': ['badge', 'badges'].includes(cell.column.columnType),
|
||||
[cellSize]: true,
|
||||
'overflow-hidden':
|
||||
['text', 'string', undefined, 'number'].includes(cell.column.columnType) &&
|
||||
!contentWrap,
|
||||
'selector-column':
|
||||
cell.column.columnType === 'selector' && cell.column.id === 'selection',
|
||||
'resizing-column': cell.column.isResizing || cell.column.id === resizingColumnId,
|
||||
'has-select': ['select', 'newMultiSelect'].includes(cell.column.columnType),
|
||||
'has-tags': cell.column.columnType === 'tags',
|
||||
'has-link': cell.column.columnType === 'link',
|
||||
'has-radio': cell.column.columnType === 'radio',
|
||||
'has-toggle': cell.column.columnType === 'toggle',
|
||||
'has-textarea': ['string', 'text'].includes(cell.column.columnType),
|
||||
isEditable: isEditable,
|
||||
}
|
||||
)}
|
||||
{...cellProps}
|
||||
|
|
@ -1515,7 +1592,7 @@ export function Table({
|
|||
>
|
||||
<div
|
||||
className={`td-container ${
|
||||
cell.column.columnType === 'image' && 'jet-table-image-column'
|
||||
cell.column.columnType === 'image' && 'jet-table-image-column h-100'
|
||||
} ${cell.column.columnType !== 'image' && `w-100 h-100`}`}
|
||||
>
|
||||
<GenerateEachCellValue
|
||||
|
|
@ -1526,6 +1603,10 @@ export function Table({
|
|||
actionButtonsArray,
|
||||
isEditable,
|
||||
horizontalAlignment,
|
||||
cellTextColor,
|
||||
contentWrap,
|
||||
autoHeight,
|
||||
isMaxRowHeightAuto,
|
||||
})}
|
||||
rowChangeSet={rowChangeSet}
|
||||
isEditable={isEditable}
|
||||
|
|
@ -1534,6 +1615,10 @@ export function Table({
|
|||
cellTextColor={cellTextColor}
|
||||
cell={cell}
|
||||
currentState={currentState}
|
||||
cellWidth={cell.column.width}
|
||||
isCellValueChanged={isCellValueChanged}
|
||||
setIsCellValueChanged={setIsCellValueChanged}
|
||||
darkMode={darkMode}
|
||||
/>
|
||||
</div>
|
||||
</td>
|
||||
|
|
@ -1611,6 +1696,7 @@ export function Table({
|
|||
variant="primary"
|
||||
className={`tj-text-xsm`}
|
||||
onClick={() => {
|
||||
setIsCellValueChanged(false);
|
||||
onEvent('onBulkUpdate', tableEvents, { component }).then(() => {
|
||||
handleChangesSaved();
|
||||
});
|
||||
|
|
@ -1629,6 +1715,7 @@ export function Table({
|
|||
variant="tertiary"
|
||||
className={`tj-text-xsm`}
|
||||
onClick={() => {
|
||||
setIsCellValueChanged(false);
|
||||
handleChangesDiscarded();
|
||||
}}
|
||||
data-cy={`table-button-discard-changes`}
|
||||
|
|
@ -1643,7 +1730,11 @@ export function Table({
|
|||
</>
|
||||
) : (
|
||||
!loadingState && (
|
||||
<span data-cy={`footer-number-of-records`} className="font-weight-500 color-slate11">
|
||||
<span
|
||||
data-cy={`footer-number-of-records`}
|
||||
className="font-weight-500"
|
||||
style={{ color: 'var(--text-placeholder)' }}
|
||||
>
|
||||
{clientSidePagination && !serverSidePagination && `${globalFilteredRows.length} Records`}
|
||||
{serverSidePagination && totalRecords ? `${totalRecords} Records` : ''}
|
||||
</span>
|
||||
|
|
@ -1681,7 +1772,7 @@ export function Table({
|
|||
<Tooltip id="tooltip-for-add-new-row" className="tooltip" />
|
||||
<ButtonSolid
|
||||
variant="ghostBlack"
|
||||
fill={`var(--slate11)`}
|
||||
fill={`var(--icons-default)`}
|
||||
className={`tj-text-xsm ${
|
||||
tableDetails.addNewRowsDetails.addingNewRows && 'cursor-not-allowed always-active-btn'
|
||||
}`}
|
||||
|
|
@ -1715,7 +1806,7 @@ export function Table({
|
|||
minWidth: '32px',
|
||||
}}
|
||||
leftIcon="filedownload"
|
||||
fill={`var(--slate11)`}
|
||||
fill={`var(--icons-default)`}
|
||||
iconWidth="16"
|
||||
size="md"
|
||||
data-tooltip-id="tooltip-for-download"
|
||||
|
|
@ -1743,7 +1834,7 @@ export function Table({
|
|||
className={`tj-text-xsm `}
|
||||
customStyles={{ minWidth: '32px' }}
|
||||
leftIcon="eye1"
|
||||
fill={`var(--slate11)`}
|
||||
fill={`var(--icons-default)`}
|
||||
iconWidth="16"
|
||||
size="md"
|
||||
data-cy={`select-column-icon`}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,20 @@
|
|||
import React, { useState } from 'react';
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import OverlayTrigger from 'react-bootstrap/OverlayTrigger';
|
||||
|
||||
export const Tags = ({ value, onChange, readOnly, containerWidth = '' }) => {
|
||||
const [showOverlay, setShowOverlay] = useState(false);
|
||||
const [hovered, setHovered] = useState(false);
|
||||
|
||||
const elem = document.querySelector('.table-tags-col-container');
|
||||
|
||||
useEffect(() => {
|
||||
if (hovered) {
|
||||
setShowOverlay(true);
|
||||
} else {
|
||||
setShowOverlay(false);
|
||||
}
|
||||
}, [hovered]);
|
||||
|
||||
export const Tags = ({ value, onChange, readOnly }) => {
|
||||
const isValid = Array.isArray(value);
|
||||
if (!isValid) console.warn('[Tags]: value provided is not an array');
|
||||
value = isValid ? value : [];
|
||||
|
|
@ -30,10 +44,10 @@ export const Tags = ({ value, onChange, readOnly }) => {
|
|||
|
||||
function renderTag(text) {
|
||||
return (
|
||||
<span className="col-auto badge bg-blue-lt p-2 mx-1 tag mb-2">
|
||||
<span className="col-auto badge bg-blue-lt p-2 mx-1 tag">
|
||||
{text}
|
||||
{!readOnly && (
|
||||
<span className="badge badge-pill bg-red-lt remove-tag-button" onClick={() => removeTag(text)}>
|
||||
<span className="badge badge-pill bg-red-lt remove-tag-button cursor-pointer" onClick={() => removeTag(text)}>
|
||||
x
|
||||
</span>
|
||||
)}
|
||||
|
|
@ -41,38 +55,101 @@ export const Tags = ({ value, onChange, readOnly }) => {
|
|||
);
|
||||
}
|
||||
|
||||
const getOverlay = (value, containerWidth) => {
|
||||
const darkMode = localStorage.getItem('darkMode') === 'true';
|
||||
return Array.isArray(value) ? (
|
||||
<div
|
||||
style={{
|
||||
maxWidth: containerWidth,
|
||||
width: containerWidth,
|
||||
}}
|
||||
className={`overlay-cell-table overlay-tags-table ${darkMode && 'dark-theme'}`}
|
||||
onMouseEnter={() => setHovered(true)}
|
||||
onMouseLeave={() => setHovered(false)}
|
||||
>
|
||||
{value?.map((tag, index) => {
|
||||
return (
|
||||
<span
|
||||
key={index}
|
||||
style={{
|
||||
wordWrap: 'break-word', // Add word-wrap property for content wrapping
|
||||
display: 'flex',
|
||||
flexWrap: 'wrap',
|
||||
overflow: 'auto',
|
||||
}}
|
||||
>
|
||||
{renderTag(tag)}
|
||||
</span>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
) : (
|
||||
<div></div>
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<div
|
||||
className="tags row"
|
||||
tabIndex={!readOnly ? 0 : null}
|
||||
onKeyUp={(e) => {
|
||||
e.stopPropagation();
|
||||
if (e.key === 'Tab' && !readOnly) {
|
||||
setShowForm(true);
|
||||
}
|
||||
}}
|
||||
<OverlayTrigger
|
||||
placement="bottom"
|
||||
overlay={getOverlay(value, containerWidth)}
|
||||
trigger={
|
||||
elem &&
|
||||
(elem?.clientHeight < elem?.scrollHeight || elem?.clientWidth < elem?.scrollWidth) &&
|
||||
!showForm &&
|
||||
value?.length >= 1 && ['click']
|
||||
}
|
||||
rootClose={true}
|
||||
show={
|
||||
elem &&
|
||||
(elem?.clientHeight < elem?.scrollHeight || elem?.clientWidth < elem?.scrollWidth) &&
|
||||
value?.length >= 1 &&
|
||||
showOverlay
|
||||
}
|
||||
>
|
||||
{value.map((item) => {
|
||||
return renderTag(item);
|
||||
})}
|
||||
|
||||
{!showForm && !readOnly && (
|
||||
<span className="col-auto badge bg-green-lt mx-1 add-tag-button" onClick={() => setShowForm(true)}>
|
||||
{'+'}
|
||||
</span>
|
||||
)}
|
||||
|
||||
{showForm && (
|
||||
<span className="col-auto badge bg-green-lt mx-1">
|
||||
<input
|
||||
type="text"
|
||||
autoFocus
|
||||
className="form-control-plaintext"
|
||||
onBlur={(e) => addTag(e.target.value)}
|
||||
onKeyDown={handleFormKeyDown}
|
||||
/>
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
<div
|
||||
className="tags row h-100"
|
||||
style={{ display: 'flex', alignItems: 'center' }}
|
||||
onMouseMove={() => {
|
||||
if (!hovered) setHovered(true);
|
||||
}}
|
||||
onMouseOut={() => setHovered(false)}
|
||||
>
|
||||
{/* Container for + button */}
|
||||
{!showForm && !readOnly && (
|
||||
<div className="add-tag-container" style={{ width: '20%' }}>
|
||||
<span className="col-auto">
|
||||
<span className="badge bg-green-lt mx-1 add-tag-button" onClick={() => setShowForm(true)}>
|
||||
{'+'}
|
||||
</span>
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
{/* Container for renderTags */}
|
||||
{!showForm && (
|
||||
<div
|
||||
className="render-tags-container table-tags-col-container h-100 d-flex flex-wrap custom-gap-3"
|
||||
style={{ width: '80%', overflow: 'hidden' }}
|
||||
>
|
||||
{value.map((item, index) => (
|
||||
<span key={index} className="col-auto tag-wrapper">
|
||||
{renderTag(item)}
|
||||
</span>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
{/* Input element */}
|
||||
{showForm && (
|
||||
<div className="col-auto badge bg-green-lt mx-1">
|
||||
<input
|
||||
type="text"
|
||||
autoFocus
|
||||
className="form-control-plaintext"
|
||||
onBlur={(e) => addTag(e.target.value)}
|
||||
onKeyDown={handleFormKeyDown}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</OverlayTrigger>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
180
frontend/src/Editor/Components/Table/Text.jsx
Normal file
180
frontend/src/Editor/Components/Table/Text.jsx
Normal file
|
|
@ -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 = () => (
|
||||
<div
|
||||
contentEditable={'true'}
|
||||
className={`${!isValid ? 'is-invalid' : ''} h-100 long-text-input text-container ${
|
||||
darkMode ? ' textarea-dark-theme' : ''
|
||||
}`}
|
||||
style={{
|
||||
color: cellTextColor ? cellTextColor : 'inherit',
|
||||
maxWidth: containerWidth,
|
||||
outline: 'none',
|
||||
border: 'none',
|
||||
background: 'inherit',
|
||||
overflowY: 'auto',
|
||||
whiteSpace: 'pre-wrap',
|
||||
position: 'static',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
}}
|
||||
readOnly={!isEditable}
|
||||
onBlur={(e) => {
|
||||
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 = () => (
|
||||
<div
|
||||
className={`d-flex align-items-center h-100 w-100 justify-content-${determineJustifyContentValue(
|
||||
horizontalAlignment
|
||||
)}`}
|
||||
style={cellStyles}
|
||||
>
|
||||
<span
|
||||
style={{
|
||||
maxHeight: isMaxRowHeightAuto
|
||||
? 'auto'
|
||||
: maxRowHeightValue
|
||||
? maxRowHeightValue - 16 // decreasing 16px for padding fix
|
||||
: cellSize === 'condensed'
|
||||
? '23px'
|
||||
: '29px',
|
||||
}}
|
||||
ref={nonEditableCellValueRef}
|
||||
>
|
||||
{cellValue}
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
|
||||
const getOverlay = () => {
|
||||
return (
|
||||
<div
|
||||
className={`overlay-cell-table ${darkMode && 'dark-theme'}`}
|
||||
onMouseEnter={() => setHovered(true)}
|
||||
onMouseLeave={() => setHovered(false)}
|
||||
style={{ whiteSpace: 'pre-wrap', color: 'var(--text-primary)' }}
|
||||
>
|
||||
<span
|
||||
dangerouslySetInnerHTML={{ __html: DOMPurify.sanitize(cellValue) }}
|
||||
style={{
|
||||
maxWidth: containerWidth,
|
||||
width: containerWidth,
|
||||
}}
|
||||
></span>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
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 (
|
||||
<>
|
||||
<OverlayTrigger
|
||||
placement="bottom"
|
||||
overlay={_showOverlay ? getOverlay() : <div></div>}
|
||||
trigger={_showOverlay && ['hover']}
|
||||
rootClose={true}
|
||||
show={_showOverlay && showOverlay && !isEditing}
|
||||
>
|
||||
<div
|
||||
className={`h-100 d-flex ${
|
||||
_showOverlay && isEditable ? '' : 'justify-content-center'
|
||||
} flex-column position-relative`}
|
||||
style={{ ...(isEditing && { zIndex: 2 }) }}
|
||||
>
|
||||
<div
|
||||
onMouseMove={() => {
|
||||
if (!hovered) setHovered(true);
|
||||
}}
|
||||
onMouseLeave={() => setHovered(false)}
|
||||
ref={ref}
|
||||
className={`${!isValid ? 'is-invalid h-100' : ''} ${isEditing ? 'h-100 content-editing' : ''}`}
|
||||
>
|
||||
{!isEditable ? _renderNonEditableData() : _renderTextArea()}
|
||||
</div>
|
||||
{isEditable && <div className={isValid ? '' : 'invalid-feedback text-truncate'}>{validationError}</div>}
|
||||
</div>
|
||||
</OverlayTrigger>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default Text;
|
||||
24
frontend/src/Editor/Components/Table/Timepicker.jsx
Normal file
24
frontend/src/Editor/Components/Table/Timepicker.jsx
Normal file
|
|
@ -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 (
|
||||
<div className="xxx">
|
||||
<TimePickerComponent
|
||||
selected={value && moment(dateObject.toISOString()).toDate()}
|
||||
timeFormat={
|
||||
component.component.definition.properties.enableTwentyFourHour.value == '{{true}}' ? `HH:mm a` : `hh:mm a`
|
||||
}
|
||||
onChange={(value) => {
|
||||
onChange(moment(value).format('hh:mm'));
|
||||
}}
|
||||
enableTime={true}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
|
@ -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';
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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 (
|
||||
<div className="h-100 d-flex flex-column justify-content-center">
|
||||
<input
|
||||
type="text"
|
||||
style={{ ...cellStyles }}
|
||||
onKeyDown={(e) => {
|
||||
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()}
|
||||
/>
|
||||
<div className="invalid-feedback">{validationError}</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
const cellTextColor = resolveReferences(column.textColor, currentState, '', { cellValue, rowData });
|
||||
return (
|
||||
<div
|
||||
className={`d-flex align-items-center h-100 w-100 justify-content-${determineJustifyContentValue(
|
||||
horizontalAlignment
|
||||
)}`}
|
||||
style={cellStyles}
|
||||
>
|
||||
{String(cellValue)}
|
||||
</div>
|
||||
<String
|
||||
isEditable={isEditable}
|
||||
darkMode={darkMode}
|
||||
handleCellValueChange={handleCellValueChange}
|
||||
cellTextColor={cellTextColor}
|
||||
horizontalAlignment={horizontalAlignment}
|
||||
cellValue={cellValue}
|
||||
column={column}
|
||||
currentState={currentState}
|
||||
containerWidth={width}
|
||||
cell={cell}
|
||||
isMaxRowHeightAuto={isMaxRowHeightAuto}
|
||||
cellSize={cellSize}
|
||||
maxRowHeightValue={maxRowHeightValue}
|
||||
/>
|
||||
);
|
||||
|
||||
// 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 ?? 'inherit',
|
||||
// };
|
||||
|
||||
// return (
|
||||
// <div className="h-100 d-flex flex-column justify-content-center position-relative">
|
||||
// <div
|
||||
// rows="1"
|
||||
// contentEditable={true}
|
||||
// className={`${!isValid ? 'is-invalid' : ''} h-100 text-container long-text-input ${darkMode ? ' textarea-dark-theme' : ''
|
||||
// }`}
|
||||
// style={{
|
||||
// ...cellStyles,
|
||||
// maxWidth: width,
|
||||
// outline: 'none',
|
||||
// border: 'none',
|
||||
// background: 'inherit',
|
||||
// }}
|
||||
// readOnly={!isEditable}
|
||||
// onBlur={(e) => {
|
||||
// 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}
|
||||
// </div>
|
||||
// <div className={isValid ? '' : 'invalid-feedback'}>{validationError}</div>
|
||||
// </div>
|
||||
// );
|
||||
// }
|
||||
// return (
|
||||
// <div
|
||||
// className={`d-flex align-items-center h-100 w-100 justify-content-${determineJustifyContentValue(
|
||||
// horizontalAlignment
|
||||
// )}`}
|
||||
// style={cellStyles}
|
||||
// >
|
||||
// {String(cellValue)}
|
||||
// </div>
|
||||
// );
|
||||
}
|
||||
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 (
|
||||
<div className="h-100 d-flex flex-column justify-content-center">
|
||||
<div className="h-100 d-flex flex-column justify-content-center position-relative">
|
||||
<input
|
||||
type="number"
|
||||
style={{ ...cellStyles }}
|
||||
style={{
|
||||
...cellStyles,
|
||||
outline: 'none',
|
||||
border: 'none',
|
||||
background: 'inherit',
|
||||
paddingRight: '20px',
|
||||
}}
|
||||
id={`table-input-${column.id}-${cell.row.id}`}
|
||||
onKeyDown={(e) => {
|
||||
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}
|
||||
/>
|
||||
<div className="invalid-feedback">{validationError}</div>
|
||||
<div className="arror-container">
|
||||
<div onClick={(e) => handleIncrement(e)}>
|
||||
<SolidIcon
|
||||
width={'16px'}
|
||||
style={{
|
||||
top: '1px',
|
||||
right: '1px',
|
||||
zIndex: 3,
|
||||
}}
|
||||
className="numberinput-up-arrow-table "
|
||||
name="uparrow"
|
||||
fill={'var(--icons-default)'}
|
||||
></SolidIcon>
|
||||
</div>
|
||||
<div onClick={(e) => handleDecrement(e)}>
|
||||
<SolidIcon
|
||||
style={{
|
||||
right: '1px',
|
||||
bottom: '1px',
|
||||
zIndex: 3,
|
||||
}}
|
||||
width={'16px'}
|
||||
className="numberinput-down-arrow-table"
|
||||
name="downarrow"
|
||||
fill={'var(--icons-default)'}
|
||||
></SolidIcon>
|
||||
</div>
|
||||
</div>
|
||||
<div className={isValid ? '' : 'invalid-feedback text-truncate'}>{validationError}</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
@ -269,31 +405,29 @@ export default function generateColumnsData({
|
|||
</div>
|
||||
);
|
||||
}
|
||||
case 'text': {
|
||||
case 'text':
|
||||
return (
|
||||
<textarea
|
||||
rows="1"
|
||||
className={`form-control-plaintext text-container ${
|
||||
darkMode ? 'text-light textarea-dark-theme' : 'text-muted'
|
||||
}`}
|
||||
readOnly={!isEditable}
|
||||
onBlur={(e) => {
|
||||
if (isEditable && e.target.defaultValue !== e.target.value) {
|
||||
handleCellValueChange(cell.row.index, column.key || column.name, e.target.value, cell.row.original);
|
||||
}
|
||||
}}
|
||||
onKeyDown={(e) => {
|
||||
e.persist();
|
||||
if (e.key === 'Enter' && !e.shiftKey && isEditable) {
|
||||
handleCellValueChange(cell.row.index, column.key || column.name, e.target.value, cell.row.original);
|
||||
}
|
||||
}}
|
||||
defaultValue={cellValue}
|
||||
onFocus={(e) => e.stopPropagation()}
|
||||
></textarea>
|
||||
// <div className="h-100 d-flex flex-column justify-content-center position-relative">
|
||||
<Text
|
||||
isEditable={isEditable}
|
||||
darkMode={darkMode}
|
||||
handleCellValueChange={handleCellValueChange}
|
||||
cellTextColor={cellTextColor}
|
||||
horizontalAlignment={horizontalAlignment}
|
||||
cellValue={cellValue}
|
||||
column={column}
|
||||
currentState={currentState}
|
||||
containerWidth={width}
|
||||
cell={cell}
|
||||
isMaxRowHeightAuto={isMaxRowHeightAuto}
|
||||
cellSize={cellSize}
|
||||
maxRowHeightValue={maxRowHeightValue}
|
||||
/>
|
||||
// </div>
|
||||
);
|
||||
}
|
||||
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 (
|
||||
<div className="h-100 d-flex align-items-center">
|
||||
<SelectSearch
|
||||
options={columnOptions.selectOptions}
|
||||
value={cellValue}
|
||||
search={true}
|
||||
onChange={(value) => {
|
||||
handleCellValueChange(cell.row.index, column.key || column.name, value, cell.row.original);
|
||||
}}
|
||||
fuzzySearch
|
||||
placeholder={t('globals.select', 'Select') + '...'}
|
||||
disabled={!isEditable}
|
||||
className="select-search"
|
||||
/>
|
||||
<div className={`invalid-feedback ${isValid ? '' : 'd-flex'}`}>{validationError}</div>
|
||||
<div
|
||||
className="h-100 d-flex align-items-center flex-column justify-content-center"
|
||||
styles={{ flex: '1 1 0' }}
|
||||
>
|
||||
{columnType === 'dropdown' && (
|
||||
<SelectSearch
|
||||
options={columnOptions.selectOptions}
|
||||
value={cellValue}
|
||||
search={true}
|
||||
onChange={(value) => {
|
||||
handleCellValueChange(cell.row.index, column.key || column.name, value, cell.row.original);
|
||||
}}
|
||||
fuzzySearch
|
||||
placeholder={t('globals.select', 'Select') + '...'}
|
||||
disabled={!isEditable}
|
||||
className="select-search"
|
||||
/>
|
||||
)}
|
||||
{['newMultiSelect', 'select'].includes(columnType) && (
|
||||
<CustomSelect
|
||||
options={columnOptions.selectOptions}
|
||||
value={cellValue}
|
||||
search={true}
|
||||
onChange={(value) => {
|
||||
handleCellValueChange(cell.row.index, column.key || column.name, value, cell.row.original);
|
||||
}}
|
||||
fuzzySearch
|
||||
placeholder={t('globals.select', 'Select') + '...'}
|
||||
disabled={!isEditable}
|
||||
className="select-search table-select-search"
|
||||
darkMode={darkMode}
|
||||
defaultOptionsList={column?.defaultOptionsList || []}
|
||||
textColor={column?.textColor || 'var(--slate12)'}
|
||||
isMulti={columnType === 'newMultiSelect' ? true : false}
|
||||
containerWidth={width}
|
||||
optionsLoadingState={
|
||||
resolveReferences(column?.useDynamicOptions, currentState) &&
|
||||
resolveReferences(column?.optionsLoadingState, currentState)
|
||||
? true
|
||||
: false
|
||||
}
|
||||
horizontalAlignment={determineJustifyContentValue(horizontalAlignment)}
|
||||
isEditable={isEditable}
|
||||
isMaxRowHeightAuto={contentWrap && isMaxRowHeightAuto}
|
||||
/>
|
||||
)}
|
||||
<div className={` ${isValid ? 'd-none' : 'invalid-feedback d-block'}`}>{validationError}</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
@ -357,11 +523,11 @@ export default function generateColumnsData({
|
|||
case 'badges': {
|
||||
return (
|
||||
<div
|
||||
className={`h-100 d-flex align-items-center justify-content-${determineJustifyContentValue(
|
||||
className={`h-100 w-100 d-flex align-items-center justify-content-${determineJustifyContentValue(
|
||||
horizontalAlignment
|
||||
)}`}
|
||||
>
|
||||
<CustomSelect
|
||||
<CustomDropdown
|
||||
options={columnOptions.selectOptions}
|
||||
value={cellValue}
|
||||
multiple={columnType === 'badges'}
|
||||
|
|
@ -371,37 +537,38 @@ export default function generateColumnsData({
|
|||
darkMode={darkMode}
|
||||
isEditable={isEditable}
|
||||
width={width}
|
||||
contentWrap={contentWrap}
|
||||
autoHeight={autoHeight}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
case 'tags': {
|
||||
return (
|
||||
<div>
|
||||
<Tags
|
||||
value={cellValue}
|
||||
onChange={(value) => {
|
||||
handleCellValueChange(cell.row.index, column.key || column.name, value, cell.row.original);
|
||||
}}
|
||||
readOnly={!isEditable}
|
||||
/>
|
||||
</div>
|
||||
<Tags
|
||||
value={cellValue}
|
||||
onChange={(value) => {
|
||||
handleCellValueChange(cell.row.index, column.key || column.name, value, cell.row.original);
|
||||
}}
|
||||
readOnly={!isEditable}
|
||||
containerWidth={width}
|
||||
/>
|
||||
);
|
||||
}
|
||||
case 'image': {
|
||||
const computeImageHeight = column?.height ? `${column?.height}px` : '100%';
|
||||
return (
|
||||
<div>
|
||||
<div style={{ height: '100%', display: 'flex', alignItems: 'center' }}>
|
||||
{cellValue && (
|
||||
<img
|
||||
src={cellValue}
|
||||
style={{
|
||||
pointerEvents: 'auto',
|
||||
width: `${column?.width}px`,
|
||||
height: `${column?.height}px`,
|
||||
height: computeImageHeight,
|
||||
borderRadius: `${column?.borderRadius}%`,
|
||||
objectFit: `${column?.objectFit}`,
|
||||
}}
|
||||
alt={cellValue}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
|
|
@ -417,6 +584,7 @@ export default function generateColumnsData({
|
|||
onChange={(value) => {
|
||||
handleCellValueChange(cell.row.index, column.key || column.name, value, cell.row.original);
|
||||
}}
|
||||
containerWidth={width}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
|
@ -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 (
|
||||
<div className="h-100 d-flex align-items-center">
|
||||
<div className="h-100 d-flex flex-column justify-content-center">
|
||||
<Datepicker
|
||||
timeZoneValue={column.timeZoneValue}
|
||||
timeZoneDisplay={column.timeZoneDisplay}
|
||||
dateDisplayFormat={column.dateFormat}
|
||||
isTimeChecked={column.isTimeChecked}
|
||||
isTimeChecked={isTimeChecked}
|
||||
value={cellValue}
|
||||
readOnly={isEditable}
|
||||
readOnly={!isEditable}
|
||||
parseDateFormat={column.parseDateFormat}
|
||||
onChange={(value) => {
|
||||
handleCellValueChange(cell.row.index, column.key || column.name, value, cell.row.original);
|
||||
}}
|
||||
tableRef={tableRef}
|
||||
isDateSelectionEnabled={isDateSelectionEnabled}
|
||||
isTwentyFourHrFormatEnabled={isTwentyFourHrFormatEnabled}
|
||||
timeFormat={column.timeFormat}
|
||||
parseTimeFormat={column.parseTimeFormat}
|
||||
parseInUnixTimestamp={parseInUnixTimestamp}
|
||||
unixTimeStamp={column.unixTimestamp}
|
||||
disabledDates={disabledDates}
|
||||
unixTimestamp={column.unixTimestamp}
|
||||
cellStyles={cellStyles}
|
||||
darkMode={darkMode}
|
||||
/>
|
||||
{isEditable && (
|
||||
<div className={isValid ? '' : 'invalid-feedback d-block text-truncate'}>{validationError}</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
case 'link': {
|
||||
const linkTarget = resolveReferences(column?.linkTarget ?? '_blank', currentState);
|
||||
const linkTarget = resolveReferences(column?.linkTarget ?? '{{true}}', currentState);
|
||||
column = {
|
||||
...column,
|
||||
linkColor: column?.linkColor ?? '#1B1F24',
|
||||
underlineColor: column?.underlineColor ?? '#4368E3',
|
||||
};
|
||||
return (
|
||||
<div className="h-100 d-flex align-items-center">
|
||||
<Link cellValue={cellValue} linkTarget={linkTarget} />
|
||||
<Link
|
||||
cellValue={cellValue}
|
||||
linkTarget={linkTarget}
|
||||
linkColor={column.linkColor}
|
||||
underlineColor={column.underlineColor}
|
||||
underline={column.underline}
|
||||
displayText={column.displayText}
|
||||
darkMode={darkMode}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
case 'boolean': {
|
||||
return (
|
||||
<div className="h-100 d-flex align-items-center">
|
||||
<Boolean
|
||||
value={!!cellValue}
|
||||
isEditable={isEditable}
|
||||
onChange={(value) =>
|
||||
handleCellValueChange(cell.row.index, column.key || column.name, value, cell.row.original)
|
||||
}
|
||||
toggleOnBg={column?.toggleOnBg}
|
||||
toggleOffBg={column?.toggleOffBg}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
418
frontend/src/Editor/Components/Table/datepicker.scss
Normal file
418
frontend/src/Editor/Components/Table/datepicker.scss
Normal file
|
|
@ -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,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="%23424242" width="18px" height="18px"><path d="M7 10l5 5 5-5z" /></svg>');
|
||||
/* 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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,
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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';
|
||||
|
|
|
|||
|
|
@ -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 = () => (
|
||||
<>
|
||||
<div
|
||||
data-cy={`label-${String(component.name).toLowerCase()}`}
|
||||
data-disabled={disable || loading}
|
||||
data-cy={`label-${String(component.name).toLowerCase()} `}
|
||||
className={`text-input d-flex ${
|
||||
defaultAlignment === 'top' &&
|
||||
((width != 0 && label?.length != 0) || (auto && width == 0 && label && label?.length != 0))
|
||||
|
|
@ -279,7 +293,7 @@ export const TextInput = function TextInput({
|
|||
? '11px'
|
||||
: (label?.length > 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({
|
|||
</div>
|
||||
{showValidationError && visibility && (
|
||||
<div
|
||||
className="tj-text-sm"
|
||||
data-cy={`${String(component.name).toLowerCase()}-invalid-feedback`}
|
||||
style={{
|
||||
color: errTextColor,
|
||||
textAlign: direction == 'left' && 'end',
|
||||
fontSize: '11px',
|
||||
fontWeight: '400',
|
||||
lineHeight: '16px',
|
||||
}}
|
||||
>
|
||||
{showValidationError && validationError}
|
||||
|
|
|
|||
79
frontend/src/Editor/Components/tableUtils.js
Normal file
79
frontend/src/Editor/Components/tableUtils.js
Normal file
|
|
@ -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;
|
||||
};
|
||||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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 (
|
||||
<div className="apploader">
|
||||
<div className={cx('apploader', { 'dark-theme theme-dark': props.darkMode })}>
|
||||
<div className="col col-* editor-center-wrapper">
|
||||
<div className="editor-center">
|
||||
<div className="canvas">
|
||||
|
|
@ -1673,7 +1745,7 @@ const EditorComponent = (props) => {
|
|||
);
|
||||
}
|
||||
return (
|
||||
<div className="editor wrapper">
|
||||
<div className={`editor wrapper`}>
|
||||
<Confirm
|
||||
show={queryConfirmationList?.length > 0}
|
||||
message={`Do you want to run this query - ${queryConfirmationList[0]?.queryName}?`}
|
||||
|
|
@ -1767,14 +1839,18 @@ const EditorComponent = (props) => {
|
|||
id="main-editor-canvas"
|
||||
>
|
||||
<div
|
||||
className={`canvas-container align-items-center ${!showLeftSidebar && 'hide-sidebar'}`}
|
||||
className={cx(
|
||||
'canvas-container align-items-center',
|
||||
{ 'dark-theme theme-dark': isAppDarkMode },
|
||||
{ 'hide-sidebar': !showLeftSidebar }
|
||||
)}
|
||||
style={{
|
||||
transform: `scale(${zoomLevel})`,
|
||||
borderLeft:
|
||||
(editorMarginLeft ? editorMarginLeft - 1 : editorMarginLeft) +
|
||||
`px solid ${computeCanvasBackgroundColor()}`,
|
||||
height: computeCanvasContainerHeight(),
|
||||
background: !props.darkMode ? '#EBEBEF' : '#2E3035',
|
||||
background: !isAppDarkMode ? '#EBEBEF' : '#2E3035',
|
||||
}}
|
||||
onMouseUp={handleCanvasContainerMouseUp}
|
||||
ref={canvasContainerRef}
|
||||
|
|
@ -1821,14 +1897,14 @@ const EditorComponent = (props) => {
|
|||
</div>
|
||||
)}
|
||||
{defaultComponentStateComputed && (
|
||||
<>
|
||||
<div>
|
||||
<Container
|
||||
canvasWidth={getCanvasWidth()}
|
||||
socket={socket}
|
||||
appDefinition={appDefinition}
|
||||
appDefinitionChanged={appDefinitionChanged}
|
||||
snapToGrid={true}
|
||||
darkMode={props.darkMode}
|
||||
darkMode={isAppDarkMode}
|
||||
mode={'edit'}
|
||||
zoomLevel={zoomLevel}
|
||||
deviceWindowWidth={deviceWindowWidth}
|
||||
|
|
@ -1849,7 +1925,7 @@ const EditorComponent = (props) => {
|
|||
canvasWidth={getCanvasWidth()}
|
||||
onDragging={(isDragging) => setIsDragging(isDragging)}
|
||||
/>
|
||||
</>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -1868,7 +1944,7 @@ const EditorComponent = (props) => {
|
|||
/>
|
||||
<ReactTooltip id="tooltip-for-add-query" className="tooltip" />
|
||||
</div>
|
||||
<div className="editor-sidebar">
|
||||
<div className={cx('editor-sidebar', { 'dark-theme theme-dark': props.darkMode })}>
|
||||
<EditorKeyHooks
|
||||
moveComponents={moveComponents}
|
||||
cloneComponents={cloningComponents}
|
||||
|
|
@ -1898,7 +1974,9 @@ const EditorComponent = (props) => {
|
|||
/>
|
||||
</div>
|
||||
{config.COMMENT_FEATURE_ENABLE && showComments && (
|
||||
<CommentNotifications socket={socket} pageId={currentPageId} />
|
||||
<div className={cx({ 'dark-theme theme-dark': props.darkMode })}>
|
||||
<CommentNotifications socket={socket} pageId={currentPageId} />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</DndProvider>
|
||||
|
|
|
|||
|
|
@ -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 (
|
||||
|
|
|
|||
56
frontend/src/Editor/Header/AppModeToggle.jsx
Normal file
56
frontend/src/Editor/Header/AppModeToggle.jsx
Normal file
|
|
@ -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 (
|
||||
<div className="d-flex align-items-center mb-3">
|
||||
<span data-cy={`label-maintenance-mode`}>{t('leftSidebar.Settings.appMode', 'App mode')}</span>
|
||||
<div className="ms-auto position-relative app-mode-switch" style={{ paddingLeft: '0px', width: '158px' }}>
|
||||
<ToggleGroup
|
||||
onValueChange={(value) => {
|
||||
onAppModeChange(value);
|
||||
let exposedTheme = value;
|
||||
if (value === 'auto') {
|
||||
exposedTheme = localStorage.getItem('darkMode') === 'true' ? 'dark' : 'light';
|
||||
}
|
||||
useSuperStore
|
||||
.getState()
|
||||
.modules[moduleName].useCurrentStateStore.getState()
|
||||
.actions.setCurrentState({
|
||||
globals: {
|
||||
...useSuperStore.getState().modules[moduleName].useCurrentStateStore.getState().globals,
|
||||
theme: { name: exposedTheme },
|
||||
},
|
||||
});
|
||||
globalSettingsChanged({ appMode: value });
|
||||
}}
|
||||
defaultValue={appMode}
|
||||
>
|
||||
{APP_MODES.map((appMode) => (
|
||||
<ToggleGroupItem key={appMode.value} value={appMode.value}>
|
||||
{appMode.label}
|
||||
</ToggleGroupItem>
|
||||
))}
|
||||
</ToggleGroup>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default AppModeToggle;
|
||||
|
|
@ -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 = ({
|
|||
/>
|
||||
)}
|
||||
<div id="" className={cx({ 'dark-theme': darkMode })}>
|
||||
<div bsPrefix="global-settings-popover">
|
||||
<div bsPrefix="global-settings-popover" className="global-settings-panel">
|
||||
<HeaderSection darkMode={darkMode}>
|
||||
<HeaderSection.PanelHeader title="Global settings" />
|
||||
</HeaderSection>
|
||||
|
|
@ -380,7 +380,7 @@ export const GlobalSettings = ({
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<AppModeToggle globalSettingsChanged={globalSettingsChanged} />
|
||||
<div className="d-flex align-items-center global-popover-div-wrap mb-3">
|
||||
<p className="tj-text-xsm color-slate12 w-full m-auto">Export app</p>
|
||||
<div>
|
||||
|
|
|
|||
|
|
@ -13,8 +13,8 @@ function HeaderActions({
|
|||
showToggleLayoutBtn,
|
||||
showUndoRedoBtn,
|
||||
showFullWidth,
|
||||
darkMode,
|
||||
}) {
|
||||
const darkMode = localStorage.getItem('darkMode') === 'true';
|
||||
const { currentLayout, toggleCurrentLayout } = useEditorStore(
|
||||
(state) => ({
|
||||
currentLayout: state.currentLayout,
|
||||
|
|
|
|||
|
|
@ -81,7 +81,7 @@ export default function EditorHeader({
|
|||
const shouldRenderReleaseButton = !!app?.id;
|
||||
|
||||
return (
|
||||
<div className="header" style={{ width: '100%' }}>
|
||||
<div className={cx('header', { 'dark-theme theme-dark': darkMode })} style={{ width: '100%' }}>
|
||||
<header className="navbar navbar-expand-md d-print-none">
|
||||
<div className="container-xl header-container">
|
||||
<div className="d-flex w-100">
|
||||
|
|
@ -116,6 +116,7 @@ export default function EditorHeader({
|
|||
handleRedo={handleRedo}
|
||||
showToggleLayoutBtn
|
||||
showUndoRedoBtn
|
||||
darkMode={darkMode}
|
||||
/>
|
||||
<div className="d-flex align-items-center">
|
||||
<div style={{ width: '100px', marginRight: '20px' }}>
|
||||
|
|
@ -153,6 +154,7 @@ export default function EditorHeader({
|
|||
setAppDefinitionFromVersion={setAppDefinitionFromVersion}
|
||||
onVersionDelete={onVersionDelete}
|
||||
isPublic={isPublic ?? false}
|
||||
darkMode={darkMode}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -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') {
|
||||
|
|
|
|||
|
|
@ -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 (
|
||||
<>
|
||||
<Popover.Header>
|
||||
<div className="d-flex custom-gap-4 align-self-stretch tj-text tj-text-xsm font-weight-500 text-secondary cursor-pointer">
|
||||
<div
|
||||
className={`${activeTab === 'propertiesTab' && 'active-column-tab'} column-header-tab`}
|
||||
onClick={() => {
|
||||
if (activeTab !== 'propertiesTab') setActiveTab('propertiesTab');
|
||||
}}
|
||||
>
|
||||
Properties
|
||||
</div>
|
||||
<div
|
||||
className={`${activeTab === 'stylesTab' && 'active-column-tab'} column-header-tab`}
|
||||
onClick={() => {
|
||||
if (activeTab !== 'stylesTab') setActiveTab('stylesTab');
|
||||
}}
|
||||
>
|
||||
Styles
|
||||
</div>
|
||||
</div>
|
||||
</Popover.Header>
|
||||
<Popover.Body className={`table-column-popover ${darkMode && 'theme-dark'}`}>
|
||||
{activeTab === 'propertiesTab' ? (
|
||||
<PropertiesTabElements
|
||||
column={column}
|
||||
index={index}
|
||||
darkMode={darkMode}
|
||||
currentState={currentState}
|
||||
onColumnItemChange={onColumnItemChange}
|
||||
getPopoverFieldSource={getPopoverFieldSource}
|
||||
setColumnPopoverRootCloseBlocker={setColumnPopoverRootCloseBlocker}
|
||||
component={component}
|
||||
props={props}
|
||||
columnEventChanged={columnEventChanged}
|
||||
timeZoneOptions={timeZoneOptions}
|
||||
handleEventManagerPopoverCallback={handleEventManagerPopoverCallback}
|
||||
/>
|
||||
) : (
|
||||
<StylesTabElements
|
||||
column={column}
|
||||
index={index}
|
||||
darkMode={darkMode}
|
||||
currentState={currentState}
|
||||
onColumnItemChange={onColumnItemChange}
|
||||
getPopoverFieldSource={getPopoverFieldSource}
|
||||
component={component}
|
||||
/>
|
||||
)}
|
||||
</Popover.Body>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
|
@ -0,0 +1,389 @@
|
|||
import React, { useState } from 'react';
|
||||
import { ProgramaticallyHandleProperties } from '../ProgramaticallyHandleProperties';
|
||||
import Select from '@/_ui/Select';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import Accordion from '@/_ui/Accordion';
|
||||
import { resolveReferences } from '@/_helpers/utils';
|
||||
import styles from '@/_ui/Select/styles';
|
||||
import FxButton from '../../../../CodeBuilder/Elements/FxButton';
|
||||
import { CodeHinter } from '../../../../CodeBuilder/CodeHinter';
|
||||
|
||||
const TIMEZONE_OPTIONS = [
|
||||
{ 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' },
|
||||
];
|
||||
|
||||
const DATE_FORMAT_OPTIONS = [
|
||||
{
|
||||
label: 'DD/MM/YYYY',
|
||||
value: 'DD/MM/YYYY',
|
||||
},
|
||||
{
|
||||
label: 'MM/DD/YYYY',
|
||||
value: 'MM/DD/YYYY',
|
||||
},
|
||||
{
|
||||
label: 'YYYY/DD/MM',
|
||||
value: 'YYYY/DD/MM',
|
||||
},
|
||||
{
|
||||
label: 'YYYY/MM/DD',
|
||||
value: 'YYYY/MM/DD',
|
||||
},
|
||||
];
|
||||
|
||||
const UNIX_TIMESTAMP_OPTIONS = [
|
||||
{
|
||||
label: 's',
|
||||
value: 'seconds',
|
||||
},
|
||||
{
|
||||
label: 'ms',
|
||||
value: 'milliseconds',
|
||||
},
|
||||
];
|
||||
|
||||
const DatepickerProperties = ({ column, index, darkMode, currentState, onColumnItemChange, component }) => {
|
||||
const { t } = useTranslation();
|
||||
const items = [];
|
||||
const [isDateDisplayFormatFxOn, setIsDateDisplayFormatFxOn] = useState(
|
||||
!column?.notActiveFxActiveFields?.includes('dateFormat') ?? true
|
||||
);
|
||||
const [isParseDateFormatFxOn, setIsParseDateFormatFxOn] = useState(
|
||||
!column?.notActiveFxActiveFields?.includes('parseDateFormat') ?? true
|
||||
);
|
||||
|
||||
items.push(
|
||||
{
|
||||
title: 'Date format',
|
||||
className: 'table-date-picker-formatting-accordion',
|
||||
children: (
|
||||
<>
|
||||
<div className="grey-bg-section mb-2 border">
|
||||
<div style={{ background: 'var(--surfaces-surface-02)', padding: '8px 12px', borderRadius: '6px' }}>
|
||||
<ProgramaticallyHandleProperties
|
||||
label="Enable date"
|
||||
currentState={currentState}
|
||||
index={index}
|
||||
darkMode={darkMode}
|
||||
callbackFunction={onColumnItemChange}
|
||||
property="isDateSelectionEnabled"
|
||||
props={column}
|
||||
component={component}
|
||||
paramType="properties"
|
||||
paramMeta={{ type: 'toggle', displayName: 'Enable date' }}
|
||||
/>
|
||||
</div>
|
||||
{resolveReferences(column?.isDateSelectionEnabled, currentState) && (
|
||||
<div
|
||||
data-cy={`input-date-display-format`}
|
||||
className="field mb-2 w-100"
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
>
|
||||
<div style={{ padding: '0px 12px' }}>
|
||||
<div className="d-flex justify-content-between">
|
||||
<label data-cy={`label-date-display-format`} className="form-label">
|
||||
{t('widget.Table.dateDisplayformat', 'Date format')}
|
||||
</label>
|
||||
<span>
|
||||
<FxButton
|
||||
active={isDateDisplayFormatFxOn}
|
||||
onPress={() => {
|
||||
let resultFxActiveFields = column?.notActiveFxActiveFields || [];
|
||||
if (isDateDisplayFormatFxOn) {
|
||||
resultFxActiveFields.push('dateFormat');
|
||||
} else {
|
||||
resultFxActiveFields = resultFxActiveFields.filter((field) => field !== 'dateFormat');
|
||||
}
|
||||
setIsDateDisplayFormatFxOn(!isDateDisplayFormatFxOn);
|
||||
onColumnItemChange(index, 'notActiveFxActiveFields', resultFxActiveFields);
|
||||
}}
|
||||
/>
|
||||
</span>
|
||||
</div>
|
||||
{isDateDisplayFormatFxOn ? (
|
||||
<CodeHinter
|
||||
initialValue={column?.dateFormat}
|
||||
theme={darkMode ? 'monokai' : 'default'}
|
||||
mode="javascript"
|
||||
lineNumbers={false}
|
||||
// placeholder={validation?.placeholder ?? ''}
|
||||
onChange={(value) => onColumnItemChange(index, 'dateFormat', value)}
|
||||
/>
|
||||
) : (
|
||||
<Select
|
||||
options={DATE_FORMAT_OPTIONS}
|
||||
value={column?.dateFormat ?? 'DD/MM/YYYY'}
|
||||
search={true}
|
||||
closeOnSelect={true}
|
||||
onChange={(value) => {
|
||||
onColumnItemChange(index, 'dateFormat', value);
|
||||
}}
|
||||
fuzzySearch
|
||||
placeholder="Select.."
|
||||
useCustomStyles={true}
|
||||
styles={styles(darkMode, '100%')}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<div className="grey-bg-section mb-2 border">
|
||||
<div style={{ background: 'var(--surfaces-surface-02)', padding: '8px 12px', borderRadius: '6px' }}>
|
||||
<ProgramaticallyHandleProperties
|
||||
label="Enable time"
|
||||
currentState={currentState}
|
||||
index={index}
|
||||
darkMode={darkMode}
|
||||
callbackFunction={onColumnItemChange}
|
||||
property="isTimeChecked"
|
||||
props={column}
|
||||
component={component}
|
||||
paramType="properties"
|
||||
paramMeta={{ type: 'toggle', displayName: 'Enable time' }}
|
||||
/>
|
||||
</div>
|
||||
{resolveReferences(column?.isTimeChecked, currentState) && (
|
||||
<>
|
||||
{!isDateDisplayFormatFxOn && (
|
||||
<div className="field mb-2" onClick={(e) => e.stopPropagation()} style={{ padding: '0px 12px' }}>
|
||||
<label className="form-label">{t('widget.Table.timeFormat', 'Time Format')}</label>
|
||||
<Select
|
||||
options={[
|
||||
{
|
||||
label: 'HH:mm',
|
||||
value: 'HH:mm',
|
||||
},
|
||||
]}
|
||||
value={column?.timeFormat ?? 'HH:mm'}
|
||||
search={true}
|
||||
closeOnSelect={true}
|
||||
onChange={(value) => {
|
||||
onColumnItemChange(index, 'timeFormat', value);
|
||||
}}
|
||||
fuzzySearch
|
||||
placeholder="Select.."
|
||||
useCustomStyles={true}
|
||||
styles={styles(darkMode, '100%')}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
<div style={{ padding: '0px 12px' }}>
|
||||
<ProgramaticallyHandleProperties
|
||||
label="Enable 24 hr time format"
|
||||
currentState={currentState}
|
||||
index={index}
|
||||
darkMode={darkMode}
|
||||
callbackFunction={onColumnItemChange}
|
||||
property="isTwentyFourHrFormatEnabled"
|
||||
props={column}
|
||||
component={component}
|
||||
paramType="properties"
|
||||
paramMeta={{ type: 'toggle', displayName: 'Enable 24 hr time format' }}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div
|
||||
data-cy={`input-display-time-zone`}
|
||||
className="field mb-2"
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
style={{ padding: '0px 12px' }}
|
||||
>
|
||||
<label data-cy={`label-display-time-zone`} className="form-label">
|
||||
Time zone
|
||||
</label>
|
||||
<Select
|
||||
options={TIMEZONE_OPTIONS}
|
||||
value={column?.timeZoneDisplay ?? ''}
|
||||
search={true}
|
||||
closeOnSelect={true}
|
||||
onChange={(value) => {
|
||||
onColumnItemChange(index, 'timeZoneDisplay', value);
|
||||
}}
|
||||
fuzzySearch
|
||||
placeholder="Select.."
|
||||
useCustomStyles={true}
|
||||
styles={styles(darkMode, '100%')}
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: 'Parse format',
|
||||
children: (
|
||||
<>
|
||||
<ProgramaticallyHandleProperties
|
||||
label="Parse in unix timestamp"
|
||||
currentState={currentState}
|
||||
index={index}
|
||||
darkMode={darkMode}
|
||||
callbackFunction={onColumnItemChange}
|
||||
property="parseInUnixTimestamp"
|
||||
props={column}
|
||||
component={component}
|
||||
paramType="properties"
|
||||
paramMeta={{ type: 'toggle', displayName: 'Parse in unix timestamp' }}
|
||||
/>
|
||||
{resolveReferences(column?.parseInUnixTimestamp, currentState) ? (
|
||||
<div className="mt-2">
|
||||
<div className="field mb-2 tj-app-input">
|
||||
<label data-cy={`label-date-parse-format`} className="form-label">
|
||||
{t('widget.Table.unixTimestamp', 'Unix timestamp')}
|
||||
</label>
|
||||
<Select
|
||||
options={UNIX_TIMESTAMP_OPTIONS}
|
||||
value={column?.unixTimestamp ?? 'seconds'}
|
||||
search={true}
|
||||
closeOnSelect={true}
|
||||
onChange={(value) => {
|
||||
onColumnItemChange(index, 'unixTimestamp', value);
|
||||
}}
|
||||
fuzzySearch
|
||||
// placeholder="Select.."
|
||||
useCustomStyles={true}
|
||||
styles={styles(darkMode, '100%')}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<div className="mt-2">
|
||||
{resolveReferences(column?.isDateSelectionEnabled, currentState) && (
|
||||
<div data-cy={`input-parse-timezone`} className="field mb-2">
|
||||
<div className="d-flex justify-content-between">
|
||||
<label data-cy={`label-parse-timezone`} className="form-label">
|
||||
Date
|
||||
</label>
|
||||
<span>
|
||||
<FxButton
|
||||
active={isParseDateFormatFxOn}
|
||||
onPress={() => {
|
||||
let resultFxActiveFields = column?.notActiveFxActiveFields || [];
|
||||
if (isDateDisplayFormatFxOn) {
|
||||
resultFxActiveFields.push('parseDateFormat');
|
||||
} else {
|
||||
resultFxActiveFields = resultFxActiveFields.filter((field) => field !== 'parseDateFormat');
|
||||
}
|
||||
setIsParseDateFormatFxOn(!isParseDateFormatFxOn);
|
||||
onColumnItemChange(index, 'notActiveFxActiveFields', resultFxActiveFields);
|
||||
}}
|
||||
/>
|
||||
</span>
|
||||
</div>
|
||||
{isParseDateFormatFxOn ? (
|
||||
<CodeHinter
|
||||
initialValue={column?.parseDateFormat}
|
||||
theme={darkMode ? 'monokai' : 'default'}
|
||||
mode="javascript"
|
||||
lineNumbers={false}
|
||||
// placeholder={validation?.placeholder ?? ''}
|
||||
onChange={(value) => onColumnItemChange(index, 'parseDateFormat', value)}
|
||||
/>
|
||||
) : (
|
||||
<Select
|
||||
options={DATE_FORMAT_OPTIONS}
|
||||
value={column?.parseDateFormat ?? 'DD/MM/YYYY'}
|
||||
search={true}
|
||||
closeOnSelect={true}
|
||||
onChange={(value) => {
|
||||
onColumnItemChange(index, 'parseDateFormat', value);
|
||||
}}
|
||||
fuzzySearch
|
||||
placeholder="Select.."
|
||||
useCustomStyles={true}
|
||||
styles={styles(darkMode, '100%')}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
{resolveReferences(column?.isTimeChecked, currentState) && (
|
||||
<>
|
||||
{!isParseDateFormatFxOn && (
|
||||
<div className="field mb-2" onClick={(e) => e.stopPropagation()}>
|
||||
<label className="form-label">{t('widget.Table.timeFormat', 'Time')}</label>
|
||||
<Select
|
||||
options={[
|
||||
{
|
||||
label: 'HH:mm',
|
||||
value: 'HH:mm',
|
||||
},
|
||||
]}
|
||||
value={column?.parseTimeFormat ?? 'HH:mm'}
|
||||
search={true}
|
||||
closeOnSelect={true}
|
||||
onChange={(value) => {
|
||||
onColumnItemChange(index, 'parseTimeFormat', value);
|
||||
}}
|
||||
fuzzySearch
|
||||
placeholder="Select.."
|
||||
useCustomStyles={true}
|
||||
styles={styles(darkMode, '100%')}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
<div data-cy={`input-parse-timezone`} className="field mb-2">
|
||||
<label data-cy={`label-parse-timezone`} className="form-label">
|
||||
Time zone
|
||||
</label>
|
||||
<Select
|
||||
options={TIMEZONE_OPTIONS}
|
||||
value={column?.timeZoneValue ?? ''}
|
||||
search={true}
|
||||
closeOnSelect={true}
|
||||
onChange={(value) => {
|
||||
onColumnItemChange(index, 'timeZoneValue', value);
|
||||
}}
|
||||
fuzzySearch
|
||||
placeholder="Select.."
|
||||
useCustomStyles={true}
|
||||
styles={styles(darkMode, '100%')}
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
),
|
||||
}
|
||||
);
|
||||
|
||||
return <Accordion items={items} className="table-column-date-picker-accordion" />;
|
||||
};
|
||||
|
||||
export default DatepickerProperties;
|
||||
|
|
@ -0,0 +1,60 @@
|
|||
import React from 'react';
|
||||
import Icon from '@/_ui/Icon/solidIcons/index';
|
||||
import { ToolTip } from '@/_components/ToolTip';
|
||||
|
||||
export const DEPRECATED_COLUMN_TYPES = [
|
||||
{ label: 'Default', value: 'default', docLink: '', currentAlternativeColumnLabel: 'String' },
|
||||
{ label: 'Badge', value: 'badge', currentAlternativeColumnLabel: 'Multiselect' },
|
||||
{ label: 'Dropdown', value: 'dropdown', currentAlternativeColumnLabel: 'Single select' },
|
||||
{ label: 'Multiple badges', value: 'badges', currentAlternativeColumnLabel: 'Multiselect' },
|
||||
{ label: 'Tags', value: 'tags', currentAlternativeColumnLabel: 'Multiselect' },
|
||||
{ label: 'Radio', value: 'radio', currentAlternativeColumnLabel: 'Single select' },
|
||||
{ label: 'Multiselect', value: 'multiselect', currentAlternativeColumnLabel: 'Multiselect' },
|
||||
{ label: 'Toggle switch', value: 'toggle', currentAlternativeColumnLabel: 'Single select' },
|
||||
];
|
||||
|
||||
export const checkIfTableColumnDeprecated = (columnType) => {
|
||||
return DEPRECATED_COLUMN_TYPES.some((ct) => ct.value === columnType);
|
||||
};
|
||||
|
||||
export const TooltipBody = ({ columnLabel }) => {
|
||||
return (
|
||||
<div style={{ padding: '8px 4px', textAlign: 'left' }}>
|
||||
<div className="font-weight-bold mb-2">Deprecating column type</div>
|
||||
<div>{`This column type is deprecated and will be removed in a future update. We recommend using the new ${columnLabel} when creating applications moving forward.`}</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export const DeprecatedColumnTooltip = ({ columnType, children }) => {
|
||||
const deprecatedColumnType = DEPRECATED_COLUMN_TYPES.find((ct) => ct.value === columnType);
|
||||
return (
|
||||
<ToolTip
|
||||
message={<TooltipBody columnLabel={deprecatedColumnType?.currentAlternativeColumnLabel} />}
|
||||
show={deprecatedColumnType ?? false}
|
||||
placement="left"
|
||||
>
|
||||
{children}
|
||||
</ToolTip>
|
||||
);
|
||||
};
|
||||
|
||||
const DeprecatedColumnTypeMsg = ({ columnType }) => {
|
||||
const deprecatedColumnType = DEPRECATED_COLUMN_TYPES.find((ct) => ct.value === columnType);
|
||||
if (!deprecatedColumnType) return null;
|
||||
return (
|
||||
<div
|
||||
className="d-flex mx-3"
|
||||
style={{ padding: '12px 16px', gap: '6px', backgroundColor: '#FCEEEF', borderRadius: '6px' }}
|
||||
>
|
||||
<span>
|
||||
<Icon name={'warning'} height={16} width={16} fill="#DB4324" />
|
||||
</span>
|
||||
<span style={{ color: '#2D343B' }}>
|
||||
{`This column type is deprecated and will be removed in a future update. We recommend using the new ${deprecatedColumnType.currentAlternativeColumnLabel} when creating applications moving forward.`}
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default DeprecatedColumnTypeMsg;
|
||||
|
|
@ -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 && <DeprecatedColumnTypeMsg columnType={column.columnType} darkMode={darkMode} />}
|
||||
<div className="field px-3" data-cy={`dropdown-column-type`} onClick={(e) => e.stopPropagation()}>
|
||||
<label data-cy={`label-column-type`} className="form-label">
|
||||
{t('widget.Table.columnType', 'Column type')}
|
||||
</label>
|
||||
|
||||
<CustomSelect
|
||||
options={[
|
||||
{ label: 'String', value: 'string' },
|
||||
{ label: 'Number', value: 'number' },
|
||||
{ label: 'Text', value: 'text' },
|
||||
{ label: 'Date Picker', value: 'datepicker' },
|
||||
{ label: 'Select', value: 'select' },
|
||||
{ label: 'MultiSelect', value: 'newMultiSelect' },
|
||||
{ label: 'Boolean', value: 'boolean' },
|
||||
{ label: 'Image', value: 'image' },
|
||||
{ label: 'Link', value: 'link' },
|
||||
// Following column types are deprecated
|
||||
{ label: 'Default', value: 'default' },
|
||||
{ label: 'Dropdown', value: 'dropdown' },
|
||||
{ label: 'Multiselect', value: 'multiselect' },
|
||||
{ label: 'Toggle switch', value: 'toggle' },
|
||||
{ label: 'Radio', value: 'radio' },
|
||||
{ label: 'Badge', value: 'badge' },
|
||||
{ label: 'Multiple badges', value: 'badges' },
|
||||
{ label: 'Tags', value: 'tags' },
|
||||
]}
|
||||
components={{ DropdownIndicator, Option }}
|
||||
onChange={(value) => {
|
||||
onColumnItemChange(index, 'columnType', value);
|
||||
}}
|
||||
value={column.columnType}
|
||||
useCustomStyles={true}
|
||||
styles={customStylesForSelect}
|
||||
className={`column-type-table-inspector`}
|
||||
/>
|
||||
</div>
|
||||
<div className="field px-3" data-cy={`input-and-label-column-name`}>
|
||||
<label data-cy={`label-column-name`} className="form-label">
|
||||
{t('widget.Table.columnName', 'Column name')}
|
||||
</label>
|
||||
<CodeHinter
|
||||
currentState={currentState}
|
||||
initialValue={column.name}
|
||||
theme={darkMode ? 'monokai' : 'default'}
|
||||
mode="javascript"
|
||||
lineNumbers={false}
|
||||
placeholder={column.name}
|
||||
onChange={(value) => onColumnItemChange(index, 'name', value)}
|
||||
componentName={getPopoverFieldSource(column.columnType, 'name')}
|
||||
popOverCallback={(showing) => {
|
||||
setColumnPopoverRootCloseBlocker('name', showing);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<div data-cy={`input-and-label-key`} className="field px-3">
|
||||
<label className="form-label">{t('widget.Table.key', 'Key')}</label>
|
||||
<CodeHinter
|
||||
currentState={currentState}
|
||||
initialValue={column.key}
|
||||
theme={darkMode ? 'monokai' : 'default'}
|
||||
mode="javascript"
|
||||
lineNumbers={false}
|
||||
placeholder={column.name}
|
||||
onChange={(value) => onColumnItemChange(index, 'key', value)}
|
||||
componentName={getPopoverFieldSource(column.columnType, 'key')}
|
||||
popOverCallback={(showing) => {
|
||||
setColumnPopoverRootCloseBlocker('tableKey', showing);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<div data-cy={`transformation-field`} className="field px-3">
|
||||
<label className="form-label">{t('widget.Table.transformationField', 'Transformation')}</label>
|
||||
<CodeHinter
|
||||
currentState={currentState}
|
||||
initialValue={column?.transformation ?? '{{cellValue}}'}
|
||||
theme={darkMode ? 'monokai' : 'default'}
|
||||
mode="javascript"
|
||||
lineNumbers={false}
|
||||
placeholder={column.name}
|
||||
onChange={(value) => onColumnItemChange(index, 'transformation', value)}
|
||||
componentName={getPopoverFieldSource(column.columnType, 'transformation')}
|
||||
popOverCallback={(showing) => {
|
||||
setColumnPopoverRootCloseBlocker('transformation', showing);
|
||||
}}
|
||||
enablePreview={false}
|
||||
/>
|
||||
</div>
|
||||
{column.columnType === 'toggle' && (
|
||||
<div className="px-3">
|
||||
<EventManager
|
||||
sourceId={props?.component?.id}
|
||||
eventSourceType="table_column"
|
||||
hideEmptyEventsAlert={true}
|
||||
eventMetaDefinition={{ events: { onChange: { displayName: 'On change' } } }}
|
||||
currentState={currentState}
|
||||
dataQueries={props.dataQueries}
|
||||
components={props.components}
|
||||
eventsChanged={(events) => columnEventChanged(column, events)}
|
||||
apps={props.apps}
|
||||
popOverCallback={(showing) => {
|
||||
handleEventManagerPopoverCallback(showing);
|
||||
}}
|
||||
pages={props.pages}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
{(column.columnType === 'dropdown' ||
|
||||
column.columnType === 'multiselect' ||
|
||||
column.columnType === 'badge' ||
|
||||
column.columnType === 'badges' ||
|
||||
column.columnType === 'radio') && (
|
||||
<div>
|
||||
<div data-cy={`input-and-label-values`} className="field mb-2 px-3">
|
||||
<label className="form-label">{t('widget.Table.values', 'Values')}</label>
|
||||
<CodeHinter
|
||||
currentState={currentState}
|
||||
initialValue={column.values}
|
||||
theme={darkMode ? 'monokai' : 'default'}
|
||||
mode="javascript"
|
||||
lineNumbers={false}
|
||||
placeholder={'{{[1, 2, 3]}}'}
|
||||
onChange={(value) => onColumnItemChange(index, 'values', value)}
|
||||
componentName={getPopoverFieldSource(column.columnType, 'values')}
|
||||
popOverCallback={(showing) => {
|
||||
setColumnPopoverRootCloseBlocker('values', showing);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<div data-cy={`input-and-label-labels`} className="field mb-2 px-3">
|
||||
<label className="form-label">{t('widget.Table.labels', 'Labels')}</label>
|
||||
<CodeHinter
|
||||
currentState={currentState}
|
||||
initialValue={column.labels}
|
||||
theme={darkMode ? 'monokai' : 'default'}
|
||||
mode="javascript"
|
||||
lineNumbers={false}
|
||||
placeholder={'{{["one", "two", "three"]}}'}
|
||||
onChange={(value) => onColumnItemChange(index, 'labels', value)}
|
||||
componentName={getPopoverFieldSource(column.columnType, 'labels')}
|
||||
popOverCallback={(showing) => {
|
||||
setColumnPopoverRootCloseBlocker('labels', showing);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{column.columnType === 'link' && (
|
||||
<>
|
||||
<div className="field mb-2 px-3">
|
||||
<label className="form-label">Display text</label>
|
||||
<CodeHinter
|
||||
currentState={currentState}
|
||||
initialValue={column?.displayText}
|
||||
theme={darkMode ? 'monokai' : 'default'}
|
||||
mode="javascript"
|
||||
lineNumbers={false}
|
||||
placeholder={'Display text'}
|
||||
onChange={(value) => onColumnItemChange(index, 'displayText', value)}
|
||||
componentName={getPopoverFieldSource(column.columnType, 'displayText')}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="border mx-3" style={{ borderRadius: '6px', overflow: 'hidden' }}>
|
||||
<div style={{ background: 'var(--surfaces-surface-02)', padding: '8px 12px' }}>
|
||||
<ProgramaticallyHandleProperties
|
||||
label="Link target"
|
||||
currentState={currentState}
|
||||
index={index}
|
||||
darkMode={darkMode}
|
||||
callbackFunction={onColumnItemChange}
|
||||
property="linkTarget"
|
||||
props={column}
|
||||
component={component}
|
||||
paramMeta={{
|
||||
type: 'toggle',
|
||||
displayName: 'Open in new tab',
|
||||
}}
|
||||
paramType="properties"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
{column.columnType === 'number' && (
|
||||
<div className="field mb-2 px-3">
|
||||
<label className="form-label">{t('widget.Table.decimalPlaces', 'Decimal Places')}</label>
|
||||
<CodeHinter
|
||||
currentState={currentState}
|
||||
initialValue={column?.decimalPlaces}
|
||||
theme={darkMode ? 'monokai' : 'default'}
|
||||
mode="javascript"
|
||||
lineNumbers={false}
|
||||
placeholder={'{{2}}'}
|
||||
onChange={(value) => onColumnItemChange(index, 'decimalPlaces', value)}
|
||||
componentName={getPopoverFieldSource(column.columnType, 'decimalPlaces')}
|
||||
popOverCallback={(showing) => {
|
||||
setColumnPopoverRootCloseBlocker('decimalPlaces', showing);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
{!['image', 'link'].includes(column.columnType) && (
|
||||
<div className="border mx-3" style={{ borderRadius: '6px', overflow: 'hidden' }}>
|
||||
<div style={{ background: 'var(--surfaces-surface-02)', padding: '8px 12px' }}>
|
||||
<ProgramaticallyHandleProperties
|
||||
label="make editable"
|
||||
currentState={currentState}
|
||||
index={index}
|
||||
darkMode={darkMode}
|
||||
callbackFunction={onColumnItemChange}
|
||||
property="isEditable"
|
||||
props={column}
|
||||
component={component}
|
||||
paramMeta={{ type: 'toggle', displayName: 'Make editable' }}
|
||||
paramType="properties"
|
||||
/>
|
||||
</div>
|
||||
{resolveReferences(column?.isEditable, currentState) && (
|
||||
<ValidationProperties
|
||||
column={column}
|
||||
index={index}
|
||||
darkMode={darkMode}
|
||||
currentState={currentState}
|
||||
onColumnItemChange={onColumnItemChange}
|
||||
getPopoverFieldSource={getPopoverFieldSource}
|
||||
setColumnPopoverRootCloseBlocker={setColumnPopoverRootCloseBlocker}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
<div className="border mx-3" style={{ borderRadius: '6px', overflow: 'hidden', marginTop: '-8px' }}>
|
||||
<div style={{ background: 'var(--surfaces-surface-02)', padding: '8px 12px' }}>
|
||||
<ProgramaticallyHandleProperties
|
||||
label="Visibility"
|
||||
currentState={currentState}
|
||||
index={index}
|
||||
darkMode={darkMode}
|
||||
callbackFunction={onColumnItemChange}
|
||||
property="columnVisibility"
|
||||
props={column}
|
||||
component={component}
|
||||
paramMeta={{ type: 'toggle', displayName: 'Visibility' }}
|
||||
paramType="properties"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{['select', 'newMultiSelect', 'datepicker'].includes(column.columnType) && <hr className="mx-0 my-2" />}
|
||||
{column.columnType === 'datepicker' && (
|
||||
<div className="field" style={{ marginTop: '-24px' }}>
|
||||
<DatepickerProperties
|
||||
column={column}
|
||||
index={index}
|
||||
darkMode={darkMode}
|
||||
currentState={currentState}
|
||||
onColumnItemChange={onColumnItemChange}
|
||||
component={component}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
{['select', 'newMultiSelect'].includes(column.columnType) && (
|
||||
<OptionsList
|
||||
column={column}
|
||||
props={props}
|
||||
index={index}
|
||||
darkMode={darkMode}
|
||||
currentState={currentState}
|
||||
getPopoverFieldSource={getPopoverFieldSource}
|
||||
setColumnPopoverRootCloseBlocker={setColumnPopoverRootCloseBlocker}
|
||||
component={component}
|
||||
onColumnItemChange={onColumnItemChange}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
const DropdownIndicator = (props) => {
|
||||
return (
|
||||
<div {...props}>
|
||||
{/* Your custom SVG */}
|
||||
{props.selectProps.menuIsOpen ? (
|
||||
<SolidIcon name="arrowUpTriangle" width="16" height="16" fill={'#6A727C'} />
|
||||
) : (
|
||||
<SolidIcon name="arrowDownTriangle" width="16" height="16" fill={'#6A727C'} />
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
|
@ -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 (
|
||||
<>
|
||||
<div className="field d-flex custom-gap-12 align-items-center align-self-stretch justify-content-between px-3">
|
||||
<label className="d-flex align-items-center" style={{ flex: '1 1 0' }}>
|
||||
{column.columnType !== 'boolean' && column.columnType !== 'image'
|
||||
? t('widget.Table.textAlignment', 'Text Alignment')
|
||||
: 'Alignment'}
|
||||
</label>
|
||||
<ToggleGroup
|
||||
onValueChange={(_value) => onColumnItemChange(index, 'horizontalAlignment', _value)}
|
||||
defaultValue={column?.horizontalAlignment || 'left'}
|
||||
style={{ width: '58%' }}
|
||||
>
|
||||
<ToggleGroupItem value="left">
|
||||
<AlignLeft width={14} />
|
||||
</ToggleGroupItem>
|
||||
<ToggleGroupItem value="center">
|
||||
<AlignCenter width={14} />
|
||||
</ToggleGroupItem>
|
||||
<ToggleGroupItem value="right">
|
||||
<AlignRight width={14} />
|
||||
</ToggleGroupItem>
|
||||
</ToggleGroup>
|
||||
</div>
|
||||
{column.columnType === 'toggle' && (
|
||||
<div>
|
||||
<div className="field px-3">
|
||||
<Color
|
||||
param={{ name: 'Active color' }}
|
||||
paramType="properties"
|
||||
componentMeta={{ properties: { color: { displayName: 'Active color' } } }}
|
||||
definition={{ value: column.activeColor || '#3c92dc' }}
|
||||
onChange={(name, value, color) => onColumnItemChange(index, 'activeColor', color)}
|
||||
shouldFlexDirectionBeRow={true}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{column.columnType === 'image' && (
|
||||
<>
|
||||
<div data-cy={`input-and-label-border-radius`} className="field px-3">
|
||||
<label className="form-label">{t('widget.Table.borderRadius', 'Border radius')}</label>
|
||||
<CodeHinter
|
||||
currentState={currentState}
|
||||
initialValue={column.borderRadius}
|
||||
theme={darkMode ? 'monokai' : 'default'}
|
||||
mode="javascript"
|
||||
lineNumbers={false}
|
||||
placeholder={''}
|
||||
onChange={(value) => onColumnItemChange(index, 'borderRadius', value)}
|
||||
componentName={getPopoverFieldSource(column.columnType, 'borderRadius')}
|
||||
/>
|
||||
</div>
|
||||
<div data-cy={`input-and-label-object-fit`} className="field px-3">
|
||||
<label className="form-label">{t('widget.Table.imageFit', 'Image fit')}</label>
|
||||
<Select
|
||||
className={'select-search'}
|
||||
meta={{
|
||||
options: [
|
||||
{ label: 'Cover', value: 'cover' },
|
||||
{ label: 'Contain', value: 'contain' },
|
||||
{ label: 'Fill', value: 'fill' },
|
||||
],
|
||||
}}
|
||||
value={column.objectFit}
|
||||
search={true}
|
||||
closeOnSelect={true}
|
||||
onChange={(value) => {
|
||||
onColumnItemChange(index, 'objectFit', value);
|
||||
}}
|
||||
fuzzySearch
|
||||
placeholder={t('Select') + '...'}
|
||||
width={'100%'}
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
{column.columnType === 'boolean' && (
|
||||
<div className="d-flex flex-column custom-gap-16">
|
||||
<div className="field px-3">
|
||||
<Color
|
||||
param={{ name: 'Checked' }}
|
||||
paramType="properties"
|
||||
componentMeta={{ properties: { color: { displayName: 'Checked' } } }}
|
||||
definition={{ value: column?.toggleOnBg ? column.toggleOnBg : darkMode ? '#849DFF' : '#3A5CCC' }}
|
||||
onChange={(name, value, color) => onColumnItemChange(index, 'toggleOnBg', color)}
|
||||
shouldFlexDirectionBeRow={true}
|
||||
/>
|
||||
</div>
|
||||
<div className="field px-3">
|
||||
<Color
|
||||
param={{ name: 'Unchecked' }}
|
||||
paramType="properties"
|
||||
componentMeta={{ properties: { color: { displayName: 'Unchecked' } } }}
|
||||
definition={{ value: column?.toggleOffBg ? column.toggleOffBg : darkMode ? '#3A3F42' : '#D7DBDF' }}
|
||||
onChange={(name, value, color) => onColumnItemChange(index, 'toggleOffBg', color)}
|
||||
shouldFlexDirectionBeRow={true}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{['string', 'default', undefined, 'number', 'boolean', 'select', 'text', 'newMultiSelect', 'datepicker'].includes(
|
||||
column.columnType
|
||||
) && (
|
||||
<>
|
||||
{column.columnType !== 'boolean' && (
|
||||
<div data-cy={`input-and-label-text-color`} className="field px-3">
|
||||
<ProgramaticallyHandleProperties
|
||||
label="Text color"
|
||||
currentState={currentState}
|
||||
index={index}
|
||||
darkMode={darkMode}
|
||||
callbackFunction={onColumnItemChange}
|
||||
property="textColor"
|
||||
props={column}
|
||||
component={component}
|
||||
paramMeta={{ type: 'color', displayName: 'Text color' }}
|
||||
paramType="properties"
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
<div className="field px-3" data-cy={`input-and-label-cell-background-color`}>
|
||||
<ProgramaticallyHandleProperties
|
||||
label="Cell color"
|
||||
currentState={currentState}
|
||||
index={index}
|
||||
darkMode={darkMode}
|
||||
callbackFunction={onColumnItemChange}
|
||||
property="cellBackgroundColor"
|
||||
props={column}
|
||||
component={component}
|
||||
paramMeta={{ type: 'color', displayName: 'Cell color' }}
|
||||
paramType="properties"
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
|
||||
{column.columnType === 'link' && (
|
||||
<>
|
||||
<div data-cy={`input-and-label-text-color`} className="field px-3">
|
||||
<ProgramaticallyHandleProperties
|
||||
label="Text color"
|
||||
currentState={currentState}
|
||||
index={index}
|
||||
darkMode={darkMode}
|
||||
callbackFunction={onColumnItemChange}
|
||||
property="linkColor"
|
||||
props={column}
|
||||
component={component}
|
||||
paramMeta={{ type: 'color', displayName: 'Text color' }}
|
||||
paramType="properties"
|
||||
/>
|
||||
</div>
|
||||
<div className="field px-3" data-cy={`input-and-label-cell-background-color`}>
|
||||
<ProgramaticallyHandleProperties
|
||||
label="Underline color"
|
||||
currentState={currentState}
|
||||
index={index}
|
||||
darkMode={darkMode}
|
||||
callbackFunction={onColumnItemChange}
|
||||
property="underlineColor"
|
||||
props={column}
|
||||
component={component}
|
||||
paramMeta={{ type: 'color', displayName: 'Underline color' }}
|
||||
paramType="properties"
|
||||
/>
|
||||
</div>
|
||||
<div className="d-flex px-3 flex-column custom-gap-16">
|
||||
<div
|
||||
data-cy={`input-overflow`}
|
||||
className="field d-flex custom-gap-12 align-items-center align-self-stretch"
|
||||
>
|
||||
<label data-cy={`label-overflow`} className="d-flex align-items-center" style={{ flex: '1 1 0' }}>
|
||||
Show underline
|
||||
</label>
|
||||
<ToggleGroup
|
||||
onValueChange={(_value) => onColumnItemChange(index, 'underline', _value)}
|
||||
defaultValue={column.underline || 'hover'}
|
||||
style={{ flex: '1 1 0' }}
|
||||
>
|
||||
<ToggleGroupItem value="hover">Hover</ToggleGroupItem>
|
||||
<ToggleGroupItem value="always">Always</ToggleGroupItem>
|
||||
</ToggleGroup>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
|
@ -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 (
|
||||
<div
|
||||
data-cy={validation.dataCy}
|
||||
className="field flex-fill inspector-validation-date-picker"
|
||||
key={validation.property}
|
||||
>
|
||||
<label className="form-label">{t(`widget.Table.${validation.property}`, validation.label)}</label>
|
||||
<ReactDatePicker
|
||||
selected={getDate(column?.[validation.property], 'MM/DD/YYYY')}
|
||||
onChange={(date) => onColumnItemChange(index, validation.property, moment(date).format('MM/DD/YYYY'))}
|
||||
showTimeSelectOnly={validation.showOnlyTime}
|
||||
placeholderText={validation?.placeholder ?? ''}
|
||||
renderCustomHeader={(headerProps) => <CustomDatePickerHeader {...headerProps} />}
|
||||
popperClassName={darkMode && 'theme-dark dark-theme'}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
case 'timepicker':
|
||||
return (
|
||||
<div
|
||||
data-cy={validation.dataCy}
|
||||
className="field flex-fill inspector-validation-date-picker"
|
||||
key={validation.property}
|
||||
>
|
||||
<label className="form-label">{t(`widget.Table.${validation.property}`, validation.label)}</label>
|
||||
<Timepicker
|
||||
selected={getDate(column?.[validation.property], 'HH:mm')}
|
||||
onChange={(date) => onColumnItemChange(index, validation.property, moment(date).format('HH:mm'))}
|
||||
placeholderText={validation?.placeholder ?? ''}
|
||||
timeFormat={'HH:mm'}
|
||||
darkMode={darkMode}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
default:
|
||||
return (
|
||||
<div data-cy={validation.dataCy} className="field flex-fill" key={validation.property}>
|
||||
<label className="form-label">{t(`widget.Table.${validation.property}`, validation.label)}</label>
|
||||
<CodeHinter
|
||||
currentState={currentState}
|
||||
initialValue={column?.[validation.property]}
|
||||
theme={darkMode ? 'monokai' : 'default'}
|
||||
mode="javascript"
|
||||
lineNumbers={false}
|
||||
placeholder={validation?.placeholder ?? ''}
|
||||
onChange={(value) => onColumnItemChange(index, validation.property, value)}
|
||||
componentName={getPopoverFieldSource(column.columnType, validation.property)}
|
||||
popOverCallback={(showing) => {
|
||||
setColumnPopoverRootCloseBlocker(validation.property, showing);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="optional-properties-when-editable-true">
|
||||
<div className="d-flex flex-column custom-gap-8">
|
||||
{validationsList.map((validation) => {
|
||||
if (Array.isArray(validation)) {
|
||||
return (
|
||||
<div className="d-flex align-item-start align-self-stretch custom-gap-8" key={validation.property}>
|
||||
{validation.map((validation) => {
|
||||
{
|
||||
return renderAsPerFieldType(validation);
|
||||
}
|
||||
})}
|
||||
</div>
|
||||
);
|
||||
} else {
|
||||
return renderAsPerFieldType(validation);
|
||||
}
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
|
@ -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 (
|
||||
<div className={`mb-2 field ${options.className}`} onClick={(e) => e.stopPropagation()}>
|
||||
<div className={`field ${options.className}`} onClick={(e) => e.stopPropagation()}>
|
||||
<CodeHinter
|
||||
enablePreview={true}
|
||||
initialValue={initialValue}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,346 @@
|
|||
import React from 'react';
|
||||
import Accordion from '@/_ui/Accordion';
|
||||
import AddNewButton from '@/ToolJetUI/Buttons/AddNewButton/AddNewButton';
|
||||
import List from '@/ToolJetUI/List/List';
|
||||
import { DragDropContext, Droppable, Draggable } from 'react-beautiful-dnd';
|
||||
import NoListItem from '../NoListItem';
|
||||
import OverlayTrigger from 'react-bootstrap/OverlayTrigger';
|
||||
import Popover from 'react-bootstrap/Popover';
|
||||
import { CodeHinter } from '@/Editor/CodeBuilder/CodeHinter';
|
||||
import { ProgramaticallyHandleProperties } from '../ProgramaticallyHandleProperties';
|
||||
import { resolveReferences } from '@/_helpers/utils';
|
||||
import { unset } from 'lodash';
|
||||
export const OptionsList = ({
|
||||
column,
|
||||
props,
|
||||
index,
|
||||
darkMode,
|
||||
currentState,
|
||||
getPopoverFieldSource,
|
||||
setColumnPopoverRootCloseBlocker,
|
||||
onColumnItemChange,
|
||||
component,
|
||||
}) => {
|
||||
const items = [];
|
||||
|
||||
const recordOptions = (startIndex, endIndex) => {
|
||||
const columns = props.component.component.definition.properties.columns;
|
||||
const column = columns.value[index];
|
||||
const options = column.options;
|
||||
const [removed] = options.splice(startIndex, 1);
|
||||
options.splice(endIndex, 0, removed);
|
||||
column.options = options;
|
||||
const newColumns = columns.value;
|
||||
newColumns[index] = column;
|
||||
props.paramUpdated({ name: 'columns' }, 'value', newColumns, 'properties', true);
|
||||
};
|
||||
|
||||
const onDragEnd = async ({ source, destination }) => {
|
||||
if (!destination || source?.index === destination?.index) {
|
||||
return;
|
||||
}
|
||||
await recordOptions(source.index, destination.index);
|
||||
};
|
||||
|
||||
const getItemStyle = (draggableStyle) => {
|
||||
return {
|
||||
...draggableStyle,
|
||||
userSelect: 'none',
|
||||
position: 'static',
|
||||
};
|
||||
};
|
||||
|
||||
const createNewOption = () => {
|
||||
const columns = props.component.component.definition.properties.columns;
|
||||
const column = columns.value[index];
|
||||
const options = column.options || [];
|
||||
options.push({ label: 'one', value: '1' });
|
||||
column.options = options;
|
||||
const newColumns = columns.value;
|
||||
newColumns[index] = column;
|
||||
props.paramUpdated({ name: 'columns' }, 'value', newColumns, 'properties', true);
|
||||
};
|
||||
|
||||
const deleteOption = (option, optionIndex) => {
|
||||
const columns = props.component.component.definition.properties.columns;
|
||||
const column = columns.value[index];
|
||||
const options = column.options;
|
||||
const deletedOption = options.splice(optionIndex, 1);
|
||||
column.options = options;
|
||||
const defaultOptionsList = column.defaultOptionsList || [];
|
||||
const deletedOptionIndexInDefaultOptionsList = defaultOptionsList?.findIndex((defaultOption, index) => {
|
||||
if (
|
||||
defaultOption.value === deletedOption.value &&
|
||||
defaultOption.label === deletedOption.label &&
|
||||
defaultOption.makeDefaultOption === deletedOption.makeDefaultOption
|
||||
) {
|
||||
return index;
|
||||
} else {
|
||||
return -1;
|
||||
}
|
||||
});
|
||||
if (deletedOptionIndexInDefaultOptionsList !== -1) {
|
||||
defaultOptionsList.splice(deletedOptionIndexInDefaultOptionsList, 1);
|
||||
column.defaultOptionsList = defaultOptionsList;
|
||||
}
|
||||
const newColumns = columns.value;
|
||||
newColumns[index] = column;
|
||||
props.paramUpdated({ name: 'columns' }, 'value', newColumns, 'properties', true);
|
||||
};
|
||||
|
||||
const selectPopover = (option, optionIndex) => {
|
||||
const handleSelectOption = (option, optionIndex, value, index, optionItemChanged) => {
|
||||
const columns = props.component.component.definition.properties.columns;
|
||||
const column = columns.value[index];
|
||||
const options = column.options;
|
||||
options[optionIndex][optionItemChanged] = value;
|
||||
column.options = options;
|
||||
const newColumns = columns.value;
|
||||
newColumns[index] = column;
|
||||
props.paramUpdated({ name: 'columns' }, 'value', newColumns, 'properties', true);
|
||||
};
|
||||
|
||||
const handleDefaultOptionSelection = (optionIndex, property, value) => {
|
||||
const columns = props.component.component.definition.properties.columns;
|
||||
const column = columns.value[index];
|
||||
const options = column.options;
|
||||
options[optionIndex][property] = value;
|
||||
column.options = options;
|
||||
const isValueTruthy = !!resolveReferences(value, currentState);
|
||||
|
||||
// This block is responsible for updating list of defaultOptions when makeDefaultOption prop is updated
|
||||
if (property === 'makeDefaultOption') {
|
||||
if (column.columnType === 'select') {
|
||||
// Disable all other default options except the one selected
|
||||
column.options.forEach((option) => unset(option, 'makeDefaultOption'));
|
||||
column.options[optionIndex][property] = value;
|
||||
if (isValueTruthy) {
|
||||
column.defaultOptionsList = [column.options[optionIndex]];
|
||||
} else {
|
||||
column.defaultOptionsList = [];
|
||||
}
|
||||
} else {
|
||||
const defaultOptionsList = column?.defaultOptionsList ?? [];
|
||||
const indexOfOptionInList = defaultOptionsList.findIndex((option) => {
|
||||
if (option.value === options[optionIndex].value && option.label === options[optionIndex].label) {
|
||||
return option;
|
||||
}
|
||||
});
|
||||
if (indexOfOptionInList === -1 && isValueTruthy) {
|
||||
defaultOptionsList.push(options[optionIndex]);
|
||||
}
|
||||
if (indexOfOptionInList !== -1 && !isValueTruthy) {
|
||||
defaultOptionsList.splice(indexOfOptionInList, 1);
|
||||
}
|
||||
column.defaultOptionsList = defaultOptionsList;
|
||||
}
|
||||
}
|
||||
|
||||
const newColumns = columns.value;
|
||||
newColumns[index] = column;
|
||||
|
||||
props.paramUpdated({ name: 'columns' }, 'value', newColumns, 'properties', true);
|
||||
};
|
||||
return (
|
||||
<Popover
|
||||
id="popover-basic"
|
||||
className={`${darkMode && 'dark-theme'}`}
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
style={{ zIndex: 99999, minWidth: 200 }}
|
||||
>
|
||||
<Popover.Body>
|
||||
<div className="field mb-2 tj-app-input" onClick={(e) => e.stopPropagation()}>
|
||||
<label data-cy={`label-action-button-text`} className="form-label">
|
||||
Option label
|
||||
</label>
|
||||
<CodeHinter
|
||||
currentState={currentState}
|
||||
initialValue={option?.label}
|
||||
theme={darkMode ? 'monokai' : 'default'}
|
||||
mode="javascript"
|
||||
lineNumbers={false}
|
||||
placeholder={option?.label}
|
||||
onChange={(value) => handleSelectOption(option, optionIndex, value, index, 'label')}
|
||||
componentName={getPopoverFieldSource(column.columnType, 'options::label')}
|
||||
popOverCallback={(showing) => {
|
||||
setColumnPopoverRootCloseBlocker('options::label', showing);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<div className="field mb-2 tj-app-input" onClick={(e) => e.stopPropagation()}>
|
||||
<label data-cy={`label-action-button-text`} className="form-label">
|
||||
Option value
|
||||
</label>
|
||||
<CodeHinter
|
||||
currentState={currentState}
|
||||
initialValue={option.value}
|
||||
theme={darkMode ? 'monokai' : 'default'}
|
||||
mode="javascript"
|
||||
lineNumbers={false}
|
||||
placeholder={option.value}
|
||||
onChange={(value) => handleSelectOption(option, optionIndex, value, index, 'value')}
|
||||
componentName={getPopoverFieldSource(column.columnType, 'options::value')}
|
||||
popOverCallback={(showing) => {
|
||||
setColumnPopoverRootCloseBlocker('options::value', showing);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<ProgramaticallyHandleProperties
|
||||
label="Make this option as default"
|
||||
currentState={currentState}
|
||||
index={optionIndex}
|
||||
darkMode={darkMode}
|
||||
callbackFunction={handleDefaultOptionSelection}
|
||||
property="makeDefaultOption"
|
||||
props={column.options}
|
||||
component={component}
|
||||
paramMeta={{
|
||||
type: 'toggle',
|
||||
displayName: 'Make this option as default',
|
||||
}}
|
||||
paramType="properties"
|
||||
/>
|
||||
</Popover.Body>
|
||||
</Popover>
|
||||
);
|
||||
};
|
||||
|
||||
const defaultOptionsValues = () => {
|
||||
return {
|
||||
options: [
|
||||
{ label: 'Reading', value: 'Reading' },
|
||||
{ label: 'Traveling', value: 'Traveling' },
|
||||
{ label: 'Photography', value: 'Photography' },
|
||||
{ label: 'Music', value: 'Music' },
|
||||
],
|
||||
};
|
||||
};
|
||||
|
||||
items.push({
|
||||
title: 'Options',
|
||||
children: (
|
||||
<div className="d-flex custom-gap-7 flex-column">
|
||||
<ProgramaticallyHandleProperties
|
||||
label="Dynamic option"
|
||||
currentState={currentState}
|
||||
index={index}
|
||||
darkMode={darkMode}
|
||||
callbackFunction={onColumnItemChange}
|
||||
property="useDynamicOptions"
|
||||
props={column}
|
||||
component={component}
|
||||
paramMeta={{
|
||||
type: 'toggle',
|
||||
displayName: 'Dynamic option',
|
||||
}}
|
||||
paramType="properties"
|
||||
/>
|
||||
{resolveReferences(column?.useDynamicOptions, currentState) ? (
|
||||
<div className="d-flex custom-gap-7 flex-column">
|
||||
<CodeHinter
|
||||
currentState={currentState}
|
||||
initialValue={column?.dynamicOptions}
|
||||
theme={darkMode ? 'monokai' : 'default'}
|
||||
mode="javascript"
|
||||
lineNumbers={false}
|
||||
onChange={(value) => onColumnItemChange(index, 'dynamicOptions', value)}
|
||||
componentName={getPopoverFieldSource(column.columnType, 'dynamicOptions')}
|
||||
popOverCallback={(showing) => {
|
||||
setColumnPopoverRootCloseBlocker('dynamicOptions', showing);
|
||||
}}
|
||||
placeholder={`{{[{ label: 'Reading', value: 'Reading' }, { label: 'Traveling', value: 'Traveling' }, { label: 'Photography', value: 'Photography' }, { label: 'Music', value: 'Music' }]}}`}
|
||||
/>
|
||||
<ProgramaticallyHandleProperties
|
||||
label="Options loading state"
|
||||
currentState={currentState}
|
||||
index={index}
|
||||
darkMode={darkMode}
|
||||
callbackFunction={onColumnItemChange}
|
||||
property="optionsLoadingState"
|
||||
props={column}
|
||||
component={component}
|
||||
paramMeta={{
|
||||
type: 'toggle',
|
||||
displayName: 'Options loading state',
|
||||
}}
|
||||
paramType="properties"
|
||||
/>
|
||||
</div>
|
||||
) : (
|
||||
<List>
|
||||
<DragDropContext
|
||||
onDragEnd={(result) => {
|
||||
onDragEnd(result);
|
||||
}}
|
||||
>
|
||||
<Droppable droppableId="droppable">
|
||||
{({ innerRef, droppableProps, placeholder }) => {
|
||||
const columnHasOptions = column.hasOwnProperty('options');
|
||||
if (!columnHasOptions) {
|
||||
const defaultOptions = defaultOptionsValues(columnHasOptions);
|
||||
Object.assign(column, defaultOptions);
|
||||
}
|
||||
return (
|
||||
<div className="w-100" {...droppableProps} ref={innerRef}>
|
||||
{column?.options?.map((option, optionIndex) => {
|
||||
const resolvedItemName = option.label;
|
||||
return (
|
||||
<Draggable key={option.label} draggableId={option.label} index={optionIndex}>
|
||||
{(provided, snapshot) => {
|
||||
return (
|
||||
<div
|
||||
key={optionIndex}
|
||||
ref={provided.innerRef}
|
||||
{...provided.draggableProps}
|
||||
{...provided.dragHandleProps}
|
||||
style={{ ...getItemStyle(provided.draggableProps.style) }}
|
||||
>
|
||||
<OverlayTrigger
|
||||
trigger="click"
|
||||
placement="top"
|
||||
rootClose={true}
|
||||
overlay={selectPopover(option, optionIndex)}
|
||||
>
|
||||
<div key={resolvedItemName}>
|
||||
<List.Item
|
||||
isDraggable={true}
|
||||
primaryText={resolvedItemName}
|
||||
data-cy={`column-${resolvedItemName}`}
|
||||
enableActionsMenu={false}
|
||||
onMenuOptionClick={(listItem, menuOptionLabel) => {
|
||||
if (menuOptionLabel === 'Delete') deleteOption(option, optionIndex);
|
||||
}}
|
||||
darkMode={darkMode}
|
||||
deleteIconOutsideMenu={true}
|
||||
/>
|
||||
</div>
|
||||
</OverlayTrigger>
|
||||
</div>
|
||||
);
|
||||
}}
|
||||
</Draggable>
|
||||
);
|
||||
})}
|
||||
{placeholder}
|
||||
</div>
|
||||
);
|
||||
}}
|
||||
</Droppable>
|
||||
</DragDropContext>
|
||||
<div>
|
||||
{column?.options?.length === 0 && <NoListItem text={'There are no columns'} dataCy={`-columns`} />}
|
||||
<div>
|
||||
<AddNewButton dataCy={`button-add-column`} onClick={() => createNewOption()}>
|
||||
{/* {this.props.t('widget.Table.addNewColumn', ' Add new column')} */}
|
||||
Add new option
|
||||
</AddNewButton>
|
||||
</div>
|
||||
</div>
|
||||
</List>
|
||||
)}
|
||||
</div>
|
||||
),
|
||||
});
|
||||
|
||||
return <Accordion items={items} className="table-select-column-accordian" />;
|
||||
};
|
||||
File diff suppressed because it is too large
Load diff
|
|
@ -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 (
|
||||
<div className="field mb-3">
|
||||
<ToolTip label={displayName} meta={paramMeta} />
|
||||
<div
|
||||
className={`field ${shouldFlexDirectionBeRow && 'd-flex custom-gap-12 align-items-center align-self-stretch'}`}
|
||||
>
|
||||
<ToolTip label={displayName} meta={paramMeta} labelClass={shouldFlexDirectionBeRow && 'flex-fill'} />
|
||||
|
||||
{showPicker && (
|
||||
<div>
|
||||
|
|
@ -70,7 +80,7 @@ export const Color = ({ param, definition, onChange, paramType, componentMeta, c
|
|||
boxShadow: `0px 1px 2px 0px rgba(16, 24, 40, 0.05)`,
|
||||
}}
|
||||
></div>
|
||||
<div style={{ height: '20px' }} className="col">
|
||||
<div style={{ height: '20px', flex: '1 1 0' }} className="col">
|
||||
{definition.value}
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -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 = (
|
||||
<div style={{ marginBottom: '6rem' }} className={`${isVersionReleased && 'disabled'}`}>
|
||||
<div
|
||||
className={
|
||||
component.component.component !== 'TextInput' &&
|
||||
component.component.component !== 'PasswordInput' &&
|
||||
component.component.component !== 'NumberInput' &&
|
||||
component.component.component !== 'Text' &&
|
||||
'p-3'
|
||||
}
|
||||
>
|
||||
<div className={!isRevampedComponent && 'p-3'}>
|
||||
<Inspector.RenderStyleOptions
|
||||
componentMeta={componentMeta}
|
||||
component={component}
|
||||
|
|
@ -330,7 +340,7 @@ export const Inspector = ({
|
|||
allComponents={allComponents}
|
||||
/>
|
||||
</div>
|
||||
{!shouldAddBoxShadow.includes(component.component.component) && buildGeneralStyle()}
|
||||
{!isRevampedComponent && buildGeneralStyle()}
|
||||
</div>
|
||||
);
|
||||
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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 })]);
|
||||
|
|
|
|||
|
|
@ -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: () => <Icon style={{ height: 16, width: 16, marginRight: 12 }} /> };
|
||||
const source = allDs.find((ds) => ds.kind === query.kind);
|
||||
return { iconName: query.name, jsx: () => <DataSourceIcon source={source} height={16} /> };
|
||||
});
|
||||
|
||||
const componentIcons = Object.entries(currentState['components']).map(([key, value]) => {
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@ import useDebugger from './SidebarDebugger/useDebugger';
|
|||
import { GlobalSettings } from '../Header/GlobalSettings';
|
||||
import { resolveReferences } from '@/_helpers/utils';
|
||||
import { useCurrentState } from '@/_stores/currentStateStore';
|
||||
import cx from 'classnames';
|
||||
|
||||
export const LeftSidebar = forwardRef((props, ref) => {
|
||||
const router = useRouter();
|
||||
|
|
@ -71,9 +72,10 @@ export const LeftSidebar = forwardRef((props, ref) => {
|
|||
}),
|
||||
shallow
|
||||
);
|
||||
const { showComments } = useEditorStore(
|
||||
const { showComments, appMode } = useEditorStore(
|
||||
(state) => ({
|
||||
showComments: state?.showComments,
|
||||
appMode: state?.appMode,
|
||||
}),
|
||||
shallow
|
||||
);
|
||||
|
|
@ -84,7 +86,6 @@ export const LeftSidebar = forwardRef((props, ref) => {
|
|||
currentPageId,
|
||||
isDebuggerOpen: !!selectedSidebarItem,
|
||||
});
|
||||
|
||||
const sideBarBtnRefs = useRef({});
|
||||
|
||||
useEffect(() => {
|
||||
|
|
@ -232,7 +233,7 @@ export const LeftSidebar = forwardRef((props, ref) => {
|
|||
}, [JSON.stringify(resolveReferences(backgroundFxQuery, currentState))]);
|
||||
|
||||
return (
|
||||
<div className="left-sidebar" data-cy="left-sidebar-inspector">
|
||||
<div className={cx('left-sidebar', { 'dark-theme theme-dark': darkMode })} data-cy="left-sidebar-inspector">
|
||||
<LeftSidebarItem
|
||||
selectedSidebarItem={selectedSidebarItem}
|
||||
onClick={() => handleSelectedSidebarItem('page')}
|
||||
|
|
@ -314,9 +315,9 @@ export const LeftSidebar = forwardRef((props, ref) => {
|
|||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<DarkModeToggle switchDarkMode={switchDarkMode} darkMode={darkMode} tooltipPlacement="right" />
|
||||
</div>
|
||||
{/* <LeftSidebarItem icon='support' className='left-sidebar-item' /> */}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -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({
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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) => {
|
||||
|
|
|
|||
|
|
@ -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 (
|
||||
<>
|
||||
<div className={cx({ 'dark-theme theme-dark': darkMode })}>
|
||||
<div
|
||||
className="query-pane"
|
||||
style={{
|
||||
|
|
@ -212,7 +217,7 @@ const QueryPanel = ({
|
|||
</div>
|
||||
</div>
|
||||
<Tooltip id="tooltip-for-query-panel-footer-btn" className="tooltip" />
|
||||
</>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
/* eslint-disable import/no-named-as-default */
|
||||
import React, { useCallback, useState, useEffect, useRef } from 'react';
|
||||
import React, { useCallback, useState, useEffect, useRef, useContext } from 'react';
|
||||
import { useDrop, useDragLayer } from 'react-dnd';
|
||||
import { ItemTypes } from './ItemTypes';
|
||||
import { DraggableBox } from './DraggableBox';
|
||||
|
|
@ -15,9 +15,10 @@ import { useCurrentState } from '@/_stores/currentStateStore';
|
|||
import { useAppVersionStore } from '@/_stores/appVersionStore';
|
||||
import { shallow } from 'zustand/shallow';
|
||||
import { useMounted } from '@/_hooks/use-mount';
|
||||
import { useEditorStore } from '@/_stores/editorStore';
|
||||
import { useSuperStore } from '@/_stores/superStore';
|
||||
// eslint-disable-next-line import/no-unresolved
|
||||
import { diff } from 'deep-object-diff';
|
||||
import { ModuleContext } from '../_contexts/ModuleContext';
|
||||
import { isPDFSupported } from '@/_stores/utils';
|
||||
|
||||
const NO_OF_GRIDS = 43;
|
||||
|
|
@ -63,6 +64,8 @@ export const SubContainer = ({
|
|||
Listview: 'listItem',
|
||||
});
|
||||
|
||||
const moduleName = useContext(ModuleContext);
|
||||
|
||||
const customResolverVariable = widgetResolvables[parentComponent?.component];
|
||||
const currentState = useCurrentState();
|
||||
const { enableReleasedVersionPopupState, isVersionReleased } = useAppVersionStore(
|
||||
|
|
@ -410,7 +413,9 @@ export const SubContainer = ({
|
|||
let newBoxes = { ...boxes };
|
||||
|
||||
const subContainerHeight = canvasBounds.height - 30;
|
||||
const selectedComponents = useEditorStore.getState().selectedComponents;
|
||||
const selectedComponents = useSuperStore
|
||||
.getState()
|
||||
.modules[moduleName].useEditorStore.getState().selectedComponents;
|
||||
|
||||
if (selectedComponents) {
|
||||
for (const selectedComponent of selectedComponents) {
|
||||
|
|
|
|||
|
|
@ -39,6 +39,8 @@ import { shallow } from 'zustand/shallow';
|
|||
import { useAppDataActions, useAppDataStore } from '@/_stores/appDataStore';
|
||||
import { getPreviewQueryParams, redirectToErrorPage } from '@/_helpers/routes';
|
||||
import { ERROR_TYPES } from '@/_helpers/constants';
|
||||
import { useSuperStore } from '../_stores/superStore';
|
||||
import { ModuleContext, useModuleName } from '../_contexts/ModuleContext';
|
||||
import { useAppVersionStore } from '@/_stores/appVersionStore';
|
||||
import TooljetLogoIcon from '@/_ui/Icon/solidIcons/TooljetLogoIcon';
|
||||
import TooljetLogoText from '@/_ui/Icon/solidIcons/TooljetLogoText';
|
||||
|
|
@ -46,8 +48,11 @@ import ViewerSidebarNavigation from './Viewer/ViewerSidebarNavigation';
|
|||
import MobileHeader from './Viewer/MobileHeader';
|
||||
import DesktopHeader from './Viewer/DesktopHeader';
|
||||
import './Viewer/viewer.scss';
|
||||
import useAppDarkMode from '@/_hooks/useAppDarkMode';
|
||||
|
||||
class ViewerComponent extends React.Component {
|
||||
static contextType = ModuleContext;
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
|
|
@ -55,7 +60,7 @@ class ViewerComponent extends React.Component {
|
|||
|
||||
const slug = this.props.params.slug;
|
||||
this.subscription = null;
|
||||
|
||||
this.props.setEditorOrViewer('viewer');
|
||||
this.state = {
|
||||
slug,
|
||||
deviceWindowWidth,
|
||||
|
|
@ -77,6 +82,7 @@ class ViewerComponent extends React.Component {
|
|||
navigate: this.props.navigate,
|
||||
switchPage: this.switchPage,
|
||||
currentPageId: this.state.currentPageId,
|
||||
moduleName: this.context,
|
||||
};
|
||||
}
|
||||
|
||||
|
|
@ -87,8 +93,16 @@ class ViewerComponent extends React.Component {
|
|||
appDefData.homePageId = data.homePageId;
|
||||
appDefData.showViewerNavigation = data.showViewerNavigation;
|
||||
}
|
||||
useAppVersionStore.getState().actions.updateEditingVersion(data.editing_version);
|
||||
useAppVersionStore.getState().actions.updateReleasedVersionId(data.currentVersionId);
|
||||
const appMode = data.globalSettings?.appMode || data?.editing_version?.globalSettings?.appMode;
|
||||
useSuperStore
|
||||
.getState()
|
||||
.modules[this.context].useAppVersionStore.getState()
|
||||
.actions.updateEditingVersion(data.editing_version);
|
||||
useSuperStore
|
||||
.getState()
|
||||
.modules[this.context].useAppVersionStore.getState()
|
||||
.actions.updateReleasedVersionId(data.currentVersionId);
|
||||
useSuperStore.getState().modules[this.context].useEditorStore.getState().actions.setAppMode(appMode);
|
||||
this.setState({
|
||||
app: data,
|
||||
isLoading: false,
|
||||
|
|
@ -161,7 +175,7 @@ class ViewerComponent extends React.Component {
|
|||
const currentPageId = pages.filter((page) => page.handle === startingPageHandle)[0]?.id ?? homePageId;
|
||||
const currentPage = pages.find((page) => page.id === currentPageId);
|
||||
|
||||
useDataQueriesStore.getState().actions.setDataQueries(dataQueries);
|
||||
useSuperStore.getState().modules[this.context].useDataQueriesStore.getState().actions.setDataQueries(dataQueries);
|
||||
this.props.setCurrentState({
|
||||
queries: queryState,
|
||||
components: {},
|
||||
|
|
@ -183,7 +197,10 @@ class ViewerComponent extends React.Component {
|
|||
...variables,
|
||||
...constants,
|
||||
});
|
||||
useEditorStore.getState().actions.toggleCurrentLayout(this.props?.currentLayout == 'mobile' ? 'mobile' : 'desktop');
|
||||
useSuperStore
|
||||
.getState()
|
||||
.modules[this.context].useEditorStore.getState()
|
||||
.actions.toggleCurrentLayout(this.props?.currentLayout == 'mobile' ? 'mobile' : 'desktop');
|
||||
this.props.updateState({ events: data.events ?? [] });
|
||||
this.setState(
|
||||
{
|
||||
|
|
@ -204,7 +221,7 @@ class ViewerComponent extends React.Component {
|
|||
() => {
|
||||
const components = appDefData?.pages[currentPageId]?.components || {};
|
||||
|
||||
computeComponentState(components).then(async () => {
|
||||
computeComponentState(components, this.context).then(async () => {
|
||||
this.setState({ initialComputationOfStateDone: true, defaultComponentStateComputed: true });
|
||||
this.runQueries(dataQueries);
|
||||
|
||||
|
|
@ -274,7 +291,10 @@ class ViewerComponent extends React.Component {
|
|||
|
||||
fetchAppVersions = async (appId) => {
|
||||
const appVersions = await appEnvironmentService.getVersionsByEnvironment(appId);
|
||||
useAppVersionStore.getState().actions.setAppVersions(appVersions.appVersions);
|
||||
useSuperStore
|
||||
.getState()
|
||||
.modules[this.context].useAppVersionStore.getState()
|
||||
.actions.setAppVersions(appVersions.appVersions);
|
||||
};
|
||||
|
||||
loadApplicationBySlug = (slug, authentication_failed = false) => {
|
||||
|
|
@ -331,7 +351,10 @@ class ViewerComponent extends React.Component {
|
|||
};
|
||||
|
||||
updateQueryConfirmationList = (queryConfirmationList) =>
|
||||
useEditorStore.getState().actions.updateQueryConfirmationList(queryConfirmationList);
|
||||
useSuperStore
|
||||
.getState()
|
||||
.modules[this.context].useEditorStore.getState()
|
||||
.actions.updateQueryConfirmationList(queryConfirmationList);
|
||||
|
||||
setupViewer() {
|
||||
this.subscription = authenticationService.currentSession.subscribe((currentSession) => {
|
||||
|
|
@ -341,7 +364,7 @@ class ViewerComponent extends React.Component {
|
|||
|
||||
if (currentSession?.load_app && slug) {
|
||||
if (currentSession?.group_permissions) {
|
||||
useAppDataStore.getState().actions.setAppId(appId);
|
||||
useSuperStore.getState().modules[this.context].useAppDataStore.getState().actions.setAppId(appId);
|
||||
|
||||
const currentUser = currentSession.current_user;
|
||||
const userVars = {
|
||||
|
|
@ -390,7 +413,10 @@ class ViewerComponent extends React.Component {
|
|||
componentDidMount() {
|
||||
this.setupViewer();
|
||||
const isMobileDevice = this.state.deviceWindowWidth < 600;
|
||||
useEditorStore.getState().actions.toggleCurrentLayout(isMobileDevice ? 'mobile' : 'desktop');
|
||||
useSuperStore
|
||||
.getState()
|
||||
.modules[this.context].useEditorStore.getState()
|
||||
.actions.toggleCurrentLayout(isMobileDevice ? 'mobile' : 'desktop');
|
||||
window.addEventListener('message', this.handleMessage);
|
||||
}
|
||||
|
||||
|
|
@ -400,8 +426,14 @@ class ViewerComponent extends React.Component {
|
|||
this.loadApplicationBySlug(this.props.params.slug);
|
||||
}
|
||||
if (prevProps.currentLayout !== this.props.currentLayout) {
|
||||
if (this.props.id && useAppVersionStore.getState()?.editingVersion?.id) {
|
||||
this.loadApplicationByVersion(this.props.id, useAppVersionStore.getState().editingVersion.id);
|
||||
if (
|
||||
this.props.id &&
|
||||
useSuperStore.getState().modules[this.context].useAppVersionStore.getState()?.editingVersion?.id
|
||||
) {
|
||||
this.loadApplicationByVersion(
|
||||
this.props.id,
|
||||
useSuperStore.getState().modules[this.context].useAppVersionStore.getState().editingVersion.id
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -453,7 +485,10 @@ class ViewerComponent extends React.Component {
|
|||
name: targetPage.name,
|
||||
},
|
||||
async () => {
|
||||
computeComponentState(this.state.appDefinition?.pages[this.state.currentPageId].components).then(async () => {
|
||||
computeComponentState(
|
||||
this.state.appDefinition?.pages[this.state.currentPageId].components,
|
||||
this.context
|
||||
).then(async () => {
|
||||
const currentPageEvents = this.state.events.filter(
|
||||
(event) => event.target === 'page' && event.sourceId === this.state.currentPageId
|
||||
);
|
||||
|
|
@ -572,6 +607,8 @@ class ViewerComponent extends React.Component {
|
|||
dataQueries,
|
||||
canvasWidth,
|
||||
} = this.state;
|
||||
|
||||
const moduleName = this.context;
|
||||
const currentCanvasWidth = canvasWidth;
|
||||
const queryConfirmationList = this.props?.queryConfirmationList ?? [];
|
||||
const canvasMaxWidth = this.computeCanvasMaxWidth();
|
||||
|
|
@ -606,7 +643,12 @@ class ViewerComponent extends React.Component {
|
|||
);
|
||||
} else {
|
||||
return (
|
||||
<div className={`viewer wrapper ${this.props.currentLayout === 'mobile' ? 'mobile-layout' : ''}`}>
|
||||
<div
|
||||
className={cx('viewer wrapper', {
|
||||
'mobile-layout': this.props.currentLayout,
|
||||
'theme-dark dark-theme': this.props.darkMode,
|
||||
})}
|
||||
>
|
||||
<Confirm
|
||||
show={queryConfirmationList.length > 0}
|
||||
message={'Do you want to run this query?'}
|
||||
|
|
@ -616,6 +658,7 @@ class ViewerComponent extends React.Component {
|
|||
onCancel={() => onQueryConfirmOrCancel(this.getViewerRef(), queryConfirmationList[0], false, 'view')}
|
||||
queryConfirmationData={queryConfirmationList[0]}
|
||||
key={queryConfirmationList[0]?.queryName}
|
||||
darkMode={this.props.darkMode}
|
||||
/>
|
||||
<DndProvider backend={HTML5Backend}>
|
||||
{this.props.currentLayout !== 'mobile' && (
|
||||
|
|
@ -658,7 +701,6 @@ class ViewerComponent extends React.Component {
|
|||
<ViewerSidebarNavigation
|
||||
showHeader={!appDefinition.globalSettings?.hideHeader && isAppLoaded}
|
||||
isMobileDevice={this.props.currentLayout === 'mobile'}
|
||||
canvasBackgroundColor={this.computeCanvasBackgroundColor()}
|
||||
pages={pages}
|
||||
currentPageId={this.state?.currentPageId ?? this.state.appDefinition?.homePageId}
|
||||
switchPage={this.switchPage}
|
||||
|
|
@ -712,7 +754,6 @@ class ViewerComponent extends React.Component {
|
|||
appDefinitionChanged={() => false} // function not relevant in viewer
|
||||
snapToGrid={true}
|
||||
appLoading={isLoading}
|
||||
darkMode={this.props.darkMode}
|
||||
onEvent={this.handleEvent}
|
||||
mode="view"
|
||||
deviceWindowWidth={isMobilePreviewMode ? '450px' : deviceWindowWidth}
|
||||
|
|
@ -724,12 +765,15 @@ class ViewerComponent extends React.Component {
|
|||
onComponentClick(this, id, component, 'view');
|
||||
}}
|
||||
onComponentOptionChanged={(component, optionName, value) => {
|
||||
return onComponentOptionChanged(component, optionName, value);
|
||||
return onComponentOptionChanged(this.context, component, optionName, value);
|
||||
}}
|
||||
onComponentOptionsChanged={onComponentOptionsChanged}
|
||||
onComponentOptionsChanged={(...props) =>
|
||||
onComponentOptionsChanged(this.context, ...props)
|
||||
}
|
||||
canvasWidth={this.getCanvasWidth()}
|
||||
dataQueries={dataQueries}
|
||||
currentPageId={this.state.currentPageId}
|
||||
darkMode={this.props.darkMode}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
|
|
@ -739,7 +783,8 @@ class ViewerComponent extends React.Component {
|
|||
className="powered-with-tj"
|
||||
onClick={() => {
|
||||
const url = `https://tooljet.com/?utm_source=powered_by_banner&utm_medium=${
|
||||
useAppDataStore.getState()?.metadata?.instance_id
|
||||
useSuperStore.getState().modules[this.context].useAppDataStore.getState()?.metadata
|
||||
?.instance_id
|
||||
}&utm_campaign=self_hosted`;
|
||||
window.open(url, '_blank');
|
||||
}}
|
||||
|
|
@ -774,6 +819,7 @@ const withStore = (Component) => (props) => {
|
|||
shallow
|
||||
);
|
||||
const { updateState } = useAppDataActions();
|
||||
const { isAppDarkMode } = useAppDarkMode();
|
||||
return (
|
||||
<Component
|
||||
{...props}
|
||||
|
|
@ -782,6 +828,7 @@ const withStore = (Component) => (props) => {
|
|||
currentLayout={currentLayout}
|
||||
updateState={updateState}
|
||||
queryConfirmationList={queryConfirmationList}
|
||||
darkMode={isAppDarkMode}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ import { redirectToDashboard } from '@/_helpers/routes';
|
|||
import classNames from 'classnames';
|
||||
import { useAppVersionStore } from '@/_stores/appVersionStore';
|
||||
import PreviewSettings from './PreviewSettings';
|
||||
import { useEditorStore } from '@/_stores/editorStore';
|
||||
|
||||
const DesktopHeader = ({ showHeader, appName, changeDarkMode, darkMode, setAppDefinitionFromVersion }) => {
|
||||
const { isVersionReleased, editingVersion } = useAppVersionStore(
|
||||
|
|
@ -19,6 +20,13 @@ const DesktopHeader = ({ showHeader, appName, changeDarkMode, darkMode, setAppDe
|
|||
}),
|
||||
shallow
|
||||
);
|
||||
const { showDarkModeToggle } = useEditorStore(
|
||||
(state) => ({
|
||||
showDarkModeToggle: state.appMode === 'auto' || !state.appMode,
|
||||
}),
|
||||
shallow
|
||||
);
|
||||
|
||||
const _renderAppNameAndLogo = () => (
|
||||
<div
|
||||
className={classNames('d-flex', 'align-items-center')}
|
||||
|
|
@ -54,9 +62,11 @@ const DesktopHeader = ({ showHeader, appName, changeDarkMode, darkMode, setAppDe
|
|||
darkMode={darkMode}
|
||||
/>
|
||||
)}
|
||||
<span className="released-version-no-header-dark-mode-icon">
|
||||
<DarkModeToggle switchDarkMode={changeDarkMode} darkMode={darkMode} />
|
||||
</span>
|
||||
{showDarkModeToggle && (
|
||||
<span className="released-version-no-header-dark-mode-icon">
|
||||
<DarkModeToggle switchDarkMode={changeDarkMode} darkMode={darkMode} />
|
||||
</span>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
|
@ -76,9 +86,11 @@ const DesktopHeader = ({ showHeader, appName, changeDarkMode, darkMode, setAppDe
|
|||
darkMode={darkMode}
|
||||
/>
|
||||
)}
|
||||
<div className="d-flex align-items-center">
|
||||
<DarkModeToggle switchDarkMode={changeDarkMode} darkMode={darkMode} />
|
||||
</div>
|
||||
{showDarkModeToggle && (
|
||||
<div className="d-flex align-items-center">
|
||||
<DarkModeToggle switchDarkMode={changeDarkMode} darkMode={darkMode} />
|
||||
</div>
|
||||
)}
|
||||
</Header>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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 (
|
||||
<span
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ import { DarkModeToggle } from '@/_components/DarkModeToggle';
|
|||
import Header from './Header';
|
||||
import Cross from '@/_ui/Icon/solidIcons/Cross';
|
||||
|
||||
const MobileNavigationMenu = ({ pages, switchPage, currentPageId, darkMode, changeDarkMode }) => {
|
||||
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
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<hr className="m-0 mb-3" />
|
||||
<div className="d-flex justify-content-center">
|
||||
<div
|
||||
className={`d-flex align-items-center justify-content-center`}
|
||||
style={{ border: '1px solid var(--slate7)', width: 'calc(100% - 20px)' }}
|
||||
>
|
||||
<DarkModeToggle
|
||||
switchDarkMode={changeDarkMode}
|
||||
darkMode={darkMode}
|
||||
showText={true}
|
||||
tooltipPlacement={'top'}
|
||||
/>
|
||||
{showDarkModeToggle && (
|
||||
<div>
|
||||
<hr className="m-0 mb-3" />
|
||||
<div className="d-flex justify-content-center">
|
||||
<div
|
||||
className={`d-flex align-items-center justify-content-center`}
|
||||
style={{ border: '1px solid var(--slate7)', width: 'calc(100% - 20px)' }}
|
||||
>
|
||||
<DarkModeToggle
|
||||
switchDarkMode={changeDarkMode}
|
||||
darkMode={darkMode}
|
||||
showText={true}
|
||||
tooltipPlacement={'top'}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</MobileMenu>
|
||||
</>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -32,6 +32,7 @@ const PreviewSettings = ({ isMobileLayout, setAppDefinitionFromVersion, showHead
|
|||
onVersionDelete={noop}
|
||||
isEditable={false}
|
||||
isViewer
|
||||
darkMode={darkMode}
|
||||
/>
|
||||
)}
|
||||
</span>
|
||||
|
|
@ -98,12 +99,13 @@ const PreviewSettings = ({ isMobileLayout, setAppDefinitionFromVersion, showHead
|
|||
onVersionDelete={noop}
|
||||
isEditable={false}
|
||||
isViewer
|
||||
darkMode={darkMode}
|
||||
/>
|
||||
)}
|
||||
</span>
|
||||
<div className="d-flex p-2 align-items-center">
|
||||
<span style={{ marginRight: '24px' }}>layout</span>
|
||||
<HeaderActions showToggleLayoutBtn showFullWidth />
|
||||
<HeaderActions showToggleLayoutBtn showFullWidth darkMode={darkMode} />
|
||||
</div>
|
||||
</Offcanvas.Body>
|
||||
)}
|
||||
|
|
|
|||
|
|
@ -123,7 +123,7 @@
|
|||
|
||||
|
||||
.canvas-container::-webkit-scrollbar {
|
||||
width: 0;
|
||||
// width: 0;
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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',
|
||||
|
|
|
|||
|
|
@ -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' },
|
||||
|
|
|
|||
|
|
@ -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 (
|
||||
<Layout switchDarkMode={this.props.switchDarkMode} darkMode={this.props.darkMode}>
|
||||
<div className="wrapper home-page">
|
||||
{showCreateAppModal && (
|
||||
{(showCreateAppModal || showCreateModuleModal) && (
|
||||
<AppModal
|
||||
closeModal={this.closeCreateAppModal}
|
||||
processApp={this.createApp}
|
||||
processApp={(name) => this.createApp(name, showCreateAppModal ? 'front-end' : 'module')}
|
||||
show={this.openCreateAppModal}
|
||||
title={'Create app'}
|
||||
actionButton={'+ Create app'}
|
||||
title={showCreateAppModal ? 'Create app' : 'Create module'}
|
||||
actionButton={showCreateAppModal ? '+ Create app' : '+ Create module'}
|
||||
actionLoadingButton={'Creating'}
|
||||
/>
|
||||
)}
|
||||
|
|
|
|||
|
|
@ -3,11 +3,15 @@ import './list.scss';
|
|||
import ListGroup from 'react-bootstrap/ListGroup';
|
||||
import { OverlayTrigger, Popover } from 'react-bootstrap';
|
||||
import Trash from '@/_ui/Icon/solidIcons/Trash';
|
||||
import SolidIcon from '@/_ui/Icon/SolidIcons';
|
||||
import classNames from 'classnames';
|
||||
import Edit from '@/_ui/Icon/bulkIcons/Edit';
|
||||
import { ButtonSolid } from '@/_ui/AppButton/AppButton';
|
||||
import MoreVertical from '@/_ui/Icon/solidIcons/MoreVertical';
|
||||
import SortableList from '@/_components/SortableList';
|
||||
import { DeprecatedColumnTooltip } from '@/Editor/Inspector/Components/Table/ColumnManager/DeprecatedColumnTypeMsg';
|
||||
import Icons from '@/_ui/Icon/solidIcons/index';
|
||||
|
||||
function List({ children, ...restProps }) {
|
||||
return <ListGroup {...restProps}>{children}</ListGroup>;
|
||||
}
|
||||
|
|
@ -22,11 +26,16 @@ function ListItem({
|
|||
onMenuOptionClick,
|
||||
isEditable,
|
||||
isDraggable,
|
||||
deleteIconOutsideMenu = false,
|
||||
showCopyColumnOption = false,
|
||||
showVisibilityIcon = false,
|
||||
isColumnVisible = true,
|
||||
columnType,
|
||||
isDeprecated,
|
||||
...restProps
|
||||
}) {
|
||||
const [isHovered, setIsHovered] = useState(false);
|
||||
const [showActionsMenu, setShowActionsMenu] = useState(false);
|
||||
|
||||
return (
|
||||
<div>
|
||||
<ListGroup.Item
|
||||
|
|
@ -54,8 +63,21 @@ function ListItem({
|
|||
<Edit width={16} />
|
||||
</span>
|
||||
)}
|
||||
{showVisibilityIcon && !isColumnVisible && (
|
||||
<span style={{ marginLeft: '8px' }}>
|
||||
<SolidIcon name="eyedisable" width={16} />
|
||||
</span>
|
||||
)}
|
||||
|
||||
{isDeprecated && (
|
||||
<DeprecatedColumnTooltip columnType={columnType}>
|
||||
<span className={'list-item-deprecated-column-type'}>
|
||||
<Icons name={'warning'} height={14} width={14} fill="#DB4324" />
|
||||
</span>
|
||||
</DeprecatedColumnTooltip>
|
||||
)}
|
||||
</div>
|
||||
<div className="col-auto">
|
||||
<div className="col-auto d-flex align-items-center custom-gap-4">
|
||||
<OverlayTrigger
|
||||
trigger={'click'}
|
||||
placement={'bottom-end'}
|
||||
|
|
@ -97,12 +119,7 @@ function ListItem({
|
|||
}}
|
||||
>
|
||||
{enableActionsMenu && isHovered && (
|
||||
<ButtonSolid
|
||||
variant="tertiary"
|
||||
size="xs"
|
||||
className={'list-menu-option-btn'}
|
||||
// data-cy={'page-menu'}
|
||||
>
|
||||
<ButtonSolid variant="tertiary" size="xs" className={'list-menu-option-btn'}>
|
||||
<span>
|
||||
<MoreVertical fill={'var(--slate12)'} width={'20'} />
|
||||
</span>
|
||||
|
|
@ -110,6 +127,38 @@ function ListItem({
|
|||
)}
|
||||
</span>
|
||||
</OverlayTrigger>
|
||||
{showCopyColumnOption && isHovered && (
|
||||
<ButtonSolid
|
||||
variant="ghostBlack"
|
||||
size="xs"
|
||||
className={'copy-column-icon'}
|
||||
// data-cy={'page-menu'}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
onMenuOptionClick(primaryText, 'copyColumn');
|
||||
}}
|
||||
>
|
||||
<span className="d-flex">
|
||||
<SolidIcon name="copy" fill={'var(--icons-strong)'} width={16} />
|
||||
</span>
|
||||
</ButtonSolid>
|
||||
)}
|
||||
{deleteIconOutsideMenu && isHovered && (
|
||||
<ButtonSolid
|
||||
variant="danger"
|
||||
size="xs"
|
||||
className={'delete-icon-btn'}
|
||||
// data-cy={'page-menu'}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
onMenuOptionClick(primaryText, 'Delete');
|
||||
}}
|
||||
>
|
||||
<span className="d-flex">
|
||||
<Trash fill={'var(--tomato9)'} width={16} />
|
||||
</span>
|
||||
</ButtonSolid>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</ListGroup.Item>
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
29
frontend/src/ToolJetUI/Timepicker/Timepicker.jsx
Normal file
29
frontend/src/ToolJetUI/Timepicker/Timepicker.jsx
Normal file
|
|
@ -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 (
|
||||
<div className="tj-timepicker">
|
||||
<DatePickerComponent
|
||||
{...props}
|
||||
selected={selected}
|
||||
onChange={onChange}
|
||||
showTimeSelect
|
||||
showTimeSelectOnly
|
||||
timeIntervals={15}
|
||||
timeCaption="Time"
|
||||
dateFormat={timeFormat}
|
||||
maxTime={maxTime}
|
||||
timeFormat={timeFormat}
|
||||
minTime={minTime}
|
||||
timeInputLabel=""
|
||||
popperClassName={cx('tj-timepicker-popper', { 'theme-dark dark-theme': darkMode })}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Timepicker;
|
||||
23
frontend/src/ToolJetUI/Timepicker/timepicker.scss
Normal file
23
frontend/src/ToolJetUI/Timepicker/timepicker.scss
Normal file
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -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({
|
|||
<OverlayTrigger
|
||||
placement={tooltipPlacement}
|
||||
delay={{ show: 250, hide: 400 }}
|
||||
trigger={'hover'}
|
||||
overlay={
|
||||
<Tooltip id="button-tooltip">
|
||||
{darkMode
|
||||
|
|
@ -60,7 +63,7 @@ export const DarkModeToggle = function DarkModeToggle({
|
|||
}
|
||||
>
|
||||
<div
|
||||
className="unstyled-button dark-theme-toggle-btn sidebar-svg-icon left-sidebar-item "
|
||||
className={classnames('unstyled-button dark-theme-toggle-btn sidebar-svg-icon left-sidebar-item')}
|
||||
onClick={toggleDarkMode}
|
||||
>
|
||||
<animated.svg
|
||||
|
|
|
|||
11
frontend/src/_contexts/ModuleContext.jsx
Normal file
11
frontend/src/_contexts/ModuleContext.jsx
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
import { createContext, useContext } from 'react';
|
||||
|
||||
export const ModuleContext = createContext(null);
|
||||
|
||||
export const useModuleName = () => {
|
||||
const moduleName = useContext(ModuleContext);
|
||||
|
||||
if (!moduleName) throw Error('useModuleName can only be used inside a ModuleContext');
|
||||
|
||||
return moduleName;
|
||||
};
|
||||
|
|
@ -34,6 +34,7 @@ import { useAppVersionStore } from '@/_stores/appVersionStore';
|
|||
import { camelizeKeys } from 'humps';
|
||||
import { useAppDataStore } from '@/_stores/appDataStore';
|
||||
import { useEditorStore } from '@/_stores/editorStore';
|
||||
import { useSuperStore } from '@/_stores/superStore';
|
||||
|
||||
const ERROR_TYPES = Object.freeze({
|
||||
ReferenceError: 'ReferenceError',
|
||||
|
|
@ -61,9 +62,9 @@ export function setCurrentStateAsync(_ref, changes) {
|
|||
});
|
||||
}
|
||||
|
||||
export function onComponentOptionsChanged(component, options) {
|
||||
export function onComponentOptionsChanged(moduleName, component, options) {
|
||||
const componentName = component.name;
|
||||
const components = getCurrentState().components;
|
||||
const components = getCurrentState(moduleName).components;
|
||||
let componentData = components[componentName];
|
||||
componentData = componentData || {};
|
||||
|
||||
|
|
@ -71,27 +72,36 @@ export function onComponentOptionsChanged(component, options) {
|
|||
componentData[option[0]] = option[1];
|
||||
}
|
||||
|
||||
useCurrentStateStore.getState().actions.setCurrentState({
|
||||
components: { ...components, [componentName]: componentData },
|
||||
});
|
||||
useSuperStore
|
||||
.getState()
|
||||
.modules[moduleName].useCurrentStateStore.getState()
|
||||
.actions.setCurrentState({
|
||||
components: { ...components, [componentName]: componentData },
|
||||
});
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
export function onComponentOptionChanged(component, option_name, value) {
|
||||
export function onComponentOptionChanged(moduleName, component, option_name, value) {
|
||||
const componentName = component.name;
|
||||
const components = getCurrentState().components;
|
||||
const components = getCurrentState(moduleName).components;
|
||||
let componentData = components[componentName];
|
||||
componentData = componentData || {};
|
||||
componentData[option_name] = value;
|
||||
|
||||
if (option_name !== 'id') {
|
||||
useCurrentStateStore.getState().actions.setCurrentState({
|
||||
components: { ...components, [componentName]: componentData },
|
||||
});
|
||||
useSuperStore
|
||||
.getState()
|
||||
.modules[moduleName].useCurrentStateStore.getState()
|
||||
.actions.setCurrentState({
|
||||
components: { ...components, [componentName]: componentData },
|
||||
});
|
||||
} else if (!componentData?.id) {
|
||||
useCurrentStateStore.getState().actions.setCurrentState({
|
||||
components: { ...components, [componentName]: componentData },
|
||||
});
|
||||
useSuperStore
|
||||
.getState()
|
||||
.modules[moduleName].useCurrentStateStore.getState()
|
||||
.actions.setCurrentState({
|
||||
components: { ...components, [componentName]: componentData },
|
||||
});
|
||||
}
|
||||
|
||||
return Promise.resolve();
|
||||
|
|
@ -141,15 +151,15 @@ const evaluatePythonCode = async (options) => {
|
|||
run: () => actions.runQuery(key),
|
||||
|
||||
getData: () => {
|
||||
return getCurrentState().queries[key].data;
|
||||
return currentState.queries[key].data;
|
||||
},
|
||||
|
||||
getRawData: () => {
|
||||
return getCurrentState().queries[key].rawData;
|
||||
return currentState.queries[key].rawData;
|
||||
},
|
||||
|
||||
getloadingState: () => {
|
||||
return getCurrentState().queries[key].isLoading;
|
||||
return currentState.queries[key].isLoading;
|
||||
},
|
||||
};
|
||||
}
|
||||
|
|
@ -223,7 +233,7 @@ export async function runTransformation(
|
|||
|
||||
let result = [];
|
||||
|
||||
const currentState = getCurrentState() || {};
|
||||
const currentState = getCurrentState(_ref.moduleName) || {};
|
||||
|
||||
if (transformationLanguage === 'python') {
|
||||
result = await runPythonTransformation(currentState, data, transformation, query, mode);
|
||||
|
|
@ -304,13 +314,13 @@ function showModal(_ref, modal, show) {
|
|||
const modalMeta = _ref.appDefinition.pages[_ref.currentPageId].components[modalId]; //! NeedToFix
|
||||
|
||||
const _components = {
|
||||
...getCurrentState().components,
|
||||
...getCurrentState(_ref.moduleName).components,
|
||||
[modalMeta.component.name]: {
|
||||
...getCurrentState().components[modalMeta.component.name],
|
||||
...getCurrentState(_ref.moduleName).components[modalMeta.component.name],
|
||||
show: show,
|
||||
},
|
||||
};
|
||||
useCurrentStateStore.getState().actions.setCurrentState({
|
||||
useSuperStore.getState().modules[_ref.moduleName].useCurrentStateStore.getState().actions.setCurrentState({
|
||||
components: _components,
|
||||
});
|
||||
return Promise.resolve();
|
||||
|
|
@ -350,14 +360,19 @@ export const executeAction = debounce(executeActionWithDebounce);
|
|||
function executeActionWithDebounce(_ref, event, mode, customVariables) {
|
||||
if (event) {
|
||||
if (event.runOnlyIf) {
|
||||
const shouldRun = resolveReferences(event.runOnlyIf, getCurrentState(), undefined, customVariables);
|
||||
const shouldRun = resolveReferences(
|
||||
event.runOnlyIf,
|
||||
getCurrentState(_ref.moduleName),
|
||||
undefined,
|
||||
customVariables
|
||||
);
|
||||
if (!shouldRun) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
switch (event.actionId) {
|
||||
case 'show-alert': {
|
||||
const message = resolveReferences(event.message, getCurrentState(), undefined, customVariables);
|
||||
const message = resolveReferences(event.message, getCurrentState(_ref.moduleName), undefined, customVariables);
|
||||
switch (event.alertType) {
|
||||
case 'success':
|
||||
case 'error':
|
||||
|
|
@ -381,11 +396,15 @@ function executeActionWithDebounce(_ref, event, mode, customVariables) {
|
|||
const resolvedParams = {};
|
||||
if (params) {
|
||||
Object.keys(params).map(
|
||||
(param) => (resolvedParams[param] = resolveReferences(params[param], getCurrentState(), undefined))
|
||||
(param) =>
|
||||
(resolvedParams[param] = resolveReferences(params[param], getCurrentState(_ref.moduleName), undefined))
|
||||
);
|
||||
}
|
||||
const name =
|
||||
useDataQueriesStore.getState().dataQueries.find((query) => query.id === queryId)?.name ?? queryName;
|
||||
useSuperStore
|
||||
.getState()
|
||||
.modules[_ref.moduleName].useDataQueriesStore.getState()
|
||||
.dataQueries.find((query) => query.id === queryId)?.name ?? queryName;
|
||||
return runQuery(_ref, queryId, name, undefined, mode, resolvedParams);
|
||||
}
|
||||
case 'logout': {
|
||||
|
|
@ -393,20 +412,20 @@ function executeActionWithDebounce(_ref, event, mode, customVariables) {
|
|||
}
|
||||
|
||||
case 'open-webpage': {
|
||||
const url = resolveReferences(event.url, getCurrentState(), undefined, customVariables);
|
||||
const url = resolveReferences(event.url, getCurrentState(_ref.moduleName), undefined, customVariables);
|
||||
window.open(url, '_blank');
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
case 'go-to-app': {
|
||||
const slug = resolveReferences(event.slug, getCurrentState(), undefined, customVariables);
|
||||
const slug = resolveReferences(event.slug, getCurrentState(_ref.moduleName), undefined, customVariables);
|
||||
const queryParams = event.queryParams?.reduce(
|
||||
(result, queryParam) => ({
|
||||
...result,
|
||||
...{
|
||||
[resolveReferences(queryParam[0], getCurrentState())]: resolveReferences(
|
||||
[resolveReferences(queryParam[0], getCurrentState(_ref.moduleName))]: resolveReferences(
|
||||
queryParam[1],
|
||||
getCurrentState(),
|
||||
getCurrentState(_ref.moduleName),
|
||||
undefined,
|
||||
customVariables
|
||||
),
|
||||
|
|
@ -440,15 +459,20 @@ function executeActionWithDebounce(_ref, event, mode, customVariables) {
|
|||
return showModal(_ref, event.modal, false);
|
||||
|
||||
case 'copy-to-clipboard': {
|
||||
const contentToCopy = resolveReferences(event.contentToCopy, getCurrentState(), undefined, customVariables);
|
||||
const contentToCopy = resolveReferences(
|
||||
event.contentToCopy,
|
||||
getCurrentState(_ref.moduleName),
|
||||
undefined,
|
||||
customVariables
|
||||
);
|
||||
copyToClipboard(contentToCopy);
|
||||
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
case 'set-localstorage-value': {
|
||||
const key = resolveReferences(event.key, getCurrentState(), undefined, customVariables);
|
||||
const value = resolveReferences(event.value, getCurrentState(), undefined, customVariables);
|
||||
const key = resolveReferences(event.key, getCurrentState(_ref.moduleName), undefined, customVariables);
|
||||
const value = resolveReferences(event.value, getCurrentState(_ref.moduleName), undefined, customVariables);
|
||||
localStorage.setItem(key, value);
|
||||
|
||||
return Promise.resolve();
|
||||
|
|
@ -456,9 +480,11 @@ function executeActionWithDebounce(_ref, event, mode, customVariables) {
|
|||
|
||||
case 'generate-file': {
|
||||
// const fileType = event.fileType;
|
||||
const data = resolveReferences(event.data, getCurrentState(), undefined, customVariables) ?? [];
|
||||
const fileName = resolveReferences(event.fileName, getCurrentState(), undefined, customVariables) ?? 'data.txt';
|
||||
const fileType = resolveReferences(event.fileType, getCurrentState(), undefined, customVariables) ?? 'csv';
|
||||
const data = resolveReferences(event.data, getCurrentState(_ref.moduleName), undefined, customVariables) ?? [];
|
||||
const fileName =
|
||||
resolveReferences(event.fileName, getCurrentState(_ref.moduleName), undefined, customVariables) ?? 'data.txt';
|
||||
const fileType =
|
||||
resolveReferences(event.fileType, getCurrentState(_ref.moduleName), undefined, customVariables) ?? 'csv';
|
||||
const fileData = {
|
||||
csv: generateCSV,
|
||||
plaintext: (plaintext) => plaintext,
|
||||
|
|
@ -469,18 +495,21 @@ function executeActionWithDebounce(_ref, event, mode, customVariables) {
|
|||
}
|
||||
|
||||
case 'set-table-page': {
|
||||
setTablePageIndex(event.table, event.pageIndex);
|
||||
setTablePageIndex(event.table, event.pageIndex, _ref);
|
||||
break;
|
||||
}
|
||||
|
||||
case 'set-custom-variable': {
|
||||
const key = resolveReferences(event.key, getCurrentState(), undefined, customVariables);
|
||||
const value = resolveReferences(event.value, getCurrentState(), undefined, customVariables);
|
||||
const customAppVariables = { ...getCurrentState().variables };
|
||||
const key = resolveReferences(event.key, getCurrentState(_ref.moduleName), undefined, customVariables);
|
||||
const value = resolveReferences(event.value, getCurrentState(_ref.moduleName), undefined, customVariables);
|
||||
const customAppVariables = { ...getCurrentState(_ref.moduleName).variables };
|
||||
customAppVariables[key] = value;
|
||||
return useCurrentStateStore.getState().actions.setCurrentState({
|
||||
variables: customAppVariables,
|
||||
});
|
||||
return useSuperStore
|
||||
.getState()
|
||||
.modules[_ref.moduleName].useCurrentStateStore.getState()
|
||||
.actions.setCurrentState({
|
||||
variables: customAppVariables,
|
||||
});
|
||||
}
|
||||
|
||||
case 'get-custom-variable': {
|
||||
|
|
@ -490,27 +519,33 @@ function executeActionWithDebounce(_ref, event, mode, customVariables) {
|
|||
}
|
||||
|
||||
case 'unset-custom-variable': {
|
||||
const key = resolveReferences(event.key, getCurrentState(), undefined, customVariables);
|
||||
const customAppVariables = { ...getCurrentState().variables };
|
||||
const key = resolveReferences(event.key, getCurrentState(_ref.moduleName), undefined, customVariables);
|
||||
const customAppVariables = { ...getCurrentState(_ref.moduleName).variables };
|
||||
delete customAppVariables[key];
|
||||
return useCurrentStateStore.getState().actions.setCurrentState({
|
||||
variables: customAppVariables,
|
||||
});
|
||||
return useSuperStore
|
||||
.getState()
|
||||
.modules[_ref.moduleName].useCurrentStateStore.getState()
|
||||
.actions.setCurrentState({
|
||||
variables: customAppVariables,
|
||||
});
|
||||
}
|
||||
|
||||
case 'set-page-variable': {
|
||||
const key = resolveReferences(event.key, getCurrentState(), undefined, customVariables);
|
||||
const value = resolveReferences(event.value, getCurrentState(), undefined, customVariables);
|
||||
const key = resolveReferences(event.key, getCurrentState(_ref.moduleName), undefined, customVariables);
|
||||
const value = resolveReferences(event.value, getCurrentState(_ref.moduleName), undefined, customVariables);
|
||||
const customPageVariables = {
|
||||
...getCurrentState().page.variables,
|
||||
...getCurrentState(_ref.moduleName).page.variables,
|
||||
[key]: value,
|
||||
};
|
||||
return useCurrentStateStore.getState().actions.setCurrentState({
|
||||
page: {
|
||||
...getCurrentState().page,
|
||||
variables: customPageVariables,
|
||||
},
|
||||
});
|
||||
return useSuperStore
|
||||
.getState()
|
||||
.modules[_ref.moduleName].useCurrentStateStore.getState()
|
||||
.actions.setCurrentState({
|
||||
page: {
|
||||
...getCurrentState(_ref.moduleName).page,
|
||||
variables: customPageVariables,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
case 'get-page-variable': {
|
||||
|
|
@ -522,18 +557,21 @@ function executeActionWithDebounce(_ref, event, mode, customVariables) {
|
|||
}
|
||||
|
||||
case 'unset-page-variable': {
|
||||
const key = resolveReferences(event.key, getCurrentState(), undefined, customVariables);
|
||||
const customPageVariables = _.omit(getCurrentState().page.variables, key);
|
||||
return useCurrentStateStore.getState().actions.setCurrentState({
|
||||
page: {
|
||||
...getCurrentState().page,
|
||||
variables: customPageVariables,
|
||||
},
|
||||
});
|
||||
const key = resolveReferences(event.key, getCurrentState(_ref.moduleName), undefined, customVariables);
|
||||
const customPageVariables = _.omit(getCurrentState(_ref.moduleName).page.variables, key);
|
||||
return useSuperStore
|
||||
.getState()
|
||||
.modules[_ref.moduleName].useCurrentStateStore.getState()
|
||||
.actions.setCurrentState({
|
||||
page: {
|
||||
...getCurrentState(_ref.moduleName).page,
|
||||
variables: customPageVariables,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
case 'control-component': {
|
||||
let component = Object.values(getCurrentState()?.components ?? {}).filter(
|
||||
let component = Object.values(getCurrentState(_ref.moduleName)?.components ?? {}).filter(
|
||||
(component) => component.id === event.componentId
|
||||
)[0];
|
||||
let action = '';
|
||||
|
|
@ -541,8 +579,10 @@ function executeActionWithDebounce(_ref, event, mode, customVariables) {
|
|||
// check if component id not found then try to find if its available as child widget else continue
|
||||
// with normal flow finding action
|
||||
if (component == undefined) {
|
||||
component = _ref.appDefinition.pages[getCurrentState()?.page?.id].components[event.componentId].component;
|
||||
const parent = Object.values(getCurrentState()?.components ?? {}).find(
|
||||
component =
|
||||
_ref.appDefinition.pages[getCurrentState(_ref.moduleName)?.page?.id].components[event.componentId]
|
||||
.component;
|
||||
const parent = Object.values(getCurrentState(_ref.moduleName)?.components ?? {}).find(
|
||||
(item) => item.id === component.parent
|
||||
);
|
||||
const child = Object.values(parent?.children).find((item) => item.id === event.componentId);
|
||||
|
|
@ -555,7 +595,7 @@ function executeActionWithDebounce(_ref, event, mode, customVariables) {
|
|||
}
|
||||
actionArguments = _.map(event.componentSpecificActionParams, (param) => ({
|
||||
...param,
|
||||
value: resolveReferences(param.value, getCurrentState(), undefined, customVariables),
|
||||
value: resolveReferences(param.value, getCurrentState(_ref.moduleName), undefined, customVariables),
|
||||
}));
|
||||
const actionPromise = action && action(...actionArguments.map((argument) => argument.value));
|
||||
return actionPromise ?? Promise.resolve();
|
||||
|
|
@ -566,7 +606,10 @@ function executeActionWithDebounce(_ref, event, mode, customVariables) {
|
|||
|
||||
// Don't allow switching to disabled page in editor as well as viewer
|
||||
if (!disabled) {
|
||||
_ref.switchPage(event.pageId, resolveReferences(event.queryParams, getCurrentState(), [], customVariables));
|
||||
_ref.switchPage(
|
||||
event.pageId,
|
||||
resolveReferences(event.queryParams, getCurrentState(_ref.moduleName), [], customVariables)
|
||||
);
|
||||
}
|
||||
if (_ref.appDefinition.pages[event.pageId]) {
|
||||
if (disabled) {
|
||||
|
|
@ -580,7 +623,10 @@ function executeActionWithDebounce(_ref, event, mode, customVariables) {
|
|||
},
|
||||
},
|
||||
};
|
||||
useCurrentStateStore.getState().actions.setErrors(generalProps);
|
||||
useSuperStore
|
||||
.getState()
|
||||
.modules[_ref.moduleName].useCurrentStateStore.getState()
|
||||
.actions.setErrors(generalProps);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -603,44 +649,53 @@ export async function onEvent(_ref, eventName, events, options = {}, mode = 'edi
|
|||
|
||||
if (eventName === 'onTrigger') {
|
||||
const { component, queryId, queryName, parameters } = options;
|
||||
useCurrentStateStore.getState().actions.setCurrentState({
|
||||
components: {
|
||||
...getCurrentState().components,
|
||||
[component.name]: {
|
||||
...getCurrentState().components[component.name],
|
||||
useSuperStore
|
||||
.getState()
|
||||
.modules[_ref.moduleName].useCurrentStateStore.getState()
|
||||
.actions.setCurrentState({
|
||||
components: {
|
||||
...getCurrentState(_ref.moduleName).components,
|
||||
[component.name]: {
|
||||
...getCurrentState(_ref.moduleName).components[component.name],
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
runQuery(_ref, queryId, queryName, true, mode, parameters);
|
||||
}
|
||||
|
||||
if (eventName === 'onCalendarEventSelect') {
|
||||
const { component, calendarEvent } = options;
|
||||
|
||||
useCurrentStateStore.getState().actions.setCurrentState({
|
||||
components: {
|
||||
...getCurrentState().components,
|
||||
[component.name]: {
|
||||
...getCurrentState().components[component.name],
|
||||
selectedEvent: { ...calendarEvent },
|
||||
useSuperStore
|
||||
.getState()
|
||||
.modules[_ref.moduleName].useCurrentStateStore.getState()
|
||||
.actions.setCurrentState({
|
||||
components: {
|
||||
...getCurrentState(_ref.moduleName).components,
|
||||
[component.name]: {
|
||||
...getCurrentState(_ref.moduleName).components[component.name],
|
||||
selectedEvent: { ...calendarEvent },
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
executeActionsForEventId(_ref, 'onCalendarEventSelect', events, mode, customVariables);
|
||||
}
|
||||
|
||||
if (eventName === 'onCalendarSlotSelect') {
|
||||
const { component, selectedSlots } = options;
|
||||
useCurrentStateStore.getState().actions.setCurrentState({
|
||||
components: {
|
||||
...getCurrentState().components,
|
||||
[component.name]: {
|
||||
...getCurrentState().components[component.name],
|
||||
selectedSlots,
|
||||
useSuperStore
|
||||
.getState()
|
||||
.modules[_ref.moduleName].useCurrentStateStore.getState()
|
||||
.actions.setCurrentState({
|
||||
components: {
|
||||
...getCurrentState(_ref.moduleName).components,
|
||||
[component.name]: {
|
||||
...getCurrentState(_ref.moduleName).components[component.name],
|
||||
selectedSlots,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
executeActionsForEventId(_ref, 'onCalendarSlotSelect', events, mode, customVariables);
|
||||
}
|
||||
|
|
@ -792,7 +847,7 @@ export function getQueryVariables(options, state) {
|
|||
|
||||
export function previewQuery(_ref, query, calledFromQuery = false, userSuppliedParameters = {}) {
|
||||
let parameters = userSuppliedParameters;
|
||||
const queryPanelState = useQueryPanelStore.getState();
|
||||
const queryPanelState = useSuperStore.getState().modules[_ref.moduleName].useQueryPanelStore.getState();
|
||||
const { queryPreviewData } = queryPanelState;
|
||||
const { setPreviewLoading, setPreviewData } = queryPanelState.actions;
|
||||
|
||||
|
|
@ -811,7 +866,7 @@ export function previewQuery(_ref, query, calledFromQuery = false, userSuppliedP
|
|||
);
|
||||
}
|
||||
|
||||
const queryState = { ...getCurrentState(), parameters };
|
||||
const queryState = { ...getCurrentState(_ref.moduleName), parameters };
|
||||
const options = getQueryVariables(query.options, queryState);
|
||||
|
||||
return new Promise(function (resolve, reject) {
|
||||
|
|
@ -824,7 +879,7 @@ export function previewQuery(_ref, query, calledFromQuery = false, userSuppliedP
|
|||
queryExecutionPromise = dataqueryService.preview(
|
||||
query,
|
||||
options,
|
||||
useAppVersionStore.getState().editingVersion?.id
|
||||
useSuperStore.getState().modules[_ref.moduleName].useAppVersionStore.getState().editingVersion?.id
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -903,15 +958,19 @@ export function runQuery(
|
|||
shouldSetPreviewData = false
|
||||
) {
|
||||
let parameters = userSuppliedParameters;
|
||||
const query = useDataQueriesStore.getState().dataQueries.find((query) => query.id === queryId);
|
||||
const queryEvents = useAppDataStore
|
||||
const query = useSuperStore
|
||||
.getState()
|
||||
.modules[_ref.moduleName].useDataQueriesStore.getState()
|
||||
.dataQueries.find((query) => query.id === queryId);
|
||||
const queryEvents = useSuperStore
|
||||
.getState()
|
||||
.modules[_ref.moduleName].useAppDataStore.getState()
|
||||
.events.filter((event) => event.target === 'data_query' && event.sourceId === queryId);
|
||||
|
||||
let dataQuery = {};
|
||||
|
||||
// const { setPreviewLoading, setPreviewData } = useQueryPanelStore.getState().actions;
|
||||
const queryPanelState = useQueryPanelStore.getState();
|
||||
// const { setPreviewLoading, setPreviewData } = useSuperStore.getState().modules[_ref.moduleName].useQueryPanelStore.getState().actions;
|
||||
const queryPanelState = useSuperStore.getState().modules[_ref.moduleName].useQueryPanelStore.getState();
|
||||
const { queryPreviewData } = queryPanelState;
|
||||
const { setPreviewLoading, setPreviewData } = queryPanelState.actions;
|
||||
if (shouldSetPreviewData) {
|
||||
|
|
@ -936,12 +995,13 @@ export function runQuery(
|
|||
);
|
||||
}
|
||||
|
||||
const queryState = { ...getCurrentState(), parameters };
|
||||
const queryState = { ...getCurrentState(_ref.moduleName), parameters };
|
||||
const options = getQueryVariables(dataQuery.options, queryState);
|
||||
|
||||
if (dataQuery.options?.requestConfirmation) {
|
||||
const queryConfirmationList = useEditorStore.getState().queryConfirmationList
|
||||
? [...useEditorStore.getState().queryConfirmationList]
|
||||
const queryConfirmationList = useSuperStore.getState().modules[_ref.moduleName].useEditorStore.getState()
|
||||
.queryConfirmationList
|
||||
? [...useSuperStore.getState().modules[_ref.moduleName].useEditorStore.getState().queryConfirmationList]
|
||||
: [];
|
||||
|
||||
const queryConfirmation = {
|
||||
|
|
@ -963,18 +1023,21 @@ export function runQuery(
|
|||
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
return new Promise(function (resolve, reject) {
|
||||
useCurrentStateStore.getState().actions.setCurrentState({
|
||||
queries: {
|
||||
...getCurrentState().queries,
|
||||
[queryName]: {
|
||||
...getCurrentState().queries[queryName],
|
||||
isLoading: true,
|
||||
data: [],
|
||||
rawData: [],
|
||||
useSuperStore
|
||||
.getState()
|
||||
.modules[_ref.moduleName].useCurrentStateStore.getState()
|
||||
.actions.setCurrentState({
|
||||
queries: {
|
||||
...getCurrentState(_ref.moduleName).queries,
|
||||
[queryName]: {
|
||||
...getCurrentState(_ref.moduleName).queries[queryName],
|
||||
isLoading: true,
|
||||
data: [],
|
||||
rawData: [],
|
||||
},
|
||||
},
|
||||
},
|
||||
errors: {},
|
||||
});
|
||||
errors: {},
|
||||
});
|
||||
let queryExecutionPromise = null;
|
||||
if (query.kind === 'runjs') {
|
||||
queryExecutionPromise = executeMultilineJS(_self, query.options.code, query?.id, false, mode, parameters);
|
||||
|
|
@ -1029,33 +1092,39 @@ export function runQuery(
|
|||
setPreviewData(errorData);
|
||||
}
|
||||
// errorData = query.kind === 'runpy' ? data.data : data;
|
||||
useCurrentStateStore.getState().actions.setErrors({
|
||||
[queryName]: {
|
||||
type: 'query',
|
||||
kind: query.kind,
|
||||
data: errorData,
|
||||
options: options,
|
||||
},
|
||||
});
|
||||
useSuperStore
|
||||
.getState()
|
||||
.modules[_ref.moduleName].useCurrentStateStore.getState()
|
||||
.actions.setErrors({
|
||||
[queryName]: {
|
||||
type: 'query',
|
||||
kind: query.kind,
|
||||
data: errorData,
|
||||
options: options,
|
||||
},
|
||||
});
|
||||
|
||||
useCurrentStateStore.getState().actions.setCurrentState({
|
||||
queries: {
|
||||
...getCurrentState().queries,
|
||||
[queryName]: _.assign(
|
||||
{
|
||||
...getCurrentState().queries[queryName],
|
||||
isLoading: false,
|
||||
},
|
||||
query.kind === 'restapi'
|
||||
? {
|
||||
request: data.data.requestObject,
|
||||
response: data.data.responseObject,
|
||||
responseHeaders: data.data.responseHeaders,
|
||||
}
|
||||
: {}
|
||||
),
|
||||
},
|
||||
});
|
||||
useSuperStore
|
||||
.getState()
|
||||
.modules[_ref.moduleName].useCurrentStateStore.getState()
|
||||
.actions.setCurrentState({
|
||||
queries: {
|
||||
...getCurrentState(_ref.moduleName).queries,
|
||||
[queryName]: _.assign(
|
||||
{
|
||||
...getCurrentState(_ref.moduleName).queries[queryName],
|
||||
isLoading: false,
|
||||
},
|
||||
query.kind === 'restapi'
|
||||
? {
|
||||
request: data.data.requestObject,
|
||||
response: data.data.responseObject,
|
||||
responseHeaders: data.data.responseHeaders,
|
||||
}
|
||||
: {}
|
||||
),
|
||||
},
|
||||
});
|
||||
resolve(data);
|
||||
onEvent(_self, 'onDataQueryFailure', queryEvents);
|
||||
if (mode !== 'view') {
|
||||
|
|
@ -1077,23 +1146,29 @@ export function runQuery(
|
|||
'edit'
|
||||
);
|
||||
if (finalData.status === 'failed') {
|
||||
useCurrentStateStore.getState().actions.setCurrentState({
|
||||
queries: {
|
||||
...getCurrentState().queries,
|
||||
[queryName]: {
|
||||
...getCurrentState().queries[queryName],
|
||||
isLoading: false,
|
||||
useSuperStore
|
||||
.getState()
|
||||
.modules[_ref.moduleName].useCurrentStateStore.getState()
|
||||
.actions.setCurrentState({
|
||||
queries: {
|
||||
...getCurrentState(_ref.moduleName).queries,
|
||||
[queryName]: {
|
||||
...getCurrentState(_ref.moduleName).queries[queryName],
|
||||
isLoading: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
useCurrentStateStore.getState().actions.setErrors({
|
||||
[queryName]: {
|
||||
type: 'transformations',
|
||||
data: finalData,
|
||||
options: options,
|
||||
},
|
||||
});
|
||||
useSuperStore
|
||||
.getState()
|
||||
.modules[_ref.moduleName].useCurrentStateStore.getState()
|
||||
.actions.setErrors({
|
||||
[queryName]: {
|
||||
type: 'transformations',
|
||||
data: finalData,
|
||||
options: options,
|
||||
},
|
||||
});
|
||||
resolve(finalData);
|
||||
onEvent(_self, 'onDataQueryFailure', queryEvents);
|
||||
return;
|
||||
|
|
@ -1111,60 +1186,68 @@ export function runQuery(
|
|||
duration: notificationDuration,
|
||||
});
|
||||
}
|
||||
useCurrentStateStore.getState().actions.setCurrentState({
|
||||
queries: {
|
||||
...getCurrentState().queries,
|
||||
[queryName]: _.assign(
|
||||
{
|
||||
...getCurrentState().queries[queryName],
|
||||
isLoading: false,
|
||||
data: finalData,
|
||||
rawData,
|
||||
},
|
||||
query.kind === 'restapi'
|
||||
? {
|
||||
request: data.request,
|
||||
response: data.response,
|
||||
responseHeaders: data.responseHeaders,
|
||||
}
|
||||
: {}
|
||||
),
|
||||
},
|
||||
// Used to generate logs
|
||||
succededQuery: {
|
||||
[queryName]: {
|
||||
type: 'query',
|
||||
kind: query.kind,
|
||||
useSuperStore
|
||||
.getState()
|
||||
.modules[_ref.moduleName].useCurrentStateStore.getState()
|
||||
.actions.setCurrentState({
|
||||
queries: {
|
||||
...getCurrentState(_ref.moduleName).queries,
|
||||
[queryName]: _.assign(
|
||||
{
|
||||
...getCurrentState(_ref.moduleName).queries[queryName],
|
||||
isLoading: false,
|
||||
data: finalData,
|
||||
rawData,
|
||||
},
|
||||
query.kind === 'restapi'
|
||||
? {
|
||||
request: data.request,
|
||||
response: data.response,
|
||||
responseHeaders: data.responseHeaders,
|
||||
}
|
||||
: {}
|
||||
),
|
||||
},
|
||||
},
|
||||
});
|
||||
// Used to generate logs
|
||||
succededQuery: {
|
||||
[queryName]: {
|
||||
type: 'query',
|
||||
kind: query.kind,
|
||||
},
|
||||
},
|
||||
});
|
||||
resolve({ status: 'ok', data: finalData });
|
||||
onEvent(_self, 'onDataQuerySuccess', queryEvents, mode);
|
||||
}
|
||||
})
|
||||
.catch(({ error }) => {
|
||||
if (mode !== 'view') toast.error(error ?? 'Unknown error');
|
||||
useCurrentStateStore.getState().actions.setCurrentState({
|
||||
queries: {
|
||||
...getCurrentState().queries,
|
||||
[queryName]: {
|
||||
isLoading: false,
|
||||
useSuperStore
|
||||
.getState()
|
||||
.modules[_ref.moduleName].useCurrentStateStore.getState()
|
||||
.actions.setCurrentState({
|
||||
queries: {
|
||||
...getCurrentState(_ref.moduleName).queries,
|
||||
[queryName]: {
|
||||
isLoading: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
resolve({ status: 'failed', message: error });
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
export function setTablePageIndex(tableId, index) {
|
||||
export function setTablePageIndex(tableId, index, _ref) {
|
||||
if (_.isEmpty(tableId)) {
|
||||
console.log('No table is associated with this event.');
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
const table = Object.entries(getCurrentState().components).filter((entry) => entry?.[1]?.id === tableId)?.[0]?.[1];
|
||||
const table = Object.entries(getCurrentState(_ref.moduleName).components).filter(
|
||||
(entry) => entry?.[1]?.id === tableId
|
||||
)?.[0]?.[1];
|
||||
const newPageIndex = resolveReferences(index, getCurrentState());
|
||||
table.setPage(newPageIndex ?? 1);
|
||||
return Promise.resolve();
|
||||
|
|
@ -1186,10 +1269,10 @@ for computing component state. It replaces the previous try-catch block with
|
|||
a more efficient approach, precomputing the parent component types and using
|
||||
conditional checks for better performance and error handling.*/
|
||||
|
||||
export function computeComponentState(components = {}) {
|
||||
export function computeComponentState(components = {}, moduleName) {
|
||||
try {
|
||||
let componentState = {};
|
||||
const currentComponents = getCurrentState().components;
|
||||
const currentComponents = getCurrentState(moduleName).components;
|
||||
|
||||
// Precompute parent component types
|
||||
const parentComponentTypes = {};
|
||||
|
|
@ -1225,14 +1308,17 @@ export function computeComponentState(components = {}) {
|
|||
}
|
||||
});
|
||||
|
||||
useCurrentStateStore.getState().actions.setCurrentState({
|
||||
components: {
|
||||
...componentState,
|
||||
},
|
||||
});
|
||||
useSuperStore
|
||||
.getState()
|
||||
.modules[moduleName].useCurrentStateStore.getState()
|
||||
.actions.setCurrentState({
|
||||
components: {
|
||||
...componentState,
|
||||
},
|
||||
});
|
||||
|
||||
return new Promise((resolve) => {
|
||||
useEditorStore.getState().actions.updateEditorState({
|
||||
useSuperStore.getState().modules[moduleName].useEditorStore.getState().actions.updateEditorState({
|
||||
defaultComponentStateComputed: true,
|
||||
});
|
||||
resolve();
|
||||
|
|
@ -1256,14 +1342,17 @@ export const getSvgIcon = (key, height = 50, width = 50, iconFile = undefined, s
|
|||
};
|
||||
|
||||
export const debuggerActions = {
|
||||
error: (errors) => {
|
||||
useCurrentStateStore.getState().actions.setErrors({
|
||||
...errors,
|
||||
});
|
||||
error: (errors, moduleName) => {
|
||||
useSuperStore
|
||||
.getState()
|
||||
.modules[moduleName].useCurrentStateStore.getState()
|
||||
.actions.setErrors({
|
||||
...errors,
|
||||
});
|
||||
},
|
||||
|
||||
flush: () => {
|
||||
useCurrentStateStore.getState().actions.setCurrentState({
|
||||
flush: (moduleName) => {
|
||||
useSuperStore.getState().modules[moduleName].useCurrentStateStore.getState().actions.setCurrentState({
|
||||
errors: {},
|
||||
});
|
||||
},
|
||||
|
|
@ -1357,8 +1446,8 @@ export const debuggerActions = {
|
|||
});
|
||||
return querySuccesslogs;
|
||||
},
|
||||
flushAllLog: () => {
|
||||
useCurrentStateStore.getState().actions.setCurrentState({
|
||||
flushAllLog: (moduleName) => {
|
||||
useSuperStore.getState().modules[moduleName].useCurrentStateStore.getState().actions.setCurrentState({
|
||||
succededQuery: {},
|
||||
});
|
||||
},
|
||||
|
|
@ -1399,7 +1488,8 @@ export const cloneComponents = (
|
|||
currentPageId,
|
||||
updateAppDefinition,
|
||||
isCloning = true,
|
||||
isCut = false
|
||||
isCut = false,
|
||||
moduleName
|
||||
) => {
|
||||
if (selectedComponents.length < 1) return getSelectedText();
|
||||
|
||||
|
|
@ -1466,7 +1556,7 @@ export const cloneComponents = (
|
|||
}
|
||||
|
||||
return new Promise((resolve) => {
|
||||
useEditorStore.getState().actions.updateEditorState({
|
||||
useSuperStore.getState().modules[moduleName].useEditorStore.getState().actions.updateEditorState({
|
||||
currentSidebarTab: 2,
|
||||
});
|
||||
resolve();
|
||||
|
|
@ -1776,8 +1866,11 @@ function convertMapSet(obj) {
|
|||
}
|
||||
}
|
||||
|
||||
export const checkExistingQueryName = (newName) =>
|
||||
useDataQueriesStore.getState().dataQueries.some((query) => query.name === newName);
|
||||
export const checkExistingQueryName = (newName, moduleName) =>
|
||||
useSuperStore
|
||||
.getState()
|
||||
.modules[moduleName].useDataQueriesStore.getState()
|
||||
.dataQueries.some((query) => query.name === newName);
|
||||
|
||||
export const runQueries = (queries, _ref) => {
|
||||
queries.forEach((query) => {
|
||||
|
|
@ -1787,30 +1880,33 @@ export const runQueries = (queries, _ref) => {
|
|||
});
|
||||
};
|
||||
|
||||
export const computeQueryState = (queries) => {
|
||||
export const computeQueryState = (queries, moduleName) => {
|
||||
let queryState = {};
|
||||
queries.forEach((query) => {
|
||||
if (query.plugin?.plugin_id) {
|
||||
queryState[query.name] = {
|
||||
...query.plugin.manifest_file.data?.source?.exposedVariables,
|
||||
kind: query.plugin.manifest_file.data.source.kind,
|
||||
...getCurrentState().queries[query.name],
|
||||
...getCurrentState(moduleName).queries[query.name],
|
||||
};
|
||||
} else {
|
||||
queryState[query.name] = {
|
||||
...DataSourceTypes.find((source) => source.kind === query.kind)?.exposedVariables,
|
||||
kind: DataSourceTypes.find((source) => source.kind === query.kind)?.kind,
|
||||
...getCurrentState()?.queries[query.name],
|
||||
...getCurrentState(moduleName)?.queries[query.name],
|
||||
};
|
||||
}
|
||||
});
|
||||
const hasDiffQueryState = !_.isEqual(getCurrentState()?.queries, queryState);
|
||||
const hasDiffQueryState = !_.isEqual(getCurrentState(moduleName)?.queries, queryState);
|
||||
if (hasDiffQueryState) {
|
||||
useCurrentStateStore.getState().actions.setCurrentState({
|
||||
queries: {
|
||||
...queryState,
|
||||
},
|
||||
});
|
||||
useSuperStore
|
||||
.getState()
|
||||
.modules[moduleName].useCurrentStateStore.getState()
|
||||
.actions.setCurrentState({
|
||||
queries: {
|
||||
...queryState,
|
||||
},
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -3,15 +3,15 @@ import moment from 'moment';
|
|||
import _, { isEmpty } from 'lodash';
|
||||
import axios from 'axios';
|
||||
import JSON5 from 'json5';
|
||||
import { previewQuery, executeAction } from '@/_helpers/appUtils';
|
||||
import { executeAction } from '@/_helpers/appUtils';
|
||||
import { toast } from 'react-hot-toast';
|
||||
import { authenticationService } from '@/_services/authentication.service';
|
||||
|
||||
import { useDataQueriesStore } from '@/_stores/dataQueriesStore';
|
||||
import { useSuperStore } from '../_stores/superStore';
|
||||
import { getCurrentState } from '@/_stores/currentStateStore';
|
||||
import { getWorkspaceIdOrSlugFromURL, getSubpath, returnWorkspaceIdIfNeed } from './routes';
|
||||
import { getCookie, eraseCookie } from '@/_helpers/cookie';
|
||||
import { staticDataSources } from '@/Editor/QueryManager/constants';
|
||||
import { getDateTimeFormat } from '@/Editor/Components/Table/Datepicker';
|
||||
|
||||
export function findProp(obj, prop, defval) {
|
||||
if (typeof defval === 'undefined') defval = null;
|
||||
|
|
@ -413,6 +413,97 @@ export function validateWidget({ validationObject, widgetValue, currentState, cu
|
|||
};
|
||||
}
|
||||
|
||||
export function validateDates({ validationObject, widgetValue, currentState, customResolveObjects }) {
|
||||
let isValid = true;
|
||||
let validationError = null;
|
||||
const validationDateFormat = validationObject?.dateFormat?.value || 'MM/DD/YYYY';
|
||||
const validationTimeFormat = validationObject?.timeFormat?.value || 'HH:mm';
|
||||
const customRule = validationObject?.customRule?.value;
|
||||
const parsedDateFormat = validationObject?.parseDateFormat?.value;
|
||||
const isTwentyFourHrFormatEnabled = validationObject?.isTwentyFourHrFormatEnabled?.value ?? false;
|
||||
const isDateSelectionEnabled = validationObject?.isDateSelectionEnabled?.value ?? true;
|
||||
const _widgetDateValue = moment(widgetValue, parsedDateFormat);
|
||||
const _widgetTimeValue = moment(
|
||||
widgetValue,
|
||||
getDateTimeFormat(parsedDateFormat, true, isTwentyFourHrFormatEnabled, isDateSelectionEnabled)
|
||||
).format(validationTimeFormat);
|
||||
|
||||
const resolvedMinDate = resolveWidgetFieldValue(
|
||||
validationObject?.minDate?.value,
|
||||
currentState,
|
||||
undefined,
|
||||
customResolveObjects
|
||||
);
|
||||
const resolvedMaxDate = resolveWidgetFieldValue(
|
||||
validationObject?.maxDate?.value,
|
||||
currentState,
|
||||
undefined,
|
||||
customResolveObjects
|
||||
);
|
||||
const resolvedMinTime = resolveWidgetFieldValue(
|
||||
validationObject?.minTime?.value,
|
||||
currentState,
|
||||
undefined,
|
||||
customResolveObjects
|
||||
);
|
||||
const resolvedMaxTime = resolveWidgetFieldValue(
|
||||
validationObject?.maxTime?.value,
|
||||
currentState,
|
||||
undefined,
|
||||
customResolveObjects
|
||||
);
|
||||
|
||||
// Minimum date validation
|
||||
if (resolvedMinDate !== undefined && moment(resolvedMinDate).isValid()) {
|
||||
if (!moment(resolvedMinDate, validationDateFormat).isBefore(moment(_widgetDateValue, validationDateFormat))) {
|
||||
return {
|
||||
isValid: false,
|
||||
validationError: `Minimum date is ${resolvedMinDate}`,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// Maximum date validation
|
||||
if (resolvedMaxDate !== undefined && moment(resolvedMaxDate).isValid()) {
|
||||
if (!moment(resolvedMaxDate, validationDateFormat).isAfter(moment(_widgetDateValue, validationDateFormat))) {
|
||||
return {
|
||||
isValid: false,
|
||||
validationError: `Maximum date is ${resolvedMaxDate}`,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// Minimum time validation
|
||||
if (resolvedMinTime !== undefined && moment(resolvedMinTime, validationTimeFormat, true).isValid()) {
|
||||
if (!moment(resolvedMinTime, validationTimeFormat).isBefore(moment(_widgetTimeValue, validationTimeFormat))) {
|
||||
return {
|
||||
isValid: false,
|
||||
validationError: `Minimum time is ${resolvedMinTime}`,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// Maximum time validation
|
||||
if (resolvedMaxTime !== undefined && moment(resolvedMaxTime, validationTimeFormat, true).isValid()) {
|
||||
if (!moment(resolvedMaxTime, validationTimeFormat).isAfter(moment(_widgetTimeValue, validationTimeFormat))) {
|
||||
return {
|
||||
isValid: false,
|
||||
validationError: `Maximum time is ${resolvedMaxTime}`,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
//Custom rule validation
|
||||
const resolvedCustomRule = resolveWidgetFieldValue(customRule, currentState, false, customResolveObjects);
|
||||
if (typeof resolvedCustomRule === 'string' && resolvedCustomRule !== '') {
|
||||
return { isValid: false, validationError: resolvedCustomRule };
|
||||
}
|
||||
return {
|
||||
isValid,
|
||||
validationError,
|
||||
};
|
||||
}
|
||||
|
||||
export function validateEmail(email) {
|
||||
const emailRegex =
|
||||
/^(([^<>()[\]\.,;:\s@\"]+(\.[^<>()[\]\.,;:\s@\"]+)*)|(\".+\"))@(([^<>()[\]\.,;:\s@\"]+\.)+[^<>()[\]\.,;:\s@\"]{2,})$/i;
|
||||
|
|
@ -420,8 +511,16 @@ export function validateEmail(email) {
|
|||
}
|
||||
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
export async function executeMultilineJS(_ref, code, queryId, isPreview, mode = '', parameters = {}) {
|
||||
const currentState = getCurrentState();
|
||||
export async function executeMultilineJS(
|
||||
_ref,
|
||||
code,
|
||||
queryId,
|
||||
isPreview,
|
||||
mode = '',
|
||||
parameters = {},
|
||||
hasParamSupport = false
|
||||
) {
|
||||
const currentState = getCurrentState(_ref.moduleName);
|
||||
let result = {},
|
||||
error = null;
|
||||
|
||||
|
|
@ -432,7 +531,10 @@ export async function executeMultilineJS(_ref, code, queryId, isPreview, mode =
|
|||
|
||||
const actions = generateAppActions(_ref, queryId, mode, isPreview);
|
||||
|
||||
const queryDetails = useDataQueriesStore.getState().dataQueries.find((q) => q.id === queryId);
|
||||
const queryDetails = useSuperStore
|
||||
.getState()
|
||||
.modules[_ref.moduleName].useDataQueriesStore.getState()
|
||||
.dataQueries.find((q) => q.id === queryId);
|
||||
|
||||
const defaultParams =
|
||||
queryDetails?.options?.parameters?.reduce(
|
||||
|
|
@ -462,7 +564,10 @@ export async function executeMultilineJS(_ref, code, queryId, isPreview, mode =
|
|||
params = {};
|
||||
}
|
||||
const processedParams = {};
|
||||
const query = useDataQueriesStore.getState().dataQueries.find((q) => q.name === key);
|
||||
const query = useSuperStore
|
||||
.getState()
|
||||
.modules.modules[_ref.moduleName].useDataQueriesStore.getState()
|
||||
.dataQueries.find((q) => q.name === key);
|
||||
query.options.parameters?.forEach((arg) => (processedParams[arg.name] = params[arg.name]));
|
||||
return actions.runQuery(key, processedParams);
|
||||
},
|
||||
|
|
@ -591,14 +696,17 @@ export const generateAppActions = (_ref, queryId, mode, isPreview = false) => {
|
|||
: {};
|
||||
|
||||
const runQuery = (queryName = '', parameters) => {
|
||||
const query = useDataQueriesStore.getState().dataQueries.find((query) => {
|
||||
const isFound = query.name === queryName;
|
||||
if (isPreview) {
|
||||
return isFound;
|
||||
} else {
|
||||
return isFound && isQueryRunnable(query);
|
||||
}
|
||||
});
|
||||
const query = useSuperStore
|
||||
.getState()
|
||||
.modules[_ref.moduleName].useDataQueriesStore.getState()
|
||||
.dataQueries.find((query) => {
|
||||
const isFound = query.name === queryName;
|
||||
if (isPreview) {
|
||||
return isFound;
|
||||
} else {
|
||||
return isFound && isQueryRunnable(query);
|
||||
}
|
||||
});
|
||||
|
||||
const processedParams = {};
|
||||
if (_.isEmpty(query) || queryId === query?.id) {
|
||||
|
|
|
|||
46
frontend/src/_hooks/useAppDarkMode.js
Normal file
46
frontend/src/_hooks/useAppDarkMode.js
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
import { useCallback, useMemo } from 'react';
|
||||
import { shallow } from 'zustand/shallow';
|
||||
import { useEditorStore } from '@/_stores/editorStore';
|
||||
import { useAppDataStore } from '@/_stores/appDataStore';
|
||||
|
||||
const useAppDarkMode = () => {
|
||||
const { appMode, setAppMode } = useEditorStore(
|
||||
(state) => ({
|
||||
appMode: state.appMode,
|
||||
setAppMode: state.actions.setAppMode,
|
||||
}),
|
||||
shallow
|
||||
);
|
||||
|
||||
const { isTJDarkMode } = useAppDataStore(
|
||||
(state) => ({
|
||||
isTJDarkMode: state.isTJDarkMode,
|
||||
}),
|
||||
shallow
|
||||
);
|
||||
|
||||
const handleAppModeChange = useCallback(
|
||||
(appMode = 'auto') => {
|
||||
setAppMode(appMode);
|
||||
},
|
||||
[setAppMode]
|
||||
);
|
||||
|
||||
const isAppDarkMode = useMemo(() => {
|
||||
if (appMode === 'light') {
|
||||
return false;
|
||||
} else if (appMode === 'dark') {
|
||||
return true;
|
||||
} else {
|
||||
return localStorage.getItem('darkMode') === 'true';
|
||||
}
|
||||
}, [appMode, isTJDarkMode]);
|
||||
|
||||
return {
|
||||
onAppModeChange: handleAppModeChange,
|
||||
appMode,
|
||||
isAppDarkMode,
|
||||
};
|
||||
};
|
||||
|
||||
export default useAppDarkMode;
|
||||
|
|
@ -1,153 +1,170 @@
|
|||
import { appVersionService } from '@/_services';
|
||||
import { create, zustandDevTools } from './utils';
|
||||
import { shallow } from 'zustand/shallow';
|
||||
import { useContext } from 'react';
|
||||
import { useSuperStore } from './superStore';
|
||||
import { ModuleContext } from '../_contexts/ModuleContext';
|
||||
|
||||
const initialState = {
|
||||
editingVersion: null,
|
||||
currentUser: null,
|
||||
apps: [],
|
||||
appName: null,
|
||||
slug: null,
|
||||
isPublic: null,
|
||||
isMaintenanceOn: null,
|
||||
organizationId: null,
|
||||
currentVersionId: null,
|
||||
userId: null,
|
||||
app: {},
|
||||
components: [],
|
||||
pages: [],
|
||||
layouts: [],
|
||||
events: [],
|
||||
eventHandlers: [],
|
||||
appDefinitionDiff: null,
|
||||
appDiffOptions: {},
|
||||
isSaving: false,
|
||||
appId: null,
|
||||
areOthersOnSameVersionAndPage: false,
|
||||
appVersionPreviewLink: null,
|
||||
metadata: null,
|
||||
eventsUpdatedLoader: false,
|
||||
eventsCreatedLoader: false,
|
||||
actionsUpdatedLoader: false,
|
||||
eventToDeleteLoaderIndex: null,
|
||||
};
|
||||
export function createAppDataStore(moduleName) {
|
||||
const initialState = {
|
||||
editingVersion: null,
|
||||
currentUser: null,
|
||||
apps: [],
|
||||
appName: null,
|
||||
slug: null,
|
||||
isPublic: null,
|
||||
isMaintenanceOn: null,
|
||||
organizationId: null,
|
||||
currentVersionId: null,
|
||||
userId: null,
|
||||
app: {},
|
||||
components: [],
|
||||
pages: [],
|
||||
layouts: [],
|
||||
events: [],
|
||||
eventHandlers: [],
|
||||
appDefinitionDiff: null,
|
||||
appDiffOptions: {},
|
||||
isSaving: false,
|
||||
appId: null,
|
||||
areOthersOnSameVersionAndPage: false,
|
||||
appVersionPreviewLink: null,
|
||||
moduleName,
|
||||
metadata: null,
|
||||
eventsUpdatedLoader: false,
|
||||
eventsCreatedLoader: false,
|
||||
actionsUpdatedLoader: false,
|
||||
eventToDeleteLoaderIndex: null,
|
||||
isTJDarkMode: localStorage.getItem('darkMode') === 'true',
|
||||
};
|
||||
return create(
|
||||
zustandDevTools(
|
||||
(set, get) => ({
|
||||
...initialState,
|
||||
actions: {
|
||||
updateEditingVersion: (version) => set(() => ({ editingVersion: version })),
|
||||
updateApps: (apps) => set(() => ({ apps: apps })),
|
||||
updateState: (state) => set((prev) => ({ ...prev, ...state })),
|
||||
updateAppDefinitionDiff: (appDefinitionDiff) => set(() => ({ appDefinitionDiff: appDefinitionDiff })),
|
||||
updateAppVersion: (appId, versionId, pageId, appDefinitionDiff, isUserSwitchedVersion = false) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
get().actions.setIsSaving(true);
|
||||
const isComponentCutProcess = get().appDiffOptions?.componentCut === true;
|
||||
|
||||
export const useAppDataStore = create(
|
||||
zustandDevTools(
|
||||
(set, get) => ({
|
||||
...initialState,
|
||||
actions: {
|
||||
updateEditingVersion: (version) => set(() => ({ editingVersion: version })),
|
||||
updateApps: (apps) => set(() => ({ apps: apps })),
|
||||
updateState: (state) => set((prev) => ({ ...prev, ...state })),
|
||||
|
||||
updateAppDefinitionDiff: (appDefinitionDiff) => set(() => ({ appDefinitionDiff: appDefinitionDiff })),
|
||||
updateAppVersion: (appId, versionId, pageId, appDefinitionDiff, isUserSwitchedVersion = false) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
useAppDataStore.getState().actions.setIsSaving(true);
|
||||
const isComponentCutProcess = get().appDiffOptions?.componentCut === true;
|
||||
|
||||
appVersionService
|
||||
.autoSaveApp(
|
||||
appId,
|
||||
versionId,
|
||||
appDefinitionDiff.updateDiff,
|
||||
appDefinitionDiff.type,
|
||||
pageId,
|
||||
appDefinitionDiff.operation,
|
||||
isUserSwitchedVersion,
|
||||
isComponentCutProcess
|
||||
)
|
||||
.then(() => {
|
||||
useAppDataStore.getState().actions.setIsSaving(false);
|
||||
})
|
||||
.catch((error) => {
|
||||
useAppDataStore.getState().actions.setIsSaving(false);
|
||||
reject(error);
|
||||
})
|
||||
.finally(() => resolve());
|
||||
});
|
||||
},
|
||||
updateAppVersionEventHandlers: async (events, updateType = 'update', param) => {
|
||||
useAppDataStore.getState().actions.setIsSaving(true);
|
||||
if (param === 'actionId') {
|
||||
set({ actionsUpdatedLoader: true });
|
||||
}
|
||||
if (param === 'eventId') {
|
||||
set({ eventsUpdatedLoader: true });
|
||||
}
|
||||
const appId = get().appId;
|
||||
const versionId = get().currentVersionId;
|
||||
|
||||
const response = await appVersionService.saveAppVersionEventHandlers(appId, versionId, events, updateType);
|
||||
|
||||
useAppDataStore.getState().actions.setIsSaving(false);
|
||||
set({ eventsUpdatedLoader: false, actionsUpdatedLoader: false });
|
||||
const updatedEvents = get().events;
|
||||
|
||||
updatedEvents.forEach((e, index) => {
|
||||
const toUpdate = response.find((r) => r.id === e.id);
|
||||
if (toUpdate) {
|
||||
updatedEvents[index] = toUpdate;
|
||||
appVersionService
|
||||
.autoSaveApp(
|
||||
appId,
|
||||
versionId,
|
||||
appDefinitionDiff.updateDiff,
|
||||
appDefinitionDiff.type,
|
||||
pageId,
|
||||
appDefinitionDiff.operation,
|
||||
isUserSwitchedVersion,
|
||||
isComponentCutProcess
|
||||
)
|
||||
.then(() => {
|
||||
get().actions.setIsSaving(false);
|
||||
})
|
||||
.catch((error) => {
|
||||
get().actions.setIsSaving(false);
|
||||
reject(error);
|
||||
})
|
||||
.finally(() => resolve());
|
||||
});
|
||||
},
|
||||
updateAppVersionEventHandlers: async (events, updateType = 'update', param) => {
|
||||
get().actions.setIsSaving(true);
|
||||
if (param === 'actionId') {
|
||||
set({ actionsUpdatedLoader: true });
|
||||
}
|
||||
});
|
||||
if (param === 'eventId') {
|
||||
set({ eventsUpdatedLoader: true });
|
||||
}
|
||||
const appId = get().appId;
|
||||
const versionId = get().currentVersionId;
|
||||
|
||||
set(() => ({ events: updatedEvents }));
|
||||
},
|
||||
const response = await appVersionService.saveAppVersionEventHandlers(appId, versionId, events, updateType);
|
||||
|
||||
createAppVersionEventHandlers: async (event) => {
|
||||
useAppDataStore.getState().actions.setIsSaving(true);
|
||||
set({ eventsCreatedLoader: true });
|
||||
get().actions.setIsSaving(false);
|
||||
set({ eventsUpdatedLoader: false, actionsUpdatedLoader: false });
|
||||
const updatedEvents = get().events;
|
||||
|
||||
const appId = get().appId;
|
||||
const versionId = get().currentVersionId;
|
||||
|
||||
const updatedEvents = get().events;
|
||||
const response = await appVersionService.createAppVersionEventHandler(appId, versionId, event);
|
||||
useAppDataStore.getState().actions.setIsSaving(false);
|
||||
set({ eventsCreatedLoader: false });
|
||||
|
||||
updatedEvents.push(response);
|
||||
|
||||
set(() => ({ events: updatedEvents }));
|
||||
},
|
||||
|
||||
deleteAppVersionEventHandler: async (eventId) => {
|
||||
useAppDataStore.getState().actions.setIsSaving(true);
|
||||
const appId = get().appId;
|
||||
const versionId = get().currentVersionId;
|
||||
|
||||
const updatedEvents = get().events;
|
||||
|
||||
const response = await appVersionService.deleteAppVersionEventHandler(appId, versionId, eventId);
|
||||
useAppDataStore.getState().actions.setIsSaving(false);
|
||||
|
||||
set({ eventToDeleteLoaderIndex: null });
|
||||
if (response?.affected === 1) {
|
||||
updatedEvents.splice(
|
||||
updatedEvents.findIndex((e) => e.id === eventId),
|
||||
1
|
||||
);
|
||||
updatedEvents.forEach((e, index) => {
|
||||
const toUpdate = response.find((r) => r.id === e.id);
|
||||
if (toUpdate) {
|
||||
updatedEvents[index] = toUpdate;
|
||||
}
|
||||
});
|
||||
|
||||
set(() => ({ events: updatedEvents }));
|
||||
}
|
||||
},
|
||||
autoUpdateEventStore: async (versionId) => {
|
||||
const appId = get().appId;
|
||||
const response = await appVersionService.findAllEventsWithSourceId(appId, versionId);
|
||||
},
|
||||
|
||||
set(() => ({ events: response }));
|
||||
createAppVersionEventHandlers: async (event) => {
|
||||
get().actions.setIsSaving(true);
|
||||
set({ eventsCreatedLoader: true });
|
||||
|
||||
const appId = get().appId;
|
||||
const versionId = get().currentVersionId;
|
||||
|
||||
const updatedEvents = get().events;
|
||||
const response = await appVersionService.createAppVersionEventHandler(appId, versionId, event);
|
||||
get().actions.setIsSaving(false);
|
||||
set({ eventsCreatedLoader: false });
|
||||
|
||||
updatedEvents.push(response);
|
||||
|
||||
set(() => ({ events: updatedEvents }));
|
||||
},
|
||||
|
||||
deleteAppVersionEventHandler: async (eventId) => {
|
||||
get().actions.setIsSaving(true);
|
||||
const appId = get().appId;
|
||||
const versionId = get().currentVersionId;
|
||||
|
||||
const updatedEvents = get().events;
|
||||
|
||||
const response = await appVersionService.deleteAppVersionEventHandler(appId, versionId, eventId);
|
||||
get().actions.setIsSaving(false);
|
||||
|
||||
set({ eventToDeleteLoaderIndex: null });
|
||||
if (response?.affected === 1) {
|
||||
updatedEvents.splice(
|
||||
updatedEvents.findIndex((e) => e.id === eventId),
|
||||
1
|
||||
);
|
||||
|
||||
set(() => ({ events: updatedEvents }));
|
||||
}
|
||||
},
|
||||
|
||||
autoUpdateEventStore: async (versionId) => {
|
||||
const appId = get().appId;
|
||||
const response = await appVersionService.findAllEventsWithSourceId(appId, versionId);
|
||||
|
||||
set(() => ({ events: response }));
|
||||
},
|
||||
setIsSaving: (isSaving) => set(() => ({ isSaving })),
|
||||
setAppId: (appId) => set(() => ({ appId })),
|
||||
setAppPreviewLink: (appVersionPreviewLink) => set(() => ({ appVersionPreviewLink })),
|
||||
setMetadata: (metadata) => set(() => ({ metadata })),
|
||||
setEventToDeleteLoaderIndex: (index) => set(() => ({ eventToDeleteLoaderIndex: index })),
|
||||
updateIsTJDarkMode: (isTJDarkMode) => set({ isTJDarkMode }),
|
||||
},
|
||||
setIsSaving: (isSaving) => set(() => ({ isSaving })),
|
||||
setAppId: (appId) => set(() => ({ appId })),
|
||||
setAppPreviewLink: (appVersionPreviewLink) => set(() => ({ appVersionPreviewLink })),
|
||||
setMetadata: (metadata) => set(() => ({ metadata })),
|
||||
setEventToDeleteLoaderIndex: (index) => set(() => ({ eventToDeleteLoaderIndex: index })),
|
||||
},
|
||||
}),
|
||||
{ name: 'App Data Store' }
|
||||
)
|
||||
);
|
||||
}),
|
||||
{ name: 'App Data Store' }
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
export const useAppDataStore = (callback, shallow) => {
|
||||
const moduleName = useContext(ModuleContext);
|
||||
|
||||
if (!moduleName) throw Error('module context not available');
|
||||
|
||||
const _useAppDataStore = useSuperStore((state) => state.modules[moduleName].useAppDataStore);
|
||||
|
||||
return _useAppDataStore(callback, shallow);
|
||||
};
|
||||
|
||||
export const useEditingVersion = () => useAppDataStore((state) => state.editingVersion, shallow);
|
||||
export const useIsSaving = () => useAppDataStore((state) => state.isSaving, shallow);
|
||||
|
|
|
|||
|
|
@ -1,33 +1,49 @@
|
|||
import { create, zustandDevTools } from './utils';
|
||||
import { useContext } from 'react';
|
||||
import { useSuperStore } from './superStore';
|
||||
import { ModuleContext } from '../_contexts/ModuleContext';
|
||||
|
||||
const initialState = {
|
||||
editingVersion: null,
|
||||
isUserEditingTheVersion: false,
|
||||
releasedVersionId: null,
|
||||
isVersionReleased: false,
|
||||
appVersions: [],
|
||||
export function createAppVersionStore(moduleName) {
|
||||
const initialState = {
|
||||
editingVersion: null,
|
||||
isUserEditingTheVersion: false,
|
||||
releasedVersionId: null,
|
||||
isVersionReleased: false,
|
||||
appVersions: [],
|
||||
moduleName,
|
||||
};
|
||||
|
||||
return create(
|
||||
zustandDevTools(
|
||||
(set, get) => ({
|
||||
...initialState,
|
||||
actions: {
|
||||
updateEditingVersion: (version) =>
|
||||
set({ editingVersion: version, isVersionReleased: get().releasedVersionId === version?.id }),
|
||||
enableReleasedVersionPopupState: () => set({ isUserEditingTheVersion: true }),
|
||||
disableReleasedVersionPopupState: () => set({ isUserEditingTheVersion: false }),
|
||||
updateReleasedVersionId: (versionId) =>
|
||||
set({
|
||||
releasedVersionId: versionId,
|
||||
isVersionReleased: get().editingVersion?.id ? get().editingVersion?.id === versionId : false,
|
||||
}),
|
||||
setAppVersions: (versions) => set({ appVersions: versions }),
|
||||
},
|
||||
}),
|
||||
{ name: 'App Version Manager Store' }
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
export const useAppVersionStore = (callback, shallow) => {
|
||||
const moduleName = useContext(ModuleContext);
|
||||
|
||||
if (!moduleName) throw Error('module context not available');
|
||||
|
||||
const _useAppVersionStore = useSuperStore((state) => state.modules[moduleName].useAppVersionStore);
|
||||
|
||||
return _useAppVersionStore(callback, shallow);
|
||||
};
|
||||
|
||||
export const useAppVersionStore = create(
|
||||
zustandDevTools(
|
||||
(set, get) => ({
|
||||
...initialState,
|
||||
actions: {
|
||||
updateEditingVersion: (version) =>
|
||||
set({ editingVersion: version, isVersionReleased: get().releasedVersionId === version?.id }),
|
||||
enableReleasedVersionPopupState: () => set({ isUserEditingTheVersion: true }),
|
||||
disableReleasedVersionPopupState: () => set({ isUserEditingTheVersion: false }),
|
||||
updateReleasedVersionId: (versionId) =>
|
||||
set({
|
||||
releasedVersionId: versionId,
|
||||
isVersionReleased: get().editingVersion?.id ? get().editingVersion?.id === versionId : false,
|
||||
}),
|
||||
setAppVersions: (versions) => set({ appVersions: versions }),
|
||||
},
|
||||
}),
|
||||
{ name: 'App Version Manager Store' }
|
||||
)
|
||||
);
|
||||
|
||||
export const useAppVersionActions = () => useAppVersionStore((state) => state.actions);
|
||||
export const useAppVersionState = () => useAppVersionStore((state) => state);
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue