outsource filterbutton to new file
make addtag popup filterable
This commit is contained in:
		
							
								
								
									
										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 ?
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user