<commit_before>/* eslint-env mocha */
import Vue from 'vue';
import Vuex from 'vuex';
import assert from 'assert';
import _ from 'lodash';
import * as s from '../../src/state/store';
import * as getters from '../../src/state/getters';
import * as coreActions from '../../src/state/actions';
import * as constants from '../../src/constants';
import sinon from 'sinon';
import urls from 'kolibri.urls';
import { SessionResource } from 'kolibri.resources';
import * as browser from '../../src/utils/browser';
import ConditionalPromise from '../../src/conditionalPromise';

Vue.use(Vuex);

function createStore() {
  return new Vuex.Store({
    state: _.cloneDeep(s.initialState),
    mutations: s.mutations,
    getters,
    actions: coreActions,
  });
}

describe('Vuex store/actions for core module', () => {
  describe('error handling', () => {
    it('handleError action updates core state', () => {
      const store = createStore();
      coreActions.handleError(store, 'catastrophic failure');
      assert.equal(store.state.core.error, 'catastrophic failure');
      assert.equal(store.state.core.loading, false);
      assert.equal(store.state.core.title, 'Error');
    });

    it('handleApiError action updates core state', () => {
      const store = createStore();
      const apiError = { message: 'Too Bad' };
      coreActions.handleApiError(store, apiError);
      assert(store.state.core.error.match(/Too Bad/));
      assert.equal(store.state.core.loading, false);
      assert.equal(store.state.core.title, 'Error');
    });
  });

  describe('kolibriLogin', () => {
    let store;
    let assignStub;

    beforeEach(() => {
      store = createStore();
      assignStub = sinon.stub(browser, 'redirectBrowser');
    });

    afterEach(() => {
      assignStub.restore();
    });

    it('successful login', done => {
      urls['kolibri:managementplugin:management'] = () => '';
      Object.assign(SessionResource, {
        createModel: () => ({
          save: () =>
            Promise.resolve({
              // just sending subset of sessionPayload
              id: '123',
              username: 'e_fermi',
              kind: ['cool-guy-user'],
            }),
        }),
      });

      function runAssertions() {
        const { session } = store.state.core;
        assert.equal(session.id, '123');
        assert.equal(session.username, 'e_fermi');
        assert.deepEqual(session.kind, ['cool-guy-user']);
        sinon.assert.called(assignStub);
      }

      coreActions
        .kolibriLogin(store, {})
        .then(runAssertions)
        .then(done, done);
    });

    it('failed login (401)', done => {
      Object.assign(SessionResource, {
        createModel: () => ({
          save: () => Promise.reject({ status: { code: 401 } }),
        }),
      });

      coreActions
        .kolibriLogin(store, {})
        .then(() => {
          assert.equal(store.state.core.loginError, constants.LoginErrors.INVALID_CREDENTIALS);
        })
        .then(done, done);
    });

    it('successful logout', done => {
      const clearCachesSpy = sinon.spy();
      const getModelStub = sinon.stub().returns({
        delete: () => Promise.resolve('goodbye'),
      });
      Object.assign(SessionResource, {
        getModel: getModelStub,
      });

      coreActions
        .kolibriLogout(store)
        .then(() => {
          sinon.assert.calledWith(getModelStub, 'current');
          sinon.assert.called(assignStub);
        })
        .then(done, done);
    });
  });
});

describe('Vuex core logging actions', () => {
  describe('attempt log saving', () => {
    it('saveAndStoreAttemptLog does not overwrite state if item id has changed', done => {
      const store = createStore();
      kolibri.resources = {};
      kolibri.resources.AttemptLog = {
        createModel: obj => ({ attributes: obj }),
      };
      coreActions.createAttemptLog(store, 'first');
      let externalResolve;
      const firstState = Object.assign({}, store.state.core.logging.attempt);
      coreActions.__set__(
        'saveAttemptLog',
        () =>
          new ConditionalPromise(resolve => {
            externalResolve = resolve;
          })
      );
      const promise = coreActions.saveAndStoreAttemptLog(store);
      coreActions.createAttemptLog(store, 'second');
      store.state.core.logging.attempt.id = 'assertion';
      externalResolve(firstState);
      promise.then(() => {
        assert.equal(store.state.core.logging.attempt.id, 'assertion');
        assert.equal(store.state.core.logging.attempt.item, 'second');
        done();
      });
    });
  });
});
<commit_msg>Fix test to no longer rely on module rewire.<commit_after>/* eslint-env mocha */
import Vue from 'vue';
import Vuex from 'vuex';
import assert from 'assert';
import _ from 'lodash';
import * as s from '../../src/state/store';
import * as getters from '../../src/state/getters';
import * as coreActions from '../../src/state/actions';
import * as constants from '../../src/constants';
import sinon from 'sinon';
import urls from 'kolibri.urls';
import { SessionResource, AttemptLogResource } from 'kolibri.resources';
import * as browser from '../../src/utils/browser';
import ConditionalPromise from '../../src/conditionalPromise';

Vue.use(Vuex);

function createStore() {
  return new Vuex.Store({
    state: _.cloneDeep(s.initialState),
    mutations: s.mutations,
    getters,
    actions: coreActions,
  });
}

describe('Vuex store/actions for core module', () => {
  describe('error handling', () => {
    it('handleError action updates core state', () => {
      const store = createStore();
      coreActions.handleError(store, 'catastrophic failure');
      assert.equal(store.state.core.error, 'catastrophic failure');
      assert.equal(store.state.core.loading, false);
      assert.equal(store.state.core.title, 'Error');
    });

    it('handleApiError action updates core state', () => {
      const store = createStore();
      const apiError = { message: 'Too Bad' };
      coreActions.handleApiError(store, apiError);
      assert(store.state.core.error.match(/Too Bad/));
      assert.equal(store.state.core.loading, false);
      assert.equal(store.state.core.title, 'Error');
    });
  });

  describe('kolibriLogin', () => {
    let store;
    let assignStub;

    beforeEach(() => {
      store = createStore();
      assignStub = sinon.stub(browser, 'redirectBrowser');
    });

    afterEach(() => {
      assignStub.restore();
    });

    it('successful login', done => {
      urls['kolibri:managementplugin:management'] = () => '';
      Object.assign(SessionResource, {
        createModel: () => ({
          save: () =>
            Promise.resolve({
              // just sending subset of sessionPayload
              id: '123',
              username: 'e_fermi',
              kind: ['cool-guy-user'],
            }),
        }),
      });

      function runAssertions() {
        const { session } = store.state.core;
        assert.equal(session.id, '123');
        assert.equal(session.username, 'e_fermi');
        assert.deepEqual(session.kind, ['cool-guy-user']);
        sinon.assert.called(assignStub);
      }

      coreActions.kolibriLogin(store, {}).then(runAssertions).then(done, done);
    });

    it('failed login (401)', done => {
      Object.assign(SessionResource, {
        createModel: () => ({
          save: () => Promise.reject({ status: { code: 401 } }),
        }),
      });

      coreActions
        .kolibriLogin(store, {})
        .then(() => {
          assert.equal(store.state.core.loginError, constants.LoginErrors.INVALID_CREDENTIALS);
        })
        .then(done, done);
    });

    it('successful logout', done => {
      const clearCachesSpy = sinon.spy();
      const getModelStub = sinon.stub().returns({
        delete: () => Promise.resolve('goodbye'),
      });
      Object.assign(SessionResource, {
        getModel: getModelStub,
      });

      coreActions
        .kolibriLogout(store)
        .then(() => {
          sinon.assert.calledWith(getModelStub, 'current');
          sinon.assert.called(assignStub);
        })
        .then(done, done);
    });
  });
});

describe('Vuex core logging actions', () => {
  describe('attempt log saving', () => {
    it('saveAndStoreAttemptLog does not overwrite state if item id has changed', done => {
      const store = createStore();
      coreActions.createAttemptLog(store, 'first');
      let externalResolve;
      const firstState = Object.assign({}, store.state.core.logging.attempt);
      const findModelStub = sinon.stub(AttemptLogResource, 'findModel');
      findModelStub.returns({
        save: () =>
          new ConditionalPromise(resolve => {
            externalResolve = resolve;
          }),
      });
      const promise = coreActions.saveAndStoreAttemptLog(store);
      coreActions.createAttemptLog(store, 'second');
      store.state.core.logging.attempt.id = 'assertion';
      externalResolve(firstState);
      promise.then(() => {
        assert.equal(store.state.core.logging.attempt.id, 'assertion');
        assert.equal(store.state.core.logging.attempt.item, 'second');
        done();
      });
    });
  });
});
<|endoftext|>


<commit_before>/* ***********************************************************************************************

    Unify Project

    Homepage: unify-project.org
    License: MIT + Apache (V2)
    Copyright: 2009-2010 Deutsche Telekom AG, Germany, http://telekom.com

*********************************************************************************************** */

/**
 * The TabBar is an singleton element of the ui. If it is enabled by adding
 * buttons to it, the tab bar is displayed on all views. It is possible to
 * set a view to "full screen" (@see unify.view.StaticView#isFullScreen)
 * to prevent display of tab bar on this view. The default and recommended behaviour
 * is to display it on all views.
 */
qx.Class.define("unify.view.TabViewManager",
{
  extend : qx.core.Object,

  /*
  *****************************************************************************
     CONSTRUCTOR
  *****************************************************************************
  */

  construct : function(viewManager)
  {
    this.base(arguments);

    if (qx.core.Variant.isSet("qx.debug", "on"))
    {
      if (viewManager == null) {
        throw new Error("TabViewManager needs a ViewManager at construction time!");
      }
    }

    this.__viewManager = viewManager;
    viewManager.addListener("changePath", this.__onViewManagerChangePath, this);
  },

  /*
  *****************************************************************************
     PROPERTIES
  *****************************************************************************
  */

  properties :
  {
    /** ID of selected view */
    selected :
    {
      check : "String",
      event : "changeSelected",
      nullable : true,
      apply : "_applySelected"
    }
  },

  /*
  *****************************************************************************
     MEMBERS
  *****************************************************************************
  */

  members :
  {
    /** {unify.view.ViewManager} Instance of attached view manager */
    __viewManager : null,
    __element : null,
    __bar : null,
    __pane : null,

    /**
     * Returns the tab view element
     *
     * @return {Element} Root DOM element of tab view
     */
    getElement : function()
    {
      var elem = this.__element;
      if (!elem)
      {
        var elem = this.__element = document.createElement("div");
        elem.className = "tab-view";

        var pane = this.__viewManager.getElement();
        elem.appendChild(pane);

        var bar = this.__bar = document.createElement("div");
        bar.className = "tab-bar";
        elem.appendChild(bar);
        qx.event.Registration.addListener(bar, "tap", this.__onTap, this);
      }

      return elem;
    },

    /**
     * Adds a view to the tab bar. The buttons are displayed in the order of
     * execution of this function.
     *
     * @param viewClass {Class} Class of the view to register {@see unify.view.StaticView}
     */
    add : function(viewClass)
    {
      var root = this.getElement();
      var viewInstance = viewClass.getInstance();

      var elem = document.createElement("div");
      elem.className = "tab-bar-element";
      elem.setAttribute("view", viewInstance.getId());
      elem.innerHTML = "

" + viewInstance.getTitle("tab-bar");

      this.__bar.appendChild(elem);
    },

    /**
     * Reacts on path changes of the view manager and updates "selected" property accordingly.
     *
     * @param e {qx.event.type.Data} Data event
     */
    __onViewManagerChangePath : function(e)
    {
      var path = e.getData();
      if (path)
      {
        var first = path[0];
        if (first) {
          return this.setSelected(first.view);
        }
      }

      this.resetSelected();
    },

    _applySelected : function(value, old)
    {
      this.debug("View: " + value);

      var Class = qx.bom.element2.Class;
      var bar = this.__bar;
      var children = bar.childNodes;
      for (var i=0, l=children.length; i<l; i++)
      {
        var elem = children[i];
        var view = elem.getAttribute("view");

        if (view == value) {
          Class.add(elem, "selected");
        } else if (view == old) {
          Class.remove(elem, "selected");
        }
      }
    },

    /**
     * Reacts on tabbing on the tabbar buttons.
     *
     * @param e {unify.event.type.Touch} Touch event
     */
    __onTap : function(e)
    {
      var elem = qx.dom.Hierarchy.closest(e.getTarget(), "div[goto]");
      if (elem)
      {
        var path = unify.view.Path.fromString(elem.getAttribute("view"));
        this.__viewManager.navigate(path);
      }
    }
  }
});
<commit_msg>Minor fixes<commit_after>/* ***********************************************************************************************

    Unify Project

    Homepage: unify-project.org
    License: MIT + Apache (V2)
    Copyright: 2009-2010 Deutsche Telekom AG, Germany, http://telekom.com

*********************************************************************************************** */

/**
 * The TabBar is an singleton element of the ui. If it is enabled by adding
 * buttons to it, the tab bar is displayed on all views. It is possible to
 * set a view to "full screen" (@see unify.view.StaticView#isFullScreen)
 * to prevent display of tab bar on this view. The default and recommended behaviour
 * is to display it on all views.
 */
qx.Class.define("unify.view.TabViewManager",
{
  extend : qx.core.Object,

  /*
  *****************************************************************************
     CONSTRUCTOR
  *****************************************************************************
  */

  construct : function(viewManager)
  {
    this.base(arguments);

    if (qx.core.Variant.isSet("qx.debug", "on"))
    {
      if (viewManager == null) {
        throw new Error("TabViewManager needs a ViewManager at construction time!");
      }
    }

    this.__viewManager = viewManager;
    viewManager.addListener("changePath", this.__onViewManagerChangePath, this);
  },

  /*
  *****************************************************************************
     PROPERTIES
  *****************************************************************************
  */

  properties :
  {
    /** ID of selected view */
    selected :
    {
      check : "String",
      event : "changeSelected",
      nullable : true,
      apply : "_applySelected"
    }
  },

  /*
  *****************************************************************************
     MEMBERS
  *****************************************************************************
  */

  members :
  {
    /** {unify.view.ViewManager} Instance of attached view manager */
    __viewManager : null,
    __element : null,
    __bar : null,
    __pane : null,

    /**
     * Returns the tab view element
     *
     * @return {Element} Root DOM element of tab view
     */
    getElement : function()
    {
      var elem = this.__element;
      if (!elem)
      {
        var elem = this.__element = document.createElement("div");
        elem.className = "tab-view";

        var pane = this.__viewManager.getElement();
        elem.appendChild(pane);

        var bar = this.__bar = document.createElement("div");
        bar.className = "tab-bar";
        elem.appendChild(bar);
        qx.event.Registration.addListener(bar, "tap", this.__onTap, this);
      }

      return elem;
    },

    /**
     * Adds a view to the tab bar. The buttons are displayed in the order of
     * execution of this function.
     *
     * @param viewClass {Class} Class of the view to register {@see unify.view.StaticView}
     */
    add : function(viewClass)
    {
      var root = this.getElement();
      var viewInstance = viewClass.getInstance();

      var elem = document.createElement("div");
      elem.className = "tab-bar-element";
      elem.setAttribute("view", viewInstance.getId());
      elem.innerHTML = "

" + viewInstance.getTitle("tab-bar");

      this.__bar.appendChild(elem);
    },

    /**
     * Reacts on path changes of the view manager and updates "selected" property accordingly.
     *
     * @param e {qx.event.type.Data} Data event
     */
    __onViewManagerChangePath : function(e)
    {
      var path = e.getData();
      if (path)
      {
        var first = path[0];
        if (first) {
          return this.setSelected(first.view);
        }
      }

      this.resetSelected();
    },

    // property apply
    _applySelected : function(value, old)
    {
      this.debug("View: " + value);

      var Class = qx.bom.element2.Class;
      var bar = this.__bar;
      var children = bar.childNodes;
      for (var i=0, l=children.length; i<l; i++)
      {
        var elem = children[i];
        var view = elem.getAttribute("view");

        if (view == value) {
          Class.add(elem, "selected");
        } else if (view == old) {
          Class.remove(elem, "selected");
        }
      }
    },

    /**
     * Reacts on tabbing on the tabbar buttons.
     *
     * @param e {unify.event.type.Touch} Touch event
     */
    __onTap : function(e)
    {
      var elem = qx.dom.Hierarchy.closest(e.getTarget(), "div[view]");
      if (elem)
      {
        var path = unify.view.Path.fromString(elem.getAttribute("view"));
        this.__viewManager.navigate(path);
      }
    }
  }
});
<|endoftext|>


<commit_before>(function() {
  $('#feedback_button').on('click', openFeedbackFrame);
  $('#feedback_frame>.dimmer').on('click', closeFeedbackFrame);
}());

function closeFeedbackFrame() {
  $('#feedback_frame').fadeOut();
  $('#feedback_frame iframe')[0].contentWindow.onCloseFrame();
}

function openFeedbackFrame() {
  $('#feedback_frame').fadeIn();
}
<commit_msg>Removed old button.js file<commit_after><|endoftext|>


<commit_before>/*
 * Copyright (c) 2013-2014 Chukong Technologies Inc.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */

sp.SkeletonAnimation.prototype._ctor = function(skeletonDataFile, atlasFile, scale) {
	atlasFile !== undefined && this.initWithFiles(skeletonDataFile, atlasFile, scale || 1);
};

sp.SkeletonAnimation.extend = cc.Class.extend;
<commit_msg>Improve jsb_spine.js<commit_after>/*
 * Copyright (c) 2013-2014 Chukong Technologies Inc.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */

sp.SkeletonAnimation.prototype._ctor = function(skeletonDataFile, atlasFile, scale) {
	if(atlasFile) {
		this.initWithFile(skeletonDataFile, atlasFile, scale);
		this.initialize();
	}
};

sp.SkeletonAnimation.extend = cc.Class.extend;
<|endoftext|>


<commit_before>frappe.provide('frappe.views');

frappe.views.KanbanView = class KanbanView extends frappe.views.ListView {
	static load_last_view() {
		const route = frappe.get_route();
		if (route.length === 3) {
			const doctype = route[1];
			const user_settings = frappe.get_user_settings(doctype)['Kanban'] || {};
			if (!user_settings.last_kanban_board) {
				frappe.msgprint({
					title: __('Error'),
					indicator: 'red',
					message: __('Missing parameter Kanban Board Name')
				});
				frappe.set_route('List', doctype, 'List');
				return true;
			}
			route.push(user_settings.last_kanban_board);
			frappe.set_route(route);
			return true;
		}
		return false;
	}

	get view_name() {
		return 'Kanban';
	}

	setup_defaults() {
		return super.setup_defaults()
			.then(() => {
				this.board_name = frappe.get_route()[3];
				this.page_title = this.board_name;
				this.card_meta = this.get_card_meta();

				this.menu_items.push({
					label: __('Save filters'),
					action: () => {
						this.save_kanban_board_filters();
					}
				});

				return this.get_board();
			});
	}

	get_board() {
		return frappe.db.get_doc('Kanban Board', this.board_name)
			.then(board => {
				this.board = board;
				this.board.filters_array = JSON.parse(this.board.filters || '[]');
				this.filters = this.board.filters_array;
			});
	}

	before_refresh() {

	}

	setup_view() {
		this.setup_realtime_updates();
	}

	set_fields() {
		super.set_fields();
		this._add_field(this.card_meta.title_field);
	}

	before_render() {
		frappe.model.user_settings.save(this.doctype, 'last_view', this.view_name);
		this.save_view_user_settings({
			last_kanban_board: this.board_name
		});
	}

	on_filter_change() {
		if (JSON.stringify(this.board.filters_array) !== JSON.stringify(this.filter_area.get())) {
			this.page.set_indicator(__('Not Saved'), 'orange');
		} else {
			this.page.clear_indicator();
		}
	}

	save_kanban_board_filters() {
		const filters = this.filter_area.get();

		frappe.call({
			method: 'frappe.desk.doctype.kanban_board.kanban_board.save_filters',
			args: {
				board_name: this.board_name,
				filters: filters
			}
		}).then(r => {
			if (r.exc) {
				frappe.show_alert({
					indicator: 'red',
					message: __('There was an error saving filters')
				});
				return;
			}
			frappe.show_alert({
				indicator: 'green',
				message: __('Filters saved')
			});

			this.board.filters_array = filters;
			this.on_filter_change();
		});
	}

	render() {
		const board_name = this.board_name;
		if (this.kanban && board_name === this.kanban.board_name) {
			this.kanban.update(this.data);
			return;
		}

		this.kanban = new frappe.views.KanbanBoard({
			doctype: this.doctype,
			board: this.board,
			board_name: board_name,
			cards: this.data,
			card_meta: this.card_meta,
			wrapper: this.$result,
			cur_list: this,
			user_settings: this.view_user_settings
		});
	}

	get_card_meta() {
		var meta = frappe.get_meta(this.doctype);
		var doc = frappe.model.get_new_doc(this.doctype);
		var title_field = null;
		var quick_entry = false;

		if (this.meta.title_field) {
			title_field = frappe.meta.get_field(this.doctype, this.meta.title_field);
		}

		this.meta.fields.forEach((df) => {
			const is_valid_field =
				in_list(['Data', 'Text', 'Small Text', 'Text Editor'], df.fieldtype)
				&& !df.hidden;

			if (is_valid_field && !title_field) {
				// can be mapped to textarea
				title_field = df;
			}
		});

		// quick entry
		var mandatory = meta.fields.filter((df) => df.reqd && !doc[df.fieldname]);

		if (mandatory.some(df => frappe.model.table_fields.includes(df.fieldtype)) || mandatory.length > 1) {
			quick_entry = true;
		}

		if (!title_field) {
			title_field = frappe.meta.get_field(this.doctype, 'name');
		}

		return {
			quick_entry: quick_entry,
			title_field: title_field
		};
	}

	get required_libs() {
		return [
			'assets/frappe/js/lib/fluxify.min.js',
			'assets/frappe/js/frappe/views/kanban/kanban_board.js'
		];
	}
};

frappe.views.KanbanView.setup_dropdown_in_sidebar = function(doctype, $dropdown) {
	// get kanban boards and append to dropdown
	get_kanban_boards()
		.then((kanban_boards) => {
			if (!kanban_boards) return;

			$('

').appendTo($dropdown);

			kanban_boards.forEach(board => {
				const route = ['List', board.reference_doctype, 'Kanban', board.name].join('/');

				$(`

						${__(board.name)}
					${board.private ? '' : ''}

				`).appendTo($dropdown);
			});
		});

	$dropdown.on('click', '.new-kanban-board', () => {
		const dialog = new_kanban_dialog();
		dialog.show();
	});

	function make_kanban_board(board_name, field_name, project) {
		return frappe.call({
			method: 'frappe.desk.doctype.kanban_board.kanban_board.quick_kanban_board',
			args: {
				doctype,
				board_name,
				field_name,
				project
			},
			callback: function(r) {
				var kb = r.message;
				if (kb.filters) {
					frappe.provide('frappe.kanban_filters');
					frappe.kanban_filters[kb.kanban_board_name] = kb.filters;
				}
				frappe.set_route('List', doctype, 'Kanban', kb.kanban_board_name);
			}
		});
	}

	let dialog = null;

	function new_kanban_dialog() {
		if (dialog) return dialog;

		const fields = get_fields_for_dialog();

		let primary_action_label = fields.length > 1 ? __('Save') : '';
		let primary_action = fields.length > 1 ?
			({ board_name, field_name, project }) => {
				make_kanban_board(board_name, field_name, project)
					.then(() => dialog.hide(), (err) => frappe.msgprint(err));
			} : null;

		dialog = new frappe.ui.Dialog({
			title: __('New Kanban Board'),
			fields,
			primary_action_label,
			primary_action
		});
		return dialog;
	}

	function get_fields_for_dialog() {

		let fields = [{
			fieldtype: 'Data',
			fieldname: 'board_name',
			label: __('Kanban Board Name'),
			reqd: 1,
			description: ['Note', 'ToDo'].includes(doctype) ?
				__('This Kanban Board will be private') : ''
		}];

		if (doctype === 'Task') {
			fields.push({
				fieldtype: 'Link',
				fieldname: 'project',
				label: __('Project'),
				options: 'Project'
			});
		}

		const select_fields =
			frappe.get_meta(doctype).fields
				.filter(df => {
					return df.fieldtype === 'Select' &&
						df.fieldname !== 'kanban_column';
				});

		if (select_fields.length > 0) {
			fields.push({
				fieldtype: 'Select',
				fieldname: 'field_name',
				label: __('Columns based on'),
				options: select_fields.map(df => ({label: df.label, value: df.fieldname})),
				default: select_fields[0],
				reqd: 1,
			});
		} else {
			fields = [{
				fieldtype: 'HTML',
				options: `

						${__('No fields found that can be used as a Kanban Column. Use the Customize Form to add a Custom Field of type "Select".')}

							${__('Customize Form')}

				`
			}];
		}

		return fields;
	}

	function get_kanban_boards() {
		return frappe.call('frappe.desk.doctype.kanban_board.kanban_board.get_kanban_boards', { doctype })
			.then(r => r.message);
	}
};
<commit_msg>fix: missing column datafor Kanban board<commit_after>frappe.provide('frappe.views');

frappe.views.KanbanView = class KanbanView extends frappe.views.ListView {
	static load_last_view() {
		const route = frappe.get_route();
		if (route.length === 3) {
			const doctype = route[1];
			const user_settings = frappe.get_user_settings(doctype)['Kanban'] || {};
			if (!user_settings.last_kanban_board) {
				frappe.msgprint({
					title: __('Error'),
					indicator: 'red',
					message: __('Missing parameter Kanban Board Name')
				});
				frappe.set_route('List', doctype, 'List');
				return true;
			}
			route.push(user_settings.last_kanban_board);
			frappe.set_route(route);
			return true;
		}
		return false;
	}

	get view_name() {
		return 'Kanban';
	}

	setup_defaults() {
		return super.setup_defaults()
			.then(() => {
				this.board_name = frappe.get_route()[3];
				this.page_title = this.board_name;
				this.card_meta = this.get_card_meta();

				this.menu_items.push({
					label: __('Save filters'),
					action: () => {
						this.save_kanban_board_filters();
					}
				});

				return this.get_board();
			});
	}

	get_board() {
		return frappe.db.get_doc('Kanban Board', this.board_name)
			.then(board => {
				this.board = board;
				this.board.filters_array = JSON.parse(this.board.filters || '[]');
				this.filters = this.board.filters_array;
			});
	}

	before_refresh() {

	}

	setup_view() {
		this.setup_realtime_updates();
	}

	set_fields() {
		super.set_fields();
		this._add_field(this.card_meta.title_field);
	}

	before_render() {
		frappe.model.user_settings.save(this.doctype, 'last_view', this.view_name);
		this.save_view_user_settings({
			last_kanban_board: this.board_name
		});
	}

	on_filter_change() {
		if (JSON.stringify(this.board.filters_array) !== JSON.stringify(this.filter_area.get())) {
			this.page.set_indicator(__('Not Saved'), 'orange');
		} else {
			this.page.clear_indicator();
		}
	}

	save_kanban_board_filters() {
		const filters = this.filter_area.get();

		frappe.call({
			method: 'frappe.desk.doctype.kanban_board.kanban_board.save_filters',
			args: {
				board_name: this.board_name,
				filters: filters
			}
		}).then(r => {
			if (r.exc) {
				frappe.show_alert({
					indicator: 'red',
					message: __('There was an error saving filters')
				});
				return;
			}
			frappe.show_alert({
				indicator: 'green',
				message: __('Filters saved')
			});

			this.board.filters_array = filters;
			this.on_filter_change();
		});
	}

	get_fields() {
		this.fields.push([this.board.field_name, this.board.reference_doctype]);
		return super.get_fields();
	}

	render() {
		const board_name = this.board_name;
		if (this.kanban && board_name === this.kanban.board_name) {
			this.kanban.update(this.data);
			return;
		}

		this.kanban = new frappe.views.KanbanBoard({
			doctype: this.doctype,
			board: this.board,
			board_name: board_name,
			cards: this.data,
			card_meta: this.card_meta,
			wrapper: this.$result,
			cur_list: this,
			user_settings: this.view_user_settings
		});
	}

	get_card_meta() {
		var meta = frappe.get_meta(this.doctype);
		var doc = frappe.model.get_new_doc(this.doctype);
		var title_field = null;
		var quick_entry = false;

		if (this.meta.title_field) {
			title_field = frappe.meta.get_field(this.doctype, this.meta.title_field);
		}

		this.meta.fields.forEach((df) => {
			const is_valid_field =
				in_list(['Data', 'Text', 'Small Text', 'Text Editor'], df.fieldtype)
				&& !df.hidden;

			if (is_valid_field && !title_field) {
				// can be mapped to textarea
				title_field = df;
			}
		});

		// quick entry
		var mandatory = meta.fields.filter((df) => df.reqd && !doc[df.fieldname]);

		if (mandatory.some(df => frappe.model.table_fields.includes(df.fieldtype)) || mandatory.length > 1) {
			quick_entry = true;
		}

		if (!title_field) {
			title_field = frappe.meta.get_field(this.doctype, 'name');
		}

		return {
			quick_entry: quick_entry,
			title_field: title_field
		};
	}

	get required_libs() {
		return [
			'assets/frappe/js/lib/fluxify.min.js',
			'assets/frappe/js/frappe/views/kanban/kanban_board.js'
		];
	}
};

frappe.views.KanbanView.setup_dropdown_in_sidebar = function(doctype, $dropdown) {
	// get kanban boards and append to dropdown
	get_kanban_boards()
		.then((kanban_boards) => {
			if (!kanban_boards) return;

			$('

').appendTo($dropdown);

			kanban_boards.forEach(board => {
				const route = ['List', board.reference_doctype, 'Kanban', board.name].join('/');

				$(`

						${__(board.name)}
					${board.private ? '' : ''}

				`).appendTo($dropdown);
			});
		});

	$dropdown.on('click', '.new-kanban-board', () => {
		const dialog = new_kanban_dialog();
		dialog.show();
	});

	function make_kanban_board(board_name, field_name, project) {
		return frappe.call({
			method: 'frappe.desk.doctype.kanban_board.kanban_board.quick_kanban_board',
			args: {
				doctype,
				board_name,
				field_name,
				project
			},
			callback: function(r) {
				var kb = r.message;
				if (kb.filters) {
					frappe.provide('frappe.kanban_filters');
					frappe.kanban_filters[kb.kanban_board_name] = kb.filters;
				}
				frappe.set_route('List', doctype, 'Kanban', kb.kanban_board_name);
			}
		});
	}

	let dialog = null;

	function new_kanban_dialog() {
		if (dialog) return dialog;

		const fields = get_fields_for_dialog();

		let primary_action_label = fields.length > 1 ? __('Save') : '';
		let primary_action = fields.length > 1 ?
			({ board_name, field_name, project }) => {
				make_kanban_board(board_name, field_name, project)
					.then(() => dialog.hide(), (err) => frappe.msgprint(err));
			} : null;

		dialog = new frappe.ui.Dialog({
			title: __('New Kanban Board'),
			fields,
			primary_action_label,
			primary_action
		});
		return dialog;
	}

	function get_fields_for_dialog() {

		let fields = [{
			fieldtype: 'Data',
			fieldname: 'board_name',
			label: __('Kanban Board Name'),
			reqd: 1,
			description: ['Note', 'ToDo'].includes(doctype) ?
				__('This Kanban Board will be private') : ''
		}];

		if (doctype === 'Task') {
			fields.push({
				fieldtype: 'Link',
				fieldname: 'project',
				label: __('Project'),
				options: 'Project'
			});
		}

		const select_fields =
			frappe.get_meta(doctype).fields
				.filter(df => {
					return df.fieldtype === 'Select' &&
						df.fieldname !== 'kanban_column';
				});

		if (select_fields.length > 0) {
			fields.push({
				fieldtype: 'Select',
				fieldname: 'field_name',
				label: __('Columns based on'),
				options: select_fields.map(df => ({label: df.label, value: df.fieldname})),
				default: select_fields[0],
				reqd: 1,
			});
		} else {
			fields = [{
				fieldtype: 'HTML',
				options: `

						${__('No fields found that can be used as a Kanban Column. Use the Customize Form to add a Custom Field of type "Select".')}

							${__('Customize Form')}

				`
			}];
		}

		return fields;
	}

	function get_kanban_boards() {
		return frappe.call('frappe.desk.doctype.kanban_board.kanban_board.get_kanban_boards', { doctype })
			.then(r => r.message);
	}
};
<|endoftext|>


<commit_before>'use strict';
// =========================================================================
//
// Policies for buckets
//
// =========================================================================
var acl  = require ('acl');
acl      = new acl (new acl.memoryBackend ());

exports.invokeRolesPolicies = function () {
	acl.allow ('admin', [
		'/api/bucket',
		'/api/bucket/:bucket',
		'/api/new/bucket'
		], '*'
	);
	acl.allow ('guest', [
		'/api/bucket',
		'/api/bucket/:bucket',
		'/api/new/bucket'
		], 'get'
	);
};

exports.isAllowed = function (req, res, next) {
	var roles = (req.user) ? req.user.roles : ['admin'];
	acl.areAnyRolesAllowed (roles, req.route.path, req.method.toLowerCase(), function (err, isAllowed) {
		if (err) {
			// An authorization error occurred.
			return res.status(500).send('Unexpected authorization error');
		} else {
			if (isAllowed) {
				// Access granted! Invoke next middleware
				return next();
			} else {
				return res.status(403).json({
					message: 'User is not authorized'
				});
			}
		}
	});
};

<commit_msg>Added 'base' url to policy<commit_after>'use strict';
// =========================================================================
//
// Policies for buckets
//
// =========================================================================
var acl  = require ('acl');
acl      = new acl (new acl.memoryBackend ());

exports.invokeRolesPolicies = function () {
	acl.allow ('admin', [
		'/api/bucket',
		'/api/bucket/:bucket',
		'/api/base/bucket',
		'/api/new/bucket'
		], '*'
	);
	acl.allow ('guest', [
		'/api/bucket',
		'/api/bucket/:bucket',
		'/api/base/bucket',
		'/api/new/bucket'
		], 'get'
	);
};

exports.isAllowed = function (req, res, next) {
	var roles = (req.user) ? req.user.roles : ['admin'];
	acl.areAnyRolesAllowed (roles, req.route.path, req.method.toLowerCase(), function (err, isAllowed) {
		if (err) {
			// An authorization error occurred.
			return res.status(500).send('Unexpected authorization error');
		} else {
			if (isAllowed) {
				// Access granted! Invoke next middleware
				return next();
			} else {
				return res.status(403).json({
					message: 'User is not authorized'
				});
			}
		}
	});
};

<|endoftext|>


<commit_before>import R from 'ramda';

const makeStyleReducer = (key, isStore = false) => function styleReducer(styles) {
  let result = {};
  const storeKey = (isStore) ? 'props' : 'state';
  if (this[storeKey] && this[storeKey][key]) {
    const val = this[storeKey][key];
    result = this.styles.keys[key].reduce(
      (prev, cur) => {
        let p = prev;
        const hasStyles = R.has(key, this.styles.styles[cur]);
        if (hasStyles) {
          const hasPropStyle = (
            typeof this.styles.styles[cur][key] === 'object' &&
            R.has(val, this.styles.styles[cur][key])
          );
          let updateWith;
          if (hasPropStyle) {
            updateWith = this.styles.styles[cur][key][val];
          } else {
            updateWith = this.styles.styles[cur][key];
          }

          p = R.assoc(cur, updateWith, prev);
        }
        return p;
      }, {}
    );
  }
  return R.mergeWith(R.merge, styles, result);
};

const Stylizer = {
  init: (component) => {
    const c = component;
    if (!c.styles) {
      Stylizer.assignStyles(c, {});
    }
    if (!c.state) {
      c.state = {};
    }
  },
  assignStyles: (component, styles) => {
    const c = component;

    if (!c.styles) {
      c.styles = {
        styles,
        keys: {},
        getters: [],
      };
    }
    // assign default getters
    c.styles.getters.push(Stylizer.getters.normal.bind(c));
    // assign default getStyle method
    c.getStyle = Stylizer.funcs.getStyle.bind(c);
  },
  hoverable: (component, keys) => {
    const c = component;
    Stylizer.init(c);
    c.state.hover = false;

    c.onMouseOver = Stylizer.funcs.onMouseOver;
    c.onMouseOut = Stylizer.funcs.onMouseOut;
    c.handleOver = c.onMouseOver.bind(c);
    c.handleOut = c.onMouseOut.bind(c);

    c.styles.keys.hover = keys;
    c.styles.getters.push(Stylizer.getters.hover.bind(c));
  },
  selectable: (component, keys) => {
    const c = component;
    Stylizer.init(c);
    c.state.selected = false;

    c.styles.keys.selected = keys;
    c.styles.getters.push(Stylizer.getters.selected.bind(c));
  },
  dependOnProp: (component, propName, keys) => {
    const c = component;
    Stylizer.init(c);

    c.styles.keys[propName] = keys;
    c.styles.getters.push(Stylizer.getters.dependOnProp(c, propName));
  },
  getters: {
    normal(styles) {
      return Object.keys(styles).reduce(
        (prev, cur) => {
          const p = prev;
          p[cur] = ((R.has('normal', styles[cur])) ? styles[cur].normal : styles[cur]);
          return p;
        }, {}
      );
    },
    hover: makeStyleReducer('hover'),
    selected: makeStyleReducer('selected', true),
    dependOnProp: (component, propName) => makeStyleReducer(propName, true).bind(component),
  },

  funcs: {
    getStyle() {
      let styles = Object.assign({}, this.styles.styles);
      this.styles.getters.forEach((func) => {
        styles = func(styles);
      });
      return styles;
    },
    onMouseOver() {
      const state = Object.assign({}, this.state);
      state.hover = true;
      this.setState(state);
    },
    onMouseOut() {
      const state = Object.assign({}, this.state);
      state.hover = false;
      this.setState(state);
    },
  },
};

export default Stylizer;
<commit_msg>tweak(xod-client): remove Stylizer util<commit_after><|endoftext|>


<commit_before>// @flow
import * as FsGen from '../fs-gen'
import * as Saga from '../../util/saga'
import * as Config from '../../constants/config'
import * as RPCTypes from '../../constants/types/rpc-gen'
import * as Types from '../../constants/types/fs'
import * as Constants from '../../constants/fs'
import * as SafeElectron from '../../util/safe-electron.desktop'
import fs from 'fs'
import type {TypedState} from '../../constants/reducer'
import {fileUIName, isLinux, isWindows} from '../../constants/platform'
import {fsTab} from '../../constants/tabs'
import logger from '../../logger'
import {spawn, execFileSync} from 'child_process'
import path from 'path'
import {putActionIfOnPath, navigateTo, navigateAppend, navigateUp} from '../route-tree'
import {saveAttachmentDialog, showShareActionSheet} from '../platform-specific'

type pathType = 'file' | 'directory'

// pathToURL takes path and converts to (file://) url.
// See https://github.com/sindresorhus/file-url
function pathToURL(path: string): string {
  let goodPath = path.replace(/\\/g, '/')

  // Windows drive letter must be prefixed with a slash
  if (goodPath[0] !== '/') {
    goodPath = '/' + goodPath
  }

  return encodeURI('file://' + goodPath).replace(/#/g, '%23')
}

function openInDefaultDirectory(openPath: string) {
  return new Promise((resolve, reject) => {
    // Paths in directories might be symlinks, so resolve using
    // realpath.
    // For example /keybase/private/gabrielh,chris gets redirected to
    // /keybase/private/chris,gabrielh.
    fs.realpath(openPath, (err, resolvedPath) => {
      if (err) {
        reject(new Error(`No realpath for ${openPath}: ${err}`))
        return
      }
      // Convert to URL for openExternal call.
      // We use openExternal instead of openItem because it
      // correctly focuses' the Finder, and also uses a newer
      // native API on macOS.
      const url = pathToURL(resolvedPath)
      logger.info('Open URL (directory):', url)

      SafeElectron.getShell().openExternal(url, {activate: true}, err => {
        if (err) {
          reject(err)
          return
        }
        logger.info('Opened directory:', openPath)
        resolve()
      })
    })
  })
}

function getPathType(openPath: string): Promise<pathType> {
  return new Promise((resolve, reject) => {
    fs.stat(openPath, (err, stats) => {
      if (err) {
        reject(new Error(`Unable to open/stat file: ${openPath}`))
        return
      }
      if (stats.isFile()) {
        resolve('file')
      } else if (stats.isDirectory()) {
        resolve('directory')
      } else {
        reject(new Error(`Unable to open: Not a file or directory`))
      }
    })
  })
}

function _open(openPath: string) {
  return new Promise((resolve, reject) => {
    getPathType(openPath).then(typ => {
      if (typ === 'directory') {
        if (isWindows) {
          if (!SafeElectron.getShell().openItem(openPath)) {
            reject(new Error(`Unable to open item: ${openPath}`))
            return
          }
        } else {
          openInDefaultDirectory(openPath).then(resolve, reject)
          return
        }
      } else if (typ === 'file') {
        if (!SafeElectron.getShell().showItemInFolder(openPath)) {
          reject(new Error(`Unable to open item in folder: ${openPath}`))
          return
        }
      } else {
        reject(new Error(`Invalid path type`))
        return
      }
      resolve()
    })
  })
}

function openInFileUISaga({payload: {path}}: FsGen.OpenInFileUIPayload, state: TypedState) {
  const openPath = path || Config.defaultKBFSPath
  const enabled = state.fs.fuseStatus && state.fs.fuseStatus.kextStarted
  if (isLinux || enabled) {
    return Saga.call(_open, openPath)
  } else {
    return Saga.put(navigateTo([fsTab, {props: {path: Types.stringToPath(openPath)}, selected: 'folder'}]))
  }
}

function waitForMount(attempt: number) {
  return new Promise((resolve, reject) => {
    // Read the KBFS path waiting for files to exist, which means it's mounted
    // TODO: should handle current mount directory
    fs.readdir(`${Config.defaultKBFSPath}${Config.defaultPrivatePrefix}`, (err, files) => {
      if (!err && files.length > 0) {
        resolve(true)
      } else if (attempt > 15) {
        reject(new Error(`${Config.defaultKBFSPath} is unavailable. Please try again.`))
      } else {
        setTimeout(() => {
          waitForMount(attempt + 1).then(resolve, reject)
        }, 1000)
      }
    })
  })
}

const installKBFSSuccess = (result: RPCTypes.InstallResult) =>
  Saga.sequentially([
    Saga.call(waitForMount, 0),
    Saga.put(FsGen.createSetFlags({kbfsInstalling: false, showBanner: true})),
  ])

function fuseStatusResultSaga({payload: {prevStatus, status}}: FsGen.FuseStatusResultPayload) {
  // If our kextStarted status changed, finish KBFS install
  if (status.kextStarted && prevStatus && !prevStatus.kextStarted) {
    return Saga.put(FsGen.createInstallKBFS())
  }
}

function* fuseStatusSaga(): Saga.SagaGenerator<any, any> {
  const state: TypedState = yield Saga.select()
  const prevStatus = state.fs.fuseStatus

  let status = yield Saga.call(RPCTypes.installFuseStatusRpcPromise, {bundleVersion: ''})
  if (isWindows && status.installStatus !== RPCTypes.installInstallStatus.installed) {
    // Check if another Dokan we didn't install mounted the filesystem
    const kbfsMount = yield Saga.call(RPCTypes.kbfsMountGetCurrentMountDirRpcPromise)
    if (kbfsMount && fs.existsSync(kbfsMount)) {
      status.installStatus = RPCTypes.installInstallStatus.installed
      status.installAction = RPCTypes.installInstallAction.none
      status.kextStarted = true
    }
  }
  yield Saga.put(FsGen.createFuseStatusResult({prevStatus, status}))
}

function* installFuseSaga(): Saga.SagaGenerator<any, any> {
  const result: RPCTypes.InstallResult = yield Saga.call(RPCTypes.installInstallFuseRpcPromise)
  const fuseResults =
    result && result.componentResults ? result.componentResults.filter(c => c.name === 'fuse') : []
  const kextPermissionError =
    fuseResults.length > 0 && fuseResults[0].exitCode === Constants.ExitCodeFuseKextPermissionError

  if (kextPermissionError) {
    // Add a small delay here, since on 10.13 the OS will be a little laggy
    // when showing a kext permission error.
    yield Saga.delay(1e3)
  }

  yield Saga.put(FsGen.createInstallFuseResult({kextPermissionError}))
  yield Saga.put(FsGen.createFuseStatus())
  yield Saga.put(FsGen.createSetFlags({fuseInstalling: false}))
  // TODO: do something like uninstallConfirmSaga here
}

const uninstallKBFSConfirm = (action: FsGen.UninstallKBFSConfirmPayload) =>
  new Promise((resolve, reject) =>
    SafeElectron.getDialog().showMessageBox(
      null,
      {
        buttons: ['Remove & Restart', 'Cancel'],
        detail: `Are you sure you want to remove Keybase from ${fileUIName} and restart the app?`,
        message: `Remove Keybase from ${fileUIName}`,
        type: 'question',
      },
      resp => resolve(resp)
    )
  )

const uninstallKBFSConfirmSuccess = resp =>
  resp
    ? undefined
    : Saga.sequentially([
        Saga.call(RPCTypes.installUninstallKBFSRpcPromise),
        Saga.call(() => {
          // Restart since we had to uninstall KBFS and it's needed by the service (for chat)
          SafeElectron.getApp().relaunch()
          SafeElectron.getApp().exit(0)
        }),
      ])

const openSecurityPreferences = () =>
  Saga.call(
    () =>
      new Promise((resolve, reject) => {
        SafeElectron.getShell().openExternal(
          'x-apple.systempreferences:com.apple.preference.security?General',
          {activate: true},
          err => {
            if (err) {
              reject(err)
              return
            }
            logger.info('Opened Security Preferences')
            resolve()
          }
        )
      })
  )

// Invoking the cached installer package has to happen from the topmost process
// or it won't be visible to the user. The service also does this to support command line
// operations.
function installCachedDokan() {
  return new Promise((resolve, reject) => {
    logger.info('Invoking dokan installer')
    const dokanPath = path.resolve(String(process.env.LOCALAPPDATA), 'Keybase', 'DokanSetup_redist.exe')
    try {
      execFileSync(dokanPath, [])
    } catch (err) {
      logger.error('installCachedDokan caught', err)
      reject(err)
      return
    }
    // restart the service, particularly kbfsdokan
    // based on desktop/app/start-win-service.js
    const binPath = path.resolve(String(process.env.LOCALAPPDATA), 'Keybase', 'keybase.exe')
    if (!binPath) {
      return
    }
    const rqPath = binPath.replace('keybase.exe', 'keybaserq.exe')
    const args = [binPath, 'ctl', 'restart']

    spawn(rqPath, args, {
      detached: true,
      stdio: 'ignore',
    })

    resolve()
  })
}

function installDokanSaga() {
  return Saga.call(installCachedDokan)
}

function openFinderPopup(action: FsGen.OpenFinderPopupPayload) {
  const {targetRect, routePath} = action.payload
  return Saga.put(
    putActionIfOnPath(
      routePath,
      navigateAppend([
        {
          props: {
            targetRect,
            position: 'bottom right',
            onHidden: () => Saga.put(navigateUp()),
            onInstall: () => Saga.put(FsGen.createInstallFuse()),
          },
          selected: 'finderAction',
        },
      ])
    )
  )
}

function platformSpecificIntentEffect(
  intent: Types.DownloadIntent,
  localPath: string,
  mimeType: string
): ?Saga.Effect {
  switch (intent) {
    case 'camera-roll':
      return Saga.call(saveAttachmentDialog, localPath)
    case 'share':
      return Saga.call(showShareActionSheet, {url: localPath, mimeType})
    case 'none':
    case 'web-view':
    case 'web-view-text':
      return null
    default:
      /*::
      declare var ifFlowErrorsHereItsCauseYouDidntHandleAllTypesAbove: (a: empty) => any
      ifFlowErrorsHereItsCauseYouDidntHandleAllTypesAbove(intent);
      */
      return null
  }
}

const pickAndUpload = ({payload: {type}}: FsGen.PickAndUploadPayload) =>
  new Promise((resolve, reject) =>
    SafeElectron.getDialog().showOpenDialog(
      SafeElectron.getCurrentWindowFromRemote(),
      {
        title: 'Select a file or folder to upload',
        properties: [
          'multiSelections',
          ...(['file', 'both'].includes(type) ? ['openFile'] : []),
          ...(['directory', 'both'].includes(type) ? ['openDirectory'] : []),
        ],
      },
      filePaths => {
        return resolve(filePaths)
      }
    )
  )

const pickAndUploadSuccess = (localPaths, action: FsGen.PickAndUploadPayload) =>
  localPaths &&
  Saga.sequentially(
    localPaths.map(localPath =>
      Saga.put(FsGen.createUpload({localPath, parentPath: action.payload.parentPath}))
    )
  )

function* platformSpecificSaga(): Saga.SagaGenerator<any, any> {
  yield Saga.safeTakeEveryPure(FsGen.openInFileUI, openInFileUISaga)
  yield Saga.safeTakeEvery(FsGen.fuseStatus, fuseStatusSaga)
  yield Saga.safeTakeEveryPure(FsGen.fuseStatusResult, fuseStatusResultSaga)
  yield Saga.safeTakeEveryPure(FsGen.installKBFS, RPCTypes.installInstallKBFSRpcPromise, installKBFSSuccess)
  yield Saga.safeTakeEveryPure(FsGen.uninstallKBFSConfirm, uninstallKBFSConfirm, uninstallKBFSConfirmSuccess)
  yield Saga.safeTakeEveryPure(FsGen.pickAndUpload, pickAndUpload, pickAndUploadSuccess)
  if (isWindows) {
    yield Saga.safeTakeEveryPure(FsGen.installFuse, installDokanSaga)
  } else {
    yield Saga.safeTakeEvery(FsGen.installFuse, installFuseSaga)
  }
  yield Saga.safeTakeEveryPure(FsGen.openSecurityPreferences, openSecurityPreferences)

  // These are saga tasks that may use actions above.
  yield Saga.safeTakeEveryPure(FsGen.openFinderPopup, openFinderPopup)
}

export {platformSpecificIntentEffect, platformSpecificSaga}
<commit_msg>fs: Fix broken Windows viewing in Explorer (#12787)<commit_after>// @flow
import * as FsGen from '../fs-gen'
import * as ConfigGen from '../config-gen'
import * as Saga from '../../util/saga'
import * as Config from '../../constants/config'
import * as RPCTypes from '../../constants/types/rpc-gen'
import * as Types from '../../constants/types/fs'
import * as Constants from '../../constants/fs'
import * as SafeElectron from '../../util/safe-electron.desktop'
import fs from 'fs'
import type {TypedState} from '../../constants/reducer'
import {fileUIName, isLinux, isWindows} from '../../constants/platform'
import {fsTab} from '../../constants/tabs'
import logger from '../../logger'
import {spawn, execFileSync} from 'child_process'
import path from 'path'
import {putActionIfOnPath, navigateTo, navigateAppend, navigateUp} from '../route-tree'
import {saveAttachmentDialog, showShareActionSheet} from '../platform-specific'

type pathType = 'file' | 'directory'

// pathToURL takes path and converts to (file://) url.
// See https://github.com/sindresorhus/file-url
function pathToURL(path: string): string {
  let goodPath = path.replace(/\\/g, '/')

  // Windows drive letter must be prefixed with a slash
  if (goodPath[0] !== '/') {
    goodPath = '/' + goodPath
  }

  return encodeURI('file://' + goodPath).replace(/#/g, '%23')
}

function openInDefaultDirectory(openPath: string) {
  return new Promise((resolve, reject) => {
    // Paths in directories might be symlinks, so resolve using
    // realpath.
    // For example /keybase/private/gabrielh,chris gets redirected to
    // /keybase/private/chris,gabrielh.
    fs.realpath(openPath, (err, resolvedPath) => {
      if (err) {
        reject(new Error(`No realpath for ${openPath}: ${err}`))
        return
      }
      // Convert to URL for openExternal call.
      // We use openExternal instead of openItem because it
      // correctly focuses' the Finder, and also uses a newer
      // native API on macOS.
      const url = pathToURL(resolvedPath)
      logger.info('Open URL (directory):', url)

      SafeElectron.getShell().openExternal(url, {activate: true}, err => {
        if (err) {
          reject(err)
          return
        }
        logger.info('Opened directory:', openPath)
        resolve()
      })
    })
  })
}

function getPathType(openPath: string): Promise<pathType> {
  return new Promise((resolve, reject) => {
    fs.stat(openPath, (err, stats) => {
      if (err) {
        reject(new Error(`Unable to open/stat file: ${openPath}`))
        return
      }
      if (stats.isFile()) {
        resolve('file')
      } else if (stats.isDirectory()) {
        resolve('directory')
      } else {
        reject(new Error(`Unable to open: Not a file or directory`))
      }
    })
  })
}

function _open(openPath: string) {
  return new Promise((resolve, reject) => {
    getPathType(openPath).then(typ => {
      if (typ === 'directory') {
        if (isWindows) {
          if (!SafeElectron.getShell().openItem(openPath)) {
            reject(new Error(`Unable to open item: ${openPath}`))
            return
          }
        } else {
          openInDefaultDirectory(openPath).then(resolve, reject)
          return
        }
      } else if (typ === 'file') {
        if (!SafeElectron.getShell().showItemInFolder(openPath)) {
          reject(new Error(`Unable to open item in folder: ${openPath}`))
          return
        }
      } else {
        reject(new Error(`Invalid path type`))
        return
      }
      resolve()
    })
  })
}

function* openWithCurrentMountDir(openPath: string): Saga.SagaGenerator<any, any> {
  const goodPath = path.posix.normalize(openPath)
  if (!openPath.startsWith(Config.defaultKBFSPath)) {
    throw new Error(`openWithCurrentMountDir requires ${Config.defaultKBFSPath} prefix: ${openPath}`)
  }

  // turns '/keybase/private/alice' to 'private/alice'
  const subPath = goodPath
    .split('/')
    .slice(2)
    .join(path.sep)

  const state: TypedState = yield Saga.select()
  let {
    config: {kbfsPath},
  } = state

  if (!kbfsPath) {
    kbfsPath = yield Saga.call(RPCTypes.kbfsMountGetCurrentMountDirRpcPromise)

    if (!kbfsPath) {
      throw new Error('No kbfsPath (RPC)')
    }

    yield Saga.put(ConfigGen.createChangeKBFSPath({kbfsPath}))
  }

  const resolvedPath = path.resolve(kbfsPath, subPath)
  // Check to make sure our resolved path starts with the kbfsPath
  // i.e. (not opening a folder outside kbfs)
  if (!resolvedPath.startsWith(kbfsPath)) {
    throw new Error(`openWithCurrentMountDir requires ${kbfsPath} prefix: ${goodPath}`)
  }

  yield Saga.call(_open, resolvedPath)
}

function openInFileUISaga({payload: {path}}: FsGen.OpenInFileUIPayload, state: TypedState) {
  const openPath = path || Config.defaultKBFSPath
  const enabled = state.fs.fuseStatus && state.fs.fuseStatus.kextStarted
  if (isLinux || enabled) {
    return Saga.call(openWithCurrentMountDir, openPath)
  } else {
    return Saga.put(navigateTo([fsTab, {props: {path: Types.stringToPath(openPath)}, selected: 'folder'}]))
  }
}

function waitForMount(attempt: number) {
  return new Promise((resolve, reject) => {
    // Read the KBFS path waiting for files to exist, which means it's mounted
    // TODO: should handle current mount directory
    fs.readdir(`${Config.defaultKBFSPath}${Config.defaultPrivatePrefix}`, (err, files) => {
      if (!err && files.length > 0) {
        resolve(true)
      } else if (attempt > 15) {
        reject(new Error(`${Config.defaultKBFSPath} is unavailable. Please try again.`))
      } else {
        setTimeout(() => {
          waitForMount(attempt + 1).then(resolve, reject)
        }, 1000)
      }
    })
  })
}

const installKBFSSuccess = (result: RPCTypes.InstallResult) =>
  Saga.sequentially([
    Saga.call(waitForMount, 0),
    Saga.put(FsGen.createSetFlags({kbfsInstalling: false, showBanner: true})),
  ])

function fuseStatusResultSaga({payload: {prevStatus, status}}: FsGen.FuseStatusResultPayload) {
  // If our kextStarted status changed, finish KBFS install
  if (status.kextStarted && prevStatus && !prevStatus.kextStarted) {
    return Saga.put(FsGen.createInstallKBFS())
  }
}

function* fuseStatusSaga(): Saga.SagaGenerator<any, any> {
  const state: TypedState = yield Saga.select()
  const prevStatus = state.fs.fuseStatus

  let status = yield Saga.call(RPCTypes.installFuseStatusRpcPromise, {bundleVersion: ''})
  if (isWindows && status.installStatus !== RPCTypes.installInstallStatus.installed) {
    // Check if another Dokan we didn't install mounted the filesystem
    const kbfsMount = yield Saga.call(RPCTypes.kbfsMountGetCurrentMountDirRpcPromise)
    if (kbfsMount && fs.existsSync(kbfsMount)) {
      status.installStatus = RPCTypes.installInstallStatus.installed
      status.installAction = RPCTypes.installInstallAction.none
      status.kextStarted = true
    }
  }
  yield Saga.put(FsGen.createFuseStatusResult({prevStatus, status}))
}

function* installFuseSaga(): Saga.SagaGenerator<any, any> {
  const result: RPCTypes.InstallResult = yield Saga.call(RPCTypes.installInstallFuseRpcPromise)
  const fuseResults =
    result && result.componentResults ? result.componentResults.filter(c => c.name === 'fuse') : []
  const kextPermissionError =
    fuseResults.length > 0 && fuseResults[0].exitCode === Constants.ExitCodeFuseKextPermissionError

  if (kextPermissionError) {
    // Add a small delay here, since on 10.13 the OS will be a little laggy
    // when showing a kext permission error.
    yield Saga.delay(1e3)
  }

  yield Saga.put(FsGen.createInstallFuseResult({kextPermissionError}))
  yield Saga.put(FsGen.createFuseStatus())
  yield Saga.put(FsGen.createSetFlags({fuseInstalling: false}))
  // TODO: do something like uninstallConfirmSaga here
}

const uninstallKBFSConfirm = (action: FsGen.UninstallKBFSConfirmPayload) =>
  new Promise((resolve, reject) =>
    SafeElectron.getDialog().showMessageBox(
      null,
      {
        buttons: ['Remove & Restart', 'Cancel'],
        detail: `Are you sure you want to remove Keybase from ${fileUIName} and restart the app?`,
        message: `Remove Keybase from ${fileUIName}`,
        type: 'question',
      },
      resp => resolve(resp)
    )
  )

const uninstallKBFSConfirmSuccess = resp =>
  resp
    ? undefined
    : Saga.sequentially([
        Saga.call(RPCTypes.installUninstallKBFSRpcPromise),
        Saga.call(() => {
          // Restart since we had to uninstall KBFS and it's needed by the service (for chat)
          SafeElectron.getApp().relaunch()
          SafeElectron.getApp().exit(0)
        }),
      ])

const openSecurityPreferences = () =>
  Saga.call(
    () =>
      new Promise((resolve, reject) => {
        SafeElectron.getShell().openExternal(
          'x-apple.systempreferences:com.apple.preference.security?General',
          {activate: true},
          err => {
            if (err) {
              reject(err)
              return
            }
            logger.info('Opened Security Preferences')
            resolve()
          }
        )
      })
  )

// Invoking the cached installer package has to happen from the topmost process
// or it won't be visible to the user. The service also does this to support command line
// operations.
function installCachedDokan() {
  return new Promise((resolve, reject) => {
    logger.info('Invoking dokan installer')
    const dokanPath = path.resolve(String(process.env.LOCALAPPDATA), 'Keybase', 'DokanSetup_redist.exe')
    try {
      execFileSync(dokanPath, [])
    } catch (err) {
      logger.error('installCachedDokan caught', err)
      reject(err)
      return
    }
    // restart the service, particularly kbfsdokan
    // based on desktop/app/start-win-service.js
    const binPath = path.resolve(String(process.env.LOCALAPPDATA), 'Keybase', 'keybase.exe')
    if (!binPath) {
      return
    }
    const rqPath = binPath.replace('keybase.exe', 'keybaserq.exe')
    const args = [binPath, 'ctl', 'restart']

    spawn(rqPath, args, {
      detached: true,
      stdio: 'ignore',
    })

    resolve()
  })
}

function installDokanSaga() {
  return Saga.call(installCachedDokan)
}

function openFinderPopup(action: FsGen.OpenFinderPopupPayload) {
  const {targetRect, routePath} = action.payload
  return Saga.put(
    putActionIfOnPath(
      routePath,
      navigateAppend([
        {
          props: {
            targetRect,
            position: 'bottom right',
            onHidden: () => Saga.put(navigateUp()),
            onInstall: () => Saga.put(FsGen.createInstallFuse()),
          },
          selected: 'finderAction',
        },
      ])
    )
  )
}

function platformSpecificIntentEffect(
  intent: Types.DownloadIntent,
  localPath: string,
  mimeType: string
): ?Saga.Effect {
  switch (intent) {
    case 'camera-roll':
      return Saga.call(saveAttachmentDialog, localPath)
    case 'share':
      return Saga.call(showShareActionSheet, {url: localPath, mimeType})
    case 'none':
    case 'web-view':
    case 'web-view-text':
      return null
    default:
      /*::
      declare var ifFlowErrorsHereItsCauseYouDidntHandleAllTypesAbove: (a: empty) => any
      ifFlowErrorsHereItsCauseYouDidntHandleAllTypesAbove(intent);
      */
      return null
  }
}

const pickAndUpload = ({payload: {type}}: FsGen.PickAndUploadPayload) =>
  new Promise((resolve, reject) =>
    SafeElectron.getDialog().showOpenDialog(
      SafeElectron.getCurrentWindowFromRemote(),
      {
        title: 'Select a file or folder to upload',
        properties: [
          'multiSelections',
          ...(['file', 'both'].includes(type) ? ['openFile'] : []),
          ...(['directory', 'both'].includes(type) ? ['openDirectory'] : []),
        ],
      },
      filePaths => {
        return resolve(filePaths)
      }
    )
  )

const pickAndUploadSuccess = (localPaths, action: FsGen.PickAndUploadPayload) =>
  localPaths &&
  Saga.sequentially(
    localPaths.map(localPath =>
      Saga.put(FsGen.createUpload({localPath, parentPath: action.payload.parentPath}))
    )
  )

function* platformSpecificSaga(): Saga.SagaGenerator<any, any> {
  yield Saga.safeTakeEveryPure(FsGen.openInFileUI, openInFileUISaga)
  yield Saga.safeTakeEvery(FsGen.fuseStatus, fuseStatusSaga)
  yield Saga.safeTakeEveryPure(FsGen.fuseStatusResult, fuseStatusResultSaga)
  yield Saga.safeTakeEveryPure(FsGen.installKBFS, RPCTypes.installInstallKBFSRpcPromise, installKBFSSuccess)
  yield Saga.safeTakeEveryPure(FsGen.uninstallKBFSConfirm, uninstallKBFSConfirm, uninstallKBFSConfirmSuccess)
  yield Saga.safeTakeEveryPure(FsGen.pickAndUpload, pickAndUpload, pickAndUploadSuccess)
  if (isWindows) {
    yield Saga.safeTakeEveryPure(FsGen.installFuse, installDokanSaga)
  } else {
    yield Saga.safeTakeEvery(FsGen.installFuse, installFuseSaga)
  }
  yield Saga.safeTakeEveryPure(FsGen.openSecurityPreferences, openSecurityPreferences)

  // These are saga tasks that may use actions above.
  yield Saga.safeTakeEveryPure(FsGen.openFinderPopup, openFinderPopup)
}

export {platformSpecificIntentEffect, platformSpecificSaga}
<|endoftext|>


<commit_before>// @flow

import React from 'react'
import {Card} from '../../components/card'
import {FlatButton} from '../../components/button'
import {Icon} from '../../components/icon'
import {Toolbar} from '../../components/toolbar'
import Modal from '../../components/modal'
import List from '../../components/list'
import {close} from '../../icons/ionicons'
import type {HydratedStudentType} from '@gob/object-student'

import {encodeStudent} from '@gob/object-student'

type Props = {
	navigate: string => mixed,
	student?: HydratedStudentType,
	queryString?: string,
}

export function ShareSheet(props: Props) {
	let {student, navigate, queryString = window.location.search} = props

	const boundCloseModal = () => {
		let params = new URLSearchParams(queryString)
		params.delete('share')
		console.log(params.toString())
		navigate(`./${student.id}?${params.toString()}`)
	}

	if (!student) {
		return (
			<Modal onClose={boundCloseModal} contentLabel="Share">
				<Card>
					<Toolbar>
						<FlatButton onClick={boundCloseModal}>
							<Icon>{close}</Icon>
						</FlatButton>
					</Toolbar>

No student given?

				</Card>
			</Modal>
		)
	}

	let encodedStudent = encodeStudent(student)
	let encodedStudentUrl = `data:text/json;charset=utf-8,${encodedStudent}`

	return (
		<Modal onClose={boundCloseModal} contentLabel="Share">
			<Card>
				<Toolbar>
					<FlatButton onClick={boundCloseModal}>
						<Icon>{close}</Icon>
					</FlatButton>
				</Toolbar>

				<>
					Share "{student.name}" via:
					<List type="bullet">

Google Drive (not implemented)

							<a
								download={`${student.name}.gbstudent`}
								href={encodedStudentUrl}
							>
								Download file

					</List>
				</>
			</Card>
		</Modal>
	)
}

export default ShareSheet
<commit_msg>make student non-optional on ShareStudent<commit_after>// @flow

import React from 'react'
import {Card} from '../../components/card'
import {FlatButton} from '../../components/button'
import {Icon} from '../../components/icon'
import {Toolbar} from '../../components/toolbar'
import Modal from '../../components/modal'
import List from '../../components/list'
import {close} from '../../icons/ionicons'
import type {HydratedStudentType} from '@gob/object-student'

import {encodeStudent} from '@gob/object-student'

type Props = {
	navigate: string => mixed,
	student: HydratedStudentType,
	queryString?: string,
}

export function ShareSheet(props: Props) {
	let {student, navigate, queryString = window.location.search} = props

	const boundCloseModal = () => {
		let params = new URLSearchParams(queryString)
		params.delete('share')
		console.log(params.toString())
		navigate(`./${student.id}?${params.toString()}`)
	}

	if (!student) {
		return (
			<Modal onClose={boundCloseModal} contentLabel="Share">
				<Card>
					<Toolbar>
						<FlatButton onClick={boundCloseModal}>
							<Icon>{close}</Icon>
						</FlatButton>
					</Toolbar>

No student given?

				</Card>
			</Modal>
		)
	}

	let encodedStudent = encodeStudent(student)
	let encodedStudentUrl = `data:text/json;charset=utf-8,${encodedStudent}`

	return (
		<Modal onClose={boundCloseModal} contentLabel="Share">
			<Card>
				<Toolbar>
					<FlatButton onClick={boundCloseModal}>
						<Icon>{close}</Icon>
					</FlatButton>
				</Toolbar>

				<>
					Share "{student.name}" via:
					<List type="bullet">

Google Drive (not implemented)

							<a
								download={`${student.name}.gbstudent`}
								href={encodedStudentUrl}
							>
								Download file

					</List>
				</>
			</Card>
		</Modal>
	)
}

export default ShareSheet
<|endoftext|>


<commit_before>/**
 * @fileOverview `seamless_ttlselect` view adapter test case.
 * This spec is managed by the Jasmine testing library.
 **/
/*global describe, it, expect, beforeEach, afterEach, jasmine, spyOn */
/*global Privly, Promise */
describe('Privly.app.viewAdapter.SeamlessTTLSelect', function () {

  var msgSent;

  beforeEach(function () {
    $(document.body).append('

');
    msgSent = [];
    Privly.message.messageExtension = function (msg, hasResponse) {
      msgSent.push(msg);
      return Promise.resolve();
    };
    spyOn(Privly.message, 'messageExtension').and.callThrough();
  });

  afterEach(function () {
    document.body.innerHTML = "";
  });

  it('msgTTLChange calls Privly.message.messageExtension', function () {
    var adapter = new Privly.app.viewAdapter.SeamlessTTLSelect({});
    adapter.msgTTLChange('2147');
    expect(Privly.message.messageExtension).toHaveBeenCalled();
    expect(msgSent[0].action).toBe('posting/contentScript/TTLChanged');
    expect(msgSent[0].value).toBe('2147');
  });

  it('msgAppReady calls Privly.message.messageExtension', function () {
    var adapter = new Privly.app.viewAdapter.SeamlessTTLSelect({});
    adapter.msgAppReady({
      width: 100,
      height: 300
    });
    expect(Privly.message.messageExtension).toHaveBeenCalled();
    expect(msgSent[0].action).toBe('posting/contentScript/TTLSelectReady');
    expect(msgSent[0].size).toEqual({
      width: 100,
      height: 300
    });
  });

  it('getTTLOptions returns a Promise of empty array if application.getTTLOptions is not a function', function (done) {
    var application = {};
    var adapter = new Privly.app.viewAdapter.SeamlessTTLSelect(application);
    expect(adapter.getTTLOptions().then).toEqual(jasmine.any(Function));
    adapter.getTTLOptions().then(function (options) {
      expect(options).toEqual([]);
      done();
    });
  });

  it('getTTLOptions returns a Promise of options from application.getTTLOptions', function (done) {
    var application = {
      getTTLOptions: function () {
        return Promise.resolve([
          {ttl: '1', text: 'text for 1'},
          {ttl: '10', text: 'text for 10'},
        ]);
      }
    };
    var adapter = new Privly.app.viewAdapter.SeamlessTTLSelect(application);
    adapter.getTTLOptions().then(function (options) {
      expect(options).toEqual([
        {ttl: '1', text: 'text for 1'},
        {ttl: '10', text: 'text for 10'},
      ]);
      done();
    });
  });

  it('creates proper menu items if getTTLOptions does not contain default values and isAbove is false', function (done) {
    var application = {
      getTTLOptions: function () {
        return Promise.resolve([
          {ttl: '1', text: 'text for 1'},
          {ttl: '10', text: 'text for 10'},
        ]);
      }
    };
    var adapter = new Privly.app.viewAdapter.SeamlessTTLSelect(application);
    adapter.initMenu(false, '10', function () {
      expect(document.querySelectorAll('.posting-select-item')[0].getAttribute('data-value')).toBe('1');
      expect(document.querySelectorAll('.posting-select-item')[0].innerText.trim()).toBe('text for 1');
      expect(document.querySelectorAll('.posting-select-item')[1].getAttribute('data-value')).toBe('10');
      expect(document.querySelectorAll('.posting-select-item')[1].innerText.trim()).toBe('text for 10');
      // second is selected
      expect(document.querySelectorAll('.posting-select-item')[1].classList.contains('selected')).toBe(true);
      done();
    });
  });

  it('creates proper menu items if getTTLOptions does not contain default values and isAbove is true', function (done) {
    var application = {
      getTTLOptions: function () {
        return Promise.resolve([
          {ttl: '1', text: 'text for 1'},
          {ttl: '10', text: 'text for 10'},
        ]);
      }
    };
    // if isAbove=true, the memu should be reversed so that the smallest index is always closest to user
    var adapter = new Privly.app.viewAdapter.SeamlessTTLSelect(application);
    adapter.initMenu(true, '10', function () {
      expect(document.querySelectorAll('.posting-select-item')[1].getAttribute('data-value')).toBe('1');
      expect(document.querySelectorAll('.posting-select-item')[1].innerText.trim()).toBe('text for 1');
      expect(document.querySelectorAll('.posting-select-item')[0].getAttribute('data-value')).toBe('10');
      expect(document.querySelectorAll('.posting-select-item')[0].innerText.trim()).toBe('text for 10');
      expect(document.querySelectorAll('.posting-select-item')[0].classList.contains('selected')).toBe(true);
      done();
    });
  });

  it('creates proper menu items if getTTLOptions contains default value', function (done) {
    var application = {
      getTTLOptions: function () {
        return Promise.resolve([
          {ttl: '1', text: 'text for 1'},
          {ttl: '10', text: 'text for 10'},
          {ttl: '100', text: 'default value', default: true}
        ]);
      }
    };
    var adapter = new Privly.app.viewAdapter.SeamlessTTLSelect(application);
    adapter.initMenu(false, null, function () {
      expect(document.querySelectorAll('.posting-select-item')[0].getAttribute('data-value')).toBe('1');
      expect(document.querySelectorAll('.posting-select-item')[0].innerText.trim()).toBe('text for 1');
      expect(document.querySelectorAll('.posting-select-item')[1].getAttribute('data-value')).toBe('10');
      expect(document.querySelectorAll('.posting-select-item')[1].innerText.trim()).toBe('text for 10');
      expect(document.querySelectorAll('.posting-select-item')[2].getAttribute('data-value')).toBe('100');
      expect(document.querySelectorAll('.posting-select-item')[2].innerText.trim()).toBe('default value');
      // default
      expect(document.querySelectorAll('.posting-select-item')[2].classList.contains('selected')).toBe(true);
      done();
    });
  });

  it('call initMenu when receives posting/app/initializeTTLSelect', function () {
    var adapter = new Privly.app.viewAdapter.SeamlessTTLSelect({});
    spyOn(adapter, 'initMenu').and.callThrough();
    adapter.onMessageReceived({
      action: 'posting/app/initializeTTLSelect'
    }, function () {});
    expect(adapter.initMenu).toHaveBeenCalled();
  });

  it('call msgTTLChange when user clicks an item', function () {
    var adapter = new Privly.app.viewAdapter.SeamlessTTLSelect({});
    spyOn(adapter, 'msgTTLChange').and.callThrough();
    adapter.onItemSelected({
      target: {
        getAttribute: function () {
          return '100';
        }
      }
    });
    expect(adapter.msgTTLChange).toHaveBeenCalledWith('100');
  });

  it('start will call msgAppReady', function (done) {
    var application = {
      getTTLOptions: function () {
        return Promise.resolve([
          {ttl: '1', text: 'text for 1'},
          {ttl: '10', text: 'text for 10'},
          {ttl: '100', text: 'default value', default: true}
        ]);
      }
    };
    var adapter = new Privly.app.viewAdapter.SeamlessTTLSelect(application);
    spyOn(adapter, 'msgAppReady').and.callThrough();
    adapter.start();
    setTimeout(function () {
      expect(adapter.msgAppReady).toHaveBeenCalled();
      done();
    }, 50);
  });

});
<commit_msg>switched .innerText to .textContent<commit_after>/**
 * @fileOverview `seamless_ttlselect` view adapter test case.
 * This spec is managed by the Jasmine testing library.
 **/
/*global describe, it, expect, beforeEach, afterEach, jasmine, spyOn */
/*global Privly, Promise */
describe('Privly.app.viewAdapter.SeamlessTTLSelect', function () {

  var msgSent;

  beforeEach(function () {
    $(document.body).append('

');
    msgSent = [];
    Privly.message.messageExtension = function (msg, hasResponse) {
      msgSent.push(msg);
      return Promise.resolve();
    };
    spyOn(Privly.message, 'messageExtension').and.callThrough();
  });

  afterEach(function () {
    document.body.innerHTML = "";
  });

  it('msgTTLChange calls Privly.message.messageExtension', function () {
    var adapter = new Privly.app.viewAdapter.SeamlessTTLSelect({});
    adapter.msgTTLChange('2147');
    expect(Privly.message.messageExtension).toHaveBeenCalled();
    expect(msgSent[0].action).toBe('posting/contentScript/TTLChanged');
    expect(msgSent[0].value).toBe('2147');
  });

  it('msgAppReady calls Privly.message.messageExtension', function () {
    var adapter = new Privly.app.viewAdapter.SeamlessTTLSelect({});
    adapter.msgAppReady({
      width: 100,
      height: 300
    });
    expect(Privly.message.messageExtension).toHaveBeenCalled();
    expect(msgSent[0].action).toBe('posting/contentScript/TTLSelectReady');
    expect(msgSent[0].size).toEqual({
      width: 100,
      height: 300
    });
  });

  it('getTTLOptions returns a Promise of empty array if application.getTTLOptions is not a function', function (done) {
    var application = {};
    var adapter = new Privly.app.viewAdapter.SeamlessTTLSelect(application);
    expect(adapter.getTTLOptions().then).toEqual(jasmine.any(Function));
    adapter.getTTLOptions().then(function (options) {
      expect(options).toEqual([]);
      done();
    });
  });

  it('getTTLOptions returns a Promise of options from application.getTTLOptions', function (done) {
    var application = {
      getTTLOptions: function () {
        return Promise.resolve([
          {ttl: '1', text: 'text for 1'},
          {ttl: '10', text: 'text for 10'},
        ]);
      }
    };
    var adapter = new Privly.app.viewAdapter.SeamlessTTLSelect(application);
    adapter.getTTLOptions().then(function (options) {
      expect(options).toEqual([
        {ttl: '1', text: 'text for 1'},
        {ttl: '10', text: 'text for 10'},
      ]);
      done();
    });
  });

  it('creates proper menu items if getTTLOptions does not contain default values and isAbove is false', function (done) {
    var application = {
      getTTLOptions: function () {
        return Promise.resolve([
          {ttl: '1', text: 'text for 1'},
          {ttl: '10', text: 'text for 10'},
        ]);
      }
    };
    var adapter = new Privly.app.viewAdapter.SeamlessTTLSelect(application);
    adapter.initMenu(false, '10', function () {
      expect(document.querySelectorAll('.posting-select-item')[0].getAttribute('data-value')).toBe('1');
      expect(document.querySelectorAll('.posting-select-item')[0].textContent.trim()).toBe('text for 1');
      expect(document.querySelectorAll('.posting-select-item')[1].getAttribute('data-value')).toBe('10');
      expect(document.querySelectorAll('.posting-select-item')[1].textContent.trim()).toBe('text for 10');
      // second is selected
      expect(document.querySelectorAll('.posting-select-item')[1].classList.contains('selected')).toBe(true);
      done();
    });
  });

  it('creates proper menu items if getTTLOptions does not contain default values and isAbove is true', function (done) {
    var application = {
      getTTLOptions: function () {
        return Promise.resolve([
          {ttl: '1', text: 'text for 1'},
          {ttl: '10', text: 'text for 10'}
        ]);
      }
    };
    // if isAbove=true, the memu should be reversed so that the smallest index is always closest to user
    var adapter = new Privly.app.viewAdapter.SeamlessTTLSelect(application);
    adapter.initMenu(true, '10', function () {
      expect(document.querySelectorAll('.posting-select-item')[1].getAttribute('data-value')).toBe('1');
      expect(document.querySelectorAll('.posting-select-item')[1].textContent.trim()).toBe('text for 1');
      expect(document.querySelectorAll('.posting-select-item')[0].getAttribute('data-value')).toBe('10');
      expect(document.querySelectorAll('.posting-select-item')[0].textContent.trim()).toBe('text for 10');
      expect(document.querySelectorAll('.posting-select-item')[0].classList.contains('selected')).toBe(true);
      done();
    });
  });

  it('creates proper menu items if getTTLOptions contains default value', function (done) {
    var application = {
      getTTLOptions: function () {
        return Promise.resolve([
          {ttl: '1', text: 'text for 1'},
          {ttl: '10', text: 'text for 10'},
          {ttl: '100', text: 'default value', default: true}
        ]);
      }
    };
    var adapter = new Privly.app.viewAdapter.SeamlessTTLSelect(application);
    adapter.initMenu(false, null, function () {
      expect(document.querySelectorAll('.posting-select-item')[0].getAttribute('data-value')).toBe('1');
      expect(document.querySelectorAll('.posting-select-item')[0].textContent.trim()).toBe('text for 1');
      expect(document.querySelectorAll('.posting-select-item')[1].getAttribute('data-value')).toBe('10');
      expect(document.querySelectorAll('.posting-select-item')[1].textContent.trim()).toBe('text for 10');
      expect(document.querySelectorAll('.posting-select-item')[2].getAttribute('data-value')).toBe('100');
      expect(document.querySelectorAll('.posting-select-item')[2].textContent.trim()).toBe('default value');
      // default
      expect(document.querySelectorAll('.posting-select-item')[2].classList.contains('selected')).toBe(true);
      done();
    });
  });

  it('call initMenu when receives posting/app/initializeTTLSelect', function () {
    var adapter = new Privly.app.viewAdapter.SeamlessTTLSelect({});
    spyOn(adapter, 'initMenu').and.callThrough();
    adapter.onMessageReceived({
      action: 'posting/app/initializeTTLSelect'
    }, function () {});
    expect(adapter.initMenu).toHaveBeenCalled();
  });

  it('call msgTTLChange when user clicks an item', function () {
    var adapter = new Privly.app.viewAdapter.SeamlessTTLSelect({});
    spyOn(adapter, 'msgTTLChange').and.callThrough();
    adapter.onItemSelected({
      target: {
        getAttribute: function () {
          return '100';
        }
      }
    });
    expect(adapter.msgTTLChange).toHaveBeenCalledWith('100');
  });

  it('start will call msgAppReady', function (done) {
    var application = {
      getTTLOptions: function () {
        return Promise.resolve([
          {ttl: '1', text: 'text for 1'},
          {ttl: '10', text: 'text for 10'},
          {ttl: '100', text: 'default value', default: true}
        ]);
      }
    };
    var adapter = new Privly.app.viewAdapter.SeamlessTTLSelect(application);
    spyOn(adapter, 'msgAppReady').and.callThrough();
    adapter.start();
    setTimeout(function () {
      expect(adapter.msgAppReady).toHaveBeenCalled();
      done();
    }, 50);
  });

});
<|endoftext|>


<commit_before>describe('The dp-map directive', function () {
    var $compile,
        $rootScope,
        L,
        layers,
        variableWidth,
        mockedMapState;

    beforeEach(function () {
        angular.mock.module(
            'dpMap',
            {
                mapConfig: {
                    MAP_OPTIONS: {
                        doThisThing: false,
                        someVariable: 4
                    }
                }
            }
        );

        angular.mock.inject(function (_$compile_, _$rootScope_, _L_, _layers_, _variableWidth_) {
            $compile = _$compile_;
            $rootScope = _$rootScope_;
            L = _L_;
            layers = _layers_;
            variableWidth = _variableWidth_;
        });

        spyOn(L, 'map').and.returnValue('I_AM_A_FAKE_LEAFLET_MAP');

        spyOn(layers, 'setBaseLayer');
        spyOn(layers, 'addOverlay');
        spyOn(layers, 'removeOverlay');

        spyOn(variableWidth, 'initialize');

        mockedMapState = {
            baseLayer: 'topografie',
            overlays: [],
            viewCenter: [52.789, 4.123],
            zoom: 12
        };
    });

    function getDirective (mapState, markers) {
        var directive,
            element,
            scope;

        element = document.createElement('dp-map');
        element.setAttribute('map-state', 'mapState');
        element.setAttribute('markers', 'markers');

        scope = $rootScope.$new();
        scope.mapState = mapState;
        scope.markers = markers;

        directive = $compile(element)(scope);
        scope.$apply();

        return directive;
    }

    it('creates a Leaflet map with options based on both the map state and mapConfig', function () {
        var directive,
            element;

        directive = getDirective(mockedMapState, {});
        element = directive[0].querySelector('.js-leaflet-map');

        expect(L.map).toHaveBeenCalledWith(element, {
            center: [52.789, 4.123],
            zoom: 12,
            doThisThing: false,
            someVariable: 4
        });
    });

    describe('has a base layer which', function () {
        it('is set on initialization', function () {
            getDirective(mockedMapState, {});

            expect(layers.setBaseLayer).toHaveBeenCalledWith('I_AM_A_FAKE_LEAFLET_MAP', 'topografie');
        });

        it('changes when the mapState changes', function () {
            getDirective(mockedMapState, {});
            expect(layers.setBaseLayer).toHaveBeenCalledTimes(1);

            mockedMapState.baseLayer = 'luchtfoto_2015';
            $rootScope.$apply();

            expect(layers.setBaseLayer).toHaveBeenCalledTimes(2);
            expect(layers.setBaseLayer).toHaveBeenCalledWith('I_AM_A_FAKE_LEAFLET_MAP', 'luchtfoto_2015');
        });
    });

    describe('has overlays which', function () {
        it('can be added on initialization', function () {
            mockedMapState.overlays = ['some_overlay'];
            getDirective(mockedMapState, {});
            expect(layers.addOverlay).toHaveBeenCalledWith('I_AM_A_FAKE_LEAFLET_MAP', 'some_overlay');
        });

        it('can be added when the mapState changes', function () {
            getDirective(mockedMapState, {});
            expect(layers.addOverlay).not.toHaveBeenCalled();

            mockedMapState.overlays = ['some_overlay', 'some_other_overlay'];
            $rootScope.$apply();

            expect(layers.addOverlay).toHaveBeenCalledWith('I_AM_A_FAKE_LEAFLET_MAP', 'some_overlay');
            expect(layers.addOverlay).toHaveBeenCalledWith('I_AM_A_FAKE_LEAFLET_MAP', 'some_other_overlay');
        });

        it('can be removed when the mapState changes', function () {
            mockedMapState.overlays = ['some_overlay', 'some_other_overlay'];
            getDirective(mockedMapState, {});

            expect(layers.removeOverlay).not.toHaveBeenCalled();

            mockedMapState.overlays = [];
            $rootScope.$apply();

            expect(layers.removeOverlay).toHaveBeenCalledWith('I_AM_A_FAKE_LEAFLET_MAP', 'some_overlay');
            expect(layers.removeOverlay).toHaveBeenCalledWith('I_AM_A_FAKE_LEAFLET_MAP', 'some_other_overlay');
        });
    });

    it('initializes the variableWidth factory', function () {
        var directive,
            container;

        directive = getDirective(mockedMapState, {});
        container = directive[0].querySelector('.js-leaflet-map');

        expect(variableWidth.initialize).toHaveBeenCalledWith(container, 'I_AM_A_FAKE_LEAFLET_MAP');
    });
});<commit_msg>Had to mock dp-link.<commit_after>describe('The dp-map directive', function () {
    var $compile,
        $rootScope,
        L,
        layers,
        variableWidth,
        mockedMapState;

    beforeEach(function () {
        angular.mock.module(
            'dpMap',
            {
                mapConfig: {
                    MAP_OPTIONS: {
                        doThisThing: false,
                        someVariable: 4
                    }
                }
            },
            function ($provide) {
                $provide.factory('dpLinkDirective', function () {
                    return {};
                });
            }
        );

        angular.mock.inject(function (_$compile_, _$rootScope_, _L_, _layers_, _variableWidth_) {
            $compile = _$compile_;
            $rootScope = _$rootScope_;
            L = _L_;
            layers = _layers_;
            variableWidth = _variableWidth_;
        });

        spyOn(L, 'map').and.returnValue('I_AM_A_FAKE_LEAFLET_MAP');

        spyOn(layers, 'setBaseLayer');
        spyOn(layers, 'addOverlay');
        spyOn(layers, 'removeOverlay');

        spyOn(variableWidth, 'initialize');

        mockedMapState = {
            baseLayer: 'topografie',
            overlays: [],
            viewCenter: [52.789, 4.123],
            zoom: 12
        };
    });

    function getDirective (mapState, markers) {
        var directive,
            element,
            scope;

        element = document.createElement('dp-map');
        element.setAttribute('map-state', 'mapState');
        element.setAttribute('markers', 'markers');

        scope = $rootScope.$new();
        scope.mapState = mapState;
        scope.markers = markers;

        directive = $compile(element)(scope);
        scope.$apply();

        return directive;
    }

    it('creates a Leaflet map with options based on both the map state and mapConfig', function () {
        var directive,
            element;

        directive = getDirective(mockedMapState, {});
        element = directive[0].querySelector('.js-leaflet-map');

        expect(L.map).toHaveBeenCalledWith(element, {
            center: [52.789, 4.123],
            zoom: 12,
            doThisThing: false,
            someVariable: 4
        });
    });

    describe('has a base layer which', function () {
        it('is set on initialization', function () {
            getDirective(mockedMapState, {});

            expect(layers.setBaseLayer).toHaveBeenCalledWith('I_AM_A_FAKE_LEAFLET_MAP', 'topografie');
        });

        it('changes when the mapState changes', function () {
            getDirective(mockedMapState, {});
            expect(layers.setBaseLayer).toHaveBeenCalledTimes(1);

            mockedMapState.baseLayer = 'luchtfoto_2015';
            $rootScope.$apply();

            expect(layers.setBaseLayer).toHaveBeenCalledTimes(2);
            expect(layers.setBaseLayer).toHaveBeenCalledWith('I_AM_A_FAKE_LEAFLET_MAP', 'luchtfoto_2015');
        });
    });

    describe('has overlays which', function () {
        it('can be added on initialization', function () {
            mockedMapState.overlays = ['some_overlay'];
            getDirective(mockedMapState, {});
            expect(layers.addOverlay).toHaveBeenCalledWith('I_AM_A_FAKE_LEAFLET_MAP', 'some_overlay');
        });

        it('can be added when the mapState changes', function () {
            getDirective(mockedMapState, {});
            expect(layers.addOverlay).not.toHaveBeenCalled();

            mockedMapState.overlays = ['some_overlay', 'some_other_overlay'];
            $rootScope.$apply();

            expect(layers.addOverlay).toHaveBeenCalledWith('I_AM_A_FAKE_LEAFLET_MAP', 'some_overlay');
            expect(layers.addOverlay).toHaveBeenCalledWith('I_AM_A_FAKE_LEAFLET_MAP', 'some_other_overlay');
        });

        it('can be removed when the mapState changes', function () {
            mockedMapState.overlays = ['some_overlay', 'some_other_overlay'];
            getDirective(mockedMapState, {});

            expect(layers.removeOverlay).not.toHaveBeenCalled();

            mockedMapState.overlays = [];
            $rootScope.$apply();

            expect(layers.removeOverlay).toHaveBeenCalledWith('I_AM_A_FAKE_LEAFLET_MAP', 'some_overlay');
            expect(layers.removeOverlay).toHaveBeenCalledWith('I_AM_A_FAKE_LEAFLET_MAP', 'some_other_overlay');
        });
    });

    it('initializes the variableWidth factory', function () {
        var directive,
            container;

        directive = getDirective(mockedMapState, {});
        container = directive[0].querySelector('.js-leaflet-map');

        expect(variableWidth.initialize).toHaveBeenCalledWith(container, 'I_AM_A_FAKE_LEAFLET_MAP');
    });
});<|endoftext|>


<commit_before>let vdos = [
  {video_id: 'CaBPn3vZkRs'},
  {video_id: 'Xjrb1kDnUYY'},
  {video_id: 'Jw8HYF1ZpJM'},
  {video_id: 'hnLWPMve4ws'},
  {video_id: 'QvRrl928MxE'},
  {video_id: 'ALGsdybvI_A'},
  {video_id: 'BedQkstH8ZA'},
  {video_id: 'lgGoht6qYZY'},
  {video_id: 'OZrVVp8_iEQ'},
  {video_id: 'HpGadjOUzz8'},
  {video_id: '8AaZK9xHEhM'},
  {video_id: 'd0J_CBnb2GI'},
  {video_id: 'gTdp1iOcDKc'},
  {video_id: 'GS37cPnRqTs'},
  {video_id: 'hQFeNt0Sv4c'}
]

Meteor.subscribe('vdos', () =>
  (! Vdos.find().count()) && vdos.map(({video_id}) => checkAndSave(video_id)))
<commit_msg>Add a tournament vdo<commit_after>const vdos = [
  // Fuseki
  {video_id: 'CaBPn3vZkRs'},
  {video_id: 'Xjrb1kDnUYY'},
  {video_id: 'Jw8HYF1ZpJM'},
  {video_id: 'hnLWPMve4ws'},
  {video_id: 'QvRrl928MxE'},
  {video_id: 'ALGsdybvI_A'},
  {video_id: 'BedQkstH8ZA'},
  {video_id: 'lgGoht6qYZY'},
  {video_id: 'OZrVVp8_iEQ'},
  {video_id: 'HpGadjOUzz8'},
  {video_id: '8AaZK9xHEhM'},
  {video_id: 'd0J_CBnb2GI'},
  {video_id: 'gTdp1iOcDKc'},
  {video_id: 'GS37cPnRqTs'},
  {video_id: 'hQFeNt0Sv4c'},
  {video_id: '9ziMKccj91g'}   // Tournament
]
Meteor.subscribe('vdos', () =>
  (! Vdos.find().count()) && vdos.map(({video_id}) => checkAndSave(video_id)))
<|endoftext|>