Skip to content

Commit ef5def7

Browse files
authored
Merge pull request #707 from Lemoncode/feature/#567-create-a-circular-progress-shape-under-rich-components
Feature /#567 Create a circular progress shape under rich components
2 parents e25bcc4 + 6f596f1 commit ef5def7

File tree

15 files changed

+233
-22
lines changed

15 files changed

+233
-22
lines changed

package-lock.json

Lines changed: 8 additions & 6 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

public/rich-components/gauge.svg

Lines changed: 32 additions & 0 deletions
Loading
Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
import { Circle, Group, Path, Text } from 'react-konva';
2+
import { ShapeSizeRestrictions, ShapeType } from '@/core/model';
3+
import { forwardRef } from 'react';
4+
import { ShapeProps } from '../../shape.model';
5+
import { fitSizeToShapeSizeRestrictions } from '@/common/utils/shapes/shape-restrictions';
6+
import { useShapeProps } from '../../../shapes/use-shape-props.hook';
7+
8+
import { BASIC_SHAPE } from '@/common/components/mock-components/front-components/shape.const';
9+
import { useGroupShapeProps } from '../../mock-components.utils';
10+
import {
11+
endsWhithPercentageSymbol,
12+
extractNumbersAsTwoDigitString,
13+
} from './gauge.utils';
14+
15+
const gaugeShapeSizeRestrictions: ShapeSizeRestrictions = {
16+
minWidth: 70,
17+
minHeight: 70,
18+
maxWidth: -1,
19+
maxHeight: -1,
20+
defaultWidth: 150,
21+
defaultHeight: 150,
22+
};
23+
24+
export const getGaugeShapeSizeRestrictions = (): ShapeSizeRestrictions =>
25+
gaugeShapeSizeRestrictions;
26+
27+
const shapeType: ShapeType = 'gauge';
28+
29+
export const Gauge = forwardRef<any, ShapeProps>((props, ref) => {
30+
const {
31+
x,
32+
y,
33+
width,
34+
height,
35+
id,
36+
onSelected,
37+
text,
38+
otherProps,
39+
...shapeProps
40+
} = props;
41+
const restrictedSize = fitSizeToShapeSizeRestrictions(
42+
gaugeShapeSizeRestrictions,
43+
width,
44+
height
45+
);
46+
47+
const { width: restrictedWidth, height: restrictedHeight } = restrictedSize;
48+
const { fontSize } = useShapeProps(otherProps, BASIC_SHAPE);
49+
const commonGroupProps = useGroupShapeProps(
50+
props,
51+
restrictedSize,
52+
shapeType,
53+
ref
54+
);
55+
const { stroke, fill, textColor } = useShapeProps(otherProps, BASIC_SHAPE);
56+
57+
const numericPart = extractNumbersAsTwoDigitString(text);
58+
59+
const progress = Number(numericPart);
60+
const displayValue = endsWhithPercentageSymbol(text)
61+
? `${numericPart}%`
62+
: numericPart;
63+
64+
const size = Math.min(restrictedWidth, restrictedHeight);
65+
const strokeWidth = Math.min(restrictedWidth, restrictedHeight) / 10;
66+
const radius = (size - strokeWidth) / 2;
67+
const center = size / 2;
68+
const angle = (progress / 100.01) * 360;
69+
const fontSizeScaled = fontSize * (size / 80);
70+
const arcPath = () => {
71+
const startAngle = -90;
72+
const endAngle = startAngle + angle;
73+
const largeArcFlag = angle > 180 ? 1 : 0;
74+
const startX = center + radius * Math.cos((Math.PI * startAngle) / 180);
75+
const startY = center + radius * Math.sin((Math.PI * startAngle) / 180);
76+
const endX = center + radius * Math.cos((Math.PI * endAngle) / 180);
77+
const endY = center + radius * Math.sin((Math.PI * endAngle) / 180);
78+
return `M ${startX} ${startY} A ${radius} ${radius} 0 ${largeArcFlag} 1 ${endX} ${endY}`;
79+
};
80+
return (
81+
<Group {...commonGroupProps} {...shapeProps}>
82+
{/* Background */}
83+
<Circle
84+
x={center}
85+
y={center}
86+
radius={radius}
87+
fill={fill}
88+
stroke={fill}
89+
strokeWidth={strokeWidth}
90+
/>
91+
92+
{/* Moving Arc */}
93+
<Path data={arcPath()} stroke={stroke} strokeWidth={strokeWidth + 1} />
94+
95+
{/* Percent */}
96+
<Text
97+
x={center - 10 - radius / 2}
98+
y={center - fontSizeScaled / 2}
99+
width={center + 10}
100+
text={displayValue}
101+
fontFamily="Arial, sans-serif"
102+
fontSize={fontSizeScaled}
103+
align="center"
104+
fill={textColor}
105+
fontStyle="bold"
106+
/>
107+
</Group>
108+
);
109+
});
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
export const extractNumbersAsTwoDigitString = (text: string): string => {
2+
const numbersAsString = text.replace(/\D/g, '');
3+
return numbersAsString === '100' ? '100' : numbersAsString.slice(0, 2) || '0';
4+
};
5+
6+
export const endsWhithPercentageSymbol = (text: string): boolean => {
7+
return text.trim().endsWith('%');
8+
};
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export * from './gauge';

src/common/components/mock-components/front-rich-components/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,3 +17,4 @@ export * from './audio-player';
1717
export * from './loading-indicator';
1818
export * from './videoconference';
1919
export * from './togglelightdark-shape';
20+
export * from './gauge/gauge';

src/core/model/index.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,8 @@ export type ShapeType =
7474
| 'richtext'
7575
| 'loading-indicator'
7676
| 'videoconference'
77-
| 'richtext';
77+
| 'richtext'
78+
| 'gauge';
7879

7980
export const ShapeDisplayName: Record<ShapeType, string> = {
8081
multiple: 'multiple',
@@ -138,6 +139,7 @@ export const ShapeDisplayName: Record<ShapeType, string> = {
138139
cilinder: 'Cilinder',
139140
'loading-indicator': 'Loading',
140141
videoconference: 'Videoconference',
142+
gauge: 'Gauge',
141143
};
142144

143145
export type EditType = 'input' | 'textarea' | 'imageupload';

src/pods/canvas/model/inline-editable.model.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ const inlineEditableShapes = new Set<ShapeType>([
3636
'datepickerinput',
3737
'browser',
3838
'modalDialog',
39+
'gauge',
3940
'loading-indicator',
4041
]);
4142

@@ -78,6 +79,7 @@ const shapeTypesWithDefaultText = new Set<ShapeType>([
7879
'browser',
7980
'modalDialog',
8081
'loading-indicator',
82+
'gauge',
8183
]);
8284

8385
// Map of ShapeTypes to their default text values
@@ -108,6 +110,7 @@ const defaultTextValueMap: Partial<Record<ShapeType, string>> = {
108110
modal:
109111
'Alert\nWarning: The action you are about to perform may affect existing data. Are you sure you want to proceed? Once confirmed, this action cannot be undone.\nConfirm,Cancel',
110112
appBar: 'AppBar',
113+
gauge: '10%',
111114
buttonBar: 'Button 1, Button 2, Button 3',
112115
tabsBar: 'Tab 1, Tab 2, Tab 3',
113116
link: 'Link',

src/pods/canvas/model/shape-other-props.utils.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,12 @@ export const generateDefaultOtherProps = (
6868
disabled: BASIC_SHAPE.DEFAULT_DISABLED,
6969
};
7070
case 'modal':
71+
case 'gauge':
72+
return {
73+
backgroundColor: '#d3d3d3',
74+
stroke: '#808080',
75+
textColor: BASIC_SHAPE.DEFAULT_FILL_TEXT,
76+
};
7177
case 'buttonBar':
7278
return {
7379
stroke: BASIC_SHAPE.DEFAULT_STROKE_COLOR,

src/pods/canvas/model/shape-size.mapper.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@ import {
6262
getVerticalMenuShapeSizeRestrictions,
6363
getVideoPlayerShapeSizeRestrictions,
6464
getVideoconferenceShapeSizeRestrictions,
65+
getGaugeShapeSizeRestrictions,
6566
// other imports
6667
} from '@/common/components/mock-components/front-rich-components';
6768
import {
@@ -148,6 +149,7 @@ const shapeSizeMap: Record<ShapeType, () => ShapeSizeRestrictions> = {
148149
cilinder: getCilinderShapeSizeRestrictions,
149150
'loading-indicator': getLoadIndicatorSizeRestrictions,
150151
videoconference: getVideoconferenceShapeSizeRestrictions,
152+
gauge: getGaugeShapeSizeRestrictions,
151153
};
152154

153155
export default shapeSizeMap;

0 commit comments

Comments
 (0)