@configuratorware/configurator-admingui 1.37.3 → 1.38.1

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 (25) hide show
  1. package/App/Data.js +4 -4
  2. package/Screens/Designer/SubScreens/Visualization/Components/AreaEditor.js +89 -67
  3. package/Screens/Designer/SubScreens/Visualization/Components/AreaEditorForm.js +203 -0
  4. package/Screens/Designer/SubScreens/Visualization/Containers/Edit/DataLinks.js +15 -11
  5. package/Screens/Designer/SubScreens/Visualization/Containers/Edit/Edit.js +66 -10
  6. package/Screens/Designer/SubScreens/Visualization/Screen.js +1 -1
  7. package/Screens/Item/Components/CopyCreatorItemDialog.js +246 -0
  8. package/Screens/Item/Containers/Edit.js +1 -1
  9. package/Screens/Item/Containers/List.js +3 -1
  10. package/Screens/Item/Reducers/Actions.js +18 -2
  11. package/Screens/Item/Translations.js +10 -3
  12. package/Screens/Itemclassification/Reducers/Actions.js +5 -1
  13. package/package.json +2 -2
  14. package/src/App/Data.js +4 -3
  15. package/src/Screens/Designer/SubScreens/Visualization/Components/AreaEditor.js +30 -5
  16. package/src/Screens/Designer/SubScreens/Visualization/Components/AreaEditorForm.js +139 -0
  17. package/src/Screens/Designer/SubScreens/Visualization/Containers/Edit/DataLinks.js +14 -8
  18. package/src/Screens/Designer/SubScreens/Visualization/Containers/Edit/Edit.js +68 -10
  19. package/src/Screens/Designer/SubScreens/Visualization/Screen.js +1 -1
  20. package/src/Screens/Item/Components/CopyCreatorItemDialog.js +130 -0
  21. package/src/Screens/Item/Containers/Edit.js +1 -1
  22. package/src/Screens/Item/Containers/List.js +8 -4
  23. package/src/Screens/Item/Reducers/Actions.js +10 -1
  24. package/src/Screens/Item/Translations.js +9 -2
  25. package/src/Screens/Itemclassification/Reducers/Actions.js +5 -1
@@ -0,0 +1,246 @@
1
+ "use strict";
2
+
3
+ function _typeof(obj) { "@babel/helpers - typeof"; if (typeof Symbol === "function" && typeof Symbol.iterator === "symbol") { _typeof = function _typeof(obj) { return typeof obj; }; } else { _typeof = function _typeof(obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }; } return _typeof(obj); }
4
+
5
+ Object.defineProperty(exports, "__esModule", {
6
+ value: true
7
+ });
8
+ exports["default"] = void 0;
9
+
10
+ var _react = _interopRequireWildcard(require("react"));
11
+
12
+ var _reactRedux = require("react-redux");
13
+
14
+ var _i18n = require("../../../App/i18n");
15
+
16
+ var _Dialog = _interopRequireDefault(require("../../../UIComponents/Dialog"));
17
+
18
+ var _FlatButton = _interopRequireDefault(require("../../../UIComponents/FlatButton"));
19
+
20
+ var _TextField = _interopRequireDefault(require("../../../UIComponents/TextField"));
21
+
22
+ var _IconButton = _interopRequireDefault(require("../../../UIComponents/IconButton"));
23
+
24
+ var _Tooltip = _interopRequireDefault(require("../../../UIComponents/Tooltip"));
25
+
26
+ var _FileCopy = _interopRequireDefault(require("@material-ui/icons/FileCopy"));
27
+
28
+ var _Actions = _interopRequireDefault(require("../Reducers/Actions"));
29
+
30
+ var _IntlInput = _interopRequireDefault(require("../../../Components/FormFragments/IntlInput"));
31
+
32
+ var _Form = require("../../../Components/Form");
33
+
34
+ var _Data = require("../../../App/Data");
35
+
36
+ var _Actions2 = require("../../../App/Reducers/Frame/Actions");
37
+
38
+ function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { "default": obj }; }
39
+
40
+ function _getRequireWildcardCache(nodeInterop) { if (typeof WeakMap !== "function") return null; var cacheBabelInterop = new WeakMap(); var cacheNodeInterop = new WeakMap(); return (_getRequireWildcardCache = function _getRequireWildcardCache(nodeInterop) { return nodeInterop ? cacheNodeInterop : cacheBabelInterop; })(nodeInterop); }
41
+
42
+ function _interopRequireWildcard(obj, nodeInterop) { if (!nodeInterop && obj && obj.__esModule) { return obj; } if (obj === null || _typeof(obj) !== "object" && typeof obj !== "function") { return { "default": obj }; } var cache = _getRequireWildcardCache(nodeInterop); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (key !== "default" && Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } newObj["default"] = obj; if (cache) { cache.set(obj, newObj); } return newObj; }
43
+
44
+ function asyncGeneratorStep(gen, resolve, reject, _next, _throw, key, arg) { try { var info = gen[key](arg); var value = info.value; } catch (error) { reject(error); return; } if (info.done) { resolve(value); } else { Promise.resolve(value).then(_next, _throw); } }
45
+
46
+ function _asyncToGenerator(fn) { return function () { var self = this, args = arguments; return new Promise(function (resolve, reject) { var gen = fn.apply(self, args); function _next(value) { asyncGeneratorStep(gen, resolve, reject, _next, _throw, "next", value); } function _throw(err) { asyncGeneratorStep(gen, resolve, reject, _next, _throw, "throw", err); } _next(undefined); }); }; }
47
+
48
+ function _slicedToArray(arr, i) { return _arrayWithHoles(arr) || _iterableToArrayLimit(arr, i) || _unsupportedIterableToArray(arr, i) || _nonIterableRest(); }
49
+
50
+ function _nonIterableRest() { throw new TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); }
51
+
52
+ function _unsupportedIterableToArray(o, minLen) { if (!o) return; if (typeof o === "string") return _arrayLikeToArray(o, minLen); var n = Object.prototype.toString.call(o).slice(8, -1); if (n === "Object" && o.constructor) n = o.constructor.name; if (n === "Map" || n === "Set") return Array.from(o); if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _arrayLikeToArray(o, minLen); }
53
+
54
+ function _arrayLikeToArray(arr, len) { if (len == null || len > arr.length) len = arr.length; for (var i = 0, arr2 = new Array(len); i < len; i++) { arr2[i] = arr[i]; } return arr2; }
55
+
56
+ function _iterableToArrayLimit(arr, i) { var _i = arr == null ? null : typeof Symbol !== "undefined" && arr[Symbol.iterator] || arr["@@iterator"]; if (_i == null) return; var _arr = []; var _n = true; var _d = false; var _s, _e; try { for (_i = _i.call(arr); !(_n = (_s = _i.next()).done); _n = true) { _arr.push(_s.value); if (i && _arr.length === i) break; } } catch (err) { _d = true; _e = err; } finally { try { if (!_n && _i["return"] != null) _i["return"](); } finally { if (_d) throw _e; } } return _arr; }
57
+
58
+ function _arrayWithHoles(arr) { if (Array.isArray(arr)) return arr; }
59
+
60
+ var IntlInput = (0, _IntlInput["default"])(_Form.InputSwitch);
61
+
62
+ var CopyCreatorItemDialog = function CopyCreatorItemDialog(_ref) {
63
+ var data = _ref.data,
64
+ selected = _ref.selected,
65
+ copyCreatorItem = _ref.copyCreatorItem,
66
+ clearSelection = _ref.clearSelection,
67
+ reloadList = _ref.reloadList,
68
+ showErrorMessage = _ref.showErrorMessage;
69
+
70
+ var _useState = (0, _react.useState)(false),
71
+ _useState2 = _slicedToArray(_useState, 2),
72
+ showEditor = _useState2[0],
73
+ setShowEditor = _useState2[1];
74
+
75
+ var _useState3 = (0, _react.useState)(''),
76
+ _useState4 = _slicedToArray(_useState3, 2),
77
+ identifier = _useState4[0],
78
+ setIdentifier = _useState4[1];
79
+
80
+ var _useState5 = (0, _react.useState)(null),
81
+ _useState6 = _slicedToArray(_useState5, 2),
82
+ identifierError = _useState6[0],
83
+ setIdentifierError = _useState6[1];
84
+
85
+ var _useState7 = (0, _react.useState)([]),
86
+ _useState8 = _slicedToArray(_useState7, 2),
87
+ titles = _useState8[0],
88
+ setTitles = _useState8[1];
89
+
90
+ var hasSelection = selected && selected.length === 1;
91
+ var hasOnlyCreatorItemsSelected = hasSelection;
92
+
93
+ if (hasSelection) {
94
+ selected.map(function (selectedIndex) {
95
+ return hasOnlyCreatorItemsSelected = data[selectedIndex].configurationMode === 'creator';
96
+ });
97
+ }
98
+
99
+ var onTitleChange = function onTitleChange(name, values) {
100
+ setTitles(values);
101
+ };
102
+
103
+ var onIdentifierChange = function onIdentifierChange(name, identifier) {
104
+ setIdentifier(identifier);
105
+ };
106
+
107
+ var validateIdentifier = function validateIdentifier() {
108
+ var isSameIdentifier = identifier === data[selected[0]].identifier;
109
+
110
+ if (identifier.match(_Data.identifierPattern) && identifier.match(_Data.identifierPattern)[0] === identifier && !isSameIdentifier) {
111
+ setIdentifierError(null);
112
+ } else {
113
+ setIdentifierError((0, _i18n.t)('customValidators.identifier'));
114
+ }
115
+ };
116
+
117
+ var onSave = /*#__PURE__*/function () {
118
+ var _ref2 = _asyncToGenerator( /*#__PURE__*/regeneratorRuntime.mark(function _callee() {
119
+ var saveResult;
120
+ return regeneratorRuntime.wrap(function _callee$(_context) {
121
+ while (1) {
122
+ switch (_context.prev = _context.next) {
123
+ case 0:
124
+ _context.next = 2;
125
+ return copyCreatorItem(data[selected[0]].id, identifier, titles);
126
+
127
+ case 2:
128
+ saveResult = _context.sent;
129
+
130
+ if (saveResult.error) {
131
+ showErrorMessage((0, _i18n.t)('entity.saveError'));
132
+ } else {
133
+ clearSelection();
134
+ reloadList();
135
+ }
136
+
137
+ case 4:
138
+ case "end":
139
+ return _context.stop();
140
+ }
141
+ }
142
+ }, _callee);
143
+ }));
144
+
145
+ return function onSave() {
146
+ return _ref2.apply(this, arguments);
147
+ };
148
+ }();
149
+
150
+ if (!hasOnlyCreatorItemsSelected) {
151
+ return null;
152
+ }
153
+
154
+ return /*#__PURE__*/_react["default"].createElement(_react["default"].Fragment, null, /*#__PURE__*/_react["default"].createElement(_Tooltip["default"], {
155
+ title: (0, _i18n.t)('item.copyCreatorItemDialogTitle')
156
+ }, /*#__PURE__*/_react["default"].createElement(_IconButton["default"], {
157
+ color: hasSelection ? 'primary' : '',
158
+ onClick: function onClick() {
159
+ return setShowEditor(true);
160
+ }
161
+ }, /*#__PURE__*/_react["default"].createElement(_FileCopy["default"], null))), /*#__PURE__*/_react["default"].createElement(_Dialog["default"], {
162
+ title: (0, _i18n.t)('item.copyCreatorItemDialogTitle'),
163
+ actions: /*#__PURE__*/_react["default"].createElement(_react["default"].Fragment, null, /*#__PURE__*/_react["default"].createElement(_FlatButton["default"], {
164
+ label: (0, _i18n.t)('cancel'),
165
+ onClick: function onClick() {
166
+ return setShowEditor(false);
167
+ }
168
+ }), /*#__PURE__*/_react["default"].createElement(_FlatButton["default"], {
169
+ label: (0, _i18n.t)('duplicate'),
170
+ primary: true,
171
+ onClick: onSave,
172
+ disabled: identifier === '' || identifierError !== null
173
+ })),
174
+ open: showEditor && hasOnlyCreatorItemsSelected
175
+ }, /*#__PURE__*/_react["default"].createElement("div", {
176
+ style: {
177
+ display: 'flex',
178
+ mindWidth: '620px',
179
+ flexDirection: 'column'
180
+ }
181
+ }, /*#__PURE__*/_react["default"].createElement(_TextField["default"], {
182
+ label: (0, _i18n.t)('item.copyItemIdentifier'),
183
+ onChange: function onChange(evt) {
184
+ onIdentifierChange(name, evt.target.value);
185
+ },
186
+ onBlur: validateIdentifier,
187
+ style: {
188
+ width: '100%'
189
+ },
190
+ error: identifierError !== null,
191
+ helperText: identifierError,
192
+ value: identifier,
193
+ type: 'text'
194
+ }), /*#__PURE__*/_react["default"].createElement(IntlInput, {
195
+ intl: {
196
+ name: 'title',
197
+ type: 'text'
198
+ },
199
+ label: (0, _i18n.t)('item.copyItemTitle'),
200
+ onChange: onTitleChange,
201
+ style: {
202
+ width: 200
203
+ },
204
+ value: titles
205
+ }))));
206
+ };
207
+
208
+ var mapDispatchToProps = function mapDispatchToProps(dispatch) {
209
+ return {
210
+ copyCreatorItem: function () {
211
+ var _copyCreatorItem = _asyncToGenerator( /*#__PURE__*/regeneratorRuntime.mark(function _callee2(sourceItemId, newItemIdentifier, itemTexts) {
212
+ return regeneratorRuntime.wrap(function _callee2$(_context2) {
213
+ while (1) {
214
+ switch (_context2.prev = _context2.next) {
215
+ case 0:
216
+ _context2.next = 2;
217
+ return dispatch(_Actions["default"].copyCreatorItem(sourceItemId, newItemIdentifier, itemTexts));
218
+
219
+ case 2:
220
+ return _context2.abrupt("return", _context2.sent);
221
+
222
+ case 3:
223
+ case "end":
224
+ return _context2.stop();
225
+ }
226
+ }
227
+ }, _callee2);
228
+ }));
229
+
230
+ function copyCreatorItem(_x, _x2, _x3) {
231
+ return _copyCreatorItem.apply(this, arguments);
232
+ }
233
+
234
+ return copyCreatorItem;
235
+ }(),
236
+ showErrorMessage: function showErrorMessage(errorMessage) {
237
+ return dispatch((0, _Actions2.showErrorMessage)(errorMessage));
238
+ }
239
+ };
240
+ };
241
+
242
+ var _default = (0, _reactRedux.connect)(function () {
243
+ return null;
244
+ }, mapDispatchToProps)(CopyCreatorItemDialog);
245
+
246
+ exports["default"] = _default;
@@ -371,7 +371,7 @@ var formFields = [{
371
371
  }
372
372
  }, {
373
373
  name: 'itemclassifications',
374
- label: 'itemclassifications',
374
+ label: 'ItemClassifications',
375
375
  type: 'dynSource',
376
376
  sourceConfig: {
377
377
  value: 'id',
@@ -17,6 +17,8 @@ var _DefaultConnectedList = _interopRequireDefault(require("../../../Components/
17
17
 
18
18
  var _ListItemStatusEditor = _interopRequireDefault(require("../Components/ListItemStatusEditor"));
19
19
 
20
+ var _CopyCreatorItemDialog = _interopRequireDefault(require("../Components/CopyCreatorItemDialog"));
21
+
20
22
  var _excluded = ["listProps"];
21
23
 
22
24
  function _getRequireWildcardCache(nodeInterop) { if (typeof WeakMap !== "function") return null; var cacheBabelInterop = new WeakMap(); var cacheNodeInterop = new WeakMap(); return (_getRequireWildcardCache = function _getRequireWildcardCache(nodeInterop) { return nodeInterop ? cacheNodeInterop : cacheBabelInterop; })(nodeInterop); }
@@ -108,7 +110,7 @@ var ItemList = function ItemList(_ref) {
108
110
  return /*#__PURE__*/_react["default"].createElement(ConnectedItemList, _extends({}, props, {
109
111
  listProps: _objectSpread({
110
112
  renderAction: function renderAction(props) {
111
- return /*#__PURE__*/_react["default"].createElement(_ListItemStatusEditor["default"], props);
113
+ return /*#__PURE__*/_react["default"].createElement(_react["default"].Fragment, null, /*#__PURE__*/_react["default"].createElement(_ListItemStatusEditor["default"], props), /*#__PURE__*/_react["default"].createElement(_CopyCreatorItemDialog["default"], props));
112
114
  }
113
115
  }, listProps)
114
116
  }));
@@ -5,7 +5,7 @@ function _typeof(obj) { "@babel/helpers - typeof"; if (typeof Symbol === "functi
5
5
  Object.defineProperty(exports, "__esModule", {
6
6
  value: true
7
7
  });
8
- exports["default"] = exports.ITEM_REDUCER_NAME = exports.ITEM_DATA_KEY = exports.ITEM_STATUS_URL = exports.ITEM_LIST_URL = exports.ITEM_LIST_KEY = void 0;
8
+ exports["default"] = exports.ITEM_REDUCER_NAME = exports.ITEM_DATA_KEY = exports.ITEM_STATUS_URL = exports.ITEM_COPY_URL = exports.ITEM_LIST_URL = exports.ITEM_LIST_KEY = void 0;
9
9
 
10
10
  var apiActions = _interopRequireWildcard(require("../../../App/Reducers/Api/Actions"));
11
11
 
@@ -27,6 +27,12 @@ var ITEM_LIST_KEY = 'items';
27
27
  exports.ITEM_LIST_KEY = ITEM_LIST_KEY;
28
28
  var ITEM_LIST_URL = 'items';
29
29
  exports.ITEM_LIST_URL = ITEM_LIST_URL;
30
+
31
+ var ITEM_COPY_URL = function ITEM_COPY_URL(id) {
32
+ return "items/".concat(id, "/duplicate");
33
+ };
34
+
35
+ exports.ITEM_COPY_URL = ITEM_COPY_URL;
30
36
  var ITEM_STATUS_URL = 'items/status';
31
37
  exports.ITEM_STATUS_URL = ITEM_STATUS_URL;
32
38
  var ITEM_DATA_KEY = 'item';
@@ -73,6 +79,15 @@ var setItemStatusForMultipleItems = function setItemStatusForMultipleItems(selec
73
79
  };
74
80
  };
75
81
 
82
+ var copyCreatorItem = function copyCreatorItem(sourceItemId, newItemIdentifier, itemTexts) {
83
+ return function (dispatch, getState) {
84
+ return dispatch(apiActions.postData(ITEM_COPY_URL(sourceItemId), {
85
+ "identifier": newItemIdentifier,
86
+ itemTexts: itemTexts
87
+ }));
88
+ };
89
+ };
90
+
76
91
  var setFieldData = function setFieldData(field, value) {
77
92
  return function (dispatch, getState) {
78
93
  var entityState = getState()[ITEM_REDUCER_NAME];
@@ -91,7 +106,8 @@ var setFieldData = function setFieldData(field, value) {
91
106
  var _default = _objectSpread(_objectSpread({}, actions), {}, {
92
107
  setFieldData: setFieldData,
93
108
  setItemStatusForMultipleItems: setItemStatusForMultipleItems,
94
- loadItemStatus: loadItemStatus
109
+ loadItemStatus: loadItemStatus,
110
+ copyCreatorItem: copyCreatorItem
95
111
  });
96
112
 
97
113
  exports["default"] = _default;
@@ -17,7 +17,7 @@ require("../../App/i18n").use({
17
17
  save: 'Save',
18
18
  editDialogTitle: 'Edit selected entries',
19
19
  editDialogHint: 'Use the checkboxes to select one or more entries in the list</br>and then click this icon to set the item status for them.',
20
- itemclassifications: 'Product categories',
20
+ ItemClassifications: 'Product categories',
21
21
  visualizationData: {
22
22
  '2d': "2D",
23
23
  '3d': "3D",
@@ -56,7 +56,10 @@ require("../../App/i18n").use({
56
56
  coreData: {
57
57
  dialogTitle: 'Edit core data'
58
58
  }
59
- }
59
+ },
60
+ copyCreatorItemDialogTitle: 'Copy selected product',
61
+ copyItemIdentifier: 'New identifier',
62
+ copyItemTitle: 'New title'
60
63
  },
61
64
  configuratorQuickView: {
62
65
  buttonText: 'Preview'
@@ -93,8 +96,9 @@ require("../../App/i18n").use({
93
96
  cancel: 'Abbrechen',
94
97
  save: 'Speichern',
95
98
  editDialogTitle: 'Gewählte Einträge bearbeiten',
99
+ duplicate: 'Duplizieren',
96
100
  editDialogHint: 'Nutze die Checkboxen in der Liste und klicke dann dieses Icon,</br>um den Status für einen oder mehrere Einträge zu setzen.',
97
- itemclassifications: 'Produktkategorien',
101
+ ItemClassifications: 'Produktkategorien',
98
102
  visualizationData: {
99
103
  '2d': "2D",
100
104
  '3d': "3D",
@@ -117,6 +121,9 @@ require("../../App/i18n").use({
117
121
  searchOrAddAttributeValue: 'Suche einen Attributwert oder lege einen neuen mit dem + an'
118
122
  },
119
123
  headline: 'Dieses Produkt ist folgendermaßen definiert:',
124
+ copyCreatorItemDialogTitle: 'Gewähltes Produkt kopieren',
125
+ copyItemIdentifier: 'Neuer Identifier',
126
+ copyItemTitle: 'Neuer Titel',
120
127
  editorPopup: {
121
128
  attribute: {
122
129
  dialogTitle: 'Attribut hinzufügen'
@@ -7,6 +7,8 @@ exports["default"] = exports.postData = exports.loadEntity = exports.hideDetails
7
7
 
8
8
  var _Actions = require("../../../App/Reducers/Entity/Actions");
9
9
 
10
+ var _Actions2 = require("../../Item/DataStructures/Attributes/Actions");
11
+
10
12
  var ITEMCLASSIFICATION_LIST_KEY = 'itemclassifications';
11
13
  exports.ITEMCLASSIFICATION_LIST_KEY = ITEMCLASSIFICATION_LIST_KEY;
12
14
  var ITEMCLASSIFICATION_DATA_KEY = 'itemclassification';
@@ -15,7 +17,9 @@ var ITEMCLASSIFICATION_REDUCER_NAME = 'itemclassificationData';
15
17
  exports.ITEMCLASSIFICATION_REDUCER_NAME = ITEMCLASSIFICATION_REDUCER_NAME;
16
18
  var ITEMCLASSIFICATIONS_URL = 'itemclassifications';
17
19
  exports.ITEMCLASSIFICATIONS_URL = ITEMCLASSIFICATIONS_URL;
18
- var actions = (0, _Actions.generateDefaultActions)(ITEMCLASSIFICATION_LIST_KEY, ITEMCLASSIFICATION_DATA_KEY, ITEMCLASSIFICATION_REDUCER_NAME, ITEMCLASSIFICATIONS_URL);
20
+ var actions = (0, _Actions.generateDefaultActions)(ITEMCLASSIFICATION_LIST_KEY, ITEMCLASSIFICATION_DATA_KEY, ITEMCLASSIFICATION_REDUCER_NAME, ITEMCLASSIFICATIONS_URL, {
21
+ customDispatchHandler: _Actions2.customDispatchHandler
22
+ });
19
23
  var hideDetails = actions.hideDetails,
20
24
  loadEntity = actions.loadEntity,
21
25
  postData = actions.postData;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@configuratorware/configurator-admingui",
3
- "version": "1.37.3",
3
+ "version": "1.38.1",
4
4
  "license": "UNLICENSED",
5
5
  "private": false,
6
6
  "dependencies": {
@@ -29,7 +29,7 @@
29
29
  "react-redux-i18n": "^1.9.3",
30
30
  "react-router": "^3.2.6",
31
31
  "react-sortable-hoc": "^1.11.0",
32
- "redhotmagma-visualization": "1.37.3",
32
+ "redhotmagma-visualization": "1.38.1",
33
33
  "redux": "^4.1.0",
34
34
  "redux-logger": "^3.0.6",
35
35
  "redux-persist": "^5.10.0",
package/src/App/Data.js CHANGED
@@ -5,6 +5,8 @@ import { t } from './i18n';
5
5
 
6
6
  export const whiteSpacePattern = /^[\S]+$/;
7
7
 
8
+ export const identifierPattern = /^[A-za-z0-9_.-]+$/;
9
+
8
10
  validate.validators.nonEmptyArray = function(value, options) {
9
11
  if (options && options === true && isArray(value) && value.length !== 0) {
10
12
  return null;
@@ -30,13 +32,12 @@ validate.validators.numericOrEmpty = function (value, options) {
30
32
  }
31
33
 
32
34
  validate.validators.identifier = function(value, options) {
33
- const pattern = /^[A-za-z0-9_.-]+$/;
34
35
  if (
35
36
  options &&
36
37
  options === true &&
37
38
  typeof value === 'string' &&
38
- value.match(pattern) &&
39
- value.match(pattern)[0] === value
39
+ value.match(identifierPattern) &&
40
+ value.match(identifierPattern)[0] === value
40
41
  ) {
41
42
  return null;
42
43
  }
@@ -1,6 +1,7 @@
1
1
  import React from 'react';
2
2
  import PropTypes from 'prop-types';
3
3
  import debounce from 'lodash/debounce';
4
+ import isEqual from 'lodash/isEqual';
4
5
  import {
5
6
  createImageObject,
6
7
  draw,
@@ -32,13 +33,34 @@ export class AreaEditor extends React.Component {
32
33
  activeControl = null;
33
34
  backgroundImage = null;
34
35
 
35
- async componentDidMount() {
36
+ componentDidMount() {
37
+ this.setupAreaEditor();
38
+ window.addEventListener('resize', this.setupAreaEditor);
39
+ }
40
+
41
+ componentDidUpdate(prevProps, prevState, snapshot) {
42
+ prevProps.areaData !== this.props.areaData && this.setupAreaEditor();
43
+ }
44
+
45
+ onChangeDebounced = debounce(prevArea => {
46
+ const { area } = this;
47
+ const { onChange } = this.props;
48
+ onChange && !isEqual(area, prevArea) && onChange();
49
+ }, 1000);
50
+
51
+ setupAreaEditor = async () => {
36
52
  const canvas = this.canvasRef.current;
37
53
  this.ctx = canvas.getContext('2d');
54
+ const { width } = canvas.parentElement.getBoundingClientRect();
55
+ const { backgroundImageElement } = this.props;
56
+
57
+ // resize the canvas to fit the texture aspect ratio
58
+ const textureAspectRatio = backgroundImageElement.width / backgroundImageElement.height;
59
+ canvas.width = width;
60
+ canvas.height = width / textureAspectRatio;
38
61
 
39
62
  this.area = await this.createScaledArea();
40
63
 
41
- const { backgroundImageElement } = this.props;
42
64
  if (backgroundImageElement) {
43
65
  this.backgroundImage = fitSize(
44
66
  createImageObject(backgroundImageElement),
@@ -59,19 +81,22 @@ export class AreaEditor extends React.Component {
59
81
 
60
82
  this.onRedraw();
61
83
  },
62
- controlFactory: object => setDefaultControls(object),
84
+ controlFactory: object => setDefaultControls(object, 20, 0),
63
85
  onUpdate: ({ getData }) => {
64
86
  const { object } = getData();
87
+ const { area } = this;
65
88
  this.area = object;
89
+ this.onChangeDebounced(area);
66
90
  },
67
91
  });
68
92
 
69
93
  this._movableArea.init();
70
94
 
71
95
  this.onUpdate();
72
- }
96
+ };
73
97
 
74
98
  componentWillUnmount() {
99
+ window.removeEventListener('resize', this.setupAreaEditor);
75
100
  this._movableArea.dispose();
76
101
  }
77
102
 
@@ -144,6 +169,6 @@ export class AreaEditor extends React.Component {
144
169
  canvasRef = React.createRef();
145
170
 
146
171
  render() {
147
- return <canvas ref={this.canvasRef} width={500} height={500} style={{ border: '1px solid #000' }} />;
172
+ return <canvas ref={this.canvasRef} style={{ border: '1px solid #000', maxWidth: '100%' }} />;
148
173
  }
149
174
  }
@@ -0,0 +1,139 @@
1
+ import React, { useEffect, useMemo, useState } from 'react';
2
+ import get from 'lodash/get';
3
+ import debounce from 'lodash/debounce';
4
+ import TextField from '@material-ui/core/TextField';
5
+ import { withStyles } from '@material-ui/core/styles';
6
+ import { t } from '../../../../../App/i18n';
7
+
8
+ const toNumber = value => Number(value) || 0;
9
+
10
+ const toRad = deg => (Math.PI * deg) / 180;
11
+
12
+ const toDeg = rad => ((180 * rad) / Math.PI).toFixed(2);
13
+
14
+ const formatters = {
15
+ angle: v => toDeg(toNumber(v)),
16
+ number: v => toNumber(v).toFixed(2),
17
+ boolean: v => !!v,
18
+ };
19
+
20
+ function persistEvent(fn) {
21
+ return function(evt) {
22
+ evt.persist();
23
+ fn(evt);
24
+ };
25
+ }
26
+
27
+ const NumberInput = withStyles({
28
+ root: {
29
+ maxWidth: 100,
30
+ marginRight: 20,
31
+ },
32
+ })(({ classes, onChange, value, ...props }) => {
33
+ const [_value, _setValue] = useState(value);
34
+ const [focused, setFocused] = useState(false);
35
+ const onFocus = () => setFocused(true);
36
+ const onBlur = useMemo(
37
+ () => () => {
38
+ _setValue(value);
39
+ setFocused(false);
40
+ },
41
+ [value]
42
+ );
43
+ useEffect(() => {
44
+ !focused && _setValue(value);
45
+ }, [value, focused]);
46
+ const onChangeDebounced = useMemo(() => persistEvent(debounce(onChange, 50)), [onChange]);
47
+ return (
48
+ <TextField
49
+ type="number"
50
+ className={classes.root}
51
+ onChange={evt => {
52
+ _setValue(evt.target.value);
53
+ onChangeDebounced(evt);
54
+ }}
55
+ onFocus={onFocus}
56
+ onBlur={onBlur}
57
+ value={_value}
58
+ {...props}
59
+ />
60
+ );
61
+ });
62
+
63
+ export const AreaEditorForm = withStyles({
64
+ sectionTitle: {
65
+ fontSize: '1.2em',
66
+ marginTop: '1.2em',
67
+ marginBottom: '0.6em',
68
+ },
69
+ groupTitle: {
70
+ marginTop: '0.6em',
71
+ },
72
+ })(({ designAreaData, onChange, className, classes }) => {
73
+ const numberChangeHandler = evt => {
74
+ if (evt.target.value) {
75
+ onChange(evt.target.name, toNumber(evt.target.value));
76
+ }
77
+ };
78
+
79
+ const angleChangeHandler = evt => {
80
+ if (evt.target.value) {
81
+ onChange(evt.target.name, toRad(toNumber(evt.target.value)));
82
+ }
83
+ };
84
+
85
+ const format = (path, formatter = formatters.number) => formatter(toNumber(get(designAreaData, path)));
86
+
87
+ return (
88
+ <div className={className}>
89
+ <div>
90
+ <NumberInput
91
+ name={'position.angle'}
92
+ label={t('designer.advancedEditor.rotation')}
93
+ onChange={angleChangeHandler}
94
+ value={format('position.angle', formatters.angle)}
95
+ />
96
+ </div>
97
+
98
+ <div>
99
+ <div className={classes.groupTitle}>{t('designer.advancedEditor.scaling')}</div>
100
+ <NumberInput
101
+ name={'position.width'}
102
+ label={t('designer.advancedEditor.width')}
103
+ onChange={numberChangeHandler}
104
+ value={format('position.width')}
105
+ inputProps={{
106
+ min: 1,
107
+ }}
108
+ />
109
+ <NumberInput
110
+ name={'position.height'}
111
+ label={t('designer.advancedEditor.height')}
112
+ onChange={numberChangeHandler}
113
+ value={format('position.height')}
114
+ inputProps={{
115
+ min: 1,
116
+ }}
117
+ />
118
+ </div>
119
+
120
+ <div>
121
+ <div className={classes.groupTitle}>{t('designer.advancedEditor.positioning')}</div>
122
+ <NumberInput
123
+ name={'position.x'}
124
+ label={'x'}
125
+ onChange={numberChangeHandler}
126
+ value={format('position.x')}
127
+ />
128
+ <NumberInput
129
+ name={'position.y'}
130
+ label={'y'}
131
+ onChange={numberChangeHandler}
132
+ value={format('position.y')}
133
+ />
134
+ </div>
135
+ </div>
136
+ );
137
+ });
138
+
139
+ export default AreaEditorForm;