Merge branch 'addtag_filterablesearch' into 'master'
Filterbutton on addtag pupup See merge request lukas/openmediacenter!38
This commit is contained in:
commit
162b4efd0e
64
src/elements/FilterButton/FilterButton.test.js
Normal file
64
src/elements/FilterButton/FilterButton.test.js
Normal file
@ -0,0 +1,64 @@
|
||||
import {shallow} from 'enzyme';
|
||||
import React from 'react';
|
||||
import FilterButton from './FilterButton';
|
||||
import RandomPage from "../../pages/RandomPage/RandomPage";
|
||||
import {callAPI} from "../../utils/Api";
|
||||
|
||||
describe('<FilterButton/>', function () {
|
||||
it('renders without crashing ', function () {
|
||||
const wrapper = shallow(<FilterButton onFilterChange={() => {}}/>);
|
||||
wrapper.unmount();
|
||||
});
|
||||
|
||||
it('test initial render ', function () {
|
||||
const wrapper = shallow(<FilterButton onFilterChange={() => {}}/>);
|
||||
expect(wrapper.find('input')).toHaveLength(0);
|
||||
});
|
||||
|
||||
it('test clicking', function () {
|
||||
const wrapper = shallow(<FilterButton onFilterChange={() => {}}/>);
|
||||
wrapper.simulate('click');
|
||||
|
||||
expect(wrapper.find('input')).toHaveLength(1);
|
||||
});
|
||||
|
||||
it('test call of callback on textfield change', function () {
|
||||
let val = '';
|
||||
const func = jest.fn((vali => {val = vali}));
|
||||
|
||||
const wrapper = shallow(<FilterButton onFilterChange={func}/>);
|
||||
wrapper.simulate('click');
|
||||
|
||||
wrapper.find('input').simulate('change', {target: {value: 'test'}});
|
||||
|
||||
expect(func).toHaveBeenCalledTimes(1);
|
||||
expect(val).toBe('test')
|
||||
});
|
||||
|
||||
it('test closing on x button click', function () {
|
||||
const wrapper = shallow(<FilterButton onFilterChange={() => {}}/>);
|
||||
wrapper.simulate('click');
|
||||
|
||||
expect(wrapper.find('input')).toHaveLength(1);
|
||||
|
||||
wrapper.find('Button').simulate('click');
|
||||
|
||||
expect(wrapper.find('input')).toHaveLength(0);
|
||||
});
|
||||
|
||||
it('test shortkey press', function () {
|
||||
let events = [];
|
||||
document.addEventListener = jest.fn((event, cb) => {
|
||||
events[event] = cb;
|
||||
});
|
||||
|
||||
shallow(<RandomPage/>);
|
||||
|
||||
const wrapper = shallow(<FilterButton onFilterChange={() => {}}/>);
|
||||
expect(wrapper.find('input')).toHaveLength(0);
|
||||
// trigger the keypress event
|
||||
events.keyup({key: 'f'});
|
||||
|
||||
expect(wrapper.find('input')).toHaveLength(1);
|
||||
});
|
||||
});
|
99
src/elements/FilterButton/FilterButton.tsx
Normal file
99
src/elements/FilterButton/FilterButton.tsx
Normal file
@ -0,0 +1,99 @@
|
||||
import React from "react";
|
||||
import style from "../Popups/AddActorPopup/AddActorPopup.module.css";
|
||||
import {Button} from "../GPElements/Button";
|
||||
import {FontAwesomeIcon} from "@fortawesome/react-fontawesome";
|
||||
import {faFilter, faTimes} from "@fortawesome/free-solid-svg-icons";
|
||||
import {addKeyHandler, removeKeyHandler} from "../../utils/ShortkeyHandler";
|
||||
|
||||
interface props {
|
||||
onFilterChange: (filter: string) => void
|
||||
}
|
||||
|
||||
interface state {
|
||||
filtervisible: boolean;
|
||||
filter: string;
|
||||
}
|
||||
|
||||
class FilterButton extends React.Component<props, state> {
|
||||
// filterfield anchor, needed to focus after filter btn click
|
||||
private filterfield: HTMLInputElement | null | undefined;
|
||||
|
||||
|
||||
constructor(props: props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
filtervisible: false,
|
||||
filter: ''
|
||||
}
|
||||
|
||||
this.keypress = this.keypress.bind(this);
|
||||
this.enableFilterField = this.enableFilterField.bind(this);
|
||||
}
|
||||
|
||||
componentWillUnmount(): void {
|
||||
removeKeyHandler(this.keypress);
|
||||
}
|
||||
|
||||
componentDidMount(): void {
|
||||
addKeyHandler(this.keypress);
|
||||
}
|
||||
|
||||
render(): JSX.Element {
|
||||
if (this.state.filtervisible) {
|
||||
return (
|
||||
<>
|
||||
<input className={'form-control mr-sm-2 ' + style.searchinput}
|
||||
type='text' placeholder='Filter' value={this.state.filter}
|
||||
onChange={(e): void => {
|
||||
this.props.onFilterChange(e.target.value);
|
||||
this.setState({filter: e.target.value});
|
||||
}}
|
||||
ref={(input): void => {
|
||||
this.filterfield = input;
|
||||
}}/>
|
||||
<Button title={<FontAwesomeIcon style={{
|
||||
verticalAlign: 'middle',
|
||||
lineHeight: '130px'
|
||||
}} icon={faTimes} size='1x'/>} color={{backgroundColor: 'red'}} onClick={(): void => {
|
||||
this.setState({filter: '', filtervisible: false});
|
||||
}}/>
|
||||
</>
|
||||
);
|
||||
} else {
|
||||
return (<Button
|
||||
title={<span>Filter <FontAwesomeIcon
|
||||
style={{
|
||||
verticalAlign: 'middle',
|
||||
lineHeight: '130px'
|
||||
}}
|
||||
icon={faFilter}
|
||||
size='1x'/></span>}
|
||||
color={{backgroundColor: 'cornflowerblue', color: 'white'}}
|
||||
onClick={this.enableFilterField}/>)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* enable filterfield and focus into searchbar
|
||||
*/
|
||||
private enableFilterField(): void {
|
||||
this.setState({filtervisible: true}, () => {
|
||||
// focus filterfield after state update
|
||||
this.filterfield?.focus();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* key event handling
|
||||
* @param event keyevent
|
||||
*/
|
||||
private keypress(event: KeyboardEvent): void {
|
||||
// hide if escape is pressed
|
||||
if (event.key === 'f') {
|
||||
this.enableFilterField();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default FilterButton;
|
@ -6,10 +6,7 @@ import {NewActorPopupContent} from '../NewActorPopup/NewActorPopup';
|
||||
import {APINode, callAPI} from '../../../utils/Api';
|
||||
import {ActorType} from '../../../types/VideoTypes';
|
||||
import {GeneralSuccess} from '../../../types/GeneralTypes';
|
||||
import {FontAwesomeIcon} from '@fortawesome/react-fontawesome';
|
||||
import {faFilter, faTimes} from '@fortawesome/free-solid-svg-icons';
|
||||
import {Button} from '../../GPElements/Button';
|
||||
import {addKeyHandler, removeKeyHandler} from '../../../utils/ShortkeyHandler';
|
||||
import FilterButton from "../../FilterButton/FilterButton";
|
||||
|
||||
interface props {
|
||||
onHide: () => void;
|
||||
@ -20,7 +17,6 @@ interface state {
|
||||
contentDefault: boolean;
|
||||
actors: ActorType[];
|
||||
filter: string;
|
||||
filtervisible: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -36,23 +32,14 @@ class AddActorPopup extends React.Component<props, state> {
|
||||
this.state = {
|
||||
contentDefault: true,
|
||||
actors: [],
|
||||
filter: '',
|
||||
filtervisible: false
|
||||
filter: ''
|
||||
};
|
||||
|
||||
this.tileClickHandler = this.tileClickHandler.bind(this);
|
||||
this.filterSearch = this.filterSearch.bind(this);
|
||||
this.parentSubmit = this.parentSubmit.bind(this);
|
||||
this.keypress = this.keypress.bind(this);
|
||||
}
|
||||
|
||||
componentWillUnmount(): void {
|
||||
removeKeyHandler(this.keypress);
|
||||
}
|
||||
|
||||
componentDidMount(): void {
|
||||
addKeyHandler(this.keypress);
|
||||
|
||||
// fetch the available actors
|
||||
this.loadActors();
|
||||
}
|
||||
@ -94,30 +81,9 @@ class AddActorPopup extends React.Component<props, state> {
|
||||
return (
|
||||
<>
|
||||
<div className={style.searchbar}>
|
||||
{
|
||||
this.state.filtervisible ?
|
||||
<>
|
||||
<input className={'form-control mr-sm-2 ' + style.searchinput}
|
||||
type='text' placeholder='Filter' value={this.state.filter}
|
||||
onChange={(e): void => {
|
||||
this.setState({filter: e.target.value});
|
||||
}}
|
||||
ref={(input): void => {this.filterfield = input;}}/>
|
||||
<Button title={<FontAwesomeIcon style={{
|
||||
verticalAlign: 'middle',
|
||||
lineHeight: '130px'
|
||||
}} icon={faTimes} size='1x'/>} color={{backgroundColor: 'red'}} onClick={(): void => {
|
||||
this.setState({filter: '', filtervisible: false});
|
||||
}}/>
|
||||
</> :
|
||||
<Button
|
||||
title={<span>Filter <FontAwesomeIcon style={{
|
||||
verticalAlign: 'middle',
|
||||
lineHeight: '130px'
|
||||
}} icon={faFilter} size='1x'/></span>}
|
||||
color={{backgroundColor: 'cornflowerblue', color: 'white'}}
|
||||
onClick={(): void => this.enableFilterField()}/>
|
||||
}
|
||||
<FilterButton onFilterChange={(filter): void => {
|
||||
this.setState({filter: filter})
|
||||
}}/>
|
||||
</div>
|
||||
{this.state.actors.filter(this.filterSearch).map((el) => (<ActorTile actor={el} onClick={this.tileClickHandler}/>))}
|
||||
</>
|
||||
@ -155,16 +121,6 @@ class AddActorPopup extends React.Component<props, state> {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* enable filterfield and focus into searchbar
|
||||
*/
|
||||
private enableFilterField(): void {
|
||||
this.setState({filtervisible: true}, () => {
|
||||
// focus filterfield after state update
|
||||
this.filterfield?.focus();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* filter the actor array for search matches
|
||||
* @param actor
|
||||
@ -185,17 +141,6 @@ class AddActorPopup extends React.Component<props, state> {
|
||||
this.tileClickHandler(filteredList[0]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* key event handling
|
||||
* @param event keyevent
|
||||
*/
|
||||
private keypress(event: KeyboardEvent): void {
|
||||
// hide if escape is pressed
|
||||
if (event.key === 'f') {
|
||||
this.enableFilterField();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default AddActorPopup;
|
||||
|
@ -1,26 +1,3 @@
|
||||
.popup {
|
||||
border: 3px #3574fe solid;
|
||||
border-radius: 18px;
|
||||
height: 80%;
|
||||
left: 20%;
|
||||
opacity: 0.95;
|
||||
position: absolute;
|
||||
top: 10%;
|
||||
width: 60%;
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
.header {
|
||||
cursor: move;
|
||||
font-size: x-large;
|
||||
margin-left: 15px;
|
||||
margin-top: 10px;
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.content {
|
||||
margin-left: 20px;
|
||||
margin-right: 20px;
|
||||
margin-top: 10px;
|
||||
opacity: 1;
|
||||
.actionbar{
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
@ -25,11 +25,37 @@ describe('<AddTagPopup/>', function () {
|
||||
const wrapper = shallow(<AddTagPopup submit={jest.fn()} onHide={jest.fn()}/>);
|
||||
|
||||
wrapper.setState({
|
||||
items: [{tag_id: 1, tag_name: 'test'}]
|
||||
items: [{TagId: 1, TagName: 'test'}]
|
||||
}, () => {
|
||||
wrapper.find('Tag').first().dive().simulate('click');
|
||||
expect(wrapper.instance().props.submit).toHaveBeenCalledTimes(1);
|
||||
expect(wrapper.instance().props.onHide).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
});
|
||||
|
||||
it('test parent submit if one item left', function () {
|
||||
const onhide = jest.fn();
|
||||
const submit = jest.fn();
|
||||
|
||||
const wrapper = shallow(<AddTagPopup submit={submit} onHide={onhide}/>);
|
||||
|
||||
wrapper.setState({
|
||||
items: [{TagId: 1, TagName: 'test'}]
|
||||
}, () => {
|
||||
wrapper.instance().parentSubmit();
|
||||
|
||||
expect(onhide).toHaveBeenCalledTimes(1);
|
||||
expect(submit).toHaveBeenCalledTimes(1);
|
||||
|
||||
wrapper.setState({
|
||||
items: [{TagId: 1, TagName: 'test'}, {TagId: 3, TagName: 'test3'}]
|
||||
}, () => {
|
||||
wrapper.instance().parentSubmit();
|
||||
|
||||
// expect no submit if there are more than 1 item left...
|
||||
expect(onhide).toHaveBeenCalledTimes(1);
|
||||
expect(submit).toHaveBeenCalledTimes(1);
|
||||
})
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -3,15 +3,17 @@ import Tag from '../../Tag/Tag';
|
||||
import PopupBase from '../PopupBase';
|
||||
import {APINode, callAPI} from '../../../utils/Api';
|
||||
import {TagType} from '../../../types/VideoTypes';
|
||||
import FilterButton from "../../FilterButton/FilterButton";
|
||||
import styles from './AddTagPopup.module.css'
|
||||
|
||||
interface props {
|
||||
onHide: () => void;
|
||||
submit: (tagId: number, tagName: string) => void;
|
||||
movie_id: number;
|
||||
}
|
||||
|
||||
interface state {
|
||||
items: TagType[];
|
||||
filter: string;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -21,7 +23,11 @@ class AddTagPopup extends React.Component<props, state> {
|
||||
constructor(props: props) {
|
||||
super(props);
|
||||
|
||||
this.state = {items: []};
|
||||
this.state = {items: [], filter: ''};
|
||||
|
||||
this.tagFilter = this.tagFilter.bind(this);
|
||||
this.parentSubmit = this.parentSubmit.bind(this);
|
||||
this.onItemClick = this.onItemClick.bind(this);
|
||||
}
|
||||
|
||||
componentDidMount(): void {
|
||||
@ -34,18 +40,37 @@ class AddTagPopup extends React.Component<props, state> {
|
||||
|
||||
render(): JSX.Element {
|
||||
return (
|
||||
<PopupBase title='Add a Tag to this Video:' onHide={this.props.onHide}>
|
||||
<PopupBase title='Add a Tag to this Video:' onHide={this.props.onHide} ParentSubmit={this.parentSubmit}>
|
||||
<div className={styles.actionbar}>
|
||||
<FilterButton onFilterChange={(filter): void => this.setState({filter: filter})}/>
|
||||
</div>
|
||||
{this.state.items ?
|
||||
this.state.items.map((i) => (
|
||||
this.state.items.filter(this.tagFilter).map((i) => (
|
||||
<Tag tagInfo={i}
|
||||
onclick={(): void => {
|
||||
this.props.submit(i.TagId, i.TagName);
|
||||
this.props.onHide();
|
||||
}}/>
|
||||
onclick={(): void => this.onItemClick(i)}/>
|
||||
)) : null}
|
||||
</PopupBase>
|
||||
);
|
||||
}
|
||||
|
||||
private onItemClick(tag: TagType): void {
|
||||
this.props.submit(tag.TagId, tag.TagName);
|
||||
this.props.onHide();
|
||||
}
|
||||
|
||||
private tagFilter(tag: TagType): boolean {
|
||||
return tag.TagName.toLowerCase().includes(this.state.filter.toLowerCase());
|
||||
}
|
||||
|
||||
private parentSubmit(): void {
|
||||
// allow submit only if one item is left in selection
|
||||
const filteredList = this.state.items.filter(this.tagFilter);
|
||||
|
||||
if (filteredList.length === 1) {
|
||||
// simulate click if parent submit
|
||||
this.onItemClick(filteredList[0]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default AddTagPopup;
|
||||
|
@ -181,13 +181,10 @@ export class Player extends React.Component<myprops, mystate> {
|
||||
handlePopOvers(): JSX.Element {
|
||||
return (
|
||||
<>
|
||||
{this.state.popupvisible ?
|
||||
<AddTagPopup onHide={(): void => {
|
||||
this.setState({popupvisible: false});
|
||||
}}
|
||||
submit={this.quickAddTag}
|
||||
movie_id={this.state.movie_id}/> :
|
||||
null
|
||||
{
|
||||
this.state.popupvisible ?
|
||||
<AddTagPopup onHide={(): void => this.setState({popupvisible: false})}
|
||||
submit={this.quickAddTag}/> : null
|
||||
}
|
||||
{
|
||||
this.state.actorpopupvisible ?
|
||||
|
Loading…
Reference in New Issue
Block a user