sanity-plugin-hotspot-array 0.0.4 → 0.0.7
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.
- package/README.md +2 -2
- package/lib/Feedback.js.map +1 -1
- package/lib/HotspotArray.js +56 -28
- package/lib/HotspotArray.js.map +1 -1
- package/lib/Spot.js +103 -94
- package/lib/Spot.js.map +1 -1
- package/lib/useUnsetInputComponent.js.map +1 -1
- package/package.json +15 -14
- package/src/HotspotArray.tsx +74 -34
- package/src/Spot.tsx +133 -68
- package/src/useUnsetInputComponent.ts +13 -13
package/README.md
CHANGED
|
@@ -2,14 +2,14 @@
|
|
|
2
2
|
|
|
3
3
|
A configurable Custom Input for Arrays that will add and update items by clicking on an Image
|
|
4
4
|
|
|
5
|
+
<img src="https://user-images.githubusercontent.com/209129/174171697-57319ebc-03a7-4d82-a73e-b6effcb0b3ba.gif" width="600" />
|
|
6
|
+
|
|
5
7
|
## Installation
|
|
6
8
|
|
|
7
9
|
```
|
|
8
10
|
sanity install hotspot-array
|
|
9
11
|
```
|
|
10
12
|
|
|
11
|
-

|
|
12
|
-
|
|
13
13
|
## Setup
|
|
14
14
|
|
|
15
15
|
Import the `HotspotArray` component from this package, into your schema. And insert it as the `inputComponent` of an `array` field.
|
package/lib/Feedback.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"
|
|
1
|
+
{"version":3,"file":"Feedback.js","names":["Feedback","children"],"sources":["../src/Feedback.tsx"],"sourcesContent":["import React from 'react'\nimport {Card, Text} from '@sanity/ui'\n\nexport default function Feedback({children}) {\n return (\n <Card padding={4} radius={2} shadow={1} tone=\"caution\">\n <Text>{children}</Text>\n </Card>\n )\n}\n"],"mappings":";;;;;;;AAAA;;AACA;;;;AAEe,SAASA,QAAT,OAA8B;EAAA,IAAXC,QAAW,QAAXA,QAAW;EAC3C,oBACE,6BAAC,QAAD;IAAM,OAAO,EAAE,CAAf;IAAkB,MAAM,EAAE,CAA1B;IAA6B,MAAM,EAAE,CAArC;IAAwC,IAAI,EAAC;EAA7C,gBACE,6BAAC,QAAD,QAAOA,QAAP,CADF,CADF;AAKD"}
|
package/lib/HotspotArray.js
CHANGED
|
@@ -9,67 +9,95 @@ var _client = _interopRequireDefault(require("part:@sanity/base/client"));
|
|
|
9
9
|
|
|
10
10
|
var _formBuilder = require("part:@sanity/form-builder");
|
|
11
11
|
|
|
12
|
-
var
|
|
13
|
-
|
|
14
|
-
var _ui = require("@sanity/ui");
|
|
12
|
+
var _assetUtils = require("@sanity/asset-utils");
|
|
15
13
|
|
|
16
14
|
var _FormBuilderInput = require("@sanity/form-builder/lib/FormBuilderInput");
|
|
17
15
|
|
|
18
|
-
var _content = require("@sanity/util/content");
|
|
19
|
-
|
|
20
16
|
var _PatchEvent = require("@sanity/form-builder/PatchEvent");
|
|
21
17
|
|
|
22
18
|
var _imageUrl = _interopRequireDefault(require("@sanity/image-url"));
|
|
23
19
|
|
|
24
|
-
var
|
|
20
|
+
var _ui = require("@sanity/ui");
|
|
21
|
+
|
|
22
|
+
var _content = require("@sanity/util/content");
|
|
25
23
|
|
|
26
24
|
var _get = _interopRequireDefault(require("lodash/get"));
|
|
27
25
|
|
|
26
|
+
var _react = _interopRequireWildcard(require("react"));
|
|
27
|
+
|
|
28
|
+
var _web = require("@react-hookz/web");
|
|
29
|
+
|
|
30
|
+
var _Feedback = _interopRequireDefault(require("./Feedback"));
|
|
31
|
+
|
|
28
32
|
var _Spot = _interopRequireDefault(require("./Spot"));
|
|
29
33
|
|
|
30
34
|
var _useUnsetInputComponent = require("./useUnsetInputComponent");
|
|
31
35
|
|
|
32
|
-
var
|
|
36
|
+
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); }
|
|
37
|
+
|
|
38
|
+
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; }
|
|
33
39
|
|
|
34
40
|
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
|
|
35
41
|
|
|
36
|
-
function _extends() { _extends = Object.assign
|
|
42
|
+
function _extends() { _extends = Object.assign ? Object.assign.bind() : function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); }
|
|
43
|
+
|
|
44
|
+
function _slicedToArray(arr, i) { return _arrayWithHoles(arr) || _iterableToArrayLimit(arr, i) || _unsupportedIterableToArray(arr, i) || _nonIterableRest(); }
|
|
45
|
+
|
|
46
|
+
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."); }
|
|
47
|
+
|
|
48
|
+
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); }
|
|
37
49
|
|
|
38
|
-
|
|
50
|
+
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; }
|
|
39
51
|
|
|
40
|
-
var
|
|
52
|
+
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; }
|
|
53
|
+
|
|
54
|
+
function _arrayWithHoles(arr) { if (Array.isArray(arr)) return arr; }
|
|
41
55
|
|
|
42
56
|
var imageStyle = {
|
|
43
57
|
width: "100%",
|
|
44
58
|
height: "auto"
|
|
45
59
|
};
|
|
60
|
+
var VALID_ROOT_PATHS = ['document', 'parent'];
|
|
46
61
|
|
|
47
62
|
var HotspotArray = /*#__PURE__*/_react.default.forwardRef((props, ref) => {
|
|
48
|
-
var _type$options, _type$options2;
|
|
63
|
+
var _type$options, _type$options2, _type$options3;
|
|
49
64
|
|
|
50
65
|
var type = props.type,
|
|
51
66
|
value = props.value,
|
|
52
67
|
onChange = props.onChange,
|
|
53
|
-
|
|
68
|
+
document = props.document;
|
|
54
69
|
|
|
55
70
|
var _ref = type !== null && type !== void 0 ? type : {},
|
|
56
71
|
options = _ref.options; // Attempt prevention of infinite loop in <FormBuilderInput />
|
|
57
72
|
// Re-renders can still occur if this Component is used again in a nested field
|
|
58
73
|
|
|
59
74
|
|
|
60
|
-
var typeWithoutInputComponent = (0, _useUnsetInputComponent.useUnsetInputComponent)(type, type === null || type === void 0 ? void 0 : type.inputComponent);
|
|
61
|
-
|
|
75
|
+
var typeWithoutInputComponent = (0, _useUnsetInputComponent.useUnsetInputComponent)(type, type === null || type === void 0 ? void 0 : type.inputComponent);
|
|
76
|
+
var imageHotspotPathRoot = VALID_ROOT_PATHS.includes(options === null || options === void 0 ? void 0 : options.imageHotspotPathRoot) ? props[options.imageHotspotPathRoot] : document;
|
|
77
|
+
/**
|
|
78
|
+
* Finding the image from the imageHotspotPathRoot (defaults to document),
|
|
79
|
+
* using the path from the hotspot's `options` field
|
|
80
|
+
*
|
|
81
|
+
* when changes in imageHotspotPathRoot (e.g. document) occur,
|
|
82
|
+
* check if there are any changes to the hotspotImage and update the reference
|
|
83
|
+
*/
|
|
84
|
+
|
|
85
|
+
var hotspotImage = _react.default.useMemo(() => {
|
|
86
|
+
return (0, _get.default)(imageHotspotPathRoot, options === null || options === void 0 ? void 0 : options.hotspotImagePath);
|
|
87
|
+
}, [imageHotspotPathRoot]);
|
|
62
88
|
|
|
63
89
|
var displayImage = _react.default.useMemo(() => {
|
|
64
90
|
var _hotspotImage$asset;
|
|
65
91
|
|
|
66
|
-
var
|
|
92
|
+
var builder = (0, _imageUrl.default)(_client.default).dataset(_client.default.config().dataset);
|
|
93
|
+
|
|
94
|
+
var urlFor = source => builder.image(source);
|
|
67
95
|
|
|
68
96
|
if (hotspotImage !== null && hotspotImage !== void 0 && (_hotspotImage$asset = hotspotImage.asset) !== null && _hotspotImage$asset !== void 0 && _hotspotImage$asset._ref) {
|
|
69
97
|
var _getImageDimensions = (0, _assetUtils.getImageDimensions)(hotspotImage.asset._ref),
|
|
70
98
|
aspectRatio = _getImageDimensions.aspectRatio;
|
|
71
99
|
|
|
72
|
-
var width =
|
|
100
|
+
var width = 1200;
|
|
73
101
|
var height = Math.round(width / aspectRatio);
|
|
74
102
|
var url = urlFor(hotspotImage).width(width).url();
|
|
75
103
|
return {
|
|
@@ -80,7 +108,7 @@ var HotspotArray = /*#__PURE__*/_react.default.forwardRef((props, ref) => {
|
|
|
80
108
|
}
|
|
81
109
|
|
|
82
110
|
return null;
|
|
83
|
-
}, [
|
|
111
|
+
}, [hotspotImage]);
|
|
84
112
|
|
|
85
113
|
var handleHotspotImageClick = _react.default.useCallback(event => {
|
|
86
114
|
var nativeEvent = event.nativeEvent; // Calculate the x/y percentage of the click position
|
|
@@ -103,14 +131,6 @@ var HotspotArray = /*#__PURE__*/_react.default.forwardRef((props, ref) => {
|
|
|
103
131
|
}, []);
|
|
104
132
|
|
|
105
133
|
var handleHotspotMove = _react.default.useCallback((key, x, y) => {
|
|
106
|
-
if (!Number(x) || !Number(y)) {
|
|
107
|
-
console.warn("Missing or non-number X or Y", {
|
|
108
|
-
x,
|
|
109
|
-
y
|
|
110
|
-
});
|
|
111
|
-
return;
|
|
112
|
-
}
|
|
113
|
-
|
|
114
134
|
onChange(_PatchEvent.PatchEvent.from( // Set the `x` value of this array key item
|
|
115
135
|
(0, _PatchEvent.set)(x, [{
|
|
116
136
|
_key: key
|
|
@@ -122,16 +142,24 @@ var HotspotArray = /*#__PURE__*/_react.default.forwardRef((props, ref) => {
|
|
|
122
142
|
|
|
123
143
|
var hotspotImageRef = _react.default.useRef(null);
|
|
124
144
|
|
|
145
|
+
var _useState = (0, _react.useState)(),
|
|
146
|
+
_useState2 = _slicedToArray(_useState, 2),
|
|
147
|
+
imageRect = _useState2[0],
|
|
148
|
+
setImageRect = _useState2[1];
|
|
149
|
+
|
|
150
|
+
var updateImageRectCallback = (0, _web.useDebouncedCallback)(e => setImageRect(e.contentRect), [setImageRect], 200);
|
|
151
|
+
(0, _web.useResizeObserver)(hotspotImageRef, updateImageRectCallback);
|
|
125
152
|
return /*#__PURE__*/_react.default.createElement(_ui.Stack, {
|
|
126
153
|
space: [2, 2, 3]
|
|
127
154
|
}, displayImage !== null && displayImage !== void 0 && displayImage.url ? /*#__PURE__*/_react.default.createElement("div", {
|
|
128
155
|
style: {
|
|
129
156
|
position: "relative"
|
|
130
157
|
}
|
|
131
|
-
}, (value === null || value === void 0 ? void 0 : value.length) > 0 && value.map(spot => /*#__PURE__*/_react.default.createElement(_Spot.default, {
|
|
158
|
+
}, imageRect && (value === null || value === void 0 ? void 0 : value.length) > 0 && value.map((spot, index) => /*#__PURE__*/_react.default.createElement(_Spot.default, {
|
|
159
|
+
index: index,
|
|
132
160
|
key: spot._key,
|
|
133
161
|
spot: spot,
|
|
134
|
-
bounds:
|
|
162
|
+
bounds: imageRect,
|
|
135
163
|
update: handleHotspotMove,
|
|
136
164
|
hotspotDescriptionPath: options === null || options === void 0 ? void 0 : options.hotspotDescriptionPath,
|
|
137
165
|
tooltip: options === null || options === void 0 ? void 0 : options.hotspotTooltip
|
|
@@ -149,7 +177,7 @@ var HotspotArray = /*#__PURE__*/_react.default.forwardRef((props, ref) => {
|
|
|
149
177
|
alt: "",
|
|
150
178
|
style: imageStyle,
|
|
151
179
|
onClick: handleHotspotImageClick
|
|
152
|
-
})))) : /*#__PURE__*/_react.default.createElement(_Feedback.default, null, type !== null && type !== void 0 && (_type$options = type.options) !== null && _type$options !== void 0 && _type$options.hotspotImagePath ? /*#__PURE__*/_react.default.createElement(_react.default.Fragment, null, "No Hotspot image found at path ", /*#__PURE__*/_react.default.createElement("code", null, type === null || type === void 0 ? void 0 : (_type$options2 = type.options) === null || _type$options2 === void 0 ? void 0 : _type$options2.hotspotImagePath)) : /*#__PURE__*/_react.default.createElement(_react.default.Fragment, null, "Define a path in this field using to the image field in this document at
|
|
180
|
+
})))) : /*#__PURE__*/_react.default.createElement(_Feedback.default, null, type !== null && type !== void 0 && (_type$options = type.options) !== null && _type$options !== void 0 && _type$options.hotspotImagePath ? /*#__PURE__*/_react.default.createElement(_react.default.Fragment, null, "No Hotspot image found at path ", /*#__PURE__*/_react.default.createElement("code", null, type === null || type === void 0 ? void 0 : (_type$options2 = type.options) === null || _type$options2 === void 0 ? void 0 : _type$options2.hotspotImagePath)) : /*#__PURE__*/_react.default.createElement(_react.default.Fragment, null, "Define a path in this field using to the image field in this document at", ' ', /*#__PURE__*/_react.default.createElement("code", null, "options.hotspotImagePath"))), (type === null || type === void 0 ? void 0 : (_type$options3 = type.options) === null || _type$options3 === void 0 ? void 0 : _type$options3.imageHotspotPathRoot) && !VALID_ROOT_PATHS.includes(type.options.imageHotspotPathRoot) && /*#__PURE__*/_react.default.createElement(_Feedback.default, null, "The supplied imageHotspotPathRoot \"", type.options.imageHotspotPathRoot, "\" is not valid, falling back to \"document\". Available values are \"", VALID_ROOT_PATHS.join(', '), "\"."), /*#__PURE__*/_react.default.createElement(_FormBuilderInput.FormBuilderInput, _extends({}, props, {
|
|
153
181
|
type: typeWithoutInputComponent,
|
|
154
182
|
ref: ref
|
|
155
183
|
})));
|
package/lib/HotspotArray.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/HotspotArray.tsx"],"names":["builder","sanityClient","dataset","config","urlFor","source","image","imageStyle","width","height","HotspotArray","React","forwardRef","props","ref","type","value","onChange","sanityDocument","document","options","typeWithoutInputComponent","inputComponent","displayImage","useMemo","hotspotImage","hotspotImagePath","asset","_ref","aspectRatio","Math","round","url","handleHotspotImageClick","useCallback","event","nativeEvent","x","Number","offsetX","srcElement","toFixed","y","offsetY","description","newRow","_key","_type","hotspotDescriptionPath","PatchEvent","from","handleHotspotMove","key","console","warn","hotspotImageRef","useRef","position","length","map","spot","hotspotTooltip"],"mappings":";;;;;;;AAGA;;AAEA;;AAEA;;AACA;;AACA;;AACA;;AACA;;AACA;;AACA;;AACA;;AAEA;;AACA;;AACA;;;;;;AAEA,IAAMA,OAAO,GAAG,uBAAgBC,eAAhB,EAA8BC,OAA9B,CAAsCD,gBAAaE,MAAb,GAAsBD,OAA5D,CAAhB;;AACA,IAAME,MAAM,GAAIC,MAAD,IAAYL,OAAO,CAACM,KAAR,CAAcD,MAAd,CAA3B;;AACA,IAAME,UAAU,GAAG;AAACC,EAAAA,KAAK,QAAN;AAAgBC,EAAAA,MAAM;AAAtB,CAAnB;;AAGA,IAAMC,YAAY,gBAAGC,eAAMC,UAAN,CAAiB,CAACC,KAAD,EAAQC,GAAR,KAAgB;AAAA;;AACpD,MAAOC,IAAP,GAA0DF,KAA1D,CAAOE,IAAP;AAAA,MAAaC,KAAb,GAA0DH,KAA1D,CAAaG,KAAb;AAAA,MAAoBC,QAApB,GAA0DJ,KAA1D,CAAoBI,QAApB;AAAA,MAAwCC,cAAxC,GAA0DL,KAA1D,CAA8BM,QAA9B;;AACA,aAAkBJ,IAAlB,aAAkBA,IAAlB,cAAkBA,IAAlB,GAA0B,EAA1B;AAAA,MAAOK,OAAP,QAAOA,OAAP,CAFoD,CAIpD;AACA;;;AACA,MAAMC,yBAAyB,GAAG,oDAAuBN,IAAvB,EAA6BA,IAA7B,aAA6BA,IAA7B,uBAA6BA,IAAI,CAAEO,cAAnC,CAAlC,CANoD,CAQpD;AACA;;AACA,MAAMC,YAAY,GAAGZ,eAAMa,OAAN,CAAc,MAAM;AAAA;;AACvC,QAAMC,YAAY,GAAG,kBAAIP,cAAJ,EAAoBE,OAApB,aAAoBA,OAApB,uBAAoBA,OAAO,CAAEM,gBAA7B,CAArB;;AAEA,QAAID,YAAJ,aAAIA,YAAJ,sCAAIA,YAAY,CAAEE,KAAlB,gDAAI,oBAAqBC,IAAzB,EAA+B;AAC7B,gCAAsB,oCAAmBH,YAAY,CAACE,KAAb,CAAmBC,IAAtC,CAAtB;AAAA,UAAOC,WAAP,uBAAOA,WAAP;;AACA,UAAMrB,KAAK,GAAG,GAAd;AACA,UAAMC,MAAM,GAAGqB,IAAI,CAACC,KAAL,CAAWvB,KAAK,GAAGqB,WAAnB,CAAf;AACA,UAAMG,GAAG,GAAG5B,MAAM,CAACqB,YAAD,CAAN,CAAqBjB,KAArB,CAA2BA,KAA3B,EAAkCwB,GAAlC,EAAZ;AAEA,aAAO;AAACxB,QAAAA,KAAD;AAAQC,QAAAA,MAAR;AAAgBuB,QAAAA;AAAhB,OAAP;AACD;;AAED,WAAO,IAAP;AACD,GAboB,EAalB,CAACd,cAAD,EAAiBH,IAAjB,CAbkB,CAArB;;AAeA,MAAMkB,uBAAuB,GAAGtB,eAAMuB,WAAN,CAAmBC,KAAD,IAAW;AAC3D,QAAOC,WAAP,GAAsBD,KAAtB,CAAOC,WAAP,CAD2D,CAG3D;;AACA,QAAMC,CAAC,GAAGC,MAAM,CAAC,CAAEF,WAAW,CAACG,OAAZ,GAAsB,GAAvB,GAA8BH,WAAW,CAACI,UAAZ,CAAuBhC,KAAtD,EAA6DiC,OAA7D,CAAqE,CAArE,CAAD,CAAhB;AACA,QAAMC,CAAC,GAAGJ,MAAM,CAAC,CAAEF,WAAW,CAACO,OAAZ,GAAsB,GAAvB,GAA8BP,WAAW,CAACI,UAAZ,CAAuB/B,MAAtD,EAA8DgC,OAA9D,CAAsE,CAAtE,CAAD,CAAhB;AACA,QAAMG,WAAW,4BAAqBP,CAArB,iBAA6BK,CAA7B,MAAjB;AAEA,QAAMG,MAAM,GAAG;AACbC,MAAAA,IAAI,EAAE,wBAAU,EAAV,CADO;AAEbC,MAAAA,KAAK,QAFQ;AAGbV,MAAAA,CAHa;AAIbK,MAAAA;AAJa,KAAf;;AAOA,QAAItB,OAAJ,aAAIA,OAAJ,eAAIA,OAAO,CAAE4B,sBAAb,EAAqC;AACnCH,MAAAA,MAAM,CAACzB,OAAO,CAAC4B,sBAAT,CAAN,GAAyCJ,WAAzC;AACD;;AAED3B,IAAAA,QAAQ,CAACgC,uBAAWC,IAAX,CAAgB,8BAAa,EAAb,CAAhB,EAAkC,wBAAO,CAACL,MAAD,CAAP,EAAiB,OAAjB,EAA0B,CAAC,CAAC,CAAF,CAA1B,CAAlC,CAAD,CAAR;AACD,GApB+B,EAoB7B,EApB6B,CAAhC;;AAsBA,MAAMM,iBAAiB,GAAGxC,eAAMuB,WAAN,CACxB,CAACkB,GAAD,EAAMf,CAAN,EAASK,CAAT,KAAe;AACb,QAAI,CAACJ,MAAM,CAACD,CAAD,CAAP,IAAc,CAACC,MAAM,CAACI,CAAD,CAAzB,EAA8B;AAC5BW,MAAAA,OAAO,CAACC,IAAR,iCAA6C;AAACjB,QAAAA,CAAD;AAAIK,QAAAA;AAAJ,OAA7C;AACA;AACD;;AAEDzB,IAAAA,QAAQ,CACNgC,uBAAWC,IAAX,EACE;AACA,yBAAIb,CAAJ,EAAO,CAAC;AAACS,MAAAA,IAAI,EAAEM;AAAP,KAAD,EAAc,GAAd,CAAP,CAFF,EAGE;AACA,yBAAIV,CAAJ,EAAO,CAAC;AAACI,MAAAA,IAAI,EAAEM;AAAP,KAAD,EAAc,GAAd,CAAP,CAJF,CADM,CAAR;AAQD,GAfuB,EAgBxB,CAACpC,KAAD,CAhBwB,CAA1B;;AAmBA,MAAMuC,eAAe,GAAG5C,eAAM6C,MAAN,CAAa,IAAb,CAAxB;;AAEA,sBACE,6BAAC,SAAD;AAAO,IAAA,KAAK,EAAE,CAAC,CAAD,EAAG,CAAH,EAAK,CAAL;AAAd,KACGjC,YAAY,SAAZ,IAAAA,YAAY,WAAZ,IAAAA,YAAY,CAAES,GAAd,gBACC;AAAK,IAAA,KAAK,EAAE;AAACyB,MAAAA,QAAQ;AAAT;AAAZ,KACG,CAAAzC,KAAK,SAAL,IAAAA,KAAK,WAAL,YAAAA,KAAK,CAAE0C,MAAP,IAAgB,CAAhB,IACC1C,KAAK,CAAC2C,GAAN,CAAWC,IAAD,iBACR,6BAAC,aAAD;AACE,IAAA,GAAG,EAAEA,IAAI,CAACd,IADZ;AAEE,IAAA,IAAI,EAAEc,IAFR;AAGE,IAAA,MAAM,EAAEL,eAHV;AAIE,IAAA,MAAM,EAAEJ,iBAJV;AAKE,IAAA,sBAAsB,EAAE/B,OAAF,aAAEA,OAAF,uBAAEA,OAAO,CAAE4B,sBALnC;AAME,IAAA,OAAO,EAAE5B,OAAF,aAAEA,OAAF,uBAAEA,OAAO,CAAEyC;AANpB,IADF,CAFJ,eAaE,6BAAC,QAAD;AAAM,IAAA,oBAAoB,MAA1B;AAA2B,IAAA,MAAM,EAAE;AAAnC,kBACE,6BAAC,QAAD;AAAM,IAAA,KAAK,EAAC,QAAZ;AAAqB,IAAA,OAAO,EAAC;AAA7B,kBACE;AACE,IAAA,GAAG,EAAEN,eADP;AAEE,IAAA,GAAG,EAAEhC,YAAY,CAACS,GAFpB;AAGE,IAAA,KAAK,EAAET,YAAY,CAACf,KAHtB;AAIE,IAAA,MAAM,EAAEe,YAAY,CAACd,MAJvB;AAKE,IAAA,GAAG,EAAC,EALN;AAME,IAAA,KAAK,EAAEF,UANT;AAOE,IAAA,OAAO,EAAE0B;AAPX,IADF,CADF,CAbF,CADD,gBA6BC,6BAAC,iBAAD,QACKlB,IAAI,SAAJ,IAAAA,IAAI,WAAJ,qBAAAA,IAAI,CAAEK,OAAN,wDAAeM,gBAAf,gBACG,4GAAiC,2CAAOX,IAAP,aAAOA,IAAP,yCAAOA,IAAI,CAAEK,OAAb,mDAAO,eAAeM,gBAAtB,CAAjC,CADH,gBAEG,sJAA2E,sEAA3E,CAHR,CA9BJ,eAqCE,6BAAC,kCAAD,eAAsBb,KAAtB;AAA6B,IAAA,IAAI,EAAEQ,yBAAnC;AAA8D,IAAA,GAAG,EAAEP;AAAnE,KArCF,CADF;AAyCD,CA7GoB,CAArB;;eA+Ge,+BAAaJ,YAAb,C","sourcesContent":["/* eslint-disable react/display-name */\n\n// @ts-ignore\nimport sanityClient from 'part:@sanity/base/client'\n// @ts-ignore\nimport {withDocument} from 'part:@sanity/form-builder'\n\nimport React from 'react'\nimport {Card, Flex, Stack, Text} from '@sanity/ui'\nimport {FormBuilderInput} from '@sanity/form-builder/lib/FormBuilderInput'\nimport {randomKey} from '@sanity/util/content'\nimport {PatchEvent, setIfMissing, set, insert} from '@sanity/form-builder/PatchEvent'\nimport imageUrlBuilder from '@sanity/image-url'\nimport {getImageDimensions} from '@sanity/asset-utils'\nimport get from 'lodash/get'\n\nimport Spot from './Spot'\nimport { useUnsetInputComponent } from './useUnsetInputComponent'\nimport Feedback from './Feedback'\n\nconst builder = imageUrlBuilder(sanityClient).dataset(sanityClient.config().dataset)\nconst urlFor = (source) => builder.image(source)\nconst imageStyle = {width: `100%`, height: `auto`}\n\n\nconst HotspotArray = React.forwardRef((props, ref) => {\n const {type, value, onChange, document: sanityDocument} = props\n const {options} = type ?? {}\n\n // Attempt prevention of infinite loop in <FormBuilderInput />\n // Re-renders can still occur if this Component is used again in a nested field\n const typeWithoutInputComponent = useUnsetInputComponent(type, type?.inputComponent)\n\n // Finding the image from the document,\n // using the path from the hotspot's `options` field\n const displayImage = React.useMemo(() => {\n const hotspotImage = get(sanityDocument, options?.hotspotImagePath)\n\n if (hotspotImage?.asset?._ref) {\n const {aspectRatio} = getImageDimensions(hotspotImage.asset._ref)\n const width = 600\n const height = Math.round(width / aspectRatio)\n const url = urlFor(hotspotImage).width(width).url()\n\n return {width, height, url}\n }\n\n return null\n }, [sanityDocument, type])\n\n const handleHotspotImageClick = React.useCallback((event) => {\n const {nativeEvent} = event\n\n // Calculate the x/y percentage of the click position\n const x = Number(((nativeEvent.offsetX * 100) / nativeEvent.srcElement.width).toFixed(2))\n const y = Number(((nativeEvent.offsetY * 100) / nativeEvent.srcElement.height).toFixed(2))\n const description = `New Hotspot at ${x}% x ${y}%`\n\n const newRow = {\n _key: randomKey(12),\n _type: `spot`,\n x,\n y,\n }\n\n if (options?.hotspotDescriptionPath) {\n newRow[options.hotspotDescriptionPath] = description\n }\n\n onChange(PatchEvent.from(setIfMissing([]), insert([newRow], 'after', [-1])))\n }, [])\n\n const handleHotspotMove = React.useCallback(\n (key, x, y) => {\n if (!Number(x) || !Number(y)) {\n console.warn(`Missing or non-number X or Y`, {x, y})\n return\n }\n\n onChange(\n PatchEvent.from(\n // Set the `x` value of this array key item\n set(x, [{_key: key}, 'x']),\n // Set the `y` value of this array key item\n set(y, [{_key: key}, 'y'])\n )\n )\n },\n [value]\n )\n\n const hotspotImageRef = React.useRef(null)\n\n return (\n <Stack space={[2,2,3]}>\n {displayImage?.url ? (\n <div style={{position: `relative`}}>\n {value?.length > 0 &&\n value.map((spot) => (\n <Spot\n key={spot._key}\n spot={spot}\n bounds={hotspotImageRef}\n update={handleHotspotMove}\n hotspotDescriptionPath={options?.hotspotDescriptionPath}\n tooltip={options?.hotspotTooltip}\n />\n ))}\n\n <Card __unstable_checkered shadow={1}>\n <Flex align=\"center\" justify=\"center\">\n <img\n ref={hotspotImageRef}\n src={displayImage.url}\n width={displayImage.width}\n height={displayImage.height}\n alt=\"\"\n style={imageStyle}\n onClick={handleHotspotImageClick}\n />\n </Flex>\n </Card>\n </div>\n ) : (\n <Feedback>\n {type?.options?.hotspotImagePath \n ? <>No Hotspot image found at path <code>{type?.options?.hotspotImagePath}</code></> \n : <>Define a path in this field using to the image field in this document at <code>options.hotspotImagePath</code></>\n }\n </Feedback>\n )}\n <FormBuilderInput {...props} type={typeWithoutInputComponent} ref={ref} />\n </Stack>\n )\n})\n\nexport default withDocument(HotspotArray)\n"],"file":"HotspotArray.js"}
|
|
1
|
+
{"version":3,"file":"HotspotArray.js","names":["imageStyle","width","height","VALID_ROOT_PATHS","HotspotArray","React","forwardRef","props","ref","type","value","onChange","document","options","typeWithoutInputComponent","useUnsetInputComponent","inputComponent","imageHotspotPathRoot","includes","hotspotImage","useMemo","get","hotspotImagePath","displayImage","builder","imageUrlBuilder","sanityClient","dataset","config","urlFor","source","image","asset","_ref","getImageDimensions","aspectRatio","Math","round","url","handleHotspotImageClick","useCallback","event","nativeEvent","x","Number","offsetX","srcElement","toFixed","y","offsetY","description","newRow","_key","randomKey","_type","hotspotDescriptionPath","PatchEvent","from","setIfMissing","insert","handleHotspotMove","key","set","hotspotImageRef","useRef","useState","imageRect","setImageRect","updateImageRectCallback","useDebouncedCallback","e","contentRect","useResizeObserver","position","length","map","spot","index","hotspotTooltip","join","withDocument"],"sources":["../src/HotspotArray.tsx"],"sourcesContent":["/* eslint-disable react/display-name */\n\n// @ts-ignore\nimport sanityClient from 'part:@sanity/base/client'\n// @ts-ignore\nimport {withDocument} from 'part:@sanity/form-builder'\n\nimport {getImageDimensions} from '@sanity/asset-utils'\nimport {FormBuilderInput} from '@sanity/form-builder/lib/FormBuilderInput'\nimport {insert, PatchEvent, set, setIfMissing} from '@sanity/form-builder/PatchEvent'\nimport imageUrlBuilder from '@sanity/image-url'\nimport {Card, Flex, Stack} from '@sanity/ui'\nimport {randomKey} from '@sanity/util/content'\nimport get from 'lodash/get'\nimport React, {useState} from 'react'\n\nimport {IUseResizeObserverCallback, useDebouncedCallback, useResizeObserver} from '@react-hookz/web'\nimport Feedback from './Feedback'\nimport Spot from './Spot'\nimport {useUnsetInputComponent} from './useUnsetInputComponent'\n\nconst imageStyle = {width: `100%`, height: `auto`}\n\nconst VALID_ROOT_PATHS = ['document', 'parent']\n\nexport type FnHotspotMove = (key: string, x: number, y: number) => void\n\nexport type TSpot = {\n _key: string\n _type: `spot`\n x: number\n y: number\n} & {[key: string]: unknown}\n\nconst HotspotArray = React.forwardRef((props: any, ref) => {\n const {type, value, onChange, document} = props\n const {options} = type ?? {}\n\n // Attempt prevention of infinite loop in <FormBuilderInput />\n // Re-renders can still occur if this Component is used again in a nested field\n const typeWithoutInputComponent = useUnsetInputComponent(type, type?.inputComponent)\n const imageHotspotPathRoot = VALID_ROOT_PATHS.includes(options?.imageHotspotPathRoot)\n ? props[options.imageHotspotPathRoot]\n : document\n\n /**\n * Finding the image from the imageHotspotPathRoot (defaults to document),\n * using the path from the hotspot's `options` field\n *\n * when changes in imageHotspotPathRoot (e.g. document) occur,\n * check if there are any changes to the hotspotImage and update the reference\n */\n const hotspotImage = React.useMemo(() => {\n return get(imageHotspotPathRoot, options?.hotspotImagePath)\n }, [imageHotspotPathRoot])\n\n const displayImage = React.useMemo(() => {\n const builder = imageUrlBuilder(sanityClient).dataset(sanityClient.config().dataset)\n const urlFor = (source) => builder.image(source)\n\n if (hotspotImage?.asset?._ref) {\n const {aspectRatio} = getImageDimensions(hotspotImage.asset._ref)\n const width = 1200\n const height = Math.round(width / aspectRatio)\n const url = urlFor(hotspotImage).width(width).url()\n\n return {width, height, url}\n }\n\n return null\n }, [hotspotImage])\n\n const handleHotspotImageClick = React.useCallback((event) => {\n const {nativeEvent} = event\n\n // Calculate the x/y percentage of the click position\n const x = Number(((nativeEvent.offsetX * 100) / nativeEvent.srcElement.width).toFixed(2))\n const y = Number(((nativeEvent.offsetY * 100) / nativeEvent.srcElement.height).toFixed(2))\n const description = `New Hotspot at ${x}% x ${y}%`\n\n const newRow: TSpot = {\n _key: randomKey(12),\n _type: `spot`,\n x,\n y,\n }\n\n if (options?.hotspotDescriptionPath) {\n newRow[options.hotspotDescriptionPath] = description\n }\n\n onChange(PatchEvent.from(setIfMissing([]), insert([newRow], 'after', [-1])))\n }, [])\n\n const handleHotspotMove: FnHotspotMove = React.useCallback(\n (key, x, y) => {\n onChange(\n PatchEvent.from(\n // Set the `x` value of this array key item\n set(x, [{_key: key}, 'x']),\n // Set the `y` value of this array key item\n set(y, [{_key: key}, 'y'])\n )\n )\n },\n [value]\n )\n\n const hotspotImageRef = React.useRef<HTMLImageElement | null>(null)\n\n const [imageRect, setImageRect] = useState<DOMRectReadOnly>()\n const updateImageRectCallback = useDebouncedCallback(\n ((e) => setImageRect(e.contentRect)) as IUseResizeObserverCallback,\n [setImageRect],\n 200\n )\n useResizeObserver(hotspotImageRef, updateImageRectCallback)\n\n return (\n <Stack space={[2, 2, 3]}>\n {displayImage?.url ? (\n <div style={{position: `relative`}}>\n {imageRect &&\n value?.length > 0 &&\n value.map((spot, index) => (\n <Spot\n index={index}\n key={spot._key}\n spot={spot}\n bounds={imageRect}\n update={handleHotspotMove}\n hotspotDescriptionPath={options?.hotspotDescriptionPath}\n tooltip={options?.hotspotTooltip}\n />\n ))}\n\n <Card __unstable_checkered shadow={1}>\n <Flex align=\"center\" justify=\"center\">\n <img\n ref={hotspotImageRef}\n src={displayImage.url}\n width={displayImage.width}\n height={displayImage.height}\n alt=\"\"\n style={imageStyle}\n onClick={handleHotspotImageClick}\n />\n </Flex>\n </Card>\n </div>\n ) : (\n <Feedback>\n {type?.options?.hotspotImagePath ? (\n <>\n No Hotspot image found at path <code>{type?.options?.hotspotImagePath}</code>\n </>\n ) : (\n <>\n Define a path in this field using to the image field in this document at{' '}\n <code>options.hotspotImagePath</code>\n </>\n )}\n </Feedback>\n )}\n {type?.options?.imageHotspotPathRoot &&\n !VALID_ROOT_PATHS.includes(type.options.imageHotspotPathRoot) && (\n <Feedback>\n The supplied imageHotspotPathRoot \"{type.options.imageHotspotPathRoot}\" is not valid,\n falling back to \"document\". Available values are \"{VALID_ROOT_PATHS.join(', ')}\".\n </Feedback>\n )}\n <FormBuilderInput {...props} type={typeWithoutInputComponent} ref={ref} />\n </Stack>\n )\n})\n\nexport default withDocument(HotspotArray)\n"],"mappings":"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"}
|
package/lib/Spot.js
CHANGED
|
@@ -5,16 +5,26 @@ Object.defineProperty(exports, "__esModule", {
|
|
|
5
5
|
});
|
|
6
6
|
exports.default = Spot;
|
|
7
7
|
|
|
8
|
-
var _react = _interopRequireDefault(require("react"));
|
|
9
|
-
|
|
10
8
|
var _ui = require("@sanity/ui");
|
|
11
9
|
|
|
12
10
|
var _framerMotion = require("framer-motion");
|
|
13
11
|
|
|
14
12
|
var _get = _interopRequireDefault(require("lodash/get"));
|
|
15
13
|
|
|
14
|
+
var _react = _interopRequireWildcard(require("react"));
|
|
15
|
+
|
|
16
|
+
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); }
|
|
17
|
+
|
|
18
|
+
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; }
|
|
19
|
+
|
|
16
20
|
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
|
|
17
21
|
|
|
22
|
+
function ownKeys(object, enumerableOnly) { var keys = Object.keys(object); if (Object.getOwnPropertySymbols) { var symbols = Object.getOwnPropertySymbols(object); enumerableOnly && (symbols = symbols.filter(function (sym) { return Object.getOwnPropertyDescriptor(object, sym).enumerable; })), keys.push.apply(keys, symbols); } return keys; }
|
|
23
|
+
|
|
24
|
+
function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { var source = null != arguments[i] ? arguments[i] : {}; i % 2 ? ownKeys(Object(source), !0).forEach(function (key) { _defineProperty(target, key, source[key]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)) : ownKeys(Object(source)).forEach(function (key) { Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key)); }); } return target; }
|
|
25
|
+
|
|
26
|
+
function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }
|
|
27
|
+
|
|
18
28
|
function _slicedToArray(arr, i) { return _arrayWithHoles(arr) || _iterableToArrayLimit(arr, i) || _unsupportedIterableToArray(arr, i) || _nonIterableRest(); }
|
|
19
29
|
|
|
20
30
|
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."); }
|
|
@@ -27,110 +37,108 @@ function _iterableToArrayLimit(arr, i) { var _i = arr == null ? null : typeof Sy
|
|
|
27
37
|
|
|
28
38
|
function _arrayWithHoles(arr) { if (Array.isArray(arr)) return arr; }
|
|
29
39
|
|
|
30
|
-
function ownKeys(object, enumerableOnly) { var keys = Object.keys(object); if (Object.getOwnPropertySymbols) { var symbols = Object.getOwnPropertySymbols(object); enumerableOnly && (symbols = symbols.filter(function (sym) { return Object.getOwnPropertyDescriptor(object, sym).enumerable; })), keys.push.apply(keys, symbols); } return keys; }
|
|
31
|
-
|
|
32
|
-
function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { var source = null != arguments[i] ? arguments[i] : {}; i % 2 ? ownKeys(Object(source), !0).forEach(function (key) { _defineProperty(target, key, source[key]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)) : ownKeys(Object(source)).forEach(function (key) { Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key)); }); } return target; }
|
|
33
|
-
|
|
34
|
-
function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }
|
|
35
|
-
|
|
36
40
|
var dragStyle = {
|
|
37
|
-
width:
|
|
38
|
-
height:
|
|
39
|
-
position:
|
|
41
|
+
width: '1.4rem',
|
|
42
|
+
height: '1.4rem',
|
|
43
|
+
position: 'absolute',
|
|
44
|
+
top: 0,
|
|
45
|
+
left: 0,
|
|
46
|
+
margin: '-0.7rem 0 0 -0.7rem',
|
|
47
|
+
cursor: 'pointer',
|
|
48
|
+
display: 'flex',
|
|
49
|
+
justifyContent: 'center',
|
|
50
|
+
alignItems: 'center',
|
|
51
|
+
borderRadius: '50%',
|
|
52
|
+
background: '#000',
|
|
53
|
+
color: 'white'
|
|
40
54
|
};
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
55
|
+
var dragStyleWhileDrag = {
|
|
56
|
+
background: 'rgba(0, 0, 0, 0.1)',
|
|
57
|
+
border: '1px solid #fff',
|
|
58
|
+
cursor: 'none'
|
|
59
|
+
};
|
|
60
|
+
var dragStyleWhileHover = {
|
|
61
|
+
background: 'rgba(0, 0, 0, 0.1)',
|
|
62
|
+
border: '1px solid #fff'
|
|
63
|
+
};
|
|
64
|
+
var dotStyle = {
|
|
65
|
+
position: 'absolute',
|
|
66
|
+
left: '50%',
|
|
67
|
+
top: '50%',
|
|
68
|
+
height: '0.2rem',
|
|
69
|
+
width: '0.2rem',
|
|
70
|
+
margin: '-0.1rem 0 0 -0.1rem',
|
|
71
|
+
background: '#fff',
|
|
72
|
+
visibility: 'hidden',
|
|
73
|
+
borderRadius: '50%',
|
|
44
74
|
// make sure pointer events only run on the parent
|
|
45
|
-
pointerEvents:
|
|
46
|
-
}
|
|
75
|
+
pointerEvents: 'none'
|
|
76
|
+
};
|
|
77
|
+
var dotStyleWhileActive = {
|
|
78
|
+
visibility: 'visible'
|
|
79
|
+
};
|
|
80
|
+
var labelStyle = {
|
|
81
|
+
color: 'white',
|
|
82
|
+
fontSize: '0.7rem',
|
|
83
|
+
fontWeight: 600,
|
|
84
|
+
lineHeight: '1'
|
|
85
|
+
};
|
|
86
|
+
var labelStyleWhileActive = {
|
|
87
|
+
visibility: 'hidden'
|
|
88
|
+
};
|
|
47
89
|
|
|
48
90
|
var round = num => Math.round(num * 100) / 100;
|
|
49
91
|
|
|
50
92
|
function Spot(_ref) {
|
|
51
93
|
var spot = _ref.spot,
|
|
52
|
-
|
|
53
|
-
bounds = _ref$bounds === void 0 ? undefined : _ref$bounds,
|
|
94
|
+
bounds = _ref.bounds,
|
|
54
95
|
update = _ref.update,
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
// x/y are stored as % but need to be displayed as px
|
|
60
|
-
var _React$useState = _react.default.useState({
|
|
61
|
-
x: 0,
|
|
62
|
-
y: 0
|
|
63
|
-
}),
|
|
64
|
-
_React$useState2 = _slicedToArray(_React$useState, 2),
|
|
65
|
-
_React$useState2$ = _React$useState2[0],
|
|
66
|
-
x = _React$useState2$.x,
|
|
67
|
-
y = _React$useState2$.y,
|
|
68
|
-
setXY = _React$useState2[1];
|
|
69
|
-
|
|
70
|
-
var _React$useState3 = _react.default.useState(bounds !== null && bounds !== void 0 && bounds.current ? bounds.current.getBoundingClientRect() : {
|
|
71
|
-
width: 0,
|
|
72
|
-
height: 0
|
|
73
|
-
}),
|
|
74
|
-
_React$useState4 = _slicedToArray(_React$useState3, 2),
|
|
75
|
-
rect = _React$useState4[0],
|
|
76
|
-
setRect = _React$useState4[1];
|
|
77
|
-
|
|
78
|
-
var _React$useState5 = _react.default.useState(false),
|
|
79
|
-
_React$useState6 = _slicedToArray(_React$useState5, 2),
|
|
80
|
-
isDragging = _React$useState6[0],
|
|
81
|
-
setIsDragging = _React$useState6[1];
|
|
82
|
-
|
|
83
|
-
_react.default.useEffect(() => {
|
|
84
|
-
var _bounds$current;
|
|
96
|
+
hotspotDescriptionPath = _ref.hotspotDescriptionPath,
|
|
97
|
+
tooltip = _ref.tooltip,
|
|
98
|
+
index = _ref.index;
|
|
85
99
|
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
setXY({
|
|
91
|
-
x: round(clientRect.width * (spot.x / 100)),
|
|
92
|
-
y: round(clientRect.height * (spot.y / 100))
|
|
93
|
-
});
|
|
100
|
+
var _React$useState = _react.default.useState(false),
|
|
101
|
+
_React$useState2 = _slicedToArray(_React$useState, 2),
|
|
102
|
+
isDragging = _React$useState2[0],
|
|
103
|
+
setIsDragging = _React$useState2[1];
|
|
94
104
|
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
}, [bounds, bounds.current]);
|
|
105
|
+
var _React$useState3 = _react.default.useState(false),
|
|
106
|
+
_React$useState4 = _slicedToArray(_React$useState3, 2),
|
|
107
|
+
isHovering = _React$useState4[0],
|
|
108
|
+
setIsHovering = _React$useState4[1]; // x/y are stored as % but need to be converted to px
|
|
100
109
|
|
|
101
|
-
var handleDragEnd = _react.default.useCallback(event => {
|
|
102
|
-
setIsDragging(false); // I don't know why, but framer-motion doesn't give you the actual transform values
|
|
103
|
-
// So we have to regex the `px` values off the inline styles
|
|
104
110
|
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
currentY = _event$srcElement$sty2[1];
|
|
111
|
+
var x = (0, _framerMotion.useMotionValue)(round(bounds.width * (spot.x / 100)));
|
|
112
|
+
var y = (0, _framerMotion.useMotionValue)(round(bounds.height * (spot.y / 100)));
|
|
113
|
+
/**
|
|
114
|
+
* update x/y if the bounds change when resizing the window
|
|
115
|
+
*/
|
|
111
116
|
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
}, event.srcElement);
|
|
117
|
-
}
|
|
117
|
+
(0, _react.useEffect)(() => {
|
|
118
|
+
x.set(round(bounds.width * (spot.x / 100)));
|
|
119
|
+
y.set(round(bounds.height * (spot.y / 100)));
|
|
120
|
+
}, [bounds]);
|
|
118
121
|
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
rect
|
|
122
|
-
}, bounds === null || bounds === void 0 ? void 0 : bounds.current);
|
|
123
|
-
} // Which we need to convert back to `%` to patch the document
|
|
122
|
+
var handleDragEnd = _react.default.useCallback(() => {
|
|
123
|
+
setIsDragging(false); // get current values for x/y in px
|
|
124
124
|
|
|
125
|
+
var currentX = x.get();
|
|
126
|
+
var currentY = y.get(); // Which we need to convert back to `%` to patch the document
|
|
125
127
|
|
|
126
|
-
var newX = round(currentX * 100 /
|
|
127
|
-
var newY = round(currentY * 100 /
|
|
128
|
+
var newX = round(currentX * 100 / bounds.width);
|
|
129
|
+
var newY = round(currentY * 100 / bounds.height); // Don't go below 0 or above 100
|
|
128
130
|
|
|
129
131
|
var safeX = Math.max(0, Math.min(100, newX));
|
|
130
132
|
var safeY = Math.max(0, Math.min(100, newY));
|
|
131
133
|
update(spot._key, safeX, safeY);
|
|
132
134
|
}, [spot]);
|
|
133
135
|
|
|
136
|
+
var handleDragStart = _react.default.useCallback(() => setIsDragging(true), []);
|
|
137
|
+
|
|
138
|
+
var handleHoverStart = _react.default.useCallback(() => setIsHovering(true), []);
|
|
139
|
+
|
|
140
|
+
var handleHoverEnd = _react.default.useCallback(() => setIsHovering(false), []);
|
|
141
|
+
|
|
134
142
|
if (!x || !y) {
|
|
135
143
|
return null;
|
|
136
144
|
}
|
|
@@ -138,7 +146,6 @@ function Spot(_ref) {
|
|
|
138
146
|
return /*#__PURE__*/_react.default.createElement(_ui.Tooltip, {
|
|
139
147
|
key: spot._key,
|
|
140
148
|
disabled: isDragging,
|
|
141
|
-
boundaryElement: bounds.current,
|
|
142
149
|
portal: true,
|
|
143
150
|
content: tooltip && typeof tooltip === 'function' ? /*#__PURE__*/_react.default.createElement(tooltip, {
|
|
144
151
|
spot
|
|
@@ -154,18 +161,20 @@ function Spot(_ref) {
|
|
|
154
161
|
}, /*#__PURE__*/_react.default.createElement(_framerMotion.motion.div, {
|
|
155
162
|
drag: true,
|
|
156
163
|
dragConstraints: bounds,
|
|
164
|
+
dragElastic: 0,
|
|
157
165
|
dragMomentum: false,
|
|
158
|
-
|
|
166
|
+
onDragEnd: handleDragEnd,
|
|
167
|
+
onDragStart: handleDragStart,
|
|
168
|
+
onHoverStart: handleHoverStart,
|
|
169
|
+
onHoverEnd: handleHoverEnd,
|
|
170
|
+
style: _objectSpread(_objectSpread(_objectSpread({}, dragStyle), {}, {
|
|
159
171
|
x,
|
|
160
172
|
y
|
|
161
|
-
},
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
shadow: 3,
|
|
168
|
-
style: dotStyle
|
|
169
|
-
})));
|
|
173
|
+
}, isDragging && _objectSpread({}, dragStyleWhileDrag)), isHovering && _objectSpread({}, dragStyleWhileHover))
|
|
174
|
+
}, /*#__PURE__*/_react.default.createElement(_ui.Box, {
|
|
175
|
+
style: _objectSpread(_objectSpread({}, dotStyle), (isDragging || isHovering) && _objectSpread({}, dotStyleWhileActive))
|
|
176
|
+
}), /*#__PURE__*/_react.default.createElement("div", {
|
|
177
|
+
style: _objectSpread(_objectSpread({}, labelStyle), (isDragging || isHovering) && _objectSpread({}, labelStyleWhileActive))
|
|
178
|
+
}, index + 1)));
|
|
170
179
|
}
|
|
171
180
|
//# sourceMappingURL=Spot.js.map
|
package/lib/Spot.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"
|
|
1
|
+
{"version":3,"file":"Spot.js","names":["dragStyle","width","height","position","top","left","margin","cursor","display","justifyContent","alignItems","borderRadius","background","color","dragStyleWhileDrag","border","dragStyleWhileHover","dotStyle","visibility","pointerEvents","dotStyleWhileActive","labelStyle","fontSize","fontWeight","lineHeight","labelStyleWhileActive","round","num","Math","Spot","spot","bounds","update","hotspotDescriptionPath","tooltip","index","React","useState","isDragging","setIsDragging","isHovering","setIsHovering","x","useMotionValue","y","useEffect","set","handleDragEnd","useCallback","currentX","get","currentY","newX","newY","safeX","max","min","safeY","_key","handleDragStart","handleHoverStart","handleHoverEnd","createElement","maxWidth"],"sources":["../src/Spot.tsx"],"sourcesContent":["import {Box, Text, Tooltip} from '@sanity/ui'\nimport {motion, useMotionValue} from 'framer-motion'\nimport get from 'lodash/get'\nimport React, {CSSProperties, ReactElement, useEffect} from 'react'\nimport {FnHotspotMove, TSpot} from './HotspotArray'\n\nconst dragStyle: CSSProperties = {\n width: '1.4rem',\n height: '1.4rem',\n position: 'absolute',\n top: 0,\n left: 0,\n margin: '-0.7rem 0 0 -0.7rem',\n cursor: 'pointer',\n display: 'flex',\n justifyContent: 'center',\n alignItems: 'center',\n borderRadius: '50%',\n background: '#000',\n color: 'white',\n}\n\nconst dragStyleWhileDrag: CSSProperties = {\n background: 'rgba(0, 0, 0, 0.1)',\n border: '1px solid #fff',\n cursor: 'none',\n}\n\nconst dragStyleWhileHover: CSSProperties = {\n background: 'rgba(0, 0, 0, 0.1)',\n border: '1px solid #fff',\n}\n\nconst dotStyle: CSSProperties = {\n position: 'absolute',\n left: '50%',\n top: '50%',\n height: '0.2rem',\n width: '0.2rem',\n margin: '-0.1rem 0 0 -0.1rem',\n background: '#fff',\n visibility: 'hidden',\n borderRadius: '50%',\n // make sure pointer events only run on the parent\n pointerEvents: 'none',\n}\n\nconst dotStyleWhileActive: CSSProperties = {\n visibility: 'visible',\n}\n\nconst labelStyle: CSSProperties = {\n color: 'white',\n fontSize: '0.7rem',\n fontWeight: 600,\n lineHeight: '1',\n}\n\nconst labelStyleWhileActive: CSSProperties = {\n visibility: 'hidden',\n}\n\nconst round = (num) => Math.round(num * 100) / 100\n\ninterface IHotspot {\n spot: TSpot\n bounds: DOMRectReadOnly\n update: FnHotspotMove\n hotspotDescriptionPath?: string\n tooltip?: ReactElement\n index: number\n}\n\nexport default function Spot({\n spot,\n bounds,\n update,\n hotspotDescriptionPath,\n tooltip,\n index,\n}: IHotspot) {\n const [isDragging, setIsDragging] = React.useState(false)\n const [isHovering, setIsHovering] = React.useState(false)\n\n // x/y are stored as % but need to be converted to px\n const x = useMotionValue(round(bounds.width * (spot.x / 100)))\n const y = useMotionValue(round(bounds.height * (spot.y / 100)))\n\n /**\n * update x/y if the bounds change when resizing the window\n */\n useEffect(() => {\n x.set(round(bounds.width * (spot.x / 100)))\n y.set(round(bounds.height * (spot.y / 100)))\n }, [bounds])\n\n const handleDragEnd = React.useCallback(() => {\n setIsDragging(false)\n\n // get current values for x/y in px\n const currentX = x.get()\n const currentY = y.get()\n\n // Which we need to convert back to `%` to patch the document\n const newX = round((currentX * 100) / bounds.width)\n const newY = round((currentY * 100) / bounds.height)\n\n // Don't go below 0 or above 100\n const safeX = Math.max(0, Math.min(100, newX))\n const safeY = Math.max(0, Math.min(100, newY))\n\n update(spot._key, safeX, safeY)\n }, [spot])\n const handleDragStart = React.useCallback(() => setIsDragging(true), [])\n\n const handleHoverStart = React.useCallback(() => setIsHovering(true), [])\n const handleHoverEnd = React.useCallback(() => setIsHovering(false), [])\n\n if (!x || !y) {\n return null\n }\n\n return (\n <Tooltip\n key={spot._key}\n disabled={isDragging}\n portal\n content={\n tooltip && typeof tooltip === 'function' ? (\n React.createElement(tooltip, {spot})\n ) : (\n <Box padding={2} style={{maxWidth: 200, pointerEvents: `none`}}>\n <Text textOverflow=\"ellipsis\">\n {hotspotDescriptionPath\n ? (get(spot, hotspotDescriptionPath) as string)\n : `${spot.x}% x ${spot.y}%`}\n </Text>\n </Box>\n )\n }\n >\n <motion.div\n drag\n dragConstraints={bounds}\n dragElastic={0}\n dragMomentum={false}\n onDragEnd={handleDragEnd}\n onDragStart={handleDragStart}\n onHoverStart={handleHoverStart}\n onHoverEnd={handleHoverEnd}\n style={{\n ...dragStyle,\n x,\n y,\n ...(isDragging && {...dragStyleWhileDrag}),\n ...(isHovering && {...dragStyleWhileHover}),\n }}\n >\n {/* Dot */}\n <Box\n style={{\n ...dotStyle,\n ...((isDragging || isHovering) && {...dotStyleWhileActive}),\n }}\n />\n {/* Label */}\n <div\n style={{\n ...labelStyle,\n ...((isDragging || isHovering) && {...labelStyleWhileActive}),\n }}\n >\n {index + 1}\n </div>\n </motion.div>\n </Tooltip>\n )\n}\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"
|
|
1
|
+
{"version":3,"file":"useUnsetInputComponent.js","names":["useUnsetInputComponent","type","component","React","useMemo","unsetInputComponent","t","inputComponent","undefined","typeOfType"],"sources":["../src/useUnsetInputComponent.ts"],"sourcesContent":["import React from 'react'\n\nexport function useUnsetInputComponent(type, component) {\n return React.useMemo(() => unsetInputComponent(type, component), [type, component])\n}\n\nfunction unsetInputComponent(type, component) {\n const t = {\n ...type,\n inputComponent: type.inputComponent === component ? undefined : type.inputComponent,\n }\n const typeOfType = t.type ? unsetInputComponent(t.type, component) : undefined\n return {\n ...t,\n type: typeOfType,\n }\n}\n"],"mappings":";;;;;;;AAAA;;;;;;;;;;AAEO,SAASA,sBAAT,CAAgCC,IAAhC,EAAsCC,SAAtC,EAAiD;EACtD,OAAOC,cAAA,CAAMC,OAAN,CAAc,MAAMC,mBAAmB,CAACJ,IAAD,EAAOC,SAAP,CAAvC,EAA0D,CAACD,IAAD,EAAOC,SAAP,CAA1D,CAAP;AACD;;AAED,SAASG,mBAAT,CAA6BJ,IAA7B,EAAmCC,SAAnC,EAA8C;EAC5C,IAAMI,CAAC,mCACFL,IADE;IAELM,cAAc,EAAEN,IAAI,CAACM,cAAL,KAAwBL,SAAxB,GAAoCM,SAApC,GAAgDP,IAAI,CAACM;EAFhE,EAAP;;EAIA,IAAME,UAAU,GAAGH,CAAC,CAACL,IAAF,GAASI,mBAAmB,CAACC,CAAC,CAACL,IAAH,EAASC,SAAT,CAA5B,GAAkDM,SAArE;EACA,uCACKF,CADL;IAEEL,IAAI,EAAEQ;EAFR;AAID"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "sanity-plugin-hotspot-array",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.7",
|
|
4
4
|
"description": "A configurable Custom Input for Arrays that will add and update items by clicking on an Image",
|
|
5
5
|
"main": "lib/HotspotArray.js",
|
|
6
6
|
"scripts": {
|
|
@@ -28,27 +28,28 @@
|
|
|
28
28
|
"author": "Simeon Griggs <simeon@sanity.io>",
|
|
29
29
|
"license": "MIT",
|
|
30
30
|
"dependencies": {
|
|
31
|
+
"@react-hookz/web": "^14.2.2",
|
|
31
32
|
"@sanity/asset-utils": "^1.2.3",
|
|
32
|
-
"@sanity/base": "^2.
|
|
33
|
-
"@sanity/form-builder": "^2.
|
|
34
|
-
"@sanity/ui": "^0.
|
|
35
|
-
"framer-motion": "^
|
|
36
|
-
"husky": "^
|
|
33
|
+
"@sanity/base": "^2.30.1",
|
|
34
|
+
"@sanity/form-builder": "^2.30.1",
|
|
35
|
+
"@sanity/ui": "^0.37.12",
|
|
36
|
+
"framer-motion": "^6.3.11",
|
|
37
|
+
"husky": "^8.0.1"
|
|
37
38
|
},
|
|
38
39
|
"devDependencies": {
|
|
39
|
-
"eslint": "^
|
|
40
|
-
"eslint-config-prettier": "^8.
|
|
41
|
-
"eslint-config-sanity": "^
|
|
42
|
-
"eslint-plugin-react": "^7.
|
|
40
|
+
"eslint": "^8.17.0",
|
|
41
|
+
"eslint-config-prettier": "^8.5.0",
|
|
42
|
+
"eslint-config-sanity": "^6.0.0",
|
|
43
|
+
"eslint-plugin-react": "^7.30.0",
|
|
43
44
|
"pinst": "^2.1.6",
|
|
44
|
-
"prettier": "^2.
|
|
45
|
+
"prettier": "^2.7.0",
|
|
45
46
|
"sanipack": "^2.1.0"
|
|
46
47
|
},
|
|
47
48
|
"peerDependencies": {
|
|
48
|
-
"react": "^17.0.0",
|
|
49
|
-
"@sanity/util": "2.23.2",
|
|
50
49
|
"@sanity/image-url": "1.0.1",
|
|
51
|
-
"
|
|
50
|
+
"@sanity/util": "2.29.5",
|
|
51
|
+
"lodash": "4.17.21",
|
|
52
|
+
"react": "^17.0.2"
|
|
52
53
|
},
|
|
53
54
|
"bugs": {
|
|
54
55
|
"url": "https://github.com/SimeonGriggs/sanity-plugin-hotspot-array/issues"
|
package/src/HotspotArray.tsx
CHANGED
|
@@ -5,40 +5,62 @@ import sanityClient from 'part:@sanity/base/client'
|
|
|
5
5
|
// @ts-ignore
|
|
6
6
|
import {withDocument} from 'part:@sanity/form-builder'
|
|
7
7
|
|
|
8
|
-
import
|
|
9
|
-
import {Card, Flex, Stack, Text} from '@sanity/ui'
|
|
8
|
+
import {getImageDimensions} from '@sanity/asset-utils'
|
|
10
9
|
import {FormBuilderInput} from '@sanity/form-builder/lib/FormBuilderInput'
|
|
11
|
-
import {
|
|
12
|
-
import {PatchEvent, setIfMissing, set, insert} from '@sanity/form-builder/PatchEvent'
|
|
10
|
+
import {insert, PatchEvent, set, setIfMissing} from '@sanity/form-builder/PatchEvent'
|
|
13
11
|
import imageUrlBuilder from '@sanity/image-url'
|
|
14
|
-
import {
|
|
12
|
+
import {Card, Flex, Stack} from '@sanity/ui'
|
|
13
|
+
import {randomKey} from '@sanity/util/content'
|
|
15
14
|
import get from 'lodash/get'
|
|
15
|
+
import React, {useState} from 'react'
|
|
16
16
|
|
|
17
|
-
import
|
|
18
|
-
import { useUnsetInputComponent } from './useUnsetInputComponent'
|
|
17
|
+
import {IUseResizeObserverCallback, useDebouncedCallback, useResizeObserver} from '@react-hookz/web'
|
|
19
18
|
import Feedback from './Feedback'
|
|
19
|
+
import Spot from './Spot'
|
|
20
|
+
import {useUnsetInputComponent} from './useUnsetInputComponent'
|
|
20
21
|
|
|
21
|
-
const builder = imageUrlBuilder(sanityClient).dataset(sanityClient.config().dataset)
|
|
22
|
-
const urlFor = (source) => builder.image(source)
|
|
23
22
|
const imageStyle = {width: `100%`, height: `auto`}
|
|
24
23
|
|
|
24
|
+
const VALID_ROOT_PATHS = ['document', 'parent']
|
|
25
|
+
|
|
26
|
+
export type FnHotspotMove = (key: string, x: number, y: number) => void
|
|
25
27
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
+
export type TSpot = {
|
|
29
|
+
_key: string
|
|
30
|
+
_type: `spot`
|
|
31
|
+
x: number
|
|
32
|
+
y: number
|
|
33
|
+
} & {[key: string]: unknown}
|
|
34
|
+
|
|
35
|
+
const HotspotArray = React.forwardRef((props: any, ref) => {
|
|
36
|
+
const {type, value, onChange, document} = props
|
|
28
37
|
const {options} = type ?? {}
|
|
29
38
|
|
|
30
39
|
// Attempt prevention of infinite loop in <FormBuilderInput />
|
|
31
40
|
// Re-renders can still occur if this Component is used again in a nested field
|
|
32
41
|
const typeWithoutInputComponent = useUnsetInputComponent(type, type?.inputComponent)
|
|
42
|
+
const imageHotspotPathRoot = VALID_ROOT_PATHS.includes(options?.imageHotspotPathRoot)
|
|
43
|
+
? props[options.imageHotspotPathRoot]
|
|
44
|
+
: document
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Finding the image from the imageHotspotPathRoot (defaults to document),
|
|
48
|
+
* using the path from the hotspot's `options` field
|
|
49
|
+
*
|
|
50
|
+
* when changes in imageHotspotPathRoot (e.g. document) occur,
|
|
51
|
+
* check if there are any changes to the hotspotImage and update the reference
|
|
52
|
+
*/
|
|
53
|
+
const hotspotImage = React.useMemo(() => {
|
|
54
|
+
return get(imageHotspotPathRoot, options?.hotspotImagePath)
|
|
55
|
+
}, [imageHotspotPathRoot])
|
|
33
56
|
|
|
34
|
-
// Finding the image from the document,
|
|
35
|
-
// using the path from the hotspot's `options` field
|
|
36
57
|
const displayImage = React.useMemo(() => {
|
|
37
|
-
const
|
|
58
|
+
const builder = imageUrlBuilder(sanityClient).dataset(sanityClient.config().dataset)
|
|
59
|
+
const urlFor = (source) => builder.image(source)
|
|
38
60
|
|
|
39
61
|
if (hotspotImage?.asset?._ref) {
|
|
40
62
|
const {aspectRatio} = getImageDimensions(hotspotImage.asset._ref)
|
|
41
|
-
const width =
|
|
63
|
+
const width = 1200
|
|
42
64
|
const height = Math.round(width / aspectRatio)
|
|
43
65
|
const url = urlFor(hotspotImage).width(width).url()
|
|
44
66
|
|
|
@@ -46,7 +68,7 @@ const HotspotArray = React.forwardRef((props, ref) => {
|
|
|
46
68
|
}
|
|
47
69
|
|
|
48
70
|
return null
|
|
49
|
-
}, [
|
|
71
|
+
}, [hotspotImage])
|
|
50
72
|
|
|
51
73
|
const handleHotspotImageClick = React.useCallback((event) => {
|
|
52
74
|
const {nativeEvent} = event
|
|
@@ -56,7 +78,7 @@ const HotspotArray = React.forwardRef((props, ref) => {
|
|
|
56
78
|
const y = Number(((nativeEvent.offsetY * 100) / nativeEvent.srcElement.height).toFixed(2))
|
|
57
79
|
const description = `New Hotspot at ${x}% x ${y}%`
|
|
58
80
|
|
|
59
|
-
const newRow = {
|
|
81
|
+
const newRow: TSpot = {
|
|
60
82
|
_key: randomKey(12),
|
|
61
83
|
_type: `spot`,
|
|
62
84
|
x,
|
|
@@ -70,13 +92,8 @@ const HotspotArray = React.forwardRef((props, ref) => {
|
|
|
70
92
|
onChange(PatchEvent.from(setIfMissing([]), insert([newRow], 'after', [-1])))
|
|
71
93
|
}, [])
|
|
72
94
|
|
|
73
|
-
const handleHotspotMove = React.useCallback(
|
|
95
|
+
const handleHotspotMove: FnHotspotMove = React.useCallback(
|
|
74
96
|
(key, x, y) => {
|
|
75
|
-
if (!Number(x) || !Number(y)) {
|
|
76
|
-
console.warn(`Missing or non-number X or Y`, {x, y})
|
|
77
|
-
return
|
|
78
|
-
}
|
|
79
|
-
|
|
80
97
|
onChange(
|
|
81
98
|
PatchEvent.from(
|
|
82
99
|
// Set the `x` value of this array key item
|
|
@@ -89,18 +106,28 @@ const HotspotArray = React.forwardRef((props, ref) => {
|
|
|
89
106
|
[value]
|
|
90
107
|
)
|
|
91
108
|
|
|
92
|
-
const hotspotImageRef = React.useRef(null)
|
|
109
|
+
const hotspotImageRef = React.useRef<HTMLImageElement | null>(null)
|
|
110
|
+
|
|
111
|
+
const [imageRect, setImageRect] = useState<DOMRectReadOnly>()
|
|
112
|
+
const updateImageRectCallback = useDebouncedCallback(
|
|
113
|
+
((e) => setImageRect(e.contentRect)) as IUseResizeObserverCallback,
|
|
114
|
+
[setImageRect],
|
|
115
|
+
200
|
|
116
|
+
)
|
|
117
|
+
useResizeObserver(hotspotImageRef, updateImageRectCallback)
|
|
93
118
|
|
|
94
119
|
return (
|
|
95
|
-
<Stack space={[2,2,3]}>
|
|
120
|
+
<Stack space={[2, 2, 3]}>
|
|
96
121
|
{displayImage?.url ? (
|
|
97
122
|
<div style={{position: `relative`}}>
|
|
98
|
-
{
|
|
99
|
-
value
|
|
123
|
+
{imageRect &&
|
|
124
|
+
value?.length > 0 &&
|
|
125
|
+
value.map((spot, index) => (
|
|
100
126
|
<Spot
|
|
127
|
+
index={index}
|
|
101
128
|
key={spot._key}
|
|
102
129
|
spot={spot}
|
|
103
|
-
bounds={
|
|
130
|
+
bounds={imageRect}
|
|
104
131
|
update={handleHotspotMove}
|
|
105
132
|
hotspotDescriptionPath={options?.hotspotDescriptionPath}
|
|
106
133
|
tooltip={options?.hotspotTooltip}
|
|
@@ -117,18 +144,31 @@ const HotspotArray = React.forwardRef((props, ref) => {
|
|
|
117
144
|
alt=""
|
|
118
145
|
style={imageStyle}
|
|
119
146
|
onClick={handleHotspotImageClick}
|
|
120
|
-
|
|
147
|
+
/>
|
|
121
148
|
</Flex>
|
|
122
149
|
</Card>
|
|
123
150
|
</div>
|
|
124
151
|
) : (
|
|
125
152
|
<Feedback>
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
153
|
+
{type?.options?.hotspotImagePath ? (
|
|
154
|
+
<>
|
|
155
|
+
No Hotspot image found at path <code>{type?.options?.hotspotImagePath}</code>
|
|
156
|
+
</>
|
|
157
|
+
) : (
|
|
158
|
+
<>
|
|
159
|
+
Define a path in this field using to the image field in this document at{' '}
|
|
160
|
+
<code>options.hotspotImagePath</code>
|
|
161
|
+
</>
|
|
162
|
+
)}
|
|
163
|
+
</Feedback>
|
|
131
164
|
)}
|
|
165
|
+
{type?.options?.imageHotspotPathRoot &&
|
|
166
|
+
!VALID_ROOT_PATHS.includes(type.options.imageHotspotPathRoot) && (
|
|
167
|
+
<Feedback>
|
|
168
|
+
The supplied imageHotspotPathRoot "{type.options.imageHotspotPathRoot}" is not valid,
|
|
169
|
+
falling back to "document". Available values are "{VALID_ROOT_PATHS.join(', ')}".
|
|
170
|
+
</Feedback>
|
|
171
|
+
)}
|
|
132
172
|
<FormBuilderInput {...props} type={typeWithoutInputComponent} ref={ref} />
|
|
133
173
|
</Stack>
|
|
134
174
|
)
|
package/src/Spot.tsx
CHANGED
|
@@ -1,81 +1,120 @@
|
|
|
1
|
-
import
|
|
2
|
-
import {
|
|
3
|
-
import {motion} from 'framer-motion'
|
|
1
|
+
import {Box, Text, Tooltip} from '@sanity/ui'
|
|
2
|
+
import {motion, useMotionValue} from 'framer-motion'
|
|
4
3
|
import get from 'lodash/get'
|
|
4
|
+
import React, {CSSProperties, ReactElement, useEffect} from 'react'
|
|
5
|
+
import {FnHotspotMove, TSpot} from './HotspotArray'
|
|
6
|
+
|
|
7
|
+
const dragStyle: CSSProperties = {
|
|
8
|
+
width: '1.4rem',
|
|
9
|
+
height: '1.4rem',
|
|
10
|
+
position: 'absolute',
|
|
11
|
+
top: 0,
|
|
12
|
+
left: 0,
|
|
13
|
+
margin: '-0.7rem 0 0 -0.7rem',
|
|
14
|
+
cursor: 'pointer',
|
|
15
|
+
display: 'flex',
|
|
16
|
+
justifyContent: 'center',
|
|
17
|
+
alignItems: 'center',
|
|
18
|
+
borderRadius: '50%',
|
|
19
|
+
background: '#000',
|
|
20
|
+
color: 'white',
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const dragStyleWhileDrag: CSSProperties = {
|
|
24
|
+
background: 'rgba(0, 0, 0, 0.1)',
|
|
25
|
+
border: '1px solid #fff',
|
|
26
|
+
cursor: 'none',
|
|
27
|
+
}
|
|
5
28
|
|
|
6
|
-
const
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
position: `absolute`,
|
|
29
|
+
const dragStyleWhileHover: CSSProperties = {
|
|
30
|
+
background: 'rgba(0, 0, 0, 0.1)',
|
|
31
|
+
border: '1px solid #fff',
|
|
10
32
|
}
|
|
11
33
|
|
|
12
|
-
const dotStyle = {
|
|
13
|
-
|
|
14
|
-
|
|
34
|
+
const dotStyle: CSSProperties = {
|
|
35
|
+
position: 'absolute',
|
|
36
|
+
left: '50%',
|
|
37
|
+
top: '50%',
|
|
38
|
+
height: '0.2rem',
|
|
39
|
+
width: '0.2rem',
|
|
40
|
+
margin: '-0.1rem 0 0 -0.1rem',
|
|
41
|
+
background: '#fff',
|
|
42
|
+
visibility: 'hidden',
|
|
43
|
+
borderRadius: '50%',
|
|
15
44
|
// make sure pointer events only run on the parent
|
|
16
|
-
pointerEvents:
|
|
45
|
+
pointerEvents: 'none',
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const dotStyleWhileActive: CSSProperties = {
|
|
49
|
+
visibility: 'visible',
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const labelStyle: CSSProperties = {
|
|
53
|
+
color: 'white',
|
|
54
|
+
fontSize: '0.7rem',
|
|
55
|
+
fontWeight: 600,
|
|
56
|
+
lineHeight: '1',
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const labelStyleWhileActive: CSSProperties = {
|
|
60
|
+
visibility: 'hidden',
|
|
17
61
|
}
|
|
18
62
|
|
|
19
63
|
const round = (num) => Math.round(num * 100) / 100
|
|
20
64
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
65
|
+
interface IHotspot {
|
|
66
|
+
spot: TSpot
|
|
67
|
+
bounds: DOMRectReadOnly
|
|
68
|
+
update: FnHotspotMove
|
|
69
|
+
hotspotDescriptionPath?: string
|
|
70
|
+
tooltip?: ReactElement
|
|
71
|
+
index: number
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export default function Spot({
|
|
75
|
+
spot,
|
|
76
|
+
bounds,
|
|
77
|
+
update,
|
|
78
|
+
hotspotDescriptionPath,
|
|
79
|
+
tooltip,
|
|
80
|
+
index,
|
|
81
|
+
}: IHotspot) {
|
|
25
82
|
const [isDragging, setIsDragging] = React.useState(false)
|
|
83
|
+
const [isHovering, setIsHovering] = React.useState(false)
|
|
26
84
|
|
|
27
|
-
|
|
28
|
-
|
|
85
|
+
// x/y are stored as % but need to be converted to px
|
|
86
|
+
const x = useMotionValue(round(bounds.width * (spot.x / 100)))
|
|
87
|
+
const y = useMotionValue(round(bounds.height * (spot.y / 100)))
|
|
29
88
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
89
|
+
/**
|
|
90
|
+
* update x/y if the bounds change when resizing the window
|
|
91
|
+
*/
|
|
92
|
+
useEffect(() => {
|
|
93
|
+
x.set(round(bounds.width * (spot.x / 100)))
|
|
94
|
+
y.set(round(bounds.height * (spot.y / 100)))
|
|
95
|
+
}, [bounds])
|
|
36
96
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
}, [bounds, bounds.current])
|
|
42
|
-
|
|
43
|
-
const handleDragEnd = React.useCallback(
|
|
44
|
-
(event) => {
|
|
45
|
-
setIsDragging(false)
|
|
46
|
-
|
|
47
|
-
// I don't know why, but framer-motion doesn't give you the actual transform values
|
|
48
|
-
// So we have to regex the `px` values off the inline styles
|
|
49
|
-
const [currentX, currentY] = event.srcElement.style.transform.split(` `).map((v) => {
|
|
50
|
-
return v
|
|
51
|
-
? v
|
|
52
|
-
.match(/\(([^)]+)\)/)
|
|
53
|
-
.pop()
|
|
54
|
-
.replace(`px`, ``)
|
|
55
|
-
: null
|
|
56
|
-
})
|
|
57
|
-
|
|
58
|
-
if (!Number(currentX) || !Number(currentY)) {
|
|
59
|
-
return console.warn(`Missing or non-number X or Y`, {currentX, currentY}, event.srcElement)
|
|
60
|
-
}
|
|
97
|
+
const handleDragEnd = React.useCallback(() => {
|
|
98
|
+
setIsDragging(false)
|
|
61
99
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
100
|
+
// get current values for x/y in px
|
|
101
|
+
const currentX = x.get()
|
|
102
|
+
const currentY = y.get()
|
|
65
103
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
// Don't go below 0 or above 100
|
|
71
|
-
const safeX = Math.max(0, Math.min(100, newX))
|
|
72
|
-
const safeY = Math.max(0, Math.min(100, newY))
|
|
104
|
+
// Which we need to convert back to `%` to patch the document
|
|
105
|
+
const newX = round((currentX * 100) / bounds.width)
|
|
106
|
+
const newY = round((currentY * 100) / bounds.height)
|
|
73
107
|
|
|
74
|
-
|
|
75
|
-
|
|
108
|
+
// Don't go below 0 or above 100
|
|
109
|
+
const safeX = Math.max(0, Math.min(100, newX))
|
|
110
|
+
const safeY = Math.max(0, Math.min(100, newY))
|
|
76
111
|
|
|
77
|
-
|
|
78
|
-
)
|
|
112
|
+
update(spot._key, safeX, safeY)
|
|
113
|
+
}, [spot])
|
|
114
|
+
const handleDragStart = React.useCallback(() => setIsDragging(true), [])
|
|
115
|
+
|
|
116
|
+
const handleHoverStart = React.useCallback(() => setIsHovering(true), [])
|
|
117
|
+
const handleHoverEnd = React.useCallback(() => setIsHovering(false), [])
|
|
79
118
|
|
|
80
119
|
if (!x || !y) {
|
|
81
120
|
return null
|
|
@@ -85,14 +124,17 @@ export default function Spot({spot, bounds = undefined, update, hotspotDescripti
|
|
|
85
124
|
<Tooltip
|
|
86
125
|
key={spot._key}
|
|
87
126
|
disabled={isDragging}
|
|
88
|
-
boundaryElement={bounds.current}
|
|
89
127
|
portal
|
|
90
128
|
content={
|
|
91
129
|
tooltip && typeof tooltip === 'function' ? (
|
|
92
130
|
React.createElement(tooltip, {spot})
|
|
93
131
|
) : (
|
|
94
132
|
<Box padding={2} style={{maxWidth: 200, pointerEvents: `none`}}>
|
|
95
|
-
<Text textOverflow="ellipsis">
|
|
133
|
+
<Text textOverflow="ellipsis">
|
|
134
|
+
{hotspotDescriptionPath
|
|
135
|
+
? (get(spot, hotspotDescriptionPath) as string)
|
|
136
|
+
: `${spot.x}% x ${spot.y}%`}
|
|
137
|
+
</Text>
|
|
96
138
|
</Box>
|
|
97
139
|
)
|
|
98
140
|
}
|
|
@@ -100,14 +142,37 @@ export default function Spot({spot, bounds = undefined, update, hotspotDescripti
|
|
|
100
142
|
<motion.div
|
|
101
143
|
drag
|
|
102
144
|
dragConstraints={bounds}
|
|
145
|
+
dragElastic={0}
|
|
103
146
|
dragMomentum={false}
|
|
104
|
-
initial={{x, y}}
|
|
105
147
|
onDragEnd={handleDragEnd}
|
|
106
|
-
onDragStart={
|
|
107
|
-
|
|
148
|
+
onDragStart={handleDragStart}
|
|
149
|
+
onHoverStart={handleHoverStart}
|
|
150
|
+
onHoverEnd={handleHoverEnd}
|
|
151
|
+
style={{
|
|
152
|
+
...dragStyle,
|
|
153
|
+
x,
|
|
154
|
+
y,
|
|
155
|
+
...(isDragging && {...dragStyleWhileDrag}),
|
|
156
|
+
...(isHovering && {...dragStyleWhileHover}),
|
|
157
|
+
}}
|
|
108
158
|
>
|
|
109
|
-
|
|
159
|
+
{/* Dot */}
|
|
160
|
+
<Box
|
|
161
|
+
style={{
|
|
162
|
+
...dotStyle,
|
|
163
|
+
...((isDragging || isHovering) && {...dotStyleWhileActive}),
|
|
164
|
+
}}
|
|
165
|
+
/>
|
|
166
|
+
{/* Label */}
|
|
167
|
+
<div
|
|
168
|
+
style={{
|
|
169
|
+
...labelStyle,
|
|
170
|
+
...((isDragging || isHovering) && {...labelStyleWhileActive}),
|
|
171
|
+
}}
|
|
172
|
+
>
|
|
173
|
+
{index + 1}
|
|
174
|
+
</div>
|
|
110
175
|
</motion.div>
|
|
111
176
|
</Tooltip>
|
|
112
177
|
)
|
|
113
|
-
}
|
|
178
|
+
}
|
|
@@ -1,17 +1,17 @@
|
|
|
1
1
|
import React from 'react'
|
|
2
2
|
|
|
3
3
|
export function useUnsetInputComponent(type, component) {
|
|
4
|
-
|
|
4
|
+
return React.useMemo(() => unsetInputComponent(type, component), [type, component])
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
function unsetInputComponent(type, component) {
|
|
8
|
+
const t = {
|
|
9
|
+
...type,
|
|
10
|
+
inputComponent: type.inputComponent === component ? undefined : type.inputComponent,
|
|
11
|
+
}
|
|
12
|
+
const typeOfType = t.type ? unsetInputComponent(t.type, component) : undefined
|
|
13
|
+
return {
|
|
14
|
+
...t,
|
|
15
|
+
type: typeOfType,
|
|
5
16
|
}
|
|
6
|
-
|
|
7
|
-
function unsetInputComponent(type, component) {
|
|
8
|
-
const t = {
|
|
9
|
-
...type,
|
|
10
|
-
inputComponent: type.inputComponent === component ? undefined : type.inputComponent,
|
|
11
|
-
}
|
|
12
|
-
const typeOfType = t.type ? unsetInputComponent(t.type, component) : undefined
|
|
13
|
-
return {
|
|
14
|
-
...t,
|
|
15
|
-
type: typeOfType,
|
|
16
|
-
}
|
|
17
|
-
}
|
|
17
|
+
}
|