<commit_before>/**
 * Copyright 2015-2019 OpenCB
 *
 * 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.
 */

import {LitElement, html} from "lit";
import UtilsNew from "../../core/utilsNew.js";
import "./file-view.js";
import "../loading-spinner.js";

export default class FileManager extends LitElement {

    constructor() {
        super();
        this._init();
    }

    createRenderRoot() {
        return this;
    }

    static get properties() {
        return {
            opencgaSession: {
                type: Object
            },
            root: {
                type: String
            },
            config: {
                type: Object
            }
        };
    }

    _init() {
        this.currentRootId = ":";

        this.tree = null;
        this.fileId = null;
        this.loading = false;
    }

    connectedCallback() {
        super.connectedCallback();
        this._config = {...this.getDefaultConfig(), ...this.config};
    }

    async updated(changedProperties) {
        if (changedProperties.has("opencgaSession")) {
            this.loading = true;
            this.currentRoot = null;
            this.requestUpdate();
            await this.updateComplete;
            this.opencgaSession.opencgaClient.files().tree(this.currentRootId, {study: this.opencgaSession.study.fqn, maxDepth: 1, include: "id,name,path,size,format"})
                .then(restResponse => {
                    this.errorState = false;
                    this.tree = restResponse.getResult(0);
                    this.tree.visited = true;
                    this.currentRoot = this.tree;
                    this.requestUpdate();
                })
                .catch(restResponse => {
                    this.currentRoot = null;
                    if (restResponse.getEvents?.("ERROR")?.length) {
                        this.errorState = restResponse.getEvents("ERROR").map(error => error.message).join("
");
                    } else {
                        this.errorState = "Server Error";
                    }
                    console.error(restResponse);
                })
                .finally(() => {
                    this.loading = false;
                    this.requestUpdate();
                });
        }
        if (changedProperties.has("config")) {
            this._config = {...this.getDefaultConfig(), ...this.config};
        }
    }

    async fetchFolder(node) {
        try {
            // console.log("FETCHING", node)
            if (!node.visited) {
                const restResponse = await this.opencgaSession.opencgaClient.files().tree(node.file.id, {study: this.opencgaSession.study.fqn, maxDepth: 1, include: "id,name,path,size,format"});
                const result = restResponse.getResult(0);
                node.children = result.children;
                node.visited = true;
            }
            this.errorState = false;
            // console.log("current root", this.currentRoot)
            this.requestUpdate();
        } catch (restResponse) {
            if (restResponse.getEvents?.("ERROR")?.length) {
                this.errorState = restResponse.getEvents("ERROR").map(error => error.message).join("
");
            } else {
                this.errorState = "Server Error";
            }
            console.error(restResponse);
        }

    }

    searchNode(nodeId, baseNode) {
        // console.log(`searching [${nodeId}]`)
        if (nodeId === ":") {
            return this.tree;
        }
        if (baseNode.file.id === nodeId) {
            return baseNode;
        }
        if (baseNode.file.type.toUpperCase() === "DIRECTORY") {
            for (const f of baseNode.children) {
                const r = this.searchNode(nodeId, f);
                if (r) return r;
            }
        }

        // console.error("Node not found!", nodeId);
        // return this.tree;
    }

    renderFileManager(root) {
        const children = root.children;
        return html`
            ${this.path(root)}

                ${children.map(node => {
                    if (node.file.type.toUpperCase() === "DIRECTORY") {
                        return html`${this.folder(node)}`;
                    } else if (node.file.type.toUpperCase() === "FILE") {
                        return html`${this.file(node)}`;
                    } else {
                        throw new Error("Type not recognized " + node.file.type);
                    }
                })}

        `;
    }

    renderTree(root) {
        const children = root.children;
        const domId = `tree-${root.file.id.replace(/:/g, "")}`;
        return html`
            ${root.file.name !== "." ? html`
                     this.toggleFolder(domId, root)}" class="fas fa-angle-${root.exploded ? "down" : "right"}">
                     this.toggleFolder(domId, root)}"> ${root.file.name}
                ` : html`
                      Home`}

                ${children.map(node => {
                    if (node.file.type === "DIRECTORY") {
                        return html`

                                ${this.renderTree(node)}

`;
                    } else if (node.file.type.toUpperCase() === "FILE") {
                        return html`

 this.onClickFile(node.file.id)}">
                                ${this.icon(node.file.format)} ${node.file.name}

`;
                    } else {
                        throw new Error("Type not recognized " + node.file.type);
                    }
                })}

        `;
    }

    async toggleFolder(domId, node) {
        // check for the root
        if (domId !== "tree-") {
            if (!node.exploded) {
                /* node.exploded = true;
                if(!node.visited) {
                    await this.fetchFolder(node);
                }
                $("." + domId + "").addClass("exploded")*/
                await this.route(node.file.id);
            } else {
                node.exploded = false;
                $("." + domId + "").removeClass("exploded");
            }
            // console.log("toggle" + node.file.id);
            // console.log($("." + domId + " ul"));

        } else {
            console.error("no id!");
        }
        // $("." + id + " + ul").slideToggle();
        this.requestUpdate();
        await this.updateComplete;
    }

    icon(format, size) {
        const icon = {
            IMAGE: "fas fa-file-image",
            VCF: "fas fa-file"
        }[format];
        return html`<i class="${icon || "fas fa-file"}${size ? ` fa-${size}x` : ""}"></i>`;
    }

    folder(node) {
        return html`

                 this.route(node.file.id)}">

                         ${node.file.name}

        `;
    }

    file(node) {
        return html`

                 this.onClickFile(node.file.id)}">
                    ${this.icon(node.file.format, 4)}${node.file.format !== "UNKNOWN" ? node.file.format : ""}

                                ${node.file.name}

                        ${UtilsNew.getDiskUsage(node.file.size)}

        `;
    }

    path(node) {
        const path = node.file.id.split(":").filter(Boolean);
        return html`

                 ~  /
                ${path.map((name, i) => html`<a @click="${() => this.route(path.slice(0, i + 1).join(":") + ":")}"> ${name} </a> <span class="path-separator">/</span>`)}

`;
    }

    async route(id, resetFileId = true) {
        // console.log("route", id)
        this.currentRoot = this.searchNode(id, this.tree);
        // console.log("setting currentRoute", this.currentRoot)

        this.currentRoot.exploded = true;

        if (!this.currentRoot.visited) {
            await this.fetchFolder(this.currentRoot);
        } else {
            console.log("node already visited", this.currentRoot);
        }
        const domId = `tree-${id.replace(/:/g, "")}`;
        $("." + domId + "").addClass("exploded");
        if (resetFileId) {
            this.fileId = null;
        }
        this.requestUpdate();
    }

    reset() {
        this.currentRoot = this.tree;
        this.fileId = null;
        this.requestUpdate();
    }

    onClickFile(id) {
        const path = id.split(":").slice(0, -1).join(":") + ":";
        // console.log("PATH", path)
        this.fileId = id;
        this.route(path, false);
        this.requestUpdate();
    }

    render() {
        return html`

                <tool-header title="${this._config.title}" icon="${this._config.icon}"></tool-header>

                        ${this.tree ? html`${this.renderTree(this.tree)}` : null}

                    ${this.errorState ? html`

                            ${this.errorState}

                    ` : null}
                    ${this.loading ? html`

                            <loading-spinner></loading-spinner>

                    ` : null}
                    ${this.currentRoot ? html`

                            ${this.renderFileManager(this.currentRoot)}

                            ${this.fileId ? html`

                                    <file-view
                                        .opencgaSession="${this.opencgaSession}"
                                        .fileId="${this.fileId}"
                                        mode="full">
                                    </file-view>

                            ` : null}
                        ` : null}

        `;
    }

    getDefaultConfig() {
        return {
            title: "File Explorer",
            icon: "img/tools/icons/file_explorer.svg"
        };
    }

}

customElements.define("file-manager", FileManager);
<commit_msg>wc: minor fixes in file-manager #TASK-1364<commit_after>/**
 * Copyright 2015-2019 OpenCB
 *
 * 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.
 */

import {LitElement, html} from "lit";
import UtilsNew from "../../core/utilsNew.js";
import "./file-view.js";
import "../loading-spinner.js";

export default class FileManager extends LitElement {

    constructor() {
        super();
        this._init();
    }

    createRenderRoot() {
        return this;
    }

    static get properties() {
        return {
            opencgaSession: {
                type: Object
            },
            root: {
                type: String
            },
            config: {
                type: Object
            }
        };
    }

    _init() {
        this.currentRootId = ":";

        this.tree = null;
        this.fileId = null;
        this.loading = false;

        this._config = this.getDefaultConfig();
    }

    update(changedProperties) {
        if (changedProperties.has("opencgaSession")) {
            this.opencgaSessionObserver();
        }

        if (changedProperties.has("config")) {
            this._config = {
                ...this.getDefaultConfig(),
                ...this.config,
            };
        }

        super.update(changedProperties);
    }

    opencgaSessionObserver() {
        this.currentRoot = null;
        if (this.opencgaSession) {
            this.loading = true;
            const query = {
                study: this.opencgaSession.study.fqn,
                maxDepth: 1,
                include: "id,name,path,size,format",
            };
            this.opencgaSession.opencgaClient.files().tree(this.currentRootId, query)
                .then(restResponse => {
                    this.errorState = false;
                    this.tree = restResponse.getResult(0);
                    this.tree.visited = true;
                    this.currentRoot = this.tree;
                    this.requestUpdate();
                })
                .catch(restResponse => {
                    this.currentRoot = null;
                    if (restResponse.getEvents?.("ERROR")?.length) {
                        this.errorState = restResponse.getEvents("ERROR").map(error => error.message).join("
");
                    } else {
                        this.errorState = "Server Error";
                    }
                    console.error(restResponse);
                })
                .finally(() => {
                    this.loading = false;
                    this.requestUpdate();
                });
        }
    }

    async fetchFolder(node) {
        try {
            if (!node.visited) {
                const restResponse = await this.opencgaSession.opencgaClient.files().tree(node.file.id, {study: this.opencgaSession.study.fqn, maxDepth: 1, include: "id,name,path,size,format"});
                const result = restResponse.getResult(0);
                node.children = result.children;
                node.visited = true;
            }
            this.errorState = false;
            this.requestUpdate();
        } catch (restResponse) {
            if (restResponse.getEvents?.("ERROR")?.length) {
                this.errorState = restResponse.getEvents("ERROR").map(error => error.message).join("
");
            } else {
                this.errorState = "Server Error";
            }
            console.error(restResponse);
        }

    }

    searchNode(nodeId, baseNode) {
        if (nodeId === ":") {
            return this.tree;
        }
        if (baseNode.file.id === nodeId) {
            return baseNode;
        }
        if (baseNode.file.type.toUpperCase() === "DIRECTORY") {
            for (const f of baseNode.children) {
                const r = this.searchNode(nodeId, f);
                if (r) return r;
            }
        }
    }

    renderFileManager(root) {
        const children = root.children;
        return html`
            ${this.path(root)}

                ${children.map(node => {
                    if (node.file.type.toUpperCase() === "DIRECTORY") {
                        return html`${this.folder(node)}`;
                    } else if (node.file.type.toUpperCase() === "FILE") {
                        return html`${this.file(node)}`;
                    } else {
                        throw new Error("Type not recognized " + node.file.type);
                    }
                })}

        `;
    }

    renderTree(root) {
        const children = root.children;
        const domId = `tree-${root.file.id.replace(/:/g, "")}`;
        return html`
            ${root.file.name !== "." ? html`
                     this.toggleFolder(domId, root)}" class="fas fa-angle-${root.exploded ? "down" : "right"}">
                     this.toggleFolder(domId, root)}"> ${root.file.name}
                ` : html`
                      Home`}

                ${children.map(node => {
                    if (node.file.type === "DIRECTORY") {
                        return html`

                                ${this.renderTree(node)}

`;
                    } else if (node.file.type.toUpperCase() === "FILE") {
                        return html`

 this.onClickFile(node.file.id)}">
                                ${this.icon(node.file.format)} ${node.file.name}

`;
                    } else {
                        throw new Error("Type not recognized " + node.file.type);
                    }
                })}

        `;
    }

    async toggleFolder(domId, node) {
        // check for the root
        if (domId !== "tree-") {
            if (!node.exploded) {
                /* node.exploded = true;
                if(!node.visited) {
                    await this.fetchFolder(node);
                }
                $("." + domId + "").addClass("exploded")*/
                await this.route(node.file.id);
            } else {
                node.exploded = false;
                $("." + domId + "").removeClass("exploded");
            }
            // console.log("toggle" + node.file.id);
            // console.log($("." + domId + " ul"));

        } else {
            console.error("no id!");
        }
        // $("." + id + " + ul").slideToggle();
        this.requestUpdate();
        await this.updateComplete;
    }

    icon(format, size) {
        const icon = {
            IMAGE: "fas fa-file-image",
            VCF: "fas fa-file"
        }[format];
        return html`<i class="${icon || "fas fa-file"}${size ? ` fa-${size}x` : ""}"></i>`;
    }

    folder(node) {
        return html`

                 this.route(node.file.id)}">

                         ${node.file.name}

        `;
    }

    file(node) {
        return html`

                 this.onClickFile(node.file.id)}">
                    ${this.icon(node.file.format, 4)}${node.file.format !== "UNKNOWN" ? node.file.format : ""}

                                ${node.file.name}

                        ${UtilsNew.getDiskUsage(node.file.size)}

        `;
    }

    path(node) {
        const path = node.file.id.split(":").filter(Boolean);
        return html`

                 ~  /
                ${path.map((name, i) => html`<a @click="${() => this.route(path.slice(0, i + 1).join(":") + ":")}"> ${name} </a> <span class="path-separator">/</span>`)}

`;
    }

    async route(id, resetFileId = true) {
        this.currentRoot = this.searchNode(id, this.tree);
        this.currentRoot.exploded = true;

        if (!this.currentRoot.visited) {
            await this.fetchFolder(this.currentRoot);
        } else {
            console.log("node already visited", this.currentRoot);
        }
        const domId = `tree-${id.replace(/:/g, "")}`;
        $("." + domId + "").addClass("exploded");
        if (resetFileId) {
            this.fileId = null;
        }
        this.requestUpdate();
    }

    reset() {
        this.currentRoot = this.tree;
        this.fileId = null;
        this.requestUpdate();
    }

    onClickFile(id) {
        const path = id.split(":").slice(0, -1).join(":") + ":";
        this.fileId = id;
        this.route(path, false);
        this.requestUpdate();
    }

    render() {
        if (!this.opencgaSession || !this.currentRoot) {
            return null;
        }

        return html`

                <tool-header title="${this._config.title}" icon="${this._config.icon}"></tool-header>

                        ${this.tree ? html`${this.renderTree(this.tree)}` : null}

                    ${this.errorState ? html`

                            ${this.errorState}

                    ` : null}
                    ${this.loading ? html`

                            <loading-spinner></loading-spinner>

                    ` : null}
                    ${this.currentRoot ? html`

                            ${this.renderFileManager(this.currentRoot)}

                            ${this.fileId ? html`

                                    <file-view
                                        .opencgaSession="${this.opencgaSession}"
                                        .fileId="${this.fileId}"
                                        mode="full">
                                    </file-view>

                            ` : null}
                        ` : null}

        `;
    }

    getDefaultConfig() {
        return {
            title: "File Explorer",
            icon: "img/tools/icons/file_explorer.svg"
        };
    }

}

customElements.define("file-manager", FileManager);
<|endoftext|>


<commit_before>
// @require core/widget/widget.js
// @require lib/color/color.js
// @require lib/transform/transform.js
// @require widgets/draggable/draggable.js

//TODO: Add support for not setting a starting color, in some cases it might be needed not to set a color by default
//TODO: Add support for alpha channel, by adding an opacity slider at the bottom of the sbWrp, it should be optional

(function ( $, _, Svelto, Factory, Color, Keyboard ) {

  /* CONFIG */

  let config = {
    name: 'colorpicker',
    plugin: true,
    selector: '.colorpicker',
    options: {
      exporters: {
        hex ( color ) {
          let hex = color.getHex ();
          return '#' + hex.r + hex.g + hex.b;
        }
      },
      startColor: '#ff0000', // It can be anything supported by the `Color` obj
      format: {
        type: 'hex', // One of the formats implemented in the exporters
        data: undefined // Passed to the called the exporter
      },
      live: false, // Whether it will update the input also on `Draggable.move` or just on `Draggable.end`
      selectors: {
        sb: {
          wrp: '.colorpicker-sb',
          handler: '.colorpicker-sb .colorpicker-handler'
        },
        hue: {
          wrp: '.colorpicker-hue',
          handler: '.colorpicker-hue .colorpicker-handler'
        },
        input: 'input'
      },
      callbacks: {
        change: _.noop
      }
    }
  };

  /* COLORPICKER */

  class Colorpicker extends Svelto.Widget {

    /* SPECIAL */

    _variables () {

      this.$colorpicker = this.$element;
      this.$sbWrp = this.$colorpicker.find ( this.options.selectors.sb.wrp );
      this.$sbHandler = this.$colorpicker.find ( this.options.selectors.sb.handler );
      this.$hueWrp = this.$colorpicker.find ( this.options.selectors.hue.wrp );
      this.$hueHandler = this.$colorpicker.find ( this.options.selectors.hue.handler );

      this.$input = this.$colorpicker.find ( this.options.selectors.input );

      this.sbWrpSize = this.$sbWrp.width ();

      this.hueWrpHeight = this.sbWrpSize;

      this.hsv = false;

    }

    _init () {

      this.set ( this.$input.val () );

      if ( !this.hsv ) {

        this.set ( this.options.startColor );

      }

    }

    _events () {

      this.___change ();

      this.___sbKeydown ();
      this.___sbDrag ();

      this.___hueKeydown ();
      this.___hueDrag ();

    }

    _destroy () {

      /* DRAG */

      this.$sbHandler.draggable ( 'destroy' );
      this.$hueHandler.draggable ( 'destroy' );

    }

    /* CHANGE */

    ___change () {

      this._on ( true, this.$input, 'change', this.__change );

    }

    __change () {

      this.set ( this.$input.val () );

    }

    /* SB ARROWS */

    ___sbKeydown () {

      this._onHover ( this.$sbWrp, [$.$document, 'keydown', this.__sbKeydown] );

    }

    __sbKeydown ( event ) {

      switch ( event.keyCode ) {

        case Keyboard.keys.UP:
          this.hsv.v = Math.min ( 100, this.hsv.v + 1 );
          break;

        case Keyboard.keys.RIGHT:
          this.hsv.s = Math.min ( 100, this.hsv.s + 1 );
          break;

        case Keyboard.keys.DOWN:
          this.hsv.v = Math.max ( 0, this.hsv.v - 1 );
          break;

        case Keyboard.keys.LEFT:
          this.hsv.s = Math.max ( 0, this.hsv.s - 1 );
          break;

        default:
          return;

      }

      event.preventDefault ();
      event.stopImmediatePropagation ();

      this._updateSb ();
      this._updateInput ();

    }

    /* SB DRAG */

    ___sbDrag () {

      this.$sbHandler.draggable ({
        draggable: this.isEnabled.bind ( this ),
        proxy: {
          $element: this.$sbWrp
        },
        constrainer: {
          $element: this.$sbWrp,
          center: true
        },
        callbacks: {
          start: this.__sbDragStart.bind ( this ),
          move: this._frames ( this.__sbDragMove.bind ( this ) ),
          end: this.__sbDragEnd.bind ( this )
        }
      });

    }

    _sbDragSet ( XY, update ) {

      this.hsv.s =  _.clamp ( XY.x, 0, this.sbWrpSize ) * 100 / this.sbWrpSize;
      this.hsv.v =  100 - ( _.clamp ( XY.y, 0, this.sbWrpSize ) * 100 / this.sbWrpSize );

      this._updateSb ( false );

      if ( update ) {

        this._updateInput ();

      }

    }

    __sbDragStart () {

      this._sbDragging = true;

    }

    __sbDragMove ( event, data ) {

      if ( !this._sbDragging ) return;

      this._sbDragSet ( data.dragXY, this.options.live );

    }

    __sbDragEnd ( event, data ) {

      this._sbDragging = false;

      this._sbDragSet ( data.dragXY, true );

    }

    /* HUE ARROWS */

    ___hueKeydown () {

      this._onHover ( this.$hueWrp, [$.$document, 'keydown', this.__hueKeydown] );

    }

    __hueKeydown ( event ) {

      switch ( event.keyCode ) {

        case Keyboard.keys.UP:
          this.hsv.h = Math.min ( 359, this.hsv.h + 1 );
          break;

        case Keyboard.keys.DOWN:
          this.hsv.h = Math.max ( 0, this.hsv.h - 1 );
          break;

        default:
          return;

      }

      event.preventDefault ();
      event.stopImmediatePropagation ();

      this._updateHue ();
      this._updateInput ();

    }

    /* HUE DRAG */

    ___hueDrag () {

      this.$hueHandler.draggable ({
        draggable: this.isEnabled.bind ( this ),
        axis: 'y',
        proxy: {
          $element: this.$hueWrp
        },
        constrainer: {
          $element: this.$hueWrp
        },
        callbacks: {
          start: this.__hueDragStart.bind ( this ),
          move: this._frames ( this.__hueDragMove.bind ( this ) ),
          end: this.__hueDragEnd.bind ( this )
        }
      });

    }

    _hueDragSet ( XY, update ) {

      this.hsv.h = 359 - ( _.clamp ( XY.y, 0, this.hueWrpHeight ) * 359 / this.hueWrpHeight );

      this._updateHue ( false );

      if ( update ) {

        this._updateInput ();

      }

    }

    __hueDragStart () {

      this._hueDragging = true;

    }

    __hueDragMove ( event, data ) {

      if ( !this._hueDragging ) return;

      this._hueDragSet ( data.dragXY, this.options.live );

    }

    __hueDragEnd ( event, data ) {

      this._hueDragging = false;

      this._hueDragSet ( data.dragXY, true );

    }

    /* UPDATE */

    _updateSb ( _translate = true ) {

      /* HSL */

      let hsl = Color.hsv2hsl ( this.hsv );

      this.$sbHandler.hsl ( hsl.h, hsl.s, hsl.l );

      /* TRANSLATE */

      if ( _translate ) {

        let translateX = this.sbWrpSize / 100 * this.hsv.s,
            translateY = this.sbWrpSize / 100 * ( 100 - this.hsv.v );

        this.$sbHandler.translate ( translateX, translateY );

      }

    }

    _updateHue ( _translate = true ) {

      /* HSL */

      let hsl = Color.hsv2hsl ( this.hsv );

      this.$hueHandler.hsl ( this.hsv.h, 100, 50 );
      this.$sbHandler.hsl ( hsl.h, hsl.s, hsl.l );
      this.$sbWrp.hsl ( this.hsv.h, 100, 50 );

      /* TRANSLATE */

      if ( _translate ) {

        let translateY = this.hueWrpHeight / 100 * ( 100 - ( this.hsv.h / 360 * 100 ) );

        this.$hueHandler.translateY ( translateY );

      }

    }

    _updateInput () {

      this.$input.val ( this._export () ).trigger ( 'change' );

      this._trigger ( 'change' );

    }

    _update () {

      this._updateSb ();
      this._updateHue ();
      this._updateInput ();

    }

    /* EXPORT */

    _export () {

      return this.options.exporters[this.options.format.type] ( new Color ( this.hsv, 'hsv' ), this.options.format.data );

    }

    /* API */

    get () {

      return this._export ();

    }

    set ( color ) {

      color = _.attempt ( () => new Color ( color ) );

      if ( _.isError ( color ) ) return;

      let hsv = color.getHsv ();

      if ( _.isEqualJSON ( this.hsv, hsv ) ) return;

      this.hsv = hsv;

      this._update ();

    }

  }

  /* FACTORY */

  Factory.make ( Colorpicker, config );

}( Svelto.$, Svelto._, Svelto, Svelto.Factory, Svelto.Color, Svelto.Keyboard ));
<commit_msg>Colorpicker: improved revealing at a later time<commit_after>
// @require core/widget/widget.js
// @require lib/color/color.js
// @require lib/transform/transform.js
// @require widgets/draggable/draggable.js

//TODO: Add support for not setting a starting color, in some cases it might be needed not to set a color by default
//TODO: Add support for alpha channel, by adding an opacity slider at the bottom of the sbWrp, it should be optional

(function ( $, _, Svelto, Factory, Color, Keyboard ) {

  /* CONFIG */

  let config = {
    name: 'colorpicker',
    plugin: true,
    selector: '.colorpicker',
    options: {
      exporters: {
        hex ( color ) {
          let hex = color.getHex ();
          return '#' + hex.r + hex.g + hex.b;
        }
      },
      startColor: '#ff0000', // It can be anything supported by the `Color` obj
      format: {
        type: 'hex', // One of the formats implemented in the exporters
        data: undefined // Passed to the called the exporter
      },
      live: false, // Whether it will update the input also on `Draggable.move` or just on `Draggable.end`
      selectors: {
        sb: {
          wrp: '.colorpicker-sb',
          handler: '.colorpicker-sb .colorpicker-handler'
        },
        hue: {
          wrp: '.colorpicker-hue',
          handler: '.colorpicker-hue .colorpicker-handler'
        },
        input: 'input'
      },
      callbacks: {
        change: _.noop
      }
    }
  };

  /* COLORPICKER */

  class Colorpicker extends Svelto.Widget {

    /* SPECIAL */

    _variables () {

      this.$colorpicker = this.$element;
      this.$sbWrp = this.$colorpicker.find ( this.options.selectors.sb.wrp );
      this.$sbHandler = this.$colorpicker.find ( this.options.selectors.sb.handler );
      this.$hueWrp = this.$colorpicker.find ( this.options.selectors.hue.wrp );
      this.$hueHandler = this.$colorpicker.find ( this.options.selectors.hue.handler );

      this.$input = this.$colorpicker.find ( this.options.selectors.input );

      this.dimension = this.$colorpicker.width () || 174; //FIXME: It shouldn't be set manually, but this widget might be hidden at init time

      this.hsv = false;

    }

    _init () {

      this.set ( this.$input.val () );

      if ( !this.hsv ) {

        this.set ( this.options.startColor );

      }

    }

    _events () {

      this.___change ();

      this.___sbKeydown ();
      this.___sbDrag ();

      this.___hueKeydown ();
      this.___hueDrag ();

    }

    _destroy () {

      /* DRAG */

      this.$sbHandler.draggable ( 'destroy' );
      this.$hueHandler.draggable ( 'destroy' );

    }

    /* CHANGE */

    ___change () {

      this._on ( true, this.$input, 'change', this.__change );

    }

    __change () {

      this.set ( this.$input.val () );

    }

    /* SB ARROWS */

    ___sbKeydown () {

      this._onHover ( this.$sbWrp, [$.$document, 'keydown', this.__sbKeydown] );

    }

    __sbKeydown ( event ) {

      switch ( event.keyCode ) {

        case Keyboard.keys.UP:
          this.hsv.v = Math.min ( 100, this.hsv.v + 1 );
          break;

        case Keyboard.keys.RIGHT:
          this.hsv.s = Math.min ( 100, this.hsv.s + 1 );
          break;

        case Keyboard.keys.DOWN:
          this.hsv.v = Math.max ( 0, this.hsv.v - 1 );
          break;

        case Keyboard.keys.LEFT:
          this.hsv.s = Math.max ( 0, this.hsv.s - 1 );
          break;

        default:
          return;

      }

      event.preventDefault ();
      event.stopImmediatePropagation ();

      this._updateSb ();
      this._updateInput ();

    }

    /* SB DRAG */

    ___sbDrag () {

      this.$sbHandler.draggable ({
        draggable: this.isEnabled.bind ( this ),
        proxy: {
          $element: this.$sbWrp
        },
        constrainer: {
          $element: this.$sbWrp,
          center: true
        },
        callbacks: {
          start: this.__sbDragStart.bind ( this ),
          move: this._frames ( this.__sbDragMove.bind ( this ) ),
          end: this.__sbDragEnd.bind ( this )
        }
      });

    }

    _sbDragSet ( XY, update ) {

      this.hsv.s =  _.clamp ( XY.x, 0, this.dimension ) * 100 / this.dimension;
      this.hsv.v =  100 - ( _.clamp ( XY.y, 0, this.dimension ) * 100 / this.dimension );

      this._updateSb ( false );

      if ( update ) {

        this._updateInput ();

      }

    }

    __sbDragStart () {

      this._sbDragging = true;

    }

    __sbDragMove ( event, data ) {

      if ( !this._sbDragging ) return;

      this._sbDragSet ( data.dragXY, this.options.live );

    }

    __sbDragEnd ( event, data ) {

      this._sbDragging = false;

      this._sbDragSet ( data.dragXY, true );

    }

    /* HUE ARROWS */

    ___hueKeydown () {

      this._onHover ( this.$hueWrp, [$.$document, 'keydown', this.__hueKeydown] );

    }

    __hueKeydown ( event ) {

      switch ( event.keyCode ) {

        case Keyboard.keys.UP:
          this.hsv.h = Math.min ( 359, this.hsv.h + 1 );
          break;

        case Keyboard.keys.DOWN:
          this.hsv.h = Math.max ( 0, this.hsv.h - 1 );
          break;

        default:
          return;

      }

      event.preventDefault ();
      event.stopImmediatePropagation ();

      this._updateHue ();
      this._updateInput ();

    }

    /* HUE DRAG */

    ___hueDrag () {

      this.$hueHandler.draggable ({
        draggable: this.isEnabled.bind ( this ),
        axis: 'y',
        proxy: {
          $element: this.$hueWrp
        },
        constrainer: {
          $element: this.$hueWrp
        },
        callbacks: {
          start: this.__hueDragStart.bind ( this ),
          move: this._frames ( this.__hueDragMove.bind ( this ) ),
          end: this.__hueDragEnd.bind ( this )
        }
      });

    }

    _hueDragSet ( XY, update ) {

      this.hsv.h = 359 - ( _.clamp ( XY.y, 0, this.dimension ) * 359 / this.dimension );

      this._updateHue ( false );

      if ( update ) {

        this._updateInput ();

      }

    }

    __hueDragStart () {

      this._hueDragging = true;

    }

    __hueDragMove ( event, data ) {

      if ( !this._hueDragging ) return;

      this._hueDragSet ( data.dragXY, this.options.live );

    }

    __hueDragEnd ( event, data ) {

      this._hueDragging = false;

      this._hueDragSet ( data.dragXY, true );

    }

    /* UPDATE */

    _updateSb ( _translate = true ) {

      /* HSL */

      let hsl = Color.hsv2hsl ( this.hsv );

      this.$sbHandler.hsl ( hsl.h, hsl.s, hsl.l );

      /* TRANSLATE */

      if ( _translate ) {

        let translateX = this.dimension / 100 * this.hsv.s,
            translateY = this.dimension / 100 * ( 100 - this.hsv.v );

        this.$sbHandler.translate ( translateX, translateY );

      }

    }

    _updateHue ( _translate = true ) {

      /* HSL */

      let hsl = Color.hsv2hsl ( this.hsv );

      this.$hueHandler.hsl ( this.hsv.h, 100, 50 );
      this.$sbHandler.hsl ( hsl.h, hsl.s, hsl.l );
      this.$sbWrp.hsl ( this.hsv.h, 100, 50 );

      /* TRANSLATE */

      if ( _translate ) {

        let translateY = this.dimension / 100 * ( 100 - ( this.hsv.h / 360 * 100 ) );

        this.$hueHandler.translateY ( translateY );

      }

    }

    _updateInput () {

      this.$input.val ( this._export () ).trigger ( 'change' );

      this._trigger ( 'change' );

    }

    _update () {

      this._updateSb ();
      this._updateHue ();
      this._updateInput ();

    }

    /* EXPORT */

    _export () {

      return this.options.exporters[this.options.format.type] ( new Color ( this.hsv, 'hsv' ), this.options.format.data );

    }

    /* API */

    get () {

      return this._export ();

    }

    set ( color ) {

      color = _.attempt ( () => new Color ( color ) );

      if ( _.isError ( color ) ) return;

      let hsv = color.getHsv ();

      if ( _.isEqualJSON ( this.hsv, hsv ) ) return;

      this.hsv = hsv;

      this._update ();

    }

  }

  /* FACTORY */

  Factory.make ( Colorpicker, config );

}( Svelto.$, Svelto._, Svelto, Svelto.Factory, Svelto.Color, Svelto.Keyboard ));
<|endoftext|>


<commit_before>var _manifest;
var _manifestError;
var _offlineView;
var _mainView;
var _zIndex = 10000;
var _enableOfflineSupport = true;
var _lastKnownLocation;
var _lastKnownLocationFailed = false;
var _whiteList = [];

function bridgeNativeEvent(e) {
    _mainView.invokeScriptAsync('eval', "cordova && cordova.fireDocumentEvent('" + e.type + "', null, true);").start();
}

//document.addEventListener('backbutton', bridgeNativeEvent, false);
document.addEventListener('pause', bridgeNativeEvent, false);
document.addEventListener('resume', bridgeNativeEvent, false);

// creates a webview to host content
function configureHost(url, zOrder, display) {
    var webView = document.createElement(cordova.platformId === 'windows8' ? 'iframe' : 'x-ms-webview');
    var style = webView.style;
    style.position = 'absolute';
    style.top = 0;
    style.left = 0;
    style.zIndex = zOrder;
    style.width = '100%';
    style.height = '100%';
    if (display) {
        style.display = display;
    }

    if (url) {
        webView.src = url;
    }

    webView.addEventListener("MSWebViewNavigationCompleted", navigationCompletedEvent, false);
    webView.addEventListener("MSWebViewNavigationStarting", navigationStartingEvent, false);

    document.body.appendChild(webView);

    return webView;
}

// handles webview's navigation starting event
function navigationStartingEvent(evt) {
    if (handleCordovaExecCalls(evt)) {
        evt.stopImmediatePropagation();
        evt.preventDefault();
        return;
    }

    if (evt.uri && evt.uri !== "") {
        var isInWhitelist = false;
        for (var i = 0; i < _whiteList.length; i++) {
            var rule = _whiteList[i];
            if (rule.test(evt.uri)) {
                isInWhitelist = true;
                break;
            }
        }

        // if the url to navigate to does not match any of the rules in the whitelist, open it outside de app
        if (!isInWhitelist) {
            evt.stopImmediatePropagation();
            evt.preventDefault();
            console.log("Whitelist rejection: url='" + evt.uri + "'");
            Windows.System.Launcher.launchUriAsync(new Windows.Foundation.Uri(evt.uri));
        }
    }
}

// handles webview's navigation completed event
function navigationCompletedEvent(evt) {
    if (evt.uri && evt.uri !== "") {
        if (evt.isSuccess) {
            _lastKnownLocationFailed = false;
            if (_offlineView) {
                _offlineView.style.display = 'none';
            }
        } else {
            _lastKnownLocationFailed = true;
        }

        _lastKnownLocation = evt.uri;
    }
}

function domContentLoadedEvent(evt) {
    console.log('Finished loading URL: ' + _mainView.src);

    hideExtendedSplashScreen();

    // inject Cordova
    if (isCordovaEnabled()) {
        var cordova = _manifest.mjs_cordova || {};

        var pluginMode = cordova.pluginMode || 'client';
        var cordovaBaseUrl = (cordova.baseUrl || '').trim();
        if (cordovaBaseUrl.indexOf('/', cordovaBaseUrl.length - 1) === -1) {
            cordovaBaseUrl += '/';
        }

        _mainView.invokeScriptAsync('eval', 'window.hostedWebApp = { \'platform\': \'windows\', \'pluginMode\': \'' + pluginMode + '\', \'cordovaBaseUrl\': \'' + cordovaBaseUrl + '\'};').start();

        var scriptsToInject = [];
        if (pluginMode === 'client') {
            scriptsToInject.push('cordova.js');
        }

        scriptsToInject.push('hostedapp-bridge.js');
        injectScripts(scriptsToInject);
    }

    // inject import scripts
    if (_manifest && _manifest.mjs_import_scripts && _manifest.mjs_import_scripts instanceof Array) {
        var scriptFiles = _manifest.mjs_import_scripts
            .filter(isMatchingRuleForPage)
            .map(function (item) {
                return item.src;
            });

        if (scriptFiles.length) {
            injectScripts(scriptFiles);
        }
    }
}

// checks if Cordova runtime environment is enabled for the current page
function isCordovaEnabled() {
    var allow = true;
    var enableCordova = false;
    var accessRules = _manifest.mjs_api_access;
    if (accessRules) {
        accessRules.forEach(function (rule) {
            if (isMatchingRuleForPage(rule)) {
                var access = rule.access;
                if (!access || access === 'cordova') {
                    enableCordova = true;
                }
                else if (access === 'none') {
                    allow = false;
                }
                else {
                    console.log('Unsupported API access type \'' + access + '\' found in mjs_api_access rule.');
                }
            }
        });
    }

    return enableCordova && allow;
}

// check if an API access or custom script match rule applies to the current page
function isMatchingRuleForPage(rule) {

    // ensure rule applies to current platform
    if (rule.platform && rule.platform.split(';')
        .map(function (item) { return item.trim(); })
        .indexOf('windows') < 0) {
        return false;
    }

    // ensure rule applies to current page
    var match = rule.match;
    if (match) {
        if (typeof match === 'string' && match.length) {
            match = [match];
        }

        return match.some(function (item) { return convertPatternToRegex(item).test(_mainView.src); });
    }

    return true;
}

// handles network connectivity change events
function connectivityEvent(evt) {
    console.log('Received a network connectivity change notification. The device is currently ' + evt.type + '.');
    if (_enableOfflineSupport) {
        if (evt.type === 'offline') {
            _offlineView.style.display = 'block';
        } else if (evt.type === 'online') {
            if (_lastKnownLocationFailed) {
                if (_lastKnownLocation) {
                    console.log("Reload last known location: '" + _lastKnownLocation + "'");
                    _mainView.src = _lastKnownLocation;
                }
            } else {
                _offlineView.style.display = 'none';
            }
        }
    }
}

// sets up a secondary webview to host the offline page
function configureOfflineSupport(offlinePage) {
    var offlinePageUrl = '///www/' + offlinePage;
    var url = new Windows.Foundation.Uri('ms-appx:' + offlinePageUrl);
    Windows.Storage.StorageFile.getFileFromApplicationUriAsync(url).then(
        function (file) {
            _offlineView = configureHost('ms-appx-web:' + offlinePageUrl, 10001, 'none');
        },
        function (err) {
            var message = 'It looks like you are offline. Please reconnect to use this application.';
            var offlinePageTemplate = '
' + message + '
';
            _offlineView = configureHost(null, _zIndex + 1, 'none');

            if (cordova.platformId === 'windows8') {
                _offlineView.style.backgroundColor = 'white';
                _offlineView.contentDocument.write(offlinePageTemplate);
            } else {
                _offlineView.navigateToString(offlinePageTemplate);
            }
        }).done(function () {
            document.addEventListener('offline', connectivityEvent, false);
            document.addEventListener('online', connectivityEvent, false);
        });
}

// escapes regular expression reserved symbols
function escapeRegex(str) {
    return ("" + str).replace(/([.?*+^$[\]\\(){}|-])/g, "\\$1");
}

// converts a string pattern to a regular expression
function convertPatternToRegex(pattern, excludeLineStart, excludeLineEnd) {
    var isNot = (pattern[0] == '!');
    if (isNot) { pattern = pattern.substr(1) };

    var regexBody = escapeRegex(pattern);

    excludeLineStart = !!excludeLineStart;
    excludeLineEnd = !!excludeLineEnd;

    regexBody = regexBody.replace(/\\\?/g, ".?").replace(/\\\*/g, ".*?");
    if (isNot) { regexBody = "((?!" + regexBody + ").)*"; }
    if (!excludeLineStart) { regexBody = "^" + regexBody; }
    if (!excludeLineEnd) { regexBody += "\/?$"; }

    return new RegExp(regexBody);
}

// initializes an array of the access rules defined in the manifest
function configureWhiteList(manifest) {
    if (manifest) {
        // Add base access rule based on the start_url and the scope
        var baseUrlPattern = new Windows.Foundation.Uri(manifest.start_url);
        if (manifest.scope && manifest.scope.length) {
            baseUrlPattern = baseUrlPattern.combineUri(manifest.scope);
        }

        baseUrlPattern = baseUrlPattern.combineUri('*');
        _whiteList.push(convertPatternToRegex(baseUrlPattern.absoluteUri));

        // add additional access rules
        if (manifest.mjs_access_whitelist && manifest.mjs_access_whitelist instanceof Array) {
            manifest.mjs_access_whitelist.forEach(function (rule) {
                _whiteList.push(convertPatternToRegex(rule.url));
            });
        }
    }
}

// hides the extended splash screen
function hideExtendedSplashScreen() {
    var extendedSplashScreen = document.getElementById("extendedSplashScreen");
    extendedSplashScreen.style.display = "none";
}

// handle the hardware backbutton
function navigateBack(e) {
    if (!_mainView.canGoBack) {
        return false;
    }

    try {
        _mainView.goBack();
    } catch (err) {
        return false;
    }

    return true;
}

var exec = require('cordova/exec');

function injectScripts(files, successCallback, errorCallback) {

    var script = (arguments.length > 3 && typeof arguments[3] === 'string') ? arguments[3] : '';
    var fileList = (arguments.length > 4 && typeof arguments[4] === 'string') ? arguments[4] : '';

    if (typeof files === 'string') {
        files = files.length ? [files] : [];
    }

    var fileName = files.shift();
    if (!fileName) {
        var asyncOp = _mainView.invokeScriptAsync('eval', script);
        asyncOp.oncomplete = function () { successCallback && successCallback(true); };
        asyncOp.onerror = function (err) {
            console.log('Error injecting script file(s): ' + fileList + ' - ' + asyncOp.error);
            errorCallback && errorCallback(err);
        };

        asyncOp.start()
        return;
    }

    console.log('Injecting script file: ' + fileName);
    var uri = new Windows.Foundation.Uri('ms-appx:///www/', fileName);

    var onSuccess = function (content) {
        script += '\r\n//# sourceURL=' + fileName + '\r\n' + content;
        injectScripts(files, successCallback, errorCallback, script, (fileList ? ', ' : '') + fileName);
    };

    var onError = function (err) {
        console.log('Error retrieving script file from app package: ' + fileName + ' - ' + err);
        if (errorCallback) {
            errorCallback(err);
        }
    };

    if (uri.schemeName == 'ms-appx') {
        Windows.Storage.StorageFile.getFileFromApplicationUriAsync(uri)
            .done(function (file) {
                Windows.Storage.FileIO.readTextAsync(file)
                    .done(onSuccess, onError);
            }, onError);
    } else {
        var httpClient = new Windows.Web.Http.HttpClient();
        httpClient.getStringAsync(uri).done(onSuccess, onError);
        httpClient.close();
    }
}

function handleCordovaExecCalls(evt) {
    if (evt.uri) {
        var targetUri = new Windows.Foundation.Uri(evt.uri);
        if (targetUri.host === '.cordova' && targetUri.path === '/exec') {
            var service = targetUri.queryParsed.getFirstValueByName('service');
            var action = targetUri.queryParsed.getFirstValueByName('action');
            var args = JSON.parse(decodeURIComponent(targetUri.queryParsed.getFirstValueByName('args')));
            var callbackId = targetUri.queryParsed.getFirstValueByName('callbackId');

            var success, fail;
            if (callbackId !== '0') {
                success = function (args) {
                    var params = args ? '"' + encodeURIComponent(JSON.stringify(args)) + '"' : '';
                    var script = 'cordova.callbacks["' + callbackId + '"].success(' + params + ');';
                    _mainView.invokeScriptAsync('eval', script).start();
                };

                fail = function (err) {
                    var params = args ? '"' + encodeURIComponent(JSON.stringify(err)) + '"' : '';
                    var script = 'cordova.callbacks["' + callbackId + '"].fail(' + params + ');';
                    _mainView.invokeScriptAsync('eval', script).start();
                };
            }

            exec(success, fail, service, action, args);

            return true;
        }
    }

    return false;
}

module.exports = {
    // loads the W3C manifest file and parses it
    loadManifest: function (successCallback, errorCallback, args) {
        var manifestFileName = (args && args instanceof Array && args.length > 0) ? args[0] : 'manifest.json';
        var configFile = 'ms-appx:///www/' + manifestFileName;
        var uri = new Windows.Foundation.Uri(configFile);
        Windows.Storage.StorageFile.getFileFromApplicationUriAsync(uri).then(
            function (file) {
                Windows.Storage.FileIO.readTextAsync(file).then(function (data) {
                    try {
                        _manifest = JSON.parse(data);
                        cordova.fireDocumentEvent("manifestLoaded", { manifest: _manifest });
                        successCallback &&  successCallback(_manifest);
                    } catch (err) {
                        _manifestError = 'Error parsing manifest file: ' + manifestFileName + ' - ' + err.message;
                        console.log(_manifestError);
                    }
                });
            },
            function (err) {
                _manifestError = 'Error reading manifest file: ' + manifestFileName + ' - ' + err;
                console.log(_manifestError);
                errorCallback && errorCallback(err);
            });
    },

    // returns the currently loaded manifest
    getManifest: function (successCallback, errorCallback) {
        if (_manifest) {
            successCallback && successCallback(_manifest);
        } else {
            errorCallback && errorCallback(new Error(_manifestError));
        }
    },

    // enables offline page support
    enableOfflinePage: function (successCallback, errorCallback) {
        _enableOfflineSupport = true;
        successCallback && successCallback();
    },

    // disables offline page support
    disableOfflinePage: function (successCallback, errorCallback) {
        _enableOfflineSupport = false;
        successCallback && successCallback();
    },

    getWebView: function () {
      return _mainView;
    },

    injectPluginScript: function (successCallback, errorCallback, file) {
        injectScripts(file, successCallback, errorCallback);
    }
}; // exports

cordova.commandProxy.add('HostedWebApp', module.exports);

module.exports.loadManifest(
    function (manifest) {
        configureOfflineSupport('offline.html');
        configureWhiteList(manifest);
        _mainView = configureHost(manifest ? manifest.start_url : 'about:blank', _zIndex);
        _mainView.addEventListener("MSWebViewDOMContentLoaded", domContentLoadedEvent, false);

        cordova.fireDocumentEvent("webviewCreated", { webView: _mainView });
        WinJS.Application.onbackclick = navigateBack;
    });
<commit_msg>Removed support for ´platform' attribute in mjs_import_script (Windows)<commit_after>var _manifest;
var _manifestError;
var _offlineView;
var _mainView;
var _zIndex = 10000;
var _enableOfflineSupport = true;
var _lastKnownLocation;
var _lastKnownLocationFailed = false;
var _whiteList = [];

function bridgeNativeEvent(e) {
    _mainView.invokeScriptAsync('eval', "cordova && cordova.fireDocumentEvent('" + e.type + "', null, true);").start();
}

//document.addEventListener('backbutton', bridgeNativeEvent, false);
document.addEventListener('pause', bridgeNativeEvent, false);
document.addEventListener('resume', bridgeNativeEvent, false);

// creates a webview to host content
function configureHost(url, zOrder, display) {
    var webView = document.createElement(cordova.platformId === 'windows8' ? 'iframe' : 'x-ms-webview');
    var style = webView.style;
    style.position = 'absolute';
    style.top = 0;
    style.left = 0;
    style.zIndex = zOrder;
    style.width = '100%';
    style.height = '100%';
    if (display) {
        style.display = display;
    }

    if (url) {
        webView.src = url;
    }

    webView.addEventListener("MSWebViewNavigationCompleted", navigationCompletedEvent, false);
    webView.addEventListener("MSWebViewNavigationStarting", navigationStartingEvent, false);

    document.body.appendChild(webView);

    return webView;
}

// handles webview's navigation starting event
function navigationStartingEvent(evt) {
    if (handleCordovaExecCalls(evt)) {
        evt.stopImmediatePropagation();
        evt.preventDefault();
        return;
    }

    if (evt.uri && evt.uri !== "") {
        var isInWhitelist = false;
        for (var i = 0; i < _whiteList.length; i++) {
            var rule = _whiteList[i];
            if (rule.test(evt.uri)) {
                isInWhitelist = true;
                break;
            }
        }

        // if the url to navigate to does not match any of the rules in the whitelist, open it outside de app
        if (!isInWhitelist) {
            evt.stopImmediatePropagation();
            evt.preventDefault();
            console.log("Whitelist rejection: url='" + evt.uri + "'");
            Windows.System.Launcher.launchUriAsync(new Windows.Foundation.Uri(evt.uri));
        }
    }
}

// handles webview's navigation completed event
function navigationCompletedEvent(evt) {
    if (evt.uri && evt.uri !== "") {
        if (evt.isSuccess) {
            _lastKnownLocationFailed = false;
            if (_offlineView) {
                _offlineView.style.display = 'none';
            }
        } else {
            _lastKnownLocationFailed = true;
        }

        _lastKnownLocation = evt.uri;
    }
}

function domContentLoadedEvent(evt) {
    console.log('Finished loading URL: ' + _mainView.src);

    hideExtendedSplashScreen();

    // inject Cordova
    if (isCordovaEnabled()) {
        var cordova = _manifest.mjs_cordova || {};

        var pluginMode = cordova.pluginMode || 'client';
        var cordovaBaseUrl = (cordova.baseUrl || '').trim();
        if (cordovaBaseUrl.indexOf('/', cordovaBaseUrl.length - 1) === -1) {
            cordovaBaseUrl += '/';
        }

        _mainView.invokeScriptAsync('eval', 'window.hostedWebApp = { \'platform\': \'windows\', \'pluginMode\': \'' + pluginMode + '\', \'cordovaBaseUrl\': \'' + cordovaBaseUrl + '\'};').start();

        var scriptsToInject = [];
        if (pluginMode === 'client') {
            scriptsToInject.push('cordova.js');
        }

        scriptsToInject.push('hostedapp-bridge.js');
        injectScripts(scriptsToInject);
    }

    // inject import scripts
    if (_manifest && _manifest.mjs_import_scripts && _manifest.mjs_import_scripts instanceof Array) {
        var scriptFiles = _manifest.mjs_import_scripts
            .filter(isMatchingRuleForPage)
            .map(function (item) {
                return item.src;
            });

        if (scriptFiles.length) {
            injectScripts(scriptFiles);
        }
    }
}

// checks if Cordova runtime environment is enabled for the current page
function isCordovaEnabled() {
    var allow = true;
    var enableCordova = false;
    var accessRules = _manifest.mjs_api_access;
    if (accessRules) {
        accessRules.forEach(function (rule) {
            if (isMatchingRuleForPage(rule, true)) {
                var access = rule.access;
                if (!access || access === 'cordova') {
                    enableCordova = true;
                }
                else if (access === 'none') {
                    allow = false;
                }
                else {
                    console.log('Unsupported API access type \'' + access + '\' found in mjs_api_access rule.');
                }
            }
        });
    }

    return enableCordova && allow;
}

// check if an API access or custom script match rule applies to the current page
function isMatchingRuleForPage(rule, checkPlatform) {

    // ensure rule applies to current platform
    if (checkPlatform) {
        if (rule.platform && rule.platform.split(';')
            .map(function (item) { return item.trim(); })
            .indexOf('windows') < 0) {
                return false;
            }
    }

    // ensure rule applies to current page
    var match = rule.match;
    if (match) {
        if (typeof match === 'string' && match.length) {
            match = [match];
        }

        return match.some(function (item) { return convertPatternToRegex(item).test(_mainView.src); });
    }

    return true;
}

// handles network connectivity change events
function connectivityEvent(evt) {
    console.log('Received a network connectivity change notification. The device is currently ' + evt.type + '.');
    if (_enableOfflineSupport) {
        if (evt.type === 'offline') {
            _offlineView.style.display = 'block';
        } else if (evt.type === 'online') {
            if (_lastKnownLocationFailed) {
                if (_lastKnownLocation) {
                    console.log("Reload last known location: '" + _lastKnownLocation + "'");
                    _mainView.src = _lastKnownLocation;
                }
            } else {
                _offlineView.style.display = 'none';
            }
        }
    }
}

// sets up a secondary webview to host the offline page
function configureOfflineSupport(offlinePage) {
    var offlinePageUrl = '///www/' + offlinePage;
    var url = new Windows.Foundation.Uri('ms-appx:' + offlinePageUrl);
    Windows.Storage.StorageFile.getFileFromApplicationUriAsync(url).then(
        function (file) {
            _offlineView = configureHost('ms-appx-web:' + offlinePageUrl, 10001, 'none');
        },
        function (err) {
            var message = 'It looks like you are offline. Please reconnect to use this application.';
            var offlinePageTemplate = '
' + message + '
';
            _offlineView = configureHost(null, _zIndex + 1, 'none');

            if (cordova.platformId === 'windows8') {
                _offlineView.style.backgroundColor = 'white';
                _offlineView.contentDocument.write(offlinePageTemplate);
            } else {
                _offlineView.navigateToString(offlinePageTemplate);
            }
        }).done(function () {
            document.addEventListener('offline', connectivityEvent, false);
            document.addEventListener('online', connectivityEvent, false);
        });
}

// escapes regular expression reserved symbols
function escapeRegex(str) {
    return ("" + str).replace(/([.?*+^$[\]\\(){}|-])/g, "\\$1");
}

// converts a string pattern to a regular expression
function convertPatternToRegex(pattern, excludeLineStart, excludeLineEnd) {
    var isNot = (pattern[0] == '!');
    if (isNot) { pattern = pattern.substr(1) };

    var regexBody = escapeRegex(pattern);

    excludeLineStart = !!excludeLineStart;
    excludeLineEnd = !!excludeLineEnd;

    regexBody = regexBody.replace(/\\\?/g, ".?").replace(/\\\*/g, ".*?");
    if (isNot) { regexBody = "((?!" + regexBody + ").)*"; }
    if (!excludeLineStart) { regexBody = "^" + regexBody; }
    if (!excludeLineEnd) { regexBody += "\/?$"; }

    return new RegExp(regexBody);
}

// initializes an array of the access rules defined in the manifest
function configureWhiteList(manifest) {
    if (manifest) {
        // Add base access rule based on the start_url and the scope
        var baseUrlPattern = new Windows.Foundation.Uri(manifest.start_url);
        if (manifest.scope && manifest.scope.length) {
            baseUrlPattern = baseUrlPattern.combineUri(manifest.scope);
        }

        baseUrlPattern = baseUrlPattern.combineUri('*');
        _whiteList.push(convertPatternToRegex(baseUrlPattern.absoluteUri));

        // add additional access rules
        if (manifest.mjs_access_whitelist && manifest.mjs_access_whitelist instanceof Array) {
            manifest.mjs_access_whitelist.forEach(function (rule) {
                _whiteList.push(convertPatternToRegex(rule.url));
            });
        }
    }
}

// hides the extended splash screen
function hideExtendedSplashScreen() {
    var extendedSplashScreen = document.getElementById("extendedSplashScreen");
    extendedSplashScreen.style.display = "none";
}

// handle the hardware backbutton
function navigateBack(e) {
    if (!_mainView.canGoBack) {
        return false;
    }

    try {
        _mainView.goBack();
    } catch (err) {
        return false;
    }

    return true;
}

var exec = require('cordova/exec');

function injectScripts(files, successCallback, errorCallback) {

    var script = (arguments.length > 3 && typeof arguments[3] === 'string') ? arguments[3] : '';
    var fileList = (arguments.length > 4 && typeof arguments[4] === 'string') ? arguments[4] : '';

    if (typeof files === 'string') {
        files = files.length ? [files] : [];
    }

    var fileName = files.shift();
    if (!fileName) {
        var asyncOp = _mainView.invokeScriptAsync('eval', script);
        asyncOp.oncomplete = function () { successCallback && successCallback(true); };
        asyncOp.onerror = function (err) {
            console.log('Error injecting script file(s): ' + fileList + ' - ' + asyncOp.error);
            errorCallback && errorCallback(err);
        };

        asyncOp.start()
        return;
    }

    console.log('Injecting script file: ' + fileName);
    var uri = new Windows.Foundation.Uri('ms-appx:///www/', fileName);

    var onSuccess = function (content) {
        script += '\r\n//# sourceURL=' + fileName + '\r\n' + content;
        injectScripts(files, successCallback, errorCallback, script, (fileList ? ', ' : '') + fileName);
    };

    var onError = function (err) {
        console.log('Error retrieving script file from app package: ' + fileName + ' - ' + err);
        if (errorCallback) {
            errorCallback(err);
        }
    };

    if (uri.schemeName == 'ms-appx') {
        Windows.Storage.StorageFile.getFileFromApplicationUriAsync(uri)
            .done(function (file) {
                Windows.Storage.FileIO.readTextAsync(file)
                    .done(onSuccess, onError);
            }, onError);
    } else {
        var httpClient = new Windows.Web.Http.HttpClient();
        httpClient.getStringAsync(uri).done(onSuccess, onError);
        httpClient.close();
    }
}

function handleCordovaExecCalls(evt) {
    if (evt.uri) {
        var targetUri = new Windows.Foundation.Uri(evt.uri);
        if (targetUri.host === '.cordova' && targetUri.path === '/exec') {
            var service = targetUri.queryParsed.getFirstValueByName('service');
            var action = targetUri.queryParsed.getFirstValueByName('action');
            var args = JSON.parse(decodeURIComponent(targetUri.queryParsed.getFirstValueByName('args')));
            var callbackId = targetUri.queryParsed.getFirstValueByName('callbackId');

            var success, fail;
            if (callbackId !== '0') {
                success = function (args) {
                    var params = args ? '"' + encodeURIComponent(JSON.stringify(args)) + '"' : '';
                    var script = 'cordova.callbacks["' + callbackId + '"].success(' + params + ');';
                    _mainView.invokeScriptAsync('eval', script).start();
                };

                fail = function (err) {
                    var params = args ? '"' + encodeURIComponent(JSON.stringify(err)) + '"' : '';
                    var script = 'cordova.callbacks["' + callbackId + '"].fail(' + params + ');';
                    _mainView.invokeScriptAsync('eval', script).start();
                };
            }

            exec(success, fail, service, action, args);

            return true;
        }
    }

    return false;
}

module.exports = {
    // loads the W3C manifest file and parses it
    loadManifest: function (successCallback, errorCallback, args) {
        var manifestFileName = (args && args instanceof Array && args.length > 0) ? args[0] : 'manifest.json';
        var configFile = 'ms-appx:///www/' + manifestFileName;
        var uri = new Windows.Foundation.Uri(configFile);
        Windows.Storage.StorageFile.getFileFromApplicationUriAsync(uri).then(
            function (file) {
                Windows.Storage.FileIO.readTextAsync(file).then(function (data) {
                    try {
                        _manifest = JSON.parse(data);
                        cordova.fireDocumentEvent("manifestLoaded", { manifest: _manifest });
                        successCallback &&  successCallback(_manifest);
                    } catch (err) {
                        _manifestError = 'Error parsing manifest file: ' + manifestFileName + ' - ' + err.message;
                        console.log(_manifestError);
                    }
                });
            },
            function (err) {
                _manifestError = 'Error reading manifest file: ' + manifestFileName + ' - ' + err;
                console.log(_manifestError);
                errorCallback && errorCallback(err);
            });
    },

    // returns the currently loaded manifest
    getManifest: function (successCallback, errorCallback) {
        if (_manifest) {
            successCallback && successCallback(_manifest);
        } else {
            errorCallback && errorCallback(new Error(_manifestError));
        }
    },

    // enables offline page support
    enableOfflinePage: function (successCallback, errorCallback) {
        _enableOfflineSupport = true;
        successCallback && successCallback();
    },

    // disables offline page support
    disableOfflinePage: function (successCallback, errorCallback) {
        _enableOfflineSupport = false;
        successCallback && successCallback();
    },

    getWebView: function () {
      return _mainView;
    },

    injectPluginScript: function (successCallback, errorCallback, file) {
        injectScripts(file, successCallback, errorCallback);
    }
}; // exports

cordova.commandProxy.add('HostedWebApp', module.exports);

module.exports.loadManifest(
    function (manifest) {
        configureOfflineSupport('offline.html');
        configureWhiteList(manifest);
        _mainView = configureHost(manifest ? manifest.start_url : 'about:blank', _zIndex);
        _mainView.addEventListener("MSWebViewDOMContentLoaded", domContentLoadedEvent, false);

        cordova.fireDocumentEvent("webviewCreated", { webView: _mainView });
        WinJS.Application.onbackclick = navigateBack;
    });
<|endoftext|>


<commit_before>/*
 * GeoTag-X page analytics.
 */
;(function($, undefined){
	"use strict";

	// When the script is loaded, we have to make sure the GTM script is
	// fully loaded and to do so, we check whether the window.analyticsListener
	// variable is defined. If it isn't we keep checking at intervals of 450ms
	// until the variable gets defined, or we ultimately give up after ~10 seconds.
	// When the GTM script is ready, we trigger the 'gtmready' custom event
	// which starts page-agnostic, as well as project-specific analytics.
	$(document).ready(function(){
		var maxWaitPeriod = 10000;
		var dt = 450;
		var timer = setInterval(function(){
			if (maxWaitPeriod <= 0)
				clearInterval(timer);
			else {
				if (window.analyticsListener){
					clearInterval(timer);
					$(document).trigger("gtmready");
				}
				else
					maxWaitPeriod -= dt;
			}
		}, dt);
	});

	// The analytics begins only when the Google Tag Manager (GTM) script is
	// ready, i.e. when the "gtmready" custom event has been fired.
	$(document).on("gtmready", function(){
		analytics.setGlobal("userId", $("body").data("user-id"));

		// Are we viewing a category's profile page?
		var $categoryProfile = $("#category-profile[data-short-name!=]");
		if ($categoryProfile.length > 0)
			onVisitCategory($categoryProfile.data("short-name"));

		// Or is it a project's profile page?
		var $projectProfile = $("#project-profile[data-short-name!=]");
		if ($projectProfile.length > 0)
			onVisitProject($projectProfile.data("short-name"));

		$("html").on("click.analytics", onElementClicked);
		$("#share-category > a").on("click.analytics", onShareCategory);
		$("#share-project > a").on("click.analytics", onShareProject);
		$("a[href!=]").on("click.analytics", onLinkClicked);
		$("a.project-launcher").on("click.analytics", onStartProject);
	});
	/**
	 * Fires an event when a user clicks the browser's "Back" button.
	 */
	function onGoingBackButtonClicked(){
		var data = {
			"url":null
		};
		analytics.fireEvent("action.goingBackButtonClicked", data);
	}
	/**
	 * Determines if the element that triggered this event is clickable. If it isn't
	 * then the action.invalidClick event is fired, otherwise no operation is performed.
	 */
	function onElementClicked(e){
		var $target = $(e.target);
		var elementClass = $target.attr("class");
		if (elementClass){
			var isClickable =
				$target.is(".clickable") || // A set of elements that aren't usually clickable but have been made so (e.g. when an event handler has been attached to them).
				$target.is("div.modal.fade") || // A modal underlay which can be used to close a modal.
				$target.is("input:enabled") || // Enabled input fields.
				$target.is("label.illustration-label") || // Illustration labels in multi-choice answers are clickable ...
				$target.is("img.illustration-img") || // ... as well as the images.
				$target.is("canvas") || // A canvas can be interacted with, e.g. the canvas used by the OpenLayers map.
				$target.is("input:enabled") || // Enabled input fields can be selected when clicked on.
				$target.is("#questionnaire-rewind") || // The "Go to previous question" button is disabled and hidden when there're no more questions. It is still considered clickable.
				$target.is("button:enabled") || // Enabled buttons.
				$target.is("a[href!=]"); // Anchors with valid links.

			if (!isClickable){
				var data = {
					"elementClass":elementClass,
					"url":window.location.href
				};
				analytics.fireEvent("action.invalidClick", data);
			}
		}
	}
	/**
	 * Fires an event when a user visits a category's page.
	 */
	function onVisitCategory(categoryId){
		var data = {
			"categoryId":categoryId
		};
		analytics.fireEvent("action.visitCategory", data);
	}
	/**
	 * Fires an event when a user visits a project.
	 */
	function onVisitProject(projectId){
		var data = {
			"projectId":projectId
		};
		analytics.fireEvent("action.visitProject", data);
	}
	/**
	 * Fires an action.onInternalLinkClicked event when the user clicks on one of
	 * of the various internal links e.g. Find photos, my profile, etc., otherwise
	 * fires an action.onExternalLinkClicked event.
	 */
	function onLinkClicked(){
		var url = $(this).attr("href");
		var isInternalLink =
			url.charAt(0) === "#" ||
			url.charAt(0) === "/" ||
			!url.indexOf("http://geotagx.org") ||
			!url.indexOf("https://geotagx.org");

		var eventName = isInternalLink ? "action.internalLinkClicked" : "action.externalLinkClicked";
		var data = {
			"elementUrl":url
		};
		analytics.fireEvent(eventName, data);
	}
	/**
	 * Fires an event when a user shares a category (on social media).
	 */
	function onShareCategory(){
		var data = {
			"categoryId":$("#share-category").data("name"),
			"elementUrl":$(this).attr("href")
		};
		analytics.fireEvent("action.shareCategory", data);
	}
	/**
	 * Fires an event when a user shares a project (on social media).
	 */
	function onShareProject(){
		var data = {
			"projectId":$("#share-project").data("name"),
			"elementUrl":$(this).attr("href")
		};
		analytics.fireEvent("action.shareProject", data);
	}
	/**
	 * Fires an event when a user clicks an element that starts a project.
	 */
	function onStartProject(){
		var url  = $(this).attr("href");
		var data = {
			"projectId":url.substr(9, url.indexOf("/", 9) - 9),
			"elementUrl":window.location.origin + url
		};
		analytics.fireEvent("action.startProject", data);
	}
})(jQuery);
<commit_msg>Refs #48 - Added a constraint to clicks on external links: Only external links on project profile pages are now tracked.<commit_after>/*
 * GeoTag-X page analytics.
 */
;(function($, undefined){
	"use strict";

	// When the script is loaded, we have to make sure the GTM script is
	// fully loaded and to do so, we check whether the window.analyticsListener
	// variable is defined. If it isn't we keep checking at intervals of 450ms
	// until the variable gets defined, or we ultimately give up after ~10 seconds.
	// When the GTM script is ready, we trigger the 'gtmready' custom event
	// which starts page-agnostic, as well as project-specific analytics.
	$(document).ready(function(){
		var maxWaitPeriod = 10000;
		var dt = 450;
		var timer = setInterval(function(){
			if (maxWaitPeriod <= 0)
				clearInterval(timer);
			else {
				if (window.analyticsListener){
					clearInterval(timer);
					$(document).trigger("gtmready");
				}
				else
					maxWaitPeriod -= dt;
			}
		}, dt);
	});

	// The analytics begins only when the Google Tag Manager (GTM) script is
	// ready, i.e. when the "gtmready" custom event has been fired.
	$(document).on("gtmready", function(){
		analytics.setGlobal("userId", $("body").data("user-id"));

		// Are we viewing a category's profile page?
		var $categoryProfile = $("#category-profile[data-short-name!=]");
		if ($categoryProfile.length > 0)
			onVisitCategory($categoryProfile.data("short-name"));

		// Or is it a project's profile page?
		var $projectProfile = $("#project-profile[data-short-name!=]");
		if ($projectProfile.length > 0)
			onVisitProject($projectProfile.data("short-name"));

		$("html").on("click.analytics", onElementClicked);
		$("#share-category > a").on("click.analytics", onShareCategory);
		$("#share-project > a").on("click.analytics", onShareProject);
		$("a[href!=]").on("click.analytics", onLinkClicked);
		$("a.project-launcher").on("click.analytics", onStartProject);
	});
	/**
	 * Fires an event when a user clicks the browser's "Back" button.
	 */
	function onGoingBackButtonClicked(){
		var data = {
			"url":null
		};
		analytics.fireEvent("action.goingBackButtonClicked", data);
	}
	/**
	 * Determines if the element that triggered this event is clickable. If it isn't
	 * then the action.invalidClick event is fired, otherwise no operation is performed.
	 */
	function onElementClicked(e){
		var $target = $(e.target);
		var elementClass = $target.attr("class");
		if (elementClass){
			var isClickable =
				$target.is(".clickable") || // A set of elements that aren't usually clickable but have been made so (e.g. when an event handler has been attached to them).
				$target.is("div.modal.fade") || // A modal underlay which can be used to close a modal.
				$target.is("input:enabled") || // Enabled input fields.
				$target.is("label.illustration-label") || // Illustration labels in multi-choice answers are clickable ...
				$target.is("img.illustration-img") || // ... as well as the images.
				$target.is("canvas") || // A canvas can be interacted with, e.g. the canvas used by the OpenLayers map.
				$target.is("input:enabled") || // Enabled input fields can be selected when clicked on.
				$target.is("#questionnaire-rewind") || // The "Go to previous question" button is disabled and hidden when there're no more questions. It is still considered clickable.
				$target.is("button:enabled") || // Enabled buttons.
				$target.is("a[href!=]"); // Anchors with valid links.

			if (!isClickable){
				var data = {
					"elementClass":elementClass,
					"url":window.location.href
				};
				analytics.fireEvent("action.invalidClick", data);
			}
		}
	}
	/**
	 * Fires an event when a user visits a category's page.
	 */
	function onVisitCategory(categoryId){
		var data = {
			"categoryId":categoryId
		};
		analytics.fireEvent("action.visitCategory", data);
	}
	/**
	 * Fires an event when a user visits a project.
	 */
	function onVisitProject(projectId){
		var data = {
			"projectId":projectId
		};
		analytics.fireEvent("action.visitProject", data);
	}
	/**
	 * Fires an action.onInternalLinkClicked event when the user clicks on one of
	 * of the various internal links e.g. Find photos, my profile, etc., otherwise
	 * fires an action.onExternalLinkClicked event.
	 */
	function onLinkClicked(){
		var url = $(this).attr("href");
		var isInternalLink =
			url.charAt(0) === "#" ||
			url.charAt(0) === "/" ||
			url.indexOf("http://geotagx.org") === 0 ||
			url.indexOf("https://geotagx.org") === 0;

		var data = {
			"elementUrl":url
		};
		if (isInternalLink)
			analytics.fireEvent("action.internalLinkClicked", data);
		else {
			// Clicks to external pages are only interesting if we're viewing a project's profile page.
			var $projectProfile = $("#project-profile[data-short-name!=]");
			if ($projectProfile.length > 0){
				data.projectId = $projectProfile.data("short-name");
				analytics.fireEvent("action.externalLinkClicked", data);
			}
		}
	}
	/**
	 * Fires an event when a user shares a category (on social media).
	 */
	function onShareCategory(){
		var data = {
			"categoryId":$("#share-category").data("name"),
			"elementUrl":$(this).attr("href")
		};
		analytics.fireEvent("action.shareCategory", data);
	}
	/**
	 * Fires an event when a user shares a project (on social media).
	 */
	function onShareProject(){
		var data = {
			"projectId":$("#share-project").data("name"),
			"elementUrl":$(this).attr("href")
		};
		analytics.fireEvent("action.shareProject", data);
	}
	/**
	 * Fires an event when a user clicks an element that starts a project.
	 */
	function onStartProject(){
		var url  = $(this).attr("href");
		var data = {
			"projectId":url.substr(9, url.indexOf("/", 9) - 9),
			"elementUrl":window.location.origin + url
		};
		analytics.fireEvent("action.startProject", data);
	}
})(jQuery);
<|endoftext|>


<commit_before>/** @jsx React.DOM */
var GamedayFrame = React.createClass({
  loadDataFromServer: function() {
    $.ajax({
      url: this.props.url,
      success: function(events) {
        this.setState({events: events});
      }.bind(this)
    });
  },
  getInitialState: function() {
    return {
      events: [],
      displayedEvents: []
    };
  },
  componentWillMount: function() {
    //this.loadDataFromServer();
    //setInterval(this.loadDataFromServer, this.props.pollInterval);
    this.setState({events: this.props.events})
  },
  render: function() {
    return (

        <GamedayNavbar
          events={this.state.events}
          onWebcastAdd={this.handleWebcastAdd}
          onWebcastReset={this.handleWebcastReset} />
        <VideoGrid events={this.state.displayedEvents} />

    );
  },
  handleWebcastAdd: function(eventModel) {
    var displayedEvents = this.state.displayedEvents;
    var newDisplayedEvents = displayedEvents.concat([eventModel]);
    this.setState({displayedEvents: newDisplayedEvents});
  },
  handleWebcastReset: function() {
    this.setState({displayedEvents: []});
  }
});

var GamedayNavbar = React.createClass({
  render: function() {
    return (

          <button type="button" className="navbar-toggle" data-toggle="collapse" data-target="#bs-example-navbar-collapse-1">
            Toggle navigation

          </button>
          Gameday

              Layouts

First

Second

Third

Separated link

            <WebcastDropdown
              events={this.props.events}
              onWebcastAdd={this.props.onWebcastAdd}
              onWebcastReset={this.props.onWebcastReset} />

Chat

Hashtags

Settings

    );
  }
});

var VideoGrid = React.createClass({
  render: function() {
    var videoCellNodes = this.props.events.map(function (eventModel, idx, events) {
      var vidHeight = (100/events.length).toString() + "%";
      var vidWidth = "100%";
      return (
        <VideoCell
          eventModel={eventModel}
          vidHeight={vidHeight}
          vidWidth={vidWidth} />
        );
    });
    return (

        {videoCellNodes}

    );
  },
});

var VideoCell = React.createClass({
  render: function() {
    if (this.props.eventModel) {
      if (this.props.eventModel.webcasts) {
        var src = "//www.youtube.com/embed/" + this.props.eventModel.webcasts[0].channel;
      }
    } else {
      var src = "";
    }
    var id = this.props.eventModel.name + "-1";
    return (

    );
  }
});

var WebcastDropdown = React.createClass({
  render: function() {
    var webcastListItems = [];
    for (var index in this.props.events) {
      webcastListItems.push(<WebcastListItem eventModel={this.props.events[index]} onWebcastAdd={this.props.onWebcastAdd} />)
    };
    return (

        Webcasts

          {webcastListItems}

          <BootstrapNavDropdownListItem handleClick={this.props.onWebcastReset}>Reset Webcasts</BootstrapNavDropdownListItem>

    );
  }
})

var WebcastListItem = React.createClass({
  handleClick: function() {
    this.props.onWebcastAdd(this.props.eventModel);
  },
  render: function() {
    return <BootstrapNavDropdownListItem handleClick={this.handleClick}>{this.props.eventModel.name}</BootstrapNavDropdownListItem>
  },
})

var BootstrapNavDropdownListItem = React.createClass({
  componentWillMount: function() {
    if (!this.props.a) {
      this.props.a = "#";
    }
  },
  handleClick: function() {
    if (this.props.handleClick) {
      this.props.handleClick();
      return false;
    }
  },
  render: function() {
    return (

{this.props.children}

    )
  },
})

var events = [
  {"key": "2014nh", "name": "BAE Granite State", "webcasts": [{"type": "youtube", "channel": "olhwB5grOtA"}]},
  {"key": "2014ct", "name": "UTC Regional", "webcasts": [{"type": "youtube", "channel": "FKpIWmsDPq4"}]},
  {"key": "2014az", "name": "Arizona!", "webcasts": [{"type": "youtube", "channel": "QZv70PG9eXM"}]}
]

React.renderComponent(
  <GamedayFrame events={events} pollInterval={20000} />,
  document.getElementById('content')
);<commit_msg>Better layouts.<commit_after>/** @jsx React.DOM */
var GamedayFrame = React.createClass({
  loadDataFromServer: function() {
    $.ajax({
      url: this.props.url,
      success: function(events) {
        this.setState({events: events});
      }.bind(this)
    });
  },
  getInitialState: function() {
    return {
      events: [],
      displayedEvents: []
    };
  },
  componentWillMount: function() {
    //this.loadDataFromServer();
    //setInterval(this.loadDataFromServer, this.props.pollInterval);
    this.setState({events: this.props.events})
  },
  render: function() {
    return (

        <GamedayNavbar
          events={this.state.events}
          onWebcastAdd={this.handleWebcastAdd}
          onWebcastReset={this.handleWebcastReset} />
        <VideoGrid events={this.state.displayedEvents} />

    );
  },
  handleWebcastAdd: function(eventModel) {
    var displayedEvents = this.state.displayedEvents;
    var newDisplayedEvents = displayedEvents.concat([eventModel]);
    this.setState({displayedEvents: newDisplayedEvents});
  },
  handleWebcastReset: function() {
    this.setState({displayedEvents: []});
  }
});

var GamedayNavbar = React.createClass({
  render: function() {
    return (

          <button type="button" className="navbar-toggle" data-toggle="collapse" data-target="#bs-example-navbar-collapse-1">
            Toggle navigation

          </button>
          Gameday

              Layouts

First

Second

Third

Separated link

            <WebcastDropdown
              events={this.props.events}
              onWebcastAdd={this.props.onWebcastAdd}
              onWebcastReset={this.props.onWebcastReset} />

Chat

Hashtags

Settings

    );
  }
});

var VideoGrid = React.createClass({
  render: function() {
    var videoCellNodes = this.props.events.map(function (eventModel, idx, events) {
      var vidHeight = (100/events.length).toString() + "%";
      var vidWidth = "100%";
      return (
        <VideoCell
          eventModel={eventModel}
          vidHeight={vidHeight}
          vidWidth={vidWidth} />
        );
    });
    switch (this.props.events.length) {
      case 0:
        var layout = <VideoCellLayoutZero />
        break;
      case 1:
        var layout = <VideoCellLayoutOne events={this.props.events} />
        break;
      case 2:
        var layout = <VideoCellLayoutTwo events={this.props.events} />
        break;
      case 3:
        var layout = <VideoCellLayoutThree events={this.props.events} />
        break;
    }
    return (

        {layout}

    );
  },
});

var VideoCellLayoutZero = React.createClass({
  render: function() {
    return (

No Videos!

      );
  }
});

var VideoCellLayoutOne = React.createClass({
  render: function() {
    if (this.props.events) {
      var eventModel = this.props.events[0];
    } else {
      var eventModel = null;
    }
    return (

            <VideoCell
              eventModel={eventModel}
              vidHeight="100%"
              vidWidth="100%" />

      );
  }
});

var VideoCellLayoutTwo = React.createClass({
  render: function() {
    return (

            <VideoCell
              eventModel={this.props.events[0]}
              vidHeight="100%"
              vidWidth="100%" />

            <VideoCell
              eventModel={this.props.events[1]}
              vidHeight="100%"
              vidWidth="100%" />

      );
  }
});

var VideoCellLayoutThree = React.createClass({
  render: function() {
    return (

            <VideoCell
              eventModel={this.props.events[0]}
              vidHeight="100%"
              vidWidth="100%" />

                <VideoCell
                  eventModel={this.props.events[1]}
                  vidHeight="50%"
                  vidWidth="100%" />

                <VideoCell
                  eventModel={this.props.events[2]}
                  vidHeight="50%"
                  vidWidth="100%" />

      );
  }
});

var VideoCell = React.createClass({
  render: function() {
    if (this.props.eventModel) {
      if (this.props.eventModel.webcasts) {
        var src = "//www.youtube.com/embed/" + this.props.eventModel.webcasts[0].channel;
        var id = this.props.eventModel.name + "-1";
      }
    } else {
      var src = "";
      var id = "blank";
    }

    return (

    );
  }
});

var WebcastDropdown = React.createClass({
  render: function() {
    var webcastListItems = [];
    for (var index in this.props.events) {
      webcastListItems.push(<WebcastListItem eventModel={this.props.events[index]} onWebcastAdd={this.props.onWebcastAdd} />)
    };
    return (

        Webcasts

          {webcastListItems}

          <BootstrapNavDropdownListItem handleClick={this.props.onWebcastReset}>Reset Webcasts</BootstrapNavDropdownListItem>

    );
  }
})

var WebcastListItem = React.createClass({
  handleClick: function() {
    this.props.onWebcastAdd(this.props.eventModel);
  },
  render: function() {
    return <BootstrapNavDropdownListItem handleClick={this.handleClick}>{this.props.eventModel.name}</BootstrapNavDropdownListItem>
  },
})

var BootstrapNavDropdownListItem = React.createClass({
  componentWillMount: function() {
    if (!this.props.a) {
      this.props.a = "#";
    }
  },
  handleClick: function() {
    if (this.props.handleClick) {
      this.props.handleClick();
      return false;
    }
  },
  render: function() {
    return (

{this.props.children}

    )
  },
})

var events = [
  {"key": "2014nh", "name": "BAE Granite State", "webcasts": [{"type": "youtube", "channel": "olhwB5grOtA"}]},
  {"key": "2014ct", "name": "UTC Regional", "webcasts": [{"type": "youtube", "channel": "FKpIWmsDPq4"}]},
  {"key": "2014az", "name": "Arizona!", "webcasts": [{"type": "youtube", "channel": "QZv70PG9eXM"}]}
]

React.renderComponent(
  <GamedayFrame events={events} pollInterval={20000} />,
  document.getElementById('content')
);<|endoftext|>


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

    function NodesCtrl($scope, $http, NodesSvc, nodes) {
        $scope.nodes = nodes;
        $scope.$expanded = {};

        $scope.loadNodes = function () {
            NodesSvc.all().then(function (data) {
                $scope.nodes = data;
            });
        };

        $scope.selectNode = function (node) {
            angular.forEach($scope.docTypes, function (docType) {
                if (docType.id === node.document.docType.id) {
                    node.document.docType = docType;
                }
            });
            $scope.n = node;
        };

        $scope.newNode = function () {
            $scope.n = {};
        };

        $scope.nodeHasChildren = function (node) {
            var ret = false;
            angular.forEach($scope.nodes, function (n) {
                if (n.parent_id === node.id) {
                    ret = true;
                    return false;
                }
            });
            return ret;
        };

        $scope.$on('nodes:updated', function () {
            $scope.loadNodes();
        });

        $scope.newNode();
    }

    window.app.controller('NodesCtrl', ['$scope', '$http', 'NodesSvc', 'nodes', NodesCtrl]);

    function NodeDetailCtrl($scope, $http, $state, NodesSvc, node, docTypes, templates) {
        $scope.node = node;
        $scope.docTypes = docTypes;
        $scope.templates = templates;

        $scope.saveNode = function () {
            var url = ($scope.node && $scope.node.id) ? '/cms/api/node/' + $scope.node.id : '/cms/api/nodes';
            $http.post(url, $scope.node).then(function () {
                $scope.$emit('nodes:updated');

                $state.transitionTo('nodes');
            });
        };

        $scope.deleteNode = function () {
            var url = '/cms/api/node/' + $scope.node.id;
            $http.delete(url).then(function () {
                $scope.$emit('nodes:updated');

                $state.transitionTo('nodes');
            });
        };
    }

    window.app.controller('NodeDetailCtrl', ['$scope', '$http', '$state', 'NodesSvc', 'node', 'docTypes', 'templates', NodeDetailCtrl]);

})(window);
<commit_msg>bugfix<commit_after>(function (window) {
    'use strict';

    function NodesCtrl($scope, $http, NodesSvc, nodes) {
        $scope.nodes = nodes;
        $scope.$expanded = {};

        $scope.loadNodes = function () {
            NodesSvc.all().then(function (data) {
                $scope.nodes = data;
            });
        };

        $scope.nodeHasChildren = function (node) {
            var ret = false;
            angular.forEach($scope.nodes, function (n) {
                if (n.parent_id === node.id) {
                    ret = true;
                    return false;
                }
            });
            return ret;
        };

        $scope.$on('nodes:updated', function () {
            $scope.loadNodes();
        });
    }

    window.app.controller('NodesCtrl', ['$scope', '$http', 'NodesSvc', 'nodes', NodesCtrl]);

    function NodeDetailCtrl($scope, $http, $state, NodesSvc, node, docTypes, templates) {
        angular.forEach(docTypes, function (docType) {
            if (docType.id === node.document.docType.id) {
                node.document.docType = docType;
            }
        });

        $scope.node = node;
        $scope.docTypes = docTypes;
        $scope.templates = templates;

        $scope.saveNode = function () {
            var url = ($scope.node && $scope.node.id) ? '/cms/api/node/' + $scope.node.id : '/cms/api/nodes';
            $http.post(url, $scope.node).then(function () {
                $scope.$emit('nodes:updated');

                $state.transitionTo('nodes');
            });
        };

        $scope.deleteNode = function () {
            var url = '/cms/api/node/' + $scope.node.id;
            $http.delete(url).then(function () {
                $scope.$emit('nodes:updated');

                $state.transitionTo('nodes');
            });
        };
    }

    window.app.controller('NodeDetailCtrl', ['$scope', '$http', '$state', 'NodesSvc', 'node', 'docTypes', 'templates', NodeDetailCtrl]);

})(window);
<|endoftext|>