mirror of
https://github.com/mediacms-io/mediacms.git
synced 2025-11-22 06:17: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,58 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { TextsContext } from '../../utils/contexts/';
|
||||
import { MediaPageStore } from '../../utils/stores/';
|
||||
import { formatViewsNumber } from '../../utils/helpers/';
|
||||
import { PageActions, MediaPageActions } from '../../utils/actions/';
|
||||
import { CircleIconButton, MaterialIcon } from '../_shared/';
|
||||
|
||||
export function MediaDislikeIcon() {
|
||||
const [dislikedMedia, setDislikedMedia] = useState(MediaPageStore.get('user-liked-media'));
|
||||
const [dislikesCounter, setDislikesCounter] = useState(formatViewsNumber(MediaPageStore.get('media-likes'), false));
|
||||
|
||||
function updateStateValues() {
|
||||
setDislikedMedia(MediaPageStore.get('user-disliked-media'));
|
||||
setDislikesCounter(formatViewsNumber(MediaPageStore.get('media-dislikes'), false));
|
||||
}
|
||||
|
||||
function onCompleteMediaDislike() {
|
||||
updateStateValues();
|
||||
PageActions.addNotification(TextsContext._currentValue.messages.addToDisliked, 'mediaDislike');
|
||||
}
|
||||
|
||||
function onCompleteMediaDislikeCancel() {
|
||||
updateStateValues();
|
||||
PageActions.addNotification(TextsContext._currentValue.messages.removeFromDisliked, 'cancelMediaDislike');
|
||||
}
|
||||
|
||||
function onFailMediaDislikeRequest() {
|
||||
PageActions.addNotification('Action failed', 'mediaDislikeRequestFail');
|
||||
}
|
||||
|
||||
function toggleDislike(ev) {
|
||||
ev.preventDefault();
|
||||
ev.stopPropagation();
|
||||
MediaPageActions[dislikedMedia ? 'undislikeMedia' : 'dislikeMedia']();
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
MediaPageStore.on('disliked_media', onCompleteMediaDislike);
|
||||
MediaPageStore.on('undisliked_media', onCompleteMediaDislikeCancel);
|
||||
MediaPageStore.on('disliked_media_failed_request', onFailMediaDislikeRequest);
|
||||
return () => {
|
||||
MediaPageStore.removeListener('disliked_media', onCompleteMediaDislike);
|
||||
MediaPageStore.removeListener('undisliked_media', onCompleteMediaDislikeCancel);
|
||||
MediaPageStore.removeListener('disliked_media_failed_request', onFailMediaDislikeRequest);
|
||||
};
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div className="like">
|
||||
<button onClick={toggleDislike}>
|
||||
<CircleIconButton type="span">
|
||||
<MaterialIcon type="thumb_down" />
|
||||
</CircleIconButton>
|
||||
<span className="dislikes-counter">{dislikesCounter}</span>
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { TextsContext } from '../../utils/contexts/';
|
||||
import { MediaPageStore } from '../../utils/stores/';
|
||||
import { formatViewsNumber } from '../../utils/helpers/';
|
||||
import { PageActions, MediaPageActions } from '../../utils/actions/';
|
||||
import { CircleIconButton, MaterialIcon } from '../_shared/';
|
||||
|
||||
export function MediaLikeIcon() {
|
||||
const [likedMedia, setLikedMedia] = useState(MediaPageStore.get('user-liked-media'));
|
||||
const [likesCounter, setLikesCounter] = useState(formatViewsNumber(MediaPageStore.get('media-likes'), false));
|
||||
|
||||
function updateStateValues() {
|
||||
setLikedMedia(MediaPageStore.get('user-liked-media'));
|
||||
setLikesCounter(formatViewsNumber(MediaPageStore.get('media-likes'), false));
|
||||
}
|
||||
|
||||
function onCompleteMediaLike() {
|
||||
updateStateValues();
|
||||
PageActions.addNotification(TextsContext._currentValue.addToLiked, 'likedMedia');
|
||||
}
|
||||
|
||||
function onCompleteMediaLikeCancel() {
|
||||
updateStateValues();
|
||||
PageActions.addNotification(TextsContext._currentValue.removeFromLiked, 'unlikedMedia');
|
||||
}
|
||||
|
||||
function onFailMediaLikeRequest() {
|
||||
PageActions.addNotification('Action failed', 'likedMediaRequestFail');
|
||||
}
|
||||
|
||||
function toggleLike(ev) {
|
||||
ev.preventDefault();
|
||||
ev.stopPropagation();
|
||||
MediaPageActions[likedMedia ? 'unlikeMedia' : 'likeMedia']();
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
MediaPageStore.on('liked_media', onCompleteMediaLike);
|
||||
MediaPageStore.on('unliked_media', onCompleteMediaLikeCancel);
|
||||
MediaPageStore.on('liked_media_failed_request', onFailMediaLikeRequest);
|
||||
return () => {
|
||||
MediaPageStore.removeListener('liked_media', onCompleteMediaLike);
|
||||
MediaPageStore.removeListener('unliked_media', onCompleteMediaLikeCancel);
|
||||
MediaPageStore.removeListener('liked_media_failed_request', onFailMediaLikeRequest);
|
||||
};
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div className="like">
|
||||
<button onClick={toggleLike}>
|
||||
<CircleIconButton type="span">
|
||||
<MaterialIcon type="thumb_up" />
|
||||
</CircleIconButton>
|
||||
<span className="likes-counter">{likesCounter}</span>
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,350 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { formatInnerLink } from '../../utils/helpers/';
|
||||
import { usePopup, useUser } from '../../utils/hooks/';
|
||||
import { SiteContext } from '../../utils/contexts/';
|
||||
import { PageActions, MediaPageActions } from '../../utils/actions/';
|
||||
import { MediaPageStore } from '../../utils/stores/';
|
||||
import { CircleIconButton, MaterialIcon, NavigationContentApp, NavigationMenuList, PopupMain } from '../_shared/';
|
||||
import { ReportForm } from '../report-form/ReportForm';
|
||||
|
||||
function downloadOptions(mediaData, allowDownload) {
|
||||
const site = SiteContext._currentValue;
|
||||
|
||||
const encodingsInfo = mediaData.encodings_info;
|
||||
|
||||
const options = {};
|
||||
|
||||
let k, g;
|
||||
|
||||
for (k in encodingsInfo) {
|
||||
if (encodingsInfo.hasOwnProperty(k)) {
|
||||
if (Object.keys(encodingsInfo[k]).length) {
|
||||
for (g in encodingsInfo[k]) {
|
||||
if (encodingsInfo[k].hasOwnProperty(g)) {
|
||||
if ('success' === encodingsInfo[k][g].status && 100 === encodingsInfo[k][g].progress) {
|
||||
options[encodingsInfo[k][g].title] = {
|
||||
text: k + ' - ' + g.toUpperCase() + ' (' + encodingsInfo[k][g].size + ')',
|
||||
link: formatInnerLink(encodingsInfo[k][g].url, site.url),
|
||||
linkAttr: {
|
||||
target: '_blank',
|
||||
download: mediaData.title + '_' + k + '_' + g.toUpperCase(),
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
options.original_media_url = {
|
||||
text: 'Original file (' + mediaData.size + ')',
|
||||
link: formatInnerLink(mediaData.original_media_url, site.url),
|
||||
linkAttr: {
|
||||
target: '_blank',
|
||||
download: mediaData.title,
|
||||
},
|
||||
};
|
||||
|
||||
return Object.values(options);
|
||||
}
|
||||
|
||||
function optionsItems(userCan, mediaData, allowDownload, downloadLink, mediaReported) {
|
||||
|
||||
const items = [];
|
||||
|
||||
const mediaType = mediaData.media_type;
|
||||
const mediaIsVideo = 'video' === mediaType;
|
||||
const mediaReportedTimes = mediaData.reported_times;
|
||||
|
||||
if (allowDownload && userCan.downloadMedia) {
|
||||
if (!mediaIsVideo) {
|
||||
if (downloadLink) {
|
||||
items.push({
|
||||
itemType: 'link',
|
||||
link: downloadLink,
|
||||
text: 'Download',
|
||||
icon: 'arrow_downward',
|
||||
itemAttr: {
|
||||
className: 'visible-only-in-small',
|
||||
},
|
||||
linkAttr: {
|
||||
target: '_blank',
|
||||
download: mediaData.title,
|
||||
},
|
||||
});
|
||||
}
|
||||
} else {
|
||||
items.push({
|
||||
itemType: 'open-subpage',
|
||||
text: 'Download',
|
||||
icon: 'arrow_downward',
|
||||
itemAttr: {
|
||||
className: 'visible-only-in-small',
|
||||
},
|
||||
buttonAttr: {
|
||||
className: 'change-page',
|
||||
'data-page-id': 'videoDownloadOptions',
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (mediaIsVideo && userCan.editMedia) {
|
||||
items.push({
|
||||
itemType: 'open-subpage',
|
||||
text: 'Status info',
|
||||
icon: 'info',
|
||||
buttonAttr: {
|
||||
className: 'change-page',
|
||||
'data-page-id': 'mediaStatusInfo',
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
if (userCan.reportMedia) {
|
||||
if (mediaReported) {
|
||||
items.push({
|
||||
itemType: 'div',
|
||||
text: 'Reported',
|
||||
icon: 'flag',
|
||||
divAttr: {
|
||||
className: 'reported-label loggedin-media-reported',
|
||||
},
|
||||
});
|
||||
} else {
|
||||
items.push({
|
||||
itemType: 'open-subpage',
|
||||
text: 'Report',
|
||||
icon: 'flag',
|
||||
buttonAttr: {
|
||||
className: 'change-page' + (mediaReportedTimes ? ' loggedin-media-reported' : ''),
|
||||
'data-page-id': 'loggedInReportMedia',
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return items;
|
||||
}
|
||||
|
||||
function getPopupPages(userCan, mediaData, allowDownload, downloadLink, mediaReported, submitReportForm, cancelReportForm) {
|
||||
|
||||
const mediaUrl = mediaData.url;
|
||||
const mediaType = mediaData.media_type;
|
||||
const mediaState = mediaData.state || 'N/A';
|
||||
const mediaEncodingStatus = mediaData.encoding_status || 'N/A';
|
||||
const mediaReportedTimes = mediaData.reported_times;
|
||||
const mediaIsReviewed = mediaData.is_reviewed;
|
||||
|
||||
const mediaIsVideo = 'video' === mediaType;
|
||||
|
||||
const navItems = optionsItems(userCan, mediaData, allowDownload, downloadLink, mediaReported);
|
||||
|
||||
const pages = {};
|
||||
|
||||
if (navItems.length) {
|
||||
pages.main = (
|
||||
<div className="main-options">
|
||||
<PopupMain>
|
||||
<NavigationMenuList items={navItems} />
|
||||
</PopupMain>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (userCan.reportMedia) {
|
||||
pages.loggedInReportMedia = mediaReported ? null : (
|
||||
<div className="popup-fullscreen">
|
||||
<PopupMain>
|
||||
<span className="popup-fullscreen-overlay"></span>
|
||||
<div>
|
||||
<ReportForm mediaUrl={mediaUrl} submitReportForm={submitReportForm} cancelReportForm={cancelReportForm} />
|
||||
</div>
|
||||
</PopupMain>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (userCan.editMedia) {
|
||||
pages.mediaStatusInfo = (
|
||||
<div className="main-options">
|
||||
<PopupMain>
|
||||
<ul className="media-status-info">
|
||||
<li>
|
||||
Media type: <span>{mediaType}</span>
|
||||
</li>
|
||||
<li>
|
||||
State: <span>{mediaState}</span>
|
||||
</li>
|
||||
<li>
|
||||
Review state: <span>{mediaIsReviewed ? 'Is reviewed' : 'Pending review'}</span>
|
||||
</li>
|
||||
{mediaIsVideo ? (
|
||||
<li>
|
||||
Encoding Status: <span>{mediaEncodingStatus}</span>
|
||||
</li>
|
||||
) : null}
|
||||
{mediaReportedTimes ? (
|
||||
<li className="reports">
|
||||
Reports: <span>{mediaReportedTimes}</span>
|
||||
</li>
|
||||
) : null}
|
||||
</ul>
|
||||
</PopupMain>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (allowDownload && userCan.downloadMedia && mediaIsVideo) {
|
||||
pages.videoDownloadOptions = (
|
||||
<div className="video-download-options">
|
||||
<PopupMain>
|
||||
<NavigationMenuList items={downloadOptions(mediaData, allowDownload)} />
|
||||
</PopupMain>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return pages;
|
||||
}
|
||||
|
||||
const defaultContainerClassname = 'more-options active-options';
|
||||
|
||||
export function MediaMoreOptionsIcon(props) {
|
||||
const { userCan } = useUser();
|
||||
|
||||
const site = SiteContext._currentValue;
|
||||
|
||||
const downloadLink = formatInnerLink(MediaPageStore.get('media-original-url'), site.url);
|
||||
const mediaData = MediaPageStore.get('media-data');
|
||||
const mediaIsVideo = 'video' === mediaData.media_type;
|
||||
|
||||
const [popupContentRef, PopupContent, PopupTrigger] = usePopup();
|
||||
|
||||
const [visible, setVisible] = useState(false);
|
||||
const [reported, setReported] = useState(false);
|
||||
const [popupPages, setPopupPages] = useState({});
|
||||
const [popupCurrentPage, setPopupCurrentPage] = useState('main');
|
||||
const [containerClassname, setContainerClassname] = useState(defaultContainerClassname);
|
||||
|
||||
function submitReportForm(reportDescription) {
|
||||
MediaPageActions.reportMedia(reportDescription);
|
||||
}
|
||||
function cancelReportFormSubmission() {
|
||||
popupContentRef.current.toggle();
|
||||
}
|
||||
function onPopupPageChange(newPage) {
|
||||
setPopupCurrentPage(newPage);
|
||||
}
|
||||
function onPopupHide() {
|
||||
setPopupCurrentPage('main');
|
||||
}
|
||||
|
||||
function onCompleteMediaReport() {
|
||||
popupContentRef.current.tryToHide();
|
||||
// FIXME: Without delay creates conflict [ Uncaught Error: Dispatch.dispatch(...): Cannot dispatch in the middle of a dispatch. ].
|
||||
setTimeout(function () {
|
||||
PageActions.addNotification('Media Reported', 'reportedMedia');
|
||||
setReported(true);
|
||||
MediaPageStore.removeListener('reported_media', onCompleteMediaReport);
|
||||
}, 100);
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if (!reported) {
|
||||
if (visible) {
|
||||
MediaPageStore.on('reported_media', onCompleteMediaReport);
|
||||
} else {
|
||||
MediaPageStore.removeListener('reported_media', onCompleteMediaReport);
|
||||
}
|
||||
}
|
||||
}, [visible]);
|
||||
|
||||
useEffect(() => {
|
||||
setVisible(Object.keys(popupPages).length && props.allowDownload && userCan.downloadMedia);
|
||||
}, [popupPages]);
|
||||
|
||||
useEffect(() => {
|
||||
let classname = defaultContainerClassname;
|
||||
if (props.allowDownload && userCan.downloadMedia && 'videoDownloadOptions' === popupCurrentPage) {
|
||||
classname += ' video-downloads';
|
||||
}
|
||||
if (
|
||||
1 === Object.keys(popupPages).length &&
|
||||
props.allowDownload &&
|
||||
userCan.downloadMedia &&
|
||||
(mediaIsVideo || downloadLink)
|
||||
) {
|
||||
classname += ' visible-only-in-small';
|
||||
}
|
||||
setContainerClassname(classname);
|
||||
}, [popupCurrentPage]);
|
||||
|
||||
useEffect(() => {
|
||||
setPopupPages(
|
||||
getPopupPages(
|
||||
userCan,
|
||||
mediaData,
|
||||
props.allowDownload,
|
||||
downloadLink,
|
||||
reported,
|
||||
submitReportForm,
|
||||
cancelReportFormSubmission
|
||||
)
|
||||
);
|
||||
}, [reported]);
|
||||
|
||||
useEffect(() => {
|
||||
setPopupPages(
|
||||
getPopupPages(
|
||||
userCan,
|
||||
mediaData,
|
||||
props.allowDownload,
|
||||
downloadLink,
|
||||
reported,
|
||||
submitReportForm,
|
||||
cancelReportFormSubmission
|
||||
)
|
||||
);
|
||||
return () => {
|
||||
if (visible && !reported) {
|
||||
MediaPageStore.removeListener('reported_media', onCompleteMediaReport);
|
||||
}
|
||||
};
|
||||
}, []);
|
||||
|
||||
return !visible ? null : (
|
||||
<div className={containerClassname}>
|
||||
<PopupTrigger contentRef={popupContentRef}>
|
||||
<span>
|
||||
<CircleIconButton type="button">
|
||||
<MaterialIcon type="more_horiz" />
|
||||
</CircleIconButton>
|
||||
</span>
|
||||
</PopupTrigger>
|
||||
|
||||
<div className={'nav-page-' + popupCurrentPage}>
|
||||
<PopupContent contentRef={popupContentRef} hideCallback={onPopupHide}>
|
||||
<NavigationContentApp
|
||||
pageChangeCallback={onPopupPageChange}
|
||||
initPage={popupCurrentPage}
|
||||
focusFirstItemOnPageChange={false}
|
||||
pages={popupPages}
|
||||
pageChangeSelector={'.change-page'}
|
||||
pageIdSelectorAttr={'data-page-id'}
|
||||
/>
|
||||
</PopupContent>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
MediaMoreOptionsIcon.propTypes = {
|
||||
allowDownload: PropTypes.bool.isRequired,
|
||||
};
|
||||
|
||||
MediaMoreOptionsIcon.defaultProps = {
|
||||
allowDownload: false,
|
||||
};
|
||||
@@ -0,0 +1,58 @@
|
||||
import React, { useState } from 'react';
|
||||
import { usePopup } from '../../utils/hooks/';
|
||||
import { CircleIconButton, MaterialIcon, NavigationContentApp, PopupMain } from '../_shared/';
|
||||
import { PlaylistsSelection } from '../playlists-selection/PlaylistsSelection';
|
||||
|
||||
function mediaSavePopupPages(onTriggerPopupClose) {
|
||||
return {
|
||||
selectPlaylist: (
|
||||
<div className="popup-fullscreen">
|
||||
<PopupMain>
|
||||
<span className="popup-fullscreen-overlay"></span>
|
||||
<PlaylistsSelection triggerPopupClose={onTriggerPopupClose} />
|
||||
</PopupMain>
|
||||
</div>
|
||||
),
|
||||
createPlaylist: (
|
||||
<div className="popup-fullscreen">
|
||||
<PopupMain>
|
||||
<span className="popup-fullscreen-overlay"></span>
|
||||
</PopupMain>
|
||||
</div>
|
||||
),
|
||||
};
|
||||
}
|
||||
|
||||
export function MediaSaveButton(props) {
|
||||
const [popupContentRef, PopupContent, PopupTrigger] = usePopup();
|
||||
|
||||
const [popupCurrentPage, setPopupCurrentPage] = useState('selectPlaylist');
|
||||
|
||||
function triggerPopupClose() {
|
||||
popupContentRef.current.toggle();
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="save">
|
||||
<PopupTrigger contentRef={popupContentRef}>
|
||||
<button>
|
||||
<CircleIconButton type="span">
|
||||
<MaterialIcon type="playlist_add" />
|
||||
</CircleIconButton>
|
||||
<span>SAVE</span>
|
||||
</button>
|
||||
</PopupTrigger>
|
||||
|
||||
<PopupContent contentRef={popupContentRef}>
|
||||
<NavigationContentApp
|
||||
initPage={popupCurrentPage}
|
||||
pageChangeSelector={'.change-page'}
|
||||
pageIdSelectorAttr={'data-page-id'}
|
||||
pages={mediaSavePopupPages(triggerPopupClose)}
|
||||
focusFirstItemOnPageChange={false}
|
||||
pageChangeCallback={setPopupCurrentPage}
|
||||
/>
|
||||
</PopupContent>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
import React, { useState } from 'react';
|
||||
import { usePopup } from '../../utils/hooks/';
|
||||
import { CircleIconButton, MaterialIcon, NavigationContentApp, PopupMain } from '../_shared/';
|
||||
import { MediaShareEmbed } from './MediaShareEmbed';
|
||||
import { MediaShareOptions } from './MediaShareOptions';
|
||||
|
||||
function mediaSharePopupPages() {
|
||||
return {
|
||||
shareOptions: (
|
||||
<div className="popup-fullscreen">
|
||||
<PopupMain>
|
||||
<span className="popup-fullscreen-overlay"></span>
|
||||
<MediaShareOptions />
|
||||
</PopupMain>
|
||||
</div>
|
||||
),
|
||||
};
|
||||
}
|
||||
|
||||
function videoSharePopupPages(onTriggerPopupClose) {
|
||||
return {
|
||||
...mediaSharePopupPages(),
|
||||
shareEmbed: (
|
||||
<div className="popup-fullscreen share-embed-popup">
|
||||
<PopupMain>
|
||||
<span className="popup-fullscreen-overlay"></span>
|
||||
<MediaShareEmbed triggerPopupClose={onTriggerPopupClose} />
|
||||
</PopupMain>
|
||||
</div>
|
||||
),
|
||||
};
|
||||
}
|
||||
|
||||
export function MediaShareButton(props) {
|
||||
const [popupContentRef, PopupContent, PopupTrigger] = usePopup();
|
||||
|
||||
const [popupCurrentPage, setPopupCurrentPage] = useState('shareOptions');
|
||||
|
||||
function triggerPopupClose() {
|
||||
popupContentRef.current.toggle();
|
||||
}
|
||||
|
||||
function onPopupPageChange(newPage) {
|
||||
setPopupCurrentPage(newPage);
|
||||
}
|
||||
function onPopupHide() {
|
||||
setPopupCurrentPage('shareOptions');
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="share">
|
||||
<PopupTrigger contentRef={popupContentRef}>
|
||||
<button>
|
||||
<CircleIconButton type="span">
|
||||
<MaterialIcon type="share" />
|
||||
</CircleIconButton>
|
||||
<span>SHARE</span>
|
||||
</button>
|
||||
</PopupTrigger>
|
||||
|
||||
<PopupContent contentRef={popupContentRef} hideCallback={onPopupHide}>
|
||||
<NavigationContentApp
|
||||
initPage={popupCurrentPage}
|
||||
pageChangeSelector={'.change-page'}
|
||||
pageIdSelectorAttr={'data-page-id'}
|
||||
pages={props.isVideo ? videoSharePopupPages(triggerPopupClose) : mediaSharePopupPages()}
|
||||
focusFirstItemOnPageChange={false}
|
||||
pageChangeCallback={onPopupPageChange}
|
||||
/>
|
||||
</PopupContent>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,249 @@
|
||||
import React, { useRef, useState, useEffect, useContext } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { LinksContext } from '../../utils/contexts/';
|
||||
import { PageStore, MediaPageStore } from '../../utils/stores/';
|
||||
import { PageActions, MediaPageActions } from '../../utils/actions/';
|
||||
import { CircleIconButton, MaterialIcon, NumericInputWithUnit } from '../_shared/';
|
||||
import VideoViewer from '../media-viewer/VideoViewer';
|
||||
|
||||
export function MediaShareEmbed(props) {
|
||||
const embedVideoDimensions = PageStore.get('config-options').embedded.video.dimensions;
|
||||
|
||||
const links = useContext(LinksContext);
|
||||
|
||||
const aspectRatioValueRef = useRef(null);
|
||||
const onRightRef = useRef(null);
|
||||
const onRightTopRef = useRef(null);
|
||||
const onRightMiddleRef = useRef(null);
|
||||
const onRightBottomRef = useRef(null);
|
||||
|
||||
const [maxHeight, setMaxHeight] = useState(window.innerHeight - 144 + 56);
|
||||
const [keepAspectRatio, setKeepAspectRatio] = useState(false);
|
||||
const [aspectRatio, setAspectRatio] = useState('16:9');
|
||||
const [embedWidthValue, setEmbedWidthValue] = useState(embedVideoDimensions.width);
|
||||
const [embedWidthUnit, setEmbedWidthUnit] = useState(embedVideoDimensions.widthUnit);
|
||||
const [embedHeightValue, setEmbedHeightValue] = useState(embedVideoDimensions.height);
|
||||
const [embedHeightUnit, setEmbedHeightUnit] = useState(embedVideoDimensions.heightUnit);
|
||||
const [rightMiddlePositionTop, setRightMiddlePositionTop] = useState(60);
|
||||
const [rightMiddlePositionBottom, setRightMiddlePositionBottom] = useState(60);
|
||||
const [unitOptions, setUnitOptions] = useState([
|
||||
{ key: 'px', label: 'px' },
|
||||
{ key: 'percent', label: '%' },
|
||||
]);
|
||||
|
||||
function onClickCopyMediaLink() {
|
||||
MediaPageActions.copyEmbedMediaCode(onRightMiddleRef.current.querySelector('textarea'));
|
||||
}
|
||||
|
||||
function onClickEmbedShareExit() {
|
||||
if (void 0 !== props.triggerPopupClose) {
|
||||
props.triggerPopupClose();
|
||||
}
|
||||
}
|
||||
|
||||
function onEmbedWidthValueChange(newVal) {
|
||||
newVal = '' === newVal ? 0 : newVal;
|
||||
|
||||
const arr = aspectRatio.split(':');
|
||||
const x = arr[0];
|
||||
const y = arr[1];
|
||||
|
||||
setEmbedWidthValue(newVal);
|
||||
setEmbedHeightValue(keepAspectRatio ? parseInt((newVal * y) / x, 10) : embedHeightValue);
|
||||
}
|
||||
|
||||
function onEmbedWidthUnitChange(newVal) {
|
||||
setEmbedWidthUnit(newVal);
|
||||
}
|
||||
|
||||
function onEmbedHeightValueChange(newVal) {
|
||||
newVal = '' === newVal ? 0 : newVal;
|
||||
|
||||
const arr = aspectRatio.split(':');
|
||||
const x = arr[0];
|
||||
const y = arr[1];
|
||||
|
||||
setEmbedHeightValue(newVal);
|
||||
setEmbedWidthValue(keepAspectRatio ? parseInt((newVal * x) / y, 10) : embedWidthValue);
|
||||
}
|
||||
|
||||
function onEmbedHeightUnitChange(newVal) {
|
||||
setEmbedHeightUnit(newVal);
|
||||
}
|
||||
|
||||
function onKeepAspectRatioChange() {
|
||||
const newVal = !keepAspectRatio;
|
||||
|
||||
const arr = aspectRatio.split(':');
|
||||
const x = arr[0];
|
||||
const y = arr[1];
|
||||
|
||||
setKeepAspectRatio(newVal);
|
||||
setEmbedWidthUnit(newVal ? 'px' : embedWidthUnit);
|
||||
setEmbedHeightUnit(newVal ? 'px' : embedHeightUnit);
|
||||
setEmbedHeightValue(newVal ? parseInt((embedWidthValue * y) / x, 10) : embedHeightValue);
|
||||
setUnitOptions(
|
||||
newVal
|
||||
? [{ key: 'px', label: 'px' }]
|
||||
: [
|
||||
{ key: 'px', label: 'px' },
|
||||
{ key: 'percent', label: '%' },
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
function onAspectRatioChange() {
|
||||
const newVal = aspectRatioValueRef.current.value;
|
||||
|
||||
const arr = newVal.split(':');
|
||||
const x = arr[0];
|
||||
const y = arr[1];
|
||||
|
||||
setAspectRatio(newVal);
|
||||
setEmbedHeightValue(keepAspectRatio ? parseInt((embedWidthValue * y) / x, 10) : embedHeightValue);
|
||||
}
|
||||
|
||||
function onWindowResize() {
|
||||
setMaxHeight(window.innerHeight - 144 + 56);
|
||||
setRightMiddlePositionTop(onRightTopRef.current.offsetHeight);
|
||||
setRightMiddlePositionBottom(onRightBottomRef.current.offsetHeight);
|
||||
}
|
||||
|
||||
function onCompleteCopyMediaLink() {
|
||||
setTimeout(function () {
|
||||
PageActions.addNotification('Embed media code copied to clipboard', 'clipboardEmbedMediaCodeCopy');
|
||||
}, 100);
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
setMaxHeight(window.innerHeight - 144 + 56);
|
||||
setRightMiddlePositionTop(onRightTopRef.current.offsetHeight);
|
||||
setRightMiddlePositionBottom(onRightBottomRef.current.offsetHeight);
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
PageStore.on('window_resize', onWindowResize);
|
||||
MediaPageStore.on('copied_embed_media_code', onCompleteCopyMediaLink);
|
||||
return () => {
|
||||
PageStore.removeListener('window_resize', onWindowResize);
|
||||
MediaPageStore.removeListener('copied_embed_media_code', onCompleteCopyMediaLink);
|
||||
};
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div className="share-embed" style={{ maxHeight: maxHeight + 'px' }}>
|
||||
<div className="share-embed-inner">
|
||||
<div className="on-left">
|
||||
<div className="media-embed-wrap">
|
||||
<VideoViewer data={MediaPageStore.get('media-data')} inEmbed={true} />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div ref={onRightRef} className="on-right">
|
||||
<div ref={onRightTopRef} className="on-right-top">
|
||||
<div className="on-right-top-inner">
|
||||
<span className="ttl">Embed Video</span>
|
||||
<CircleIconButton type="button" onClick={onClickEmbedShareExit}>
|
||||
<MaterialIcon type="close" />
|
||||
</CircleIconButton>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
ref={onRightMiddleRef}
|
||||
className="on-right-middle"
|
||||
style={{ top: rightMiddlePositionTop + 'px', bottom: rightMiddlePositionBottom + 'px' }}
|
||||
>
|
||||
<textarea
|
||||
readOnly
|
||||
value={
|
||||
'<iframe width="' +
|
||||
('percent' === embedWidthUnit ? embedWidthValue + '%' : embedWidthValue) +
|
||||
'" height="' +
|
||||
('percent' === embedHeightUnit ? embedHeightValue + '%' : embedHeightValue) +
|
||||
'" src="' +
|
||||
links.embed +
|
||||
MediaPageStore.get('media-id') +
|
||||
'" frameborder="0" allowfullscreen></iframe>'
|
||||
}
|
||||
></textarea>
|
||||
|
||||
<div className="iframe-config">
|
||||
<div className="iframe-config-options-title">Embed options</div>
|
||||
|
||||
<div className="iframe-config-option">
|
||||
{/*<div className="option-title">
|
||||
<span>Dimensions</span>
|
||||
</div>*/}
|
||||
|
||||
<div className="option-content">
|
||||
<div className="ratio-options">
|
||||
<div className="options-group">
|
||||
<label style={{ minHeight: '36px' }}>
|
||||
<input type="checkbox" checked={keepAspectRatio} onChange={onKeepAspectRatioChange} />
|
||||
Keep aspect ratio
|
||||
</label>
|
||||
</div>
|
||||
|
||||
{!keepAspectRatio ? null : (
|
||||
<div className="options-group">
|
||||
<select ref={aspectRatioValueRef} onChange={onAspectRatioChange} value={aspectRatio}>
|
||||
<optgroup label="Horizontal orientation">
|
||||
<option value="16:9">16:9</option>
|
||||
<option value="4:3">4:3</option>
|
||||
<option value="3:2">3:2</option>
|
||||
</optgroup>
|
||||
<optgroup label="Vertical orientation">
|
||||
<option value="9:16">9:16</option>
|
||||
<option value="3:4">3:4</option>
|
||||
<option value="2:3">2:3</option>
|
||||
</optgroup>
|
||||
</select>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<br />
|
||||
|
||||
<div className="options-group">
|
||||
<NumericInputWithUnit
|
||||
valueCallback={onEmbedWidthValueChange}
|
||||
unitCallback={onEmbedWidthUnitChange}
|
||||
label={'Width'}
|
||||
defaultValue={parseInt(embedWidthValue, 10)}
|
||||
defaultUnit={embedWidthUnit}
|
||||
minValue={1}
|
||||
maxValue={99999}
|
||||
units={unitOptions}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="options-group">
|
||||
<NumericInputWithUnit
|
||||
valueCallback={onEmbedHeightValueChange}
|
||||
unitCallback={onEmbedHeightUnitChange}
|
||||
label={'Height'}
|
||||
defaultValue={parseInt(embedHeightValue, 10)}
|
||||
defaultUnit={embedHeightUnit}
|
||||
minValue={1}
|
||||
maxValue={99999}
|
||||
units={unitOptions}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div ref={onRightBottomRef} className="on-right-bottom">
|
||||
<button onClick={onClickCopyMediaLink}>COPY</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
MediaShareEmbed.propTypes = {
|
||||
triggerPopupClose: PropTypes.func,
|
||||
};
|
||||
@@ -0,0 +1,282 @@
|
||||
import React, { useRef, useState, useEffect } from 'react';
|
||||
import { ShareOptionsContext } from '../../utils/contexts/';
|
||||
import { PageStore, MediaPageStore } from '../../utils/stores/';
|
||||
import { PageActions, MediaPageActions } from '../../utils/actions/';
|
||||
import ItemsInlineSlider from '../item-list/includes/itemLists/ItemsInlineSlider';
|
||||
import { CircleIconButton } from '../_shared/';
|
||||
|
||||
function shareOptionsList() {
|
||||
const socialMedia = ShareOptionsContext._currentValue;
|
||||
const mediaUrl = MediaPageStore.get('media-url');
|
||||
const mediaTitle = MediaPageStore.get('media-data').title;
|
||||
|
||||
const ret = {};
|
||||
|
||||
let i = 0;
|
||||
|
||||
while (i < socialMedia.length) {
|
||||
switch (socialMedia[i]) {
|
||||
case 'embed':
|
||||
if ('video' === MediaPageStore.get('media-data').media_type) {
|
||||
ret[socialMedia[i]] = {};
|
||||
}
|
||||
break;
|
||||
case 'email':
|
||||
ret[socialMedia[i]] = {
|
||||
title: 'Email',
|
||||
shareUrl: 'mailto:?body=' + mediaUrl,
|
||||
};
|
||||
break;
|
||||
case 'fb':
|
||||
ret[socialMedia[i]] = {
|
||||
title: 'Facebook',
|
||||
shareUrl: 'https://www.facebook.com/sharer.php?u=' + mediaUrl,
|
||||
};
|
||||
break;
|
||||
case 'tw':
|
||||
ret[socialMedia[i]] = {
|
||||
title: 'Twitter',
|
||||
shareUrl: 'https://twitter.com/intent/tweet?url=' + mediaUrl,
|
||||
};
|
||||
break;
|
||||
case 'reddit':
|
||||
ret[socialMedia[i]] = {
|
||||
title: 'reddit',
|
||||
shareUrl: 'https://reddit.com/submit?url=' + mediaUrl + '&title=' + mediaTitle,
|
||||
};
|
||||
break;
|
||||
case 'tumblr':
|
||||
ret[socialMedia[i]] = {
|
||||
title: 'Tumblr',
|
||||
shareUrl: 'https://www.tumblr.com/widgets/share/tool?canonicalUrl=' + mediaUrl + '&title=' + mediaTitle,
|
||||
};
|
||||
break;
|
||||
case 'pinterest':
|
||||
ret[socialMedia[i]] = {
|
||||
title: 'Pinterest',
|
||||
shareUrl: 'http://pinterest.com/pin/create/link/?url=' + mediaUrl,
|
||||
};
|
||||
break;
|
||||
case 'vk':
|
||||
ret[socialMedia[i]] = {
|
||||
title: 'ВКонтакте',
|
||||
shareUrl: 'http://vk.com/share.php?url=' + mediaUrl + '&title=' + mediaTitle,
|
||||
};
|
||||
break;
|
||||
case 'linkedin':
|
||||
ret[socialMedia[i]] = {
|
||||
title: 'LinkedIn',
|
||||
shareUrl: 'https://www.linkedin.com/shareArticle?mini=true&url=' + mediaUrl,
|
||||
};
|
||||
break;
|
||||
case 'mix':
|
||||
ret[socialMedia[i]] = {
|
||||
title: 'Mix',
|
||||
shareUrl: 'https://mix.com/add?url=' + mediaUrl,
|
||||
};
|
||||
break;
|
||||
case 'whatsapp':
|
||||
ret[socialMedia[i]] = {
|
||||
title: 'WhatsApp',
|
||||
shareUrl: 'whatsapp://send?text=' + mediaUrl,
|
||||
};
|
||||
break;
|
||||
case 'telegram':
|
||||
ret[socialMedia[i]] = {
|
||||
title: 'Telegram',
|
||||
shareUrl: 'https://t.me/share/url?url=' + mediaUrl + '&text=' + mediaTitle,
|
||||
};
|
||||
break;
|
||||
}
|
||||
|
||||
i += 1;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
function ShareOptions() {
|
||||
const shareOptions = shareOptionsList();
|
||||
|
||||
const compList = [];
|
||||
|
||||
for (let k in shareOptions) {
|
||||
if (shareOptions.hasOwnProperty(k)) {
|
||||
if (k === 'embed') {
|
||||
compList.push(
|
||||
<div key={'share-' + k} className={'sh-option share-' + k + '-opt'}>
|
||||
<button className="sh-option change-page" data-page-id="shareEmbed">
|
||||
<span>
|
||||
<i className="material-icons">code</i>
|
||||
</span>
|
||||
<span>Embed</span>
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
} else if (k === 'whatsapp') {
|
||||
compList.push(
|
||||
<div key={'share-' + k} className={'sh-option share-' + k}>
|
||||
<a
|
||||
href={shareOptions[k].shareUrl}
|
||||
title=""
|
||||
target="_blank"
|
||||
data-action="share/whatsapp/share"
|
||||
rel="noreferrer"
|
||||
>
|
||||
<span></span>
|
||||
<span>{shareOptions[k].title}</span>
|
||||
</a>
|
||||
</div>
|
||||
);
|
||||
} else if (k === 'email') {
|
||||
compList.push(
|
||||
<div key="share-email" className="sh-option share-email">
|
||||
<a href={shareOptions[k].shareUrl} title="">
|
||||
<span>
|
||||
<i className="material-icons">email</i>
|
||||
</span>
|
||||
<span>{shareOptions[k].title}</span>
|
||||
</a>
|
||||
</div>
|
||||
);
|
||||
} else {
|
||||
compList.push(
|
||||
<div key={'share-' + k} className={'sh-option share-' + k}>
|
||||
<a href={shareOptions[k].shareUrl} title="" target="_blank" rel="noreferrer">
|
||||
<span></span>
|
||||
<span>{shareOptions[k].title}</span>
|
||||
</a>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return compList;
|
||||
}
|
||||
|
||||
function NextSlideButton({ onClick }) {
|
||||
return (
|
||||
<span className="next-slide">
|
||||
<CircleIconButton buttonShadow={true} onClick={onClick}>
|
||||
<i className="material-icons">keyboard_arrow_right</i>
|
||||
</CircleIconButton>
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
function PreviousSlideButton({ onClick }) {
|
||||
return (
|
||||
<span className="previous-slide">
|
||||
<CircleIconButton buttonShadow={true} onClick={onClick}>
|
||||
<i className="material-icons">keyboard_arrow_left</i>
|
||||
</CircleIconButton>
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
function updateDimensions() {
|
||||
return {
|
||||
maxFormContentHeight: window.innerHeight - (56 + 4 * 24 + 44),
|
||||
maxPopupWidth: 518 > window.innerWidth - 2 * 40 ? window.innerWidth - 2 * 40 : null,
|
||||
};
|
||||
}
|
||||
|
||||
export function MediaShareOptions(props) {
|
||||
const containerRef = useRef(null);
|
||||
const shareOptionsInnerRef = useRef(null);
|
||||
|
||||
const [inlineSlider, setInlineSlider] = useState(null);
|
||||
const [sliderButtonsVisible, setSliderButtonsVisible] = useState({ prev: false, next: false });
|
||||
|
||||
const [dimensions, setDimensions] = useState(updateDimensions());
|
||||
const [shareOptions] = useState(ShareOptions());
|
||||
|
||||
function onWindowResize() {
|
||||
setDimensions(updateDimensions());
|
||||
}
|
||||
|
||||
function onClickCopyMediaLink() {
|
||||
MediaPageActions.copyShareLink(containerRef.current.querySelector('.copy-field input'));
|
||||
}
|
||||
|
||||
function onCompleteCopyMediaLink() {
|
||||
// FIXME: Without delay throws conflict error [ Uncaught Error: Dispatch.dispatch(...): Cannot dispatch in the middle of a dispatch. ].
|
||||
setTimeout(function () {
|
||||
PageActions.addNotification('Link copied to clipboard', 'clipboardLinkCopy');
|
||||
}, 100);
|
||||
}
|
||||
|
||||
function updateSlider() {
|
||||
inlineSlider.scrollToCurrentSlide();
|
||||
updateSliderButtonsView();
|
||||
}
|
||||
|
||||
function updateSliderButtonsView() {
|
||||
setSliderButtonsVisible({
|
||||
prev: inlineSlider.hasPreviousSlide(),
|
||||
next: inlineSlider.hasNextSlide(),
|
||||
});
|
||||
}
|
||||
|
||||
function nextSlide() {
|
||||
inlineSlider.nextSlide();
|
||||
updateSlider();
|
||||
}
|
||||
|
||||
function prevSlide() {
|
||||
inlineSlider.previousSlide();
|
||||
updateSlider();
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
setInlineSlider(new ItemsInlineSlider(shareOptionsInnerRef.current, '.sh-option'));
|
||||
}, [shareOptions]);
|
||||
|
||||
useEffect(() => {
|
||||
if (inlineSlider) {
|
||||
inlineSlider.updateDataStateOnResize(shareOptions.length, true, true);
|
||||
updateSlider();
|
||||
}
|
||||
}, [dimensions, inlineSlider]);
|
||||
|
||||
useEffect(() => {
|
||||
PageStore.on('window_resize', onWindowResize);
|
||||
MediaPageStore.on('copied_media_link', onCompleteCopyMediaLink);
|
||||
|
||||
return () => {
|
||||
PageStore.removeListener('window_resize', onWindowResize);
|
||||
MediaPageStore.removeListener('copied_media_link', onCompleteCopyMediaLink);
|
||||
setInlineSlider(null);
|
||||
};
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div
|
||||
ref={containerRef}
|
||||
style={null !== dimensions.maxPopupWidth ? { maxWidth: dimensions.maxPopupWidth + 'px' } : null}
|
||||
>
|
||||
<div
|
||||
className="scrollable-content"
|
||||
style={null !== dimensions.maxFormContentHeight ? { maxHeight: dimensions.maxFormContentHeight + 'px' } : null}
|
||||
>
|
||||
<div className="share-popup-title">Share media</div>
|
||||
{shareOptions.length ? (
|
||||
<div className="share-options">
|
||||
{sliderButtonsVisible.prev ? <PreviousSlideButton onClick={prevSlide} /> : null}
|
||||
<div ref={shareOptionsInnerRef} className="share-options-inner">
|
||||
{shareOptions}
|
||||
</div>
|
||||
{sliderButtonsVisible.next ? <NextSlideButton onClick={nextSlide} /> : null}
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
<div className="copy-field">
|
||||
<div>
|
||||
<input type="text" readOnly value={MediaPageStore.get('media-url')} />
|
||||
<button onClick={onClickCopyMediaLink}>COPY</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { CircleIconButton, MaterialIcon } from '../_shared/';
|
||||
|
||||
export function OtherMediaDownloadLink(props) {
|
||||
return (
|
||||
<div className="download hidden-only-in-small">
|
||||
<a href={props.link} target="_blank" download={props.title} title="Download" rel="noreferrer">
|
||||
<CircleIconButton type="span">
|
||||
<MaterialIcon type="arrow_downward" />
|
||||
</CircleIconButton>
|
||||
<span>DOWNLOAD</span>
|
||||
</a>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
OtherMediaDownloadLink.propTypes = {
|
||||
link: PropTypes.string.isRequired,
|
||||
title: PropTypes.string.isRequired,
|
||||
};
|
||||
@@ -0,0 +1,92 @@
|
||||
import React, { useState } from 'react';
|
||||
import { usePopup } from '../../utils/hooks/';
|
||||
import { SiteContext } from '../../utils/contexts/';
|
||||
import { MediaPageStore } from '../../utils/stores/';
|
||||
import { formatInnerLink } from '../../utils/helpers/';
|
||||
import { CircleIconButton, MaterialIcon, NavigationContentApp, NavigationMenuList, PopupMain } from '../_shared/';
|
||||
|
||||
function downloadOptionsList() {
|
||||
const media_data = MediaPageStore.get('media-data');
|
||||
|
||||
const title = media_data.title;
|
||||
const encodings_info = media_data.encodings_info;
|
||||
|
||||
const optionsList = {};
|
||||
|
||||
let k, g;
|
||||
for (k in encodings_info) {
|
||||
if (encodings_info.hasOwnProperty(k)) {
|
||||
if (Object.keys(encodings_info[k]).length) {
|
||||
for (g in encodings_info[k]) {
|
||||
if (encodings_info[k].hasOwnProperty(g)) {
|
||||
if ('success' === encodings_info[k][g].status && 100 === encodings_info[k][g].progress) {
|
||||
optionsList[encodings_info[k][g].title] = {
|
||||
text: k + ' - ' + g.toUpperCase() + ' (' + encodings_info[k][g].size + ')',
|
||||
link: formatInnerLink(encodings_info[k][g].url, SiteContext._currentValue.url),
|
||||
linkAttr: {
|
||||
target: '_blank',
|
||||
download: media_data.title + '_' + k + '_' + g.toUpperCase(),
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
optionsList.original_media_url = {
|
||||
text: 'Original file (' + media_data.size + ')',
|
||||
link: formatInnerLink(media_data.original_media_url, SiteContext._currentValue.url),
|
||||
linkAttr: {
|
||||
target: '_blank',
|
||||
download: media_data.title,
|
||||
},
|
||||
};
|
||||
|
||||
return Object.values(optionsList);
|
||||
}
|
||||
|
||||
function downloadOptionsPages() {
|
||||
return {
|
||||
main: (
|
||||
<div className="main-options">
|
||||
<PopupMain>
|
||||
<NavigationMenuList items={downloadOptionsList()} />
|
||||
</PopupMain>
|
||||
</div>
|
||||
),
|
||||
};
|
||||
}
|
||||
|
||||
export function VideoMediaDownloadLink(props) {
|
||||
const [popupContentRef, PopupContent, PopupTrigger] = usePopup();
|
||||
|
||||
const [downloadOptionsCurrentPage, setDownloadOptionsCurrentPage] = useState('main');
|
||||
|
||||
return (
|
||||
<div className="video-downloads hidden-only-in-small">
|
||||
<PopupTrigger contentRef={popupContentRef}>
|
||||
<button>
|
||||
<CircleIconButton type="span">
|
||||
<MaterialIcon type="arrow_downward" />
|
||||
</CircleIconButton>
|
||||
<span>DOWNLOAD</span>
|
||||
</button>
|
||||
</PopupTrigger>
|
||||
|
||||
<div className={'nav-page-' + downloadOptionsCurrentPage}>
|
||||
<PopupContent contentRef={popupContentRef}>
|
||||
<NavigationContentApp
|
||||
pageChangeCallback={null}
|
||||
initPage="main"
|
||||
focusFirstItemOnPageChange={false}
|
||||
pages={downloadOptionsPages()}
|
||||
pageChangeSelector={'.change-page'}
|
||||
pageIdSelectorAttr={'data-page-id'}
|
||||
/>
|
||||
</PopupContent>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
9
frontend/src/static/js/components/media-actions/index.js
Normal file
9
frontend/src/static/js/components/media-actions/index.js
Normal file
@@ -0,0 +1,9 @@
|
||||
export * from './MediaDislikeIcon.jsx';
|
||||
export * from './MediaLikeIcon.jsx';
|
||||
export * from './MediaMoreOptionsIcon.jsx';
|
||||
export * from './MediaSaveButton.jsx';
|
||||
export * from './MediaShareButton.jsx';
|
||||
export * from './MediaShareEmbed.jsx';
|
||||
export * from './MediaShareOptions.jsx';
|
||||
export * from './OtherMediaDownloadLink.jsx';
|
||||
export * from './VideoMediaDownloadLink.jsx';
|
||||
Reference in New Issue
Block a user