<commit_before>/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at <http://mozilla.org/MPL/2.0/>. */

// @flow
import PropTypes from "prop-types";
import React, { PureComponent } from "react";
import ReactDOM from "react-dom";
import { connect } from "react-redux";
import classnames from "classnames";
import { debugGlobal } from "devtools-launchpad";
import { isLoaded } from "../../utils/source";
import { isFirefox } from "devtools-config";
import { features } from "../../utils/prefs";

import {
  getActiveSearch,
  getSelectedLocation,
  getSelectedSource,
  getHitCountForSource,
  getCoverageEnabled,
  getConditionalPanelLine,
  getSymbols
} from "../../selectors";

// Redux actions
import { bindActionCreators } from "redux";
import actions from "../../actions";

import Footer from "./Footer";
import SearchBar from "./SearchBar";
import HighlightLines from "./HighlightLines";
import Preview from "./Preview";
import Breakpoints from "./Breakpoints";
import HitMarker from "./HitMarker";
import CallSites from "./CallSites";
import DebugLine from "./DebugLine";
import HighlightLine from "./HighlightLine";
import EmptyLines from "./EmptyLines";
import GutterMenu from "./GutterMenu";
import EditorMenu from "./EditorMenu";
import ConditionalPanel from "./ConditionalPanel";
import type { SymbolDeclarations } from "../../workers/parser/types";

import {
  showSourceText,
  updateDocument,
  showLoading,
  showErrorMessage,
  shouldShowFooter,
  createEditor,
  clearEditor,
  getCursorLine,
  toSourceLine,
  scrollToColumn,
  toEditorPosition,
  getSourceLocationFromMouseEvent
} from "../../utils/editor";

import { resizeToggleButton, resizeBreakpointGutter } from "../../utils/ui";

import "./Editor.css";
import "./Highlight.css";

import type { SourceEditor } from "../../utils/editor/source-editor";

const cssVars = {
  searchbarHeight: "var(--editor-searchbar-height)",
  secondSearchbarHeight: "var(--editor-second-searchbar-height)",
  footerHeight: "var(--editor-footer-height)"
};

export type Props = {
  hitCount: Object,
  selectedLocation: Object,
  selectedSource: Object,
  searchOn: boolean,
  coverageOn: boolean,
  horizontal: boolean,
  startPanelSize: number,
  endPanelSize: number,
  conditionalPanelLine: number,
  symbols: SymbolDeclarations,

  // Actions
  openConditionalPanel: (?number) => void,
  closeConditionalPanel: void => void,
  setContextMenu: (string, any) => void,
  continueToHere: (?number) => void,
  toggleBreakpoint: (?number) => void,
  addOrToggleDisabledBreakpoint: (?number) => void,
  jumpToMappedLocation: any => void,
  traverseResults: (boolean, Object) => void
};

type State = {
  editor: SourceEditor
};

class Editor extends PureComponent<Props, State> {
  $editorWrapper: ?HTMLDivElement;
  constructor(props: Props) {
    super(props);

    this.state = {
      highlightedLineRange: null,
      editor: null
    };
  }

  componentWillReceiveProps(nextProps) {
    if (!this.state.editor) {
      return;
    }

    resizeBreakpointGutter(this.state.editor.codeMirror);
    resizeToggleButton(this.state.editor.codeMirror);
  }

  componentWillUpdate(nextProps) {
    this.setText(nextProps);
    this.setSize(nextProps);
    this.scrollToLocation(nextProps);
  }

  setupEditor() {
    const editor = createEditor();

    // disables the default search shortcuts
    // $FlowIgnore
    editor._initShortcuts = () => {};

    const node = ReactDOM.findDOMNode(this);
    if (node instanceof HTMLElement) {
      editor.appendToLocalElement(node.querySelector(".editor-mount"));
    }

    const { codeMirror } = editor;
    const codeMirrorWrapper = codeMirror.getWrapperElement();

    resizeBreakpointGutter(codeMirror);
    resizeToggleButton(codeMirror);

    debugGlobal("cm", codeMirror);

    codeMirror.on("gutterClick", this.onGutterClick);

    // Set code editor wrapper to be focusable
    codeMirrorWrapper.tabIndex = 0;
    codeMirrorWrapper.addEventListener("keydown", e => this.onKeyDown(e));
    codeMirrorWrapper.addEventListener("click", e => this.onClick(e));

    const toggleFoldMarkerVisibility = e => {
      if (node instanceof HTMLElement) {
        node
          .querySelectorAll(".CodeMirror-guttermarker-subtle")
          .forEach(elem => {
            elem.classList.toggle("visible");
          });
      }
    };

    const codeMirrorGutter = codeMirror.getGutterElement();
    codeMirrorGutter.addEventListener("mouseleave", toggleFoldMarkerVisibility);
    codeMirrorGutter.addEventListener("mouseenter", toggleFoldMarkerVisibility);

    if (!isFirefox()) {
      codeMirror.on("gutterContextMenu", (cm, line, eventName, event) =>
        this.onGutterContextMenu(event)
      );
      codeMirror.on("contextmenu", (cm, event) => this.openMenu(event));
    } else {
      codeMirrorWrapper.addEventListener("contextmenu", event =>
        this.openMenu(event)
      );
    }

    this.setState({ editor });
    return editor;
  }

  componentDidMount() {
    const editor = this.setupEditor();

    const { selectedSource } = this.props;
    const { shortcuts } = this.context;

    const searchAgainKey = L10N.getStr("sourceSearch.search.again.key2");
    const searchAgainPrevKey = L10N.getStr(
      "sourceSearch.search.againPrev.key2"
    );

    shortcuts.on(L10N.getStr("toggleBreakpoint.key"), this.onToggleBreakpoint);
    shortcuts.on(
      L10N.getStr("toggleCondPanel.key"),
      this.toggleConditionalPanel
    );
    shortcuts.on("Esc", this.onEscape);
    shortcuts.on(searchAgainPrevKey, this.onSearchAgain);
    shortcuts.on(searchAgainKey, this.onSearchAgain);

    updateDocument(editor, selectedSource);
  }

  componentWillUnmount() {
    if (this.state.editor) {
      this.state.editor.destroy();
      this.setState({ editor: null });
    }

    const searchAgainKey = L10N.getStr("sourceSearch.search.again.key2");
    const searchAgainPrevKey = L10N.getStr(
      "sourceSearch.search.againPrev.key2"
    );
    const shortcuts = this.context.shortcuts;
    shortcuts.off(L10N.getStr("toggleBreakpoint.key"));
    shortcuts.off(L10N.getStr("toggleCondPanel.key"));
    shortcuts.off(searchAgainPrevKey);
    shortcuts.off(searchAgainKey);
  }

  componentDidUpdate(prevProps, prevState) {
    // NOTE: when devtools are opened, the editor is not set when
    // the source loads so we need to wait until the editor is
    // set to update the text and size.
    if (!prevState.editor && this.state.editor) {
      this.setText(this.props);
      this.setSize(this.props);
    }
  }

  getCurrentLine() {
    const { codeMirror } = this.state.editor;
    const { selectedSource } = this.props;
    const line = getCursorLine(codeMirror);

    return toSourceLine(selectedSource.get("id"), line);
  }

  onToggleBreakpoint = (key, e) => {
    e.preventDefault();
    const { selectedSource, conditionalPanelLine } = this.props;

    if (!selectedSource) {
      return;
    }

    const line = this.getCurrentLine();

    if (e.shiftKey) {
      this.toggleConditionalPanel(line);
    } else if (!conditionalPanelLine) {
      this.props.toggleBreakpoint(line);
    } else {
      this.toggleConditionalPanel(line);
      this.props.toggleBreakpoint(line);
    }
  };

  onKeyDown(e) {
    const { codeMirror } = this.state.editor;
    const { key, target } = e;
    const codeWrapper = codeMirror.getWrapperElement();
    const textArea = codeWrapper.querySelector("textArea");

    if (key === "Escape" && target == textArea) {
      e.stopPropagation();
      e.preventDefault();
      codeWrapper.focus();
    } else if (key === "Enter" && target == codeWrapper) {
      e.preventDefault();
      // Focus into editor's text area
      textArea.focus();
    }
  }

  /*
   * The default Esc command is overridden in the CodeMirror keymap to allow
   * the Esc keypress event to be catched by the toolbox and trigger the
   * split console. Restore it here, but preventDefault if and only if there
   * is a multiselection.
   */
  onEscape = (key, e) => {
    if (!this.state.editor) {
      return;
    }

    const { codeMirror } = this.state.editor;
    if (codeMirror.listSelections().length > 1) {
      codeMirror.execCommand("singleSelection");
      e.preventDefault();
    }
  };

  onSearchAgain = (_, e) => {
    this.props.traverseResults(e.shiftKey, this.state.editor);
  };

  openMenu(event) {
    event.stopPropagation();
    event.preventDefault();

    const { setContextMenu } = this.props;
    if (event.target.classList.contains("CodeMirror-linenumber")) {
      return setContextMenu("Gutter", event);
    }

    return setContextMenu("Editor", event);
  }

  onGutterClick = (cm, line, gutter, ev) => {
    const {
      selectedSource,
      conditionalPanelLine,
      closeConditionalPanel,
      addOrToggleDisabledBreakpoint,
      toggleBreakpoint,
      continueToHere
    } = this.props;

    // ignore right clicks in the gutter
    if (
      (ev.ctrlKey && ev.button === 0) ||
      ev.which === 3 ||
      (selectedSource && selectedSource.get("isBlackBoxed")) ||
      !selectedSource
    ) {
      return;
    }

    if (conditionalPanelLine) {
      return closeConditionalPanel();
    }

    if (gutter === "CodeMirror-foldgutter") {
      return;
    }

    const sourceLine = toSourceLine(selectedSource.get("id"), line);

    if (ev.altKey) {
      return continueToHere(sourceLine);
    }

    if (ev.shiftKey) {
      return addOrToggleDisabledBreakpoint(sourceLine);
    }

    return toggleBreakpoint(sourceLine);
  };

  onGutterContextMenu = event => {
    event.stopPropagation();
    event.preventDefault();
    return this.props.setContextMenu("Gutter", event);
  };

  onClick(e: MouseEvent) {
    const { selectedLocation, jumpToMappedLocation } = this.props;

    if (e.metaKey && e.altKey) {
      const sourceLocation = getSourceLocationFromMouseEvent(
        this.state.editor,
        selectedLocation,
        e
      );
      jumpToMappedLocation(sourceLocation);
    }
  }

  toggleConditionalPanel = line => {
    const {
      conditionalPanelLine,
      closeConditionalPanel,
      openConditionalPanel
    } = this.props;

    if (!line || isNaN(line)) {
      line = this.getCurrentLine();
    }

    if (conditionalPanelLine) {
      return closeConditionalPanel();
    }

    return openConditionalPanel(line);
  };

  closeConditionalPanel = () => {
    return this.props.closeConditionalPanel();
  };

  shouldScrollToLocation(nextProps) {
    const { selectedLocation, selectedSource } = this.props;
    const { editor } = this.state;

    if (!nextProps.selectedSource || !editor || !nextProps.selectedLocation) {
      return false;
    }

    if (!isLoaded(nextProps.selectedSource)) {
      return false;
    }

    if (!nextProps.selectedLocation.line) {
      return false;
    }

    const isFirstLoad =
      (!selectedSource || !isLoaded(selectedSource)) &&
      isLoaded(nextProps.selectedSource);

    const locationChanged = selectedLocation !== nextProps.selectedLocation;
    return isFirstLoad || locationChanged;
  }

  scrollToLocation(nextProps) {
    const { editor } = this.state;

    if (this.shouldScrollToLocation(nextProps)) {
      const { line, column } = toEditorPosition(nextProps.selectedLocation);
      scrollToColumn(editor.codeMirror, line, column);
    }
  }

  setSize(nextProps) {
    if (!this.state.editor) {
      return;
    }

    if (
      nextProps.startPanelSize !== this.props.startPanelSize ||
      nextProps.endPanelSize !== this.props.endPanelSize
    ) {
      this.state.editor.codeMirror.setSize();
    }
  }

  setText(props) {
    const { selectedSource, symbols } = props;

    if (!this.state.editor) {
      return;
    }

    if (!selectedSource) {
      return this.clearEditor();
    }

    if (!isLoaded(selectedSource)) {
      return showLoading(this.state.editor);
    }

    if (selectedSource.get("error")) {
      return this.showErrorMessage(selectedSource.get("error"));
    }
    if (selectedSource) {
      return showSourceText(this.state.editor, selectedSource.toJS(), symbols);
    }
  }

  clearEditor() {
    const { editor } = this.state;
    if (!editor) {
      return;
    }

    clearEditor(editor);
  }

  showErrorMessage(msg) {
    const { editor } = this.state;
    if (!editor) {
      return;
    }

    showErrorMessage(editor, msg);
  }

  getInlineEditorStyles() {
    const { selectedSource, horizontal, searchOn } = this.props;

    const subtractions = [];

    if (shouldShowFooter(selectedSource, horizontal)) {
      subtractions.push(cssVars.footerHeight);
    }

    if (searchOn) {
      subtractions.push(cssVars.searchbarHeight);
      subtractions.push(cssVars.secondSearchbarHeight);
    }

    return {
      height:
        subtractions.length === 0
          ? "100%"
          : `calc(100% - ${subtractions.join(" - ")})`
    };
  }

  renderHitCounts() {
    const { hitCount, selectedSource } = this.props;

    if (
      !selectedSource ||
      !isLoaded(selectedSource) ||
      !hitCount ||
      !this.state.editor
    ) {
      return;
    }

    return hitCount
      .filter(marker => marker.get("count") > 0)
      .map(marker => (
        <HitMarker
          key={marker.get("line")}
          hitData={marker.toJS()}
          editor={this.state.editor.codeMirror}
        />
      ));
  }

  renderItems() {
    const { horizontal, selectedSource } = this.props;
    const { editor } = this.state;

    if (!editor || !selectedSource) {
      return null;
    }

    return (
      <div>
        <DebugLine />
        <HighlightLine />
        <EmptyLines editor={editor} />
        <Breakpoints editor={editor} />
        <Preview editor={editor} editorRef={this.$editorWrapper} />;
        <Footer editor={editor} horizontal={horizontal} />
        <HighlightLines editor={editor} />
        <EditorMenu editor={editor} />
        <GutterMenu editor={editor} />
        <ConditionalPanel editor={editor} />
        {features.columnBreakpoints ? <CallSites editor={editor} /> : null}
        {this.renderHitCounts()}
      </div>
    );
  }

  renderSearchBar() {
    const { editor } = this.state;

    if (!editor) {
      return null;
    }

    return <SearchBar editor={editor} />;
  }

  render() {
    const { coverageOn } = this.props;

    return (
      <div
        className={classnames("editor-wrapper", {
          "coverage-on": coverageOn
        })}
        ref={c => (this.$editorWrapper = c)}
      >
        {this.renderSearchBar()}
        <div
          className="editor-mount devtools-monospace"
          style={this.getInlineEditorStyles()}
        />
        {this.renderItems()}
      </div>
    );
  }
}

Editor.contextTypes = {
  shortcuts: PropTypes.object
};

const mapStateToProps = state => {
  const selectedSource = getSelectedSource(state);
  const sourceId = selectedSource ? selectedSource.get("id") : "";
  return {
    selectedLocation: getSelectedLocation(state),
    selectedSource,
    searchOn: getActiveSearch(state) === "file",
    hitCount: getHitCountForSource(state, sourceId),
    coverageOn: getCoverageEnabled(state),
    conditionalPanelLine: getConditionalPanelLine(state),
    symbols: getSymbols(state, selectedSource && selectedSource.toJS())
  };
};

export default connect(mapStateToProps, dispatch =>
  bindActionCreators(actions, dispatch)
)(Editor);
<commit_msg>Toggling the conditional bp pane should not open bookmarks (#5299)<commit_after>/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at <http://mozilla.org/MPL/2.0/>. */

// @flow
import PropTypes from "prop-types";
import React, { PureComponent } from "react";
import ReactDOM from "react-dom";
import { connect } from "react-redux";
import classnames from "classnames";
import { debugGlobal } from "devtools-launchpad";
import { isLoaded } from "../../utils/source";
import { isFirefox } from "devtools-config";
import { features } from "../../utils/prefs";

import {
  getActiveSearch,
  getSelectedLocation,
  getSelectedSource,
  getHitCountForSource,
  getCoverageEnabled,
  getConditionalPanelLine,
  getSymbols
} from "../../selectors";

// Redux actions
import { bindActionCreators } from "redux";
import actions from "../../actions";

import Footer from "./Footer";
import SearchBar from "./SearchBar";
import HighlightLines from "./HighlightLines";
import Preview from "./Preview";
import Breakpoints from "./Breakpoints";
import HitMarker from "./HitMarker";
import CallSites from "./CallSites";
import DebugLine from "./DebugLine";
import HighlightLine from "./HighlightLine";
import EmptyLines from "./EmptyLines";
import GutterMenu from "./GutterMenu";
import EditorMenu from "./EditorMenu";
import ConditionalPanel from "./ConditionalPanel";
import type { SymbolDeclarations } from "../../workers/parser/types";

import {
  showSourceText,
  updateDocument,
  showLoading,
  showErrorMessage,
  shouldShowFooter,
  createEditor,
  clearEditor,
  getCursorLine,
  toSourceLine,
  scrollToColumn,
  toEditorPosition,
  getSourceLocationFromMouseEvent
} from "../../utils/editor";

import { resizeToggleButton, resizeBreakpointGutter } from "../../utils/ui";

import "./Editor.css";
import "./Highlight.css";

import type { SourceEditor } from "../../utils/editor/source-editor";

const cssVars = {
  searchbarHeight: "var(--editor-searchbar-height)",
  secondSearchbarHeight: "var(--editor-second-searchbar-height)",
  footerHeight: "var(--editor-footer-height)"
};

export type Props = {
  hitCount: Object,
  selectedLocation: Object,
  selectedSource: Object,
  searchOn: boolean,
  coverageOn: boolean,
  horizontal: boolean,
  startPanelSize: number,
  endPanelSize: number,
  conditionalPanelLine: number,
  symbols: SymbolDeclarations,

  // Actions
  openConditionalPanel: (?number) => void,
  closeConditionalPanel: void => void,
  setContextMenu: (string, any) => void,
  continueToHere: (?number) => void,
  toggleBreakpoint: (?number) => void,
  addOrToggleDisabledBreakpoint: (?number) => void,
  jumpToMappedLocation: any => void,
  traverseResults: (boolean, Object) => void
};

type State = {
  editor: SourceEditor
};

class Editor extends PureComponent<Props, State> {
  $editorWrapper: ?HTMLDivElement;
  constructor(props: Props) {
    super(props);

    this.state = {
      highlightedLineRange: null,
      editor: null
    };
  }

  componentWillReceiveProps(nextProps) {
    if (!this.state.editor) {
      return;
    }

    resizeBreakpointGutter(this.state.editor.codeMirror);
    resizeToggleButton(this.state.editor.codeMirror);
  }

  componentWillUpdate(nextProps) {
    this.setText(nextProps);
    this.setSize(nextProps);
    this.scrollToLocation(nextProps);
  }

  setupEditor() {
    const editor = createEditor();

    // disables the default search shortcuts
    // $FlowIgnore
    editor._initShortcuts = () => {};

    const node = ReactDOM.findDOMNode(this);
    if (node instanceof HTMLElement) {
      editor.appendToLocalElement(node.querySelector(".editor-mount"));
    }

    const { codeMirror } = editor;
    const codeMirrorWrapper = codeMirror.getWrapperElement();

    resizeBreakpointGutter(codeMirror);
    resizeToggleButton(codeMirror);

    debugGlobal("cm", codeMirror);

    codeMirror.on("gutterClick", this.onGutterClick);

    // Set code editor wrapper to be focusable
    codeMirrorWrapper.tabIndex = 0;
    codeMirrorWrapper.addEventListener("keydown", e => this.onKeyDown(e));
    codeMirrorWrapper.addEventListener("click", e => this.onClick(e));

    const toggleFoldMarkerVisibility = e => {
      if (node instanceof HTMLElement) {
        node
          .querySelectorAll(".CodeMirror-guttermarker-subtle")
          .forEach(elem => {
            elem.classList.toggle("visible");
          });
      }
    };

    const codeMirrorGutter = codeMirror.getGutterElement();
    codeMirrorGutter.addEventListener("mouseleave", toggleFoldMarkerVisibility);
    codeMirrorGutter.addEventListener("mouseenter", toggleFoldMarkerVisibility);

    if (!isFirefox()) {
      codeMirror.on("gutterContextMenu", (cm, line, eventName, event) =>
        this.onGutterContextMenu(event)
      );
      codeMirror.on("contextmenu", (cm, event) => this.openMenu(event));
    } else {
      codeMirrorWrapper.addEventListener("contextmenu", event =>
        this.openMenu(event)
      );
    }

    this.setState({ editor });
    return editor;
  }

  componentDidMount() {
    const editor = this.setupEditor();

    const { selectedSource } = this.props;
    const { shortcuts } = this.context;

    const searchAgainKey = L10N.getStr("sourceSearch.search.again.key2");
    const searchAgainPrevKey = L10N.getStr(
      "sourceSearch.search.againPrev.key2"
    );

    shortcuts.on(L10N.getStr("toggleBreakpoint.key"), this.onToggleBreakpoint);
    shortcuts.on(
      L10N.getStr("toggleCondPanel.key"),
      this.onToggleConditionalPanel
    );
    shortcuts.on("Esc", this.onEscape);
    shortcuts.on(searchAgainPrevKey, this.onSearchAgain);
    shortcuts.on(searchAgainKey, this.onSearchAgain);

    updateDocument(editor, selectedSource);
  }

  componentWillUnmount() {
    if (this.state.editor) {
      this.state.editor.destroy();
      this.setState({ editor: null });
    }

    const searchAgainKey = L10N.getStr("sourceSearch.search.again.key2");
    const searchAgainPrevKey = L10N.getStr(
      "sourceSearch.search.againPrev.key2"
    );
    const shortcuts = this.context.shortcuts;
    shortcuts.off(L10N.getStr("toggleBreakpoint.key"));
    shortcuts.off(L10N.getStr("toggleCondPanel.key"));
    shortcuts.off(searchAgainPrevKey);
    shortcuts.off(searchAgainKey);
  }

  componentDidUpdate(prevProps, prevState) {
    // NOTE: when devtools are opened, the editor is not set when
    // the source loads so we need to wait until the editor is
    // set to update the text and size.
    if (!prevState.editor && this.state.editor) {
      this.setText(this.props);
      this.setSize(this.props);
    }
  }

  getCurrentLine() {
    const { codeMirror } = this.state.editor;
    const { selectedSource } = this.props;
    const line = getCursorLine(codeMirror);

    return toSourceLine(selectedSource.get("id"), line);
  }

  onToggleBreakpoint = (key, e) => {
    e.preventDefault();
    e.stopPropagation();
    const { selectedSource, conditionalPanelLine } = this.props;

    if (!selectedSource) {
      return;
    }

    const line = this.getCurrentLine();

    if (e.shiftKey) {
      this.toggleConditionalPanel(line);
    } else if (!conditionalPanelLine) {
      this.props.toggleBreakpoint(line);
    } else {
      this.toggleConditionalPanel(line);
      this.props.toggleBreakpoint(line);
    }
  };

  onToggleConditionalPanel = (key, e) => {
    e.stopPropagation();
    e.preventDefault();
    const line = this.getCurrentLine();
    this.toggleConditionalPanel(line);
  };

  onKeyDown(e) {
    const { codeMirror } = this.state.editor;
    const { key, target } = e;
    const codeWrapper = codeMirror.getWrapperElement();
    const textArea = codeWrapper.querySelector("textArea");

    if (key === "Escape" && target == textArea) {
      e.stopPropagation();
      e.preventDefault();
      codeWrapper.focus();
    } else if (key === "Enter" && target == codeWrapper) {
      e.preventDefault();
      // Focus into editor's text area
      textArea.focus();
    }
  }

  /*
   * The default Esc command is overridden in the CodeMirror keymap to allow
   * the Esc keypress event to be catched by the toolbox and trigger the
   * split console. Restore it here, but preventDefault if and only if there
   * is a multiselection.
   */
  onEscape = (key, e) => {
    if (!this.state.editor) {
      return;
    }

    const { codeMirror } = this.state.editor;
    if (codeMirror.listSelections().length > 1) {
      codeMirror.execCommand("singleSelection");
      e.preventDefault();
    }
  };

  onSearchAgain = (_, e) => {
    this.props.traverseResults(e.shiftKey, this.state.editor);
  };

  openMenu(event) {
    event.stopPropagation();
    event.preventDefault();

    const { setContextMenu } = this.props;
    if (event.target.classList.contains("CodeMirror-linenumber")) {
      return setContextMenu("Gutter", event);
    }

    return setContextMenu("Editor", event);
  }

  onGutterClick = (cm, line, gutter, ev) => {
    const {
      selectedSource,
      conditionalPanelLine,
      closeConditionalPanel,
      addOrToggleDisabledBreakpoint,
      toggleBreakpoint,
      continueToHere
    } = this.props;

    // ignore right clicks in the gutter
    if (
      (ev.ctrlKey && ev.button === 0) ||
      ev.which === 3 ||
      (selectedSource && selectedSource.get("isBlackBoxed")) ||
      !selectedSource
    ) {
      return;
    }

    if (conditionalPanelLine) {
      return closeConditionalPanel();
    }

    if (gutter === "CodeMirror-foldgutter") {
      return;
    }

    const sourceLine = toSourceLine(selectedSource.get("id"), line);

    if (ev.altKey) {
      return continueToHere(sourceLine);
    }

    if (ev.shiftKey) {
      return addOrToggleDisabledBreakpoint(sourceLine);
    }

    return toggleBreakpoint(sourceLine);
  };

  onGutterContextMenu = event => {
    event.stopPropagation();
    event.preventDefault();
    return this.props.setContextMenu("Gutter", event);
  };

  onClick(e: MouseEvent) {
    const { selectedLocation, jumpToMappedLocation } = this.props;

    if (e.metaKey && e.altKey) {
      const sourceLocation = getSourceLocationFromMouseEvent(
        this.state.editor,
        selectedLocation,
        e
      );
      jumpToMappedLocation(sourceLocation);
    }
  }

  toggleConditionalPanel = line => {
    const {
      conditionalPanelLine,
      closeConditionalPanel,
      openConditionalPanel
    } = this.props;

    if (conditionalPanelLine) {
      return closeConditionalPanel();
    }

    return openConditionalPanel(line);
  };

  closeConditionalPanel = () => {
    return this.props.closeConditionalPanel();
  };

  shouldScrollToLocation(nextProps) {
    const { selectedLocation, selectedSource } = this.props;
    const { editor } = this.state;

    if (!nextProps.selectedSource || !editor || !nextProps.selectedLocation) {
      return false;
    }

    if (!isLoaded(nextProps.selectedSource)) {
      return false;
    }

    if (!nextProps.selectedLocation.line) {
      return false;
    }

    const isFirstLoad =
      (!selectedSource || !isLoaded(selectedSource)) &&
      isLoaded(nextProps.selectedSource);

    const locationChanged = selectedLocation !== nextProps.selectedLocation;
    return isFirstLoad || locationChanged;
  }

  scrollToLocation(nextProps) {
    const { editor } = this.state;

    if (this.shouldScrollToLocation(nextProps)) {
      const { line, column } = toEditorPosition(nextProps.selectedLocation);
      scrollToColumn(editor.codeMirror, line, column);
    }
  }

  setSize(nextProps) {
    if (!this.state.editor) {
      return;
    }

    if (
      nextProps.startPanelSize !== this.props.startPanelSize ||
      nextProps.endPanelSize !== this.props.endPanelSize
    ) {
      this.state.editor.codeMirror.setSize();
    }
  }

  setText(props) {
    const { selectedSource, symbols } = props;

    if (!this.state.editor) {
      return;
    }

    if (!selectedSource) {
      return this.clearEditor();
    }

    if (!isLoaded(selectedSource)) {
      return showLoading(this.state.editor);
    }

    if (selectedSource.get("error")) {
      return this.showErrorMessage(selectedSource.get("error"));
    }
    if (selectedSource) {
      return showSourceText(this.state.editor, selectedSource.toJS(), symbols);
    }
  }

  clearEditor() {
    const { editor } = this.state;
    if (!editor) {
      return;
    }

    clearEditor(editor);
  }

  showErrorMessage(msg) {
    const { editor } = this.state;
    if (!editor) {
      return;
    }

    showErrorMessage(editor, msg);
  }

  getInlineEditorStyles() {
    const { selectedSource, horizontal, searchOn } = this.props;

    const subtractions = [];

    if (shouldShowFooter(selectedSource, horizontal)) {
      subtractions.push(cssVars.footerHeight);
    }

    if (searchOn) {
      subtractions.push(cssVars.searchbarHeight);
      subtractions.push(cssVars.secondSearchbarHeight);
    }

    return {
      height:
        subtractions.length === 0
          ? "100%"
          : `calc(100% - ${subtractions.join(" - ")})`
    };
  }

  renderHitCounts() {
    const { hitCount, selectedSource } = this.props;

    if (
      !selectedSource ||
      !isLoaded(selectedSource) ||
      !hitCount ||
      !this.state.editor
    ) {
      return;
    }

    return hitCount
      .filter(marker => marker.get("count") > 0)
      .map(marker => (
        <HitMarker
          key={marker.get("line")}
          hitData={marker.toJS()}
          editor={this.state.editor.codeMirror}
        />
      ));
  }

  renderItems() {
    const { horizontal, selectedSource } = this.props;
    const { editor } = this.state;

    if (!editor || !selectedSource) {
      return null;
    }

    return (
      <div>
        <DebugLine />
        <HighlightLine />
        <EmptyLines editor={editor} />
        <Breakpoints editor={editor} />
        <Preview editor={editor} editorRef={this.$editorWrapper} />;
        <Footer editor={editor} horizontal={horizontal} />
        <HighlightLines editor={editor} />
        <EditorMenu editor={editor} />
        <GutterMenu editor={editor} />
        <ConditionalPanel editor={editor} />
        {features.columnBreakpoints ? <CallSites editor={editor} /> : null}
        {this.renderHitCounts()}
      </div>
    );
  }

  renderSearchBar() {
    const { editor } = this.state;

    if (!editor) {
      return null;
    }

    return <SearchBar editor={editor} />;
  }

  render() {
    const { coverageOn } = this.props;

    return (
      <div
        className={classnames("editor-wrapper", {
          "coverage-on": coverageOn
        })}
        ref={c => (this.$editorWrapper = c)}
      >
        {this.renderSearchBar()}
        <div
          className="editor-mount devtools-monospace"
          style={this.getInlineEditorStyles()}
        />
        {this.renderItems()}
      </div>
    );
  }
}

Editor.contextTypes = {
  shortcuts: PropTypes.object
};

const mapStateToProps = state => {
  const selectedSource = getSelectedSource(state);
  const sourceId = selectedSource ? selectedSource.get("id") : "";
  return {
    selectedLocation: getSelectedLocation(state),
    selectedSource,
    searchOn: getActiveSearch(state) === "file",
    hitCount: getHitCountForSource(state, sourceId),
    coverageOn: getCoverageEnabled(state),
    conditionalPanelLine: getConditionalPanelLine(state),
    symbols: getSymbols(state, selectedSource && selectedSource.toJS())
  };
};

export default connect(mapStateToProps, dispatch =>
  bindActionCreators(actions, dispatch)
)(Editor);
<|endoftext|>


<commit_before>import { h, Component } from 'preact';
import ReactDOM from 'react-dom';

import Warning from './Warning.js';
import * as Util from './util.js';
import WARNING_MESSAGES from './WarningMessages.json';
import domRegexpMatch from 'dom-regexp-match';

export const WAIT_TIME_BEFORE_RECALC_WARNINGS = 500;

class JustNotSorry extends Component {
  constructor(props) {
    super(props);

    this.state = {
      editableDivCount: 0,
      warnings: [],
    };

    this.documentObserver = new MutationObserver(
      this.handleContentEditableDivChange.bind(this)
    );
    this.observer = new MutationObserver(
      this.handleContentEditableContentInsert.bind(this)
    );
    this.initializeObserver();
  }

  initializeObserver() {
    this.documentObserver.observe(document, { subtree: true, childList: true });
  }

  handleContentEditableContentInsert(mutations) {
    if (mutations[0]) {
      mutations.forEach((mutation) => {
        if (
          mutation.type !== 'characterData' &&
          mutation.target.hasAttribute('contentEditable')
        ) {
          let id = mutation.target.id;
          if (id) {
            let targetDiv = document.getElementById(id);
            // generate input event to fire checkForWarnings again
            let inputEvent = new Event('input', {
              bubbles: true,
              cancelable: true,
            });
            targetDiv.dispatchEvent(inputEvent);
          }
        }
      });
    }
  }

  handleContentEditableDivChange(mutations) {
    let divCount = this.getEditableDivs().length;
    if (divCount !== this.state.editableDivCount) {
      this.setState({ editableDivCount: divCount });
      if (mutations[0]) {
        mutations.forEach((mutation) => {
          if (
            mutation.type === 'childList' &&
            mutation.target.hasAttribute('contentEditable')
          ) {
            let id = mutation.target.id;
            if (id) {
              this.applyEventListeners(id);
            }
          }
        });
      }
    }
  }

  checkForWarnings(parentElement) {
    return Util.debounce(
      () => this.checkForWarningsImpl(parentElement),
      WAIT_TIME_BEFORE_RECALC_WARNINGS
    );
  }

  checkForWarningsImpl(parentElement) {
    this.setState({ warnings: [] });
    this.addWarnings(parentElement);
  }

  applyEventListeners(id) {
    let targetDiv = document.getElementById(id);
    targetDiv.removeEventListener('focus', this.addObserver);
    targetDiv.addEventListener('focus', this.addObserver.bind(this));
    targetDiv.addEventListener('blur', this.removeObserver.bind(this));
  }

  addObserver(event) {
    const element = event.currentTarget;
    element.addEventListener(
      'input',
      this.checkForWarnings(element.parentNode)
    );
    this.setState({ warnings: [] });
    this.addWarnings(element.parentNode);
    this.observer.observe(element, {
      characterData: false,
      subtree: true,
      childList: true,
      attributes: false,
    });
  }

  removeObserver(event) {
    const element = event.currentTarget;
    this.setState({ warnings: [] });
    element.removeEventListener('input', this.checkForWarnings);
    this.observer.disconnect();
  }

  getEditableDivs() {
    return document.querySelectorAll('div[contentEditable=true]');
  }

  addWarning(node, keyword, message) {
    const pattern = new RegExp('\\b(' + keyword + ')\\b', 'ig');
    domRegexpMatch(node, pattern, (match, range) => {
      let newWarning = {
        keyword: keyword,
        message: message,
        parentNode: node,
        rangeToHighlight: range,
      };

      if (match.index < 2000) {
        this.setState((prevState) => ({
          warnings: [...prevState.warnings, newWarning],
        }));
      }
    });
  }

  addWarnings(node) {
    WARNING_MESSAGES.map((warning) => {
      this.addWarning(node, warning.keyword, warning.message);
    });
  }

  render() {
    const warningList = this.state.warnings.map((warning) =>
      ReactDOM.createPortal(
        <Warning class=".jns-warning" key={warning.keyword} value={warning} />,
        warning.parentNode
      )
    );

    return
{warningList}
;
  }
}

export default JustNotSorry;
<commit_msg>fix: improvement on tooltip highlight fix<commit_after>import { h, Component } from 'preact';
import ReactDOM from 'react-dom';

import Warning from './Warning.js';
import * as Util from './util.js';
import WARNING_MESSAGES from './WarningMessages.json';
import domRegexpMatch from 'dom-regexp-match';

export const WAIT_TIME_BEFORE_RECALC_WARNINGS = 500;

class JustNotSorry extends Component {
  constructor(props) {
    super(props);

    this.state = {
      editableDivCount: 0,
      warnings: [],
    };

    this.documentObserver = new MutationObserver(
      this.handleContentEditableDivChange.bind(this)
    );
    this.observer = new MutationObserver(
      this.handleContentEditableContentInsert.bind(this)
    );
    this.initializeObserver();
  }

  initializeObserver() {
    this.documentObserver.observe(document, { subtree: true, childList: true });
  }

  handleContentEditableContentInsert(mutations) {
    if (mutations[0]) {
      mutations.forEach((mutation) => {
        if (
          mutation.type !== 'characterData' &&
          mutation.target.hasAttribute('contentEditable')
        ) {
          let id = mutation.target.id;
          if (id) {
            let targetDiv = document.getElementById(id);
            // generate input event to fire checkForWarnings again
            let inputEvent = new Event('input', {
              bubbles: true,
              cancelable: true,
            });
            targetDiv.dispatchEvent(inputEvent);
          }
        }
      });
    }
  }

  handleContentEditableDivChange(mutations) {
    let divCount = this.getEditableDivs().length;
    if (divCount !== this.state.editableDivCount) {
      this.setState({ editableDivCount: divCount });
      if (mutations[0]) {
        mutations.forEach((mutation) => {
          if (
            mutation.type === 'childList' &&
            mutation.target.hasAttribute('contentEditable')
          ) {
            let id = mutation.target.id;
            if (id) {
              this.applyEventListeners(id);
            }
          }
        });
      }
    }
  }

  checkForWarnings(parentElement) {
    return Util.debounce(
      () => this.checkForWarningsImpl(parentElement),
      WAIT_TIME_BEFORE_RECALC_WARNINGS
    );
  }

  checkForWarningsImpl(parentElement) {
    this.setState({ warnings: [] });
    this.addWarnings(parentElement);
  }

  applyEventListeners(id) {
    let targetDiv = document.getElementById(id);
    targetDiv.removeEventListener('focus', this.addObserver);
    targetDiv.addEventListener('focus', this.addObserver.bind(this));
    targetDiv.addEventListener('blur', this.removeObserver.bind(this));
  }

  addObserver(event) {
    const element = event.currentTarget;
    element.addEventListener(
      'input',
      this.checkForWarnings(element.parentNode)
    );
    this.setState({ warnings: [] });
    this.addWarnings(element.parentNode);
    this.observer.observe(element, {
      characterData: false,
      subtree: true,
      childList: true,
      attributes: false,
    });
  }

  removeObserver(event) {
    const element = event.currentTarget;
    this.setState({ warnings: [] });
    element.removeEventListener('input', this.checkForWarnings);
    this.observer.disconnect();
  }

  getEditableDivs() {
    return document.querySelectorAll('div[contentEditable=true]');
  }

  addWarning(node, keyword, message) {
    const pattern = new RegExp('\\b(' + keyword + ')\\b', 'ig');
    domRegexpMatch(node, pattern, (match, range) => {
      let newWarning = {
        keyword: keyword,
        message: message,
        parentNode: node,
        rangeToHighlight: range,
      };

      if (!match.input.includes(`color: #fff;`)) {
        this.setState((prevState) => ({
          warnings: [...prevState.warnings, newWarning],
        }));
      }
    });
  }

  addWarnings(node) {
    WARNING_MESSAGES.map((warning) => {
      this.addWarning(node, warning.keyword, warning.message);
    });
  }

  render() {
    const warningList = this.state.warnings.map((warning) =>
      ReactDOM.createPortal(
        <Warning class=".jns-warning" key={warning.keyword} value={warning} />,
        warning.parentNode
      )
    );

    return
{warningList}
;
  }
}

export default JustNotSorry;
<|endoftext|>


<commit_before>import React from 'react';
import PureRenderMixin from 'react-addons-pure-render-mixin';
import PlayerHeader from './components/Header.react';
import PlayerControls from './components/Controls';
import PlayerRender from './components/Renderer.react';
import Playlist from './components/Playlist.react';
import Settings from './components/MenuHolders/Settings';
import SubtitleList from './components/Subtitles.react';
import SubtitleText from './components/SubtitleText';
import Announcement from './components/Announcement.react';

import {
    webFrame
} from 'electron';
import remote from 'remote';
import ls from 'local-storage';
import player from './utils/player';
import cacheUtil from './utils/cache';
import hotkeys from './utils/hotkeys';
import contextMenu from './utils/contextMenu';

import ControlStore from './components/Controls/store';
import VisibilityStore from './components/Visibility/store';
import VisibilityActions from './components/Visibility/actions';

import ReactNotify from 'react-notify';

import {mouseTrap} from 'react-mousetrap';

const Player = React.createClass({
    mixins: [PureRenderMixin],

    getInitialState() {
        var visibilityState = VisibilityStore.getState();
        return {
            uiShown: visibilityState.uiShown
        }
    },
    componentWillMount() {
        if (!ls.isSet('customSubSize'))
            ls('customSubSize', 100);
        VisibilityStore.listen(this.update);
        remote.getCurrentWindow().setMinimumSize(392, 228);
        webFrame.setZoomLevel(ls.isSet('zoomLevel') ? ls('zoomLevel') : 0);
        hotkeys.attach(this.props);
    },
    componentWillUnmount() {
        VisibilityStore.unlisten(this.update);
        hotkeys.detach(this.props);
        cacheUtil.stop();
        window.removeListener('contextmenu', contextMenu.listen);
    },
    componentDidMount() {
        var announcer = document.getElementsByClassName('wcjs-announce')[0];
//        if (['', '0'].indexOf(announcer.style.opacity) > -1) {
//            events.buffering(0);
//        }
        player.set({
            notifier: this.refs.notificator
        });
        cacheUtil.start(player);
        player.loadState();
        window.addEventListener('contextmenu', contextMenu.listen, false);
    },
    update() {
//        console.log('player update');
        if (this.isMounted()) {
            var visibilityState = VisibilityStore.getState();
            this.setState({
                uiShown: visibilityState.uiShown
            });
        }
    },
    hideUI() {
        if (!ControlStore.getState().scrobbling)
            VisibilityActions.uiShown(false);
        else
            player.hoverTimeout = setTimeout(this.hideUI, 3000);
    },
    hover(event) {
        player.hoverTimeout && clearTimeout(player.hoverTimeout);
        this.state.uiShown || VisibilityActions.uiShown(true);
        player.hoverTimeout = setTimeout(this.hideUI, 3000);
    },
    render() {
        var cursorStyle = {
            cursor: this.state.uiShown ? 'pointer' : 'none'
        };
        return (

                <PlayerHeader />
                <PlayerRender />
                <Announcement />
                <SubtitleText />
                <PlayerControls />
                <Playlist />
                <Settings />
                <SubtitleList />
                <ReactNotify ref='notificator'/>

        );
    }
});

export
default mouseTrap(Player)
<commit_msg>Quick Fix<commit_after>import React from 'react';
import PureRenderMixin from 'react-addons-pure-render-mixin';
import PlayerHeader from './components/Header.react';
import PlayerControls from './components/Controls';
import PlayerRender from './components/Renderer.react';
import Playlist from './components/Playlist.react';
import Settings from './components/MenuHolders/Settings';
import SubtitleList from './components/Subtitles.react';
import SubtitleText from './components/SubtitleText';
import Announcement from './components/Announcement.react';

import {
    webFrame
} from 'electron';
import remote from 'remote';
import ls from 'local-storage';
import player from './utils/player';
import cacheUtil from './utils/cache';
import hotkeys from './utils/hotkeys';
import contextMenu from './utils/contextMenu';

import ControlStore from './components/Controls/store';
import VisibilityStore from './components/Visibility/store';
import VisibilityActions from './components/Visibility/actions';

import ReactNotify from 'react-notify';

import {mouseTrap} from 'react-mousetrap';

const Player = React.createClass({
    mixins: [PureRenderMixin],

    getInitialState() {
        var visibilityState = VisibilityStore.getState();
        return {
            uiShown: visibilityState.uiShown
        }
    },
    componentWillMount() {
        if (!ls.isSet('customSubSize'))
            ls('customSubSize', 100);
        VisibilityStore.listen(this.update);
        remote.getCurrentWindow().setMinimumSize(392, 228);
        webFrame.setZoomLevel(ls.isSet('zoomLevel') ? ls('zoomLevel') : 0);
        hotkeys.attach(this.props);
    },
    componentWillUnmount() {
        VisibilityStore.unlisten(this.update);
        hotkeys.detach(this.props);
        cacheUtil.stop();
        window.removeEventListener('contextmenu', contextMenu.listen);
    },
    componentDidMount() {
        var announcer = document.getElementsByClassName('wcjs-announce')[0];
//        if (['', '0'].indexOf(announcer.style.opacity) > -1) {
//            events.buffering(0);
//        }
        player.set({
            notifier: this.refs.notificator
        });
        cacheUtil.start(player);
        player.loadState();
        window.addEventListener('contextmenu', contextMenu.listen, false);
    },
    update() {
//        console.log('player update');
        if (this.isMounted()) {
            var visibilityState = VisibilityStore.getState();
            this.setState({
                uiShown: visibilityState.uiShown
            });
        }
    },
    hideUI() {
        if (!ControlStore.getState().scrobbling)
            VisibilityActions.uiShown(false);
        else
            player.hoverTimeout = setTimeout(this.hideUI, 3000);
    },
    hover(event) {
        player.hoverTimeout && clearTimeout(player.hoverTimeout);
        this.state.uiShown || VisibilityActions.uiShown(true);
        player.hoverTimeout = setTimeout(this.hideUI, 3000);
    },
    render() {
        var cursorStyle = {
            cursor: this.state.uiShown ? 'pointer' : 'none'
        };
        return (

                <PlayerHeader />
                <PlayerRender />
                <Announcement />
                <SubtitleText />
                <PlayerControls />
                <Playlist />
                <Settings />
                <SubtitleList />
                <ReactNotify ref='notificator'/>

        );
    }
});

export
default mouseTrap(Player)
<|endoftext|>


<commit_before>import React from 'react';
import { Redirect } from 'react-router';
import { Link } from 'react-router-dom';
import { trackPage } from '../classes/googleanalytics';

export default class LandingPage extends React.Component {
  constructor(props) {
    super(props);
    trackPage('/', 'nl');
  }

  render() {
    if (window.location.hash) {
      if (window.location.hash.length > 4) {
        return <Redirect to={ '/nl' + window.location.hash.substr(1) }/>
      } else {
        return <Redirect to={ '/' }/>
      }
    }

    return(

Smart life met CT-3000

               CodeUur

Programmeer je Smart Life

Wil je iets bedenken voor een Smart Life of een Smart Home? Wil jij bijvoorbeeld dat de muziek aan gaat als de zon schijnt? Of moet de radio je wekken als het 7.00 in de ochtend is, en weer zelf uit gaan als je om 8.15 naar school gaat? Of wil je een sms ontvangen als iemand je voordeur open doet?

Dan is het handig als je kunt programmeren! Met de programmeer-tool CT3000 oefen je een aantal belangrijke programmeer-principes, zoals ‘IF-THEN’. Dus, bedenk iets slims en programmeer het zelf!

              <Link to={'/nl/tool'} className="fat-link">Aan de slag met CT3000</Link>

Het thema Smart Life

De programmeer-tool CT3000 hoort bij het thema Smart Life, over computional thinking en programmeren.
                Het thema is ontwikkeld door uitgeverij Blink
                , stichting CodeUur en Q42.

Het thema Smart Life omvat vijf lessen en is ontwikkeld voor groep 5/6 en
                7/8. Vanuit de lessen wordt verwezen naar de tool. De lessen en de tool
                zijn gratis beschikbaar voor alle scholen.

De lessen zijn gratis beschikbaar via onderstaande de knop. Het thema is
                ook toegankelijk in de lesomgeving van Blink: Blink Studio.

              Naar de lessen

    );
  }
};
<commit_msg>ct-3000 ipv ct3000<commit_after>import React from 'react';
import { Redirect } from 'react-router';
import { Link } from 'react-router-dom';
import { trackPage } from '../classes/googleanalytics';

export default class LandingPage extends React.Component {
  constructor(props) {
    super(props);
    trackPage('/', 'nl');
  }

  render() {
    if (window.location.hash) {
      if (window.location.hash.length > 4) {
        return <Redirect to={ '/nl' + window.location.hash.substr(1) }/>
      } else {
        return <Redirect to={ '/' }/>
      }
    }

    return(

Smart life met CT-3000

               CodeUur

Programmeer je Smart Life

Wil je iets bedenken voor een Smart Life of een Smart Home? Wil jij bijvoorbeeld dat de muziek aan gaat als de zon schijnt? Of moet de radio je wekken als het 7.00 in de ochtend is, en weer zelf uit gaan als je om 8.15 naar school gaat? Of wil je een sms ontvangen als iemand je voordeur open doet?

Dan is het handig als je kunt programmeren! Met de programmeer-tool CT-3000 oefen je een aantal belangrijke programmeer-principes, zoals ‘IF-THEN’. Dus, bedenk iets slims en programmeer het zelf!

              <Link to={'/nl/tool'} className="fat-link">Aan de slag met CT-3000</Link>

Het thema Smart Life

De programmeer-tool CT-3000 hoort bij het thema Smart Life, over computional thinking en programmeren.
                Het thema is ontwikkeld door uitgeverij Blink
                , stichting CodeUur en Q42.

Het thema Smart Life omvat vijf lessen en is ontwikkeld voor groep 5/6 en
                7/8. Vanuit de lessen wordt verwezen naar de tool. De lessen en de tool
                zijn gratis beschikbaar voor alle scholen.

De lessen zijn gratis beschikbaar via onderstaande de knop. Het thema is
                ook toegankelijk in de lesomgeving van Blink: Blink Studio.

              Naar de lessen

    );
  }
};
<|endoftext|>


<commit_before>import { noView, inject, customElement } from 'aurelia-framework';
import React from 'react';
import ReactDOM from 'react-dom';
import ReactPlayer from 'react-player';

@noView()
@inject(Element)
@customElement('music-player')
export class MusicPlayer {
  constructor(element) {
    this.element = element;
    this.urls = [['DG.mp3', 'Don\'t Go / Web Jam Band'], ['MRM.mp3', 'Misty Rainy Moring / Web Jam Band'], ['AT.mp3', 'Alone Time / Web Jam Band'],
      ['TTGA.mp3', 'Try to Get Along / Web Jam Band'], ['https://soundcloud.com/joshandmariamusic/good-enough', 'Good Enough / Web Jam Enough'],
      ['https://www.youtube.com/embed/ach2ubW21h4', ''], ['https://www.youtube.com/embed/mCvUBjuzfo8', '']];

    this._urls = [['DG.mp3', 'Don\'t Go / Web Jam Band'], ['MRM.mp3', 'Misty Rainy Moring / Web Jam Band'], ['AT.mp3', 'Alone Time / Web Jam Band'],
      ['TTGA.mp3', 'Try to Get Along / Web Jam Band'], ['https://soundcloud.com/joshandmariamusic/good-enough', 'Good Enough / Web Jam Enough'],
      ['https://www.youtube.com/embed/ach2ubW21h4', ''], ['https://www.youtube.com/embed/mCvUBjuzfo8', '']];

    this.play = this.play.bind(this);
    this.index = 0;
    this.url = this.urls[this.index];
    this.playing = false;
    this.playEnd = this.playEnd.bind(this);
    this.pause = this.pause.bind(this);
    this.shuffle = this.shuffle.bind(this);
    this.share = this.share.bind(this);
    this.copyShare = this.copyShare.bind(this);
    this.next = this.next.bind(this);
    this.prev = this.prev.bind(this);
    this.playUrl = `${document.location.href}?oneplayer=true`;
    this.shown = false;
    this.navigator = navigator;
    this.isShuffleOn = false;
  }

  get html() {
    return (

            <ReactPlayer
              style={{ backgroundColor: '#eee', textAlign: 'center' }}
              url={this.url[0]}
              playing={this.playing}
              controls
              onEnded={this.playEnd}
              width="100%"
              id="mainPlayer"
              config={{ file: { attributes: { controlsList: 'nodownload' } } }}
            />

              <input id="copyUrl" disabled value={this.playUrl} style={{ backgroundColor: '#fff' }} className="form-control" />

 {}} role="presentation" onClick={this.copyShare} style={{ cursor: 'pointer' }}>
                Copy URL

            Url copied Url to clipboard

{this.url[1]}

            <button role="menu" onClick={this.play}>Play/Pause</button>
            <button role="menu" onClick={this.next}>Next</button>

            <button role="menu" onClick={this.prev}>Prev</button>
            <button id="shuffle" role="menu" onClick={this.shuffle}>Shuffle</button>
            <button role="menu" onClick={this.share}>Share</button>

    );
  }

  /**
   * Shuffles array in place. ES6 version
   */
  shuffle() {
    if (this.isShuffleOn) {
      this.urls = this._urls;
      this.isShuffleOn = false;
      document.getElementById('shuffle').classList.remove('on');
    } else {
      this.urls = this.urls.slice();
      for (let i = this.urls.length - 1; i > 0; i -= 1) {
        const j = Math.floor(Math.random() * (i + 1));
        [this.urls[i], this.urls[j]] = [this.urls[j], this.urls[i]];
      }
      document.getElementById('shuffle').classList.add('on');
      this.isShuffleOn = true;
    }
    this.index = 0;
    this.url = this.urls[this.index];
    this.bind();
  }

  playEnd() {
    this.next();
  }

  playTrue() {
    this.bind();
  }

  prev() {
    this.index -= 1;
    if (this.index < 0) {
      this.index = this.urls.length - 1;
      this.url = this.urls[this.index];
    } else {
      this.url = this.urls[this.index];
    }
    this.playTrue();
    this.playSound();
  }

  play() {
    this.playing = !this.playing;
    this.bind();
  }

  pause() {
    this.playing = false;
    this.bind();
  }

  playSound() {
    if (this.url[0] === 'https://soundcloud.com/joshandmariamusic/good-enough') {
      setTimeout(() => { this.playing = true; this.playTrue(); }, 5000);
    }
  }

  next() {
    this.index += 1;
    if (this.index >= this.urls.length) {
      this.index = 0;
      this.url = this.urls[this.index];
    } else {
      this.url = this.urls[this.index];
    }
    this.playTrue();
    this.playSound();
  }

  render() {
    ReactDOM.render(this.html, this.element);
  }

  share() {
    const el = document.getElementById('copier');
    if (this.shown) {
      el.classList.add('d-none');
      this.shown = false;
    } else {
      el.classList.remove('d-none');
      this.shown = true;
    }
  }

  copyShare() {
    this.navigator.clipboard.writeText(this.playUrl).then(() => {
      this.share();

      const el = document.getElementById('copyMessage');

      el.classList.remove('d-none');

      setTimeout(() => {
        el.classList.add('d-none');
      }, 1500);
    }, () => {});
  }

  bind() {
    this.render();
  }
}
<commit_msg>music player refactored<commit_after>import { noView, inject, customElement } from 'aurelia-framework';
import React from 'react';
import ReactDOM from 'react-dom';
import ReactPlayer from 'react-player';

@noView()
@inject(Element)
@customElement('music-player')
export class MusicPlayer {
  constructor(element) {
    this.element = element;
    this.urls = [['DG.mp3', 'Don\'t Go / Web Jam Band'], ['MRM.mp3', 'Misty Rainy Moring / Web Jam Band'], ['AT.mp3', 'Alone Time / Web Jam Band'],
      ['TTGA.mp3', 'Try to Get Along / Web Jam Band'], ['https://soundcloud.com/joshandmariamusic/good-enough', 'Good Enough / Web Jam Band'],
      ['https://www.youtube.com/embed/ach2ubW21h4', 'Boogie Board Rash / Web Jam Band'], ['https://www.youtube.com/embed/mCvUBjuzfo8', 'Hey Red / Web Jam Band']];

    this._urls = [['DG.mp3', 'Don\'t Go / Web Jam Band'], ['MRM.mp3', 'Misty Rainy Moring / Web Jam Band'], ['AT.mp3', 'Alone Time / Web Jam Band'],
      ['TTGA.mp3', 'Try to Get Along / Web Jam Band'], ['https://soundcloud.com/joshandmariamusic/good-enough', 'Good Enough / Web Jam Enough'],
      ['https://www.youtube.com/embed/ach2ubW21h4', ''], ['https://www.youtube.com/embed/mCvUBjuzfo8', '']];

    this.play = this.play.bind(this);
    this.index = 0;
    this.url = this.urls[this.index];
    this.playing = false;
    this.playEnd = this.playEnd.bind(this);
    this.pause = this.pause.bind(this);
    this.shuffle = this.shuffle.bind(this);
    this.share = this.share.bind(this);
    this.copyShare = this.copyShare.bind(this);
    this.next = this.next.bind(this);
    this.prev = this.prev.bind(this);
    this.playUrl = `${document.location.href}?oneplayer=true`;
    this.shown = false;
    this.navigator = navigator;
    this.isShuffleOn = false;
  }

  get html() {
    return (

            <ReactPlayer
              style={{ backgroundColor: '#eee', textAlign: 'center' }}
              url={this.url[0]}
              playing={this.playing}
              controls
              onEnded={this.playEnd}
              width="100%"
              id="mainPlayer"
              config={{ file: { attributes: { controlsList: 'nodownload' } } }}
            />

              <input id="copyUrl" disabled value={this.playUrl} style={{ backgroundColor: '#fff' }} className="form-control" />

 {}} role="presentation" onClick={this.copyShare} style={{ cursor: 'pointer' }}>
                Copy URL

            Url copied Url to clipboard

{this.url[1]}

            <button role="menu" onClick={this.play}>Play/Pause</button>
            <button role="menu" onClick={this.next}>Next</button>

            <button role="menu" onClick={this.prev}>Prev</button>
            <button id="shuffle" role="menu" onClick={this.shuffle}>Shuffle</button>
            <button role="menu" onClick={this.share}>Share</button>

    );
  }

  /**
   * Shuffles array in place. ES6 version
   */
  shuffle() {
    if (this.isShuffleOn) {
      this.urls = this._urls;
      this.isShuffleOn = false;
      document.getElementById('shuffle').classList.remove('on');
    } else {
      this.urls = this.urls.slice();
      for (let i = this.urls.length - 1; i > 0; i -= 1) {
        const j = Math.floor(Math.random() * (i + 1));
        [this.urls[i], this.urls[j]] = [this.urls[j], this.urls[i]];
      }
      document.getElementById('shuffle').classList.add('on');
      this.isShuffleOn = true;
    }
    this.index = 0;
    this.url = this.urls[this.index];
    this.bind();
  }

  playEnd() {
    this.next();
  }

  playTrue() {
    this.bind();
  }

  prev() {
    this.index -= 1;
    if (this.index < 0) {
      this.index = this.urls.length - 1;
      this.url = this.urls[this.index];
    } else {
      this.url = this.urls[this.index];
    }
    this.playTrue();
    this.playSound();
  }

  play() {
    this.playing = !this.playing;
    this.bind();
  }

  pause() {
    this.playing = false;
    this.bind();
  }

  playSound() {
    if (this.url[0] === 'https://soundcloud.com/joshandmariamusic/good-enough') {
      setTimeout(() => { this.playing = true; this.playTrue(); }, 5000);
    }
  }

  next() {
    this.index += 1;
    if (this.index >= this.urls.length) {
      this.index = 0;
      this.url = this.urls[this.index];
    } else {
      this.url = this.urls[this.index];
    }
    this.playTrue();
    this.playSound();
  }

  render() {
    ReactDOM.render(this.html, this.element);
  }

  share() {
    const el = document.getElementById('copier');
    if (this.shown) {
      el.classList.add('d-none');
      this.shown = false;
    } else {
      el.classList.remove('d-none');
      this.shown = true;
    }
  }

  copyShare() {
    this.navigator.clipboard.writeText(this.playUrl).then(() => {
      this.share();

      const el = document.getElementById('copyMessage');

      el.classList.remove('d-none');

      setTimeout(() => {
        el.classList.add('d-none');
      }, 1500);
    }, () => {});
  }

  bind() {
    this.render();
  }
}
<|endoftext|>


<commit_before>import React from 'react';
import TestUtils from 'react/lib/ReactTestUtils';
import Pod from './pod';
import Button from './../button';

describe('Pod', () => {
  let instance;

  describe('componentWillMount', () => {
    describe('when collapsed is passed as a prop', () => {
      it('sets the collapsed state to the passed value', () => {
        instance = TestUtils.renderIntoDocument(<Pod collapsed={true} />);
        expect(instance.state.collapsed).toBeTruthy();
      });
    });

    describe('when collapsed is not passed as a prop', () => {
      it('sets the collapsed state is not defined', () => {
        instance = TestUtils.renderIntoDocument(<Pod/>);
        expect(instance.state.collapsed).toBeUndefined();
      });
    });
  });

  describe('podHeader', () => {
    describe('when title is not passed as a prop', () => {
      it('returns null', () => {
        instance = TestUtils.renderIntoDocument(<Pod/>);
        expect(instance.podHeader).toBeUndefined();
      });
    });

    describe('when title is passed as a prop', () => {
      it('Adds a title to the pod', () => {
        instance = TestUtils.renderIntoDocument(<Pod title='Title'/>);
        let header = TestUtils.findRenderedDOMComponentWithTag(instance, 'h2');
        expect(header.textContent).toEqual('Title');
      });

      describe('when pod is collapsible', () => {
        beforeEach(() => {
          instance = TestUtils.renderIntoDocument(<Pod title='Title' collapsed={true} />);
        });

        it('Adds a additional collaspsible arrow to the the header', () => {
          let arrow = TestUtils.findRenderedDOMComponentWithClass(instance, 'ui-pod__arrow');
          expect(arrow.className).toEqual('ui-icon ui-pod__arrow ui-pod__arrow--true icon-dropdown');
        });

        it('Adds a additonal class header', () => {
          let header = TestUtils.findRenderedDOMComponentWithClass(instance, 'ui-pod__header');
          expect(header.className).toEqual('ui-pod__header unselectable ui-pod__header--true');
        });

        it('Adds a onClick handler to the header', () => {
          spyOn(instance, 'setState');
          let header = TestUtils.findRenderedDOMComponentWithClass(instance, 'ui-pod__header');
          TestUtils.Simulate.click(header);
          expect(instance.setState).toHaveBeenCalledWith({ collapsed: false });
        });
      });

      describe('when pod is NOT collapsible', () => {
        it('does not add additional collapsible arrow', () => {
          instance = TestUtils.renderIntoDocument(<Pod title='Title'/>);
          let arrows = TestUtils.scryRenderedDOMComponentsWithClass(instance, 'ui-pod__arrow');
          expect(arrows.length).toEqual(0);
        });
      });
    });
  });

  describe('podDescription', () => {
    describe('when description is passed as a prop', () => {
      it('renders a description div', () => {
        instance = TestUtils.renderIntoDocument(
          <Pod title='Title' description='This is the pod description'/>
        );
        let description = TestUtils.findRenderedDOMComponentWithClass(instance, 'ui-pod__description');
        expect(description.textContent).toEqual('This is the pod description');
      });
    });

    describe('when description is not passed as a prop', () => {
      it('does not render a description', () => {
        instance = TestUtils.renderIntoDocument(<Pod/>);
        let description = TestUtils.scryRenderedDOMComponentsWithClass(instance, 'ui-pod__description');
        expect(description.length).toEqual(0);
      });
    });
  });

  describe('mainClasses', () => {
    describe('if border is enabled and there is no footer', () => {
      it('renders relevant classes', () => {
        instance = TestUtils.renderIntoDocument(<Pod />);
        expect(instance.mainClasses).toEqual('ui-pod ui-pod--primary');
      });
    });

    describe('if border is disabled and there is a footer', () => {
      it('renders relevant classes', () => {
        instance = TestUtils.renderIntoDocument(<Pod border={ false } footer={<div />} />);
        expect(instance.mainClasses).toEqual('ui-pod ui-pod--primary ui-pod--no-border ui-pod--footer');
      });
    });
  });

  describe('contentClasses', () => {
    describe('if border is enabled and there is no footer', () => {
      it('renders relevant classes', () => {
        instance = TestUtils.renderIntoDocument(<Pod />);
        expect(instance.contentClasses).toEqual('ui-pod__content ui-pod__content--primary ui-pod--padding-medium');
      });
    });

    describe('if border is disabled and there is a footer', () => {
      it('renders relevant classes', () => {
        instance = TestUtils.renderIntoDocument(<Pod border={ false } footer={<div />} />);
        expect(instance.contentClasses).toEqual('ui-pod__content ui-pod__content--primary ui-pod--padding-medium ui-pod__content--footer ui-pod--no-border');
      });
    });
  });

  describe('footerClasses', () => {
    describe('if border is enabled and there is no footer', () => {
      it('renders relevant classes', () => {
        instance = TestUtils.renderIntoDocument(<Pod />);
        expect(instance.footerClasses).toEqual('ui-pod__footer ui-pod__footer--primary ui-pod__footer--padding-medium');
      });
    });

    describe('if border is disabled and there is a footer', () => {
      it('renders relevant classes', () => {
        instance = TestUtils.renderIntoDocument(<Pod border={ false } />);
        expect(instance.footerClasses).toEqual('ui-pod__footer ui-pod__footer--primary ui-pod__footer--padding-medium ui-pod--no-border');
      });
    });
  });

  describe('footer', () => {
    describe('if there is no footer', () => {
      it('returns null', () => {
        instance = TestUtils.renderIntoDocument(<Pod />);
        expect(instance.footer).toBe(null);
      });
    });

    describe('if there is a footer', () => {
      it('returns the footer', () => {
        instance = TestUtils.renderIntoDocument(<Pod footer={ <div /> } />);
        let footer = instance.footer;
        expect(footer.props.className).toEqual(instance.footerClasses);
      });
    });
  });

  describe('edit', () => {
    describe('if no prop is passed', () => {
      it('returns nothing', () => {
        expect(instance.edit).toBe(null);
      });
    });

    describe('if a string is passed', () => {
      it('returns a link with a href prop', () => {
        instance = TestUtils.renderIntoDocument(<Pod onEdit="foo" />);
        expect(instance.edit.props.to).toEqual('foo');
      });
    });

    describe('if a function is passed', () => {
      it('returns a link with an onClick prop', () => {
        let foo = () => {};
        instance = TestUtils.renderIntoDocument(<Pod onEdit={ foo } />);
        expect(instance.edit.props.onClick).toEqual(foo);
      });
    });

    describe('if an object is passed', () => {
      it('returns a link with a object as props', () => {
        let foo = { foo: "foo", bar: "bar" };
        instance = TestUtils.renderIntoDocument(<Pod onEdit={ foo } />);
        expect(instance.edit.props.foo).toEqual("foo");
        expect(instance.edit.props.bar).toEqual("bar");
      });
    });
  });

  describe('render', () => {
    it('applies all props to the pod', () => {
      instance = TestUtils.renderIntoDocument(<Pod foo="bar" />);
      let div = TestUtils.scryRenderedDOMComponentsWithTag(instance, 'div')[0];
      expect(div.props.foo).toEqual("bar");
    });

    describe('pod content', () => {
      describe('when pod is closed', () => {
        it('does not render the pods content', () => {
          instance = TestUtils.renderIntoDocument(
            <Pod collapsed={true} ><Button>Button</Button> </Pod>
          );
          let buttons = TestUtils.scryRenderedDOMComponentsWithTag(instance, 'button')
          expect(buttons.length).toEqual(0);
        });
      });

      describe('when pod is open', () => {
        it('renders the pods content', () => {
          instance = TestUtils.renderIntoDocument(
            <Pod collapsed={false} ><Button>Button</Button> </Pod>
          );
          let buttons = TestUtils.scryRenderedDOMComponentsWithTag(instance, 'button')
          expect(buttons.length).toEqual(1);
        });
      });

      describe('when pod is not collapsible', () => {
        instance = TestUtils.renderIntoDocument(
          <Pod><Button>Button</Button> </Pod>
        );
        it('renders the pods content', () => {
          let buttons = TestUtils.scryRenderedDOMComponentsWithTag(instance, 'button')
          expect(buttons.length).toEqual(1);
        });
      });
    });

    it('renders all children passed to it', () => {
      instance = TestUtils.renderIntoDocument(
        <Pod>
          <Button>Button</Button>
          <Button>Button</Button>
          <Button>Button</Button>
        </Pod>
      );

      let buttons = TestUtils.scryRenderedDOMComponentsWithTag(instance, 'button')
      expect(buttons.length).toEqual(3);
    });
  });
});
<commit_msg>fixed pod spec<commit_after>import React from 'react';
import TestUtils from 'react/lib/ReactTestUtils';
import Pod from './pod';
import Button from './../button';

describe('Pod', () => {
  let instance;

  describe('componentWillMount', () => {
    describe('when collapsed is passed as a prop', () => {
      it('sets the collapsed state to the passed value', () => {
        instance = TestUtils.renderIntoDocument(<Pod collapsed={true} />);
        expect(instance.state.collapsed).toBeTruthy();
      });
    });

    describe('when collapsed is not passed as a prop', () => {
      it('sets the collapsed state is not defined', () => {
        instance = TestUtils.renderIntoDocument(<Pod/>);
        expect(instance.state.collapsed).toBeUndefined();
      });
    });
  });

  describe('podHeader', () => {
    describe('when title is not passed as a prop', () => {
      it('returns null', () => {
        instance = TestUtils.renderIntoDocument(<Pod/>);
        expect(instance.podHeader).toBeUndefined();
      });
    });

    describe('when title is passed as a prop', () => {
      it('Adds a title to the pod', () => {
        instance = TestUtils.renderIntoDocument(<Pod title='Title'/>);
        let header = TestUtils.findRenderedDOMComponentWithTag(instance, 'h4');
        expect(header.textContent).toEqual('Title');
      });

      describe('when pod is collapsible', () => {
        beforeEach(() => {
          instance = TestUtils.renderIntoDocument(<Pod title='Title' collapsed={true} />);
        });

        it('Adds a additional collaspsible arrow to the the header', () => {
          let arrow = TestUtils.findRenderedDOMComponentWithClass(instance, 'ui-pod__arrow');
          expect(arrow.className).toEqual('ui-icon ui-pod__arrow ui-pod__arrow--true icon-dropdown');
        });

        it('Adds a additonal class header', () => {
          let header = TestUtils.findRenderedDOMComponentWithClass(instance, 'ui-pod__header');
          expect(header.className).toEqual('ui-pod__header unselectable ui-pod__header--true');
        });

        it('Adds a onClick handler to the header', () => {
          spyOn(instance, 'setState');
          let header = TestUtils.findRenderedDOMComponentWithClass(instance, 'ui-pod__header');
          TestUtils.Simulate.click(header);
          expect(instance.setState).toHaveBeenCalledWith({ collapsed: false });
        });
      });

      describe('when pod is NOT collapsible', () => {
        it('does not add additional collapsible arrow', () => {
          instance = TestUtils.renderIntoDocument(<Pod title='Title'/>);
          let arrows = TestUtils.scryRenderedDOMComponentsWithClass(instance, 'ui-pod__arrow');
          expect(arrows.length).toEqual(0);
        });
      });
    });
  });

  describe('podDescription', () => {
    describe('when description is passed as a prop', () => {
      it('renders a description div', () => {
        instance = TestUtils.renderIntoDocument(
          <Pod title='Title' description='This is the pod description'/>
        );
        let description = TestUtils.findRenderedDOMComponentWithClass(instance, 'ui-pod__description');
        expect(description.textContent).toEqual('This is the pod description');
      });
    });

    describe('when description is not passed as a prop', () => {
      it('does not render a description', () => {
        instance = TestUtils.renderIntoDocument(<Pod/>);
        let description = TestUtils.scryRenderedDOMComponentsWithClass(instance, 'ui-pod__description');
        expect(description.length).toEqual(0);
      });
    });
  });

  describe('mainClasses', () => {
    describe('if border is enabled and there is no footer', () => {
      it('renders relevant classes', () => {
        instance = TestUtils.renderIntoDocument(<Pod />);
        expect(instance.mainClasses).toEqual('ui-pod ui-pod--primary');
      });
    });

    describe('if border is disabled and there is a footer', () => {
      it('renders relevant classes', () => {
        instance = TestUtils.renderIntoDocument(<Pod border={ false } footer={<div />} />);
        expect(instance.mainClasses).toEqual('ui-pod ui-pod--primary ui-pod--no-border ui-pod--footer');
      });
    });
  });

  describe('contentClasses', () => {
    describe('if border is enabled and there is no footer', () => {
      it('renders relevant classes', () => {
        instance = TestUtils.renderIntoDocument(<Pod />);
        expect(instance.contentClasses).toEqual('ui-pod__content ui-pod__content--primary ui-pod--padding-medium');
      });
    });

    describe('if border is disabled and there is a footer', () => {
      it('renders relevant classes', () => {
        instance = TestUtils.renderIntoDocument(<Pod border={ false } footer={<div />} />);
        expect(instance.contentClasses).toEqual('ui-pod__content ui-pod__content--primary ui-pod--padding-medium ui-pod__content--footer ui-pod--no-border');
      });
    });
  });

  describe('footerClasses', () => {
    describe('if border is enabled and there is no footer', () => {
      it('renders relevant classes', () => {
        instance = TestUtils.renderIntoDocument(<Pod />);
        expect(instance.footerClasses).toEqual('ui-pod__footer ui-pod__footer--primary ui-pod__footer--padding-medium');
      });
    });

    describe('if border is disabled and there is a footer', () => {
      it('renders relevant classes', () => {
        instance = TestUtils.renderIntoDocument(<Pod border={ false } />);
        expect(instance.footerClasses).toEqual('ui-pod__footer ui-pod__footer--primary ui-pod__footer--padding-medium ui-pod--no-border');
      });
    });
  });

  describe('footer', () => {
    describe('if there is no footer', () => {
      it('returns null', () => {
        instance = TestUtils.renderIntoDocument(<Pod />);
        expect(instance.footer).toBe(null);
      });
    });

    describe('if there is a footer', () => {
      it('returns the footer', () => {
        instance = TestUtils.renderIntoDocument(<Pod footer={ <div /> } />);
        let footer = instance.footer;
        expect(footer.props.className).toEqual(instance.footerClasses);
      });
    });
  });

  describe('edit', () => {
    describe('if no prop is passed', () => {
      it('returns nothing', () => {
        expect(instance.edit).toBe(null);
      });
    });

    describe('if a string is passed', () => {
      it('returns a link with a href prop', () => {
        instance = TestUtils.renderIntoDocument(<Pod onEdit="foo" />);
        expect(instance.edit.props.to).toEqual('foo');
      });
    });

    describe('if a function is passed', () => {
      it('returns a link with an onClick prop', () => {
        let foo = () => {};
        instance = TestUtils.renderIntoDocument(<Pod onEdit={ foo } />);
        expect(instance.edit.props.onClick).toEqual(foo);
      });
    });

    describe('if an object is passed', () => {
      it('returns a link with a object as props', () => {
        let foo = { foo: "foo", bar: "bar" };
        instance = TestUtils.renderIntoDocument(<Pod onEdit={ foo } />);
        expect(instance.edit.props.foo).toEqual("foo");
        expect(instance.edit.props.bar).toEqual("bar");
      });
    });
  });

  describe('render', () => {
    it('applies all props to the pod', () => {
      instance = TestUtils.renderIntoDocument(<Pod foo="bar" />);
      let div = TestUtils.scryRenderedDOMComponentsWithTag(instance, 'div')[0];
      expect(div.props.foo).toEqual("bar");
    });

    describe('pod content', () => {
      describe('when pod is closed', () => {
        it('does not render the pods content', () => {
          instance = TestUtils.renderIntoDocument(
            <Pod collapsed={true} ><Button>Button</Button> </Pod>
          );
          let buttons = TestUtils.scryRenderedDOMComponentsWithTag(instance, 'button')
          expect(buttons.length).toEqual(0);
        });
      });

      describe('when pod is open', () => {
        it('renders the pods content', () => {
          instance = TestUtils.renderIntoDocument(
            <Pod collapsed={false} ><Button>Button</Button> </Pod>
          );
          let buttons = TestUtils.scryRenderedDOMComponentsWithTag(instance, 'button')
          expect(buttons.length).toEqual(1);
        });
      });

      describe('when pod is not collapsible', () => {
        instance = TestUtils.renderIntoDocument(
          <Pod><Button>Button</Button> </Pod>
        );
        it('renders the pods content', () => {
          let buttons = TestUtils.scryRenderedDOMComponentsWithTag(instance, 'button')
          expect(buttons.length).toEqual(1);
        });
      });
    });

    it('renders all children passed to it', () => {
      instance = TestUtils.renderIntoDocument(
        <Pod>
          <Button>Button</Button>
          <Button>Button</Button>
          <Button>Button</Button>
        </Pod>
      );

      let buttons = TestUtils.scryRenderedDOMComponentsWithTag(instance, 'button')
      expect(buttons.length).toEqual(3);
    });
  });
});
<|endoftext|>


<commit_before>import React from 'react';
import check from 'check-types';
import classNames from 'classnames';
import { default as omit } from 'lodash.omit';

export const ProgressColors = {
  SECONDARY: 'secondary',
  SUCCESS: 'success',
  WARNING: 'warning',
  ALERT: 'alert'
};

/**
 * Progress component.
 * http://foundation.zurb.com/sites/docs/progress-bar.html
 *
 * @param {Object} props
 * @returns {XML}
 */
export const Progress = props => {
  check.assert.maybe.number(props.min, 'ProgressBar.props.min must be a number.');
  check.assert.maybe.number(props.max, 'ProgressBar.props.max must be a number.');
  check.assert.maybe.number(props.value, 'ProgressBar.props.value must be a number.');
  check.assert.maybe.string(props.color, 'ProgressBar.props.color must be a string.');

  const { meter: meterProps = {} } = props;

  const className = classNames(
    props.className || 'progress',
    props.color
  );

  const omitProps = [
    'min',
    'max',
    'value',
    'valueText',
    'color',
    'meter'
  ];

  if (props.value) {
    meterProps.widthPercent = props.value;
  }

  return (

      {meterProps.text ? <ProgressMeterWithText {...meterProps}/> : <ProgressMeter {...meterProps}/>}

  );
};

/**
 * Progress meter sub-component.
 *
 * @param {Object} props
 * @returns {XML}
 */
export const ProgressMeter = props => (

);

/**
 * Progress meter with text sub-component.
 *
 * @param {Object} props
 * @returns {XML}
 */
export const ProgressMeterWithText = props => (

    <ProgressMeterText>{props.text}</ProgressMeterText>

);

/**
 * Progress meter text sub-component.
 *
 * @param {Object} props
 * @returns {XML}
 */
export const ProgressMeterText = props => (

);

/**
 * Native progress component.
 * http://foundation.zurb.com/sites/docs/progress-bar.html#native-progress
 *
 * @param {Object} props
 * @returns {XML}
 */
export const NativeProgress = props => {
  check.assert.maybe.number(props.max, 'ProgressBar.props.max must be a number.');
  check.assert.maybe.number(props.value, 'ProgressBar.props.value must be a number.');
  check.assert.maybe.string(props.color, 'ProgressBar.props.color must be a string.');

  const className = classNames(props.className, props.color);

  const omitProps = ['color'];

  return (
    <progress {...omit(props, omitProps)} className={className}/>
  );
};

// TODO: Consider adding support for native meter.

export default Progress;
<commit_msg>Fix Progress component validation error messages<commit_after>import React from 'react';
import check from 'check-types';
import classNames from 'classnames';
import { default as omit } from 'lodash.omit';

export const ProgressColors = {
  SECONDARY: 'secondary',
  SUCCESS: 'success',
  WARNING: 'warning',
  ALERT: 'alert'
};

/**
 * Progress component.
 * http://foundation.zurb.com/sites/docs/progress-bar.html
 *
 * @param {Object} props
 * @returns {XML}
 */
export const Progress = props => {
  check.assert.maybe.number(props.min, 'Progress.props.min must be a number.');
  check.assert.maybe.number(props.max, 'Progress.props.max must be a number.');
  check.assert.maybe.number(props.value, 'Progress.props.value must be a number.');
  check.assert.maybe.string(props.color, 'Progress.props.color must be a string.');

  const { meter: meterProps = {} } = props;

  const className = classNames(
    props.className || 'progress',
    props.color
  );

  const omitProps = [
    'min',
    'max',
    'value',
    'valueText',
    'color',
    'meter'
  ];

  if (props.value) {
    meterProps.widthPercent = props.value;
  }

  return (

      {meterProps.text ? <ProgressMeterWithText {...meterProps}/> : <ProgressMeter {...meterProps}/>}

  );
};

/**
 * Progress meter sub-component.
 *
 * @param {Object} props
 * @returns {XML}
 */
export const ProgressMeter = props => (

);

/**
 * Progress meter with text sub-component.
 *
 * @param {Object} props
 * @returns {XML}
 */
export const ProgressMeterWithText = props => (

    <ProgressMeterText>{props.text}</ProgressMeterText>

);

/**
 * Progress meter text sub-component.
 *
 * @param {Object} props
 * @returns {XML}
 */
export const ProgressMeterText = props => (

);

/**
 * Native progress component.
 * http://foundation.zurb.com/sites/docs/progress-bar.html#native-progress
 *
 * @param {Object} props
 * @returns {XML}
 */
export const NativeProgress = props => {
  check.assert.maybe.number(props.max, 'ProgressBar.props.max must be a number.');
  check.assert.maybe.number(props.value, 'ProgressBar.props.value must be a number.');
  check.assert.maybe.string(props.color, 'ProgressBar.props.color must be a string.');

  const className = classNames(props.className, props.color);

  const omitProps = ['color'];

  return (
    <progress {...omit(props, omitProps)} className={className}/>
  );
};

// TODO: Consider adding support for native meter.

export default Progress;
<|endoftext|>


<commit_before>import { isBoolean, isFunction, isString, flow, filter, map } from 'lodash/fp'

export default {
  provide () {
    return {
      setStyle: ::this.setStyle,
      getStyle: ::this.getStyle
    }
  },
  beforeCreate () {
    this.styles = []
  },
  methods: {
    styleTarget () { },
    setStyle (style) {
      this.styles = style
      const styleTarget = this.styleTarget()

      if (styleTarget) {
        if (this.styles && this.styles.length) {
          styleTarget.setStyle(
            isFunction(this.styles)
              ? this.styles
              : createStyleFunc(this.styles, styleTarget)
          )
        } else {
          styleTarget.setStyle(undefined)
        }
        this.refresh()
      }
    },
    getStyle () {
      return this.styles || []
    }
  }
}

export function createStylesFilter (styles) {
  return (featureData, resolution) => flow(
    filter(([ style, condition ]) => {
      if (condition == null) return style

      if (condition != null && isString(condition)) {
        // eslint-disable-next-line no-new-func
        condition = new Function('feature', 'resolution', `return ${condition}`)
      }

      return isBoolean(condition) && condition ||
             isFunction(condition) && condition(featureData, resolution)
    }),
    map(([ style ]) => style)
  )(styles)
}

export function createStyleFunc (styles, styleTarget) {
  const styleTargetStylesFilter = createStylesFilter(styles)

  return function __styleTargetStyleFunc (feature, resolution) {
    console.log(feature)
    // apply feature level styles
    if (feature.$vm && feature.$vm.styles && feature.$vm.styles.length) {
      const featureStylesFilter = createStylesFilter(feature.$vm.styles)

      return featureStylesFilter(feature.$vm.plain(), resolution)
    }

    return styleTargetStylesFilter(
      feature.$vm
        ? feature.$vm.plain()
        : feature.getProperties(),
      resolution
    )
  }
}
<commit_msg>remove log outputs<commit_after>import { isBoolean, isFunction, isString, flow, filter, map } from 'lodash/fp'

export default {
  provide () {
    return {
      setStyle: ::this.setStyle,
      getStyle: ::this.getStyle
    }
  },
  beforeCreate () {
    this.styles = []
  },
  methods: {
    styleTarget () { },
    setStyle (style) {
      this.styles = style
      const styleTarget = this.styleTarget()

      if (styleTarget) {
        if (this.styles && this.styles.length) {
          styleTarget.setStyle(
            isFunction(this.styles)
              ? this.styles
              : createStyleFunc(this.styles, styleTarget)
          )
        } else {
          styleTarget.setStyle(undefined)
        }
        this.refresh()
      }
    },
    getStyle () {
      return this.styles || []
    }
  }
}

export function createStylesFilter (styles) {
  return (featureData, resolution) => flow(
    filter(([ style, condition ]) => {
      if (condition == null) return style

      if (condition != null && isString(condition)) {
        // eslint-disable-next-line no-new-func
        condition = new Function('feature', 'resolution', `return ${condition}`)
      }

      return isBoolean(condition) && condition ||
             isFunction(condition) && condition(featureData, resolution)
    }),
    map(([ style ]) => style)
  )(styles)
}

export function createStyleFunc (styles, styleTarget) {
  const styleTargetStylesFilter = createStylesFilter(styles)

  return function __styleTargetStyleFunc (feature, resolution) {
    // apply feature level styles
    if (feature.$vm && feature.$vm.styles && feature.$vm.styles.length) {
      const featureStylesFilter = createStylesFilter(feature.$vm.styles)

      return featureStylesFilter(feature.$vm.plain(), resolution)
    }

    return styleTargetStylesFilter(
      feature.$vm
        ? feature.$vm.plain()
        : feature.getProperties(),
      resolution
    )
  }
}
<|endoftext|>


<commit_before>import React from 'react'
import 'spectrum-colorpicker'
import 'spectrum-colorpicker/spectrum.css'
import lang from 'i18n/lang'
import './index.less'
import _ from 'lodash'
export default React.createClass({
  componentDidMount: function () {
    let that = this
    $("#sp-styler-modal").draggable({
      handle: ".modal-header"
    })
    $("#colorpicker").spectrum({
      color: "#f00",
      showAlpha: true,
      showInput: true,
    })
    $('#sp-styler-modal').on('hide.bs.modal', function (e) {
      that.props.setTargetStyle(that.props.selectedStyleTarget + 'Style', null)
    })
  },
  getStyle: function () {
    let s
    switch (this.props.selectedStyleTarget) {
      case 'defaultSlide':
        s = this.props.defaultSlideStyle || this.props.deck.defaultSlideStyle
        break
      case 'thisSlide':
        s = this.props.thisSlideStyle || this.props.deck.getActiveSlide().style
        break
      case 'selectedSlides':
        let commonStyle
        this.props.deck.components.forEach((e) => {
          if (!e.selected) {
            return
          }
          let thisSlideStyle = e.style || this.props.deck.defaultSlideStyle
          if (commonStyle === undefined) {
            commonStyle = thisSlideStyle
          }
          else if (commonStyle !== thisSlideStyle) {
            commonStyle = null
          }
        })
        s = this.props.selectedSlidesStyle || commonStyle
        break
      case 'entirePresentation':
        s = this.props.entirePresentationStyle || this.props.deck.style
        break
    }
    return s
  },
  updateStyle: function (newStyleComponent) {
    let s = this.getStyle()
    let targetStyle = _.mergeWith({}, s, newStyleComponent, (ov, sv, k, o, s) => {
      if (sv === undefined) {
        delete o[k]
      }
    })
    this.props.setTargetStyle(this.props.selectedStyleTarget + 'Style', targetStyle)
  },
  render: function () {
    let s, sDisp, attrs = []
    s = this.getStyle()
    sDisp = _.reduce(s, (p, e, k)=> {
      var capitalLtrs = k.match(/([A-Z])/)
      if (capitalLtrs) {
        for (let i = 1; i < capitalLtrs.length; i++) {
          let capitalLtr = capitalLtrs[i]
          let lowerLtr = capitalLtr.toLowerCase()
          k = k.replace(capitalLtr, '-' + lowerLtr)
        }
      }
      attrs.push(k)
      p.push(
{k}: {e}
)
      return p
    }, [])
    return

            <button type="button" className="close" data-dismiss="modal"
                    aria-label="Close">×</button>

{lang.setAppearance} {lang[this.props.selectedStyleTarget]}

                {sDisp}

{lang.background}

{lang.border}

                    <input type="checkbox" onChange={(evt)=> {
                      this.updateStyle({backgroundColor: evt.target.checked ? null : undefined})
                    }}
                           checked={attrs.indexOf('background-color') >= 0}/>color:
                    <input id='colorpicker'/>

                    <input type="checkbox"/>gradient

{lang.border}

Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo
                      consequat.

            <button type="button" className="btn btn-default" data-dismiss="modal">Close</button>
            <button type="button" className="btn btn-primary">Save changes</button>

  }
})
<commit_msg>hooked up color<commit_after>import React from 'react'
import 'spectrum-colorpicker'
import 'spectrum-colorpicker/spectrum.css'
import lang from 'i18n/lang'
import './index.less'
import _ from 'lodash'
export default React.createClass({
  componentDidMount: function () {
    let that = this
    $("#sp-styler-modal").draggable({
      handle: ".modal-header"
    })
    $('#sp-styler-modal').on('hide.bs.modal', function (e) {
      that.props.setTargetStyle(that.props.selectedStyleTarget + 'Style', null)
    })
    $('#sp-styler-modal').on('show.bs.modal', function (e) {
      let s = that.getStyle()
      $("#colorpicker").spectrum({
        color: s.backgroundColor,
        showAlpha: true,
        showInput: true,
        allowEmpty: true,
        change: function (tinycolor) {
          that.updateStyle({backgroundColor: tinycolor && tinycolor.toHexString(true)})
        },
      })
    })
  },
  getStyle: function () {
    let s
    switch (this.props.selectedStyleTarget) {
      case 'defaultSlide':
        s = this.props.defaultSlideStyle || this.props.deck.defaultSlideStyle
        break
      case 'thisSlide':
        s = this.props.thisSlideStyle || this.props.deck.getActiveSlide().style
        break
      case 'selectedSlides':
        let commonStyle
        this.props.deck.components.forEach((e) => {
          if (!e.selected) {
            return
          }
          let thisSlideStyle = e.style || this.props.deck.defaultSlideStyle
          if (commonStyle === undefined) {
            commonStyle = thisSlideStyle
          }
          else if (commonStyle !== thisSlideStyle) {
            commonStyle = null
          }
        })
        s = this.props.selectedSlidesStyle || commonStyle
        break
      case 'entirePresentation':
        s = this.props.entirePresentationStyle || this.props.deck.style
        break
    }
    return s
  },
  updateStyle: function (newStyleComponent) {
    let s = this.getStyle()
    let targetStyle = _.mergeWith({}, s, newStyleComponent, (ov, sv, k, o, s) => {
      if (sv === undefined) {
        delete o[k]
      }
    })
    this.props.setTargetStyle(this.props.selectedStyleTarget + 'Style', targetStyle)
  },
  render: function () {
    let s, sDisp, attrs = []
    s = this.getStyle()
    sDisp = _.reduce(s, (p, e, k)=> {
      var capitalLtrs = k.match(/([A-Z])/)
      if (capitalLtrs) {
        for (let i = 1; i < capitalLtrs.length; i++) {
          let capitalLtr = capitalLtrs[i]
          let lowerLtr = capitalLtr.toLowerCase()
          k = k.replace(capitalLtr, '-' + lowerLtr)
        }
      }
      attrs.push(k)
      p.push(
{k}: {e}
)
      return p
    }, [])
    return

            <button type="button" className="close" data-dismiss="modal"
                    aria-label="Close">×</button>

{lang.setAppearance} {lang[this.props.selectedStyleTarget]}

                {sDisp}

{lang.background}

{lang.border}

                    <input type="checkbox" onChange={(evt)=> {
                      this.updateStyle({backgroundColor: evt.target.checked ? null : undefined})
                    }}
                           checked={attrs.indexOf('background-color') >= 0}/>color:
                    <input id='colorpicker'/>

                    <input type="checkbox"/>gradient

{lang.border}

Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo
                      consequat.

            <button type="button" className="btn btn-default" data-dismiss="modal">Close</button>
            <button type="button" className="btn btn-primary">Save changes</button>

  }
})
<|endoftext|>


<commit_before>import {connect} from 'react-redux'
import {createSelector} from 'reselect'
import DemoPieChart from '../components/DemoPieChart'
import {countLetters} from '../utils/stringStats'
import {incrementRenderCount, piechartToggleFilter} from '../actions'
import toJS from '../toJS'

const getText = state => state.get('text')
const getHover = state => state.get('hover')
const getFilterEnabled = state => state.get('piechartFilterEnabled')

const selectHover = createSelector(
  [getHover, getFilterEnabled],
  (hover, filter) => {
    return filter ? hover : null
  }
)

const selectData = createSelector([getText, selectHover], (text, hover) => {
  return text.reduce((result, userText, user) => {
    const nbOfLetters = countLetters(userText, hover ? hover.toJS() : null)
    if (nbOfLetters > 0) {
      result.push({
        name: user,
        value: nbOfLetters
      })
    }
    return result
  }, [])
})

const mapStateToProps = (state, ownProps) => {
  return {
    data: selectData(state),
    hover: selectHover(state),
    filter: state.get('piechartFilterEnabled')
  }
}

const mapDispatchToProps = (dispatch, ownProps) => {
  return {
    incrementRenderCount: mode =>
      dispatch(incrementRenderCount('piechart', mode)),
    toggleFilter: () => dispatch(piechartToggleFilter())
  }
}

export default connect(mapStateToProps, mapDispatchToProps)(toJS(DemoPieChart))
<commit_msg>Fix piechart on 0 chars<commit_after>import {connect} from 'react-redux'
import {createSelector} from 'reselect'
import DemoPieChart from '../components/DemoPieChart'
import {countLetters} from '../utils/stringStats'
import {incrementRenderCount, piechartToggleFilter} from '../actions'
import toJS from '../toJS'

const getText = state => state.get('text')
const getHover = state => state.get('hover')
const getFilterEnabled = state => state.get('piechartFilterEnabled')

const selectHover = createSelector(
  [getHover, getFilterEnabled],
  (hover, filter) => {
    return filter ? hover : null
  }
)

const selectData = createSelector([getText, selectHover], (text, hover) => {
  return text.reduce((result, userText, user) => {
    const nbOfLetters = countLetters(userText, hover ? hover.toJS() : null)
    result.push({
      name: user,
      value: nbOfLetters
    })
    return result
  }, [])
})

const mapStateToProps = (state, ownProps) => {
  return {
    data: selectData(state),
    hover: selectHover(state),
    filter: state.get('piechartFilterEnabled')
  }
}

const mapDispatchToProps = (dispatch, ownProps) => {
  return {
    incrementRenderCount: mode =>
      dispatch(incrementRenderCount('piechart', mode)),
    toggleFilter: () => dispatch(piechartToggleFilter())
  }
}

export default connect(mapStateToProps, mapDispatchToProps)(toJS(DemoPieChart))
<|endoftext|>