Merge pull request #10663 from strapi/chore/tutorial-box

Remove outdated tutorials videos
This commit is contained in:
cyril lopez 2021-07-28 13:41:37 +02:00 committed by GitHub
commit 4622909f5c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 27 additions and 550 deletions

View File

@ -168,6 +168,7 @@ class StrapiApp {
addSettingsLinks: this.addSettingsLinks,
getPlugin: this.getPlugin,
injectContentManagerComponent: this.injectContentManagerComponent,
injectAdminComponent: this.injectAdminComponent,
registerHook: this.registerHook,
});
}
@ -183,6 +184,7 @@ class StrapiApp {
addSettingsLinks: this.addSettingsLinks,
getPlugin: this.getPlugin,
injectContentManagerComponent: this.injectContentManagerComponent,
injectAdminComponent: this.injectAdminComponent,
registerHook: this.registerHook,
});
}
@ -299,6 +301,16 @@ class StrapiApp {
this.admin.injectionZones.contentManager[containerName][blockName].push(component);
};
injectAdminComponent = (containerName, blockName, component) => {
invariant(
this.admin.injectionZones.admin[containerName]?.[blockName],
`The ${containerName} ${blockName} zone is not defined in the admin`
);
invariant(component, 'A Component must be provided');
this.admin.injectionZones.admin[containerName][blockName].push(component);
};
/**
* Load the admin translations
* @returns {Object} The imported admin translations

View File

@ -1,150 +0,0 @@
import styled from 'styled-components';
const Li = styled.li`
display: block;
padding: 8px 20px;
cursor: pointer;
margin-top: 0;
&:hover {
background-color: #f7f8f8;
.title {
color: #0e7de7;
}
}
.txtWrapper,
.thumbWrapper {
display: inline-block;
vertical-align: middle;
}
.thumbWrapper {
position: relative;
width: 55px;
height: 38px;
background-color: #d8d8d8;
border-radius: 2px;
overflow: hidden;
.overlay {
position: absolute;
top: 0;
left: 0;
z-index: 1;
width: 100%;
height: 100%;
background-color: rgba(#0e7de7, 0.8);
}
img {
position: relative;
z-index: 0;
width: 100%;
height: 100%;
}
.play {
position: absolute;
top: calc(50% - 10px);
left: calc(50% - 10px);
width: 20px;
height: 20px;
background-color: #0e7de7;
border: 1px solid white;
text-align: center;
line-height: 20px;
border-radius: 50%;
z-index: 2;
&::before {
content: '\f04b';
display: inline-block;
vertical-align: top;
height: 100%;
font-family: 'FontAwesome';
color: white;
font-size: 10px;
margin-left: 3px;
line-height: 18px;
}
}
}
&.finished {
.title {
color: #919bae;
}
.thumbWrapper {
.overlay {
background-color: transparent;
}
img {
opacity: 0.6;
}
.play {
background-color: #5a9e06;
border-color: #5a9e06;
&::before {
content: '\f00c';
margin-left: 0;
font-size: 11px;
line-height: 20px;
}
}
}
}
.txtWrapper {
padding: 0 15px;
p {
font-size: 14px;
line-height: 26px;
font-family: Lato;
font-weight: 600;
}
.time {
color: #919bae;
font-family: Lato;
font-weight: bold;
font-size: 11px;
line-height: 11px;
}
}
.hiddenPlayerWrapper {
display: none;
}
.videoModal {
margin-right: auto !important;
margin-left: auto !important;
.videoModalHeader {
padding-bottom: 0;
border-bottom: 0;
> h5 {
font-family: Lato;
font-weight: bold !important;
font-size: 1.8rem !important;
line-height: 3.1rem;
color: #333740;
}
> button {
display: flex;
position: absolute;
right: 0;
top: 0;
margin-top: 0;
margin-right: 0;
padding: 10px;
cursor: pointer;
span {
line-height: 0.6em;
}
}
}
.videoPlayer {
> button {
top: 50%;
margin-top: -0.75em;
left: 50%;
margin-left: -1.5em;
}
}
}
`;
export default Li;

View File

@ -7,6 +7,7 @@
import React from 'react';
import { useIntl } from 'react-intl';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { InjectionZone } from '../../../shared/components';
import StyledLink from './StyledLink';
function StaticLinks() {
@ -38,6 +39,7 @@ function StaticLinks() {
</li>
);
})}
<InjectionZone area="admin.tutorials.links" />
</ul>
);
}

View File

@ -1,180 +0,0 @@
/**
*
* OnboardingList
*
*/
import React from 'react';
import PropTypes from 'prop-types';
import cn from 'classnames';
import { isNaN } from 'lodash';
import { Modal, ModalHeader, ModalBody } from 'reactstrap';
import { Player } from 'video-react';
import 'video-react/dist/video-react.css';
import Li from './Li';
/* eslint-disable */
class OnboardingVideo extends React.Component {
componentDidMount() {
if (this.hiddenPlayer.current) {
this.hiddenPlayer.current.subscribeToStateChange(this.handleChangeState);
}
}
hiddenPlayer = React.createRef();
player = React.createRef();
handleChangeState = (state, prevState) => {
const { duration } = state;
const { id } = this.props;
if (duration !== prevState.duration && !isNaN(duration)) {
this.props.setVideoDuration(id, duration);
}
};
handleChangeIsPlayingState = (state, prevState) => {
const { isActive } = state;
const { id } = this.props;
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);
};
handleModalOpen = () => {
this.player.current.subscribeToStateChange(this.handleChangeIsPlayingState);
this.player.current.play();
if (this.props.video.startTime === 0) {
const { player } = this.player.current.getState();
player.isActive = true;
this.props.didPlayVideo(this.props.id, this.props.video.startTime);
} else {
this.player.current.pause();
}
};
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();
}
};
getVideoTime = (duration, sign) => {
const operator = Math.floor(eval(duration + sign + 60));
if (operator < 10) {
return `0${operator}`;
}
return operator;
};
render() {
const { video } = this.props;
const time = isNaN(video.duration)
? '\xA0'
: `${Math.floor(video.duration / 60)}:${this.getVideoTime(video.duration, '%')}`;
return (
<Li
key={this.props.id}
onClick={this.props.onClick}
id={this.props.id}
className={cn(video.end && 'finished')}
>
<div className="thumbWrapper">
<img src={video.preview} alt="preview" />
<div className="overlay" />
<div className="play" />
</div>
<div className="txtWrapper">
<p className="title">{video.title}</p>
<p className="time">{time}</p>
</div>
<Modal
isOpen={video.isOpen}
toggle={this.props.onClick} // eslint-disable-line react/jsx-handler-names
className="videoModal"
onOpened={this.handleModalOpen}
onClosed={this.handleModalClose}
>
<ModalHeader
toggle={this.props.onClick} // eslint-disable-line react/jsx-handler-names
className="videoModalHeader"
>
{video.title}
</ModalHeader>
<ModalBody className="modalBodyHelper">
<div>
<Player
ref={this.player}
className="videoPlayer"
src={video.video}
startTime={video.startTime}
preload="auto"
onPause={this.handleVideoPause}
onplay={this.videoStart}
subscribeToStateChange={this.subscribeToStateChange}
/>
</div>
</ModalBody>
</Modal>
{!this.props.video.duration && (
<div className="hiddenPlayerWrapper">
<Player
ref={this.hiddenPlayer}
src={video.video}
preload="auto"
subscribeToStateChange={this.subscribeToStateChange}
/>
</div>
)}
</Li>
);
}
}
OnboardingVideo.defaultProps = {
didPlayVideo: () => {},
didStopVideo: () => {},
getVideoCurrentTime: () => {},
id: 0,
onClick: () => {},
setVideoDuration: () => {},
video: {},
};
OnboardingVideo.propTypes = {
didPlayVideo: PropTypes.func,
didStopVideo: PropTypes.func,
getVideoCurrentTime: PropTypes.func,
id: PropTypes.number,
onClick: PropTypes.func,
setVideoDuration: PropTypes.func,
video: PropTypes.object,
};
export default OnboardingVideo;

View File

@ -1,17 +1,10 @@
import React, { useEffect, useReducer } from 'react';
import { FormattedMessage } from 'react-intl';
import axios from 'axios';
import React, { useState } from 'react';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { faQuestion, faTimes } from '@fortawesome/free-solid-svg-icons';
import cn from 'classnames';
import { useTracking } from '@strapi/helper-plugin';
import { useConfigurations } from '../../hooks';
import formatVideoArray from './utils/formatAndStoreVideoArray';
import StaticLinks from './StaticLinks';
import Video from './Video';
import Wrapper from './Wrapper';
import reducer, { initialState } from './reducer';
const Onboarding = () => {
const { showTutorials } = useConfigurations();
@ -24,109 +17,15 @@ const Onboarding = () => {
};
const OnboardingVideos = () => {
const { trackUsage } = useTracking();
const [{ isLoading, isOpen, videos }, dispatch] = useReducer(reducer, initialState);
useEffect(() => {
const getData = async () => {
try {
const { data } = await axios.get('https://strapi.io/videos', {
timeout: 1000,
});
const { didWatchVideos, videos } = formatVideoArray(data);
dispatch({
type: 'GET_DATA_SUCCEEDED',
didWatchVideos,
videos,
});
} catch (err) {
console.error(err);
dispatch({
type: 'HIDE_VIDEO_ONBOARDING',
});
}
};
getData();
}, []);
// Hide the player in case of request error
if (isLoading) {
return null;
}
const [isOpen, setIsOpen] = useState(false);
const handleClick = () => {
const eventName = isOpen
? 'didOpenGetStartedVideoContainer'
: 'didCloseGetStartedVideoContainer';
dispatch({ type: 'SET_IS_OPEN' });
trackUsage(eventName);
setIsOpen(prev => !prev);
};
const handleClickOpenVideo = videoIndexToOpen => {
dispatch({
type: 'TOGGLE_VIDEO_MODAL',
videoIndexToOpen,
});
};
const handleUpdateVideoStartTime = (videoIndex, elapsedTime) => {
dispatch({
type: 'UPDATE_VIDEO_STARTED_TIME_AND_PLAYED_INFOS',
videoIndex,
elapsedTime,
});
};
const setVideoDuration = (videoIndex, duration) => {
dispatch({
type: 'SET_VIDEO_DURATION',
duration,
videoIndex,
});
};
const hasVideos = videos.length > 0;
const className = hasVideos ? 'visible' : 'hidden';
return (
<Wrapper className={className} isOpen={isOpen}>
<Wrapper className="visible" isOpen={isOpen}>
<div className={cn('videosContent', isOpen ? 'shown' : 'hide')}>
<div className="videosHeader">
<p>
<FormattedMessage id="app.components.Onboarding.title" />
</p>
<p>
{Math.floor((videos.filter(v => v.end).length * 100) / videos.length)}
<FormattedMessage id="app.components.Onboarding.label.completed" />
</p>
</div>
<ul className="onboardingList">
{videos.map((video, index) => (
<Video
key={video.id || index}
id={index}
video={video}
onClick={() => handleClickOpenVideo(index)}
setVideoDuration={(_, duration) => {
setVideoDuration(index, duration);
}}
getVideoCurrentTime={(_, elapsedTime) => {
handleUpdateVideoStartTime(index, elapsedTime);
}}
didPlayVideo={(_, elapsedTime) => {
const eventName = `didPlay${index}GetStartedVideo`;
trackUsage(eventName, { timestamp: elapsedTime });
}}
didStopVideo={(_, elapsedTime) => {
const eventName = `didStop${index}Video`;
trackUsage(eventName, { timestamp: elapsedTime });
}}
/>
))}
</ul>
<StaticLinks />
</div>
<div className="openBtn">

View File

@ -1,68 +0,0 @@
import produce from 'immer';
import set from 'lodash/set';
const initialState = {
isLoading: true,
isOpen: false,
videos: [],
};
const reducer = (state, action) =>
// eslint-disable-next-line consistent-return
produce(state, draftState => {
switch (action.type) {
case 'GET_DATA_SUCCEEDED': {
draftState.isOpen = !action.didWatchVideos;
draftState.isLoading = false;
draftState.videos = action.videos;
break;
}
case 'SET_IS_OPEN': {
draftState.isOpen = !state.isOpen;
break;
}
case 'SET_VIDEO_DURATION': {
set(draftState, ['videos', action.videoIndex, 'duration'], parseFloat(action.duration, 10));
break;
}
case 'TOGGLE_VIDEO_MODAL': {
const nextVideos = state.videos.map((video, index) => {
if (index === action.videoIndexToOpen) {
return { ...video, isOpen: !video.isOpen };
}
return { ...video, isOpen: false };
});
draftState.videos = nextVideos;
break;
}
case 'UPDATE_VIDEO_STARTED_TIME_AND_PLAYED_INFOS': {
const nextVideos = state.videos.map((video, index) => {
if (index !== action.videoIndex) {
return video;
}
const elapsedTime = parseFloat(action.elapsedTime, 10);
const videoDuration = parseFloat(video.duration, 10);
const percentElapsedTime = (elapsedTime * 100) / videoDuration;
const end = video.end === true ? video.end : percentElapsedTime > 80;
return { ...video, startTime: elapsedTime, end };
});
// Update the local storage and make sure that the modal video does not automatically open
localStorage.setItem(
'videos',
JSON.stringify(nextVideos.map(v => ({ ...v, isOpen: false })))
);
// Update the state
draftState.videos = nextVideos;
break;
}
default:
return draftState;
}
});
export default reducer;
export { initialState };

View File

@ -1,27 +0,0 @@
const formatVideosArray = array => {
const alreadyFetchedVideos = JSON.parse(localStorage.getItem('videos')) || [];
const didWatchVideos = alreadyFetchedVideos.length === array.length;
let videos;
if (!didWatchVideos) {
videos = array.map(video => {
return {
...video,
duration: null,
end: false,
isOpen: false,
key: video.order,
startTime: 0,
};
});
// Store the videos in the localStorage
localStorage.setItem('videos', JSON.stringify(videos));
} else {
videos = alreadyFetchedVideos;
}
return { didWatchVideos, videos };
};
export default formatVideosArray;

View File

@ -5,6 +5,12 @@
* @type {Object}
*/
const injectionZones = {
admin: {
// Temporary injection zone, support for the react-tour plugin in foodadvisor
tutorials: {
links: [],
},
},
contentManager: {
editView: { informations: [], 'right-links': [] },
listView: { actions: [], deleteModalAdditionalInfos: [] },

View File

@ -38,8 +38,8 @@
"@fortawesome/free-brands-svg-icons": "^5.15.3",
"@fortawesome/free-solid-svg-icons": "^5.15.3",
"@fortawesome/react-fontawesome": "^0.1.14",
"@strapi/helper-plugin": "3.6.5",
"@strapi/babel-plugin-switch-ee-ce": "1.0.0",
"@strapi/helper-plugin": "3.6.5",
"@strapi/utils": "3.6.5",
"axios": "^0.21.1",
"babel-loader": "8.2.2",
@ -123,7 +123,6 @@
"styled-components": "^5.2.3",
"terser-webpack-plugin": "4.2.3",
"url-loader": "4.1.1",
"video-react": "^0.13.2",
"webpack": "5.36.2",
"webpack-cli": "4.6.0",
"webpack-dev-server": "3.11.2",

View File

@ -1160,7 +1160,7 @@
dependencies:
regenerator-runtime "^0.13.4"
"@babel/runtime@^7.0.0", "@babel/runtime@^7.1.2", "@babel/runtime@^7.10.2", "@babel/runtime@^7.10.5", "@babel/runtime@^7.11.2", "@babel/runtime@^7.12.0", "@babel/runtime@^7.12.1", "@babel/runtime@^7.12.5", "@babel/runtime@^7.13.10", "@babel/runtime@^7.2.0", "@babel/runtime@^7.4.5", "@babel/runtime@^7.5.5", "@babel/runtime@^7.6.2", "@babel/runtime@^7.7.2", "@babel/runtime@^7.8.4", "@babel/runtime@^7.8.7", "@babel/runtime@^7.9.2":
"@babel/runtime@^7.0.0", "@babel/runtime@^7.1.2", "@babel/runtime@^7.10.2", "@babel/runtime@^7.10.5", "@babel/runtime@^7.11.2", "@babel/runtime@^7.12.0", "@babel/runtime@^7.12.1", "@babel/runtime@^7.12.5", "@babel/runtime@^7.13.10", "@babel/runtime@^7.2.0", "@babel/runtime@^7.5.5", "@babel/runtime@^7.6.2", "@babel/runtime@^7.7.2", "@babel/runtime@^7.8.4", "@babel/runtime@^7.8.7", "@babel/runtime@^7.9.2":
version "7.14.6"
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.14.6.tgz#535203bc0892efc7dec60bdc27b2ecf6e409062d"
integrity sha512-/PCB2uJ7oM44tz8YhC4Z/6PeOKXp4K588f+5M3clr1M4zbqztlo0XEfJ2LEzj/FgwfgGcIdl8n7YYjTCI0BYwg==
@ -6541,7 +6541,7 @@ class-utils@^0.3.5:
isobject "^3.0.0"
static-extend "^0.1.1"
classnames@^2.2.0, classnames@^2.2.3, classnames@^2.2.6, classnames@^2.3.1:
classnames@^2.2.0, classnames@^2.2.3, classnames@^2.3.1:
version "2.3.1"
resolved "https://registry.yarnpkg.com/classnames/-/classnames-2.3.1.tgz#dfcfa3891e306ec1dad105d0e88f4417b8535e8e"
integrity sha512-OlQdbZ7gLfGarSqxesMesDa5uz7KFbID8Kpq/SxIoNGDqY8lSYs0D+hhtBXhcdB3rcbXArFr7vlHheLk1voeNA==
@ -13609,11 +13609,6 @@ lodash.templatesettings@^4.0.0:
dependencies:
lodash._reinterpolate "^3.0.0"
lodash.throttle@^4.1.1:
version "4.1.1"
resolved "https://registry.yarnpkg.com/lodash.throttle/-/lodash.throttle-4.1.1.tgz#c23e91b710242ac70c37f1e1cda9274cc39bf2f4"
integrity sha1-wj6RtxAkKscMN/HhzaknTMOb8vQ=
lodash.topairs@^4.3.0:
version "4.3.0"
resolved "https://registry.yarnpkg.com/lodash.topairs/-/lodash.topairs-4.3.0.tgz#3b6deaa37d60fb116713c46c5f17ea190ec48d64"
@ -21369,17 +21364,6 @@ vfile@^4.0.0:
unist-util-stringify-position "^2.0.0"
vfile-message "^2.0.0"
video-react@^0.13.2:
version "0.13.9"
resolved "https://registry.yarnpkg.com/video-react/-/video-react-0.13.9.tgz#be397fc5dd7a50908368f0a47124904b87ee3307"
integrity sha512-nT8WjOGr3va7zJDR+OfpyqFpMrpMX3LfY3PuVyrt9ZdKDzlHgv9gQc/saAFb/pvImatzOs3+XA2GWrb5hXbTkg==
dependencies:
"@babel/runtime" "^7.4.5"
classnames "^2.2.6"
lodash.throttle "^4.1.1"
prop-types "^15.7.2"
redux "^4.0.1"
vm-browserify@^1.1.2:
version "1.1.2"
resolved "https://registry.yarnpkg.com/vm-browserify/-/vm-browserify-1.1.2.tgz#78641c488b8e6ca91a75f511e7a3b32a86e5dda0"