Skip to content

Commit

Permalink
feat(scene composer): icon picker rule changes
Browse files Browse the repository at this point in the history
  • Loading branch information
divya-sea committed Oct 2, 2023
1 parent fe0794f commit e126b53
Show file tree
Hide file tree
Showing 38 changed files with 340 additions and 195 deletions.
6 changes: 3 additions & 3 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions packages/scene-composer/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,7 @@
"@react-three/postprocessing": "2.6.2",
"@react-three/test-renderer": "^8.1.3",
"@tanstack/react-query": "^4.29.15",
"@testing-library/user-event": "^14.4.3",
"@tweenjs/tween.js": "^20.0.3",
"3d-tiles-renderer": "0.3.16",
"arraybuffer-to-string": "1.0.2",
Expand Down
4 changes: 2 additions & 2 deletions packages/scene-composer/src/assets/anchors/IconSvgs.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -69,8 +69,8 @@ export const CustomIconSvgString = `
<g fill='none' fillRule='evenodd' transform='translate(2 2)'>
<ellipse cx='26' cy='26' rx='21' ry='21' stroke='${colors.customBlue}' stroke-width='2' />
<circle cx='26' cy='26' r='16' fill='${colors.customBlue}'/>
<g transform='translate(26,26) scale(0.03) translate(${iconWidth}, ${iconHeight})'>
<path d='M22 80a48 48 0 1 1 96 0A48 48 0 1 1 48 80zM 224c0-17.7 14.3-32 32-32H96c17.7 0 32 14.3 32 32V448h32c17.7 0 32 14.3 32 32s-14.3 32-32 32H32c-17.7 0-32-14.3-32-32s14.3-32 32-32H64V256H32c-17.7 0-32-14.3-32-32z' fill='${colors.infoRingWhite}' />
<g transform='translate(26,26) scale(0.03) translate(${iconWidth}, ${iconHeight})' fill=''>
<path d='M22 80a48 48 0 1 1 96 0A48 48 0 1 1 48 80zM 224c0-17.7 14.3-32 32-32H96c17.7 0 32 14.3 32 32V448h32c17.7 0 32 14.3 32 32s-14.3 32-32 32H32c-17.7 0-32-14.3-32-32s14.3-32 32-32H64V256H32c-17.7 0-32-14.3-32-32z' fill=''/>
</g>
</g>
</svg>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { IconLookup, findIconDefinition } from '@fortawesome/fontawesome-svg-core';
import { ThreeEvent, extend } from '@react-three/fiber';
import React, { ReactElement, useCallback, useContext, useEffect, useMemo, useRef, useState } from 'react';
import * as THREE from 'three';
import { IconLookup, findIconDefinition } from '@fortawesome/fontawesome-svg-core';

import {
CustomIconSvgString,
Expand All @@ -25,6 +25,7 @@ import {
SceneResourceType,
SelectedAnchor,
} from '../../../../interfaces';
import { IIconLookup } from '../../../../models/SceneModels';
import { IAnchorComponentInternal, ISceneNodeInternal, useStore, useViewOptionState } from '../../../../store';
import { applyDataBindingTemplate } from '../../../../utils/dataBindingTemplateUtils';
import { findComponentByType } from '../../../../utils/nodeUtils';
Expand All @@ -33,7 +34,6 @@ import { colors } from '../../../../utils/styleUtils';
import { Anchor } from '../../../three';
import { WidgetSprite, WidgetVisual } from '../../../three/visuals';
import svgIconToWidgetSprite from '../common/SvgIconToWidgetSprite';
import { IIconLookup } from '../../../../models/SceneModels';

export interface AnchorWidgetProps {
node: ISceneNodeInternal;
Expand All @@ -45,8 +45,6 @@ export interface AnchorWidgetProps {
navLink?: INavLink;
}

type overrideCustomColorType = (value: string | undefined) => void;

// Adds the custom objects to React Three Fiber
extend({ Anchor, WidgetVisual, WidgetSprite });

Expand Down Expand Up @@ -95,13 +93,8 @@ export function AsyncLoadedAnchorWidget({

const [_parent, setParent] = useState<THREE.Object3D | undefined>(getObject3DFromSceneNodeRef(node.parentRef));
const [overrideCustomColor, setOverrideCustomColor] = useState<string | undefined>(undefined);
const selectedIconDefinition = findIconDefinition({
prefix: customIcon?.prefix,
iconName: customIcon?.iconName,
} as IconLookup);
const newCustomIcon = selectedIconDefinition?.icon[4] as string;
const newIconWidth = selectedIconDefinition?.icon[0];
const newIconHeight = selectedIconDefinition?.icon[1];
const [overrideCustomIcon, setOverrideCustomIcon] = useState<IIconLookup | undefined>(undefined);

const baseScale = useMemo(() => {
// NOTE: For Fixed Size value was [0.05, 0.05, 1]
const defaultScale = autoRescale ? [0.05, 0.05, 1] : [0.5, 0.5, 1];
Expand All @@ -113,16 +106,54 @@ export function AsyncLoadedAnchorWidget({
useEffect(() => {
setParent(node.parentRef ? getObject3DFromSceneNodeRef(node.parentRef) : undefined);
}, [node.parentRef]);

// Evaluate visual state based on data binding
const visualState = useMemo(() => {
getCustomIconColor(ruleResult, setOverrideCustomColor);
const ruleTargetInfo = getSceneResourceInfo(ruleResult as string);
// Anchor widget only accepts icon, otherwise, default to Info icon
return ruleTargetInfo.type === SceneResourceType.Icon ? ruleTargetInfo.value : defaultIcon;
const ruleTargetInfo = getSceneResourceInfo(ruleResult.target as string);
if (ruleTargetInfo.type === SceneResourceType.Icon) {
// check overrideCustomColor is defined but not same as rule targetMetadata color
if (overrideCustomColor && overrideCustomColor !== ruleResult.targetMetadata?.color) {
setOverrideCustomColor(ruleResult.targetMetadata?.color);
}
// check overrideCustomColor is undefined but targetMetadata color is defined
if (!overrideCustomColor && ruleResult.targetMetadata?.color) {
setOverrideCustomColor(ruleResult.targetMetadata?.color);
}

// check overrideCustomIcon is defined but not same as rule targetMetadata icon
if (
overrideCustomIcon &&
(overrideCustomIcon.iconName !== ruleResult.targetMetadata?.iconName ||
overrideCustomIcon.prefix !== ruleResult.targetMetadata?.iconPrefix)
) {
setOverrideCustomIcon({
prefix: ruleResult.targetMetadata?.iconPrefix,
iconName: ruleResult.targetMetadata?.iconName,
} as IIconLookup);
}

// check targetMetadata icon is defined
if (ruleResult.targetMetadata && (ruleResult.targetMetadata.iconName || ruleResult.targetMetadata.iconPrefix)) {
setOverrideCustomIcon({
prefix: ruleResult.targetMetadata?.iconPrefix,
iconName: ruleResult.targetMetadata?.iconName,
} as IIconLookup);
}
return ruleTargetInfo.value;
}
return defaultIcon;
}, [ruleResult]);

const visualRuleCustomColor = overrideCustomColor !== undefined ? overrideCustomColor : chosenColor;
const visualCustomIcon =
overrideCustomIcon !== undefined && overrideCustomIcon.iconName !== undefined ? overrideCustomIcon : customIcon;
const selectedIconDefinition = findIconDefinition({
prefix: visualCustomIcon?.prefix,
iconName: visualCustomIcon?.iconName,
} as IconLookup);
const newCustomIcon = selectedIconDefinition?.icon[4] as string;
const newIconWidth = selectedIconDefinition?.icon[0];
const newIconHeight = selectedIconDefinition?.icon[1];
const defaultVisualMap = useMemo(() => {
// NOTE: Due to the refactor from a Widget Visual (SVG to Mesh) to a Widget Sprite (SVG to Sprite)
// we need a new way of showing selection. This is done by showing a transparent larger image BEHIND the
Expand All @@ -149,19 +180,19 @@ export function AsyncLoadedAnchorWidget({
return iconStrings.map((iconString, index) => {
const iconStyle = keys[index];
if (iconStyle === 'Custom' && (visualRuleCustomColor || newCustomIcon)) {
const modifiedIconStyle = visualRuleCustomColor || newCustomIcon;
let modifiedIconStyle = iconStyle;
if (visualRuleCustomColor) {
modifiedIconStyle = `${modifiedIconStyle}-${visualRuleCustomColor}`;
}
if (newCustomIcon) {
modifiedIconStyle = `${modifiedIconStyle}-${newCustomIcon}`;
}
const modifiedSvg = modifySvg(iconString, visualRuleCustomColor, newCustomIcon, newIconWidth, newIconHeight);
return svgIconToWidgetSprite(
modifiedSvg,
iconStyle,
modifiedIconStyle ? `${iconStyle}-${modifiedIconStyle}` : iconStyle,
isAlwaysVisible,
!autoRescale,
);
return svgIconToWidgetSprite(modifiedSvg, iconStyle, modifiedIconStyle, isAlwaysVisible, !autoRescale);
}
return svgIconToWidgetSprite(iconString, iconStyle, iconStyle, isAlwaysVisible, !autoRescale);
});
}, [autoRescale, CustomIconSvgString, visualRuleCustomColor, newCustomIcon]);
}, [autoRescale, CustomIconSvgString, visualRuleCustomColor, visualCustomIcon, newCustomIcon]);

const isAnchor = (nodeRef?: string) => {
const node = getSceneNodeByRef(nodeRef);
Expand Down Expand Up @@ -197,6 +228,7 @@ export function AsyncLoadedAnchorWidget({
navLink,
visualRuleCustomColor,
newCustomIcon,
visualCustomIcon,
]);

const onClick = useCallback(
Expand Down Expand Up @@ -295,19 +327,6 @@ export const AnchorWidget: React.FC<AnchorWidgetProps> = (props: AnchorWidgetPro
);
};

/**
* This method sets the custom icon color if it is returned from the rule.
* @param ruleTarget
* @param setOverrideCustomColor
*/
function getCustomIconColor(ruleTarget: string | number, setOverrideCustomColor: overrideCustomColorType) {
const ruleColor =
typeof ruleTarget === 'string' && ruleTarget.includes('Custom-')
? ruleTarget.split(':')[1].split('-')[1]
: undefined;
setOverrideCustomColor(ruleColor);
}

/**
* This method parse the svg string and fill the color
* @param iconString
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -143,7 +143,10 @@ describe('AnchorWidget', () => {
statements: [
{
expression: "alarm_status == 'ACTIVE'",
target: 'iottwinmaker.common.icon:Custom-#ffffff',
target: 'iottwinmaker.common.icon:Custom',
targetMetadata: {
color: '#ffffff',
},
},
],
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import React from 'react';
import { render } from '@testing-library/react';
import React from 'react';

import { SceneRulesPanel } from './SceneRulesPanel';

Expand Down Expand Up @@ -35,7 +35,6 @@ jest.mock('../../store/Store', () => {
describe('SceneRulesPanel returns expected elements.', () => {
it('SceneRulesPanel returns expected elements.', async () => {
const { container } = render(<SceneRulesPanel />);

expect(container).toMatchSnapshot();
});
});
19 changes: 15 additions & 4 deletions packages/scene-composer/src/components/panels/SceneRulesPanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,11 @@ import {
} from '@awsui/components-react';
import { useIntl } from 'react-intl';

import { sceneComposerIdContext } from '../../common/sceneComposerIdContext';
import LogProvider from '../../logger/react-logger/log-provider';
import { useSceneDocument } from '../../store';
import { IRuleBasedMapInternal, IRuleStatementInternal } from '../../store/internalInterfaces';
import { sceneComposerIdContext } from '../../common/sceneComposerIdContext';
import { validateRuleId } from '../../utils/inputValidationUtils';
import LogProvider from '../../logger/react-logger/log-provider';

import { ExpandableInfoSection } from './CommonPanelComponents';
import { SceneRuleTargetEditor } from './scene-rule-components/SceneRuleTargetEditor';
Expand Down Expand Up @@ -76,6 +76,7 @@ export const SceneRuleMapExpandableInfoSection: React.FC<
const items: IRuleStatementInternal[] = ruleMap.statements.map((rule) => ({
expression: rule.expression,
target: rule.target,
targetMetadata: rule.targetMetadata,
}));
if (newRule) {
items.push(newRule);
Expand Down Expand Up @@ -112,7 +113,13 @@ export const SceneRuleMapExpandableInfoSection: React.FC<
defaultMessage: 'e.g. value > 0',
description: 'AttributeEditor control placeholder',
})}
onChange={(event) => onUpdateRule(itemIndex, { expression: event.detail.value, target: item.target })}
onChange={(event) =>
onUpdateRule(itemIndex, {
expression: event.detail.value,
target: item.target,
targetMetadata: item.targetMetadata,
})
}
/>
),
},
Expand All @@ -121,7 +128,10 @@ export const SceneRuleMapExpandableInfoSection: React.FC<
control: (item, itemIndex) => (
<SceneRuleTargetEditor
target={item.target}
onChange={(target) => onUpdateRule(itemIndex, { expression: item.expression, target })}
targetMetadata={item.targetMetadata}
onChange={(target, targetMetadata) => {
onUpdateRule(itemIndex, { expression: item.expression, target, targetMetadata });
}}
/>
),
},
Expand Down Expand Up @@ -179,6 +189,7 @@ export const SceneRulesPanel: React.FC = () => {
errorText={errorMessage}
>
<Input
data-testid='input-rule'
value={newRuleBasedMapId}
onChange={(event) => {
setNewRuleBasedMapId(event.detail.value);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ exports[`SceneRulesPanel returns expected elements. SceneRulesPanel returns expe
>
<div
data-mocked="Input"
data-testid="input-rule"
value="mapId"
/>
</div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,8 @@ jest.mock('../../../../src/store/Store', () => {
__esModule: true,
...originalModule,
useSceneDocument: jest.fn(() => ({
getSceneProperty: jest.fn(),
getSceneRuleMapById: jest.fn(),
listSceneRuleMapIds: jest.fn(() => {
return ['rule1'];
}),
Expand Down

0 comments on commit e126b53

Please sign in to comment.