pages_core 3.14.0 → 3.15.0
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.
- checksums.yaml +4 -4
- data/VERSION +1 -1
- data/app/assets/builds/pages_core/admin-dist.js +19 -8
- data/app/assets/builds/pages_core/admin-dist.js.map +4 -4
- data/app/assets/builds/pages_core/admin.css +672 -379
- data/app/assets/fonts/Inter-Black.woff2 +0 -0
- data/app/assets/fonts/Inter-BlackItalic.woff2 +0 -0
- data/app/assets/fonts/Inter-Bold.woff2 +0 -0
- data/app/assets/fonts/Inter-BoldItalic.woff2 +0 -0
- data/app/assets/fonts/Inter-ExtraBold.woff2 +0 -0
- data/app/assets/fonts/Inter-ExtraBoldItalic.woff2 +0 -0
- data/app/assets/fonts/Inter-ExtraLight.woff2 +0 -0
- data/app/assets/fonts/Inter-ExtraLightItalic.woff2 +0 -0
- data/app/assets/fonts/Inter-Italic.woff2 +0 -0
- data/app/assets/fonts/Inter-Light.woff2 +0 -0
- data/app/assets/fonts/Inter-LightItalic.woff2 +0 -0
- data/app/assets/fonts/Inter-Medium.woff2 +0 -0
- data/app/assets/fonts/Inter-MediumItalic.woff2 +0 -0
- data/app/assets/fonts/Inter-Regular.woff2 +0 -0
- data/app/assets/fonts/Inter-SemiBold.woff2 +0 -0
- data/app/assets/fonts/Inter-SemiBoldItalic.woff2 +0 -0
- data/app/assets/fonts/Inter-Thin.woff2 +0 -0
- data/app/assets/fonts/Inter-ThinItalic.woff2 +0 -0
- data/app/assets/fonts/InterDisplay-Black.woff2 +0 -0
- data/app/assets/fonts/InterDisplay-BlackItalic.woff2 +0 -0
- data/app/assets/fonts/InterDisplay-Bold.woff2 +0 -0
- data/app/assets/fonts/InterDisplay-BoldItalic.woff2 +0 -0
- data/app/assets/fonts/InterDisplay-ExtraBold.woff2 +0 -0
- data/app/assets/fonts/InterDisplay-ExtraBoldItalic.woff2 +0 -0
- data/app/assets/fonts/InterDisplay-ExtraLight.woff2 +0 -0
- data/app/assets/fonts/InterDisplay-ExtraLightItalic.woff2 +0 -0
- data/app/assets/fonts/InterDisplay-Italic.woff2 +0 -0
- data/app/assets/fonts/InterDisplay-Light.woff2 +0 -0
- data/app/assets/fonts/InterDisplay-LightItalic.woff2 +0 -0
- data/app/assets/fonts/InterDisplay-Medium.woff2 +0 -0
- data/app/assets/fonts/InterDisplay-MediumItalic.woff2 +0 -0
- data/app/assets/fonts/InterDisplay-Regular.woff2 +0 -0
- data/app/assets/fonts/InterDisplay-SemiBold.woff2 +0 -0
- data/app/assets/fonts/InterDisplay-SemiBoldItalic.woff2 +0 -0
- data/app/assets/fonts/InterDisplay-Thin.woff2 +0 -0
- data/app/assets/fonts/InterDisplay-ThinItalic.woff2 +0 -0
- data/app/assets/fonts/InterVariable-Italic.woff2 +0 -0
- data/app/assets/fonts/InterVariable.woff2 +0 -0
- data/app/assets/stylesheets/pages_core/admin/components/archive.css +1 -1
- data/app/assets/stylesheets/pages_core/admin/components/attachments.css +22 -34
- data/app/assets/stylesheets/pages_core/admin/components/base.css +1 -68
- data/app/assets/stylesheets/pages_core/admin/components/forms.css +107 -48
- data/app/assets/stylesheets/pages_core/admin/components/header.css +56 -58
- data/app/assets/stylesheets/pages_core/admin/components/image_editor.css +35 -24
- data/app/assets/stylesheets/pages_core/admin/components/image_grid.css +28 -27
- data/app/assets/stylesheets/pages_core/admin/components/image_uploader.css +5 -5
- data/app/assets/stylesheets/pages_core/admin/components/layout.css +7 -1
- data/app/assets/stylesheets/pages_core/admin/components/list_table.css +24 -15
- data/app/assets/stylesheets/pages_core/admin/components/page_tree.css +63 -104
- data/app/assets/stylesheets/pages_core/admin/components/pagination.css +12 -13
- data/app/assets/stylesheets/pages_core/admin/components/search.css +1 -16
- data/app/assets/stylesheets/pages_core/admin/components/sidebar.css +5 -11
- data/app/assets/stylesheets/pages_core/admin/components/tag_editor.css +22 -36
- data/app/assets/stylesheets/pages_core/admin/components/toast.css +1 -2
- data/app/assets/stylesheets/pages_core/admin/components/toolbar.css +10 -10
- data/app/assets/stylesheets/pages_core/admin/components/totp.css +1 -1
- data/app/assets/stylesheets/pages_core/admin/controllers/pages.css +37 -51
- data/app/assets/stylesheets/pages_core/admin/global/fonts.css +271 -0
- data/app/assets/stylesheets/pages_core/admin/global/typography.css +109 -0
- data/app/assets/stylesheets/pages_core/admin/vars.css +1 -3
- data/app/assets/stylesheets/pages_core/admin.postcss.css +1 -0
- data/app/controllers/admin/account_recoveries_controller.rb +2 -2
- data/app/controllers/admin/pages_controller.rb +22 -42
- data/app/controllers/concerns/pages_core/error_reporting.rb +1 -1
- data/app/controllers/concerns/pages_core/page_parameters.rb +29 -0
- data/app/controllers/concerns/pages_core/policies_helper.rb +1 -1
- data/app/controllers/concerns/pages_core/preview_pages_controller.rb +20 -20
- data/app/controllers/pages_core/admin_controller.rb +0 -2
- data/app/controllers/pages_core/frontend/pages_controller.rb +2 -6
- data/app/formatters/pages_core/html_formatter.rb +2 -4
- data/app/helpers/admin/menu_helper.rb +5 -4
- data/app/helpers/admin/pages_helper.rb +1 -21
- data/app/helpers/pages_core/admin/admin_helper.rb +2 -3
- data/app/helpers/pages_core/admin/content_tabs_helper.rb +1 -2
- data/app/helpers/pages_core/admin/labelled_field_helper.rb +1 -1
- data/app/helpers/pages_core/frontend_helper.rb +1 -1
- data/app/helpers/pages_core/images_helper.rb +10 -8
- data/app/helpers/pages_core/labelled_form_builder.rb +2 -7
- data/app/helpers/pages_core/page_path_helper.rb +1 -1
- data/app/javascript/components/Attachments/Attachment.tsx +20 -18
- data/app/javascript/components/Attachments/AttachmentEditor.tsx +11 -9
- data/app/javascript/components/{Attachments.jsx → Attachments/List.tsx} +58 -63
- data/app/javascript/components/Attachments/useAttachments.ts +15 -0
- data/app/javascript/components/Attachments.tsx +14 -0
- data/app/javascript/components/DateRangeSelect.tsx +105 -0
- data/app/javascript/components/DateTimeSelect.tsx +136 -0
- data/app/javascript/components/EditableImage.tsx +11 -9
- data/app/javascript/components/FileUploadButton.tsx +7 -7
- data/app/javascript/components/ImageCropper/FocalPoint.tsx +9 -12
- data/app/javascript/components/ImageCropper/Image.tsx +10 -8
- data/app/javascript/components/ImageCropper/Toolbar.tsx +11 -12
- data/app/javascript/components/ImageCropper/useCrop.ts +24 -53
- data/app/javascript/components/ImageCropper.tsx +10 -15
- data/app/javascript/components/ImageEditor/Form.tsx +12 -8
- data/app/javascript/components/ImageEditor.tsx +12 -7
- data/app/javascript/components/ImageGrid/DragElement.tsx +9 -12
- data/app/javascript/components/{ImageGrid.jsx → ImageGrid/Grid.tsx} +62 -71
- data/app/javascript/components/ImageGrid/GridImage.tsx +22 -23
- data/app/javascript/components/ImageGrid/Placeholder.tsx +2 -2
- data/app/javascript/components/ImageGrid/useImageGrid.ts +26 -0
- data/app/javascript/components/ImageGrid.tsx +15 -0
- data/app/javascript/components/ImageUploader.tsx +35 -22
- data/app/javascript/components/LabelledField.tsx +34 -0
- data/app/javascript/components/Modal.tsx +2 -2
- data/app/javascript/components/PageForm/Block.tsx +81 -0
- data/app/javascript/components/PageForm/Content.tsx +54 -0
- data/app/javascript/components/PageForm/Dates.tsx +66 -0
- data/app/javascript/components/PageForm/Files.tsx +28 -0
- data/app/javascript/components/PageForm/Form.tsx +41 -0
- data/app/javascript/components/PageForm/Images.tsx +28 -0
- data/app/javascript/components/PageForm/LocaleLinks.tsx +36 -0
- data/app/javascript/components/PageForm/Metadata.tsx +67 -0
- data/app/javascript/components/PageForm/Options.tsx +180 -0
- data/app/javascript/components/PageForm/PageDescription.tsx +48 -0
- data/app/javascript/components/PageForm/PathSegment.tsx +65 -0
- data/app/javascript/components/PageForm/TabPanel.tsx +21 -0
- data/app/javascript/components/PageForm/Tabs.tsx +33 -0
- data/app/javascript/components/PageForm/UnconfiguredContent.tsx +42 -0
- data/app/javascript/components/PageForm/pageParams.ts +95 -0
- data/app/javascript/components/PageForm/preview.ts +23 -0
- data/app/javascript/components/PageForm/usePage.ts +169 -0
- data/app/javascript/components/PageForm/useTabs.ts +46 -0
- data/app/javascript/components/PageForm.tsx +163 -0
- data/app/javascript/components/PageImages.tsx +7 -9
- data/app/javascript/components/PageTree/Draggable.tsx +40 -39
- data/app/javascript/components/PageTree/Node.tsx +62 -56
- data/app/javascript/components/PageTree/PageName.tsx +28 -0
- data/app/javascript/components/PageTree.tsx +65 -53
- data/app/javascript/components/{RichTextArea.jsx → RichTextArea.tsx} +98 -79
- data/app/javascript/components/RichTextToolbarButton.tsx +4 -6
- data/app/javascript/components/TagEditor/AddTagForm.tsx +19 -12
- data/app/javascript/components/TagEditor/Editor.tsx +32 -0
- data/app/javascript/components/TagEditor/Tag.tsx +6 -4
- data/app/javascript/components/TagEditor/useTags.ts +58 -0
- data/app/javascript/components/TagEditor.tsx +8 -58
- data/app/javascript/components/Toast.tsx +3 -3
- data/app/javascript/components/drag/draggedOrder.ts +22 -14
- data/app/javascript/components/drag/useDragCollection.ts +35 -30
- data/app/javascript/components/drag/useDragUploader.ts +32 -21
- data/app/javascript/components/drag/useDraggable.ts +7 -6
- data/app/javascript/components/drag.ts +0 -1
- data/app/javascript/components.ts +1 -3
- data/app/javascript/features/RichText.tsx +2 -3
- data/app/javascript/features/contentTabs.ts +79 -0
- data/app/javascript/index.ts +5 -12
- data/app/javascript/lib/Tree.ts +31 -45
- data/app/javascript/lib/request.ts +11 -11
- data/app/javascript/stores/useToastStore.ts +1 -1
- data/app/javascript/types/Attachments.ts +29 -0
- data/app/javascript/types/Crop.ts +36 -0
- data/app/javascript/types/Drag.ts +34 -0
- data/app/javascript/types/Images.ts +47 -0
- data/app/javascript/types/PageEditor.ts +26 -0
- data/app/javascript/types/Pages.ts +75 -0
- data/app/javascript/types/Tags.ts +9 -0
- data/app/javascript/types/Template.ts +24 -0
- data/app/javascript/types/Trees.ts +19 -0
- data/app/javascript/types.ts +2 -25
- data/app/models/attachment.rb +1 -1
- data/app/models/concerns/pages_core/authenticable_user.rb +63 -0
- data/app/models/concerns/pages_core/emailable.rb +16 -0
- data/app/models/concerns/pages_core/page_model/templateable.rb +2 -16
- data/app/models/invite.rb +2 -6
- data/app/models/otp_secret.rb +4 -4
- data/app/models/page.rb +0 -3
- data/app/models/user.rb +2 -46
- data/app/policies/page_policy.rb +6 -2
- data/app/resources/admin/page_resource.rb +95 -0
- data/app/resources/admin/page_tree_resource.rb +27 -0
- data/app/resources/admin/template_configuration_resource.rb +50 -0
- data/app/views/admin/news/_sidebar.html.erb +2 -4
- data/app/views/admin/news/index.html.erb +0 -1
- data/app/views/admin/pages/_form.html.erb +10 -30
- data/app/views/admin/pages/_search_bar.html.erb +1 -1
- data/app/views/admin/pages/edit.html.erb +1 -57
- data/app/views/admin/pages/index.html.erb +1 -1
- data/app/views/admin/pages/new.html.erb +1 -44
- data/app/views/admin/sessions/new.html.erb +9 -11
- data/app/views/admin/users/_access_control.html.erb +5 -1
- data/app/views/admin/users/_list.html.erb +12 -7
- data/app/views/layouts/admin/_header.html.erb +2 -4
- data/app/views/layouts/admin/_page_header.html.erb +1 -2
- data/app/views/layouts/admin.html.erb +1 -1
- data/config/locales/en.yml +0 -4
- data/config/routes.rb +3 -7
- data/db/migrate/20240126160700_add_2fa_fields.rb +5 -1
- data/db/migrate/20240131140700_change_email_to_citext.rb +18 -0
- data/db/migrate/20240201160700_remove_persistent_data.rb +7 -0
- data/db/migrate/20240508145300_remove_categories.rb +21 -0
- data/lib/pages_core/configuration/base.rb +2 -2
- data/lib/pages_core/templates/configuration.rb +1 -1
- data/lib/pages_core/templates/configuration_proxy.rb +2 -2
- data/lib/pages_core/templates/template_configuration.rb +11 -1
- data/lib/pages_core/templates.rb +6 -4
- data/lib/pages_core/version.rb +1 -1
- data/lib/rails/generators/pages_core/frontend/templates/javascript/lib/gridOverlay.ts +6 -7
- data/lib/rails/generators/pages_core/frontend/templates/javascript/lib/responsiveEmbeds.ts +17 -12
- data/lib/rails/generators/pages_core/rspec/rspec_generator.rb +0 -2
- data/lib/rails/generators/pages_core/rspec/templates/rails_helper.rb +3 -4
- metadata +95 -29
- data/app/assets/stylesheets/pages_core/admin/components/login.css +0 -27
- data/app/controllers/admin/categories_controller.rb +0 -56
- data/app/controllers/concerns/pages_core/admin/persistent_params.rb +0 -75
- data/app/helpers/pages_core/admin/page_blocks_helper.rb +0 -66
- data/app/helpers/pages_core/admin/page_json_helper.rb +0 -23
- data/app/javascript/components/DateRangeSelect.jsx +0 -225
- data/app/javascript/components/PageDates.jsx +0 -73
- data/app/javascript/components/PageFiles.jsx +0 -25
- data/app/javascript/components/PageTree/types.ts +0 -15
- data/app/javascript/components/drag/types.ts +0 -28
- data/app/javascript/controllers/EditPageController.ts +0 -22
- data/app/javascript/controllers/MainController.ts +0 -74
- data/app/javascript/controllers/PageOptionsController.js +0 -67
- data/app/models/category.rb +0 -22
- data/app/models/concerns/pages_core/has_otp.rb +0 -27
- data/app/models/page_category.rb +0 -6
- data/app/views/admin/pages/_edit_content.html.erb +0 -19
- data/app/views/admin/pages/_edit_files.html.erb +0 -4
- data/app/views/admin/pages/_edit_images.html.erb +0 -4
- data/app/views/admin/pages/_edit_metadata.html.erb +0 -35
- data/app/views/admin/pages/_edit_options.html.erb +0 -91
- data/lib/rails/generators/pages_core/rspec/templates/mailer_macros.rb +0 -11
@@ -1,46 +1,9 @@
|
|
1
1
|
import { useEffect, useReducer, useState } from "react";
|
2
2
|
|
3
|
-
import
|
3
|
+
import * as Crop from "../../types/Crop";
|
4
|
+
import * as Images from "../../types/Images";
|
4
5
|
|
5
|
-
|
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) {
|
6
|
+
function applyAspect(state: Crop.State, aspect: number | null) {
|
44
7
|
const crop = cropSize(state);
|
45
8
|
const image = state.image;
|
46
9
|
const imageAspect = image.real_width / image.real_height;
|
@@ -65,7 +28,7 @@ function applyAspect(state: CropState, aspect: number | null) {
|
|
65
28
|
return applyCrop(state, crop);
|
66
29
|
}
|
67
30
|
|
68
|
-
function applyCrop(state:
|
31
|
+
function applyCrop(state: Crop.State, crop: Crop.CropSize) {
|
69
32
|
const { image } = state;
|
70
33
|
|
71
34
|
// Don't crop if dimensions are below the threshold
|
@@ -86,7 +49,14 @@ function applyCrop(state: CropState, crop: CropSize) {
|
|
86
49
|
};
|
87
50
|
}
|
88
51
|
|
89
|
-
function
|
52
|
+
function setFocal(state: Crop.State, position: Crop.Position) {
|
53
|
+
return {
|
54
|
+
crop_gravity_x: state.crop_width * (position.x / 100) + state.crop_start_x,
|
55
|
+
crop_gravity_y: state.crop_height * (position.y / 100) + state.crop_start_y
|
56
|
+
};
|
57
|
+
}
|
58
|
+
|
59
|
+
function cropReducer(state: Crop.State, action: Crop.Action): Crop.State {
|
90
60
|
const {
|
91
61
|
crop_start_x,
|
92
62
|
crop_start_y,
|
@@ -119,11 +89,7 @@ function cropReducer(state: CropState, action: CropAction): CropState {
|
|
119
89
|
case "setAspect":
|
120
90
|
return { ...state, ...applyAspect(state, action.payload) };
|
121
91
|
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
|
-
};
|
92
|
+
return { ...state, ...setFocal(state, action.payload) };
|
127
93
|
case "startCrop":
|
128
94
|
return { ...state, cropping: true };
|
129
95
|
case "toggleFocal":
|
@@ -140,7 +106,10 @@ function cropReducer(state: CropState, action: CropAction): CropState {
|
|
140
106
|
}
|
141
107
|
}
|
142
108
|
|
143
|
-
function croppedImageCanvas(
|
109
|
+
function croppedImageCanvas(
|
110
|
+
img: HTMLImageElement,
|
111
|
+
crop: Crop.CropSize
|
112
|
+
): [HTMLCanvasElement, CanvasRenderingContext2D] {
|
144
113
|
const canvas = document.createElement("canvas");
|
145
114
|
canvas.width = img.naturalWidth * (crop.width / 100);
|
146
115
|
canvas.height = img.naturalHeight * (crop.height / 100);
|
@@ -172,13 +141,13 @@ function imageDataUrl(
|
|
172
141
|
return canvas.toDataURL("image/jpeg");
|
173
142
|
}
|
174
143
|
|
175
|
-
export function cropParams(state:
|
144
|
+
export function cropParams(state: Crop.State): Crop.Params {
|
176
145
|
const maybe = (func: (number) => number) => (val: number | null) =>
|
177
146
|
val === null ? val : func(val);
|
178
147
|
const maybeRound = maybe(Math.round);
|
179
148
|
const maybeCeil = maybe(Math.ceil);
|
180
149
|
|
181
|
-
const crop:
|
150
|
+
const crop: Crop.Params = {
|
182
151
|
crop_start_x: maybeRound(state.crop_start_x),
|
183
152
|
crop_start_y: maybeRound(state.crop_start_y),
|
184
153
|
crop_width: maybeCeil(state.crop_width),
|
@@ -198,7 +167,7 @@ export function cropParams(state: CropState): CropParams {
|
|
198
167
|
return crop;
|
199
168
|
}
|
200
169
|
|
201
|
-
export function cropSize(state:
|
170
|
+
export function cropSize(state: Crop.State): Crop.CropSize {
|
202
171
|
const { image, aspect, crop_start_x, crop_start_y, crop_width, crop_height } =
|
203
172
|
state;
|
204
173
|
const imageAspect = image.real_width / image.real_height;
|
@@ -221,8 +190,10 @@ export function cropSize(state: CropState): CropSize {
|
|
221
190
|
}
|
222
191
|
}
|
223
192
|
|
224
|
-
export default function useCrop(
|
225
|
-
|
193
|
+
export default function useCrop(
|
194
|
+
image: Images.Resource
|
195
|
+
): [Crop.State, (action: Crop.Action) => void, string] {
|
196
|
+
const initialState: Crop.State = {
|
226
197
|
aspect: null,
|
227
198
|
cropping: false,
|
228
199
|
crop_start_x: image.crop_start_x || 0,
|
@@ -1,24 +1,19 @@
|
|
1
1
|
import React, { useEffect, useRef, useState } from "react";
|
2
2
|
|
3
|
+
import * as Crop from "../types/Crop";
|
4
|
+
|
3
5
|
import Image from "./ImageCropper/Image";
|
4
6
|
import Toolbar from "./ImageCropper/Toolbar";
|
5
7
|
|
6
|
-
import {
|
7
|
-
CropAction,
|
8
|
-
CropSize,
|
9
|
-
CropState,
|
10
|
-
Position
|
11
|
-
} from "./ImageCropper/useCrop";
|
12
|
-
|
13
8
|
export { default as useCrop, cropParams } from "./ImageCropper/useCrop";
|
14
9
|
|
15
|
-
interface
|
10
|
+
interface Props {
|
16
11
|
croppedImage: string;
|
17
|
-
cropState:
|
18
|
-
dispatch: (action:
|
12
|
+
cropState: Crop.State;
|
13
|
+
dispatch: (action: Crop.Action) => void;
|
19
14
|
}
|
20
15
|
|
21
|
-
function focalPoint(state:
|
16
|
+
function focalPoint(state: Crop.State): Crop.Position {
|
22
17
|
if (state.crop_gravity_x === null || state.crop_gravity_y === null) {
|
23
18
|
return null;
|
24
19
|
} else {
|
@@ -29,9 +24,9 @@ function focalPoint(state: CropState): Position {
|
|
29
24
|
}
|
30
25
|
}
|
31
26
|
|
32
|
-
export default function ImageCropper(props:
|
27
|
+
export default function ImageCropper(props: Props) {
|
33
28
|
const containerRef = useRef<HTMLDivElement>();
|
34
|
-
const [containerSize, setContainerSize] = useState();
|
29
|
+
const [containerSize, setContainerSize] = useState<Crop.Size>();
|
35
30
|
|
36
31
|
const handleResize = () => {
|
37
32
|
const elem = containerRef.current;
|
@@ -56,11 +51,11 @@ export default function ImageCropper(props: ImageCropperProps) {
|
|
56
51
|
props.dispatch({ type: "setAspect", payload: aspect });
|
57
52
|
};
|
58
53
|
|
59
|
-
const setCrop = (crop: CropSize) => {
|
54
|
+
const setCrop = (crop: Crop.CropSize) => {
|
60
55
|
props.dispatch({ type: "setCrop", payload: crop });
|
61
56
|
};
|
62
57
|
|
63
|
-
const setFocal = (focal: Position) => {
|
58
|
+
const setFocal = (focal: Crop.Position) => {
|
64
59
|
props.dispatch({ type: "setFocal", payload: focal });
|
65
60
|
};
|
66
61
|
|
@@ -1,28 +1,30 @@
|
|
1
|
-
import React, { ChangeEvent } from "react";
|
1
|
+
import React, { ChangeEvent, MouseEvent } from "react";
|
2
|
+
|
2
3
|
import useModalStore from "../../stores/useModalStore";
|
3
4
|
import useToastStore from "../../stores/useToastStore";
|
4
|
-
import { Locale, ImageResource } from "../../types";
|
5
5
|
import copyToClipboard, { copySupported } from "../../lib/copyToClipboard";
|
6
|
+
import * as Images from "../../types/Images";
|
7
|
+
import { Locale } from "../../types";
|
6
8
|
|
7
|
-
interface
|
9
|
+
interface Props {
|
8
10
|
alternative: Record<string, string>;
|
9
11
|
caption: Record<string, string>;
|
10
|
-
image:
|
12
|
+
image: Images.Resource;
|
11
13
|
locale: string;
|
12
14
|
locales: Record<string, Locale>;
|
13
15
|
setLocale: (locale: string) => void;
|
14
|
-
save: (evt:
|
16
|
+
save: (evt: MouseEvent) => void;
|
15
17
|
showCaption: boolean;
|
16
18
|
updateLocalization: (name: "alternative" | "caption", value: string) => void;
|
17
19
|
}
|
18
20
|
|
19
|
-
export default function Form(props:
|
21
|
+
export default function Form(props: Props) {
|
20
22
|
const { alternative, caption, image, locale, locales } = props;
|
21
23
|
|
22
24
|
const closeModal = useModalStore((state) => state.close);
|
23
25
|
const notice = useToastStore((state) => state.notice);
|
24
26
|
|
25
|
-
const copyEmbedCode = (evt:
|
27
|
+
const copyEmbedCode = (evt: MouseEvent) => {
|
26
28
|
evt.preventDefault();
|
27
29
|
copyToClipboard(`[image:${image.id}]`);
|
28
30
|
notice("Embed code copied to clipboard");
|
@@ -86,7 +88,9 @@ export default function Form(props: FormProps) {
|
|
86
88
|
</div>
|
87
89
|
)}
|
88
90
|
<div className="buttons">
|
89
|
-
<button onClick={props.save}>
|
91
|
+
<button className="primary" onClick={props.save}>
|
92
|
+
Save
|
93
|
+
</button>
|
90
94
|
<button onClick={closeModal}>Cancel</button>
|
91
95
|
</div>
|
92
96
|
</form>
|
@@ -1,20 +1,25 @@
|
|
1
|
-
import React, { useState } from "react";
|
1
|
+
import React, { MouseEvent, useState } from "react";
|
2
|
+
|
2
3
|
import useModalStore from "../stores/useModalStore";
|
3
4
|
import { putJson } from "../lib/request";
|
5
|
+
import * as Images from "../types/Images";
|
6
|
+
import { Locale } from "../types";
|
4
7
|
|
5
|
-
import { Locale, ImageResource } from "../types";
|
6
8
|
import ImageCropper, { useCrop, cropParams } from "./ImageCropper";
|
7
9
|
import Form from "./ImageEditor/Form";
|
8
10
|
|
9
|
-
interface
|
10
|
-
image:
|
11
|
+
interface Props {
|
12
|
+
image: Images.Resource;
|
11
13
|
caption: boolean;
|
12
14
|
locale: string;
|
13
15
|
locales: Record<string, Locale>;
|
14
|
-
onUpdate?: (
|
16
|
+
onUpdate?: (
|
17
|
+
data: Partial<Images.Resource>,
|
18
|
+
croppedImage: string | null
|
19
|
+
) => void;
|
15
20
|
}
|
16
21
|
|
17
|
-
export default function ImageEditor(props:
|
22
|
+
export default function ImageEditor(props: Props) {
|
18
23
|
const [cropState, dispatch, croppedImage] = useCrop(props.image);
|
19
24
|
const [locale, setLocale] = useState(props.locale);
|
20
25
|
const [localizations, setLocalizations] = useState({
|
@@ -34,7 +39,7 @@ export default function ImageEditor(props: ImageEditorProps) {
|
|
34
39
|
});
|
35
40
|
};
|
36
41
|
|
37
|
-
const save = (evt:
|
42
|
+
const save = (evt: MouseEvent) => {
|
38
43
|
evt.preventDefault();
|
39
44
|
evt.stopPropagation();
|
40
45
|
|
@@ -1,20 +1,21 @@
|
|
1
1
|
import React, { RefObject } from "react";
|
2
2
|
|
3
|
-
import
|
4
|
-
import
|
3
|
+
import * as Drag from "../../types/Drag";
|
4
|
+
import * as Images from "../../types/Images";
|
5
5
|
|
6
|
-
interface
|
6
|
+
interface Props {
|
7
7
|
container: RefObject<HTMLDivElement>;
|
8
|
-
draggable:
|
9
|
-
dragState:
|
8
|
+
draggable: Drag.Item<Images.Record>;
|
9
|
+
dragState: Drag.State;
|
10
10
|
}
|
11
11
|
|
12
|
-
export default function DragElement(props:
|
12
|
+
export default function DragElement(props: Props) {
|
13
13
|
const { draggable, dragState, container } = props;
|
14
14
|
|
15
15
|
if (draggable === "Files") {
|
16
16
|
return "";
|
17
|
-
} else {
|
17
|
+
} else if (typeof draggable !== "string") {
|
18
|
+
const record = draggable.record;
|
18
19
|
const containerSize = container.current.getBoundingClientRect();
|
19
20
|
const x = dragState.x - (containerSize.x || containerSize.left);
|
20
21
|
const y = dragState.y - (containerSize.y || containerSize.top);
|
@@ -23,11 +24,7 @@ export default function DragElement(props: DragElementProps) {
|
|
23
24
|
};
|
24
25
|
return (
|
25
26
|
<div className="drag-image" style={translateStyle}>
|
26
|
-
{
|
27
|
-
<img
|
28
|
-
src={draggable.record.src || draggable.record.image.thumbnail_url}
|
29
|
-
/>
|
30
|
-
)}
|
27
|
+
{record.image && <img src={record.src || record.image.thumbnail_url} />}
|
31
28
|
</div>
|
32
29
|
);
|
33
30
|
}
|
@@ -1,20 +1,20 @@
|
|
1
|
-
import React, { useRef
|
2
|
-
import
|
3
|
-
import
|
4
|
-
import
|
5
|
-
import
|
6
|
-
import
|
7
|
-
import
|
8
|
-
import
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
}
|
1
|
+
import React, { useRef } from "react";
|
2
|
+
import FileUploadButton from "../FileUploadButton";
|
3
|
+
import DragElement from "./DragElement";
|
4
|
+
import FilePlaceholder from "./FilePlaceholder";
|
5
|
+
import GridImage from "./GridImage";
|
6
|
+
import useToastStore from "../../stores/useToastStore";
|
7
|
+
import { post } from "../../lib/request";
|
8
|
+
import * as Drag from "../../types/Drag";
|
9
|
+
import * as Images from "../../types/Images";
|
10
|
+
|
11
|
+
import { createDraggable, collectionOrder, useDragUploader } from "../drag";
|
12
|
+
|
13
|
+
interface Props extends Images.GridOptions {
|
14
|
+
state: Images.GridState;
|
15
|
+
}
|
16
16
|
|
17
|
-
function filterFiles(files) {
|
17
|
+
function filterFiles(files: File[]): File[] {
|
18
18
|
const validMimeTypes = [
|
19
19
|
"image/gif",
|
20
20
|
"image/jpeg",
|
@@ -25,7 +25,11 @@ function filterFiles(files) {
|
|
25
25
|
return files.filter((f) => validMimeTypes.indexOf(f.type) !== -1);
|
26
26
|
}
|
27
27
|
|
28
|
-
function draggedImageOrder(
|
28
|
+
function draggedImageOrder(
|
29
|
+
primaryCollection: Drag.Collection<Images.Record>,
|
30
|
+
imagesCollection: Drag.Collection<Images.Record>,
|
31
|
+
dragState: Drag.State<Images.Record>
|
32
|
+
): [Drag.Item<Images.Record>, Drag.Item<Images.Record>[]] {
|
29
33
|
const [primary, ...rest] = collectionOrder(primaryCollection, dragState);
|
30
34
|
let images = [...rest, ...collectionOrder(imagesCollection, dragState)];
|
31
35
|
|
@@ -45,29 +49,18 @@ function draggedImageOrder(primaryCollection, imagesCollection, dragState) {
|
|
45
49
|
return [primary, images];
|
46
50
|
}
|
47
51
|
|
48
|
-
function
|
49
|
-
const primary = props.
|
50
|
-
? props.records.filter((r) => r.primary).slice(0, 1)
|
51
|
-
: [];
|
52
|
-
|
53
|
-
return [primary, props.records.filter((r) => primary.indexOf(r) === -1)];
|
54
|
-
}
|
55
|
-
|
56
|
-
export default function ImageGrid(props) {
|
57
|
-
const [initPrimary, initImages] = initRecords(props);
|
58
|
-
const primary = useDragCollection(initPrimary);
|
59
|
-
const images = useDragCollection(initImages);
|
60
|
-
const [deleted, setDeleted] = useState([]);
|
61
|
-
const error = useToastStore((state) => state.error);
|
52
|
+
export default function Grid(props: Props) {
|
53
|
+
const { primary, images, deleted, setDeleted } = props.state;
|
62
54
|
|
63
55
|
const containerRef = useRef();
|
56
|
+
const error = useToastStore((state) => state.error);
|
64
57
|
|
65
58
|
const dispatchAll = (action) => {
|
66
59
|
primary.dispatch(action);
|
67
60
|
images.dispatch(action);
|
68
61
|
};
|
69
62
|
|
70
|
-
const dragEnd = (dragState
|
63
|
+
const dragEnd = (dragState: Drag.State<Images.Record>, files: File[]) => {
|
71
64
|
const [draggedPrimary, draggedImages] = draggedImageOrder(
|
72
65
|
primary,
|
73
66
|
images,
|
@@ -86,40 +79,44 @@ export default function ImageGrid(props) {
|
|
86
79
|
}
|
87
80
|
};
|
88
81
|
|
89
|
-
const [dragState, dragStart, listeners] = useDragUploader(
|
82
|
+
const [dragState, dragStart, listeners] = useDragUploader<Images.Record>(
|
90
83
|
[primary, images],
|
91
84
|
dragEnd
|
92
85
|
);
|
93
86
|
|
94
|
-
const position = (record) => {
|
87
|
+
const position = (record: Images.Record) => {
|
95
88
|
return (
|
96
89
|
[
|
97
|
-
...primary.draggables.map(
|
98
|
-
|
90
|
+
...primary.draggables.map(
|
91
|
+
(d: Drag.Draggable<Images.Record>) => d.record
|
92
|
+
),
|
93
|
+
...images.draggables.map(
|
94
|
+
(d: Drag.Draggable<Images.Record>) => d.record
|
95
|
+
),
|
99
96
|
...deleted
|
100
97
|
].indexOf(record) + 1
|
101
98
|
);
|
102
99
|
};
|
103
100
|
|
104
|
-
const attrName = (record) => {
|
101
|
+
const attrName = (record: Images.Record) => {
|
105
102
|
return `${props.attribute}[${position(record)}]`;
|
106
103
|
};
|
107
104
|
|
108
|
-
const uploadImage = (file) => {
|
105
|
+
const uploadImage = (file: File) => {
|
109
106
|
const draggable = createDraggable({ image: null, file: file });
|
110
107
|
|
111
|
-
|
108
|
+
const data = new FormData();
|
112
109
|
|
113
110
|
data.append("image[file]", file);
|
114
111
|
|
115
|
-
post("/admin/images.json", data).then((json) => {
|
116
|
-
if (json.status === "error") {
|
117
|
-
error(
|
112
|
+
void post("/admin/images.json", data).then((json: Images.Response) => {
|
113
|
+
if ("status" in json && json.status === "error") {
|
114
|
+
error(`Error uploading image: ${json.error}`);
|
118
115
|
dispatchAll({ type: "remove", payload: draggable });
|
119
116
|
} else {
|
120
117
|
dispatchAll({
|
121
118
|
type: "update",
|
122
|
-
payload: { ...draggable, record: { image: json } }
|
119
|
+
payload: { ...draggable, record: { image: json } } as Drag.Draggable
|
123
120
|
});
|
124
121
|
}
|
125
122
|
});
|
@@ -127,26 +124,30 @@ export default function ImageGrid(props) {
|
|
127
124
|
return draggable;
|
128
125
|
};
|
129
126
|
|
130
|
-
const update =
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
127
|
+
const update =
|
128
|
+
(draggable: Drag.Draggable<Images.Record>) => (image: Images.Resource) => {
|
129
|
+
const { record } = draggable;
|
130
|
+
const updated = {
|
131
|
+
...draggable,
|
132
|
+
record: {
|
133
|
+
...record,
|
134
|
+
image: { ...record.image, ...image }
|
135
|
+
}
|
136
|
+
};
|
137
|
+
dispatchAll({ type: "update", payload: updated });
|
138
138
|
};
|
139
|
-
dispatchAll({ type: "update", payload: updated });
|
140
|
-
};
|
141
139
|
|
142
|
-
const remove = (draggable) => () => {
|
140
|
+
const remove = (draggable: Drag.Draggable<Images.Record>) => () => {
|
143
141
|
dispatchAll({ type: "remove", payload: draggable });
|
144
142
|
if (draggable.record.id) {
|
145
143
|
setDeleted([...deleted, draggable.record]);
|
146
144
|
}
|
147
145
|
};
|
148
146
|
|
149
|
-
const renderImage = (
|
147
|
+
const renderImage = (
|
148
|
+
draggable: Drag.Item<Images.Record>,
|
149
|
+
isPrimary: boolean
|
150
|
+
) => {
|
150
151
|
const { dragging } = dragState;
|
151
152
|
|
152
153
|
if (draggable === "Files") {
|
@@ -172,7 +173,7 @@ export default function ImageGrid(props) {
|
|
172
173
|
);
|
173
174
|
};
|
174
175
|
|
175
|
-
const uploadPrimary = (files) => {
|
176
|
+
const uploadPrimary = (files: File[]) => {
|
176
177
|
const [first, ...rest] = filterFiles(files).map((f) => uploadImage(f));
|
177
178
|
if (first) {
|
178
179
|
images.dispatch({
|
@@ -183,14 +184,14 @@ export default function ImageGrid(props) {
|
|
183
184
|
}
|
184
185
|
};
|
185
186
|
|
186
|
-
const uploadAdditional = (files) => {
|
187
|
+
const uploadAdditional = (files: File[]) => {
|
187
188
|
images.dispatch({
|
188
189
|
type: "append",
|
189
190
|
payload: filterFiles(files).map((f) => uploadImage(f))
|
190
191
|
});
|
191
192
|
};
|
192
193
|
|
193
|
-
|
194
|
+
const classNames = ["image-grid"];
|
194
195
|
if (props.enablePrimary) {
|
195
196
|
classNames.push("with-primary-image");
|
196
197
|
}
|
@@ -221,7 +222,7 @@ export default function ImageGrid(props) {
|
|
221
222
|
type="hidden"
|
222
223
|
name={props.primaryAttribute}
|
223
224
|
value={
|
224
|
-
(draggedPrimary
|
225
|
+
(draggedPrimary !== "Files" &&
|
225
226
|
draggedPrimary.record.image &&
|
226
227
|
draggedPrimary.record.image.id) ||
|
227
228
|
""
|
@@ -260,14 +261,14 @@ export default function ImageGrid(props) {
|
|
260
261
|
<span className="deleted-image" key={r.id}>
|
261
262
|
<input name={`${attrName(r)}[id]`} type="hidden" value={r.id} />
|
262
263
|
<input
|
263
|
-
name={`${attrName(r)}[
|
264
|
+
name={`${attrName(r)}[attachment_id]`}
|
264
265
|
type="hidden"
|
265
266
|
value={(r.image && r.image.id) || ""}
|
266
267
|
/>
|
267
268
|
<input
|
268
269
|
name={`${attrName(r)}[_destroy]`}
|
269
270
|
type="hidden"
|
270
|
-
value={true}
|
271
|
+
value={"true"}
|
271
272
|
/>
|
272
273
|
</span>
|
273
274
|
))}
|
@@ -275,13 +276,3 @@ export default function ImageGrid(props) {
|
|
275
276
|
</div>
|
276
277
|
);
|
277
278
|
}
|
278
|
-
|
279
|
-
ImageGrid.propTypes = {
|
280
|
-
attribute: PropTypes.string,
|
281
|
-
locale: PropTypes.string,
|
282
|
-
locales: PropTypes.object,
|
283
|
-
records: PropTypes.array,
|
284
|
-
enablePrimary: PropTypes.bool,
|
285
|
-
primaryAttribute: PropTypes.string,
|
286
|
-
showEmbed: PropTypes.bool
|
287
|
-
};
|
@@ -1,21 +1,17 @@
|
|
1
|
-
import React, { useEffect, useState } from "react";
|
1
|
+
import React, { MouseEvent, useEffect, useState } from "react";
|
2
|
+
|
2
3
|
import copyToClipboard from "../../lib/copyToClipboard";
|
3
|
-
import EditableImage from "../EditableImage";
|
4
4
|
import useToastStore from "../../stores/useToastStore";
|
5
|
-
import
|
6
|
-
import
|
7
|
-
|
5
|
+
import * as Drag from "../../types/Drag";
|
6
|
+
import * as Images from "../../types/Images";
|
7
|
+
import { Locale } from "../../types";
|
8
8
|
import { useDraggable } from "../drag";
|
9
9
|
|
10
|
-
|
11
|
-
|
12
|
-
image: ImageResource;
|
13
|
-
src: string | null;
|
14
|
-
file: File | null;
|
15
|
-
}
|
10
|
+
import EditableImage from "../EditableImage";
|
11
|
+
import Placeholder from "./Placeholder";
|
16
12
|
|
17
|
-
interface
|
18
|
-
draggable:
|
13
|
+
interface Props {
|
14
|
+
draggable: Drag.Draggable<Images.Record>;
|
19
15
|
attributeName: string;
|
20
16
|
locale: string;
|
21
17
|
locales: { [index: string]: Locale };
|
@@ -25,36 +21,39 @@ interface GridImageProps {
|
|
25
21
|
primary: boolean;
|
26
22
|
position: number;
|
27
23
|
deleteImage: () => void;
|
28
|
-
startDrag: (
|
29
|
-
|
24
|
+
startDrag: (
|
25
|
+
evt: MouseEvent,
|
26
|
+
draggable: Drag.Draggable<Images.Record>
|
27
|
+
) => void;
|
28
|
+
onUpdate: (newImage: Images.Resource, src: string) => void;
|
30
29
|
}
|
31
30
|
|
32
|
-
export default function GridImage(props:
|
31
|
+
export default function GridImage(props: Props) {
|
33
32
|
const { attributeName, draggable } = props;
|
34
33
|
const record = draggable.record;
|
35
34
|
const image = record.image;
|
36
35
|
|
37
36
|
const notice = useToastStore((state) => state.notice);
|
38
37
|
|
39
|
-
const [src, setSrc] = useState(record.src || null);
|
38
|
+
const [src, setSrc] = useState<string>(record.src || null);
|
40
39
|
|
41
40
|
const dragAttrs = useDraggable(draggable, props.startDrag);
|
42
41
|
|
43
42
|
useEffect(() => {
|
44
|
-
if (record.file) {
|
43
|
+
if ("file" in record && record.file) {
|
45
44
|
const reader = new FileReader();
|
46
|
-
reader.onload = () => setSrc(reader.result);
|
45
|
+
reader.onload = () => setSrc(reader.result as string);
|
47
46
|
reader.readAsDataURL(record.file);
|
48
47
|
}
|
49
48
|
}, []);
|
50
49
|
|
51
|
-
const copyEmbed = (evt:
|
50
|
+
const copyEmbed = (evt: MouseEvent) => {
|
52
51
|
evt.preventDefault();
|
53
52
|
copyToClipboard(`[image:${image.id}]`);
|
54
53
|
notice("Embed code copied to clipboard");
|
55
54
|
};
|
56
55
|
|
57
|
-
const deleteImage = (evt:
|
56
|
+
const deleteImage = (evt: MouseEvent) => {
|
58
57
|
evt.preventDefault();
|
59
58
|
if (props.deleteImage) {
|
60
59
|
props.deleteImage();
|
@@ -65,7 +64,7 @@ export default function GridImage(props: GridImageProps) {
|
|
65
64
|
if (props.placeholder) {
|
66
65
|
classes.push("placeholder");
|
67
66
|
}
|
68
|
-
if (record
|
67
|
+
if ("file" in record) {
|
69
68
|
classes.push("uploading");
|
70
69
|
}
|
71
70
|
|
@@ -90,7 +89,7 @@ export default function GridImage(props: GridImageProps) {
|
|
90
89
|
<input
|
91
90
|
name={`${attributeName}[primary]`}
|
92
91
|
type="hidden"
|
93
|
-
value={props.primary}
|
92
|
+
value={props.primary ? "true" : "false"}
|
94
93
|
/>
|
95
94
|
)}
|
96
95
|
{!image && <Placeholder src={src} />}
|
@@ -1,10 +1,10 @@
|
|
1
1
|
import React from "react";
|
2
2
|
|
3
|
-
interface
|
3
|
+
interface Props {
|
4
4
|
src: string;
|
5
5
|
}
|
6
6
|
|
7
|
-
export default function Placeholder(props:
|
7
|
+
export default function Placeholder(props: Props) {
|
8
8
|
if (props.src) {
|
9
9
|
return (
|
10
10
|
<div className="temp-image">
|