From 73a38ab4f484896daa2277cf2b0426f4c6801006 Mon Sep 17 00:00:00 2001 From: fragosti Date: Wed, 31 Jul 2019 15:15:55 -0700 Subject: [PATCH] Add sandwich video to landing page --- packages/website/package.json | 11 +- packages/website/ts/components/button.tsx | 2 +- packages/website/ts/components/hero.tsx | 55 ++-- .../ts/components/modals/modal_video.tsx | 259 +++++++++++++++ .../ts/components/sections/landing/hero.tsx | 72 ++++- packages/website/ts/globals.d.ts | 1 + .../website/ts/icons/illustrations/play.svg | 3 + packages/website/ts/index.tsx | 1 + packages/website/webpack.config.js | 28 +- yarn.lock | 305 +++++++++++++++++- 10 files changed, 667 insertions(+), 70 deletions(-) create mode 100644 packages/website/ts/components/modals/modal_video.tsx create mode 100644 packages/website/ts/icons/illustrations/play.svg diff --git a/packages/website/package.json b/packages/website/package.json index 57ffc3cf48..44864af19d 100644 --- a/packages/website/package.json +++ b/packages/website/package.json @@ -46,7 +46,6 @@ "ethereumjs-util": "^5.1.1", "find-versions": "^2.0.0", "is-mobile": "^0.2.2", - "valid-url": "^1.0.9", "jsonschema": "^1.2.0", "less": "^2.7.2", "lodash": "^4.17.11", @@ -67,6 +66,7 @@ "react-lazyload": "^2.3.0", "react-loadable": "^5.5.0", "react-markdown": "^4.0.6", + "react-modal-video": "^1.2.2", "react-popper": "^1.0.0-beta.6", "react-redux": "^5.0.3", "react-responsive": "^6.0.1", @@ -75,15 +75,17 @@ "react-scrollable-anchor": "^0.6.1", "react-syntax-highlighter": "^10.1.1", "react-tooltip": "^3.2.7", + "react-transition-group": "^4.2.1", "react-typist": "^2.0.4", "redux": "^3.6.0", "redux-devtools-extension": "^2.13.2", "rollbar": "^2.4.7", - "semver-sort": "0.0.4", "semver": "5.5.0", + "semver-sort": "0.0.4", "styled-components": "^4.1.1", "thenby": "^1.2.3", "truffle-contract": "2.0.1", + "valid-url": "^1.0.9", "web3-provider-engine": "14.0.6", "xml-js": "^1.6.4" }, @@ -94,7 +96,6 @@ "@types/deep-equal": "^1.0.0", "@types/find-versions": "^2.0.0", "@types/is-mobile": "0.3.0", - "@types/valid-url": "^1.0.2", "@types/jsonschema": "^1.1.1", "@types/lodash": "4.14.104", "@types/material-ui": "^0.20.0", @@ -110,15 +111,19 @@ "@types/react-scroll": "1.5.3", "@types/react-syntax-highlighter": "^0.0.8", "@types/react-tap-event-plugin": "0.0.30", + "@types/react-transition-group": "^4.2.0", "@types/redux": "^3.6.0", + "@types/valid-url": "^1.0.2", "@types/web3-provider-engine": "^14.0.0", "awesome-typescript-loader": "^5.2.1", "css-loader": "0.23.x", "less-loader": "^4.1.0", "make-promises-safe": "^1.1.0", + "node-sass": "^4.12.0", "raw-loader": "^0.5.1", "react-svg-loader": "^2.1.0", "rollbar-sourcemap-webpack-plugin": "^2.4.0", + "sass-loader": "^7.1.0", "shx": "^0.2.2", "source-map-loader": "^0.2.4", "style-loader": "0.23.x", diff --git a/packages/website/ts/components/button.tsx b/packages/website/ts/components/button.tsx index 7f515cc38e..3ca578c300 100644 --- a/packages/website/ts/components/button.tsx +++ b/packages/website/ts/components/button.tsx @@ -108,7 +108,7 @@ const ButtonBase = styled.button` border-color: ${props => props.isTransparent && !props.isNoBorder && !props.isWithArrow && '#00AE99'}; svg { - transform: translate3d(2px, -2px, 0); + transform: ${props => (props.isWithArrow ? 'translate3d(2px, -2px, 0)' : '')}; } } `; diff --git a/packages/website/ts/components/hero.tsx b/packages/website/ts/components/hero.tsx index 14d78df997..8e8c0d8ca0 100644 --- a/packages/website/ts/components/hero.tsx +++ b/packages/website/ts/components/hero.tsx @@ -117,7 +117,7 @@ const ButtonWrap = styled.div` flex-direction: column; justify-content: center; - * { + > * { padding-left: 20px; padding-right: 20px; } @@ -138,26 +138,39 @@ const BackgroundWrap = styled.div` top: 0; `; -export const Hero: React.StatelessComponent = (props: Props) => ( -
- {!!props.background && {props.background}} - - {props.figure && {props.figure}} +export class Hero extends React.Component { + public static defaultProps = { + isCenteredMobile: true, + }; + public shouldComponentUpdate(): boolean { + // The hero is a static component with animations. + // We do not want state changes in parent components to re-trigger animations. + return false; + } + public render(): React.ReactNode { + const props = this.props; + return ( +
+ {!!props.background && {props.background}} + + {props.figure && {props.figure}} - - {!!props.announcement && } - - {props.title} - + + {!!props.announcement && } + + {props.title} + - {props.description} + {props.description} - {props.actions && {props.actions}} - - -
-); - -Hero.defaultProps = { - isCenteredMobile: true, -}; + {props.actions && {props.actions}} + +
+
+ ); + } +} diff --git a/packages/website/ts/components/modals/modal_video.tsx b/packages/website/ts/components/modals/modal_video.tsx new file mode 100644 index 0000000000..ba3b4f5710 --- /dev/null +++ b/packages/website/ts/components/modals/modal_video.tsx @@ -0,0 +1,259 @@ +import React from 'react'; +import CSSTransition from 'react-transition-group/CSSTransition'; + +interface ModalVideoClassnames { + modalVideoEffect: string; + modalVideo: string; + modalVideoClose: string; + modalVideoBody: string; + modalVideoInner: string; + modalVideoIframeWrap: string; + modalVideoCloseBtn: string; +} + +interface Aria { + openMessage: string; + dismissBtnMessage: string; +} + +export interface ModalVideoProps { + onClose?: () => void; + isOpen: boolean; + classNames?: ModalVideoClassnames; + ratio?: string; + animationSpeed?: number; + allowFullScreen?: boolean; + aria?: Aria; + videoId?: string; + channel?: string; + youtube?: any; + vimeo?: any; + youku?: any; +} + +export interface ModalVideoState { + isOpen: boolean; +} + +export class ModalVideo extends React.Component { + public static defaultProps: ModalVideoProps = { + channel: 'youtube', + isOpen: false, + youtube: { + autoplay: 1, + cc_load_policy: 1, + color: null, + controls: 1, + disablekb: 0, + enablejsapi: 0, + end: null, + fs: 1, + h1: null, + iv_load_policy: 1, + list: null, + listType: null, + loop: 0, + modestbranding: null, + origin: null, + playlist: null, + playsinline: null, + rel: 0, + showinfo: 1, + start: 0, + wmode: 'transparent', + theme: 'dark', + }, + ratio: '16:9', + vimeo: { + api: false, + autopause: true, + autoplay: true, + byline: true, + callback: null, + color: null, + height: null, + loop: false, + maxheight: null, + maxwidth: null, + player_id: null, + portrait: true, + title: true, + width: null, + xhtml: false, + }, + youku: { + autoplay: 1, + show_related: 0, + }, + allowFullScreen: true, + animationSpeed: 300, + classNames: { + modalVideoEffect: 'modal-video-effect', + modalVideo: 'modal-video', + modalVideoClose: 'modal-video-close', + modalVideoBody: 'modal-video-body', + modalVideoInner: 'modal-video-inner', + modalVideoIframeWrap: 'modal-video-movie-wrap', + modalVideoCloseBtn: 'modal-video-close-btn', + }, + aria: { + openMessage: 'You just openned the modal video', + dismissBtnMessage: 'Close the modal by clicking here', + }, + }; + public modal: any; + public modalbtn: any; + constructor(props: ModalVideoProps) { + super(props); + this.state = { + isOpen: false, + }; + this.closeModal = this.closeModal.bind(this); + this.updateFocus = this.updateFocus.bind(this); + } + + public openModal(): void { + this.setState({ isOpen: true }); + } + + public closeModal(): void { + this.setState({ isOpen: false }); + if (typeof this.props.onClose === 'function') { + this.props.onClose(); + } + } + + public keydownHandler(e: any): void { + if (e.keyCode === 27) { + this.closeModal(); + } + } + + public componentDidMount(): void { + document.addEventListener('keydown', this.keydownHandler.bind(this)); + } + + public componentWillUnmount(): void { + document.removeEventListener('keydown', this.keydownHandler.bind(this)); + } + + public componentWillReceiveProps(nextProps: ModalVideoProps): void { + this.setState({ isOpen: nextProps.isOpen }); + } + + public componentDidUpdate(): void { + if (this.state.isOpen && this.modal) { + this.modal.focus(); + } + } + + public updateFocus(e: any): void { + if (e.keyCode === 9) { + e.preventDefault(); + e.stopPropagation(); + if (this.modal === document.activeElement) { + this.modalbtn.focus(); + } else { + this.modal.focus(); + } + } + } + + public getQueryString(obj: any): string { + let url = ''; + for (const key of Object.keys(obj)) { + if (obj.hasOwnProperty(key)) { + if (obj[key] !== null) { + url += `${key}=${obj[key]}&`; + } + } + } + return url.substr(0, url.length - 1); + } + + public getYoutubeUrl(youtube: any, videoId: string): string { + const query = this.getQueryString(youtube); + return `//www.youtube.com/embed/${videoId}?${query}`; + } + + public getVimeoUrl(vimeo: any, videoId: string): string { + const query = this.getQueryString(vimeo); + return `//play.vimeo.com/video/${videoId}?${query}`; + } + + public getYoukuUrl(youku: any, videoId: string): string { + const query = this.getQueryString(youku); + return `//player.youku.com/embed/${videoId}?${query}`; + } + + public getVideoUrl(opt: any, videoId: string): string { + if (opt.channel === 'youtube') { + return this.getYoutubeUrl(opt.youtube, videoId); + } else if (opt.channel === 'vimeo') { + return this.getVimeoUrl(opt.vimeo, videoId); + } else if (opt.channel === 'youku') { + return this.getYoukuUrl(opt.youku, videoId); + } + return ''; + } + + public getPadding(ratio: string): string { + const arr = ratio.split(':'); + const width = Number(arr[0]); + const height = Number(arr[1]); + const padding = (height * 100) / width; + return `${padding}%`; + } + + public render(): React.ReactNode { + const style = { + paddingBottom: this.getPadding(this.props.ratio), + }; + return ( + + {() => { + if (!this.state.isOpen) { + return null; + } + return ( +
{ + this.modal = node; + }} + onKeyDown={this.updateFocus} + > +
+
+
+