mirror of
https://github.com/strapi/strapi.git
synced 2025-09-08 16:16:21 +00:00
Add events in the content-type builder
This commit is contained in:
commit
cd4313236c
@ -0,0 +1,126 @@
|
|||||||
|
/**
|
||||||
|
*
|
||||||
|
* OnboardingList
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
import React from 'react';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import cn from 'classnames';
|
||||||
|
|
||||||
|
import { Modal, ModalHeader, ModalBody } from 'reactstrap';
|
||||||
|
import { Player } from 'video-react';
|
||||||
|
import '../../../../node_modules/video-react/dist/video-react.css';
|
||||||
|
|
||||||
|
import styles from './styles.scss';
|
||||||
|
|
||||||
|
class OnboardingVideo extends React.Component {
|
||||||
|
hiddenPlayer = React.createRef();
|
||||||
|
player = React.createRef();
|
||||||
|
|
||||||
|
componentDidMount() {
|
||||||
|
this.hiddenPlayer.current.subscribeToStateChange(
|
||||||
|
this.handleChangeState,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
handleChangeState = (state, prevState) => {
|
||||||
|
const { duration } = state;
|
||||||
|
const { id } = this.props;
|
||||||
|
|
||||||
|
if (duration !== prevState.duration) {
|
||||||
|
this.props.setVideoDuration(id, duration);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
handleCurrentTimeChange = (curr) => {
|
||||||
|
|
||||||
|
this.props.getVideoCurrentTime(this.props.id, curr);
|
||||||
|
}
|
||||||
|
|
||||||
|
afterOpenModal = () => {
|
||||||
|
this.player.current.play();
|
||||||
|
};
|
||||||
|
|
||||||
|
onModalClose = () => {
|
||||||
|
|
||||||
|
const { player } = this.player.current.getState();
|
||||||
|
const currTime = player.currentTime;
|
||||||
|
this.handleCurrentTimeChange(currTime);
|
||||||
|
};
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const { video } = this.props;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<li
|
||||||
|
key={this.props.id}
|
||||||
|
onClick={this.props.onClick}
|
||||||
|
id={this.props.id}
|
||||||
|
className={cn(styles.listItem, video.end ? styles.finished : '')}
|
||||||
|
>
|
||||||
|
<div className={styles.thumbWrapper}>
|
||||||
|
<img src={video.preview} />
|
||||||
|
<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}
|
||||||
|
className={styles.videoModal}
|
||||||
|
onOpened={this.afterOpenModal}
|
||||||
|
onClosed={this.onModalClose}
|
||||||
|
>
|
||||||
|
<ModalHeader
|
||||||
|
toggle={this.props.onClick}
|
||||||
|
className={styles.videoModalHeader}
|
||||||
|
>
|
||||||
|
{video.title}
|
||||||
|
</ModalHeader>
|
||||||
|
<ModalBody className={styles.modalBodyHelper}>
|
||||||
|
<div>
|
||||||
|
<Player
|
||||||
|
ref={this.player}
|
||||||
|
poster="/assets/poster.png"
|
||||||
|
src={video.video}
|
||||||
|
startTime={video.startTime}
|
||||||
|
preload="auto"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</ModalBody>
|
||||||
|
</Modal>
|
||||||
|
{!video.duration && (
|
||||||
|
<div className={cn(styles.hiddenPlayerWrapper)}>
|
||||||
|
<Player
|
||||||
|
ref={this.hiddenPlayer}
|
||||||
|
poster="/assets/poster.png"
|
||||||
|
src={video.video}
|
||||||
|
preload="auto"
|
||||||
|
subscribeToStateChange={this.subscribeToStateChange}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</li>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
OnboardingVideo.defaultProps = {
|
||||||
|
currTime: 0,
|
||||||
|
video: {},
|
||||||
|
setVideoDuration: () => {},
|
||||||
|
getVideoCurrentTime: () => {},
|
||||||
|
};
|
||||||
|
|
||||||
|
OnboardingVideo.propTypes = {
|
||||||
|
currTime: PropTypes.number,
|
||||||
|
videos: PropTypes.object,
|
||||||
|
setVideoDuration: PropTypes.func,
|
||||||
|
getVideoCurrentTime: PropTypes.func,
|
||||||
|
};
|
||||||
|
|
||||||
|
export default OnboardingVideo;
|
@ -0,0 +1,100 @@
|
|||||||
|
li.listItem {
|
||||||
|
display: block;
|
||||||
|
padding: 10px 15px;
|
||||||
|
cursor: pointer;
|
||||||
|
&.finished {
|
||||||
|
.title {
|
||||||
|
color: #919bae;
|
||||||
|
}
|
||||||
|
.thumbWrapper {
|
||||||
|
.play {
|
||||||
|
background-color: #5a9e06;
|
||||||
|
&::before {
|
||||||
|
content: '\f00c';
|
||||||
|
font-size: 11px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
&:hover {
|
||||||
|
background-color: #f7f8f8;
|
||||||
|
.title {
|
||||||
|
color: #0e7de7;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.txtWrapper,
|
||||||
|
.thumbWrapper {
|
||||||
|
display: inline-block;
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
||||||
|
.thumbWrapper {
|
||||||
|
width: 55px;
|
||||||
|
height: 38px;
|
||||||
|
background-color: #d8d8d8;
|
||||||
|
border-radius: 2px;
|
||||||
|
overflow: hidden;
|
||||||
|
position: relative;
|
||||||
|
img {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
.play {
|
||||||
|
width: 20px;
|
||||||
|
height: 20px;
|
||||||
|
line-height: 20px;
|
||||||
|
border-radius: 50%;
|
||||||
|
background-color: #0e7de7;
|
||||||
|
position: absolute;
|
||||||
|
top: 50%;
|
||||||
|
margin-top: -10px;
|
||||||
|
left: 50%;
|
||||||
|
margin-left: -10px;
|
||||||
|
text-align: center;
|
||||||
|
&::before {
|
||||||
|
content: '\f04b';
|
||||||
|
font-family: 'FontAwesome';
|
||||||
|
font-size: 9px;
|
||||||
|
color: white;
|
||||||
|
display: inline-block;
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.txtWrapper {
|
||||||
|
padding: 0 15px;
|
||||||
|
p {
|
||||||
|
font-size: 14px;
|
||||||
|
font-family: Lato-SemiBold;
|
||||||
|
}
|
||||||
|
.time {
|
||||||
|
font-size: 11px;
|
||||||
|
color: #919bae;
|
||||||
|
font-family: Lato-Bold;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.modal .modal-dialog {
|
||||||
|
background: red;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hiddenPlayerWrapper {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.videoModal {
|
||||||
|
margin-right: auto !important;
|
||||||
|
margin-left: auto !important;
|
||||||
|
.videoModalHeader {
|
||||||
|
border-bottom: 0;
|
||||||
|
padding-bottom: 0;
|
||||||
|
> h5 {
|
||||||
|
font-family: Lato;
|
||||||
|
font-weight: bold!important;
|
||||||
|
font-size: 1.8rem!important;
|
||||||
|
}
|
||||||
|
> button {
|
||||||
|
margin-top: 0;
|
||||||
|
margin-right: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,11 @@
|
|||||||
|
// import OnboardingList from '../index';
|
||||||
|
|
||||||
|
import expect from 'expect';
|
||||||
|
// import { shallow } from 'enzyme';
|
||||||
|
// import React from 'react';
|
||||||
|
|
||||||
|
describe('<OnboardingList />', () => {
|
||||||
|
it('Expect to have unit tests specified', () => {
|
||||||
|
expect(true).toEqual(false);
|
||||||
|
});
|
||||||
|
});
|
File diff suppressed because one or more lines are too long
@ -0,0 +1,152 @@
|
|||||||
|
.videosWrapper {
|
||||||
|
position: fixed;
|
||||||
|
right: 15px;
|
||||||
|
bottom: 15px;
|
||||||
|
button,
|
||||||
|
button:focus,
|
||||||
|
a {
|
||||||
|
cursor: pointer;
|
||||||
|
outline: 0;
|
||||||
|
}
|
||||||
|
p {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
.videosHeader {
|
||||||
|
padding: 15px 15px 0 15px;
|
||||||
|
p {
|
||||||
|
font-family: Lato-Bold;
|
||||||
|
font-size: 11px;
|
||||||
|
text-transform: uppercase;
|
||||||
|
display: inline-block;
|
||||||
|
width: 50%;
|
||||||
|
vertical-align: top;
|
||||||
|
color: #5c5f66;
|
||||||
|
&:last-of-type {
|
||||||
|
text-align: right;
|
||||||
|
color: #5a9e06;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.videosContent {
|
||||||
|
background-color: white;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
margin-right: 15px;
|
||||||
|
box-shadow: 0 2px 4px 0 #e3e9f3;
|
||||||
|
border-radius: 3px;
|
||||||
|
transition: all 0.3s linear;
|
||||||
|
&.shown {
|
||||||
|
visibility: visible;
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
&.hide {
|
||||||
|
opacity: 0;
|
||||||
|
visibility: hidden;
|
||||||
|
}
|
||||||
|
ul {
|
||||||
|
list-style: none;
|
||||||
|
padding: 10px 0;
|
||||||
|
li {
|
||||||
|
&.finished {
|
||||||
|
.title {
|
||||||
|
color: #919bae;
|
||||||
|
}
|
||||||
|
.thumbWrapper {
|
||||||
|
.play {
|
||||||
|
background-color: #5a9e06;
|
||||||
|
&::before {
|
||||||
|
content: '\f00c';
|
||||||
|
font-size: 11px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
a {
|
||||||
|
display: block;
|
||||||
|
padding: 10px 15px;
|
||||||
|
&:hover {
|
||||||
|
background-color: #f7f8f8;
|
||||||
|
.title {
|
||||||
|
color: #0e7de7;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.txtWrapper,
|
||||||
|
.thumbWrapper {
|
||||||
|
display: inline-block;
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
||||||
|
.thumbWrapper {
|
||||||
|
width: 55px;
|
||||||
|
height: 38px;
|
||||||
|
background-color: #d8d8d8;
|
||||||
|
border-radius: 2px;
|
||||||
|
overflow: hidden;
|
||||||
|
position: relative;
|
||||||
|
img {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
.play {
|
||||||
|
width: 20px;
|
||||||
|
height: 20px;
|
||||||
|
line-height: 20px;
|
||||||
|
border-radius: 50%;
|
||||||
|
background-color: #0e7de7;
|
||||||
|
position: absolute;
|
||||||
|
top: 50%;
|
||||||
|
margin-top: -10px;
|
||||||
|
left: 50%;
|
||||||
|
margin-left: -10px;
|
||||||
|
text-align: center;
|
||||||
|
&::before {
|
||||||
|
content: '\f04b';
|
||||||
|
font-family: 'FontAwesome';
|
||||||
|
font-size: 9px;
|
||||||
|
color: white;
|
||||||
|
display: inline-block;
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.txtWrapper {
|
||||||
|
padding: 0 15px;
|
||||||
|
p {
|
||||||
|
font-size: 14px;
|
||||||
|
font-family: Lato-SemiBold;
|
||||||
|
}
|
||||||
|
.time {
|
||||||
|
font-size: 11px;
|
||||||
|
color: #919bae;
|
||||||
|
font-family: Lato-Bold;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.openBtn {
|
||||||
|
width: 38px;
|
||||||
|
height: 38px;
|
||||||
|
float: right;
|
||||||
|
button {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
border-radius: 50%;
|
||||||
|
color: white;
|
||||||
|
background: #0e7de7;
|
||||||
|
-webkit-box-shadow: 0px 2px 4px 0px rgba(227, 233, 243, 1);
|
||||||
|
-moz-box-shadow: 0px 2px 4px 0px rgba(227, 233, 243, 1);
|
||||||
|
box-shadow: 0px 2px 4px 0px rgba(227, 233, 243, 1);
|
||||||
|
i:last-of-type {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
&.active {
|
||||||
|
i:first-of-type {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
i:last-of-type {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,67 @@
|
|||||||
|
/**
|
||||||
|
*
|
||||||
|
* 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;
|
File diff suppressed because one or more lines are too long
@ -6,6 +6,7 @@
|
|||||||
import {
|
import {
|
||||||
GET_ADMIN_DATA,
|
GET_ADMIN_DATA,
|
||||||
GET_ADMIN_DATA_SUCCEEDED,
|
GET_ADMIN_DATA_SUCCEEDED,
|
||||||
|
EMIT_EVENT,
|
||||||
} from './constants';
|
} from './constants';
|
||||||
|
|
||||||
export function getAdminData() {
|
export function getAdminData() {
|
||||||
@ -20,3 +21,11 @@ export function getAdminDataSucceeded(data) {
|
|||||||
data,
|
data,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function emitEvent(event, properties) {
|
||||||
|
return {
|
||||||
|
type: EMIT_EVENT,
|
||||||
|
event,
|
||||||
|
properties,
|
||||||
|
};
|
||||||
|
}
|
@ -6,3 +6,4 @@
|
|||||||
|
|
||||||
export const GET_ADMIN_DATA = 'app/Admin/GET_ADMIN_DATA';
|
export const GET_ADMIN_DATA = 'app/Admin/GET_ADMIN_DATA';
|
||||||
export const GET_ADMIN_DATA_SUCCEEDED = 'app/Admin/GET_ADMIN_DATA_SUCCEEDED';
|
export const GET_ADMIN_DATA_SUCCEEDED = 'app/Admin/GET_ADMIN_DATA_SUCCEEDED';
|
||||||
|
export const EMIT_EVENT = 'app/Admin/EMIT_EVENT';
|
@ -38,6 +38,7 @@ import Content from 'containers/Content';
|
|||||||
import LocaleToggle from 'containers/LocaleToggle';
|
import LocaleToggle from 'containers/LocaleToggle';
|
||||||
import CTAWrapper from 'components/CtaWrapper';
|
import CTAWrapper from 'components/CtaWrapper';
|
||||||
import Header from 'components/Header/index';
|
import Header from 'components/Header/index';
|
||||||
|
import Onboarding from 'containers/Onboarding';
|
||||||
import HomePage from 'containers/HomePage/Loadable';
|
import HomePage from 'containers/HomePage/Loadable';
|
||||||
import Marketplace from 'containers/Marketplace/Loadable';
|
import Marketplace from 'containers/Marketplace/Loadable';
|
||||||
import LeftMenu from 'containers/LeftMenu';
|
import LeftMenu from 'containers/LeftMenu';
|
||||||
@ -52,7 +53,7 @@ import FullStory from 'components/FullStory';
|
|||||||
import auth from 'utils/auth';
|
import auth from 'utils/auth';
|
||||||
import injectReducer from 'utils/injectReducer';
|
import injectReducer from 'utils/injectReducer';
|
||||||
import injectSaga from 'utils/injectSaga';
|
import injectSaga from 'utils/injectSaga';
|
||||||
import { getAdminData } from './actions';
|
import { emitEvent, getAdminData } from './actions';
|
||||||
import reducer from './reducer';
|
import reducer from './reducer';
|
||||||
import saga from './saga';
|
import saga from './saga';
|
||||||
import selectAdminPage from './selectors';
|
import selectAdminPage from './selectors';
|
||||||
@ -70,6 +71,7 @@ export class AdminPage extends React.Component {
|
|||||||
getChildContext = () => ({
|
getChildContext = () => ({
|
||||||
currentEnvironment: this.props.adminPage.currentEnvironment,
|
currentEnvironment: this.props.adminPage.currentEnvironment,
|
||||||
disableGlobalOverlayBlocker: this.props.disableGlobalOverlayBlocker,
|
disableGlobalOverlayBlocker: this.props.disableGlobalOverlayBlocker,
|
||||||
|
emitEvent: this.props.emitEvent,
|
||||||
enableGlobalOverlayBlocker: this.props.enableGlobalOverlayBlocker,
|
enableGlobalOverlayBlocker: this.props.enableGlobalOverlayBlocker,
|
||||||
plugins: this.props.plugins,
|
plugins: this.props.plugins,
|
||||||
updatePlugin: this.props.updatePlugin,
|
updatePlugin: this.props.updatePlugin,
|
||||||
@ -267,7 +269,11 @@ export class AdminPage extends React.Component {
|
|||||||
<Route path="/plugins/:pluginId" component={PluginPage} />
|
<Route path="/plugins/:pluginId" component={PluginPage} />
|
||||||
<Route path="/plugins" component={ComingSoonPage} />
|
<Route path="/plugins" component={ComingSoonPage} />
|
||||||
<Route path="/list-plugins" component={ListPluginsPage} exact />
|
<Route path="/list-plugins" component={ListPluginsPage} exact />
|
||||||
<Route path="/marketplace" render={this.renderMarketPlace} exact />
|
<Route
|
||||||
|
path="/marketplace"
|
||||||
|
render={this.renderMarketPlace}
|
||||||
|
exact
|
||||||
|
/>
|
||||||
<Route path="/configuration" component={ComingSoonPage} exact />
|
<Route path="/configuration" component={ComingSoonPage} exact />
|
||||||
<Route path="" component={NotFoundPage} />
|
<Route path="" component={NotFoundPage} />
|
||||||
<Route path="404" component={NotFoundPage} />
|
<Route path="404" component={NotFoundPage} />
|
||||||
@ -278,6 +284,7 @@ export class AdminPage extends React.Component {
|
|||||||
isOpen={this.props.blockApp && this.props.showGlobalAppBlocker}
|
isOpen={this.props.blockApp && this.props.showGlobalAppBlocker}
|
||||||
{...this.props.overlayBlockerData}
|
{...this.props.overlayBlockerData}
|
||||||
/>
|
/>
|
||||||
|
<Onboarding />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -285,6 +292,7 @@ export class AdminPage extends React.Component {
|
|||||||
|
|
||||||
AdminPage.childContextTypes = {
|
AdminPage.childContextTypes = {
|
||||||
currentEnvironment: PropTypes.string.isRequired,
|
currentEnvironment: PropTypes.string.isRequired,
|
||||||
|
emitEvent: PropTypes.func,
|
||||||
disableGlobalOverlayBlocker: PropTypes.func,
|
disableGlobalOverlayBlocker: PropTypes.func,
|
||||||
enableGlobalOverlayBlocker: PropTypes.func,
|
enableGlobalOverlayBlocker: PropTypes.func,
|
||||||
plugins: PropTypes.object,
|
plugins: PropTypes.object,
|
||||||
@ -336,6 +344,7 @@ function mapDispatchToProps(dispatch) {
|
|||||||
return bindActionCreators(
|
return bindActionCreators(
|
||||||
{
|
{
|
||||||
disableGlobalOverlayBlocker,
|
disableGlobalOverlayBlocker,
|
||||||
|
emitEvent,
|
||||||
enableGlobalOverlayBlocker,
|
enableGlobalOverlayBlocker,
|
||||||
getAdminData,
|
getAdminData,
|
||||||
pluginLoaded,
|
pluginLoaded,
|
||||||
|
@ -11,11 +11,13 @@ import {
|
|||||||
} from './constants';
|
} from './constants';
|
||||||
|
|
||||||
const initialState = fromJS({
|
const initialState = fromJS({
|
||||||
uuid: false,
|
uuid: true,
|
||||||
currentEnvironment: 'development',
|
currentEnvironment: 'development',
|
||||||
isLoading: true,
|
isLoading: true,
|
||||||
layout: Map({}),
|
layout: Map({}),
|
||||||
strapiVersion: '3',
|
strapiVersion: '3',
|
||||||
|
eventName: '',
|
||||||
|
shouldEmit: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
function adminPageReducer(state = initialState, action) {
|
function adminPageReducer(state = initialState, action) {
|
||||||
|
@ -5,7 +5,35 @@ import { makeSelectAppPlugins } from 'containers/App/selectors';
|
|||||||
import {
|
import {
|
||||||
getAdminDataSucceeded,
|
getAdminDataSucceeded,
|
||||||
} from './actions';
|
} from './actions';
|
||||||
import { GET_ADMIN_DATA } from './constants';
|
import { makeSelectUuid } from './selectors';
|
||||||
|
import { EMIT_EVENT, GET_ADMIN_DATA } from './constants';
|
||||||
|
|
||||||
|
function* emitter(action) {
|
||||||
|
try {
|
||||||
|
const requestURL = 'https://analytics.strapi.io/track';
|
||||||
|
const uuid = yield select(makeSelectUuid());
|
||||||
|
const { event, properties } = action;
|
||||||
|
|
||||||
|
if (uuid) {
|
||||||
|
yield call(
|
||||||
|
fetch,
|
||||||
|
requestURL,
|
||||||
|
{
|
||||||
|
method: 'POST',
|
||||||
|
body: JSON.stringify({ event, uuid, properties }),
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
false,
|
||||||
|
true,
|
||||||
|
{ noAuth: true },
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} catch(err) {
|
||||||
|
console.log(err); // eslint-disable-line no-console
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function* getData() {
|
function* getData() {
|
||||||
try {
|
try {
|
||||||
@ -32,6 +60,7 @@ function* getData() {
|
|||||||
function* defaultSaga() {
|
function* defaultSaga() {
|
||||||
yield all([
|
yield all([
|
||||||
fork(takeLatest, GET_ADMIN_DATA, getData),
|
fork(takeLatest, GET_ADMIN_DATA, getData),
|
||||||
|
fork(takeLatest, EMIT_EVENT, emitter),
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -14,9 +14,15 @@ const selectAdminPageDomain = () => state => state.get('adminPage');
|
|||||||
* Default selector used by HomePage
|
* Default selector used by HomePage
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
const makeSelectUuid = () => createSelector(
|
||||||
|
selectAdminPageDomain(),
|
||||||
|
substate => substate.get('uuid'),
|
||||||
|
);
|
||||||
|
|
||||||
const selectAdminPage = () => createSelector(
|
const selectAdminPage = () => createSelector(
|
||||||
selectAdminPageDomain(),
|
selectAdminPageDomain(),
|
||||||
(substate) => substate.toJS(),
|
(substate) => substate.toJS(),
|
||||||
);
|
);
|
||||||
|
|
||||||
export default selectAdminPage;
|
export default selectAdminPage;
|
||||||
|
export { makeSelectUuid };
|
||||||
|
@ -122,7 +122,11 @@ export class HomePage extends React.PureComponent {
|
|||||||
|
|
||||||
handleSubmit = e => {
|
handleSubmit = e => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
const errors = validateInput(this.props.homePage.body.email, { required: true }, 'email');
|
const errors = validateInput(
|
||||||
|
this.props.homePage.body.email,
|
||||||
|
{ required: true },
|
||||||
|
'email',
|
||||||
|
);
|
||||||
this.setState({ errors });
|
this.setState({ errors });
|
||||||
|
|
||||||
if (isEmpty(errors)) {
|
if (isEmpty(errors)) {
|
||||||
@ -131,13 +135,18 @@ export class HomePage extends React.PureComponent {
|
|||||||
};
|
};
|
||||||
|
|
||||||
showFirstBlock = () =>
|
showFirstBlock = () =>
|
||||||
get(this.props.plugins.toJS(), 'content-manager.leftMenuSections.0.links', []).length === 0;
|
get(
|
||||||
|
this.props.plugins.toJS(),
|
||||||
|
'content-manager.leftMenuSections.0.links',
|
||||||
|
[],
|
||||||
|
).length === 0;
|
||||||
|
|
||||||
renderButton = () => {
|
renderButton = () => {
|
||||||
const data = this.showFirstBlock()
|
const data = this.showFirstBlock()
|
||||||
? {
|
? {
|
||||||
className: styles.homePageTutorialButton,
|
className: styles.homePageTutorialButton,
|
||||||
href: 'https://strapi.io/documentation/3.x.x/getting-started/quick-start.html#_3-create-a-content-type',
|
href:
|
||||||
|
'https://strapi.io/documentation/3.x.x/getting-started/quick-start.html#_3-create-a-content-type',
|
||||||
id: 'app.components.HomePage.button.quickStart',
|
id: 'app.components.HomePage.button.quickStart',
|
||||||
primary: true,
|
primary: true,
|
||||||
}
|
}
|
||||||
@ -158,7 +167,9 @@ export class HomePage extends React.PureComponent {
|
|||||||
};
|
};
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { homePage: { articles, body } } = this.props;
|
const {
|
||||||
|
homePage: { articles, body },
|
||||||
|
} = this.props;
|
||||||
const WELCOME_AGAIN_BLOCK = [
|
const WELCOME_AGAIN_BLOCK = [
|
||||||
{
|
{
|
||||||
title: {
|
title: {
|
||||||
@ -177,7 +188,12 @@ export class HomePage extends React.PureComponent {
|
|||||||
<Block>
|
<Block>
|
||||||
{this.showFirstBlock() &&
|
{this.showFirstBlock() &&
|
||||||
FIRST_BLOCK.map((value, key) => (
|
FIRST_BLOCK.map((value, key) => (
|
||||||
<Sub key={key} {...value} underline={key === 0} bordered={key === 0} />
|
<Sub
|
||||||
|
key={key}
|
||||||
|
{...value}
|
||||||
|
underline={key === 0}
|
||||||
|
bordered={key === 0}
|
||||||
|
/>
|
||||||
))}
|
))}
|
||||||
{!this.showFirstBlock() &&
|
{!this.showFirstBlock() &&
|
||||||
WELCOME_AGAIN_BLOCK.concat(articles).map((value, key) => (
|
WELCOME_AGAIN_BLOCK.concat(articles).map((value, key) => (
|
||||||
@ -191,14 +207,21 @@ export class HomePage extends React.PureComponent {
|
|||||||
))}
|
))}
|
||||||
{this.renderButton()}
|
{this.renderButton()}
|
||||||
<div className={styles.homePageFlex}>
|
<div className={styles.homePageFlex}>
|
||||||
{FIRST_BLOCK_LINKS.map((value, key) => <BlockLink {...value} key={key} />)}
|
{FIRST_BLOCK_LINKS.map((value, key) => (
|
||||||
|
<BlockLink {...value} key={key} />
|
||||||
|
))}
|
||||||
</div>
|
</div>
|
||||||
</Block>
|
</Block>
|
||||||
<Block>
|
<Block>
|
||||||
<Sub {...SECOND_BLOCK} />
|
<Sub {...SECOND_BLOCK} />
|
||||||
<div className={styles.homePageFlex}>
|
<div className={styles.homePageFlex}>
|
||||||
<div className="row" style={{ width: '100%', marginRight: '0' }}>
|
<div
|
||||||
{SOCIAL_LINKS.map((value, key) => <SocialLink key={key} {...value} />)}
|
className="row"
|
||||||
|
style={{ width: '100%', marginRight: '0' }}
|
||||||
|
>
|
||||||
|
{SOCIAL_LINKS.map((value, key) => (
|
||||||
|
<SocialLink key={key} {...value} />
|
||||||
|
))}
|
||||||
</div>
|
</div>
|
||||||
<div className={styles.newsLetterWrapper}>
|
<div className={styles.newsLetterWrapper}>
|
||||||
<div>
|
<div>
|
||||||
@ -265,10 +288,17 @@ function mapDispatchToProps(dispatch) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const withConnect = connect(mapStateToProps, mapDispatchToProps);
|
const withConnect = connect(
|
||||||
|
mapStateToProps,
|
||||||
|
mapDispatchToProps,
|
||||||
|
);
|
||||||
|
|
||||||
const withReducer = injectReducer({ key: 'homePage', reducer });
|
const withReducer = injectReducer({ key: 'homePage', reducer });
|
||||||
const withSaga = injectSaga({ key: 'homePage', saga });
|
const withSaga = injectSaga({ key: 'homePage', saga });
|
||||||
|
|
||||||
// export default connect(mapDispatchToProps)(HomePage);
|
// export default connect(mapDispatchToProps)(HomePage);
|
||||||
export default compose(withReducer, withSaga, withConnect)(HomePage);
|
export default compose(
|
||||||
|
withReducer,
|
||||||
|
withSaga,
|
||||||
|
withConnect,
|
||||||
|
)(HomePage);
|
||||||
|
@ -1,14 +1,7 @@
|
|||||||
import 'whatwg-fetch';
|
import 'whatwg-fetch';
|
||||||
import { dropRight, take } from 'lodash';
|
import { dropRight, take } from 'lodash';
|
||||||
import removeMd from 'remove-markdown';
|
import removeMd from 'remove-markdown';
|
||||||
import {
|
import { all, call, fork, put, select, takeLatest } from 'redux-saga/effects';
|
||||||
all,
|
|
||||||
call,
|
|
||||||
fork,
|
|
||||||
put,
|
|
||||||
select,
|
|
||||||
takeLatest,
|
|
||||||
} from 'redux-saga/effects';
|
|
||||||
import request from 'utils/request';
|
import request from 'utils/request';
|
||||||
import { getArticlesSucceeded, submitSucceeded } from './actions';
|
import { getArticlesSucceeded, submitSucceeded } from './actions';
|
||||||
import { GET_ARTICLES, SUBMIT } from './constants';
|
import { GET_ARTICLES, SUBMIT } from './constants';
|
||||||
@ -19,7 +12,11 @@ function* getArticles() {
|
|||||||
const articles = yield call(fetchArticles);
|
const articles = yield call(fetchArticles);
|
||||||
const posts = articles.posts.reduce((acc, curr) => {
|
const posts = articles.posts.reduce((acc, curr) => {
|
||||||
// Limit to 200 characters and remove last word.
|
// Limit to 200 characters and remove last word.
|
||||||
const content = dropRight(take(removeMd(curr.markdown), 250).join('').split(' ')).join(' ');
|
const content = dropRight(
|
||||||
|
take(removeMd(curr.markdown), 250)
|
||||||
|
.join('')
|
||||||
|
.split(' '),
|
||||||
|
).join(' ');
|
||||||
|
|
||||||
acc.push({
|
acc.push({
|
||||||
title: curr.title,
|
title: curr.title,
|
||||||
@ -31,17 +28,19 @@ function* getArticles() {
|
|||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
yield put(getArticlesSucceeded(posts));
|
yield put(getArticlesSucceeded(posts));
|
||||||
} catch(err) {
|
} catch (err) {
|
||||||
// Silent
|
// Silent
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
function* submit() {
|
function* submit() {
|
||||||
try {
|
try {
|
||||||
const body = yield select(makeSelectBody());
|
const body = yield select(makeSelectBody());
|
||||||
yield call(request, 'https://analytics.strapi.io/register', { method: 'POST', body });
|
yield call(request, 'https://analytics.strapi.io/register', {
|
||||||
} catch(err) {
|
method: 'POST',
|
||||||
|
body,
|
||||||
|
});
|
||||||
|
} catch (err) {
|
||||||
// silent
|
// silent
|
||||||
} finally {
|
} finally {
|
||||||
strapi.notification.success('HomePage.notification.newsLetter.success');
|
strapi.notification.success('HomePage.notification.newsLetter.success');
|
||||||
@ -56,11 +55,12 @@ function* defaultSaga() {
|
|||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
function fetchArticles() {
|
function fetchArticles() {
|
||||||
return fetch('https://blog.strapi.io/ghost/api/v0.1/posts/?client_id=ghost-frontend&client_secret=1f260788b4ec&limit=2', {})
|
return fetch(
|
||||||
.then(resp => {
|
'https://blog.strapi.io/ghost/api/v0.1/posts/?client_id=ghost-frontend&client_secret=1f260788b4ec&limit=2',
|
||||||
return resp.json ? resp.json() : resp;
|
{},
|
||||||
});
|
).then(resp => {
|
||||||
|
return resp.json ? resp.json() : resp;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
export default defaultSaga;
|
export default defaultSaga;
|
||||||
|
@ -0,0 +1,43 @@
|
|||||||
|
/*
|
||||||
|
*
|
||||||
|
* Onboarding actions
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { GET_VIDEOS, GET_VIDEOS_SUCCEEDED, ON_CLICK, SET_VIDEOS_DURATION, UPDATE_VIDEO_START_TIME } from './constants';
|
||||||
|
|
||||||
|
export function getVideos() {
|
||||||
|
return {
|
||||||
|
type: GET_VIDEOS,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getVideosSucceeded(videos) {
|
||||||
|
return {
|
||||||
|
type: GET_VIDEOS_SUCCEEDED,
|
||||||
|
videos,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function onClick(e) {
|
||||||
|
return {
|
||||||
|
type: ON_CLICK,
|
||||||
|
index: parseInt(e.currentTarget.id, 10),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function setVideoDuration(index, duration) {
|
||||||
|
return {
|
||||||
|
type: SET_VIDEOS_DURATION,
|
||||||
|
index: parseInt(index, 10),
|
||||||
|
duration: parseFloat(duration, 10),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function updateVideoStartTime(index, startTime) {
|
||||||
|
return {
|
||||||
|
type: UPDATE_VIDEO_START_TIME,
|
||||||
|
index: parseInt(index, 10),
|
||||||
|
startTime: parseFloat(startTime, 10),
|
||||||
|
};
|
||||||
|
}
|
@ -0,0 +1,12 @@
|
|||||||
|
/*
|
||||||
|
*
|
||||||
|
* Onboarding constants
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
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';
|
133
packages/strapi-admin/admin/src/containers/Onboarding/index.js
Normal file
133
packages/strapi-admin/admin/src/containers/Onboarding/index.js
Normal file
@ -0,0 +1,133 @@
|
|||||||
|
/**
|
||||||
|
*
|
||||||
|
* Onboarding
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
import React from 'react';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import cn from 'classnames';
|
||||||
|
import { connect } from 'react-redux';
|
||||||
|
import { bindActionCreators, compose } from 'redux';
|
||||||
|
import { getVideos, onClick, setVideoDuration, updateVideoStartTime } from './actions';
|
||||||
|
|
||||||
|
import injectSaga from 'utils/injectSaga';
|
||||||
|
import injectReducer from 'utils/injectReducer';
|
||||||
|
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 };
|
||||||
|
|
||||||
|
componentWillMount() {
|
||||||
|
this.setState({ showVideos: true });
|
||||||
|
this.props.getVideos();
|
||||||
|
}
|
||||||
|
|
||||||
|
toggleVideos = () => {
|
||||||
|
// Display videos card
|
||||||
|
this.setState(prevState => ({ showVideos: !prevState.showVideos }));
|
||||||
|
|
||||||
|
// EmitEvent
|
||||||
|
const { showVideos } = this.state;
|
||||||
|
const eventName = showVideos ? 'didOpenGetStartedVideoContainer' : 'didCloseGetStartedVideoContainer';
|
||||||
|
this.context.emitEvent(eventName);
|
||||||
|
};
|
||||||
|
|
||||||
|
updateLocalStorage = (index, current) => {
|
||||||
|
// Update store
|
||||||
|
this.props.updateVideoStartTime(index, current);
|
||||||
|
|
||||||
|
// Update localStorage
|
||||||
|
let videosTime = JSON.parse(localStorage.getItem('videos'));
|
||||||
|
videosTime.fill(0);
|
||||||
|
videosTime[index] = current;
|
||||||
|
localStorage.setItem('videos', JSON.stringify(videosTime));
|
||||||
|
};
|
||||||
|
|
||||||
|
// eslint-disable-line react/prefer-stateless-function
|
||||||
|
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={styles.videosHeader}>
|
||||||
|
<p>Get started video</p>
|
||||||
|
<p>25% completed</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<ul className={styles.onboardingList}>
|
||||||
|
{videos.map((video, i) => {
|
||||||
|
return (
|
||||||
|
<OnboardingVideo
|
||||||
|
key={i}
|
||||||
|
id={i}
|
||||||
|
video={video}
|
||||||
|
onClick={onClick}
|
||||||
|
setVideoDuration={setVideoDuration}
|
||||||
|
getVideoCurrentTime={this.updateLocalStorage}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className={styles.openBtn}>
|
||||||
|
<button
|
||||||
|
onClick={this.toggleVideos}
|
||||||
|
className={this.state.showVideos ? styles.active : ''}
|
||||||
|
>
|
||||||
|
<i className="fa fa-question" />
|
||||||
|
<i className="fa fa-times" />
|
||||||
|
<span />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Onboarding.contextTypes = {
|
||||||
|
emitEvent: PropTypes.func,
|
||||||
|
};
|
||||||
|
|
||||||
|
Onboarding.propTypes = {
|
||||||
|
getVideos: PropTypes.func.isRequired,
|
||||||
|
};
|
||||||
|
|
||||||
|
const mapStateToProps = makeSelectOnboarding();
|
||||||
|
|
||||||
|
function mapDispatchToProps(dispatch) {
|
||||||
|
return bindActionCreators({ getVideos, onClick, setVideoDuration, updateVideoStartTime }, dispatch);
|
||||||
|
}
|
||||||
|
|
||||||
|
const withConnect = connect(
|
||||||
|
mapStateToProps,
|
||||||
|
mapDispatchToProps,
|
||||||
|
);
|
||||||
|
|
||||||
|
/* Remove this line if the container doesn't have a route and
|
||||||
|
* check the documentation to see how to create the container's store
|
||||||
|
*/
|
||||||
|
const withReducer = injectReducer({ key: 'onboarding', reducer });
|
||||||
|
|
||||||
|
/* Remove the line below the container doesn't have a route and
|
||||||
|
* check the documentation to see how to create the container's store
|
||||||
|
*/
|
||||||
|
const withSaga = injectSaga({ key: 'onboarding', saga });
|
||||||
|
|
||||||
|
export default compose(
|
||||||
|
withReducer,
|
||||||
|
withSaga,
|
||||||
|
withConnect,
|
||||||
|
)(Onboarding);
|
@ -0,0 +1,47 @@
|
|||||||
|
/*
|
||||||
|
*
|
||||||
|
* Onboarding reducer
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { fromJS } from 'immutable';
|
||||||
|
import { GET_VIDEOS_SUCCEEDED, ON_CLICK, SET_VIDEOS_DURATION, UPDATE_VIDEO_START_TIME } from './constants';
|
||||||
|
|
||||||
|
const initialState = fromJS({
|
||||||
|
videos: fromJS([]),
|
||||||
|
});
|
||||||
|
|
||||||
|
function onboardingReducer(state = initialState, action) {
|
||||||
|
switch (action.type) {
|
||||||
|
case GET_VIDEOS_SUCCEEDED:
|
||||||
|
return state.update('videos', () => fromJS(action.videos));
|
||||||
|
case ON_CLICK:
|
||||||
|
return state.updateIn(['videos'], list => {
|
||||||
|
return list.reduce((acc, current, index) => {
|
||||||
|
|
||||||
|
if (index === action.index) {
|
||||||
|
return acc.updateIn([index, 'isOpen'], v => !v);
|
||||||
|
}
|
||||||
|
|
||||||
|
return acc.updateIn([index, 'isOpen'], () => false);
|
||||||
|
}, list);
|
||||||
|
});
|
||||||
|
case SET_VIDEOS_DURATION:
|
||||||
|
return state.updateIn(['videos', action.index, 'duration'], () => action.duration);
|
||||||
|
case UPDATE_VIDEO_START_TIME:
|
||||||
|
return state.updateIn(['videos'], list => {
|
||||||
|
return list.reduce((acc, current, index) => {
|
||||||
|
|
||||||
|
if (index === action.index) {
|
||||||
|
return acc.updateIn([index, 'startTime'], () => action.startTime);
|
||||||
|
}
|
||||||
|
|
||||||
|
return acc.updateIn([index, 'startTime'], () => 0);
|
||||||
|
}, list);
|
||||||
|
});
|
||||||
|
default:
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default onboardingReducer;
|
@ -0,0 +1,50 @@
|
|||||||
|
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',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
false,
|
||||||
|
true,
|
||||||
|
{ noAuth: true },
|
||||||
|
);
|
||||||
|
|
||||||
|
let currTimes = Array.apply(null, Array(videos.length)).map(Number.prototype.valueOf,0);
|
||||||
|
|
||||||
|
// Retrieve start time if enable in localStorage
|
||||||
|
if (localStorage.getItem('videos')) {
|
||||||
|
currTimes.splice(0, currTimes.length, ...JSON.parse(localStorage.getItem('videos')));
|
||||||
|
} else {
|
||||||
|
localStorage.setItem('videos', JSON.stringify(currTimes));
|
||||||
|
}
|
||||||
|
|
||||||
|
yield put(
|
||||||
|
getVideosSucceeded(
|
||||||
|
videos.map((video, index) => {
|
||||||
|
video.isOpen = false;
|
||||||
|
video.duration = null;
|
||||||
|
video.startTime = currTimes[index];
|
||||||
|
|
||||||
|
return video;
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
} catch (err) {
|
||||||
|
console.log('err');
|
||||||
|
console.log({ err });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function* defaultSaga() {
|
||||||
|
yield all([fork(takeLatest, GET_VIDEOS, getVideos)]);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default defaultSaga;
|
@ -0,0 +1,25 @@
|
|||||||
|
import { createSelector } from 'reselect';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Direct selector to the onboarding state domain
|
||||||
|
*/
|
||||||
|
const selectOnboardingDomain = () => (state) => state.get('onboarding');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Other specific selectors
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Default selector used by Onboarding
|
||||||
|
*/
|
||||||
|
|
||||||
|
const makeSelectOnboarding = () => createSelector(
|
||||||
|
selectOnboardingDomain(),
|
||||||
|
(substate) => substate.toJS()
|
||||||
|
);
|
||||||
|
|
||||||
|
export default makeSelectOnboarding;
|
||||||
|
export {
|
||||||
|
selectOnboardingDomain,
|
||||||
|
};
|
@ -0,0 +1,77 @@
|
|||||||
|
.videosWrapper {
|
||||||
|
position: fixed;
|
||||||
|
right: 15px;
|
||||||
|
bottom: 15px;
|
||||||
|
button,
|
||||||
|
button:focus,
|
||||||
|
a {
|
||||||
|
cursor: pointer;
|
||||||
|
outline: 0;
|
||||||
|
}
|
||||||
|
p {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
.videosHeader {
|
||||||
|
padding: 15px 15px 0 15px;
|
||||||
|
p {
|
||||||
|
font-family: Lato-Bold;
|
||||||
|
font-size: 11px;
|
||||||
|
text-transform: uppercase;
|
||||||
|
display: inline-block;
|
||||||
|
width: 50%;
|
||||||
|
vertical-align: top;
|
||||||
|
color: #5c5f66;
|
||||||
|
&:last-of-type {
|
||||||
|
text-align: right;
|
||||||
|
color: #5a9e06;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.videosContent {
|
||||||
|
background-color: white;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
margin-right: 15px;
|
||||||
|
box-shadow: 0 2px 4px 0 #e3e9f3;
|
||||||
|
border-radius: 3px;
|
||||||
|
transition: all 0.3s linear;
|
||||||
|
&.shown {
|
||||||
|
visibility: visible;
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
&.hide {
|
||||||
|
opacity: 0;
|
||||||
|
visibility: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
ul {
|
||||||
|
list-style: none;
|
||||||
|
padding: 10px 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.openBtn {
|
||||||
|
width: 38px;
|
||||||
|
height: 38px;
|
||||||
|
float: right;
|
||||||
|
button {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
border-radius: 50%;
|
||||||
|
color: white;
|
||||||
|
background: #0e7de7;
|
||||||
|
-webkit-box-shadow: 0px 2px 4px 0px rgba(227, 233, 243, 1);
|
||||||
|
-moz-box-shadow: 0px 2px 4px 0px rgba(227, 233, 243, 1);
|
||||||
|
box-shadow: 0px 2px 4px 0px rgba(227, 233, 243, 1);
|
||||||
|
i:last-of-type {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
&.active {
|
||||||
|
i:first-of-type {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
i:last-of-type {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,18 @@
|
|||||||
|
|
||||||
|
import {
|
||||||
|
defaultAction,
|
||||||
|
} from '../actions';
|
||||||
|
import {
|
||||||
|
DEFAULT_ACTION,
|
||||||
|
} from '../constants';
|
||||||
|
|
||||||
|
describe('Onboarding actions', () => {
|
||||||
|
describe('Default Action', () => {
|
||||||
|
it('has a type of DEFAULT_ACTION', () => {
|
||||||
|
const expected = {
|
||||||
|
type: DEFAULT_ACTION,
|
||||||
|
};
|
||||||
|
expect(defaultAction()).toEqual(expected);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
@ -0,0 +1,10 @@
|
|||||||
|
// import React from 'react';
|
||||||
|
// import { shallow } from 'enzyme';
|
||||||
|
|
||||||
|
// import { Onboarding } from '../index';
|
||||||
|
|
||||||
|
describe('<Onboarding />', () => {
|
||||||
|
it('Expect to have unit tests specified', () => {
|
||||||
|
expect(true).toEqual(false);
|
||||||
|
});
|
||||||
|
});
|
@ -0,0 +1,9 @@
|
|||||||
|
|
||||||
|
import { fromJS } from 'immutable';
|
||||||
|
import onboardingReducer from '../reducer';
|
||||||
|
|
||||||
|
describe('onboardingReducer', () => {
|
||||||
|
it('returns the initial state', () => {
|
||||||
|
expect(onboardingReducer(undefined, {})).toEqual(fromJS({}));
|
||||||
|
});
|
||||||
|
});
|
@ -0,0 +1,15 @@
|
|||||||
|
/**
|
||||||
|
* Test sagas
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* eslint-disable redux-saga/yield-effects */
|
||||||
|
// import { take, call, put, select } from 'redux-saga/effects';
|
||||||
|
// import { defaultSaga } from '../saga';
|
||||||
|
|
||||||
|
// const generator = defaultSaga();
|
||||||
|
|
||||||
|
describe('defaultSaga Saga', () => {
|
||||||
|
it('Expect to have unit tests specified', () => {
|
||||||
|
expect(true).toEqual(false);
|
||||||
|
});
|
||||||
|
});
|
@ -0,0 +1,10 @@
|
|||||||
|
// import { fromJS } from 'immutable';
|
||||||
|
// import { makeSelectOnboardingDomain } from '../selectors';
|
||||||
|
|
||||||
|
// const selector = makeSelectOnboardingDomain();
|
||||||
|
|
||||||
|
describe('makeSelectOnboardingDomain', () => {
|
||||||
|
it('Expect to have unit tests specified', () => {
|
||||||
|
expect(true).toEqual(false);
|
||||||
|
});
|
||||||
|
});
|
@ -26,9 +26,13 @@
|
|||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"intl": "^1.2.5",
|
"intl": "^1.2.5",
|
||||||
|
"react": "^16.8.1",
|
||||||
|
"react-dom": "^16.8.1",
|
||||||
"react-ga": "^2.4.1",
|
"react-ga": "^2.4.1",
|
||||||
|
"redux": "^4.0.1",
|
||||||
"remove-markdown": "^0.2.2",
|
"remove-markdown": "^0.2.2",
|
||||||
"shelljs": "^0.7.8"
|
"shelljs": "^0.7.8",
|
||||||
|
"video-react": "^0.13.2"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"cross-env": "^5.0.5",
|
"cross-env": "^5.0.5",
|
||||||
|
@ -47,7 +47,7 @@ const addDevMiddlewares = (app, webpackConfig) => {
|
|||||||
/**
|
/**
|
||||||
* Front-end middleware
|
* Front-end middleware
|
||||||
*/
|
*/
|
||||||
module.exports = (app) => {
|
module.exports = app => {
|
||||||
const webpackConfig = require('../../internals/webpack/webpack.dev.babel');
|
const webpackConfig = require('../../internals/webpack/webpack.dev.babel');
|
||||||
|
|
||||||
// const webpackConfig = require(path.resolve(process.cwd(), 'node_modules', 'strapi-helper-plugin', 'internals', 'webpack', 'webpack.dev.babel'));
|
// const webpackConfig = require(path.resolve(process.cwd(), 'node_modules', 'strapi-helper-plugin', 'internals', 'webpack', 'webpack.dev.babel'));
|
||||||
|
@ -73,7 +73,6 @@ const auth = {
|
|||||||
return null;
|
return null;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
||||||
setToken(value = '', isLocalStorage = false, tokenKey = TOKEN_KEY) {
|
setToken(value = '', isLocalStorage = false, tokenKey = TOKEN_KEY) {
|
||||||
return auth.set(value, tokenKey, isLocalStorage);
|
return auth.set(value, tokenKey, isLocalStorage);
|
||||||
},
|
},
|
||||||
|
@ -9,6 +9,7 @@ import auth from 'utils/auth';
|
|||||||
* @return {object} The parsed JSON from the request
|
* @return {object} The parsed JSON from the request
|
||||||
*/
|
*/
|
||||||
function parseJSON(response) {
|
function parseJSON(response) {
|
||||||
|
// return response;
|
||||||
return response.json ? response.json() : response;
|
return response.json ? response.json() : response;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -41,21 +42,22 @@ function checkTokenValidity(response) {
|
|||||||
method: 'GET',
|
method: 'GET',
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/json',
|
'Content-Type': 'application/json',
|
||||||
'Authorization': `Bearer ${auth.getToken()}`,
|
Authorization: `Bearer ${auth.getToken()}`,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
if (auth.getToken()) {
|
if (auth.getToken()) {
|
||||||
return fetch(`${strapi.backendURL}/user/me`, options)
|
return fetch(`${strapi.backendURL}/user/me`, options).then(() => {
|
||||||
.then(() => {
|
if (response.status === 401) {
|
||||||
if (response.status === 401) {
|
window.location = `${
|
||||||
window.location = `${strapi.remoteURL}/plugins/users-permissions/auth/login`;
|
strapi.remoteURL
|
||||||
|
}/plugins/users-permissions/auth/login`;
|
||||||
|
|
||||||
auth.clearAppStorage();
|
auth.clearAppStorage();
|
||||||
}
|
}
|
||||||
|
|
||||||
return checkStatus(response, false);
|
return checkStatus(response, false);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -72,12 +74,12 @@ function formatQueryParams(params) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Server restart watcher
|
* Server restart watcher
|
||||||
* @param response
|
* @param response
|
||||||
* @returns {object} the response data
|
* @returns {object} the response data
|
||||||
*/
|
*/
|
||||||
function serverRestartWatcher(response) {
|
function serverRestartWatcher(response) {
|
||||||
return new Promise((resolve) => {
|
return new Promise(resolve => {
|
||||||
fetch(`${strapi.backendURL}/_health`, {
|
fetch(`${strapi.backendURL}/_health`, {
|
||||||
method: 'HEAD',
|
method: 'HEAD',
|
||||||
mode: 'no-cors',
|
mode: 'no-cors',
|
||||||
@ -93,8 +95,7 @@ function serverRestartWatcher(response) {
|
|||||||
})
|
})
|
||||||
.catch(() => {
|
.catch(() => {
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
return serverRestartWatcher(response)
|
return serverRestartWatcher(response).then(resolve);
|
||||||
.then(resolve);
|
|
||||||
}, 100);
|
}, 100);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@ -108,22 +109,38 @@ function serverRestartWatcher(response) {
|
|||||||
*
|
*
|
||||||
* @return {object} The response data
|
* @return {object} The response data
|
||||||
*/
|
*/
|
||||||
export default function request(url, options = {}, shouldWatchServerRestart = false, stringify = true ) {
|
export default function request(...args) {
|
||||||
|
let [url, options = {}, shouldWatchServerRestart, stringify = true, ...rest] = args;
|
||||||
|
let noAuth;
|
||||||
|
|
||||||
|
try {
|
||||||
|
[{ noAuth }] = rest;
|
||||||
|
} catch(err) {
|
||||||
|
noAuth = false;
|
||||||
|
}
|
||||||
|
|
||||||
// Set headers
|
// Set headers
|
||||||
if (!options.headers) {
|
if (!options.headers) {
|
||||||
options.headers = Object.assign({
|
options.headers = Object.assign(
|
||||||
'Content-Type': 'application/json',
|
{
|
||||||
}, options.headers, {
|
'Content-Type': 'application/json',
|
||||||
'X-Forwarded-Host': 'strapi',
|
},
|
||||||
});
|
options.headers,
|
||||||
|
{
|
||||||
|
'X-Forwarded-Host': 'strapi',
|
||||||
|
},
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const token = auth.getToken();
|
const token = auth.getToken();
|
||||||
|
|
||||||
if (token) {
|
if (token && !noAuth) {
|
||||||
options.headers = Object.assign({
|
options.headers = Object.assign(
|
||||||
'Authorization': `Bearer ${token}`,
|
{
|
||||||
}, options.headers);
|
Authorization: `Bearer ${token}`,
|
||||||
|
},
|
||||||
|
options.headers,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add parameters to url
|
// Add parameters to url
|
||||||
@ -142,7 +159,7 @@ export default function request(url, options = {}, shouldWatchServerRestart = fa
|
|||||||
return fetch(url, options)
|
return fetch(url, options)
|
||||||
.then(checkStatus)
|
.then(checkStatus)
|
||||||
.then(parseJSON)
|
.then(parseJSON)
|
||||||
.then((response) => {
|
.then(response => {
|
||||||
if (shouldWatchServerRestart) {
|
if (shouldWatchServerRestart) {
|
||||||
// Display the global OverlayBlocker
|
// Display the global OverlayBlocker
|
||||||
strapi.lockApp(shouldWatchServerRestart);
|
strapi.lockApp(shouldWatchServerRestart);
|
||||||
|
@ -56,6 +56,7 @@ class AttributeRow extends React.Component {
|
|||||||
handleEdit = () => this.props.onEditAttribute(this.props.row.name);
|
handleEdit = () => this.props.onEditAttribute(this.props.row.name);
|
||||||
|
|
||||||
handleDelete = () => {
|
handleDelete = () => {
|
||||||
|
this.context.emitEvent('willDeleteFieldOfContentType');
|
||||||
this.props.onDelete(this.props.row.name);
|
this.props.onDelete(this.props.row.name);
|
||||||
this.setState({ showWarning: false });
|
this.setState({ showWarning: false });
|
||||||
};
|
};
|
||||||
@ -136,6 +137,10 @@ class AttributeRow extends React.Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
AttributeRow.contextTypes = {
|
||||||
|
emitEvent: PropTypes.func,
|
||||||
|
};
|
||||||
|
|
||||||
AttributeRow.propTypes = {
|
AttributeRow.propTypes = {
|
||||||
onDelete: PropTypes.func.isRequired,
|
onDelete: PropTypes.func.isRequired,
|
||||||
onEditAttribute: PropTypes.func.isRequired,
|
onEditAttribute: PropTypes.func.isRequired,
|
||||||
|
@ -17,6 +17,9 @@ import styles from './styles.scss';
|
|||||||
/* eslint-disable jsx-a11y/click-events-have-key-events */
|
/* eslint-disable jsx-a11y/click-events-have-key-events */
|
||||||
class ContentHeader extends React.Component { // eslint-disable-line react/prefer-stateless-function
|
class ContentHeader extends React.Component { // eslint-disable-line react/prefer-stateless-function
|
||||||
handleEdit = () => {
|
handleEdit = () => {
|
||||||
|
// Send event.
|
||||||
|
this.context.emitEvent('willEditNameOfContentType');
|
||||||
|
// Open modal.
|
||||||
router.push(this.props.editPath);
|
router.push(this.props.editPath);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -41,6 +44,7 @@ class ContentHeader extends React.Component { // eslint-disable-line react/prefe
|
|||||||
renderContentHeader = () => {
|
renderContentHeader = () => {
|
||||||
const description = isEmpty(this.props.description) ? '' : <FormattedMessage id={this.props.description} defaultMessage='{description}' values={{ description: this.props.description}} />;
|
const description = isEmpty(this.props.description) ? '' : <FormattedMessage id={this.props.description} defaultMessage='{description}' values={{ description: this.props.description}} />;
|
||||||
const buttons = this.props.addButtons ? this.renderButtonContainer() : '';
|
const buttons = this.props.addButtons ? this.renderButtonContainer() : '';
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={styles.contentHeader} style={this.props.styles}>
|
<div className={styles.contentHeader} style={this.props.styles}>
|
||||||
<div>
|
<div>
|
||||||
@ -74,6 +78,10 @@ class ContentHeader extends React.Component { // eslint-disable-line react/prefe
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ContentHeader.contextTypes = {
|
||||||
|
emitEvent: PropTypes.func
|
||||||
|
};
|
||||||
|
|
||||||
ContentHeader.propTypes = {
|
ContentHeader.propTypes = {
|
||||||
addButtons: PropTypes.bool,
|
addButtons: PropTypes.bool,
|
||||||
buttonsContent: PropTypes.array,
|
buttonsContent: PropTypes.array,
|
||||||
|
@ -15,6 +15,12 @@ import styles from './styles.scss';
|
|||||||
|
|
||||||
class PopUpHeaderNavLink extends React.Component { // eslint-disable-line react/prefer-stateless-function
|
class PopUpHeaderNavLink extends React.Component { // eslint-disable-line react/prefer-stateless-function
|
||||||
handleGoTo = () => {
|
handleGoTo = () => {
|
||||||
|
if (this.props.routePath.indexOf('#create::contentType') !== -1 && this.props.name === 'advancedSettings') {
|
||||||
|
this.context.emitEvent('didSelectContentTypeSettings');
|
||||||
|
} else if (this.props.routePath.indexOf('#create') !== -1 && this.props.routePath.indexOf('::attribute') !== -1 && this.props.name === 'advancedSettings') {
|
||||||
|
this.context.emitEvent('didSelectContentTypeFieldSettings');
|
||||||
|
}
|
||||||
|
|
||||||
router.push(replace(this.props.routePath, this.props.nameToReplace, this.props.name));
|
router.push(replace(this.props.routePath, this.props.nameToReplace, this.props.name));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -29,6 +35,10 @@ class PopUpHeaderNavLink extends React.Component { // eslint-disable-line react/
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
PopUpHeaderNavLink.contextTypes = {
|
||||||
|
emitEvent: PropTypes.func
|
||||||
|
};
|
||||||
|
|
||||||
PopUpHeaderNavLink.propTypes = {
|
PopUpHeaderNavLink.propTypes = {
|
||||||
message: PropTypes.string.isRequired,
|
message: PropTypes.string.isRequired,
|
||||||
name: PropTypes.string.isRequired,
|
name: PropTypes.string.isRequired,
|
||||||
|
@ -373,6 +373,7 @@ export class Form extends React.Component { // eslint-disable-line react/prefer-
|
|||||||
}
|
}
|
||||||
|
|
||||||
goToAttributeTypeView = (attributeType) => {
|
goToAttributeTypeView = (attributeType) => {
|
||||||
|
this.context.emitEvent('didSelectContentTypeFieldType', { type: attributeType });
|
||||||
const settings = attributeType === 'relation' ? 'defineRelation' : 'baseSettings';
|
const settings = attributeType === 'relation' ? 'defineRelation' : 'baseSettings';
|
||||||
router.push(`${this.props.routePath}#create${this.props.modelName}::attribute${attributeType}::${settings}`);
|
router.push(`${this.props.routePath}#create${this.props.modelName}::attribute${attributeType}::${settings}`);
|
||||||
}
|
}
|
||||||
@ -461,6 +462,12 @@ export class Form extends React.Component { // eslint-disable-line react/prefer-
|
|||||||
let dataSucces = null;
|
let dataSucces = null;
|
||||||
let cbFail;
|
let cbFail;
|
||||||
|
|
||||||
|
if (redirectToChoose) {
|
||||||
|
this.context.emitEvent('willAddMoreFieldToContentType');
|
||||||
|
} else if (this.props.hash.indexOf('#edit') !== -1 && this.props.hash.indexOf('::attribute') !== -1) {
|
||||||
|
this.context.emitEvent('willEditFieldOfContentType');
|
||||||
|
}
|
||||||
|
|
||||||
switch (true) {
|
switch (true) {
|
||||||
case includes(hashArray[0], '#edit'): {
|
case includes(hashArray[0], '#edit'): {
|
||||||
// Check if the user is editing the attribute
|
// Check if the user is editing the attribute
|
||||||
@ -682,6 +689,7 @@ export class Form extends React.Component { // eslint-disable-line react/prefer-
|
|||||||
}
|
}
|
||||||
|
|
||||||
Form.contextTypes = {
|
Form.contextTypes = {
|
||||||
|
emitEvent: PropTypes.func,
|
||||||
plugins: PropTypes.object,
|
plugins: PropTypes.object,
|
||||||
updatePlugin: PropTypes.func,
|
updatePlugin: PropTypes.func,
|
||||||
};
|
};
|
||||||
|
@ -39,6 +39,8 @@ export function* editContentType(action) {
|
|||||||
const response = yield call(request, requestUrl, opts, true);
|
const response = yield call(request, requestUrl, opts, true);
|
||||||
|
|
||||||
if (response.ok) {
|
if (response.ok) {
|
||||||
|
action.context.emitEvent('didEditNameOfContentType');
|
||||||
|
|
||||||
yield put(contentTypeActionSucceeded());
|
yield put(contentTypeActionSucceeded());
|
||||||
yield put(unsetButtonLoading());
|
yield put(unsetButtonLoading());
|
||||||
|
|
||||||
|
@ -46,6 +46,9 @@ export class HomePage extends React.Component { // eslint-disable-line react/pre
|
|||||||
if (storeData.getIsModelTemporary()) {
|
if (storeData.getIsModelTemporary()) {
|
||||||
strapi.notification.info('content-type-builder.notification.info.contentType.creating.notSaved');
|
strapi.notification.info('content-type-builder.notification.info.contentType.creating.notSaved');
|
||||||
} else {
|
} else {
|
||||||
|
// Send event.
|
||||||
|
this.context.emitEvent('willCreateContentType');
|
||||||
|
// Open CT modal.
|
||||||
this.toggleModal();
|
this.toggleModal();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -108,6 +111,7 @@ export class HomePage extends React.Component { // eslint-disable-line react/pre
|
|||||||
}
|
}
|
||||||
|
|
||||||
HomePage.contextTypes = {
|
HomePage.contextTypes = {
|
||||||
|
emitEvent: PropTypes.func,
|
||||||
plugins: PropTypes.object,
|
plugins: PropTypes.object,
|
||||||
updatePlugin: PropTypes.func,
|
updatePlugin: PropTypes.func,
|
||||||
};
|
};
|
||||||
|
@ -315,6 +315,7 @@ export class ModelPage extends React.Component { // eslint-disable-line react/pr
|
|||||||
}
|
}
|
||||||
|
|
||||||
ModelPage.contextTypes = {
|
ModelPage.contextTypes = {
|
||||||
|
emitEvent: PropTypes.func,
|
||||||
plugins: PropTypes.object,
|
plugins: PropTypes.object,
|
||||||
updatePlugin: PropTypes.func,
|
updatePlugin: PropTypes.func,
|
||||||
};
|
};
|
||||||
|
@ -107,15 +107,22 @@ export function* submitChanges(action) {
|
|||||||
set(body, 'plugin', pluginModel);
|
set(body, 'plugin', pluginModel);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const { emitEvent } = action.context;
|
||||||
const method = modelName === body.name ? 'POST' : 'PUT';
|
const method = modelName === body.name ? 'POST' : 'PUT';
|
||||||
const baseUrl = '/content-type-builder/models/';
|
const baseUrl = '/content-type-builder/models/';
|
||||||
const requestUrl = method === 'POST' ? baseUrl : `${baseUrl}${body.name}`;
|
const requestUrl = method === 'POST' ? baseUrl : `${baseUrl}${body.name}`;
|
||||||
const opts = { method, body };
|
const opts = { method, body };
|
||||||
|
|
||||||
|
// Send event.
|
||||||
|
yield put(emitEvent('willSaveContentType'));
|
||||||
|
|
||||||
|
// Send request to save the content type.
|
||||||
const response = yield call(request, requestUrl, opts, true);
|
const response = yield call(request, requestUrl, opts, true);
|
||||||
|
|
||||||
if (response.ok) {
|
if (response.ok) {
|
||||||
if (method === 'POST') {
|
if (method === 'POST') {
|
||||||
storeData.clearAppStorage();
|
storeData.clearAppStorage();
|
||||||
|
yield put(emitEvent('didSaveContentType'));
|
||||||
yield put(temporaryContentTypePosted(size(get(body, 'attributes'))));
|
yield put(temporaryContentTypePosted(size(get(body, 'attributes'))));
|
||||||
yield put(postContentTypeSucceeded());
|
yield put(postContentTypeSucceeded());
|
||||||
|
|
||||||
|
@ -80,6 +80,8 @@ module.exports = {
|
|||||||
|
|
||||||
if (_.isEmpty(strapi.api)) {
|
if (_.isEmpty(strapi.api)) {
|
||||||
strapi.emit('didCreateFirstContentType');
|
strapi.emit('didCreateFirstContentType');
|
||||||
|
} else {
|
||||||
|
strapi.emit('didCreateContentType');
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx.send({ ok: true });
|
ctx.send({ ok: true });
|
||||||
|
Loading…
x
Reference in New Issue
Block a user