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