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

This commit is contained in:
Alexey Kostenko 2021-03-05 14:47:33 +03:00
commit 9a791c61ad
28 changed files with 798 additions and 1757 deletions

View File

@ -4,10 +4,7 @@ import Backdrop from "@appserver/components/backdrop";
//import ProgressBar from "@appserver/components/progress-bar";
import { size } from "@appserver/components/utils/device";
import { Provider } from "@appserver/components/utils/context";
import { withTranslation } from "react-i18next";
import { isMobile } from "react-device-detect";
import i18n from "./i18n";
import Article from "./sub-components/article";
import SubArticleHeader from "./sub-components/article-header";
import SubArticleMainButton from "./sub-components/article-main-button";
@ -20,7 +17,6 @@ import SubSectionBody from "./sub-components/section-body";
import SubSectionBodyContent from "./sub-components/section-body-content";
import SubSectionPaging from "./sub-components/section-paging";
import SectionToggler from "./sub-components/section-toggler";
import { changeLanguage } from "../../utils";
import ReactResizeDetector from "react-resize-detector";
import FloatingButton from "../FloatingButton";
import { inject, observer } from "mobx-react";
@ -60,7 +56,7 @@ function SectionPaging() {
}
SectionPaging.displayName = "SectionPaging";
class PageLayoutComponent extends React.Component {
class PageLayout extends React.Component {
static ArticleHeader = ArticleHeader;
static ArticleMainButton = ArticleMainButton;
static ArticleBody = ArticleBody;
@ -282,9 +278,7 @@ class PageLayoutComponent extends React.Component {
{isArticleBodyAvailable && (
<ArticlePinPanel
pinned={this.state.isArticlePinned}
pinText={this.props.t("Pin")}
onPin={this.pinArticle}
unpinText={this.props.t("Unpin")}
onUnpin={this.unpinArticle}
/>
)}
@ -410,11 +404,10 @@ class PageLayoutComponent extends React.Component {
}
}
PageLayoutComponent.propTypes = {
PageLayout.propTypes = {
children: PropTypes.any,
withBodyScroll: PropTypes.bool,
withBodyAutoFocus: PropTypes.bool,
t: PropTypes.func,
showPrimaryProgressBar: PropTypes.bool,
primaryProgressBarValue: PropTypes.number,
showPrimaryButtonAlert: PropTypes.bool,
@ -437,21 +430,11 @@ PageLayoutComponent.propTypes = {
firstLoad: PropTypes.bool,
};
PageLayoutComponent.defaultProps = {
PageLayout.defaultProps = {
withBodyScroll: true,
withBodyAutoFocus: false,
};
const PageLayoutTranslated = withTranslation()(PageLayoutComponent);
const PageLayout = ({ language, ...rest }) => {
useEffect(() => {
changeLanguage(i18n, language);
}, [language]);
return <PageLayoutTranslated i18n={i18n} {...rest} />;
};
PageLayout.ArticleHeader = ArticleHeader;
PageLayout.ArticleMainButton = ArticleMainButton;
PageLayout.ArticleBody = ArticleBody;
@ -460,13 +443,8 @@ PageLayout.SectionFilter = SectionFilter;
PageLayout.SectionBody = SectionBody;
PageLayout.SectionPaging = SectionPaging;
PageLayout.propTypes = {
language: PropTypes.string,
children: PropTypes.any,
};
export default inject(({ auth }) => {
const { isLoaded, language, settingsStore } = auth;
const { isLoaded, settingsStore } = auth;
const {
isHeaderVisible,
isTabletView,
@ -475,7 +453,6 @@ export default inject(({ auth }) => {
} = settingsStore;
return {
isLoaded,
language,
isTabletView,
isHeaderVisible,
isArticlePinned,

View File

@ -7,6 +7,7 @@ import { tablet, smallTablet } from "@appserver/components/utils/device";
import CatalogPinIcon from "../../../../../public/images/catalog.pin.react.svg";
import CatalogUnpinIcon from "../../../../../public/images/catalog.unpin.react.svg";
import commonIconsStyles from "@appserver/components/utils/common-icons-style";
import i18n from "../i18n";
const StyledCatalogPinIcon = styled(CatalogPinIcon)`
${commonIconsStyles}
@ -60,7 +61,8 @@ const StyledArticlePinPanel = styled.div`
const ArticlePinPanel = React.memo((props) => {
//console.log("PageLayout ArticlePinPanel render");
const { pinned, pinText, onPin, unpinText, onUnpin } = props;
const { pinned, onPin, onUnpin } = props;
const textStyles = {
as: "span",
color: "#555F65",
@ -75,14 +77,14 @@ const ArticlePinPanel = React.memo((props) => {
<div className="icon-wrapper">
<StyledCatalogUnpinIcon size="scale" />
</div>
<Text {...textStyles}>{unpinText}</Text>
<Text {...textStyles}>{i18n.t("Unpin")}</Text>
</div>
) : (
<div onClick={onPin}>
<div className="icon-wrapper">
<StyledCatalogPinIcon size="scale" />
</div>
<Text {...textStyles}>{pinText}</Text>
<Text {...textStyles}>{i18n.t("Pin")}</Text>
</div>
)}
</StyledArticlePinPanel>

View File

@ -158,7 +158,7 @@ class AuthStore {
logout = async (withoutRedirect) => {
const response = await api.user.logout();
console.log("Logout response ", response);
//console.log("Logout response ", response);
setWithCredentialsStatus(false);

View File

@ -10,7 +10,7 @@ const Icon = ({ size, primary, icon, isHovered }) => (
<div className="btnIcon">
{icon &&
React.cloneElement(icon, {
isfill: true,
//isfill: true,
size: size === "large" ? "large" : size === "big" ? "medium" : "small",
color: icon.props.color
? isHovered

View File

@ -22,7 +22,8 @@ import treeFoldersStore from "./store/TreeFoldersStore";
import selectedFolderStore from "./store/SelectedFolderStore";
import filesActionsStore from "./store/FilesActionsStore";
import "./custom.scss";
import "./i18n";
import i18n from "./i18n";
import { I18nextProvider } from "react-i18next";
//import { regDesktop } from "@appserver/common/src/desktop";
const Error404 = React.lazy(() => import("studio/Error404"));
@ -135,6 +136,8 @@ export default () => (
selectedFolderStore={selectedFolderStore}
filesActionsStore={filesActionsStore}
>
<Files />
<I18nextProvider i18n={i18n}>
<Files />
</I18nextProvider>
</FilesProvider>
);

View File

@ -2,8 +2,7 @@ import React from "react";
import styled, { css } from "styled-components";
import Link from "@appserver/components/link";
import history from "@appserver/common/history";
import { changeLanguage } from "@appserver/common/utils";
import { withTranslation, I18nextProvider } from "react-i18next";
import { withTranslation } from "react-i18next";
import { isMobile } from "react-device-detect";
import { inject, observer } from "mobx-react";

View File

@ -115,8 +115,8 @@ class FilesRowContent extends React.PureComponent {
super(props);
let titleWithoutExt = getTitleWithoutExst(props.item);
if (props.fileAction.id === -1) {
titleWithoutExt = this.getDefaultName(props.fileAction.extension);
if (props.fileActionId === -1) {
titleWithoutExt = this.getDefaultName(props.fileActionExt);
}
this.state = {
@ -129,70 +129,18 @@ class FilesRowContent extends React.PureComponent {
};
}
onSelectItem = (item) => {
const { selected, setSelected, setSelection } = this.props;
selected === "close" && setSelected("none");
setSelection([item]);
};
//TODO: move to actions, used in files row content and tile
onEditComplete = (id, isFolder) => {
const {
selectedFolderId,
fileAction,
filter,
folders,
files,
treeFolders,
setTreeFolders,
setIsLoading,
fetchFiles,
setAction,
} = this.props;
const selectedItem = this.props.item;
const items = [...folders, ...files];
const item = items.find((o) => o.id === id && !o.fileExst); //TODO maybe need files find and folders find, not at one function?
if (
fileAction.type === FileAction.Create ||
fileAction.type === FileAction.Rename
) {
setIsLoading(true);
fetchFiles(selectedFolderId, filter)
.then((data) => {
const newItem = (item && item.id) === -1 ? null : item; //TODO not add new folders?
if (isFolder) {
const path = data.selectedFolder.pathParts;
const newTreeFolders = treeFolders;
const folders = data.selectedFolder.folders;
loopTreeFolders(path, newTreeFolders, folders, null, newItem);
setTreeFolders(newTreeFolders);
}
})
.finally(() => {
setAction({ type: null, id: null, extension: null });
setIsLoading(false);
fileAction.type === FileAction.Rename &&
this.onSelectItem(selectedItem);
});
}
//this.setState({ editingId: null }, () => {
// setAction({type: null});
//});
};
completeAction = (id) => {
this.onEditComplete(id, !this.props.item.fileExst);
const { item } = this.props;
this.props.editCompleteAction(id, !item.fileExst, /* item */);
};
updateItem = (e) => {
updateItem = () => {
const {
fileAction,
updateFile,
renameFolder,
item,
setIsLoading,
fileActionId,
} = this.props;
const { itemTitle } = this.state;
@ -203,15 +151,15 @@ class FilesRowContent extends React.PureComponent {
this.setState({
itemTitle: originalTitle,
});
return this.completeAction(fileAction.id);
return this.completeAction(fileActionId);
}
item.fileExst
? updateFile(fileAction.id, itemTitle)
.then(() => this.completeAction(fileAction.id))
? updateFile(fileActionId, itemTitle)
.then(() => this.completeAction(fileActionId))
.finally(() => setIsLoading(false))
: renameFolder(fileAction.id, itemTitle)
.then(() => this.completeAction(fileAction.id))
: renameFolder(fileActionId, itemTitle)
.then(() => this.completeAction(fileActionId))
.finally(() => setIsLoading(false));
};
@ -292,7 +240,7 @@ class FilesRowContent extends React.PureComponent {
};
// componentDidUpdate(prevProps) {
// const { fileAction, item, newRowItems, setNewRowItems } = this.props;
// const { item, newRowItems, setNewRowItems } = this.props;
// const itemId = item.id.toString();
// if (newRowItems.length && newRowItems.includes(itemId)) {
@ -303,8 +251,8 @@ class FilesRowContent extends React.PureComponent {
// }
// if (fileAction) {
// if (fileAction.id !== prevProps.fileAction.id) {
// this.setState({ editingId: fileAction.id });
// if (fileActionId !== prevProps.fileActionId) {
// this.setState({ editingId: fileActionId });
// }
// }
// }
@ -330,7 +278,7 @@ class FilesRowContent extends React.PureComponent {
};
onClickUpdateItem = (e) => {
this.props.fileAction.type === FileAction.Create
this.props.fileActionType === FileAction.Create
? this.createItem(e)
: this.updateItem(e);
};
@ -546,14 +494,14 @@ class FilesRowContent extends React.PureComponent {
const {
t,
item,
fileAction,
isTrashFolder,
folders,
isLoading,
isMobile,
canWebEdit,
/* canConvert,*/
sectionWidth,
fileActionId,
fileActionExt,
} = this.props;
const {
itemTitle,
@ -585,7 +533,7 @@ class FilesRowContent extends React.PureComponent {
const accessToEdit =
item.access === ShareAccessRights.FullAccess ||
item.access === ShareAccessRights.None; // TODO: fix access type for owner (now - None)
const isEdit = id === fileAction.id && fileExst === fileAction.extension;
const isEdit = id === fileActionId && fileExst === fileActionExt;
const linkStyles =
isTrashFolder || window.innerWidth <= 1024
@ -618,7 +566,6 @@ class FilesRowContent extends React.PureComponent {
visible={showNewFilesPanel}
onClose={this.onShowNewFilesPanel}
folderId={newFolderId}
folders={folders}
/>
)}
<SimpleFilesRowContent
@ -818,14 +765,11 @@ export default inject(
uploadDataStore,
treeFoldersStore,
selectedFolderStore,
filesActionsStore,
},
{ item }
) => {
const {
replaceFileStream,
getEncryptionAccess,
setEncryptionAccess,
} = auth;
const { replaceFileStream, setEncryptionAccess } = auth;
const { homepage, culture, isDesktopClient } = auth.settingsStore;
const { setIsLoading, isLoading } = initFilesStore;
const { secondaryProgressDataStore } = uploadDataStore;
@ -836,8 +780,6 @@ export default inject(
} = formatsStore;
const {
files,
folders,
fetchFiles,
filter,
setNewRowItems,
@ -847,9 +789,6 @@ export default inject(
renameFolder,
createFolder,
openDocEditor,
selected,
setSelected,
setSelection
} = filesStore;
const {
@ -861,9 +800,12 @@ export default inject(
addExpandedKeys,
} = treeFoldersStore;
const { type, extension, id, setAction } = filesStore.fileActionStore;
const {
type: fileActionType,
extension: fileActionExt,
id: fileActionId,
} = filesStore.fileActionStore;
const fileAction = { type, extension, id };
const {
setSecondaryProgressBarData,
clearSecondaryProgressData,
@ -880,9 +822,9 @@ export default inject(
homepage,
viewer: auth.userStore.user,
culture,
fileAction,
files,
folders,
fileActionId,
fileActionType,
fileActionExt,
selectedFolderId: selectedFolderStore.id,
selectedFolderPathParts: selectedFolderStore.pathParts,
newItems: selectedFolderStore.new,
@ -899,7 +841,6 @@ export default inject(
isSound,
newRowItems,
expandedKeys,
selected,
setIsLoading,
fetchFiles,
@ -912,13 +853,10 @@ export default inject(
updateFile,
renameFolder,
replaceFileStream,
getEncryptionAccess,
setEncryptionAccess,
addExpandedKeys,
openDocEditor,
setAction,
setSelected,
setSelection
editCompleteAction: filesActionsStore.editCompleteAction,
};
}
)(withRouter(withTranslation("Home")(observer(FilesRowContent))));

View File

@ -10,6 +10,7 @@ import Row from "@appserver/components/row";
import FilesRowContent from "./FilesRowContent";
import history from "@appserver/common/history";
import toastr from "@appserver/components/toast";
import { FileAction } from "@appserver/common/constants";
import { lockFile, finalizeVersion } from "@appserver/common/api/files"; //TODO: move to actions
@ -88,6 +89,7 @@ const SimpleFilesRow = (props) => {
isTabletView,
filter,
selectedFolderId,
actionId,
fetchFiles,
setSharingPanelVisible,
@ -108,7 +110,9 @@ const SimpleFilesRow = (props) => {
removeItemFromFavorite,
getFileInfo,
fetchFavoritesFolder,
actionId,
copyToAction,
deleteFileAction,
deleteFolderAction,
} = props;
const {
@ -289,14 +293,13 @@ const SimpleFilesRow = (props) => {
alert: false,
});
//TODO: need add to action
// this.copyTo(
// selectedFolderId,
// folderIds,
// fileIds,
// conflictResolveType,
// deleteAfter
// );
copyToAction(
selectedFolderId,
folderIds,
fileIds,
conflictResolveType,
deleteAfter
);
};
const onClickRename = () => {
@ -320,18 +323,22 @@ const SimpleFilesRow = (props) => {
};
const onClickDelete = () => {
const splitItem = id.split("-");
if (isThirdPartyFolder) {
const splitItem = id.split("-");
setRemoveItem({ id: splitItem[splitItem.length - 1], title });
showDeleteThirdPartyDialog(true);
return;
}
const item = this.props.selection[0];
const translations = {
deleteOperation: t("DeleteOperation"),
folderRemoved: t("FolderRemoved"),
fileRemoved: t("FileRemoved"),
};
item.fileExst
? this.onDeleteFile(item.id, item.folderId)
: this.onDeleteFolder(item.id, item.parentId);
? deleteFileAction(item.id, item.folderId, translations)
: deleteFolderAction(item.id, item.parentId, translations);
};
const getFilesContextOptions = (options, item) => {
@ -594,6 +601,7 @@ export default inject(
settingsStore,
versionHistoryStore,
uploadDataStore,
filesActionsStore,
},
{ item }
) => {
@ -695,6 +703,19 @@ export default inject(
removeItemFromFavorite,
getFileInfo,
fetchFavoritesFolder,
copyToAction: filesActionsStore.copyToAction,
deleteFileAction: filesActionsStore.deleteFileAction,
deleteFolderAction: filesActionsStore.deleteFolderAction,
};
}
)(withTranslation()(observer(SimpleFilesRow)));
// onDrop = (item, items, e) => {
// const { onDropZoneUpload, selectedFolderId } = this.props;
// if (!item.fileExst) {
// onDropZoneUpload(items, item.id);
// } else {
// onDropZoneUpload(items, selectedFolderId);
// }
// };

View File

@ -1,59 +1,39 @@
import i18n from "i18next";
import { initReactI18next } from "react-i18next";
import Backend from "i18next-http-backend";
import config from "../package.json";
import { LANGUAGE } from "@appserver/common/constants";
//const { LANGUAGE /*i18nBaseSettings*/ } = constants;
//import LanguageDetector from "i18next-browser-languagedetector";
// not like to use this?
// have a look at the Quick start guide
// for passing in lng and translations on init
const languages = ["en", "ru"];
i18n
/*
load translation using http -> see /public/locales (i.e. https://github.com/i18next/react-i18next/tree/master/example/react/public/locales)
learn more: https://github.com/i18next/i18next-http-backend
*/
.use(Backend)
/*
detect user language
learn more: https://github.com/i18next/i18next-browser-languageDetector
*/
//.use(LanguageDetector)
/*
pass the i18n instance to react-i18next.
*/
.use(initReactI18next)
/*
init i18next
for all options read: https://www.i18next.com/overview/configuration-options
*/
.init({
lng: localStorage.getItem(LANGUAGE) || "en",
supportedLngs: languages,
whitelist: languages,
fallbackLng: "en",
load: "languageOnly",
//debug: true,
const newInstance = i18n.createInstance();
interpolation: {
escapeValue: false, // not needed for react as it escapes by default
format: function (value, format) {
if (format === "lowercase") return value.toLowerCase();
return value;
},
const lng = localStorage.getItem(LANGUAGE) || "en";
newInstance.use(Backend).init({
lng: lng,
supportedLngs: languages,
whitelist: languages,
fallbackLng: false,
load: "languageOnly",
debug: true,
interpolation: {
escapeValue: false, // not needed for react as it escapes by default
format: function (value, format) {
if (format === "lowercase") return value.toLowerCase();
return value;
},
},
backend: {
loadPath: `${config.homepage}/locales/{{lng}}/{{ns}}.json`,
},
backend: {
loadPath: `${config.homepage}/locales/{{lng}}/{{ns}}.json`,
allowMultiLoading: false,
crossDomain: false,
},
react: {
useSuspense: false,
},
});
react: {
useSuspense: true,
},
});
export default i18n;
export default newInstance;

View File

@ -1,16 +1,32 @@
import { makeAutoObservable } from "mobx";
import store from "studio/store";
import dialogsStore from "./DialogsStore";
import uploadDataStore from "./UploadDataStore";
import treeFoldersStore from "./TreeFoldersStore";
import filesStore from "./FilesStore";
import selectedFolderStore from "./SelectedFolderStore";
import { removeFiles, getProgress } from "@appserver/common/api/files";
import initFilesStore from "./InitFilesStore";
import {
removeFiles,
getProgress,
copyToFolder,
deleteFile,
deleteFolder,
moveToFolder,
} from "@appserver/common/api/files";
import { FileAction } from "@appserver/common/constants";
import { TIMEOUT } from "../helpers/constants";
import { loopTreeFolders } from "../helpers/files-helpers";
const { confirmDelete } = store.auth.settingsStore;
const { secondaryProgressDataStore } = uploadDataStore;
//import toastr from "@appserver/components/toast";
const { fetchFiles } = filesStore;
const { setTreeFolders } = treeFoldersStore;
const { setIsLoading } = initFilesStore;
const { secondaryProgressDataStore, loopFilesOperations } = uploadDataStore;
const {
setSecondaryProgressBarData,
clearSecondaryProgressData,
} = secondaryProgressDataStore;
class FilesActionStore {
constructor() {
@ -18,73 +34,6 @@ class FilesActionStore {
}
deleteAction = (translations) => {
if (confirmDelete) {
dialogsStore.setDeleteDialogVisible(false);
} else {
return this.onDelete(translations);
}
};
loopDeleteOperation = (id, translations) => {
const { filter, fetchFiles } = filesStore;
const isRecycleBin = treeFoldersStore.isRecycleBinFolder;
const {
setSecondaryProgressBarData,
clearSecondaryProgressData,
} = secondaryProgressDataStore;
const successMessage = isRecycleBin
? translations.deleteFromTrash
: translations.deleteSelectedElem;
getProgress()
.then((res) => {
const currentProcess = res.find((x) => x.id === id);
if (currentProcess && currentProcess.progress !== 100) {
setSecondaryProgressBarData({
icon: "trash",
percent: currentProcess.progress,
label: translations.deleteOperation,
visible: true,
alert: false,
});
setTimeout(() => this.loopDeleteOperation(id, translations), 1000);
} else {
setSecondaryProgressBarData({
icon: "trash",
percent: 100,
label: translations.deleteOperation,
visible: true,
alert: false,
});
setTimeout(() => clearSecondaryProgressData(), TIMEOUT);
fetchFiles(selectedFolderStore.id, filter).then((data) => {
if (!isRecycleBin) {
const path = data.selectedFolder.pathParts.slice(0);
const newTreeFolders = treeFoldersStore.treeFolders;
const folders = data.selectedFolder.folders;
const foldersCount = data.selectedFolder.foldersCount;
loopTreeFolders(path, newTreeFolders, folders, foldersCount);
treeFoldersStore.setTreeFolders(newTreeFolders);
}
//toastr.success(successMessage);
});
}
})
.catch((err) => {
setSecondaryProgressBarData({
visible: true,
alert: true,
});
//toastr.error(err);
setTimeout(() => clearSecondaryProgressData(), TIMEOUT);
});
};
onDelete = (translations) => {
const {
setSecondaryProgressBarData,
clearSecondaryProgressData,
} = secondaryProgressDataStore;
const { isRecycleBinFolder, isPrivacyFolder } = treeFoldersStore;
const { selection } = filesStore;
@ -129,14 +78,60 @@ class FilesActionStore {
}
};
loopDeleteOperation = (id, translations) => {
const { filter } = filesStore;
const { isRecycleBinFolder } = treeFoldersStore;
const successMessage = isRecycleBinFolder
? translations.deleteFromTrash
: translations.deleteSelectedElem;
getProgress()
.then((res) => {
const currentProcess = res.find((x) => x.id === id);
if (currentProcess && currentProcess.progress !== 100) {
setSecondaryProgressBarData({
icon: "trash",
percent: currentProcess.progress,
label: translations.deleteOperation,
visible: true,
alert: false,
});
setTimeout(() => this.loopDeleteOperation(id, translations), 1000);
} else {
setSecondaryProgressBarData({
icon: "trash",
percent: 100,
label: translations.deleteOperation,
visible: true,
alert: false,
});
setTimeout(() => clearSecondaryProgressData(), TIMEOUT);
fetchFiles(selectedFolderStore.id, filter).then((data) => {
if (!isRecycleBinFolder) {
const path = data.selectedFolder.pathParts.slice(0);
const newTreeFolders = treeFoldersStore.treeFolders;
const folders = data.selectedFolder.folders;
const foldersCount = data.selectedFolder.foldersCount;
loopTreeFolders(path, newTreeFolders, folders, foldersCount);
setTreeFolders(newTreeFolders);
}
//toastr.success(successMessage);
});
}
})
.catch((err) => {
setSecondaryProgressBarData({
visible: true,
alert: true,
});
//toastr.error(err);
setTimeout(() => clearSecondaryProgressData(), TIMEOUT);
});
};
getDownloadProgress = (data, label) => {
const url = data.url;
const {
setSecondaryProgressBarData,
clearSecondaryProgressData,
} = secondaryProgressDataStore;
getProgress()
.then((res) => {
const currentItem = res.find((x) => x.id === data.id);
@ -163,6 +158,203 @@ class FilesActionStore {
setTimeout(() => clearSecondaryProgressData(), TIMEOUT);
});
};
editCompleteAction = (id, isFolder /* selectedItem */) => {
const { filter, folders, files, fileActionStore } = filesStore;
const { type, setAction } = fileActionStore;
const { treeFolders } = treeFoldersStore;
const items = [...folders, ...files];
const item = items.find((o) => o.id === id && !o.fileExst); //TODO: maybe need files find and folders find, not at one function?
if (type === FileAction.Create || type === FileAction.Rename) {
setIsLoading(true);
fetchFiles(selectedFolderStore.id, filter)
.then((data) => {
const newItem = (item && item.id) === -1 ? null : item; //TODO: not add new folders?
if (isFolder) {
const path = data.selectedFolder.pathParts;
const newTreeFolders = treeFolders;
const folders = data.selectedFolder.folders;
loopTreeFolders(path, newTreeFolders, folders, null, newItem);
setTreeFolders(newTreeFolders);
}
})
.finally(() => {
setAction({ type: null, id: null, extension: null });
setIsLoading(false);
//uncomment if need to select item
//type === FileAction.Rename && this.onSelectItem(selectedItem);
// onSelectItem = (item) => {
// const { selected, setSelected, setSelection } = this.props;
// selected === "close" && setSelected("none");
// setSelection([item]);
// };
});
}
//this.setState({ editingId: null }, () => {
// setAction({type: null});
//});
};
copyToAction = (
destFolderId,
folderIds,
fileIds,
conflictResolveType,
deleteAfter
) => {
copyToFolder(
destFolderId,
folderIds,
fileIds,
conflictResolveType,
deleteAfter
)
.then((res) => {
const id = res[0] && res[0].id ? res[0].id : null;
loopFilesOperations(id, destFolderId, true);
})
.catch((err) => {
setSecondaryProgressBarData({
visible: true,
alert: true,
});
//toastr.error(err);
setTimeout(() => clearSecondaryProgressData(), TIMEOUT);
});
};
moveToAction = (
destFolderId,
folderIds,
fileIds,
conflictResolveType,
deleteAfter
) => {
moveToFolder(
destFolderId,
folderIds,
fileIds,
conflictResolveType,
deleteAfter
)
.then((res) => {
const id = res[0] && res[0].id ? res[0].id : null;
loopFilesOperations(id, destFolderId, false);
})
.catch((err) => {
setSecondaryProgressBarData({
visible: true,
alert: true,
});
//toastr.error(err);
setTimeout(() => clearSecondaryProgressData(), TIMEOUT);
});
};
deleteFileAction = (fileId, currentFolderId, translations) => {
setSecondaryProgressBarData({
icon: "trash",
visible: true,
percent: 0,
label: translations.deleteOperation,
alert: false,
});
deleteFile(fileId)
.then((res) => {
const id = res[0] && res[0].id ? res[0].id : null;
this.loopDeleteProgress(id, currentFolderId, false, translations);
})
.catch((err) => {
setSecondaryProgressBarData({
visible: true,
alert: true,
});
//toastr.error(err);
setTimeout(() => clearSecondaryProgressData(), TIMEOUT);
});
};
deleteFolderAction = (folderId, currentFolderId, translations) => {
setSecondaryProgressBarData({
icon: "trash",
visible: true,
percent: 0,
label: translations.deleteOperation,
alert: false,
});
deleteFolder(folderId, currentFolderId)
.then((res) => {
const id = res[0] && res[0].id ? res[0].id : null;
this.loopDeleteProgress(id, currentFolderId, true, translations);
})
.catch((err) => {
setSecondaryProgressBarData({
visible: true,
alert: true,
});
//toastr.error(err);
setTimeout(() => clearSecondaryProgressData(), TIMEOUT);
});
};
loopDeleteProgress = (id, folderId, isFolder, translations) => {
const { filter } = filesStore;
const { treeFolders, isRecycleBinFolder } = treeFoldersStore;
getProgress().then((res) => {
const deleteProgress = res.find((x) => x.id === id);
if (deleteProgress && deleteProgress.progress !== 100) {
setSecondaryProgressBarData({
icon: "trash",
visible: true,
percent: deleteProgress.progress,
label: translations.deleteOperation,
alert: false,
});
setTimeout(
() => this.loopDeleteProgress(id, folderId, isFolder, translations),
1000
);
} else {
setSecondaryProgressBarData({
icon: "trash",
visible: true,
percent: 100,
label: translations.deleteOperation,
alert: false,
});
fetchFiles(folderId, filter)
.then((data) => {
if (!isRecycleBinFolder && isFolder) {
const path = data.selectedFolder.pathParts.slice(0);
const newTreeFolders = treeFolders;
const folders = data.selectedFolder.folders;
const foldersCount = data.selectedFolder.foldersCount;
loopTreeFolders(path, newTreeFolders, folders, foldersCount);
setTreeFolders(newTreeFolders);
}
//isFolder
// ? toastr.success(translations.folderRemoved)
// : toastr.success(translations.fileRemoved);
})
.catch((err) => {
setSecondaryProgressBarData({
visible: true,
alert: true,
});
//toastr.error(err);
setTimeout(() => clearSecondaryProgressData(), TIMEOUT);
})
.finally(() =>
setTimeout(() => clearSecondaryProgressData(), TIMEOUT)
);
}
});
};
}
export default new FilesActionStore();

View File

@ -15,7 +15,8 @@ const GroupAction = React.lazy(() => import("./components/pages/GroupAction"));
const Reassign = React.lazy(() => import("./components/pages/Reassign"));
import config from "../package.json";
import "./custom.scss";
import "./i18n";
import i18n from "./i18n";
import { I18nextProvider } from "react-i18next";
const Error404 = React.lazy(() => import("studio/Error404"));
@ -86,8 +87,10 @@ const People = inject(({ auth, peopleStore }) => ({
const peopleStore = new PeopleStore();
export default () => (
export default (props) => (
<PeopleProvider peopleStore={peopleStore}>
<People />
<I18nextProvider i18n={i18n}>
<People {...props}/>
</I18nextProvider>
</PeopleProvider>
);

View File

@ -1,7 +1,6 @@
import React from "react";
//import PropTypes from "prop-types";
import { withRouter } from "react-router";
import MainButton from "@appserver/components/main-button";
import DropDownItem from "@appserver/components/drop-down-item";
import InviteDialog from "./../../dialogs/InviteDialog/index";
@ -67,17 +66,18 @@ class PureArticleMainButtonContent extends React.Component {
<>
<MainButton isDisabled={false} isDropdown={true} text={t("Actions")}>
<DropDownItem
icon="images/add.employee.react.svg"
icon={`${homepage}/images/add.employee.react.svg`}
label={userCaption}
onClick={this.goToEmployeeCreate}
/>
<DropDownItem
icon="images/add.guest.react.svg"
icon={`${homepage}/images/add.guest.react.svg`}
label={guestCaption}
onClick={this.goToGuestCreate}
/>
<DropDownItem
icon="images/add.department.react.svg"
icon={`${homepage}/images/add.department.react.svg`}
label={groupCaption}
onClick={this.goToGroupCreate}
/>
@ -94,7 +94,7 @@ class PureArticleMainButtonContent extends React.Component {
/> */}
{false && (
<DropDownItem
icon="images/import.react.svg"
icon={`${homepage}/images/import.react.svg`}
label={t("ImportPeople")}
onClick={this.onDropDownItemClick.bind(
this,

View File

@ -16,7 +16,7 @@ import RectangleLoader from "@appserver/common/components/Loaders/RectangleLoade
import { updateTempContent } from "@appserver/common/utils";
import { Provider as MobxProvider } from "mobx-react";
import ThemeProvider from "@appserver/components/theme-provider";
import { Base, Dark } from "@appserver/components/themes";
import { Base } from "@appserver/components/themes";
import store from "studio/store";
import config from "../package.json";
import "./custom.scss";
@ -237,28 +237,6 @@ const Shell = ({ items = [], page = "home", ...rest }) => {
);
};
// const mapStateToProps = (state) => {
// const { modules, isLoaded, settings } = state.auth;
// const { organizationName } = settings;
// return {
// modules,
// isLoaded,
// organizationName,
// };
// };
// const mapDispatchToProps = (dispatch) => {
// return {
// getIsAuthenticated: () => getIsAuthenticated(dispatch),
// getPortalSettings: () => getPortalSettings(dispatch),
// getUser: () => getUser(dispatch),
// getModules: () => getModules(dispatch),
// setIsLoaded: () => dispatch(setIsLoaded(true)),
// };
// };
// export default connect(mapStateToProps, mapDispatchToProps)(Shell);
const ShellWrapper = inject(({ auth }) => {
const { init, isLoaded } = auth;

View File

@ -0,0 +1,39 @@
import i18n from "i18next";
import Backend from "i18next-http-backend";
import { LANGUAGE } from "@appserver/common/constants";
//import LanguageDetector from "i18next-browser-languagedetector";
// not like to use this?
// have a look at the Quick start guide
// for passing in lng and translations on init
const languages = ["en", "ru"];
const newInstance = i18n.createInstance();
newInstance.use(Backend).init({
lng: localStorage.getItem(LANGUAGE) || "en",
supportedLngs: languages,
whitelist: languages,
fallbackLng: "en",
load: "languageOnly",
//debug: true,
interpolation: {
escapeValue: false, // not needed for react as it escapes by default
format: function (value, format) {
if (format === "lowercase") return value.toLowerCase();
return value;
},
},
backend: {
loadPath: `/locales/{{lng}}/About.json`,
},
react: {
useSuspense: false,
},
});
export default newInstance;

View File

@ -2,11 +2,12 @@
import Text from "@appserver/components/text";
import Link from "@appserver/components/link";
import PageLayout from "@appserver/common/components/PageLayout";
import { useTranslation, Trans } from "react-i18next";
import { I18nextProvider, useTranslation, Trans } from "react-i18next";
import version from "../../../../package.json";
import styled from "styled-components";
import { isMobile } from "react-device-detect";
import { setDocumentTitle } from "../../../helpers/utils";
import i18n from "./i18n";
const BodyStyle = styled.div`
margin-top: ${isMobile ? "80px" : "24px"};
@ -191,11 +192,13 @@ const Body = () => {
};
const About = ({ language }) => (
<PageLayout>
<PageLayout.SectionBody>
<Body language={language} />
</PageLayout.SectionBody>
</PageLayout>
<I18nextProvider i18n={i18n}>
<PageLayout>
<PageLayout.SectionBody>
<Body language={language} />
</PageLayout.SectionBody>
</PageLayout>
</I18nextProvider>
);
export default About;

View File

@ -17,6 +17,8 @@ import { isMobile, isIOS } from "react-device-detect";
import { setDocumentTitle } from "../../../helpers/utils";
import { inject } from "mobx-react";
import i18n from "../../../i18n";
import { I18nextProvider } from "react-i18next";
const commonStyles = `
.link-box {
@ -228,8 +230,14 @@ ComingSoon.propTypes = {
isLoaded: PropTypes.bool,
};
export default inject(({ auth }) => ({
const ComingSoonWrapper = inject(({ auth }) => ({
modules: auth.moduleStore.modules,
isLoaded: auth.isLoaded,
setCurrentProductId: auth.settingsStore.setCurrentProductId,
}))(withRouter(ComingSoon));
export default (props) => (
<I18nextProvider i18n={i18n}>
<ComingSoonWrapper {...props} />
</I18nextProvider>
);

View File

@ -53,7 +53,7 @@ const Tiles = ({ modules, isPrimary }) => {
(m) => m.isPrimary === isPrimary && m.isolateMode !== true
);
console.log("Tiles", mapped, isPrimary);
//console.log("Tiles", mapped, isPrimary);
return mapped.length > 0 ? (
<div className="home-modules">

View File

@ -4,55 +4,33 @@ import Backend from "i18next-http-backend";
import config from "../package.json";
import { LANGUAGE } from "@appserver/common/constants";
//import LanguageDetector from "i18next-browser-languagedetector";
// not like to use this?
// have a look at the Quick start guide
// for passing in lng and translations on init
const languages = ["en", "ru"];
i18n
/*
load translation using http -> see /public/locales (i.e. https://github.com/i18next/react-i18next/tree/master/example/react/public/locales)
learn more: https://github.com/i18next/i18next-http-backend
*/
.use(Backend)
/*
detect user language
learn more: https://github.com/i18next/i18next-browser-languageDetector
*/
//.use(LanguageDetector)
/*
pass the i18n instance to react-i18next.
*/
.use(initReactI18next)
/*
init i18next
for all options read: https://www.i18next.com/overview/configuration-options
*/
.init({
lng: localStorage.getItem(LANGUAGE) || "en",
supportedLngs: languages,
whitelist: languages,
fallbackLng: "en",
load: "languageOnly",
//debug: true,
const newInstance = i18n.createInstance();
interpolation: {
escapeValue: false, // not needed for react as it escapes by default
format: function (value, format) {
if (format === "lowercase") return value.toLowerCase();
return value;
},
newInstance.use(Backend).init({
lng: localStorage.getItem(LANGUAGE) || "en",
supportedLngs: languages,
whitelist: languages,
fallbackLng: "en",
load: "languageOnly",
//debug: true,
interpolation: {
escapeValue: false, // not needed for react as it escapes by default
format: function (value, format) {
if (format === "lowercase") return value.toLowerCase();
return value;
},
},
backend: {
loadPath: `${config.homepage}/locales/{{lng}}/{{ns}}.json`,
},
backend: {
loadPath: `${config.homepage}/locales/{{lng}}/{{ns}}.json`,
},
react: {
useSuspense: false,
},
});
react: {
useSuspense: false,
},
});
export default i18n;
export default newInstance;

View File

@ -1,6 +1,5 @@
import React, { Component, useEffect } from "react";
import React, { useEffect, useState } from "react";
import styled, { css } from "styled-components";
import { withTranslation } from "react-i18next";
import PropTypes from "prop-types";
import { withRouter } from "react-router";
import Box from "@appserver/components/box";
@ -20,7 +19,8 @@ import { checkPwd } from "@appserver/common/desktop";
import { sendInstructionsToChangePassword } from "@appserver/common/api/people";
import { createPasswordHash, tryRedirectTo } from "@appserver/common/utils";
import { inject, observer } from "mobx-react";
import "./i18n";
import i18n from "./i18n";
import { I18nextProvider, useTranslation } from "react-i18next";
const LoginContainer = styled.div`
display: flex;
@ -115,108 +115,107 @@ const LoginFormWrapper = styled.div`
height: calc(100vh-56px);
`;
class Form extends Component {
constructor(props) {
super(props);
const Form = (props) => {
const [identifierValid, setIdentifierValid] = useState(true);
const [identifier, setIdentifier] = useState("");
const [isLoading, setIsLoading] = useState(false);
const [isDisabled, setIsDisabled] = useState(false);
this.state = {
identifierValid: true,
identifier: "",
isLoading: false,
isDisabled: false,
passwordValid: true,
password: "",
isChecked: false,
openDialog: false,
email: "",
emailError: false,
errorText: "",
socialButtons: [],
};
}
const [passwordValid, setPasswordValid] = useState(true);
const [password, setPassword] = useState("");
const [isChecked, setIsChecked] = useState(false);
const [openDialog, setOpenDialog] = useState(false);
const [email, setEmail] = useState("");
const [emailError, setEmailError] = useState(false);
const [errorText, setErrorText] = useState("");
const [socialButtons, setSocialButtons] = useState([]);
onChangeLogin = (event) => {
this.setState({ identifier: event.target.value });
!this.state.identifierValid && this.setState({ identifierValid: true });
this.state.errorText && this.setState({ errorText: "" });
const {
login,
hashSettings,
isDesktop,
defaultPage,
match,
organizationName,
greetingTitle,
} = props;
const { error, confirmedEmail } = match.params;
const { t } = useTranslation("Login");
const onChangeLogin = (event) => {
setIdentifier(event.target.value);
!identifierValid && setIdentifierValid(true);
errorText && setErrorText("");
};
onChangePassword = (event) => {
this.setState({ password: event.target.value });
!this.state.passwordValid && this.setState({ passwordValid: true });
this.state.errorText && this.setState({ errorText: "" });
const onChangePassword = (event) => {
setPassword(event.target.value);
!passwordValid && setPasswordValid(true);
errorText && setErrorText("");
};
onChangeEmail = (event) => {
this.setState({ email: event.target.value, emailError: false });
const onChangeEmail = (event) => {
setEmail(event.target.value);
setEmailError(false);
};
onChangeCheckbox = () => this.setState({ isChecked: !this.state.isChecked });
const onChangeCheckbox = () => setIsChecked(!isChecked);
onClick = () => {
this.setState({
openDialog: true,
isDisabled: true,
email: this.state.identifier,
});
const onClick = () => {
setOpenDialog(true);
setIsDisabled(true);
setEmail(identifier);
};
onKeyPress = (event) => {
const onKeyPress = (event) => {
if (event.key === "Enter") {
!this.state.isDisabled
? this.onSubmit()
: this.onSendPasswordInstructions();
!isDisabled ? onSubmit() : onSendPasswordInstructions();
}
};
onSendPasswordInstructions = () => {
if (!this.state.email.trim()) {
this.setState({ emailError: true });
const onSendPasswordInstructions = () => {
if (!email.trim()) {
setEmailError(true);
} else {
this.setState({ isLoading: true });
sendInstructionsToChangePassword(this.state.email)
setIsLoading(true);
sendInstructionsToChangePassword(email)
.then(
(res) => toastr.success(res),
(message) => toastr.error(message)
)
.finally(this.onDialogClose());
.finally(onDialogClose());
}
};
onDialogClose = () => {
this.setState({
openDialog: false,
isDisabled: false,
isLoading: false,
email: "",
emailError: false,
});
const onDialogClose = () => {
setOpenDialog(false);
setIsDisabled(false);
setIsLoading(false);
setEmail("");
setEmailError(false);
};
onSubmit = () => {
const { errorText, identifier, password } = this.state;
const { login, hashSettings, isDesktop, defaultPage } = this.props;
errorText && this.setState({ errorText: "" });
const onSubmit = () => {
errorText && setErrorText("");
let hasError = false;
const userName = identifier.trim();
if (!userName) {
hasError = true;
this.setState({ identifierValid: !hasError });
setIdentifierValid(!hasError);
}
const pass = password.trim();
if (!pass) {
hasError = true;
this.setState({ passwordValid: !hasError });
setPasswordValid(!hasError);
}
if (hasError) return false;
this.setState({ isLoading: true });
setIsLoading(true);
const hash = createPasswordHash(pass, hashSettings);
isDesktop && checkPwd();
@ -224,203 +223,178 @@ class Form extends Component {
login(userName, hash)
.then(() => tryRedirectTo(defaultPage))
.catch((error) => {
this.setState({
errorText: error,
identifierValid: !error,
passwordValid: !error,
isLoading: false,
});
setErrorText(error);
setIdentifierValid(!error);
setPasswordValid(!error);
setIsLoading(false);
});
};
componentDidMount() {
const { match, t, organizationName } = this.props;
const { error, confirmedEmail } = match.params;
useEffect(() => {
document.title = `${t("Authorization")} ${organizationName}`; //TODO: implement the setDocumentTitle() utility in ASC.Web.Common
error && this.setState({ errorText: error });
confirmedEmail && this.setState({ identifier: confirmedEmail });
window.addEventListener("keyup", this.onKeyPress);
}
error && setErrorText(error);
confirmedEmail && setIdentifier(confirmedEmail);
window.addEventListener("keyup", onKeyPress);
componentWillUnmount() {
window.removeEventListener("keyup", this.onKeyPress);
}
return () => {
window.removeEventListener("keyup", onKeyPress);
};
}, []);
settings = {
const settings = {
minLength: 6,
upperCase: false,
digits: false,
specSymbols: false,
};
render() {
const { greetingTitle, match, t } = this.props;
//console.log("Login render");
const {
identifierValid,
identifier,
isLoading,
passwordValid,
password,
isChecked,
openDialog,
email,
emailError,
errorText,
socialButtons,
} = this.state;
const { confirmedEmail } = match.params;
return (
<>
<LoginContainer>
<Text
fontSize="32px"
fontWeight={600}
textAlign="center"
className="greeting-title"
>
{greetingTitle}
</Text>
//console.log("Login render");
return (
<>
<LoginContainer>
<Text
fontSize="32px"
fontWeight={600}
textAlign="center"
className="greeting-title"
<form className="auth-form-container">
<FieldContainer
isVertical={true}
labelVisible={false}
hasError={!identifierValid}
errorMessage={errorText ? errorText : t("RequiredFieldMessage")} //TODO: Add wrong login server error
>
{greetingTitle}
</Text>
<form className="auth-form-container">
<FieldContainer
isVertical={true}
labelVisible={false}
<TextInput
id="login"
name="login"
hasError={!identifierValid}
errorMessage={errorText ? errorText : t("RequiredFieldMessage")} //TODO: Add wrong login server error
>
<TextInput
id="login"
name="login"
hasError={!identifierValid}
value={identifier}
placeholder={t("RegistrationEmailWatermark")}
size="large"
scale={true}
isAutoFocussed={true}
tabIndex={1}
isDisabled={isLoading}
autoComplete="username"
onChange={this.onChangeLogin}
onKeyDown={this.onKeyPress}
/>
</FieldContainer>
<FieldContainer
isVertical={true}
labelVisible={false}
value={identifier}
placeholder={t("RegistrationEmailWatermark")}
size="large"
scale={true}
isAutoFocussed={true}
tabIndex={1}
isDisabled={isLoading}
autoComplete="username"
onChange={onChangeLogin}
onKeyDown={onKeyPress}
/>
</FieldContainer>
<FieldContainer
isVertical={true}
labelVisible={false}
hasError={!passwordValid}
errorMessage={errorText ? "" : t("RequiredFieldMessage")} //TODO: Add wrong password server error
>
<PasswordInput
simpleView={true}
passwordSettings={settings}
id="password"
inputName="password"
placeholder={t("Password")}
type="password"
hasError={!passwordValid}
errorMessage={errorText ? "" : t("RequiredFieldMessage")} //TODO: Add wrong password server error
>
<PasswordInput
simpleView={true}
passwordSettings={this.settings}
id="password"
inputName="password"
placeholder={t("Password")}
type="password"
hasError={!passwordValid}
inputValue={password}
size="large"
scale={true}
tabIndex={1}
isDisabled={isLoading}
autoComplete="current-password"
onChange={this.onChangePassword}
onKeyDown={this.onKeyPress}
inputValue={password}
size="large"
scale={true}
tabIndex={1}
isDisabled={isLoading}
autoComplete="current-password"
onChange={onChangePassword}
onKeyDown={onKeyPress}
/>
</FieldContainer>
<div className="login-forgot-wrapper">
<div className="login-checkbox-wrapper">
<Checkbox
className="login-checkbox"
isChecked={isChecked}
onChange={onChangeCheckbox}
label={<Text fontSize="13px">{t("Remember")}</Text>}
/>
</FieldContainer>
<div className="login-forgot-wrapper">
<div className="login-checkbox-wrapper">
<Checkbox
className="login-checkbox"
isChecked={isChecked}
onChange={this.onChangeCheckbox}
label={<Text fontSize="13px">{t("Remember")}</Text>}
/>
{/*<HelpButton
{/*<HelpButton
className="login-tooltip"
helpButtonHeaderContent={t("CookieSettingsTitle")}
tooltipContent={
<Text fontSize="12px">{t("RememberHelper")}</Text>
}
/>*/}
<Link
fontSize="13px"
color="#316DAA"
className="login-link"
type="page"
isHovered={false}
onClick={this.onClick}
>
{t("ForgotPassword")}
</Link>
</div>
<Link
fontSize="13px"
color="#316DAA"
className="login-link"
type="page"
isHovered={false}
onClick={onClick}
>
{t("ForgotPassword")}
</Link>
</div>
</div>
{openDialog && (
<ForgotPasswordModalDialog
openDialog={openDialog}
isLoading={isLoading}
email={email}
emailError={emailError}
onChangeEmail={this.onChangeEmail}
onSendPasswordInstructions={this.onSendPasswordInstructions}
onDialogClose={this.onDialogClose}
t={t}
/>
)}
<Button
id="button"
className="login-button"
primary
size="large"
scale={true}
label={isLoading ? t("LoadingProcessing") : t("LoginButton")}
tabIndex={1}
isDisabled={isLoading}
{openDialog && (
<ForgotPasswordModalDialog
openDialog={openDialog}
isLoading={isLoading}
onClick={this.onSubmit}
email={email}
emailError={emailError}
onChangeEmail={onChangeEmail}
onSendPasswordInstructions={onSendPasswordInstructions}
onDialogClose={onDialogClose}
t={t}
/>
)}
{confirmedEmail && (
<Text isBold={true} fontSize="16px">
{t("MessageEmailConfirmed")} {t("MessageAuthorize")}
</Text>
)}
{/* TODO: old error indication
<Button
id="button"
className="login-button"
primary
size="large"
scale={true}
label={isLoading ? t("LoadingProcessing") : t("LoginButton")}
tabIndex={1}
isDisabled={isLoading}
isLoading={isLoading}
onClick={onSubmit}
/>
{confirmedEmail && (
<Text isBold={true} fontSize="16px">
{t("MessageEmailConfirmed")} {t("MessageAuthorize")}
</Text>
)}
{/* TODO: old error indication
<Text fontSize="14px" color="#c30">
{errorText}
</Text> */}
{socialButtons.length ? (
<Box displayProp="flex" alignItems="center">
<div className="login-bottom-border"></div>
<Text className="login-bottom-text" color="#A3A9AE">
{t("Or")}
</Text>
<div className="login-bottom-border"></div>
</Box>
) : null}
</form>
</LoginContainer>
</>
);
}
}
{socialButtons.length ? (
<Box displayProp="flex" alignItems="center">
<div className="login-bottom-border"></div>
<Text className="login-bottom-text" color="#A3A9AE">
{t("Or")}
</Text>
<div className="login-bottom-border"></div>
</Box>
) : null}
</form>
</LoginContainer>
</>
);
};
Form.propTypes = {
login: PropTypes.func.isRequired,
match: PropTypes.object.isRequired,
hashSettings: PropTypes.object,
greetingTitle: PropTypes.string.isRequired,
t: PropTypes.func.isRequired,
socialButtons: PropTypes.array,
organizationName: PropTypes.string,
homepage: PropTypes.string,
@ -434,10 +408,6 @@ Form.defaultProps = {
email: "",
};
const FormWrapper = withTranslation()(Form);
//const RegisterWrapper = withTranslation()(Register);
const LoginForm = (props) => {
const { enabledJoin, isDesktop } = props;
@ -445,7 +415,7 @@ const LoginForm = (props) => {
<LoginFormWrapper enabledJoin={enabledJoin} isDesktop={isDesktop}>
<PageLayout>
<PageLayout.SectionBody>
<FormWrapper {...props} />
<Form {...props} />
</PageLayout.SectionBody>
</PageLayout>
<Register />
@ -459,7 +429,7 @@ LoginForm.propTypes = {
isDesktop: PropTypes.bool.isRequired,
};
export default inject(({ auth }) => {
const Login = inject(({ auth }) => {
const { settingsStore, isAuthenticated, isLoaded, login } = auth;
const {
greetingSettings: greetingTitle,
@ -482,3 +452,9 @@ export default inject(({ auth }) => {
login,
};
})(withRouter(observer(LoginForm)));
export default (props) => (
<I18nextProvider i18n={i18n}>
<Login {...props} />
</I18nextProvider>
);

View File

@ -0,0 +1,25 @@
// Override default variables before the import
//$font-family-base: "Open Sans", sans-serif;
html,
body {
height: 100%;
}
#root {
min-height: 100%;
.pageLoader {
position: fixed;
left: calc(50% - 20px);
top: 35%;
}
}
body {
margin: 0;
overflow: hidden;
}
body.loading * {
cursor: wait !important;
}

View File

@ -4,39 +4,22 @@ import Backend from "i18next-http-backend";
import config from "../package.json";
import { LANGUAGE } from "@appserver/common/constants";
//import LanguageDetector from "i18next-browser-languagedetector";
// not like to use this?
// have a look at the Quick start guide
// for passing in lng and translations on init
const languages = ["en", "ru"];
i18n
/*
load translation using http -> see /public/locales (i.e. https://github.com/i18next/react-i18next/tree/master/example/react/public/locales)
learn more: https://github.com/i18next/i18next-http-backend
*/
.use(Backend)
/*
detect user language
learn more: https://github.com/i18next/i18next-browser-languageDetector
*/
//.use(LanguageDetector)
/*
pass the i18n instance to react-i18next.
*/
const newInstance = i18n.createInstance();
const lng = localStorage.getItem(LANGUAGE) || "en";
newInstance
.use(initReactI18next)
/*
init i18next
for all options read: https://www.i18next.com/overview/configuration-options
*/
.use(Backend)
.init({
lng: localStorage.getItem(LANGUAGE) || "en",
lng: "ru",
supportedLngs: languages,
whitelist: languages,
fallbackLng: "en",
//whitelist: languages,
fallbackLng: "ru",
load: "languageOnly",
//debug: true,
debug: true,
interpolation: {
escapeValue: false, // not needed for react as it escapes by default
@ -48,11 +31,16 @@ i18n
backend: {
loadPath: `${config.homepage}/locales/{{lng}}/{{ns}}.json`,
allowMultiLoading: true,
crossDomain: false,
},
ns: ["Login"],
defaultNS: "Login",
react: {
useSuspense: true,
},
});
export default i18n;
export default newInstance;

View File

@ -1,3 +0,0 @@
body {
font-family: Arial, Helvetica, sans-serif;
}

View File

@ -8,7 +8,7 @@ import RegisterModalDialog from "./register-modal-dialog";
import styled from "styled-components";
import PropTypes from "prop-types";
import { sendRegisterRequest } from "@appserver/common/api/settings";
import { I18nextProvider, withTranslation } from "react-i18next";
import { I18nextProvider, useTranslation } from "react-i18next";
import i18n from "../i18n";
import { inject, observer } from "mobx-react";
@ -26,13 +26,16 @@ const StyledRegister = styled(Box)`
cursor: pointer;
`;
const Register = ({ t }) => {
const Register = (props) => {
const { enabledJoin, isAuthenticated } = props;
const [visible, setVisible] = useState(false);
const [loading, setLoading] = useState(false);
const [email, setEmail] = useState("");
const [emailErr, setEmailErr] = useState(false);
const { t } = useTranslation("Login");
const onRegisterClick = () => {
setVisible(true);
};
@ -64,50 +67,31 @@ const Register = ({ t }) => {
};
return (
<>
<StyledRegister onClick={onRegisterClick}>
<Text color="#316DAA">{t("Register")}</Text>
</StyledRegister>
enabledJoin &&
!isAuthenticated && (
<>
<StyledRegister onClick={onRegisterClick}>
<Text color="#316DAA">{t("Register")}</Text>
</StyledRegister>
{visible && (
<RegisterModalDialog
visible={visible}
loading={loading}
email={email}
emailErr={emailErr}
t={t}
onChangeEmail={onChangeEmail}
onRegisterModalClose={onRegisterModalClose}
onSendRegisterRequest={onSendRegisterRequest}
/>
)}
</>
{visible && (
<RegisterModalDialog
visible={visible}
loading={loading}
email={email}
emailErr={emailErr}
t={t}
onChangeEmail={onChangeEmail}
onRegisterModalClose={onRegisterModalClose}
onSendRegisterRequest={onSendRegisterRequest}
/>
)}
</>
)
);
};
Register.propTypes = {
t: PropTypes.func.isRequired,
};
const RegisterTranslationWrapper = withTranslation()(Register);
const RegisterWrapper = (props) => {
const { language, isAuthenticated, enabledJoin } = props;
useEffect(() => {
i18n.changeLanguage(language);
}, [language]);
return (
<I18nextProvider i18n={i18n}>
{enabledJoin && !isAuthenticated && (
<RegisterTranslationWrapper {...props} />
)}
</I18nextProvider>
);
};
RegisterWrapper.propTypes = {
language: PropTypes.string,
isAuthenticated: PropTypes.bool,
enabledJoin: PropTypes.bool,
@ -121,4 +105,4 @@ export default inject(({ auth }) => {
isAuthenticated,
language,
};
})(observer(RegisterWrapper));
})(observer(Register));

View File

@ -77,8 +77,15 @@ var config = {
},
{ test: /\.json$/, loader: "json-loader" },
{
test: /\.css$/i,
use: ["style-loader", "css-loader"],
test: /\.s[ac]ss$/i,
use: [
// Creates `style` nodes from JS strings
"style-loader",
// Translates CSS into CommonJS
"css-loader",
// Compiles Sass to CSS
"sass-loader",
],
},
{
test: /\.(js|jsx)$/,

View File

@ -5818,9 +5818,9 @@ caniuse-api@^3.0.0:
lodash.uniq "^4.5.0"
caniuse-lite@^1.0.0, caniuse-lite@^1.0.30001109, caniuse-lite@^1.0.30001125, caniuse-lite@^1.0.30001181:
version "1.0.30001194"
resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001194.tgz#3d16ff3d734a5a7d9818402c28b1f636c5be5bed"
integrity sha512-iDUOH+oFeBYk5XawYsPtsx/8fFpndAPUQJC7gBTfxHM8xw5nOZv7ceAD4frS1MKCLUac7QL5wdAJiFQlDRjXlA==
version "1.0.30001196"
resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001196.tgz#00518a2044b1abf3e0df31fadbe5ed90b63f4e64"
integrity sha512-CPvObjD3ovWrNBaXlAIGWmg2gQQuJ5YhuciUOjPRox6hIQttu8O+b51dx6VIpIY9ESd2d0Vac1RKpICdG4rGUg==
capture-exit@^2.0.0:
version "2.0.0"
@ -7584,9 +7584,9 @@ ejs@^3.1.2:
jake "^10.6.1"
electron-to-chromium@^1.3.564, electron-to-chromium@^1.3.649:
version "1.3.678"
resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.678.tgz#c7c6960463167126b7ed076fade14cac6223bfff"
integrity sha512-E5ha1pE9+aWWrT2fUD5wdPBWUnYtKnEnloewbtVyrkAs79HvodOiNO4rMR94+hKbxgMFQG4fnPQACOc1cfMfBg==
version "1.3.680"
resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.680.tgz#88cc44bd2a85b46cf7521f714db57dd74d0cd488"
integrity sha512-XBACJT9RdpdWtoMXQPR8Be3ZtmizWWbxfw8cY2b5feUwiDO3FUl8qo4W2jXoq/WnnA3xBRqafu1XbpczqyUvlA==
element-resize-detector@^1.2.1:
version "1.2.2"
@ -7826,27 +7826,10 @@ error-stack-parser@^2.0.6:
dependencies:
stackframe "^1.1.1"
es-abstract@^1.17.0-next.0, es-abstract@^1.17.2, es-abstract@^1.17.4:
version "1.17.7"
resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.17.7.tgz#a4de61b2f66989fc7421676c1cb9787573ace54c"
integrity sha512-VBl/gnfcJ7OercKA9MVaegWsBHFjV492syMudcnQZvt/Dw8ezpcOHYZXa/J96O8vx+g4x65YKhxOwDUh63aS5g==
dependencies:
es-to-primitive "^1.2.1"
function-bind "^1.1.1"
has "^1.0.3"
has-symbols "^1.0.1"
is-callable "^1.2.2"
is-regex "^1.1.1"
object-inspect "^1.8.0"
object-keys "^1.1.1"
object.assign "^4.1.1"
string.prototype.trimend "^1.0.1"
string.prototype.trimstart "^1.0.1"
es-abstract@^1.18.0-next.1, es-abstract@^1.18.0-next.2:
version "1.18.0-next.3"
resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.18.0-next.3.tgz#56bc8b5cc36b2cca25a13be07f3c02c2343db6b7"
integrity sha512-VMzHx/Bczjg59E6jZOQjHeN3DEoptdhejpARgflAViidlqSpjdq9zA6lKwlhRRs/lOw1gHJv2xkkSFRgvEwbQg==
es-abstract@^1.17.0-next.0, es-abstract@^1.17.2, es-abstract@^1.17.4, es-abstract@^1.18.0-next.1, es-abstract@^1.18.0-next.2:
version "1.18.0"
resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.18.0.tgz#ab80b359eecb7ede4c298000390bc5ac3ec7b5a4"
integrity sha512-LJzK7MrQa8TS0ja2w3YNLzUgJCGPdPOV1yVvezjNnS89D+VR08+Szt2mz3YB2Dck/+w5tfIq/RoUAFqJJGM2yw==
dependencies:
call-bind "^1.0.2"
es-to-primitive "^1.2.1"
@ -10117,7 +10100,7 @@ is-buffer@^2.0.0:
resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-2.0.5.tgz#ebc252e400d22ff8d77fa09888821a24a658c191"
integrity sha512-i2R6zNFDwgEHJyQUtJEk0XFi1i0dPFn/oqjK3/vPCcDeJvW5NQ83V8QbicfF1SupOaB0h8ntgBC2YiE7dfyctQ==
is-callable@^1.1.3, is-callable@^1.1.4, is-callable@^1.1.5, is-callable@^1.2.2, is-callable@^1.2.3:
is-callable@^1.1.3, is-callable@^1.1.4, is-callable@^1.1.5, is-callable@^1.2.3:
version "1.2.3"
resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.2.3.tgz#8b1e0500b73a1d76c70487636f368e519de8db8e"
integrity sha512-J1DcMe8UYTBSrKezuIUTUwjXsho29693unXM2YhJUTR2txK/eG47bvNa/wipPFmZFgr/N6f1GA66dv0mEyTIyQ==
@ -12713,7 +12696,7 @@ object-copy@^0.1.0:
define-property "^0.2.5"
kind-of "^3.0.3"
object-inspect@^1.7.0, object-inspect@^1.8.0, object-inspect@^1.9.0:
object-inspect@^1.7.0, object-inspect@^1.9.0:
version "1.9.0"
resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.9.0.tgz#c90521d74e1127b67266ded3394ad6116986533a"
integrity sha512-i3Bp9iTqwhaLZBxGkRfo5ZbE07BQRT7MGu8+nNgwW9ItGp1TzCTw2DLEoWwjClxBjOFI/hWljTAmYGCEwmtnOw==
@ -12738,7 +12721,7 @@ object-visit@^1.0.0:
dependencies:
isobject "^3.0.0"
object.assign@^4.1.0, object.assign@^4.1.1, object.assign@^4.1.2:
object.assign@^4.1.0, object.assign@^4.1.2:
version "4.1.2"
resolved "https://registry.yarnpkg.com/object.assign/-/object.assign-4.1.2.tgz#0ed54a342eceb37b38ff76eb831a0e788cb63940"
integrity sha512-ixT2L5THXsApyiUPYKmW+2EHpXXe5Ii3M+f4e+aJFAHao5amFRW6J0OO6c/LU8Be47utCx2GL89hxGB6XSmKuQ==
@ -14506,9 +14489,9 @@ react-hammerjs@^1.0.1:
hammerjs "^2.0.8"
react-helmet-async@^1.0.2:
version "1.0.8"
resolved "https://registry.yarnpkg.com/react-helmet-async/-/react-helmet-async-1.0.8.tgz#28178a85ca4d669dff295d5c119d575af4451855"
integrity sha512-FSOjIzbHdJYTQ0An0yZlqd4mmTzQxPLyZC2LF5zmobo6Af+tXDKnLmmAE7s+vZ7JuRt/L8baK3VnULSB1usY0g==
version "1.0.9"
resolved "https://registry.yarnpkg.com/react-helmet-async/-/react-helmet-async-1.0.9.tgz#5b9ed2059de6b4aab47f769532f9fbcbce16c5ca"
integrity sha512-N+iUlo9WR3/u9qGMmP4jiYfaD6pe9IvDTapZLFJz2D3xlTlCM1Bzy4Ab3g72Nbajo/0ZyW+W9hdz8Hbe4l97pQ==
dependencies:
"@babel/runtime" "^7.12.5"
invariant "^2.2.4"
@ -16511,7 +16494,7 @@ string.prototype.trim@^1.2.1:
define-properties "^1.1.3"
es-abstract "^1.18.0-next.2"
string.prototype.trimend@^1.0.1, string.prototype.trimend@^1.0.4:
string.prototype.trimend@^1.0.4:
version "1.0.4"
resolved "https://registry.yarnpkg.com/string.prototype.trimend/-/string.prototype.trimend-1.0.4.tgz#e75ae90c2942c63504686c18b287b4a0b1a45f80"
integrity sha512-y9xCjw1P23Awk8EvTpcyL2NIr1j7wJ39f+k6lvRnSMz+mz9CGz9NYPelDk42kOz6+ql8xjfK8oYzy3jAP5QU5A==
@ -16519,7 +16502,7 @@ string.prototype.trimend@^1.0.1, string.prototype.trimend@^1.0.4:
call-bind "^1.0.2"
define-properties "^1.1.3"
string.prototype.trimstart@^1.0.1, string.prototype.trimstart@^1.0.4:
string.prototype.trimstart@^1.0.4:
version "1.0.4"
resolved "https://registry.yarnpkg.com/string.prototype.trimstart/-/string.prototype.trimstart-1.0.4.tgz#b36399af4ab2999b4c9c648bd7a3fb2bb26feeed"
integrity sha512-jh6e984OBfvxS50tdY2nRZnoC5/mLFKOREQfw8t5yytkoUsJRNxvI/E39qu1sD0OtWI3OC0XgKSmcWwziwYuZw==