Merge branch 'develop' into feature/table-view
# Conflicts: # packages/asc-web-common/components/PageLayout/index.js # products/ASC.Files/Client/src/pages/Home/Section/Header/index.js # public/locales/de/Common.json # public/locales/it/Common.json # public/locales/lo/Common.json # public/locales/pt-BR/Common.json # public/locales/ro/Common.json
@ -88,6 +88,11 @@ server {
|
||||
try_files /$basename /index.html =404;
|
||||
}
|
||||
|
||||
location ~* /static/images/icons/(?<content>[^/]+) {
|
||||
root $public_root;
|
||||
try_files /images/icons/$content/$basename /index.html =404;
|
||||
}
|
||||
|
||||
location ~* /static/images/ {
|
||||
root $public_root;
|
||||
try_files /images/$basename /index.html =404;
|
||||
|
@ -538,10 +538,13 @@ export function getNewFiles(folderId) {
|
||||
});
|
||||
}
|
||||
|
||||
export function convertFile(fileId) {
|
||||
export function convertFile(fileId, sync = false) {
|
||||
const data = { sync };
|
||||
|
||||
return request({
|
||||
method: "put",
|
||||
url: `/files/file/${fileId}/checkconversion`,
|
||||
data,
|
||||
});
|
||||
}
|
||||
|
||||
@ -767,3 +770,10 @@ export function createThumbnails(fileIds) {
|
||||
|
||||
return request(options);
|
||||
}
|
||||
|
||||
export function getPresignedUri(fileId) {
|
||||
return request({
|
||||
method: "get",
|
||||
url: `files/file/${fileId}/presigned`,
|
||||
});
|
||||
}
|
||||
|
@ -315,3 +315,11 @@ export function validateTfaCode(code) {
|
||||
data,
|
||||
});
|
||||
}
|
||||
|
||||
export function getCommonThirdPartyList() {
|
||||
const options = {
|
||||
method: "get",
|
||||
url: "/files/thirdparty/common",
|
||||
};
|
||||
return request(options);
|
||||
}
|
||||
|
@ -75,14 +75,6 @@ class PageLayout extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
const isArticleVisibleAndPinned = !!this.props.isArticlePinned;
|
||||
|
||||
this.state = {
|
||||
isBackdropVisible: false,
|
||||
isArticleVisible: isArticleVisibleAndPinned,
|
||||
isArticlePinned: isArticleVisibleAndPinned,
|
||||
};
|
||||
|
||||
this.timeoutHandler = null;
|
||||
this.intervalHandler = null;
|
||||
|
||||
@ -95,13 +87,9 @@ class PageLayout extends React.Component {
|
||||
}
|
||||
|
||||
if (
|
||||
(this.props.hideAside &&
|
||||
!this.state.isArticlePinned &&
|
||||
this.props.hideAside !== prevProps.hideAside) ||
|
||||
(this.props.isLoading !== prevProps.isLoading &&
|
||||
this.props.isLoaded &&
|
||||
this.state.isArticleVisible &&
|
||||
!this.state.isArticlePinned)
|
||||
this.props.hideAside &&
|
||||
!this.props.isArticlePinned &&
|
||||
this.props.hideAside !== prevProps.hideAside
|
||||
) {
|
||||
this.backdropClick();
|
||||
}
|
||||
@ -136,42 +124,30 @@ class PageLayout extends React.Component {
|
||||
};
|
||||
|
||||
backdropClick = () => {
|
||||
this.setState({
|
||||
isBackdropVisible: false,
|
||||
isArticleVisible: false,
|
||||
isArticlePinned: false,
|
||||
});
|
||||
this.props.setArticlePinned(false);
|
||||
this.props.setIsBackdropVisible(false);
|
||||
this.props.setIsArticleVisible(false);
|
||||
isMobile && this.props.setArticleVisibleOnUnpin(false);
|
||||
};
|
||||
|
||||
pinArticle = () => {
|
||||
this.setState({
|
||||
isBackdropVisible: false,
|
||||
isArticlePinned: true,
|
||||
isArticleVisible: true,
|
||||
});
|
||||
|
||||
this.props.setIsBackdropVisible(false);
|
||||
this.props.setIsArticleVisible(true);
|
||||
this.props.setArticlePinned(true);
|
||||
isMobile && this.props.setArticleVisibleOnUnpin(false);
|
||||
};
|
||||
|
||||
unpinArticle = () => {
|
||||
this.setState({
|
||||
isBackdropVisible: true,
|
||||
isArticlePinned: false,
|
||||
isArticleVisible: true,
|
||||
});
|
||||
|
||||
this.props.setIsBackdropVisible(true);
|
||||
this.props.setIsArticleVisible(true);
|
||||
this.props.setArticlePinned(false);
|
||||
isMobile && this.props.setArticleVisibleOnUnpin(true);
|
||||
};
|
||||
|
||||
showArticle = () => {
|
||||
this.setState({
|
||||
isBackdropVisible: true,
|
||||
isArticleVisible: true,
|
||||
isArticlePinned: false,
|
||||
});
|
||||
this.props.setArticlePinned(false);
|
||||
this.props.setIsBackdropVisible(true);
|
||||
this.props.setIsArticleVisible(true);
|
||||
isMobile && this.props.setArticleVisibleOnUnpin(true);
|
||||
};
|
||||
|
||||
@ -224,8 +200,10 @@ class PageLayout extends React.Component {
|
||||
onOpenUploadPanel,
|
||||
isTabletView,
|
||||
firstLoad,
|
||||
isLoading,
|
||||
dragging,
|
||||
isArticleVisible,
|
||||
isBackdropVisible,
|
||||
isArticlePinned,
|
||||
} = this.props;
|
||||
let articleHeaderContent = null;
|
||||
let articleMainButtonContent = null;
|
||||
@ -294,16 +272,15 @@ class PageLayout extends React.Component {
|
||||
{isBackdropAvailable && (
|
||||
<Backdrop
|
||||
zIndex={400}
|
||||
visible={this.state.isBackdropVisible}
|
||||
visible={isBackdropVisible}
|
||||
onClick={this.backdropClick}
|
||||
/>
|
||||
)}
|
||||
{isArticleAvailable && (
|
||||
<Article
|
||||
visible={this.state.isArticleVisible}
|
||||
pinned={this.state.isArticlePinned}
|
||||
visible={isArticleVisible}
|
||||
pinned={isArticlePinned}
|
||||
firstLoad={firstLoad}
|
||||
isLoading={!isLoading}
|
||||
>
|
||||
{isArticleHeaderAvailable && (
|
||||
<SubArticleHeader>
|
||||
@ -320,7 +297,7 @@ class PageLayout extends React.Component {
|
||||
</SubArticleMainButton>
|
||||
)}
|
||||
{isArticleBodyAvailable && (
|
||||
<SubArticleBody pinned={this.state.isArticlePinned}>
|
||||
<SubArticleBody pinned={isArticlePinned}>
|
||||
{articleBodyContent
|
||||
? articleBodyContent.props.children
|
||||
: null}
|
||||
@ -328,7 +305,7 @@ class PageLayout extends React.Component {
|
||||
)}
|
||||
{isArticleBodyAvailable && (
|
||||
<ArticlePinPanel
|
||||
pinned={this.state.isArticlePinned}
|
||||
pinned={isArticlePinned}
|
||||
onPin={this.pinArticle}
|
||||
onUnpin={this.unpinArticle}
|
||||
/>
|
||||
@ -351,12 +328,12 @@ class PageLayout extends React.Component {
|
||||
<Section
|
||||
widthProp={width}
|
||||
unpinArticle={this.unpinArticle}
|
||||
pinned={this.state.isArticlePinned}
|
||||
pinned={isArticlePinned}
|
||||
>
|
||||
{isSectionHeaderAvailable && (
|
||||
<SubSectionHeader
|
||||
isHeaderVisible={isHeaderVisible}
|
||||
isArticlePinned={this.state.isArticlePinned}
|
||||
isArticlePinned={isArticlePinned}
|
||||
>
|
||||
{sectionHeaderContent
|
||||
? sectionHeaderContent.props.children
|
||||
@ -388,7 +365,7 @@ class PageLayout extends React.Component {
|
||||
uploadFiles={uploadFiles}
|
||||
withScroll={withBodyScroll}
|
||||
autoFocus={isMobile || isTabletView ? false : true}
|
||||
pinned={this.state.isArticlePinned}
|
||||
pinned={isArticlePinned}
|
||||
viewAs={viewAs}
|
||||
>
|
||||
{isSectionFilterAvailable && (
|
||||
@ -451,7 +428,7 @@ class PageLayout extends React.Component {
|
||||
|
||||
{isArticleAvailable && (
|
||||
<SectionToggler
|
||||
visible={!this.state.isArticleVisible}
|
||||
visible={!isArticleVisible}
|
||||
onClick={this.showArticle}
|
||||
/>
|
||||
)}
|
||||
@ -520,7 +497,6 @@ PageLayout.propTypes = {
|
||||
isTabletView: PropTypes.bool,
|
||||
isHeaderVisible: PropTypes.bool,
|
||||
firstLoad: PropTypes.bool,
|
||||
isLoading: PropTypes.bool,
|
||||
};
|
||||
|
||||
PageLayout.defaultProps = {
|
||||
@ -542,15 +518,24 @@ export default inject(({ auth }) => {
|
||||
isHeaderVisible,
|
||||
isTabletView,
|
||||
isArticlePinned,
|
||||
isArticleVisible,
|
||||
isBackdropVisible,
|
||||
setArticlePinned,
|
||||
setArticleVisibleOnUnpin,
|
||||
setIsArticleVisible,
|
||||
setIsBackdropVisible,
|
||||
} = settingsStore;
|
||||
|
||||
return {
|
||||
isLoaded,
|
||||
isTabletView,
|
||||
isHeaderVisible,
|
||||
isArticlePinned,
|
||||
isArticleVisible,
|
||||
setArticlePinned,
|
||||
setArticleVisibleOnUnpin,
|
||||
setIsArticleVisible,
|
||||
isBackdropVisible,
|
||||
setIsBackdropVisible,
|
||||
};
|
||||
})(observer(PageLayout));
|
||||
|
@ -47,7 +47,7 @@ const StyledArticle = styled.article`
|
||||
? props.pinned
|
||||
? `
|
||||
min-width: 240px;
|
||||
max-width: ${props.isLoading ? "calc(100vw - 368px)" : "240px"};
|
||||
max-width: 240px;
|
||||
|
||||
|
||||
.increaseHeight {
|
||||
|
@ -60,6 +60,8 @@ class SettingsStore {
|
||||
isTabletView = false;
|
||||
isArticlePinned =
|
||||
localStorage.getItem(ARTICLE_PINNED_KEY) === "true" || false;
|
||||
isArticleVisible = false;
|
||||
isBackdropVisible = false;
|
||||
|
||||
isArticleVisibleOnUnpin = false;
|
||||
|
||||
@ -105,6 +107,19 @@ class SettingsStore {
|
||||
return `https://helpcenter.onlyoffice.com/${lang}/administration/configuration.aspx#CustomizingPortal_block`;
|
||||
}
|
||||
|
||||
setIsArticleVisible = (visible) => {
|
||||
this.isArticleVisible = this.isArticlePinned ? true : visible;
|
||||
};
|
||||
|
||||
setIsBackdropVisible = (visible) => {
|
||||
this.isBackdropVisible = visible;
|
||||
};
|
||||
|
||||
hideArticle = () => {
|
||||
this.setIsArticleVisible(false);
|
||||
this.setIsBackdropVisible(false);
|
||||
};
|
||||
|
||||
setValue = (key, value) => {
|
||||
this[key] = value;
|
||||
};
|
||||
|
@ -6,13 +6,21 @@ import StyledAside from "./styled-aside";
|
||||
|
||||
const Aside = React.memo((props) => {
|
||||
//console.log("Aside render");
|
||||
const { visible, children, scale, zIndex, className } = props;
|
||||
const {
|
||||
visible,
|
||||
children,
|
||||
scale,
|
||||
zIndex,
|
||||
className,
|
||||
contentPaddingBottom,
|
||||
} = props;
|
||||
|
||||
return (
|
||||
<StyledAside
|
||||
visible={visible}
|
||||
scale={scale}
|
||||
zIndex={zIndex}
|
||||
contentPaddingBottom={contentPaddingBottom}
|
||||
className={`${className} not-selectable aside`}
|
||||
>
|
||||
<Scrollbar>{children}</Scrollbar>
|
||||
@ -26,6 +34,7 @@ Aside.propTypes = {
|
||||
visible: PropTypes.bool,
|
||||
scale: PropTypes.bool,
|
||||
className: PropTypes.string,
|
||||
contentPaddingBottom: PropTypes.string,
|
||||
zIndex: PropTypes.number,
|
||||
children: PropTypes.oneOfType([
|
||||
PropTypes.arrayOf(PropTypes.node),
|
||||
|
@ -4,9 +4,13 @@ import Base from "../themes/base";
|
||||
|
||||
/* eslint-disable no-unused-vars */
|
||||
/* eslint-disable react/prop-types */
|
||||
const Container = ({ visible, scale, zIndex, ...props }) => (
|
||||
<aside {...props} />
|
||||
);
|
||||
const Container = ({
|
||||
visible,
|
||||
scale,
|
||||
zIndex,
|
||||
contentPaddingBottom,
|
||||
...props
|
||||
}) => <aside {...props} />;
|
||||
/* eslint-enable react/prop-types */
|
||||
/* eslint-enable no-unused-vars */
|
||||
|
||||
@ -27,7 +31,10 @@ const StyledAside = styled(Container)`
|
||||
box-sizing: border-box;
|
||||
|
||||
&.modal-dialog-aside {
|
||||
padding-bottom: ${(props) => props.theme.aside.paddingBottom};
|
||||
padding-bottom: ${(props) =>
|
||||
props.contentPaddingBottom
|
||||
? props.contentPaddingBottom
|
||||
: props.theme.aside.paddingBottom};
|
||||
|
||||
.modal-dialog-aside-footer {
|
||||
position: fixed;
|
||||
|
@ -106,6 +106,8 @@ class ModalDialog extends React.Component {
|
||||
style,
|
||||
children,
|
||||
isLoading,
|
||||
contentPaddingBottom,
|
||||
removeScroll,
|
||||
} = this.props;
|
||||
|
||||
let header = null;
|
||||
@ -176,9 +178,14 @@ class ModalDialog extends React.Component {
|
||||
visible={visible}
|
||||
scale={scale}
|
||||
zIndex={zIndex}
|
||||
contentPaddingBottom={contentPaddingBottom}
|
||||
className="modal-dialog-aside not-selectable"
|
||||
>
|
||||
<Content contentHeight={contentHeight} contentWidth={contentWidth}>
|
||||
<Content
|
||||
contentHeight={contentHeight}
|
||||
contentWidth={contentWidth}
|
||||
removeScroll={removeScroll}
|
||||
>
|
||||
{isLoading ? (
|
||||
<Loaders.DialogAsideLoader withoutAside />
|
||||
) : (
|
||||
@ -192,6 +199,7 @@ class ModalDialog extends React.Component {
|
||||
<BodyBox
|
||||
className="modal-dialog-aside-body"
|
||||
paddingProp={bodyPadding}
|
||||
removeScroll={removeScroll}
|
||||
>
|
||||
{body ? body.props.children : null}
|
||||
</BodyBox>
|
||||
@ -229,9 +237,11 @@ ModalDialog.propTypes = {
|
||||
contentHeight: PropTypes.string,
|
||||
contentWidth: PropTypes.string,
|
||||
isLoading: PropTypes.bool,
|
||||
removeScroll: PropTypes.bool,
|
||||
className: PropTypes.string,
|
||||
id: PropTypes.string,
|
||||
style: PropTypes.oneOfType([PropTypes.object, PropTypes.array]),
|
||||
contentPaddingBottom: PropTypes.string,
|
||||
};
|
||||
|
||||
ModalDialog.defaultProps = {
|
||||
|
@ -1,4 +1,4 @@
|
||||
import styled from "styled-components";
|
||||
import styled, { css } from "styled-components";
|
||||
import Base from "../themes/base";
|
||||
import Box from "../box";
|
||||
import CrossSidebarIcon from "../../../public/images/cross.sidebar.react.svg";
|
||||
@ -34,6 +34,12 @@ const Content = styled.div`
|
||||
font-weight: ${(props) =>
|
||||
props.theme.modalDialog.content.heading.fontWeight};
|
||||
}
|
||||
|
||||
${(props) =>
|
||||
props.removeScroll &&
|
||||
css`
|
||||
overflow: hidden;
|
||||
`}
|
||||
`;
|
||||
Content.defaultProps = { theme: Base };
|
||||
|
||||
@ -66,6 +72,12 @@ CloseButton.defaultProps = { theme: Base };
|
||||
|
||||
const BodyBox = styled(Box)`
|
||||
position: relative;
|
||||
|
||||
${(props) =>
|
||||
props.removeScroll &&
|
||||
css`
|
||||
height: 100%;
|
||||
`}
|
||||
`;
|
||||
|
||||
export { CloseButton, StyledHeader, Content, Dialog, BodyBox };
|
||||
|
@ -45,6 +45,8 @@ const TableRow = (props) => {
|
||||
onContentSelect && onContentSelect(e.target.checked, item);
|
||||
};
|
||||
|
||||
console.log("selectionProp", selectionProp);
|
||||
|
||||
return (
|
||||
<StyledTableRow
|
||||
onContextMenu={onContextMenu}
|
||||
@ -55,7 +57,7 @@ const TableRow = (props) => {
|
||||
checked={checked}
|
||||
{...selectionProp}
|
||||
style={style}
|
||||
className={`${selectionProp.className} table-container_row-checkbox-wrapper`}
|
||||
className={`${selectionProp?.className} table-container_row-checkbox-wrapper`}
|
||||
>
|
||||
<div className="table-container_element">{element}</div>
|
||||
<Checkbox
|
||||
|
@ -77,7 +77,7 @@
|
||||
"react-tooltip": "^4.2.11",
|
||||
"react-virtualized-auto-sizer": "^1.0.2",
|
||||
"react-window": "^1.8.6",
|
||||
"react-window-infinite-loader": "^1.0.5",
|
||||
"react-window-infinite-loader": "^1.0.7",
|
||||
"resize-image": "^0.1.0",
|
||||
"sjcl": "^1.0.8",
|
||||
"styled-components": "^5.2.1",
|
||||
|
@ -77,4 +77,4 @@
|
||||
"UploadToFolder": "In den Ordner hochladen",
|
||||
"ViewList": "Liste",
|
||||
"ViewTiles": "Kacheln"
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,3 @@
|
||||
{
|
||||
"SelectFile": "Select file"
|
||||
}
|
@ -0,0 +1,3 @@
|
||||
{
|
||||
"NotAvailableFolder": "No folders available"
|
||||
}
|
@ -20,5 +20,6 @@
|
||||
"Restore": "Restore",
|
||||
"Spreadsheets": "Spreadsheets",
|
||||
"ThirdPartyInfo": "Change the third-party info",
|
||||
"DownloadApps": "Download applications"
|
||||
"DownloadApps": "Download applications",
|
||||
"SelectFolder": "Select folder"
|
||||
}
|
||||
|
@ -77,4 +77,4 @@
|
||||
"UploadToFolder": "Carica nella cartella",
|
||||
"ViewList": "Lista",
|
||||
"ViewTiles": "Selezioni"
|
||||
}
|
||||
}
|
||||
|
@ -77,4 +77,4 @@
|
||||
"UploadToFolder": "ອັບໂຫລດໄປຍັງໂຟຣເດີ",
|
||||
"ViewList": "ລາຍການ",
|
||||
"ViewTiles": "ຫົວຂໍ້"
|
||||
}
|
||||
}
|
||||
|
@ -77,4 +77,4 @@
|
||||
"UploadToFolder": "Upload para pasta",
|
||||
"ViewList": "Lista",
|
||||
"ViewTiles": "Azulejos"
|
||||
}
|
||||
}
|
||||
|
@ -77,4 +77,4 @@
|
||||
"UploadToFolder": "Încarcă în dosarul",
|
||||
"ViewList": "Listă",
|
||||
"ViewTiles": "Cadre"
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,3 @@
|
||||
{
|
||||
"SelectFile": "Выбрать файл"
|
||||
}
|
@ -0,0 +1,3 @@
|
||||
{
|
||||
"NotAvailableFolder": "Нет доступных папок"
|
||||
}
|
@ -20,5 +20,6 @@
|
||||
"Restore": "Восстановить",
|
||||
"Spreadsheets": "Таблицы",
|
||||
"ThirdPartyInfo": "Изменить настройки подключения",
|
||||
"DownloadApps": "Скачать приложения"
|
||||
"DownloadApps": "Скачать приложения",
|
||||
"SelectFolder": "Выбрать папку"
|
||||
}
|
||||
|
@ -300,7 +300,7 @@ export default function withContextOptions(WrappedComponent) {
|
||||
return {
|
||||
key: option,
|
||||
label: t("SharingSettings"),
|
||||
icon: "images/catalog.shared.react.svg",
|
||||
icon: "/static/images/catalog.shared.react.svg",
|
||||
onClick: this.onClickShare,
|
||||
disabled: !isShareable,
|
||||
};
|
||||
@ -315,7 +315,7 @@ export default function withContextOptions(WrappedComponent) {
|
||||
return {
|
||||
key: option,
|
||||
label: t("Translations:OwnerChange"),
|
||||
icon: "images/catalog.user.react.svg",
|
||||
icon: "/static/images/catalog.user.react.svg",
|
||||
onClick: this.onOwnerChange,
|
||||
disabled: false,
|
||||
};
|
||||
|
@ -76,22 +76,22 @@ class TreeFolders extends React.Component {
|
||||
|
||||
switch (item.rootFolderType) {
|
||||
case FolderType.USER:
|
||||
iconUrl = "images/catalog.user.react.svg";
|
||||
iconUrl = "/static/images/catalog.user.react.svg";
|
||||
break;
|
||||
case FolderType.SHARE:
|
||||
iconUrl = "images/catalog.shared.react.svg";
|
||||
iconUrl = "/static/images/catalog.shared.react.svg";
|
||||
break;
|
||||
case FolderType.COMMON:
|
||||
iconUrl = "images/catalog.portfolio.react.svg";
|
||||
iconUrl = "/static/images/catalog.portfolio.react.svg";
|
||||
break;
|
||||
case FolderType.Favorites:
|
||||
iconUrl = "images/catalog.favorites.react.svg";
|
||||
iconUrl = "/static/images/catalog.favorites.react.svg";
|
||||
break;
|
||||
case FolderType.Recent:
|
||||
iconUrl = "images/catalog.recent.react.svg";
|
||||
iconUrl = "/static/images/catalog.recent.react.svg";
|
||||
break;
|
||||
case FolderType.Privacy:
|
||||
iconUrl = "images/catalog.private.react.svg";
|
||||
iconUrl = "/static/images/catalog.private.react.svg";
|
||||
break;
|
||||
case FolderType.TRASH:
|
||||
iconUrl = "/static/images/catalog.trash.react.svg";
|
||||
@ -100,38 +100,39 @@ class TreeFolders extends React.Component {
|
||||
break;
|
||||
}
|
||||
|
||||
if (item.parentId !== 0) iconUrl = "images/catalog.folder.react.svg";
|
||||
if (item.parentId !== 0)
|
||||
iconUrl = "/static/images/catalog.folder.react.svg";
|
||||
|
||||
switch (item.providerKey) {
|
||||
case "GoogleDrive":
|
||||
iconUrl = "images/cloud.services.google.drive.react.svg";
|
||||
iconUrl = "/static/images/cloud.services.google.drive.react.svg";
|
||||
break;
|
||||
case "Box":
|
||||
iconUrl = "images/cloud.services.box.react.svg";
|
||||
iconUrl = "/static/images/cloud.services.box.react.svg";
|
||||
break;
|
||||
case "DropboxV2":
|
||||
iconUrl = "images/cloud.services.dropbox.react.svg";
|
||||
iconUrl = "/static/images/cloud.services.dropbox.react.svg";
|
||||
break;
|
||||
case "OneDrive":
|
||||
iconUrl = "images/cloud.services.onedrive.react.svg";
|
||||
iconUrl = "/static/images/cloud.services.onedrive.react.svg";
|
||||
break;
|
||||
case "SharePoint":
|
||||
iconUrl = "images/cloud.services.onedrive.react.svg";
|
||||
iconUrl = "/static/images/cloud.services.onedrive.react.svg";
|
||||
break;
|
||||
case "kDrive":
|
||||
iconUrl = "images/catalog.folder.react.svg";
|
||||
iconUrl = "/static/images/catalog.folder.react.svg";
|
||||
break;
|
||||
case "Yandex":
|
||||
iconUrl = "images/catalog.folder.react.svg";
|
||||
iconUrl = "/static/images/catalog.folder.react.svg";
|
||||
break;
|
||||
case "NextCloud":
|
||||
iconUrl = "images/cloud.services.nextcloud.react.svg";
|
||||
iconUrl = "/static/images/cloud.services.nextcloud.react.svg";
|
||||
break;
|
||||
case "OwnCloud":
|
||||
iconUrl = "images/catalog.folder.react.svg";
|
||||
iconUrl = "/static/images/catalog.folder.react.svg";
|
||||
break;
|
||||
case "WebDav":
|
||||
iconUrl = "images/catalog.folder.react.svg";
|
||||
iconUrl = "/static/images/catalog.folder.react.svg";
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
@ -157,7 +158,8 @@ class TreeFolders extends React.Component {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (draggableItems.find((x) => x.id === item.id)) return false;
|
||||
if (!draggableItems || draggableItems.find((x) => x.id === item.id))
|
||||
return false;
|
||||
|
||||
// const isMy = rootFolderType === FolderType.USER;
|
||||
// const isCommon = rootFolderType === FolderType.COMMON;
|
||||
@ -196,6 +198,7 @@ class TreeFolders extends React.Component {
|
||||
};
|
||||
|
||||
getItems = (data) => {
|
||||
const { withoutProvider } = this.props;
|
||||
return data.map((item) => {
|
||||
const dragging = this.props.dragging ? this.showDragItems(item) : false;
|
||||
|
||||
@ -203,8 +206,13 @@ class TreeFolders extends React.Component {
|
||||
? item.newItems > 0 && this.props.needUpdate
|
||||
: false;
|
||||
|
||||
const provider = item.providerKey;
|
||||
|
||||
const serviceFolder = !!item.providerKey;
|
||||
let className = `tree-drag tree-id_${item.id}`;
|
||||
|
||||
if (withoutProvider && provider) return;
|
||||
|
||||
if (dragging) className += " dragging";
|
||||
if ((item.folders && item.folders.length > 0) || serviceFolder) {
|
||||
return (
|
||||
@ -339,6 +347,7 @@ class TreeFolders extends React.Component {
|
||||
};
|
||||
|
||||
onLoadData = (treeNode, isExpand) => {
|
||||
const { data: incomingDate, certainFolders } = this.props;
|
||||
isExpand && this.setState({ isExpand: true });
|
||||
this.props.setIsLoading && this.props.setIsLoading(true);
|
||||
//console.log("load data...", treeNode);
|
||||
@ -353,7 +362,9 @@ class TreeFolders extends React.Component {
|
||||
const listIds = data.listIds;
|
||||
listIds.push(itemId);
|
||||
|
||||
const treeData = [...this.props.treeFolders];
|
||||
const treeData = certainFolders
|
||||
? incomingDate
|
||||
: [...this.props.treeFolders];
|
||||
|
||||
this.getNewTreeData(treeData, listIds, data.folders, data.level);
|
||||
this.props.setTreeFolders(treeData);
|
||||
@ -462,7 +473,10 @@ TreeFolders.defaultProps = {
|
||||
};
|
||||
|
||||
export default inject(
|
||||
({ auth, filesStore, treeFoldersStore, selectedFolderStore }) => {
|
||||
(
|
||||
{ auth, filesStore, treeFoldersStore, selectedFolderStore },
|
||||
{ useDefaultSelectedKeys, selectedKeys }
|
||||
) => {
|
||||
const {
|
||||
filter,
|
||||
selection,
|
||||
@ -494,9 +508,12 @@ export default inject(
|
||||
commonId: commonFolderId,
|
||||
isPrivacy: isPrivacyFolder,
|
||||
filter,
|
||||
draggableItems: dragging ? selection : [],
|
||||
draggableItems: dragging ? selection : null,
|
||||
expandedKeys,
|
||||
treeFolders,
|
||||
selectedKeys: useDefaultSelectedKeys
|
||||
? treeFoldersStore.selectedKeys
|
||||
: selectedKeys,
|
||||
|
||||
setDragging,
|
||||
setIsLoading,
|
||||
|
@ -12,57 +12,41 @@ import Banner from "./Banner";
|
||||
import { inject, observer } from "mobx-react";
|
||||
import { withRouter } from "react-router-dom";
|
||||
import config from "../../../../package.json";
|
||||
import { clickBackdrop, combineUrl } from "@appserver/common/utils";
|
||||
import { combineUrl } from "@appserver/common/utils";
|
||||
import { AppServerConfig } from "@appserver/common/constants";
|
||||
import FilesFilter from "@appserver/common/api/files/filter";
|
||||
import { isDesktop, isTablet } from "react-device-detect";
|
||||
|
||||
class ArticleBodyContent extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
const { selectedFolderTitle } = props;
|
||||
|
||||
selectedFolderTitle
|
||||
? setDocumentTitle(selectedFolderTitle)
|
||||
: setDocumentTitle();
|
||||
}
|
||||
|
||||
/*componentDidMount() {
|
||||
if (this.props.currentId) {
|
||||
const currentId = [this.props.currentId + ""];
|
||||
this.props.setSelectedNode(currentId);
|
||||
}
|
||||
}*/
|
||||
|
||||
onSelect = (data, e) => {
|
||||
const {
|
||||
filter,
|
||||
setIsLoading,
|
||||
selectedTreeNode,
|
||||
setSelectedNode,
|
||||
fetchFiles,
|
||||
homepage,
|
||||
history,
|
||||
hideArticle,
|
||||
} = this.props;
|
||||
|
||||
//if (!selectedTreeNode || selectedTreeNode[0] !== data[0]) {
|
||||
setSelectedNode(data);
|
||||
setIsLoading(true);
|
||||
hideArticle(false);
|
||||
|
||||
const selectedFolderTitle =
|
||||
(e.node && e.node.props && e.node.props.title) || null;
|
||||
// const selectedFolderTitle =
|
||||
// (e.node && e.node.props && e.node.props.title) || null;
|
||||
|
||||
selectedFolderTitle
|
||||
? setDocumentTitle(selectedFolderTitle)
|
||||
: setDocumentTitle();
|
||||
// selectedFolderTitle
|
||||
// ? setDocumentTitle(selectedFolderTitle)
|
||||
// : setDocumentTitle();
|
||||
|
||||
if (window.location.pathname.indexOf("/filter") > 0) {
|
||||
fetchFiles(data[0])
|
||||
.catch((err) => toastr.error(err))
|
||||
.finally(() => setIsLoading(false));
|
||||
} else {
|
||||
const urlFilter = FilesFilter.getDefault().toUrlParams();
|
||||
const newFilter = FilesFilter.getDefault();
|
||||
newFilter.folder = data[0];
|
||||
const urlFilter = newFilter.toUrlParams();
|
||||
history.push(
|
||||
combineUrl(AppServerConfig.proxyURL, homepage, `/filter?${urlFilter}`)
|
||||
);
|
||||
@ -78,12 +62,13 @@ class ArticleBodyContent extends React.Component {
|
||||
const {
|
||||
treeFolders,
|
||||
onTreeDrop,
|
||||
selectedTreeNode,
|
||||
enableThirdParty,
|
||||
isVisitor,
|
||||
personal,
|
||||
} = this.props;
|
||||
|
||||
//console.log("Article Body render");
|
||||
|
||||
const campaigns = (localStorage.getItem("campaigns") || "")
|
||||
.split(",")
|
||||
.filter((campaign) => campaign.length > 0);
|
||||
@ -93,7 +78,7 @@ class ArticleBodyContent extends React.Component {
|
||||
) : (
|
||||
<>
|
||||
<TreeFolders
|
||||
selectedKeys={selectedTreeNode}
|
||||
useDefaultSelectedKeys
|
||||
onSelect={this.onSelect}
|
||||
data={treeFolders}
|
||||
onBadgeClick={this.onShowNewFilesPanel}
|
||||
@ -119,39 +104,32 @@ export default inject(
|
||||
dialogsStore,
|
||||
settingsStore,
|
||||
}) => {
|
||||
const { fetchFiles, filter, setIsLoading } = filesStore;
|
||||
const { fetchFiles, setIsLoading } = filesStore;
|
||||
const { treeFolders, setSelectedNode, setTreeFolders } = treeFoldersStore;
|
||||
|
||||
const selectedNode = treeFoldersStore.selectedTreeNode;
|
||||
|
||||
const selectedTreeNode =
|
||||
selectedNode.length > 0 &&
|
||||
selectedNode[0] !== "@my" &&
|
||||
selectedNode[0] !== "@common"
|
||||
? selectedNode
|
||||
: [selectedFolderStore.id + ""];
|
||||
|
||||
const { setNewFilesPanelVisible } = dialogsStore;
|
||||
|
||||
const { personal } = auth.settingsStore;
|
||||
const { personal, hideArticle } = auth.settingsStore;
|
||||
|
||||
const selectedFolderTitle = selectedFolderStore.title;
|
||||
|
||||
selectedFolderTitle
|
||||
? setDocumentTitle(selectedFolderTitle)
|
||||
: setDocumentTitle();
|
||||
|
||||
return {
|
||||
selectedFolderTitle: selectedFolderStore.title,
|
||||
treeFolders,
|
||||
selectedTreeNode,
|
||||
filter,
|
||||
enableThirdParty: settingsStore.enableThirdParty,
|
||||
isVisitor: auth.userStore.user.isVisitor,
|
||||
homepage: config.homepage,
|
||||
personal,
|
||||
|
||||
setIsLoading,
|
||||
fetchFiles,
|
||||
setSelectedNode,
|
||||
setTreeFolders,
|
||||
setNewFilesPanelVisible,
|
||||
|
||||
homepage: config.homepage,
|
||||
|
||||
personal,
|
||||
hideArticle,
|
||||
};
|
||||
}
|
||||
)(observer(withRouter(ArticleBodyContent)));
|
||||
|
@ -73,7 +73,7 @@ const EmptyFolderContainer = ({
|
||||
return (
|
||||
<EmptyContainer
|
||||
headerText={t("EmptyFolderHeader")}
|
||||
imageSrc="images/empty_screen.png"
|
||||
imageSrc="/static/images/empty_screen.png"
|
||||
buttons={buttons}
|
||||
/>
|
||||
);
|
||||
|
@ -52,7 +52,7 @@ const RootFolderContainer = (props) => {
|
||||
case FolderType.USER:
|
||||
return {
|
||||
descriptionText: myDescription,
|
||||
imageSrc: "images/empty_screen.png",
|
||||
imageSrc: "/static/images/empty_screen.png",
|
||||
buttons: commonButtons,
|
||||
};
|
||||
case FolderType.SHARE:
|
||||
|
@ -0,0 +1,96 @@
|
||||
import React from "react";
|
||||
import { inject, observer } from "mobx-react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import Loader from "@appserver/components/loader";
|
||||
import Text from "@appserver/components/text";
|
||||
import Scrollbar from "@appserver/components/scrollbar";
|
||||
import TreeFolders from "../Article/Body/TreeFolders";
|
||||
import { StyledSelectFolderPanel } from "../panels/StyledPanels";
|
||||
const FolderTreeBody = ({
|
||||
isLoadingData,
|
||||
expandedKeys,
|
||||
folderList,
|
||||
onSelect,
|
||||
withoutProvider,
|
||||
certainFolders,
|
||||
isAvailable,
|
||||
filter,
|
||||
selectedKeys,
|
||||
heightContent,
|
||||
displayType,
|
||||
isHeaderChildren,
|
||||
}) => {
|
||||
const { t } = useTranslation(["SelectFolder", "Common"]);
|
||||
return (
|
||||
<>
|
||||
{!isLoadingData ? (
|
||||
isAvailable ? (
|
||||
<StyledSelectFolderPanel
|
||||
heightContent={heightContent}
|
||||
displayType={displayType}
|
||||
isHeaderChildren={isHeaderChildren}
|
||||
>
|
||||
<div className="select-folder-dialog_tree-folder">
|
||||
<Scrollbar id="folder-tree-scroll-bar">
|
||||
<TreeFolders
|
||||
expandedPanelKeys={expandedKeys}
|
||||
data={folderList}
|
||||
filter={filter}
|
||||
onSelect={onSelect}
|
||||
withoutProvider={withoutProvider}
|
||||
certainFolders={certainFolders}
|
||||
selectedKeys={selectedKeys}
|
||||
needUpdate={false}
|
||||
/>
|
||||
</Scrollbar>
|
||||
</div>
|
||||
</StyledSelectFolderPanel>
|
||||
) : (
|
||||
<StyledSelectFolderPanel
|
||||
heightContent={heightContent}
|
||||
isHeaderChildren={isHeaderChildren}
|
||||
>
|
||||
<div className="tree-folder-empty-list select-folder-dialog_tree-folder">
|
||||
<Text as="span">{t("NotAvailableFolder")}</Text>
|
||||
</div>
|
||||
</StyledSelectFolderPanel>
|
||||
)
|
||||
) : (
|
||||
<StyledSelectFolderPanel heightContent={heightContent}>
|
||||
<div className="tree-folder-Loader" key="loader">
|
||||
<Loader
|
||||
type="oval"
|
||||
size="16px"
|
||||
style={{
|
||||
display: "inline",
|
||||
marginRight: "10px",
|
||||
marginTop: "16px",
|
||||
}}
|
||||
/>
|
||||
<Text as="span">{`${t("Common:LoadingProcessing")} ${t(
|
||||
"Common:LoadingDescription"
|
||||
)}`}</Text>
|
||||
</div>
|
||||
</StyledSelectFolderPanel>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
FolderTreeBody.defaultProps = {
|
||||
isAvailable: true,
|
||||
isLoadingData: false,
|
||||
};
|
||||
|
||||
export default inject(
|
||||
({ filesStore, treeFoldersStore, selectedFolderStore }) => {
|
||||
const { filter } = filesStore;
|
||||
const { expandedPanelKeys } = treeFoldersStore;
|
||||
return {
|
||||
expandedKeys: expandedPanelKeys
|
||||
? expandedPanelKeys
|
||||
: selectedFolderStore.pathParts,
|
||||
filter,
|
||||
};
|
||||
}
|
||||
)(observer(FolderTreeBody));
|
@ -1,7 +1,13 @@
|
||||
import React from "react";
|
||||
import { ReactSVG } from "react-svg";
|
||||
import { EncryptedFileIcon } from "./Icons";
|
||||
import { inject, observer } from "mobx-react";
|
||||
import styled from "styled-components";
|
||||
|
||||
const StyledIcon = styled.img`
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
margin-top: 4px;
|
||||
`;
|
||||
|
||||
const ItemIcon = ({
|
||||
id,
|
||||
@ -17,14 +23,11 @@ const ItemIcon = ({
|
||||
(actionType !== null && actionId === id && fileExst === actionExtension) ||
|
||||
id <= 0;
|
||||
|
||||
const svgLoader = () => <div style={{ width: "24px" }}></div>;
|
||||
|
||||
return (
|
||||
<>
|
||||
<ReactSVG
|
||||
<StyledIcon
|
||||
className={`react-svg-icon${isEdit ? " is-edit" : ""}`}
|
||||
src={icon}
|
||||
loading={svgLoader}
|
||||
/>
|
||||
{isPrivacy && fileExst && (
|
||||
<EncryptedFileIcon isEdit={isEdit && viewAs !== "tile"} />
|
||||
|
@ -3,7 +3,7 @@ import Text from "@appserver/components/text";
|
||||
import IconButton from "@appserver/components/icon-button";
|
||||
import { inject, observer } from "mobx-react";
|
||||
|
||||
const getSharedButton = ({
|
||||
const SharedButton = ({
|
||||
t,
|
||||
id,
|
||||
isFolder,
|
||||
@ -34,7 +34,7 @@ const getSharedButton = ({
|
||||
color={color}
|
||||
hoverColor="#657077"
|
||||
size={18}
|
||||
iconName="images/catalog.shared.react.svg"
|
||||
iconName="/static/images/catalog.shared.react.svg"
|
||||
/>
|
||||
{t("Share")}
|
||||
</Text>
|
||||
@ -46,4 +46,4 @@ export default inject(({ filesActionsStore, dialogsStore }) => {
|
||||
onSelectItem: filesActionsStore.onSelectItem,
|
||||
setSharingPanelVisible: dialogsStore.setSharingPanelVisible,
|
||||
};
|
||||
})(observer(getSharedButton));
|
||||
})(observer(SharedButton));
|
||||
|
@ -0,0 +1,90 @@
|
||||
import styled from "styled-components";
|
||||
import Base from "@appserver/components/themes/base";
|
||||
|
||||
const paddingRightStyle = (props) =>
|
||||
props.theme.fileInput.paddingRight[props.size];
|
||||
|
||||
const widthIconStyle = (props) => props.theme.fileInput.icon.width[props.size];
|
||||
const heightIconStyle = (props) =>
|
||||
props.theme.fileInput.icon.height[props.size];
|
||||
|
||||
const StyledFileInput = styled.div`
|
||||
display: flex;
|
||||
position: relative;
|
||||
outline: none;
|
||||
.file-text-input {
|
||||
width: 100%;
|
||||
max-width: 820px;
|
||||
}
|
||||
width: ${(props) =>
|
||||
(props.scale && "100%") ||
|
||||
(props.size === "base" && props.theme.input.width.base) ||
|
||||
(props.size === "middle" && props.theme.input.width.middle) ||
|
||||
(props.size === "big" && props.theme.input.width.big) ||
|
||||
(props.size === "huge" && props.theme.input.width.huge) ||
|
||||
(props.size === "large" && props.theme.input.width.large)};
|
||||
|
||||
.text-input {
|
||||
border-color: ${(props) =>
|
||||
(props.hasError && props.theme.input.errorBorderColor) ||
|
||||
(props.hasWarning && props.theme.input.warningBorderColor) ||
|
||||
(props.isDisabled && props.theme.input.disabledBorderColor) ||
|
||||
props.theme.input.borderColor};
|
||||
|
||||
text-overflow: ellipsis;
|
||||
padding-right: 40px;
|
||||
padding-right: ${(props) => paddingRightStyle(props)};
|
||||
cursor: ${(props) => (props.isDisabled ? "default" : "pointer")};
|
||||
}
|
||||
|
||||
:hover {
|
||||
.icon {
|
||||
border-color: ${(props) =>
|
||||
(props.hasError && props.theme.input.hoverErrorBorderColor) ||
|
||||
(props.hasWarning && props.theme.input.hoverWarningBorderColor) ||
|
||||
(props.isDisabled && props.theme.input.hoverDisabledBorderColor) ||
|
||||
props.theme.input.hoverBorderColor};
|
||||
}
|
||||
}
|
||||
|
||||
:active {
|
||||
.icon {
|
||||
border-color: ${(props) =>
|
||||
(props.hasError && props.theme.input.focusErrorBorderColor) ||
|
||||
(props.hasWarning && props.theme.input.focusWarningBorderColor) ||
|
||||
(props.isDisabled && props.theme.input.focusDisabledBorderColor) ||
|
||||
props.theme.input.focusBorderColor};
|
||||
}
|
||||
}
|
||||
|
||||
.icon {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
position: absolute;
|
||||
right: 0;
|
||||
background: white;
|
||||
width: ${(props) => widthIconStyle(props)};
|
||||
|
||||
height: ${(props) => heightIconStyle(props)};
|
||||
|
||||
margin: 0;
|
||||
border: ${(props) => props.theme.fileInput.icon.border};
|
||||
border-radius: ${(props) => props.theme.fileInput.icon.borderRadius};
|
||||
|
||||
border-color: ${(props) =>
|
||||
(props.hasError && props.theme.input.errorBorderColor) ||
|
||||
(props.hasWarning && props.theme.input.warningBorderColor) ||
|
||||
(props.isDisabled && props.theme.input.disabledBorderColor) ||
|
||||
props.theme.input.borderColor};
|
||||
cursor: ${(props) => (props.isDisabled ? "default" : "pointer")};
|
||||
}
|
||||
|
||||
.icon-button {
|
||||
cursor: ${(props) => (props.isDisabled ? "default" : "pointer")};
|
||||
}
|
||||
`;
|
||||
StyledFileInput.defaultProps = { theme: Base };
|
||||
|
||||
export default StyledFileInput;
|
@ -0,0 +1,110 @@
|
||||
import React from "react";
|
||||
import PropTypes from "prop-types";
|
||||
|
||||
import IconButton from "@appserver/components/icon-button";
|
||||
import TextInput from "@appserver/components/text-input";
|
||||
import StyledFileInput from "./StyledSimpleFileInput";
|
||||
|
||||
let iconSize;
|
||||
const SimpleFileInput = ({
|
||||
size,
|
||||
placeholder,
|
||||
isDisabled,
|
||||
scale,
|
||||
isError,
|
||||
hasWarning,
|
||||
id,
|
||||
onClickInput,
|
||||
name,
|
||||
className,
|
||||
textField,
|
||||
...rest
|
||||
}) => {
|
||||
switch (size) {
|
||||
case "base":
|
||||
iconSize = 15;
|
||||
break;
|
||||
case "middle":
|
||||
iconSize = 15;
|
||||
break;
|
||||
case "big":
|
||||
iconSize = 16;
|
||||
break;
|
||||
case "huge":
|
||||
iconSize = 16;
|
||||
break;
|
||||
case "large":
|
||||
iconSize = 16;
|
||||
break;
|
||||
}
|
||||
|
||||
return (
|
||||
<StyledFileInput
|
||||
size={size}
|
||||
scale={scale ? 1 : 0}
|
||||
hasError={isError}
|
||||
hasWarning={hasWarning}
|
||||
isDisabled={isDisabled}
|
||||
className={className}
|
||||
{...rest}
|
||||
>
|
||||
<TextInput
|
||||
id={id}
|
||||
className="file-text-input"
|
||||
placeholder={placeholder}
|
||||
value={textField}
|
||||
size={size}
|
||||
isDisabled={isDisabled}
|
||||
hasError={isError}
|
||||
hasWarning={hasWarning}
|
||||
scale={scale}
|
||||
onClick={onClickInput}
|
||||
isReadOnly
|
||||
name={name}
|
||||
/>
|
||||
|
||||
<div className="icon" onClick={!isDisabled ? onClickInput : null}>
|
||||
<IconButton
|
||||
className="icon-button"
|
||||
iconName={"/static/images/catalog.folder.react.svg"}
|
||||
color={"#A3A9AE"}
|
||||
isDisabled={isDisabled}
|
||||
size={iconSize}
|
||||
/>
|
||||
</div>
|
||||
</StyledFileInput>
|
||||
);
|
||||
};
|
||||
SimpleFileInput.propTypes = {
|
||||
/** Accepts css style */
|
||||
style: PropTypes.oneOfType([PropTypes.object, PropTypes.array]),
|
||||
/** Placeholder text for the input */
|
||||
placeholder: PropTypes.string,
|
||||
/** Supported size of the input fields */
|
||||
size: PropTypes.oneOf(["base", "middle", "big", "huge", "large"]),
|
||||
/** Indicates the input field has scale */
|
||||
scale: PropTypes.bool,
|
||||
/** Accepts class */
|
||||
className: PropTypes.string,
|
||||
/** Indicates the input field has an error */
|
||||
hasError: PropTypes.bool,
|
||||
/** Indicates the input field has a warning */
|
||||
hasWarning: PropTypes.bool,
|
||||
/** Used as HTML `id` property */
|
||||
id: PropTypes.string,
|
||||
/** Indicates that the field cannot be used (e.g not authorised, or changes not saved) */
|
||||
isDisabled: PropTypes.bool,
|
||||
/** Used as HTML `name` property */
|
||||
name: PropTypes.string,
|
||||
};
|
||||
|
||||
SimpleFileInput.defaultProps = {
|
||||
size: "base",
|
||||
scale: false,
|
||||
hasWarning: false,
|
||||
hasError: false,
|
||||
isDisabled: false,
|
||||
baseFolder: "",
|
||||
};
|
||||
|
||||
export default SimpleFileInput;
|
@ -0,0 +1,153 @@
|
||||
import React, { useState } from "react";
|
||||
import {
|
||||
StyledAsidePanel,
|
||||
StyledSelectFilePanel,
|
||||
StyledHeaderContent,
|
||||
} from "../StyledPanels";
|
||||
import Text from "@appserver/components/text";
|
||||
import SelectFolderInput from "../SelectFolderInput";
|
||||
import FilesListBody from "./FilesListBody";
|
||||
import Aside from "@appserver/components/aside";
|
||||
import Heading from "@appserver/components/heading";
|
||||
import Backdrop from "@appserver/components/backdrop";
|
||||
import Button from "@appserver/components/button";
|
||||
import Loaders from "@appserver/common/components/Loaders";
|
||||
import Loader from "@appserver/components/loader";
|
||||
import EmptyContainer from "../../EmptyContainer/EmptyContainer";
|
||||
const DISPLAY_TYPE = "aside";
|
||||
const SelectFileDialogAsideView = ({
|
||||
t,
|
||||
isPanelVisible,
|
||||
zIndex,
|
||||
onClose,
|
||||
isVisible,
|
||||
withoutProvider,
|
||||
foldersType,
|
||||
onSelectFile,
|
||||
onClickInput,
|
||||
onCloseSelectFolderDialog,
|
||||
onSelectFolder,
|
||||
filesList,
|
||||
hasNextPage,
|
||||
isNextPageLoading,
|
||||
loadNextPage,
|
||||
selectedFolder,
|
||||
header,
|
||||
loadingText,
|
||||
selectedFile,
|
||||
onClickSave,
|
||||
onSetFileName,
|
||||
fileName,
|
||||
displayType,
|
||||
isTranslationsReady,
|
||||
passedId,
|
||||
headerName,
|
||||
isAvailableFolderList,
|
||||
}) => {
|
||||
const [isLoadingData, setIsLoadingData] = useState(false);
|
||||
const onSetLoadingData = (loading) => {
|
||||
setIsLoadingData(loading);
|
||||
};
|
||||
const isHeaderChildren = !!header;
|
||||
|
||||
return (
|
||||
<StyledAsidePanel visible={isPanelVisible}>
|
||||
<Backdrop
|
||||
onClick={onClose}
|
||||
visible={isPanelVisible}
|
||||
zIndex={zIndex}
|
||||
isAside={true}
|
||||
/>
|
||||
<Aside visible={isPanelVisible} zIndex={zIndex}>
|
||||
{isTranslationsReady ? (
|
||||
<StyledSelectFilePanel
|
||||
displayType={DISPLAY_TYPE}
|
||||
isHeaderChildren={isHeaderChildren}
|
||||
>
|
||||
<StyledHeaderContent className="select-file-dialog_aside-header">
|
||||
<Heading
|
||||
size="medium"
|
||||
className="select-file-dialog_aside-header_title"
|
||||
>
|
||||
{headerName ? headerName : t("SelectFile")}
|
||||
</Heading>
|
||||
</StyledHeaderContent>
|
||||
|
||||
<div className="select-file-dialog_aside-body_wrapper">
|
||||
<div className="select-file-dialog_aside-children">{header}</div>
|
||||
<Text fontWeight="600" fontSize="14px">
|
||||
{t("Translations:SelectFolder")}
|
||||
</Text>
|
||||
<div className="select-file-dialog_aside_body">
|
||||
<SelectFolderInput
|
||||
onClickInput={onClickInput}
|
||||
onClose={onCloseSelectFolderDialog}
|
||||
onSelectFolder={onSelectFolder}
|
||||
onSetLoadingData={onSetLoadingData}
|
||||
isPanelVisible={isVisible}
|
||||
foldersType={foldersType}
|
||||
isNeedArrowIcon
|
||||
withoutProvider={withoutProvider}
|
||||
isSetFolderImmediately
|
||||
selectedFolderId={selectedFolder}
|
||||
id={passedId}
|
||||
onSetFileName={onSetFileName}
|
||||
fileName={fileName}
|
||||
displayType={displayType}
|
||||
dialogWithFiles
|
||||
/>
|
||||
|
||||
{selectedFolder && !isLoadingData ? (
|
||||
<FilesListBody
|
||||
filesList={filesList}
|
||||
onSelectFile={onSelectFile}
|
||||
hasNextPage={hasNextPage}
|
||||
isNextPageLoading={isNextPageLoading}
|
||||
loadNextPage={loadNextPage}
|
||||
selectedFolder={selectedFolder}
|
||||
displayType={DISPLAY_TYPE}
|
||||
loadingText={loadingText}
|
||||
selectedFile={selectedFile}
|
||||
/>
|
||||
) : isAvailableFolderList ? (
|
||||
<div key="loader">
|
||||
<Loader type="oval" size="16px" className="panel-loader" />
|
||||
<Text as="span">{`${t("Common:LoadingProcessing")} ${t(
|
||||
"Common:LoadingDescription"
|
||||
)}`}</Text>
|
||||
</div>
|
||||
) : (
|
||||
<div className="select-file-dialog_empty-container">
|
||||
<EmptyContainer
|
||||
headerText={t("Home:EmptyFolderHeader")}
|
||||
imageSrc="/static/images/empty_screen.png"
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<div className="select-file-dialog-aside_buttons">
|
||||
<Button
|
||||
className="select-file-dialog-buttons-save"
|
||||
primary
|
||||
size="big"
|
||||
label={t("Common:SaveButton")}
|
||||
onClick={onClickSave}
|
||||
isDisabled={selectedFile.length === 0}
|
||||
/>
|
||||
<Button
|
||||
primary
|
||||
size="big"
|
||||
label={t("Common:CloseButton")}
|
||||
onClick={onClose}
|
||||
/>
|
||||
</div>
|
||||
</StyledSelectFilePanel>
|
||||
) : (
|
||||
<Loaders.DialogAsideLoader withoutAside isPanel />
|
||||
)}
|
||||
</Aside>
|
||||
</StyledAsidePanel>
|
||||
);
|
||||
};
|
||||
export default SelectFileDialogAsideView;
|
@ -0,0 +1,177 @@
|
||||
import React, { useCallback, useEffect, useRef } from "react";
|
||||
import Loader from "@appserver/components/loader";
|
||||
import Text from "@appserver/components/text";
|
||||
import { useTranslation, withTranslation } from "react-i18next";
|
||||
import CustomScrollbarsVirtualList from "@appserver/components/scrollbar/custom-scrollbars-virtual-list";
|
||||
import InfiniteLoader from "react-window-infinite-loader";
|
||||
import AutoSizer from "react-virtualized-auto-sizer";
|
||||
import { FixedSizeList as List } from "react-window";
|
||||
import { inject, observer } from "mobx-react";
|
||||
import FilesListRow from "./FilesListRow";
|
||||
import EmptyContainer from "../../EmptyContainer/EmptyContainer";
|
||||
import i18n from "./i18n";
|
||||
|
||||
import { I18nextProvider } from "react-i18next";
|
||||
|
||||
const FilesListBody = ({
|
||||
filesList,
|
||||
onSelectFile,
|
||||
loadNextPage,
|
||||
hasNextPage,
|
||||
isNextPageLoading,
|
||||
displayType,
|
||||
viewer,
|
||||
listHeight,
|
||||
needRowSelection,
|
||||
loadingText,
|
||||
selectedFolder,
|
||||
isMultiSelect,
|
||||
selectedFile,
|
||||
}) => {
|
||||
const { t } = useTranslation(["SelectFile", "Common"]);
|
||||
const filesListRef = useRef(null);
|
||||
|
||||
useEffect(() => {
|
||||
if (filesListRef && filesListRef.current) {
|
||||
filesListRef.current.resetloadMoreItemsCache(true);
|
||||
}
|
||||
}, [selectedFolder, displayType]);
|
||||
// Every row is loaded except for our loading indicator row.
|
||||
const isItemLoaded = useCallback(
|
||||
(index) => {
|
||||
return !hasNextPage || index < filesList.length;
|
||||
},
|
||||
[filesList, hasNextPage]
|
||||
);
|
||||
// If there are more items to be loaded then add an extra row to hold a loading indicator.
|
||||
const itemCount = hasNextPage ? filesList.length + 1 : filesList.length;
|
||||
|
||||
const loadMoreItems = useCallback(() => {
|
||||
if (isNextPageLoading) return;
|
||||
loadNextPage && loadNextPage();
|
||||
}, [isNextPageLoading, filesList, displayType]);
|
||||
|
||||
const renderLoader = useCallback(
|
||||
(style) => {
|
||||
return (
|
||||
<div style={style}>
|
||||
<div key="loader" className="panel-loader-wrapper">
|
||||
<Loader type="oval" size="16px" className="panel-loader" />
|
||||
<Text as="span">{loadingText}</Text>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
[loadingText]
|
||||
);
|
||||
const isFileChecked = useCallback(
|
||||
(file) => {
|
||||
const checked = selectedFile ? file.id === selectedFile.id : false;
|
||||
return checked;
|
||||
},
|
||||
[selectedFile]
|
||||
);
|
||||
const Item = useCallback(
|
||||
({ index, style }) => {
|
||||
const isLoaded = isItemLoaded(index);
|
||||
|
||||
if (!isLoaded) {
|
||||
return renderLoader(style);
|
||||
}
|
||||
|
||||
const file = filesList[index];
|
||||
const fileName = file.title;
|
||||
const fileExst = file.fileExst;
|
||||
const modifyFileName = fileName.substring(
|
||||
0,
|
||||
fileName.indexOf(`${fileExst}`)
|
||||
);
|
||||
|
||||
const fileOwner =
|
||||
file.createdBy &&
|
||||
((viewer.id === file.createdBy.id && t("Common:MeLabel")) ||
|
||||
file.createdBy.displayName);
|
||||
|
||||
const isChecked = isFileChecked(file);
|
||||
|
||||
return (
|
||||
<div style={style}>
|
||||
<FilesListRow
|
||||
displayType={displayType}
|
||||
needRowSelection={needRowSelection}
|
||||
index={index}
|
||||
onSelectFile={onSelectFile}
|
||||
fileName={modifyFileName}
|
||||
fileExst={fileExst}
|
||||
isMultiSelect={isMultiSelect}
|
||||
isChecked={isChecked}
|
||||
>
|
||||
<Text data-index={index} className="files-list_file-owner">
|
||||
{fileOwner}
|
||||
</Text>
|
||||
</FilesListRow>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
[filesList, selectedFile, displayType, renderLoader]
|
||||
);
|
||||
return (
|
||||
<>
|
||||
<AutoSizer>
|
||||
{({ width, height }) => (
|
||||
<InfiniteLoader
|
||||
ref={filesListRef}
|
||||
isItemLoaded={isItemLoaded}
|
||||
itemCount={itemCount}
|
||||
loadMoreItems={loadMoreItems}
|
||||
>
|
||||
{({ onItemsRendered, ref }) => (
|
||||
<List
|
||||
height={displayType === "aside" ? height : listHeight}
|
||||
itemCount={itemCount}
|
||||
itemSize={displayType === "aside" ? 56 : 36}
|
||||
onItemsRendered={onItemsRendered}
|
||||
ref={ref}
|
||||
width={width + 8}
|
||||
outerElementType={CustomScrollbarsVirtualList}
|
||||
>
|
||||
{Item}
|
||||
</List>
|
||||
)}
|
||||
</InfiniteLoader>
|
||||
)}
|
||||
</AutoSizer>
|
||||
|
||||
{!hasNextPage && itemCount === 0 && (
|
||||
<div className="select-file-dialog_empty-container">
|
||||
<EmptyContainer
|
||||
headerText={t("Home:EmptyFolderHeader")}
|
||||
imageSrc="/static/images/empty_screen.png"
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
FilesListBody.defaultProps = {
|
||||
listHeight: 300,
|
||||
isMultiSelect: false,
|
||||
};
|
||||
const FilesListBodyWrapper = inject(({ auth }) => {
|
||||
const { user } = auth.userStore;
|
||||
return {
|
||||
viewer: user,
|
||||
};
|
||||
})(observer(withTranslation(["Common", "Home"])(FilesListBody)));
|
||||
|
||||
class FilesList extends React.Component {
|
||||
render() {
|
||||
return (
|
||||
<I18nextProvider i18n={i18n}>
|
||||
<FilesListBodyWrapper {...this.props} />
|
||||
</I18nextProvider>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default FilesList;
|
@ -0,0 +1,76 @@
|
||||
import React from "react";
|
||||
import { StyledFilesList } from "../StyledPanels";
|
||||
import { ReactSVG } from "react-svg";
|
||||
import { inject, observer } from "mobx-react";
|
||||
import Text from "@appserver/components/text";
|
||||
import Checkbox from "@appserver/components/checkbox";
|
||||
import RadioButton from "@appserver/components/radio-button";
|
||||
const FilesListRow = ({
|
||||
displayType,
|
||||
needRowSelection,
|
||||
index,
|
||||
onSelectFile,
|
||||
fileName,
|
||||
children,
|
||||
fileExst,
|
||||
iconSrc,
|
||||
isMultiSelect, // it will be needed
|
||||
isChecked,
|
||||
}) => {
|
||||
return (
|
||||
<StyledFilesList
|
||||
displayType={displayType}
|
||||
needRowSelection={needRowSelection}
|
||||
isChecked={isChecked}
|
||||
>
|
||||
<div
|
||||
data-index={index}
|
||||
className="modal-dialog_file-name"
|
||||
onClick={onSelectFile}
|
||||
>
|
||||
{isMultiSelect ? ( // it will be needed
|
||||
<Checkbox
|
||||
label=""
|
||||
isChecked={isChecked}
|
||||
className="select-file-dialog_checked"
|
||||
/>
|
||||
) : (
|
||||
<RadioButton
|
||||
fontSize="13px"
|
||||
fontWeight="400"
|
||||
name={`${index}`}
|
||||
label=""
|
||||
isChecked={isChecked}
|
||||
onClick={onSelectFile}
|
||||
value=""
|
||||
className="select-file-dialog_checked"
|
||||
/>
|
||||
)}
|
||||
<ReactSVG src={iconSrc} className="select-file-dialog_icon" />
|
||||
<div data-index={index} className="files-list_full-name">
|
||||
<Text data-index={index} className="entry-title">
|
||||
{fileName}
|
||||
</Text>
|
||||
|
||||
<div data-index={index} className="file-exst">
|
||||
{fileExst}
|
||||
</div>
|
||||
</div>
|
||||
<div className="files-list_file-children_wrapper">{children}</div>
|
||||
</div>
|
||||
</StyledFilesList>
|
||||
);
|
||||
};
|
||||
|
||||
FilesListRow.defaultProps = {
|
||||
needRowSelection: true,
|
||||
isMultiSelect: false,
|
||||
};
|
||||
|
||||
export default inject(({ formatsStore }, { fileExst }) => {
|
||||
const { iconFormatsStore } = formatsStore;
|
||||
const iconSrc = iconFormatsStore.getIconSrc(fileExst, 24);
|
||||
return {
|
||||
iconSrc,
|
||||
};
|
||||
})(observer(FilesListRow));
|
@ -0,0 +1,259 @@
|
||||
import React from "react";
|
||||
import { Provider as MobxProvider } from "mobx-react";
|
||||
import stores from "../../../store/index";
|
||||
import { StyledAsidePanel, StyledSelectFilePanel } from "../StyledPanels";
|
||||
import ModalDialog from "@appserver/components/modal-dialog";
|
||||
import SelectFolderDialog from "../SelectFolderDialog";
|
||||
import FolderTreeBody from "../../FolderTreeBody";
|
||||
import FilesListBody from "./FilesListBody";
|
||||
import Button from "@appserver/components/button";
|
||||
import Loader from "@appserver/components/loader";
|
||||
import Text from "@appserver/components/text";
|
||||
import { isArrayEqual } from "@appserver/components/utils/array";
|
||||
import { FolderType } from "@appserver/common/constants";
|
||||
import { getFoldersTree } from "@appserver/common/api/files";
|
||||
|
||||
const exceptSortedByTagsFolders = [
|
||||
FolderType.Recent,
|
||||
FolderType.TRASH,
|
||||
FolderType.Favorites,
|
||||
];
|
||||
|
||||
const exceptTrashFolder = [FolderType.TRASH];
|
||||
class SelectFileDialogModalView extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
isLoading: true,
|
||||
isAvailable: true,
|
||||
};
|
||||
this.folderList = "";
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
const { onSetLoadingData } = this.props;
|
||||
this.setState({ isLoadingData: true }, function () {
|
||||
onSetLoadingData && onSetLoadingData(true);
|
||||
|
||||
this.trySwitch();
|
||||
});
|
||||
}
|
||||
trySwitch = async () => {
|
||||
const {
|
||||
foldersType,
|
||||
onSelectFolder,
|
||||
selectedFolder,
|
||||
passedId,
|
||||
} = this.props;
|
||||
switch (foldersType) {
|
||||
case "exceptSortedByTags":
|
||||
try {
|
||||
const foldersTree = await getFoldersTree();
|
||||
this.folderList = SelectFolderDialog.convertFolders(
|
||||
foldersTree,
|
||||
exceptSortedByTagsFolders
|
||||
);
|
||||
this.onSetSelectedFolder();
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
}
|
||||
|
||||
this.loadersCompletes();
|
||||
break;
|
||||
case "exceptTrashFolder":
|
||||
try {
|
||||
const foldersTree = await getFoldersTree();
|
||||
this.folderList = SelectFolderDialog.convertFolders(
|
||||
foldersTree,
|
||||
exceptTrashFolder
|
||||
);
|
||||
this.onSetSelectedFolder();
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
}
|
||||
|
||||
this.loadersCompletes();
|
||||
break;
|
||||
case "common":
|
||||
try {
|
||||
this.folderList = await SelectFolderDialog.getCommonFolders();
|
||||
|
||||
!selectedFolder &&
|
||||
onSelectFolder &&
|
||||
onSelectFolder(
|
||||
`${
|
||||
selectedFolder
|
||||
? selectedFolder
|
||||
: passedId
|
||||
? passedId
|
||||
: this.folderList[0].id
|
||||
}`
|
||||
);
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
}
|
||||
|
||||
this.loadersCompletes();
|
||||
break;
|
||||
case "third-party":
|
||||
try {
|
||||
this.folderList = await SelectFolderDialog.getCommonThirdPartyList();
|
||||
this.folderList.length !== 0
|
||||
? this.onSetSelectedFolder()
|
||||
: this.setState({ isAvailable: false });
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
}
|
||||
|
||||
this.loadersCompletes();
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
loadersCompletes = () => {
|
||||
const { onSetLoadingData } = this.props;
|
||||
onSetLoadingData && onSetLoadingData(false);
|
||||
|
||||
this.setState({
|
||||
isLoading: false,
|
||||
});
|
||||
};
|
||||
|
||||
onSetSelectedFolder = () => {
|
||||
const { onSelectFolder, selectedFolder, passedId } = this.props;
|
||||
|
||||
onSelectFolder &&
|
||||
onSelectFolder(
|
||||
`${
|
||||
selectedFolder
|
||||
? selectedFolder
|
||||
: passedId
|
||||
? passedId
|
||||
: this.folderList[0].id
|
||||
}`
|
||||
);
|
||||
};
|
||||
onSelect = (folder) => {
|
||||
const { onSelectFolder, selectedFolder } = this.props;
|
||||
|
||||
if (isArrayEqual([folder[0]], [selectedFolder])) {
|
||||
return;
|
||||
}
|
||||
|
||||
onSelectFolder && onSelectFolder(folder[0]);
|
||||
};
|
||||
render() {
|
||||
const {
|
||||
t,
|
||||
isPanelVisible,
|
||||
onClose,
|
||||
zIndex,
|
||||
withoutProvider,
|
||||
expandedKeys,
|
||||
filter,
|
||||
onSelectFile,
|
||||
filesList,
|
||||
hasNextPage,
|
||||
isNextPageLoading,
|
||||
loadNextPage,
|
||||
selectedFolder,
|
||||
header,
|
||||
loadingText,
|
||||
selectedFile,
|
||||
onClickSave,
|
||||
headerName,
|
||||
} = this.props;
|
||||
|
||||
const { isLoading, isAvailable } = this.state;
|
||||
|
||||
const isHeaderChildren = !!header;
|
||||
|
||||
return (
|
||||
<StyledAsidePanel visible={isPanelVisible}>
|
||||
<ModalDialog
|
||||
visible={isPanelVisible}
|
||||
zIndex={zIndex}
|
||||
onClose={onClose}
|
||||
className="select-file-modal-dialog"
|
||||
style={{ maxWidth: "890px" }}
|
||||
displayType="modal"
|
||||
bodyPadding="0"
|
||||
>
|
||||
<ModalDialog.Header>
|
||||
{headerName ? headerName : t("SelectFile")}
|
||||
</ModalDialog.Header>
|
||||
<ModalDialog.Body className="select-file_body-modal-dialog">
|
||||
<StyledSelectFilePanel isHeaderChildren={isHeaderChildren}>
|
||||
{!isLoading ? (
|
||||
<div className="modal-dialog_body">
|
||||
<div className="modal-dialog_children">{header}</div>
|
||||
<div className="modal-dialog_tree-body">
|
||||
<FolderTreeBody
|
||||
expandedKeys={expandedKeys}
|
||||
folderList={this.folderList}
|
||||
onSelect={this.onSelect}
|
||||
withoutProvider={withoutProvider}
|
||||
certainFolders
|
||||
isAvailable={isAvailable}
|
||||
filter={filter}
|
||||
selectedKeys={[selectedFolder]}
|
||||
isHeaderChildren={isHeaderChildren}
|
||||
/>
|
||||
</div>
|
||||
<div className="modal-dialog_files-body">
|
||||
{selectedFolder && (
|
||||
<FilesListBody
|
||||
filesList={filesList}
|
||||
onSelectFile={onSelectFile}
|
||||
hasNextPage={hasNextPage}
|
||||
isNextPageLoading={isNextPageLoading}
|
||||
loadNextPage={loadNextPage}
|
||||
selectedFolder={selectedFolder}
|
||||
loadingText={loadingText}
|
||||
selectedFile={selectedFile}
|
||||
listHeight={isHeaderChildren ? 280 : 310}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<div
|
||||
key="loader"
|
||||
className="select-file-dialog_modal-loader panel-loader-wrapper"
|
||||
>
|
||||
<Loader type="oval" size="16px" className="panel-loader" />
|
||||
<Text as="span">{`${t("Common:LoadingProcessing")} ${t(
|
||||
"Common:LoadingDescription"
|
||||
)}`}</Text>
|
||||
</div>
|
||||
)}
|
||||
</StyledSelectFilePanel>
|
||||
</ModalDialog.Body>
|
||||
<ModalDialog.Footer>
|
||||
<StyledSelectFilePanel isHeaderChildren={isHeaderChildren}>
|
||||
<div className="select-file-dialog-modal_buttons">
|
||||
<Button
|
||||
className="select-file-modal-dialog-buttons-save"
|
||||
primary
|
||||
size="medium"
|
||||
label={t("Common:SaveButton")}
|
||||
onClick={onClickSave}
|
||||
isDisabled={selectedFile.length === 0}
|
||||
/>
|
||||
<Button
|
||||
className="modal-dialog-button"
|
||||
primary
|
||||
size="medium"
|
||||
label={t("Common:CloseButton")}
|
||||
onClick={onClose}
|
||||
/>
|
||||
</div>
|
||||
</StyledSelectFilePanel>
|
||||
</ModalDialog.Footer>
|
||||
</ModalDialog>
|
||||
</StyledAsidePanel>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default SelectFileDialogModalView;
|
@ -0,0 +1,35 @@
|
||||
import i18n from "i18next";
|
||||
import Backend from "i18next-http-backend";
|
||||
import { LANGUAGE } from "@appserver/common/constants";
|
||||
import config from "../../../../package.json";
|
||||
import { loadLanguagePath } from "@appserver/common/utils";
|
||||
|
||||
const newInstance = i18n.createInstance();
|
||||
|
||||
newInstance.use(Backend).init({
|
||||
lng: localStorage.getItem(LANGUAGE) || "en",
|
||||
fallbackLng: "en",
|
||||
load: "all",
|
||||
//debug: true,
|
||||
|
||||
interpolation: {
|
||||
escapeValue: false, // not needed for react as it escapes by default
|
||||
format: function (value, format) {
|
||||
if (format === "lowercase") return value.toLowerCase();
|
||||
return value;
|
||||
},
|
||||
},
|
||||
|
||||
backend: {
|
||||
loadPath: loadLanguagePath(config.homepage),
|
||||
},
|
||||
|
||||
ns: ["SelectFile", "Common", "Home", "Translations"],
|
||||
defaultNS: "SelectFile",
|
||||
|
||||
react: {
|
||||
useSuspense: false,
|
||||
},
|
||||
});
|
||||
|
||||
export default newInstance;
|
@ -0,0 +1,389 @@
|
||||
import React from "react";
|
||||
import { inject, observer, Provider as MobxProvider } from "mobx-react";
|
||||
import { I18nextProvider } from "react-i18next";
|
||||
import { withTranslation } from "react-i18next";
|
||||
import PropTypes from "prop-types";
|
||||
import throttle from "lodash/throttle";
|
||||
|
||||
import stores from "../../../store/index";
|
||||
import i18n from "./i18n";
|
||||
import SelectFileDialogModalView from "./ModalView";
|
||||
import SelectFileDialogAsideView from "./AsideView";
|
||||
|
||||
import utils from "@appserver/components/utils";
|
||||
import SelectFolderDialog from "../SelectFolderDialog";
|
||||
import { getFolder } from "@appserver/common/api/files";
|
||||
import { FilterType } from "@appserver/common/constants";
|
||||
|
||||
const { desktop } = utils.device;
|
||||
|
||||
import store from "studio/store";
|
||||
|
||||
const { auth: authStore } = store;
|
||||
|
||||
class SelectFileDialogBody extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
const { folderId, storeFolderId, fileInfo, filter } = this.props;
|
||||
|
||||
this.state = {
|
||||
isVisible: false,
|
||||
selectedFolder: storeFolderId || "",
|
||||
passedId: folderId,
|
||||
selectedFile: fileInfo || "",
|
||||
fileName: (fileInfo && fileInfo.title) || "",
|
||||
filesList: [],
|
||||
hasNextPage: true,
|
||||
isNextPageLoading: false,
|
||||
displayType: this.getDisplayType(),
|
||||
page: 0,
|
||||
filterParams: this.getFilterParameters(),
|
||||
isAvailableFolderList: true,
|
||||
};
|
||||
this.throttledResize = throttle(this.setDisplayType, 300);
|
||||
this.newFilter = filter.clone();
|
||||
this._isLoadNextPage = false;
|
||||
}
|
||||
|
||||
getFilterParameters = () => {
|
||||
const {
|
||||
isImageOnly,
|
||||
isDocumentsOnly,
|
||||
isArchiveOnly,
|
||||
isPresentationOnly,
|
||||
isTablesOnly,
|
||||
isMediaOnly,
|
||||
searchParam = "",
|
||||
} = this.props;
|
||||
|
||||
if (isImageOnly) {
|
||||
return { filterType: FilterType.ImagesOnly, filterValue: searchParam };
|
||||
}
|
||||
if (isDocumentsOnly) {
|
||||
return { filterType: FilterType.DocumentsOnly, filterValue: searchParam };
|
||||
}
|
||||
if (isArchiveOnly) {
|
||||
return { filterType: FilterType.ArchiveOnly, filterValue: searchParam };
|
||||
}
|
||||
if (isPresentationOnly) {
|
||||
return {
|
||||
filterType: FilterType.PresentationsOnly,
|
||||
filterValue: searchParam,
|
||||
};
|
||||
}
|
||||
if (isTablesOnly) {
|
||||
return {
|
||||
filterType: FilterType.SpreadsheetsOnly,
|
||||
filterValue: searchParam,
|
||||
};
|
||||
}
|
||||
if (isMediaOnly) {
|
||||
return { filterType: FilterType.MediaOnly, filterValue: searchParam };
|
||||
}
|
||||
return { filterType: FilterType.FilesOnly, filterValue: "" };
|
||||
};
|
||||
|
||||
setFilter = () => {
|
||||
const { filterParams } = this.state;
|
||||
const { withSubfolders = true } = this.props;
|
||||
|
||||
this.newFilter.filterType = filterParams.filterType;
|
||||
this.newFilter.search = filterParams.filterValue;
|
||||
this.newFilter.withSubfolders = withSubfolders;
|
||||
};
|
||||
|
||||
componentDidMount() {
|
||||
authStore.init(true); // it will work if authStore is not initialized
|
||||
|
||||
window.addEventListener("resize", this.throttledResize);
|
||||
this.setFilter();
|
||||
}
|
||||
componentWillUnmount() {
|
||||
const {
|
||||
resetTreeFolders,
|
||||
setExpandedPanelKeys,
|
||||
setDefaultSelectedFolder,
|
||||
setFolderId,
|
||||
setFile,
|
||||
} = this.props;
|
||||
this.throttledResize && this.throttledResize.cancel();
|
||||
window.removeEventListener("resize", this.throttledResize);
|
||||
|
||||
if (resetTreeFolders) {
|
||||
setExpandedPanelKeys(null);
|
||||
setDefaultSelectedFolder();
|
||||
|
||||
setFolderId(null);
|
||||
setFile(null);
|
||||
}
|
||||
}
|
||||
|
||||
getDisplayType = () => {
|
||||
const displayType =
|
||||
window.innerWidth < desktop.match(/\d+/)[0] ? "aside" : "modal";
|
||||
|
||||
return displayType;
|
||||
};
|
||||
|
||||
setDisplayType = () => {
|
||||
const displayType = this.getDisplayType();
|
||||
|
||||
this.setState({ displayType: displayType });
|
||||
};
|
||||
|
||||
onClickInput = () => {
|
||||
this.setState({
|
||||
isVisible: true,
|
||||
});
|
||||
};
|
||||
|
||||
onCloseSelectFolderDialog = () => {
|
||||
this.setState({
|
||||
isVisible: false,
|
||||
});
|
||||
};
|
||||
|
||||
onSelectFolder = (id) => {
|
||||
const { setFolderId } = this.props;
|
||||
|
||||
if (id) {
|
||||
setFolderId(id);
|
||||
|
||||
this.setState({
|
||||
selectedFolder: id,
|
||||
hasNextPage: true,
|
||||
filesList: [],
|
||||
page: 0,
|
||||
});
|
||||
} else
|
||||
this.setState({
|
||||
isAvailableFolderList: false,
|
||||
});
|
||||
};
|
||||
|
||||
onSelectFile = (e) => {
|
||||
const { filesList } = this.state;
|
||||
const { setFile } = this.props;
|
||||
const index = e.target.dataset.index || e.target.name;
|
||||
|
||||
if (!index) return;
|
||||
setFile(filesList[+index]);
|
||||
this.setState({
|
||||
selectedFile: filesList[+index],
|
||||
fileName: filesList[+index].title,
|
||||
});
|
||||
};
|
||||
|
||||
onClickSave = () => {
|
||||
const { onSetFileName, onClose, onSelectFile } = this.props;
|
||||
const { fileName, selectedFile } = this.state;
|
||||
|
||||
onSetFileName && onSetFileName(fileName);
|
||||
onSelectFile && onSelectFile(selectedFile);
|
||||
onClose && onClose();
|
||||
};
|
||||
|
||||
loadNextPage = () => {
|
||||
const { setSelectedNode, setSelectedFolder } = this.props;
|
||||
const { selectedFolder, page } = this.state;
|
||||
|
||||
if (this._isLoadNextPage) return;
|
||||
|
||||
this._isLoadNextPage = true;
|
||||
|
||||
const pageCount = 30;
|
||||
this.newFilter.page = page;
|
||||
this.newFilter.pageCount = pageCount;
|
||||
|
||||
this.setState({ isNextPageLoading: true }, () => {
|
||||
getFolder(selectedFolder, this.newFilter)
|
||||
.then((data) => {
|
||||
let newFilesList = page
|
||||
? this.state.filesList.concat(data.files)
|
||||
: data.files;
|
||||
|
||||
setSelectedNode([selectedFolder + ""]);
|
||||
const newPathParts = SelectFolderDialog.convertPathParts(
|
||||
data.pathParts
|
||||
);
|
||||
|
||||
setSelectedFolder({
|
||||
folders: data.folders,
|
||||
...data.current,
|
||||
pathParts: newPathParts,
|
||||
...{ new: data.new },
|
||||
});
|
||||
this.setState({
|
||||
hasNextPage: newFilesList.length < data.total,
|
||||
isNextPageLoading: false,
|
||||
filesList: newFilesList,
|
||||
page: page + 1,
|
||||
});
|
||||
})
|
||||
.catch((error) => console.log(error))
|
||||
.finally(() => (this._isLoadNextPage = false));
|
||||
});
|
||||
};
|
||||
render() {
|
||||
const {
|
||||
t,
|
||||
isPanelVisible,
|
||||
onClose,
|
||||
zIndex,
|
||||
foldersType,
|
||||
withoutProvider,
|
||||
header,
|
||||
loadingLabel,
|
||||
folderId,
|
||||
onSetFileName,
|
||||
tReady,
|
||||
headerName,
|
||||
} = this.props;
|
||||
const {
|
||||
isVisible,
|
||||
filesList,
|
||||
hasNextPage,
|
||||
isNextPageLoading,
|
||||
selectedFolder,
|
||||
displayType,
|
||||
selectedFile,
|
||||
fileName,
|
||||
passedId,
|
||||
isAvailableFolderList,
|
||||
} = this.state;
|
||||
|
||||
const loadingText = loadingLabel
|
||||
? loadingLabel
|
||||
: `${t("Common:LoadingProcessing")} ${t("Common:LoadingDescription")}`;
|
||||
|
||||
return displayType === "aside" ? (
|
||||
<SelectFileDialogAsideView
|
||||
t={t}
|
||||
isPanelVisible={isPanelVisible}
|
||||
zIndex={zIndex}
|
||||
onClose={onClose}
|
||||
isVisible={isVisible}
|
||||
withoutProvider={withoutProvider}
|
||||
foldersType={foldersType}
|
||||
filesList={filesList}
|
||||
onSelectFile={this.onSelectFile}
|
||||
onClickInput={this.onClickInput}
|
||||
onClickSave={this.onClickSave}
|
||||
onCloseSelectFolderDialog={this.onCloseSelectFolderDialog}
|
||||
onSelectFolder={this.onSelectFolder}
|
||||
hasNextPage={hasNextPage}
|
||||
isNextPageLoading={isNextPageLoading}
|
||||
loadNextPage={this.loadNextPage}
|
||||
selectedFolder={selectedFolder}
|
||||
headerName={headerName}
|
||||
loadingText={loadingText}
|
||||
selectedFile={selectedFile}
|
||||
folderId={folderId}
|
||||
onSetFileName={onSetFileName}
|
||||
fileName={fileName}
|
||||
displayType={displayType}
|
||||
isTranslationsReady={tReady}
|
||||
passedId={passedId}
|
||||
header={header}
|
||||
isAvailableFolderList={isAvailableFolderList}
|
||||
/>
|
||||
) : (
|
||||
<SelectFileDialogModalView
|
||||
t={t}
|
||||
isPanelVisible={isPanelVisible}
|
||||
onClose={onClose}
|
||||
onSelectFolder={this.onSelectFolder}
|
||||
onSelectFile={this.onSelectFile}
|
||||
foldersType={foldersType}
|
||||
onClickSave={this.onClickSave}
|
||||
filesList={filesList}
|
||||
hasNextPage={hasNextPage}
|
||||
isNextPageLoading={isNextPageLoading}
|
||||
loadNextPage={this.loadNextPage}
|
||||
selectedFolder={selectedFolder}
|
||||
withoutProvider={withoutProvider}
|
||||
headerName={headerName}
|
||||
loadingText={loadingText}
|
||||
selectedFile={selectedFile}
|
||||
folderId={folderId}
|
||||
passedId={passedId}
|
||||
header={header}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
SelectFileDialogBody.propTypes = {
|
||||
onClose: PropTypes.func.isRequired,
|
||||
isPanelVisible: PropTypes.bool.isRequired,
|
||||
onSelectFile: PropTypes.func.isRequired,
|
||||
foldersType: PropTypes.oneOf([
|
||||
"common",
|
||||
"third-party",
|
||||
"exceptSortedByTags",
|
||||
"exceptTrashFolder",
|
||||
]),
|
||||
folderId: PropTypes.string,
|
||||
withoutProvider: PropTypes.bool,
|
||||
headerName: PropTypes.string,
|
||||
zIndex: PropTypes.number,
|
||||
};
|
||||
|
||||
SelectFileDialogBody.defaultProps = {
|
||||
folderId: "",
|
||||
header: "",
|
||||
withoutProvider: false,
|
||||
zIndex: 310,
|
||||
};
|
||||
|
||||
const SelectFileDialogWrapper = inject(
|
||||
({
|
||||
filesStore,
|
||||
selectedFilesStore,
|
||||
treeFoldersStore,
|
||||
selectedFolderStore,
|
||||
}) => {
|
||||
const {
|
||||
folderId: storeFolderId,
|
||||
fileInfo,
|
||||
setFolderId,
|
||||
setFile,
|
||||
} = selectedFilesStore;
|
||||
|
||||
const { setSelectedNode, setExpandedPanelKeys } = treeFoldersStore;
|
||||
const { filter } = filesStore;
|
||||
const {
|
||||
setSelectedFolder,
|
||||
toDefault: setDefaultSelectedFolder,
|
||||
} = selectedFolderStore;
|
||||
return {
|
||||
storeFolderId,
|
||||
fileInfo,
|
||||
setFile,
|
||||
setFolderId,
|
||||
setSelectedFolder,
|
||||
setSelectedNode,
|
||||
filter,
|
||||
setDefaultSelectedFolder,
|
||||
setExpandedPanelKeys,
|
||||
};
|
||||
}
|
||||
)(
|
||||
observer(
|
||||
withTranslation(["SelectFile", "Common", "Translations"])(
|
||||
SelectFileDialogBody
|
||||
)
|
||||
)
|
||||
);
|
||||
class SelectFileDialog extends React.Component {
|
||||
render() {
|
||||
return (
|
||||
<MobxProvider auth={authStore} {...stores}>
|
||||
<I18nextProvider i18n={i18n}>
|
||||
<SelectFileDialogWrapper {...this.props} />
|
||||
</I18nextProvider>
|
||||
</MobxProvider>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default SelectFileDialog;
|
@ -0,0 +1,10 @@
|
||||
import styled from "styled-components";
|
||||
|
||||
const StyledComponent = styled.div`
|
||||
.file-input {
|
||||
margin: 16px 0;
|
||||
width: 100%;
|
||||
max-width: 820px;
|
||||
}
|
||||
`;
|
||||
export default StyledComponent;
|
@ -0,0 +1,111 @@
|
||||
import React from "react";
|
||||
import { Provider as MobxProvider } from "mobx-react";
|
||||
import PropTypes from "prop-types";
|
||||
|
||||
import stores from "../../../store/index";
|
||||
import SelectFileDialog from "../SelectFileDialog";
|
||||
import StyledComponent from "./StyledSelectFileInput";
|
||||
import SimpleFileInput from "../../SimpleFileInput";
|
||||
|
||||
class SelectFileInputBody extends React.PureComponent {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
fileName: "",
|
||||
};
|
||||
}
|
||||
|
||||
onSetFileName = (fileName) => {
|
||||
this.setState({
|
||||
fileName: fileName,
|
||||
});
|
||||
};
|
||||
render() {
|
||||
const {
|
||||
name,
|
||||
onClickInput,
|
||||
isPanelVisible,
|
||||
withoutProvider,
|
||||
onClose,
|
||||
isError,
|
||||
isDisabled,
|
||||
foldersType,
|
||||
withSubfolders,
|
||||
onSelectFile,
|
||||
folderId,
|
||||
headerName,
|
||||
isImageOnly,
|
||||
isArchiveOnly,
|
||||
isDocumentsOnly,
|
||||
searchParam,
|
||||
isPresentationOnly,
|
||||
isTablesOnly,
|
||||
isMediaOnly,
|
||||
loadingLabel,
|
||||
header,
|
||||
zIndex,
|
||||
} = this.props;
|
||||
const { fileName } = this.state;
|
||||
|
||||
return (
|
||||
<StyledComponent>
|
||||
<SimpleFileInput
|
||||
name={name}
|
||||
className="file-input"
|
||||
textField={fileName}
|
||||
isDisabled={isDisabled}
|
||||
isError={isError}
|
||||
onClickInput={onClickInput}
|
||||
/>
|
||||
|
||||
{isPanelVisible && (
|
||||
<SelectFileDialog
|
||||
zIndex={zIndex}
|
||||
onClose={onClose}
|
||||
isPanelVisible={isPanelVisible}
|
||||
foldersType={foldersType}
|
||||
onSetFileName={this.onSetFileName}
|
||||
withoutProvider={withoutProvider}
|
||||
withSubfolders={withSubfolders}
|
||||
onSelectFile={onSelectFile}
|
||||
folderId={folderId}
|
||||
headerName={headerName}
|
||||
searchParam={searchParam}
|
||||
isImageOnly={isImageOnly}
|
||||
isArchiveOnly={isArchiveOnly}
|
||||
isDocumentsOnly={isDocumentsOnly}
|
||||
isPresentation={isPresentationOnly}
|
||||
isTables={isTablesOnly}
|
||||
isMediaOnly={isMediaOnly}
|
||||
loadingLabel={loadingLabel}
|
||||
header={header}
|
||||
/>
|
||||
)}
|
||||
</StyledComponent>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
SelectFileInputBody.propTypes = {
|
||||
onClickInput: PropTypes.func.isRequired,
|
||||
onClose: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
SelectFileInputBody.defaultProps = {
|
||||
withoutProvider: false,
|
||||
isDisabled: false,
|
||||
zIndex: 310,
|
||||
};
|
||||
|
||||
class SelectFileInput extends React.Component {
|
||||
render() {
|
||||
return (
|
||||
<MobxProvider {...stores}>
|
||||
<SelectFileInputBody {...this.props} />
|
||||
</MobxProvider>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default SelectFileInput;
|
@ -0,0 +1,109 @@
|
||||
import React from "react";
|
||||
|
||||
import IconButton from "@appserver/components/icon-button";
|
||||
import FolderTreeBody from "../../FolderTreeBody";
|
||||
import { StyledAsidePanel, StyledSelectFolderPanel } from "../StyledPanels";
|
||||
import Button from "@appserver/components/button";
|
||||
import ModalDialog from "@appserver/components/modal-dialog";
|
||||
|
||||
const DISPLAY_TYPE = "aside";
|
||||
const SelectFolderDialogAsideView = ({
|
||||
t,
|
||||
isPanelVisible,
|
||||
zIndex,
|
||||
onClose,
|
||||
withoutProvider,
|
||||
isNeedArrowIcon,
|
||||
asideHeightContent,
|
||||
isAvailable,
|
||||
certainFolders,
|
||||
folderId,
|
||||
isLoadingData,
|
||||
folderList,
|
||||
onSelect,
|
||||
footer,
|
||||
showButtons,
|
||||
onSave,
|
||||
headerName,
|
||||
header,
|
||||
canCreate,
|
||||
isLoading,
|
||||
}) => {
|
||||
return (
|
||||
<StyledAsidePanel visible={isPanelVisible}>
|
||||
<ModalDialog
|
||||
visible={isPanelVisible}
|
||||
zIndex={zIndex}
|
||||
contentHeight="100%"
|
||||
contentPaddingBottom={!footer && !showButtons ? "0px" : "80px"}
|
||||
onClose={onClose}
|
||||
removeScroll
|
||||
displayType="aside"
|
||||
>
|
||||
<ModalDialog.Header>
|
||||
<StyledSelectFolderPanel>
|
||||
<div className="select-folder-dialog_header">
|
||||
{isNeedArrowIcon && (
|
||||
<IconButton
|
||||
className="select-folder-dialog_header-icon"
|
||||
size="16"
|
||||
iconName="/static/images/arrow.path.react.svg"
|
||||
onClick={onClose}
|
||||
color="#A3A9AE"
|
||||
/>
|
||||
)}
|
||||
{headerName ? headerName : t("Translations:SelectFolder")}
|
||||
</div>
|
||||
</StyledSelectFolderPanel>
|
||||
</ModalDialog.Header>
|
||||
<ModalDialog.Body>
|
||||
<StyledSelectFolderPanel
|
||||
displayType={DISPLAY_TYPE}
|
||||
showButtons={showButtons}
|
||||
>
|
||||
<div className="select-folder-dialog_aside_body">
|
||||
<div>{header} </div>
|
||||
|
||||
<FolderTreeBody
|
||||
isLoadingData={isLoadingData}
|
||||
folderList={folderList}
|
||||
onSelect={onSelect}
|
||||
withoutProvider={withoutProvider}
|
||||
certainFolders={certainFolders}
|
||||
isAvailable={isAvailable}
|
||||
selectedKeys={[folderId]}
|
||||
heightContent={asideHeightContent}
|
||||
displayType={DISPLAY_TYPE}
|
||||
/>
|
||||
</div>
|
||||
</StyledSelectFolderPanel>
|
||||
</ModalDialog.Body>
|
||||
<ModalDialog.Footer>
|
||||
<StyledSelectFolderPanel>
|
||||
{footer}
|
||||
{showButtons && (
|
||||
<div className="select-folder-dialog-modal_buttons">
|
||||
<Button
|
||||
className="select-folder-dialog-buttons-save"
|
||||
primary
|
||||
size="big"
|
||||
label={t("Common:SaveButton")}
|
||||
onClick={onSave}
|
||||
isDisabled={isLoadingData || !isAvailable || !canCreate}
|
||||
/>
|
||||
<Button
|
||||
primary
|
||||
size="big"
|
||||
label={t("Common:CloseButton")}
|
||||
onClick={onClose}
|
||||
isDisabled={isLoadingData || isLoading}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</StyledSelectFolderPanel>
|
||||
</ModalDialog.Footer>
|
||||
</ModalDialog>
|
||||
</StyledAsidePanel>
|
||||
);
|
||||
};
|
||||
export default SelectFolderDialogAsideView;
|
@ -0,0 +1,86 @@
|
||||
import React, { useEffect } from "react";
|
||||
|
||||
import ModalDialog from "@appserver/components/modal-dialog";
|
||||
import { StyledAsidePanel, StyledSelectFolderPanel } from "../StyledPanels";
|
||||
import FolderTreeBody from "../../FolderTreeBody";
|
||||
import Button from "@appserver/components/button";
|
||||
|
||||
const SelectFolderDialogModalView = ({
|
||||
t,
|
||||
isPanelVisible,
|
||||
zIndex,
|
||||
onClose,
|
||||
withoutProvider,
|
||||
isNeedArrowIcon,
|
||||
modalHeightContent,
|
||||
isAvailable,
|
||||
certainFolders,
|
||||
folderId,
|
||||
isLoadingData,
|
||||
folderList,
|
||||
onSelect,
|
||||
header,
|
||||
footer,
|
||||
headerName,
|
||||
showButtons,
|
||||
onSave,
|
||||
canCreate,
|
||||
isLoading,
|
||||
}) => {
|
||||
return (
|
||||
<StyledAsidePanel visible={isPanelVisible}>
|
||||
<ModalDialog
|
||||
visible={isPanelVisible}
|
||||
zIndex={zIndex}
|
||||
onClose={onClose}
|
||||
displayType="modal"
|
||||
{...(!header && !footer && !showButtons && { contentHeight: "416px" })}
|
||||
>
|
||||
<ModalDialog.Header>
|
||||
{headerName ? headerName : t("Translations:SelectFolder")}
|
||||
</ModalDialog.Header>
|
||||
|
||||
<ModalDialog.Body>
|
||||
<StyledSelectFolderPanel isNeedArrowIcon={isNeedArrowIcon}>
|
||||
<div className="select-folder-modal-dialog-header">{header} </div>
|
||||
<FolderTreeBody
|
||||
isLoadingData={isLoadingData}
|
||||
folderList={folderList}
|
||||
onSelect={onSelect}
|
||||
withoutProvider={withoutProvider}
|
||||
certainFolders={certainFolders}
|
||||
isAvailable={isAvailable}
|
||||
selectedKeys={[folderId]}
|
||||
heightContent={modalHeightContent}
|
||||
/>
|
||||
</StyledSelectFolderPanel>
|
||||
</ModalDialog.Body>
|
||||
<ModalDialog.Footer>
|
||||
<StyledSelectFolderPanel>
|
||||
{footer}
|
||||
{showButtons && (
|
||||
<div className="select-folder-dialog-modal_buttons">
|
||||
<Button
|
||||
className="select-folder-dialog-buttons-save"
|
||||
primary
|
||||
size="medium"
|
||||
label={t("Common:SaveButton")}
|
||||
onClick={onSave}
|
||||
isDisabled={isLoadingData || !isAvailable || !canCreate}
|
||||
/>
|
||||
<Button
|
||||
primary
|
||||
size="medium"
|
||||
label={t("Common:CloseButton")}
|
||||
onClick={onClose}
|
||||
isDisabled={isLoadingData || isLoading}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</StyledSelectFolderPanel>
|
||||
</ModalDialog.Footer>
|
||||
</ModalDialog>
|
||||
</StyledAsidePanel>
|
||||
);
|
||||
};
|
||||
export default SelectFolderDialogModalView;
|
@ -0,0 +1,35 @@
|
||||
import i18n from "i18next";
|
||||
import Backend from "i18next-http-backend";
|
||||
import { LANGUAGE } from "@appserver/common/constants";
|
||||
import config from "../../../../package.json";
|
||||
import { loadLanguagePath } from "@appserver/common/utils";
|
||||
|
||||
const newInstance = i18n.createInstance();
|
||||
|
||||
newInstance.use(Backend).init({
|
||||
lng: localStorage.getItem(LANGUAGE) || "en",
|
||||
fallbackLng: "en",
|
||||
load: "all",
|
||||
//debug: true,
|
||||
|
||||
interpolation: {
|
||||
escapeValue: false, // not needed for react as it escapes by default
|
||||
format: function (value, format) {
|
||||
if (format === "lowercase") return value.toLowerCase();
|
||||
return value;
|
||||
},
|
||||
},
|
||||
|
||||
backend: {
|
||||
loadPath: loadLanguagePath(config.homepage),
|
||||
},
|
||||
|
||||
ns: ["SelectFolder", "Common", "Translations"],
|
||||
defaultNS: "SelectFolder",
|
||||
|
||||
react: {
|
||||
useSuspense: false,
|
||||
},
|
||||
});
|
||||
|
||||
export default newInstance;
|
@ -0,0 +1,636 @@
|
||||
import React from "react";
|
||||
import { inject, observer, Provider as MobxProvider } from "mobx-react";
|
||||
import { I18nextProvider } from "react-i18next";
|
||||
import { withTranslation } from "react-i18next";
|
||||
import PropTypes from "prop-types";
|
||||
import throttle from "lodash/throttle";
|
||||
|
||||
import { getCommonThirdPartyList } from "@appserver/common/api/settings";
|
||||
import {
|
||||
getCommonFolderList,
|
||||
getFolder,
|
||||
getFolderPath,
|
||||
getFoldersTree,
|
||||
} from "@appserver/common/api/files";
|
||||
|
||||
import SelectFolderInput from "../SelectFolderInput";
|
||||
import i18n from "./i18n";
|
||||
import SelectFolderDialogAsideView from "./AsideView";
|
||||
import SelectFolderDialogModalView from "./ModalView";
|
||||
import stores from "../../../store/index";
|
||||
import utils from "@appserver/components/utils";
|
||||
import { FolderType } from "@appserver/common/constants";
|
||||
import { isArrayEqual } from "@appserver/components/utils/array";
|
||||
import store from "studio/store";
|
||||
import toastr from "studio/toastr";
|
||||
|
||||
const { auth: authStore } = store;
|
||||
|
||||
const { desktop } = utils.device;
|
||||
|
||||
let pathName = "";
|
||||
let folderList;
|
||||
|
||||
const exceptSortedByTagsFolders = [
|
||||
FolderType.Recent,
|
||||
FolderType.TRASH,
|
||||
FolderType.Favorites,
|
||||
];
|
||||
|
||||
const exceptTrashFolder = [FolderType.TRASH];
|
||||
class SelectFolderModalDialog extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
const { isSetFolderImmediately, id, displayType } = this.props;
|
||||
const isNeedFolder = id ? true : isSetFolderImmediately;
|
||||
this.state = {
|
||||
isLoadingData: false,
|
||||
isLoading: false,
|
||||
isAvailable: true,
|
||||
certainFolders: true,
|
||||
folderId: "",
|
||||
displayType: displayType || this.getDisplayType(),
|
||||
isSetFolderImmediately: isNeedFolder,
|
||||
canCreate: true,
|
||||
};
|
||||
this.throttledResize = throttle(this.setDisplayType, 300);
|
||||
this.folderTitle = "";
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
const { onSetLoadingData, onSetLoadingInput, displayType } = this.props;
|
||||
|
||||
authStore.init(true); // it will work if authStore is not initialized
|
||||
|
||||
!displayType && window.addEventListener("resize", this.throttledResize);
|
||||
this.setState({ isLoadingData: true }, function () {
|
||||
onSetLoadingData && onSetLoadingData(true);
|
||||
onSetLoadingInput && onSetLoadingInput(true);
|
||||
this.trySwitch();
|
||||
});
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps) {
|
||||
const { storeFolderId, canCreate, showButtons } = this.props;
|
||||
if (showButtons && storeFolderId !== prevProps.storeFolderId) {
|
||||
this.setState({
|
||||
canCreate: canCreate,
|
||||
isLoading: false,
|
||||
});
|
||||
}
|
||||
}
|
||||
trySwitch = async () => {
|
||||
const {
|
||||
folderPath,
|
||||
onSelectFolder,
|
||||
onSetBaseFolderPath,
|
||||
foldersType,
|
||||
id,
|
||||
selectedFolderId,
|
||||
} = this.props;
|
||||
switch (foldersType) {
|
||||
case "exceptSortedByTags":
|
||||
try {
|
||||
const foldersTree = await getFoldersTree();
|
||||
folderList = SelectFolderDialog.convertFolders(
|
||||
foldersTree,
|
||||
exceptSortedByTagsFolders
|
||||
);
|
||||
this.setBaseSettings();
|
||||
} catch (err) {
|
||||
console.error("error", err);
|
||||
this.loadersCompletes();
|
||||
}
|
||||
break;
|
||||
case "exceptTrashFolder":
|
||||
try {
|
||||
const foldersTree = await getFoldersTree();
|
||||
folderList = SelectFolderDialog.convertFolders(
|
||||
foldersTree,
|
||||
exceptTrashFolder
|
||||
);
|
||||
this.setBaseSettings();
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
this.loadersCompletes();
|
||||
}
|
||||
break;
|
||||
case "common":
|
||||
try {
|
||||
folderList = await SelectFolderDialog.getCommonFolders();
|
||||
folderPath.length === 0 &&
|
||||
!selectedFolderId &&
|
||||
onSelectFolder &&
|
||||
onSelectFolder(`${id ? id : folderList[0].id}`);
|
||||
|
||||
this.setState({
|
||||
folderId: `${
|
||||
selectedFolderId ? selectedFolderId : id ? id : folderList[0].id
|
||||
}`,
|
||||
});
|
||||
|
||||
!id &&
|
||||
!selectedFolderId &&
|
||||
onSetBaseFolderPath &&
|
||||
onSetBaseFolderPath(folderList[0].title);
|
||||
|
||||
this.setFolderInfo();
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
this.loadersCompletes();
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case "third-party":
|
||||
try {
|
||||
folderList = await SelectFolderDialog.getCommonThirdPartyList();
|
||||
|
||||
this.setBaseSettings();
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
|
||||
this.loadersCompletes();
|
||||
}
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
loadersCompletes = () => {
|
||||
const {
|
||||
onSetLoadingData,
|
||||
|
||||
onSetLoadingInput,
|
||||
} = this.props;
|
||||
|
||||
onSetLoadingData && onSetLoadingData(false);
|
||||
onSetLoadingInput && onSetLoadingInput(false);
|
||||
|
||||
this.setState({
|
||||
isLoadingData: false,
|
||||
});
|
||||
};
|
||||
setBaseSettings = async () => {
|
||||
const { isSetFolderImmediately } = this.state;
|
||||
const {
|
||||
onSelectFolder,
|
||||
onSetBaseFolderPath,
|
||||
id,
|
||||
selectedFolderId,
|
||||
showButtons,
|
||||
} = this.props;
|
||||
|
||||
if (folderList.length === 0) {
|
||||
this.setState({ isAvailable: false });
|
||||
onSelectFolder(null);
|
||||
this.loadersCompletes();
|
||||
return;
|
||||
}
|
||||
|
||||
!id && showButtons && this.setFolderToTree(folderList[0].id);
|
||||
|
||||
isSetFolderImmediately &&
|
||||
!selectedFolderId &&
|
||||
onSelectFolder &&
|
||||
onSelectFolder(
|
||||
`${selectedFolderId ? selectedFolderId : id ? id : folderList[0].id}`
|
||||
);
|
||||
|
||||
isSetFolderImmediately &&
|
||||
this.setState({
|
||||
folderId: `${
|
||||
selectedFolderId ? selectedFolderId : id ? id : folderList[0].id
|
||||
}`,
|
||||
});
|
||||
|
||||
if (onSetBaseFolderPath) {
|
||||
try {
|
||||
this.folderTitle = await SelectFolderDialog.getFolderPath(
|
||||
id ? id : folderList[0].id
|
||||
);
|
||||
|
||||
!id &&
|
||||
!selectedFolderId &&
|
||||
isSetFolderImmediately &&
|
||||
onSetBaseFolderPath(this.folderTitle);
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
}
|
||||
}
|
||||
|
||||
this.setFolderInfo();
|
||||
};
|
||||
|
||||
setFolderInfo = () => {
|
||||
const {
|
||||
id,
|
||||
onSetFileName,
|
||||
fileName,
|
||||
selectedFolderId,
|
||||
dialogWithFiles,
|
||||
onSetBaseFolderPath,
|
||||
} = this.props;
|
||||
|
||||
fileName && onSetFileName && onSetFileName(fileName);
|
||||
|
||||
if (!id && !selectedFolderId) {
|
||||
this.loadersCompletes();
|
||||
return;
|
||||
}
|
||||
|
||||
if (selectedFolderId) {
|
||||
onSetBaseFolderPath
|
||||
? this.setBaseFolderPath(selectedFolderId)
|
||||
: this.loadersCompletes();
|
||||
}
|
||||
|
||||
if (id && !selectedFolderId) {
|
||||
if (!dialogWithFiles) this.setSelectedFolder(id);
|
||||
else {
|
||||
this.setBaseFolderPath(id);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
setBaseFolderPath = () => {
|
||||
const { onSetBaseFolderPath, selectedFolderId } = this.props;
|
||||
|
||||
SelectFolderDialog.getFolderPath(selectedFolderId)
|
||||
.then((folderPath) => (this.folderTitle = folderPath))
|
||||
.then(() => onSetBaseFolderPath(this.folderTitle))
|
||||
.catch((error) => console.log("error", error))
|
||||
.finally(() => {
|
||||
this.loadersCompletes();
|
||||
});
|
||||
};
|
||||
setSelectedFolder = async (id) => {
|
||||
const { onSetBaseFolderPath } = this.props;
|
||||
|
||||
let folder,
|
||||
folderPath,
|
||||
requests = [];
|
||||
|
||||
requests.push(getFolder(id));
|
||||
|
||||
if (onSetBaseFolderPath) {
|
||||
requests.push(getFolderPath(id));
|
||||
}
|
||||
|
||||
try {
|
||||
[folder, folderPath] = await Promise.all(requests);
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
|
||||
folder && this.setFolderObjectToTree(id, folder);
|
||||
|
||||
if (onSetBaseFolderPath && folderPath) {
|
||||
this.folderTitle = SelectFolderInput.setFullFolderPath(folderPath);
|
||||
onSetBaseFolderPath(this.folderTitle);
|
||||
}
|
||||
|
||||
this.loadersCompletes();
|
||||
};
|
||||
|
||||
setFolderToTree = (id) => {
|
||||
getFolder(id)
|
||||
.then((data) => {
|
||||
this.setFolderObjectToTree(id, data);
|
||||
})
|
||||
.catch((error) => console.log("error", error));
|
||||
};
|
||||
|
||||
setFolderObjectToTree = (id, data) => {
|
||||
const { setSelectedNode, setSelectedFolder } = this.props;
|
||||
|
||||
setSelectedNode([id + ""]);
|
||||
|
||||
const newPathParts = SelectFolderDialog.convertPathParts(data.pathParts);
|
||||
setSelectedFolder({
|
||||
folders: data.folders,
|
||||
...data.current,
|
||||
pathParts: newPathParts,
|
||||
...{ new: data.new },
|
||||
});
|
||||
};
|
||||
|
||||
componentWillUnmount() {
|
||||
const {
|
||||
setExpandedPanelKeys,
|
||||
setDefaultSelectedFolder,
|
||||
|
||||
resetTreeFolders,
|
||||
|
||||
dialogWithFiles,
|
||||
} = this.props;
|
||||
if (this.throttledResize) {
|
||||
this.throttledResize && this.throttledResize.cancel();
|
||||
window.removeEventListener("resize", this.throttledResize);
|
||||
}
|
||||
|
||||
if (resetTreeFolders && !dialogWithFiles) {
|
||||
setExpandedPanelKeys(null);
|
||||
setDefaultSelectedFolder();
|
||||
}
|
||||
}
|
||||
getDisplayType = () => {
|
||||
const displayType =
|
||||
window.innerWidth < desktop.match(/\d+/)[0] ? "aside" : "modal";
|
||||
|
||||
return displayType;
|
||||
};
|
||||
|
||||
setDisplayType = () => {
|
||||
const displayType = this.getDisplayType();
|
||||
|
||||
this.setState({ displayType: displayType });
|
||||
};
|
||||
|
||||
onSelect = async (folder) => {
|
||||
const { onSelectFolder, onClose, showButtons, onSetFullPath } = this.props;
|
||||
const { folderId } = this.state;
|
||||
|
||||
let requests = [];
|
||||
|
||||
if (isArrayEqual([folder[0]], [folderId])) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.setState({
|
||||
folderId: folder[0],
|
||||
});
|
||||
|
||||
let folderInfo, folderPath;
|
||||
|
||||
if (showButtons) {
|
||||
this.setState({
|
||||
isLoading: true,
|
||||
canCreate: false,
|
||||
});
|
||||
}
|
||||
|
||||
try {
|
||||
if (showButtons && onSetFullPath) {
|
||||
requests.push(getFolder(folder[0]), getFolderPath(folder));
|
||||
|
||||
[folderInfo, folderPath] = await Promise.all(requests);
|
||||
} else {
|
||||
showButtons
|
||||
? (folderInfo = await getFolder(folder[0]))
|
||||
: (folderPath = await getFolderPath(folder));
|
||||
}
|
||||
|
||||
if (folderInfo) {
|
||||
this.setFolderObjectToTree(folder[0], folderInfo);
|
||||
}
|
||||
|
||||
if (folderPath) {
|
||||
pathName = SelectFolderInput.setFullFolderPath(folderPath);
|
||||
onSetFullPath && onSetFullPath(pathName);
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
toastr.error();
|
||||
|
||||
if (showButtons) {
|
||||
this.setState({
|
||||
isLoading: false,
|
||||
canCreate: true,
|
||||
});
|
||||
|
||||
onClose && onClose();
|
||||
}
|
||||
}
|
||||
|
||||
onSelectFolder && onSelectFolder(folder[0]);
|
||||
!showButtons && onClose && onClose();
|
||||
|
||||
this.loadersCompletes();
|
||||
};
|
||||
onSave = (e) => {
|
||||
const { onClose, onSave } = this.props;
|
||||
const { folderId } = this.state;
|
||||
|
||||
onSave && onSave(e, folderId);
|
||||
onClose && onClose();
|
||||
};
|
||||
render() {
|
||||
const {
|
||||
t,
|
||||
isPanelVisible,
|
||||
zIndex,
|
||||
onClose,
|
||||
withoutProvider,
|
||||
isNeedArrowIcon,
|
||||
modalHeightContent,
|
||||
asideHeightContent,
|
||||
header,
|
||||
headerName,
|
||||
footer,
|
||||
showButtons,
|
||||
} = this.props;
|
||||
const {
|
||||
isAvailable,
|
||||
certainFolders,
|
||||
folderId,
|
||||
displayType,
|
||||
isLoadingData,
|
||||
canCreate,
|
||||
isLoading,
|
||||
} = this.state;
|
||||
|
||||
return displayType === "aside" ? (
|
||||
<SelectFolderDialogAsideView
|
||||
t={t}
|
||||
isPanelVisible={isPanelVisible}
|
||||
zIndex={zIndex}
|
||||
onClose={onClose}
|
||||
withoutProvider={withoutProvider}
|
||||
isNeedArrowIcon={isNeedArrowIcon}
|
||||
asideHeightContent={asideHeightContent}
|
||||
isAvailable={isAvailable}
|
||||
certainFolders={certainFolders}
|
||||
folderId={folderId}
|
||||
folderList={folderList}
|
||||
onSelect={this.onSelect}
|
||||
onSave={this.onSave}
|
||||
header={header}
|
||||
headerName={headerName}
|
||||
footer={footer}
|
||||
showButtons={showButtons}
|
||||
isLoadingData={isLoadingData}
|
||||
canCreate={canCreate}
|
||||
isLoading={isLoading}
|
||||
/>
|
||||
) : (
|
||||
<SelectFolderDialogModalView
|
||||
t={t}
|
||||
isPanelVisible={isPanelVisible}
|
||||
zIndex={zIndex}
|
||||
onClose={onClose}
|
||||
withoutProvider={withoutProvider}
|
||||
modalHeightContent={modalHeightContent}
|
||||
isAvailable={isAvailable}
|
||||
certainFolders={certainFolders}
|
||||
folderId={folderId}
|
||||
folderList={folderList}
|
||||
onSelect={this.onSelect}
|
||||
onSave={this.onSave}
|
||||
header={header}
|
||||
headerName={headerName}
|
||||
footer={footer}
|
||||
showButtons={showButtons}
|
||||
canCreate={canCreate}
|
||||
isLoadingData={isLoadingData}
|
||||
isLoading={isLoading}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
SelectFolderModalDialog.propTypes = {
|
||||
onSelectFolder: PropTypes.func,
|
||||
onClose: PropTypes.func.isRequired,
|
||||
isPanelVisible: PropTypes.bool.isRequired,
|
||||
foldersType: PropTypes.oneOf([
|
||||
"common",
|
||||
"third-party",
|
||||
"exceptSortedByTags",
|
||||
"exceptTrashFolder",
|
||||
]),
|
||||
displayType: PropTypes.oneOf(["aside", "modal"]),
|
||||
id: PropTypes.string,
|
||||
zIndex: PropTypes.number,
|
||||
withoutProvider: PropTypes.bool,
|
||||
isNeedArrowIcon: PropTypes.bool,
|
||||
dialogWithFiles: PropTypes.bool,
|
||||
showButtons: PropTypes.bool,
|
||||
modalHeightContent: PropTypes.string,
|
||||
asideHeightContent: PropTypes.string,
|
||||
};
|
||||
SelectFolderModalDialog.defaultProps = {
|
||||
isSetFolderImmediately: false,
|
||||
dialogWithFiles: false,
|
||||
isNeedArrowIcon: false,
|
||||
id: "",
|
||||
modalHeightContent: "325px",
|
||||
asideHeightContent: "100%",
|
||||
zIndex: 310,
|
||||
withoutProvider: false,
|
||||
folderPath: "",
|
||||
};
|
||||
|
||||
const SelectFolderDialogWrapper = inject(
|
||||
({
|
||||
treeFoldersStore,
|
||||
selectedFolderStore,
|
||||
selectedFilesStore,
|
||||
filesStore,
|
||||
}) => {
|
||||
const { setSelectedNode, setExpandedPanelKeys } = treeFoldersStore;
|
||||
const { canCreate } = filesStore;
|
||||
const {
|
||||
setSelectedFolder,
|
||||
id,
|
||||
toDefault: setDefaultSelectedFolder,
|
||||
} = selectedFolderStore;
|
||||
const { setFolderId, setFile } = selectedFilesStore;
|
||||
return {
|
||||
setSelectedFolder,
|
||||
setSelectedNode,
|
||||
canCreate,
|
||||
storeFolderId: id,
|
||||
setExpandedPanelKeys,
|
||||
setDefaultSelectedFolder,
|
||||
setFolderId,
|
||||
setFile,
|
||||
};
|
||||
}
|
||||
)(
|
||||
observer(
|
||||
withTranslation(["SelectFolder", "Common", "Translations"])(
|
||||
SelectFolderModalDialog
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
class SelectFolderDialog extends React.Component {
|
||||
static getCommonThirdPartyList = async () => {
|
||||
const commonThirdPartyArray = await getCommonThirdPartyList();
|
||||
|
||||
commonThirdPartyArray.map((currentValue, index) => {
|
||||
commonThirdPartyArray[index].key = `0-${index}`;
|
||||
});
|
||||
|
||||
return commonThirdPartyArray;
|
||||
};
|
||||
|
||||
static getCommonFolders = async () => {
|
||||
const commonFolders = await getCommonFolderList();
|
||||
|
||||
const convertedData = {
|
||||
id: commonFolders.current.id,
|
||||
key: 0 - 1,
|
||||
parentId: commonFolders.current.parentId,
|
||||
title: commonFolders.current.title,
|
||||
rootFolderType: +commonFolders.current.rootFolderType,
|
||||
rootFolderName: "@common",
|
||||
folders: commonFolders.folders.map((folder) => {
|
||||
return {
|
||||
id: folder.id,
|
||||
title: folder.title,
|
||||
access: folder.access,
|
||||
foldersCount: folder.foldersCount,
|
||||
rootFolderType: folder.rootFolderType,
|
||||
providerKey: folder.providerKey,
|
||||
newItems: folder.new,
|
||||
};
|
||||
}),
|
||||
pathParts: commonFolders.pathParts,
|
||||
foldersCount: commonFolders.current.foldersCount,
|
||||
newItems: commonFolders.new,
|
||||
};
|
||||
|
||||
return [convertedData];
|
||||
};
|
||||
|
||||
static getFolderPath = async (folderId) => {
|
||||
const foldersArray = await getFolderPath(folderId);
|
||||
const convertFoldersArray = SelectFolderInput.setFullFolderPath(
|
||||
foldersArray
|
||||
);
|
||||
|
||||
return convertFoldersArray;
|
||||
};
|
||||
static convertPathParts = (pathParts) => {
|
||||
let newPathParts = [];
|
||||
for (let i = 0; i < pathParts.length - 1; i++) {
|
||||
if (typeof pathParts[i] === "number") {
|
||||
newPathParts.push(String(pathParts[i]));
|
||||
} else {
|
||||
newPathParts.push(pathParts[i]);
|
||||
}
|
||||
}
|
||||
return newPathParts;
|
||||
};
|
||||
static convertFolders = (folders, arrayOfExceptions) => {
|
||||
let newArray = [];
|
||||
|
||||
for (let i = 0; i < folders.length; i++) {
|
||||
!arrayOfExceptions.includes(folders[i].rootFolderType) &&
|
||||
newArray.push(folders[i]);
|
||||
}
|
||||
return newArray;
|
||||
};
|
||||
render() {
|
||||
return (
|
||||
<MobxProvider auth={authStore} {...stores}>
|
||||
<I18nextProvider i18n={i18n}>
|
||||
<SelectFolderDialogWrapper {...this.props} />
|
||||
</I18nextProvider>
|
||||
</MobxProvider>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default SelectFolderDialog;
|
@ -0,0 +1,21 @@
|
||||
import styled from "styled-components";
|
||||
|
||||
const StyledComponent = styled.div`
|
||||
.input-with-folder-path {
|
||||
margin: 16px 0;
|
||||
|
||||
width: 100%;
|
||||
max-width: 820px;
|
||||
}
|
||||
|
||||
.panel-loader-wrapper {
|
||||
margin-top: 8px;
|
||||
padding-left: 32px;
|
||||
}
|
||||
.panel-loader {
|
||||
display: inline;
|
||||
margin-right: 10px;
|
||||
}
|
||||
`;
|
||||
|
||||
export default StyledComponent;
|
@ -0,0 +1,186 @@
|
||||
import React from "react";
|
||||
import { Provider as MobxProvider } from "mobx-react";
|
||||
|
||||
import PropTypes from "prop-types";
|
||||
|
||||
import stores from "../../../store/index";
|
||||
import SelectFolderDialog from "../SelectFolderDialog/index";
|
||||
import StyledComponent from "./StyledSelectFolderInput";
|
||||
import SimpleFileInput from "../../SimpleFileInput";
|
||||
|
||||
let path = "";
|
||||
|
||||
class SelectFolderInputBody extends React.PureComponent {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
isLoading: false,
|
||||
baseFolderPath: "",
|
||||
fullFolderPath: "",
|
||||
fullFolderPathDefault: "",
|
||||
};
|
||||
}
|
||||
componentDidMount() {
|
||||
const { folderPath } = this.props;
|
||||
|
||||
if (folderPath.length !== 0) {
|
||||
this.setState({
|
||||
fullFolderPath: folderPath,
|
||||
fullFolderPathDefault: folderPath,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps) {
|
||||
const { isSetDefaultFolderPath, folderPath } = this.props;
|
||||
|
||||
if (
|
||||
isSetDefaultFolderPath &&
|
||||
isSetDefaultFolderPath !== prevProps.isSetDefaultFolderPath
|
||||
) {
|
||||
this.setState({
|
||||
fullFolderPath: this.state.fullFolderPathDefault,
|
||||
});
|
||||
}
|
||||
if (folderPath !== prevProps.folderPath) {
|
||||
this.setState({
|
||||
fullFolderPath: folderPath,
|
||||
fullFolderPathDefault: folderPath,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
onSetFullPath = (pathName) => {
|
||||
this.setState({
|
||||
fullFolderPath: pathName,
|
||||
});
|
||||
};
|
||||
|
||||
onSetBaseFolderPath = (pathName) => {
|
||||
this.setState({
|
||||
baseFolderPath: pathName,
|
||||
});
|
||||
};
|
||||
|
||||
onSetLoadingInput = (loading) => {
|
||||
this.setState({
|
||||
isLoading: loading,
|
||||
});
|
||||
};
|
||||
render() {
|
||||
const {
|
||||
name,
|
||||
onClickInput,
|
||||
isPanelVisible,
|
||||
withoutProvider,
|
||||
onClose,
|
||||
isError,
|
||||
isSavingProcess,
|
||||
isDisabled,
|
||||
onSelectFolder,
|
||||
onSetLoadingData,
|
||||
foldersType,
|
||||
folderPath,
|
||||
isNeedArrowIcon,
|
||||
isSetFolderImmediately,
|
||||
id,
|
||||
selectedFolderId,
|
||||
displayType,
|
||||
dialogWithFiles,
|
||||
modalHeightContent,
|
||||
asideHeightContent,
|
||||
zIndex,
|
||||
showButtons,
|
||||
header,
|
||||
headerName,
|
||||
footer,
|
||||
} = this.props;
|
||||
const { isLoading, baseFolderPath, fullFolderPath } = this.state;
|
||||
|
||||
return (
|
||||
<StyledComponent>
|
||||
<SimpleFileInput
|
||||
name={name}
|
||||
className="input-with-folder-path"
|
||||
textField={fullFolderPath || baseFolderPath}
|
||||
isDisabled={isLoading || isSavingProcess || isDisabled}
|
||||
isError={isError}
|
||||
onClickInput={onClickInput}
|
||||
/>
|
||||
|
||||
<SelectFolderDialog
|
||||
zIndex={zIndex}
|
||||
isPanelVisible={isPanelVisible}
|
||||
onClose={onClose}
|
||||
folderPath={folderPath}
|
||||
onSelectFolder={onSelectFolder}
|
||||
onSetLoadingData={onSetLoadingData}
|
||||
foldersType={foldersType}
|
||||
withoutProvider={withoutProvider}
|
||||
onSetFullPath={this.onSetFullPath}
|
||||
onSetBaseFolderPath={this.onSetBaseFolderPath}
|
||||
onSetLoadingInput={this.onSetLoadingInput}
|
||||
isNeedArrowIcon={isNeedArrowIcon}
|
||||
isSetFolderImmediately={isSetFolderImmediately}
|
||||
id={id}
|
||||
selectedFolderId={selectedFolderId}
|
||||
displayType={displayType}
|
||||
dialogWithFiles={dialogWithFiles}
|
||||
modalHeightContent={modalHeightContent}
|
||||
asideHeightContent={asideHeightContent}
|
||||
showButtons={showButtons}
|
||||
header={header}
|
||||
headerName={headerName}
|
||||
footer={footer}
|
||||
/>
|
||||
</StyledComponent>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
SelectFolderInputBody.propTypes = {
|
||||
onClickInput: PropTypes.func.isRequired,
|
||||
onClose: PropTypes.func.isRequired,
|
||||
onSelectFolder: PropTypes.func.isRequired,
|
||||
onSetLoadingData: PropTypes.func,
|
||||
isPanelVisible: PropTypes.bool.isRequired,
|
||||
name: PropTypes.string,
|
||||
withoutProvider: PropTypes.bool,
|
||||
isError: PropTypes.bool,
|
||||
isSavingProcess: PropTypes.bool,
|
||||
};
|
||||
|
||||
SelectFolderInputBody.defaultProps = {
|
||||
withoutProvider: false,
|
||||
isDisabled: false,
|
||||
isError: false,
|
||||
folderPath: "",
|
||||
};
|
||||
|
||||
class SelectFolderInput extends React.Component {
|
||||
static setFullFolderPath = (foldersArray) => {
|
||||
path = "";
|
||||
if (foldersArray.length > 1) {
|
||||
for (let item of foldersArray) {
|
||||
if (!path) {
|
||||
path = path + `${item.title}`;
|
||||
} else path = path + " " + "/" + " " + `${item.title}`;
|
||||
}
|
||||
} else {
|
||||
for (let item of foldersArray) {
|
||||
path = `${item.title}`;
|
||||
}
|
||||
}
|
||||
return path;
|
||||
};
|
||||
render() {
|
||||
return (
|
||||
<MobxProvider {...stores}>
|
||||
<SelectFolderInputBody {...this.props} />
|
||||
</MobxProvider>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default SelectFolderInput;
|
@ -514,6 +514,258 @@ const StyledLinkRow = styled.div`
|
||||
}
|
||||
`;
|
||||
|
||||
const StyledSelectFolderPanel = styled.div`
|
||||
${(props) =>
|
||||
props.displayType === "aside" &&
|
||||
css`
|
||||
height: 100%;
|
||||
`}
|
||||
.modal-dialog_header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
.select-folder-modal-dialog-header {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
.modal-dialog_header-title {
|
||||
${(props) => props.isNeedArrowIcon && `margin-left:16px;`}
|
||||
}
|
||||
.select-folder-dialog_tree-folder {
|
||||
height: ${(props) =>
|
||||
props.heightContent
|
||||
? props.heightContent
|
||||
: props.isHeaderChildren
|
||||
? "284px"
|
||||
: "300px"};
|
||||
}
|
||||
.rc-tree-child-tree-open {
|
||||
width: fit-content;
|
||||
}
|
||||
.select-folder-dialog-buttons-save {
|
||||
margin-right: 8px;
|
||||
}
|
||||
.select-folder-dialog-modal_buttons {
|
||||
margin-top: 16px;
|
||||
}
|
||||
|
||||
.select-folder-dialog_header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
.select-folder-dialog_header-icon {
|
||||
margin-right: 16px;
|
||||
}
|
||||
.select-folder-dialog_aside_body {
|
||||
height: calc(100% - 64px);
|
||||
width: 296px;
|
||||
}
|
||||
#folder-tree-scroll-bar {
|
||||
.nav-thumb-horizontal {
|
||||
height: 0px !important;
|
||||
}
|
||||
}
|
||||
.tree-folder-Loader {
|
||||
${(props) =>
|
||||
props.displayType === "aside"
|
||||
? css`
|
||||
margin-top: 16px;
|
||||
`
|
||||
: css`
|
||||
height: ${props.heightContent};
|
||||
`}
|
||||
}
|
||||
`;
|
||||
const StyledSelectFilePanel = styled.div`
|
||||
.select-file-dialog_empty-container {
|
||||
.ec-header {
|
||||
word-break: break-word;
|
||||
}
|
||||
}
|
||||
|
||||
${(props) =>
|
||||
props.displayType === "aside" &&
|
||||
css`
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
`}
|
||||
|
||||
.select-file-dialog-modal_buttons {
|
||||
${(props) =>
|
||||
props.isHeaderChildren ? "margin-top: 40px" : "margin-top:20px"};
|
||||
}
|
||||
.select-file-dialog_aside-body_wrapper {
|
||||
height: ${(props) =>
|
||||
props.isHeaderChildren ? "calc(100% - 260px);" : "calc(100% - 212px);"};
|
||||
}
|
||||
.select-file-dialog_aside-body_wrapper,
|
||||
.select-folder-dialog_aside-body_wrapper {
|
||||
width: 320px;
|
||||
padding: 0 16px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
.select-folder-dialog_aside-body_wrapper {
|
||||
height: 100%;
|
||||
}
|
||||
.select-file-dialog_aside-children {
|
||||
${(props) => props.isHeaderChildren && `padding-bottom: 16px;`}
|
||||
}
|
||||
.select-file-dialog_aside_body {
|
||||
margin-top: 16px;
|
||||
height: 100%;
|
||||
width: 290px;
|
||||
}
|
||||
.select-file-dialog_aside-header_title {
|
||||
margin: 0px;
|
||||
line-height: 56px;
|
||||
max-width: 474px;
|
||||
width: 400px;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
}
|
||||
.select-file-dialog_aside-header {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
.select-file-dialog_aside-header,
|
||||
.file-name {
|
||||
border-bottom: 1px solid #eceef1;
|
||||
}
|
||||
.file-name {
|
||||
display: flex;
|
||||
padding: 7px 0px;
|
||||
}
|
||||
.panel-loader-wrapper {
|
||||
margin-top: 8px;
|
||||
}
|
||||
.select-file-dialog_modal-loader {
|
||||
height: 290px;
|
||||
padding-top: 16px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
.panel-loader {
|
||||
display: inline;
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
.modal-dialog_children {
|
||||
grid-area: children;
|
||||
${(props) => props.isHeaderChildren && `padding: 16px 0;`}
|
||||
}
|
||||
.modal-dialog_tree-body {
|
||||
grid-area: tree;
|
||||
}
|
||||
.modal-dialog_files-body {
|
||||
grid-area: files-list;
|
||||
}
|
||||
|
||||
.modal-dialog_body {
|
||||
display: grid;
|
||||
grid-template-columns: 240px 1fr;
|
||||
height: 300px;
|
||||
grid-column-gap: 8px;
|
||||
grid-template-areas: "children children" "tree files-list";
|
||||
.modal-dialog_tree-body {
|
||||
${(props) =>
|
||||
props.isHeaderChildren ? `padding-top: 0;` : `padding-top: 16px;`}
|
||||
border-right: 1px solid #dee2e6;
|
||||
}
|
||||
}
|
||||
.select-file-dialog-aside_buttons {
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
padding-top: 8px;
|
||||
background-color: white;
|
||||
height: 40px;
|
||||
width: 100%;
|
||||
}
|
||||
.select-file-dialog-buttons-save {
|
||||
margin-right: 8px;
|
||||
margin-left: 16px;
|
||||
}
|
||||
.select-file-modal-dialog-buttons-save {
|
||||
margin-right: 8px;
|
||||
}
|
||||
.select-folder-dialog-buttons-save {
|
||||
margin-right: 8px;
|
||||
}
|
||||
.select-folder-dialog-modal_buttons {
|
||||
margin-top: 8px;
|
||||
}
|
||||
`;
|
||||
|
||||
const StyledFilesList = styled.div`
|
||||
.select-file-dialog_icon {
|
||||
margin-right: 8px;
|
||||
}
|
||||
.entry-title {
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
max-width: ${(props) =>
|
||||
props.displayType === "aside" ? "240px" : "250px"};
|
||||
}
|
||||
.files-list_file-owner {
|
||||
//margin-left: auto;
|
||||
max-width: 207px;
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
color: #a3a9ae;
|
||||
}
|
||||
.entry-title {
|
||||
font-weight: 600;
|
||||
}
|
||||
.file-exst {
|
||||
color: #a3a9ae;
|
||||
}
|
||||
.modal-dialog_file-name:hover {
|
||||
background-color: #eceef1;
|
||||
}
|
||||
.files-list_full-name {
|
||||
grid-area: full-name;
|
||||
display: flex;
|
||||
${(props) =>
|
||||
props.displayType === "aside" &&
|
||||
css`
|
||||
padding-top: 4px;
|
||||
`}
|
||||
}
|
||||
.select-file-dialog_icon {
|
||||
grid-area: icon-name;
|
||||
}
|
||||
.select-file-dialog_checked {
|
||||
grid-area: checked-button;
|
||||
padding-left: 6px;
|
||||
}
|
||||
.files-list_file-children_wrapper {
|
||||
grid-area: owner-name;
|
||||
margin-right: 16px;
|
||||
${(props) =>
|
||||
props.displayType === "aside" &&
|
||||
css`
|
||||
margin-top: -17px;
|
||||
`}
|
||||
}
|
||||
.modal-dialog_file-name {
|
||||
border-radius: 3px;
|
||||
${(props) => props.isChecked && `background:#eceef1;`}
|
||||
cursor: ${(props) => (props.needRowSelection ? "pointer" : "default")};
|
||||
border-bottom: 1px solid #eceef1;
|
||||
align-items: center;
|
||||
display: grid;
|
||||
${(props) =>
|
||||
props.displayType === "aside"
|
||||
? css`
|
||||
height: 56px;
|
||||
grid-template-areas: "checked-button icon-name full-name full-name" "checked-button icon-name owner-name owner-name";
|
||||
`
|
||||
: css`
|
||||
height: 36px;
|
||||
grid-template-areas: "checked-button icon-name full-name owner-name";
|
||||
`}
|
||||
grid-template-columns: 22px 32px 1fr;
|
||||
}
|
||||
`;
|
||||
export {
|
||||
StyledAsidePanel,
|
||||
StyledAddGroupsPanel,
|
||||
@ -526,4 +778,7 @@ export {
|
||||
StyledSharingBody,
|
||||
StyledFooter,
|
||||
StyledLinkRow,
|
||||
StyledSelectFolderPanel,
|
||||
StyledSelectFilePanel,
|
||||
StyledFilesList,
|
||||
};
|
||||
|
@ -21,7 +21,7 @@ const ShareButton = (props) => {
|
||||
|
||||
return (
|
||||
<IconButton
|
||||
iconName="images/catalog.shared.react.svg"
|
||||
iconName="/static/images/catalog.shared.react.svg"
|
||||
className="upload_panel-icon"
|
||||
color={color}
|
||||
isClickable
|
||||
|
@ -1,10 +1,13 @@
|
||||
import authStore from "@appserver/common/store/AuthStore";
|
||||
import { AppServerConfig } from "@appserver/common/constants";
|
||||
import config from "../../package.json";
|
||||
import { combineUrl } from "@appserver/common/utils";
|
||||
import { combineUrl, toUrlParams } from "@appserver/common/utils";
|
||||
import { addFileToRecentlyViewed } from "@appserver/common/api/files";
|
||||
import i18n from "./i18n";
|
||||
|
||||
import { request } from "@appserver/common/api/client";
|
||||
import docserviceStore from "../store/DocserviceStore";
|
||||
|
||||
export const setDocumentTitle = (subTitle = null) => {
|
||||
const { isAuthenticated, settingsStore, product: currentModule } = authStore;
|
||||
const { organizationName } = settingsStore;
|
||||
@ -72,3 +75,36 @@ export const openDocEditor = async (
|
||||
|
||||
return Promise.resolve();
|
||||
};
|
||||
|
||||
export const SaveAs = (title, url, folderId, openNewTab) => {
|
||||
const options = {
|
||||
action: "create",
|
||||
fileuri: url,
|
||||
title: title,
|
||||
folderid: folderId,
|
||||
response: openNewTab ? null : "message",
|
||||
};
|
||||
const params = toUrlParams(options, true);
|
||||
!openNewTab
|
||||
? request({
|
||||
baseURL: combineUrl(AppServerConfig.proxyURL, config.homepage),
|
||||
method: "get",
|
||||
url: `/httphandlers/filehandler.ashx?${params}`,
|
||||
})
|
||||
.then((data) => console.log("data", data))
|
||||
.catch((e) => console.error("error", e))
|
||||
: window.open(
|
||||
combineUrl(
|
||||
AppServerConfig.proxyURL,
|
||||
config.homepage,
|
||||
`/httphandlers/filehandler.ashx?${params}`
|
||||
),
|
||||
"_blank"
|
||||
);
|
||||
};
|
||||
|
||||
export const canConvert = (fileExst) => {
|
||||
const { canConvert } = docserviceStore;
|
||||
|
||||
return canConvert(fileExst);
|
||||
};
|
||||
|
@ -74,7 +74,12 @@ const SimpleFilesRow = (props) => {
|
||||
|
||||
const sharedButton =
|
||||
item.canShare && showShare ? (
|
||||
<SharedButton t={t} id={item.id} isFolder={item.isFolder} />
|
||||
<SharedButton
|
||||
t={t}
|
||||
id={item.id}
|
||||
shared={item.shared}
|
||||
isFolder={item.isFolder}
|
||||
/>
|
||||
) : null;
|
||||
|
||||
const element = (
|
||||
|
@ -46,7 +46,12 @@ const FilesTile = (props) => {
|
||||
const { thumbnailUrl } = item;
|
||||
const sharedButton =
|
||||
item.canShare && showShare ? (
|
||||
<SharedButton t={t} id={item.id} isFolder={item.isFolder} />
|
||||
<SharedButton
|
||||
t={t}
|
||||
id={item.id}
|
||||
shared={item.shared}
|
||||
isFolder={item.isFolder}
|
||||
/>
|
||||
) : null;
|
||||
const element = (
|
||||
<ItemIcon id={item.id} icon={item.icon} fileExst={item.fileExst} />
|
||||
|
@ -190,20 +190,12 @@ class PureHome extends React.Component {
|
||||
};
|
||||
componentDidUpdate(prevProps) {
|
||||
const {
|
||||
isLoading,
|
||||
isProgressFinished,
|
||||
secondaryProgressDataStoreIcon,
|
||||
selectionLength,
|
||||
selectionTitle,
|
||||
firstLoad,
|
||||
} = this.props;
|
||||
if (isLoading !== prevProps.isLoading && !firstLoad) {
|
||||
if (isLoading) {
|
||||
showLoader();
|
||||
} else {
|
||||
hideLoader();
|
||||
}
|
||||
}
|
||||
|
||||
if (this.props.isHeaderVisible !== prevProps.isHeaderVisible) {
|
||||
this.props.setHeaderVisible(this.props.isHeaderVisible);
|
||||
}
|
||||
@ -239,7 +231,6 @@ class PureHome extends React.Component {
|
||||
secondaryProgressDataStoreIcon,
|
||||
secondaryProgressDataStoreAlert,
|
||||
|
||||
isLoading,
|
||||
dragging,
|
||||
tReady,
|
||||
} = this.props;
|
||||
@ -271,7 +262,6 @@ class PureHome extends React.Component {
|
||||
isLoaded={!firstLoad}
|
||||
isHeaderVisible={isHeaderVisible}
|
||||
onOpenUploadPanel={this.showUploadPanel}
|
||||
isLoading={isLoading}
|
||||
firstLoad={firstLoad}
|
||||
dragging={dragging}
|
||||
>
|
||||
@ -365,12 +355,19 @@ export default inject(
|
||||
? filesStore.selectionTitle
|
||||
: null;
|
||||
|
||||
if (!firstLoad) {
|
||||
if (isLoading) {
|
||||
showLoader();
|
||||
} else {
|
||||
hideLoader();
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
homepage: config.homepage,
|
||||
firstLoad,
|
||||
dragging,
|
||||
fileActionId: id,
|
||||
isLoading,
|
||||
viewAs,
|
||||
uploaded,
|
||||
converted,
|
||||
|
@ -254,7 +254,7 @@ class ConnectClouds extends React.Component {
|
||||
<EmptyFolderContainer
|
||||
headerText={t("ConnectAccounts")}
|
||||
subheadingText={t("ConnectAccountsSubTitle")}
|
||||
imageSrc="images/empty_screen.png"
|
||||
imageSrc="/static/images/empty_screen.png"
|
||||
buttons={
|
||||
<div className="empty-folder_container-links empty-connect_container-links">
|
||||
<img
|
||||
|
@ -661,6 +661,7 @@ class FilesActionStore {
|
||||
isThirdPartySelection,
|
||||
userAccess,
|
||||
isViewedSelected,
|
||||
hasSelection,
|
||||
} = this.filesStore;
|
||||
|
||||
const {
|
||||
@ -682,7 +683,7 @@ class FilesActionStore {
|
||||
},
|
||||
{
|
||||
label: t("Common:Download"),
|
||||
disabled: !selectionCount,
|
||||
disabled: !hasSelection,
|
||||
onClick: () =>
|
||||
this.downloadAction(t("Translations:ArchivingData")).catch((err) =>
|
||||
toastr.error(err)
|
||||
@ -690,7 +691,7 @@ class FilesActionStore {
|
||||
},
|
||||
{
|
||||
label: t("Translations:DownloadAs"),
|
||||
disabled: !selectionCount || !isWebEditSelected,
|
||||
disabled: !hasSelection || !isWebEditSelected,
|
||||
onClick: () => setDownloadDialogVisible(true),
|
||||
},
|
||||
{
|
||||
@ -699,18 +700,18 @@ class FilesActionStore {
|
||||
isFavoritesFolder ||
|
||||
isRecentFolder ||
|
||||
!isAccessedSelected ||
|
||||
!selectionCount ||
|
||||
!hasSelection ||
|
||||
isThirdPartySelection,
|
||||
onClick: () => setMoveToPanelVisible(true),
|
||||
},
|
||||
{
|
||||
label: t("Translations:Copy"),
|
||||
disabled: !selectionCount,
|
||||
disabled: !hasSelection,
|
||||
onClick: () => setCopyPanelVisible(true),
|
||||
},
|
||||
{
|
||||
label: t("Common:Delete"),
|
||||
disabled: !selectionCount || !userAccess || isThirdPartySelection,
|
||||
disabled: !hasSelection || isThirdPartySelection,
|
||||
onClick: () => {
|
||||
if (this.settingsStore.confirmDelete) {
|
||||
setDeleteDialogVisible(true);
|
||||
@ -751,13 +752,16 @@ class FilesActionStore {
|
||||
headerMenu.splice(4, 1);
|
||||
}
|
||||
|
||||
if (isRecentFolder || isFavoritesFolder) {
|
||||
menu.splice(1, 1);
|
||||
}
|
||||
|
||||
if (
|
||||
(this.authStore.settingsStore.personal &&
|
||||
!isWebEditSelected &&
|
||||
!isViewedSelected) ||
|
||||
selectionCount > 1
|
||||
this.authStore.settingsStore.personal &&
|
||||
!isWebEditSelected &&
|
||||
!isViewedSelected
|
||||
) {
|
||||
headerMenu.splice(1, 1);
|
||||
menu.splice(1, 1);
|
||||
}
|
||||
|
||||
return headerMenu;
|
||||
|
@ -1246,6 +1246,10 @@ class FilesStore {
|
||||
return this.selection.find((el) => el.title).title;
|
||||
}
|
||||
|
||||
get hasSelection() {
|
||||
return !!this.selection.length;
|
||||
}
|
||||
|
||||
getOptions = (selection, externalAccess = false) => {
|
||||
const {
|
||||
canWebEdit,
|
||||
|
@ -236,7 +236,7 @@ class IconFormatsStore {
|
||||
sound = false,
|
||||
html = false
|
||||
) => {
|
||||
const folderPath = `images/icons/${size}`;
|
||||
const folderPath = `/static/images/icons/${size}`;
|
||||
|
||||
if (archive) return `${folderPath}/file_archive.svg`;
|
||||
|
||||
@ -331,7 +331,7 @@ class IconFormatsStore {
|
||||
};
|
||||
|
||||
getIconSrc = (ext, size = 24) => {
|
||||
const folderPath = `images/icons/${size}`;
|
||||
const folderPath = `/static/images/icons/${size}`;
|
||||
|
||||
if (presentInArray(this.archive, ext, true))
|
||||
return `${folderPath}/file_archive.svg`;
|
||||
|
25
products/ASC.Files/Client/src/store/SelectedFilesStore.js
Normal file
@ -0,0 +1,25 @@
|
||||
import { makeObservable, action, observable } from "mobx";
|
||||
|
||||
class SelectedFilesStore {
|
||||
folderId = null;
|
||||
fileInfo = null;
|
||||
|
||||
constructor() {
|
||||
makeObservable(this, {
|
||||
fileInfo: observable,
|
||||
folderId: observable,
|
||||
|
||||
setFolderId: action,
|
||||
setFile: action,
|
||||
});
|
||||
}
|
||||
|
||||
setFolderId = (id) => {
|
||||
this.folderId = id;
|
||||
};
|
||||
setFile = (obj) => {
|
||||
this.fileInfo = obj;
|
||||
};
|
||||
}
|
||||
|
||||
export default new SelectedFilesStore();
|
@ -162,6 +162,16 @@ class TreeFoldersStore {
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
get selectedKeys() {
|
||||
const selectedKeys =
|
||||
this.selectedTreeNode.length > 0 &&
|
||||
this.selectedTreeNode[0] !== "@my" &&
|
||||
this.selectedTreeNode[0] !== "@common"
|
||||
? this.selectedTreeNode
|
||||
: [this.selectedFolderStore.id + ""];
|
||||
return selectedKeys;
|
||||
}
|
||||
}
|
||||
|
||||
export default TreeFoldersStore;
|
||||
|
@ -16,7 +16,7 @@ import PrimaryProgressDataStore from "./PrimaryProgressDataStore";
|
||||
|
||||
import VersionHistoryStore from "./VersionHistoryStore";
|
||||
import DialogsStore from "./DialogsStore";
|
||||
|
||||
import selectedFilesStore from "./SelectedFilesStore";
|
||||
import store from "studio/store";
|
||||
|
||||
const formatsStore = new FormatsStore(
|
||||
@ -36,7 +36,8 @@ const filesStore = new FilesStore(
|
||||
selectedFolderStore,
|
||||
treeFoldersStore,
|
||||
formatsStore,
|
||||
settingsStore
|
||||
settingsStore,
|
||||
selectedFilesStore
|
||||
);
|
||||
const mediaViewerDataStore = new MediaViewerDataStore(filesStore);
|
||||
|
||||
@ -71,6 +72,7 @@ const filesActionsStore = new FilesActionsStore(
|
||||
|
||||
const versionHistoryStore = new VersionHistoryStore(filesStore);
|
||||
|
||||
//const selectedFilesStore = new SelectedFilesStore(selectedFilesStore);
|
||||
const stores = {
|
||||
filesStore,
|
||||
settingsStore,
|
||||
@ -82,6 +84,7 @@ const stores = {
|
||||
treeFoldersStore,
|
||||
selectedFolderStore,
|
||||
filesActionsStore,
|
||||
selectedFilesStore,
|
||||
};
|
||||
|
||||
export default stores;
|
||||
|
@ -152,6 +152,8 @@ var config = {
|
||||
"./app": "./src/Files.jsx",
|
||||
"./SharingDialog": "./src/components/panels/SharingDialog",
|
||||
"./utils": "./src/helpers/utils.js",
|
||||
"./SelectFileDialog": "./src/components/panels/SelectFileDialog",
|
||||
"./SelectFolderDialog": "./src/components/panels/SelectFolderDialog",
|
||||
},
|
||||
shared: {
|
||||
...deps,
|
||||
|
@ -1516,10 +1516,11 @@ namespace ASC.Web.Files.Services.WCFService
|
||||
return FileOperationsManager.Delete(AuthContext.CurrentAccount.ID, TenantManager.GetCurrentTenant(), foldersId, filesId, false, true, false, GetHttpHeaders());
|
||||
}
|
||||
|
||||
public List<FileOperationResult> CheckConversion(List<List<string>> filesInfoJSON)
|
||||
public List<FileOperationResult> CheckConversion(List<List<string>> filesInfoJSON, bool sync = false)
|
||||
{
|
||||
if (filesInfoJSON == null || filesInfoJSON.Count == 0) return new List<FileOperationResult>();
|
||||
|
||||
var results = new List<FileOperationResult>();
|
||||
var fileDao = GetFileDao();
|
||||
var files = new List<KeyValuePair<File<T>, bool>>();
|
||||
foreach (var fileInfo in filesInfoJSON)
|
||||
@ -1547,7 +1548,14 @@ namespace ASC.Web.Files.Services.WCFService
|
||||
{
|
||||
try
|
||||
{
|
||||
FileConverter.ExecAsync(file, false, fileInfo.Count > 3 ? fileInfo[3] : null);
|
||||
if (sync)
|
||||
{
|
||||
results.Add(FileConverter.ExecSync(file, fileInfo.Count > 3 ? fileInfo[3] : null));
|
||||
}
|
||||
else
|
||||
{
|
||||
FileConverter.ExecAsync(file, false, fileInfo.Count > 3 ? fileInfo[3] : null);
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
@ -1558,9 +1566,14 @@ namespace ASC.Web.Files.Services.WCFService
|
||||
files.Add(new KeyValuePair<File<T>, bool>(file, false));
|
||||
}
|
||||
|
||||
var results = FileConverter.GetStatus(files).ToList();
|
||||
|
||||
return new List<FileOperationResult>(results);
|
||||
if (sync)
|
||||
{
|
||||
return results;
|
||||
}
|
||||
else
|
||||
{
|
||||
return FileConverter.GetStatus(files).ToList();
|
||||
}
|
||||
}
|
||||
|
||||
public void ReassignStorage(Guid userFromId, Guid userToId)
|
||||
|
@ -115,7 +115,8 @@ namespace ASC.Web.Files
|
||||
private FileShareLink FileShareLink { get; }
|
||||
private FileConverter FileConverter { get; }
|
||||
private FFmpegService FFmpegService { get; }
|
||||
private IServiceProvider ServiceProvider { get; }
|
||||
private IServiceProvider ServiceProvider { get; }
|
||||
public TempStream TempStream { get; }
|
||||
private UserManager UserManager { get; }
|
||||
private ILog Logger { get; }
|
||||
|
||||
@ -143,7 +144,8 @@ namespace ASC.Web.Files
|
||||
FileShareLink fileShareLink,
|
||||
FileConverter fileConverter,
|
||||
FFmpegService fFmpegService,
|
||||
IServiceProvider serviceProvider)
|
||||
IServiceProvider serviceProvider,
|
||||
TempStream tempStream)
|
||||
{
|
||||
FilesLinkUtility = filesLinkUtility;
|
||||
TenantExtra = tenantExtra;
|
||||
@ -165,7 +167,8 @@ namespace ASC.Web.Files
|
||||
FileShareLink = fileShareLink;
|
||||
FileConverter = fileConverter;
|
||||
FFmpegService = fFmpegService;
|
||||
ServiceProvider = serviceProvider;
|
||||
ServiceProvider = serviceProvider;
|
||||
TempStream = tempStream;
|
||||
UserManager = userManager;
|
||||
Logger = optionsMonitor.CurrentValue;
|
||||
}
|
||||
@ -1056,8 +1059,15 @@ namespace ASC.Web.Files
|
||||
await CreateFile(context, GlobalFolderHelper.FolderMy);
|
||||
}
|
||||
else
|
||||
{
|
||||
await CreateFile(context, folderId);
|
||||
{
|
||||
if (int.TryParse(folderId, out var id))
|
||||
{
|
||||
await CreateFile(context, id);
|
||||
}
|
||||
else
|
||||
{
|
||||
await CreateFile(context, folderId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1174,10 +1184,21 @@ namespace ASC.Web.Files
|
||||
}
|
||||
|
||||
var fileDao = DaoFactory.GetFileDao<T>();
|
||||
using var fileStream = req.GetResponse().GetResponseStream();
|
||||
file.ContentLength = fileStream.Length;
|
||||
|
||||
return fileDao.SaveFile(file, fileStream);
|
||||
using var fileStream = req.GetResponse().GetResponseStream();
|
||||
|
||||
if (fileStream.CanSeek)
|
||||
{
|
||||
file.ContentLength = fileStream.Length;
|
||||
return fileDao.SaveFile(file, fileStream);
|
||||
}
|
||||
else
|
||||
{
|
||||
using var buffered = TempStream.GetBuffered(fileStream);
|
||||
file.ContentLength = buffered.Length;
|
||||
return fileDao.SaveFile(file, buffered);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
private void Redirect(HttpContext context)
|
||||
@ -1235,8 +1256,8 @@ namespace ASC.Web.Files
|
||||
|
||||
private async Task TrackFile(HttpContext context)
|
||||
{
|
||||
var q = context.Request.Query[FilesLinkUtility.FileId];
|
||||
|
||||
var q = context.Request.Query[FilesLinkUtility.FileId];
|
||||
|
||||
if (int.TryParse(q, out var id))
|
||||
{
|
||||
await TrackFile(context, id);
|
||||
@ -1281,7 +1302,7 @@ namespace ASC.Web.Files
|
||||
};
|
||||
fileData = JsonSerializer.Deserialize<DocumentServiceTracker.TrackerData>(body, options);
|
||||
}
|
||||
catch(JsonException e)
|
||||
catch (JsonException e)
|
||||
{
|
||||
Logger.Error("DocService track error read body", e);
|
||||
throw new HttpException((int)HttpStatusCode.BadRequest, "DocService request is incorrect");
|
||||
|
7
products/ASC.Files/Core/Model/CheckConversionModel.cs
Normal file
@ -0,0 +1,7 @@
|
||||
namespace ASC.Files.Core.Model
|
||||
{
|
||||
public class CheckConversionModel
|
||||
{
|
||||
public bool Sync { get; set; }
|
||||
}
|
||||
}
|
@ -396,7 +396,7 @@ namespace ASC.Web.Files.Utils
|
||||
return string.Format("fileConvertation-{0}", f.ID);
|
||||
}
|
||||
|
||||
private string FileJsonSerializer(EntryStatusManager EntryManager, File<T> file, string folderTitle)
|
||||
internal string FileJsonSerializer(EntryStatusManager EntryManager, File<T> file, string folderTitle)
|
||||
{
|
||||
if (file == null) return string.Empty;
|
||||
|
||||
@ -539,6 +539,7 @@ namespace ASC.Web.Files.Utils
|
||||
private DocumentServiceConnector DocumentServiceConnector { get; }
|
||||
private FileTrackerHelper FileTracker { get; }
|
||||
private BaseCommonLinkUtility BaseCommonLinkUtility { get; }
|
||||
private EntryStatusManager EntryStatusManager { get; }
|
||||
private IServiceProvider ServiceProvider { get; }
|
||||
private IHttpContextAccessor HttpContextAccesor { get; }
|
||||
|
||||
@ -560,7 +561,8 @@ namespace ASC.Web.Files.Utils
|
||||
DocumentServiceHelper documentServiceHelper,
|
||||
DocumentServiceConnector documentServiceConnector,
|
||||
FileTrackerHelper fileTracker,
|
||||
BaseCommonLinkUtility baseCommonLinkUtility,
|
||||
BaseCommonLinkUtility baseCommonLinkUtility,
|
||||
EntryStatusManager entryStatusManager,
|
||||
IServiceProvider serviceProvider)
|
||||
{
|
||||
FileUtility = fileUtility;
|
||||
@ -581,6 +583,7 @@ namespace ASC.Web.Files.Utils
|
||||
DocumentServiceConnector = documentServiceConnector;
|
||||
FileTracker = fileTracker;
|
||||
BaseCommonLinkUtility = baseCommonLinkUtility;
|
||||
EntryStatusManager = entryStatusManager;
|
||||
ServiceProvider = serviceProvider;
|
||||
}
|
||||
public FileConverter(
|
||||
@ -601,13 +604,14 @@ namespace ASC.Web.Files.Utils
|
||||
DocumentServiceHelper documentServiceHelper,
|
||||
DocumentServiceConnector documentServiceConnector,
|
||||
FileTrackerHelper fileTracker,
|
||||
BaseCommonLinkUtility baseCommonLinkUtility,
|
||||
BaseCommonLinkUtility baseCommonLinkUtility,
|
||||
EntryStatusManager entryStatusManager,
|
||||
IServiceProvider serviceProvider,
|
||||
IHttpContextAccessor httpContextAccesor)
|
||||
: this(fileUtility, filesLinkUtility, daoFactory, setupInfo, pathProvider, fileSecurity,
|
||||
fileMarker, tenantManager, authContext, entryManager, filesSettingsHelper,
|
||||
globalFolderHelper, filesMessageService, fileShareLink, documentServiceHelper, documentServiceConnector, fileTracker,
|
||||
baseCommonLinkUtility, serviceProvider)
|
||||
baseCommonLinkUtility, entryStatusManager, serviceProvider)
|
||||
{
|
||||
HttpContextAccesor = httpContextAccesor;
|
||||
}
|
||||
@ -682,7 +686,7 @@ namespace ASC.Web.Files.Utils
|
||||
return new ResponseStream(((HttpWebRequest)WebRequest.Create(convertUri)).GetResponse());
|
||||
}
|
||||
|
||||
public File<T> ExecSync<T>(File<T> file, string doc)
|
||||
public FileOperationResult ExecSync<T>(File<T> file, string doc)
|
||||
{
|
||||
var fileDao = DaoFactory.GetFileDao<T>();
|
||||
var fileSecurity = FileSecurity;
|
||||
@ -705,9 +709,47 @@ namespace ASC.Web.Files.Utils
|
||||
var docKey = DocumentServiceHelper.GetDocKey(file);
|
||||
|
||||
fileUri = DocumentServiceConnector.ReplaceCommunityAdress(fileUri);
|
||||
DocumentServiceConnector.GetConvertedUri(fileUri, fileExtension, toExtension, docKey, null, null, null, false, out var convertUri);
|
||||
|
||||
return SaveConvertedFile(file, convertUri);
|
||||
DocumentServiceConnector.GetConvertedUri(fileUri, fileExtension, toExtension, docKey, null, null, null, false, out var convertUri);
|
||||
|
||||
var operationResult = new ConvertFileOperationResult
|
||||
{
|
||||
Source = string.Format("{{\"id\":\"{0}\", \"version\":\"{1}\"}}", file.ID, file.Version),
|
||||
OperationType = FileOperationType.Convert,
|
||||
Error = string.Empty,
|
||||
Progress = 0,
|
||||
Result = string.Empty,
|
||||
Processed = "",
|
||||
Id = string.Empty,
|
||||
TenantId = TenantManager.GetCurrentTenant().TenantId,
|
||||
Account = AuthContext.CurrentAccount,
|
||||
Delete = false,
|
||||
StartDateTime = DateTime.Now,
|
||||
Url = HttpContextAccesor?.HttpContext != null ? HttpContextAccesor.HttpContext.Request.GetUrlRewriter().ToString() : null,
|
||||
Password = null,
|
||||
ServerRootPath = BaseCommonLinkUtility.ServerRootPath
|
||||
};
|
||||
|
||||
var operationResultError = string.Empty;
|
||||
|
||||
var newFile = SaveConvertedFile(file, fileUri);
|
||||
if (newFile != null)
|
||||
{
|
||||
var folderDao = DaoFactory.GetFolderDao<T>();
|
||||
var folder = folderDao.GetFolder(newFile.FolderID);
|
||||
var folderTitle = fileSecurity.CanRead(folder) ? folder.Title : null;
|
||||
operationResult.Result = GetFileConverter<T>().FileJsonSerializer(EntryStatusManager, newFile, folderTitle);
|
||||
}
|
||||
|
||||
operationResult.Progress = 100;
|
||||
operationResult.StopDateTime = DateTime.UtcNow;
|
||||
operationResult.Processed = "1";
|
||||
|
||||
if (!string.IsNullOrEmpty(operationResultError))
|
||||
{
|
||||
operationResult.Error = operationResultError;
|
||||
}
|
||||
|
||||
return operationResult;
|
||||
}
|
||||
|
||||
public void ExecAsync<T>(File<T> file, bool deleteAfter, string password = null)
|
||||
|
@ -1200,15 +1200,15 @@ namespace ASC.Api.Documents
|
||||
/// <param name="fileId"></param>
|
||||
/// <returns>Operation result</returns>
|
||||
[Update("file/{fileId}/checkconversion")]
|
||||
public IEnumerable<ConversationResult<string>> StartConversion(string fileId)
|
||||
public IEnumerable<ConversationResult<string>> StartConversion(string fileId, [FromBody(EmptyBodyBehavior = Microsoft.AspNetCore.Mvc.ModelBinding.EmptyBodyBehavior.Allow)] CheckConversionModel model)
|
||||
{
|
||||
return FilesControllerHelperString.StartConversion(fileId);
|
||||
return FilesControllerHelperString.StartConversion(fileId, model?.Sync ?? false);
|
||||
}
|
||||
|
||||
[Update("file/{fileId:int}/checkconversion")]
|
||||
public IEnumerable<ConversationResult<int>> StartConversion(int fileId)
|
||||
public IEnumerable<ConversationResult<int>> StartConversion(int fileId, [FromBody(EmptyBodyBehavior = Microsoft.AspNetCore.Mvc.ModelBinding.EmptyBodyBehavior.Allow)] CheckConversionModel model)
|
||||
{
|
||||
return FilesControllerHelperInt.StartConversion(fileId);
|
||||
return FilesControllerHelperInt.StartConversion(fileId, model?.Sync ?? false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -1443,6 +1443,18 @@ namespace ASC.Api.Documents
|
||||
return FilesControllerHelperInt.GetFileVersionInfo(fileId);
|
||||
}
|
||||
|
||||
[Read("file/{fileId}/presigned")]
|
||||
public DocumentService.FileLink GetPresignedUri(string fileId)
|
||||
{
|
||||
return FilesControllerHelperString.GetPresignedUri(fileId);
|
||||
}
|
||||
|
||||
[Read("file/{fileId:int}/presigned")]
|
||||
public DocumentService.FileLink GetPresignedUri(int fileId)
|
||||
{
|
||||
return FilesControllerHelperInt.GetPresignedUri(fileId);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Change version history
|
||||
/// </summary>
|
||||
|
@ -362,17 +362,17 @@ namespace ASC.Files.Helpers
|
||||
.Select(FileOperationWraperHelper.Get);
|
||||
}
|
||||
|
||||
public IEnumerable<ConversationResult<T>> StartConversion(T fileId)
|
||||
public IEnumerable<ConversationResult<T>> StartConversion(T fileId, bool sync = false)
|
||||
{
|
||||
return CheckConversion(fileId, true);
|
||||
return CheckConversion(fileId, true, sync);
|
||||
}
|
||||
|
||||
public IEnumerable<ConversationResult<T>> CheckConversion(T fileId, bool start)
|
||||
public IEnumerable<ConversationResult<T>> CheckConversion(T fileId, bool start, bool sync = false)
|
||||
{
|
||||
return FileStorageService.CheckConversion(new List<List<string>>
|
||||
{
|
||||
new List<string> { fileId.ToString(), "0", start.ToString() }
|
||||
})
|
||||
}, sync)
|
||||
.Select(r =>
|
||||
{
|
||||
var o = new ConversationResult<T>
|
||||
@ -508,6 +508,11 @@ namespace ASC.Files.Helpers
|
||||
return FileWrapperHelper.Get(result);
|
||||
}
|
||||
|
||||
public DocumentService.FileLink GetPresignedUri(T fileId)
|
||||
{
|
||||
return FileStorageService.GetPresignedUri(fileId);
|
||||
}
|
||||
|
||||
public string UpdateComment(T fileId, int version, string comment)
|
||||
{
|
||||
return FileStorageService.UpdateComment(fileId, version, comment);
|
||||
|
Before Width: | Height: | Size: 790 B After Width: | Height: | Size: 790 B |
Before Width: | Height: | Size: 941 B After Width: | Height: | Size: 941 B |
Before Width: | Height: | Size: 762 B After Width: | Height: | Size: 762 B |
Before Width: | Height: | Size: 417 B After Width: | Height: | Size: 417 B |
Before Width: | Height: | Size: 321 B After Width: | Height: | Size: 321 B |
Before Width: | Height: | Size: 579 B After Width: | Height: | Size: 579 B |
Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 1.1 KiB |
Before Width: | Height: | Size: 1.5 KiB After Width: | Height: | Size: 1.5 KiB |
Before Width: | Height: | Size: 591 B After Width: | Height: | Size: 591 B |
Before Width: | Height: | Size: 1.2 KiB After Width: | Height: | Size: 1.2 KiB |
Before Width: | Height: | Size: 1.3 KiB After Width: | Height: | Size: 1.3 KiB |
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 11 KiB |
Before Width: | Height: | Size: 929 B After Width: | Height: | Size: 929 B |
Before Width: | Height: | Size: 817 B After Width: | Height: | Size: 817 B |
Before Width: | Height: | Size: 768 B After Width: | Height: | Size: 768 B |
Before Width: | Height: | Size: 788 B After Width: | Height: | Size: 788 B |
Before Width: | Height: | Size: 825 B After Width: | Height: | Size: 825 B |
Before Width: | Height: | Size: 918 B After Width: | Height: | Size: 918 B |
Before Width: | Height: | Size: 723 B After Width: | Height: | Size: 723 B |
Before Width: | Height: | Size: 710 B After Width: | Height: | Size: 710 B |
Before Width: | Height: | Size: 748 B After Width: | Height: | Size: 748 B |
Before Width: | Height: | Size: 457 B After Width: | Height: | Size: 457 B |
Before Width: | Height: | Size: 478 B After Width: | Height: | Size: 478 B |
Before Width: | Height: | Size: 819 B After Width: | Height: | Size: 819 B |
Before Width: | Height: | Size: 885 B After Width: | Height: | Size: 885 B |
Before Width: | Height: | Size: 913 B After Width: | Height: | Size: 913 B |
Before Width: | Height: | Size: 884 B After Width: | Height: | Size: 884 B |
Before Width: | Height: | Size: 1.6 KiB After Width: | Height: | Size: 1.6 KiB |
Before Width: | Height: | Size: 856 B After Width: | Height: | Size: 856 B |
Before Width: | Height: | Size: 3.2 KiB After Width: | Height: | Size: 3.2 KiB |
Before Width: | Height: | Size: 1.2 KiB After Width: | Height: | Size: 1.2 KiB |
Before Width: | Height: | Size: 860 B After Width: | Height: | Size: 860 B |
Before Width: | Height: | Size: 870 B After Width: | Height: | Size: 870 B |