From 76f879a0f28ca505e321227d06966146a289784c Mon Sep 17 00:00:00 2001 From: Lukas Heiligenbrunner Date: Fri, 9 Oct 2020 14:00:51 +0000 Subject: [PATCH] add drag and drop support for addtagpopup esc closes the popup theme style is used --- src/elements/AddTagPopup/AddTagPopup.js | 152 ++++++++++++------ .../AddTagPopup/AddTagPopup.module.css | 26 +++ src/elements/AddTagPopup/AddTagPopup.test.js | 81 ++++++---- src/elements/Preview/Preview.js | 7 +- src/elements/SideBar/SideBar.js | 3 +- src/elements/SideBar/SideBar.test.js | 12 +- src/pages/Player/Player.js | 42 +++-- src/pages/Player/Player.test.js | 4 +- 8 files changed, 235 insertions(+), 92 deletions(-) create mode 100644 src/elements/AddTagPopup/AddTagPopup.module.css diff --git a/src/elements/AddTagPopup/AddTagPopup.js b/src/elements/AddTagPopup/AddTagPopup.js index d6b8a5a..eeb928b 100644 --- a/src/elements/AddTagPopup/AddTagPopup.js +++ b/src/elements/AddTagPopup/AddTagPopup.js @@ -1,27 +1,36 @@ import React from "react"; -import Modal from 'react-bootstrap/Modal' -import Dropdown from "react-bootstrap/Dropdown"; -import DropdownButton from "react-bootstrap/DropdownButton"; +import ReactDom from 'react-dom'; +import style from './AddTagPopup.module.css' +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 */ class AddTagPopup extends React.Component { + /// instance of root element + element; + constructor(props, context) { super(props, context); - this.state = { - selection: { - name: "nothing selected", - id: -1 - }, - items: [] - }; + this.state = {items: []}; + this.handleClickOutside = this.handleClickOutside.bind(this); + this.keypress = this.keypress.bind(this); this.props = props; } 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(); 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() { + const themeStyle = GlobalInfos.getThemeStyle(); return ( - <> - - - - Add to Tag - - - -

Select a Tag:

- - {this.state.items ? - this.state.items.map((i) => ( - { - this.setState({selection: {name: i.tag_name, id: i.tag_id}}) - }}>{i.tag_name} - )) : - loading tags...} - -
- - - -
- +
this.element = el}> +
Add a Tag to this Video:
+ +
+ {this.state.items ? + this.state.items.map((i) => ( + { + this.addTag(i.tag_id, i.tag_name); + }}>{i.tag_name} + )) : null} +
+
); } /** - * 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(); updateRequest.append('action', 'addTag'); - updateRequest.append('id', this.state.selection.id); + updateRequest.append('id', tagid); updateRequest.append('movieid', this.props.movie_id); fetch('/api/tags.php', {method: 'POST', body: updateRequest}) @@ -86,10 +107,51 @@ class AddTagPopup extends React.Component { if (result.result !== "success") { console.log("error occured while writing to db -- todo error handling"); console.log(result.result); + } else { + this.props.submit(tagid, tagname); } 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; diff --git a/src/elements/AddTagPopup/AddTagPopup.module.css b/src/elements/AddTagPopup/AddTagPopup.module.css new file mode 100644 index 0000000..773d661 --- /dev/null +++ b/src/elements/AddTagPopup/AddTagPopup.module.css @@ -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; +} diff --git a/src/elements/AddTagPopup/AddTagPopup.test.js b/src/elements/AddTagPopup/AddTagPopup.test.js index 2feec36..9728dbd 100644 --- a/src/elements/AddTagPopup/AddTagPopup.test.js +++ b/src/elements/AddTagPopup/AddTagPopup.test.js @@ -11,47 +11,68 @@ describe('', function () { wrapper.unmount(); }); - it('test dropdown insertion', function () { + it('test tag insertion', function () { const wrapper = shallow(); - wrapper.setState({items: ["test1", "test2", "test3"]}); - expect(wrapper.find('DropdownItem')).toHaveLength(3); + wrapper.setState({ + 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 => { - const mockSuccessResponse = {}; - const mockJsonPromise = Promise.resolve(mockSuccessResponse); - const mockFetchPromise = Promise.resolve({ - json: () => mockJsonPromise, - }); - global.fetch = jest.fn().mockImplementation(() => mockFetchPromise); - - const func = jest.fn(); - + it('test tag click', function () { const wrapper = shallow(); - wrapper.setProps({ - onHide: () => { - func() - } - }); + wrapper.instance().addTag = jest.fn(); wrapper.setState({ - items: ["test1", "test2", "test3"], - selection: { - name: "test1", - id: 42 - } + items: [{tag_id: 1, tag_name: 'test'}] + }, () => { + wrapper.find('Tag').first().dive().simulate('click'); + expect(wrapper.instance().addTag).toHaveBeenCalledTimes(1); + }); + }); + + it('test addtag', done => { + const wrapper = shallow(); + + global.fetch = prepareFetchApi({result: "success"}); + + wrapper.setProps({ + submit: jest.fn((arg1, arg2) => {}), + onHide: jest.fn() + }, () => { + wrapper.instance().addTag(1, "test"); + + expect(global.fetch).toHaveBeenCalledTimes(1); }); - // first call of fetch is getting of available tags - expect(global.fetch).toHaveBeenCalledTimes(1); - wrapper.find('ModalFooter').find('button').simulate('click'); + process.nextTick(() => { + expect(wrapper.instance().props.submit).toHaveBeenCalledTimes(1); + expect(wrapper.instance().props.onHide).toHaveBeenCalledTimes(1); - // now called 2 times - expect(global.fetch).toHaveBeenCalledTimes(2); + global.fetch.mockClear(); + done(); + }); + }); + + it('test failing addTag', done => { + const wrapper = shallow(); + + 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(() => { - //callback to close window should have called - expect(func).toHaveBeenCalledTimes(1); + expect(wrapper.instance().props.submit).toHaveBeenCalledTimes(0); + expect(wrapper.instance().props.onHide).toHaveBeenCalledTimes(1); global.fetch.mockClear(); done(); diff --git a/src/elements/Preview/Preview.js b/src/elements/Preview/Preview.js index 3c6b1f3..ff3aa8b 100644 --- a/src/elements/Preview/Preview.js +++ b/src/elements/Preview/Preview.js @@ -40,7 +40,8 @@ class Preview extends React.Component { render() { const themeStyle = GlobalInfos.getThemeStyle(); return ( -
this.itemClick()}> +
this.itemClick()}>
{this.state.name}
{this.state.previewpicture !== null ? @@ -77,7 +78,9 @@ export class TagPreview extends React.Component { render() { const themeStyle = GlobalInfos.getThemeStyle(); return ( -
this.itemClick()}> +
this.itemClick()}>
{this.props.name}
diff --git a/src/elements/SideBar/SideBar.js b/src/elements/SideBar/SideBar.js index 15573da..f1c331d 100644 --- a/src/elements/SideBar/SideBar.js +++ b/src/elements/SideBar/SideBar.js @@ -33,7 +33,8 @@ export class SideBarItem extends React.Component { render() { const themeStyle = GlobalInfos.getThemeStyle(); return ( -
{this.props.children}
+
{this.props.children}
); } } diff --git a/src/elements/SideBar/SideBar.test.js b/src/elements/SideBar/SideBar.test.js index 1b8a8b8..ad3b3f8 100644 --- a/src/elements/SideBar/SideBar.test.js +++ b/src/elements/SideBar/SideBar.test.js @@ -1,5 +1,5 @@ import React from "react"; -import SideBar from "./SideBar"; +import SideBar, {SideBarItem, SideBarTitle} from "./SideBar"; import "@testing-library/jest-dom" import {shallow} from "enzyme"; @@ -14,4 +14,14 @@ describe('', function () { const wrapper = shallow(test); expect(wrapper.children().text()).toBe("test"); }); + + it('sidebar Item renders without crashing', function () { + const wrapper = shallow(Test); + expect(wrapper.children().text()).toBe("Test"); + }); + + it('renderes sidebartitle correctly', function () { + const wrapper = shallow(Test); + expect(wrapper.children().text()).toBe("Test"); + }); }); diff --git a/src/pages/Player/Player.js b/src/pages/Player/Player.js index 9799b65..faaa564 100644 --- a/src/pages/Player/Player.js +++ b/src/pages/Player/Player.js @@ -44,6 +44,8 @@ class Player extends React.Component { suggesttag: [], popupvisible: false }; + + this.quickAddTag = this.quickAddTag.bind(this); } componentDidMount() { @@ -56,7 +58,6 @@ class Player extends React.Component { * @param tag_name name of tag to add */ quickAddTag(tag_id, tag_name) { - // save the tag const updateRequest = new FormData(); updateRequest.append('action', 'addTag'); updateRequest.append('id', tag_id); @@ -75,6 +76,7 @@ class Player extends React.Component { return e.tag_id; }).indexOf(tag_id); + // check if tag is available in quickadds if (index !== -1) { array.splice(index, 1); @@ -82,11 +84,35 @@ class Player extends React.Component { tags: [...this.state.tags, {tag_name: tag_name}], 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 ? + { + this.setState({popupvisible: false}); + }} + submit={this.quickAddTag} + movie_id={this.state.movie_id}/> : + null + } + + ); + } + /** * generate sidebar with all items */ @@ -143,19 +169,13 @@ class Player extends React.Component { - {this.state.popupvisible ? - { - this.setState({popupvisible: false}); - this.fetchMovieData(); - }} - movie_id={this.state.movie_id}/> : - null - } -
+ { + // handle the popovers switched on and off according to state changes + this.handlePopOvers() + }
); } diff --git a/src/pages/Player/Player.test.js b/src/pages/Player/Player.test.js index f4b0105..eac9e4e 100644 --- a/src/pages/Player/Player.test.js +++ b/src/pages/Player/Player.test.js @@ -23,7 +23,7 @@ describe('', function () { expect(wrapper.find("r")).toHaveLength(1); }); - function simulateLikeButtonClick(){ + function simulateLikeButtonClick() { const wrapper = shallow(); // initial fetch for getting movie data @@ -166,7 +166,7 @@ describe('', function () { }); }); - function generatetag(){ + function generatetag() { const wrapper = shallow(); expect(wrapper.find("Tag")).toHaveLength(0);