mirror of
https://github.com/ToolJet/ToolJet
synced 2026-05-23 00:48:25 +00:00
Merge pull request #12894 from ToolJet/appbuilder/sprint-12
Appbuilder/sprint 12
This commit is contained in:
commit
cb7eb39334
47 changed files with 2208 additions and 689 deletions
|
|
@ -1 +1 @@
|
|||
Subproject commit 280578f99c45224428f78ee16285b62f4c3631fd
|
||||
Subproject commit 1b77a556709211daed8924821383db9dccc95eb5
|
||||
|
|
@ -58,6 +58,7 @@
|
|||
"dotenv": "^16.0.3",
|
||||
"draft-js": "^0.11.7",
|
||||
"draft-js-export-html": "^1.4.1",
|
||||
"draft-js-import-html": "^1.4.1",
|
||||
"driver.js": "^0.9.8",
|
||||
"emoji-mart": "^5.5.2",
|
||||
"file-loader": "^6.2.0",
|
||||
|
|
|
|||
|
|
@ -232,6 +232,7 @@ export const getAllChildComponents = (allComponents, parentId) => {
|
|||
const childTabId = componentParentId.split('-').at(-1);
|
||||
if (componentParentId === `${parentId}-${childTabId}`) {
|
||||
childComponent.isParentTabORCalendar = true;
|
||||
childComponent.events = useStore.getState().eventsSlice.getEventsByComponentsId(componentId);
|
||||
childComponents.push(childComponent);
|
||||
// Recursively find children of the current child component
|
||||
const childrenOfChild = getAllChildComponents(allComponents, componentId);
|
||||
|
|
@ -242,6 +243,7 @@ export const getAllChildComponents = (allComponents, parentId) => {
|
|||
if (componentParentId === parentId) {
|
||||
let childComponent = deepClone(allComponents[componentId]);
|
||||
childComponent.id = componentId;
|
||||
childComponent.events = useStore.getState().eventsSlice.getEventsByComponentsId(componentId);
|
||||
childComponents.push(childComponent);
|
||||
|
||||
// Recursively find children of the current child component
|
||||
|
|
|
|||
|
|
@ -0,0 +1,27 @@
|
|||
/* eslint-disable import/no-unresolved */
|
||||
import React from 'react';
|
||||
import { openSearchPanel } from '@codemirror/search';
|
||||
import './SearchBox.scss';
|
||||
import { Button as ButtonComponent } from '@/components/ui/Button/Button.jsx';
|
||||
|
||||
export const CodeHinterBtns = ({ view, isPanelOpen, renderCopilot }) => {
|
||||
return (
|
||||
<div
|
||||
className="d-flex tw-flex-col align-items-end tw-gap-[2.5px] w-100 position-absolute tw-pt-[4px] tw-pr-[4px]"
|
||||
style={{ top: isPanelOpen ? '44px' : 0 }}
|
||||
>
|
||||
{!isPanelOpen && (
|
||||
<ButtonComponent
|
||||
iconOnly
|
||||
trailingIcon="search01"
|
||||
size="small"
|
||||
variant="outline"
|
||||
ariaLabel="Open search panel"
|
||||
className="codehinter-search-btn"
|
||||
onClick={() => openSearchPanel(view)}
|
||||
/>
|
||||
)}
|
||||
{renderCopilot && renderCopilot()}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
|
@ -20,10 +20,12 @@ import { PreviewBox } from './PreviewBox';
|
|||
import { removeNestedDoubleCurlyBraces } from '@/_helpers/utils';
|
||||
import useStore from '@/AppBuilder/_stores/store';
|
||||
import { shallow } from 'zustand/shallow';
|
||||
import { syntaxTree } from '@codemirror/language';
|
||||
import { search, searchKeymap, searchPanelOpen } from '@codemirror/search';
|
||||
import { handleSearchPanel, SearchBtn } from './SearchBox';
|
||||
import { handleSearchPanel } from './SearchBox';
|
||||
import { useQueryPanelKeyHooks } from './useQueryPanelKeyHooks';
|
||||
import { isInsideParent } from './utils';
|
||||
import { CodeHinterBtns } from './CodehinterOverlayTriggers';
|
||||
|
||||
const langSupport = Object.freeze({
|
||||
javascript: javascript(),
|
||||
|
|
@ -66,7 +68,7 @@ const MultiLineCodeEditor = (props) => {
|
|||
|
||||
const context = useContext(CodeHinterContext);
|
||||
|
||||
const { suggestionList } = createReferencesLookup(context, true);
|
||||
const { suggestionList: paramList } = createReferencesLookup(context, true);
|
||||
|
||||
const currentValueRef = useRef(initialValue);
|
||||
|
||||
|
|
@ -74,6 +76,7 @@ const MultiLineCodeEditor = (props) => {
|
|||
|
||||
const [editorView, setEditorView] = React.useState(null);
|
||||
|
||||
const [isSearchPanelOpen, setIsSearchPanelOpen] = React.useState(false);
|
||||
const { queryPanelKeybindings } = useQueryPanelKeyHooks(onChange, currentValueRef, 'multiline');
|
||||
|
||||
const handleOnBlur = () => {
|
||||
|
|
@ -146,8 +149,29 @@ const MultiLineCodeEditor = (props) => {
|
|||
return suggestion.hint.includes(nearestSubstring);
|
||||
});
|
||||
|
||||
const localVariables = new Set();
|
||||
|
||||
// Traverse the syntax tree to extract variable declarations
|
||||
syntaxTree(context.state).iterate({
|
||||
enter: (node) => {
|
||||
// JavaScript: Detect variable declarations (var, let, const)
|
||||
if (node.name === 'VariableDefinition') {
|
||||
const varName = context.state.sliceDoc(node.from, node.to);
|
||||
if (varName && varName.startsWith(nearestSubstring)) localVariables.add(varName);
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
// Convert Set to an array of completion suggestions
|
||||
const localVariableSuggestions = [...localVariables].map((varName) => ({
|
||||
hint: varName,
|
||||
type: 'variable',
|
||||
}));
|
||||
|
||||
const suggestionList = paramList.filter((paramSuggestion) => paramSuggestion.hint.includes(nearestSubstring));
|
||||
|
||||
const suggestions = generateHints(
|
||||
[...JSLangHints, ...autoSuggestionList, ...suggestionList],
|
||||
[...localVariableSuggestions, ...JSLangHints, ...autoSuggestionList, ...suggestionList],
|
||||
null,
|
||||
nearestSubstring
|
||||
).map((hint) => {
|
||||
|
|
@ -204,6 +228,7 @@ const MultiLineCodeEditor = (props) => {
|
|||
return {
|
||||
from: context.pos,
|
||||
options: [...suggestions],
|
||||
filter: false,
|
||||
};
|
||||
}
|
||||
|
||||
|
|
@ -237,7 +262,7 @@ const MultiLineCodeEditor = (props) => {
|
|||
]);
|
||||
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
const overRideFunction = React.useCallback((context) => autoCompleteExtensionConfig(context), []);
|
||||
const overRideFunction = React.useCallback((context) => autoCompleteExtensionConfig(context), [paramList]);
|
||||
const { handleTogglePopupExapand, isOpen, setIsOpen, forceUpdate } = portalProps;
|
||||
let cyLabel = paramLabel ? paramLabel.toLowerCase().trim().replace(/\s+/g, '-') : props.cyLabel;
|
||||
|
||||
|
|
@ -258,7 +283,7 @@ const MultiLineCodeEditor = (props) => {
|
|||
ref={wrapperRef}
|
||||
>
|
||||
<div className={`${className} ${darkMode && 'cm-codehinter-dark-themed'}`}>
|
||||
<SearchBtn view={editorView} />
|
||||
<CodeHinterBtns view={editorView} isPanelOpen={isSearchPanelOpen} renderCopilot={renderCopilot} />
|
||||
<CodeHinter.PopupIcon
|
||||
callback={handleTogglePopupExapand}
|
||||
icon="portal-open"
|
||||
|
|
@ -266,7 +291,6 @@ const MultiLineCodeEditor = (props) => {
|
|||
isMultiEditor={true}
|
||||
isQueryManager={isInsideQueryPane}
|
||||
/>
|
||||
{renderCopilot && renderCopilot()}
|
||||
|
||||
<CodeHinter.Portal
|
||||
isCopilotEnabled={false}
|
||||
|
|
@ -326,12 +350,7 @@ const MultiLineCodeEditor = (props) => {
|
|||
readOnly={readOnly}
|
||||
editable={editable} //for transformations in query manager
|
||||
onCreateEditor={(view) => setEditorView(view)}
|
||||
onUpdate={(view) => {
|
||||
const icon = document.querySelector('.codehinter-search-btn');
|
||||
if (searchPanelOpen(view.state)) {
|
||||
icon.style.display = 'none';
|
||||
} else icon.style.display = 'block';
|
||||
}}
|
||||
onUpdate={(view) => setIsSearchPanelOpen(searchPanelOpen(view.state))}
|
||||
/>
|
||||
</div>
|
||||
{showPreview && (
|
||||
|
|
|
|||
|
|
@ -9,7 +9,6 @@ import {
|
|||
findPrevious,
|
||||
replaceNext,
|
||||
replaceAll,
|
||||
openSearchPanel,
|
||||
} from '@codemirror/search';
|
||||
import './SearchBox.scss';
|
||||
import InputComponent from '@/components/ui/Input/Index.jsx';
|
||||
|
|
@ -162,22 +161,3 @@ function SearchPanel({ view }) {
|
|||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export const SearchBtn = ({ view }) => {
|
||||
return (
|
||||
<div
|
||||
className="d-flex justify-content-end w-100 position-absolute tw-pt-[3px] tw-pr-[4px] codehinter-search-btn-wrapper"
|
||||
style={{ top: 0 }}
|
||||
>
|
||||
<ButtonComponent
|
||||
iconOnly
|
||||
trailingIcon="search01"
|
||||
size="small"
|
||||
variant="outline"
|
||||
ariaLabel="Open search panel"
|
||||
className="codehinter-search-btn"
|
||||
onClick={() => openSearchPanel(view)}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -44,7 +44,5 @@
|
|||
}
|
||||
|
||||
.code-hinter-wrapper .codehinter-search-btn {
|
||||
display: block;
|
||||
padding-top: 1px;
|
||||
z-index: 10000;
|
||||
z-index: 1000;
|
||||
}
|
||||
|
|
@ -1,12 +1,18 @@
|
|||
/* eslint-disable import/no-unresolved */
|
||||
import React, { useEffect, useMemo, useRef, useState } from 'react';
|
||||
import React, { useEffect, useMemo, useRef, useState, useContext } from 'react';
|
||||
import { PreviewBox } from './PreviewBox';
|
||||
import { ToolTip } from '@/Editor/Inspector/Elements/Components/ToolTip';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { camelCase, isEmpty, noop, get } from 'lodash';
|
||||
import CodeMirror from '@uiw/react-codemirror';
|
||||
import { javascript } from '@codemirror/lang-javascript';
|
||||
import { autocompletion, completionKeymap, completionStatus, acceptCompletion } from '@codemirror/autocomplete';
|
||||
import {
|
||||
autocompletion,
|
||||
completionKeymap,
|
||||
completionStatus,
|
||||
acceptCompletion,
|
||||
startCompletion,
|
||||
} from '@codemirror/autocomplete';
|
||||
import { defaultKeymap } from '@codemirror/commands';
|
||||
import { keymap } from '@codemirror/view';
|
||||
import FxButton from '../CodeBuilder/Elements/FxButton';
|
||||
|
|
@ -22,6 +28,8 @@ import CodeHinter from './CodeHinter';
|
|||
import { removeNestedDoubleCurlyBraces } from '@/_helpers/utils';
|
||||
import useStore from '@/AppBuilder/_stores/store';
|
||||
import { shallow } from 'zustand/shallow';
|
||||
import { CodeHinterContext } from '../CodeBuilder/CodeHinterContext';
|
||||
import { createReferencesLookup } from '@/_stores/utils';
|
||||
import { useQueryPanelKeyHooks } from './useQueryPanelKeyHooks';
|
||||
|
||||
const SingleLineCodeEditor = ({ componentName, fieldMeta = {}, componentId, ...restProps }) => {
|
||||
|
|
@ -73,6 +81,7 @@ const SingleLineCodeEditor = ({ componentName, fieldMeta = {}, componentId, ...r
|
|||
if (typeof initialValue === 'string' && (initialValue?.includes('components') || initialValue?.includes('queries'))) {
|
||||
newInitialValue = replaceIdsWithName(initialValue);
|
||||
}
|
||||
|
||||
//! Re render the component when the componentName changes as the initialValue is not updated
|
||||
|
||||
// const { variablesExposedForPreview } = useContext(EditorContext) || {};
|
||||
|
|
@ -199,9 +208,14 @@ const EditorInput = ({
|
|||
wrapperRef,
|
||||
showSuggestions,
|
||||
}) => {
|
||||
const getServerSideGlobalSuggestions = useStore((state) => state.getServerSideGlobalSuggestions, shallow);
|
||||
const codeHinterContext = useContext(CodeHinterContext);
|
||||
const { suggestionList: paramHints } = createReferencesLookup(codeHinterContext, true);
|
||||
|
||||
const getSuggestions = useStore((state) => state.getSuggestions, shallow);
|
||||
const [codeMirrorView, setCodeMirrorView] = useState(undefined);
|
||||
|
||||
const getServerSideGlobalSuggestions = useStore((state) => state.getServerSideGlobalSuggestions, shallow);
|
||||
|
||||
const { queryPanelKeybindings } = useQueryPanelKeyHooks(onBlurUpdate, currentValue, 'singleline');
|
||||
|
||||
const isInsideQueryManager = useMemo(
|
||||
|
|
@ -209,16 +223,16 @@ const EditorInput = ({
|
|||
[wrapperRef.current]
|
||||
);
|
||||
function autoCompleteExtensionConfig(context) {
|
||||
const hints = getSuggestions();
|
||||
const hintsWithoutParamHints = getSuggestions();
|
||||
const serverHints = getServerSideGlobalSuggestions(isInsideQueryManager);
|
||||
|
||||
const allHints = {
|
||||
...hints,
|
||||
appHints: [...hints.appHints, ...serverHints],
|
||||
};
|
||||
|
||||
let word = context.matchBefore(/\w*/);
|
||||
|
||||
const hints = {
|
||||
...hintsWithoutParamHints,
|
||||
appHints: [...hintsWithoutParamHints.appHints, ...serverHints, ...paramHints],
|
||||
};
|
||||
|
||||
const totalReferences = (context.state.doc.toString().match(/{{/g) || []).length;
|
||||
|
||||
let queryInput = context.state.doc.toString();
|
||||
|
|
@ -247,17 +261,18 @@ const EditorInput = ({
|
|||
queryInput = '{{' + currentWord + '}}';
|
||||
}
|
||||
|
||||
let completions = getAutocompletion(queryInput, validationType, allHints, totalReferences, originalQueryInput);
|
||||
let completions = getAutocompletion(queryInput, validationType, hints, totalReferences, originalQueryInput);
|
||||
|
||||
return {
|
||||
from: word.from,
|
||||
options: completions,
|
||||
validFor: /^\{\{.*\}\}$/,
|
||||
filter: false,
|
||||
};
|
||||
}
|
||||
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
const overRideFunction = React.useCallback((context) => autoCompleteExtensionConfig(context), [isInsideQueryManager]);
|
||||
const overRideFunction = React.useCallback((context) => autoCompleteExtensionConfig(context), [isInsideQueryManager, paramHints]);
|
||||
|
||||
const autoCompleteConfig = autocompletion({
|
||||
override: [overRideFunction],
|
||||
|
|
@ -424,6 +439,9 @@ const EditorInput = ({
|
|||
ref={previewRef}
|
||||
>
|
||||
<CodeMirror
|
||||
onCreateEditor={(view) => {
|
||||
setCodeMirrorView(view);
|
||||
}}
|
||||
value={currentValue}
|
||||
placeholder={placeholder}
|
||||
height={isInsideQueryPane ? '100%' : showLineNumbers ? '400px' : '100%'}
|
||||
|
|
@ -460,11 +478,16 @@ const EditorInput = ({
|
|||
theme={theme}
|
||||
indentWithTab={false}
|
||||
readOnly={disabled}
|
||||
onKeyDown={(event) => {
|
||||
if (event.key === 'Backspace') {
|
||||
startCompletion(codeMirrorView);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</ErrorBoundary>
|
||||
</CodeHinter.Portal>
|
||||
</div>
|
||||
</ErrorBoundary >
|
||||
</CodeHinter.Portal >
|
||||
</div >
|
||||
);
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -67,7 +67,8 @@ export const getAutocompletion = (input, fieldType, hints, totalReferences = 1,
|
|||
originalQueryInput,
|
||||
searchInput
|
||||
);
|
||||
return orderSuggestions(suggestions, fieldType);
|
||||
|
||||
return suggestions;
|
||||
};
|
||||
|
||||
function orderSuggestions(suggestions, validationType) {
|
||||
|
|
@ -90,10 +91,18 @@ export const generateHints = (hints, totalReferences = 1, input, searchText) =>
|
|||
const hasDepth = currentWord.includes('.');
|
||||
const lastDepth = getLastSubstring(currentWord);
|
||||
|
||||
const displayLabel = getLastDepth(displayedHint);
|
||||
let displayLabel = getLastDepth(displayedHint);
|
||||
|
||||
if (type != 'js_method') {
|
||||
const currentWordDepth = currentWord.split('.').length;
|
||||
displayLabel = hint
|
||||
.split('.')
|
||||
.slice(currentWordDepth - 1)
|
||||
.join('.');
|
||||
}
|
||||
|
||||
return {
|
||||
displayLabel: lastDepth === '' ? displayedHint : displayLabel,
|
||||
displayLabel,
|
||||
label: displayedHint,
|
||||
info: displayedHint,
|
||||
type: type === 'js_method' ? 'js_methods' : type?.toLowerCase(),
|
||||
|
|
@ -154,40 +163,24 @@ export const generateHints = (hints, totalReferences = 1, input, searchText) =>
|
|||
};
|
||||
|
||||
function filterHintsByDepth(input, hints) {
|
||||
if (input === '') return hints;
|
||||
const inputParts = input.split('.');
|
||||
const inputDepth = inputParts.length + 1;
|
||||
|
||||
const inputDepth = input.includes('.') ? input.split('.').length : 0;
|
||||
|
||||
const filteredHints = hints.filter((cm) => {
|
||||
const hintParts = cm.hint.split('.');
|
||||
|
||||
let shouldInclude =
|
||||
(cm.hint.startsWith(input) && hintParts.length === inputDepth + 1) ||
|
||||
(cm.hint.startsWith(input) && hintParts.length === inputDepth);
|
||||
|
||||
const shouldFuzzyMatch = !shouldInclude ? hintParts.length > inputDepth : false;
|
||||
|
||||
if (shouldFuzzyMatch) {
|
||||
// fuzzy match
|
||||
let matchedDepth = -1;
|
||||
for (let i = 0; i < hintParts.length; i++) {
|
||||
if (hintParts[i].includes(input)) {
|
||||
matchedDepth = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (matchedDepth !== -1) {
|
||||
shouldInclude = hintParts.length === matchedDepth + 1;
|
||||
}
|
||||
} else if (input.endsWith('.')) {
|
||||
shouldInclude = cm.hint.startsWith(input) && hintParts.length === inputDepth;
|
||||
}
|
||||
|
||||
return shouldInclude;
|
||||
const hintsWithDepth = hints.map((hint) => {
|
||||
const hintParts = hint.hint.split('.');
|
||||
return {
|
||||
...hint,
|
||||
depth: hintParts.length,
|
||||
};
|
||||
});
|
||||
|
||||
return filteredHints;
|
||||
const filteredHints = hintsWithDepth.filter((hint) => {
|
||||
return hint.depth <= inputDepth;
|
||||
});
|
||||
|
||||
const sortedHints = filteredHints.sort((hint1, hint2) => hint1.depth - hint2.depth);
|
||||
|
||||
return sortedHints;
|
||||
}
|
||||
|
||||
export function findNearestSubstring(inputStr, currentCurosorPos) {
|
||||
|
|
|
|||
|
|
@ -1,8 +1,20 @@
|
|||
import React from 'react';
|
||||
import React, { useState } from 'react';
|
||||
import Select from '@/_ui/Select';
|
||||
import { decodeEntities } from '@/_helpers/utils';
|
||||
import usePopoverObserver from '@/AppBuilder/_hooks/usePopoverObserver';
|
||||
|
||||
export const ChangeDataSource = ({ dataSources, onChange, value, isVersionReleased }) => {
|
||||
const [isMenuOpen, setIsMenuOpen] = useState(false);
|
||||
|
||||
usePopoverObserver(
|
||||
document.getElementsByClassName('query-details')[0],
|
||||
document.querySelector('.change-data-source-select.react-select__control'),
|
||||
document.querySelector('.change-data-source-select.react-select__menu'),
|
||||
isMenuOpen,
|
||||
() => (document.querySelector('.change-data-source-select.react-select__menu').style.display = 'block'),
|
||||
() => (document.querySelector('.change-data-source-select.react-select__menu').style.display = 'none')
|
||||
);
|
||||
|
||||
return (
|
||||
<Select
|
||||
className="w-100"
|
||||
|
|
@ -14,6 +26,13 @@ export const ChangeDataSource = ({ dataSources, onChange, value, isVersionReleas
|
|||
}}
|
||||
useMenuPortal={true}
|
||||
isDisabled={isVersionReleased}
|
||||
customClassPrefix="change-data-source-select"
|
||||
onMenuOpen={() => {
|
||||
setIsMenuOpen(true);
|
||||
}}
|
||||
onMenuClose={() => {
|
||||
setIsMenuOpen(false);
|
||||
}}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -31,6 +31,9 @@ class Restapi extends React.Component {
|
|||
codeHinterHeight: 32, // Default height
|
||||
};
|
||||
this.codeHinterRef = React.createRef();
|
||||
this.isMenuOpenRef = React.createRef();
|
||||
this.prevIsMenuOpenRef = React.createRef(false);
|
||||
this.intersectionObserver = null;
|
||||
this.resizeObserver = null;
|
||||
}
|
||||
|
||||
|
|
@ -47,6 +50,9 @@ class Restapi extends React.Component {
|
|||
if (this.codeHinterRef.current && !this.resizeObserver) {
|
||||
this.setupResizeObserver();
|
||||
}
|
||||
if (!this.intersectionObserver) {
|
||||
this.setupIntersectionObserver();
|
||||
}
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
|
|
@ -75,6 +81,7 @@ class Restapi extends React.Component {
|
|||
}, 1000);
|
||||
|
||||
this.setupResizeObserver();
|
||||
this.setupIntersectionObserver();
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
}
|
||||
|
|
@ -84,6 +91,9 @@ class Restapi extends React.Component {
|
|||
if (this.resizeObserver) {
|
||||
this.resizeObserver.disconnect();
|
||||
}
|
||||
if (this.intersectionObserver) {
|
||||
this.intersectionObserver.disconnect();
|
||||
}
|
||||
}
|
||||
|
||||
setupResizeObserver() {
|
||||
|
|
@ -132,6 +142,33 @@ class Restapi extends React.Component {
|
|||
this.resizeObserver.observe(element);
|
||||
}
|
||||
|
||||
setupIntersectionObserver() {
|
||||
const container = document.getElementsByClassName('query-details')[0];
|
||||
const trigger = document.querySelector('.restapi-method-select.react-select__control');
|
||||
|
||||
if (this.intersectionObserver) {
|
||||
this.intersectionObserver.disconnect();
|
||||
}
|
||||
|
||||
this.intersectionObserver = new IntersectionObserver(
|
||||
([entry]) => {
|
||||
const popover = document.querySelector('.restapi-method-select.react-select__menu');
|
||||
if (entry.isIntersecting) {
|
||||
if (this.prevIsMenuOpenRef.current) {
|
||||
popover.style.display = 'block';
|
||||
this.prevIsMenuOpenRef.current = false;
|
||||
}
|
||||
} else if (this.isMenuOpenRef.current) {
|
||||
popover.style.display = 'none';
|
||||
this.prevIsMenuOpenRef.current = true;
|
||||
}
|
||||
},
|
||||
{ root: container, threshold: [0.5] }
|
||||
);
|
||||
|
||||
this.intersectionObserver.observe(trigger);
|
||||
}
|
||||
|
||||
initizalizeRetryNetworkErrorsToggle = () => {
|
||||
const isRetryNetworkErrorToggleUnused = this.props.options.retry_network_errors === null;
|
||||
if (isRetryNetworkErrorToggleUnused) {
|
||||
|
|
@ -287,6 +324,13 @@ class Restapi extends React.Component {
|
|||
height={32}
|
||||
styles={this.customSelectStyles(this.props.darkMode, 91)}
|
||||
useCustomStyles={true}
|
||||
customClassPrefix="restapi-method-select"
|
||||
onMenuOpen={() => {
|
||||
this.isMenuOpenRef.current = true;
|
||||
}}
|
||||
onMenuClose={() => {
|
||||
this.isMenuOpenRef.current = false;
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
|
|
|
|||
|
|
@ -5,14 +5,25 @@ import CodeHinter from '@/AppBuilder/CodeEditor';
|
|||
import './workflows-query.scss';
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
import useStore from '@/AppBuilder/_stores/store';
|
||||
import usePopoverObserver from '@/AppBuilder/_hooks/usePopoverObserver';
|
||||
|
||||
export function Workflows({ options, optionsChanged, currentState }) {
|
||||
const [workflowOptions, setWorkflowOptions] = useState([]);
|
||||
const [isMenuOpen, setIsMenuOpen] = useState(false);
|
||||
const [_selectedWorkflowId, setSelectedWorkflowId] = useState(undefined);
|
||||
const [params, setParams] = useState([...(options.params ?? [{ key: '', value: '' }])]);
|
||||
|
||||
const appId = useStore((state) => state.app.appId);
|
||||
|
||||
usePopoverObserver(
|
||||
document.getElementsByClassName('query-details')[0],
|
||||
document.querySelector('.workflow-select.react-select__control'),
|
||||
document.querySelector('.workflow-select.react-select__menu'),
|
||||
isMenuOpen,
|
||||
() => (document.querySelector('.workflow-select.react-select__menu').style.display = 'block'),
|
||||
() => (document.querySelector('.workflow-select.react-select__menu').style.display = 'none')
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
appsService.getWorkflows(appId).then(({ workflows }) => {
|
||||
setWorkflowOptions(
|
||||
|
|
@ -50,6 +61,13 @@ export function Workflows({ options, optionsChanged, currentState }) {
|
|||
customWrap={true}
|
||||
width="300px"
|
||||
menuPlacement="bottom"
|
||||
customClassPrefix="workflow-select"
|
||||
onMenuOpen={() => {
|
||||
setIsMenuOpen(true);
|
||||
}}
|
||||
onMenuClose={() => {
|
||||
setIsMenuOpen(false);
|
||||
}}
|
||||
/>
|
||||
<label className="my-2">Params</label>
|
||||
<div className="grid"></div>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,538 @@
|
|||
import React, { useState, useEffect } from 'react';
|
||||
import Accordion from '@/_ui/Accordion';
|
||||
import { EventManager } from '../EventManager';
|
||||
import { renderElement } from '../Utils';
|
||||
import OverlayTrigger from 'react-bootstrap/OverlayTrigger';
|
||||
import Popover from 'react-bootstrap/Popover';
|
||||
import List from '@/ToolJetUI/List/List';
|
||||
import { DragDropContext, Droppable, Draggable } from 'react-beautiful-dnd';
|
||||
import useStore from '@/AppBuilder/_stores/store';
|
||||
import CodeHinter from '@/AppBuilder/CodeEditor';
|
||||
import AddNewButton from '@/ToolJetUI/Buttons/AddNewButton/AddNewButton';
|
||||
import ListGroup from 'react-bootstrap/ListGroup';
|
||||
import { ButtonSolid } from '@/_ui/AppButton/AppButton';
|
||||
import SortableList from '@/_components/SortableList';
|
||||
import Trash from '@/_ui/Icon/solidIcons/Trash';
|
||||
import { shallow } from 'zustand/shallow';
|
||||
import Switch from '@/Editor/CodeBuilder/Elements/Switch';
|
||||
import { usePrevious } from '@dnd-kit/utilities';
|
||||
|
||||
export function Steps({ componentMeta, darkMode, ...restProps }) {
|
||||
const {
|
||||
layoutPropertyChanged,
|
||||
component,
|
||||
dataQueries,
|
||||
paramUpdated,
|
||||
currentState,
|
||||
eventsChanged,
|
||||
apps,
|
||||
allComponents,
|
||||
pages,
|
||||
} = restProps;
|
||||
const getResolvedValue = useStore((state) => state.getResolvedValue, shallow);
|
||||
|
||||
const isDynamicOptionsEnabled = getResolvedValue(component?.component?.definition?.properties?.advanced?.value);
|
||||
const variant = component?.component?.definition?.properties?.variant?.value;
|
||||
const prevVariant = usePrevious(variant)
|
||||
console.log("variant", component?.component?.definition);
|
||||
|
||||
|
||||
const [options, setOptions] = useState([]);
|
||||
const [hoveredOptionIndex, setHoveredOptionIndex] = useState(null);
|
||||
let properties = [];
|
||||
let additionalActions = [];
|
||||
let optionsProperties = [];
|
||||
|
||||
for (const [key] of Object.entries(componentMeta?.properties)) {
|
||||
if (componentMeta?.properties[key]?.section === 'additionalActions') {
|
||||
additionalActions.push(key);
|
||||
} else if (componentMeta?.properties[key]?.accordian === 'Options') {
|
||||
optionsProperties.push(key);
|
||||
} else {
|
||||
properties.push(key);
|
||||
}
|
||||
}
|
||||
|
||||
// the default style of "number" & "titles" type are different for completed label
|
||||
// TODO: Need to revisit this logic when text custom themes are implemented
|
||||
useEffect(() => {
|
||||
const completedLabelColor = component?.component?.definition?.styles?.completedLabel?.value;
|
||||
if (variant !== prevVariant) {
|
||||
if (variant === "numbers" && completedLabelColor === "#1B1F24") {
|
||||
paramUpdated({ name: 'completedLabel' }, 'value', "#FFFFFF", 'styles', false, {});
|
||||
} else if (variant === "titles" && completedLabelColor === "#FFFFFF") {
|
||||
paramUpdated({ name: 'completedLabel' }, 'value', "#1B1F24", 'styles', false, {});
|
||||
}
|
||||
}
|
||||
|
||||
}, [variant])
|
||||
|
||||
const getItemStyle = (isDragging, draggableStyle) => ({
|
||||
userSelect: 'none',
|
||||
...draggableStyle,
|
||||
});
|
||||
|
||||
const updateAllOptionsParams = (options, props) => {
|
||||
paramUpdated({ name: 'steps' }, 'value', options, 'properties', false, props);
|
||||
};
|
||||
|
||||
const generateNewOptions = () => {
|
||||
let found = false;
|
||||
let label = '';
|
||||
let currentNumber = options.length + 1;
|
||||
while (!found) {
|
||||
label = `step ${currentNumber}`;
|
||||
if (options.find((option) => option.name === label) === undefined) {
|
||||
found = true;
|
||||
}
|
||||
currentNumber += 1;
|
||||
}
|
||||
return {
|
||||
name: label,
|
||||
id: currentNumber - 1,
|
||||
tooltip: label,
|
||||
visible: { value: '{{true}}' },
|
||||
disabled: { value: '{{false}}' },
|
||||
};
|
||||
};
|
||||
|
||||
const handleAddOption = () => {
|
||||
let _option = generateNewOptions();
|
||||
const _items = [...options, _option];
|
||||
setOptions(_items);
|
||||
updateAllOptionsParams(_items);
|
||||
};
|
||||
const handleDeleteOption = (index) => {
|
||||
const _items = options.filter((option, i) => i !== index);
|
||||
setOptions(_items);
|
||||
updateAllOptionsParams(_items, { isParamFromDropdownOptions: true });
|
||||
};
|
||||
|
||||
const handleLabelChange = (propertyName, value, index) => {
|
||||
const _options = options.map((option, i) => {
|
||||
if (i === index) {
|
||||
return {
|
||||
...option,
|
||||
[propertyName]: value,
|
||||
};
|
||||
}
|
||||
return option;
|
||||
});
|
||||
setOptions(_options);
|
||||
updateAllOptionsParams(_options);
|
||||
};
|
||||
|
||||
const reorderOptions = async (startIndex, endIndex) => {
|
||||
const result = [...options];
|
||||
const [removed] = result.splice(startIndex, 1);
|
||||
result.splice(endIndex, 0, removed);
|
||||
setOptions(result);
|
||||
updateAllOptionsParams(result);
|
||||
};
|
||||
|
||||
const onDragEnd = ({ source, destination }) => {
|
||||
if (!destination || source?.index === destination?.index) {
|
||||
return;
|
||||
}
|
||||
reorderOptions(source.index, destination.index);
|
||||
};
|
||||
|
||||
const handleOnFxPress = (active, index, key) => {
|
||||
const _options = options.map((option, i) => {
|
||||
if (i === index) {
|
||||
return {
|
||||
...option,
|
||||
[key]: {
|
||||
...option[key],
|
||||
fxActive: active,
|
||||
},
|
||||
};
|
||||
}
|
||||
return option;
|
||||
});
|
||||
setOptions(_options);
|
||||
updateAllOptionsParams(_options);
|
||||
};
|
||||
|
||||
const _renderOverlay = (item, index) => {
|
||||
return (
|
||||
<Popover className={`${darkMode && 'dark-theme theme-dark'}`} style={{ minWidth: '248px' }}>
|
||||
<Popover.Body>
|
||||
<div className="field mb-3" data-cy={`input-and-label-column-name`}>
|
||||
<label data-cy={`label-column-name`} className="font-weight-500 mb-1 font-size-12">
|
||||
{'Id'}
|
||||
</label>
|
||||
<CodeHinter
|
||||
type={'basic'}
|
||||
initialValue={item?.id + ''}
|
||||
theme={darkMode ? 'monokai' : 'default'}
|
||||
mode="javascript"
|
||||
lineNumbers={false}
|
||||
placeholder={'Option label'}
|
||||
onChange={(value) => handleLabelChange('id', value, index)}
|
||||
/>
|
||||
</div>
|
||||
<div className="field mb-3" data-cy={`input-and-label-column-name`}>
|
||||
<label data-cy={`label-column-name`} className="font-weight-500 mb-1 font-size-12">
|
||||
{'Label'}
|
||||
</label>
|
||||
<CodeHinter
|
||||
type={'basic'}
|
||||
initialValue={item?.name}
|
||||
theme={darkMode ? 'monokai' : 'default'}
|
||||
mode="javascript"
|
||||
lineNumbers={false}
|
||||
placeholder={'Option label'}
|
||||
onChange={(value) => handleLabelChange('name', value, index)}
|
||||
/>
|
||||
</div>
|
||||
<div className="field mb-3" data-cy={`input-and-label-column-name`}>
|
||||
<label data-cy={`label-column-name`} className="font-weight-500 mb-1 font-size-12">
|
||||
{'Tooltip'}
|
||||
</label>
|
||||
<CodeHinter
|
||||
type={'basic'}
|
||||
initialValue={item?.tooltip + ''}
|
||||
theme={darkMode ? 'monokai' : 'default'}
|
||||
mode="javascript"
|
||||
lineNumbers={false}
|
||||
placeholder={'Tooltip'}
|
||||
onChange={(value) => handleLabelChange('tooltip', value, index)}
|
||||
/>
|
||||
</div>
|
||||
<div className="field mb-2" data-cy={`input-and-label-column-name`}>
|
||||
<CodeHinter
|
||||
initialValue={item?.visible?.value}
|
||||
theme={darkMode ? 'monokai' : 'default'}
|
||||
mode="javascript"
|
||||
lineNumbers={false}
|
||||
component={component}
|
||||
type={'fxEditor'}
|
||||
paramLabel={'Visibility'}
|
||||
onChange={(value) =>
|
||||
handleLabelChange(
|
||||
'visible',
|
||||
{
|
||||
value,
|
||||
},
|
||||
index
|
||||
)
|
||||
}
|
||||
paramName={'visible'}
|
||||
onFxPress={(active) => handleOnFxPress(active, index, 'visible')}
|
||||
fxActive={item?.visible?.fxActive}
|
||||
fieldMeta={{
|
||||
type: 'toggle',
|
||||
displayName: 'Make editable',
|
||||
}}
|
||||
paramType={'toggle'}
|
||||
/>
|
||||
</div>
|
||||
<div className="field" data-cy={`input-and-label-column-name`}>
|
||||
<CodeHinter
|
||||
initialValue={item?.disabled?.value}
|
||||
theme={darkMode ? 'monokai' : 'default'}
|
||||
mode="javascript"
|
||||
lineNumbers={false}
|
||||
component={component}
|
||||
type={'fxEditor'}
|
||||
paramLabel={'Disable'}
|
||||
paramName={'disable'}
|
||||
onChange={(value) => handleLabelChange('disabled', { value }, index)}
|
||||
onFxPress={(active) => handleOnFxPress(active, index, 'disabled')}
|
||||
fxActive={item?.disabled?.fxActive}
|
||||
fieldMeta={{
|
||||
type: 'toggle',
|
||||
displayName: 'Make editable',
|
||||
}}
|
||||
paramType={'toggle'}
|
||||
/>
|
||||
</div>
|
||||
</Popover.Body>
|
||||
</Popover>
|
||||
);
|
||||
};
|
||||
const _renderOptions = () => {
|
||||
return (
|
||||
<List style={{ marginBottom: '20px' }}>
|
||||
<DragDropContext
|
||||
onDragEnd={(result) => {
|
||||
onDragEnd(result);
|
||||
}}
|
||||
>
|
||||
<Droppable droppableId="droppable">
|
||||
{({ innerRef, droppableProps, placeholder }) => (
|
||||
<div className="w-100" {...droppableProps} ref={innerRef}>
|
||||
{options?.map((item, index) => {
|
||||
return (
|
||||
<Draggable key={item.name} draggableId={item.name} index={index}>
|
||||
{(provided, snapshot) => (
|
||||
<div
|
||||
key={index}
|
||||
ref={provided.innerRef}
|
||||
{...provided.draggableProps}
|
||||
{...provided.dragHandleProps}
|
||||
style={getItemStyle(snapshot.isDragging, provided.draggableProps.style)}
|
||||
>
|
||||
<OverlayTrigger
|
||||
trigger="click"
|
||||
placement="left"
|
||||
rootClose
|
||||
overlay={_renderOverlay(item, index)}
|
||||
>
|
||||
<div key={item.name + item.id}>
|
||||
<ListGroup.Item
|
||||
style={{ marginBottom: '8px', backgroundColor: 'var(--slate3)' }}
|
||||
onMouseEnter={() => setHoveredOptionIndex(index)}
|
||||
onMouseLeave={() => setHoveredOptionIndex(null)}
|
||||
{...restProps}
|
||||
>
|
||||
<div className="row">
|
||||
<div className="col-auto d-flex align-items-center">
|
||||
<SortableList.DragHandle show />
|
||||
</div>
|
||||
<div className="col text-truncate cursor-pointer" style={{ padding: '0px' }}>
|
||||
{getResolvedValue(item.name)}
|
||||
</div>
|
||||
<div className="col-auto">
|
||||
{index === hoveredOptionIndex && (
|
||||
<ButtonSolid
|
||||
variant="danger"
|
||||
size="xs"
|
||||
className={'delete-icon-btn'}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
handleDeleteOption(index);
|
||||
}}
|
||||
>
|
||||
<span className="d-flex">
|
||||
<Trash fill={'var(--tomato9)'} width={12} />
|
||||
</span>
|
||||
</ButtonSolid>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</ListGroup.Item>
|
||||
</div>
|
||||
</OverlayTrigger>
|
||||
</div>
|
||||
)}
|
||||
</Draggable>
|
||||
);
|
||||
})}
|
||||
{placeholder}
|
||||
</div>
|
||||
)}
|
||||
</Droppable>
|
||||
</DragDropContext>
|
||||
<AddNewButton onClick={handleAddOption} dataCy="add-new-dropdown-option" className="mt-0">
|
||||
Add new option
|
||||
</AddNewButton>
|
||||
</List>
|
||||
);
|
||||
};
|
||||
|
||||
const isDynamicStepsEnabled = getResolvedValue(component?.component?.definition?.properties?.advanced?.value);
|
||||
useEffect(() => {
|
||||
setOptions(constructSteps());
|
||||
}, [component?.id, isDynamicStepsEnabled]);
|
||||
|
||||
const constructSteps = () => {
|
||||
try {
|
||||
let optionsValue = isDynamicOptionsEnabled
|
||||
? component?.component?.definition?.properties?.schema?.value
|
||||
: component?.component?.definition?.properties?.steps?.value;
|
||||
let options = [];
|
||||
|
||||
if (isDynamicOptionsEnabled || typeof optionsValue === 'string') {
|
||||
options = getResolvedValue(optionsValue);
|
||||
} else {
|
||||
options = optionsValue?.map((option) => option);
|
||||
}
|
||||
return options.map((option) => {
|
||||
const newOption = { ...option };
|
||||
|
||||
Object.keys(option).forEach((key) => {
|
||||
if (typeof option[key]?.value === 'boolean') {
|
||||
newOption[key]['value'] = `{{${option[key]?.value}}}`;
|
||||
}
|
||||
});
|
||||
|
||||
if (!('visible' in newOption)) {
|
||||
newOption['visible'] = { value: '{{true}}' };
|
||||
}
|
||||
return newOption;
|
||||
});
|
||||
} catch (error) {
|
||||
return [];
|
||||
}
|
||||
};
|
||||
|
||||
let items = [];
|
||||
|
||||
items.push({
|
||||
title: 'Steps',
|
||||
isOpen: true,
|
||||
children: (
|
||||
<>
|
||||
{properties
|
||||
.filter((property) => !optionsProperties.includes(property))
|
||||
?.map((property) => {
|
||||
if (property === 'steps') {
|
||||
return (
|
||||
<>
|
||||
{renderElement(
|
||||
component,
|
||||
componentMeta,
|
||||
paramUpdated,
|
||||
dataQueries,
|
||||
'advanced',
|
||||
'properties',
|
||||
currentState,
|
||||
allComponents
|
||||
)}
|
||||
{isDynamicStepsEnabled
|
||||
? renderElement(
|
||||
component,
|
||||
componentMeta,
|
||||
paramUpdated,
|
||||
dataQueries,
|
||||
'schema',
|
||||
'properties',
|
||||
currentState,
|
||||
allComponents
|
||||
)
|
||||
: _renderOptions()}
|
||||
</>
|
||||
);
|
||||
}
|
||||
// else if (property === 'variant') {
|
||||
// return renderTest(
|
||||
// component,
|
||||
// componentMeta,
|
||||
// paramUpdated,
|
||||
// dataQueries,
|
||||
// 'variant',
|
||||
// 'properties',
|
||||
// currentState,
|
||||
// allComponents,
|
||||
// handleLabelChange
|
||||
// );
|
||||
// }
|
||||
return renderElement(
|
||||
component,
|
||||
componentMeta,
|
||||
paramUpdated,
|
||||
dataQueries,
|
||||
property,
|
||||
'properties',
|
||||
currentState,
|
||||
allComponents,
|
||||
darkMode
|
||||
);
|
||||
})}
|
||||
</>
|
||||
),
|
||||
});
|
||||
|
||||
items.push({
|
||||
title: 'Events',
|
||||
isOpen: true,
|
||||
children: (
|
||||
<EventManager
|
||||
sourceId={component?.id}
|
||||
eventSourceType="component"
|
||||
eventMetaDefinition={componentMeta}
|
||||
dataQueries={dataQueries}
|
||||
components={allComponents}
|
||||
eventsChanged={eventsChanged}
|
||||
apps={apps}
|
||||
darkMode={darkMode}
|
||||
pages={pages}
|
||||
/>
|
||||
),
|
||||
});
|
||||
items.push({
|
||||
title: `Additional Actions`,
|
||||
isOpen: true,
|
||||
children: additionalActions.map((property) => {
|
||||
return renderElement(
|
||||
component,
|
||||
componentMeta,
|
||||
paramUpdated,
|
||||
dataQueries,
|
||||
property,
|
||||
'properties',
|
||||
currentState,
|
||||
allComponents,
|
||||
darkMode,
|
||||
componentMeta.properties?.[property]?.placeholder
|
||||
);
|
||||
}),
|
||||
});
|
||||
|
||||
items.push({
|
||||
title: 'Devices',
|
||||
isOpen: true,
|
||||
children: (
|
||||
<>
|
||||
{renderElement(
|
||||
component,
|
||||
componentMeta,
|
||||
layoutPropertyChanged,
|
||||
dataQueries,
|
||||
'showOnDesktop',
|
||||
'others',
|
||||
currentState,
|
||||
allComponents
|
||||
)}
|
||||
{renderElement(
|
||||
component,
|
||||
componentMeta,
|
||||
layoutPropertyChanged,
|
||||
dataQueries,
|
||||
'showOnMobile',
|
||||
'others',
|
||||
currentState,
|
||||
allComponents
|
||||
)}
|
||||
</>
|
||||
),
|
||||
});
|
||||
|
||||
return <Accordion items={items} />;
|
||||
}
|
||||
|
||||
function renderTest(...props) {
|
||||
const [
|
||||
component,
|
||||
componentMeta,
|
||||
paramUpdated,
|
||||
dataQueries,
|
||||
param,
|
||||
paramType,
|
||||
currentState,
|
||||
components = {},
|
||||
darkMode = false,
|
||||
placeholder = '',
|
||||
validationFn,
|
||||
] = props;
|
||||
const value = componentMeta?.definition?.properties?.variant?.value;
|
||||
return (
|
||||
<div style={{ marginBottom: 8 }}>
|
||||
<Switch
|
||||
value={value}
|
||||
onChange={(e) => {
|
||||
paramUpdated({ name: 'variant' }, 'value', e, 'properties', false, props);
|
||||
}}
|
||||
meta={{
|
||||
...componentMeta.properties[param],
|
||||
fullWidth: true,
|
||||
}}
|
||||
paramName={param}
|
||||
isIcon={false}
|
||||
component={component.component.definition.name}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
@ -255,7 +255,7 @@ export const PropertiesTabElements = ({
|
|||
paramType="properties"
|
||||
/>
|
||||
</div>
|
||||
{resolveReferences(column?.isEditable) && (
|
||||
{(column?.fxActiveFields?.includes('isEditable') || resolveReferences(column?.isEditable)) && (
|
||||
<ValidationProperties
|
||||
column={column}
|
||||
index={index}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import React, { useState, useEffect, useContext } from 'react';
|
||||
import React, { useState, useEffect, useContext, useRef } from 'react';
|
||||
|
||||
import { ActionTypes } from '@/Editor/ActionTypes';
|
||||
import OverlayTrigger from 'react-bootstrap/OverlayTrigger';
|
||||
|
|
@ -32,6 +32,9 @@ import useStore from '@/AppBuilder/_stores/store';
|
|||
import { useEventActions, useEvents } from '@/AppBuilder/_stores/slices/eventsSlice';
|
||||
import ToggleGroup from '@/ToolJetUI/SwitchGroup/ToggleGroup';
|
||||
import ToggleGroupItem from '@/ToolJetUI/SwitchGroup/ToggleGroupItem';
|
||||
import usePopoverObserver from '@/AppBuilder/_hooks/usePopoverObserver';
|
||||
import SolidIcon from '@/_ui/Icon/SolidIcons';
|
||||
import { components as selectComponents } from 'react-select';
|
||||
|
||||
export const EventManager = ({
|
||||
sourceId,
|
||||
|
|
@ -82,6 +85,8 @@ export const EventManager = ({
|
|||
|
||||
const [events, setEvents] = useState([]);
|
||||
const [focusedEventIndex, setFocusedEventIndex] = useState(null);
|
||||
const lastFocusedEventIndex = useRef(null);
|
||||
const shouldSkipOnToggle = useRef(null);
|
||||
|
||||
const { t } = useTranslation();
|
||||
|
||||
|
|
@ -101,8 +106,23 @@ export const EventManager = ({
|
|||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [JSON.stringify(currentEvents)]);
|
||||
|
||||
let actionOptions = ActionTypes.map((action) => {
|
||||
return { name: action.name, value: action.id };
|
||||
let groupedOptions = ActionTypes.reduce((acc, action) => {
|
||||
const groupName = action.group;
|
||||
|
||||
if (!acc[groupName]) {
|
||||
acc[groupName] = [];
|
||||
}
|
||||
|
||||
acc[groupName].push({
|
||||
label: action.name,
|
||||
value: action.id,
|
||||
});
|
||||
|
||||
return acc;
|
||||
}, {});
|
||||
|
||||
let actionOptions = Object.keys(groupedOptions).map((groupName) => {
|
||||
return { label: groupName, options: groupedOptions[groupName] };
|
||||
});
|
||||
|
||||
let checkIfClicksAreInsideOf = document.querySelector('.cm-completionListIncompleteBottom');
|
||||
|
|
@ -124,6 +144,46 @@ export const EventManager = ({
|
|||
}),
|
||||
};
|
||||
|
||||
const actionStyles = {
|
||||
...styles,
|
||||
menuList: (base) => ({
|
||||
...base,
|
||||
padding: '8px 0 8px 8px',
|
||||
'&::-webkit-scrollbar': {
|
||||
width: '10px',
|
||||
},
|
||||
'&::-webkit-scrollbar-track': {
|
||||
background: 'transparent',
|
||||
},
|
||||
'&::-webkit-scrollbar-thumb': {
|
||||
background: '#E4E7EB',
|
||||
border: '1px solid transparent',
|
||||
backgroundClip: 'content-box',
|
||||
},
|
||||
'&::-webkit-scrollbar-thumb:hover': {
|
||||
background: '#E4E7EB !important',
|
||||
border: '1px solid transparent !important',
|
||||
backgroundClip: 'content-box !important',
|
||||
},
|
||||
'&:hover': {
|
||||
'&::-webkit-scrollbar-thumb': {
|
||||
background: '#E4E7EB !important',
|
||||
border: '1px solid transparent !important',
|
||||
backgroundClip: 'content-box !important',
|
||||
},
|
||||
},
|
||||
}),
|
||||
group: (base) => ({
|
||||
...base,
|
||||
padding: 0,
|
||||
}),
|
||||
groupHeading: (base) => ({
|
||||
...base,
|
||||
margin: 0,
|
||||
padding: '0',
|
||||
}),
|
||||
};
|
||||
|
||||
const actionLookup = Object.fromEntries(ActionTypes.map((actionType) => [actionType.id, actionType]));
|
||||
|
||||
let alertTypes = [
|
||||
|
|
@ -394,6 +454,29 @@ export const EventManager = ({
|
|||
return defaultValue;
|
||||
};
|
||||
|
||||
const formatGroupLabel = (data) => {
|
||||
if (data.label === 'run-action') return;
|
||||
return (
|
||||
<div
|
||||
className="tw-border-x-0 tw-border-t-0 tw-border-b-[0.5px] tw-border-solid tw-my-[4px]"
|
||||
style={{ borderColor: 'var(--border-weak)' }}
|
||||
></div>
|
||||
);
|
||||
};
|
||||
|
||||
const CustomOption = (props) => {
|
||||
return (
|
||||
<selectComponents.Option {...props}>
|
||||
<div className="d-flex align-items-center">
|
||||
<div style={{ width: '16px', marginRight: '6px' }}>
|
||||
{props.isSelected && <SolidIcon name="tickv3" width="16px" height="16px" />}
|
||||
</div>
|
||||
<span>{props.label}</span>
|
||||
</div>
|
||||
</selectComponents.Option>
|
||||
);
|
||||
};
|
||||
|
||||
function eventPopover(event, index) {
|
||||
return (
|
||||
<Popover
|
||||
|
|
@ -433,13 +516,17 @@ export const EventManager = ({
|
|||
<Select
|
||||
className={`${darkMode ? 'select-search-dark' : 'select-search'} w-100`}
|
||||
options={actionOptions}
|
||||
value={event.actionId}
|
||||
value={actionOptions
|
||||
.flatMap((group) => group.options)
|
||||
.find((option) => option.value === event.actionId)}
|
||||
components={{ Option: CustomOption }}
|
||||
search={false}
|
||||
onChange={(value) => handlerChanged(index, 'actionId', value)}
|
||||
placeholder={t('globals.select', 'Select') + '...'}
|
||||
styles={styles}
|
||||
styles={actionStyles}
|
||||
useMenuPortal={false}
|
||||
useCustomStyles={true}
|
||||
formatGroupLabel={formatGroupLabel}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -1006,10 +1093,21 @@ export const EventManager = ({
|
|||
placement={popoverPlacement || 'left'}
|
||||
rootClose={true}
|
||||
overlay={eventPopover(event.event, index)}
|
||||
onHide={() => setFocusedEventIndex(null)}
|
||||
onToggle={(showing) => {
|
||||
// If the toggle action should be skipped (e.g., due to a previous state change), reset the flag and exit early.
|
||||
if (shouldSkipOnToggle.current) {
|
||||
shouldSkipOnToggle.current = false;
|
||||
return;
|
||||
}
|
||||
|
||||
// If there is already a focused event, set the skip flag to prevent unnecessary state updates.
|
||||
if (focusedEventIndex !== null && showing) {
|
||||
shouldSkipOnToggle.current = true;
|
||||
}
|
||||
|
||||
if (showing) {
|
||||
setFocusedEventIndex(index);
|
||||
lastFocusedEventIndex.current = index;
|
||||
} else {
|
||||
setFocusedEventIndex(null);
|
||||
}
|
||||
|
|
@ -1018,6 +1116,7 @@ export const EventManager = ({
|
|||
>
|
||||
<div
|
||||
key={index}
|
||||
id={`${sourceId}-${index}`}
|
||||
ref={provided.innerRef}
|
||||
{...provided.draggableProps}
|
||||
{...provided.dragHandleProps}
|
||||
|
|
@ -1061,6 +1160,17 @@ export const EventManager = ({
|
|||
);
|
||||
};
|
||||
|
||||
const shouldUsePopoverObserver = events.length !== 0 && eventSourceType === 'data_query';
|
||||
|
||||
usePopoverObserver(
|
||||
shouldUsePopoverObserver ? document.getElementsByClassName('query-details')[0] : null,
|
||||
document.getElementById(`${sourceId}-${lastFocusedEventIndex.current}`),
|
||||
document.getElementById('popover-basic'),
|
||||
focusedEventIndex !== null,
|
||||
() => (document.getElementById('popover-basic').style.display = 'block'),
|
||||
() => (document.getElementById('popover-basic').style.display = 'none')
|
||||
);
|
||||
|
||||
if (events.length === 0) {
|
||||
return (
|
||||
<>
|
||||
|
|
|
|||
|
|
@ -36,6 +36,7 @@ import Inspect from '@/_ui/Icon/solidIcons/Inspect';
|
|||
import classNames from 'classnames';
|
||||
import { EMPTY_ARRAY } from '@/_stores/editorStore';
|
||||
import { Select } from './Components/Select';
|
||||
import { Steps } from './Components/Steps.jsx';
|
||||
import { deepClone } from '@/_helpers/utilities/utils.helpers';
|
||||
import useStore from '@/AppBuilder/_stores/store';
|
||||
// import { componentTypes } from '@/Editor/WidgetManager/components';
|
||||
|
|
@ -90,6 +91,7 @@ const NEW_REVAMPED_COMPONENTS = [
|
|||
'VerticalDivider',
|
||||
'ModalV2',
|
||||
'Link',
|
||||
'Steps',
|
||||
];
|
||||
|
||||
export const Inspector = ({ componentDefinitionChanged, darkMode, pages, selectedComponentId }) => {
|
||||
|
|
@ -539,8 +541,8 @@ export const Inspector = ({ componentDefinitionChanged, darkMode, pages, selecte
|
|||
componentMeta.displayName === 'Toggle Switch (Legacy)'
|
||||
? 'Toggle (Legacy)'
|
||||
: componentMeta.displayName === 'Toggle Switch'
|
||||
? 'Toggle Switch'
|
||||
: componentMeta.component,
|
||||
? 'Toggle Switch'
|
||||
: componentMeta.component,
|
||||
})}
|
||||
</small>
|
||||
</span>
|
||||
|
|
@ -740,6 +742,8 @@ const GetAccordion = React.memo(
|
|||
case 'DatePickerV2':
|
||||
case 'TimePicker':
|
||||
return <DatetimePickerV2 {...restProps} componentName={componentName} />;
|
||||
case 'Steps':
|
||||
return <Steps {...restProps} />;
|
||||
case 'PhoneInput':
|
||||
return <PhoneInput {...restProps} />;
|
||||
case 'CurrencyInput':
|
||||
|
|
|
|||
|
|
@ -14,6 +14,9 @@ const NEW_WIDGETS = [
|
|||
'TimePicker',
|
||||
'ModalV2',
|
||||
'TextArea',
|
||||
'EmailInput',
|
||||
'PhoneInput',
|
||||
'CurrencyInput',
|
||||
];
|
||||
|
||||
export const WidgetBox = ({ component, darkMode }) => {
|
||||
|
|
|
|||
|
|
@ -168,6 +168,7 @@ export const Viewer = ({ id: appId, darkMode, moduleId = 'canvas', switchDarkMod
|
|||
showViewerNavigation={!isPagesSidebarHidden}
|
||||
handleAppEnvironmentChanged={handleAppEnvironmentChanged}
|
||||
changeToDarkMode={changeToDarkMode}
|
||||
switchPage={switchPage}
|
||||
/>
|
||||
)}
|
||||
<div className="sub-section">
|
||||
|
|
|
|||
|
|
@ -3,8 +3,8 @@ export const containerConfig = {
|
|||
displayName: 'Container',
|
||||
description: 'Group components',
|
||||
defaultSize: {
|
||||
width: 10,
|
||||
height: 200,
|
||||
width: 13,
|
||||
height: 480,
|
||||
},
|
||||
component: 'Container',
|
||||
others: {
|
||||
|
|
|
|||
|
|
@ -4,25 +4,38 @@ export const stepsConfig = {
|
|||
description: 'Step-by-step navigation aid',
|
||||
component: 'Steps',
|
||||
properties: {
|
||||
variant: {
|
||||
type: 'switch',
|
||||
displayName: 'Variant',
|
||||
validation: { schema: { type: 'string' }, defaultValue: 'titles' },
|
||||
options: [
|
||||
{ displayName: 'Label', value: 'titles' },
|
||||
{ displayName: 'Number', value: 'numbers' },
|
||||
{ displayName: 'Plain', value: 'plain' },
|
||||
],
|
||||
accordian: 'label',
|
||||
},
|
||||
schema: {
|
||||
type: 'code',
|
||||
displayName: 'Schema',
|
||||
conditionallyRender: {
|
||||
key: 'advanced',
|
||||
value: true,
|
||||
},
|
||||
accordian: 'Options',
|
||||
},
|
||||
steps: {
|
||||
type: 'code',
|
||||
displayName: 'Steps',
|
||||
displayName: '',
|
||||
showLabel: false,
|
||||
validation: {
|
||||
schema: {
|
||||
type: 'array',
|
||||
element: { type: 'object', object: { id: { type: 'number' } } },
|
||||
element: { type: 'object' },
|
||||
},
|
||||
defaultValue: `[{ name: 'step 1'}, {name: 'step 2'}]`,
|
||||
},
|
||||
},
|
||||
currentStep: {
|
||||
type: 'code',
|
||||
displayName: 'Current step',
|
||||
validation: {
|
||||
schema: { type: 'number' },
|
||||
defaultValue: 1,
|
||||
},
|
||||
},
|
||||
stepsSelectable: {
|
||||
type: 'toggle',
|
||||
displayName: 'Steps selectable',
|
||||
|
|
@ -30,7 +43,38 @@ export const stepsConfig = {
|
|||
schema: { type: 'boolean' },
|
||||
defaultValue: false,
|
||||
},
|
||||
section: 'additionalActions',
|
||||
},
|
||||
disabledState: {
|
||||
type: 'toggle',
|
||||
displayName: 'Disable',
|
||||
validation: { schema: { type: 'boolean' } },
|
||||
section: 'additionalActions',
|
||||
},
|
||||
visibility: {
|
||||
type: 'toggle',
|
||||
displayName: 'Visibility',
|
||||
validation: { schema: { type: 'boolean' }, defaultValue: true },
|
||||
section: 'additionalActions',
|
||||
},
|
||||
advanced: {
|
||||
type: 'toggle',
|
||||
displayName: 'Dynamic options',
|
||||
validation: {
|
||||
schema: { type: 'boolean' },
|
||||
defaultValue: true,
|
||||
},
|
||||
accordian: 'Options',
|
||||
},
|
||||
currentStep: {
|
||||
type: 'code',
|
||||
displayName: 'Current step',
|
||||
validation: {
|
||||
schema: { type: 'number' },
|
||||
defaultValue: 1,
|
||||
},
|
||||
},
|
||||
|
||||
},
|
||||
defaultSize: {
|
||||
width: 22,
|
||||
|
|
@ -40,46 +84,126 @@ export const stepsConfig = {
|
|||
showOnDesktop: { type: 'toggle', displayName: 'Show on desktop' },
|
||||
showOnMobile: { type: 'toggle', displayName: 'Show on mobile' },
|
||||
},
|
||||
actions: [
|
||||
{
|
||||
handle: 'setStep',
|
||||
displayName: 'Set step',
|
||||
params: [
|
||||
{
|
||||
handle: 'option',
|
||||
displayName: 'Option',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
handle: 'setVisibility',
|
||||
displayName: 'Set visibility',
|
||||
params: [{ handle: 'visible', displayName: 'Value', defaultValue: '{{false}}', type: 'toggle' }],
|
||||
},
|
||||
{
|
||||
handle: 'setDisabled',
|
||||
displayName: 'Set disabled',
|
||||
params: [{ handle: 'disable', displayName: 'Value', defaultValue: '{{true}}', type: 'toggle' }],
|
||||
},
|
||||
{
|
||||
handle: 'resetSteps',
|
||||
displayName: 'Reset steps',
|
||||
params: [],
|
||||
},
|
||||
{
|
||||
handle: 'setStepVisible',
|
||||
displayName: 'Set step visible',
|
||||
params: [
|
||||
{
|
||||
handle: 'id',
|
||||
displayName: 'Step id',
|
||||
},
|
||||
{
|
||||
handle: 'visibility',
|
||||
displayName: 'visibility',
|
||||
defaultValue: '{{false}}',
|
||||
type: 'toggle',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
handle: 'setStepDisable',
|
||||
displayName: 'Set step disable',
|
||||
params: [
|
||||
{
|
||||
handle: 'id',
|
||||
displayName: 'Step id',
|
||||
},
|
||||
{
|
||||
handle: 'disabled',
|
||||
displayName: 'disabled',
|
||||
defaultValue: '{{true}}',
|
||||
type: 'toggle',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
events: {
|
||||
onSelect: { displayName: 'On select' },
|
||||
},
|
||||
styles: {
|
||||
color: {
|
||||
incompletedAccent: {
|
||||
type: 'colorSwatches',
|
||||
displayName: 'colorSwatches',
|
||||
displayName: 'Incompleted accent',
|
||||
validation: {
|
||||
schema: { type: 'string' },
|
||||
defaultValue: '#CCD1D5',
|
||||
},
|
||||
accordian: 'steps',
|
||||
},
|
||||
incompletedLabel: {
|
||||
type: 'colorSwatches',
|
||||
displayName: 'Incompleted label',
|
||||
validation: {
|
||||
schema: { type: 'string' },
|
||||
defaultValue: '#1B1F24',
|
||||
},
|
||||
accordian: 'steps',
|
||||
},
|
||||
completedAccent: {
|
||||
type: 'colorSwatches',
|
||||
displayName: 'Completed accent',
|
||||
validation: {
|
||||
schema: { type: 'string' },
|
||||
defaultValue: 'var(--primary-brand)',
|
||||
},
|
||||
accordian: 'steps',
|
||||
},
|
||||
textColor: {
|
||||
completedLabel: {
|
||||
type: 'colorSwatches',
|
||||
displayName: 'Text color',
|
||||
displayName: 'Completed label',
|
||||
validation: {
|
||||
schema: { type: 'string' },
|
||||
defaultValue: '#000000',
|
||||
defaultValue: '#1B1F24',
|
||||
},
|
||||
accordian: 'steps',
|
||||
},
|
||||
theme: {
|
||||
type: 'select',
|
||||
displayName: 'Theme',
|
||||
currentStepLabel: {
|
||||
type: 'colorSwatches',
|
||||
displayName: 'Current step label',
|
||||
validation: {
|
||||
schema: { type: 'string' },
|
||||
defaultValue: '#1B1F24',
|
||||
},
|
||||
accordian: 'steps',
|
||||
},
|
||||
padding: {
|
||||
type: 'switch',
|
||||
displayName: 'Padding',
|
||||
validation: {
|
||||
schema: { type: 'union', schemas: [{ type: 'string' }, { type: 'number' }] },
|
||||
defaultValue: 'default',
|
||||
},
|
||||
options: [
|
||||
{ name: 'titles', value: 'titles' },
|
||||
{ name: 'numbers', value: 'numbers' },
|
||||
{ name: 'plain', value: 'plain' },
|
||||
{ displayName: 'Default', value: 'default' },
|
||||
{ displayName: 'None', value: 'none' },
|
||||
],
|
||||
validation: {
|
||||
schema: { type: 'string' },
|
||||
defaultValue: 'titles',
|
||||
},
|
||||
},
|
||||
visibility: {
|
||||
type: 'toggle',
|
||||
displayName: 'Visibility',
|
||||
validation: {
|
||||
schema: { type: 'boolean' },
|
||||
defaultValue: true,
|
||||
},
|
||||
accordian: 'container',
|
||||
},
|
||||
},
|
||||
exposedVariables: {
|
||||
|
|
@ -92,17 +216,35 @@ export const stepsConfig = {
|
|||
},
|
||||
properties: {
|
||||
steps: {
|
||||
value: `{{ [{ name: 'step 1', tooltip: 'some tooltip', id: 1},{ name: 'step 2', tooltip: 'some tooltip', id: 2},{ name: 'step 3', tooltip: 'some tooltip', id: 3},{ name: 'step 4', tooltip: 'some tooltip', id: 4},{ name: 'step 5', tooltip: 'some tooltip', id: 5}]}}`,
|
||||
value: [
|
||||
{ name: 'step 1', tooltip: '', id: 1, visible: { value: true }, disabled: { value: false } },
|
||||
{ name: 'step 2', tooltip: '', id: 2, visible: { value: true }, disabled: { value: false } },
|
||||
{ name: 'step 3', tooltip: '', id: 3, visible: { value: true }, disabled: { value: false } },
|
||||
{ name: 'step 4', tooltip: '', id: 4, visible: { value: true }, disabled: { value: false } },
|
||||
{ name: 'step 5', tooltip: '', id: 5, visible: { value: true }, disabled: { value: false } },
|
||||
],
|
||||
},
|
||||
schema: {
|
||||
value: `{{ [{ name: 'step 1', tooltip: '', id: 1,visible: true, disabled: false},{ name: 'step 2', tooltip: '', id: 2,visible: true, disabled: false},{ name: 'step 3', tooltip: '', id: 3,visible: true, disabled: false},{ name: 'step 4', tooltip: '', id: 4,visible: true, disabled: false},{ name: 'step 5', tooltip: '', id: 5,visible: true, disabled: false}]}}`,
|
||||
},
|
||||
disabledState: { value: '{{false}}' },
|
||||
variant: { value: 'titles' },
|
||||
currentStep: { value: '{{3}}' },
|
||||
stepsSelectable: { value: true },
|
||||
advanced: { value: `{{false}}` },
|
||||
visibility: { value: '{{true}}' },
|
||||
},
|
||||
events: [],
|
||||
styles: {
|
||||
visibility: { value: '{{true}}' },
|
||||
theme: { value: 'titles' },
|
||||
color: { value: 'var(--primary-brand)' },
|
||||
textColor: { value: '' },
|
||||
// color: { value: '' },
|
||||
// textColor: { value: '' },
|
||||
padding: { value: 'default' },
|
||||
incompletedAccent: { value: '#E4E7EB' },
|
||||
incompletedLabel: { value: '#1B1F24' },
|
||||
completedAccent: { value: 'var(--primary-brand)' },
|
||||
completedLabel: { value: '#1B1F24' },
|
||||
currentStepLabel: { value: '#1B1F24' },
|
||||
},
|
||||
},
|
||||
};
|
||||
|
|
|
|||
|
|
@ -4,9 +4,9 @@ function usePopoverObserver(containerRef, triggerRef, popoverRef, show, onShow,
|
|||
const prevShow = useRef(false);
|
||||
|
||||
// Check if it is a ref or a DOM element
|
||||
const container = containerRef?.current ? containerRef.current : containerRef;
|
||||
const trigger = triggerRef?.current ? triggerRef.current : triggerRef;
|
||||
const popover = popoverRef?.current ? popoverRef.current : popoverRef;
|
||||
const container = containerRef?.current !== undefined ? containerRef.current : containerRef;
|
||||
const trigger = triggerRef?.current !== undefined ? triggerRef.current : triggerRef;
|
||||
const popover = popoverRef?.current !== undefined ? popoverRef.current : popoverRef;
|
||||
|
||||
useEffect(() => {
|
||||
if (!container || !trigger) return;
|
||||
|
|
|
|||
|
|
@ -258,7 +258,11 @@ export const createDataQuerySlice = (set, get) => ({
|
|||
set((state) => {
|
||||
state.dataQuery.creatingQueryInProcessId = null;
|
||||
state.dataQuery.queries.modules[moduleId] = [
|
||||
{ ...data, data_source_id: queryToClone.data_source_id },
|
||||
{
|
||||
...data,
|
||||
data_source_id: queryToClone.data_source_id,
|
||||
plugin: { iconFile: queryToClone.plugin?.iconFile, icon_file: queryToClone.plugin?.icon_file },
|
||||
},
|
||||
...state.dataQuery.queries.modules[moduleId],
|
||||
];
|
||||
});
|
||||
|
|
|
|||
|
|
@ -122,6 +122,7 @@ export const createResolvedSlice = (set, get) => ({
|
|||
'setVariables'
|
||||
);
|
||||
get().updateDependencyValues(`variables.${key}`);
|
||||
get().checkAndSetTrueBuildSuggestionsFlag();
|
||||
},
|
||||
|
||||
getVariable: (key, moduleId = 'canvas') => {
|
||||
|
|
@ -165,6 +166,7 @@ export const createResolvedSlice = (set, get) => ({
|
|||
'setPageVariable'
|
||||
);
|
||||
get().updateDependencyValues(`page.variables.${key}`);
|
||||
get().checkAndSetTrueBuildSuggestionsFlag();
|
||||
},
|
||||
|
||||
getPageVariable: (key, moduleId = 'canvas') => {
|
||||
|
|
|
|||
|
|
@ -1,62 +1,36 @@
|
|||
export const ActionTypes = [
|
||||
{
|
||||
name: 'Run query',
|
||||
id: 'run-query',
|
||||
options: [{ queryId: '' }],
|
||||
group: 'run-action',
|
||||
},
|
||||
{
|
||||
name: 'Show Alert',
|
||||
id: 'show-alert',
|
||||
options: [{ name: 'message', type: 'text', default: 'Message !' }],
|
||||
group: 'run-action',
|
||||
},
|
||||
{
|
||||
name: 'Logout',
|
||||
id: 'logout',
|
||||
},
|
||||
{
|
||||
name: 'Run Query',
|
||||
id: 'run-query',
|
||||
options: [{ queryId: '' }],
|
||||
},
|
||||
{
|
||||
name: 'Open Webpage',
|
||||
id: 'open-webpage',
|
||||
options: [{ name: 'url', type: 'text', default: 'https://example.com' }],
|
||||
},
|
||||
{
|
||||
name: 'Go to app',
|
||||
id: 'go-to-app',
|
||||
name: 'Control component',
|
||||
id: 'control-component',
|
||||
options: [
|
||||
{ name: 'app', type: 'text', default: '' },
|
||||
{ name: 'queryParams', type: 'code', default: '[]' },
|
||||
{ name: 'component', type: 'text', default: '' },
|
||||
{ name: 'action', type: 'text', default: '' },
|
||||
],
|
||||
group: 'control-component',
|
||||
},
|
||||
{
|
||||
name: 'Show Modal',
|
||||
name: 'Show modal',
|
||||
id: 'show-modal',
|
||||
options: [{ name: 'modal', type: 'text', default: '' }],
|
||||
group: 'control-component',
|
||||
},
|
||||
{
|
||||
name: 'Close Modal',
|
||||
name: 'Close modal',
|
||||
id: 'close-modal',
|
||||
options: [{ name: 'modal', type: 'text', default: '' }],
|
||||
},
|
||||
{
|
||||
name: 'Copy to clipboard',
|
||||
id: 'copy-to-clipboard',
|
||||
options: [{ name: 'copy-to-clipboard', type: 'text', default: '' }],
|
||||
},
|
||||
{
|
||||
name: 'Set local storage',
|
||||
id: 'set-localstorage-value',
|
||||
options: [
|
||||
{ name: 'key', type: 'code', default: '' },
|
||||
{ name: 'value', type: 'code', default: '' },
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'Generate file',
|
||||
id: 'generate-file',
|
||||
options: [
|
||||
{ name: 'fileType', type: 'text', default: '' },
|
||||
{ name: 'fileName', type: 'text', default: '' },
|
||||
{ name: 'data', type: 'code', default: '{{[]}}' },
|
||||
],
|
||||
group: 'control-component',
|
||||
},
|
||||
{
|
||||
name: 'Set table page',
|
||||
|
|
@ -69,28 +43,28 @@ export const ActionTypes = [
|
|||
},
|
||||
{ name: 'pageIndex', type: 'text', default: '{{1}}' },
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'Set variable',
|
||||
id: 'set-custom-variable',
|
||||
options: [
|
||||
{ name: 'key', type: 'code', default: '' },
|
||||
{ name: 'value', type: 'code', default: '' },
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'Unset all variables',
|
||||
id: 'unset-all-custom-variables',
|
||||
},
|
||||
{
|
||||
name: 'Unset variable',
|
||||
id: 'unset-custom-variable',
|
||||
options: [{ name: 'key', type: 'code', default: '' }],
|
||||
group: 'control-component',
|
||||
},
|
||||
{
|
||||
name: 'Switch page',
|
||||
id: 'switch-page',
|
||||
options: [{ name: 'page', type: 'text', default: '' }],
|
||||
group: 'navigation',
|
||||
},
|
||||
{
|
||||
name: 'Go to app',
|
||||
id: 'go-to-app',
|
||||
options: [
|
||||
{ name: 'app', type: 'text', default: '' },
|
||||
{ name: 'queryParams', type: 'code', default: '[]' },
|
||||
],
|
||||
group: 'navigation',
|
||||
},
|
||||
{
|
||||
name: 'Open webpage',
|
||||
id: 'open-webpage',
|
||||
options: [{ name: 'url', type: 'text', default: 'https://example.com' }],
|
||||
group: 'navigation',
|
||||
},
|
||||
{
|
||||
name: 'Set page variable',
|
||||
|
|
@ -99,10 +73,7 @@ export const ActionTypes = [
|
|||
{ name: 'key', type: 'code', default: '' },
|
||||
{ name: 'value', type: 'code', default: '' },
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'Unset all page variables',
|
||||
id: 'unset-all-page-variables',
|
||||
group: 'variable',
|
||||
},
|
||||
{
|
||||
name: 'Unset page variable',
|
||||
|
|
@ -111,14 +82,61 @@ export const ActionTypes = [
|
|||
{ name: 'key', type: 'code', default: '' },
|
||||
{ name: 'value', type: 'code', default: '' },
|
||||
],
|
||||
group: 'variable',
|
||||
},
|
||||
|
||||
{
|
||||
name: 'Control component',
|
||||
id: 'control-component',
|
||||
name: 'Unset all page variables',
|
||||
id: 'unset-all-page-variables',
|
||||
group: 'variable',
|
||||
},
|
||||
{
|
||||
name: 'Set variable',
|
||||
id: 'set-custom-variable',
|
||||
options: [
|
||||
{ name: 'component', type: 'text', default: '' },
|
||||
{ name: 'action', type: 'text', default: '' },
|
||||
{ name: 'key', type: 'code', default: '' },
|
||||
{ name: 'value', type: 'code', default: '' },
|
||||
],
|
||||
group: 'variable',
|
||||
},
|
||||
{
|
||||
name: 'Unset variable',
|
||||
id: 'unset-custom-variable',
|
||||
options: [{ name: 'key', type: 'code', default: '' }],
|
||||
group: 'variable',
|
||||
},
|
||||
{
|
||||
name: 'Unset all variables',
|
||||
id: 'unset-all-custom-variables',
|
||||
group: 'variable',
|
||||
},
|
||||
{
|
||||
name: 'Logout',
|
||||
id: 'logout',
|
||||
group: 'other',
|
||||
},
|
||||
{
|
||||
name: 'Generate file',
|
||||
id: 'generate-file',
|
||||
options: [
|
||||
{ name: 'fileType', type: 'text', default: '' },
|
||||
{ name: 'fileName', type: 'text', default: '' },
|
||||
{ name: 'data', type: 'code', default: '{{[]}}' },
|
||||
],
|
||||
group: 'other',
|
||||
},
|
||||
{
|
||||
name: 'Set local storage',
|
||||
id: 'set-localstorage-value',
|
||||
options: [
|
||||
{ name: 'key', type: 'code', default: '' },
|
||||
{ name: 'value', type: 'code', default: '' },
|
||||
],
|
||||
group: 'other',
|
||||
},
|
||||
{
|
||||
name: 'Copy to clipboard',
|
||||
id: 'copy-to-clipboard',
|
||||
options: [{ name: 'copy-to-clipboard', type: 'text', default: '' }],
|
||||
group: 'other',
|
||||
},
|
||||
];
|
||||
|
|
|
|||
|
|
@ -1,7 +1,8 @@
|
|||
/* eslint-disable react/no-string-refs */
|
||||
import React from 'react';
|
||||
import { Editor, EditorState, RichUtils, getDefaultKeyBinding, ContentState, convertFromHTML } from 'draft-js';
|
||||
import { Editor, EditorState, RichUtils, getDefaultKeyBinding } from 'draft-js';
|
||||
import 'draft-js/dist/Draft.css';
|
||||
import { stateFromHTML } from 'draft-js-import-html';
|
||||
import { stateToHTML } from 'draft-js-export-html';
|
||||
import Loader from '@/ToolJetUI/Loader/Loader';
|
||||
import DOMPurify from 'dompurify';
|
||||
|
|
@ -150,11 +151,8 @@ const InlineStyleControls = (props) => {
|
|||
class DraftEditor extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
const blocksFromHTML = convertFromHTML(DOMPurify.sanitize(this.props.defaultValue));
|
||||
this.state = {
|
||||
editorState: EditorState.createWithContent(
|
||||
ContentState.createFromBlockArray(blocksFromHTML.contentBlocks, blocksFromHTML.entityMap)
|
||||
),
|
||||
editorState: EditorState.createWithContent(stateFromHTML(DOMPurify.sanitize(this.props.defaultValue))),
|
||||
};
|
||||
|
||||
this.editorContainerRef = React.createRef();
|
||||
|
|
@ -173,6 +171,18 @@ class DraftEditor extends React.Component {
|
|||
this.toggleInlineStyle = this._toggleInlineStyle.bind(this);
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps) {
|
||||
if (prevProps.defaultValue !== this.props.defaultValue) {
|
||||
const newContentState = stateFromHTML(DOMPurify.sanitize(this.props.defaultValue));
|
||||
const newEditorState = EditorState.createWithContent(newContentState);
|
||||
const html = stateToHTML(newContentState);
|
||||
|
||||
this.props.handleChange(html);
|
||||
|
||||
this.setState({ editorState: newEditorState });
|
||||
}
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
//For resizing the editor container based on the height of rich text editor controls
|
||||
this.resizeObserver = new ResizeObserver(() => {
|
||||
|
|
@ -193,11 +203,7 @@ class DraftEditor extends React.Component {
|
|||
isVisible: this.props.isVisible,
|
||||
isLoading: this.props.isLoading,
|
||||
setValue: async (text) => {
|
||||
const blocksFromHTML = convertFromHTML(DOMPurify.sanitize(text));
|
||||
const newContentState = ContentState.createFromBlockArray(
|
||||
blocksFromHTML.contentBlocks,
|
||||
blocksFromHTML.entityMap
|
||||
);
|
||||
const newContentState = stateFromHTML(DOMPurify.sanitize(text));
|
||||
const newEditorState = EditorState.createWithContent(newContentState);
|
||||
const html = stateToHTML(newContentState);
|
||||
this.props.handleChange(html);
|
||||
|
|
@ -226,19 +232,6 @@ class DraftEditor extends React.Component {
|
|||
}
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps) {
|
||||
if (prevProps.defaultValue !== this.props.defaultValue) {
|
||||
const blocksFromHTML = convertFromHTML(DOMPurify.sanitize(this.props.defaultValue));
|
||||
const newContentState = ContentState.createFromBlockArray(blocksFromHTML.contentBlocks, blocksFromHTML.entityMap);
|
||||
const newEditorState = EditorState.createWithContent(newContentState);
|
||||
const html = stateToHTML(newContentState);
|
||||
|
||||
this.props.handleChange(html);
|
||||
|
||||
this.setState({ editorState: newEditorState });
|
||||
}
|
||||
}
|
||||
|
||||
_handleKeyCommand(command, editorState) {
|
||||
const newState = RichUtils.handleKeyCommand(editorState, command);
|
||||
if (newState) {
|
||||
|
|
|
|||
|
|
@ -191,7 +191,7 @@ export const RadioButtonV2 = ({
|
|||
data-cy={`label-${String(componentName).toLowerCase()} `}
|
||||
data-disabled={isDisabled}
|
||||
id={String(componentName)}
|
||||
className={cx('radio-button,', 'd-flex', {
|
||||
className={cx('radio-button', 'd-flex', {
|
||||
[alignment === 'top' &&
|
||||
((labelWidth != 0 && label?.length != 0) ||
|
||||
(labelAutoWidth && labelWidth == 0 && label && label?.length != 0))
|
||||
|
|
@ -279,7 +279,7 @@ export const RadioButtonV2 = ({
|
|||
</div>
|
||||
</div>
|
||||
<div
|
||||
className={`${isValid ? '' : visibility ? 'd-flex' : 'none'}`}
|
||||
className={`${isValid ? 'd-none' : visibility ? 'd-flex' : 'd-none'}`}
|
||||
style={{
|
||||
color: 'var(--status-error-strong)',
|
||||
justifyContent: direction === 'right' ? 'flex-start' : 'flex-end',
|
||||
|
|
|
|||
|
|
@ -1,53 +1,226 @@
|
|||
import React, { useEffect, useState } from 'react';
|
||||
import React, { useEffect, useState, useRef } from 'react';
|
||||
import { isExpectedDataType } from '@/_helpers/utils';
|
||||
import { ToolTip } from '@/_components/ToolTip';
|
||||
import './Steps.scss';
|
||||
|
||||
export const Steps = function Button({ properties, styles, fireEvent, setExposedVariable, height, darkMode, dataCy }) {
|
||||
const { stepsSelectable } = properties;
|
||||
const currentStep = isExpectedDataType(properties.currentStep, 'number');
|
||||
const steps = isExpectedDataType(properties.steps, 'array');
|
||||
const { color, theme, visibility, boxShadow } = styles;
|
||||
export const Steps = function Steps({ properties, styles, fireEvent, setExposedVariable, height, darkMode, dataCy }) {
|
||||
const { stepsSelectable, disabledState } = properties;
|
||||
const visibility = isExpectedDataType(properties.visibility, 'boolean');
|
||||
const currentStepId = isExpectedDataType(properties.currentStep, 'number');
|
||||
const isDynamicStepsEnabled = isExpectedDataType(properties.advanced, 'boolean');
|
||||
const steps = isDynamicStepsEnabled ? properties.schema : properties.steps;
|
||||
const { color, boxShadow } = styles;
|
||||
const textColor = darkMode && styles.textColor === '#000' ? '#fff' : styles.textColor;
|
||||
const [activeStep, setActiveStep] = useState(null);
|
||||
const { completedAccent, incompletedAccent, incompletedLabel, completedLabel, currentStepLabel } = styles;
|
||||
const [stepsArr, setStepsArr] = useState(steps);
|
||||
const [isVisible, setIsVisible] = useState(visibility);
|
||||
const [isDisabled, setIsDisabled] = useState(disabledState);
|
||||
const [activeStepId, setActiveStepId] = useState(currentStepId);
|
||||
const theme = properties.variant;
|
||||
const [progressBarWidth, setProgressBarWidth] = useState(0);
|
||||
const [containerPadding, setContainerPadding] = useState(0);
|
||||
const [containerWidth, setContainerWidth] = useState(0);
|
||||
const [filteredSteps, setFilteredSteps] = useState([]);
|
||||
const firstLabelRef = useRef(null);
|
||||
const lastLabelRef = useRef(null);
|
||||
const containerRef = useRef(null);
|
||||
|
||||
const currentStepIndex = filteredSteps.findIndex((step) => step.id == activeStepId);
|
||||
|
||||
useEffect(() => {
|
||||
const sanitizedSteps = JSON.parse(JSON.stringify(steps || [])).map((step) => ({
|
||||
...step,
|
||||
visible: 'visible' in step ? step.visible : true,
|
||||
disabled: 'disabled' in step ? step.disabled : false,
|
||||
}));
|
||||
const newFilteredSteps = (sanitizedSteps || []).filter((step) => step.visible);
|
||||
setFilteredSteps(newFilteredSteps);
|
||||
setStepsArr(sanitizedSteps);
|
||||
}, [JSON.stringify(steps)]);
|
||||
|
||||
// Common function to calculate progress bar width and label padding
|
||||
const calculateProgressBarWidth = () => {
|
||||
if (!containerRef.current || theme !== 'titles') return;
|
||||
|
||||
const containerWidth = containerRef.current.offsetWidth;
|
||||
setContainerWidth(containerWidth);
|
||||
|
||||
const stepWidth = 20; // width of dot + padding
|
||||
const totalStepsWidth = filteredSteps.length * stepWidth;
|
||||
const totalProgressBars = filteredSteps.length - 1;
|
||||
|
||||
if (filteredSteps.length === 1) {
|
||||
setProgressBarWidth(containerWidth);
|
||||
setContainerPadding(0); // No padding needed for single step
|
||||
return;
|
||||
}
|
||||
|
||||
// Calculate progress bar width
|
||||
const progressBarWidth = (containerWidth - totalStepsWidth) / totalProgressBars;
|
||||
setProgressBarWidth(Math.min(progressBarWidth, (containerWidth - totalStepsWidth) / filteredSteps.length));
|
||||
|
||||
// Calculate container padding
|
||||
if (firstLabelRef.current && lastLabelRef.current) {
|
||||
const labelWidth = (containerWidth - (filteredSteps.length - 1) - 4) / filteredSteps.length;
|
||||
|
||||
const firstLabelWidth = firstLabelRef.current.offsetWidth;
|
||||
const lastLabelWidth = lastLabelRef.current.offsetWidth;
|
||||
const maxLabelWidth = Math.max(firstLabelWidth, lastLabelWidth);
|
||||
|
||||
const calculatedPadding = (maxLabelWidth / 2) - 1;
|
||||
setContainerPadding(Math.max(2, calculatedPadding)); // Ensure minimum padding of 2px
|
||||
}
|
||||
};
|
||||
|
||||
// Add resize observer to track container width and calculate progress bar width
|
||||
useEffect(() => {
|
||||
calculateProgressBarWidth();
|
||||
if (theme !== 'titles') return;
|
||||
|
||||
const resizeObserver = new ResizeObserver((entries) => {
|
||||
for (const entry of entries) {
|
||||
calculateProgressBarWidth();
|
||||
}
|
||||
});
|
||||
|
||||
if (containerRef.current) {
|
||||
resizeObserver.observe(containerRef.current);
|
||||
}
|
||||
|
||||
return () => resizeObserver.disconnect();
|
||||
}, [theme, JSON.stringify(steps), filteredSteps]);
|
||||
// Dynamic styles for theming
|
||||
const dynamicStyle = {
|
||||
'--bgColor': styles.color,
|
||||
'--textColor': textColor,
|
||||
};
|
||||
const activeStepHandler = (id) => {
|
||||
const active = steps.filter((item) => item.id == id);
|
||||
setExposedVariable('currentStepId', active[0].id);
|
||||
fireEvent('onSelect');
|
||||
setActiveStep(active[0].id);
|
||||
'--completedAccent': completedAccent === '#4368E3' ? 'var(--primary-brand)' : completedAccent,
|
||||
'--incompletedAccent': incompletedAccent === '#E4E7EB' ? 'var(--surfaces-surface-03)' : incompletedAccent,
|
||||
'--incompletedLabel': incompletedLabel === '#1B1F24' ? 'var(--text-primary)' : incompletedLabel,
|
||||
'--completedLabel': completedLabel === '#1B1F24' ? 'var(--text-primary)' : completedLabel,
|
||||
'--currentStepLabel': currentStepLabel === '#1B1F24' ? 'var(--text-primary)' : currentStepLabel,
|
||||
};
|
||||
|
||||
// Step click handler
|
||||
const handleStepClick = (id) => {
|
||||
const step = filteredSteps.find((item) => item.id == id);
|
||||
if (step && !step.disabled && !isDisabled) {
|
||||
setActiveStepId(step.id);
|
||||
fireEvent('onSelect');
|
||||
}
|
||||
};
|
||||
|
||||
// Expose variables and methods
|
||||
useEffect(() => {
|
||||
setActiveStep(currentStep);
|
||||
setExposedVariable('currentStepId', currentStep);
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [currentStep]);
|
||||
setExposedVariable('isVisible', isVisible);
|
||||
setExposedVariable('isDisabled', isDisabled);
|
||||
setExposedVariable('currentStepId', activeStepId);
|
||||
setExposedVariable('steps', stepsArr);
|
||||
|
||||
setExposedVariable('setStepVisible', (stepId, visibility) => {
|
||||
setStepsArr((prev) => {
|
||||
const updatedSteps = prev.map((item) =>
|
||||
item.id == stepId ? { ...item, visible: visibility } : item
|
||||
);
|
||||
setExposedVariable('steps', updatedSteps);
|
||||
return updatedSteps;
|
||||
});
|
||||
});
|
||||
|
||||
setExposedVariable('setStepDisable', (stepId, disabled) => {
|
||||
setStepsArr((prev) => {
|
||||
const updatedSteps = prev.map((item) =>
|
||||
item.id == stepId ? { ...item, disabled: disabled } : item
|
||||
);
|
||||
setExposedVariable('steps', updatedSteps);
|
||||
return updatedSteps;
|
||||
});
|
||||
});
|
||||
|
||||
setExposedVariable('resetSteps', () => {
|
||||
setActiveStepId(stepsArr.filter((step) => step.visible)?.[0]?.id);
|
||||
});
|
||||
|
||||
setExposedVariable('setStep', (stepId) => {
|
||||
if (!disabledState) setActiveStepId(stepId);
|
||||
});
|
||||
setExposedVariable('setVisibility', (visibility) => setIsVisible(visibility));
|
||||
setExposedVariable('setDisable', (disabled) => setIsDisabled(disabled));
|
||||
}, [isVisible, isDisabled, activeStepId, stepsArr, disabledState]);
|
||||
|
||||
// Update state from props
|
||||
useEffect(() => setIsVisible(visibility), [visibility]);
|
||||
useEffect(() => setIsDisabled(disabledState), [disabledState]);
|
||||
useEffect(() => setActiveStepId(currentStepId), [currentStepId]);
|
||||
|
||||
if (!isVisible) return null;
|
||||
|
||||
return (
|
||||
visibility && (
|
||||
<div
|
||||
className={`steps ${theme == 'numbers' && 'steps-counter '}`}
|
||||
style={{ color: textColor, height, boxShadow }}
|
||||
data-cy={dataCy}
|
||||
>
|
||||
{steps?.map((item) => (
|
||||
<a
|
||||
key={item.id}
|
||||
className={`step-item ${item.id == activeStep && 'active'} ${!stepsSelectable && 'step-item-disabled'} ${
|
||||
color && `step-${color}`
|
||||
}`}
|
||||
data-bs-toggle="tooltip"
|
||||
title={item?.tooltip}
|
||||
onClick={() => stepsSelectable && activeStepHandler(item.id)}
|
||||
style={dynamicStyle}
|
||||
>
|
||||
{theme == 'titles' && item.name}
|
||||
</a>
|
||||
))}
|
||||
<div
|
||||
ref={containerRef}
|
||||
className={`steps-container ${isDisabled ? 'disabled' : ''} ${filteredSteps.length === 1 ? 'single-step' : ''}`}
|
||||
style={{
|
||||
height,
|
||||
boxShadow,
|
||||
padding: theme === 'titles' ? `0 ${containerPadding}px` : 2,
|
||||
paddingTop: theme === 'plain' ? `3px` : theme === 'numbers' ? `2px` : 0,
|
||||
...dynamicStyle
|
||||
}}
|
||||
data-cy={dataCy}
|
||||
>
|
||||
<div className={`progress-line-container ${filteredSteps.length === 1 ? 'single-step' : ''}`}>
|
||||
{filteredSteps.map((step, index) => {
|
||||
const isStepDisabled = step.disabled;
|
||||
const isCompleted = index < currentStepIndex;
|
||||
const isActive = index === currentStepIndex;
|
||||
const isUpcoming = index > currentStepIndex;
|
||||
const isFirstStep = index === 0;
|
||||
const isLastStep = index === filteredSteps.length - 1;
|
||||
|
||||
return (
|
||||
<React.Fragment key={index}> {/* using index as key to avoid issues due to duplicate step ids */}
|
||||
<ToolTip
|
||||
show={!step.disabled && !isDisabled && step.tooltip}
|
||||
message={step.tooltip || ''}
|
||||
>
|
||||
<div
|
||||
onClick={() => stepsSelectable && handleStepClick(step.id)}
|
||||
className={`milestone ${theme === 'numbers' ? 'numbers' : ''} ${isDisabled || isStepDisabled ? 'disabled' : ''
|
||||
} ${isCompleted ? 'completed' : isActive ? 'active' : 'incomplete'}`}
|
||||
>
|
||||
{theme === 'numbers' ? (
|
||||
index + 1
|
||||
) : (
|
||||
<>
|
||||
<div
|
||||
className={`dot ${isCompleted ? 'completed' : isActive ? 'active' : 'incomplete'}`}
|
||||
style={{
|
||||
border: `2px solid ${isCompleted ? completedAccent : isActive ? completedAccent : incompletedAccent}`,
|
||||
backgroundColor: isActive ? 'transparent' : (isCompleted ? completedAccent : incompletedAccent)
|
||||
}}
|
||||
/>
|
||||
{theme === 'titles' && (
|
||||
<div
|
||||
ref={isFirstStep ? firstLabelRef : isLastStep ? lastLabelRef : null}
|
||||
className={`label ${isCompleted ? 'completed' : isActive ? 'active' : 'incomplete'}`}
|
||||
style={{ maxWidth: `${progressBarWidth}px` }}
|
||||
>
|
||||
{step.name}
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</ToolTip>
|
||||
|
||||
{index < filteredSteps.length - 1 && (
|
||||
<div
|
||||
className={`step-connector ${isCompleted ? 'completed' : 'incomplete'}`}
|
||||
/>
|
||||
)}
|
||||
</React.Fragment>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
)
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
132
frontend/src/Editor/Components/Steps.scss
Normal file
132
frontend/src/Editor/Components/Steps.scss
Normal file
|
|
@ -0,0 +1,132 @@
|
|||
.steps-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: 100%;
|
||||
opacity: 1;
|
||||
|
||||
&.disabled {
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
&.single-step {
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.progress-line-container {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 2px;
|
||||
|
||||
&.single-step {
|
||||
width: auto;
|
||||
}
|
||||
}
|
||||
|
||||
.milestone {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
position: relative;
|
||||
overflow: visible;
|
||||
transition: all 0.3s ease;
|
||||
cursor: pointer;
|
||||
|
||||
&.numbers {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
border-radius: 50%;
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
box-sizing: content-box;
|
||||
|
||||
&.completed {
|
||||
background-color: var(--completedAccent);
|
||||
color: var(--completedLabel);
|
||||
border: 2px solid var(--completedAccent);
|
||||
}
|
||||
|
||||
&.active {
|
||||
color: var(--currentStepLabel);
|
||||
border: 2px solid var(--completedAccent);
|
||||
}
|
||||
|
||||
&.incomplete {
|
||||
background-color: var(--incompletedAccent);
|
||||
color: var(--incompletedLabel);
|
||||
border: 2px solid var(--incompletedAccent);
|
||||
}
|
||||
}
|
||||
|
||||
&.disabled {
|
||||
opacity: 0.5;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
}
|
||||
|
||||
.dot {
|
||||
width: 0.5rem;
|
||||
height: 0.5rem;
|
||||
border-radius: 50%;
|
||||
transition: all 0.3s ease;
|
||||
box-sizing: content-box;
|
||||
|
||||
&.completed {
|
||||
background-color: var(--completedAccent);
|
||||
border: 2px solid var(--completedAccent);
|
||||
}
|
||||
|
||||
&.active {
|
||||
background-color: white;
|
||||
border: 2px solid var(--primary-brand);
|
||||
}
|
||||
|
||||
&.incomplete {
|
||||
background-color: var(--incompletedAccent);
|
||||
border: 2px solid var(--incompletedAccent);
|
||||
}
|
||||
}
|
||||
|
||||
.label {
|
||||
font-size: 12px;
|
||||
font-style: normal;
|
||||
font-weight: 500;
|
||||
line-height: 18px;
|
||||
text-align: center;
|
||||
margin-top: 2px;
|
||||
position: absolute;
|
||||
top: 100%;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
width: max-content;
|
||||
|
||||
&.completed {
|
||||
color: var(--completedLabel);
|
||||
}
|
||||
|
||||
&.active {
|
||||
color: var(--completedLabel);
|
||||
}
|
||||
|
||||
&.incomplete {
|
||||
color: var(--incompletedLabel);
|
||||
}
|
||||
}
|
||||
|
||||
.step-connector {
|
||||
flex-grow: 1;
|
||||
height: 2px;
|
||||
align-self: center;
|
||||
transition: all 0.3s ease;
|
||||
|
||||
&.completed {
|
||||
background-color: var(--completedAccent);
|
||||
}
|
||||
|
||||
&.incomplete {
|
||||
background-color: var(--incompletedAccent);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -3,8 +3,8 @@ export const containerConfig = {
|
|||
displayName: 'Container',
|
||||
description: 'Group components',
|
||||
defaultSize: {
|
||||
width: 10,
|
||||
height: 200,
|
||||
width: 13,
|
||||
height: 480,
|
||||
},
|
||||
component: 'Container',
|
||||
others: {
|
||||
|
|
|
|||
|
|
@ -47,7 +47,7 @@ import { verticalDividerConfig } from './verticalDivider';
|
|||
import { customComponentConfig } from './customComponent';
|
||||
import { buttonGroupConfig } from './buttonGroup';
|
||||
import { pdfConfig } from './pdf';
|
||||
import { stepsConfig } from './steps';
|
||||
// import { stepsConfig } from './steps';
|
||||
import { kanbanConfig } from './kanban';
|
||||
import { colorPickerConfig } from './colorPicker';
|
||||
import { treeSelectConfig } from './treeSelect';
|
||||
|
|
@ -106,7 +106,7 @@ export {
|
|||
customComponentConfig,
|
||||
buttonGroupConfig,
|
||||
pdfConfig,
|
||||
stepsConfig,
|
||||
// stepsConfig,
|
||||
kanbanConfig,
|
||||
kanbanBoardConfig, //!Depreciated
|
||||
colorPickerConfig,
|
||||
|
|
|
|||
|
|
@ -1,108 +0,0 @@
|
|||
export const stepsConfig = {
|
||||
name: 'Steps',
|
||||
displayName: 'Steps',
|
||||
description: 'Step-by-step navigation aid',
|
||||
component: 'Steps',
|
||||
properties: {
|
||||
steps: {
|
||||
type: 'code',
|
||||
displayName: 'Steps',
|
||||
validation: {
|
||||
schema: {
|
||||
type: 'array',
|
||||
element: { type: 'object', object: { id: { type: 'number' } } },
|
||||
},
|
||||
defaultValue: `[{ name: 'step 1'}, {name: 'step 2'}]`,
|
||||
},
|
||||
},
|
||||
currentStep: {
|
||||
type: 'code',
|
||||
displayName: 'Current step',
|
||||
validation: {
|
||||
schema: { type: 'number' },
|
||||
defaultValue: 1,
|
||||
},
|
||||
},
|
||||
stepsSelectable: {
|
||||
type: 'toggle',
|
||||
displayName: 'Steps selectable',
|
||||
validation: {
|
||||
schema: { type: 'boolean' },
|
||||
defaultValue: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
defaultSize: {
|
||||
width: 22,
|
||||
height: 38,
|
||||
},
|
||||
others: {
|
||||
showOnDesktop: { type: 'toggle', displayName: 'Show on desktop' },
|
||||
showOnMobile: { type: 'toggle', displayName: 'Show on mobile' },
|
||||
},
|
||||
events: {
|
||||
onSelect: { displayName: 'On select' },
|
||||
},
|
||||
styles: {
|
||||
color: {
|
||||
type: 'color',
|
||||
displayName: 'Color',
|
||||
validation: {
|
||||
schema: { type: 'string' },
|
||||
defaultValue: '#000000',
|
||||
},
|
||||
},
|
||||
textColor: {
|
||||
type: 'color',
|
||||
displayName: 'Text color',
|
||||
validation: {
|
||||
schema: { type: 'string' },
|
||||
defaultValue: '#000000',
|
||||
},
|
||||
},
|
||||
theme: {
|
||||
type: 'select',
|
||||
displayName: 'Theme',
|
||||
options: [
|
||||
{ name: 'titles', value: 'titles' },
|
||||
{ name: 'numbers', value: 'numbers' },
|
||||
{ name: 'plain', value: 'plain' },
|
||||
],
|
||||
validation: {
|
||||
schema: { type: 'string' },
|
||||
defaultValue: 'titles',
|
||||
},
|
||||
},
|
||||
visibility: {
|
||||
type: 'toggle',
|
||||
displayName: 'Visibility',
|
||||
validation: {
|
||||
schema: { type: 'boolean' },
|
||||
defaultValue: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
exposedVariables: {
|
||||
currentStepId: '3',
|
||||
},
|
||||
definition: {
|
||||
others: {
|
||||
showOnDesktop: { value: '{{true}}' },
|
||||
showOnMobile: { value: '{{false}}' },
|
||||
},
|
||||
properties: {
|
||||
steps: {
|
||||
value: `{{ [{ name: 'step 1', tooltip: 'some tooltip', id: 1},{ name: 'step 2', tooltip: 'some tooltip', id: 2},{ name: 'step 3', tooltip: 'some tooltip', id: 3},{ name: 'step 4', tooltip: 'some tooltip', id: 4},{ name: 'step 5', tooltip: 'some tooltip', id: 5}]}}`,
|
||||
},
|
||||
currentStep: { value: '{{3}}' },
|
||||
stepsSelectable: { value: true },
|
||||
},
|
||||
events: [],
|
||||
styles: {
|
||||
visibility: { value: '{{true}}' },
|
||||
theme: { value: 'titles' },
|
||||
color: { value: '' },
|
||||
textColor: { value: '' },
|
||||
},
|
||||
},
|
||||
};
|
||||
|
|
@ -7675,29 +7675,33 @@ fieldset:disabled .btn {
|
|||
}
|
||||
|
||||
.rounded {
|
||||
border-radius: 4px ;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.rounded-0 {
|
||||
border-radius: 0 !important
|
||||
}
|
||||
|
||||
.rounded-top-left{
|
||||
.rounded-top-left {
|
||||
border-top-left-radius: 4px;
|
||||
}
|
||||
|
||||
.rounded-top-left-0{
|
||||
.rounded-top-left-0 {
|
||||
border-top-left-radius: 0 !important;
|
||||
}
|
||||
.rounded-top-right-0{
|
||||
|
||||
.rounded-top-right-0 {
|
||||
border-top-right-radius: 0 !important;
|
||||
}
|
||||
.rounded-bottom-left-0{
|
||||
|
||||
.rounded-bottom-left-0 {
|
||||
border-bottom-left-radius: 0 !important;
|
||||
}
|
||||
.rounded-bottom-right-0{
|
||||
|
||||
.rounded-bottom-right-0 {
|
||||
border-bottom-right-radius: 0 !important;
|
||||
}
|
||||
|
||||
.rounded-1 {
|
||||
border-radius: 2px !important
|
||||
}
|
||||
|
|
@ -17484,8 +17488,8 @@ a.step-item:hover {
|
|||
|
||||
.step-item:not(:first-child):after {
|
||||
position: absolute;
|
||||
left: -50%;
|
||||
width: 100%;
|
||||
left: calc(-50% + 8px);
|
||||
width: calc(100% - 16px);
|
||||
content: "";
|
||||
transform: translateY(-50%)
|
||||
}
|
||||
|
|
@ -17498,13 +17502,25 @@ a.step-item:hover {
|
|||
box-sizing: content-box;
|
||||
display: block;
|
||||
content: "";
|
||||
border: 2px solid #fff;
|
||||
border: 2px solid transparent;
|
||||
border-radius: 50%;
|
||||
transform: translateX(-50%)
|
||||
}
|
||||
|
||||
.step-item.active {
|
||||
font-weight: 600
|
||||
.steps.steps-counter {
|
||||
.step-item:not(:first-child):after {
|
||||
left: calc(-50% + 16px) !important;
|
||||
width: calc(100% - 32px) !important;
|
||||
}
|
||||
}
|
||||
.steps-counter .step-item:before {
|
||||
color:var(--completedLabel) !important;
|
||||
}
|
||||
.steps .step-item.active:before{
|
||||
color : var(--currentStepLabel) !important;
|
||||
}
|
||||
.step-item {
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.step-item.active:before {
|
||||
|
|
@ -17521,7 +17537,7 @@ a.step-item:hover {
|
|||
}
|
||||
|
||||
.step-item.active~.step-item:before {
|
||||
color: #656d77 !important
|
||||
color: var(--incompletedLabel) !important
|
||||
}
|
||||
|
||||
.steps-counter {
|
||||
|
|
@ -17549,7 +17565,8 @@ a.step-item:hover {
|
|||
.steps-counter .step-item:before {
|
||||
font-size: .75rem;
|
||||
line-height: 1.5rem;
|
||||
content: counter(steps)
|
||||
content: counter(steps);
|
||||
font-weight: 500 !important;
|
||||
}
|
||||
|
||||
.steps-counter .step-item.active~.step-item:before {
|
||||
|
|
@ -19156,4 +19173,4 @@ img {
|
|||
background: #1f2936;
|
||||
border-color: #dadcde
|
||||
}
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load diff
23
frontend/src/_ui/Icon/solidIcons/Moon.jsx
Normal file
23
frontend/src/_ui/Icon/solidIcons/Moon.jsx
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
import React from 'react';
|
||||
|
||||
const Moon = ({ fill = '#6A727C', width = '24', className = '', viewBox = '0 0 24 24' }) => {
|
||||
return (
|
||||
<svg
|
||||
width={width}
|
||||
height={width}
|
||||
viewBox={viewBox}
|
||||
className={className}
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
d="M16.107 6.64283C15.6139 6.64283 15.2142 6.24309 15.2142 5.74998V5.21426H14.6785C14.1854 5.21426 13.7856 4.81452 13.7856 4.32141C13.7856 3.8283 14.1854 3.42856 14.6785 3.42856H15.2142V2.89285C15.2142 2.39974 15.6139 2 16.107 2C16.6002 2 16.9999 2.39974 16.9999 2.89285V3.42856H17.5356C18.0288 3.42856 18.4285 3.8283 18.4285 4.32141C18.4285 4.81452 18.0288 5.21426 17.5356 5.21426H16.9999V5.74998C16.9999 6.24309 16.6002 6.64283 16.107 6.64283ZM18.7856 11.4642C18.7856 11.9573 19.1853 12.3571 19.6785 12.3571C20.1716 12.3571 20.5713 11.9573 20.5713 11.4642V10.9285H21.107C21.6002 10.9285 21.9999 10.5288 21.9999 10.0357C21.9999 9.54255 21.6002 9.14281 21.107 9.14281H20.5713V8.6071C20.5713 8.11399 20.1716 7.71425 19.6785 7.71425C19.1853 7.71425 18.7856 8.11399 18.7856 8.6071V9.14281H18.2499C17.7568 9.14281 17.357 9.54255 17.357 10.0357C17.357 10.5288 17.7568 10.9285 18.2499 10.9285H18.7856V11.4642ZM6.07263 6.07373C7.96356 4.98201 10.1187 4.74735 12.1075 5.23996C12.7387 5.39633 12.9939 5.95217 12.9996 6.40209C13.0051 6.8352 12.7967 7.33059 12.3328 7.59845C10.4674 8.67541 9.82636 11.1468 10.9769 13.1396C12.1274 15.1324 14.5883 15.8129 16.4536 14.736C16.9176 14.4681 17.4508 14.5354 17.8232 14.7567C18.2099 14.9866 18.5637 15.4855 18.3835 16.1103C17.8158 18.0789 16.5349 19.828 14.644 20.9197C10.6598 23.2201 5.53735 21.7172 3.18302 17.6395C0.828705 13.5616 2.08841 8.37403 6.07263 6.07373Z"
|
||||
fill={fill}
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
};
|
||||
|
||||
export default Moon;
|
||||
|
|
@ -234,6 +234,7 @@ import NewTabSmall from './NewTabSmall.jsx';
|
|||
import Code from './Code.jsx';
|
||||
import WorkflowV3 from './WorkflowV3.jsx';
|
||||
import WorkspaceV3 from './WorkspaceV3.jsx';
|
||||
import Moon from './Moon.jsx';
|
||||
|
||||
const Icon = (props) => {
|
||||
switch (props.name) {
|
||||
|
|
@ -707,6 +708,8 @@ const Icon = (props) => {
|
|||
return <AICrown {...props} />;
|
||||
case 'play01':
|
||||
return <Play01 {...props} />;
|
||||
case 'moon':
|
||||
return <Moon {...props} />;
|
||||
default:
|
||||
return <Apps {...props} />;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@ export const SelectComponent = ({ options = [], value, onChange, closeMenuOnSele
|
|||
isDisabled = false,
|
||||
borderRadius,
|
||||
openMenuOnFocus = false,
|
||||
customClassPrefix = '',
|
||||
} = restProps;
|
||||
|
||||
const customStyles = useCustomStyles ? styles : defaultStyles(isDarkMode, width, height, styles, borderRadius);
|
||||
|
|
@ -74,7 +75,7 @@ export const SelectComponent = ({ options = [], value, onChange, closeMenuOnSele
|
|||
maxMenuHeight={maxMenuHeight}
|
||||
menuPortalTarget={useMenuPortal ? document.body : menuPortalTarget}
|
||||
closeMenuOnSelect={closeMenuOnSelect ?? true}
|
||||
classNamePrefix={`${isDarkMode && 'dark-theme'} ${'react-select'}`}
|
||||
classNamePrefix={`${customClassPrefix} ${isDarkMode && 'dark-theme'} ${'react-select'}`}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ import { Input } from '../Input';
|
|||
import { HelperMessage, ValidationMessage } from '../InputUtils/InputUtils';
|
||||
import Tooltip from '../../Tooltip/Tooltip';
|
||||
|
||||
const EditableTitleInput = ({ size, disabled, helperText, onChange, ...restProps }) => {
|
||||
const EditableTitleInput = ({ size, disabled, helperText, onChange: change, readOnly, placeholder, ...restProps }) => {
|
||||
const inputRef = useRef(null);
|
||||
const [tooltipWidth, setTooltipWidth] = useState('auto');
|
||||
const [isValid, setIsValid] = useState(null);
|
||||
|
|
@ -17,7 +17,7 @@ const EditableTitleInput = ({ size, disabled, helperText, onChange, ...restProps
|
|||
setIsValid(validateObj.valid);
|
||||
setMessage(validateObj.message);
|
||||
}
|
||||
onChange(e, validateObj);
|
||||
change(e, validateObj);
|
||||
};
|
||||
|
||||
const inputStyle = `tw-border-transparent hover:tw-border-border-default tw-font-medium tw-pl-[12px] tw-pr-[12px] ${
|
||||
|
|
@ -35,7 +35,14 @@ const EditableTitleInput = ({ size, disabled, helperText, onChange, ...restProps
|
|||
return (
|
||||
<div className="tw-relative">
|
||||
<div className="tw-peer tw-relative" ref={inputRef}>
|
||||
<Input size={size} disabled={disabled} onChange={handleChange} {...restProps} className={inputStyle} />
|
||||
<Input
|
||||
size={size}
|
||||
disabled={disabled}
|
||||
placeholder={disabled && readOnly ? readOnly : placeholder}
|
||||
onChange={handleChange}
|
||||
{...restProps}
|
||||
className={inputStyle}
|
||||
/>
|
||||
<SolidIcon
|
||||
name="editable"
|
||||
width="16px"
|
||||
|
|
|
|||
|
|
@ -33,7 +33,7 @@ const Input = React.forwardRef(({ className, size, type, multiline, response, ro
|
|||
type={isPasswordField && isPasswordVisible ? 'text' : type}
|
||||
className={cn(
|
||||
inputVariants({ size }),
|
||||
`tw-relative tw-peer tw-flex tw-text-[12px]/[18px] tw-w-full tw-rounded-[8px] tw-border-[1px] tw-border-solid tw-bg-background-surface-layer-01 tw-py-[7px] tw-text-text-default focus-visible:tw-ring-[1px] focus-visible:tw-ring-offset-[1px] focus-visible:tw-ring-border-accent-strong focus-visible:tw-ring-offset-border-accent-strong focus-visible:tw-border-transparent disabled:tw-cursor-not-allowed ${props.styles}`,
|
||||
`tw-peer tw-flex tw-text-[12px]/[18px] tw-w-full tw-rounded-[8px] tw-border-[1px] tw-border-solid tw-bg-background-surface-layer-01 tw-py-[7px] tw-text-text-default focus-visible:tw-ring-[1px] focus-visible:tw-ring-offset-[1px] focus-visible:tw-ring-border-accent-strong focus-visible:tw-ring-offset-border-accent-strong focus-visible:tw-border-transparent disabled:tw-cursor-not-allowed ${props.styles}`,
|
||||
className
|
||||
)}
|
||||
ref={ref}
|
||||
|
|
|
|||
81
server/data-migrations/1742369436314-StepsV2Migration.ts
Normal file
81
server/data-migrations/1742369436314-StepsV2Migration.ts
Normal file
|
|
@ -0,0 +1,81 @@
|
|||
import { Component } from '@entities/component.entity';
|
||||
import { EntityManager, MigrationInterface, QueryRunner } from 'typeorm';
|
||||
import { processDataInBatches } from '@helpers/migration.helper';
|
||||
|
||||
export class StepsV2Migration1742369436314 implements MigrationInterface {
|
||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||
const componentTypes = ['Steps'];
|
||||
const batchSize = 100;
|
||||
const entityManager = queryRunner.manager;
|
||||
|
||||
for (const componentType of componentTypes) {
|
||||
await processDataInBatches(
|
||||
entityManager,
|
||||
async (entityManager: EntityManager) => {
|
||||
return await entityManager.find(Component, {
|
||||
where: { type: componentType },
|
||||
order: { createdAt: 'ASC' },
|
||||
});
|
||||
},
|
||||
async (entityManager: EntityManager, components: Component[]) => {
|
||||
await this.processUpdates(entityManager, components);
|
||||
},
|
||||
batchSize
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
public async down(queryRunner: QueryRunner): Promise<void> {}
|
||||
|
||||
private async processUpdates(entityManager, components) {
|
||||
for (const component of components) {
|
||||
const properties = component.properties;
|
||||
const styles = component.styles;
|
||||
const general = component.general;
|
||||
const generalStyles = component.generalStyles;
|
||||
const validation = component.validation;
|
||||
|
||||
if (styles.visibility) {
|
||||
properties.visibility = styles.visibility;
|
||||
delete styles.visibility;
|
||||
}
|
||||
if (styles.theme) {
|
||||
properties['variant'] = styles.theme;
|
||||
delete styles.theme;
|
||||
}
|
||||
if (styles.color) {
|
||||
styles['completedAccent'] = styles.color;
|
||||
}
|
||||
delete styles.color;
|
||||
if (styles.textColor) {
|
||||
styles['completedLabel'] = styles.textColor;
|
||||
styles['incompletedLabel'] = styles.textColor;
|
||||
styles['currentStepLabel'] = styles.textColor;
|
||||
}
|
||||
delete styles.textColor;
|
||||
if (properties.steps) {
|
||||
properties['schema'] = properties.steps;
|
||||
delete properties.steps;
|
||||
properties['advanced'] = { value: '{{true}}' };
|
||||
}
|
||||
|
||||
// if (properties.stepsSelectable) {
|
||||
// properties.disabledState = styles.disabledState;
|
||||
// delete styles.disabledState;
|
||||
// }
|
||||
|
||||
// if (generalStyles?.boxShadow) {
|
||||
// styles.boxShadow = generalStyles?.boxShadow;
|
||||
// delete generalStyles?.boxShadow;
|
||||
// }
|
||||
|
||||
await entityManager.update(Component, component.id, {
|
||||
properties,
|
||||
styles,
|
||||
general,
|
||||
generalStyles,
|
||||
validation,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1 +1 @@
|
|||
Subproject commit 69bdefb1f3f1d35bd6e7231e50799ff10a77a60f
|
||||
Subproject commit 8155e72286b253042ede33cab64a5099d441ff44
|
||||
|
|
@ -95,7 +95,9 @@ export class ComponentsService implements IComponentsService {
|
|||
if (componentData.type === 'Table' && _.isArray(objValue)) {
|
||||
return srcValue;
|
||||
} else if (
|
||||
(componentData.type === 'DropdownV2' || componentData.type === 'MultiselectV2') &&
|
||||
(componentData.type === 'DropdownV2' ||
|
||||
componentData.type === 'MultiselectV2' ||
|
||||
componentData.type === 'Steps') &&
|
||||
_.isArray(objValue)
|
||||
) {
|
||||
return _.isArray(srcValue) ? srcValue : Object.values(srcValue);
|
||||
|
|
|
|||
|
|
@ -3,8 +3,8 @@ export const containerConfig = {
|
|||
displayName: 'Container',
|
||||
description: 'Group components',
|
||||
defaultSize: {
|
||||
width: 10,
|
||||
height: 200,
|
||||
width: 13,
|
||||
height: 480,
|
||||
},
|
||||
component: 'Container',
|
||||
others: {
|
||||
|
|
|
|||
|
|
@ -4,25 +4,38 @@ export const stepsConfig = {
|
|||
description: 'Step-by-step navigation aid',
|
||||
component: 'Steps',
|
||||
properties: {
|
||||
variant: {
|
||||
type: 'switch',
|
||||
displayName: 'Variant',
|
||||
validation: { schema: { type: 'string' }, defaultValue: 'titles' },
|
||||
options: [
|
||||
{ displayName: 'Label', value: 'titles' },
|
||||
{ displayName: 'Number', value: 'numbers' },
|
||||
{ displayName: 'Plain', value: 'plain' },
|
||||
],
|
||||
accordian: 'label',
|
||||
},
|
||||
schema: {
|
||||
type: 'code',
|
||||
displayName: 'Schema',
|
||||
conditionallyRender: {
|
||||
key: 'advanced',
|
||||
value: true,
|
||||
},
|
||||
accordian: 'Options',
|
||||
},
|
||||
steps: {
|
||||
type: 'code',
|
||||
displayName: 'Steps',
|
||||
displayName: '',
|
||||
showLabel: false,
|
||||
validation: {
|
||||
schema: {
|
||||
type: 'array',
|
||||
element: { type: 'object', object: { id: { type: 'number' } } },
|
||||
element: { type: 'object' },
|
||||
},
|
||||
defaultValue: `[{ name: 'step 1'}, {name: 'step 2'}]`,
|
||||
},
|
||||
},
|
||||
currentStep: {
|
||||
type: 'code',
|
||||
displayName: 'Current step',
|
||||
validation: {
|
||||
schema: { type: 'number' },
|
||||
defaultValue: 1,
|
||||
},
|
||||
},
|
||||
stepsSelectable: {
|
||||
type: 'toggle',
|
||||
displayName: 'Steps selectable',
|
||||
|
|
@ -30,6 +43,36 @@ export const stepsConfig = {
|
|||
schema: { type: 'boolean' },
|
||||
defaultValue: false,
|
||||
},
|
||||
section: 'additionalActions',
|
||||
},
|
||||
disabledState: {
|
||||
type: 'toggle',
|
||||
displayName: 'Disable',
|
||||
validation: { schema: { type: 'boolean' } },
|
||||
section: 'additionalActions',
|
||||
},
|
||||
visibility: {
|
||||
type: 'toggle',
|
||||
displayName: 'Visibility',
|
||||
validation: { schema: { type: 'boolean' }, defaultValue: true },
|
||||
section: 'additionalActions',
|
||||
},
|
||||
advanced: {
|
||||
type: 'toggle',
|
||||
displayName: 'Dynamic options',
|
||||
validation: {
|
||||
schema: { type: 'boolean' },
|
||||
defaultValue: true,
|
||||
},
|
||||
accordian: 'Options',
|
||||
},
|
||||
currentStep: {
|
||||
type: 'code',
|
||||
displayName: 'Current step',
|
||||
validation: {
|
||||
schema: { type: 'number' },
|
||||
defaultValue: 1,
|
||||
},
|
||||
},
|
||||
},
|
||||
defaultSize: {
|
||||
|
|
@ -40,46 +83,126 @@ export const stepsConfig = {
|
|||
showOnDesktop: { type: 'toggle', displayName: 'Show on desktop' },
|
||||
showOnMobile: { type: 'toggle', displayName: 'Show on mobile' },
|
||||
},
|
||||
actions: [
|
||||
{
|
||||
handle: 'setStep',
|
||||
displayName: 'Set step',
|
||||
params: [
|
||||
{
|
||||
handle: 'option',
|
||||
displayName: 'Option',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
handle: 'setVisibility',
|
||||
displayName: 'Set visibility',
|
||||
params: [{ handle: 'visible', displayName: 'Value', defaultValue: '{{false}}', type: 'toggle' }],
|
||||
},
|
||||
{
|
||||
handle: 'setDisabled',
|
||||
displayName: 'Set disabled',
|
||||
params: [{ handle: 'disable', displayName: 'Value', defaultValue: '{{true}}', type: 'toggle' }],
|
||||
},
|
||||
{
|
||||
handle: 'resetSteps',
|
||||
displayName: 'Reset steps',
|
||||
params: [],
|
||||
},
|
||||
{
|
||||
handle: 'setStepVisible',
|
||||
displayName: 'Set step visible',
|
||||
params: [
|
||||
{
|
||||
handle: 'id',
|
||||
displayName: 'Step id',
|
||||
},
|
||||
{
|
||||
handle: 'visibility',
|
||||
displayName: 'visibility',
|
||||
defaultValue: '{{false}}',
|
||||
type: 'toggle',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
handle: 'setStepDisable',
|
||||
displayName: 'Set step disable',
|
||||
params: [
|
||||
{
|
||||
handle: 'id',
|
||||
displayName: 'Step id',
|
||||
},
|
||||
{
|
||||
handle: 'disabled',
|
||||
displayName: 'disabled',
|
||||
defaultValue: '{{true}}',
|
||||
type: 'toggle',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
events: {
|
||||
onSelect: { displayName: 'On select' },
|
||||
},
|
||||
styles: {
|
||||
color: {
|
||||
incompletedAccent: {
|
||||
type: 'colorSwatches',
|
||||
displayName: 'Color',
|
||||
displayName: 'Incompleted accent',
|
||||
validation: {
|
||||
schema: { type: 'string' },
|
||||
defaultValue: '#CCD1D5',
|
||||
},
|
||||
accordian: 'steps',
|
||||
},
|
||||
incompletedLabel: {
|
||||
type: 'colorSwatches',
|
||||
displayName: 'Incompleted label',
|
||||
validation: {
|
||||
schema: { type: 'string' },
|
||||
defaultValue: '#1B1F24',
|
||||
},
|
||||
accordian: 'steps',
|
||||
},
|
||||
completedAccent: {
|
||||
type: 'colorSwatches',
|
||||
displayName: 'Completed accent',
|
||||
validation: {
|
||||
schema: { type: 'string' },
|
||||
defaultValue: 'var(--primary-brand)',
|
||||
},
|
||||
accordian: 'steps',
|
||||
},
|
||||
textColor: {
|
||||
completedLabel: {
|
||||
type: 'colorSwatches',
|
||||
displayName: 'Text color',
|
||||
displayName: 'Completed label',
|
||||
validation: {
|
||||
schema: { type: 'string' },
|
||||
defaultValue: '#000000',
|
||||
defaultValue: '#1B1F24',
|
||||
},
|
||||
accordian: 'steps',
|
||||
},
|
||||
theme: {
|
||||
type: 'select',
|
||||
displayName: 'Theme',
|
||||
currentStepLabel: {
|
||||
type: 'colorSwatches',
|
||||
displayName: 'Current step label',
|
||||
validation: {
|
||||
schema: { type: 'string' },
|
||||
defaultValue: '#1B1F24',
|
||||
},
|
||||
accordian: 'steps',
|
||||
},
|
||||
padding: {
|
||||
type: 'switch',
|
||||
displayName: 'Padding',
|
||||
validation: {
|
||||
schema: { type: 'union', schemas: [{ type: 'string' }, { type: 'number' }] },
|
||||
defaultValue: 'default',
|
||||
},
|
||||
options: [
|
||||
{ name: 'titles', value: 'titles' },
|
||||
{ name: 'numbers', value: 'numbers' },
|
||||
{ name: 'plain', value: 'plain' },
|
||||
{ displayName: 'Default', value: 'default' },
|
||||
{ displayName: 'None', value: 'none' },
|
||||
],
|
||||
validation: {
|
||||
schema: { type: 'string' },
|
||||
defaultValue: 'titles',
|
||||
},
|
||||
},
|
||||
visibility: {
|
||||
type: 'toggle',
|
||||
displayName: 'Visibility',
|
||||
validation: {
|
||||
schema: { type: 'boolean' },
|
||||
defaultValue: true,
|
||||
},
|
||||
accordian: 'container',
|
||||
},
|
||||
},
|
||||
exposedVariables: {
|
||||
|
|
@ -92,17 +215,35 @@ export const stepsConfig = {
|
|||
},
|
||||
properties: {
|
||||
steps: {
|
||||
value: `{{ [{ name: 'step 1', tooltip: 'some tooltip', id: 1},{ name: 'step 2', tooltip: 'some tooltip', id: 2},{ name: 'step 3', tooltip: 'some tooltip', id: 3},{ name: 'step 4', tooltip: 'some tooltip', id: 4},{ name: 'step 5', tooltip: 'some tooltip', id: 5}]}}`,
|
||||
value: [
|
||||
{ name: 'step 1', tooltip: '', id: 1, visible: { value: true }, disabled: { value: false } },
|
||||
{ name: 'step 2', tooltip: '', id: 2, visible: { value: true }, disabled: { value: false } },
|
||||
{ name: 'step 3', tooltip: '', id: 3, visible: { value: true }, disabled: { value: false } },
|
||||
{ name: 'step 4', tooltip: '', id: 4, visible: { value: true }, disabled: { value: false } },
|
||||
{ name: 'step 5', tooltip: '', id: 5, visible: { value: true }, disabled: { value: false } },
|
||||
],
|
||||
},
|
||||
schema: {
|
||||
value: `{{ [{ name: 'step 1', tooltip: '', id: 1,visible: true, disabled: false},{ name: 'step 2', tooltip: '', id: 2,visible: true, disabled: false},{ name: 'step 3', tooltip: '', id: 3,visible: true, disabled: false},{ name: 'step 4', tooltip: '', id: 4,visible: true, disabled: false},{ name: 'step 5', tooltip: '', id: 5,visible: true, disabled: false}]}}`,
|
||||
},
|
||||
disabledState: { value: '{{false}}' },
|
||||
variant: { value: 'titles' },
|
||||
currentStep: { value: '{{3}}' },
|
||||
stepsSelectable: { value: true },
|
||||
advanced: { value: `{{false}}` },
|
||||
visibility: { value: '{{true}}' },
|
||||
},
|
||||
events: [],
|
||||
styles: {
|
||||
visibility: { value: '{{true}}' },
|
||||
theme: { value: 'titles' },
|
||||
color: { value: 'var(--primary-brand)' },
|
||||
textColor: { value: '' },
|
||||
// color: { value: '' },
|
||||
// textColor: { value: '' },
|
||||
padding: { value: 'default' },
|
||||
incompletedAccent: { value: '#E4E7EB' },
|
||||
incompletedLabel: { value: '#1B1F24' },
|
||||
completedAccent: { value: '#4368E3' },
|
||||
completedLabel: { value: '#1B1F24' },
|
||||
currentStepLabel: { value: '#1B1F24' },
|
||||
},
|
||||
},
|
||||
};
|
||||
|
|
|
|||
|
|
@ -490,7 +490,7 @@ export class AppsUtilService implements IAppsUtilService {
|
|||
if (['Table'].includes(currentComponentData?.component?.component) && isArray(objValue)) {
|
||||
return srcValue;
|
||||
} else if (
|
||||
['DropdownV2', 'MultiselectV2'].includes(currentComponentData?.component?.component) &&
|
||||
['DropdownV2', 'MultiselectV2', 'Steps'].includes(currentComponentData?.component?.component) &&
|
||||
isArray(objValue)
|
||||
) {
|
||||
return isArray(srcValue) ? srcValue : Object.values(srcValue);
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@ export class DataQueriesService implements IDataQueriesService {
|
|||
protected readonly dataQueryRepository: DataQueryRepository,
|
||||
protected readonly dataQueryUtilService: DataQueriesUtilService,
|
||||
protected readonly dataSourceRepository: DataSourcesRepository
|
||||
) {}
|
||||
) { }
|
||||
|
||||
async getAll(versionId: string) {
|
||||
const queries = await this.dataQueryRepository.getAll(versionId);
|
||||
|
|
@ -30,9 +30,6 @@ export class DataQueriesService implements IDataQueriesService {
|
|||
|
||||
// serialize
|
||||
for (const query of queries) {
|
||||
if (query.dataSource.type === DataSourceTypes.STATIC) {
|
||||
delete query['dataSourceId'];
|
||||
}
|
||||
delete query['dataSource'];
|
||||
|
||||
const decamelizeQuery = decamelizeKeys(query);
|
||||
|
|
|
|||
|
|
@ -31,11 +31,11 @@ export const TJDefaultTheme: Definition = {
|
|||
light: '#1B1F24',
|
||||
dark: '#CFD3D8',
|
||||
},
|
||||
secondary: {
|
||||
placeholder: {
|
||||
light: '#6A727C',
|
||||
dark: '#858C94',
|
||||
},
|
||||
tertiary: {
|
||||
disabled: {
|
||||
light: '#ACB2B9',
|
||||
dark: '#545B64',
|
||||
},
|
||||
|
|
@ -48,15 +48,15 @@ export const TJDefaultTheme: Definition = {
|
|||
large: 0,
|
||||
},
|
||||
colors: {
|
||||
primary: {
|
||||
default: {
|
||||
light: '#CCD1D5',
|
||||
dark: '#3C434B',
|
||||
},
|
||||
secondary: {
|
||||
weak: {
|
||||
light: '#E4E7EB',
|
||||
dark: '#EEF0F1',
|
||||
},
|
||||
tertiary: {
|
||||
disabled: {
|
||||
light: '#E4E7EB',
|
||||
dark: '#F6F8FA',
|
||||
},
|
||||
|
|
@ -64,15 +64,15 @@ export const TJDefaultTheme: Definition = {
|
|||
},
|
||||
systemStatus: {
|
||||
colors: {
|
||||
primary: {
|
||||
success: {
|
||||
light: '#1E823B',
|
||||
dark: '#318344',
|
||||
},
|
||||
secondary: {
|
||||
error: {
|
||||
light: '#D72D39',
|
||||
dark: '#D03F43',
|
||||
},
|
||||
tertiary: {
|
||||
warning: {
|
||||
light: '#BF4F03',
|
||||
dark: '#BA5722',
|
||||
},
|
||||
|
|
@ -84,6 +84,18 @@ export const TJDefaultTheme: Definition = {
|
|||
light: '#F6F6F6',
|
||||
dark: '#121518',
|
||||
},
|
||||
surface1: {
|
||||
light: '#FFFFFF',
|
||||
dark: '#1E2226',
|
||||
},
|
||||
surface2: {
|
||||
light: '#F6F8FA',
|
||||
dark: '#2B3036',
|
||||
},
|
||||
surface3: {
|
||||
light: '#E4E7EB',
|
||||
dark: '#3C434B',
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
|
|
|||
|
|
@ -33,12 +33,12 @@ class TextColors {
|
|||
@IsOptional()
|
||||
@ValidateNested()
|
||||
@Type(() => Color)
|
||||
secondary?: Color;
|
||||
placeholder?: Color;
|
||||
|
||||
@IsOptional()
|
||||
@ValidateNested()
|
||||
@Type(() => Color)
|
||||
tertiary?: Color;
|
||||
disabled?: Color;
|
||||
}
|
||||
|
||||
class Text {
|
||||
|
|
@ -64,17 +64,17 @@ class BorderRadius {
|
|||
class BorderColors {
|
||||
@ValidateNested()
|
||||
@Type(() => Color)
|
||||
primary: Color;
|
||||
default: Color;
|
||||
|
||||
@IsOptional()
|
||||
@ValidateNested()
|
||||
@Type(() => Color)
|
||||
secondary?: Color;
|
||||
weak?: Color;
|
||||
|
||||
@IsOptional()
|
||||
@ValidateNested()
|
||||
@Type(() => Color)
|
||||
tertiary?: Color;
|
||||
disabled?: Color;
|
||||
}
|
||||
|
||||
class Border {
|
||||
|
|
@ -90,17 +90,17 @@ class Border {
|
|||
class SystemStatusColors {
|
||||
@ValidateNested()
|
||||
@Type(() => Color)
|
||||
primary: Color;
|
||||
success: Color;
|
||||
|
||||
@IsOptional()
|
||||
@ValidateNested()
|
||||
@Type(() => Color)
|
||||
secondary?: Color;
|
||||
error?: Color;
|
||||
|
||||
@IsOptional()
|
||||
@ValidateNested()
|
||||
@Type(() => Color)
|
||||
tertiary?: Color;
|
||||
warning?: Color;
|
||||
}
|
||||
|
||||
class SystemStatus {
|
||||
|
|
@ -121,6 +121,18 @@ class SurfaceColors {
|
|||
@ValidateNested()
|
||||
@Type(() => AppBackgroundColor)
|
||||
appBackground: AppBackgroundColor;
|
||||
|
||||
@ValidateNested()
|
||||
@Type(() => Color)
|
||||
surface1: Color;
|
||||
|
||||
@ValidateNested()
|
||||
@Type(() => Color)
|
||||
surface2: Color;
|
||||
|
||||
@ValidateNested()
|
||||
@Type(() => Color)
|
||||
surface3: Color;
|
||||
}
|
||||
|
||||
class Surface {
|
||||
|
|
|
|||
Loading…
Reference in a new issue