<commit_before>/*
Copyright 2015, 2016 OpenMarket Ltd
Copyright 2017 Vector Creations Ltd

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.
*/

'use strict';

// for ES6 stuff like startsWith() that Safari doesn't handle
// and babel doesn't do by default
// Note we use this, as well as the babel transform-runtime plugin
// since transform-runtime does not cover instance methods
// such as "foobar".includes("foo") which bits of our library
// code use, but the babel transform-runtime plugin allows the
// regenerator runtime to be injected early enough in the process
// (it can't be here as it's too late: the alternative is to put
// the babel-polyfill as the first 'entry' in the webpack config).
// https://babeljs.io/docs/plugins/transform-runtime/
require('babel-polyfill');

// Require common CSS here; this will make webpack process it into bundle.css.
// Our own CSS (which is themed) is imported via separate webpack entry points
// in webpack.config.js
require('gemini-scrollbar/gemini-scrollbar.css');
require('gfm.css/gfm.css');
require('highlight.js/styles/github.css');
require('draft-js/dist/Draft.css');

const rageshake = require("./rageshake");
rageshake.init().then(() => {
    console.log("Initialised rageshake: See https://bugs.chromium.org/p/chromium/issues/detail?id=583193 to fix line numbers on Chrome.");
    rageshake.cleanup();
}, (err) => {
    console.error("Failed to initialise rageshake: " + err);
});

window.addEventListener('beforeunload', (e) => {
    console.log('riot-web closing');
    // try to flush the logs to indexeddb
    rageshake.flush();
});

 // add React and ReactPerf to the global namespace, to make them easier to
 // access via the console
global.React = require("react");
if (process.env.NODE_ENV !== 'production') {
    global.Perf = require("react-addons-perf");
}

var RunModernizrTests = require("./modernizr"); // this side-effects a global
var ReactDOM = require("react-dom");
var sdk = require("matrix-react-sdk");
const PlatformPeg = require("matrix-react-sdk/lib/PlatformPeg");
sdk.loadSkin(require('../component-index'));
var VectorConferenceHandler = require('../VectorConferenceHandler');
import Promise from 'bluebird';
var request = require('browser-request');
import * as UserSettingsStore from 'matrix-react-sdk/lib/UserSettingsStore';
import * as languageHandler from 'matrix-react-sdk/lib/languageHandler';
// Also import _t directly so we can call it just `_t` as this is what gen-i18n.js expects
import { _t } from 'matrix-react-sdk/lib/languageHandler';

import url from 'url';

import {parseQs, parseQsFromFragment} from './url_utils';
import Platform from './platform';

import MatrixClientPeg from 'matrix-react-sdk/lib/MatrixClientPeg';

var lastLocationHashSet = null;

var CallHandler = require("matrix-react-sdk/lib/CallHandler");
CallHandler.setConferenceHandler(VectorConferenceHandler);

MatrixClientPeg.setIndexedDbWorkerScript(window.vector_indexeddb_worker_script);

function checkBrowserFeatures(featureList) {
    if (!window.Modernizr) {
        console.error("Cannot check features - Modernizr global is missing.");
        return false;
    }
    var featureComplete = true;
    for (var i = 0; i < featureList.length; i++) {
        if (window.Modernizr[featureList[i]] === undefined) {
            console.error(
                "Looked for feature '%s' but Modernizr has no results for this. " +
                "Has it been configured correctly?", featureList[i]
            );
            return false;
        }
        if (window.Modernizr[featureList[i]] === false) {
            console.error("Browser missing feature: '%s'", featureList[i]);
            // toggle flag rather than return early so we log all missing features
            // rather than just the first.
            featureComplete = false;
        }
    }
    return featureComplete;
}

var validBrowser = checkBrowserFeatures([
    "displaytable", "flexbox", "es5object", "es5function", "localstorage",
    "objectfit", "indexeddb", "webworkers",
]);

// Parse the given window.location and return parameters that can be used when calling
// MatrixChat.showScreen(screen, params)
function getScreenFromLocation(location) {
    const fragparts = parseQsFromFragment(location);
    return {
        screen: fragparts.location.substring(1),
        params: fragparts.params,
    }
}

// Here, we do some crude URL analysis to allow
// deep-linking.
function routeUrl(location) {
    if (!window.matrixChat) return;

    console.log("Routing URL ", location.href);
    const s = getScreenFromLocation(location);
    window.matrixChat.showScreen(s.screen, s.params);
}

function onHashChange(ev) {
    if (decodeURIComponent(window.location.hash) == lastLocationHashSet) {
        // we just set this: no need to route it!
        return;
    }
    routeUrl(window.location);
}

// This will be called whenever the SDK changes screens,
// so a web page can update the URL bar appropriately.
var onNewScreen = function(screen) {
    console.log("newscreen "+screen);
    var hash = '#/' + screen;
    lastLocationHashSet = hash;
    window.location.hash = hash;
};

// We use this to work out what URL the SDK should
// pass through when registering to allow the user to
// click back to the client having registered.
// It's up to us to recognise if we're loaded with
// this URL and tell MatrixClient to resume registration.
//
// If we're in electron, we should never pass through a file:// URL otherwise
// the identity server will try to 302 the browser to it, which breaks horribly.
// so in that instance, hardcode to use riot.im/app for now instead.
var makeRegistrationUrl = function(params) {
    let url;
    if (window.location.protocol === "file:") {
        url = 'https://riot.im/app/#/register';
    } else {
        url = (
            window.location.protocol + '//' +
            window.location.host +
            window.location.pathname +
            '#/register'
        );
    }

    const keys = Object.keys(params);
    for (let i = 0; i < keys.length; ++i) {
        if (i == 0) {
            url += '?';
        } else {
            url += '&';
        }
        const k = keys[i];
        url += k + '=' + encodeURIComponent(params[k]);
    }
    return url;
}

window.addEventListener('hashchange', onHashChange);

function getConfig(configJsonFilename) {
    let deferred = Promise.defer();

    request(
        { method: "GET", url: configJsonFilename },
        (err, response, body) => {
            if (err || response.status < 200 || response.status >= 300) {
                // Lack of a config isn't an error, we should
                // just use the defaults.
                // Also treat a blank config as no config, assuming
                // the status code is 0, because we don't get 404s
                // from file: URIs so this is the only way we can
                // not fail if the file doesn't exist when loading
                // from a file:// URI.
                if (response) {
                    if (response.status == 404 || (response.status == 0 && body == '')) {
                        deferred.resolve({});
                    }
                }
                deferred.reject({err: err, response: response});
                return;
            }

            // We parse the JSON ourselves rather than use the JSON
            // parameter, since this throws a parse error on empty
            // which breaks if there's no config.json and we're
            // loading from the filesystem (see above).
            deferred.resolve(JSON.parse(body));
        }
    );

    return deferred.promise;
}

function onTokenLoginCompleted() {
    // if we did a token login, we're now left with the token, hs and is
    // url as query params in the url; a little nasty but let's redirect to
    // clear them.
    var parsedUrl = url.parse(window.location.href);
    parsedUrl.search = "";
    var formatted = url.format(parsedUrl);
    console.log("Redirecting to " + formatted + " to drop loginToken " +
                "from queryparams");
    window.location.href = formatted;
}

async function loadApp() {
    await loadLanguage();

    const fragparts = parseQsFromFragment(window.location);
    const params = parseQs(window.location);

    // set the platform for react sdk (our Platform object automatically picks the right one)
    PlatformPeg.set(new Platform());

    // don't try to redirect to the native apps if we're
    // verifying a 3pid
    const preventRedirect = Boolean(fragparts.params.client_secret);

    if (!preventRedirect) {
        if (/iPad|iPhone|iPod/.test(navigator.userAgent) && !window.MSStream) {
            if (confirm(_t("Riot is not supported on mobile web. Install the app?"))) {
                window.location = "https://itunes.apple.com/us/app/vector.im/id1083446067";
                return;
            }
        }
        else if (/Android/.test(navigator.userAgent)) {
            if (confirm(_t("Riot is not supported on mobile web. Install the app?"))) {
                window.location = "https://play.google.com/store/apps/details?id=im.vector.alpha";
                return;
            }
        }
    }

    // Load the config file. First try to load up a domain-specific config of the
    // form "config.$domain.json" and if that fails, fall back to config.json.
    let configJson;
    let configError;
    try {
        try {
            configJson = await getConfig(`config.${document.domain}.json`);
            // 404s succeed with an empty json config, so check that there are keys
            if (Object.keys(configJson).length === 0) {
                throw new Error(); // throw to enter the catch
            }
        } catch (e) {
            configJson = await getConfig("config.json");
        }
    } catch (e) {
        configError = e;
    }

    if (window.localStorage && window.localStorage.getItem('mx_accepts_unsupported_browser')) {
        console.log('User has previously accepted risks in using an unsupported browser');
        validBrowser = true;
    }

    console.log("Vector starting at "+window.location);
    if (configError) {
        window.matrixChat = ReactDOM.render(<div className="error">
            Unable to load config file: please refresh the page to try again.
        </div>, document.getElementById('matrixchat'));
    } else if (validBrowser) {
        const platform = PlatformPeg.get();
        platform.startUpdater();

        const MatrixChat = sdk.getComponent('structures.MatrixChat');
        window.matrixChat = ReactDOM.render(
            <MatrixChat
                onNewScreen={onNewScreen}
                makeRegistrationUrl={makeRegistrationUrl}
                ConferenceHandler={VectorConferenceHandler}
                config={configJson}
                realQueryParams={params}
                startingFragmentQueryParams={fragparts.params}
                enableGuest={true}
                onTokenLoginCompleted={onTokenLoginCompleted}
                initialScreenAfterLogin={getScreenFromLocation(window.location)}
                defaultDeviceDisplayName={platform.getDefaultDeviceDisplayName()}
            />,
            document.getElementById('matrixchat')
        );
    } else {
        console.error("Browser is missing required features.");
        // take to a different landing page to AWOOOOOGA at the user
        var CompatibilityPage = sdk.getComponent("structures.CompatibilityPage");
        window.matrixChat = ReactDOM.render(
            <CompatibilityPage onAccept={function() {
                if (window.localStorage) window.localStorage.setItem('mx_accepts_unsupported_browser', true);
                validBrowser = true;
                console.log("User accepts the compatibility risks.");
                loadApp();
            }} />,
            document.getElementById('matrixchat')
        );
    }
}

async function loadLanguage() {
    const prefLang = UserSettingsStore.getLocalSetting('language');
    let langs = [];

    if (!prefLang) {
        languageHandler.getLanguagesFromBrowser().forEach((l) => {
            langs.push(...languageHandler.getNormalizedLanguageKeys(l));
        });
    } else {
        langs = [prefLang];
    }
    try {
        await languageHandler.setLanguage(langs);
    } catch (e) {
        console.error("Unable to set language", e);
    }
}

loadApp();
<commit_msg>Convert local settings to granular settings<commit_after>/*
Copyright 2015, 2016 OpenMarket Ltd
Copyright 2017 Vector Creations Ltd

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.
*/

'use strict';

// for ES6 stuff like startsWith() that Safari doesn't handle
// and babel doesn't do by default
// Note we use this, as well as the babel transform-runtime plugin
// since transform-runtime does not cover instance methods
// such as "foobar".includes("foo") which bits of our library
// code use, but the babel transform-runtime plugin allows the
// regenerator runtime to be injected early enough in the process
// (it can't be here as it's too late: the alternative is to put
// the babel-polyfill as the first 'entry' in the webpack config).
// https://babeljs.io/docs/plugins/transform-runtime/
require('babel-polyfill');

// Require common CSS here; this will make webpack process it into bundle.css.
// Our own CSS (which is themed) is imported via separate webpack entry points
// in webpack.config.js
require('gemini-scrollbar/gemini-scrollbar.css');
require('gfm.css/gfm.css');
require('highlight.js/styles/github.css');
require('draft-js/dist/Draft.css');

const rageshake = require("./rageshake");
rageshake.init().then(() => {
    console.log("Initialised rageshake: See https://bugs.chromium.org/p/chromium/issues/detail?id=583193 to fix line numbers on Chrome.");
    rageshake.cleanup();
}, (err) => {
    console.error("Failed to initialise rageshake: " + err);
});

window.addEventListener('beforeunload', (e) => {
    console.log('riot-web closing');
    // try to flush the logs to indexeddb
    rageshake.flush();
});

 // add React and ReactPerf to the global namespace, to make them easier to
 // access via the console
global.React = require("react");
if (process.env.NODE_ENV !== 'production') {
    global.Perf = require("react-addons-perf");
}

var RunModernizrTests = require("./modernizr"); // this side-effects a global
var ReactDOM = require("react-dom");
var sdk = require("matrix-react-sdk");
const PlatformPeg = require("matrix-react-sdk/lib/PlatformPeg");
sdk.loadSkin(require('../component-index'));
var VectorConferenceHandler = require('../VectorConferenceHandler');
import Promise from 'bluebird';
var request = require('browser-request');
import * as languageHandler from 'matrix-react-sdk/lib/languageHandler';
// Also import _t directly so we can call it just `_t` as this is what gen-i18n.js expects
import { _t } from 'matrix-react-sdk/lib/languageHandler';

import url from 'url';

import {parseQs, parseQsFromFragment} from './url_utils';
import Platform from './platform';

import MatrixClientPeg from 'matrix-react-sdk/lib/MatrixClientPeg';
import SettingsStore from "../../../matrix-react-sdk/src/settings/SettingsStore";

var lastLocationHashSet = null;

var CallHandler = require("matrix-react-sdk/lib/CallHandler");
CallHandler.setConferenceHandler(VectorConferenceHandler);

MatrixClientPeg.setIndexedDbWorkerScript(window.vector_indexeddb_worker_script);

function checkBrowserFeatures(featureList) {
    if (!window.Modernizr) {
        console.error("Cannot check features - Modernizr global is missing.");
        return false;
    }
    var featureComplete = true;
    for (var i = 0; i < featureList.length; i++) {
        if (window.Modernizr[featureList[i]] === undefined) {
            console.error(
                "Looked for feature '%s' but Modernizr has no results for this. " +
                "Has it been configured correctly?", featureList[i]
            );
            return false;
        }
        if (window.Modernizr[featureList[i]] === false) {
            console.error("Browser missing feature: '%s'", featureList[i]);
            // toggle flag rather than return early so we log all missing features
            // rather than just the first.
            featureComplete = false;
        }
    }
    return featureComplete;
}

var validBrowser = checkBrowserFeatures([
    "displaytable", "flexbox", "es5object", "es5function", "localstorage",
    "objectfit", "indexeddb", "webworkers",
]);

// Parse the given window.location and return parameters that can be used when calling
// MatrixChat.showScreen(screen, params)
function getScreenFromLocation(location) {
    const fragparts = parseQsFromFragment(location);
    return {
        screen: fragparts.location.substring(1),
        params: fragparts.params,
    }
}

// Here, we do some crude URL analysis to allow
// deep-linking.
function routeUrl(location) {
    if (!window.matrixChat) return;

    console.log("Routing URL ", location.href);
    const s = getScreenFromLocation(location);
    window.matrixChat.showScreen(s.screen, s.params);
}

function onHashChange(ev) {
    if (decodeURIComponent(window.location.hash) == lastLocationHashSet) {
        // we just set this: no need to route it!
        return;
    }
    routeUrl(window.location);
}

// This will be called whenever the SDK changes screens,
// so a web page can update the URL bar appropriately.
var onNewScreen = function(screen) {
    console.log("newscreen "+screen);
    var hash = '#/' + screen;
    lastLocationHashSet = hash;
    window.location.hash = hash;
};

// We use this to work out what URL the SDK should
// pass through when registering to allow the user to
// click back to the client having registered.
// It's up to us to recognise if we're loaded with
// this URL and tell MatrixClient to resume registration.
//
// If we're in electron, we should never pass through a file:// URL otherwise
// the identity server will try to 302 the browser to it, which breaks horribly.
// so in that instance, hardcode to use riot.im/app for now instead.
var makeRegistrationUrl = function(params) {
    let url;
    if (window.location.protocol === "file:") {
        url = 'https://riot.im/app/#/register';
    } else {
        url = (
            window.location.protocol + '//' +
            window.location.host +
            window.location.pathname +
            '#/register'
        );
    }

    const keys = Object.keys(params);
    for (let i = 0; i < keys.length; ++i) {
        if (i == 0) {
            url += '?';
        } else {
            url += '&';
        }
        const k = keys[i];
        url += k + '=' + encodeURIComponent(params[k]);
    }
    return url;
}

window.addEventListener('hashchange', onHashChange);

function getConfig(configJsonFilename) {
    let deferred = Promise.defer();

    request(
        { method: "GET", url: configJsonFilename },
        (err, response, body) => {
            if (err || response.status < 200 || response.status >= 300) {
                // Lack of a config isn't an error, we should
                // just use the defaults.
                // Also treat a blank config as no config, assuming
                // the status code is 0, because we don't get 404s
                // from file: URIs so this is the only way we can
                // not fail if the file doesn't exist when loading
                // from a file:// URI.
                if (response) {
                    if (response.status == 404 || (response.status == 0 && body == '')) {
                        deferred.resolve({});
                    }
                }
                deferred.reject({err: err, response: response});
                return;
            }

            // We parse the JSON ourselves rather than use the JSON
            // parameter, since this throws a parse error on empty
            // which breaks if there's no config.json and we're
            // loading from the filesystem (see above).
            deferred.resolve(JSON.parse(body));
        }
    );

    return deferred.promise;
}

function onTokenLoginCompleted() {
    // if we did a token login, we're now left with the token, hs and is
    // url as query params in the url; a little nasty but let's redirect to
    // clear them.
    var parsedUrl = url.parse(window.location.href);
    parsedUrl.search = "";
    var formatted = url.format(parsedUrl);
    console.log("Redirecting to " + formatted + " to drop loginToken " +
                "from queryparams");
    window.location.href = formatted;
}

async function loadApp() {
    await loadLanguage();

    const fragparts = parseQsFromFragment(window.location);
    const params = parseQs(window.location);

    // set the platform for react sdk (our Platform object automatically picks the right one)
    PlatformPeg.set(new Platform());

    // don't try to redirect to the native apps if we're
    // verifying a 3pid
    const preventRedirect = Boolean(fragparts.params.client_secret);

    if (!preventRedirect) {
        if (/iPad|iPhone|iPod/.test(navigator.userAgent) && !window.MSStream) {
            if (confirm(_t("Riot is not supported on mobile web. Install the app?"))) {
                window.location = "https://itunes.apple.com/us/app/vector.im/id1083446067";
                return;
            }
        }
        else if (/Android/.test(navigator.userAgent)) {
            if (confirm(_t("Riot is not supported on mobile web. Install the app?"))) {
                window.location = "https://play.google.com/store/apps/details?id=im.vector.alpha";
                return;
            }
        }
    }

    // Load the config file. First try to load up a domain-specific config of the
    // form "config.$domain.json" and if that fails, fall back to config.json.
    let configJson;
    let configError;
    try {
        try {
            configJson = await getConfig(`config.${document.domain}.json`);
            // 404s succeed with an empty json config, so check that there are keys
            if (Object.keys(configJson).length === 0) {
                throw new Error(); // throw to enter the catch
            }
        } catch (e) {
            configJson = await getConfig("config.json");
        }
    } catch (e) {
        configError = e;
    }

    if (window.localStorage && window.localStorage.getItem('mx_accepts_unsupported_browser')) {
        console.log('User has previously accepted risks in using an unsupported browser');
        validBrowser = true;
    }

    console.log("Vector starting at "+window.location);
    if (configError) {
        window.matrixChat = ReactDOM.render(<div className="error">
            Unable to load config file: please refresh the page to try again.
        </div>, document.getElementById('matrixchat'));
    } else if (validBrowser) {
        const platform = PlatformPeg.get();
        platform.startUpdater();

        const MatrixChat = sdk.getComponent('structures.MatrixChat');
        window.matrixChat = ReactDOM.render(
            <MatrixChat
                onNewScreen={onNewScreen}
                makeRegistrationUrl={makeRegistrationUrl}
                ConferenceHandler={VectorConferenceHandler}
                config={configJson}
                realQueryParams={params}
                startingFragmentQueryParams={fragparts.params}
                enableGuest={true}
                onTokenLoginCompleted={onTokenLoginCompleted}
                initialScreenAfterLogin={getScreenFromLocation(window.location)}
                defaultDeviceDisplayName={platform.getDefaultDeviceDisplayName()}
            />,
            document.getElementById('matrixchat')
        );
    } else {
        console.error("Browser is missing required features.");
        // take to a different landing page to AWOOOOOGA at the user
        var CompatibilityPage = sdk.getComponent("structures.CompatibilityPage");
        window.matrixChat = ReactDOM.render(
            <CompatibilityPage onAccept={function() {
                if (window.localStorage) window.localStorage.setItem('mx_accepts_unsupported_browser', true);
                validBrowser = true;
                console.log("User accepts the compatibility risks.");
                loadApp();
            }} />,
            document.getElementById('matrixchat')
        );
    }
}

async function loadLanguage() {
    const prefLang = SettingsStore.getValue("language");
    let langs = [];

    if (!prefLang) {
        languageHandler.getLanguagesFromBrowser().forEach((l) => {
            langs.push(...languageHandler.getNormalizedLanguageKeys(l));
        });
    } else {
        langs = [prefLang];
    }
    try {
        await languageHandler.setLanguage(langs);
    } catch (e) {
        console.error("Unable to set language", e);
    }
}

loadApp();
<|endoftext|>


<commit_before>/* @flow */

import React, { PropTypes } from 'react';

import {
  Animated,
  Platform,
  StyleSheet,
  View,
} from 'react-native';

import ReactComponentWithPureRenderMixin from 'react/lib/ReactComponentWithPureRenderMixin';

import HeaderTitle from './HeaderTitle';
import HeaderBackButton from './HeaderBackButton';
import HeaderStyleInterpolator from './HeaderStyleInterpolator';
import NavigationPropTypes from '../PropTypes';
import addNavigationHelpers from '../addNavigationHelpers';

import type {
  NavigationScene,
  NavigationRouter,
  NavigationRoute,
  NavigationAction,
  NavigationScreenProp,
  NavigationSceneRendererProps,
  NavigationStyleInterpolator,
} from '../TypeDefinition';

export type HeaderMode = 'float' | 'screen' | 'none';

type SubViewProps = NavigationSceneRendererProps & {
  onNavigateBack: ?() => void,
};

type Navigation = NavigationScreenProp<NavigationRoute, NavigationAction>;

type SubViewRenderer = (subViewProps: SubViewProps) => ?React.Element<*>;

export type HeaderProps = NavigationSceneRendererProps & {
  mode: HeaderMode,
  onNavigateBack: ?Function,
  renderLeftComponent: SubViewRenderer,
  renderRightComponent: SubViewRenderer,
  renderTitleComponent: SubViewRenderer,
  tintColor: ?string,
  router: NavigationRouter,
  style?: any,
};

type SubViewName = 'left' | 'title' | 'right';

const APPBAR_HEIGHT = Platform.OS === 'ios' ? 44 : 56;
const STATUSBAR_HEIGHT = Platform.OS === 'ios' ? 20 : 0;

class Header extends React.Component<void, HeaderProps, void> {

  static HEIGHT = APPBAR_HEIGHT + STATUSBAR_HEIGHT;
  static Title = HeaderTitle;
  static BackButton = HeaderBackButton;

  // propTypes for people who don't use Flow
  static propTypes = {
    ...NavigationPropTypes.SceneRendererProps,
    onNavigateBack: PropTypes.func,
    renderLeftComponent: PropTypes.func,
    renderRightComponent: PropTypes.func,
    renderTitleComponent: PropTypes.func,
    router: PropTypes.object,
    style: PropTypes.any,
  };

  props: HeaderProps;

  shouldComponentUpdate(nextProps: HeaderProps, nextState: any): boolean {
    return ReactComponentWithPureRenderMixin.shouldComponentUpdate.call(
      this,
      nextProps,
      nextState
    );
  }

  _getHeaderTitle(navigation: Navigation): ?string {
    const header = this.props.router.getScreenConfig(navigation, 'header');
    let title;
    if (header && header.title) {
      title = header.title;
    } else {
      title = this.props.router.getScreenConfig(navigation, 'title');
    }
    return typeof title === 'string' ? title : undefined;
  }

  _getHeaderTintColor(navigation: Navigation): ?string {
    const header = this.props.router.getScreenConfig(navigation, 'header');
    if (header && header.tintColor) {
      return header.tintColor;
    }
    return undefined;
  }

  _getHeaderTitleStyle(navigation: Navigation): ?object {
    const header = this.props.router.getScreenConfig(navigation, 'header');
    if (header && header.titleStyle) {
      return header.titleStyle;
    }
    return undefined;
  }

  _renderTitleComponent = (props: SubViewProps) => {
    const titleStyle = this._getHeaderTitleStyle(props.navigation);
    const color = this._getHeaderTintColor(props.navigation);
    const title = this._getHeaderTitle(props.navigation);
    return <HeaderTitle style={[color && { color }, titleStyle]}>{title}</HeaderTitle>;
  };

  _renderLeftComponent = (props: SubViewProps) => {
    if (props.scene.index === 0 || !props.onNavigateBack) {
      return null;
    }
    const tintColor = this._getHeaderTintColor(props.navigation);
    // @todo(grabobu):
    // We have implemented support for back button label (which works 100% fine),
    // but when title is too long, it will overlap the <HeaderTitle />.
    // We had to revert the PR implementing that because of Android issues,
    // I will land it this week and re-enable that for next release.
    //
    // const previousNavigation = addNavigationHelpers({
    //   ...props.navigation,
    //   state: props.scenes[props.scene.index - 1].route,
    // });
    // const backButtonTitle = this._getHeaderTitle(previousNavigation);
    return (
      <HeaderBackButton
        onPress={props.onNavigateBack}
        tintColor={tintColor}
      />
    );
  };

  _renderRightComponent = () => {
    return null;
  };

  _renderLeft(props: NavigationSceneRendererProps): ?React.Element<*> {
    return this._renderSubView(
      props,
      'left',
      this.props.renderLeftComponent,
      this._renderLeftComponent,
      HeaderStyleInterpolator.forLeft,
    );
  }

  _renderTitle(props: NavigationSceneRendererProps, options: *): ?React.Element<*> {
    const style = {};

    if (Platform.OS === 'android') {
      if (!options.hasLeftComponent) {
        style.left = 0;
      }
      if (!options.hasRightComponent) {
        style.right = 0;
      }
    }

    return this._renderSubView(
      { ...props, style },
      'title',
      this.props.renderTitleComponent,
      this._renderTitleComponent,
      HeaderStyleInterpolator.forCenter,
    );
  }

  _renderRight(props: NavigationSceneRendererProps): ?React.Element<*> {
    return this._renderSubView(
      props,
      'right',
      this.props.renderRightComponent,
      this._renderRightComponent,
      HeaderStyleInterpolator.forRight,
    );
  }

  _renderSubView(
    props: NavigationSceneRendererProps,
    name: SubViewName,
    renderer: SubViewRenderer,
    defaultRenderer: SubViewRenderer,
    styleInterpolator: NavigationStyleInterpolator,
  ): ?React.Element<*> {
    const {
      scene,
      navigationState,
    } = props;
    const {
      index,
      isStale,
      key,
    } = scene;

    const offset = navigationState.index - index;

    if (Math.abs(offset) > 2) {
      // Scene is far away from the active scene. Hides it to avoid unnecessary
      // rendering.
      return null;
    }

    const subViewProps = {
      ...props,
      onNavigateBack: this.props.onNavigateBack,
    };

    let subView = renderer(subViewProps);
    if (subView === undefined) {
      subView = defaultRenderer(subViewProps);
    }

    if (subView === null) {
      return null;
    }

    const pointerEvents = offset !== 0 || isStale ? 'none' : 'box-none';
    return (
      <Animated.View
        pointerEvents={pointerEvents}
        key={`${name}_${key}`}
        style={[
          styles.item,
          styles[name],
          props.style,
          styleInterpolator(props),
        ]}
      >
        {subView}
      </Animated.View>
    );
  }

  render(): React.Element<*> {
    // eslint-disable-next-line no-unused-vars
    const { scenes, scene, style, position, progress, ...rest } = this.props;

    let leftComponents = null;
    let titleComponents = null;
    let rightComponents = null;

    if (this.props.mode === 'float') {
      const scenesProps = (scenes.map((scene: NavigationScene, index: number) => {
        const props = NavigationPropTypes.extractSceneRendererProps(this.props);
        props.scene = scene;
        props.index = index;
        props.navigation = addNavigationHelpers({
          ...this.props.navigation,
          state: scene.route,
        });
        return props;
      }): Array<NavigationSceneRendererProps>);
      leftComponents = scenesProps.map(this._renderLeft, this);
      rightComponents = scenesProps.map(this._renderRight, this);
      titleComponents = scenesProps.map((props: *, i: number) =>
        this._renderTitle(props, {
          hasLeftComponent: leftComponents && !!leftComponents[i],
          hasRightComponent: rightComponents && !!rightComponents[i],
        })
      );
    } else {
      const staticRendererProps = {
        ...this.props,
        position: new Animated.Value(scene.index),
        progress: new Animated.Value(0),
      };
      leftComponents = this._renderLeft(staticRendererProps);
      rightComponents = this._renderRight(staticRendererProps);
      titleComponents = this._renderTitle(staticRendererProps, {
        hasLeftComponent: !!leftComponents,
        hasRightComponent: !!rightComponents,
      });
    }

    return (
      <Animated.View {...rest} style={[styles.container, style]}>
        <View style={styles.appBar}>
          {titleComponents}
          {leftComponents}
          {rightComponents}
        </View>
      </Animated.View>
    );
  }
}

const styles = StyleSheet.create({
  container: {
    paddingTop: STATUSBAR_HEIGHT,
    backgroundColor: Platform.OS === 'ios' ? '#EFEFF2' : '#FFF',
    shadowColor: 'black',
    shadowOpacity: 0.1,
    shadowRadius: StyleSheet.hairlineWidth,
    shadowOffset: {
      height: StyleSheet.hairlineWidth,
    },
    elevation: 4,
  },
  appBar: {
    height: APPBAR_HEIGHT,
  },
  item: {
    flexDirection: 'row',
    alignItems: 'center',
  },
  title: {
    bottom: 0,
    left: 40,
    position: 'absolute',
    right: 40,
    top: 0,
  },
  left: {
    bottom: 0,
    left: 0,
    position: 'absolute',
    top: 0,
  },
  right: {
    bottom: 0,
    position: 'absolute',
    right: 0,
    top: 0,
  },
});

module.exports = Header;
<commit_msg>Fix style prop for header<commit_after>/* @flow */

import React, { PropTypes } from 'react';

import {
  Animated,
  Platform,
  StyleSheet,
  View,
} from 'react-native';

import ReactComponentWithPureRenderMixin from 'react/lib/ReactComponentWithPureRenderMixin';

import HeaderTitle from './HeaderTitle';
import HeaderBackButton from './HeaderBackButton';
import HeaderStyleInterpolator from './HeaderStyleInterpolator';
import NavigationPropTypes from '../PropTypes';
import addNavigationHelpers from '../addNavigationHelpers';

import type {
  NavigationScene,
  NavigationRouter,
  NavigationRoute,
  NavigationAction,
  NavigationScreenProp,
  NavigationSceneRendererProps,
  NavigationStyleInterpolator,
} from '../TypeDefinition';

export type HeaderMode = 'float' | 'screen' | 'none';

type SubViewProps = NavigationSceneRendererProps & {
  onNavigateBack: ?() => void,
};

type Navigation = NavigationScreenProp<NavigationRoute, NavigationAction>;

type SubViewRenderer = (subViewProps: SubViewProps) => ?React.Element<*>;

export type HeaderProps = NavigationSceneRendererProps & {
  mode: HeaderMode,
  onNavigateBack: ?Function,
  renderLeftComponent: SubViewRenderer,
  renderRightComponent: SubViewRenderer,
  renderTitleComponent: SubViewRenderer,
  tintColor: ?string,
  router: NavigationRouter,
};

type SubViewName = 'left' | 'title' | 'right';

const APPBAR_HEIGHT = Platform.OS === 'ios' ? 44 : 56;
const STATUSBAR_HEIGHT = Platform.OS === 'ios' ? 20 : 0;

class Header extends React.Component<void, HeaderProps, void> {

  static HEIGHT = APPBAR_HEIGHT + STATUSBAR_HEIGHT;
  static Title = HeaderTitle;
  static BackButton = HeaderBackButton;

  // propTypes for people who don't use Flow
  static propTypes = {
    ...NavigationPropTypes.SceneRendererProps,
    onNavigateBack: PropTypes.func,
    renderLeftComponent: PropTypes.func,
    renderRightComponent: PropTypes.func,
    renderTitleComponent: PropTypes.func,
    router: PropTypes.object,
    style: PropTypes.any,
  };

  props: HeaderProps;

  shouldComponentUpdate(nextProps: HeaderProps, nextState: any): boolean {
    return ReactComponentWithPureRenderMixin.shouldComponentUpdate.call(
      this,
      nextProps,
      nextState
    );
  }

  _getHeaderTitle(navigation: Navigation): ?string {
    const header = this.props.router.getScreenConfig(navigation, 'header');
    let title;
    if (header && header.title) {
      title = header.title;
    } else {
      title = this.props.router.getScreenConfig(navigation, 'title');
    }
    return typeof title === 'string' ? title : undefined;
  }

  _getHeaderTintColor(navigation: Navigation): ?string {
    const header = this.props.router.getScreenConfig(navigation, 'header');
    if (header && header.tintColor) {
      return header.tintColor;
    }
    return undefined;
  }

  _getHeaderTitleStyle(navigation: Navigation): ?object {
    const header = this.props.router.getScreenConfig(navigation, 'header');
    if (header && header.titleStyle) {
      return header.titleStyle;
    }
    return undefined;
  }

  _renderTitleComponent = (props: SubViewProps) => {
    const titleStyle = this._getHeaderTitleStyle(props.navigation);
    const color = this._getHeaderTintColor(props.navigation);
    const title = this._getHeaderTitle(props.navigation);
    return <HeaderTitle style={[color && { color }, titleStyle]}>{title}</HeaderTitle>;
  };

  _renderLeftComponent = (props: SubViewProps) => {
    if (props.scene.index === 0 || !props.onNavigateBack) {
      return null;
    }
    const tintColor = this._getHeaderTintColor(props.navigation);
    // @todo(grabobu):
    // We have implemented support for back button label (which works 100% fine),
    // but when title is too long, it will overlap the <HeaderTitle />.
    // We had to revert the PR implementing that because of Android issues,
    // I will land it this week and re-enable that for next release.
    //
    // const previousNavigation = addNavigationHelpers({
    //   ...props.navigation,
    //   state: props.scenes[props.scene.index - 1].route,
    // });
    // const backButtonTitle = this._getHeaderTitle(previousNavigation);
    return (
      <HeaderBackButton
        onPress={props.onNavigateBack}
        tintColor={tintColor}
      />
    );
  };

  _renderRightComponent = () => {
    return null;
  };

  _renderLeft(props: NavigationSceneRendererProps): ?React.Element<*> {
    return this._renderSubView(
      props,
      'left',
      this.props.renderLeftComponent,
      this._renderLeftComponent,
      HeaderStyleInterpolator.forLeft,
    );
  }

  _renderTitle(props: NavigationSceneRendererProps, options: *): ?React.Element<*> {
    const style = {};

    if (Platform.OS === 'android') {
      if (!options.hasLeftComponent) {
        style.left = 0;
      }
      if (!options.hasRightComponent) {
        style.right = 0;
      }
    }

    return this._renderSubView(
      { ...props, style },
      'title',
      this.props.renderTitleComponent,
      this._renderTitleComponent,
      HeaderStyleInterpolator.forCenter,
    );
  }

  _renderRight(props: NavigationSceneRendererProps): ?React.Element<*> {
    return this._renderSubView(
      props,
      'right',
      this.props.renderRightComponent,
      this._renderRightComponent,
      HeaderStyleInterpolator.forRight,
    );
  }

  _renderSubView(
    props: NavigationSceneRendererProps,
    name: SubViewName,
    renderer: SubViewRenderer,
    defaultRenderer: SubViewRenderer,
    styleInterpolator: NavigationStyleInterpolator,
  ): ?React.Element<*> {
    const {
      scene,
      navigationState,
    } = props;
    const {
      index,
      isStale,
      key,
    } = scene;

    const offset = navigationState.index - index;

    if (Math.abs(offset) > 2) {
      // Scene is far away from the active scene. Hides it to avoid unnecessary
      // rendering.
      return null;
    }

    const subViewProps = {
      ...props,
      onNavigateBack: this.props.onNavigateBack,
    };

    let subView = renderer(subViewProps);
    if (subView === undefined) {
      subView = defaultRenderer(subViewProps);
    }

    if (subView === null) {
      return null;
    }

    const pointerEvents = offset !== 0 || isStale ? 'none' : 'box-none';
    return (
      <Animated.View
        pointerEvents={pointerEvents}
        key={`${name}_${key}`}
        style={[
          styles.item,
          styles[name],
          styleInterpolator(props),
        ]}
      >
        {subView}
      </Animated.View>
    );
  }

  render(): React.Element<*> {
    // eslint-disable-next-line no-unused-vars
    const { scenes, scene, style, position, progress, ...rest } = this.props;

    let leftComponents = null;
    let titleComponents = null;
    let rightComponents = null;

    if (this.props.mode === 'float') {
      const scenesProps = (scenes.map((scene: NavigationScene, index: number) => {
        const props = NavigationPropTypes.extractSceneRendererProps(this.props);
        props.scene = scene;
        props.index = index;
        props.navigation = addNavigationHelpers({
          ...this.props.navigation,
          state: scene.route,
        });
        return props;
      }): Array<NavigationSceneRendererProps>);
      leftComponents = scenesProps.map(this._renderLeft, this);
      rightComponents = scenesProps.map(this._renderRight, this);
      titleComponents = scenesProps.map((props: *, i: number) =>
        this._renderTitle(props, {
          hasLeftComponent: leftComponents && !!leftComponents[i],
          hasRightComponent: rightComponents && !!rightComponents[i],
        })
      );
    } else {
      const staticRendererProps = {
        ...this.props,
        position: new Animated.Value(scene.index),
        progress: new Animated.Value(0),
      };
      leftComponents = this._renderLeft(staticRendererProps);
      rightComponents = this._renderRight(staticRendererProps);
      titleComponents = this._renderTitle(staticRendererProps, {
        hasLeftComponent: !!leftComponents,
        hasRightComponent: !!rightComponents,
      });
    }

    return (
      <Animated.View {...rest} style={[styles.container, style]}>
        <View style={styles.appBar}>
          {titleComponents}
          {leftComponents}
          {rightComponents}
        </View>
      </Animated.View>
    );
  }
}

const styles = StyleSheet.create({
  container: {
    paddingTop: STATUSBAR_HEIGHT,
    backgroundColor: Platform.OS === 'ios' ? '#EFEFF2' : '#FFF',
    shadowColor: 'black',
    shadowOpacity: 0.1,
    shadowRadius: StyleSheet.hairlineWidth,
    shadowOffset: {
      height: StyleSheet.hairlineWidth,
    },
    elevation: 4,
  },
  appBar: {
    height: APPBAR_HEIGHT,
  },
  item: {
    flexDirection: 'row',
    alignItems: 'center',
  },
  title: {
    bottom: 0,
    left: 40,
    position: 'absolute',
    right: 40,
    top: 0,
  },
  left: {
    bottom: 0,
    left: 0,
    position: 'absolute',
    top: 0,
  },
  right: {
    bottom: 0,
    position: 'absolute',
    right: 0,
    top: 0,
  },
});

module.exports = Header;
<|endoftext|>


<commit_before>var type = require("../type");
var event = require("../event");
var render = require("../render");

require("./event");

var store = new WeakMap();
var cachedProps = new WeakMap();

function update(dom, component, root) {
  event.emit(component, "update");
  var vtree = component.render();
  render(root, vtree);
  event.emit(component, "updated");
}

function emitEvent(dom, component, type, detail) {
  var event = new CustomEvent(type, {
    detail: detail
  });
  dom.dispatchEvent(event);
}

function castType(value, propType) {
  if(propType === type.boolean) {
    return value !== null;
  } else {
    return type.cast(value, propType);
  }
}

function wrapMethod(method) {
  return function() {
    var state = store.get(this);
    return method.apply(state.component, arguments);
  };
}

function register(name, componentClass) {
  var propTypes = componentClass.prototype.propTypes;
  var propNames = Object.keys(propTypes);
  var prototype = Object.assign(Object.create(HTMLElement.prototype), {
    createdCallback: function() {
      var props = {};
      for(var i = 0; i < propNames.length; i++) {
        var name = propNames[i];
        var value = this.getAttribute(name);
        if(name !== null) {
          props[name] = castType(value, propTypes[name]);
        }
      }
      var component = new componentClass(props);
      var root = this.createShadowRoot();
      event.on(component, "upstream:update", update.bind(null, this, component, root));
      event.on(component, "upstream:emit-event", emitEvent.bind(null, this, component));
      event.emit(component, "create");
      store.set(this, {
        component: component,
        root: root
      });
    },
    attachedCallback: function() {
      var state = store.get(this);
      event.emit(state.component, "mount", state.root);
    },
    detachedCallback: function() {
      var state = store.get(this);
      event.emit(state.component, "unmount");
    },
    attributeChangedCallback: function(name, previous, value) {
      if(!propTypes[name]) {
        return;
      }
      var state = store.get(this);
      var oldProps = cachedProps.get(this) || {};
      var props = Object.assign({}, oldProps);
      if(value === null) {
        delete props[name];
      } else {
        props[name] = castType(value, propTypes[name]);
      }
      cachedProps.set(this, props);
      state.component.replaceProps(props);
    }
  });
  for(var key in componentClass.prototype.dom) {
    if(componentClass.prototype.dom.hasOwnProperty(key)) {
      var value = componentClass.prototype.dom[key];
      if(typeof value == "function") {
        prototype[key] = wrapMethod(value);
      } else {
        prototype[key] = value;
      }
    }
  }
  document.registerElement(name, {
    prototype: prototype
  });
}

module.exports = register;
<commit_msg>support for "extends" of custom elements<commit_after>var type = require("../type");
var event = require("../event");
var render = require("../render");

require("./event");

var store = new WeakMap();
var cachedProps = new WeakMap();

function update(dom, component, root) {
  event.emit(component, "update");
  var vtree = component.render();
  render(root, vtree);
  event.emit(component, "updated");
}

function emitEvent(dom, component, type, detail) {
  var event = new CustomEvent(type, {
    detail: detail
  });
  dom.dispatchEvent(event);
}

function castType(value, propType) {
  if(propType === type.boolean) {
    return value !== null;
  } else {
    return type.cast(value, propType);
  }
}

function wrapMethod(method) {
  return function() {
    var state = store.get(this);
    return method.apply(state.component, arguments);
  };
}

function register(name, options, componentClass) {
  if(!componentClass) {
    componentClass = options;
    options = undefined;
  }
  options = options || {};
  var propTypes = componentClass.prototype.propTypes;
  var propNames = Object.keys(propTypes);
  var prototype = Object.assign(Object.create(HTMLElement.prototype), {
    createdCallback: function() {
      var props = {};
      for(var i = 0; i < propNames.length; i++) {
        var name = propNames[i];
        var value = this.getAttribute(name);
        if(name !== null) {
          props[name] = castType(value, propTypes[name]);
        }
      }
      var component = new componentClass(props);
      var root = this.createShadowRoot();
      event.on(component, "upstream:update", update.bind(null, this, component, root));
      event.on(component, "upstream:emit-event", emitEvent.bind(null, this, component));
      event.emit(component, "create");
      store.set(this, {
        component: component,
        root: root
      });
    },
    attachedCallback: function() {
      var state = store.get(this);
      event.emit(state.component, "mount", state.root);
    },
    detachedCallback: function() {
      var state = store.get(this);
      event.emit(state.component, "unmount");
    },
    attributeChangedCallback: function(name, previous, value) {
      if(!propTypes[name]) {
        return;
      }
      var state = store.get(this);
      var oldProps = cachedProps.get(this) || {};
      var props = Object.assign({}, oldProps);
      if(value === null) {
        delete props[name];
      } else {
        props[name] = castType(value, propTypes[name]);
      }
      cachedProps.set(this, props);
      state.component.replaceProps(props);
    }
  });
  for(var key in componentClass.prototype.dom) {
    if(componentClass.prototype.dom.hasOwnProperty(key)) {
      var value = componentClass.prototype.dom[key];
      if(typeof value == "function") {
        prototype[key] = wrapMethod(value);
      } else {
        prototype[key] = value;
      }
    }
  }
  document.registerElement(name, {
    prototype: prototype,
    extends: options.extends
  });
}

module.exports = register;
<|endoftext|>


<commit_before>/*
 * windowsocket
 * https://github.com/goliatone/windowsocket
 * Created with gbase.
 * Copyright (c) 2014 goliatone
 * Licensed under the MIT license.
 */
/* jshint strict: false, plusplus: true */
/*global define: false, require: false, module: false, exports: false */
(function(root, name, deps, factory) {
    "use strict";
    // Node
    if (typeof deps === 'function') {
        factory = deps;
        deps = [];
    }

    if (typeof exports === 'object') {
        module.exports = factory.apply(root, deps.map(require));
    } else if (typeof define === 'function' && 'amd' in define) {
        //require js, here we assume the file is named as the lower
        //case module name.
        define(name.toLowerCase(), deps, factory);
    } else {
        // Browser
        var d, i = 0,
            global = root,
            old = global[name],
            mod;
        while ((d = deps[i]) !== undefined) deps[i++] = root[d];
        global[name] = mod = factory.apply(global, deps);
        //Export no 'conflict module', aliases the module.
        mod.noConflict = function() {
            global[name] = old;
            return mod;
        };
    }
}(this, 'windowsocket', ['extend'], function(extend) {

    /**
     * Extend method.
     * @param  {Object} target Source object
     * @return {Object}        Resulting object from
     *                         meging target to params.
     */
    var _extend = extend;

    /**
     * Shim console, make sure that if no console
     * available calls do not generate errors.
     * @return {Object} Console shim.
     */
    var _shimConsole = function(con) {

        if (con) return con;

        con = {};
        var empty = {},
            noop = function() {},
            properties = 'memory'.split(','),
            methods = ('assert,clear,count,debug,dir,dirxml,error,exception,group,' +
                'groupCollapsed,groupEnd,info,log,markTimeline,profile,profileEnd,' +
                'table,time,timeEnd,timeStamp,trace,warn').split(','),
            prop,
            method;

        while (method = methods.pop()) con[method] = noop;
        while (prop = properties.pop()) con[prop] = empty;

        return con;
    };

    ///////////////////////////////////////////////////
    // CONSTRUCTOR
    ///////////////////////////////////////////////////

    var OPTIONS = {
        autoinitialize: true,

        binaryType: 'blob',
        bufferedAmount: 0,
        extensions: '',
        readyState: 0,

        SCHEME: 'tcp://',

        provider: function() {
            return WindowServer.instance();
        },
        generateUID:function(){
            window.WSUID || (window.WSUID = 0);
            return ++window.WSUID;
        },

        onerror: function() {},
        onclose: function() {},
        onmessage: function() {},
        onopen: function() {}
    };

    var ERROR_REQUIRED_ARGUMENT = 'Failed to construct \'WindowSocket\': 1 argument required, but only 0 present.';
    var ERROR_URL_SCHEME = 'Failed to construct \'WindowSocket\': The URL\'s scheme must be \'{{scheme}}\'.';
    var ERROR_CONNECT_URL = 'WindowSocket connection to \'{{url}}\' failed: Connection closed before receiving a handshake response.'
    var ERROR_INVALID_STATE = 'WindowSocket connection has not been established';
    var ERROR_UNKNOWN_EVENT_TYPE = 'WindowSocket unkown event type {{type}}';
    /**
     * WindowSocket constructor
     *
     * @param  {String} url       The URL to which to connect; this
     *                            should be the URL to which the
     *                            WindowSocket
     *                            server will respond.
     * @param  {String|Array} protocols Optional. Either a single protocol
     *                                  string or an array of protocol strings.
     *                                  These strings are used to indicate
     *                                  sub-protocols, so that a single server
     *                                  can implement multiple WebSocket
     *                                  sub-protocols (for example, you might
     *                                  want one server to be able to handle
     *                                  different types of interactions depending
     *                                  on the specified protocol). If you don't
     *                                  specify a protocol string, an empty string
     *                                  is assumed.
     * @param  {object} config Configuration object.
     */
    var WindowSocket = function(url, protocols, config) {
        this.url = url;
        this.protocol = protocols;

        this._listeners = {};

        config = _extend({}, this.constructor.DEFAULTS, config);

        this.SERVER = config.provider();

        if (config.autoinitialize) this.init(config);
    };

    WindowSocket.name = WindowSocket.prototype.name = 'WindowSocket';

    WindowSocket.VERSION = '0.0.0';

    /**
     * Make default options available so we
     * can override.
     */
    WindowSocket.DEFAULTS = OPTIONS;

    /**
     * The connection is not yet open.
     */
    WindowSocket.CONNECTING = 0;
    /**
     * The connection is open and ready to communicate.
     */
    WindowSocket.OPEN = 1;
    /**
     * The connection is in the process of closing.
     */
    WindowSocket.CLOSING = 2;
    /**
     * The connection is closed or couldn't be opened.
     */
    WindowSocket.CLOSED = 3;

    ///////////////////////////////////////////////////
    // PUBLIC METHODS
    ///////////////////////////////////////////////////

    /**
     * The WindowSocket object provides the API for creating
     * and managing a WindowSocket connection to a server,
     * as well as for sending and receiving data
     * on the connection.
     *
     * @param  {String} url       The URL to which to connect; this
     *                            should be the URL to which the
     *                            WindowSocket
     *                            server will respond.
     * @param  {String|Array} protocols Optional. Either a single protocol
     *                                  string or an array of protocol strings.
     *                                  These strings are used to indicate
     *                                  sub-protocols, so that a single server
     *                                  can implement multiple WebSocket
     *                                  sub-protocols (for example, you might
     *                                  want one server to be able to handle
     *                                  different types of interactions depending
     *                                  on the specified protocol). If you don't
     *                                  specify a protocol string, an empty string
     *                                  is assumed.
     * @throws SECURITY_ERR The port to which the connection is being attempted is being blocked.
     */
    WindowSocket.prototype.init = function(config) {
        if (this.initialized) return this.logger.warn('Already initialized');
        this.initialized = true;

        this.ID = config.generateUID();

        console.log('WindowSocket: Init!');
        _extend(this, config);

        if (!this.url) throw new TypeError(ERROR_REQUIRED_ARGUMENT);
        if (this.url.indexOf(this.SCHEME) !== 0) throw new SyntaxError(ERROR_REQUIRED_ARGUMENT);
        if (!this.SERVER.connect(this)) throw new Error(ERROR_CONNECT_URL);

        return 'This is just a stub!';
    };

    /**
     * Transmits data to the server over the WindowSocket
     * connection.
     * @param  {String} data A text string to send to
     *                       the server
     * @throws {INVALID_STATE_ERR} If connection is not currently OPEN.
     * @throws {SYNTAX_ERR} The data is a string that has unpaired surrogates.
     * @return {void}
     */
    WindowSocket.prototype.send = function(data) {
        //Since we send the connection event on the next frame
        //we might construct a new WindowSocket instance and
        //call send right away. If state is CONNECTING we should
        //probably throw an Error?
        //TODO: Confirm WebSocket behavior.
        if (this.readyState === WindowSocket.CONNECTING) {
            return;
        }

        if (this.readyState !== WindowSocket.OPEN) {
            throw new Error(ERROR_INVALID_STATE);
        }

        this.SERVER.send(this, data);
    };

    /**
     * Closes the WindowSocket connection or connection
     * attempt, if any. If the connection is already
     * CLOSED, this method does nothing.
     * @return {void}
     *
     * @param  {Number} code   A numeric value indicating the status
     *                         code explaining why the connection is
     *                         being closed. If this parameter is not
     *                         specified, a default value of 1000
     *                         (indicating a normal "transaction complete"
     *                         closure) is assumed. See the list of status
     *                         codes on the CloseEvent page for
     *                         permitted values.
     * @param  {String} reason A human-readable string explaining why the
     *                         connection is closing. This string must be
     *                         no longer than 123 bytes of UTF-8 text
     *                         (not characters).
     * @throws {INVALID_ACCESS_ERR} If an invalid code was specified.
     * @throws {SYNTAX_ERR} The reason string is too long or contains
     *                      unpaired surrogates.
     * @return {void}
     */
    WindowSocket.prototype.close = function(code, reason) {
        //TODO: Guard against initial frame delay!
        //
        if (this.readyState === WindowSocket.CLOSED || this.readyState === WindowSocket.CLOSING) {
            return;
        }

        this.readyState = WindowSocket.CLOSING;
        this.SERVER.close(this);
    };

    ///////////////////////////////////////////////////
    // EventTarget Interface
    // @see http://www.w3.org/TR/DOM-Level-2-Events/events.html#Events-registration
    ///////////////////////////////////////////////////

    /**
     * DOM 2 EventTarget Interface implementation.
     * @param {String} type
     * @param {Function} listener
     * @param {Boolean} useCapture
     * @return {void}
     */
    WindowSocket.prototype.addEventListener = function(type, listener, useCapture) {
        if (!(type in this._listeners)) this._listeners[type] = [];
        this._listeners[type].push(listener);
    };

    /**
     * DOM 2 EventTarget Interface implementation.
     * @param {String} type
     * @param {Function} listener
     * @param {Boolean} useCapture
     * @return {void}
     */
    WindowSocket.prototype.removeEventListener = function(type, listener, useCapture) {
        if (!(type in this._listeners)) return;
        var events = this._listeners[type];
        var i = events.length - 1;
        for (i; i >= 0; --i) {
            if (events[i] !== listener) continue;
            events.splice(i, 1);
            break;
        }
    };

    /**
     * DOM 2 EventTarget Interface implementation.
     * @param {Event} event
     * @return {void}
     */
    WindowSocket.prototype.dispatchEvent = function(event) {
        var events = this._listeners[event.type] || [];
        var t = events.length,
            i = 0;
        for (; i < t; ++i) {
            events[i](event);
        }
        //apply delegated callbacks:
        var handler = this['on' + event.type];
        if (typeof handler === 'function') handler.call(this, event);
    };

    ///////////////////////////////////////////////////
    // PRIVATE METHODS
    ///////////////////////////////////////////////////
    /**
     * Method to trigger external events.
     * @param  {Object} e
     * @return {void}
     */
    WindowSocket.prototype._triggerEvent = function(e) {
        if (e.readyState) this.readyState = e.readyState;

        //TODO: Clean this up. It's really gross!
        var event,
            events = ['open', 'error', 'close'];
        if (events.indexOf(e.type) !== -1) {
            event = this._buildEvent(e);
        } else if (e.type === 'message') {
            event = this._buildMessageEvent(e);
        } else {
            //TODO: ErrorMessage factory, replace strings.
            throw new Error(ERROR_UNKNOWN_EVENT_TYPE);
        }

        this.dispatchEvent(event);
    };

    /**
     * Create an Event instance.
     * Interface DocumentEvent DOM Level 2.
     *
     * @see http://www.w3.org/TR/DOM-Level-2-Events/events.html#Events-DocumentEvent
     * @see https://developer.mozilla.org/en-US/docs/Web/API/document.createEvent
     * @param  {String} type
     * @return {void}
     */
    WindowSocket.prototype._buildEvent = function(e) {
        //assert(e.type);
        if (document.createEvent && window.Event) {
            var event = document.createEvent('Event');
            event.initEvent(e.type, false, false);
            return event;
        }
        return {
            type: e.type,
            bubbles: false,
            cancelable: false
        };
    };

    /**
     * Create an MessageEvent instance.
     * Interface DocumentEvent DOM Level 2.
     *
     * @see http://www.w3.org/TR/DOM-Level-2-Events/events.html#Events-DocumentEvent
     * @see https://developer.mozilla.org/en-US/docs/Web/API/document.createEvent
     * @param  {String} data
     * @return {void}
     */
    WindowSocket.prototype._buildMessageEvent = function(e) {
        //TODO: assert(e.message);
        var data = e.message;
        console.debug('BUILD MESSAGE EVENT', e, data)
        if (window.MessageEvent && typeof MessageEvent === 'function') {
            return new MessageEvent('message', {
                'view': window,
                'bubbles': false,
                'cancelable': false,
                'data': data
            });
        }

        if (document.createEvent && window.MessageEvent) {
            var event = document.createEvent('MessageEvent');
            event.initWithMessage('message', false, false, data, null, null, window, null);
            return event;
        }

        return {
            type: 'message',
            data: data,
            bubbles: false,
            cancelable: false
        };
    };

    /**
     * @see  https://developer.mozilla.org/en-US/docs/Web/API/CloseEvent
     * @param  {Event} e
     * @return {void}
     */
    WindowSocket.prototype._buildCloseEvent = function(e) {
        //TODO: assert(e.code, e.reason, e.clean);
        var event = this._buildEvent('close');
        event.code = e.code;
        event.reason = e.reason;
        event.wasClean = e.clean;

        return event;
    };

    WindowSocket.prototype._throwError = function(message) {
        var args = [].splice.call(arguments, 1);
        //TODO: replace message tokens, throw error
    };

    /**
     * Logger method, meant to be implemented by
     * mixin. As a placeholder, we use console if available
     * or a shim if not present.
     */
    WindowSocket.prototype.logger = _shimConsole(console);

    function WindowServer() {
        this._instances = {};
    }

    WindowServer.instance = function() {
        if (this._instance) return this._instance;
        this._instance = new WindowServer();
        // this._instance.connected = false;
        return this._instance;
    };

    WindowServer.prototype.init = function() {
        if (this.initialized) return;
        this.initialized = true;
    };

    WindowServer.prototype.connect = function(instance) {
        if (!this.connection) return false;

        this._instances[instance.ID] = instance;
        var url = instance.url;

        //execute next
        setTimeout(function() {
            instance.readyState = WindowSocket.OPEN;
            instance.onopen(new Event('open'));
        }, 0);

        return true;
    };

    WindowServer.prototype.receive = function(events) {
        !Array.isArray(events) && (events = [events]);
        //event => should have type
        //message
        events.map(function mapEvent(event) {
            //TODO: What to do?
            if(!event.clientId) return console.warn('Event needs clientId!');
            try{
                this._instances[event.clientId]._triggerEvent(event);
            }catch(e){console.log(e);}

        }, this);

    };

    WindowServer.prototype.send = function(instance, data) {
        if(!this._instances[instance.ID]) return console.warn('No instance registered');
        this.connection.send(instance.url, instance.ID, data);
    };

    WindowServer.prototype.close = function(instance) {
        this.connection.close(instance.url, instance.ID);
        instance.readyState = WindowSocket.CLOSED;
    };

    window.server = WindowServer.instance();
    window.server.connection = _tcpManager();
    return WindowSocket;
}));
//receive
function _tcpManager(server) {

    this.receive = function(e) {

    };

    this.send = function(url, id, msg) {
        console.log('SENDING:', url, id, msg)
    };
    this.close = function(url, id) {
        console.log('CLOSING')
    };

    return this;
}<commit_msg>WindowSocket: Update lib export name. Fix MessageEvent check<commit_after>/*
 * windowsocket
 * https://github.com/goliatone/windowsocket
 * Created with gbase.
 * Copyright (c) 2014 goliatone
 * Licensed under the MIT license.
 */
/* jshint strict: false, plusplus: true */
/*global define: false, require: false, module: false, exports: false */
(function(root, name, deps, factory) {
    "use strict";
    // Node
    if (typeof deps === 'function') {
        factory = deps;
        deps = [];
    }

    if (typeof exports === 'object') {
        module.exports = factory.apply(root, deps.map(require));
    } else if (typeof define === 'function' && 'amd' in define) {
        //require js, here we assume the file is named as the lower
        //case module name.
        define(name.toLowerCase(), deps, factory);
    } else {
        // Browser
        var d, i = 0,
            global = root,
            old = global[name],
            mod;
        while ((d = deps[i]) !== undefined) deps[i++] = root[d];
        global[name] = mod = factory.apply(global, deps);
        //Export no 'conflict module', aliases the module.
        mod.noConflict = function() {
            global[name] = old;
            return mod;
        };
    }
}(this, 'WindowSocket', ['extend'], function(extend) {

    /**
     * Extend method.
     * @param  {Object} target Source object
     * @return {Object}        Resulting object from
     *                         meging target to params.
     */
    var _extend = extend;

    /**
     * Shim console, make sure that if no console
     * available calls do not generate errors.
     * @return {Object} Console shim.
     */
    var _shimConsole = function(con) {

        if (con) return con;

        con = {};
        var empty = {},
            noop = function() {},
            properties = 'memory'.split(','),
            methods = ('assert,clear,count,debug,dir,dirxml,error,exception,group,' +
                'groupCollapsed,groupEnd,info,log,markTimeline,profile,profileEnd,' +
                'table,time,timeEnd,timeStamp,trace,warn').split(','),
            prop,
            method;

        while (method = methods.pop()) con[method] = noop;
        while (prop = properties.pop()) con[prop] = empty;

        return con;
    };

    ///////////////////////////////////////////////////
    // CONSTRUCTOR
    ///////////////////////////////////////////////////

    var OPTIONS = {
        autoinitialize: true,

        binaryType: 'blob',
        bufferedAmount: 0,
        extensions: '',
        readyState: 0,

        SCHEME: 'tcp://',

        provider: function() {
            return WindowServer.instance();
        },
        generateUID:function(){
            window.WSUID || (window.WSUID = 0);
            return ++window.WSUID;
        },

        onerror: function() {},
        onclose: function() {},
        onmessage: function() {},
        onopen: function() {}
    };

    var ERROR_REQUIRED_ARGUMENT = 'Failed to construct \'WindowSocket\': 1 argument required, but only 0 present.';
    var ERROR_URL_SCHEME = 'Failed to construct \'WindowSocket\': The URL\'s scheme must be \'{{scheme}}\'.';
    var ERROR_CONNECT_URL = 'WindowSocket connection to \'{{url}}\' failed: Connection closed before receiving a handshake response.'
    var ERROR_INVALID_STATE = 'WindowSocket connection has not been established';
    var ERROR_UNKNOWN_EVENT_TYPE = 'WindowSocket unkown event type {{type}}';
    /**
     * WindowSocket constructor
     *
     * @param  {String} url       The URL to which to connect; this
     *                            should be the URL to which the
     *                            WindowSocket
     *                            server will respond.
     * @param  {String|Array} protocols Optional. Either a single protocol
     *                                  string or an array of protocol strings.
     *                                  These strings are used to indicate
     *                                  sub-protocols, so that a single server
     *                                  can implement multiple WebSocket
     *                                  sub-protocols (for example, you might
     *                                  want one server to be able to handle
     *                                  different types of interactions depending
     *                                  on the specified protocol). If you don't
     *                                  specify a protocol string, an empty string
     *                                  is assumed.
     * @param  {object} config Configuration object.
     */
    var WindowSocket = function(url, protocols, config) {
        this.url = url;
        this.protocol = protocols;

        this._listeners = {};

        config = _extend({}, this.constructor.DEFAULTS, config);

        this.SERVER = config.provider();

        if (config.autoinitialize) this.init(config);
    };

    WindowSocket.name = WindowSocket.prototype.name = 'WindowSocket';

    WindowSocket.VERSION = '0.0.0';

    /**
     * Make default options available so we
     * can override.
     */
    WindowSocket.DEFAULTS = OPTIONS;

    /**
     * The connection is not yet open.
     */
    WindowSocket.CONNECTING = 0;
    /**
     * The connection is open and ready to communicate.
     */
    WindowSocket.OPEN = 1;
    /**
     * The connection is in the process of closing.
     */
    WindowSocket.CLOSING = 2;
    /**
     * The connection is closed or couldn't be opened.
     */
    WindowSocket.CLOSED = 3;

    ///////////////////////////////////////////////////
    // PUBLIC METHODS
    ///////////////////////////////////////////////////

    /**
     * The WindowSocket object provides the API for creating
     * and managing a WindowSocket connection to a server,
     * as well as for sending and receiving data
     * on the connection.
     *
     * @param  {String} url       The URL to which to connect; this
     *                            should be the URL to which the
     *                            WindowSocket
     *                            server will respond.
     * @param  {String|Array} protocols Optional. Either a single protocol
     *                                  string or an array of protocol strings.
     *                                  These strings are used to indicate
     *                                  sub-protocols, so that a single server
     *                                  can implement multiple WebSocket
     *                                  sub-protocols (for example, you might
     *                                  want one server to be able to handle
     *                                  different types of interactions depending
     *                                  on the specified protocol). If you don't
     *                                  specify a protocol string, an empty string
     *                                  is assumed.
     * @throws SECURITY_ERR The port to which the connection is being attempted is being blocked.
     */
    WindowSocket.prototype.init = function(config) {
        if (this.initialized) return this.logger.warn('Already initialized');
        this.initialized = true;

        this.ID = config.generateUID();

        console.log('WindowSocket: Init!');
        _extend(this, config);

        if (!this.url) throw new TypeError(ERROR_REQUIRED_ARGUMENT);
        if (this.url.indexOf(this.SCHEME) !== 0) throw new SyntaxError(ERROR_REQUIRED_ARGUMENT);
        if (!this.SERVER.connect(this)) throw new Error(ERROR_CONNECT_URL);

        return 'This is just a stub!';
    };

    /**
     * Transmits data to the server over the WindowSocket
     * connection.
     * @param  {String} data A text string to send to
     *                       the server
     * @throws {INVALID_STATE_ERR} If connection is not currently OPEN.
     * @throws {SYNTAX_ERR} The data is a string that has unpaired surrogates.
     * @return {void}
     */
    WindowSocket.prototype.send = function(data) {
        //Since we send the connection event on the next frame
        //we might construct a new WindowSocket instance and
        //call send right away. If state is CONNECTING we should
        //probably throw an Error?
        //TODO: Confirm WebSocket behavior.
        if (this.readyState === WindowSocket.CONNECTING) {
            return;
        }

        if (this.readyState !== WindowSocket.OPEN) {
            throw new Error(ERROR_INVALID_STATE);
        }

        this.SERVER.send(this, data);
    };

    /**
     * Closes the WindowSocket connection or connection
     * attempt, if any. If the connection is already
     * CLOSED, this method does nothing.
     * @return {void}
     *
     * @param  {Number} code   A numeric value indicating the status
     *                         code explaining why the connection is
     *                         being closed. If this parameter is not
     *                         specified, a default value of 1000
     *                         (indicating a normal "transaction complete"
     *                         closure) is assumed. See the list of status
     *                         codes on the CloseEvent page for
     *                         permitted values.
     * @param  {String} reason A human-readable string explaining why the
     *                         connection is closing. This string must be
     *                         no longer than 123 bytes of UTF-8 text
     *                         (not characters).
     * @throws {INVALID_ACCESS_ERR} If an invalid code was specified.
     * @throws {SYNTAX_ERR} The reason string is too long or contains
     *                      unpaired surrogates.
     * @return {void}
     */
    WindowSocket.prototype.close = function(code, reason) {
        //TODO: Guard against initial frame delay!
        //
        if (this.readyState === WindowSocket.CLOSED || this.readyState === WindowSocket.CLOSING) {
            return;
        }

        this.readyState = WindowSocket.CLOSING;
        this.SERVER.close(this);
    };

    ///////////////////////////////////////////////////
    // EventTarget Interface
    // @see http://www.w3.org/TR/DOM-Level-2-Events/events.html#Events-registration
    ///////////////////////////////////////////////////

    /**
     * DOM 2 EventTarget Interface implementation.
     * @param {String} type
     * @param {Function} listener
     * @param {Boolean} useCapture
     * @return {void}
     */
    WindowSocket.prototype.addEventListener = function(type, listener, useCapture) {
        if (!(type in this._listeners)) this._listeners[type] = [];
        this._listeners[type].push(listener);
    };

    /**
     * DOM 2 EventTarget Interface implementation.
     * @param {String} type
     * @param {Function} listener
     * @param {Boolean} useCapture
     * @return {void}
     */
    WindowSocket.prototype.removeEventListener = function(type, listener, useCapture) {
        if (!(type in this._listeners)) return;
        var events = this._listeners[type];
        var i = events.length - 1;
        for (i; i >= 0; --i) {
            if (events[i] !== listener) continue;
            events.splice(i, 1);
            break;
        }
    };

    /**
     * DOM 2 EventTarget Interface implementation.
     * @param {Event} event
     * @return {void}
     */
    WindowSocket.prototype.dispatchEvent = function(event) {
        var events = this._listeners[event.type] || [];
        var t = events.length,
            i = 0;
        for (; i < t; ++i) {
            events[i](event);
        }
        //apply delegated callbacks:
        var handler = this['on' + event.type];
        if (typeof handler === 'function') handler.call(this, event);
    };

    ///////////////////////////////////////////////////
    // PRIVATE METHODS
    ///////////////////////////////////////////////////
    /**
     * Method to trigger external events.
     * @param  {Object} e
     * @return {void}
     */
    WindowSocket.prototype._triggerEvent = function(e) {
        if (e.readyState) this.readyState = e.readyState;

        //TODO: Clean this up. It's really gross!
        var event,
            events = ['open', 'error', 'close'];
        if (events.indexOf(e.type) !== -1) {
            event = this._buildEvent(e);
        } else if (e.type === 'message') {
            event = this._buildMessageEvent(e);
        } else {
            //TODO: ErrorMessage factory, replace strings.
            throw new Error(ERROR_UNKNOWN_EVENT_TYPE);
        }

        this.dispatchEvent(event);
    };

    /**
     * Create an Event instance.
     * Interface DocumentEvent DOM Level 2.
     *
     * @see http://www.w3.org/TR/DOM-Level-2-Events/events.html#Events-DocumentEvent
     * @see https://developer.mozilla.org/en-US/docs/Web/API/document.createEvent
     * @param  {String} type
     * @return {void}
     */
    WindowSocket.prototype._buildEvent = function(e) {
        //assert(e.type);
        if (document.createEvent && window.Event) {
            var event = document.createEvent('Event');
            event.initEvent(e.type, false, false);
            return event;
        }
        return {
            type: e.type,
            bubbles: false,
            cancelable: false
        };
    };

    /**
     * Create an MessageEvent instance.
     * Interface DocumentEvent DOM Level 2.
     *
     * @see http://www.w3.org/TR/DOM-Level-2-Events/events.html#Events-DocumentEvent
     * @see https://developer.mozilla.org/en-US/docs/Web/API/document.createEvent
     * @param  {String} data
     * @return {void}
     */
    WindowSocket.prototype._buildMessageEvent = function(e) {
        //TODO: assert(e.message);
        var data = e.message;
        console.debug('BUILD MESSAGE EVENT', e, data)
        //Safari's MessageEvent constructor is an Object! Just like WebSocket
        if (window.MessageEvent /*&& typeof MessageEvent === 'function'*/) {
            return new MessageEvent('message', {
                'view': window,
                'bubbles': false,
                'cancelable': false,
                'data': data
            });
        }

        if (document.createEvent && window.MessageEvent) {
            var event = document.createEvent('MessageEvent');
            event.initWithMessage('message', false, false, data, null, null, window, null);
            return event;
        }

        return {
            type: 'message',
            data: data,
            bubbles: false,
            cancelable: false
        };
    };

    /**
     * @see  https://developer.mozilla.org/en-US/docs/Web/API/CloseEvent
     * @param  {Event} e
     * @return {void}
     */
    WindowSocket.prototype._buildCloseEvent = function(e) {
        //TODO: assert(e.code, e.reason, e.clean);
        var event = this._buildEvent('close');
        event.code = e.code;
        event.reason = e.reason;
        event.wasClean = e.clean;

        return event;
    };

    WindowSocket.prototype._throwError = function(message) {
        var args = [].splice.call(arguments, 1);
        //TODO: replace message tokens, throw error
    };

    /**
     * Logger method, meant to be implemented by
     * mixin. As a placeholder, we use console if available
     * or a shim if not present.
     */
    WindowSocket.prototype.logger = _shimConsole(console);

    function WindowServer() {
        this._instances = {};
    }

    WindowServer.instance = function() {
        if (this._instance) return this._instance;
        this._instance = new WindowServer();
        // this._instance.connected = false;
        return this._instance;
    };

    WindowServer.prototype.init = function() {
        if (this.initialized) return;
        this.initialized = true;
    };

    WindowServer.prototype.connect = function(instance) {
        if (!this.connection) return false;

        this._instances[instance.ID] = instance;
        var url = instance.url;

        //execute next
        setTimeout(function() {
            instance.readyState = WindowSocket.OPEN;
            instance.onopen(new Event('open'));
        }, 0);

        return true;
    };

    WindowServer.prototype.receive = function(events) {
        !Array.isArray(events) && (events = [events]);
        //event => should have type
        //message
        events.map(function mapEvent(event) {
            //TODO: What to do?
            if(!event.clientId) return console.warn('Event needs clientId!');
            try{
                this._instances[event.clientId]._triggerEvent(event);
            }catch(e){console.log(e);}

        }, this);

    };

    WindowServer.prototype.send = function(instance, data) {
        if(!this._instances[instance.ID]) return console.warn('No instance registered');
        this.connection.send(instance.url, instance.ID, data);
    };

    WindowServer.prototype.close = function(instance) {
        this.connection.close(instance.url, instance.ID);
        instance.readyState = WindowSocket.CLOSED;
    };

    window.server = WindowServer.instance();
    window.server.connection = _tcpManager();
    return WindowSocket;
}));
//receive
function _tcpManager(server) {

    this.receive = function(e) {

    };

    this.send = function(url, id, msg) {
        console.log('SENDING:', url, id, msg)
    };
    this.close = function(url, id) {
        console.log('CLOSING')
    };

    return this;
}<|endoftext|>


<commit_before>import React, { useState, useEffect } from "react";
import PubSub from 'pubsub-js'
import IconButton from "@material-ui/core/IconButton";
import MenuIcon from "@material-ui/icons/Menu";
import Tooltip from '@material-ui/core/Tooltip';
import { AppMenu, menuItem, menuParentItem } from "../common/Menus";
import { store } from "./diagram/Store";
import Printer from "../common/Printer";
import { useAbout } from "./About";
import { useLogin } from "./Login";
import { setConfirmAlert } from "../common/AlertDialog";

const getDiagramsMenuItems = () => {
    const diagrams = store.getRecentDiagramInfos().slice(1)
    return diagrams.map(d => menuItem(d.name, () => PubSub.publish('canvas.OpenDiagram', d.diagramId)))
}

export function ApplicationMenu() {
    const [menu, setMenu] = useState(null);
    const [, setShowAbout] = useAbout()
    const [, setShowLogin] = useLogin()

    useEffect(() => {
        const handler = Printer.registerPrintKey(() => PubSub.publish('canvas.Print'))
        return () => Printer.deregisterPrintKey(handler)

    })

    const deleteDiagram = () => {
        setConfirmAlert('Delete', 'Do you really want to delete the current diagram?',
            ok => {
                if (ok) {
                    PubSub.publish('canvas.DeleteDiagram')
                }
            })
    };

    // const clearLocalData = () => {
    //     if (!confirm('Do you really want to clear all local data?')) {//eslint-disable-line
    //         return
    //     }

    //     store.clearLocalData()
    //     window.location.reload()
    // };

    // const clearAllData = async () => {
    //     if (!confirm('Do you really want to clear all local and remote data?')) {//eslint-disable-line
    //         return
    //     }

    //     if (await store.clearRemoteData()) {
    //         store.clearLocalData()
    //         window.location.reload()
    //     }
    // };

    const diagrams = menu == null ? [] : getDiagramsMenuItems()
    const isInStandaloneMode = () =>
        (window.matchMedia('(display-mode: standalone)').matches)
        || (window.navigator.standalone)
        || document.referrer.includes('android-app://');

    const menuItems = [
        menuItem('New Diagram', () => PubSub.publish('canvas.NewDiagram')),
        menuParentItem('Open Recent', diagrams, diagrams.length > 0),
        menuItem('Print', () => PubSub.publish('canvas.Print')),
        menuItem('Delete', deleteDiagram),
        menuItem('Enable cloud sync', () => setShowLogin(true), true, !store.isCloudSyncEnabled()),
        menuItem('Disable cloud sync', () => store.disableCloudSync(), true, store.isCloudSyncEnabled()),
        menuParentItem('Files', [
            menuItem('Open file ...', () => PubSub.publish('canvas.OpenFile')),
            menuItem('Save diagram to file', () => PubSub.publish('canvas.SaveDiagramToFile')),
            menuItem('Save/Archive all to file', () => PubSub.publish('canvas.ArchiveToFile')),
        ]),
        menuItem('Reload web page', () => window.location.reload(), true, isInStandaloneMode()),
        menuItem('About', () => setShowAbout(true)),
        // menuParentItem('Advanced', [
        //     menuItem('Clear all local data', () => clearLocalData()),
        //     menuItem('Clear all local and remote user data', () => clearAllData()),
        // ]),
    ]

    return (
        <>
            <Tooltip title="Customize and control">
                <IconButton
                    edge="start"
                    color="inherit"
                    onClick={e => setMenu(e.currentTarget)}
                >
                    <MenuIcon />
                </IconButton>
            </Tooltip>

            <AppMenu anchorEl={menu} items={menuItems} onClose={setMenu} />
        </>
    )
}

<commit_msg>Fix delete diagram<commit_after>import React, { useState, useEffect } from "react";
import PubSub from 'pubsub-js'
import IconButton from "@material-ui/core/IconButton";
import MenuIcon from "@material-ui/icons/Menu";
import Tooltip from '@material-ui/core/Tooltip';
import { AppMenu, menuItem, menuParentItem } from "../common/Menus";
import { store } from "./diagram/Store";
import Printer from "../common/Printer";
import { useAbout } from "./About";
import { useLogin } from "./Login";
import { setConfirmAlert } from "../common/AlertDialog";

const getDiagramsMenuItems = () => {
    const diagrams = store.getRecentDiagramInfos().slice(1)
    return diagrams.map(d => menuItem(d.name, () => PubSub.publish('canvas.OpenDiagram', d.diagramId)))
}

export function ApplicationMenu() {
    const [menu, setMenu] = useState(null);
    const [, setShowAbout] = useAbout()
    const [, setShowLogin] = useLogin()

    useEffect(() => {
        const handler = Printer.registerPrintKey(() => PubSub.publish('canvas.Print'))
        return () => Printer.deregisterPrintKey(handler)

    })

    const deleteDiagram = () => {
        setConfirmAlert('Delete', 'Do you really want to delete the current diagram?',
            () => PubSub.publish('canvas.DeleteDiagram'))
    };

    // const clearLocalData = () => {
    //     if (!confirm('Do you really want to clear all local data?')) {//eslint-disable-line
    //         return
    //     }

    //     store.clearLocalData()
    //     window.location.reload()
    // };

    // const clearAllData = async () => {
    //     if (!confirm('Do you really want to clear all local and remote data?')) {//eslint-disable-line
    //         return
    //     }

    //     if (await store.clearRemoteData()) {
    //         store.clearLocalData()
    //         window.location.reload()
    //     }
    // };

    const diagrams = menu == null ? [] : getDiagramsMenuItems()
    const isInStandaloneMode = () =>
        (window.matchMedia('(display-mode: standalone)').matches)
        || (window.navigator.standalone)
        || document.referrer.includes('android-app://');

    const menuItems = [
        menuItem('New Diagram', () => PubSub.publish('canvas.NewDiagram')),
        menuParentItem('Open Recent', diagrams, diagrams.length > 0),
        menuItem('Print', () => PubSub.publish('canvas.Print')),
        menuItem('Delete', deleteDiagram),
        menuItem('Enable cloud sync', () => setShowLogin(true), true, !store.isCloudSyncEnabled()),
        menuItem('Disable cloud sync', () => store.disableCloudSync(), true, store.isCloudSyncEnabled()),
        menuParentItem('Files', [
            menuItem('Open file ...', () => PubSub.publish('canvas.OpenFile')),
            menuItem('Save diagram to file', () => PubSub.publish('canvas.SaveDiagramToFile')),
            menuItem('Save/Archive all to file', () => PubSub.publish('canvas.ArchiveToFile')),
        ]),
        menuItem('Reload web page', () => window.location.reload(), true, isInStandaloneMode()),
        menuItem('About', () => setShowAbout(true)),
        // menuParentItem('Advanced', [
        //     menuItem('Clear all local data', () => clearLocalData()),
        //     menuItem('Clear all local and remote user data', () => clearAllData()),
        // ]),
    ]

    return (
        <>
            <Tooltip title="Customize and control">
                <IconButton
                    edge="start"
                    color="inherit"
                    onClick={e => setMenu(e.currentTarget)}
                >
                    <MenuIcon />
                </IconButton>
            </Tooltip>

            <AppMenu anchorEl={menu} items={menuItems} onClose={setMenu} />
        </>
    )
}

<|endoftext|>


<commit_before>// WizardModal
// ---------------------------------
//
// A simple modal for dialogs with multi-step wizard behavior

BackboneBootstrapModals.WizardModal = BackboneBootstrapModals.BaseModal.extend({

  events: {
    'click #confirmation-previous-btn': 'onClickPrevious',
    'click #confirmation-next-btn': 'onClickNext'
  },

  initialize: function(opts) {
    if (!opts.steps || !_.isArray(opts.steps) || !opts.steps.length) {
      throw new Error("steps array must be specified and non-empty");
    }
    this.stepIndex = 0;
    this.steps = opts.steps;

    var options = this.getOptionsForStep(this.stepIndex);
    options.modalOptions = _.extend({}, this.defaultModalOptions, opts.modalOptions);
    BackboneBootstrapModals.BaseModal.prototype.initialize.call(this, options);
  },

  // Return BaseModal options for the current step index
  getOptionsForStep: function(index) {
    var step = this.steps[index];
    return {
      headerViewOptions: {
        label: step.label,
        labelId: 'myModalLabel',
        showClose: true,
      },
      bodyView: new step.view(step.viewOptions),
      footerViewOptions: {
        buttons: this.getButtonsForStep(index)
      }
    };
  },

  getButtonsForStep: function(index) {
    var step = this.steps[index],
        previousText = step.previousText || 'Previous',
        nextText = step.nextText || 'Next';
    if (index === 0) {
      return [
        { id: 'confirmation-next-btn',className: 'btn btn-primary', value: nextText }
      ];
    } else {
      return [
        { id: 'confirmation-previous-btn',className: 'btn btn-default', value: previousText },
        { id: 'confirmation-next-btn',className: 'btn btn-primary', value: nextText }
      ];
    }
  },

  onClickPrevious: function(e) {
    e.preventDefault();
    e.currentTarget.disabled = true;
    this.stepIndex -= 1;
    this.renderStep();
  },

  onClickNext: function(e) {
    e.preventDefault();
    e.currentTarget.disabled = true;

    // Execute the specified callback if it exists, then proceed.
    // The modal will not proceed if the callback returns false.
    if (this.onNext) {
        if (this.onNext.call() === false) {
            return;
        }
    }

    if (this.stepIndex === this.steps.length-1) {
      this.hide();
    } else {
      this.stepIndex += 1;
      this.renderStep();
    }
  },

  // Remove Previous sub views and initialize sub views for the new step
  renderStep: function() {
    var options = this.getOptionsForStep(this.stepIndex);
    BackboneBootstrapModals.BaseModal.prototype.removeSubViews.call(this);
    BackboneBootstrapModals.BaseModal.prototype.initializeSubViews.call(this, options);
    this.render();
  }
});
<commit_msg>Small tweak to wizard<commit_after>// WizardModal
// ---------------------------------
//
// A simple modal for dialogs with multi-step wizard behavior

BackboneBootstrapModals.WizardModal = BackboneBootstrapModals.BaseModal.extend({

  events: {
    'click #confirmation-previous-btn': 'onClickPrevious',
    'click #confirmation-next-btn': 'onClickNext'
  },

  initialize: function(opts) {
    if (!opts.steps || !_.isArray(opts.steps) || !opts.steps.length) {
      throw new Error("steps array must be specified and non-empty");
    }
    this.stepIndex = 0;
    if (!this.steps) {
      this.steps = opts.steps;
    }

    var options = this.getOptionsForStep(this.stepIndex);
    options.modalOptions = _.extend({}, this.defaultModalOptions, opts.modalOptions);
    BackboneBootstrapModals.BaseModal.prototype.initialize.call(this, options);
  },

  // Return BaseModal options for the current step index
  getOptionsForStep: function(index) {
    var step = this.steps[index];
    return {
      headerViewOptions: {
        label: step.label,
        labelId: 'myModalLabel',
        showClose: true,
      },
      bodyView: new step.view(step.viewOptions),
      footerViewOptions: {
        buttons: this.getButtonsForStep(index)
      }
    };
  },

  getButtonsForStep: function(index) {
    var step = this.steps[index],
        previousText = step.previousText || 'Previous',
        nextText = step.nextText || 'Next';
    if (index === 0) {
      return [
        { id: 'confirmation-next-btn',className: 'btn btn-primary', value: nextText }
      ];
    } else {
      return [
        { id: 'confirmation-previous-btn',className: 'btn btn-default', value: previousText },
        { id: 'confirmation-next-btn',className: 'btn btn-primary', value: nextText }
      ];
    }
  },

  onClickPrevious: function(e) {
    e.preventDefault();
    e.currentTarget.disabled = true;
    this.stepIndex -= 1;
    this.renderStep();
  },

  onClickNext: function(e) {
    e.preventDefault();
    e.currentTarget.disabled = true;

    // Execute the specified callback if it exists, then proceed.
    // The modal will not proceed if the callback returns false.
    if (this.onNext) {
        if (this.onNext.call() === false) {
            return;
        }
    }

    if (this.stepIndex === this.steps.length-1) {
      this.hide();
    } else {
      this.stepIndex += 1;
      this.renderStep();
    }
  },

  // Remove Previous sub views and initialize sub views for the new step
  renderStep: function() {
    var options = this.getOptionsForStep(this.stepIndex);
    BackboneBootstrapModals.BaseModal.prototype.removeSubViews.call(this);
    BackboneBootstrapModals.BaseModal.prototype.initializeSubViews.call(this, options);
    this.render();
  }
});
<|endoftext|>