<commit_before>/**
 * @license
 * Copyright 2020 Google LLC
 * SPDX-License-Identifier: Apache-2.0
 */

/**
 * @fileoverview Entry point for the Blockly playground.
 * @author samelh@google.com (Sam El-Husseini)
 */

import * as Blockly from 'blockly/core';
import * as BlocklyJS from 'blockly/javascript';
import * as BlocklyPython from 'blockly/python';
import * as BlocklyLua from 'blockly/lua';
import * as BlocklyDart from 'blockly/dart';
import * as BlocklyPHP from 'blockly/php';

import {renderPlayground, renderCheckbox, renderCodeTab} from './ui';
import {addCodeEditor} from './monaco';
import {addGUIControls} from './options';
import {LocalStorageState} from './state';
import {id} from './id';

import toolboxCategories from '../toolboxCategories';
import toolboxSimple from '../toolboxSimple';
import {toolboxTestBlocks, toolboxTestBlocksInit} from '@blockly/block-test';

/**
 * @typedef {function(!HTMLElement,!Blockly.BlocklyOptions):Blockly.Workspace}
 */
let CreateWorkspaceFn;

/**
 * @typedef {{
 *     generate: function():void,
 *     state: ?,
 *     tabElement: !HTMLElement,
 * }}
 */
let PlaygroundTab;

/**
 * @typedef {Blockly.utils.toolbox.ToolboxDefinition} BlocklyToolbox
 */

/**
 * @typedef {Object} PlaygroundConfig
 * @property {boolean} [auto] Whether or not to automatically import, and run
 *     the XML and code generators.
 * @property {Object<string,BlocklyToolbox>} [toolboxes] The toolbox registry.
 */

/**
 * @typedef {{
 *     state: ?,
 *     addAction:function(string,function(!Blockly.Workspace):void,string=):
 *         dat.GUI,
 *     addCheckboxAction:function(string,
 *         function(!Blockly.Workspace,boolean):void,string=,boolean=):dat.GUI,
 *     addGenerator: function(string,!Blockly.Generator,string=):void,
 *     getCurrentTab: function():!PlaygroundTab,
 *     getGUI: function():!dat.GUI,
 *     getWorkspace: function():!Blockly.WorkspaceSvg,
 * }}
 */
let PlaygroundAPI;

/**
 * Create the Blockly playground.
 * @param {!HTMLElement} container Container element.
 * @param {CreateWorkspaceFn} createWorkspace A workspace creation method called
 *     every time the toolbox is re-configured.
 * @param {Blockly.BlocklyOptions} defaultOptions The default workspace options
 *     to use.
 * @param {PlaygroundConfig=} config Optional Playground config.
 * @param {string=} vsEditorPath Optional editor path.
 * @return {Promise<PlaygroundAPI>} A promise to the playground API.
 */
export function createPlayground(container, createWorkspace,
    defaultOptions, config = {}, vsEditorPath) {
  const {blocklyDiv, minimizeButton, monacoDiv, guiContainer, playgroundDiv, tabButtons, tabsDiv} =
    renderPlayground(container);

  // Load the code editor.
  return addCodeEditor(monacoDiv, {
    model: null,
    language: 'xml',
    minimap: {
      enabled: false,
    },
    theme: 'vs-dark',
    scrollBeyondLastLine: false,
    automaticLayout: true,
  }, vsEditorPath).then((editor) => {
    let workspace;

    // Create a model for displaying errors.
    const errorModel = window.monaco.editor.createModel('');
    const editorXmlContextKey = editor.createContextKey('isEditorXml', true);

    // Load / Save playground state.
    const playgroundState = new LocalStorageState(`playgroundState_${id}`, {
      activeTab: 'XML',
      autoGenerate: config && config.auto != undefined ? config.auto : true,
      workspaceXml: '',
    });
    playgroundState.load();

    /**
     * Register a generator and create a new code tab for it.
     * @param {string} name The generator label.
     * @param {string} language The monaco language to use.
     * @param {Blockly.Generator} generator The Blockly generator.
     * @param {boolean=} isReadOnly Whether the editor should be set to
     *     read-only mode.
     * @return {!PlaygroundTab} An object that represents the newly created tab.
     */
    function registerGenerator(name, language, generator, isReadOnly) {
      const tabElement = renderCodeTab(name);
      tabElement.setAttribute('data-tab', name);
      tabsDiv.appendChild(tabElement);

      // Create a monaco editor model for each tab.
      const model = window.monaco.editor.createModel('', language);
      const state = {
        name,
        model,
        language,
        viewState: undefined,
      };

      /**
       * Call the generator, displaying an error message if it fails.
       */
      function generate() {
        let text;
        let generateModel = model;
        try {
          text = generator(workspace);
        } catch (e) {
          console.error(e);
          text = e.message;
          generateModel = errorModel;
          editor.updateOptions({
            wordWrap: true,
          });
        }
        generateModel.pushEditOperations([],
            [{range: generateModel.getFullModelRange(), text}], () => null);
        editor.setModel(generateModel);
        editor.setSelection(new window.monaco.Range(0, 0, 0, 0));
      }

      const tab = {
        generate,
        state,
        tabElement,
      };
      return tab;
    }

    /**
     * Set the active tab.
     * @param {!PlaygroundTab} tab The new tab.
     */
    const setActiveTab = (tab) => {
      currentTab = tab;
      currentGenerate = tab.generate;
      const isXml = tab.state.name == 'XML';
      editor.setModel(currentTab.state.model);
      editor.updateOptions({
        readOnly: !isXml,
        wordWrap: false,
      });

      // Update tab UI.
      Object.values(tabs).forEach((t) =>
        t.tabElement.style.background =
          (t.tabElement == tab.tabElement) ? '#1E1E1E' : '#2D2D2D');
      // Update editor state.
      editorXmlContextKey.set(isXml);
      playgroundState.set('activeTab', tab.state.name);
      playgroundState.save();
    };

    /**
     * Call the current generate method if we are in 'auto' mode. In addition,
     * persist the current workspace xml regardless of which tab we are in.
     */
    const updateEditor = () => {
      if (playgroundState.get('autoGenerate')) {
        if (initialWorkspaceXml && isFirstLoad) {
          isFirstLoad = false;
          try {
            Blockly.Xml.domToWorkspace(
                Blockly.Xml.textToDom(initialWorkspaceXml), workspace);
          } catch (e) {
            console.warn('Failed to auto import.', e);
          }
        }

        if (currentGenerate) {
          currentGenerate();
        }

        let code = '';
        try {
          code = Blockly.Xml.domToPrettyText(Blockly.Xml.workspaceToDom(
              workspace));
        } catch (e) {
          console.warn('Failed to auto save.', e);
        }
        playgroundState.set('workspaceXml', code);
        playgroundState.save();
      }
    };

    // Register default tabs.
    const tabs = {
      'XML': registerGenerator('XML', 'xml', (ws) =>
        Blockly.Xml.domToPrettyText(Blockly.Xml.workspaceToDom(ws))),
      'JavaScript': registerGenerator('JavaScript', 'javascript',
          (ws) => (BlocklyJS || Blockly.JavaScript).workspaceToCode(ws), true),
      'Python': registerGenerator('Python', 'python',
          (ws) => (BlocklyPython || Blockly.Python).workspaceToCode(ws), true),
      'Dart': registerGenerator('Dart', 'javascript',
          (ws) => (BlocklyDart || Blockly.Dart).workspaceToCode(ws), true),
      'Lua': registerGenerator('Lua', 'lua',
          (ws) => (BlocklyLua || Blockly.Lua).workspaceToCode(ws), true),
      'PHP': registerGenerator('PHP', 'php',
          (ws) => (BlocklyPHP || Blockly.PHP).workspaceToCode(ws), true),
    };

    // Handle tab click.
    tabsDiv.addEventListener('click', (e) => {
      const target = /** @type {HTMLElement} */ (e.target);
      const tabName = target.getAttribute('data-tab');
      if (!tabName) {
        // Not a tab.
        return;
      }
      const tab = tabs[tabName];

      // Save current tab state (eg: scroll position).
      currentTab.state.viewState = editor.saveViewState();

      setActiveTab(tab);
      updateEditor();

      // Restore tab state (eg: scroll position).
      editor.restoreViewState(currentTab.state.viewState);
      editor.focus();
    });

    // Initialized saved XML and bind change listener.
    const initialWorkspaceXml = playgroundState.get('workspaceXml') || '';
    const xmlTab = tabs['XML'];
    const xmlModel = xmlTab.state.model;
    let isFirstLoad = true;
    xmlModel.setValue(initialWorkspaceXml);
    xmlModel.onDidChangeContent(() => {
      playgroundState.set('workspaceXml', xmlModel.getValue());
      playgroundState.save();
    });

    // Set the initial tab as active.
    const activeTab = playgroundState.get('activeTab');
    let currentTab = tabs[activeTab];
    let currentGenerate;
    if (currentTab) {
      setActiveTab(currentTab);
    }

    // Load the GUI controls.
    const gui = addGUIControls((options) => {
      workspace = createWorkspace(blocklyDiv, options);

      // Initialize the test toolbox.
      toolboxTestBlocksInit(/** @type {!Blockly.WorkspaceSvg} */ (workspace));

      updateEditor();
      workspace.addChangeListener((e) => {
        if (e.type !== 'ui') {
          updateEditor();
        }
      });
      return workspace;
    }, defaultOptions, {
      disableResize: true,
      toolboxes: config.toolboxes || {
        'categories': toolboxCategories,
        'simple': toolboxSimple,
        'test blocks': toolboxTestBlocks,
      },
    });

    // Move the GUI Element to the gui container.
    const guiElement = gui.domElement;
    guiElement.removeChild(guiElement.firstChild);
    guiElement.style.position = 'relative';
    guiElement.style.minWidth = '100%';
    guiContainer.appendChild(guiElement);

    // Create minimize button
    minimizeButton.addEventListener('click', (e) => {
      if (playgroundDiv.style.display === 'none') {
        playgroundDiv.style.display = 'flex';
        minimizeButton.textContent = 'Collapse';
      } else {
        playgroundDiv.style.display = 'none';
        minimizeButton.textContent = 'Expand';
      }

      Blockly.svgResize(workspace);
    });

    // Playground API.

    /**
     * Get the current GUI controls.
     * @return {!dat.GUI} The GUI controls.
     */
    const getGUI = function() {
      return gui;
    };

    /**
     * Get the current workspace.
     * @return {!Blockly.WorkspaceSvg} The Blockly workspace.
     */
    const getWorkspace = function() {
      return workspace;
    };

    /**
     * Get the current tab.
     * @return {!PlaygroundTab} The current tab.
     */
    const getCurrentTab = function() {
      return currentTab;
    };

    /**
     * Add a generator tab.
     * @param {string} label The label of the generator tab.
     * @param {Blockly.Generator} generator The Blockly generator.
     * @param {string=} language Optional editor language, defaults to
     *     'javascript'.
     */
    const addGenerator = function(label, generator, language) {
      if (!label || !generator) {
        throw Error('usage: addGenerator(label, generator, language?);');
      }
      tabs[label] = registerGenerator(label, language || 'javascript',
          (ws) => generator.workspaceToCode(ws), true);
      if (activeTab === label) {
        // Set the new generator as the current tab if it is currently active.
        // This occurs when a dynamically added generator is active and the page
        // is reloaded.
        setActiveTab(tabs[label]);
      }
    };

    const playground = {
      state: playgroundState,
      addAction: (/** @type {?} */ (gui)).addAction,
      addCheckboxAction: (/** @type {?} */ (gui)).addCheckboxAction,
      addGenerator,
      getCurrentTab,
      getGUI,
      getWorkspace,
    };

    // Add tab buttons.
    registerTabButtons(editor, playground, tabButtons, updateEditor);

    // Register editor commands.
    registerEditorCommands(editor, playground);

    return playground;
  });
}

/**
 * Register tab buttons.
 * @param {monaco.editor.IStandaloneCodeEditor} editor The monaco editor.
 * @param {PlaygroundAPI} playground The current playground.
 * @param {!HTMLElement} tabButtons Tab buttons element wrapper.
 * @param {function():void} updateEditor Update Editor method.
 */
function registerTabButtons(editor, playground, tabButtons, updateEditor) {
  const [autoGenerateCheckbox, autoGenerateLabel] =
    renderCheckbox('autoGenerate', 'Auto');
  /** @type {HTMLInputElement} */ (autoGenerateCheckbox).checked =
    playground.state.get('autoGenerate');
  autoGenerateCheckbox.addEventListener('change', (e) => {
    const inputTarget = /** @type {HTMLInputElement} */ (e.target);
    playground.state.set('autoGenerate', !!inputTarget.checked);
    playground.state.save();

    updateEditor();
  });
  tabButtons.appendChild(autoGenerateCheckbox);
  tabButtons.appendChild(autoGenerateLabel);
}

/**
 * Register editor commands / shortcuts.
 * @param {monaco.editor.IStandaloneCodeEditor} editor The monaco editor.
 * @param {PlaygroundAPI} playground The current playground.
 */
function registerEditorCommands(editor, playground) {
  const load = () => {
    if (playground.getCurrentTab().state.name !== 'XML') {
      return;
    }
    const xml = editor.getModel().getValue();
    const workspace = playground.getWorkspace();
    Blockly.Xml.domToWorkspace(Blockly.Xml.textToDom(xml), workspace);
  };
  const save = () => {
    playground.getCurrentTab().generate();
  };

  // Add XMl Import action (only available on the XML tab).
  editor.addAction({
    id: 'import-xml',
    label: 'Import from XML',
    keybindings: [
      window.monaco.KeyMod.CtrlCmd | window.monaco.KeyCode.Enter,
    ],
    precondition: 'isEditorXml',
    contextMenuGroupId: 'playground',
    contextMenuOrder: 0,
    run: load,
  });
  // Add XMl Export action (only available on the XML tab).
  editor.addAction({
    id: 'export-xml',
    label: 'Export to XML',
    keybindings: [
      window.monaco.KeyMod.CtrlCmd | window.monaco.KeyCode.KEY_S,
    ],
    precondition: 'isEditorXml',
    contextMenuGroupId: 'playground',
    contextMenuOrder: 1,
    run: save,
  });
  editor.addAction({
    id: 'clean-xml',
    label: 'Clean XML',
    precondition: 'isEditorXml',
    contextMenuGroupId: 'playground',
    contextMenuOrder: 2,
    run: () => {
      const model = editor.getModel();
      const text = model.getValue().replace(/ (x|y|id)="[^"]*"/gmi, '');
      model.pushEditOperations([],
          [{range: model.getFullModelRange(), text}], () => null);
      editor.setSelection(new window.monaco.Range(0, 0, 0, 0));
    },
  });
  // Add a Generator generate action.
  editor.addAction({
    id: 'generate',
    label: 'Generate',
    keybindings: [
      window.monaco.KeyMod.CtrlCmd | window.monaco.KeyCode.KEY_S,
    ],
    precondition: '!isEditorXml',
    contextMenuGroupId: 'playground',
    contextMenuOrder: 1,
    run: save,
  });
  document.addEventListener('keydown', (e) => {
    const ctrlCmd = e.metaKey || e.ctrlKey;
    if (ctrlCmd && e.keyCode === Blockly.utils.KeyCodes.S) {
      // Save.
      save();
      e.preventDefault();
    } else if (ctrlCmd && e.keyCode === Blockly.utils.KeyCodes.ENTER) {
      // Load.
      load();
      e.preventDefault();
    }
  });
}
<commit_msg>Add download screenshot option to playground (#588)<commit_after>/**
 * @license
 * Copyright 2020 Google LLC
 * SPDX-License-Identifier: Apache-2.0
 */

/**
 * @fileoverview Entry point for the Blockly playground.
 * @author samelh@google.com (Sam El-Husseini)
 */

import * as Blockly from 'blockly/core';
import * as BlocklyJS from 'blockly/javascript';
import * as BlocklyPython from 'blockly/python';
import * as BlocklyLua from 'blockly/lua';
import * as BlocklyDart from 'blockly/dart';
import * as BlocklyPHP from 'blockly/php';

import {renderPlayground, renderCheckbox, renderCodeTab} from './ui';
import {addCodeEditor} from './monaco';
import {addGUIControls} from './options';
import {LocalStorageState} from './state';
import {id} from './id';

import {downloadWorkspaceScreenshot} from '../screenshot';
import toolboxCategories from '../toolboxCategories';
import toolboxSimple from '../toolboxSimple';
import {toolboxTestBlocks, toolboxTestBlocksInit} from '@blockly/block-test';

/**
 * @typedef {function(!HTMLElement,!Blockly.BlocklyOptions):Blockly.Workspace}
 */
let CreateWorkspaceFn;

/**
 * @typedef {{
 *     generate: function():void,
 *     state: ?,
 *     tabElement: !HTMLElement,
 * }}
 */
let PlaygroundTab;

/**
 * @typedef {Blockly.utils.toolbox.ToolboxDefinition} BlocklyToolbox
 */

/**
 * @typedef {Object} PlaygroundConfig
 * @property {boolean} [auto] Whether or not to automatically import, and run
 *     the XML and code generators.
 * @property {Object<string,BlocklyToolbox>} [toolboxes] The toolbox registry.
 */

/**
 * @typedef {{
 *     state: ?,
 *     addAction:function(string,function(!Blockly.Workspace):void,string=):
 *         dat.GUI,
 *     addCheckboxAction:function(string,
 *         function(!Blockly.Workspace,boolean):void,string=,boolean=):dat.GUI,
 *     addGenerator: function(string,!Blockly.Generator,string=):void,
 *     getCurrentTab: function():!PlaygroundTab,
 *     getGUI: function():!dat.GUI,
 *     getWorkspace: function():!Blockly.WorkspaceSvg,
 * }}
 */
let PlaygroundAPI;

/**
 * Create the Blockly playground.
 * @param {!HTMLElement} container Container element.
 * @param {CreateWorkspaceFn} createWorkspace A workspace creation method called
 *     every time the toolbox is re-configured.
 * @param {Blockly.BlocklyOptions} defaultOptions The default workspace options
 *     to use.
 * @param {PlaygroundConfig=} config Optional Playground config.
 * @param {string=} vsEditorPath Optional editor path.
 * @return {Promise<PlaygroundAPI>} A promise to the playground API.
 */
export function createPlayground(container, createWorkspace,
    defaultOptions, config = {}, vsEditorPath) {
  const {blocklyDiv, minimizeButton, monacoDiv, guiContainer, playgroundDiv, tabButtons, tabsDiv} =
    renderPlayground(container);

  // Load the code editor.
  return addCodeEditor(monacoDiv, {
    model: null,
    language: 'xml',
    minimap: {
      enabled: false,
    },
    theme: 'vs-dark',
    scrollBeyondLastLine: false,
    automaticLayout: true,
  }, vsEditorPath).then((editor) => {
    let workspace;

    // Create a model for displaying errors.
    const errorModel = window.monaco.editor.createModel('');
    const editorXmlContextKey = editor.createContextKey('isEditorXml', true);

    // Load / Save playground state.
    const playgroundState = new LocalStorageState(`playgroundState_${id}`, {
      activeTab: 'XML',
      autoGenerate: config && config.auto != undefined ? config.auto : true,
      workspaceXml: '',
    });
    playgroundState.load();

    /**
     * Register a generator and create a new code tab for it.
     * @param {string} name The generator label.
     * @param {string} language The monaco language to use.
     * @param {Blockly.Generator} generator The Blockly generator.
     * @param {boolean=} isReadOnly Whether the editor should be set to
     *     read-only mode.
     * @return {!PlaygroundTab} An object that represents the newly created tab.
     */
    function registerGenerator(name, language, generator, isReadOnly) {
      const tabElement = renderCodeTab(name);
      tabElement.setAttribute('data-tab', name);
      tabsDiv.appendChild(tabElement);

      // Create a monaco editor model for each tab.
      const model = window.monaco.editor.createModel('', language);
      const state = {
        name,
        model,
        language,
        viewState: undefined,
      };

      /**
       * Call the generator, displaying an error message if it fails.
       */
      function generate() {
        let text;
        let generateModel = model;
        try {
          text = generator(workspace);
        } catch (e) {
          console.error(e);
          text = e.message;
          generateModel = errorModel;
          editor.updateOptions({
            wordWrap: true,
          });
        }
        generateModel.pushEditOperations([],
            [{range: generateModel.getFullModelRange(), text}], () => null);
        editor.setModel(generateModel);
        editor.setSelection(new window.monaco.Range(0, 0, 0, 0));
      }

      const tab = {
        generate,
        state,
        tabElement,
      };
      return tab;
    }

    /**
     * Set the active tab.
     * @param {!PlaygroundTab} tab The new tab.
     */
    const setActiveTab = (tab) => {
      currentTab = tab;
      currentGenerate = tab.generate;
      const isXml = tab.state.name == 'XML';
      editor.setModel(currentTab.state.model);
      editor.updateOptions({
        readOnly: !isXml,
        wordWrap: false,
      });

      // Update tab UI.
      Object.values(tabs).forEach((t) =>
        t.tabElement.style.background =
          (t.tabElement == tab.tabElement) ? '#1E1E1E' : '#2D2D2D');
      // Update editor state.
      editorXmlContextKey.set(isXml);
      playgroundState.set('activeTab', tab.state.name);
      playgroundState.save();
    };

    /**
     * Call the current generate method if we are in 'auto' mode. In addition,
     * persist the current workspace xml regardless of which tab we are in.
     */
    const updateEditor = () => {
      if (playgroundState.get('autoGenerate')) {
        if (initialWorkspaceXml && isFirstLoad) {
          isFirstLoad = false;
          try {
            Blockly.Xml.domToWorkspace(
                Blockly.Xml.textToDom(initialWorkspaceXml), workspace);
          } catch (e) {
            console.warn('Failed to auto import.', e);
          }
        }

        if (currentGenerate) {
          currentGenerate();
        }

        let code = '';
        try {
          code = Blockly.Xml.domToPrettyText(Blockly.Xml.workspaceToDom(
              workspace));
        } catch (e) {
          console.warn('Failed to auto save.', e);
        }
        playgroundState.set('workspaceXml', code);
        playgroundState.save();
      }
    };

    // Register default tabs.
    const tabs = {
      'XML': registerGenerator('XML', 'xml', (ws) =>
        Blockly.Xml.domToPrettyText(Blockly.Xml.workspaceToDom(ws))),
      'JavaScript': registerGenerator('JavaScript', 'javascript',
          (ws) => (BlocklyJS || Blockly.JavaScript).workspaceToCode(ws), true),
      'Python': registerGenerator('Python', 'python',
          (ws) => (BlocklyPython || Blockly.Python).workspaceToCode(ws), true),
      'Dart': registerGenerator('Dart', 'dart',
          (ws) => (BlocklyDart || Blockly.Dart).workspaceToCode(ws), true),
      'Lua': registerGenerator('Lua', 'lua',
          (ws) => (BlocklyLua || Blockly.Lua).workspaceToCode(ws), true),
      'PHP': registerGenerator('PHP', 'php',
          (ws) => (BlocklyPHP || Blockly.PHP).workspaceToCode(ws), true),
    };

    // Handle tab click.
    tabsDiv.addEventListener('click', (e) => {
      const target = /** @type {HTMLElement} */ (e.target);
      const tabName = target.getAttribute('data-tab');
      if (!tabName) {
        // Not a tab.
        return;
      }
      const tab = tabs[tabName];

      // Save current tab state (eg: scroll position).
      currentTab.state.viewState = editor.saveViewState();

      setActiveTab(tab);
      updateEditor();

      // Restore tab state (eg: scroll position).
      editor.restoreViewState(currentTab.state.viewState);
      editor.focus();
    });

    // Initialized saved XML and bind change listener.
    const initialWorkspaceXml = playgroundState.get('workspaceXml') || '';
    const xmlTab = tabs['XML'];
    const xmlModel = xmlTab.state.model;
    let isFirstLoad = true;
    xmlModel.setValue(initialWorkspaceXml);
    xmlModel.onDidChangeContent(() => {
      playgroundState.set('workspaceXml', xmlModel.getValue());
      playgroundState.save();
    });

    // Set the initial tab as active.
    const activeTab = playgroundState.get('activeTab');
    let currentTab = tabs[activeTab];
    let currentGenerate;
    if (currentTab) {
      setActiveTab(currentTab);
    }

    // Load the GUI controls.
    const gui = addGUIControls((options) => {
      workspace = createWorkspace(blocklyDiv, options);

      // Initialize the test toolbox.
      toolboxTestBlocksInit(/** @type {!Blockly.WorkspaceSvg} */ (workspace));

      // Add download screenshot option.
      const prevConfigureContextMenu = workspace.configureContextMenu;
      workspace.configureContextMenu = (menuOptions, e) => {
        prevConfigureContextMenu?.call(null, menuOptions, e);

        const screenshotOption = {
          text: 'Download Screenshot',
          enabled: workspace.getTopBlocks().length,
          callback: function() {
            downloadWorkspaceScreenshot(workspace);
          },
        };
        menuOptions.push(screenshotOption);
      };

      updateEditor();
      workspace.addChangeListener((e) => {
        if (e.type !== 'ui') {
          updateEditor();
        }
      });
      return workspace;
    }, defaultOptions, {
      disableResize: true,
      toolboxes: config.toolboxes || {
        'categories': toolboxCategories,
        'simple': toolboxSimple,
        'test blocks': toolboxTestBlocks,
      },
    });

    // Move the GUI Element to the gui container.
    const guiElement = gui.domElement;
    guiElement.removeChild(guiElement.firstChild);
    guiElement.style.position = 'relative';
    guiElement.style.minWidth = '100%';
    guiContainer.appendChild(guiElement);

    // Create minimize button
    minimizeButton.addEventListener('click', (e) => {
      if (playgroundDiv.style.display === 'none') {
        playgroundDiv.style.display = 'flex';
        minimizeButton.textContent = 'Collapse';
      } else {
        playgroundDiv.style.display = 'none';
        minimizeButton.textContent = 'Expand';
      }

      Blockly.svgResize(workspace);
    });

    // Playground API.

    /**
     * Get the current GUI controls.
     * @return {!dat.GUI} The GUI controls.
     */
    const getGUI = function() {
      return gui;
    };

    /**
     * Get the current workspace.
     * @return {!Blockly.WorkspaceSvg} The Blockly workspace.
     */
    const getWorkspace = function() {
      return workspace;
    };

    /**
     * Get the current tab.
     * @return {!PlaygroundTab} The current tab.
     */
    const getCurrentTab = function() {
      return currentTab;
    };

    /**
     * Add a generator tab.
     * @param {string} label The label of the generator tab.
     * @param {Blockly.Generator} generator The Blockly generator.
     * @param {string=} language Optional editor language, defaults to
     *     'javascript'.
     */
    const addGenerator = function(label, generator, language) {
      if (!label || !generator) {
        throw Error('usage: addGenerator(label, generator, language?);');
      }
      tabs[label] = registerGenerator(label, language || 'javascript',
          (ws) => generator.workspaceToCode(ws), true);
      if (activeTab === label) {
        // Set the new generator as the current tab if it is currently active.
        // This occurs when a dynamically added generator is active and the page
        // is reloaded.
        setActiveTab(tabs[label]);
      }
    };

    const playground = {
      state: playgroundState,
      addAction: (/** @type {?} */ (gui)).addAction,
      addCheckboxAction: (/** @type {?} */ (gui)).addCheckboxAction,
      addGenerator,
      getCurrentTab,
      getGUI,
      getWorkspace,
    };

    // Add tab buttons.
    registerTabButtons(editor, playground, tabButtons, updateEditor);

    // Register editor commands.
    registerEditorCommands(editor, playground);

    return playground;
  });
}

/**
 * Register tab buttons.
 * @param {monaco.editor.IStandaloneCodeEditor} editor The monaco editor.
 * @param {PlaygroundAPI} playground The current playground.
 * @param {!HTMLElement} tabButtons Tab buttons element wrapper.
 * @param {function():void} updateEditor Update Editor method.
 */
function registerTabButtons(editor, playground, tabButtons, updateEditor) {
  const [autoGenerateCheckbox, autoGenerateLabel] =
    renderCheckbox('autoGenerate', 'Auto');
  /** @type {HTMLInputElement} */ (autoGenerateCheckbox).checked =
    playground.state.get('autoGenerate');
  autoGenerateCheckbox.addEventListener('change', (e) => {
    const inputTarget = /** @type {HTMLInputElement} */ (e.target);
    playground.state.set('autoGenerate', !!inputTarget.checked);
    playground.state.save();

    updateEditor();
  });
  tabButtons.appendChild(autoGenerateCheckbox);
  tabButtons.appendChild(autoGenerateLabel);
}

/**
 * Register editor commands / shortcuts.
 * @param {monaco.editor.IStandaloneCodeEditor} editor The monaco editor.
 * @param {PlaygroundAPI} playground The current playground.
 */
function registerEditorCommands(editor, playground) {
  const load = () => {
    if (playground.getCurrentTab().state.name !== 'XML') {
      return;
    }
    const xml = editor.getModel().getValue();
    const workspace = playground.getWorkspace();
    Blockly.Xml.domToWorkspace(Blockly.Xml.textToDom(xml), workspace);
  };
  const save = () => {
    playground.getCurrentTab().generate();
  };

  // Add XMl Import action (only available on the XML tab).
  editor.addAction({
    id: 'import-xml',
    label: 'Import from XML',
    keybindings: [
      window.monaco.KeyMod.CtrlCmd | window.monaco.KeyCode.Enter,
    ],
    precondition: 'isEditorXml',
    contextMenuGroupId: 'playground',
    contextMenuOrder: 0,
    run: load,
  });
  // Add XMl Export action (only available on the XML tab).
  editor.addAction({
    id: 'export-xml',
    label: 'Export to XML',
    keybindings: [
      window.monaco.KeyMod.CtrlCmd | window.monaco.KeyCode.KEY_S,
    ],
    precondition: 'isEditorXml',
    contextMenuGroupId: 'playground',
    contextMenuOrder: 1,
    run: save,
  });
  editor.addAction({
    id: 'clean-xml',
    label: 'Clean XML',
    precondition: 'isEditorXml',
    contextMenuGroupId: 'playground',
    contextMenuOrder: 2,
    run: () => {
      const model = editor.getModel();
      const text = model.getValue().replace(/ (x|y|id)="[^"]*"/gmi, '');
      model.pushEditOperations([],
          [{range: model.getFullModelRange(), text}], () => null);
      editor.setSelection(new window.monaco.Range(0, 0, 0, 0));
    },
  });
  // Add a Generator generate action.
  editor.addAction({
    id: 'generate',
    label: 'Generate',
    keybindings: [
      window.monaco.KeyMod.CtrlCmd | window.monaco.KeyCode.KEY_S,
    ],
    precondition: '!isEditorXml',
    contextMenuGroupId: 'playground',
    contextMenuOrder: 1,
    run: save,
  });
  document.addEventListener('keydown', (e) => {
    const ctrlCmd = e.metaKey || e.ctrlKey;
    if (ctrlCmd && e.keyCode === Blockly.utils.KeyCodes.S) {
      // Save.
      save();
      e.preventDefault();
    } else if (ctrlCmd && e.keyCode === Blockly.utils.KeyCodes.ENTER) {
      // Load.
      load();
      e.preventDefault();
    }
  });
}
<|endoftext|>


<commit_before>E2.p = E2.plugins["from_mesh_custom_shader"] = function(core, node)
{
	this.desc = 'Auto-generate a shader embedding user-defined main bodies tailored to correctly and optimally render the supplied mesh.';

	this.input_slots = [
		 { name: 'mesh', dt: core.datatypes.MESH, desc: 'Mesh to adapt the shader to.' },
		 { name: 'material', dt: core.datatypes.MATERIAL, desc: 'The surface material.' }
	];

	this.output_slots = [
		{ name: 'shader', dt: core.datatypes.SHADER, desc: 'The resulting shader.' }
	];

	this.state = {
		vs_src: '',
		ps_src: '',
		changed: false,
		slot_ids: {}
	};

	this.core = core;
	this.node = node;
	this.gl = core.renderer.context;
	this.shader = null;
	this.slot_data = [];
};

E2.p.prototype.reset = function()
{
};

E2.p.prototype.open_editor = function(self, src_id, title, done_func, dest) { return function(e)
{
	var diag = make('span');
	var src = $('<textarea></textarea>');

	diag.css({
		'margin': '0px',
		'padding': '2px',
	});

	src.css({
		'margin': '0px',
		'padding': '0px',
		'margin-top': '2px',
		'border': 'none',
		'width': '455px',
		'height': '400px',
		'resize': 'none',
		'font-size': '9pt',
		'font-family': 'Monospace',
		'overflow': 'scroll',
		'word-wrap': 'normal',
		'white-space': 'pre',
		'background-color': '#ddd'
	});

	src.val(self.state[src_id]);

	diag.append(src);

	var btn_span = make('span');
	var comp_btn = $('<input id="comp_btn" type="button" value="Compile" title="Click to rebuild the shader." />');
	var add_btn = $('<input id="add_btn" type="button" value="Add slot" title="Click to add new shader input slot." />');
	var rem_btn = $('<input id="rem_btn" type="button" value="Remove slot" title="Click to remove the selected slot(s)." />');
	var slot_list = $('<select size="4" />');

	slot_list.css({
		'border': 'none',
		'width': '457px',
		'margin-left': '2px',
		'background-color': '#ddd'
	});

	btn_span.css('width', '455px');
	btn_span.append(comp_btn);
	btn_span.append(add_btn);
	btn_span.append(rem_btn);

	diag.append(make('br'));
	diag.append(btn_span);
	diag.append(make('br'));
	diag.append(slot_list);

	// Rebuild slot list.
	for(var ident in self.state.slot_ids)
	{
		var slot = self.state.slot_ids[ident];

		slot_list.append($('<option>', { value: slot.id }).text(ident));
	}

	comp_btn.click(function(e)
	{
		dest(src.val());
		done_func(null);
	});

	add_btn.click(function(self) { return function(e)
	{
		var diag2 = make('div');
		var inp = $('<input type="input" />');
		var dt_sel = $('<select />');
		var dts = self.core.datatypes;
		var allowed = [dts.FLOAT, dts.TEXTURE, dts.COLOR, dts.MATRIX, dts.VECTOR];

		for(var i = 0, len = allowed.length; i < len; i++)
		{
			var dt = allowed[i];

			dt_sel.append($('<option>', { value : dt.id }).text(dt.name));
		}

		var l1 = make('span');
		var lbl = $('
Identifier:
');

		inp.css('width', '220px');

		lbl.css({
			'padding-top': '3px',
			'float': 'left',
			'width': '80px'
		});

		l1.append(lbl);
		l1.append(inp);

		diag2.append(l1);
		diag2.append(make('br'));

		var l2 = make('span');

		lbl = $('
Data type:
');

		lbl.css({
			'padding-top': '3px',
			'float': 'left',
			'width': '78px'
		});

		l2.append(lbl);
		l2.append(dt_sel);

		diag2.append(l2);

		var finish_func = function(self) { return function()
		{
			var sname = inp.val();
			var cdt = parseInt(dt_sel.val());

			for(var sdt in self.core.datatypes)
			{
				sdt = self.core.datatypes[sdt];

				if(sdt.id === cdt)
				{
					var cid = sname.replace(/[^\w]/gi, '_').toLowerCase();
					var sid = self.node.add_slot(E2.slot_type.input, { name: cid, dt: sdt });

					self.state.slot_ids[cid] = { id: sid, dt: sdt, uniform: null };
					self.slot_data[sid] = self.core.get_default_value(E2.slot_type.input, sdt);
					slot_list.append($('<option>', { value: sid }).text(cid));
					break;
				}
			}

			diag2.dialog('close');
			// self.rebuild_shader();
			self.dirty = true;
		}}(self);

		diag2.dialog({
			width: 360,
			height: 170,
			modal: true,
			title: 'Create new slot.',
			show: 'slide',
			hide: 'slide',
			buttons: {
				'OK': function()
				{
					finish_func();
				},
				'Cancel': function()
				{
					$(this).dialog('close');
				}
			},
			open: function()
			{
				diag2.keyup(function(e)
				{
					if(e.keyCode === $.ui.keyCode.ENTER)
						finish_func();
				});
			}
		});
	}}(self));

	rem_btn.click(function(self) { return function(e)
	{
		var sel = slot_list.val();

		if(sel === null)
			return;

		var cid = slot_list.find("option[value='" + sel + "']").remove().text();

		sel = parseInt(sel);

		delete self.state.slot_ids[cid];
		self.node.remove_slot(E2.slot_type.input, sel);
		self.dirty = true;
	}}(self));

	diag.dialog({
		width: 460,
		height: 150,
		modal: true,
		title: 'Edit ' + title + ' shader.',
		show: 'slide',
		hide: 'slide',
		buttons: {
			'OK': function()
			{
				dest(src.val());
				done_func(diag);
			},
			'Cancel': function()
			{
				$(this).dialog('close');
			}
		}
	});
}};

E2.p.prototype.create_ui = function()
{
	var layout = make('div');
	var inp_vs = $('<input id="vs_btn" type="button" value="Vertex" title="Click to edit the vertex shader source." />');
	var inp_ps = $('<input id="ps_btn" type="button" value="Pixel" title="Click to edit the pixel shader source." />');

	inp_vs.css('width', '55px');
	inp_ps.css('width', '55px');

	var done_func = function(self) { return function(diag)
	{
		self.updated = self.dirty = true;

		if(diag)
			diag.dialog('close');
	}}(this);

	inp_vs.click(this.open_editor(this, 'vs_src', 'vertex', done_func, function(self) { return function(v)
	{
		self.state.vs_src = v;
		self.state.changed = true;
	}}(this)));

	inp_ps.click(this.open_editor(this, 'ps_src', 'pixel', done_func, function(self) { return function(v)
	{
		self.state.ps_src = v;
		self.state.changed = true;
	}}(this)));

	layout.append(inp_vs);
	layout.append(make('br'));
	layout.append(inp_ps);

	return layout;
};

E2.p.prototype.connection_changed = function(on, conn, slot)
{
	if(!on && slot.type === E2.slot_type.input)
	{
		if(slot.index === 0)
		{
			this.mesh = null;
			this.shader = null;
		}
		else if(slot.index === 1)
		{
			this.material = null;
		}
	}
};

E2.p.prototype.rebuild_shader = function()
{
	var u_vs = '';
	var u_ps = '';
	var st = this.state;
	var gl = this.gl;
	var dts = this.core.datatypes;

	for(var ident in st.slot_ids)
	{
		var dt = '';
		var slot = st.slot_ids[ident];
		var dtid = slot.dt.id;

		if(dtid === dts.FLOAT.id)
			dt = 'float';
		else if(dtid === dts.TEXTURE.id)
			dt = 'sampler2D';
		else if(dtid === dts.COLOR.id)
			dt = 'vec4';
		else if(dtid === dts.MATRIX.id)
			dt = 'mat4';
		else if(dtid === dts.VECTOR.id)
			dt = 'vec3';

		if(dtid !== dts.TEXTURE.id)
			u_vs += 'uniform ' + dt + ' ' + ident + ';\n';

		u_ps += 'uniform ' + dt + ' ' + ident + ';\n';
	}

	this.shader = ComposeShader(null, this.mesh, this.material, u_vs, u_ps, st.vs_src && st.changed ? st.vs_src : null, st.ps_src && st.changed ? st.ps_src : null);

	for(var ident in st.slot_ids)
	{
		var slot = st.slot_ids[ident];

		slot.uniform = gl.getUniformLocation(this.shader.program, ident);
	}

	this.shader.apply_uniforms_custom = function(self) { return function()
	{
		var tex_slot = 1;
		var sd = self.slot_data;
		var dts = self.core.datatypes;

		for(var ident in st.slot_ids)
		{
			var slot = st.slot_ids[ident];

			if(slot.uniform === null || sd[slot.id] === null)
				continue;

			var dtid = slot.dt.id;

			if(dtid === dts.FLOAT.id)
				gl.uniform1f(slot.uniform, sd[slot.id]);
			else if(dtid === dts.TEXTURE.id)
			{
				gl.uniform1i(slot.uniform, tex_slot);
				sd[slot.id].enable(gl.TEXTURE0 + tex_slot);
				tex_slot++;
			}
			else if(dtid === dts.COLOR.id)
				gl.uniform4fv(slot.uniform, new Float32Array(sd[slot.id].rgba));
			else if(dtid === dts.MATRIX.id)
				gl.uniformMatrix4fv(slot.uniform, false, sd[slot.id]);
			else if(dtid === dts.VECTOR.id)
				gl.uniform3fv(slot.uniform, new Float32Array(sd[slot.id]));
		}
	}}(this);

	if(st.vs_src === '')
		st.vs_src = this.shader.vs_c_src;

	if(st.ps_src === '')
		st.ps_src = this.shader.ps_c_src;
};

E2.p.prototype.update_input = function(slot, data)
{
	if(slot.uid === undefined)
	{
		if(slot.index === 0)
		{
			if(this.mesh !== data)
			{
				this.mesh = data;
				this.dirty = true;
			}
		}
		else if(slot.index === 1)
		{
			this.material = data;
		}
	}
	else
	{
		this.slot_data[slot.uid] = data;
	}
};

E2.p.prototype.update_state = function(delta_t)
{
	if(!this.mesh)
		return;

	var caps = Material.get_caps_hash(this.mesh, this.material);

	if(!this.dirty && this.caps_hash === caps)
	{
		this.shader.material = this.material;
		return;
	}

	if(this.dirty || this.caps_hash !== caps)
		this.rebuild_shader();

	this.caps_hash = caps;
	this.updated = true;
	this.dirty = false;
};

E2.p.prototype.update_output = function(slot)
{
	return this.shader;
};

E2.p.prototype.state_changed = function(ui)
{
	if(!ui)
	{
		this.mesh = null;
		this.material = null;
		this.caps_mask = '';
		this.dirty = false;
	}
};
<commit_msg>From mesh custom shader: Updated the shader rebuild logic to ensure that the shader is rebuild next frame if the current frame update is missed.<commit_after>E2.p = E2.plugins["from_mesh_custom_shader"] = function(core, node)
{
	this.desc = 'Auto-generate a shader embedding user-defined main bodies tailored to correctly and optimally render the supplied mesh.';

	this.input_slots = [
		 { name: 'mesh', dt: core.datatypes.MESH, desc: 'Mesh to adapt the shader to.' },
		 { name: 'material', dt: core.datatypes.MATERIAL, desc: 'The surface material.' }
	];

	this.output_slots = [
		{ name: 'shader', dt: core.datatypes.SHADER, desc: 'The resulting shader.' }
	];

	this.state = {
		vs_src: '',
		ps_src: '',
		changed: false,
		slot_ids: {}
	};

	this.core = core;
	this.node = node;
	this.gl = core.renderer.context;
	this.shader = null;
	this.slot_data = [];
};

E2.p.prototype.reset = function()
{
};

E2.p.prototype.open_editor = function(self, src_id, title, done_func, dest) { return function(e)
{
	var diag = make('span');
	var src = $('<textarea></textarea>');

	diag.css({
		'margin': '0px',
		'padding': '2px',
	});

	src.css({
		'margin': '0px',
		'padding': '0px',
		'margin-top': '2px',
		'border': 'none',
		'width': '455px',
		'height': '400px',
		'resize': 'none',
		'font-size': '9pt',
		'font-family': 'Monospace',
		'overflow': 'scroll',
		'word-wrap': 'normal',
		'white-space': 'pre',
		'background-color': '#ddd'
	});

	src.val(self.state[src_id]);

	diag.append(src);

	var btn_span = make('span');
	var comp_btn = $('<input id="comp_btn" type="button" value="Compile" title="Click to rebuild the shader." />');
	var add_btn = $('<input id="add_btn" type="button" value="Add slot" title="Click to add new shader input slot." />');
	var rem_btn = $('<input id="rem_btn" type="button" value="Remove slot" title="Click to remove the selected slot(s)." />');
	var slot_list = $('<select size="4" />');

	slot_list.css({
		'border': 'none',
		'width': '457px',
		'margin-left': '2px',
		'background-color': '#ddd'
	});

	btn_span.css('width', '455px');
	btn_span.append(comp_btn);
	btn_span.append(add_btn);
	btn_span.append(rem_btn);

	diag.append(make('br'));
	diag.append(btn_span);
	diag.append(make('br'));
	diag.append(slot_list);

	// Rebuild slot list.
	for(var ident in self.state.slot_ids)
	{
		var slot = self.state.slot_ids[ident];

		slot_list.append($('<option>', { value: slot.id }).text(ident));
	}

	comp_btn.click(function(e)
	{
		dest(src.val());
		done_func(null);
	});

	add_btn.click(function(self) { return function(e)
	{
		var diag2 = make('div');
		var inp = $('<input type="input" />');
		var dt_sel = $('<select />');
		var dts = self.core.datatypes;
		var allowed = [dts.FLOAT, dts.TEXTURE, dts.COLOR, dts.MATRIX, dts.VECTOR];

		for(var i = 0, len = allowed.length; i < len; i++)
		{
			var dt = allowed[i];

			dt_sel.append($('<option>', { value : dt.id }).text(dt.name));
		}

		var l1 = make('span');
		var lbl = $('
Identifier:
');

		inp.css('width', '220px');

		lbl.css({
			'padding-top': '3px',
			'float': 'left',
			'width': '80px'
		});

		l1.append(lbl);
		l1.append(inp);

		diag2.append(l1);
		diag2.append(make('br'));

		var l2 = make('span');

		lbl = $('
Data type:
');

		lbl.css({
			'padding-top': '3px',
			'float': 'left',
			'width': '78px'
		});

		l2.append(lbl);
		l2.append(dt_sel);

		diag2.append(l2);

		var finish_func = function(self) { return function()
		{
			var sname = inp.val();
			var cdt = parseInt(dt_sel.val());

			for(var sdt in self.core.datatypes)
			{
				sdt = self.core.datatypes[sdt];

				if(sdt.id === cdt)
				{
					var cid = sname.replace(/[^\w]/gi, '_').toLowerCase();
					var sid = self.node.add_slot(E2.slot_type.input, { name: cid, dt: sdt });

					self.state.slot_ids[cid] = { id: sid, dt: sdt, uniform: null };
					self.slot_data[sid] = self.core.get_default_value(E2.slot_type.input, sdt);
					slot_list.append($('<option>', { value: sid }).text(cid));
					break;
				}
			}

			diag2.dialog('close');
			// self.rebuild_shader();
			self.dirty = true;
		}}(self);

		diag2.dialog({
			width: 360,
			height: 170,
			modal: true,
			title: 'Create new slot.',
			show: 'slide',
			hide: 'slide',
			buttons: {
				'OK': function()
				{
					finish_func();
				},
				'Cancel': function()
				{
					$(this).dialog('close');
				}
			},
			open: function()
			{
				diag2.keyup(function(e)
				{
					if(e.keyCode === $.ui.keyCode.ENTER)
						finish_func();
				});
			}
		});
	}}(self));

	rem_btn.click(function(self) { return function(e)
	{
		var sel = slot_list.val();

		if(sel === null)
			return;

		var cid = slot_list.find("option[value='" + sel + "']").remove().text();

		sel = parseInt(sel);

		delete self.state.slot_ids[cid];
		self.node.remove_slot(E2.slot_type.input, sel);
		self.dirty = true;
	}}(self));

	diag.dialog({
		width: 460,
		height: 150,
		modal: true,
		title: 'Edit ' + title + ' shader.',
		show: 'slide',
		hide: 'slide',
		buttons: {
			'OK': function()
			{
				dest(src.val());
				done_func(diag);
			},
			'Cancel': function()
			{
				$(this).dialog('close');
			}
		}
	});
}};

E2.p.prototype.create_ui = function()
{
	var layout = make('div');
	var inp_vs = $('<input id="vs_btn" type="button" value="Vertex" title="Click to edit the vertex shader source." />');
	var inp_ps = $('<input id="ps_btn" type="button" value="Pixel" title="Click to edit the pixel shader source." />');

	inp_vs.css('width', '55px');
	inp_ps.css('width', '55px');

	var done_func = function(self) { return function(diag)
	{
		self.updated = self.dirty = true;
		self.node.queued_update = 1;

		if(diag)
			diag.dialog('close');
	}}(this);

	inp_vs.click(this.open_editor(this, 'vs_src', 'vertex', done_func, function(self) { return function(v)
	{
		self.state.vs_src = v;
		self.state.changed = true;
	}}(this)));

	inp_ps.click(this.open_editor(this, 'ps_src', 'pixel', done_func, function(self) { return function(v)
	{
		self.state.ps_src = v;
		self.state.changed = true;
	}}(this)));

	layout.append(inp_vs);
	layout.append(make('br'));
	layout.append(inp_ps);

	return layout;
};

E2.p.prototype.connection_changed = function(on, conn, slot)
{
	if(!on && slot.type === E2.slot_type.input)
	{
		if(slot.index === 0)
		{
			this.mesh = null;
			this.shader = null;
		}
		else if(slot.index === 1)
		{
			this.material = null;
		}
	}
};

E2.p.prototype.rebuild_shader = function()
{
	var u_vs = '';
	var u_ps = '';
	var st = this.state;
	var gl = this.gl;
	var dts = this.core.datatypes;

	for(var ident in st.slot_ids)
	{
		var dt = '';
		var slot = st.slot_ids[ident];
		var dtid = slot.dt.id;

		if(dtid === dts.FLOAT.id)
			dt = 'float';
		else if(dtid === dts.TEXTURE.id)
			dt = 'sampler2D';
		else if(dtid === dts.COLOR.id)
			dt = 'vec4';
		else if(dtid === dts.MATRIX.id)
			dt = 'mat4';
		else if(dtid === dts.VECTOR.id)
			dt = 'vec3';

		if(dtid !== dts.TEXTURE.id)
			u_vs += 'uniform ' + dt + ' ' + ident + ';\n';

		u_ps += 'uniform ' + dt + ' ' + ident + ';\n';
	}

	this.shader = ComposeShader(null, this.mesh, this.material, u_vs, u_ps, st.vs_src && st.changed ? st.vs_src : null, st.ps_src && st.changed ? st.ps_src : null);

	for(var ident in st.slot_ids)
	{
		var slot = st.slot_ids[ident];

		slot.uniform = gl.getUniformLocation(this.shader.program, ident);
	}

	this.shader.apply_uniforms_custom = function(self) { return function()
	{
		var tex_slot = 1;
		var sd = self.slot_data;
		var dts = self.core.datatypes;

		for(var ident in st.slot_ids)
		{
			var slot = st.slot_ids[ident];

			if(slot.uniform === null || sd[slot.id] === null)
				continue;

			var dtid = slot.dt.id;

			if(dtid === dts.FLOAT.id)
				gl.uniform1f(slot.uniform, sd[slot.id]);
			else if(dtid === dts.TEXTURE.id)
			{
				gl.uniform1i(slot.uniform, tex_slot);
				sd[slot.id].enable(gl.TEXTURE0 + tex_slot);
				tex_slot++;
			}
			else if(dtid === dts.COLOR.id)
				gl.uniform4fv(slot.uniform, new Float32Array(sd[slot.id].rgba));
			else if(dtid === dts.MATRIX.id)
				gl.uniformMatrix4fv(slot.uniform, false, sd[slot.id]);
			else if(dtid === dts.VECTOR.id)
				gl.uniform3fv(slot.uniform, new Float32Array(sd[slot.id]));
		}
	}}(this);

	if(st.vs_src === '')
		st.vs_src = this.shader.vs_c_src;

	if(st.ps_src === '')
		st.ps_src = this.shader.ps_c_src;
};

E2.p.prototype.update_input = function(slot, data)
{
	if(slot.uid === undefined)
	{
		if(slot.index === 0)
		{
			if(this.mesh !== data)
			{
				this.mesh = data;
				this.dirty = true;
			}
		}
		else if(slot.index === 1)
		{
			this.material = data;
		}
	}
	else
	{
		this.slot_data[slot.uid] = data;
	}
};

E2.p.prototype.update_state = function(delta_t)
{
	if(!this.mesh)
		return;

	var caps = Material.get_caps_hash(this.mesh, this.material);

	if(!this.dirty && this.caps_hash === caps)
	{
		this.shader.material = this.material;
		return;
	}

	if(this.dirty || this.caps_hash !== caps)
		this.rebuild_shader();

	this.caps_hash = caps;
	this.updated = true;
	this.dirty = false;
};

E2.p.prototype.update_output = function(slot)
{
	return this.shader;
};

E2.p.prototype.state_changed = function(ui)
{
	if(!ui)
	{
		this.mesh = null;
		this.material = null;
		this.caps_mask = '';
		this.dirty = false;
	}
};
<|endoftext|>


<commit_before>AppUtil = {};

AppUtil.run = function (app) {
  var image = Images.findOne({_id: app.imageId});
  // Delete old container if one already exists
  Docker.removeContainer(app.name, function (err) {
    if (err) { console.error(err); }
    Docker.runContainer(app, image, function (err, container) {
      if (err) { throw err; }
      Docker.getContainerData(container.id, function (err, data) {
        if (err) { console.error(err); }
        // Set a delay for app to spin up
        Meteor.setTimeout(function () {
          Apps.update(app._id, {$set: {
            docker: data,
            status: 'READY'
          }});
        }, 2500);
      });
    });
  });
};

AppUtil.restartHelper = function (app) {
  if (app.docker && app.docker.Id) {
    Docker.restartContainer(app.docker.Id, function (err) {
      if (err) { console.error(err); }
      Docker.getContainerData(app.docker.Id, function (err, data) {
        if (err) { console.error(err); }
        Apps.update(app._id, {$set: {
          status: 'READY',
          docker: data
        }});
        // Use dig to refresh the DNS
        exec('/usr/bin/dig ' + app.name + '.kite @172.17.42.1', function(err, stdout, stderr) {
          console.log(err);
          console.log(stdout);
          console.log(stderr);
        });
      });
    });
  }
};

AppUtil.restart = function (appId) {
  var app = Apps.findOne(appId);
  if (app && app.docker) {
    Apps.update(app._id, {$set: {
      status: 'STARTING'
    }});
    AppUtil.restartHelper(app);
  }
};

AppUtil.remove = function (appId) {
  var app = Apps.findOne(appId);
  if (app.docker) {
    Apps.remove({_id: appId});
    Docker.removeContainer(app.docker.Id, function (err) {
      if (err) { console.error(err); }
      var appPath = path.join(KITE_PATH, app.name);
      Util.deleteFolder(appPath);
      Docker.removeBindFolder(app.name, function () {
        console.log('Deleted Kite ' + app.name + ' directory.');
      });
    });
  }
};

AppUtil.configVar = function (appId, configVars) {
  Apps.update(appId, {$set: {
    config: configVars,
    status: 'STARTING'
  }});
  var app = Apps.findOne({_id: appId});
  AppUtil.run(app);
};

AppUtil.logs = function (appId) {
  var app = Apps.findOne(appId);
  if (app.docker && app.docker.Id) {
    var container = docker.getContainer(app.docker.Id);
    container.logs({follow: false, stdout: true, stderr: true, timestamps: true, tail: 300}, function (err, response) {
      if (err) { throw err; }
      Apps.update(app._id, {
        $set: {
          logs: []
        }
      });
      var logs = [];
      response.setEncoding('utf8');
      response.on('data', function (line) {
        logs.push(convert.toHtml(line.slice(8)));
        Apps.update(app._id, {
          $set: {
            logs: logs
          }
        });
      });
      response.on('end', function () {});
    });
  }
};

AppUtil.recover = function () {
  var apps = Apps.find({}).fetch();
  _.each(apps, function (app) {
    // Update the app with the latest container info
    if (!app.docker) {
      return;
    }
    var container = docker.getContainer(app.docker.Id);
    container.inspect(function (err, data) {
      if (app.status !== 'STARTING' && data && data.State && !data.State.Running) {
        console.log('Restarting: ' + app.name);
        console.log(app.docker.Id);
        AppUtil.restartHelper(app, function (err) {
          if (err) { console.error(err); }
        });
      }
    });
  });
};
<commit_msg>Turned off log timestamp.<commit_after>AppUtil = {};

AppUtil.run = function (app) {
  var image = Images.findOne({_id: app.imageId});
  // Delete old container if one already exists
  Docker.removeContainer(app.name, function (err) {
    if (err) { console.error(err); }
    Docker.runContainer(app, image, function (err, container) {
      if (err) { throw err; }
      Docker.getContainerData(container.id, function (err, data) {
        if (err) { console.error(err); }
        // Set a delay for app to spin up
        Meteor.setTimeout(function () {
          Apps.update(app._id, {$set: {
            docker: data,
            status: 'READY'
          }});
        }, 2500);
      });
    });
  });
};

AppUtil.restartHelper = function (app) {
  if (app.docker && app.docker.Id) {
    Docker.restartContainer(app.docker.Id, function (err) {
      if (err) { console.error(err); }
      Docker.getContainerData(app.docker.Id, function (err, data) {
        if (err) { console.error(err); }
        Apps.update(app._id, {$set: {
          status: 'READY',
          docker: data
        }});
        // Use dig to refresh the DNS
        exec('/usr/bin/dig ' + app.name + '.kite @172.17.42.1', function(err, stdout, stderr) {
          console.log(err);
          console.log(stdout);
          console.log(stderr);
        });
      });
    });
  }
};

AppUtil.restart = function (appId) {
  var app = Apps.findOne(appId);
  if (app && app.docker) {
    Apps.update(app._id, {$set: {
      status: 'STARTING'
    }});
    AppUtil.restartHelper(app);
  }
};

AppUtil.remove = function (appId) {
  var app = Apps.findOne(appId);
  if (app.docker) {
    Apps.remove({_id: appId});
    Docker.removeContainer(app.docker.Id, function (err) {
      if (err) { console.error(err); }
      var appPath = path.join(KITE_PATH, app.name);
      Util.deleteFolder(appPath);
      Docker.removeBindFolder(app.name, function () {
        console.log('Deleted Kite ' + app.name + ' directory.');
      });
    });
  }
};

AppUtil.configVar = function (appId, configVars) {
  Apps.update(appId, {$set: {
    config: configVars,
    status: 'STARTING'
  }});
  var app = Apps.findOne({_id: appId});
  AppUtil.run(app);
};

AppUtil.logs = function (appId) {
  var app = Apps.findOne(appId);
  if (app.docker && app.docker.Id) {
    var container = docker.getContainer(app.docker.Id);
    container.logs({follow: false, stdout: true, stderr: true, timestamps: false, tail: 300}, function (err, response) {
      if (err) { throw err; }
      Apps.update(app._id, {
        $set: {
          logs: []
        }
      });
      var logs = [];
      response.setEncoding('utf8');
      response.on('data', function (line) {
        logs.push(convert.toHtml(line.slice(8)));
        Apps.update(app._id, {
          $set: {
            logs: logs
          }
        });
      });
      response.on('end', function () {});
    });
  }
};

AppUtil.recover = function () {
  var apps = Apps.find({}).fetch();
  _.each(apps, function (app) {
    // Update the app with the latest container info
    if (!app.docker) {
      return;
    }
    var container = docker.getContainer(app.docker.Id);
    container.inspect(function (err, data) {
      if (app.status !== 'STARTING' && data && data.State && !data.State.Running) {
        console.log('Restarting: ' + app.name);
        console.log(app.docker.Id);
        AppUtil.restartHelper(app, function (err) {
          if (err) { console.error(err); }
        });
      }
    });
  });
};
<|endoftext|>


<commit_before>import { graphql } from 'react-apollo';
import { compose } from 'recompose';
import personQuery from './personQuery';
import loginMutation from './loginMutation';
import registerMutation from './registerMutation';
import changePasswordMutation from './changePasswordMutation';
import forgotPasswordMutation from './forgotPasswordMutation';
import resetPasswordMutation from './resetPasswordMutation';

// TODO: Set login token in local storage
const loginAction = graphql(loginMutation, {
  props: ({ mutate }) => ({
    login: ({ email, password } = {}) => {
      console.log('set email and password encrypted in local storage, have requests pick up on that for headers', {
        email,
        password,
      });
      return mutate({
        variables: {
          email,
          password,
        },
      });
    },
  }),
});

const registerAction = graphql(registerMutation, {
  props: ({ mutate }) => ({
    register: (params = {}) => {
      const {
        email,
        password,
        firstName,
        lastName,
      } = params;

      mutate({
        variables: {
          email,
          password,
          firstName,
          lastName,
        },
      });
    },
  }),
});

const changePasswordAction = graphql(changePasswordMutation, {
  props: ({ mutate }) => ({
    changePassword: (params = {}) => {
      const {
        oldPassword,
        newPassword,
      } = params;

      mutate({
        variables: {
          oldPassword,
          newPassword,
        },
      });
    },
  }),
});

const forgotPasswordAction = graphql(forgotPasswordMutation, {
  props: ({ mutate }) => ({
    forgotPassword: (params = {}) => {
      const {
        email,
        sourceURL,
      } = params;

      mutate({
        variables: {
          email,
          sourceURL,
        },
      });
    },
  }),
});

const resetPasswordAction = graphql(resetPasswordMutation, {
  props: ({ mutate }) => ({
    resetPassword: (params = {}) => {
      const {
        token,
        newPassword,
      } = params;

      mutate({
        variables: {
          token,
          newPassword,
        },
      });
    },
  }),
});

const user = graphql(personQuery, {
  props: ({ data: { person } }) => ({
    user: person,
  }),
});

export default compose(
  loginAction,
  registerAction,
  changePasswordAction,
  forgotPasswordAction,
  resetPasswordAction,
  user,
);
<commit_msg>Store encrypted email and password<commit_after>import { graphql } from 'react-apollo';
import { compose } from 'recompose';
import {
  AsyncStorage,
} from 'react-native';
import personQuery from './personQuery';
import loginMutation from './loginMutation';
import registerMutation from './registerMutation';
import changePasswordMutation from './changePasswordMutation';
import forgotPasswordMutation from './forgotPasswordMutation';
import resetPasswordMutation from './resetPasswordMutation';

// TODO: Set login token in local storage
const loginAction = graphql(loginMutation, {
  props: ({ mutate }) => ({
    login: async ({ email, password } = {}) => {
      try {
        const result = await mutate({
          variables: {
            email,
            password,
          },
        });

        await AsyncStorage.setItem('authToken', `${btoa(email)}::${btoa(password)}`);
        return result;
      } catch (err) {
        throw err;
      }
    },
  }),
});

const registerAction = graphql(registerMutation, {
  props: ({ mutate }) => ({
    register: (params = {}) => {
      const {
        email,
        password,
        firstName,
        lastName,
      } = params;

      mutate({
        variables: {
          email,
          password,
          firstName,
          lastName,
        },
      });
    },
  }),
});

const changePasswordAction = graphql(changePasswordMutation, {
  props: ({ mutate }) => ({
    changePassword: (params = {}) => {
      const {
        oldPassword,
        newPassword,
      } = params;

      mutate({
        variables: {
          oldPassword,
          newPassword,
        },
      });
    },
  }),
});

const forgotPasswordAction = graphql(forgotPasswordMutation, {
  props: ({ mutate }) => ({
    forgotPassword: (params = {}) => {
      const {
        email,
        sourceURL,
      } = params;

      mutate({
        variables: {
          email,
          sourceURL,
        },
      });
    },
  }),
});

const resetPasswordAction = graphql(resetPasswordMutation, {
  props: ({ mutate }) => ({
    resetPassword: (params = {}) => {
      const {
        token,
        newPassword,
      } = params;

      mutate({
        variables: {
          token,
          newPassword,
        },
      });
    },
  }),
});

const user = graphql(personQuery, {
  props: ({ data: { person } }) => ({
    user: person,
  }),
});

export default compose(
  loginAction,
  registerAction,
  changePasswordAction,
  forgotPasswordAction,
  resetPasswordAction,
  user,
);
<|endoftext|>


<commit_before>//Copyright (C) 2012 Kory Nunn

//Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

//The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

//THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

/*

    This code is not formatted for readability, but rather run-speed and to assist compilers.

    However, the code's intention should be transparent.

    *** IE SUPPORT ***

    If you require this library to work in IE7, add the following after declaring crel.

    var testDiv = document.createElement('div'),
        testLabel = document.createElement('label');

    testDiv.setAttribute('class', 'a');
    testDiv['className'] !== 'a' ? crel.attrMap['class'] = 'className':undefined;
    testDiv.setAttribute('name','a');
    testDiv['name'] !== 'a' ? crel.attrMap['name'] = function(element, value){
        element.id = value;
    }:undefined;

    testLabel.setAttribute('for', 'a');
    testLabel['htmlFor'] !== 'a' ? crel.attrMap['for'] = 'htmlFor':undefined;

*/

(function (root, factory) {
    if (typeof exports === 'object') {
        module.exports = factory();
    } else if (typeof define === 'function' && define.amd) {
        define(factory);
    } else {
        root.crel = factory();
    }
}(this, function () {
    var fn = 'function',
        obj = 'object',
        nodeType = 'nodeType',
        textContent = 'textContent',
        setAttribute = 'setAttribute',
        attrMapString = 'attrMap',
        isNodeString = 'isNode',
        isElementString = 'isElement',
        d = typeof document === obj ? document : {},
        isType = function(a, type){
            return typeof a === type;
        },
        isNode = typeof Node === fn ? function (object) {
            return object instanceof Node;
        } :
        // in IE <= 8 Node is an object, obviously..
        function(object){
            return object &&
                isType(object, obj) &&
                (nodeType in object) &&
                isType(object.ownerDocument,obj);
        },
        isElement = function (object) {
            return crel[isNodeString](object) && object[nodeType] === 1;
        },
        isArray = function(a){
            return a instanceof Array;
        },
        appendChild = function(element, child) {
            if (isArray(child)) {
                child.map(function(subChild){
                    appendChild(element, subChild);
                });
                return;
            }
            if(!crel[isNodeString](child)){
                child = d.createTextNode(child);
            }
            element.appendChild(child);
        };

    function crel(){
        var args = arguments, //Note: assigned to a variable to assist compilers. Saves about 40 bytes in closure compiler. Has negligable effect on performance.
            element = args[0],
            child,
            settings = args[1],
            childIndex = 2,
            argumentsLength = args.length,
            attributeMap = crel[attrMapString];

        element = crel[isElementString](element) ? element : d.createElement(element);
        // shortcut
        if(argumentsLength === 1){
            return element;
        }

        if(!isType(settings,obj) || crel[isNodeString](settings) || isArray(settings)) {
            --childIndex;
            settings = null;
        }

        // shortcut if there is only one child that is a string
        if((argumentsLength - childIndex) === 1 && isType(args[childIndex], 'string') && element[textContent] !== undefined){
            element[textContent] = args[childIndex];
        }else{
            for(; childIndex < argumentsLength; ++childIndex){
                child = args[childIndex];

                if(child !== null){
                    appendChild(element, child);
                }
            }
        }

        for(var key in settings){
            if(!attributeMap[key]){
                if(isType(settings[key],fn)){
                    element[key] = settings[key];
                }else{
                    element[setAttribute](key, settings[key]);
                }
            }else{
                var attr = attributeMap[key];
                if(typeof attr === fn){
                    attr(element, settings[key]);
                }else{
                    element[setAttribute](attr, settings[key]);
                }
            }
        }

        return element;
    }

    // Used for mapping one kind of attribute to the supported version of that in bad browsers.
    crel[attrMapString] = {};

    crel[isElementString] = isElement;

    crel[isNodeString] = isNode;

    if(typeof Proxy !== 'undefined'){
        crel.proxy = new Proxy(crel, {
            get: function(target, key){
                !(key in crel) && (crel[key] = crel.bind(null, key));
                return crel[key];
            }
        });
    }

    return crel;
}));
<commit_msg>Crel: Replace ´typeof´ tests with isType()<commit_after>//Copyright (C) 2012 Kory Nunn

//Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

//The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

//THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

/*

    This code is not formatted for readability, but rather run-speed and to assist compilers.

    However, the code's intention should be transparent.

    *** IE SUPPORT ***

    If you require this library to work in IE7, add the following after declaring crel.

    var testDiv = document.createElement('div'),
        testLabel = document.createElement('label');

    testDiv.setAttribute('class', 'a');
    testDiv['className'] !== 'a' ? crel.attrMap['class'] = 'className':undefined;
    testDiv.setAttribute('name','a');
    testDiv['name'] !== 'a' ? crel.attrMap['name'] = function(element, value){
        element.id = value;
    }:undefined;

    testLabel.setAttribute('for', 'a');
    testLabel['htmlFor'] !== 'a' ? crel.attrMap['for'] = 'htmlFor':undefined;

*/

(function (root, factory) {
    if (typeof exports === 'object') {
        module.exports = factory();
    } else if (typeof define === 'function' && define.amd) {
        define(factory);
    } else {
        root.crel = factory();
    }
}(this, function () {
    var isType = function(a, type){ // A helper function used throughout the script, so declare it early
            return typeof a === type;
        },
        fn = 'function',
        obj = 'object',
        nodeType = 'nodeType',
        textContent = 'textContent',
        setAttribute = 'setAttribute',
        attrMapString = 'attrMap',
        isNodeString = 'isNode',
        isElementString = 'isElement',
        d = isType(document, obj) ? document : {},
        isNode = isType(Node, fn) ? function (object) {
            return object instanceof Node;
        } :
        // in IE <= 8 Node is an object, obviously..
        function(object){
            return object &&
                isType(object, obj) &&
                (nodeType in object) &&
                isType(object.ownerDocument,obj);
        },
        isElement = function (object) {
            return crel[isNodeString](object) && object[nodeType] === 1;
        },
        isArray = function(a){
            return a instanceof Array;
        },
        appendChild = function(element, child) {
            if (isArray(child)) {
                child.map(function(subChild){
                    appendChild(element, subChild);
                });
                return;
            }
            if(!crel[isNodeString](child)){
                child = d.createTextNode(child);
            }
            element.appendChild(child);
        };

    function crel(){
        var args = arguments, //Note: assigned to a variable to assist compilers. Saves about 40 bytes in closure compiler. Has negligable effect on performance.
            element = args[0],
            child,
            settings = args[1],
            childIndex = 2,
            argumentsLength = args.length,
            attributeMap = crel[attrMapString];

        element = crel[isElementString](element) ? element : d.createElement(element);
        // shortcut
        if(argumentsLength === 1){
            return element;
        }

        if(!isType(settings,obj) || crel[isNodeString](settings) || isArray(settings)) {
            --childIndex;
            settings = null;
        }

        // shortcut if there is only one child that is a string
        if((argumentsLength - childIndex) === 1 && isType(args[childIndex], 'string') && element[textContent] !== undefined){
            element[textContent] = args[childIndex];
        }else{
            for(; childIndex < argumentsLength; ++childIndex){
                child = args[childIndex];

                if(child !== null){
                    appendChild(element, child);
                }
            }
        }

        for(var key in settings){
            if(!attributeMap[key]){
                if(isType(settings[key],fn)){
                    element[key] = settings[key];
                }else{
                    element[setAttribute](key, settings[key]);
                }
            }else{
                var attr = attributeMap[key];
                if(isType(attr, fn)){
                    attr(element, settings[key]);
                }else{
                    element[setAttribute](attr, settings[key]);
                }
            }
        }

        return element;
    }

    // Used for mapping one kind of attribute to the supported version of that in bad browsers.
    crel[attrMapString] = {};

    crel[isElementString] = isElement;

    crel[isNodeString] = isNode;

    if(!isType(Proxy, 'undefined')){
        crel.proxy = new Proxy(crel, {
            get: function(target, key){
                !(key in crel) && (crel[key] = crel.bind(null, key));
                return crel[key];
            }
        });
    }

    return crel;
}));
<|endoftext|>


<commit_before>//Copyright (C) 2012 Kory Nunn

//Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

//The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

//THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

/*

    This code is not formatted for readability, but rather run-speed and to assist compilers.

    However, the code's intention should be transparent.

*/

(function (root, factory) {
    if (typeof exports === 'object') {
        module.exports = factory();
    } else if (typeof define === 'function' && define.amd) {
        define(factory);
    } else {
        root.crel = factory();
    }
}(this, function () {
    var isType = function(a, type){ // A helper function used throughout the script, so declare it early
            return typeof a === type;
        },
        fn = 'function',
        obj = 'object',
        nodeType = 'nodeType',
        setAttribute = 'setAttribute',
        attrMapString = 'attrMap',
        isNodeString = 'isNode',
        isElementString = 'isElement',
        d = document,
        isNode = function (object) {
            return object instanceof Node;
        },
        isElement = function (object) {
            return crel[isNodeString](object) && object[nodeType] === 1;
        },
        appendChild = function(element, child) {
            if (Array.isArray(child)) {
                child.map(function(subChild){
                    appendChild(element, subChild);
                });
                return;
            }
            if(!crel[isNodeString](child)){
                child = d.createTextNode(child);
            }
            element.appendChild(child);
        };

    function crel(){
        var args = arguments, //Note: assigned to a variable to assist compilers. Saves about 40 bytes in closure compiler. Has negligable effect on performance.
            element = args[0],
            child,
            settings = args[1],
            childIndex = 2,
            argumentsLength = args.length,
            attributeMap = crel[attrMapString];

        element = crel[isElementString](element) ? element : d.createElement(element);
        // shortcut
        if(argumentsLength > 1){
            if(!isType(settings,obj) || crel[isNodeString](settings) || Array.isArray(settings)) {
                --childIndex;
                settings = null;
            }

            // shortcut if there is only one child that is a string
            if((argumentsLength - childIndex) === 1 && isType(args[childIndex], 'string')){
                element.textContent = args[childIndex];
            }else{
                for(; childIndex < argumentsLength; ++childIndex){
                    child = args[childIndex];

                    if(child !== null){
                        appendChild(element, child);
                    }
                }
            }

            for(var key in settings){
                if(!attributeMap[key]){
                    if(isType(settings[key],fn)){
                        element[key] = settings[key];
                    }else{
                        element[setAttribute](key, settings[key]);
                    }
                }else{
                    var attr = attributeMap[key];
                    if(isType(attr, fn)){
                        attr(element, settings[key]);
                    }else{
                        element[setAttribute](attr, settings[key]);
                    }
                }
            }
        }

        return element;
    }

    // Used for mapping one kind of attribute to the supported version of that in bad browsers.
    crel[attrMapString] = {};

    crel[isElementString] = isElement;

    crel[isNodeString] = isNode;

    if(!isType(Proxy, 'undefined')){
        crel.proxy = new Proxy(crel, {
            get: function(target, key){
                !(key in crel) && (crel[key] = crel.bind(null, key));
                return crel[key];
            }
        });
    }

    return crel;
}));
<commit_msg>Crel: Shorten isElement<commit_after>//Copyright (C) 2012 Kory Nunn

//Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

//The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

//THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

/*

    This code is not formatted for readability, but rather run-speed and to assist compilers.

    However, the code's intention should be transparent.

*/

(function (root, factory) {
    if (typeof exports === 'object') {
        module.exports = factory();
    } else if (typeof define === 'function' && define.amd) {
        define(factory);
    } else {
        root.crel = factory();
    }
}(this, function () {
    var isType = function(a, type){ // A helper function used throughout the script, so declare it early
            return typeof a === type;
        },
        fn = 'function',
        obj = 'object',
        setAttribute = 'setAttribute',
        attrMapString = 'attrMap',
        isNodeString = 'isNode',
        isElementString = 'isElement',
        d = document,
        isNode = function (object) {
            return object instanceof Node;
        },
        isElement = function (object) {
            return object instanceof Element;
        },
        appendChild = function(element, child) {
            if (Array.isArray(child)) {
                child.map(function(subChild){
                    appendChild(element, subChild);
                });
                return;
            }
            if(!crel[isNodeString](child)){
                child = d.createTextNode(child);
            }
            element.appendChild(child);
        };

    function crel(){
        var args = arguments, //Note: assigned to a variable to assist compilers. Saves about 40 bytes in closure compiler. Has negligable effect on performance.
            element = args[0],
            child,
            settings = args[1],
            childIndex = 2,
            argumentsLength = args.length,
            attributeMap = crel[attrMapString];

        element = crel[isElementString](element) ? element : d.createElement(element);
        // shortcut
        if(argumentsLength > 1){
            if(!isType(settings,obj) || crel[isNodeString](settings) || Array.isArray(settings)) {
                --childIndex;
                settings = null;
            }

            // shortcut if there is only one child that is a string
            if((argumentsLength - childIndex) === 1 && isType(args[childIndex], 'string')){
                element.textContent = args[childIndex];
            }else{
                for(; childIndex < argumentsLength; ++childIndex){
                    child = args[childIndex];

                    if(child !== null){
                        appendChild(element, child);
                    }
                }
            }

            for(var key in settings){
                if(!attributeMap[key]){
                    if(isType(settings[key],fn)){
                        element[key] = settings[key];
                    }else{
                        element[setAttribute](key, settings[key]);
                    }
                }else{
                    var attr = attributeMap[key];
                    if(isType(attr, fn)){
                        attr(element, settings[key]);
                    }else{
                        element[setAttribute](attr, settings[key]);
                    }
                }
            }
        }

        return element;
    }

    // Used for mapping one kind of attribute to the supported version of that in bad browsers.
    crel[attrMapString] = {};

    crel[isElementString] = isElement;

    crel[isNodeString] = isNode;

    if(!isType(Proxy, 'undefined')){
        crel.proxy = new Proxy(crel, {
            get: function(target, key){
                !(key in crel) && (crel[key] = crel.bind(null, key));
                return crel[key];
            }
        });
    }

    return crel;
}));
<|endoftext|>


<commit_before>(function(window, undefined) {
	var Crog = window.Crog = function(parent, width, height) {
		if(height === undefined) {
			height = width;
		}

		this.containerWidth = width;
		this.containerHeight = height;
		this.containerRatio = width / height;

		this.parent = parent;

		this.container = document.createElement('div');
		this.container.className = 'crog-container';
		this.container.style.cursor = 'move';
		this.container.style.overflow = 'hidden';
		this.container.style.position = 'relative';
		this.container.style.width = width + 'px';
		this.container.style.height = height + 'px';

		this.image = document.createElement('img');
		this.image.className = 'crog-image';
		this.image.style.position = 'absolute';
		this.image.style.display = 'block';

		this.container.appendChild(this.image);
		this.parent.appendChild(this.container);

		this.bindEvents();
	};

	Crog.fn = Crog.prototype;

	Crog.fn.destroy = function() {
		this.parent.removeChild(this.container);
		this.container.innerHTML = '';
		this.container = undefined;
		this.image = undefined;
	};

	Crog.fn.setURL = function(url, fn) {
		this.loaded = false;

		var _this = this;

		this.image.onload = function() {
			_this.imageRatio = _this.image.naturalWidth / _this.image.naturalHeight;
			_this.needsCrop = _this.imageRatio !== _this.containerRatio;

			if(!fn || fn.call(_this) !== false) {
				_this.image.style.visibility = 'visible';
				_this.loaded = true;
				_this.resizeImage();
			}
		};

		this.image.style.visibility = 'hidden';
		this.image.src = url;
	};

	Crog.fn.getRect = function() {
		var left = Math.abs(parseInt(this.image.style.left, 10));
		var top = Math.abs(parseInt(this.image.style.top, 10));

		return {
			top: top,
			right: left + this.containerWidth,
			bottom: top + this.containerHeight,
			left: left
		};
	};

	Crog.fn.resizeImage = function() {
		this.image.style.width = 'auto';
		this.image.style.height = 'auto';
		this.image.style.left = this.image.style.top = 0;

		if(this.imageRatio > this.containerRatio) {
			this.image.style.height = '100%';
		} else {
			this.image.style.width = '100%';
		}
	};

	Crog.fn.handleEvent = function(e) {
		e.preventDefault();

		if(!this.loaded) {
			return;
		}

		var pageX;
		var pageY;

		if(e.touches && e.touches.length) {
			pageX = e.touches[0].pageX;
			pageY = e.touches[0].pageY;
		} else {
			pageX = e.pageX;
			pageY = e.pageY;
		}

		switch(e.type) {
			case 'mousedown':
			case 'touchstart':
				this.drag = true;
				this.startX = pageX;
				this.startY = pageY;
				this.startImageX = parseInt(this.image.style.left, 10);
				this.startImageY = parseInt(this.image.style.top, 10);
				return;

			case 'mousemove':
			case 'touchmove':
				if(!this.drag) {
					return;
				}

				var dX = pageX - this.startX;
				var dY = pageY - this.startY;
				var targetX = Math.max(Math.min(this.startImageX + dX, 0), this.containerWidth - this.image.clientWidth);
				var targetY = Math.max(Math.min(this.startImageY + dY, 0), this.containerHeight - this.image.clientHeight);

				this.image.style.left = targetX + 'px';
				this.image.style.top = targetY + 'px';

				return;

			case 'mouseup':
			case 'touchend':
			case 'touchcancel':
				this.drag = false;
				return;
		}
	};

	Crog.fn.bindEvents = function() {
		this.container.addEventListener('dragstart', this, false);

		this.container.addEventListener('mousedown', this, false);
		this.container.addEventListener('touchstart', this, false);
		this.container.addEventListener('mousemove', this, false);
		this.container.addEventListener('touchmove', this, false);

		window.addEventListener('mouseup', this, false);
		window.addEventListener('touchend', this, false);
		window.addEventListener('touchcancel', this, false);
	};

}(window));<commit_msg>IE event stuff<commit_after>(function(window, undefined) {
	var Crog = window.Crog = function(parent, width, height) {
		if(height === undefined) {
			height = width;
		}

		this.containerWidth = width;
		this.containerHeight = height;
		this.containerRatio = width / height;

		this.parent = parent;

		this.container = document.createElement('div');
		this.container.className = 'crog-container';
		this.container.style.cursor = 'move';
		this.container.style.overflow = 'hidden';
		this.container.style.position = 'relative';
		this.container.style.width = width + 'px';
		this.container.style.height = height + 'px';

		this.image = document.createElement('img');
		this.image.className = 'crog-image';
		this.image.style.position = 'absolute';
		this.image.style.display = 'block';

		this.container.appendChild(this.image);
		this.parent.appendChild(this.container);

		this.bindEvents();
	};

	Crog.fn = Crog.prototype;

	Crog.fn.destroy = function() {
		this.parent.removeChild(this.container);
		this.container.innerHTML = '';
		this.container = undefined;
		this.image = undefined;
	};

	Crog.fn.setURL = function(url, fn) {
		this.loaded = false;

		var _this = this;

		this.image.onload = function() {
			_this.imageRatio = _this.image.naturalWidth / _this.image.naturalHeight;
			_this.needsCrop = _this.imageRatio !== _this.containerRatio;

			if(!fn || fn.call(_this) !== false) {
				_this.image.style.visibility = 'visible';
				_this.loaded = true;
				_this.resizeImage();
			}
		};

		this.image.style.visibility = 'hidden';
		this.image.src = url;
	};

	Crog.fn.getRect = function() {
		var left = Math.abs(parseInt(this.image.style.left, 10));
		var top = Math.abs(parseInt(this.image.style.top, 10));

		return {
			top: top,
			right: left + this.containerWidth,
			bottom: top + this.containerHeight,
			left: left
		};
	};

	Crog.fn.resizeImage = function() {
		this.image.style.width = 'auto';
		this.image.style.height = 'auto';
		this.image.style.left = this.image.style.top = 0;

		if(this.imageRatio > this.containerRatio) {
			this.image.style.height = '100%';
		} else {
			this.image.style.width = '100%';
		}
	};

	Crog.fn.handleEvent = function(e) {
		if(e.preventDefault) {
			e.preventDefault();
		} else {
			e.returnValue = false;
		}

		if(!this.loaded) {
			return;
		}

		var pageX;
		var pageY;

		if(e.touches && e.touches.length) {
			pageX = e.touches[0].pageX;
			pageY = e.touches[0].pageY;
		} else {
			pageX = e.pageX;
			pageY = e.pageY;
		}

		switch(e.type) {
			case 'mousedown':
			case 'touchstart':
				this.drag = true;
				this.startX = pageX;
				this.startY = pageY;
				this.startImageX = parseInt(this.image.style.left, 10);
				this.startImageY = parseInt(this.image.style.top, 10);
				return;

			case 'mousemove':
			case 'touchmove':
				if(!this.drag) {
					return;
				}

				var dX = pageX - this.startX;
				var dY = pageY - this.startY;
				var targetX = Math.max(Math.min(this.startImageX + dX, 0), this.containerWidth - this.image.clientWidth);
				var targetY = Math.max(Math.min(this.startImageY + dY, 0), this.containerHeight - this.image.clientHeight);

				this.image.style.left = targetX + 'px';
				this.image.style.top = targetY + 'px';

				return;

			case 'mouseup':
			case 'touchend':
			case 'touchcancel':
				this.drag = false;
				return;
		}
	};

	Crog.fn.bindEvents = function() {
		this.container.addEventListener('dragstart', this, false);

		this.container.addEventListener('mousedown', this, false);
		this.container.addEventListener('touchstart', this, false);
		this.container.addEventListener('mousemove', this, false);
		this.container.addEventListener('touchmove', this, false);

		window.addEventListener('mouseup', this, false);
		window.addEventListener('touchend', this, false);
		window.addEventListener('touchcancel', this, false);
	};

	Crog.fn.addEvent = function(name, element, callback) {
		if(element.addEventListener) {
			element.addEventListener(name, callback, false);
		} else {
			element.attachEvent('on' + name, callback);
		}
	};

}(window));<|endoftext|>