Merge branch 'refactor/app-builder-merge-main' into fix-refactor-copy-pasting-tab

This commit is contained in:
Nakul Nagargade 2024-10-24 13:11:57 +05:30
commit 76e970a1be
47 changed files with 3701 additions and 268 deletions

View file

@ -330,8 +330,9 @@ const updateComponentLayout = (components, parentId, isCut = false) => {
};
const isChildOfTabsOrCalendar = (component, allComponents = [], componentParentId = undefined) => {
const parentId = componentParentId ?? component.component?.parent?.split('-').slice(0, -1).join('-');
const parentId = componentParentId ?? component.component?.parent?.match(/([a-fA-F0-9-]{36})-(.+)/)?.[1];
const parentComponent = allComponents?.[parentId];
if (parentComponent) {
return parentComponent.component.component === 'Tabs' || parentComponent.component.component === 'Calendar';
}

View file

@ -26,6 +26,7 @@ export const CreateVersion = ({ showCreateAppVersion, setShowCreateAppVersion })
appId,
setCurrentVersionId,
selectedVersion,
fetchDevelopmentVersions,
} = useStore(
(state) => ({
createNewVersionAction: state.createNewVersionAction,
@ -40,14 +41,15 @@ export const CreateVersion = ({ showCreateAppVersion, setShowCreateAppVersion })
currentVersionId: state.currentVersionId,
setCurrentVersionId: state.setCurrentVersionId,
selectedVersion: state.selectedVersion,
fetchDevelopmentVersions: state.fetchDevelopmentVersions,
}),
shallow
);
const [selectedVersionForCreation, setSelectedVersionForCreation] = useState(null);
// useEffect(() => {
// fetchDevelopmentVersions(appId);
// }, []);
useEffect(() => {
fetchDevelopmentVersions(appId);
}, []);
useEffect(() => {
if (developmentVersions?.length && selectedVersion?.id) {
@ -59,7 +61,7 @@ export const CreateVersion = ({ showCreateAppVersion, setShowCreateAppVersion })
const { t } = useTranslation();
console.log({ developmentVersions });
const options = versionsPromotedToEnvironment.map((version) => {
const options = developmentVersions.map((version) => {
return { label: version.name, value: version };
});

View file

@ -190,7 +190,7 @@ const useAppData = (appId, moduleId, mode = 'edit', { environmentId, versionId }
);
setPages(pages, moduleId);
setPageSettings(deepCamelCase(appData?.editing_version?.page_settings));
setPageSettings(deepCamelCase(appData?.editing_version?.page_settings || appData?.page_settings));
// set starting page as homepage initially
let startingPage = appData.pages.find((page) => page.id === homePageId);

View file

@ -54,7 +54,7 @@ export const createEnvironmentsAndVersionsSlice = (set, get) => ({
setEnvironmentDropdownStatus: (status) => set({ initializedEnvironmentDropdown: status }),
fetchDevelopmentVersions: async (appId) => {
const developmentEnvironmentId = get().environments.find((environment) => environment.name === 'development').id;
const developmentEnvironmentId = get().environments.find((environment) => environment.name === 'production').id;
try {
const response = await appEnvironmentService.getVersionsByEnvironment(appId, developmentEnvironmentId);

View file

@ -205,7 +205,6 @@ export const createQueryPanelSlice = (set, get) => ({
setPreviewPanelExpanded,
executeRunPycode,
runTransformation,
executeWorkflow,
executeMultilineJS,
} = queryPanel;
const { onEvent } = eventsSlice;
@ -297,14 +296,6 @@ export const createQueryPanelSlice = (set, get) => ({
queryExecutionPromise = executeMultilineJS(query.options.code, query?.id, false, mode, parameters);
} else if (query.kind === 'runpy') {
queryExecutionPromise = executeRunPycode(query.options.code, query, false, mode, queryState);
} else if (query.kind === 'workflows') {
queryExecutionPromise = executeWorkflow(
moduleId,
query.options.workflowId,
query.options.blocking,
query.options?.params,
(currentAppEnvironmentId ?? environmentId) || selectedEnvironment?.id //TODO: currentAppEnvironmentId may no longer required. Need to check
);
} else {
queryExecutionPromise = dataqueryService.run(
queryId,
@ -465,7 +456,6 @@ export const createQueryPanelSlice = (set, get) => ({
setPreviewPanelExpanded,
executeRunPycode,
runTransformation,
executeWorkflow,
executeMultilineJS,
setIsPreviewQueryLoading,
} = queryPanel;
@ -514,14 +504,6 @@ export const createQueryPanelSlice = (set, get) => ({
queryExecutionPromise = executeMultilineJS(query.options.code, query?.id, true, '', parameters);
} else if (query.kind === 'runpy') {
queryExecutionPromise = executeRunPycode(query.options.code, query, true, 'edit', queryState);
} else if (query.kind === 'workflows') {
queryExecutionPromise = executeWorkflow(
moduleId,
query.options.workflowId,
query.options.blocking,
query.options?.params,
(currentAppEnvironmentId ?? environmentId) || selectedEnvironment?.id //TODO: currentAppEnvironmentId may no longer required. Need to check
);
} else {
queryExecutionPromise = dataqueryService.preview(query, options, currentVersionId, currentAppEnvironmentId);
}

View file

@ -4,6 +4,8 @@ import OverlayTrigger from 'react-bootstrap/OverlayTrigger';
import Popover from 'react-bootstrap/Popover';
import classNames from 'classnames';
import { computeColor } from '@/_helpers/utils';
import SolidIcon from '@/_ui/Icon/SolidIcons';
import { Tooltip } from 'react-bootstrap';
export const Color = ({
value,
@ -12,11 +14,12 @@ export const Color = ({
cyLabel,
asBoxShadowPopover = true,
meta,
outerWidth = '142px',
component,
styleDefinition,
onReset,
}) => {
value = component == 'Button' ? computeColor(styleDefinition, value, meta) : value;
const [showPicker, setShowPicker] = useState(false);
const darkMode = localStorage.getItem('darkMode') === 'true';
const colorPickerPosition = meta?.colorPickerPosition ?? '';
@ -28,7 +31,7 @@ export const Color = ({
left: '0px',
};
const outerStyles = {
width: '142px',
width: outerWidth,
height: '32px',
borderRadius: ' 6px',
display: 'flex',
@ -109,6 +112,15 @@ export const Color = ({
<div className="col tj-text-xsm p-0 color-slate12" data-cy={`${String(cyLabel)}-value`}>
{value}
</div>
{typeof onReset === 'function' && (
<div className="col-auto p-0">
<OverlayTrigger placement="left" overlay={<Tooltip id="reset-default-color">Reset to default</Tooltip>}>
<div onClick={onReset} className="color-reset">
<SolidIcon name="reset" />
</div>
</OverlayTrigger>
</div>
)}
</div>
);
};

View file

@ -105,11 +105,6 @@
align-items: center;
}
.p-3 {
padding: 16px;
padding-left: 0px !important;
}
.workspace-setting-buttons-wrap {
display: flex;
align-items: center;

View file

@ -3,9 +3,10 @@ import React from 'react';
import * as ToggleGroup from '@radix-ui/react-toggle-group';
import SolidIcon from '@/_ui/Icon/SolidIcons';
const ToggleGroupItem = ({ children, value, isIcon, ...restProps }) => {
const ToggleGroupItem = ({ children, value, isIcon, className, ...restProps }) => {
return (
<ToggleGroup.Item className="ToggleGroupItem" value={value} {...restProps}>
<ToggleGroup.Item className={`ToggleGroupItem ${className}`} value={value} {...restProps}>
{' '}
<div className="toggle-item" data-cy={`togglr-button-${value}`}>
{!isIcon ? (
children

View file

@ -0,0 +1,22 @@
import React from 'react';
import isEmpty from 'lodash/isEmpty';
import Accordion from '@/_ui/Accordion';
const AccordionForm = ({ formComponent, getLayout }) => {
const sections = Object.keys(formComponent)
.map((key) => ({
title: formComponent[key].title,
inputs: formComponent[key].inputs,
}))
.filter(({ inputs }) => inputs && !isEmpty(inputs));
const items = sections.map(({ title, inputs }) => ({
title: title,
isOpen: true,
children: getLayout(inputs),
}));
return <Accordion items={items} />;
};
export default AccordionForm;

View file

@ -102,7 +102,9 @@ export const DarkModeToggle = function DarkModeToggle({
</animated.svg>
{showText && (
<span className="dark-theme-toggle-btn-text">Switch to {!darkMode ? 'dark mode' : 'light mode'}</span>
<span className="dark-theme-toggle-btn-text" onClick={toggleDarkMode}>
Switch to {!darkMode ? 'dark mode' : 'light mode'}
</span>
)}
</div>
</OverlayTrigger>

View file

@ -8,13 +8,13 @@ import OAuth from '@/_ui/OAuth';
import Toggle from '@/_ui/Toggle';
import OpenApi from '@/_ui/OpenAPI';
import { Checkbox, CheckboxGroup } from '@/_ui/CheckBox';
import CodeHinter from '@/Editor/CodeEditor';
import CodeHinter from '@/AppBuilder/CodeEditor';
import GoogleSheets from '@/_components/Googlesheets';
import Slack from '@/_components/Slack';
import Zendesk from '@/_components/Zendesk';
import { ConditionFilter, CondtionSort, MultiColumn } from '@/_components/MultiConditions';
import Salesforce from '@/_components/Salesforce';
import ToolJetDbOperations from '@/Editor/QueryManager/QueryEditors/TooljetDatabase/ToolJetDbOperations';
import ToolJetDbOperations from '@/AppBuilder/QueryManager/QueryEditors/TooljetDatabase/ToolJetDbOperations';
import { orgEnvironmentVariableService, orgEnvironmentConstantService } from '../_services';
import { find, isEmpty } from 'lodash';
import { ButtonSolid } from './AppButton';

View file

@ -24,6 +24,7 @@ export default function OverflowTooltip({ children, className, whiteSpace = 'now
>
<div
ref={textElementRef}
className={rest.childrenClassName}
style={{
whiteSpace,
overflow: 'hidden',

View file

@ -39,7 +39,7 @@ export const SearchBox = forwardRef(
};
const handleClickOutside = (event) => {
if (ref.current && !ref.current.contains(event.target)) {
if (ref?.current && !ref.current.contains(event.target)) {
clearSearchText();
// Your function to be triggered
}

View file

@ -4,26 +4,26 @@ import { SortableContext, arrayMove, sortableKeyboardCoordinates } from '@dnd-ki
import { SortableItem } from './components';
import { useAppVersionStore } from '@/_stores/appVersionStore';
import { shallow } from 'zustand/shallow';
import useStore from '@/AppBuilder/_stores/store';
export function SortableList({ items, onChange, renderItem }) {
const sensors = useSensors(
useSensor(PointerSensor),
useSensor(PointerSensor, {
activationConstraint: { delay: 150 },
}),
useSensor(KeyboardSensor, {
coordinateGetter: sortableKeyboardCoordinates,
})
);
const { enableReleasedVersionPopupState, isVersionReleased } = useAppVersionStore(
(state) => ({
enableReleasedVersionPopupState: state.actions.enableReleasedVersionPopupState,
isVersionReleased: state.isVersionReleased,
}),
shallow
);
const shouldFreeze = useStore((state) => state.isVersionReleased || state.isEditorFreezed);
const enableReleasedVersionPopupState = useStore((state) => state.enableReleasedVersionPopupState);
return (
<DndContext
sensors={sensors}
onDragEnd={({ active, over }) => {
if (isVersionReleased) {
if (shouldFreeze) {
enableReleasedVersionPopupState();
return;
}

View file

@ -29,7 +29,7 @@ export function SortableItem({ children, id, classNames }) {
return (
<SortableItemContext.Provider value={context}>
<div className={classNames} ref={setNodeRef} style={style}>
<div {...attributes} {...listeners} ref={setNodeRef} className={classNames} style={style}>
{children}
</div>
</SortableItemContext.Provider>
@ -51,3 +51,16 @@ export function DragHandle({ show = true }) {
</button>
);
}
// hoc for wrapping components that need to be draggable
export function withDraggable(Component) {
return function DraggableComponent(props) {
const { attributes, listeners, ref } = useContext(SortableItemContext);
return (
<div {...attributes} {...listeners} ref={ref} className="draggable-container">
<Component {...props} />
</div>
);
};
}

View file

@ -1,36 +1,41 @@
import React from 'react';
import useStore from '@/AppBuilder/_stores/store';
import EmptyIllustration from '@assets/images/no-results.svg';
import { SortableList } from './SortableList';
import { DragHandle } from './components';
import { shallow } from 'zustand/shallow';
import _ from 'lodash';
const SortableComponent = ({ data, Element, ...restProps }) => {
const { onSort } = restProps;
const allpages = useStore((state) => _.get(state, 'modules.canvas.pages', []), shallow);
const reorderPages = useStore((state) => state.reorderPages);
const [items, setItems] = React.useState([]);
const showSearch = useStore((state) => state.showSearch);
const pageSearchResults = useStore((state) => state.pageSearchResults);
React.useEffect(() => {
setItems(data);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [JSON.stringify(data)]);
const pagesTorender =
showSearch && pageSearchResults !== null
? allpages.filter((page) => pageSearchResults.includes(page.id))
: allpages;
//function to check if the item in items array has changed position with respect to the original data
const didItemChangePosition = (originalArr, sortedArry) => {
return originalArr.some((item, index) => {
return item.id !== sortedArry[index].id;
});
};
React.useEffect(() => {
if (items.length > 0 && didItemChangePosition(data, items)) {
onSort(items);
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [items]);
if (pagesTorender.length === 0) {
return (
<div className="d-flex justify-content-center align-items-center" style={{ height: '100%' }}>
<div>
<EmptyIllustration />
<p data-cy={`label-no-pages-found`} className="mt-3 color-slate12">
No pages found
</p>
</div>
</div>
);
}
return (
<div style={{ maxWidth: 400, margin: '0' }}>
<SortableList
items={items}
onChange={setItems}
items={pagesTorender}
onChange={reorderPages}
renderItem={(page) => (
<SortableList.Item id={page.id} classNames={restProps.classNames}>
<Element page={page} {...restProps} />

View file

@ -1856,7 +1856,7 @@ const updateComponentLayout = (components, parentId, isCut = false) => {
};
//
const isChildOfTabsOrCalendar = (component, allComponents = [], componentParentId = undefined) => {
const parentId = componentParentId ?? component.component?.parent?.split('-').slice(0, -1).join('-');
const parentId = componentParentId ?? component.component?.parent?.match(/([a-fA-F0-9-]{36})-(.+)/)?.[1];
const parentComponent = allComponents.find((comp) => comp.componentId === parentId);

View file

@ -1,31 +1,17 @@
import { useCallback, useMemo } from 'react';
import { useMemo } from 'react';
import { shallow } from 'zustand/shallow';
import { useEditorStore } from '@/_stores/editorStore';
import { useAppDataStore } from '@/_stores/appDataStore';
import useStore from '@/AppBuilder/_stores/store';
const useAppDarkMode = () => {
const { appMode, setAppMode } = useEditorStore(
(state) => ({
appMode: state.appMode,
setAppMode: state.actions.setAppMode,
}),
shallow
);
const { isTJDarkMode } = useAppDataStore(
const { appMode, globalSettingsChanged, isTJDarkMode } = useStore(
(state) => ({
appMode: state.globalSettings.appMode,
globalSettingsChanged: state.globalSettingsChanged,
isTJDarkMode: state.isTJDarkMode,
}),
shallow
);
const handleAppModeChange = useCallback(
(appMode = 'auto') => {
setAppMode(appMode);
},
[setAppMode]
);
const isAppDarkMode = useMemo(() => {
if (appMode === 'light') {
return false;
@ -37,7 +23,7 @@ const useAppDarkMode = () => {
}, [appMode, isTJDarkMode]);
return {
onAppModeChange: handleAppModeChange,
onAppModeChange: globalSettingsChanged,
appMode,
isAppDarkMode,
};

View file

@ -93,6 +93,9 @@ function autoSaveApp(
global_settings: {
update: { ...diff },
},
page_settings: {
update: { ...diff },
},
};
const body = !type

View file

@ -40,7 +40,7 @@ export const useGridStore = create(
useGridStore.subscribe(({ draggingComponentId }) => {
if (draggingComponentId) {
document.querySelector(`.dragged-movable-control-box`)?.classList?.remove('dragged-movable-control-box');
document.querySelector(`[target-id='${draggingComponentId}']`).classList.add('dragged-movable-control-box');
document.querySelector(`[target-id='${draggingComponentId}']`)?.classList.add('dragged-movable-control-box');
} else if (document.querySelector(`.dragged-movable-control-box`)) {
document.querySelector(`.dragged-movable-control-box`)?.classList.remove('dragged-movable-control-box');
}

View file

@ -3,6 +3,7 @@ import { allOperations } from '@tooljet/plugins/client';
import { capitalize, cloneDeep } from 'lodash';
import { DATA_SOURCE_TYPE } from '@/_helpers/constants';
import { useDataQueriesStore } from '@/_stores/dataQueriesStore';
import useStore from '@/AppBuilder/_stores/store';
export const getDefaultOptions = (source) => {
const isSchemaUnavailable = Object.keys(schemaUnavailableOptions).includes(source.kind);
@ -42,7 +43,9 @@ export const getDefaultOptions = (source) => {
const computeQueryName = (source) => {
const { kind, type } = source;
const dataQueries = useDataQueriesStore.getState().dataQueries;
// TODO: Might need to move this out
// const dataQueries = useDataQueriesStore.getState().dataQueries;
const dataQueries = useStore.getState().dataQuery.queries.modules.canvas;
let currentQueriesForKind = dataQueries.filter((query) => query.kind === kind);
if (type == DATA_SOURCE_TYPE.SAMPLE) {
currentQueriesForKind = currentQueriesForKind.filter((query) => query.data_source_id === source.id);

View file

@ -125,7 +125,11 @@ $btn-dark-color: #FFFFFF;
.page-selector-panel-body {
height: 100%;
padding: 12px 16px;
background-color: var(--base);
border-right: 1px solid #DFE3E6;
&.dark-theme {
border-right: 1px solid var(--slate7);
}
.page-handler {
height: 32px !important;

View file

@ -214,6 +214,23 @@
}
}
.expired-gradient-border {
border: none !important;
position: relative;
background-color: var(--slate3) !important;
color: var(--indigo9) !important;
}
.expired-gradient-border::before {
content: "";
position: absolute;
bottom: 0;
left: 0;
right: 0;
height: 2px;
background: var(--upgrade-default);
}
.debugger-content {
background-color: var(--base);
@ -366,7 +383,7 @@
}
.modal-searchbar {
width: 200px;
width: 200px !important;
height: 36px;
float: right;
margin-right: 3.5rem !important;
@ -440,6 +457,17 @@
}
}
.select-datasource-list-modal {
.form-control:focus {
padding: 0px;
width: 200px !important;
}
.modal-body-content {
scrollbar-width: none;
}
}
.modal-body {
.datasource-modal-sidebar-footer {
border: 1px solid var(--slate5);
@ -543,6 +571,32 @@
.page-handler-wrapper {
background: transparent;
.tj-list-item-selected {
.custom-icon {
svg {
color: #4368E3;
stroke: #4368E3;
}
}
}
.custom-icon {
svg {
color: #6A727C;
stroke: #6A727C;
}
}
&.dark-theme {
.custom-icon {
svg {
color: #ffffff;
stroke: #ffffff;
}
}
}
// }
}
@ -657,10 +711,10 @@
align-items: center;
flex-direction: column;
position: absolute;
bottom: 0;
bottom: 0px;
padding-bottom: 8px;
width: 44px;
max-height: 180px;
max-height: 230px;
}
.tj-leftsidebar-icon-items {

View file

@ -3,26 +3,41 @@
}
.viewer {
.page-name, .navigation-area, .tj-list-item, .canvas-box {
.page-name,
.navigation-area,
.tj-list-item,
.canvas-box {
transition: var(--tran-01);
}
.page-name {
font-size: 14px;
}
.navigation-area {
z-index: 2;
border-right: 1px solid var(--slate6);
.left-sidebar-header-btn {
width: 25px;
height: 25px;
}
.tj-list-item {
width: 96%;
justify-content: start;
}
&.close {
transform: translateX(-90%);
transform: translateX(-90%);
.tj-list-item {
opacity: 0;
display: none;
}
}
.pin {
position: absolute;
right: -30px;
@ -30,13 +45,173 @@
}
&.sidebar-overlay:hover {
transform: translateX(0%);
box-shadow: 0px 12px 16px -4px rgba(16, 24, 40, 0.08), 0px 4px 6px -2px rgba(16, 24, 40, 0.03);
transform: translateX(0%);
box-shadow: 0px 12px 16px -4px rgba(16, 24, 40, 0.08), 0px 4px 6px -2px rgba(16, 24, 40, 0.03);
.tj-list-item {
opacity: 1;
display: unset;
.tj-list-item {
opacity: 1;
display: flex;
}
}
&.icon-only {
width: 65px !important;
padding: 0.5rem;
.tj-list-item {
justify-content: center;
.custom-icon {
display: flex;
align-items: center;
justify-content: center;
}
}
}
.accordion-item{
border: none;
}
.accordion-body{
padding: 4px 0 4px 16px !important;
}
.accordion-header{
height: auto !important;
position: relative;
.page-group{
height: 32px;
display: flex;
align-items: center;
svg{
height: 18px;
width: 18px;
}
}
.tj-list-item{
border:none !important;
padding-left: 8px !important;
outline: none !important;
&:hover{
background: none !important;
}
}
.active-page-group-highlight{
height: 32px;
width: 5px;
background: var(--primary-brand);
position: absolute;
left: -1rem;
}
}
.accordion-header button{
margin: 0;
padding: 0 !important;
}
}
}
.pages-settings {
.label-style {
width: unset !important;
.ToggleGroupItem {
width: unset !important;
.toggle-item {
width: unset !important;
padding: 6px;
;
}
}
}
.settings-tab {
.field {
label {
width: 100px;
white-space: nowrap;
text-overflow: ellipsis;
overflow: hidden;
}
}
}
}
.icon-selector {
width: 25px;
height: 25px;
margin-right: 6px;
justify-content: center;
.selector-wrapper {
display: flex;
align-items: center;
justify-content: center;
}
&:hover {
background: var(--slate7);
border-radius: 6px;
}
}
.page-menu-item {
.icon-selector {
svg {
color: #6A727C;
stroke: #6A727C;
}
}
&.is-selected {
.icon-selector {
svg {
color: #4368E3;
stroke: #4368E3;
}
}
}
&.dark-theme {
.icon-selector {
svg {
color: #ffffff;
stroke: #ffffff;
}
}
}
}
.active-group{
border-left: 2px solid red;
}
.page-group-collapse-icon{
margin-right: 6px;
position: relative;
top: 1px;
}
.page-drag-overlay {
background: var(--slate5);
svg {
margin-right: 12px;
width: 20px !important;
height: 20px !important;
color:#6A727C;
}
&.dark-theme{
color: #fff;
svg{
color:#fff;
}
}
}
.rename-input-buttons{
button{
box-shadow: 0px 1px 0px 0px rgba(0, 0, 0, 0.10);
border: 1px solid #CCD1D5;
}
}

View file

@ -20,7 +20,6 @@
will-change: initial;
}
.PopoverArrow {
fill: white;
}

File diff suppressed because it is too large Load diff

View file

@ -31,17 +31,18 @@ const AccordionItem = ({ open = true, index, title, children }) => {
}
return (
<div className="accordion-item">
<h2 onClick={() => setShow(!show)} className="accordion-header" id={`heading-${index}`}>
<button
className={cx('accordion-button', { collapsed: !show })}
type="button"
data-bs-toggle="collapse"
data-bs-target={`collapse-${index}`}
aria-expanded="false"
data-cy={`widget-accordion-${title.toLowerCase()}`}
>
<h2 className="accordion-header" id={`heading-${index}`} data-cy={`widget-accordion-${title.toLowerCase()}`}>
<div className={cx('accordion-button inspector')}>
<span className="text-capitalize accordion-title-text">{title}</span>
</button>
<div
type="button"
data-bs-toggle="collapse"
data-bs-target={`collapse-${index}`}
aria-expanded="false"
className={cx('accordion-item-trigger', { collapsed: !show })}
onClick={() => setShow((prev) => !prev)}
></div>
</div>
</h2>
<div
id={`collapse-${index}`}

View file

@ -5,6 +5,7 @@ import Skeleton from 'react-loading-skeleton';
import { ButtonSolid } from '../AppButton/AppButton';
import Overlay from 'react-bootstrap/Overlay';
import cx from 'classnames';
import { Tooltip } from 'react-tooltip'; // Import Tooltip
function FolderList({
overlayFunctionParam,
@ -23,6 +24,10 @@ function FolderList({
overLayComponent,
darkMode,
toolTipText,
disableHoverOption = false,
customStyles,
CustomIcon,
toolTipDisabled = false,
...restProps
}) {
const [isHovered, setIsHovered] = useState(false);
@ -49,6 +54,8 @@ function FolderList({
setIsHoveredInside(false);
};
const computedStyles = customStyles ? customStyles(selectedItem, isHovered) : {};
return (
<>
{!isLoading ? (
@ -59,13 +66,17 @@ function FolderList({
'tj-list-item-disabled': disabled,
'tj-list-item-option-opened': showGroupOptions,
})}
style={backgroundColor && { backgroundColor }}
style={{
...(backgroundColor && { backgroundColor }),
...{ ...computedStyles.pill, ...computedStyles.text },
}}
onClick={isHoveredInside ? menuToggle : onClick}
data-cy={`${dataCy}-list-item`}
onMouseEnter={handleMouseEnter}
onMouseLeave={handleMouseLeave}
data-tooltip-content={toolTipText}
data-tooltip-id="button-content"
data-tooltip-hidden={!toolTipDisabled}
>
{LeftIcon && (
<div className="tj-list-item-icon">
@ -73,10 +84,24 @@ function FolderList({
</div>
)}
{CustomIcon && (
<div className="custom-icon">
<CustomIcon
color={computedStyles?.icon?.color}
style={{
width: '18px',
height: '18px',
color: computedStyles?.icon?.color,
stroke: computedStyles?.icon?.color,
}}
/>
</div>
)}
{children}
{RightIcon && <div className="tj-list-item-icon">{RightIcon && <SolidIcon name={RightIcon} />}</div>}
{overLayComponent && (isHovered || showGroupOptions) && (
{overLayComponent && ((!disableHoverOption && isHovered) || showGroupOptions) && (
<>
<div ref={target}>
<ButtonSolid
@ -87,7 +112,7 @@ function FolderList({
variant="tertiary"
onMouseEnter={handleMouseEnterInside}
onMouseLeave={handleMouseLeaveInside}
data-cy="groups-list-option-button"
dataCy={'groups-list-option-button'}
></ButtonSolid>
</div>
<Overlay
@ -114,6 +139,8 @@ function FolderList({
) : (
<Skeleton count={4} />
)}
<Tooltip id="button-content" place="right" style={{ zIndex: 99999, width: '150px' }} show={toolTipDisabled} />
</>
);
}

View file

@ -0,0 +1,21 @@
import React from 'react';
const Reset = ({ fill = '#6A727C', width = '10', className = '', viewBox = '0 0 10 10' }) => (
<svg
width={width}
height={width}
viewBox={viewBox}
fill="none"
xmlns="http://www.w3.org/2000/svg"
className={className}
>
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="M3.71935 1.71937C3.9983 1.44042 3.9983 0.988158 3.71935 0.70921C3.4404 0.430263 2.98814 0.430263 2.7092 0.70921L0.209197 3.20921C-0.0697323 3.48816 -0.0697323 3.94042 0.209197 4.21937L2.7092 6.71938C2.98814 6.99832 3.4404 6.99832 3.71935 6.71938C3.9983 6.44042 3.9983 5.98817 3.71935 5.70922L2.4387 4.42858H6.60714C7.69198 4.42858 8.57143 5.30802 8.57143 6.39287C8.57143 7.47773 7.69198 8.35716 6.60714 8.35716H4.99999C4.6055 8.35716 4.2857 8.67694 4.2857 9.07144C4.2857 9.46594 4.6055 9.78573 4.99999 9.78573H6.60714C8.48096 9.78573 10 8.26673 10 6.39287C10 4.51904 8.48096 3 6.60714 3H2.4387L3.71935 1.71937Z"
fill={fill}
/>
</svg>
);
export default Reset;

View file

@ -173,6 +173,7 @@ import Search01 from './Search01.jsx';
import ShiftButtonIcon from './ShiftButtonIcon.jsx';
import Unpin01 from './Unpin01.jsx';
import WarningUserNotFound from './WarningUserNotFound.jsx';
import Reset from './Reset.jsx';
const Icon = (props) => {
switch (props.name) {
@ -394,6 +395,8 @@ const Icon = (props) => {
return <RightOuterJoin {...props} />;
case 'row':
return <Row {...props} />;
case 'reset':
return <Reset {...props} />;
case 'sadrectangle':
return <SadRectangle {...props} />;
case 'search':

View file

@ -0,0 +1,74 @@
import React from 'react';
import { ButtonSolid } from '@/_ui/AppButton/AppButton';
import AddRectangle from '@/_ui/Icon/bulkIcons/AddRectangle';
import CodeHinter from '@/Editor/CodeEditor';
import InfoIcon from '@assets/images/icons/info.svg';
import Trash from '@/_ui/Icon/solidIcons/Trash';
import Select from '@/_ui/Select';
import Input from '@/_ui/Input';
import '@/_ui/Sort/sortStyles.scss';
export default ({ options, addNewKeyValuePair, removeKeyValuePair, keyValuePairValueChanged, buttonText }) => {
const darkMode = localStorage.getItem('darkMode') === 'true';
const sortOptions = [
{ value: 'asc', label: 'Ascending' },
{ value: 'desc', label: 'Descending' },
];
return (
<div>
{options.length === 0 && (
<div className="empty-key-value">
<InfoIcon style={{ width: '16px', marginRight: '5px' }} />
<span>There are no key value pairs added</span>
</div>
)}
{options.map((option, index) => {
return (
<div className="d-flex" key={index}>
<div className="d-flex mb-2 justify-content-between w-100">
<div className="w-100 sort-input">
<Input
value={option[0]}
className="form-control"
type="text"
placeholder="Field"
style={{ height: '32px' }}
onChange={(e) => keyValuePairValueChanged(e.target.value, 0, index)}
/>
</div>
<div className="w-100 sort-input">
<Select
options={sortOptions}
value={sortOptions.find((opt) => opt.value === option[1])}
onChange={(value) => keyValuePairValueChanged(value, 1, index)}
width={'100%'}
placeholder="Select direction"
/>
</div>
</div>
<button
className={`d-flex justify-content-center align-items-center delete-field-option bg-transparent border-0 rounded-0 border-top border-bottom border-end border-start rounded-start rounded-end trash ${
darkMode ? 'delete-field-option-dark' : ''
}`}
role="button"
onClick={() => {
removeKeyValuePair(index);
}}
>
<Trash fill="var(--slate9)" style={{ height: '16px' }} />
</button>
</div>
);
})}
<ButtonSolid
variant="ghostBlue"
size="sm"
onClick={() => addNewKeyValuePair(options)}
style={{ gap: '0px', padding: '2px 8px' }}
>
<AddRectangle width="15" fill="#3E63DD" opacity="1" secondaryFill="#ffffff" />
&nbsp;&nbsp;{buttonText}
</ButtonSolid>
</div>
);
};

View file

@ -0,0 +1,80 @@
import React from 'react';
import Input from '../Input';
import Trash from '@/_ui/Icon/solidIcons/Trash';
import { ButtonSolid } from '@/_ui/AppButton/AppButton';
import AddRectangle from '@/_ui/Icon/bulkIcons/AddRectangle';
import InfoIcon from '@assets/images/icons/info.svg';
import '@/_ui/Sort/sortStyles.scss';
import Select from '@/_ui/Select';
export default ({
options,
addNewKeyValuePair,
removeKeyValuePair,
keyValuePairValueChanged,
workspaceConstants,
isDisabled,
width,
}) => {
const darkMode = localStorage.getItem('darkMode') === 'true';
const sortOptions = [
{ value: 'asc', label: 'Ascending' },
{ value: 'desc', label: 'Descending' },
];
return (
<div className="table-content-wrapper">
{options.length === 0 && (
<div className="empty-key-value">
<InfoIcon style={{ width: '16px', marginRight: '5px' }} />
<span>There are no key value pairs added</span>
</div>
)}
{options.map((option, index) => (
<div className="d-flex align-items-top row-container query-manager-border-color" key={index}>
<Input
value={option[0]}
className="form-control"
type="text"
placeholder="Field"
style={{ width: width ? width : '300px', borderTopRightRadius: '0px', borderBottomRightRadius: '0px' }}
onChange={(e) => keyValuePairValueChanged(e.target.value, 0, index)}
/>
<Select
options={sortOptions}
value={sortOptions.find((opt) => opt.value === option[1])}
onChange={(value) => keyValuePairValueChanged(value, 1, index)}
width={'316px'}
height={'35px'}
placeholder="Select direction"
/>
<button
className={`d-flex justify-content-center align-items-center delete-field-option bg-transparent border-0 rounded-0 border-top border-bottom border-end rounded-end ${
darkMode ? 'delete-field-option-dark' : ''
}`}
style={{ height: '35px' }}
role="button"
disabled={isDisabled}
onClick={() => removeKeyValuePair(index)}
>
<Trash fill="var(--slate9)" style={{ height: '16px' }} />
</button>
</div>
))}
<div className="d-flex mb-2" style={{ height: '16px' }}>
<ButtonSolid
variant="ghostBlue"
size="sm"
onClick={() => addNewKeyValuePair(options)}
style={{ gap: '0px', paddingTop: '2px', paddingRight: '8px', paddingBottom: '2px', paddingLeft: '8px' }}
disabled={isDisabled}
>
<AddRectangle width="15" fill="#3E63DD" opacity="1" secondaryFill="#ffffff" />
&nbsp;&nbsp;Add
</ButtonSolid>
</div>
</div>
);
};

View file

@ -0,0 +1,53 @@
import React from 'react';
import _ from 'lodash';
import QueryEditor from './QueryEditor';
import SourceEditor from './SourceEditor';
import { deepClone } from '@/_helpers/utilities/utils.helpers';
export default ({
getter,
options = [['', '']],
optionchanged,
currentState,
isRenderedAsQueryEditor,
workspaceConstants,
isDisabled,
buttonText,
width,
}) => {
function addNewKeyValuePair(options) {
const newPairs = [...options, ['', '']];
optionchanged(getter, newPairs);
}
function removeKeyValuePair(index) {
options.splice(index, 1);
optionchanged(getter, options);
}
function keyValuePairValueChanged(value, keyIndex, index) {
if (!isRenderedAsQueryEditor) {
const newOptions = deepClone(options);
newOptions[index][keyIndex] = value;
options.length - 1 === index ? addNewKeyValuePair(newOptions) : optionchanged(getter, newOptions);
} else {
options[index][keyIndex] = value;
optionchanged(getter, options);
}
}
const commonProps = {
options,
addNewKeyValuePair,
removeKeyValuePair,
keyValuePairValueChanged,
isDisabled,
buttonText,
};
return isRenderedAsQueryEditor ? (
<QueryEditor {...commonProps} />
) : (
<SourceEditor {...commonProps} workspaceConstants={workspaceConstants} width={width} />
);
};

View file

@ -0,0 +1,96 @@
.query-manager-border-color {
input.form-control,
textarea,
.input-control {
gap: 16px !important;
background: var(--base) !important;
border: 1px solid var(--slate7) !important;
border-radius: 6px;
margin-bottom: 4px !important;
color: var(--slate12) !important;
transition: none;
height: 35px;
padding-left: 0.4375rem;
padding-right: 0.4375rem;
padding-top: 0.75rem;
padding-bottom: 0.75rem;
overflow-x: 'auto';
white-space: 'nowrap';
&:hover {
background: var(--slate1) !important;
border: 1px solid var(--slate8) !important;
-webkit-box-shadow: none !important;
box-shadow: none !important;
outline: none;
}
&:focus-visible {
background: var(--indigo2) !important;
border: 1px solid var(--indigo9) !important;
box-shadow: none !important;
}
&.input-error-border {
border-color: #DB4324 !important;
}
&:-webkit-autofill {
box-shadow: 0 0 0 1000px var(--base) inset !important;
-webkit-text-fill-color: var(--slate12) !important;
&:hover {
box-shadow: 0 0 0 1000px var(--slate1) inset !important;
-webkit-text-fill-color: var(--slate12) !important;
}
&:focus-visible {
box-shadow: 0 0 0 1000px var(--indigo2) inset !important;
-webkit-text-fill-color: var(--slate12) !important;
}
}
}
}
.empty-key-value {
border-radius: 6px;
padding: 10px;
text-align: center;
width: 625px;
height: 32px;
display: flex;
align-items: center;
justify-content: center;
margin-bottom: 10px;
color: #687076;
font-size: 12px;
font-weight: 400;
line-height: 20px;
border: 1px dashed #E6E8EB;
}
.trash {
height: 32px;
display: flex;
justify-content: 'center';
align-items: 'center';
}
.sort-input,
.table-content-wrapper{
.tj-app-input{
.form-control{
border-radius: 6px 0px 0px 6px !important;
}
}
}
.sort-input,
.table-content-wrapper{
.dark-theme.react-select__control{
border-radius: 0px 6px 6px 0px !important;
}
}

View file

@ -0,0 +1,38 @@
import { MigrationInterface, QueryRunner } from 'typeorm';
export class MoveHiddenFieldInAppVersionsToPageSettings1718357264489 implements MigrationInterface {
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.startTransaction();
try {
const pagesWitHiddenTrue = await queryRunner.query(
`SELECT id, show_viewer_navigation FROM app_versions WHERE show_viewer_navigation = 'true'`
);
const pagesWithHiddenFalse = await queryRunner.query(
`SELECT id, show_viewer_navigation FROM app_versions WHERE show_viewer_navigation = 'false'`
);
const idsToUpdate = pagesWitHiddenTrue.map((page) => page.id);
const idsToUpdateFalse = pagesWithHiddenFalse.map((page) => page.id);
if (idsToUpdate.length > 0) {
const quotedIds = idsToUpdate.map((id) => `'${id}'`).join(',');
await queryRunner.query(
`UPDATE app_versions SET page_settings = '{"properties": {"disableMenu": {"value": "{{false}}", "fxActive": false}}}' WHERE id IN (${quotedIds})`
);
}
if (idsToUpdateFalse.length > 0) {
const quotedIds = idsToUpdateFalse.map((id) => `'${id}'`).join(',');
await queryRunner.query(
`UPDATE app_versions SET page_settings = '{"properties": {"disableMenu": {"value": "{{true}}", "fxActive": false}}}' WHERE id IN (${quotedIds})`
);
}
await queryRunner.commitTransaction();
} catch (error) {
await queryRunner.rollbackTransaction();
throw error;
}
}
public async down(queryRunner: QueryRunner): Promise<void> {
return Promise.resolve();
}
}

View file

@ -0,0 +1,18 @@
import { MigrationInterface, QueryRunner, TableColumn } from 'typeorm';
export class AddPageSettingsColumnToAppVersionTable1716890766240 implements MigrationInterface {
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.addColumn(
'app_versions',
new TableColumn({
name: 'page_settings',
type: 'json',
isNullable: true,
})
);
}
public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.dropColumn('app_versions', 'page_settings');
}
}

View file

@ -0,0 +1,18 @@
import { MigrationInterface, QueryRunner, TableColumn } from 'typeorm';
export class AddIconFieldToPagesTable1716921638529 implements MigrationInterface {
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.addColumn(
'pages',
new TableColumn({
name: 'icon',
type: 'varchar',
isNullable: true,
})
);
}
public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.dropColumn('pages', 'icon');
}
}

View file

@ -153,6 +153,7 @@ export class AppsControllerV2 {
homePageId: versionToLoad.homePageId,
globalSettings: { ...versionToLoad.globalSettings, theme: appTheme },
showViewerNavigation: versionToLoad.showViewerNavigation,
pageSettings: versionToLoad?.pageSettings,
};
}
@ -230,7 +231,7 @@ export class AppsControllerV2 {
@UseGuards(JwtAuthGuard)
@UseInterceptors(ValidAppInterceptor)
@Put(':id/versions/:versionId/global_settings')
@Put([':id/versions/:versionId/global_settings', ':id/versions/:versionId/page_settings'])
async updateGlobalSettings(
@User() user,
@Param('id') id,
@ -408,6 +409,23 @@ export class AppsControllerV2 {
await this.pageService.updatePage(updatePageDto, versionId);
}
@UseGuards(JwtAuthGuard)
@UseInterceptors(ValidAppInterceptor)
@Put(':id/versions/:versionId/pages/reorder')
async reorderPages(@User() user, @Param('id') id, @Param('versionId') versionId, @Body() reorderPagesDto) {
const version = await this.appsService.findVersion(versionId);
const app = version.app;
if (app.id !== id) {
throw new BadRequestException();
}
const ability = await this.appsAbilityFactory.appsActions(user, id);
if (!ability.can(APP_RESOURCE_ACTIONS.VERSION_UPDATE, app)) {
throw new ForbiddenException('You do not have permissions to perform this action');
}
console.log(reorderPagesDto, 'payload');
await this.pageService.reorderPages(reorderPagesDto, versionId);
}
@UseGuards(JwtAuthGuard)
@UseInterceptors(ValidAppInterceptor)
@Delete(':id/versions/:versionId/pages')

View file

@ -23,4 +23,7 @@ export class AppVersionUpdateDto {
@IsOptional()
globalSettings: any;
@IsOptional()
pageSettings: any;
}

View file

@ -23,7 +23,7 @@ export class CreatePageDto {
disabled: boolean;
@IsOptional()
hidden: boolean;
hidden: Record<string, any>;
@IsOptional()
autoComputeLayout: boolean;

View file

@ -31,6 +31,9 @@ export class AppVersion extends BaseEntity {
@Column('simple-json', { name: 'global_settings' })
globalSettings;
@Column('simple-json', { name: 'page_settings' })
pageSettings;
@Column({ name: 'show_viewer_navigation' })
showViewerNavigation: boolean;

View file

@ -28,8 +28,11 @@ export class Page {
@Column()
disabled: boolean;
@Column('simple-json', { name: 'hidden' })
hidden;
@Column()
hidden: boolean;
icon: string;
@CreateDateColumn({ default: () => 'now()', name: 'created_at' })
createdAt: Date;

View file

@ -6,14 +6,32 @@ export function updateEntityReferences(node, resourceMapping: Record<string, str
const referenceExists = value;
if (referenceExists) {
const ref = value.replace('{{', '').replace('}}', '');
const matches = value.match(/{{(.*?)}}/g);
// gett all references {{entityName}}
if (matches) {
matches.forEach((match) => {
// remove curly braces and extract the entity "component.entityName.something"
const ref = match.slice(2, -2).trim();
const entityName = ref.split('.')[1];
const entityName = ref.split('.')[1];
if (resourceMapping[entityName]) {
const newValue = value.replace(entityName, resourceMapping[entityName]);
if (resourceMapping[entityName]) {
const newValue = value.replace(entityName, resourceMapping[entityName]);
node[key] = newValue;
value = newValue;
}
});
} else {
// kept this logic for fallback, although it should not be needed
const ref = value.replace('{{', '').replace('}}', '');
node[key] = newValue;
const entityName = ref.split('.')[1];
if (resourceMapping[entityName]) {
const newValue = value.replace(entityName, resourceMapping[entityName]);
node[key] = newValue;
}
}
}
} else if (typeof value === 'object') {
@ -45,11 +63,23 @@ export function findAllEntityReferences(node, allRefs): [] {
const referenceExists = value;
if (referenceExists) {
const ref = value.replace('{{', '').replace('}}', '');
const matches = value.match(/{{(.*?)}}/g);
if (matches) {
matches.forEach((match) => {
const ref = match.slice(2, -2).trim(); // Remove {{ and }}
const entityName = ref.split('.')[1];
if (entityName && !allRefs.includes(entityName)) {
allRefs.push(entityName);
}
});
} else {
// kept this logic for fallback, although it should not be needed
const ref = value.replace('{{', '').replace('}}', '');
const entityName = ref.split('.')[1];
const entityName = ref.split('.')[1];
allRefs.push(entityName);
allRefs.push(entityName);
}
}
} else if (typeof value === 'object') {
findAllEntityReferences(value, allRefs);

View file

@ -319,3 +319,35 @@ export const isValidDomain = (email: string, restrictedDomain: string): boolean
export const isHttpsEnabled = () => {
return !!process.env.TOOLJET_HOST?.startsWith('https');
};
export function isObject(obj) {
return obj && typeof obj === 'object';
}
export function mergeDeep(target, source, seen = new WeakMap()) {
if (!isObject(target)) {
target = {};
}
if (!isObject(source)) {
return target;
}
if (seen.has(source)) {
return seen.get(source);
}
seen.set(source, target);
for (const key in source) {
if (isObject(source[key])) {
if (!target[key]) {
Object.assign(target, { [key]: {} });
}
mergeDeep(target[key], source[key], seen);
} else {
Object.assign(target, { [key]: source[key] });
}
}
return target;
}

View file

@ -794,13 +794,13 @@ export class AppImportExportService {
const isParentTabOrCalendar = isChildOfTabsOrCalendar(component, pageComponents, parentId, true);
if (isParentTabOrCalendar) {
const childTabId = component.parent.split('-')[component.parent.split('-').length - 1];
const _parentId = component?.parent?.split('-').slice(0, -1).join('-');
const childTabId = component?.parent ? component.parent?.match(/([a-fA-F0-9-]{36})-(.+)/)?.[2] : null;
const _parentId = component?.parent ? component.parent?.match(/([a-fA-F0-9-]{36})-(.+)/)?.[1] : null;
const mappedParentId = newComponentIdsMap[_parentId];
parentId = `${mappedParentId}-${childTabId}`;
} else if (isChildOfKanbanModal(component, pageComponents, parentId, true)) {
const _parentId = component?.parent?.split('-').slice(0, -1).join('-');
const _parentId = component?.parent ? component.parent?.match(/([a-fA-F0-9-]{36})-(.+)/)?.[1] : null;
const mappedParentId = newComponentIdsMap[_parentId];
parentId = `${mappedParentId}-modal`;
@ -1885,13 +1885,13 @@ function transformComponentData(
);
if (isParentTabOrCalendar) {
const childTabId = component.parent.split('-')[component.parent.split('-').length - 1];
const _parentId = component?.parent?.split('-').slice(0, -1).join('-');
const childTabId = component?.parent ? component.parent?.match(/([a-fA-F0-9-]{36})-(.+)/)?.[2] : null;
const _parentId = component?.parent ? component.parent?.match(/([a-fA-F0-9-]{36})-(.+)/)?.[1] : null;
const mappedParentId = componentsMapping[_parentId];
parentId = `${mappedParentId}-${childTabId}`;
} else if (isChildOfKanbanModal(component, allComponents, parentId, true)) {
const _parentId = component?.parent?.split('-').slice(0, -1).join('-');
const _parentId = component?.parent ? component.parent?.match(/([a-fA-F0-9-]{36})-(.+)/)?.[1] : null;
const mappedParentId = componentsMapping[_parentId];
parentId = `${mappedParentId}-modal`;
@ -1940,7 +1940,7 @@ const isChildOfTabsOrCalendar = (
isNormalizedAppDefinitionSchema: boolean
) => {
if (componentParentId) {
const parentId = component?.parent?.split('-').slice(0, -1).join('-');
const parentId = component?.parent ? component.parent?.match(/([a-fA-F0-9-]{36})-(.+)/)?.[1] : null;
const parentComponent = allComponents.find((comp) => comp.id === parentId);
@ -1964,7 +1964,7 @@ const isChildOfKanbanModal = (
) => {
if (!componentParentId || !componentParentId.includes('modal')) return false;
const parentId = component?.parent?.split('-').slice(0, -1).join('-');
const parentId = component?.parent ? component.parent?.match(/([a-fA-F0-9-]{36})-(.+)/)?.[1] : null;
const parentComponent = allComponents.find((comp) => comp.id === parentId);

View file

@ -10,7 +10,7 @@ import { DataQuery } from 'src/entities/data_query.entity';
import { AppImportExportService } from './app_import_export.service';
import { DataSourcesService } from './data_sources.service';
import { Credential } from 'src/entities/credential.entity';
import { catchDbException, cleanObject, defaultAppEnvironments } from 'src/helpers/utils.helper';
import { catchDbException, cleanObject, defaultAppEnvironments, mergeDeep } from 'src/helpers/utils.helper';
import { AppUpdateDto } from '@dto/app-update.dto';
import { viewableAppsQueryUsingPermissions } from 'src/helpers/queries';
import { VersionEditDto } from '@dto/version-edit.dto';
@ -374,6 +374,7 @@ export class AppsService {
if (versionFrom) {
(appVersion.showViewerNavigation = versionFrom.showViewerNavigation),
(appVersion.globalSettings = versionFrom.globalSettings),
(appVersion.pageSettings = versionFrom.pageSettings),
await manager.save(appVersion);
const oldDataQueryToNewMapping = await this.createNewDataSourcesAndQueriesForVersion(
@ -529,7 +530,7 @@ export class AppsService {
const isChildOfTabsOrCalendar = (component, allComponents = [], componentParentId = undefined) => {
if (componentParentId) {
const parentId = component?.parent?.split('-').slice(0, -1).join('-');
const parentId = component?.parent?.match(/([a-fA-F0-9-]{36})-(.+)/)?.[1];
const parentComponent = allComponents.find((comp) => comp.id === parentId);
@ -545,7 +546,7 @@ export class AppsService {
if (!componentParentId.includes('modal')) return false;
if (componentParentId) {
const parentId = componentParentId.split('-').slice(0, -1).join('-');
const parentId = componentParentId.match(/([a-fA-F0-9-]{36})-(.+)/)?.[1];
const isParentKandban = allComponents.find((comp) => comp.id === parentId)?.type === 'Kanban';
return isParentKandban;
@ -597,8 +598,8 @@ export class AppsService {
const isParentTabOrCalendar = isChildOfTabsOrCalendar(component, page.components, parentId);
if (isParentTabOrCalendar) {
const childTabId = component.parent.split('-')[component.parent.split('-').length - 1];
const _parentId = component?.parent?.split('-').slice(0, -1).join('-');
const childTabId = component?.parent?.match(/([a-fA-F0-9-]{36})-(.+)/)?.[2];
const _parentId = component?.parent?.match(/([a-fA-F0-9-]{36})-(.+)/)?.[1];
const mappedParentId = oldComponentToNewComponentMapping[_parentId];
parentId = `${mappedParentId}-${childTabId}`;
@ -660,12 +661,12 @@ export class AppsService {
if (isParentTabOrCalendar) {
const childTabId = component?.parent?.split('-')[component?.parent?.split('-').length - 1];
const _parentId = component?.parent?.split('-').slice(0, -1).join('-');
const _parentId = component?.parent?.match(/([a-fA-F0-9-]{36})-(.+)/)?.[1];
const mappedParentId = oldComponentToNewComponentMapping[_parentId];
parentId = `${mappedParentId}-${childTabId}`;
} else if (isChildOfKanbanModal(component.parent, page.components)) {
const _parentId = component?.parent?.split('-').slice(0, -1).join('-');
const _parentId = component?.parent?.match(/([a-fA-F0-9-]{36})-(.+)/)?.[1];
const mappedParentId = oldComponentToNewComponentMapping[_parentId];
parentId = `${mappedParentId}-modal`;
@ -1022,7 +1023,7 @@ export class AppsService {
async updateAppVersion(version: AppVersion, body: AppVersionUpdateDto) {
const editableParams = {};
const { globalSettings, homePageId } = await this.appVersionsRepository.findOne({
const { globalSettings, homePageId, pageSettings } = await this.appVersionsRepository.findOne({
where: { id: version.id },
});
@ -1037,6 +1038,12 @@ export class AppsService {
};
}
if (body?.pageSettings) {
editableParams['pageSettings'] = {
...mergeDeep(pageSettings, body.pageSettings),
};
}
if (typeof body?.showViewerNavigation === 'boolean') {
editableParams['showViewerNavigation'] = body.showViewerNavigation;
}

View file

@ -72,11 +72,7 @@ export class ComponentsService {
for (const componentId in componentDiff) {
const { component } = componentDiff[componentId];
const doesComponentExist = await manager.findOneOrFail(Component, {
where: {
id: componentId,
},
});
const doesComponentExist = await manager.findAndCount(Component, { where: { id: componentId } });
if (doesComponentExist[1] === 0) {
return {
@ -133,8 +129,8 @@ export class ComponentsService {
async delete(componentIds: string[], appVersionId: string, isComponentCut = false) {
return dbTransactionForAppVersionAssociationsUpdate(async (manager: EntityManager) => {
const components = await manager.find(Component, {
where: { id: In(componentIds) },
const components = await manager.findBy(Component, {
id: In(componentIds),
});
if (!components.length) {
@ -161,7 +157,7 @@ export class ComponentsService {
) {
return dbTransactionForAppVersionAssociationsUpdate(async (manager: EntityManager) => {
for (const componentId in componenstLayoutDiff) {
const doesComponentExist = await manager.findOneOrFail(Component, { where: { id: componentId } });
const doesComponentExist = await manager.findAndCount(Component, { where: { id: componentId } });
if (doesComponentExist[1] === 0) {
return {