From 3e999329868554bfd8e0c3e3de9659b5f72f14d5 Mon Sep 17 00:00:00 2001 From: gopienkonikita Date: Wed, 24 Apr 2024 10:27:39 +0300 Subject: [PATCH] Web: Files: Templates: added tile view --- packages/client/src/HOCs/withFileActions.js | 14 + packages/client/src/components/Badges.js | 2 +- .../Section/Body/RowsView/SimpleFilesRow.js | 9 +- .../Section/Body/TableView/StyledTable.js | 3 +- .../TableView/sub-components/ContentCell.js | 14 +- .../Home/Section/Body/TilesView/FileTile.js | 3 + .../Body/TilesView/FilesTileContent.js | 22 +- .../TilesView/sub-components/TemplatesTile.js | 556 ++++++++++++++++++ .../Body/TilesView/sub-components/Tile.js | 23 +- .../TilesView/sub-components/TileContainer.js | 11 - .../TilesView/sub-components/TileContent.js | 1 + packages/shared/themes/base.ts | 1 + packages/shared/themes/dark.ts | 1 + 13 files changed, 626 insertions(+), 34 deletions(-) create mode 100644 packages/client/src/pages/Home/Section/Body/TilesView/sub-components/TemplatesTile.js diff --git a/packages/client/src/HOCs/withFileActions.js b/packages/client/src/HOCs/withFileActions.js index d495472108..4ba3b1fbae 100644 --- a/packages/client/src/HOCs/withFileActions.js +++ b/packages/client/src/HOCs/withFileActions.js @@ -29,6 +29,7 @@ import { inject, observer } from "mobx-react"; import { DeviceType, RoomsType } from "@docspace/shared/enums"; import Planet12ReactSvgUrl from "PUBLIC_DIR/images/icons/12/planet.react.svg?url"; +import { isMobile } from "@docspace/shared/utils"; export default function withFileActions(WrappedFileItem) { class WithFileActions extends React.Component { @@ -258,6 +259,16 @@ export default function withFileActions(WrappedFileItem) { } }; + additionalComponent = () => { + const { t, item } = this.props; + const { contentLength, providerKey, foldersCount, filesCount } = item; + + if (!contentLength && !providerKey && !isMobile()) + return `${t("Translations:Folders")}: ${foldersCount} | ${t("Translations:Files")}: ${filesCount}`; + + return ""; + }; + render() { const { item, @@ -317,6 +328,8 @@ export default function withFileActions(WrappedFileItem) { const badgeUrl = showPlanetIcon ? Planet12ReactSvgUrl : null; + const additionalInfo = this.additionalComponent(); + return ( ); diff --git a/packages/client/src/components/Badges.js b/packages/client/src/components/Badges.js index d5edfa1f8c..0f12c166c1 100644 --- a/packages/client/src/components/Badges.js +++ b/packages/client/src/components/Badges.js @@ -329,7 +329,7 @@ const Badges = ({ { - const { contentLength, providerKey, foldersCount, filesCount } = item; - - const additionalComponent = () => { - if (!contentLength && !providerKey && !isMobile()) - return `${t("Translations:Folders")}: ${foldersCount} | ${t("Translations:Files")}: ${filesCount}`; - - return ""; - }; - - const additionalInfo = additionalComponent(); - +const ContentCell = ({ sideColor, additionalInfo }) => { return ( { onDragOver, onDragLeave, badgeUrl, + additionalInfo, } = props; const temporaryExtension = @@ -174,8 +175,10 @@ const FileTile = (props) => { withShiftSelect={withShiftSelect} isHighlight={isHighlight} thumbnails1280x720={thumbnails1280x720} + additionalInfo={additionalInfo} > { const { fileExst, title, viewAccessibility } = item; const isMedia = viewAccessibility?.ImageView || viewAccessibility?.MediaView; + const roomType = getRoomTypeTitleTranslation(item.roomType, t); + return ( <> {titleWithoutExt} + {isTemplate && ( + + {roomType} + + )} ); }; export default inject(({ settingsStore, treeFoldersStore }) => { - const { isRoomsFolder, isArchiveFolder } = treeFoldersStore; + const { isRoomsFolder, isArchiveFolder, isTemplatesFolder } = + treeFoldersStore; const isRooms = isRoomsFolder || isArchiveFolder; @@ -178,6 +197,7 @@ export default inject(({ settingsStore, treeFoldersStore }) => { theme: settingsStore.theme, currentDeviceType: settingsStore.currentDeviceType, isRooms, + isTemplate: isTemplatesFolder, }; })( observer( diff --git a/packages/client/src/pages/Home/Section/Body/TilesView/sub-components/TemplatesTile.js b/packages/client/src/pages/Home/Section/Body/TilesView/sub-components/TemplatesTile.js new file mode 100644 index 0000000000..fa0e805351 --- /dev/null +++ b/packages/client/src/pages/Home/Section/Body/TilesView/sub-components/TemplatesTile.js @@ -0,0 +1,556 @@ +// (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 { Checkbox } from "@docspace/shared/components/checkbox"; +import { ContextMenuButton } from "@docspace/shared/components/context-menu-button"; +import PropTypes from "prop-types"; +import React from "react"; +import { ReactSVG } from "react-svg"; +import styled, { css } from "styled-components"; +import { ContextMenu } from "@docspace/shared/components/context-menu"; +import { tablet } from "@docspace/shared/utils"; +import { isMobile } from "react-device-detect"; +import { withTheme } from "styled-components"; +import { Link } from "@docspace/shared/components/link"; +import { Text } from "@docspace/shared/components/text"; +import { Loader } from "@docspace/shared/components/loader"; +import { Base } from "@docspace/shared/themes"; +import { ROOMS_TYPE_TRANSLATIONS } from "@docspace/shared/constants"; + +const svgLoader = () =>
; + +const StyledTemplatesTile = styled.div` + display: contents; + + .room-tile-template_top-content { + width: 100%; + height: 66px; + + box-sizing: border-box; + + display: flex; + flex-direction: row; + flex-wrap: nowrap; + + justify-content: flex-start; + align-items: center; + align-content: center; + + background: ${(props) => + props.theme.filesSection.tilesView.tile.backgroundColor}; + + border-radius: ${({ theme, isRooms }) => + isRooms + ? theme.filesSection.tilesView.tile.roomsUpperBorderRadius + : theme.filesSection.tilesView.tile.upperBorderRadius}; + } + + .room-tile-template_bottom-content { + display: ${(props) => props.isThirdParty && "flex"}; + width: 100%; + height: 60px; + align-content: center; + + box-sizing: border-box; + overflow: hidden; + + padding: 0px 16px 8px; + background: ${(props) => + props.theme.filesSection.tilesView.tile.backgroundColor}; + border-radius: ${({ theme, isRooms }) => + isRooms + ? theme.filesSection.tilesView.tile.roomsBottomBorderRadius + : theme.filesSection.tilesView.tile.bottomBorderRadius}; + + .room-tile_bottom-content-wrapper { + display: grid; + grid-template-columns: 1fr 2fr; + /* gap: 14px; */ + + .room-tile_bottom-content_field { + display: flex; + flex-direction: column; + overflow: hidden; + gap: 8px; + } + } + } +`; + +const StyledContent = styled.div` + display: flex; + align-items: center; + gap: 8px; + flex-basis: 100%; + overflow: hidden; + + .tile-content, + .row-main-container-wrapper { + overflow: hidden; + } + + a { + display: block !important; + } + + .new-items { + margin-left: 12px; + } + + .badges { + display: flex; + flex-wrap: nowrap; + align-items: center; + gap: 12px; + + :not(:empty) { + margin-inline-start: 12px; + } + + > div { + margin: 0; + } + } + + @media ${tablet} { + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + } +`; + +const StyledElement = styled.div` + flex: 0 0 auto; + display: flex; + ${(props) => + props.theme.interfaceDirection === "rtl" + ? css` + margin-left: ${(props) => (props.isRoom ? "8px" : "4px")}; + ` + : css` + margin-right: ${(props) => (props.isRoom ? "8px" : "4px")}; + `} + user-select: none; + margin-top: 3px; + + height: 32px; + width: 32px; +`; + +const StyledOptionButton = styled.div` + display: block; + + .expandButton > div:first-child { + padding: 8px 15px; + } +`; + +StyledOptionButton.defaultProps = { theme: Base }; + +const badgesPosition = css` + ${(props) => + props.theme.interfaceDirection === "rtl" + ? css` + right: 9px; + ` + : css` + left: 9px; + `} + + .badges { + display: grid; + grid-template-columns: repeat(3, fit-content(60px)); + grid-template-rows: 32px; + grid-gap: 7px; + + .badge-new-version { + order: 1; + } + + .badge-version-current { + order: 2; + } + + .is-editing, + .can-convert { + order: 3; + } + } +`; + +const quickButtonsPosition = css` + ${(props) => + props.theme.interfaceDirection === "rtl" + ? css` + left: 9px; + ` + : css` + right: 9px; + `} + + .badges { + display: grid; + grid-template-columns: 32px; + grid-template-rows: repeat(3, 32px); + grid-gap: 7px; + } +`; + +const StyledIcons = styled.div` + position: absolute; + top: 8px; + + ${(props) => props.isBadges && badgesPosition} + ${(props) => props.isQuickButtons && quickButtonsPosition} + + .badge { + display: flex; + align-items: center; + justify-content: center; + padding: 8px; + background: ${(props) => + props.theme.filesSection.tilesView.tile.backgroundBadgeColor}; + border-radius: 4px; + box-shadow: 0px 2px 4px rgba(4, 15, 27, 0.16); + } +`; + +StyledIcons.defaultProps = { theme: Base }; + +class TemplatesTile extends React.PureComponent { + constructor(props) { + super(props); + + this.state = { + errorLoadSrc: false, + }; + + this.cm = React.createRef(); + this.tile = React.createRef(); + this.checkboxContainerRef = React.createRef(); + } + + onError = () => { + this.setState({ + errorLoadSrc: true, + }); + }; + + getIconFile = () => { + const { temporaryIcon, thumbnailClick, thumbnail, item } = this.props; + + const icon = item.isPlugin + ? item.fileTileIcon + : thumbnail && !this.state.errorLoadSrc + ? thumbnail + : temporaryIcon; + + return ( + + {thumbnail && !this.state.errorLoadSrc ? ( + Thumbnail-img + ) : ( + + )} + + ); + }; + + changeCheckbox = (e) => { + const { onSelect, item } = this.props; + onSelect && onSelect(e.target.checked, item); + }; + + onFileIconClick = () => { + if (!isMobile) return; + + const { onSelect, item } = this.props; + onSelect && onSelect(true, item); + }; + + onFileClick = (e) => { + const { + onSelect, + item, + checked, + setSelection, + withCtrlSelect, + withShiftSelect, + } = this.props; + + if (e.ctrlKey || e.metaKey) { + withCtrlSelect(item); + e.preventDefault(); + return; + } + + if (e.shiftKey) { + withShiftSelect(item); + e.preventDefault(); + return; + } + + if ( + e.detail === 1 && + !e.target.closest(".badges") && + !e.target.closest(".item-file-name") && + !e.target.closest(".tag") + ) { + if ( + e.target.nodeName !== "IMG" && + e.target.nodeName !== "INPUT" && + e.target.nodeName !== "rect" && + e.target.nodeName !== "path" && + e.target.nodeName !== "svg" && + !this.checkboxContainerRef.current?.contains(e.target) + ) { + setSelection && setSelection([]); + } + + onSelect && onSelect(!checked, item); + } + }; + + render() { + const { + checked, + children, + contextButtonSpacerWidth, + contextOptions, + element, + indeterminate, + tileContextClick, + item, + inProgress, + isEdit, + getContextModel, + hideContextMenu, + t, + selectOption, + additionalInfo, + theme, + } = this.props; + const { isFolder, isRoom, id, fileExst, createdBy } = item; + + const renderElement = Object.prototype.hasOwnProperty.call( + this.props, + "element", + ); + + const renderContext = + Object.prototype.hasOwnProperty.call(item, "contextOptions") && + contextOptions.length > 0; + + const getOptions = () => { + tileContextClick && tileContextClick(); + return contextOptions; + }; + + const onContextMenu = (e) => { + tileContextClick && tileContextClick(e.button === 2); + if (!this.cm.current.menuRef.current) { + this.tile.current.click(e); //TODO: need fix context menu to global + } + this.cm.current.show(e); + }; + const [FilesTileContent, badges] = children; + + const contextMenuHeader = { + icon: children[0].props.item.icon, + title: children[0].props.item.title, + color: children[0].props.item.logo?.color, + }; + + const title = item.isFolder + ? t("Translations:TitleShowFolderActions") + : t("Translations:TitleShowActions"); + + const tags = []; + + if (item.providerType) { + tags.push({ + isThirdParty: true, + icon: item.thirdPartyIcon, + label: item.providerKey, + onClick: () => + selectOption({ + option: "typeProvider", + value: item.providerType, + }), + }); + } + + if (item?.tags?.length > 0) { + tags.push(...item.tags); + } else { + tags.push({ + isDefault: true, + roomType: item.roomType, + label: t(ROOMS_TYPE_TRANSLATIONS[item.roomType]), + onClick: () => + selectOption({ + option: "defaultTypeRoom", + value: item.roomType, + }), + }); + } + + return ( + +
+ {renderElement && !(!fileExst && id === -1) && !isEdit && ( + <> + {!inProgress ? ( +
+ + {element} + + + +
+ ) : ( + + )} + + )} + + {FilesTileContent} + {badges} + + + {renderContext ? ( + + ) : ( +
+ )} + + +
+
+
+
+ + {t("Common:Owner")} + + + {t("Common:Content")} + +
+
+ + {createdBy.displayName} + + + {additionalInfo} + +
+
+
+ + ); + } +} + +TemplatesTile.propTypes = { + checked: PropTypes.bool, + children: PropTypes.oneOfType([ + PropTypes.arrayOf(PropTypes.element), + PropTypes.element, + ]), + className: PropTypes.string, + contextButtonSpacerWidth: PropTypes.string, + contextOptions: PropTypes.array, + element: PropTypes.element, + id: PropTypes.string, + indeterminate: PropTypes.bool, + onSelect: PropTypes.func, + tileContextClick: PropTypes.func, +}; + +TemplatesTile.defaultProps = { + contextButtonSpacerWidth: "32px", + item: {}, +}; + +export default withTheme(TemplatesTile); diff --git a/packages/client/src/pages/Home/Section/Body/TilesView/sub-components/Tile.js b/packages/client/src/pages/Home/Section/Body/TilesView/sub-components/Tile.js index 2a8ac1de28..8a65dd14c3 100644 --- a/packages/client/src/pages/Home/Section/Body/TilesView/sub-components/Tile.js +++ b/packages/client/src/pages/Home/Section/Body/TilesView/sub-components/Tile.js @@ -40,6 +40,7 @@ import { Base } from "@docspace/shared/themes"; import { Tags } from "@docspace/shared/components/tags"; import { Tag } from "@docspace/shared/components/tag"; import { ROOMS_TYPE_TRANSLATIONS } from "@docspace/shared/constants"; +import TemplatesTile from "./TemplatesTile"; const svgLoader = () =>
; @@ -100,11 +101,22 @@ const roomsStyles = css` isRooms ? theme.filesSection.tilesView.tile.roomsBottomBorderRadius : theme.filesSection.tilesView.tile.bottomBorderRadius}; + + .room-tile_bottom-content-wrapper { + display: flex; + gap: 20px; + + .room-tile_bottom-content_field { + display: flex; + flex-direction: column; + } + } } `; const FolderStyles = css` height: ${(props) => (props.isRoom ? "120px" : "64px")}; + height: 126px; //TODO: Templates `; const FileStyles = css` @@ -150,8 +162,8 @@ const StyledTile = styled.div` width: 100%; border: ${(props) => props.theme.filesSection.tilesView.tile.border}; - border-radius: ${({ isRooms, theme }) => - isRooms + border-radius: ${({ isRooms, isTemplate, theme }) => + isRooms && !isTemplate ? theme.filesSection.tilesView.tile.roomsBorderRadius : theme.filesSection.tilesView.tile.borderRadius}; ${(props) => props.showHotkeyBorder && "border-color: #2DA7DB"}; @@ -663,7 +675,7 @@ class Tile extends React.PureComponent { isHighlight, thumbnails1280x720, } = this.props; - const { isFolder, isRoom, id, fileExst } = item; + const { isFolder, isRoom, isTemplate, id, fileExst } = item; const renderElement = Object.prototype.hasOwnProperty.call( this.props, @@ -755,9 +767,12 @@ class Tile extends React.PureComponent { onClick={this.onFileClick} isThirdParty={item.providerType} isHighlight={isHighlight} + isTemplate={isTemplate} > {isFolder || (!fileExst && id === -1) ? ( - isRoom ? ( + isTemplate ? ( + + ) : isRoom ? ( <>
{renderElement && !(!fileExst && id === -1) && !isEdit && ( diff --git a/packages/client/src/pages/Home/Section/Body/TilesView/sub-components/TileContainer.js b/packages/client/src/pages/Home/Section/Body/TilesView/sub-components/TileContainer.js index dece73a15a..1e419fd4e7 100644 --- a/packages/client/src/pages/Home/Section/Body/TilesView/sub-components/TileContainer.js +++ b/packages/client/src/pages/Home/Section/Body/TilesView/sub-components/TileContainer.js @@ -207,17 +207,6 @@ const StyledTileContainer = styled.div` } } } - - @media ${tablet} { - ${(props) => - props.theme.interfaceDirection === "rtl" - ? css` - margin-left: -3px; - ` - : css` - margin-right: -3px; - `} - } `; StyledTileContainer.defaultProps = { theme: Base }; diff --git a/packages/client/src/pages/Home/Section/Body/TilesView/sub-components/TileContent.js b/packages/client/src/pages/Home/Section/Body/TilesView/sub-components/TileContent.js index 6f36942a9a..ba4af5e998 100644 --- a/packages/client/src/pages/Home/Section/Body/TilesView/sub-components/TileContent.js +++ b/packages/client/src/pages/Home/Section/Body/TilesView/sub-components/TileContent.js @@ -79,6 +79,7 @@ const TileContent = (props) => { onClick={onClick} > {children} diff --git a/packages/shared/themes/base.ts b/packages/shared/themes/base.ts index 8a42ec43bb..e4d4f0704c 100644 --- a/packages/shared/themes/base.ts +++ b/packages/shared/themes/base.ts @@ -2352,6 +2352,7 @@ export const getBaseTheme = () => { sideColor: black, color: black, textColor: gray, + subTextColor: "#657077", }, animationColor: "rgba(82, 153, 224, 0.16)", diff --git a/packages/shared/themes/dark.ts b/packages/shared/themes/dark.ts index 3b88f42302..c382dba5e1 100644 --- a/packages/shared/themes/dark.ts +++ b/packages/shared/themes/dark.ts @@ -2331,6 +2331,7 @@ const Dark: TTheme = { sideColor: grayMaxLight, color: grayMaxLight, textColor: "#858585", + subTextColor: "#858585", }, animationColor: "rgba(82, 153, 224, 0.16)",