Merge branch 'filterbutton' into 'master'

SortBY feature

See merge request lukas/openmediacenter!49
This commit is contained in:
Lukas Heiligenbrunner 2021-06-22 20:15:05 +00:00
commit c93d02ca14
5 changed files with 167 additions and 21 deletions

View File

@ -31,23 +31,46 @@ func getVideoHandlers() {
*/
AddHandler("getMovies", VideoNode, func(info *HandlerInfo) []byte {
var args struct {
Tag int
Tag uint32
Sort uint8
}
if err := FillStruct(&args, info.Data); err != nil {
fmt.Println(err.Error())
return nil
}
const (
date = iota
likes = iota
random = iota
names = iota
)
var SortClause string
switch args.Sort {
case date:
SortClause = "ORDER BY create_date DESC, movie_name"
break
case likes:
SortClause = "ORDER BY likes DESC"
break
case random:
SortClause = "ORDER BY RAND()"
break
case names:
SortClause = "ORDER BY movie_name"
break
}
var query string
// 1 is the id of the ALL tag
if args.Tag != 1 {
query = fmt.Sprintf(`SELECT movie_id,movie_name,t.tag_name FROM videos
INNER JOIN video_tags vt on videos.movie_id = vt.video_id
INNER JOIN tags t on vt.tag_id = t.tag_id
WHERE t.tag_id = %d
ORDER BY likes DESC, create_date, movie_name`, args.Tag)
WHERE t.tag_id = %d %s`, args.Tag, SortClause)
} else {
query = "SELECT movie_id,movie_name, (SELECT 'All' as tag_name) FROM videos ORDER BY create_date DESC, movie_name"
query = fmt.Sprintf("SELECT movie_id,movie_name, (SELECT 'All' as tag_name) FROM videos %s", SortClause)
}
var result struct {

View File

@ -41,18 +41,24 @@ class DynamicContentContainer<T> extends React.Component<Props<T>, state<T>> {
componentDidUpdate(prevProps: Props<T>): void {
// when source props change force update!
if (prevProps.data.length !== this.props.data.length) {
this.clean();
if (
// diff the two arrays
this.props.data
.filter((x) => !prevProps.data.includes(x))
.concat(prevProps.data.filter((x) => !this.props.data.includes(x))).length !== 0
) {
this.clean((): void => {
this.loadPreviewBlock(this.InitialLoadNR);
});
}
}
/**
* clear all elements rendered...
*/
clean(): void {
clean(callback: () => void): void {
this.loadindex = 0;
this.setState({loadeditems: []});
this.setState({loadeditems: []}, callback);
}
render(): JSX.Element {

View File

@ -7,3 +7,61 @@
float: right;
margin-top: 25px;
}
.sortbyLabel {
color: grey;
margin-right: 5px;
margin-left: 25px;
}
/* Style The Dropdown Button */
.dropbtn {
display: flex;
justify-content: center;
align-items: center;
flex-direction: row;
height: 100%;
cursor: pointer;
color: white;
text-align: center;
}
/* The container <div> - needed to position the dropdown content */
.dropdown {
position: relative;
display: inline-block;
}
/* Dropdown Content (Hidden by Default) */
.dropdownContent {
display: none;
position: absolute;
background-color: #f9f9f9;
min-width: 160px;
box-shadow: 0px 8px 16px 0px rgba(0,0,0,0.2);
z-index: 1;
}
/* Links inside the dropdown */
.dropdownContent span {
color: black;
padding: 12px 16px;
text-decoration: none;
display: block;
cursor: pointer;
}
/* Change color of dropdown links on hover */
.dropdownContent span:hover {
background-color: #f1f1f1;
}
/* Show the dropdown menu on hover */
.dropdown:hover .dropdownContent {
display: block;
}
/* Change the background color of the dropdown button when the dropdown content is shown */
.dropdown:hover .dropbtn {
background-color: #3574fe;
}

View File

@ -1,8 +1,10 @@
import {shallow} from 'enzyme';
import React from 'react';
import {HomePage} from './HomePage';
import {HomePage, SortBy} from './HomePage';
import VideoContainer from '../../elements/VideoContainer/VideoContainer';
import {SearchHandling} from './SearchHandling';
import exp from "constants";
import {DefaultTags} from "../../types/GeneralTypes";
describe('<HomePage/>', function () {
it('renders without crashing ', function () {
@ -83,6 +85,20 @@ describe('<HomePage/>', function () {
testBtn(tags.first());
});
it('test sortby type change', function () {
const wrapper = shallow(<HomePage/>);
// expect those default values
expect(wrapper.state().sortby).toBe('Date Added');
expect(wrapper.instance().sortState).toBe(SortBy.date);
expect(wrapper.instance().tagState).toBe(DefaultTags.all);
wrapper.instance().onDropDownItemClick(SortBy.name, 'namesort');
expect(wrapper.state().sortby).toBe('namesort');
expect(wrapper.instance().sortState).toBe(SortBy.name);
});
});
describe('<SearchHandling/>', () => {

View File

@ -11,6 +11,16 @@ import {RouteComponentProps} from 'react-router';
import SearchHandling from './SearchHandling';
import {VideoTypes} from '../../types/ApiTypes';
import {DefaultTags} from '../../types/GeneralTypes';
import {FontAwesomeIcon} from '@fortawesome/react-fontawesome';
import {faSortDown} from '@fortawesome/free-solid-svg-icons';
// eslint-disable-next-line no-shadow
export enum SortBy {
date,
likes,
random,
name
}
interface Props extends RouteComponentProps {}
@ -19,6 +29,7 @@ interface state {
subtitle: string;
data: VideoTypes.VideoUnloadedType[];
selectionnr: number;
sortby: string;
}
/**
@ -42,13 +53,17 @@ export class HomePage extends React.Component<Props, state> {
},
subtitle: 'All Videos',
data: [],
selectionnr: 0
selectionnr: 0,
sortby: 'Date Added'
};
}
sortState = SortBy.date;
tagState = DefaultTags.all;
componentDidMount(): void {
// initial get of all videos
this.fetchVideoData(DefaultTags.all.TagId);
this.fetchVideoData();
this.fetchStartData();
}
@ -58,14 +73,11 @@ export class HomePage extends React.Component<Props, state> {
*
* @param tag tag to fetch videos
*/
fetchVideoData(tag: number): void {
fetchVideoData(): void {
callAPI(
APINode.Video,
{action: 'getMovies', Tag: tag},
{action: 'getMovies', Tag: this.tagState.TagId, Sort: this.sortState},
(result: {Videos: VideoTypes.VideoUnloadedType[]; TagName: string}) => {
this.setState({
data: []
});
this.setState({
data: result.Videos,
selectionnr: result.Videos.length
@ -135,32 +147,52 @@ export class HomePage extends React.Component<Props, state> {
<Tag
tagInfo={{TagName: 'All', TagId: DefaultTags.all.TagId}}
onclick={(): void => {
this.fetchVideoData(DefaultTags.all.TagId);
this.tagState = DefaultTags.all;
this.fetchVideoData();
this.setState({subtitle: 'All Videos'});
}}
/>
<Tag
tagInfo={{TagName: 'Full Hd', TagId: DefaultTags.fullhd.TagId}}
onclick={(): void => {
this.fetchVideoData(DefaultTags.fullhd.TagId);
this.tagState = DefaultTags.fullhd;
this.fetchVideoData();
this.setState({subtitle: 'Full Hd Videos'});
}}
/>
<Tag
tagInfo={{TagName: 'Low Quality', TagId: DefaultTags.lowq.TagId}}
onclick={(): void => {
this.fetchVideoData(DefaultTags.lowq.TagId);
this.tagState = DefaultTags.lowq;
this.fetchVideoData();
this.setState({subtitle: 'Low Quality Videos'});
}}
/>
<Tag
tagInfo={{TagName: 'HD', TagId: DefaultTags.hd.TagId}}
onclick={(): void => {
this.fetchVideoData(DefaultTags.hd.TagId);
this.tagState = DefaultTags.hd;
this.fetchVideoData();
this.setState({subtitle: 'HD Videos'});
}}
/>
</SideBar>
<div>
<span className={style.sortbyLabel}>Sort By: </span>
<div className={style.dropdown}>
<span className={style.dropbtn}>
<span>{this.state.sortby}</span>
<FontAwesomeIcon style={{marginLeft: 3, paddingBottom: 3}} icon={faSortDown} size='1x' />
</span>
<div className={style.dropdownContent}>
<span onClick={(): void => this.onDropDownItemClick(SortBy.date, 'Date Added')}>Date Added</span>
<span onClick={(): void => this.onDropDownItemClick(SortBy.likes, 'Most likes')}>Most likes</span>
<span onClick={(): void => this.onDropDownItemClick(SortBy.random, 'Random')}>Random</span>
<span onClick={(): void => this.onDropDownItemClick(SortBy.name, 'Name')}>Name</span>
</div>
</div>
</div>
<VideoContainer data={this.state.data} />
<div className={style.rightinfo} />
</Route>
@ -168,6 +200,17 @@ export class HomePage extends React.Component<Props, state> {
</>
);
}
/**
* click handler for sortby dropdown item click
* @param type type of sort action
* @param name new header title
*/
onDropDownItemClick(type: SortBy, name: string): void {
this.sortState = type;
this.setState({sortby: name});
this.fetchVideoData();
}
}
export default withRouter(HomePage);