pages_core 3.12.1 → 3.12.2

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 (113) hide show
  1. checksums.yaml +4 -4
  2. data/VERSION +1 -1
  3. data/app/assets/builds/pages_core/admin-dist.js +59 -14
  4. data/app/assets/builds/pages_core/admin-dist.js.map +7 -0
  5. data/app/assets/builds/pages_core/admin.css +39 -0
  6. data/app/assets/stylesheets/pages_core/admin/components/search.css +27 -0
  7. data/app/assets/stylesheets/pages_core/admin/controllers/pages.css +6 -0
  8. data/app/controllers/admin/pages_controller.rb +12 -11
  9. data/app/controllers/concerns/pages_core/rss_controller.rb +17 -1
  10. data/app/controllers/pages_core/admin_controller.rb +6 -0
  11. data/app/controllers/pages_core/frontend/pages_controller.rb +9 -5
  12. data/app/controllers/pages_core/sitemaps_controller.rb +3 -5
  13. data/app/helpers/admin/pages_helper.rb +32 -0
  14. data/app/javascript/admin-dist.ts +2 -0
  15. data/app/javascript/components/Attachments/{Attachment.jsx → Attachment.tsx} +42 -33
  16. data/app/javascript/components/Attachments/{AttachmentEditor.jsx → AttachmentEditor.tsx} +23 -23
  17. data/app/javascript/components/{EditableImage.jsx → EditableImage.tsx} +27 -24
  18. data/app/javascript/components/{FileUploadButton.jsx → FileUploadButton.tsx} +15 -16
  19. data/app/javascript/components/ImageCropper/FocalPoint.tsx +94 -0
  20. data/app/javascript/components/ImageCropper/{Image.jsx → Image.tsx} +13 -14
  21. data/app/javascript/components/ImageCropper/{Toolbar.jsx → Toolbar.tsx} +16 -12
  22. data/app/javascript/components/ImageCropper/{useCrop.js → useCrop.ts} +80 -37
  23. data/app/javascript/components/{ImageCropper.jsx → ImageCropper.tsx} +17 -15
  24. data/app/javascript/components/ImageEditor/{Form.jsx → Form.tsx} +24 -23
  25. data/app/javascript/components/{ImageEditor.jsx → ImageEditor.tsx} +17 -15
  26. data/app/javascript/components/ImageGrid/{DragElement.jsx → DragElement.tsx} +12 -10
  27. data/app/javascript/components/ImageGrid/{GridImage.jsx → GridImage.tsx} +40 -30
  28. data/app/javascript/components/ImageGrid/{Placeholder.jsx → Placeholder.tsx} +5 -6
  29. data/app/javascript/components/ImageGrid.jsx +3 -4
  30. data/app/javascript/components/{ImageUploader.jsx → ImageUploader.tsx} +46 -41
  31. data/app/javascript/components/Modal.tsx +48 -0
  32. data/app/javascript/components/PageImages.tsx +28 -0
  33. data/app/javascript/components/{PageTreeDraggable.jsx → PageTree/Draggable.tsx} +79 -57
  34. data/app/javascript/components/{PageTreeNode.jsx → PageTree/Node.tsx} +79 -70
  35. data/app/javascript/components/PageTree/types.ts +15 -0
  36. data/app/javascript/components/PageTree.tsx +206 -0
  37. data/app/javascript/components/RichTextToolbarButton.tsx +17 -0
  38. data/app/javascript/components/TagEditor/{AddTagForm.jsx → AddTagForm.tsx} +9 -10
  39. data/app/javascript/components/TagEditor/{Tag.jsx → Tag.tsx} +8 -9
  40. data/app/javascript/components/{TagEditor.jsx → TagEditor.tsx} +12 -13
  41. data/app/javascript/components/Toast.tsx +61 -0
  42. data/app/javascript/components/drag/{draggedOrder.js → draggedOrder.ts} +22 -12
  43. data/app/javascript/components/drag/types.ts +28 -0
  44. data/app/javascript/components/drag/{useDragCollection.js → useDragCollection.ts} +40 -22
  45. data/app/javascript/components/drag/{useDragUploader.js → useDragUploader.ts} +34 -25
  46. data/app/javascript/components/drag/useDraggable.ts +21 -0
  47. data/app/javascript/components/{drag.js → drag.ts} +1 -0
  48. data/app/javascript/controllers/{EditPageController.js → EditPageController.ts} +3 -1
  49. data/app/javascript/controllers/{LoginController.js → LoginController.ts} +7 -3
  50. data/app/javascript/controllers/{MainController.js → MainController.ts} +19 -14
  51. data/app/javascript/{index.js → index.ts} +8 -7
  52. data/app/javascript/lib/{Tree.js → Tree.ts} +106 -85
  53. data/app/javascript/lib/{copyToClipboard.js → copyToClipboard.ts} +1 -1
  54. data/app/javascript/lib/{readyHandler.js → readyHandler.ts} +4 -2
  55. data/app/javascript/lib/{request.js → request.ts} +11 -5
  56. data/app/javascript/stores/useModalStore.ts +15 -0
  57. data/app/javascript/stores/useToastStore.ts +26 -0
  58. data/app/javascript/stores.ts +2 -0
  59. data/app/javascript/types.ts +30 -0
  60. data/app/policies/page_policy.rb +4 -0
  61. data/app/views/admin/calendars/_sidebar.html.erb +3 -0
  62. data/app/views/admin/news/_sidebar.html.erb +3 -0
  63. data/app/views/admin/pages/_list_item.html.erb +4 -22
  64. data/app/views/admin/pages/_search_bar.html.erb +12 -0
  65. data/app/views/admin/pages/index.html.erb +3 -0
  66. data/app/views/admin/pages/search.html.erb +54 -0
  67. data/app/views/feeds/pages.rss.builder +3 -9
  68. data/config/routes.rb +1 -0
  69. data/lib/pages_core/configuration/pages.rb +0 -1
  70. data/lib/rails/generators/pages_core/frontend/frontend_generator.rb +33 -17
  71. data/lib/rails/generators/pages_core/frontend/templates/application.html.erb +0 -1
  72. data/lib/rails/generators/pages_core/frontend/templates/javascript/lib/gridOverlay.ts +40 -0
  73. data/lib/rails/generators/pages_core/frontend/templates/javascript/lib/responsiveEmbeds.ts +68 -0
  74. data/lib/rails/generators/pages_core/frontend/templates/postcss.config.js +17 -0
  75. data/lib/rails/generators/pages_core/frontend/templates/stylesheets/application.postcss.css +4 -0
  76. data/lib/rails/generators/pages_core/frontend/templates/stylesheets/components/base.css +24 -0
  77. data/lib/rails/generators/pages_core/frontend/templates/stylesheets/components/layout.css +21 -0
  78. data/lib/rails/generators/pages_core/frontend/templates/stylesheets/config.css +5 -0
  79. data/lib/rails/generators/pages_core/frontend/templates/stylesheets/global/animation.css +5 -0
  80. data/lib/rails/generators/pages_core/frontend/templates/stylesheets/global/colors.css +18 -0
  81. data/lib/rails/generators/pages_core/frontend/templates/stylesheets/global/fonts.css +6 -0
  82. data/lib/rails/generators/pages_core/frontend/templates/stylesheets/global/grid.css +65 -0
  83. data/lib/rails/generators/pages_core/frontend/templates/stylesheets/global/typography.css +131 -0
  84. data/lib/rails/generators/pages_core/install/templates/pages_initializer.rb +0 -3
  85. metadata +68 -62
  86. data/app/javascript/admin-dist.js +0 -2
  87. data/app/javascript/components/ImageCropper/FocalPoint.jsx +0 -93
  88. data/app/javascript/components/Modal.jsx +0 -59
  89. data/app/javascript/components/PageImages.jsx +0 -25
  90. data/app/javascript/components/PageTree.jsx +0 -196
  91. data/app/javascript/components/RichTextToolbarButton.jsx +0 -20
  92. data/app/javascript/components/Toast.jsx +0 -72
  93. data/app/javascript/components/drag/useDraggable.js +0 -17
  94. data/app/javascript/stores/ModalStore.jsx +0 -12
  95. data/app/javascript/stores/ToastStore.jsx +0 -14
  96. data/app/javascript/stores.js +0 -2
  97. data/lib/rails/generators/pages_core/frontend/templates/javascript/lib/GridOverlay.js +0 -66
  98. data/lib/rails/generators/pages_core/frontend/templates/javascript/lib/ResponsiveEmbeds.js +0 -72
  99. data/lib/rails/generators/pages_core/frontend/templates/stylesheets/application.sass.scss +0 -15
  100. data/lib/rails/generators/pages_core/frontend/templates/stylesheets/components/base.scss +0 -12
  101. data/lib/rails/generators/pages_core/frontend/templates/stylesheets/config.scss +0 -26
  102. data/lib/rails/generators/pages_core/frontend/templates/stylesheets/framework/breakpoints.scss +0 -42
  103. data/lib/rails/generators/pages_core/frontend/templates/stylesheets/framework/clearfix.scss +0 -7
  104. data/lib/rails/generators/pages_core/frontend/templates/stylesheets/framework/fonts.scss +0 -32
  105. data/lib/rails/generators/pages_core/frontend/templates/stylesheets/framework/grid.scss +0 -168
  106. data/lib/rails/generators/pages_core/frontend/templates/stylesheets/framework/grid_overlay.scss +0 -44
  107. data/lib/rails/generators/pages_core/frontend/templates/stylesheets/global/colors.scss +0 -8
  108. data/lib/rails/generators/pages_core/frontend/templates/stylesheets/global/typography.scss +0 -90
  109. data/lib/rails/generators/pages_core/frontend/templates/stylesheets/vendor/normalize.css +0 -349
  110. /data/app/javascript/components/Attachments/{Placeholder.jsx → Placeholder.tsx} +0 -0
  111. /data/app/javascript/components/ImageGrid/{FilePlaceholder.jsx → FilePlaceholder.tsx} +0 -0
  112. /data/app/javascript/{components.js → components.ts} +0 -0
  113. /data/app/javascript/{hooks.js → hooks.ts} +0 -0
@@ -0,0 +1,94 @@
1
+ import React, { useRef, useState } from "react";
2
+
3
+ interface Position {
4
+ x: number,
5
+ y: number,
6
+ }
7
+
8
+ interface FocalPointProps {
9
+ x: number,
10
+ y: number,
11
+ onChange: (pos: Position) => void,
12
+ width: number,
13
+ height: number
14
+ }
15
+
16
+ function clamp(val: number, min: number, max: number): number {
17
+ if (val < min) {
18
+ return min;
19
+ } else if (val > max) {
20
+ return max;
21
+ } else {
22
+ return val;
23
+ }
24
+ }
25
+
26
+ export default function FocalPoint(props: FocalPointProps) {
27
+ const { width, height, onChange } = props;
28
+
29
+ const [dragging, setDragging] = useState(false);
30
+ const [position, setPosition] = useState<Position>({ x: props.x, y: props.y });
31
+
32
+ const containerRef = useRef<HTMLDivElement>();
33
+ const pointRef = useRef<HTMLDivElement>();
34
+
35
+ const dragStart = (evt: Event) => {
36
+ evt.preventDefault();
37
+ evt.stopPropagation();
38
+ if (evt.target == pointRef.current) {
39
+ setDragging(true);
40
+ }
41
+ };
42
+
43
+ const dragEnd = () => {
44
+ if (dragging) {
45
+ setDragging(false);
46
+ onChange(position);
47
+ }
48
+ };
49
+
50
+ const drag = (evt: TouchEvent | MouseEvent) => {
51
+ if (dragging) {
52
+ let x: number , y: number;
53
+ const containerSize = containerRef.current.getBoundingClientRect();
54
+ evt.preventDefault();
55
+
56
+ if ("touches" in evt && evt.type == "touchmove") {
57
+ x = evt.touches[0].clientX - (containerSize.x || containerSize.left);
58
+ y = evt.touches[0].clientY - (containerSize.y || containerSize.top);
59
+ } else {
60
+ x = evt.clientX - (containerSize.x || containerSize.left);
61
+ y = evt.clientY - (containerSize.y || containerSize.top);
62
+ }
63
+
64
+ x = clamp(x, 0, width);
65
+ y = clamp(y, 0, height);
66
+
67
+ setPosition({
68
+ x: (x / width) * 100,
69
+ y: (y / height) * 100
70
+ });
71
+ }
72
+ };
73
+
74
+ const x = width * (position.x / 100);
75
+ const y = height * (position.y / 100);
76
+ const pointStyle = {
77
+ transform: `translate3d(${x}px, ${y}px, 0)`
78
+ };
79
+
80
+ return (
81
+ <div className="focal-editor"
82
+ ref={containerRef}
83
+ onTouchStart={dragStart}
84
+ onTouchEnd={dragEnd}
85
+ onTouchMove={drag}
86
+ onMouseDown={dragStart}
87
+ onMouseUp={dragEnd}
88
+ onMouseMove={drag}>
89
+ <div className="focal-point"
90
+ style={pointStyle}
91
+ ref={pointRef} />
92
+ </div>
93
+ );
94
+ }
@@ -1,11 +1,19 @@
1
1
  import React from "react";
2
- import PropTypes from "prop-types";
3
2
  import ReactCrop from "react-image-crop";
4
3
 
5
- import { cropSize } from "./useCrop";
4
+ import { cropSize, CropSize, CropState, Position, Size } from "./useCrop";
6
5
  import FocalPoint from "./FocalPoint";
7
6
 
8
- export default function Image(props) {
7
+ interface ImageProps {
8
+ containerSize: Size,
9
+ croppedImage: string,
10
+ cropState: CropState,
11
+ focalPoint: Position,
12
+ setCrop: (crop: CropSize) => void,
13
+ setFocal: (focal: Position) => void
14
+ }
15
+
16
+ export default function Image(props: ImageProps) {
9
17
  const imageSize = () => {
10
18
  const { image, cropping, crop_width, crop_height } = props.cropState;
11
19
  if (cropping) {
@@ -19,8 +27,8 @@ export default function Image(props) {
19
27
  const maxHeight = props.containerSize.height;
20
28
  const aspect = imageSize().width / imageSize().height;
21
29
 
22
- var width = maxWidth;
23
- var height = maxWidth / aspect;
30
+ let width = maxWidth;
31
+ let height = maxWidth / aspect;
24
32
 
25
33
  if (height > maxHeight) {
26
34
  height = maxHeight;
@@ -54,12 +62,3 @@ export default function Image(props) {
54
62
  }
55
63
 
56
64
  }
57
-
58
- Image.propTypes = {
59
- containerSize: PropTypes.object,
60
- croppedImage: PropTypes.string,
61
- cropState: PropTypes.object,
62
- focalPoint: PropTypes.object,
63
- setCrop: PropTypes.func,
64
- setFocal: PropTypes.func
65
- };
@@ -1,7 +1,19 @@
1
1
  import React from "react";
2
- import PropTypes from "prop-types";
3
2
 
4
- export default function Toolbar(props) {
3
+ import { ImageResource } from "../../types";
4
+ import { CropState } from "./useCrop";
5
+
6
+ type Ratio = number | null;
7
+
8
+ interface ToolbarProps {
9
+ cropState: CropState,
10
+ image: ImageResource,
11
+ setAspect: (Ratio) => void,
12
+ toggleCrop: (evt: Event) => void,
13
+ toggleFocal: (evt: Event) => void
14
+ }
15
+
16
+ export default function Toolbar(props: ToolbarProps) {
5
17
  const { cropping } = props.cropState;
6
18
 
7
19
  const aspectRatios = [
@@ -10,7 +22,7 @@ export default function Toolbar(props) {
10
22
  ["16:9", 16/9]
11
23
  ];
12
24
 
13
- const updateAspect = (ratio) => (evt) => {
25
+ const updateAspect = (ratio: Ratio) => (evt: Event) => {
14
26
  evt.preventDefault();
15
27
  props.setAspect(ratio);
16
28
  };
@@ -52,7 +64,7 @@ export default function Toolbar(props) {
52
64
  Lock aspect ratio:
53
65
  </div>
54
66
  {aspectRatios.map(ratio => (
55
- <button key={"ratio-" + ratio[1]}
67
+ <button key={ratio[0]}
56
68
  className={(ratio[1] == props.cropState.aspect) ? "active" : ""}
57
69
  onClick={updateAspect(ratio[1])}>
58
70
  {ratio[0]}
@@ -63,11 +75,3 @@ export default function Toolbar(props) {
63
75
  </div>
64
76
  );
65
77
  }
66
-
67
- Toolbar.propTypes = {
68
- cropState: PropTypes.object,
69
- image: PropTypes.object,
70
- setAspect: PropTypes.func,
71
- toggleCrop: PropTypes.func,
72
- toggleFocal: PropTypes.func
73
- };
@@ -1,9 +1,49 @@
1
1
  import { useEffect, useReducer, useState } from "react";
2
2
 
3
- function applyAspect(state, aspect) {
4
- let crop = cropSize(state);
5
- let image = state.image;
6
- let imageAspect = image.real_width / image.real_height;
3
+ import { ImageResource } from "../../types";
4
+
5
+ export interface Position {
6
+ x: number,
7
+ y: number,
8
+ }
9
+
10
+ export interface Size {
11
+ width: number,
12
+ height: number
13
+ }
14
+
15
+ interface CropParams {
16
+ crop_start_x: number,
17
+ crop_start_y: number,
18
+ crop_width: number,
19
+ crop_height: number,
20
+ crop_gravity_x: number,
21
+ crop_gravity_y: number,
22
+ }
23
+
24
+ export interface CropState extends CropParams {
25
+ aspect: number | null,
26
+ cropping: boolean,
27
+ image: ImageResource
28
+ }
29
+
30
+ export interface CropSize {
31
+ x: number,
32
+ y: number,
33
+ width: number,
34
+ height: number,
35
+ aspect?: number
36
+ }
37
+
38
+ export interface CropAction {
39
+ type: string,
40
+ payload?: CropSize | Position
41
+ }
42
+
43
+ function applyAspect(state: CropState, aspect: number | null) {
44
+ const crop = cropSize(state);
45
+ const image = state.image;
46
+ const imageAspect = image.real_width / image.real_height;
7
47
 
8
48
  // Maximize and center crop area
9
49
  if (aspect) {
@@ -25,7 +65,7 @@ function applyAspect(state, aspect) {
25
65
  return(applyCrop(state, crop));
26
66
  }
27
67
 
28
- function applyCrop(state, crop) {
68
+ function applyCrop(state: CropState, crop: CropSize) {
29
69
  const { image } = state;
30
70
 
31
71
  // Don't crop if dimensions are below the threshold
@@ -44,7 +84,7 @@ function applyCrop(state, crop) {
44
84
  crop_height: image.real_height * (crop.height / 100) });
45
85
  }
46
86
 
47
- function cropReducer(state, action) {
87
+ function cropReducer(state: CropState, action: CropAction): CropState {
48
88
  const { crop_start_x,
49
89
  crop_start_y,
50
90
  crop_width,
@@ -86,7 +126,7 @@ function cropReducer(state, action) {
86
126
  }
87
127
  }
88
128
 
89
- function croppedImageCanvas(img, crop) {
129
+ function croppedImageCanvas(img: HTMLImageElement, crop: CropSize) {
90
130
  const canvas = document.createElement("canvas");
91
131
  canvas.width = (img.naturalWidth * (crop.width / 100));
92
132
  canvas.height = (img.naturalHeight * (crop.height / 100));
@@ -105,9 +145,9 @@ function croppedImageCanvas(img, crop) {
105
145
  return [canvas, ctx];
106
146
  }
107
147
 
108
- function imageDataUrl(canvas, ctx) {
109
- let pixels = ctx.getImageData(0, 0, canvas.width, canvas.height).data;
110
- for (var i = 0; i < (pixels.length / 4); i++) {
148
+ function imageDataUrl(canvas: HTMLCanvasElement, ctx: CanvasRenderingContext2D): string {
149
+ const pixels = ctx.getImageData(0, 0, canvas.width, canvas.height).data;
150
+ for (let i = 0; i < (pixels.length / 4); i++) {
111
151
  if (pixels[(i * 4) + 3] !== 255) {
112
152
  return canvas.toDataURL("image/png");
113
153
  }
@@ -115,17 +155,19 @@ function imageDataUrl(canvas, ctx) {
115
155
  return canvas.toDataURL("image/jpeg");
116
156
  }
117
157
 
118
- export function cropParams(state) {
119
- const maybe = (func) => (val) => (val === null) ? val : func(val);
158
+ export function cropParams(state: CropState): CropParams {
159
+ const maybe = (func: (number) => number) => (val: number | null) => (val === null) ? val : func(val);
120
160
  const maybeRound = maybe(Math.round);
121
161
  const maybeCeil = maybe(Math.ceil);
122
162
 
123
- let crop = { crop_start_x: maybeRound(state.crop_start_x),
124
- crop_start_y: maybeRound(state.crop_start_y),
125
- crop_width: maybeCeil(state.crop_width),
126
- crop_height: maybeCeil(state.crop_height),
127
- crop_gravity_x: maybeRound(state.crop_gravity_x),
128
- crop_gravity_y: maybeRound(state.crop_gravity_y) };
163
+ const crop: CropParams = {
164
+ crop_start_x: maybeRound(state.crop_start_x),
165
+ crop_start_y: maybeRound(state.crop_start_y),
166
+ crop_width: maybeCeil(state.crop_width),
167
+ crop_height: maybeCeil(state.crop_height),
168
+ crop_gravity_x: maybeRound(state.crop_gravity_x),
169
+ crop_gravity_y: maybeRound(state.crop_gravity_y)
170
+ };
129
171
 
130
172
  if (crop.crop_start_x + crop.crop_width > state.image.real_width) {
131
173
  crop.crop_width = state.image.real_width - crop.crop_start_x;
@@ -138,7 +180,7 @@ export function cropParams(state) {
138
180
  return(crop);
139
181
  }
140
182
 
141
- export function cropSize(state) {
183
+ export function cropSize(state: CropState): CropSize {
142
184
  const { image,
143
185
  aspect,
144
186
  crop_start_x,
@@ -149,8 +191,8 @@ export function cropSize(state) {
149
191
  const x = (crop_start_x / image.real_width) * 100;
150
192
  const y = (crop_start_y / image.real_height) * 100;
151
193
 
152
- var width = (crop_width / image.real_width) * 100;
153
- var height = (crop_height / image.real_height) * 100;
194
+ let width = (crop_width / image.real_width) * 100;
195
+ let height = (crop_height / image.real_height) * 100;
154
196
 
155
197
  if (aspect && width) {
156
198
  height = (width / aspect) * imageAspect;
@@ -165,24 +207,25 @@ export function cropSize(state) {
165
207
  }
166
208
  }
167
209
 
168
- export default function useCrop(image) {
169
- const [state, dispatch] = useReducer(
170
- cropReducer,
171
- { aspect: null,
172
- cropping: false,
173
- crop_start_x: image.crop_start_x || 0,
174
- crop_start_y: image.crop_start_y || 0,
175
- crop_width: image.crop_width || image.real_width,
176
- crop_height: image.crop_height || image.real_height,
177
- crop_gravity_x: image.crop_gravity_x,
178
- crop_gravity_y: image.crop_gravity_y,
179
- image: image }
180
- );
210
+ export default function useCrop(image: ImageResource) {
211
+ const initialState: CropState = {
212
+ aspect: null,
213
+ cropping: false,
214
+ crop_start_x: image.crop_start_x || 0,
215
+ crop_start_y: image.crop_start_y || 0,
216
+ crop_width: image.crop_width || image.real_width,
217
+ crop_height: image.crop_height || image.real_height,
218
+ crop_gravity_x: image.crop_gravity_x,
219
+ crop_gravity_y: image.crop_gravity_y,
220
+ image: image
221
+ };
222
+
223
+ const [state, dispatch] = useReducer(cropReducer, initialState);
181
224
 
182
- const [croppedImage, setCroppedImage] = useState(null);
225
+ const [croppedImage, setCroppedImage] = useState<string | null>(null);
183
226
 
184
227
  async function updateCroppedImage() {
185
- const img = new Image();
228
+ const img: HTMLImageElement = new Image();
186
229
  img.src = state.image.uncropped_url;
187
230
  await img.decode();
188
231
  const [canvas, ctx] = croppedImageCanvas(img, cropSize(state));
@@ -191,7 +234,7 @@ export default function useCrop(image) {
191
234
 
192
235
  useEffect(() => {
193
236
  if (!state.cropping) {
194
- updateCroppedImage();
237
+ void updateCroppedImage();
195
238
  }
196
239
  }, [state.cropping]);
197
240
 
@@ -1,13 +1,21 @@
1
1
  import React, { useEffect, useRef, useState } from "react";
2
- import PropTypes from "prop-types";
3
2
 
4
3
  import Image from "./ImageCropper/Image";
5
4
  import Toolbar from "./ImageCropper/Toolbar";
6
5
 
6
+ import { CropAction, CropSize, CropState,
7
+ Position } from "./ImageCropper/useCrop";
8
+
7
9
  export { default as useCrop,
8
10
  cropParams } from "./ImageCropper/useCrop";
9
11
 
10
- function focalPoint(state) {
12
+ interface ImageCropperProps {
13
+ croppedImage: string,
14
+ cropState: CropState,
15
+ dispatch: (action: CropAction) => void
16
+ }
17
+
18
+ function focalPoint(state: CropState): Position {
11
19
  if (state.crop_gravity_x === null || state.crop_gravity_y === null) {
12
20
  return null;
13
21
  } else {
@@ -18,12 +26,12 @@ function focalPoint(state) {
18
26
  }
19
27
  }
20
28
 
21
- export default function ImageCropper(props) {
22
- const containerRef = useRef();
23
- const [containerSize, setContainerSize] = useState(null);
29
+ export default function ImageCropper(props: ImageCropperProps) {
30
+ const containerRef = useRef<HTMLDivElement>();
31
+ const [containerSize, setContainerSize] = useState();
24
32
 
25
33
  const handleResize = () => {
26
- let elem = containerRef.current;
34
+ const elem = containerRef.current;
27
35
  if (elem) {
28
36
  setContainerSize({ width: elem.offsetWidth - 2,
29
37
  height: elem.offsetHeight - 2 });
@@ -39,15 +47,15 @@ export default function ImageCropper(props) {
39
47
 
40
48
  useEffect(handleResize, []);
41
49
 
42
- const setAspect = (aspect) => {
50
+ const setAspect = (aspect: number) => {
43
51
  props.dispatch({ type: "setAspect", payload: aspect });
44
52
  };
45
53
 
46
- const setCrop = (crop) => {
54
+ const setCrop = (crop: CropSize) => {
47
55
  props.dispatch({ type: "setCrop", payload: crop });
48
56
  };
49
57
 
50
- const setFocal = (focal) => {
58
+ const setFocal = (focal: Position) => {
51
59
  props.dispatch({ type: "setFocal", payload: focal });
52
60
  };
53
61
 
@@ -82,9 +90,3 @@ export default function ImageCropper(props) {
82
90
  </div>
83
91
  );
84
92
  }
85
-
86
- ImageCropper.propTypes = {
87
- croppedImage: PropTypes.string,
88
- cropState: PropTypes.object,
89
- dispatch: PropTypes.func
90
- };
@@ -1,21 +1,34 @@
1
- import React from "react";
2
- import PropTypes from "prop-types";
3
- import ModalStore from "../../stores/ModalStore";
4
- import ToastStore from "../../stores/ToastStore";
1
+ import React, { ChangeEvent } from "react";
2
+ import useModalStore from "../../stores/useModalStore";
3
+ import useToastStore from "../../stores/useToastStore";
4
+ import { Locale, ImageResource } from "../../types";
5
5
  import copyToClipboard, { copySupported } from "../../lib/copyToClipboard";
6
6
 
7
- export default function Form(props) {
7
+ interface FormProps {
8
+ alternative: Record<string, string>,
9
+ caption: Record<string, string>,
10
+ image: ImageResource,
11
+ locale: string,
12
+ locales: Record<string, Locale>,
13
+ setLocale: (locale: string) => void,
14
+ save: (evt: Event) => void,
15
+ showCaption: boolean,
16
+ updateLocalization: (name: "alternative" | "caption", value: string) => void
17
+ }
18
+
19
+ export default function Form(props: FormProps) {
8
20
  const { alternative, caption, image, locale, locales } = props;
9
21
 
10
- const copyEmbedCode = (evt) => {
22
+ const closeModal = useModalStore((state) => state.close);
23
+ const notice = useToastStore((state) => state.notice);
24
+
25
+ const copyEmbedCode = (evt: Event) => {
11
26
  evt.preventDefault();
12
27
  copyToClipboard(`[image:${image.id}]`);
13
- ToastStore.dispatch({
14
- type: "NOTICE", message: "Embed code copied to clipboard"
15
- });
28
+ notice("Embed code copied to clipboard");
16
29
  };
17
30
 
18
- const handleChangeLocale = (evt) => {
31
+ const handleChangeLocale = (evt: ChangeEvent<HTMLSelectElement>) => {
19
32
  props.setLocale(evt.target.value);
20
33
  };
21
34
 
@@ -83,22 +96,10 @@ export default function Form(props) {
83
96
  <button onClick={props.save}>
84
97
  Save
85
98
  </button>
86
- <button onClick={() => ModalStore.dispatch({ type: "CLOSE" })}>
99
+ <button onClick={closeModal}>
87
100
  Cancel
88
101
  </button>
89
102
  </div>
90
103
  </form>
91
104
  );
92
105
  }
93
-
94
- Form.propTypes = {
95
- alternative: PropTypes.object,
96
- caption: PropTypes.object,
97
- image: PropTypes.object,
98
- locale: PropTypes.string,
99
- locales: PropTypes.object,
100
- setLocale: PropTypes.func,
101
- save: PropTypes.func,
102
- showCaption: PropTypes.bool,
103
- updateLocalization: PropTypes.func
104
- };
@@ -1,12 +1,20 @@
1
1
  import React, { useState } from "react";
2
- import PropTypes from "prop-types";
3
- import ModalStore from "../stores/ModalStore";
2
+ import useModalStore from "../stores/useModalStore";
4
3
  import { putJson } from "../lib/request";
5
4
 
5
+ import { Locale, ImageResource } from "../types";
6
6
  import ImageCropper, { useCrop, cropParams } from "./ImageCropper";
7
7
  import Form from "./ImageEditor/Form";
8
8
 
9
- export default function ImageEditor(props) {
9
+ interface ImageEditorProps {
10
+ image: ImageResource,
11
+ caption: boolean,
12
+ locale: string,
13
+ locales: Record<string, Locale>,
14
+ onUpdate?: (data: ImageResource, croppedImage: string | null) => void
15
+ }
16
+
17
+ export default function ImageEditor(props: ImageEditorProps) {
10
18
  const [cropState, dispatch, croppedImage] = useCrop(props.image);
11
19
  const [locale, setLocale] = useState(props.locale);
12
20
  const [localizations, setLocalizations] = useState({
@@ -14,24 +22,26 @@ export default function ImageEditor(props) {
14
22
  alternative: props.image.alternative || {},
15
23
  });
16
24
 
17
- const updateLocalization = (name, value) => {
25
+ const closeModal = useModalStore((state) => state.close);
26
+
27
+ const updateLocalization = (name: "alternative" | "caption", value: string) => {
18
28
  setLocalizations({
19
29
  ...localizations,
20
30
  [name]: { ...localizations[name], [locale]: value }
21
31
  });
22
32
  };
23
33
 
24
- const save = (evt) => {
34
+ const save = (evt: Event) => {
25
35
  evt.preventDefault();
26
36
  evt.stopPropagation();
27
37
 
28
38
  const data = { ...localizations, ...cropParams(cropState) };
29
- putJson(`/admin/images/${props.image.id}`, { image: data });
39
+ void putJson(`/admin/images/${props.image.id}`, { image: data });
30
40
 
31
41
  if (props.onUpdate) {
32
42
  props.onUpdate(data, croppedImage);
33
43
  }
34
- ModalStore.dispatch({ type: "CLOSE" });
44
+ closeModal();
35
45
  };
36
46
 
37
47
  return (
@@ -52,11 +62,3 @@ export default function ImageEditor(props) {
52
62
  </div>
53
63
  );
54
64
  }
55
-
56
- ImageEditor.propTypes = {
57
- image: PropTypes.object,
58
- locale: PropTypes.string,
59
- locales: PropTypes.object,
60
- caption: PropTypes.bool,
61
- onUpdate: PropTypes.func
62
- };
@@ -1,7 +1,15 @@
1
- import React from "react";
2
- import PropTypes from "prop-types";
1
+ import React, { RefObject } from "react";
3
2
 
4
- export default function DragElement(props) {
3
+ import { ImageResource } from "../../types";
4
+ import { DragState } from "../drag";
5
+
6
+ interface DragElementProps {
7
+ container: RefObject<HTMLDivElement>,
8
+ draggable: string | { record: { image: ImageResource, src?: string } },
9
+ dragState: DragState
10
+ }
11
+
12
+ export default function DragElement(props: DragElementProps) {
5
13
  const { draggable, dragState, container } = props;
6
14
 
7
15
  if (draggable === "Files") {
@@ -15,16 +23,10 @@ export default function DragElement(props) {
15
23
  };
16
24
  return (
17
25
  <div className="drag-image" style={translateStyle}>
18
- {draggable.record.image && (
26
+ {"record" in draggable && draggable.record.image && (
19
27
  <img src={draggable.record.src || draggable.record.image.thumbnail_url} />
20
28
  )}
21
29
  </div>
22
30
  );
23
31
  }
24
32
  }
25
-
26
- DragElement.propTypes = {
27
- draggable: PropTypes.oneOfType([PropTypes.object, PropTypes.string]),
28
- dragState: PropTypes.object,
29
- container: PropTypes.object
30
- };