pages_core 3.12.0 → 3.12.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (199) hide show
  1. checksums.yaml +4 -4
  2. data/VERSION +1 -1
  3. data/app/assets/builds/fonts/661557ef.ttf +0 -0
  4. data/app/assets/builds/fonts/a18fc2d2.woff2 +0 -0
  5. data/app/assets/builds/fonts/b2c7b78f.woff2 +0 -0
  6. data/app/assets/builds/fonts/ceddc204.ttf +0 -0
  7. data/app/assets/builds/pages_core/admin-dist.js +60 -14
  8. data/app/assets/builds/pages_core/admin-dist.js.map +7 -0
  9. data/app/assets/builds/pages_core/admin.css +9272 -0
  10. data/app/assets/images/pages/admin/angle-down-solid.svg +1 -0
  11. data/app/assets/images/pages/admin/icon.svg +1 -0
  12. data/app/assets/stylesheets/pages_core/admin/components/archive.css +6 -0
  13. data/app/assets/stylesheets/{pages/admin/components/attachments.scss → pages_core/admin/components/attachments.css} +35 -28
  14. data/app/assets/stylesheets/{pages/admin.scss → pages_core/admin/components/base.css} +125 -123
  15. data/app/assets/stylesheets/pages_core/admin/components/forms.css +223 -0
  16. data/app/assets/stylesheets/{pages/admin/components/header.scss → pages_core/admin/components/header.css} +76 -46
  17. data/app/assets/stylesheets/{pages/admin/components/image_editor.scss → pages_core/admin/components/image_editor.css} +42 -31
  18. data/app/assets/stylesheets/{pages/admin/components/image_grid.scss → pages_core/admin/components/image_grid.css} +76 -64
  19. data/app/assets/stylesheets/{pages/admin/components/image_uploader.scss → pages_core/admin/components/image_uploader.css} +12 -12
  20. data/app/assets/stylesheets/{pages/admin/components/layout.scss → pages_core/admin/components/layout.css} +13 -9
  21. data/app/assets/stylesheets/pages_core/admin/components/links.css +40 -0
  22. data/app/assets/stylesheets/pages_core/admin/components/list_table.css +66 -0
  23. data/app/assets/stylesheets/{pages/admin/components/login.scss → pages_core/admin/components/login.css} +6 -5
  24. data/app/assets/stylesheets/{pages/admin/components/modal.scss → pages_core/admin/components/modal.css} +10 -12
  25. data/app/assets/stylesheets/{pages/admin/components/page_tree.scss → pages_core/admin/components/page_tree.css} +54 -55
  26. data/app/assets/stylesheets/{pages/admin/components/pagination.scss → pages_core/admin/components/pagination.css} +17 -17
  27. data/app/assets/stylesheets/pages_core/admin/components/search.css +27 -0
  28. data/app/assets/stylesheets/{pages/admin/components/sidebar.scss → pages_core/admin/components/sidebar.css} +8 -7
  29. data/app/assets/stylesheets/{pages/admin/components/tag_editor.scss → pages_core/admin/components/tag_editor.css} +10 -15
  30. data/app/assets/stylesheets/{pages/admin/components/textarea.scss → pages_core/admin/components/textarea.css} +1 -1
  31. data/app/assets/stylesheets/{pages/admin/components/toast.scss → pages_core/admin/components/toast.css} +5 -3
  32. data/app/assets/stylesheets/{pages/admin/components/toolbar.scss → pages_core/admin/components/toolbar.css} +56 -29
  33. data/app/assets/stylesheets/{pages/admin/controllers/pages.scss → pages_core/admin/controllers/pages.css} +69 -52
  34. data/app/assets/stylesheets/pages_core/admin/controllers/users.css +3 -0
  35. data/app/assets/stylesheets/pages_core/admin/vars.css +34 -0
  36. data/app/assets/stylesheets/pages_core/admin.postcss.css +9 -0
  37. data/app/controllers/admin/pages_controller.rb +12 -11
  38. data/app/controllers/concerns/pages_core/rss_controller.rb +17 -1
  39. data/app/controllers/pages_core/admin_controller.rb +6 -0
  40. data/app/controllers/pages_core/frontend/pages_controller.rb +9 -5
  41. data/app/controllers/pages_core/sitemaps_controller.rb +3 -5
  42. data/app/formatters/pages_core/image_embedder.rb +5 -27
  43. data/app/helpers/admin/calendars_helper.rb +8 -0
  44. data/app/helpers/admin/news_helper.rb +13 -0
  45. data/app/helpers/admin/pages_helper.rb +32 -0
  46. data/app/helpers/pages_core/admin/admin_helper.rb +11 -54
  47. data/app/helpers/pages_core/admin/deprecated_admin_helper.rb +40 -0
  48. data/app/helpers/pages_core/images_helper.rb +37 -0
  49. data/app/javascript/admin-dist.ts +2 -0
  50. data/app/javascript/components/Attachments/{Attachment.jsx → Attachment.tsx} +44 -35
  51. data/app/javascript/components/Attachments/{AttachmentEditor.jsx → AttachmentEditor.tsx} +23 -23
  52. data/app/javascript/components/{EditableImage.jsx → EditableImage.tsx} +28 -25
  53. data/app/javascript/components/{FileUploadButton.jsx → FileUploadButton.tsx} +15 -16
  54. data/app/javascript/components/ImageCropper/FocalPoint.tsx +94 -0
  55. data/app/javascript/components/ImageCropper/{Image.jsx → Image.tsx} +13 -14
  56. data/app/javascript/components/ImageCropper/{Toolbar.jsx → Toolbar.tsx} +19 -15
  57. data/app/javascript/components/ImageCropper/{useCrop.js → useCrop.ts} +80 -37
  58. data/app/javascript/components/{ImageCropper.jsx → ImageCropper.tsx} +17 -15
  59. data/app/javascript/components/ImageEditor/{Form.jsx → Form.tsx} +24 -23
  60. data/app/javascript/components/{ImageEditor.jsx → ImageEditor.tsx} +17 -15
  61. data/app/javascript/components/ImageGrid/{DragElement.jsx → DragElement.tsx} +12 -10
  62. data/app/javascript/components/ImageGrid/{GridImage.jsx → GridImage.tsx} +40 -30
  63. data/app/javascript/components/ImageGrid/{Placeholder.jsx → Placeholder.tsx} +5 -6
  64. data/app/javascript/components/ImageGrid.jsx +3 -4
  65. data/app/javascript/components/{ImageUploader.jsx → ImageUploader.tsx} +46 -41
  66. data/app/javascript/components/Modal.tsx +48 -0
  67. data/app/javascript/components/PageImages.tsx +28 -0
  68. data/app/javascript/components/{PageTreeDraggable.jsx → PageTree/Draggable.tsx} +79 -57
  69. data/app/javascript/components/{PageTreeNode.jsx → PageTree/Node.tsx} +86 -77
  70. data/app/javascript/components/PageTree/types.ts +15 -0
  71. data/app/javascript/components/PageTree.tsx +206 -0
  72. data/app/javascript/components/RichTextToolbarButton.tsx +17 -0
  73. data/app/javascript/components/TagEditor/{AddTagForm.jsx → AddTagForm.tsx} +9 -10
  74. data/app/javascript/components/TagEditor/{Tag.jsx → Tag.tsx} +8 -9
  75. data/app/javascript/components/{TagEditor.jsx → TagEditor.tsx} +12 -13
  76. data/app/javascript/components/Toast.tsx +61 -0
  77. data/app/javascript/components/drag/{draggedOrder.js → draggedOrder.ts} +22 -12
  78. data/app/javascript/components/drag/types.ts +28 -0
  79. data/app/javascript/components/drag/{useDragCollection.js → useDragCollection.ts} +40 -22
  80. data/app/javascript/components/drag/{useDragUploader.js → useDragUploader.ts} +34 -25
  81. data/app/javascript/components/drag/useDraggable.ts +21 -0
  82. data/app/javascript/components/{drag.js → drag.ts} +1 -0
  83. data/app/javascript/controllers/{EditPageController.js → EditPageController.ts} +3 -1
  84. data/app/javascript/controllers/{LoginController.js → LoginController.ts} +7 -3
  85. data/app/javascript/controllers/{MainController.js → MainController.ts} +19 -14
  86. data/app/javascript/{index.js → index.ts} +8 -7
  87. data/app/javascript/lib/{Tree.js → Tree.ts} +106 -85
  88. data/app/javascript/lib/{copyToClipboard.js → copyToClipboard.ts} +1 -1
  89. data/app/javascript/lib/{readyHandler.js → readyHandler.ts} +4 -2
  90. data/app/javascript/lib/{request.js → request.ts} +11 -5
  91. data/app/javascript/stores/useModalStore.ts +15 -0
  92. data/app/javascript/stores/useToastStore.ts +26 -0
  93. data/app/javascript/stores.ts +2 -0
  94. data/app/javascript/types.ts +30 -0
  95. data/app/mailers/admin_mailer.rb +1 -0
  96. data/app/models/invite.rb +8 -0
  97. data/app/policies/page_policy.rb +4 -0
  98. data/app/views/admin/calendars/_sidebar.html.erb +50 -0
  99. data/app/views/admin/calendars/show.html.erb +15 -53
  100. data/app/views/admin/invites/new.html.erb +2 -8
  101. data/app/views/admin/invites/show.html.erb +2 -4
  102. data/app/views/admin/news/_sidebar.html.erb +51 -0
  103. data/app/views/admin/news/index.html.erb +21 -56
  104. data/app/views/admin/pages/_list_item.html.erb +4 -22
  105. data/app/views/admin/pages/_search_bar.html.erb +12 -0
  106. data/app/views/admin/pages/deleted.html.erb +10 -8
  107. data/app/views/admin/pages/edit.html.erb +20 -11
  108. data/app/views/admin/pages/index.html.erb +10 -8
  109. data/app/views/admin/pages/new.html.erb +10 -14
  110. data/app/views/admin/pages/search.html.erb +54 -0
  111. data/app/views/admin/password_resets/show.html.erb +3 -5
  112. data/app/views/admin/users/deactivated.html.erb +6 -7
  113. data/app/views/admin/users/edit.html.erb +7 -9
  114. data/app/views/admin/users/index.html.erb +3 -6
  115. data/app/views/admin/users/login.html.erb +4 -5
  116. data/app/views/admin/users/new.html.erb +2 -4
  117. data/app/views/admin/users/new_password.html.erb +4 -5
  118. data/app/views/admin/users/show.html.erb +11 -9
  119. data/app/views/errors/401.html.erb +2 -1
  120. data/app/views/errors/403.html.erb +2 -1
  121. data/app/views/errors/404.html.erb +1 -3
  122. data/app/views/errors/405.html.erb +2 -1
  123. data/app/views/errors/422.html.erb +2 -1
  124. data/app/views/errors/500.html.erb +2 -3
  125. data/app/views/feeds/pages.rss.builder +3 -9
  126. data/app/views/layouts/admin/_header.html.erb +1 -2
  127. data/app/views/layouts/admin/_page_header.html.erb +4 -4
  128. data/app/views/layouts/admin.html.erb +3 -3
  129. data/app/views/layouts/errors.html.erb +127 -4
  130. data/config/routes.rb +1 -0
  131. data/lib/pages_core/configuration/pages.rb +0 -1
  132. data/lib/pages_core/engine.rb +4 -3
  133. data/lib/pages_core.rb +0 -1
  134. data/lib/rails/generators/pages_core/frontend/frontend_generator.rb +33 -17
  135. data/lib/rails/generators/pages_core/frontend/templates/application.html.erb +0 -1
  136. data/lib/rails/generators/pages_core/frontend/templates/javascript/lib/gridOverlay.ts +40 -0
  137. data/lib/rails/generators/pages_core/frontend/templates/javascript/lib/responsiveEmbeds.ts +68 -0
  138. data/lib/rails/generators/pages_core/frontend/templates/postcss.config.js +17 -0
  139. data/lib/rails/generators/pages_core/frontend/templates/stylesheets/application.postcss.css +4 -0
  140. data/lib/rails/generators/pages_core/frontend/templates/stylesheets/components/base.css +24 -0
  141. data/lib/rails/generators/pages_core/frontend/templates/stylesheets/components/layout.css +21 -0
  142. data/lib/rails/generators/pages_core/frontend/templates/stylesheets/config.css +5 -0
  143. data/lib/rails/generators/pages_core/frontend/templates/stylesheets/global/animation.css +5 -0
  144. data/lib/rails/generators/pages_core/frontend/templates/stylesheets/global/colors.css +18 -0
  145. data/lib/rails/generators/pages_core/frontend/templates/stylesheets/global/fonts.css +6 -0
  146. data/lib/rails/generators/pages_core/frontend/templates/stylesheets/global/grid.css +65 -0
  147. data/lib/rails/generators/pages_core/frontend/templates/stylesheets/global/typography.css +131 -0
  148. data/lib/rails/generators/pages_core/install/templates/pages_initializer.rb +0 -3
  149. metadata +104 -255
  150. data/app/assets/images/pages/admin/icon.png +0 -0
  151. data/app/assets/images/pages/admin/image-editor-bg.png +0 -0
  152. data/app/assets/images/pages/admin/list-table-pin-blue.gif +0 -0
  153. data/app/assets/images/pages/admin/list-table-pin-disabled.gif +0 -0
  154. data/app/assets/images/pages/admin/list-table-pin-green.gif +0 -0
  155. data/app/assets/images/pages/admin/list-table-pin-red.gif +0 -0
  156. data/app/assets/images/pages/admin/list-table-pin-yellow.gif +0 -0
  157. data/app/assets/images/pages/admin/loading-modal.gif +0 -0
  158. data/app/assets/images/pages/feed-icon-14x14.png +0 -0
  159. data/app/assets/stylesheets/pages/admin/components/archive.scss +0 -6
  160. data/app/assets/stylesheets/pages/admin/components/buttons.scss +0 -23
  161. data/app/assets/stylesheets/pages/admin/components/forms.scss +0 -169
  162. data/app/assets/stylesheets/pages/admin/components/links.scss +0 -43
  163. data/app/assets/stylesheets/pages/admin/components/list_table.scss +0 -61
  164. data/app/assets/stylesheets/pages/admin/controllers/users.scss +0 -3
  165. data/app/assets/stylesheets/pages/admin/mixins/breakpoints.scss +0 -21
  166. data/app/assets/stylesheets/pages/admin/mixins/clearfix.scss +0 -7
  167. data/app/assets/stylesheets/pages/admin/mixins/gradients.scss +0 -7
  168. data/app/assets/stylesheets/pages/admin/vars.scss +0 -30
  169. data/app/assets/stylesheets/pages/errors.css +0 -128
  170. data/app/javascript/admin-dist.js +0 -2
  171. data/app/javascript/components/ImageCropper/FocalPoint.jsx +0 -93
  172. data/app/javascript/components/Modal.jsx +0 -59
  173. data/app/javascript/components/PageImages.jsx +0 -25
  174. data/app/javascript/components/PageTree.jsx +0 -196
  175. data/app/javascript/components/RichTextToolbarButton.jsx +0 -20
  176. data/app/javascript/components/Toast.jsx +0 -72
  177. data/app/javascript/components/drag/useDraggable.js +0 -17
  178. data/app/javascript/stores/ModalStore.jsx +0 -12
  179. data/app/javascript/stores/ToastStore.jsx +0 -14
  180. data/app/javascript/stores.js +0 -2
  181. data/lib/rails/generators/pages_core/frontend/templates/javascript/lib/GridOverlay.js +0 -66
  182. data/lib/rails/generators/pages_core/frontend/templates/javascript/lib/ResponsiveEmbeds.js +0 -72
  183. data/lib/rails/generators/pages_core/frontend/templates/stylesheets/application.sass.scss +0 -15
  184. data/lib/rails/generators/pages_core/frontend/templates/stylesheets/components/base.scss +0 -12
  185. data/lib/rails/generators/pages_core/frontend/templates/stylesheets/config.scss +0 -26
  186. data/lib/rails/generators/pages_core/frontend/templates/stylesheets/framework/breakpoints.scss +0 -42
  187. data/lib/rails/generators/pages_core/frontend/templates/stylesheets/framework/clearfix.scss +0 -7
  188. data/lib/rails/generators/pages_core/frontend/templates/stylesheets/framework/fonts.scss +0 -32
  189. data/lib/rails/generators/pages_core/frontend/templates/stylesheets/framework/grid.scss +0 -168
  190. data/lib/rails/generators/pages_core/frontend/templates/stylesheets/framework/grid_overlay.scss +0 -44
  191. data/lib/rails/generators/pages_core/frontend/templates/stylesheets/global/colors.scss +0 -8
  192. data/lib/rails/generators/pages_core/frontend/templates/stylesheets/global/typography.scss +0 -90
  193. data/lib/rails/generators/pages_core/frontend/templates/stylesheets/vendor/normalize.css +0 -349
  194. data/vendor/assets/stylesheets/ReactCrop.css +0 -167
  195. /data/app/assets/stylesheets/{pages/admin/components/tabs.scss → pages_core/admin/components/tabs.css} +0 -0
  196. /data/app/javascript/components/Attachments/{Placeholder.jsx → Placeholder.tsx} +0 -0
  197. /data/app/javascript/components/ImageGrid/{FilePlaceholder.jsx → FilePlaceholder.tsx} +0 -0
  198. /data/app/javascript/{components.js → components.ts} +0 -0
  199. /data/app/javascript/{hooks.js → hooks.ts} +0 -0
@@ -1,13 +1,19 @@
1
- import React, { useRef } from "react";
2
- import PropTypes from "prop-types";
1
+ import React, { ChangeEvent, useRef } from "react";
3
2
 
4
- export default function FileUploadButton(props) {
5
- const inputRef = useRef();
3
+ interface FileUploadButtonProps {
4
+ callback: (files: File[]) => void,
5
+ type: string,
6
+ multiple: boolean,
7
+ multiline: boolean
8
+ }
9
+
10
+ export default function FileUploadButton(props: FileUploadButtonProps) {
11
+ const inputRef = useRef<HTMLInputElement>();
6
12
 
7
- const handleChange = (evt) => {
8
- let fileList = evt.target.files;
9
- let files = [];
10
- for (var i = 0; i < fileList.length; i++) {
13
+ const handleChange = (evt: ChangeEvent<HTMLInputElement>) => {
14
+ const fileList = evt.target.files;
15
+ const files: File[] = [];
16
+ for (let i = 0; i < fileList.length; i++) {
11
17
  files.push(fileList[i]);
12
18
  }
13
19
  if (files.length > 0) {
@@ -15,7 +21,7 @@ export default function FileUploadButton(props) {
15
21
  }
16
22
  };
17
23
 
18
- const triggerDialog = (evt) => {
24
+ const triggerDialog = (evt: Event) => {
19
25
  evt.preventDefault();
20
26
  inputRef.current.click();
21
27
  };
@@ -38,10 +44,3 @@ export default function FileUploadButton(props) {
38
44
  </div>
39
45
  );
40
46
  }
41
-
42
- FileUploadButton.propTypes = {
43
- callback: PropTypes.func,
44
- type: PropTypes.string,
45
- multiple: PropTypes.bool,
46
- multiline: PropTypes.bool
47
- };
@@ -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
  };
@@ -30,12 +42,12 @@ export default function Toolbar(props) {
30
42
  <button title="Crop image"
31
43
  onClick={props.toggleCrop}
32
44
  className={cropping ? "active" : ""}>
33
- <i className="fa fa-crop" />
45
+ <i className="fa-solid fa-crop" />
34
46
  </button>
35
47
  <button disabled={cropping}
36
48
  title="Toggle focal point"
37
49
  onClick={props.toggleFocal}>
38
- <i className="fa fa-bullseye" />
50
+ <i className="fa-solid fa-bullseye" />
39
51
  </button>
40
52
  <a href={props.image.original_url}
41
53
  className="button"
@@ -43,7 +55,7 @@ export default function Toolbar(props) {
43
55
  disabled={cropping}
44
56
  download={props.image.filename}
45
57
  onClick={evt => cropping && evt.preventDefault()}>
46
- <i className="fa fa-download" />
58
+ <i className="fa-solid fa-download" />
47
59
  </a>
48
60
  </div>
49
61
  {cropping && (
@@ -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
- };