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 {APINode, callAPI} from '../../../utils/Api';
|
||||||
import {ActorType} from '../../../types/VideoTypes';
|
import {ActorType} from '../../../types/VideoTypes';
|
||||||
import {GeneralSuccess} from '../../../types/GeneralTypes';
|
import {GeneralSuccess} from '../../../types/GeneralTypes';
|
||||||
import {FontAwesomeIcon} from '@fortawesome/react-fontawesome';
|
import FilterButton from "../../FilterButton/FilterButton";
|
||||||
import {faFilter, faTimes} from '@fortawesome/free-solid-svg-icons';
|
|
||||||
import {Button} from '../../GPElements/Button';
|
|
||||||
import {addKeyHandler, removeKeyHandler} from '../../../utils/ShortkeyHandler';
|
|
||||||
|
|
||||||
interface props {
|
interface props {
|
||||||
onHide: () => void;
|
onHide: () => void;
|
||||||
@ -20,7 +17,6 @@ interface state {
|
|||||||
contentDefault: boolean;
|
contentDefault: boolean;
|
||||||
actors: ActorType[];
|
actors: ActorType[];
|
||||||
filter: string;
|
filter: string;
|
||||||
filtervisible: boolean;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -36,23 +32,14 @@ class AddActorPopup extends React.Component<props, state> {
|
|||||||
this.state = {
|
this.state = {
|
||||||
contentDefault: true,
|
contentDefault: true,
|
||||||
actors: [],
|
actors: [],
|
||||||
filter: '',
|
filter: ''
|
||||||
filtervisible: false
|
|
||||||
};
|
};
|
||||||
|
|
||||||
this.tileClickHandler = this.tileClickHandler.bind(this);
|
this.tileClickHandler = this.tileClickHandler.bind(this);
|
||||||
this.filterSearch = this.filterSearch.bind(this);
|
this.filterSearch = this.filterSearch.bind(this);
|
||||||
this.parentSubmit = this.parentSubmit.bind(this);
|
this.parentSubmit = this.parentSubmit.bind(this);
|
||||||
this.keypress = this.keypress.bind(this);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillUnmount(): void {
|
|
||||||
removeKeyHandler(this.keypress);
|
|
||||||
}
|
|
||||||
|
|
||||||
componentDidMount(): void {
|
componentDidMount(): void {
|
||||||
addKeyHandler(this.keypress);
|
|
||||||
|
|
||||||
// fetch the available actors
|
// fetch the available actors
|
||||||
this.loadActors();
|
this.loadActors();
|
||||||
}
|
}
|
||||||
@ -94,30 +81,9 @@ class AddActorPopup extends React.Component<props, state> {
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div className={style.searchbar}>
|
<div className={style.searchbar}>
|
||||||
{
|
<FilterButton onFilterChange={(filter): void => {
|
||||||
this.state.filtervisible ?
|
this.setState({filter: filter})
|
||||||
<>
|
}}/>
|
||||||
<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()}/>
|
|
||||||
}
|
|
||||||
</div>
|
</div>
|
||||||
{this.state.actors.filter(this.filterSearch).map((el) => (<ActorTile actor={el} onClick={this.tileClickHandler}/>))}
|
{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
|
* filter the actor array for search matches
|
||||||
* @param actor
|
* @param actor
|
||||||
@ -185,17 +141,6 @@ class AddActorPopup extends React.Component<props, state> {
|
|||||||
this.tileClickHandler(filteredList[0]);
|
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;
|
export default AddActorPopup;
|
||||||
|
@ -1,26 +1,3 @@
|
|||||||
.popup {
|
.actionbar{
|
||||||
border: 3px #3574fe solid;
|
margin-bottom: 15px;
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
@ -25,11 +25,37 @@ describe('<AddTagPopup/>', function () {
|
|||||||
const wrapper = shallow(<AddTagPopup submit={jest.fn()} onHide={jest.fn()}/>);
|
const wrapper = shallow(<AddTagPopup submit={jest.fn()} onHide={jest.fn()}/>);
|
||||||
|
|
||||||
wrapper.setState({
|
wrapper.setState({
|
||||||
items: [{tag_id: 1, tag_name: 'test'}]
|
items: [{TagId: 1, TagName: 'test'}]
|
||||||
}, () => {
|
}, () => {
|
||||||
wrapper.find('Tag').first().dive().simulate('click');
|
wrapper.find('Tag').first().dive().simulate('click');
|
||||||
expect(wrapper.instance().props.submit).toHaveBeenCalledTimes(1);
|
expect(wrapper.instance().props.submit).toHaveBeenCalledTimes(1);
|
||||||
expect(wrapper.instance().props.onHide).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 PopupBase from '../PopupBase';
|
||||||
import {APINode, callAPI} from '../../../utils/Api';
|
import {APINode, callAPI} from '../../../utils/Api';
|
||||||
import {TagType} from '../../../types/VideoTypes';
|
import {TagType} from '../../../types/VideoTypes';
|
||||||
|
import FilterButton from "../../FilterButton/FilterButton";
|
||||||
|
import styles from './AddTagPopup.module.css'
|
||||||
|
|
||||||
interface props {
|
interface props {
|
||||||
onHide: () => void;
|
onHide: () => void;
|
||||||
submit: (tagId: number, tagName: string) => void;
|
submit: (tagId: number, tagName: string) => void;
|
||||||
movie_id: number;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
interface state {
|
interface state {
|
||||||
items: TagType[];
|
items: TagType[];
|
||||||
|
filter: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -21,7 +23,11 @@ class AddTagPopup extends React.Component<props, state> {
|
|||||||
constructor(props: props) {
|
constructor(props: props) {
|
||||||
super(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 {
|
componentDidMount(): void {
|
||||||
@ -34,18 +40,37 @@ class AddTagPopup extends React.Component<props, state> {
|
|||||||
|
|
||||||
render(): JSX.Element {
|
render(): JSX.Element {
|
||||||
return (
|
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 ?
|
||||||
this.state.items.map((i) => (
|
this.state.items.filter(this.tagFilter).map((i) => (
|
||||||
<Tag tagInfo={i}
|
<Tag tagInfo={i}
|
||||||
onclick={(): void => {
|
onclick={(): void => this.onItemClick(i)}/>
|
||||||
this.props.submit(i.TagId, i.TagName);
|
|
||||||
this.props.onHide();
|
|
||||||
}}/>
|
|
||||||
)) : null}
|
)) : null}
|
||||||
</PopupBase>
|
</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;
|
export default AddTagPopup;
|
||||||
|
@ -181,13 +181,10 @@ export class Player extends React.Component<myprops, mystate> {
|
|||||||
handlePopOvers(): JSX.Element {
|
handlePopOvers(): JSX.Element {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{this.state.popupvisible ?
|
{
|
||||||
<AddTagPopup onHide={(): void => {
|
this.state.popupvisible ?
|
||||||
this.setState({popupvisible: false});
|
<AddTagPopup onHide={(): void => this.setState({popupvisible: false})}
|
||||||
}}
|
submit={this.quickAddTag}/> : null
|
||||||
submit={this.quickAddTag}
|
|
||||||
movie_id={this.state.movie_id}/> :
|
|
||||||
null
|
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
this.state.actorpopupvisible ?
|
this.state.actorpopupvisible ?
|
||||||
|
Loading…
Reference in New Issue
Block a user