From 123665d3c95077a6aff06f742a7aecf5ea38969c Mon Sep 17 00:00:00 2001 From: Tatiana Lopaeva Date: Tue, 25 Jun 2024 17:05:06 +0300 Subject: [PATCH] Added icon preview for watermark. --- .../Watermarks/ImageWatermark.js | 146 +++++++++++++----- .../Watermarks/StyledComponent.js | 31 +++- .../sub-components/Watermarks/ViewerInfo.js | 2 +- .../client/src/store/CreateEditRoomStore.js | 10 +- .../image-editor/ButtonDelete/index.tsx | 123 +++++++++++++++ .../image-editor/ImageCropper/index.tsx | 14 +- .../image-editor/ImageEditor.styled.ts | 44 ------ .../shared/components/image-editor/index.tsx | 3 +- 8 files changed, 269 insertions(+), 104 deletions(-) create mode 100644 packages/shared/components/image-editor/ButtonDelete/index.tsx diff --git a/packages/client/src/components/dialogs/CreateEditRoomDialog/sub-components/Watermarks/ImageWatermark.js b/packages/client/src/components/dialogs/CreateEditRoomDialog/sub-components/Watermarks/ImageWatermark.js index 75fc399e29..93e54e793a 100644 --- a/packages/client/src/components/dialogs/CreateEditRoomDialog/sub-components/Watermarks/ImageWatermark.js +++ b/packages/client/src/components/dialogs/CreateEditRoomDialog/sub-components/Watermarks/ImageWatermark.js @@ -52,14 +52,19 @@ import { useState, useRef, useEffect } from "react"; import { useTranslation } from "react-i18next"; +import { ReactSVG } from "react-svg"; + import { Text } from "@docspace/shared/components/text"; import { ComboBox } from "@docspace/shared/components/combobox"; import { inject, observer } from "mobx-react"; import { StyledWatermark } from "./StyledComponent"; - import { DropDownItem } from "@docspace/shared/components/drop-down-item"; import { FileInput } from "@docspace/shared/components/file-input"; import { imageProcessing } from "@docspace/shared/utils/common"; +import { Button } from "@docspace/shared/components/button"; + +import TrashReactSvgUrl from "PUBLIC_DIR/images/trash.react.svg"; +import { ButtonDelete } from "@docspace/shared/components/image-editor"; const scaleOptions = [ { key: 100, label: "100" }, @@ -98,10 +103,12 @@ const ImageWatermark = ({ isEdit, setWatermarks, initialWatermarksSettings, + imageUrl, }) => { const { t } = useTranslation(["CreateEditRoomDialog", "Common"]); const initialInfo = useRef(null); + const previewRef = useRef(null); if (initialInfo.current === null) { initialInfo.current = { @@ -113,25 +120,42 @@ const ImageWatermark = ({ const initialInfoRef = initialInfo.current; useEffect(() => { - if (!isEdit) return; + if (isEdit) return; setWatermarks({ rotate: initialInfoRef.rotate.key, + scale: initialInfoRef.scale.key, additions: 0, isImage: true, enabled: true, }); }, []); + useEffect(() => { + return () => { + URL.revokeObjectURL(previewRef.current); + previewRef.current = null; + }; + }, []); const [selectedRotate, setRotate] = useState(initialInfoRef.rotate); const [selectedScale, setScale] = useState(initialInfoRef.scale); + const [selectedImageUrl, setImageUrl] = useState(imageUrl); const onInput = (file) => { - console.log("onInput", file); - imageProcessing(file) .then((f) => { - if (f instanceof File) setWatermarks({ image: f }); + if (f instanceof File) { + setWatermarks({ image: f }); + + const img = new Image(); + + previewRef.current = URL.createObjectURL(f); + img.src = previewRef.current; + + img.onload = () => { + setImageUrl(previewRef.current); + }; + } }) .catch((error) => { if ( @@ -155,6 +179,16 @@ const ImageWatermark = ({ setWatermarks({ rotate: item.key }); }; + const onButtonClick = () => { + if (previewRef.current) { + URL.revokeObjectURL(previewRef.current); + previewRef.current = null; + } + + setWatermarks({ image: null }); + setImageUrl(""); + }; + const rotateItems = () => { const items = rotateOptions.map((item) => { return ( @@ -194,8 +228,18 @@ const ImageWatermark = ({ // }; return ( - - + + {!selectedImageUrl && ( + + )} {/* */} -
-
- - {t("Scale")} - - -
{selectedScale.label}%
-
-
-
- - {t("Rotate")} - - -
{selectedRotate.label}°
-
+ {selectedImageUrl && ( +
+
+
+ logo +
+ +
+ +
+
+ + {t("Scale")} + + +
{selectedScale.label}%
+
+
+
+ + {t("Rotate")} + + + +
{selectedRotate.label}°
+
+
+
-
+ )} ); }; export default inject(({ createEditRoomStore }) => { - const { setWatermarks, initialWatermarksSettings } = createEditRoomStore; + const { setWatermarks, initialWatermarksSettings, watermarksSettings } = + createEditRoomStore; + const { imageUrl } = watermarksSettings; + console.log("watermarksSettings", watermarksSettings); return { setWatermarks, initialWatermarksSettings, + imageUrl, }; })(observer(ImageWatermark)); diff --git a/packages/client/src/components/dialogs/CreateEditRoomDialog/sub-components/Watermarks/StyledComponent.js b/packages/client/src/components/dialogs/CreateEditRoomDialog/sub-components/Watermarks/StyledComponent.js index e1c87b50fd..aab74869ac 100644 --- a/packages/client/src/components/dialogs/CreateEditRoomDialog/sub-components/Watermarks/StyledComponent.js +++ b/packages/client/src/components/dialogs/CreateEditRoomDialog/sub-components/Watermarks/StyledComponent.js @@ -24,7 +24,7 @@ // content are licensed under the terms of the Creative Commons Attribution-ShareAlike 4.0 // International. See the License terms at http://creativecommons.org/licenses/by-sa/4.0/legalcode -import styled from "styled-components"; +import styled, { css } from "styled-components"; const StyledWatermark = styled.div` margin-top: 16px; @@ -41,9 +41,36 @@ const StyledWatermark = styled.div` .options-wrapper { display: grid; - grid-template-columns: minmax(216px, 1fr) minmax(216px, 1fr); + grid-template-rows: 56px 56px; gap: 16px; } + + .image-wrapper { + display: grid; + grid-template-columns: 216px auto; + gap: 16px; + + .HELLO { + width: 216px; + height: 216px; + border: 1px solid #eceef1; + + overflow: hidden; + display: flex; + justify-content: center; + align-items: center; + + img { + width: 88%; + height: 88%; + transform: ${(props) => + `rotate(${props.rotate}deg) scale(${props.scale})`}; + + opacity: 0.4; + margin: auto; + } + } + } `; const StyledBody = styled.div` .types-content { diff --git a/packages/client/src/components/dialogs/CreateEditRoomDialog/sub-components/Watermarks/ViewerInfo.js b/packages/client/src/components/dialogs/CreateEditRoomDialog/sub-components/Watermarks/ViewerInfo.js index e059bd9ad2..bfed57c027 100644 --- a/packages/client/src/components/dialogs/CreateEditRoomDialog/sub-components/Watermarks/ViewerInfo.js +++ b/packages/client/src/components/dialogs/CreateEditRoomDialog/sub-components/Watermarks/ViewerInfo.js @@ -136,7 +136,7 @@ const ViewerInfoWatermark = ({ const initialInfoRef = initialInfo.current; useEffect(() => { - if (!isEdit) return; + if (isEdit) return; setWatermarks({ rotate: initialInfoRef.rotate.key, diff --git a/packages/client/src/store/CreateEditRoomStore.js b/packages/client/src/store/CreateEditRoomStore.js index 26f37b673e..a7e14eaa21 100644 --- a/packages/client/src/store/CreateEditRoomStore.js +++ b/packages/client/src/store/CreateEditRoomStore.js @@ -146,6 +146,8 @@ class CreateEditRoomStore { } const watermarkImage = this.watermarksSettings.image; + const watermarksSettings = this.watermarksSettings; + const getMeta = (url, onSetInfo) => { //url for this.watermarksSettings.image.viewUrl const img = new Image(); @@ -174,11 +176,11 @@ class CreateEditRoomStore { } return setWatermarkSettings(room.id, { - enabled: this.watermarksSettings.enabled, - imageScale: this.watermarksSettings.imageScale, - rotate: this.watermarksSettings.rotate, + enabled: watermarksSettings.enabled, + imageScale: watermarksSettings.imageScale, + rotate: watermarksSettings.rotate, imageUrl: response.data, - // imageId: this.watermarksSettings.image.id, + // imageId: watermarksSettings.image.id, imageWidth: img.naturalWidth, imageHeight: img.naturalHeight, }); diff --git a/packages/shared/components/image-editor/ButtonDelete/index.tsx b/packages/shared/components/image-editor/ButtonDelete/index.tsx new file mode 100644 index 0000000000..a8d2b7e569 --- /dev/null +++ b/packages/shared/components/image-editor/ButtonDelete/index.tsx @@ -0,0 +1,123 @@ +// (c) Copyright Ascensio System SIA 2009-2024 +// +// This program is a free software product. +// You can redistribute it and/or modify it under the terms +// of the GNU Affero General Public License (AGPL) version 3 as published by the Free Software +// Foundation. In accordance with Section 7(a) of the GNU AGPL its Section 15 shall be amended +// to the effect that Ascensio System SIA expressly excludes the warranty of non-infringement of +// any third-party rights. +// +// This program is distributed WITHOUT ANY WARRANTY, without even the implied warranty +// of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. For details, see +// the GNU AGPL at: http://www.gnu.org/licenses/agpl-3.0.html +// +// You can contact Ascensio System SIA at Lubanas st. 125a-25, Riga, Latvia, EU, LV-1021. +// +// The interactive user interfaces in modified source and object code versions of the Program must +// display Appropriate Legal Notices, as required under Section 5 of the GNU AGPL version 3. +// +// Pursuant to Section 7(b) of the License you must retain the original Product logo when +// distributing the program. Pursuant to Section 7(e) we decline to grant you any rights under +// trademark law for use of our trademarks. +// +// All the Product's GUI elements, including illustrations and icon sets, as well as technical writing +// content are licensed under the terms of the Creative Commons Attribution-ShareAlike 4.0 +// International. See the License terms at http://creativecommons.org/licenses/by-sa/4.0/legalcode +// (c) Copyright Ascensio System SIA 2009-2024 +// +// This program is a free software product. +// You can redistribute it and/or modify it under the terms +// of the GNU Affero General Public License (AGPL) version 3 as published by the Free Software +// Foundation. In accordance with Section 7(a) of the GNU AGPL its Section 15 shall be amended +// to the effect that Ascensio System SIA expressly excludes the warranty of non-infringement of +// any third-party rights. +// +// This program is distributed WITHOUT ANY WARRANTY, without even the implied warranty +// of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. For details, see +// the GNU AGPL at: http://www.gnu.org/licenses/agpl-3.0.html +// +// You can contact Ascensio System SIA at Lubanas st. 125a-25, Riga, Latvia, EU, LV-1021. +// +// The interactive user interfaces in modified source and object code versions of the Program must +// display Appropriate Legal Notices, as required under Section 5 of the GNU AGPL version 3. +// +// Pursuant to Section 7(b) of the License you must retain the original Product logo when +// distributing the program. Pursuant to Section 7(e) we decline to grant you any rights under +// trademark law for use of our trademarks. +// +// All the Product's GUI elements, including illustrations and icon sets, as well as technical writing +// content are licensed under the terms of the Creative Commons Attribution-ShareAlike 4.0 +// International. See the License terms at http://creativecommons.org/licenses/by-sa/4.0/legalcode + +import { ReactSVG } from "react-svg"; +import styled from "styled-components"; + +import TrashReactSvgUrl from "PUBLIC_DIR/images/trash.react.svg?url"; +import { TTranslation } from "../../../types"; + +const StyledButton = styled.div` + cursor: pointer; + display: flex; + flex-direction: row; + align-items: center; + justify-content: center; + gap: 8px; + width: 100%; + padding: 6px 0; + background: ${(props) => + props.theme.createEditRoomDialog.iconCropper.deleteButton.background}; + border: 1px solid + ${(props) => + props.theme.createEditRoomDialog.iconCropper.deleteButton.borderColor}; + border-radius: 3px; + margin-bottom: 12px; + + transition: all 0.2s ease; + &:hover { + background: ${(props) => + props.theme.createEditRoomDialog.iconCropper.deleteButton + .hoverBackground}; + border: 1px solid + ${(props) => + props.theme.createEditRoomDialog.iconCropper.deleteButton + .hoverBorderColor}; + } + + &-text { + user-select: none; + font-weight: 600; + line-height: 20px; + color: ${(props) => + props.theme.createEditRoomDialog.iconCropper.deleteButton.color}; + } + + svg { + path { + fill: ${(props) => + props.theme.createEditRoomDialog.iconCropper.deleteButton.iconColor}; + } + } +`; + +const ButtonDelete = ({ + onClick, + t, +}: { + onClick: (e: React.MouseEvent) => void; + t: TTranslation; +}) => { + return ( + + +
+ {t("Common:Delete")} +
+
+ ); +}; + +export default ButtonDelete; diff --git a/packages/shared/components/image-editor/ImageCropper/index.tsx b/packages/shared/components/image-editor/ImageCropper/index.tsx index b520d4a65f..11ffbc7172 100644 --- a/packages/shared/components/image-editor/ImageCropper/index.tsx +++ b/packages/shared/components/image-editor/ImageCropper/index.tsx @@ -32,12 +32,12 @@ import AvatarEditor, { Position } from "react-avatar-editor"; import ZoomMinusReactSvgUrl from "PUBLIC_DIR/images/zoom-minus.react.svg?url"; import ZoomPlusReactSvgUrl from "PUBLIC_DIR/images/zoom-plus.react.svg?url"; import IconCropperGridSvgUrl from "PUBLIC_DIR/images/icon-cropper-grid.svg?url"; -import TrashReactSvgUrl from "PUBLIC_DIR/images/trash.react.svg?url"; import { Slider } from "../../slider"; import { IconButton } from "../../icon-button"; import { StyledImageCropper } from "../ImageEditor.styled"; import { ImageCropperProps } from "../ImageEditor.types"; +import ButtonDelete from "../ButtonDelete"; const ImageCropper = ({ t, @@ -132,16 +132,8 @@ const ImageCropper = ({ crossOrigin="anonymous" />
-
- -
- {t("Common:Delete")} -
-
+ + {typeof uploadedFile !== "string" && uploadedFile?.name && diff --git a/packages/shared/components/image-editor/ImageEditor.styled.ts b/packages/shared/components/image-editor/ImageEditor.styled.ts index e65a20f69e..9f858ae972 100644 --- a/packages/shared/components/image-editor/ImageEditor.styled.ts +++ b/packages/shared/components/image-editor/ImageEditor.styled.ts @@ -63,50 +63,6 @@ const StyledImageCropper = styled.div<{ disableImageRescaling?: boolean }>` `}; } - .icon_cropper-delete_button { - cursor: pointer; - display: flex; - flex-direction: row; - align-items: center; - justify-content: center; - gap: 8px; - width: 100%; - padding: 6px 0; - background: ${(props) => - props.theme.createEditRoomDialog.iconCropper.deleteButton.background}; - border: 1px solid - ${(props) => - props.theme.createEditRoomDialog.iconCropper.deleteButton.borderColor}; - border-radius: 3px; - margin-bottom: 12px; - - transition: all 0.2s ease; - &:hover { - background: ${(props) => - props.theme.createEditRoomDialog.iconCropper.deleteButton - .hoverBackground}; - border: 1px solid - ${(props) => - props.theme.createEditRoomDialog.iconCropper.deleteButton - .hoverBorderColor}; - } - - &-text { - user-select: none; - font-weight: 600; - line-height: 20px; - color: ${(props) => - props.theme.createEditRoomDialog.iconCropper.deleteButton.color}; - } - - svg { - path { - fill: ${(props) => - props.theme.createEditRoomDialog.iconCropper.deleteButton.iconColor}; - } - } - } - .icon_cropper-zoom-container { display: flex; flex-direction: row; diff --git a/packages/shared/components/image-editor/index.tsx b/packages/shared/components/image-editor/index.tsx index da7f0d5332..f20aea714f 100644 --- a/packages/shared/components/image-editor/index.tsx +++ b/packages/shared/components/image-editor/index.tsx @@ -27,6 +27,7 @@ import React from "react"; import Dropzone from "./Dropzone"; import ImageCropper from "./ImageCropper"; +import ButtonDelete from "./ButtonDelete"; import { ImageEditorProps } from "./ImageEditor.types"; import AvatarPreview from "./AvatarPreview"; @@ -75,4 +76,4 @@ const ImageEditor = ({ ); }; -export { ImageEditor, AvatarPreview, Dropzone }; +export { ImageEditor, AvatarPreview, Dropzone, ButtonDelete };