@pie-element/hotspot 11.1.2-next.5 → 11.1.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (234) hide show
  1. package/CHANGELOG.json +997 -0
  2. package/CHANGELOG.md +2226 -0
  3. package/LICENSE.md +5 -0
  4. package/README.md +1 -0
  5. package/configure/CHANGELOG.json +682 -0
  6. package/configure/CHANGELOG.md +1963 -0
  7. package/configure/lib/DeleteWidget.js +64 -0
  8. package/configure/lib/DeleteWidget.js.map +1 -0
  9. package/configure/lib/button.js +42 -0
  10. package/configure/lib/button.js.map +1 -0
  11. package/configure/lib/buttons/circle.js +33 -0
  12. package/configure/lib/buttons/circle.js.map +1 -0
  13. package/configure/lib/buttons/polygon.js +39 -0
  14. package/configure/lib/buttons/polygon.js.map +1 -0
  15. package/configure/lib/buttons/rectangle.js +39 -0
  16. package/configure/lib/buttons/rectangle.js.map +1 -0
  17. package/configure/lib/defaults.js +155 -0
  18. package/configure/lib/defaults.js.map +1 -0
  19. package/configure/lib/hotspot-circle.js +192 -0
  20. package/configure/lib/hotspot-circle.js.map +1 -0
  21. package/configure/lib/hotspot-container.js +320 -0
  22. package/configure/lib/hotspot-container.js.map +1 -0
  23. package/configure/lib/hotspot-drawable.js +519 -0
  24. package/configure/lib/hotspot-drawable.js.map +1 -0
  25. package/configure/lib/hotspot-palette.js +107 -0
  26. package/configure/lib/hotspot-palette.js.map +1 -0
  27. package/configure/lib/hotspot-polygon.js +293 -0
  28. package/configure/lib/hotspot-polygon.js.map +1 -0
  29. package/configure/lib/hotspot-rectangle.js +190 -0
  30. package/configure/lib/hotspot-rectangle.js.map +1 -0
  31. package/configure/lib/icons.js +7 -0
  32. package/configure/lib/icons.js.map +1 -0
  33. package/configure/lib/image-konva.js +66 -0
  34. package/configure/lib/image-konva.js.map +1 -0
  35. package/configure/lib/index.js +194 -0
  36. package/configure/lib/index.js.map +1 -0
  37. package/configure/lib/root.js +330 -0
  38. package/configure/lib/root.js.map +1 -0
  39. package/configure/lib/shapes/circle.js +84 -0
  40. package/configure/lib/shapes/circle.js.map +1 -0
  41. package/configure/lib/shapes/index.js +50 -0
  42. package/configure/lib/shapes/index.js.map +1 -0
  43. package/configure/lib/shapes/polygon.js +82 -0
  44. package/configure/lib/shapes/polygon.js.map +1 -0
  45. package/configure/lib/shapes/rectagle.js +84 -0
  46. package/configure/lib/shapes/rectagle.js.map +1 -0
  47. package/configure/lib/shapes/utils.js +21 -0
  48. package/configure/lib/shapes/utils.js.map +1 -0
  49. package/configure/lib/upload-control.js +41 -0
  50. package/configure/lib/upload-control.js.map +1 -0
  51. package/configure/lib/utils.js +185 -0
  52. package/configure/lib/utils.js.map +1 -0
  53. package/configure/package.json +26 -0
  54. package/configure/src/DeleteWidget.jsx +51 -0
  55. package/configure/src/__tests__/DeleteWidget.test.jsx +366 -0
  56. package/configure/src/__tests__/button.test.jsx +198 -0
  57. package/configure/src/__tests__/hotspot-circle.test.jsx +259 -0
  58. package/configure/src/__tests__/hotspot-container.test.js +366 -0
  59. package/configure/src/__tests__/hotspot-drawable.test.js +271 -0
  60. package/configure/src/__tests__/hotspot-palette.test.jsx +71 -0
  61. package/configure/src/__tests__/image-konva.test.jsx +226 -0
  62. package/configure/src/__tests__/index.test.js +329 -0
  63. package/configure/src/__tests__/root.test.js +400 -0
  64. package/configure/src/__tests__/utils.test.js +241 -0
  65. package/configure/src/button.jsx +35 -0
  66. package/configure/src/buttons/circle.jsx +18 -0
  67. package/configure/src/buttons/polygon.jsx +29 -0
  68. package/configure/src/buttons/rectangle.jsx +29 -0
  69. package/configure/src/defaults.js +109 -0
  70. package/configure/src/hotspot-circle.jsx +183 -0
  71. package/configure/src/hotspot-container.jsx +330 -0
  72. package/configure/src/hotspot-drawable.jsx +527 -0
  73. package/configure/src/hotspot-palette.jsx +90 -0
  74. package/configure/src/hotspot-polygon.jsx +294 -0
  75. package/configure/src/hotspot-rectangle.jsx +169 -0
  76. package/configure/src/icons.js +5 -0
  77. package/configure/src/image-konva.jsx +63 -0
  78. package/configure/src/index.js +208 -0
  79. package/configure/src/root.jsx +346 -0
  80. package/configure/src/shapes/circle.js +81 -0
  81. package/configure/src/shapes/index.js +4 -0
  82. package/configure/src/shapes/polygon.js +81 -0
  83. package/configure/src/shapes/rectagle.js +82 -0
  84. package/configure/src/shapes/utils.js +16 -0
  85. package/configure/src/upload-control.jsx +33 -0
  86. package/configure/src/utils.js +210 -0
  87. package/controller/CHANGELOG.json +362 -0
  88. package/controller/CHANGELOG.md +1304 -0
  89. package/controller/lib/defaults.js +33 -0
  90. package/controller/lib/defaults.js.map +1 -0
  91. package/controller/lib/index.js +341 -0
  92. package/controller/lib/index.js.map +1 -0
  93. package/controller/lib/utils.js +32 -0
  94. package/controller/lib/utils.js.map +1 -0
  95. package/controller/package.json +18 -0
  96. package/controller/src/__tests__/index.test.js +419 -0
  97. package/controller/src/__tests__/utils.test.js +5 -0
  98. package/controller/src/defaults.js +19 -0
  99. package/controller/src/index.js +328 -0
  100. package/controller/src/utils.js +29 -0
  101. package/docs/config-schema.json +2023 -0
  102. package/docs/config-schema.json.md +1495 -0
  103. package/docs/demo/config.js +8 -0
  104. package/docs/demo/generate.js +118 -0
  105. package/docs/demo/index.html +1 -0
  106. package/docs/demo/session.js +11 -0
  107. package/docs/pie-schema.json +1204 -0
  108. package/docs/pie-schema.json.md +851 -0
  109. package/lib/hotspot/circle.js +156 -0
  110. package/lib/hotspot/circle.js.map +1 -0
  111. package/lib/hotspot/container.js +206 -0
  112. package/lib/hotspot/container.js.map +1 -0
  113. package/lib/hotspot/icons.js +8 -0
  114. package/lib/hotspot/icons.js.map +1 -0
  115. package/lib/hotspot/image-konva-tooltip.js +86 -0
  116. package/lib/hotspot/image-konva-tooltip.js.map +1 -0
  117. package/lib/hotspot/index.js +163 -0
  118. package/lib/hotspot/index.js.map +1 -0
  119. package/lib/hotspot/polygon.js +203 -0
  120. package/lib/hotspot/polygon.js.map +1 -0
  121. package/lib/hotspot/rectangle.js +175 -0
  122. package/lib/hotspot/rectangle.js.map +1 -0
  123. package/lib/index.js +213 -0
  124. package/lib/index.js.map +1 -0
  125. package/lib/session-updater.js +42 -0
  126. package/lib/session-updater.js.map +1 -0
  127. package/package.json +18 -83
  128. package/src/__tests__/container.test.jsx +58 -0
  129. package/src/__tests__/index.test.js +123 -0
  130. package/src/__tests__/session-updater.test.jsx +69 -0
  131. package/src/hotspot/__tests__/circle.test.jsx +464 -0
  132. package/src/hotspot/__tests__/container.test.jsx +546 -0
  133. package/src/hotspot/__tests__/image-konva-tooltip.test.jsx +510 -0
  134. package/src/hotspot/__tests__/polygon.test.jsx +502 -0
  135. package/src/hotspot/__tests__/rectangle.test.jsx +418 -0
  136. package/src/hotspot/circle.jsx +152 -0
  137. package/src/hotspot/container.jsx +217 -0
  138. package/src/hotspot/icons.js +7 -0
  139. package/src/hotspot/image-konva-tooltip.jsx +76 -0
  140. package/src/hotspot/index.jsx +165 -0
  141. package/src/hotspot/polygon.jsx +195 -0
  142. package/src/hotspot/rectangle.jsx +171 -0
  143. package/src/index.js +226 -0
  144. package/src/session-updater.js +29 -0
  145. package/configure.js +0 -2
  146. package/controller.js +0 -1
  147. package/dist/author/DeleteWidget.d.ts +0 -38
  148. package/dist/author/DeleteWidget.js +0 -46
  149. package/dist/author/button.d.ts +0 -31
  150. package/dist/author/button.js +0 -27
  151. package/dist/author/buttons/circle.d.ts +0 -18
  152. package/dist/author/buttons/circle.js +0 -25
  153. package/dist/author/buttons/polygon.d.ts +0 -18
  154. package/dist/author/buttons/polygon.js +0 -36
  155. package/dist/author/buttons/rectangle.d.ts +0 -18
  156. package/dist/author/buttons/rectangle.js +0 -36
  157. package/dist/author/defaults.d.ts +0 -157
  158. package/dist/author/defaults.js +0 -119
  159. package/dist/author/hotspot-circle.d.ts +0 -21
  160. package/dist/author/hotspot-circle.js +0 -124
  161. package/dist/author/hotspot-container.d.ts +0 -29
  162. package/dist/author/hotspot-container.js +0 -210
  163. package/dist/author/hotspot-drawable.d.ts +0 -31
  164. package/dist/author/hotspot-drawable.js +0 -312
  165. package/dist/author/hotspot-palette.d.ts +0 -14
  166. package/dist/author/hotspot-palette.js +0 -72
  167. package/dist/author/hotspot-polygon.d.ts +0 -38
  168. package/dist/author/hotspot-polygon.js +0 -200
  169. package/dist/author/hotspot-rectangle.d.ts +0 -20
  170. package/dist/author/hotspot-rectangle.js +0 -119
  171. package/dist/author/icons.d.ts +0 -9
  172. package/dist/author/icons.js +0 -4
  173. package/dist/author/image-konva.d.ts +0 -19
  174. package/dist/author/image-konva.js +0 -49
  175. package/dist/author/index.d.ts +0 -52
  176. package/dist/author/index.js +0 -143
  177. package/dist/author/root.d.ts +0 -15
  178. package/dist/author/root.js +0 -215
  179. package/dist/author/shapes/circle.d.ts +0 -18
  180. package/dist/author/shapes/circle.js +0 -47
  181. package/dist/author/shapes/index.d.ts +0 -12
  182. package/dist/author/shapes/polygon.d.ts +0 -19
  183. package/dist/author/shapes/polygon.js +0 -51
  184. package/dist/author/shapes/rectagle.d.ts +0 -18
  185. package/dist/author/shapes/rectagle.js +0 -57
  186. package/dist/author/shapes/utils.d.ts +0 -19
  187. package/dist/author/shapes/utils.js +0 -16
  188. package/dist/author/upload-control.d.ts +0 -29
  189. package/dist/author/upload-control.js +0 -28
  190. package/dist/author/utils.d.ts +0 -24
  191. package/dist/author/utils.js +0 -83
  192. package/dist/browser/ReactKonva-CFo7dxdy.js +0 -19336
  193. package/dist/browser/ReactKonva-CFo7dxdy.js.map +0 -1
  194. package/dist/browser/author/index.js +0 -47549
  195. package/dist/browser/author/index.js.map +0 -1
  196. package/dist/browser/browser-CfnAFove.js +0 -219
  197. package/dist/browser/browser-CfnAFove.js.map +0 -1
  198. package/dist/browser/controller/index.js +0 -198
  199. package/dist/browser/controller/index.js.map +0 -1
  200. package/dist/browser/delivery/index.js +0 -2460
  201. package/dist/browser/delivery/index.js.map +0 -1
  202. package/dist/browser/dist-CeB-1djc.js +0 -100
  203. package/dist/browser/dist-CeB-1djc.js.map +0 -1
  204. package/dist/browser/hotspot.css +0 -2
  205. package/dist/controller/defaults.d.ts +0 -35
  206. package/dist/controller/defaults.js +0 -29
  207. package/dist/controller/index.d.ts +0 -22
  208. package/dist/controller/index.js +0 -154
  209. package/dist/controller/utils.d.ts +0 -10
  210. package/dist/controller/utils.js +0 -12
  211. package/dist/delivery/hotspot/circle.d.ts +0 -19
  212. package/dist/delivery/hotspot/circle.js +0 -100
  213. package/dist/delivery/hotspot/container.d.ts +0 -16
  214. package/dist/delivery/hotspot/container.js +0 -150
  215. package/dist/delivery/hotspot/icons.d.ts +0 -10
  216. package/dist/delivery/hotspot/icons.js +0 -4
  217. package/dist/delivery/hotspot/image-konva-tooltip.d.ts +0 -19
  218. package/dist/delivery/hotspot/image-konva-tooltip.js +0 -66
  219. package/dist/delivery/hotspot/index.d.ts +0 -17
  220. package/dist/delivery/hotspot/index.js +0 -114
  221. package/dist/delivery/hotspot/polygon.d.ts +0 -21
  222. package/dist/delivery/hotspot/polygon.js +0 -108
  223. package/dist/delivery/hotspot/rectangle.d.ts +0 -19
  224. package/dist/delivery/hotspot/rectangle.js +0 -104
  225. package/dist/delivery/index.d.ts +0 -20
  226. package/dist/delivery/index.js +0 -107
  227. package/dist/delivery/session-updater.d.ts +0 -10
  228. package/dist/delivery/session-updater.js +0 -14
  229. package/dist/index.d.ts +0 -1
  230. package/dist/index.iife.d.ts +0 -8
  231. package/dist/index.iife.js +0 -169
  232. package/dist/index.js +0 -2
  233. package/dist/runtime-support.d.ts +0 -12
  234. package/dist/runtime-support.js +0 -12
@@ -0,0 +1,208 @@
1
+ import React from 'react';
2
+ import { createRoot } from 'react-dom/client';
3
+ import debug from 'debug';
4
+ import {
5
+ ModelUpdatedEvent,
6
+ DeleteImageEvent,
7
+ InsertImageEvent,
8
+ InsertSoundEvent,
9
+ DeleteSoundEvent,
10
+ } from '@pie-framework/pie-configure-events';
11
+
12
+ import Root from './root';
13
+ import sensibleDefaults from './defaults';
14
+
15
+ const log = debug('hotspot:configure');
16
+
17
+ export default class HotspotConfigure extends HTMLElement {
18
+ static createDefaultModel = (model = {}) => ({
19
+ ...sensibleDefaults.model,
20
+ ...model,
21
+ hotspotList: model.hotspotList || [model.hotspotColor] || sensibleDefaults.model.hotspotList,
22
+ outlineList: model.outlineList || [model.outlineColor] || sensibleDefaults.model.outlineList,
23
+ shapes: model.shapes || sensibleDefaults.model.shapes || {},
24
+ });
25
+
26
+ constructor() {
27
+ super();
28
+ this._root = null;
29
+ this._model = HotspotConfigure.createDefaultModel();
30
+ this._configuration = sensibleDefaults.configuration;
31
+ this.onModelChanged = this.onModelChanged.bind(this);
32
+ }
33
+
34
+ set model(s) {
35
+ this._model = HotspotConfigure.createDefaultModel(s);
36
+ this._render();
37
+ }
38
+
39
+ set configuration(c) {
40
+ const newConfiguration = {
41
+ ...sensibleDefaults.configuration,
42
+ ...c,
43
+ };
44
+
45
+ this._configuration = newConfiguration;
46
+
47
+ // if language:enabled is true, then the corresponding default item model should include a language value;
48
+ // if it is false, then the language field should be omitted from the item model.
49
+ // if a default item model includes a language value (e.g., en_US) and the corresponding authoring view settings have language:settings = true,
50
+ // then (a) language:enabled should also be true, and (b) that default language value should be represented in languageChoices[] (as a key).
51
+ if (newConfiguration?.language?.enabled) {
52
+ if (newConfiguration?.languageChoices?.options?.length) {
53
+ this._model.language = newConfiguration?.languageChoices.options[0].value;
54
+ }
55
+ } else if (newConfiguration.language.settings && this._model.language) {
56
+ this._configuration.language.enabled = true;
57
+
58
+ if (!this._configuration.languageChoices.options || !this._configuration.languageChoices.options.length) {
59
+ this._configuration.languageChoices.options = [];
60
+ }
61
+
62
+ // check if the language is already included in the languageChoices.options array
63
+ // and if not, then add it.
64
+ if (!this._configuration.languageChoices.options.find((option) => option.value === this._model.language)) {
65
+ this._configuration.languageChoices.options.push({
66
+ value: this._model.language,
67
+ label: this._model.language,
68
+ });
69
+ }
70
+ } else {
71
+ delete this._model.language;
72
+ }
73
+
74
+ this._render();
75
+ }
76
+
77
+ dispatchModelUpdated(reset) {
78
+ const resetValue = !!reset;
79
+
80
+ this.dispatchEvent(new ModelUpdatedEvent(this._model, resetValue));
81
+ }
82
+
83
+ onModelChanged(m, reset) {
84
+ this._model = m;
85
+ this.dispatchModelUpdated(reset);
86
+ this._render();
87
+ }
88
+
89
+ onConfigurationChanged = (c) => {
90
+ this._configuration = c;
91
+ this._render();
92
+ };
93
+
94
+ onModelChangedByConfig = (m, propertyType) => {
95
+ const _model = m;
96
+
97
+ if (propertyType === 'multipleCorrect') {
98
+ const { rectangles = [], polygons = [], circles = [] } = _model.shapes || {};
99
+
100
+ _model.shapes.rectangles = rectangles.map((shape) => ({ ...shape, correct: false }));
101
+ _model.shapes.polygons = polygons.map((shape) => ({ ...shape, correct: false }));
102
+ _model.shapes.circles = circles.map((shape) => ({ ...shape, correct: false }));
103
+ }
104
+
105
+ this.onModelChanged(_model);
106
+ };
107
+
108
+ onColorChanged = (colorType, color) => {
109
+ this.onModelChanged({
110
+ ...this._model,
111
+ [colorType]: color,
112
+ });
113
+ };
114
+
115
+ onPromptChanged = (prompt) => {
116
+ this.onModelChanged({
117
+ ...this._model,
118
+ prompt,
119
+ });
120
+ };
121
+
122
+ onRationaleChanged = (rationale) => {
123
+ this.onModelChanged({
124
+ ...this._model,
125
+ rationale,
126
+ });
127
+ };
128
+
129
+ onTeacherInstructionsChanged = (teacherInstructions) => {
130
+ this.onModelChanged({
131
+ ...this._model,
132
+ teacherInstructions,
133
+ });
134
+ };
135
+
136
+ onUpdateImageDimension = (dimensions) => {
137
+ this.onModelChanged({
138
+ ...this._model,
139
+ dimensions,
140
+ });
141
+ };
142
+
143
+ onUpdateShapes = (shapes) => {
144
+ this.onModelChanged({
145
+ ...this._model,
146
+ shapes,
147
+ });
148
+ };
149
+
150
+ onImageUpload = (imageUrl) => {
151
+ this.onModelChanged({
152
+ ...this._model,
153
+ imageUrl,
154
+ });
155
+ };
156
+
157
+ insertImage = (handler) => {
158
+ this.dispatchEvent(new InsertImageEvent(handler));
159
+ };
160
+
161
+ onDeleteImage = (src, done) => {
162
+ this.dispatchEvent(new DeleteImageEvent(src, done));
163
+ };
164
+
165
+ insertSound(handler) {
166
+ this.dispatchEvent(new InsertSoundEvent(handler));
167
+ }
168
+
169
+ onDeleteSound(src, done) {
170
+ this.dispatchEvent(new DeleteSoundEvent(src, done));
171
+ }
172
+
173
+ _render() {
174
+ log('_render');
175
+ let element = React.createElement(Root, {
176
+ configuration: this._configuration,
177
+ model: this._model,
178
+ onColorChanged: this.onColorChanged,
179
+ onImageUpload: this.onImageUpload,
180
+ onRationaleChanged: this.onRationaleChanged,
181
+ onConfigurationChanged: this.onConfigurationChanged,
182
+ onPromptChanged: this.onPromptChanged,
183
+ onUpdateImageDimension: this.onUpdateImageDimension,
184
+ imageSupport: {
185
+ add: this.insertImage,
186
+ delete: this.onDeleteImage,
187
+ },
188
+ uploadSoundSupport: {
189
+ add: this.insertSound.bind(this),
190
+ delete: this.onDeleteSound.bind(this),
191
+ },
192
+ onUpdateShapes: this.onUpdateShapes,
193
+ onModelChangedByConfig: this.onModelChangedByConfig,
194
+ onTeacherInstructionsChanged: this.onTeacherInstructionsChanged,
195
+ });
196
+
197
+ if (!this._root) {
198
+ this._root = createRoot(this);
199
+ }
200
+ this._root.render(element);
201
+ }
202
+
203
+ disconnectedCallback() {
204
+ if (this._root) {
205
+ this._root.unmount();
206
+ }
207
+ }
208
+ }
@@ -0,0 +1,346 @@
1
+ import React from 'react';
2
+ import { settings, layout, InputContainer, NumberTextField } from '@pie-lib/config-ui';
3
+ import PropTypes from 'prop-types';
4
+ import EditableHtml from '@pie-lib/editable-html-tip-tap';
5
+ import { styled } from '@mui/material/styles';
6
+ import Typography from '@mui/material/Typography';
7
+ import Info from '@mui/icons-material/Info';
8
+ import Tooltip from '@mui/material/Tooltip';
9
+ import HotspotPalette from './hotspot-palette';
10
+ import HotspotContainer from './hotspot-container';
11
+ import { updateImageDimensions, generateValidationMessage, getUpdatedShapes, getAllShapes, groupShapes } from './utils';
12
+
13
+ const { Panel, toggle, dropdown } = settings;
14
+
15
+ const DimensionsContainer = styled('div')(({ theme }) => ({
16
+ display: 'flex',
17
+ marginBottom: theme.spacing(1.5),
18
+ }));
19
+
20
+ const FieldContainer = styled('div')({
21
+ flex: 1,
22
+ width: '90%',
23
+ });
24
+
25
+ const PromptContainer = styled(InputContainer)(({ theme }) => ({
26
+ paddingTop: theme.spacing(1),
27
+ marginTop: theme.spacing(2),
28
+ marginBottom: theme.spacing(2),
29
+ width: '100%',
30
+ }));
31
+
32
+ const SubHeading = styled(Typography)(({ theme }) => ({
33
+ marginRight: theme.spacing(1),
34
+ }));
35
+
36
+ const FlexContainer = styled('div')({
37
+ display: 'flex',
38
+ alignItems: 'center',
39
+ });
40
+
41
+ const StyledTooltip = styled(Tooltip)(({ theme }) => ({
42
+ '& .MuiTooltip-tooltip': {
43
+ fontSize: theme.typography.fontSize - 2,
44
+ whiteSpace: 'pre',
45
+ maxWidth: '500px',
46
+ },
47
+ }));
48
+
49
+ const ErrorText = styled('div')(({ theme }) => ({
50
+ fontSize: theme.typography.fontSize - 2,
51
+ color: theme.palette.error.main,
52
+ paddingTop: theme.spacing(1),
53
+ }));
54
+
55
+ export class Root extends React.Component {
56
+ handleColorChange = (fieldType, color) => {
57
+ const { onColorChanged } = this.props;
58
+ const cType = `${fieldType}Color`;
59
+
60
+ onColorChanged(cType, color);
61
+ };
62
+
63
+ handleOnUpdateImageDimensions = (value, resizeType) => {
64
+ const {
65
+ model: { dimensions, shapes },
66
+ configuration: { preserveAspectRatio = {} },
67
+ onUpdateImageDimension,
68
+ onUpdateShapes,
69
+ } = this.props;
70
+
71
+ const nextImageDimensions = { ...dimensions, [resizeType]: value };
72
+
73
+ // if preserveAspectRatio.enabled, updateImageDimensions function makes sure aspect ratio is kept
74
+ const updatedDimensions = updateImageDimensions(
75
+ dimensions,
76
+ nextImageDimensions,
77
+ preserveAspectRatio.enabled,
78
+ resizeType,
79
+ );
80
+ // transform shapes map into shapes array
81
+ const shapesArray = getAllShapes(shapes);
82
+ // transform all the shapes to fit the re-sized image
83
+ const updatedShapes = getUpdatedShapes(dimensions, updatedDimensions, shapesArray);
84
+ // transform shapes array back into shapes map
85
+
86
+ onUpdateShapes(groupShapes(updatedShapes));
87
+ onUpdateImageDimension(updatedDimensions);
88
+ };
89
+
90
+ render() {
91
+ const {
92
+ configuration,
93
+ model,
94
+ imageSupport,
95
+ uploadSoundSupport,
96
+ onConfigurationChanged,
97
+ onImageUpload,
98
+ onModelChangedByConfig,
99
+ onPromptChanged,
100
+ onRationaleChanged,
101
+ onUpdateImageDimension,
102
+ onTeacherInstructionsChanged,
103
+ onUpdateShapes,
104
+ } = this.props;
105
+ const {
106
+ baseInputConfiguration = {},
107
+ contentDimensions = {},
108
+ maxImageWidth = {},
109
+ maxImageHeight = {},
110
+ multipleCorrect = {},
111
+ partialScoring = {},
112
+ preserveAspectRatio = {},
113
+ prompt = {},
114
+ rationale = {},
115
+ settingsPanelDisabled,
116
+ spellCheck = {},
117
+ teacherInstructions = {},
118
+ withRubric = {},
119
+ mathMlOptions = {},
120
+ language = {},
121
+ languageChoices = {},
122
+ } = configuration || {};
123
+ const {
124
+ errors,
125
+ extraCSSRules,
126
+ promptEnabled,
127
+ rationaleEnabled,
128
+ spellCheckEnabled,
129
+ teacherInstructionsEnabled,
130
+ toolbarEditorPosition,
131
+ } = model || {};
132
+ const {
133
+ prompt: promptError,
134
+ rationale: rationaleError,
135
+ shapes: shapesError,
136
+ selections: selectionsError,
137
+ teacherInstructions: teacherInstructionsError,
138
+ } = errors || {};
139
+ const validationMessage = generateValidationMessage(configuration);
140
+
141
+ const defaultImageMaxWidth = maxImageWidth && maxImageWidth.prompt;
142
+ const defaultImageMaxHeight = maxImageHeight && maxImageHeight.prompt;
143
+
144
+ const toolbarOpts = {
145
+ position: toolbarEditorPosition === 'top' ? 'top' : 'bottom',
146
+ };
147
+
148
+ const panelSettings = {
149
+ multipleCorrect: multipleCorrect.settings && toggle(multipleCorrect.label),
150
+ partialScoring: partialScoring.settings && toggle(partialScoring.label),
151
+ promptEnabled: prompt.settings && toggle(prompt.label),
152
+ 'language.enabled': language.settings && toggle(language.label, true),
153
+ language: language.settings && language.enabled && dropdown(languageChoices.label, languageChoices.options),
154
+ };
155
+ const panelProperties = {
156
+ teacherInstructionsEnabled: teacherInstructions.settings && toggle(teacherInstructions.label),
157
+ rationaleEnabled: rationale.settings && toggle(rationale.label),
158
+ spellCheckEnabled: spellCheck.settings && toggle(spellCheck.label),
159
+ rubricEnabled: withRubric?.settings && toggle(withRubric?.label),
160
+ };
161
+
162
+ const getPluginProps = (props = {}) => ({
163
+ ...baseInputConfiguration,
164
+ ...props,
165
+ });
166
+
167
+ return (
168
+ <layout.ConfigLayout
169
+ extraCSSRules={extraCSSRules}
170
+ dimensions={contentDimensions}
171
+ hideSettings={settingsPanelDisabled}
172
+ settings={
173
+ <Panel
174
+ model={model}
175
+ onChangeModel={onModelChangedByConfig}
176
+ configuration={configuration}
177
+ onChangeConfiguration={onConfigurationChanged}
178
+ groups={{
179
+ Settings: panelSettings,
180
+ Properties: panelProperties,
181
+ }}
182
+ />
183
+ }
184
+ >
185
+ {teacherInstructionsEnabled && (
186
+ <PromptContainer label={teacherInstructions.label}>
187
+ <EditableHtml
188
+ markup={model.teacherInstructions || ''}
189
+ onChange={onTeacherInstructionsChanged}
190
+ imageSupport={imageSupport}
191
+ nonEmpty={false}
192
+ error={teacherInstructionsError}
193
+ toolbarOpts={toolbarOpts}
194
+ pluginProps={getPluginProps(teacherInstructions?.inputConfiguration)}
195
+ spellCheck={spellCheckEnabled}
196
+ maxImageWidth={(maxImageWidth && maxImageWidth.teacherInstructions) || defaultImageMaxWidth}
197
+ maxImageHeight={(maxImageHeight && maxImageHeight.teacherInstructions) || defaultImageMaxHeight}
198
+ uploadSoundSupport={uploadSoundSupport}
199
+ languageCharactersProps={[{ language: 'spanish' }, { language: 'special' }]}
200
+ mathMlOptions={mathMlOptions}
201
+ />
202
+ {teacherInstructionsError && <ErrorText>{teacherInstructionsError}</ErrorText>}
203
+ </PromptContainer>
204
+ )}
205
+
206
+ {promptEnabled && (
207
+ <PromptContainer label={prompt.label}>
208
+ <EditableHtml
209
+ markup={model.prompt || ''}
210
+ onChange={onPromptChanged}
211
+ imageSupport={imageSupport}
212
+ nonEmpty={false}
213
+ error={promptError}
214
+ toolbarOpts={toolbarOpts}
215
+ pluginProps={getPluginProps(prompt?.inputConfiguration)}
216
+ spellCheck={spellCheckEnabled}
217
+ maxImageWidth={defaultImageMaxWidth}
218
+ maxImageHeight={defaultImageMaxHeight}
219
+ uploadSoundSupport={uploadSoundSupport}
220
+ languageCharactersProps={[{ language: 'spanish' }, { language: 'special' }]}
221
+ mathMlOptions={mathMlOptions}
222
+ />
223
+ {promptError && <ErrorText>{promptError}</ErrorText>}
224
+ </PromptContainer>
225
+ )}
226
+
227
+ <FlexContainer>
228
+ <SubHeading variant="h6">
229
+ Define Hotspot
230
+ </SubHeading>
231
+ <StyledTooltip
232
+ disableFocusListener
233
+ disableTouchListener
234
+ placement={'left'}
235
+ title={validationMessage}
236
+ >
237
+ <Info fontSize={'small'} color={'primary'} style={{ float: 'right' }} />
238
+ </StyledTooltip>
239
+ </FlexContainer>
240
+
241
+ <HotspotPalette
242
+ hotspotColor={model.hotspotColor}
243
+ hotspotList={model.hotspotList}
244
+ outlineColor={model.outlineColor}
245
+ outlineList={model.outlineList}
246
+ onHotspotColorChange={(color) => this.handleColorChange('hotspot', color)}
247
+ onOutlineColorChange={(color) => this.handleColorChange('outline', color)}
248
+ />
249
+
250
+ <HotspotContainer
251
+ dimensions={model.dimensions}
252
+ imageUrl={model.imageUrl}
253
+ multipleCorrect={model.multipleCorrect}
254
+ hasErrors={!!shapesError || !!selectionsError}
255
+ hotspotColor={model.hotspotColor}
256
+ outlineColor={model.outlineColor}
257
+ selectedHotspotColor={model.selectedHotspotColor}
258
+ hoverOutlineColor={model.hoverOutlineColor}
259
+ onUpdateImageDimension={onUpdateImageDimension}
260
+ onUpdateShapes={onUpdateShapes}
261
+ onImageUpload={onImageUpload}
262
+ shapes={model.shapes}
263
+ strokeWidth={model.strokeWidth}
264
+ preserveAspectRatioEnabled={preserveAspectRatio.enabled}
265
+ insertImage={imageSupport && imageSupport.add}
266
+ />
267
+ {shapesError && <ErrorText>{shapesError}</ErrorText>}
268
+ {selectionsError && <ErrorText>{selectionsError}</ErrorText>}
269
+
270
+ {model.imageUrl && (
271
+ <React.Fragment>
272
+ <Typography variant="h6">Image Dimensions</Typography>
273
+
274
+ <DimensionsContainer>
275
+ <FieldContainer>
276
+ <NumberTextField
277
+ key="hotspot-manual-width"
278
+ label="Width"
279
+ value={model.dimensions.width}
280
+ min={0}
281
+ onChange={(e, value) => this.handleOnUpdateImageDimensions(value, 'width')}
282
+ showErrorWhenOutsideRange
283
+ />
284
+ </FieldContainer>
285
+
286
+ <FieldContainer>
287
+ <NumberTextField
288
+ key="hotspot-manual-height"
289
+ label="Height"
290
+ value={model.dimensions.height}
291
+ min={0}
292
+ onChange={(e, value) => this.handleOnUpdateImageDimensions(value, 'height')}
293
+ showErrorWhenOutsideRange
294
+ />
295
+ </FieldContainer>
296
+ </DimensionsContainer>
297
+ </React.Fragment>
298
+ )}
299
+
300
+ {rationaleEnabled && (
301
+ <PromptContainer label={rationale.label}>
302
+ <EditableHtml
303
+ markup={model.rationale || ''}
304
+ onChange={onRationaleChanged}
305
+ imageSupport={imageSupport}
306
+ error={rationaleError}
307
+ toolbarOpts={toolbarOpts}
308
+ pluginProps={getPluginProps(rationale?.inputConfiguration)}
309
+ spellCheck={spellCheckEnabled}
310
+ maxImageWidth={(maxImageWidth && maxImageWidth.rationale) || defaultImageMaxWidth}
311
+ maxImageHeight={(maxImageHeight && maxImageHeight.rationale) || defaultImageMaxHeight}
312
+ uploadSoundSupport={uploadSoundSupport}
313
+ languageCharactersProps={[{ language: 'spanish' }, { language: 'special' }]}
314
+ mathMlOptions={mathMlOptions}
315
+ />
316
+ {rationaleError && <ErrorText>{rationaleError}</ErrorText>}
317
+ </PromptContainer>
318
+ )}
319
+ </layout.ConfigLayout>
320
+ );
321
+ }
322
+ }
323
+
324
+ Root.propTypes = {
325
+ configuration: PropTypes.object,
326
+ model: PropTypes.object.isRequired,
327
+ imageSupport: PropTypes.shape({
328
+ add: PropTypes.func,
329
+ delete: PropTypes.func,
330
+ }),
331
+ uploadSoundSupport: PropTypes.shape({
332
+ add: PropTypes.func,
333
+ delete: PropTypes.func,
334
+ }),
335
+ onImageUpload: PropTypes.func.isRequired,
336
+ onColorChanged: PropTypes.func.isRequired,
337
+ onPromptChanged: PropTypes.func.isRequired,
338
+ onUpdateImageDimension: PropTypes.func.isRequired,
339
+ onUpdateShapes: PropTypes.func.isRequired,
340
+ onModelChangedByConfig: PropTypes.func.isRequired,
341
+ onRationaleChanged: PropTypes.func.isRequired,
342
+ onConfigurationChanged: PropTypes.func.isRequired,
343
+ onTeacherInstructionsChanged: PropTypes.func.isRequired,
344
+ };
345
+
346
+ export default Root;
@@ -0,0 +1,81 @@
1
+ export class CircleShape {
2
+ static name = 'circle'
3
+
4
+ static create(shapes, e) {
5
+ const newShapes = [...shapes];
6
+ const highestId = Math.max(...newShapes.map((shape) => parseInt(shape.id)), 0);
7
+ const newCircle = {
8
+ id: `${highestId + 1}`,
9
+ radius: 0,
10
+ x: e.evt.layerX,
11
+ y: e.evt.layerY,
12
+ group: 'circles',
13
+ index: newShapes.length,
14
+ };
15
+
16
+ newShapes.push(newCircle);
17
+
18
+ return {
19
+ shapes: newShapes,
20
+ isDrawing: true,
21
+ isDrawingShapeId: newCircle.id,
22
+ };
23
+ }
24
+
25
+ static finalizeCreation(state, props) {
26
+ const currentShapeIndex = state.shapes.findIndex((shape) => shape.id === state.isDrawingShapeId);
27
+
28
+ if (currentShapeIndex !== -1) {
29
+ const currentShape = state.shapes[currentShapeIndex];
30
+
31
+ // Check if the shape is a valid circle (has more than 0 radius) before finalizing
32
+ if (currentShape.radius > 0) {
33
+ return {
34
+ ...state,
35
+ isDrawing: false,
36
+ stateShapes: false,
37
+ isDrawingShapeId: undefined,
38
+ };
39
+ } else {
40
+ return {
41
+ ...state,
42
+ isDrawing: false,
43
+ stateShapes: false,
44
+ isDrawingShapeId: undefined,
45
+ shapes: state.shapes.filter((shape) => shape.id !== state.isDrawingShapeId),
46
+ };
47
+ }
48
+ }
49
+
50
+ return {
51
+ ...state,
52
+ isDrawing: false,
53
+ stateShapes: false,
54
+ isDrawingShapeId: undefined,
55
+ };
56
+ }
57
+
58
+ static handleMouseMove(state, e) {
59
+ const { isDrawing, isDrawingShapeId, shapes } = state;
60
+
61
+ if (isDrawing) {
62
+ const tempShapes = [...shapes];
63
+ const resizingShapeIndex = tempShapes.findIndex((shape) => shape.id === isDrawingShapeId);
64
+
65
+ if (resizingShapeIndex !== -1) {
66
+ const resizingShape = tempShapes[resizingShapeIndex];
67
+
68
+ // Calculate radius based on mouse position
69
+ const dx = e.evt.layerX - resizingShape.x;
70
+ const dy = e.evt.layerY - resizingShape.y;
71
+ resizingShape.radius = Math.sqrt(dx * dx + dy * dy);
72
+
73
+ return {
74
+ shapes: tempShapes,
75
+ };
76
+ }
77
+ }
78
+
79
+ return state;
80
+ }
81
+ }
@@ -0,0 +1,4 @@
1
+ export * from './circle';
2
+ export * from './rectagle';
3
+ export * from './polygon';
4
+ export * from './utils';