= {
12 | alerts: [],
13 | };
14 |
15 | public render() {
16 | return {this.props.alerts.map(alert =>
)}
;
17 | }
18 | }
19 |
20 | export default AlertList;
21 |
--------------------------------------------------------------------------------
/src/components/App/index.spec.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react";
2 | import { shallow } from "enzyme";
3 | import { createStore } from "redux";
4 |
5 | import reducer, { State } from "../../redux";
6 |
7 | import createApp from "./";
8 |
9 | describe("App", () => {
10 | const store = createStore(reducer, new State());
11 | const App = createApp(store);
12 |
13 | it("should exist", () => {
14 | expect(App).toBeDefined();
15 | });
16 |
17 | it("should match snapshot", () => {
18 | const tree = shallow();
19 | expect(tree).toMatchSnapshot();
20 | });
21 | });
22 |
--------------------------------------------------------------------------------
/src/helpers/wrap.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react";
2 | import { Provider } from "react-redux";
3 | import { Store } from "redux";
4 |
5 | import { State } from "../redux";
6 |
7 | type HOC = React.ComponentClass
| React.SFC
;
8 |
9 | export function wrap
(MyComponent: HOC
, store: Store) {
10 | class Wrapped extends React.Component {
11 | public render() {
12 | return (
13 |
14 |
15 |
16 | );
17 | }
18 | }
19 |
20 | return Wrapped;
21 | }
22 |
23 | export default wrap;
24 |
--------------------------------------------------------------------------------
/src/components/Placeholder.module.scss:
--------------------------------------------------------------------------------
1 | @import "global.scss";
2 |
3 | .placeholder {
4 | /* stylelint-disable declaration-block-no-duplicate-properties, declaration-block-no-shorthand-property-overrides */
5 | @include preventive-overrides;
6 | outline: 1px solid darken($greySubtle, 5%) !important;
7 | background: #fff;
8 | /* stylelint-enable */
9 | margin: 0;
10 | background-size: auto 80px !important;
11 | position: relative;
12 | min-height: 300px;
13 | display: flex;
14 | align-items: center;
15 | justify-content: center;
16 | }
17 |
18 | .placeholder > code,
19 | .placeholder > div,
20 | .placeholder > p {
21 | display: none;
22 | }
23 |
--------------------------------------------------------------------------------
/src/containers/App.spec.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react";
2 | import { shallow } from "enzyme";
3 | import { createStore } from "redux";
4 | import { Provider } from "react-redux";
5 |
6 | import reducer, { State } from "../redux";
7 |
8 | import App from "./App";
9 |
10 | describe("App", () => {
11 | const store = createStore(reducer, new State());
12 |
13 | it("should exist", () => {
14 | expect(App).toBeDefined();
15 | });
16 |
17 | it("should match snapshot", () => {
18 | const tree = shallow(
19 |
20 |
21 |
22 | );
23 | expect(tree).toMatchSnapshot();
24 | });
25 | });
26 |
--------------------------------------------------------------------------------
/src/containers/Plot.spec.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react";
2 | import { shallow } from "enzyme";
3 | import { createStore } from "redux";
4 | import { Provider } from "react-redux";
5 |
6 | import reducer, { State } from "../redux";
7 |
8 | import Plot from "./Plot";
9 |
10 | describe("Plot", () => {
11 | const store = createStore(reducer, new State());
12 |
13 | it("should exist", () => {
14 | expect(Plot).toBeDefined();
15 | });
16 |
17 | it("should match snapshot", () => {
18 | const tree = shallow(
19 |
20 |
21 |
22 | );
23 | expect(tree).toMatchSnapshot();
24 | });
25 | });
26 |
--------------------------------------------------------------------------------
/src/containers/Editor.spec.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react";
2 | import { shallow } from "enzyme";
3 | import { createStore } from "redux";
4 | import { Provider } from "react-redux";
5 |
6 | import reducer, { State } from "../redux";
7 |
8 | import Editor from "./Editor";
9 |
10 | describe("Editor", () => {
11 | const store = createStore(reducer, new State());
12 |
13 | it("should exist", () => {
14 | expect(Editor).toBeDefined();
15 | });
16 |
17 | it("should match snapshot", () => {
18 | const tree = shallow(
19 |
20 |
21 |
22 | );
23 | expect(tree).toMatchSnapshot();
24 | });
25 | });
26 |
--------------------------------------------------------------------------------
/src/containers/Footer.spec.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react";
2 | import { shallow } from "enzyme";
3 | import { createStore } from "redux";
4 | import { Provider } from "react-redux";
5 |
6 | import reducer, { State } from "../redux";
7 |
8 | import Footer from "./Footer";
9 |
10 | describe("Footer", () => {
11 | const store = createStore(reducer, new State());
12 |
13 | it("should exist", () => {
14 | expect(Footer).toBeDefined();
15 | });
16 |
17 | it("should match snapshot", () => {
18 | const tree = shallow(
19 |
20 |
21 |
22 | );
23 | expect(tree).toMatchSnapshot();
24 | });
25 | });
26 |
--------------------------------------------------------------------------------
/src/containers/Console.spec.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react";
2 | import { shallow } from "enzyme";
3 | import { createStore } from "redux";
4 | import { Provider } from "react-redux";
5 |
6 | import reducer, { State } from "../redux";
7 |
8 | import Console from "./Console";
9 |
10 | describe("Console", () => {
11 | const store = createStore(reducer, new State());
12 |
13 | it("should exist", () => {
14 | expect(Console).toBeDefined();
15 | });
16 |
17 | it("should match snapshot", () => {
18 | const tree = shallow(
19 |
20 |
21 |
22 | );
23 | expect(tree).toMatchSnapshot();
24 | });
25 | });
26 |
--------------------------------------------------------------------------------
/src/containers/GlobalAlerts.spec.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react";
2 | import { shallow } from "enzyme";
3 | import { createStore } from "redux";
4 | import { Provider } from "react-redux";
5 |
6 | import reducer, { State } from "../redux";
7 |
8 | import GlobalAlerts from "./GlobalAlerts";
9 |
10 | describe("GlobalAlerts", () => {
11 | const store = createStore(reducer, new State());
12 |
13 | it("should exist", () => {
14 | expect(GlobalAlerts).toBeDefined();
15 | });
16 |
17 | it("should match snapshot", () => {
18 | const tree = shallow(
19 |
20 |
21 |
22 | );
23 | expect(tree).toMatchSnapshot();
24 | });
25 | });
26 |
--------------------------------------------------------------------------------
/src/containers/BackendStatus.spec.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react";
2 | import { shallow } from "enzyme";
3 | import { createStore } from "redux";
4 | import { Provider } from "react-redux";
5 |
6 | import reducer, { State } from "../redux";
7 |
8 | import BackendStatus from "./BackendStatus";
9 |
10 | describe("BackendStatus", () => {
11 | const store = createStore(reducer, new State());
12 |
13 | it("should exist", () => {
14 | expect(BackendStatus).toBeDefined();
15 | });
16 |
17 | it("should match snapshot", () => {
18 | const tree = shallow(
19 |
20 |
21 |
22 | );
23 | expect(tree).toMatchSnapshot();
24 | });
25 | });
26 |
--------------------------------------------------------------------------------
/src/containers/SolutionEditor.spec.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react";
2 | import { shallow } from "enzyme";
3 | import { createStore } from "redux";
4 | import { Provider } from "react-redux";
5 |
6 | import reducer, { State } from "../redux";
7 |
8 | import SolutionEditor from "./SolutionEditor";
9 |
10 | describe("SolutionEditor", () => {
11 | const store = createStore(reducer, new State());
12 |
13 | it("should exist", () => {
14 | expect(SolutionEditor).toBeDefined();
15 | });
16 |
17 | it("should match snapshot", () => {
18 | const tree = shallow(
19 |
20 |
21 |
22 | );
23 | expect(tree).toMatchSnapshot();
24 | });
25 | });
26 |
--------------------------------------------------------------------------------
/src/containers/FeedbackMessage.spec.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react";
2 | import { shallow } from "enzyme";
3 | import { createStore } from "redux";
4 | import { Provider } from "react-redux";
5 |
6 | import reducer, { State } from "../redux";
7 |
8 | import FeedbackMessage from "./FeedbackMessage";
9 |
10 | describe("FeedbackMessage", () => {
11 | const store = createStore(reducer, new State());
12 |
13 | it("should exist", () => {
14 | expect(FeedbackMessage).toBeDefined();
15 | });
16 |
17 | it("should match snapshot", () => {
18 | const tree = shallow(
19 |
20 |
21 |
22 | );
23 | expect(tree).toMatchSnapshot();
24 | });
25 | });
26 |
--------------------------------------------------------------------------------
/src/redux/index.spec.ts:
--------------------------------------------------------------------------------
1 | import reducer, * as index from "./index";
2 |
3 | describe("Redux", () => {
4 | test("should define a State class", () => {
5 | expect(index.State).toBeDefined();
6 | });
7 |
8 | test("should have an initial state with all modules", () => {
9 | const state = new index.State();
10 | expect(state.get("autocomplete")).toBeDefined();
11 | expect(state.get("backendSession")).toBeDefined();
12 | expect(state.get("exercise")).toBeDefined();
13 | expect(state.get("output")).toBeDefined();
14 | expect(state.get("view")).toBeDefined();
15 | });
16 |
17 | test("should export a reducer", () => {
18 | expect(reducer).toBeDefined();
19 | expect(reducer).toBeInstanceOf(Function);
20 | });
21 | });
22 |
--------------------------------------------------------------------------------
/vendor/@datacamp/ui-plot/index.d.ts:
--------------------------------------------------------------------------------
1 | declare module "@datacamp/ui-plot" {
2 | import { Component, Props } from "react";
3 |
4 | export interface IPlotSource {
5 | type: "img" | "html";
6 | src: string;
7 | }
8 |
9 | export interface IPlotProps extends Props {
10 | sources?: IPlotSource[];
11 | currentIndex?: number;
12 | setIndex: (currentIndex: number) => void;
13 | exercise: {
14 | language: string;
15 | type: string;
16 | };
17 | resizePlot: (
18 | currentIndex: number,
19 | language: string,
20 | width?: number,
21 | height?: number
22 | ) => void;
23 | expand: (source: string) => void;
24 | }
25 |
26 | export default class Plot extends Component {}
27 | }
28 |
--------------------------------------------------------------------------------
/src/containers/RestartSessionButton.spec.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react";
2 | import { shallow } from "enzyme";
3 | import { createStore } from "redux";
4 | import { Provider } from "react-redux";
5 |
6 | import reducer, { State } from "../redux";
7 |
8 | import RestartSessionButton from "./RestartSessionButton";
9 |
10 | describe("RestartSessionButton", () => {
11 | const store = createStore(reducer, new State());
12 |
13 | it("should exist", () => {
14 | expect(RestartSessionButton).toBeDefined();
15 | });
16 |
17 | it("should match snapshot", () => {
18 | const tree = shallow(
19 |
20 |
21 |
22 | );
23 | expect(tree).toMatchSnapshot();
24 | });
25 | });
26 |
--------------------------------------------------------------------------------
/src/containers/App.tsx:
--------------------------------------------------------------------------------
1 | import { connect, MapDispatchToProps, MapStateToProps } from "react-redux";
2 | import { Store } from "redux";
3 |
4 | import { State } from "../redux";
5 | import { selectPlots } from "../redux/output";
6 |
7 | import { App, IAppProps } from "../components/App";
8 |
9 | const mapStateToProps: MapStateToProps, {}> = (
10 | state: State
11 | ) => ({
12 | nPlots: selectPlots(state).length,
13 | });
14 |
15 | const createAppInstance = (store: Store) =>
16 | class AppInstance extends App {
17 | constructor() {
18 | super();
19 | this.store = store;
20 | }
21 | };
22 |
23 | export default (store: Store) =>
24 | connect, {}, IAppProps>(mapStateToProps)(
25 | createAppInstance(store)
26 | );
27 |
--------------------------------------------------------------------------------
/src/components/__snapshots__/Footer.spec.tsx.snap:
--------------------------------------------------------------------------------
1 | // Jest Snapshot v1, https://goo.gl/fbAQLP
2 |
3 | exports[`Footer should match snapshot 1`] = `
4 |
7 |
15 |
18 |
21 |
25 |
31 |
32 |
33 |
34 | `;
35 |
--------------------------------------------------------------------------------
/src/helpers/uuid.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Inspired by http://stackoverflow.com/questions/105034/create-guid-uuid-in-javascript/
3 | */
4 | export const generateUUID = () => {
5 | let d: number;
6 | if (Date.now) {
7 | d = Date.now();
8 | } else {
9 | d = new Date().getTime();
10 | }
11 |
12 | if (window.performance) {
13 | d += performance.now(); // use high-precision timer if available
14 | }
15 |
16 | const uuid = "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, c => {
17 | // tslint:disable-next-line:no-bitwise
18 | const r = ((d + Math.random() * 16) % 16) | 0;
19 | d = Math.floor(d / 16);
20 | // tslint:disable-next-line:no-bitwise
21 | return (c === "x" ? r : (r & 0x3) | 0x8).toString(16);
22 | });
23 | return uuid;
24 | };
25 |
26 | export default generateUUID;
27 |
--------------------------------------------------------------------------------
/vendor/@datacamp/ui-console/lib/style.css:
--------------------------------------------------------------------------------
1 | .Console-module__console--I4oMQ .ace_editor {
2 | position: absolute;
3 | top: 0;
4 | left: 0;
5 | right: 0;
6 | bottom: 0;
7 | height: 100%;
8 | width: 100%;
9 | }
10 | .Console-module__console--I4oMQ .ace_error {
11 | color: #e70909;
12 | }
13 | .Console-module__console--I4oMQ .ace_output {
14 | color: #8e6627;
15 | }
16 | .Console-module__console--I4oMQ
17 | .ace_editor.ace_autocomplete
18 | .ace_marker-layer
19 | .ace_active-line {
20 | background-color: rgba(51, 170, 204, 0.5) !important;
21 | }
22 | .Console-module__console--I4oMQ .ace_editor.ace_autocomplete .ace_line-hover {
23 | border: 1px solid #3ac !important;
24 | background-color: rgba(51, 170, 204, 0.32) !important;
25 | }
26 | .Console-module__console--I4oMQ .ace_editor .ace_storage.ace_type {
27 | width: auto;
28 | }
29 |
--------------------------------------------------------------------------------
/src/helpers/hub.ts:
--------------------------------------------------------------------------------
1 | export type Listener = (type: string, payload: any) => void;
2 |
3 | export default class Hub {
4 | listeners: { [x: string]: Listener } = {};
5 |
6 | on(type: string, listener: Listener) {
7 | if (!this.listeners) {
8 | this.listeners = {};
9 | }
10 |
11 | this.listeners[type] = listener;
12 | console.debug("HUB/on", type, listener, this);
13 | }
14 |
15 | off(type: string) {
16 | if (!this.listeners) {
17 | this.listeners = {};
18 | }
19 |
20 | this.listeners[type] = undefined;
21 | console.debug("HUB/off", type, this);
22 | }
23 |
24 | process(type: string, payload: any) {
25 | if (!this.listeners) {
26 | this.listeners = {};
27 | }
28 |
29 | const listener = this.listeners[type];
30 | if (listener) {
31 | listener(type, payload);
32 | }
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/src/containers/FeedbackMessage.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react";
2 | import { connect, MapStateToProps } from "react-redux";
3 |
4 | import { State } from "../redux";
5 | import { selectFeedback } from "../redux/exercise";
6 | import { IAlert } from "../redux/view";
7 |
8 | import Alert, { IAlertProps } from "../components/Alert";
9 |
10 | interface IOwnProps {
11 | className?: string;
12 | }
13 |
14 | interface IFeedbackMessageState {
15 | alert?: IAlert;
16 | className?: string;
17 | }
18 |
19 | const mapStateToProps: MapStateToProps, IOwnProps> = (
20 | state: State,
21 | ownProps
22 | ) => ({
23 | alert: selectFeedback(state),
24 | className: ownProps.className,
25 | });
26 |
27 | export default connect(mapStateToProps)(
28 | (state: IFeedbackMessageState) =>
29 | state.alert ? (
30 |
31 | ) : null
32 | );
33 |
--------------------------------------------------------------------------------
/src/containers/SolutionEditor.tsx:
--------------------------------------------------------------------------------
1 | import { connect, MapDispatchToProps, MapStateToProps } from "react-redux";
2 | import EditorBody, { IEditorProps } from "@datacamp/ui-editor";
3 | import "@datacamp/ui-editor/lib/style.css";
4 |
5 | import { State } from "../redux";
6 | import {
7 | selectLanguage,
8 | selectPreExerciseCode,
9 | selectSolution,
10 | selectSct,
11 | updateCode,
12 | } from "../redux/exercise";
13 |
14 | interface IOwnProps extends Partial {
15 | className?: string;
16 | id?: number;
17 | }
18 |
19 | const mapStateToProps: MapStateToProps = (
20 | state: State,
21 | ownProps
22 | ) => ({
23 | className: ownProps.className,
24 | id: ownProps.id,
25 | code: selectSolution(state),
26 | language: selectLanguage(state),
27 | pre_exercise_code: selectPreExerciseCode(state),
28 | sct: selectSct(state),
29 | });
30 |
31 | export default connect(mapStateToProps)(EditorBody);
32 |
--------------------------------------------------------------------------------
/src/components/Alert.spec.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react";
2 | import { shallow } from "enzyme";
3 |
4 | import { Alert } from "./Alert";
5 |
6 | describe("Alert", () => {
7 | it("should exist", () => {
8 | expect(Alert).toBeDefined();
9 | });
10 |
11 | it("should match snapshot", () => {
12 | const tree = shallow();
13 | expect(tree).toMatchSnapshot();
14 | });
15 |
16 | it("should prevent xss", async () => {
17 | const maliciousContent =
18 | '
' +
19 | '';
20 | const component = shallow(
21 |
22 | );
23 | const $ = component.render();
24 | const img = $.find("img");
25 |
26 | expect(img).toHaveLength(1);
27 | expect(img.attr("onerror")).toBeUndefined();
28 | expect($.find("script")).toHaveLength(0);
29 | });
30 | });
31 |
--------------------------------------------------------------------------------
/src/containers/RestartSessionButton.tsx:
--------------------------------------------------------------------------------
1 | import { connect, MapDispatchToProps, MapStateToProps } from "react-redux";
2 |
3 | import { restartSession } from "../redux/backend-session";
4 | import { State } from "../redux";
5 |
6 | import {
7 | RestartSessionButton,
8 | IRestartSessionButtonProps,
9 | } from "../components/RestartSessionButton";
10 |
11 | export interface IOwnProps {
12 | visible?: boolean;
13 | className?: string;
14 | }
15 |
16 | const mapStateToProps: MapStateToProps<
17 | IRestartSessionButtonProps,
18 | IOwnProps
19 | > = (state: State, ownProps) => ({
20 | visible: ownProps.visible,
21 | className: ownProps.className,
22 | });
23 |
24 | const mapDispatchToProps: MapDispatchToProps<
25 | IRestartSessionButtonProps,
26 | IOwnProps
27 | > = dispatch => ({
28 | onClickRestart: event => dispatch(restartSession()),
29 | });
30 |
31 | export default connect(mapStateToProps, mapDispatchToProps)(
32 | RestartSessionButton
33 | );
34 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "es5",
4 | "moduleResolution": "node",
5 | "rootDir": "src",
6 | "sourceMap": true,
7 | "emitDecoratorMetadata": true,
8 | "experimentalDecorators": true,
9 | "noImplicitAny": true, // `false` is for pussies
10 | "strictNullChecks": false,
11 | "noStrictGenericChecks": true,
12 | "lib": ["dom", "dom.iterable", "esnext"],
13 | "jsx": "react",
14 | "baseUrl": ".",
15 | "allowJs": true,
16 | "paths": {
17 | "*": ["*", "vendor/"]
18 | }
19 | },
20 | "include": ["src/**/*.{ts,tsx,svg}", "lib/**/*.js"],
21 | "exclude": ["node_modules", "dist", "webpack.config.js", "configs"],
22 | "files": ["src/index.d.ts", "src/index.ts"],
23 | "awesomeTypescriptLoaderOptions": {
24 | "forkChecker": true,
25 | "useWebpackText": true,
26 | "reportFiles": ["src/**/*.{ts,tsx,js,jsx}"]
27 | },
28 | "compileOnSave": false,
29 | "buildOnSave": false
30 | }
31 |
--------------------------------------------------------------------------------
/src/containers/Terminal.tsx:
--------------------------------------------------------------------------------
1 | import { connect, MapDispatchToProps, MapStateToProps } from "react-redux";
2 | import Terminal, { ITerminalProps } from "../components/Terminal";
3 |
4 | import { State } from "../redux";
5 | import { selectPlots, selectPlotIndex, setPlotIndex } from "../redux/output";
6 | import { selectBackendSessionIsBroken } from "../redux/backend-session";
7 | import { selectExercise, selectShellProxy } from "../redux/exercise";
8 | import { expandTab, setRenderSize } from "../redux/view";
9 |
10 | import "@datacamp/ui-plot/lib/style.css";
11 |
12 | const mapStateToProps: MapStateToProps, {}> = (
13 | state: State
14 | ) => ({
15 | socketUrl: selectShellProxy(state),
16 | isSessionBroken: selectBackendSessionIsBroken(state),
17 | });
18 |
19 | const mapDispatchToProps: MapDispatchToProps, {}> = (
20 | dispatch: any
21 | ) => ({});
22 |
23 | export default connect(mapStateToProps, mapDispatchToProps)(Terminal);
24 |
--------------------------------------------------------------------------------
/src/components/Footer.module.scss:
--------------------------------------------------------------------------------
1 | @import "global.scss";
2 |
3 | .footer {
4 | height: 48px; /* if you change me, also change the value in the golden layout root style, in ./index.tsx */
5 | background: $greySubtle;
6 | text-align: left;
7 | display: flex;
8 | justify-content: flex-start;
9 | align-items: center;
10 | align-content: center;
11 | }
12 |
13 | .spacer {
14 | flex-grow: 1;
15 | }
16 |
17 | .restart {
18 | display: inline-block;
19 | }
20 |
21 | .status {
22 | margin: 0 $baseline;
23 |
24 | svg + span {
25 | color: $navy;
26 | }
27 | }
28 |
29 | :root a.logo,
30 | :root a.logo:focus,
31 | :root a.logo:hover {
32 | text-decoration: none;
33 | border: none;
34 | border-bottom: none;
35 | line-height: 48px;
36 | }
37 |
38 | .logo svg {
39 | height: 2em;
40 | margin: 0 16px 0 0;
41 | display: block;
42 | line-height: 48px;
43 | }
44 |
45 | @media screen and (max-width: 640px) {
46 | .status {
47 | margin: 0 $baseline/4;
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/src/index.ts:
--------------------------------------------------------------------------------
1 | import "./helpers/polyfill";
2 | import "nodelist-foreach-polyfill";
3 |
4 | import boot from "./boot";
5 | import Hub, { Listener } from "./helpers/hub";
6 |
7 | import "./styles/golden-layout/index.css";
8 |
9 | export let instances: { [x: string]: Hub } = {};
10 |
11 | function bootstrapDCLightExercises() {
12 | // Load all exercise div's and bootstrap the DataCamp Light Angular app
13 | const exercises = document.querySelectorAll("[data-datacamp-exercise]");
14 | exercises.forEach((el: HTMLDivElement) => {
15 | let hub = new Hub();
16 | const id = boot(el, hub);
17 | instances[id] = hub;
18 | });
19 | }
20 |
21 | (global as any).bootstrapDCLightExercises = bootstrapDCLightExercises;
22 | (global as any).initAddedDCLightExercises = bootstrapDCLightExercises;
23 |
24 | document.addEventListener("DOMContentLoaded", bootstrapDCLightExercises);
25 |
26 | if (document.readyState !== "loading") {
27 | bootstrapDCLightExercises();
28 | }
29 |
30 | // Exports are exported in global variable 'DCL'
31 |
32 | export const init = bootstrapDCLightExercises;
33 |
--------------------------------------------------------------------------------
/vendor/@datacamp/ui-editor/lib/style.css:
--------------------------------------------------------------------------------
1 | .Editor-module__editor--3du3f .ace_editor {
2 | position: absolute;
3 | top: 0;
4 | left: 0;
5 | right: 0;
6 | bottom: 0;
7 | height: 100%;
8 | width: 100%;
9 | line-height: 1.3em;
10 | }
11 | .Editor-module__editor--3du3f .ace_editor[id^="ace-code-editor-"] {
12 | bottom: 50px;
13 | }
14 | .Editor-module__editor--3du3f .ace_editor.ace-crimson-editor .ace_gutter {
15 | background-color: #fff;
16 | }
17 | .Editor-module__editor--3du3f
18 | .ace_editor.ace-crimson-editor
19 | .ace_gutter-active-line {
20 | background-color: #d5eaef;
21 | }
22 | .Editor-module__editor--3du3f
23 | .ace_editor.ace-crimson-editor
24 | .ace_marker-layer
25 | .ace_active-line {
26 | background-color: #ebf4f7;
27 | }
28 | .Editor-module__editor--3du3f .error-line {
29 | position: absolute;
30 | background-color: #ffbfbf;
31 | z-index: 3;
32 | }
33 | .Editor-module__editor--3du3f .error-gutter {
34 | background-color: #ffbfbf;
35 | }
36 | .Editor-module__editor--3du3f .ace_gutter .ace_gutter-layer {
37 | min-width: 50px !important;
38 | }
39 |
--------------------------------------------------------------------------------
/configs/webpack.serve.js:
--------------------------------------------------------------------------------
1 | const path = require("path");
2 | const webpack = require("webpack");
3 | const Merge = require("webpack-merge");
4 |
5 | const config = require("./config");
6 | const DevConfig = require("./webpack.dev");
7 |
8 | module.exports = Merge.strategy({
9 | entry: "prepend",
10 | })(DevConfig, {
11 | entry: {
12 | main: [
13 | "react-hot-loader/patch",
14 | // activate HMR for React
15 |
16 | `webpack-dev-server/client?http://localhost:${config.port}`,
17 | // bundle the client for webpack-dev-server
18 | // and connect to the provided endpoint
19 |
20 | "webpack/hot/only-dev-server",
21 | // bundle the client for hot reloading
22 | // only- means to only hot reload for successful updates
23 | ],
24 | },
25 |
26 | devServer: {
27 | port: config.port,
28 | host: config.host,
29 | contentBase: path.resolve(config.root, "dist"),
30 | historyApiFallback: true,
31 | hot: true,
32 | watchOptions: {
33 | ignored: /node_modules/,
34 | },
35 | },
36 |
37 | plugins: [new webpack.HotModuleReplacementPlugin()],
38 | });
39 |
--------------------------------------------------------------------------------
/src/containers/BackendStatus.tsx:
--------------------------------------------------------------------------------
1 | import * as BackendStatus from "@datacamp/ui-backend-status";
2 | import "@datacamp/ui-backend-status/lib/style.css";
3 | import { connect, MapStateToProps } from "react-redux";
4 |
5 | import { State } from "../redux";
6 | import { selectBackendUIStatus } from "../redux/backend-session";
7 | import { selectLanguage } from "../redux/exercise";
8 |
9 | interface IStateProps extends Partial {
10 | isInitSession: boolean;
11 | language: string;
12 | status: {
13 | code: string;
14 | text: string;
15 | };
16 | }
17 |
18 | interface IOwnProps extends Partial {
19 | runtimeConfig?: string;
20 | className?: string;
21 | }
22 |
23 | const mapStateToProps: MapStateToProps = (
24 | state: State
25 | ) => ({
26 | isInitSession: true,
27 | language: selectLanguage(state),
28 | runtimeConfig: "spark",
29 | status: selectBackendUIStatus(state),
30 | });
31 |
32 | export default connect(
33 | mapStateToProps
34 | )(BackendStatus as any);
35 |
--------------------------------------------------------------------------------
/vendor/@datacamp/ui-backend-status/lib/style.css:
--------------------------------------------------------------------------------
1 | ._backendStatus_17b2u_1 {
2 | cursor: default;
3 | display: inline-block;
4 | position: relative;
5 | }
6 | ._backendStatus_17b2u_1 .fa.fa-circle {
7 | float: none;
8 | }
9 | ._backendStatus_17b2u_1 i.fa-circle {
10 | font-size: 1.1rem;
11 | padding-top: 8px;
12 | float: left;
13 | }
14 |
15 | ._busy_17b2u_12 {
16 | color: #fcce0d;
17 | }
18 |
19 | ._broken_17b2u_15 {
20 | color: #ff5400;
21 | }
22 |
23 | ._none_17b2u_18 {
24 | color: #d1d3d8;
25 | }
26 |
27 | ._ready_17b2u_21 {
28 | color: #03ef62;
29 | }
30 |
31 | ._statusText_17b2u_24 {
32 | padding-top: 6px;
33 | }
34 |
35 | ._animation_17b2u_27 {
36 | position: absolute;
37 | transition: all 0.3s ease-out;
38 | white-space: pre;
39 | right: 20px;
40 | top: 5px;
41 | }
42 |
43 | ._statusText_17b2u_24 {
44 | display: inline;
45 | }
46 |
47 | @media screen and (max-width: 640px) {
48 | ._statusText_17b2u_24 {
49 | display: none;
50 | }
51 | }
52 |
53 | ._hidden_17b2u_41 {
54 | display: none;
55 | }
56 |
57 | ._icon_17b2u_44 {
58 | margin-right: 5px;
59 | height: 100% !important;
60 | }
61 |
--------------------------------------------------------------------------------
/src/containers/Editor.tsx:
--------------------------------------------------------------------------------
1 | import { connect, MapDispatchToProps, MapStateToProps } from "react-redux";
2 | import EditorBody, { IEditorProps } from "@datacamp/ui-editor";
3 | import "@datacamp/ui-editor/lib/style.css";
4 |
5 | import { updateCode } from "../redux/exercise";
6 | import { State } from "../redux";
7 | import {
8 | selectLanguage,
9 | selectPreExerciseCode,
10 | selectSampleCode,
11 | selectSct,
12 | } from "../redux/exercise";
13 |
14 | interface IOwnProps extends Partial {
15 | className?: string;
16 | id?: number;
17 | }
18 |
19 | const mapStateToProps: MapStateToProps = (
20 | state: State,
21 | ownProps
22 | ) => ({
23 | className: ownProps.className,
24 | code: selectSampleCode(state),
25 | language: selectLanguage(state),
26 | pre_exercise_code: selectPreExerciseCode(state),
27 | sct: selectSct(state),
28 | id: ownProps.id,
29 | });
30 |
31 | const mapDispatchToProps: MapDispatchToProps = dispatch => ({
32 | onCodeChange: ({ code }) => dispatch(updateCode(code)),
33 | });
34 |
35 | export default connect(mapStateToProps, mapDispatchToProps)(EditorBody);
36 |
--------------------------------------------------------------------------------
/src/helpers/epics.ts:
--------------------------------------------------------------------------------
1 | import { values } from "lodash";
2 | import { combineEpics, Epic } from "redux-observable";
3 | import { Observable } from "rxjs/Observable";
4 | import { BehaviorSubject } from "rxjs/BehaviorSubject";
5 | import "rxjs/add/observable/of";
6 | import "rxjs/add/observable/concat";
7 | import "rxjs/add/operator/mergeMap";
8 | import "rxjs/add/operator/catch";
9 | import { AnyAction as Action } from "typescript-fsa";
10 |
11 | import { State, epics } from "../redux";
12 | import { backendError } from "../redux/backend-session";
13 |
14 | const epic$ = new BehaviorSubject(combineEpics(...values(epics)));
15 |
16 | export const mergeEpics: Epic = (action$, store) =>
17 | epic$.mergeMap(epic =>
18 | epic(action$, store, null).catch((e, stream) => {
19 | if (__IS_DEBUG__) {
20 | // tslint:disable-next-line:no-console
21 | console.error("Error uncaught: ", e);
22 | }
23 | return Observable.concat(
24 | Observable.of(
25 | backendError({
26 | output: e,
27 | })
28 | ),
29 | stream
30 | );
31 | })
32 | );
33 |
34 | export default mergeEpics;
35 |
--------------------------------------------------------------------------------
/src/components/RestartSessionButton.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react";
2 |
3 | import Button from "./Button";
4 | import * as styles from "./Button.module.scss";
5 |
6 | import noop from "../helpers/noop";
7 |
8 | export interface IRestartSessionButtonProps
9 | extends React.Props {
10 | className?: string;
11 | visible?: boolean;
12 | onClickRestart?: (event: React.SyntheticEvent) => void;
13 | }
14 |
15 | export class RestartSessionButton extends React.Component<
16 | IRestartSessionButtonProps
17 | > {
18 | public static defaultProps: Partial = {
19 | visible: true,
20 | onClickRestart: noop,
21 | };
22 |
23 | public render() {
24 | if (!this.props.visible) {
25 | return false;
26 | }
27 |
28 | return (
29 |
30 |
38 |
39 | );
40 | }
41 | }
42 |
43 | export default RestartSessionButton;
44 |
--------------------------------------------------------------------------------
/src/containers/Plot.tsx:
--------------------------------------------------------------------------------
1 | import { connect, MapDispatchToProps, MapStateToProps } from "react-redux";
2 | import * as Plot from "@datacamp/ui-plot";
3 |
4 | import { State } from "../redux";
5 | import { selectPlots, selectPlotIndex, setPlotIndex } from "../redux/output";
6 | import { selectExercise } from "../redux/exercise";
7 | import { expandTab, setRenderSize } from "../redux/view";
8 |
9 | import "@datacamp/ui-plot/lib/style.css";
10 |
11 | const mapStateToProps: MapStateToProps, {}> = (
12 | state: State
13 | ) => ({
14 | sources: selectPlots(state),
15 | currentIndex: selectPlotIndex(state),
16 | exercise: selectExercise(state),
17 | });
18 |
19 | const mapDispatchToProps: MapDispatchToProps, {}> = (
20 | dispatch: any
21 | ) => ({
22 | setIndex: (index: number) => dispatch(setPlotIndex(index)),
23 | resizePlot: (currentIndex: any, language: any, width: any, height: any) =>
24 | dispatch(
25 | setRenderSize({
26 | width,
27 | height,
28 | })
29 | ),
30 | expand: src =>
31 | dispatch(expandTab({ category: "graphicalTabs", tabKey: "plot", src })),
32 | });
33 |
34 | export default connect(mapStateToProps, mapDispatchToProps)(Plot as any);
35 |
--------------------------------------------------------------------------------
/src/components/App/__snapshots__/index.spec.tsx.snap:
--------------------------------------------------------------------------------
1 | // Jest Snapshot v1, https://goo.gl/fbAQLP
2 |
3 | exports[`App should match snapshot 1`] = `
4 |
15 |
52 |
53 | `;
54 |
--------------------------------------------------------------------------------
/vendor/@datacamp/ui-editor/index.d.ts:
--------------------------------------------------------------------------------
1 | declare module "@datacamp/ui-editor" {
2 | import { Component, Props } from "react";
3 |
4 | export const formatCode: (code: string) => string;
5 | export const getEditor: (editorId: any) => any;
6 | export const setEditorCode: (editorId: any, code: string) => void;
7 |
8 | interface IEditorProps extends Props {
9 | lineErrors?: { [x: string]: any };
10 | uniqueId?: string;
11 | code?: string;
12 | language?: string;
13 | saveCode?: (
14 | data: {
15 | id: number;
16 | code: string;
17 | tabKey: string;
18 | category: string;
19 | }
20 | ) => void;
21 | id?: number;
22 | pre_exercise_code?: string;
23 | solution?: string;
24 | sct?: string;
25 | type?: string;
26 | ckey?: string;
27 | onFocusChanged?: (isFocus: boolean) => void;
28 | onCodeChange?: (
29 | data: {
30 | code: string;
31 | }
32 | ) => void;
33 | getCompletions?: (...args: any[]) => void; // TODO
34 | setCompletionsCallback?: (...args: any[]) => void; // TODO
35 | enableAutocomplete?: boolean;
36 | aceMode?: string;
37 | onEditorMount?: (...args: any[]) => void;
38 | className?: string;
39 | }
40 |
41 | export default class EditorBody extends Component {}
42 | }
43 |
--------------------------------------------------------------------------------
/vendor/@datacamp/ui-console/index.d.ts:
--------------------------------------------------------------------------------
1 | declare module "@datacamp/ui-console" {
2 | import { Component, Props } from "react";
3 |
4 | export const getEditor: (consoleId: string) => any;
5 |
6 | export interface IConsoleProps extends Props {
7 | canExecuteCommand?: boolean;
8 | code?: string;
9 | consoleId?: string;
10 | getCompletions?: (...args: any[]) => any;
11 | id?: number;
12 | language?: string;
13 | onFocusChanged?: (isFocus: boolean) => void;
14 | pre_exercise_code?: string;
15 | printDelay?: number;
16 | promptPrefix?: string;
17 | saveCode?: (
18 | data: {
19 | id: number;
20 | code: string;
21 | tabKey: string;
22 | category: string;
23 | }
24 | ) => void;
25 | sct?: string;
26 | setCompletionsCallback?: (...args: any[]) => any;
27 | setOutputCallback?: (callback: (outputArray: any[]) => void) => void;
28 | solution?: string;
29 | submitCode?: (
30 | command: {
31 | code: string;
32 | command: string;
33 | id?: number;
34 | language?: string;
35 | pec?: string;
36 | sct?: string;
37 | solution?: string;
38 | }
39 | ) => void;
40 | type?: string;
41 | }
42 |
43 | export default class ConsoleBody extends Component {}
44 | }
45 |
--------------------------------------------------------------------------------
/src/images/datacamp-logo.svg:
--------------------------------------------------------------------------------
1 |
2 |
11 |
--------------------------------------------------------------------------------
/src/containers/Footer.tsx:
--------------------------------------------------------------------------------
1 | import { connect, MapDispatchToProps, MapStateToProps } from "react-redux";
2 |
3 | import { State } from "../redux";
4 | import {
5 | selectBackendSessionIsBroken,
6 | selectBackendSessionIsBusy,
7 | submitCode,
8 | } from "../redux/backend-session";
9 | import {
10 | showHint,
11 | selectHint,
12 | selectSolution,
13 | selectSct,
14 | selectShowRunButton,
15 | selectLanguage,
16 | } from "../redux/exercise";
17 |
18 | import { Footer, IFooterProps } from "../components/Footer";
19 |
20 | interface IOwnProps extends Partial {
21 | onShowSolution?: (event: React.SyntheticEvent) => void;
22 | }
23 |
24 | const mapStateToProps: MapStateToProps = (
25 | state: State,
26 | ownProps
27 | ) => ({
28 | isSessionBroken: selectBackendSessionIsBroken(state),
29 | isSessionBusy: selectBackendSessionIsBusy(state),
30 | hint: selectHint(state),
31 | solution: selectSolution(state),
32 | showRunButton: selectShowRunButton(state),
33 | sct: selectSct(state),
34 | language: selectLanguage(state),
35 | });
36 |
37 | const mapDispatchToProps: MapDispatchToProps = (
38 | dispatch,
39 | ownProps
40 | ) => ({
41 | onShowHint: event => dispatch(showHint()),
42 | onSubmit: payload => dispatch(submitCode(payload)),
43 | onShowSolution: ownProps.onShowSolution,
44 | });
45 |
46 | export default connect(mapStateToProps, mapDispatchToProps)(Footer);
47 |
--------------------------------------------------------------------------------
/src/images/github-icon.svg:
--------------------------------------------------------------------------------
1 |
6 |
--------------------------------------------------------------------------------
/src/components/Button.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react";
2 | const outdent = require("strip-indent");
3 |
4 | import * as styles from "./Button.module.scss";
5 | type StyleKey = keyof typeof styles;
6 |
7 | interface IButtonProps extends React.Props