mirror of
https://github.com/ToolJet/ToolJet
synced 2026-05-23 08:58:26 +00:00
Merged changes
This commit is contained in:
commit
54df390149
17 changed files with 210 additions and 65 deletions
|
|
@ -1,4 +1,4 @@
|
|||
import React, { useState, forwardRef, useRef, useEffect } from 'react';
|
||||
import React, { useState, forwardRef, useRef, useEffect, useCallback } from 'react';
|
||||
import RenameIcon from '../Icons/RenameIcon';
|
||||
import Eye1 from '@/_ui/Icon/solidIcons/Eye1';
|
||||
import Play from '@/_ui/Icon/solidIcons/Play';
|
||||
|
|
@ -13,6 +13,7 @@ import { decodeEntities } from '@/_helpers/utils';
|
|||
import { canDeleteDataSource, canReadDataSource, canUpdateDataSource } from '@/_helpers';
|
||||
import useStore from '@/AppBuilder/_stores/store';
|
||||
import { useModuleId } from '@/AppBuilder/_contexts/ModuleContext';
|
||||
import { debounce } from 'lodash';
|
||||
|
||||
export const QueryManagerHeader = forwardRef(({ darkMode, setActiveTab, activeTab }, ref) => {
|
||||
const moduleId = useModuleId();
|
||||
|
|
@ -166,16 +167,17 @@ const NameInput = ({ onInput, value, darkMode, isDiabled, selectedQuery }) => {
|
|||
}
|
||||
}, [isFocused]);
|
||||
|
||||
const debouncedHandleInput = useCallback(
|
||||
debounce((newName) => {
|
||||
onInput(newName);
|
||||
}, 300),
|
||||
[onInput]
|
||||
);
|
||||
|
||||
const handleChange = (event) => {
|
||||
const sanitizedValue = event.target.value.replace(/[ \t&]/g, '');
|
||||
setName(sanitizedValue);
|
||||
};
|
||||
|
||||
const handleInput = (newName) => {
|
||||
const result = onInput(newName);
|
||||
if (!result) {
|
||||
setName(value);
|
||||
}
|
||||
debouncedHandleInput(sanitizedValue);
|
||||
};
|
||||
|
||||
return (
|
||||
|
|
@ -200,12 +202,12 @@ const NameInput = ({ onInput, value, darkMode, isDiabled, selectedQuery }) => {
|
|||
event.persist();
|
||||
if (event.keyCode === 13) {
|
||||
setIsFocused(false);
|
||||
handleInput(event.target.value);
|
||||
debouncedHandleInput(event.target.value);
|
||||
}
|
||||
}}
|
||||
onBlur={({ target }) => {
|
||||
setIsFocused(false);
|
||||
handleInput(target.value);
|
||||
debouncedHandleInput(target.value);
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
|
|
|
|||
|
|
@ -295,7 +295,7 @@ export function Select({ componentMeta, darkMode, ...restProps }) {
|
|||
</div>
|
||||
<div className="field mb-2" data-cy={`input-and-label-column-name`}>
|
||||
<CodeHinter
|
||||
initialValue={isMultiSelect ? `{{${markedAsDefault.includes(item?.value)}}}` : item?.default?.value}
|
||||
initialValue={isMultiSelect ? `{{${markedAsDefault?.includes(item?.value)}}}` : item?.default?.value}
|
||||
theme={darkMode ? 'monokai' : 'default'}
|
||||
mode="javascript"
|
||||
lineNumbers={false}
|
||||
|
|
|
|||
|
|
@ -134,7 +134,7 @@ const MobileNavigationMenu = ({ pages, switchPage, currentPageId, darkMode, chan
|
|||
version: selectedVersionName,
|
||||
env: selectedEnvironmentName,
|
||||
};
|
||||
switchPage(pageId, pages.find((page) => page.id === pageId)?.handle, Object.entries(queryParams), true);
|
||||
switchPage(pageId, pages.find((page) => page.id === pageId)?.handle, Object.entries(queryParams));
|
||||
};
|
||||
var styles = {
|
||||
bmBurgerButton: {
|
||||
|
|
|
|||
|
|
@ -95,7 +95,7 @@ export const ViewerSidebarNavigation = ({
|
|||
version: selectedVersionName,
|
||||
env: selectedEnvironmentName,
|
||||
};
|
||||
switchPage(pageId, pages.find((page) => page.id === pageId)?.handle, Object.entries(queryParams), true);
|
||||
switchPage(pageId, pages.find((page) => page.id === pageId)?.handle, Object.entries(queryParams));
|
||||
};
|
||||
|
||||
const isLicensed =
|
||||
|
|
|
|||
|
|
@ -41,6 +41,15 @@ export const Filter = memo(({ table, darkMode, setFilters, setShowFilter }) => {
|
|||
[columns]
|
||||
);
|
||||
|
||||
const isFilterComplete = (filter) => {
|
||||
if (!filter.id) return false;
|
||||
if (!filter.value?.condition) return false;
|
||||
// For isEmpty/isNotEmpty operations, we don't need a value
|
||||
if (['isEmpty', 'isNotEmpty'].includes(filter.value.condition)) return true;
|
||||
// For other operations, we need a value
|
||||
return filter.value?.value !== undefined && filter.value?.value !== '';
|
||||
};
|
||||
|
||||
const filterOperationChanged = (index, value) => {
|
||||
const newFilters = [...localFilters];
|
||||
newFilters[index].value = {
|
||||
|
|
@ -52,13 +61,12 @@ export const Filter = memo(({ table, darkMode, setFilters, setShowFilter }) => {
|
|||
newFilters[index].value.value = '';
|
||||
}
|
||||
setLocalFilters(newFilters);
|
||||
applyFilters(newFilters.filter((filter) => filter.id !== ''));
|
||||
debouncedFilterChanged(newFilters);
|
||||
};
|
||||
|
||||
const debouncedFilterChanged = useCallback(
|
||||
(newFilters) => {
|
||||
const validFilters = newFilters.filter((filter) => filter.id !== '');
|
||||
|
||||
const validFilters = newFilters.filter(isFilterComplete);
|
||||
applyFilters(validFilters);
|
||||
},
|
||||
[applyFilters]
|
||||
|
|
|
|||
|
|
@ -109,6 +109,12 @@ export const TableContainer = ({
|
|||
}));
|
||||
}, [rowsPerPage, setPagination]);
|
||||
|
||||
useEffect(() => {
|
||||
if (serverSideSearch && globalFilter?.trim() !== '') {
|
||||
setPagination((prev) => ({ ...prev, pageIndex: 0 }));
|
||||
}
|
||||
}, [globalFilter, serverSideSearch, setPagination]);
|
||||
|
||||
useEffect(() => {
|
||||
setColumnOrder(columns.map((column) => column.id));
|
||||
}, [columns, setColumnOrder]);
|
||||
|
|
|
|||
|
|
@ -27,7 +27,7 @@ export const TableRow = ({
|
|||
|
||||
return (
|
||||
<tr
|
||||
key={row.uniqueId}
|
||||
key={`${row.id}-${virtualRow.index}`} // Added virtualRow.index to make the key unique and work with useVirtualizer
|
||||
ref={measureElement}
|
||||
data-index={virtualRow.index}
|
||||
style={{
|
||||
|
|
|
|||
|
|
@ -179,7 +179,9 @@ export const TableExposedVariables = ({
|
|||
// Expose applied filters
|
||||
useEffect(() => {
|
||||
setExposedVariables({ filters: appliedFilters.map((filter) => filter.value) });
|
||||
mounted && fireEvent('onFilterChanged');
|
||||
if (appliedFilters.length > 0) {
|
||||
mounted && fireEvent('onFilterChanged');
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [appliedFilters, setExposedVariables, fireEvent]); // Didn't add mounted as it's not a dependency
|
||||
|
||||
|
|
@ -248,20 +250,30 @@ export const TableExposedVariables = ({
|
|||
|
||||
useEffect(() => {
|
||||
function selectRow(key, value) {
|
||||
const item = tableData.find((item) => item[key] == value);
|
||||
const index = data.findIndex((item) => item[key] == value);
|
||||
const item = index !== -1 ? data[index] : null;
|
||||
if (item) {
|
||||
setRowSelection({ [item.id - 1]: true });
|
||||
setRowSelection({ [index]: true });
|
||||
}
|
||||
setExposedVariables({
|
||||
selectedRow: item,
|
||||
selectedRowId: isNaN(item?.id) ? String(item?.id) : item?.id,
|
||||
});
|
||||
}
|
||||
|
||||
function deselectRow(key, value) {
|
||||
const item = tableData.find((item) => item[key] == value);
|
||||
const index = data.findIndex((item) => item[key] == value);
|
||||
const item = index !== -1 ? data[index] : null;
|
||||
if (item) {
|
||||
setRowSelection({ [item.id - 1]: false });
|
||||
setRowSelection({ [index]: false });
|
||||
}
|
||||
setExposedVariables({
|
||||
selectedRow: {},
|
||||
selectedRowId: null,
|
||||
});
|
||||
}
|
||||
setExposedVariables({ selectRow, deselectRow });
|
||||
}, [tableData, setExposedVariables, setRowSelection]);
|
||||
}, [data, setExposedVariables, setRowSelection]);
|
||||
|
||||
// CSA to set & clear filters
|
||||
useEffect(() => {
|
||||
|
|
|
|||
|
|
@ -7,7 +7,6 @@ import {
|
|||
getFilteredRowModel,
|
||||
} from '@tanstack/react-table';
|
||||
import { applyFilters } from '../_components/Header/_components/Filter/filterUtils';
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
|
||||
export function useTable({
|
||||
data,
|
||||
|
|
@ -33,15 +32,7 @@ export function useTable({
|
|||
|
||||
// When the columns change, the data is not getting re-rendered. So, we need to create a new data array
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
const newData = useMemo(
|
||||
() =>
|
||||
data.map((row) => ({
|
||||
...row,
|
||||
uniqueId: row.uniqueId || uuidv4(), // Use existing ID if available
|
||||
})),
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
[data, columns]
|
||||
);
|
||||
const newData = useMemo(() => [...data], [data, columns]);
|
||||
|
||||
const table = useReactTable({
|
||||
data: newData,
|
||||
|
|
@ -53,7 +44,6 @@ export function useTable({
|
|||
getFilteredRowModel: getFilteredRowModel(),
|
||||
enableColumnResizing: true,
|
||||
columnResizeMode: 'onChange',
|
||||
getRowId: (row) => row.uniqueId,
|
||||
enableRowSelection: true,
|
||||
enableMultiRowSelection: showBulkSelector,
|
||||
state: {
|
||||
|
|
|
|||
|
|
@ -326,6 +326,16 @@ const useAppData = (appId, moduleId, darkMode, mode = 'edit', { environmentId, v
|
|||
|
||||
// navigate(`/${getWorkspaceId()}/apps/${slug ?? appId}/${startingPage.handle}`);
|
||||
}
|
||||
|
||||
// Add page id and handle to the state on initial load
|
||||
const currentState = window.history.state || {};
|
||||
const pageInfo = {
|
||||
id: startingPage.id,
|
||||
handle: startingPage.handle,
|
||||
};
|
||||
const newState = { ...currentState, ...pageInfo };
|
||||
window.history.replaceState(newState, '', window.location.href);
|
||||
|
||||
setCurrentPageHandle(startingPage.handle);
|
||||
updateFeatureAccess();
|
||||
setCurrentPageId(startingPage.id, moduleId);
|
||||
|
|
|
|||
|
|
@ -94,7 +94,7 @@ export const createAppSlice = (set, get) => ({
|
|||
console.error('Error updating page:', error);
|
||||
}
|
||||
},
|
||||
switchPage: (pageId, handle, queryParams = []) => {
|
||||
switchPage: (pageId, handle, queryParams = [], isBackOrForward = false) => {
|
||||
get().debugger.resetUnreadErrorCount();
|
||||
// reset stores
|
||||
if (get().pageSwitchInProgress) {
|
||||
|
|
@ -139,14 +139,19 @@ export const createAppSlice = (set, get) => ({
|
|||
const queryParamsString = filteredQueryParams.map(([key, value]) => `${key}=${value}`).join('&');
|
||||
const slug = get().app.slug;
|
||||
|
||||
navigate(
|
||||
`/${isPreview ? 'applications' : getWorkspaceId() + '/apps'}/${slug ?? appId}/${handle}?${queryParamsString}`,
|
||||
{
|
||||
state: {
|
||||
isSwitchingPage: true,
|
||||
},
|
||||
}
|
||||
);
|
||||
if (!isBackOrForward) {
|
||||
navigate(
|
||||
`/${isPreview ? 'applications' : getWorkspaceId() + '/apps'}/${slug ?? appId}/${handle}?${queryParamsString}`,
|
||||
{
|
||||
state: {
|
||||
isSwitchingPage: true,
|
||||
id: pageId,
|
||||
handle: handle,
|
||||
},
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
const newPage = pages.find((p) => p.id === pageId);
|
||||
setResolvedPageConstants({
|
||||
id: newPage?.id,
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ const initialState = {
|
|||
creatingQueryInProcessId: null,
|
||||
queryConfirmationList: [],
|
||||
queuedActions: {},
|
||||
queryUpdates: {},
|
||||
queries: {
|
||||
modules: {
|
||||
canvas: [],
|
||||
|
|
@ -381,8 +382,11 @@ export const createDataQuerySlice = (set, get) => ({
|
|||
return;
|
||||
}
|
||||
const versionId = get().currentVersionId;
|
||||
dataqueryService
|
||||
.update(newValues?.id, versionId, newValues?.name, newValues?.options)
|
||||
const updatePromise = dataqueryService.update(newValues?.id, versionId, newValues?.name, newValues?.options);
|
||||
set((state) => {
|
||||
state.dataQuery.queryUpdates[newValues?.id] = updatePromise;
|
||||
});
|
||||
updatePromise
|
||||
.then((data) => {
|
||||
localStorage.removeItem('transformation');
|
||||
set((state) => {
|
||||
|
|
@ -401,7 +405,12 @@ export const createDataQuerySlice = (set, get) => ({
|
|||
state.dataQuery.isUpdatingQueryInProcess = false;
|
||||
});
|
||||
})
|
||||
.finally(() => setIsAppSaving(false));
|
||||
.finally(() => {
|
||||
setIsAppSaving(false);
|
||||
set((state) => {
|
||||
delete state.dataQuery.queryUpdates[newValues?.id];
|
||||
});
|
||||
});
|
||||
}, 500),
|
||||
runOnLoadQueries: async () => {
|
||||
const queries = get().dataQuery.queries.modules.canvas;
|
||||
|
|
|
|||
|
|
@ -206,7 +206,6 @@ export const createQueryPanelSlice = (set, get) => ({
|
|||
isOnLoad = false,
|
||||
moduleId = 'canvas'
|
||||
) => {
|
||||
//! TODO get this using get() when migrated into slice
|
||||
const {
|
||||
eventsSlice,
|
||||
dataQuery: dataQuerySlice,
|
||||
|
|
@ -227,6 +226,28 @@ export const createQueryPanelSlice = (set, get) => ({
|
|||
executeWorkflow,
|
||||
executeMultilineJS,
|
||||
} = queryPanel;
|
||||
const queryUpdatePromise = dataQuerySlice.queryUpdates[queryId];
|
||||
if (queryUpdatePromise) {
|
||||
setResolvedQuery(queryId, {
|
||||
isLoading: true,
|
||||
});
|
||||
return queryUpdatePromise.then(() =>
|
||||
get().queryPanel.runQuery(
|
||||
queryId,
|
||||
queryName,
|
||||
confirmed,
|
||||
mode,
|
||||
userSuppliedParameters,
|
||||
component,
|
||||
eventId,
|
||||
shouldSetPreviewData,
|
||||
isOnLoad,
|
||||
moduleId
|
||||
)
|
||||
);
|
||||
}
|
||||
//! TODO get this using get() when migrated into slice
|
||||
|
||||
const { onEvent } = eventsSlice;
|
||||
const { queryConfirmationList } = dataQuerySlice;
|
||||
|
||||
|
|
@ -494,6 +515,14 @@ export const createQueryPanelSlice = (set, get) => ({
|
|||
executeMultilineJS,
|
||||
setIsPreviewQueryLoading,
|
||||
} = queryPanel;
|
||||
const queryUpdatePromise = get().dataQuery.queryUpdates[query?.id];
|
||||
if (queryUpdatePromise) {
|
||||
setPreviewLoading(true);
|
||||
setIsPreviewQueryLoading(true);
|
||||
return queryUpdatePromise.then(() =>
|
||||
get().queryPanel.previewQuery(query, calledFromQuery, userSuppliedParameters, moduleId)
|
||||
);
|
||||
}
|
||||
const { onEvent } = eventsSlice;
|
||||
|
||||
let parameters = userSuppliedParameters;
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ import Label from '@/_ui/Label';
|
|||
import cx from 'classnames';
|
||||
import { getInputBackgroundColor, getInputBorderColor, getInputFocusedColor } from './utils';
|
||||
import { isMobileDevice } from '@/_helpers/appUtils';
|
||||
import useStore from '@/AppBuilder/_stores/store';
|
||||
|
||||
const { DropdownIndicator, ClearIndicator } = components;
|
||||
const INDICATOR_CONTAINER_WIDTH = 60;
|
||||
|
|
@ -39,7 +40,7 @@ export const CustomDropdownIndicator = (props) => {
|
|||
export const CustomClearIndicator = (props) => {
|
||||
return (
|
||||
<ClearIndicator {...props}>
|
||||
<ClearIndicatorIcon width={'18'} fill={'var(--borders-strong)'} className="cursor-pointer" />
|
||||
<ClearIndicatorIcon width={'18'} fill={'var(--borders-strong)'} className="cursor-pointer clear-indicator" />
|
||||
</ClearIndicator>
|
||||
);
|
||||
};
|
||||
|
|
@ -88,6 +89,7 @@ export const DropdownV2 = ({
|
|||
padding,
|
||||
} = styles;
|
||||
const isInitialRender = useRef(true);
|
||||
const [isMenuOpen, setIsMenuOpen] = useState(false);
|
||||
const [currentValue, setCurrentValue] = useState(() => findDefaultItem(schema));
|
||||
const isMandatory = validation?.mandatory ?? false;
|
||||
const options = properties?.options;
|
||||
|
|
@ -95,11 +97,14 @@ export const DropdownV2 = ({
|
|||
const { isValid, validationError } = validationStatus;
|
||||
const ref = React.useRef(null);
|
||||
const dropdownRef = React.useRef(null);
|
||||
const selectRef = React.useRef(null);
|
||||
const [visibility, setVisibility] = useState(properties.visibility);
|
||||
const [isDropdownLoading, setIsDropdownLoading] = useState(dropdownLoadingState);
|
||||
const [isDropdownDisabled, setIsDropdownDisabled] = useState(disabledState);
|
||||
const [searchInputValue, setSearchInputValue] = useState('');
|
||||
const [userInteracted, setUserInteracted] = useState(false);
|
||||
const currentMode = useStore((state) => state.currentMode);
|
||||
const isEditor = currentMode === 'edit';
|
||||
|
||||
const _height = padding === 'default' ? `${height}px` : `${height + 4}px`;
|
||||
const labelRef = useRef();
|
||||
|
|
@ -166,6 +171,12 @@ export const DropdownV2 = ({
|
|||
setExposedVariable('isValid', validationStatus?.isValid);
|
||||
};
|
||||
|
||||
const handleClickInEditor = (e) => {
|
||||
if (e.target.className.includes('clear-indicator') || isMenuOpen) return;
|
||||
e.stopPropagation();
|
||||
selectRef.current?.onControlMouseDown(e);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
setInputValue(findDefaultItem(advanced ? schema : options));
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
|
|
@ -353,15 +364,16 @@ export const DropdownV2 = ({
|
|||
...provided,
|
||||
padding: '0px',
|
||||
}),
|
||||
option: (provided) => ({
|
||||
option: (provided, _state) => ({
|
||||
...provided,
|
||||
backgroundColor: 'var(--surfaces-surface-01)',
|
||||
backgroundColor: _state.isFocused ? 'var(--interactive-overlays-fill-hover)' : 'var(--surfaces-surface-01)',
|
||||
color:
|
||||
selectedTextColor !== '#1B1F24'
|
||||
? selectedTextColor
|
||||
: isDropdownDisabled || isDropdownLoading
|
||||
? 'var(--text-disabled)'
|
||||
: 'var(--text-primary)',
|
||||
borderRadius: _state.isFocused && '8px',
|
||||
padding: '8px 6px 8px 38px',
|
||||
'&:hover': {
|
||||
backgroundColor: 'var(--interactive-overlays-fill-hover)',
|
||||
|
|
@ -429,8 +441,14 @@ export const DropdownV2 = ({
|
|||
_width={_width}
|
||||
top={'1px'}
|
||||
/>
|
||||
<div className="w-100 px-0 h-100 dropdownV2-widget" ref={ref}>
|
||||
<div
|
||||
className="w-100 px-0 h-100 dropdownV2-widget"
|
||||
ref={ref}
|
||||
onMouseDownCapture={isEditor && handleClickInEditor}
|
||||
>
|
||||
<Select
|
||||
ref={selectRef}
|
||||
menuIsOpen={isMenuOpen}
|
||||
isDisabled={isDropdownDisabled}
|
||||
value={selectOptions.filter((option) => option.value === currentValue)[0] ?? null}
|
||||
onChange={(selectedOption, actionProps) => {
|
||||
|
|
@ -460,6 +478,7 @@ export const DropdownV2 = ({
|
|||
ClearIndicator: CustomClearIndicator,
|
||||
}}
|
||||
isClearable
|
||||
tabSelectsValue={false}
|
||||
icon={icon}
|
||||
doShowIcon={iconVisibility}
|
||||
iconColor={iconColor}
|
||||
|
|
@ -467,8 +486,24 @@ export const DropdownV2 = ({
|
|||
darkMode={darkMode}
|
||||
optionsLoadingState={optionsLoadingState && advanced}
|
||||
menuPlacement="auto"
|
||||
onMenuOpen={() => fireEvent('onFocus')}
|
||||
onMenuClose={() => fireEvent('onBlur')}
|
||||
onMenuOpen={() => {
|
||||
setIsMenuOpen(true);
|
||||
fireEvent('onFocus');
|
||||
}}
|
||||
onMenuClose={() => {
|
||||
setIsMenuOpen(false);
|
||||
fireEvent('onBlur');
|
||||
}}
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === 'Enter' && !isMenuOpen) {
|
||||
setIsMenuOpen(true);
|
||||
e.preventDefault();
|
||||
}
|
||||
if (e.key === 'Escape' && isMenuOpen) {
|
||||
setIsMenuOpen(false);
|
||||
e.preventDefault();
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ import * as Icons from '@tabler/icons-react';
|
|||
const { ValueContainer, Placeholder } = components;
|
||||
import './multiselectV2.scss';
|
||||
|
||||
const CustomValueContainer = ({ ...props }) => {
|
||||
const CustomValueContainer = ({ children, ...props }) => {
|
||||
const selectProps = props.selectProps;
|
||||
const values = Array.isArray(selectProps?.value) && selectProps?.value?.map((option) => option.label);
|
||||
const isAllOptionsSelected = selectProps?.value.length === selectProps.options.length;
|
||||
|
|
@ -39,6 +39,13 @@ const CustomValueContainer = ({ ...props }) => {
|
|||
{isAllOptionsSelected ? 'All items are selected.' : values.join(', ')}
|
||||
</span>
|
||||
)}
|
||||
{/* Rendering children except Placeholder component to preserve the default behavior of react-select like focus
|
||||
handling */}
|
||||
{React.Children.map(children, (child) => {
|
||||
if (child.type !== Placeholder) {
|
||||
return child;
|
||||
}
|
||||
})}
|
||||
</span>
|
||||
</div>
|
||||
</ValueContainer>
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ import Label from '@/_ui/Label';
|
|||
const tinycolor = require('tinycolor2');
|
||||
import { CustomDropdownIndicator, CustomClearIndicator } from '../DropdownV2/DropdownV2';
|
||||
import { getInputBackgroundColor, getInputBorderColor, getInputFocusedColor } from '../DropdownV2/utils';
|
||||
import useStore from '@/AppBuilder/_stores/store';
|
||||
|
||||
export const MultiselectV2 = ({
|
||||
id,
|
||||
|
|
@ -62,6 +63,7 @@ export const MultiselectV2 = ({
|
|||
const isMandatory = validation?.mandatory ?? false;
|
||||
const multiselectRef = React.useRef(null);
|
||||
const labelRef = React.useRef(null);
|
||||
const selectRef = React.useRef(null);
|
||||
const [validationStatus, setValidationStatus] = useState(
|
||||
validate(selected?.length ? selected?.map((option) => option.value) : null)
|
||||
);
|
||||
|
|
@ -74,6 +76,8 @@ export const MultiselectV2 = ({
|
|||
const [searchInputValue, setSearchInputValue] = useState('');
|
||||
const _height = padding === 'default' ? `${height}px` : `${height + 4}px`;
|
||||
const [userInteracted, setUserInteracted] = useState(false);
|
||||
const currentMode = useStore((state) => state.currentMode);
|
||||
const isEditor = currentMode === 'edit';
|
||||
|
||||
const [isMultiselectOpen, setIsMultiselectOpen] = useState(false);
|
||||
useEffect(() => {
|
||||
|
|
@ -281,6 +285,12 @@ export const MultiselectV2 = ({
|
|||
}
|
||||
};
|
||||
|
||||
const handleClickInEditor = (e) => {
|
||||
if (e.target.className.includes('clear-indicator') || isMultiselectOpen) return;
|
||||
e.stopPropagation();
|
||||
selectRef.current?.onControlMouseDown(e);
|
||||
};
|
||||
|
||||
const setInputValue = (values) => {
|
||||
setSelected(values);
|
||||
setExposedVariables({
|
||||
|
|
@ -386,7 +396,7 @@ export const MultiselectV2 = ({
|
|||
}),
|
||||
option: (provided, _state) => ({
|
||||
...provided,
|
||||
backgroundColor: 'var(--surfaces-surface-01)',
|
||||
backgroundColor: _state.isFocused ? 'var(--interactive-overlays-fill-hover)' : 'var(--surfaces-surface-01)',
|
||||
color: _state.isDisabled
|
||||
? 'var(_--text-disbled)'
|
||||
: selectedTextColor !== '#1B1F24'
|
||||
|
|
@ -394,6 +404,7 @@ export const MultiselectV2 = ({
|
|||
: isMultiSelectDisabled || isMultiSelectLoading
|
||||
? 'var(--text-disabled)'
|
||||
: 'var(--text-primary)',
|
||||
borderRadius: _state.isFocused && '8px',
|
||||
padding: '8px 6px 8px 12px',
|
||||
'&:hover': {
|
||||
backgroundColor: 'var(--interactive-overlays-fill-hover)',
|
||||
|
|
@ -456,16 +467,9 @@ export const MultiselectV2 = ({
|
|||
_width={_width}
|
||||
top={'1px'}
|
||||
/>
|
||||
<div
|
||||
className="w-100 px-0 h-100"
|
||||
onClick={() => {
|
||||
if (!isMultiSelectDisabled) {
|
||||
fireEvent('onFocus');
|
||||
setIsMultiselectOpen(!isMultiselectOpen);
|
||||
}
|
||||
}}
|
||||
>
|
||||
<div className="w-100 px-0 h-100" onMouseDownCapture={isEditor && handleClickInEditor}>
|
||||
<Select
|
||||
ref={selectRef}
|
||||
menuId={id}
|
||||
isDisabled={isMultiSelectDisabled}
|
||||
value={selected}
|
||||
|
|
@ -490,9 +494,26 @@ export const MultiselectV2 = ({
|
|||
isMulti
|
||||
hideSelectedOptions={false}
|
||||
closeMenuOnSelect={false}
|
||||
tabSelectsValue={false}
|
||||
controlShouldRenderValue={false}
|
||||
isSearchable={false}
|
||||
onMenuOpen={() => {
|
||||
fireEvent('onFocus');
|
||||
setIsMultiselectOpen(true);
|
||||
fireEvent('onFocus');
|
||||
}}
|
||||
onMenuClose={() => {
|
||||
setIsMultiselectOpen(false);
|
||||
fireEvent('onBlur');
|
||||
}}
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === 'Enter' && !isMultiselectOpen) {
|
||||
setIsMultiselectOpen(true);
|
||||
e.preventDefault();
|
||||
}
|
||||
if (e.key === 'Escape' && isMultiselectOpen) {
|
||||
setIsMultiselectOpen(false);
|
||||
e.preventDefault();
|
||||
}
|
||||
}}
|
||||
// select props
|
||||
icon={icon}
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ import { useLocation, useNavigate, useParams } from 'react-router-dom';
|
|||
import { handleAppAccess } from '@/_helpers/handleAppAccess';
|
||||
import { getQueryParams } from '@/_helpers/routes';
|
||||
import queryString from 'query-string';
|
||||
import useStore from '@/AppBuilder/_stores/store';
|
||||
|
||||
export const AppsRoute = ({ children, componentType }) => {
|
||||
const params = useParams();
|
||||
|
|
@ -20,6 +21,7 @@ export const AppsRoute = ({ children, componentType }) => {
|
|||
});
|
||||
const clonedElement = React.cloneElement(children, extraProps);
|
||||
const navigate = useNavigate();
|
||||
const switchPage = useStore((state) => state.switchPage);
|
||||
|
||||
/*
|
||||
any extra logic specifc to the route can be done
|
||||
|
|
@ -29,6 +31,10 @@ export const AppsRoute = ({ children, componentType }) => {
|
|||
if (isValidSession) {
|
||||
onValidSession();
|
||||
}
|
||||
|
||||
// handle back and forward navigation
|
||||
window.addEventListener('popstate', handleBrowserNavigation);
|
||||
return () => window.removeEventListener('popstate', handleBrowserNavigation);
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [isValidSession]);
|
||||
|
||||
|
|
@ -69,5 +75,10 @@ export const AppsRoute = ({ children, componentType }) => {
|
|||
}
|
||||
};
|
||||
|
||||
const handleBrowserNavigation = (e) => {
|
||||
const { id, handle } = e.state;
|
||||
switchPage(id, handle, [], true);
|
||||
};
|
||||
|
||||
return <RouteLoader isLoading={isLoading}>{clonedElement}</RouteLoader>;
|
||||
};
|
||||
|
|
|
|||
Loading…
Reference in a new issue