mirror of
https://github.com/Unleash/unleash.git
synced 2025-05-26 01:17:00 +02:00
feat: add search for applications
This commit is contained in:
parent
d88435f0c5
commit
130110f5a4
@ -2,6 +2,7 @@ import React, { Component } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { ProgressBar, Card, CardText, Icon } from 'react-mdl';
|
||||
import { AppsLinkList, styles as commonStyles } from '../common';
|
||||
import SearchField from '../common/search-field';
|
||||
|
||||
const Empty = () => (
|
||||
<React.Fragment>
|
||||
@ -22,6 +23,8 @@ class ClientStrategies extends Component {
|
||||
static propTypes = {
|
||||
applications: PropTypes.array,
|
||||
fetchAll: PropTypes.func.isRequired,
|
||||
settings: PropTypes.object.isRequired,
|
||||
updateSetting: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
componentDidMount() {
|
||||
@ -35,9 +38,17 @@ class ClientStrategies extends Component {
|
||||
return <ProgressBar indeterminate />;
|
||||
}
|
||||
return (
|
||||
<Card shadow={0} className={commonStyles.fullwidth}>
|
||||
{applications.length > 0 ? <AppsLinkList apps={applications} /> : <Empty />}
|
||||
</Card>
|
||||
<div>
|
||||
<div className={commonStyles.toolbar}>
|
||||
<SearchField
|
||||
value={this.props.settings.filter}
|
||||
updateValue={this.props.updateSetting.bind(this, 'filter')}
|
||||
/>
|
||||
</div>
|
||||
<Card shadow={0} className={commonStyles.fullwidth}>
|
||||
{applications.length > 0 ? <AppsLinkList apps={applications} /> : <Empty />}
|
||||
</Card>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -1,9 +1,21 @@
|
||||
import { connect } from 'react-redux';
|
||||
import ApplicationList from './application-list-component';
|
||||
import { fetchAll } from './../../store/application/actions';
|
||||
import { updateSettingForGroup } from '../../store/settings/actions';
|
||||
|
||||
const mapStateToProps = state => ({ applications: state.applications.get('list').toJS() });
|
||||
const mapStateToProps = state => {
|
||||
const applications = state.applications.get('list').toJS();
|
||||
const settings = state.settings.toJS().application || {};
|
||||
|
||||
const Container = connect(mapStateToProps, { fetchAll })(ApplicationList);
|
||||
const regex = new RegExp(settings.filter, 'i');
|
||||
|
||||
return {
|
||||
applications: settings.filter ? applications.filter(a => regex.test(a.appName)) : applications,
|
||||
settings,
|
||||
};
|
||||
};
|
||||
const mapDispatchToProps = { fetchAll, updateSetting: updateSettingForGroup('application') };
|
||||
|
||||
const Container = connect(mapStateToProps, mapDispatchToProps)(ApplicationList);
|
||||
|
||||
export default Container;
|
||||
|
@ -85,4 +85,17 @@
|
||||
.toggleName {
|
||||
color: #37474f !important;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.toolbar {
|
||||
position: relative;
|
||||
padding: 0 24px 16px 24px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.toolbarButton {
|
||||
position: absolute;
|
||||
top: 56px;
|
||||
right: 24px;
|
||||
z-index: 2;
|
||||
}
|
@ -11,14 +11,14 @@ export const shorten = (str, len = 50) => (str && str.length > len ? `${str.subs
|
||||
export const AppsLinkList = ({ apps }) => (
|
||||
<List>
|
||||
{apps.length > 0 &&
|
||||
apps.map(({ appName, description = '-', icon }) => (
|
||||
apps.map(({ appName, description, icon }) => (
|
||||
<ListItem twoLine key={appName}>
|
||||
<span className="mdl-list__item-primary-content" style={{ minWidth: 0 }}>
|
||||
<Icon name={icon || 'apps'} className="mdl-list__item-avatar" />
|
||||
<Link to={`/applications/${appName}`} className={[styles.listLink, styles.truncate].join(' ')}>
|
||||
{appName}
|
||||
<span className={['mdl-list__item-sub-title', styles.truncate].join(' ')}>
|
||||
{description}
|
||||
{description || 'No descriptionn'}
|
||||
</span>
|
||||
</Link>
|
||||
</span>
|
||||
|
50
frontend/src/component/common/search-field.jsx
Normal file
50
frontend/src/component/common/search-field.jsx
Normal file
@ -0,0 +1,50 @@
|
||||
import React, { useState } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { debounce } from 'debounce';
|
||||
import { FABButton, Icon, Textfield } from 'react-mdl';
|
||||
|
||||
function SearchField({ value, updateValue }) {
|
||||
const [localValue, setLocalValue] = useState(value);
|
||||
const debounceUpdateValue = debounce(updateValue, 500);
|
||||
|
||||
const handleCange = e => {
|
||||
e.preventDefault();
|
||||
const v = e.target.value || '';
|
||||
setLocalValue(v);
|
||||
debounceUpdateValue(v);
|
||||
};
|
||||
|
||||
const handleKeyPress = e => {
|
||||
if (e.key === 'Enter') {
|
||||
updateValue(localValue);
|
||||
}
|
||||
};
|
||||
|
||||
const updateNow = () => {
|
||||
updateValue(localValue);
|
||||
};
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Textfield
|
||||
floatingLabel
|
||||
value={localValue}
|
||||
onChange={handleCange}
|
||||
onBlur={updateNow}
|
||||
onKeyPress={handleKeyPress}
|
||||
label="Search"
|
||||
style={{ width: '500px', maxWidth: '80%' }}
|
||||
/>
|
||||
<FABButton mini className={'mdl-cell--hide-phone'}>
|
||||
<Icon name="search" />
|
||||
</FABButton>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
SearchField.propTypes = {
|
||||
value: PropTypes.string.isRequired,
|
||||
updateValue: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
export default SearchField;
|
@ -5,16 +5,29 @@ exports[`renders correctly with one feature 1`] = `
|
||||
<div
|
||||
className="toolbar"
|
||||
>
|
||||
<react-mdl-Textfield
|
||||
floatingLabel={true}
|
||||
label="Search"
|
||||
onChange={[Function]}
|
||||
style={
|
||||
Object {
|
||||
"width": "100%",
|
||||
<div>
|
||||
<react-mdl-Textfield
|
||||
floatingLabel={true}
|
||||
label="Search"
|
||||
onBlur={[Function]}
|
||||
onChange={[Function]}
|
||||
onKeyPress={[Function]}
|
||||
style={
|
||||
Object {
|
||||
"maxWidth": "80%",
|
||||
"width": "500px",
|
||||
}
|
||||
}
|
||||
}
|
||||
/>
|
||||
/>
|
||||
<react-mdl-FABButton
|
||||
className="mdl-cell--hide-phone"
|
||||
mini={true}
|
||||
>
|
||||
<react-mdl-Icon
|
||||
name="search"
|
||||
/>
|
||||
</react-mdl-FABButton>
|
||||
</div>
|
||||
<a
|
||||
className="toolbarButton"
|
||||
href="/features/create"
|
||||
@ -190,16 +203,29 @@ exports[`renders correctly with one feature without permissions 1`] = `
|
||||
<div
|
||||
className="toolbar"
|
||||
>
|
||||
<react-mdl-Textfield
|
||||
floatingLabel={true}
|
||||
label="Search"
|
||||
onChange={[Function]}
|
||||
style={
|
||||
Object {
|
||||
"width": "100%",
|
||||
<div>
|
||||
<react-mdl-Textfield
|
||||
floatingLabel={true}
|
||||
label="Search"
|
||||
onBlur={[Function]}
|
||||
onChange={[Function]}
|
||||
onKeyPress={[Function]}
|
||||
style={
|
||||
Object {
|
||||
"maxWidth": "80%",
|
||||
"width": "500px",
|
||||
}
|
||||
}
|
||||
}
|
||||
/>
|
||||
/>
|
||||
<react-mdl-FABButton
|
||||
className="mdl-cell--hide-phone"
|
||||
mini={true}
|
||||
>
|
||||
<react-mdl-Icon
|
||||
name="search"
|
||||
/>
|
||||
</react-mdl-FABButton>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<react-mdl-Card
|
||||
|
@ -1,15 +1,3 @@
|
||||
.toolbar {
|
||||
position: relative;
|
||||
padding: 0 104px 16px 24px;
|
||||
}
|
||||
|
||||
.toolbarButton {
|
||||
position: absolute;
|
||||
top: 56px;
|
||||
right: 24px;
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
.listItemMetric {
|
||||
width: 40px;
|
||||
flex-shrink: 0;
|
||||
|
@ -2,9 +2,10 @@ import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { debounce } from 'debounce';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { Icon, FABButton, Textfield, Menu, MenuItem, Card, CardActions, List } from 'react-mdl';
|
||||
import { Icon, FABButton, Menu, MenuItem, Card, CardActions, List } from 'react-mdl';
|
||||
import Feature from './feature-list-item-component';
|
||||
import { MenuItemWithIcon, DropdownButton, styles as commonStyles } from '../common';
|
||||
import SearchField from '../common/search-field';
|
||||
import styles from './feature.scss';
|
||||
import { CREATE_FEATURE } from '../../permissions';
|
||||
|
||||
@ -59,18 +60,13 @@ export default class FeatureListComponent extends React.Component {
|
||||
});
|
||||
return (
|
||||
<div>
|
||||
<div className={styles.toolbar}>
|
||||
<Textfield
|
||||
floatingLabel
|
||||
value={this.state.filter}
|
||||
onChange={e => {
|
||||
this.setFilter(e.target.value);
|
||||
}}
|
||||
label="Search"
|
||||
style={{ width: '100%' }}
|
||||
<div className={commonStyles.toolbar}>
|
||||
<SearchField
|
||||
value={this.props.settings.filter}
|
||||
updateValue={this.props.updateSetting.bind(this, 'filter')}
|
||||
/>
|
||||
{hasPermission(CREATE_FEATURE) ? (
|
||||
<Link to="/features/create" className={styles.toolbarButton}>
|
||||
<Link to="/features/create" className={commonStyles.toolbarButton}>
|
||||
<FABButton accent title="Create feature toggle">
|
||||
<Icon name="add" />
|
||||
</FABButton>
|
||||
|
Loading…
Reference in New Issue
Block a user