This commit is contained in:
Andrey Savihin 2019-07-16 10:12:38 +03:00
commit 2655a0fa52
17 changed files with 468 additions and 172 deletions

15
build/tools/check.sh Normal file
View File

@ -0,0 +1,15 @@
#!/bin/bash
CHANGES=$(/snap/libxml2/current/bin/xmllint --xpath '//changeSet/item/affectedPath/text()' $1);
shift
for i in $CHANGES
do
for j in $@
do
if [[ $i == $j* ]]; then
exit 1
fi
done
done
exit 0

View File

@ -0,0 +1,5 @@
server {
listen 8081;
root /var/www/story;
index index.html;
}

View File

@ -1,95 +1,93 @@
map $http_host $this_host {
"" $host;
default $http_host;
}
map $http_x_forwarded_proto $the_scheme {
default $http_x_forwarded_proto;
"" $scheme;
}
map $http_host $this_host {
"" $host;
default $http_host;
map $http_x_forwarded_host $the_host {
default $http_x_forwarded_host;
"" $this_host;
}
server {
listen 8092;
add_header Access-Control-Allow-Origin *;
large_client_header_buffers 4 16k;
set $X_REWRITER_URL $the_scheme://$the_host;
if ($http_x_rewriter_url != '') {
set $X_REWRITER_URL $http_x_rewriter_url ;
}
fastcgi_read_timeout 600;
fastcgi_send_timeout 600;
fastcgi_keep_conn on;
fastcgi_intercept_errors on;
map $http_x_forwarded_proto $the_scheme {
default $http_x_forwarded_proto;
"" $scheme;
}
include fastcgi_params;
map $http_x_forwarded_host $the_host {
default $http_x_forwarded_host;
"" $this_host;
}
server {
listen 8092;
add_header Access-Control-Allow-Origin *;
large_client_header_buffers 4 16k;
set $X_REWRITER_URL $the_scheme://$the_host;
if ($http_x_rewriter_url != '') {
set $X_REWRITER_URL $http_x_rewriter_url ;
}
fastcgi_param HTTP_X_REWRITER_URL $http_x_rewriter_url;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
fastcgi_param PATH_INFO "";
fastcgi_read_timeout 600;
fastcgi_send_timeout 600;
fastcgi_keep_conn on;
fastcgi_intercept_errors on;
location / {
proxy_pass http://localhost:5001;
location ~ /sockjs-node {
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $remote_addr;
proxy_set_header Host $host;
include fastcgi_params;
proxy_pass http://localhost:5001;
fastcgi_param HTTP_X_REWRITER_URL $http_x_rewriter_url;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
fastcgi_param PATH_INFO "";
location / {
proxy_pass http://localhost:5001;
location ~ /sockjs-node {
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $remote_addr;
proxy_set_header Host $host;
proxy_redirect off;
proxy_pass http://localhost:5001;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
}
location ~ /api/2.0 {
proxy_pass http://localhost:5000;
proxy_set_header X-REWRITER-URL $X_REWRITER_URL;
proxy_redirect off;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
}
location ~ /api/2.0 {
proxy_pass http://localhost:5000;
location ~ /people {
proxy_pass http://localhost:5004;
proxy_set_header X-REWRITER-URL $X_REWRITER_URL;
location ~ /people {
proxy_pass http://localhost:5004;
proxy_set_header X-REWRITER-URL $X_REWRITER_URL;
}
}
}
location /products {
location ~ /people {
#rewrite products/people/(.*) /$1 break;
proxy_pass http://localhost:5002;
proxy_set_header X-REWRITER-URL $X_REWRITER_URL;
location ~ /sockjs-node {
rewrite products/people/(.*) /$1 break;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $remote_addr;
proxy_set_header Host $host;
location /products {
location ~ /people {
#rewrite products/people/(.*) /$1 break;
proxy_pass http://localhost:5002;
proxy_set_header X-REWRITER-URL $X_REWRITER_URL;
location ~ /sockjs-node {
rewrite products/people/(.*) /$1 break;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $remote_addr;
proxy_set_header Host $host;
proxy_pass http://localhost:5002;
proxy_redirect off;
proxy_redirect off;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
}
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
}
}
}
}
}
}
include /etc/nginx/includes/onlyoffice-*.conf;

View File

@ -1,8 +1,9 @@
import React, { Suspense } from 'react';
import { BrowserRouter, Route, Switch } from 'react-router-dom';
import { BrowserRouter, Switch } from 'react-router-dom';
import { Loader, ErrorContainer } from 'asc-web-components';
import PeopleLayout from './components/Layout';
import Home from './components/pages/Home';
import { PrivateRoute } from './helpers/privateRoute';
var config = require('../package.json');
const App = () => {
@ -11,8 +12,8 @@ const App = () => {
<PeopleLayout>
<Suspense fallback={<Loader className="pageLoader" type="rombs" size={40} />}>
<Switch>
<Route exact path={['/', config.homepage]} component={Home} />
<Route component={() => (
<PrivateRoute exact path={config.homepage} component={Home} />
<PrivateRoute component={() => (
<ErrorContainer>
Sorry, the resource
cannot be found.

View File

@ -3,10 +3,10 @@ import { connect } from 'react-redux';
import PropTypes from 'prop-types';
import { withRouter } from "react-router";
import { Layout } from 'asc-web-components';
// import { logout } from '../../actions/authActions';
import { logout } from '../actions/authActions';
const PeopleLayout = props => {
const { auth, children, history } = props;
const { auth, logout, children } = props;
const currentUserActions = [
{
key: 'ProfileBtn', label: 'Profile', onClick: () => {
@ -20,8 +20,7 @@ const PeopleLayout = props => {
},
{
key: 'LogoutBtn', label: 'Log out', onClick: () => {
//logout();
history.push('/');
logout();
}
},
];
@ -39,16 +38,46 @@ const PeopleLayout = props => {
};
PeopleLayout.propTypes = {
auth: PropTypes.object.isRequired
auth: PropTypes.object.isRequired,
logout: PropTypes.func.isRequired
};
function convertModules(modules) {
const separator = { seporator: true, id: 'nav-seporator-1' };
const chat = {
id: '22222222-2222-2222-2222-222222222222',
title: 'Chat',
iconName: 'ChatIcon',
notifications: 3,
url: '/products/chat/',
onClick: () => window.open('/products/chat/', '_blank'),
onBadgeClick: e => console.log('ChatIconBadge Clicked')(e),
isolateMode: true
};
let items = modules.map(item => {
return {
id: '11111111-1111-1111-1111-111111111111',
title: item.title,
iconName: 'PeopleIcon',
notifications: 0,
url: item.link,
onClick: () => window.open(item.link, '_self'),
onBadgeClick: e => console.log('PeopleIconBadge Clicked')(e)
};
}) || [];
return items.length ? [separator, ...items, chat] : items;
}
function mapStateToProps(state) {
let availableModules = convertModules(state.auth.modules);
return {
auth: state.auth,
availableModules: state.auth.modules,
availableModules: availableModules,
currentUser: state.auth.user,
currentModuleId: state.auth.currentModuleId
};
}
export default connect(mapStateToProps)(withRouter(PeopleLayout));
export default connect(mapStateToProps, { logout })(withRouter(PeopleLayout));

View File

@ -1,4 +1,7 @@
import React, { useState } from "react";
import { connect } from 'react-redux';
import PropTypes from 'prop-types';
import { withRouter } from "react-router";
import _ from 'lodash';
import {
PageLayout,
@ -791,4 +794,17 @@ const Home = () => {
);
};
export default Home;
Home.propTypes = {
modules: PropTypes.array.isRequired,
history: PropTypes.object.isRequired,
isLoaded: PropTypes.bool
};
function mapStateToProps(state) {
return {
modules: state.auth.modules,
isLoaded: state.auth.isLoaded
};
}
export default connect(mapStateToProps)(withRouter(Home));

View File

@ -1,12 +1,25 @@
import React from 'react';
import ReactDOM from 'react-dom';
import { Provider } from 'react-redux';
import Cookies from 'universal-cookie';
import setAuthorizationToken from './utils/setAuthorizationToken';
import { AUTH_KEY } from './helpers/constants';
import store from './store/store';
import './custom.scss';
import App from './App';
import './i18n'
import * as serviceWorker from './serviceWorker';
import { getUserInfo } from './actions/authActions';
var token = (new Cookies()).get(AUTH_KEY);
if (token) {
setAuthorizationToken(token);
store.dispatch(getUserInfo);
}
else
throw new Error("Unauthorized");
ReactDOM.render(
<Provider store={store}>

View File

@ -2,90 +2,11 @@ import { SET_CURRENT_USER, SET_MODULES, SET_IS_LOADED, LOGOUT } from '../actions
import isEmpty from 'lodash/isEmpty';
const initialState = {
isAuthenticated: true,
isLoaded: true,
user: {
id: '00000000-0000-0000-0000-000000000000',
userName: 'Jane Doe',
email: 'janedoe@gmail.com',
isOwner: false,
isAdmin: false,
isVisitor: false,
avatarSmall: '',
avatarMedium: '',
},
currentModuleId: '11111111-1111-1111-1111-111111111111',
modules: [
{
seporator: true,
id: 'nav-seporator-1',
},
{
id: '11111111-1111-1111-1111-111111111111',
title: 'People',
iconName: 'PeopleIcon',
notifications: 0,
url: '/products/people/',
onClick: e => console.log('PeopleIcon Clicked', e),
onBadgeClick: e => console.log('PeopleIconBadge Clicked', e),
},
{
id: '22222222-2222-2222-2222-222222222222',
title: 'Documents',
iconName: 'DocumentsIcon',
notifications: 2,
url: '/products/documents/',
onClick: e => console.log('DocumentsIcon Clicked', e),
onBadgeClick: e => console.log('DocumentsIconBadge Clicked', e),
},
{
id: '33333333-3333-3333-3333-333333333333',
title: 'Chat',
iconName: 'ChatIcon',
notifications: 3,
url: '/products/chat/',
onClick: e => console.log('ChatIcon Clicked', e),
isolateMode: true,
},
{
id: '44444444-4444-4444-4444-444444444444',
title: 'Mail',
iconName: 'MailIcon',
notifications: 7,
url: '/products/mail/',
onClick: e => console.log('MailIcon Clicked', e),
onBadgeClick: e => console.log('MailIconBadge Clicked', e),
},
{
id: '55555555-5555-5555-5555-555555555555',
title: 'Projects',
iconName: 'ProjectsIcon',
notifications: 5,
onClick: e => console.log('ProjectsIcon Clicked', e),
onBadgeClick: e => console.log('ProjectsIconBadge Clicked', e),
},
{
id: '77777777-7777-7777-7777-777777777777',
title: 'CRM',
iconName: 'CrmIcon',
notifications: 0,
onClick: e => console.log('CrmIcon Clicked', e),
onBadgeClick: e => console.log('CrmIcon Clicked', e),
},
{
seporator: true,
id: 'nav-seporator-2',
},
{
id: '66666666-6666-6666-6666-666666666666',
title: 'Calendar',
iconName: 'CalendarCheckedIcon',
notifications: 0,
onClick: e => console.log('CalendarIcon Clicked', e),
onBadgeClick: e => console.log('CalendarIconBadge Clicked', e),
},
]
}
isAuthenticated: false,
isLoaded: false,
user: {},
modules: []
};
const auth = (state = initialState, action) => {
switch (action.type) {

View File

@ -20,6 +20,8 @@ export default function setAuthorizationToken(token) {
}
else {
delete axios.defaults.headers.common["Authorization"];
cookies.remove(AUTH_KEY);
cookies.remove(AUTH_KEY, {
path: '/'
});
}
}

View File

@ -16,6 +16,7 @@ const StudioLayout = props => {
{
key: 'AboutBtn', label: 'About', onClick: () => {
console.log('AboutBtn');
history.push('/about');
}
},
{
@ -63,7 +64,7 @@ function convertModules(modules) {
iconName: 'PeopleIcon',
notifications: 0,
url: item.link,
onClick: () => window.open(item.link, '_blank'),
onClick: () => window.open(item.link, '_self'),
onBadgeClick: e => console.log('DocumentsIconBadge Clicked')(e)
};
}) || [];

View File

@ -13,7 +13,7 @@ const Tiles = ({ modules, isPrimary, history }) => {
{
modules.filter(m => m.isPrimary === isPrimary).map(module => (
<Col key={++index}>
<ModuleTile {...module} onClick={() => window.open(module.link, "_blank")} />
<ModuleTile {...module} onClick={() => window.open(module.link, '_self')} />
</Col>
))
}

View File

@ -1,3 +1,4 @@
<svg width="33" height="26" viewBox="0 0 33 26" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M0.5 3C0.5 1.61929 1.61929 0.5 3 0.5H21.1954C21.9785 0.5 22.7163 0.866919 23.1889 1.49134L31.2783 12.1803C31.6858 12.7188 31.6832 13.4633 31.2719 13.999L23.1913 24.5226C22.7182 25.1388 21.9854 25.5 21.2084 25.5H3C1.61929 25.5 0.5 24.3807 0.5 23V3Z" fill="#F8F9F9" stroke="#ECEEF1"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M12 12L6 12V14L12 14V20H14L14 14H20V12H14V6L12 6L12 12Z" fill="#A3A9AE"/>
</svg>

Before

Width:  |  Height:  |  Size: 395 B

After

Width:  |  Height:  |  Size: 518 B

View File

@ -30,7 +30,7 @@ const StyledIconBlock = styled.div`
const StyledChildrenBlock = styled.div`
display: flex;
align-items: center;
padding-left: 2px;
padding: 2px 0px 2px 2px;
`;
const CustomInputGroup = ({ isIconFill, hasError, hasWarning, isDisabled, scale, ...props }) => (

View File

@ -0,0 +1,201 @@
import React from "react";
import PropTypes from "prop-types";
import styled from 'styled-components';
import InputBlock from '../input-block';
import IconButton from '../icon-button';
import ContextMenuButton from '../context-menu-button';
const StyledFilterItem = styled.div`
display: flex;
align-items: center;
height: 100%;
padding: 3px;
margin-right: 2px;
border: 1px solid #d4e4ec;
border-radius: 3px;
background-color: #edf6fd;
`;
const StyledIconButtonBlock = styled.div`
display: inline-block;
margin-left: 5px;
`;
const FilterItem = props => {
const { groupLabel, id, label } = props;
return (
<StyledFilterItem key={id}>
{groupLabel} {label}
<StyledIconButtonBlock>
<IconButton
color={props.color}
size={10}
iconName={"CrossIcon"}
isFill={true}
isDisabled={props.isDisabled}
onClick={!props.isDisabled ? ((e) => props.onClose(e, id)) : undefined}
/>
</StyledIconButtonBlock>
</StyledFilterItem>
);
};
class SearchInput extends React.Component {
constructor(props) {
super(props);
this.state = {
filterItems: []
};
this.onClickDropDownItem = this.onClickDropDownItem.bind(this);
this.getData = this.getData.bind(this);
this.onSearchClick = this.onSearchClick.bind(this);
this.onDeleteFilterItem = this.onDeleteFilterItem.bind(this);
this.getFilterItems = this.getFilterItems.bind(this);
}
onClickDropDownItem(event, filterItem){
let curentFilterItems = this.state.filterItems.slice();
let filterItems = this.getData()
let indexFilterItem = curentFilterItems.findIndex(x => x.key === filterItem.group);
if(indexFilterItem != -1){
curentFilterItems.splice(indexFilterItem, 1);
}
let selectFilterItem = {
key: filterItem.group,
value: filterItem.key,
label: filterItem.label,
groupLabel: filterItems.find(x => x.key === filterItem.group).label
};
curentFilterItems.push(selectFilterItem);
this.setState({ filterItems: curentFilterItems});
if(typeof this.props.onChangeFilter === "function")
this.props.onChangeFilter({
inputValue: this.props.value,
filterValue: this.props.isNeedFilter ? curentFilterItems : null
});
}
getData(){
let _this = this;
let d= this.props.getFilterData();
d.map(function(item){
item.onClick = !item.isSeparator && !item.isHeader && !item.disabled ? ((e) => _this.onClickDropDownItem(e, item)) : undefined;
return item;
});
return d;
}
onSearchClick(e, value){
let searchResult = {
inputValue: value,
filterValue: this.props.isNeedFilter ? this.state.filterItems : null
};
if(typeof this.props.onSearchClick === "function")
this.props.onSearchClick(searchResult);
}
onDeleteFilterItem(e , key){
let curentFilterItems = this.state.filterItems.slice();
let indexFilterItem = curentFilterItems.findIndex(x => x.key === key);
if(indexFilterItem != -1){
curentFilterItems.splice(indexFilterItem, 1);
}
this.setState({ filterItems: curentFilterItems});
if(typeof this.props.onChangeFilter === "function")
this.props.onChangeFilter({
inputValue: this.props.value,
filterValue: this.props.isNeedFilter ? curentFilterItems : null
});
}
getFilterItems(){
let _this = this;
const result = this.state.filterItems.map(function(item) {
return <FilterItem
isDisabled={_this.props.isDisabled}
key={item.key}
id={item.key}
groupLabel={item.groupLabel}
label={item.label}
onClose={_this.onDeleteFilterItem}>
</FilterItem>
})
return result;
}
render() {
let _this = this;
let iconSize = 32;
switch (this.props.size) {
case 'base':
iconSize = 32
break;
case 'middle':
case 'big':
case 'huge':
iconSize = 41
break;
default:
break;
}
return (
<InputBlock
id={this.props.id}
isDisabled={this.props.isDisabled}
iconName={"SearchIcon"}
isIconFill={true}
iconColor={"#A3A9AE"}
onIconClick={this.onSearchClick}
size={this.props.size}
scale={true}
value={this.props.value}
placeholder={this.props.placeholder}
onChange={this.props.onChange}
>
{ this.props.isNeedFilter && this.getFilterItems()}
{
this.props.isNeedFilter &&
<ContextMenuButton
title={'Actions'}
iconName={'RectangleFilterIcon'}
color='#A3A9AE'
size={iconSize}
getData={_this.getData}
/>
}
</InputBlock>
);
}
};
SearchInput.propTypes = {
id: PropTypes.string,
size: PropTypes.oneOf(['base', 'middle', 'big', 'huge']),
value:PropTypes.string,
scale: PropTypes.bool,
placeholder: PropTypes.string,
onChange: PropTypes.func,
getFilterData:PropTypes.func,
isNeedFilter: PropTypes.bool,
isDisabled: PropTypes.bool
};
SearchInput.defaultProps = {
size: 'base',
value: '',
scale: false,
isNeedFilter: false,
isDisabled: false
};
export default SearchInput;

View File

@ -25,4 +25,5 @@ export { default as Badge } from './components/badge'
export { default as ErrorContainer } from './components/error-container'
export { default as InputBlock } from './components/input-block'
export { default as IconButton } from './components/icon-button'
export { default as SearchInput } from './components/search-input'
export { default as Backdrop } from './components/backdrop'

View File

@ -0,0 +1,37 @@
# Input: SearchInput
## Usage
```js
import { SearchInput } from 'asc-web-components';
```
#### Description
SearchInput description
#### Usage
```js
<SearchInput
isNeedFilter={true}
getFilterData={() => [ { key: 'filter-example', group: 'filter-example', label: 'example group', isHeader: true },
{ key: 'filter-example-test', group: 'filter-example', label: 'Test' }]
}
onSearchClick={(result) => {console.log(result)}}
onChangeFilter={(result) => {console.log(result)}}
/>
```
| Props | Type | Required | Values | Default | Description |
| ---------------------- | -------- | :------: | ---------------------------- | ------- | ------------------------------------------------------------------------------------------------------ |
| `id` | `string` | - | - | - | Used as HTML `id` property |
| `value` | `string` | - | - | - | Value of the input |
| `onChange` | `func` | - | - | - | Called with the new value. Required when input is not read only. Parent should pass it back as `value` |
| `isDisabled` | `bool` | - | - | `false` | Indicates that the field cannot be used (e.g not authorised, or changes not saved) |
| `placeholder` | `string` | - | - | - | Placeholder text for the input |
| `size` | `string` | | `base`, `middle`, `big`, `huge`| `base` | Supported size of the input fields. |
| `scale` | `bool` | - | - | - | Indicates the input field has scale |
| `isNeedFilter` | `bool` | | | `false` | Determines if filter is needed |

View File

@ -0,0 +1,55 @@
import React from 'react';
import { storiesOf } from '@storybook/react';
import { action } from '@storybook/addon-actions';
import { StringValue } from 'react-values';
import { withKnobs, boolean, text, select, number } from '@storybook/addon-knobs/react';
import withReadme from 'storybook-readme/with-readme';
import Readme from './README.md';
import { SearchInput } from 'asc-web-components';
import Section from '../../../.storybook/decorators/section';
const sizeOptions = ['base', 'middle', 'big', 'huge'];
function getData() {
return [
{ key: 'filter-status', group: 'filter-status', label: 'Status', isHeader: true },
{ key: 'filter-status-active', group: 'filter-status', label: 'Active' },
{ key: 'filter-status-disabled', group: 'filter-status', label: 'Disabled' },
{ key: 'filter-type', group: 'filter-type', label: 'Type', isHeader: true },
{ key: 'filter-type-administrator', group: 'filter-type', label: 'Administrator' },
{ key: 'filter-type-employee', group: 'filter-type', label: 'Employee' },
];
}
storiesOf('Components|Input', module)
.addDecorator(withKnobs)
.addDecorator(withReadme(Readme))
.add('search', () => (
<Section>
<StringValue
onChange={e => {
action('onChange')(e);
}
}
>
{({ value, set }) => (
<Section>
<SearchInput
id={text('id', '')}
isDisabled={boolean('isDisabled', false)}
size={select('size', sizeOptions, 'base')}
scale={boolean('scale', false)}
isNeedFilter={boolean('isNeedFilter', true)}
getFilterData={getData}
placeholder={text('placeholder', 'Search')}
onSearchClick={(result) => {console.log(result)}}
onChangeFilter={(result) => {console.log(result)}}
value={value}
onChange={e => {
set(e.target.value);
}}
/>
</Section>
)}
</StringValue>
</Section>
));