Skip to content

Commit

Permalink
Merge branch 'release-3.x' into loader-fix
Browse files Browse the repository at this point in the history
  • Loading branch information
divya-sea authored Aug 16, 2023
2 parents acfded8 + a33b232 commit ec95e01
Show file tree
Hide file tree
Showing 15 changed files with 146 additions and 52 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ export interface AnchorWidgetProps {
valueDataBinding?: IValueDataBinding;
rule?: IRuleBasedMap;
navLink?: INavLink;
customColors?: string[];
}
type overrideCustomColorType = (rulevalue: string | undefined) => void;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import renderer, { act } from 'react-test-renderer';
import React from 'react';
import * as THREE from 'three';
import { useLoader } from '@react-three/fiber';
import wrapper from '@awsui/components-react/test-utils/dom';

import { AnchorWidget } from '../AnchorWidget';
import { DefaultAnchorStatus, DEFAULT_TAG_GLOBAL_SETTINGS, KnownComponentType } from '../../../../..';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -164,7 +164,8 @@ export const AnchorComponentEditor: React.FC<IAnchorComponentEditorProps> = ({

const hasIcon = iconSelectedOptionIndex >= 0;
const iconGridDefinition = hasIcon ? [{ colspan: 10 }, { colspan: 2 }] : [{ colspan: 12 }];
const isAllValid = tagStyle && iconOptions[iconSelectedOptionIndex]?.value === 'Custom';
const isCustomStyle = tagStyle && iconOptions[iconSelectedOptionIndex]?.value === 'Custom';

return (
<SpaceBetween size='s'>
<FormField label={intl.formatMessage({ defaultMessage: 'Default Icon', description: 'Form field label' })}>
Expand All @@ -189,7 +190,7 @@ export const AnchorComponentEditor: React.FC<IAnchorComponentEditorProps> = ({
placeholder={intl.formatMessage({ defaultMessage: 'Choose an icon', description: 'placeholder' })}
/>
{hasIcon &&
(isAllValid ? (
(isCustomStyle ? (
<DecodeSvgString
selectedColor={anchorComponent.chosenColor ?? colors.customBlue}
iconString={iconString!}
Expand All @@ -201,12 +202,19 @@ export const AnchorComponentEditor: React.FC<IAnchorComponentEditorProps> = ({
))}
</Grid>
</FormField>
{isAllValid ? (
<FormField stretch>
{isCustomStyle ? (
<FormField>
<ColorPicker
color={anchorComponent.chosenColor ?? colors.customBlue}
onSelectColor={(pickedColor) => onUpdateCallback({ chosenColor: pickedColor })}
label={intl.formatMessage({ defaultMessage: 'Colors', description: 'Colors' })}
onSelectColor={(pickedColor) => {
onUpdateCallback({
chosenColor: pickedColor,
});
}}
onUpdateCustomColors={(chosenCustomColors) => onUpdateCallback({ customColors: chosenCustomColors })}
customColors={anchorComponent.customColors}
colorPickerLabel={intl.formatMessage({ defaultMessage: 'Colors', description: 'Colors' })}
customColorLabel={intl.formatMessage({ defaultMessage: 'Custom colors', description: 'Custom colors' })}
/>
</FormField>
) : null}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { Button, FormField, Icon, Input, InputProps, SpaceBetween, TextContent } from '@awsui/components-react';
import { NonCancelableCustomEvent } from '@awsui/components-react/internal/events';
import React, { useCallback, useState } from 'react';
import { ChromePicker, CirclePicker } from 'react-color';
import React, { useCallback, useEffect, useState } from 'react';
import { CirclePicker, ColorResult, SketchPicker } from 'react-color';
import { useIntl } from 'react-intl';

import { IColorPickerProps } from '../interface';
Expand All @@ -17,12 +17,19 @@ import {
import { colorPickerPreviewSvg } from './ColorPickerUtils/SvgParserHelper';
import { palleteColors } from './ColorPickerUtils/TagColors';

export const ColorPicker = ({ color, onSelectColor, label }: IColorPickerProps): JSX.Element => {
export const ColorPicker = ({
color,
onSelectColor,
customColors,
onUpdateCustomColors,
colorPickerLabel,
customColorLabel,
}: IColorPickerProps): JSX.Element => {
const [showPicker, setShowPicker] = useState<boolean>(false);
const [newColor, setNewColor] = useState<string>(color);
const [showChromePicker, setShowChromePicker] = useState<boolean>(false);
const [hexCodeError, setHexCodeError] = useState<string>(''); // State variable for hex code error

const [customInternalColors, setCustomInternalColors] = useState<string[]>(customColors ?? []);
const intl = useIntl();

/**
Expand All @@ -34,30 +41,40 @@ export const ColorPicker = ({ color, onSelectColor, label }: IColorPickerProps):
* @returns
*/
const isValidHexCode = (hexCode: string) => {
const hexRegex = /^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$/;
const hexRegex = /^#([A-Fa-f0-9]{6})$/;
return hexRegex.test(hexCode);
};

const handleClick = () => {
setShowPicker(!showPicker);
};

const handleColorChange = (color) => {
setNewColor(color.hex);
onSelectColor(color.hex);
};
const handleOutsideClick = useCallback((event: MouseEvent) => {
const target = event.target as HTMLElement;
const pickerContainer = document.getElementById('circle-picker');
if (pickerContainer && !pickerContainer.contains(target)) {
setShowPicker(false);
}
}, []);

const handleHexCodeChange = useCallback((event: NonCancelableCustomEvent<InputProps.ChangeDetail>) => {
setNewColor(event.detail.value);
if (isValidHexCode(event.detail.value)) {
setHexCodeError(''); // Clear any existing error message
onSelectColor(event.detail.value);
useEffect(() => {
if (showPicker) {
document.addEventListener('click', handleOutsideClick);
} else {
setHexCodeError(
intl.formatMessage({ defaultMessage: 'Invalid hex code', description: 'hex validations messages' }),
); // Set the error message
document.removeEventListener('click', handleOutsideClick);
}
}, []);

return () => {
document.removeEventListener('click', handleOutsideClick);
};
}, [showPicker, handleOutsideClick]);

const checkIfCustomColor = (color: any) => {
if (!Object.values(palleteColors).includes(color)) {
setCustomInternalColors(customInternalColors.concat(color));
onUpdateCustomColors?.([...new Set(customInternalColors)]);
}
};

const handleClick = () => {
setShowPicker(!showPicker);
};

const handleShowChromePicker = () => {
setShowChromePicker(true);
Expand All @@ -69,22 +86,62 @@ export const ColorPicker = ({ color, onSelectColor, label }: IColorPickerProps):
setShowChromePicker(false);
};

const handleCloseChromePicker = () => {
const handleCloseCustomPicker = () => {
setShowChromePicker(false);
};

const handleColorChange = useCallback(
(color: ColorResult) => {
checkIfCustomColor(color.hex);
setHexCodeError(''); // Clear any existing error message
setNewColor(color.hex);
onSelectColor(color.hex);
},
[color, onSelectColor, onUpdateCustomColors],
);

const handleHexCodeChange = useCallback(
(event: NonCancelableCustomEvent<InputProps.ChangeDetail>) => {
setNewColor(event.detail.value);
if (isValidHexCode(event.detail.value)) {
setHexCodeError(''); // Clear any existing error message
onSelectColor(event.detail.value);
checkIfCustomColor(event.detail.value);
} else {
setHexCodeError(
intl.formatMessage({ defaultMessage: 'Invalid hex code', description: 'hex validations messages' }),
); // Set the error message
}
},
[color, onSelectColor],
);

const handleCustomPickerSelection = (color) => {
checkIfCustomColor(color.hex);
setNewColor(color.hex);
onSelectColor(color.hex);
};

useEffect(() => {
setNewColor(color);
}, [color]);

useEffect(() => {
checkIfCustomColor(color);
}, [color]);

return (
<SpaceBetween size='m'>
<SpaceBetween size='l'>
<FormField errorText={hexCodeError}>
<SpaceBetween size='m' direction='horizontal'>
<TextContent>
<h5>{label}</h5>
<h5>{colorPickerLabel}</h5>
</TextContent>
<Button
data-testid='color-preview'
ariaLabel={intl.formatMessage({ defaultMessage: 'colorPreview', description: 'color picker preview' })}
variant='inline-icon'
iconSvg={<Icon size='big' svg={colorPickerPreviewSvg(color)} />}
iconSvg={<Icon size='big' svg={colorPickerPreviewSvg(newColor)} />}
onClick={() => {
handleClick();
setHexCodeError('');
Expand All @@ -96,7 +153,7 @@ export const ColorPicker = ({ color, onSelectColor, label }: IColorPickerProps):
value={newColor}
onChange={handleHexCodeChange}
/>
<div style={tmColorPickerContainer}>
<div id='circle-picker' style={tmColorPickerContainer}>
{showPicker && !showChromePicker && (
<div style={tmColorPickerPopover}>
<div>
Expand All @@ -110,10 +167,21 @@ export const ColorPicker = ({ color, onSelectColor, label }: IColorPickerProps):
onChange={handleColorChange}
/>
</div>
<div style={tmDivider} />
<button style={tmAddButton} onClick={handleShowChromePicker}>
<Icon name='add-plus' />
</button>
<SpaceBetween size='s'>
<div style={tmDivider} />
<TextContent>
<h5>{customColorLabel}</h5>
</TextContent>
<CirclePicker
width='300px'
colors={[...new Set(customInternalColors)]}
color={newColor}
onChange={handleColorChange}
/>
<button style={tmAddButton} onClick={handleShowChromePicker}>
<Icon name='add-plus' />
</button>
</SpaceBetween>
</div>
)}
</div>
Expand All @@ -124,9 +192,9 @@ export const ColorPicker = ({ color, onSelectColor, label }: IColorPickerProps):
<div
aria-label={intl.formatMessage({ defaultMessage: 'chromePicker', description: 'chrome picker' })}
style={tmCover}
onClick={handleCloseChromePicker}
onClick={handleCloseCustomPicker}
/>
<ChromePicker color={color} onChangeComplete={(newColor) => onSelectColor(newColor.hex)} />
<SketchPicker disableAlpha={true} color={newColor} onChangeComplete={handleCustomPickerSelection} />
</div>
)}
</SpaceBetween>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ export const replaceFillAttribute = (element: Element, selectedColor: string): v
element.setAttribute('stroke', selectedColor);
}
if (tagName === 'circle') {
element.setAttribute('fill', selectedColor!);
element.setAttribute('fill', selectedColor);
}
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,8 @@ export interface IColorPickerProps {
color: string;
onSelectColor: (color: string) => void;
iconSvg?: string;
label?: string;
colorPickerLabel?: string;
customColorLabel?: string;
customColors?: string[];
onUpdateCustomColors?: (customColors: string[]) => void;
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React, { useState } from 'react';
import React, { useEffect, useState } from 'react';
import { useIntl, defineMessages } from 'react-intl';
import { Grid, Select } from '@awsui/components-react';

Expand Down Expand Up @@ -60,7 +60,12 @@ export const SceneRuleTargetEditor: React.FC<ISceneRuleTargetEditorProps> = ({
label: formatMessage(i18nSceneResourceTypeStrings[SceneResourceType[type]]) || SceneResourceType[type],
value: SceneResourceType[type],
}));
const isAllValid = tagStyle && targetInfo.value === 'Custom';
const isCustomStyle = tagStyle && targetInfo.value === 'Custom';

useEffect(() => {
setChosenColor(getCustomColor);
}, [getCustomColor]);

return (
<Grid gridDefinition={[{ colspan: 4 }, { colspan: 8 }]}>
<Select
Expand Down Expand Up @@ -92,7 +97,7 @@ export const SceneRuleTargetEditor: React.FC<ISceneRuleTargetEditorProps> = ({
}}
chosenColor={chosenColor}
/>
{isAllValid && (
{isCustomStyle && (
<ColorPicker
color={chosenColor}
onSelectColor={(newColor) => {
Expand All @@ -101,7 +106,8 @@ export const SceneRuleTargetEditor: React.FC<ISceneRuleTargetEditorProps> = ({
onChange(convertToIotTwinMakerNamespace(targetInfo.type, colorWithIcon));
setChosenColor(newColor);
}}
label={formatMessage({ defaultMessage: 'Colors', description: 'Colors' })}
colorPickerLabel={formatMessage({ defaultMessage: 'Colors', description: 'Colors' })}
customColorLabel={formatMessage({ defaultMessage: 'Custom colors', description: 'Custom colors' })}
/>
)}
</>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ export const SceneRuleTargetIconEditor: React.FC<ISceneRuleTargetIconEditorProps
return btoa(SCENE_ICONS[selectedIcon]);
}, [selectedIcon]);

const isAllValid = tagStyle && targetValue === 'Custom';
const isCustomStyle = tagStyle && targetValue === 'Custom';
return (
<Grid gridDefinition={[{ colspan: 9 }, { colspan: 2 }]}>
<Select
Expand All @@ -60,7 +60,7 @@ export const SceneRuleTargetIconEditor: React.FC<ISceneRuleTargetIconEditorProps
'Specifies the localized string that describes an option as being selected. This is required to provide a good screen reader experience',
})}
/>
{isAllValid ? (
{isCustomStyle ? (
<DecodeSvgString
selectedColor={chosenColor ?? colors.customBlue}
iconString={iconString}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { fireEvent, render, screen } from '@testing-library/react';
import React from 'react';
import wrapper from '@awsui/components-react/test-utils/dom';
import { render, screen } from '@testing-library/react';
import React from 'react';

import { DefaultAnchorStatus, IotTwinMakerNumberNamespace, SceneResourceType } from '../../../../';
import { getGlobalSettings } from '../../../../common/GlobalSettings';
Expand Down Expand Up @@ -37,7 +37,7 @@ describe('SceneRuleTargetEditor', () => {
});
render(<SceneRuleTargetEditor target='Custom-123' onChange={onChange} />);

const sceneRuleTargetIconEditor = screen.getByLabelText('Custom style');
const sceneRuleTargetIconEditor = screen.getByRole('button', { name: /Custom style/i });
expect(sceneRuleTargetIconEditor).toBeTruthy();
const colorPicker = screen.getByTestId('color-preview');
colorPicker.click();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@ interface IAnchorComponentProps {
const AnchorComponent: React.FC<IAnchorComponentProps> = ({ node, component }: IAnchorComponentProps) => {
const sceneComposerId = useSceneComposerId();
const rule = useStore(sceneComposerId)((state) => state.getSceneRuleMapById(component.ruleBasedMapId));

return (
<group name={getComponentGroupName(node.ref, 'TAG')}>
<AnchorWidget
Expand Down
3 changes: 2 additions & 1 deletion packages/scene-composer/src/interfaces/components.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ export interface IAnchorComponent extends ISceneComponent {
navLink?: INavLink;
offset?: Vector3;
chosenColor?: string;
customColors?: string[];
}

/**
Expand All @@ -70,9 +71,9 @@ export const SelectedAnchor = 'Selected';
*/
export interface ITagData {
chosenColor?: string;

navLink?: INavLink;
dataBindingContext?: unknown;
customColors?: string[];
}

/**
Expand Down
1 change: 1 addition & 0 deletions packages/scene-composer/src/models/SceneModels.ts
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,7 @@ export namespace Component {
navLink?: NavLink;
offset?: Vector3;
chosenColor?: string;
customColors?: string[];
}

export interface ModelShader extends IComponent, IDataBindingRuleMap {}
Expand Down
Loading

0 comments on commit ec95e01

Please sign in to comment.