pages_core 3.13.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 +704 -388
- 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 +26 -0
- 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 +87 -0
- data/app/controllers/admin/invites_controller.rb +3 -2
- data/app/controllers/admin/otp_secrets_controller.rb +45 -0
- data/app/controllers/admin/pages_controller.rb +22 -42
- data/app/controllers/admin/recovery_codes_controller.rb +32 -0
- data/app/controllers/admin/sessions_controller.rb +65 -0
- data/app/controllers/admin/users_controller.rb +2 -8
- data/app/controllers/concerns/pages_core/authentication.rb +12 -10
- 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 +1 -3
- 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 +13 -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 -14
- 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/mailers/admin_mailer.rb +2 -2
- 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 +101 -0
- data/app/models/page.rb +0 -3
- data/app/models/user.rb +2 -68
- data/app/policies/page_policy.rb +6 -2
- data/app/policies/user_policy.rb +4 -0
- 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/account_recoveries/new.html.erb +22 -0
- data/app/views/admin/account_recoveries/show.html.erb +37 -0
- data/app/views/admin/invites/show.html.erb +1 -1
- data/app/views/admin/news/_sidebar.html.erb +2 -4
- data/app/views/admin/news/index.html.erb +0 -1
- data/app/views/admin/otp_secrets/create.html.erb +7 -0
- data/app/views/admin/otp_secrets/new.html.erb +60 -0
- 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/recovery_codes/_codes.html.erb +14 -0
- data/app/views/admin/recovery_codes/create.html.erb +7 -0
- data/app/views/admin/recovery_codes/new.html.erb +11 -0
- data/app/views/admin/sessions/_otp_form.html.erb +13 -0
- data/app/views/admin/sessions/new.html.erb +31 -0
- data/app/views/admin/sessions/verify_otp.html.erb +19 -0
- data/app/views/admin/users/_access_control.html.erb +5 -1
- data/app/views/admin/users/_list.html.erb +12 -7
- data/app/views/admin/users/edit.html.erb +31 -1
- data/app/views/admin/users/new.html.erb +1 -1
- data/app/views/admin_mailer/account_recovery.text.erb +10 -0
- data/app/views/layouts/admin/_header.html.erb +3 -5
- data/app/views/layouts/admin/_page_header.html.erb +1 -2
- data/app/views/layouts/admin/_toast.html.erb +12 -0
- data/app/views/layouts/admin.html.erb +2 -2
- data/config/locales/en.yml +11 -7
- data/config/routes.rb +13 -12
- data/db/migrate/20240126160700_add_2fa_fields.rb +26 -0
- data/db/migrate/20240129201300_remove_password_reset_tokens.rb +13 -0
- 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/pages_core.rb +6 -0
- 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 +143 -35
- data/app/assets/stylesheets/pages_core/admin/components/login.css +0 -33
- data/app/controllers/admin/categories_controller.rb +0 -56
- data/app/controllers/admin/password_resets_controller.rb +0 -85
- data/app/controllers/concerns/pages_core/admin/persistent_params.rb +0 -75
- data/app/controllers/sessions_controller.rb +0 -27
- 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/LoginController.ts +0 -32
- 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/page_category.rb +0 -6
- data/app/models/password_reset_token.rb +0 -34
- 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/app/views/admin/password_resets/show.html.erb +0 -21
- data/app/views/admin/users/login.html.erb +0 -65
- data/app/views/admin_mailer/password_reset.text.erb +0 -11
- data/lib/rails/generators/pages_core/rspec/templates/mailer_macros.rb +0 -11
@@ -1,20 +1,22 @@
|
|
1
|
-
import React, { useState } from "react";
|
2
|
-
|
1
|
+
import React, { MouseEvent, useState } from "react";
|
2
|
+
|
3
3
|
import useModalStore from "../stores/useModalStore";
|
4
|
+
import * as Images from "../types/Images";
|
5
|
+
import { Locale } from "../types";
|
4
6
|
|
5
|
-
import
|
7
|
+
import ImageEditor from "./ImageEditor";
|
6
8
|
|
7
|
-
interface
|
8
|
-
image:
|
9
|
+
interface Props {
|
10
|
+
image: Images.Resource;
|
9
11
|
src: string;
|
10
12
|
caption: boolean;
|
11
13
|
locale: string;
|
12
14
|
locales: Record<string, Locale>;
|
13
15
|
width: number;
|
14
|
-
onUpdate?: (newImage:
|
16
|
+
onUpdate?: (newImage: Images.Resource, src: string) => void;
|
15
17
|
}
|
16
18
|
|
17
|
-
export default function EditableImage(props:
|
19
|
+
export default function EditableImage(props: Props) {
|
18
20
|
const [image, setImage] = useState(props.image);
|
19
21
|
const [src, setSrc] = useState(props.src);
|
20
22
|
|
@@ -26,7 +28,7 @@ export default function EditableImage(props: EditableImageProps) {
|
|
26
28
|
return Math.round((height / width) * props.width);
|
27
29
|
};
|
28
30
|
|
29
|
-
const updateImage = (updatedImage:
|
31
|
+
const updateImage = (updatedImage: Images.Resource, src: string) => {
|
30
32
|
const newImage = { ...image, ...updatedImage };
|
31
33
|
setSrc(src);
|
32
34
|
setImage(newImage);
|
@@ -35,7 +37,7 @@ export default function EditableImage(props: EditableImageProps) {
|
|
35
37
|
}
|
36
38
|
};
|
37
39
|
|
38
|
-
const handleClick = (evt:
|
40
|
+
const handleClick = (evt: MouseEvent) => {
|
39
41
|
evt.preventDefault();
|
40
42
|
openModal(
|
41
43
|
<ImageEditor
|
@@ -1,13 +1,13 @@
|
|
1
|
-
import React, { ChangeEvent, useRef } from "react";
|
1
|
+
import React, { ChangeEvent, MouseEvent, useRef } from "react";
|
2
2
|
|
3
|
-
interface
|
3
|
+
interface Props {
|
4
4
|
callback: (files: File[]) => void;
|
5
|
-
type
|
6
|
-
|
7
|
-
|
5
|
+
type?: string;
|
6
|
+
multiline?: boolean;
|
7
|
+
multiple?: boolean;
|
8
8
|
}
|
9
9
|
|
10
|
-
export default function FileUploadButton(props:
|
10
|
+
export default function FileUploadButton(props: Props) {
|
11
11
|
const inputRef = useRef<HTMLInputElement>();
|
12
12
|
|
13
13
|
const handleChange = (evt: ChangeEvent<HTMLInputElement>) => {
|
@@ -21,7 +21,7 @@ export default function FileUploadButton(props: FileUploadButtonProps) {
|
|
21
21
|
}
|
22
22
|
};
|
23
23
|
|
24
|
-
const triggerDialog = (evt:
|
24
|
+
const triggerDialog = (evt: MouseEvent) => {
|
25
25
|
evt.preventDefault();
|
26
26
|
inputRef.current.click();
|
27
27
|
};
|
@@ -1,14 +1,11 @@
|
|
1
|
-
import React, { useRef, useState } from "react";
|
1
|
+
import React, { MouseEvent, TouchEvent, useRef, useState } from "react";
|
2
2
|
|
3
|
-
|
4
|
-
x: number;
|
5
|
-
y: number;
|
6
|
-
}
|
3
|
+
import * as Crop from "../../types/Crop";
|
7
4
|
|
8
|
-
interface
|
5
|
+
interface Props {
|
9
6
|
x: number;
|
10
7
|
y: number;
|
11
|
-
onChange: (pos: Position) => void;
|
8
|
+
onChange: (pos: Crop.Position) => void;
|
12
9
|
width: number;
|
13
10
|
height: number;
|
14
11
|
}
|
@@ -23,11 +20,11 @@ function clamp(val: number, min: number, max: number): number {
|
|
23
20
|
}
|
24
21
|
}
|
25
22
|
|
26
|
-
export default function FocalPoint(props:
|
23
|
+
export default function FocalPoint(props: Props) {
|
27
24
|
const { width, height, onChange } = props;
|
28
25
|
|
29
26
|
const [dragging, setDragging] = useState(false);
|
30
|
-
const [position, setPosition] = useState<Position>({
|
27
|
+
const [position, setPosition] = useState<Crop.Position>({
|
31
28
|
x: props.x,
|
32
29
|
y: props.y
|
33
30
|
});
|
@@ -35,7 +32,7 @@ export default function FocalPoint(props: FocalPointProps) {
|
|
35
32
|
const containerRef = useRef<HTMLDivElement>();
|
36
33
|
const pointRef = useRef<HTMLDivElement>();
|
37
34
|
|
38
|
-
const dragStart = (evt:
|
35
|
+
const dragStart = (evt: MouseEvent | TouchEvent) => {
|
39
36
|
evt.preventDefault();
|
40
37
|
evt.stopPropagation();
|
41
38
|
if (evt.target == pointRef.current) {
|
@@ -50,7 +47,7 @@ export default function FocalPoint(props: FocalPointProps) {
|
|
50
47
|
}
|
51
48
|
};
|
52
49
|
|
53
|
-
const drag = (evt:
|
50
|
+
const drag = (evt: MouseEvent | TouchEvent) => {
|
54
51
|
if (dragging) {
|
55
52
|
let x: number, y: number;
|
56
53
|
const containerSize = containerRef.current.getBoundingClientRect();
|
@@ -59,7 +56,7 @@ export default function FocalPoint(props: FocalPointProps) {
|
|
59
56
|
if ("touches" in evt && evt.type == "touchmove") {
|
60
57
|
x = evt.touches[0].clientX - (containerSize.x || containerSize.left);
|
61
58
|
y = evt.touches[0].clientY - (containerSize.y || containerSize.top);
|
62
|
-
} else {
|
59
|
+
} else if ("clientX" in evt) {
|
63
60
|
x = evt.clientX - (containerSize.x || containerSize.left);
|
64
61
|
y = evt.clientY - (containerSize.y || containerSize.top);
|
65
62
|
}
|
@@ -1,19 +1,21 @@
|
|
1
1
|
import React from "react";
|
2
2
|
import ReactCrop from "react-image-crop";
|
3
3
|
|
4
|
-
import
|
4
|
+
import * as Crop from "../../types/Crop";
|
5
|
+
|
6
|
+
import { cropSize } from "./useCrop";
|
5
7
|
import FocalPoint from "./FocalPoint";
|
6
8
|
|
7
|
-
interface
|
8
|
-
containerSize: Size;
|
9
|
+
interface Props {
|
10
|
+
containerSize: Crop.Size;
|
9
11
|
croppedImage: string;
|
10
|
-
cropState:
|
11
|
-
focalPoint: Position;
|
12
|
-
setCrop: (crop: CropSize) => void;
|
13
|
-
setFocal: (focal: Position) => void;
|
12
|
+
cropState: Crop.State;
|
13
|
+
focalPoint: Crop.Position;
|
14
|
+
setCrop: (crop: Crop.CropSize) => void;
|
15
|
+
setFocal: (focal: Crop.Position) => void;
|
14
16
|
}
|
15
17
|
|
16
|
-
export default function Image(props:
|
18
|
+
export default function Image(props: Props) {
|
17
19
|
const imageSize = () => {
|
18
20
|
const { image, cropping, crop_width, crop_height } = props.cropState;
|
19
21
|
if (cropping) {
|
@@ -1,22 +1,22 @@
|
|
1
|
-
import React from "react";
|
1
|
+
import React, { MouseEvent } from "react";
|
2
2
|
|
3
|
-
import
|
4
|
-
import
|
3
|
+
import * as Crop from "../../types/Crop";
|
4
|
+
import * as Images from "../../types/Images";
|
5
5
|
|
6
6
|
type Ratio = number | null;
|
7
7
|
|
8
|
-
interface
|
9
|
-
cropState:
|
10
|
-
image:
|
8
|
+
interface Props {
|
9
|
+
cropState: Crop.State;
|
10
|
+
image: Images.Resource;
|
11
11
|
setAspect: (Ratio) => void;
|
12
|
-
toggleCrop: (evt:
|
13
|
-
toggleFocal: (evt:
|
12
|
+
toggleCrop: (evt: MouseEvent) => void;
|
13
|
+
toggleFocal: (evt: MouseEvent) => void;
|
14
14
|
}
|
15
15
|
|
16
|
-
export default function Toolbar(props:
|
16
|
+
export default function Toolbar(props: Props) {
|
17
17
|
const { cropping } = props.cropState;
|
18
18
|
|
19
|
-
const aspectRatios = [
|
19
|
+
const aspectRatios: Array<[string, Ratio]> = [
|
20
20
|
["Free", null],
|
21
21
|
["1:1", 1],
|
22
22
|
["3:2", 3 / 2],
|
@@ -28,7 +28,7 @@ export default function Toolbar(props: ToolbarProps) {
|
|
28
28
|
["16:9", 16 / 9]
|
29
29
|
];
|
30
30
|
|
31
|
-
const updateAspect = (ratio: Ratio) => (evt:
|
31
|
+
const updateAspect = (ratio: Ratio) => (evt: MouseEvent) => {
|
32
32
|
evt.preventDefault();
|
33
33
|
props.setAspect(ratio);
|
34
34
|
};
|
@@ -61,7 +61,6 @@ export default function Toolbar(props: ToolbarProps) {
|
|
61
61
|
href={props.image.original_url}
|
62
62
|
className="button"
|
63
63
|
title="Download original image"
|
64
|
-
disabled={cropping}
|
65
64
|
download={props.image.filename}
|
66
65
|
onClick={(evt) => cropping && evt.preventDefault()}>
|
67
66
|
<i className="fa-solid fa-download" />
|
@@ -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
|
}
|