mirror of
https://github.com/ToolJet/ToolJet
synced 2026-05-24 01:18:23 +00:00
Merge branch 'refactor/app-builder-merge-main' into fix-refactor-copy-pasting-tab
This commit is contained in:
commit
76e970a1be
47 changed files with 3701 additions and 268 deletions
|
|
@ -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';
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 };
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -105,11 +105,6 @@
|
|||
align-items: center;
|
||||
}
|
||||
|
||||
.p-3 {
|
||||
padding: 16px;
|
||||
padding-left: 0px !important;
|
||||
}
|
||||
|
||||
.workspace-setting-buttons-wrap {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
22
frontend/src/_components/AccordionForm.jsx
Normal file
22
frontend/src/_components/AccordionForm.jsx
Normal 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;
|
||||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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';
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@ export default function OverflowTooltip({ children, className, whiteSpace = 'now
|
|||
>
|
||||
<div
|
||||
ref={textElementRef}
|
||||
className={rest.childrenClassName}
|
||||
style={{
|
||||
whiteSpace,
|
||||
overflow: 'hidden',
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
);
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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} />
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
};
|
||||
|
|
|
|||
|
|
@ -93,6 +93,9 @@ function autoSaveApp(
|
|||
global_settings: {
|
||||
update: { ...diff },
|
||||
},
|
||||
page_settings: {
|
||||
update: { ...diff },
|
||||
},
|
||||
};
|
||||
|
||||
const body = !type
|
||||
|
|
|
|||
|
|
@ -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');
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -20,7 +20,6 @@
|
|||
will-change: initial;
|
||||
}
|
||||
|
||||
|
||||
.PopoverArrow {
|
||||
fill: white;
|
||||
}
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -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}`}
|
||||
|
|
|
|||
|
|
@ -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} />
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
21
frontend/src/_ui/Icon/solidIcons/Reset.jsx
Normal file
21
frontend/src/_ui/Icon/solidIcons/Reset.jsx
Normal 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;
|
||||
|
|
@ -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':
|
||||
|
|
|
|||
74
frontend/src/_ui/Sort/QueryEditor.jsx
Normal file
74
frontend/src/_ui/Sort/QueryEditor.jsx
Normal 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" />
|
||||
{buttonText}
|
||||
</ButtonSolid>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
80
frontend/src/_ui/Sort/SourceEditor.jsx
Normal file
80
frontend/src/_ui/Sort/SourceEditor.jsx
Normal 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" />
|
||||
Add
|
||||
</ButtonSolid>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
53
frontend/src/_ui/Sort/index.js
Normal file
53
frontend/src/_ui/Sort/index.js
Normal 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} />
|
||||
);
|
||||
};
|
||||
96
frontend/src/_ui/Sort/sortStyles.scss
Normal file
96
frontend/src/_ui/Sort/sortStyles.scss
Normal 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;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
@ -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');
|
||||
}
|
||||
}
|
||||
18
server/migrations/1716921638529-AddIconFieldToPagesTable.ts
Normal file
18
server/migrations/1716921638529-AddIconFieldToPagesTable.ts
Normal 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');
|
||||
}
|
||||
}
|
||||
|
|
@ -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')
|
||||
|
|
|
|||
|
|
@ -23,4 +23,7 @@ export class AppVersionUpdateDto {
|
|||
|
||||
@IsOptional()
|
||||
globalSettings: any;
|
||||
|
||||
@IsOptional()
|
||||
pageSettings: any;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@ export class CreatePageDto {
|
|||
disabled: boolean;
|
||||
|
||||
@IsOptional()
|
||||
hidden: boolean;
|
||||
hidden: Record<string, any>;
|
||||
|
||||
@IsOptional()
|
||||
autoComputeLayout: boolean;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
Loading…
Reference in a new issue