mirror of
https://github.com/mediacms-io/mediacms.git
synced 2025-11-22 14:27:58 -05:00
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:
@@ -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,
|
||||
};
|
||||
@@ -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,
|
||||
};
|
||||
@@ -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,
|
||||
};
|
||||
@@ -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,
|
||||
};
|
||||
@@ -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,
|
||||
};
|
||||
@@ -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,
|
||||
};
|
||||
201
frontend/src/static/js/components/management-table/ManageItemList-filters.scss
Executable file
201
frontend/src/static/js/components/management-table/ManageItemList-filters.scss
Executable 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 {
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
};
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
export default class MediaItem {
|
||||
constructor(item) {
|
||||
if (!Node.prototype.isPrototypeOf(item)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
this.element = item;
|
||||
}
|
||||
|
||||
element() {
|
||||
return this.element;
|
||||
}
|
||||
}
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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}
|
||||
/>
|
||||
)),
|
||||
];
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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,
|
||||
};
|
||||
@@ -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,
|
||||
};
|
||||
Reference in New Issue
Block a user