Merge branch 'feature/files' of github.com:ONLYOFFICE/AppServer into feature/files

This commit is contained in:
pavelbannov 2020-05-20 11:28:24 +03:00
commit ea9bb3df38
17 changed files with 388 additions and 63 deletions

View File

@ -1,7 +1,5 @@
import React from "react";
import { TreeMenu, TreeNode, Icons, toastr, utils } from "asc-web-components";
import { fetchFiles } from "../../../store/files/actions";
import store from "../../../store/store";
import { api } from "asc-web-common";
const { files } = api;
@ -10,8 +8,7 @@ class TreeFolders extends React.Component {
super(props);
const treeData = props.data;
this.state = { treeData, expandedKeys: this.props.expandedKeys };
this.state = { treeData, expandedKeys: props.expandedKeys };
this.ref = React.createRef();
}
@ -56,18 +53,6 @@ class TreeFolders extends React.Component {
}
};
onSelect = data => {
if (this.props.selectedKeys[0] !== data[0]) {
this.props.onLoading(true);
const newFilter = this.props.filter.clone();
fetchFiles(data[0], newFilter, store.dispatch).catch(err =>
toastr.error("Something went wrong", err)
).finally(() => this.props.onLoading(false));
}
//this.props.selectFolder(data && data.length === 1 && data[0] !== "root" ? data[0] : null);
};
loop = (data, curId, child, level) => {
//if (level < 1 || curId.length - 3 > level * 2) return;
data.forEach(item => {
@ -156,7 +141,7 @@ class TreeFolders extends React.Component {
const treeData = [...this.state.treeData];
this.getNewTreeData(treeData, listIds, data.folders, 10);
this.props.setTreeFolders(treeData);
this.props.needUpdate && this.props.setTreeFolders(treeData);
this.setState({ treeData });
})
.catch(() => this.props.onLoading(false))
@ -164,16 +149,18 @@ class TreeFolders extends React.Component {
};
onExpand = data => {
const newFilter = this.props.filter;
newFilter.treeFolders = data;
if(this.props.needUpdate) {
const newFilter = this.props.filter.clone();
newFilter.treeFolders = data;
this.props.setFilter(newFilter);
}
this.props.setFilter(newFilter);
this.setState({ expandedKeys: data });
};
componentDidUpdate(prevProps) {
const { expandedKeys, data } = this.props;
if (this.state.expandedKeys.length !== expandedKeys.length) {
const { expandedKeys, data, needUpdate } = this.props;
if (needUpdate && expandedKeys && this.state.expandedKeys.length !== expandedKeys.length) {
this.setState({ expandedKeys });
}
@ -183,7 +170,7 @@ class TreeFolders extends React.Component {
}
render() {
const { selectedKeys, fakeNewDocuments, isLoading } = this.props;
const { selectedKeys, fakeNewDocuments, isLoading, onSelect } = this.props;
const { treeData, expandedKeys } = this.state;
return (
@ -196,7 +183,7 @@ class TreeFolders extends React.Component {
multiple={false}
showIcon
switcherIcon={this.switcherIcon}
onSelect={this.onSelect}
onSelect={onSelect}
selectedKeys={selectedKeys}
badgeLabel={fakeNewDocuments}
onBadgeClick={() => console.log("onBadgeClick")}
@ -210,4 +197,9 @@ class TreeFolders extends React.Component {
}
}
TreeFolders.defaultProps = {
selectedKeys: [],
needUpdate: true
};
export default TreeFolders;

View File

@ -29,10 +29,10 @@ class ArticleBodyContent extends React.Component {
expandedKeys.pop();
fetchFiles(folderId, newFilter, store.dispatch).catch(err =>
toastr.error("Something went wrong", err)
toastr.error(err)
);
})
.catch(err => toastr.error("Something went wrong", err))
.catch(err => toastr.error(err))
.finally(() => this.setState({ expandedKeys }));
}
@ -46,12 +46,22 @@ class ArticleBodyContent extends React.Component {
}
}
onSelect = data => {
const { selectedKeys, filter, onLoading } = this.props;
if (selectedKeys[0] !== data[0]) {
onLoading(true);
const newFilter = filter.clone();
fetchFiles(data[0], newFilter, store.dispatch).catch(err =>
toastr.error(err)
).finally(() => onLoading(false));
}
};
render() {
const {
data,
selectedKeys,
fakeNewDocuments,
currentModule,
filter,
setFilter,
setTreeFolders,
@ -64,7 +74,7 @@ class ArticleBodyContent extends React.Component {
<TreeFolders
selectedKeys={selectedKeys}
fakeNewDocuments={fakeNewDocuments}
currentModule={currentModule}
onSelect={this.onSelect}
data={data}
filter={filter}
setFilter={setFilter}
@ -86,7 +96,6 @@ function mapStateToProps(state) {
data: treeFolders,
selectedKeys: selectedFolder ? [currentFolderId] : [""],
fakeNewDocuments,
currentModule: currentFolderId,
filter
};
}

View File

@ -162,12 +162,12 @@ class DownloadDialogComponent extends React.Component {
}
onDownload = () => {
const { startUploadSession, closeUploadSession, onDownloadProgress, onClose } = this.props;
const { startUploadSession, closeUploadSession, onDownloadProgress, onClose, t } = this.props;
const downloadItems = this.getDownloadItems();
const fileConvertIds = downloadItems[0];
const folderIds = downloadItems[1];
startUploadSession();
startUploadSession(t("ArchivingData"));
api.files
.downloadFormatFiles(fileConvertIds, folderIds)

View File

@ -11,5 +11,6 @@
"ConvertToZip": "Files will be compressed into the .zip file",
"ConvertMessage": "If you choose to convert the file to the format different from the original, some data might be lost.",
"DownloadButton": "Download",
"CancelButton": "Cancel"
"CancelButton": "Cancel",
"ArchivingData": "Archiving data"
}

View File

@ -11,5 +11,6 @@
"ConvertToZip": "Файлы будут упакованы в .zip файл",
"ConvertMessage": "Если вы решите сконвертировать файл в формат, отличный от исходного, некоторые данные могут быть потеряны.",
"DownloadButton": "Скачать",
"CancelButton": "Отмена"
"CancelButton": "Отмена",
"ArchivingData": "Архивирование данных"
}

View File

@ -14,7 +14,7 @@ import {
import { fetchFiles, setAction } from "../../../../../store/files/actions";
import { default as filesStore } from "../../../../../store/store";
import { EmptyTrashDialog, DeleteDialog, DownloadDialog } from "../../../../dialogs";
import { SharingPanel } from "../../../../panels";
import { SharingPanel, OperationsPanel } from "../../../../panels";
import { isCanBeDeleted, checkFolderType } from "../../../../../store/files/selectors";
const { isAdmin } = store.auth.selectors;
@ -103,7 +103,9 @@ class SectionHeaderContent extends React.Component {
showSharingPanel: false,
showDeleteDialog: false,
showDownloadDialog: false,
showEmptyTrashDialog: false
showEmptyTrashDialog: false,
showMoveToPanel: false,
showCopyPanel: false
};
}
@ -162,14 +164,14 @@ class SectionHeaderContent extends React.Component {
createLinkForPortalUsers = () =>
toastr.info("createLinkForPortalUsers click");
moveAction = () => toastr.info("moveAction click");
onMoveAction = () => this.setState({ showMoveToPanel: !this.state.showMoveToPanel });
copyAction = () => toastr.info("copyAction click");
onCopyAction = () => this.setState({ showCopyPanel: !this.state.showCopyPanel });
startUploadSession = () => {
const { onLoading, t, setProgressLabel, setProgressVisible} = this.props;
startFilesOperations = progressBarLabel => {
const { onLoading, setProgressLabel, setProgressVisible} = this.props;
onLoading(true);
setProgressLabel(t("ArchivingData"));
setProgressLabel(progressBarLabel);
setProgressVisible(true);
}
@ -207,7 +209,7 @@ class SectionHeaderContent extends React.Component {
}
}
this.startUploadSession();
this.startFilesOperations(this.props.t("ArchivingData"));
api.files
.downloadFiles(fileIds, folderIds)
@ -249,13 +251,13 @@ class SectionHeaderContent extends React.Component {
{
key: "move-to",
label: t("MoveTo"),
onClick: this.moveAction,
onClick: this.onMoveAction,
disabled: true
},
{
key: "copy",
label: t("Copy"),
onClick: this.copyAction,
onClick: this.onCopyAction,
disabled: true
},
{
@ -287,8 +289,6 @@ class SectionHeaderContent extends React.Component {
);
};
render() {
//console.log("Body header render");
@ -306,13 +306,18 @@ class SectionHeaderContent extends React.Component {
onCheck,
title,
currentFolderId,
onLoading
onLoading,
isLoading,
filter,
setProgressValue
} = this.props;
const {
showDeleteDialog,
showSharingPanel,
showEmptyTrashDialog,
showDownloadDialog
showDownloadDialog,
showMoveToPanel,
showCopyPanel
} = this.state;
const isItemsSelected = selection.length;
const isOnlyFolderSelected = selection.every(
@ -369,12 +374,12 @@ class SectionHeaderContent extends React.Component {
{
label: t("MoveTo"),
disabled: !isItemsSelected,
onClick: this.moveAction
onClick: this.onMoveAction
},
{
label: t("Copy"),
disabled: !isItemsSelected,
onClick: this.copyAction
onClick: this.onCopyAction
},
{
label: t("Delete"),
@ -389,6 +394,14 @@ class SectionHeaderContent extends React.Component {
onClick: this.onEmptyTrashAction
});
const operationsPanelProps = {
onLoading,
isLoading,
setProgressValue,
startFilesOperations: this.startFilesOperations,
closeUploadSession: this.closeUploadSession
};
return (
<StyledContainer isHeaderVisible={isHeaderVisible}>
{isHeaderVisible ? (
@ -486,11 +499,29 @@ class SectionHeaderContent extends React.Component {
/>
)}
{showMoveToPanel && (
<OperationsPanel
{...operationsPanelProps}
isCopy={false}
visible={showMoveToPanel}
onClose={this.onMoveAction}
/>
)}
{showCopyPanel && (
<OperationsPanel
{...operationsPanelProps}
isCopy={true}
visible={showCopyPanel}
onClose={this.onCopyAction}
/>
)}
{showDownloadDialog && (
<DownloadDialog
visible={showDownloadDialog}
onClose={this.downloadAsAction}
startUploadSession={this.startUploadSession}
startUploadSession={this.startFilesOperations}
closeUploadSession={this.closeUploadSession}
onDownloadProgress={this.loop}
/>

View File

@ -151,7 +151,7 @@ class PureHome extends React.Component {
return (
<>
<RequestLoader
visible={this.state.isLoading}
visible={isLoading}
zIndex={256}
loaderSize='16px'
loaderColor={"#999"}
@ -184,6 +184,7 @@ class PureHome extends React.Component {
onSelect={this.onSectionHeaderContentSelect}
onClose={this.onClose}
onLoading={this.onLoading}
isLoading={isLoading}
setProgressVisible={this.setProgressVisible}
setProgressValue={this.setProgressValue}
setProgressLabel={this.setProgressLabel}

View File

@ -0,0 +1,54 @@
import i18n from "i18next";
import Backend from "i18next-xhr-backend";
import config from "../../../../package.json";
import { constants } from 'asc-web-common';
const { LANGUAGE } = constants;
const newInstance = i18n.createInstance();
if (process.env.NODE_ENV === "production") {
newInstance
.use(Backend)
.init({
lng: localStorage.getItem(LANGUAGE) || 'en',
fallbackLng: "en",
interpolation: {
escapeValue: false, // not needed for react as it escapes by default
},
react: {
useSuspense: true
},
backend: {
loadPath: `${config.homepage}/locales/MoveToPanel/{{lng}}/{{ns}}.json`
}
});
} else if (process.env.NODE_ENV === "development") {
const resources = {
en: {
translation: require("./locales/en/translation.json")
},
ru: {
translation: require("./locales/ru/translation.json")
},
};
newInstance.init({
resources: resources,
lng: localStorage.getItem(LANGUAGE) || 'en',
fallbackLng: "en",
debug: true,
interpolation: {
escapeValue: false, // not needed for react as it escapes by default
},
react: {
useSuspense: true
}
});
}
export default newInstance;

View File

@ -0,0 +1,185 @@
import React from "react";
import PropTypes from "prop-types";
import { connect } from "react-redux";
import { withRouter } from "react-router";
import { Backdrop, Heading, Aside } from "asc-web-components";
import { withTranslation } from "react-i18next";
import { utils as commonUtils } from "asc-web-common";
import i18n from "./i18n";
import {
StyledAsidePanel,
StyledContent,
StyledHeaderContent,
StyledBody
} from "../StyledPanels";
import TreeFolders from "../../Article/Body/TreeFolders";
import {
getProgress,
fetchFiles,
setTreeFolders,
getFolder,
copyToFolder,
moveToFolder
} from "../../../store/files/actions";
import { default as filesStore } from "../../../store/store";
import { loopTreeFolders } from "../../../store/files/selectors";
const { changeLanguage } = commonUtils;
class OperationsPanelComponent extends React.Component {
constructor(props) {
super(props);
changeLanguage(i18n);
}
loop = (id, destFolderId) => {
const {
getProgress,
setProgressValue,
closeUploadSession,
filter,
currentFolderId,
treeFolders,
getFolder
} = this.props;
getProgress().then(res => {
const currentItem = res.find(x => x.id === id);
if(currentItem && currentItem.progress !== 100) {
setProgressValue(currentItem.progress);
setTimeout(() => this.loop(id, destFolderId), 1000);
} else {
getFolder(destFolderId).then(data => {
let newTreeFolders = treeFolders;
let path = data.pathParts.slice(0);
let folders = data.folders;
let foldersCount = data.current.foldersCount;
loopTreeFolders(path, newTreeFolders, folders, foldersCount);
fetchFiles(currentFolderId, filter, filesStore.dispatch).then((data) => {
newTreeFolders = treeFolders;
path = data.selectedFolder.pathParts.slice(0);
folders = data.selectedFolder.folders;
foldersCount = data.selectedFolder.foldersCount;
loopTreeFolders(path, newTreeFolders, folders, foldersCount);
setTreeFolders(newTreeFolders);
}).catch(err => closeUploadSession(err))
.finally(() => {
setProgressValue(100);
closeUploadSession();
})
}).catch(err => closeUploadSession(err))
}
}).catch(err => closeUploadSession(err));
}
onSelect = e => {
const {
t,
isCopy,
onClose,
selection,
startFilesOperations,
closeUploadSession,
copyToFolder,
moveToFolder
} = this.props;
const destFolderId = Number(e);
const conflictResolveType = "skip"; //Skip, Overwrite, Duplicate
const deleteAfter = true;
const folderIds = [];
const fileIds = [];
for(let item of selection) {
if(item.fileExst) {
fileIds.push(item.id);
} else {
folderIds.push(item.id);
}
}
onClose();
if(isCopy) {
startFilesOperations(t("CopyOperation"));
copyToFolder(destFolderId, folderIds, fileIds, conflictResolveType, deleteAfter)
.then(res => this.loop(res[0].id, destFolderId))
.catch(err => closeUploadSession(err))
} else {
startFilesOperations(t("MoveToOperation"));
moveToFolder(destFolderId, folderIds, fileIds, conflictResolveType, deleteAfter)
.then(res => this.loop(res[0].id, destFolderId))
.catch(err => closeUploadSession(err))
}
}
render() {
//console.log("Operations panel render");
const { t, visible, onClose, onLoading, isLoading, filter, treeFolders, isCopy } = this.props;
const zIndex = 310;
const fakeNewDocuments = 8;
const data = treeFolders.slice(0, 3);
const expandedKeys = this.props.expandedKeys.map(item => item.toString());
return (
<StyledAsidePanel visible={visible}>
<Backdrop onClick={onClose} visible={visible} zIndex={zIndex} />
<Aside className="header_aside-panel" visible={visible}>
<StyledContent>
<StyledHeaderContent className="files-operations-panel">
<Heading size="medium" truncate>
{isCopy ? t("Copy") : t("Move")}
</Heading>
</StyledHeaderContent>
<StyledBody className="files-operations-body">
<TreeFolders
expandedKeys={expandedKeys}
fakeNewDocuments={fakeNewDocuments}
data={data}
filter={filter}
onLoading={onLoading}
isLoading={isLoading}
onSelect={this.onSelect}
needUpdate={false}
/>
</StyledBody>
</StyledContent>
</Aside>
</StyledAsidePanel>
);
}
}
OperationsPanelComponent.propTypes = {
onClose: PropTypes.func,
visible: PropTypes.bool,
};
const OperationsPanelContainerTranslated = withTranslation()(OperationsPanelComponent);
const OperationsPanel = (props) => (
<OperationsPanelContainerTranslated i18n={i18n} {...props} />
);
const mapStateToProps = (state) => {
const { selectedFolder, selection, treeFolders, filter } = state.files;
const { pathParts, id } = selectedFolder;
return {
treeFolders,
filter,
selection,
expandedKeys: pathParts,
currentFolderId: id
};
};
export default connect(mapStateToProps, {
setTreeFolders,
getFolder,
getProgress,
copyToFolder,
moveToFolder,
})(withRouter(OperationsPanel));

View File

@ -0,0 +1,6 @@
{
"Copy": "Copy",
"Move": "Move",
"CopyOperation": "Copying",
"MoveToOperation": "Moving"
}

View File

@ -0,0 +1,6 @@
{
"Copy": "Копирование",
"Move": "Перемещение",
"CopyOperation": "Копирование",
"MoveToOperation": "Перемещение"
}

View File

@ -9,7 +9,6 @@ import {
Button,
DropDown,
DropDownItem,
utils,
toastr,
Textarea,
ComboBox,
@ -18,12 +17,12 @@ import {
import { connect } from "react-redux";
import { withRouter } from "react-router";
import { withTranslation } from "react-i18next";
import { utils as commonUtils, constants, api } from "asc-web-common";
import { utils as commonUtils, constants } from "asc-web-common";
import i18n from "./i18n";
import { getShareUsers, setShareFiles } from "../../../store/files/actions";
import { getAccessOption } from '../../../store/files/selectors';
import {
StyledSharingPanel,
StyledAsidePanel,
StyledContent,
StyledFooter,
StyledSharingHeaderContent,
@ -34,7 +33,6 @@ import SharingRow from "./SharingRow";
const { changeLanguage } = commonUtils;
const { ShareAccessRights } = constants;
const { files } = api;
class SharingPanelComponent extends React.Component {
constructor(props) {
@ -561,7 +559,7 @@ class SharingPanelComponent extends React.Component {
);
return (
<StyledSharingPanel visible={visible}>
<StyledAsidePanel visible={visible}>
<Backdrop onClick={this.onClose} visible={visible} zIndex={zIndex} />
<Aside className="header_aside-panel" visible={visible}>
<StyledContent>
@ -676,7 +674,7 @@ class SharingPanelComponent extends React.Component {
onClose={this.onShowEmbeddingPanel}
embeddingLink={shareLink}
/>
</StyledSharingPanel>
</StyledAsidePanel>
);
}
}

View File

@ -14,7 +14,7 @@ const PanelStyles = css`
}
`;
const StyledSharingPanel = styled.div`
const StyledAsidePanel = styled.div`
.header_aside-panel {
transform: translateX(${(props) => (props.visible ? "0" : "500px")});
width: 500px;
@ -73,6 +73,14 @@ const StyledContent = styled.div`
background-color: #fff;
padding: 0 16px 16px;
.files-operations-panel {
border-bottom: 1px solid #dee2e6;
}
.files-operations-body {
padding: 16px 0;
}
.header_aside-panel-header {
max-width: 500px;
margin: 0 0 0 16px;
@ -91,6 +99,10 @@ const StyledHeaderContent = styled.div`
`;
const StyledBody = styled.div`
.files-operations-body {
padding: 0 16px;
}
.selector-wrapper {
position: fixed;
height: 94%;
@ -257,7 +269,7 @@ const StyledFooter = styled.div`
`;
export {
StyledSharingPanel,
StyledAsidePanel,
StyledAddGroupsPanel,
StyledAddUsersPanelPanel,
StyledEmbeddingPanel,
@ -266,5 +278,5 @@ export {
StyledBody,
StyledSharingHeaderContent,
StyledSharingBody,
StyledFooter,
StyledFooter
};

View File

@ -2,5 +2,6 @@ import SharingPanel from "./SharingPanel/SharingPanel";
import AddUsersPanel from "./AddUsersPanel/AddUsersPanel";
import AddGroupsPanel from "./AddGroupsPanel/AddGroupsPanel";
import EmbeddingPanel from "./EmbeddingPanel/EmbeddingPanel";
import OperationsPanel from "./OperationsPanel";
export { SharingPanel, AddUsersPanel, AddGroupsPanel, EmbeddingPanel }
export { SharingPanel, AddUsersPanel, AddGroupsPanel, EmbeddingPanel, OperationsPanel }

View File

@ -14,7 +14,7 @@ import {
import config from "../../../package.json";
import { getTreeFolders } from "./selectors";
const { files, groups, FilesFilter } = api;
const { files, FilesFilter } = api;
export const SET_FOLDER = "SET_FOLDER";
export const SET_FOLDERS = "SET_FOLDERS";
@ -327,6 +327,24 @@ export function getShareUsers(folderIds, fileIds) {
return axios.all(requests).then(res => res);
}
export function getProgress() {
return dispatch => {
return files.getProgress();
};
};
export function copyToFolder(destFolderId, folderIds, fileIds, conflictResolveType, deleteAfter) {
return dispatch => {
return files.copyToFolder(destFolderId, folderIds, fileIds, conflictResolveType, deleteAfter);
};
};
export function moveToFolder(destFolderId, folderIds, fileIds, conflictResolveType, deleteAfter) {
return dispatch => {
return files.moveToFolder(destFolderId, folderIds, fileIds, conflictResolveType, deleteAfter);
};
};
/*export function deleteGroup(id) {
return (dispatch, getState) => {
const { people } = getState();

View File

@ -1,6 +1,6 @@
{
"name": "asc-web-common",
"version": "1.0.139",
"version": "1.0.140",
"description": "Ascensio System SIA common components and solutions library",
"license": "AGPL-3.0",
"files": [

View File

@ -335,3 +335,13 @@ export function downloadFormatFiles(fileConvertIds, folderIds) {
export function getProgress() {
return request({ method: "get", url: "/files/fileops" });
}
export function copyToFolder(destFolderId, folderIds, fileIds, conflictResolveType, deleteAfter) {
const data = { destFolderId, folderIds, fileIds, conflictResolveType, deleteAfter };
return request({ method: "put", url: "/files/fileops/copy", data });
}
export function moveToFolder(destFolderId, folderIds, fileIds, conflictResolveType, deleteAfter) {
const data = { destFolderId, folderIds, fileIds, conflictResolveType, deleteAfter };
return request({ method: "put", url: "/files/fileops/move", data });
}