Merge pull request #698 from ONLYOFFICE/feature/create-from-modal

Feature/create from modal
This commit is contained in:
Alexey Safronov 2022-07-11 17:28:09 +03:00 committed by GitHub
commit 477f386047
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
22 changed files with 819 additions and 615 deletions

View File

@ -29,6 +29,7 @@ import {
ArticleMainButtonContent,
} from "./components/Article";
import FormGallery from "./pages/FormGallery";
import GlobalEvents from "./components/GlobalEvents";
const { proxyURL } = AppServerConfig;
const homepage = config.homepage;
@ -169,6 +170,7 @@ class FilesContent extends React.Component {
return (
<>
<GlobalEvents />
<Panels />
<FilesArticle history={this.props.history} />
<FilesSection />

View File

@ -21,384 +21,19 @@ export default function withContent(WrappedContent) {
constructor(props) {
super(props);
const {
item,
fileActionId,
fileActionExt,
fileActionTemplateId,
fromTemplate,
} = props;
let titleWithoutExt = props.titleWithoutExt;
if (
fileActionId === -1 &&
item.id === fileActionId &&
fileActionTemplateId === null &&
!fromTemplate
) {
titleWithoutExt = getDefaultFileName(fileActionExt);
}
this.state = { itemTitle: titleWithoutExt };
}
componentDidUpdate(prevProps) {
const {
fileActionId,
fileActionExt,
setIsUpdatingRowItem,
isUpdatingRowItem,
isEdit,
titleWithoutExt,
} = this.props;
if (fileActionId === -1 && fileActionExt !== prevProps.fileActionExt) {
const itemTitle = getDefaultFileName(fileActionExt);
this.setState({ itemTitle });
}
if (fileActionId === null && prevProps.fileActionId !== fileActionId) {
isUpdatingRowItem && setIsUpdatingRowItem(false);
}
const { titleWithoutExt } = this.props;
if (!isEdit && titleWithoutExt !== this.state.itemTitle) {
if (titleWithoutExt !== this.state.itemTitle) {
this.setState({ itemTitle: titleWithoutExt });
}
}
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,
addActiveItems,
clearActiveOperations,
} = this.props;
const { itemTitle } = this.state;
const originalTitle = getTitleWithoutExst(item);
setIsLoading(true);
let timerId;
const isSameTitle =
originalTitle.trim() === itemTitle.trim() || itemTitle.trim() === "";
const isFile = item.fileExst || item.contentLength;
if (isSameTitle) {
this.setState({
itemTitle: originalTitle,
});
return editCompleteAction(fileActionId, item, isSameTitle);
} else {
timerId = setTimeout(() => {
isFile ? addActiveItems([item.id]) : addActiveItems(null, [item.id]);
}, 500);
}
isFile
? updateFile(fileActionId, itemTitle)
.then(() => this.completeAction(fileActionId))
.then(() =>
toastr.success(
t("FileRenamed", {
oldTitle: item.title,
newTitle: itemTitle + item.fileExst,
})
)
)
.catch((err) => {
toastr.error(err);
this.completeAction(fileActionId);
})
.finally(() => {
clearTimeout(timerId);
timerId = null;
clearActiveOperations([item.id]);
setIsLoading(false);
})
: renameFolder(fileActionId, itemTitle)
.then(() => this.completeAction(fileActionId))
.then(() =>
toastr.success(
t("FolderRenamed", {
folderTitle: item.title,
newFoldedTitle: itemTitle,
})
)
)
.catch((err) => {
toastr.error(err);
this.completeAction(fileActionId);
})
.finally(() => {
clearTimeout(timerId);
timerId = null;
clearActiveOperations(null, [item.id]);
setIsLoading(false);
});
};
cancelUpdateItem = (e) => {
const { item } = this.props;
const originalTitle = getTitleWithoutExst(item);
this.setState({
itemTitle: originalTitle,
});
return this.completeAction(e);
};
onClickUpdateItem = (e, open = true) => {
const {
fileActionType,
setIsUpdatingRowItem,
addActiveItems,
item,
} = this.props;
setIsUpdatingRowItem(true);
if (fileActionType === FileAction.Create) {
!item.fileExst && !item.contentLength
? addActiveItems(null, [item.id])
: addActiveItems([item.id]);
this.createItem(e, open);
} else {
this.updateItem(e);
}
};
createItem = (e, open) => {
const {
createFile,
createFolder,
fileActionTemplateId,
isDesktop,
isPrivacy,
item,
openDocEditor,
replaceFileStream,
setEncryptionAccess,
setIsLoading,
t,
setConvertPasswordDialogVisible,
setFormCreationInfo,
setIsUpdatingRowItem,
clearActiveOperations,
addActiveItems,
fileCopyAs,
fromTemplate,
gallerySelected,
setCreatedItem,
} = this.props;
const { itemTitle } = this.state;
const { parentId, fileExst } = item;
const isMakeFormFromFile = fileActionTemplateId ? true : false;
let title = itemTitle;
setIsLoading(true);
const itemId = e.currentTarget.dataset.itemid;
let createdFileId, createdFolderId;
if (itemTitle.trim() === "") {
title =
fileActionTemplateId === null
? getDefaultFileName(item.fileExst)
: getTitleWithoutExst(item);
this.setState({
itemTitle: title,
});
}
let tab =
!isDesktop && item.fileExst && open
? window.open(
combineUrl(
AppServerConfig.proxyURL,
config.homepage,
"/doceditor"
),
"_blank"
)
: null;
if (!item.fileExst && !item.contentLength) {
createFolder(item.parentId, title)
.then((folder) => {
createdFolderId = folder.id;
addActiveItems(null, [folder.id]);
setCreatedItem({ id: createdFolderId, type: "folder" });
})
.then(() => this.completeAction(itemId))
.catch((e) => {
toastr.error(e);
this.completeAction(itemId);
})
.finally(() => {
const folderIds = [+itemId];
createdFolderId && folderIds.push(createdFolderId);
setIsUpdatingRowItem(false);
clearActiveOperations(null, folderIds);
return setIsLoading(false);
});
} else {
if (isMakeFormFromFile) {
fileCopyAs(
fileActionTemplateId,
`${title}.${item.fileExst}`,
item.parentId
)
.then((file) => {
createdFileId = file.id;
addActiveItems([file.id]);
open && openDocEditor(file.id, file.providerKey, tab);
})
.then(() => this.completeAction(itemId))
.catch((err) => {
if (err.indexOf("password") == -1) {
toastr.error(err, t("Common:Warning"));
return;
}
toastr.error(
t("Translations:FileProtected"),
t("Common:Warning")
);
setFormCreationInfo({
newTitle: `${title}.${item.fileExst}`,
fromExst: ".docx",
toExst: item.fileExst,
open,
actionId: itemId,
fileInfo: {
id: fileActionTemplateId,
folderId: item.parentId,
fileExst: item.fileExst,
},
});
setConvertPasswordDialogVisible(true);
open && openDocEditor(null, null, tab);
})
.finally(() => {
const fileIds = [+itemId];
createdFileId && fileIds.push(createdFileId);
setIsUpdatingRowItem(false);
clearActiveOperations(fileIds);
return setIsLoading(false);
});
} else if (fromTemplate) {
createFile(
parentId,
`${itemTitle}.${fileExst}`,
undefined,
gallerySelected.id
)
.then((file) => {
createdFileId = file.id;
setCreatedItem({ id: createdFileId, type: "file" });
addActiveItems([file.id]);
return open && openDocEditor(file.id, file.providerKey, tab);
})
.then(() => this.completeAction(itemId))
.catch((e) => {
toastr.error(e);
tab && tab.close();
this.completeAction(itemId);
})
.finally(() => {
const fileIds = [+itemId];
createdFileId && fileIds.push(createdFileId);
setIsUpdatingRowItem(false);
clearActiveOperations(fileIds);
return setIsLoading(false);
});
} else {
createFile(item.parentId, `${title}.${item.fileExst}`)
.then((file) => {
createdFileId = file.id;
setCreatedItem({ id: createdFileId, type: "file" });
addActiveItems([file.id]);
if (isPrivacy) {
return setEncryptionAccess(file).then((encryptedFile) => {
if (!encryptedFile) return Promise.resolve();
toastr.info(t("Translations:EncryptedFileSaving"));
return replaceFileStream(
file.id,
encryptedFile,
true,
false
).then(
() => open && openDocEditor(file.id, file.providerKey, tab)
);
});
}
return open && openDocEditor(file.id, file.providerKey, tab);
})
.then(() => this.completeAction(itemId))
.catch((e) => {
toastr.error(e);
tab && tab.close();
this.completeAction(itemId);
})
.finally(() => {
const fileIds = [+itemId];
createdFileId && fileIds.push(createdFileId);
setIsUpdatingRowItem(false);
clearActiveOperations(fileIds);
return setIsLoading(false);
});
}
}
};
renameTitle = (e) => {
const { t, folderFormValidation } = this.props;
let title = e.target.value;
//const chars = '*+:"<>?|/'; TODO: think how to solve problem with interpolation escape values in i18n translate
if (title.match(folderFormValidation)) {
toastr.warning(t("ContainsSpecCharacter"));
}
title = title.replace(folderFormValidation, "_");
return this.setState({ itemTitle: title });
};
getStatusByDate = (create) => {
const { culture, item, personal } = this.props;
const { created, updated } = item;
@ -413,7 +48,6 @@ export default function withContent(WrappedContent) {
};
render() {
const { itemTitle } = this.state;
const {
element,
isDesktop,
@ -421,23 +55,13 @@ export default function withContent(WrappedContent) {
item,
onFilesClick,
t,
viewAs,
viewer,
isUpdatingRowItem,
passwordEntryProcess,
isEdit,
titleWithoutExt,
} = this.props;
const {
access,
createdBy,
fileExst,
fileStatus,
href,
icon,
id,
isFolder,
} = item;
const { access, createdBy, fileExst, fileStatus, href, icon, id } = item;
const updatedDate = this.getStatusByDate(false);
const createdDate = this.getStatusByDate(true);
@ -462,27 +86,8 @@ export default function withContent(WrappedContent) {
const newItems =
item.new || (fileStatus & FileStatus.IsNew) === FileStatus.IsNew;
const showNew = !!newItems;
const elementIcon = element ? (
element
) : (
<ItemIcon id={id} icon={icon} fileExst={fileExst} />
);
return isEdit ? (
<EditingWrapperComponent
className={"editing-wrapper-component"}
elementIcon={elementIcon}
itemTitle={itemTitle}
itemId={id}
viewAs={viewAs}
renameTitle={this.renameTitle}
onClickUpdateItem={this.onClickUpdateItem}
cancelUpdateItem={this.cancelUpdateItem}
isUpdatingRowItem={isUpdatingRowItem}
passwordEntryProcess={passwordEntryProcess}
isFolder={item.fileExst ? false : true}
/>
) : (
return (
<WrappedContent
titleWithoutExt={titleWithoutExt}
updatedDate={updatedDate}
@ -531,13 +136,6 @@ export default function withContent(WrappedContent) {
const { clearActiveOperations, fileCopyAs } = uploadDataStore;
const { isRecycleBinFolder, isPrivacyFolder } = treeFoldersStore;
const {
extension: fileActionExt,
id: fileActionId,
templateId: fileActionTemplateId,
type: fileActionType,
fromTemplate,
} = filesStore.fileActionStore;
const { replaceFileStream, setEncryptionAccess } = auth;
const {
@ -553,20 +151,14 @@ export default function withContent(WrappedContent) {
setFormCreationInfo,
} = dialogsStore;
const isEdit =
item.id === fileActionId && item.fileExst === fileActionExt;
const titleWithoutExt = getTitleWithoutExst(item, fromTemplate);
const titleWithoutExt = getTitleWithoutExst(item, false);
return {
createFile,
createFolder,
culture,
editCompleteAction,
fileActionExt,
fileActionId,
fileActionTemplateId,
fileActionType,
folderFormValidation,
homepage: config.homepage,
isDesktop: isDesktopClient,
@ -589,9 +181,9 @@ export default function withContent(WrappedContent) {
addActiveItems,
clearActiveOperations,
fileCopyAs,
isEdit,
titleWithoutExt,
fromTemplate,
gallerySelected,
setCreatedItem,
personal,

View File

@ -172,9 +172,7 @@ export default function withFileActions(WrappedFileItem) {
draggable,
allowShareIn,
isPrivacy,
actionType,
actionExtension,
actionId,
sectionWidth,
checked,
dragging,
@ -186,9 +184,6 @@ export default function withFileActions(WrappedFileItem) {
} = this.props;
const { fileExst, access, id } = item;
const isEdit =
actionType !== null && actionId === id && fileExst === actionExtension;
const isDragging = isFolder && access < 2 && !isTrashFolder && !isPrivacy;
let className = isDragging ? " droppable" : "";
@ -209,13 +204,12 @@ export default function withFileActions(WrappedFileItem) {
const showShare =
!isShareable ||
isEdit ||
(isPrivacy && (!isDesktop || !fileExst)) ||
(personal && !canWebEdit && !canViewedDocs)
? false
: true;
const checkedProps = isEdit || id <= 0 ? false : checked;
const checkedProps = id <= 0 ? false : checked;
return (
<WrappedFileItem
@ -235,7 +229,6 @@ export default function withFileActions(WrappedFileItem) {
showShare={showShare}
checkedProps={checkedProps}
dragging={dragging}
isEdit={isEdit}
getContextModel={this.getContextModel}
{...this.props}
/>
@ -277,7 +270,7 @@ export default function withFileActions(WrappedFileItem) {
selection,
setTooltipPosition,
setStartDrag,
fileActionStore,
getFolderInfo,
viewAs,
bufferSelection,
@ -290,14 +283,12 @@ export default function withFileActions(WrappedFileItem) {
} = filesStore;
const { startUpload } = uploadDataStore;
const { type, extension, id } = fileActionStore;
const selectedItem = selection.find(
(x) => x.id === item.id && x.fileExst === item.fileExst
);
const draggable =
!isRecycleBinFolder && selectedItem && selectedItem.id !== id;
const draggable = !isRecycleBinFolder && selectedItem;
const isFolder = selectedItem ? false : !item.isFolder ? false : true;
const canWebEdit = settingsStore.canWebEdit(item.fileExst);
@ -338,9 +329,7 @@ export default function withFileActions(WrappedFileItem) {
setStartDrag,
isFolder,
allowShareIn: filesStore.canShare,
actionType: type,
actionExtension: extension,
actionId: id,
checked: !!selectedItem,
//parentFolder: selectedFolderStore.parentId,
setParentId: selectedFolderStore.setParentId,

View File

@ -2,6 +2,7 @@ import React, { useEffect } from "react";
import { useHotkeys } from "react-hotkeys-hook";
import { observer, inject } from "mobx-react";
import { FileAction } from "@appserver/common/constants";
import { Events } from "../helpers/constants";
import toastr from "studio/toastr";
const withHotkeys = (Component) => {
@ -12,7 +13,6 @@ const withHotkeys = (Component) => {
setSelected,
viewAs,
setViewAs,
setAction,
setHotkeyPanelVisible,
confirmDelete,
setDeleteDialogVisible,
@ -65,6 +65,20 @@ const withHotkeys = (Component) => {
const folderWithNoAction =
isFavoritesFolder || isRecentFolder || isTrashFolder;
const onCreate = (extension) => {
if (folderWithNoAction) return;
const event = new Event(Events.CREATE);
const payload = {
extension: extension,
id: -1,
};
event.payload = payload;
window.dispatchEvent(event);
};
useEffect(() => {
window.addEventListener("keydown", onKeyDown);
@ -163,48 +177,28 @@ const withHotkeys = (Component) => {
);
//Crete document
useHotkeys(
"Shift+d",
() => {
if (folderWithNoAction) return;
setAction({ type: FileAction.Create, extension: "docx", id: -1 });
},
{ ...hotkeysFilter, ...{ keyup: true } }
);
useHotkeys("Shift+d", () => onCreate("docx"), {
...hotkeysFilter,
...{ keyup: true },
});
//Crete spreadsheet
useHotkeys(
"Shift+s",
() => {
if (folderWithNoAction) return;
setAction({ type: FileAction.Create, extension: "xlsx", id: -1 });
},
{ ...hotkeysFilter, ...{ keyup: true } }
);
useHotkeys("Shift+s", () => onCreate("xlsx"), {
...hotkeysFilter,
...{ keyup: true },
});
//Crete presentation
useHotkeys(
"Shift+p",
() => {
if (folderWithNoAction) return;
setAction({ type: FileAction.Create, extension: "pptx", id: -1 });
},
{ ...hotkeysFilter, ...{ keyup: true } }
);
useHotkeys("Shift+p", () => onCreate("pptx"), {
...hotkeysFilter,
...{ keyup: true },
});
//Crete form template
useHotkeys(
"Shift+o",
() => {
if (folderWithNoAction) return;
setAction({ type: FileAction.Create, extension: "docxf", id: -1 });
},
{ ...hotkeysFilter, ...{ keyup: true } }
);
useHotkeys("Shift+o", () => onCreate("docxf"), {
...hotkeysFilter,
...{ keyup: true },
});
//Crete form template from file
useHotkeys(
@ -218,14 +212,10 @@ const withHotkeys = (Component) => {
);
//Crete folder
useHotkeys(
"Shift+f",
() => {
if (folderWithNoAction) return;
setAction({ type: FileAction.Create, id: -1 });
},
{ ...hotkeysFilter, ...{ keyup: true } }
);
useHotkeys("Shift+f", () => onCreate(null), {
...hotkeysFilter,
...{ keyup: true },
});
//Delete selection
useHotkeys(
@ -329,7 +319,6 @@ const withHotkeys = (Component) => {
enabledHotkeys,
selection,
} = filesStore;
const { setAction } = fileActionStore;
const {
selectFile,
@ -376,7 +365,6 @@ const withHotkeys = (Component) => {
setSelected,
viewAs,
setViewAs,
setAction,
setHotkeyPanelVisible,
setDeleteDialogVisible,

View File

@ -67,8 +67,6 @@ export default function withQuickButtons(WrappedComponent) {
isTrashFolder,
isAdmin,
showShare,
fileActionExt,
fileActionId,
sectionWidth,
viewAs,
} = this.props;
@ -79,9 +77,7 @@ export default function withQuickButtons(WrappedComponent) {
access === ShareAccessRights.FullAccess ||
access === ShareAccessRights.None; // TODO: fix access type for owner (now - None)
const isEdit = id === fileActionId && fileExst === fileActionExt;
const quickButtonsComponent = !isEdit ? (
const quickButtonsComponent = (
<QuickButtons
t={t}
theme={theme}
@ -98,7 +94,7 @@ export default function withQuickButtons(WrappedComponent) {
onClickFavorite={this.onClickFavorite}
onClickShare={this.onClickShare}
/>
) : null;
);
return (
<WrappedComponent
@ -125,10 +121,6 @@ export default function withQuickButtons(WrappedComponent) {
onSelectItem,
} = filesActionsStore;
const {
extension: fileActionExt,
id: fileActionId,
} = filesStore.fileActionStore;
const { setSharingPanelVisible } = dialogsStore;
const { canWebEdit } = settingsStore;
return {
@ -137,8 +129,6 @@ export default function withQuickButtons(WrappedComponent) {
isTrashFolder: isRecycleBinFolder,
lockFileAction,
setFavoriteAction,
fileActionExt,
fileActionId,
onSelectItem,
setSharingPanelVisible,
canWebEdit,

View File

@ -14,6 +14,7 @@ import MobileView from "./MobileView";
import { combineUrl } from "@appserver/common/utils";
import config from "../../../../package.json";
import withLoader from "../../../HOCs/withLoader";
import { Events } from "../../../helpers/constants";
const ArticleMainButtonContent = (props) => {
const {
@ -47,11 +48,16 @@ const ArticleMainButtonContent = (props) => {
const onCreate = React.useCallback(
(e) => {
const format = e.action || null;
setAction({
type: FileAction.Create,
const event = new Event(Events.CREATE);
const payload = {
extension: format,
id: -1,
});
};
event.payload = payload;
window.dispatchEvent(event);
},
[setAction]
);
@ -286,13 +292,7 @@ export default inject(
treeFoldersStore,
selectedFolderStore,
}) => {
const {
isLoaded,
firstLoad,
isLoading,
fileActionStore,
canCreate,
} = filesStore;
const { isLoaded, firstLoad, isLoading, canCreate } = filesStore;
const {
isPrivacyFolder,
isFavoritesFolder,
@ -321,7 +321,6 @@ export default inject(
isShareFolder,
canCreate,
setAction: fileActionStore.setAction,
startUpload,
setSelectFileDialogVisible,

View File

@ -5,6 +5,7 @@ import EmptyFilterContainer from "./EmptyFilterContainer";
import EmptyFolderContainer from "./EmptyFolderContainer";
import { FileAction } from "@appserver/common/constants";
import { isMobile } from "react-device-detect";
import { Events } from "../../helpers/constants";
const linkStyles = {
isHovered: true,
@ -16,7 +17,6 @@ const linkStyles = {
const EmptyContainer = ({
isFiltered,
setAction,
isPrivacyFolder,
parentId,
isEncryptionSupport,
@ -26,11 +26,16 @@ const EmptyContainer = ({
const onCreate = (e) => {
const format = e.currentTarget.dataset.format || null;
setAction({
type: FileAction.Create,
const event = new Event(Events.CREATE);
const payload = {
extension: format,
id: -1,
});
};
event.payload = payload;
window.dispatchEvent(event);
};
return isFiltered ? (
@ -59,7 +64,6 @@ export default inject(
isEncryptionSupport: auth.settingsStore.isEncryptionSupport,
theme: auth.settingsStore.theme,
isFiltered,
setAction: filesStore.fileActionStore.setAction,
isPrivacyFolder,
parentId: selectedFolderStore.parentId,
};

View File

@ -0,0 +1,312 @@
import React from "react";
import { inject, observer } from "mobx-react";
import { useTranslation } from "react-i18next";
import toastr from "studio/toastr";
import { AppServerConfig } from "@appserver/common/constants";
import { combineUrl } from "@appserver/common/utils";
import config from "../../../package.json";
import { getTitleWithoutExst } from "../../helpers/files-helpers";
import { getDefaultFileName } from "../../helpers/utils";
import Dialog from "./sub-components/Dialog";
const CreateEvent = ({
id,
type,
extension,
title,
templateId,
fromTemplate,
onClose,
setIsLoading,
createFile,
createFolder,
addActiveItems,
openDocEditor,
setIsUpdatingRowItem,
gallerySelected,
setCreatedItem,
parentId,
isPrivacy,
isDesktop,
editCompleteAction,
clearActiveOperations,
fileCopyAs,
setConvertPasswordDialogVisible,
setFormCreationInfo,
replaceFileStream,
setEncryptionAccess,
}) => {
const [visible, setVisible] = React.useState(false);
const [headerTitle, setHeaderTitle] = React.useState(null);
const [startValue, setStartValue] = React.useState("");
const { t } = useTranslation(["Translations", "Common"]);
React.useEffect(() => {
const defaultName = getDefaultFileName(extension);
if (title) {
const item = { fileExst: extension, title: title };
setStartValue(getTitleWithoutExst(item, fromTemplate));
} else {
setStartValue(defaultName);
}
setHeaderTitle(defaultName);
setVisible(true);
}, [extension, title, fromTemplate]);
const onSave = (e, value, open = true) => {
let item;
let createdFileId, createdFolderId;
const isMakeFormFromFile = templateId ? true : false;
setIsLoading(true);
const newValue = value;
if (value.trim() === "") {
newValue =
templateId === null
? getDefaultFileName(extension)
: getTitleWithoutExst({ fileExst: extension });
setStartValue(newValue);
}
let tab =
!isDesktop && extension && open
? window.open(
combineUrl(AppServerConfig.proxyURL, config.homepage, "/doceditor"),
"_blank"
)
: null;
if (!extension) {
createFolder(parentId, newValue)
.then((folder) => {
item = folder;
createdFolderId = folder.id;
addActiveItems(null, [folder.id]);
setCreatedItem({ id: createdFolderId, type: "folder" });
})
.then(() => editCompleteAction(id, item, false, type))
.catch((e) => toastr.error(e))
.finally(() => {
const folderIds = [+id];
createdFolderId && folderIds.push(createdFolderId);
clearActiveOperations(null, folderIds);
onClose();
return setIsLoading(false);
});
} else {
if (isMakeFormFromFile) {
fileCopyAs(templateId, `${newValue}.${extension}`, parentId)
.then((file) => {
item = file;
createdFileId = file.id;
addActiveItems([file.id]);
open && openDocEditor(file.id, file.providerKey, tab);
})
.then(() => editCompleteAction(id, item, false, type))
.catch((err) => {
if (err.indexOf("password") == -1) {
toastr.error(err, t("Common:Warning"));
return;
}
toastr.error(t("Translations:FileProtected"), t("Common:Warning"));
setVisible(false);
setFormCreationInfo({
newTitle: `${newValue}.${extension}`,
fromExst: ".docx",
toExst: extension,
open,
actionId: id,
fileInfo: {
id: templateId,
folderId: parentId,
fileExst: extension,
},
});
setConvertPasswordDialogVisible(true);
open && openDocEditor(null, null, tab);
})
.finally(() => {
const fileIds = [+id];
createdFileId && fileIds.push(createdFileId);
clearActiveOperations(fileIds);
onClose();
return setIsLoading(false);
});
} else if (fromTemplate) {
createFile(
parentId,
`${newValue}.${extension}`,
undefined,
gallerySelected.id
)
.then((file) => {
item = file;
createdFileId = file.id;
setCreatedItem({ id: createdFileId, type: "file" });
addActiveItems([file.id]);
return open && openDocEditor(file.id, file.providerKey, tab);
})
.then(() => editCompleteAction(id, item, false, type))
.catch((e) => toastr.error(e))
.finally(() => {
const fileIds = [+id];
createdFileId && fileIds.push(createdFileId);
clearActiveOperations(fileIds);
onClose();
return setIsLoading(false);
});
} else {
createFile(parentId, `${newValue}.${extension}`)
.then((file) => {
createdFileId = file.id;
item = file;
setCreatedItem({ id: createdFileId, type: "file" });
addActiveItems([file.id]);
if (isPrivacy) {
return setEncryptionAccess(file).then((encryptedFile) => {
if (!encryptedFile) return Promise.resolve();
toastr.info(t("Translations:EncryptedFileSaving"));
return replaceFileStream(
file.id,
encryptedFile,
true,
false
).then(
() => open && openDocEditor(file.id, file.providerKey, tab)
);
});
}
return open && openDocEditor(file.id, file.providerKey, tab);
})
.then(() => editCompleteAction(id, item, false, type))
.catch((e) => toastr.error(e))
.finally(() => {
const fileIds = [+id];
createdFileId && fileIds.push(createdFileId);
clearActiveOperations(fileIds);
onClose();
return setIsLoading(false);
});
}
}
};
const onCancel = React.useCallback(
(e) => {
onClose && onClose();
},
[onClose]
);
return (
<Dialog
t={t}
visible={visible}
title={headerTitle}
startValue={startValue}
onSave={onSave}
onCancel={onCancel}
onClose={onClose}
/>
);
};
export default inject(
({
auth,
filesStore,
filesActionsStore,
selectedFolderStore,
treeFoldersStore,
uploadDataStore,
dialogsStore,
}) => {
const {
setIsLoading,
createFile,
createFolder,
addActiveItems,
openDocEditor,
setIsUpdatingRowItem,
gallerySelected,
setCreatedItem,
} = filesStore;
const { editCompleteAction } = filesActionsStore;
const { clearActiveOperations, fileCopyAs } = uploadDataStore;
const { isRecycleBinFolder, isPrivacyFolder } = treeFoldersStore;
const { id: parentId } = selectedFolderStore;
const { replaceFileStream, setEncryptionAccess } = auth;
const { isDesktopClient } = auth.settingsStore;
const {
setConvertPasswordDialogVisible,
setFormCreationInfo,
} = dialogsStore;
return {
setIsLoading,
createFile,
createFolder,
addActiveItems,
openDocEditor,
setIsUpdatingRowItem,
gallerySelected,
setCreatedItem,
parentId,
isDesktop: isDesktopClient,
isPrivacy: isPrivacyFolder,
isTrashFolder: isRecycleBinFolder,
editCompleteAction,
clearActiveOperations,
fileCopyAs,
setConvertPasswordDialogVisible,
setFormCreationInfo,
replaceFileStream,
setEncryptionAccess,
};
}
)(observer(CreateEvent));

View File

@ -0,0 +1,142 @@
import React from "react";
import { inject, observer } from "mobx-react";
import { useTranslation } from "react-i18next";
import toastr from "studio/toastr";
import { getTitleWithoutExst } from "../../helpers/files-helpers";
import Dialog from "./sub-components/Dialog";
const RenameEvent = ({
type,
item,
onClose,
setIsLoading,
addActiveItems,
updateFile,
renameFolder,
editCompleteAction,
clearActiveOperations,
}) => {
const [visible, setVisible] = React.useState(false);
const [startValue, setStartValue] = React.useState("");
const { t } = useTranslation(["Home"]);
React.useEffect(() => {
setStartValue(getTitleWithoutExst(item, false));
setVisible(true);
}, [item]);
const onUpdate = React.useCallback((e, value) => {
const originalTitle = getTitleWithoutExst(item);
setIsLoading(true);
let timerId;
const isSameTitle =
originalTitle.trim() === value.trim() || value.trim() === "";
const isFile = item.fileExst || item.contentLength;
if (isSameTitle) {
setStartValue(originalTitle);
return editCompleteAction(item.id, item, isSameTitle, type);
} else {
timerId = setTimeout(() => {
isFile ? addActiveItems([item.id]) : addActiveItems(null, [item.id]);
}, 500);
}
isFile
? updateFile(item.id, value)
.then(() => editCompleteAction(item.id, item, false, type))
.then(() =>
toastr.success(
t("FileRenamed", {
oldTitle: item.title,
newTitle: value + item.fileExst,
})
)
)
.catch((err) => {
toastr.error(err);
editCompleteAction(item.id, item, false, type);
})
.finally(() => {
clearTimeout(timerId);
timerId = null;
clearActiveOperations([item.id]);
setIsLoading(false);
onClose();
})
: renameFolder(item.id, value)
.then(() => editCompleteAction(item.id, item, false, type))
.then(() =>
toastr.success(
t("FolderRenamed", {
folderTitle: item.title,
newFoldedTitle: value,
})
)
)
.catch((err) => {
toastr.error(err);
editCompleteAction(item.id, item, false, type);
})
.finally(() => {
clearTimeout(timerId);
timerId = null;
clearActiveOperations(null, [item.id]);
setIsLoading(false);
onClose();
});
}, []);
const onCancel = React.useCallback(
(e) => {
onClose && onClose();
},
[onClose]
);
return (
<Dialog
t={t}
visible={visible}
title={t("Home: Rename")}
startValue={startValue}
onSave={onUpdate}
onCancel={onCancel}
onClose={onClose}
/>
);
};
export default inject(({ filesStore, filesActionsStore, uploadDataStore }) => {
const { setIsLoading, addActiveItems, updateFile, renameFolder } = filesStore;
const { editCompleteAction } = filesActionsStore;
const { clearActiveOperations } = uploadDataStore;
return {
setIsLoading,
addActiveItems,
updateFile,
renameFolder,
editCompleteAction,
clearActiveOperations,
};
})(observer(RenameEvent));

View File

@ -0,0 +1,93 @@
import React from "react";
import { FileAction } from "@appserver/common/constants";
import { Events } from "../../helpers/constants";
import CreateEvent from "./CreateEvent";
import RenameEvent from "./RenameEvent";
const GlobalEvents = () => {
const [createDialogProps, setCreateDialogProps] = React.useState({
visible: false,
id: null,
type: null,
extension: null,
title: "",
templateId: null,
fromTemplate: null,
onClose: null,
});
const [renameDialogProps, setRenameDialogProps] = React.useState({
visible: false,
item: null,
onClose: null,
});
const onCreate = React.useCallback((e) => {
const { payload } = e;
const visible = payload.id ? true : false;
setCreateDialogProps({
visible: visible,
id: payload.id,
type: FileAction.Create,
extension: payload.extension,
title: payload.title || null,
templateId: payload.templateId || null,
fromTemplate: payload.fromTemplate || null,
onClose: () => {
setCreateDialogProps({
visible: false,
id: null,
type: null,
extension: null,
title: "",
templateId: null,
fromTemplate: null,
onClose: null,
});
},
});
}, []);
const onRename = React.useCallback((e) => {
const visible = e.item ? true : false;
setRenameDialogProps({
visible: visible,
type: FileAction.Rename,
item: e.item,
onClose: () => {
setRenameDialogProps({
visible: false,
typ: null,
item: null,
});
},
});
}, []);
React.useEffect(() => {
window.addEventListener(Events.CREATE, onCreate);
window.addEventListener(Events.RENAME, onRename);
return () => {
window.removeEventListener(Events.CREATE, onCreate);
window.removeEventListener(Events.RENAME, onRename);
};
}, [onRename, onCreate]);
return [
createDialogProps.visible && (
<CreateEvent key={Events.CREATE} {...createDialogProps} />
),
renameDialogProps.visible && (
<RenameEvent key={Events.RENAME} {...renameDialogProps} />
),
];
};
export default React.memo(GlobalEvents);

View File

@ -0,0 +1,120 @@
import React from "react";
import { inject, observer } from "mobx-react";
import styled from "styled-components";
import toastr from "@appserver/components/toast/toastr";
import ModalDialog from "@appserver/components/modal-dialog";
import TextInput from "@appserver/components/text-input";
import SaveCancelButtons from "@appserver/components/save-cancel-buttons";
const StyledModalDialog = styled(ModalDialog)`
width: 400px;
@media (max-width: 400px) {
width: 100%;
}
`;
const StyledSaveCancelButtons = styled(SaveCancelButtons)`
position: relative !important;
padding: 8px 0 0;
.buttons-flex {
width: 100%;
display: grid;
align-items: center;
grid-template-columns: 1fr 1fr;
grid-gap: 8px;
}
button {
width: 100%;
}
`;
const Dialog = ({
t,
title,
startValue,
visible,
folderFormValidation,
onSave,
onCancel,
onClose,
}) => {
const [value, setValue] = React.useState("");
const [isDisabled, setIsDisabled] = React.useState(false);
React.useEffect(() => {
if (startValue) setValue(startValue);
}, [startValue]);
const onChange = React.useCallback((e) => {
let newValue = e.target.value;
if (newValue.match(folderFormValidation)) {
toastr.warning(t("ContainsSpecCharacter"));
}
newValue = newValue.replace(folderFormValidation, "_");
setValue(newValue);
}, []);
const onFocus = React.useCallback((e) => {
e.target.select();
}, []);
const onSaveAction = React.useCallback(
(e) => {
setIsDisabled(true);
onSave && onSave(e, value);
},
[onSave, value]
);
const onCancelAction = React.useCallback((e) => {
onCancel && onCancel(e);
}, []);
return (
<StyledModalDialog
visible={visible}
displayType={"modal"}
scale={true}
onClose={onClose}
>
<ModalDialog.Header>{title}</ModalDialog.Header>
<ModalDialog.Body>
<TextInput
name={"create"}
type={"text"}
scale={true}
value={value}
isAutoFocussed={true}
tabIndex={1}
onChange={onChange}
onFocus={onFocus}
isDisabled={isDisabled}
/>
</ModalDialog.Body>
<ModalDialog.Footer>
<StyledSaveCancelButtons
saveButtonLabel={"Save"}
cancelButtonLabel={"Cancel"}
onSaveClick={onSaveAction}
onCancelClick={onCancelAction}
showReminder={!isDisabled}
/>
</ModalDialog.Footer>
</StyledModalDialog>
);
};
export default inject(({ auth }) => {
const { folderFormValidation } = auth.settingsStore;
return { folderFormValidation };
})(observer(Dialog));

View File

@ -23,35 +23,42 @@ const ItemIcon = ({
fileExst,
isPrivacy,
viewAs,
actionType,
actionExtension,
actionId,
// actionType,
// actionExtension,
// actionId,
}) => {
const isEdit =
(actionType !== null && actionId === id && fileExst === actionExtension) ||
id <= 0;
// const isEdit =
// (actionType !== null && actionId === id && fileExst === actionExtension) ||
// id <= 0;
// return (
// <>
// <StyledIcon
// className={`react-svg-icon${isEdit ? " is-edit" : ""}`}
// src={icon}
// />
// {isPrivacy && fileExst && (
// <EncryptedFileIcon isEdit={isEdit && viewAs !== "tile"} />
// )}
// </>
// );
return (
<>
<StyledIcon
className={`react-svg-icon${isEdit ? " is-edit" : ""}`}
src={icon}
/>
{isPrivacy && fileExst && (
<EncryptedFileIcon isEdit={isEdit && viewAs !== "tile"} />
)}
<StyledIcon className={`react-svg-icon`} src={icon} />
{isPrivacy && fileExst && <EncryptedFileIcon isEdit={false} />}
</>
);
};
export default inject(({ filesStore, treeFoldersStore }) => {
const { type, extension, id } = filesStore.fileActionStore;
// const { type, extension, id } = filesStore.fileActionStore;
return {
viewAs: filesStore.viewAs,
isPrivacy: treeFoldersStore.isPrivacyFolder,
actionType: type,
actionExtension: extension,
actionId: id,
// actionType: type,
// actionExtension: extension,
// actionId: id,
};
})(observer(ItemIcon));

View File

@ -10,3 +10,5 @@ export const thumbnailStatuses = {
};
export const ADS_TIMEOUT = 300000; // 5 min
export const Events = Object.freeze({ CREATE: "create", RENAME: "rename" });

View File

@ -20,7 +20,6 @@ const SectionBodyContent = (props) => {
const {
t,
tReady,
fileActionId,
isEmptyFilesList,
folderId,
dragging,
@ -243,7 +242,7 @@ const SectionBodyContent = (props) => {
return (
<Consumer>
{(context) =>
(!fileActionId && isEmptyFilesList) || null ? (
isEmptyFilesList || null ? (
<>
<EmptyContainer />
</>
@ -277,7 +276,6 @@ export default inject(
uploadDataStore,
}) => {
const {
fileActionStore,
isEmptyFilesList,
dragging,
setDragging,
@ -299,7 +297,7 @@ export default inject(
dragging,
startDrag,
setStartDrag,
fileActionId: fileActionStore.id,
isEmptyFilesList,
setDragging,
folderId: selectedFolderStore.id,

View File

@ -13,6 +13,7 @@ import { Consumer } from "@appserver/components/utils/context";
import { inject, observer } from "mobx-react";
import TableGroupMenu from "@appserver/components/table-container/TableGroupMenu";
import Navigation from "@appserver/common/components/Navigation";
import { Events } from "../../../../helpers/constants";
import config from "../../../../../package.json";
import { combineUrl } from "@appserver/common/utils";
@ -49,11 +50,16 @@ class SectionHeaderContent extends React.Component {
}
onCreate = (format) => {
this.props.setAction({
type: FileAction.Create,
const event = new Event(Events.CREATE);
const payload = {
extension: format,
id: -1,
});
};
event.payload = payload;
window.dispatchEvent(event);
};
createDocument = () => this.onCreate("docx");
@ -445,7 +451,7 @@ export default inject(
const {
setSelected,
setSelection,
fileActionStore,
canCreate,
isHeaderVisible,
isHeaderIndeterminate,
@ -462,7 +468,7 @@ export default inject(
activeFiles,
activeFolders,
} = filesStore;
const { setAction } = fileActionStore;
const {
setSharingPanelVisible,
setMoveToPanelVisible,
@ -509,7 +515,7 @@ export default inject(
setSelected,
setSelection,
setAction,
setSharingPanelVisible,
setMoveToPanelVisible,
setCopyPanelVisible,

View File

@ -32,7 +32,7 @@ import DragTooltip from "../../components/DragTooltip";
import { observer, inject } from "mobx-react";
import config from "../../../package.json";
import { Consumer } from "@appserver/components/utils/context";
import { FileAction } from "@appserver/common/constants";
import { Events } from "../../helpers/constants";
class PureHome extends React.Component {
componentDidMount() {
@ -48,7 +48,6 @@ class PureHome extends React.Component {
isMediaOrImage,
getFileInfo,
gallerySelected,
setAction,
setIsUpdatingRowItem,
} = this.props;
@ -169,13 +168,19 @@ class PureHome extends React.Component {
.then(() => {
if (gallerySelected) {
setIsUpdatingRowItem(false);
setAction({
type: FileAction.Create,
const event = new Event(Events.CREATE);
const payload = {
extension: "docxf",
id: -1,
fromTemplate: true,
title: gallerySelected.attributes.name_form,
id: -1,
});
};
event.payload = payload;
window.dispatchEvent(event);
}
})
.finally(() => {
@ -290,7 +295,7 @@ class PureHome extends React.Component {
//console.log("Home render");
const {
viewAs,
fileActionId,
firstLoad,
isHeaderVisible,
isPrivacyFolder,
@ -337,9 +342,7 @@ class PureHome extends React.Component {
clearUploadedFilesHistory={clearUploadedFilesHistory}
viewAs={viewAs}
hideAside={
!!fileActionId ||
primaryProgressDataVisible ||
secondaryProgressDataStoreVisible //TODO: use hideArticle action
primaryProgressDataVisible || secondaryProgressDataStoreVisible //TODO: use hideArticle action
}
isLoaded={!firstLoad}
isHeaderVisible={isHeaderVisible}
@ -412,7 +415,6 @@ export default inject(
firstLoad,
setFirstLoad,
fetchFiles,
fileActionStore,
selection,
setSelections,
dragging,
@ -425,7 +427,6 @@ export default inject(
setIsUpdatingRowItem,
} = filesStore;
const { id, setAction } = fileActionStore;
const {
isRecycleBinFolder,
isPrivacyFolder,
@ -478,7 +479,6 @@ export default inject(
homepage: config.homepage,
firstLoad,
dragging,
fileActionId: id,
viewAs,
uploaded,
converted,
@ -527,7 +527,6 @@ export default inject(
isMediaOrImage: settingsStore.isMediaOrImage,
getFileInfo,
gallerySelected,
setAction,
setIsUpdatingRowItem,
};
}

View File

@ -10,6 +10,7 @@ import {
isMobile as isMobileUtils,
isTablet as isTabletUtils,
} from "@appserver/components/utils/device";
import { Events } from "../helpers/constants";
class ContextOptionsStore {
authStore;
@ -246,12 +247,11 @@ class ContextOptionsStore {
};
onClickRename = (item) => {
const { id, fileExst } = item;
this.filesStore.fileActionStore.setAction({
type: FileAction.Rename,
extension: fileExst,
id,
});
const event = new Event(Events.RENAME);
event.item = item;
window.dispatchEvent(event);
};
onChangeThirdPartyInfo = (providerKey) => {
@ -793,13 +793,11 @@ class ContextOptionsStore {
getModel = (item, t) => {
const { selection } = this.filesStore;
const { type, id, extension } = this.filesStore.fileActionStore;
const { fileExst, contextOptions } = item;
const isEdit = !!type && id === item.id && fileExst === extension;
const contextOptionsProps =
!isEdit && contextOptions && contextOptions.length > 0
contextOptions && contextOptions.length > 0
? selection.length > 1
? this.getGroupContextOptions(t)
: this.getFilesContextOptions(item, t)

View File

@ -1,6 +1,7 @@
import { getNewFiles } from "@appserver/common/api/files";
import { FileAction } from "@appserver/common/constants";
import { makeAutoObservable } from "mobx";
import { Events } from "../helpers/constants";
class DialogsStore {
authStore;
@ -212,18 +213,21 @@ class DialogsStore {
};
createMasterForm = async (fileInfo) => {
const { setAction } = this.filesStore.fileActionStore;
let newTitle = fileInfo.title;
newTitle = newTitle.substring(0, newTitle.lastIndexOf("."));
setAction({
type: FileAction.Create,
const event = new Event(Events.CREATE);
const payload = {
extension: "docxf",
id: -1,
title: `${newTitle}.docxf`,
templateId: fileInfo.id,
});
};
event.payload = payload;
window.dispatchEvent(event);
};
get someDialogIsOpen() {

View File

@ -1,27 +0,0 @@
import { makeAutoObservable } from "mobx";
class FileActionStore {
id = null;
type = null;
extension = null;
title = "";
templateId = null;
fromTemplate = null;
constructor() {
makeAutoObservable(this);
}
setAction = (fileAction) => {
if (fileAction.fromTemplate === undefined && this.fromTemplate) return;
const fileActionItems = Object.keys(fileAction);
for (let key of fileActionItems) {
if (key in this) {
this[key] = fileAction[key];
}
}
};
}
export default new FileActionStore();

View File

@ -20,7 +20,7 @@ import {
import { makeAutoObservable } from "mobx";
import toastr from "studio/toastr";
import { TIMEOUT } from "../helpers/constants";
import { Events, TIMEOUT } from "../helpers/constants";
import { loopTreeFolders, checkProtocol } from "../helpers/files-helpers";
import { combineUrl } from "@appserver/common/utils";
import { AppServerConfig } from "@appserver/common/constants";
@ -453,16 +453,16 @@ class FilesActionStore {
return this.downloadFiles(fileIds, folderIds, label);
};
editCompleteAction = async (id, selectedItem, isCancelled = false) => {
editCompleteAction = async (id, selectedItem, isCancelled = false, type) => {
const {
filter,
folders,
files,
fileActionStore,
fetchFiles,
setIsLoading,
} = this.filesStore;
const { type, setAction } = fileActionStore;
const { treeFolders, setTreeFolders } = this.treeFoldersStore;
const items = [...folders, ...files];
@ -480,14 +480,7 @@ class FilesActionStore {
setTreeFolders(treeFolders);
}
}
setAction({
type: null,
id: null,
extension: null,
title: "",
templateId: null,
fromTemplate: null,
});
setIsLoading(false);
type === FileAction.Rename &&
this.onSelectItem(

View File

@ -26,7 +26,7 @@ class FilesStore {
authStore;
settingsStore;
userStore;
fileActionStore;
selectedFolderStore;
treeFoldersStore;
filesSettingsStore;
@ -78,7 +78,7 @@ class FilesStore {
authStore,
settingsStore,
userStore,
fileActionStore,
selectedFolderStore,
treeFoldersStore,
filesSettingsStore
@ -90,7 +90,7 @@ class FilesStore {
this.authStore = authStore;
this.settingsStore = settingsStore;
this.userStore = userStore;
this.fileActionStore = fileActionStore;
this.selectedFolderStore = selectedFolderStore;
this.treeFoldersStore = treeFoldersStore;
this.filesSettingsStore = filesSettingsStore;
@ -654,7 +654,6 @@ class FilesStore {
});
if (clearFilter) {
this.fileActionStore.setAction({ type: null });
if (clearSelection) {
this.setSelected("close");
}
@ -1669,10 +1668,6 @@ class FilesStore {
};
});
if (this.fileActionStore.type === FileAction.Create) {
this.onCreateAddTempItem(newItem);
}
return newItem;
}

View File

@ -1,5 +1,4 @@
import FilesStore from "./FilesStore";
import fileActionStore from "./FileActionStore";
import SelectedFolderStore from "./SelectedFolderStore";
import TreeFoldersStore from "./TreeFoldersStore";
import thirdPartyStore from "./ThirdPartyStore";
@ -26,7 +25,6 @@ const filesStore = new FilesStore(
store.auth,
store.auth.settingsStore,
store.auth.userStore,
fileActionStore,
selectedFolderStore,
treeFoldersStore,
settingsStore,