From df808d2e15917771c1b7457da4ae926e4096566b Mon Sep 17 00:00:00 2001 From: Tatiana Lopaeva Date: Mon, 13 May 2024 20:42:56 +0300 Subject: [PATCH] Web: Added a component with an image. --- .../Watermarks/ImageWatermark.js | 187 ++++++++++++++++++ .../Watermarks/StyledComponent.js | 6 + .../sub-components/Watermarks/ViewerInfo.js | 102 ++++++++-- .../sub-components/Watermarks/index.js | 110 +++-------- .../client/src/store/CreateEditRoomStore.js | 52 +++-- 5 files changed, 347 insertions(+), 110 deletions(-) create mode 100644 packages/client/src/components/dialogs/CreateEditRoomDialog/sub-components/Watermarks/ImageWatermark.js 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 new file mode 100644 index 0000000000..c99e626029 --- /dev/null +++ b/packages/client/src/components/dialogs/CreateEditRoomDialog/sub-components/Watermarks/ImageWatermark.js @@ -0,0 +1,187 @@ +// (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 { useState, useRef, useEffect } from "react"; +import { useTranslation } from "react-i18next"; +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 { FileInput } from "@docspace/shared/components/file-input"; + +import { imageProcessing } from "@docspace/shared/utils/common"; + +const scaleOptions = () => [ + { key: 100, label: "100" }, + { key: 200, label: "200" }, + { key: 300, label: "300" }, + { key: 400, label: "400" }, + { key: 500, label: "500" }, +]; +const getInitialScale = (scale, isEdit) => { + const dataScale = scaleOptions(); + + if (!isEdit || scale === undefined || scale === 0) return dataScale[0]; + + return dataScale.find((item) => { + return item.key === scale; + }); +}; + +const ImageWatermark = ({ + getInitialRotate, + isEdit, + rotateOptions, + + setWatermarks, + watermarksSettings, +}) => { + const { t } = useTranslation(["CreateEditRoomDialog", "Common"]); + + const initialInfo = useRef(null); + + if (initialInfo.current === null) { + initialInfo.current = { + dataScale: scaleOptions(), + dataRotate: rotateOptions(t), + rotate: getInitialRotate(watermarksSettings?.rotate, isEdit, true, t), + scale: getInitialScale(watermarksSettings?.imageScale, isEdit), + url: watermarksSettings?.imageUrl ?? "", + }; + } + + const initialInfoRef = initialInfo.current; + + useEffect(() => { + setWatermarks({ + rotate: initialInfoRef.rotate.key, + imageScale: initialInfoRef.scale.key, + imageUrl: initialInfoRef.url, + text: "", + enabled: true, + additions: 0, + }); + }, []); + + const [selectedRotate, setRotate] = useState(initialInfoRef.rotate); + const [selectedScale, setScale] = useState(initialInfoRef.scale); + + const onInput = (file) => { + console.log("onInput", file); + + imageProcessing(file) + .then((f) => { + if (f instanceof File) + setWatermarks({ ...watermarksSettings, image: f }); + }) + .catch((error) => { + if ( + error instanceof Error && + error.message === "recursion depth exceeded" + ) { + toastr.error(t("Common:SizeImageLarge")); + } + }); + }; + const onScaleChange = (item) => { + setScale(item); + + setWatermarks({ ...watermarksSettings, imageScale: item.key }); + }; + + const onRotateChange = (item) => { + setRotate(item); + + setWatermarks({ ...watermarksSettings, rotate: item.key }); + }; + + return ( + + + +
+
+ + {t("Scale")} + + +
+
+ + {t("Rotate")} + + +
+
+
+ ); +}; + +export default inject(({ createEditRoomStore }) => { + const { setWatermarks, watermarksSettings } = createEditRoomStore; + return { + setWatermarks, + watermarksSettings, + }; +})(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 0f2c5d0b12..e1c87b50fd 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 @@ -38,6 +38,12 @@ const StyledWatermark = styled.div` .watermark-checkbox { margin: 18px 0 0 0; } + + .options-wrapper { + display: grid; + grid-template-columns: minmax(216px, 1fr) minmax(216px, 1fr); + gap: 16px; + } `; 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 e7c3d5427e..ebfb20fb1d 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 @@ -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 { useState, useRef } from "react"; +import { useState, useRef, useEffect } from "react"; import { useTranslation } from "react-i18next"; import { inject, observer } from "mobx-react"; @@ -36,6 +36,34 @@ import { WatermarkAdditions } from "@docspace/shared/enums"; import { StyledWatermark } from "./StyledComponent"; +const tabsOptions = (t) => [ + { + key: "UserName", + title: t("UserName"), + index: 0, + }, + { + key: "UserEmail", + title: t("UserEmail"), + index: 1, + }, + { + key: "UserIpAdress", + title: t("UserIPAddress"), + index: 2, + }, + { + key: "CurrentDate", + title: t("Common:CurrentDate"), + index: 3, + }, + { + key: "RoomName", + title: t("Common:RoomName"), + index: 4, + }, +]; + const getInitialState = (initialTab) => { const state = { UserName: false, @@ -52,19 +80,59 @@ const getInitialState = (initialTab) => { return state; }; +const getInitialText = (text, isEdit) => { + return isEdit && text ? text : ""; +}; + +const getInitialTabs = (additions, isEdit, t) => { + const dataTabs = tabsOptions(t); + + if (!isEdit || !additions) return [dataTabs[0]]; + + return dataTabs.filter((item) => additions & WatermarkAdditions[item.key]); +}; + const ViewerInfoWatermark = ({ + getInitialRotate, + rotateOptions, + isEdit, + setWatermarks, - initialPosition, - dataPosition, - dataTabs, - initialTab, - initialText, + watermarksSettings, }) => { const { t } = useTranslation(["CreateEditRoomDialog", "Common"]); - const elements = useRef(getInitialState(initialTab)); - const [selectedPosition, setSelectedPosition] = useState(initialPosition); - const [textValue, setTextValue] = useState(initialText ?? ""); + const elements = useRef(null); + const initialInfo = useRef(null); + + if (initialInfo.current === null) { + initialInfo.current = { + dataRotate: rotateOptions(t), + dataTabs: tabsOptions(t), + rotate: getInitialRotate(watermarksSettings?.rotate, isEdit, false, t), + tabs: getInitialTabs(watermarksSettings?.additions, isEdit, t), + text: getInitialText(watermarksSettings?.text, isEdit), + additions: watermarksSettings?.additions ?? WatermarkAdditions.UserName, + }; + + elements.current = getInitialState(initialInfo.current.tabs); + } + + const initialInfoRef = initialInfo.current; + + useEffect(() => { + setWatermarks({ + rotate: initialInfoRef.rotate.key, + text: initialInfoRef.text, + enabled: true, + additions: initialInfoRef.additions, + }); + }, []); + + const [selectedPosition, setSelectedPosition] = useState( + initialInfoRef.rotate, + ); + const [textValue, setTextValue] = useState(initialInfoRef.text); const onSelect = (item) => { let elementsData = elements.current; @@ -81,21 +149,20 @@ const ViewerInfoWatermark = ({ flagsCount += WatermarkAdditions[key]; } } - - setWatermarks({ additions: flagsCount }); + setWatermarks({ ...watermarksSettings, additions: flagsCount }); }; const onPositionChange = (item) => { setSelectedPosition(item); - setWatermarks({ rotate: item.key }); + setWatermarks({ ...watermarksSettings, rotate: item.key }); }; const onTextChange = (e) => { const { value } = e.target; setTextValue(value); - setWatermarks({ text: value }); + setWatermarks({ ...watermarksSettings, text: value }); }; return ( @@ -104,9 +171,9 @@ const ViewerInfoWatermark = ({ {t("AddWatermarkElements")} item.index)} + selectedItem={initialInfoRef.tabs.map((item) => item.index)} multiple withBorder /> @@ -126,7 +193,7 @@ const ViewerInfoWatermark = ({ { - const { setWatermarks } = createEditRoomStore; + const { setWatermarks, watermarksSettings } = createEditRoomStore; return { setWatermarks, + watermarksSettings, }; })(observer(ViewerInfoWatermark)); diff --git a/packages/client/src/components/dialogs/CreateEditRoomDialog/sub-components/Watermarks/index.js b/packages/client/src/components/dialogs/CreateEditRoomDialog/sub-components/Watermarks/index.js index f0b00100a0..6296439c06 100644 --- a/packages/client/src/components/dialogs/CreateEditRoomDialog/sub-components/Watermarks/index.js +++ b/packages/client/src/components/dialogs/CreateEditRoomDialog/sub-components/Watermarks/index.js @@ -24,15 +24,15 @@ // 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 { useState, useEffect, useRef } from "react"; +import { useState } from "react"; import { useTranslation } from "react-i18next"; import { inject, observer } from "mobx-react"; import { RadioButtonGroup } from "@docspace/shared/components/radio-button-group"; -import { WatermarkAdditions } from "@docspace/shared/enums"; import ViewerInfoWatermark from "./ViewerInfo"; import { StyledBody } from "./StyledComponent"; +import ImageWatermark from "./ImageWatermark"; const imageWatermark = "image", viewerInfoWatermark = "viewerInfo"; @@ -53,44 +53,16 @@ const rotateOptions = (t) => [ { key: 0, label: t("Horizontal") }, ]; -const tabsOptions = (t) => [ - { - key: "UserName", - title: t("UserName"), - index: 0, - }, - { - key: "UserEmail", - title: t("UserEmail"), - index: 1, - }, - { - key: "UserIpAdress", - title: t("UserIPAddress"), - index: 2, - }, - { - key: "CurrentDate", - title: t("Common:CurrentDate"), - index: 3, - }, - { - key: "RoomName", - title: t("Common:RoomName"), - index: 4, - }, +const imageRotateOptions = () => [ + { key: 0, label: "0" }, + { key: 30, label: "30" }, + { key: 45, label: "45" }, + { key: 60, label: "60" }, + { key: 90, label: "90" }, ]; -const getInitialTabs = (additions, isEdit, t) => { - const dataTabs = tabsOptions(t); - - if (!isEdit || !additions) return [dataTabs[0]]; - - return dataTabs.filter((item) => additions & WatermarkAdditions[item.key]); -}; - -const getInitialRotate = (rotate, isEdit, t) => { - const dataRotate = rotateOptions(t); +const getInitialRotate = (rotate, isEdit, isImage, t) => { + const dataRotate = isImage ? imageRotateOptions() : rotateOptions(t); if (!isEdit || rotate === undefined) return dataRotate[0]; @@ -99,40 +71,18 @@ const getInitialRotate = (rotate, isEdit, t) => { }); }; -const getInitialText = (text, isEdit) => { - return isEdit && text ? text : ""; -}; -const Watermarks = ({ setWatermarks, watermarksSettings, isEdit }) => { - const { t } = useTranslation(["CreateEditRoomDialog", "Common"]); - const [type, setType] = useState(viewerInfoWatermark); - - const initialInfo = useRef(null); - - if (initialInfo.current === null) { - initialInfo.current = { - dataRotate: rotateOptions(t), - dataTabs: tabsOptions(t), - initialRotate: getInitialRotate(watermarksSettings?.rotate, isEdit, t), - initialTabs: getInitialTabs(watermarksSettings?.additions, isEdit, t), - initialText: getInitialText(watermarksSettings?.text, isEdit), - }; +const getOptionType = (additions, isEdit) => { + if (isEdit) { + return additions === 0 ? imageWatermark : viewerInfoWatermark; } - const initialInfoRef = initialInfo.current; - - useEffect(() => { - if (!isEdit) { - setWatermarks( - { - rotate: initialInfoRef.initialRotate.key, - text: "", - additions: WatermarkAdditions.UserName, - enabled: true, - }, - true, - ); - } - }, []); + return viewerInfoWatermark; +}; +const Watermarks = ({ isEdit, watermarksSettings }) => { + const { t } = useTranslation(["CreateEditRoomDialog", "Common"]); + const [type, setType] = useState( + getOptionType(watermarksSettings?.additions, isEdit), + ); const onSelectType = (e) => { const { value } = e.target; @@ -142,8 +92,6 @@ const Watermarks = ({ setWatermarks, watermarksSettings, isEdit }) => { const typeOptions = options(t); - console.log("============Watermarks render", watermarksSettings); - return ( { {type === viewerInfoWatermark && ( + )} + {type === imageWatermark && ( + )} @@ -170,9 +123,8 @@ const Watermarks = ({ setWatermarks, watermarksSettings, isEdit }) => { }; export default inject(({ createEditRoomStore }) => { - const { setWatermarks, watermarksSettings } = createEditRoomStore; + const { watermarksSettings } = createEditRoomStore; return { - setWatermarks, watermarksSettings, }; })(observer(Watermarks)); diff --git a/packages/client/src/store/CreateEditRoomStore.js b/packages/client/src/store/CreateEditRoomStore.js index ffb84f78cb..9c784474da 100644 --- a/packages/client/src/store/CreateEditRoomStore.js +++ b/packages/client/src/store/CreateEditRoomStore.js @@ -98,26 +98,46 @@ class CreateEditRoomStore { setWatermarks = (watermarksSettings, isInit = false) => { if (isInit) { this.initialWatermarksSettings = watermarksSettings; - this.watermarksSettings = watermarksSettings; - - return; } - if (!watermarksSettings) { - this.watermarksSettings = null; - return; - } - - this.watermarksSettings = { - ...this.watermarksSettings, - ...watermarksSettings, - }; + this.watermarksSettings = watermarksSettings; }; isEqualWatermarkChanges = () => { return isEqual(this.watermarksSettings, this.initialWatermarksSettings); }; + getWatermarkRequest = async (room) => { + if (!this.watermarksSettings.image) { + return setWatermarkSettings(room.id, this.watermarksSettings); + } + + const { uploadRoomLogo } = this.filesStore; + + const watermarkImage = this.watermarksSettings.image; + const uploadWatermarkData = new FormData(); + uploadWatermarkData.append(0, watermarkImage); + + const response = await uploadRoomLogo(uploadWatermarkData); + + let newImage = new Image(); + newImage.src = URL.createObjectURL(watermarkImage); + + newImage.onload = () => { + let width = newImage.width; + let height = newImage.height; + + return setWatermarkSettings(room.id, { + enabled: true, + imageScale: this.watermarksSettings.imageScale, + rotate: this.watermarksSettings.rotate, + imageUrl: response.data, + imageWidth: width, + imageHeight: height, + }); + }; + }; + onCreateRoom = async (withConfirm = false, t) => { const roomParams = this.roomParams; @@ -184,13 +204,16 @@ class CreateEditRoomStore { const actions = []; + const requests = []; + if (this.watermarksSettings) { - await setWatermarkSettings(room.id, this.watermarksSettings); + requests.push(this.getWatermarkRequest(room)); } // delete thirdparty account if not needed if (!isThirdparty && storageFolderId) - await deleteThirdParty(thirdpartyAccount.providerId); + requests.push(deleteThirdParty(thirdpartyAccount.providerId)); + await Promise.all(requests); // create new tags for (let i = 0; i < createTagsData.length; i++) { actions.push(createTag(createTagsData[i])); @@ -205,6 +228,7 @@ class CreateEditRoomStore { await uploadRoomLogo(uploadLogoData).then(async (response) => { const url = URL.createObjectURL(roomParams.icon.uploadedFile); const img = new Image(); + img.onload = async () => { const { x, y, zoom } = roomParams.icon; try {