mirror of
https://github.com/strapi/strapi.git
synced 2025-11-01 10:23:34 +00:00
Some UI fixes
This commit is contained in:
parent
9e1620cab9
commit
e01dc0eee3
@ -15,8 +15,6 @@ import '../../../../node_modules/video-react/dist/video-react.css';
|
||||
import styles from './styles.scss';
|
||||
|
||||
class OnboardingVideo extends React.Component {
|
||||
player = React.createRef();
|
||||
hiddenPlayer = React.createRef();
|
||||
|
||||
componentDidMount() {
|
||||
this.hiddenPlayer.current.subscribeToStateChange(
|
||||
@ -24,6 +22,9 @@ class OnboardingVideo extends React.Component {
|
||||
);
|
||||
}
|
||||
|
||||
hiddenPlayer = React.createRef();
|
||||
player = React.createRef();
|
||||
|
||||
handleChangeState = (state, prevState) => {
|
||||
const { duration } = state;
|
||||
const { id } = this.props;
|
||||
@ -37,18 +38,17 @@ class OnboardingVideo extends React.Component {
|
||||
|
||||
const { isActive } = state;
|
||||
const { id } = this.props;
|
||||
// Manual play
|
||||
|
||||
if (isActive !== prevState.isActive && isActive) {
|
||||
this.props.didPlayVideo(id, this.props.video.startTime);
|
||||
}
|
||||
};
|
||||
|
||||
handleCurrentTimeChange = (curr) => {
|
||||
|
||||
this.props.getVideoCurrentTime(this.props.id, curr, this.props.video.duration);
|
||||
}
|
||||
|
||||
afterOpenModal = () => {
|
||||
handleModalOpen = () => {
|
||||
|
||||
this.player.current.subscribeToStateChange(
|
||||
this.handleChangeIsPlayingState,
|
||||
@ -65,24 +65,22 @@ class OnboardingVideo extends React.Component {
|
||||
}
|
||||
};
|
||||
|
||||
onModalClose = () => {
|
||||
|
||||
const { player } = this.player.current.getState();
|
||||
const paused = player.paused;
|
||||
|
||||
if (!paused) {
|
||||
this.videoPause();
|
||||
}
|
||||
};
|
||||
|
||||
videoPause = () => {
|
||||
|
||||
handleVideoPause = () => {
|
||||
const { player } = this.player.current.getState();
|
||||
const currTime = player.currentTime;
|
||||
this.handleCurrentTimeChange(currTime);
|
||||
this.props.didStopVideo(this.props.id, currTime);
|
||||
};
|
||||
|
||||
handleModalClose = () => {
|
||||
const { player } = this.player.current.getState();
|
||||
const paused = player.paused;
|
||||
|
||||
if (!paused) {
|
||||
this.handleVideoPause();
|
||||
}
|
||||
};
|
||||
|
||||
render() {
|
||||
const { video } = this.props;
|
||||
|
||||
@ -94,24 +92,24 @@ class OnboardingVideo extends React.Component {
|
||||
className={cn(styles.listItem, video.end ? styles.finished : '')}
|
||||
>
|
||||
<div className={styles.thumbWrapper}>
|
||||
<img src={video.preview} />
|
||||
<div className={styles.overlay}/>
|
||||
<img src={video.preview} alt="preview" />
|
||||
<div className={styles.overlay} />
|
||||
<div className={styles.play} />
|
||||
</div>
|
||||
<div className={styles.txtWrapper}>
|
||||
<p className={styles.title}>{video.title}</p>
|
||||
<p className={styles.time}>{isNaN(video.duration) ? ' ' : `${Math.floor(video.duration / 60)}:${Math.floor(video.duration)%60}`}</p>
|
||||
</div>
|
||||
|
||||
|
||||
<Modal
|
||||
isOpen={video.isOpen}
|
||||
toggle={this.props.onClick}
|
||||
toggle={this.props.onClick} // eslint-disable-line react/jsx-handler-names
|
||||
className={styles.videoModal}
|
||||
onOpened={this.afterOpenModal}
|
||||
onClosed={this.onModalClose}
|
||||
onOpened={this.handleModalOpen}
|
||||
onClosed={this.handleModalClose}
|
||||
>
|
||||
<ModalHeader
|
||||
toggle={this.props.onClick}
|
||||
toggle={this.props.onClick} // eslint-disable-line react/jsx-handler-names
|
||||
className={styles.videoModalHeader}
|
||||
>
|
||||
{video.title}
|
||||
@ -125,7 +123,7 @@ class OnboardingVideo extends React.Component {
|
||||
src={video.video}
|
||||
startTime={video.startTime}
|
||||
preload="auto"
|
||||
onPause={this.videoPause}
|
||||
onPause={this.handleVideoPause}
|
||||
onplay={this.videoStart}
|
||||
subscribeToStateChange={this.subscribeToStateChange}
|
||||
/>
|
||||
@ -150,21 +148,23 @@ class OnboardingVideo extends React.Component {
|
||||
}
|
||||
|
||||
OnboardingVideo.defaultProps = {
|
||||
currTime: 0,
|
||||
video: {},
|
||||
setVideoDuration: () => {},
|
||||
getVideoCurrentTime: () => {},
|
||||
didStopVideo: () => {},
|
||||
didPlayVideo: () => {},
|
||||
didStopVideo: () => {},
|
||||
getVideoCurrentTime: () => {},
|
||||
id: 0,
|
||||
onClick: () => {},
|
||||
setVideoDuration: () => {},
|
||||
video: {},
|
||||
};
|
||||
|
||||
OnboardingVideo.propTypes = {
|
||||
currTime: PropTypes.number,
|
||||
videos: PropTypes.object,
|
||||
setVideoDuration: PropTypes.func,
|
||||
getVideoCurrentTime: PropTypes.func,
|
||||
didStopVideo: PropTypes.func,
|
||||
didPlayVideo: PropTypes.func,
|
||||
didStopVideo: PropTypes.func,
|
||||
getVideoCurrentTime: PropTypes.func,
|
||||
id: PropTypes.number,
|
||||
onClick: PropTypes.func,
|
||||
setVideoDuration: PropTypes.func,
|
||||
video: PropTypes.object,
|
||||
};
|
||||
|
||||
export default OnboardingVideo;
|
||||
|
||||
@ -111,6 +111,7 @@ li.listItem {
|
||||
font-family: Lato;
|
||||
font-weight: bold!important;
|
||||
font-size: 1.8rem!important;
|
||||
color: #333740;
|
||||
}
|
||||
> button {
|
||||
margin-top: 0;
|
||||
|
||||
@ -1,67 +0,0 @@
|
||||
/**
|
||||
*
|
||||
* PopUpWarning
|
||||
*
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
// modal
|
||||
import { Button, Modal, ModalHeader, ModalBody } from 'reactstrap';
|
||||
import { Player } from 'video-react';
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
import styles from './styles.scss';
|
||||
|
||||
function PopUpVideo({
|
||||
content,
|
||||
isOpen,
|
||||
onConfirm,
|
||||
onlyConfirmButton,
|
||||
popUpWarningType,
|
||||
toggleModal,
|
||||
video,
|
||||
}) {
|
||||
return (
|
||||
<div className={styles.popUpWarningHelper}>
|
||||
<Player playsInline poster="/assets/poster.png" src={video} />
|
||||
|
||||
{/* <Modal
|
||||
isOpen={isOpen}
|
||||
toggle={toggleModal}
|
||||
className={styles.modalPosition}
|
||||
>
|
||||
<ModalHeader toggle={toggleModal} className={styles.popUpWarningHeader}>
|
||||
<FormattedMessage
|
||||
id={content.title || 'components.popUpWarning.title'}
|
||||
/>
|
||||
</ModalHeader>
|
||||
<ModalBody className={styles.modalBodyHelper}>
|
||||
<div>
|
||||
<video controls autoPlay src={src} />
|
||||
</div>
|
||||
</ModalBody>
|
||||
</Modal> */}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
PopUpVideo.propTypes = {
|
||||
content: PropTypes.shape({
|
||||
cancel: PropTypes.string,
|
||||
confirm: PropTypes.string,
|
||||
message: PropTypes.string,
|
||||
title: PropTypes.string,
|
||||
}),
|
||||
};
|
||||
|
||||
PopUpVideo.defaultProps = {
|
||||
content: {
|
||||
cancel: 'components.popUpWarning.button.cancel',
|
||||
confirm: 'components.popUpWarning.button.confirm',
|
||||
message: 'components.popUpWarning.message',
|
||||
title: 'components.popUpWarning.title',
|
||||
},
|
||||
};
|
||||
|
||||
export default PopUpVideo;
|
||||
@ -26,6 +26,6 @@ export function emitEvent(event, properties) {
|
||||
return {
|
||||
type: EMIT_EVENT,
|
||||
event,
|
||||
properties
|
||||
properties,
|
||||
};
|
||||
}
|
||||
@ -284,7 +284,7 @@ export class AdminPage extends React.Component {
|
||||
isOpen={this.props.blockApp && this.props.showGlobalAppBlocker}
|
||||
{...this.props.overlayBlockerData}
|
||||
/>
|
||||
{this.shouldDisplayLogout() && <Onboarding/>}
|
||||
{this.shouldDisplayLogout() && <Onboarding />}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@ -315,6 +315,7 @@ AdminPage.propTypes = {
|
||||
appPlugins: PropTypes.array,
|
||||
blockApp: PropTypes.bool.isRequired,
|
||||
disableGlobalOverlayBlocker: PropTypes.func.isRequired,
|
||||
emitEvent: PropTypes.func.isRequired,
|
||||
enableGlobalOverlayBlocker: PropTypes.func.isRequired,
|
||||
getAdminData: PropTypes.func.isRequired,
|
||||
hasUserPlugin: PropTypes.bool,
|
||||
|
||||
@ -16,7 +16,7 @@ function* emitter(action) {
|
||||
|
||||
if (uuid) {
|
||||
yield call(
|
||||
fetch,
|
||||
fetch, // eslint-disable-line no-undef
|
||||
requestURL,
|
||||
{
|
||||
method: 'POST',
|
||||
|
||||
@ -7,8 +7,8 @@
|
||||
export const GET_VIDEOS = 'StrapiAdmin/Onboarding/GET_VIDEOS';
|
||||
export const GET_VIDEOS_SUCCEEDED =
|
||||
'StrapiAdmin/Onboarding/GET_VIDEOS_SUCCEEDED';
|
||||
export const ON_CLICK = 'StrapiAdmin/Onboarding/ON_CLICK';
|
||||
export const SET_VIDEOS_DURATION = 'StrapiAdmin/Onboarding/SET_VIDEOS_DURATION';
|
||||
export const UPDATE_VIDEO_START_TIME = 'StrapiAdmin/Onboarding/UPDATE_VIDEO_START_TIME';
|
||||
export const SET_VIDEO_END = 'StrapiAdmin/Onboarding/SET_VIDEO_END';
|
||||
export const REMOVE_VIDEOS = 'StrapiAdmin/Onboarding/REMOVE_VIDEOS';
|
||||
export const ON_CLICK = 'StrapiAdmin/Onboarding/ON_CLICK';
|
||||
export const SET_VIDEOS_DURATION = 'StrapiAdmin/Onboarding/SET_VIDEOS_DURATION';
|
||||
export const UPDATE_VIDEO_START_TIME = 'StrapiAdmin/Onboarding/UPDATE_VIDEO_START_TIME';
|
||||
export const SET_VIDEO_END = 'StrapiAdmin/Onboarding/SET_VIDEO_END';
|
||||
export const REMOVE_VIDEOS = 'StrapiAdmin/Onboarding/REMOVE_VIDEOS';
|
||||
|
||||
@ -9,32 +9,73 @@ import PropTypes from 'prop-types';
|
||||
import cn from 'classnames';
|
||||
import { connect } from 'react-redux';
|
||||
import { bindActionCreators, compose } from 'redux';
|
||||
import { getVideos, onClick, setVideoDuration, updateVideoStartTime, setVideoEnd, removeVideos } from './actions';
|
||||
|
||||
import injectSaga from 'utils/injectSaga';
|
||||
import injectReducer from 'utils/injectReducer';
|
||||
import OnboardingVideo from 'components/OnboardingVideo';
|
||||
|
||||
import { getVideos, onClick, removeVideos, setVideoDuration, setVideoEnd, updateVideoStartTime } from './actions';
|
||||
import makeSelectOnboarding from './selectors';
|
||||
import reducer from './reducer';
|
||||
import saga from './saga';
|
||||
|
||||
import OnboardingVideo from 'components/OnboardingVideo';
|
||||
|
||||
import styles from './styles.scss';
|
||||
|
||||
export class Onboarding extends React.Component {
|
||||
state = { showVideos: false };
|
||||
state = { showVideos: false, percentage: 0 };
|
||||
|
||||
componentDidMount() {
|
||||
this.setState({ showVideos: true });
|
||||
this.props.getVideos();
|
||||
|
||||
}
|
||||
|
||||
|
||||
componentWillReceiveProps(){
|
||||
|
||||
if(!localStorage.getItem('onboarding')) {
|
||||
setTimeout(() => {
|
||||
this.setState({ showVideos: true });
|
||||
localStorage.setItem('onboarding', true);
|
||||
}, 500);
|
||||
}
|
||||
this.getCompletedPercentage();
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
this.props.removeVideos();
|
||||
localStorage.removeItem('videos');
|
||||
}
|
||||
|
||||
toggleVideos = () => {
|
||||
setVideoEnd = () => {
|
||||
this.setVideoEnd();
|
||||
}
|
||||
|
||||
getCompletedPercentage = () => {
|
||||
|
||||
let videosEnd = 0;
|
||||
let videos = JSON.parse(localStorage.getItem('videos'));
|
||||
|
||||
for (let i = 0; i < videos.length; i++) {
|
||||
let video = videos[i];
|
||||
|
||||
if (video.end) {
|
||||
videosEnd++;
|
||||
}
|
||||
}
|
||||
|
||||
this.setState({ percentage: Math.floor(videosEnd*100/videos.length)});
|
||||
}
|
||||
|
||||
didPlayVideo = (index, currTime) => {
|
||||
const eventName = `didPlay${index}GetStartedVideo`;
|
||||
this.context.emitEvent(eventName, {timestamp: currTime});
|
||||
}
|
||||
|
||||
didStopVideo = (index, currTime) => {
|
||||
const eventName = `didStop${index}Video`;
|
||||
this.context.emitEvent(eventName, {timestamp: currTime});
|
||||
}
|
||||
|
||||
|
||||
handleVideosToggle = () => {
|
||||
// Display videos card
|
||||
this.setState(prevState => ({ showVideos: !prevState.showVideos }));
|
||||
|
||||
@ -44,22 +85,6 @@ export class Onboarding extends React.Component {
|
||||
this.context.emitEvent(eventName);
|
||||
};
|
||||
|
||||
setVideoEnd = () => {
|
||||
this.setVideoEnd();
|
||||
}
|
||||
|
||||
didPlayVideo = (index, currTime) => {
|
||||
|
||||
const eventName = `didPlay${index}GetStartedVideo`;
|
||||
this.context.emitEvent(eventName, {timestamp: currTime});
|
||||
}
|
||||
|
||||
didStopVideo = (index, currTime) => {
|
||||
|
||||
const eventName = `didStop${index}Video`;
|
||||
this.context.emitEvent(eventName, {timestamp: currTime});
|
||||
}
|
||||
|
||||
updateLocalStorage = (index, current, duration) => {
|
||||
// Update store
|
||||
this.props.updateVideoStartTime(index, current);
|
||||
@ -67,8 +92,8 @@ export class Onboarding extends React.Component {
|
||||
// Update localStorage
|
||||
let videosTime = JSON.parse(localStorage.getItem('videos'));
|
||||
videosTime[index].startTime = current;
|
||||
|
||||
let percent = current * 100 / duration;
|
||||
|
||||
if (percent >= 80) {
|
||||
if (videosTime[index].end === false) {
|
||||
videosTime[index].end = true;
|
||||
@ -77,25 +102,21 @@ export class Onboarding extends React.Component {
|
||||
}
|
||||
|
||||
localStorage.setItem('videos', JSON.stringify(videosTime));
|
||||
this.getCompletedPercentage();
|
||||
};
|
||||
|
||||
// eslint-disable-line react/prefer-stateless-function
|
||||
// eslint-disable-line jsx-handler-names
|
||||
render() {
|
||||
const { videos, onClick, setVideoDuration } = this.props;
|
||||
|
||||
return (
|
||||
<div className={styles.videosWrapper}>
|
||||
<div
|
||||
className={cn(
|
||||
styles.videosContent,
|
||||
this.state.showVideos ? styles.shown : styles.hide,
|
||||
)}
|
||||
>
|
||||
<div className={cn(styles.videosWrapper, videos.length > 0 ? styles.visible : styles.hidden)}>
|
||||
<div className={cn(styles.videosContent, this.state.showVideos ? styles.shown : styles.hide)}>
|
||||
<div className={styles.videosHeader}>
|
||||
<p>Get started video</p>
|
||||
<p>25% completed</p>
|
||||
<p>{this.state.percentage}% completed</p>
|
||||
</div>
|
||||
|
||||
<ul className={styles.onboardingList}>
|
||||
{videos.map((video, i) => {
|
||||
return (
|
||||
@ -116,7 +137,7 @@ export class Onboarding extends React.Component {
|
||||
|
||||
<div className={styles.openBtn}>
|
||||
<button
|
||||
onClick={this.toggleVideos}
|
||||
onClick={this.handleVideosToggle}
|
||||
className={this.state.showVideos ? styles.active : ''}
|
||||
>
|
||||
<i className="fa fa-question" />
|
||||
@ -132,8 +153,23 @@ Onboarding.contextTypes = {
|
||||
emitEvent: PropTypes.func,
|
||||
};
|
||||
|
||||
Onboarding.defaultProps = {
|
||||
onClick: () => {},
|
||||
removeVideos: () => {},
|
||||
setVideoDuration: () => {},
|
||||
setVideoEnd: () => {},
|
||||
updateVideoStartTime: () => {},
|
||||
videos: [],
|
||||
};
|
||||
|
||||
Onboarding.propTypes = {
|
||||
getVideos: PropTypes.func.isRequired,
|
||||
onClick: PropTypes.func,
|
||||
removeVideos: PropTypes.func,
|
||||
setVideoDuration: PropTypes.func,
|
||||
setVideoEnd: PropTypes.func,
|
||||
updateVideoStartTime: PropTypes.func,
|
||||
videos: PropTypes.array,
|
||||
};
|
||||
|
||||
const mapStateToProps = makeSelectOnboarding();
|
||||
|
||||
@ -1,20 +1,20 @@
|
||||
import request from 'utils/request';
|
||||
import { all, call, fork, takeLatest, put } from 'redux-saga/effects';
|
||||
|
||||
import { GET_VIDEOS } from './constants';
|
||||
import { getVideosSucceeded } from './actions';
|
||||
import request from 'utils/request';
|
||||
|
||||
import { all, call, fork, takeLatest, put } from 'redux-saga/effects';
|
||||
|
||||
function* getVideos() {
|
||||
try {
|
||||
const videos = yield call(request, 'https://strapi.io/videos', {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
false,
|
||||
true,
|
||||
{ noAuth: true },
|
||||
},
|
||||
false,
|
||||
true,
|
||||
{ noAuth: true },
|
||||
);
|
||||
|
||||
let currTimes = Array.apply(null, Array(videos.length)).map((e, i) => {
|
||||
@ -23,7 +23,7 @@ function* getVideos() {
|
||||
end: false,
|
||||
key: i,
|
||||
id: videos[i].id,
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
// Retrieve start time if enable in localStorage
|
||||
@ -46,8 +46,7 @@ function* getVideos() {
|
||||
),
|
||||
);
|
||||
} catch (err) {
|
||||
console.log('err');
|
||||
console.log({ err });
|
||||
console.log(err); // eslint-disable-line no-console
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -15,6 +15,7 @@
|
||||
padding: 15px 15px 0 15px;
|
||||
p {
|
||||
font-family: Lato-Bold;
|
||||
letter-spacing: 0.5px;
|
||||
font-size: 11px;
|
||||
text-transform: uppercase;
|
||||
display: inline-block;
|
||||
@ -27,20 +28,25 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
&.visible {
|
||||
opacity: 1;
|
||||
}
|
||||
&.hidden {
|
||||
opacity: 0;
|
||||
}
|
||||
.videosContent {
|
||||
min-width: 320px;
|
||||
background-color: white;
|
||||
margin-bottom: 10px;
|
||||
margin-right: 15px;
|
||||
box-shadow: 0 2px 4px 0 #e3e9f3;
|
||||
border-radius: 3px;
|
||||
transition: all 0.3s linear;
|
||||
overflow: hidden;
|
||||
&.shown {
|
||||
visibility: visible;
|
||||
opacity: 1;
|
||||
animation: fadeIn 0.5s forwards;
|
||||
}
|
||||
&.hide {
|
||||
opacity: 0;
|
||||
visibility: hidden;
|
||||
animation: fadeOut 0.5s forwards;
|
||||
}
|
||||
|
||||
ul {
|
||||
@ -75,3 +81,36 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@keyframes fadeIn {
|
||||
0% {
|
||||
width: auto;
|
||||
height: auto;
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
5% {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
100% {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes fadeOut {
|
||||
0% {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
60% {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
100% {
|
||||
opacity: 0;
|
||||
width: 0;
|
||||
height: 0;
|
||||
}
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user