<commit_before>Ext.define("DARPA.EntityGraphPanel", {
	extend: "DARPA.AbstractGraphPanel",

	constructor: function(config) {
		var self = this;

		this.GraphVis = new CytoscapeGraphVis(config.id + "-ENTcygraph");

		var graphSettings = Ext.create("DARPA.GraphSettings", {
			id: config.id + "-Settings"
		});

		graphSettings.setGraph(self);

		var filterSettings = Ext.create("DARPA.FilterSettings", {
			id: config.id + "-Filter"
		});

		filterSettings.setAdditionalFields([{
			dispFieldName: "Node Color", 		dispFieldType: "dropdown", 	dispFieldWidth: 100,
			dispFieldChoices: "getfromnode",	dataSourceType: "nodes", 	dataSourceField: "color"
		}, {
			dispFieldName: "Node Name", 		dispFieldType: "dropdown", 	dispFieldWidth: 100,
			dispFieldChoices: "getfromnode", 	dataSourceType: "nodes", 	dataSourceField: "name"
		}, {
			dispFieldName: "Identifier Type", 	dispFieldType: "dropdown", 	dispFieldWidth: 100,
			dispFieldChoices: "getfromnode", 	dataSourceType: "nodes", 	dataSourceField: "idType"
		}, {
			dispFieldName: "getfromkey", 	dispFieldType: "text", 		dispFieldWidth: 100,
			dispFieldChoices: "", 			dataSourceType: "nodes", 	dataSourceField: "attrs"
		}, {
			dispFieldName: "Amount", 	dispFieldType: "text", 		dispFieldWidth: 100,
			dispFieldChoices: "", 		dataSourceType: "edges", 	dataSourceField: "amount"
		}, {
			dispFieldName: "getfromkey", dispFieldType: "text", dispFieldWidth: 100,
			dispFieldChoices: "", dataSourceType: "edges", dataSourceField: "attrs"
		}]);

		filterSettings.setGraph(self);
		filterSettings.enableTimeFilter(false); // TODO renable based on graph type or if data is temporally-based
		filterSettings.setSearchFieldLabel("Identifiers(s)");

		var graphContainer = Ext.create("Ext.Container", {
			width: 'auto',
			height: 'auto',
			id: config.id + "-ENTcygraph",
			flex: 1
		});

		var settingsPanel = Ext.create("Ext.tab.Panel", {
			width: 320,
			height: 'auto',
			collapsible: true,
			collapseDirection: 'right',
			listeners: {
				collapse: function(e) {
					self.GraphVis.resize();
				},
				expand: function(e) {
					self.GraphVis.resize();
				}
			},
			items: [
				Ext.create("Ext.panel.Panel", {
					title: "DETAILS/ACTIONS",
					layout: 'fit',
					items: [
						Ext.create("DARPA.EntityNodeDisplay", {
							id: config.id + "-NodeDisplay",
							height: "auto"
						})
					]
				}),
				Ext.create("Ext.panel.Panel", {
					title: "SETTINGS/FILTER",
					items: [
						graphSettings,
						Ext.create("Ext.panel.Panel", {
							title: "FILTER",
							collapsible: false,
							items: filterSettings
						})
					] // END settings/filter items
				}),
				/* Hide until it is complete *//*
				Ext.create("DARPA.AdvancedQueryPanel", {
					title: "ADV. QUERY",
					id: config.id + "-AdvQuery",
					graphRef: self.GraphVis
				})
				*/
			] // END settings panel items
		});

		this.items = [graphContainer, settingsPanel];
		this.callParent(arguments);
	},

	getNodeDisplay : function() {
		var tabPanel = this.items.items[1];
		var tabScreen = tabPanel.items.items[0];
		return tabScreen.items.items[0];
	},

	getSettings : function() {
		var tabPanel = this.items.items[1];
		var settingsPanel = tabPanel.items.items[1];
		return settingsPanel.items.items[0];
	},

	afterLayout: function() {
		var self = this;
		if (self.GraphVis.getGv() == null) {
			var config = {/* configs go here */};
			self.GraphVis.init(config, self, function() {
				//self.showjson(self.prevLoadParams.value);
			}, true);
		}
		this.callParent(arguments); // ?
	},

	load: function(custno, useSaved) {
		var self = this;

		// TODO write catch to make sure variable useSaved is a boolean

		var graphStore = self.graphStore;
		var hops = self.getSettings().getMaxHops();
		var degree = parseInt(hops);// + 1; //djue

		self.json = null;
		self.prevLoadParams.searchValue = self.prevLoadParams.value = self.prevLoadParams.prevValue = custno;

		self.getProgressBar().wait({
        	interval: 1000,
        	duration: 90000,
        	increment: 10,
        	text: "Searching..."
        });

		graphStore.proxy.extraParams.degree = degree;
		graphStore.proxy.extraParams.useSaved = useSaved;
		graphStore.proxy.url = Config.entityGraphCSUrl + 'customer/' + custno;

		graphStore.load({
			scope: this, // ?
			callback: function(records, operation, success) {

				self.getProgressBar().reset();

				if (success == false || records == null || records.length == 0) {
					if (success == false) self.setStatus("SERVER ERROR REQUESTING GRAPH", 0);
					else if (records == null) self.setStatus("SERVER RETURNED NULL GRAPH", 0);
					else if (records.length == 0) self.setStatus("SERVER RETURNED EMPTY GRAPH", 0);
					self.clear();
					return;
				}

				var retJSON = records[0].raw;

				self.json = retJSON;
				self.GraphVis.setUserName(retJSON.userName);
				self.setStatus(retJSON.strStatus, 1);

				self.legendJSON = retJSON.legend;
				if (typeof self.legendJSON == "string") {
					self.legendJSON = Ext.decode(self.legendJSON);
				} // else assume JSON

				var loadGraph = function(scope, useSaved) {
					if (scope.GraphVis.getGv() != null) {
						//scope.clear();
						scope.showjson(scope.prevLoadParams.value, useSaved);
						scope.getNodeDisplay().updateLegend(scope.legendJSON, "EntityGraph");
					}
				};

				if (typeof self.json == "undefined") {
					//TODO error notice
					return;
				}

				var THRESHOLD = 300;
				var useSaved = false;

				if (self.json.nodes.length < THRESHOLD) {
					useSaved = self.json.nodes[0].position != null;
					loadGraph(self, useSaved);
				} else {
					useSaved = self.json.nodes[0].position != null;
					Ext.Msg.confirm(
						"Loading a Large Graph",
						"The expected graph contains over " + THRESHOLD + " nodes.  This may take a moment to render.  Do you wish to wait?\n" +
						"If not, the graph will not render.",
						function(ans) {
							if (ans == 'yes') {
								loadGraph(self, useSaved);
							}
						}
					);
				}
			}
		});
	},

	loadOneHop: function(node, intype) {
		var self = this;
		var graphStore = self.graphStore;
		var s = self.getSettings();
		var maxNewCallsAlertThresh = 30; // Adjust as needed

		graphStore.proxy.extraParams.degree = 1; // labelled hops. only 1 hop out from this node
		graphStore.proxy.extraParams.maxEdgesPerNode = s.getMaxEdgesPerNode();
		graphStore.proxy.extraParams.maxNodes = s.getMaxNodes();
		if (graphStore.proxy.extraParams.maxNodes > 200) {
			graphStore.proxy.extraParams.maxNodes = 200; // hard limit for this case
		}
		if (maxNewCallsAlertThresh > graphStore.proxy.extraParams.maxEdgesPerNode) {
			maxNewCallsAlertThresh = graphStore.proxy.extraParams.maxEdgesPerNode;
		}
		graphStore.proxy.extraParams.minWeight = s.getMinWeight();

		if (intype == null || intype.length == 0) {
			intype = "customer";
		}
		graphStore.proxy.extraParams.Type = intype;

		// FIXME: REST services don't seem to like properly encoded symbols. Re-address when time permits
		// In the meantime, use regex to replace all / characters with a space
		/* graphStore.proxy.url = Config.entityGraphUrl + intype + "/" + encodeURIComponent(node.data("name")); */
		graphStore.proxy.url = Config.entityGraphUrl + intype + "/" + node.data("name").replace(/\//g, " ");

		self.json1Hop = null; // prevents us from trying to display the previous graph if we switch to this tab before we have fully loaded the new graph
		self.json1HopNode = node;

		self.getProgressBar().wait({
        	interval: 1000,
        	duration: 90000,
        	increment: 10,
        	text: "Expanding..."
        });

		graphStore.load({
			scope : this, // ?
			callback : function(records, operation, success) {

				self.getProgressBar().reset();

				if (success == false || records == null || records.length == 0) {
					if (success == false) self.setStatus("SERVER ERROR REQUESTING GRAPH", 0);
					else if (records == null) self.setStatus("SERVER RETURNED NULL GRAPH", 0);
					else if (records.length == 0) self.setStatus("SERVER RETURNED EMPTY GRAPH", 0);
					self.clear();
					return;
				}

				var retJSON = records[0].raw;

				self.json = retJSON;
				self.setStatus(retJSON.strStatus, 1);

				self.legendJSON = retJSON.legend;
				if (typeof self.legendJSON == "string") {
					self.legendJSON = Ext.decode(self.legendJSON);
				} // else assume JSON

				// should be self.json.nodes.length
				if (self.json.length > maxNewCallsAlertThresh) {
					Ext.Msg.confirm(
						'Confirm',
						'This value has more than ' + maxNewCallsAlertThresh + ' items and may clutter the display. Do you want to continue displaying it?',
						function(ans) {
							if (ans == 'yes') {
								self.GraphVis.expand(self.json, node);
								self.getNodeDisplay().updateLegend(self.legendJSON, "EntityGraph");
							}
						}
					);
				} else {
					self.GraphVis.expand(self.json, node);
					self.getNodeDisplay().updateLegend(self.legendJSON, "EntityGraph");
				}
			}
		});
	},

	nodeClick: function(node) {
		var self = this;
		self.currentNode = node;
		var nodeDisp = self.getNodeDisplay();
		nodeDisp.showNodeAttrs(node);
		var type = node.data().idType;
		var isEntity = false;
		if (typeof isPivotableType == "function") {
			// isPivotableType is a global function found in .html
			isEntity = isPivotableType(type);
		} else {
			isEntity = (type == 'customer' || type == 'LENDER' || type == 'BORROWER');
		}

		var len = this.GraphVis.getSelectedNodes().length;
		var msg = "" + len + " selected node" + ((len > 1) ? "s" : "");
		this.setStatus(msg, 1);

		nodeDisp.enablePivot(isEntity && len == 1);
		nodeDisp.enableShow(type == 'account' || isEntity);
		nodeDisp.enableHide(true);
	},

	edgeClick: function(edge) {
		var nodeDisp = this.getNodeDisplay();
		var d = edge.data();
		if (d && d.attrs) {
			nodeDisp.showEdgeAttrs(edge);
		}
	},

	edgeRightClick: function(edge) {
		// do nothing
	}
});<commit_msg>Loading the graph respects the constraints provided by the user in the right display panel, namely the number of hops, the maximum number of nodes allowed, and the number of connections per node<commit_after>Ext.define("DARPA.EntityGraphPanel", {
	extend: "DARPA.AbstractGraphPanel",

	constructor: function(config) {
		var self = this;

		this.GraphVis = new CytoscapeGraphVis(config.id + "-ENTcygraph");

		var graphSettings = Ext.create("DARPA.GraphSettings", {
			id: config.id + "-Settings"
		});

		graphSettings.setGraph(self);

		var filterSettings = Ext.create("DARPA.FilterSettings", {
			id: config.id + "-Filter"
		});

		filterSettings.setAdditionalFields([{
			dispFieldName: "Node Color", 		dispFieldType: "dropdown", 	dispFieldWidth: 100,
			dispFieldChoices: "getfromnode",	dataSourceType: "nodes", 	dataSourceField: "color"
		}, {
			dispFieldName: "Node Name", 		dispFieldType: "dropdown", 	dispFieldWidth: 100,
			dispFieldChoices: "getfromnode", 	dataSourceType: "nodes", 	dataSourceField: "name"
		}, {
			dispFieldName: "Identifier Type", 	dispFieldType: "dropdown", 	dispFieldWidth: 100,
			dispFieldChoices: "getfromnode", 	dataSourceType: "nodes", 	dataSourceField: "idType"
		}, {
			dispFieldName: "getfromkey", 	dispFieldType: "text", 		dispFieldWidth: 100,
			dispFieldChoices: "", 			dataSourceType: "nodes", 	dataSourceField: "attrs"
		}, {
			dispFieldName: "Amount", 	dispFieldType: "text", 		dispFieldWidth: 100,
			dispFieldChoices: "", 		dataSourceType: "edges", 	dataSourceField: "amount"
		}, {
			dispFieldName: "getfromkey", dispFieldType: "text", dispFieldWidth: 100,
			dispFieldChoices: "", dataSourceType: "edges", dataSourceField: "attrs"
		}]);

		filterSettings.setGraph(self);
		filterSettings.enableTimeFilter(false); // TODO renable based on graph type or if data is temporally-based
		filterSettings.setSearchFieldLabel("Identifiers(s)");

		var graphContainer = Ext.create("Ext.Container", {
			width: 'auto',
			height: 'auto',
			id: config.id + "-ENTcygraph",
			flex: 1
		});

		var settingsPanel = Ext.create("Ext.tab.Panel", {
			width: 320,
			height: 'auto',
			collapsible: true,
			collapseDirection: 'right',
			listeners: {
				collapse: function(e) {
					self.GraphVis.resize();
				},
				expand: function(e) {
					self.GraphVis.resize();
				}
			},
			items: [
				Ext.create("Ext.panel.Panel", {
					title: "DETAILS/ACTIONS",
					layout: 'fit',
					items: [
						Ext.create("DARPA.EntityNodeDisplay", {
							id: config.id + "-NodeDisplay",
							height: "auto"
						})
					]
				}),
				Ext.create("Ext.panel.Panel", {
					title: "SETTINGS/FILTER",
					items: [
						graphSettings,
						Ext.create("Ext.panel.Panel", {
							title: "FILTER",
							collapsible: false,
							items: filterSettings
						})
					] // END settings/filter items
				}),
				/* Hide until it is complete *//*
				Ext.create("DARPA.AdvancedQueryPanel", {
					title: "ADV. QUERY",
					id: config.id + "-AdvQuery",
					graphRef: self.GraphVis
				})
				*/
			] // END settings panel items
		});

		this.items = [graphContainer, settingsPanel];
		this.callParent(arguments);
	},

	getNodeDisplay : function() {
		var tabPanel = this.items.items[1];
		var tabScreen = tabPanel.items.items[0];
		return tabScreen.items.items[0];
	},

	getSettings : function() {
		var tabPanel = this.items.items[1];
		var settingsPanel = tabPanel.items.items[1];
		return settingsPanel.items.items[0];
	},

	afterLayout: function() {
		var self = this;
		if (self.GraphVis.getGv() == null) {
			var config = {/* configs go here */};
			self.GraphVis.init(config, self, function() {
				//self.showjson(self.prevLoadParams.value);
			}, true);
		}
		this.callParent(arguments); // ?
	},

	load: function(custno, useSaved) {
		var self = this;

		// TODO write catch to make sure variable useSaved is a boolean
		// useSaved = useSaved === true;

		var graphStore = self.graphStore;
		var hops = self.getSettings().getMaxHops();
		var degree = parseInt(hops);// + 1; //djue
		var s = self.getSettings();

		self.json = null;
		self.prevLoadParams.searchValue = self.prevLoadParams.value = self.prevLoadParams.prevValue = custno;

		self.getProgressBar().wait({
        	interval: 1000,
        	duration: 90000,
        	increment: 10,
        	text: "Searching..."
        });

		graphStore.proxy.extraParams.degree = degree;
		graphStore.proxy.extraParams.useSaved = useSaved;
		graphStore.proxy.extraParams.maxEdgesPerNode = s.getMaxEdgesPerNode();
		graphStore.proxy.extraParams.maxNodes = s.getMaxNodes();

		graphStore.proxy.url = Config.entityGraphCSUrl + 'customer/' + custno;

		graphStore.load({
			scope: this, // ?
			callback: function(records, operation, success) {

				self.getProgressBar().reset();

				if (success == false || records == null || records.length == 0) {
					if (success == false) self.setStatus("SERVER ERROR REQUESTING GRAPH", 0);
					else if (records == null) self.setStatus("SERVER RETURNED NULL GRAPH", 0);
					else if (records.length == 0) self.setStatus("SERVER RETURNED EMPTY GRAPH", 0);
					self.clear();
					return;
				}

				var retJSON = records[0].raw;

				self.json = retJSON;
				self.GraphVis.setUserName(retJSON.userName);
				self.setStatus(retJSON.strStatus, 1);

				self.legendJSON = retJSON.legend;
				if (typeof self.legendJSON == "string") {
					self.legendJSON = Ext.decode(self.legendJSON);
				} // else assume JSON

				var loadGraph = function(scope, useSaved) {
					if (scope.GraphVis.getGv() != null) {
						//scope.clear();
						scope.showjson(scope.prevLoadParams.value, useSaved);
						scope.getNodeDisplay().updateLegend(scope.legendJSON, "EntityGraph");
					}
				};

				if (typeof self.json == "undefined" || self.json.nodes.length <= 0) {
					//TODO error notice
					console.log("Problem with Entity graph - No nodes to load");
					return;
				}

				var THRESHOLD = 300;
				var useSaved = false;

				if (self.json.nodes.length < THRESHOLD) {
					useSaved = self.json.nodes[0].position != null;
					loadGraph(self, useSaved);
				} else {
					useSaved = self.json.nodes[0].position != null;
					Ext.Msg.confirm(
						"Loading a Large Graph",
						"The expected graph contains over " + THRESHOLD + " nodes.  This may take a moment to render.  Do you wish to wait?\n" +
						"If not, the graph will not render.",
						function(ans) {
							if (ans == 'yes') {
								loadGraph(self, useSaved);
							}
						}
					);
				}
			}
		});
	},

	loadOneHop: function(node, intype) {
		var self = this;
		var graphStore = self.graphStore;
		var s = self.getSettings();
		var maxNewCallsAlertThresh = 30; // Adjust as needed

		graphStore.proxy.extraParams.degree = 1; // labeled hops. only 1 hop out from this node
		graphStore.proxy.extraParams.maxEdgesPerNode = s.getMaxEdgesPerNode();
		graphStore.proxy.extraParams.maxNodes = s.getMaxNodes();
		graphStore.proxy.extraParams.minWeight = s.getMinWeight();

		if (maxNewCallsAlertThresh > graphStore.proxy.extraParams.maxEdgesPerNode) {
			maxNewCallsAlertThresh = graphStore.proxy.extraParams.maxEdgesPerNode;
		}

		if (intype == null || intype.length == 0) {
			intype = "customer";
		}

		graphStore.proxy.extraParams.Type = intype;

		// FIXME: REST services don't seem to like properly encoded symbols. Re-address when time permits
		// In the meantime, use regex to replace all / characters with a space
		/* graphStore.proxy.url = Config.entityGraphUrl + intype + "/" + encodeURIComponent(node.data("name")); */
		graphStore.proxy.url = Config.entityGraphUrl + intype + "/" + node.data("name").replace(/\//g, " ");

		self.json1Hop = null; // prevents us from trying to display the previous graph if we switch to this tab before we have fully loaded the new graph
		self.json1HopNode = node;

		self.getProgressBar().wait({
        	interval: 1000,
        	duration: 90000,
        	increment: 10,
        	text: "Expanding..."
        });

		graphStore.load({
			scope : this, // ?
			callback : function(records, operation, success) {

				self.getProgressBar().reset();

				if (success == false || records == null || records.length == 0) {
					if (success == false) self.setStatus("SERVER ERROR REQUESTING GRAPH", 0);
					else if (records == null) self.setStatus("SERVER RETURNED NULL GRAPH", 0);
					else if (records.length == 0) self.setStatus("SERVER RETURNED EMPTY GRAPH", 0);
					self.clear();
					return;
				}

				var retJSON = records[0].raw;

				self.json = retJSON;
				self.setStatus(retJSON.strStatus, 1);

				self.legendJSON = retJSON.legend;
				if (typeof self.legendJSON == "string") {
					self.legendJSON = Ext.decode(self.legendJSON);
				} // else assume JSON

				// should be self.json.nodes.length
				if (self.json.length > maxNewCallsAlertThresh) {
					Ext.Msg.confirm(
						'Confirm',
						'This value has more than ' + maxNewCallsAlertThresh + ' items and may clutter the display. Do you want to continue displaying it?',
						function(ans) {
							if (ans == 'yes') {
								self.GraphVis.expand(self.json, node);
								self.getNodeDisplay().updateLegend(self.legendJSON, "EntityGraph");
							}
						}
					);
				} else {
					self.GraphVis.expand(self.json, node);
					self.getNodeDisplay().updateLegend(self.legendJSON, "EntityGraph");
				}
			}
		});
	},

	nodeClick: function(node) {
		var self = this;
		self.currentNode = node;
		var nodeDisp = self.getNodeDisplay();
		nodeDisp.showNodeAttrs(node);
		var type = node.data().idType;
		var isEntity = false;
		if (typeof isPivotableType == "function") {
			// isPivotableType is a global function found in .html
			isEntity = isPivotableType(type);
		} else {
			isEntity = (type == 'customer' || type == 'LENDER' || type == 'BORROWER');
		}

		var len = this.GraphVis.getSelectedNodes().length;
		var msg = "" + len + " selected node" + ((len > 1) ? "s" : "");
		this.setStatus(msg, 1);

		nodeDisp.enablePivot(isEntity && len == 1);
		nodeDisp.enableShow(type == 'account' || isEntity);
		nodeDisp.enableHide(true);
	},

	edgeClick: function(edge) {
		var nodeDisp = this.getNodeDisplay();
		var d = edge.data();
		if (d && d.attrs) {
			nodeDisp.showEdgeAttrs(edge);
		}
	},

	edgeRightClick: function(edge) {
		// do nothing
	}
});<|endoftext|>


<commit_before>// @flow
import { action, computed, observable } from 'mobx';
import { get } from 'lodash';
import Store from './lib/Store';
import { sidebarConfig } from '../config/sidebarConfig';
import { WalletSyncStateTags } from '../domains/Wallet';
import { formattedWalletAmount } from '../utils/formatters';
import type { SidebarWalletType } from '../types/sidebarTypes';

export default class SidebarStore extends Store {
  @observable CATEGORIES: Array<any> = sidebarConfig.CATEGORIES;
  @observable activeSidebarCategory: string = this.CATEGORIES[0].route;
  @observable isShowingSubMenus: boolean = true;

  setup() {
    const { sidebar: sidebarActions } = this.actions;

    sidebarActions.showSubMenus.listen(this._showSubMenus);
    sidebarActions.toggleSubMenus.listen(this._toggleSubMenus);
    sidebarActions.activateSidebarCategory.listen(
      this._onActivateSidebarCategory
    );
    sidebarActions.walletSelected.listen(this._onWalletSelected);

    this.registerReactions([this._syncSidebarRouteWithRouter]);
    this._configureCategories();
  }

  @computed get wallets(): Array<SidebarWalletType> {
    const { networkStatus, wallets } = this.stores;
    return wallets.all.map(w => ({
      id: w.id,
      title: w.name,
      info: formattedWalletAmount(w.amount),
      isConnected: networkStatus.isConnected,
      isRestoreActive:
        get(w, 'syncState.tag') === WalletSyncStateTags.RESTORING,
      restoreProgress: get(w, 'syncState.data.percentage.quantity', 0),
    }));
  }

  @action _configureCategories = () => {
    if (this.stores.networkStatus.environment.isDev) {
      this.CATEGORIES = sidebarConfig.CATEGORIES_WITH_STAKING;
    }
  };

  @action _onActivateSidebarCategory = (params: {
    category: string,
    showSubMenu?: boolean,
  }) => {
    const { category, showSubMenu } = params;
    if (category !== this.activeSidebarCategory) {
      this.activeSidebarCategory = category;
      if (showSubMenu != null) this.isShowingSubMenus = showSubMenu;
      this.actions.router.goToRoute.trigger({ route: category });
    } else if (showSubMenu == null || this.isShowingSubMenus !== showSubMenu) {
      // If no explicit preferred state is given -> toggle sub menus
      this._toggleSubMenus();
    } else {
      this.isShowingSubMenus = showSubMenu;
    }
  };

  @action _onWalletSelected = ({ walletId }: { walletId: string }) => {
    this.stores.wallets.goToWalletRoute(walletId);
  };

  @action _setActivateSidebarCategory = (category: string) => {
    this.activeSidebarCategory = category;
  };

  @action _resetActivateSidebarCategory = () => {
    this.activeSidebarCategory = '';
  };

  @action _showSubMenus = () => {
    this.isShowingSubMenus = true;
  };

  @action _hideSubMenus = () => {
    this.isShowingSubMenus = false;
  };

  @action _toggleSubMenus = () => {
    this.isShowingSubMenus = !this.isShowingSubMenus;
  };

  _syncSidebarRouteWithRouter = () => {
    const route = this.stores.app.currentRoute;
    this.CATEGORIES.forEach(category => {
      // If the current route starts with the root of the category
      if (route.indexOf(category.route) === 0)
        this._setActivateSidebarCategory(category.route);
    });
  };
}
<commit_msg>[DDW-649] Staking available for testnet<commit_after>// @flow
import { action, computed, observable } from 'mobx';
import { get } from 'lodash';
import Store from './lib/Store';
import { sidebarConfig } from '../config/sidebarConfig';
import { WalletSyncStateTags } from '../domains/Wallet';
import { formattedWalletAmount } from '../utils/formatters';
import type { SidebarWalletType } from '../types/sidebarTypes';

export default class SidebarStore extends Store {
  @observable CATEGORIES: Array<any> = sidebarConfig.CATEGORIES;
  @observable activeSidebarCategory: string = this.CATEGORIES[0].route;
  @observable isShowingSubMenus: boolean = true;

  setup() {
    const { sidebar: sidebarActions } = this.actions;

    sidebarActions.showSubMenus.listen(this._showSubMenus);
    sidebarActions.toggleSubMenus.listen(this._toggleSubMenus);
    sidebarActions.activateSidebarCategory.listen(
      this._onActivateSidebarCategory
    );
    sidebarActions.walletSelected.listen(this._onWalletSelected);

    this.registerReactions([this._syncSidebarRouteWithRouter]);
    this._configureCategories();
  }

  @computed get wallets(): Array<SidebarWalletType> {
    const { networkStatus, wallets } = this.stores;
    return wallets.all.map(w => ({
      id: w.id,
      title: w.name,
      info: formattedWalletAmount(w.amount),
      isConnected: networkStatus.isConnected,
      isRestoreActive:
        get(w, 'syncState.tag') === WalletSyncStateTags.RESTORING,
      restoreProgress: get(w, 'syncState.data.percentage.quantity', 0),
    }));
  }

  @action _configureCategories = () => {
    if (
      this.stores.networkStatus.environment.isDev ||
      this.stores.networkStatus.environment.isTestnet
    ) {
      this.CATEGORIES = sidebarConfig.CATEGORIES_WITH_STAKING;
    }
  };

  @action _onActivateSidebarCategory = (params: {
    category: string,
    showSubMenu?: boolean,
  }) => {
    const { category, showSubMenu } = params;
    if (category !== this.activeSidebarCategory) {
      this.activeSidebarCategory = category;
      if (showSubMenu != null) this.isShowingSubMenus = showSubMenu;
      this.actions.router.goToRoute.trigger({ route: category });
    } else if (showSubMenu == null || this.isShowingSubMenus !== showSubMenu) {
      // If no explicit preferred state is given -> toggle sub menus
      this._toggleSubMenus();
    } else {
      this.isShowingSubMenus = showSubMenu;
    }
  };

  @action _onWalletSelected = ({ walletId }: { walletId: string }) => {
    this.stores.wallets.goToWalletRoute(walletId);
  };

  @action _setActivateSidebarCategory = (category: string) => {
    this.activeSidebarCategory = category;
  };

  @action _resetActivateSidebarCategory = () => {
    this.activeSidebarCategory = '';
  };

  @action _showSubMenus = () => {
    this.isShowingSubMenus = true;
  };

  @action _hideSubMenus = () => {
    this.isShowingSubMenus = false;
  };

  @action _toggleSubMenus = () => {
    this.isShowingSubMenus = !this.isShowingSubMenus;
  };

  _syncSidebarRouteWithRouter = () => {
    const route = this.stores.app.currentRoute;
    this.CATEGORIES.forEach(category => {
      // If the current route starts with the root of the category
      if (route.indexOf(category.route) === 0)
        this._setActivateSidebarCategory(category.route);
    });
  };
}
<|endoftext|>


<commit_before>var CHEMICAL_ELEMENTS = {
  0: {'background-color': '#F1EEFA',
      'border-color': '#AAA',
      'color': '#F1EEFA',
      'name': 'n/a',
      'symbol': ''},

  1: {'background-color': '#dde',
      'border-color': '#888',
      'color': 'black',
      'name': 'hydrogen',
      'symbol': 'H'},

  2: {'background-color': '#edd',
      'border-color': '#888',
      'color': 'black',
      'name': 'helium',
      'symbol': 'He'},
};

var BLOCK_FONT = "12px Verdana";
var DROP_DELAY = 300;
var INPUT_DELAY = 200;

var GRID_HEIGHT = 20;
var GRID_WIDTH = 10;

var BLOCK_WIDTH, BLOCK_HEIGHT;
var BLOCK_SPACING_WIDTH, BLOCK_SPACING_HEIGHT;

var TETRINIMO_TEMPLATES = {
  jBlock: ['xx',
           'x ',
           'x '],
  lBlock: ['xx',
           ' x',
           ' x'],
  square: ['xx',
           'xx'],
  sBlock: ['x ',
           'xx',
           ' x'],
  zBlock: [' x',
           'xx',
           'x '],
  line:   ['x',
           'x',
           'x',
           'x'],
  tBlock: ['xxx',
           ' x ']
};
var processTetrinimos = function() {
  var tetraShape = new Object;
  for(var shape in TETRINIMO_TEMPLATES) {
    if( TETRINIMO_TEMPLATES.hasOwnProperty(shape)) {
      tetraShape[shape] = new Array;
      var currentShape = TETRINIMO_TEMPLATES[shape];
      for(var row in currentShape) {
        for(var col in currentShape[row]) {
          if(currentShape[row][col] == 'x') {
            tetraShape[shape].push({x: parseInt(col, 10),
                                    y: parseInt(row, 10)});
          }
        }
      }
    }
  }
  return tetraShape
}
var TETRINIMO_SHAPES = processTetrinimos();
<commit_msg>Add key codes constant to constants.js<commit_after>var CHEMICAL_ELEMENTS = {
  0: {'background-color': '#F1EEFA',
      'border-color': '#AAA',
      'color': '#F1EEFA',
      'name': 'n/a',
      'symbol': ''},

  1: {'background-color': '#dde',
      'border-color': '#888',
      'color': 'black',
      'name': 'hydrogen',
      'symbol': 'H'},

  2: {'background-color': '#edd',
      'border-color': '#888',
      'color': 'black',
      'name': 'helium',
      'symbol': 'He'},
};

var BLOCK_FONT = "12px Verdana";
var DROP_DELAY = 300;
var INPUT_DELAY = 200;

var GRID_HEIGHT = 20;
var GRID_WIDTH = 10;

var BLOCK_WIDTH, BLOCK_HEIGHT;
var BLOCK_SPACING_WIDTH, BLOCK_SPACING_HEIGHT;

var TETRINIMO_TEMPLATES = {
  jBlock: ['xx',
           'x ',
           'x '],
  lBlock: ['xx',
           ' x',
           ' x'],
  square: ['xx',
           'xx'],
  sBlock: ['x ',
           'xx',
           ' x'],
  zBlock: [' x',
           'xx',
           'x '],
  line:   ['x',
           'x',
           'x',
           'x'],
  tBlock: ['xxx',
           ' x ']
};
var processTetrinimos = function() {
  var tetraShape = new Object;
  for(var shape in TETRINIMO_TEMPLATES) {
    if( TETRINIMO_TEMPLATES.hasOwnProperty(shape)) {
      tetraShape[shape] = new Array;
      var currentShape = TETRINIMO_TEMPLATES[shape];
      for(var row in currentShape) {
        for(var col in currentShape[row]) {
          if(currentShape[row][col] == 'x') {
            tetraShape[shape].push({x: parseInt(col, 10),
                                    y: parseInt(row, 10)});
          }
        }
      }
    }
  }
  return tetraShape
}
var TETRINIMO_SHAPES = processTetrinimos();

var KEY_CODES = {
  37: 'left',
  38: 'up',
  39: 'right',
  40: 'down',
  8: 'backspace'
}
<|endoftext|>


<commit_before>'use strict';

/**
 * Requirements
 * @ignore
 */
const Route = require('./Route.js').Route;
const CliLogger = require('../../cli/CliLogger.js').CliLogger;
const UrlsConfiguration = require('../../model/configuration/UrlsConfiguration.js').UrlsConfiguration;
const Environment = require('../../nunjucks/Environment.js').Environment;
const assertParameter = require('../../utils/assert.js').assertParameter;
const co = require('co');
const fs = require('fs-extra');

/**
 * A route to server any entity related nunjucks files
 *
 * @memberOf server.routes
 */
class EntityTemplateRoute extends Route
{
    /**
     * @param {cli.CliLogger} cliLogger
     * @param {model.configuration.UrlsConfiguration} urlsConfiguration
     * @param {nunjucks.Environment} nunjucks
     */
    constructor(cliLogger, urlsConfiguration, nunjucks, options)
    {
        super(cliLogger);

        // Check params
        assertParameter(this, 'urlsConfiguration', urlsConfiguration, true, UrlsConfiguration);
        assertParameter(this, 'nunjucks', nunjucks, true, Environment);

        // Assign options
        const opts = options || {};
        this._basePath = opts.basePath || '';
        this._urlsConfiguration = urlsConfiguration;
        this._nunjucks = nunjucks;
    }

    /**
     * @inheritDocs
     */
    static get injections()
    {
        return { 'parameters': [CliLogger, UrlsConfiguration, Environment, 'server.route/EntityTemplateRoute.options'] };
    }

    /**
     * @inheritDocs
     */
    static get className()
    {
        return 'server.route/EntityTemplateRoute';
    }

    /**
     * @type {model.configuration.UrlsConfiguration}
     */
    get urlsConfiguration()
    {
        return this._urlsConfiguration;
    }

    /**
     * @type {nunjucks.Environment}
     */
    get nunjucks()
    {
        return this._nunjucks;
    }

    /**
     * @inheritDocs
     */
    handleEntityTemplate(request, response, next)
    {
        const scope = this;
        const promise = co(function *()
        {
            // Check extension
            if (!request.path.endsWith('.j2'))
            {
                next();
                return;
            }

            // Check file hit
            const data = yield scope.urlsConfiguration.matchEntityFile(request.path);
            if (!data || !data.file)
            {
                next();
                return;
            }

            // Render template
            const tpl = yield fs.readFile(data.file.filename, { encoding: 'utf8' });
            const work = scope.cliLogger.work('Serving url <' + request.url + '> using template <' + data.file.filename + '>');
            let html;
            try
            {
                const location =
                {
                    site: data.site,
                    entity: data.entity,
                    customPath: data.customPath
                };
                scope._nunjucks.addGlobal('location', location);
                scope._nunjucks.addGlobal('request', request);
                html = scope.nunjucks.renderString(tpl, data);
            }
            catch (e)
            {
                /* istanbul ignore next */
                scope.logger.error('handleEntityTemplate', e);
                /* istanbul ignore next */
                scope.cliLogger.error(scope.className + '::handleEntityTemplate', e);
                /* istanbul ignore next */
                next();
                return;
            }

            // Send
            response.send(html);
            scope.cliLogger.end(work);
        })
        .catch(function(error)
        {
            /* istanbul ignore next */
            scope.logger.error('handleEntityTemplate', error);
            /* istanbul ignore next */
            scope.cliLogger.error(scope.className + '::handleTemplate', error.stack);
        });
        return promise;
    }

    /**
     * @inheritDocs
     */
    register(express)
    {
        super.register(express);
        express.all('/:site/*', this.handleEntityTemplate.bind(this));
    }
}

/**
 * Exports
 * @ignore
 */
module.exports.EntityTemplateRoute = EntityTemplateRoute;
<commit_msg>refactor(server.route.EntityTemplateRoute): register returns a promise<commit_after>'use strict';

/**
 * Requirements
 * @ignore
 */
const Route = require('./Route.js').Route;
const CliLogger = require('../../cli/CliLogger.js').CliLogger;
const UrlsConfiguration = require('../../model/configuration/UrlsConfiguration.js').UrlsConfiguration;
const Environment = require('../../nunjucks/Environment.js').Environment;
const assertParameter = require('../../utils/assert.js').assertParameter;
const co = require('co');
const fs = require('fs-extra');

/**
 * A route to server any entity related nunjucks files
 *
 * @memberOf server.routes
 */
class EntityTemplateRoute extends Route
{
    /**
     * @param {cli.CliLogger} cliLogger
     * @param {model.configuration.UrlsConfiguration} urlsConfiguration
     * @param {nunjucks.Environment} nunjucks
     */
    constructor(cliLogger, urlsConfiguration, nunjucks, options)
    {
        super(cliLogger);

        // Check params
        assertParameter(this, 'urlsConfiguration', urlsConfiguration, true, UrlsConfiguration);
        assertParameter(this, 'nunjucks', nunjucks, true, Environment);

        // Assign options
        const opts = options || {};
        this._basePath = opts.basePath || '';
        this._urlsConfiguration = urlsConfiguration;
        this._nunjucks = nunjucks;
    }

    /**
     * @inheritDocs
     */
    static get injections()
    {
        return { 'parameters': [CliLogger, UrlsConfiguration, Environment, 'server.route/EntityTemplateRoute.options'] };
    }

    /**
     * @inheritDocs
     */
    static get className()
    {
        return 'server.route/EntityTemplateRoute';
    }

    /**
     * @type {model.configuration.UrlsConfiguration}
     */
    get urlsConfiguration()
    {
        return this._urlsConfiguration;
    }

    /**
     * @type {nunjucks.Environment}
     */
    get nunjucks()
    {
        return this._nunjucks;
    }

    /**
     * @inheritDocs
     */
    handleEntityTemplate(request, response, next)
    {
        const scope = this;
        const promise = co(function *()
        {
            // Check extension
            if (!request.path.endsWith('.j2'))
            {
                next();
                return;
            }

            // Check file hit
            const data = yield scope.urlsConfiguration.matchEntityFile(request.path);
            if (!data || !data.file)
            {
                next();
                return;
            }

            // Render template
            const tpl = yield fs.readFile(data.file.filename, { encoding: 'utf8' });
            const work = scope.cliLogger.work('Serving url <' + request.url + '> using template <' + data.file.filename + '>');
            let html;
            try
            {
                const location =
                {
                    site: data.site,
                    entity: data.entity,
                    customPath: data.customPath
                };
                scope._nunjucks.addGlobal('location', location);
                scope._nunjucks.addGlobal('request', request);
                html = scope.nunjucks.renderString(tpl, data);
            }
            catch (e)
            {
                /* istanbul ignore next */
                scope.logger.error('handleEntityTemplate', e);
                /* istanbul ignore next */
                scope.cliLogger.error(scope.className + '::handleEntityTemplate', e);
                /* istanbul ignore next */
                next();
                return;
            }

            // Send
            response.send(html);
            scope.cliLogger.end(work);
        })
        .catch(function(error)
        {
            /* istanbul ignore next */
            scope.logger.error('handleEntityTemplate', error);
            /* istanbul ignore next */
            scope.cliLogger.error(scope.className + '::handleTemplate', error.stack);
        });
        return promise;
    }

    /**
     * @inheritDocs
     */
    register(express)
    {
        const promise = super.register(express);
        promise.then(() =>
        {
            express.all('/:site/*', this.handleEntityTemplate.bind(this));
        });
        return promise;
    }
}

/**
 * Exports
 * @ignore
 */
module.exports.EntityTemplateRoute = EntityTemplateRoute;
<|endoftext|>


<commit_before>// @flow

import * as React from 'react'
import {Image, ScrollView, StyleSheet, Text, View, Platform} from 'react-native'
import noop from 'lodash/noop'
import {
	trackStreamPlay,
	trackStreamPause,
	trackStreamError,
} from '../../../analytics'
import * as c from '../../components/colors'
import {callPhone} from '../../components/call-phone'
import {Row} from '../../components/layout'
import type {TopLevelViewPropsType} from '../../types'
import {StreamPlayer} from './player'
import type {PlayState, HtmlAudioError} from './types'
import {ActionButton, ShowCalendarButton, CallButton} from './buttons'
import {openUrl} from '../../components/open-url'
import {Viewport} from '../../components/viewport'

type Props = TopLevelViewPropsType & {
	image: number,
	playerUrl: string,
	stationNumber: string,
	title: string,
	scheduleViewName: string,
	stationName: string,
	source: {
		useEmbeddedPlayer: boolean,
		embeddedPlayerUrl: string,
		streamSourceUrl: string,
	},
}

type State = {
	playState: PlayState,
	streamError: ?HtmlAudioError,
	uplinkError: ?string,
}

export class RadioControllerView extends React.PureComponent<Props, State> {
	state = {
		playState: 'paused',
		streamError: null,
		uplinkError: null,
	}

	play = () => {
		this.setState(() => ({playState: 'checking'}))
	}

	pause = () => {
		this.setState(() => ({playState: 'paused'}))
	}

	handleStreamPlay = () => {
		trackStreamPlay(this.props.stationName)
		this.setState(() => ({playState: 'playing'}))
	}

	handleStreamPause = () => {
		trackStreamPause(this.props.stationName)
		this.setState(() => ({playState: 'paused'}))
	}

	handleStreamEnd = () => {
		this.setState(() => ({playState: 'paused'}))
	}

	handleStreamError = (e: {code: number, message: string}) => {
		trackStreamError(this.props.stationName)
		this.setState(() => ({streamError: e, playState: 'paused'}))
	}

	openSchedule = () => {
		this.props.navigation.navigate(this.props.scheduleViewName)
	}

	callStation = () => {
		callPhone(this.props.stationNumber)
	}

	openStreamWebsite = () => {
		openUrl(this.props.playerUrl)
	}

	renderPlayButton = (state: PlayState) => {
		if (Platform.OS === 'android') {
			return (
				<ActionButton
					icon="ios-planet"
					onPress={this.openStreamWebsite}
					text="Open"
				/>
			)
		}

		switch (state) {
			case 'paused':
				return (
					<ActionButton icon="ios-play" onPress={this.play} text="Listen" />
				)

			case 'checking':
				return (
					<ActionButton icon="ios-more" onPress={this.pause} text="Starting" />
				)

			case 'playing':
				return (
					<ActionButton icon="ios-pause" onPress={this.pause} text="Pause" />
				)

			default:
				return <ActionButton icon="ios-bug" onPress={noop} text="Error" />
		}
	}

	render() {
		const error = this.state.uplinkError ? (
			<Text style={styles.status}>{this.state.uplinkError}</Text>
		) : this.state.streamError ? (
			<Text style={styles.status}>
				Error Code {this.state.streamError.code}:{' '}
				{this.state.streamError.message}
			</Text>
		) : null

		return (
			<Viewport
				render={viewport => {
					const sideways = viewport.width > viewport.height

					const logoWidth = Math.min(
						viewport.width / 1.5,
						viewport.height / 1.75,
					)

					const logoSize = {
						width: logoWidth,
						height: logoWidth,
					}

					return (
						<ScrollView
							contentContainerStyle={[styles.root, sideways && landscape.root]}
						>
							<View
								style={[styles.logoWrapper, sideways && landscape.logoWrapper]}
							>
								<Image
									resizeMode="contain"
									source={this.props.image}
									style={[styles.logo, logoSize]}
								/>
							</View>

							<View style={styles.container}>
								<View style={styles.titleWrapper}>
									<Text selectable={true} style={styles.heading}>
										{this.props.title}
									</Text>
									<Text selectable={true} style={styles.subHeading}>
										{this.props.stationName}
									</Text>

									{error}
								</View>

								<Row>
									{this.renderPlayButton(this.state.playState)}
									<View style={styles.spacer} />
									<CallButton onPress={this.callStation} />
									<View style={styles.spacer} />
									<ShowCalendarButton onPress={this.openSchedule} />
								</Row>

								{Platform.OS !== 'android' ? (
									<StreamPlayer
										embeddedPlayerUrl={this.props.source.embeddedPlayerUrl}
										onEnded={this.handleStreamEnd}
										// onWaiting={this.handleStreamWait}
										onError={this.handleStreamError}
										// onStalled={this.handleStreamStall}
										onPause={this.handleStreamPause}
										onPlay={this.handleStreamPlay}
										playState={this.state.playState}
										streamSourceUrl={this.props.source.streamSourceUrl}
										style={styles.webview}
										useEmbeddedPlayer={this.props.source.useEmbeddedPlayer}
									/>
								) : null}
							</View>
						</ScrollView>
					)
				}}
			/>
		)
	}
}

const styles = StyleSheet.create({
	root: {
		flexDirection: 'column',
		alignItems: 'stretch',
		justifyContent: 'space-between',
		padding: 20,
	},
	container: {
		alignItems: 'center',
		flex: 1,
		marginTop: 20,
		marginBottom: 20,
	},
	logoWrapper: {
		alignItems: 'center',
		justifyContent: 'center',
		flex: 1,
	},
	logo: {
		borderRadius: 6,
		borderColor: c.kstoSecondaryDark,
		borderWidth: 3,
	},
	titleWrapper: {
		alignItems: 'center',
		marginBottom: 20,
	},
	heading: {
		color: c.kstoPrimaryDark,
		fontWeight: '600',
		fontSize: 28,
		textAlign: 'center',
	},
	subHeading: {
		marginTop: 5,
		color: c.kstoPrimaryDark,
		fontWeight: '300',
		fontSize: 28,
		textAlign: 'center',
	},
	status: {
		fontWeight: '400',
		fontSize: 18,
		textAlign: 'center',
		color: c.grapefruit,
		marginTop: 15,
		marginBottom: 5,
	},
	webview: {
		display: 'none',
	},
	spacer: {
		width: 5,
	},
})

const landscape = StyleSheet.create({
	root: {
		flex: 1,
		padding: 20,
		flexDirection: 'row',
		alignItems: 'center',
	},
	logoWrapper: {
		flex: 0,
	},
})
<commit_msg>clean up Controller's render() a little bit<commit_after>// @flow

import * as React from 'react'
import {Image, ScrollView, StyleSheet, Text, View, Platform} from 'react-native'
import noop from 'lodash/noop'
import {
	trackStreamPlay,
	trackStreamPause,
	trackStreamError,
} from '../../../analytics'
import * as c from '../../components/colors'
import {callPhone} from '../../components/call-phone'
import {Row} from '../../components/layout'
import type {TopLevelViewPropsType} from '../../types'
import {StreamPlayer} from './player'
import type {PlayState, HtmlAudioError} from './types'
import {ActionButton, ShowCalendarButton, CallButton} from './buttons'
import {openUrl} from '../../components/open-url'
import {Viewport} from '../../components/viewport'

type Props = TopLevelViewPropsType & {
	image: number,
	playerUrl: string,
	stationNumber: string,
	title: string,
	scheduleViewName: string,
	stationName: string,
	source: {
		useEmbeddedPlayer: boolean,
		embeddedPlayerUrl: string,
		streamSourceUrl: string,
	},
}

type State = {
	playState: PlayState,
	streamError: ?HtmlAudioError,
	uplinkError: ?string,
}

export class RadioControllerView extends React.PureComponent<Props, State> {
	state = {
		playState: 'paused',
		streamError: null,
		uplinkError: null,
	}

	play = () => {
		this.setState(() => ({playState: 'checking'}))
	}

	pause = () => {
		this.setState(() => ({playState: 'paused'}))
	}

	handleStreamPlay = () => {
		trackStreamPlay(this.props.stationName)
		this.setState(() => ({playState: 'playing'}))
	}

	handleStreamPause = () => {
		trackStreamPause(this.props.stationName)
		this.setState(() => ({playState: 'paused'}))
	}

	handleStreamEnd = () => {
		this.setState(() => ({playState: 'paused'}))
	}

	handleStreamError = (e: {code: number, message: string}) => {
		trackStreamError(this.props.stationName)
		this.setState(() => ({streamError: e, playState: 'paused'}))
	}

	openSchedule = () => {
		this.props.navigation.navigate(this.props.scheduleViewName)
	}

	callStation = () => {
		callPhone(this.props.stationNumber)
	}

	openStreamWebsite = () => {
		openUrl(this.props.playerUrl)
	}

	renderPlayButton = (state: PlayState) => {
		if (Platform.OS === 'android') {
			return (
				<ActionButton
					icon="ios-planet"
					onPress={this.openStreamWebsite}
					text="Open"
				/>
			)
		}

		switch (state) {
			case 'paused':
				return (
					<ActionButton icon="ios-play" onPress={this.play} text="Listen" />
				)

			case 'checking':
				return (
					<ActionButton icon="ios-more" onPress={this.pause} text="Starting" />
				)

			case 'playing':
				return (
					<ActionButton icon="ios-pause" onPress={this.pause} text="Pause" />
				)

			default:
				return <ActionButton icon="ios-bug" onPress={noop} text="Error" />
		}
	}

	render() {
		const {source, title, stationName, image} = this.props
		const {uplinkError, streamError, playState} = this.state

		const error = uplinkError ? (
			<Text style={styles.status}>{uplinkError}</Text>
		) : streamError ? (
			<Text style={styles.status}>
				Error Code {streamError.code}: {streamError.message}
			</Text>
		) : null

		const titleBlock = (
			<View style={styles.titleWrapper}>
				<Text selectable={true} style={styles.heading}>
					{title}
				</Text>
				<Text selectable={true} style={styles.subHeading}>
					{stationName}
				</Text>

				{error}
			</View>
		)

		const controlsBlock = (
			<Row>
				{this.renderPlayButton(playState)}
				<View style={styles.spacer} />
				<CallButton onPress={this.callStation} />
				<View style={styles.spacer} />
				<ShowCalendarButton onPress={this.openSchedule} />
			</Row>
		)

		const playerBlock =
			Platform.OS !== 'android' ? (
				<StreamPlayer
					embeddedPlayerUrl={source.embeddedPlayerUrl}
					onEnded={this.handleStreamEnd}
					// onWaiting={this.handleStreamWait}
					onError={this.handleStreamError}
					// onStalled={this.handleStreamStall}
					onPause={this.handleStreamPause}
					onPlay={this.handleStreamPlay}
					playState={playState}
					streamSourceUrl={source.streamSourceUrl}
					style={styles.webview}
					useEmbeddedPlayer={source.useEmbeddedPlayer}
				/>
			) : null

		return (
			<Viewport
				render={({width, height}) => {
					const sideways = width > height

					const logoWidth = Math.min(width / 1.5, height / 1.75)
					const logoSize = {width: logoWidth, height: logoWidth}

					const root = [styles.root, sideways && landscape.root]
					const logo = [styles.logo, logoSize]
					const logoWrapper = [
						styles.logoWrapper,
						sideways && landscape.logoWrapper,
					]

					return (
						<ScrollView contentContainerStyle={root}>
							<View style={logoWrapper}>
								<Image resizeMode="contain" source={image} style={logo} />
							</View>

							<View style={styles.container}>
								{titleBlock}
								{controlsBlock}
								{playerBlock}
							</View>
						</ScrollView>
					)
				}}
			/>
		)
	}
}

const styles = StyleSheet.create({
	root: {
		flexDirection: 'column',
		alignItems: 'stretch',
		justifyContent: 'space-between',
		padding: 20,
	},
	container: {
		alignItems: 'center',
		flex: 1,
		marginTop: 20,
		marginBottom: 20,
	},
	logoWrapper: {
		alignItems: 'center',
		justifyContent: 'center',
		flex: 1,
	},
	logo: {
		borderRadius: 6,
		borderColor: c.kstoSecondaryDark,
		borderWidth: 3,
	},
	titleWrapper: {
		alignItems: 'center',
		marginBottom: 20,
	},
	heading: {
		color: c.kstoPrimaryDark,
		fontWeight: '600',
		fontSize: 28,
		textAlign: 'center',
	},
	subHeading: {
		marginTop: 5,
		color: c.kstoPrimaryDark,
		fontWeight: '300',
		fontSize: 28,
		textAlign: 'center',
	},
	status: {
		fontWeight: '400',
		fontSize: 18,
		textAlign: 'center',
		color: c.grapefruit,
		marginTop: 15,
		marginBottom: 5,
	},
	webview: {
		display: 'none',
	},
	spacer: {
		width: 5,
	},
})

const landscape = StyleSheet.create({
	root: {
		flex: 1,
		padding: 20,
		flexDirection: 'row',
		alignItems: 'center',
	},
	logoWrapper: {
		flex: 0,
	},
})
<|endoftext|>


<commit_before>'use strict'

const crypto = require('crypto');
const resolve = require('path').resolve;
const fs = require('fs');
const suffixTest = /\.json$/;
const root = resolve(process.argv[2] || '.') + '/';
const path = root + 'composition/';
const states = {};
const files = fs.readdirSync(path);
const rdf_syntax = 'http://www.w3.org/1999/02/22-rdf-syntax-ns#';
const hashids = {};
const entrypoints = {};
const temp_index = {};

function write (subject, predicate, object) {
    process.stdout.write(subject + ' <' + predicate + '> ' + object + ' .\n');
}

function getMethodIri (module, method) {

    // git+https://github.com/ + module + .git
    let owner = 'jillix';
    switch (module) {
        case 'flow-view':
        case 'flow-auth':
        case 'flow-streams':
        case 'flow-visualizer':
        case 'flow-compress':
            owner = 'adioo';
            break;
        case 'flow-keypress':
        case 'flow-ace':
            owner = 'danandrei';
            break;
    }

    // TODO method descriptor
    return '<' + owner + '/' + module + '?' + method + '>';
}

function parseHandler (state, handler) {
    handler = handler.split('/');
    let parsed = {
        type: handler[0][0]
    };
    handler[0] = handler[0].substr(1);

    if (handler.length > 1) {
        parsed.state = handler[0];
        parsed.path = handler[1];
    } else {
        parsed.state = state;
        parsed.path = handler[0]
    }

    if (parsed.type !== '>') {
        parsed.path = getMethodIri(states[parsed.state].module, parsed.path);
    } else {
        parsed.path = '_:' + crypto.createHash('md5').update(parsed.state + parsed.path).digest('hex');
    }

    return parsed;
}

function UID (len) {
    len = len || 23;
    for (var i = 0, random = ''; i < len; ++i) {
        random += '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'[0 | Math.random() * 62];
    }
    return random;
}

function getHash (string) {
    let hash = '_:' + crypto.createHash('md5').update(string).digest('hex');
    if (!hashids[hash]) {
        hashids[hash] = 1;
        write(hash, rdf_syntax + 'string', '"' + string.replace(/"/g, '\\"') + '"');
    }

    return hash;
}

// Convert composition files to triples
files.forEach(file => {

    if (!suffixTest.test(file)) {
        return;
    }

    try {
        let state = JSON.parse(fs.readFileSync(path + file));
        states[state.name] = state;
    } catch (error) {
        throw new Error(path + file + '\n' + error);
    }
});

for (let name in states) {
    let state = states[name];

    if (!state.flow) {
        continue;
    }

    for (let sequence in state.flow) {
        let sequence_id = '_:' + crypto.createHash('md5').update(state.name + sequence).digest('hex');

        // name
        write(
            sequence_id,
            'http://schema.org/name',
            getHash(state.name + '-' + sequence)
        );

        // type
        write(
            sequence_id,
            rdf_syntax + 'type',
            '<http://schema.jillix.net/vocab/Sequence>'
        );

        // roles
        if (state.roles) {
            for (let role in state.roles) {
                write(
                    sequence_id,
                    'http://schema.jillix.net/vocab/role',
                    getHash(role)
                );
            }
        }

        // end event
        if (state.flow[sequence].e) {
            write(
                sequence_id,
                'http://schema.jillix.net/vocab/onEnd',
                '_:' + crypto.createHash('md5').update(state.flow[sequence].e).digest('hex')
            );
        }

        // error event
        if (state.flow[sequence].r) {
            write(
                sequence_id,
                'http://schema.jillix.net/vocab/onError',
                '_:' + crypto.createHash('md5').update(state.flow[sequence].r).digest('hex')
            );
        }

        // TODO env vars
        if (entrypoints[sequence]) {
            // ..
        }

        // sequence
        if (state.flow[sequence].d) {
            let previous;
            state.flow[sequence].d.forEach((handler, index) => {

                let handler_id = '_:' + crypto.createHash('md5').update(UID(8)).digest('hex');
                let args;

                if (typeof handler === 'string') {
                    handler = handler;
                    args = null;
                } else {
                    args = handler[1];
                    handler = handler[0];
                }

                handler = parseHandler(state.name, handler);

                // type
                write(
                    handler_id,
                    rdf_syntax + 'type',
                    '<http://schema.jillix.net/vocab/Handler>'
                );

                // method args
                if (args) {
                    args = JSON.stringify(args);
                    let args_id = getHash(args);
                    write(
                        handler_id,
                        'http://schema.jillix.net/vocab/args',
                        args_id
                    );

                    // potential emits from args
                    let potential_emits = args.match(/\{FLOW\:([^\}]+)\}/g);
                    if (potential_emits) {
                        potential_emits.forEach(emit => {
                            emit = emit.slice(6, -1).replace('/', '');
                            emit = '_:' + crypto.createHash('md5').update(emit).digest('hex');
                            let triple = args_id + 'emit' + emit;
                            if (!temp_index[triple]) {
                                temp_index[triple] = 1;
                                write(
                                    args_id,
                                    'http://schema.jillix.net/vocab/emit',
                                    emit
                                );
                            }
                        });
                    }
                }

                // state
                write(
                    handler_id,
                    'http://schema.jillix.net/vocab/state',
                    getHash(handler.state)
                );

                switch (handler.type) {
                    case '.':
                    case ':':
                        // method (data)
                        write(
                            handler_id,
                            'http://schema.jillix.net/vocab/data',
                            handler.path
                        );
                        break;
                    case '*':
                        // method (stream)
                        write(
                            handler_id,
                            'http://schema.jillix.net/vocab/stream',
                            handler.path
                        );
                        break;

                    case '>':
                        // emit
                        write(
                            handler_id,
                            'http://schema.jillix.net/vocab/emit',
                            handler.path
                        );
                        break;
                }

                // next
                write(
                    index === 0 ? sequence_id : previous,
                    'http://schema.jillix.net/vocab/next',
                    handler_id
                );

                previous = handler_id;

                // link back to sequence (owner)
                write(
                    sequence_id,
                    'http://schema.jillix.net/vocab/handler',
                    handler_id
                );
            });
        }
    }
}
<commit_msg>replace args flow emits with sequence ids<commit_after>'use strict'

const crypto = require('crypto');
const resolve = require('path').resolve;
const fs = require('fs');
const suffixTest = /\.json$/;
const root = resolve(process.argv[2] || '.') + '/';
const path = root + 'composition/';
const states = {};
const files = fs.readdirSync(path);
const rdf_syntax = 'http://www.w3.org/1999/02/22-rdf-syntax-ns#';
const hashids = {};
const entrypoints = {};
const temp_index = {};

function write (subject, predicate, object) {
    process.stdout.write(subject + ' <' + predicate + '> ' + object + ' .\n');
}

function getMethodIri (module, method) {

    // git+https://github.com/ + module + .git
    let owner = 'jillix';
    switch (module) {
        case 'flow-view':
        case 'flow-auth':
        case 'flow-streams':
        case 'flow-visualizer':
        case 'flow-compress':
            owner = 'adioo';
            break;
        case 'flow-keypress':
        case 'flow-ace':
            owner = 'danandrei';
            break;
    }

    // TODO method descriptor
    return '<' + owner + '/' + module + '?' + method + '>';
}

function parseHandler (state, handler) {
    handler = handler.split('/');
    let parsed = {
        type: handler[0][0]
    };
    handler[0] = handler[0].substr(1);

    if (handler.length > 1) {
        parsed.state = handler[0];
        parsed.path = handler[1];
    } else {
        parsed.state = state;
        parsed.path = handler[0]
    }

    if (parsed.type !== '>') {
        parsed.path = getMethodIri(states[parsed.state].module, parsed.path);
    } else {
        parsed.path = '_:' + crypto.createHash('md5').update(parsed.state + parsed.path).digest('hex');
    }

    return parsed;
}

function UID (len) {
    len = len || 23;
    for (var i = 0, random = ''; i < len; ++i) {
        random += '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'[0 | Math.random() * 62];
    }
    return random;
}

function getHash (string) {
    let hash = '_:' + crypto.createHash('md5').update(string).digest('hex');
    if (!hashids[hash]) {
        hashids[hash] = 1;
        write(hash, rdf_syntax + 'string', '"' + string.replace(/"/g, '\\"') + '"');
    }

    return hash;
}

// Convert composition files to triples
files.forEach(file => {

    if (!suffixTest.test(file)) {
        return;
    }

    try {
        let state = JSON.parse(fs.readFileSync(path + file));
        states[state.name] = state;
    } catch (error) {
        throw new Error(path + file + '\n' + error);
    }
});

for (let name in states) {
    let state = states[name];

    if (!state.flow) {
        continue;
    }

    for (let sequence in state.flow) {
        let sequence_id = '_:' + crypto.createHash('md5').update(state.name + sequence).digest('hex');

        // name
        write(
            sequence_id,
            'http://schema.org/name',
            getHash(state.name + '-' + sequence)
        );

        // type
        write(
            sequence_id,
            rdf_syntax + 'type',
            '<http://schema.jillix.net/vocab/Sequence>'
        );

        // roles
        if (state.roles) {
            for (let role in state.roles) {
                write(
                    sequence_id,
                    'http://schema.jillix.net/vocab/role',
                    getHash(role)
                );
            }
        }

        // end event
        if (state.flow[sequence].e) {
            write(
                sequence_id,
                'http://schema.jillix.net/vocab/onEnd',
                '_:' + crypto.createHash('md5').update(state.flow[sequence].e).digest('hex')
            );
        }

        // error event
        if (state.flow[sequence].r) {
            write(
                sequence_id,
                'http://schema.jillix.net/vocab/onError',
                '_:' + crypto.createHash('md5').update(state.flow[sequence].r).digest('hex')
            );
        }

        // TODO env vars
        if (entrypoints[sequence]) {
            // ..
        }

        // sequence
        if (state.flow[sequence].d) {
            let previous;
            state.flow[sequence].d.forEach((handler, index) => {

                let handler_id = '_:' + crypto.createHash('md5').update(UID(8)).digest('hex');
                let args;

                if (typeof handler === 'string') {
                    handler = handler;
                    args = null;
                } else {
                    args = handler[1];
                    handler = handler[0];
                }

                handler = parseHandler(state.name, handler);

                // type
                write(
                    handler_id,
                    rdf_syntax + 'type',
                    '<http://schema.jillix.net/vocab/Handler>'
                );

                // method args
                if (args) {
                    args = JSON.stringify(args);

                    // potential emits from args
                    let potential_emits = args.match(/\{FLOW\:([^\}]+)\}/g);
                    let emits = [];
                    if (potential_emits) {
                        potential_emits.forEach(emit => {
                            let replace = emit;
                            emit = emit.slice(6, -1).replace('/', '');
                            emit = '_:' + crypto.createHash('md5').update(emit).digest('hex');
                            args = args.replace(replace, emit);
                            emits.push(emit);
                        });
                    }

                    let args_id = getHash(args);
                    write(
                        handler_id,
                        'http://schema.jillix.net/vocab/args',
                        args_id
                    );

                    emits.forEach(emit => {
                        let key = args_id + emit;
                        if (!temp_index[key]) {
                            temp_index[key] = 1;
                            write(
                                args_id,
                                'http://schema.jillix.net/vocab/emit',
                                emit
                            );
                        }
                    });
                }

                // state
                write(
                    handler_id,
                    'http://schema.jillix.net/vocab/state',
                    getHash(handler.state)
                );

                switch (handler.type) {
                    case '.':
                    case ':':
                        // method (data)
                        write(
                            handler_id,
                            'http://schema.jillix.net/vocab/data',
                            handler.path
                        );
                        break;
                    case '*':
                        // method (stream)
                        write(
                            handler_id,
                            'http://schema.jillix.net/vocab/stream',
                            handler.path
                        );
                        break;

                    case '>':
                        // emit
                        write(
                            handler_id,
                            'http://schema.jillix.net/vocab/emit',
                            handler.path
                        );
                        break;
                }

                // next
                write(
                    index === 0 ? sequence_id : previous,
                    'http://schema.jillix.net/vocab/next',
                    handler_id
                );

                previous = handler_id;

                // link back to sequence (owner)
                write(
                    sequence_id,
                    'http://schema.jillix.net/vocab/handler',
                    handler_id
                );
            });
        }
    }
}
<|endoftext|>


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

/**
 * WordPress dependencies
 */
import { Fragment, useCallback, useState } from '@wordpress/element';
import { __, sprintf } from '@wordpress/i18n';

/**
 * Internal dependencies
 */
import Data from 'googlesitekit-data';
import BannerNotification from '../../../../../components/notifications/BannerNotification';
import {
	PropertySelect,
	UseSnippetSwitch,
} from '../../../../analytics-4/components/common';
import ErrorNotice from '../../../../../components/ErrorNotice';
import SpinnerButton from '../../../../../components/SpinnerButton';
import { MODULES_ANALYTICS_4 } from '../../../datastore/constants';
import { CORE_USER } from '../../../../../googlesitekit/datastore/user/constants';
import useExistingTagEffect from '../../../../analytics-4/hooks/useExistingTagEffect';
import { getBannerDismissalExpiryTime } from '../../../utils/banner-dismissal-expiry';
const { useDispatch, useSelect } = Data;

export default function SetupBanner( {} ) {
	const [ errorNotice, setErrorNotice ] = useState( null );

	const ga4MeasurementID = useSelect( ( select ) =>
		select( MODULES_ANALYTICS_4 ).getMeasurementID()
	);
	const existingTag = useSelect( ( select ) =>
		select( MODULES_ANALYTICS_4 ).getExistingTag()
	);

	useExistingTagEffect();

	const referenceDateString = useSelect( ( select ) =>
		select( CORE_USER ).getReferenceDate()
	);

	const { submitChanges } = useDispatch( MODULES_ANALYTICS_4 );

	const handleSubmitChanges = useCallback( async () => {
		const { error } = await submitChanges();

		if ( error ) {
			setErrorNotice( error );
		}
	}, [ submitChanges ] );

	let title;
	let ctaLabel;
	let footer;
	let children;

	if ( ga4MeasurementID ) {
		title = __(
			'Connect the Google Analytics 4 property that’s associated with your existing Universal Analytics property',
			'google-site-kit'
		);
		ctaLabel = __( 'Connect', 'google-site-kit' );
		footer = __(
			'You can always add/edit this in the Site Kit Settings.',
			'google-site-kit'
		);
		children = (

				<PropertySelect
					label={ __(
						'Google Analytics 4 Property',
						'google-site-kit'
					) }
				/>
				{ existingTag && (
					<UseSnippetSwitch
						description={
							<Fragment>

									{ sprintf(
										/* translators: %s: existing tag ID */
										__(
											'A tag %s for the selected property already exists on the site.',
											'google-site-kit'
										),
										existingTag
									) }

									{ __(
										'Make sure you remove it if you decide to place the same GA4 tag via Site Kit, otherwise they will be duplicated.',
										'google-site-kit'
									) }

							</Fragment>
						}
					/>
				) }

		);
	} else if ( existingTag ) {
		title = __(
			'No existing Google Analytics 4 property found, Site Kit will help you create a new one and insert it on your site',
			'google-site-kit'
		);
		ctaLabel = __( 'Create property', 'google-site-kit' );
		footer = sprintf(
			/* translators: %s: The existing tag ID. */
			__(
				'A GA4 tag %s is found on this site but this property is not associated with your Google Analytics account. You can always add/edit this in the Site Kit Settings.',
				'google-site-kit'
			),
			existingTag
		);
	} else {
		title = __(
			'No existing Google Analytics 4 property found, Site Kit will help you create a new one and insert it on your site',
			'google-site-kit'
		);
		ctaLabel = __( 'Create property', 'google-site-kit' );
		footer = __(
			'You can always add/edit this in the Site Kit Settings.',
			'google-site-kit'
		);
	}

	return (
		<BannerNotification
			id="ga4-activation-banner"
			className="googlesitekit-ga4-setup-banner"
			title={ title }
			ctaComponent={
				! ga4MeasurementID && (
					<SpinnerButton onClick={ handleSubmitChanges }>
						{ ctaLabel }
					</SpinnerButton>
				)
			}
			footer={
{ footer }
 }
			dismiss={ __( 'Cancel', 'google-site-kit' ) }
			dismissExpires={ getBannerDismissalExpiryTime(
				referenceDateString
			) }
		>
			{ errorNotice && <ErrorNotice error={ errorNotice } /> }
			{ children }
		</BannerNotification>
	);
}
<commit_msg>Set the GA4 propertyID to PROPERTY_CREATE for no existing GA4 property.<commit_after>/**
 * SetupBanner component.
 *
 * Site Kit by Google, Copyright 2022 Google LLC
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

/**
 * WordPress dependencies
 */
import { Fragment, useCallback, useState } from '@wordpress/element';
import { __, sprintf } from '@wordpress/i18n';

/**
 * Internal dependencies
 */
import Data from 'googlesitekit-data';
import BannerNotification from '../../../../../components/notifications/BannerNotification';
import {
	PropertySelect,
	UseSnippetSwitch,
} from '../../../../analytics-4/components/common';
import ErrorNotice from '../../../../../components/ErrorNotice';
import SpinnerButton from '../../../../../components/SpinnerButton';
import {
	MODULES_ANALYTICS_4,
	PROPERTY_CREATE,
} from '../../../datastore/constants';
import { CORE_USER } from '../../../../../googlesitekit/datastore/user/constants';
import useExistingTagEffect from '../../../../analytics-4/hooks/useExistingTagEffect';
import { getBannerDismissalExpiryTime } from '../../../utils/banner-dismissal-expiry';
const { useDispatch, useSelect } = Data;

export default function SetupBanner( {} ) {
	const [ errorNotice, setErrorNotice ] = useState( null );

	const ga4MeasurementID = useSelect( ( select ) =>
		select( MODULES_ANALYTICS_4 ).getMeasurementID()
	);
	const existingTag = useSelect( ( select ) =>
		select( MODULES_ANALYTICS_4 ).getExistingTag()
	);

	useExistingTagEffect();

	const referenceDateString = useSelect( ( select ) =>
		select( CORE_USER ).getReferenceDate()
	);

	const { submitChanges, selectProperty } =
		useDispatch( MODULES_ANALYTICS_4 );

	const handleSubmitChanges = useCallback( async () => {
		const { error } = await submitChanges();

		if ( error ) {
			setErrorNotice( error );
		}
	}, [ submitChanges ] );

	let title;
	let ctaLabel;
	let footer;
	let children;

	if ( ga4MeasurementID ) {
		title = __(
			'Connect the Google Analytics 4 property that’s associated with your existing Universal Analytics property',
			'google-site-kit'
		);
		ctaLabel = __( 'Connect', 'google-site-kit' );
		footer = __(
			'You can always add/edit this in the Site Kit Settings.',
			'google-site-kit'
		);
		children = (

				<PropertySelect
					label={ __(
						'Google Analytics 4 Property',
						'google-site-kit'
					) }
				/>
				{ existingTag && (
					<UseSnippetSwitch
						description={
							<Fragment>

									{ sprintf(
										/* translators: %s: existing tag ID */
										__(
											'A tag %s for the selected property already exists on the site.',
											'google-site-kit'
										),
										existingTag
									) }

									{ __(
										'Make sure you remove it if you decide to place the same GA4 tag via Site Kit, otherwise they will be duplicated.',
										'google-site-kit'
									) }

							</Fragment>
						}
					/>
				) }

		);
	} else {
		selectProperty( PROPERTY_CREATE );

		if ( existingTag ) {
			title = __(
				'No existing Google Analytics 4 property found, Site Kit will help you create a new one and insert it on your site',
				'google-site-kit'
			);
			ctaLabel = __( 'Create property', 'google-site-kit' );
			footer = sprintf(
				/* translators: %s: The existing tag ID. */
				__(
					'A GA4 tag %s is found on this site but this property is not associated with your Google Analytics account. You can always add/edit this in the Site Kit Settings.',
					'google-site-kit'
				),
				existingTag
			);
		} else {
			title = __(
				'No existing Google Analytics 4 property found, Site Kit will help you create a new one and insert it on your site',
				'google-site-kit'
			);
			ctaLabel = __( 'Create property', 'google-site-kit' );
			footer = __(
				'You can always add/edit this in the Site Kit Settings.',
				'google-site-kit'
			);
		}
	}

	return (
		<BannerNotification
			id="ga4-activation-banner"
			className="googlesitekit-ga4-setup-banner"
			title={ title }
			ctaComponent={
				! ga4MeasurementID && (
					<SpinnerButton onClick={ handleSubmitChanges }>
						{ ctaLabel }
					</SpinnerButton>
				)
			}
			footer={
{ footer }
 }
			dismiss={ __( 'Cancel', 'google-site-kit' ) }
			dismissExpires={ getBannerDismissalExpiryTime(
				referenceDateString
			) }
		>
			{ errorNotice && <ErrorNotice error={ errorNotice } /> }
			{ children }
		</BannerNotification>
	);
}
<|endoftext|>


<commit_before>'use strict';

var R_NAME = /^[_a-z]\w*(?:\.[_a-z]\w*)*$/i;

var FistError = /** @type FistError */ require('./fist-error');

var _ = require('lodash-node');
var f = require('util').format;
var hasProperty = Object.prototype.hasOwnProperty;
var init = require('./init');
var logging = require('loggin');
var path = require('path');
var vow = require('vow');
var vowFs = require('vow-fs');

/**
 * @class Core
 * @param {Object} [params]
 * */
function Core(params) {
    params = this._createParams(params);

    /**
     * @public
     * @memberOf {Core}
     * @property
     * @type {Object}
     * */
    this.params = params;

    /**
     * @public
     * @memberOf {Core}
     * @property
     * @type {Logger}
     * */
    this.logger = logging.getLogger(params.name).conf(params.logging);

    /**
     * @protected
     * @memberOf {Core}
     * @property
     * @type {Object}
     * */
    this._class = {};

    /**
     * @protected
     * @memberOf {Core}
     * @property
     * @type {Array}
     * */
    this._decls = [];

    /**
     * @protected
     * @memberOf {Core}
     * @property
     * @type {Object}
     * */
    this._units = {};

    /**
     * @protected
     * @memberOf {Core}
     * @property
     * @type {Array}
     * */
    this._plugs = [];

    /**
     * @protected
     * @memberOf {Core}
     * @property
     * @type {Array}
     * */
    this._installed = {};

    init(this);
}

/**
 * @private
 * @memberOf {Core}
 * @method
 *
 * @constructs
 * */
Core.prototype.constructor = Core;

/**
 * @public
 * @memberOf {Core}
 * @method
 *
 * @param {Object|String} aliasFrom
 * @param {String} [aliasTo]
 *
 * @returns {Core}
 * */
Core.prototype.alias = function (aliasFrom, aliasTo) {
    if (_.isObject(aliasFrom)) {
        _.forOwn(aliasFrom, function (name, base) {
            this.unit({
                base: base,
                name: name
            });
        }, this);

        return this;
    }

    this.unit({
        base: aliasFrom,
        name: aliasTo
    });

    return this;
};

/**
 * @public
 * @memberOf {Core}
 * @method
 *
 * @param {Object} members
 * @param {Object} [statics]
 *
 * @returns {Core}
 * */
Core.prototype.unit = function (members, statics) {
    var name = members.name;

    if (!(_.has(members, 'name') && _.isString(name) && R_NAME.test(name))) {
        throw new FistError(FistError.BAD_UNIT, f('Unit name %j should be identifier (%(source)s)', name, R_NAME));
    }

    members = Object(members);
    statics = Object(statics);

    this._decls.push({
        members: members,
        statics: statics
    });

    return this;
};

/**
 * @public
 * @memberOf {Core}
 * @method
 *
 * @param {String} name
 *
 * @returns {Unit}
 * */
Core.prototype.getUnit = function (name) {
    return this._units[name];
};

/**
 * @public
 * @memberOf {Core}
 * @method
 *
 * @param {Object} track
 * @param {String} name
 * @param {Object} args
 * @param {Function} done
 * */
Core.prototype.callUnit = function (track, name, args, done) {
    if (hasProperty.call(this._units, name)) {
        track.eject(this._units[name], args, done);
    } else {
        done(new FistError(FistError.NO_SUCH_UNIT, f('Can not call unknown unit %j', name)));
    }
};

/**
 * @public
 * @memberOf {Core}
 * @method
 *
 * @param {*} name
 *
 * @returns {Object}
 * */
Core.prototype.getUnitClass = function (name) {
    return this._class[name];
};

/**
 * @public
 * @memberOf {Core}
 * @method
 *
 * @returns {vow.Promise}
 * */
Core.prototype.ready = function () {
    if (this.__readyPromise) {
        //  already initialized
        return this.__readyPromise;
    }

    this.logger.debug('Pending...');

    this.__readyPromise = this._getReady();

    /** @this {Core} */
    this.__readyPromise.done(function () {
        this.logger.note('Ready.');
    }, function (err) {
        this.logger.fatal('Failed to start application', err);
    }, this);

    return this.__readyPromise;
};

/**
 * @public
 * @memberOf {Core}
 * @method
 *
 * @param {*} plugin
 *
 * @returns {Core}
 * */
Core.prototype.plugin = function (plugin) {
    this._plugs.push(plugin);
    return this;
};

/**
 * @public
 * @memberOf {Core}
 * @method
 *
 * @param {String} moduleName
 * @param {*} [settings]
 *
 * @returns {Core}
 * */
Core.prototype.install = function (moduleName, settings) {

    try {
        //  is module
        moduleName = require.resolve(moduleName);

        this.plugin(createInstaller(moduleName, settings));

    } catch (err) {
        this.plugin(function (agent) {
            var opts = {silent: true, cwd: agent.params.root};
            return vowFs.glob(moduleName, opts).then(function (paths) {
                _.forEach(paths, function (fileName) {
                    agent.plugin(createInstaller(fileName, settings));
                });
            });
        });
    }

    return this;
};

/**
 * @protected
 * @memberOf {Core}
 * @method
 *
 * @param {*} [params]
 *
 * @returns {Object}
 * */
Core.prototype._createParams = function (params) {
    var parent = module;

    while (parent.parent) {
        parent = parent.parent;
    }

    params = _.extend({
        root: path.dirname(parent.filename),
        implicitBase: 0
    }, params);

    params.unitSettings = Object(params.unitSettings);

    _.forOwn(params.unitSettings, function (settings, unitName) {
        params.unitSettings[unitName] = _.extend({}, settings);
    });

    return params;
};

/**
 * @protected
 * @memberOf {Core}
 * @method
 *
 * @returns {vow.Promise}
 * */
Core.prototype._getReady = function () {
    return this._installPlugin(function () {}).then(createUnits, this);
};

/**
 * @protected
 * @memberOf {Core}
 * @method
 *
 * @param {Function} plug
 *
 * @returns {vow.Promise}
 * */
Core.prototype._installPlugin = function (plug) {
    return callPlugin.call(this, plug).then(function () {
        //  install children plugins
        var plugs = this._plugs;
        this._plugs = [];
        return _.reduce(plugs, function (promise, childPlug) {
            return promise.then(function () {
                return this._installPlugin(childPlug);
            }, this);
        }, vow.resolve(), this);
    }, this);

};

function createInstaller(moduleName, settings) {
    return function (agent) {
        if (_.has(agent._installed, moduleName)) {
            agent.logger.debug('The plugin %s has already installed, skipping', moduleName);
            return;
        }

        agent._installed[moduleName] = true;

        if (settings) {
            agent.logger.debug('Installing plugin %s(%j)', moduleName, settings);
            agent.plugin(require(moduleName)(settings));
        } else {
            agent.logger.debug('Installing plugin %s', moduleName);
            agent.plugin(require(moduleName));
        }
    };
}

function callPlugin(func) {
    if (!_.isFunction(func)) {
        //  is not a function, just resolve
        return vow.resolve(func);
    }

    if (func.length < 2) {
        //  synchronous plugin
        return vow.invoke(func, this);
    }

    //  asynchronous plugin
    return vow.invoke(function (self) {
        var defer = vow.defer();

        func(self, function done(err) {
            if (!arguments.length) {
                //  done();
                defer.resolve();
            } else {
                //  done(err);
                defer.reject(err);
            }
        });

        return defer.promise();
    }, this);
}

function createUnits() {
    Object.freeze(this._decls);
    return _.reduce(this._decls, function (promise, decl) {
        return promise.then(function () {
            return createUnitClass.call(this, decl);
        }, this);
    }, vow.resolve(), this).then(function () {
        Object.freeze(this._class);
        _.forOwn(this._class, function (UnitClass) {
            var name = UnitClass.prototype.name;

            if (/^[a-z]/i.test(name)) {
                this._units[name] = new UnitClass(this.params.unitSettings[name]);
            }

        }, this);
        Object.freeze(this._units);
    }, this);
}

function createUnitClass(decl) {
    var members = decl.members;
    var statics = decl.statics;
    var name = members.name;
    var base;
    var promise;

    if (_.has(this._class, name)) {
        //  Was already created
        return vow.resolve(this._class[name]);
    }

    base = members.base;

    //  Looking for base
    if (base === void 0) {
        base = this.params.implicitBase;
        this.logger.debug('The base for unit "%s" is implicitly defined as "%s"', name, base);
    }

    if (base === this.Unit.prototype.name) {
        promise = vow.invoke(function (self) {
            return self.Unit.inherit(members, statics);
        }, this);
    } else {
        promise = _.find(this._decls, {members: {name: base}});

        if (!promise) {
            return vow.reject(new FistError(FistError.NO_SUCH_UNIT,
                f('No base found for unit "%s" ("%s")', name, base)));
        }

        promise = createUnitClass.call(this, promise).then(function (Base) {
            return Base.inherit(members, statics);
        });
    }

    return promise.then(function (UnitClass) {
        this._class[name] = UnitClass;
        return UnitClass;
    }, this);
}

module.exports = Core;
<commit_msg>Refactor cloning unitSettings<commit_after>'use strict';

var R_NAME = /^[_a-z]\w*(?:\.[_a-z]\w*)*$/i;

var FistError = /** @type FistError */ require('./fist-error');

var _ = require('lodash-node');
var f = require('util').format;
var hasProperty = Object.prototype.hasOwnProperty;
var init = require('./init');
var logging = require('loggin');
var path = require('path');
var vow = require('vow');
var vowFs = require('vow-fs');

/**
 * @class Core
 * @param {Object} [params]
 * */
function Core(params) {
    params = this._createParams(params);

    /**
     * @public
     * @memberOf {Core}
     * @property
     * @type {Object}
     * */
    this.params = params;

    /**
     * @public
     * @memberOf {Core}
     * @property
     * @type {Logger}
     * */
    this.logger = logging.getLogger(params.name).conf(params.logging);

    /**
     * @protected
     * @memberOf {Core}
     * @property
     * @type {Object}
     * */
    this._class = {};

    /**
     * @protected
     * @memberOf {Core}
     * @property
     * @type {Array}
     * */
    this._decls = [];

    /**
     * @protected
     * @memberOf {Core}
     * @property
     * @type {Object}
     * */
    this._units = {};

    /**
     * @protected
     * @memberOf {Core}
     * @property
     * @type {Array}
     * */
    this._plugs = [];

    /**
     * @protected
     * @memberOf {Core}
     * @property
     * @type {Array}
     * */
    this._installed = {};

    init(this);
}

/**
 * @private
 * @memberOf {Core}
 * @method
 *
 * @constructs
 * */
Core.prototype.constructor = Core;

/**
 * @public
 * @memberOf {Core}
 * @method
 *
 * @param {Object|String} aliasFrom
 * @param {String} [aliasTo]
 *
 * @returns {Core}
 * */
Core.prototype.alias = function (aliasFrom, aliasTo) {
    if (_.isObject(aliasFrom)) {
        _.forOwn(aliasFrom, function (name, base) {
            this.unit({
                base: base,
                name: name
            });
        }, this);

        return this;
    }

    this.unit({
        base: aliasFrom,
        name: aliasTo
    });

    return this;
};

/**
 * @public
 * @memberOf {Core}
 * @method
 *
 * @param {Object} members
 * @param {Object} [statics]
 *
 * @returns {Core}
 * */
Core.prototype.unit = function (members, statics) {
    var name = members.name;

    if (!(_.has(members, 'name') && _.isString(name) && R_NAME.test(name))) {
        throw new FistError(FistError.BAD_UNIT, f('Unit name %j should be identifier (%(source)s)', name, R_NAME));
    }

    members = Object(members);
    statics = Object(statics);

    this._decls.push({
        members: members,
        statics: statics
    });

    return this;
};

/**
 * @public
 * @memberOf {Core}
 * @method
 *
 * @param {String} name
 *
 * @returns {Unit}
 * */
Core.prototype.getUnit = function (name) {
    return this._units[name];
};

/**
 * @public
 * @memberOf {Core}
 * @method
 *
 * @param {Object} track
 * @param {String} name
 * @param {Object} args
 * @param {Function} done
 * */
Core.prototype.callUnit = function (track, name, args, done) {
    if (hasProperty.call(this._units, name)) {
        track.eject(this._units[name], args, done);
    } else {
        done(new FistError(FistError.NO_SUCH_UNIT, f('Can not call unknown unit %j', name)));
    }
};

/**
 * @public
 * @memberOf {Core}
 * @method
 *
 * @param {*} name
 *
 * @returns {Object}
 * */
Core.prototype.getUnitClass = function (name) {
    return this._class[name];
};

/**
 * @public
 * @memberOf {Core}
 * @method
 *
 * @returns {vow.Promise}
 * */
Core.prototype.ready = function () {
    if (this.__readyPromise) {
        //  already initialized
        return this.__readyPromise;
    }

    this.logger.debug('Pending...');

    this.__readyPromise = this._getReady();

    /** @this {Core} */
    this.__readyPromise.done(function () {
        this.logger.note('Ready.');
    }, function (err) {
        this.logger.fatal('Failed to start application', err);
    }, this);

    return this.__readyPromise;
};

/**
 * @public
 * @memberOf {Core}
 * @method
 *
 * @param {*} plugin
 *
 * @returns {Core}
 * */
Core.prototype.plugin = function (plugin) {
    this._plugs.push(plugin);
    return this;
};

/**
 * @public
 * @memberOf {Core}
 * @method
 *
 * @param {String} moduleName
 * @param {*} [settings]
 *
 * @returns {Core}
 * */
Core.prototype.install = function (moduleName, settings) {

    try {
        //  is module
        moduleName = require.resolve(moduleName);

        this.plugin(createInstaller(moduleName, settings));

    } catch (err) {
        this.plugin(function (agent) {
            var opts = {silent: true, cwd: agent.params.root};
            return vowFs.glob(moduleName, opts).then(function (paths) {
                _.forEach(paths, function (fileName) {
                    agent.plugin(createInstaller(fileName, settings));
                });
            });
        });
    }

    return this;
};

/**
 * @protected
 * @memberOf {Core}
 * @method
 *
 * @param {*} [params]
 *
 * @returns {Object}
 * */
Core.prototype._createParams = function (params) {
    var parent = module;

    while (parent.parent) {
        parent = parent.parent;
    }

    params = _.extend({
        root: path.dirname(parent.filename),
        implicitBase: 0
    }, params);

    params.unitSettings = _.mapValues(params.unitSettings, function (settings) {
        return _.clone(settings);
    });

    return params;
};

/**
 * @protected
 * @memberOf {Core}
 * @method
 *
 * @returns {vow.Promise}
 * */
Core.prototype._getReady = function () {
    return this._installPlugin(function () {}).then(createUnits, this);
};

/**
 * @protected
 * @memberOf {Core}
 * @method
 *
 * @param {Function} plug
 *
 * @returns {vow.Promise}
 * */
Core.prototype._installPlugin = function (plug) {
    return callPlugin.call(this, plug).then(function () {
        //  install children plugins
        var plugs = this._plugs;
        this._plugs = [];
        return _.reduce(plugs, function (promise, childPlug) {
            return promise.then(function () {
                return this._installPlugin(childPlug);
            }, this);
        }, vow.resolve(), this);
    }, this);

};

function createInstaller(moduleName, settings) {
    return function (agent) {
        if (_.has(agent._installed, moduleName)) {
            agent.logger.debug('The plugin %s has already installed, skipping', moduleName);
            return;
        }

        agent._installed[moduleName] = true;

        if (settings) {
            agent.logger.debug('Installing plugin %s(%j)', moduleName, settings);
            agent.plugin(require(moduleName)(settings));
        } else {
            agent.logger.debug('Installing plugin %s', moduleName);
            agent.plugin(require(moduleName));
        }
    };
}

function callPlugin(func) {
    if (!_.isFunction(func)) {
        //  is not a function, just resolve
        return vow.resolve(func);
    }

    if (func.length < 2) {
        //  synchronous plugin
        return vow.invoke(func, this);
    }

    //  asynchronous plugin
    return vow.invoke(function (self) {
        var defer = vow.defer();

        func(self, function done(err) {
            if (!arguments.length) {
                //  done();
                defer.resolve();
            } else {
                //  done(err);
                defer.reject(err);
            }
        });

        return defer.promise();
    }, this);
}

function createUnits() {
    Object.freeze(this._decls);
    return _.reduce(this._decls, function (promise, decl) {
        return promise.then(function () {
            return createUnitClass.call(this, decl);
        }, this);
    }, vow.resolve(), this).then(function () {
        Object.freeze(this._class);
        _.forOwn(this._class, function (UnitClass) {
            var name = UnitClass.prototype.name;

            if (/^[a-z]/i.test(name)) {
                this._units[name] = new UnitClass(this.params.unitSettings[name]);
            }

        }, this);
        Object.freeze(this._units);
    }, this);
}

function createUnitClass(decl) {
    var members = decl.members;
    var statics = decl.statics;
    var name = members.name;
    var base;
    var promise;

    if (_.has(this._class, name)) {
        //  Was already created
        return vow.resolve(this._class[name]);
    }

    base = members.base;

    //  Looking for base
    if (base === void 0) {
        base = this.params.implicitBase;
        this.logger.debug('The base for unit "%s" is implicitly defined as "%s"', name, base);
    }

    if (base === this.Unit.prototype.name) {
        promise = vow.invoke(function (self) {
            return self.Unit.inherit(members, statics);
        }, this);
    } else {
        promise = _.find(this._decls, {members: {name: base}});

        if (!promise) {
            return vow.reject(new FistError(FistError.NO_SUCH_UNIT,
                f('No base found for unit "%s" ("%s")', name, base)));
        }

        promise = createUnitClass.call(this, promise).then(function (Base) {
            return Base.inherit(members, statics);
        });
    }

    return promise.then(function (UnitClass) {
        this._class[name] = UnitClass;
        return UnitClass;
    }, this);
}

module.exports = Core;
<|endoftext|>