diff --git a/config/appsettings.json b/config/appsettings.json index 564e5652f1..0e4ae5e01b 100644 --- a/config/appsettings.json +++ b/config/appsettings.json @@ -159,7 +159,7 @@ "enabled": "true" }, "thumbnail": { - "thumbnaillHeight": 156, - "thumbnaillWidth": 216 + "thumbnaillHeight": 260, + "thumbnaillWidth": 360 } } \ No newline at end of file diff --git a/config/kafka.json b/config/kafka.json index 1f77afe240..02fa6e1afb 100644 --- a/config/kafka.json +++ b/config/kafka.json @@ -1,5 +1,5 @@ -{ - "kafka": { - "BootstrapServers": "" - } -} +{ + "kafka": { + "BootstrapServers": "localhost:9092" + } +} diff --git a/packages/asc-web-common/components/FilterInput/index.js b/packages/asc-web-common/components/FilterInput/index.js index 034fd0bfcb..5a94aa689b 100644 --- a/packages/asc-web-common/components/FilterInput/index.js +++ b/packages/asc-web-common/components/FilterInput/index.js @@ -13,103 +13,105 @@ import SortButton from "./sub-components/SortButton"; import { StyledFilterInput, StyledSearchInput } from "./StyledFilterInput"; -const FilterInput = ({ - t, - sectionWidth, - getFilterData, - getSortData, - getViewSettingsData, - getSelectedFilterData, - onFilter, - onSearch, - onSort, - onChangeViewAs, - viewAs, - placeholder, - contextMenuHeader, - headerLabel, - viewSelectorVisible, - isRecentFolder, - isFavoritesFolder, - ...props -}) => { - const [viewSettings, setViewSettings] = React.useState([]); - const [selectedFilterData, setSelectedFilterData] = React.useState([]); +const FilterInput = React.memo( + ({ + t, + sectionWidth, + getFilterData, + getSortData, + getViewSettingsData, + getSelectedFilterData, + onFilter, + onSearch, + onSort, + onChangeViewAs, + viewAs, + placeholder, + contextMenuHeader, + headerLabel, + viewSelectorVisible, + isRecentFolder, + isFavoritesFolder, + ...props + }) => { + const [viewSettings, setViewSettings] = React.useState([]); + const [selectedFilterData, setSelectedFilterData] = React.useState([]); - const [inputValue, setInputValue] = React.useState(""); + const [inputValue, setInputValue] = React.useState(""); - const getSelectedFilterDataAction = React.useCallback(async () => { - const data = await getSelectedFilterData(); + const getSelectedFilterDataAction = React.useCallback(async () => { + const data = await getSelectedFilterData(); - setSelectedFilterData(data); - setInputValue(!!data.inputValue ? data.inputValue : ""); - }, [getSelectedFilterData]); + setSelectedFilterData(data); + setInputValue(!!data.inputValue ? data.inputValue : ""); + }, [getSelectedFilterData]); - React.useEffect(() => { - getSelectedFilterDataAction(); - }, [getSelectedFilterData]); + React.useEffect(() => { + getSelectedFilterDataAction(); + }, [getSelectedFilterData]); - React.useEffect(() => { - getViewSettingsData && setViewSettings(getViewSettingsData()); - }, [getViewSettingsData]); + React.useEffect(() => { + getViewSettingsData && setViewSettings(getViewSettingsData()); + }, [getViewSettingsData]); - const onClearSearch = () => { - onSearch && onSearch(); - }; + const onClearSearch = () => { + onSearch && onSearch(); + }; - return ( - - - - - - {viewSettings && - !isMobile && - viewSelectorVisible && - !isMobileUtils() && - !isTabletUtils() ? ( - + - ) : ( - <> - {(isMobile || isTabletUtils() || isMobileUtils()) && ( - - )} - - )} - - ); -}; + + + + {viewSettings && + !isMobile && + viewSelectorVisible && + !isMobileUtils() && + !isTabletUtils() ? ( + + ) : ( + <> + {(isMobile || isTabletUtils() || isMobileUtils()) && ( + + )} + + )} + + ); + } +); FilterInput.defaultProps = { viewSelectorVisible: false, }; -export default React.memo(FilterInput); +export default FilterInput; diff --git a/packages/asc-web-common/components/Navigation/Navigation.js b/packages/asc-web-common/components/Navigation/Navigation.js index e7b73d09bc..529cfacb3b 100644 --- a/packages/asc-web-common/components/Navigation/Navigation.js +++ b/packages/asc-web-common/components/Navigation/Navigation.js @@ -30,6 +30,10 @@ const Navigation = ({ isRecycleBinFolder, isEmptyFilesList, clearTrash, + showFolderInfo, + isCurrentFolderInfo, + toggleInfoPanel, + isInfoPanelVisible, ...rest }) => { const [isOpen, setIsOpen] = React.useState(false); @@ -133,6 +137,8 @@ const Navigation = ({ isRecycleBinFolder={isRecycleBinFolder} isEmptyFilesList={isEmptyFilesList} clearTrash={clearTrash} + toggleInfoPanel={toggleInfoPanel} + isInfoPanelVisible={isInfoPanelVisible} /> diff --git a/packages/asc-web-common/components/Navigation/StyledNavigation.js b/packages/asc-web-common/components/Navigation/StyledNavigation.js index 9d664a94ec..5765ce9195 100644 --- a/packages/asc-web-common/components/Navigation/StyledNavigation.js +++ b/packages/asc-web-common/components/Navigation/StyledNavigation.js @@ -3,16 +3,11 @@ import { isMobile, isMobileOnly } from "react-device-detect"; import { tablet, desktop, mobile } from "@appserver/components/utils/device"; const StyledContainer = styled.div` - padding: ${(props) => (props.isDropBox ? "14px 0 3px" : "14px 0 0px")}; - - width: fit-content; - + width: 100% !important; display: grid; - - grid-template-columns: ${(props) => - props.isRootFolder ? "1fr auto" : "29px 1fr auto"}; - align-items: center; + grid-template-columns: ${(props) => + props.isRootFolder ? "auto 1fr" : "29px auto 1fr"}; .arrow-button { width: 17px; @@ -31,8 +26,7 @@ const StyledContainer = styled.div` `} @media ${mobile} { - width: 100%; - padding: ${(props) => (props.isDropBox ? "12px 0 5px" : "12px 0 0")}; + height: 53px; } ${isMobileOnly && diff --git a/packages/asc-web-common/components/Navigation/sub-components/control-btn.js b/packages/asc-web-common/components/Navigation/sub-components/control-btn.js index e7b5e1a463..c7bdb84f36 100644 --- a/packages/asc-web-common/components/Navigation/sub-components/control-btn.js +++ b/packages/asc-web-common/components/Navigation/sub-components/control-btn.js @@ -5,6 +5,7 @@ import ContextMenuButton from "@appserver/components/context-menu-button"; import IconButton from "@appserver/components/icon-button"; import { isMobile } from "react-device-detect"; import { tablet } from "@appserver/components/utils/device"; +import { Base } from "@appserver/components/themes"; const StyledContainer = styled.div` margin-left: 20px; @@ -30,7 +31,8 @@ const StyledContainer = styled.div` } .option-button { - margin-right: 8px; + margin-left: auto; + margin-right: 15px; min-width: 17px; } @@ -39,6 +41,35 @@ const StyledContainer = styled.div` } `; +const StyledInfoPanelToggleWrapper = styled.div` + display: flex; + align-items: center; + align-self: center; + justify-content: center; + margin-left: ${({ isRootFolder }) => (isRootFolder ? "auto" : "none")}; + + .info-panel-toggle-bg { + height: 32px; + width: 32px; + display: flex; + align-items: center; + justify-content: center; + border-radius: 50%; + background-color: ${(props) => + props.isInfoPanelVisible + ? props.theme.infoPanel.sectionHeaderToggleBgActive + : props.theme.infoPanel.sectionHeaderToggleBg}; + + path { + fill: ${(props) => + props.isInfoPanelVisible + ? props.theme.infoPanel.sectionHeaderToggleIconActive + : props.theme.infoPanel.sectionHeaderToggleIcon}; + } + } +`; +StyledInfoPanelToggleWrapper.defaultProps = { theme: Base }; + const ControlButtons = ({ personal, isDropBox, @@ -49,6 +80,8 @@ const ControlButtons = ({ isRecycleBinFolder, isEmptyFilesList, clearTrash, + isInfoPanelVisible, + toggleInfoPanel, }) => { return ( @@ -96,6 +129,20 @@ const ControlButtons = ({ ) : ( <> )} + +
+ +
+
); }; diff --git a/packages/asc-web-common/components/Section/index.js b/packages/asc-web-common/components/Section/index.js index f74c27ca19..516f1d919f 100644 --- a/packages/asc-web-common/components/Section/index.js +++ b/packages/asc-web-common/components/Section/index.js @@ -19,6 +19,10 @@ import SubSectionBody from "./sub-components/section-body"; import SubSectionBodyContent from "./sub-components/section-body-content"; import SubSectionBar from "./sub-components/section-bar"; import SubSectionPaging from "./sub-components/section-paging"; +//import SectionToggler from "./sub-components/section-toggler"; +import InfoPanel from "./sub-components/info-panel"; +import SubInfoPanelBody from "./sub-components/info-panel-body"; +import SubInfoPanelHeader from "./sub-components/info-panel-header"; import ReactResizeDetector from "react-resize-detector"; import FloatingButton from "../FloatingButton"; @@ -36,8 +40,13 @@ const StyledMainBar = styled.div` box-sizing: border-box; margin-left: -20px; - width: calc(100vw - 256px); - max-width: calc(100vw - 256px); + /* width: calc(100vw - 256px); + max-width: calc(100vw - 256px); */ + + width: ${(props) => + props.infoPanelIsVisible ? "calc(100vw - 657px)" : "calc(100vw - 256px)"}; + max-width: ${(props) => + props.infoPanelIsVisible ? "calc(100vw - 657px)" : "calc(100vw - 256px)"}; #bar-banner { margin-bottom: -3px; @@ -123,12 +132,24 @@ function SectionPaging() { } SectionPaging.displayName = "SectionPaging"; +function InfoPanelBody() { + return null; +} +InfoPanelBody.displayName = "InfoPanelBody"; + +function InfoPanelHeader() { + return null; +} +InfoPanelHeader.displayName = "InfoPanelHeader"; + class Section extends React.Component { static SectionHeader = SectionHeader; static SectionFilter = SectionFilter; static SectionBody = SectionBody; static SectionBar = SectionBar; static SectionPaging = SectionPaging; + static InfoPanelBody = InfoPanelBody; + static InfoPanelHeader = InfoPanelHeader; constructor(props) { super(props); @@ -208,6 +229,7 @@ class Section extends React.Component { setMaintenanceExist, snackbarExist, showText, + infoPanelIsVisible, } = this.props; let sectionHeaderContent = null; @@ -215,6 +237,9 @@ class Section extends React.Component { let sectionFilterContent = null; let sectionPagingContent = null; let sectionBodyContent = null; + let infoPanelBodyContent = null; + let infoPanelHeaderContent = null; + React.Children.forEach(children, (child) => { const childType = child && child.type && (child.type.displayName || child.type.name); @@ -235,6 +260,12 @@ class Section extends React.Component { case SectionBody.displayName: sectionBodyContent = child; break; + case InfoPanelBody.displayName: + infoPanelBodyContent = child; + break; + case InfoPanelHeader.displayName: + infoPanelHeaderContent = child; + break; default: break; } @@ -277,6 +308,7 @@ class Section extends React.Component { maintenanceExist={maintenanceExist} isSectionBarAvailable={isSectionBarAvailable} isSectionHeaderAvailable={isSectionHeaderAvailable} + infoPanelIsVisible={infoPanelIsVisible} > {!isMobile && ( @@ -322,6 +356,7 @@ class Section extends React.Component { )} + {isSectionBodyAvailable && ( <> {sectionHeaderContent ? sectionHeaderContent.props.children @@ -372,11 +409,13 @@ class Section extends React.Component { : null} )} + {sectionBodyContent ? sectionBodyContent.props.children : null} + {isSectionPagingAvailable && ( {sectionPagingContent @@ -387,6 +426,7 @@ class Section extends React.Component { )} + {!(isMobile || isMobileUtils() || isTabletUtils()) ? ( showPrimaryProgressBar && showSecondaryProgressBar ? ( <> @@ -428,6 +468,12 @@ class Section extends React.Component { <> )} + + + {infoPanelHeaderContent} + + {infoPanelBodyContent} + )} @@ -501,12 +547,14 @@ Section.defaultProps = { withBodyAutoFocus: false, }; +Section.InfoPanelHeader = InfoPanelHeader; +Section.InfoPanelBody = InfoPanelBody; Section.SectionHeader = SectionHeader; Section.SectionFilter = SectionFilter; Section.SectionBody = SectionBody; Section.SectionPaging = SectionPaging; -export default inject(({ auth }) => { +export default inject(({ auth, infoPanelStore }) => { const { isLoaded, settingsStore } = auth; const { isHeaderVisible, @@ -523,6 +571,9 @@ export default inject(({ auth }) => { showText, } = settingsStore; + let infoPanelIsVisible = false; + if (infoPanelStore) infoPanelIsVisible = infoPanelStore.isVisible; + return { isLoaded, isTabletView, @@ -536,5 +587,7 @@ export default inject(({ auth }) => { isDesktop: isDesktopClient, showText, + + infoPanelIsVisible: infoPanelIsVisible, }; })(observer(Section)); diff --git a/packages/asc-web-common/components/Section/sub-components/info-panel-body.js b/packages/asc-web-common/components/Section/sub-components/info-panel-body.js new file mode 100644 index 0000000000..918caefd6b --- /dev/null +++ b/packages/asc-web-common/components/Section/sub-components/info-panel-body.js @@ -0,0 +1,16 @@ +import Scrollbar from "@appserver/components/scrollbar"; +import React from "react"; + +const SubInfoPanelBody = ({ children }) => { + const content = children?.props?.children; + + return ( + + {content} + + ); +}; + +SubInfoPanelBody.displayName = "SubInfoPanelBody"; + +export default SubInfoPanelBody; diff --git a/packages/asc-web-common/components/Section/sub-components/info-panel-header.js b/packages/asc-web-common/components/Section/sub-components/info-panel-header.js new file mode 100644 index 0000000000..d4741c0ec7 --- /dev/null +++ b/packages/asc-web-common/components/Section/sub-components/info-panel-header.js @@ -0,0 +1,58 @@ +import IconButton from "@appserver/components/icon-button"; +import Text from "@appserver/components/text"; +import { Base } from "@appserver/components/themes"; +import { tablet } from "@appserver/components/utils/device"; +import { inject, observer } from "mobx-react"; +import PropTypes from "prop-types"; +import React from "react"; +import styled from "styled-components"; + +const StyledInfoPanelHeader = styled.div` + width: 100%; + max-width: 100%; + height: 54px; + min-height: 54px; + box-sizing: border-box; + display: flex; + justify-content: space-between; + align-items: center; + border-bottom: ${(props) => `1px solid ${props.theme.infoPanel.borderColor}`}; + + .header-text { + margin-left: 20px; + } +`; + +const SubInfoPanelHeader = ({ children, onHeaderCrossClick }) => { + const content = children?.props?.children; + + return ( + + + {content} + + + ); +}; + +SubInfoPanelHeader.propTypes = { + children: PropTypes.oneOfType([ + PropTypes.arrayOf(PropTypes.node), + PropTypes.node, + PropTypes.any, + ]), + toggleIsVisible: PropTypes.func, +}; + +StyledInfoPanelHeader.defaultProps = { theme: Base }; +SubInfoPanelHeader.defaultProps = { theme: Base }; + +SubInfoPanelHeader.displayName = "SubInfoPanelHeader"; + +export default inject(({ infoPanelStore }) => { + let onHeaderCrossClick = () => {}; + if (infoPanelStore) { + onHeaderCrossClick = infoPanelStore.onHeaderCrossClick; + } + return { onHeaderCrossClick }; +})(observer(SubInfoPanelHeader)); diff --git a/packages/asc-web-common/components/Section/sub-components/info-panel.js b/packages/asc-web-common/components/Section/sub-components/info-panel.js new file mode 100644 index 0000000000..95f28418c4 --- /dev/null +++ b/packages/asc-web-common/components/Section/sub-components/info-panel.js @@ -0,0 +1,140 @@ +import IconButton from "@appserver/components/icon-button"; +import { Base } from "@appserver/components/themes"; +import { isTablet, mobile, tablet } from "@appserver/components/utils/device"; +import { inject } from "mobx-react"; +import PropTypes from "prop-types"; +import React, { useEffect } from "react"; +import styled from "styled-components"; + +const StyledInfoPanelWrapper = styled.div.attrs(({ id }) => ({ + id: id, +}))` + height: auto; + width: auto; + background: rgba(6, 22, 38, 0.2); + backdrop-filter: blur(18px); + + @media ${tablet} { + z-index: 309; + position: absolute; + top: 0; + bottom: 0; + left: 0; + right: 0; + } +`; + +const StyledInfoPanel = styled.div` + height: 100%; + width: 400px; + background-color: ${(props) => props.theme.infoPanel.backgroundColor}; + border-left: ${(props) => `1px solid ${props.theme.infoPanel.borderColor}`}; + display: flex; + flex-direction: column; + + @media ${tablet} { + position: absolute; + border: none; + right: 0; + width: 480px; + max-width: calc(100vw - 69px); + } + + @media ${mobile} { + bottom: 0; + height: 80%; + width: 100vw; + max-width: 100vw; + } +`; + +const StyledCloseButtonWrapper = styled.div` + position: absolute; + display: none; + background-color: ${(props) => props.theme.infoPanel.closeButtonBg}; + padding: ${(props) => props.theme.infoPanel.closeButtonWrapperPadding}; + border-radius: 50%; + + .info-panel-button { + svg { + width: ${(props) => props.theme.infoPanel.closeButtonSize}; + height: ${(props) => props.theme.infoPanel.closeButtonSize}; + } + path { + fill: ${(props) => props.theme.infoPanel.closeButtonIcon}; + } + } + + @media ${tablet} { + display: block; + top: 0; + left: 0; + margin-top: 18px; + margin-left: -34px; + } + @media ${mobile} { + right: 0; + left: auto; + margin-top: -34px; + margin-right: 10px; + } +`; + +const InfoPanel = ({ children, isVisible, setIsVisible }) => { + if (!isVisible) return null; + + const closeInfoPanel = () => setIsVisible(false); + + useEffect(() => { + const onMouseDown = (e) => { + if (e.target.id === "InfoPanelWrapper") closeInfoPanel(); + }; + + if (isTablet()) document.addEventListener("mousedown", onMouseDown); + return () => document.removeEventListener("mousedown", onMouseDown); + }, []); + + return ( + + + + + + {children} + + + ); +}; + +InfoPanel.propTypes = { + children: PropTypes.oneOfType([ + PropTypes.arrayOf(PropTypes.node), + PropTypes.node, + PropTypes.any, + ]), + isVisible: PropTypes.bool, +}; + +StyledInfoPanelWrapper.defaultProps = { theme: Base }; +StyledCloseButtonWrapper.defaultProps = { theme: Base }; +StyledInfoPanel.defaultProps = { theme: Base }; +InfoPanel.defaultProps = { theme: Base }; + +export default inject(({ infoPanelStore }) => { + let isVisible = false; + let setIsVisible = () => {}; + + if (infoPanelStore) { + isVisible = infoPanelStore.isVisible; + setIsVisible = infoPanelStore.setIsVisible; + } + + return { + isVisible, + setIsVisible, + }; +})(InfoPanel); diff --git a/packages/asc-web-common/components/Section/sub-components/section-container.js b/packages/asc-web-common/components/Section/sub-components/section-container.js index 862951a63c..6aaf8577fe 100644 --- a/packages/asc-web-common/components/Section/sub-components/section-container.js +++ b/packages/asc-web-common/components/Section/sub-components/section-container.js @@ -38,8 +38,10 @@ const StyledSectionContainer = styled.section` display: flex; flex-direction: column; - width: 100%; - max-width: 100vw; + width: ${(props) => + props.infoPanelIsVisible ? "calc(100% - 677px)" : "100%"}; + max-width: ${(props) => + props.infoPanelIsVisible ? "calc(100vw - 677px)" : "100vw"}; @media ${tablet} { width: 100%; diff --git a/packages/asc-web-common/components/Section/sub-components/section-header.js b/packages/asc-web-common/components/Section/sub-components/section-header.js index 8ce1c3f1b7..c541768aed 100644 --- a/packages/asc-web-common/components/Section/sub-components/section-header.js +++ b/packages/asc-web-common/components/Section/sub-components/section-header.js @@ -16,8 +16,16 @@ const StyledSectionHeader = styled.div` margin-right: 20px; ${NoUserSelect} - width: calc(100vw - 296px); - max-width: calc(100vw - 296px); + display: grid; + align-items: center; + + /* width: calc(100vw - 296px); + max-width: calc(100vw - 296px); */ + + width: ${(props) => + props.infoPanelIsVisible ? "calc(100vw - 696px)" : "calc(100vw - 296px)"}; + max-width: ${(props) => + props.infoPanelIsVisible ? "calc(100vw - 696px)" : "calc(100vw - 296px)"}; @media ${tablet} { width: ${(props) => diff --git a/packages/asc-web-common/constants/index.js b/packages/asc-web-common/constants/index.js index 2e0049d4ec..a3cda8f3a3 100644 --- a/packages/asc-web-common/constants/index.js +++ b/packages/asc-web-common/constants/index.js @@ -11,7 +11,6 @@ export const EmployeeActivationStatus = Object.freeze({ Pending: 2, AutoGenerated: 4, }); - /** * Enum for employee status. * @readonly @@ -20,7 +19,6 @@ export const EmployeeStatus = Object.freeze({ Active: 1, Disabled: 2, }); - /** * Enum for employee type. * @readonly @@ -29,7 +27,6 @@ export const EmployeeType = Object.freeze({ User: 1, Guest: 2, }); - /** * Enum for filter type. * @readonly @@ -48,7 +45,6 @@ export const FilterType = Object.freeze({ ByExtension: 11, MediaOnly: 12, }); - /** * Enum for file type. * @readonly @@ -63,7 +59,6 @@ export const FileType = Object.freeze({ Presentation: 6, Document: 7, }); - /** * Enum for file action. * @readonly @@ -72,7 +67,6 @@ export const FileAction = Object.freeze({ Create: 0, Rename: 1, }); - /** * Enum for root folders type. * @readonly @@ -90,7 +84,6 @@ export const FolderType = Object.freeze({ Templates: 12, Privacy: 13, }); - export const ShareAccessRights = Object.freeze({ None: 0, FullAccess: 1, @@ -102,7 +95,6 @@ export const ShareAccessRights = Object.freeze({ FormFilling: 7, CustomFilter: 8, }); - export const ConflictResolveType = Object.freeze({ Skip: 0, Overwrite: 1, @@ -127,7 +119,6 @@ export const providersData = Object.freeze({ icon: "/static/images/share.linkedin.react.svg", }, }); - export const LoaderStyle = { title: "", width: "100%", @@ -183,7 +174,6 @@ export const TenantTrustedDomainsType = Object.freeze({ Custom: 1, All: 2, }); - export const PasswordLimitSpecialCharacters = "!@#$%^&*"; /** diff --git a/packages/asc-web-common/custom.scss b/packages/asc-web-common/custom.scss index 0e90d957db..83fc18eafe 100644 --- a/packages/asc-web-common/custom.scss +++ b/packages/asc-web-common/custom.scss @@ -7,11 +7,9 @@ html, body { height: 100%; } - #root { min-height: 100%; position: relative; - .pageLoader { position: fixed; left: calc(50% - 20px); @@ -40,17 +38,14 @@ body { body { margin: 0; } - body.loading * { cursor: wait !important; } - body.drag-cursor * { cursor: url('data:image/svg+xml;utf8,') 6 6, auto !important; } - body.desktop { user-select: none; -moz-user-select: none; @@ -59,7 +54,6 @@ body.desktop { -o-user-select: none; mozuserselect: none; } - #snackbar { display: flex; justify-content: center; diff --git a/packages/asc-web-components/public/static/images/panel.react.svg b/packages/asc-web-components/public/static/images/panel.react.svg new file mode 100644 index 0000000000..8da7c48595 --- /dev/null +++ b/packages/asc-web-components/public/static/images/panel.react.svg @@ -0,0 +1,3 @@ + + + diff --git a/packages/asc-web-components/table-container/GroupMenuItem.js b/packages/asc-web-components/table-container/GroupMenuItem.js index 867b1122d4..f60d05e41b 100644 --- a/packages/asc-web-components/table-container/GroupMenuItem.js +++ b/packages/asc-web-components/table-container/GroupMenuItem.js @@ -45,11 +45,19 @@ const StyledButton = styled(Button)` padding-right: 8px; } + .button-content { + @media ${tablet} { + flex-direction: column; + gap: 0px; + } + } + @media ${tablet} { display: flex; + justify-content: center; flex-direction: column; height: 60px; - padding: 22px 12px 0 12px; + padding: 0px 12px; .btnIcon { padding: 0; margin: 0 auto; @@ -57,7 +65,7 @@ const StyledButton = styled(Button)` } @media ${mobile} { - padding: 18px 16px 0 16px; + padding: 0 16px; height: 50px; font-size: 0; line-height: 0; diff --git a/packages/asc-web-components/table-container/StyledTableContainer.js b/packages/asc-web-components/table-container/StyledTableContainer.js index d9d4a082de..2756c79a75 100644 --- a/packages/asc-web-components/table-container/StyledTableContainer.js +++ b/packages/asc-web-components/table-container/StyledTableContainer.js @@ -7,7 +7,7 @@ import { isMobile } from "react-device-detect"; const StyledTableContainer = styled.div` -moz-user-select: none; - width: calc(100% - 5px); + width: 100%; max-width: 100%; margin-top: -19px; @@ -79,7 +79,7 @@ const StyledTableGroupMenu = styled.div` align-items: center; width: 100%; z-index: 199; - height: 52px; + height: 53px; box-shadow: ${(props) => props.theme.tableContainer.groupMenu.boxShadow}; border-radius: 0px 0px 6px 6px; margin: 0; @@ -125,6 +125,42 @@ const StyledTableGroupMenu = styled.div` StyledTableGroupMenu.defaultProps = { theme: Base }; +const StyledInfoPanelToggleWrapper = styled.div` + display: flex; + align-items: center; + align-self: center; + justify-content: center; + margin: 0 20px 0 auto; + height: 100%; + width: auto; + padding-left: 20px; + + @media ${tablet} { + margin: 0 16px 0 auto; + } + + .info-panel-toggle-bg { + height: 32px; + width: 32px; + display: flex; + align-items: center; + justify-content: center; + border-radius: 50%; + background-color: ${(props) => + props.isInfoPanelVisible + ? props.theme.infoPanel.sectionHeaderToggleBgActive + : props.theme.infoPanel.sectionHeaderToggleBg}; + + path { + fill: ${(props) => + props.isInfoPanelVisible + ? props.theme.infoPanel.sectionHeaderToggleIconActive + : props.theme.infoPanel.sectionHeaderToggleIcon}; + } + } +`; +StyledInfoPanelToggleWrapper.defaultProps = { theme: Base }; + const StyledTableHeader = styled.div` position: fixed; background: ${(props) => props.theme.tableContainer.header.background}; @@ -302,6 +338,9 @@ const StyledScrollbar = styled(Scrollbar)` .scroll-body { display: flex; } + .nav-thumb-vertical { + display: none !important; + } .nav-thumb-horizontal { ${isMobile && "display: none !important"}; } @@ -318,6 +357,7 @@ export { StyledTableCell, StyledTableSettings, StyledTableGroupMenu, + StyledInfoPanelToggleWrapper, StyledEmptyTableContainer, StyledScrollbar, }; diff --git a/packages/asc-web-components/table-container/TableGroupMenu.js b/packages/asc-web-components/table-container/TableGroupMenu.js index dd42f3b419..6052f1387a 100644 --- a/packages/asc-web-components/table-container/TableGroupMenu.js +++ b/packages/asc-web-components/table-container/TableGroupMenu.js @@ -1,10 +1,15 @@ import React from "react"; import PropTypes from "prop-types"; import Checkbox from "../checkbox"; -import { StyledTableGroupMenu, StyledScrollbar } from "./StyledTableContainer"; +import { + StyledTableGroupMenu, + StyledScrollbar, + StyledInfoPanelToggleWrapper, +} from "./StyledTableContainer"; import ComboBox from "../combobox"; import GroupMenuItem from "./GroupMenuItem"; import { useTranslation } from "react-i18next"; +import IconButton from "../icon-button"; const TableGroupMenu = (props) => { const { @@ -14,15 +19,14 @@ const TableGroupMenu = (props) => { onChange, checkboxOptions, checkboxMargin, + isInfoPanelVisible, + toggleInfoPanel, ...rest } = props; - const onCheckboxChange = (e) => { onChange && onChange(e.target && e.target.checked); }; - const { t } = useTranslation("Common"); - return ( <> { ))} + +
+ +
+
); }; - TableGroupMenu.propTypes = { isChecked: PropTypes.bool, isIndeterminate: PropTypes.bool, @@ -68,5 +82,4 @@ TableGroupMenu.propTypes = { onChange: PropTypes.func, checkboxMargin: PropTypes.string, }; - export default TableGroupMenu; diff --git a/packages/asc-web-components/table-container/TableHeader.js b/packages/asc-web-components/table-container/TableHeader.js index edfd13827a..28657e174c 100644 --- a/packages/asc-web-components/table-container/TableHeader.js +++ b/packages/asc-web-components/table-container/TableHeader.js @@ -117,7 +117,7 @@ class TableHeader extends React.Component { const column2Width = this.getSubstring(widths[colIndex]); const defaultColumn = document.getElementById("column_" + colIndex); - if (defaultColumn.dataset.defaultSize) return; + if (!defaultColumn || defaultColumn.dataset.defaultSize) return; if (column2Width + offset >= defaultMinColumnSize) { widths[+columnIndex] = newWidth + "px"; @@ -236,8 +236,8 @@ class TableHeader extends React.Component { const storageSize = !resetColumnsSize && localStorage.getItem(columnStorageName); - const defaultSize = this.props.columns.find((col) => col.defaultSize) - ?.defaultSize; + const defaultSize = + this.props.columns.find((col) => col.defaultSize)?.defaultSize || 0; //TODO: Fixed columns size if something went wrong if (storageSize) { @@ -262,9 +262,12 @@ class TableHeader extends React.Component { const containerWidth = +container.clientWidth; - const oldWidth = tableContainer - .map((column) => this.getSubstring(column)) - .reduce((x, y) => x + y); + const oldWidth = + tableContainer + .map((column) => this.getSubstring(column)) + .reduce((x, y) => x + y) - + defaultSize - + settingsSize; let str = ""; @@ -278,7 +281,7 @@ class TableHeader extends React.Component { const enable = index == tableContainer.length - 1 || (column ? column.dataset.enable === "true" : item !== "0px"); - const defaultSize = column && column.dataset.defaultSize; + const defaultColumnSize = column && column.dataset.defaultSize; const isActiveNow = item === "0px" && enable; if (isActiveNow && column) activeColumnIndex = index; @@ -301,18 +304,14 @@ class TableHeader extends React.Component { } else if (item !== `${settingsSize}px`) { const percent = (this.getSubstring(item) / oldWidth) * 100; - if (index == 1) { - const newItemWidth = (containerWidth * percent) / 100 + "px"; - gridTemplateColumns.push(newItemWidth); - } else { - const newItemWidth = defaultSize - ? `${defaultSize}px` - : percent === 0 - ? `${minColumnSize}px` - : (containerWidth * percent) / 100 + "px"; + const newItemWidth = defaultColumnSize + ? `${defaultColumnSize}px` + : percent === 0 + ? `${minColumnSize}px` + : ((containerWidth - defaultSize - settingsSize) * percent) / 100 + + "px"; - gridTemplateColumns.push(newItemWidth); - } + gridTemplateColumns.push(newItemWidth); } else { gridTemplateColumns.push(item); } @@ -351,7 +350,8 @@ class TableHeader extends React.Component { const enableColumns = this.props.columns .filter((x) => x.enable) - .filter((x) => !x.defaultSize); + .filter((x) => !x.defaultSize) + .filter((x) => !x.default); const container = containerRef.current ? containerRef.current diff --git a/packages/asc-web-components/themes/base.js b/packages/asc-web-components/themes/base.js index 26ef48b91d..ca007dbc09 100644 --- a/packages/asc-web-components/themes/base.js +++ b/packages/asc-web-components/themes/base.js @@ -1952,6 +1952,29 @@ const Base = { }, }, + infoPanel: { + sectionHeaderToggleIcon: gray, + sectionHeaderToggleIconActive: "#3B72A7", + sectionHeaderToggleBg: "transparent", + sectionHeaderToggleBgActive: grayLight, + + backgroundColor: white, + borderColor: grayLightMid, + thumbnailBorderColor: grayLightMid, + textColor: black, + + closeButtonWrapperPadding: "0px", + closeButtonIcon: white, + closeButtonSize: "17px", + closeButtonBg: "transparent", + + accessGroupBg: grayLightMid, + accessGroupText: black, + + showAccessUsersTextColor: gray, + showAccessPanelTextColor: "#3b72a7", + }, + filesArticleBody: { background: lightGrayishStrongBlue, panelBackground: lightGrayishStrongBlue, diff --git a/packages/asc-web-components/themes/dark.js b/packages/asc-web-components/themes/dark.js index 23f1168df1..b9bf32194d 100644 --- a/packages/asc-web-components/themes/dark.js +++ b/packages/asc-web-components/themes/dark.js @@ -1954,6 +1954,29 @@ const Dark = { }, }, + infoPanel: { + sectionHeaderToggleIcon: "#858585", + sectionHeaderToggleIconActive: "#c4c4c4", + sectionHeaderToggleBg: "transparent", + sectionHeaderToggleBgActive: "#292929", + + backgroundColor: black, + borderColor: "#292929", + thumbnailBorderColor: grayLightMid, + textColor: white, + + closeButtonWrapperPadding: "6px", + closeButtonIcon: black, + closeButtonSize: "12px", + closeButtonBg: "#a2a2a2", + + accessGroupBg: "#242424", + accessGroupText: white, + + showAccessUsersTextColor: gray, + showAccessPanelTextColor: "#E06A1B", + }, + filesArticleBody: { background: black, panelBackground: "#474747", diff --git a/products/ASC.Files/Client/public/images/empty_screen.png b/products/ASC.Files/Client/public/images/empty_screen.png new file mode 100644 index 0000000000..c3d7e07cf3 Binary files /dev/null and b/products/ASC.Files/Client/public/images/empty_screen.png differ diff --git a/products/ASC.Files/Client/public/images/info.react.svg b/products/ASC.Files/Client/public/images/info.react.svg new file mode 100644 index 0000000000..9a0368096e --- /dev/null +++ b/products/ASC.Files/Client/public/images/info.react.svg @@ -0,0 +1,3 @@ + + + diff --git a/products/ASC.Files/Client/public/images/panel.svg b/products/ASC.Files/Client/public/images/panel.svg new file mode 100644 index 0000000000..8da7c48595 --- /dev/null +++ b/products/ASC.Files/Client/public/images/panel.svg @@ -0,0 +1,3 @@ + + + diff --git a/products/ASC.Files/Client/public/locales/en/InfoPanel.json b/products/ASC.Files/Client/public/locales/en/InfoPanel.json new file mode 100644 index 0000000000..42218ea27a --- /dev/null +++ b/products/ASC.Files/Client/public/locales/en/InfoPanel.json @@ -0,0 +1,15 @@ +{ + "Info": "Info", + "ViewDetails": "View Details", + "ItemsSelected": "Items selected", + "SystemProperties": "System properties", + "WhoHasAccess": "Who has access", + "Members": "members", + "OpenSharingSettings": "Open sharing settings", + + "Location": "Location", + "FileExtension": "File extension", + "LastModifiedBy": "Last modified by", + "Versions": "Versions", + "Comments": "Comments" +} diff --git a/products/ASC.Files/Client/public/locales/ru/InfoPanel.json b/products/ASC.Files/Client/public/locales/ru/InfoPanel.json new file mode 100644 index 0000000000..426fb5c3d5 --- /dev/null +++ b/products/ASC.Files/Client/public/locales/ru/InfoPanel.json @@ -0,0 +1,15 @@ +{ + "Info": "Информация", + "ViewDetails": "Просмотреть подробную информацию", + "ItemsSelected": "Выбрано элементов", + "SystemProperties": "Системные свойства", + "WhoHasAccess": "У кого есть доступ", + "Members": "участников", + "OpenSharingSettings": "Открыть настройки общего доступа", + + "Location": "Местоположение", + "FileExtension": "Расширение файла", + "LastModifiedBy": "Автор последнего корректива", + "Versions": "Версии", + "Comments": "Комментарии" +} diff --git a/products/ASC.Files/Client/src/HOCs/withFileActions.js b/products/ASC.Files/Client/src/HOCs/withFileActions.js index f2f31c94af..257365b2d4 100644 --- a/products/ASC.Files/Client/src/HOCs/withFileActions.js +++ b/products/ASC.Files/Client/src/HOCs/withFileActions.js @@ -82,6 +82,7 @@ export default function withFileActions(WrappedFileItem) { if (mouseButton || e.currentTarget.tagName !== "DIV" || label) { return e; } + e.preventDefault(); setTooltipPosition(e.pageX, e.pageY); setStartDrag(true); @@ -92,13 +93,14 @@ export default function withFileActions(WrappedFileItem) { const { viewAs } = this.props; if ( - e.target.closest(".checkbox") || e.target.tagName === "INPUT" || e.target.tagName === "SPAN" || e.target.tagName === "A" || - e.target.closest(".expandButton") || - e.target.closest(".badges") || + e.target.closest(".checkbox") || e.button !== 0 || + e.target.closest(".expandButton") || + e.target.querySelector(".expandButton") || + e.target.closest(".badges") || e.target.closest(".not-selectable") ) return; @@ -106,12 +108,10 @@ export default function withFileActions(WrappedFileItem) { if (viewAs === "tile") { if (e.target.closest(".edit-button") || e.target.tagName === "IMG") return; - if (e.detail === 1) this.fileContextClick(); - } else { - this.fileContextClick(); - } + } else this.fileContextClick(); }; + onFilesClick = (e) => { const { item, openFileAction } = this.props; if ( @@ -247,6 +247,7 @@ export default function withFileActions(WrappedFileItem) { activeFiles, activeFolders, } = filesStore; + const { startUpload } = uploadDataStore; const { type, extension, id } = fileActionStore; diff --git a/products/ASC.Files/Client/src/pages/Home/InfoPanel/Body/SeveralItems.js b/products/ASC.Files/Client/src/pages/Home/InfoPanel/Body/SeveralItems.js new file mode 100644 index 0000000000..5b79344972 --- /dev/null +++ b/products/ASC.Files/Client/src/pages/Home/InfoPanel/Body/SeveralItems.js @@ -0,0 +1,32 @@ +import Text from "@appserver/components/text"; +import React from "react"; +import { withTranslation } from "react-i18next"; +import { ReactSVG } from "react-svg"; + +import { StyledTitle } from "./styles/styles.js"; + +const SeveralItems = (props) => { + const { t, selectedItems, getIcon, getFolderInfo } = props; + const itemsIcon = getIcon(24, ".file"); + + return ( + <> + + + + {`${t("ItemsSelected")}: ${selectedItems.length}`} + + + +
+ +
+ + ); +}; + +export default withTranslation(["InfoPanel"])(SeveralItems); diff --git a/products/ASC.Files/Client/src/pages/Home/InfoPanel/Body/SingleItem.js b/products/ASC.Files/Client/src/pages/Home/InfoPanel/Body/SingleItem.js new file mode 100644 index 0000000000..fcb31a8e2f --- /dev/null +++ b/products/ASC.Files/Client/src/pages/Home/InfoPanel/Body/SingleItem.js @@ -0,0 +1,415 @@ +import { FileType } from "@appserver/common/constants"; +import { LANGUAGE } from "@appserver/common/constants"; +import Link from "@appserver/components/link"; +import Text from "@appserver/components/text"; +import Tooltip from "@appserver/components/tooltip"; +import React, { useEffect, useState } from "react"; +import { ReactSVG } from "react-svg"; +import { + StyledAccess, + StyledAccessItem, + StyledOpenSharingPanel, + StyledProperties, + StyledSubtitle, + StyledThumbnail, + StyledTitle, +} from "./styles/styles.js"; + +const moment = require("moment"); + +const SingleItem = (props) => { + const { + t, + selectedItem, + onSelectItem, + setSharingPanelVisible, + getFolderInfo, + getIcon, + getFolderIcon, + getShareUsers, + dontShowSize, + dontShowLocation, + dontShowAccess, + } = props; + + let updateSubscription = true; + const [item, setItem] = useState({ + id: "", + isFolder: false, + title: "", + iconUrl: "", + thumbnailUrl: "", + properties: [], + access: { + owner: { + img: "", + link: "", + }, + others: [], + }, + }); + + const updateItemsInfo = async (selectedItem) => { + const getItemIcon = (item, size) => { + return item.isFolder + ? getFolderIcon(item.providerKey, size) + : getIcon(size, item.fileExst || ".file"); + }; + + const getSingleItemProperties = (item) => { + const styledLink = (text, href) => ( + + {text} + + ); + + const styledText = (text) => ( + {text} + ); + + const parseAndFormatDate = (date) => { + return moment(date) + .locale(localStorage.getItem(LANGUAGE)) + .format("DD.MM.YY hh:mm A"); + }; + + const getItemType = (fileType) => { + switch (fileType) { + case FileType.Unknown: + return t("Common:Unknown"); + case FileType.Archive: + return t("Common:Archive"); + case FileType.Video: + return t("Common:Video"); + case FileType.Audio: + return t("Common:Audio"); + case FileType.Image: + return t("Common:Image"); + case FileType.Spreadsheet: + return t("Home:Spreadsheet"); + case FileType.Presentation: + return t("Home:Presentation"); + case FileType.Document: + return t("Home:Document"); + default: + return t("Home:Folder"); + } + }; + + const itemSize = item.isFolder + ? `${t("Translations:Folders")}: ${item.foldersCount} | ${t( + "Translations:Files" + )}: ${item.filesCount}` + : item.contentLength; + + const itemType = getItemType(item.fileType); + + let result = [ + { + id: "Owner", + title: t("Common:Owner"), + content: styledLink( + item.createdBy?.displayName, + item.createdBy?.profileUrl + ), + }, + // { + // id: "Location", + // title: t("InfoPanel:Location"), + // content: styledText("..."), + // }, + { + id: "Type", + title: t("Common:Type"), + content: styledText(itemType), + }, + { + id: "Size", + title: t("Common:Size"), + content: styledText(itemSize), + }, + { + id: "ByLastModifiedDate", + title: t("Home:ByLastModifiedDate"), + content: styledText(parseAndFormatDate(item.updated)), + }, + { + id: "LastModifiedBy", + title: t("LastModifiedBy"), + content: styledLink( + item.updatedBy?.displayName, + item.updatedBy?.profileUrl + ), + }, + { + id: "ByCreationDate", + title: t("Home:ByCreationDate"), + content: styledText(parseAndFormatDate(item.created)), + }, + ]; + + if (item.isFolder) return result; + + result.splice(3, 0, { + id: "FileExtension", + title: t("FileExtension"), + content: styledText( + item.fileExst ? item.fileExst.split(".")[1].toUpperCase() : "-" + ), + }); + + result.push( + { + id: "Versions", + title: t("Versions"), + content: styledText(item.version), + }, + { + id: "Comments", + title: t("Comments"), + content: styledText(item.comment), + } + ); + + return result; + }; + + const displayedItem = { + id: selectedItem.id, + isFolder: selectedItem.isFolder, + title: selectedItem.title, + iconUrl: getItemIcon(selectedItem, 32), + thumbnailUrl: selectedItem.thumbnailUrl || getItemIcon(selectedItem, 96), + properties: getSingleItemProperties(selectedItem), + access: { + owner: { + img: selectedItem.createdBy?.avatarSmall, + link: selectedItem.createdBy?.profileUrl, + }, + others: [], + }, + }; + + setItem(displayedItem); + await loadAsyncData(displayedItem, selectedItem); + }; + + const loadAsyncData = async (displayedItem, selectedItem) => { + if (!updateSubscription) return; + + const updateLoadedItemProperties = async (displayedItem, selectedItem) => { + const parentFolderId = selectedItem.isFolder + ? selectedItem.parentId + : selectedItem.folderId; + + const noLocationProperties = [...displayedItem.properties].filter( + (dip) => dip.id !== "Location" + ); + + let result; + await getFolderInfo(parentFolderId) + .catch(() => { + result = noLocationProperties; + }) + .then((data) => { + if (!data) { + result = noLocationProperties; + return; + } + result = [...displayedItem.properties].map((dip) => + dip.id === "Location" + ? { + id: "Location", + title: t("Location"), + content: ( + + {data.title} + + ), + } + : dip + ); + }); + + return result; + }; + + const updateLoadedItemAccess = async (selectedItem) => { + const accesses = await getShareUsers( + [selectedItem.isFolder ? selectedItem.parentId : selectedItem.folderId], + [selectedItem.id] + ); + + const result = { + owner: {}, + others: [], + }; + + accesses.forEach((access) => { + let key = access.sharedTo.id, + img = access.sharedTo.avatarSmall, + link = access.sharedTo.profileUrl, + name = access.sharedTo.displayName || access.sharedTo.name, + { manager } = access.sharedTo; + + if (access.isOwner) result.owner = { key, img, link, name }; + else { + if (access.sharedTo.email) + result.others.push({ key, type: "user", img, link, name }); + else if (access.sharedTo.manager) + result.others.push({ key, type: "group", name, manager }); + } + }); + + result.others = result.others.sort((a) => (a.type === "group" ? -1 : 1)); + return result; + }; + + // const properties = await updateLoadedItemProperties( + // displayedItem, + // selectedItem + // ); + + if (dontShowAccess) { + setItem({ + ...displayedItem, + //properties: properties, + }); + return; + } + + const access = await updateLoadedItemAccess(selectedItem); + setItem({ + ...displayedItem, + // properties: properties, + access: access, + }); + }; + + const openSharingPanel = () => { + const { id, isFolder } = item; + onSelectItem({ id, isFolder }); + setSharingPanelVisible(true); + }; + + useEffect(() => { + if (selectedItem.id !== item.id && updateSubscription) + updateItemsInfo(selectedItem); + return () => (updateSubscription = false); + }, [selectedItem]); + + return ( + <> + + + {item.title} + + + {selectedItem.thumbnailUrl ? ( + + + + ) : ( +
+ +
+ )} + + + + {t("SystemProperties")} + + + + + {item.properties.map((p) => { + if (dontShowSize && p.id === "Size") return; + if (dontShowLocation && p.id === "Location") return; + return ( +
+ {p.title} + {p.content} +
+ ); + })} +
+ + {!dontShowAccess && item.access && ( + <> + + + {t("WhoHasAccess")} + + + + + + dataTip ? {dataTip} : null + } + /> + + +
+
+ + + +
+
+
+ + {item.access.others.length > 0 &&
} + + {item.access.others.map((item, i) => { + if (i < 3) + return ( +
+ +
+ {item.type === "user" ? ( +
+ + + +
+ ) : ( +
+ {item.name.substr(0, 2).toUpperCase()} +
+ )} +
+
+
+ ); + })} + + {item.access.others.length > 3 && ( +
+ {`+ ${item.access.others.length - 3} ${t("Members")}`} +
+ )} +
+ + {t("OpenSharingSettings")} + + + )} + + ); +}; + +export default SingleItem; diff --git a/products/ASC.Files/Client/src/pages/Home/InfoPanel/Body/index.js b/products/ASC.Files/Client/src/pages/Home/InfoPanel/Body/index.js new file mode 100644 index 0000000000..10552e759b --- /dev/null +++ b/products/ASC.Files/Client/src/pages/Home/InfoPanel/Body/index.js @@ -0,0 +1,109 @@ +import { inject, observer } from "mobx-react"; +import React, { useEffect, useState } from "react"; +import { withTranslation } from "react-i18next"; +import { withRouter } from "react-router"; +import SeveralItems from "./SeveralItems"; +import SingleItem from "./SingleItem"; +import { StyledInfoRoomBody } from "./styles/styles.js"; +import { Base } from "@appserver/components/themes"; + +const InfoPanelBodyContent = ({ + t, + selectedFolder, + selectedItems, + getFolderInfo, + getIcon, + getFolderIcon, + getShareUsers, + onSelectItem, + setSharingPanelVisible, + isRecycleBinFolder, + isRecentFolder, + isFavoritesFolder, +}) => { + const singleItem = (item) => { + const dontShowLocation = item.isFolder && item.parentId === 0; + const dontShowSize = item.isFolder && (isFavoritesFolder || isRecentFolder); + const dontShowAccess = + isRecycleBinFolder || + (item.isFolder && item.parentId === 0) || + item.rootFolderId === 7 || + (item.isFolder && item.pathParts && item.pathParts[0] === 7); + + return ( + + ); + }; + + return ( + + <> + {selectedItems.length === 0 ? ( + singleItem({ + ...selectedFolder, + isFolder: true, + }) + ) : selectedItems.length === 1 ? ( + singleItem(selectedItems[0]) + ) : ( + + )} + + + ); +}; + +InfoPanelBodyContent.defaultProps = { theme: Base }; + +export default inject( + ({ + filesStore, + settingsStore, + filesActionsStore, + dialogsStore, + treeFoldersStore, + selectedFolderStore, + }) => { + const { selection, getFolderInfo, getShareUsers } = filesStore; + const { getIcon, getFolderIcon } = settingsStore; + const { onSelectItem } = filesActionsStore; + const { setSharingPanelVisible } = dialogsStore; + const { + isRecycleBinFolder, + isRecentFolder, + isFavoritesFolder, + } = treeFoldersStore; + + return { + selectedFolder: { ...selectedFolderStore }, + selectedItems: [...selection], + getFolderInfo, + getShareUsers, + getIcon, + getFolderIcon, + onSelectItem, + setSharingPanelVisible, + isRecycleBinFolder, + isRecentFolder, + isFavoritesFolder, + }; + } +)( + withRouter( + withTranslation(["InfoPanel", "Home", "Common", "Translations"])( + observer(InfoPanelBodyContent) + ) + ) +); diff --git a/products/ASC.Files/Client/src/pages/Home/InfoPanel/Body/styles/styles.js b/products/ASC.Files/Client/src/pages/Home/InfoPanel/Body/styles/styles.js new file mode 100644 index 0000000000..eef83fb25b --- /dev/null +++ b/products/ASC.Files/Client/src/pages/Home/InfoPanel/Body/styles/styles.js @@ -0,0 +1,234 @@ +import styled from "styled-components"; + +import { Base } from "@appserver/components/themes"; + +const StyledInfoRoomBody = styled.div` + padding: 0px 0px 0 16px; + height: auto; + background-color: ${(props) => props.theme.infoPanel.backgroundColor}; + color: ${(props) => props.theme.infoPanel.textColor}; + + .no-item { + text-align: center; + } + + .no-thumbnail-img-wrapper { + height: auto; + width: 100%; + display: flex; + justify-content: center; + .no-thumbnail-img { + height: 96px; + width: 96px; + } + } + + .current-folder-loader-wrapper { + width: 100%; + display: flex; + justify-content: center; + height: 96px; + margin-top: 116.56px; + } +`; + +const StyledTitle = styled.div` + display: flex; + flex-wrap: no-wrap; + flex-direction: row; + align-items: center; + width: 100%; + height: 44px; + padding: 23px 0; + + .icon { + display: flex; + align-items: center; + svg { + height: 32px; + width: 32px; + } + } + + .text { + font-weight: 600; + font-size: 16px; + line-height: 22px; + max-height: 44px; + margin: 0 8px; + overflow: hidden; + text-overflow: ellipsis; + display: -webkit-box; + -webkit-box-orient: vertical; + -webkit-line-clamp: 2; + } +`; + +const StyledThumbnail = styled.div` + display: flex; + justify-content: center; + align-items: center; + width: 100%; + height: auto; + img { + border: ${(props) => `solid 1px ${props.theme.infoPanel.borderColor}`}; + border-radius: 6px; + //width: 100%; + width: auto; + max-width: 100%; + height: auto; + } +`; + +const StyledSubtitle = styled.div` + display: flex; + flex-direction: row; + align-items: center; + width: 100%; + padding: 24px 0; +`; + +const StyledProperties = styled.div` + display: flex; + flex-direction: column; + width: 100%; + gap: 8px; + + .property { + width: 100%; + display: grid; + grid-template-columns: 150px 1fr; + grid-column-gap: 24px; + + .property-title { + font-size: 13px; + } + + .property-content { + display: flex; + align-items: center; + + font-weight: 600; + font-size: 13px; + } + } +`; + +const StyledAccess = styled.div` + display: flex; + flex-wrap: wrap; + flex-direction: row; + gap: 8px; + align-items: center; + .divider { + background: ${(props) => props.theme.infoPanel.borderColor}; + margin: 2px 4px; + width: 1px; + height: 28px; + } + + .show-more-users { + position: static; + width: 101px; + height: 16px; + left: 120px; + top: 8px; + padding-left: 1px; + + font-family: "Open Sans"; + font-style: normal; + font-weight: normal; + font-size: 12px; + line-height: 16px; + text-align: left; + + color: ${(props) => props.theme.infoPanel.showAccessUsersTextColor}; + + flex: none; + order: 3; + flex-grow: 0; + + cursor: pointer; + &:hover { + text-decoration: underline; + } + } +`; + +const StyledAccessItem = styled.div` + width: 32px; + height: 32px; + border-radius: 50%; + + .access-item-tooltip { + cursor: pointer; + width: 100%; + height: 100%; + + .item-group { + border-radius: 50%; + background-color: ${(props) => props.theme.infoPanel.accessGroupBg}; + width: 100%; + height: 100%; + display: flex; + align-items: center; + justify-content: center; + + span { + font-family: "Open Sans"; + font-weight: 700; + font-size: 12px; + color: ${(props) => props.theme.infoPanel.accessGroupText}; + line-height: 16px; + } + } + + .item-user { + img { + border-radius: 50%; + width: 100%; + height: 100%; + } + } + } +`; + +const StyledOpenSharingPanel = styled.div` + position: static; + width: auto; + height: 15px; + left: 0px; + top: 2px; + + font-family: "Open Sans"; + font-style: normal; + font-weight: 600; + font-size: 13px; + line-height: 15px; + + color: ${(props) => props.theme.infoPanel.showAccessPanelTextColor}; + + display: flex; + margin: 16px 0px; + + cursor: pointer; + text-decoration: underline; + text-decoration-style: dashed; +`; + +StyledInfoRoomBody.defaultProps = { theme: Base }; +StyledThumbnail.defaultProps = { theme: Base }; +StyledAccess.defaultProps = { theme: Base }; +StyledAccessItem.defaultProps = { theme: Base }; +StyledOpenSharingPanel.defaultProps = { theme: Base }; + +export { + StyledInfoRoomBody, + StyledTitle, + StyledThumbnail, + StyledSubtitle, + StyledProperties, + StyledAccess, + StyledAccessItem, + StyledOpenSharingPanel, +}; diff --git a/products/ASC.Files/Client/src/pages/Home/InfoPanel/Header/index.js b/products/ASC.Files/Client/src/pages/Home/InfoPanel/Header/index.js new file mode 100644 index 0000000000..09ab3e8913 --- /dev/null +++ b/products/ASC.Files/Client/src/pages/Home/InfoPanel/Header/index.js @@ -0,0 +1,8 @@ +import React from "react"; +import { withTranslation } from "react-i18next"; + +const InfoPanelHeaderContent = ({ t }) => { + return <>{t("Info")}; +}; + +export default withTranslation(["InfoPanel"])(InfoPanelHeaderContent); diff --git a/products/ASC.Files/Client/src/pages/Home/InfoPanel/index.js b/products/ASC.Files/Client/src/pages/Home/InfoPanel/index.js new file mode 100644 index 0000000000..ea39524769 --- /dev/null +++ b/products/ASC.Files/Client/src/pages/Home/InfoPanel/index.js @@ -0,0 +1,2 @@ +export { default as InfoPanelHeaderContent } from "./Header"; +export { default as InfoPanelBodyContent } from "./Body"; diff --git a/products/ASC.Files/Client/src/pages/Home/Section/Body/RowsView/SimpleFilesRow.js b/products/ASC.Files/Client/src/pages/Home/Section/Body/RowsView/SimpleFilesRow.js index 9261a61e68..c6e78706e1 100644 --- a/products/ASC.Files/Client/src/pages/Home/Section/Body/RowsView/SimpleFilesRow.js +++ b/products/ASC.Files/Client/src/pages/Home/Section/Body/RowsView/SimpleFilesRow.js @@ -97,8 +97,8 @@ const StyledSimpleFilesRow = styled(Row)` } .row_content { - ${(props) => props.sectionWidth > 500 && `max-width: fit-content;`} - min-width: auto; + ${(props) => + props.sectionWidth > 500 && `max-width: fit-content;`}//min-width: auto;; } .badges { diff --git a/products/ASC.Files/Client/src/pages/Home/Section/Body/TilesView/sub-components/Tile.js b/products/ASC.Files/Client/src/pages/Home/Section/Body/TilesView/sub-components/Tile.js index ec2f040f74..c0d8b0da60 100644 --- a/products/ASC.Files/Client/src/pages/Home/Section/Body/TilesView/sub-components/Tile.js +++ b/products/ASC.Files/Client/src/pages/Home/Section/Body/TilesView/sub-components/Tile.js @@ -146,15 +146,17 @@ const StyledFileTileTop = styled.div` align-items: baseline; height: 156px; position: relative; + border-radius: 6px 6px 0 0; .thumbnail-image { pointer-events: none; position: absolute; height: 100%; width: 100%; - object-fit: ${(props) => (props.isMedia ? "cover" : "none")}; + object-fit: cover; object-position: top; z-index: 0; + border-radius: 6px 6px 0 0; } .temporary-icon > .injected-svg { diff --git a/products/ASC.Files/Client/src/pages/Home/Section/Body/index.js b/products/ASC.Files/Client/src/pages/Home/Section/Body/index.js index bfe0637677..1c0653ebe9 100644 --- a/products/ASC.Files/Client/src/pages/Home/Section/Body/index.js +++ b/products/ASC.Files/Client/src/pages/Home/Section/Body/index.js @@ -71,6 +71,7 @@ const SectionBodyContent = (props) => { (e.target.closest(".scroll-body") && !e.target.closest(".files-item") && !e.target.closest(".not-selectable") && + !e.target.closest(".info-panel") && !e.target.closest(".table-container_group-menu")) || e.target.closest(".files-main-button") || e.target.closest(".add-button") || diff --git a/products/ASC.Files/Client/src/pages/Home/Section/Header/index.js b/products/ASC.Files/Client/src/pages/Home/Section/Header/index.js index 499c907d03..5a945ef7bb 100644 --- a/products/ASC.Files/Client/src/pages/Home/Section/Header/index.js +++ b/products/ASC.Files/Client/src/pages/Home/Section/Header/index.js @@ -18,7 +18,7 @@ import TableGroupMenu from "@appserver/components/table-container/TableGroupMenu import Navigation from "@appserver/common/components/Navigation"; const StyledContainer = styled.div` - padding: 0 0 15px; + /* padding: 0 0 15px; @media ${tablet} { padding: 0 0 17px; @@ -36,7 +36,7 @@ const StyledContainer = styled.div` ${isMobileOnly && css` padding: 0 0 13px; - `} + `} */ .table-container_group-menu { ${(props) => @@ -317,6 +317,7 @@ class SectionHeaderContent extends React.Component { const { t, tReady, + isInfoPanelVisible, isRootFolder, title, canCreate, @@ -344,6 +345,8 @@ class SectionHeaderContent extends React.Component { width={context.sectionWidth} isRootFolder={isRootFolder} canCreate={canCreate} + isRecycleBinFolder={isRecycleBinFolder} + title={title} isTitle={title} isDesktop={isDesktop} isTabletView={isTabletView} @@ -357,6 +360,8 @@ class SectionHeaderContent extends React.Component { isChecked={isHeaderChecked} isIndeterminate={isHeaderIndeterminate} headerMenu={headerMenu} + isInfoPanelVisible={this.props.isInfoPanelVisible} + toggleInfoPanel={this.props.toggleInfoPanel} /> ) : (
@@ -383,6 +388,8 @@ class SectionHeaderContent extends React.Component { isEmptyFilesList={isEmptyFilesList} clearTrash={this.onEmptyTrashAction} onBackToParentFolder={this.onBackToParentFolder} + toggleInfoPanel={this.props.toggleInfoPanel} + isInfoPanelVisible={this.props.isInfoPanelVisible} /> )}
@@ -403,6 +410,7 @@ export default inject( treeFoldersStore, filesActionsStore, settingsStore, + infoPanelStore, }) => { const { setSelected, @@ -441,9 +449,10 @@ export default inject( backToParentFolder, } = filesActionsStore; + const { toggleIsVisible, isVisible } = infoPanelStore; + return { showText: auth.settingsStore.showText, - isDesktop: auth.settingsStore.isDesktopClient, isRootFolder: selectedFolderStore.parentId === 0, title: selectedFolderStore.title, @@ -451,6 +460,8 @@ export default inject( pathParts: selectedFolderStore.pathParts, navigationPath: selectedFolderStore.navigationPath, canCreate, + toggleInfoPanel: toggleIsVisible, + isInfoPanelVisible: isVisible, isHeaderVisible, isHeaderIndeterminate, isHeaderChecked, diff --git a/products/ASC.Files/Client/src/pages/Home/index.js b/products/ASC.Files/Client/src/pages/Home/index.js index 3a515dbd6d..ba88d51227 100644 --- a/products/ASC.Files/Client/src/pages/Home/index.js +++ b/products/ASC.Files/Client/src/pages/Home/index.js @@ -22,6 +22,7 @@ import { SectionPagingContent, Bar, } from "./Section"; +import { InfoPanelBodyContent, InfoPanelHeaderContent } from "./InfoPanel"; import { ArticleMainButtonContent } from "../../components/Article"; @@ -287,6 +288,7 @@ class PureHome extends React.Component { setMaintenanceExist, snackbarExist, } = this.props; + return ( <> @@ -345,6 +347,14 @@ class PureHome extends React.Component { + + + + + + + + diff --git a/products/ASC.Files/Client/src/store/ContextOptionsStore.js b/products/ASC.Files/Client/src/store/ContextOptionsStore.js index e6bd0ba090..92f9aff574 100644 --- a/products/ASC.Files/Client/src/store/ContextOptionsStore.js +++ b/products/ASC.Files/Client/src/store/ContextOptionsStore.js @@ -22,6 +22,7 @@ class ContextOptionsStore { uploadDataStore; versionHistoryStore; settingsStore; + infoPanelStore; constructor( authStore, @@ -32,7 +33,8 @@ class ContextOptionsStore { treeFoldersStore, uploadDataStore, versionHistoryStore, - settingsStore + settingsStore, + infoPanelStore ) { makeAutoObservable(this); this.authStore = authStore; @@ -44,6 +46,7 @@ class ContextOptionsStore { this.uploadDataStore = uploadDataStore; this.versionHistoryStore = versionHistoryStore; this.settingsStore = settingsStore; + this.infoPanelStore = infoPanelStore; } onOpenFolder = (item) => { @@ -335,6 +338,11 @@ class ContextOptionsStore { return options; }; + onShowInfoPanel = () => { + const { setIsVisible } = this.infoPanelStore; + setIsVisible(true); + }; + getFilesContextOptions = (item, t) => { const { contextOptions } = item; const isRootThirdPartyFolder = @@ -510,6 +518,13 @@ class ContextOptionsStore { disabled: true, }, ...versionActions, + { + key: "show-info", + label: t("InfoPanel:ViewDetails"), + icon: "/static/images/info.outline.react.svg", + onClick: this.onShowInfoPanel, + disabled: false, + }, { key: "block-unblock-version", label: t("UnblockVersion"), diff --git a/products/ASC.Files/Client/src/store/FilesActionsStore.js b/products/ASC.Files/Client/src/store/FilesActionsStore.js index 39f76a3d1b..2550e1062a 100644 --- a/products/ASC.Files/Client/src/store/FilesActionsStore.js +++ b/products/ASC.Files/Client/src/store/FilesActionsStore.js @@ -1,26 +1,26 @@ -import { makeAutoObservable } from "mobx"; - import { - removeFiles, + checkFileConflicts, deleteFile, deleteFolder, - finalizeVersion, - lockFile, downloadFiles, - markAsRead, - checkFileConflicts, - removeShareFiles, - getSubfolders, emptyTrash, + finalizeVersion, + getSubfolders, + lockFile, + markAsRead, + removeFiles, + removeShareFiles, } from "@appserver/common/api/files"; import { ConflictResolveType, FileAction, FileStatus, } from "@appserver/common/constants"; +import { makeAutoObservable } from "mobx"; +import toastr from "studio/toastr"; + import { TIMEOUT } from "../helpers/constants"; import { loopTreeFolders, checkProtocol } from "../helpers/files-helpers"; -import toastr from "studio/toastr"; import { combineUrl } from "@appserver/common/utils"; import { AppServerConfig } from "@appserver/common/constants"; import config from "../../package.json"; @@ -34,6 +34,7 @@ class FilesActionStore { settingsStore; dialogsStore; mediaViewerDataStore; + infoPanelStore; constructor( authStore, @@ -43,7 +44,8 @@ class FilesActionStore { selectedFolderStore, settingsStore, dialogsStore, - mediaViewerDataStore + mediaViewerDataStore, + infoPanelStore ) { makeAutoObservable(this); this.authStore = authStore; @@ -53,6 +55,7 @@ class FilesActionStore { this.selectedFolderStore = selectedFolderStore; this.settingsStore = settingsStore; this.dialogsStore = dialogsStore; + this.infoPanelStore = infoPanelStore; this.mediaViewerDataStore = mediaViewerDataStore; } @@ -786,6 +789,7 @@ class FilesActionStore { switch (option) { case "share": return isAccessedSelected && !personal; //isFavoritesFolder ||isRecentFolder + case "showInfo": case "copy": case "download": return hasSelection; @@ -831,6 +835,8 @@ class FilesActionStore { setDeleteDialogVisible, } = this.dialogsStore; + const { toggleIsVisible } = this.infoPanelStore; + switch (option) { case "share": if (!this.isAvailableOption("share")) return null; @@ -915,6 +921,7 @@ class FilesActionStore { const moveTo = this.getOption("moveTo", t); const copy = this.getOption("copy", t); const deleteOption = this.getOption("delete", t); + const showInfo = this.getOption("showInfo", t); itemsCollection .set("share", share) @@ -922,7 +929,8 @@ class FilesActionStore { .set("downloadAs", downloadAs) .set("moveTo", moveTo) .set("copy", copy) - .set("delete", deleteOption); + .set("delete", deleteOption) + .set("showInfo", showInfo); return this.convertToArray(itemsCollection); }; @@ -932,12 +940,15 @@ class FilesActionStore { const download = this.getOption("download", t); const downloadAs = this.getOption("downloadAs", t); const copy = this.getOption("copy", t); + const showInfo = this.getOption("showInfo", t); itemsCollection .set("share", share) .set("download", download) .set("downloadAs", downloadAs) - .set("copy", copy); + .set("copy", copy) + .set("showInfo", showInfo); + return this.convertToArray(itemsCollection); }; @@ -948,6 +959,7 @@ class FilesActionStore { const download = this.getOption("download", t); const downloadAs = this.getOption("downloadAs", t); const copy = this.getOption("copy", t); + const showInfo = this.getOption("showInfo", t); itemsCollection .set("share", share) @@ -960,7 +972,9 @@ class FilesActionStore { setUnsubscribe(true); setDeleteDialogVisible(true); }, - }); + }) + .set("showInfo", showInfo); + return this.convertToArray(itemsCollection); }; @@ -968,12 +982,15 @@ class FilesActionStore { const moveTo = this.getOption("moveTo", t); const deleteOption = this.getOption("delete", t); const download = this.getOption("download", t); + const showInfo = this.getOption("showInfo", t); itemsCollection .set("download", download) .set("moveTo", moveTo) - .set("delete", deleteOption); + .set("delete", deleteOption) + .set("showInfo", showInfo); + return this.convertToArray(itemsCollection); }; @@ -984,6 +1001,7 @@ class FilesActionStore { const download = this.getOption("download", t); const downloadAs = this.getOption("downloadAs", t); const copy = this.getOption("copy", t); + const showInfo = this.getOption("showInfo", t); itemsCollection .set("share", share) @@ -999,7 +1017,9 @@ class FilesActionStore { .then(() => toastr.success(t("RemovedFromFavorites"))) .catch((err) => toastr.error(err)); }, - }); + }) + .set("showInfo", showInfo); + return this.convertToArray(itemsCollection); }; @@ -1012,6 +1032,7 @@ class FilesActionStore { const download = this.getOption("download", t); const downloadAs = this.getOption("downloadAs", t); const deleteOption = this.getOption("delete", t); + const showInfo = this.getOption("showInfo", t); itemsCollection .set("download", download) @@ -1021,9 +1042,12 @@ class FilesActionStore { onClick: () => setMoveToPanelVisible(true), iconUrl: "/static/images/move.react.svg", }) - .set("delete", deleteOption); + .set("delete", deleteOption) + .set("showInfo", showInfo); + return this.convertToArray(itemsCollection); }; + getHeaderMenu = (t) => { const { isFavoritesFolder, diff --git a/products/ASC.Files/Client/src/store/FilesStore.js b/products/ASC.Files/Client/src/store/FilesStore.js index 2a2c236bf7..dc253ac5d8 100644 --- a/products/ASC.Files/Client/src/store/FilesStore.js +++ b/products/ASC.Files/Client/src/store/FilesStore.js @@ -1,23 +1,24 @@ import { makeAutoObservable, runInAction } from "mobx"; import api from "@appserver/common/api"; import { - FolderType, - FilterType, - FileType, - FileAction, AppServerConfig, + FileAction, + FileType, + FilterType, + FolderType, FileStatus, } from "@appserver/common/constants"; import history from "@appserver/common/history"; -import { loopTreeFolders } from "../helpers/files-helpers"; -import config from "../../package.json"; import { combineUrl } from "@appserver/common/utils"; import { updateTempContent } from "@appserver/common/utils"; -import { thumbnailStatuses } from "../helpers/constants"; import { isMobile } from "react-device-detect"; -import { openDocEditor as openEditor } from "../helpers/utils"; import toastr from "studio/toastr"; +import config from "../../package.json"; +import { thumbnailStatuses } from "../helpers/constants"; +import { loopTreeFolders } from "../helpers/files-helpers"; +import { openDocEditor as openEditor } from "../helpers/utils"; + const { FilesFilter } = api; const storageViewAs = localStorage.getItem("viewAs"); @@ -717,6 +718,7 @@ class FilesStore { "version", //category "finalize-version", "show-version-history", + "show-info", "block-unblock-version", //need split "separator1", "open-location", @@ -1004,6 +1006,7 @@ class FilesStore { "separator0", "sharing-settings", "owner-change", + "show-info", "link-for-portal-users", "separator1", "open-location", diff --git a/products/ASC.Files/Client/src/store/InfoPanelStore.js b/products/ASC.Files/Client/src/store/InfoPanelStore.js new file mode 100644 index 0000000000..e02d4f9b85 --- /dev/null +++ b/products/ASC.Files/Client/src/store/InfoPanelStore.js @@ -0,0 +1,27 @@ +import { makeAutoObservable } from "mobx"; + +class InfoPanelStore { + isVisible = false; + + constructor() { + makeAutoObservable(this); + } + + onHeaderCrossClick = () => { + this.isVisible = false; + }; + + toggleIsVisible = () => { + this.isVisible = !this.isVisible; + }; + + setVisible = () => { + this.isVisible = true; + }; + + setIsVisible = (bool) => { + this.isVisible = bool; + }; +} + +export default InfoPanelStore; diff --git a/products/ASC.Files/Client/src/store/SettingsStore.js b/products/ASC.Files/Client/src/store/SettingsStore.js index 64a331c15f..0dc3b264d6 100644 --- a/products/ASC.Files/Client/src/store/SettingsStore.js +++ b/products/ASC.Files/Client/src/store/SettingsStore.js @@ -1,11 +1,11 @@ -import { makeAutoObservable } from "mobx"; import api from "@appserver/common/api"; -import axios from "axios"; import { setFavoritesSetting, setRecentSetting, } from "@appserver/common/api/files"; import { FolderType } from "@appserver/common/constants"; +import axios from "axios"; +import { makeAutoObservable } from "mobx"; import { presentInArray } from "../helpers/files-helpers"; class SettingsStore { @@ -62,6 +62,10 @@ class SettingsStore { this.treeFoldersStore = treeFoldersStore; } + get infoPanelIsVisible() { + return this.infoPanelIsVisible; + } + setIsLoaded = (isLoaded) => { this.settingsIsLoaded = isLoaded; }; diff --git a/products/ASC.Files/Client/src/store/index.js b/products/ASC.Files/Client/src/store/index.js index 72e7245771..850cc5af36 100644 --- a/products/ASC.Files/Client/src/store/index.js +++ b/products/ASC.Files/Client/src/store/index.js @@ -16,13 +16,12 @@ import selectedFilesStore from "./SelectedFilesStore"; import ContextOptionsStore from "./ContextOptionsStore"; import HotkeyStore from "./HotkeyStore"; import store from "studio/store"; +import InfoPanelStore from "./InfoPanelStore"; const selectedFolderStore = new SelectedFolderStore(store.auth.settingsStore); const treeFoldersStore = new TreeFoldersStore(selectedFolderStore); - const settingsStore = new SettingsStore(thirdPartyStore, treeFoldersStore); - const filesStore = new FilesStore( store.auth, store.auth.settingsStore, @@ -37,10 +36,8 @@ const mediaViewerDataStore = new MediaViewerDataStore( filesStore, settingsStore ); - const secondaryProgressDataStore = new SecondaryProgressDataStore(); const primaryProgressDataStore = new PrimaryProgressDataStore(); - const dialogsStore = new DialogsStore( store.auth, treeFoldersStore, @@ -57,6 +54,8 @@ const uploadDataStore = new UploadDataStore( settingsStore ); +const infoPanelStore = new InfoPanelStore(); + const filesActionsStore = new FilesActionsStore( store.auth, uploadDataStore, @@ -65,11 +64,11 @@ const filesActionsStore = new FilesActionsStore( selectedFolderStore, settingsStore, dialogsStore, - mediaViewerDataStore + mediaViewerDataStore, + infoPanelStore ); const versionHistoryStore = new VersionHistoryStore(filesStore); - const contextOptionsStore = new ContextOptionsStore( store.auth, dialogsStore, @@ -79,7 +78,8 @@ const contextOptionsStore = new ContextOptionsStore( treeFoldersStore, uploadDataStore, versionHistoryStore, - settingsStore + settingsStore, + infoPanelStore ); const hotkeyStore = new HotkeyStore( @@ -105,6 +105,7 @@ const stores = { selectedFilesStore, contextOptionsStore, hotkeyStore, + infoPanelStore, }; export default stores; diff --git a/public/images/info.outline.react.svg b/public/images/info.outline.react.svg new file mode 100644 index 0000000000..ac41fda0d9 --- /dev/null +++ b/public/images/info.outline.react.svg @@ -0,0 +1,12 @@ + + + + + + + + + + + +