Frontent dev env (#247)

* Added frontend development files/environment

* More items-categories related removals

* Improvements in pages templates (inc. static pages)

* Improvements in video player

* Added empty home page message + cta

* Updates in media, playlist and management pages

* Improvements in material icons font loading

* Replaced media & playlists links in frontend dev-env

* frontend package version update

* chnaged frontend dev url port

* static files update

* Changed default position of theme switcher

* enabled frontend docker container
This commit is contained in:
Yiannis Stergiou
2021-07-11 18:01:34 +03:00
committed by GitHub
parent 060bb45725
commit aa6520daac
555 changed files with 201927 additions and 66002 deletions

View File

@@ -0,0 +1,194 @@
import React, { useRef, useState, useEffect, useCallback } from 'react';
import PropTypes from 'prop-types';
import { usePopup } from '../../../utils/hooks/';
import { PageStore } from '../../../utils/stores/';
import { PopupMain } from '../../_shared';
import { ManageItemDate } from './ManageMediaItem';
function ManageItemCommentAuthor(props) {
if (void 0 !== props.name && void 0 !== props.url) {
return (
<a href={props.url} title={props.name}>
{props.name}
</a>
);
}
if (void 0 !== props.name) {
return props.name;
}
if (void 0 !== props.url) {
return props.url;
}
return <i className="non-available">N/A</i>;
}
function ManageItemCommentActions(props) {
const [popupContentRef, PopupContent, PopupTrigger] = usePopup();
const [isOpenPopup, setIsOpenPopup] = useState(false);
function onPopupShow() {
setIsOpenPopup(true);
}
function onPopupHide() {
setIsOpenPopup(false);
}
function onCancel() {
popupContentRef.current.tryToHide();
if ('function' === typeof props.onCancel) {
props.onCancel();
}
}
function onProceed() {
popupContentRef.current.tryToHide();
if ('function' === typeof props.onProceed) {
props.onProceed();
}
}
const positionState = { updating: false, pending: 0 };
const onWindowResize = useCallback(function () {
if (positionState.updating) {
positionState.pending = positionState.pending + 1;
} else {
positionState.updating = true;
const popupElem = props.containerRef.current.querySelector('.popup');
if (popupElem) {
const containerClientRect = props.containerRef.current.getBoundingClientRect();
popupElem.style.position = 'fixed';
popupElem.style.left = containerClientRect.x + 'px';
if (document.body.offsetHeight < 32 + popupElem.offsetHeight + window.scrollY + containerClientRect.top) {
popupElem.style.top = containerClientRect.y - popupElem.offsetHeight + 'px';
} else {
popupElem.style.top = containerClientRect.y + containerClientRect.height + 'px';
}
}
setTimeout(() => {
positionState.updating = false;
if (positionState.pending) {
positionState.pending = 0;
onWindowResize();
}
}, 8);
}
}, []);
useEffect(() => {
if (isOpenPopup) {
PageStore.on('window_scroll', onWindowResize);
PageStore.on('window_resize', onWindowResize);
onWindowResize();
} else {
PageStore.removeListener('window_scroll', onWindowResize);
PageStore.removeListener('window_resize', onWindowResize);
}
}, [isOpenPopup]);
return (
<div ref={props.containerRef} className="actions">
{void 0 === props.media_url ? null : (
<span>
<a href={props.media_url}>View media</a>
</span>
)}
{void 0 === props.media_url || props.hideDeleteAction ? null : <span className="seperator">|</span>}
<PopupTrigger contentRef={popupContentRef}>
<button title="Delete comment">Delete</button>
</PopupTrigger>
<PopupContent contentRef={popupContentRef} showCallback={onPopupShow} hideCallback={onPopupHide}>
<PopupMain>
<div className="popup-message">
<span className="popup-message-title">Comment removal</span>
<span className="popup-message-main">You're willing to remove comment?</span>
</div>
<hr />
<span className="popup-message-bottom">
<button className="button-link cancel-profile-removal" onClick={onCancel}>
CANCEL
</button>
<button className="button-link proceed-profile-removal" onClick={onProceed}>
PROCEED
</button>
</span>
</PopupMain>
</PopupContent>
</div>
);
}
export function ManageCommentsItem(props) {
const actionsContainerRef = useRef(null);
const [selected, setSelected] = useState(false);
function onRowCheck() {
setSelected(!selected);
}
function onClickProceed() {
if ('function' === typeof props.onProceedRemoval) {
props.onProceedRemoval(props.uid);
}
}
useEffect(() => {
if ('function' === typeof props.onCheckRow) {
props.onCheckRow(props.uid, selected);
}
}, [selected]);
useEffect(() => {
setSelected(props.selectedRow);
}, [props.selectedRow]);
return (
<div className="item manage-item manage-comments-item">
<div className="mi-checkbox">
<input type="checkbox" checked={selected} onChange={onRowCheck} />
</div>
<div className="mi-author">
<ManageItemCommentAuthor name={props.author_name} url={props.author_url} />
</div>
<div className="mi-comment">
{void 0 === props.text ? <i className="non-available">N/A</i> : props.text}
{void 0 === props.text || (void 0 === props.media_url && props.hideDeleteAction) ? null : (
<ManageItemCommentActions
containerRef={actionsContainerRef}
title={props.title}
onProceed={onClickProceed}
media_url={props.media_url}
hideDeleteAction={props.hideDeleteAction}
/>
)}
</div>
<div className="mi-added">
<ManageItemDate date={props.add_date} />
</div>
</div>
);
}
ManageCommentsItem.propTypes = {
author_name: PropTypes.string,
author_url: PropTypes.string,
author_thumbnail_url: PropTypes.string,
add_date: PropTypes.string,
text: PropTypes.string,
selectedRow: PropTypes.bool.isRequired,
hideDeleteAction: PropTypes.bool.isRequired,
uid: PropTypes.string.isRequired,
};

View File

@@ -0,0 +1,55 @@
import React from 'react';
import PropTypes from 'prop-types';
import { useManagementTableHeader } from '../../../utils/hooks/';
import { MaterialIcon } from '../../_shared/material-icon/MaterialIcon';
export function ManageCommentsItemHeader(props) {
const [sort, order, isSelected, sortByColumn, checkAll] = useManagementTableHeader({ ...props, type: 'comments' });
return (
<div className="item manage-item manage-item-header manage-comments-item">
<div className="mi-checkbox">
<input type="checkbox" checked={isSelected} onChange={checkAll} />
</div>
<div className="mi-author">Author</div>
<div
id="text"
onClick={sortByColumn}
className={'mi-comment mi-col-sort' + ('text' === sort ? ('asc' === order ? ' asc' : ' desc') : '')}
>
Comment
<div className="mi-col-sort-icons">
<span>
<MaterialIcon type="arrow_drop_up" />
</span>
<span>
<MaterialIcon type="arrow_drop_down" />
</span>
</div>
</div>
<div
id="add_date"
onClick={sortByColumn}
className={'mi-added mi-col-sort' + ('add_date' === sort ? ('asc' === order ? ' asc' : ' desc') : '')}
>
Date added
<div className="mi-col-sort-icons">
<span>
<MaterialIcon type="arrow_drop_up" />
</span>
<span>
<MaterialIcon type="arrow_drop_down" />
</span>
</div>
</div>
</div>
);
}
ManageCommentsItemHeader.propTypes = {
sort: PropTypes.string.isRequired,
order: PropTypes.string.isRequired,
selected: PropTypes.bool.isRequired,
onClickColumnSort: PropTypes.func,
onCheckAllRows: PropTypes.func,
};

View File

@@ -0,0 +1,256 @@
import React, { useRef, useState, useEffect, useCallback } from 'react';
import PropTypes from 'prop-types';
import { usePopup } from '../../../utils/hooks/usePopup';
import { formatManagementTableDate } from '../../../utils/helpers/';
import { PageStore } from '../../../utils/stores/';
import { PopupMain } from '../../_shared';
import { MaterialIcon } from '../../_shared/material-icon/MaterialIcon';
function ManageItemTitle(props) {
if (void 0 !== props.title && void 0 !== props.url) {
return (
<a href={props.url} title={props.title}>
{props.title}
</a>
);
}
if (void 0 !== props.title) {
return props.title;
}
if (void 0 !== props.url) {
return props.url;
}
return <i className="non-available">N/A</i>;
}
export function ManageItemDate(props) {
if (void 0 !== props.date) {
return formatManagementTableDate(new Date(Date.parse(props.date)));
}
return <i className="non-available">N/A</i>;
}
function ManageItemMediaAuthor(props) {
if (void 0 !== props.name && void 0 !== props.url) {
return (
<a href={props.url} title={props.name}>
{props.name}
</a>
);
}
if (void 0 !== props.name) {
return props.name;
}
if (void 0 !== props.url) {
return props.url;
}
return <i className="non-available">N/A</i>;
}
function ManageItemMediaActions(props) {
const [popupContentRef, PopupContent, PopupTrigger] = usePopup();
const [isOpenPopup, setIsOpenPopup] = useState(false);
function onPopupShow() {
setIsOpenPopup(true);
}
function onPopupHide() {
setIsOpenPopup(false);
}
function onCancel() {
popupContentRef.current.tryToHide();
if ('function' === typeof props.onCancel) {
props.onCancel();
}
}
function onProceed() {
popupContentRef.current.tryToHide();
if ('function' === typeof props.onProceed) {
props.onProceed();
}
}
const positionState = { updating: false, pending: 0 };
const onWindowResize = useCallback(function () {
if (positionState.updating) {
positionState.pending = positionState.pending + 1;
} else {
positionState.updating = true;
const popupElem = props.containerRef.current.querySelector('.popup');
if (popupElem) {
const containerClientRect = props.containerRef.current.getBoundingClientRect();
popupElem.style.position = 'fixed';
popupElem.style.left = containerClientRect.x + 'px';
if (document.body.offsetHeight < 32 + popupElem.offsetHeight + window.scrollY + containerClientRect.top) {
popupElem.style.top = containerClientRect.y - popupElem.offsetHeight + 'px';
} else {
popupElem.style.top = containerClientRect.y + containerClientRect.height + 'px';
}
}
setTimeout(() => {
positionState.updating = false;
if (positionState.pending) {
positionState.pending = 0;
onWindowResize();
}
}, 8);
}
}, []);
useEffect(() => {
if (isOpenPopup) {
PageStore.on('window_scroll', onWindowResize);
PageStore.on('window_resize', onWindowResize);
onWindowResize();
} else {
PageStore.removeListener('window_scroll', onWindowResize);
PageStore.removeListener('window_resize', onWindowResize);
}
}, [isOpenPopup]);
return (
<div ref={props.containerRef} className="actions">
<PopupTrigger contentRef={popupContentRef}>
<button title={'Delete' + (void 0 !== props.title ? ' "' + props.title + '"' : '')}>Delete</button>
</PopupTrigger>
<PopupContent contentRef={popupContentRef} showCallback={onPopupShow} hideCallback={onPopupHide}>
<PopupMain>
<div className="popup-message">
<span className="popup-message-title">Media removal</span>
<span className="popup-message-main">
{"You're willing to remove media" + (void 0 !== props.title ? ' "' + props.title + '"' : '')}?
</span>
</div>
<hr />
<span className="popup-message-bottom">
<button className="button-link cancel-profile-removal" onClick={onCancel}>
CANCEL
</button>
<button className="button-link proceed-profile-removal" onClick={onProceed}>
PROCEED
</button>
</span>
</PopupMain>
</PopupContent>
</div>
);
}
export function ManageMediaItem(props) {
const actionsContainerRef = useRef(null);
const [selected, setSelected] = useState(false);
function onRowCheck() {
setSelected(!selected);
}
function onClickProceed() {
if ('function' === typeof props.onProceedRemoval) {
props.onProceedRemoval(props.token);
}
}
useEffect(() => {
if ('function' === typeof props.onCheckRow) {
props.onCheckRow(props.token, selected);
}
}, [selected]);
useEffect(() => {
setSelected(props.selectedRow);
}, [props.selectedRow]);
return (
<div className="item manage-item manage-media-item">
<div className="mi-checkbox">
<input type="checkbox" checked={selected} onChange={onRowCheck} />
</div>
<div className="mi-title">
<ManageItemTitle title={props.title} url={props.url} />
{props.hideDeleteAction ? null : (
<ManageItemMediaActions containerRef={actionsContainerRef} title={props.title} onProceed={onClickProceed} />
)}
</div>
<div className="mi-added">
<ManageItemDate date={props.add_date} />
</div>
<div className="mi-author">
<ManageItemMediaAuthor name={props.author_name} url={props.author_url} />
</div>
<div className="mi-type">
{void 0 === props.media_type ? <i className="non-available">N/A</i> : props.media_type}
</div>
<div className="mi-encoding">
{void 0 === props.encoding_status ? <i className="non-available">N/A</i> : props.encoding_status}
</div>
<div className="mi-state">{void 0 === props.state ? <i className="non-available">N/A</i> : props.state}</div>
<div className="mi-reviewed">
{void 0 === props.is_reviewed ? (
<i className="non-available">N/A</i>
) : props.is_reviewed ? (
<MaterialIcon type="check_circle" />
) : (
<MaterialIcon type="cancel" />
)}
</div>
<div className="mi-featured">
{void 0 === props.featured ? (
<i className="non-available">N/A</i>
) : props.featured ? (
<MaterialIcon type="check_circle" />
) : (
'-'
)}
</div>
<div className="mi-reported">
{void 0 === props.reported_times ? (
<i className="non-available">N/A</i>
) : 0 === props.reported_times ? (
<span>-</span>
) : (
<span className="reported-number">
{props.reported_times} {'time' + (1 < props.reported_times ? 's' : '')}
</span>
)}
</div>
</div>
);
}
ManageMediaItem.propTypes = {
thumbnail_url: PropTypes.string,
token: PropTypes.string,
title: PropTypes.string,
url: PropTypes.string,
author_name: PropTypes.string,
author_url: PropTypes.string,
add_date: PropTypes.string,
media_type: PropTypes.string,
encoding_status: PropTypes.string,
state: PropTypes.string,
is_reviewed: PropTypes.bool,
featured: PropTypes.bool,
reported_times: PropTypes.number,
onCheckRow: PropTypes.func,
selectedRow: PropTypes.bool.isRequired,
hideDeleteAction: PropTypes.bool.isRequired,
};

View File

@@ -0,0 +1,61 @@
import React from 'react';
import PropTypes from 'prop-types';
import { useManagementTableHeader } from '../../../utils/hooks/';
import { MaterialIcon } from '../../_shared/material-icon/MaterialIcon.jsx';
export function ManageMediaItemHeader(props) {
const [sort, order, isSelected, sortByColumn, checkAll] = useManagementTableHeader({ ...props, type: 'media' });
return (
<div className="item manage-item manage-item-header manage-media-item">
<div className="mi-checkbox">
<input type="checkbox" checked={isSelected} onChange={checkAll} />
</div>
<div
id="title"
onClick={sortByColumn}
className={'mi-title mi-col-sort' + ('title' === sort ? ('asc' === order ? ' asc' : ' desc') : '')}
>
Title
<div className="mi-col-sort-icons">
<span>
<MaterialIcon type="arrow_drop_up" />
</span>
<span>
<MaterialIcon type="arrow_drop_down" />
</span>
</div>
</div>
<div
id="add_date"
onClick={sortByColumn}
className={'mi-added mi-col-sort' + ('add_date' === sort ? ('asc' === order ? ' asc' : ' desc') : '')}
>
Date added
<div className="mi-col-sort-icons">
<span>
<MaterialIcon type="arrow_drop_up" />
</span>
<span>
<MaterialIcon type="arrow_drop_down" />
</span>
</div>
</div>
<div className="mi-author">Author</div>
<div className="mi-type">Media type</div>
<div className="mi-encoding">Encoding status</div>
<div className="mi-state">State</div>
<div className="mi-reviewed">Reviewed</div>
<div className="mi-featured">Featured</div>
<div className="mi-reported">Reported</div>
</div>
);
}
ManageMediaItemHeader.propTypes = {
sort: PropTypes.string.isRequired,
order: PropTypes.string.isRequired,
selected: PropTypes.bool.isRequired,
onClickColumnSort: PropTypes.func,
onCheckAllRows: PropTypes.func,
};

View File

@@ -0,0 +1,251 @@
import React, { useRef, useState, useEffect, useCallback } from 'react';
import PropTypes from 'prop-types';
import { usePopup } from '../../../utils/hooks/';
import { PageStore } from '../../../utils/stores/';
import { PopupMain } from '../../_shared';
import { MaterialIcon } from '../../_shared/material-icon/MaterialIcon.jsx';
import { ManageItemDate } from './ManageMediaItem';
function ManageItemName(props) {
if (void 0 !== props.url) {
if (null !== props.name && '' !== props.name) {
return (
<a href={props.url} title={props.name}>
{props.name}
</a>
);
}
} else if (null !== props.name && '' !== props.name) {
return props.name;
}
return <i className="non-available">N/A</i>;
}
function ManageItemUsername(props) {
if (void 0 !== props.url) {
if (null !== props.username && '' !== props.username) {
return (
<a href={props.url} title={props.username}>
{props.username}
</a>
);
}
} else if (null !== props.username && '' !== props.username) {
return props.username;
}
return <i className="non-available">N/A</i>;
}
function ManageItemCommentActions(props) {
const [popupContentRef, PopupContent, PopupTrigger] = usePopup();
const [isOpenPopup, setIsOpenPopup] = useState(false);
function onPopupShow() {
setIsOpenPopup(true);
}
function onPopupHide() {
setIsOpenPopup(false);
}
function onCancel() {
popupContentRef.current.tryToHide();
if ('function' === typeof props.onCancel) {
props.onCancel();
}
}
function onProceed() {
popupContentRef.current.tryToHide();
if ('function' === typeof props.onProceed) {
props.onProceed();
}
}
const positionState = { updating: false, pending: 0 };
const onWindowResize = useCallback(function () {
if (positionState.updating) {
positionState.pending = positionState.pending + 1;
} else {
positionState.updating = true;
const popupElem = props.containerRef.current.querySelector('.popup');
if (popupElem) {
const containerClientRect = props.containerRef.current.getBoundingClientRect();
popupElem.style.position = 'fixed';
popupElem.style.left = containerClientRect.x + 'px';
if (document.body.offsetHeight < 32 + popupElem.offsetHeight + window.scrollY + containerClientRect.top) {
popupElem.style.top = containerClientRect.y - popupElem.offsetHeight + 'px';
} else {
popupElem.style.top = containerClientRect.y + containerClientRect.height + 'px';
}
}
setTimeout(() => {
positionState.updating = false;
if (positionState.pending) {
positionState.pending = 0;
onWindowResize();
}
}, 8);
}
}, []);
useEffect(() => {
if (isOpenPopup) {
PageStore.on('window_scroll', onWindowResize);
PageStore.on('window_resize', onWindowResize);
onWindowResize();
} else {
PageStore.removeListener('window_scroll', onWindowResize);
PageStore.removeListener('window_resize', onWindowResize);
}
}, [isOpenPopup]);
return (
<div ref={props.containerRef} className="actions">
<PopupTrigger contentRef={popupContentRef}>
<button title={'Delete "' + props.name + '"'}>Delete</button>
</PopupTrigger>
<PopupContent contentRef={popupContentRef} showCallback={onPopupShow} hideCallback={onPopupHide}>
<PopupMain>
<div className="popup-message">
<span className="popup-message-title">Member removal</span>
<span className="popup-message-main">{'You\'re willing to remove member "' + props.name + '"'}?</span>
</div>
<hr />
<span className="popup-message-bottom">
<button className="button-link cancel-profile-removal" onClick={onCancel}>
CANCEL
</button>
<button className="button-link proceed-profile-removal" onClick={onProceed}>
PROCEED
</button>
</span>
</PopupMain>
</PopupContent>
</div>
);
}
export function ManageUsersItem(props) {
const actionsContainerRef = useRef(null);
const [selected, setSelected] = useState(false);
function onRowCheck() {
setSelected(!selected);
}
function onClickProceed() {
if ('function' === typeof props.onProceedRemoval) {
props.onProceedRemoval(props.username);
}
}
useEffect(() => {
if ('function' === typeof props.onCheckRow) {
props.onCheckRow(props.username, selected);
}
}, [selected]);
useEffect(() => {
setSelected(props.selectedRow);
}, [props.selectedRow]);
return (
<div className="item manage-item manage-users-item">
<div className="mi-checkbox">
<input type="checkbox" checked={selected} onChange={onRowCheck} />
</div>
<div className="mi-name">
<ManageItemName name={props.name} url={props.url} />
<ManageItemCommentActions
containerRef={actionsContainerRef}
name={props.name || props.username}
onProceed={onClickProceed}
/>
</div>
<div className="mi-username">
<ManageItemUsername username={props.username} url={props.url} />
</div>
<div className="mi-added">
<ManageItemDate date={props.add_date} />
</div>
{props.has_roles ? (
<div className="mi-role">
{void 0 === props.roles ? (
<i className="non-available">N/A</i>
) : props.roles.length ? (
props.roles.join('\n')
) : (
'-'
)}
</div>
) : null}
{props.has_verified ? (
<div className="mi-verified">
{void 0 === props.is_verified ? (
<i className="non-available">N/A</i>
) : props.is_verified ? (
<MaterialIcon type="check_circle" />
) : (
'-'
)}
</div>
) : null}
{props.has_trusted ? (
<div className="mi-trusted">
{void 0 === props.is_trusted ? (
<i className="non-available">N/A</i>
) : props.is_trusted ? (
<MaterialIcon type="check_circle" />
) : (
'-'
)}
</div>
) : null}
<div className="mi-featured">
{void 0 === props.is_featured ? (
<i className="non-available">N/A</i>
) : props.is_featured ? (
<MaterialIcon type="check_circle" />
) : (
'-'
)}
</div>
</div>
);
}
ManageUsersItem.propTypes = {
thumbnail_url: PropTypes.string,
name: PropTypes.string,
url: PropTypes.string,
username: PropTypes.string,
add_date: PropTypes.string,
is_featured: PropTypes.bool,
onCheckRow: PropTypes.func,
selectedRow: PropTypes.bool.isRequired,
hideDeleteAction: PropTypes.bool.isRequired,
has_roles: PropTypes.bool,
has_verified: PropTypes.bool,
has_trusted: PropTypes.bool,
roles: PropTypes.array,
is_verified: PropTypes.bool,
is_trusted: PropTypes.bool,
};
ManageUsersItem.defaultProps = {
has_roles: false,
has_verified: false,
has_trusted: false,
};

View File

@@ -0,0 +1,68 @@
import React from 'react';
import PropTypes from 'prop-types';
import { useManagementTableHeader } from '../../../utils/hooks/';
import { MaterialIcon } from '../../_shared/material-icon/MaterialIcon';
export function ManageUsersItemHeader(props) {
const [sort, order, isSelected, sortByColumn, checkAll] = useManagementTableHeader({ ...props, type: 'users' });
return (
<div className="item manage-item manage-item-header manage-users-item">
<div className="mi-checkbox">
<input type="checkbox" checked={isSelected} onChange={checkAll} />
</div>
<div
id="name"
onClick={sortByColumn}
className={'mi-name mi-col-sort' + ('name' === sort ? ('asc' === order ? ' asc' : ' desc') : '')}
>
Name
<div className="mi-col-sort-icons">
<span>
<MaterialIcon type="arrow_drop_up" />
</span>
<span>
<MaterialIcon type="arrow_drop_down" />
</span>
</div>
</div>
<div className="mi-username">Username</div>
<div
id="add_date"
onClick={sortByColumn}
className={'mi-added mi-col-sort' + ('add_date' === sort ? ('asc' === order ? ' asc' : ' desc') : '')}
>
Date added
<div className="mi-col-sort-icons">
<span>
<MaterialIcon type="arrow_drop_up" />
</span>
<span>
<MaterialIcon type="arrow_drop_down" />
</span>
</div>
</div>
{props.has_roles ? <div className="mi-role">Role</div> : null}
{props.has_verified ? <div className="mi-verified">Verified</div> : null}
{props.has_trusted ? <div className="mi-trusted">Trusted</div> : null}
<div className="mi-featured">Featured</div>
</div>
);
}
ManageUsersItemHeader.propTypes = {
sort: PropTypes.string.isRequired,
order: PropTypes.string.isRequired,
selected: PropTypes.bool.isRequired,
onClickColumnSort: PropTypes.func,
onCheckAllRows: PropTypes.func,
has_roles: PropTypes.bool,
has_verified: PropTypes.bool,
has_trusted: PropTypes.bool,
};
ManageUsersItemHeader.defaultProps = {
has_roles: false,
has_verified: false,
has_trusted: false,
};

View File

@@ -0,0 +1,201 @@
@use "sass:math";
@import '../../../css/includes/_variables.scss';
@import '../../../css/includes/_variables_dimensions.scss';
@import '../../../css/config/index.scss';
.mi-filters-row {
position: relative;
display: block;
overflow: hidden;
transition-property: all;
transition-duration: 0.2s;
&.hidden {
height: 0px !important;
}
}
.mi-filters-row-inner {
position: relative;
display: block;
padding-bottom: 8px;
margin-bottom: 24px;
border-style: solid;
border-width: 0 0 1px;
border-color: var(--sidebar-nav-border-color);
.mi-filter {
position: relative;
display: inline-block;
vertical-align: top;
width: 100%;
margin-bottom: 24px;
@media (min-width: 480px) {
width: 50%;
&:nth-child(2n + 1) {
padding-left: 0;
padding-right: 16px;
}
&:nth-child(2n + 2) {
padding-left: 16px;
padding-right: 0;
}
}
@media (min-width: 768px) {
width: math.div(1,3) * 100%;
&:nth-child(3n + 1) {
padding-left: 0;
padding-right: 21px;
}
&:nth-child(3n + 2) {
padding-left: 11px;
padding-right: 11px;
}
&:nth-child(3n + 3) {
padding-left: 21px;
padding-right: 0;
}
}
@media (min-width: 1024px) {
width: 20%;
&:nth-child(3n + 1),
&:nth-child(3n + 2),
&:nth-child(3n + 3) {
padding-left: 0;
padding-right: 0;
}
&:nth-child(5n + 1) {
padding-left: 0;
padding-right: 32px;
}
&:nth-child(5n + 2) {
padding-right: 24px;
}
&:nth-child(5n + 3) {
padding-left: 8px;
padding-right: 8px;
}
&:nth-child(5n + 4) {
padding-left: 24px;
}
&:nth-child(5n + 5) {
padding-left: 32px;
padding-right: 0;
}
}
}
.mi-filter-title {
padding: 4px 0 16px 0;
font-size: 13px;
font-weight: 500;
letter-spacing: 0.007px;
margin-bottom: 4px;
border-style: solid;
border-width: 0 0 1px;
border-color: var(--sidebar-nav-border-color);
}
.mi-filter-options {
position: relative;
display: block;
> * {
display: block;
margin-top: 8px;
button {
display: inline-block;
padding: 3px 6px 4px 0;
// line-height:16px;
line-height: 1.5;
text-align: initial;
color: var(--header-circle-button-color);
border: 0;
background: none;
opacity: 0.85;
.dark_theme & {
opacity: 0.5;
}
span {
display: inline-block;
}
.material-icons {
display: inline-block;
vertical-align: top;
padding: 1px 0 0;
margin: 0 0 0 4px;
font-size: 1em;
line-height: 1.45;
}
}
&.active button,
button:hover {
color: inherit;
opacity: 1;
}
}
}
}
.mi-filters-toggle {
position: absolute;
top: 12px;
right: 0;
button {
vertical-align: middle;
height: 40px;
line-height: 40px;
margin: 2px 0;
font-size: 14px;
font-weight: 500;
letter-spacing: 0.007px;
color: var(--header-circle-button-color);
border: 0;
background: none;
opacity: 0.85;
.dark_theme & {
opacity: 0.5;
}
&.active,
&:hover {
color: inherit;
opacity: 1;
}
}
.material-icons {
margin-top: -2px;
margin-right: 8px;
}
.filter-button-label {
display: inline-block;
}
.filter-button-label-text {
}
}

View File

@@ -0,0 +1,657 @@
import React, { useRef, useState, useEffect } from 'react';
import PropTypes from 'prop-types';
import urlParse from 'url-parse';
import { deleteRequest, csrfToken } from '../../../utils/helpers/';
import { usePopup } from '../../../utils/hooks/';
import { PopupMain } from '../../_shared';
import { PendingItemsList } from '../../item-list/PendingItemsList.jsx';
import { renderManageItems } from './includes/functions';
import initManageItemsList from './includes/initManageItemsList';
import { ManageItemsListHandler } from './includes/ManageItemsListHandler';
import './ManageItemList.scss';
function useManageItemList(props, itemsListRef) {
let previousItemsLength = 0;
let itemsListInstance = null;
const [items, setItems] = useState([]);
const [countedItems, setCountedItems] = useState(false);
const [listHandler, setListHandler] = useState(null);
function onItemsLoad(itemsArray) {
setItems([...itemsArray]);
}
function onItemsCount(totalItems) {
setCountedItems(true);
if (void 0 !== props.itemsCountCallback) {
props.itemsCountCallback(totalItems);
}
}
function addListItems() {
if (previousItemsLength < items.length) {
if (null === itemsListInstance) {
itemsListInstance = initManageItemsList([itemsListRef.current])[0];
}
// FIXME: Should get item elements from children components.
const itemsElem = itemsListRef.current.querySelectorAll('.item');
if (!itemsElem || !itemsElem.length) {
return;
}
let i = previousItemsLength;
while (i < items.length) {
itemsListInstance.appendItems(itemsElem[i]);
i += 1;
}
previousItemsLength = items.length;
}
}
useEffect(() => {
if (void 0 !== props.itemsLoadCallback) {
props.itemsLoadCallback();
}
}, [items]);
return [items, countedItems, listHandler, setListHandler, onItemsLoad, onItemsCount, addListItems];
}
function useManageItemListSync(props) {
const itemsListRef = useRef(null);
const itemsListWrapperRef = useRef(null);
const [items, countedItems, listHandler, setListHandler, onItemsLoad, onItemsCount, addListItems] = useManageItemList(
{ ...props, itemsCountCallback },
itemsListRef
);
const [totalItems, setTotalItems] = useState(null);
let classname = {
list: 'manage-items-list',
listOuter: 'items-list-outer' + ('string' === typeof props.className ? ' ' + props.className.trim() : ''),
};
function onClickLoadMore() {
listHandler.loadItems();
}
function itemsCountCallback(itemsSumm) {
setTotalItems(itemsSumm);
}
function afterItemsLoad() {}
function renderBeforeListWrap() {
return null;
}
function renderAfterListWrap() {
if (!listHandler) {
return null;
}
return 1 > listHandler.totalPages() || listHandler.loadedAllItems() ? null : (
<button className="load-more" onClick={onClickLoadMore}>
SHOW MORE
</button>
);
}
useEffect(() => {
addListItems();
afterItemsLoad();
}, [items]);
return [
countedItems,
totalItems,
items,
listHandler,
setListHandler,
classname,
itemsListWrapperRef,
itemsListRef,
onItemsCount,
onItemsLoad,
renderBeforeListWrap,
renderAfterListWrap,
];
}
function pageUrlQuery(baseQuery, pageNumber) {
let queryParams = [];
let pos = 0;
if ('' !== baseQuery) {
queryParams = baseQuery.split('?')[1].split('&');
let param;
let i = 0;
while (i < queryParams.length) {
param = queryParams[i].split('=');
if ('page' === param[0]) {
pos = i;
break;
}
i += 1;
}
}
queryParams[pos] = 'page=' + pageNumber;
return '?' + queryParams.join('&');
}
function pageUrl(parsedUrl, query) {
return parsedUrl.set('query', query).href;
}
function BulkActions(props) {
const [popupContentRef, PopupContent, PopupTrigger] = usePopup();
const [selectedBulkAction, setSelectedBulkAction] = useState('');
const [selectedItemsSize, setSelectedItemsSize] = useState(props.selectedItemsSize);
function onBulkActionSelect(ev) {
setSelectedBulkAction(ev.currentTarget.value);
}
function onClickProceed() {
if ('function' === typeof props.onProceedRemoval) {
props.onProceedRemoval();
}
popupContentRef.current.tryToHide();
}
function onClickCancel() {
popupContentRef.current.tryToHide();
}
useEffect(() => {
setSelectedItemsSize(props.selectedItemsSize);
}, [props.selectedItemsSize]);
return (
<div className="manage-items-bulk-action">
<select value={selectedBulkAction} onChange={onBulkActionSelect}>
<option value="">Bulk actions</option>
<option value="delete">Delete selected</option>
</select>
{!selectedItemsSize || !selectedBulkAction ? null : (
<PopupTrigger contentRef={popupContentRef}>
<button>Apply</button>
</PopupTrigger>
)}
<PopupContent contentRef={popupContentRef}>
<PopupMain>
<div className="popup-message">
<span className="popup-message-title">Bulk removal</span>
<span className="popup-message-main">You're willing to remove selected items permanently?</span>
</div>
<hr />
<span className="popup-message-bottom">
<button className="button-link cancel-profile-removal" onClick={onClickCancel}>
CANCEL
</button>
<button className="button-link proceed-profile-removal" onClick={onClickProceed}>
PROCEED
</button>
</span>
</PopupMain>
</PopupContent>
</div>
);
}
function ManageItemsOptions(props) {
return (
<div className={props.className}>
<BulkActions selectedItemsSize={props.items.length} onProceedRemoval={props.onProceedRemoval} />
{1 === props.pagesSize ? null : (
<div className="manage-items-pagination">
<PaginationButtons
totalItems={props.totalItems}
pageItems={props.pageItems}
onPageButtonClick={props.onPageButtonClick}
query={props.query}
/>
</div>
)}
</div>
);
}
function PaginationButtons(props) {
const buttons = [];
let i;
let maxPagin = 11;
const newPagesNumber = {
last: Math.ceil(props.totalItems / props.pageItems),
current: 1,
};
if ('' !== props.query) {
const queryParams = props.query.split('?')[1].split('&');
let param;
let i = 0;
while (i < queryParams.length) {
param = queryParams[i].split('=');
if ('page' === param[0]) {
newPagesNumber.current = parseInt(param[1], 10);
break;
}
i += 1;
}
}
const paginButtonsData = paginationButtonsList(maxPagin, newPagesNumber);
i = 0;
while (i < paginButtonsData.length) {
if ('button' === paginButtonsData[i].type) {
buttons.push(
<button
key={i + '[button]'}
onClick={props.onPageButtonClick}
page={paginButtonsData[i].number}
className={newPagesNumber.current === paginButtonsData[i].number ? 'active' : ''}
>
{paginButtonsData[i].number}
</button>
);
} else if ('dots' === paginButtonsData[i].type) {
buttons.push(
<span key={i + '[dots]'} className="pagination-dots">
...
</span>
);
}
i += 1;
}
return buttons;
}
function paginationButtonsList(maxPagin, pagesNumber) {
if (3 > maxPagin) {
maxPagin = 3;
}
let i;
let maxCurr;
let maxEdge = 1;
if (maxPagin >= pagesNumber.last) {
maxPagin = pagesNumber.last;
maxCurr = pagesNumber.last;
maxEdge = 0;
} else {
if (5 < maxPagin) {
if (7 >= maxPagin) {
maxEdge = 2;
} else {
maxEdge = Math.floor(maxPagin / 4);
}
}
maxCurr = maxPagin - 2 * maxEdge;
}
const currentArr = [];
const firstArr = [];
const lastArr = [];
if (pagesNumber.current <= maxCurr + maxEdge - pagesNumber.current) {
i = 1;
while (i <= maxCurr + maxEdge) {
currentArr.push(i);
i += 1;
}
i = pagesNumber.last - maxPagin + currentArr.length + 1;
while (i <= pagesNumber.last) {
lastArr.push(i);
i += 1;
}
} else if (pagesNumber.current > pagesNumber.last - (maxCurr + maxEdge - 1)) {
i = pagesNumber.last - (maxCurr + maxEdge - 1);
while (i <= pagesNumber.last) {
currentArr.push(i);
i += 1;
}
i = 1;
while (i <= maxPagin - currentArr.length) {
firstArr.push(i);
i += 1;
}
} else {
currentArr.push(pagesNumber.current);
i = 1;
while (maxCurr > currentArr.length) {
currentArr.push(pagesNumber.current + i);
if (maxCurr === currentArr.length) {
break;
}
currentArr.unshift(pagesNumber.current - i);
i += 1;
}
i = 1;
while (i <= maxEdge) {
firstArr.push(i);
i += 1;
}
i = pagesNumber.last - (maxPagin - (firstArr.length + currentArr.length) - 1);
while (i <= pagesNumber.last) {
lastArr.push(i);
i += 1;
}
}
const ret = [];
i = 0;
while (i < firstArr.length) {
ret.push({
type: 'button',
number: firstArr[i],
});
i += 1;
}
if (firstArr.length && currentArr.length && firstArr[firstArr.length - 1] + 1 < currentArr[0]) {
ret.push({
type: 'dots',
});
}
i = 0;
while (i < currentArr.length) {
ret.push({
type: 'button',
number: currentArr[i],
});
i += 1;
}
if (currentArr.length && lastArr.length && currentArr[currentArr.length - 1] + 1 < lastArr[0]) {
ret.push({
type: 'dots',
});
}
i = 0;
while (i < lastArr.length) {
ret.push({
type: 'button',
number: lastArr[i],
});
i += 1;
}
return ret;
}
export function ManageItemList(props) {
const [
countedItems,
totalItems,
items,
listHandler,
setListHandler,
classname,
itemsListWrapperRef,
itemsListRef,
onItemsCount,
onItemsLoad,
] = useManageItemListSync(props);
const [selectedItems, setSelectedItems] = useState([]);
const [selectedAllItems, setSelectedAllItems] = useState(false);
const [parsedRequestUrl, setParsedRequestUrl] = useState(null);
const [parsedRequestUrlQuery, setParsedRequestUrlQuery] = useState(null);
function onPageButtonClick(ev) {
const clickedPageUrl = pageUrl(
parsedRequestUrl,
pageUrlQuery(parsedRequestUrlQuery, ev.currentTarget.getAttribute('page'))
);
if ('function' === typeof props.onPageChange) {
props.onPageChange(clickedPageUrl, ev.currentTarget.getAttribute('page'));
}
}
function onBulkItemsRemoval() {
deleteSelectedItems();
}
function onAllRowsCheck(selectedAllRows, tableType) {
const newSelected = [];
if (selectedAllRows) {
if (items.length !== selectedItems.length) {
let entry;
if ('media' === tableType) {
for (entry of items) {
newSelected.push(entry.friendly_token);
}
} else if ('users' === tableType) {
for (entry of items) {
newSelected.push(entry.username);
}
} else if ('comments' === tableType) {
for (entry of items) {
newSelected.push(entry.uid);
}
}
}
}
setSelectedItems(newSelected);
setSelectedAllItems(newSelected.length === items.length);
}
function onRowCheck(token, isSelected) {
if (void 0 !== token) {
let newSelected;
if (-1 === selectedItems.indexOf(token)) {
if (isSelected) {
newSelected = [...selectedItems, token];
setSelectedItems(newSelected);
setSelectedAllItems(newSelected.length === items.length);
}
} else {
if (!isSelected) {
newSelected = [];
let entry;
for (entry of selectedItems) {
if (token !== entry) {
newSelected.push(entry);
}
}
setSelectedItems(newSelected);
setSelectedAllItems(newSelected.length === items.length);
}
}
}
}
function removeBulkMediaResponse(response) {
if (response && 204 === response.status) {
setSelectedItems([]);
setSelectedAllItems(false);
if ('function' === typeof props.onRowsDelete) {
props.onRowsDelete(true);
}
}
}
function removeBulkMediaFail() {
if ('function' === typeof props.onRowsDeleteFail) {
props.onRowsDeleteFail(true);
}
}
function deleteItem(token, isManageComments) {
deleteRequest(
props.requestUrl.split('?')[0] + ('comments' === props.manageType ? '?comment_ids=' : '?tokens=') + token,
{
headers: {
'X-CSRFToken': csrfToken(),
},
tokens: token,
},
false,
removeMediaResponse,
removeMediaFail
);
}
function deleteSelectedItems() {
deleteRequest(
props.requestUrl.split('?')[0] +
('comments' === props.manageType ? '?comment_ids=' : '?tokens=') +
selectedItems.join(','),
{
headers: {
'X-CSRFToken': csrfToken(),
},
},
false,
removeBulkMediaResponse,
removeBulkMediaFail
);
}
function removeMediaResponse(response) {
if (response && 204 === response.status) {
props.onRowsDelete(false);
}
}
function removeMediaFail() {
props.onRowsDeleteFail(false);
}
useEffect(() => {
if (parsedRequestUrl) {
setParsedRequestUrlQuery(parsedRequestUrl.query);
}
}, [parsedRequestUrl]);
useEffect(() => {
setParsedRequestUrl(urlParse(props.requestUrl));
}, [props.requestUrl]);
useEffect(() => {
setListHandler(
new ManageItemsListHandler(props.pageItems, props.maxItems, props.requestUrl, onItemsCount, onItemsLoad)
);
return () => {
if (listHandler) {
listHandler.cancelAll();
setListHandler(null);
}
};
}, []);
return !countedItems ? (
<PendingItemsList className={classname.listOuter} />
) : !items.length ? null : (
<div className={classname.listOuter}>
<ManageItemsOptions
totalItems={totalItems}
pageItems={props.pageItems}
onPageButtonClick={onPageButtonClick}
query={parsedRequestUrlQuery || ''}
className="manage-items-options"
items={selectedItems}
pagesSize={listHandler.totalPages()}
onProceedRemoval={onBulkItemsRemoval}
/>
<div ref={itemsListWrapperRef} className="items-list-wrap">
<div ref={itemsListRef} className={classname.list}>
{renderManageItems(items, {
...props,
onAllRowsCheck: onAllRowsCheck,
onRowCheck: onRowCheck,
selectedItems: selectedItems,
selectedAllItems: selectedAllItems,
onDelete: deleteItem,
})}
</div>
</div>
<ManageItemsOptions
totalItems={totalItems}
pageItems={props.pageItems}
onPageButtonClick={onPageButtonClick}
query={parsedRequestUrlQuery || ''}
className="manage-items-options popup-on-top"
items={selectedItems}
pagesSize={listHandler.totalPages()}
onProceedRemoval={onBulkItemsRemoval}
/>
</div>
);
}
ManageItemList.defaultProps = {
itemsCountCallback: PropTypes.func,
maxItems: PropTypes.number.isRequired,
pageItems: PropTypes.number.isRequired,
requestUrl: PropTypes.string.isRequired,
onPageChange: PropTypes.func,
onRowsDelete: PropTypes.func,
onRowsDeleteFail: PropTypes.func,
pageItems: 24,
};
ManageItemList.defaultProps = {
maxItems: 99999,
pageItems: 24,
requestUrl: null,
};

View File

@@ -0,0 +1,666 @@
@use "sass:math";
@import '../../../../css/includes/_variables.scss';
@import '../../../../css/includes/_variables_dimensions.scss';
@import '../../../../css/config/index.scss';
#page-manage-media,
#page-manage-users,
#page-manage-comments {
.media-list-wrapper {
padding: 0 16px;
@media (min-width: 710px) {
padding: 0 24px;
}
max-width: calc(48px + calc(var(--default-item-width) * var(--default-max-row-items)));
}
.manage-items-list {
overflow: auto;
}
.items-list-outer {
position: relative;
display: block;
}
.items-list-wrap {
position: relative;
display: inline-block;
width: 100%;
min-height: 0;
}
}
.media-list-header {
display: block;
padding: 12px 0;
h2,
h3 {
display: inline-block;
margin: 12px 0;
font-weight: 500;
}
h2 {
font-size: 16px;
line-height: 1.25;
}
h3 {
font-size: 14px;
a {
margin: 10px 16px;
text-decoration: none;
color: var(--media-list-header-title-link-text-color);
}
}
}
.manage-items-list {
display: block;
width: 100%;
margin-bottom: 24px;
word-break: break-word;
border-radius: 1px;
box-shadow: 0px 4px 8px 0 rgba(17, 17, 17, 0.06);
overflow: auto;
a {
text-decoration: none;
&:hover {
text-decoration: underline;
}
}
}
.media-list-header {
display: block;
padding: 12px 0;
h2,
h3 {
display: inline-block;
margin: 12px 0;
font-weight: 500;
}
h2 {
font-size: 16px;
line-height: 1.25;
}
h3 {
font-size: 14px;
a {
margin: 10px 16px;
text-decoration: none;
color: var(--media-list-header-title-link-text-color);
}
}
}
.manage-item,
.item.manage-item {
position: relative;
display: table;
width: 100%;
max-width: 100%;
margin: 0;
border-style: solid;
border-width: 0 0 1px;
border-color: #f0f0f0;
background-color: var(--user-action-form-inner-bg-color);
.dark_theme & {
border-color: #2d2d2d;
}
&:last-child {
border-width: 0;
}
&:nth-child(2n + 1) {
background-color: #f5f5f5;
.dark_theme & {
background-color: #202020;
}
}
&:hover {
background-color: #eaeaea;
box-shadow: 0px 1px 2px 0 rgba(#000, 0.12);
.dark_theme & {
background-color: #181818;
box-shadow: 0px 1px 2px 0 rgba(#000, 0.12);
}
}
> * {
display: table-cell;
border-right: 1px solid #f0f0f0;
.dark_theme & {
border-color: #2d2d2d;
}
&:last-child {
border-right: 0;
}
}
&:hover {
> * {
border-color: #eaeaea;
.dark_theme & {
border-color: #181818;
}
}
}
.material-icons[data-icon='check_circle'],
.material-icons[data-icon='check_circle_outline'] {
color: var(--success-color);
}
.material-icons[data-icon='cancel'],
.material-icons[data-icon='highlight_off'],
.reported-number {
color: var(--danger-color);
}
.reported-number {
font-weight: 500;
}
.non-available {
opacity: 0.4;
}
> * {
position: relative;
min-width: 98px;
padding-top: 14px;
padding-bottom: 14px;
vertical-align: middle;
}
.mi-title,
.mi-name {
.actions {
position: relative;
display: block;
padding-top: 4px;
button {
font-size: 12px;
color: var(--danger-color);
border: 0;
background: none;
&:hover {
text-decoration: underline;
}
}
.popup {
position: absolute;
top: 100%;
left: 0;
font-size: initial;
font-weight: initial;
}
.popup-message-bottom {
position: relative;
float: left;
width: 100%;
button {
position: relative;
float: left;
font-size: 14px;
color: var(--popup-msg-main-text-color);
&.proceed-profile-removal {
float: right;
color: var(--default-theme-color);
}
&.cancel-profile-removal {
float: left;
}
&:hover {
color: inherit;
text-decoration: none;
}
}
}
}
}
&.manage-media-item {
> * {
width: 10%;
text-align: center;
}
.mi-title,
.mi-author {
padding-left: 16px;
padding-right: 16px;
text-align: inherit;
font-weight: 500;
}
.mi-type,
.mi-encoding,
.mi-state {
text-transform: capitalize;
}
.mi-checkbox {
min-width: 48px;
width: 48px;
}
.mi-title {
min-width: 240px;
width: 100%;
}
.mi-author {
min-width: 184px;
}
.mi-added {
min-width: 168px;
min-width: 120px;
}
.mi-type {
}
.mi-encoding {
min-width: 136px;
}
.mi-state,
.mi-reviewed,
.mi-featured,
.mi-reported {
min-width: 88px;
}
}
&.manage-users-item {
> * {
width: math.div(1,8) * 100%;
}
.mi-added,
.mi-role,
.mi-featured,
.mi-verified,
.mi-trusted,
.mi-checkbox {
text-align: center;
}
.mi-name,
.mi-username {
min-width: 240px;
min-width: 200px;
width: 50%;
padding-left: 16px;
padding-right: 16px;
font-weight: 500;
}
.mi-checkbox {
min-width: 48px;
width: 48px;
}
.mi-added {
min-width: 168px;
@media (min-width: 768px) {
min-width: 200px;
}
}
}
&.manage-comments-item {
> * {
width: 16%;
}
.mi-title,
.mi-comment,
.mi-author {
padding-left: 16px;
padding-right: 16px;
}
.mi-comment,
.mi-added {
width: 100%;
}
.mi-author {
min-width: 160px;
font-weight: 500;
}
.mi-comment {
min-width: 240px;
.actions {
margin: 0.5em 0 0;
font-size: 0.92857em;
// font-weight:500;
.seperator {
margin: 0 4px;
opacity: 0.65;
}
button {
color: var(--danger-color);
border: 0;
background: none;
&:hover {
text-decoration: underline;
}
}
.popup {
position: absolute;
top: 100%;
left: 0;
font-size: initial;
font-weight: initial;
}
.popup-message-bottom {
position: relative;
float: left;
width: 100%;
button {
position: relative;
float: left;
font-size: 14px;
color: var(--popup-msg-main-text-color);
&.proceed-profile-removal {
float: right;
color: var(--default-theme-color);
}
&.cancel-profile-removal {
float: left;
}
&:hover {
color: inherit;
text-decoration: none;
}
}
}
}
}
.mi-added {
min-width: 192px;
text-align: center;
}
.mi-checkbox {
min-width: 48px;
width: 48px;
text-align: center;
}
&.manage-item-header {
.mi-comment {
padding-left: 16px;
}
}
}
&.manage-item-header {
font-size: 13px;
font-weight: 500;
letter-spacing: 0.007px;
background-color: #e3e3e3;
.dark_theme & {
background-color: #151515;
}
&:hover {
box-shadow: none;
}
> * {
padding-top: 20px;
padding-bottom: 20px;
border-right: 0;
text-transform: uppercase !important;
}
> .mi-col-sort {
.mi-col-sort-icons {
position: relative;
display: inline;
vertical-align: top;
background-color: yellow;
.material-icons {
width: auto;
height: auto;
padding: 0 0 0 1px;
font-size: 22px;
line-height: 1;
}
> * {
opacity: 0.25;
position: absolute;
left: 0;
&:first-child {
bottom: 0px;
}
&:last-child {
top: 0px;
}
}
}
cursor: pointer;
&:hover {
text-decoration: underline;
.mi-col-sort-icons {
> * {
opacity: 0.35;
}
}
}
&.desc {
.mi-col-sort-icons {
> *:last-child {
opacity: 0.8;
}
}
}
&.asc {
.mi-col-sort-icons {
> *:first-child {
opacity: 0.8;
}
}
}
}
}
}
.manage-items-options {
position: relative;
float: left;
width: 100%;
clear: both;
}
.manage-items-bulk-action {
position: relative;
width: auto;
float: left;
display: inline-block;
margin-bottom: 12px;
> select {
margin-right: 16px;
margin-bottom: 12px;
border-color: var(--input-bg-color);
background-color: var(--user-action-form-inner-bg-color);
box-shadow: 0px 1px 4px 0 rgba(17, 17, 17, 0.06);
}
> button {
padding: 0;
height: 36px;
line-height: 36px;
margin-right: 16px;
margin-bottom: 12px;
color: var(--default-theme-color);
border: 0;
background: none;
&:hover,
&:focus {
text-decoration: underline;
}
}
.popup {
position: absolute;
top: 100%;
left: 0;
background-color: var(--user-action-form-inner-bg-color);
.popup-on-top & {
top: auto;
bottom: 100%;
}
.popup-message-bottom {
float: left;
}
button {
position: relative;
width: auto;
float: left;
padding-top: 4px;
padding-bottom: 4px;
font-size: 1em;
color: var(--popup-msg-main-text-color);
border: 0;
background: none;
&.proceed-profile-removal {
float: right;
color: var(--default-theme-color);
}
&.cancel-profile-removal {
float: left;
}
&:hover {
color: inherit;
}
}
}
}
.manage-items-pagination {
position: relative;
width: auto;
margin-bottom: 12px;
font-size: 13px;
float: right;
display: inline-block;
button,
.pagination-dots {
padding: 0;
margin: 0 12px 12px 0;
}
button {
display: inline-block;
width: 36px;
height: 36px;
border: 0;
color: inherit;
background: var(--user-action-form-inner-bg-color);
border-radius: 1px;
box-shadow: 0px 1px 4px 0 rgba(17, 17, 17, 0.06);
border-width: 1px;
border-style: solid;
border-color: #f0f0f0;
.dark_theme & {
border-color: #2d2d2d;
}
&:hover {
font-weight: 500;
color: var(--default-theme-color);
}
&.active {
color: var(--user-action-form-inner-bg-color);
.dark_theme & {
color: inherit;
}
background-color: var(--default-theme-color);
border-color: var(--default-theme-color);
}
&:last-child {
margin-right: 0;
}
}
.pagination-dots {
letter-spacing: 1px;
}
}

View File

@@ -0,0 +1,132 @@
import { PageStore } from '../../../../utils/stores/';
import { formatInnerLink, getRequest } from '../../../../utils/helpers/';
export function ManageItemsListHandler(itemsPerPage, maxItems, request_url, itemsCountCallback, loadItemsCallback) {
const config = {
maxItems: maxItems || 255,
pageItems: itemsPerPage ? Math.min(maxItems, itemsPerPage) : 1,
};
const state = {
totalItems: 0,
totalPages: 0,
nextRequestUrl: formatInnerLink(request_url, PageStore.get('config-site').url),
};
const waiting = {
pageItems: 0,
requestResponse: false,
};
let firstItemUrl = null;
const items = [];
const responseItems = [];
const callbacks = {
itemsCount: function () {
if ('function' === typeof itemsCountCallback) {
itemsCountCallback(state.totalItems);
}
},
itemsLoad: function () {
if ('function' === typeof loadItemsCallback) {
loadItemsCallback(items);
}
},
};
function loadNextItems(itemsLength) {
let itemsToLoad, needExtraRequest;
itemsLength = !isNaN(itemsLength) ? itemsLength : config.pageItems;
if (waiting.pageItems && waiting.pageItems <= responseItems.length) {
itemsToLoad = waiting.pageItems;
needExtraRequest = false;
waiting.pageItems = 0;
} else {
itemsToLoad = Math.min(itemsLength, responseItems.length);
needExtraRequest = itemsLength > responseItems.length && !!state.nextRequestUrl;
waiting.pageItems = needExtraRequest ? itemsLength - responseItems.length : 0;
}
if (itemsToLoad) {
let i = 0;
while (i < itemsToLoad) {
items.push(responseItems.shift());
i += 1;
}
callbacks.itemsLoad();
}
if (needExtraRequest) {
runRequest();
}
}
function runRequest(initialRequest) {
waiting.requestResponse = true;
function fn(response) {
waiting.requestResponse = false;
if (!!!response || !!!response.data) {
return;
}
let data = response.data;
let results = void 0 !== data.results ? data.results : data; // NOTE: The structure of response data in the case of categories differs from the others.
// console.log( firstItemUrl );
let i = 0;
while (i < results.length && config.maxItems > responseItems.length) {
if (null === firstItemUrl || firstItemUrl !== results[i].url) {
responseItems.push(results[i]);
}
i += 1;
}
state.nextRequestUrl = !!data.next && config.maxItems > responseItems.length ? data.next : null;
if (initialRequest) {
// In some cases, (total) 'count' field is missing, but probably doesn't need (eg. in recommended media).
state.totalItems = !!data.count ? data.count : responseItems.length;
state.totalItems = Math.min(config.maxItems, state.totalItems);
state.totalPages = Math.ceil(state.totalItems / config.pageItems);
callbacks.itemsCount();
}
loadNextItems();
}
getRequest(state.nextRequestUrl, false, fn);
state.nextRequestUrl = null;
}
function loadItems(itemsLength) {
if (!waiting.requestResponse && items.length < state.totalItems) {
loadNextItems(itemsLength);
}
}
function totalPages() {
return state.totalPages;
}
function loadedAllItems() {
return items.length === state.totalItems;
}
runRequest(true);
return {
loadItems,
totalPages,
loadedAllItems,
};
}

View File

@@ -0,0 +1,13 @@
export default class MediaItem {
constructor(item) {
if (!Node.prototype.isPrototypeOf(item)) {
return null;
}
this.element = item;
}
element() {
return this.element;
}
}

View File

@@ -0,0 +1,39 @@
import ManageMediaItem from './ManageMediaItem';
const _ManageMediaItemsListData = {};
export default class MediaItemsList {
constructor(listContainer, initialItems) {
if (!Node.prototype.isPrototypeOf(listContainer)) {
return null;
}
_ManageMediaItemsListData[
Object.defineProperty(this, 'id', {
value: 'ManageMediaItemsList_' + Object.keys(_ManageMediaItemsListData).length,
}).id
] = {};
this.items = [];
this.container = listContainer;
this.appendItems(initialItems);
}
dataObject() {
return _ManageMediaItemsListData;
}
appendItems(items) {
var i;
if (NodeList.prototype.isPrototypeOf(items)) {
i = 0;
while (i < items.length) {
this.items.push(new ManageMediaItem(items[i]));
i += 1;
}
} else if (Node.prototype.isPrototypeOf(items)) {
this.items.push(new ManageMediaItem(items));
}
}
}

View File

@@ -0,0 +1,171 @@
import React from 'react';
import { ManageMediaItem } from '../../ManageItem/ManageMediaItem';
import { ManageUsersItem } from '../../ManageItem/ManageUsersItem';
import { ManageCommentsItem } from '../../ManageItem/ManageCommentsItem';
import { ManageMediaItemHeader } from '../../ManageItem/ManageMediaItemHeader';
import { ManageUsersItemHeader } from '../../ManageItem/ManageUsersItemHeader';
import { ManageCommentsItemHeader } from '../../ManageItem/ManageCommentsItemHeader';
function useManageItem(props) {
const itemData = props.item;
const itemProps = {
order: props.order,
onCheckRow: props.onCheckRow,
selectedRow: props.selectedRow,
onProceedRemoval: props.onProceedRemoval,
hideDeleteAction: props.hideDeleteAction,
};
return [itemData, itemProps];
}
function ListManageMediaItem(props) {
const [itemData, itemProps] = useManageItem(props);
const args = {
...itemProps,
thumbnail_url: itemData.thumbnail_url,
title: itemData.title,
url: itemData.url.replace(' ', '%20'),
author_name: itemData.author_name,
author_url: itemData.author_profile,
add_date: itemData.add_date,
media_type: itemData.media_type,
encoding_status: itemData.encoding_status,
state: itemData.state,
is_reviewed: itemData.is_reviewed,
featured: itemData.featured,
reported_times: itemData.reported_times,
token: itemData.friendly_token,
};
return <ManageMediaItem {...args} />;
}
function ListManageUserItem(props) {
const [itemData, itemProps] = useManageItem(props);
const roles = [];
if (void 0 !== itemData.is_editor && itemData.is_editor) {
roles.push('Editor');
}
if (void 0 !== itemData.is_manager && itemData.is_manager) {
roles.push('Manager');
}
const args = {
...itemProps,
thumbnail_url: itemData.thumbnail_url,
name: itemData.name,
url: itemData.url.replace(' ', '%20'),
username: itemData.username,
add_date: itemData.date_added,
is_featured: itemData.is_featured,
roles: roles,
is_verified: true === itemData.email_is_verified,
is_trusted: true === itemData.advancedUser,
has_roles: void 0 !== itemData.is_editor || void 0 !== itemData.is_manager,
has_verified: void 0 !== itemData.email_is_verified,
has_trusted: void 0 !== itemData.advancedUser,
};
return <ManageUsersItem {...args} />;
}
function ListManageCommentItem(props) {
const [itemData, itemProps] = useManageItem(props);
const args = {
...itemProps,
media_url: void 0 !== itemData.media_url ? itemData.media_url.replace(' ', '%20') : void 0,
author_name: itemData.author_name,
author_url: itemData.author_profile,
author_thumbnail_url: itemData.author_thumbnail_url,
add_date: itemData.add_date,
text: itemData.text,
uid: itemData.uid,
};
return <ManageCommentsItem {...args} />;
}
function ListManageItem(props) {
const args = {
item: props.item,
order: props.order,
hideDeleteAction: false,
onCheckRow: props.onCheckRow,
onProceedRemoval: props.onProceedRemoval,
};
if ('media' === props.type) {
return <ListManageMediaItem {...args} selectedRow={-1 < props.selectedItems.indexOf(props.item.friendly_token)} />;
}
if ('users' === props.type) {
return <ListManageUserItem {...args} selectedRow={-1 < props.selectedItems.indexOf(props.item.username)} />;
}
if ('comments' === props.type) {
return <ListManageCommentItem {...args} selectedRow={-1 < props.selectedItems.indexOf(props.item.uid)} />;
}
return null;
}
function ListManageItemHeader(props) {
const args = {
sort: props.sort,
order: props.order,
selected: props.selected,
onCheckAllRows: props.onCheckAllRows,
onClickColumnSort: props.onClickColumnSort,
};
if ('media' === props.type) {
return <ManageMediaItemHeader {...args} />;
}
if ('users' === props.type) {
args.has_roles =
props.items.length && (void 0 !== props.items[0].is_editor || void 0 !== props.items[0].is_manager);
args.has_verified = props.items.length && void 0 !== props.items[0].email_is_verified;
args.has_trusted = props.items.length && void 0 !== props.items[0].advancedUser;
return <ManageUsersItemHeader {...args} />;
}
if ('comments' === props.type) {
return <ManageCommentsItemHeader {...args} />;
}
return null;
}
export function renderManageItems(items, props) {
return [
<ListManageItemHeader
key={0}
type={props.manageType}
items={items}
sort={props.sortBy}
order={props.ordering}
selected={props.selectedAllItems}
onCheckAllRows={props.onAllRowsCheck}
onClickColumnSort={props.onClickColumnSort}
/>,
...items.map((item, index) => (
<ListManageItem
key={index + 1}
order={index + 1}
item={item}
type={props.manageType}
onCheckRow={props.onRowCheck}
onProceedRemoval={props.onDelete}
selectedItems={props.selectedItems}
/>
)),
];
}

View File

@@ -0,0 +1,28 @@
import ManageMediaItemsList from './ManageMediaItemsList';
var CSS_selectors = {
mediaItems: '.item',
};
var ManageMediaItemsListInstances = [];
export default function (lists) {
if (!lists.length) {
return null;
}
let items,
i = 0;
while (i < lists.length) {
items = lists[i].querySelectorAll(CSS_selectors.mediaItems);
if (items.length) {
ManageMediaItemsListInstances = ManageMediaItemsListInstances || [];
ManageMediaItemsListInstances.push(new ManageMediaItemsList(lists[i], items));
}
i += 1;
}
return ManageMediaItemsListInstances;
}

View File

@@ -0,0 +1,165 @@
import React, { useState, useEffect, useRef } from 'react';
import PropTypes from 'prop-types';
import { PageStore } from '../../utils/stores/';
import { FilterOptions } from '../_shared';
import './ManageItemList-filters.scss';
const filters = {
state: [
{ id: 'all', title: 'All' },
{ id: 'public', title: 'Public' },
{ id: 'private', title: 'Private' },
{ id: 'unlisted', title: 'Unlisted' },
],
media_type: [
{ id: 'all', title: 'All' },
{ id: 'video', title: 'Video' },
{ id: 'audio', title: 'Audio' },
{ id: 'image', title: 'Image' },
{ id: 'pdf', title: 'Pdf' },
],
encoding_status: [
{ id: 'all', title: 'All' },
{ id: 'success', title: 'Success' },
{ id: 'running', title: 'Running' },
{ id: 'pending', title: 'Pending' },
{ id: 'fail', title: 'Fail' },
],
reviewed: [
{ id: 'all', title: 'All' },
{ id: 'true', title: 'Yes' },
{ id: 'false', title: 'No' },
],
featured: [
{ id: 'all', title: 'All' },
{ id: 'true', title: 'Yes' },
{ id: 'false', title: 'No' },
],
};
export function ManageMediaFilters(props) {
const [isHidden, setIsHidden] = useState(props.hidden);
const [state, setState] = useState('all');
const [mediaType, setMediaType] = useState('all');
const [encodingStatus, setEncodingStatus] = useState('all');
const [isFeatured, setIsFeatured] = useState('all');
const [isReviewed, setIsReviewed] = useState('all');
const containerRef = useRef(null);
const innerContainerRef = useRef(null);
function onWindowResize() {
if (!isHidden) {
containerRef.current.style.height = 24 + innerContainerRef.current.offsetHeight + 'px';
}
}
function onFilterSelect(ev) {
const args = {
state: state,
media_type: mediaType,
encoding_status: encodingStatus,
featured: isFeatured,
is_reviewed: isReviewed,
};
switch (ev.currentTarget.getAttribute('filter')) {
case 'state':
args.state = ev.currentTarget.getAttribute('value');
props.onFiltersUpdate(args);
setState(args.state);
break;
case 'media_type':
args.media_type = ev.currentTarget.getAttribute('value');
props.onFiltersUpdate(args);
setMediaType(args.media_type);
break;
case 'encoding_status':
args.encoding_status = ev.currentTarget.getAttribute('value');
props.onFiltersUpdate(args);
setEncodingStatus(args.encoding_status);
break;
case 'featured':
args.featured = ev.currentTarget.getAttribute('value');
props.onFiltersUpdate(args);
setIsFeatured(args.featured);
break;
case 'reviewed':
args.is_reviewed = ev.currentTarget.getAttribute('value');
props.onFiltersUpdate(args);
setIsReviewed(args.is_reviewed);
break;
}
}
useEffect(() => {
setIsHidden(props.hidden);
onWindowResize();
}, [props.hidden]);
useEffect(() => {
PageStore.on('window_resize', onWindowResize);
return () => PageStore.removeListener('window_resize', onWindowResize);
}, []);
return (
<div ref={containerRef} className={'mi-filters-row' + (isHidden ? ' hidden' : '')}>
<div ref={innerContainerRef} className="mi-filters-row-inner">
<div className="mi-filter">
<div className="mi-filter-title">STATE</div>
<div className="mi-filter-options">
<FilterOptions id={'state'} options={filters.state} selected={state} onSelect={onFilterSelect} />
</div>
</div>
<div className="mi-filter">
<div className="mi-filter-title">MEDIA TYPE</div>
<div className="mi-filter-options">
<FilterOptions
id={'media_type'}
options={filters.media_type}
selected={mediaType}
onSelect={onFilterSelect}
/>
</div>
</div>
<div className="mi-filter">
<div className="mi-filter-title">ENCODING STATUS</div>
<div className="mi-filter-options">
<FilterOptions
id={'encoding_status'}
options={filters.encoding_status}
selected={encodingStatus}
onSelect={onFilterSelect}
/>
</div>
</div>
<div className="mi-filter">
<div className="mi-filter-title">REVIEWED</div>
<div className="mi-filter-options">
<FilterOptions id={'reviewed'} options={filters.reviewed} selected={isReviewed} onSelect={onFilterSelect} />
</div>
</div>
<div className="mi-filter">
<div className="mi-filter-title">FEATURED</div>
<div className="mi-filter-options">
<FilterOptions id={'featured'} options={filters.featured} selected={isFeatured} onSelect={onFilterSelect} />
</div>
</div>
</div>
</div>
);
}
ManageMediaFilters.propTypes = {
hidden: PropTypes.bool,
};
ManageMediaFilters.defaultProps = {
hidden: false,
};

View File

@@ -0,0 +1,74 @@
import React, { useState, useEffect, useRef } from 'react';
import PropTypes from 'prop-types';
import { PageStore } from '../../utils/stores/';
import { FilterOptions } from '../_shared';
import './ManageItemList-filters.scss';
const filters = {
role: [
{ id: 'all', title: 'All' },
{ id: 'editor', title: 'Editor' },
{ id: 'manager', title: 'Manager' },
],
};
export function ManageUsersFilters(props) {
const [isHidden, setIsHidden] = useState(props.hidden);
const [role, setFilterRole] = useState('all');
const containerRef = useRef(null);
const innerContainerRef = useRef(null);
function onWindowResize() {
if (!isHidden) {
containerRef.current.style.height = 24 + innerContainerRef.current.offsetHeight + 'px';
}
}
function onFilterSelect(ev) {
const args = {
role: role,
};
switch (ev.currentTarget.getAttribute('filter')) {
case 'role':
args.role = ev.currentTarget.getAttribute('value');
props.onFiltersUpdate(args);
setFilterRole(args.role);
break;
}
}
useEffect(() => {
setIsHidden(props.hidden);
onWindowResize();
}, [props.hidden]);
useEffect(() => {
PageStore.on('window_resize', onWindowResize);
return () => PageStore.removeListener('window_resize', onWindowResize);
}, []);
return (
<div ref={containerRef} className={'mi-filters-row' + (isHidden ? ' hidden' : '')}>
<div ref={innerContainerRef} className="mi-filters-row-inner">
<div className="mi-filter">
<div className="mi-filter-title">ROLE</div>
<div className="mi-filter-options">
<FilterOptions id={'role'} options={filters.role} selected={role} onSelect={onFilterSelect} />
</div>
</div>
</div>
</div>
);
}
ManageUsersFilters.propTypes = {
hidden: PropTypes.bool,
};
ManageUsersFilters.defaultProps = {
hidden: false,
};