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,28 +1,48 @@
1
1
  import React from "react";
2
- import PropTypes from "prop-types";
3
2
  import copyToClipboard from "../../lib/copyToClipboard";
4
3
  import AttachmentEditor from "./AttachmentEditor";
5
- import ModalStore from "../../stores/ModalStore";
6
- import ToastStore from "../../stores/ToastStore";
4
+ import useModalStore from "../../stores/useModalStore";
5
+ import useToastStore from "../../stores/useToastStore";
6
+ import { AttachmentResource, Locale } from "../../types";
7
7
 
8
- import { useDraggable } from "../drag";
8
+ import { useDraggable, Draggable } from "../drag";
9
9
 
10
- export default function Attachment(props) {
10
+ interface Record {
11
+ id: number | null,
12
+ attachment: AttachmentResource,
13
+ uploading: boolean
14
+ }
15
+
16
+ interface AttachmentProps {
17
+ attributeName: string,
18
+ placeholder: boolean,
19
+ draggable: { record: Record },
20
+ locale: string,
21
+ locales: { [index: string]: Locale },
22
+ deleteRecord: () => void,
23
+ showEmbed: boolean,
24
+ position: number,
25
+ onUpdate: (localizations: Record<string, Record<string, string>>) => void,
26
+ startDrag: (evt: Event, draggable: Draggable) => void
27
+ }
28
+
29
+ export default function Attachment(props: AttachmentProps) {
11
30
  const { attributeName, draggable, locales, locale } = props;
12
31
  const { record } = draggable;
13
32
  const { attachment, uploading } = record;
14
33
 
34
+ const openModal = useModalStore((state) => state.open);
35
+ const notice = useToastStore((state) => state.notice);
36
+
15
37
  const listeners = useDraggable(draggable, props.startDrag);
16
38
 
17
- const copyEmbed = (evt) => {
39
+ const copyEmbed = (evt: Event) => {
18
40
  evt.preventDefault();
19
41
  copyToClipboard(`[attachment:${attachment.id}]`);
20
- ToastStore.dispatch({
21
- type: "NOTICE", message: "Embed code copied to clipboard"
22
- });
42
+ notice("Embed code copied to clipboard");
23
43
  };
24
44
 
25
- const deleteRecord = (evt) => {
45
+ const deleteRecord = (evt: Event) => {
26
46
  evt.preventDefault();
27
47
  if (props.deleteRecord) {
28
48
  props.deleteRecord();
@@ -43,15 +63,15 @@ export default function Attachment(props) {
43
63
  return null;
44
64
  };
45
65
 
46
- const editAttachment = (evt) => {
66
+ const editAttachment = (evt: Event) => {
47
67
  evt.preventDefault();
48
- ModalStore.dispatch({
49
- type: "OPEN",
50
- payload: <AttachmentEditor attachment={attachment}
51
- locale={locale}
52
- locales={locales}
53
- onUpdate={props.onUpdate} />
54
- });
68
+ openModal(
69
+ <AttachmentEditor
70
+ attachment={attachment}
71
+ locale={locale}
72
+ locales={locales}
73
+ onUpdate={props.onUpdate} />
74
+ );
55
75
  };
56
76
 
57
77
  const classes = ["attachment"];
@@ -64,7 +84,10 @@ export default function Attachment(props) {
64
84
 
65
85
  const icon = uploading ? "cloud-arrow-up" : "paperclip";
66
86
 
67
- const localeDir = (locales && locales[locale] && locales[locale].dir) || "ltr";
87
+ let localeDir = "ltr";
88
+ if (locale in locales && locales[locale].dir) {
89
+ localeDir = locales[locale].dir;
90
+ }
68
91
 
69
92
  return (
70
93
  <div className={classes.join(" ")}
@@ -108,17 +131,3 @@ export default function Attachment(props) {
108
131
  </div>
109
132
  );
110
133
  }
111
-
112
- Attachment.propTypes = {
113
- locale: PropTypes.string,
114
- locales: PropTypes.object,
115
- draggable: PropTypes.object,
116
- deleteRecord: PropTypes.func,
117
- startDrag: PropTypes.func,
118
- showEmbed: PropTypes.bool,
119
- onUpdate: PropTypes.func,
120
- attributeName: PropTypes.string,
121
- placeholder: PropTypes.bool,
122
- position: PropTypes.number,
123
- ref: PropTypes.object
124
- };
@@ -1,11 +1,18 @@
1
- import React, { useState } from "react";
2
- import PropTypes from "prop-types";
1
+ import React, { ChangeEvent, useState } from "react";
3
2
  import copyToClipboard, { copySupported } from "../../lib/copyToClipboard";
4
- import ModalStore from "../../stores/ModalStore";
5
- import ToastStore from "../../stores/ToastStore";
3
+ import useModalStore from "../../stores/useModalStore";
4
+ import useToastStore from "../../stores/useToastStore";
5
+ import { AttachmentResource, Locale } from "../../types";
6
6
  import { putJson } from "../../lib/request";
7
7
 
8
- export default function AttachmentEditor(props) {
8
+ interface AttachmentEditorProps {
9
+ attachment: AttachmentResource,
10
+ locale: string,
11
+ locales: { [index: string]: Locale },
12
+ onUpdate: (localizations: Record<string, Record<string, string>>) => void
13
+ }
14
+
15
+ export default function AttachmentEditor(props: AttachmentEditorProps) {
9
16
  const { attachment, locales } = props;
10
17
 
11
18
  const [locale, setLocale] = useState(props.locale);
@@ -14,7 +21,10 @@ export default function AttachmentEditor(props) {
14
21
  description: attachment.description || {},
15
22
  });
16
23
 
17
- const updateLocalization = (name) => (evt) => {
24
+ const notice = useToastStore((state) => state.notice);
25
+ const closeModal = useModalStore((state) => state.close);
26
+
27
+ const updateLocalization = (name: "name" | "description") => (evt: ChangeEvent<HTMLInputElement>) => {
18
28
  setLocalizations({
19
29
  ...localizations,
20
30
  [name]: { ...localizations[name],
@@ -22,27 +32,24 @@ export default function AttachmentEditor(props) {
22
32
  });
23
33
  };
24
34
 
25
- const copyEmbedCode = (evt) => {
35
+ const copyEmbedCode = (evt: Event) => {
26
36
  evt.preventDefault();
27
37
  copyToClipboard(`[attachment:${attachment.id}]`);
28
- ToastStore.dispatch({
29
- type: "NOTICE", message: "Embed code copied to clipboard"
30
- });
38
+ notice("Embed code copied to clipboard");
31
39
  };
32
40
 
33
- const save = (evt) => {
41
+ const save = (evt: Event) => {
34
42
  evt.preventDefault();
35
43
  evt.stopPropagation();
36
44
 
37
- let data = { ...localizations };
45
+ const data = { ...localizations };
38
46
 
39
- putJson(`/admin/attachments/${attachment.id}`,
40
- { attachment: data });
47
+ void putJson(`/admin/attachments/${attachment.id}`, { attachment: data });
41
48
 
42
49
  if (props.onUpdate) {
43
50
  props.onUpdate(data);
44
51
  }
45
- ModalStore.dispatch({ type: "CLOSE" });
52
+ closeModal();
46
53
  };
47
54
 
48
55
  const inputDir = (locales && locales[locale] && locales[locale].dir) || "ltr";
@@ -106,7 +113,7 @@ export default function AttachmentEditor(props) {
106
113
  <button onClick={save}>
107
114
  Save
108
115
  </button>
109
- <button onClick={() => ModalStore.dispatch({ type: "CLOSE" })}>
116
+ <button onClick={closeModal}>
110
117
  Cancel
111
118
  </button>
112
119
  </div>
@@ -114,10 +121,3 @@ export default function AttachmentEditor(props) {
114
121
  </div>
115
122
  );
116
123
  }
117
-
118
- AttachmentEditor.propTypes = {
119
- attachment: PropTypes.object,
120
- locale: PropTypes.string,
121
- locales: PropTypes.object,
122
- onUpdate: PropTypes.func
123
- };
@@ -1,20 +1,33 @@
1
1
  import React, { useState } from "react";
2
- import PropTypes from "prop-types";
3
2
  import ImageEditor from "./ImageEditor";
4
- import ModalStore from "../stores/ModalStore";
3
+ import useModalStore from "../stores/useModalStore";
5
4
 
6
- export default function EditableImage(props) {
5
+ import { Locale, ImageResource } from "../types";
6
+
7
+ interface EditableImageProps {
8
+ image: ImageResource,
9
+ src: string,
10
+ caption: boolean,
11
+ locale: string,
12
+ locales: Record<string, Locale>,
13
+ width: number,
14
+ onUpdate?: (newImage: ImageResource, src: string) => void
15
+ }
16
+
17
+ export default function EditableImage(props: EditableImageProps) {
7
18
  const [image, setImage] = useState(props.image);
8
19
  const [src, setSrc] = useState(props.src);
9
20
 
21
+ const openModal = useModalStore((state) => state.open);
22
+
10
23
  const height = () => {
11
24
  const width = image.crop_width || image.real_width;
12
25
  const height = image.crop_height || image.real_height;
13
26
  return Math.round((height / width) * props.width);
14
27
  };
15
28
 
16
- const updateImage = (updatedImage, src) => {
17
- let newImage = { ...image, ...updatedImage };
29
+ const updateImage = (updatedImage: ImageResource, src: string) => {
30
+ const newImage = { ...image, ...updatedImage };
18
31
  setSrc(src);
19
32
  setImage(newImage);
20
33
  if (props.onUpdate) {
@@ -22,16 +35,16 @@ export default function EditableImage(props) {
22
35
  }
23
36
  };
24
37
 
25
- const handleClick = (evt) => {
38
+ const handleClick = (evt: Event) => {
26
39
  evt.preventDefault();
27
- ModalStore.dispatch({
28
- type: "OPEN",
29
- payload: <ImageEditor image={image}
30
- caption={props.caption}
31
- locale={props.locale}
32
- locales={props.locales}
33
- onUpdate={updateImage} />
34
- });
40
+ openModal(
41
+ <ImageEditor
42
+ image={image}
43
+ caption={props.caption}
44
+ locale={props.locale}
45
+ locales={props.locales}
46
+ onUpdate={updateImage} />
47
+ );
35
48
  };
36
49
 
37
50
  const altWarning = !image.alternative[props.locale];
@@ -49,13 +62,3 @@ export default function EditableImage(props) {
49
62
  </div>
50
63
  );
51
64
  }
52
-
53
- EditableImage.propTypes = {
54
- image: PropTypes.object,
55
- src: PropTypes.string,
56
- caption: PropTypes.bool,
57
- locale: PropTypes.string,
58
- locales: PropTypes.object,
59
- width: PropTypes.number,
60
- onUpdate: PropTypes.func
61
- };
@@ -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
  };
@@ -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
- };