<commit_before>// Theirs
import React from 'react'
import HTML5Backend from 'react-dnd-html5-backend'
import { DragDropContext } from 'react-dnd'
import domtoimage from 'dom-to-image'
import ReadFileDropContainer, { DATA_URL, TEXT } from 'dropperx'
import Spinner from 'react-spinner'

// Ours
import Button from './Button'
import Dropdown from './Dropdown'
import BackgroundSelect from './BackgroundSelect'
import Settings from './Settings'
import Toolbar from './Toolbar'
import Overlay from './Overlay'
import Carbon from './Carbon'
import api from '../lib/api'
import {
  THEMES,
  THEMES_HASH,
  LANGUAGES,
  LANGUAGE_MIME_HASH,
  LANGUAGE_MODE_HASH,
  LANGUAGE_NAME_HASH,
  DEFAULT_THEME,
  DEFAULT_EXPORT_SIZE,
  COLORS,
  EXPORT_SIZES_HASH,
  DEFAULT_CODE,
  DEFAULT_SETTINGS
} from '../lib/constants'
import { serializeState } from '../lib/routing'
import { getState } from '../lib/util'

const saveButtonOptions = {
  button: true,
  color: '#c198fb',
  selected: { id: 'SAVE_IMAGE', name: 'Save Image' },
  list: ['png', 'svg', 'open ↗'].map(id => ({ id, name: id.toUpperCase() }))
}

class Editor extends React.Component {
  constructor(props) {
    super(props)
    this.state = {
      ...DEFAULT_SETTINGS,
      loading: true,
      uploading: false,
      code: props.content,
      online: true
    }

    this.save = this.save.bind(this)
    this.upload = this.upload.bind(this)
    this.updateSetting = this.updateSetting.bind(this)
    this.updateCode = this.updateSetting.bind(this, 'code')
    this.updateAspectRatio = this.updateSetting.bind(this, 'aspectRatio')
    this.updateTitleBar = this.updateSetting.bind(this, 'titleBar')
    this.updateTheme = this.updateTheme.bind(this)
    this.updateLanguage = this.updateLanguage.bind(this)
    this.updateBackground = this.updateBackground.bind(this)
    this.resetDefaultSettings = this.resetDefaultSettings.bind(this)
    this.getCarbonImage = this.getCarbonImage.bind(this)
    this.onDrop = this.onDrop.bind(this)

    this.setOffline = () => this.setState({ online: false })
    this.setOnline = () => this.setState({ online: true })
  }

  componentDidMount() {
    // Load from localStorage and then URL params
    this.setState({
      ...getState(localStorage),
      ...this.props.initialState,
      loading: false,
      online: Boolean(window && window.navigator && window.navigator.onLine)
    })

    window.addEventListener('offline', this.setOffline);
    window.addEventListener('online', this.setOnline);
  }

  componentWillUnmount() {
    window.removeEventListener('offline', this.setOffline);
    window.removeEventListener('online', this.setOnline);
  }

  componentDidUpdate() {
    this.props.onUpdate(this.state)
  }

  getCarbonImage({ format, type } = { format: 'png' }) {
    // if safari, get image from api
    if (
      navigator.userAgent.indexOf('Safari') !== -1 &&
      navigator.userAgent.indexOf('Chrome') === -1 &&
      format !== 'svg'
    ) {
      const encodedState = serializeState(this.state)
      return api.image(encodedState)
    }

    const node = document.getElementById('export-container')

    const exportSize = (EXPORT_SIZES_HASH[this.state.exportSize] || DEFAULT_EXPORT_SIZE).value
    const width = node.offsetWidth * exportSize
    const height = this.state.squaredImage
      ? node.offsetWidth * exportSize
      : node.offsetHeight * exportSize

    const config = {
      style: {
        transform: `scale(${exportSize})`,
        'transform-origin': 'center',
        background: this.state.squaredImage ? this.state.backgroundColor : 'none'
      },
      filter: n => {
        // %[00 -> 19] cause failures
        if (
          n.innerText && n.innerText.match(/%\S\S/) &&
          n.className &&
          n.className.startsWith('cm-') && // is CodeMirror primitive string
          format === 'png' // only occurs when saving PNG
        ) {
          n.innerText = encodeURIComponent(n.innerText)
        }
        if (n.className) {
          return String(n.className).indexOf('eliminateOnRender') < 0
        }
        return true
      },
      width,
      height
    }

    if (type === 'blob') {
      if (format === 'svg') {
        return domtoimage
          .toSvg(node, config)
          .then(dataUrl => dataUrl.replace(/ /g, ' '))
          .then(uri => uri.slice(uri.indexOf(',') + 1))
          .then(data => new Blob([data], { type: 'image/svg+xml' }))
          .then(data => window.URL.createObjectURL(data))
      }

      return domtoimage.toBlob(node, config).then(blob => window.URL.createObjectURL(blob))
    }

    // Twitter needs regular dataurls
    return domtoimage.toPng(node, config)
  }

  updateSetting(key, value) {
    this.setState({ [key]: value })
  }

  save({ id: format = 'png' }) {
    const link = document.createElement('a')

    const timestamp = this.state.timestamp ? `_${formatTimestamp()}` : ''

    return this.getCarbonImage({ format, type: 'blob' }).then(url => {
      if (format !== 'open ↗') {
        link.download = `carbon${timestamp}.${format}`
      }
      link.href = url
      document.body.appendChild(link)
      link.click()
      link.remove()
    })
  }

  resetDefaultSettings() {
    this.setState(DEFAULT_SETTINGS)
    localStorage.clear()
  }

  upload() {
    this.setState({ uploading: true })
    this.getCarbonImage({ format: 'png' })
      .then(this.props.tweet.bind(null, this.state.code || DEFAULT_CODE))
      // eslint-disable-next-line
      .catch(console.error)
      .then(() => this.setState({ uploading: false }))
  }

  onDrop([file]) {
    if (isImage(file)) {
      this.setState({
        backgroundImage: file.content,
        backgroundImageSelection: null,
        backgroundMode: 'image'
      })
    } else {
      this.setState({ code: file.content, language: 'auto' })
    }
  }

  updateTheme(theme) {
    this.updateSetting('theme', theme.id)
  }

  updateLanguage(language) {
    this.updateSetting('language', language.mime || language.mode)
  }

  updateBackground({ photographer, ...changes } = {}) {
    if (photographer) {
      this.setState(({ code = DEFAULT_CODE }) => ({
        ...changes,
        code: code + `\n\n// Photo by ${photographer.name} on Unsplash`
      }))
    } else {
      this.setState(changes)
    }
  }

  render() {
    if (this.state.loading) {
      return <Spinner />
    }
    return (
      <React.Fragment>

          <Toolbar>
            <Dropdown
              selected={THEMES_HASH[this.state.theme] || DEFAULT_THEME}
              list={THEMES}
              onChange={this.updateTheme}
            />
            <Dropdown
              selected={
                LANGUAGE_NAME_HASH[this.state.language] ||
                LANGUAGE_MIME_HASH[this.state.language] ||
                LANGUAGE_MODE_HASH[this.state.language] ||
                'auto'
              }
              list={LANGUAGES}
              onChange={this.updateLanguage}
            />
            <BackgroundSelect
              onChange={this.updateBackground}
              mode={this.state.backgroundMode}
              color={this.state.backgroundColor}
              image={this.state.backgroundImage}
              aspectRatio={this.state.aspectRatio}
            />
            <Settings
              {...this.state}
              onChange={this.updateSetting}
              resetDefaultSettings={this.resetDefaultSettings}
            />

              {this.props.tweet && this.state.online && (
                <Button
                  className="tweetButton"
                  onClick={this.upload}
                  title={this.state.uploading ? 'Loading...' : 'Tweet Image'}
                  color="#57b5f9"
                  style={{ marginRight: '8px' }}
                />
              )}
              <Dropdown {...saveButtonOptions} onChange={this.save} />

          </Toolbar>

          <ReadFileDropContainer readAs={readAs} onDrop={this.onDrop}>
            {({ isOver, canDrop }) => (
              <Overlay
                isOver={isOver || canDrop}
                title={`Drop your file here to import ${isOver ? '✋' : '✊'}`}
              >
                <Carbon
                  config={this.state}
                  updateCode={this.updateCode}
                  onAspectRatioChange={this.updateAspectRatio}
                  titleBar={this.state.titleBar}
                  updateTitleBar={this.updateTitleBar}
                >
                  {this.state.code != null ? this.state.code : DEFAULT_CODE}
                </Carbon>
              </Overlay>
            )}
          </ReadFileDropContainer>

      </React.Fragment>
    )
  }
}

function formatTimestamp() {
  const timezoneOffset = (new Date()).getTimezoneOffset() * 60000
  const timeString = (new Date(Date.now() - timezoneOffset)).toISOString()
    .slice(0, 19)
    .replace(/:/g,'-')
    .replace('T','_')

  return timeString;
}

function isImage(file) {
  return file.type.split('/')[0] === 'image'
}

function readAs(file) {
  if (isImage(file)) {
    return DATA_URL
  }
  return TEXT
}

Editor.defaultProps = {
  onUpdate: () => {}
}

export default DragDropContext(HTML5Backend)(Editor)
<commit_msg>encode text before reading the container width (#466)<commit_after>// Theirs
import React from 'react'
import HTML5Backend from 'react-dnd-html5-backend'
import { DragDropContext } from 'react-dnd'
import domtoimage from 'dom-to-image'
import ReadFileDropContainer, { DATA_URL, TEXT } from 'dropperx'
import Spinner from 'react-spinner'

// Ours
import Button from './Button'
import Dropdown from './Dropdown'
import BackgroundSelect from './BackgroundSelect'
import Settings from './Settings'
import Toolbar from './Toolbar'
import Overlay from './Overlay'
import Carbon from './Carbon'
import api from '../lib/api'
import {
  THEMES,
  THEMES_HASH,
  LANGUAGES,
  LANGUAGE_MIME_HASH,
  LANGUAGE_MODE_HASH,
  LANGUAGE_NAME_HASH,
  DEFAULT_THEME,
  DEFAULT_EXPORT_SIZE,
  COLORS,
  EXPORT_SIZES_HASH,
  DEFAULT_CODE,
  DEFAULT_SETTINGS
} from '../lib/constants'
import { serializeState } from '../lib/routing'
import { getState } from '../lib/util'

const saveButtonOptions = {
  button: true,
  color: '#c198fb',
  selected: { id: 'SAVE_IMAGE', name: 'Save Image' },
  list: ['png', 'svg', 'open ↗'].map(id => ({ id, name: id.toUpperCase() }))
}

class Editor extends React.Component {
  constructor(props) {
    super(props)
    this.state = {
      ...DEFAULT_SETTINGS,
      loading: true,
      uploading: false,
      code: props.content,
      online: true
    }

    this.save = this.save.bind(this)
    this.upload = this.upload.bind(this)
    this.updateSetting = this.updateSetting.bind(this)
    this.updateCode = this.updateSetting.bind(this, 'code')
    this.updateAspectRatio = this.updateSetting.bind(this, 'aspectRatio')
    this.updateTitleBar = this.updateSetting.bind(this, 'titleBar')
    this.updateTheme = this.updateTheme.bind(this)
    this.updateLanguage = this.updateLanguage.bind(this)
    this.updateBackground = this.updateBackground.bind(this)
    this.resetDefaultSettings = this.resetDefaultSettings.bind(this)
    this.getCarbonImage = this.getCarbonImage.bind(this)
    this.onDrop = this.onDrop.bind(this)

    this.setOffline = () => this.setState({ online: false })
    this.setOnline = () => this.setState({ online: true })
  }

  componentDidMount() {
    // Load from localStorage and then URL params
    this.setState({
      ...getState(localStorage),
      ...this.props.initialState,
      loading: false,
      online: Boolean(window && window.navigator && window.navigator.onLine)
    })

    window.addEventListener('offline', this.setOffline);
    window.addEventListener('online', this.setOnline);
  }

  componentWillUnmount() {
    window.removeEventListener('offline', this.setOffline);
    window.removeEventListener('online', this.setOnline);
  }

  componentDidUpdate() {
    this.props.onUpdate(this.state)
  }

  getCarbonImage({ format, type } = { format: 'png' }) {
    // if safari, get image from api
    const isPNG = format !== 'svg'
    if (
      navigator.userAgent.indexOf('Safari') !== -1 &&
      navigator.userAgent.indexOf('Chrome') === -1 &&
      isPNG
    ) {
      const encodedState = serializeState(this.state)
      return api.image(encodedState)
    }

    if (isPNG) {
      document.querySelectorAll('.CodeMirror-line > span > span').forEach(n => {
        if (n.innerText && n.innerText.match(/%\S\S/)) {
          n.innerText = encodeURIComponent(n.innerText);
        }
      })
    }

    const node = document.getElementById('export-container')

    const exportSize = (EXPORT_SIZES_HASH[this.state.exportSize] || DEFAULT_EXPORT_SIZE).value
    const width = node.offsetWidth * exportSize
    const height = this.state.squaredImage
      ? node.offsetWidth * exportSize
      : node.offsetHeight * exportSize

    const config = {
      style: {
        transform: `scale(${exportSize})`,
        'transform-origin': 'center',
        background: this.state.squaredImage ? this.state.backgroundColor : 'none'
      },
      filter: n => {
        if (n.className) {
          return String(n.className).indexOf('eliminateOnRender') < 0
        }
        return true
      },
      width,
      height
    }

    if (type === 'blob') {
      if (format === 'svg') {
        return domtoimage
          .toSvg(node, config)
          .then(dataUrl => dataUrl.replace(/ /g, ' '))
          .then(uri => uri.slice(uri.indexOf(',') + 1))
          .then(data => new Blob([data], { type: 'image/svg+xml' }))
          .then(data => window.URL.createObjectURL(data))
      }

      return domtoimage.toBlob(node, config).then(blob => window.URL.createObjectURL(blob))
    }

    // Twitter needs regular dataurls
    return domtoimage.toPng(node, config)
  }

  updateSetting(key, value) {
    this.setState({ [key]: value })
  }

  save({ id: format = 'png' }) {
    const link = document.createElement('a')

    const timestamp = this.state.timestamp ? `_${formatTimestamp()}` : ''

    return this.getCarbonImage({ format, type: 'blob' }).then(url => {
      if (format !== 'open ↗') {
        link.download = `carbon${timestamp}.${format}`
      }
      link.href = url
      document.body.appendChild(link)
      link.click()
      link.remove()
    })
  }

  resetDefaultSettings() {
    this.setState(DEFAULT_SETTINGS)
    localStorage.clear()
  }

  upload() {
    this.setState({ uploading: true })
    this.getCarbonImage({ format: 'png' })
      .then(this.props.tweet.bind(null, this.state.code || DEFAULT_CODE))
      // eslint-disable-next-line
      .catch(console.error)
      .then(() => this.setState({ uploading: false }))
  }

  onDrop([file]) {
    if (isImage(file)) {
      this.setState({
        backgroundImage: file.content,
        backgroundImageSelection: null,
        backgroundMode: 'image'
      })
    } else {
      this.setState({ code: file.content, language: 'auto' })
    }
  }

  updateTheme(theme) {
    this.updateSetting('theme', theme.id)
  }

  updateLanguage(language) {
    this.updateSetting('language', language.mime || language.mode)
  }

  updateBackground({ photographer, ...changes } = {}) {
    if (photographer) {
      this.setState(({ code = DEFAULT_CODE }) => ({
        ...changes,
        code: code + `\n\n// Photo by ${photographer.name} on Unsplash`
      }))
    } else {
      this.setState(changes)
    }
  }

  render() {
    if (this.state.loading) {
      return <Spinner />
    }
    return (
      <React.Fragment>

          <Toolbar>
            <Dropdown
              selected={THEMES_HASH[this.state.theme] || DEFAULT_THEME}
              list={THEMES}
              onChange={this.updateTheme}
            />
            <Dropdown
              selected={
                LANGUAGE_NAME_HASH[this.state.language] ||
                LANGUAGE_MIME_HASH[this.state.language] ||
                LANGUAGE_MODE_HASH[this.state.language] ||
                'auto'
              }
              list={LANGUAGES}
              onChange={this.updateLanguage}
            />
            <BackgroundSelect
              onChange={this.updateBackground}
              mode={this.state.backgroundMode}
              color={this.state.backgroundColor}
              image={this.state.backgroundImage}
              aspectRatio={this.state.aspectRatio}
            />
            <Settings
              {...this.state}
              onChange={this.updateSetting}
              resetDefaultSettings={this.resetDefaultSettings}
            />

              {this.props.tweet && this.state.online && (
                <Button
                  className="tweetButton"
                  onClick={this.upload}
                  title={this.state.uploading ? 'Loading...' : 'Tweet Image'}
                  color="#57b5f9"
                  style={{ marginRight: '8px' }}
                />
              )}
              <Dropdown {...saveButtonOptions} onChange={this.save} />

          </Toolbar>

          <ReadFileDropContainer readAs={readAs} onDrop={this.onDrop}>
            {({ isOver, canDrop }) => (
              <Overlay
                isOver={isOver || canDrop}
                title={`Drop your file here to import ${isOver ? '✋' : '✊'}`}
              >
                <Carbon
                  config={this.state}
                  updateCode={this.updateCode}
                  onAspectRatioChange={this.updateAspectRatio}
                  titleBar={this.state.titleBar}
                  updateTitleBar={this.updateTitleBar}
                >
                  {this.state.code != null ? this.state.code : DEFAULT_CODE}
                </Carbon>
              </Overlay>
            )}
          </ReadFileDropContainer>

      </React.Fragment>
    )
  }
}

function formatTimestamp() {
  const timezoneOffset = (new Date()).getTimezoneOffset() * 60000
  const timeString = (new Date(Date.now() - timezoneOffset)).toISOString()
    .slice(0, 19)
    .replace(/:/g,'-')
    .replace('T','_')

  return timeString;
}

function isImage(file) {
  return file.type.split('/')[0] === 'image'
}

function readAs(file) {
  if (isImage(file)) {
    return DATA_URL
  }
  return TEXT
}

Editor.defaultProps = {
  onUpdate: () => {}
}

export default DragDropContext(HTML5Backend)(Editor)
<|endoftext|>


<commit_before>const CleanCSS = require('clean-css');
const eleventyNavigationPlugin = require("@11ty/eleventy-navigation");
const syntaxHighlight = require("@11ty/eleventy-plugin-syntaxhighlight");
const pluginTOC = require('eleventy-plugin-nesting-toc');
const imageOptimizations = require('./_11ty/image-dimensions.js');

module.exports = function(config) {
	config.addPassthroughCopy('img');
	config.addPassthroughCopy('fonts');
	config.addPassthroughCopy('sw.js');
	config.addPassthroughCopy('manifest.json');

	config.addPlugin(pluginTOC, {tags: ['h2']});
	config.addPlugin(syntaxHighlight);
	config.addPlugin(imageOptimizations);
	config.addPlugin(eleventyNavigationPlugin);
	config.addFilter('cssmin', function(code) {
		return new CleanCSS({}).minify(code).styles;
	});
	const { minify } = require("terser");
	config.addNunjucksAsyncFilter("jsmin", async function (
		code,
		callback
	  ) {
		try {
		  const minified = await minify(code);
		  callback(null, minified.code);
		} catch (err) {
		  console.error("Terser error: ", err);
		  // Fail gracefully.
		  callback(null, code);
		}
	  });

	let markdownIt = require("markdown-it");
	const markdownItAnchor = require('markdown-it-anchor');
	const markdownItAttrs = require('markdown-it-attrs');
	const markdownItDefList = require("markdown-it-deflist");
	let options = {
	  html: true,
	  linkify: true,
	  breaks: true
	};
	let markdownLib = markdownIt(options).use(
		markdownItAnchor,
		markdownItAttrs, {
			leftDelimiter: '{:',
			rightDelimiter: '}',
		}
	);

	config.setLibrary("md", markdownLib);

	return {
		dir: { input: './src', output: 'dist', includes: '_includes', data: '_data' },
		passthroughFileCopy: true
	};
};<commit_msg>adding src/img as passthrough<commit_after>const CleanCSS = require('clean-css');
const eleventyNavigationPlugin = require("@11ty/eleventy-navigation");
const syntaxHighlight = require("@11ty/eleventy-plugin-syntaxhighlight");
const pluginTOC = require('eleventy-plugin-nesting-toc');
const imageOptimizations = require('./_11ty/image-dimensions.js');

module.exports = function(config) {
	config.addPassthroughCopy('src/img');

	config.addPlugin(pluginTOC, {tags: ['h2']});
	config.addPlugin(syntaxHighlight);
	config.addPlugin(imageOptimizations);
	config.addPlugin(eleventyNavigationPlugin);
	config.addFilter('cssmin', function(code) {
		return new CleanCSS({}).minify(code).styles;
	});
	const { minify } = require("terser");
	config.addNunjucksAsyncFilter("jsmin", async function (
		code,
		callback
	  ) {
		try {
		  const minified = await minify(code);
		  callback(null, minified.code);
		} catch (err) {
		  console.error("Terser error: ", err);
		  // Fail gracefully.
		  callback(null, code);
		}
	  });

	let markdownIt = require("markdown-it");
	const markdownItAnchor = require('markdown-it-anchor');
	const markdownItAttrs = require('markdown-it-attrs');
	const markdownItDefList = require("markdown-it-deflist");
	let options = {
	  html: true,
	  linkify: true,
	  breaks: true
	};
	let markdownLib = markdownIt(options).use(
		markdownItAnchor,
		markdownItAttrs, {
			leftDelimiter: '{:',
			rightDelimiter: '}',
		}
	);

	config.setLibrary("md", markdownLib);

	return {
		dir: { input: './src', output: 'dist', includes: '_includes', data: '_data' },
		passthroughFileCopy: true
	};
};<|endoftext|>


<commit_before>/*
 * Copyright © 2015 Cask Data, Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
 * use this file except in compliance with the License. You may obtain a copy of
 * the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations under
 * the License.
 */

/**
 * The format of vm.validationFields:
 *
 * vm.validationFields = {
 *   <field name>:[
 *     {
 *       "fieldName": "<field name>",
 *       "operation": true/false,
 *       "validation": "<validator classname>.<function name>"
 *       "arguments": {
 *         "<argument 1>": <value 1>,
 *         "<argument 2>": <value 2>,
 *         ...
 *       }
 *     },
 *     ...
 *   ]
 * }
 **/
angular.module(PKG.name + '.commons')
  .factory('js_beautify', function ($window) {
    /**
     * js_beautify is to format the indentation for javascript code
     **/

    return $window.js_beautify;
  });

angular.module(PKG.name + '.commons')
  .controller('MyValidatorsCtrl', function($scope, myHydratorValidatorsApi, EventPipe, NodeConfigStore, myHelpers, NonStorePipelineErrorFactory, GLOBALS, js_beautify) {
    var vm = this;

    vm.validators = [];
    vm.isRule = true;
    vm.validationFields = $scope.model.validationFields || {};
    vm.functionMap = {};
    vm.nodeLabelError = '';

    var validatorsList;
    var classNameList = [];

    // We just need to set the input schema as the output schema
    $scope.outputSchema = JSON.stringify({fields: $scope.inputSchema});

    myHydratorValidatorsApi.get()
      .$promise
      .then(function (res) {
        delete res.$promise;
        delete res.$resolved;

        validatorsList = Object.keys(res).join(', ');

        angular.forEach(res, function (value, key) {
          classNameList.push(value.classname);

          angular.forEach(value.functions, function (v) {
            v.className = value.classname;
            v.validator = key;
            v.validationKey = v.className + '.' + v.name;
            v.displayName = v.name + ' (' + key + ')';
            vm.functionMap[v.validationKey] = v;
          });
          vm.validators = vm.validators.concat(value.functions);
        });

        $scope.$watch(function () {
          return vm.validationFields;
        }, formatValidationRules, true);

      });

    vm.addFieldGroup = function (fieldName) {
      if (vm.validationFields[fieldName]) { return; }
      vm.validationFields[fieldName] = [];
    };

    vm.removeFieldGroup = function (fieldName) {
      vm.validationFields[fieldName].splice(0, vm.validationFields[fieldName].length);
      delete vm.validationFields[fieldName];
    };

    vm.addRule = function (fieldName) {
      vm.validationFields[fieldName].push({
        fieldName: fieldName,
        operation: true // true === 'AND'
      });
    };

    vm.removeRule = function (fieldName, rule) {
      var index = vm.validationFields[fieldName].indexOf(rule);
      vm.validationFields[fieldName].splice(index, 1);
    };

    function formatValidationRules() {
      if (Object.keys(vm.validationFields).length === 0) { return; }

      var conditions = '';
      var flattenRulesArrays = [];

      angular.forEach(vm.validationFields, function (value) {
        flattenRulesArrays = flattenRulesArrays.concat(value);
      });

      // this will get triggered when user switch the validation rule
      function deleteArguments (value, key) {
        if (validation.arguments.indexOf(key) === -1) {
          delete field.arguments[key];
        }
      }

      /**
       * This block code will go from the last property. It will create an
       * If/Else block. If the next item in the array has AND operation, it
       * will put the current condition in IF block. For OR, it will put the
       * current condition in ELSE block.
       **/
      for (var i = flattenRulesArrays.length - 1; i >= 0; i--) {
        var field = flattenRulesArrays[i];

        // skipping the property if there is no function assigned for the property
        if (!field.fieldName || !field.validation) { continue; }

        // skipping if the required arguments have not been set
        var validation = vm.functionMap[field.validation];
        if (validation.arguments.length > 1 &&
          (!field.arguments || Object.keys(field.arguments).length !== validation.arguments.length - 1)) {
          continue;
        }

        if (field.arguments) {
          angular.forEach(field.arguments, deleteArguments);
        }

        var emessage = validation.emessage || '';
        emessage = emessage.replace(/<field:1>/g, '" + input.' + field.fieldName + ' + "');

        var currentBlock = '';

        currentBlock = 'if (' +
          validation.className + '.' +
          validation.name + '(';

        /*jshint -W083 */
        angular.forEach(validation.arguments, function (val, $index) {
          if ($index !== 0) {
            currentBlock += ', ';
          }
          if (val === '<field:1>') {
            currentBlock += 'input.' + field.fieldName;
          } else {
            currentBlock += field.arguments[val];
          }

          if (val.startsWith('<field') && val !== '<field:1>') {
            var re = new RegExp(val, 'g');
            emessage = emessage.replace(re, '" + input.' +
              field.arguments[val] + ' + "');
          }

        });

        if (i === flattenRulesArrays.length - 1 || flattenRulesArrays[i + 1].operation) {
          currentBlock += ')) {\n' +
            conditions +
            '} else {\n' +
            'isValid = false;\n' +
            'errMsg = "' + emessage + '";\n' +
            'errCode = ' + validation.ecode + ';\n' +
            '}\n';

        } else { // if operation === 'OR'
          currentBlock += ')) {\n} else {\n' +
            conditions + '\n}\n';
        }

        conditions = currentBlock;
      }

      conditions += '\n\n';

      var initFn = 'function isValid(input, context) {\n' +
        'var isValid = true;\n' +
        'var errMsg = "";\n' +
        'var errCode = 0;\n';

      // LOAD CONTEXT
      var context = '';
      angular.forEach(classNameList, function (className) {
        context = context + 'var ' + className +
          ' = context.getValidator("' + className + '");\n';
      });

      var loggerLoad = 'var logger = context.getLogger();\n\n';
      var loggerEnd = 'if (!isValid) {\n' +
        'var message = "(" + errCode + ") " + errMsg;\n' +
        'logger.warn("Validation failed with error {}", message);\n' +
        '}\n\n';

      var fn = initFn + context +
        loggerLoad + conditions +
        loggerEnd +
        'return {\n' +
        '"isValid": isValid,\n' +
        '"errorCode": errCode,\n' +
        '"errorMsg": errMsg\n' +
        '};\n}\n';

      var validatorProperties = {
        validators: validatorsList,
        validationScript: js_beautify(fn, { indent_size: 2 })
      };

      if ($scope.model.properties !== validatorProperties) {
        $scope.model.properties = validatorProperties;
      }
      if ($scope.model.validationFields !== vm.validationFields) {
        $scope.model.validationFields = vm.validationFields;
      }

    }

    function validateNodesLabels () {
      var nodes = NodeConfigStore.ConfigStore.getNodes();
      var nodeName = $scope.model.label;

      if (!nodeName) {
        return;
      }
      NonStorePipelineErrorFactory.isNodeNameUnique(nodeName, nodes, function (err) {
        if (err) {
          vm.nodeLabelError = GLOBALS.en.hydrator.studio.error[err];
        } else {
          vm.nodeLabelError = '';
        }
      });
    }

    $scope.$watch('model.label', validateNodesLabels);

    // Since validation fields is a reference and we overwrite the array
    // reference all the time $watch will not be triggered hence the event communication.
    EventPipe.on('resetValidatorValidationFields', function(validationFields) {
      vm.validationFields = validationFields || {};
      $scope.model.validationFields = vm.validationFields;
    });
  });
<commit_msg>remove extra newline on the script generated<commit_after>/*
 * Copyright © 2015 Cask Data, Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
 * use this file except in compliance with the License. You may obtain a copy of
 * the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations under
 * the License.
 */

/**
 * The format of vm.validationFields:
 *
 * vm.validationFields = {
 *   <field name>:[
 *     {
 *       "fieldName": "<field name>",
 *       "operation": true/false,
 *       "validation": "<validator classname>.<function name>"
 *       "arguments": {
 *         "<argument 1>": <value 1>,
 *         "<argument 2>": <value 2>,
 *         ...
 *       }
 *     },
 *     ...
 *   ]
 * }
 **/
angular.module(PKG.name + '.commons')
  .factory('js_beautify', function ($window) {
    /**
     * js_beautify is to format the indentation for javascript code
     **/

    return $window.js_beautify;
  });

angular.module(PKG.name + '.commons')
  .controller('MyValidatorsCtrl', function($scope, myHydratorValidatorsApi, EventPipe, NodeConfigStore, myHelpers, NonStorePipelineErrorFactory, GLOBALS, js_beautify) {
    var vm = this;

    vm.validators = [];
    vm.isRule = true;
    vm.validationFields = $scope.model.validationFields || {};
    vm.functionMap = {};
    vm.nodeLabelError = '';

    var validatorsList;
    var classNameList = [];

    // We just need to set the input schema as the output schema
    $scope.outputSchema = JSON.stringify({fields: $scope.inputSchema});

    myHydratorValidatorsApi.get()
      .$promise
      .then(function (res) {
        delete res.$promise;
        delete res.$resolved;

        validatorsList = Object.keys(res).join(', ');

        angular.forEach(res, function (value, key) {
          classNameList.push(value.classname);

          angular.forEach(value.functions, function (v) {
            v.className = value.classname;
            v.validator = key;
            v.validationKey = v.className + '.' + v.name;
            v.displayName = v.name + ' (' + key + ')';
            vm.functionMap[v.validationKey] = v;
          });
          vm.validators = vm.validators.concat(value.functions);
        });

        $scope.$watch(function () {
          return vm.validationFields;
        }, formatValidationRules, true);

      });

    vm.addFieldGroup = function (fieldName) {
      if (vm.validationFields[fieldName]) { return; }
      vm.validationFields[fieldName] = [];
    };

    vm.removeFieldGroup = function (fieldName) {
      vm.validationFields[fieldName].splice(0, vm.validationFields[fieldName].length);
      delete vm.validationFields[fieldName];
    };

    vm.addRule = function (fieldName) {
      vm.validationFields[fieldName].push({
        fieldName: fieldName,
        operation: true // true === 'AND'
      });
    };

    vm.removeRule = function (fieldName, rule) {
      var index = vm.validationFields[fieldName].indexOf(rule);
      vm.validationFields[fieldName].splice(index, 1);
    };

    function formatValidationRules() {
      if (Object.keys(vm.validationFields).length === 0) { return; }

      var conditions = '';
      var flattenRulesArrays = [];

      angular.forEach(vm.validationFields, function (value) {
        flattenRulesArrays = flattenRulesArrays.concat(value);
      });

      // this will get triggered when user switch the validation rule
      function deleteArguments (value, key) {
        if (validation.arguments.indexOf(key) === -1) {
          delete field.arguments[key];
        }
      }

      /**
       * This block code will go from the last property. It will create an
       * If/Else block. If the next item in the array has AND operation, it
       * will put the current condition in IF block. For OR, it will put the
       * current condition in ELSE block.
       **/
      for (var i = flattenRulesArrays.length - 1; i >= 0; i--) {
        var field = flattenRulesArrays[i];

        // skipping the property if there is no function assigned for the property
        if (!field.fieldName || !field.validation) { continue; }

        // skipping if the required arguments have not been set
        var validation = vm.functionMap[field.validation];
        if (validation.arguments.length > 1 &&
          (!field.arguments || Object.keys(field.arguments).length !== validation.arguments.length - 1)) {
          continue;
        }

        if (field.arguments) {
          angular.forEach(field.arguments, deleteArguments);
        }

        var emessage = validation.emessage || '';
        emessage = emessage.replace(/<field:1>/g, '" + input.' + field.fieldName + ' + "');

        var currentBlock = '';

        currentBlock = 'if (' +
          validation.className + '.' +
          validation.name + '(';

        /*jshint -W083 */
        angular.forEach(validation.arguments, function (val, $index) {
          if ($index !== 0) {
            currentBlock += ', ';
          }
          if (val === '<field:1>') {
            currentBlock += 'input.' + field.fieldName;
          } else {
            currentBlock += field.arguments[val];
          }

          if (val.startsWith('<field') && val !== '<field:1>') {
            var re = new RegExp(val, 'g');
            emessage = emessage.replace(re, '" + input.' +
              field.arguments[val] + ' + "');
          }

        });

        if (i === flattenRulesArrays.length - 1 || flattenRulesArrays[i + 1].operation) {
          currentBlock += ')) {\n' +
            conditions +
            '} else {\n' +
            'isValid = false;\n' +
            'errMsg = "' + emessage + '";\n' +
            'errCode = ' + validation.ecode + ';\n' +
            '}\n';

        } else { // if operation === 'OR'
          currentBlock += ')) {\n} else {\n' +
            conditions + '\n}\n';
        }

        conditions = currentBlock;
      }

      conditions += '\n';

      var initFn = 'function isValid(input, context) {\n' +
        'var isValid = true;\n' +
        'var errMsg = "";\n' +
        'var errCode = 0;\n';

      // LOAD CONTEXT
      var context = '';
      angular.forEach(classNameList, function (className) {
        context = context + 'var ' + className +
          ' = context.getValidator("' + className + '");\n';
      });

      var loggerLoad = 'var logger = context.getLogger();\n\n';
      var loggerEnd = 'if (!isValid) {\n' +
        'var message = "(" + errCode + ") " + errMsg;\n' +
        'logger.warn("Validation failed with error {}", message);\n' +
        '}\n\n';

      var fn = initFn + context +
        loggerLoad + conditions +
        loggerEnd +
        'return {\n' +
        '"isValid": isValid,\n' +
        '"errorCode": errCode,\n' +
        '"errorMsg": errMsg\n' +
        '};\n}\n';

      var validatorProperties = {
        validators: validatorsList,
        validationScript: js_beautify(fn, { indent_size: 2 })
      };

      if ($scope.model.properties !== validatorProperties) {
        $scope.model.properties = validatorProperties;
      }
      if ($scope.model.validationFields !== vm.validationFields) {
        $scope.model.validationFields = vm.validationFields;
      }

    }

    function validateNodesLabels () {
      var nodes = NodeConfigStore.ConfigStore.getNodes();
      var nodeName = $scope.model.label;

      if (!nodeName) {
        return;
      }
      NonStorePipelineErrorFactory.isNodeNameUnique(nodeName, nodes, function (err) {
        if (err) {
          vm.nodeLabelError = GLOBALS.en.hydrator.studio.error[err];
        } else {
          vm.nodeLabelError = '';
        }
      });
    }

    $scope.$watch('model.label', validateNodesLabels);

    // Since validation fields is a reference and we overwrite the array
    // reference all the time $watch will not be triggered hence the event communication.
    EventPipe.on('resetValidatorValidationFields', function(validationFields) {
      vm.validationFields = validationFields || {};
      $scope.model.validationFields = vm.validationFields;
    });
  });
<|endoftext|>


<commit_before>import CircleStyle from '../../../src/ol/style/Circle.js';
import Feature from '../../../src/ol/Feature.js';
import Fill from '../../../src/ol/style/Fill.js';
import Map from '../../../src/ol/Map.js';
import Point from '../../../src/ol/geom/Point.js';
import Stroke from '../../../src/ol/style/Stroke.js';
import Style from '../../../src/ol/style/Style.js';
import Text from '../../../src/ol/style/Text.js';
import VectorLayer from '../../../src/ol/layer/Vector.js';
import VectorSource from '../../../src/ol/source/Vector.js';
import View from '../../../src/ol/View.js';

const vectorSource = new VectorSource();
let feature;

// Latin - end (right)
feature = new Feature({
  geometry: new Point([-10, 50]),
});
feature.setStyle(
  new Style({
    text: new Text({
      text: 'Latin',
      font: '24px Ubuntu',
      textAlign: 'end',
      fill: new Fill({
        color: 'black',
      }),
      stroke: new Stroke({
        color: 'white',
      }),
    }),
    image: new CircleStyle({
      radius: 10,
      fill: new Fill({
        color: 'cyan',
      }),
    }),
  })
);
vectorSource.addFeature(feature);

// Hebrew - start (right)
feature = new Feature({
  geometry: new Point([-10, 0]),
});
feature.setStyle(
  new Style({
    text: new Text({
      text: 'עִברִית',
      font: '24px Ubuntu',
      textAlign: 'start',
      fill: new Fill({
        color: 'black',
      }),
      stroke: new Stroke({
        color: 'white',
      }),
    }),
    image: new CircleStyle({
      radius: 10,
      fill: new Fill({
        color: 'cyan',
      }),
    }),
  })
);
vectorSource.addFeature(feature);

// Arabic - start (right)
feature = new Feature({
  geometry: new Point([-10, -50]),
});
feature.setStyle(
  new Style({
    text: new Text({
      text: 'عربى',
      font: '24px Ubuntu',
      textAlign: 'start',
      fill: new Fill({
        color: 'black',
      }),
      stroke: new Stroke({
        color: 'white',
      }),
    }),
    image: new CircleStyle({
      radius: 10,
      fill: new Fill({
        color: 'cyan',
      }),
    }),
  })
);
vectorSource.addFeature(feature);

// Latin - start (left)
feature = new Feature({
  geometry: new Point([10, 50]),
});
feature.setStyle(
  new Style({
    text: new Text({
      text: 'Latin',
      font: '24px Ubuntu',
      textAlign: 'start',
      fill: new Fill({
        color: 'black',
      }),
      stroke: new Stroke({
        color: 'white',
      }),
    }),
    image: new CircleStyle({
      radius: 10,
      fill: new Fill({
        color: 'cyan',
      }),
    }),
  })
);
vectorSource.addFeature(feature);

// Hebrew - end (left)
feature = new Feature({
  geometry: new Point([10, 0]),
});
feature.setStyle(
  new Style({
    text: new Text({
      text: 'עִברִית',
      font: '24px Ubuntu',
      textAlign: 'end',
      fill: new Fill({
        color: 'black',
      }),
      stroke: new Stroke({
        color: 'white',
      }),
    }),
    image: new CircleStyle({
      radius: 10,
      fill: new Fill({
        color: 'cyan',
      }),
    }),
  })
);
vectorSource.addFeature(feature);

// Arabic - end (left)
feature = new Feature({
  geometry: new Point([10, -50]),
});
feature.setStyle(
  new Style({
    text: new Text({
      text: 'عربى',
      font: '24px Ubuntu',
      textAlign: 'end',
      fill: new Fill({
        color: 'black',
      }),
      stroke: new Stroke({
        color: 'white',
      }),
    }),
    image: new CircleStyle({
      radius: 10,
      fill: new Fill({
        color: 'cyan',
      }),
    }),
  })
);
vectorSource.addFeature(feature);

new Map({
  pixelRatio: 1,
  layers: [
    new VectorLayer({
      source: vectorSource,
    }),
  ],
  target: 'map',
  view: new View({
    center: [0, 0],
    resolution: 1,
  }),
});

render({tolerance: 0.01});
<commit_msg>Improve readability of rendering test<commit_after>import CircleStyle from '../../../src/ol/style/Circle.js';
import Feature from '../../../src/ol/Feature.js';
import Fill from '../../../src/ol/style/Fill.js';
import Map from '../../../src/ol/Map.js';
import Point from '../../../src/ol/geom/Point.js';
import Stroke from '../../../src/ol/style/Stroke.js';
import Style from '../../../src/ol/style/Style.js';
import Text from '../../../src/ol/style/Text.js';
import VectorLayer from '../../../src/ol/layer/Vector.js';
import VectorSource from '../../../src/ol/source/Vector.js';
import View from '../../../src/ol/View.js';

const vectorSource = new VectorSource({
  features: [
    // Latin - end (right)
    new Feature({
      geometry: new Point([-10, 50]),
      text: 'Latin',
      textAlign: 'end',
    }),

    // Hebrew - start (right)
    new Feature({
      geometry: new Point([-10, 0]),
      text: 'עִברִית',
      textAlign: 'start',
    }),

    // Arabic - start (right)
    new Feature({
      geometry: new Point([-10, -50]),
      text: 'عربى',
      textAlign: 'start',
    }),

    // Latin - start (left)
    new Feature({
      geometry: new Point([10, 50]),
      text: 'Latin',
      textAlign: 'start',
    }),

    // Hebrew - end (left)
    new Feature({
      geometry: new Point([10, 0]),
      text: 'עִברִית',
      textAlign: 'end',
    }),

    // Arabic - end (left)
    new Feature({
      geometry: new Point([10, -50]),
      text: 'عربى',
      font: '24px Ubuntu',
      textAlign: 'end',
    }),
  ],
});

new Map({
  pixelRatio: 1,
  layers: [
    new VectorLayer({
      source: vectorSource,
      style: function (feature) {
        return new Style({
          text: new Text({
            text: feature.get('text'),
            font: '24px Ubuntu',
            textAlign: feature.get('textAlign'),
            fill: new Fill({
              color: 'black',
            }),
            stroke: new Stroke({
              color: 'white',
            }),
          }),
          image: new CircleStyle({
            radius: 10,
            fill: new Fill({
              color: 'cyan',
            }),
          }),
        });
      },
    }),
  ],
  target: 'map',
  view: new View({
    center: [0, 0],
    resolution: 1,
  }),
});

render({tolerance: 0.01});
<|endoftext|>


<commit_before>#!/usr/bin/env node

var copy, sheetParserWrapper, build, clean, main,
    fs = require('fs'),
    read = function(filename) { return fs.readFileSync(filename, 'utf8'); },
    path = require('path'),
    join = path.join,
    resolve = path.resolve,
    basename = path.basename,
    dirname = path.dirname,
    exists = path.existsSync,
    moduleBase = resolve(join(__dirname, '..')),
    srcDir = join(moduleBase, 'src'),
    rjsDir = join(moduleBase, 'vendor/requirejs'),
    qunitDir = join(dirname(require.resolve('qunit')), 'deps/qunit/qunit'),
    sources = [
        join(rjsDir, 'require.js'),
        join(rjsDir, 'text.js'),
        join(rjsDir, 'order.js'),
        join(qunitDir, 'qunit.js'),
        join(qunitDir, 'qunit.css')
    ];

copy = function(fromPath, toPath) {
    fs.writeFileSync(toPath, fs.readFileSync(fromPath, 'utf8'));
};

sheetParserWrapper = function(js) {
    return ';(function() {\nvar require;\n' + js + '\n})();';
};

build = function() {
    var sheetPath = dirname(require.resolve('Sheet')),
        sheetRegExPath = join(sheetPath, 'sg-regex-tools.js'),
        sheetParserPath = join(sheetPath, 'SheetParser.CSS.js');
    sources.forEach(function(src) {
        copy(src, join(srcDir, basename(src)));
    });

    fs.writeFileSync(
            'src/css.js',
            [
                read(sheetRegExPath),
                sheetParserWrapper(read(sheetParserPath)),
                read('lib/css_rewrite.js'),
                read('lib/css_requirejs_plugin.js')
            ].join(''));
};

clean = function() {
    sources.forEach(function(src) {
		src = join(srcDir, basename(src));
		if (exists(src)) {
			fs.unlinkSync(src);
		}
    });
};

main = function() {
    var argv = require('optimist')
        .usage('USAGE: $0 [-c|--clean]')
        .boolean('c').alias('c', 'clean').describe('c', 'remove installed files')
        .argv;

    process.exit(argv.clean? clean() : build());
};

if (require.main === module) {
    main();
}

<commit_msg>build script installs python module as well<commit_after>#!/usr/bin/env node

var copy, sheetParserWrapper, installPythonPackage, build, clean, main,
    fs = require('fs'),
    read = function(filename) { return fs.readFileSync(filename, 'utf8'); },
    exec = require('child_process').exec,
    path = require('path'),
    join = path.join,
    resolve = path.resolve,
    basename = path.basename,
    dirname = path.dirname,
    exists = path.existsSync,
    moduleBase = resolve(join(__dirname, '..')),
    srcDir = join(moduleBase, 'src'),
    rjsDir = join(moduleBase, 'vendor/requirejs'),
    qunitDir = join(dirname(require.resolve('qunit')), 'deps/qunit/qunit'),
    sources = [
        join(rjsDir, 'require.js'),
        join(rjsDir, 'text.js'),
        join(rjsDir, 'order.js'),
        join(qunitDir, 'qunit.js'),
        join(qunitDir, 'qunit.css')
    ];

copy = function(fromPath, toPath) {
    fs.writeFileSync(toPath, fs.readFileSync(fromPath, 'utf8'));
};

sheetParserWrapper = function(js) {
    return ';(function() {\nvar require;\n' + js + '\n})();';
};

installPythonPackage = function(callback) {
    exec('python setup.py install', function(error, stdout, stderr) {
        process.stdout.write(stdout);
        process.stderr.write(stderr);
        callback(error, stdout, stderr);
    });
};

build = function(callback) {
    var sheetPath = dirname(require.resolve('Sheet')),
        sheetRegExPath = join(sheetPath, 'sg-regex-tools.js'),
        sheetParserPath = join(sheetPath, 'SheetParser.CSS.js');
    sources.forEach(function(src) {
        copy(src, join(srcDir, basename(src)));
    });

    fs.writeFileSync(
            'src/css.js',
            [
                read(sheetRegExPath),
                sheetParserWrapper(read(sheetParserPath)),
                read('lib/css_rewrite.js'),
                read('lib/css_requirejs_plugin.js')
            ].join(''));

    installPythonPackage(callback);
};

clean = function(callback) {
    sources.forEach(function(src) {
		src = join(srcDir, basename(src));
		if (exists(src)) {
			fs.unlinkSync(src);
		}
    });

    callback(0);
};

main = function() {
    var argv = require('optimist')
        .usage('USAGE: $0 [-c|--clean]')
        .boolean('c')
            .alias('c', 'clean')
            .describe('c', 'remove installed files')
        .argv;

    (argv.clean? clean : build)(process.exit);
};

if (require.main === module) {
    main();
}

<|endoftext|>


<commit_before>(function() {
    'use strict';

    angular
        .module('crowbarWidgets')
        .directive('suseModal', suseModal);

    var modalController = function ($uibModalInstance, $scope) {
        $scope.dismiss = function() {
            $uibModalInstance.dismiss();
        };
    };

    suseModal.$inject = ['$uibModal'];
    modalController.$inject = ['$uibModalInstance', '$scope'];

    function suseModal($uibModal) {
        return {
            restrict: 'E',
            scope: {
                error: '='
            },
            link: function (scope) {
                scope.$watch('error', function(value) {
                    if (angular.isDefined(value)) {
                        $uibModal.open({
                            templateUrl: 'app/widgets/suse-modal/suse-modal.directive.html',
                            controller: modalController,
                            scope: scope
                        });

                    }
                });
            }
        };
    }
})();
<commit_msg>upgrade:  group $injects with their respective controllers<commit_after>(function() {
    'use strict';

    angular
        .module('crowbarWidgets')
        .directive('suseModal', suseModal);

    suseModal.$inject = ['$uibModal'];

    var modalController = function ($uibModalInstance, $scope) {
        $scope.dismiss = function() {
            $uibModalInstance.dismiss();
        };
    };

    modalController.$inject = ['$uibModalInstance', '$scope'];

    function suseModal($uibModal) {
        return {
            restrict: 'E',
            scope: {
                error: '='
            },
            link: function (scope) {
                scope.$watch('error', function(value) {
                    if (angular.isDefined(value)) {
                        $uibModal.open({
                            templateUrl: 'app/widgets/suse-modal/suse-modal.directive.html',
                            controller: modalController,
                            scope: scope
                        });

                    }
                });
            }
        };
    }
})();
<|endoftext|>


<commit_before>// ==UserScript==
// @name         Duolingo - Lesson Review
// @description  This script allows you to go back and review all the different challenges in a lesson.
// @match        *://www.duolingo.com/*
// @author       HodofHod
// @namespace    HodofHod
// @version      0.1.4
// ==/UserScript==

//Beware all who enter here. This code may be hideous and worse.
//I am not responsible for any damage done to your eyes, or the ears of those around you.

//TODO: Inject a stylesheet and use classes instead of .css()
//      Maybe get really fancy, and hijack Session and SessionView, and switch lessons with .set({positon,x}) and .render()
//          Needs work. Doesn't save grading. Replays wrong audio.
//Fix: Switching from successview before graphs are loaded, breaks graphs, and logs infinite errors.
//          hide() instead of .detach() works, but it breaks sound and other things on non end lessons.
//

function inject() { //Inject the script into the document
	var script;
	for (var i = 0; i < arguments.length; i++) {
		if (typeof (arguments[i]) === 'function') {
			script = document.createElement('script');
			script.type = 'text/javascript';
			script.textContent = '(' + arguments[i].toString() + ')(jQuery)';
			document.head.appendChild(script);
		}
	}
}

inject(init);

function init(){
    console.log('Duolingo Lesson Review');
    var origNext         = duo.SessionView.prototype.next,
        origRendered     = duo.SessionView.prototype.rendered,
        origGraded       = duo.SessionView.prototype.graded,
        origShowFailView = duo.SessionView.prototype.showFailView,
        origShowEndView  = duo.SessionView.prototype.showEndView,
        origTimesUp      = duo.TimedSessionView.prototype.timesUp,
        origPushState    = window.history.pushState;

    if (/^\/practice|skill|word_practice\/.+/.test(window.location.pathname)){ //for the first time the script is loaded, check if we're already on a page.
        main();
    }
    window.history.pushState = function checkPage(a, b, c){  //Hijack the window's pushState so we know whenever duolingo navigates to a new page.
        /^\/practice|skill|word_practice\/.+/.test(c) ? main() : cleanup();
        return origPushState.call(this, a, b, c);
    };

    function cleanup(){
        $(document).off('.hh'); //unbind any handlers from previous lessons.
        $.extend(duo.SessionView.prototype, {
            next: origNext,
            rendered: origRendered,
            graded: origGraded,
            showFailView: origShowFailView,
            showEndView: origShowEndView
        });
        duo.TimedSessionView.prototype.timesUp = origTimesUp;
    }

    function main(){
        console.log('main');
        cleanup();
        add_arrows();

        var lessons = {},
            cur_lesson_id = 1,
            selected_lesson_id = cur_lesson_id,
            finished = false,
            failed = false,
            header, last_lesson_id,
            waiting_for_discussion_load = false,
            redesign = duo.user.get("ab_options").site_redesign_experiment ;

        function save_lesson(save_id){
            var app_class = $('#app').prop('class');
            lessons[save_id] = redesign ? [$('.player-container>footer, .player-main').clone(), $('#discussion-modal-container').clone(), $('#app').prop('class')]
                                        : [$('#app>div:not(.player-header)').clone(), $('#app').prop('class')];
        }

        function replace_lesson(replace_id){
            console.log('cur: ' + cur_lesson_id + ', old id: ' + selected_lesson_id + ', new id ' + replace_id);
            if (selected_lesson_id === replace_id){ return false; } //don't replace yourself with yourself.
            if (selected_lesson_id === cur_lesson_id || selected_lesson_id === 'end'){//if switching away from cur_lesson
                lessons[cur_lesson_id] = redesign ? [$('.player-container>footer, .player-main, #end-carousel').detach(), $('#discussion-modal-container').detach(), $('#app').prop('class')]
                                                  : [$('#app>div:not(.player-header)').detach(), $('#app').prop('class')];
            }else if (lessons[replace_id] === undefined){
                return false;
            }else{
                redesign ? $('#controls, .player-main, .discussion-modal-container').remove()
                		 : $('#app>div:not(.player-header)').remove();
            }

            var l = lessons[replace_id][0];
            $(redesign?'.player-container':'#app').append(l);
            redesign && $('body').append(lessons[replace_id][1]);
            $('#app').prop('class', lessons[replace_id][redesign+1]);//tricky trick
            bind_discussion_toggle();//TODO: Remove the other?

            $('.token-wrapper:has(.non-space)').hover(function(){
                var a = $(this).outerWidth(),
                    b = $(this).find('.hint-table').outerWidth(),
                    c = (b / 2) - (a / 2);
                $(this).css('position','relative').find('.hint-table').toggle().css({left: -1*c+'px', top: '15px'});
            });

            //Disable all controls except for discuss
            if (replace_id !== cur_lesson_id){//switching not to cur
                l.find('#next_button, #continue_button, #show-report-options, #discussion-modal textarea').attr('disabled', 'disabled');
                l.find('#submit_button, #skip_button, .twipsy\
                   #home-button, #fix_mistakes_button, #controls .tooltip').remove();
                l.find('.hint-table').hide();
                var resume_button = $('<button id="resume_button" class="btn success large right" tabindex="20">Resume</button>');
                l.find('#next_button, #continue_button, #retry-button').after(resume_button).remove();

                if (finished && !failed){
                    $(redesign?'.player-container':'#app').prepend(header);
                }
            }else if (finished && !failed){//if switching to end of successful lesson, remove header.
                $('.player-header').detach();
            }else{ $('.twipsy').hide(); }

            selected_lesson_id = replace_id;
            select_cell(selected_lesson_id);
        }

        function select_cell(cell_num){
            //TODO: For redesign: add the box-shadow to .inner only.
            //       Inherit b-color on .nothing, and reset on .done (red uses b-image, so it won't reset that);
            $('li[id^=element-]').find('.inner').andSelf().css({'box-shadow': '', 'border-right-width': '1px'});
            $('li#element-'+cell_num).find('.inner')
                                     .andSelf().css({'box-shadow': '1px 1px 3px 1px black inset',
                                                     'border-right-width': '0px'});
            $('li[id^=element-]:first-child').css({'-webkit-border-radius': '9px 0 0 9px',
                                                  'border-radius': '9px 0 0 9px'});
            $('li[id^=element-]:last-child').css({'-webkit-border-radius': '0 9px 9px 0',
                                                  'border-radius': '0 9px 9px 0'});
            activate_arrows();
        }

        //Hijack the rendered() function (it runs after each problem is rendered)
        duo.SessionView.prototype.rendered = function newRendered(){
            console.log('render');
            if ($('#prev-arrow, #next-arrow').length !== 2){
                add_arrows();
            }
            select_cell(selected_lesson_id);
            return origRendered.apply(this, arguments);
        };

        //Hijack the function that switches lessons.
        duo.SessionView.prototype.next = function newNext(){
            if ($('#discussion-toggle').size() && !$("#discussion-modal").size()){
                waiting_for_discussion_load = true;
                console.log('wait for discussions to finish loading');
            } else {
                cur_lesson_id = (this.model.get('position') + 1); //0-indexed
                save_lesson(cur_lesson_id);
                cur_lesson_id += 1;
                selected_lesson_id = cur_lesson_id;
                return origNext.apply(this, arguments);
            }
        };

        duo.SessionView.prototype.graded = function(){
            var solution = this.model.getSubmittedSolution();
            origGraded.apply(this, arguments);
            if (solution.get('incorrect') && !solution.get('try-again')){
                $('li#element-' + cur_lesson_id + '>.inner').andSelf()
                    .css('background-image', '-webkit-linear-gradient(top, #EE6969, #FF0000)')
                    .css('background-image', '-moz-linear-gradient(center top , #EE6969, #FF0000)')
                    .css(redesign ? {} : {'border': '1px solid #950000', 'border-left':'none'});
            }
        }
        function bind_discussion_toggle(){
            $('#discussion-toggle').on('click', function(e){
                e.stopImmediatePropagation();
                $('#discussion-modal').modal('show');
                $('.close-modal-background, .modal-backdrop, .player-discussion>.close').on('click', function(){
                    $('#discussion-modal').modal('hide');
                });
            });
        }

        //Load the discussions so they're saved by the cloning.
        document.body.addEventListener('DOMNodeInserted', function (e) {
            if (redesign ? $(e.target).has('#discussion-toggle').length && e.target.id !== 'controls'
                           : e.target.id === 'discussion-modal-container'){
                $('').appendTo("head");
                $('#discussion-modal-container').hide();
                console.log('load discussions');
                $('#discussion-toggle').click();
                bind_discussion_toggle();
                document.body.addEventListener('DOMNodeInserted', function wait() {
                    if  ($('.modal-backdrop.in').length){
                        $('#discussion-modal').modal('toggle')
                        $('#discussion-modal-container').show();

                        $('.modal-backdrop.in, #modalcss').remove();
                        if (waiting_for_discussion_load === true){
                            waiting_for_discussion_load = false;
                            $('#next_button, #continue_button').click();
                        }
                        document.body.removeEventListener('DOMNodeInserted', wait);
                    }
                });
            }else if (finished && $(e.target).has('.session-end-footer').length){
                finish();
            }
        });

        $(document).on('click.hh', 'li[id^=element-]', function(){
            var clicked_id = parseInt(this.id.replace('element-', ''), 10);
            if (lessons[clicked_id] !== undefined){
                if (failed){ console.log('fail click'); $('#review_button').click(); }
                replace_lesson(clicked_id);
            }
        });

        $(document).on('click.hh', '#resume_button', function(){
            replace_lesson(cur_lesson_id);
            //TODO: See if this is necessary after the redesign.
            if (finished && ($('#end-carousel .left').length > 1 || !$('#end-carousel .active').length)){
                $('#end-carousel .item').removeClass('active next left');
                $('#end-carousel .item:eq(' +$('.' + (redesign ? 'carousel-' : '') + 'dots .active').data('slide')+ ')').addClass('active');
                $('#end-carousel .item.active').is(':last-child') && $(".carousel").carousel("pause");
            }
        });

        function finish(){
            var button = $('<button id="review_button" class="btn large btn-lg btn-standard right" style="margin:0 10px 0 0;">Review</button>'),
                loc = failed ? '#controls>' + (redesign ? '.col-right' : '.row>.right')
                             : (redesign ? '.session-end-footer' : 'div.buttons');
            failed ? $(loc).append(button) : $(loc).prepend(button);//Redesign CAN both be prepend
            !failed && redesign ? button.css({position:'absolute', left:'40px'})
                                : button.css('float','left');
            finished = true;
            last_lesson_id = cur_lesson_id;
            cur_lesson_id = 'end';
            selected_lesson_id = 'end';
            select_cell();//none
        }

        duo.SessionView.prototype.showFailView = function(){
            if ($('#discussion-toggle').size() && !$("#discussion-modal").size()){
                console.log('wait for discussions to finish loading');
                waiting_for_discussion_load = true;//TODO
            }
            save_lesson(cur_lesson_id);
            r = origShowFailView.apply(this, arguments);
            failed = true;
            finish();
            $('.close-fail').hide();
            add_arrows();
            return r;
        };

        duo.SessionView.prototype.showEndView = function(){
            //Timed out lessons don't need this. Timeouts will set finished to true earlier.
            cur_lesson_id -= !finished; //newNext() increments on the last one too, adjust for that.
            header = $('.player-header').detach();//TODO: Improve?
            origShowEndView.apply(this, arguments);
            finished = true; //TODO FIX THIS ONCE THE MIGRATION TO THE REDESIGN IS COMPLETE.
            //finish(); end view won't fully load by here. Instead, I'm adding an else if to the DOMNodeInserted binding
        };

        duo.TimedSessionView.prototype.timesUp = function(){
            save_lesson(cur_lesson_id);
            finished = true;
            origTimesUp.apply(this, arguments);
        }

        $(document).on('click.hh', '#review_button', function(){
            if (failed){
                save_lesson('end');
            }
            replace_lesson(last_lesson_id);
        });

        $(document).on('click.hh', '#prev-arrow, #next-arrow', function(){
            lesson_id = (this.id === 'prev-arrow') ? selected_lesson_id-1 : selected_lesson_id+1;
            if (selected_lesson_id === 'end'){
                $('#review_button').click();
            }else if (!lessons[lesson_id] && cur_lesson_id === 'end'){
                replace_lesson('end');
            }else{
                replace_lesson(lesson_id);
            }
        });

        function add_arrows(){
            $('#prev-arrow, #next-arrow').remove();//precaution
            arrow = $('').css({
                background: 'url("//d7mj4aqfscim2.cloudfront.net/images/sprite_mv_082bd900117422dec137f596afcc1708.png") no-repeat',
                width: '24px', height: '18px', float: 'left', 'pointer-events':'none', 'background-position': '-323px -130px'
            });

            arrow.clone().attr('id', 'prev-arrow').css({margin: redesign ? '31px 0 0 -15px' : '22px -30px 0 5px',
                     transform: 'rotate(-90deg)',
                     '-webkit-transform': 'rotate(-90deg)'
            }).prependTo('#progress-bar');

            arrow.clone().attr('id', 'next-arrow').css({margin: redesign ? '31px 0 0 0' : '22px 0 0 4px',
                     transform: 'rotate(90deg)',
                     '-webkit-transform': 'rotate(90deg)'
            }).appendTo('#progress-bar');

            activate_arrows();
        }

        function activate_arrows(){
            selected_lesson_id > 1 || selected_lesson_id === 'end' ? activate($('#prev-arrow')) : deactivate($('#prev-arrow'));
            selected_lesson_id !== cur_lesson_id ? activate($('#next-arrow')) : deactivate($('#next-arrow'));
            function activate($arrow){
                $arrow.css({'background-position':'-323px -178px',cursor:'pointer','pointer-events':''});
            }
            function deactivate($arrow){
                $arrow.css({'background-position':'-323px -130px',cursor:'initial','pointer-events':'none'});
            }
        }
    }
}
<commit_msg>Prep for duo-mods<commit_after>// ==UserScript==
// @name         Duolingo - Lesson Review
// @description  This script allows you to go back and review all the different challenges in a lesson.
// @match        *://www.duolingo.com/*
// @author       HodofHod
// @namespace    HodofHod
// @version      0.1.5
// ==/UserScript==

//Beware all who enter here. This code may be hideous and worse.
//I am not responsible for any damage done to your eyes, or the ears of those around you.

//TODO: Inject a stylesheet and use classes instead of .css()
//      Maybe get really fancy, and hijack Session and SessionView, and switch lessons with .set({positon,x}) and .render()
//          Needs work. Doesn't save grading. Replays wrong audio.
//Fix: Switching from successview before graphs are loaded, breaks graphs, and logs infinite errors.
//          hide() instead of .detach() works, but it breaks sound and other things on non end lessons.
//

function inject(f) { //Inject the script into the document
    var script;
    script = document.createElement('script');
    script.type = 'text/javascript';
    script.name = 'lesson_review';
    script.textContent = '(' + f.toString() + ')(jQuery)';
    document.head.appendChild(script);
}

inject(init);

function init(){
    console.log('Duolingo Lesson Review');
    var origNext         = duo.SessionView.prototype.next,
        origRendered     = duo.SessionView.prototype.rendered,
        origGraded       = duo.SessionView.prototype.graded,
        origShowFailView = duo.SessionView.prototype.showFailView,
        origShowEndView  = duo.SessionView.prototype.showEndView,
        origTimesUp      = duo.TimedSessionView.prototype.timesUp,
        origPushState    = window.history.pushState;

    if (/^\/practice|skill|word_practice\/.+/.test(window.location.pathname)){ //for the first time the script is loaded, check if we're already on a page.
        main();
    }
    window.history.pushState = function checkPage(a, b, c){  //Hijack the window's pushState so we know whenever duolingo navigates to a new page.
        /^\/practice|skill|word_practice\/.+/.test(c) ? main() : cleanup();
        return origPushState.call(this, a, b, c);
    };

    function cleanup(){
        $(document).off('.hh'); //unbind any handlers from previous lessons.
        $.extend(duo.SessionView.prototype, {
            next: origNext,
            rendered: origRendered,
            graded: origGraded,
            showFailView: origShowFailView,
            showEndView: origShowEndView
        });
        duo.TimedSessionView.prototype.timesUp = origTimesUp;
    }

    function main(){
        console.log('main');
        cleanup();
        add_arrows();

        var lessons = {},
            cur_lesson_id = 1,
            selected_lesson_id = cur_lesson_id,
            finished = false,
            failed = false,
            header, last_lesson_id,
            waiting_for_discussion_load = false,
            redesign = duo.user.get("ab_options").site_redesign_experiment ;

        function save_lesson(save_id){
            var app_class = $('#app').prop('class');
            lessons[save_id] = redesign ? [$('.player-container>footer, .player-main').clone(), $('#discussion-modal-container').clone(), $('#app').prop('class')]
                                        : [$('#app>div:not(.player-header)').clone(), $('#app').prop('class')];
        }

        function replace_lesson(replace_id){
            console.log('cur: ' + cur_lesson_id + ', old id: ' + selected_lesson_id + ', new id ' + replace_id);
            if (selected_lesson_id === replace_id){ return false; } //don't replace yourself with yourself.
            if (selected_lesson_id === cur_lesson_id || selected_lesson_id === 'end'){//if switching away from cur_lesson
                lessons[cur_lesson_id] = redesign ? [$('.player-container>footer, .player-main, #end-carousel').detach(), $('#discussion-modal-container').detach(), $('#app').prop('class')]
                                                  : [$('#app>div:not(.player-header)').detach(), $('#app').prop('class')];
            }else if (lessons[replace_id] === undefined){
                return false;
            }else{
                redesign ? $('#controls, .player-main, .discussion-modal-container').remove()
                		 : $('#app>div:not(.player-header)').remove();
            }

            var l = lessons[replace_id][0];
            $(redesign?'.player-container':'#app').append(l);
            redesign && $('body').append(lessons[replace_id][1]);
            $('#app').prop('class', lessons[replace_id][redesign+1]);//tricky trick
            bind_discussion_toggle();//TODO: Remove the other?

            $('.token-wrapper:has(.non-space)').hover(function(){
                var a = $(this).outerWidth(),
                    b = $(this).find('.hint-table').outerWidth(),
                    c = (b / 2) - (a / 2);
                $(this).css('position','relative').find('.hint-table').toggle().css({left: -1*c+'px', top: '15px'});
            });

            //Disable all controls except for discuss
            if (replace_id !== cur_lesson_id){//switching not to cur
                l.find('#next_button, #continue_button, #show-report-options, #discussion-modal textarea').attr('disabled', 'disabled');
                l.find('#submit_button, #skip_button, .twipsy\
                   #home-button, #fix_mistakes_button, #controls .tooltip').remove();
                l.find('.hint-table').hide();
                var resume_button = $('<button id="resume_button" class="btn success large right" tabindex="20">Resume</button>');
                l.find('#next_button, #continue_button, #retry-button').after(resume_button).remove();

                if (finished && !failed){
                    $(redesign?'.player-container':'#app').prepend(header);
                }
            }else if (finished && !failed){//if switching to end of successful lesson, remove header.
                $('.player-header').detach();
            }else{ $('.twipsy').hide(); }

            selected_lesson_id = replace_id;
            select_cell(selected_lesson_id);
        }

        function select_cell(cell_num){
            //TODO: For redesign: add the box-shadow to .inner only.
            //       Inherit b-color on .nothing, and reset on .done (red uses b-image, so it won't reset that);
            $('li[id^=element-]').find('.inner').andSelf().css({'box-shadow': '', 'border-right-width': '1px'});
            $('li#element-'+cell_num).find('.inner')
                                     .andSelf().css({'box-shadow': '1px 1px 3px 1px black inset',
                                                     'border-right-width': '0px'});
            $('li[id^=element-]:first-child').css({'-webkit-border-radius': '9px 0 0 9px',
                                                  'border-radius': '9px 0 0 9px'});
            $('li[id^=element-]:last-child').css({'-webkit-border-radius': '0 9px 9px 0',
                                                  'border-radius': '0 9px 9px 0'});
            activate_arrows();
        }

        //Hijack the rendered() function (it runs after each problem is rendered)
        duo.SessionView.prototype.rendered = function newRendered(){
            console.log('render');
            if ($('#prev-arrow, #next-arrow').length !== 2){
                add_arrows();
            }
            select_cell(selected_lesson_id);
            return origRendered.apply(this, arguments);
        };

        //Hijack the function that switches lessons.
        duo.SessionView.prototype.next = function newNext(){
            if ($('#discussion-toggle').size() && !$("#discussion-modal").size()){
                waiting_for_discussion_load = true;
                console.log('wait for discussions to finish loading');
            } else {
                cur_lesson_id = (this.model.get('position') + 1); //0-indexed
                save_lesson(cur_lesson_id);
                cur_lesson_id += 1;
                selected_lesson_id = cur_lesson_id;
                return origNext.apply(this, arguments);
            }
        };

        duo.SessionView.prototype.graded = function(){
            var solution = this.model.getSubmittedSolution();
            origGraded.apply(this, arguments);
            if (solution.get('incorrect') && !solution.get('try-again')){
                $('li#element-' + cur_lesson_id + '>.inner').andSelf()
                    .css('background-image', '-webkit-linear-gradient(top, #EE6969, #FF0000)')
                    .css('background-image', '-moz-linear-gradient(center top , #EE6969, #FF0000)')
                    .css(redesign ? {} : {'border': '1px solid #950000', 'border-left':'none'});
            }
        }
        function bind_discussion_toggle(){
            $('#discussion-toggle').on('click', function(e){
                e.stopImmediatePropagation();
                $('#discussion-modal').modal('show');
                $('.close-modal-background, .modal-backdrop, .player-discussion>.close').on('click', function(){
                    $('#discussion-modal').modal('hide');
                });
            });
        }

        //Load the discussions so they're saved by the cloning.
        document.body.addEventListener('DOMNodeInserted', function (e) {
            if (redesign ? $(e.target).has('#discussion-toggle').length && e.target.id !== 'controls'
                           : e.target.id === 'discussion-modal-container'){
                $('').appendTo("head");
                $('#discussion-modal-container').hide();
                console.log('load discussions');
                $('#discussion-toggle').click();
                bind_discussion_toggle();
                document.body.addEventListener('DOMNodeInserted', function wait() {
                    if  ($('.modal-backdrop.in').length){
                        $('#discussion-modal').modal('toggle')
                        $('#discussion-modal-container').show();

                        $('.modal-backdrop.in, #modalcss').remove();
                        if (waiting_for_discussion_load === true){
                            waiting_for_discussion_load = false;
                            $('#next_button, #continue_button').click();
                        }
                        document.body.removeEventListener('DOMNodeInserted', wait);
                    }
                });
            }else if (finished && $(e.target).has('.session-end-footer').length){
                finish();
            }
        });

        $(document).on('click.hh', 'li[id^=element-]', function(){
            var clicked_id = parseInt(this.id.replace('element-', ''), 10);
            if (lessons[clicked_id] !== undefined){
                if (failed){ console.log('fail click'); $('#review_button').click(); }
                replace_lesson(clicked_id);
            }
        });

        $(document).on('click.hh', '#resume_button', function(){
            replace_lesson(cur_lesson_id);
            //TODO: See if this is necessary after the redesign.
            if (finished && ($('#end-carousel .left').length > 1 || !$('#end-carousel .active').length)){
                $('#end-carousel .item').removeClass('active next left');
                $('#end-carousel .item:eq(' +$('.' + (redesign ? 'carousel-' : '') + 'dots .active').data('slide')+ ')').addClass('active');
                $('#end-carousel .item.active').is(':last-child') && $(".carousel").carousel("pause");
            }
        });

        function finish(){
            var button = $('<button id="review_button" class="btn large btn-lg btn-standard right" style="margin:0 10px 0 0;">Review</button>'),
                loc = failed ? '#controls>' + (redesign ? '.col-right' : '.row>.right')
                             : (redesign ? '.session-end-footer' : 'div.buttons');
            failed ? $(loc).append(button) : $(loc).prepend(button);//Redesign CAN both be prepend
            !failed && redesign ? button.css({position:'absolute', left:'40px'})
                                : button.css('float','left');
            finished = true;
            last_lesson_id = cur_lesson_id;
            cur_lesson_id = 'end';
            selected_lesson_id = 'end';
            select_cell();//none
        }

        duo.SessionView.prototype.showFailView = function(){
            if ($('#discussion-toggle').size() && !$("#discussion-modal").size()){
                console.log('wait for discussions to finish loading');
                waiting_for_discussion_load = true;//TODO
            }
            save_lesson(cur_lesson_id);
            r = origShowFailView.apply(this, arguments);
            failed = true;
            finish();
            $('.close-fail').hide();
            add_arrows();
            return r;
        };

        duo.SessionView.prototype.showEndView = function(){
            //Timed out lessons don't need this. Timeouts will set finished to true earlier.
            cur_lesson_id -= !finished; //newNext() increments on the last one too, adjust for that.
            header = $('.player-header').detach();//TODO: Improve?
            origShowEndView.apply(this, arguments);
            finished = true; //TODO FIX THIS ONCE THE MIGRATION TO THE REDESIGN IS COMPLETE.
            //finish(); end view won't fully load by here. Instead, I'm adding an else if to the DOMNodeInserted binding
        };

        duo.TimedSessionView.prototype.timesUp = function(){
            save_lesson(cur_lesson_id);
            finished = true;
            origTimesUp.apply(this, arguments);
        }

        $(document).on('click.hh', '#review_button', function(){
            if (failed){
                save_lesson('end');
            }
            replace_lesson(last_lesson_id);
        });

        $(document).on('click.hh', '#prev-arrow, #next-arrow', function(){
            lesson_id = (this.id === 'prev-arrow') ? selected_lesson_id-1 : selected_lesson_id+1;
            if (selected_lesson_id === 'end'){
                $('#review_button').click();
            }else if (!lessons[lesson_id] && cur_lesson_id === 'end'){
                replace_lesson('end');
            }else{
                replace_lesson(lesson_id);
            }
        });

        function add_arrows(){
            $('#prev-arrow, #next-arrow').remove();//precaution
            arrow = $('').css({
                background: 'url("//d7mj4aqfscim2.cloudfront.net/images/sprite_mv_082bd900117422dec137f596afcc1708.png") no-repeat',
                width: '24px', height: '18px', float: 'left', 'pointer-events':'none', 'background-position': '-323px -130px'
            });

            arrow.clone().attr('id', 'prev-arrow').css({margin: redesign ? '31px 0 0 -15px' : '22px -30px 0 5px',
                     transform: 'rotate(-90deg)',
                     '-webkit-transform': 'rotate(-90deg)'
            }).prependTo('#progress-bar');

            arrow.clone().attr('id', 'next-arrow').css({margin: redesign ? '31px 0 0 0' : '22px 0 0 4px',
                     transform: 'rotate(90deg)',
                     '-webkit-transform': 'rotate(90deg)'
            }).appendTo('#progress-bar');

            activate_arrows();
        }

        function activate_arrows(){
            selected_lesson_id > 1 || selected_lesson_id === 'end' ? activate($('#prev-arrow')) : deactivate($('#prev-arrow'));
            selected_lesson_id !== cur_lesson_id ? activate($('#next-arrow')) : deactivate($('#next-arrow'));
            function activate($arrow){
                $arrow.css({'background-position':'-323px -178px',cursor:'pointer','pointer-events':''});
            }
            function deactivate($arrow){
                $arrow.css({'background-position':'-323px -130px',cursor:'initial','pointer-events':'none'});
            }
        }
    }
}
<|endoftext|>


<commit_before>var Twit = require('twit')
var Const = require('./constants');

var T = new Twit({
    consumer_key:         Const.CONSTANTS['consumer_key']
  , consumer_secret:      Const.CONSTANTS['consumer_secret']
  , access_token:         Const.CONSTANTS['access_token']
  , access_token_secret:  Const.CONSTANTS['access_token_secret']
})

/*
 * Gives the time since the tweet was posted
 * @params	tweet	array		json formated tweet
 * @return	diff	int			time since the tweet was posted (in seconds)
 */
function timeSincePosted(tweet) {
    var tweet_date = new Date(Date.parse(tweet.created_at));
    var now = new Date();

    var diff = Math.floor((now - tweet_date) / 1000);

    return diff;
}

function myCallback(err, data, response) {
	// console.log(JSON.stringify(data, null, 2));

	for (var tweetNb in data) {
		var tweet = data[tweetNb];
		var id = tweet.id;
		var userName = tweet.user.screen_name;
		var creationDate = tweet.created_at;

		lastId = (lastId < id) ? id : lastId;

		// We want to treat only recent posts
		if (timeSincePosted(tweet) < 5*60) {

			console.log("Le tweet de @" + userName + " a t post il y a " + timeSincePosted(tweet) + " secondes. Son id est " + id + " et lastId est " + lastId);
			// console.log ("L'utilisateur qui a envoy le tweet, c'est @" + userName);

			// T.post('statuses/update', { status: 'Salut @'+userName+', merci de ta charmante attention !' }, function(err, data, response) {
			  // console.log(data)
			// })

		}
	}
}

lastId = 0;

// Every 60s
setInterval( function(){
	// Get statuses

	if (lastId == 0)
		T.get('statuses/mentions_timeline', { count: '5' }, myCallback);
	else
		T.get('statuses/mentions_timeline', { count: '5', since_id: lastId }, myCallback);
}, (60*1000))

<commit_msg>Feature: sends request to brewerydb (static for now)<commit_after>var Twit = require('twit')
var Const = require('./constants');
var http = require('http');

var T = new Twit({
    consumer_key:         Const.CONSTANTS['consumer_key']
  , consumer_secret:      Const.CONSTANTS['consumer_secret']
  , access_token:         Const.CONSTANTS['access_token']
  , access_token_secret:  Const.CONSTANTS['access_token_secret']
})

/*
 * Gives the time since the tweet was posted
 * @params	tweet	array		json formated tweet
 * @return	diff	int			time since the tweet was posted (in seconds)
 */
function timeSincePosted(tweet) {
    var tweet_date = new Date(Date.parse(tweet.created_at));
    var now = new Date();

    var diff = Math.floor((now - tweet_date) / 1000);

    return diff;
}

/*
 * Send a GET request to brewrydb
 * @params	path	string		the path of the request
 * 			data	array		the parameters of the request
 */
function getRequest(path, data) {

	// Formating the parameters into the path
	var fullpath = path + "?";
	Object.keys(data).forEach(function(key){
		fullpath += key + "=" + data[key] + "&";
	});
	fullpath = fullpath.substring(0, fullpath.length - 1);

	// Setting request options
	var options = {
		  host : 'api.brewerydb.com', // here only the domain name
		  // (no http/https !)
		  port : 80,
		  path : fullpath, // the rest of the url with parameters if needed
		  method : 'GET', // do POST
	};

	// Set up the request
	var get_req = http.request(options, function(res) {
		res.setEncoding('utf8');
		res.on('data', function (res) {
			// here a callback instead of log
			console.log(res);
		});
	});

	// post the data
	get_req.write(JSON.stringify(data));
	get_req.end();

}

/*
 * Get last mentions and treat them
 */
function getAndTreatMentions(err, data, response) {
	// console.log(JSON.stringify(data, null, 2));

	// for each mentions
	for (var tweetNb = 0; tweetNb < data.length; tweetNb++) {
		// get informations
		var tweet = data[tweetNb];
		var id = tweet.id;
		var userName = tweet.user.screen_name;
		var creationDate = tweet.created_at;

		// We want to treat only recent posts
		if ((timeSincePosted(tweet) < 5*60) && (id != lastId)) {

			console.log("Le tweet de @" + userName + " a t post il y a " + timeSincePosted(tweet) + " secondes. Son id est " + id + " et lastId est " + lastId);

			var Const = require('./constants');
			var data = {
				"key" 		: Const.CONSTANTS['brewerydb_key'],
				"type" 		: "beer",
				"q" 		: "aventinus"
			};

			getRequest('/v2/search', data);
		}
		// For not getting oldest mentions
		lastId = (lastId < id) ? id : lastId;
	}
}

// needs to be global for the callback
lastId = 0;

// Every 60s
setInterval( function(){
	// Get statuses
	if (lastId == 0) {
		console.log("pas de lastid");
		T.get('statuses/mentions_timeline', { count: '5' }, getAndTreatMentions);
	}
	else {
		console.log("lastid = " + lastId);
		T.get('statuses/mentions_timeline', { count: '5', since_id: lastId }, getAndTreatMentions);
	}
}, (6*1000))

<|endoftext|>