mirror of
https://github.com/ToolJet/ToolJet
synced 2026-05-06 06:48:21 +00:00
Merge branch 'modularisation/v3' into fix/codehinter-search-replace
This commit is contained in:
commit
a3f25097f5
32 changed files with 360 additions and 103 deletions
|
|
@ -5,19 +5,13 @@ const MIN_TABLE_ROW_HEIGHT_DEFAULT = 45;
|
|||
|
||||
const TableRowHeightInput = ({ value, onChange, cyLabel, staticText, styleDefinition }) => {
|
||||
const [inputValue, setInputValue] = useState(value);
|
||||
const minValue =
|
||||
styleDefinition.cellSize?.value === 'condensed' ? MIN_TABLE_ROW_HEIGHT_CONDENSED : MIN_TABLE_ROW_HEIGHT_DEFAULT;
|
||||
|
||||
useEffect(() => {
|
||||
setInputValue(value < minValue ? minValue : value);
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [value, styleDefinition.cellSize?.value]);
|
||||
useEffect(() => {
|
||||
onChange(
|
||||
styleDefinition.cellSize?.value === 'condensed' ? MIN_TABLE_ROW_HEIGHT_CONDENSED : MIN_TABLE_ROW_HEIGHT_DEFAULT
|
||||
);
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []);
|
||||
|
||||
const minValue =
|
||||
styleDefinition.cellSize?.value === 'condensed' ? MIN_TABLE_ROW_HEIGHT_CONDENSED : MIN_TABLE_ROW_HEIGHT_DEFAULT;
|
||||
|
||||
const handleBlur = () => {
|
||||
const newValue = Math.max(inputValue, minValue);
|
||||
|
|
|
|||
|
|
@ -150,7 +150,7 @@ const TJDBCodeEditor = (props) => {
|
|||
className="cm-codehinter position-relative"
|
||||
style={{
|
||||
width: '100%',
|
||||
height: isOpen ? '350p' : 'auto',
|
||||
height: isOpen ? '350px' : 'auto',
|
||||
}}
|
||||
>
|
||||
<div className={`cm-codehinter ${darkMode && 'cm-codehinter-dark-themed'}`}>
|
||||
|
|
@ -167,14 +167,14 @@ const TJDBCodeEditor = (props) => {
|
|||
componentName={componentName}
|
||||
key={componentName}
|
||||
forceUpdate={forceUpdate}
|
||||
optionalProps={{ styles: { height: 300 }, cls: '' }}
|
||||
optionalProps={{ styles: { height: 300 }, cls: 'tjdb-hinter-portal' }}
|
||||
darkMode={darkMode}
|
||||
selectors={{ className: 'preview-block-portal tjdb-portal-codehinter' }}
|
||||
dragResizePortal={true}
|
||||
callgpt={null}
|
||||
>
|
||||
<ErrorBoundary>
|
||||
<div className={`${errorState && 'tjdb-hinter-error'}`} data-cy={`${cyLabel}-input-field`}>
|
||||
<div className={`${errorState && 'tjdb-hinter-error'} h-100`} data-cy={`${cyLabel}-input-field`}>
|
||||
<CodeMirror
|
||||
value={currentValue}
|
||||
placeholder={placeholder}
|
||||
|
|
|
|||
|
|
@ -653,4 +653,10 @@
|
|||
|
||||
.cm-searchMatch.cm-searchMatch-selected {
|
||||
background-color: #F28F2D !important;
|
||||
}
|
||||
|
||||
.tjdb-hinter-portal{
|
||||
.cm-theme{
|
||||
height: 100% ;
|
||||
}
|
||||
}
|
||||
|
|
@ -88,9 +88,19 @@ const LeftSidebarInspector = ({ darkMode, pinned, setPinned }) => {
|
|||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [sortedComponents, sortedQueries, sortedVariables, sortedConstants, sortedPageVariables, sortedGlobalVariables]);
|
||||
|
||||
const handleNodeExpansion = (path) => {
|
||||
const handleNodeExpansion = (path, data, currentNode) => {
|
||||
if (pathToBeInspected && path?.length > 0) {
|
||||
return pathToBeInspected.includes(path[path.length - 1]);
|
||||
const shouldExpand = pathToBeInspected.includes(path[path.length - 1]);
|
||||
|
||||
// Scroll to the component in the inspector
|
||||
if (path?.length === 2 && path?.[0] === 'components' && shouldExpand) {
|
||||
const target = document.getElementById(`inspector-node-${String(currentNode).toLowerCase()}`);
|
||||
if (target) {
|
||||
target.scrollIntoView({ behavior: 'smooth', block: 'center' });
|
||||
}
|
||||
}
|
||||
|
||||
return shouldExpand;
|
||||
} else return false;
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ const useCallbackActions = () => {
|
|||
const currentPageComponents = useStore((state) => state?.getCurrentPageComponents(), shallow);
|
||||
const shouldFreeze = useStore((state) => state.getShouldFreeze());
|
||||
const runQuery = useStore((state) => state.queryPanel.runQuery);
|
||||
const getComponentIdToAutoScroll = useStore((state) => state.getComponentIdToAutoScroll);
|
||||
|
||||
const handleRemoveComponent = (component) => {
|
||||
deleteComponents([component.id]);
|
||||
|
|
@ -30,30 +31,22 @@ const useCallbackActions = () => {
|
|||
return toast.success('Copied to the clipboard', { position: 'top-center' });
|
||||
};
|
||||
|
||||
const autoScrollTo = (id) => {
|
||||
setSelectedComponents([id]);
|
||||
const target = document.getElementById(id);
|
||||
target.scrollIntoView({ behavior: 'smooth', block: 'center' });
|
||||
};
|
||||
|
||||
const handleAutoScrollToComponent = (data) => {
|
||||
const currentPageComponents = useStore.getState().getCurrentPageComponents();
|
||||
const component = currentPageComponents?.[data.id];
|
||||
|
||||
let parentId = component?.component?.parent;
|
||||
if (parentId) {
|
||||
const regex = /-\d+$/;
|
||||
if (regex.test(parentId)) {
|
||||
parentId = parentId.replace(regex, ''); // To get parentId without tab index if parent type is Tab
|
||||
}
|
||||
const parentType = currentPageComponents?.[parentId]?.component?.component;
|
||||
if (parentType && (parentType === 'Modal' || parentType === 'Tabs')) {
|
||||
autoScrollTo(parentId); // To scroll to parent component if parent type is Modal or Tabs
|
||||
return;
|
||||
}
|
||||
const { isAccessible, computedComponentId, isOnCanvas } = getComponentIdToAutoScroll(data.id);
|
||||
if (!isAccessible) {
|
||||
if (isOnCanvas) {
|
||||
toast.success(
|
||||
`This component can't be opened because it's on the main canvas. Close ${computedComponentId} and click "Go to component" to view it there`
|
||||
);
|
||||
} else
|
||||
toast.success(
|
||||
`This component can't be opened because it's inside ${computedComponentId}. Open ${computedComponentId} and click "Go to component"to view it.`
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
autoScrollTo(data.id);
|
||||
setSelectedComponents([computedComponentId]);
|
||||
const target = document.getElementById(computedComponentId);
|
||||
target.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
|
||||
};
|
||||
|
||||
const callbackActions = [
|
||||
|
|
|
|||
|
|
@ -1,21 +1,31 @@
|
|||
import React from 'react';
|
||||
import OverflowTooltip from '@/_components/OverflowTooltip';
|
||||
|
||||
export const BaseUrl = ({ dataSourceURL, theme }) => {
|
||||
export const BaseUrl = ({ dataSourceURL, theme, className = 'col-auto', style = {} }) => {
|
||||
return (
|
||||
<span
|
||||
className="col-auto"
|
||||
htmlFor=""
|
||||
className={`${className} base-url-container`}
|
||||
style={{
|
||||
padding: '5px',
|
||||
border: theme === 'default' ? '1px solid rgb(217 220 222)' : '1px solid white',
|
||||
borderRightWidth: 0,
|
||||
background: theme === 'default' ? 'rgb(246 247 251)' : '#20211e',
|
||||
color: theme === 'default' ? '#9ca1a6' : '#9e9e9e',
|
||||
height: '32px',
|
||||
borderRadius: '6px 0 0 6px',
|
||||
display: 'flex',
|
||||
transition: 'height 0.2s ease',
|
||||
...style,
|
||||
}}
|
||||
>
|
||||
{dataSourceURL}
|
||||
<OverflowTooltip
|
||||
text={dataSourceURL}
|
||||
width="559px"
|
||||
whiteSpace="normal"
|
||||
placement="auto"
|
||||
style={{ height: '100%' }}
|
||||
>
|
||||
{dataSourceURL}
|
||||
</OverflowTooltip>
|
||||
</span>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -28,7 +28,10 @@ class Restapi extends React.Component {
|
|||
|
||||
this.state = {
|
||||
options,
|
||||
codeHinterHeight: 32, // Default height
|
||||
};
|
||||
this.codeHinterRef = React.createRef();
|
||||
this.resizeObserver = null;
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps) {
|
||||
|
|
@ -40,21 +43,95 @@ class Restapi extends React.Component {
|
|||
},
|
||||
});
|
||||
}
|
||||
// Setup resize observer if it's not already set up
|
||||
if (this.codeHinterRef.current && !this.resizeObserver) {
|
||||
this.setupResizeObserver();
|
||||
}
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
try {
|
||||
if (isEmpty(this.state.options['headers'])) {
|
||||
this.addNewKeyValuePair('headers');
|
||||
}
|
||||
if (isEmpty(this.state.options['cookies'])) {
|
||||
this.addNewKeyValuePair('cookies');
|
||||
}
|
||||
if (isEmpty(this.state.options['method'])) {
|
||||
changeOption(this, 'method', 'get');
|
||||
}
|
||||
setTimeout(() => {
|
||||
if (isEmpty(this.state.options['url_params'])) {
|
||||
this.addNewKeyValuePair('url_params');
|
||||
}
|
||||
}, 1000);
|
||||
setTimeout(() => {
|
||||
if (isEmpty(this.state.options['body'])) {
|
||||
this.addNewKeyValuePair('body');
|
||||
}
|
||||
}, 1000);
|
||||
setTimeout(() => {
|
||||
this.initizalizeRetryNetworkErrorsToggle();
|
||||
}, 1000);
|
||||
|
||||
this.setupResizeObserver();
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
}
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
if (this.resizeObserver) {
|
||||
this.resizeObserver.disconnect();
|
||||
}
|
||||
}
|
||||
|
||||
setupResizeObserver() {
|
||||
if (!this.codeHinterRef.current) return;
|
||||
|
||||
// Try to find the editor element, checking multiple possible selectors
|
||||
const findEditorElement = () => {
|
||||
const element =
|
||||
this.codeHinterRef.current.querySelector('.cm-editor') ||
|
||||
this.codeHinterRef.current.querySelector('.codehinter-input') ||
|
||||
this.codeHinterRef.current.querySelector('.code-hinter-wrapper');
|
||||
return element;
|
||||
};
|
||||
|
||||
// Initial attempt to find editor
|
||||
let editorElement = findEditorElement();
|
||||
|
||||
// If not found immediately, try again after a short delay
|
||||
if (!editorElement) {
|
||||
setTimeout(() => {
|
||||
editorElement = findEditorElement();
|
||||
if (editorElement) {
|
||||
this.setupObserverForElement(editorElement);
|
||||
}
|
||||
}, 100);
|
||||
return;
|
||||
}
|
||||
|
||||
this.setupObserverForElement(editorElement);
|
||||
}
|
||||
|
||||
setupObserverForElement(element) {
|
||||
if (this.resizeObserver) {
|
||||
this.resizeObserver.disconnect();
|
||||
}
|
||||
|
||||
this.resizeObserver = new ResizeObserver((entries) => {
|
||||
for (let entry of entries) {
|
||||
const height = Math.max(32, Math.min(entry.contentRect.height, 220));
|
||||
if (height !== this.state.codeHinterHeight) {
|
||||
this.setState({ codeHinterHeight: height });
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
this.resizeObserver.observe(element);
|
||||
}
|
||||
|
||||
initizalizeRetryNetworkErrorsToggle = () => {
|
||||
const isRetryNetworkErrorToggleUnused = this.props.options.retry_network_errors === null;
|
||||
if (isRetryNetworkErrorToggleUnused) {
|
||||
|
|
@ -212,13 +289,30 @@ class Restapi extends React.Component {
|
|||
useCustomStyles={true}
|
||||
/>
|
||||
</div>
|
||||
<div className={`field w-100 rest-methods-url`}>
|
||||
<div
|
||||
className={`field rest-methods-url ${dataSourceURL && 'data-source-exists'}`}
|
||||
style={{ width: 'calc(100% - 214px)' }}
|
||||
>
|
||||
<div className="font-weight-medium color-slate12">URL</div>
|
||||
<div className="d-flex">
|
||||
<div className="d-flex h-100 w-100">
|
||||
{dataSourceURL && (
|
||||
<BaseUrl theme={this.props.darkMode ? 'monokai' : 'default'} dataSourceURL={dataSourceURL} />
|
||||
<BaseUrl
|
||||
theme={this.props.darkMode ? 'monokai' : 'default'}
|
||||
dataSourceURL={dataSourceURL}
|
||||
style={{
|
||||
overflowWrap: 'anywhere',
|
||||
maxWidth: '40%',
|
||||
width: 'fit-content',
|
||||
height: `${this.state.codeHinterHeight}px`,
|
||||
minHeight: '32px',
|
||||
maxHeight: '220px',
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
<div className={`flex-grow-1 rest-api-url-codehinter ${dataSourceURL ? 'url-input-group' : ''}`}>
|
||||
<div
|
||||
ref={this.codeHinterRef}
|
||||
className={` flex-grow-1 rest-api-url-codehinter ${dataSourceURL ? 'url-input-group' : ''}`}
|
||||
>
|
||||
<CodeHinter
|
||||
type="basic"
|
||||
initialValue={options.url}
|
||||
|
|
|
|||
|
|
@ -33,7 +33,7 @@ export const BulkUploadPrimaryKey = () => {
|
|||
>
|
||||
<input
|
||||
type="text"
|
||||
value={bulkUpdatePrimaryKey?.primary_key?.join() || ''}
|
||||
value={bulkUpdatePrimaryKey?.primary_key?.join(', ') || ''}
|
||||
style={{
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
|
|
@ -53,7 +53,7 @@ export const BulkUploadPrimaryKey = () => {
|
|||
<div className="field flex-grow-1 minw-400-w-400">
|
||||
<CodeHinter
|
||||
type="basic"
|
||||
initialValue={bulkUpdatePrimaryKey?.rows_update ?? {}}
|
||||
initialValue={`{{${JSON.stringify(bulkUpdatePrimaryKey?.rows_update ?? [])}}}`}
|
||||
className="codehinter-plugins"
|
||||
placeholder="{{ [ { 'column1': 'value', ... } ] }}"
|
||||
onChange={(newValue) => {
|
||||
|
|
|
|||
|
|
@ -214,4 +214,9 @@
|
|||
.input-value-padding {
|
||||
box-sizing: border-box;
|
||||
padding-right: 30px !important;
|
||||
}
|
||||
|
||||
.react-datepicker__navigation{
|
||||
overflow: visible !important;
|
||||
height: inherit !important;
|
||||
}
|
||||
|
|
@ -677,6 +677,7 @@ const ToolJetDbOperations = ({ optionchanged, options, darkMode, isHorizontalLay
|
|||
}}
|
||||
componentName="TooljetDatabase"
|
||||
delayOnChange={false}
|
||||
className="w-100"
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
|
|
|||
|
|
@ -56,6 +56,7 @@ export function KanbanBoard({ widgetHeight, kanbanProps, parentRef, id }) {
|
|||
const [containers, setContainers] = useState([]);
|
||||
|
||||
const [showModal, setShowModal] = useState(false);
|
||||
const setModalOpenOnCanvas = useStore((state) => state.setModalOpenOnCanvas);
|
||||
const [activeId, setActiveId] = useState(null);
|
||||
const cardMovementRef = useRef(null);
|
||||
const shouldUpdateData = useRef(false);
|
||||
|
|
@ -117,6 +118,7 @@ export function KanbanBoard({ widgetHeight, kanbanProps, parentRef, id }) {
|
|||
}
|
||||
/**** End - Logic to reduce the zIndex of modal control box ****/
|
||||
}
|
||||
setModalOpenOnCanvas(`${id}-modal`, showModal);
|
||||
}, [showModal]);
|
||||
|
||||
useEffect(() => {
|
||||
|
|
|
|||
|
|
@ -49,6 +49,7 @@ export const Modal = function Modal({
|
|||
const size = properties.size ?? 'lg';
|
||||
const [modalWidth, setModalWidth] = useState();
|
||||
const mode = useStore((state) => state.currentMode, shallow);
|
||||
const setModalOpenOnCanvas = useStore((state) => state.setModalOpenOnCanvas);
|
||||
|
||||
/**** Start - Logic to reset the zIndex of modal control box ****/
|
||||
useEffect(() => {
|
||||
|
|
@ -63,6 +64,7 @@ export const Modal = function Modal({
|
|||
useGridStore.getState().actions.setOpenModalWidgetId(null);
|
||||
}
|
||||
}
|
||||
setModalOpenOnCanvas(id, showModal);
|
||||
}, [showModal, id, mode]);
|
||||
/**** End - Logic to reset the zIndex of modal control box ****/
|
||||
|
||||
|
|
|
|||
|
|
@ -263,6 +263,9 @@ export const Datepicker = function Datepicker({
|
|||
}
|
||||
setIsDateInputFocussed(false);
|
||||
}}
|
||||
closeOnScroll={(e) => {
|
||||
return e.target.className === 'table-responsive jet-data-table false false';
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -1114,10 +1114,9 @@ export const Table = React.memo(
|
|||
<div
|
||||
style={{
|
||||
position: 'absolute',
|
||||
top: 0,
|
||||
top: `${items[0]?.start ?? 0}px`,
|
||||
left: 0,
|
||||
width: '100%',
|
||||
transform: `translateY(${items[0]?.start ?? 0}px)`,
|
||||
}}
|
||||
>
|
||||
{items.map((virtualRow) => {
|
||||
|
|
|
|||
|
|
@ -44,6 +44,7 @@ const initialState = {
|
|||
currentPageHandle: null,
|
||||
showWidgetDeleteConfirmation: false,
|
||||
focusedParentId: null,
|
||||
modalsOpenOnCanvas: [],
|
||||
};
|
||||
|
||||
export const createComponentsSlice = (set, get) => ({
|
||||
|
|
@ -1867,4 +1868,17 @@ export const createComponentsSlice = (set, get) => ({
|
|||
const currentPage = getCurrentPage(moduleId);
|
||||
return currentPage?.autoComputeLayout;
|
||||
},
|
||||
setModalOpenOnCanvas: (modalId, isOpen) => {
|
||||
const { modalsOpenOnCanvas } = get();
|
||||
let newModalOpenOnCanvas = [];
|
||||
|
||||
if (isOpen) {
|
||||
newModalOpenOnCanvas = [...modalsOpenOnCanvas, modalId];
|
||||
} else {
|
||||
newModalOpenOnCanvas = modalsOpenOnCanvas.filter((id) => id !== modalId);
|
||||
}
|
||||
set((state) => {
|
||||
state.modalsOpenOnCanvas = newModalOpenOnCanvas;
|
||||
});
|
||||
},
|
||||
});
|
||||
|
|
|
|||
|
|
@ -37,4 +37,85 @@ export const createLeftSideBarSlice = (set, get) => ({
|
|||
toggleLeftSidebar(true);
|
||||
}
|
||||
},
|
||||
getComponentIdToAutoScroll: (componentId) => {
|
||||
const { getCurrentPageComponents, getAllExposedValues, modalsOpenOnCanvas } = get();
|
||||
const currentPageComponents = getCurrentPageComponents();
|
||||
|
||||
let targetComponentId = componentId;
|
||||
let current = componentId;
|
||||
const visited = new Set();
|
||||
let isInsideOpenModal = false;
|
||||
|
||||
// Bubble up to the outermost parent to find the target component
|
||||
// eslint-disable-next-line no-constant-condition
|
||||
while (true) {
|
||||
if (visited.has(current)) break;
|
||||
visited.add(current);
|
||||
|
||||
const parentId = currentPageComponents?.[current]?.component?.parent;
|
||||
if (!parentId) break;
|
||||
|
||||
let isComponentVisibleInParent = true;
|
||||
let nextPossibleCandidate = parentId;
|
||||
|
||||
// If the component exists inside a tab component
|
||||
const regForTabs = /-(?!\d{12}$)\d+$/; // Parent id for tabs follow the format 'id-index' and index is not UUIDv4 id segment
|
||||
if (regForTabs.test(parentId)) {
|
||||
const reg = /-(\d+)$/;
|
||||
const tabIndex = Number(parentId.match(reg)[1]); // Tab index inside which the component exists
|
||||
|
||||
const tabId = parentId.replace(regForTabs, ''); // Extract tab id from parent id
|
||||
|
||||
const { currentTab } = getAllExposedValues().components?.[tabId] || {};
|
||||
const activeTabIndex = Number(currentTab);
|
||||
|
||||
nextPossibleCandidate = tabId;
|
||||
if (tabIndex !== activeTabIndex) {
|
||||
isComponentVisibleInParent = false;
|
||||
}
|
||||
}
|
||||
|
||||
// If the component exists inside a modal component
|
||||
if (currentPageComponents?.[parentId]?.component?.component === 'Modal') {
|
||||
nextPossibleCandidate = parentId;
|
||||
if (!modalsOpenOnCanvas.includes(parentId)) {
|
||||
isComponentVisibleInParent = false;
|
||||
}
|
||||
}
|
||||
|
||||
// If the component exists inside the kanban component's modal
|
||||
if (parentId.endsWith('-modal')) {
|
||||
nextPossibleCandidate = parentId.replace(/-modal$/, ''); // Extract kanban id from parent id
|
||||
if (!modalsOpenOnCanvas.includes(parentId)) {
|
||||
isComponentVisibleInParent = false;
|
||||
}
|
||||
}
|
||||
|
||||
// If the open modal contains the component
|
||||
if (modalsOpenOnCanvas[modalsOpenOnCanvas.length - 1] === parentId) {
|
||||
isInsideOpenModal = true;
|
||||
}
|
||||
|
||||
if (!isComponentVisibleInParent) {
|
||||
targetComponentId = nextPossibleCandidate;
|
||||
}
|
||||
current = nextPossibleCandidate;
|
||||
}
|
||||
|
||||
if (modalsOpenOnCanvas.length > 0 && !isInsideOpenModal) {
|
||||
const targetId = visited.size === 1 ? modalsOpenOnCanvas[modalsOpenOnCanvas.length - 1] : current;
|
||||
const componentName = currentPageComponents?.[targetId]?.component?.name;
|
||||
|
||||
return {
|
||||
isAccessible: false,
|
||||
computedComponentId: componentName,
|
||||
isOnCanvas: visited.size === 1,
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
isAccessible: true,
|
||||
computedComponentId: targetComponentId,
|
||||
};
|
||||
},
|
||||
});
|
||||
|
|
|
|||
|
|
@ -42,7 +42,7 @@ export const CustomComponent = (props) => {
|
|||
setCustomProps({ ...customPropRef.current, ...e.data.updatedObj });
|
||||
} else if (e.data.message === 'RUN_QUERY') {
|
||||
const options = {
|
||||
parameters: e.data.parameters,
|
||||
parameters: JSON.parse(e.data.parameters),
|
||||
queryName: e.data.queryName,
|
||||
};
|
||||
onEvent('onTrigger', [], options);
|
||||
|
|
|
|||
|
|
@ -436,6 +436,7 @@ export const DropdownV2 = ({
|
|||
onChange={(selectedOption, actionProps) => {
|
||||
if (actionProps.action === 'clear') {
|
||||
setInputValue(null);
|
||||
fireEvent('onSelect');
|
||||
}
|
||||
if (actionProps.action === 'select-option') {
|
||||
setInputValue(selectedOption.value);
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import JSONTreeViewer from '@/_ui/JSONTreeViewer';
|
|||
import cx from 'classnames';
|
||||
import SolidIcon from '@/_ui/Icon/SolidIcons';
|
||||
import useStore from '@/AppBuilder/_stores/store';
|
||||
import { toast } from 'react-hot-toast';
|
||||
|
||||
function Logs({ logProps, idx }) {
|
||||
const [open, setOpen] = React.useState(false);
|
||||
|
|
@ -52,10 +53,19 @@ function Logs({ logProps, idx }) {
|
|||
}
|
||||
};
|
||||
|
||||
const copyToClipboard = (data) => {
|
||||
const stringified = JSON.stringify(data, null, 2).replace(/\\/g, '');
|
||||
navigator.clipboard.writeText(stringified);
|
||||
return toast.success('Value copied to clipboard', { position: 'top-center' });
|
||||
};
|
||||
|
||||
const callbackActions = [
|
||||
{
|
||||
for: 'all',
|
||||
actions: [{ name: 'Select Widget', dispatchAction: handleSelectComponentOnEditor, icon: false, onSelect: true }],
|
||||
actions: [
|
||||
{ name: 'Copy value', dispatchAction: copyToClipboard, icon: false },
|
||||
{ name: 'Select Widget', dispatchAction: handleSelectComponentOnEditor, icon: false, onSelect: true },
|
||||
],
|
||||
enableForAllChildren: true,
|
||||
enableFor1stLevelChildren: true,
|
||||
},
|
||||
|
|
|
|||
|
|
@ -163,7 +163,7 @@ export const DateTimePicker = ({
|
|||
<div className={`fw-400 tjdbCellMenuShortcutsText`}>Save Changes</div>
|
||||
</div>
|
||||
<div className="d-flex align-items-center gap-1">
|
||||
<div className={`fw-500 tjdbCellMenuShortcutsInfo`} id="escbutton">
|
||||
<div className={`fw-500 tjdbCellMenuShortcutsInfo esc-btn-datepicker`} id="escbutton">
|
||||
Esc
|
||||
</div>
|
||||
<div className={`fw-400 tjdbCellMenuShortcutsText`}>Discard Changes</div>
|
||||
|
|
|
|||
|
|
@ -214,4 +214,24 @@
|
|||
.input-value-padding {
|
||||
box-sizing: border-box;
|
||||
padding-right: 30px !important;
|
||||
}
|
||||
|
||||
.datepicker-widget.theme-tjdb{
|
||||
.react-datepicker__navigation{
|
||||
overflow: visible !important;
|
||||
height: inherit !important;
|
||||
}
|
||||
}
|
||||
|
||||
.esc-btn-datepicker{
|
||||
height: 18px ;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.tjdb-td-wrapper{
|
||||
.react-datepicker-time__input{
|
||||
input{
|
||||
line-height: normal !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -251,11 +251,7 @@ const Filter = ({
|
|||
}
|
||||
/>
|
||||
<div className="tw-flex items-center tw-ml-[3px]">
|
||||
{filterCount > 0 ? (
|
||||
<span>{pluralize(validFilterCountRef.current, 'filter')}</span>
|
||||
) : (
|
||||
<div> Filter</div>
|
||||
)}
|
||||
{filterCount > 0 ? <span>{pluralize(filterCount, 'filter')}</span> : <div> Filter</div>}
|
||||
</div>
|
||||
{/* {areFiltersApplied && (
|
||||
<span>ed by {pluralize(Object.values(filters).filter(checkIsFilterObjectEmpty).length, 'column')}</span>
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import React, { useEffect, useRef, useState } from 'react';
|
||||
import { ToolTip } from '@/_components';
|
||||
|
||||
export default function OverflowTooltip({ children, className, whiteSpace = 'nowrap', ...rest }) {
|
||||
export default function OverflowTooltip({ children, className, whiteSpace = 'nowrap', placement = 'bottom', ...rest }) {
|
||||
const [isOverflowed, setIsOverflow] = useState(false);
|
||||
const textElementRef = useRef();
|
||||
|
||||
|
|
@ -17,7 +17,7 @@ export default function OverflowTooltip({ children, className, whiteSpace = 'now
|
|||
className={className}
|
||||
delay={{ show: '0', hide: '0' }}
|
||||
tooltipClassName="overflow-tooltip"
|
||||
placement="bottom"
|
||||
placement={placement}
|
||||
message={children}
|
||||
show={isOverflowed}
|
||||
width={rest?.width}
|
||||
|
|
|
|||
|
|
@ -3,13 +3,13 @@ import { authHeader, handleResponse, handleResponseWithoutValidation } from '@/_
|
|||
|
||||
function save(body) {
|
||||
const requestOptions = { method: 'POST', headers: authHeader(), credentials: 'include', body: JSON.stringify(body) };
|
||||
return fetch(`${config.apiUrl}/custom-styles/`, requestOptions).then(handleResponse);
|
||||
return fetch(`${config.apiUrl}/custom-styles`, requestOptions).then(handleResponse);
|
||||
}
|
||||
|
||||
function get(validateResponse = true) {
|
||||
const requestOptions = { method: 'GET', headers: authHeader(), credentials: 'include' };
|
||||
const handleOutput = validateResponse ? handleResponse : handleResponseWithoutValidation;
|
||||
return fetch(`${config.apiUrl}/custom-styles/`, requestOptions).then(handleOutput);
|
||||
return fetch(`${config.apiUrl}/custom-styles`, requestOptions).then(handleOutput);
|
||||
}
|
||||
|
||||
function getForAppViewerEditor(validateResponse = true) {
|
||||
|
|
|
|||
|
|
@ -1250,6 +1250,11 @@ $border-radius: 4px;
|
|||
color: var(--slate12) !important;
|
||||
}
|
||||
}
|
||||
&.data-source-exists {
|
||||
.cm-editor {
|
||||
border-radius: 0 4px 4px 0 !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.rest-api-methods-select-element-container {
|
||||
|
|
|
|||
|
|
@ -53,7 +53,7 @@ export const JSONNode = ({ data, ...restProps }) => {
|
|||
|
||||
React.useEffect(() => {
|
||||
if (typeof shouldExpandNode === 'function') {
|
||||
set(shouldExpandNode(path, data));
|
||||
set(shouldExpandNode(path, data, currentNode));
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [pathToBeInspected]);
|
||||
|
|
@ -268,7 +268,15 @@ export const JSONNode = ({ data, ...restProps }) => {
|
|||
};
|
||||
|
||||
return (
|
||||
<div style={{ fontSize: '9px', marginTop: '0px', right: '10px' }} className="d-flex position-absolute">
|
||||
<div
|
||||
style={{
|
||||
paddingLeft: !enableCopyToClipboard ? '13px' : '0px', // Temporary fix for hover issue for copy value button. Need to remove this once inspector gets revamped.
|
||||
fontSize: '9px',
|
||||
marginTop: '0px',
|
||||
right: '10px',
|
||||
}}
|
||||
className="d-flex position-absolute"
|
||||
>
|
||||
{enableCopyToClipboard && (
|
||||
<ToolTip message={'Copy to clipboard'}>
|
||||
<span
|
||||
|
|
@ -337,6 +345,7 @@ export const JSONNode = ({ data, ...restProps }) => {
|
|||
'group-object-container': shouldDisplayIntendedBlock,
|
||||
'mx-2': typeofCurrentNode !== 'Object' && typeofCurrentNode !== 'Array',
|
||||
})}
|
||||
id={`inspector-node-${String(currentNode).toLowerCase()}`}
|
||||
data-cy={`inspector-node-${String(currentNode).toLowerCase()}`}
|
||||
>
|
||||
{$NODEIcon && <div className="json-tree-icon-container">{$NODEIcon}</div>}
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ import { utils } from '@/modules/common/helpers';
|
|||
import { getSubpath } from '@/_helpers/routes';
|
||||
import { TJLoader } from '@/_ui/TJLoader/TJLoader';
|
||||
import useOnboardingStore from '@/modules/common/helpers/onboardingStoreHelper';
|
||||
import useInvitationsStore from '@/modules/common/helpers/invitationStoreHelper';
|
||||
|
||||
const PostOnboardingComponent = () => <TJLoader />;
|
||||
export const InvitationPage = (darkMode = false) => {
|
||||
|
|
@ -24,7 +25,7 @@ export const InvitationPage = (darkMode = false) => {
|
|||
const source = searchParams.get('source');
|
||||
const redirectTo = searchParams.get('redirectTo');
|
||||
|
||||
const { initiateInvitedUserOnboarding } = invitationsStore();
|
||||
const { initiateInvitedUserOnboarding } = useInvitationsStore();
|
||||
const { resumeSignupOnboarding, isOnboardingStepsCompleted } = useOnboardingStore();
|
||||
useEffect(() => {
|
||||
// getUserDetails();
|
||||
|
|
|
|||
|
|
@ -40,6 +40,7 @@ import { ImportExportResourcesModule } from '@modules/import-export-resources/mo
|
|||
import { TooljetDbModule } from '@modules/tooljet-db/module';
|
||||
import { WorkflowsModule } from '@modules/workflows/module';
|
||||
import { AiModule } from '@modules/ai/module';
|
||||
import { CustomStylesModule } from '@modules/custom-styles/module';
|
||||
|
||||
export class AppModule implements OnModuleInit {
|
||||
static async register(configs: { IS_GET_CONTEXT: boolean }): Promise<DynamicModule> {
|
||||
|
|
@ -92,6 +93,7 @@ export class AppModule implements OnModuleInit {
|
|||
await TooljetDbModule.register(configs),
|
||||
await WorkflowsModule.register(configs),
|
||||
await AiModule.register(configs),
|
||||
await CustomStylesModule.register(configs),
|
||||
];
|
||||
|
||||
return {
|
||||
|
|
|
|||
|
|
@ -15,7 +15,13 @@ export class FeatureAbilityFactory extends AbilityFactory<FEATURE_KEY, Subjects>
|
|||
return App;
|
||||
}
|
||||
|
||||
protected defineAbilityFor(can: AbilityBuilder<FeatureAbility>['can'], UserAllPermissions: UserAllPermissions): void {
|
||||
protected defineAbilityFor(
|
||||
can: AbilityBuilder<FeatureAbility>['can'],
|
||||
UserAllPermissions: UserAllPermissions,
|
||||
extractedMetadata: { moduleName: string; features: string[] },
|
||||
request?: any
|
||||
): void {
|
||||
const appId = request?.tj_resource_id;
|
||||
const { superAdmin, isAdmin, userPermission } = UserAllPermissions;
|
||||
|
||||
const userAppPermissions = userPermission?.[MODULES.APP];
|
||||
|
|
@ -51,7 +57,10 @@ export class FeatureAbilityFactory extends AbilityFactory<FEATURE_KEY, Subjects>
|
|||
can(FEATURE_KEY.CREATE, App);
|
||||
}
|
||||
|
||||
if (isAllAppsEditable) {
|
||||
if (
|
||||
isAllAppsEditable ||
|
||||
(userAppPermissions?.editableAppsId?.length && appId && userAppPermissions.editableAppsId.includes(appId))
|
||||
) {
|
||||
can(
|
||||
[
|
||||
FEATURE_KEY.UPDATE,
|
||||
|
|
@ -70,35 +79,14 @@ export class FeatureAbilityFactory extends AbilityFactory<FEATURE_KEY, Subjects>
|
|||
can(FEATURE_KEY.DELETE, App);
|
||||
}
|
||||
return;
|
||||
} else if (userAppPermissions?.editableAppsId?.length) {
|
||||
can(
|
||||
[
|
||||
FEATURE_KEY.DELETE,
|
||||
FEATURE_KEY.UPDATE_ICON,
|
||||
FEATURE_KEY.GET_ONE,
|
||||
FEATURE_KEY.GET_BY_SLUG,
|
||||
FEATURE_KEY.RELEASE,
|
||||
FEATURE_KEY.VALIDATE_PRIVATE_APP_ACCESS,
|
||||
FEATURE_KEY.VALIDATE_RELEASED_APP_ACCESS,
|
||||
FEATURE_KEY.UPDATE,
|
||||
FEATURE_KEY.GET_ASSOCIATED_TABLES,
|
||||
],
|
||||
App,
|
||||
{ id: { $in: userAppPermissions.editableAppsId } }
|
||||
);
|
||||
if (isAllAppsDeletable) {
|
||||
// Gives delete permission only for editable apps
|
||||
can(FEATURE_KEY.DELETE, App, { id: { $in: userAppPermissions.editableAppsId } });
|
||||
}
|
||||
}
|
||||
|
||||
if (isAllAppsViewable) {
|
||||
// add view permissions for all apps
|
||||
if (
|
||||
isAllAppsViewable ||
|
||||
(userAppPermissions?.viewableAppsId?.length && appId && userAppPermissions.viewableAppsId.includes(appId))
|
||||
) {
|
||||
// add view permissions for all apps or specific app
|
||||
can([FEATURE_KEY.GET_ONE, FEATURE_KEY.GET_BY_SLUG, FEATURE_KEY.VALIDATE_RELEASED_APP_ACCESS], App);
|
||||
} else if (userAppPermissions?.viewableAppsId?.length) {
|
||||
can([FEATURE_KEY.GET_ONE, FEATURE_KEY.GET_BY_SLUG, FEATURE_KEY.VALIDATE_RELEASED_APP_ACCESS], App, {
|
||||
id: { $in: userAppPermissions.viewableAppsId },
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,11 +1,21 @@
|
|||
import { Module } from '@nestjs/common';
|
||||
import { CustomStylesController } from '@modules/custom-styles/controller';
|
||||
import { CustomStylesService } from '@modules/custom-styles/service';
|
||||
import { DynamicModule } from '@nestjs/common';
|
||||
import { getImportPath } from '@modules/app/constants';
|
||||
import { OrganizationsModule } from '@modules/organizations/module';
|
||||
import { FeatureAbilityFactory } from './ability';
|
||||
import { OrganizationRepository } from '@modules/organizations/repository';
|
||||
import { AppsRepository } from '@modules/apps/repository';
|
||||
|
||||
@Module({
|
||||
imports: [],
|
||||
providers: [CustomStylesService],
|
||||
controllers: [CustomStylesController],
|
||||
exports: [],
|
||||
})
|
||||
export class CustomStylesModule {}
|
||||
export class CustomStylesModule {
|
||||
static async register(configs?: { IS_GET_CONTEXT: boolean }): Promise<DynamicModule> {
|
||||
const importPath = await getImportPath(configs?.IS_GET_CONTEXT);
|
||||
const { CustomStylesController } = await import(`${importPath}/custom-styles/controller`);
|
||||
const { CustomStylesService } = await import(`${importPath}/custom-styles/service`);
|
||||
return {
|
||||
module: CustomStylesModule,
|
||||
imports: [await OrganizationsModule.register(configs)],
|
||||
providers: [CustomStylesService, FeatureAbilityFactory, OrganizationRepository, AppsRepository],
|
||||
controllers: [CustomStylesController],
|
||||
exports: [],
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ export const FEATURES: FeaturesConfig = {
|
|||
[MODULES.ORGANIZATION_THEMES]: {
|
||||
[FEATURE_KEY.THEMES_CREATE]: { license: LICENSE_FIELD.CUSTOM_THEMES },
|
||||
[FEATURE_KEY.THEMES_DELETE]: { license: LICENSE_FIELD.CUSTOM_THEMES },
|
||||
[FEATURE_KEY.THEMES_GET_ALL]: { license: LICENSE_FIELD.CUSTOM_THEMES },
|
||||
[FEATURE_KEY.THEMES_GET_ALL]: {},
|
||||
[FEATURE_KEY.THEMES_UPDATE_DEFAULT]: { license: LICENSE_FIELD.CUSTOM_THEMES },
|
||||
[FEATURE_KEY.THEMES_UPDATE_DEFINITION]: { license: LICENSE_FIELD.CUSTOM_THEMES },
|
||||
[FEATURE_KEY.THEMES_UPDATE_NAME]: { license: LICENSE_FIELD.CUSTOM_THEMES },
|
||||
|
|
|
|||
|
|
@ -65,6 +65,7 @@ export class UserRepository extends Repository<User> {
|
|||
organizationId: true,
|
||||
organization: {
|
||||
name: true,
|
||||
status: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
|
|
|
|||
Loading…
Reference in a new issue