Merge branch 'enter_popup_submit' into 'master'

Shortkeys

See merge request lukas/openmediacenter!31
This commit is contained in:
Lukas Heiligenbrunner 2021-01-28 19:50:26 +00:00
commit fbf286c09c
10 changed files with 146 additions and 26 deletions

View File

@ -28,11 +28,9 @@ class ActorTile extends React.Component<props> {
</Link> </Link>
); );
} }
} }
renderActorTile(customclickhandler: (actor: ActorType) => void): JSX.Element { renderActorTile(customclickhandler: (actor: ActorType) => void): JSX.Element {
console.log(this.props.actor);
return ( return (
<div className={style.actortile} onClick={(): void => customclickhandler(this.props.actor)}> <div className={style.actortile} onClick={(): void => customclickhandler(this.props.actor)}>
<div className={style.actortile_thumbnail}> <div className={style.actortile_thumbnail}>

View File

@ -74,4 +74,16 @@ describe('<AddActorPopup/>', function () {
expect(wrapper.find('PopupBase').find('ActorTile')).toHaveLength(0); expect(wrapper.find('PopupBase').find('ActorTile')).toHaveLength(0);
}); });
it('test Enter submit if only one element left', function () {
const wrapper = shallow(<AddActorPopup/>);
callAPIMock({});
wrapper.setState({actors: [{name: 'test', actor_id: 1}]});
wrapper.find('PopupBase').props().ParentSubmit();
expect(callAPI).toHaveBeenCalledTimes(1);
});
}); });

View File

@ -9,6 +9,7 @@ import {GeneralSuccess} from '../../../types/GeneralTypes';
import {FontAwesomeIcon} from '@fortawesome/react-fontawesome'; import {FontAwesomeIcon} from '@fortawesome/react-fontawesome';
import {faFilter, faTimes} from '@fortawesome/free-solid-svg-icons'; import {faFilter, faTimes} from '@fortawesome/free-solid-svg-icons';
import {Button} from '../../GPElements/Button'; import {Button} from '../../GPElements/Button';
import {addKeyHandler, removeKeyHandler} from '../../../utils/ShortkeyHandler';
interface props { interface props {
onHide: () => void; onHide: () => void;
@ -41,6 +42,19 @@ class AddActorPopup extends React.Component<props, state> {
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.keypress = this.keypress.bind(this);
}
componentWillUnmount(): void {
removeKeyHandler(this.keypress);
}
componentDidMount(): void {
addKeyHandler(this.keypress);
// fetch the available actors
this.loadActors();
} }
render(): JSX.Element { render(): JSX.Element {
@ -52,18 +66,13 @@ class AddActorPopup extends React.Component<props, state> {
className={style.newactorbutton} className={style.newactorbutton}
onClick={(): void => { onClick={(): void => {
this.setState({contentDefault: false}); this.setState({contentDefault: false});
}}>Create new Actor</button>}> }}>Create new Actor</button>} ParentSubmit={this.parentSubmit}>
{this.resolvePage()} {this.resolvePage()}
</PopupBase> </PopupBase>
</> </>
); );
} }
componentDidMount(): void {
// fetch the available actors
this.loadActors();
}
/** /**
* selector for current showing popup page * selector for current showing popup page
* @returns {JSX.Element} * @returns {JSX.Element}
@ -101,15 +110,13 @@ class AddActorPopup extends React.Component<props, state> {
this.setState({filter: '', filtervisible: false}); this.setState({filter: '', filtervisible: false});
}}/> }}/>
</> : </> :
<Button title={<span>Filter <FontAwesomeIcon style={{ <Button
verticalAlign: 'middle', title={<span>Filter <FontAwesomeIcon style={{
lineHeight: '130px' verticalAlign: 'middle',
}} icon={faFilter} size='1x'/></span>} color={{backgroundColor: 'cornflowerblue', color: 'white'}} onClick={(): void => { lineHeight: '130px'
this.setState({filtervisible: true}, () => { }} icon={faFilter} size='1x'/></span>}
// focus filterfield after state update color={{backgroundColor: 'cornflowerblue', color: 'white'}}
this.filterfield?.focus(); 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}/>))}
@ -120,6 +127,16 @@ 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();
});
}
/** /**
* event handling for ActorTile Click * event handling for ActorTile Click
*/ */
@ -155,6 +172,30 @@ class AddActorPopup extends React.Component<props, state> {
private filterSearch(actor: ActorType): boolean { private filterSearch(actor: ActorType): boolean {
return actor.name.toLowerCase().includes(this.state.filter.toLowerCase()); return actor.name.toLowerCase().includes(this.state.filter.toLowerCase());
} }
/**
* handle a Popupbase parent submit action
*/
private parentSubmit(): void {
// allow submit only if one item is left in selection
const filteredList = this.state.actors.filter(this.filterSearch);
if (filteredList.length === 1) {
// simulate click if parent submit
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;

View File

@ -26,7 +26,6 @@ class AddTagPopup extends React.Component<props, state> {
componentDidMount(): void { componentDidMount(): void {
callAPI('tags.php', {action: 'getAllTags'}, (result: TagType[]) => { callAPI('tags.php', {action: 'getAllTags'}, (result: TagType[]) => {
console.log(result);
this.setState({ this.setState({
items: result items: result
}); });

View File

@ -16,7 +16,7 @@ class NewTagPopup extends React.Component<props> {
render(): JSX.Element { render(): JSX.Element {
return ( return (
<PopupBase title='Add new Tag' onHide={this.props.onHide} height='200px' width='400px'> <PopupBase title='Add new Tag' onHide={this.props.onHide} height='200px' width='400px' ParentSubmit={(): void => this.storeselection()}>
<div><input type='text' placeholder='Tagname' onChange={(v): void => { <div><input type='text' placeholder='Tagname' onChange={(v): void => {
this.value = v.target.value; this.value = v.target.value;
}}/></div> }}/></div>

View File

@ -8,12 +8,16 @@ describe('<PopupBase/>', function () {
wrapper.unmount(); wrapper.unmount();
}); });
it('simulate keypress', function () { let events;
let events = []; function mockKeyPress(){
events = [];
document.addEventListener = jest.fn((event, cb) => { document.addEventListener = jest.fn((event, cb) => {
events[event] = cb; events[event] = cb;
}); });
}
it('simulate keypress', function () {
mockKeyPress();
const func = jest.fn(); const func = jest.fn();
shallow(<PopupBase onHide={() => func()}/>); shallow(<PopupBase onHide={() => func()}/>);
@ -23,4 +27,14 @@ describe('<PopupBase/>', function () {
expect(func).toBeCalledTimes(1); expect(func).toBeCalledTimes(1);
}); });
it('test an Enter sumit', function () {
mockKeyPress();
const func = jest.fn();
shallow(<PopupBase ParentSubmit={() => func()}/>);
// trigger the keypress event
events.keyup({key: 'Enter'});
expect(func).toBeCalledTimes(1);
});
}); });

View File

@ -2,13 +2,15 @@ import GlobalInfos from '../../utils/GlobalInfos';
import style from './PopupBase.module.css'; import style from './PopupBase.module.css';
import {Line} from '../PageTitle/PageTitle'; import {Line} from '../PageTitle/PageTitle';
import React, {RefObject} from 'react'; import React, {RefObject} from 'react';
import {addKeyHandler, removeKeyHandler} from '../../utils/ShortkeyHandler';
interface props { interface props {
width?: string; width?: string;
height?: string; height?: string;
banner?: JSX.Element; banner?: JSX.Element;
title: string; title: string;
onHide: () => void onHide: () => void;
ParentSubmit?: () => void;
} }
/** /**
@ -38,7 +40,7 @@ class PopupBase extends React.Component<props> {
componentDidMount(): void { componentDidMount(): void {
document.addEventListener('mousedown', this.handleClickOutside); document.addEventListener('mousedown', this.handleClickOutside);
document.addEventListener('keyup', this.keypress); addKeyHandler(this.keypress);
// add element drag drop events // add element drag drop events
if (this.wrapperRef != null) { if (this.wrapperRef != null) {
@ -49,7 +51,7 @@ class PopupBase extends React.Component<props> {
componentWillUnmount(): void { componentWillUnmount(): void {
// remove the appended listeners // remove the appended listeners
document.removeEventListener('mousedown', this.handleClickOutside); document.removeEventListener('mousedown', this.handleClickOutside);
document.removeEventListener('keyup', this.keypress); removeKeyHandler(this.keypress);
} }
render(): JSX.Element { render(): JSX.Element {
@ -86,6 +88,9 @@ class PopupBase extends React.Component<props> {
// hide if escape is pressed // hide if escape is pressed
if (event.key === 'Escape') { if (event.key === 'Escape') {
this.props.onHide(); this.props.onHide();
} else if (event.key === 'Enter') {
// call a parentsubmit if defined
if (this.props.ParentSubmit) this.props.ParentSubmit();
} }
} }

View File

@ -1,6 +1,8 @@
import {shallow} from 'enzyme'; import {shallow} from 'enzyme';
import React from 'react'; import React from 'react';
import RandomPage from './RandomPage'; import RandomPage from './RandomPage';
import {callAPI} from '../../utils/Api';
import PopupBase from '../../elements/Popups/PopupBase';
describe('<RandomPage/>', function () { describe('<RandomPage/>', function () {
it('renders without crashing ', function () { it('renders without crashing ', function () {
@ -45,4 +47,20 @@ describe('<RandomPage/>', function () {
expect(wrapper.find('Tag')).toHaveLength(2); expect(wrapper.find('Tag')).toHaveLength(2);
}); });
it('test shortkey press', function () {
let events = [];
document.addEventListener = jest.fn((event, cb) => {
events[event] = cb;
});
shallow(<RandomPage/>);
callAPIMock({rows: [], tags: []});
// trigger the keypress event
events.keyup({key: 's'});
expect(callAPI).toBeCalledTimes(1);
});
}); });

View File

@ -7,6 +7,7 @@ import VideoContainer from '../../elements/VideoContainer/VideoContainer';
import {callAPI} from '../../utils/Api'; import {callAPI} from '../../utils/Api';
import {TagType} from '../../types/VideoTypes'; import {TagType} from '../../types/VideoTypes';
import {VideoTypes} from '../../types/ApiTypes'; import {VideoTypes} from '../../types/ApiTypes';
import {addKeyHandler, removeKeyHandler} from '../../utils/ShortkeyHandler';
interface state { interface state {
videos: VideoTypes.VideoUnloadedType[]; videos: VideoTypes.VideoUnloadedType[];
@ -29,12 +30,20 @@ class RandomPage extends React.Component<{}, state> {
videos: [], videos: [],
tags: [] tags: []
}; };
this.keypress = this.keypress.bind(this);
} }
componentDidMount(): void { componentDidMount(): void {
addKeyHandler(this.keypress);
this.loadShuffledvideos(4); this.loadShuffledvideos(4);
} }
componentWillUnmount(): void {
removeKeyHandler(this.keypress);
}
render(): JSX.Element { render(): JSX.Element {
return ( return (
<div> <div>
@ -75,8 +84,6 @@ class RandomPage extends React.Component<{}, state> {
*/ */
loadShuffledvideos(nr: number): void { loadShuffledvideos(nr: number): void {
callAPI<GetRandomMoviesType>('video.php', {action: 'getRandomMovies', number: nr}, result => { callAPI<GetRandomMoviesType>('video.php', {action: 'getRandomMovies', number: nr}, result => {
console.log(result);
this.setState({videos: []}); // needed to trigger rerender of main videoview this.setState({videos: []}); // needed to trigger rerender of main videoview
this.setState({ this.setState({
videos: result.rows, videos: result.rows,
@ -84,6 +91,17 @@ class RandomPage extends React.Component<{}, state> {
}); });
}); });
} }
/**
* key event handling
* @param event keyevent
*/
private keypress(event: KeyboardEvent): void {
// bind s to shuffle
if (event.key === 's') {
this.loadShuffledvideos(4);
}
}
} }
export default RandomPage; export default RandomPage;

View File

@ -0,0 +1,15 @@
/**
* add a new keyhandler
* @param handler function to be called onkeyup
*/
export const addKeyHandler = (handler: (event: KeyboardEvent) => void): void => {
document.addEventListener('keyup', handler);
};
/**
* delete keyhandler
* @param handler handler to be removed
*/
export const removeKeyHandler = (handler: (event: KeyboardEvent) => void): void => {
document.removeEventListener('keyup', handler);
}