Web: Files: added download as dialog

This commit is contained in:
Nikita Gopienko 2020-05-13 10:00:04 +03:00
parent 11cde4c7e7
commit 1205f03a5a
8 changed files with 903 additions and 4 deletions

View File

@ -0,0 +1,248 @@
import React from "react";
import {
Row,
RowContent,
RowContainer,
Text,
LinkWithDropdown
} from "asc-web-components";
const DownloadContent = (props) => {
const {
t,
checkedTitle,
indeterminateTitle,
items,
formatKeys,
onSelectFormat,
onRowSelect,
getItemIcon,
titleFormat,
type
} = props;
const getTitleLabel = (format) => {
switch (format) {
case 0:
return t("OriginalFormat");
case 1:
return ".txt";
case 2:
return ".docx";
case 3:
return ".odt";
case 4:
return ".ods";
case 5:
return ".odp";
case 6:
return ".pdf";
case 7:
return ".rtf";
case 8:
return ".xlsx";
case 9:
return ".pptx";
case 10:
return t("CustomFormat");
default:
return "";
}
};
const getFormats = item => {
const documentFormats = [
{
key: "key1",
label: t("OriginalFormat"),
onClick: onSelectFormat.bind(this, formatKeys.OriginalFormat, item, "document"),
},
{
key: "key2",
label: ".txt",
onClick: onSelectFormat.bind(this, formatKeys.TxtFormat, item, "document"),
},
{
key: "key3",
label: ".docx",
onClick: onSelectFormat.bind(this, formatKeys.DocxFormat, item, "document"),
},
{
key: "key4",
label: ".odt",
onClick: onSelectFormat.bind(this, formatKeys.OdtFormat, item, "document"),
},
{
key: "key5",
label: ".pdf",
onClick: onSelectFormat.bind(this, formatKeys.PdfFormat, item, "document"),
},
{
key: "key6",
label: ".rtf",
onClick: onSelectFormat.bind(this, formatKeys.RtfFormat, item, "document"),
},
{
key: "key7",
label: t("CustomFormat"),
onClick: onSelectFormat.bind(this, formatKeys.CustomFormat, item, "document"),
},
];
const presentationFormats = [
{
key: "key1",
label: t("OriginalFormat"),
onClick: onSelectFormat.bind(this, formatKeys.OriginalFormat, item, "presentation"),
},
{
key: "key2",
label: ".odp",
onClick: onSelectFormat.bind(this, formatKeys.OdpFormat, item, "presentation"),
},
{
key: "key3",
label: ".pdf",
onClick: onSelectFormat.bind(this, formatKeys.PdfFormat, item, "presentation"),
},
{
key: "key4",
label: ".pptx",
onClick: onSelectFormat.bind(this, formatKeys.PptxFormat, item, "presentation"),
},
{
key: "key5",
label: t("CustomFormat"),
onClick: onSelectFormat.bind(this, formatKeys.CustomFormat, item, "presentation"),
},
];
const spreadsheetFormats = [
{
key: "key1",
label: t("OriginalFormat"),
onClick: onSelectFormat.bind(this, formatKeys.OriginalFormat, item, "spreadsheet"),
},
{
key: "key2",
label: ".odp",
onClick: onSelectFormat.bind(this, formatKeys.OdsFormat, item, "spreadsheet"),
},
{
key: "key3",
label: ".pdf",
onClick: onSelectFormat.bind(this, formatKeys.PdfFormat, item, "spreadsheet"),
},
{
key: "key4",
label: ".xlsx",
onClick: onSelectFormat.bind(this, formatKeys.XlsxFormat, item, "spreadsheet"),
},
{
key: "key5",
label: t("CustomFormat"),
onClick: onSelectFormat.bind(this, formatKeys.CustomFormat, item, "spreadsheet"),
},
];
switch(type) {
case "document": return documentFormats;
case "spreadsheet": return spreadsheetFormats;
case "presentation": return presentationFormats;
default: return [];
}
}
const getTitle = () => {
switch(type) {
case "document": return t("Documents");
case "spreadsheet": return t("Spreadsheets");
case "presentation": return t("Presentations");
default: return "";
}
}
const title = getTitle();
const length = items.length;
const minHeight = length > 2 ? 110 : length * 50;
const showTitle = length > 1;
const formats = getFormats();
const documentsTitle = getTitleLabel(titleFormat);
return (
<>
{showTitle && (
<Row
key={"title"}
onSelect={onRowSelect.bind(this, "All", type)}
checked={checkedTitle}
indeterminate={indeterminateTitle}
>
<RowContent>
<Text truncate type="page" title={title} fontSize="14px">
{title}
</Text>
<></>
<Text fontSize="12px" containerWidth="auto">{t("ConvertInto")}</Text>
<LinkWithDropdown
containerWidth="auto"
data={formats}
directionX="right"
directionY="bottom"
dropdownType="appearDashedAfterHover"
fontSize="12px"
>
{documentsTitle}
</LinkWithDropdown>
</RowContent>
</Row>
)}
<RowContainer
useReactWindow={length > 2}
style={{ minHeight: minHeight, padding: "8px 0" }}
itemHeight={50}
>
{items.map((file) => {
const element = getItemIcon(file);
let dropdownItems = getFormats(file);
const format = getTitleLabel(file.format);
dropdownItems = dropdownItems.slice(1, -1);
dropdownItems = dropdownItems.filter(x => x.label !== file.fileExst);
return (
<Row
key={file.id}
onSelect={onRowSelect.bind(this, file, type)}
checked={file.checked}
element={element}
>
<RowContent>
<Text truncate type="page" title={file.title} fontSize="14px">
{file.title}
</Text>
<></>
<Text fontSize="12px" containerWidth="auto">
{file.checked && t("ConvertInto")}
</Text>
<LinkWithDropdown
dropdownType="appearDashedAfterHover"
containerWidth="auto"
data={dropdownItems}
directionX="right"
directionY="bottom"
fontSize="12px"
>
{format}
</LinkWithDropdown>
</RowContent>
</Row>
);
})}
</RowContainer>
</>
);
};
export default DownloadContent;

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/DownloadDialog/{{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,556 @@
import React from "react";
import { withRouter } from "react-router";
import { connect } from "react-redux";
import ModalDialogContainer from "../ModalDialogContainer";
import {
toastr,
ModalDialog,
Button,
Text,
Row,
RowContent,
RowContainer
} from "asc-web-components";
import { ReactSVG } from "react-svg";
import { withTranslation } from "react-i18next";
import i18n from "./i18n";
import { utils } from "asc-web-common";
import {
getFileIcon,
getFolderIcon,
isSpreadsheet,
isPresentation,
isDocument,
} from "../../../store/files/selectors";
import DownloadContent from "./DownloadContent";
const { changeLanguage } = utils;
const formatKeys = Object.freeze({
OriginalFormat: 0,
TxtFormat: 1,
DocxFormat: 2,
OdtFormat: 3,
OdsFormat: 4,
OdpFormat: 5,
PdfFormat: 6,
RtfFormat: 7,
XlsxFormat: 8,
PptxFormat: 9,
CustomFormat: 10,
});
class DownloadDialogComponent extends React.Component {
constructor(props) {
super(props);
changeLanguage(i18n);
const documents = [];
const spreadsheets = [];
const presentations = [];
const other = [];
for (let item of props.items) {
item.checked = true;
item.format = formatKeys.OriginalFormat;
if (item.fileExst) {
if (isSpreadsheet(item.fileExst)) {
spreadsheets.push(item);
} else if (isPresentation(item.fileExst)) {
presentations.push(item);
} else if (item.fileExst !== ".pdf" && isDocument(item.fileExst)) {
documents.push(item);
} else {
other.push(item);
}
} else {
other.push(item);
}
}
this.state = {
documents,
spreadsheets,
presentations,
other,
documentsTitleFormat: formatKeys.OriginalFormat,
spreadsheetsTitleFormat: formatKeys.OriginalFormat,
presentationsTitleFormat: formatKeys.OriginalFormat,
checkedDocTitle: true,
checkedSpreadsheetTitle: true,
checkedPresentationTitle: true,
checkedOtherTitle: true,
indeterminateDocTitle: false,
indeterminateSpreadsheetTitle: false,
indeterminatePresentationTitle: false,
indeterminateOtherTitle: false,
};
}
onDownload = () => {
const { documents, spreadsheets, presentations, other } = this.state;
const folderIds = [];
const fileIds = [];
for (let item of documents) {
if (item.checked) {
fileIds.push(item.id);
}
}
for (let item of spreadsheets) {
if (item.checked) {
fileIds.push(item.id);
}
}
for (let item of presentations) {
if (item.checked) {
fileIds.push(item.id);
}
}
for (let item of other) {
if (item.checked) {
folderIds.push(item.id);
}
}
toastr.success("onDownload click");
};
getItemIcon = (item) => {
const extension = item.fileExst;
const icon = extension
? getFileIcon(extension, 24)
: getFolderIcon(item.providerKey, 24);
return (
<ReactSVG
beforeInjection={(svg) => {
svg.setAttribute("style", "margin-top: 4px");
}}
src={icon}
loading={this.svgLoader}
/>
);
};
onSelectFormat = (format, file, type) => {
const { documents, spreadsheets, presentations } = this.state;
const newDocuments = documents;
const newSpreadsheets = spreadsheets;
const newPresentations = presentations;
if (type === "document") {
//Set all documents format
if (!file) {
for (let file of newDocuments) {
file.format =
format === formatKeys.CustomFormat || file.fileExst === format
? formatKeys.OriginalFormat
: format;
}
this.setState({
documents: newDocuments,
documentsTitleFormat: format,
});
} else {
//Set single document format
const newDoc = newDocuments.find((x) => x.id === file.id);
if (newDoc.format !== format) {
newDoc.format = format;
this.setState({
documents: newDocuments,
documentsTitleFormat: formatKeys.CustomFormat,
});
}
}
} else if (type === "spreadsheet") {
//Set all spreadsheets format
if (!file) {
for (let file of newSpreadsheets) {
file.format =
format === formatKeys.CustomFormat || file.fileExst === format
? formatKeys.OriginalFormat
: format;
}
this.setState({
spreadsheets: newSpreadsheets,
spreadsheetsTitleFormat: format,
});
} else {
//Set single spreadsheet format
const newSpreadsheet = newSpreadsheets.find((x) => x.id === file.id);
if (newSpreadsheet.format !== format) {
newSpreadsheet.format = format;
this.setState({
spreadsheets: newSpreadsheets,
spreadsheetsTitleFormat: formatKeys.CustomFormat,
});
}
}
} else if (type === "presentation") {
//Set all presentations format
if (!file) {
for (let file of newPresentations) {
file.format =
format === formatKeys.CustomFormat || file.fileExst === format
? formatKeys.OriginalFormat
: format;
}
this.setState({
presentations: newPresentations,
presentationsTitleFormat: format,
});
} else {
//Set single presentation format
const newPresentation = newPresentations.find((x) => x.id === file.id);
if (newPresentation.format !== format) {
newPresentation.format = format;
this.setState({
presentations: newPresentations,
presentationsTitleFormat: formatKeys.CustomFormat,
});
}
}
}
};
onRowSelect = (item, type) => {
const {
documents,
spreadsheets,
presentations,
other,
checkedDocTitle,
checkedSpreadsheetTitle,
checkedPresentationTitle,
checkedOtherTitle,
indeterminateDocTitle,
indeterminateSpreadsheetTitle,
indeterminatePresentationTitle,
indeterminateOtherTitle,
} = this.state;
const newDocuments = documents;
const newSpreadsheets = spreadsheets;
const newPresentations = presentations;
const newOthers = other;
if (type === "document") {
//Select all documents
if (item === "All") {
const checked = indeterminateDocTitle ? false : !checkedDocTitle;
for (let file of newDocuments) {
file.checked = checked;
}
this.setState({
documents: newDocuments,
indeterminateDocTitle: false,
checkedDocTitle: checked,
});
} else {
//Select single document
const newDoc = newDocuments.find((x) => x.id === item.id);
newDoc.checked = !newDoc.checked;
const disableFiles = newDocuments.find((x) => x.checked === false);
const activeFiles = newDocuments.find((x) => x.checked === true);
const indeterminate = !activeFiles ? false : !!disableFiles;
const title = disableFiles ? false : true;
this.setState({
documents: newDocuments,
indeterminateDocTitle: indeterminate,
checkedDocTitle: title,
});
}
} else if (type === "spreadsheet") {
if (item === "All") {
//Select all spreadsheets
const checked = indeterminateSpreadsheetTitle
? false
: !checkedSpreadsheetTitle;
for (let spreadsheet of newSpreadsheets) {
spreadsheet.checked = checked;
}
this.setState({
spreadsheets: newSpreadsheets,
indeterminateSpreadsheetTitle: false,
checkedSpreadsheetTitle: checked,
});
} else {
//Select single spreadsheet
const newSpreadsheet = newSpreadsheets.find((x) => x.id === item.id);
newSpreadsheet.checked = !newSpreadsheet.checked;
const disableSpreadsheet = newSpreadsheets.find(
(x) => x.checked === false
);
const activeSpreadsheet = newSpreadsheets.find(
(x) => x.checked === true
);
const indeterminate = !activeSpreadsheet ? false : !!disableSpreadsheet;
const title = disableSpreadsheet ? false : true;
this.setState({
spreadsheets: newSpreadsheets,
indeterminateSpreadsheetTitle: indeterminate,
checkedSpreadsheetTitle: title,
});
}
} else if (type === "presentation") {
if (item === "All") {
//Select all presentations
const checked = indeterminatePresentationTitle
? false
: !checkedPresentationTitle;
for (let presentation of newPresentations) {
presentation.checked = checked;
}
this.setState({
presentations: newPresentations,
indeterminatePresentationTitle: false,
checkedPresentationTitle: checked,
});
} else {
//Select single presentation
const newPresentation = newPresentations.find((x) => x.id === item.id);
newPresentation.checked = !newPresentation.checked;
const disablePresentation = newPresentations.find(
(x) => x.checked === false
);
const activePresentation = newPresentations.find(
(x) => x.checked === true
);
const indeterminate = !activePresentation
? false
: !!disablePresentation;
const title = disablePresentation ? false : true;
this.setState({
presentations: newPresentations,
indeterminatePresentationTitle: indeterminate,
checkedPresentationTitle: title,
});
}
} else {
if (item === "All") {
const checked = indeterminateOtherTitle ? false : !checkedOtherTitle;
for (let folder of newOthers) {
folder.checked = checked;
}
this.setState({
other: newOthers,
indeterminateOtherTitle: false,
checkedOtherTitle: checked,
});
} else {
const newOther = newOthers.find((x) => x.id === item.id);
newOther.checked = !newOther.checked;
const disableFolders = newOthers.find((x) => x.checked === false);
const activeFolders = newOthers.find((x) => x.checked === true);
const indeterminate = !activeFolders ? false : !!disableFolders;
const title = disableFolders ? false : true;
this.setState({
other: newOthers,
indeterminateOtherTitle: indeterminate,
checkedOtherTitle: title,
});
}
}
};
render() {
const { onClose, visible, t } = this.props;
const {
documentsTitleFormat,
spreadsheetsTitleFormat,
presentationsTitleFormat,
documents,
other,
spreadsheets,
presentations,
checkedDocTitle,
checkedSpreadsheetTitle,
checkedPresentationTitle,
checkedOtherTitle,
indeterminateDocTitle,
indeterminateSpreadsheetTitle,
indeterminatePresentationTitle,
indeterminateOtherTitle
} = this.state;
const otherLength = other.length;
const showOther = otherLength > 1;
const minHeight = otherLength > 2 ? 110 : otherLength * 50;
return (
<ModalDialogContainer>
<ModalDialog
visible={visible}
onClose={onClose}
headerContent={t("DownloadAs")}
bodyContent={
<>
<Text>{t("ChooseFormatText")}</Text>
{documents.length > 0 && (
<DownloadContent
t={t}
checkedTitle={checkedDocTitle}
indeterminateTitle={indeterminateDocTitle}
items={documents}
formatKeys={formatKeys}
onSelectFormat={this.onSelectFormat}
onRowSelect={this.onRowSelect}
getItemIcon={this.getItemIcon}
titleFormat={documentsTitleFormat}
type="document"
/>
)}
{spreadsheets.length > 0 && (
<DownloadContent
t={t}
checkedTitle={checkedSpreadsheetTitle}
indeterminateTitle={indeterminateSpreadsheetTitle}
items={spreadsheets}
formatKeys={formatKeys}
onSelectFormat={this.onSelectFormat}
onRowSelect={this.onRowSelect}
getItemIcon={this.getItemIcon}
titleFormat={spreadsheetsTitleFormat}
type="spreadsheet"
/>
)}
{presentations.length > 0 && (
<DownloadContent
t={t}
checkedTitle={checkedPresentationTitle}
indeterminateTitle={indeterminatePresentationTitle}
items={presentations}
formatKeys={formatKeys}
onSelectFormat={this.onSelectFormat}
onRowSelect={this.onRowSelect}
getItemIcon={this.getItemIcon}
titleFormat={presentationsTitleFormat}
type="presentation"
/>
)}
{otherLength > 0 && (
<>
{showOther && (
<Row
key="title2"
onSelect={this.onRowSelect.bind(this, "All", "other")}
checked={checkedOtherTitle}
indeterminate={indeterminateOtherTitle}
>
<RowContent>
<Text
truncate
type="page"
title={"Other"}
fontSize="14px"
>
{t("Other")}
</Text>
<></>
</RowContent>
</Row>
)}
<RowContainer
useReactWindow
style={{ minHeight: minHeight, padding: "8px 0" }}
itemHeight={50}
>
{other.map((folder) => {
const element = this.getItemIcon(folder);
return (
<Row
key={folder.id}
onSelect={this.onRowSelect.bind(
this,
folder,
"other"
)}
checked={folder.checked}
element={element}
>
<RowContent>
<Text
truncate
type="page"
title={folder.title}
fontSize="14px"
>
{folder.title}
</Text>
<></>
<Text fontSize="12px" containerWidth="auto">
{folder.fileExst && t("OriginalFormat")}
</Text>
</RowContent>
</Row>
);
})}
</RowContainer>
</>
)}
<Text>{t("ConvertToZip")}</Text>
<Text>{t("ConvertMessage")}</Text>
</>
}
footerContent={
<>
<Button
key="DownloadButton"
label={t("DownloadButton")}
size="medium"
primary
onClick={this.onDownload}
//isLoading={isLoading}
/>
<Button
className="button-dialog"
key="CancelButton"
label={t("CancelButton")}
size="medium"
onClick={onClose}
//isLoading={isLoading}
/>
</>
}
/>
</ModalDialogContainer>
);
}
}
const ModalDialogContainerTranslated = withTranslation()(
DownloadDialogComponent
);
const DownloadDialog = (props) => (
<ModalDialogContainerTranslated i18n={i18n} {...props} />
);
const mapStateToProps = (state) => {
const { selection } = state.files;
return {
items: selection,
};
};
export default connect(mapStateToProps, {})(withRouter(DownloadDialog));

View File

@ -0,0 +1,15 @@
{
"DownloadAs": "Download as",
"OriginalFormat": "Original format",
"CustomFormat": "Custom format",
"ChooseFormatText": "Choose format for each file to be downloaded",
"Documents": "Documents",
"Spreadsheets": "Spreadsheets",
"Presentations": "Presentations",
"ConvertInto": "Convert into",
"Other": "Other",
"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"
}

View File

@ -0,0 +1,15 @@
{
"DownloadAs": "Скачать как",
"OriginalFormat": "Исходный формат",
"CustomFormat": "Пользовательский формат",
"ChooseFormatText": "Выберите формат для каждого из файлов, которые нужно скачать",
"Documents": "Документы",
"Spreadsheets": "Таблицы",
"Presentations": "Презентации",
"ConvertInto": "Сконвертировать в",
"Other": "Другой",
"ConvertToZip": "Файлы будут упакованы в .zip файл",
"ConvertMessage": "Если вы решите сконвертировать файл в формат, отличный от исходного, некоторые данные могут быть потеряны.",
"DownloadButton": "Скачать",
"CancelButton": "Отмена"
}

View File

@ -1,4 +1,5 @@
import EmptyTrashDialog from "./EmptyTrashDialog";
import DeleteDialog from "./DeleteDialog";
import DownloadDialog from "./DownloadDialog";
export { EmptyTrashDialog, DeleteDialog };
export { EmptyTrashDialog, DeleteDialog, DownloadDialog };

View File

@ -13,7 +13,7 @@ import {
} from "asc-web-components";
import { fetchFiles, setAction } from "../../../../../store/files/actions";
import { default as filesStore } from "../../../../../store/store";
import { EmptyTrashDialog, DeleteDialog } from "../../../../dialogs";
import { EmptyTrashDialog, DeleteDialog, DownloadDialog } from "../../../../dialogs";
import { SharingPanel } from "../../../../panels";
import { isCanBeDeleted, checkFolderType } from "../../../../../store/files/selectors";
@ -102,6 +102,7 @@ class SectionHeaderContent extends React.Component {
this.state = {
showSharingPanel: false,
showDeleteDialog: false,
showDownloadDialog: false,
showEmptyTrashDialog: false
};
}
@ -167,7 +168,7 @@ class SectionHeaderContent extends React.Component {
downloadAction = () => toastr.info("downloadAction click");
downloadAsAction = () => toastr.info("downloadAsAction click");
downloadAsAction = () => this.setState({ showDownloadDialog: !this.state.showDownloadDialog });
renameAction = () => toastr.info("renameAction click");
@ -435,6 +436,13 @@ class SectionHeaderContent extends React.Component {
visible={showSharingPanel}
/>
)}
{showDownloadDialog && (
<DownloadDialog
visible={showDownloadDialog}
onClose={this.downloadAsAction}
/>
)}
</StyledContainer>
);
}

View File

@ -29,7 +29,9 @@ const SharingRow = (props) => {
onShowEmbeddingPanel,
} = props;
const linkVisible = selection && selection.length === 1 && item.shareLink;
const linkVisible =
(selection && selection.length === 1 && item.shareLink) ||
(!selection.length && item.shareLink);
const onCopyInternalLink = () => {
const internalLink = item.shareLink.split("&");