Merge branch 'master' into feature/advanced-selector-v2
# Conflicts: # products/ASC.People/Client/yarn.lock # web/ASC.Web.Client/yarn.lock
This commit is contained in:
commit
559c87a450
@ -1,15 +1,15 @@
|
||||
syntax = "proto3";
|
||||
|
||||
import "google/protobuf/wrappers.proto";
|
||||
package ASC.Core;
|
||||
|
||||
message AzRecordCache {
|
||||
string SubjectId = 1;
|
||||
google.protobuf.StringValue SubjectId = 1;
|
||||
|
||||
string ActionId = 2;
|
||||
google.protobuf.StringValue ActionId = 2;
|
||||
|
||||
string ObjectId = 3;
|
||||
google.protobuf.StringValue ObjectId = 3;
|
||||
|
||||
string Reaction = 4;
|
||||
google.protobuf.StringValue Reaction = 4;
|
||||
|
||||
int32 Tenant = 5;
|
||||
}
|
@ -6,6 +6,7 @@ import { Layout, Toast } from 'asc-web-components';
|
||||
import { logout } from '../../store/auth/actions';
|
||||
import { withTranslation, I18nextProvider } from 'react-i18next';
|
||||
import i18n from "./i18n";
|
||||
import { isAdmin } from "../../store/auth/selectors";
|
||||
|
||||
class PurePeopleLayout extends React.Component {
|
||||
shouldComponentUpdate(nextProps) {
|
||||
@ -66,7 +67,23 @@ class PurePeopleLayout extends React.Component {
|
||||
};
|
||||
|
||||
|
||||
const getAvailableModules = (modules) => {
|
||||
const getAvailableModules = (modules, currentUser) => {
|
||||
const isUserAdmin = isAdmin(currentUser);
|
||||
const customModules = isUserAdmin ? [
|
||||
{
|
||||
separator: true,
|
||||
id: "nav-separator-2"
|
||||
},
|
||||
{
|
||||
id: 'settings',
|
||||
title: 'Settings',
|
||||
iconName: "SettingsIcon",
|
||||
notifications: 0,
|
||||
url: '/settings',
|
||||
onClick: () => window.open('/settings', "_self"),
|
||||
onBadgeClick: e => console.log("SettingsIconBadge Clicked", e)
|
||||
}] : [];
|
||||
|
||||
const separator = { separator: true, id: 'nav-separator-1' };
|
||||
const products = modules.map(product => {
|
||||
return {
|
||||
@ -80,13 +97,13 @@ const getAvailableModules = (modules) => {
|
||||
};
|
||||
}) || [];
|
||||
|
||||
return products.length ? [separator, ...products] : products;
|
||||
return products.length ? [separator, ...products, ...customModules] : products;
|
||||
};
|
||||
|
||||
function mapStateToProps(state) {
|
||||
return {
|
||||
hasChanges: state.auth.isAuthenticated && state.auth.isLoaded,
|
||||
availableModules: getAvailableModules(state.auth.modules),
|
||||
availableModules: getAvailableModules(state.auth.modules, state.auth.user),
|
||||
currentUser: state.auth.user,
|
||||
currentModuleId: state.auth.settings.currentProductId,
|
||||
settings: state.auth.settings,
|
||||
@ -96,7 +113,7 @@ function mapStateToProps(state) {
|
||||
|
||||
const PeopleLayoutContainer = withTranslation()(PurePeopleLayout);
|
||||
|
||||
const PeopleLayout = (props) => {
|
||||
const PeopleLayout = (props) => {
|
||||
const { language } = props;
|
||||
i18n.changeLanguage(language);
|
||||
return (<I18nextProvider i18n={i18n}><PeopleLayoutContainer {...props} /></I18nextProvider>);
|
||||
|
@ -1,38 +1,75 @@
|
||||
import React, { useCallback } from "react";
|
||||
import { withRouter } from "react-router";
|
||||
import { RowContent, Link, Icons } from "asc-web-components";
|
||||
import { RowContent, Link, LinkWithDropdown, Icons, toastr } from "asc-web-components";
|
||||
import { connect } from "react-redux";
|
||||
import { getUserStatus } from "../../../../../store/people/selectors";
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { headOfDepartment } from './../../../../../helpers/customNames';
|
||||
import history from "../../../../../history";
|
||||
|
||||
const getFormatedGroups = groups => {
|
||||
let temp = [];
|
||||
|
||||
if (!groups) temp.push({ key: 0, label: '' });
|
||||
|
||||
groups && groups.map(group =>
|
||||
temp.push(
|
||||
{
|
||||
key: group.id,
|
||||
label: group.name,
|
||||
onClick: () => history.push(`/products/people/filter?group=${group.id}`)
|
||||
}
|
||||
)
|
||||
);
|
||||
|
||||
if (temp.length <= 1) {
|
||||
return (
|
||||
<Link
|
||||
containerWidth='160px'
|
||||
type='action'
|
||||
title={temp[0].label}
|
||||
fontSize={12}
|
||||
onClick={temp[0].onClick}
|
||||
>
|
||||
{temp[0].label}
|
||||
</Link>);
|
||||
} else {
|
||||
return (
|
||||
<LinkWithDropdown
|
||||
isTextOverflow={true}
|
||||
containerWidth='160px'
|
||||
type='action'
|
||||
title={temp[0].label}
|
||||
fontSize={12}
|
||||
data={temp}
|
||||
>
|
||||
{temp[0].label}
|
||||
</LinkWithDropdown>);
|
||||
}
|
||||
};
|
||||
|
||||
const UserContent = ({ user, history, settings }) => {
|
||||
const { userName, displayName, headDepartment, department, mobilePhone, email } = user;
|
||||
const { userName, displayName, title, mobilePhone, email } = user;
|
||||
const status = getUserStatus(user);
|
||||
const groups = getFormatedGroups(user.groups);
|
||||
|
||||
const onUserNameClick = useCallback(() => {
|
||||
console.log("User name action");
|
||||
history.push(`${settings.homepage}/view/${userName}`);
|
||||
}, [history, settings.homepage, userName]);
|
||||
|
||||
const onHeadDepartmentClick = useCallback(
|
||||
() => console.log("Head of department action"),
|
||||
[]
|
||||
const onUserTitleClick = useCallback(
|
||||
() => toastr.success(`Filter action by user title: ${title}`),
|
||||
[title]
|
||||
);
|
||||
|
||||
const onDepartmentClick = useCallback(
|
||||
() => console.log("Department action"),
|
||||
[]
|
||||
);
|
||||
|
||||
|
||||
const onPhoneClick = useCallback(
|
||||
() => console.log("Phone action"),
|
||||
[]
|
||||
() => window.open(`sms:${mobilePhone}`),
|
||||
[mobilePhone]
|
||||
);
|
||||
|
||||
const onEmailClick = useCallback(
|
||||
() => console.log("Email action"),
|
||||
[]
|
||||
() => window.open(`mailto:${email}`),
|
||||
[email]
|
||||
);
|
||||
|
||||
const nameColor = status === 'pending' ? '#A3A9AE' : '#333333';
|
||||
@ -40,7 +77,7 @@ const UserContent = ({ user, history, settings }) => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const headDepartmentStyle = {
|
||||
width: '80px'
|
||||
width: '110px'
|
||||
}
|
||||
|
||||
return (
|
||||
@ -50,20 +87,20 @@ const UserContent = ({ user, history, settings }) => {
|
||||
{status === 'pending' && <Icons.SendClockIcon size='small' isfill={true} color='#3B72A7' />}
|
||||
{status === 'disabled' && <Icons.CatalogSpamIcon size='small' isfill={true} color='#3B72A7' />}
|
||||
</>
|
||||
{headDepartment
|
||||
{title
|
||||
? <Link
|
||||
containerWidth='80px'
|
||||
containerWidth='110px'
|
||||
type='page'
|
||||
title={t('CustomHeadOfDepartment', { headOfDepartment })}
|
||||
title={title}
|
||||
fontSize={12}
|
||||
color={sideInfoColor}
|
||||
onClick={onHeadDepartmentClick}
|
||||
onClick={onUserTitleClick}
|
||||
>
|
||||
{t('CustomHeadOfDepartment', { headOfDepartment })}
|
||||
{title}
|
||||
</Link>
|
||||
: <div style={headDepartmentStyle}></div>
|
||||
}
|
||||
<Link containerWidth='160px' type='action' title={department} fontSize={12} color={sideInfoColor} onClick={onDepartmentClick} >{department}</Link>
|
||||
{groups}
|
||||
<Link type='page' title={mobilePhone} fontSize={12} color={sideInfoColor} onClick={onPhoneClick} >{mobilePhone}</Link>
|
||||
<Link containerWidth='220px' type='page' title={email} fontSize={12} color={sideInfoColor} onClick={onEmailClick} >{email}</Link>
|
||||
</RowContent>
|
||||
|
@ -108,7 +108,7 @@ const SectionBodyContent = props => {
|
||||
</EditButtonWrapper>
|
||||
)}
|
||||
</AvatarWrapper>
|
||||
<ProfileInfo profile={profile} updateProfileCulture={updateProfileCulture} isSelf={isSelf} isAdmin={isAdmin} t={t} cultures={settings.cultures} culture={settings.culture} updateProfileCulture={updateProfileCulture} />
|
||||
<ProfileInfo profile={profile} updateProfileCulture={updateProfileCulture} isSelf={isSelf} isAdmin={isAdmin} t={t} cultures={settings.cultures} culture={settings.culture} />
|
||||
{isSelf && (
|
||||
<ToggleWrapper isSelf={true} >
|
||||
<ToggleContent label={t('Subscriptions')} isOpen={true} >
|
||||
|
@ -1,6 +1,16 @@
|
||||
import React, { useCallback } from "react";
|
||||
import React, { useCallback, useState } from "react";
|
||||
import { connect } from "react-redux";
|
||||
import { Text, IconButton, ContextMenuButton, toastr, utils } from "asc-web-components";
|
||||
import {
|
||||
Text,
|
||||
IconButton,
|
||||
ContextMenuButton,
|
||||
toastr,
|
||||
utils,
|
||||
TextInput,
|
||||
Button,
|
||||
ModalDialog,
|
||||
AvatarEditor
|
||||
} from "asc-web-components";
|
||||
import { withRouter } from "react-router";
|
||||
import { isAdmin, isMe } from "../../../../../store/auth/selectors";
|
||||
import { getUserStatus } from "../../../../../store/people/selectors";
|
||||
@ -9,6 +19,13 @@ import { resendUserInvites } from "../../../../../store/services/api";
|
||||
import { EmployeeStatus } from "../../../../../helpers/constants";
|
||||
import { updateUserStatus } from "../../../../../store/people/actions";
|
||||
import { fetchProfile } from '../../../../../store/profile/actions';
|
||||
import {
|
||||
sendInstructionsToChangePassword,
|
||||
sendInstructionsToChangeEmail,
|
||||
createThumbnailsAvatar,
|
||||
loadAvatar,
|
||||
deleteAvatar
|
||||
} from "../../../../../store/services/api";
|
||||
import styled from 'styled-components';
|
||||
|
||||
const wrapperStyle = {
|
||||
@ -28,24 +45,180 @@ const Header = styled(Text.ContentHeader)`
|
||||
const SectionHeaderContent = props => {
|
||||
const { profile, history, settings, isAdmin, viewer, updateUserStatus, fetchProfile } = props;
|
||||
|
||||
const [newEmailState, setNewEmail] = useState(profile.email);
|
||||
const [dialogState, setDialog] = useState(
|
||||
{
|
||||
visible: false,
|
||||
header: "",
|
||||
body: "",
|
||||
buttons: [],
|
||||
newEmail: profile.email,
|
||||
});
|
||||
const [avatarState, setAvatar] = useState(
|
||||
{
|
||||
tmpFile: "",
|
||||
image: profile.avatarDefault ? "data:image/png;base64," + profile.avatarDefault : null,
|
||||
defaultWidth: 0,
|
||||
defaultHeight: 0
|
||||
});
|
||||
const [avatarEditorState, setAvatarEditorVisible] = useState(false);
|
||||
|
||||
const openAvatarEditor = () => {
|
||||
let avatarDefault = profile.avatarDefault ? "data:image/png;base64," + profile.avatarDefault : null;
|
||||
if (avatarDefault !== null) {
|
||||
let img = new Image();
|
||||
img.onload = function () {
|
||||
setAvatar({
|
||||
defaultWidth: img.width,
|
||||
defaultHeight: img.height
|
||||
})
|
||||
};
|
||||
img.src = avatarDefault;
|
||||
}
|
||||
setAvatarEditorVisible(true);
|
||||
}
|
||||
|
||||
const onLoadFileAvatar = (file) => {
|
||||
let data = new FormData();
|
||||
data.append("file", file);
|
||||
data.append("Autosave", false);
|
||||
loadAvatar(profile.id, data)
|
||||
.then((response) => {
|
||||
var img = new Image();
|
||||
img.onload = function () {
|
||||
var stateCopy = Object.assign({}, avatarState);
|
||||
stateCopy = {
|
||||
tmpFile: response.data,
|
||||
image: response.data,
|
||||
defaultWidth: img.width,
|
||||
defaultHeight: img.height
|
||||
}
|
||||
|
||||
setAvatar(stateCopy);
|
||||
};
|
||||
img.src = response.data;
|
||||
})
|
||||
.catch((error) => toastr.error(error));
|
||||
}
|
||||
|
||||
const onSaveAvatar = (isUpdate, result) => {
|
||||
if (isUpdate) {
|
||||
createThumbnailsAvatar(profile.id, {
|
||||
x: Math.round(result.x * avatarState.defaultWidth - result.width / 2),
|
||||
y: Math.round(result.y * avatarState.defaultHeight - result.height / 2),
|
||||
width: result.width,
|
||||
height: result.height,
|
||||
tmpFile: avatarState.tmpFile
|
||||
})
|
||||
.then((response) => {
|
||||
setAvatarEditorVisible(false);
|
||||
setAvatar({ tmpFile: '' });
|
||||
fetchProfile(profile.id);
|
||||
toastr.success("Success");
|
||||
})
|
||||
.catch((error) => toastr.error(error));
|
||||
} else {
|
||||
deleteAvatar(profile.id)
|
||||
.then((response) => {
|
||||
setAvatarEditorVisible(false);
|
||||
fetchProfile(profile.id);
|
||||
toastr.success("Success");
|
||||
})
|
||||
.catch((error) => toastr.error(error));
|
||||
}
|
||||
}
|
||||
|
||||
const onCloseAvatarEditor = () => setAvatarEditorVisible(false);
|
||||
|
||||
const onEmailChange = event => {
|
||||
const emailRegex = /.+@.+\..+/;
|
||||
const newEmail = (event && event.target.value) || newEmailState;
|
||||
const hasError = !emailRegex.test(newEmail);
|
||||
|
||||
const dialog = {
|
||||
visible: true,
|
||||
header: "Change email",
|
||||
body: (
|
||||
<Text.Body>
|
||||
<span style={{ display: "block", marginBottom: "8px" }}>The activation instructions will be sent to the entered email</span>
|
||||
<TextInput
|
||||
id="new-email"
|
||||
scale={true}
|
||||
isAutoFocussed={true}
|
||||
value={newEmail}
|
||||
onChange={onEmailChange}
|
||||
hasError={hasError}
|
||||
/>
|
||||
</Text.Body>
|
||||
),
|
||||
buttons: [
|
||||
<Button
|
||||
key="SendBtn"
|
||||
label="Send"
|
||||
size="medium"
|
||||
primary={true}
|
||||
onClick={onSendEmailChangeInstructions}
|
||||
isDisabled={hasError}
|
||||
/>
|
||||
],
|
||||
newEmail: newEmail
|
||||
};
|
||||
|
||||
setDialog(dialog);
|
||||
}
|
||||
|
||||
const onSendEmailChangeInstructions = () => {
|
||||
sendInstructionsToChangeEmail(profile.id, newEmailState)
|
||||
.then((res) => {
|
||||
toastr.success(res);
|
||||
})
|
||||
.catch((error) => toastr.error(error))
|
||||
.finally(onDialogClose);
|
||||
}
|
||||
|
||||
const onPasswordChange = () => {
|
||||
const dialog = {
|
||||
visible: true,
|
||||
header: "Change password",
|
||||
body: (
|
||||
<Text.Body>
|
||||
Send the password change instructions to the <a href={`mailto:${profile.email}`}>{profile.email}</a> email address
|
||||
</Text.Body>
|
||||
),
|
||||
buttons: [
|
||||
<Button
|
||||
key="SendBtn"
|
||||
label="Send"
|
||||
size="medium"
|
||||
primary={true}
|
||||
onClick={onSendPasswordChangeInstructions}
|
||||
/>
|
||||
]
|
||||
};
|
||||
|
||||
setDialog(dialog);
|
||||
}
|
||||
|
||||
const onSendPasswordChangeInstructions = () => {
|
||||
sendInstructionsToChangePassword(profile.email)
|
||||
.then((res) => {
|
||||
toastr.success(res);
|
||||
})
|
||||
.catch((error) => toastr.error(error))
|
||||
.finally(onDialogClose);
|
||||
}
|
||||
|
||||
const onDialogClose = () => {
|
||||
const dialog = { visible: false, newEmailState: profile.email };
|
||||
setDialog(dialog);
|
||||
}
|
||||
|
||||
const selectedUserIds = new Array(profile.id);
|
||||
|
||||
const onEditClick = () => {
|
||||
history.push(`${settings.homepage}/edit/${profile.userName}`);
|
||||
};
|
||||
|
||||
const onChangePasswordClick = () => {
|
||||
toastr.success("Context action: Change password");
|
||||
};
|
||||
|
||||
const onChangePhoneClick = () => {
|
||||
toastr.success("Context action: Change phone");
|
||||
};
|
||||
|
||||
const onChangeEmailClick = () => {
|
||||
toastr.success("Context action: Change e-mail");
|
||||
};
|
||||
|
||||
const onDisableClick = () => {
|
||||
updateUserStatus(EmployeeStatus.Disabled, selectedUserIds)
|
||||
.then(() => toastr.success(t("SuccessChangeUserStatus")))
|
||||
@ -53,7 +226,7 @@ const SectionHeaderContent = props => {
|
||||
};
|
||||
|
||||
const onEditPhoto = () => {
|
||||
toastr.success("Context action: Edit Photo");
|
||||
openAvatarEditor();
|
||||
};
|
||||
|
||||
const onEnableClick = () => {
|
||||
@ -81,7 +254,6 @@ const SectionHeaderContent = props => {
|
||||
.catch(error => toastr.error(error));
|
||||
};
|
||||
const getUserContextOptions = (user, viewer, t) => {
|
||||
|
||||
let status = "";
|
||||
|
||||
if (isAdmin || (!isAdmin && isMe(user, viewer.userName))) {
|
||||
@ -98,30 +270,33 @@ const SectionHeaderContent = props => {
|
||||
onClick: onEditClick
|
||||
},
|
||||
{
|
||||
key: "edit-photo",
|
||||
label: t('EditPhoto'),
|
||||
onClick: onEditPhoto
|
||||
key: "change-password",
|
||||
label: t('PasswordChangeButton'),
|
||||
onClick: onPasswordChange
|
||||
},
|
||||
{
|
||||
key: "change-email",
|
||||
label: t('EmailChangeButton'),
|
||||
onClick: onChangeEmailClick
|
||||
onClick: onEmailChange
|
||||
},
|
||||
{
|
||||
key: "change-phone",
|
||||
label: t('PhoneChange'),
|
||||
onClick: onChangePhoneClick
|
||||
key: "edit-photo",
|
||||
label: t('EditPhoto'),
|
||||
onClick: onEditPhoto
|
||||
},
|
||||
{
|
||||
key: "change-password",
|
||||
label: t('PasswordChangeButton'),
|
||||
onClick: onChangePasswordClick
|
||||
},
|
||||
{
|
||||
key: "disable",
|
||||
label: t('DisableUserButton'),
|
||||
onClick: onDisableClick
|
||||
}
|
||||
isMe(user, viewer.userName)
|
||||
? viewer.isOwner
|
||||
? {}
|
||||
: {
|
||||
key: "delete-profile",
|
||||
label: t("DeleteSelfProfile"),
|
||||
onClick: onDeleteProfileClick
|
||||
}
|
||||
: {
|
||||
key: "disable",
|
||||
label: t("DisableUserButton"),
|
||||
onClick: onDisableClick
|
||||
}
|
||||
];
|
||||
case "disabled":
|
||||
return [
|
||||
@ -158,25 +333,38 @@ const SectionHeaderContent = props => {
|
||||
label: t('EditButton'),
|
||||
onClick: onEditClick
|
||||
},
|
||||
{
|
||||
key: "edit-photo",
|
||||
label: t('EditPhoto'),
|
||||
onClick: onEditPhoto
|
||||
},
|
||||
{
|
||||
key: "invite-again",
|
||||
label: t('InviteAgainLbl'),
|
||||
onClick: onInviteAgainClick
|
||||
},
|
||||
{
|
||||
key: "disable",
|
||||
label: t('DisableUserButton'),
|
||||
onClick: onDisableClick
|
||||
key: "edit-photo",
|
||||
label: t('EditPhoto'),
|
||||
onClick: onEditPhoto
|
||||
},
|
||||
!isMe(user, viewer.userName) &&
|
||||
(user.status === EmployeeStatus.Active
|
||||
? {
|
||||
key: "disable",
|
||||
label: t("DisableUserButton"),
|
||||
onClick: onDisableClick
|
||||
}
|
||||
: {
|
||||
key: "enable",
|
||||
label: t("EnableUserButton"),
|
||||
onClick: onEnableClick
|
||||
}),
|
||||
isMe(user, viewer.userName) && {
|
||||
key: "delete-profile",
|
||||
label: t("DeleteSelfProfile"),
|
||||
onClick: onDeleteProfileClick
|
||||
}
|
||||
];
|
||||
default:
|
||||
return [];
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
const { t } = useTranslation();
|
||||
@ -200,7 +388,7 @@ const SectionHeaderContent = props => {
|
||||
{profile.displayName}
|
||||
{profile.isLDAP && ` (${t('LDAPLbl')})`}
|
||||
</Header>
|
||||
{(isAdmin || isMe(viewer, profile.userName)) && (
|
||||
{((isAdmin && !profile.isOwner) || isMe(viewer, profile.userName)) && (
|
||||
<ContextMenuButton
|
||||
directionX="right"
|
||||
title={t('Actions')}
|
||||
@ -211,6 +399,25 @@ const SectionHeaderContent = props => {
|
||||
isDisabled={false}
|
||||
/>
|
||||
)}
|
||||
<ModalDialog
|
||||
visible={dialogState.visible}
|
||||
headerContent={dialogState.header}
|
||||
bodyContent={dialogState.body}
|
||||
footerContent={dialogState.buttons}
|
||||
onClose={onDialogClose}
|
||||
/>
|
||||
<AvatarEditor
|
||||
image={avatarState.image}
|
||||
visible={avatarEditorState}
|
||||
onClose={onCloseAvatarEditor}
|
||||
onSave={onSaveAvatar}
|
||||
onLoadFile={onLoadFileAvatar}
|
||||
headerLabel={t("editAvatar")}
|
||||
chooseFileLabel={t("chooseFileLabel")}
|
||||
unknownTypeError={t("unknownTypeError")}
|
||||
maxSizeFileError={t("maxSizeFileError")}
|
||||
unknownError={t("unknownError")}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
@ -36,5 +36,11 @@
|
||||
"Culture_en": "English (United Kingdom)",
|
||||
"Culture_en-US": "English (United States)",
|
||||
"Culture_ru-RU": "Russian (Russia)",
|
||||
"LearnMore": "Learn more..."
|
||||
"LearnMore": "Learn more...",
|
||||
|
||||
"chooseFileLabel": "Drop file here, or click to select file",
|
||||
"unknownTypeError": "Unknown image file type",
|
||||
"maxSizeFileError": "Maximum file size exceeded",
|
||||
"unknownError": "Error",
|
||||
"editAvatar": "Edit photo"
|
||||
}
|
@ -36,5 +36,11 @@
|
||||
"Culture_en": "Английский (Великобритания)",
|
||||
"Culture_en-US": "Английский (США)",
|
||||
"Culture_ru-RU": "Русский (Россия)",
|
||||
"LearnMore": "Подробнее..."
|
||||
"LearnMore": "Подробнее...",
|
||||
|
||||
"chooseFileLabel": "Перетащите файл сюда или нажмите, чтобы выбрать файл",
|
||||
"unknownTypeError": "Неизвестный тип файла изображения",
|
||||
"maxSizeFileError": "Превышен максимальный размер файла",
|
||||
"unknownError": "Ошибка",
|
||||
"editAvatar": "Изменить фотографию"
|
||||
}
|
@ -42,7 +42,7 @@ export function getPortalPasswordSettings() {
|
||||
export function getUser(userId) {
|
||||
return request({
|
||||
method: "get",
|
||||
url: "/people/@self.json"
|
||||
url: `/people/${userId || "@self"}.json`
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -1802,10 +1802,12 @@ asap@~2.0.6:
|
||||
integrity sha1-5QNHYR1+aQlDIIu9r+vLwvuGbUY=
|
||||
|
||||
"asc-web-components@file:../../../packages/asc-web-components":
|
||||
version "1.0.147"
|
||||
version "1.0.152"
|
||||
dependencies:
|
||||
email-addresses "^3.0.3"
|
||||
moment "^2.24.0"
|
||||
prop-types "^15.7.2"
|
||||
punycode "^2.1.1"
|
||||
rc-tree "^2.1.2"
|
||||
react-autosize-textarea "^7.0.0"
|
||||
react-avatar-editor "^11.0.7"
|
||||
@ -3745,6 +3747,11 @@ elliptic@^6.0.0:
|
||||
minimalistic-assert "^1.0.0"
|
||||
minimalistic-crypto-utils "^1.0.0"
|
||||
|
||||
email-addresses@^3.0.3:
|
||||
version "3.1.0"
|
||||
resolved "https://registry.yarnpkg.com/email-addresses/-/email-addresses-3.1.0.tgz#cabf7e085cbdb63008a70319a74e6136188812fb"
|
||||
integrity sha512-k0/r7GrWVL32kZlGwfPNgB2Y/mMXVTq/decgLczm/j34whdaspNrZO8CnXPf1laaHxI6ptUlsnAxN+UAPw+fzg==
|
||||
|
||||
emoji-regex@^7.0.1, emoji-regex@^7.0.2:
|
||||
version "7.0.3"
|
||||
resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-7.0.3.tgz#933a04052860c85e83c122479c4748a8e4c72156"
|
||||
|
@ -126,6 +126,7 @@ namespace ASC.Api.Settings
|
||||
settings.Timezone = timeZone.Id;
|
||||
settings.UtcOffset = timeZone.GetUtcOffset(DateTime.UtcNow);
|
||||
settings.UtcHoursOffset = settings.UtcOffset.TotalHours;
|
||||
settings.OwnerId = Tenant.OwnerId;
|
||||
}
|
||||
|
||||
return settings;
|
||||
@ -145,7 +146,7 @@ namespace ASC.Api.Settings
|
||||
}
|
||||
|
||||
[Read("timezones")]
|
||||
public IEnumerable<TimeZoneInfo> GetTimeZones()
|
||||
public List<object> GetTimeZones()
|
||||
{
|
||||
var timeZones = TimeZoneInfo.GetSystemTimeZones().ToList();
|
||||
|
||||
@ -154,7 +155,72 @@ namespace ASC.Api.Settings
|
||||
timeZones.Add(TimeZoneInfo.Utc);
|
||||
}
|
||||
|
||||
return timeZones;
|
||||
List<object> listOfTimezones = new List<object>();
|
||||
|
||||
foreach (var tz in timeZones.OrderBy(z => z.BaseUtcOffset))
|
||||
{
|
||||
var displayName = tz.DisplayName;
|
||||
if (tz.StandardName.StartsWith("GMT") && !tz.StandardName.StartsWith("GMT "))
|
||||
{
|
||||
displayName = string.Format("(UTC{0}{1}) ", tz.BaseUtcOffset < TimeSpan.Zero ? "-" : "+", tz.BaseUtcOffset.ToString(@"hh\:mm")) + tz.Id;
|
||||
|
||||
}
|
||||
|
||||
listOfTimezones.Add(new TimezonesModel { Id = tz.Id, DisplayName = displayName });
|
||||
|
||||
}
|
||||
|
||||
return listOfTimezones;
|
||||
}
|
||||
|
||||
[AllowAnonymous]
|
||||
[Read("greetingsettings")]
|
||||
public string GetGreetingSettings()
|
||||
{
|
||||
return Tenant.Name;
|
||||
}
|
||||
|
||||
[Create("greetingsettings")]
|
||||
public object SaveGreetingSettings(GreetingSettingsModel model)
|
||||
{
|
||||
try
|
||||
{
|
||||
SecurityContext.DemandPermissions(Tenant, SecutiryConstants.EditPortalSettings);
|
||||
|
||||
Tenant.Name = model.Title;
|
||||
CoreContext.TenantManager.SaveTenant(Tenant);
|
||||
|
||||
MessageService.Send(MessageAction.GreetingSettingsUpdated);
|
||||
|
||||
return new { Status = 1, Message = Resource.SuccessfullySaveGreetingSettingsMessage };
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
return new { Status = 0, Message = e.Message.HtmlEncode() };
|
||||
}
|
||||
}
|
||||
|
||||
[Create("greetingsettings/restore")]
|
||||
public object RestoreGreetingSettings()
|
||||
{
|
||||
try
|
||||
{
|
||||
SecurityContext.DemandPermissions(Tenant, SecutiryConstants.EditPortalSettings);
|
||||
|
||||
TenantInfoSettings.Load().RestoreDefaultTenantName();
|
||||
//_tenantInfoSettings.Save();
|
||||
|
||||
return new
|
||||
{
|
||||
Status = 1,
|
||||
Message = Resource.SuccessfullySaveGreetingSettingsMessage,
|
||||
CompanyName = CoreContext.TenantManager.GetCurrentTenant().Name
|
||||
};
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
return new { Status = 0, Message = e.Message.HtmlEncode() };
|
||||
}
|
||||
}
|
||||
|
||||
[Read("recalculatequota")]
|
||||
|
7
web/ASC.Web.Api/Models/GreetingSettingsModel.cs
Normal file
7
web/ASC.Web.Api/Models/GreetingSettingsModel.cs
Normal file
@ -0,0 +1,7 @@
|
||||
namespace ASC.Web.Api.Models
|
||||
{
|
||||
public class GreetingSettingsModel
|
||||
{
|
||||
public string Title { get; set; }
|
||||
}
|
||||
}
|
@ -53,6 +53,10 @@ namespace ASC.Api.Settings
|
||||
[DataMember(EmitDefaultValue = false)]
|
||||
public double UtcHoursOffset { get; set; }
|
||||
|
||||
|
||||
[DataMember(EmitDefaultValue = false)]
|
||||
public Guid OwnerId { get; set; }
|
||||
|
||||
public static SettingsWrapper GetSample()
|
||||
{
|
||||
return new SettingsWrapper
|
||||
@ -61,8 +65,9 @@ namespace ASC.Api.Settings
|
||||
Timezone = TimeZoneInfo.Utc.ToString(),
|
||||
TrustedDomains = new List<string> { "mydomain.com" },
|
||||
UtcHoursOffset = -8.5,
|
||||
UtcOffset = TimeSpan.FromHours(-8.5)
|
||||
};
|
||||
UtcOffset = TimeSpan.FromHours(-8.5),
|
||||
OwnerId = Guid.NewGuid()
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
8
web/ASC.Web.Api/Models/TimezonesModel.cs
Normal file
8
web/ASC.Web.Api/Models/TimezonesModel.cs
Normal file
@ -0,0 +1,8 @@
|
||||
namespace ASC.Web.Api.Models
|
||||
{
|
||||
public class TimezonesModel
|
||||
{
|
||||
public string Id { get; set; }
|
||||
public string DisplayName { get; set; }
|
||||
}
|
||||
}
|
@ -72,7 +72,7 @@ const getAvailableModules = (modules, currentUser) => {
|
||||
id: "nav-separator-2"
|
||||
},
|
||||
{
|
||||
id: 'testId',
|
||||
id: 'settings',
|
||||
title: 'Settings',
|
||||
iconName: "SettingsIcon",
|
||||
notifications: 0,
|
||||
@ -102,7 +102,7 @@ function mapStateToProps(state) {
|
||||
hasChanges: state.auth.isAuthenticated && state.auth.isLoaded,
|
||||
availableModules: availableModules,
|
||||
currentUser: state.auth.user,
|
||||
currentModuleId: state.auth.settings.currentModuleId,
|
||||
currentModuleId: state.auth.settings.currentProductId,
|
||||
language: state.auth.user.cultureName || state.auth.settings.culture,
|
||||
};
|
||||
};
|
||||
|
@ -25,9 +25,9 @@ import { login } from "../../../store/auth/actions";
|
||||
import styled from "styled-components";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import i18n from "./i18n";
|
||||
import { welcomePageTitle } from "./../../../helpers/customNames";
|
||||
import { sendInstructionsToChangePassword } from "../../../store/services/api";
|
||||
import SubModalDialog from "./sub-components/modal-dialog";
|
||||
import { getGreetingSettings } from '../../../store/services/api';
|
||||
|
||||
const FormContainer = styled(Container)`
|
||||
margin-top: 70px;
|
||||
@ -106,6 +106,7 @@ const Form = props => {
|
||||
const [email, setEmail] = useState("");
|
||||
const [isDisabled, setIsDisabled] = useState(false);
|
||||
const [isChecked, setIsisChecked] = useState(false);
|
||||
const [greetingTitle, setGreetingTitle] = useState('');
|
||||
|
||||
const onClick = () => {
|
||||
setOpenDialog(true);
|
||||
@ -182,6 +183,13 @@ const Form = props => {
|
||||
};
|
||||
}, [onKeyPress, params, language]);
|
||||
|
||||
useEffect(() => {
|
||||
getGreetingSettings()
|
||||
.then((res) => {
|
||||
setGreetingTitle(res)
|
||||
});
|
||||
}, []);
|
||||
|
||||
const onChangePassword = event => {
|
||||
setPassword(event.target.value);
|
||||
!passwordValid && setPasswordValid(true);
|
||||
@ -212,7 +220,7 @@ const Form = props => {
|
||||
top
|
||||
/>
|
||||
<CardTitle className="card-title">
|
||||
{t("CustomWelcomePageTitle", { welcomePageTitle })}
|
||||
{greetingTitle}
|
||||
</CardTitle>
|
||||
</Card>
|
||||
</Col>
|
||||
|
@ -1,235 +0,0 @@
|
||||
import React from 'react';
|
||||
import { utils } from 'asc-web-components';
|
||||
import { connect } from 'react-redux';
|
||||
import {
|
||||
TreeMenu,
|
||||
TreeNode,
|
||||
Icons
|
||||
} from "asc-web-components";
|
||||
import { setNewSelectedNode } from '../../../../../store/auth/actions';
|
||||
import { withRouter } from "react-router";
|
||||
|
||||
const getItems = data => {
|
||||
return data.map(item => {
|
||||
if (item.children && item.children.length) {
|
||||
return (
|
||||
<TreeNode
|
||||
title={item.title}
|
||||
key={item.key}
|
||||
icon={item.icon && React.createElement(Icons[item.icon], {
|
||||
size: 'scale',
|
||||
isfill: true,
|
||||
color: 'dimgray',
|
||||
})}
|
||||
>
|
||||
{getItems(item.children)}
|
||||
</TreeNode>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<TreeNode
|
||||
key={item.key}
|
||||
title={item.title}
|
||||
icon={item.icon && React.createElement(Icons[item.icon], {
|
||||
size: 'scale',
|
||||
isfill: true,
|
||||
color: 'dimgray',
|
||||
})}
|
||||
/>
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
const getObjectForState = (key, title, subtitle, link) => {
|
||||
const selectedInfo = {
|
||||
selectedKey: key,
|
||||
selectedTitle: title,
|
||||
selectedSubtitle: subtitle,
|
||||
selectedLink: link,
|
||||
};
|
||||
return selectedInfo;
|
||||
}
|
||||
|
||||
const getKeyByLink = (data, linkArr) => {
|
||||
const length = linkArr.length;
|
||||
if (length === 1 || !linkArr[1].length) {
|
||||
const arrLength = data.length;
|
||||
for (let i = 0; i < arrLength; i++) {
|
||||
if (data[i].link === linkArr[0]) {
|
||||
return data[i].children ? data[i].children[0].key : data[i].key;
|
||||
}
|
||||
}
|
||||
} else if (length === 2) {
|
||||
const arrLength = data.length;
|
||||
let key;
|
||||
|
||||
for (let i = 0; i < arrLength; i++) {
|
||||
if (data[i].link === linkArr[0]) {
|
||||
key = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
const selectedArr = data[key].children;
|
||||
const childrenLength = selectedArr.length;
|
||||
for (let i = 0; i < childrenLength; i++) {
|
||||
if (selectedArr[i].link === linkArr[1]) {
|
||||
return selectedArr[i].key;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
return '0-0';
|
||||
}
|
||||
|
||||
class ArticleBodyContent extends React.Component {
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
const { data, selectedKeys, match, history } = props;
|
||||
const fullSettingsUrl = props.match.url;
|
||||
const locationPathname = props.location.pathname;
|
||||
|
||||
if (locationPathname === fullSettingsUrl) {
|
||||
const newPath = match.path + this.getSelectedLinkByKey(selectedKeys[0]);
|
||||
history.push(newPath);
|
||||
return;
|
||||
}
|
||||
|
||||
const fullSettingsUrlLength = fullSettingsUrl.length;
|
||||
|
||||
const resultPath = locationPathname.slice(fullSettingsUrlLength + 1);
|
||||
const arrayOfParams = resultPath.split('/');
|
||||
|
||||
const key = getKeyByLink(data, arrayOfParams);
|
||||
const title = this.getSelectedTitleByKey(key[0]);
|
||||
const subtitle = this.getSelectedTitleByKey(key);
|
||||
const link = this.getSelectedLinkByKey(key);
|
||||
|
||||
this.sendNewSelectedData([key], title, subtitle, link);
|
||||
const path = match.path + link;
|
||||
history.push(path);
|
||||
}
|
||||
|
||||
componentDidUpdate() {
|
||||
const { selectedKeys, match, history } = this.props;
|
||||
const settingsPath = this.getSelectedLinkByKey(selectedKeys[0]);
|
||||
const newPath = match.path + settingsPath;
|
||||
history.push(newPath);
|
||||
}
|
||||
|
||||
shouldComponentUpdate(nextProps) {
|
||||
if (!utils.array.isArrayEqual(nextProps.selectedKeys, this.props.selectedKeys)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!utils.array.isArrayEqual(nextProps.data, this.props.data)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
sendNewSelectedData = (key, title, subtitle, link) => {
|
||||
const { setNewSelectedNode } = this.props;
|
||||
const data = getObjectForState(key, title, subtitle, link);
|
||||
setNewSelectedNode(data);
|
||||
}
|
||||
|
||||
getSelectedTitleByKey = key => {
|
||||
const { data } = this.props;
|
||||
const length = key.length;
|
||||
if (length === 1) {
|
||||
return data[key].title;
|
||||
}
|
||||
else if (length === 3) {
|
||||
return data[key[0]].children[key[2]].title;
|
||||
}
|
||||
}
|
||||
|
||||
getSelectedLinkByKey = key => {
|
||||
const { data } = this.props;
|
||||
const length = key.length;
|
||||
if (length === 1) {
|
||||
return '/' + data[key].link;
|
||||
}
|
||||
else if (length === 3) {
|
||||
return '/' + data[key[0]].link + '/' + data[key[0]].children[key[2]].link;
|
||||
}
|
||||
}
|
||||
onSelect = value => {
|
||||
const { data, selectedKeys } = this.props;
|
||||
|
||||
if (value) {
|
||||
if (utils.array.isArrayEqual(value, selectedKeys)) {
|
||||
|
||||
return;
|
||||
}
|
||||
const selectedKey = value[0];
|
||||
if (selectedKey.length === 3) {
|
||||
const selectedTitle = this.getSelectedTitleByKey(selectedKey[0]);
|
||||
const selectedSubtitle = this.getSelectedTitleByKey(selectedKey);
|
||||
const selectedLink = this.getSelectedLinkByKey(selectedKey);
|
||||
this.sendNewSelectedData(value, selectedTitle, selectedSubtitle, selectedLink);
|
||||
}
|
||||
else if (selectedKey.length === 1 && (selectedKey.toString() !== selectedKeys.toString()[0] || selectedKeys.toString()[2] !== '0')) {
|
||||
const selectedKeys = data[value].children ? [`${value.toString()}-0`] : value;
|
||||
const selectedTitle = this.getSelectedTitleByKey(selectedKey);
|
||||
const selectedSubtitle = this.getSelectedTitleByKey(selectedKeys[0]);
|
||||
const selectedLink = this.getSelectedLinkByKey(selectedKeys[0]);
|
||||
this.sendNewSelectedData(selectedKeys, selectedTitle, selectedSubtitle, selectedLink);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
switcherIcon = obj => {
|
||||
if (obj.isLeaf) {
|
||||
return null;
|
||||
}
|
||||
if (obj.expanded) {
|
||||
return (
|
||||
<Icons.ExpanderDownIcon size="scale" isfill={true} color="dimgray" />
|
||||
);
|
||||
} else {
|
||||
return (
|
||||
<Icons.ExpanderRightIcon size="scale" isfill={true} color="dimgray" />
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
render() {
|
||||
const { data, selectedKeys } = this.props;
|
||||
|
||||
console.log("SettingsTreeMenu", this.props);
|
||||
|
||||
return (
|
||||
<TreeMenu
|
||||
className="people-tree-menu"
|
||||
checkable={false}
|
||||
draggable={false}
|
||||
disabled={false}
|
||||
multiple={false}
|
||||
showIcon={true}
|
||||
defaultExpandAll={true}
|
||||
switcherIcon={this.switcherIcon}
|
||||
onSelect={this.onSelect}
|
||||
selectedKeys={selectedKeys}
|
||||
>
|
||||
{getItems(data)}
|
||||
</TreeMenu>
|
||||
);
|
||||
};
|
||||
};
|
||||
|
||||
function mapStateToProps(state) {
|
||||
return {
|
||||
data: state.auth.settings.settingsTree.list,
|
||||
selectedKeys: state.auth.settings.settingsTree.selectedKey,
|
||||
selectedTitle: state.auth.settings.settingsTree.selectedTitle,
|
||||
selectedSubtitle: state.auth.settings.settingsTree.selectedSubtitle,
|
||||
selectedLink: state.auth.settings.settingsTree.selectedLink,
|
||||
};
|
||||
}
|
||||
|
||||
export default connect(mapStateToProps, { setNewSelectedNode })(withRouter(ArticleBodyContent));
|
@ -0,0 +1,168 @@
|
||||
import React from 'react';
|
||||
import { utils } from 'asc-web-components';
|
||||
import { connect } from 'react-redux';
|
||||
import {
|
||||
TreeMenu,
|
||||
TreeNode,
|
||||
Icons,
|
||||
Link
|
||||
} from "asc-web-components";
|
||||
import { withRouter } from "react-router";
|
||||
import styled from 'styled-components';
|
||||
import { withTranslation } from 'react-i18next';
|
||||
import { getKeyByLink, settingsTree, getSelectedLinkByKey, selectKeyOfTreeElement } from '../../../utils';
|
||||
|
||||
const StyledTreeMenu = styled(TreeMenu)`
|
||||
.inherit-title-link {
|
||||
& > span {
|
||||
font-size: inherit;
|
||||
font-weight: inherit;
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
const getTreeItems = (data, path, t) => {
|
||||
return data.map(item => {
|
||||
if (item.children && item.children.length) {
|
||||
const link = path + getSelectedLinkByKey(item.key, settingsTree);
|
||||
return (
|
||||
<TreeNode
|
||||
title={<Link className='inherit-title-link' href={link}>{t(item.tKey)}</Link>}
|
||||
key={item.key}
|
||||
icon={item.icon && React.createElement(Icons[item.icon], {
|
||||
size: 'scale',
|
||||
isfill: true,
|
||||
color: 'dimgray',
|
||||
})}
|
||||
>
|
||||
{getTreeItems(item.children, path, t)}
|
||||
</TreeNode>
|
||||
);
|
||||
};
|
||||
const link = path + getSelectedLinkByKey(item.key, settingsTree);
|
||||
return (
|
||||
<TreeNode
|
||||
key={item.key}
|
||||
title={<Link className='inherit-title-link' href={link}>{t(item.tKey)}</Link>}
|
||||
icon={item.icon && React.createElement(Icons[item.icon], {
|
||||
size: 'scale',
|
||||
isfill: true,
|
||||
color: 'dimgray',
|
||||
})}
|
||||
/>
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
class ArticleBodyContent extends React.Component {
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
const { match, history, i18n, language } = props;
|
||||
const fullSettingsUrl = props.match.url;
|
||||
const locationPathname = props.location.pathname;
|
||||
i18n.changeLanguage(language);
|
||||
|
||||
if (locationPathname === fullSettingsUrl) {
|
||||
const defaultKey = ['0'];
|
||||
const rightKey = selectKeyOfTreeElement(defaultKey[0], settingsTree)
|
||||
const newPath = match.path + getSelectedLinkByKey(rightKey[0], settingsTree);
|
||||
history.push(newPath);
|
||||
this.state = {
|
||||
selectedKeys: rightKey
|
||||
};
|
||||
return;
|
||||
}
|
||||
|
||||
const fullSettingsUrlLength = fullSettingsUrl.length;
|
||||
const resultPath = locationPathname.slice(fullSettingsUrlLength + 1);
|
||||
const arrayOfParams = resultPath.split('/');
|
||||
|
||||
const key = getKeyByLink(arrayOfParams, settingsTree);
|
||||
const rightKey = selectKeyOfTreeElement(key[0], settingsTree)
|
||||
const link = getSelectedLinkByKey(rightKey[0], settingsTree);
|
||||
|
||||
const path = match.path + link;
|
||||
history.push(path);
|
||||
|
||||
this.state = {
|
||||
selectedKeys: rightKey
|
||||
};
|
||||
}
|
||||
|
||||
componentDidUpdate() {
|
||||
const { selectedKeys } = this.state;
|
||||
const { match, history } = this.props;
|
||||
const settingsPath = getSelectedLinkByKey(selectedKeys[0], settingsTree);
|
||||
const newPath = match.path + settingsPath;
|
||||
history.push(newPath);
|
||||
}
|
||||
|
||||
shouldComponentUpdate(nextProps, nextState) {
|
||||
if (!utils.array.isArrayEqual(nextState.selectedKeys, this.state.selectedKeys)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
onSelect = value => {
|
||||
const { selectedKeys } = this.state;
|
||||
|
||||
if (utils.array.isArrayEqual(value, selectedKeys)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const key = selectKeyOfTreeElement(value, settingsTree);
|
||||
this.setState({ selectedKeys: key });
|
||||
};
|
||||
|
||||
switcherIcon = obj => {
|
||||
if (obj.isLeaf) {
|
||||
return null;
|
||||
}
|
||||
if (obj.expanded) {
|
||||
return (
|
||||
<Icons.ExpanderDownIcon size="scale" isfill={true} color="dimgray" />
|
||||
);
|
||||
} else {
|
||||
return (
|
||||
<Icons.ExpanderRightIcon size="scale" isfill={true} color="dimgray" />
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
render() {
|
||||
const { selectedKeys } = this.state;
|
||||
const { match, t } = this.props;
|
||||
|
||||
console.log("SettingsTreeMenu", this.props);
|
||||
|
||||
return (
|
||||
<StyledTreeMenu
|
||||
className="people-tree-menu"
|
||||
checkable={false}
|
||||
draggable={false}
|
||||
disabled={false}
|
||||
multiple={false}
|
||||
showIcon={true}
|
||||
defaultExpandAll={true}
|
||||
switcherIcon={this.switcherIcon}
|
||||
onSelect={this.onSelect}
|
||||
selectedKeys={selectedKeys}
|
||||
>
|
||||
{getTreeItems(settingsTree, match.path, t)}
|
||||
</StyledTreeMenu>
|
||||
);
|
||||
};
|
||||
};
|
||||
|
||||
function mapStateToProps(state) {
|
||||
return {
|
||||
language: state.auth.user.cultureName,
|
||||
};
|
||||
}
|
||||
|
||||
export default connect(mapStateToProps)(withRouter(withTranslation()(ArticleBodyContent)));
|
@ -1,8 +1,10 @@
|
||||
import React from 'react';
|
||||
import { Text } from 'asc-web-components';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
const ArticleHeaderContent = () => {
|
||||
return <Text.MenuHeader>Settings</Text.MenuHeader>;
|
||||
const { t } = useTranslation();
|
||||
return <Text.MenuHeader>{t('Settings')}</Text.MenuHeader>;
|
||||
}
|
||||
|
||||
export default ArticleHeaderContent;
|
@ -0,0 +1,66 @@
|
||||
import React from "react";
|
||||
import { withRouter } from "react-router";
|
||||
import { Text, utils } from 'asc-web-components';
|
||||
import styled from 'styled-components';
|
||||
import { withTranslation } from 'react-i18next';
|
||||
import { getKeyByLink, settingsTree, getTKeyByKey } from '../../../utils';
|
||||
|
||||
const Header = styled(Text.ContentHeader)`
|
||||
margin-right: 16px;
|
||||
max-width: calc(100vw - 430px);
|
||||
@media ${utils.device.tablet} {
|
||||
max-width: calc(100vw - 96px);
|
||||
}
|
||||
`;
|
||||
|
||||
class SectionHeaderContent extends React.Component {
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
const { match, location } = props;
|
||||
const fullSettingsUrl = match.url;
|
||||
const locationPathname = location.pathname;
|
||||
|
||||
const fullSettingsUrlLength = fullSettingsUrl.length;
|
||||
|
||||
const resultPath = locationPathname.slice(fullSettingsUrlLength + 1);
|
||||
const arrayOfParams = resultPath.split('/');
|
||||
|
||||
const key = getKeyByLink(arrayOfParams, settingsTree);
|
||||
const header = getTKeyByKey(key, settingsTree);
|
||||
this.state = {
|
||||
header
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
componentDidUpdate() {
|
||||
const { match, location } = this.props;
|
||||
const fullSettingsUrl = match.url;
|
||||
const locationPathname = location.pathname;
|
||||
|
||||
|
||||
const fullSettingsUrlLength = fullSettingsUrl.length;
|
||||
const resultPath = locationPathname.slice(fullSettingsUrlLength + 1);
|
||||
const arrayOfParams = resultPath.split('/');
|
||||
|
||||
const key = getKeyByLink(arrayOfParams, settingsTree);
|
||||
const header = getTKeyByKey(key, settingsTree);
|
||||
header !== this.state.header && this.setState({ header });
|
||||
|
||||
}
|
||||
|
||||
render() {
|
||||
const { t } = this.props;
|
||||
const { header } = this.state;
|
||||
|
||||
return (
|
||||
<Header truncate={true}>
|
||||
{t(header)}
|
||||
</Header>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
export default withRouter(withTranslation()(SectionHeaderContent));
|
@ -0,0 +1 @@
|
||||
export { default as SectionHeaderContent } from './Header';
|
@ -0,0 +1,44 @@
|
||||
import React, { Suspense, useEffect } from "react";
|
||||
import { connect } from 'react-redux';
|
||||
import { Loader, PageLayout } from "asc-web-components";
|
||||
import i18n from "../i18n";
|
||||
import { I18nextProvider } from "react-i18next";
|
||||
import {
|
||||
ArticleHeaderContent,
|
||||
ArticleBodyContent
|
||||
} from "./Article";
|
||||
import { SectionHeaderContent } from './Section';
|
||||
import { setCurrentProductId } from '../../../../store/auth/actions';
|
||||
|
||||
const Layout = ({ currentProductId, setCurrentProductId, language, children }) => {
|
||||
|
||||
useEffect(() => {
|
||||
currentProductId !== 'settings' && setCurrentProductId('settings');
|
||||
i18n.changeLanguage(language);
|
||||
}, [language]);
|
||||
|
||||
return (
|
||||
<I18nextProvider i18n={i18n}>
|
||||
<Suspense
|
||||
fallback={<Loader className="pageLoader" type="rombs" size={40} />}
|
||||
>
|
||||
<PageLayout
|
||||
withBodyScroll={true}
|
||||
articleHeaderContent={<ArticleHeaderContent />}
|
||||
articleBodyContent={<ArticleBodyContent />}
|
||||
sectionHeaderContent={<SectionHeaderContent />}
|
||||
sectionBodyContent={children}
|
||||
/>
|
||||
|
||||
</Suspense>
|
||||
</I18nextProvider >
|
||||
);
|
||||
};
|
||||
|
||||
function mapStateToProps(state) {
|
||||
return {
|
||||
language: state.auth.user.cultureName
|
||||
};
|
||||
}
|
||||
|
||||
export default connect(mapStateToProps, { setCurrentProductId })(Layout);
|
@ -1,33 +0,0 @@
|
||||
import React, { lazy } from "react";
|
||||
import { Route, Switch } from "react-router-dom";
|
||||
import { withRouter } from "react-router";
|
||||
import { Scrollbar } from 'asc-web-components'
|
||||
|
||||
const CustomizationSettings = lazy(() => import("../../sub-components/common/customization"));
|
||||
const NotImplementedSettings = lazy(() => import("../../sub-components/notImplementedSettings"));
|
||||
const AccessRight = lazy(() => import("../../sub-components/security/accessRights"));
|
||||
class SectionBodyContent extends React.PureComponent {
|
||||
|
||||
render() {
|
||||
return (
|
||||
<Scrollbar stype="mediumBlack">
|
||||
<Switch>
|
||||
<Route
|
||||
exact
|
||||
path={[`${this.props.match.path}/common/customization`,`${this.props.match.path}/common`, this.props.match.path]}
|
||||
component={CustomizationSettings}
|
||||
/>
|
||||
<Route
|
||||
exact
|
||||
path={`${this.props.match.path}/security/access-rights`}
|
||||
component={AccessRight}
|
||||
/>
|
||||
|
||||
<Route component={NotImplementedSettings} />
|
||||
</Switch>
|
||||
</Scrollbar>
|
||||
);
|
||||
};
|
||||
};
|
||||
|
||||
export default withRouter(SectionBodyContent);
|
@ -1,31 +0,0 @@
|
||||
import React from "react";
|
||||
import { connect } from "react-redux";
|
||||
import { withRouter } from "react-router";
|
||||
import { Text, utils } from 'asc-web-components';
|
||||
import styled from 'styled-components';
|
||||
|
||||
const Header = styled(Text.ContentHeader)`
|
||||
margin-left: 16px;
|
||||
margin-right: 16px;
|
||||
max-width: calc(100vw - 430px);
|
||||
@media ${utils.device.tablet} {
|
||||
max-width: calc(100vw - 96px);
|
||||
}
|
||||
`;
|
||||
|
||||
const SectionHeaderContent = props => {
|
||||
|
||||
return (
|
||||
<Header truncate={true}>
|
||||
{props.header}
|
||||
</Header>
|
||||
);
|
||||
};
|
||||
|
||||
function mapStateToProps(state) {
|
||||
return {
|
||||
header: state.auth.settings.settingsTree.selectedSubtitle
|
||||
};
|
||||
}
|
||||
|
||||
export default connect(mapStateToProps)(withRouter(SectionHeaderContent));
|
@ -1,2 +0,0 @@
|
||||
export { default as SectionHeaderContent } from './Header';
|
||||
export { default as SectionBodyContent } from './Body';
|
@ -0,0 +1,284 @@
|
||||
import React from "react";
|
||||
import { connect } from "react-redux";
|
||||
import { withTranslation } from 'react-i18next';
|
||||
import { FieldContainer, Text, ComboBox, Loader, Button, toastr, Link, TextInput } from "asc-web-components";
|
||||
import { getCultures, setLanguageAndTime, getPortalTimezones } from '../../../../../store/auth/actions';
|
||||
import { getGreetingTitle, setGreetingTitle, restoreGreetingTitle } from '../../../../../store/settings/actions';
|
||||
import styled from 'styled-components';
|
||||
import { Trans } from 'react-i18next';
|
||||
|
||||
const mapCulturesToArray = (cultures, t) => {
|
||||
return cultures.map((culture) => {
|
||||
return { key: culture, label: t(`Culture_${culture}`) };
|
||||
});
|
||||
};
|
||||
|
||||
const mapTimezonesToArray = (timezones) => {
|
||||
return timezones.map((timezone) => {
|
||||
return { key: timezone.id, label: timezone.displayName };
|
||||
});
|
||||
};
|
||||
|
||||
const findSelectedItemByKey = (items, selectedItemKey) => {
|
||||
return items.find(item => item.key === selectedItemKey);
|
||||
}
|
||||
|
||||
const StyledComponent = styled.div`
|
||||
.margin-top {
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
.margin-left {
|
||||
margin-left: 20px;
|
||||
}
|
||||
|
||||
.settings-block {
|
||||
margin-bottom: 70px;
|
||||
}
|
||||
|
||||
.input-width {
|
||||
width: 500px;
|
||||
}
|
||||
|
||||
.dropdown-item-width {
|
||||
& > div:first-child {
|
||||
div:first-child{
|
||||
max-width: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
class Customization extends React.Component {
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
const { portalLanguage, portalTimeZoneId, rawCultures, rawTimezones, t, greetingSettings } = props;
|
||||
const languages = mapCulturesToArray(rawCultures, t);
|
||||
const timezones = mapTimezonesToArray(rawTimezones);
|
||||
|
||||
this.state = {
|
||||
isLoadedData: false,
|
||||
isLoading: false,
|
||||
timezones,
|
||||
timezone: findSelectedItemByKey(timezones, portalTimeZoneId),
|
||||
languages,
|
||||
language: findSelectedItemByKey(languages, portalLanguage),
|
||||
greetingTitle: greetingSettings,
|
||||
isLoadingGreeting: false,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
componentDidMount() {
|
||||
const { getCultures, portalLanguage, portalTimeZoneId, t, getPortalTimezones, getGreetingTitle } = this.props;
|
||||
const { timezones, languages, greetingTitle } = this.state;
|
||||
|
||||
if (!greetingTitle.length) {
|
||||
getGreetingTitle()
|
||||
.then(() => this.setState({ greetingTitle: this.props.greetingSettings }));
|
||||
}
|
||||
|
||||
if (!timezones.length && !languages.length) {
|
||||
let languages;
|
||||
getCultures()
|
||||
.then(() => {
|
||||
languages = mapCulturesToArray(this.props.rawCultures, t);
|
||||
})
|
||||
.then(() => getPortalTimezones())
|
||||
.then(() => {
|
||||
const timezones = mapTimezonesToArray(this.props.rawTimezones);
|
||||
const timezone = findSelectedItemByKey(timezones, portalTimeZoneId);
|
||||
const language = findSelectedItemByKey(languages, portalLanguage);
|
||||
|
||||
this.setState({ languages, language, timezones, timezone, isLoadedData: true });
|
||||
});
|
||||
}
|
||||
else {
|
||||
this.setState({ isLoadedData: true });
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
onLanguageSelect = (language) => {
|
||||
this.setState({ language })
|
||||
};
|
||||
|
||||
onTimezoneSelect = (timezone) => {
|
||||
this.setState({ timezone })
|
||||
};
|
||||
|
||||
onSaveLngTZSettings = () => {
|
||||
const { setLanguageAndTime, t } = this.props;
|
||||
this.setState({ isLoading: true }, function () {
|
||||
setLanguageAndTime(this.state.language.key, this.state.timezone.key)
|
||||
.then(() => {
|
||||
this.setState({ isLoading: false })
|
||||
toastr.success(t('SuccessfullySaveSettingsMessage'));
|
||||
});
|
||||
})
|
||||
}
|
||||
|
||||
onChangeGreetingTitle = (e) => {
|
||||
this.setState({ greetingTitle: e.target.value })
|
||||
};
|
||||
|
||||
onSaveGreetingSettings = () => {
|
||||
const { setGreetingTitle, t } = this.props;
|
||||
this.setState({ isLoadingGreeting: true }, function () {
|
||||
setGreetingTitle(this.state.greetingTitle)
|
||||
.then(() => {
|
||||
this.setState({ isLoadingGreeting: false })
|
||||
toastr.success(t('SuccessfullySaveGreetingSettingsMessage'));
|
||||
});
|
||||
})
|
||||
}
|
||||
|
||||
onRestoreGreetingSettings = () => {
|
||||
const { restoreGreetingTitle, t } = this.props;
|
||||
this.setState({ isLoadingGreeting: true }, function () {
|
||||
restoreGreetingTitle()
|
||||
.then(() => {
|
||||
this.setState({
|
||||
isLoadingGreeting: false,
|
||||
greetingTitle: this.props.greetingSettings
|
||||
})
|
||||
toastr.success(t('SuccessfullySaveGreetingSettingsMessage'));
|
||||
});
|
||||
})
|
||||
}
|
||||
|
||||
render() {
|
||||
const { t, i18n } = this.props;
|
||||
const { isLoadedData, languages, language, isLoading, timezones, timezone, greetingTitle, isLoadingGreeting } = this.state;
|
||||
const supportEmail = "documentation@onlyoffice.com";
|
||||
const tooltipLanguage =
|
||||
<Text.Body fontSize={13}>
|
||||
<Trans i18nKey="NotFoundLanguage" i18n={i18n}>
|
||||
"In case you cannot find your language in the list of the
|
||||
available ones, feel free to write to us at
|
||||
<Link href="mailto:documentation@onlyoffice.com" isHovered={true}>
|
||||
{{ supportEmail }}
|
||||
</Link> to take part in the translation and get up to 1 year free of
|
||||
charge."
|
||||
</Trans>
|
||||
{" "}
|
||||
<Link isHovered={true} href="https://helpcenter.onlyoffice.com/ru/guides/become-translator.aspx">{t("LearnMore")}</Link>
|
||||
</Text.Body>
|
||||
|
||||
console.log("CustomizationSettings render");
|
||||
return (
|
||||
!isLoadedData ?
|
||||
<Loader className="pageLoader" type="rombs" size={40} />
|
||||
: <>
|
||||
<StyledComponent>
|
||||
<div className='settings-block'>
|
||||
<Text.Body fontSize={16}>{t('StudioTimeLanguageSettings')}</Text.Body>
|
||||
<FieldContainer
|
||||
id='fieldContainerLanguage'
|
||||
className='margin-top'
|
||||
labelText={`${t("Language")}:`}
|
||||
tooltipContent={tooltipLanguage}
|
||||
isVertical={true}>
|
||||
<ComboBox
|
||||
id='comboBoxLanguage'
|
||||
options={languages}
|
||||
selectedOption={language}
|
||||
onSelect={this.onLanguageSelect}
|
||||
isDisabled={isLoading}
|
||||
noBorder={false}
|
||||
scaled={false}
|
||||
scaledOptions={true}
|
||||
dropDownMaxHeight={300}
|
||||
size='huge'
|
||||
/>
|
||||
</FieldContainer>
|
||||
|
||||
<FieldContainer
|
||||
id='fieldContainerTimezone'
|
||||
labelText={`${t("TimeZone")}:`}
|
||||
isVertical={true}>
|
||||
<ComboBox
|
||||
id='comboBoxTimezone'
|
||||
options={timezones}
|
||||
selectedOption={timezone}
|
||||
onSelect={this.onTimezoneSelect}
|
||||
isDisabled={isLoading}
|
||||
noBorder={false}
|
||||
scaled={false}
|
||||
scaledOptions={true}
|
||||
dropDownMaxHeight={300}
|
||||
size='huge'
|
||||
className='dropdown-item-width'
|
||||
/>
|
||||
</FieldContainer>
|
||||
<Button
|
||||
id='btnSaveLngTZ'
|
||||
className='margin-top'
|
||||
primary={true}
|
||||
size='medium'
|
||||
label={t('SaveButton')}
|
||||
isLoading={isLoading}
|
||||
onClick={this.onSaveLngTZSettings}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className='settings-block'>
|
||||
<Text.Body fontSize={16}>{t('GreetingSettingsTitle')}</Text.Body>
|
||||
<FieldContainer
|
||||
id='fieldContainerWelcomePage'
|
||||
className='margin-top'
|
||||
labelText={`${t("GreetingTitle")}:`}
|
||||
isVertical={true}>
|
||||
<TextInput
|
||||
className='input-width'
|
||||
scale={true}
|
||||
value={greetingTitle}
|
||||
onChange={this.onChangeGreetingTitle}
|
||||
isDisabled={isLoadingGreeting}
|
||||
/>
|
||||
|
||||
</FieldContainer>
|
||||
|
||||
<Button
|
||||
id='btnSaveGreetingSetting'
|
||||
className='margin-top'
|
||||
primary={true}
|
||||
size='medium'
|
||||
label={t('SaveButton')}
|
||||
isLoading={isLoadingGreeting}
|
||||
onClick={this.onSaveGreetingSettings}
|
||||
/>
|
||||
|
||||
<Button
|
||||
id='btnRestoreToDefault'
|
||||
className='margin-top margin-left'
|
||||
size='medium'
|
||||
label={t('RestoreDefaultButton')}
|
||||
isDisabled={isLoadingGreeting}
|
||||
onClick={this.onRestoreGreetingSettings}
|
||||
/>
|
||||
</div>
|
||||
|
||||
</StyledComponent>
|
||||
|
||||
</>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
function mapStateToProps(state) {
|
||||
return {
|
||||
portalLanguage: state.auth.settings.culture,
|
||||
portalTimeZoneId: state.auth.settings.timezone,
|
||||
language: state.auth.user.cultureName || state.auth.settings.culture,
|
||||
rawTimezones: state.auth.settings.timezones,
|
||||
rawCultures: state.auth.settings.cultures,
|
||||
greetingSettings: state.settings.greetingSettings,
|
||||
};
|
||||
}
|
||||
|
||||
export default connect(mapStateToProps, {
|
||||
getCultures, setLanguageAndTime, getPortalTimezones,
|
||||
getGreetingTitle, setGreetingTitle, restoreGreetingTitle
|
||||
})(withTranslation()(Customization));
|
@ -0,0 +1,23 @@
|
||||
import React, { lazy } from "react";
|
||||
import { Route, Switch } from "react-router-dom";
|
||||
import { withRouter } from "react-router";
|
||||
|
||||
const CustomizationSettings = lazy(() => import("./customization"));
|
||||
|
||||
const Common = ({ match }) => {
|
||||
const basePath = '/settings/common';
|
||||
|
||||
return (
|
||||
<Switch>
|
||||
<Route
|
||||
exact
|
||||
path={[`${basePath}/customization`, '/common', match.path]}
|
||||
component={CustomizationSettings}
|
||||
/>
|
||||
|
||||
</Switch>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
export default withRouter(Common);
|
@ -0,0 +1,469 @@
|
||||
import React, { Component } from "react";
|
||||
import PropTypes from "prop-types";
|
||||
import { connect } from "react-redux";
|
||||
import { withRouter } from "react-router";
|
||||
import i18n from "../../i18n";
|
||||
import { I18nextProvider, withTranslation } from "react-i18next";
|
||||
import styled from "styled-components";
|
||||
import {
|
||||
changeAdmins,
|
||||
getListAdmins,
|
||||
getListUsers,
|
||||
getUserById
|
||||
} from "../../../../../store/settings/actions";
|
||||
import {
|
||||
getUsers,
|
||||
getAdmins,
|
||||
getSelectorOptions
|
||||
} from "../../../../../store/settings/selectors";
|
||||
import {
|
||||
Text,
|
||||
Avatar,
|
||||
ToggleContent,
|
||||
Row,
|
||||
RowContent,
|
||||
RowContainer,
|
||||
Link,
|
||||
RadioButtonGroup,
|
||||
Paging,
|
||||
SelectorAddButton,
|
||||
IconButton,
|
||||
AdvancedSelector,
|
||||
toastr,
|
||||
RequestLoader
|
||||
} from "asc-web-components";
|
||||
|
||||
const MainContainer = styled.div`
|
||||
padding: 16px 16px 16px 24px;
|
||||
width: 100%;
|
||||
|
||||
.page_loader {
|
||||
position: fixed;
|
||||
left: 48%;
|
||||
}
|
||||
`;
|
||||
|
||||
const ProjectsContainer = styled.div`
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
flex-direction: row;
|
||||
flex-wrap: wrap;
|
||||
|
||||
.display-block {
|
||||
display: block;
|
||||
}
|
||||
|
||||
div label:not(:first-child) {
|
||||
margin: 0;
|
||||
}
|
||||
`;
|
||||
|
||||
const RadioButtonContainer = styled.div`
|
||||
margin-right: 150px;
|
||||
margin-bottom: 16px;
|
||||
width: 310px;
|
||||
`;
|
||||
|
||||
const HeaderContainer = styled.div`
|
||||
margin-bottom: 16px;
|
||||
`;
|
||||
|
||||
const BodyContainer = styled.div`
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
flex-direction: row;
|
||||
flex-wrap: wrap;
|
||||
margin-bottom: 24px;
|
||||
`;
|
||||
|
||||
const AvatarContainer = styled.div`
|
||||
display: flex;
|
||||
width: 330px;
|
||||
height: 120px;
|
||||
margin-right: 130px;
|
||||
margin-bottom: 24px;
|
||||
padding: 8px;
|
||||
border: 1px solid lightgrey;
|
||||
|
||||
.avatar_wrapper {
|
||||
width: 100px;
|
||||
height: 100px;
|
||||
}
|
||||
|
||||
.avatar_body {
|
||||
margin-left: 24px;
|
||||
max-width: 190px;
|
||||
word-wrap: break-word;
|
||||
overflow: hidden;
|
||||
}
|
||||
`;
|
||||
|
||||
const ToggleContentContainer = styled.div`
|
||||
.toggle_content {
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
.wrapper {
|
||||
margin-top: 16px;
|
||||
}
|
||||
|
||||
.advanced-selector {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.selector-button {
|
||||
max-width: 34px;
|
||||
}
|
||||
`;
|
||||
|
||||
const ProjectsBody = styled.div`
|
||||
width: 280px;
|
||||
`;
|
||||
|
||||
class PureAccessRights extends Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
showSelector: false,
|
||||
options: [],
|
||||
isLoading: false,
|
||||
selectedOptions: []
|
||||
};
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
const {
|
||||
getListAdmins,
|
||||
getListUsers,
|
||||
getUserById,
|
||||
ownerId,
|
||||
productId
|
||||
} = this.props;
|
||||
|
||||
getUserById(ownerId).catch(error => {
|
||||
toastr.error(error);
|
||||
//console.log("accessRights getUserById", error);
|
||||
});
|
||||
|
||||
getListUsers().catch(error => {
|
||||
toastr.error(error);
|
||||
//console.log("accessRights getListAdmins", error);
|
||||
});
|
||||
|
||||
getListAdmins(productId).catch(error => {
|
||||
toastr.error(error);
|
||||
//console.log("accessRights getListAdmins", error);
|
||||
});
|
||||
}
|
||||
|
||||
onChangeAdmin = (userId, isAdmin) => {
|
||||
this.onLoading(true);
|
||||
const { changeAdmins, productId } = this.props;
|
||||
|
||||
changeAdmins(userId, productId, isAdmin)
|
||||
.catch(error => {
|
||||
toastr.error("accessRights onChangeAdmin", error);
|
||||
//console.log("accessRights onChangeAdmin", error)
|
||||
})
|
||||
.finally(() => this.onLoading(false));
|
||||
};
|
||||
|
||||
onShowGroupSelector = () =>
|
||||
this.setState({
|
||||
showSelector: !this.state.showSelector,
|
||||
options: this.props.options,
|
||||
selectedOptions: []
|
||||
});
|
||||
|
||||
onSelect = selected => {
|
||||
selected.map(user => this.onChangeAdmin(user.key, true));
|
||||
this.onShowGroupSelector();
|
||||
this.setState({ selectedOptions: selected });
|
||||
};
|
||||
|
||||
onSearchUsers = template => {
|
||||
this.onLoading(true);
|
||||
this.setState({
|
||||
options: this.filterUserSelectorOptions(this.props.options, template)
|
||||
});
|
||||
this.onLoading(false);
|
||||
};
|
||||
|
||||
filterUserSelectorOptions = (options, template) =>
|
||||
options.filter(option => option.label.indexOf(template) > -1);
|
||||
|
||||
onLoading = status => {
|
||||
console.log("onLoading status", status);
|
||||
this.setState({ isLoading: status });
|
||||
};
|
||||
|
||||
render() {
|
||||
const { t, owner, admins } = this.props;
|
||||
const { showSelector, options, selectedOptions, isLoading } = this.state;
|
||||
const OwnerOpportunities = t("AccessRightsOwnerOpportunities").split("|");
|
||||
|
||||
const countItems = [
|
||||
{ key: 25, label: t("CountPerPage", { count: 25 }) },
|
||||
{ key: 50, label: t("CountPerPage", { count: 50 }) },
|
||||
{ key: 100, label: t("CountPerPage", { count: 100 }) }
|
||||
];
|
||||
|
||||
console.log("isLoading", isLoading);
|
||||
|
||||
return (
|
||||
<MainContainer>
|
||||
<RequestLoader
|
||||
visible={isLoading}
|
||||
zIndex={256}
|
||||
loaderSize={16}
|
||||
loaderColor={"#999"}
|
||||
label={`${t("LoadingProcessing")} ${t("LoadingDescription")}`}
|
||||
fontSize={12}
|
||||
fontColor={"#999"}
|
||||
className="page_loader"
|
||||
/>
|
||||
<HeaderContainer>
|
||||
<Text.Body fontSize={18}>{t("PortalOwner")}</Text.Body>
|
||||
</HeaderContainer>
|
||||
|
||||
<BodyContainer>
|
||||
<AvatarContainer>
|
||||
<Avatar
|
||||
className="avatar_wrapper"
|
||||
size="big"
|
||||
role="owner"
|
||||
userName={owner.userName}
|
||||
source={owner.avatar}
|
||||
/>
|
||||
<div className="avatar_body">
|
||||
<Text.Body className="avatar_text" fontSize={16} isBold={true}>
|
||||
{owner.displayName}
|
||||
</Text.Body>
|
||||
{owner.groups &&
|
||||
owner.groups.map(group => (
|
||||
<Link fontSize={12} key={group.id} href={owner.profileUrl}>
|
||||
{group.name}
|
||||
</Link>
|
||||
))}
|
||||
</div>
|
||||
</AvatarContainer>
|
||||
<ProjectsBody>
|
||||
<Text.Body className="portal_owner" fontSize={12}>
|
||||
{t("AccessRightsOwnerCan")}:
|
||||
</Text.Body>
|
||||
<Text.Body fontSize={12}>
|
||||
{OwnerOpportunities.map((item, key) => (
|
||||
<li key={key}>{item};</li>
|
||||
))}
|
||||
</Text.Body>
|
||||
</ProjectsBody>
|
||||
</BodyContainer>
|
||||
|
||||
<ToggleContentContainer>
|
||||
<ToggleContent
|
||||
className="toggle_content"
|
||||
label={t("AdminSettings")}
|
||||
isOpen={true}
|
||||
>
|
||||
<SelectorAddButton
|
||||
className="selector-button"
|
||||
isDisabled={isLoading}
|
||||
//title={showGroupSelectorButtonTitle}
|
||||
onClick={this.onShowGroupSelector}
|
||||
/>
|
||||
<div className="advanced-selector">
|
||||
<AdvancedSelector
|
||||
displayType="dropdown"
|
||||
isOpen={showSelector}
|
||||
placeholder="placeholder"
|
||||
options={options}
|
||||
onSearchChanged={this.onSearchUsers}
|
||||
//groups={groups}
|
||||
isMultiSelect={true}
|
||||
buttonLabel="Add members"
|
||||
onSelect={this.onSelect}
|
||||
onCancel={this.onShowGroupSelector}
|
||||
onAddNewClick={() => console.log("onAddNewClick")}
|
||||
selectAllLabel="selectorSelectAllText"
|
||||
selectedOptions={selectedOptions}
|
||||
/>
|
||||
</div>
|
||||
<div className="wrapper">
|
||||
<RowContainer manualHeight={`${admins.length * 50}px`}>
|
||||
{admins.map(user => {
|
||||
const element = (
|
||||
<Avatar
|
||||
size="small"
|
||||
role="admin"
|
||||
userName={user.displayName}
|
||||
source={user.avatarSmall}
|
||||
/>
|
||||
);
|
||||
const nameColor =
|
||||
user.status === "pending" ? "#A3A9AE" : "#333333";
|
||||
|
||||
return (
|
||||
<Row
|
||||
key={user.id}
|
||||
status={user.status}
|
||||
data={user}
|
||||
element={element}
|
||||
>
|
||||
<RowContent disableSideInfo={true}>
|
||||
<Link
|
||||
containerWidth="120px"
|
||||
type="page"
|
||||
title={user.displayName}
|
||||
isBold={true}
|
||||
fontSize={15}
|
||||
color={nameColor}
|
||||
href={user.profileUrl}
|
||||
>
|
||||
{user.displayName}
|
||||
</Link>
|
||||
|
||||
<div style={{ maxWidth: 120 }} />
|
||||
<div style={{ marginLeft: "60px" }}>
|
||||
<IconButton
|
||||
size="16"
|
||||
isDisabled={isLoading}
|
||||
onClick={this.onChangeAdmin.bind(
|
||||
this,
|
||||
user.id,
|
||||
false
|
||||
)}
|
||||
iconName={"CatalogTrashIcon"}
|
||||
isFill={true}
|
||||
isClickable={false}
|
||||
/>
|
||||
</div>
|
||||
</RowContent>
|
||||
</Row>
|
||||
);
|
||||
})}
|
||||
</RowContainer>
|
||||
</div>
|
||||
{admins.length > 25 ? (
|
||||
<div className="wrapper">
|
||||
<Paging
|
||||
previousLabel={t("PreviousPage")}
|
||||
nextLabel={t("NextPage")}
|
||||
openDirection="top"
|
||||
displayItems={false}
|
||||
countItems={countItems}
|
||||
selectedPageItem={{ label: "1 of 1" }}
|
||||
selectedCountItem={{ label: "25 per page" }}
|
||||
previousAction={() => console.log("previousAction")}
|
||||
nextAction={() => console.log("nextAction")}
|
||||
onSelectPage={a => console.log(a)}
|
||||
onSelectCount={a => console.log(a)}
|
||||
//pageItems={pageItems}
|
||||
//disablePrevious={!filter.hasPrev()}
|
||||
//disableNext={!filter.hasNext()}
|
||||
/>
|
||||
</div>
|
||||
) : null}
|
||||
</ToggleContent>
|
||||
|
||||
<ToggleContent
|
||||
className="toggle_content"
|
||||
label={t("People")}
|
||||
isOpen={true}
|
||||
>
|
||||
<ProjectsContainer>
|
||||
<RadioButtonContainer>
|
||||
<Text.Body>
|
||||
{t("AccessRightsAccessToProduct", { product: t("People") })}:
|
||||
</Text.Body>
|
||||
<RadioButtonGroup
|
||||
name="selectGroup"
|
||||
selected="allUsers"
|
||||
options={[
|
||||
{
|
||||
value: "allUsers",
|
||||
label: t("AccessRightsAllUsers", {
|
||||
users: t("Employees")
|
||||
})
|
||||
},
|
||||
{
|
||||
value: "usersFromTheList",
|
||||
label: t("AccessRightsUsersFromList", {
|
||||
users: t("Employees")
|
||||
})
|
||||
}
|
||||
]}
|
||||
className="display-block"
|
||||
/>
|
||||
</RadioButtonContainer>
|
||||
<ProjectsBody>
|
||||
<Text.Body className="projects_margin" fontSize={12}>
|
||||
{t("AccessRightsProductUsersCan", {
|
||||
category: t("People")
|
||||
})}
|
||||
</Text.Body>
|
||||
<Text.Body fontSize={12}>
|
||||
<li>{t("ViewProfilesAndGroups")}</li>
|
||||
</Text.Body>
|
||||
</ProjectsBody>
|
||||
</ProjectsContainer>
|
||||
</ToggleContent>
|
||||
</ToggleContentContainer>
|
||||
</MainContainer>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const AccessRightsContainer = withTranslation()(PureAccessRights);
|
||||
|
||||
const AccessRights = props => {
|
||||
const { language } = props;
|
||||
|
||||
i18n.changeLanguage(language);
|
||||
|
||||
return (
|
||||
<I18nextProvider i18n={i18n}>
|
||||
<AccessRightsContainer {...props} />
|
||||
</I18nextProvider>
|
||||
);
|
||||
};
|
||||
|
||||
function mapStateToProps(state) {
|
||||
const { ownerId } = state.auth.settings;
|
||||
const { admins, users, owner } = state.settings;
|
||||
const arrayUsers = getUsers(users, ownerId);
|
||||
const filterArrayUsers = getAdmins(arrayUsers);
|
||||
const options = getSelectorOptions(filterArrayUsers);
|
||||
|
||||
return {
|
||||
users: filterArrayUsers,
|
||||
admins: getUsers(admins, ownerId),
|
||||
productId: state.auth.modules[0].id,
|
||||
owner,
|
||||
ownerId,
|
||||
options
|
||||
};
|
||||
}
|
||||
|
||||
AccessRights.defaultProps = {
|
||||
users: [],
|
||||
admins: [],
|
||||
productId: "",
|
||||
ownerId: "",
|
||||
owner: {},
|
||||
options: []
|
||||
};
|
||||
|
||||
AccessRights.propTypes = {
|
||||
users: PropTypes.arrayOf(PropTypes.object),
|
||||
admins: PropTypes.arrayOf(PropTypes.object),
|
||||
productId: PropTypes.string,
|
||||
ownerId: PropTypes.string,
|
||||
owner: PropTypes.object,
|
||||
options: PropTypes.arrayOf(PropTypes.object)
|
||||
};
|
||||
|
||||
export default connect(
|
||||
mapStateToProps,
|
||||
{ getUserById, changeAdmins, getListAdmins, getListUsers }
|
||||
)(withRouter(AccessRights));
|
@ -0,0 +1,23 @@
|
||||
import React, { lazy } from "react";
|
||||
import { Route, Switch } from "react-router-dom";
|
||||
import { withRouter } from "react-router";
|
||||
|
||||
const AccessRightsSettings = lazy(() => import("./accessRights"));
|
||||
|
||||
const Security = () => {
|
||||
const basePath = '/settings/security';
|
||||
|
||||
return (
|
||||
<Switch>
|
||||
<Route
|
||||
exact
|
||||
path={[`${basePath}/accessrights`, basePath]}
|
||||
component={AccessRightsSettings}
|
||||
/>
|
||||
|
||||
</Switch>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
export default withRouter(Security);
|
@ -1,31 +1,38 @@
|
||||
import React, { Suspense } from "react";
|
||||
import { Loader, PageLayout } from "asc-web-components";
|
||||
import i18n from "./i18n";
|
||||
import { I18nextProvider } from "react-i18next";
|
||||
import {
|
||||
ArticleHeaderContent,
|
||||
ArticleBodyContent
|
||||
} from "./Article";
|
||||
import { SectionHeaderContent, SectionBodyContent } from './Section';
|
||||
import React, { lazy } from "react";
|
||||
import { Route, Switch, Redirect } from "react-router-dom";
|
||||
import { withRouter } from "react-router";
|
||||
import Layout from './Layout';
|
||||
|
||||
const CommonSettings = lazy(() => import("./categories/common"));
|
||||
const SecuritySettings = lazy(() => import("./categories/security"));
|
||||
|
||||
|
||||
const Settings = () => {
|
||||
console.log("Settings render");
|
||||
|
||||
const basePath = '/settings';
|
||||
return (
|
||||
<I18nextProvider i18n={i18n}>
|
||||
<Suspense
|
||||
fallback={<Loader className="pageLoader" type="rombs" size={40} />}
|
||||
>
|
||||
<PageLayout
|
||||
withBodyScroll={false}
|
||||
articleHeaderContent={<ArticleHeaderContent />}
|
||||
articleBodyContent={<ArticleBodyContent />}
|
||||
sectionHeaderContent={<SectionHeaderContent />}
|
||||
sectionBodyContent={<SectionBodyContent />}
|
||||
<Layout key='1'>
|
||||
<Switch>
|
||||
|
||||
<Route
|
||||
path={`${basePath}/security`}
|
||||
component={SecuritySettings}
|
||||
/>
|
||||
|
||||
</Suspense>
|
||||
</I18nextProvider >
|
||||
<Route
|
||||
path={[`${basePath}/common`, basePath]}
|
||||
component={CommonSettings}
|
||||
/>
|
||||
|
||||
<Redirect
|
||||
to={{
|
||||
pathname: "/error/404",
|
||||
}}
|
||||
/>
|
||||
|
||||
</Switch>
|
||||
</Layout>
|
||||
);
|
||||
};
|
||||
|
||||
export default Settings;
|
||||
export default withRouter(Settings);
|
||||
|
@ -16,6 +16,38 @@
|
||||
"AccessRightsAllUsers": "All {{users}}",
|
||||
"AccessRightsUsersFromList": "{{users}} from the list",
|
||||
"Employees": "users",
|
||||
"StudioTimeLanguageSettings": "Language and Time Zone Settings",
|
||||
"Language": "Language",
|
||||
"TimeZone": "Time Zone",
|
||||
"SaveButton": "Save",
|
||||
"SuccessfullySaveSettingsMessage": "Settings have been successfully updated",
|
||||
"NotFoundLanguage": "In case you cannot find your language in the list of the available ones, feel free to write to us at <1>{{supportEmail}}</1> to take part in the translation and get up to 1 year free of charge.",
|
||||
"Settings": "Settings",
|
||||
"PreviousPage": "Previous",
|
||||
"NextPage": "Next",
|
||||
"ManagementCategoryCommon": "Common",
|
||||
"Customization": "Customization",
|
||||
"ProductsAndInstruments": "Modules & tools",
|
||||
"WhiteLabel": "White label",
|
||||
"ManagementCategorySecurity": "Security",
|
||||
"PortalSecurity": "Portal Access",
|
||||
"AccessRights": "Access Rights",
|
||||
"LoginHistoryNav": "Login History",
|
||||
"AuditTrailNav": "Audit Trail",
|
||||
"DataManagement": "Data Management",
|
||||
"Migration": "Migration",
|
||||
"Backup": "Backup",
|
||||
"DeactivationDeletionPortal": "Portal Deactivation/Deletion",
|
||||
"ManagementCategoryIntegration": "Integration",
|
||||
"ThirdPartyAuthorization": "Third Party Authorization",
|
||||
"SmtpSettings": "SMTP Settings",
|
||||
"ManagementCategoryStatistic": "Statistics",
|
||||
"LoadingProcessing": "Loading...",
|
||||
"LoadingDescription": "Please wait...",
|
||||
"GreetingSettingsTitle": "Welcome Page Settings",
|
||||
"GreetingTitle": "Title",
|
||||
"RestoreDefaultButton": "Restore to Default",
|
||||
"SuccessfullySaveGreetingSettingsMessage": "Welcome Page settings have been successfully saved",
|
||||
|
||||
|
||||
|
||||
@ -31,5 +63,10 @@
|
||||
"Sample": "Sample",
|
||||
"ManageOwnMailAccounts": "Manage own mail accounts, receive and send letters",
|
||||
"ManageOwnMailSettings": "Manage own Mail settings",
|
||||
"ManageTheTagsAndAddressBook": "Manage the tags and address book"
|
||||
"ManageTheTagsAndAddressBook": "Manage the tags and address book",
|
||||
"Culture_en": "English (United Kingdom)",
|
||||
"Culture_en-US": "English (United States)",
|
||||
"Culture_ru-RU": "Russian (Russia)",
|
||||
"LearnMore": "Learn more...",
|
||||
"CountPerPage": "{{count}} per page"
|
||||
}
|
@ -15,7 +15,38 @@
|
||||
"AccessRightsAllUsers": "Всех участников со статусом {{users}}",
|
||||
"AccessRightsUsersFromList": "Участников со статусом Пользователи из списка",
|
||||
"Employees": "Пользователи",
|
||||
|
||||
"StudioTimeLanguageSettings": "Настройки языка и часового пояса",
|
||||
"Language": "Язык",
|
||||
"TimeZone": "Часовой пояс",
|
||||
"SaveButton": "Сохранить",
|
||||
"SuccessfullySaveSettingsMessage": "Настройки успешно обновлены",
|
||||
"NotFoundLanguage": "Если Вы не можете найти свой язык в списке доступных, Вы всегда можете написать нам по адресу <1>{{supportEmail}}</1>, чтобы принять участие в переводе и получить до 1 года бесплатного использования.",
|
||||
"Settings": "Настройки",
|
||||
"PreviousPage": "Предыдущая",
|
||||
"NextPage": "Следующая",
|
||||
"ManagementCategoryCommon": "Общие",
|
||||
"Customization": "Кастомизация",
|
||||
"ProductsAndInstruments": "Модули и инструменты",
|
||||
"WhiteLabel": "Ребрендинг",
|
||||
"ManagementCategorySecurity": "Безопасность",
|
||||
"PortalSecurity": "Доступ к порталу",
|
||||
"AccessRights": "Права доступа",
|
||||
"LoginHistoryNav": "История входов в систему",
|
||||
"AuditTrailNav": "Журнал аудита",
|
||||
"DataManagement": "Управление данными",
|
||||
"Migration": "Миграция",
|
||||
"Backup": "Резервное копирование",
|
||||
"DeactivationDeletionPortal": "Деактивация/Удаление портала",
|
||||
"ManagementCategoryIntegration": "Интеграция",
|
||||
"ThirdPartyAuthorization": "Сторонние сервисы",
|
||||
"SmtpSettings": "Настройки SMTP",
|
||||
"ManagementCategoryStatistic": "Статистика",
|
||||
"LoadingProcessing": "Загрузка...",
|
||||
"LoadingDescription": "Пожалуйста подождите...",
|
||||
"GreetingSettingsTitle": "Настройки страницы приветствия",
|
||||
"GreetingTitle": "Заголовок",
|
||||
"RestoreDefaultButton": "Настройки по умолчанию",
|
||||
"SuccessfullySaveGreetingSettingsMessage": "Настройки страницы приветствия успешно сохранены",
|
||||
|
||||
|
||||
|
||||
@ -31,5 +62,10 @@
|
||||
"Sample": "Sample",
|
||||
"ManageOwnMailAccounts": "Управлять своими учетными записями почты, получать и отправлять письма",
|
||||
"ManageOwnMailSettings": "Управлять своими настройками Почты",
|
||||
"ManageTheTagsAndAddressBook": "Управлять тегами и адресной книгой"
|
||||
"ManageTheTagsAndAddressBook": "Управлять тегами и адресной книгой",
|
||||
"Culture_en": "Английский (Великобритания)",
|
||||
"Culture_en-US": "Английский (США)",
|
||||
"Culture_ru-RU": "Русский (Россия)",
|
||||
"LearnMore": "Подробнее...",
|
||||
"CountPerPage": "{{count}} на странице"
|
||||
}
|
@ -1,137 +0,0 @@
|
||||
import React from "react";
|
||||
import { connect } from "react-redux";
|
||||
import { withTranslation } from 'react-i18next';
|
||||
import { FieldContainer, Text, ComboBox, Loader, Button, toastr } from "asc-web-components";
|
||||
import { getCultures, setLanguageAndTime } from '../../../../../store/auth/actions';
|
||||
import { getPortalTimezones } from '../../../../../store/services/api';
|
||||
import styled from 'styled-components'
|
||||
|
||||
|
||||
const StyledComponent = styled.div`
|
||||
.margin-top {
|
||||
margin-top: 20px;
|
||||
}
|
||||
`;
|
||||
class Customization extends React.Component {
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this.state = {
|
||||
isLoadedData: false,
|
||||
isLoading: false,
|
||||
timezones: [],
|
||||
timezone: {},
|
||||
languages: [],
|
||||
language: {},
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
componentDidMount() {
|
||||
const { getCultures, portalLanguage, portalTimeZoneId } = this.props;
|
||||
let languages, timezones;
|
||||
getCultures()
|
||||
.then((cultures) => {
|
||||
languages = cultures.map((culture) => {
|
||||
return { key: culture, label: culture };
|
||||
});
|
||||
return getPortalTimezones();
|
||||
})
|
||||
.then((timezonesArr) => {
|
||||
timezones = timezonesArr.map((timezone) => {
|
||||
return { key: timezone.Id, label: timezone.DisplayName };
|
||||
});
|
||||
const language = languages.find(item => item.key === portalLanguage);
|
||||
const timezone = timezones.find(item => item.key === portalTimeZoneId);
|
||||
|
||||
this.setState({ timezones, timezone, languages, language, isLoadedData: true });
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
onLanguageSelect = (language) => {
|
||||
this.setState({ language })
|
||||
};
|
||||
|
||||
onTimezoneSelect = (timezone) => {
|
||||
this.setState({ timezone })
|
||||
};
|
||||
|
||||
onSaveLngTZSettings = () => {
|
||||
const { setLanguageAndTime } = this.props;
|
||||
this.setState({ isLoading: true }, function () {
|
||||
setLanguageAndTime(this.state.language.key, this.state.timezone.key)
|
||||
.then(() => {
|
||||
this.setState({ isLoading: false })
|
||||
toastr.success('Settings have been successfully updated');
|
||||
});
|
||||
})
|
||||
}
|
||||
|
||||
render() {
|
||||
const { isLoadedData } = this.state;
|
||||
const tooltipContent = 'Tooltip content 123456';
|
||||
|
||||
console.log("CustomizationSettings render");
|
||||
return (
|
||||
!isLoadedData ?
|
||||
<Loader className="pageLoader" type="rombs" size={40} />
|
||||
: <>
|
||||
<StyledComponent>
|
||||
<Text.Body fontSize={16}>Language and Time Zone Settings</Text.Body>
|
||||
<FieldContainer
|
||||
className='margin-top'
|
||||
labelText="Language:"
|
||||
tooltipContent={tooltipContent}
|
||||
isVertical={true}>
|
||||
<ComboBox
|
||||
options={this.state.languages}
|
||||
selectedOption={this.state.language}
|
||||
onSelect={this.onLanguageSelect}
|
||||
isDisabled={this.state.isLoading}
|
||||
noBorder={false}
|
||||
scaled={false}
|
||||
scaledOptions={true}
|
||||
size='huge'
|
||||
/>
|
||||
</FieldContainer>
|
||||
|
||||
<FieldContainer
|
||||
labelText="Time Zone:"
|
||||
tooltipContent={tooltipContent}
|
||||
isVertical={true}>
|
||||
<ComboBox
|
||||
options={this.state.timezones}
|
||||
selectedOption={this.state.timezone}
|
||||
onSelect={this.onTimezoneSelect}
|
||||
isDisabled={this.state.isLoading}
|
||||
noBorder={false}
|
||||
scaled={false}
|
||||
scaledOptions={true}
|
||||
dropDownMaxHeight={300}
|
||||
size='huge'
|
||||
/>
|
||||
</FieldContainer>
|
||||
<Button
|
||||
className='margin-top'
|
||||
primary={true}
|
||||
size='big'
|
||||
label='Save'
|
||||
isLoading={this.state.isLoading}
|
||||
onClick={this.onSaveLngTZSettings}
|
||||
/>
|
||||
</StyledComponent>
|
||||
|
||||
</>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
function mapStateToProps(state) {
|
||||
return {
|
||||
portalLanguage: state.auth.settings.culture,
|
||||
portalTimeZoneId: state.auth.settings.timezone
|
||||
};
|
||||
}
|
||||
|
||||
export default connect(mapStateToProps, { getCultures, setLanguageAndTime })(withTranslation()(Customization));
|
@ -1,16 +0,0 @@
|
||||
import React from "react";
|
||||
|
||||
class NotImplementedSettings extends React.Component {
|
||||
|
||||
render() {
|
||||
//console.log("NotImplementedSettings render");
|
||||
return (
|
||||
<div>
|
||||
Settings section in progress
|
||||
</div>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
export default NotImplementedSettings;
|
@ -1,557 +0,0 @@
|
||||
import React, { Component } from "react";
|
||||
import PropTypes from "prop-types";
|
||||
import { connect } from "react-redux";
|
||||
import { withRouter } from "react-router";
|
||||
import i18n from "../../i18n";
|
||||
import { I18nextProvider, withTranslation } from "react-i18next";
|
||||
import styled from "styled-components";
|
||||
import { getAdminUsers } from "../../../../../store/people/actions";
|
||||
import {
|
||||
Text,
|
||||
Avatar,
|
||||
ToggleContent,
|
||||
Row,
|
||||
RowContent,
|
||||
RowContainer,
|
||||
RadioButtonGroup,
|
||||
Link,
|
||||
Checkbox
|
||||
//toastr,
|
||||
} from "asc-web-components";
|
||||
|
||||
const MainContainer = styled.div`
|
||||
padding: 16px 16px 16px 24px;
|
||||
width: 100%;
|
||||
`;
|
||||
|
||||
const HeaderContainer = styled.div`
|
||||
margin-bottom: 16px;
|
||||
`;
|
||||
|
||||
const BodyContainer = styled.div`
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
flex-direction: row;
|
||||
flex-wrap: wrap;
|
||||
`;
|
||||
|
||||
const AvatarContainer = styled.div`
|
||||
display: flex;
|
||||
width: 330px;
|
||||
height: 100px;
|
||||
margin-right: 130px;
|
||||
margin-bottom: 24px;
|
||||
|
||||
padding: 8px;
|
||||
border: 1px solid lightgrey;
|
||||
`;
|
||||
|
||||
const ToggleContentContainer = styled.div`
|
||||
.toggle_content {
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
`;
|
||||
|
||||
const ProjectsContainer = styled.div`
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
flex-direction: row;
|
||||
flex-wrap: wrap;
|
||||
|
||||
.display-block {
|
||||
display: block;
|
||||
}
|
||||
|
||||
div label:not(:first-child) {
|
||||
margin: 0;
|
||||
}
|
||||
`;
|
||||
|
||||
const RadioButtonContainer = styled.div`
|
||||
margin-right: 150px;
|
||||
margin-bottom: 16px;
|
||||
width: 310px;
|
||||
`;
|
||||
|
||||
const ProjectsBody = styled.div`
|
||||
width: 280px;
|
||||
`;
|
||||
|
||||
/*const AdministratorsHead = styled.div`
|
||||
display: flex;
|
||||
margin-right: 70px;
|
||||
|
||||
.category {
|
||||
margin-left: 5px;
|
||||
flex-basis: 12.5%; (1/elementsCount*100%)
|
||||
text-align: center;
|
||||
}
|
||||
`;*/
|
||||
|
||||
class PureAccessRights extends Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
isChecked: this.props.isChecked,
|
||||
portalOwner: null,
|
||||
};
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
const { getAdminUsers } = this.props;
|
||||
|
||||
getAdminUsers()
|
||||
.then(res => {
|
||||
console.log("getAdminUsers response", res);
|
||||
})
|
||||
.catch(error => {
|
||||
console.log(error);
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
//componentDidUpdate(prevProps, prevState) {}
|
||||
//componentWillUnmount() {}
|
||||
|
||||
onChange = e => {
|
||||
//console.log(e.target.value);
|
||||
//e.target.value = !e.target.value;
|
||||
};
|
||||
|
||||
render() {
|
||||
const { t } = this.props;
|
||||
const { users } = this.props;
|
||||
const { portalOwner } = this.state;
|
||||
const OwnerOpportunities = t("AccessRightsOwnerOpportunities").split("|");
|
||||
|
||||
return (
|
||||
<MainContainer>
|
||||
<HeaderContainer>
|
||||
<Text.Body fontSize={18}>{t("PortalOwner")}</Text.Body>
|
||||
</HeaderContainer>
|
||||
|
||||
<BodyContainer>
|
||||
<AvatarContainer>
|
||||
<Avatar size="big" role="owner" />
|
||||
{portalOwner ? (
|
||||
<div style={{ marginLeft: "24px" }}>
|
||||
<Text.Body className="avatar_text" fontSize={16} isBold={true}>
|
||||
{portalOwner.displayName}
|
||||
</Text.Body>
|
||||
<Text.Body className="avatar_text" fontSize={12}>
|
||||
{portalOwner.department}
|
||||
</Text.Body>
|
||||
</div>
|
||||
) : null}
|
||||
</AvatarContainer>
|
||||
<ProjectsBody>
|
||||
<Text.Body className="portal_owner" fontSize={12}>
|
||||
{t("AccessRightsOwnerCan")}:
|
||||
</Text.Body>
|
||||
<Text.Body fontSize={12}>
|
||||
{OwnerOpportunities.map((item, key) => (
|
||||
<li key={key}>{item};</li>
|
||||
))}
|
||||
</Text.Body>
|
||||
</ProjectsBody>
|
||||
</BodyContainer>
|
||||
|
||||
<ToggleContentContainer>
|
||||
<ToggleContent
|
||||
className="toggle_content"
|
||||
label={t("AdminSettings")}
|
||||
isOpen={true}
|
||||
>
|
||||
<RowContainer manualHeight="200px">
|
||||
{users.map(user => {
|
||||
const element = (
|
||||
<Avatar
|
||||
size="small"
|
||||
role="admin"
|
||||
userName={user.userName}
|
||||
source={user.avatar}
|
||||
/>
|
||||
);
|
||||
const nameColor =
|
||||
user.status === "pending" ? "#A3A9AE" : "#333333";
|
||||
|
||||
return (
|
||||
<Row
|
||||
key={user.id}
|
||||
status={user.status}
|
||||
checked={false}
|
||||
data={user}
|
||||
element={element}
|
||||
//contextOptions={user.contextOptions}
|
||||
>
|
||||
<RowContent disableSideInfo={true}>
|
||||
<Link
|
||||
containerWidth="120px"
|
||||
type="page"
|
||||
title={user.userName}
|
||||
isBold={true}
|
||||
fontSize={15}
|
||||
color={nameColor}
|
||||
href={user.profileUrl}
|
||||
>
|
||||
{user.userName}
|
||||
</Link>
|
||||
|
||||
<div
|
||||
/*containerWidth='120px'*/ style={{ maxWidth: 120 }}
|
||||
></div>
|
||||
|
||||
<Checkbox
|
||||
isChecked={false}
|
||||
onChange={this.onChange}
|
||||
id={`fullAccess_${user.id}`}
|
||||
/>
|
||||
<Checkbox
|
||||
isChecked={false}
|
||||
onChange={this.onChange}
|
||||
id={`people_${user.id}`}
|
||||
/>
|
||||
</RowContent>
|
||||
</Row>
|
||||
);
|
||||
})}
|
||||
</RowContainer>
|
||||
</ToggleContent>
|
||||
|
||||
<ToggleContent
|
||||
className="toggle_content"
|
||||
label={t("ProjectsProduct")}
|
||||
isOpen={true}
|
||||
>
|
||||
<ProjectsContainer>
|
||||
<RadioButtonContainer>
|
||||
<Text.Body>
|
||||
{t("AccessRightsAccessToProduct", {
|
||||
product: t("ProjectsProduct")
|
||||
})}
|
||||
:
|
||||
</Text.Body>
|
||||
<RadioButtonGroup
|
||||
name="selectGroup"
|
||||
selected="allUsers"
|
||||
options={[
|
||||
{
|
||||
value: "allUsers",
|
||||
label: t("AccessRightsAllUsers", {
|
||||
users: t("Employees")
|
||||
})
|
||||
},
|
||||
{
|
||||
value: "usersFromTheList",
|
||||
label: t("AccessRightsUsersFromList", {
|
||||
users: t("Employees")
|
||||
})
|
||||
}
|
||||
]}
|
||||
className="display-block"
|
||||
/>
|
||||
</RadioButtonContainer>
|
||||
<ProjectsBody>
|
||||
<Text.Body className="projects_margin" fontSize={12}>
|
||||
{t("AccessRightsProductUsersCan", {
|
||||
category: t("ProjectsProduct")
|
||||
})}
|
||||
</Text.Body>
|
||||
<Text.Body fontSize={12}>
|
||||
<li>{t("ProjectsUserCapabilityView")}</li>
|
||||
<li>{t("ProjectsUserCapabilityCreate")}</li>
|
||||
<li>{t("ProjectsUserCapabilityTrack")}</li>
|
||||
<li>{t("ProjectsUserCapabilityForm")}</li>
|
||||
</Text.Body>
|
||||
</ProjectsBody>
|
||||
</ProjectsContainer>
|
||||
</ToggleContent>
|
||||
|
||||
<ToggleContent
|
||||
className="toggle_content"
|
||||
label={t("CrmProduct")}
|
||||
isOpen={true}
|
||||
>
|
||||
<ProjectsContainer>
|
||||
<RadioButtonContainer>
|
||||
<Text.Body>
|
||||
{t("AccessRightsAccessToProduct", {
|
||||
product: t("CrmProduct")
|
||||
})}
|
||||
:
|
||||
</Text.Body>
|
||||
<RadioButtonGroup
|
||||
name="selectGroup"
|
||||
selected="allUsers"
|
||||
options={[
|
||||
{
|
||||
value: "allUsers",
|
||||
label: t("AccessRightsAllUsers", {
|
||||
users: t("Employees")
|
||||
})
|
||||
},
|
||||
{
|
||||
value: "usersFromTheList",
|
||||
label: t("AccessRightsUsersFromList", {
|
||||
users: t("Employees")
|
||||
})
|
||||
}
|
||||
]}
|
||||
className="display-block"
|
||||
/>
|
||||
</RadioButtonContainer>
|
||||
<ProjectsBody>
|
||||
<Text.Body className="projects_margin" fontSize={12}>
|
||||
{t("AccessRightsProductUsersCan", {
|
||||
category: t("CrmProduct")
|
||||
})}
|
||||
</Text.Body>
|
||||
<Text.Body fontSize={12}>
|
||||
<li>{t("CRMUserCapability")}</li>
|
||||
<li>{t("CRMUserCapabilityEdit")}</li>
|
||||
</Text.Body>
|
||||
</ProjectsBody>
|
||||
</ProjectsContainer>
|
||||
</ToggleContent>
|
||||
|
||||
<ToggleContent
|
||||
className="toggle_content"
|
||||
label={t("CommunityProduct")}
|
||||
isOpen={true}
|
||||
>
|
||||
<ProjectsContainer>
|
||||
<RadioButtonContainer>
|
||||
<Text.Body>
|
||||
{t("AccessRightsAccessToProduct", {
|
||||
product: t("CommunityProduct")
|
||||
})}
|
||||
:
|
||||
</Text.Body>
|
||||
<RadioButtonGroup
|
||||
name="selectGroup"
|
||||
selected="allUsers"
|
||||
options={[
|
||||
{
|
||||
value: "allUsers",
|
||||
label: t("AccessRightsAllUsers", {
|
||||
users: t("Employees")
|
||||
})
|
||||
},
|
||||
{
|
||||
value: "usersFromTheList",
|
||||
label: t("AccessRightsUsersFromList", {
|
||||
users: t("Employees")
|
||||
})
|
||||
}
|
||||
]}
|
||||
className="display-block"
|
||||
/>
|
||||
</RadioButtonContainer>
|
||||
<ProjectsBody>
|
||||
<Text.Body className="projects_margin" fontSize={12}>
|
||||
{t("AccessRightsProductUsersCan", {
|
||||
category: t("CommunityProduct")
|
||||
})}
|
||||
</Text.Body>
|
||||
<Text.Body fontSize={12}>
|
||||
<li>{t("CommunityUserCapability")}</li>
|
||||
</Text.Body>
|
||||
</ProjectsBody>
|
||||
</ProjectsContainer>
|
||||
</ToggleContent>
|
||||
|
||||
<ToggleContent
|
||||
className="toggle_content"
|
||||
label={t("People")}
|
||||
isOpen={true}
|
||||
>
|
||||
<ProjectsContainer>
|
||||
<RadioButtonContainer>
|
||||
<Text.Body>
|
||||
{t("AccessRightsAccessToProduct", { product: t("People") })}:
|
||||
</Text.Body>
|
||||
<RadioButtonGroup
|
||||
name="selectGroup"
|
||||
selected="allUsers"
|
||||
options={[
|
||||
{
|
||||
value: "allUsers",
|
||||
label: t("AccessRightsAllUsers", {
|
||||
users: t("Employees")
|
||||
})
|
||||
},
|
||||
{
|
||||
value: "usersFromTheList",
|
||||
label: t("AccessRightsUsersFromList", {
|
||||
users: t("Employees")
|
||||
})
|
||||
}
|
||||
]}
|
||||
className="display-block"
|
||||
/>
|
||||
</RadioButtonContainer>
|
||||
<ProjectsBody>
|
||||
<Text.Body className="projects_margin" fontSize={12}>
|
||||
{t("AccessRightsProductUsersCan", { category: t("People") })}
|
||||
</Text.Body>
|
||||
<Text.Body fontSize={12}>
|
||||
<li>{t("ViewProfilesAndGroups")}</li>
|
||||
</Text.Body>
|
||||
</ProjectsBody>
|
||||
</ProjectsContainer>
|
||||
</ToggleContent>
|
||||
|
||||
<ToggleContent
|
||||
className="toggle_content"
|
||||
label={t("Sample")}
|
||||
isOpen={true}
|
||||
>
|
||||
<Text.Body fontSize={12}>
|
||||
{t("AccessRightsDisabledProduct", { module: "Sample" })}
|
||||
</Text.Body>
|
||||
</ToggleContent>
|
||||
|
||||
<ToggleContent
|
||||
className="toggle_content"
|
||||
label={t("Mail")}
|
||||
isOpen={true}
|
||||
>
|
||||
<ProjectsContainer>
|
||||
<RadioButtonContainer>
|
||||
<Text.Body>
|
||||
{t("AccessRightsAccessToProduct", { product: t("Mail") })}:
|
||||
</Text.Body>
|
||||
<RadioButtonGroup
|
||||
name="selectGroup"
|
||||
selected="allUsers"
|
||||
options={[
|
||||
{
|
||||
value: "allUsers",
|
||||
label: t("AccessRightsAllUsers", {
|
||||
users: t("Employees")
|
||||
})
|
||||
},
|
||||
{
|
||||
value: "usersFromTheList",
|
||||
label: t("AccessRightsUsersFromList", {
|
||||
users: t("Employees")
|
||||
})
|
||||
}
|
||||
]}
|
||||
className="display-block"
|
||||
/>
|
||||
</RadioButtonContainer>
|
||||
<ProjectsBody>
|
||||
<Text.Body className="projects_margin" fontSize={12}>
|
||||
{t("AccessRightsProductUsersCan", { category: t("Mail") })}
|
||||
</Text.Body>
|
||||
<Text.Body fontSize={12}>
|
||||
<li>{t("ManageOwnMailAccounts")}</li>
|
||||
<li>{t("ManageOwnMailSettings")}</li>
|
||||
<li>{t("ManageTheTagsAndAddressBook")}</li>
|
||||
</Text.Body>
|
||||
</ProjectsBody>
|
||||
</ProjectsContainer>
|
||||
</ToggleContent>
|
||||
</ToggleContentContainer>
|
||||
</MainContainer>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
PureAccessRights.propTypes = {};
|
||||
|
||||
PureAccessRights.defaultProps = {
|
||||
isChecked: false
|
||||
};
|
||||
|
||||
const AccessRightsContainer = withTranslation()(PureAccessRights);
|
||||
|
||||
const AccessRights = props => {
|
||||
const { language } = props;
|
||||
|
||||
i18n.changeLanguage(language);
|
||||
|
||||
return (
|
||||
<I18nextProvider i18n={i18n}>
|
||||
<AccessRightsContainer {...props} />
|
||||
</I18nextProvider>
|
||||
);
|
||||
};
|
||||
|
||||
function mapStateToProps(state) {
|
||||
return {
|
||||
users: state.people.users
|
||||
};
|
||||
}
|
||||
|
||||
AccessRights.defaultProps = {
|
||||
users: []
|
||||
};
|
||||
|
||||
AccessRights.propTypes = {
|
||||
users: PropTypes.arrayOf(PropTypes.object)
|
||||
};
|
||||
|
||||
export default connect(
|
||||
mapStateToProps,
|
||||
{ getAdminUsers }
|
||||
)(withRouter((AccessRights)));
|
||||
/*
|
||||
<AdministratorsHead>
|
||||
<div style={{width: 250}}></div>
|
||||
<Text.Body className="category">
|
||||
{t("AccessRightsFullAccess")}
|
||||
</Text.Body>
|
||||
<Text.Body className="category">{t("DocumentsProduct")}</Text.Body>
|
||||
<Text.Body className="category">{t("ProjectsProduct")}</Text.Body>
|
||||
<Text.Body className="category">{t("CrmProduct")}</Text.Body>
|
||||
<Text.Body className="category">{t("CommunityProduct")}</Text.Body>
|
||||
<Text.Body className="category">{t("People")}</Text.Body>
|
||||
<Text.Body className="category">{t("Sample")}</Text.Body>
|
||||
<Text.Body className="category">{t("Mail")}</Text.Body>
|
||||
</AdministratorsHead>
|
||||
*/
|
||||
/*
|
||||
<Checkbox
|
||||
isChecked={false}
|
||||
onChange={this.onChange}
|
||||
id={`fullAccess_${user.id}`}
|
||||
/>
|
||||
<Checkbox
|
||||
isChecked={false}
|
||||
onChange={this.onChange}
|
||||
id={`documents_${user.id}`}
|
||||
/>
|
||||
<Checkbox
|
||||
isChecked={false}
|
||||
onChange={this.onChange}
|
||||
id={`projects_${user.id}`}
|
||||
/>
|
||||
<Checkbox
|
||||
isChecked={false}
|
||||
onChange={this.onChange}
|
||||
id={`crm_${user.id}`}
|
||||
/>
|
||||
<Checkbox
|
||||
isChecked={false}
|
||||
onChange={this.onChange}
|
||||
id={`community_${user.id}`}
|
||||
/>
|
||||
<Checkbox
|
||||
isChecked={false}
|
||||
onChange={this.onChange}
|
||||
id={`people_${user.id}`}
|
||||
/>
|
||||
<Checkbox
|
||||
isChecked={false}
|
||||
onChange={this.onChange}
|
||||
id={`sample_${user.id}`}
|
||||
/>
|
||||
<Checkbox
|
||||
isChecked={false}
|
||||
onChange={this.onChange}
|
||||
id={`mail_${user.id}`}
|
||||
/>
|
||||
*/
|
@ -0,0 +1,11 @@
|
||||
export const checkForRoot = (key, treeData, depth = 0) => {
|
||||
let newKey = key.slice(0, 1 + 2 * depth);
|
||||
const item = treeData.find(item => item.key === newKey);
|
||||
if (key === newKey) {
|
||||
return item.children ? true : false;
|
||||
}
|
||||
else {
|
||||
const depthLevel = depth + 1;
|
||||
return checkForRoot(key, item.children, depthLevel);
|
||||
}
|
||||
}
|
@ -0,0 +1,16 @@
|
||||
export const getKeyByLink = (linkArr, data, index = 0) => {
|
||||
const length = linkArr.length;
|
||||
const currentElement = linkArr[index];
|
||||
const item = data.find(item => item.link === currentElement);
|
||||
|
||||
if (index === length - 1 && item) {
|
||||
return item.key;
|
||||
}
|
||||
else if (!item || !item.children) {
|
||||
return '0';
|
||||
}
|
||||
else {
|
||||
const newIndex = index + 1;
|
||||
return getKeyByLink(linkArr, item.children, newIndex);
|
||||
}
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
export const getSelectedLinkByKey = (key, treeData, depth = 0) => {
|
||||
let newKey = key.slice(0, 1 + 2 * depth);
|
||||
const item = treeData.find(item => item.key === newKey);
|
||||
if (key === newKey) {
|
||||
return '/' + item.link;
|
||||
}
|
||||
else {
|
||||
const depthLevel = depth + 1;
|
||||
return '/' + item.link + getSelectedLinkByKey(key, item.children, depthLevel);
|
||||
}
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
export const getTKeyByKey = (key, treeData, depth = 0) => {
|
||||
let newKey = key.slice(0, 1 + 2 * depth);
|
||||
const item = treeData.find(item => item.key === newKey);
|
||||
if (key === newKey) {
|
||||
return item.tKey;
|
||||
}
|
||||
else {
|
||||
const depthLevel = depth + 1;
|
||||
return getTKeyByKey(key, item.children, depthLevel);
|
||||
}
|
||||
}
|
@ -0,0 +1,5 @@
|
||||
export { getKeyByLink } from './getKeyByLink';
|
||||
export { settingsTree } from './settingsTree';
|
||||
export { getTKeyByKey } from './getTKeyByKey';
|
||||
export { getSelectedLinkByKey } from './getSelectedLinkByKey';
|
||||
export { selectKeyOfTreeElement } from './selectKeyOfTreeElement';
|
@ -0,0 +1,9 @@
|
||||
export const selectFirstChildItem = (rootKey, data) => {
|
||||
const item = data.find(item => item.key[0] === rootKey);
|
||||
if (item.children) {
|
||||
return selectFirstChildItem(rootKey, item.children);
|
||||
}
|
||||
else {
|
||||
return [data[0].key];
|
||||
}
|
||||
}
|
@ -0,0 +1,16 @@
|
||||
|
||||
import { checkForRoot } from './checkForRoot';
|
||||
import { selectFirstChildItem } from './selectFirstChildItem';
|
||||
|
||||
export const selectKeyOfTreeElement = (value, settingsTree) => {
|
||||
const selectedKey = value[0];
|
||||
const isRootElementSelected = checkForRoot(selectedKey, settingsTree);
|
||||
|
||||
if (isRootElementSelected) {
|
||||
const firstChildren = selectFirstChildItem(selectedKey[0], settingsTree);
|
||||
return firstChildren;
|
||||
}
|
||||
else {
|
||||
return value;
|
||||
}
|
||||
}
|
@ -0,0 +1,152 @@
|
||||
/**
|
||||
* Array for generation current settings tree.
|
||||
*/
|
||||
export const settingsTree = [
|
||||
{
|
||||
key: '0',
|
||||
icon: 'SettingsIcon',
|
||||
link: 'common',
|
||||
tKey: 'ManagementCategoryCommon',
|
||||
children: [
|
||||
{
|
||||
key: '0-0',
|
||||
icon: '',
|
||||
link: 'customization',
|
||||
tKey: 'Customization',
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
key: '1',
|
||||
icon: 'SettingsIcon',
|
||||
link: 'security',
|
||||
tKey: 'ManagementCategorySecurity',
|
||||
children: [
|
||||
{
|
||||
key: '1-0',
|
||||
icon: '',
|
||||
link: 'accessrights',
|
||||
tKey: 'AccessRights',
|
||||
},
|
||||
]
|
||||
},
|
||||
|
||||
];
|
||||
|
||||
/**
|
||||
* Array for generation full settings tree, old structure.
|
||||
param title is unused
|
||||
param link also used as translation key
|
||||
*/
|
||||
export const settingsTreeFull = [
|
||||
{
|
||||
title: 'Common',
|
||||
key: '0',
|
||||
icon: 'SettingsIcon',
|
||||
link: 'common',
|
||||
children: [
|
||||
{
|
||||
title: 'Customization',
|
||||
key: '0-0',
|
||||
icon: '',
|
||||
link: 'customization',
|
||||
},
|
||||
{
|
||||
title: 'Modules & tools',
|
||||
key: '0-1',
|
||||
icon: '',
|
||||
link: 'modules-and-tools',
|
||||
},
|
||||
{
|
||||
title: 'White label',
|
||||
key: '0-2',
|
||||
icon: '',
|
||||
link: 'white-label',
|
||||
},
|
||||
]
|
||||
},
|
||||
{
|
||||
title: 'Security',
|
||||
key: '1',
|
||||
icon: 'SettingsIcon',
|
||||
link: 'security',
|
||||
children: [
|
||||
{
|
||||
title: 'Portal Access',
|
||||
key: '1-0',
|
||||
icon: '',
|
||||
link: 'portal-access',
|
||||
},
|
||||
{
|
||||
title: 'Access Rights',
|
||||
key: '1-1',
|
||||
icon: '',
|
||||
link: 'access-rights',
|
||||
},
|
||||
{
|
||||
title: 'Login History',
|
||||
key: '1-2',
|
||||
icon: '',
|
||||
link: 'login-history',
|
||||
},
|
||||
{
|
||||
title: 'Audit Trail',
|
||||
key: '1-3',
|
||||
icon: '',
|
||||
link: 'audit-trail',
|
||||
},
|
||||
]
|
||||
},
|
||||
{
|
||||
title: 'Data Management',
|
||||
key: '2',
|
||||
icon: 'SettingsIcon',
|
||||
link: 'data-management',
|
||||
children: [
|
||||
{
|
||||
title: 'Migration',
|
||||
key: '2-0',
|
||||
icon: '',
|
||||
link: 'migration',
|
||||
},
|
||||
{
|
||||
title: 'Backup',
|
||||
key: '2-1',
|
||||
icon: '',
|
||||
link: 'backup',
|
||||
},
|
||||
{
|
||||
title: 'Portal Deactivation/Deletion',
|
||||
key: '2-2',
|
||||
icon: '',
|
||||
link: 'portal-deactivation-deletion',
|
||||
},
|
||||
]
|
||||
},
|
||||
{
|
||||
title: 'Integration',
|
||||
key: '3',
|
||||
icon: 'SettingsIcon',
|
||||
link: 'integration',
|
||||
children: [
|
||||
{
|
||||
title: 'Third-Party Services',
|
||||
key: '3-0',
|
||||
icon: '',
|
||||
link: 'third-party-services',
|
||||
},
|
||||
{
|
||||
title: 'SMTP Settings',
|
||||
key: '3-1',
|
||||
icon: '',
|
||||
link: 'smtp-settings',
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
title: 'Statistics',
|
||||
key: '4',
|
||||
icon: 'SettingsIcon',
|
||||
link: 'statistics',
|
||||
},
|
||||
];
|
@ -26,7 +26,8 @@
|
||||
"ForgotPassword",
|
||||
"PasswordRecoveryTitle",
|
||||
"MessageSendPasswordRecoveryInstructionsOnEmail",
|
||||
"RememberHelper"
|
||||
"RememberHelper",
|
||||
"LoadingDescription"
|
||||
]
|
||||
},
|
||||
"Confirm": {
|
||||
@ -68,6 +69,30 @@
|
||||
"AccessRightsDisabledProduct",
|
||||
"AccessRightsAllUsers",
|
||||
"AccessRightsUsersFromList",
|
||||
"StudioTimeLanguageSettings",
|
||||
"Language",
|
||||
"TimeZone",
|
||||
"SaveButton",
|
||||
"NotFoundLanguage",
|
||||
"ManagementCategoryCommon",
|
||||
"Customization",
|
||||
"ProductsAndInstruments",
|
||||
"WhiteLabel",
|
||||
"ManagementCategorySecurity",
|
||||
"PortalSecurity",
|
||||
"AccessRights",
|
||||
"ManagementCategoryIntegration",
|
||||
"ManagementCategoryStatistic",
|
||||
"DataManagement",
|
||||
"Migration",
|
||||
"Backup",
|
||||
"DeactivationDeletionPortal",
|
||||
"ThirdPartyAuthorization",
|
||||
"SmtpSettings",
|
||||
"GreetingSettingsTitle",
|
||||
"GreetingTitle",
|
||||
"RestoreDefaultButton",
|
||||
"SuccessfullySaveGreetingSettingsMessage",
|
||||
"Employees"
|
||||
],
|
||||
"FeedResource": [
|
||||
@ -75,6 +100,17 @@
|
||||
"CrmProduct",
|
||||
"CommunityProduct",
|
||||
"DocumentsProduct"
|
||||
],
|
||||
"PeopleResource": [
|
||||
"Settings"
|
||||
],
|
||||
"AuditResource": [
|
||||
"LoginHistoryNav",
|
||||
"AuditTrailNav"
|
||||
],
|
||||
"UserControlsCommonResource": [
|
||||
"NextPage",
|
||||
"PreviousPage"
|
||||
]
|
||||
}
|
||||
},
|
||||
|
@ -10,9 +10,10 @@ export const LOGOUT = 'LOGOUT';
|
||||
export const SET_PASSWORD_SETTINGS = 'SET_PASSWORD_SETTINGS';
|
||||
export const SET_IS_CONFIRM_LOADED = 'SET_IS_CONFIRM_LOADED';
|
||||
export const SET_NEW_EMAIL = 'SET_NEW_EMAIL';
|
||||
export const SET_NEW_SETTING_NODE = 'SET_NEW_SETTING_NODE';
|
||||
export const GET_PORTAL_CULTURES = 'GET_PORTAL_CULTURES';
|
||||
export const SET_PORTAL_LANGUAGE_AND_TIME = 'SET_PORTAL_LANGUAGE_AND_TIME';
|
||||
export const GET_TIMEZONES = 'GET_TIMEZONES';
|
||||
export const SET_CURRENT_PRODUCT_ID = 'SET_CURRENT_PRODUCT_ID';
|
||||
|
||||
export function setCurrentUser(user) {
|
||||
return {
|
||||
@ -69,13 +70,6 @@ export function setNewEmail(email) {
|
||||
};
|
||||
};
|
||||
|
||||
export function setNewSelectedNode(selectedNodeData) {
|
||||
return {
|
||||
type: SET_NEW_SETTING_NODE,
|
||||
selectedNodeData
|
||||
};
|
||||
};
|
||||
|
||||
export function getPortalCultures(cultures) {
|
||||
return {
|
||||
type: GET_PORTAL_CULTURES,
|
||||
@ -90,6 +84,20 @@ export function setPortalLanguageAndTime(newSettings) {
|
||||
};
|
||||
};
|
||||
|
||||
export function getTimezones(timezones) {
|
||||
return {
|
||||
type: GET_TIMEZONES,
|
||||
timezones
|
||||
};
|
||||
};
|
||||
|
||||
export function setCurrentProductId(currentProductId) {
|
||||
return {
|
||||
type: SET_CURRENT_PRODUCT_ID,
|
||||
currentProductId
|
||||
};
|
||||
};
|
||||
|
||||
export function getUser(dispatch) {
|
||||
return api.getUser()
|
||||
.then(user => dispatch(setCurrentUser(user)));
|
||||
@ -196,7 +204,6 @@ export function getCultures() {
|
||||
return api.getPortalCultures()
|
||||
.then(cultures => {
|
||||
dispatch(getPortalCultures(cultures));
|
||||
return cultures;
|
||||
}
|
||||
);
|
||||
};
|
||||
@ -208,3 +215,12 @@ export function setLanguageAndTime(lng, timeZoneID) {
|
||||
.then(() => dispatch(setPortalLanguageAndTime({ lng, timeZoneID })));
|
||||
};
|
||||
}
|
||||
|
||||
export function getPortalTimezones() {
|
||||
return dispatch => {
|
||||
return api.getPortalTimezones()
|
||||
.then((timezones) => {
|
||||
dispatch(getTimezones(timezones))
|
||||
});
|
||||
};
|
||||
};
|
||||
|
@ -1,122 +1,10 @@
|
||||
import {
|
||||
SET_CURRENT_USER, SET_MODULES, SET_SETTINGS, SET_IS_LOADED, LOGOUT, SET_PASSWORD_SETTINGS, SET_IS_CONFIRM_LOADED, SET_NEW_EMAIL, SET_NEW_SETTING_NODE,
|
||||
GET_PORTAL_CULTURES, SET_PORTAL_LANGUAGE_AND_TIME
|
||||
SET_CURRENT_USER, SET_MODULES, SET_SETTINGS, SET_IS_LOADED, LOGOUT, SET_PASSWORD_SETTINGS, SET_IS_CONFIRM_LOADED, SET_NEW_EMAIL,
|
||||
GET_PORTAL_CULTURES, SET_PORTAL_LANGUAGE_AND_TIME, GET_TIMEZONES, SET_CURRENT_PRODUCT_ID
|
||||
} from './actions';
|
||||
import isEmpty from 'lodash/isEmpty';
|
||||
import config from "../../../package.json";
|
||||
|
||||
const settingsTree = [
|
||||
{
|
||||
title: 'Common',
|
||||
key: '0',
|
||||
icon: 'SettingsIcon',
|
||||
link: 'common',
|
||||
children: [
|
||||
{
|
||||
title: 'Customization',
|
||||
key: '0-0',
|
||||
icon: '',
|
||||
link: 'customization',
|
||||
},
|
||||
{
|
||||
title: 'Modules & tools',
|
||||
key: '0-1',
|
||||
icon: '',
|
||||
link: 'modules-and-tools',
|
||||
},
|
||||
{
|
||||
title: 'White label',
|
||||
key: '0-2',
|
||||
icon: '',
|
||||
link: 'white-label',
|
||||
},
|
||||
]
|
||||
},
|
||||
{
|
||||
title: 'Security',
|
||||
key: '1',
|
||||
icon: 'SettingsIcon',
|
||||
link: 'security',
|
||||
children: [
|
||||
{
|
||||
title: 'Portal Access',
|
||||
key: '1-0',
|
||||
icon: '',
|
||||
link: 'portal-access',
|
||||
},
|
||||
{
|
||||
title: 'Access Rights',
|
||||
key: '1-1',
|
||||
icon: '',
|
||||
link: 'access-rights',
|
||||
},
|
||||
{
|
||||
title: 'Login History',
|
||||
key: '1-2',
|
||||
icon: '',
|
||||
link: 'login-history',
|
||||
},
|
||||
{
|
||||
title: 'Audit Trail',
|
||||
key: '1-3',
|
||||
icon: '',
|
||||
link: 'audit-trail',
|
||||
},
|
||||
]
|
||||
},
|
||||
{
|
||||
title: 'Data Management',
|
||||
key: '2',
|
||||
icon: 'SettingsIcon',
|
||||
link: 'data-management',
|
||||
children: [
|
||||
{
|
||||
title: 'Migration',
|
||||
key: '2-0',
|
||||
icon: '',
|
||||
link: 'migration',
|
||||
},
|
||||
{
|
||||
title: 'Backup',
|
||||
key: '2-1',
|
||||
icon: '',
|
||||
link: 'backup',
|
||||
},
|
||||
{
|
||||
title: 'Portal Deactivation/Deletion',
|
||||
key: '2-2',
|
||||
icon: '',
|
||||
link: 'portal-deactivation-deletion',
|
||||
},
|
||||
]
|
||||
},
|
||||
{
|
||||
title: 'Integration',
|
||||
key: '3',
|
||||
icon: 'SettingsIcon',
|
||||
link: 'integration',
|
||||
children: [
|
||||
{
|
||||
title: 'Third-Party Services',
|
||||
key: '3-0',
|
||||
icon: '',
|
||||
link: 'third-party-services',
|
||||
},
|
||||
{
|
||||
title: 'SMTP Settings',
|
||||
key: '3-1',
|
||||
icon: '',
|
||||
link: 'smtp-settings',
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
title: 'Statistics',
|
||||
key: '4',
|
||||
icon: 'SettingsIcon',
|
||||
link: 'statistics',
|
||||
},
|
||||
];
|
||||
|
||||
const initialState = {
|
||||
isAuthenticated: false,
|
||||
@ -127,9 +15,11 @@ const initialState = {
|
||||
settings: {
|
||||
currentProductId: "home",
|
||||
culture: "en-US",
|
||||
cultures: [],
|
||||
trustedDomains: [],
|
||||
trustedDomainsType: 1,
|
||||
timezone: "UTC",
|
||||
timezones: [],
|
||||
utcOffset: "00:00:00",
|
||||
utcHoursOffset: 0,
|
||||
homepage: config.homepage,
|
||||
@ -142,11 +32,7 @@ const initialState = {
|
||||
timePattern: "h:mm tt"
|
||||
},
|
||||
settingsTree: {
|
||||
list: settingsTree,
|
||||
selectedKey: ['0-0'],
|
||||
selectedTitle: 'Common',
|
||||
selectedSubtitle: 'Customization',
|
||||
selectedLink: '/common/customization',
|
||||
selectedKey: ['0-0']
|
||||
}
|
||||
}/*,
|
||||
password: null*/
|
||||
@ -187,14 +73,18 @@ const authReducer = (state = initialState, action) => {
|
||||
return Object.assign({}, state, {
|
||||
user: { ...state.user, email: action.email }
|
||||
});
|
||||
case SET_NEW_SETTING_NODE:
|
||||
return Object.assign({}, state, {
|
||||
settings: { ...state.settings, settingsTree: { ...state.settings.settingsTree, ...action.selectedNodeData } }
|
||||
});
|
||||
case SET_PORTAL_LANGUAGE_AND_TIME:
|
||||
return Object.assign({}, state, {
|
||||
settings: { ...state.settings, culture: action.newSettings.lng, timezone: action.newSettings.timeZoneID }
|
||||
});
|
||||
case GET_TIMEZONES:
|
||||
return Object.assign({}, state, {
|
||||
settings: { ...state.settings, timezones: action.timezones }
|
||||
});
|
||||
case SET_CURRENT_PRODUCT_ID:
|
||||
return Object.assign({}, state, {
|
||||
settings: { ...state.settings, currentProductId: action.currentProductId }
|
||||
});
|
||||
case LOGOUT:
|
||||
return initialState;
|
||||
default:
|
||||
|
@ -1,17 +0,0 @@
|
||||
import * as api from "../services/api";
|
||||
|
||||
export const SET_USERS = "SET_USERS";
|
||||
|
||||
export function setUsers(users) {
|
||||
return {
|
||||
type: SET_USERS,
|
||||
users
|
||||
};
|
||||
}
|
||||
|
||||
export function getAdminUsers() {
|
||||
return dispatch => {
|
||||
return api.getUserList()
|
||||
.then(users => dispatch(setUsers(users)));
|
||||
};
|
||||
}
|
@ -1,27 +0,0 @@
|
||||
import { SET_USERS } from "./actions";
|
||||
|
||||
const initialState = {
|
||||
users: [],
|
||||
//groups: [],
|
||||
//selection: [],
|
||||
//selected: "none",
|
||||
//selectedGroup: null,
|
||||
//filter: Filter.getDefault(),
|
||||
//selector: {
|
||||
// users: []
|
||||
//}
|
||||
};
|
||||
|
||||
const peopleReducer = (state = initialState, action) => {
|
||||
switch (action.type) {
|
||||
|
||||
case SET_USERS:
|
||||
return Object.assign({}, state, {
|
||||
users: action.users
|
||||
});
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
};
|
||||
|
||||
export default peopleReducer;
|
@ -1,10 +1,10 @@
|
||||
import { combineReducers } from 'redux';
|
||||
import authReducer from './auth/reducer';
|
||||
import peopleReducer from './people/reducer';
|
||||
import peopleReducer from './settings/reducer';
|
||||
|
||||
const rootReducer = combineReducers({
|
||||
auth: authReducer,
|
||||
people: peopleReducer
|
||||
settings: peopleReducer
|
||||
});
|
||||
|
||||
export default rootReducer;
|
@ -78,7 +78,6 @@ export function changePassword(userId, password, key) {
|
||||
}
|
||||
|
||||
export function changeEmail(userId, email, key) {
|
||||
|
||||
const data = { email };
|
||||
|
||||
return request({
|
||||
@ -89,7 +88,6 @@ export function changeEmail(userId, email, key) {
|
||||
});
|
||||
}
|
||||
export function updateActivationStatus(activationStatus, userId, key) {
|
||||
|
||||
return request({
|
||||
method: "put",
|
||||
url: `/people/activationstatus/${activationStatus}.json`,
|
||||
@ -154,6 +152,54 @@ export function getPortalTimezones() {
|
||||
export function getUserList() {
|
||||
return request({
|
||||
method: "get",
|
||||
url: "/people/filter?isAdministrator=true"
|
||||
url: `/people`
|
||||
});
|
||||
}
|
||||
|
||||
export function getProductAdminsList(productId) {
|
||||
return request({
|
||||
method: "get",
|
||||
url: `/settings/security/administrator/${productId}`
|
||||
});
|
||||
}
|
||||
|
||||
export function changeProductAdmin(userId, productId, administrator) {
|
||||
return request({
|
||||
method: "put",
|
||||
url: "/settings/security/administrator",
|
||||
data: {
|
||||
productId,
|
||||
userId,
|
||||
administrator
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
export function getUserById(userId) {
|
||||
return request({
|
||||
method: "get",
|
||||
url: `/people/${userId}`,
|
||||
});
|
||||
}
|
||||
|
||||
export function getGreetingSettings() {
|
||||
return request({
|
||||
method: "get",
|
||||
url: `/settings/greetingsettings.json`,
|
||||
});
|
||||
}
|
||||
|
||||
export function setGreetingSettings(title) {
|
||||
return request({
|
||||
method: "post",
|
||||
url: `/settings/greetingsettings.json`,
|
||||
data: { title }
|
||||
});
|
||||
}
|
||||
|
||||
export function restoreGreetingSettings() {
|
||||
return request({
|
||||
method: "post",
|
||||
url: `/settings/greetingsettings/restore.json`
|
||||
});
|
||||
}
|
88
web/ASC.Web.Client/src/store/settings/actions.js
Normal file
88
web/ASC.Web.Client/src/store/settings/actions.js
Normal file
@ -0,0 +1,88 @@
|
||||
import * as api from "../services/api";
|
||||
|
||||
export const SET_USERS = "SET_USERS";
|
||||
export const SET_ADMINS = "SET_ADMINS";
|
||||
export const SET_OWNER = "SET_OWNER";
|
||||
export const SET_GREETING_SETTINGS = "SET_GREETING_SETTINGS";
|
||||
|
||||
export function setUsers(users) {
|
||||
return {
|
||||
type: SET_USERS,
|
||||
users
|
||||
};
|
||||
}
|
||||
|
||||
export function setAdmins(admins) {
|
||||
return {
|
||||
type: SET_ADMINS,
|
||||
admins
|
||||
};
|
||||
}
|
||||
|
||||
export function setOwner(owner) {
|
||||
return {
|
||||
type: SET_OWNER,
|
||||
owner
|
||||
};
|
||||
}
|
||||
|
||||
export function setGreetingSettings(title) {
|
||||
return {
|
||||
type: SET_GREETING_SETTINGS,
|
||||
title
|
||||
};
|
||||
}
|
||||
|
||||
export function getListUsers() {
|
||||
return dispatch => {
|
||||
return api.getUserList().then(users => dispatch(setUsers(users)));
|
||||
};
|
||||
}
|
||||
|
||||
export function getListAdmins(productId) {
|
||||
return dispatch => {
|
||||
return api
|
||||
.getProductAdminsList(productId)
|
||||
.then(admins => dispatch(setAdmins(admins)));
|
||||
};
|
||||
}
|
||||
|
||||
export function changeAdmins(userId, productId, isAdmin) {
|
||||
return dispatch => {
|
||||
return api.changeProductAdmin(userId, productId, isAdmin).then(() => {
|
||||
dispatch(getListUsers());
|
||||
dispatch(getListAdmins(productId));
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
export function getUserById(userId) {
|
||||
return dispatch => {
|
||||
return api.getUserById(userId).then(owner => dispatch(setOwner(owner)));
|
||||
};
|
||||
}
|
||||
|
||||
export function getGreetingTitle() {
|
||||
return dispatch => {
|
||||
return api.getGreetingSettings()
|
||||
.then(greetingTitle => dispatch(setGreetingSettings(greetingTitle)));
|
||||
};
|
||||
}
|
||||
|
||||
export function setGreetingTitle(greetingTitle) {
|
||||
return dispatch => {
|
||||
return api.setGreetingSettings(greetingTitle)
|
||||
.then((res) => {
|
||||
dispatch(setGreetingSettings(greetingTitle))
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
export function restoreGreetingTitle() {
|
||||
return dispatch => {
|
||||
return api.restoreGreetingSettings()
|
||||
.then((res) => {
|
||||
dispatch(setGreetingSettings(res.companyName))
|
||||
});
|
||||
};
|
||||
}
|
33
web/ASC.Web.Client/src/store/settings/reducer.js
Normal file
33
web/ASC.Web.Client/src/store/settings/reducer.js
Normal file
@ -0,0 +1,33 @@
|
||||
import { SET_USERS, SET_ADMINS, SET_OWNER, SET_GREETING_SETTINGS } from "./actions";
|
||||
|
||||
const initialState = {
|
||||
users: [],
|
||||
admins: [],
|
||||
owner: {},
|
||||
greetingSettings: ''
|
||||
};
|
||||
|
||||
const peopleReducer = (state = initialState, action) => {
|
||||
switch (action.type) {
|
||||
case SET_USERS:
|
||||
return Object.assign({}, state, {
|
||||
users: action.users
|
||||
});
|
||||
case SET_ADMINS:
|
||||
return Object.assign({}, state, {
|
||||
admins: action.admins
|
||||
});
|
||||
case SET_OWNER:
|
||||
return Object.assign({}, state, {
|
||||
owner: action.owner
|
||||
});
|
||||
case SET_GREETING_SETTINGS:
|
||||
return Object.assign({}, state, {
|
||||
greetingSettings: action.title
|
||||
});
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
};
|
||||
|
||||
export default peopleReducer;
|
31
web/ASC.Web.Client/src/store/settings/selectors.js
Normal file
31
web/ASC.Web.Client/src/store/settings/selectors.js
Normal file
@ -0,0 +1,31 @@
|
||||
import { filter } from "lodash";
|
||||
|
||||
export function getUsers(users, ownerId) {
|
||||
return filter(users, function(f) {
|
||||
return f.id !== ownerId;
|
||||
});
|
||||
}
|
||||
|
||||
export function getAdmins(users) {
|
||||
const newArray = [];
|
||||
users.map(user => {
|
||||
if (user.listAdminModules !== undefined) {
|
||||
if (!user.listAdminModules.includes("people")) {
|
||||
newArray.push(user);
|
||||
}
|
||||
} else {
|
||||
newArray.push(user);
|
||||
}
|
||||
});
|
||||
return newArray.filter(user => !user.isVisitor);
|
||||
}
|
||||
|
||||
export function getSelectorOptions(users) {
|
||||
return users.map(user => {
|
||||
return {
|
||||
key: user.id,
|
||||
label: user.displayName,
|
||||
selected: false
|
||||
};
|
||||
});
|
||||
}
|
@ -1802,10 +1802,12 @@ asap@~2.0.6:
|
||||
integrity sha1-5QNHYR1+aQlDIIu9r+vLwvuGbUY=
|
||||
|
||||
"asc-web-components@file:../../packages/asc-web-components":
|
||||
version "1.0.147"
|
||||
version "1.0.152"
|
||||
dependencies:
|
||||
email-addresses "^3.0.3"
|
||||
moment "^2.24.0"
|
||||
prop-types "^15.7.2"
|
||||
punycode "^2.1.1"
|
||||
rc-tree "^2.1.2"
|
||||
react-autosize-textarea "^7.0.0"
|
||||
react-avatar-editor "^11.0.7"
|
||||
|
@ -26,7 +26,7 @@ module.exports = ({ config }) => {
|
||||
// add story source
|
||||
{
|
||||
test: /\.stories\.js$/,
|
||||
loaders: [require.resolve('@storybook/addon-storysource/loader')],
|
||||
loaders: [require.resolve('@storybook/source-loader')],
|
||||
enforce: 'pre',
|
||||
},
|
||||
// Process JS with Babel.
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "asc-web-components",
|
||||
"version": "1.0.147",
|
||||
"version": "1.0.156",
|
||||
"description": "Ascensio System SIA component library",
|
||||
"license": "AGPL-3.0",
|
||||
"main": "dist/asc-web-components.js",
|
||||
@ -46,16 +46,16 @@
|
||||
"@babel/preset-react": "^7.0.0",
|
||||
"@emotion/babel-preset-css-prop": "^10.0.17",
|
||||
"@storybook/addon-a11y": "^5.1.11",
|
||||
"@storybook/addon-actions": "^5.1.11",
|
||||
"@storybook/addon-actions": "^5.2.5",
|
||||
"@storybook/addon-console": "^1.2.1",
|
||||
"@storybook/addon-knobs": "^5.1.11",
|
||||
"@storybook/addon-links": "^5.1.11",
|
||||
"@storybook/addon-options": "^5.1.11",
|
||||
"@storybook/addon-storysource": "^5.1.11",
|
||||
"@storybook/addon-viewport": "^5.1.11",
|
||||
"@storybook/addons": "^5.1.11",
|
||||
"@storybook/react": "^5.1.11",
|
||||
"@svgr/rollup": "^4.3.2",
|
||||
"@storybook/addon-knobs": "^5.2.5",
|
||||
"@storybook/addon-links": "^5.2.5",
|
||||
"@storybook/addon-options": "^5.2.5",
|
||||
"@storybook/addon-storysource": "^5.2.5",
|
||||
"@storybook/addon-viewport": "^5.2.5",
|
||||
"@storybook/addons": "^5.2.5",
|
||||
"@storybook/react": "^5.2.5",
|
||||
"@svgr/rollup": "^4.3.3",
|
||||
"@svgr/webpack": "^4.3.2",
|
||||
"@testing-library/react": "^8.0.8",
|
||||
"@types/jest": "^24.0.17",
|
||||
|
@ -49,8 +49,7 @@ const getButtons = (primary) => {
|
||||
};
|
||||
|
||||
storiesOf('Components|Buttons', module)
|
||||
// To set a default viewport for all the stories for this component
|
||||
.addParameters({ viewport: { defaultViewport: 'responsive' } })
|
||||
|
||||
.addParameters({ options: { showAddonPanel: false } })
|
||||
.add('all', () => (
|
||||
<Section>
|
||||
|
@ -1,16 +1,151 @@
|
||||
import React from 'react';
|
||||
import { mount } from 'enzyme';
|
||||
import { mount, shallow } from 'enzyme';
|
||||
import ContextMenuButton from '.';
|
||||
|
||||
const baseData = () => [
|
||||
{
|
||||
key: 'key',
|
||||
label: 'label',
|
||||
onClick: () => jest.fn()
|
||||
}
|
||||
];
|
||||
|
||||
const baseProps = {
|
||||
title: 'Actions',
|
||||
iconName: 'VerticalDotsIcon',
|
||||
size: 16,
|
||||
color: '#A3A9AE',
|
||||
getData: baseData,
|
||||
isDisabled: false
|
||||
}
|
||||
|
||||
describe('<ContextMenuButton />', () => {
|
||||
it('renders without error', () => {
|
||||
const wrapper = mount(
|
||||
<ContextMenuButton
|
||||
title="Actions"
|
||||
getData={() => [{key: 'key', label: 'label', onClick: () => alert('label')}]}
|
||||
/>
|
||||
);
|
||||
const wrapper = mount(<ContextMenuButton {...baseProps} />);
|
||||
|
||||
expect(wrapper).toExist();
|
||||
});
|
||||
|
||||
it('render with full custom props', () => {
|
||||
const wrapper = mount(
|
||||
<ContextMenuButton
|
||||
color='red'
|
||||
hoverColor='red'
|
||||
clickColor='red'
|
||||
size={20}
|
||||
iconName='CatalogFolderIcon'
|
||||
iconHoverName='CatalogFolderIcon'
|
||||
iconClickName='CatalogFolderIcon'
|
||||
isFill={true}
|
||||
isDisabled={true}
|
||||
onClick={() => jest.fn()}
|
||||
onMouseEnter={() => jest.fn()}
|
||||
onMouseLeave={() => jest.fn()}
|
||||
onMouseOver={() => jest.fn()}
|
||||
onMouseOut={() => jest.fn()}
|
||||
getData={() => [
|
||||
{
|
||||
key: 'key',
|
||||
icon: 'CatalogFolderIcon',
|
||||
onClick: () => jest.fn()
|
||||
},
|
||||
{
|
||||
label: 'CatalogFolderIcon',
|
||||
onClick: () => jest.fn()
|
||||
},
|
||||
{}
|
||||
]}
|
||||
directionX='right'
|
||||
opened={true}
|
||||
/>);
|
||||
|
||||
expect(wrapper).toExist();
|
||||
});
|
||||
|
||||
it('disabled', () => {
|
||||
const wrapper = mount(<ContextMenuButton {...baseProps} isDisabled={true} />);
|
||||
|
||||
expect(wrapper.prop('isDisabled')).toEqual(true);
|
||||
});
|
||||
|
||||
it('not re-render', () => {
|
||||
const wrapper = shallow(<ContextMenuButton {...baseProps} />).instance();
|
||||
|
||||
const shouldUpdate = wrapper.shouldComponentUpdate(wrapper.props, wrapper.state);
|
||||
|
||||
expect(shouldUpdate).toBe(false);
|
||||
});
|
||||
|
||||
it('re-render', () => {
|
||||
const wrapper = shallow(<ContextMenuButton {...baseProps} />).instance();
|
||||
|
||||
const shouldUpdate = wrapper.shouldComponentUpdate({ opened: true }, wrapper.state);
|
||||
|
||||
expect(shouldUpdate).toBe(true);
|
||||
});
|
||||
|
||||
it('causes function onDropDownItemClick()', () => {
|
||||
const onClick = jest.fn();
|
||||
|
||||
const wrapper = shallow(<ContextMenuButton {...baseProps} opened={true} onClick={onClick} />);
|
||||
const instance = wrapper.instance();
|
||||
|
||||
instance.onDropDownItemClick({
|
||||
key: 'key',
|
||||
label: 'label',
|
||||
onClick: onClick
|
||||
});
|
||||
|
||||
expect(wrapper.state('isOpen')).toBe(false);
|
||||
expect(onClick).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('causes function onIconButtonClick()', () => {
|
||||
|
||||
const wrapper = shallow(<ContextMenuButton {...baseProps} isDisabled={false} opened={true} />);
|
||||
const instance = wrapper.instance();
|
||||
|
||||
instance.onIconButtonClick();
|
||||
|
||||
expect(wrapper.state('isOpen')).toBe(false);
|
||||
});
|
||||
|
||||
it('causes function onIconButtonClick() with isDisabled prop', () => {
|
||||
|
||||
const wrapper = shallow(<ContextMenuButton {...baseProps} isDisabled={true} opened={true} />);
|
||||
const instance = wrapper.instance();
|
||||
|
||||
instance.onIconButtonClick();
|
||||
|
||||
expect(wrapper.state('isOpen')).toBe(true);
|
||||
});
|
||||
|
||||
it('componentDidUpdate() state lifecycle test', () => {
|
||||
const wrapper = shallow(<ContextMenuButton {...baseProps} />);
|
||||
const instance = wrapper.instance();
|
||||
|
||||
wrapper.setState({ isOpen: false });
|
||||
|
||||
instance.componentDidUpdate(wrapper.props(), wrapper.state());
|
||||
|
||||
expect(wrapper.state()).toBe(wrapper.state());
|
||||
});
|
||||
|
||||
it('componentDidUpdate() props lifecycle test', () => {
|
||||
const wrapper = shallow(<ContextMenuButton {...baseProps} />);
|
||||
const instance = wrapper.instance();
|
||||
|
||||
instance.componentDidUpdate({ opened: true }, wrapper.state());
|
||||
|
||||
expect(wrapper.props()).toBe(wrapper.props());
|
||||
});
|
||||
|
||||
it('componentWillUnmount() props lifecycle test', () => {
|
||||
const wrapper = shallow(<ContextMenuButton {...baseProps} />);
|
||||
const instance = wrapper.instance();
|
||||
|
||||
instance.componentWillUnmount();
|
||||
|
||||
expect(wrapper).toExist(false);
|
||||
});
|
||||
});
|
||||
|
@ -6,7 +6,7 @@ import DropDown from '../drop-down'
|
||||
import IconButton from '../icon-button'
|
||||
import { handleAnyClick } from '../../utils/event';
|
||||
|
||||
const StyledOuther = styled.div`
|
||||
const StyledOuter = styled.div`
|
||||
display: inline-block;
|
||||
position: relative;
|
||||
cursor: pointer;
|
||||
@ -29,7 +29,7 @@ class ContextMenuButton extends React.Component {
|
||||
this.onIconButtonClick = this.onIconButtonClick.bind(this);
|
||||
this.onDropDownItemClick = this.onDropDownItemClick.bind(this);
|
||||
|
||||
if(props.opened)
|
||||
if (props.opened)
|
||||
handleAnyClick(true, this.handleClick);
|
||||
}
|
||||
|
||||
@ -42,23 +42,21 @@ class ContextMenuButton extends React.Component {
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps, prevState) {
|
||||
// Store prevId in state so we can compare when props change.
|
||||
// Clear out previously-loaded data (so we don't render stale stuff).
|
||||
if (this.props.opened !== prevProps.opened) {
|
||||
this.toggle(this.props.opened);
|
||||
}
|
||||
|
||||
if(this.state.isOpen !== prevState.isOpen) {
|
||||
if (this.state.isOpen !== prevState.isOpen) {
|
||||
handleAnyClick(this.state.isOpen, this.handleClick);
|
||||
}
|
||||
}
|
||||
|
||||
onIconButtonClick = () => {
|
||||
if(!this.props.isDisabled) {
|
||||
this.setState({
|
||||
data: this.props.getData(),
|
||||
isOpen: !this.state.isOpen
|
||||
});
|
||||
if (!this.props.isDisabled) {
|
||||
this.setState({
|
||||
data: this.props.getData(),
|
||||
isOpen: !this.state.isOpen
|
||||
});
|
||||
}
|
||||
else {
|
||||
this.stopAction
|
||||
@ -79,35 +77,51 @@ class ContextMenuButton extends React.Component {
|
||||
|
||||
render() {
|
||||
//console.log("ContextMenuButton render");
|
||||
const {
|
||||
color,
|
||||
hoverColor,
|
||||
clickColor,
|
||||
size,
|
||||
iconName,
|
||||
iconHoverName,
|
||||
iconClickName,
|
||||
isDisabled,
|
||||
onMouseEnter,
|
||||
onMouseLeave,
|
||||
onMouseOver,
|
||||
onMouseOut,
|
||||
directionX
|
||||
} = this.props;
|
||||
|
||||
const { isOpen } = this.state;
|
||||
|
||||
return (
|
||||
<StyledOuther ref={this.ref}>
|
||||
<StyledOuter ref={this.ref}>
|
||||
<IconButton
|
||||
color={this.props.color}
|
||||
hoverColor={this.props.hoverColor}
|
||||
clickColor={this.props.clickColor}
|
||||
size={this.props.size}
|
||||
iconName={this.props.iconName}
|
||||
iconHoverName={this.props.iconHoverName}
|
||||
iconClickName={this.props.iconClickName}
|
||||
color={color}
|
||||
hoverColor={hoverColor}
|
||||
clickColor={clickColor}
|
||||
size={size}
|
||||
iconName={iconName}
|
||||
iconHoverName={iconHoverName}
|
||||
iconClickName={iconClickName}
|
||||
isFill={false}
|
||||
isDisabled={this.props.isDisabled}
|
||||
isDisabled={isDisabled}
|
||||
onClick={this.onIconButtonClick}
|
||||
onMouseEnter={this.props.onMouseEnter}
|
||||
onMouseLeave={this.props.onMouseLeave}
|
||||
onMouseOver={this.props.onMouseOver}
|
||||
onMouseOut={this.props.onMouseOut}
|
||||
onMouseEnter={onMouseEnter}
|
||||
onMouseLeave={onMouseLeave}
|
||||
onMouseOver={onMouseOver}
|
||||
onMouseOut={onMouseOut}
|
||||
/>
|
||||
<DropDown directionX={this.props.directionX || 'left'} isOpen={this.state.isOpen}>
|
||||
<DropDown directionX={directionX} isOpen={isOpen}>
|
||||
{
|
||||
this.state.data.map(item =>
|
||||
<DropDownItem
|
||||
{...item}
|
||||
onClick={this.onDropDownItemClick.bind(this, item)}
|
||||
this.state.data.map((item, index) =>
|
||||
(item.label || item.icon) && <DropDownItem {...item} key={item.key || index} onClick={this.onDropDownItemClick.bind(this, item)}
|
||||
/>
|
||||
)
|
||||
}
|
||||
</DropDown>
|
||||
</StyledOuther>
|
||||
</StyledOuter>
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -120,7 +134,20 @@ ContextMenuButton.propTypes = {
|
||||
iconName: PropTypes.string,
|
||||
size: PropTypes.number,
|
||||
color: PropTypes.string,
|
||||
isDisabled: PropTypes.bool
|
||||
isDisabled: PropTypes.bool,
|
||||
|
||||
hoverColor: PropTypes.string,
|
||||
clickColor: PropTypes.string,
|
||||
|
||||
iconHoverName: PropTypes.string,
|
||||
iconClickName: PropTypes.string,
|
||||
|
||||
onMouseEnter: PropTypes.func,
|
||||
onMouseLeave: PropTypes.func,
|
||||
onMouseOver: PropTypes.func,
|
||||
onMouseOut: PropTypes.func,
|
||||
|
||||
directionX: PropTypes.string
|
||||
};
|
||||
|
||||
ContextMenuButton.defaultProps = {
|
||||
@ -129,7 +156,8 @@ ContextMenuButton.defaultProps = {
|
||||
title: '',
|
||||
iconName: 'VerticalDotsIcon',
|
||||
size: 16,
|
||||
isDisabled: false
|
||||
isDisabled: false,
|
||||
directionX: 'left'
|
||||
};
|
||||
|
||||
export default ContextMenuButton
|
@ -1,18 +1,76 @@
|
||||
import React from 'react';
|
||||
import { mount } from 'enzyme';
|
||||
import { mount, shallow } from 'enzyme';
|
||||
import DropDownItem from '.';
|
||||
|
||||
const baseProps = {
|
||||
isSeparator: false,
|
||||
isHeader: false,
|
||||
tabIndex: -1,
|
||||
label: 'test',
|
||||
disabled: false,
|
||||
icon: 'NavLogoIcon',
|
||||
noHover: false,
|
||||
onClick: jest.fn()
|
||||
}
|
||||
|
||||
describe('<DropDownItem />', () => {
|
||||
it('renders without error', () => {
|
||||
const wrapper = mount(
|
||||
<DropDownItem
|
||||
isSeparator={false}
|
||||
isHeader={false}
|
||||
label='Button 1'
|
||||
icon='NavLogoIcon'
|
||||
onClick={() => console.log('Button 1 clicked')} />
|
||||
<DropDownItem {...baseProps} />
|
||||
);
|
||||
|
||||
expect(wrapper).toExist();
|
||||
});
|
||||
|
||||
it('check disabled props', () => {
|
||||
const wrapper = mount(
|
||||
<DropDownItem {...baseProps} disabled={true} />
|
||||
);
|
||||
|
||||
expect(wrapper.prop('disabled')).toEqual(true);
|
||||
});
|
||||
|
||||
it('check isSeparator props', () => {
|
||||
const wrapper = mount(
|
||||
<DropDownItem {...baseProps} isSeparator={true} />
|
||||
);
|
||||
|
||||
expect(wrapper.prop('isSeparator')).toEqual(true);
|
||||
});
|
||||
|
||||
it('check isHeader props', () => {
|
||||
const wrapper = mount(
|
||||
<DropDownItem {...baseProps} isHeader={true} />
|
||||
);
|
||||
|
||||
expect(wrapper.prop('isHeader')).toEqual(true);
|
||||
});
|
||||
|
||||
it('check noHover props', () => {
|
||||
const wrapper = mount(
|
||||
<DropDownItem {...baseProps} noHover={true} />
|
||||
);
|
||||
|
||||
expect(wrapper.prop('noHover')).toEqual(true);
|
||||
});
|
||||
|
||||
it('causes function onClick()', () => {
|
||||
const onClick = jest.fn();
|
||||
|
||||
const wrapper = shallow(<DropDownItem id='test' {...baseProps} onClick={onClick} />);
|
||||
|
||||
wrapper.find("#test").simulate("click")
|
||||
|
||||
expect(onClick).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('render without child', () => {
|
||||
const wrapper = shallow(
|
||||
<DropDownItem >
|
||||
test
|
||||
</DropDownItem>
|
||||
);
|
||||
|
||||
expect(wrapper.props.children).toEqual(undefined);
|
||||
});
|
||||
});
|
||||
|
@ -35,12 +35,24 @@ describe('<DropDown />', () => {
|
||||
expect(wrapper.prop('directionX')).toEqual('right');
|
||||
});
|
||||
|
||||
it('directionX right manualX', () => {
|
||||
const wrapper = mount(<DropDown {...baseProps} directionX='right' manualX='100px' />);
|
||||
|
||||
expect(wrapper.prop('directionX')).toEqual('right');
|
||||
});
|
||||
|
||||
it('directionY top', () => {
|
||||
const wrapper = mount(<DropDown {...baseProps} directionY='top' />);
|
||||
|
||||
expect(wrapper.prop('directionY')).toEqual('top');
|
||||
});
|
||||
|
||||
it('directionY top manualY', () => {
|
||||
const wrapper = mount(<DropDown {...baseProps} directionY='top' manualY='100%' />);
|
||||
|
||||
expect(wrapper.prop('directionY')).toEqual('top');
|
||||
});
|
||||
|
||||
it('withArrow', () => {
|
||||
const wrapper = mount(<DropDown {...baseProps} withArrow />);
|
||||
|
||||
@ -74,15 +86,37 @@ describe('<DropDown />', () => {
|
||||
|
||||
expect(wrapper.children()).toHaveLength(1);
|
||||
});
|
||||
/*
|
||||
it('with maxHeight and children', () => {
|
||||
const wrapper = mount((
|
||||
<DropDown {...baseProps} maxHeight={200}>
|
||||
<div>1</div>
|
||||
</DropDown>
|
||||
));
|
||||
|
||||
expect(wrapper.children()).toHaveLength(1);
|
||||
it('with maxHeight and children', () => {
|
||||
const child = (<div>1</div>);
|
||||
const wrapper = shallow((
|
||||
<DropDown
|
||||
maxHeight={0}>
|
||||
{child}
|
||||
</DropDown>
|
||||
)).instance();
|
||||
|
||||
expect(wrapper.props.children).toEqual(child);
|
||||
});
|
||||
*/
|
||||
|
||||
it('componentDidUpdate() state lifecycle test', () => {
|
||||
const wrapper = shallow(<DropDown {...baseProps} />);
|
||||
const instance = wrapper.instance();
|
||||
|
||||
wrapper.setState({ isOpen: true });
|
||||
|
||||
instance.componentDidUpdate(wrapper.props(), wrapper.state());
|
||||
|
||||
expect(wrapper.state()).toBe(wrapper.state());
|
||||
});
|
||||
|
||||
it('componentDidUpdate() props lifecycle test', () => {
|
||||
const wrapper = shallow(<DropDown {...baseProps} />);
|
||||
const instance = wrapper.instance();
|
||||
|
||||
instance.componentDidUpdate({ opened: true }, wrapper.state());
|
||||
|
||||
expect(wrapper.props()).toBe(wrapper.props());
|
||||
});
|
||||
|
||||
});
|
||||
|
@ -94,7 +94,9 @@ class DropDown extends React.PureComponent {
|
||||
|
||||
render() {
|
||||
const {maxHeight, withArrow, directionX, children} = this.props;
|
||||
const dropDownMaxHeightProp = maxHeight ? { height: maxHeight + 'px' } : {};
|
||||
const fullHeight = children && children.length * 36;
|
||||
const calculatedHeight = ((fullHeight > 0) && (fullHeight < maxHeight)) ? fullHeight : maxHeight;
|
||||
const dropDownMaxHeightProp = maxHeight ? { height: calculatedHeight + 'px' } : {};
|
||||
//console.log("DropDown render");
|
||||
return (
|
||||
<StyledDropdown
|
||||
@ -105,7 +107,7 @@ class DropDown extends React.PureComponent {
|
||||
{withArrow && <Arrow directionX={directionX} />}
|
||||
{maxHeight
|
||||
? <FixedSizeList
|
||||
height={maxHeight}
|
||||
height={calculatedHeight}
|
||||
width={this.state.width}
|
||||
itemSize={36}
|
||||
itemCount={children.length}
|
||||
|
@ -77,6 +77,7 @@ class EmailInput extends React.Component {
|
||||
|
||||
onChangeAction = (e) => {
|
||||
this.props.onChange && this.props.onChange(e);
|
||||
|
||||
this.props.customValidateFunc ? this.props.customValidateFunc(e) : this.checkEmail(e.target.value);
|
||||
}
|
||||
|
||||
@ -96,7 +97,7 @@ class EmailInput extends React.Component {
|
||||
isValidEmail={isValid || isValidEmail}
|
||||
value={inputValue}
|
||||
onChange={this.onChangeAction}
|
||||
type='email'
|
||||
type='text'
|
||||
onValidateInput={onValidateInput}
|
||||
{...rest}
|
||||
/>
|
||||
|
@ -84,8 +84,6 @@ const LoginForm = props => {
|
||||
};
|
||||
|
||||
storiesOf('EXAMPLES|Forms', module)
|
||||
// To set a default viewport for all the stories for this component
|
||||
.addParameters({ viewport: { defaultViewport: 'responsive' } })
|
||||
.add('login', () => {
|
||||
const onSubmit = (e, credentials) => {
|
||||
console.log("onSubmit", e, credentials);
|
||||
|
@ -82,8 +82,6 @@ function getRandomInt(min, max) {
|
||||
|
||||
storiesOf("EXAMPLES|AdvancedSelector", module)
|
||||
.addDecorator(withKnobs)
|
||||
// To set a default viewport for all the stories for this component
|
||||
.addParameters({ viewport: { defaultViewport: "responsive" } })
|
||||
.add("people group selector", () => {
|
||||
return (
|
||||
<Section>
|
||||
|
@ -1,15 +1,165 @@
|
||||
import React from 'react';
|
||||
import { mount } from 'enzyme';
|
||||
import { mount, shallow } from 'enzyme';
|
||||
import GroupButton from '.';
|
||||
|
||||
describe('<GroupButton />', () => {
|
||||
it('renders without error', () => {
|
||||
const wrapper = mount(
|
||||
<GroupButton label='Group button' disabled={false} isDropdown={false} opened={false} ></GroupButton>
|
||||
);
|
||||
const baseProps = {
|
||||
label: 'test',
|
||||
disabled: false,
|
||||
opened: false,
|
||||
isDropdown: false,
|
||||
}
|
||||
|
||||
describe('<GroupButton />', () => {
|
||||
it('renders without error', () => {
|
||||
const wrapper = mount(<GroupButton {...baseProps} />);
|
||||
|
||||
expect(wrapper).toExist();
|
||||
});
|
||||
|
||||
it('renders with child', () => {
|
||||
const wrapper = mount(
|
||||
<GroupButton {...baseProps} isDropdown={true} >
|
||||
<div key='demo' label='demo'>1</div>
|
||||
</GroupButton>);
|
||||
|
||||
expect(wrapper).toExist();
|
||||
});
|
||||
|
||||
it('applies disabled prop', () => {
|
||||
const wrapper = mount(<GroupButton {...baseProps} disabled={true} />);
|
||||
|
||||
expect(wrapper.prop('disabled')).toEqual(true);
|
||||
});
|
||||
|
||||
it('applies opened prop', () => {
|
||||
const wrapper = mount(<GroupButton {...baseProps} opened={true} />);
|
||||
|
||||
expect(wrapper.prop('opened')).toEqual(true);
|
||||
expect(wrapper.state('isOpen')).toEqual(true);
|
||||
});
|
||||
|
||||
it('applies isDropdown prop', () => {
|
||||
const wrapper = mount(<GroupButton {...baseProps} isDropdown={true} />);
|
||||
|
||||
expect(wrapper.prop('isDropdown')).toEqual(true);
|
||||
});
|
||||
|
||||
it('applies activated prop', () => {
|
||||
const wrapper = mount(<GroupButton {...baseProps} activated={true} />);
|
||||
|
||||
expect(wrapper.prop('activated')).toEqual(true);
|
||||
});
|
||||
|
||||
it('applies hovered prop', () => {
|
||||
const wrapper = mount(<GroupButton {...baseProps} hovered={true} />);
|
||||
|
||||
expect(wrapper.prop('hovered')).toEqual(true);
|
||||
});
|
||||
|
||||
it('applies isSeparator prop', () => {
|
||||
const wrapper = mount(<GroupButton {...baseProps} isSeparator={true} />);
|
||||
|
||||
expect(wrapper.prop('isSeparator')).toEqual(true);
|
||||
});
|
||||
|
||||
it('applies isSelect prop', () => {
|
||||
const wrapper = mount(<GroupButton {...baseProps} isSelect={true} />);
|
||||
|
||||
expect(wrapper.prop('isSelect')).toEqual(true);
|
||||
});
|
||||
|
||||
it('applies checked prop', () => {
|
||||
const wrapper = mount(<GroupButton {...baseProps} checked={true} />);
|
||||
|
||||
expect(wrapper.prop('checked')).toEqual(true);
|
||||
});
|
||||
|
||||
it('applies isIndeterminate prop', () => {
|
||||
const wrapper = mount(<GroupButton {...baseProps} isIndeterminate={true} />);
|
||||
|
||||
expect(wrapper.prop('isIndeterminate')).toEqual(true);
|
||||
});
|
||||
|
||||
it('applies dropDownMaxHeight prop', () => {
|
||||
const wrapper = mount(<GroupButton {...baseProps} dropDownMaxHeight={100} />);
|
||||
|
||||
expect(wrapper.prop('dropDownMaxHeight')).toEqual(100);
|
||||
});
|
||||
|
||||
it('componentDidUpdate() state lifecycle test', () => {
|
||||
const wrapper = shallow(<GroupButton {...baseProps} />);
|
||||
const instance = wrapper.instance();
|
||||
|
||||
wrapper.setState({ isOpen: true });
|
||||
|
||||
instance.componentDidUpdate(wrapper.props(), wrapper.state());
|
||||
|
||||
expect(wrapper.state()).toBe(wrapper.state());
|
||||
});
|
||||
|
||||
it('componentDidUpdate() props lifecycle test', () => {
|
||||
const wrapper = shallow(<GroupButton {...baseProps} />);
|
||||
const instance = wrapper.instance();
|
||||
|
||||
instance.componentDidUpdate({ opened: true }, wrapper.state());
|
||||
|
||||
expect(wrapper.props()).toBe(wrapper.props());
|
||||
});
|
||||
|
||||
it('componentWillUnmount() props lifecycle test', () => {
|
||||
const wrapper = shallow(<GroupButton {...baseProps} />);
|
||||
const instance = wrapper.instance();
|
||||
|
||||
instance.componentWillUnmount();
|
||||
|
||||
expect(wrapper).toExist(false);
|
||||
});
|
||||
|
||||
it('causes function dropDownItemClick()', () => {
|
||||
const onClick = jest.fn();
|
||||
const onSelect = jest.fn()
|
||||
const child = (<div onClick={onClick}>1</div>);
|
||||
const wrapper = shallow(
|
||||
<GroupButton {...baseProps} opened={true} onSelect={onSelect} >
|
||||
{child}
|
||||
</GroupButton>);
|
||||
const instance = wrapper.instance();
|
||||
|
||||
instance.dropDownItemClick(child);
|
||||
|
||||
expect(wrapper.state('isOpen')).toBe(false);
|
||||
expect(onClick).toBeCalled();
|
||||
});
|
||||
|
||||
it('causes function dropDownToggleClick()', () => {
|
||||
const wrapper = shallow(
|
||||
<GroupButton {...baseProps} opened={true} />);
|
||||
const instance = wrapper.instance();
|
||||
|
||||
instance.dropDownToggleClick();
|
||||
|
||||
expect(wrapper.state('isOpen')).toBe(false);
|
||||
});
|
||||
|
||||
it('causes function checkboxChange()', () => {
|
||||
const wrapper = shallow(
|
||||
<GroupButton {...baseProps} selected='demo' />);
|
||||
const instance = wrapper.instance();
|
||||
|
||||
instance.checkboxChange();
|
||||
|
||||
expect(wrapper.state('selected')).toBe('demo');
|
||||
});
|
||||
|
||||
it('causes function handleClick()', () => {
|
||||
const wrapper = mount(<GroupButton {...baseProps} />);
|
||||
const instance = wrapper.instance();
|
||||
|
||||
instance.handleClick(new Event('click'));
|
||||
|
||||
expect(wrapper.state('isOpen')).toBe(false);
|
||||
});
|
||||
|
||||
expect(wrapper).toExist();
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
|
@ -1,40 +1,145 @@
|
||||
import React from 'react';
|
||||
import { mount } from 'enzyme';
|
||||
import { mount, shallow } from 'enzyme';
|
||||
import GroupButtonsMenu from '.';
|
||||
import DropDownItem from '../drop-down-item';
|
||||
|
||||
const defaultMenuItems = [
|
||||
{
|
||||
label: 'Select',
|
||||
isDropdown: true,
|
||||
isSeparator: true,
|
||||
isSelect: true,
|
||||
fontWeight: 'bold',
|
||||
children: [
|
||||
<DropDownItem key='aaa' label='aaa' />,
|
||||
<DropDownItem key='bbb' label='bbb' />,
|
||||
<DropDownItem key='ccc' label='ccc' />,
|
||||
],
|
||||
onSelect: () => { }
|
||||
},
|
||||
{
|
||||
label: 'Menu item 1',
|
||||
disabled: false,
|
||||
onClick: () => { }
|
||||
},
|
||||
{
|
||||
label: 'Menu item 2',
|
||||
disabled: true,
|
||||
onClick: () => { }
|
||||
}
|
||||
];
|
||||
|
||||
const baseProps = {
|
||||
checked: false,
|
||||
menuItems: defaultMenuItems,
|
||||
visible: true,
|
||||
moreLabel: 'More',
|
||||
closeTitle: 'Close'
|
||||
}
|
||||
|
||||
describe('<GroupButtonsMenu />', () => {
|
||||
it('renders without error', () => {
|
||||
const menuItems = [
|
||||
{
|
||||
label: 'Select',
|
||||
isDropdown: true,
|
||||
isSeparator: true,
|
||||
isSelect: true,
|
||||
fontWeight: 'bold',
|
||||
children: [
|
||||
<DropDownItem key='aaa' label='aaa' />,
|
||||
<DropDownItem key='bbb' label='bbb' />,
|
||||
<DropDownItem key='ccc' label='ccc' />,
|
||||
],
|
||||
onSelect: () => {}
|
||||
},
|
||||
{
|
||||
label: 'Menu item 1',
|
||||
disabled: false,
|
||||
onClick: () => {}
|
||||
},
|
||||
{
|
||||
label: 'Menu item 2',
|
||||
disabled: true,
|
||||
onClick: () => {}
|
||||
}
|
||||
];
|
||||
it('renders without error', () => {
|
||||
const wrapper = mount(<GroupButtonsMenu {...baseProps} />);
|
||||
|
||||
const wrapper = mount(
|
||||
<GroupButtonsMenu checked={false} menuItems={menuItems} visible={true} moreLabel='More' closeTitle='Close' />
|
||||
);
|
||||
expect(wrapper).toExist();
|
||||
});
|
||||
|
||||
expect(wrapper).toExist();
|
||||
it('applies checked prop', () => {
|
||||
const wrapper = mount(<GroupButtonsMenu {...baseProps} checked={true} />);
|
||||
|
||||
expect(wrapper.prop('checked')).toEqual(true);
|
||||
});
|
||||
|
||||
it('applies visible prop', () => {
|
||||
const wrapper = mount(<GroupButtonsMenu {...baseProps} visible={true} />);
|
||||
|
||||
expect(wrapper.prop('visible')).toEqual(true);
|
||||
});
|
||||
|
||||
it('causes function closeMenu()', () => {
|
||||
const onClose = jest.fn();
|
||||
const wrapper = mount(<GroupButtonsMenu {...baseProps} onClose={onClose} />);
|
||||
const instance = wrapper.instance();
|
||||
|
||||
instance.closeMenu(new Event('click'));
|
||||
|
||||
expect(wrapper.state('visible')).toBe(false);
|
||||
expect(onClose).toBeCalled();
|
||||
});
|
||||
|
||||
it('causes function groupButtonClick()', () => {
|
||||
const onClick = jest.fn();
|
||||
const item = {
|
||||
label: 'Menu item 1',
|
||||
disabled: false,
|
||||
onClick: onClick
|
||||
};
|
||||
const wrapper = mount(<GroupButtonsMenu {...baseProps} />);
|
||||
const instance = wrapper.instance();
|
||||
|
||||
instance.groupButtonClick(item);
|
||||
|
||||
expect(wrapper.state('visible')).toBe(false);
|
||||
expect(onClick).toBeCalled();
|
||||
});
|
||||
|
||||
it('causes function countMenuItems()', () => {
|
||||
const wrapper = mount(<GroupButtonsMenu {...baseProps} />);
|
||||
const instance = wrapper.instance();
|
||||
|
||||
instance.countMenuItems([], 2, 1);
|
||||
instance.countMenuItems([1, 2], 200, 2);
|
||||
instance.countMenuItems([1,2,3,4], 1, 2);
|
||||
|
||||
expect(wrapper.state('visible')).toBe(true);
|
||||
});
|
||||
|
||||
it('causes function groupButtonClick() if disabled', () => {
|
||||
const onClick = jest.fn();
|
||||
const item = {
|
||||
label: 'Menu item 1',
|
||||
disabled: true,
|
||||
onClick: onClick
|
||||
};
|
||||
const wrapper = mount(<GroupButtonsMenu {...baseProps} />);
|
||||
const instance = wrapper.instance();
|
||||
|
||||
instance.groupButtonClick(item);
|
||||
|
||||
expect(wrapper.state('visible')).toBe(true);
|
||||
expect(onClick).toBeCalledTimes(0);
|
||||
});
|
||||
|
||||
it('componentDidUpdate() props lifecycle test', () => {
|
||||
const wrapper = shallow(<GroupButtonsMenu {...baseProps} visible={false} />);
|
||||
const instance = wrapper.instance();
|
||||
|
||||
instance.componentDidUpdate({ visible: true, menuItems: defaultMenuItems }, wrapper.state());
|
||||
|
||||
expect(wrapper.props()).toBe(wrapper.props());
|
||||
});
|
||||
|
||||
it('componentWillUnmount() props lifecycle test', () => {
|
||||
const wrapper = shallow(<GroupButtonsMenu {...baseProps} />);
|
||||
const instance = wrapper.instance();
|
||||
|
||||
instance.componentWillUnmount();
|
||||
|
||||
expect(wrapper).toExist(false);
|
||||
});
|
||||
|
||||
it('filled state moreItems', () => {
|
||||
const wrapper = shallow(<GroupButtonsMenu {...baseProps} />);
|
||||
|
||||
wrapper.setState({
|
||||
moreItems: [{
|
||||
label: 'Menu item 1',
|
||||
disabled: false,
|
||||
onClick: jest.fn()
|
||||
}]
|
||||
});
|
||||
|
||||
expect(wrapper).toExist(false);
|
||||
});
|
||||
|
||||
});
|
@ -60,9 +60,9 @@ class GroupButtonsMenu extends React.PureComponent {
|
||||
this.state = {
|
||||
priorityItems: props.menuItems,
|
||||
moreItems: [],
|
||||
visible: true
|
||||
visible: props.visible
|
||||
}
|
||||
|
||||
|
||||
this.throttledResize = throttle(this.updateMenu, 300);
|
||||
}
|
||||
|
||||
@ -78,30 +78,38 @@ class GroupButtonsMenu extends React.PureComponent {
|
||||
};
|
||||
|
||||
componentDidMount() {
|
||||
const groupMenuItems = document.getElementById("groupMenu")
|
||||
? document.getElementById("groupMenu").children
|
||||
: [];
|
||||
const groupMenuElement = document.getElementById("groupMenu");
|
||||
|
||||
const groupMenuItems = groupMenuElement ? groupMenuElement.children : [0];
|
||||
const groupMenuItemsArray = [...groupMenuItems];
|
||||
|
||||
this.widthsArray = groupMenuItemsArray.map(item => item.getBoundingClientRect().width);
|
||||
this.widthsArray = groupMenuItemsArray.map(item => item.offsetWidth);
|
||||
|
||||
window.addEventListener('resize', this.throttledResize);
|
||||
|
||||
this.updateMenu();
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps) {
|
||||
componentDidUpdate(prevProps, prevState) {
|
||||
if (this.props.visible !== prevProps.visible) {
|
||||
this.setState({ visible: this.props.visible });
|
||||
}
|
||||
|
||||
if(!isArrayEqual(this.props.menuItems, prevProps.menuItems)){
|
||||
if (!isArrayEqual(this.props.menuItems, prevProps.menuItems)) {
|
||||
this.setState({ priorityItems: this.props.menuItems, });
|
||||
}
|
||||
|
||||
if (this.state.priorityItems.length !== prevState.priorityItems.length || this.state.moreItems.length !== prevState.moreItems.length) {
|
||||
this.updateMenu();
|
||||
}
|
||||
}
|
||||
|
||||
countMenuItems = (array, outerWidth, initialWidth) => {
|
||||
let total = (initialWidth + 80);
|
||||
for (let i = 0, len = array.length; i < len; i++) {
|
||||
countMenuItems = (array, outerWidth, moreWidth) => {
|
||||
const itemsArray = array || []
|
||||
const moreButton = moreWidth || 0;
|
||||
let total = (moreButton + 80);
|
||||
|
||||
for (let i = 0, len = itemsArray.length; i < len; i++) {
|
||||
if (total + array[i] > outerWidth) {
|
||||
return i < 1 ? 1 : i;
|
||||
} else {
|
||||
@ -114,12 +122,12 @@ class GroupButtonsMenu extends React.PureComponent {
|
||||
const moreMenuElement = document.getElementById("moreMenu");
|
||||
const groupMenuOuterElement = document.getElementById("groupMenuOuter");
|
||||
|
||||
const moreMenuWidth = moreMenuElement ? moreMenuElement.getBoundingClientRect().width : 0;
|
||||
const groupMenuOuterWidth = groupMenuOuterElement ? groupMenuOuterElement.getBoundingClientRect().width : 0;
|
||||
const moreMenuWidth = moreMenuElement && moreMenuElement.getBoundingClientRect().width;
|
||||
const groupMenuOuterWidth = groupMenuOuterElement && groupMenuOuterElement.getBoundingClientRect().width;
|
||||
|
||||
const visibleItemsCount = this.countMenuItems(this.widthsArray, groupMenuOuterWidth, moreMenuWidth);
|
||||
const navItemsCopy = this.props.menuItems;
|
||||
|
||||
|
||||
const priorityItems = navItemsCopy.slice(0, visibleItemsCount);
|
||||
const moreItems = priorityItems.length !== navItemsCopy.length ? navItemsCopy.slice(visibleItemsCount, navItemsCopy.length) : [];
|
||||
|
||||
@ -137,12 +145,12 @@ class GroupButtonsMenu extends React.PureComponent {
|
||||
//console.log("GroupButtonsMenu render");
|
||||
const { selected, moreLabel, closeTitle } = this.props;
|
||||
const { priorityItems, moreItems, visible } = this.state;
|
||||
|
||||
|
||||
return (
|
||||
<StyledGroupButtonsMenu id="groupMenuOuter" visible={visible} >
|
||||
<GroupMenuWrapper id="groupMenu">
|
||||
{priorityItems.map((item, i) =>
|
||||
<GroupButton
|
||||
<GroupButton
|
||||
key={`navItem-${i}`}
|
||||
label={item.label}
|
||||
isDropdown={item.isDropdown}
|
||||
@ -160,8 +168,8 @@ class GroupButtonsMenu extends React.PureComponent {
|
||||
)}
|
||||
</GroupMenuWrapper>
|
||||
{moreItems.length > 0 &&
|
||||
<GroupButton
|
||||
id="moreMenu"
|
||||
<GroupButton
|
||||
id="moreMenu"
|
||||
isDropdown={true}
|
||||
label={moreLabel}
|
||||
>
|
||||
@ -197,7 +205,7 @@ GroupButtonsMenu.propTypes = {
|
||||
GroupButtonsMenu.defaultProps = {
|
||||
checked: false,
|
||||
selected: 'Select',
|
||||
visible: PropTypes.bool,
|
||||
visible: true,
|
||||
moreLabel: 'More',
|
||||
closeTitle: 'Close'
|
||||
}
|
||||
|
@ -25,7 +25,6 @@ const data = [
|
||||
const dropdownType = ['alwaysDashed', 'appearDashedAfterHover'];
|
||||
|
||||
storiesOf('Components|LinkWithDropdown', module)
|
||||
.addParameters({ viewport: { defaultViewport: 'responsive' } })
|
||||
.addParameters({ options: { showAddonPanel: false } })
|
||||
.add('all', () => (
|
||||
<>
|
||||
|
@ -17,7 +17,6 @@ const headerStyle = {
|
||||
|
||||
|
||||
storiesOf('Components|Link', module)
|
||||
.addParameters({ viewport: { defaultViewport: 'responsive' } })
|
||||
.addParameters({ options: { showAddonPanel: false } })
|
||||
.add('all', () => (
|
||||
<>
|
||||
|
@ -89,7 +89,8 @@ Link.propTypes = {
|
||||
target: PropTypes.oneOf(["_blank", "_self", "_parent", "_top"]),
|
||||
title: PropTypes.string,
|
||||
type: PropTypes.oneOf(["action", "page"]),
|
||||
children: PropTypes.string
|
||||
children: PropTypes.string,
|
||||
className: PropTypes.string
|
||||
};
|
||||
|
||||
Link.defaultProps = {
|
||||
@ -103,6 +104,7 @@ Link.defaultProps = {
|
||||
rel: "noopener noreferrer",
|
||||
tabIndex: -1,
|
||||
type: "page",
|
||||
className: "",
|
||||
};
|
||||
|
||||
export default Link;
|
||||
|
@ -7,8 +7,6 @@ import ModuleTile from '../module-tile';
|
||||
const rowStyle = { marginTop: 8 };
|
||||
|
||||
storiesOf('Components|ModuleTile', module)
|
||||
// To set a default viewport for all the stories for this component
|
||||
.addParameters({ viewport: { defaultViewport: 'responsive' } })
|
||||
.addParameters({ options: { showAddonPanel: false }})
|
||||
.add('all', () => (
|
||||
<Container>
|
||||
|
@ -10,8 +10,6 @@ import Readme from './README.md';
|
||||
const rowStyle = { marginTop: 8 };
|
||||
|
||||
storiesOf('Components|ModuleTile', module)
|
||||
// To set a default viewport for all the stories for this component
|
||||
.addParameters({ viewport: { defaultViewport: 'responsive' } })
|
||||
.addDecorator(withKnobs)
|
||||
.addDecorator(withReadme(Readme))
|
||||
.add('base', () => (
|
||||
|
@ -46,9 +46,13 @@ const MainContainerWrapper = styled.div`
|
||||
`;
|
||||
|
||||
const MainContainer = styled.div`
|
||||
${truncateCss};
|
||||
height: 20px;
|
||||
margin-right: 8px;
|
||||
|
||||
@media ${tablet} {
|
||||
${truncateCss};
|
||||
}
|
||||
|
||||
`;
|
||||
|
||||
const MainIcons = styled.div`
|
||||
@ -57,9 +61,12 @@ const MainIcons = styled.div`
|
||||
`;
|
||||
|
||||
const SideContainerWrapper = styled.div`
|
||||
${truncateCss};
|
||||
${commonCss};
|
||||
|
||||
@media ${tablet} {
|
||||
${truncateCss};
|
||||
}
|
||||
|
||||
align-self: center;
|
||||
width: ${props => props.containerWidth ? props.containerWidth : '100px'};
|
||||
color: ${props => props.color && props.color};
|
||||
|
@ -4,6 +4,7 @@ import PropTypes from 'prop-types'
|
||||
|
||||
import Checkbox from '../checkbox'
|
||||
import ContextMenuButton from '../context-menu-button'
|
||||
import { tablet } from '../../utils/device';
|
||||
|
||||
const StyledRow = styled.div`
|
||||
cursor: default;
|
||||
@ -27,9 +28,11 @@ const StyledContent = styled.div`
|
||||
|
||||
min-width: 160px;
|
||||
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
@media ${tablet} {
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
`;
|
||||
|
||||
const StyledCheckbox = styled.div`
|
||||
|
@ -26,7 +26,6 @@ const StyledContainerInline = styled.div`
|
||||
`;
|
||||
|
||||
storiesOf('Components|SelectedItem', module)
|
||||
.addParameters({ viewport: { defaultViewport: 'responsive' } })
|
||||
.addParameters({ options: { showAddonPanel: false } })
|
||||
.add('all', () => {
|
||||
|
||||
|
@ -36,6 +36,7 @@ const SelectorAddButton = (props) => {
|
||||
iconName='PlusIcon'
|
||||
isFill={true}
|
||||
isDisabled={isDisabled}
|
||||
isClickable={!isDisabled}
|
||||
/>
|
||||
</StyledButton>
|
||||
);
|
||||
|
@ -48,6 +48,5 @@ class TostWrapper extends React.Component {
|
||||
}
|
||||
|
||||
storiesOf('Components|Toast', module)
|
||||
.addParameters({ viewport: { defaultViewport: 'responsive' } })
|
||||
.addParameters({ options: { showAddonPanel: false } })
|
||||
.add('all', () => (<TostWrapper />));
|
||||
|
@ -59,4 +59,4 @@ export { default as DatePicker } from './components/date-picker'
|
||||
export { default as PasswordInput } from './components/password-input'
|
||||
export { default as Tooltip } from './components/tooltip'
|
||||
export { default as HelpButton } from './components/help-button'
|
||||
|
||||
export { default as EmailInput } from './components/email-input'
|
||||
|
@ -1,7 +1,7 @@
|
||||
import emailAddresses from "email-addresses";
|
||||
import punycode from "punycode";
|
||||
import { parseErrorTypes } from "./../constants";
|
||||
import { EmailSettings } from './index';
|
||||
import { EmailSettings } from './emailSettings';
|
||||
|
||||
const getParts = string => {
|
||||
let mass = [];
|
||||
|
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue
Block a user