Merge branch 'master' of https://github.com/ONLYOFFICE/CommunityServer-AspNetCore
This commit is contained in:
commit
b9edf10894
2
.gitignore
vendored
2
.gitignore
vendored
@ -3,7 +3,6 @@
|
|||||||
*.suo
|
*.suo
|
||||||
*.user
|
*.user
|
||||||
.vs/
|
.vs/
|
||||||
.vscode/
|
|
||||||
*-lock.json
|
*-lock.json
|
||||||
**/node_modules/
|
**/node_modules/
|
||||||
**/storybook-static/
|
**/storybook-static/
|
||||||
@ -12,3 +11,4 @@
|
|||||||
*.log
|
*.log
|
||||||
/packages/asc-web-components
|
/packages/asc-web-components
|
||||||
/products/ASC.People/Data/
|
/products/ASC.People/Data/
|
||||||
|
Data/
|
||||||
|
@ -0,0 +1,54 @@
|
|||||||
|
import React from "react";
|
||||||
|
import styled from "styled-components";
|
||||||
|
import isEqual from "lodash/isEqual";
|
||||||
|
import { ComboBox, TextInput } from "asc-web-components";
|
||||||
|
|
||||||
|
const Container = styled.div`
|
||||||
|
display: flex;
|
||||||
|
margin: 0 0 16px 0;
|
||||||
|
`;
|
||||||
|
|
||||||
|
class ContactField extends React.Component {
|
||||||
|
shouldComponentUpdate(nextProps) {
|
||||||
|
return !isEqual(this.props, nextProps);
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
console.log("ContactField render");
|
||||||
|
|
||||||
|
const {
|
||||||
|
isDisabled,
|
||||||
|
|
||||||
|
comboBoxName,
|
||||||
|
comboBoxOptions,
|
||||||
|
comboBoxSelectedOption,
|
||||||
|
comboBoxOnChange,
|
||||||
|
|
||||||
|
inputName,
|
||||||
|
inputValue,
|
||||||
|
inputOnChange
|
||||||
|
} = this.props;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Container>
|
||||||
|
<ComboBox
|
||||||
|
name={comboBoxName}
|
||||||
|
options={comboBoxOptions}
|
||||||
|
onSelect={comboBoxOnChange}
|
||||||
|
selectedOption={comboBoxSelectedOption}
|
||||||
|
isDisabled={isDisabled}
|
||||||
|
scaled={false}
|
||||||
|
className="field-select"
|
||||||
|
/>
|
||||||
|
<TextInput
|
||||||
|
name={inputName}
|
||||||
|
value={inputValue}
|
||||||
|
isDisabled={isDisabled}
|
||||||
|
onChange={inputOnChange}
|
||||||
|
/>
|
||||||
|
</Container>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ContactField;
|
@ -1,6 +1,8 @@
|
|||||||
import React from 'react'
|
import React from "react";
|
||||||
import styled from 'styled-components';
|
import styled from "styled-components";
|
||||||
import { ComboBox, TextInput } from 'asc-web-components'
|
import isEqual from "lodash/isEqual";
|
||||||
|
import ContactField from "./ContactField";
|
||||||
|
import { ComboBox } from "asc-web-components";
|
||||||
|
|
||||||
const Container = styled.div`
|
const Container = styled.div`
|
||||||
width: 100%;
|
width: 100%;
|
||||||
@ -12,82 +14,96 @@ const Container = styled.div`
|
|||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const Item = styled.div`
|
|
||||||
display: flex;
|
|
||||||
margin: 0 0 16px 0;
|
|
||||||
`;
|
|
||||||
|
|
||||||
const getOptions = (patterns, keyPrefix) => {
|
const getOptions = (patterns, keyPrefix) => {
|
||||||
return patterns.map((item, index) => {
|
return patterns.map((item, index) => {
|
||||||
return {
|
return {
|
||||||
key: keyPrefix + index,
|
key: keyPrefix + index,
|
||||||
label: item.type, //from resource
|
label: item.type, //from resource
|
||||||
icon: item.icon,
|
icon: item.icon,
|
||||||
value: item.type
|
value: item.type
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
}
|
};
|
||||||
|
|
||||||
const renderItems = (contacts, pattern, onTypeChange, onTextChange, isDisabled) => {
|
const renderItems = (
|
||||||
const items = contacts.map((contact, index) => {
|
contacts,
|
||||||
|
pattern,
|
||||||
|
onTypeChange,
|
||||||
|
onTextChange,
|
||||||
|
isDisabled
|
||||||
|
) => {
|
||||||
|
const items = contacts.map((contact, index) => {
|
||||||
|
const prefix = contact.id + "_";
|
||||||
|
const itemOptions = getOptions(pattern, prefix);
|
||||||
|
const itemSelectedOption = itemOptions.filter(
|
||||||
|
option => option.value === contact.type
|
||||||
|
)[0];
|
||||||
|
|
||||||
const prefix = contact.id + "_";
|
return (
|
||||||
|
<ContactField
|
||||||
|
key={prefix + "item_" + index}
|
||||||
|
isDisabled={isDisabled}
|
||||||
|
comboBoxName={prefix + "type"}
|
||||||
|
comboBoxOptions={itemOptions}
|
||||||
|
comboBoxSelectedOption={itemSelectedOption}
|
||||||
|
comboBoxOnChange={onTypeChange}
|
||||||
|
inputName={prefix + "value"}
|
||||||
|
inputValue={contact.value}
|
||||||
|
inputOnChange={onTextChange}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
const itemOptions = getOptions(pattern, prefix);
|
return items;
|
||||||
|
};
|
||||||
|
|
||||||
const itemSelectedOption = itemOptions.filter(option => option.value === contact.type)[0];
|
class ContactsField extends React.Component {
|
||||||
|
shouldComponentUpdate(nextProps) {
|
||||||
|
return !isEqual(this.props, nextProps);
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
render() {
|
||||||
<Item key={prefix + "item_" + index}>
|
console.log("ContactsField render");
|
||||||
<ComboBox
|
|
||||||
name={prefix + "type"}
|
|
||||||
options={itemOptions}
|
|
||||||
onSelect={onTypeChange}
|
|
||||||
selectedOption={itemSelectedOption}
|
|
||||||
isDisabled={isDisabled}
|
|
||||||
scaled={false}
|
|
||||||
className="field-select"
|
|
||||||
/>
|
|
||||||
<TextInput
|
|
||||||
name={prefix + "value"}
|
|
||||||
value={contact.value}
|
|
||||||
isDisabled={isDisabled}
|
|
||||||
onChange={onTextChange}
|
|
||||||
/>
|
|
||||||
</Item>
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
return items;
|
const {
|
||||||
};
|
pattern,
|
||||||
|
contacts,
|
||||||
|
addItemText,
|
||||||
|
onItemAdd,
|
||||||
|
onItemTypeChange,
|
||||||
|
onItemTextChange,
|
||||||
|
isDisabled
|
||||||
|
} = this.props;
|
||||||
|
|
||||||
const ContactsField = React.memo((props) => {
|
const existItems = renderItems(
|
||||||
|
contacts,
|
||||||
const { pattern, contacts, addItemText, onItemAdd, onItemTypeChange, onItemTextChange, isDisabled } = props;
|
pattern,
|
||||||
|
onItemTypeChange,
|
||||||
const existItems = renderItems(contacts, pattern, onItemTypeChange, onItemTextChange, isDisabled);
|
onItemTextChange,
|
||||||
|
isDisabled
|
||||||
|
);
|
||||||
|
|
||||||
const prefix = "null_";
|
const prefix = "null_";
|
||||||
|
|
||||||
const options = getOptions(pattern, prefix);
|
const options = getOptions(pattern, prefix);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Container>
|
<Container>
|
||||||
{existItems}
|
{existItems}
|
||||||
<ComboBox
|
<ComboBox
|
||||||
options={options}
|
options={options}
|
||||||
onSelect={onItemAdd}
|
onSelect={onItemAdd}
|
||||||
selectedOption={{
|
selectedOption={{
|
||||||
key: prefix,
|
key: prefix,
|
||||||
label: addItemText,
|
label: addItemText,
|
||||||
value: ""
|
value: ""
|
||||||
}}
|
}}
|
||||||
isDisabled={isDisabled}
|
isDisabled={isDisabled}
|
||||||
scaled={false}
|
scaled={false}
|
||||||
className="field-select"
|
className="field-select"
|
||||||
/>
|
/>
|
||||||
</Container>
|
</Container>
|
||||||
);
|
);
|
||||||
});
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export default ContactsField
|
export default ContactsField;
|
||||||
|
@ -1,35 +1,44 @@
|
|||||||
import React from 'react'
|
import React from "react";
|
||||||
import { FieldContainer, DateInput } from 'asc-web-components'
|
import isEqual from "lodash/isEqual";
|
||||||
|
import { FieldContainer, DatePicker } from "asc-web-components";
|
||||||
|
|
||||||
const DateField = React.memo((props) => {
|
class DateField extends React.Component {
|
||||||
const {
|
shouldComponentUpdate(nextProps) {
|
||||||
isRequired,
|
return !isEqual(this.props, nextProps);
|
||||||
hasError,
|
}
|
||||||
labelText,
|
|
||||||
|
|
||||||
inputName,
|
render() {
|
||||||
inputValue,
|
console.log("DateField render");
|
||||||
inputIsDisabled,
|
|
||||||
inputOnChange,
|
|
||||||
inputTabIndex
|
|
||||||
} = props;
|
|
||||||
|
|
||||||
return (
|
const {
|
||||||
<FieldContainer
|
isRequired,
|
||||||
isRequired={isRequired}
|
hasError,
|
||||||
hasError={hasError}
|
labelText,
|
||||||
labelText={labelText}
|
|
||||||
>
|
inputName,
|
||||||
<DateInput
|
inputValue,
|
||||||
name={inputName}
|
inputIsDisabled,
|
||||||
selected={inputValue}
|
inputOnChange,
|
||||||
disabled={inputIsDisabled}
|
inputTabIndex
|
||||||
onChange={inputOnChange}
|
} = this.props;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<FieldContainer
|
||||||
|
isRequired={isRequired}
|
||||||
hasError={hasError}
|
hasError={hasError}
|
||||||
tabIndex={inputTabIndex}
|
labelText={labelText}
|
||||||
/>
|
>
|
||||||
</FieldContainer>
|
<DatePicker
|
||||||
);
|
name={inputName}
|
||||||
});
|
selectedDate={inputValue}
|
||||||
|
disabled={inputIsDisabled}
|
||||||
|
onChange={inputOnChange}
|
||||||
|
hasError={hasError}
|
||||||
|
tabIndex={inputTabIndex}
|
||||||
|
/>
|
||||||
|
</FieldContainer>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export default DateField
|
export default DateField;
|
||||||
|
@ -1,42 +1,57 @@
|
|||||||
import React from 'react'
|
import React from "react";
|
||||||
import { FieldContainer, SelectorAddButton, SelectedItem } from 'asc-web-components'
|
import isEqual from "lodash/isEqual";
|
||||||
|
import {
|
||||||
|
FieldContainer,
|
||||||
|
SelectorAddButton,
|
||||||
|
SelectedItem
|
||||||
|
} from "asc-web-components";
|
||||||
|
|
||||||
const DepartmentField = React.memo((props) => {
|
class DepartmentField extends React.Component {
|
||||||
const {
|
shouldComponentUpdate(nextProps) {
|
||||||
isRequired,
|
return !isEqual(this.props, nextProps);
|
||||||
isDisabled,
|
}
|
||||||
hasError,
|
|
||||||
labelText,
|
|
||||||
addButtonTitle,
|
|
||||||
departments,
|
|
||||||
onAddDepartment,
|
|
||||||
onRemoveDepartment
|
|
||||||
} = props;
|
|
||||||
|
|
||||||
return (
|
render() {
|
||||||
<FieldContainer
|
console.log("DepartmentField render");
|
||||||
isRequired={isRequired}
|
|
||||||
hasError={hasError}
|
const {
|
||||||
labelText={labelText}
|
isRequired,
|
||||||
className="departments-field"
|
isDisabled,
|
||||||
>
|
hasError,
|
||||||
<SelectorAddButton
|
labelText,
|
||||||
isDisabled={isDisabled}
|
addButtonTitle,
|
||||||
title={addButtonTitle}
|
departments,
|
||||||
onClick={onAddDepartment}
|
onAddDepartment,
|
||||||
className="department-add-btn"
|
onRemoveDepartment
|
||||||
/>
|
} = this.props;
|
||||||
{departments && departments.map((department) => (
|
|
||||||
<SelectedItem
|
return (
|
||||||
key={`department_${department.id}`}
|
<FieldContainer
|
||||||
text={department.name}
|
isRequired={isRequired}
|
||||||
onClose={() => { onRemoveDepartment(department.id) }}
|
hasError={hasError}
|
||||||
isInline={true}
|
labelText={labelText}
|
||||||
className="department-item"
|
className="departments-field"
|
||||||
|
>
|
||||||
|
<SelectorAddButton
|
||||||
|
isDisabled={isDisabled}
|
||||||
|
title={addButtonTitle}
|
||||||
|
onClick={onAddDepartment}
|
||||||
|
className="department-add-btn"
|
||||||
/>
|
/>
|
||||||
))}
|
{departments.map(department => (
|
||||||
</FieldContainer>
|
<SelectedItem
|
||||||
);
|
key={`department_${department.id}`}
|
||||||
});
|
text={department.name}
|
||||||
|
onClose={() => {
|
||||||
|
onRemoveDepartment(department.id);
|
||||||
|
}}
|
||||||
|
isInline={true}
|
||||||
|
className="department-item"
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</FieldContainer>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export default DepartmentField
|
export default DepartmentField;
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import React from 'react'
|
import React from "react";
|
||||||
import styled from 'styled-components';
|
import styled from "styled-components";
|
||||||
import { Text } from 'asc-web-components'
|
import { Text } from "asc-web-components";
|
||||||
|
|
||||||
const Container = styled.div`
|
const Container = styled.div`
|
||||||
margin: 0 0 40px 0;
|
margin: 0 0 40px 0;
|
||||||
@ -11,7 +11,7 @@ const Header = styled(Text.ContentHeader)`
|
|||||||
line-height: unset;
|
line-height: unset;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const InfoFieldContainer = React.memo((props) => {
|
const InfoFieldContainer = React.memo(props => {
|
||||||
const { headerText, children } = props;
|
const { headerText, children } = props;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -22,4 +22,4 @@ const InfoFieldContainer = React.memo((props) => {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
export default InfoFieldContainer
|
export default InfoFieldContainer;
|
||||||
|
@ -1,66 +1,80 @@
|
|||||||
import React from 'react'
|
import React from "react";
|
||||||
import { FieldContainer, RadioButtonGroup, PasswordInput } from 'asc-web-components'
|
import isEqual from "lodash/isEqual";
|
||||||
|
import {
|
||||||
|
FieldContainer,
|
||||||
|
RadioButtonGroup,
|
||||||
|
PasswordInput
|
||||||
|
} from "asc-web-components";
|
||||||
|
|
||||||
const PasswordField = React.memo((props) => {
|
class PasswordField extends React.Component {
|
||||||
const {
|
shouldComponentUpdate(nextProps) {
|
||||||
isRequired,
|
return !isEqual(this.props, nextProps);
|
||||||
hasError,
|
}
|
||||||
labelText,
|
|
||||||
passwordSettings,
|
|
||||||
|
|
||||||
radioName,
|
render() {
|
||||||
radioValue,
|
console.log("PasswordField render");
|
||||||
radioOptions,
|
|
||||||
radioIsDisabled,
|
|
||||||
radioOnChange,
|
|
||||||
|
|
||||||
inputName,
|
const {
|
||||||
emailInputName,
|
isRequired,
|
||||||
inputValue,
|
hasError,
|
||||||
inputIsDisabled,
|
labelText,
|
||||||
inputOnChange,
|
passwordSettings,
|
||||||
inputTabIndex,
|
|
||||||
|
|
||||||
copyLinkText,
|
radioName,
|
||||||
} = props;
|
radioValue,
|
||||||
|
radioOptions,
|
||||||
|
radioIsDisabled,
|
||||||
|
radioOnChange,
|
||||||
|
|
||||||
const tooltipPasswordLength = 'from ' + passwordSettings.minLength + ' to 30 characters';
|
inputName,
|
||||||
|
emailInputName,
|
||||||
|
inputValue,
|
||||||
|
inputIsDisabled,
|
||||||
|
inputOnChange,
|
||||||
|
inputTabIndex,
|
||||||
|
|
||||||
return (
|
copyLinkText
|
||||||
<FieldContainer
|
} = this.props;
|
||||||
isRequired={isRequired}
|
|
||||||
hasError={hasError}
|
const tooltipPasswordLength =
|
||||||
labelText={labelText}
|
"from " + passwordSettings.minLength + " to 30 characters";
|
||||||
>
|
|
||||||
<RadioButtonGroup
|
return (
|
||||||
name={radioName}
|
<FieldContainer
|
||||||
selected={radioValue}
|
isRequired={isRequired}
|
||||||
options={radioOptions}
|
hasError={hasError}
|
||||||
isDisabled={radioIsDisabled}
|
labelText={labelText}
|
||||||
onClick={radioOnChange}
|
>
|
||||||
className="radio-group"
|
<RadioButtonGroup
|
||||||
/>
|
name={radioName}
|
||||||
<PasswordInput
|
selected={radioValue}
|
||||||
inputName={inputName}
|
options={radioOptions}
|
||||||
emailInputName={emailInputName}
|
isDisabled={radioIsDisabled}
|
||||||
inputValue={inputValue}
|
onClick={radioOnChange}
|
||||||
inputWidth="320px"
|
className="radio-group"
|
||||||
inputTabIndex={inputTabIndex}
|
/>
|
||||||
onChange={inputOnChange}
|
<PasswordInput
|
||||||
clipActionResource={copyLinkText}
|
inputName={inputName}
|
||||||
clipEmailResource='E-mail: '
|
emailInputName={emailInputName}
|
||||||
clipPasswordResource='Password: '
|
inputValue={inputValue}
|
||||||
tooltipPasswordTitle='Password must contain:'
|
inputWidth="320px"
|
||||||
tooltipPasswordLength={tooltipPasswordLength}
|
inputTabIndex={inputTabIndex}
|
||||||
tooltipPasswordDigits='digits'
|
onChange={inputOnChange}
|
||||||
tooltipPasswordCapital='capital letters'
|
clipActionResource={copyLinkText}
|
||||||
tooltipPasswordSpecial='special characters (!@#$%^&*)'
|
clipEmailResource="E-mail: "
|
||||||
generatorSpecial='!@#$%^&*'
|
clipPasswordResource="Password: "
|
||||||
passwordSettings={passwordSettings}
|
tooltipPasswordTitle="Password must contain:"
|
||||||
isDisabled={inputIsDisabled}
|
tooltipPasswordLength={tooltipPasswordLength}
|
||||||
/>
|
tooltipPasswordDigits="digits"
|
||||||
</FieldContainer>
|
tooltipPasswordCapital="capital letters"
|
||||||
);
|
tooltipPasswordSpecial="special characters (!@#$%^&*)"
|
||||||
});
|
generatorSpecial="!@#$%^&*"
|
||||||
|
passwordSettings={passwordSettings}
|
||||||
|
isDisabled={inputIsDisabled}
|
||||||
|
/>
|
||||||
|
</FieldContainer>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export default PasswordField;
|
export default PasswordField;
|
@ -1,35 +1,44 @@
|
|||||||
import React from 'react'
|
import React from "react";
|
||||||
import { FieldContainer, RadioButtonGroup } from 'asc-web-components'
|
import isEqual from "lodash/isEqual";
|
||||||
|
import { FieldContainer, RadioButtonGroup } from "asc-web-components";
|
||||||
|
|
||||||
const RadioField = React.memo((props) => {
|
class RadioField extends React.Component {
|
||||||
const {
|
shouldComponentUpdate(nextProps) {
|
||||||
isRequired,
|
return !isEqual(this.props, nextProps);
|
||||||
hasError,
|
}
|
||||||
labelText,
|
|
||||||
|
|
||||||
radioName,
|
render() {
|
||||||
radioValue,
|
console.log("RadioField render");
|
||||||
radioOptions,
|
|
||||||
radioIsDisabled,
|
|
||||||
radioOnChange
|
|
||||||
} = props;
|
|
||||||
|
|
||||||
return (
|
const {
|
||||||
<FieldContainer
|
isRequired,
|
||||||
isRequired={isRequired}
|
hasError,
|
||||||
hasError={hasError}
|
labelText,
|
||||||
labelText={labelText}
|
|
||||||
>
|
|
||||||
<RadioButtonGroup
|
|
||||||
name={radioName}
|
|
||||||
selected={radioValue}
|
|
||||||
options={radioOptions}
|
|
||||||
isDisabled={radioIsDisabled}
|
|
||||||
onClick={radioOnChange}
|
|
||||||
className="radio-group"
|
|
||||||
/>
|
|
||||||
</FieldContainer>
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
export default RadioField
|
radioName,
|
||||||
|
radioValue,
|
||||||
|
radioOptions,
|
||||||
|
radioIsDisabled,
|
||||||
|
radioOnChange
|
||||||
|
} = this.props;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<FieldContainer
|
||||||
|
isRequired={isRequired}
|
||||||
|
hasError={hasError}
|
||||||
|
labelText={labelText}
|
||||||
|
>
|
||||||
|
<RadioButtonGroup
|
||||||
|
name={radioName}
|
||||||
|
selected={radioValue}
|
||||||
|
options={radioOptions}
|
||||||
|
isDisabled={radioIsDisabled}
|
||||||
|
onClick={radioOnChange}
|
||||||
|
className="radio-group"
|
||||||
|
/>
|
||||||
|
</FieldContainer>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default RadioField;
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import React from 'react'
|
import React from "react";
|
||||||
import styled from 'styled-components';
|
import styled from "styled-components";
|
||||||
import { FieldContainer, TextInput, Button } from 'asc-web-components'
|
import isEqual from "lodash/isEqual";
|
||||||
|
import { FieldContainer, TextInput, Button } from "asc-web-components";
|
||||||
|
|
||||||
const InputContainer = styled.div`
|
const InputContainer = styled.div`
|
||||||
width: 100%;
|
width: 100%;
|
||||||
@ -9,47 +10,55 @@ const InputContainer = styled.div`
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const TextChangeField = React.memo((props) => {
|
class TextChangeField extends React.Component {
|
||||||
const {
|
shouldComponentUpdate(nextProps) {
|
||||||
isRequired,
|
return !isEqual(this.props, nextProps);
|
||||||
hasError,
|
}
|
||||||
labelText,
|
|
||||||
|
|
||||||
inputName,
|
render() {
|
||||||
inputValue,
|
console.log("TextChangeField render");
|
||||||
inputTabIndex,
|
|
||||||
|
|
||||||
buttonText,
|
const {
|
||||||
buttonIsDisabled,
|
isRequired,
|
||||||
buttonOnClick,
|
hasError,
|
||||||
buttonTabIndex
|
labelText,
|
||||||
} = props;
|
|
||||||
|
|
||||||
return (
|
inputName,
|
||||||
<FieldContainer
|
inputValue,
|
||||||
isRequired={isRequired}
|
inputTabIndex,
|
||||||
hasError={hasError}
|
|
||||||
labelText={labelText}
|
buttonText,
|
||||||
>
|
buttonIsDisabled,
|
||||||
<InputContainer>
|
buttonOnClick,
|
||||||
<TextInput
|
buttonTabIndex
|
||||||
name={inputName}
|
} = this.props;
|
||||||
value={inputValue}
|
|
||||||
isDisabled={true}
|
return (
|
||||||
hasError={hasError}
|
<FieldContainer
|
||||||
tabIndex={inputTabIndex}
|
isRequired={isRequired}
|
||||||
/>
|
hasError={hasError}
|
||||||
<Button
|
labelText={labelText}
|
||||||
label={buttonText}
|
>
|
||||||
onClick={buttonOnClick}
|
<InputContainer>
|
||||||
isDisabled={buttonIsDisabled}
|
<TextInput
|
||||||
size="medium"
|
name={inputName}
|
||||||
style={{ marginLeft: "8px" }}
|
value={inputValue}
|
||||||
tabIndex={buttonTabIndex}
|
isDisabled={true}
|
||||||
/>
|
hasError={hasError}
|
||||||
</InputContainer>
|
tabIndex={inputTabIndex}
|
||||||
</FieldContainer>
|
/>
|
||||||
);
|
<Button
|
||||||
});
|
label={buttonText}
|
||||||
|
onClick={buttonOnClick}
|
||||||
|
isDisabled={buttonIsDisabled}
|
||||||
|
size="medium"
|
||||||
|
style={{ marginLeft: "8px" }}
|
||||||
|
tabIndex={buttonTabIndex}
|
||||||
|
/>
|
||||||
|
</InputContainer>
|
||||||
|
</FieldContainer>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export default TextChangeField;
|
export default TextChangeField;
|
@ -1,38 +1,47 @@
|
|||||||
import React from 'react'
|
import React from "react";
|
||||||
import { FieldContainer, TextInput } from 'asc-web-components'
|
import isEqual from "lodash/isEqual";
|
||||||
|
import { FieldContainer, TextInput } from "asc-web-components";
|
||||||
|
|
||||||
const TextField = React.memo((props) => {
|
class TextField extends React.Component {
|
||||||
const {
|
shouldComponentUpdate(nextProps) {
|
||||||
isRequired,
|
return !isEqual(this.props, nextProps);
|
||||||
hasError,
|
}
|
||||||
labelText,
|
|
||||||
|
|
||||||
inputName,
|
render() {
|
||||||
inputValue,
|
console.log("TextField render");
|
||||||
inputIsDisabled,
|
|
||||||
inputOnChange,
|
|
||||||
inputAutoFocussed,
|
|
||||||
inputTabIndex
|
|
||||||
} = props;
|
|
||||||
|
|
||||||
return (
|
const {
|
||||||
<FieldContainer
|
isRequired,
|
||||||
isRequired={isRequired}
|
hasError,
|
||||||
hasError={hasError}
|
labelText,
|
||||||
labelText={labelText}
|
|
||||||
>
|
inputName,
|
||||||
<TextInput
|
inputValue,
|
||||||
name={inputName}
|
inputIsDisabled,
|
||||||
value={inputValue}
|
inputOnChange,
|
||||||
isDisabled={inputIsDisabled}
|
inputAutoFocussed,
|
||||||
onChange={inputOnChange}
|
inputTabIndex
|
||||||
|
} = this.props;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<FieldContainer
|
||||||
|
isRequired={isRequired}
|
||||||
hasError={hasError}
|
hasError={hasError}
|
||||||
className="field-input"
|
labelText={labelText}
|
||||||
isAutoFocussed={inputAutoFocussed}
|
>
|
||||||
tabIndex={inputTabIndex}
|
<TextInput
|
||||||
/>
|
name={inputName}
|
||||||
</FieldContainer>
|
value={inputValue}
|
||||||
);
|
isDisabled={inputIsDisabled}
|
||||||
});
|
onChange={inputOnChange}
|
||||||
|
hasError={hasError}
|
||||||
|
className="field-input"
|
||||||
|
isAutoFocussed={inputAutoFocussed}
|
||||||
|
tabIndex={inputTabIndex}
|
||||||
|
/>
|
||||||
|
</FieldContainer>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export default TextField;
|
export default TextField;
|
@ -1,7 +1,7 @@
|
|||||||
import React from 'react'
|
import React from 'react'
|
||||||
import { withRouter } from 'react-router'
|
import { withRouter } from 'react-router'
|
||||||
import { connect } from 'react-redux'
|
import { connect } from 'react-redux'
|
||||||
import { Avatar, Button, Textarea, Text, toastr } from 'asc-web-components'
|
import { Avatar, Button, Textarea, toastr } from 'asc-web-components'
|
||||||
import { withTranslation } from 'react-i18next';
|
import { withTranslation } from 'react-i18next';
|
||||||
import { toEmployeeWrapper, getUserRole, getUserContactsPattern, getUserContacts } from "../../../../../store/people/selectors";
|
import { toEmployeeWrapper, getUserRole, getUserContactsPattern, getUserContacts } from "../../../../../store/people/selectors";
|
||||||
import { createProfile } from '../../../../../store/profile/actions';
|
import { createProfile } from '../../../../../store/profile/actions';
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { find, filter } from "lodash";
|
import { find, filter, cloneDeep } from "lodash";
|
||||||
import { EmployeeActivationStatus, EmployeeStatus } from "../../helpers/constants";
|
import { EmployeeActivationStatus, EmployeeStatus } from "../../helpers/constants";
|
||||||
|
|
||||||
export function getSelectedUser(selection, userId) {
|
export function getSelectedUser(selection, userId) {
|
||||||
@ -141,5 +141,5 @@ export function toEmployeeWrapper(profile) {
|
|||||||
contacts: []
|
contacts: []
|
||||||
};
|
};
|
||||||
|
|
||||||
return { ...emptyData, ...profile };
|
return cloneDeep({ ...emptyData, ...profile });
|
||||||
}
|
}
|
@ -318,6 +318,10 @@ namespace ASC.Employee.Core.Controllers
|
|||||||
[Authorize(AuthenticationSchemes = "confirm")]
|
[Authorize(AuthenticationSchemes = "confirm")]
|
||||||
public EmployeeWraperFull AddMember(MemberModel memberModel)
|
public EmployeeWraperFull AddMember(MemberModel memberModel)
|
||||||
{
|
{
|
||||||
|
if (HttpContext.User.IsInRole(ASC.Common.Security.Authorizing.Role.System))
|
||||||
|
{
|
||||||
|
SecurityContext.AuthenticateMe(ASC.Core.Configuration.Constants.CoreSystem);
|
||||||
|
}
|
||||||
SecurityContext.DemandPermissions(Tenant, Constants.Action_AddRemoveUser);
|
SecurityContext.DemandPermissions(Tenant, Constants.Action_AddRemoveUser);
|
||||||
|
|
||||||
if (string.IsNullOrEmpty(memberModel.Password))
|
if (string.IsNullOrEmpty(memberModel.Password))
|
||||||
|
@ -60,8 +60,6 @@ namespace ASC.People
|
|||||||
|
|
||||||
var builder = services.AddMvc(config =>
|
var builder = services.AddMvc(config =>
|
||||||
{
|
{
|
||||||
var policy = new AuthorizationPolicyBuilder().RequireAuthenticatedUser().Build();
|
|
||||||
config.Filters.Add(new AuthorizeFilter(policy));
|
|
||||||
config.Filters.Add(new TypeFilterAttribute(typeof(TenantStatusFilter)));
|
config.Filters.Add(new TypeFilterAttribute(typeof(TenantStatusFilter)));
|
||||||
config.Filters.Add(new TypeFilterAttribute(typeof(PaymentFilter)));
|
config.Filters.Add(new TypeFilterAttribute(typeof(PaymentFilter)));
|
||||||
config.Filters.Add(new TypeFilterAttribute(typeof(IpSecurityFilter)));
|
config.Filters.Add(new TypeFilterAttribute(typeof(IpSecurityFilter)));
|
||||||
|
@ -4,6 +4,7 @@ import { Loader } from "asc-web-components";
|
|||||||
import StudioLayout from "./components/Layout/index";
|
import StudioLayout from "./components/Layout/index";
|
||||||
import Login from "./components/pages/Login";
|
import Login from "./components/pages/Login";
|
||||||
import { PrivateRoute } from "./helpers/privateRoute";
|
import { PrivateRoute } from "./helpers/privateRoute";
|
||||||
|
import { PublicRoute } from "./helpers/publicRoute";
|
||||||
import { Error404 } from "./components/pages/Error";
|
import { Error404 } from "./components/pages/Error";
|
||||||
|
|
||||||
const Home = lazy(() => import("./components/pages/Home"));
|
const Home = lazy(() => import("./components/pages/Home"));
|
||||||
@ -18,8 +19,8 @@ const App = () => {
|
|||||||
fallback={<Loader className="pageLoader" type="rombs" size={40} />}
|
fallback={<Loader className="pageLoader" type="rombs" size={40} />}
|
||||||
>
|
>
|
||||||
<Switch>
|
<Switch>
|
||||||
<Route exact path="/login" component={Login} />
|
<PublicRoute exact path="/login" component={Login} />
|
||||||
<Route exact path="/confirm" component={Confirm} />
|
<PublicRoute path="/confirm" component={Confirm} />
|
||||||
<PrivateRoute exact path="/" component={Home} />
|
<PrivateRoute exact path="/" component={Home} />
|
||||||
<PrivateRoute exact path="/about" component={About} />
|
<PrivateRoute exact path="/about" component={About} />
|
||||||
<PrivateRoute component={Error404} />
|
<PrivateRoute component={Error404} />
|
||||||
|
24
web/ASC.Web.Client/src/helpers/publicRoute.js
Normal file
24
web/ASC.Web.Client/src/helpers/publicRoute.js
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { Redirect, Route } from 'react-router-dom';
|
||||||
|
import { AUTH_KEY } from './constants';
|
||||||
|
import Cookies from 'universal-cookie';
|
||||||
|
|
||||||
|
export const PublicRoute = ({ component: Component, ...rest }) => {
|
||||||
|
return (
|
||||||
|
<Route
|
||||||
|
{...rest}
|
||||||
|
render={props =>
|
||||||
|
(new Cookies()).get(AUTH_KEY) ? (
|
||||||
|
<Redirect
|
||||||
|
to={{
|
||||||
|
pathname: "/",
|
||||||
|
state: { from: props.location }
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<Component {...props} />
|
||||||
|
)
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
};
|
37
web/ASC.Web.Components/.vscode/launch.json
vendored
Normal file
37
web/ASC.Web.Components/.vscode/launch.json
vendored
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
{
|
||||||
|
"version": "0.2.0",
|
||||||
|
"configurations": [
|
||||||
|
{
|
||||||
|
"type": "node",
|
||||||
|
"request": "launch",
|
||||||
|
"name": "Jest All",
|
||||||
|
"program": "${workspaceFolder}/node_modules/.bin/jest",
|
||||||
|
"args": [
|
||||||
|
"--runInBand"
|
||||||
|
],
|
||||||
|
"console": "integratedTerminal",
|
||||||
|
"internalConsoleOptions": "neverOpen",
|
||||||
|
"disableOptimisticBPs": true,
|
||||||
|
"windows": {
|
||||||
|
"program": "${workspaceFolder}/node_modules/jest-cli/bin/jest",
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "node",
|
||||||
|
"request": "launch",
|
||||||
|
"name": "Jest Current File",
|
||||||
|
"program": "${workspaceFolder}/node_modules/.bin/jest",
|
||||||
|
"args": [
|
||||||
|
"${fileBasenameNoExtension}",
|
||||||
|
"--config",
|
||||||
|
"jest.config.js"
|
||||||
|
],
|
||||||
|
"console": "integratedTerminal",
|
||||||
|
"internalConsoleOptions": "neverOpen",
|
||||||
|
"disableOptimisticBPs": true,
|
||||||
|
"windows": {
|
||||||
|
"program": "${workspaceFolder}/node_modules/jest-cli/bin/jest",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "asc-web-components",
|
"name": "asc-web-components",
|
||||||
"version": "1.0.63",
|
"version": "1.0.66",
|
||||||
"description": "Ascensio System SIA component library",
|
"description": "Ascensio System SIA component library",
|
||||||
"license": "AGPL-3.0",
|
"license": "AGPL-3.0",
|
||||||
"main": "dist/asc-web-components.js",
|
"main": "dist/asc-web-components.js",
|
||||||
|
@ -6,7 +6,7 @@ import withReadme from "storybook-readme/with-readme";
|
|||||||
import Readme from "./README.md";
|
import Readme from "./README.md";
|
||||||
import AdvancedSelector from "./";
|
import AdvancedSelector from "./";
|
||||||
import Section from "../../../.storybook/decorators/section";
|
import Section from "../../../.storybook/decorators/section";
|
||||||
import { boolean } from "@storybook/addon-knobs/dist/deprecated";
|
import { boolean, select } from "@storybook/addon-knobs/dist/deprecated";
|
||||||
import { ArrayValue, BooleanValue } from "react-values";
|
import { ArrayValue, BooleanValue } from "react-values";
|
||||||
import Button from "../button";
|
import Button from "../button";
|
||||||
|
|
||||||
@ -77,6 +77,7 @@ storiesOf("Components|AdvancedSelector", module)
|
|||||||
>
|
>
|
||||||
{({ value, set }) => (
|
{({ value, set }) => (
|
||||||
<AdvancedSelector
|
<AdvancedSelector
|
||||||
|
size={select("size", ["compact", "full"], "compact")}
|
||||||
placeholder={text("placeholder", "Search users")}
|
placeholder={text("placeholder", "Search users")}
|
||||||
onSearchChanged={value => {
|
onSearchChanged={value => {
|
||||||
action("onSearchChanged")(value);
|
action("onSearchChanged")(value);
|
||||||
@ -179,6 +180,7 @@ storiesOf("Components|AdvancedSelector", module)
|
|||||||
>
|
>
|
||||||
{({ value, set }) => (
|
{({ value, set }) => (
|
||||||
<AdvancedSelector
|
<AdvancedSelector
|
||||||
|
size={select("size", ["compact", "full"], "compact")}
|
||||||
isDropDown={true}
|
isDropDown={true}
|
||||||
isOpen={isOpen}
|
isOpen={isOpen}
|
||||||
placeholder={text("placeholder", "Search users")}
|
placeholder={text("placeholder", "Search users")}
|
||||||
|
@ -12,6 +12,8 @@ import { isArrayEqual } from "../../utils/array";
|
|||||||
import findIndex from "lodash/findIndex";
|
import findIndex from "lodash/findIndex";
|
||||||
import filter from "lodash/filter";
|
import filter from "lodash/filter";
|
||||||
import DropDown from "../drop-down";
|
import DropDown from "../drop-down";
|
||||||
|
import { handleAnyClick } from "../../utils/event";
|
||||||
|
import isEmpty from 'lodash/isEmpty';
|
||||||
|
|
||||||
/* eslint-disable no-unused-vars */
|
/* eslint-disable no-unused-vars */
|
||||||
/* eslint-disable react/prop-types */
|
/* eslint-disable react/prop-types */
|
||||||
@ -19,7 +21,7 @@ const Container = ({
|
|||||||
value,
|
value,
|
||||||
placeholder,
|
placeholder,
|
||||||
isMultiSelect,
|
isMultiSelect,
|
||||||
mode,
|
size,
|
||||||
width,
|
width,
|
||||||
maxHeight,
|
maxHeight,
|
||||||
isDisabled,
|
isDisabled,
|
||||||
@ -32,16 +34,27 @@ const Container = ({
|
|||||||
groups,
|
groups,
|
||||||
selectedGroups,
|
selectedGroups,
|
||||||
onChangeGroup,
|
onChangeGroup,
|
||||||
|
isOpen,
|
||||||
|
isDropDown,
|
||||||
|
containerWidth,
|
||||||
|
containerHeight,
|
||||||
...props
|
...props
|
||||||
}) => <div {...props} />;
|
}) => <div {...props} />;
|
||||||
/* eslint-enable react/prop-types */
|
/* eslint-enable react/prop-types */
|
||||||
/* eslint-enable no-unused-vars */
|
/* eslint-enable no-unused-vars */
|
||||||
|
|
||||||
const StyledContainer = styled(Container)`
|
const StyledContainer = styled(Container)`
|
||||||
${props => (props.width ? `width: ${props.width}px;` : "")}
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
|
||||||
|
${props => (props.containerWidth ? `width: ${props.containerWidth}px;` : "")}
|
||||||
|
${props =>
|
||||||
|
props.containerHeight
|
||||||
|
? `height: ${props.containerHeight}px;`
|
||||||
|
: ""}
|
||||||
|
|
||||||
.data_container {
|
.data_container {
|
||||||
margin: 16px;
|
margin: 16px 16px 0 16px;
|
||||||
|
|
||||||
.options_searcher {
|
.options_searcher {
|
||||||
margin-bottom: 12px;
|
margin-bottom: 12px;
|
||||||
@ -53,7 +66,7 @@ const StyledContainer = styled(Container)`
|
|||||||
|
|
||||||
.option_select_all_checkbox {
|
.option_select_all_checkbox {
|
||||||
margin-bottom: 12px;
|
margin-bottom: 12px;
|
||||||
margin-left: 8px;
|
/*margin-left: 8px;*/
|
||||||
}
|
}
|
||||||
|
|
||||||
.options_list {
|
.options_list {
|
||||||
@ -62,33 +75,36 @@ const StyledContainer = styled(Container)`
|
|||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
|
||||||
.option_checkbox {
|
.option_checkbox {
|
||||||
margin-left: 8px;
|
/*margin-left: 8px;*/
|
||||||
}
|
}
|
||||||
|
|
||||||
.option_link {
|
.option_link {
|
||||||
padding-left: 8px;
|
padding-left: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
&:hover {
|
/*&:hover {
|
||||||
background-color: #eceef1;
|
background-color: #eceef1;
|
||||||
}
|
}*/
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.button_container {
|
.button_container {
|
||||||
border-top: 1px solid #eceef1;
|
border-top: 1px solid #eceef1;
|
||||||
.add_members_btn {
|
display: flex;
|
||||||
margin: 16px;
|
|
||||||
width: 293px;
|
.add_members_btn {
|
||||||
|
margin: 16px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
`;
|
`;
|
||||||
|
|
||||||
class AdvancedSelector extends React.Component {
|
class AdvancedSelector extends React.Component {
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
|
|
||||||
|
this.ref = React.createRef();
|
||||||
|
|
||||||
const groups = this.convertGroups(this.props.groups);
|
const groups = this.convertGroups(this.props.groups);
|
||||||
const currentGroup = this.getCurrentGroup(groups);
|
const currentGroup = this.getCurrentGroup(groups);
|
||||||
|
|
||||||
@ -98,25 +114,53 @@ class AdvancedSelector extends React.Component {
|
|||||||
groups: groups,
|
groups: groups,
|
||||||
currentGroup: currentGroup
|
currentGroup: currentGroup
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if (props.isOpen) handleAnyClick(true, this.handleClick);
|
||||||
|
}
|
||||||
|
|
||||||
|
handleClick = e => {
|
||||||
|
if (this.props.isOpen && !this.ref.current.contains(e.target)) {
|
||||||
|
this.props.onSelect && this.props.onSelect(this.state.selectedOptions);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
componentWillUnmount() {
|
||||||
|
handleAnyClick(false, this.handleClick);
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidUpdate(prevProps) {
|
componentDidUpdate(prevProps) {
|
||||||
|
let newState = {};
|
||||||
|
|
||||||
if (!isArrayEqual(this.props.selectedOptions, prevProps.selectedOptions)) {
|
if (!isArrayEqual(this.props.selectedOptions, prevProps.selectedOptions)) {
|
||||||
this.setState({ selectedOptions: this.props.selectedOptions });
|
newState = { selectedOptions: this.props.selectedOptions };
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.props.isMultiSelect !== prevProps.isMultiSelect) {
|
if (this.props.isMultiSelect !== prevProps.isMultiSelect) {
|
||||||
this.setState({ selectedOptions: [] });
|
newState = Object.assign({}, newState, {
|
||||||
|
selectedOptions: []
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.props.selectedAll !== prevProps.selectedAll) {
|
if (this.props.selectedAll !== prevProps.selectedAll) {
|
||||||
this.setState({ selectedAll: this.props.selectedAll });
|
newState = Object.assign({}, newState, {
|
||||||
|
selectedAll: this.props.selectedAll
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!isArrayEqual(this.props.groups, prevProps.groups)) {
|
if (!isArrayEqual(this.props.groups, prevProps.groups)) {
|
||||||
const groups = this.convertGroups(this.props.groups);
|
const groups = this.convertGroups(this.props.groups);
|
||||||
const currentGroup = this.getCurrentGroup(groups);
|
const currentGroup = this.getCurrentGroup(groups);
|
||||||
this.setState({ groups, currentGroup });
|
newState = Object.assign({}, newState, {
|
||||||
|
groups, currentGroup
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if(!isEmpty(newState)) {
|
||||||
|
this.setState({ ...this.state, ...newState });
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.props.isOpen !== prevProps.isOpen) {
|
||||||
|
handleAnyClick(this.props.isOpen, this.handleClick);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -215,19 +259,29 @@ class AdvancedSelector extends React.Component {
|
|||||||
const {
|
const {
|
||||||
value,
|
value,
|
||||||
placeholder,
|
placeholder,
|
||||||
maxHeight,
|
|
||||||
isDisabled,
|
isDisabled,
|
||||||
onSearchChanged,
|
onSearchChanged,
|
||||||
options,
|
options,
|
||||||
isMultiSelect,
|
isMultiSelect,
|
||||||
buttonLabel,
|
buttonLabel,
|
||||||
selectAllLabel
|
selectAllLabel,
|
||||||
|
size
|
||||||
} = this.props;
|
} = this.props;
|
||||||
|
|
||||||
const { selectedOptions, selectedAll, currentGroup, groups } = this.state;
|
const { selectedOptions, selectedAll, currentGroup, groups } = this.state;
|
||||||
|
|
||||||
|
const containerHeight = size === "compact" ? (!groups || !groups.length ? 336 : 326) : 545;
|
||||||
|
const containerWidth = size === "compact" ? (!groups || !groups.length ? 325 : 326) : 690;
|
||||||
|
const listHeight = size === "compact" ? (!groups || !groups.length ? 176 : 120) : 345;
|
||||||
|
const itemHeight = 32;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<StyledContainer {...this.props}>
|
<StyledContainer
|
||||||
<div className="data_container">
|
containerHeight={containerHeight}
|
||||||
|
containerWidth={containerWidth}
|
||||||
|
{...this.props}
|
||||||
|
>
|
||||||
|
<div className="data_container" ref={this.ref}>
|
||||||
<SearchInput
|
<SearchInput
|
||||||
className="options_searcher"
|
className="options_searcher"
|
||||||
isDisabled={isDisabled}
|
isDisabled={isDisabled}
|
||||||
@ -264,10 +318,10 @@ class AdvancedSelector extends React.Component {
|
|||||||
)}
|
)}
|
||||||
<FixedSizeList
|
<FixedSizeList
|
||||||
className="options_list"
|
className="options_list"
|
||||||
height={maxHeight}
|
height={listHeight}
|
||||||
itemSize={32}
|
itemSize={itemHeight}
|
||||||
itemCount={options.length}
|
itemCount={this.props.options.length}
|
||||||
itemData={options}
|
itemData={this.props.options}
|
||||||
outerElementType={CustomScrollbarsVirtualList}
|
outerElementType={CustomScrollbarsVirtualList}
|
||||||
>
|
>
|
||||||
{this.renderRow.bind(this)}
|
{this.renderRow.bind(this)}
|
||||||
@ -310,8 +364,7 @@ AdvancedSelector.propTypes = {
|
|||||||
value: PropTypes.string,
|
value: PropTypes.string,
|
||||||
placeholder: PropTypes.string,
|
placeholder: PropTypes.string,
|
||||||
isMultiSelect: PropTypes.bool,
|
isMultiSelect: PropTypes.bool,
|
||||||
mode: PropTypes.oneOf(["base", "compact"]),
|
size: PropTypes.oneOf(["compact", "full"]),
|
||||||
width: PropTypes.number,
|
|
||||||
maxHeight: PropTypes.number,
|
maxHeight: PropTypes.number,
|
||||||
isDisabled: PropTypes.bool,
|
isDisabled: PropTypes.bool,
|
||||||
onSearchChanged: PropTypes.func,
|
onSearchChanged: PropTypes.func,
|
||||||
@ -330,9 +383,7 @@ AdvancedSelector.propTypes = {
|
|||||||
|
|
||||||
AdvancedSelector.defaultProps = {
|
AdvancedSelector.defaultProps = {
|
||||||
isMultiSelect: false,
|
isMultiSelect: false,
|
||||||
width: 325,
|
size: "compact",
|
||||||
maxHeight: 545,
|
|
||||||
mode: "base",
|
|
||||||
buttonLabel: "Add members",
|
buttonLabel: "Add members",
|
||||||
selectAllLabel: "Select all"
|
selectAllLabel: "Select all"
|
||||||
};
|
};
|
||||||
|
@ -0,0 +1,98 @@
|
|||||||
|
import React from "react";
|
||||||
|
import { storiesOf } from "@storybook/react";
|
||||||
|
import { action } from "@storybook/addon-actions";
|
||||||
|
import { withKnobs, text } from "@storybook/addon-knobs/react";
|
||||||
|
import AdvancedSelector from "../advanced-selector";
|
||||||
|
import Section from "../../../.storybook/decorators/section";
|
||||||
|
import { boolean } from "@storybook/addon-knobs/dist/deprecated";
|
||||||
|
import { ArrayValue, BooleanValue } from "react-values";
|
||||||
|
import Button from "../button";
|
||||||
|
|
||||||
|
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", () => {
|
||||||
|
const options = [
|
||||||
|
{
|
||||||
|
key: "group-all",
|
||||||
|
label: "All groups",
|
||||||
|
total: 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "group-dev",
|
||||||
|
label: "Development",
|
||||||
|
total: 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "group-management",
|
||||||
|
label: "Management",
|
||||||
|
total: 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "group-marketing",
|
||||||
|
label: "Marketing",
|
||||||
|
total: 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "group-mobile",
|
||||||
|
label: "Mobile",
|
||||||
|
total: 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "group-support",
|
||||||
|
label: "Support",
|
||||||
|
total: 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "group-web",
|
||||||
|
label: "Web",
|
||||||
|
total: 0
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Section>
|
||||||
|
<BooleanValue
|
||||||
|
defaultValue={true}
|
||||||
|
onChange={() => action("isOpen changed")}
|
||||||
|
>
|
||||||
|
{({ value: isOpen, toggle }) => (
|
||||||
|
<div style={{ position: "relative" }}>
|
||||||
|
<Button label="Toggle dropdown" onClick={toggle} />
|
||||||
|
<ArrayValue
|
||||||
|
defaultValue={options}
|
||||||
|
onChange={() => action("options onChange")}
|
||||||
|
>
|
||||||
|
{({ value, set }) => (
|
||||||
|
<AdvancedSelector
|
||||||
|
isDropDown={true}
|
||||||
|
isOpen={isOpen}
|
||||||
|
maxHeight={336}
|
||||||
|
width={379}
|
||||||
|
placeholder={text("placeholder", "Search")}
|
||||||
|
onSearchChanged={value => {
|
||||||
|
action("onSearchChanged")(value);
|
||||||
|
set(
|
||||||
|
options.filter(option => {
|
||||||
|
return option.label.indexOf(value) > -1;
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
options={value}
|
||||||
|
isMultiSelect={boolean("isMultiSelect", true)}
|
||||||
|
buttonLabel={text("buttonLabel", "Add departments")}
|
||||||
|
selectAllLabel={text("selectAllLabel", "Select all")}
|
||||||
|
onSelect={selectedOptions => {
|
||||||
|
action("onSelect")(selectedOptions);
|
||||||
|
toggle();
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</ArrayValue>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</BooleanValue>
|
||||||
|
</Section>
|
||||||
|
);
|
||||||
|
});
|
@ -2,13 +2,14 @@ import React from "react";
|
|||||||
import PropTypes from "prop-types";
|
import PropTypes from "prop-types";
|
||||||
import styled from 'styled-components';
|
import styled from 'styled-components';
|
||||||
import { Icons } from '../icons';
|
import { Icons } from '../icons';
|
||||||
|
import isEqual from 'lodash/isEqual';
|
||||||
|
|
||||||
const StyledOuter = styled.div`
|
const StyledOuter = styled.div`
|
||||||
width: ${props => props.size ? Math.abs(parseInt(props.size)) + "px" : "20px"};
|
width: ${props => props.size ? Math.abs(parseInt(props.size)) + "px" : "20px"};
|
||||||
cursor: ${props => props.isDisabled || !props.isClickable ? 'default' : 'pointer'};
|
cursor: ${props => props.isDisabled || !props.isClickable ? 'default' : 'pointer'};
|
||||||
line-height: 0;
|
line-height: 0;
|
||||||
`;
|
`;
|
||||||
class IconButton extends React.Component{
|
class IconButton extends React.Component {
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
|
|
||||||
@ -25,8 +26,8 @@ class IconButton extends React.Component{
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
onMouseEnter(e){
|
onMouseEnter(e) {
|
||||||
if(!this.props.isDisabled){
|
if (!this.props.isDisabled) {
|
||||||
this.setState({
|
this.setState({
|
||||||
currentIconName: this.props.iconHoverName ? this.props.iconHoverName : this.props.iconName,
|
currentIconName: this.props.iconHoverName ? this.props.iconHoverName : this.props.iconName,
|
||||||
currentIconColor: this.props.hoverColor ? this.props.hoverColor : this.props.color
|
currentIconColor: this.props.hoverColor ? this.props.hoverColor : this.props.color
|
||||||
@ -34,8 +35,8 @@ class IconButton extends React.Component{
|
|||||||
this.props.onMouseEnter && this.props.onMouseEnter(e);
|
this.props.onMouseEnter && this.props.onMouseEnter(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
onMouseLeave(e){
|
onMouseLeave(e) {
|
||||||
if(!this.props.isDisabled){
|
if (!this.props.isDisabled) {
|
||||||
this.setState({
|
this.setState({
|
||||||
currentIconName: this.props.iconName,
|
currentIconName: this.props.iconName,
|
||||||
currentIconColor: this.props.color
|
currentIconColor: this.props.color
|
||||||
@ -43,22 +44,22 @@ class IconButton extends React.Component{
|
|||||||
this.props.onMouseDown && this.props.onMouseDown(e);
|
this.props.onMouseDown && this.props.onMouseDown(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
onMouseDown(e){
|
onMouseDown(e) {
|
||||||
if(!this.props.isDisabled){
|
if (!this.props.isDisabled) {
|
||||||
this.setState({
|
this.setState({
|
||||||
currentIconName: this.props.iconClickName ? this.props.iconClickName : this.props.iconName,
|
currentIconName: this.props.iconClickName ? this.props.iconClickName : this.props.iconName,
|
||||||
currentIconColor: this.props.clickColor ? this.props.clickColor : this.props.color
|
currentIconColor: this.props.clickColor ? this.props.clickColor : this.props.color
|
||||||
});
|
});
|
||||||
this.props.onMouseDown && this.props.onMouseDown(e);
|
this.props.onMouseDown && this.props.onMouseDown(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
onMouseUp(e){
|
onMouseUp(e) {
|
||||||
if(!this.props.isDisabled){
|
if (!this.props.isDisabled) {
|
||||||
switch (e.nativeEvent.which) {
|
switch (e.nativeEvent.which) {
|
||||||
case 1: //Left click
|
case 1: //Left click
|
||||||
this.setState({
|
this.setState({
|
||||||
currentIconName: this.props.iconHoverName ? this.props.iconHoverName : this.props.iconName,
|
currentIconName: this.props.iconHoverName ? this.props.iconHoverName : this.props.iconName,
|
||||||
currentIconColor: this.props.iconHoverName ? this.props.iconHoverName : this.props.color
|
currentIconColor: this.props.iconHoverName ? this.props.iconHoverName : this.props.color
|
||||||
});
|
});
|
||||||
this.props.onClick && this.props.onClick(e);
|
this.props.onClick && this.props.onClick(e);
|
||||||
this.props.onMouseUp && this.props.onMouseUp(e);
|
this.props.onMouseUp && this.props.onMouseUp(e);
|
||||||
@ -69,35 +70,24 @@ class IconButton extends React.Component{
|
|||||||
|
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
shouldComponentUpdate(nextProps, nextState){
|
shouldComponentUpdate(nextProps, nextState) {
|
||||||
if(!this.isNeedUpdate){
|
|
||||||
for (let propsKey in this.props) {
|
if (!isEqual(this.props, nextProps)) {
|
||||||
if(typeof this.props[propsKey] != "function" && typeof this.props[propsKey] != "object" && this.props[propsKey] != nextProps[propsKey]){
|
let newState = {
|
||||||
this.isNeedUpdate = true;
|
currentIconName: this.state.currentIconName,
|
||||||
if(propsKey == "iconName"){
|
currentIconColor: this.state.currentIconColor
|
||||||
this.setState({
|
|
||||||
currentIconName: nextProps[propsKey]
|
|
||||||
});
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
for (let stateKey in this.state) {
|
if (this.props.iconName !== nextProps.iconName) newState.currentIconName = nextProps.iconName;
|
||||||
if(typeof this.state[stateKey] != "function" && typeof this.state[stateKey] != "object" && this.state[stateKey] != nextState[stateKey]){
|
if (this.props.color !== nextProps.color) newState.currentIconColor = nextProps.color;
|
||||||
this.isNeedUpdate = true;
|
this.setState(newState);
|
||||||
break;
|
return true;
|
||||||
}
|
|
||||||
}
|
|
||||||
if(!this.isNeedUpdate) return false;
|
|
||||||
else return true;
|
|
||||||
}
|
}
|
||||||
this.isNeedUpdate = false;
|
return !isEqual(this.state, nextState);
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
render(){
|
render() {
|
||||||
//console.log("IconButton render");
|
//console.log("IconButton render");
|
||||||
return (
|
return (
|
||||||
<StyledOuter
|
<StyledOuter
|
||||||
@ -111,7 +101,7 @@ class IconButton extends React.Component{
|
|||||||
|
|
||||||
isClickable={typeof this.props.onClick === 'function'}
|
isClickable={typeof this.props.onClick === 'function'}
|
||||||
>
|
>
|
||||||
{React.createElement(Icons[this.state.currentIconName], {size: "scale", color: this.state.currentIconColor, isfill: this.props.isFill})}
|
{React.createElement(Icons[this.state.currentIconName], { size: "scale", color: this.state.currentIconColor, isfill: this.props.isFill })}
|
||||||
</StyledOuter>
|
</StyledOuter>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -127,7 +117,7 @@ IconButton.propTypes = {
|
|||||||
iconName: PropTypes.string.isRequired,
|
iconName: PropTypes.string.isRequired,
|
||||||
iconHoverName: PropTypes.string,
|
iconHoverName: PropTypes.string,
|
||||||
iconClickName: PropTypes.string,
|
iconClickName: PropTypes.string,
|
||||||
onClick:PropTypes.func
|
onClick: PropTypes.func
|
||||||
};
|
};
|
||||||
|
|
||||||
IconButton.defaultProps = {
|
IconButton.defaultProps = {
|
||||||
|
@ -273,6 +273,7 @@ class PasswordInput extends React.PureComponent {
|
|||||||
} = this.state;
|
} = this.state;
|
||||||
|
|
||||||
const iconsColor = isDisabled ? '#D0D5DA' : '#A3A9AE';
|
const iconsColor = isDisabled ? '#D0D5DA' : '#A3A9AE';
|
||||||
|
const iconName = type === 'password' ? 'EyeIcon' : 'EyeOffIcon';
|
||||||
|
|
||||||
const tooltipContent = (
|
const tooltipContent = (
|
||||||
<StyledTooltipContainer forwardedAs='div' title={tooltipPasswordTitle}>
|
<StyledTooltipContainer forwardedAs='div' title={tooltipPasswordTitle}>
|
||||||
@ -305,7 +306,7 @@ class PasswordInput extends React.PureComponent {
|
|||||||
name={inputName}
|
name={inputName}
|
||||||
hasError={hasError}
|
hasError={hasError}
|
||||||
isDisabled={isDisabled}
|
isDisabled={isDisabled}
|
||||||
iconName='EyeIcon'
|
iconName={iconName}
|
||||||
value={inputValue}
|
value={inputValue}
|
||||||
onIconClick={this.changeInputType}
|
onIconClick={this.changeInputType}
|
||||||
onChange={this.onChangeAction}
|
onChange={this.onChangeAction}
|
||||||
|
@ -2,40 +2,98 @@ import React from 'react';
|
|||||||
import { mount } from 'enzyme';
|
import { mount } from 'enzyme';
|
||||||
import PasswordInput from '.';
|
import PasswordInput from '.';
|
||||||
|
|
||||||
|
const basePasswordSettings = {
|
||||||
|
minLength: 6,
|
||||||
|
upperCase: false,
|
||||||
|
digits: false,
|
||||||
|
specSymbols: false
|
||||||
|
};
|
||||||
|
|
||||||
|
const baseProps = {
|
||||||
|
inputName: 'demoPasswordInput',
|
||||||
|
emailInputName: 'demoEmailInput',
|
||||||
|
inputValue: '',
|
||||||
|
clipActionResource: 'Copy e-mail and password',
|
||||||
|
clipEmailResource: 'E-mail: ',
|
||||||
|
clipPasswordResource: 'Password: ',
|
||||||
|
tooltipPasswordTitle: 'Password must contain:',
|
||||||
|
tooltipPasswordLength: 'from 6 to 30 characters',
|
||||||
|
tooltipPasswordDigits: 'digits',
|
||||||
|
tooltipPasswordCapital: 'capital letters',
|
||||||
|
tooltipPasswordSpecial: 'special characters (!@#$%^&*)',
|
||||||
|
generatorSpecial: '!@#$%^&*',
|
||||||
|
passwordSettings: basePasswordSettings,
|
||||||
|
isDisabled: false,
|
||||||
|
placeholder: 'password',
|
||||||
|
onChange: () => jest.fn(),
|
||||||
|
onValidateInput: () => jest.fn(),
|
||||||
|
onCopyToClipboard: () => jest.fn()
|
||||||
|
}
|
||||||
|
|
||||||
describe('<PasswordInput />', () => {
|
describe('<PasswordInput />', () => {
|
||||||
it('renders without error', () => {
|
it('renders without error', () => {
|
||||||
const settings = {
|
const wrapper = mount(<PasswordInput {...baseProps} />);
|
||||||
minLength: 6,
|
|
||||||
upperCase: false,
|
|
||||||
digits: false,
|
|
||||||
specSymbols: false
|
|
||||||
};
|
|
||||||
|
|
||||||
const wrapper = mount(
|
|
||||||
<PasswordInput
|
|
||||||
inputName="demoPasswordInput"
|
|
||||||
emailInputName="demoEmailInput"
|
|
||||||
inputValue={""}
|
|
||||||
onChange={e => {
|
|
||||||
console.log(e.target.value);
|
|
||||||
}}
|
|
||||||
clipActionResource="Copy e-mail and password"
|
|
||||||
clipEmailResource="E-mail: "
|
|
||||||
clipPasswordResource="Password: "
|
|
||||||
tooltipPasswordTitle="Password must contain:"
|
|
||||||
tooltipPasswordLength="from 6 to 30 characters"
|
|
||||||
tooltipPasswordDigits="digits"
|
|
||||||
tooltipPasswordCapital="capital letters"
|
|
||||||
tooltipPasswordSpecial="special characters (!@#$%^&*)"
|
|
||||||
generatorSpecial="!@#$%^&*"
|
|
||||||
passwordSettings={settings}
|
|
||||||
isDisabled={false}
|
|
||||||
placeholder="password"
|
|
||||||
onValidateInput={a => console.log(a)}
|
|
||||||
onCopyToClipboard={b => console.log("Data " + b + " copied to clipboard")}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
|
|
||||||
expect(wrapper).toExist();
|
expect(wrapper).toExist();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('render password input', () => {
|
||||||
|
const wrapper = mount(<PasswordInput {...baseProps} />);
|
||||||
|
|
||||||
|
expect(wrapper.find('input').prop('type')).toEqual('password');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('have an HTML name', () => {
|
||||||
|
const wrapper = mount(<PasswordInput {...baseProps} />);
|
||||||
|
|
||||||
|
expect(wrapper.find('input').prop('name')).toEqual('demoPasswordInput');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('forward passed value', () => {
|
||||||
|
const wrapper = mount(<PasswordInput {...baseProps} inputValue='demo' />);
|
||||||
|
|
||||||
|
expect(wrapper.props().inputValue).toEqual('demo');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('call onChange when changing value', () => {
|
||||||
|
const onChange = jest.fn(event => {
|
||||||
|
expect(event.target.id).toEqual('demoPasswordInput');
|
||||||
|
expect(event.target.name).toEqual('demoPasswordInput');
|
||||||
|
expect(event.target.value).toEqual('demo');
|
||||||
|
});
|
||||||
|
|
||||||
|
const wrapper = mount(<PasswordInput {...baseProps} id="demoPasswordInput" name="demoPasswordInput" onChange={onChange} />);
|
||||||
|
|
||||||
|
const event = { target: { value: "demo" } };
|
||||||
|
|
||||||
|
wrapper.simulate('change', event);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('call onFocus when input is focused', () => {
|
||||||
|
const onFocus = jest.fn(event => {
|
||||||
|
expect(event.target.id).toEqual('demoPasswordInput');
|
||||||
|
expect(event.target.name).toEqual('demoPasswordInput');
|
||||||
|
});
|
||||||
|
|
||||||
|
const wrapper = mount(<PasswordInput {...baseProps} id="demoPasswordInput" name="demoPasswordInput" onFocus={onFocus} />);
|
||||||
|
|
||||||
|
wrapper.simulate('focus');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('call onBlur when input loses focus', () => {
|
||||||
|
const onBlur = jest.fn(event => {
|
||||||
|
expect(event.target.id).toEqual('demoPasswordInput');
|
||||||
|
expect(event.target.name).toEqual('demoPasswordInput');
|
||||||
|
});
|
||||||
|
|
||||||
|
const wrapper = mount(<PasswordInput {...baseProps} id="demoPasswordInput" name="demoPasswordInput" onBlur={onBlur} />);
|
||||||
|
|
||||||
|
wrapper.simulate('blur');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('disabled when isDisabled is passed', () => {
|
||||||
|
const wrapper = mount(<PasswordInput {...baseProps} isDisabled={true} />);
|
||||||
|
|
||||||
|
expect(wrapper.prop('isDisabled')).toEqual(true);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
Loading…
Reference in New Issue
Block a user