pages_core 3.12.4 → 3.12.5

Sign up to get free protection for your applications and to get access to all the features.
Files changed (108) hide show
  1. checksums.yaml +4 -4
  2. data/VERSION +1 -1
  3. data/app/assets/builds/pages_core/admin-dist.js +8 -43
  4. data/app/assets/builds/pages_core/admin-dist.js.map +4 -4
  5. data/app/assets/builds/pages_core/admin.css +264 -133
  6. data/app/assets/stylesheets/pages_core/admin/components/attachments.css +3 -4
  7. data/app/assets/stylesheets/pages_core/admin/components/forms.css +17 -16
  8. data/app/assets/stylesheets/pages_core/admin/components/image_editor.css +8 -4
  9. data/app/assets/stylesheets/pages_core/admin/components/image_grid.css +1 -1
  10. data/app/assets/stylesheets/pages_core/admin/components/list_table.css +11 -3
  11. data/app/assets/stylesheets/pages_core/admin/components/modal.css +9 -5
  12. data/app/assets/stylesheets/pages_core/admin/components/page_tree.css +5 -1
  13. data/app/assets/stylesheets/pages_core/admin/components/toast.css +2 -2
  14. data/app/assets/stylesheets/pages_core/admin/controllers/pages.css +4 -2
  15. data/app/assets/stylesheets/pages_core/admin/vars.css +2 -1
  16. data/app/controllers/admin/calendars_controller.rb +2 -2
  17. data/app/controllers/admin/categories_controller.rb +3 -3
  18. data/app/controllers/admin/news_controller.rb +6 -6
  19. data/app/controllers/admin/pages_controller.rb +12 -11
  20. data/app/controllers/admin/users_controller.rb +1 -1
  21. data/app/controllers/concerns/pages_core/preview_pages_controller.rb +15 -17
  22. data/app/controllers/pages_core/admin_controller.rb +2 -2
  23. data/app/controllers/pages_core/base_controller.rb +1 -8
  24. data/app/controllers/pages_core/frontend/pages_controller.rb +13 -5
  25. data/app/controllers/pages_core/frontend_controller.rb +12 -7
  26. data/app/helpers/admin/menu_helper.rb +2 -0
  27. data/app/helpers/admin/pages_helper.rb +1 -4
  28. data/app/helpers/pages_core/admin/admin_helper.rb +0 -1
  29. data/app/helpers/pages_core/admin/content_tabs_helper.rb +9 -2
  30. data/app/helpers/pages_core/application_helper.rb +2 -3
  31. data/app/helpers/pages_core/frontend_helper.rb +1 -1
  32. data/app/helpers/pages_core/head_tags_helper.rb +15 -46
  33. data/app/helpers/pages_core/images_helper.rb +76 -21
  34. data/app/helpers/pages_core/locales_helper.rb +9 -0
  35. data/app/helpers/pages_core/open_graph_tags_helper.rb +3 -5
  36. data/app/helpers/pages_core/page_path_helper.rb +1 -1
  37. data/app/javascript/components/Attachments/Attachment.tsx +55 -52
  38. data/app/javascript/components/Attachments/AttachmentEditor.tsx +45 -50
  39. data/app/javascript/components/Attachments/Placeholder.tsx +1 -2
  40. data/app/javascript/components/Attachments.jsx +69 -57
  41. data/app/javascript/components/DateRangeSelect.jsx +94 -54
  42. data/app/javascript/components/EditableImage.tsx +20 -16
  43. data/app/javascript/components/FileUploadButton.tsx +12 -12
  44. data/app/javascript/components/ImageCropper/FocalPoint.tsx +22 -20
  45. data/app/javascript/components/ImageCropper/Image.tsx +20 -16
  46. data/app/javascript/components/ImageCropper/Toolbar.tsx +35 -27
  47. data/app/javascript/components/ImageCropper/useCrop.ts +105 -91
  48. data/app/javascript/components/ImageCropper.tsx +34 -25
  49. data/app/javascript/components/ImageEditor/Form.tsx +32 -43
  50. data/app/javascript/components/ImageEditor.tsx +29 -21
  51. data/app/javascript/components/ImageGrid/DragElement.tsx +6 -4
  52. data/app/javascript/components/ImageGrid/GridImage.tsx +56 -52
  53. data/app/javascript/components/ImageGrid/Placeholder.tsx +1 -1
  54. data/app/javascript/components/ImageGrid.jsx +132 -101
  55. data/app/javascript/components/ImageUploader.tsx +59 -55
  56. data/app/javascript/components/Modal.tsx +2 -4
  57. data/app/javascript/components/PageDates.jsx +25 -20
  58. data/app/javascript/components/PageFiles.jsx +7 -5
  59. data/app/javascript/components/PageImages.tsx +9 -7
  60. data/app/javascript/components/PageTree/Draggable.tsx +46 -40
  61. data/app/javascript/components/PageTree/Node.tsx +111 -95
  62. data/app/javascript/components/PageTree/types.ts +9 -9
  63. data/app/javascript/components/PageTree.tsx +44 -29
  64. data/app/javascript/components/RichTextArea.jsx +51 -37
  65. data/app/javascript/components/RichTextToolbarButton.tsx +8 -5
  66. data/app/javascript/components/TagEditor/AddTagForm.tsx +11 -10
  67. data/app/javascript/components/TagEditor/Tag.tsx +10 -8
  68. data/app/javascript/components/TagEditor.tsx +15 -10
  69. data/app/javascript/components/Toast.tsx +3 -7
  70. data/app/javascript/components/drag/draggedOrder.ts +16 -15
  71. data/app/javascript/components/drag/types.ts +12 -12
  72. data/app/javascript/components/drag/useDragCollection.ts +36 -42
  73. data/app/javascript/components/drag/useDragUploader.ts +3 -2
  74. data/app/javascript/components/drag.ts +5 -4
  75. data/app/javascript/controllers/LoginController.ts +0 -1
  76. data/app/javascript/controllers/MainController.ts +6 -2
  77. data/app/javascript/controllers/PageOptionsController.js +7 -2
  78. data/app/javascript/features/RichText.tsx +9 -7
  79. data/app/javascript/index.ts +5 -3
  80. data/app/javascript/lib/Tree.ts +27 -24
  81. data/app/javascript/lib/copyToClipboard.ts +5 -4
  82. data/app/javascript/lib/readyHandler.ts +4 -4
  83. data/app/javascript/lib/request.ts +7 -3
  84. data/app/javascript/stores/useModalStore.ts +3 -3
  85. data/app/javascript/stores/useToastStore.ts +14 -12
  86. data/app/javascript/types.ts +22 -22
  87. data/app/models/concerns/pages_core/page_model/templateable.rb +1 -1
  88. data/app/views/admin/calendars/show.html.erb +1 -1
  89. data/app/views/admin/news/index.html.erb +1 -1
  90. data/app/views/admin/pages/_edit_files.html.erb +1 -1
  91. data/app/views/admin/pages/_edit_images.html.erb +1 -1
  92. data/app/views/admin/pages/_list_item.html.erb +1 -1
  93. data/app/views/admin/pages/_search_bar.html.erb +1 -1
  94. data/app/views/admin/pages/deleted.html.erb +2 -2
  95. data/app/views/admin/pages/edit.html.erb +3 -3
  96. data/app/views/admin/pages/index.html.erb +4 -4
  97. data/app/views/admin/pages/new.html.erb +1 -1
  98. data/app/views/admin/pages/search.html.erb +3 -3
  99. data/app/views/feeds/pages.rss.builder +2 -2
  100. data/app/views/layouts/admin/_page_header.html.erb +4 -4
  101. data/app/views/layouts/admin.html.erb +1 -2
  102. data/config/locales/en.yml +1 -0
  103. data/lib/pages_core/pages_plugin.rb +5 -3
  104. data/lib/rails/generators/pages_core/frontend/templates/application.html.erb +15 -13
  105. data/lib/tasks/pages/reports.rake +26 -0
  106. metadata +32 -4
  107. data/app/helpers/pages_core/admin/deprecated_admin_helper.rb +0 -40
  108. data/app/views/pages_core/_google_analytics.html.erb +0 -8
@@ -6,20 +6,26 @@ import { CropState } from "./useCrop";
6
6
  type Ratio = number | null;
7
7
 
8
8
  interface ToolbarProps {
9
- cropState: CropState,
10
- image: ImageResource,
11
- setAspect: (Ratio) => void,
12
- toggleCrop: (evt: Event) => void,
13
- toggleFocal: (evt: Event) => void
9
+ cropState: CropState;
10
+ image: ImageResource;
11
+ setAspect: (Ratio) => void;
12
+ toggleCrop: (evt: Event) => void;
13
+ toggleFocal: (evt: Event) => void;
14
14
  }
15
15
 
16
16
  export default function Toolbar(props: ToolbarProps) {
17
17
  const { cropping } = props.cropState;
18
18
 
19
19
  const aspectRatios = [
20
- ["Free", null], ["1:1", 1], ["3:2", 3/2], ["2:3", 2/3],
21
- ["4:3", 4/3], ["3:4", 3/4], ["5:4", 5/4], ["4:5", 4/5],
22
- ["16:9", 16/9]
20
+ ["Free", null],
21
+ ["1:1", 1],
22
+ ["3:2", 3 / 2],
23
+ ["2:3", 2 / 3],
24
+ ["4:3", 4 / 3],
25
+ ["3:4", 3 / 4],
26
+ ["5:4", 5 / 4],
27
+ ["4:5", 4 / 5],
28
+ ["16:9", 16 / 9]
23
29
  ];
24
30
 
25
31
  const updateAspect = (ratio: Ratio) => (evt: Event) => {
@@ -39,34 +45,36 @@ export default function Toolbar(props: ToolbarProps) {
39
45
  {width}x{height} {format}
40
46
  </span>
41
47
  </div>
42
- <button title="Crop image"
43
- onClick={props.toggleCrop}
44
- className={cropping ? "active" : ""}>
48
+ <button
49
+ title="Crop image"
50
+ onClick={props.toggleCrop}
51
+ className={cropping ? "active" : ""}>
45
52
  <i className="fa-solid fa-crop" />
46
53
  </button>
47
- <button disabled={cropping}
48
- title="Toggle focal point"
49
- onClick={props.toggleFocal}>
54
+ <button
55
+ disabled={cropping}
56
+ title="Toggle focal point"
57
+ onClick={props.toggleFocal}>
50
58
  <i className="fa-solid fa-bullseye" />
51
59
  </button>
52
- <a href={props.image.original_url}
53
- className="button"
54
- title="Download original image"
55
- disabled={cropping}
56
- download={props.image.filename}
57
- onClick={evt => cropping && evt.preventDefault()}>
60
+ <a
61
+ href={props.image.original_url}
62
+ className="button"
63
+ title="Download original image"
64
+ disabled={cropping}
65
+ download={props.image.filename}
66
+ onClick={(evt) => cropping && evt.preventDefault()}>
58
67
  <i className="fa-solid fa-download" />
59
68
  </a>
60
69
  </div>
61
70
  {cropping && (
62
71
  <div className="aspect-ratios toolbar">
63
- <div className="label">
64
- Lock aspect ratio:
65
- </div>
66
- {aspectRatios.map(ratio => (
67
- <button key={ratio[0]}
68
- className={(ratio[1] == props.cropState.aspect) ? "active" : ""}
69
- onClick={updateAspect(ratio[1])}>
72
+ <div className="label">Lock aspect ratio:</div>
73
+ {aspectRatios.map((ratio) => (
74
+ <button
75
+ key={ratio[0]}
76
+ className={ratio[1] == props.cropState.aspect ? "active" : ""}
77
+ onClick={updateAspect(ratio[1])}>
70
78
  {ratio[0]}
71
79
  </button>
72
80
  ))}
@@ -3,41 +3,41 @@ import { useEffect, useReducer, useState } from "react";
3
3
  import { ImageResource } from "../../types";
4
4
 
5
5
  export interface Position {
6
- x: number,
7
- y: number,
6
+ x: number;
7
+ y: number;
8
8
  }
9
9
 
10
10
  export interface Size {
11
- width: number,
12
- height: number
11
+ width: number;
12
+ height: number;
13
13
  }
14
14
 
15
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,
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
22
  }
23
23
 
24
24
  export interface CropState extends CropParams {
25
- aspect: number | null,
26
- cropping: boolean,
27
- image: ImageResource
25
+ aspect: number | null;
26
+ cropping: boolean;
27
+ image: ImageResource;
28
28
  }
29
29
 
30
30
  export interface CropSize {
31
- x: number,
32
- y: number,
33
- width: number,
34
- height: number,
35
- aspect?: number
31
+ x: number;
32
+ y: number;
33
+ width: number;
34
+ height: number;
35
+ aspect?: number;
36
36
  }
37
37
 
38
38
  export interface CropAction {
39
- type: string,
40
- payload?: CropSize | Position
39
+ type: string;
40
+ payload?: CropSize | Position;
41
41
  }
42
42
 
43
43
  function applyAspect(state: CropState, aspect: number | null) {
@@ -62,7 +62,7 @@ function applyAspect(state: CropState, aspect: number | null) {
62
62
  delete crop.aspect;
63
63
  }
64
64
 
65
- return(applyCrop(state, crop));
65
+ return applyCrop(state, crop);
66
66
  }
67
67
 
68
68
  function applyCrop(state: CropState, crop: CropSize) {
@@ -77,78 +77,95 @@ function applyCrop(state: CropState, crop: CropSize) {
77
77
  delete crop.aspect;
78
78
  }
79
79
 
80
- return({ aspect: crop.aspect,
81
- crop_start_x: image.real_width * (crop.x / 100),
82
- crop_start_y: image.real_height * (crop.y / 100),
83
- crop_width: image.real_width * (crop.width / 100),
84
- crop_height: image.real_height * (crop.height / 100) });
80
+ return {
81
+ aspect: crop.aspect,
82
+ crop_start_x: image.real_width * (crop.x / 100),
83
+ crop_start_y: image.real_height * (crop.y / 100),
84
+ crop_width: image.real_width * (crop.width / 100),
85
+ crop_height: image.real_height * (crop.height / 100)
86
+ };
85
87
  }
86
88
 
87
89
  function cropReducer(state: CropState, action: CropAction): CropState {
88
- const { crop_start_x,
89
- crop_start_y,
90
- crop_width,
91
- crop_height,
92
- crop_gravity_x,
93
- crop_gravity_y } = state;
90
+ const {
91
+ crop_start_x,
92
+ crop_start_y,
93
+ crop_width,
94
+ crop_height,
95
+ crop_gravity_x,
96
+ crop_gravity_y
97
+ } = state;
94
98
 
95
99
  switch (action.type) {
96
- case "completeCrop":
97
- // Disable focal point if it's out of bounds.
98
- if (crop_gravity_x < crop_start_x ||
99
- crop_gravity_x > (crop_start_x + crop_width) ||
100
+ case "completeCrop":
101
+ // Disable focal point if it's out of bounds.
102
+ if (
103
+ crop_gravity_x < crop_start_x ||
104
+ crop_gravity_x > crop_start_x + crop_width ||
100
105
  crop_gravity_y < crop_start_y ||
101
- crop_gravity_y > (crop_start_y + crop_height)) {
102
- return { ...state, cropping: false, crop_gravity_x: null, crop_gravity_y: null };
103
- } else {
104
- return { ...state, cropping: false };
105
- }
106
- case "setCrop":
107
- return { ...state, ...applyCrop(state, action.payload) };
108
- case "setAspect":
109
- return { ...state, ...applyAspect(state, action.payload) };
110
- case "setFocal":
111
- return {
112
- ...state,
113
- crop_gravity_x: (crop_width * (action.payload.x / 100)) + crop_start_x,
114
- crop_gravity_y: (crop_height * (action.payload.y / 100)) + crop_start_y
115
- };
116
- case "startCrop":
117
- return { ...state, cropping: true };
118
- case "toggleFocal":
119
- if (crop_gravity_x === null) {
120
- return cropReducer(state, { type: "setFocal", payload: { x: 50, y: 50 } });
121
- } else {
122
- return { ...state, crop_gravity_x: null, crop_gravity_y: null };
123
- }
124
- default:
125
- return state;
106
+ crop_gravity_y > crop_start_y + crop_height
107
+ ) {
108
+ return {
109
+ ...state,
110
+ cropping: false,
111
+ crop_gravity_x: null,
112
+ crop_gravity_y: null
113
+ };
114
+ } else {
115
+ return { ...state, cropping: false };
116
+ }
117
+ case "setCrop":
118
+ return { ...state, ...applyCrop(state, action.payload) };
119
+ case "setAspect":
120
+ return { ...state, ...applyAspect(state, action.payload) };
121
+ case "setFocal":
122
+ return {
123
+ ...state,
124
+ crop_gravity_x: crop_width * (action.payload.x / 100) + crop_start_x,
125
+ crop_gravity_y: crop_height * (action.payload.y / 100) + crop_start_y
126
+ };
127
+ case "startCrop":
128
+ return { ...state, cropping: true };
129
+ case "toggleFocal":
130
+ if (crop_gravity_x === null) {
131
+ return cropReducer(state, {
132
+ type: "setFocal",
133
+ payload: { x: 50, y: 50 }
134
+ });
135
+ } else {
136
+ return { ...state, crop_gravity_x: null, crop_gravity_y: null };
137
+ }
138
+ default:
139
+ return state;
126
140
  }
127
141
  }
128
142
 
129
143
  function croppedImageCanvas(img: HTMLImageElement, crop: CropSize) {
130
144
  const canvas = document.createElement("canvas");
131
- canvas.width = (img.naturalWidth * (crop.width / 100));
132
- canvas.height = (img.naturalHeight * (crop.height / 100));
145
+ canvas.width = img.naturalWidth * (crop.width / 100);
146
+ canvas.height = img.naturalHeight * (crop.height / 100);
133
147
  const ctx = canvas.getContext("2d");
134
148
  ctx.drawImage(
135
149
  img,
136
- (img.naturalWidth * (crop.x / 100)),
137
- (img.naturalHeight * (crop.y / 100)),
138
- (img.naturalWidth * (crop.width / 100)),
139
- (img.naturalHeight * (crop.height / 100)),
150
+ img.naturalWidth * (crop.x / 100),
151
+ img.naturalHeight * (crop.y / 100),
152
+ img.naturalWidth * (crop.width / 100),
153
+ img.naturalHeight * (crop.height / 100),
140
154
  0,
141
155
  0,
142
- (img.naturalWidth * (crop.width / 100)),
143
- (img.naturalHeight * (crop.height / 100))
156
+ img.naturalWidth * (crop.width / 100),
157
+ img.naturalHeight * (crop.height / 100)
144
158
  );
145
159
  return [canvas, ctx];
146
160
  }
147
161
 
148
- function imageDataUrl(canvas: HTMLCanvasElement, ctx: CanvasRenderingContext2D): string {
162
+ function imageDataUrl(
163
+ canvas: HTMLCanvasElement,
164
+ ctx: CanvasRenderingContext2D
165
+ ): string {
149
166
  const pixels = ctx.getImageData(0, 0, canvas.width, canvas.height).data;
150
- for (let i = 0; i < (pixels.length / 4); i++) {
151
- if (pixels[(i * 4) + 3] !== 255) {
167
+ for (let i = 0; i < pixels.length / 4; i++) {
168
+ if (pixels[i * 4 + 3] !== 255) {
152
169
  return canvas.toDataURL("image/png");
153
170
  }
154
171
  }
@@ -156,15 +173,16 @@ function imageDataUrl(canvas: HTMLCanvasElement, ctx: CanvasRenderingContext2D):
156
173
  }
157
174
 
158
175
  export function cropParams(state: CropState): CropParams {
159
- const maybe = (func: (number) => number) => (val: number | null) => (val === null) ? val : func(val);
176
+ const maybe = (func: (number) => number) => (val: number | null) =>
177
+ val === null ? val : func(val);
160
178
  const maybeRound = maybe(Math.round);
161
179
  const maybeCeil = maybe(Math.ceil);
162
180
 
163
181
  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),
182
+ crop_start_x: maybeRound(state.crop_start_x),
183
+ crop_start_y: maybeRound(state.crop_start_y),
184
+ crop_width: maybeCeil(state.crop_width),
185
+ crop_height: maybeCeil(state.crop_height),
168
186
  crop_gravity_x: maybeRound(state.crop_gravity_x),
169
187
  crop_gravity_y: maybeRound(state.crop_gravity_y)
170
188
  };
@@ -177,16 +195,12 @@ export function cropParams(state: CropState): CropParams {
177
195
  crop.crop_height = state.image.real_height - crop.crop_start_y;
178
196
  }
179
197
 
180
- return(crop);
198
+ return crop;
181
199
  }
182
200
 
183
201
  export function cropSize(state: CropState): CropSize {
184
- const { image,
185
- aspect,
186
- crop_start_x,
187
- crop_start_y,
188
- crop_width,
189
- crop_height } = state;
202
+ const { image, aspect, crop_start_x, crop_start_y, crop_width, crop_height } =
203
+ state;
190
204
  const imageAspect = image.real_width / image.real_height;
191
205
  const x = (crop_start_x / image.real_width) * 100;
192
206
  const y = (crop_start_y / image.real_height) * 100;
@@ -209,15 +223,15 @@ export function cropSize(state: CropState): CropSize {
209
223
 
210
224
  export default function useCrop(image: ImageResource) {
211
225
  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,
226
+ aspect: null,
227
+ cropping: false,
228
+ crop_start_x: image.crop_start_x || 0,
229
+ crop_start_y: image.crop_start_y || 0,
230
+ crop_width: image.crop_width || image.real_width,
231
+ crop_height: image.crop_height || image.real_height,
218
232
  crop_gravity_x: image.crop_gravity_x,
219
233
  crop_gravity_y: image.crop_gravity_y,
220
- image: image
234
+ image: image
221
235
  };
222
236
 
223
237
  const [state, dispatch] = useReducer(cropReducer, initialState);
@@ -3,16 +3,19 @@ import React, { useEffect, useRef, useState } from "react";
3
3
  import Image from "./ImageCropper/Image";
4
4
  import Toolbar from "./ImageCropper/Toolbar";
5
5
 
6
- import { CropAction, CropSize, CropState,
7
- Position } from "./ImageCropper/useCrop";
6
+ import {
7
+ CropAction,
8
+ CropSize,
9
+ CropState,
10
+ Position
11
+ } from "./ImageCropper/useCrop";
8
12
 
9
- export { default as useCrop,
10
- cropParams } from "./ImageCropper/useCrop";
13
+ export { default as useCrop, cropParams } from "./ImageCropper/useCrop";
11
14
 
12
15
  interface ImageCropperProps {
13
- croppedImage: string,
14
- cropState: CropState,
15
- dispatch: (action: CropAction) => void
16
+ croppedImage: string;
17
+ cropState: CropState;
18
+ dispatch: (action: CropAction) => void;
16
19
  }
17
20
 
18
21
  function focalPoint(state: CropState): Position {
@@ -33,8 +36,10 @@ export default function ImageCropper(props: ImageCropperProps) {
33
36
  const handleResize = () => {
34
37
  const elem = containerRef.current;
35
38
  if (elem) {
36
- setContainerSize({ width: elem.offsetWidth - 2,
37
- height: elem.offsetHeight - 2 });
39
+ setContainerSize({
40
+ width: elem.offsetWidth - 2,
41
+ height: elem.offsetHeight - 2
42
+ });
38
43
  }
39
44
  };
40
45
 
@@ -69,23 +74,27 @@ export default function ImageCropper(props: ImageCropperProps) {
69
74
 
70
75
  return (
71
76
  <div className="visual">
72
- <Toolbar cropState={props.cropState}
73
- image={props.cropState.image}
74
- setAspect={setAspect}
75
- toggleCrop={toggleCrop}
76
- toggleFocal={() => props.dispatch({ type: "toggleFocal" })} />
77
+ <Toolbar
78
+ cropState={props.cropState}
79
+ image={props.cropState.image}
80
+ setAspect={setAspect}
81
+ toggleCrop={toggleCrop}
82
+ toggleFocal={() => props.dispatch({ type: "toggleFocal" })}
83
+ />
77
84
  <div className="image-container" ref={containerRef}>
78
- {!props.croppedImage &&
79
- <div className="loading">
80
- Loading image&hellip;
81
- </div>}
82
- {props.croppedImage && containerSize &&
83
- <Image cropState={props.cropState}
84
- containerSize={containerSize}
85
- croppedImage={props.croppedImage}
86
- focalPoint={focalPoint(props.cropState)}
87
- setCrop={setCrop}
88
- setFocal={setFocal} />}
85
+ {!props.croppedImage && (
86
+ <div className="loading">Loading image&hellip;</div>
87
+ )}
88
+ {props.croppedImage && containerSize && (
89
+ <Image
90
+ cropState={props.cropState}
91
+ containerSize={containerSize}
92
+ croppedImage={props.croppedImage}
93
+ focalPoint={focalPoint(props.cropState)}
94
+ setCrop={setCrop}
95
+ setFocal={setFocal}
96
+ />
97
+ )}
89
98
  </div>
90
99
  </div>
91
100
  );
@@ -5,15 +5,15 @@ import { Locale, ImageResource } from "../../types";
5
5
  import copyToClipboard, { copySupported } from "../../lib/copyToClipboard";
6
6
 
7
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
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
17
  }
18
18
 
19
19
  export default function Form(props: FormProps) {
@@ -37,27 +37,15 @@ export default function Form(props: FormProps) {
37
37
  return (
38
38
  <form>
39
39
  <div className="field embed-code">
40
- <label>
41
- Embed code
42
- </label>
43
- <input type="text"
44
- value={`[image:${image.id}]`}
45
- disabled={true} />
46
- {copySupported() && (
47
- <button onClick={copyEmbedCode}>
48
- Copy
49
- </button>
50
- )}
40
+ <label>Embed code</label>
41
+ <input type="text" value={`[image:${image.id}]`} disabled={true} />
42
+ {copySupported() && <button onClick={copyEmbedCode}>Copy</button>}
51
43
  </div>
52
44
  {locales && Object.keys(locales).length > 1 && (
53
45
  <div className="field">
54
- <label>
55
- Locale
56
- </label>
57
- <select name="locale"
58
- value={locale}
59
- onChange={handleChangeLocale}>
60
- {Object.keys(locales).map(key => (
46
+ <label>Locale</label>
47
+ <select name="locale" value={locale} onChange={handleChangeLocale}>
48
+ {Object.keys(locales).map((key) => (
61
49
  <option key={`locale-${key}`} value={key}>
62
50
  {locales[key].name}
63
51
  </option>
@@ -65,10 +53,11 @@ export default function Form(props: FormProps) {
65
53
  </select>
66
54
  </div>
67
55
  )}
68
- <div className={"field " + (alternative[locale] ? "" : "field-with-warning")}>
69
- <label>
70
- Alternative text
71
- </label>
56
+ <div
57
+ className={
58
+ "field " + (alternative[locale] ? "" : "field-with-warning")
59
+ }>
60
+ <label>Alternative text</label>
72
61
  <span className="description">
73
62
  For visually impaired users and search engines.
74
63
  </span>
@@ -77,28 +66,28 @@ export default function Form(props: FormProps) {
77
66
  lang={locale}
78
67
  dir={inputDir}
79
68
  value={alternative[locale] || ""}
80
- onChange={e => props.updateLocalization("alternative", e.target.value)} />
69
+ onChange={(e) =>
70
+ props.updateLocalization("alternative", e.target.value)
71
+ }
72
+ />
81
73
  </div>
82
74
  {props.showCaption && (
83
75
  <div className="field">
84
- <label>
85
- Caption
86
- </label>
76
+ <label>Caption</label>
87
77
  <textarea
88
78
  lang={locale}
89
79
  dir={inputDir}
90
- onChange={e => props.updateLocalization("caption", e.target.value)}
80
+ onChange={(e) =>
81
+ props.updateLocalization("caption", e.target.value)
82
+ }
91
83
  value={caption[locale] || ""}
92
- className="caption" />
84
+ className="caption"
85
+ />
93
86
  </div>
94
87
  )}
95
88
  <div className="buttons">
96
- <button onClick={props.save}>
97
- Save
98
- </button>
99
- <button onClick={closeModal}>
100
- Cancel
101
- </button>
89
+ <button onClick={props.save}>Save</button>
90
+ <button onClick={closeModal}>Cancel</button>
102
91
  </div>
103
92
  </form>
104
93
  );
@@ -7,24 +7,27 @@ import ImageCropper, { useCrop, cropParams } from "./ImageCropper";
7
7
  import Form from "./ImageEditor/Form";
8
8
 
9
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
10
+ image: ImageResource;
11
+ caption: boolean;
12
+ locale: string;
13
+ locales: Record<string, Locale>;
14
+ onUpdate?: (data: ImageResource, croppedImage: string | null) => void;
15
15
  }
16
16
 
17
17
  export default function ImageEditor(props: ImageEditorProps) {
18
18
  const [cropState, dispatch, croppedImage] = useCrop(props.image);
19
19
  const [locale, setLocale] = useState(props.locale);
20
20
  const [localizations, setLocalizations] = useState({
21
- caption: props.image.caption || {},
22
- alternative: props.image.alternative || {},
21
+ caption: props.image.caption || {},
22
+ alternative: props.image.alternative || {}
23
23
  });
24
24
 
25
25
  const closeModal = useModalStore((state) => state.close);
26
26
 
27
- const updateLocalization = (name: "alternative" | "caption", value: string) => {
27
+ const updateLocalization = (
28
+ name: "alternative" | "caption",
29
+ value: string
30
+ ) => {
28
31
  setLocalizations({
29
32
  ...localizations,
30
33
  [name]: { ...localizations[name], [locale]: value }
@@ -46,19 +49,24 @@ export default function ImageEditor(props: ImageEditorProps) {
46
49
 
47
50
  return (
48
51
  <div className="image-editor">
49
- <ImageCropper croppedImage={croppedImage}
50
- cropState={cropState}
51
- dispatch={dispatch} />
52
- {!cropState.cropping &&
53
- <Form alternative={localizations.alternative}
54
- caption={localizations.caption}
55
- image={props.image}
56
- locale={locale}
57
- locales={props.locales}
58
- setLocale={setLocale}
59
- save={save}
60
- showCaption={props.caption}
61
- updateLocalization={updateLocalization} />}
52
+ <ImageCropper
53
+ croppedImage={croppedImage}
54
+ cropState={cropState}
55
+ dispatch={dispatch}
56
+ />
57
+ {!cropState.cropping && (
58
+ <Form
59
+ alternative={localizations.alternative}
60
+ caption={localizations.caption}
61
+ image={props.image}
62
+ locale={locale}
63
+ locales={props.locales}
64
+ setLocale={setLocale}
65
+ save={save}
66
+ showCaption={props.caption}
67
+ updateLocalization={updateLocalization}
68
+ />
69
+ )}
62
70
  </div>
63
71
  );
64
72
  }