mirror of
https://github.com/mediacms-io/mediacms.git
synced 2025-11-20 21:46:04 -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:
71
frontend/src/static/js/components/media-page/AutoPlay.jsx
Normal file
71
frontend/src/static/js/components/media-page/AutoPlay.jsx
Normal file
@@ -0,0 +1,71 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { PageActions } from '../../utils/actions/';
|
||||
import { PageStore, MediaPageStore } from '../../utils/stores/';
|
||||
import { ItemList } from '../item-list/ItemList';
|
||||
|
||||
function autoPlayMedia() {
|
||||
const dt = MediaPageStore.get('media-data');
|
||||
return dt && dt.related_media && dt.related_media.length ? dt.related_media[0] : null;
|
||||
}
|
||||
|
||||
export function AutoPlay(props) {
|
||||
const [media, setMedia] = useState(autoPlayMedia());
|
||||
const [enabledAutoPlay, setEnabledAutoPlay] = useState(PageStore.get('media-auto-play'));
|
||||
|
||||
function onKeyPress(ev) {
|
||||
if (0 === ev.keyCode) {
|
||||
PageActions.toggleMediaAutoPlay();
|
||||
}
|
||||
}
|
||||
|
||||
function onUpdateMediaAutoPlay() {
|
||||
setEnabledAutoPlay(PageStore.get('media-auto-play'));
|
||||
}
|
||||
|
||||
function onMediaDataLoad() {
|
||||
setMedia(autoPlayMedia());
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
MediaPageStore.on('loaded_media_data', onMediaDataLoad);
|
||||
PageStore.on('switched_media_auto_play', onUpdateMediaAutoPlay);
|
||||
return () => {
|
||||
MediaPageStore.removeListener('loaded_media_data', onMediaDataLoad);
|
||||
PageStore.removeListener('switched_media_auto_play', onUpdateMediaAutoPlay);
|
||||
};
|
||||
}, []);
|
||||
|
||||
return !media ? null : (
|
||||
<div className="auto-play">
|
||||
<div className="auto-play-header">
|
||||
<div className="next-label">Up next</div>
|
||||
<div className="auto-play-option">
|
||||
<label className="checkbox-label right-selectbox" tabIndex={0} onKeyPress={onKeyPress}>
|
||||
AUTOPLAY
|
||||
<span className="checkbox-switcher-wrap">
|
||||
<span className="checkbox-switcher">
|
||||
<input
|
||||
type="checkbox"
|
||||
tabIndex={-1}
|
||||
checked={enabledAutoPlay}
|
||||
onChange={PageActions.toggleMediaAutoPlay}
|
||||
/>
|
||||
</span>
|
||||
</span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<ItemList
|
||||
className="items-list-hor"
|
||||
items={[media]}
|
||||
pageItems={1}
|
||||
maxItems={1}
|
||||
singleLinkContent={true}
|
||||
horizontalItemsOrientation={true}
|
||||
hideDate={true}
|
||||
hideViews={!PageStore.get('config-media-item').displayViews}
|
||||
hideAuthor={!PageStore.get('config-media-item').displayAuthor}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
1946
frontend/src/static/js/components/media-page/MediaPage.scss
Executable file
1946
frontend/src/static/js/components/media-page/MediaPage.scss
Executable file
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,31 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { PositiveIntegerOrZero } from '../../utils/helpers/';
|
||||
import { ItemList } from '../item-list/ItemList';
|
||||
|
||||
export function PlaylistPlaybackMedia(props) {
|
||||
return (
|
||||
<ItemList
|
||||
className={'items-list-hor'}
|
||||
pageItems={9999}
|
||||
maxItems={9999}
|
||||
items={props.items}
|
||||
hideDate={true}
|
||||
hideViews={true}
|
||||
hidePlaylistOrderNumber={false}
|
||||
horizontalItemsOrientation={true}
|
||||
inPlaylistView={true}
|
||||
singleLinkContent={true}
|
||||
playlistActiveItem={props.playlistActiveItem}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
PlaylistPlaybackMedia.propTypes = {
|
||||
items: PropTypes.array.isRequired,
|
||||
playlistActiveItem: PositiveIntegerOrZero,
|
||||
};
|
||||
|
||||
PlaylistPlaybackMedia.defaultProps = {
|
||||
playlistActiveItem: 1,
|
||||
};
|
||||
171
frontend/src/static/js/components/media-page/PlaylistView.js
Normal file
171
frontend/src/static/js/components/media-page/PlaylistView.js
Normal file
@@ -0,0 +1,171 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { LinksContext } from '../../utils/contexts/';
|
||||
import { PlaylistViewStore } from '../../utils/stores/';
|
||||
import { PositiveIntegerOrZero } from '../../utils/helpers/';
|
||||
import { PageActions, PlaylistViewActions } from '../../utils/actions/';
|
||||
import { CircleIconButton } from '../_shared/';
|
||||
import { PlaylistPlaybackMedia } from './PlaylistPlaybackMedia';
|
||||
|
||||
import './PlaylistView.scss';
|
||||
|
||||
export default class PlaylistView extends React.PureComponent {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
expanded: true,
|
||||
loopRepeat: PlaylistViewStore.get('enabled-loop'),
|
||||
shuffle: PlaylistViewStore.get('enabled-shuffle'),
|
||||
savedPlaylist: PlaylistViewStore.get('saved-playlist-loop'),
|
||||
title: props.playlistData.title,
|
||||
link: props.playlistData.url,
|
||||
authorName: props.playlistData.user,
|
||||
authorLink: LinksContext._currentValue.home + '/user/' + props.playlistData.user,
|
||||
activeItem: props.activeItem,
|
||||
totalMedia: props.playlistData.media_count,
|
||||
items: props.playlistData.playlist_media,
|
||||
};
|
||||
|
||||
this.onHeaderClick = this.onHeaderClick.bind(this);
|
||||
this.onLoopClick = this.onLoopClick.bind(this);
|
||||
this.onShuffleClick = this.onShuffleClick.bind(this);
|
||||
this.onSaveClick = this.onSaveClick.bind(this);
|
||||
this.onLoopRepeatUpdate = this.onLoopRepeatUpdate.bind(this);
|
||||
this.onShuffleUpdate = this.onShuffleUpdate.bind(this);
|
||||
this.onPlaylistSaveUpdate = this.onPlaylistSaveUpdate.bind(this);
|
||||
|
||||
PlaylistViewStore.on('loop-repeat-updated', this.onLoopRepeatUpdate);
|
||||
PlaylistViewStore.on('shuffle-updated', this.onShuffleUpdate);
|
||||
PlaylistViewStore.on('saved-updated', this.onPlaylistSaveUpdate);
|
||||
}
|
||||
|
||||
onHeaderClick(ev) {
|
||||
this.setState({ expanded: !this.state.expanded });
|
||||
}
|
||||
|
||||
onLoopClick() {
|
||||
PlaylistViewActions.toggleLoop();
|
||||
}
|
||||
|
||||
onShuffleClick() {
|
||||
PlaylistViewActions.toggleShuffle();
|
||||
}
|
||||
|
||||
onSaveClick() {
|
||||
PlaylistViewActions.toggleSave();
|
||||
}
|
||||
|
||||
onShuffleUpdate() {
|
||||
this.setState(
|
||||
{
|
||||
shuffle: PlaylistViewStore.get('enabled-shuffle'),
|
||||
},
|
||||
() => {
|
||||
if (this.state.shuffle) {
|
||||
PageActions.addNotification('Playlist shuffle is on', 'shuffle-on');
|
||||
} else {
|
||||
PageActions.addNotification('Playlist shuffle is off', 'shuffle-off');
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
onLoopRepeatUpdate() {
|
||||
this.setState(
|
||||
{
|
||||
loopRepeat: PlaylistViewStore.get('enabled-loop'),
|
||||
},
|
||||
() => {
|
||||
if (this.state.loopRepeat) {
|
||||
PageActions.addNotification('Playlist loop is on', 'loop-on');
|
||||
} else {
|
||||
PageActions.addNotification('Playlist loop is off', 'loop-off');
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
onPlaylistSaveUpdate() {
|
||||
this.setState(
|
||||
{
|
||||
savedPlaylist: PlaylistViewStore.get('saved-playlist'),
|
||||
},
|
||||
() => {
|
||||
if (this.state.savedPlaylist) {
|
||||
PageActions.addNotification('Added to playlists library', 'added-to-playlists-lib');
|
||||
} else {
|
||||
PageActions.addNotification('Removed from playlists library', 'removed-from-playlists-lib');
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div className="playlist-view-wrap">
|
||||
<div className={'playlist-view' + (!this.state.expanded ? '' : ' playlist-expanded-view')}>
|
||||
<div className="playlist-header">
|
||||
<div className="playlist-title">
|
||||
<a href={this.state.link} title={this.state.title}>
|
||||
{this.state.title}
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div className="playlist-meta">
|
||||
{/*'public' === PlaylistViewStore.get('visibility') ? null :
|
||||
<div className="playlist-status">
|
||||
<span>{ PlaylistViewStore.get('visibility-icon') }</span>
|
||||
<div>{ PlaylistViewStore.get('visibility') }</div>
|
||||
</div>*/}
|
||||
<span>
|
||||
<a href={this.state.authorLink} title={this.state.authorName}>
|
||||
{this.state.authorName}
|
||||
</a>
|
||||
</span>
|
||||
-
|
||||
<span className="counter">
|
||||
{this.state.activeItem} / {this.state.totalMedia}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<CircleIconButton className="toggle-playlist-view" onClick={this.onHeaderClick}>
|
||||
{this.state.expanded ? (
|
||||
<i className="material-icons">keyboard_arrow_up</i>
|
||||
) : (
|
||||
<i className="material-icons">keyboard_arrow_down</i>
|
||||
)}
|
||||
</CircleIconButton>
|
||||
</div>
|
||||
|
||||
{!this.state.expanded ? null : (
|
||||
<div className="playlist-actions">
|
||||
<CircleIconButton
|
||||
className={this.state.loopRepeat ? 'active' : ''}
|
||||
onClick={this.onLoopClick}
|
||||
title="Loop playlist"
|
||||
>
|
||||
<i className="material-icons">repeat</i>
|
||||
</CircleIconButton>
|
||||
{/*<CircleIconButton className={ this.state.shuffle ? 'active' : '' } onClick={ this.onShuffleClick } title="Shuffle playlist"><i className="material-icons">shuffle</i></CircleIconButton>*/}
|
||||
{/*PlaylistViewStore.get('logged-in-user-playlist') ? null : <CircleIconButton className={ 'add-to-playlist' + ( this.state.savedPlaylist ? ' active' : '' ) } onClick={ this.onSaveClick } title={ this.state.savedPlaylist ? "Remove" : "Save playlist" }><i className="material-icons">{ this.state.savedPlaylist ? 'playlist_add_check' : 'playlist_add' }</i></CircleIconButton>*/}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{!this.state.expanded || !this.state.items.length ? null : (
|
||||
<div className="playlist-media">
|
||||
<PlaylistPlaybackMedia items={this.state.items} playlistActiveItem={this.state.activeItem} />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
PlaylistView.propTypes = {
|
||||
playlistData: PropTypes.object.isRequired,
|
||||
activeItem: PositiveIntegerOrZero,
|
||||
};
|
||||
|
||||
PlaylistView.defaultProps = {};
|
||||
258
frontend/src/static/js/components/media-page/PlaylistView.scss
Executable file
258
frontend/src/static/js/components/media-page/PlaylistView.scss
Executable file
@@ -0,0 +1,258 @@
|
||||
.playlist-view {
|
||||
border: 1px solid rgba(0, 0, 0, 0.1);
|
||||
|
||||
.dark_theme & {
|
||||
border: 1px solid rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
|
||||
.playlist-header {
|
||||
background-color: var(--playlist-view-header-bg-color);
|
||||
|
||||
.toggle-playlist-view {
|
||||
color: var(--playlist-view-header-toggle-text-color);
|
||||
background-color: var(--playlist-view-header-toggle-bg-color);
|
||||
}
|
||||
}
|
||||
|
||||
.playlist-title {
|
||||
a {
|
||||
color: var(--playlist-view-title-link-text-color);
|
||||
}
|
||||
}
|
||||
|
||||
.playlist-meta {
|
||||
color: var(--playlist-view-meta-text-color);
|
||||
|
||||
.counter {
|
||||
color: var(--item-meta-text-color);
|
||||
}
|
||||
|
||||
a {
|
||||
color: var(--playlist-view-meta-link-color);
|
||||
|
||||
&:hover {
|
||||
color: var(--playlist-view-meta-link-hover-text-color);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.playlist-status {
|
||||
color: var(--playlist-view-status-text-color);
|
||||
background-color: var(--playlist-view-status-bg-color);
|
||||
|
||||
.material-icons {
|
||||
color: var(--playlist-view-status-icon-text-color);
|
||||
}
|
||||
}
|
||||
|
||||
.playlist-actions {
|
||||
background-color: var(--playlist-view-actions-bg-color);
|
||||
|
||||
.circle-icon-button {
|
||||
background-color: var(--playlist-view-actions-bg-color);
|
||||
}
|
||||
}
|
||||
|
||||
.playlist-media {
|
||||
background-color: var(--playlist-view-media-bg-color);
|
||||
|
||||
.item-order-number {
|
||||
color: var(--playlist-view-media-order-number-color);
|
||||
}
|
||||
|
||||
.item-main {
|
||||
line-height: 1;
|
||||
|
||||
h3 {
|
||||
color: var(--playlist-view-item-title-text-color);
|
||||
|
||||
span {
|
||||
line-height: var(--playlist-item-title-line-height);
|
||||
max-height: calc(var(--horizontal-item-title-max-lines) * var(--playlist-item-title-line-height));
|
||||
background-color: var(--playlist-view-media-bg-color);
|
||||
background-color: transparent;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.item-meta {
|
||||
line-height: 1.230769231em;
|
||||
color: var(--item-meta-text-color);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.playlist-view-wrap {
|
||||
position: relative;
|
||||
display: block;
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
.playlist-view {
|
||||
display: block;
|
||||
|
||||
a {
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.playlist-header {
|
||||
position: relative;
|
||||
padding: 12px 16px;
|
||||
|
||||
.toggle-playlist-view {
|
||||
position: absolute;
|
||||
top: 15px;
|
||||
right: 17px;
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
}
|
||||
}
|
||||
|
||||
&.playlist-expanded-view {
|
||||
.playlist-header {
|
||||
padding-bottom: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.playlist-title {
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
line-height: 16px;
|
||||
}
|
||||
|
||||
.playlist-meta {
|
||||
margin-top: 4px;
|
||||
font-size: 12px;
|
||||
line-height: 15px;
|
||||
}
|
||||
|
||||
.playlist-status {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
margin-right: 8px;
|
||||
padding: 2px 4px;
|
||||
font-size: 12px;
|
||||
font-weight: 500;
|
||||
line-height: 12px;
|
||||
border-radius: 2px;
|
||||
|
||||
.material-icons {
|
||||
font-size: 13px;
|
||||
display: inline-block;
|
||||
margin: 0 3px 0 0;
|
||||
}
|
||||
|
||||
div {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
|
||||
&:first-letter {
|
||||
text-transform: capitalize;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.playlist-actions {
|
||||
padding: 0 16px 0 8px;
|
||||
|
||||
.circle-icon-button {
|
||||
display: inline-block;
|
||||
margin-left: 8px;
|
||||
|
||||
&.active {
|
||||
color: var(--theme-color, var(--default-theme-color));
|
||||
}
|
||||
|
||||
&.add-to-playlist {
|
||||
float: right;
|
||||
}
|
||||
|
||||
&:first-child {
|
||||
margin-left: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.playlist-media {
|
||||
max-height: 415px;
|
||||
padding: 4px 0;
|
||||
overflow: auto;
|
||||
|
||||
.items-list-outer {
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
.item {
|
||||
position: relative;
|
||||
padding: 4px 8px 4px 28px;
|
||||
max-width: 100%;
|
||||
margin: 0;
|
||||
|
||||
transition: background-color 0.05s linear;
|
||||
|
||||
&:hover,
|
||||
&.pl-active-item {
|
||||
background-color: var(--nav-menu-item-hover-bg-color);
|
||||
}
|
||||
}
|
||||
|
||||
.item-order-number {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
bottom: 0;
|
||||
display: block;
|
||||
width: 28px;
|
||||
font-size: 12px;
|
||||
line-height: 15px;
|
||||
|
||||
> div {
|
||||
display: table;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
|
||||
> div {
|
||||
display: table-cell;
|
||||
text-align: center;
|
||||
vertical-align: middle;
|
||||
}
|
||||
}
|
||||
|
||||
.material-icons {
|
||||
font-size: 17px;
|
||||
}
|
||||
}
|
||||
|
||||
.items-list-wrap {
|
||||
min-height: 64px;
|
||||
}
|
||||
|
||||
.item-content {
|
||||
padding-left: 100px !important;
|
||||
}
|
||||
|
||||
.item-thumb {
|
||||
width: 100px !important;
|
||||
height: 56px !important;
|
||||
}
|
||||
|
||||
.item-main {
|
||||
width: auto;
|
||||
display: block;
|
||||
min-height: 56px !important;
|
||||
|
||||
.item-content-link {
|
||||
float: left;
|
||||
display: inline-block;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
h3 {
|
||||
position: relative;
|
||||
float: left;
|
||||
width: 100%;
|
||||
margin: 0 !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { PageStore, MediaPageStore } from '../../utils/stores/';
|
||||
import { ItemList } from '../item-list/ItemList';
|
||||
|
||||
export function RelatedMedia(props) {
|
||||
const [items, setItems] = useState(updateMediaItems());
|
||||
const [mediaType, setMediaType] = useState(null);
|
||||
|
||||
function onMediaDataLoad() {
|
||||
setMediaType(MediaPageStore.get('media-type'));
|
||||
setItems(updateMediaItems());
|
||||
}
|
||||
|
||||
function updateMediaItems() {
|
||||
const md = MediaPageStore.get('media-data');
|
||||
return void 0 !== md && null !== md && void 0 !== md.related_media && md.related_media.length
|
||||
? md.related_media
|
||||
: null;
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
MediaPageStore.on('loaded_media_data', onMediaDataLoad);
|
||||
return () => MediaPageStore.removeListener('loaded_media_data', onMediaDataLoad);
|
||||
}, []);
|
||||
|
||||
return !items || !items.length ? null : (
|
||||
<ItemList
|
||||
className="items-list-hor"
|
||||
items={props.hideFirst && ('video' === mediaType || 'audio' === mediaType) ? items.slice(1) : items}
|
||||
pageItems={PageStore.get('config-options').pages.media.related.initialSize}
|
||||
singleLinkContent={true}
|
||||
horizontalItemsOrientation={true}
|
||||
hideDate={true}
|
||||
hideViews={!PageStore.get('config-media-item').displayViews}
|
||||
hideAuthor={!PageStore.get('config-media-item').displayAuthor}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
RelatedMedia.propTypes = {
|
||||
hideFirst: PropTypes.bool,
|
||||
};
|
||||
|
||||
RelatedMedia.defaultProps = {
|
||||
hideFirst: true,
|
||||
};
|
||||
23
frontend/src/static/js/components/media-page/ViewerError.js
Executable file
23
frontend/src/static/js/components/media-page/ViewerError.js
Executable file
@@ -0,0 +1,23 @@
|
||||
import React from 'react';
|
||||
import { MediaPageStore } from '../../utils/stores/';
|
||||
|
||||
export default class ViewerError extends React.PureComponent {
|
||||
render() {
|
||||
return (
|
||||
<div className="viewer-container" key="viewer-container-error">
|
||||
<div className="player-container player-container-error">
|
||||
<div className="player-container-inner">
|
||||
<div className="error-container">
|
||||
<div className="error-container-inner">
|
||||
<span className="icon-wrap">
|
||||
<i className="material-icons">error_outline</i>
|
||||
</span>
|
||||
<span className="msg-wrap">{MediaPageStore.get('media-load-error-message')}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
61
frontend/src/static/js/components/media-page/ViewerInfo.js
Executable file
61
frontend/src/static/js/components/media-page/ViewerInfo.js
Executable file
@@ -0,0 +1,61 @@
|
||||
import React from 'react';
|
||||
import { MediaPageStore } from '../../utils/stores/';
|
||||
import ViewerInfoContent from './ViewerInfoContent';
|
||||
import ViewerInfoTitleBanner from './ViewerInfoTitleBanner';
|
||||
|
||||
export default class ViewerInfo extends React.PureComponent {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
videoLoaded: false,
|
||||
};
|
||||
|
||||
this.onVideoLoad = this.onVideoLoad.bind(this);
|
||||
|
||||
MediaPageStore.on('loaded_media_data', this.onVideoLoad);
|
||||
}
|
||||
|
||||
onVideoLoad() {
|
||||
this.setState({
|
||||
videoLoaded: true,
|
||||
});
|
||||
}
|
||||
|
||||
render() {
|
||||
let views, categories, title, author, published, description;
|
||||
let allowDownload = false;
|
||||
|
||||
if (this.state.videoLoaded) {
|
||||
allowDownload = MediaPageStore.get('media-data').allow_download;
|
||||
|
||||
if (void 0 === allowDownload) {
|
||||
allowDownload = true;
|
||||
} else {
|
||||
allowDownload = !!allowDownload;
|
||||
}
|
||||
|
||||
views = MediaPageStore.get('media-data').views;
|
||||
categories = MediaPageStore.get('media-data').categories_info;
|
||||
title = MediaPageStore.get('media-data').title;
|
||||
|
||||
author = {
|
||||
name: MediaPageStore.get('media-data').author_name,
|
||||
url: MediaPageStore.get('media-data').author_profile,
|
||||
thumb: MediaPageStore.get('media-author-thumbnail-url'),
|
||||
};
|
||||
|
||||
published = MediaPageStore.get('media-data').add_date;
|
||||
description = MediaPageStore.get('media-data').description;
|
||||
}
|
||||
|
||||
return !this.state.videoLoaded ? null : (
|
||||
<div className="viewer-info">
|
||||
<div className="viewer-info-inner">
|
||||
<ViewerInfoTitleBanner title={title} views={views} categories={categories} allowDownload={allowDownload} />
|
||||
<ViewerInfoContent author={author} published={published} description={description} />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
246
frontend/src/static/js/components/media-page/ViewerInfoContent.js
Executable file
246
frontend/src/static/js/components/media-page/ViewerInfoContent.js
Executable file
@@ -0,0 +1,246 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { SiteContext } from '../../utils/contexts/';
|
||||
import { useUser, usePopup } from '../../utils/hooks/';
|
||||
import { PageStore, MediaPageStore } from '../../utils/stores/';
|
||||
import { PageActions, MediaPageActions } from '../../utils/actions/';
|
||||
import { formatInnerLink, publishedOnDate } from '../../utils/helpers/';
|
||||
import { PopupMain } from '../_shared/';
|
||||
import CommentsList from '../comments/Comments';
|
||||
|
||||
function metafield(arr) {
|
||||
let i;
|
||||
let sep;
|
||||
let ret = [];
|
||||
|
||||
if (arr.length) {
|
||||
i = 0;
|
||||
sep = 1 < arr.length ? ', ' : '';
|
||||
while (i < arr.length) {
|
||||
ret[i] = (
|
||||
<div key={i}>
|
||||
<a href={arr[i].url} title={arr[i].title}>
|
||||
{arr[i].title}
|
||||
</a>
|
||||
{i < arr.length - 1 ? sep : ''}
|
||||
</div>
|
||||
);
|
||||
i += 1;
|
||||
}
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
function MediaAuthorBanner(props) {
|
||||
return (
|
||||
<div className="media-author-banner">
|
||||
<div>
|
||||
<a className="author-banner-thumb" href={props.link || null} title={props.name}>
|
||||
<span style={{ backgroundImage: 'url(' + props.thumb + ')' }}>
|
||||
<img src={props.thumb} loading="lazy" alt={props.name} title={props.name} />
|
||||
</span>
|
||||
</a>
|
||||
</div>
|
||||
<div>
|
||||
<span>
|
||||
<a href={props.link} className="author-banner-name" title={props.name}>
|
||||
<span>{props.name}</span>
|
||||
</a>
|
||||
</span>
|
||||
{PageStore.get('config-media-item').displayPublishDate && props.published ? (
|
||||
<span className="author-banner-date">Published on {publishedOnDate(new Date(props.published))}</span>
|
||||
) : null}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function MediaMetaField(props) {
|
||||
return (
|
||||
<div className={props.id.trim() ? 'media-content-' + props.id.trim() : null}>
|
||||
<div className="media-content-field">
|
||||
<div className="media-content-field-label">
|
||||
<h4>{props.title}</h4>
|
||||
</div>
|
||||
<div className="media-content-field-content">{props.value}</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function EditMediaButton(props) {
|
||||
let link = props.link;
|
||||
|
||||
if (window.MediaCMS.site.devEnv) {
|
||||
link = '/edit-media.html';
|
||||
}
|
||||
|
||||
return (
|
||||
<a href={link} rel="nofollow" title="Edit media" className="edit-media">
|
||||
EDIT MEDIA
|
||||
</a>
|
||||
);
|
||||
}
|
||||
|
||||
function EditSubtitleButton(props) {
|
||||
let link = props.link;
|
||||
|
||||
if (window.MediaCMS.site.devEnv) {
|
||||
link = '#';
|
||||
}
|
||||
|
||||
return (
|
||||
<a href={link} rel="nofollow" title="Edit subtitle" className="edit-subtitle">
|
||||
EDIT SUBTITLE
|
||||
</a>
|
||||
);
|
||||
}
|
||||
|
||||
export default function ViewerInfoContent(props) {
|
||||
const { userCan } = useUser();
|
||||
|
||||
const description = props.description.trim();
|
||||
const tagsContent =
|
||||
!PageStore.get('config-enabled').taxonomies.tags || PageStore.get('config-enabled').taxonomies.tags.enabled
|
||||
? metafield(MediaPageStore.get('media-tags'))
|
||||
: [];
|
||||
const categoriesContent = PageStore.get('config-options').pages.media.categoriesWithTitle
|
||||
? []
|
||||
: !PageStore.get('config-enabled').taxonomies.categories ||
|
||||
PageStore.get('config-enabled').taxonomies.categories.enabled
|
||||
? metafield(MediaPageStore.get('media-categories'))
|
||||
: [];
|
||||
|
||||
let summary = MediaPageStore.get('media-summary');
|
||||
|
||||
summary = summary ? summary.trim() : '';
|
||||
|
||||
const [popupContentRef, PopupContent, PopupTrigger] = usePopup();
|
||||
|
||||
const [hasSummary, setHasSummary] = useState('' !== summary);
|
||||
const [isContentVisible, setIsContentVisible] = useState('' == summary);
|
||||
|
||||
function proceedMediaRemoval() {
|
||||
MediaPageActions.removeMedia();
|
||||
popupContentRef.current.toggle();
|
||||
}
|
||||
|
||||
function cancelMediaRemoval() {
|
||||
popupContentRef.current.toggle();
|
||||
}
|
||||
|
||||
function onMediaDelete(mediaId) {
|
||||
// FIXME: Without delay creates conflict [ Uncaught Error: Dispatch.dispatch(...): Cannot dispatch in the middle of a dispatch. ].
|
||||
setTimeout(function () {
|
||||
PageActions.addNotification('Media removed. Redirecting...', 'mediaDelete');
|
||||
setTimeout(function () {
|
||||
window.location.href =
|
||||
SiteContext._currentValue.url + '/' + MediaPageStore.get('media-data').author_profile.replace(/^\//g, '');
|
||||
}, 2000);
|
||||
}, 100);
|
||||
|
||||
if (void 0 !== mediaId) {
|
||||
console.info("Removed media '" + mediaId + '"');
|
||||
}
|
||||
}
|
||||
|
||||
function onMediaDeleteFail(mediaId) {
|
||||
// FIXME: Without delay creates conflict [ Uncaught Error: Dispatch.dispatch(...): Cannot dispatch in the middle of a dispatch. ].
|
||||
setTimeout(function () {
|
||||
PageActions.addNotification('Media removal failed', 'mediaDeleteFail');
|
||||
}, 100);
|
||||
|
||||
if (void 0 !== mediaId) {
|
||||
console.info('Media "' + mediaId + '"' + ' removal failed');
|
||||
}
|
||||
}
|
||||
|
||||
function onClickLoadMore() {
|
||||
setIsContentVisible(!isContentVisible);
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
MediaPageStore.on('media_delete', onMediaDelete);
|
||||
MediaPageStore.on('media_delete_fail', onMediaDeleteFail);
|
||||
return () => {
|
||||
MediaPageStore.removeListener('media_delete', onMediaDelete);
|
||||
MediaPageStore.removeListener('media_delete_fail', onMediaDeleteFail);
|
||||
};
|
||||
}, []);
|
||||
|
||||
const authorLink = formatInnerLink(props.author.url, SiteContext._currentValue.url);
|
||||
const authorThumb = formatInnerLink(props.author.thumb, SiteContext._currentValue.url);
|
||||
|
||||
return (
|
||||
<div className="media-info-content">
|
||||
{void 0 === PageStore.get('config-media-item').displayAuthor ||
|
||||
null === PageStore.get('config-media-item').displayAuthor ||
|
||||
!!PageStore.get('config-media-item').displayAuthor ? (
|
||||
<MediaAuthorBanner link={authorLink} thumb={authorThumb} name={props.author.name} published={props.published} />
|
||||
) : null}
|
||||
|
||||
<div className="media-content-banner">
|
||||
<div className="media-content-banner-inner">
|
||||
{hasSummary ? <div className="media-content-summary">{summary}</div> : null}
|
||||
{(!hasSummary || isContentVisible) && description ? (
|
||||
PageStore.get('config-options').pages.media.htmlInDescription ? (
|
||||
<div className="media-content-description" dangerouslySetInnerHTML={{ __html: description }}></div>
|
||||
) : (
|
||||
<div className="media-content-description">{description}</div>
|
||||
)
|
||||
) : null}
|
||||
{hasSummary ? (
|
||||
<button className="load-more" onClick={onClickLoadMore}>
|
||||
{isContentVisible ? 'SHOW LESS' : 'SHOW MORE'}
|
||||
</button>
|
||||
) : null}
|
||||
{tagsContent.length ? (
|
||||
<MediaMetaField value={tagsContent} title={1 < tagsContent.length ? 'Tags' : 'Tag'} id="tags" />
|
||||
) : null}
|
||||
{categoriesContent.length ? (
|
||||
<MediaMetaField
|
||||
value={categoriesContent}
|
||||
title={1 < categoriesContent.length ? 'Categories' : 'Category'}
|
||||
id="categories"
|
||||
/>
|
||||
) : null}
|
||||
|
||||
{userCan.editMedia || userCan.editSubtitle || userCan.deleteMedia ? (
|
||||
<div className="media-author-actions">
|
||||
{userCan.editMedia ? <EditMediaButton link={MediaPageStore.get('media-data').edit_url} /> : null}
|
||||
{userCan.editSubtitle && 'video' === MediaPageStore.get('media-data').media_type ? (
|
||||
<EditSubtitleButton
|
||||
link={MediaPageStore.get('media-data').edit_url.replace('edit?', 'add_subtitle?')}
|
||||
/>
|
||||
) : null}
|
||||
|
||||
<PopupTrigger contentRef={popupContentRef}>
|
||||
<button className="remove-media">DELETE MEDIA</button>
|
||||
</PopupTrigger>
|
||||
|
||||
<PopupContent contentRef={popupContentRef}>
|
||||
<PopupMain>
|
||||
<div className="popup-message">
|
||||
<span className="popup-message-title">Media removal</span>
|
||||
<span className="popup-message-main">You're willing to remove media permanently?</span>
|
||||
</div>
|
||||
<hr />
|
||||
<span className="popup-message-bottom">
|
||||
<button className="button-link cancel-comment-removal" onClick={cancelMediaRemoval}>
|
||||
CANCEL
|
||||
</button>
|
||||
<button className="button-link proceed-comment-removal" onClick={proceedMediaRemoval}>
|
||||
PROCEED
|
||||
</button>
|
||||
</span>
|
||||
</PopupMain>
|
||||
</PopupContent>
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<CommentsList />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,192 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { PageStore, MediaPageStore } from '../../utils/stores/';
|
||||
import { formatInnerLink, formatViewsNumber } from '../../utils/helpers/';
|
||||
import { MemberContext, PlaylistsContext, SiteContext } from '../../utils/contexts/';
|
||||
import { MediaLikeIcon, MediaDislikeIcon, OtherMediaDownloadLink, VideoMediaDownloadLink, MediaSaveButton, MediaShareButton, MediaMoreOptionsIcon } from '../media-actions/';
|
||||
|
||||
function Tooltip(el) {
|
||||
const parent = document.body;
|
||||
|
||||
const tooltipElem = document.createElement('span');
|
||||
|
||||
tooltipElem.innerText = el.getAttribute('data-tooltip');
|
||||
tooltipElem.setAttribute('class', 'tooltip');
|
||||
|
||||
el.removeAttribute('data-tooltip');
|
||||
|
||||
function onMouseenter() {
|
||||
const targetClientRect = el.getBoundingClientRect();
|
||||
parent.appendChild(tooltipElem);
|
||||
tooltipElem.style.top = targetClientRect.top - (0 + tooltipElem.offsetHeight) + 'px';
|
||||
tooltipElem.style.left = targetClientRect.left + 'px';
|
||||
document.addEventListener('scroll', onScroll);
|
||||
}
|
||||
|
||||
function onMouseleave() {
|
||||
parent.removeChild(tooltipElem);
|
||||
tooltipElem.style.top = '';
|
||||
tooltipElem.style.left = '';
|
||||
document.removeEventListener('scroll', onScroll);
|
||||
}
|
||||
|
||||
function onScroll() {
|
||||
const targetClientRect = el.getBoundingClientRect();
|
||||
tooltipElem.style.top = targetClientRect.top - (0 + tooltipElem.offsetHeight) + 'px';
|
||||
tooltipElem.style.left = targetClientRect.left + 'px';
|
||||
}
|
||||
|
||||
el.addEventListener('mouseenter', onMouseenter);
|
||||
el.addEventListener('mouseleave', onMouseleave);
|
||||
}
|
||||
|
||||
export default class ViewerInfoTitleBanner extends React.PureComponent {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
likedMedia: MediaPageStore.get('user-liked-media'),
|
||||
dislikedMedia: MediaPageStore.get('user-disliked-media'),
|
||||
};
|
||||
|
||||
this.downloadLink =
|
||||
'video' !== MediaPageStore.get('media-type')
|
||||
? formatInnerLink(MediaPageStore.get('media-original-url'), SiteContext._currentValue.url)
|
||||
: null;
|
||||
|
||||
this.updateStateValues = this.updateStateValues.bind(this);
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
MediaPageStore.on('liked_media', this.updateStateValues);
|
||||
MediaPageStore.on('unliked_media', this.updateStateValues);
|
||||
MediaPageStore.on('disliked_media', this.updateStateValues);
|
||||
MediaPageStore.on('undisliked_media', this.updateStateValues);
|
||||
|
||||
const tooltips = document.querySelectorAll('[data-tooltip]');
|
||||
|
||||
if (tooltips.length) {
|
||||
tooltips.forEach((tooltipElem) => Tooltip(tooltipElem));
|
||||
}
|
||||
}
|
||||
|
||||
updateStateValues() {
|
||||
this.setState({
|
||||
likedMedia: MediaPageStore.get('user-liked-media'),
|
||||
dislikedMedia: MediaPageStore.get('user-disliked-media'),
|
||||
});
|
||||
}
|
||||
|
||||
mediaCategories(overTitle) {
|
||||
if (void 0 === this.props.categories || null === this.props.categories || !this.props.categories.length) {
|
||||
return null;
|
||||
}
|
||||
|
||||
let i = 0;
|
||||
let cats = [];
|
||||
while (i < this.props.categories.length) {
|
||||
cats.push(
|
||||
<span key={i}>
|
||||
<a
|
||||
href={formatInnerLink(this.props.categories[i].url, SiteContext._currentValue.url)}
|
||||
title={this.props.categories[i].title}
|
||||
>
|
||||
{this.props.categories[i].title}
|
||||
</a>
|
||||
</span>
|
||||
);
|
||||
i += 1;
|
||||
}
|
||||
|
||||
return <div className={'media-under-title-categories' + (!!overTitle ? ' over-title' : '')}>{cats}</div>;
|
||||
}
|
||||
|
||||
render() {
|
||||
const displayViews = PageStore.get('config-options').pages.media.displayViews && void 0 !== this.props.views;
|
||||
|
||||
const mediaState = MediaPageStore.get('media-data').state;
|
||||
|
||||
let stateTooltip = '';
|
||||
|
||||
switch (mediaState) {
|
||||
case 'private':
|
||||
stateTooltip = 'The site admins have to make its access public';
|
||||
break;
|
||||
case 'unlisted':
|
||||
stateTooltip = 'The site admins have to make it appear on listings';
|
||||
break;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="media-title-banner">
|
||||
{displayViews && PageStore.get('config-options').pages.media.categoriesWithTitle
|
||||
? this.mediaCategories(true)
|
||||
: null}
|
||||
|
||||
{void 0 !== this.props.title ? <h1>{this.props.title}</h1> : null}
|
||||
|
||||
{'public' !== mediaState ? (
|
||||
<div className="media-labels-area">
|
||||
<div className="media-labels-area-inner">
|
||||
<span className="media-label-state">
|
||||
<span>{mediaState}</span>
|
||||
</span>
|
||||
<span className="helper-icon" data-tooltip={stateTooltip}>
|
||||
<i className="material-icons">help_outline</i>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
) : null}
|
||||
|
||||
<div
|
||||
className={
|
||||
'media-views-actions' +
|
||||
(this.state.likedMedia ? ' liked-media' : '') +
|
||||
(this.state.dislikedMedia ? ' disliked-media' : '')
|
||||
}
|
||||
>
|
||||
{!displayViews && PageStore.get('config-options').pages.media.categoriesWithTitle
|
||||
? this.mediaCategories()
|
||||
: null}
|
||||
|
||||
{displayViews ? (
|
||||
<div className="media-views">
|
||||
{formatViewsNumber(this.props.views, true)} {1 >= this.props.views ? 'view' : 'views'}
|
||||
</div>
|
||||
) : null}
|
||||
|
||||
<div className="media-actions">
|
||||
<div>
|
||||
{MemberContext._currentValue.can.likeMedia ? <MediaLikeIcon /> : null}
|
||||
{MemberContext._currentValue.can.dislikeMedia ? <MediaDislikeIcon /> : null}
|
||||
{MemberContext._currentValue.can.shareMedia ? <MediaShareButton isVideo={false} /> : null}
|
||||
|
||||
{!MemberContext._currentValue.is.anonymous &&
|
||||
MemberContext._currentValue.can.saveMedia &&
|
||||
-1 < PlaylistsContext._currentValue.mediaTypes.indexOf(MediaPageStore.get('media-type')) ? (
|
||||
<MediaSaveButton />
|
||||
) : null}
|
||||
|
||||
{!this.props.allowDownload || !MemberContext._currentValue.can.downloadMedia ? null : !this
|
||||
.downloadLink ? (
|
||||
<VideoMediaDownloadLink />
|
||||
) : (
|
||||
<OtherMediaDownloadLink link={this.downloadLink} title={this.props.title} />
|
||||
)}
|
||||
|
||||
<MediaMoreOptionsIcon allowDownload={this.props.allowDownload} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
ViewerInfoTitleBanner.propTypes = {
|
||||
allowDownload: PropTypes.bool.isRequired,
|
||||
};
|
||||
|
||||
ViewerInfoTitleBanner.defaultProps = {
|
||||
allowDownload: false,
|
||||
};
|
||||
49
frontend/src/static/js/components/media-page/ViewerInfoVideo.js
Executable file
49
frontend/src/static/js/components/media-page/ViewerInfoVideo.js
Executable file
@@ -0,0 +1,49 @@
|
||||
import React from 'react';
|
||||
import { MediaPageStore } from '../../utils/stores/';
|
||||
import ViewerInfoContent from './ViewerInfoContent';
|
||||
import ViewerInfoVideoTitleBanner from './ViewerInfoVideoTitleBanner';
|
||||
import ViewerInfo from './ViewerInfo';
|
||||
|
||||
export default class ViewerInfoVideo extends ViewerInfo {
|
||||
render() {
|
||||
let views, categories, title, author, published, description;
|
||||
let allowDownload = false;
|
||||
|
||||
if (this.state.videoLoaded) {
|
||||
allowDownload = MediaPageStore.get('media-data').allow_download;
|
||||
|
||||
if (void 0 === allowDownload) {
|
||||
allowDownload = true;
|
||||
} else {
|
||||
allowDownload = !!allowDownload;
|
||||
}
|
||||
|
||||
views = MediaPageStore.get('media-data').views;
|
||||
categories = MediaPageStore.get('media-data').categories_info;
|
||||
title = MediaPageStore.get('media-data').title;
|
||||
|
||||
author = {
|
||||
name: MediaPageStore.get('media-data').author_name,
|
||||
url: MediaPageStore.get('media-data').author_profile,
|
||||
thumb: MediaPageStore.get('media-author-thumbnail-url'),
|
||||
};
|
||||
|
||||
published = MediaPageStore.get('media-data').add_date;
|
||||
description = MediaPageStore.get('media-data').description;
|
||||
}
|
||||
|
||||
return !this.state.videoLoaded ? null : (
|
||||
<div className="viewer-info">
|
||||
<div className="viewer-info-inner">
|
||||
<ViewerInfoVideoTitleBanner
|
||||
title={title}
|
||||
views={views}
|
||||
categories={categories}
|
||||
allowDownload={allowDownload}
|
||||
/>
|
||||
<ViewerInfoContent author={author} published={published} description={description} />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
import React from 'react';
|
||||
import { formatViewsNumber } from '../../utils/helpers/';
|
||||
import { PageStore, MediaPageStore } from '../../utils/stores/';
|
||||
import { MemberContext, PlaylistsContext } from '../../utils/contexts/';
|
||||
import { MediaLikeIcon, MediaDislikeIcon, OtherMediaDownloadLink, VideoMediaDownloadLink, MediaSaveButton, MediaShareButton, MediaMoreOptionsIcon } from '../media-actions/';
|
||||
import ViewerInfoTitleBanner from './ViewerInfoTitleBanner';
|
||||
|
||||
export default class ViewerInfoVideoTitleBanner extends ViewerInfoTitleBanner {
|
||||
render() {
|
||||
const displayViews = PageStore.get('config-options').pages.media.displayViews && void 0 !== this.props.views;
|
||||
|
||||
const mediaState = MediaPageStore.get('media-data').state;
|
||||
|
||||
let stateTooltip = '';
|
||||
|
||||
switch (mediaState) {
|
||||
case 'private':
|
||||
stateTooltip = 'The site admins have to make its access public';
|
||||
break;
|
||||
case 'unlisted':
|
||||
stateTooltip = 'The site admins have to make it appear on listings';
|
||||
break;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="media-title-banner">
|
||||
{displayViews && PageStore.get('config-options').pages.media.categoriesWithTitle
|
||||
? this.mediaCategories(true)
|
||||
: null}
|
||||
|
||||
{void 0 !== this.props.title ? <h1>{this.props.title}</h1> : null}
|
||||
|
||||
{'public' !== mediaState ? (
|
||||
<div className="media-labels-area">
|
||||
<div className="media-labels-area-inner">
|
||||
<span className="media-label-state">
|
||||
<span>{mediaState}</span>
|
||||
</span>
|
||||
<span className="helper-icon" data-tooltip={stateTooltip}>
|
||||
<i className="material-icons">help_outline</i>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
) : null}
|
||||
|
||||
<div
|
||||
className={
|
||||
'media-views-actions' +
|
||||
(this.state.likedMedia ? ' liked-media' : '') +
|
||||
(this.state.dislikedMedia ? ' disliked-media' : '')
|
||||
}
|
||||
>
|
||||
{!displayViews && PageStore.get('config-options').pages.media.categoriesWithTitle
|
||||
? this.mediaCategories()
|
||||
: null}
|
||||
|
||||
{displayViews ? (
|
||||
<div className="media-views">
|
||||
{formatViewsNumber(this.props.views, true)} {1 >= this.props.views ? 'view' : 'views'}
|
||||
</div>
|
||||
) : null}
|
||||
|
||||
<div className="media-actions">
|
||||
<div>
|
||||
{MemberContext._currentValue.can.likeMedia ? <MediaLikeIcon /> : null}
|
||||
{MemberContext._currentValue.can.dislikeMedia ? <MediaDislikeIcon /> : null}
|
||||
{MemberContext._currentValue.can.shareMedia ? <MediaShareButton isVideo={true} /> : null}
|
||||
|
||||
{!MemberContext._currentValue.is.anonymous &&
|
||||
MemberContext._currentValue.can.saveMedia &&
|
||||
-1 < PlaylistsContext._currentValue.mediaTypes.indexOf(MediaPageStore.get('media-type')) ? (
|
||||
<MediaSaveButton />
|
||||
) : null}
|
||||
|
||||
{!this.props.allowDownload || !MemberContext._currentValue.can.downloadMedia ? null : !this
|
||||
.downloadLink ? (
|
||||
<VideoMediaDownloadLink />
|
||||
) : (
|
||||
<OtherMediaDownloadLink link={this.downloadLink} title={this.props.title} />
|
||||
)}
|
||||
|
||||
<MediaMoreOptionsIcon allowDownload={this.props.allowDownload} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
import React from 'react';
|
||||
import { MediaPageStore } from '../../utils/stores/';
|
||||
import { AutoPlay } from './AutoPlay';
|
||||
import { RelatedMedia } from './RelatedMedia';
|
||||
import PlaylistView from './PlaylistView';
|
||||
|
||||
export default class ViewerSidebar extends React.PureComponent {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
playlistData: props.playlistData,
|
||||
isPlaylistPage: !!props.playlistData,
|
||||
activeItem: 0,
|
||||
mediaType: MediaPageStore.get('media-type'),
|
||||
};
|
||||
|
||||
if (props.playlistData) {
|
||||
let i = 0;
|
||||
while (i < props.playlistData.playlist_media.length) {
|
||||
if (props.mediaId === props.playlistData.playlist_media[i].friendly_token) {
|
||||
this.state.activeItem = i + 1;
|
||||
break;
|
||||
}
|
||||
|
||||
i += 1;
|
||||
}
|
||||
}
|
||||
|
||||
this.onMediaLoad = this.onMediaLoad.bind(this);
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
MediaPageStore.on('loaded_media_data', this.onMediaLoad);
|
||||
}
|
||||
|
||||
onMediaLoad() {
|
||||
this.setState({
|
||||
mediaType: MediaPageStore.get('media-type'),
|
||||
});
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div className="viewer-sidebar">
|
||||
{this.state.isPlaylistPage ? (
|
||||
<PlaylistView activeItem={this.state.activeItem} playlistData={this.props.playlistData} />
|
||||
) : 'video' === this.state.mediaType || 'audio' === this.state.mediaType ? (
|
||||
<AutoPlay />
|
||||
) : null}
|
||||
<RelatedMedia hideFirst={!this.state.isPlaylistPage} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user