Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions web/client/components/widgets/builder/wizard/FilterWizard.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -95,8 +95,8 @@ const FilterWizard = ({
}, [editorData, setValid]);

const tabContents = {
data: <FilterDataTab data={filterData} onChange={onChange} onOpenLayerSelector={onOpenLayerSelector} openFilterEditor={openFilterEditor} onEditorChange={onEditorChange} dashBoardEditing={dashBoardEditing} selections={selections} />,
layout: <FilterLayoutTab data={filterData} onChange={onChange} selections={selections} onEditorChange={onEditorChange} selectableItems={selectableItems} />,
data: <FilterDataTab data={filterData} onChange={onChange} onOpenLayerSelector={onOpenLayerSelector} openFilterEditor={openFilterEditor} onEditorChange={onEditorChange} dashBoardEditing={dashBoardEditing} selections={selections} interactions={editorData?.interactions || []} />,
layout: <FilterLayoutTab data={filterData} onChange={onChange} selections={selections} onEditorChange={onEditorChange} selectableItems={selectableItems} interactions={editorData?.interactions || []} />,
actions: <FilterActionsTab data={filterData} onChange={onChange} onEditorChange={onEditorChange} />
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,38 +24,27 @@ const TButton = tooltip(Button);
* @param {function} setShowConfiguration toggles the UI for configuration
* @param {boolean} isPluggable tells if the interaction can be plugged or not
* @param {boolean} isConfigurable tells if the interaction can be configured or not
* @param {object} plugConstraints describes whether plugging is disabled and why
* @returns {React.ReactElement}
*/
const InteractionButtons = ({ plugged, setPlugged, showConfiguration, setShowConfiguration = () => {}, isPluggable, isConfigurable, notConnectableForSpecialCase, notConnectableForSpecialCaseMsg}) => {

if (notConnectableForSpecialCase) {
return (
<FlexBox gap="xs" className="ms-interaction-buttons">
<TButton
disabled
borderTransparent
tooltip={<Message msgId={notConnectableForSpecialCaseMsg} />}
className="square-button"
>
<Glyphicon glyph="ban-circle" />
</TButton>
</FlexBox>
);
}
const InteractionButtons = ({ plugged, setPlugged, showConfiguration, setShowConfiguration = () => {}, isPluggable, isConfigurable, plugConstraints = {} }) => {
const { disabled: isPlugConstrained = false, reason: plugConstraintReason = null } = plugConstraints;
return (
<FlexBox gap="xs" className="ms-interaction-buttons">
<FlexBox gap="xs" className={`ms-interaction-buttons${isPlugConstrained ? ' is-disabled' : ''}`}>
{isConfigurable && <TButton
visible={isConfigurable}
disabled={isPlugConstrained}
onClick={() => setShowConfiguration(!showConfiguration)}
borderTransparent
tooltip={<Message msgId="widgets.filterWidget.targetAutomaticallyNotConnectableTooltip" />}
tooltip={plugConstraintReason || <Message msgId="widgets.filterWidget.targetAutomaticallyNotConnectableTooltip" />}
variant={showConfiguration ? "primary" : undefined}

>
<Glyphicon glyph="cog" />
</TButton>}
<TButton
disabled={!isPluggable}
disabled={!isPluggable || isPlugConstrained}
tooltip={plugConstraintReason || undefined}
onClick={() => setPlugged(!plugged)}
borderTransparent
variant={plugged ? "success" : undefined}
Expand All @@ -67,4 +56,3 @@ const InteractionButtons = ({ plugged, setPlugged, showConfiguration, setShowCon
};

export default InteractionButtons;

Original file line number Diff line number Diff line change
Expand Up @@ -14,25 +14,27 @@ import { Glyphicon } from 'react-bootstrap';
import { getWidgetInteractionTreeGenerated, getEditingWidget, getAllInteractionsWhileEditingSelector } from '../../../../../../selectors/widgets';
import InteractionTargetsList from './InteractionTargetsList';
import './interaction-wizard.less';
import { detachSingleChildCollections, filterTreeWithTarget } from '../../../../../../utils/InteractionUtils';
import { detachSingleChildCollections, filterTreeWithTarget, filterDimensionTreeByValueAttributeType } from '../../../../../../utils/InteractionUtils';
import Message from '../../../../../I18N/Message';
import tooltip from '../../../../../misc/enhancers/tooltip';

const targetTitleTranslationMap = {
"Apply filter": "widgets.filterWidget.applyFilter",
"Apply style": "widgets.filterWidget.applyStyle"
};

const TButton = tooltip(Button);

const InteractionEventsSelector = ({target, expanded, toggleExpanded = () => {}, interactionTree, interactions, sourceWidgetId, currentSourceId, onEditorChange, alreadyExistingInteractions}) => {
export const InteractionEventsSelector = ({target, expanded, toggleExpanded = () => {}, interactionTree, interactions, sourceWidgetId, currentSourceId, onEditorChange, alreadyExistingInteractions, valueAttributeType, sourceSelectionMode, targetTitleMsgIds = {}, removable = false, onRemove = () => {}}) => {

const filteredInteractionTree = useMemo(() => {
const filteredTree = filterTreeWithTarget(interactionTree, target) || [];
return detachSingleChildCollections(filteredTree, ['widgets', 'traces', "layers"]);
}, [interactionTree]);
const filteredTree = filterTreeWithTarget(interactionTree, target) || { children: [] };
const dimensionFilteredTree = target?.targetType === 'applyDimension'
? filterDimensionTreeByValueAttributeType(filteredTree, valueAttributeType)
: filteredTree;
return detachSingleChildCollections(dimensionFilteredTree, ['widgets', 'traces', "map", "layers"]) || { children: [] };
}, [interactionTree, target, valueAttributeType]);
const hasConnectableNodes = (filteredInteractionTree?.children || []).length > 0;
const targetTitleMsgId = targetTitleMsgIds[target.targetType];

return (<FlexBox className="ms-interactions-container" component="ul" column gap="sm">
<FlexBox component="li" gap="xs" column>
<FlexBox className="ms-interactions-event"gap="sm" centerChildrenVertically >
<FlexBox className="ms-interactions-event" gap="sm" centerChildrenVertically >
<Button
onClick={() => toggleExpanded()}
borderTransparent
Expand All @@ -42,21 +44,37 @@ const InteractionEventsSelector = ({target, expanded, toggleExpanded = () => {},
}
</Button>
<Glyphicon glyph={target?.glyph} />
<Text className="ms-flex-fill"><Message msgId={targetTitleTranslationMap[target.title] || ""} /></Text>


<Text className="ms-flex-fill">
{targetTitleMsgId ? <Message msgId={targetTitleMsgId} /> : target.title}
</Text>
{removable && (
<TButton
onClick={onRemove}
borderTransparent
tooltip={<Message msgId="widgets.filterWidget.delete" />}
style={{ padding: 0, background: 'transparent' }}>
<Glyphicon glyph="trash" />
</TButton>
)}
</FlexBox>
{expanded && <FlexBox className="ms-interactions-targets" component="ul" column gap="sm" >
<InteractionTargetsList
target={target}
interactionTree={interactionTree}
interactions={interactions}
sourceWidgetId={sourceWidgetId}
currentSourceId={currentSourceId}
onEditorChange={onEditorChange}
filteredInteractionTree={filteredInteractionTree}
alreadyExistingInteractions={alreadyExistingInteractions}
/>
{hasConnectableNodes ? (
<InteractionTargetsList
target={target}
interactionTree={interactionTree}
interactions={interactions}
sourceWidgetId={sourceWidgetId}
currentSourceId={currentSourceId}
onEditorChange={onEditorChange}
filteredInteractionTree={filteredInteractionTree}
alreadyExistingInteractions={alreadyExistingInteractions}
sourceSelectionMode={sourceSelectionMode}
/>
) : (
<FlexBox component="li" className="ms-interactions-empty-state">
<Text><Message msgId="widgets.filterWidget.noConnectableNodesAvailable" /></Text>
</FlexBox>
)}
</FlexBox>}
</FlexBox>
</FlexBox>);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import FlexBox from '../../../../../layout/FlexBox';
// import { filterTreeWithTarget } from '../../../../../../utils/InteractionUtils';
import InteractionsRow from './InteractionsRow';

const InteractionTargetsList = ({target, interactionTree, interactions, sourceWidgetId, currentSourceId, onEditorChange, filteredInteractionTree, alreadyExistingInteractions}) => {
const InteractionTargetsList = ({target, interactionTree, interactions, sourceWidgetId, currentSourceId, onEditorChange, filteredInteractionTree, alreadyExistingInteractions, sourceSelectionMode}) => {


const renderContainer = (children) => (
Expand All @@ -26,6 +26,7 @@ const InteractionTargetsList = ({target, interactionTree, interactions, sourceWi
currentSourceId={currentSourceId}
onEditorChange={onEditorChange}
alreadyExistingInteractions={alreadyExistingInteractions}
sourceSelectionMode={sourceSelectionMode}
/>
))}
</FlexBox>
Expand All @@ -39,4 +40,3 @@ const InteractionTargetsList = ({target, interactionTree, interactions, sourceWi
};

export default InteractionTargetsList;

Original file line number Diff line number Diff line change
@@ -1,9 +1,21 @@
import React, { useEffect, useState } from 'react';
import InteractionEventsSelector from "./InteractionEventsSelector";
import { TARGET_TYPES } from '../../../../../../utils/InteractionUtils';
import FlexBox from '../../../../../layout/FlexBox';

// currentSourceId is the source that will be source of the target, in filter widget 'filterId' is expected
const InteractionEditor = ({targets = [], sourceWidgetId, currentSourceId, onEditorChange = () => {}, isStyleOnly = false}) => {
const InteractionEditor = ({
targets = [],
sourceWidgetId,
currentSourceId,
onEditorChange = () => {},
isStyleOnly = false,
valueAttributeType,
sourceSelectionMode,
targetTitleMsgIds = {},
removableTargetTypes = [],
onRemoveTarget = () => {}
}) => {
const initialExpandedItems = targets.length > 0 ? [targets[0].targetType] : [];
const [expandedItems, setExpandedItems] = useState(initialExpandedItems);
const toggleExpanded = (name) => {
Expand All @@ -21,7 +33,17 @@ const InteractionEditor = ({targets = [], sourceWidgetId, currentSourceId, onEdi
}
}, [isStyleOnly]);

return <>
useEffect(() => {
if (targets.some(target => target.targetType === TARGET_TYPES.APPLY_DIMENSION)) {
setExpandedItems(items =>
items.includes(TARGET_TYPES.APPLY_DIMENSION)
? items
: [...items, TARGET_TYPES.APPLY_DIMENSION]
);
}
}, [targets]);

return (<FlexBox column gap="xs" className="ms-interactions-editor">
{targets.map(e => {
const expanded = expandedItems.includes(e.targetType);
return (<InteractionEventsSelector
Expand All @@ -32,9 +54,14 @@ const InteractionEditor = ({targets = [], sourceWidgetId, currentSourceId, onEdi
sourceWidgetId={sourceWidgetId}
currentSourceId={currentSourceId}
onEditorChange={onEditorChange}
valueAttributeType={valueAttributeType}
sourceSelectionMode={sourceSelectionMode}
targetTitleMsgIds={targetTitleMsgIds}
removable={removableTargetTypes.includes(e.targetType)}
onRemove={() => onRemoveTarget(e.targetType)}
/>);
})}
</>;
</FlexBox>);
};

export default InteractionEditor;
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Logic for dimension should be isolated somehow. Interaction row component can not contain the logic for every possible target.
Or we have different components for different actions to do (that maybe share a basic graphic component, that allows to place the row icon, text, tooltops, components etc..., if makes sense), or a logic should be in the container using component. This depends of course on the items.

Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,15 @@ import { Glyphicon } from 'react-bootstrap';
import {
findNodeById,
getItemPluggableStatus,
isLayerDimensionTarget,
TARGET_TYPES
} from '../../../../../../utils/InteractionUtils';
import InteractionButtons from './InteractionButtons';
import InteractionConfiguration from './InteractionConfiguration';
import { buildInteractionObject, matchesInteraction } from './interactionHelpers';
import { DEFAULT_CONFIGURATION } from './interactionConstants';
import Message from '../../../../../I18N/Message';
import tooltip from '../../../../../misc/enhancers/tooltip';

import LocalizedString from '../../../../../I18N/LocalizedString';

Expand All @@ -28,8 +30,11 @@ const itemTitleTranslationMap = {
"Map": "widgets.filterWidget.map"
};

const isMapTimeTargetNodePath = (nodePath) => nodePath === 'map.time' || /(?:^|\.)maps\[[^\]]+\]\.time$/.test(nodePath);
const TFlexBox = tooltip(FlexBox);

const InteractionsRow = ({item, target, interactions, sourceWidgetId, interactionTree, currentSourceId, onEditorChange, alreadyExistingInteractions}) => {

const InteractionsRow = ({item, target, interactions, sourceWidgetId, interactionTree, currentSourceId, onEditorChange, alreadyExistingInteractions, sourceSelectionMode}) => {
// from interactions we can derive if the target is plugged or not, and its configuration

const hasChildren = item?.children?.length > 0;
Expand Down Expand Up @@ -57,6 +62,14 @@ const InteractionsRow = ({item, target, interactions, sourceWidgetId, interactio
// Get configuration directly from existing interaction or use default
const configuration = existingInteraction?.configuration || DEFAULT_CONFIGURATION;
const plugged = existingInteraction?.plugged || false;
const sourcePluggedConnections = alreadyExistingInteractions.filter(i =>
i?.plugged === true
&& i?.source?.nodePath === sourceNodePath
);
const hasMapTimeConnection = sourcePluggedConnections.some(i => isMapTimeTargetNodePath(i?.target?.nodePath));
const hasNonMapTimeConnection = sourcePluggedConnections.some(i => !isMapTimeTargetNodePath(i?.target?.nodePath));
const currentItemIsMapTime = isMapTimeTargetNodePath(targetNodePath);
const mapTimeLockConflict = (hasMapTimeConnection && !currentItemIsMapTime) || (hasNonMapTimeConnection && currentItemIsMapTime);


const { directlyPluggable, configuredToForcePlug } = getItemPluggableStatus(item, target, configuration);
Expand Down Expand Up @@ -110,9 +123,73 @@ const InteractionsRow = ({item, target, interactions, sourceWidgetId, interactio
return alreadyExistingInteractions.filter(i => i.source.nodePath !== sourceNodePath).some(i => (i.targetType === TARGET_TYPES.APPLY_STYLE && target.targetType === TARGET_TYPES.APPLY_STYLE) && i.target.nodePath === targetNodePath && i.plugged);
}, [alreadyExistingInteractions, targetNodePath, sourceNodePath]);

const layerDimensionAlreadyConnected = useMemo(() => {
if (target.targetType !== TARGET_TYPES.APPLY_DIMENSION || !isLayerDimensionTarget(targetNodePath)) {
return false;
}
return alreadyExistingInteractions
.filter(i => i.source.nodePath !== sourceNodePath)
.some(i =>
i.targetType === TARGET_TYPES.APPLY_DIMENSION
&& i.target.nodePath === targetNodePath
&& i.plugged
);
}, [alreadyExistingInteractions, target.targetType, targetNodePath, sourceNodePath]);

const dimensionMultipleSelectionDisabled = target.targetType === TARGET_TYPES.APPLY_DIMENSION
&& sourceSelectionMode === 'multiple'
&& !plugged;

const plugConstraints = useMemo(() => {
if (styleAlreadyConnected) {
return {
disabled: true,
reason: <Message msgId="widgets.filterWidget.targetAlreadyConnectedToAnotherFilterTooltip" />
};
}

if (layerDimensionAlreadyConnected) {
return {
disabled: true,
reason: <Message msgId="widgets.filterWidget.targetAlreadyConnectedToAnotherFilterTooltip" />
};
}

if (dimensionMultipleSelectionDisabled) {
return {
disabled: true,
reason: <Message msgId="widgets.filterWidget.applyDimensionMultipleSelectionDisabledTooltip" />
};
}

if (mapTimeLockConflict) {
return {
disabled: true,
reason: <Message msgId="widgets.filterWidget.targetAlreadyConnectedToTimeTooltip" />
};
}

return {
disabled: false,
reason: null
};
}, [styleAlreadyConnected, layerDimensionAlreadyConnected, dimensionMultipleSelectionDisabled, mapTimeLockConflict]);
const rowPlugConstraints = item.type === 'element'
? plugConstraints
: {
disabled: false,
reason: null
};

return (
<FlexBox key={item.id} component="li" gap="xs" column>
<FlexBox gap="xs" className="ms-connection-row" centerChildrenVertically>
<TFlexBox
gap="xs"
className={`ms-connection-row${rowPlugConstraints.disabled ? ' is-disabled' : ''}`}
centerChildrenVertically
tooltip={rowPlugConstraints.disabled ? rowPlugConstraints.reason : null}
tooltipPosition="top"
>
{hasChildren && (
<Button
onClick={() => setExpanded(!expanded)}
Expand All @@ -133,11 +210,10 @@ const InteractionsRow = ({item, target, interactions, sourceWidgetId, interactio
setPlugged={handlePlugToggle}
showConfiguration={showConfiguration}
setShowConfiguration={setShowConfiguration}
notConnectableForSpecialCase={styleAlreadyConnected}
notConnectableForSpecialCaseMsg="widgets.filterWidget.targetAlreadyConnectedToStyleTooltip"
plugConstraints={plugConstraints}
/>
)}
</FlexBox>
</TFlexBox>
<InteractionConfiguration item={item} show={showConfiguration} configuration={configuration} setConfiguration={handleConfigurationChange} setPlugged={handlePlugToggle} target={target} />
{hasChildren && expanded && (
<FlexBox component="ul" column gap="xs">
Expand All @@ -152,6 +228,7 @@ const InteractionsRow = ({item, target, interactions, sourceWidgetId, interactio
currentSourceId={currentSourceId}
onEditorChange={onEditorChange}
alreadyExistingInteractions={alreadyExistingInteractions}
sourceSelectionMode={sourceSelectionMode}
/>
))}
</FlexBox>
Expand All @@ -161,4 +238,3 @@ const InteractionsRow = ({item, target, interactions, sourceWidgetId, interactio
};

export default InteractionsRow;

Loading
Loading