add drag and drop support for addtagpopup
esc closes the popup theme style is used
This commit is contained in:
parent
6076512dd0
commit
76f879a0f2
@ -1,27 +1,36 @@
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
import Modal from 'react-bootstrap/Modal'
|
import ReactDom from 'react-dom';
|
||||||
import Dropdown from "react-bootstrap/Dropdown";
|
import style from './AddTagPopup.module.css'
|
||||||
import DropdownButton from "react-bootstrap/DropdownButton";
|
import Tag from "../Tag/Tag";
|
||||||
|
import {Line} from "../PageTitle/PageTitle";
|
||||||
|
import GlobalInfos from "../../GlobalInfos";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* component creates overlay to add a new tag to a video
|
* component creates overlay to add a new tag to a video
|
||||||
*/
|
*/
|
||||||
class AddTagPopup extends React.Component {
|
class AddTagPopup extends React.Component {
|
||||||
|
/// instance of root element
|
||||||
|
element;
|
||||||
|
|
||||||
constructor(props, context) {
|
constructor(props, context) {
|
||||||
super(props, context);
|
super(props, context);
|
||||||
|
|
||||||
this.state = {
|
this.state = {items: []};
|
||||||
selection: {
|
this.handleClickOutside = this.handleClickOutside.bind(this);
|
||||||
name: "nothing selected",
|
this.keypress = this.keypress.bind(this);
|
||||||
id: -1
|
|
||||||
},
|
|
||||||
items: []
|
|
||||||
};
|
|
||||||
|
|
||||||
this.props = props;
|
this.props = props;
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
|
document.addEventListener('click', this.handleClickOutside);
|
||||||
|
document.addEventListener('keyup', this.keypress);
|
||||||
|
|
||||||
|
// add element drag drop events
|
||||||
|
if (this.element != null) {
|
||||||
|
this.dragElement();
|
||||||
|
}
|
||||||
|
|
||||||
const updateRequest = new FormData();
|
const updateRequest = new FormData();
|
||||||
updateRequest.append('action', 'getAllTags');
|
updateRequest.append('action', 'getAllTags');
|
||||||
|
|
||||||
@ -34,50 +43,62 @@ class AddTagPopup extends React.Component {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
componentWillUnmount() {
|
||||||
|
// remove the appended listeners
|
||||||
|
document.removeEventListener('click', this.handleClickOutside);
|
||||||
|
document.removeEventListener('keyup', this.keypress);
|
||||||
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
|
const themeStyle = GlobalInfos.getThemeStyle();
|
||||||
return (
|
return (
|
||||||
<>
|
<div className={[style.popup, themeStyle.thirdbackground].join(' ')} ref={el => this.element = el}>
|
||||||
<Modal
|
<div className={[style.header, themeStyle.textcolor].join(' ')}>Add a Tag to this Video:</div>
|
||||||
show={this.props.show}
|
<Line/>
|
||||||
onHide={this.props.onHide}
|
<div className={style.content}>
|
||||||
size="lg"
|
|
||||||
aria-labelledby="contained-modal-title-vcenter"
|
|
||||||
centered>
|
|
||||||
<Modal.Header closeButton>
|
|
||||||
<Modal.Title id="contained-modal-title-vcenter">
|
|
||||||
Add to Tag
|
|
||||||
</Modal.Title>
|
|
||||||
</Modal.Header>
|
|
||||||
<Modal.Body>
|
|
||||||
<h4>Select a Tag:</h4>
|
|
||||||
<DropdownButton id="dropdown-basic-button" title={this.state.selection.name}>
|
|
||||||
{this.state.items ?
|
{this.state.items ?
|
||||||
this.state.items.map((i) => (
|
this.state.items.map((i) => (
|
||||||
<Dropdown.Item key={i.tag_name} onClick={() => {
|
<Tag onclick={() => {
|
||||||
this.setState({selection: {name: i.tag_name, id: i.tag_id}})
|
this.addTag(i.tag_id, i.tag_name);
|
||||||
}}>{i.tag_name}</Dropdown.Item>
|
}}>{i.tag_name}</Tag>
|
||||||
)) :
|
)) : null}
|
||||||
<Dropdown.Item>loading tags...</Dropdown.Item>}
|
</div>
|
||||||
</DropdownButton>
|
</div>
|
||||||
</Modal.Body>
|
|
||||||
<Modal.Footer>
|
|
||||||
<button className='btn btn-primary' onClick={() => {
|
|
||||||
this.storeselection();
|
|
||||||
}}>Add
|
|
||||||
</button>
|
|
||||||
</Modal.Footer>
|
|
||||||
</Modal>
|
|
||||||
</>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* store the filled in form to the backend
|
* Alert if clicked on outside of element
|
||||||
*/
|
*/
|
||||||
storeselection() {
|
handleClickOutside(event) {
|
||||||
|
const domNode = ReactDom.findDOMNode(this);
|
||||||
|
|
||||||
|
if (!domNode || !domNode.contains(event.target)) {
|
||||||
|
this.props.onHide();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* key event handling
|
||||||
|
* @param event keyevent
|
||||||
|
*/
|
||||||
|
keypress(event) {
|
||||||
|
// hide if escape is pressed
|
||||||
|
if (event.key === "Escape") {
|
||||||
|
this.props.onHide();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* add a new tag to this video
|
||||||
|
* @param tagid tag id to add
|
||||||
|
* @param tagname tag name to add
|
||||||
|
*/
|
||||||
|
addTag(tagid, tagname) {
|
||||||
|
console.log(this.props)
|
||||||
const updateRequest = new FormData();
|
const updateRequest = new FormData();
|
||||||
updateRequest.append('action', 'addTag');
|
updateRequest.append('action', 'addTag');
|
||||||
updateRequest.append('id', this.state.selection.id);
|
updateRequest.append('id', tagid);
|
||||||
updateRequest.append('movieid', this.props.movie_id);
|
updateRequest.append('movieid', this.props.movie_id);
|
||||||
|
|
||||||
fetch('/api/tags.php', {method: 'POST', body: updateRequest})
|
fetch('/api/tags.php', {method: 'POST', body: updateRequest})
|
||||||
@ -86,10 +107,51 @@ class AddTagPopup extends React.Component {
|
|||||||
if (result.result !== "success") {
|
if (result.result !== "success") {
|
||||||
console.log("error occured while writing to db -- todo error handling");
|
console.log("error occured while writing to db -- todo error handling");
|
||||||
console.log(result.result);
|
console.log(result.result);
|
||||||
|
} else {
|
||||||
|
this.props.submit(tagid, tagname);
|
||||||
}
|
}
|
||||||
this.props.onHide();
|
this.props.onHide();
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* make the element drag and droppable
|
||||||
|
*/
|
||||||
|
dragElement() {
|
||||||
|
let xOld = 0, yOld = 0;
|
||||||
|
|
||||||
|
const elmnt = this.element;
|
||||||
|
elmnt.firstChild.onmousedown = dragMouseDown;
|
||||||
|
|
||||||
|
|
||||||
|
function dragMouseDown(e) {
|
||||||
|
e.preventDefault();
|
||||||
|
// get the mouse cursor position at startup:
|
||||||
|
xOld = e.clientX;
|
||||||
|
yOld = e.clientY;
|
||||||
|
document.onmouseup = closeDragElement;
|
||||||
|
// call a function whenever the cursor moves:
|
||||||
|
document.onmousemove = elementDrag;
|
||||||
|
}
|
||||||
|
|
||||||
|
function elementDrag(e) {
|
||||||
|
e.preventDefault();
|
||||||
|
// calculate the new cursor position:
|
||||||
|
const dx = xOld - e.clientX;
|
||||||
|
const dy = yOld - e.clientY;
|
||||||
|
xOld = e.clientX;
|
||||||
|
yOld = e.clientY;
|
||||||
|
// set the element's new position:
|
||||||
|
elmnt.style.top = (elmnt.offsetTop - dy) + "px";
|
||||||
|
elmnt.style.left = (elmnt.offsetLeft - dx) + "px";
|
||||||
|
}
|
||||||
|
|
||||||
|
function closeDragElement() {
|
||||||
|
// stop moving when mouse button is released:
|
||||||
|
document.onmouseup = null;
|
||||||
|
document.onmousemove = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default AddTagPopup;
|
export default AddTagPopup;
|
||||||
|
26
src/elements/AddTagPopup/AddTagPopup.module.css
Normal file
26
src/elements/AddTagPopup/AddTagPopup.module.css
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
.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;
|
||||||
|
}
|
@ -11,47 +11,68 @@ describe('<AddTagPopup/>', function () {
|
|||||||
wrapper.unmount();
|
wrapper.unmount();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('test dropdown insertion', function () {
|
it('test tag insertion', function () {
|
||||||
const wrapper = shallow(<AddTagPopup/>);
|
const wrapper = shallow(<AddTagPopup/>);
|
||||||
wrapper.setState({items: ["test1", "test2", "test3"]});
|
wrapper.setState({
|
||||||
expect(wrapper.find('DropdownItem')).toHaveLength(3);
|
items: [{tag_id: 1, tag_name: 'test'}, {tag_id: 2, tag_name: "ee"}]
|
||||||
|
}, () => {
|
||||||
|
expect(wrapper.find('Tag')).toHaveLength(2);
|
||||||
|
expect(wrapper.find('Tag').first().dive().text()).toBe("test");
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('test storeseletion click event', done => {
|
it('test tag click', function () {
|
||||||
const mockSuccessResponse = {};
|
|
||||||
const mockJsonPromise = Promise.resolve(mockSuccessResponse);
|
|
||||||
const mockFetchPromise = Promise.resolve({
|
|
||||||
json: () => mockJsonPromise,
|
|
||||||
});
|
|
||||||
global.fetch = jest.fn().mockImplementation(() => mockFetchPromise);
|
|
||||||
|
|
||||||
const func = jest.fn();
|
|
||||||
|
|
||||||
const wrapper = shallow(<AddTagPopup/>);
|
const wrapper = shallow(<AddTagPopup/>);
|
||||||
wrapper.setProps({
|
wrapper.instance().addTag = jest.fn();
|
||||||
onHide: () => {
|
|
||||||
func()
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
wrapper.setState({
|
wrapper.setState({
|
||||||
items: ["test1", "test2", "test3"],
|
items: [{tag_id: 1, tag_name: 'test'}]
|
||||||
selection: {
|
}, () => {
|
||||||
name: "test1",
|
wrapper.find('Tag').first().dive().simulate('click');
|
||||||
id: 42
|
expect(wrapper.instance().addTag).toHaveBeenCalledTimes(1);
|
||||||
}
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
// first call of fetch is getting of available tags
|
it('test addtag', done => {
|
||||||
expect(global.fetch).toHaveBeenCalledTimes(1);
|
const wrapper = shallow(<AddTagPopup/>);
|
||||||
wrapper.find('ModalFooter').find('button').simulate('click');
|
|
||||||
|
|
||||||
// now called 2 times
|
global.fetch = prepareFetchApi({result: "success"});
|
||||||
expect(global.fetch).toHaveBeenCalledTimes(2);
|
|
||||||
|
wrapper.setProps({
|
||||||
|
submit: jest.fn((arg1, arg2) => {}),
|
||||||
|
onHide: jest.fn()
|
||||||
|
}, () => {
|
||||||
|
wrapper.instance().addTag(1, "test");
|
||||||
|
|
||||||
|
expect(global.fetch).toHaveBeenCalledTimes(1);
|
||||||
|
});
|
||||||
|
|
||||||
process.nextTick(() => {
|
process.nextTick(() => {
|
||||||
//callback to close window should have called
|
expect(wrapper.instance().props.submit).toHaveBeenCalledTimes(1);
|
||||||
expect(func).toHaveBeenCalledTimes(1);
|
expect(wrapper.instance().props.onHide).toHaveBeenCalledTimes(1);
|
||||||
|
|
||||||
|
global.fetch.mockClear();
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('test failing addTag', done => {
|
||||||
|
const wrapper = shallow(<AddTagPopup/>);
|
||||||
|
|
||||||
|
global.fetch = prepareFetchApi({result: "fail"});
|
||||||
|
|
||||||
|
wrapper.setProps({
|
||||||
|
submit: jest.fn((arg1, arg2) => {}),
|
||||||
|
onHide: jest.fn()
|
||||||
|
}, () => {
|
||||||
|
wrapper.instance().addTag(1, "test");
|
||||||
|
|
||||||
|
expect(global.fetch).toHaveBeenCalledTimes(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
process.nextTick(() => {
|
||||||
|
expect(wrapper.instance().props.submit).toHaveBeenCalledTimes(0);
|
||||||
|
expect(wrapper.instance().props.onHide).toHaveBeenCalledTimes(1);
|
||||||
|
|
||||||
global.fetch.mockClear();
|
global.fetch.mockClear();
|
||||||
done();
|
done();
|
||||||
|
@ -40,7 +40,8 @@ class Preview extends React.Component {
|
|||||||
render() {
|
render() {
|
||||||
const themeStyle = GlobalInfos.getThemeStyle();
|
const themeStyle = GlobalInfos.getThemeStyle();
|
||||||
return (
|
return (
|
||||||
<div className={style.videopreview + ' ' + themeStyle.secbackground + ' ' + themeStyle.preview} onClick={() => this.itemClick()}>
|
<div className={style.videopreview + ' ' + themeStyle.secbackground + ' ' + themeStyle.preview}
|
||||||
|
onClick={() => this.itemClick()}>
|
||||||
<div className={style.previewtitle + ' ' + themeStyle.lighttextcolor}>{this.state.name}</div>
|
<div className={style.previewtitle + ' ' + themeStyle.lighttextcolor}>{this.state.name}</div>
|
||||||
<div className={style.previewpic}>
|
<div className={style.previewpic}>
|
||||||
{this.state.previewpicture !== null ?
|
{this.state.previewpicture !== null ?
|
||||||
@ -77,7 +78,9 @@ export class TagPreview extends React.Component {
|
|||||||
render() {
|
render() {
|
||||||
const themeStyle = GlobalInfos.getThemeStyle();
|
const themeStyle = GlobalInfos.getThemeStyle();
|
||||||
return (
|
return (
|
||||||
<div className={style.videopreview + ' ' + style.tagpreview + ' ' + themeStyle.secbackground + ' ' + themeStyle.preview} onClick={() => this.itemClick()}>
|
<div
|
||||||
|
className={style.videopreview + ' ' + style.tagpreview + ' ' + themeStyle.secbackground + ' ' + themeStyle.preview}
|
||||||
|
onClick={() => this.itemClick()}>
|
||||||
<div className={style.tagpreviewtitle + ' ' + themeStyle.lighttextcolor}>
|
<div className={style.tagpreviewtitle + ' ' + themeStyle.lighttextcolor}>
|
||||||
{this.props.name}
|
{this.props.name}
|
||||||
</div>
|
</div>
|
||||||
|
@ -33,7 +33,8 @@ export class SideBarItem extends React.Component {
|
|||||||
render() {
|
render() {
|
||||||
const themeStyle = GlobalInfos.getThemeStyle();
|
const themeStyle = GlobalInfos.getThemeStyle();
|
||||||
return (
|
return (
|
||||||
<div className={style.sidebarinfo + ' ' + themeStyle.thirdbackground + ' ' + themeStyle.lighttextcolor}>{this.props.children}</div>
|
<div
|
||||||
|
className={style.sidebarinfo + ' ' + themeStyle.thirdbackground + ' ' + themeStyle.lighttextcolor}>{this.props.children}</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
import SideBar from "./SideBar";
|
import SideBar, {SideBarItem, SideBarTitle} from "./SideBar";
|
||||||
|
|
||||||
import "@testing-library/jest-dom"
|
import "@testing-library/jest-dom"
|
||||||
import {shallow} from "enzyme";
|
import {shallow} from "enzyme";
|
||||||
@ -14,4 +14,14 @@ describe('<SideBar/>', function () {
|
|||||||
const wrapper = shallow(<SideBar>test</SideBar>);
|
const wrapper = shallow(<SideBar>test</SideBar>);
|
||||||
expect(wrapper.children().text()).toBe("test");
|
expect(wrapper.children().text()).toBe("test");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('sidebar Item renders without crashing', function () {
|
||||||
|
const wrapper = shallow(<SideBarItem>Test</SideBarItem>);
|
||||||
|
expect(wrapper.children().text()).toBe("Test");
|
||||||
|
});
|
||||||
|
|
||||||
|
it('renderes sidebartitle correctly', function () {
|
||||||
|
const wrapper = shallow(<SideBarTitle>Test</SideBarTitle>);
|
||||||
|
expect(wrapper.children().text()).toBe("Test");
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
@ -44,6 +44,8 @@ class Player extends React.Component {
|
|||||||
suggesttag: [],
|
suggesttag: [],
|
||||||
popupvisible: false
|
popupvisible: false
|
||||||
};
|
};
|
||||||
|
|
||||||
|
this.quickAddTag = this.quickAddTag.bind(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
@ -56,7 +58,6 @@ class Player extends React.Component {
|
|||||||
* @param tag_name name of tag to add
|
* @param tag_name name of tag to add
|
||||||
*/
|
*/
|
||||||
quickAddTag(tag_id, tag_name) {
|
quickAddTag(tag_id, tag_name) {
|
||||||
// save the tag
|
|
||||||
const updateRequest = new FormData();
|
const updateRequest = new FormData();
|
||||||
updateRequest.append('action', 'addTag');
|
updateRequest.append('action', 'addTag');
|
||||||
updateRequest.append('id', tag_id);
|
updateRequest.append('id', tag_id);
|
||||||
@ -75,6 +76,7 @@ class Player extends React.Component {
|
|||||||
return e.tag_id;
|
return e.tag_id;
|
||||||
}).indexOf(tag_id);
|
}).indexOf(tag_id);
|
||||||
|
|
||||||
|
// check if tag is available in quickadds
|
||||||
if (index !== -1) {
|
if (index !== -1) {
|
||||||
array.splice(index, 1);
|
array.splice(index, 1);
|
||||||
|
|
||||||
@ -82,11 +84,35 @@ class Player extends React.Component {
|
|||||||
tags: [...this.state.tags, {tag_name: tag_name}],
|
tags: [...this.state.tags, {tag_name: tag_name}],
|
||||||
suggesttag: array
|
suggesttag: array
|
||||||
});
|
});
|
||||||
|
} else {
|
||||||
|
this.setState({
|
||||||
|
tags: [...this.state.tags, {tag_name: tag_name}]
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* handle the popovers generated according to state changes
|
||||||
|
* @returns {JSX.Element}
|
||||||
|
*/
|
||||||
|
handlePopOvers() {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{this.state.popupvisible ?
|
||||||
|
<AddTagPopup show={this.state.popupvisible}
|
||||||
|
onHide={() => {
|
||||||
|
this.setState({popupvisible: false});
|
||||||
|
}}
|
||||||
|
submit={this.quickAddTag}
|
||||||
|
movie_id={this.state.movie_id}/> :
|
||||||
|
null
|
||||||
|
}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* generate sidebar with all items
|
* generate sidebar with all items
|
||||||
*/
|
*/
|
||||||
@ -143,19 +169,13 @@ class Player extends React.Component {
|
|||||||
<button className='btn btn-primary' onClick={() => this.likebtn()}>Like this Video!</button>
|
<button className='btn btn-primary' onClick={() => this.likebtn()}>Like this Video!</button>
|
||||||
<button className='btn btn-info' onClick={() => this.setState({popupvisible: true})}>Give this Video a Tag</button>
|
<button className='btn btn-info' onClick={() => this.setState({popupvisible: true})}>Give this Video a Tag</button>
|
||||||
<button className='btn btn-danger' onClick={() =>{this.deleteVideo()}}>Delete Video</button>
|
<button className='btn btn-danger' onClick={() =>{this.deleteVideo()}}>Delete Video</button>
|
||||||
{this.state.popupvisible ?
|
|
||||||
<AddTagPopup show={this.state.popupvisible}
|
|
||||||
onHide={() => {
|
|
||||||
this.setState({popupvisible: false});
|
|
||||||
this.fetchMovieData();
|
|
||||||
}}
|
|
||||||
movie_id={this.state.movie_id}/> :
|
|
||||||
null
|
|
||||||
}
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<button className={style.closebutton} onClick={() => this.closebtn()}>Close</button>
|
<button className={style.closebutton} onClick={() => this.closebtn()}>Close</button>
|
||||||
|
{
|
||||||
|
// handle the popovers switched on and off according to state changes
|
||||||
|
this.handlePopOvers()
|
||||||
|
}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user