pages_core 3.12.1 → 3.12.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (121) hide show
  1. checksums.yaml +4 -4
  2. data/VERSION +1 -1
  3. data/app/assets/builds/pages_core/admin-dist.js +135 -50
  4. data/app/assets/builds/pages_core/admin-dist.js.map +7 -0
  5. data/app/assets/builds/pages_core/admin.css +72 -20
  6. data/app/assets/stylesheets/pages_core/admin/components/attachments.css +1 -1
  7. data/app/assets/stylesheets/pages_core/admin/components/image_editor.css +2 -2
  8. data/app/assets/stylesheets/pages_core/admin/components/image_grid.css +8 -8
  9. data/app/assets/stylesheets/pages_core/admin/components/image_uploader.css +2 -2
  10. data/app/assets/stylesheets/pages_core/admin/components/layout.css +2 -2
  11. data/app/assets/stylesheets/pages_core/admin/components/modal.css +2 -2
  12. data/app/assets/stylesheets/pages_core/admin/components/search.css +27 -0
  13. data/app/assets/stylesheets/pages_core/admin/controllers/pages.css +6 -0
  14. data/app/controllers/admin/pages_controller.rb +12 -11
  15. data/app/controllers/concerns/pages_core/rss_controller.rb +17 -1
  16. data/app/controllers/pages_core/admin_controller.rb +6 -0
  17. data/app/controllers/pages_core/frontend/pages_controller.rb +9 -5
  18. data/app/controllers/pages_core/sitemaps_controller.rb +3 -5
  19. data/app/helpers/admin/pages_helper.rb +32 -0
  20. data/app/helpers/pages_core/images_helper.rb +28 -7
  21. data/app/javascript/admin-dist.ts +2 -0
  22. data/app/javascript/components/Attachments/{Attachment.jsx → Attachment.tsx} +42 -33
  23. data/app/javascript/components/Attachments/{AttachmentEditor.jsx → AttachmentEditor.tsx} +23 -23
  24. data/app/javascript/components/{EditableImage.jsx → EditableImage.tsx} +27 -24
  25. data/app/javascript/components/{FileUploadButton.jsx → FileUploadButton.tsx} +15 -16
  26. data/app/javascript/components/ImageCropper/FocalPoint.tsx +94 -0
  27. data/app/javascript/components/ImageCropper/{Image.jsx → Image.tsx} +13 -14
  28. data/app/javascript/components/ImageCropper/{Toolbar.jsx → Toolbar.tsx} +16 -12
  29. data/app/javascript/components/ImageCropper/{useCrop.js → useCrop.ts} +80 -37
  30. data/app/javascript/components/{ImageCropper.jsx → ImageCropper.tsx} +17 -15
  31. data/app/javascript/components/ImageEditor/{Form.jsx → Form.tsx} +24 -23
  32. data/app/javascript/components/{ImageEditor.jsx → ImageEditor.tsx} +17 -15
  33. data/app/javascript/components/ImageGrid/{DragElement.jsx → DragElement.tsx} +12 -10
  34. data/app/javascript/components/ImageGrid/{GridImage.jsx → GridImage.tsx} +40 -30
  35. data/app/javascript/components/ImageGrid/{Placeholder.jsx → Placeholder.tsx} +5 -6
  36. data/app/javascript/components/ImageGrid.jsx +3 -4
  37. data/app/javascript/components/{ImageUploader.jsx → ImageUploader.tsx} +46 -41
  38. data/app/javascript/components/Modal.tsx +48 -0
  39. data/app/javascript/components/PageImages.tsx +28 -0
  40. data/app/javascript/components/{PageTreeDraggable.jsx → PageTree/Draggable.tsx} +79 -57
  41. data/app/javascript/components/{PageTreeNode.jsx → PageTree/Node.tsx} +79 -70
  42. data/app/javascript/components/PageTree/types.ts +15 -0
  43. data/app/javascript/components/PageTree.tsx +206 -0
  44. data/app/javascript/components/RichTextToolbarButton.tsx +17 -0
  45. data/app/javascript/components/TagEditor/{AddTagForm.jsx → AddTagForm.tsx} +9 -10
  46. data/app/javascript/components/TagEditor/{Tag.jsx → Tag.tsx} +8 -9
  47. data/app/javascript/components/{TagEditor.jsx → TagEditor.tsx} +12 -13
  48. data/app/javascript/components/Toast.tsx +61 -0
  49. data/app/javascript/components/drag/{draggedOrder.js → draggedOrder.ts} +22 -12
  50. data/app/javascript/components/drag/types.ts +28 -0
  51. data/app/javascript/components/drag/{useDragCollection.js → useDragCollection.ts} +40 -22
  52. data/app/javascript/components/drag/{useDragUploader.js → useDragUploader.ts} +34 -25
  53. data/app/javascript/components/drag/useDraggable.ts +21 -0
  54. data/app/javascript/components/{drag.js → drag.ts} +1 -0
  55. data/app/javascript/controllers/{EditPageController.js → EditPageController.ts} +3 -1
  56. data/app/javascript/controllers/{LoginController.js → LoginController.ts} +7 -3
  57. data/app/javascript/controllers/{MainController.js → MainController.ts} +19 -14
  58. data/app/javascript/features/{RichText.jsx → RichText.tsx} +3 -3
  59. data/app/javascript/{index.js → index.ts} +8 -7
  60. data/app/javascript/lib/{Tree.js → Tree.ts} +106 -85
  61. data/app/javascript/lib/{copyToClipboard.js → copyToClipboard.ts} +1 -1
  62. data/app/javascript/lib/{readyHandler.js → readyHandler.ts} +4 -2
  63. data/app/javascript/lib/{request.js → request.ts} +11 -5
  64. data/app/javascript/stores/useModalStore.ts +15 -0
  65. data/app/javascript/stores/useToastStore.ts +26 -0
  66. data/app/javascript/stores.ts +2 -0
  67. data/app/javascript/types.ts +30 -0
  68. data/app/policies/page_policy.rb +4 -0
  69. data/app/views/admin/calendars/_sidebar.html.erb +3 -0
  70. data/app/views/admin/news/_sidebar.html.erb +3 -0
  71. data/app/views/admin/pages/_list_item.html.erb +4 -22
  72. data/app/views/admin/pages/_search_bar.html.erb +12 -0
  73. data/app/views/admin/pages/index.html.erb +3 -0
  74. data/app/views/admin/pages/search.html.erb +54 -0
  75. data/app/views/feeds/pages.rss.builder +3 -9
  76. data/config/routes.rb +1 -0
  77. data/lib/pages_core/configuration/pages.rb +0 -1
  78. data/lib/rails/generators/pages_core/frontend/frontend_generator.rb +33 -17
  79. data/lib/rails/generators/pages_core/frontend/templates/application.html.erb +0 -1
  80. data/lib/rails/generators/pages_core/frontend/templates/javascript/lib/gridOverlay.ts +40 -0
  81. data/lib/rails/generators/pages_core/frontend/templates/javascript/lib/responsiveEmbeds.ts +68 -0
  82. data/lib/rails/generators/pages_core/frontend/templates/postcss.config.js +17 -0
  83. data/lib/rails/generators/pages_core/frontend/templates/stylesheets/application.postcss.css +4 -0
  84. data/lib/rails/generators/pages_core/frontend/templates/stylesheets/components/base.css +24 -0
  85. data/lib/rails/generators/pages_core/frontend/templates/stylesheets/components/layout.css +21 -0
  86. data/lib/rails/generators/pages_core/frontend/templates/stylesheets/config.css +5 -0
  87. data/lib/rails/generators/pages_core/frontend/templates/stylesheets/global/animation.css +5 -0
  88. data/lib/rails/generators/pages_core/frontend/templates/stylesheets/global/colors.css +18 -0
  89. data/lib/rails/generators/pages_core/frontend/templates/stylesheets/global/fonts.css +6 -0
  90. data/lib/rails/generators/pages_core/frontend/templates/stylesheets/global/grid.css +65 -0
  91. data/lib/rails/generators/pages_core/frontend/templates/stylesheets/global/typography.css +131 -0
  92. data/lib/rails/generators/pages_core/install/templates/pages_initializer.rb +0 -3
  93. metadata +69 -63
  94. data/app/javascript/admin-dist.js +0 -2
  95. data/app/javascript/components/ImageCropper/FocalPoint.jsx +0 -93
  96. data/app/javascript/components/Modal.jsx +0 -59
  97. data/app/javascript/components/PageImages.jsx +0 -25
  98. data/app/javascript/components/PageTree.jsx +0 -196
  99. data/app/javascript/components/RichTextToolbarButton.jsx +0 -20
  100. data/app/javascript/components/Toast.jsx +0 -72
  101. data/app/javascript/components/drag/useDraggable.js +0 -17
  102. data/app/javascript/stores/ModalStore.jsx +0 -12
  103. data/app/javascript/stores/ToastStore.jsx +0 -14
  104. data/app/javascript/stores.js +0 -2
  105. data/lib/rails/generators/pages_core/frontend/templates/javascript/lib/GridOverlay.js +0 -66
  106. data/lib/rails/generators/pages_core/frontend/templates/javascript/lib/ResponsiveEmbeds.js +0 -72
  107. data/lib/rails/generators/pages_core/frontend/templates/stylesheets/application.sass.scss +0 -15
  108. data/lib/rails/generators/pages_core/frontend/templates/stylesheets/components/base.scss +0 -12
  109. data/lib/rails/generators/pages_core/frontend/templates/stylesheets/config.scss +0 -26
  110. data/lib/rails/generators/pages_core/frontend/templates/stylesheets/framework/breakpoints.scss +0 -42
  111. data/lib/rails/generators/pages_core/frontend/templates/stylesheets/framework/clearfix.scss +0 -7
  112. data/lib/rails/generators/pages_core/frontend/templates/stylesheets/framework/fonts.scss +0 -32
  113. data/lib/rails/generators/pages_core/frontend/templates/stylesheets/framework/grid.scss +0 -168
  114. data/lib/rails/generators/pages_core/frontend/templates/stylesheets/framework/grid_overlay.scss +0 -44
  115. data/lib/rails/generators/pages_core/frontend/templates/stylesheets/global/colors.scss +0 -8
  116. data/lib/rails/generators/pages_core/frontend/templates/stylesheets/global/typography.scss +0 -90
  117. data/lib/rails/generators/pages_core/frontend/templates/stylesheets/vendor/normalize.css +0 -349
  118. /data/app/javascript/components/Attachments/{Placeholder.jsx → Placeholder.tsx} +0 -0
  119. /data/app/javascript/components/ImageGrid/{FilePlaceholder.jsx → FilePlaceholder.tsx} +0 -0
  120. /data/app/javascript/{components.js → components.ts} +0 -0
  121. /data/app/javascript/{hooks.js → hooks.ts} +0 -0
@@ -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
- };
@@ -1,17 +1,41 @@
1
1
  import React, { useEffect, useState } from "react";
2
- import PropTypes from "prop-types";
3
2
  import copyToClipboard from "../../lib/copyToClipboard";
4
3
  import EditableImage from "../EditableImage";
5
- import ToastStore from "../../stores/ToastStore";
4
+ import useToastStore from "../../stores/useToastStore";
5
+ import { ImageResource, Locale } from "../../types";
6
6
  import Placeholder from "./Placeholder";
7
7
 
8
8
  import { useDraggable } from "../drag";
9
9
 
10
- export default function GridImage(props) {
10
+ interface Record {
11
+ id: number | null,
12
+ image: ImageResource,
13
+ src: string | null,
14
+ file: File | null
15
+ }
16
+
17
+ interface GridImageProps {
18
+ draggable: { handle: string, record: Record }
19
+ attributeName: string,
20
+ locale: string,
21
+ locales: { [index: string]: Locale },
22
+ placeholder: boolean,
23
+ enablePrimary: boolean,
24
+ showEmbed: boolean,
25
+ primary: boolean,
26
+ position: number,
27
+ deleteImage: () => void,
28
+ startDrag: (evt: Event, draggable: Draggable) => void,
29
+ onUpdate: (newImage: ImageResource, src: string) => void
30
+ }
31
+
32
+ export default function GridImage(props: GridImageProps) {
11
33
  const { attributeName, draggable } = props;
12
34
  const record = draggable.record;
13
35
  const image = record.image;
14
36
 
37
+ const notice = useToastStore((state) => state.notice);
38
+
15
39
  const [src, setSrc] = useState(record.src || null);
16
40
 
17
41
  const dragAttrs = useDraggable(draggable, props.startDrag);
@@ -24,22 +48,20 @@ export default function GridImage(props) {
24
48
  }
25
49
  }, []);
26
50
 
27
- const copyEmbed = (evt) => {
51
+ const copyEmbed = (evt: Event) => {
28
52
  evt.preventDefault();
29
53
  copyToClipboard(`[image:${image.id}]`);
30
- ToastStore.dispatch({
31
- type: "NOTICE", message: "Embed code copied to clipboard"
32
- });
54
+ notice("Embed code copied to clipboard");
33
55
  };
34
56
 
35
- const deleteImage = (evt) => {
57
+ const deleteImage = (evt: Event) => {
36
58
  evt.preventDefault();
37
59
  if (props.deleteImage) {
38
60
  props.deleteImage();
39
61
  }
40
62
  };
41
63
 
42
- let classes = ["grid-image"];
64
+ const classes = ["grid-image"];
43
65
  if (props.placeholder) {
44
66
  classes.push("placeholder");
45
67
  }
@@ -64,13 +86,15 @@ export default function GridImage(props) {
64
86
  <Placeholder src={src} />}
65
87
  {image &&
66
88
  <>
67
- <EditableImage image={image}
68
- src={src || image.thumbnail_url}
69
- width={250}
70
- caption={true}
71
- locale={props.locale}
72
- locales={props.locales}
73
- onUpdate={props.onUpdate} />
89
+ <EditableImage
90
+ image={image}
91
+ key={props.placeholder ? "placeholder" : draggable.handle}
92
+ src={src || image.thumbnail_url}
93
+ width={250}
94
+ caption={true}
95
+ locale={props.locale}
96
+ locales={props.locales}
97
+ onUpdate={props.onUpdate} />
74
98
  <div className="actions">
75
99
  {props.showEmbed && (
76
100
  <button onClick={copyEmbed}>
@@ -87,17 +111,3 @@ export default function GridImage(props) {
87
111
  </div>
88
112
  );
89
113
  }
90
- GridImage.propTypes = {
91
- draggable: PropTypes.object,
92
- deleteImage: PropTypes.func,
93
- startDrag: PropTypes.func,
94
- locale: PropTypes.string,
95
- locales: PropTypes.object,
96
- onUpdate: PropTypes.func,
97
- attributeName: PropTypes.string,
98
- placeholder: PropTypes.bool,
99
- enablePrimary: PropTypes.bool,
100
- showEmbed: PropTypes.bool,
101
- primary: PropTypes.bool,
102
- position: PropTypes.number,
103
- };
@@ -1,7 +1,10 @@
1
1
  import React from "react";
2
- import PropTypes from "prop-types";
3
2
 
4
- export default function Placeholder(props) {
3
+ interface PlaceholderProps {
4
+ src: string
5
+ }
6
+
7
+ export default function Placeholder(props: PlaceholderProps) {
5
8
  if (props.src) {
6
9
  return (
7
10
  <div className="temp-image">
@@ -17,7 +20,3 @@ export default function Placeholder(props) {
17
20
  );
18
21
  }
19
22
  }
20
-
21
- Placeholder.propTypes = {
22
- src: PropTypes.string
23
- };
@@ -4,7 +4,7 @@ import FileUploadButton from "./FileUploadButton";
4
4
  import DragElement from "./ImageGrid/DragElement";
5
5
  import FilePlaceholder from "./ImageGrid/FilePlaceholder";
6
6
  import GridImage from "./ImageGrid/GridImage";
7
- import ToastStore from "../stores/ToastStore";
7
+ import useToastStore from "../stores/useToastStore";
8
8
  import { post } from "../lib/request";
9
9
 
10
10
  import { createDraggable,
@@ -49,6 +49,7 @@ export default function ImageGrid(props) {
49
49
  const primary = useDragCollection(initPrimary);
50
50
  const images = useDragCollection(initImages);
51
51
  const [deleted, setDeleted] = useState([]);
52
+ const error = useToastStore((state) => state.error);
52
53
 
53
54
  const containerRef = useRef();
54
55
 
@@ -99,9 +100,7 @@ export default function ImageGrid(props) {
99
100
  post("/admin/images.json", data)
100
101
  .then(json => {
101
102
  if (json.status === "error") {
102
- ToastStore.dispatch({
103
- type: "ERROR", message: "Error uploading image: " + json.error
104
- });
103
+ error("Error uploading image: " + json.error);
105
104
  dispatchAll({ type: "remove", payload: draggable });
106
105
  } else {
107
106
  dispatchAll({