pages_core 3.12.1 → 3.12.2

Sign up to get free protection for your applications and to get access to all the features.
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
- };