pages_core 3.12.4 → 3.12.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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
  }