Merge branch 'master' into feature/elastic
This commit is contained in:
commit
3fa9046bec
@ -1,5 +1,4 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
@ -101,23 +100,24 @@ namespace ASC.Common.DependencyInjection
|
||||
void LoadAssembly(string type)
|
||||
{
|
||||
var dll = type.Substring(type.IndexOf(",") + 1).Trim();
|
||||
var productPath = Path.Combine(productsDir, dll, subfolder);
|
||||
var path = GetPath(Path.Combine(productPath, "bin"), dll, SearchOption.AllDirectories) ?? GetPath(productPath, dll, SearchOption.TopDirectoryOnly);
|
||||
var path = GetFullPath(dll);
|
||||
|
||||
if (!string.IsNullOrEmpty(path))
|
||||
{
|
||||
AssemblyLoadContext.Default.Resolving += Default_Resolving(path);
|
||||
|
||||
Func<AssemblyLoadContext, AssemblyName, Assembly> Default_Resolving(string path)
|
||||
AssemblyLoadContext.Default.Resolving += (c, n) =>
|
||||
{
|
||||
return (c, n) =>
|
||||
{
|
||||
return c.LoadFromAssemblyPath(Path.Combine(Path.GetDirectoryName(path), $"{n.Name}.dll"));
|
||||
};
|
||||
}
|
||||
var path = GetFullPath(n.Name);
|
||||
return c.LoadFromAssemblyPath(Path.Combine(Path.GetDirectoryName(path), $"{n.Name}.dll"));
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
string GetFullPath(string n)
|
||||
{
|
||||
var productPath = Path.Combine(productsDir, n, subfolder);
|
||||
return GetPath(Path.Combine(productPath, "bin"), n, SearchOption.AllDirectories) ?? GetPath(productPath, n, SearchOption.TopDirectoryOnly);
|
||||
}
|
||||
|
||||
string GetPath(string dirPath, string dll, SearchOption searchOption)
|
||||
{
|
||||
if (!Directory.Exists(dirPath)) return null;
|
||||
|
@ -303,12 +303,13 @@ namespace ASC.Core.Data
|
||||
public void RemoveGroup(int tenant, Guid id, bool immediate)
|
||||
{
|
||||
var ids = CollectGroupChilds(tenant, id);
|
||||
var stringIds = ids.Select(r => r.ToString()).ToList();
|
||||
|
||||
using var tr = UserDbContext.Database.BeginTransaction();
|
||||
|
||||
UserDbContext.Acl.RemoveRange(UserDbContext.Acl.Where(r => r.Tenant == tenant && ids.Any(i => i == r.Subject)));
|
||||
UserDbContext.Subscriptions.RemoveRange(UserDbContext.Subscriptions.Where(r => r.Tenant == tenant && ids.Any(i => i.ToString() == r.Recipient)));
|
||||
UserDbContext.SubscriptionMethods.RemoveRange(UserDbContext.SubscriptionMethods.Where(r => r.Tenant == tenant && ids.Any(i => i.ToString() == r.Recipient)));
|
||||
UserDbContext.Subscriptions.RemoveRange(UserDbContext.Subscriptions.Where(r => r.Tenant == tenant && stringIds.Any(i => i == r.Recipient)));
|
||||
UserDbContext.SubscriptionMethods.RemoveRange(UserDbContext.SubscriptionMethods.Where(r => r.Tenant == tenant && stringIds.Any(i => i == r.Recipient)));
|
||||
|
||||
var userGroups = UserDbContext.UserGroups.Where(r => r.Tenant == tenant && ids.Any(i => i == r.GroupId));
|
||||
var groups = UserDbContext.Groups.Where(r => r.Tenant == tenant && ids.Any(i => i == r.Id));
|
||||
@ -633,11 +634,11 @@ namespace ASC.Core.Data
|
||||
if (!string.IsNullOrEmpty(text))
|
||||
{
|
||||
q = q.Where(
|
||||
u => u.FirstName.Contains(text) ||
|
||||
u.LastName.Contains(text) ||
|
||||
u.Title.Contains(text) ||
|
||||
u.Location.Contains(text) ||
|
||||
u.Email.Contains(text));
|
||||
u => u.FirstName.Contains(text, StringComparison.InvariantCultureIgnoreCase) ||
|
||||
u.LastName.Contains(text, StringComparison.InvariantCultureIgnoreCase) ||
|
||||
u.Title.Contains(text, StringComparison.InvariantCultureIgnoreCase) ||
|
||||
u.Location.Contains(text, StringComparison.InvariantCultureIgnoreCase) ||
|
||||
u.Email.Contains(text, StringComparison.InvariantCultureIgnoreCase));
|
||||
}
|
||||
|
||||
return q;
|
||||
|
@ -125,7 +125,8 @@ class SectionBodyContent extends React.Component {
|
||||
key: GUID_EMPTY,
|
||||
label: t("LblSelect"),
|
||||
default: true
|
||||
}
|
||||
},
|
||||
nameError: null
|
||||
};
|
||||
|
||||
return newState;
|
||||
@ -193,7 +194,10 @@ class SectionBodyContent extends React.Component {
|
||||
const { group, t, groupCaption } = this.props;
|
||||
const { groupName, groupManager, groupMembers } = this.state;
|
||||
|
||||
if (!groupName || !groupName.trim().length) return false;
|
||||
if (!groupName || !groupName.trim().length) {
|
||||
this.setState({nameError: t('EmptyFieldError')});
|
||||
return false;
|
||||
}
|
||||
|
||||
this.setState({ inLoading: true });
|
||||
|
||||
@ -253,6 +257,11 @@ class SectionBodyContent extends React.Component {
|
||||
}
|
||||
}
|
||||
|
||||
onFocusName = () => {
|
||||
if(this.state.nameError)
|
||||
this.setState({ nameError: null });
|
||||
}
|
||||
|
||||
render() {
|
||||
const { t, groupHeadCaption, groupsCaption, me } = this.props;
|
||||
const {
|
||||
@ -264,14 +273,16 @@ class SectionBodyContent extends React.Component {
|
||||
error,
|
||||
searchValue,
|
||||
groupManager,
|
||||
buttonLabel
|
||||
buttonLabel,
|
||||
nameError
|
||||
} = this.state;
|
||||
return (
|
||||
<MainContainer>
|
||||
<FieldContainer
|
||||
className="group-name_container"
|
||||
isRequired={true}
|
||||
hasError={false}
|
||||
hasError={!!nameError}
|
||||
errorMessage={nameError}
|
||||
isVertical={true}
|
||||
labelText={t("Name")}
|
||||
>
|
||||
@ -283,9 +294,11 @@ class SectionBodyContent extends React.Component {
|
||||
isBold={true}
|
||||
tabIndex={1}
|
||||
value={groupName}
|
||||
hasError={!!nameError}
|
||||
onChange={this.onGroupChange}
|
||||
isDisabled={inLoading}
|
||||
onKeyUp={this.onKeyPress}
|
||||
onFocus={this.onFocusName}
|
||||
/>
|
||||
</FieldContainer>
|
||||
<FieldContainer
|
||||
|
@ -12,7 +12,6 @@ if (process.env.NODE_ENV === "production") {
|
||||
.init({
|
||||
lng: localStorage.getItem(LANGUAGE) || 'en',
|
||||
fallbackLng: "en",
|
||||
debug: true,
|
||||
|
||||
interpolation: {
|
||||
escapeValue: false, // not needed for react as it escapes by default
|
||||
|
@ -11,5 +11,6 @@
|
||||
|
||||
"CustomNewDepartment": "{{groupCaption}} (creation)",
|
||||
"SearchAddedMembers": "Search added members",
|
||||
"MeLabel": "Me"
|
||||
"MeLabel": "Me",
|
||||
"EmptyFieldError": "Empty field"
|
||||
}
|
@ -11,5 +11,6 @@
|
||||
|
||||
"CustomNewDepartment": "{{groupCaption}} (создание)",
|
||||
"SearchAddedMembers": "Поиск добавленных участников",
|
||||
"MeLabel": "Я"
|
||||
"MeLabel": "Я",
|
||||
"EmptyFieldError": "Пустое поле"
|
||||
}
|
@ -39,7 +39,7 @@ const { resendUserInvites } = api.people;
|
||||
const { EmployeeStatus } = constants;
|
||||
const { Filter } = api;
|
||||
|
||||
|
||||
const isRefetchPeople = true;
|
||||
class SectionBodyContent extends React.PureComponent {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
@ -102,7 +102,7 @@ class SectionBodyContent extends React.PureComponent {
|
||||
const { updateUserStatus, onLoading, t } = this.props;
|
||||
|
||||
onLoading(true);
|
||||
updateUserStatus(EmployeeStatus.Disabled, [user.id])
|
||||
updateUserStatus(EmployeeStatus.Disabled, [user.id], isRefetchPeople)
|
||||
.then(() => toastr.success(t('SuccessChangeUserStatus')))
|
||||
.catch(error => toastr.error(error))
|
||||
.finally(() => onLoading(false));
|
||||
@ -112,7 +112,7 @@ class SectionBodyContent extends React.PureComponent {
|
||||
const { updateUserStatus, onLoading, t } = this.props;
|
||||
|
||||
onLoading(true);
|
||||
updateUserStatus(EmployeeStatus.Active, [user.id])
|
||||
updateUserStatus(EmployeeStatus.Active, [user.id], isRefetchPeople)
|
||||
.then(() => toastr.success(t('SuccessChangeUserStatus')))
|
||||
.catch(error => toastr.error(error))
|
||||
.finally(() => onLoading(false));
|
||||
@ -153,12 +153,10 @@ class SectionBodyContent extends React.PureComponent {
|
||||
resendUserInvites([user.id])
|
||||
.then(() =>
|
||||
toastr.success(
|
||||
<Text>
|
||||
<Trans i18nKey='MessageEmailActivationInstuctionsSentOnEmail' i18n={i18n}>
|
||||
The email activation instructions have been sent to the
|
||||
<strong>{{email: user.email}}</strong> email address
|
||||
<Trans i18nKey='MessageEmailActivationInstuctionsSentOnEmail' i18n={i18n}>
|
||||
The email activation instructions have been sent to the
|
||||
<strong>{{ email: user.email }}</strong> email address
|
||||
</Trans>
|
||||
</Text>
|
||||
)
|
||||
)
|
||||
.catch(error => toastr.error(error))
|
||||
@ -317,97 +315,97 @@ class SectionBodyContent extends React.PureComponent {
|
||||
const { dialogsVisible, user } = this.state;
|
||||
|
||||
return users == null
|
||||
? (<Loader className="pageLoader" type="rombs" size='40px' />)
|
||||
: users.length > 0 ? (
|
||||
<>
|
||||
<RowContainer useReactWindow={false}>
|
||||
{users.map(user => {
|
||||
const contextOptions = this.getUserContextOptions(user, viewer);
|
||||
const contextOptionsProps = !contextOptions.length
|
||||
? {}
|
||||
: { contextOptions };
|
||||
const checked = isUserSelected(selection, user.id);
|
||||
const checkedProps = isAdmin(viewer) ? { checked } : {};
|
||||
const element = (
|
||||
<Avatar
|
||||
size="small"
|
||||
role={getUserRole(user)}
|
||||
userName={user.displayName}
|
||||
source={user.avatar}
|
||||
/>
|
||||
);
|
||||
|
||||
return (
|
||||
<Row
|
||||
key={user.id}
|
||||
status={getUserStatus(user)}
|
||||
data={user}
|
||||
element={element}
|
||||
onSelect={this.onContentRowSelect}
|
||||
{...checkedProps}
|
||||
{...contextOptionsProps}
|
||||
needForUpdate={this.needForUpdate}
|
||||
>
|
||||
<UserContent
|
||||
user={user}
|
||||
history={history}
|
||||
settings={settings}
|
||||
? (<Loader className="pageLoader" type="rombs" size='40px' />)
|
||||
: users.length > 0 ? (
|
||||
<>
|
||||
<RowContainer useReactWindow={false}>
|
||||
{users.map(user => {
|
||||
const contextOptions = this.getUserContextOptions(user, viewer);
|
||||
const contextOptionsProps = !contextOptions.length
|
||||
? {}
|
||||
: { contextOptions };
|
||||
const checked = isUserSelected(selection, user.id);
|
||||
const checkedProps = isAdmin(viewer) ? { checked } : {};
|
||||
const element = (
|
||||
<Avatar
|
||||
size="small"
|
||||
role={getUserRole(user)}
|
||||
userName={user.displayName}
|
||||
source={user.avatar}
|
||||
/>
|
||||
</Row>
|
||||
);
|
||||
})}
|
||||
</RowContainer>
|
||||
);
|
||||
|
||||
{dialogsVisible.changeEmail &&
|
||||
<ChangeEmailDialog
|
||||
visible={dialogsVisible.changeEmail}
|
||||
onClose={this.toggleChangeEmailDialog}
|
||||
user={user}
|
||||
/>
|
||||
}
|
||||
{dialogsVisible.changePassword &&
|
||||
<ChangePasswordDialog
|
||||
visible={dialogsVisible.changePassword}
|
||||
onClose={this.toggleChangePasswordDialog}
|
||||
email={user.email}
|
||||
/>
|
||||
}
|
||||
return (
|
||||
<Row
|
||||
key={user.id}
|
||||
status={getUserStatus(user)}
|
||||
data={user}
|
||||
element={element}
|
||||
onSelect={this.onContentRowSelect}
|
||||
{...checkedProps}
|
||||
{...contextOptionsProps}
|
||||
needForUpdate={this.needForUpdate}
|
||||
>
|
||||
<UserContent
|
||||
user={user}
|
||||
history={history}
|
||||
settings={settings}
|
||||
/>
|
||||
</Row>
|
||||
);
|
||||
})}
|
||||
</RowContainer>
|
||||
|
||||
{dialogsVisible.deleteSelfProfile &&
|
||||
<DeleteSelfProfileDialog
|
||||
visible={dialogsVisible.deleteSelfProfile}
|
||||
onClose={this.toggleDeleteSelfProfileDialog}
|
||||
email={user.email}
|
||||
/>
|
||||
}
|
||||
|
||||
{dialogsVisible.deleteProfileEver &&
|
||||
<DeleteProfileEverDialog
|
||||
visible={dialogsVisible.deleteProfileEver}
|
||||
onClose={this.toggleDeleteProfileEverDialog}
|
||||
user={user}
|
||||
filter={filter}
|
||||
settings={settings}
|
||||
history={history}
|
||||
/>
|
||||
}
|
||||
</>
|
||||
) : (
|
||||
<EmptyScreenContainer
|
||||
imageSrc="images/empty_screen_filter.png"
|
||||
imageAlt="Empty Screen Filter image"
|
||||
headerText={t("NotFoundTitle")}
|
||||
descriptionText={t("NotFoundDescription")}
|
||||
buttons={
|
||||
<>
|
||||
<Icons.CrossIcon size="small" style={{ marginRight: "4px" }} />
|
||||
<Link type="action" isHovered={true} onClick={this.onResetFilter}>
|
||||
{t("ClearButton")}
|
||||
</Link>
|
||||
</>
|
||||
{dialogsVisible.changeEmail &&
|
||||
<ChangeEmailDialog
|
||||
visible={dialogsVisible.changeEmail}
|
||||
onClose={this.toggleChangeEmailDialog}
|
||||
user={user}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
);
|
||||
{dialogsVisible.changePassword &&
|
||||
<ChangePasswordDialog
|
||||
visible={dialogsVisible.changePassword}
|
||||
onClose={this.toggleChangePasswordDialog}
|
||||
email={user.email}
|
||||
/>
|
||||
}
|
||||
|
||||
{dialogsVisible.deleteSelfProfile &&
|
||||
<DeleteSelfProfileDialog
|
||||
visible={dialogsVisible.deleteSelfProfile}
|
||||
onClose={this.toggleDeleteSelfProfileDialog}
|
||||
email={user.email}
|
||||
/>
|
||||
}
|
||||
|
||||
{dialogsVisible.deleteProfileEver &&
|
||||
<DeleteProfileEverDialog
|
||||
visible={dialogsVisible.deleteProfileEver}
|
||||
onClose={this.toggleDeleteProfileEverDialog}
|
||||
user={user}
|
||||
filter={filter}
|
||||
settings={settings}
|
||||
history={history}
|
||||
/>
|
||||
}
|
||||
</>
|
||||
) : (
|
||||
<EmptyScreenContainer
|
||||
imageSrc="images/empty_screen_filter.png"
|
||||
imageAlt="Empty Screen Filter image"
|
||||
headerText={t("NotFoundTitle")}
|
||||
descriptionText={t("NotFoundDescription")}
|
||||
buttons={
|
||||
<>
|
||||
<Icons.CrossIcon size="small" style={{ marginRight: "4px" }} />
|
||||
<Link type="action" isHovered={true} onClick={this.onResetFilter}>
|
||||
{t("ClearButton")}
|
||||
</Link>
|
||||
</>
|
||||
}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -3,13 +3,12 @@ import { withRouter } from "react-router";
|
||||
import { RowContent, Link, LinkWithDropdown, Icons, Text } from "asc-web-components";
|
||||
import { connect } from "react-redux";
|
||||
import { getUserStatus } from "../../../../../store/people/selectors";
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { history } from "asc-web-common";
|
||||
|
||||
const getFormatedGroups = (user, status) => {
|
||||
let temp = [];
|
||||
const groups = user.groups;
|
||||
const linkColor = status === 'pending' ? '#D0D5DA' : '#A3A9AE';
|
||||
const linkColor = status === 'disabled' ? '#D0D5DA' : '#A3A9AE';
|
||||
|
||||
if (!groups) temp.push({ key: 0, label: '' });
|
||||
|
||||
@ -73,9 +72,8 @@ const UserContent = ({ user, history, settings }) => {
|
||||
[email]
|
||||
);
|
||||
|
||||
const nameColor = status === 'pending' ? '#A3A9AE' : '#333333';
|
||||
const sideInfoColor = ((status === 'pending') || (status === 'disabled')) ? '#D0D5DA' : '#A3A9AE';
|
||||
//const { t } = useTranslation();
|
||||
const nameColor = status === 'disabled' ? '#A3A9AE' : '#333333';
|
||||
const sideInfoColor = status === 'disabled' ? '#D0D5DA' : '#A3A9AE';
|
||||
|
||||
const headDepartmentStyle = {
|
||||
width: '110px'
|
||||
|
@ -27,6 +27,8 @@ const { isAdmin } = store.auth.selectors;
|
||||
const { resendUserInvites, deleteUsers } = api.people;
|
||||
const { EmployeeStatus, EmployeeType } = constants;
|
||||
|
||||
const isRefetchPeople = true;
|
||||
|
||||
const StyledContainer = styled.div`
|
||||
|
||||
@media (min-width: 1024px) {
|
||||
@ -35,6 +37,15 @@ const StyledContainer = styled.div`
|
||||
|
||||
.group-button-menu-container {
|
||||
margin: 0 -16px;
|
||||
-webkit-tap-highlight-color: rgba(0, 0, 0, 0);
|
||||
|
||||
@media (max-width: 1024px) {
|
||||
& > div:first-child {
|
||||
position: absolute;
|
||||
top: 56px;
|
||||
z-index: 180;
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 1024px) {
|
||||
margin: 0 -24px;
|
||||
@ -90,14 +101,20 @@ const SectionHeaderContent = props => {
|
||||
//console.log("SectionHeaderContent render", selection, selectedUserIds);
|
||||
|
||||
const onSetActive = useCallback(() => {
|
||||
updateUserStatus(EmployeeStatus.Active, selectedUserIds);
|
||||
toastr.success(t("SuccessChangeUserStatus"));
|
||||
}, [selectedUserIds, updateUserStatus, t]);
|
||||
onLoading(true);
|
||||
updateUserStatus(EmployeeStatus.Active, selectedUserIds, isRefetchPeople)
|
||||
.then(() => toastr.success(t("SuccessChangeUserStatus")))
|
||||
.catch(error => toastr.error(error))
|
||||
.finally(() => onLoading(false));
|
||||
}, [selectedUserIds, updateUserStatus, t, onLoading]);
|
||||
|
||||
const onSetDisabled = useCallback(() => {
|
||||
updateUserStatus(EmployeeStatus.Disabled, selectedUserIds);
|
||||
toastr.success(t("SuccessChangeUserStatus"));
|
||||
}, [selectedUserIds, updateUserStatus, t]);
|
||||
onLoading(true);
|
||||
updateUserStatus(EmployeeStatus.Disabled, selectedUserIds, isRefetchPeople)
|
||||
.then(() => toastr.success(t("SuccessChangeUserStatus")))
|
||||
.catch(error => toastr.error(error))
|
||||
.finally(() => onLoading(false));
|
||||
}, [selectedUserIds, updateUserStatus, t, onLoading]);
|
||||
|
||||
const onSetEmployee = useCallback(() => {
|
||||
updateUserType(EmployeeType.User, selectedUserIds);
|
||||
@ -215,7 +232,7 @@ const SectionHeaderContent = props => {
|
||||
history.push(`${settings.homepage}/group/create`);
|
||||
}, [history, settings]);
|
||||
|
||||
const onInvitationDialogClick = useCallback(() =>
|
||||
const onInvitationDialogClick = useCallback(() =>
|
||||
setDialogVisible(!dialogVisible), [dialogVisible]
|
||||
);
|
||||
|
||||
@ -268,51 +285,51 @@ const SectionHeaderContent = props => {
|
||||
/>
|
||||
</div>
|
||||
) : (
|
||||
<div className="header-container">
|
||||
{group ? (
|
||||
<>
|
||||
<Headline className='headline-header' type="content" truncate={true}>{group.name}</Headline>
|
||||
{isAdmin && (
|
||||
<ContextMenuButton
|
||||
className="action-button"
|
||||
directionX="right"
|
||||
title={t("Actions")}
|
||||
iconName="VerticalDotsIcon"
|
||||
size={16}
|
||||
color="#A3A9AE"
|
||||
getData={getContextOptionsGroup}
|
||||
isDisabled={false}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<Headline className='headline-header' truncate={true} type="content">{settings.customNames.groupsCaption}</Headline>
|
||||
{isAdmin && (
|
||||
<>
|
||||
<ContextMenuButton
|
||||
className="action-button"
|
||||
directionX="left"
|
||||
title={t("Actions")}
|
||||
iconName="PlusIcon"
|
||||
size={16}
|
||||
color="#657077"
|
||||
getData={getContextOptionsPlus}
|
||||
isDisabled={false}
|
||||
/>
|
||||
{dialogVisible &&
|
||||
<InviteDialog
|
||||
visible={dialogVisible}
|
||||
onClose={onInvitationDialogClick}
|
||||
onCloseButton={onInvitationDialogClick}
|
||||
<div className="header-container">
|
||||
{group ? (
|
||||
<>
|
||||
<Headline className='headline-header' type="content" truncate={true}>{group.name}</Headline>
|
||||
{isAdmin && (
|
||||
<ContextMenuButton
|
||||
className="action-button"
|
||||
directionX="right"
|
||||
title={t("Actions")}
|
||||
iconName="VerticalDotsIcon"
|
||||
size={16}
|
||||
color="#A3A9AE"
|
||||
getData={getContextOptionsGroup}
|
||||
isDisabled={false}
|
||||
/>
|
||||
}
|
||||
)}
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<Headline className='headline-header' truncate={true} type="content">{settings.customNames.groupsCaption}</Headline>
|
||||
{isAdmin && (
|
||||
<>
|
||||
<ContextMenuButton
|
||||
className="action-button"
|
||||
directionX="right"
|
||||
title={t("Actions")}
|
||||
iconName="PlusIcon"
|
||||
size={16}
|
||||
color="#657077"
|
||||
getData={getContextOptionsPlus}
|
||||
isDisabled={false}
|
||||
/>
|
||||
{dialogVisible &&
|
||||
<InviteDialog
|
||||
visible={dialogVisible}
|
||||
onClose={onInvitationDialogClick}
|
||||
onCloseButton={onInvitationDialogClick}
|
||||
/>
|
||||
}
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</StyledContainer>
|
||||
);
|
||||
};
|
||||
|
@ -12,7 +12,6 @@ if (process.env.NODE_ENV === "production") {
|
||||
.init({
|
||||
lng: localStorage.getItem(LANGUAGE) || 'en',
|
||||
fallbackLng: "en",
|
||||
debug: true,
|
||||
|
||||
interpolation: {
|
||||
escapeValue: false, // not needed for react as it escapes by default
|
||||
|
@ -1,6 +1,8 @@
|
||||
import i18n from "i18next";
|
||||
import Backend from "i18next-xhr-backend";
|
||||
import config from "../../../../package.json";
|
||||
import { constants } from 'asc-web-common';
|
||||
const { LANGUAGE } = constants;
|
||||
|
||||
const newInstance = i18n.createInstance();
|
||||
|
||||
@ -8,9 +10,8 @@ if (process.env.NODE_ENV === "production") {
|
||||
newInstance
|
||||
.use(Backend)
|
||||
.init({
|
||||
lng: 'en',
|
||||
lng: localStorage.getItem(LANGUAGE) || 'en',
|
||||
fallbackLng: "en",
|
||||
debug: true,
|
||||
|
||||
interpolation: {
|
||||
escapeValue: false // not needed for react as it escapes by default
|
||||
@ -33,7 +34,7 @@ if (process.env.NODE_ENV === "production") {
|
||||
|
||||
newInstance.init({
|
||||
resources: resources,
|
||||
lng: 'en',
|
||||
lng: localStorage.getItem(LANGUAGE) || 'en',
|
||||
fallbackLng: "en",
|
||||
debug: true,
|
||||
|
||||
|
@ -1,7 +1,6 @@
|
||||
import React from "react";
|
||||
import { connect } from "react-redux";
|
||||
import {
|
||||
Text,
|
||||
IconButton,
|
||||
ContextMenuButton,
|
||||
toastr,
|
||||
@ -13,14 +12,18 @@ import {
|
||||
getUserStatus,
|
||||
toEmployeeWrapper
|
||||
} from "../../../../../store/people/selectors";
|
||||
import { withTranslation } from "react-i18next";
|
||||
import { withTranslation, Trans } from "react-i18next";
|
||||
import {
|
||||
updateUserStatus
|
||||
} from "../../../../../store/people/actions";
|
||||
import {
|
||||
updateProfile
|
||||
} from "../../../../../store/profile/actions";
|
||||
import { fetchProfile, getUserPhoto } from "../../../../../store/profile/actions";
|
||||
import styled from "styled-components";
|
||||
import { store, api, constants } from "asc-web-common";
|
||||
import { DeleteSelfProfileDialog, ChangePasswordDialog, ChangeEmailDialog, DeleteProfileEverDialog } from '../../../../dialogs';
|
||||
import i18n from '../../i18n';
|
||||
const { isAdmin, isMe } = store.auth.selectors;
|
||||
const {
|
||||
resendUserInvites,
|
||||
@ -43,7 +46,7 @@ const StyledContainer = styled.div`
|
||||
margin-left: auto;
|
||||
|
||||
& > div:first-child {
|
||||
padding: 8px 16px 8px 16px;
|
||||
padding: 8px 16px 8px 0px;
|
||||
margin-right: -16px;
|
||||
}
|
||||
}
|
||||
@ -171,18 +174,22 @@ class SectionHeaderContent extends React.PureComponent {
|
||||
response.max +
|
||||
"?_=" +
|
||||
Math.floor(Math.random() * Math.floor(10000));
|
||||
toastr.success("Success");
|
||||
this.setState(stateCopy);
|
||||
})
|
||||
.catch(error => toastr.error(error))
|
||||
.then(() => this.props.fetchProfile(this.state.profile.id));
|
||||
.then(() => this.props.updateProfile(this.props.profile))
|
||||
.then(() => this.props.fetchProfile(this.state.profile.id))
|
||||
.then(() => toastr.success(this.props.t("ChangesApplied")))
|
||||
.catch((error) => {
|
||||
toastr.error(error);
|
||||
});
|
||||
} else {
|
||||
deleteAvatar(this.state.profile.id)
|
||||
.then(response => {
|
||||
let stateCopy = Object.assign({}, this.state);
|
||||
stateCopy.visibleAvatarEditor = false;
|
||||
stateCopy.profile.avatarMax = response.big;
|
||||
toastr.success("Success");
|
||||
toastr.success(this.props.t('ChangesApplied'));
|
||||
this.setState(stateCopy);
|
||||
})
|
||||
.catch(error => toastr.error(error));
|
||||
@ -205,11 +212,13 @@ class SectionHeaderContent extends React.PureComponent {
|
||||
};
|
||||
|
||||
onUpdateUserStatus = (status, userId) => {
|
||||
const { fetchProfile, updateUserStatus } = this.props;
|
||||
const { fetchProfile, updateUserStatus, t } = this.props;
|
||||
|
||||
updateUserStatus(status, new Array(userId)).then(() =>
|
||||
fetchProfile(userId)
|
||||
);
|
||||
updateUserStatus(status, new Array(userId))
|
||||
.then(() => this.props.updateProfile(this.props.profile))
|
||||
.then(() => fetchProfile(userId))
|
||||
.then(() => toastr.success(t('SuccessChangeUserStatus')))
|
||||
.catch(error => toastr.error(error))
|
||||
};
|
||||
|
||||
onDisableClick = () =>
|
||||
@ -241,10 +250,10 @@ class SectionHeaderContent extends React.PureComponent {
|
||||
resendUserInvites(new Array(this.state.profile.id))
|
||||
.then(() =>
|
||||
toastr.success(
|
||||
<Text>
|
||||
The email activation instructions have been sent to the{" "}
|
||||
<b>{this.state.profile.email}</b> email address
|
||||
</Text>
|
||||
<Trans i18nKey='MessageEmailActivationInstuctionsSentOnEmail' i18n={i18n}>
|
||||
The email activation instructions have been sent to the
|
||||
<strong>{{ email: this.state.profile.email }}</strong> email address
|
||||
</Trans>
|
||||
)
|
||||
)
|
||||
.catch(error => toastr.error(error));
|
||||
@ -415,6 +424,7 @@ class SectionHeaderContent extends React.PureComponent {
|
||||
unknownTypeError={t("ErrorUnknownFileImageType")}
|
||||
maxSizeFileError={t("maxSizeFileError")}
|
||||
unknownError={t("Error")}
|
||||
saveButtonLabel={t('SaveButton')}
|
||||
/>
|
||||
|
||||
{dialogsVisible.deleteSelfProfile &&
|
||||
@ -468,5 +478,5 @@ const mapStateToProps = state => {
|
||||
|
||||
export default connect(
|
||||
mapStateToProps,
|
||||
{ updateUserStatus, fetchProfile }
|
||||
{ updateUserStatus, fetchProfile, updateProfile }
|
||||
)(withRouter(withTranslation()(SectionHeaderContent)));
|
||||
|
@ -10,7 +10,6 @@ if (process.env.NODE_ENV === "production") {
|
||||
newInstance.use(Backend).init({
|
||||
lng: localStorage.getItem(LANGUAGE) || 'en',
|
||||
fallbackLng: "en",
|
||||
debug: true,
|
||||
|
||||
interpolation: {
|
||||
escapeValue: false // not needed for react as it escapes by default
|
||||
|
@ -5,7 +5,7 @@ import { Loader, toastr } from "asc-web-components";
|
||||
import { PageLayout, utils } from "asc-web-common";
|
||||
import { ArticleHeaderContent, ArticleMainButtonContent, ArticleBodyContent } from '../../Article';
|
||||
import { SectionHeaderContent, SectionBodyContent } from './Section';
|
||||
import { fetchProfile } from '../../../store/profile/actions';
|
||||
import { fetchProfile, resetProfile } from '../../../store/profile/actions';
|
||||
import i18n from "./i18n";
|
||||
import { I18nextProvider, withTranslation } from "react-i18next";
|
||||
const { changeLanguage } = utils;
|
||||
@ -44,6 +44,10 @@ class PureProfile extends React.Component {
|
||||
}
|
||||
}
|
||||
|
||||
componentWillUnmount(){
|
||||
this.props.resetProfile();
|
||||
}
|
||||
|
||||
render() {
|
||||
//console.log("Profile render")
|
||||
|
||||
@ -91,5 +95,5 @@ function mapStateToProps(state) {
|
||||
}
|
||||
|
||||
export default connect(mapStateToProps, {
|
||||
fetchProfile
|
||||
fetchProfile, resetProfile
|
||||
})(Profile);
|
@ -27,6 +27,10 @@
|
||||
"ErrorUnknownFileImageType": "Unknown image file type",
|
||||
"Error": "Error",
|
||||
"SocialProfiles": "Social profiles",
|
||||
"SaveButton": "Save",
|
||||
"ChangesApplied": "Changes are applied",
|
||||
"SuccessChangeUserStatus": "The user status was successfully changed",
|
||||
"MessageEmailActivationInstuctionsSentOnEmail": "The email activation instructions have been sent to the <strong>{{ email }}</strong> email address",
|
||||
|
||||
"PhoneChange": "Change phone",
|
||||
"PhoneLbl": "Phone",
|
||||
|
@ -27,6 +27,10 @@
|
||||
"ErrorUnknownFileImageType": "Неизвестный тип файла изображения",
|
||||
"Error": "Ошибка",
|
||||
"SocialProfiles": "Социальные профили",
|
||||
"SaveButton": "Сохранить",
|
||||
"ChangesApplied": "Изменения успешно применены",
|
||||
"SuccessChangeUserStatus": "Статус пользователя успешно изменен",
|
||||
"MessageEmailActivationInstuctionsSentOnEmail": "Инструкция по активации почты пользователя была отправлена по адресу <strong>{{ email }}</strong>",
|
||||
|
||||
"PhoneChange": "Изменить номер телефона",
|
||||
"PhoneLbl": "Основной телефон",
|
||||
|
@ -348,6 +348,7 @@ class CreateUserForm extends React.Component {
|
||||
unknownTypeError={t("ErrorUnknownFileImageType")}
|
||||
maxSizeFileError={t("maxSizeFileError")}
|
||||
unknownError ={t("Error")}
|
||||
saveButtonLabel={t('SaveButton')}
|
||||
/>
|
||||
</AvatarContainer>
|
||||
<MainFieldsContainer ref={this.mainFieldsContainerRef}>
|
||||
|
@ -4,7 +4,7 @@ import { connect } from 'react-redux'
|
||||
import { Avatar, Button, Textarea, Text, toastr, AvatarEditor, Link } from 'asc-web-components'
|
||||
import { withTranslation, Trans } from 'react-i18next';
|
||||
import { toEmployeeWrapper, getUserRole, getUserContactsPattern, getUserContacts, mapGroupsToGroupSelectorOptions, mapGroupSelectorOptionsToGroups, filterGroupSelectorOptions } from "../../../../../store/people/selectors";
|
||||
import { updateProfile, getUserPhoto } from '../../../../../store/profile/actions'
|
||||
import { updateProfile, getUserPhoto, fetchProfile } from '../../../../../store/profile/actions'
|
||||
import { MainContainer, AvatarContainer, MainFieldsContainer } from './FormFields/Form'
|
||||
import TextField from './FormFields/TextField'
|
||||
import TextChangeField from './FormFields/TextChangeField'
|
||||
@ -310,7 +310,9 @@ class UpdateUserForm extends React.Component {
|
||||
toastr.success(this.props.t("ChangesSavedSuccessfully"));
|
||||
this.setState(stateCopy);
|
||||
})
|
||||
.catch((error) => toastr.error(error));
|
||||
.catch(error => toastr.error(error))
|
||||
.then(() => this.props.updateProfile(this.props.profile))
|
||||
.then(() => this.props.fetchProfile(this.state.profile.id))
|
||||
} else {
|
||||
deleteAvatar(this.state.profile.id)
|
||||
.then((response) => {
|
||||
@ -471,6 +473,7 @@ class UpdateUserForm extends React.Component {
|
||||
unknownTypeError={t("ErrorUnknownFileImageType")}
|
||||
maxSizeFileError={t("maxSizeFileError")}
|
||||
unknownError={t("Error")}
|
||||
saveButtonLabel={t('SaveButton')}
|
||||
/>
|
||||
</AvatarContainer>
|
||||
<MainFieldsContainer ref={this.mainFieldsContainerRef}>
|
||||
@ -687,6 +690,6 @@ const mapStateToProps = (state) => {
|
||||
export default connect(
|
||||
mapStateToProps,
|
||||
{
|
||||
updateProfile
|
||||
updateProfile, fetchProfile
|
||||
}
|
||||
)(withRouter(withTranslation()(UpdateUserForm)));
|
@ -12,7 +12,6 @@ if (process.env.NODE_ENV === "production") {
|
||||
.init({
|
||||
lng: localStorage.getItem(LANGUAGE) || 'en',
|
||||
fallbackLng: "en",
|
||||
debug: true,
|
||||
|
||||
interpolation: {
|
||||
escapeValue: false, // not needed for react as it escapes by default
|
||||
|
@ -1,6 +1,8 @@
|
||||
import i18n from "i18next";
|
||||
import Backend from "i18next-xhr-backend";
|
||||
import config from "../../../../package.json";
|
||||
import { constants } from 'asc-web-common';
|
||||
const { LANGUAGE } = constants;
|
||||
|
||||
const newInstance = i18n.createInstance();
|
||||
|
||||
@ -8,9 +10,8 @@ if (process.env.NODE_ENV === "production") {
|
||||
newInstance
|
||||
.use(Backend)
|
||||
.init({
|
||||
lng: 'en',
|
||||
lng: localStorage.getItem(LANGUAGE) || 'en',
|
||||
fallbackLng: "en",
|
||||
debug: true,
|
||||
|
||||
interpolation: {
|
||||
escapeValue: false // not needed for react as it escapes by default
|
||||
@ -33,7 +34,7 @@ if (process.env.NODE_ENV === "production") {
|
||||
|
||||
newInstance.init({
|
||||
resources: resources,
|
||||
lng: 'en',
|
||||
lng: localStorage.getItem(LANGUAGE) || 'en',
|
||||
fallbackLng: "en",
|
||||
debug: true,
|
||||
|
||||
|
@ -13,7 +13,7 @@ html, body {
|
||||
|
||||
.pageLoader {
|
||||
position: fixed;
|
||||
left: calc(50% - 32px);
|
||||
left: calc(50% - 20px);
|
||||
top: 35%;
|
||||
}
|
||||
}
|
||||
|
@ -99,7 +99,15 @@
|
||||
"NotFoundLanguage",
|
||||
"LearnMore",
|
||||
"ErrorUnknownFileImageType",
|
||||
"SaveButton",
|
||||
"MessageEmailActivationInstuctionsSentOnEmail",
|
||||
"Error"
|
||||
],
|
||||
"ResourceJS": [
|
||||
"ChangesApplied"
|
||||
],
|
||||
"PeopleJSResource": [
|
||||
"SuccessChangeUserStatus"
|
||||
]
|
||||
},
|
||||
"ProfileAction": {
|
||||
|
@ -11,7 +11,6 @@ import {
|
||||
PAGE,
|
||||
PAGE_COUNT
|
||||
} from "../../helpers/constants";
|
||||
import unionBy from 'lodash/unionBy';
|
||||
const { EmployeeStatus } = constants;
|
||||
const { Filter } = api;
|
||||
|
||||
@ -119,7 +118,8 @@ export function setFilterUrl(filter) {
|
||||
params.push(`${SORT_BY}=${filter.sortBy}`);
|
||||
params.push(`${SORT_ORDER}=${filter.sortOrder}`);
|
||||
|
||||
if (params.length > 0) {
|
||||
const isProfileView = history.location.pathname.includes('/people/view') || history.location.pathname.includes('/people/edit');
|
||||
if (params.length > 0 && !isProfileView) {
|
||||
history.push(`${config.homepage}/filter?${params.join("&")}`);
|
||||
}
|
||||
}
|
||||
@ -160,14 +160,14 @@ export function fetchPeople(filter, dispatchFunc = null) {
|
||||
return dispatchFunc
|
||||
? fetchPeopleByFilter(dispatchFunc, filter)
|
||||
: (dispatch, getState) => {
|
||||
if (filter) {
|
||||
return fetchPeopleByFilter(dispatch, filter);
|
||||
} else {
|
||||
const { people } = getState();
|
||||
const { filter } = people;
|
||||
return fetchPeopleByFilter(dispatch, filter);
|
||||
}
|
||||
};
|
||||
if (filter) {
|
||||
return fetchPeopleByFilter(dispatch, filter);
|
||||
} else {
|
||||
const { people } = getState();
|
||||
const { filter } = people;
|
||||
return fetchPeopleByFilter(dispatch, filter);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
function fetchPeopleByFilter(dispatch, filter) {
|
||||
@ -189,15 +189,15 @@ function fetchPeopleByFilter(dispatch, filter) {
|
||||
});
|
||||
}
|
||||
|
||||
export function updateUserStatus(status, userIds) {
|
||||
export function updateUserStatus(status, userIds, isRefetchPeople = false) {
|
||||
return (dispatch, getState) => {
|
||||
return api.people.updateUserStatus(status, userIds).then(users => {
|
||||
const { people } = getState();
|
||||
const { users: currentUsers } = people;
|
||||
|
||||
const newUsers = unionBy(users, currentUsers, "id");
|
||||
|
||||
dispatch(setUsers(newUsers));
|
||||
return api.people.updateUserStatus(status, userIds)
|
||||
.then(users => {
|
||||
const { people } = getState();
|
||||
const { filter } = people;
|
||||
return isRefetchPeople
|
||||
? fetchPeople(filter, dispatch)
|
||||
: Promise.resolve();
|
||||
});
|
||||
};
|
||||
}
|
||||
|
@ -12,7 +12,6 @@ if (process.env.NODE_ENV === "production") {
|
||||
.init({
|
||||
lng: localStorage.getItem(LANGUAGE) || 'en',
|
||||
fallbackLng: "en",
|
||||
debug: true,
|
||||
|
||||
interpolation: {
|
||||
escapeValue: false, // not needed for react as it escapes by default
|
||||
|
@ -11,7 +11,6 @@ if (process.env.NODE_ENV === "production") {
|
||||
.init({
|
||||
lng: localStorage.getItem(LANGUAGE) || 'en',
|
||||
fallbackLng: "en",
|
||||
debug: true,
|
||||
|
||||
interpolation: {
|
||||
escapeValue: false, // not needed for react as it escapes by default
|
||||
|
@ -11,7 +11,6 @@ if (process.env.NODE_ENV === "production") {
|
||||
.init({
|
||||
lng: localStorage.getItem(LANGUAGE) || 'en',
|
||||
fallbackLng: "en",
|
||||
debug: false,
|
||||
|
||||
interpolation: {
|
||||
escapeValue: false, // not needed for react as it escapes by default
|
||||
|
@ -11,7 +11,6 @@ if (process.env.NODE_ENV === "production") {
|
||||
.init({
|
||||
lng: localStorage.getItem(LANGUAGE) || 'en',
|
||||
fallbackLng: "en",
|
||||
debug: true,
|
||||
|
||||
interpolation: {
|
||||
escapeValue: false, // not needed for react as it escapes by default
|
||||
|
@ -12,7 +12,7 @@ html, body {
|
||||
|
||||
.pageLoader {
|
||||
position: fixed;
|
||||
left: calc(50% - 32px);
|
||||
left: calc(50% - 20px);
|
||||
top: 35%;
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "asc-web-common",
|
||||
"version": "1.0.63",
|
||||
"version": "1.0.72",
|
||||
"description": "Ascensio System SIA common components and solutions library",
|
||||
"license": "AGPL-3.0",
|
||||
"files": [
|
||||
|
@ -16,6 +16,7 @@ import Section from "../../../.storybook/decorators/section";
|
||||
import {Button, Avatar, Text} from "asc-web-components";
|
||||
import isEqual from "lodash/isEqual";
|
||||
import { name, image, internet } from "faker";
|
||||
import UserTooltip from "../PeopleSelector/sub-components/UserTooltip";
|
||||
|
||||
function getRandomInt(min, max) {
|
||||
return Math.floor(Math.random() * (max - min)) + min;
|
||||
@ -223,39 +224,7 @@ class ADSelectorExample extends React.Component {
|
||||
|
||||
// console.log("onOptionTooltipShow", index, user);
|
||||
|
||||
return (
|
||||
<div
|
||||
style={{
|
||||
width: 253,
|
||||
minHeight: 63,
|
||||
display: "grid",
|
||||
gridTemplateColumns: "30px 1fr",
|
||||
gridTemplateRows: "1fr",
|
||||
gridColumnGap: 8
|
||||
}}
|
||||
>
|
||||
<Avatar
|
||||
size="small"
|
||||
role="user"
|
||||
source={user.avatarUrl}
|
||||
userName=""
|
||||
editing={false}
|
||||
/>
|
||||
<div>
|
||||
<Text isBold={true} fontSize="13px" fontWeight={700}>
|
||||
{user.label}
|
||||
</Text>
|
||||
<Text
|
||||
color="#A3A9AE"
|
||||
fontSize="13px"
|
||||
style={{ paddingBottom: 8 }}
|
||||
>
|
||||
{user.email}
|
||||
</Text>
|
||||
<Text fontSize="13px" fontWeight={600}>{user.position}</Text>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
return (<UserTooltip avatarUrl={user.avatarUrl} label={user.label} email={user.email} position={user.position} />);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
|
@ -317,7 +317,10 @@ const Selector = props => {
|
||||
color="#D8D8D8"
|
||||
getContent={getOptionTooltipContent}
|
||||
place="top"
|
||||
offsetLeft={160}
|
||||
offsetLeft={150}
|
||||
offsetRight={0}
|
||||
offsetTop={60}
|
||||
offsetBottom={0}
|
||||
dataTip={`${index}`}
|
||||
displayType="dropdown"
|
||||
/>
|
||||
@ -342,7 +345,10 @@ const Selector = props => {
|
||||
color="#D8D8D8"
|
||||
getContent={getOptionTooltipContent}
|
||||
place="top"
|
||||
offsetLeft={160}
|
||||
offsetLeft={150}
|
||||
offsetRight={0}
|
||||
offsetTop={60}
|
||||
offsetBottom={0}
|
||||
dataTip={`${index}`}
|
||||
displayType="dropdown"
|
||||
/>
|
||||
@ -651,36 +657,39 @@ const Selector = props => {
|
||||
</Body>
|
||||
</Column>
|
||||
{displayType === "dropdown" && groups && groups.length > 0 && (
|
||||
<Column className="column-groups" displayType={displayType} size={size}>
|
||||
<Header className="header-groups">
|
||||
<Text
|
||||
as="p"
|
||||
className="group_header"
|
||||
fontSize="15px"
|
||||
fontWeight={600}
|
||||
>
|
||||
{groupsHeaderLabel}
|
||||
</Text>
|
||||
</Header>
|
||||
<Body className="body-groups">
|
||||
<AutoSizer>
|
||||
{({ height, width }) => (
|
||||
<List
|
||||
className="group_list"
|
||||
height={height}
|
||||
width={width + 8}
|
||||
itemSize={32}
|
||||
itemCount={groups.length}
|
||||
itemData={groups}
|
||||
outerElementType={CustomScrollbarsVirtualList}
|
||||
ref={listGroupsRef}
|
||||
>
|
||||
{renderGroup}
|
||||
</List>
|
||||
)}
|
||||
</AutoSizer>
|
||||
</Body>
|
||||
</Column>
|
||||
<>
|
||||
<div className="splitter"></div>
|
||||
<Column className="column-groups" displayType={displayType} size={size}>
|
||||
<Header className="header-groups">
|
||||
<Text
|
||||
as="p"
|
||||
className="group_header"
|
||||
fontSize="15px"
|
||||
fontWeight={600}
|
||||
>
|
||||
{groupsHeaderLabel}
|
||||
</Text>
|
||||
</Header>
|
||||
<Body className="body-groups">
|
||||
<AutoSizer>
|
||||
{({ height, width }) => (
|
||||
<List
|
||||
className="group_list"
|
||||
height={height}
|
||||
width={width + 8}
|
||||
itemSize={32}
|
||||
itemCount={groups.length}
|
||||
itemData={groups}
|
||||
outerElementType={CustomScrollbarsVirtualList}
|
||||
ref={listGroupsRef}
|
||||
>
|
||||
{renderGroup}
|
||||
</List>
|
||||
)}
|
||||
</AutoSizer>
|
||||
</Body>
|
||||
</Column>
|
||||
</>
|
||||
)}
|
||||
<Footer
|
||||
className="footer"
|
||||
|
@ -17,22 +17,20 @@ const Container = ({
|
||||
|
||||
const dropdownStyles = css`
|
||||
grid-auto-rows: max-content;
|
||||
grid-template-areas: "column-options column-groups" "footer footer";
|
||||
|
||||
${props =>
|
||||
props.groups && props.groups.length > 0
|
||||
? css`grid-template-areas: "column-options splitter column-groups" "footer footer footer"`
|
||||
: css`grid-template-areas: "column-options column-groups" "footer footer"`
|
||||
};
|
||||
|
||||
.column-groups {
|
||||
box-sizing: border-box;
|
||||
grid-area: column-groups;
|
||||
|
||||
${props =>
|
||||
props.groups && props.groups.length > 0
|
||||
? css`
|
||||
border-left: 1px solid #eceef1;
|
||||
`
|
||||
: ""}
|
||||
|
||||
display: grid;
|
||||
/* background-color: gold; */
|
||||
padding: 0 16px 0 16px;
|
||||
padding: 16px 16px 0 16px;
|
||||
grid-row-gap: 2px;
|
||||
|
||||
grid-template-columns: 1fr;
|
||||
@ -72,6 +70,17 @@ const dropdownStyles = css`
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
${props =>
|
||||
props.groups && props.groups.length > 0 &&
|
||||
css `
|
||||
.splitter {
|
||||
grid-area: splitter;
|
||||
border-left: 1px solid #eceef1;
|
||||
margin-top: 16px;
|
||||
}
|
||||
`
|
||||
}
|
||||
`;
|
||||
|
||||
const asideStyles = css`
|
||||
@ -94,15 +103,13 @@ const StyledSelector = styled(Container)`
|
||||
|
||||
${props => (props.displayType === "dropdown" ? dropdownStyles : asideStyles)}
|
||||
|
||||
padding-top: 16px;
|
||||
|
||||
.column-options {
|
||||
grid-area: column-options;
|
||||
box-sizing: border-box;
|
||||
|
||||
display: grid;
|
||||
/* background-color: red; */
|
||||
padding: 0 16px 0 16px;
|
||||
padding: 16px 16px 0 16px;
|
||||
grid-row-gap: 2px;
|
||||
|
||||
grid-template-columns: 1fr;
|
||||
@ -193,11 +200,21 @@ const StyledSelector = styled(Container)`
|
||||
height: 32px;
|
||||
cursor: pointer;
|
||||
|
||||
.option_checkbox {
|
||||
width: 265px;
|
||||
}
|
||||
|
||||
.option-info {
|
||||
position: absolute;
|
||||
top: 10px;
|
||||
right: 10px;
|
||||
padding: 8px 0 8px 8px;
|
||||
margin-top: -8px;
|
||||
}
|
||||
|
||||
/* .__react_component_tooltip {
|
||||
left: 8px !important;
|
||||
} */
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -18,7 +18,6 @@ newInstance.init({
|
||||
resources: resources,
|
||||
lng: localStorage.getItem(LANGUAGE) || 'en',
|
||||
fallbackLng: "en",
|
||||
debug: true,
|
||||
|
||||
interpolation: {
|
||||
escapeValue: false, // not needed for react as it escapes by default
|
||||
|
@ -17,6 +17,11 @@ const StyledNav = styled.nav`
|
||||
|
||||
.profile-menu {
|
||||
right: 12px;
|
||||
|
||||
@media ${tablet} {
|
||||
right: 6px;
|
||||
top: 66px;
|
||||
}
|
||||
}
|
||||
|
||||
& > div {
|
||||
|
@ -1,8 +1,7 @@
|
||||
import React from "react";
|
||||
import PropTypes from "prop-types";
|
||||
import { Avatar, DropDown, DropDownItem, utils, Link } from "asc-web-components";
|
||||
import { Avatar, DropDownItem, Link } from "asc-web-components";
|
||||
import ProfileMenu from "./../../ProfileMenu";
|
||||
const { handleAnyClick } = utils.event;
|
||||
|
||||
class ProfileActions extends React.PureComponent {
|
||||
constructor(props) {
|
||||
@ -14,42 +13,20 @@ class ProfileActions extends React.PureComponent {
|
||||
opened: props.opened,
|
||||
user: props.user
|
||||
};
|
||||
|
||||
this.handleClick = this.handleClick.bind(this);
|
||||
this.toggle = this.toggle.bind(this);
|
||||
this.getUserRole = this.getUserRole.bind(this);
|
||||
this.onAvatarClick = this.onAvatarClick.bind(this);
|
||||
this.onDropDownItemClick = this.onDropDownItemClick.bind(this);
|
||||
|
||||
if (props.opened) handleAnyClick(true, this.handleClick);
|
||||
}
|
||||
|
||||
handleClick = e => {
|
||||
this.state.opened &&
|
||||
!this.ref.current.contains(e.target) &&
|
||||
this.toggle(false);
|
||||
};
|
||||
|
||||
toggle = opened => {
|
||||
setOpened = opened => {
|
||||
this.setState({ opened: opened });
|
||||
};
|
||||
|
||||
componentWillUnmount() {
|
||||
handleAnyClick(false, this.handleClick);
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps, prevState) {
|
||||
componentDidUpdate(prevProps) {
|
||||
|
||||
if (this.props.user !== prevProps.user) {
|
||||
this.setState({ user: this.props.user })
|
||||
}
|
||||
|
||||
if (this.props.opened !== prevProps.opened) {
|
||||
this.toggle(this.props.opened);
|
||||
}
|
||||
|
||||
if (this.state.opened !== prevState.opened) {
|
||||
handleAnyClick(this.state.opened, this.handleClick);
|
||||
this.setOpened(this.props.opened);
|
||||
}
|
||||
}
|
||||
|
||||
@ -62,56 +39,52 @@ class ProfileActions extends React.PureComponent {
|
||||
return "user";
|
||||
};
|
||||
|
||||
onAvatarClick = () => {
|
||||
this.toggle(!this.state.opened);
|
||||
};
|
||||
onClose = (e) => {
|
||||
if (this.ref.current.contains(e.target)) return;
|
||||
|
||||
onDropDownItemClick = action => {
|
||||
action.onClick && action.onClick();
|
||||
this.toggle(!this.state.opened);
|
||||
};
|
||||
this.setOpened(!this.state.opened);
|
||||
}
|
||||
|
||||
onClick = (action, e) => {
|
||||
action.onClick && action.onClick(e);
|
||||
|
||||
this.setOpened(!this.state.opened);
|
||||
}
|
||||
|
||||
render() {
|
||||
//console.log("Layout sub-component ProfileActions render");
|
||||
const { user, opened } = this.state;
|
||||
const userRole = this.getUserRole(user);
|
||||
|
||||
return (
|
||||
<div ref={this.ref}>
|
||||
<Avatar
|
||||
onClick={this.onClick}
|
||||
role={userRole}
|
||||
size="small"
|
||||
role={this.getUserRole(this.state.user)}
|
||||
source={this.state.user.avatarSmall}
|
||||
userName={this.state.user.displayName}
|
||||
onClick={this.onAvatarClick}
|
||||
source={user.avatarSmall}
|
||||
userName={user.displayName}
|
||||
/>
|
||||
<DropDown
|
||||
className='profile-menu'
|
||||
directionX="right"
|
||||
open={this.state.opened}
|
||||
clickOutsideAction={this.onAvatarClick}
|
||||
<ProfileMenu
|
||||
className="profile-menu"
|
||||
avatarRole={userRole}
|
||||
avatarSource={user.avatarMedium}
|
||||
displayName={user.displayName}
|
||||
email={user.email}
|
||||
open={opened}
|
||||
clickOutsideAction={this.onClose}
|
||||
>
|
||||
<ProfileMenu
|
||||
avatarRole={this.getUserRole(this.state.user)}
|
||||
avatarSource={this.state.user.avatarMedium}
|
||||
displayName={this.state.user.displayName}
|
||||
email={this.state.user.email}
|
||||
/>
|
||||
{this.props.userActions.map(action => (
|
||||
<Link
|
||||
noHover={true}
|
||||
key={action.key}
|
||||
href={action.url}
|
||||
onClick={(e) => {
|
||||
if (e) {
|
||||
this.onDropDownItemClick.bind(this, action);
|
||||
e.preventDefault();
|
||||
}
|
||||
}
|
||||
}
|
||||
>
|
||||
<DropDownItem
|
||||
{...action} />
|
||||
//onClick={this.onClick.bind(this, action)}
|
||||
>
|
||||
<DropDownItem {...action} />
|
||||
</Link>
|
||||
))}
|
||||
</DropDown>
|
||||
</ProfileMenu>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
@ -18,7 +18,6 @@ newInstance.init({
|
||||
resources: resources,
|
||||
lng: localStorage.getItem(LANGUAGE) || 'en',
|
||||
fallbackLng: "en",
|
||||
debug: true,
|
||||
|
||||
interpolation: {
|
||||
escapeValue: false, // not needed for react as it escapes by default
|
||||
|
@ -24,6 +24,32 @@ class PageLayoutComponent extends React.PureComponent {
|
||||
this.state = this.mapPropsToState(props);
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
window.addEventListener("orientationchange", this.orientationChangeHandler);
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
window.removeEventListener("orientationchange", this.orientationChangeHandler);
|
||||
}
|
||||
|
||||
orientationChangeHandler = () => {
|
||||
const articleElement = document.getElementsByTagName('article')[0];
|
||||
|
||||
if (!articleElement) return;
|
||||
|
||||
const isOrientationVertical = !(screen.orientation ? screen.orientation.angle % 180 : window.matchMedia("(orientation: portrait)"));
|
||||
const isValueExist = !!localStorage.getItem(ARTICLE_PINNED_KEY);
|
||||
const articleWidth = articleElement.offsetWidth;
|
||||
const isArticleWide = articleWidth > screen.availWidth - articleWidth;
|
||||
|
||||
if (isOrientationVertical && isArticleWide && isValueExist) {
|
||||
this.backdropClick();
|
||||
}
|
||||
if (!isOrientationVertical && isValueExist) {
|
||||
this.pinArticle();
|
||||
}
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps) {
|
||||
if (this.hasChanges(this.props, prevProps)) {
|
||||
this.setState(this.mapPropsToState(this.props));
|
||||
@ -197,7 +223,7 @@ const PageLayout = props => {
|
||||
}
|
||||
|
||||
PageLayout.propTypes = {
|
||||
language:PropTypes.string,
|
||||
language: PropTypes.string,
|
||||
}
|
||||
|
||||
PageLayoutComponent.propTypes = {
|
||||
|
@ -9,6 +9,7 @@ const StyledSectionToggler = styled.div`
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
display: none;
|
||||
-webkit-tap-highlight-color: rgba(0, 0, 0, 0);
|
||||
|
||||
@media ${tablet} {
|
||||
display: ${props => (props.visible ? "block" : "none")};
|
||||
|
@ -18,7 +18,6 @@ newInstance.init({
|
||||
resources: resources,
|
||||
lng: localStorage.getItem(LANGUAGE) || 'en',
|
||||
fallbackLng: "en",
|
||||
debug: true,
|
||||
|
||||
interpolation: {
|
||||
escapeValue: false, // not needed for react as it escapes by default
|
||||
|
@ -1,26 +1,20 @@
|
||||
import styled from "styled-components";
|
||||
|
||||
const StyledUserTooltip = styled.div`
|
||||
width: 253px;
|
||||
width: 233px;
|
||||
min-height: 63px;
|
||||
display: "grid";
|
||||
grid-template-columns: "30px 1fr";
|
||||
grid-template-rows: "1fr";
|
||||
grid-column-gap: 8px;
|
||||
display: grid;
|
||||
grid-template-columns: 33px 1fr;
|
||||
grid-template-rows: 1fr;
|
||||
grid-column-gap: 12px;
|
||||
|
||||
.email-text {
|
||||
padding-bottom: 8px;
|
||||
}
|
||||
.block-info {
|
||||
display: grid;
|
||||
grid-template-rows: 1fr;
|
||||
|
||||
.block-avatar-name {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding-bottom: 8px;
|
||||
}
|
||||
|
||||
.user-avatar {
|
||||
margin-right: 10px;
|
||||
min-width: 32px;
|
||||
.email-text {
|
||||
padding-bottom: 8px;
|
||||
}
|
||||
}
|
||||
|
||||
`;
|
||||
|
@ -6,7 +6,7 @@ import StyledUserTooltip from "./StyledUserTooltip";
|
||||
const UserTooltip = ({ avatarUrl, label, email, position }) => (
|
||||
|
||||
<StyledUserTooltip>
|
||||
<div className='block-avatar-name'>
|
||||
<div className='block-avatar'>
|
||||
<Avatar
|
||||
className='user-avatar'
|
||||
size="small"
|
||||
@ -15,16 +15,16 @@ const UserTooltip = ({ avatarUrl, label, email, position }) => (
|
||||
userName=""
|
||||
editing={false}
|
||||
/>
|
||||
<Text isBold={true} fontSize="13px" fontWeight={700}>
|
||||
</div>
|
||||
|
||||
<div className='block-info'>
|
||||
<Text isBold={true} fontSize="13px" fontWeight={700} truncate={true} title={label}>
|
||||
{label}
|
||||
</Text>
|
||||
</div>
|
||||
<div>
|
||||
|
||||
<Text color="#A3A9AE" fontSize="13px" className="email-text">
|
||||
<Text color="#A3A9AE" fontSize="13px" className="email-text" truncate={true} title={email}>
|
||||
{email}
|
||||
</Text>
|
||||
<Text fontSize="13px" fontWeight={600}>
|
||||
<Text fontSize="13px" fontWeight={600} truncate={true} title={position}>
|
||||
{position}
|
||||
</Text>
|
||||
</div>
|
||||
|
@ -1,55 +1,78 @@
|
||||
import React, { memo } from "react";
|
||||
import React from "react";
|
||||
import PropTypes from 'prop-types';
|
||||
import { Avatar } from 'asc-web-components';
|
||||
import { Avatar, DropDown } from 'asc-web-components';
|
||||
import {
|
||||
StyledProfileMenu,
|
||||
MenuContainer,
|
||||
AvatarContainer,
|
||||
MainLabelContainer,
|
||||
LabelContainer,
|
||||
MainLabelContainer,
|
||||
MenuContainer,
|
||||
StyledProfileMenu,
|
||||
TopArrow
|
||||
} from "./StyledProfileMenu";
|
||||
|
||||
// eslint-disable-next-line react/display-name
|
||||
const ProfileMenu = memo(props => {
|
||||
const {
|
||||
displayName,
|
||||
email,
|
||||
avatarRole,
|
||||
avatarSource
|
||||
} = props;
|
||||
class ProfileMenu extends React.Component {
|
||||
|
||||
return (
|
||||
<StyledProfileMenu {...props}>
|
||||
<MenuContainer {...props}>
|
||||
<AvatarContainer>
|
||||
<Avatar
|
||||
size='medium'
|
||||
role={avatarRole}
|
||||
source={avatarSource}
|
||||
userName={displayName}
|
||||
/>
|
||||
</AvatarContainer>
|
||||
<MainLabelContainer>
|
||||
{displayName}
|
||||
</MainLabelContainer>
|
||||
<LabelContainer>
|
||||
{email}
|
||||
</LabelContainer>
|
||||
</MenuContainer>
|
||||
<TopArrow />
|
||||
</StyledProfileMenu>
|
||||
);
|
||||
});
|
||||
constructor(props) {
|
||||
super(props);
|
||||
}
|
||||
|
||||
render() {
|
||||
const {
|
||||
avatarRole,
|
||||
avatarSource,
|
||||
children,
|
||||
className,
|
||||
displayName,
|
||||
email,
|
||||
clickOutsideAction,
|
||||
open
|
||||
} = this.props;
|
||||
|
||||
return (
|
||||
<DropDown
|
||||
className={className}
|
||||
directionX='right'
|
||||
open={open}
|
||||
clickOutsideAction={clickOutsideAction}
|
||||
>
|
||||
<StyledProfileMenu>
|
||||
<MenuContainer>
|
||||
<AvatarContainer>
|
||||
<Avatar
|
||||
size='medium'
|
||||
role={avatarRole}
|
||||
source={avatarSource}
|
||||
userName={displayName}
|
||||
/>
|
||||
</AvatarContainer>
|
||||
<MainLabelContainer>
|
||||
{displayName}
|
||||
</MainLabelContainer>
|
||||
<LabelContainer>
|
||||
{email}
|
||||
</LabelContainer>
|
||||
</MenuContainer>
|
||||
<TopArrow />
|
||||
</StyledProfileMenu>
|
||||
{children}
|
||||
</DropDown>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
ProfileMenu.displayName = 'ProfileMenu';
|
||||
|
||||
ProfileMenu.propTypes = {
|
||||
avatarRole: PropTypes.oneOf(['owner', 'admin', 'guest', 'user']),
|
||||
avatarSource: PropTypes.string,
|
||||
children: PropTypes.any,
|
||||
className: PropTypes.string,
|
||||
displayName: PropTypes.string,
|
||||
email: PropTypes.string,
|
||||
id: PropTypes.string,
|
||||
style: PropTypes.oneOfType([PropTypes.object, PropTypes.array])
|
||||
open: PropTypes.bool,
|
||||
style: PropTypes.oneOfType([PropTypes.object, PropTypes.array]),
|
||||
clickOutsideAction: PropTypes.func,
|
||||
};
|
||||
|
||||
export default ProfileMenu
|
@ -1,25 +1,60 @@
|
||||
import React from 'react';
|
||||
import { storiesOf } from '@storybook/react';
|
||||
import { withKnobs, text, select} from '@storybook/addon-knobs/react';
|
||||
import { withKnobs, text, select } from '@storybook/addon-knobs/react';
|
||||
import { BooleanValue } from 'react-values'
|
||||
import ProfileMenu from '.';
|
||||
import Section from '../../../.storybook/decorators/section';
|
||||
import withReadme from 'storybook-readme/with-readme';
|
||||
import Readme from './README.md';
|
||||
import { DropDownItem, Avatar } from 'asc-web-components';
|
||||
|
||||
const roleOptions = ['owner', 'admin','guest','user'];
|
||||
const roleOptions = ['owner', 'admin', 'guest', 'user'];
|
||||
const defaultAvatar = 'https://static-www.onlyoffice.com/images/team/developers_photos/personal_44_2x.jpg';
|
||||
|
||||
|
||||
|
||||
storiesOf('Components|ProfileMenu', module)
|
||||
.addDecorator(withKnobs)
|
||||
.addDecorator(withReadme(Readme))
|
||||
|
||||
.add('base', () => (
|
||||
<Section>
|
||||
<ProfileMenu
|
||||
avatarRole={select('avatarRole', roleOptions, 'admin')}
|
||||
avatarSource={text('avatarSource','') || defaultAvatar}
|
||||
displayName={text('displayName','') || 'Jane Doe'}
|
||||
email={text('email','') || 'janedoe@gmail.com'}
|
||||
/>
|
||||
</Section>
|
||||
));
|
||||
.add('base', () => {
|
||||
const userRole = select('avatarRole', roleOptions, 'admin');
|
||||
const userAvatar = text('avatarSource', '') || defaultAvatar;
|
||||
const userEmail = text('email', '') || 'janedoe@gmail.com';
|
||||
const userDisplayName = text('displayName', '') || 'Jane Doe';
|
||||
|
||||
return (
|
||||
<Section >
|
||||
<BooleanValue>
|
||||
{({ value, toggle }) => (
|
||||
<div style={{
|
||||
position: 'relative',
|
||||
float: 'right',
|
||||
height: '56px',
|
||||
paddingRight: '4px'
|
||||
}}>
|
||||
<Avatar
|
||||
size='medium'
|
||||
role={userRole}
|
||||
source={userAvatar}
|
||||
userName={userDisplayName}
|
||||
onClick={() => toggle(!value)}
|
||||
/>
|
||||
<ProfileMenu
|
||||
avatarRole={userRole}
|
||||
avatarSource={userAvatar}
|
||||
displayName={userDisplayName}
|
||||
email={userEmail}
|
||||
open={value}
|
||||
>
|
||||
<DropDownItem key='1' label='Profile' onClick={() => console.log('Profile click')} />
|
||||
<DropDownItem key='2' label='Subscriptions' onClick={() => console.log('Subscriptions click')} />
|
||||
<DropDownItem key='sep' isSeparator />
|
||||
<DropDownItem key='3' label='About this program' onClick={() => console.log('About click')} />
|
||||
<DropDownItem key='4' label='Log out' onClick={() => console.log('Log out click')} />
|
||||
</ProfileMenu>
|
||||
</div>
|
||||
)}
|
||||
</BooleanValue>
|
||||
</Section>)
|
||||
});
|
@ -6,12 +6,11 @@ import Layout from "../Layout";
|
||||
class PureStudioLayout extends React.Component {
|
||||
shouldComponentUpdate(nextProps) {
|
||||
if (this.props.availableModules && nextProps.availableModules &&
|
||||
!utils.array.isArrayEqual(nextProps.availableModules, this.props.availableModules)) {
|
||||
!utils.array.isArrayEqual(nextProps.modules, this.props.modules)) {
|
||||
return true;
|
||||
}
|
||||
return this.props.hasChanges !== nextProps.hasChanges ||
|
||||
this.props.currentModuleId !== nextProps.currentModuleId ||
|
||||
this.props.language !== nextProps.language;
|
||||
this.props.currentModuleId !== nextProps.currentModuleId
|
||||
}
|
||||
|
||||
onProfileClick = () => {
|
||||
|
@ -82,7 +82,8 @@ function mapStateToProps(state) {
|
||||
availableModules: getAvailableModules(state.auth.modules, state.auth.user),
|
||||
currentUser: state.auth.user,
|
||||
currentModuleId: state.auth.settings.currentProductId,
|
||||
settings: state.auth.settings
|
||||
settings: state.auth.settings,
|
||||
modules: state.auth.modules
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -18,7 +18,6 @@ newInstance.init({
|
||||
resources: resources,
|
||||
lng: localStorage.getItem(LANGUAGE) || 'en',
|
||||
fallbackLng: "en",
|
||||
debug: true,
|
||||
|
||||
interpolation: {
|
||||
escapeValue: false, // not needed for react as it escapes by default
|
||||
|
@ -1,6 +1,7 @@
|
||||
import i18n from "i18next";
|
||||
import en from "./locales/en/translation.json";
|
||||
import ru from "./locales/ru/translation.json";
|
||||
import { LANGUAGE } from '../../../constants';
|
||||
|
||||
const newInstance = i18n.createInstance();
|
||||
|
||||
@ -15,9 +16,8 @@ const newInstance = i18n.createInstance();
|
||||
|
||||
newInstance.init({
|
||||
resources: resources,
|
||||
lng: 'en',
|
||||
lng: localStorage.getItem(LANGUAGE) || 'en',
|
||||
fallbackLng: "en",
|
||||
debug: true,
|
||||
|
||||
interpolation: {
|
||||
escapeValue: false, // not needed for react as it escapes by default
|
||||
|
@ -1,6 +1,7 @@
|
||||
import i18n from "i18next";
|
||||
import en from "./locales/en/translation.json";
|
||||
import ru from "./locales/ru/translation.json";
|
||||
import { LANGUAGE } from '../../../constants';
|
||||
|
||||
const newInstance = i18n.createInstance();
|
||||
|
||||
@ -15,9 +16,8 @@ const newInstance = i18n.createInstance();
|
||||
|
||||
newInstance.init({
|
||||
resources: resources,
|
||||
lng: 'en',
|
||||
lng: localStorage.getItem(LANGUAGE) || 'en',
|
||||
fallbackLng: "en",
|
||||
debug: true,
|
||||
|
||||
interpolation: {
|
||||
escapeValue: false, // not needed for react as it escapes by default
|
||||
|
@ -1,6 +1,7 @@
|
||||
import i18n from "i18next";
|
||||
import en from "./locales/en/translation.json";
|
||||
import ru from "./locales/ru/translation.json";
|
||||
import { LANGUAGE } from '../../../constants';
|
||||
|
||||
const newInstance = i18n.createInstance();
|
||||
|
||||
@ -15,9 +16,8 @@ const newInstance = i18n.createInstance();
|
||||
|
||||
newInstance.init({
|
||||
resources: resources,
|
||||
lng: 'en',
|
||||
lng: localStorage.getItem(LANGUAGE) || 'en',
|
||||
fallbackLng: "en",
|
||||
debug: true,
|
||||
|
||||
interpolation: {
|
||||
escapeValue: false, // not needed for react as it escapes by default
|
||||
|
@ -1,6 +1,7 @@
|
||||
import i18n from "i18next";
|
||||
import en from "./locales/en/translation.json";
|
||||
import ru from "./locales/ru/translation.json";
|
||||
import { LANGUAGE } from '../../../constants';
|
||||
|
||||
const newInstance = i18n.createInstance();
|
||||
|
||||
@ -15,9 +16,8 @@ const newInstance = i18n.createInstance();
|
||||
|
||||
newInstance.init({
|
||||
resources: resources,
|
||||
lng: 'en',
|
||||
lng: localStorage.getItem(LANGUAGE) || 'en',
|
||||
fallbackLng: "en",
|
||||
debug: true,
|
||||
|
||||
interpolation: {
|
||||
escapeValue: false, // not needed for react as it escapes by default
|
||||
|
@ -1,6 +1,7 @@
|
||||
import i18n from "i18next";
|
||||
import en from "./locales/en/translation.json";
|
||||
import ru from "./locales/ru/translation.json";
|
||||
import { LANGUAGE } from '../../../constants';
|
||||
|
||||
const newInstance = i18n.createInstance();
|
||||
|
||||
@ -15,9 +16,8 @@ const newInstance = i18n.createInstance();
|
||||
|
||||
newInstance.init({
|
||||
resources: resources,
|
||||
lng: 'en',
|
||||
lng: localStorage.getItem(LANGUAGE) || 'en',
|
||||
fallbackLng: "en",
|
||||
debug: true,
|
||||
|
||||
interpolation: {
|
||||
escapeValue: false, // not needed for react as it escapes by default
|
||||
|
@ -1,6 +1,7 @@
|
||||
import i18n from "i18next";
|
||||
import en from "./locales/en/translation.json";
|
||||
import ru from "./locales/ru/translation.json";
|
||||
import { LANGUAGE } from '../../constants';
|
||||
|
||||
const newInstance = i18n.createInstance();
|
||||
|
||||
@ -15,9 +16,8 @@ const resources = {
|
||||
|
||||
newInstance.init({
|
||||
resources: resources,
|
||||
lng: 'en',
|
||||
lng: localStorage.getItem(LANGUAGE) || 'en',
|
||||
fallbackLng: "en",
|
||||
debug: true,
|
||||
|
||||
interpolation: {
|
||||
escapeValue: false, // not needed for react as it escapes by default
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "asc-web-components",
|
||||
"version": "1.0.337",
|
||||
"version": "1.0.347",
|
||||
"description": "Ascensio System SIA component library",
|
||||
"license": "AGPL-3.0",
|
||||
"main": "dist/asc-web-components.js",
|
||||
|
@ -19,6 +19,10 @@ const StyledComboBox = styled.div`
|
||||
position: relative;
|
||||
outline: 0;
|
||||
-webkit-tap-highlight-color: rgba(0, 0, 0, 0);
|
||||
|
||||
.dropdown-container {
|
||||
padding: ${props => props.advancedOptions && `6px 0px`};
|
||||
}
|
||||
`;
|
||||
|
||||
class ComboBox extends React.Component {
|
||||
@ -139,6 +143,7 @@ class ComboBox extends React.Component {
|
||||
/>
|
||||
{displayType !== 'toggle' &&
|
||||
<DropDown
|
||||
className='dropdown-container'
|
||||
directionX={directionX}
|
||||
directionY={directionY}
|
||||
manualY='102%'
|
||||
|
@ -68,6 +68,12 @@ class ContextMenu extends React.PureComponent {
|
||||
this.setState({ visible: false });
|
||||
}
|
||||
|
||||
itemClick = (action, e) => {
|
||||
action && action(e);
|
||||
|
||||
this.setState({ visible: false });
|
||||
}
|
||||
|
||||
render() {
|
||||
const { visible } = this.state;
|
||||
const { options, id, className, style } = this.props;
|
||||
@ -82,7 +88,7 @@ class ContextMenu extends React.PureComponent {
|
||||
>
|
||||
{options.map((item) => {
|
||||
if (item && item.key !== undefined) {
|
||||
return <DropDownItem key={item.key} {...item} />
|
||||
return <DropDownItem key={item.key} {...item} onClick={this.itemClick.bind(this, item.onClick)} />
|
||||
}
|
||||
})}
|
||||
</DropDown>
|
||||
|
@ -27,7 +27,7 @@ const disabledAndHeaderStyle = css`
|
||||
const StyledDropdownItem = styled.div`
|
||||
display: block;
|
||||
width: 100%;
|
||||
max-width: 240px;
|
||||
max-width: 500px;
|
||||
border: 0px;
|
||||
cursor: pointer;
|
||||
margin: 0px;
|
||||
|
@ -34,7 +34,7 @@ const StyledDropdown = styled.div`
|
||||
-moz-box-shadow: 0px 5px 20px rgba(0, 0, 0, 0.13);
|
||||
-webkit-box-shadow: 0px 5px 20px rgba(0, 0, 0, 0.13);
|
||||
|
||||
padding: ${props => !props.maxHeight && `6px 0px`};
|
||||
padding: ${props => !props.maxHeight && props.children && props.children.length > 1 && `6px 0px`};
|
||||
`;
|
||||
|
||||
// eslint-disable-next-line react/display-name, react/prop-types
|
||||
@ -50,7 +50,6 @@ const Row = memo(({ data, index, style }) => {
|
||||
});
|
||||
|
||||
class DropDown extends React.PureComponent {
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
@ -83,12 +82,10 @@ class DropDown extends React.PureComponent {
|
||||
else {
|
||||
this.props.disableOnClickOutside();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
handleClickOutside = e => {
|
||||
//console.log(`DropDown handleClickOutside`, e);
|
||||
this.toggleDropDown(e);
|
||||
};
|
||||
|
||||
@ -101,16 +98,12 @@ class DropDown extends React.PureComponent {
|
||||
|
||||
const rects = this.dropDownRef.current.getBoundingClientRect();
|
||||
const container = { width: window.innerWidth, height: window.innerHeight };
|
||||
|
||||
const left = rects.left < 0;
|
||||
const right = rects.right > container.width;
|
||||
|
||||
let newDirection = {};
|
||||
|
||||
newDirection.directionX = left ? 'left' : right ? 'right' : this.props.directionX;
|
||||
const left = rects.left < 0 && rects.width < container.width;
|
||||
const right = rects.left < 250 && rects.left > rects.width && rects.width < container.width;
|
||||
const x = left ? 'left' : right ? 'right' : this.props.directionX;
|
||||
|
||||
this.setState({
|
||||
directionX: newDirection.directionX,
|
||||
directionX: x,
|
||||
width: rects.width
|
||||
});
|
||||
}
|
||||
@ -152,19 +145,19 @@ class DropDown extends React.PureComponent {
|
||||
DropDown.propTypes = {
|
||||
children: PropTypes.any,
|
||||
className: PropTypes.string,
|
||||
clickOutsideAction: PropTypes.func,
|
||||
directionX: PropTypes.oneOf(['left', 'right']),
|
||||
directionY: PropTypes.oneOf(['bottom', 'top']),
|
||||
disableOnClickOutside: PropTypes.func,
|
||||
enableOnClickOutside: PropTypes.func,
|
||||
id: PropTypes.string,
|
||||
open: PropTypes.bool,
|
||||
manualWidth: PropTypes.string,
|
||||
manualX: PropTypes.string,
|
||||
manualY: PropTypes.string,
|
||||
maxHeight: PropTypes.number,
|
||||
open: PropTypes.bool,
|
||||
style: PropTypes.oneOfType([PropTypes.object, PropTypes.array]),
|
||||
withBackdrop: PropTypes.bool,
|
||||
clickOutsideAction: PropTypes.func,
|
||||
enableOnClickOutside: PropTypes.func,
|
||||
disableOnClickOutside: PropTypes.func
|
||||
withBackdrop: PropTypes.bool
|
||||
};
|
||||
|
||||
DropDown.defaultProps = {
|
||||
|
@ -1,7 +1,8 @@
|
||||
import React from 'react';
|
||||
import styled, {css} from 'styled-components';
|
||||
import styled, { css } from 'styled-components';
|
||||
import FilterButton from './filter-button';
|
||||
import HideFilter from './hide-filter';
|
||||
import throttle from 'lodash/throttle';
|
||||
import ComboBox from '../combobox';
|
||||
import CloseButton from './close-button';
|
||||
import isEqual from 'lodash/isEqual';
|
||||
@ -86,6 +87,9 @@ const StyledComboBox = styled(ComboBox)`
|
||||
width: auto;
|
||||
padding-left: 4px;
|
||||
}
|
||||
> div:last-child{
|
||||
max-width: 220px;
|
||||
}
|
||||
.combo-button-label {
|
||||
color: #555F65;
|
||||
}
|
||||
@ -136,7 +140,7 @@ class FilterItem extends React.Component {
|
||||
noBorder={true}
|
||||
opened={this.props.opened}
|
||||
directionX='left'
|
||||
toggleAction={(e,isOpen)=>{
|
||||
toggleAction={(e, isOpen) => {
|
||||
this.setState({
|
||||
isOpen: isOpen
|
||||
})
|
||||
@ -145,7 +149,7 @@ class FilterItem extends React.Component {
|
||||
: <StyledFilterName>{this.props.label}</StyledFilterName>
|
||||
}
|
||||
</StyledFilterItemContent>
|
||||
|
||||
|
||||
|
||||
<StyledCloseButtonBlock onClick={this.onClick} isDisabled={this.props.isDisabled} isClickable={true}>
|
||||
<CloseButton
|
||||
@ -165,8 +169,8 @@ FilterItem.propTypes = {
|
||||
groupItems: PropTypes.array,
|
||||
label: PropTypes.string,
|
||||
groupLabel: PropTypes.string,
|
||||
onClose:PropTypes.func,
|
||||
onSelectFilterItem:PropTypes.func
|
||||
onClose: PropTypes.func,
|
||||
onSelectFilterItem: PropTypes.func
|
||||
}
|
||||
|
||||
class FilterBlock extends React.Component {
|
||||
@ -178,6 +182,8 @@ class FilterBlock extends React.Component {
|
||||
openFilterItems: this.props.openFilterItems || []
|
||||
};
|
||||
|
||||
this.throttledRender = throttle(this.onRender, 100);
|
||||
|
||||
}
|
||||
onDeleteFilterItem = (key) => {
|
||||
this.props.onDeleteFilterItem(key);
|
||||
@ -252,6 +258,9 @@ class FilterBlock extends React.Component {
|
||||
return result;
|
||||
}
|
||||
componentDidUpdate() {
|
||||
this.throttledRender();
|
||||
}
|
||||
onRender = () => {
|
||||
this.props.onRender();
|
||||
}
|
||||
shouldComponentUpdate(nextProps, nextState) {
|
||||
|
@ -12,7 +12,7 @@ import clone from 'lodash/clone';
|
||||
|
||||
const StyledFilterInput = styled.div`
|
||||
width: 100%;
|
||||
|
||||
min-width: 255px;
|
||||
&:after {
|
||||
content: " ";
|
||||
display: block;
|
||||
|
@ -1,6 +1,6 @@
|
||||
import React from "react";
|
||||
import { storiesOf } from "@storybook/react";
|
||||
import { withKnobs } from "@storybook/addon-knobs/react";
|
||||
import { withKnobs, number } from "@storybook/addon-knobs/react";
|
||||
import withReadme from "storybook-readme/with-readme";
|
||||
import Readme from "./README.md";
|
||||
import HelpButton from ".";
|
||||
@ -28,6 +28,10 @@ storiesOf("Components|Buttons", module)
|
||||
<IconButtons>
|
||||
<HelpButton
|
||||
displayType="dropdown"
|
||||
offsetTop={number("offsetTop", 0)}
|
||||
offsetRight={number("offsetRight", 0)}
|
||||
offsetBottom={number("offsetBottom", 0)}
|
||||
offsetLeft={number("offsetLeft", 0)}
|
||||
tooltipContent={
|
||||
<Text fontSize='13px'>
|
||||
Paste you tooltip content here
|
||||
|
@ -11,14 +11,6 @@ import Heading from "../heading";
|
||||
import throttle from "lodash/throttle";
|
||||
import styled from "styled-components";
|
||||
|
||||
const HelpContainer = styled.div`
|
||||
* {
|
||||
white-space: unset;
|
||||
overflow: unset;
|
||||
text-overflow: unset;
|
||||
}
|
||||
`;
|
||||
|
||||
const Content = styled.div`
|
||||
box-sizing: border-box;
|
||||
position: relative;
|
||||
@ -132,6 +124,8 @@ class HelpButton extends React.Component {
|
||||
const {
|
||||
tooltipContent,
|
||||
place,
|
||||
offsetTop,
|
||||
offsetBottom,
|
||||
offsetRight,
|
||||
offsetLeft,
|
||||
zIndex,
|
||||
@ -145,7 +139,7 @@ class HelpButton extends React.Component {
|
||||
} = this.props;
|
||||
|
||||
return (
|
||||
<HelpContainer ref={this.ref} style={style}>
|
||||
<div ref={this.ref} style={style}>
|
||||
<IconButton
|
||||
id={this.id}
|
||||
className={`${className} help-icon`}
|
||||
@ -164,6 +158,8 @@ class HelpButton extends React.Component {
|
||||
reference={this.refTooltip}
|
||||
effect="solid"
|
||||
place={place}
|
||||
offsetTop={offsetTop}
|
||||
offsetBottom={offsetBottom}
|
||||
offsetRight={offsetRight}
|
||||
offsetLeft={offsetLeft}
|
||||
afterShow={this.afterShow}
|
||||
@ -180,7 +176,6 @@ class HelpButton extends React.Component {
|
||||
offsetLeft={offsetLeft}
|
||||
afterShow={this.afterShow}
|
||||
afterHide={this.afterHide}
|
||||
getContent={getContent}
|
||||
>
|
||||
{tooltipContent}
|
||||
</Tooltip>
|
||||
@ -202,7 +197,7 @@ class HelpButton extends React.Component {
|
||||
</Aside>
|
||||
</>
|
||||
)}
|
||||
</HelpContainer>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -214,10 +209,12 @@ HelpButton.propTypes = {
|
||||
]),
|
||||
tooltipContent: PropTypes.oneOfType([PropTypes.string, PropTypes.object]),
|
||||
offsetRight: PropTypes.number,
|
||||
offsetLeft: PropTypes.number,
|
||||
offsetTop: PropTypes.number,
|
||||
offsetBottom: PropTypes.number,
|
||||
tooltipMaxWidth: PropTypes.number,
|
||||
tooltipId: PropTypes.string,
|
||||
place: PropTypes.string,
|
||||
offsetLeft: PropTypes.number,
|
||||
zIndex: PropTypes.number,
|
||||
displayType: PropTypes.oneOf(["dropdown", "aside", "auto"]),
|
||||
helpButtonHeaderContent: PropTypes.string,
|
||||
@ -234,6 +231,8 @@ HelpButton.defaultProps = {
|
||||
place: "top",
|
||||
offsetRight: 120,
|
||||
offsetLeft: 0,
|
||||
offsetTop: 0,
|
||||
offsetBottom: 0,
|
||||
zIndex: 310,
|
||||
displayType: "auto",
|
||||
className: "icon-button",
|
||||
|
@ -64,6 +64,7 @@ const StyledDropDown = styled(DropDown)`
|
||||
`;
|
||||
|
||||
const StyledMainButton = styled.div`
|
||||
-webkit-tap-highlight-color: rgba(0, 0, 0, 0);
|
||||
position: relative;
|
||||
display: block;
|
||||
vertical-align: middle;
|
||||
|
@ -35,6 +35,7 @@ const StyledToastContainer = styled(ToastContainer)`
|
||||
top: 1em;
|
||||
right: 1em;
|
||||
margin-top: 40px;
|
||||
-webkit-tap-highlight-color: rgba(0, 0, 0, 0);
|
||||
|
||||
@media only screen and (max-width: 480px) {
|
||||
width: 100vw;
|
||||
|
@ -23,12 +23,6 @@ const TooltipStyle = styled.div`
|
||||
border: none;
|
||||
}
|
||||
}
|
||||
|
||||
* {
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
`;
|
||||
|
||||
class Tooltip extends Component {
|
||||
|
Loading…
Reference in New Issue
Block a user