diff --git a/packages/client/public/locales/en/CreateEditRoomDialog.json b/packages/client/public/locales/en/CreateEditRoomDialog.json index 6fb4e78567..5328f3b6e4 100644 --- a/packages/client/public/locales/en/CreateEditRoomDialog.json +++ b/packages/client/public/locales/en/CreateEditRoomDialog.json @@ -1,29 +1,42 @@ { - "ActivationRequired": "activation required", + "AddWatermarkElements": "Add watermark elements", + "AddStaticText": "Add static text", + "Diagonal": "Diagonal", "ChooseRoomType": "Choose room type", "CreateRoomConfirmation": "Continue without connecting the storage?\nYou have selected a third-party storage option that is not connected yet. If you proceed without connecting the service, this option will not be added.", + "CreateRoomWatermarksConfirmation": "You have not set a watermark to be applied to documents in this room. You can always add a watermark in the room editing settings. Continue without a watermark?", "CreateTagOption": "Create tag", "DisableRoomQuota": "Disable quota for this room", "FormRoomBarDescription": "This room is available to anyone with the link. External users will have Form Filler permission for all the files.", + "Center": "Center", + "Horizontal": "Horizontal", "Icon": "Icon", "MakeRoomPrivateDescription": "All files in this room will be encrypted.", "MakeRoomPrivateLimitationsWarningDescription": "With this feature, you can invite only existing {{productName}} users. After creating a room, you will not be able to change the list of users.", "MakeRoomPrivateTitle": "Make the Room Private", "PeopleSelectorInfo": "Only a room admin or a {{productName}} admin can become the owner of the room", "PublicRoomBarDescription": "This room is available to anyone with the link. External users will have View Only permission for all the files.", + "ViewerInfo": "Viewer info", + "Position": "Position", "PublicRoomSystemFoldersDescription": "System folders store copies of forms at different stages of completion. Forms that are being filled are stored in the In progress folder, and completed forms are stored in the Complete folder.", "PublicRoomSystemFoldersTitle": "System Folders", "RoomEditing": "Room editing", "RootFolderLabel": "Root folder", "StorageDescription": "Storage quota set per room. You can change this value or turn off storage limit.", "TagsPlaceholder": "Add a tag", + "Text": "Text", "ThirdPartyStorageComboBoxPlaceholder": "Select storage", "ThirdPartyStorageDescription": "Use third-party services as data storage for this room. You can create a new folder or select the existing one in the connected storage.", "ThirdPartyStorageNoStorageAlert": "Before, you need to connect the corresponding service in the “Integration” section. Otherwise, the connection will not be possible.", "ThirdPartyStoragePermanentSettingDescription": "Files are stored in a third-party {{thirdpartyTitle}} storage in the \"{{thirdpartyFolderName}}\" folder.\n{{thirdpartyPath}}", "ThirdPartyStorageRoomAdminNoStorageAlert": "To connect a third-party storage, you need to add the corresponding service in the Integration section of {{productName}} settings. Contact {{productName}} owner or administrator to enable the integration.", + "UserName": "User Name", + "UserEmail": "User Email", + "UserIPAddress": "User IP Address", "ViewOnlyRoomDescription": "Share any ready documents, reports, documentation, and other files for viewing.", "ViewOnlyRoomTitle": "View-only room", + "WatermarkPreview": "Watermark Preview", + "WatermarkPreviewHelp": "This image preview roughly shows how the watermark will be displayed in your files.", "AutomaticIndexing": "Automatic indexing", "AutomaticIndexingDescription": "Enable automatic indexing to index files and folders by serial number. Sorting by number will be set as default for all users.", "FileLifetime": "File lifetime", diff --git a/packages/client/public/locales/en/Files.json b/packages/client/public/locales/en/Files.json index ebad476fc1..a771075f1d 100644 --- a/packages/client/public/locales/en/Files.json +++ b/packages/client/public/locales/en/Files.json @@ -68,9 +68,12 @@ "EnableNotifications": "Enable notifications", "ErrorChangeIndex": "Error when changing index. The problem may be caused on the server side. Reload the page or check your Internet connection settings.", "ExcludeSubfolders": "Exclude subfolders", + "ExportRoomIndex": "Export room index", + "ExportRoomIndexAlreadyInProgressError": "Room index export is already in progress. Please wait until the current export is completed to start the new one.", "FavoritesEmptyContainerDescription": "To mark files as favorites or remove them from this list, use the context menu.", "FileContents": "File contents", "FileDownloadingIsRestricted": "File downloading is restricted in this room.", + "FileExportedToMyDocuments": "file exported to My Documents", "FileRemoved": "File moved to Trash", "FileRenamed": "The document '{{oldTitle}}' is renamed to '{{newTitle}}'", "FilesWillAppearHere": "Files and folders added to the room will appear here.", @@ -185,6 +188,10 @@ "WantToRestoreTheRooms": "All shared links in restored rooms will become active, and their contents will be available to everyone with the room links. Do you want to restore the rooms?", "WithSubfolders": "With subfolders", "YouLeftTheRoom": "You have left the room", + "RoomFilesLifetime": "The file lifetime is set to {{days}} {{period}} in this room", + "FileWillBeDeleted": "The file will be deleted {{date}}", + "LifetimeDialogDescription": "Lifetime countdown begins at the file creation date. Some files in this room exceed the proposed lifetime and will be deleted once you enable the setting.", + "LifetimeDialogDescriptionHeader": "Older files with exceeded lifetime will be deleted", "Protected": "protected", "Embed": "Embed" } diff --git a/packages/client/public/locales/en/InfoPanel.json b/packages/client/public/locales/en/InfoPanel.json index 2b381b2378..d50fceabe4 100644 --- a/packages/client/public/locales/en/InfoPanel.json +++ b/packages/client/public/locales/en/InfoPanel.json @@ -6,6 +6,7 @@ "CreationDate": "Creation date", "Data": "Data", "DateModified": "Date modified", + "LifetimeEnds": "Lifetime ends", "DeletedRoomTags": "Tags removed.", "ExpectUsers": "Expect users", "FeedLinkWasDeleted": "Link was deleted", diff --git a/packages/client/src/HOCs/withQuickButtons.js b/packages/client/src/HOCs/withQuickButtons.js index 77142676f6..2cd895c0c1 100644 --- a/packages/client/src/HOCs/withQuickButtons.js +++ b/packages/client/src/HOCs/withQuickButtons.js @@ -26,9 +26,12 @@ import React from "react"; import { inject, observer } from "mobx-react"; +import moment from "moment"; import { toastr } from "@docspace/shared/components/toast"; import { copyShareLink } from "@docspace/shared/utils/copy"; import QuickButtons from "../components/QuickButtons"; +import { LANGUAGE } from "@docspace/shared/constants"; +import { getCookie, getCorrectDate } from "@docspace/shared/utils"; export default function withQuickButtons(WrappedComponent) { class WithQuickButtons extends React.Component { @@ -100,6 +103,39 @@ export default function withQuickButtons(WrappedComponent) { } }; + getStartDate = () => { + const { period, value } = this.props.roomLifetime; + const date = new Date(this.props.item.expired); + + switch (period) { + case 0: + return new Date(date.setDate(date.getDate() - value)); + case 1: + return new Date(date.setMonth(date.getMonth() - value)); + case 2: + return new Date(date.setFullYear(date.getFullYear() - value)); + default: + break; + } + }; + + getShowLifetimeIcon = () => { + const { item } = this.props; + + const startDate = this.getStartDate(); + const dateDiff = moment(startDate).diff(item.expired) * 0.1; + const showDate = moment(item.expired).add(dateDiff, "milliseconds"); + + return moment().valueOf() >= showDate.valueOf(); + }; + + getItemExpiredDate = () => { + const { culture, item } = this.props; + + const locale = getCookie(LANGUAGE) || culture; + return getCorrectDate(locale, item.expired); + }; + render() { const { isLoading } = this.state; @@ -116,8 +152,14 @@ export default function withQuickButtons(WrappedComponent) { isArchiveFolder, isIndexEditingMode, currentDeviceType, + roomLifetime, } = this.props; + const showLifetimeIcon = + item.expired && roomLifetime ? this.getShowLifetimeIcon() : false; + const expiredDate = + item.expired && roomLifetime ? this.getItemExpiredDate() : null; + const quickButtonsComponent = ( ); @@ -180,10 +224,12 @@ export default function withQuickButtons(WrappedComponent) { isTrashFolder || isArchiveFolderRoot || isPersonalFolderRoot; const { isPublicRoom } = publicRoomStore; - const { getPrimaryFileLink, setShareChanged } = infoPanelStore; + const { getPrimaryFileLink, setShareChanged, infoPanelRoom } = + infoPanelStore; return { theme: settingsStore.theme, + culture: settingsStore.culture, currentDeviceType: settingsStore.currentDeviceType, isAdmin: authStore.isAdmin, lockFileAction, @@ -198,6 +244,7 @@ export default function withQuickButtons(WrappedComponent) { getPrimaryFileLink, setShareChanged, isIndexEditingMode, + roomLifetime: infoPanelRoom?.lifetime, }; }, )(observer(WithQuickButtons)); diff --git a/packages/client/src/components/FilesPanels/index.js b/packages/client/src/components/FilesPanels/index.js index 7dc5f48395..bad54aa5f9 100644 --- a/packages/client/src/components/FilesPanels/index.js +++ b/packages/client/src/components/FilesPanels/index.js @@ -77,6 +77,7 @@ import ChangeRoomOwnerPanel from "../panels/ChangeRoomOwnerPanel"; import { CreatedPDFFormDialog } from "../dialogs/CreatedPDFFormDialog"; import { PDFFormEditingDialog } from "../dialogs/PDFFormEditingDialog"; import ReorderIndexDialog from "../dialogs/ReorderIndexDialog"; +import LifetimeDialog from "../dialogs/LifetimeDialog"; import { SharePDFFormDialog } from "../dialogs/SharePDFFormDialog"; const Panels = (props) => { @@ -91,6 +92,7 @@ const Panels = (props) => { deleteThirdPartyDialogVisible, versionHistoryPanelVisible, deleteDialogVisible, + lifetimeDialogVisible, downloadDialogVisible, emptyTrashDialogVisible, newFilesPanelVisible, @@ -271,6 +273,7 @@ const Panels = (props) => { ), deleteDialogVisible && , + lifetimeDialogVisible && , emptyTrashDialogVisible && , downloadDialogVisible && , @@ -381,6 +384,7 @@ export default inject( connectDialogVisible, deleteThirdPartyDialogVisible, deleteDialogVisible, + lifetimeDialogVisible, downloadDialogVisible, emptyTrashDialogVisible, newFilesPanelVisible, @@ -446,6 +450,7 @@ export default inject( deleteThirdPartyDialogVisible, versionHistoryPanelVisible, deleteDialogVisible, + lifetimeDialogVisible, downloadDialogVisible, emptyTrashDialogVisible, newFilesPanelVisible, diff --git a/packages/client/src/components/GlobalEvents/CreateRoomEvent.js b/packages/client/src/components/GlobalEvents/CreateRoomEvent.js index b567b48de1..4ca16d8f8e 100644 --- a/packages/client/src/components/GlobalEvents/CreateRoomEvent.js +++ b/packages/client/src/components/GlobalEvents/CreateRoomEvent.js @@ -51,6 +51,7 @@ const CreateRoomEvent = ({ enableThirdParty, deleteThirdParty, startRoomType, + isNotWatermarkSet, }) => { const { t } = useTranslation(["CreateEditRoomDialog", "Common", "Files"]); const [fetchedTags, setFetchedTags] = useState([]); @@ -59,14 +60,17 @@ const CreateRoomEvent = ({ setRoomParams(roomParams); setOnClose(onClose); - if ( + const notConnectedThirdparty = roomParams.storageLocation.isThirdparty && - !roomParams.storageLocation.storageFolderId - ) { + !roomParams.storageLocation.storageFolderId; + + if (notConnectedThirdparty || isNotWatermarkSet()) { setCreateRoomConfirmDialogVisible(true); - } else { - onCreateRoom(false, t); + + return; } + + onCreateRoom(false, t); }; const fetchTagsAction = useCallback(async () => { @@ -134,6 +138,8 @@ export default inject( setIsLoading, setOnClose, confirmDialogIsLoading, + + isNotWatermarkSet, } = createEditRoomStore; return { @@ -151,6 +157,8 @@ export default inject( fetchThirdPartyProviders, enableThirdParty, deleteThirdParty, + + isNotWatermarkSet, }; }, )(observer(CreateRoomEvent)); diff --git a/packages/client/src/components/GlobalEvents/EditRoomEvent.js b/packages/client/src/components/GlobalEvents/EditRoomEvent.js index ae61ca971b..78421bdf04 100644 --- a/packages/client/src/components/GlobalEvents/EditRoomEvent.js +++ b/packages/client/src/components/GlobalEvents/EditRoomEvent.js @@ -27,11 +27,17 @@ import React, { useState, useEffect, useCallback } from "react"; import { inject, observer } from "mobx-react"; import { useTranslation } from "react-i18next"; +import isEqual from "lodash/isEqual"; import { EditRoomDialog } from "../dialogs"; import { Encoder } from "@docspace/shared/utils/encoder"; import api from "@docspace/shared/api"; -import { getRoomInfo } from "@docspace/shared/api/rooms"; +import { + deleteWatermarkSettings, + getRoomInfo, + getWatermarkSettings, +} from "@docspace/shared/api/rooms"; import { toastr } from "@docspace/shared/components/toast"; +import { setWatermarkSettings } from "@docspace/shared/api/rooms"; const EditRoomEvent = ({ addActiveItems, @@ -75,12 +81,18 @@ const EditRoomEvent = ({ defaultRoomsQuota, isDefaultRoomsQuotaSet, + changeRoomLifetime, + setInitialWatermarks, + getWatermarkRequest, + watermarksSettings, + isNotWatermarkSet, }) => { const { t } = useTranslation(["CreateEditRoomDialog", "Common", "Files"]); const [fetchedTags, setFetchedTags] = useState([]); const [fetchedImage, setFetchedImage] = useState(null); const [isLoading, setIsLoading] = useState(false); + const [isInitLoading, setIsInitLoading] = useState(false); const startTags = Object.values(item.tags); const startObjTags = startTags.map((tag, i) => ({ id: i, name: tag })); @@ -106,6 +118,7 @@ const EditRoomEvent = ({ }, roomOwner: item.createdBy, indexing: item.indexing, + lifetime: item.lifetime, ...(isDefaultRoomsQuotaSet && { quota: item.quotaLimit, @@ -140,6 +153,7 @@ const EditRoomEvent = ({ const isTitleChanged = roomParams?.title !== item.title; const isQuotaChanged = quotaLimit !== item.quotaLimit; const isOwnerChanged = roomParams?.roomOwner?.id !== item.createdBy.id; + const lifetimeChanged = !isEqual(roomParams.lifetime, item.lifetime); const tags = roomParams.tags.map((tag) => tag.name); const newTags = roomParams.tags.filter((t) => t.isNew).map((t) => t.name); @@ -176,6 +190,12 @@ const EditRoomEvent = ({ displayName: roomParams.roomOwner.label, }; } + + if (lifetimeChanged) { + actions.push(changeRoomLifetime(room.id, roomParams.lifetime)); + room.lifetime = roomParams.lifetime; + } + if (tags.length) { actions.push(addTagsToRoom(room.id, newTags)); room.tags = tags; @@ -183,6 +203,15 @@ const EditRoomEvent = ({ if (removedTags.length) actions.push(removeTagsFromRoom(room.id, removedTags)); + + + if (watermarksSettings && !isNotWatermarkSet()) { + + const request = getWatermarkRequest(room, watermarksSettings); + + actions.push(request); + } + await Promise.all(actions); if (!!item.logo.original && !roomParams.icon.uploadedFile) { @@ -238,7 +267,11 @@ const EditRoomEvent = ({ if (withPaging) await updateCurrentFolder(null, currentFolderId); if (item.id === currentFolderId) { - updateEditedSelectedRoom(editRoomParams.title, tags); + updateEditedSelectedRoom( + editRoomParams.title, + tags, + roomParams.lifetime, + ); if (item.logo.original && !roomParams.icon.uploadedFile) { removeLogoPaths(); // updateInfoPanelSelection(); @@ -267,24 +300,28 @@ const EditRoomEvent = ({ setFetchedImage(file); }, []); - useEffect(() => { - const logo = item?.logo?.original ? item.logo.original : ""; - if (logo) { - fetchLogoAction(logo); - } - }, []); - - const fetchTagsAction = useCallback(async () => { - const tags = await fetchTags(); - setFetchedTags(tags); - }, []); - - useEffect(() => { - fetchTagsAction(); - }, [fetchTagsAction]); - useEffect(() => { setCreateRoomDialogVisible(true); + setIsInitLoading(true); + + const logo = item?.logo?.original ? item.logo.original : ""; + + const requests = [fetchTags(), getWatermarkSettings(item.id)]; + + if (logo) requests.push(fetchLogoAction); + + const fetchInfo = async () => { + const [tags, watermarks] = await Promise.all(requests); + + setFetchedTags(tags); + + setInitialWatermarks(watermarks); + + setIsInitLoading(false); + }; + + fetchInfo(); + return () => setCreateRoomDialogVisible(false); }, []); @@ -298,6 +335,7 @@ const EditRoomEvent = ({ fetchedTags={fetchedTags} fetchedImage={fetchedImage} isLoading={isLoading} + isInitLoading={isInitLoading} /> ); }; @@ -313,6 +351,7 @@ export default inject( filesSettingsStore, infoPanelStore, currentQuotaStore, + createEditRoomStore, }) => { const { editRoom, @@ -337,13 +376,20 @@ export default inject( removeLogoPaths, updateLogoPathsCacheBreaker, } = selectedFolderStore; - const { updateCurrentFolder, changeRoomOwner } = filesActionsStore; + const { updateCurrentFolder, changeRoomOwner, changeRoomLifetime } = + filesActionsStore; const { getThirdPartyIcon } = filesSettingsStore.thirdPartyStore; const { setCreateRoomDialogVisible } = dialogsStore; const { withPaging } = settingsStore; const { updateInfoPanelSelection } = infoPanelStore; const { defaultRoomsQuota, isDefaultRoomsQuotaSet } = currentQuotaStore; + const { + setInitialWatermarks, + watermarksSettings, + isNotWatermarkSet, + getWatermarkRequest, + } = createEditRoomStore; return { defaultRoomsQuota, @@ -381,6 +427,11 @@ export default inject( updateInfoPanelSelection, changeRoomOwner, + changeRoomLifetime, + setInitialWatermarks, + watermarksSettings, + isNotWatermarkSet, + getWatermarkRequest, }; }, )(observer(EditRoomEvent)); diff --git a/packages/client/src/components/QuickButtons.js b/packages/client/src/components/QuickButtons.js index ad83ab4eb0..e2d989b7cf 100644 --- a/packages/client/src/components/QuickButtons.js +++ b/packages/client/src/components/QuickButtons.js @@ -32,21 +32,38 @@ import LinkReactSvgUrl from "PUBLIC_DIR/images/link.react.svg?url"; import LockedReactSvgUrl from "PUBLIC_DIR/images/locked.react.svg?url"; import FileActionsFavoriteReactSvgUrl from "PUBLIC_DIR/images/file.actions.favorite.react.svg?url"; import FavoriteReactSvgUrl from "PUBLIC_DIR/images/favorite.react.svg?url"; +import LifetimeReactSvgUrl from "PUBLIC_DIR/images/lifetime.react.svg?url"; import LockedReact12SvgUrl from "PUBLIC_DIR/images/icons/12/lock.react.svg?url"; import React, { useMemo } from "react"; import styled from "styled-components"; - -import { isTablet, isMobile, commonIconsStyles } from "@docspace/shared/utils"; +import { isTablet } from "@docspace/shared/utils"; import { DeviceType, FileStatus, RoomsType, ShareAccessRights, } from "@docspace/shared/enums"; +import { Tooltip } from "@docspace/shared/components/tooltip"; +import { Text } from "@docspace/shared/components/text"; import { ColorTheme, ThemeId } from "@docspace/shared/components/color-theme"; +const StyledQuickButtons = styled.div` + .file-lifetime { + svg { + rect { + fill: ${({ theme }) => theme.filesQuickButtons.lifeTimeColor}; + } + + circle, + path { + stroke: ${({ theme }) => theme.filesQuickButtons.lifeTimeColor}; + } + } + } +`; + const QuickButtons = (props) => { const { t, @@ -66,6 +83,8 @@ const QuickButtons = (props) => { isArchiveFolder, isIndexEditingMode, currentDeviceType, + showLifetimeIcon, + expiredDate, } = props; const isMobile = currentDeviceType === DeviceType.mobile; @@ -137,8 +156,36 @@ const QuickButtons = (props) => { !isArchiveFolder && !isTile; + const getTooltipContent = () => ( + + {t("Files:FileWillBeDeleted", { date: expiredDate })}. + + ); + return ( -
+ + {showLifetimeIcon && ( + <> + + + + + )} + {isAvailableLockFile && !isIndexEditingMode && ( { hoverColor={theme.filesQuickButtons.hoverColor} /> )} */} -
+ ); }; diff --git a/packages/client/src/components/dialogs/ConflictResolveDialog/index.tsx b/packages/client/src/components/dialogs/ConflictResolveDialog/index.tsx index 0e5b5b3ba2..7bab03dc07 100644 --- a/packages/client/src/components/dialogs/ConflictResolveDialog/index.tsx +++ b/packages/client/src/components/dialogs/ConflictResolveDialog/index.tsx @@ -220,13 +220,13 @@ const ConflictResolveDialog = (props: ConflictResolveDialogProps) => { isLoading={!ready} onSubmit={isUploadConflict ? onAcceptUploadType : onAcceptType} onClose={onCloseDialog} - cancelButtonLabel={t("Common:CancelButton")} - submitButtonLabel={t("Common:OKButton")} + cancelButtonLabel={t("CancelButton")} + submitButtonLabel={t("OKButton")} messageText={messageText} selectActionText={t("Common:ConflictResolveSelectAction")} overwriteTitle={t("Common:OverwriteTitle")} overwriteDescription={t("Common:OverwriteDescription")} - duplicateTitle={t("Common:CreateFileCopy")} + duplicateTitle={t("CreateFileCopy")} duplicateDescription={t("Common:CreateDescription")} skipTitle={t("Common:SkipTitle")} skipDescription={t("Common:SkipDescription")} diff --git a/packages/client/src/components/dialogs/CreateEditRoomDialog/EditRoomDialog.js b/packages/client/src/components/dialogs/CreateEditRoomDialog/EditRoomDialog.js index 115945a69c..eb2c73b5c2 100644 --- a/packages/client/src/components/dialogs/CreateEditRoomDialog/EditRoomDialog.js +++ b/packages/client/src/components/dialogs/CreateEditRoomDialog/EditRoomDialog.js @@ -25,7 +25,9 @@ // International. See the License terms at http://creativecommons.org/licenses/by-sa/4.0/legalcode import React, { useState, useEffect, useRef, useCallback } from "react"; +import { inject, observer } from "mobx-react"; +import isEqual from "lodash/isEqual"; import TagHandler from "./handlers/TagHandler"; import SetRoomParams from "./sub-components/SetRoomParams"; import DialogHeader from "./sub-components/DialogHeader"; @@ -42,6 +44,8 @@ const EditRoomDialog = ({ fetchedRoomParams, fetchedTags, fetchedImage, + isInitLoading, + isEqualWatermarkChanges, }) => { const [isScrollLocked, setIsScrollLocked] = useState(false); const [isValidTitle, setIsValidTitle] = useState(true); @@ -76,7 +80,9 @@ const EditRoomDialog = ({ currentParams.icon.uploadedFile === undefined)) || prevParams.icon.uploadedFile === currentParams.icon.uploadedFile) && prevParams.quota === currentParams.quota && - prevParams.indexing === currentParams.indexing + prevParams.indexing === currentParams.indexing && + isEqual(prevParams.lifetime, currentParams.lifetime) && + isEqualWatermarkChanges() ); }; @@ -131,6 +137,7 @@ const EditRoomDialog = ({ visible={visible} onClose={onCloseAction} isScrollLocked={isScrollLocked} + isLoading={isInitLoading} withFooterBorder > @@ -182,4 +189,10 @@ const EditRoomDialog = ({ ); }; -export default EditRoomDialog; +export default inject(({ createEditRoomStore }) => { + const { isEqualWatermarkChanges } = createEditRoomStore; + + return { + isEqualWatermarkChanges, + }; +})(observer(EditRoomDialog)); diff --git a/packages/client/src/components/dialogs/CreateEditRoomDialog/sub-components/FileLifetime.js b/packages/client/src/components/dialogs/CreateEditRoomDialog/sub-components/FileLifetime.js index fe4431de0a..f7f2d8f3bf 100644 --- a/packages/client/src/components/dialogs/CreateEditRoomDialog/sub-components/FileLifetime.js +++ b/packages/client/src/components/dialogs/CreateEditRoomDialog/sub-components/FileLifetime.js @@ -1,5 +1,6 @@ -import React, { useState } from "react"; +import { useState, useEffect } from "react"; import styled from "styled-components"; +import { capitalize } from "lodash"; import { Text } from "@docspace/shared/components/text"; import { TextInput } from "@docspace/shared/components/text-input"; import { ComboBox } from "@docspace/shared/components/combobox"; @@ -44,22 +45,28 @@ const StyledFileLifetime = styled.div` } `; -const FileLifetime = ({ t }) => { +const FileLifetime = ({ t, roomParams, setRoomParams }) => { + const lifetime = roomParams.lifetime ?? { + value: 12, + deletePermanently: false, + period: 0, + }; + const dateOptions = [ { key: 1, - label: t("Common:Days"), - "data-type": 1, + label: capitalize(t("Common:Days")), + value: 0, }, { key: 2, label: t("Common:Months"), - "data-type": 2, + value: 1, }, { key: 3, label: t("Common:Years"), - "data-type": 3, + value: 2, }, ]; @@ -67,36 +74,66 @@ const FileLifetime = ({ t }) => { { key: 1, label: t("Common:MoveToTrash"), - "data-type": 1, + value: false, }, { key: 2, label: t("Common:DeletePermanently"), - "data-type": 2, + value: true, }, ]; - const [inputValue, setInputValue] = useState(""); - const [selectedDate, setSelectedDate] = useState(dateOptions[0]); - const [selectedDelete, setSelectedDelete] = useState(deleteOptions[0]); + const selectedInputValue = lifetime.value + ""; + const selectedDateOption = dateOptions.find( + (o) => o.value === lifetime.period, + ); + const selectedDeleteOptions = lifetime.deletePermanently + ? deleteOptions[1] + : deleteOptions[0]; + + const [inputValue, setInputValue] = useState(selectedInputValue); + const [selectedDate, setSelectedDate] = useState(selectedDateOption); + const [selectedDelete, setSelectedDelete] = useState(selectedDeleteOptions); + + useEffect(() => { + if (!roomParams.lifetime) { + setRoomParams({ + ...roomParams, + lifetime, + }); + } + }, [roomParams.lifetime]); const onChange = (e) => { // /^(?:[1-9][0-9]*|0)$/ if (e.target.value && !/^(?:[1-9][0-9]*)$/.test(e.target.value)) return; setInputValue(e.target.value); + + setRoomParams({ + ...roomParams, + lifetime: { ...lifetime, value: +e.target.value }, + }); }; const isLoading = false; const onSelectDate = (option) => { setSelectedDate(option); - console.log("onDateSelect", option); + + setRoomParams({ + ...roomParams, + lifetime: { ...lifetime, period: option.value }, + }); }; const onSelectDelete = (option) => { setSelectedDelete(option); - console.log("onSelectDelete", option); + + setRoomParams({ + ...roomParams, + lifetime: { ...lifetime, deletePermanently: option.value }, + }); }; return ( diff --git a/packages/client/src/components/dialogs/CreateEditRoomDialog/sub-components/SetRoomParams.js b/packages/client/src/components/dialogs/CreateEditRoomDialog/sub-components/SetRoomParams.js index e8d34001d3..aff841a6da 100644 --- a/packages/client/src/components/dialogs/CreateEditRoomDialog/sub-components/SetRoomParams.js +++ b/packages/client/src/components/dialogs/CreateEditRoomDialog/sub-components/SetRoomParams.js @@ -223,6 +223,7 @@ const SetRoomParams = ({ t={t} roomParams={roomParams} setRoomParams={setRoomParams} + isEdit={isEdit} /> )} diff --git a/packages/client/src/components/dialogs/CreateEditRoomDialog/sub-components/VirtualDataRoomBlock.js b/packages/client/src/components/dialogs/CreateEditRoomDialog/sub-components/VirtualDataRoomBlock.js index 94033d48a9..4c833ddb1d 100644 --- a/packages/client/src/components/dialogs/CreateEditRoomDialog/sub-components/VirtualDataRoomBlock.js +++ b/packages/client/src/components/dialogs/CreateEditRoomDialog/sub-components/VirtualDataRoomBlock.js @@ -1,16 +1,17 @@ import { useState } from "react"; import { Trans } from "react-i18next"; import styled from "styled-components"; +import { inject, observer } from "mobx-react"; + import { Text } from "@docspace/shared/components/text"; import { ToggleButton } from "@docspace/shared/components/toggle-button"; + import FileLifetime from "./FileLifetime"; +import WatermarkBlock from "./Watermarks/WatermarkBlock"; const StyledVirtualDataRoomBlock = styled.div` .virtual-data-room-block { margin-bottom: 18px; - :last-child { - margin-bottom: -26px; - } .virtual-data-room-block_header { display: flex; @@ -26,6 +27,9 @@ const StyledVirtualDataRoomBlock = styled.div` color: ${({ theme }) => theme.editLink.text.color}; } + .virtual-data-room-block_content { + margin-top: 16px; + } } `; @@ -64,18 +68,20 @@ const Block = ({ ); }; -const VirtualDataRoomBlock = ({ t, roomParams, setRoomParams }) => { +const VirtualDataRoomBlock = ({ t, roomParams, setRoomParams, isEdit }) => { const role = t("Translations:RoleViewer"); - const [fileLifetimeChecked, setFileLifetimeChecked] = useState(false); + const [fileLifetimeChecked, setFileLifetimeChecked] = useState( + !!roomParams?.lifetime, + ); const [copyAndDownloadChecked, setCopyAndDownloadChecked] = useState(false); - const [watermarksChecked, setWatermarksChecked] = useState(false); const onChangeAutomaticIndexing = () => { setRoomParams({ ...roomParams, indexing: !roomParams.indexing }); }; const onChangeFileLifetime = () => { + if (fileLifetimeChecked) setRoomParams({ ...roomParams, lifetime: null }); setFileLifetimeChecked(!fileLifetimeChecked); }; @@ -83,10 +89,6 @@ const VirtualDataRoomBlock = ({ t, roomParams, setRoomParams }) => { setCopyAndDownloadChecked(!copyAndDownloadChecked); }; - const onChangeAddWatermarksToDocuments = () => { - setWatermarksChecked(!watermarksChecked); - }; - return ( { isDisabled={false} isChecked={fileLifetimeChecked} > - + { isDisabled={false} isChecked={copyAndDownloadChecked} > - + + ); }; 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..178f12b03c --- /dev/null +++ b/packages/client/src/components/dialogs/CreateEditRoomDialog/sub-components/Watermarks/ImageWatermark.js @@ -0,0 +1,334 @@ +// (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 { inject, observer } from "mobx-react"; + +import { Text } from "@docspace/shared/components/text"; +import { ComboBox } from "@docspace/shared/components/combobox"; +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 { ButtonDelete } from "@docspace/shared/components/image-editor"; +import { HelpButton } from "@docspace/shared/components/help-button"; + +import { StyledWatermark } from "./StyledComponent"; + +const scaleOptions = [ + { key: 100, label: "100" }, + { key: 200, label: "200" }, + { key: 300, label: "300" }, + { key: 400, label: "400" }, + { key: 500, label: "500" }, +]; + +const rotateOptions = [ + { key: 0, label: "0" }, + { key: 30, label: "30" }, + { key: 45, label: "45" }, + { key: 60, label: "60" }, + { key: 90, label: "90" }, +]; +const getInitialScale = (scale, isEdit) => { + if (!isEdit || !scale) return scaleOptions[0]; + + return scaleOptions.find((item) => { + return item.key === scale; + }); +}; + +const getInitialRotate = (rotate, isEdit) => { + if (!isEdit) return rotateOptions[0]; + + const item = rotateOptions.find((item) => { + return item.key === rotate; + }); + + return !item ? rotateOptions[0] : item; +}; + +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 = { + rotate: getInitialRotate(initialWatermarksSettings?.rotate, isEdit), + scale: getInitialScale(initialWatermarksSettings?.imageScale, isEdit), + }; + } + + const initialInfoRef = initialInfo.current; + + useEffect(() => { + const { enabled, isImage } = initialWatermarksSettings; + + if (isEdit && enabled && isImage) { + setWatermarks(initialWatermarksSettings); + + 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) => { + imageProcessing(file) + .then((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 ( + error instanceof Error && + error.message === "recursion depth exceeded" + ) { + toastr.error(t("Common:SizeImageLarge")); + } + }); + }; + + const onScaleChange = (item) => { + setScale(item); + + setWatermarks({ imageScale: item.key }); + }; + + const onRotateChange = (item) => { + setRotate(item); + + setWatermarks({ rotate: item.key }); + }; + + const onButtonClick = () => { + if (previewRef.current) { + URL.revokeObjectURL(previewRef.current); + previewRef.current = null; + } + + setWatermarks({ image: null, imageUrl: null }); + setImageUrl(""); + }; + + const rotateItems = () => { + const items = rotateOptions.map((item) => { + return ( + onRotateChange(item)} + > + {item.label}° + + ); + }); + + return
{items}
; + }; + + const scaleItems = () => { + const items = scaleOptions.map((item) => { + return ( + onScaleChange(item)} + > + {item.label}% + + ); + }); + + return
{items}
; + }; + + // const onSelectFile = (fileInfo) => { + // setWatermarks({ image: fileInfo }); + // }; + + console.log("selectedRotate", selectedRotate.key, selectedScale.key); + return ( + + {!selectedImageUrl && ( + + )} + + {/* */} + + {selectedImageUrl && ( +
+
+
+ + {t("WatermarkPreview")} + + {t("WatermarkPreviewHelp")} + } + offsetRight={0} + className="settings_unavailable" + /> +
+
+ logo +
+ +
+ +
+
+ + {t("Scale")} + + +
{selectedScale.label}%
+
+
+
+ + {t("Rotate")} + + + +
{selectedRotate.label}°
+
+
+
+
+ )} +
+ ); +}; + +export default inject(({ createEditRoomStore }) => { + const { setWatermarks, initialWatermarksSettings, watermarksSettings } = + createEditRoomStore; + + const { imageUrl } = 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 new file mode 100644 index 0000000000..3d80b6f43a --- /dev/null +++ b/packages/client/src/components/dialogs/CreateEditRoomDialog/sub-components/Watermarks/StyledComponent.js @@ -0,0 +1,90 @@ +// (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 styled, { css } from "styled-components"; + +const StyledWatermark = styled.div` + margin-top: 16px; + + .watermark-title { + margin: 16px 0 8px 0; + } + .title-without-top { + margin-top: 0px; + } + .watermark-checkbox { + margin: 18px 0 0 0; + } + + .options-wrapper { + display: grid; + grid-template-rows: 56px 56px; + gap: 16px; + } + + .image-wrapper { + display: grid; + grid-template-columns: 216px auto; + gap: 16px; + + .image-description { + display: flex; + gap: 8px; + align-items: baseline; + + .image-watermark_text { + margin-bottom: 8px; + } + } + + .image-watermark_wrapper { + 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 { + } +`; + +export { StyledWatermark, StyledBody }; 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 new file mode 100644 index 0000000000..7cf56facd5 --- /dev/null +++ b/packages/client/src/components/dialogs/CreateEditRoomDialog/sub-components/Watermarks/ViewerInfo.js @@ -0,0 +1,240 @@ +// (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 { inject, observer } from "mobx-react"; + +import { TextInput } from "@docspace/shared/components/text-input"; +import { Text } from "@docspace/shared/components/text"; +import { ComboBox } from "@docspace/shared/components/combobox"; +import { WatermarkAdditions } from "@docspace/shared/enums"; + +import { StyledWatermark } from "./StyledComponent"; +import { Tabs, TabsTypes } from "@docspace/shared/components/tabs"; + +const tabsOptions = (t) => [ + { + id: "UserName", + name: t("UserName"), + index: 0, + }, + { + id: "UserEmail", + name: t("UserEmail"), + index: 1, + }, + { + id: "UserIpAdress", + name: t("UserIPAddress"), + index: 2, + }, + { + id: "CurrentDate", + name: t("Common:CurrentDate"), + index: 3, + }, + { + id: "RoomName", + name: t("Common:RoomName"), + index: 4, + }, +]; + +const getInitialState = (initialTab) => { + const state = { + UserName: false, + UserEmail: false, + UserIpAdress: false, + CurrentDate: false, + RoomName: false, + }; + + initialTab.map((item) => { + state[item.id] = true; + }); + + 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.id]); +}; + +const rotateOptions = (t) => [ + { key: -45, label: t("Diagonal") }, + { key: 0, label: t("Horizontal") }, +]; + +const getInitialRotate = (rotate, isEdit, t) => { + const dataRotate = rotateOptions(t); + + if (!isEdit) return dataRotate[0]; + + const item = dataRotate.find((item) => { + return item.key === rotate; + }); + + return !item ? dataRotate[0] : item; +}; + +const ViewerInfoWatermark = ({ + isEdit, + + setWatermarks, + + initialWatermarksSettings, +}) => { + const { t } = useTranslation(["CreateEditRoomDialog", "Common"]); + + const elements = useRef(null); + const initialInfo = useRef(null); + + if (initialInfo.current === null) { + initialInfo.current = { + dataRotate: rotateOptions(t), + dataTabs: tabsOptions(t), + tabs: getInitialTabs(initialWatermarksSettings?.additions, isEdit, t), + rotate: getInitialRotate(initialWatermarksSettings?.rotate, isEdit, t), + text: getInitialText(initialWatermarksSettings?.text, isEdit), + }; + + elements.current = getInitialState(initialInfo.current.tabs); + } + + const initialInfoRef = initialInfo.current; + + useEffect(() => { + const { enabled, isImage } = initialWatermarksSettings; + + if (isEdit && enabled && !isImage) { + setWatermarks(initialWatermarksSettings); + + return; + } + + setWatermarks({ + rotate: initialInfoRef.rotate.key, + additions: WatermarkAdditions.UserName, + isImage: false, + enabled: true, + image: "", + imageWidth: 0, + imageHeight: 0, + imageScale: 0, + }); + }, []); + + const [selectedPosition, setSelectedPosition] = useState( + initialInfoRef.rotate, + ); + const [textValue, setTextValue] = useState(initialInfoRef.text); + + const onSelect = (item) => { + let elementsData = elements.current; + let flagsCount = 0; + + const key = item.id; + + elementsData[key] = !elementsData[item.id]; + + for (const key in elementsData) { + const value = elementsData[key]; + + if (value) { + flagsCount += WatermarkAdditions[key]; + } + } + + setWatermarks({ additions: flagsCount }); + }; + + const onPositionChange = (item) => { + setSelectedPosition(item); + + setWatermarks({ rotate: item.key }); + }; + + const onTextChange = (e) => { + const { value } = e.target; + setTextValue(value); + + setWatermarks({ text: value }); + }; + + return ( + + + {t("AddWatermarkElements")} + + + item.index)} + onSelect={onSelect} + type={TabsTypes.Secondary} + multiple + /> + + + {t("AddStaticText")} + + + + + {t("Position")} + + + + ); +}; + +export default inject(({ createEditRoomStore }) => { + const { setWatermarks, initialWatermarksSettings } = createEditRoomStore; + + return { + setWatermarks, + initialWatermarksSettings, + }; +})(observer(ViewerInfoWatermark)); diff --git a/packages/client/src/components/dialogs/CreateEditRoomDialog/sub-components/Watermarks/WatermarkBlock.js b/packages/client/src/components/dialogs/CreateEditRoomDialog/sub-components/Watermarks/WatermarkBlock.js new file mode 100644 index 0000000000..49acda1955 --- /dev/null +++ b/packages/client/src/components/dialogs/CreateEditRoomDialog/sub-components/Watermarks/WatermarkBlock.js @@ -0,0 +1,102 @@ +// (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, useEffect } from "react"; + +import { inject, observer } from "mobx-react"; + +import Watermarks from "./index"; + +const WatermarkBlock = ({ + BlockComponent, + setWatermarks, + isEdit = false, + isWatermarks = false, + resetWatermarks, + t, +}) => { + useEffect(() => { + return () => resetWatermarks(); + }, []); + + const [watermarksChecked, setWatermarksChecked] = useState( + isWatermarks && isEdit, + ); + + const onChangeAddWatermarksToDocuments = () => { + setWatermarksChecked(!watermarksChecked); + + setWatermarks({ enabled: !watermarksChecked }); + }; + + return ( + + + + ); +}; + +export default inject(({ createEditRoomStore }) => { + const { setWatermarks, initialWatermarksSettings, resetWatermarks } = + createEditRoomStore; + + return { + setWatermarks, + isWatermarks: initialWatermarksSettings?.enabled, + resetWatermarks, + }; +})(observer(WatermarkBlock)); 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 new file mode 100644 index 0000000000..d1a42f5a38 --- /dev/null +++ b/packages/client/src/components/dialogs/CreateEditRoomDialog/sub-components/Watermarks/index.js @@ -0,0 +1,102 @@ +// (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 } from "react"; +import { useTranslation } from "react-i18next"; +import { inject, observer } from "mobx-react"; + +import { RadioButtonGroup } from "@docspace/shared/components/radio-button-group"; + +import ViewerInfoWatermark from "./ViewerInfo"; +import { StyledBody } from "./StyledComponent"; +import ImageWatermark from "./ImageWatermark"; + +const imageWatermark = "image", + viewerInfoWatermark = "viewerInfo"; + +const options = (t) => [ + { + label: t("ViewerInfo"), + value: viewerInfoWatermark, + }, + { + label: t("Common:Image"), + value: imageWatermark, + }, +]; + +const getOptionType = (additions, isEdit) => { + if (isEdit) { + return additions === 0 ? imageWatermark : viewerInfoWatermark; + } + + return viewerInfoWatermark; +}; +const Watermarks = ({ isEdit, setWatermarks, initialWatermarksSettings }) => { + const { t } = useTranslation(["CreateEditRoomDialog", "Common"]); + const [type, setType] = useState( + getOptionType(initialWatermarksSettings?.additions, isEdit), + ); + + const onSelectType = (e) => { + const { value } = e.target; + + setType(value); + setWatermarks({ + isImage: type === imageWatermark, + }); + }; + + const typeOptions = options(t); + + return ( + + + + {type === imageWatermark ? ( + + ) : ( + + )} + + ); +}; + +export default inject(({ createEditRoomStore }) => { + const { setWatermarks, initialWatermarksSettings } = createEditRoomStore; + return { + setWatermarks, + initialWatermarksSettings, + }; +})(observer(Watermarks)); diff --git a/packages/client/src/components/dialogs/CreateRoomConfirmDialog/index.js b/packages/client/src/components/dialogs/CreateRoomConfirmDialog/index.js index 2cc3368462..27fe736cf5 100644 --- a/packages/client/src/components/dialogs/CreateRoomConfirmDialog/index.js +++ b/packages/client/src/components/dialogs/CreateRoomConfirmDialog/index.js @@ -29,6 +29,7 @@ import { ModalDialog } from "@docspace/shared/components/modal-dialog"; import { withTranslation } from "react-i18next"; import { inject, observer } from "mobx-react"; import { Button } from "@docspace/shared/components/button"; +import { RoomsType } from "@docspace/shared/enums"; const CreateRoomConfirmDialog = ({ t, @@ -46,6 +47,10 @@ const CreateRoomConfirmDialog = ({ const onClose = () => setVisible(false); + const bodyText = RoomsType.VirtualDataRoom + ? t("CreateEditRoomDialog:CreateRoomWatermarksConfirmation") + : t("CreateEditRoomDialog:CreateRoomConfirmation"); + return ( {t("Common:Warning")} - - {t("CreateEditRoomDialog:CreateRoomConfirmation")} - + {bodyText}