Remove 10 more
This commit is contained in:
@@ -1,46 +0,0 @@
|
||||
/* eslint-disable react/prop-types */
|
||||
import React, { Component } from 'react';
|
||||
|
||||
import Input from './input.jsx';
|
||||
import Icon from '@components/icon/icon.jsx';
|
||||
import './inputs.scss';
|
||||
|
||||
export default class InputEnter extends Component {
|
||||
constructor() {
|
||||
super();
|
||||
}
|
||||
render() {
|
||||
var parentClassName = '';
|
||||
|
||||
if (this.props.big) {
|
||||
parentClassName += ' big ';
|
||||
}
|
||||
if (this.props.medium) {
|
||||
parentClassName += ' medium ';
|
||||
}
|
||||
if (this.props.small) {
|
||||
parentClassName += ' small ';
|
||||
}
|
||||
|
||||
if (
|
||||
parentClassName.indexOf('medium') === parentClassName.indexOf('small') &&
|
||||
parentClassName.indexOf('big') === parentClassName.indexOf('small') &&
|
||||
parentClassName.indexOf('big') < 0
|
||||
) {
|
||||
parentClassName += ' medium';
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={'input_icon input_enter ' + parentClassName}>
|
||||
<Input
|
||||
onEchap={this.props.onEchap}
|
||||
onEnter={this.props.onEnter}
|
||||
autoFocus={this.props.autoFocus}
|
||||
onKeyDown={this.props.onKeyDown}
|
||||
{...this.props}
|
||||
/>
|
||||
<Icon type={'enter'} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,47 +0,0 @@
|
||||
/* eslint-disable react/prop-types */
|
||||
import React, { Component } from 'react';
|
||||
|
||||
import Input from './input.jsx';
|
||||
import Icon from '@components/icon/icon.jsx';
|
||||
import './inputs.scss';
|
||||
|
||||
export default class InputIcon extends Component {
|
||||
constructor() {
|
||||
super();
|
||||
}
|
||||
render() {
|
||||
var parentClassName = '';
|
||||
|
||||
if (this.props.big) {
|
||||
parentClassName += ' big ';
|
||||
}
|
||||
if (this.props.medium) {
|
||||
parentClassName += ' medium ';
|
||||
}
|
||||
if (this.props.small) {
|
||||
parentClassName += ' small ';
|
||||
}
|
||||
|
||||
if (
|
||||
parentClassName.indexOf('medium') === parentClassName.indexOf('small') &&
|
||||
parentClassName.indexOf('big') === parentClassName.indexOf('small') &&
|
||||
parentClassName.indexOf('big') < 0
|
||||
) {
|
||||
parentClassName += ' medium';
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={'input_icon ' + parentClassName}>
|
||||
<Icon type={this.props.icon} />
|
||||
<Input
|
||||
onEchap={this.props.onEchap}
|
||||
onEnter={this.props.onEnter}
|
||||
autoFocus={this.props.autoFocus}
|
||||
onKeyDown={this.props.onKeyDown}
|
||||
ref={this.refInput}
|
||||
{...this.props}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,84 +0,0 @@
|
||||
import React from 'react';
|
||||
import { Switch } from 'antd';
|
||||
|
||||
import './inputs.scss';
|
||||
|
||||
export default (props: {
|
||||
big?: boolean;
|
||||
medium?: boolean;
|
||||
small?: boolean;
|
||||
loading?: boolean;
|
||||
className?: string;
|
||||
label?: string;
|
||||
checked?: boolean;
|
||||
disabled?: boolean;
|
||||
onChange: (value: boolean) => void;
|
||||
}) => {
|
||||
const renderSwitch = () => {
|
||||
let className = props.className || '';
|
||||
|
||||
if (props.big) {
|
||||
className += ' big ';
|
||||
}
|
||||
if (props.medium) {
|
||||
className += ' medium ';
|
||||
}
|
||||
if (props.small) {
|
||||
className += ' small ';
|
||||
}
|
||||
|
||||
if (
|
||||
className.indexOf('medium') === className.indexOf('small') &&
|
||||
className.indexOf('big') === className.indexOf('small') &&
|
||||
className.indexOf('big') < 0
|
||||
) {
|
||||
className += ' medium';
|
||||
}
|
||||
return (
|
||||
<Switch
|
||||
checked={props.checked}
|
||||
onChange={(value: boolean) => {
|
||||
props.onChange(value);
|
||||
}}
|
||||
className={className}
|
||||
loading={props.loading}
|
||||
disabled={props.disabled}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
let parentClassName = '';
|
||||
|
||||
if (props.big) {
|
||||
parentClassName += ' big ';
|
||||
}
|
||||
if (props.medium) {
|
||||
parentClassName += ' medium ';
|
||||
}
|
||||
if (props.small) {
|
||||
parentClassName += ' small ';
|
||||
}
|
||||
|
||||
if (
|
||||
parentClassName.indexOf('medium') === parentClassName.indexOf('small') &&
|
||||
parentClassName.indexOf('big') === parentClassName.indexOf('small') &&
|
||||
parentClassName.indexOf('big') < 0
|
||||
) {
|
||||
parentClassName += ' medium';
|
||||
}
|
||||
|
||||
if (props.label) {
|
||||
return (
|
||||
<div
|
||||
className={
|
||||
'switch_with_label ' + (props.disabled ? 'disabled' : '') + ' ' + parentClassName
|
||||
}
|
||||
>
|
||||
{renderSwitch()}
|
||||
<div className="label">{props.label}</div>
|
||||
</div>
|
||||
);
|
||||
} else {
|
||||
return renderSwitch();
|
||||
}
|
||||
};
|
||||
@@ -1,262 +0,0 @@
|
||||
/* eslint-disable react/jsx-key */
|
||||
import React, { Component } from 'react';
|
||||
import AutoComplete from 'components/auto-complete/auto-complete';
|
||||
import Icon from '@components/icon/icon.jsx';
|
||||
import Languages from '@features/global/services/languages-service';
|
||||
import './picker.scss';
|
||||
|
||||
export default class Picker extends React.Component {
|
||||
/*
|
||||
placeholder : string,
|
||||
search : Array of search function,
|
||||
renderItem(item) : how to render item
|
||||
renderItemChoosen(item) : how to add item in input
|
||||
renderItemSimply(item) : what user will type on input and what we have to replace
|
||||
onChangeSuggested(list)
|
||||
onChange(list) :
|
||||
canCreate
|
||||
onCreate
|
||||
disableNavigationKey :
|
||||
*/
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
currentSelected: props.value || [],
|
||||
inputValue: '',
|
||||
currentList: [],
|
||||
selected: [],
|
||||
};
|
||||
this.alreadyBackspace = false;
|
||||
}
|
||||
componentWillUnmount() {
|
||||
document.removeEventListener('click', this.outsideClickListener);
|
||||
}
|
||||
componentDidMount() {
|
||||
if (!this.props.inline) {
|
||||
this.focus();
|
||||
}
|
||||
|
||||
var element = this.container;
|
||||
this.outsideClickListener = event => {
|
||||
if (!element.contains(event.target) && document.contains(event.target)) {
|
||||
this.setState({ focused: false });
|
||||
}
|
||||
};
|
||||
this.outsideClickListener = this.outsideClickListener.bind(this);
|
||||
document.addEventListener('click', this.outsideClickListener);
|
||||
}
|
||||
UNSAFE_componentWillUpdate(nextProps, nextState) {
|
||||
if (nextProps.value != this.props.value) {
|
||||
nextState.currentSelected = nextProps.value;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
onSelect(item) {
|
||||
this.alreadyBackspace = true;
|
||||
if (item && (item.id === undefined || item.id < 0) && item.front_id === undefined) {
|
||||
this.onCreate(item.textSearched);
|
||||
return;
|
||||
}
|
||||
var newList = this.state.currentSelected;
|
||||
for (var i = 0; i < newList.length; i++) {
|
||||
if (newList[i].id == item.id) {
|
||||
this.setState({ inputValue: '' });
|
||||
return false;
|
||||
}
|
||||
}
|
||||
newList.push(item);
|
||||
this.setState({ currentSelected: newList });
|
||||
this.setState({ inputValue: '' });
|
||||
if (this.props.onChange) {
|
||||
this.props.onChange(newList);
|
||||
}
|
||||
this.focus();
|
||||
}
|
||||
onRemove(item) {
|
||||
var newList = this.state.currentSelected;
|
||||
var index = newList.indexOf(item);
|
||||
if (index >= 0) {
|
||||
newList.splice(index, 1);
|
||||
this.setState({ currentSelected: newList });
|
||||
if (this.props.onChange) {
|
||||
this.props.onChange(newList);
|
||||
}
|
||||
}
|
||||
this.focus();
|
||||
}
|
||||
onBackspace() {
|
||||
if (this.alreadyBackspace) {
|
||||
// if already typed on backspace once
|
||||
var newList = this.state.currentSelected;
|
||||
newList.splice(newList.length - 1, 1);
|
||||
this.setState({ currentSelected: newList });
|
||||
} else {
|
||||
this.alreadyBackspace = true;
|
||||
}
|
||||
}
|
||||
onCreate(text) {
|
||||
if (this.props.onCreate) {
|
||||
var item = this.props.onCreate(text, item => {
|
||||
this.onSelect(item);
|
||||
});
|
||||
}
|
||||
}
|
||||
focus() {
|
||||
if (this.autocomplete_node) {
|
||||
this.autocomplete_node.focus();
|
||||
}
|
||||
}
|
||||
render() {
|
||||
var that = this;
|
||||
var objects = [
|
||||
<div className="picker">
|
||||
<div className={'pickerInput ' + (this.props.readOnly ? 'readOnly ' : '')}>
|
||||
{this.state.currentSelected.map(item => {
|
||||
return this.props.renderItemChoosen(item);
|
||||
})}
|
||||
{!this.props.readOnly && (
|
||||
<AutoComplete
|
||||
search={[
|
||||
(text, cb) => {
|
||||
text = this.state.inputValue;
|
||||
this.props.search(text, list => {
|
||||
if (that.props.canCreate && text.length > 0) {
|
||||
list.push({ id: -1, autocomplete_id: list.length, textSearched: text });
|
||||
}
|
||||
cb(list);
|
||||
});
|
||||
},
|
||||
]}
|
||||
renderItemChoosen={[
|
||||
item => {
|
||||
if (item.id < 0 && this.props.canCreate) {
|
||||
return item.textSearched;
|
||||
} else {
|
||||
return this.props.renderItemSimply(item);
|
||||
}
|
||||
},
|
||||
]}
|
||||
max={[this.props.max || 10]}
|
||||
renderItem={[() => {}]}
|
||||
regexHooked={[/(.*)/]}
|
||||
placeholder={
|
||||
this.state.currentSelected.length
|
||||
? ''
|
||||
: this.props.placeholder ||
|
||||
Languages.t('scenes.apps.drive.left.search', [], 'Search')
|
||||
}
|
||||
onChange={evt => {
|
||||
this.alreadyBackspace = false;
|
||||
this.setState({ inputValue: evt.target.value });
|
||||
}}
|
||||
onSelect={item => {
|
||||
this.onSelect(item);
|
||||
}}
|
||||
value={this.state.inputValue}
|
||||
onChangeCurrentList={(list, selected) => {
|
||||
this.setState({ currentList: list, selected: selected });
|
||||
if (this.props.onChangeSuggested) {
|
||||
this.props.onChangeSuggested(list);
|
||||
}
|
||||
}}
|
||||
hideResult={true}
|
||||
onBackspace={() => {
|
||||
this.onBackspace();
|
||||
}}
|
||||
disableNavigationKey={this.props.disableNavigationKey}
|
||||
showResultsOnInit={true}
|
||||
onHide={() => this.setState({ focused: false })}
|
||||
ref={node => {
|
||||
this.autocomplete_node = node;
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</div>,
|
||||
];
|
||||
|
||||
if (!this.props.readOnly) {
|
||||
if (!this.props.inline) {
|
||||
objects.push(<div className="menu-text">{!!this.props.title && this.props.title}</div>);
|
||||
}
|
||||
|
||||
var results = [];
|
||||
|
||||
if (this.state.currentList.length > 0) {
|
||||
results = this.state.currentList.map((item, index) => {
|
||||
if (item.id < 0) {
|
||||
return (
|
||||
<div
|
||||
key={'create'}
|
||||
className={
|
||||
'menu ' +
|
||||
(!this.props.disableNavigationKey && item.autocomplete_id == this.state.selected
|
||||
? 'is_selected'
|
||||
: '')
|
||||
}
|
||||
onClick={evt => {
|
||||
evt.stopPropagation();
|
||||
evt.preventDefault();
|
||||
this.onSelect(item);
|
||||
}}
|
||||
>
|
||||
<div className="text">
|
||||
<Icon type="plus" />
|
||||
Create {this.props.renderItem(item.textSearched)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
} else {
|
||||
return (
|
||||
<div
|
||||
key={item.id}
|
||||
className={
|
||||
'menu ' +
|
||||
(!this.props.disableNavigationKey && item.autocomplete_id == this.state.selected
|
||||
? 'is_selected'
|
||||
: '')
|
||||
}
|
||||
onClick={evt => {
|
||||
evt.stopPropagation();
|
||||
evt.preventDefault();
|
||||
this.onSelect(item);
|
||||
}}
|
||||
>
|
||||
<div className="text">{this.props.renderItem(item)}</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
if (this.props.inline) {
|
||||
results = (
|
||||
<div className={'dropmenu inline ' + (this.state.focused ? 'fade_in ' : '')}>
|
||||
{results}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
objects = objects.concat(results);
|
||||
}
|
||||
|
||||
objects = (
|
||||
<div
|
||||
onClick={() => {
|
||||
this.focus();
|
||||
this.setState({ focused: true });
|
||||
}}
|
||||
ref={node => (this.container = node)}
|
||||
className={
|
||||
'picker_container ' +
|
||||
(this.props.readOnly ? 'readOnly ' : '') +
|
||||
(this.props.inline ? 'inline ' : '') +
|
||||
(this.state.focused ? 'focused ' : '') +
|
||||
this.props.className
|
||||
}
|
||||
>
|
||||
{objects}
|
||||
</div>
|
||||
);
|
||||
|
||||
return objects;
|
||||
}
|
||||
}
|
||||
@@ -1,55 +0,0 @@
|
||||
.picker_container {
|
||||
position: relative;
|
||||
border-radius: var(--border-radius-large);
|
||||
&.inline {
|
||||
transition: box-shadow 0.2s;
|
||||
|
||||
.dropmenu {
|
||||
opacity: 0;
|
||||
transition: opacity 0.2s;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
&.focused {
|
||||
&.readOnly {
|
||||
box-shadow: none !important;
|
||||
}
|
||||
|
||||
.autocomplete {
|
||||
box-shadow: none !important;
|
||||
}
|
||||
.dropmenu {
|
||||
opacity: 1;
|
||||
pointer-events: all;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.picker {
|
||||
.pickerInput {
|
||||
border-radius: var(--border-radius-base);
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
position: relative;
|
||||
background: #f2f2f2;
|
||||
|
||||
&.readOnly {
|
||||
margin: 0;
|
||||
display: inline;
|
||||
padding: 0;
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
.autocomplete {
|
||||
flex: 1;
|
||||
min-width: 50px;
|
||||
position: unset;
|
||||
input {
|
||||
background: inherit;
|
||||
margin-top: 0px;
|
||||
margin-bottom: 0px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,98 +0,0 @@
|
||||
rm .table {
|
||||
overflow: hidden;
|
||||
border-radius: var(--border-radius-base);
|
||||
margin-bottom: 16px;
|
||||
border: 1px solid var(--grey-background);
|
||||
font-weight: 500;
|
||||
|
||||
&.unfocused {
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
&.loading {
|
||||
opacity: 0.5;
|
||||
|
||||
.tr .item .line {
|
||||
pointer-events: none;
|
||||
border-radius: var(--border-radius-large);
|
||||
height: 16px;
|
||||
margin-top: 8px;
|
||||
width: 200px;
|
||||
animation-duration: 1s;
|
||||
animation-fill-mode: forwards;
|
||||
animation-iteration-count: infinite;
|
||||
animation-name: placeHolderShimmer;
|
||||
animation-timing-function: linear;
|
||||
background-repeat: repeat !important;
|
||||
background-image: linear-gradient(
|
||||
to right,
|
||||
var(--grey-light) 0px,
|
||||
#f2f2f2 120px,
|
||||
var(--grey-light) 234px
|
||||
) !important;
|
||||
|
||||
& > * {
|
||||
opacity: 0 !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.headerTable {
|
||||
background: var(--grey-background);
|
||||
font-weight: 700;
|
||||
font-size: 14px;
|
||||
display: flex;
|
||||
padding: 8px;
|
||||
border-bottom: 1px solid var(--grey-background);
|
||||
}
|
||||
|
||||
.footerTable {
|
||||
text-align: center;
|
||||
line-height: 32px;
|
||||
font-size: 14px;
|
||||
padding: 5px 10px;
|
||||
border-top: 1px solid var(--grey-background);
|
||||
border-bottom: 1px solid var(--grey-background);
|
||||
}
|
||||
.contentTable {
|
||||
.tr {
|
||||
display: flex;
|
||||
border-bottom: 1px solid var(--grey-background);
|
||||
padding: 8px;
|
||||
font-size: 12px;
|
||||
|
||||
&:hover {
|
||||
background: var(--grey-background);
|
||||
}
|
||||
|
||||
&:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
& > div {
|
||||
position: relative;
|
||||
min-height: 32px;
|
||||
|
||||
& > .absolute_position {
|
||||
width: 100%;
|
||||
position: absolute;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.fix_text_padding_medium {
|
||||
padding: 8px 0;
|
||||
display: inline-block;
|
||||
line-height: 18px;
|
||||
}
|
||||
|
||||
.text-complete-width {
|
||||
overflow: hidden;
|
||||
flex: 1;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,243 +0,0 @@
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
/* eslint-disable jsx-a11y/anchor-is-valid */
|
||||
import React, { Component } from 'react';
|
||||
import Languages from '@features/global/services/languages-service';
|
||||
import Button from 'components/buttons/button.jsx';
|
||||
import InputIcon from 'components/inputs/input-icon.jsx';
|
||||
import './table.scss';
|
||||
|
||||
type Props = {
|
||||
column?: any;
|
||||
noHeader: boolean;
|
||||
onAdd: () => void;
|
||||
onRequestMore: (refresh: boolean) => Promise<any[]>;
|
||||
onSearch: (query: string, maxResults: number, callback: (res: any[]) => void) => Promise<any[]>;
|
||||
updatedData: (data: any) => any;
|
||||
addText?: string;
|
||||
resultsPerPage?: number;
|
||||
unFocused: boolean;
|
||||
};
|
||||
type State = {
|
||||
data: null | any[];
|
||||
searchResults: any[];
|
||||
loading: boolean;
|
||||
has_more: boolean;
|
||||
page: number;
|
||||
searching: boolean;
|
||||
};
|
||||
|
||||
export default class Table extends Component<Props, State> {
|
||||
searchFieldValue = '';
|
||||
searchRunning = false;
|
||||
searchRunningTimeout: number | null = null;
|
||||
|
||||
constructor(props: Props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
data: null,
|
||||
searchResults: [],
|
||||
loading: true,
|
||||
has_more: true,
|
||||
page: 0,
|
||||
searching: false,
|
||||
};
|
||||
Languages.addListener(this);
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
Languages.removeListener(this);
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
if (this.state.data === null) {
|
||||
this.requestMore(true);
|
||||
}
|
||||
}
|
||||
|
||||
search() {
|
||||
if (this.searchFieldValue?.length <= 2) {
|
||||
return false;
|
||||
}
|
||||
if (this.searchRunning) {
|
||||
clearTimeout(this.searchRunningTimeout as number);
|
||||
this.searchRunningTimeout = window.setTimeout(() => {
|
||||
this.search();
|
||||
}, 1000);
|
||||
return;
|
||||
}
|
||||
|
||||
this.setState({
|
||||
loading: true,
|
||||
});
|
||||
|
||||
this.searchRunning = true;
|
||||
this.props.onSearch(this.searchFieldValue, 10, (data: any[]) => {
|
||||
this.searchRunning = false;
|
||||
this.setState({
|
||||
loading: false,
|
||||
searchResults: data,
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
renderItem(col: any, item: any) {
|
||||
if (col.render) {
|
||||
return col.render(item);
|
||||
} else {
|
||||
return <div className="">{col.dataIndex ? item[col.dataIndex] : ''}</div>;
|
||||
}
|
||||
}
|
||||
|
||||
requestMore(refresh = false) {
|
||||
this.setState({ loading: true });
|
||||
this.props.onRequestMore(refresh).then(res => {
|
||||
const hasMore = res.length !== (this.state.data || []).length;
|
||||
this.setState({ loading: false, data: res, has_more: hasMore });
|
||||
});
|
||||
}
|
||||
|
||||
setPosition() {
|
||||
if (!!this.props.onSearch && !this.props.onAdd) return 'flex-end';
|
||||
else return 'space-between';
|
||||
}
|
||||
|
||||
nextPage() {
|
||||
this.setState({ page: this.state.page + 1 });
|
||||
if (this.getPageData().length < (this.state.page + 1) * this.getResultsPerPage()) {
|
||||
this.requestMore();
|
||||
}
|
||||
}
|
||||
|
||||
getPageData() {
|
||||
const from = this.state.page * this.getResultsPerPage();
|
||||
return (this.state.data || []).slice(0, from + this.getResultsPerPage());
|
||||
}
|
||||
|
||||
getResultsPerPage(): number {
|
||||
return this.props.resultsPerPage || 50;
|
||||
}
|
||||
|
||||
render() {
|
||||
let page_data = this.state.searching ? this.state.searchResults : this.getPageData();
|
||||
page_data = page_data
|
||||
.map(data => {
|
||||
data = this.props.updatedData ? this.props.updatedData(data) : data;
|
||||
if (!data) {
|
||||
return false;
|
||||
}
|
||||
return data;
|
||||
})
|
||||
.filter(i => i);
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div
|
||||
className="small-y-margin full-width"
|
||||
style={{
|
||||
display: 'flex',
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
justifyContent: this.setPosition(),
|
||||
}}
|
||||
>
|
||||
{this.props.onAdd && (
|
||||
<Button
|
||||
className="medium"
|
||||
value={this.props.addText || 'Add'}
|
||||
onClick={this.props.onAdd}
|
||||
/>
|
||||
)}
|
||||
{!!this.props.onSearch && (
|
||||
<div>
|
||||
<InputIcon
|
||||
icon="search"
|
||||
small
|
||||
placeholder={Languages.t('components.listmanager.filter', [], 'Search')}
|
||||
onChange={(event: any) => {
|
||||
const q = event.target.value;
|
||||
this.searchFieldValue = q;
|
||||
this.setState({ searching: q.length > 0 });
|
||||
this.search();
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div
|
||||
className={
|
||||
'table ' +
|
||||
(this.props.unFocused ? 'unfocused ' : '') +
|
||||
(this.state.loading ? 'loading ' : '')
|
||||
}
|
||||
>
|
||||
{!this.props.noHeader && (
|
||||
<div className="headerTable">
|
||||
{this.props.column.map((col: any, i: number) => {
|
||||
return (
|
||||
<div
|
||||
key={i}
|
||||
className="headerItem"
|
||||
style={{
|
||||
width: col.width || 'inherit',
|
||||
flex: col.width ? 'inherit' : '1',
|
||||
textAlign: (col.titleStyle || {}).textAlign,
|
||||
}}
|
||||
>
|
||||
{col.title}
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
)}
|
||||
<div className="contentTable">
|
||||
{(page_data || []).length === 0 && !this.state.loading && (
|
||||
<div className="tr" style={{ justifyContent: 'center', display: 'flex' }}>
|
||||
<div className="item" style={{ lineHeight: '32px' }}>
|
||||
{Languages.t('components.user_picker.modal_no_result')}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{(page_data || []).length === 0 &&
|
||||
!!this.state.loading &&
|
||||
Array(this.state.searching ? 1 : this.getResultsPerPage()).map((_, i: number) => {
|
||||
return (
|
||||
<div key={i} className="tr">
|
||||
<div className="item">
|
||||
<div className="line"></div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
{(page_data || []).map((data: any, i: number) => {
|
||||
return (
|
||||
<div key={i} className="tr">
|
||||
{this.props.column.map((col: any, j: number) => {
|
||||
return (
|
||||
<div
|
||||
key={j}
|
||||
className="item"
|
||||
style={{ width: col.width || 'inherit', flex: col.width ? 'inherit' : '1' }}
|
||||
>
|
||||
{this.renderItem(col, data)}
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
{!this.state.searching &&
|
||||
!!this.state.has_more &&
|
||||
page_data.length >= this.getResultsPerPage() && (
|
||||
<div className="footerTable">
|
||||
<a href="#" onClick={() => this.nextPage()}>
|
||||
{Languages.t('components.searchpopup.load_more', [], 'Load more results')}
|
||||
</a>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
-98
@@ -1,98 +0,0 @@
|
||||
import SecuredConnection from './SecuredConnection.js';
|
||||
/** MultipleSecuredConnections
|
||||
* Manage multiple secured connections as one (multiple publish and centralized event manager)
|
||||
*/
|
||||
|
||||
export default class MultipleSecuredConnections {
|
||||
constructor(callback, options) {
|
||||
if (!options) {
|
||||
options = {};
|
||||
}
|
||||
|
||||
//Options :
|
||||
this.disableDuplicates = options.disableDuplicates !== false;
|
||||
|
||||
this.secured_connections = {};
|
||||
this.callback = callback;
|
||||
|
||||
this.removing_secured_connections_timeouts = {};
|
||||
}
|
||||
|
||||
publish(data) {
|
||||
Object.keys(this.secured_connections).forEach(secured_connection_key => {
|
||||
var secured_connection = this.secured_connections[secured_connection_key];
|
||||
secured_connection.publish(data, () => {});
|
||||
});
|
||||
}
|
||||
|
||||
event(route, event, data) {
|
||||
//Disable duplicates
|
||||
var string = JSON.stringify(data);
|
||||
if (
|
||||
string === this.last_event &&
|
||||
this.disableDuplicates &&
|
||||
!(event === 'close' || event === 'open' || event === 'init')
|
||||
) {
|
||||
return;
|
||||
}
|
||||
this.last_event = string;
|
||||
|
||||
if (!data) {
|
||||
data = {};
|
||||
}
|
||||
data._route = route;
|
||||
if (this.callback) this.callback(event, data);
|
||||
}
|
||||
|
||||
addConnection(route, option, http_options, key) {
|
||||
if (this.secured_connections[route]) {
|
||||
this.secured_connections[route].removing = false;
|
||||
if (this.removing_secured_connections_timeouts[route]) {
|
||||
clearTimeout(this.removing_secured_connections_timeouts[route]);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.secured_connections[route]) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.secured_connections[route] = new SecuredConnection(
|
||||
route,
|
||||
option,
|
||||
(event, data) => {
|
||||
this.event(route, event, data);
|
||||
},
|
||||
http_options,
|
||||
key,
|
||||
);
|
||||
}
|
||||
|
||||
removeConnection(route) {
|
||||
if (!this.secured_connections[route] || this.secured_connections[route].removing) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.removing_secured_connections_timeouts[route]) {
|
||||
clearTimeout(this.removing_secured_connections_timeouts[route]);
|
||||
}
|
||||
|
||||
this.secured_connections[route].removing = true;
|
||||
this.removing_secured_connections_timeouts[route] = setTimeout(() => {
|
||||
if (this.secured_connections[route]) {
|
||||
this.secured_connections[route].close();
|
||||
delete this.secured_connections[route];
|
||||
}
|
||||
}, 10000);
|
||||
}
|
||||
|
||||
getRoutes() {
|
||||
return Object.keys(this.secured_connections);
|
||||
}
|
||||
|
||||
closeAll() {
|
||||
Object.keys(this.secured_connections).foEach(route => {
|
||||
this.removeConnection(route);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -1,265 +0,0 @@
|
||||
import Api from '@features/global/framework/api-service';
|
||||
import ws from '@deprecated/websocket/websocket.js';
|
||||
import CryptoJS from 'crypto-js';
|
||||
import sha256 from 'crypto-js/sha256';
|
||||
/** SecuredConnection
|
||||
* Create websockets encrypted connection
|
||||
*/
|
||||
import Globals from '@features/global/services/globals-tdrive-app-service';
|
||||
|
||||
export default class SecuredConnection {
|
||||
constructor(route, options, callback, http_options, collectionId) {
|
||||
this.ready = false;
|
||||
this.collectionId = collectionId;
|
||||
|
||||
this.route = route;
|
||||
this.options = options;
|
||||
|
||||
this.callback = callback;
|
||||
|
||||
//Room idea
|
||||
this.websocket_id = '';
|
||||
|
||||
//Store keys
|
||||
this.keys = [];
|
||||
this.keys_by_version = {};
|
||||
this.publish_buffer = [];
|
||||
this.http_options = http_options;
|
||||
|
||||
this.init(http_options);
|
||||
|
||||
Globals.window.CryptoAES = CryptoJS.AES;
|
||||
}
|
||||
|
||||
init(http_options) {
|
||||
this.ready = false;
|
||||
|
||||
this.options.get_options = http_options;
|
||||
|
||||
var data = {
|
||||
collection_id: this.route,
|
||||
options: this.options,
|
||||
_grouped: true,
|
||||
};
|
||||
Api.GroupedQueryApiPost('/ajax/core/collections/init', data, res => {
|
||||
var did_get = false;
|
||||
|
||||
if (res.data) {
|
||||
var data = res.data;
|
||||
this.ready = true;
|
||||
this.websocket_id = data.room_id;
|
||||
this.receiveNewKey(data.key, data.key_version, false);
|
||||
this.open();
|
||||
|
||||
if (data.get) {
|
||||
this.callback('get', { collectionId: this.collectionId, data: data.get });
|
||||
did_get = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!did_get) {
|
||||
this.callback('init', {});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
open() {
|
||||
var websocket_id = this.websocket_id;
|
||||
ws.subscribe(
|
||||
'collections/' + websocket_id,
|
||||
(uri, obj) => {
|
||||
this.receiveEvent(obj);
|
||||
},
|
||||
websocket_id,
|
||||
);
|
||||
ws.onReconnect(websocket_id, () => {
|
||||
this.close();
|
||||
this.init();
|
||||
});
|
||||
|
||||
this.callback('open', {});
|
||||
this.publish();
|
||||
}
|
||||
|
||||
close() {
|
||||
var websocket_id = this.websocket_id;
|
||||
ws.offReconnect(websocket_id);
|
||||
ws.unsubscribe(
|
||||
'collections/' + websocket_id,
|
||||
(uri, obj) => {
|
||||
this.receiveEvent(obj);
|
||||
},
|
||||
websocket_id,
|
||||
);
|
||||
|
||||
this.callback('close', {});
|
||||
}
|
||||
|
||||
publish(data, callback) {
|
||||
if (!data) {
|
||||
//Read buffer
|
||||
|
||||
var publish_buffer = JSON.parse(JSON.stringify(this.publish_buffer));
|
||||
this.publish_buffer = [];
|
||||
|
||||
publish_buffer.forEach(item => {
|
||||
this.publish(item, callback);
|
||||
});
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this.ready) {
|
||||
this.publish_buffer.push(data);
|
||||
return;
|
||||
}
|
||||
|
||||
var websocket_id = this.websocket_id;
|
||||
var encrypted = this.encrypt(data);
|
||||
ws.publish('collections/' + websocket_id, encrypted);
|
||||
|
||||
if (callback) {
|
||||
callback();
|
||||
}
|
||||
}
|
||||
|
||||
receiveEvent(event) {
|
||||
if (event.encrypted) {
|
||||
event = this.decrypt(event);
|
||||
}
|
||||
|
||||
if (event.new_key) {
|
||||
this.receiveNewKey(event.new_key, event.key_version, true);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!event) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.callback('event', event);
|
||||
}
|
||||
|
||||
//Receive new key as addon for previous key or as replacing key (sent by php server)
|
||||
receiveNewKey(key, version, asAddon) {
|
||||
if (asAddon) {
|
||||
var lastKey = this.keys[this.keys.length - 1];
|
||||
key = sha256(lastKey.key + key).toString(); //Mix keys
|
||||
}
|
||||
|
||||
this.keys.push({
|
||||
key: key,
|
||||
version: version,
|
||||
});
|
||||
this.keys_by_version[version] = key;
|
||||
|
||||
this.prepareEncryptKey();
|
||||
}
|
||||
|
||||
prepareEncryptKey() {
|
||||
var lastKey = this.keys[this.keys.length - 1];
|
||||
|
||||
try {
|
||||
if (this.prepared_key_original !== lastKey) {
|
||||
this.prepared_key_salt = CryptoJS.lib.WordArray.random(256);
|
||||
this.prepared_key_original = lastKey;
|
||||
this.prepared_key = CryptoJS.PBKDF2(lastKey.key, this.prepared_key_salt, {
|
||||
hasher: CryptoJS.algo.SHA512,
|
||||
keySize: 64 / 8,
|
||||
iterations: 9,
|
||||
});
|
||||
}
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
}
|
||||
|
||||
return this.prepared_key;
|
||||
}
|
||||
|
||||
encrypt(data) {
|
||||
var lastKey = this.keys[this.keys.length - 1];
|
||||
var prepared_key = this.prepareEncryptKey();
|
||||
if (!prepared_key) {
|
||||
prepared_key = '';
|
||||
}
|
||||
var iv = CryptoJS.lib.WordArray.random(16);
|
||||
|
||||
var encrypted = CryptoJS.AES.encrypt(JSON.stringify(data), prepared_key, { iv: iv });
|
||||
|
||||
// eslint-disable-next-line no-redeclare
|
||||
var data = {
|
||||
encrypted: CryptoJS.enc.Base64.stringify(encrypted.ciphertext),
|
||||
iv: CryptoJS.enc.Hex.stringify(iv),
|
||||
salt: CryptoJS.enc.Hex.stringify(this.prepared_key_salt),
|
||||
key_version: lastKey.version,
|
||||
};
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
decrypt(data) {
|
||||
if (!data.key_version) {
|
||||
return false;
|
||||
}
|
||||
let res = '';
|
||||
let str = '';
|
||||
try {
|
||||
if (data.public) {
|
||||
return typeof data.public === 'string' ? JSON.parse(data.public) : data.public;
|
||||
}
|
||||
|
||||
var encrypted_data = data.encrypted;
|
||||
|
||||
var key = this.keys_by_version[data.key_version];
|
||||
|
||||
if (!key) {
|
||||
if (
|
||||
this.getKeyTimestamp(key) > this.getKeyTimestamp(this.keys[this.keys.length - 1].version)
|
||||
) {
|
||||
//We have a old key, we have to update and request the message again
|
||||
this.init(this.http_options);
|
||||
//TODO reask lost message after init
|
||||
} else {
|
||||
//We have only newer keys, ask everybody to update
|
||||
//TODO ws.publish('collections/' + websocket_id, encrypted);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
var salt = CryptoJS.enc.Hex.parse(data.salt);
|
||||
var prepared_key = CryptoJS.PBKDF2(key, salt, {
|
||||
hasher: CryptoJS.algo.SHA512,
|
||||
keySize: 64 / 8,
|
||||
iterations: 9,
|
||||
});
|
||||
|
||||
var iv = CryptoJS.enc.Hex.parse(data.iv);
|
||||
var bytes = CryptoJS.AES.decrypt(encrypted_data, prepared_key, { iv: iv });
|
||||
str = bytes.toString(CryptoJS.enc.Utf8);
|
||||
|
||||
if (str) {
|
||||
res = JSON.parse(str);
|
||||
} else {
|
||||
res = '';
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('Unable to read encrypted websocket event', str, err.message);
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
getKeyTimestamp(key) {
|
||||
try {
|
||||
if (typeof key !== 'string') {
|
||||
if (key !== undefined) {
|
||||
console.log('wrong websocket key format: ', key);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
return parseInt((key || '').split('-')[1]);
|
||||
} catch (e) {
|
||||
console.log(key, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,184 +0,0 @@
|
||||
import Number from '@features/global/utils/Numbers';
|
||||
import Observable from '@deprecated/CollectionsV1/observable.js';
|
||||
import LoginService from '@features/auth/login-service';
|
||||
import Logger from '@features/global/framework/logger-service';
|
||||
import Globals from '@features/global/services/globals-tdrive-app-service';
|
||||
import WebSocket from '../../features/global/types/websocket-types';
|
||||
|
||||
/**
|
||||
* @deprecated Keeps old PHP websocket working doing the bridge with the new implementation
|
||||
*/
|
||||
class DeprecatedWebsocket extends Observable {
|
||||
constructor() {
|
||||
super();
|
||||
this.logger = Logger.getLogger('Websocket');
|
||||
this.setObservableName('websockets');
|
||||
|
||||
this.ws = null;
|
||||
this.connection = null;
|
||||
this.afterError = false;
|
||||
this.lastData = 1;
|
||||
this.testNetwork = false;
|
||||
|
||||
this.connected = true;
|
||||
this.subscribed = {};
|
||||
this.subscribedKey = {};
|
||||
this.disconnectListeners = {};
|
||||
|
||||
this.alive_connected = true;
|
||||
|
||||
this.firstTime = true;
|
||||
this.last_reconnect_call = new Date();
|
||||
this.last_reconnect_call_if_needed = new Date();
|
||||
|
||||
window.websocketsManager = this;
|
||||
|
||||
this.window_focus = true;
|
||||
this.window_last_blur = new Date();
|
||||
this.deconnectionBlurTimeout = setTimeout(() => {}, 0);
|
||||
Globals.window.addEventListener('focus', () => {
|
||||
this.reconnectIfNeeded(60);
|
||||
clearTimeout(this.deconnectionBlurTimeout);
|
||||
this.didFocusedLastMinute = true;
|
||||
this.window_focus = true;
|
||||
this.window_last_blur = new Date();
|
||||
if (this.ws == null || new Date().getTime() - this.window_last_blur.getTime() > 30000) {
|
||||
this.alive_connected = false;
|
||||
this.alive();
|
||||
}
|
||||
});
|
||||
Globals.window.addEventListener('blur', () => {
|
||||
this.window_focus = false;
|
||||
this.window_last_blur = new Date();
|
||||
});
|
||||
|
||||
this.alive.bind(this);
|
||||
this.message.bind(this);
|
||||
this.updateConnected.bind(this);
|
||||
}
|
||||
|
||||
//Send I'm alive !
|
||||
alive() {
|
||||
var nw = new Date().getTime();
|
||||
if (!this.lastAlive || nw - this.lastAlive > 60000) {
|
||||
// Wait at least 60 seconds
|
||||
this.lastAlive = nw;
|
||||
this.alive_timeout = setTimeout(() => {
|
||||
this.alive_connected = false;
|
||||
}, 5100);
|
||||
this.reconnectIfNeeded();
|
||||
clearTimeout(this.alive_timeout);
|
||||
this.alive_connected = true;
|
||||
this.didFocusedLastMinute = Globals.isReactNative || Globals.window.document.hasFocus();
|
||||
}
|
||||
}
|
||||
|
||||
//Receive server message
|
||||
message(unid, route, data) {
|
||||
route = (route || '').split('previous::').pop();
|
||||
if (unid !== this.subscribedKey[route]) {
|
||||
return;
|
||||
}
|
||||
if (this.subscribed[route]) {
|
||||
this.subscribed[route].forEach(c => {
|
||||
try {
|
||||
c.callback(route, data);
|
||||
} catch (err) {
|
||||
console.log(err);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
this.unsubscribe(route);
|
||||
}
|
||||
}
|
||||
|
||||
//Subscribe to channel
|
||||
subscribe(route, callback, key) {
|
||||
this.logger.debug(`Subscribe to ${route}`);
|
||||
route = (route || '').split('previous::').pop();
|
||||
if (!key) {
|
||||
key = callback.name;
|
||||
}
|
||||
if (!this.subscribed[route]) {
|
||||
this.subscribed[route] = [];
|
||||
}
|
||||
this.subscribed[route].push({ name: key, callback: callback });
|
||||
if (this.subscribed[route].length === 1) {
|
||||
const unid = Number.unid();
|
||||
this.subscribedKey[route] = unid;
|
||||
WebSocket.get().join('previous::' + route, '', '', (type, data) => {
|
||||
if (type === 'realtime:event') {
|
||||
this.message(unid, data.name, data.data);
|
||||
}
|
||||
if (type === 'connected') {
|
||||
this.updateConnected(true);
|
||||
}
|
||||
if (type === 'disconnected') {
|
||||
this.updateConnected(false);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
//Unsubscribe from channel
|
||||
unsubscribe(route, callback, key) {
|
||||
this.logger.debug(`Unsubscribe from ${route}`);
|
||||
route = (route || '').split('previous::').pop();
|
||||
if (!key && callback) {
|
||||
key = callback.name;
|
||||
}
|
||||
if (!key) {
|
||||
key = 'default';
|
||||
}
|
||||
if (!this.subscribed[route]) {
|
||||
this.subscribed[route] = [];
|
||||
}
|
||||
var remaining = [];
|
||||
this.subscribed[route].forEach(c => {
|
||||
if (c.name !== key) {
|
||||
remaining.push(c);
|
||||
}
|
||||
});
|
||||
this.subscribed[route] = remaining;
|
||||
if (this.subscribed[route].length === 0) {
|
||||
WebSocket.get().leave('previous::' + route, '');
|
||||
}
|
||||
}
|
||||
|
||||
publish(route, value) {
|
||||
this.logger.debug(`Publish to ${route}`);
|
||||
WebSocket.get().send('previous::' + route, value);
|
||||
}
|
||||
|
||||
reconnectIfNeeded(seconds = 30) {
|
||||
if (new Date().getTime() - this.last_reconnect_call_if_needed.getTime() > seconds * 1000) {
|
||||
//30 seconds
|
||||
if (LoginService.currentUserId) {
|
||||
LoginService.updateUser();
|
||||
}
|
||||
|
||||
this.last_reconnect_call_if_needed = new Date();
|
||||
}
|
||||
}
|
||||
|
||||
onReconnect(id, callback) {
|
||||
this.disconnectListeners[id] = callback;
|
||||
}
|
||||
|
||||
offReconnect(id) {
|
||||
delete this.disconnectListeners[id];
|
||||
}
|
||||
|
||||
updateConnected(state) {
|
||||
if (state !== this.connected) {
|
||||
this.connected = state;
|
||||
this.notify();
|
||||
}
|
||||
}
|
||||
|
||||
isConnected() {
|
||||
return this.connected;
|
||||
}
|
||||
}
|
||||
|
||||
export default new DeprecatedWebsocket();
|
||||
Reference in New Issue
Block a user