diff --git a/frontend/src/metabase/components/LoadingAndErrorWrapper.jsx b/frontend/src/metabase/components/LoadingAndErrorWrapper.jsx index d89b9d9f227d834b0cc7fa2a4717eca42306ac72..840d1a03e8f7cc3f10c6b77e7b4eb84c2341b222 100644 --- a/frontend/src/metabase/components/LoadingAndErrorWrapper.jsx +++ b/frontend/src/metabase/components/LoadingAndErrorWrapper.jsx @@ -7,15 +7,24 @@ import LoadingSpinner from "metabase/components/LoadingSpinner.jsx"; import cx from "classnames"; export default class LoadingAndErrorWrapper extends Component { + + state = { + messageIndex: 0, + sceneIndex: 0, + } + static propTypes = { - className: PropTypes.string, - error: PropTypes.any, - loading: PropTypes.any, - noBackground: PropTypes.bool, - noWrapper: PropTypes.bool, - children: PropTypes.any, - style: PropTypes.object, - showSpinner: PropTypes.bool + className: PropTypes.string, + error: PropTypes.any, + loading: PropTypes.any, + noBackground: PropTypes.bool, + noWrapper: PropTypes.bool, + children: PropTypes.any, + style: PropTypes.object, + showSpinner: PropTypes.bool, + loadingMessages: PropTypes.array, + messageInterval: PropTypes.number, + loadingScenes: PropTypes.array }; static defaultProps = { @@ -24,7 +33,9 @@ export default class LoadingAndErrorWrapper extends Component { loading: false, noBackground: false, noWrapper: false, - showSpinner: true + showSpinner: true, + loadingMessages: ['Loading...'], + messageInterval: 6000, }; getErrorMessage() { @@ -38,6 +49,29 @@ export default class LoadingAndErrorWrapper extends Component { ); } + componentDidMount () { + const { loadingMessages, messageInterval } = this.props; + // only start cycling if multiple messages are provided + if(loadingMessages.length > 1) { + this.cycle = setInterval(this.loadingInterval, messageInterval) + } + } + + componentWillUnmount () { + clearInterval(this.cycle) + } + + loadingInterval = () => { + this.cycleLoadingMessage() + if(this.props.loadingScenes) { + this.cycleLoadingScenes() + } + } + + cycleLoadingScenes = () => { + + } + getChildren() { function resolveChild(child) { if (Array.isArray(child)) { @@ -51,11 +85,41 @@ export default class LoadingAndErrorWrapper extends Component { return resolveChild(this.props.children); } + cycleLoadingMessage = () => { + this.setState({ + messageIndex: this.state.messageIndex + 1 < this.props.loadingMessages.length -1 + ? this.state.messageIndex + 1 + : 0 + + }) + } + + cycleLoadingScenes = () => { + this.setState({ + sceneIndex: this.state.sceneIndex + 1 < this.props.loadingScenes.length -1 + ? this.state.sceneIndex + 1 + : 0 + }) + } + render() { - const { loading, error, noBackground, noWrapper, showSpinner } = this.props; - const contentClassName = cx("wrapper py4 text-brand text-centered flex-full flex flex-column layout-centered", { - "bg-white": !noBackground - }); + const { + loading, + error, + noBackground, + noWrapper, + showSpinner, + loadingMessages, + loadingScenes + } = this.props; + + const { messageIndex, sceneIndex } = this.state; + + const contentClassName = cx( + "wrapper py4 text-brand text-centered flex-full flex flex-column layout-centered", + { "bg-white": !noBackground } + ); + if (noWrapper && !error && !loading) { return React.Children.only(this.getChildren()); } @@ -66,10 +130,12 @@ export default class LoadingAndErrorWrapper extends Component { <h2 className="text-normal text-grey-2 ie-wrap-content-fix">{this.getErrorMessage()}</h2> </div> : loading ? - showSpinner && <div className={contentClassName}> - <LoadingSpinner /> - <h2 className="text-normal text-grey-2 mt1">Loading...</h2> + { loadingScenes && loadingScenes[sceneIndex] } + { !loadingScenes && showSpinner && <LoadingSpinner /> } + <h2 className="text-normal text-grey-2 mt1"> + {loadingMessages[messageIndex]} + </h2> </div> : diff --git a/frontend/src/metabase/css/core/base.css b/frontend/src/metabase/css/core/base.css index bc19008d1e96161ddebe4eb394d30987483a7059..50633810bb8846ec366881b4abc6cb5e193c137e 100644 --- a/frontend/src/metabase/css/core/base.css +++ b/frontend/src/metabase/css/core/base.css @@ -77,3 +77,12 @@ textarea { .undefined { border: 1px solid red !important; } + +@keyframes spin { + 100% { transform: rotate(360deg); } +} + +@keyframes spin-reverse { + 100% { transform: rotate(-360deg); } +} + diff --git a/frontend/src/metabase/xray/components/LoadingAnimation.jsx b/frontend/src/metabase/xray/components/LoadingAnimation.jsx new file mode 100644 index 0000000000000000000000000000000000000000..487c6e8c0749fcb387e0976695d72544bfddf6f4 --- /dev/null +++ b/frontend/src/metabase/xray/components/LoadingAnimation.jsx @@ -0,0 +1,30 @@ +import React from 'react' +import Icon from 'metabase/components/Icon' + +const RotatingGear = ({name, speed, size, delay }) => + <div style={{ + animation: `${name} ${speed}ms linear ${delay}ms infinite` + }}> + <Icon name='gear' size={size} /> + </div> + +RotatingGear.defaultProps = { + name: 'spin', + delay: 0, + speed: 5000 +} + +const LoadingAnimation = () => + <div className="relative" style={{ width: 300, height: 180 }}> + <div className="absolute" style={{ top: 20, left: 135 }}> + <RotatingGear size={90} /> + </div> + <div className="absolute" style={{ top: 60, left: 80 }}> + <RotatingGear name='spin-reverse' size={60} speed={6000} /> + </div> + <div className="absolute" style={{ top: 110, left: 125 }}> + <RotatingGear speed={7000} size={45} /> + </div> + </div> + +export default LoadingAnimation diff --git a/frontend/src/metabase/xray/containers/SegmentComparison.jsx b/frontend/src/metabase/xray/containers/SegmentComparison.jsx index 6cdbd87d75fc1dfce092c64c024e3dcc7e8212be..4f854ec05100c193790da81df5db0634737bed7a 100644 --- a/frontend/src/metabase/xray/containers/SegmentComparison.jsx +++ b/frontend/src/metabase/xray/containers/SegmentComparison.jsx @@ -16,6 +16,7 @@ import { import LoadingAndErrorWrapper from 'metabase/components/LoadingAndErrorWrapper' import XRayComparison from 'metabase/xray/components/XRayComparison' +import LoadingAnimation from 'metabase/xray/components/LoadingAnimation' import { hasComparison } from 'metabase/xray/utils' @@ -45,6 +46,7 @@ class SegmentComparison extends Component { try { await this.props.fetchSegmentComparison(segmentId1, segmentId2, cost) } catch (error) { + console.log('error', error) this.setState({ error }) } } @@ -67,6 +69,14 @@ class SegmentComparison extends Component { loading={isLoading || !hasComparison(comparison)} error={error} noBackground + loadingMessages={[ + 'Generating your comparison...', + 'Teaching robots to love...', + 'Still working...', + ]} + loadingScenes={[ + <LoadingAnimation /> + ]} > { () => diff --git a/frontend/test/xray/xray.integ.spec.js b/frontend/test/xray/xray.integ.spec.js index 7468d71f8c82945ccfa680fd0283fbfae5211df4..c2f915948776e713f28c824011b131375ce1c612 100644 --- a/frontend/test/xray/xray.integ.spec.js +++ b/frontend/test/xray/xray.integ.spec.js @@ -103,7 +103,7 @@ describe("xray integration tests", () => { expect(cardXRay.text()).toMatch(/Time breakout question/); }) - xit("let you see segment xray for a question containing a segment", async () => { + it("let you see segment xray for a question containing a segment", async () => { const store = await createTestStore() store.pushPath(Urls.question(segmentQuestion.id())) const app = mount(store.getAppContainer());