Merge branch 'develop' into bugfix/thirdparty

This commit is contained in:
Alexey Kostenko 2021-04-27 11:25:30 +03:00
commit bf963f511b
70 changed files with 2520 additions and 2303 deletions

View File

@ -60,6 +60,8 @@ servers_products_name_backend=(ASC.CRM)
servers_products_name_backend+=(ASC.Files)
servers_products_name_backend+=(ASC.People)
servers_products_name_backend+=(ASC.Projects)
servers_products_name_backend+=(ASC.Calendar)
servers_products_name_backend+=(ASC.Mail)
# Publish server backend products
for i in ${!servers_products_name_backend[@]}; do

View File

@ -47,10 +47,12 @@
# service host #
API_SYSTEM_HOST=${CONTAINER_PREFIX}api-system
BACKUP_HOST=${CONTAINER_PREFIX}backup
CALENDAR_HOST=${CONTAINER_PREFIX}calendar
CRM_HOST=${CONTAINER_PREFIX}crm
STORAGE_ENCRYPTION_HOST=${CONTAINER_PREFIX}storage-encryption
FILES_HOST=${CONTAINER_PREFIX}files
FILES_SERVICES_HOST=${CONTAINER_PREFIX}files-services
MAIL_HOST=${CONTAINER_PREFIX}mail
STORAGE_MIGRATION_HOST=${CONTAINER_PREFIX}storage-migration
NOTIFY_HOST=${CONTAINER_PREFIX}notify
PEOPLE_SERVER_HOST=${CONTAINER_PREFIX}people-server
@ -68,9 +70,11 @@
SERVICE_API_SYSTEM=${API_SYSTEM_HOST}:${SERVICE_PORT}
SERVICE_BACKUP=${BACKUP_HOST}:${SERVICE_PORT}
SERVICE_CRM=${CRM_HOST}:${SERVICE_PORT}
SERVICE_CALENDAR=${CALENDAR_HOST}:${SERVICE_PORT}
SERVICE_STORAGE_ENCRYPTION=${STORAGE_ENCRYPTION_HOST}:${SERVICE_PORT}
SERVICE_FILES=${FILES_HOST}:${SERVICE_PORT}
SERVICE_FILES_SERVICES=${FILES_SERVICES_HOST}:${SERVICE_PORT}
SERVICE_MAIL=${MAIL_HOST}:${SERVICE_PORT}
SERVICE_STORAGE_MIGRATION=${STORAGE_MIGRATION_HOST}:${SERVICE_PORT}
SERVICE_NOTIFY=${NOTIFY_HOST}:${SERVICE_PORT}
SERVICE_PEOPLE_SERVER=${PEOPLE_SERVER_HOST}:${SERVICE_PORT}

View File

@ -45,11 +45,8 @@ RUN echo "nameserver 8.8.8.8" | tee /etc/resolv.conf > /dev/null && \
bash build-frontend.sh -sp ${SRC_PATH} && \
bash build-backend.sh -sp ${SRC_PATH} -ar "--disable-parallel" && \
bash publish-backend.sh -sp ${SRC_PATH} -bp ${BUILD_PATH} -ar "--disable-parallel"
COPY config/mysql/conf.d/mysql.cnf /etc/mysql/conf.d/mysql.cnf
COPY config/supervisor/supervisord.conf /etc/supervisor/conf.d/supervisord.conf
RUN sed -i 's/Server=.*;Port=/Server=127.0.0.1;Port=/' /app/onlyoffice/config/appsettings.test.json
COPY config/mysql/conf.d/mysql.cnf /etc/mysql/conf.d/mysql.cnf
RUN rm -rf /var/lib/apt/lists/*
@ -107,6 +104,8 @@ COPY --from=base ${SRC_PATH}/web/ASC.Web.Login/dist ${BUILD_PATH}/studio/login
COPY --from=base ${SRC_PATH}/products/ASC.People/Client/dist ${BUILD_PATH}/products/ASC.People/client
COPY --from=base ${SRC_PATH}/products/ASC.Projects/Client/dist ${BUILD_PATH}/products/ASC.Projects/client
COPY --from=base ${SRC_PATH}/web/ASC.Web.Client/dist ${BUILD_PATH}/studio/client
COPY --from=base ${SRC_PATH}/products/ASC.Calendar/Client/dist ${BUILD_PATH}/products/ASC.Calendar/client
COPY --from=base ${SRC_PATH}/products/ASC.Mail/Client/dist ${BUILD_PATH}/products/ASC.Mail/client
COPY /config/nginx/templates/upstream.conf.template /etc/nginx/templates/upstream.conf.template
@ -122,6 +121,8 @@ RUN chown nginx:nginx /etc/nginx/* -R && \
sed -i 's/localhost:5020/$service_projects_server/' /etc/nginx/conf.d/onlyoffice.conf && \
sed -i 's/localhost:5000/$service_api/' /etc/nginx/conf.d/onlyoffice.conf && \
sed -i 's/localhost:5003/$service_studio/' /etc/nginx/conf.d/onlyoffice.conf && \
sed -i 's/localhost:5023/$service_calendar/' /etc/nginx/conf.d/onlyoffice.conf && \
sed -i 's/localhost:5022/$service_mail/' /etc/nginx/conf.d/onlyoffice.conf && \
sed -i 's/localhost:9999/$service_urlshortener/' /etc/nginx/conf.d/onlyoffice.conf && \
sed -i 's/172.*/$document_server;/' /etc/nginx/conf.d/onlyoffice.conf && \
# configute the image nginx whith less privileged https://hub.docker.com/_/nginx
@ -146,6 +147,15 @@ COPY --from=base --chown=onlyoffice:onlyoffice ${BUILD_PATH}/services/ASC.Data.B
CMD ["ASC.Data.Backup.dll", "ASC.Data.Backup", "core:products:folder=/var/www/products/", "core:products:subfolder=server"]
## ASC.Calendar ##
FROM builder AS calendar
WORKDIR ${BUILD_PATH}/products/ASC.Calendar/server/
COPY --chown=onlyoffice:onlyoffice docker-entrypoint.sh .
COPY --from=base --chown=onlyoffice:onlyoffice ${BUILD_PATH}/products/ASC.Calendar/server/ .
CMD ["ASC.Calendar.dll", "ASC.Calendar"]
## ASC.CRM ##
FROM builder AS crm
WORKDIR ${BUILD_PATH}/products/ASC.CRM/server/
@ -182,6 +192,15 @@ COPY --from=base --chown=onlyoffice:onlyoffice ${BUILD_PATH}/services/ASC.Files.
CMD ["ASC.Files.Service.dll", "ASC.Files.Service", "core:products:folder=/var/www/products/", "core:products:subfolder=server"]
## ASC.Mail ##
FROM builder AS mail
WORKDIR ${BUILD_PATH}/products/ASC.Mail/server/
COPY --chown=onlyoffice:onlyoffice docker-entrypoint.sh .
COPY --from=base --chown=onlyoffice:onlyoffice ${BUILD_PATH}/products/ASC.Mail/server/ .
CMD ["ASC.Mail.dll", "ASC.Mail"]
## ASC.Data.Storage.Migration ##
FROM builder AS data_storage_migration
WORKDIR ${BUILD_PATH}/services/storage.migration/service/

View File

@ -29,6 +29,9 @@ x-service:
- people_data:/var/www/products/ASC.People/server/
- crm_data:/var/www/products/ASC.CRM/server/
- project_data:/var/www/products/ASC.Projects/server/
- calendar_data:/var/www/products/ASC.Calendar/server/
- mail_data:/var/www/products/ASC.Mail/server/
services:
onlyoffice-elasticsearch:
@ -95,6 +98,11 @@ services:
image: "${REPO}/${STATUS}appserver-backup:${SRV_VERSION}"
container_name: ${BACKUP_HOST}
onlyoffice-calendar:
<<: *x-service-base
image: "${REPO}/${STATUS}appserver-calendar:${SRV_VERSION}"
container_name: ${CALENDAR_HOST}
onlyoffice-crm:
<<: *x-service-base
image: "${REPO}/${STATUS}appserver-crm:${SRV_VERSION}"
@ -115,6 +123,11 @@ services:
image: "${REPO}/${STATUS}appserver-files-services:${SRV_VERSION}"
container_name: ${FILES_SERVICES_HOST}
onlyoffice-mail:
<<: *x-service-base
image: "${REPO}/${STATUS}appserver-mail:${SRV_VERSION}"
container_name: ${MAIL_HOST}
onlyoffice-storage-migration:
<<: *x-service-base
image: "${REPO}/${STATUS}appserver-storage-migration:${SRV_VERSION}"
@ -187,10 +200,12 @@ services:
depends_on:
- onlyoffice-api-system
- onlyoffice-backup
- onlyoffice-calendar
- onlyoffice-crm
- onlyoffice-storage-encryption
- onlyoffice-files
- onlyoffice-files-services
- onlyoffice-mail
- onlyoffice-storage-migration
- onlyoffice-people-server
- onlyoffice-projects-server
@ -204,10 +219,12 @@ services:
environment:
- SERVICE_API_SYSTEM=${SERVICE_API_SYSTEM}
- SERVICE_BACKUP=${SERVICE_BACKUP}
- SERVICE_CALENDAR=${SERVICE_CALENDAR}
- SERVICE_CRM=${SERVICE_CRM}
- SERVICE_STORAGE_ENCRYPTION=${SERVICE_STORAGE_ENCRYPTION}
- SERVICE_FILES=${SERVICE_FILES}
- SERVICE_FILES_SERVICES=${SERVICE_FILES_SERVICES}
- SERVICE_MAIL=${SERVICE_MAIL}
- SERVICE_STORAGE_MIGRATION=${SERVICE_STORAGE_MIGRATION}
- SERVICE_NOTIFY=${SERVICE_NOTIFY}
- SERVICE_PEOPLE_SERVER=${SERVICE_PEOPLE_SERVER}
@ -240,3 +257,5 @@ volumes:
people_data:
crm_data:
project_data:
calendar_data:
mail_data:

View File

@ -15,6 +15,13 @@ services:
target: backup
image: "${REPO}/${STATUS}appserver-backup:${SRV_VERSION}"
onlyoffice-calendar:
build:
context: ./
dockerfile: "${DOCKERFILE}"
target: calendar
image: "${REPO}/${STATUS}appserver-calendar:${SRV_VERSION}"
onlyoffice-crm:
build:
context: ./
@ -43,6 +50,13 @@ services:
target: files_services
image: "${REPO}/${STATUS}appserver-files-services:${SRV_VERSION}"
onlyoffice-mail:
build:
context: ./
dockerfile: "${DOCKERFILE}"
target: mail
image: "${REPO}/${STATUS}appserver-mail:${SRV_VERSION}"
onlyoffice-storage-migration:
build:
context: ./

View File

@ -10,6 +10,11 @@ map $SERVICE_BACKUP $service_backup {
$SERVICE_BACKUP $SERVICE_BACKUP;
}
map $SERVICE_CALENDAR $service_calendar {
volatile;
$SERVICE_CALENDAR $SERVICE_CALENDAR;
}
map $SERVICE_CRM $service_crm {
volatile;
$SERVICE_CRM $SERVICE_CRM;
@ -30,6 +35,11 @@ map $SERVICE_FILES_SERVICES $service_files_services {
$SERVICE_FILES_SERVICES $SERVICE_FILES_SERVICES;
}
map $SERVICE_MAIL $service_mail {
volatile;
$SERVICE_MAIL $SERVICE_MAIL;
}
map $SERVICE_STORAGE_MIGRATION $service_storage_migration {
volatile;
$SERVICE_STORAGE_MIGRATION $SERVICE_STORAGE_MIGRATION;

View File

@ -29,6 +29,8 @@ x-service:
- people_data:/var/www/products/ASC.People/server/
- crm_data:/var/www/products/ASC.CRM/server/
- project_data:/var/www/products/ASC.Projects/server/
- calendar_data:/var/www/products/ASC.Calendar/server/
- mail_data:/var/www/products/ASC.Mail/server/
services:
onlyoffice-notify:
@ -47,3 +49,5 @@ volumes:
people_data:
crm_data:
project_data:
calendar_data:
mail_data:

View File

@ -11,7 +11,6 @@ const Badge = (props) => {
if (!props.onClick) return;
e.preventDefault();
e.stopPropagation();
props.onClick(e);
};

View File

@ -80,8 +80,8 @@ class ContextMenuButton extends React.Component {
}
}
onIconButtonClick = () => {
if (this.props.isDisabled) {
onIconButtonClick = (e) => {
if (this.props.isDisabled || this.props.isNew) {
this.stopAction;
return;
}
@ -95,7 +95,7 @@ class ContextMenuButton extends React.Component {
!this.props.isDisabled &&
this.state.isOpen &&
this.props.onClick &&
this.props.onClick()
this.props.onClick(e)
); // eslint-disable-line react/prop-types
};
@ -125,12 +125,17 @@ class ContextMenuButton extends React.Component {
}
callNewMenu = (e) => {
if (this.props.isDisabled) {
if (this.props.isDisabled || !this.props.isNew) {
this.stopAction;
return;
}
this.props.isNew && this.props.onClick(e);
this.setState(
{
data: this.props.getData(),
},
() => this.props.onClick(e)
);
};
render() {

View File

@ -12,9 +12,9 @@ import "./custom.scss";
import i18n from "./i18n";
import { I18nextProvider } from "react-i18next";
import { regDesktop } from "@appserver/common/desktop";
import Home from "./components/pages/Home";
import Settings from "./components/pages/Settings";
import VersionHistory from "./components/pages/VersionHistory";
import Home from "./pages/Home";
import Settings from "./pages/Settings";
import VersionHistory from "./pages/VersionHistory";
import ErrorBoundary from "@appserver/common/components/ErrorBoundary";
import Panels from "./components/FilesPanels";
import { AppServerConfig } from "@appserver/common/constants";

View File

@ -0,0 +1,269 @@
import React from "react";
import { inject, observer } from "mobx-react";
import {
ShareAccessRights,
AppServerConfig,
} from "@appserver/common/constants";
import toastr from "studio/toastr";
import { combineUrl } from "@appserver/common/utils";
import {
convertFile,
getFileConversationProgress,
} from "@appserver/common/api/files";
import Badges from "../components/Badges";
import config from "../../package.json";
export default function withBadges(WrappedComponent) {
class WithBadges extends React.Component {
constructor(props) {
super(props);
this.state = { showConvertDialog: false };
}
onClickLock = () => {
const { item, lockFileAction } = this.props;
const { locked, id } = item;
lockFileAction(id, !locked).catch((err) => toastr.error(err));
};
onClickFavorite = () => {
const { t, item, setFavoriteAction } = this.props;
setFavoriteAction("remove", item.id)
.then(() => toastr.success(t("RemovedFromFavorites")))
.catch((err) => toastr.error(err));
};
onShowVersionHistory = () => {
const {
homepage,
isTabletView,
item,
setIsVerHistoryPanel,
fetchFileVersions,
history,
isTrashFolder,
} = this.props;
if (isTrashFolder) return;
if (!isTabletView) {
fetchFileVersions(item.id + "");
setIsVerHistoryPanel(true);
} else {
history.push(
combineUrl(AppServerConfig.proxyURL, homepage, `/${item.id}/history`)
);
}
};
onBadgeClick = () => {
const {
item,
selectedFolderPathParts,
markAsRead,
setNewFilesPanelVisible,
setNewFilesIds,
updateRootBadge,
updateFileBadge,
} = this.props;
if (item.fileExst) {
markAsRead([], [item.id])
.then(() => {
updateRootBadge(selectedFolderPathParts[0], 1);
updateFileBadge(item.id);
})
.catch((err) => toastr.error(err));
} else {
setNewFilesPanelVisible(true);
const newFolderIds = selectedFolderPathParts;
newFolderIds.push(item.id);
setNewFilesIds(newFolderIds);
}
};
setConvertDialogVisible = () =>
this.setState({ showConvertDialog: !this.state.showConvertDialog });
onConvert = () => {
const { item, t, setSecondaryProgressBarData } = this.props;
setSecondaryProgressBarData({
icon: "file",
visible: true,
percent: 0,
label: t("Convert"),
alert: false,
});
this.setState({ showConvertDialog: false }, () =>
convertFile(item.id).then((convertRes) => {
if (convertRes && convertRes[0] && convertRes[0].progress !== 100) {
this.getConvertProgress(item.id);
}
})
);
};
getConvertProgress = (fileId) => {
const {
selectedFolderId,
filter,
setIsLoading,
setSecondaryProgressBarData,
t,
clearSecondaryProgressData,
fetchFiles,
} = this.props;
getFileConversationProgress(fileId).then((res) => {
if (res && res[0] && res[0].progress !== 100) {
setSecondaryProgressBarData({
icon: "file",
visible: true,
percent: res[0].progress,
label: t("Convert"),
alert: false,
});
setTimeout(() => this.getConvertProgress(fileId), 1000);
} else {
if (res[0].error) {
setSecondaryProgressBarData({
visible: true,
alert: true,
});
toastr.error(res[0].error);
setTimeout(() => clearSecondaryProgressData(), TIMEOUT);
} else {
setSecondaryProgressBarData({
icon: "file",
visible: true,
percent: 100,
label: t("Convert"),
alert: false,
});
setTimeout(() => clearSecondaryProgressData(), TIMEOUT);
const newFilter = filter.clone();
fetchFiles(selectedFolderId, newFilter)
.catch((err) => {
setSecondaryProgressBarData({
visible: true,
alert: true,
});
//toastr.error(err);
setTimeout(() => clearSecondaryProgressData(), TIMEOUT);
})
.finally(() => setIsLoading(false));
}
}
});
};
render() {
const { showConvertDialog } = this.state;
const {
item,
canWebEdit,
isTrashFolder,
canConvert,
onFilesClick, // from withFileAction HOC
} = this.props;
const { fileStatus, access } = item;
const newItems = item.new || fileStatus === 2;
const showNew = !!newItems;
const accessToEdit =
access === ShareAccessRights.FullAccess ||
access === ShareAccessRights.None; // TODO: fix access type for owner (now - None)
const badgesComponent = (
<Badges
item={item}
showNew={showNew}
newItems={newItems}
canWebEdit={canWebEdit}
canConvert={canConvert}
isTrashFolder={isTrashFolder}
accessToEdit={accessToEdit}
onClickLock={this.onClickLock}
onClickFavorite={this.onClickFavorite}
onShowVersionHistory={this.onShowVersionHistory}
onBadgeClick={this.onBadgeClick}
setConvertDialogVisible={this.setConvertDialogVisible}
onFilesClick={onFilesClick}
/>
);
return (
<>
{showConvertDialog && (
<ConvertDialog
visible={showConvertDialog}
onClose={this.setConvertDialogVisible}
onConvert={this.onConvert}
/>
)}
<WrappedComponent badgesComponent={badgesComponent} {...this.props} />
</>
);
}
}
return inject(
(
{
auth,
formatsStore,
treeFoldersStore,
filesActionsStore,
versionHistoryStore,
selectedFolderStore,
dialogsStore,
filesStore,
uploadDataStore,
},
{ item }
) => {
const { docserviceStore } = formatsStore;
const { isRecycleBinFolder, updateRootBadge } = treeFoldersStore;
const {
lockFileAction,
setFavoriteAction,
markAsRead,
} = filesActionsStore;
const { isTabletView } = auth.settingsStore;
const { setIsVerHistoryPanel, fetchFileVersions } = versionHistoryStore;
const { setNewFilesPanelVisible, setNewFilesIds } = dialogsStore;
const { updateFileBadge, filter, setIsLoading, fetchFiles } = filesStore;
const { secondaryProgressDataStore } = uploadDataStore;
const {
setSecondaryProgressBarData,
clearSecondaryProgressData,
} = secondaryProgressDataStore;
const canWebEdit = docserviceStore.canWebEdit(item.fileExst);
const canConvert = docserviceStore.canConvert(item.fileExst);
return {
canWebEdit,
canConvert,
isTrashFolder: isRecycleBinFolder,
lockFileAction,
setFavoriteAction,
homepage: config.homepage,
isTabletView,
setIsVerHistoryPanel,
fetchFileVersions,
selectedFolderPathParts: selectedFolderStore.pathParts,
markAsRead,
setNewFilesPanelVisible,
setNewFilesIds,
updateRootBadge,
updateFileBadge,
setSecondaryProgressBarData,
selectedFolderId: selectedFolderStore.id,
filter,
setIsLoading,
clearSecondaryProgressData,
fetchFiles,
};
}
)(observer(WithBadges));
}

View File

@ -0,0 +1,364 @@
import React from "react";
import { inject, observer } from "mobx-react";
import { Trans } from "react-i18next";
import { isMobile } from "react-device-detect";
import toastr from "studio/toastr";
import {
AppServerConfig,
FileAction,
ShareAccessRights,
} from "@appserver/common/constants";
import { combineUrl } from "@appserver/common/utils";
import config from "../../package.json";
import EditingWrapperComponent from "../components/EditingWrapperComponent";
import { getTitleWithoutExst } from "../helpers/files-helpers";
export default function withContent(WrappedContent) {
class WithContent extends React.Component {
constructor(props) {
super(props);
let titleWithoutExt = getTitleWithoutExst(props.item);
if (props.fileActionId === -1) {
titleWithoutExt = this.getDefaultName(props.fileActionExt);
}
this.state = {
itemTitle: titleWithoutExt,
//loading: false
};
}
componentDidUpdate(prevProps) {
const { fileActionId, fileActionExt } = this.props;
if (fileActionId === -1 && fileActionExt !== prevProps.fileActionExt) {
const itemTitle = this.getDefaultName(fileActionExt);
this.setState({ itemTitle });
}
// if (fileAction) {
// if (fileActionId !== prevProps.fileActionId) {
// this.setState({ editingId: fileActionId });
// }
// }
}
getDefaultName = (format) => {
const { t } = this.props;
switch (format) {
case "docx":
return t("NewDocument");
case "xlsx":
return t("NewSpreadsheet");
case "pptx":
return t("NewPresentation");
default:
return t("NewFolder");
}
};
completeAction = (id) => {
const { editCompleteAction, item } = this.props;
const isCancel =
(id.currentTarget && id.currentTarget.dataset.action === "cancel") ||
id.keyCode === 27;
editCompleteAction(id, item, isCancel);
};
updateItem = () => {
const {
t,
updateFile,
renameFolder,
item,
setIsLoading,
fileActionId,
editCompleteAction,
} = this.props;
const { itemTitle } = this.state;
const originalTitle = getTitleWithoutExst(item);
setIsLoading(true);
const isSameTitle =
originalTitle.trim() === itemTitle.trim() || itemTitle.trim() === "";
if (isSameTitle) {
this.setState({
itemTitle: originalTitle,
});
return editCompleteAction(fileActionId, item, isSameTitle);
}
item.fileExst || item.contentLength
? updateFile(fileActionId, itemTitle)
.then(() => this.completeAction(fileActionId))
.then(() =>
toastr.success(
t("FileRenamed", {
oldTitle: item.title,
newTitle: itemTitle + item.fileExst,
})
)
)
.catch((err) => toastr.error(err))
.finally(() => setIsLoading(false))
: renameFolder(fileActionId, itemTitle)
.then(() => this.completeAction(fileActionId))
.then(() =>
toastr.success(
t("FolderRenamed", {
folderTitle: item.title,
newFoldedTitle: itemTitle,
})
)
)
.catch((err) => toastr.error(err))
.finally(() => setIsLoading(false));
};
cancelUpdateItem = (e) => {
const { item } = this.props;
const originalTitle = getTitleWithoutExst(item);
this.setState({
itemTitle: originalTitle,
});
return this.completeAction(e);
};
onClickUpdateItem = (e) => {
const { fileActionType } = this.props;
fileActionType === FileAction.Create
? this.createItem(e)
: this.updateItem(e);
};
createItem = (e) => {
const {
createFile,
item,
setIsLoading,
openDocEditor,
isPrivacy,
isDesktop,
replaceFileStream,
t,
setEncryptionAccess,
createFolder,
} = this.props;
const { itemTitle } = this.state;
setIsLoading(true);
const itemId = e.currentTarget.dataset.itemid;
if (itemTitle.trim() === "") {
toastr.warning(t("CreateWithEmptyTitle"));
return this.completeAction(itemId);
}
let tab =
!isDesktop && item.fileExst
? window.open(
combineUrl(
AppServerConfig.proxyURL,
config.homepage,
"/products/files/doceditor"
),
"_blank"
)
: null;
!item.fileExst && !item.contentLength
? createFolder(item.parentId, itemTitle)
.then(() => this.completeAction(itemId))
.then(() =>
toastr.success(
<Trans t={t} i18nKey="FolderCreated" ns="Home">
New folder {{ itemTitle }} is created
</Trans>
)
)
.catch((e) => toastr.error(e))
.finally(() => {
return setIsLoading(false);
})
: createFile(item.parentId, `${itemTitle}.${item.fileExst}`)
.then((file) => {
if (isPrivacy) {
return setEncryptionAccess(file).then((encryptedFile) => {
if (!encryptedFile) return Promise.resolve();
toastr.info(t("EncryptedFileSaving"));
return replaceFileStream(
file.id,
encryptedFile,
true,
false
).then(() =>
openDocEditor(file.id, file.providerKey, tab, file.webUrl)
);
});
}
return openDocEditor(file.id, file.providerKey, tab, file.webUrl);
})
.then(() => this.completeAction(itemId))
.then(() => {
const exst = item.fileExst;
return toastr.success(
<Trans i18nKey="FileCreated" ns="Home">
New file {{ itemTitle }}.{{ exst }} is created
</Trans>
);
})
.catch((e) => toastr.error(e))
.finally(() => {
return setIsLoading(false);
});
};
renameTitle = (e) => {
const { t } = this.props;
let title = e.target.value;
//const chars = '*+:"<>?|/'; TODO: think how to solve problem with interpolation escape values in i18n translate
const regexp = new RegExp('[*+:"<>?|\\\\/]', "gim");
if (title.match(regexp)) {
toastr.warning(t("ContainsSpecCharacter"));
}
title = title.replace(regexp, "_");
return this.setState({ itemTitle: title });
};
getStatusByDate = () => {
const { culture, t, item, sectionWidth } = this.props;
const { created, updated, version, fileExst } = item;
const title =
version > 1
? t("TitleModified")
: fileExst
? t("TitleUploaded")
: t("TitleCreated");
const date = fileExst ? updated : created;
const dateLabel = new Date(date).toLocaleString(culture);
const mobile = (sectionWidth && sectionWidth <= 375) || isMobile;
return mobile ? dateLabel : `${title}: ${dateLabel}`;
};
render() {
const { itemTitle } = this.state;
const {
item,
fileActionId,
fileActionExt,
isLoading,
viewer,
t,
isTrashFolder,
onFilesClick,
} = this.props;
const { id, fileExst, updated, createdBy, access, fileStatus } = item;
const titleWithoutExt = getTitleWithoutExst(item);
const isEdit = id === fileActionId && fileExst === fileActionExt;
const updatedDate = updated && this.getStatusByDate();
const fileOwner =
createdBy &&
((viewer.id === createdBy.id && t("AuthorMe")) ||
createdBy.displayName);
const accessToEdit =
access === ShareAccessRights.FullAccess || // only badges?
access === ShareAccessRights.None; // TODO: fix access type for owner (now - None)
const linkStyles = isTrashFolder //|| window.innerWidth <= 1024
? { noHover: true }
: { onClick: onFilesClick };
const newItems = item.new || fileStatus === 2;
const showNew = !!newItems;
return isEdit ? (
<EditingWrapperComponent
itemTitle={itemTitle}
itemId={id}
isLoading={isLoading}
renameTitle={this.renameTitle}
onClickUpdateItem={this.onClickUpdateItem}
cancelUpdateItem={this.cancelUpdateItem}
/>
) : (
<WrappedContent
titleWithoutExt={titleWithoutExt}
updatedDate={updatedDate}
fileOwner={fileOwner}
accessToEdit={accessToEdit}
linkStyles={linkStyles}
newItems={newItems}
showNew={showNew}
isTrashFolder={isTrashFolder}
onFilesClick={this.onFilesClick}
{...this.props}
/>
);
}
}
return inject(
({ filesActionsStore, filesStore, treeFoldersStore, auth }, {}) => {
const { editCompleteAction } = filesActionsStore;
const {
setIsLoading,
openDocEditor,
updateFile,
renameFolder,
createFile,
createFolder,
isLoading,
} = filesStore;
const { isRecycleBinFolder, isPrivacyFolder } = treeFoldersStore;
const {
type: fileActionType,
extension: fileActionExt,
id: fileActionId,
} = filesStore.fileActionStore;
const { replaceFileStream, setEncryptionAccess } = auth;
const { culture, isDesktopClient } = auth.settingsStore;
return {
editCompleteAction,
setIsLoading,
isTrashFolder: isRecycleBinFolder,
openDocEditor,
updateFile,
renameFolder,
fileActionId,
editCompleteAction,
fileActionType,
createFile,
isPrivacy: isPrivacyFolder,
isDesktop: isDesktopClient,
replaceFileStream,
setEncryptionAccess,
createFolder,
fileActionExt,
isLoading,
culture,
homepage: config.homepage,
viewer: auth.userStore.user,
};
}
)(observer(WithContent));
}

View File

@ -0,0 +1,503 @@
import React from "react";
import { inject, observer } from "mobx-react";
import copy from "copy-to-clipboard";
import { combineUrl } from "@appserver/common/utils";
import { FileAction, AppServerConfig } from "@appserver/common/constants";
import toastr from "studio/toastr";
import config from "../../package.json";
export default function withContextOptions(WrappedComponent) {
class WithContextOptions extends React.Component {
onOpenLocation = () => {
const { item, openLocationAction } = this.props;
const { id, folderId, fileExst } = item;
const locationId = !fileExst ? id : folderId;
openLocationAction(locationId, !fileExst);
};
onOwnerChange = () => {
const { setChangeOwnerPanelVisible } = this.props;
setChangeOwnerPanelVisible(true);
};
onMoveAction = () => {
const { setMoveToPanelVisible } = this.props;
setMoveToPanelVisible(true);
};
onCopyAction = () => {
const { setCopyPanelVisible } = this.props;
setCopyPanelVisible(true);
};
showVersionHistory = () => {
const {
item,
isTabletView,
fetchFileVersions,
setIsVerHistoryPanel,
history,
homepage,
isTrashFolder,
} = this.props;
const { id } = item;
if (isTrashFolder) return;
if (!isTabletView) {
fetchFileVersions(id + "");
setIsVerHistoryPanel(true);
} else {
history.push(
combineUrl(AppServerConfig.proxyURL, homepage, `/${id}/history`)
);
}
};
finalizeVersion = () => {
const { item, finalizeVersionAction } = this.props;
const { id } = item;
finalizeVersionAction(id).catch((err) => toastr.error(err));
};
onClickFavorite = (e) => {
const { item, setFavoriteAction, t } = this.props;
const { id } = item;
const data = (e.currentTarget && e.currentTarget.dataset) || e;
const { action } = data;
setFavoriteAction(action, id)
.then(() =>
action === "mark"
? toastr.success(t("MarkedAsFavorite"))
: toastr.success(t("RemovedFromFavorites"))
)
.catch((err) => toastr.error(err));
};
lockFile = () => {
const { item, lockFileAction } = this.props;
const { id, locked } = item;
lockFileAction(id, !locked).catch((err) => toastr.error(err));
};
onClickLinkForPortal = () => {
const { item, homepage, t } = this.props;
const { fileExst, canOpenPlayer, webUrl } = item;
const isFile = !!fileExst;
copy(
isFile
? canOpenPlayer
? `${window.location.href}&preview=${id}`
: webUrl
: `${window.location.origin + homepage}/filter?folder=${id}`
);
toastr.success(t("LinkCopySuccess"));
};
onClickLinkEdit = () => {
const { item, openDocEditor } = this.props;
const { id, providerKey } = item;
openDocEditor(id, providerKey);
};
onClickDownload = () => {
const { item, downloadAction, t } = this.props;
const { fileExst, contentLength, viewUrl } = item;
const isFile = !!fileExst && contentLength;
isFile
? window.open(viewUrl, "_blank")
: downloadAction(t("ArchivingData")).catch((err) => toastr.error(err));
};
onClickDownloadAs = () => {
const { setDownloadDialogVisible } = this.props;
setDownloadDialogVisible(true);
};
onDuplicate = () => {
const { duplicateAction, t, item } = this.props;
duplicateAction(item, t("CopyOperation")).catch((err) =>
toastr.error(err)
);
};
onClickRename = () => {
const { item, setAction } = this.props;
const { id, fileExst } = item;
setAction({
type: FileAction.Rename,
extension: fileExst,
id,
});
};
onChangeThirdPartyInfo = () => {
const { item, setThirdpartyInfo } = this.props;
const { providerKey } = item;
setThirdpartyInfo(providerKey);
};
onMediaFileClick = (fileId) => {
const { item, setMediaViewerData } = this.props;
const itemId = typeof fileId !== "object" ? fileId : item.id;
setMediaViewerData({ visible: true, id: itemId });
};
onClickDelete = () => {
const {
item,
setRemoveItem,
setDeleteThirdPartyDialogVisible,
confirmDelete,
setDeleteDialogVisible,
t,
deleteFileAction,
deleteFolderAction,
isThirdPartyFolder,
} = this.props;
const { id, title, fileExst, contentLength, folderId, parentId } = item;
if (isThirdPartyFolder) {
const splitItem = id.split("-");
setRemoveItem({ id: splitItem[splitItem.length - 1], title });
setDeleteThirdPartyDialogVisible(true);
return;
}
if (confirmDelete) {
setDeleteDialogVisible(true);
} else {
const translations = {
deleteOperation: t("DeleteOperation"),
};
fileExst || contentLength
? deleteFileAction(id, folderId, translations)
.then(() => toastr.success(t("FileRemoved")))
.catch((err) => toastr.error(err))
: deleteFolderAction(id, parentId, translations)
.then(() => toastr.success(t("FolderRemoved")))
.catch((err) => toastr.error(err));
}
};
onClickShare = () => {
const { onSelectItem, setSharingPanelVisible, item } = this.props;
onSelectItem(item);
setSharingPanelVisible(true);
};
getFilesContextOptions = () => {
const { item, t, isThirdPartyFolder } = this.props;
const { access, contextOptions } = item;
const isSharable = access !== 1 && access !== 0;
return contextOptions.map((option) => {
switch (option) {
case "open":
return {
key: option,
label: t("Open"),
icon: "images/catalog.folder.react.svg",
onClick: this.onOpenLocation,
disabled: false,
};
case "show-version-history":
return {
key: option,
label: t("ShowVersionHistory"),
icon: "images/history.react.svg",
onClick: this.showVersionHistory,
disabled: false,
};
case "finalize-version":
return {
key: option,
label: t("FinalizeVersion"),
icon: "images/history-finalized.react.svg",
onClick: this.finalizeVersion,
disabled: false,
};
case "separator0":
case "separator1":
case "separator2":
case "separator3":
return { key: option, isSeparator: true };
case "open-location":
return {
key: option,
label: t("OpenLocation"),
icon: "images/download-as.react.svg",
onClick: this.onOpenLocation,
disabled: false,
};
case "mark-as-favorite":
return {
key: option,
label: t("MarkAsFavorite"),
icon: "images/favorites.react.svg",
onClick: this.onClickFavorite,
disabled: false,
"data-action": "mark",
action: "mark",
};
case "block-unblock-version":
return {
key: option,
label: t("UnblockVersion"),
icon: "images/lock.react.svg",
onClick: this.lockFile,
disabled: false,
};
case "sharing-settings":
return {
key: option,
label: t("SharingSettings"),
icon: "images/catalog.shared.react.svg",
onClick: this.onClickShare,
disabled: isSharable,
};
case "send-by-email":
return {
key: option,
label: t("SendByEmail"),
icon: "/static/images/mail.react.svg",
disabled: true,
};
case "owner-change":
return {
key: option,
label: t("ChangeOwner"),
icon: "images/catalog.user.react.svg",
onClick: this.onOwnerChange,
disabled: false,
};
case "link-for-portal-users":
return {
key: option,
label: t("LinkForPortalUsers"),
icon: "/static/images/invitation.link.react.svg",
onClick: this.onClickLinkForPortal,
disabled: false,
};
case "edit":
return {
key: option,
label: t("Edit"),
icon: "/static/images/access.edit.react.svg",
onClick: this.onClickLinkEdit,
disabled: false,
};
case "preview":
return {
key: option,
label: t("Preview"),
icon: "EyeIcon",
onClick: this.onClickLinkEdit,
disabled: true,
};
case "view":
return {
key: option,
label: t("View"),
icon: "/static/images/eye.react.svg",
onClick: this.onMediaFileClick,
disabled: false,
};
case "download":
return {
key: option,
label: t("Download"),
icon: "images/download.react.svg",
onClick: this.onClickDownload,
disabled: false,
};
case "download-as":
return {
key: option,
label: t("DownloadAs"),
icon: "images/download-as.react.svg",
onClick: this.onClickDownloadAs,
disabled: false,
};
case "move-to":
return {
key: option,
label: t("MoveTo"),
icon: "images/move.react.svg",
onClick: this.onMoveAction,
disabled: false,
};
case "restore":
return {
key: option,
label: t("Restore"),
icon: "images/move.react.svg",
onClick: this.onMoveAction,
disabled: false,
};
case "copy-to":
return {
key: option,
label: t("Copy"),
icon: "/static/images/copy.react.svg",
onClick: this.onCopyAction,
disabled: false,
};
case "copy":
return {
key: option,
label: t("Duplicate"),
icon: "/static/images/copy.react.svg",
onClick: this.onDuplicate,
disabled: false,
};
case "rename":
return {
key: option,
label: t("Rename"),
icon: "images/rename.react.svg",
onClick: this.onClickRename,
disabled: false,
};
case "change-thirdparty-info":
return {
key: option,
label: t("ThirdPartyInfo"),
icon: "/static/images/access.edit.react.svg",
onClick: this.onChangeThirdPartyInfo,
disabled: false,
};
case "delete":
return {
key: option,
label: isThirdPartyFolder ? t("DeleteThirdParty") : t("Delete"),
icon: "/static/images/catalog.trash.react.svg",
onClick: this.onClickDelete,
disabled: false,
};
case "remove-from-favorites":
return {
key: option,
label: t("RemoveFromFavorites"),
icon: "images/favorites.react.svg",
onClick: this.onClickFavorite,
disabled: false,
"data-action": "remove",
action: "remove",
};
default:
break;
}
return undefined;
});
};
render() {
const { actionType, actionId, actionExtension, item } = this.props;
const { id, fileExst, contextOptions } = item;
const isEdit =
!!actionType && actionId === id && fileExst === actionExtension;
const contextOptionsProps =
!isEdit && contextOptions && contextOptions.length > 0
? {
contextOptions: this.getFilesContextOptions(),
}
: {};
return (
<WrappedComponent
contextOptionsProps={contextOptionsProps}
{...this.props}
/>
);
}
}
return inject(
(
{
filesStore,
filesActionsStore,
auth,
versionHistoryStore,
mediaViewerDataStore,
settingsStore,
selectedFolderStore,
dialogsStore,
treeFoldersStore,
},
{ item }
) => {
const { openDocEditor, fileActionStore } = filesStore;
const {
openLocationAction,
finalizeVersionAction,
setFavoriteAction,
lockFileAction,
downloadAction,
duplicateAction,
setThirdpartyInfo,
deleteFileAction,
deleteFolderAction,
onSelectItem,
} = filesActionsStore;
const {
setChangeOwnerPanelVisible,
setMoveToPanelVisible,
setCopyPanelVisible,
setDownloadDialogVisible,
setRemoveItem,
setDeleteThirdPartyDialogVisible,
setDeleteDialogVisible,
setSharingPanelVisible,
} = dialogsStore;
const { isTabletView } = auth.settingsStore;
const { setIsVerHistoryPanel, fetchFileVersions } = versionHistoryStore;
const { setAction, type, extension, id } = fileActionStore;
const { setMediaViewerData } = mediaViewerDataStore;
const { isRootFolder } = selectedFolderStore;
const { isRecycleBinFolder } = treeFoldersStore;
const isThirdPartyFolder = item.providerKey && isRootFolder;
return {
openLocationAction,
setChangeOwnerPanelVisible,
setMoveToPanelVisible,
setCopyPanelVisible,
isTabletView,
setIsVerHistoryPanel,
fetchFileVersions,
homepage: config.homepage,
finalizeVersionAction,
setFavoriteAction,
lockFileAction,
openDocEditor,
downloadAction,
setDownloadDialogVisible,
duplicateAction,
setAction,
setThirdpartyInfo,
setMediaViewerData,
setRemoveItem,
setDeleteThirdPartyDialogVisible,
confirmDelete: settingsStore.confirmDelete,
setDeleteDialogVisible,
deleteFileAction,
deleteFolderAction,
isThirdPartyFolder,
onSelectItem,
setSharingPanelVisible,
actionType: type,
actionId: id,
actionExtension: extension,
isTrashFolder: isRecycleBinFolder,
};
}
)(observer(WithContextOptions));
}

View File

@ -0,0 +1,339 @@
import React from "react";
import { inject, observer } from "mobx-react";
import { ReactSVG } from "react-svg";
import IconButton from "@appserver/components/icon-button";
import Text from "@appserver/components/text";
import { EncryptedFileIcon } from "../components/Icons";
const svgLoader = () => <div style={{ width: "24px" }}></div>;
export default function withFileActions(WrappedFileItem) {
class WithFileActions extends React.Component {
onContentRowSelect = (checked, file) => {
const { selectRowAction } = this.props;
if (!file) return;
selectRowAction(checked, file);
};
onClickShare = () => {
const { onSelectItem, setSharingPanelVisible, item } = this.props;
onSelectItem(item);
setSharingPanelVisible(true);
};
rowContextClick = () => {
const { onSelectItem, item } = this.props;
onSelectItem(item);
};
getSharedButton = (shared) => {
const { t } = this.props;
const color = shared ? "#657077" : "#a3a9ae";
return (
<Text
className="share-button"
as="span"
title={t("Share")}
fontSize="12px"
fontWeight={600}
color={color}
display="inline-flex"
onClick={this.onClickShare}
>
<IconButton
className="share-button-icon"
color={color}
hoverColor="#657077"
size={18}
iconName="images/catalog.shared.react.svg"
/>
{t("Share")}
</Text>
);
};
getItemIcon = (isEdit) => {
const { item, isPrivacy } = this.props;
const { icon, fileExst } = item;
return (
<>
<ReactSVG
className={`react-svg-icon${isEdit ? " is-edit" : ""}`}
src={icon}
loading={svgLoader}
/>
{isPrivacy && fileExst && <EncryptedFileIcon isEdit={isEdit} />}
</>
);
};
onDropZoneUpload = (files, uploadToFolder) => {
const {
selectedFolderId,
dragging,
setDragging,
startUpload,
} = this.props;
const folderId = uploadToFolder ? uploadToFolder : selectedFolderId;
dragging && setDragging(false);
startUpload(files, folderId, t);
};
onDrop = (items) => {
const { item, selectedFolderId } = this.props;
const { fileExst, id } = item;
if (!fileExst) {
this.onDropZoneUpload(items, id);
} else {
this.onDropZoneUpload(items, selectedFolderId);
}
};
onMouseDown = (e) => {
const { draggable, setTooltipPosition, setStartDrag } = this.props;
if (!draggable) {
return;
}
if (
window.innerWidth < 1025 ||
e.target.tagName === "rect" ||
e.target.tagName === "path"
) {
return;
}
const mouseButton = e.which
? e.which !== 1
: e.button
? e.button !== 0
: false;
const label = e.currentTarget.getAttribute("label");
if (mouseButton || e.currentTarget.tagName !== "DIV" || label) {
return;
}
setTooltipPosition(e.pageX, e.pageY);
setStartDrag(true);
};
onFilesClick = () => {
const {
filter,
parentFolder,
setIsLoading,
fetchFiles,
isImage,
isSound,
isVideo,
canWebEdit,
item,
isTrashFolder,
openDocEditor,
expandedKeys,
addExpandedKeys,
setMediaViewerData,
} = this.props;
const { id, fileExst, viewUrl, providerKey, contentLength } = item;
if (isTrashFolder) return;
if (!fileExst && !contentLength) {
setIsLoading(true);
if (!expandedKeys.includes(parentFolder + "")) {
addExpandedKeys(parentFolder + "");
}
fetchFiles(id, filter)
.catch((err) => {
toastr.error(err);
setIsLoading(false);
})
.finally(() => setIsLoading(false));
} else {
if (canWebEdit) {
return openDocEditor(id, providerKey);
}
if (isImage || isSound || isVideo) {
setMediaViewerData({ visible: true, id });
return;
}
return window.open(viewUrl, "_blank");
}
};
render() {
const {
item,
isRecycleBin,
draggable,
canShare,
isPrivacy,
actionType,
actionExtension,
actionId,
sectionWidth,
checked,
dragging,
isFolder,
} = this.props;
const { fileExst, access, contentLength, id, shared } = item;
const isEdit =
!!actionType && actionId === id && fileExst === actionExtension;
const isDragging = isFolder && access < 2 && !isRecycleBin;
let className = isDragging ? " droppable" : "";
if (draggable) className += " draggable not-selectable";
let value = fileExst || contentLength ? `file_${id}` : `folder_${id}`;
value += draggable ? "_draggable" : "";
const isMobile = sectionWidth < 500;
const displayShareButton = isMobile
? "26px"
: !canShare
? "38px"
: "96px";
const sharedButton =
!canShare || (isPrivacy && !fileExst) || isEdit || id <= 0 || isMobile
? null
: this.getSharedButton(shared);
const checkedProps = isEdit || id <= 0 ? {} : { checked };
const element = this.getItemIcon(isEdit || id <= 0);
return (
<WrappedFileItem
onContentRowSelect={this.onContentRowSelect}
onClickShare={this.onClickShare}
rowContextClick={this.rowContextClick}
onDrop={this.onDrop}
onMouseDown={this.onMouseDown}
onFilesClick={this.onFilesClick}
getClassName={this.getClassName}
className={className}
isDragging={isDragging}
value={value}
displayShareButton={displayShareButton}
isPrivacy={isPrivacy}
sharedButton={sharedButton}
checkedProps={checkedProps}
element={element}
dragging={dragging}
{...this.props}
/>
);
}
}
return inject(
(
{
filesActionsStore,
dialogsStore,
treeFoldersStore,
selectedFolderStore,
filesStore,
uploadDataStore,
formatsStore,
mediaViewerDataStore,
},
{ item, t, history }
) => {
const { selectRowAction, onSelectItem } = filesActionsStore;
const { setSharingPanelVisible } = dialogsStore;
const {
isPrivacyFolder,
isRecycleBinFolder,
expandedKeys,
addExpandedKeys,
} = treeFoldersStore;
const { id: selectedFolderId, isRootFolder } = selectedFolderStore;
const {
dragging,
setDragging,
selection,
setTooltipPosition,
setStartDrag,
fileActionStore,
canShare,
isFileSelected,
filter,
setIsLoading,
fetchFiles,
openDocEditor,
} = filesStore;
const { startUpload } = uploadDataStore;
const { type, extension, id } = fileActionStore;
const {
iconFormatsStore,
mediaViewersFormatsStore,
docserviceStore,
} = formatsStore;
const { setMediaViewerData } = mediaViewerDataStore;
const selectedItem = selection.find(
(x) => x.id === item.id && x.fileExst === item.fileExst
);
const draggable =
!isRecycleBinFolder && selectedItem && selectedItem.id !== id;
const isFolder = selectedItem
? false
: item.fileExst //|| item.contentLength
? false
: true;
const isImage = iconFormatsStore.isImage(item.fileExst);
const isSound = iconFormatsStore.isSound(item.fileExst);
const isVideo = mediaViewersFormatsStore.isVideo(item.fileExst);
const canWebEdit = docserviceStore.canWebEdit(item.fileExst);
return {
t,
item,
selectRowAction,
onSelectItem,
setSharingPanelVisible,
isPrivacy: isPrivacyFolder,
selectedFolderId,
dragging,
setDragging,
startUpload,
draggable,
setTooltipPosition,
setStartDrag,
history,
isFolder,
isRootFolder,
canShare,
actionType: type,
actionExtension: extension,
actionId: id,
checked: isFileSelected(item.id, item.parentId),
filter,
parentFolder: selectedFolderStore.parentId,
setIsLoading,
fetchFiles,
isImage,
isSound,
isVideo,
canWebEdit,
isTrashFolder: isRecycleBinFolder,
openDocEditor,
expandedKeys,
addExpandedKeys,
setMediaViewerData,
};
}
)(observer(WithFileActions));
}

View File

@ -0,0 +1,51 @@
import React, { useEffect, useState } from "react";
import { observer, inject } from "mobx-react";
import { isMobile } from "react-device-detect";
import Loaders from "@appserver/common/components/Loaders";
let loadTimeout = null;
export default function withLoader(WrappedComponent, type) {
const withLoader = (props) => {
const { tReady, firstLoad, isLoaded, isLoading } = props;
const [inLoad, setInLoad] = useState(false);
const cleanTimer = () => {
loadTimeout && clearTimeout(loadTimeout);
loadTimeout = null;
};
useEffect(() => {
if (isLoading) {
cleanTimer();
loadTimeout = setTimeout(() => {
console.log("inLoad", true);
setInLoad(true);
}, 500);
} else {
cleanTimer();
console.log("inLoad", false);
setInLoad(false);
}
return () => {
cleanTimer();
};
}, [isLoading]);
return firstLoad || !isLoaded || (isMobile && inLoad) || !tReady ? (
<Loaders.Rows />
) : (
<WrappedComponent {...props} />
);
};
return inject(({ auth, filesStore }) => {
const { firstLoad, isLoading } = filesStore;
return {
firstLoad,
isLoaded: auth.isLoaded,
isLoading,
};
})(observer(withLoader));
}

View File

@ -0,0 +1,123 @@
import React from "react";
import Badge from "@appserver/components/badge";
import IconButton from "@appserver/components/icon-button";
import {
StyledFavoriteIcon,
StyledFileActionsConvertEditDocIcon,
StyledFileActionsLockedIcon,
} from "./Icons";
const Badges = ({
newItems,
item,
canWebEdit,
isTrashFolder,
/* canConvert, */
accessToEdit,
showNew,
onFilesClick,
onClickLock,
onClickFavorite,
onShowVersionHistory,
onBadgeClick,
/*setConvertDialogVisible*/
}) => {
const { id, locked, fileStatus, versionGroup, title, fileExst } = item;
return fileExst ? (
<div className="badges additional-badges">
{/* TODO: Uncomment after fix conversation {canConvert && !isTrashFolder && (
<IconButton
onClick={setConvertDialogVisible}
iconName="FileActionsConvertIcon"
className="badge"
size="small"
isfill={true}
color="#A3A9AE"
hoverColor="#3B72A7"
/>
)} */}
{canWebEdit && !isTrashFolder && accessToEdit && (
<IconButton
onClick={onFilesClick}
iconName="/static/images/access.edit.react.svg"
className="badge icons-group"
size="small"
isfill={true}
color="#A3A9AE"
hoverColor="#3B72A7"
/>
)}
{locked && (
<StyledFileActionsLockedIcon
className="badge lock-file icons-group"
size="small"
data-id={id}
data-locked={true}
onClick={onClickLock}
/>
)}
{fileStatus === 32 && !isTrashFolder && (
<StyledFavoriteIcon
className="favorite icons-group"
size="small"
data-action="remove"
data-id={id}
data-title={title}
onClick={onClickFavorite}
/>
)}
{fileStatus === 1 && (
<StyledFileActionsConvertEditDocIcon className="badge" size="small" />
)}
{versionGroup > 1 && (
<Badge
className="badge-version icons-group"
backgroundColor="#A3A9AE"
borderRadius="11px"
color="#FFFFFF"
fontSize="10px"
fontWeight={800}
label={`Ver.${versionGroup}`}
maxWidth="50px"
onClick={onShowVersionHistory}
padding="0 5px"
data-id={id}
/>
)}
{showNew && (
<Badge
className="badge-version icons-group"
backgroundColor="#ED7309"
borderRadius="11px"
color="#FFFFFF"
fontSize="10px"
fontWeight={800}
label={`New`}
maxWidth="50px"
onClick={onBadgeClick}
padding="0 5px"
data-id={id}
/>
)}
</div>
) : (
showNew && (
<Badge
className="new-items"
backgroundColor="#ED7309"
borderRadius="11px"
color="#FFFFFF"
fontSize="10px"
fontWeight={800}
label={newItems}
maxWidth="50px"
onClick={onBadgeClick}
padding="0 5px"
data-id={id}
/>
)
);
};
export default Badges;

View File

@ -2,6 +2,35 @@ import React, { useState } from "react";
import styled from "styled-components";
import Button from "@appserver/components/button";
import TextInput from "@appserver/components/text-input";
import commonIconsStyles from "@appserver/components/utils/common-icons-style";
import CheckIcon from "../../public/images/check.react.svg";
import CrossIcon from "../../../../../public/images/cross.react.svg";
const StyledCheckIcon = styled(CheckIcon)`
${commonIconsStyles}
path {
fill: #a3a9ae;
}
:hover {
fill: #657077;
}
`;
const StyledCrossIcon = styled(CrossIcon)`
${commonIconsStyles}
path {
fill: #a3a9ae;
}
:hover {
fill: #657077;
}
`;
export const okIcon = <StyledCheckIcon className="edit-ok-icon" size="scale" />;
export const cancelIcon = (
<StyledCrossIcon className="edit-cancel-icon" size="scale" />
);
const EditingWrapper = styled.div`
width: 100%;
@ -49,8 +78,6 @@ const EditingWrapperComponent = (props) => {
const {
itemTitle,
itemId,
okIcon,
cancelIcon,
renameTitle,
onClickUpdateItem,
cancelUpdateItem,

View File

@ -0,0 +1,36 @@
import styled from "styled-components";
import commonIconsStyles from "@appserver/components/utils/common-icons-style";
import FavoriteIcon from "../../public/images/favorite.react.svg";
import FileActionsConvertEditDocIcon from "../../public/images/file.actions.convert.edit.doc.react.svg";
import FileActionsLockedIcon from "../../public/images/file.actions.locked.react.svg";
export const EncryptedFileIcon = styled.div`
background: url("images/security.svg") no-repeat 0 0 / 16px 16px transparent;
height: 16px;
position: absolute;
width: 16px;
margin-top: 14px;
margin-left: ${(props) => (props.isEdit ? "40px" : "12px")};
`;
export const StyledFavoriteIcon = styled(FavoriteIcon)`
${commonIconsStyles}
`;
export const StyledFileActionsConvertEditDocIcon = styled(
FileActionsConvertEditDocIcon
)`
${commonIconsStyles}
path {
fill: #3b72a7;
}
`;
export const StyledFileActionsLockedIcon = styled(FileActionsLockedIcon)`
${commonIconsStyles}
path {
fill: #3b72a7;
}
`;

View File

@ -1,68 +0,0 @@
import React, { useEffect, useState } from "react";
import { inject, observer } from "mobx-react";
import RowContainer from "@appserver/components/row-container";
import { Consumer } from "@appserver/components/utils/context";
import SimpleFilesRow from "./SimpleFilesRow";
import Loaders from "@appserver/common/components/Loaders";
import { isMobile } from "react-device-detect";
let loadTimeout = null;
const FilesRowContainer = ({ isLoaded, isLoading, filesList, tReady }) => {
const [inLoad, setInLoad] = useState(false);
const cleanTimer = () => {
loadTimeout && clearTimeout(loadTimeout);
loadTimeout = null;
};
useEffect(() => {
if (isLoading) {
cleanTimer();
loadTimeout = setTimeout(() => {
console.log("inLoad", true);
setInLoad(true);
}, 500);
} else {
cleanTimer();
console.log("inLoad", false);
setInLoad(false);
}
return () => {
cleanTimer();
};
}, [isLoading]);
return !isLoaded || (isMobile && inLoad) || !tReady ? (
<Loaders.Rows />
) : (
<Consumer>
{(context) => (
<RowContainer
className="files-row-container"
draggable
useReactWindow={false}
>
{filesList.map((item, index) => (
<SimpleFilesRow
key={`${item.id}_${index}`}
item={item}
sectionWidth={context.sectionWidth}
/>
))}
</RowContainer>
)}
</Consumer>
);
};
export default inject(({ auth, filesStore }) => {
const { filesList, isLoading } = filesStore;
return {
filesList,
isLoading,
isLoaded: auth.isLoaded,
};
})(observer(FilesRowContainer));

View File

@ -1,902 +0,0 @@
import React from "react";
import { withRouter } from "react-router";
import { Trans, withTranslation } from "react-i18next";
import styled from "styled-components";
import Link from "@appserver/components/link";
import Text from "@appserver/components/text";
import RowContent from "@appserver/components/row-content";
import IconButton from "@appserver/components/icon-button";
import Badge from "@appserver/components/badge";
import commonIconsStyles from "@appserver/components/utils/common-icons-style";
import {
convertFile,
getFileConversationProgress,
} from "@appserver/common/api/files";
import {
AppServerConfig,
FileAction,
ShareAccessRights,
} from "@appserver/common/constants";
import toastr from "studio/toastr";
import FavoriteIcon from "../../../../../../../public/images/favorite.react.svg";
import FileActionsConvertEditDocIcon from "../../../../../../../public/images/file.actions.convert.edit.doc.react.svg";
import FileActionsLockedIcon from "../../../../../../../public/images/file.actions.locked.react.svg";
import CheckIcon from "../../../../../../../public/images/check.react.svg";
import CrossIcon from "../../../../../../../../../../public/images/cross.react.svg";
import { TIMEOUT } from "../../../../../../helpers/constants";
import { getTitleWithoutExst } from "../../../../../../helpers/files-helpers";
import { ConvertDialog } from "../../../../../dialogs";
import EditingWrapperComponent from "../EditingWrapperComponent";
import { isMobile } from "react-device-detect";
import { observer, inject } from "mobx-react";
import config from "../../../../../../../package.json";
import { combineUrl } from "@appserver/common/utils";
const sideColor = "#A3A9AE";
const StyledCheckIcon = styled(CheckIcon)`
${commonIconsStyles}
path {
fill: #a3a9ae;
}
:hover {
fill: #657077;
}
`;
const StyledCrossIcon = styled(CrossIcon)`
${commonIconsStyles}
path {
fill: #a3a9ae;
}
:hover {
fill: #657077;
}
`;
const StyledFavoriteIcon = styled(FavoriteIcon)`
${commonIconsStyles}
`;
const StyledFileActionsConvertEditDocIcon = styled(
FileActionsConvertEditDocIcon
)`
${commonIconsStyles}
path {
fill: #3b72a7;
}
`;
const StyledFileActionsLockedIcon = styled(FileActionsLockedIcon)`
${commonIconsStyles}
path {
fill: #3b72a7;
}
`;
const SimpleFilesRowContent = styled(RowContent)`
.badge-ext {
margin-left: -8px;
margin-right: 8px;
}
.badge {
height: 14px;
width: 14px;
margin-right: 6px;
}
.lock-file {
cursor: pointer;
}
.badges {
display: flex;
align-items: center;
}
.favorite {
cursor: pointer;
margin-right: 6px;
}
.share-icon {
margin-top: -4px;
padding-right: 8px;
}
.row_update-text {
overflow: hidden;
text-overflow: ellipsis;
}
`;
const okIcon = <StyledCheckIcon className="edit-ok-icon" size="scale" />;
const cancelIcon = (
<StyledCrossIcon className="edit-cancel-icon" size="scale" />
);
class FilesRowContent extends React.PureComponent {
constructor(props) {
super(props);
let titleWithoutExt = getTitleWithoutExst(props.item);
if (props.fileActionId === -1) {
titleWithoutExt = this.getDefaultName(props.fileActionExt);
}
this.state = {
itemTitle: titleWithoutExt,
showConvertDialog: false,
//loading: false
};
}
completeAction = (id) => {
const isCancel =
(id.currentTarget && id.currentTarget.dataset.action === "cancel") ||
id.keyCode === 27;
this.props.editCompleteAction(id, this.props.item, isCancel);
};
updateItem = () => {
const {
t,
updateFile,
renameFolder,
item,
setIsLoading,
fileActionId,
editCompleteAction,
} = this.props;
const { itemTitle } = this.state;
const originalTitle = getTitleWithoutExst(item);
setIsLoading(true);
const isSameTitle =
originalTitle.trim() === itemTitle.trim() || itemTitle.trim() === "";
if (isSameTitle) {
this.setState({
itemTitle: originalTitle,
});
return editCompleteAction(fileActionId, item, isSameTitle);
}
item.fileExst || item.contentLength
? updateFile(fileActionId, itemTitle)
.then(() => this.completeAction(fileActionId))
.then(() =>
toastr.success(
t("FileRenamed", {
oldTitle: item.title,
newTitle: itemTitle + item.fileExst,
})
)
)
.catch((err) => toastr.error(err))
.finally(() => setIsLoading(false))
: renameFolder(fileActionId, itemTitle)
.then(() => this.completeAction(fileActionId))
.then(() =>
toastr.success(
t("FolderRenamed", {
folderTitle: item.title,
newFoldedTitle: itemTitle,
})
)
)
.catch((err) => toastr.error(err))
.finally(() => setIsLoading(false));
};
createItem = (e) => {
const {
createFile,
item,
setIsLoading,
openDocEditor,
isPrivacy,
isDesktop,
replaceFileStream,
t,
setEncryptionAccess,
createFolder,
} = this.props;
const { itemTitle } = this.state;
setIsLoading(true);
const itemId = e.currentTarget.dataset.itemid;
if (itemTitle.trim() === "") {
toastr.warning(this.props.t("CreateWithEmptyTitle"));
return this.completeAction(itemId);
}
let tab =
!isDesktop && item.fileExst
? window.open(
combineUrl(
AppServerConfig.proxyURL,
config.homepage,
"/products/files/doceditor"
),
"_blank"
)
: null;
!item.fileExst && !item.contentLength
? createFolder(item.parentId, itemTitle)
.then(() => this.completeAction(itemId))
.then(() =>
toastr.success(
<Trans t={t} i18nKey="FolderCreated" ns="Home">
New folder {{ itemTitle }} is created
</Trans>
)
)
.catch((e) => toastr.error(e))
.finally(() => {
return setIsLoading(false);
})
: createFile(item.parentId, `${itemTitle}.${item.fileExst}`)
.then((file) => {
if (isPrivacy) {
return setEncryptionAccess(file).then((encryptedFile) => {
if (!encryptedFile) return Promise.resolve();
toastr.info(t("EncryptedFileSaving"));
return replaceFileStream(
file.id,
encryptedFile,
true,
false
).then(() =>
openDocEditor(file.id, file.providerKey, tab, file.webUrl)
);
});
}
return openDocEditor(file.id, file.providerKey, tab, file.webUrl);
})
.then(() => this.completeAction(itemId))
.then(() => {
const exst = item.fileExst;
return toastr.success(
<Trans i18nKey="FileCreated" ns="Home">
New file {{ itemTitle }}.{{ exst }} is created
</Trans>
);
})
.catch((e) => toastr.error(e))
.finally(() => {
return setIsLoading(false);
});
};
componentDidUpdate(prevProps) {
const { fileActionId, fileActionExt } = this.props;
if (fileActionId === -1 && fileActionExt !== prevProps.fileActionExt) {
const itemTitle = this.getDefaultName(fileActionExt);
this.setState({ itemTitle });
}
// if (fileAction) {
// if (fileActionId !== prevProps.fileActionId) {
// this.setState({ editingId: fileActionId });
// }
// }
}
renameTitle = (e) => {
let title = e.target.value;
//const chars = '*+:"<>?|/'; TODO: think how to solve problem with interpolation escape values in i18n translate
const regexp = new RegExp('[*+:"<>?|\\\\/]', "gim");
if (title.match(regexp)) {
toastr.warning(this.props.t("ContainsSpecCharacter"));
}
title = title.replace(regexp, "_");
return this.setState({ itemTitle: title });
};
cancelUpdateItem = (e) => {
const originalTitle = getTitleWithoutExst(this.props.item);
this.setState({
itemTitle: originalTitle,
});
return this.completeAction(e);
};
onClickUpdateItem = (e) => {
this.props.fileActionType === FileAction.Create
? this.createItem(e)
: this.updateItem(e);
};
onFilesClick = () => {
const {
filter,
parentFolder,
setIsLoading,
fetchFiles,
isImage,
isSound,
isVideo,
canWebEdit,
item,
isTrashFolder,
openDocEditor,
expandedKeys,
addExpandedKeys,
setMediaViewerData,
} = this.props;
const { id, fileExst, viewUrl, providerKey, contentLength } = item;
if (isTrashFolder) return;
if (!fileExst && !contentLength) {
setIsLoading(true);
if (!expandedKeys.includes(parentFolder + "")) {
addExpandedKeys(parentFolder + "");
}
fetchFiles(id, filter)
.catch((err) => {
toastr.error(err);
setIsLoading(false);
})
.finally(() => setIsLoading(false));
} else {
if (canWebEdit) {
return openDocEditor(id, providerKey);
}
if (isImage || isSound || isVideo) {
setMediaViewerData({ visible: true, id });
return;
}
return window.open(viewUrl, "_blank");
}
};
onMobileRowClick = () => {
if (this.props.isTrashFolder || window.innerWidth > 1024) return;
this.onFilesClick();
};
getStatusByDate = () => {
const { culture, t, item, sectionWidth } = this.props;
const { created, updated, version, fileExst } = item;
const title =
version > 1
? t("TitleModified")
: fileExst
? t("TitleUploaded")
: t("TitleCreated");
const date = fileExst ? updated : created;
const dateLabel = new Date(date).toLocaleString(culture);
const mobile = (sectionWidth && sectionWidth <= 375) || isMobile;
return mobile ? dateLabel : `${title}: ${dateLabel}`;
};
getDefaultName = (format) => {
const { t } = this.props;
switch (format) {
case "docx":
return t("NewDocument");
case "xlsx":
return t("NewSpreadsheet");
case "pptx":
return t("NewPresentation");
default:
return t("NewFolder");
}
};
onShowVersionHistory = () => {
const {
homepage,
isTabletView,
item,
setIsVerHistoryPanel,
fetchFileVersions,
history,
isTrashFolder,
} = this.props;
if (isTrashFolder) return;
if (!isTabletView) {
fetchFileVersions(item.id + "");
setIsVerHistoryPanel(true);
} else {
history.push(
combineUrl(AppServerConfig.proxyURL, homepage, `/${item.id}/history`)
);
}
};
onBadgeClick = () => {
const {
item,
selectedFolderPathParts,
markAsRead,
setNewFilesPanelVisible,
setNewFilesIds,
updateRootBadge,
updateFileBadge,
} = this.props;
if (item.fileExst) {
markAsRead([], [item.id])
.then(() => {
updateRootBadge(selectedFolderPathParts[0], 1);
updateFileBadge(item.id);
})
.catch((err) => toastr.error(err));
} else {
setNewFilesPanelVisible(true);
const newFolderIds = this.props.selectedFolderPathParts;
newFolderIds.push(item.id);
setNewFilesIds(newFolderIds);
}
};
setConvertDialogVisible = () =>
this.setState({ showConvertDialog: !this.state.showConvertDialog });
getConvertProgress = (fileId) => {
const {
selectedFolderId,
filter,
setIsLoading,
setSecondaryProgressBarData,
t,
clearSecondaryProgressData,
fetchFiles,
} = this.props;
getFileConversationProgress(fileId).then((res) => {
if (res && res[0] && res[0].progress !== 100) {
setSecondaryProgressBarData({
icon: "file",
visible: true,
percent: res[0].progress,
label: t("Convert"),
alert: false,
});
setTimeout(() => this.getConvertProgress(fileId), 1000);
} else {
if (res[0].error) {
setSecondaryProgressBarData({
visible: true,
alert: true,
});
toastr.error(res[0].error);
setTimeout(() => clearSecondaryProgressData(), TIMEOUT);
} else {
setSecondaryProgressBarData({
icon: "file",
visible: true,
percent: 100,
label: t("Convert"),
alert: false,
});
setTimeout(() => clearSecondaryProgressData(), TIMEOUT);
const newFilter = filter.clone();
fetchFiles(selectedFolderId, newFilter)
.catch((err) => {
setSecondaryProgressBarData({
visible: true,
alert: true,
});
//toastr.error(err);
setTimeout(() => clearSecondaryProgressData(), TIMEOUT);
})
.finally(() => setIsLoading(false));
}
}
});
};
onConvert = () => {
const { item, t, setSecondaryProgressBarData } = this.props;
setSecondaryProgressBarData({
icon: "file",
visible: true,
percent: 0,
label: t("Convert"),
alert: false,
});
this.setState({ showConvertDialog: false }, () =>
convertFile(item.id).then((convertRes) => {
if (convertRes && convertRes[0] && convertRes[0].progress !== 100) {
this.getConvertProgress(item.id);
}
})
);
};
onClickLock = () => {
const { item } = this.props;
const { locked, id } = item;
this.props.lockFileAction(id, !locked).catch((err) => toastr.error(err));
};
onClickFavorite = () => {
const { t, item } = this.props;
this.props
.setFavoriteAction("remove", item.id)
.then(() => toastr.success(t("RemovedFromFavorites")))
.catch((err) => toastr.error(err));
};
render() {
const {
t,
item,
isTrashFolder,
isLoading,
isMobile,
canWebEdit,
/* canConvert,*/
sectionWidth,
fileActionId,
fileActionExt,
} = this.props;
const { itemTitle, showConvertDialog } = this.state;
const {
contentLength,
updated,
createdBy,
fileExst,
filesCount,
foldersCount,
fileStatus,
id,
versionGroup,
locked,
providerKey,
} = item;
const titleWithoutExt = getTitleWithoutExst(item);
const fileOwner =
createdBy &&
((this.props.viewer.id === createdBy.id && t("AuthorMe")) ||
createdBy.displayName);
const updatedDate = updated && this.getStatusByDate();
const accessToEdit =
item.access === ShareAccessRights.FullAccess ||
item.access === ShareAccessRights.None; // TODO: fix access type for owner (now - None)
const isEdit = id === fileActionId && fileExst === fileActionExt;
const linkStyles = isTrashFolder //|| window.innerWidth <= 1024
? { noHover: true }
: { onClick: this.onFilesClick };
const newItems = item.new || fileStatus === 2;
const showNew = !!newItems;
return isEdit ? (
<EditingWrapperComponent
itemTitle={itemTitle}
okIcon={okIcon}
cancelIcon={cancelIcon}
renameTitle={this.renameTitle}
onClickUpdateItem={this.onClickUpdateItem}
cancelUpdateItem={this.cancelUpdateItem}
itemId={id}
isLoading={isLoading}
/>
) : (
<>
{showConvertDialog && (
<ConvertDialog
visible={showConvertDialog}
onClose={this.setConvertDialogVisible}
onConvert={this.onConvert}
/>
)}
<SimpleFilesRowContent
sectionWidth={sectionWidth}
isMobile={isMobile}
sideColor={sideColor}
isFile={fileExst || contentLength}
//onClick={this.onMobileRowClick}
>
<Link
containerWidth="55%"
type="page"
title={titleWithoutExt}
fontWeight="600"
fontSize="15px"
{...linkStyles}
color="#333"
isTextOverflow
>
{titleWithoutExt}
</Link>
<>
{fileExst ? (
<div className="badges">
<Text
className="badge-ext"
as="span"
color="#A3A9AE"
fontSize="15px"
fontWeight={600}
title={fileExst}
truncate={true}
>
{fileExst}
</Text>
{/* TODO: Uncomment after fix conversation {canConvert && !isTrashFolder && (
<IconButton
onClick={this.setConvertDialogVisible}
iconName="FileActionsConvertIcon"
className="badge"
size="small"
isfill={true}
color="#A3A9AE"
hoverColor="#3B72A7"
/>
)} */}
{canWebEdit && !isTrashFolder && accessToEdit && (
<IconButton
onClick={this.onFilesClick}
iconName="/static/images/access.edit.react.svg"
className="badge"
size="small"
isfill={true}
color="#A3A9AE"
hoverColor="#3B72A7"
/>
)}
{locked && (
<StyledFileActionsLockedIcon
className="badge lock-file"
size="small"
data-id={item.id}
data-locked={true}
onClick={this.onClickLock}
/>
)}
{fileStatus === 32 && !isTrashFolder && (
<StyledFavoriteIcon
className="favorite"
size="small"
data-action="remove"
data-id={item.id}
data-title={item.title}
onClick={this.onClickFavorite}
/>
)}
{fileStatus === 1 && (
<StyledFileActionsConvertEditDocIcon
className="badge"
size="small"
/>
)}
{versionGroup > 1 && (
<Badge
className="badge-version"
backgroundColor="#A3A9AE"
borderRadius="11px"
color="#FFFFFF"
fontSize="10px"
fontWeight={800}
label={t("Version", {
version: versionGroup,
})}
maxWidth="50px"
onClick={this.onShowVersionHistory}
padding="0 5px"
data-id={id}
/>
)}
{showNew && (
<Badge
className="badge-version"
backgroundColor="#ED7309"
borderRadius="11px"
color="#FFFFFF"
fontSize="10px"
fontWeight={800}
label={t("New")}
maxWidth="50px"
onClick={this.onBadgeClick}
padding="0 5px"
data-id={id}
/>
)}
</div>
) : (
<div className="badges">
{showNew && (
<Badge
className="badge-version"
backgroundColor="#ED7309"
borderRadius="11px"
color="#FFFFFF"
fontSize="10px"
fontWeight={800}
label={newItems}
maxWidth="50px"
onClick={this.onBadgeClick}
padding="0 5px"
data-id={id}
/>
)}
</div>
)}
</>
<Text
containerMinWidth="120px"
containerWidth="15%"
as="div"
color={sideColor}
fontSize="12px"
fontWeight={400}
title={fileOwner}
truncate={true}
>
{fileOwner}
</Text>
<Text
containerMinWidth="200px"
containerWidth="15%"
title={updatedDate}
fontSize="12px"
fontWeight={400}
color={sideColor}
className="row_update-text"
>
{(fileExst || contentLength || !providerKey) &&
updatedDate &&
updatedDate}
</Text>
<Text
containerMinWidth="90px"
containerWidth="10%"
as="div"
color={sideColor}
fontSize="12px"
fontWeight={400}
title=""
truncate={true}
>
{fileExst || contentLength
? contentLength
: !providerKey
? `${t("TitleDocuments")}: ${filesCount} | ${t(
"TitleSubfolders"
)}: ${foldersCount}`
: ""}
</Text>
</SimpleFilesRowContent>
</>
);
}
}
export default inject(
(
{
auth,
filesStore,
formatsStore,
uploadDataStore,
treeFoldersStore,
selectedFolderStore,
filesActionsStore,
mediaViewerDataStore,
versionHistoryStore,
dialogsStore,
},
{ item }
) => {
const { replaceFileStream, setEncryptionAccess } = auth;
const { culture, isDesktopClient, isTabletView } = auth.settingsStore;
const { secondaryProgressDataStore } = uploadDataStore;
const { setIsVerHistoryPanel, fetchFileVersions } = versionHistoryStore;
const {
iconFormatsStore,
mediaViewersFormatsStore,
docserviceStore,
} = formatsStore;
const {
fetchFiles,
filter,
createFile,
updateFile,
renameFolder,
createFolder,
openDocEditor,
setIsLoading,
isLoading,
updateFileBadge,
} = filesStore;
const {
isRecycleBinFolder,
isPrivacyFolder,
expandedKeys,
addExpandedKeys,
updateRootBadge,
} = treeFoldersStore;
const {
type: fileActionType,
extension: fileActionExt,
id: fileActionId,
} = filesStore.fileActionStore;
const {
setSecondaryProgressBarData,
clearSecondaryProgressData,
} = secondaryProgressDataStore;
const canWebEdit = docserviceStore.canWebEdit(item.fileExst);
const canConvert = docserviceStore.canConvert(item.fileExst);
const isVideo = mediaViewersFormatsStore.isVideo(item.fileExst);
const isImage = iconFormatsStore.isImage(item.fileExst);
const isSound = iconFormatsStore.isSound(item.fileExst);
const { setMediaViewerData } = mediaViewerDataStore;
const {
editCompleteAction,
lockFileAction,
setFavoriteAction,
markAsRead,
} = filesActionsStore;
const { setNewFilesPanelVisible, setNewFilesIds } = dialogsStore;
return {
isDesktop: isDesktopClient,
isTabletView,
homepage: config.homepage,
viewer: auth.userStore.user,
culture,
fileActionId,
fileActionType,
fileActionExt,
selectedFolderId: selectedFolderStore.id,
selectedFolderPathParts: selectedFolderStore.pathParts,
parentFolder: selectedFolderStore.parentId,
isLoading,
isTrashFolder: isRecycleBinFolder,
isPrivacy: isPrivacyFolder,
filter,
canWebEdit,
canConvert,
isVideo,
isImage,
isSound,
expandedKeys,
setIsLoading,
fetchFiles,
setSecondaryProgressBarData,
clearSecondaryProgressData,
createFile,
createFolder,
updateFile,
renameFolder,
replaceFileStream,
setEncryptionAccess,
addExpandedKeys,
openDocEditor,
editCompleteAction,
lockFileAction,
setFavoriteAction,
setMediaViewerData,
setIsVerHistoryPanel,
fetchFileVersions,
markAsRead,
setNewFilesPanelVisible,
setNewFilesIds,
updateRootBadge,
updateFileBadge,
};
}
)(withRouter(withTranslation("Home")(observer(FilesRowContent))));

View File

@ -1,721 +0,0 @@
import React, { useCallback } from "react";
import { ReactSVG } from "react-svg";
import styled from "styled-components";
import { inject, observer } from "mobx-react";
import { withTranslation } from "react-i18next";
import IconButton from "@appserver/components/icon-button";
import Text from "@appserver/components/text";
import DragAndDrop from "@appserver/components/drag-and-drop";
import Row from "@appserver/components/row";
import FilesRowContent from "./FilesRowContent";
import { withRouter } from "react-router-dom";
import toastr from "studio/toastr";
import { FileAction, AppServerConfig } from "@appserver/common/constants";
import copy from "copy-to-clipboard";
import config from "../../../../../../../package.json";
import { combineUrl } from "@appserver/common/utils";
import { createSelectable } from "react-selectable-fast";
const StyledSimpleFilesRow = styled(Row)`
margin-top: -2px;
${(props) =>
!props.contextOptions &&
`
& > div:last-child {
width: 0px;
}
`}
.share-button-icon {
margin-right: 7px;
margin-top: -1px;
}
.share-button:hover,
.share-button-icon:hover {
cursor: pointer;
color: #657077;
path {
fill: #657077;
}
}
-webkit-tap-highlight-color: rgba(0, 0, 0, 0);
@media (max-width: 1312px) {
.share-button {
padding-top: 3px;
}
}
.styled-element {
margin-right: 7px;
}
`;
const EncryptedFileIcon = styled.div`
background: url("images/security.svg") no-repeat 0 0 / 16px 16px transparent;
height: 16px;
position: absolute;
width: 16px;
margin-top: 14px;
margin-left: ${(props) => (props.isEdit ? "40px" : "12px")};
`;
const svgLoader = () => <div style={{ width: "24px" }}></div>;
const SimpleFilesRow = createSelectable((props) => {
const {
t,
item,
sectionWidth,
actionType,
actionExtension,
isPrivacy,
isRecycleBin,
dragging,
checked,
canShare,
isFolder,
draggable,
isRootFolder,
homepage,
isTabletView,
actionId,
selectedFolderId,
setSharingPanelVisible,
setChangeOwnerPanelVisible,
setDeleteThirdPartyDialogVisible,
setRemoveItem,
setMoveToPanelVisible,
setCopyPanelVisible,
openDocEditor,
setIsVerHistoryPanel,
fetchFileVersions,
setAction,
deleteFileAction,
deleteFolderAction,
lockFileAction,
duplicateAction,
finalizeVersionAction,
setFavoriteAction,
openLocationAction,
selectRowAction,
setThirdpartyInfo,
setMediaViewerData,
setDragging,
setStartDrag,
startUpload,
onSelectItem,
history,
setTooltipPosition,
setDownloadDialogVisible,
downloadAction,
confirmDelete,
setDeleteDialogVisible,
} = props;
const {
id,
title,
fileExst,
contentLength,
shared,
access,
contextOptions,
icon,
providerKey,
folderId,
viewUrl,
webUrl,
canOpenPlayer,
locked,
parentId,
} = item;
const isThirdPartyFolder = providerKey && isRootFolder;
const onContentRowSelect = (checked, file) => {
if (!file) return;
selectRowAction(checked, file);
};
const onClickShare = () => {
onSelectItem(item);
setSharingPanelVisible(true);
};
const onOwnerChange = () => setChangeOwnerPanelVisible(true);
const onMoveAction = () => setMoveToPanelVisible(true);
const onCopyAction = () => setCopyPanelVisible(true);
const getSharedButton = (shared) => {
const color = shared ? "#657077" : "#a3a9ae";
return (
<Text
className="share-button"
as="span"
title={t("Share")}
fontSize="12px"
fontWeight={600}
color={color}
display="inline-flex"
onClick={onClickShare}
>
<IconButton
className="share-button-icon"
color={color}
hoverColor="#657077"
size={18}
iconName="images/catalog.shared.react.svg"
/>
{t("Share")}
</Text>
);
};
const getItemIcon = (isEdit) => {
return (
<>
<ReactSVG
className={`react-svg-icon${isEdit ? " is-edit" : ""}`}
src={icon}
loading={svgLoader}
/>
{isPrivacy && fileExst && <EncryptedFileIcon isEdit={isEdit} />}
</>
);
};
const onOpenLocation = () => {
const locationId = isFolder ? id : folderId;
openLocationAction(locationId, isFolder);
};
const showVersionHistory = () => {
if (!isTabletView) {
fetchFileVersions(id + "");
setIsVerHistoryPanel(true);
} else {
history.push(
combineUrl(AppServerConfig.proxyURL, homepage, `/${id}/history`)
);
}
};
const finalizeVersion = () =>
finalizeVersionAction(id).catch((err) => toastr.error(err));
const onClickFavorite = (e) => {
const data = (e.currentTarget && e.currentTarget.dataset) || e;
const { action } = data;
setFavoriteAction(action, id)
.then(() =>
action === "mark"
? toastr.success(t("MarkedAsFavorite"))
: toastr.success(t("RemovedFromFavorites"))
)
.catch((err) => toastr.error(err));
};
const lockFile = () =>
lockFileAction(id, !locked).catch((err) => toastr.error(err));
const onClickLinkForPortal = () => {
const isFile = !!fileExst;
copy(
isFile
? canOpenPlayer
? `${window.location.href}&preview=${id}`
: webUrl
: `${window.location.origin + homepage}/filter?folder=${id}`
);
toastr.success(t("LinkCopySuccess"));
};
const onClickLinkEdit = () => openDocEditor(id, providerKey);
const onClickDownload = () => {
const isFile = !!fileExst && contentLength;
isFile
? window.open(viewUrl, "_blank")
: downloadAction(t("ArchivingData")).catch((err) => toastr.error(err));
};
const onClickDownloadAs = () => setDownloadDialogVisible(true);
const onDuplicate = () =>
duplicateAction(item, t("CopyOperation")).catch((err) => toastr.error(err));
const onClickRename = () => {
setAction({
type: FileAction.Rename,
extension: fileExst,
id,
});
};
const onChangeThirdPartyInfo = () => setThirdpartyInfo(providerKey);
const onMediaFileClick = (fileId) => {
const itemId = typeof fileId !== "object" ? fileId : id;
setMediaViewerData({ visible: true, id: itemId });
};
const onClickDelete = () => {
if (isThirdPartyFolder) {
const splitItem = id.split("-");
setRemoveItem({ id: splitItem[splitItem.length - 1], title });
setDeleteThirdPartyDialogVisible(true);
return;
}
if (confirmDelete) {
setDeleteDialogVisible(true);
} else {
const translations = {
deleteOperation: t("DeleteOperation"),
};
fileExst || contentLength
? deleteFileAction(id, folderId, translations)
.then(() => toastr.success(t("FileRemoved")))
.catch((err) => toastr.error(err))
: deleteFolderAction(id, parentId, translations)
.then(() => toastr.success(t("FolderRemoved")))
.catch((err) => toastr.error(err));
}
};
const rowContextClick = () => {
onSelectItem(item);
};
const getFilesContextOptions = useCallback(() => {
const isSharable = access !== 1 && access !== 0;
return contextOptions.map((option) => {
switch (option) {
case "open":
return {
key: option,
label: t("Open"),
icon: "images/catalog.folder.react.svg",
onClick: onOpenLocation,
disabled: false,
};
case "show-version-history":
return {
key: option,
label: t("ShowVersionHistory"),
icon: "images/history.react.svg",
onClick: showVersionHistory,
disabled: false,
};
case "finalize-version":
return {
key: option,
label: t("FinalizeVersion"),
icon: "images/history-finalized.react.svg",
onClick: finalizeVersion,
disabled: false,
};
case "separator0":
case "separator1":
case "separator2":
case "separator3":
return { key: option, isSeparator: true };
case "open-location":
return {
key: option,
label: t("OpenLocation"),
icon: "images/download-as.react.svg",
onClick: onOpenLocation,
disabled: false,
};
case "mark-as-favorite":
return {
key: option,
label: t("MarkAsFavorite"),
icon: "images/favorites.react.svg",
onClick: onClickFavorite,
disabled: false,
"data-action": "mark",
action: "mark",
};
case "block-unblock-version":
return {
key: option,
label: t("UnblockVersion"),
icon: "images/lock.react.svg",
onClick: lockFile,
disabled: false,
};
case "sharing-settings":
return {
key: option,
label: t("SharingSettings"),
icon: "images/catalog.shared.react.svg",
onClick: onClickShare,
disabled: isSharable,
};
case "send-by-email":
return {
key: option,
label: t("SendByEmail"),
icon: "/static/images/mail.react.svg",
disabled: true,
};
case "owner-change":
return {
key: option,
label: t("ChangeOwner"),
icon: "images/catalog.user.react.svg",
onClick: onOwnerChange,
disabled: false,
};
case "link-for-portal-users":
return {
key: option,
label: t("LinkForPortalUsers"),
icon: "/static/images/invitation.link.react.svg",
onClick: onClickLinkForPortal,
disabled: false,
};
case "edit":
return {
key: option,
label: t("Edit"),
icon: "/static/images/access.edit.react.svg",
onClick: onClickLinkEdit,
disabled: false,
};
case "preview":
return {
key: option,
label: t("Preview"),
icon: "EyeIcon",
onClick: onClickLinkEdit,
disabled: true,
};
case "view":
return {
key: option,
label: t("View"),
icon: "/static/images/eye.react.svg",
onClick: onMediaFileClick,
disabled: false,
};
case "download":
return {
key: option,
label: t("Download"),
icon: "images/download.react.svg",
onClick: onClickDownload,
disabled: false,
};
case "download-as":
return {
key: option,
label: t("DownloadAs"),
icon: "images/download-as.react.svg",
onClick: onClickDownloadAs,
disabled: false,
};
case "move-to":
return {
key: option,
label: t("MoveTo"),
icon: "images/move.react.svg",
onClick: onMoveAction,
disabled: false,
};
case "restore":
return {
key: option,
label: t("Restore"),
icon: "images/move.react.svg",
onClick: onMoveAction,
disabled: false,
};
case "copy-to":
return {
key: option,
label: t("Copy"),
icon: "/static/images/copy.react.svg",
onClick: onCopyAction,
disabled: false,
};
case "copy":
return {
key: option,
label: t("Duplicate"),
icon: "/static/images/copy.react.svg",
onClick: onDuplicate,
disabled: false,
};
case "rename":
return {
key: option,
label: t("Rename"),
icon: "images/rename.react.svg",
onClick: onClickRename,
disabled: false,
};
case "change-thirdparty-info":
return {
key: option,
label: t("ThirdPartyInfo"),
icon: "/static/images/access.edit.react.svg",
onClick: onChangeThirdPartyInfo,
disabled: false,
};
case "delete":
return {
key: option,
label: isThirdPartyFolder ? t("DeleteThirdParty") : t("Delete"),
icon: "/static/images/catalog.trash.react.svg",
onClick: onClickDelete,
disabled: false,
};
case "remove-from-favorites":
return {
key: option,
label: t("RemoveFromFavorites"),
icon: "images/favorites.react.svg",
onClick: onClickFavorite,
disabled: false,
"data-action": "remove",
action: "remove",
};
default:
break;
}
return undefined;
});
}, [contextOptions, item]);
const onDropZoneUpload = (files, uploadToFolder) => {
const folderId = uploadToFolder ? uploadToFolder : selectedFolderId;
dragging && setDragging(false);
startUpload(files, folderId, t);
};
const onDrop = (items) => {
if (!fileExst) {
onDropZoneUpload(items, id);
} else {
onDropZoneUpload(items, selectedFolderId);
}
};
const onMouseDown = (e) => {
if (!draggable) {
return;
}
if (
window.innerWidth < 1025 ||
e.target.tagName === "rect" ||
e.target.tagName === "path"
) {
return;
}
const mouseButton = e.which
? e.which !== 1
: e.button
? e.button !== 0
: false;
const label = e.currentTarget.getAttribute("label");
if (mouseButton || e.currentTarget.tagName !== "DIV" || label) {
return;
}
setTooltipPosition(e.pageX, e.pageY);
setStartDrag(true);
};
const isMobile = sectionWidth < 500;
const isEdit =
!!actionType && actionId === id && fileExst === actionExtension;
const contextOptionsProps =
!isEdit && contextOptions && contextOptions.length > 0
? {
contextOptions: getFilesContextOptions(),
}
: {};
const isDragging = isFolder && access < 2 && !isRecycleBin;
const checkedProps = isEdit || id <= 0 ? {} : { checked };
const element = getItemIcon(isEdit || id <= 0);
const displayShareButton = isMobile ? "26px" : !canShare ? "38px" : "96px";
let className = isDragging ? " droppable" : "";
if (draggable) className += " draggable not-selectable";
let value = fileExst || contentLength ? `file_${id}` : `folder_${id}`;
value += draggable ? "_draggable" : "";
const sharedButton =
!canShare || (isPrivacy && !fileExst) || isEdit || id <= 0 || isMobile
? null
: getSharedButton(shared);
return (
<div ref={props.selectableRef}>
<DragAndDrop
data-title={item.title}
value={value}
className={className}
onDrop={onDrop}
onMouseDown={onMouseDown}
dragging={dragging && isDragging}
{...contextOptionsProps}
>
<StyledSimpleFilesRow
key={id}
data={item}
element={element}
sectionWidth={sectionWidth}
contentElement={sharedButton}
onSelect={onContentRowSelect}
rowContextClick={rowContextClick}
isPrivacy={isPrivacy}
{...checkedProps}
{...contextOptionsProps}
contextButtonSpacerWidth={displayShareButton}
>
<FilesRowContent item={item} sectionWidth={sectionWidth} />
</StyledSimpleFilesRow>
</DragAndDrop>
</div>
);
});
export default inject(
(
{
auth,
filesStore,
treeFoldersStore,
selectedFolderStore,
dialogsStore,
versionHistoryStore,
filesActionsStore,
mediaViewerDataStore,
uploadDataStore,
settingsStore,
},
{ item }
) => {
const { isTabletView } = auth.settingsStore;
const { type, extension, id } = filesStore.fileActionStore;
const { isRecycleBinFolder, isPrivacyFolder } = treeFoldersStore;
const {
setSharingPanelVisible,
setChangeOwnerPanelVisible,
setRemoveItem,
setDeleteThirdPartyDialogVisible,
setMoveToPanelVisible,
setCopyPanelVisible,
setDownloadDialogVisible,
setDeleteDialogVisible,
} = dialogsStore;
const {
selection,
canShare,
openDocEditor,
fileActionStore,
dragging,
setDragging,
setStartDrag,
setTooltipPosition,
isFileSelected,
} = filesStore;
const { isRootFolder, id: selectedFolderId } = selectedFolderStore;
const { setIsVerHistoryPanel, fetchFileVersions } = versionHistoryStore;
const { setAction } = fileActionStore;
const selectedItem = selection.find(
(x) => x.id === item.id && x.fileExst === item.fileExst
);
const isFolder = selectedItem
? false
: item.fileExst || item.contentLength
? false
: true;
const draggable =
!isRecycleBinFolder && selectedItem && selectedItem.id !== id;
const {
deleteFileAction,
deleteFolderAction,
lockFileAction,
finalizeVersionAction,
duplicateAction,
setFavoriteAction,
openLocationAction,
selectRowAction,
setThirdpartyInfo,
onSelectItem,
downloadAction,
} = filesActionsStore;
const { setMediaViewerData } = mediaViewerDataStore;
const { startUpload } = uploadDataStore;
return {
dragging,
actionType: type,
actionExtension: extension,
isPrivacy: isPrivacyFolder,
isRecycleBin: isRecycleBinFolder,
isRootFolder,
canShare,
checked: isFileSelected(item.id, item.parentId),
isFolder,
draggable,
isItemsSelected: !!selection.length,
homepage: config.homepage,
isTabletView,
actionId: fileActionStore.id,
setSharingPanelVisible,
setChangeOwnerPanelVisible,
setRemoveItem,
setDeleteThirdPartyDialogVisible,
setMoveToPanelVisible,
setCopyPanelVisible,
setDownloadDialogVisible,
openDocEditor,
setIsVerHistoryPanel,
fetchFileVersions,
setAction,
deleteFileAction,
deleteFolderAction,
lockFileAction,
finalizeVersionAction,
duplicateAction,
setFavoriteAction,
openLocationAction,
selectRowAction,
setThirdpartyInfo,
setMediaViewerData,
selectedFolderId,
setDragging,
setStartDrag,
startUpload,
onSelectItem,
setTooltipPosition,
downloadAction,
confirmDelete: settingsStore.confirmDelete,
setDeleteDialogVisible,
};
}
)(withTranslation("Home")(observer(withRouter(SimpleFilesRow))));

View File

@ -1,500 +0,0 @@
import React from "react";
import { withRouter } from "react-router";
import { Trans, withTranslation } from "react-i18next";
import styled from "styled-components";
import Badge from "@appserver/components/badge";
import Link from "@appserver/components/link";
import Text from "@appserver/components/text";
import { markAsRead } from "@appserver/common/api/files";
import { FileAction, AppServerConfig } from "@appserver/common/constants";
import toastr from "studio/toastr";
import { getTitleWithoutExst } from "../../../../../helpers/files-helpers";
import { NewFilesPanel } from "../../../../panels";
import EditingWrapperComponent from "./EditingWrapperComponent";
import TileContent from "./TileContent";
import { isMobile } from "react-device-detect";
import { inject, observer } from "mobx-react";
import CheckIcon from "../../../../../../public/images/check.react.svg";
import CrossIcon from "../../../../../../../../../public/images/cross.react.svg";
import config from "../../../../../../package.json";
import { combineUrl } from "@appserver/common/utils";
const SimpleFilesTileContent = styled(TileContent)`
.rowMainContainer {
height: auto;
max-width: 100%;
align-self: flex-end;
a {
word-break: break-word;
}
}
.mainIcons {
align-self: flex-end;
}
.badge-ext {
margin-left: -8px;
margin-right: 8px;
}
.badge {
margin-right: 8px;
}
.badges {
display: flex;
align-items: center;
}
.share-icon {
margin-top: -4px;
padding-right: 8px;
}
@media (max-width: 1024px) {
display: inline-flex;
height: auto;
& > div {
margin-top: 0;
}
}
`;
const okIcon = (
<CheckIcon
className="edit-ok-icon"
size="scale"
isfill={true}
color="#A3A9AE"
/>
);
const cancelIcon = (
<CrossIcon
className="edit-cancel-icon"
size="scale"
isfill={true}
color="#A3A9AE"
/>
);
class FilesTileContent extends React.PureComponent {
constructor(props) {
super(props);
let titleWithoutExt = getTitleWithoutExst(props.item);
if (props.fileAction.id === -1) {
titleWithoutExt = this.getDefaultName(props.fileAction.extension);
}
this.state = {
itemTitle: titleWithoutExt,
editingId: props.fileAction.id,
showNewFilesPanel: false,
newFolderId: [],
newItems: props.item.new,
//loading: false
};
}
completeAction = (e) => {
//this.setState({ loading: false }, () =>)
this.props.onEditComplete(e);
};
updateItem = (e) => {
const {
fileAction,
updateFile,
renameFolder,
item,
setIsLoading,
} = this.props;
const { itemTitle } = this.state;
const originalTitle = getTitleWithoutExst(item);
setIsLoading(true);
if (originalTitle === itemTitle) return this.completeAction(e);
item.fileExst
? updateFile(fileAction.id, itemTitle)
.then(() => this.completeAction(e))
.finally(() => setIsLoading(false))
: renameFolder(fileAction.id, itemTitle)
.then(() => this.completeAction(e))
.finally(() => setIsLoading(false));
};
createItem = (e) => {
const { createFile, item, setIsLoading, createFolder, t } = this.props;
const { itemTitle } = this.state;
setIsLoading(true);
if (itemTitle.trim() === "") return this.completeAction();
!item.fileExst
? createFolder(item.parentId, itemTitle)
.then(() => this.completeAction(e))
.finally(() => {
toastr.success(
<Trans t={t} i18nKey="FolderCreated" ns="Home">
New folder {{ itemTitle }} is created
</Trans>
);
return setIsLoading(false);
})
: createFile(item.parentId, `${itemTitle}.${item.fileExst}`)
.then(() => this.completeAction(e))
.finally(() => {
const exst = item.fileExst;
toastr.success(
<Trans t={t} i18nKey="FileCreated" ns="Home">
New file {{ itemTitle }}.{{ exst }} is created
</Trans>
);
return setIsLoading(false);
});
};
componentDidUpdate(prevProps) {
const { fileAction } = this.props;
if (fileAction) {
if (fileAction.id !== prevProps.fileAction.id) {
this.setState({ editingId: fileAction.id });
}
}
}
renameTitle = (e) => {
this.setState({ itemTitle: e.target.value });
};
cancelUpdateItem = (e) => {
//this.setState({ loading: false });
this.completeAction(e);
};
onClickUpdateItem = () => {
this.props.fileAction.type === FileAction.Create
? this.createItem()
: this.updateItem();
};
onKeyUpUpdateItem = (e) => {
if (e.keyCode === 13) {
this.props.fileAction.type === FileAction.Create
? this.createItem()
: this.updateItem();
}
if (e.keyCode === 27) return this.cancelUpdateItem();
};
onFilesClick = () => {
const { id, fileExst, viewUrl, providerKey } = this.props.item;
const {
filter,
parentFolder,
setIsLoading,
onMediaFileClick,
fetchFiles,
canWebEdit,
openDocEditor,
isVideo,
isImage,
isSound,
expandedKeys,
addExpandedKeys,
} = this.props;
if (!fileExst) {
setIsLoading(true);
if (!expandedKeys.includes(parentFolder + "")) {
addExpandedKeys(parentFolder + "");
}
fetchFiles(id, filter)
.catch((err) => {
toastr.error(err);
setIsLoading(false);
})
.finally(() => setIsLoading(false));
} else {
if (canWebEdit) {
return openDocEditor(id, providerKey);
}
const isOpenMedia = isImage || isSound || isVideo;
if (isOpenMedia) {
onMediaFileClick(id);
return;
}
return window.open(viewUrl, "_blank");
}
};
onMobileRowClick = (e) => {
if (!isMobile) return;
this.onFilesClick();
};
getStatusByDate = () => {
const { culture, t, item, sectionWidth } = this.props;
const { created, updated, version, fileExst } = item;
const title =
version > 1
? t("TitleModified")
: fileExst
? t("TitleUploaded")
: t("TitleCreated");
const date = fileExst ? updated : created;
const dateLabel = new Date(date).toLocaleString(culture);
const mobile = (sectionWidth && sectionWidth <= 375) || isMobile;
return mobile ? dateLabel : `${title}: ${dateLabel}`;
};
getDefaultName = (format) => {
const { t } = this.props;
switch (format) {
case "docx":
return t("NewDocument");
case "xlsx":
return t("NewSpreadsheet");
case "pptx":
return t("NewPresentation");
default:
return t("NewFolder");
}
};
onShowVersionHistory = (e) => {
const { homepage, history } = this.props;
const fileId = e.currentTarget.dataset.id;
history.push(
combineUrl(AppServerConfig.proxyURL, homepage, `/${fileId}/history`)
);
};
onBadgeClick = () => {
const { showNewFilesPanel } = this.state;
const {
item,
treeFolders,
setTreeFolders,
rootFolderId,
newItems,
filter,
fetchFiles,
} = this.props;
if (item.fileExst) {
markAsRead([], [item.id])
.then(() => {
const data = treeFolders;
const dataItem = data.find((x) => x.id === rootFolderId);
dataItem.newItems = newItems ? dataItem.newItems - 1 : 0; //////newItems
setTreeFolders(data);
fetchFiles(this.props.selectedFolderId, filter.clone());
})
.catch((err) => toastr.error(err));
} else {
const newFolderId = this.props.selectedFolderPathParts;
newFolderId.push(item.id);
this.setState({
showNewFilesPanel: !showNewFilesPanel,
newFolderId,
});
}
};
onShowNewFilesPanel = () => {
const { showNewFilesPanel } = this.state;
this.setState({ showNewFilesPanel: !showNewFilesPanel });
};
render() {
const { item, fileAction, isTrashFolder, folders } = this.props;
const {
itemTitle,
editingId,
showNewFilesPanel,
newItems,
newFolderId,
} = this.state;
const { fileExst, id } = item;
const titleWithoutExt = getTitleWithoutExst(item);
const isEdit = id === editingId && fileExst === fileAction.extension;
const linkStyles = isTrashFolder
? { noHover: true }
: { onClick: this.onFilesClick };
const showNew = item.new && item.new > 0;
return isEdit ? (
<EditingWrapperComponent
itemTitle={itemTitle}
okIcon={okIcon}
cancelIcon={cancelIcon}
renameTitle={this.renameTitle}
onKeyUpUpdateItem={this.onKeyUpUpdateItem}
onClickUpdateItem={this.onClickUpdateItem}
cancelUpdateItem={this.cancelUpdateItem}
itemId={id}
/>
) : (
<>
{showNewFilesPanel && (
<NewFilesPanel
visible={showNewFilesPanel}
onClose={this.onShowNewFilesPanel}
folderId={newFolderId}
folders={folders}
/>
)}
<SimpleFilesTileContent
sideColor="#333"
isFile={fileExst}
onClick={this.onMobileRowClick}
disableSideInfo
>
<Link
containerWidth="100%"
type="page"
title={titleWithoutExt}
fontWeight="bold"
fontSize="15px"
{...linkStyles}
color="#333"
isTextOverflow
>
{titleWithoutExt}
</Link>
<>
{fileExst ? (
<div className="badges">
<Text
className="badge-ext"
as="span"
color="#A3A9AE"
fontSize="15px"
fontWeight={600}
title={fileExst}
truncate={true}
>
{fileExst}
</Text>
</div>
) : (
<div className="badges">
{!!showNew && (
<Badge
className="badge-version"
backgroundColor="#ED7309"
borderRadius="11px"
color="#FFFFFF"
fontSize="10px"
fontWeight={800}
label={newItems}
maxWidth="50px"
onClick={this.onBadgeClick}
padding="0 5px"
data-id={id}
/>
)}
</div>
)}
</>
</SimpleFilesTileContent>
</>
);
}
}
export default inject(
(
{ auth, filesStore, formatsStore, treeFoldersStore, selectedFolderStore },
{ item }
) => {
const { culture } = auth.settingsStore;
const {
iconFormatsStore,
mediaViewersFormatsStore,
docserviceStore,
} = formatsStore;
const {
folders,
fetchFiles,
filter,
newRowItems,
createFile,
updateFile,
renameFolder,
createFolder,
setIsLoading,
isLoading,
dragging,
} = filesStore;
const {
treeFolders,
setTreeFolders,
isRecycleBinFolder,
expandedKeys,
addExpandedKeys,
} = treeFoldersStore;
const { type, extension, id } = filesStore.fileActionStore;
const fileAction = { type, extension, id };
const canWebEdit = docserviceStore.canWebEdit(item.fileExst);
const isVideo = mediaViewersFormatsStore.isVideo(item.fileExst);
const isImage = iconFormatsStore.isImage(item.fileExst);
const isSound = iconFormatsStore.isSound(item.fileExst);
return {
culture,
homepage: config.homepage,
fileAction,
folders,
rootFolderId: selectedFolderStore.pathParts,
selectedFolderId: selectedFolderStore.id,
selectedFolderPathParts: selectedFolderStore.pathParts,
newItems: selectedFolderStore.new,
parentFolder: selectedFolderStore.parentId,
isLoading,
treeFolders,
isTrashFolder: isRecycleBinFolder,
filter,
dragging,
canWebEdit,
isVideo,
isImage,
isSound,
newRowItems,
expandedKeys,
setIsLoading,
fetchFiles,
setTreeFolders,
createFile,
createFolder,
updateFile,
renameFolder,
addExpandedKeys,
};
}
)(withRouter(withTranslation("Home")(observer(FilesTileContent))));

View File

@ -12,7 +12,7 @@ import {
StyledHeaderContent,
StyledBody,
} from "../StyledPanels";
import { SectionBodyContent } from "../../pages/VersionHistory/Section/";
import { SectionBodyContent } from "../../../pages/VersionHistory/Section/";
import { inject, observer } from "mobx-react";
import config from "../../../../package.json";

View File

@ -0,0 +1,35 @@
import React from "react";
import { inject, observer } from "mobx-react";
import RowContainer from "@appserver/components/row-container";
import { Consumer } from "@appserver/components/utils/context";
import SimpleFilesRow from "./SimpleFilesRow";
const FilesRowContainer = ({ filesList }) => {
return (
<Consumer>
{(context) => (
<RowContainer
className="files-row-container"
draggable
useReactWindow={false}
>
{filesList.map((item, index) => (
<SimpleFilesRow
key={`${item.id}_${index}`}
item={item}
sectionWidth={context.sectionWidth}
/>
))}
</RowContainer>
)}
</Consumer>
);
};
export default inject(({ filesStore }) => {
const { filesList } = filesStore;
return {
filesList,
};
})(observer(FilesRowContainer));

View File

@ -0,0 +1,164 @@
import React from "react";
import { withRouter } from "react-router";
import { withTranslation } from "react-i18next";
import styled from "styled-components";
import { isMobile } from "react-device-detect";
import Link from "@appserver/components/link";
import Text from "@appserver/components/text";
import RowContent from "@appserver/components/row-content";
import withContent from "../../../../../HOCs/withContent";
import withBadges from "../../../../../HOCs/withBadges";
const sideColor = "#A3A9AE";
const SimpleFilesRowContent = styled(RowContent)`
.badge-ext {
margin-left: -8px;
margin-right: 8px;
}
.badge {
height: 14px;
width: 14px;
margin-right: 6px;
}
.lock-file {
cursor: pointer;
}
.badges {
display: flex;
align-items: center;
}
.favorite {
cursor: pointer;
margin-right: 6px;
}
.share-icon {
margin-top: -4px;
padding-right: 8px;
}
.row_update-text {
overflow: hidden;
text-overflow: ellipsis;
}
`;
const FilesRowContent = ({
t,
item,
sectionWidth,
titleWithoutExt,
updatedDate,
fileOwner,
linkStyles,
isTrashFolder,
onFilesClick,
badgesComponent,
}) => {
const {
contentLength,
fileExst,
filesCount,
foldersCount,
providerKey,
} = item;
const onMobileRowClick = () => {
if (isTrashFolder || window.innerWidth > 1024) return;
onFilesClick();
};
return (
<>
<SimpleFilesRowContent
sectionWidth={sectionWidth}
isMobile={isMobile}
sideColor={sideColor}
isFile={fileExst || contentLength}
//onClick={onMobileRowClick}
>
<Link
containerWidth="55%"
type="page"
title={titleWithoutExt}
fontWeight="600"
fontSize="15px"
{...linkStyles}
color="#333"
isTextOverflow
>
{titleWithoutExt}
</Link>
<div className="badges">
{fileExst ? (
<Text
className="badge-ext"
as="span"
color="#A3A9AE"
fontSize="15px"
fontWeight={600}
title={fileExst}
truncate={true}
>
{fileExst}
</Text>
) : null}
{badgesComponent}
</div>
<Text
containerMinWidth="120px"
containerWidth="15%"
as="div"
color={sideColor}
fontSize="12px"
fontWeight={400}
title={fileOwner}
truncate={true}
>
{fileOwner}
</Text>
<Text
containerMinWidth="200px"
containerWidth="15%"
title={updatedDate}
fontSize="12px"
fontWeight={400}
color={sideColor}
className="row_update-text"
>
{(fileExst || contentLength || !providerKey) &&
updatedDate &&
updatedDate}
</Text>
<Text
containerMinWidth="90px"
containerWidth="10%"
as="div"
color={sideColor}
fontSize="12px"
fontWeight={400}
title=""
truncate={true}
>
{fileExst || contentLength
? contentLength
: !providerKey
? `${t("TitleDocuments")}: ${filesCount} | ${t(
"TitleSubfolders"
)}: ${foldersCount}`
: ""}
</Text>
</SimpleFilesRowContent>
</>
);
};
export default withRouter(
withTranslation("Home")(withContent(withBadges(FilesRowContent)))
);

View File

@ -0,0 +1,107 @@
import React from "react";
import styled from "styled-components";
import { withTranslation } from "react-i18next";
import DragAndDrop from "@appserver/components/drag-and-drop";
import Row from "@appserver/components/row";
import FilesRowContent from "./FilesRowContent";
import { withRouter } from "react-router-dom";
import { createSelectable } from "react-selectable-fast";
import withFileActions from "../../../../../HOCs/withFileActions";
import withContextOptions from "../../../../../HOCs/withContextOptions";
const StyledSimpleFilesRow = styled(Row)`
margin-top: -2px;
${(props) =>
!props.contextOptions &&
`
& > div:last-child {
width: 0px;
}
`}
.share-button-icon {
margin-right: 7px;
margin-top: -1px;
}
.share-button:hover,
.share-button-icon:hover {
cursor: pointer;
color: #657077;
path {
fill: #657077;
}
}
-webkit-tap-highlight-color: rgba(0, 0, 0, 0);
@media (max-width: 1312px) {
.share-button {
padding-top: 3px;
}
}
.styled-element {
margin-right: 7px;
}
`;
const SimpleFilesRow = createSelectable((props) => {
const {
item,
sectionWidth,
dragging,
onContentRowSelect,
rowContextClick,
onDrop,
onMouseDown,
className,
isDragging,
value,
displayShareButton,
isPrivacy,
sharedButton,
contextOptionsProps,
checkedProps,
element,
onFilesClick,
} = props;
return (
<div ref={props.selectableRef}>
<DragAndDrop
data-title={item.title}
value={value}
className={className}
onDrop={onDrop}
onMouseDown={onMouseDown}
dragging={dragging && isDragging}
{...contextOptionsProps}
>
<StyledSimpleFilesRow
key={item.id}
data={item}
element={element}
sectionWidth={sectionWidth}
contentElement={sharedButton}
onSelect={onContentRowSelect}
rowContextClick={rowContextClick}
isPrivacy={isPrivacy}
{...checkedProps}
{...contextOptionsProps}
contextButtonSpacerWidth={displayShareButton}
>
<FilesRowContent
item={item}
sectionWidth={sectionWidth}
onFilesClick={onFilesClick}
/>
</StyledSimpleFilesRow>
</DragAndDrop>
</div>
);
});
export default withTranslation("Home")(
withFileActions(withContextOptions(withRouter(SimpleFilesRow)))
);

View File

@ -0,0 +1,64 @@
import React from "react";
import styled from "styled-components";
import { withTranslation } from "react-i18next";
import DragAndDrop from "@appserver/components/drag-and-drop";
import Tile from "./sub-components/Tile";
import FilesTileContent from "./FilesTileContent";
import { withRouter } from "react-router-dom";
import { createSelectable } from "react-selectable-fast";
import withFileActions from "../hoc/withFileActions";
const FilesTile = createSelectable((props) => {
const {
item,
sectionWidth,
dragging,
onContentRowSelect,
rowContextClick,
onDrop,
onMouseDown,
className,
isDragging,
value,
displayShareButton,
isPrivacy,
sharedButton,
contextOptionsProps,
checkedProps,
element,
} = props;
return (
<div ref={props.selectableRef}>
<DragAndDrop
data-title={item.title}
value={value}
className={className}
onDrop={onDrop}
onMouseDown={onMouseDown}
dragging={dragging && isDragging}
{...contextOptionsProps}
>
<Tile
key={item.id}
data={item}
element={element}
sectionWidth={sectionWidth}
contentElement={sharedButton}
onSelect={onContentRowSelect}
rowContextClick={rowContextClick}
isPrivacy={isPrivacy}
{...checkedProps}
{...contextOptionsProps}
contextButtonSpacerWidth={displayShareButton}
>
<FilesTileContent item={item} sectionWidth={sectionWidth} />
</Tile>
</DragAndDrop>
</div>
);
});
export default withTranslation("Home")(withFileActions(withRouter(FilesTile)));

View File

@ -0,0 +1,153 @@
import React from "react";
import { withRouter } from "react-router";
import { withTranslation } from "react-i18next";
import styled from "styled-components";
import Link from "@appserver/components/link";
import Text from "@appserver/components/text";
import TileContent from "./TileContent";
import withContent from "../hoc/withContent";
import Badges from "../sub-components/Badges";
const SimpleFilesTileContent = styled(TileContent)`
.rowMainContainer {
height: auto;
max-width: 100%;
align-self: flex-end;
a {
word-break: break-word;
}
}
.mainIcons {
align-self: flex-end;
}
.badge-ext {
margin-left: -8px;
margin-right: 8px;
}
.badge {
margin-right: 8px;
}
.badges {
display: flex;
align-items: center;
}
.share-icon {
margin-top: -4px;
padding-right: 8px;
}
@media (max-width: 1024px) {
display: inline-flex;
height: auto;
& > div {
margin-top: 0;
}
}
`;
const FilesTileContent = ({
t,
item,
sectionWidth,
titleWithoutExt,
updatedDate,
fileOwner,
accessToEdit,
linkStyles,
newItems,
showNew,
canWebEdit,
canConvert,
isTrashFolder,
onFilesClick,
onShowVersionHistory,
onBadgeClick,
onClickLock,
onClickFavorite,
setConvertDialogVisible,
}) => {
const {
contentLength,
fileExst,
filesCount,
foldersCount,
fileStatus,
id,
versionGroup,
locked,
providerKey,
} = item;
const onMobileRowClick = () => {
if (isTrashFolder || window.innerWidth > 1024) return;
onFilesClick();
};
return (
<>
<SimpleFilesTileContent
sideColor="#333"
isFile={fileExst}
//onClick={onMobileRowClick}
//disableSideInfo
>
<Link
containerWidth="100%"
type="page"
title={titleWithoutExt}
fontWeight="bold"
fontSize="15px"
{...linkStyles}
color="#333"
isTextOverflow
>
{titleWithoutExt}
{fileExst ? (
<Text
className="badge-ext"
as="span"
color="#A3A9AE"
fontSize="15px"
fontWeight={600}
title={fileExst}
truncate={true}
>
{fileExst}
</Text>
) : null}
</Link>
<div className="badges">
<Badges
newItems={newItems}
item={item}
canWebEdit={canWebEdit}
isTrashFolder={isTrashFolder}
canConvert={canConvert}
accessToEdit={accessToEdit}
showNew={showNew}
onFilesClick={onFilesClick}
onClickLock={onClickLock}
onClickFavorite={onClickFavorite}
onShowVersionHistory={onShowVersionHistory}
onBadgeClick={onBadgeClick}
setConvertDialogVisible={setConvertDialogVisible}
/>
</div>
</SimpleFilesTileContent>
</>
);
};
export default withRouter(
withTranslation("Home")(withContent(FilesTileContent))
);

View File

@ -1,25 +1,22 @@
import React, { useEffect, useState } from "react";
import React, { useEffect } from "react";
import { withRouter } from "react-router";
import { withTranslation } from "react-i18next";
import Loaders from "@appserver/common/components/Loaders";
import { isMobile } from "react-device-detect";
import { observer, inject } from "mobx-react";
import FilesRowContainer from "./FilesRow/FilesRowContainer";
import FilesTileContainer from "./FilesTile/FilesTileContainer";
import EmptyContainer from "./EmptyContainer";
import FilesRowContainer from "./RowsView/FilesRowContainer";
import FilesTileContainer from "./TilesView/FilesTileContainer";
import EmptyContainer from "../../../../components/EmptyContainer";
import withLoader from "../../../../HOCs/withLoader";
let currentDroppable = null;
let loadTimeout = null;
const SectionBodyContent = (props) => {
const {
t,
tReady,
fileActionId,
viewAs,
firstLoad,
isLoading,
isEmptyFilesList,
folderId,
dragging,
@ -31,31 +28,6 @@ const SectionBodyContent = (props) => {
moveDragItems,
} = props;
const [inLoad, setInLoad] = useState(false);
const cleanTimer = () => {
loadTimeout && clearTimeout(loadTimeout);
loadTimeout = null;
};
useEffect(() => {
if (isLoading) {
cleanTimer();
loadTimeout = setTimeout(() => {
console.log("inLoad", true);
setInLoad(true);
}, 500);
} else {
cleanTimer();
console.log("inLoad", false);
setInLoad(false);
}
return () => {
cleanTimer();
};
}, [isLoading]);
useEffect(() => {
const customScrollElm = document.querySelector(
"#customScrollBar > .scroll-body"
@ -174,11 +146,7 @@ const SectionBodyContent = (props) => {
//console.log("Files Home SectionBodyContent render", props);
return (!fileActionId && isEmptyFilesList) || null ? (
firstLoad || (isMobile && inLoad) ? (
<Loaders.Rows />
) : (
<EmptyContainer />
)
<EmptyContainer />
) : viewAs === "tile" ? (
<FilesTileContainer />
) : (
@ -194,14 +162,12 @@ export default inject(
filesActionsStore,
}) => {
const {
firstLoad,
fileActionStore,
filesList,
dragging,
setDragging,
startDrag,
setStartDrag,
isLoading,
viewAs,
setTooltipPosition,
} = filesStore;
@ -209,9 +175,7 @@ export default inject(
return {
dragging,
fileActionId: fileActionStore.id,
firstLoad,
viewAs,
isLoading,
isEmptyFilesList: filesList.length <= 0,
setDragging,
startDrag,
@ -222,4 +186,6 @@ export default inject(
moveDragItems: filesActionsStore.moveDragItems,
};
}
)(withRouter(withTranslation("Home")(observer(SectionBodyContent))));
)(
withRouter(withTranslation("Home")(withLoader(observer(SectionBodyContent))))
);

View File

@ -14,7 +14,7 @@ import {
ArticleBodyContent,
ArticleHeaderContent,
ArticleMainButtonContent,
} from "../../Article";
} from "../../components/Article";
import {
SectionBodyContent,
SectionFilterContent,
@ -22,11 +22,11 @@ import {
SectionPagingContent,
} from "./Section";
import { ConvertDialog } from "../../dialogs";
import { ConvertDialog } from "../../components/dialogs";
import MediaViewer from "./MediaViewer";
import DragTooltip from "../../DragTooltip";
import DragTooltip from "../../components/DragTooltip";
import { observer, inject } from "mobx-react";
import config from "../../../../package.json";
import config from "../../../package.json";
class PureHome extends React.Component {
componentDidMount() {

View File

@ -7,22 +7,22 @@ import Box from "@appserver/components/box";
import Row from "@appserver/components/row";
import RowContainer from "@appserver/components/row-container";
import { withTranslation } from "react-i18next";
import EmptyFolderContainer from "../../../Home/Section/Body/EmptyContainer/EmptyContainer";
import BoxIcon from "../../../../../../public/images/icon_box.react.svg";
import DropBoxIcon from "../../../../../../public/images/icon_dropbox.react.svg";
import GoogleDriveIcon from "../../../../../../public/images/icon_google_drive.react.svg";
import KDriveIcon from "../../../../../../public/images/icon_kdrive.react.svg";
import NextCloudIcon from "../../../../../../public/images/icon_nextcloud.react.svg";
import OneDriveIcon from "../../../../../../public/images/icon_onedrive.react.svg";
import OwnCloudIcon from "../../../../../../public/images/icon_owncloud.react.svg";
import SharePointIcon from "../../../../../../public/images/icon_sharepoint.react.svg";
import WebDavIcon from "../../../../../../public/images/icon_webdav.react.svg";
import YandexDiskIcon from "../../../../../../public/images/icon_yandex_disk.react.svg";
import EmptyFolderContainer from "../../../../components/EmptyContainer/EmptyContainer";
import BoxIcon from "../../../../../public/images/icon_box.react.svg";
import DropBoxIcon from "../../../../../public/images/icon_dropbox.react.svg";
import GoogleDriveIcon from "../../../../../public/images/icon_google_drive.react.svg";
import KDriveIcon from "../../../../../public/images/icon_kdrive.react.svg";
import NextCloudIcon from "../../../../../public/images/icon_nextcloud.react.svg";
import OneDriveIcon from "../../../../../public/images/icon_onedrive.react.svg";
import OwnCloudIcon from "../../../../../public/images/icon_owncloud.react.svg";
import SharePointIcon from "../../../../../public/images/icon_sharepoint.react.svg";
import WebDavIcon from "../../../../../public/images/icon_webdav.react.svg";
import YandexDiskIcon from "../../../../../public/images/icon_yandex_disk.react.svg";
import commonIconsStyles from "@appserver/components/utils/common-icons-style";
import { inject, observer } from "mobx-react";
import combineUrl from "@appserver/common/utils/combineUrl";
import AppServerConfig from "@appserver/common/constants/AppServerConfig";
import config from "../../../../../../package.json";
import config from "../../../../../package.json";
import { withRouter } from "react-router";
const StyledBoxIcon = styled(BoxIcon)`

View File

@ -6,7 +6,7 @@ import Error403 from "studio/Error403";
import Error520 from "studio/Error520";
import ConnectClouds from "./ConnectedClouds";
import { inject, observer } from "mobx-react";
import { loopTreeFolders } from "../../../../../helpers/files-helpers";
import { loopTreeFolders } from "../../../../helpers/files-helpers";
const StyledSettings = styled.div`
display: grid;

View File

@ -7,10 +7,10 @@ import {
ArticleHeaderContent,
ArticleBodyContent,
ArticleMainButtonContent,
} from "../../Article";
} from "../../components/Article";
import { SectionHeaderContent, SectionBodyContent } from "./Section";
import { withTranslation } from "react-i18next";
import { setDocumentTitle } from "../../../helpers/utils";
import { setDocumentTitle } from "../../helpers/utils";
import { inject, observer } from "mobx-react";
const PureSettings = ({

View File

@ -10,7 +10,7 @@ import { withTranslation } from "react-i18next";
import { withRouter } from "react-router";
import VersionBadge from "./VersionBadge";
import StyledVersionRow from "./StyledVersionRow";
import ExternalLinkIcon from "../../../../../../public/images/external.link.react.svg";
import ExternalLinkIcon from "../../../../../public/images/external.link.react.svg";
import commonIconsStyles from "@appserver/components/utils/common-icons-style";
import { inject, observer } from "mobx-react";

View File

@ -8,7 +8,7 @@ import {
ArticleHeaderContent,
ArticleBodyContent,
ArticleMainButtonContent,
} from "../../Article";
} from "../../components/Article";
import { SectionHeaderContent, SectionBodyContent } from "./Section";
//import { setDocumentTitle } from "../../../helpers/utils";
import { inject, observer } from "mobx-react";

View File

@ -976,10 +976,12 @@ namespace ASC.Files.Core.Data
};
FilesDbContext.AddOrUpdate(r => r.BunchObjects, toInsert);
FilesDbContext.SaveChanges();
tx.Commit(); //Commit changes
}
FilesDbContext.SaveChanges();
return newFolderId;
}

View File

@ -193,6 +193,7 @@ class SectionBodyContent extends React.PureComponent {
const providerButtons =
providers &&
providers.map((item) => {
if (!providersData[item.provider]) return;
const { icon, label, iconOptions } = providersData[item.provider];
if (!icon || !label) return <React.Fragment></React.Fragment>;
@ -247,17 +248,21 @@ class SectionBodyContent extends React.PureComponent {
return providerButtons;
};
oauthDataExists = () => {
const { providers } = this.props;
let existProviders = 0;
providers && providers.length > 0;
providers.map((item) => {
if (!providersData[item.provider]) return;
existProviders++;
});
return !!existProviders;
};
render() {
const {
profile,
cultures,
culture,
isAdmin,
viewer,
t,
isSelf,
providers,
} = this.props;
const { profile, cultures, culture, isAdmin, t, isSelf } = this.props;
const contacts = profile.contacts && getUserContacts(profile.contacts);
const role = getUserRole(profile);
@ -300,7 +305,7 @@ class SectionBodyContent extends React.PureComponent {
culture={culture}
/>
{isSelf && providers && providers.length > 0 && (
{isSelf && this.oauthDataExists() && (
<ToggleWrapper>
<ToggleContent label={t("LoginSettings")} isOpen={true}>
<ProviderButtonsWrapper>

View File

@ -1372,21 +1372,54 @@ namespace ASC.Api.Settings
return FirstTimeTenantSettings.SaveData(wizardModel);
}
[Read("tfaapp")]
public IEnumerable<TfaSettings> GetTfaSettings()
{
var result = new List<TfaSettings>();
var SmsVisible = StudioSmsNotificationSettingsHelper.IsVisibleSettings();
var SmsEnable = SmsVisible && SmsProviderManager.Enabled();
var TfaVisible = TfaAppAuthSettings.IsVisibleSettings;
if (SmsVisible)
{
result.Add(new TfaSettings
{
Enabled = StudioSmsNotificationSettingsHelper.Enable,
Id = "sms",
Title = Resource.ButtonSmsEnable,
Avaliable = SmsEnable
});
}
if (TfaVisible)
{
result.Add(new TfaSettings
{
Enabled = SettingsManager.Load<TfaAppAuthSettings>().EnableSetting,
Id = "app",
Title = Resource.ButtonTfaAppEnable,
Avaliable = true
});
}
return result;
}
[Update("tfaapp")]
public bool TfaSettingsFromBody([FromBody]TfaModel model)
{
return TfaSettings(model);
return TfaSettingsUpdate(model);
}
[Update("tfaapp")]
[Consumes("application/x-www-form-urlencoded")]
public bool TfaSettingsFromForm([FromForm] TfaModel model)
{
return TfaSettings(model);
return TfaSettingsUpdate(model);
}
private bool TfaSettings(TfaModel model)
private bool TfaSettingsUpdate(TfaModel model)
{
PermissionContext.DemandPermissions(SecutiryConstants.EditPortalSettings);

View File

@ -0,0 +1,10 @@
namespace ASC.Web.Api.Models
{
public class TfaSettings
{
public string Id { get; set; }
public string Title { get; set; }
public bool Enabled { get; set; }
public bool Avaliable { get; set; }
}
}

View File

@ -195,10 +195,11 @@ class Confirm extends React.PureComponent {
const providerButtons =
providers &&
providers.map((item, index) => {
if (!providersData[item.provider]) return;
const { icon, label, iconOptions, className } = providersData[
item.provider
];
if (!icon) return;
if (item.provider === "Facebook") {
facebookIndex = index;
return;
@ -223,6 +224,19 @@ class Confirm extends React.PureComponent {
return providerButtons;
};
oauthDataExists = () => {
const { providers } = this.props;
let existProviders = 0;
providers && providers.length > 0;
providers.map((item) => {
if (!providersData[item.provider]) return;
existProviders++;
});
return !!existProviders;
};
authCallback = (profile) => {
const { t, defaultPage } = this.props;
const { FirstName, LastName, EMail, Serialized } = profile;
@ -493,7 +507,7 @@ class Confirm extends React.PureComponent {
onClick={this.onSubmit}
/>
</div>
{providers && providers.length > 0 && (
{this.oauthDataExists && (
<Box>
<ButtonsWrapper>{this.providerButtons()}</ButtonsWrapper>
</Box>

View File

@ -105,6 +105,24 @@ namespace ASC.Web.Core.PublicResources {
}
}
/// <summary>
/// Looks up a localized string similar to By SMS.
/// </summary>
public static string ButtonSmsEnable {
get {
return ResourceManager.GetString("ButtonSmsEnable", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to By authenticator app.
/// </summary>
public static string ButtonTfaAppEnable {
get {
return ResourceManager.GetString("ButtonTfaAppEnable", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to A link to confirm the operation has been sent to :email (the email address of the portal owner)..
/// </summary>

View File

@ -53,10 +53,10 @@
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=5.0.4.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=5.0.5.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=5.0.4.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=5.0.5.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<data name="ActivateMobilePhoneEmptyCode" xml:space="preserve">
<value>Das Feld mit dem Prüfcode darf nicht leer sein</value>
@ -73,6 +73,12 @@
<data name="AdminMessageSent" xml:space="preserve">
<value>Ihre Nachricht wurde erfolgreich gesendet. Der Portaladministrator wird Sie kontaktieren.</value>
</data>
<data name="ButtonSmsEnable" xml:space="preserve">
<value>Per SMS</value>
</data>
<data name="ButtonTfaAppEnable" xml:space="preserve">
<value>Über die Authentifizierungs-App</value>
</data>
<data name="ChangePortalOwnerMsg" xml:space="preserve">
<value>Der Link zum Bestätigen der Operation wurde an :email verschickt (die E-Mail-Adresse des Portalbesitzers).</value>
</data>

View File

@ -53,10 +53,10 @@
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=5.0.4.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=5.0.5.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=5.0.4.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=5.0.5.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<data name="ActivateMobilePhoneEmptyCode" xml:space="preserve">
<value>Campo de código de validación no puede estar vacío</value>
@ -73,6 +73,12 @@
<data name="AdminMessageSent" xml:space="preserve">
<value>Su mensaje fue enviado con éxito. El administrador del portal se pondrá en contacto con usted.</value>
</data>
<data name="ButtonSmsEnable" xml:space="preserve">
<value>Por SMS</value>
</data>
<data name="ButtonTfaAppEnable" xml:space="preserve">
<value>Con ayuda de la aplicación para autenticación</value>
</data>
<data name="ChangePortalOwnerMsg" xml:space="preserve">
<value>Un enlace para confirmar la operación ha sido enviada al :correo electrónico (la dirección del correo electrónico del propietario del portal).</value>
</data>

View File

@ -53,10 +53,10 @@
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=5.0.4.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=5.0.5.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=5.0.4.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=5.0.5.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<data name="ActivateMobilePhoneEmptyCode" xml:space="preserve">
<value>Le champ du code de validation ne peut pas être vide</value>
@ -73,6 +73,12 @@
<data name="AdminMessageSent" xml:space="preserve">
<value>Votre message a été envoyé avec succès. Vous serez contacté par l'administrateur du portail.</value>
</data>
<data name="ButtonSmsEnable" xml:space="preserve">
<value>par SMS</value>
</data>
<data name="ButtonTfaAppEnable" xml:space="preserve">
<value>Par l'application authentificateur</value>
</data>
<data name="ChangePortalOwnerMsg" xml:space="preserve">
<value>Un lien pour confirmer l'opération a été envoyé à :e-mail (l'adresse e-mail du propriétaire du portail).</value>
</data>

View File

@ -53,10 +53,10 @@
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=5.0.4.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=5.0.5.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=5.0.4.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=5.0.5.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<data name="ActivateMobilePhoneEmptyCode" xml:space="preserve">
<value>Il campo Valida codice non può essere vuoto</value>
@ -73,6 +73,12 @@
<data name="AdminMessageSent" xml:space="preserve">
<value>Il tuo messaggio è stato inviato con successo. Sarai contattato dall'amministratore del portale.</value>
</data>
<data name="ButtonSmsEnable" xml:space="preserve">
<value>Tramite SMS</value>
</data>
<data name="ButtonTfaAppEnable" xml:space="preserve">
<value>Con authenticator app</value>
</data>
<data name="ChangePortalOwnerMsg" xml:space="preserve">
<value>Un collegamento per confermare l'operazione è stato inviato all'indirizzo :email (l'indirizzo email del proprietario del portale).</value>
</data>

View File

@ -53,10 +53,10 @@
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=5.0.4.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=5.0.5.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=5.0.4.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=5.0.5.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<data name="ActivateMobilePhoneEmptyCode" xml:space="preserve">
<value>Validation code field cannot be empty</value>
@ -73,6 +73,12 @@
<data name="AdminMessageSent" xml:space="preserve">
<value>Your message was successfully sent. You will be contacted by the portal administrator.</value>
</data>
<data name="ButtonSmsEnable" xml:space="preserve">
<value>By SMS</value>
</data>
<data name="ButtonTfaAppEnable" xml:space="preserve">
<value>By authenticator app</value>
</data>
<data name="ChangePortalOwnerMsg" xml:space="preserve">
<value>A link to confirm the operation has been sent to :email (the email address of the portal owner).</value>
</data>

View File

@ -53,10 +53,10 @@
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=5.0.4.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=5.0.5.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=5.0.4.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=5.0.5.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<data name="ActivateMobilePhoneEmptyCode" xml:space="preserve">
<value>Поле кода подтверждения не может быть пустым</value>
@ -73,6 +73,12 @@
<data name="AdminMessageSent" xml:space="preserve">
<value>Ваше сообщение успешно отправлено. Администратор портала с Вами свяжется.</value>
</data>
<data name="ButtonSmsEnable" xml:space="preserve">
<value>С помощью SMS</value>
</data>
<data name="ButtonTfaAppEnable" xml:space="preserve">
<value>С помощью приложения для аутентификации</value>
</data>
<data name="ChangePortalOwnerMsg" xml:space="preserve">
<value>Ссылка для подтверждения операции была отправлена на :email (адрес электронной почты владельца портала).</value>
</data>

View File

@ -362,10 +362,12 @@ const Form = (props) => {
const providerButtons =
providers &&
providers.map((item, index) => {
if (!providersData[item.provider]) return;
const { icon, label, iconOptions, className } = providersData[
item.provider
];
if (!icon) return;
if (item.provider === "Facebook") {
facebookIndex = index;
return;
@ -390,6 +392,17 @@ const Form = (props) => {
return providerButtons;
};
const oauthDataExists = () => {
let existProviders = 0;
providers && providers.length > 0;
providers.map((item) => {
if (!providersData[item.provider]) return;
existProviders++;
});
return !!existProviders;
};
//console.log("Login render");
return (
@ -507,7 +520,7 @@ const Form = (props) => {
</Text>
)}
{providers && providers.length > 0 && (
{oauthDataExists() && (
<>
<Box displayProp="flex" alignItems="center" marginProp="0 0 16px 0">
<div className="login-bottom-border"></div>