<commit_before>(function(fm, $, ng, undefined) {

	fm.app.factory("fmMapSearch", function($rootScope, $templateCache, $compile, fmUtils, L, $timeout, $q, fmMapSearchRoute) {
		return function(map) {
			var iconSuffix = ".n.32.png";

			var scope = $rootScope.$new(true);
			scope.searchString = "";
			scope.loadedSearchString = "";
			scope.searchResults = null;
			scope.showAll = false;
			scope.activeResult = null;

			scope.search = function(noZoom) {
				scope.searchResults = null;
				scope.loadedSearchString = "";
				clearRenders();

				if(scope.searchString.trim() != "") {
					if(scope.searchString.match(/ to /)) {
						scope.showRoutingForm();
						return routeUi.submit();
					}

					var lonlat = fmUtils.decodeLonLatUrl(scope.searchString);
					if(lonlat)
						return map.map.flyTo([ lonlat.lat, lonlat.lon ], lonlat.zoom);

					var q = scope.searchString;
					map.socket.emit("find", { query: scope.searchString, loadUrls: true }).then(function(results) {
						if(fmUtils.isSearchId(q) && results.length > 0 && results[0].display_name)
							scope.searchString = q = results[0].display_name;

						scope.loadedSearchString = q;

						map.mapEvents.$emit("searchchange");

						if(typeof results == "string")
							loadSearchResults(parseFiles([ results ]), noZoom);
						else
							loadSearchResults(results, noZoom);
					}).catch(function(err) {
						map.messages.showMessage("danger", err);
					});
				}
			};

			scope.showResult = function(result, noZoom) {
				if(scope.showAll) {
					if(!noZoom)
						_flyToBounds(layerGroup.getBounds());

					result.marker ? result.marker.openPopup() : result.layer.openPopup();
				} else {
					clearRenders();
					renderResult(scope.loadedSearchString, scope.searchResults, result, true, layerGroup);

					if(!noZoom) {
						if(result.lat && result.lon && result.zoom)
							map.map.flyTo([ result.lat, result.lon ], result.zoom);
						else if(result.boundingbox)
							_flyToBounds(L.latLngBounds([ [ result.boundingbox[0], result.boundingbox[3 ] ], [ result.boundingbox[1], result.boundingbox[2] ] ]));
						else if(result.layer)
							_flyToBounds(result.layer.getBounds());
					}
				}

				map.mapEvents.$emit("searchchange");
			};

			scope.showAllResults = function(noZoom) {
				clearRenders();

				for(var i=0; i<scope.searchResults.length; i++)
					renderResult(scope.loadedSearchString, scope.searchResults, scope.searchResults[i], false, layerGroup);

				if(!noZoom)
					_flyToBounds(layerGroup.getBounds());

				map.mapEvents.$emit("searchchange");
			};

			scope.showRoutingForm = function() {
				searchUi.hide();
				routeUi.show();

				if(scope.searchString.match(/ to /))
					routeUi.setQueries(fmUtils.splitRouteQuery(scope.searchString));
				else if(scope.loadedSearchString == scope.searchString)
					routeUi.setFrom(scope.searchString, scope.searchResults, scope.activeResult);
				else
					routeUi.setFrom(scope.searchString);
			};

			scope.$watch("showAll", function() {
				if(!scope.searchResults)
					return;

				if(scope.showAll)
					scope.showAllResults();
				 else if(scope.searchResults.length > 0)
					scope.showResult(scope.activeResult || scope.searchResults[0]);
			});

			var el = $($templateCache.get("map/search/search.html")).insertAfter(map.map.getContainer());
			$compile(el)(scope);
			scope.$evalAsync(); // $compile only replaces variables on next digest

			var clickMarker = L.featureGroup([]).addTo(map.map);
			clickMarker.on("popupclose", function() {
				clickMarker.clearLayers();
			});

			map.mapEvents.$on("longclick", function(e, latlng) {
				clickMarker.clearLayers();

				map.socket.emit("find", { query: "geo:" + latlng.lat + "," + latlng.lng + "?z=" + map.map.getZoom(), loadUrls: false }).then(function(results) {
					clickMarker.clearLayers();

					if(results.length > 0)
						renderResult(fmUtils.round(latlng.lat, 5) + "," + fmUtils.round(latlng.lng, 5), results, results[0], true, clickMarker);
				}).catch(function(err) {
					map.messages.showMessage("danger", err);
				});
			});

			var layerGroup = L.featureGroup([]).addTo(map.map);

			function _flyToBounds(bounds) {
				map.map.flyTo(bounds.getCenter(), Math.min(15, map.map.getBoundsZoom(bounds)));
			}

			function loadSearchResults(results, noZoom) {
				clearRenders();

				scope.searchResults = results;

				if(results && results.length > 0)
					scope.showAll ? scope.showAllResults(noZoom) : scope.showResult(scope.searchResults[0], noZoom);
			}

			function parseFiles(files) {
				var ret = [ ];
				var errors = false;
				files.forEach(function(file) {
					var geojson = null;

					if(file.match(/^\s*</)) {
						var doc = $.parseXML(file);
						var xml = $(doc).find(":root");

						if(xml.is("gpx"))
							geojson = toGeoJSON.gpx(xml[0]);
						else if(xml.is("kml"))
							geojson = toGeoJSON.kml(xml[0]);
						else if(xml.is("osm"))
							geojson = osmtogeojson(doc);
					} else if(body.match(/^\s*\{/)) {
						var content = JSON.parse(body);
						if(content.type)
							return geojson = content;
					}

					if(geojson == null)
						return errors = true;

					var features;
					if(geojson.type == "FeatureCollection")
						features = geojson.features || [ ];
					else if(geojson.type == "Feature")
						features = [ geojson ];
					else
						features = [ { type: "Feature", geometry: geojson, properties: { } } ];

					features.forEach(function(feature) {
						var name;

						if(typeof feature.properties != "object")
							feature.properties = { };

						if(feature.properties.name)
							name = feature.properties.name;
						else if(feature.properties.tags.name)
							name = feature.properties.tags.name;
						else if(feature.properties.type)
							name = feature.properties.type + " " + feature.properties.id;
						else if([ "Polygon", "MultiPolygon" ].indexOf(feature.geometry.type) != -1)
							name = "Polygon";
						else if([ "LineString", "MultiLineString" ].indexOf(feature.geometry.type) != -1)
							name = "Line";
						else if([ "Point", "MultiPoint" ].indexOf(feature.geometry.type) != -1)
							name = "Point";
						else
							name = feature.geometry.type || "Object";

						ret.push({
							short_name: name,
							display_name: name,
							extratags: feature.properties.tags || _filterAdditionalTags(feature.properties),
							geojson: feature.geometry,
							type: feature.properties.type || feature.geometry.type
						});
					});
				});

				if(errors)
					return map.messages.showMessage("danger", "Some files could not be parsed.");

				return ret;
			}

			function _filterAdditionalTags(tags) {
				var ret = { };
				for(var i in tags) {
					if(typeof tags[i] == "string" || typeof tags[i] == "number")
						ret[i] = tags[i];
				}
				return ret;
			}

			function _lineStringToTrackPoints(geometry) {
				var ret = [ ];
				var coords = (geometry.type == "MultiLineString" ? geometry.coordinates : [ geometry.coordinates ]);
				coords.forEach(function(linePart) {
					linePart.forEach(function(latlng) {
						ret.push({ lat: latlng[1], lon: latlng[0] });
					});
				});
				return ret;
			}

			function renderResult(query, results, result, showPopup, layerGroup) {
				if(!result.lat || !result.lon || (result.geojson && result.geojson.type != "Point")) { // If the geojson is just a point, we already render our own marker
					result.layer = L.geoJson(result.geojson, {
						pointToLayer: function(geoJsonPoint, latlng) {
						    return L.marker(latlng, {
						    	icon: fmUtils.createMarkerIcon("ff0000")
						    });
						}
					})
					.bindPopup($("<div/>")[0], map.popupOptions)
					.on("popupopen", function(e) {
						scope.activeResult = result;
						renderResultPopup(query, results, result, e.popup);
					})
					.on("popupclose", function(e) {
						ng.element(e.popup.getContent()).scope().$destroy();
					})
					.bindTooltip(result.display_name, $.extend({}, map.tooltipOptions, { sticky: true, offset: [ 20, 0 ] }));

					layerGroup.addLayer(result.layer);
				}

				if(result.lat != null && result.lon != null) {
					result.marker = L.marker([ result.lat, result.lon ], {
						icon: L.icon({
							iconUrl: result.icon.replace(/\.[a-z]+\.[0-9]+\.png$/, iconSuffix),
							iconSize: [ 32, 32 ],
							iconAnchor: [ 16, 16 ],
							popupAnchor: [ 0, -16 ]
						})
					})
						.bindPopup($("<div/>")[0], map.popupOptions)
						.on("popupopen", function(e) {
							scope.activeResult = result;
							renderResultPopup(query, results, result, e.popup);
						})
						.on("popupclose", function(e) {
							ng.element(e.popup.getContent()).scope().$destroy();
						})
						.bindTooltip(result.display_name, $.extend({}, map.tooltipOptions, { offset: [ 20, 0 ] }));

					layerGroup.addLayer(result.marker);
				}

				if(showPopup) {
					if(result.marker)
						result.marker.openPopup();
					else if(result.layer)
						result.layer.openPopup();
				}
			}

			function clearRenders() {
				layerGroup.clearLayers();
			}

			function renderResultPopup(query, results, result, popup) {
				var scope = map.socket.$new();

				scope.result = result;

				if((result.lat != null && result.lon != null) || result.geojson && result.geojson.type == "Point")
					scope.type = "marker";
				else if([ "LineString", "MultiLineString" ].indexOf(result.geojson && result.geojson.type) != -1)
					scope.type = "line";

				scope.addToMap = function(type) {
					if(type == null) {
						for(var i in map.socket.types) {
							if(map.socket.types[i].type == scope.type) {
								type = map.socket.types[i];
								break;
							}
						}
					}

					if(scope.type == "marker")
						map.markersUi.createMarker(result.lat != null && result.lon != null ? result : { lat: result.geojson.coordinates[1], lon: result.geojson.coordinates[0] }, type, { name: result.display_name });
					else if(scope.type == "line") {
						var trackPoints = _lineStringToTrackPoints(result.geojson);
						map.linesUi.createLine(type, [ trackPoints[0], trackPoints[trackPoints.length-1] ], { trackPoints: trackPoints, mode: "track" });
					}
				};

				scope.useForRoute = function(mode) {
					searchUi.hide();
					routeUi.show();

					if(mode == 1)
						routeUi.setFrom(query, results, result);
					else if(mode == 2)
						routeUi.addVia(query, results, result);
					else if(mode == 3)
						routeUi.setTo(query, results, result);

					routeUi.submit();

					popup.closePopup();
				};

				var el = popup.getContent();
				$(el).html($templateCache.get("map/search/result-popup.html"));
				$compile(el)(scope);

				// Prevent popup close on button click
				$("button", el).click(function(e) {
					e.preventDefault();
				});

				$timeout(function() { $timeout(function() { // $compile only replaces variables on next digest
					popup.update();
				}); });
			}

			var searchUi = {
				show: function() {
					el.show();

					if(scope.searchResults) {
						if(scope.showAll)
							scope.showAllResults();
						 else if(scope.searchResults.length > 0)
							scope.showResult(scope.activeResult || scope.searchResults[0]);
					}
				},

				hide: function() {
					clearRenders();
					el.hide();
				},

				search: function(query, noZoom, showAll) {
					if(query != null)
						scope.searchString = query;

					if(showAll != null)
						scope.showAll = showAll;

					scope.search(noZoom);
				},

				showFiles: function(files) {
					loadSearchResults(parseFiles(files));
				},

				route: function(destinations, mode, noZoom) {
					searchUi.hide();
					routeUi.show();

					routeUi.setQueries(destinations);
					if(mode)
						routeUi.setMode(mode);

					routeUi.submit(noZoom);
				},

				getCurrentSearchForHash: function() {
					if(el.is(":visible")) {
						if(!scope.showAll && scope.activeResult && scope.activeResult.id)
							return [ scope.activeResult.id ];
						else if(scope.loadedSearchString)
							return [ scope.loadedSearchString ];
					} else {
						var queries = routeUi.getQueries();
						if(queries)
							return queries.concat([ routeUi.getMode() ]);
					}
				}
			};

			var routeUi = fmMapSearchRoute(map, searchUi);

			return searchUi;
		};
	});

})(FacilMap, jQuery, angular);<commit_msg>Fix location hash behaviour of search form<commit_after>(function(fm, $, ng, undefined) {

	fm.app.factory("fmMapSearch", function($rootScope, $templateCache, $compile, fmUtils, L, $timeout, $q, fmMapSearchRoute) {
		return function(map) {
			var iconSuffix = ".n.32.png";

			var scope = $rootScope.$new(true);
			scope.searchString = "";
			scope.loadedSearchString = "";
			scope.searchResults = null;
			scope.showAll = false;
			scope.activeResult = null;

			scope.search = function(noZoom) {
				scope.searchResults = null;
				scope.activeResult = null;
				scope.loadedSearchString = "";
				clearRenders();

				if(scope.searchString.trim() != "") {
					if(scope.searchString.match(/ to /)) {
						scope.showRoutingForm();
						return routeUi.submit();
					}

					var lonlat = fmUtils.decodeLonLatUrl(scope.searchString);
					if(lonlat)
						return map.map.flyTo([ lonlat.lat, lonlat.lon ], lonlat.zoom);

					var q = scope.searchString;
					map.socket.emit("find", { query: scope.searchString, loadUrls: true }).then(function(results) {
						if(fmUtils.isSearchId(q) && results.length > 0 && results[0].display_name)
							scope.searchString = q = results[0].display_name;

						scope.loadedSearchString = q;

						map.mapEvents.$emit("searchchange");

						if(typeof results == "string")
							loadSearchResults(parseFiles([ results ]), noZoom);
						else
							loadSearchResults(results, noZoom);
					}).catch(function(err) {
						map.messages.showMessage("danger", err);
					});
				} else
					map.mapEvents.$emit("searchchange");
			};

			scope.showResult = function(result, noZoom) {
				if(scope.showAll && scope.searchResults.length > 1) {
					if(!noZoom)
						_flyToBounds(layerGroup.getBounds());

					result.marker ? result.marker.openPopup() : result.layer.openPopup();
				} else {
					clearRenders();
					renderResult(scope.loadedSearchString, scope.searchResults, result, true, layerGroup, noZoom);

					if(!noZoom) {
						if(result.lat && result.lon && result.zoom)
							map.map.flyTo([ result.lat, result.lon ], result.zoom);
						else if(result.boundingbox)
							_flyToBounds(L.latLngBounds([ [ result.boundingbox[0], result.boundingbox[3 ] ], [ result.boundingbox[1], result.boundingbox[2] ] ]));
						else if(result.layer)
							_flyToBounds(result.layer.getBounds());
					}
				}

				map.mapEvents.$emit("searchchange");
			};

			scope.showAllResults = function(noZoom) {
				clearRenders();

				for(var i=0; i<scope.searchResults.length; i++)
					renderResult(scope.loadedSearchString, scope.searchResults, scope.searchResults[i], false, layerGroup);

				if(!noZoom)
					_flyToBounds(layerGroup.getBounds());

				map.mapEvents.$emit("searchchange");
			};

			scope.showRoutingForm = function() {
				searchUi.hide();
				routeUi.show();

				if(scope.searchString.match(/ to /))
					routeUi.setQueries(fmUtils.splitRouteQuery(scope.searchString));
				else if(scope.loadedSearchString == scope.searchString)
					routeUi.setFrom(scope.searchString, scope.searchResults, scope.activeResult);
				else
					routeUi.setFrom(scope.searchString);
			};

			scope.$watch("showAll", function() {
				if(!scope.searchResults)
					return;

				if(scope.showAll)
					scope.showAllResults();
				 else if(scope.searchResults.length > 0)
					scope.showResult(scope.activeResult || scope.searchResults[0]);
			});

			var el = $($templateCache.get("map/search/search.html")).insertAfter(map.map.getContainer());
			$compile(el)(scope);
			scope.$evalAsync(); // $compile only replaces variables on next digest

			var clickMarker = L.featureGroup([]).addTo(map.map);
			clickMarker.on("popupclose", function() {
				clickMarker.clearLayers();
			});

			map.mapEvents.$on("longclick", function(e, latlng) {
				clickMarker.clearLayers();

				map.socket.emit("find", { query: "geo:" + latlng.lat + "," + latlng.lng + "?z=" + map.map.getZoom(), loadUrls: false }).then(function(results) {
					clickMarker.clearLayers();

					if(results.length > 0)
						renderResult(fmUtils.round(latlng.lat, 5) + "," + fmUtils.round(latlng.lng, 5), results, results[0], true, clickMarker);
				}).catch(function(err) {
					map.messages.showMessage("danger", err);
				});
			});

			var layerGroup = L.featureGroup([]).addTo(map.map);

			function _flyToBounds(bounds) {
				map.map.flyTo(bounds.getCenter(), Math.min(15, map.map.getBoundsZoom(bounds)));
			}

			function loadSearchResults(results, noZoom) {
				clearRenders();

				scope.searchResults = results;

				if(results && results.length > 0)
					(scope.showAll && results.length > 1) ? scope.showAllResults(noZoom) : scope.showResult(scope.searchResults[0], noZoom);
			}

			function parseFiles(files) {
				var ret = [ ];
				var errors = false;
				files.forEach(function(file) {
					var geojson = null;

					if(file.match(/^\s*</)) {
						var doc = $.parseXML(file);
						var xml = $(doc).find(":root");

						if(xml.is("gpx"))
							geojson = toGeoJSON.gpx(xml[0]);
						else if(xml.is("kml"))
							geojson = toGeoJSON.kml(xml[0]);
						else if(xml.is("osm"))
							geojson = osmtogeojson(doc);
					} else if(body.match(/^\s*\{/)) {
						var content = JSON.parse(body);
						if(content.type)
							return geojson = content;
					}

					if(geojson == null)
						return errors = true;

					var features;
					if(geojson.type == "FeatureCollection")
						features = geojson.features || [ ];
					else if(geojson.type == "Feature")
						features = [ geojson ];
					else
						features = [ { type: "Feature", geometry: geojson, properties: { } } ];

					features.forEach(function(feature) {
						var name;

						if(typeof feature.properties != "object")
							feature.properties = { };

						if(feature.properties.name)
							name = feature.properties.name;
						else if(feature.properties.tags.name)
							name = feature.properties.tags.name;
						else if(feature.properties.type)
							name = feature.properties.type + " " + feature.properties.id;
						else if([ "Polygon", "MultiPolygon" ].indexOf(feature.geometry.type) != -1)
							name = "Polygon";
						else if([ "LineString", "MultiLineString" ].indexOf(feature.geometry.type) != -1)
							name = "Line";
						else if([ "Point", "MultiPoint" ].indexOf(feature.geometry.type) != -1)
							name = "Point";
						else
							name = feature.geometry.type || "Object";

						ret.push({
							short_name: name,
							display_name: name,
							extratags: feature.properties.tags || _filterAdditionalTags(feature.properties),
							geojson: feature.geometry,
							type: feature.properties.type || feature.geometry.type
						});
					});
				});

				if(errors)
					return map.messages.showMessage("danger", "Some files could not be parsed.");

				return ret;
			}

			function _filterAdditionalTags(tags) {
				var ret = { };
				for(var i in tags) {
					if(typeof tags[i] == "string" || typeof tags[i] == "number")
						ret[i] = tags[i];
				}
				return ret;
			}

			function _lineStringToTrackPoints(geometry) {
				var ret = [ ];
				var coords = (geometry.type == "MultiLineString" ? geometry.coordinates : [ geometry.coordinates ]);
				coords.forEach(function(linePart) {
					linePart.forEach(function(latlng) {
						ret.push({ lat: latlng[1], lon: latlng[0] });
					});
				});
				return ret;
			}

			function renderResult(query, results, result, showPopup, layerGroup, noZoom) {
				if(!result.lat || !result.lon || (result.geojson && result.geojson.type != "Point")) { // If the geojson is just a point, we already render our own marker
					result.layer = L.geoJson(result.geojson, {
						pointToLayer: function(geoJsonPoint, latlng) {
						    return L.marker(latlng, {
						    	icon: fmUtils.createMarkerIcon("ff0000")
						    });
						}
					})
					.bindPopup($("<div/>")[0], map.popupOptions)
					.on("popupopen", function(e) {
						scope.activeResult = result;
						renderResultPopup(query, results, result, e.popup);
					})
					.on("popupclose", function(e) {
						ng.element(e.popup.getContent()).scope().$destroy();
					})
					.bindTooltip(result.display_name, $.extend({}, map.tooltipOptions, { sticky: true, offset: [ 20, 0 ] }));

					layerGroup.addLayer(result.layer);
				}

				if(result.lat != null && result.lon != null) {
					result.marker = L.marker([ result.lat, result.lon ], {
						icon: L.icon({
							iconUrl: result.icon.replace(/\.[a-z]+\.[0-9]+\.png$/, iconSuffix),
							iconSize: [ 32, 32 ],
							iconAnchor: [ 16, 16 ],
							popupAnchor: [ 0, -16 ]
						})
					})
						.bindPopup($("<div/>")[0], map.popupOptions)
						.on("popupopen", function(e) {
							scope.activeResult = result;
							renderResultPopup(query, results, result, e.popup);
						})
						.on("popupclose", function(e) {
							ng.element(e.popup.getContent()).scope().$destroy();
						})
						.bindTooltip(result.display_name, $.extend({}, map.tooltipOptions, { offset: [ 20, 0 ] }));

					layerGroup.addLayer(result.marker);
				}

				if(showPopup) {
					var popupLayer = result.marker || result.layer;
					if(popupLayer) {
						if(noZoom)
							popupLayer._popup.options.autoPan = false;
						popupLayer.openPopup();
					}
				}
			}

			function clearRenders() {
				layerGroup.clearLayers();
			}

			function renderResultPopup(query, results, result, popup) {
				var scope = map.socket.$new();

				scope.result = result;

				if((result.lat != null && result.lon != null) || result.geojson && result.geojson.type == "Point")
					scope.type = "marker";
				else if([ "LineString", "MultiLineString" ].indexOf(result.geojson && result.geojson.type) != -1)
					scope.type = "line";

				scope.addToMap = function(type) {
					if(type == null) {
						for(var i in map.socket.types) {
							if(map.socket.types[i].type == scope.type) {
								type = map.socket.types[i];
								break;
							}
						}
					}

					if(scope.type == "marker")
						map.markersUi.createMarker(result.lat != null && result.lon != null ? result : { lat: result.geojson.coordinates[1], lon: result.geojson.coordinates[0] }, type, { name: result.display_name });
					else if(scope.type == "line") {
						var trackPoints = _lineStringToTrackPoints(result.geojson);
						map.linesUi.createLine(type, [ trackPoints[0], trackPoints[trackPoints.length-1] ], { trackPoints: trackPoints, mode: "track" });
					}
				};

				scope.useForRoute = function(mode) {
					searchUi.hide();
					routeUi.show();

					if(mode == 1)
						routeUi.setFrom(query, results, result);
					else if(mode == 2)
						routeUi.addVia(query, results, result);
					else if(mode == 3)
						routeUi.setTo(query, results, result);

					routeUi.submit();

					popup.closePopup();
				};

				var el = popup.getContent();
				$(el).html($templateCache.get("map/search/result-popup.html"));
				$compile(el)(scope);

				// Prevent popup close on button click
				$("button", el).click(function(e) {
					e.preventDefault();
				});

				$timeout(function() { $timeout(function() { // $compile only replaces variables on next digest
					popup.update();

					// Might have been set to false in renderResult() if noZoom
					popup.options.autoPan = true;
				}); });
			}

			var searchUi = {
				show: function() {
					el.show();

					if(scope.searchResults) {
						if(scope.showAll && scope.searchResults.length > 1)
							scope.showAllResults();
						 else if(scope.searchResults.length > 0)
							scope.showResult(scope.activeResult || scope.searchResults[0]);
					}
				},

				hide: function() {
					clearRenders();
					el.hide();
				},

				search: function(query, noZoom, showAll) {
					if(query != null)
						scope.searchString = query;

					if(showAll != null)
						scope.showAll = showAll;

					scope.search(noZoom);
				},

				showFiles: function(files) {
					loadSearchResults(parseFiles(files));
				},

				route: function(destinations, mode, noZoom) {
					searchUi.hide();
					routeUi.show();

					routeUi.setQueries(destinations);
					if(mode)
						routeUi.setMode(mode);

					routeUi.submit(noZoom);
				},

				getCurrentSearchForHash: function() {
					if(el.is(":visible")) {
						if(((scope.searchResults && scope.searchResults.length == 1) || !scope.showAll) && scope.activeResult && scope.activeResult.id)
							return [ scope.activeResult.id ];
						else if(scope.loadedSearchString)
							return [ scope.loadedSearchString ];
					} else {
						var queries = routeUi.getQueries();
						if(queries)
							return queries.concat([ routeUi.getMode() ]);
					}
				}
			};

			var routeUi = fmMapSearchRoute(map, searchUi);

			return searchUi;
		};
	});

})(FacilMap, jQuery, angular);<|endoftext|>


<commit_before>'use strict';
const getUrlTemplate = require('./get-url-template');
const extend = require('./tiny-extend');

function ensureTrailingSlash(url) {
  return (url.charAt(url.length-1) === '/') ? url : (url + '/');
}

/**
 * Creates, evaluates based on context, and returns a string URL for a Mozu API request.
 * @param  {Object} context The context of a client. Should have a `baseUrl` property at minimum.
 * @param  {string} tpt     A string to be compiled into a UriTemplate. Should be a valid UriTemplate.
 * @param  {Object} body      An object consisting of the JSON body of the request, to be used to interpolate URL paramters.
 * @return {string}         A fully qualified URL.
 */
module.exports = function makeUrl(client, tpt, body) {
  let context = client.context;
  let template = getUrlTemplate(tpt);
  let fullTptEvalCtx = extend(
    // aliases for pod URLs and IDs first
    {
      homePod: context.baseUrl,
      pciPod: context.basePciUrl,
      tenantId: context.tenant,
      siteId: context.site,
      catalogId: context.catalog,
      masterCatalogId: context['master-catalog']
    },
    // all context values override those base values if provided
    context,
    // any matching values in the body override last.
    body
  );

  // ensure all base URLs have trailing slashes.
  ['baseUrl','basePciUrl','tenantPod'].forEach(x => {
    if (fullTptEvalCtx[x])
      fullTptEvalCtx[x] = ensureTrailingSlash(fullTptEvalCtx[x]);
  });

  // don't pass the API version!
  if (!body || !body.hasOwnProperty("version")) delete fullTptEvalCtx.version;

  return template.render(fullTptEvalCtx);
};
<commit_msg>fix url trailing slash bug<commit_after>'use strict';
const getUrlTemplate = require('./get-url-template');
const extend = require('./tiny-extend');

function ensureTrailingSlash(url) {
  return (url.charAt(url.length-1) === '/') ? url : (url + '/');
}

/**
 * Creates, evaluates based on context, and returns a string URL for a Mozu API request.
 * @param  {Object} context The context of a client. Should have a `baseUrl` property at minimum.
 * @param  {string} tpt     A string to be compiled into a UriTemplate. Should be a valid UriTemplate.
 * @param  {Object} body      An object consisting of the JSON body of the request, to be used to interpolate URL paramters.
 * @return {string}         A fully qualified URL.
 */
module.exports = function makeUrl(client, tpt, body) {
  let context = client.context;
  let template = getUrlTemplate(tpt);
  let fullTptEvalCtx = extend(
    // aliases for pod URLs and IDs first
    {
      homePod: context.baseUrl,
      pciPod: context.basePciUrl,
      tenantId: context.tenant,
      siteId: context.site,
      catalogId: context.catalog,
      masterCatalogId: context['master-catalog']
    },
    // all context values override those base values if provided
    context,
    // any matching values in the body override last.
    body
  );

  // ensure all base URLs have trailing slashes.
  ['homePod','pciPod','tenantPod'].forEach(x => {
    if (fullTptEvalCtx[x])
      fullTptEvalCtx[x] = ensureTrailingSlash(fullTptEvalCtx[x]);
  });

  // don't pass the API version!
  if (!body || !body.hasOwnProperty("version")) delete fullTptEvalCtx.version;

  return template.render(fullTptEvalCtx);
};
<|endoftext|>


<commit_before>(function($) {
    jQuery.fn.lazyload = function(pattern, options) {
        var settings = {
            threshold: 0,
            scrollThreshold: 300,
            placeholder: 'loading...',
            checkInterval: 2000
        };

        if (options) {
            $.extend(settings, options);
        }

        var container = this;
        container.data('lazyload:lastCheckedScrollTop', -10000);

        function aboveViewport(container, element, threshold) {
            return $(container).offset().top >= $(element).offset().top + $(element).height() + threshold;
        }

        function belowViewport(container, element, threshold) {
            return $(container).offset().top + $(container).height() + threshold <= $(element).offset().top;
        }

        function checkScroll() {
            if (Math.abs(container.scrollTop() - container.data('lazyload:lastCheckedScrollTop')) > settings.scrollThreshold) {
                container.data('lazyload:lastCheckedScrollTop', container.scrollTop());

                $(pattern, container).each(function() {
                    if (aboveViewport(container, this, settings.threshold)
                        || belowViewport(container, this, settings.threshold)) {
                        $(this).html(settings.placeholder);
                    } else {
                        $(this).html('');
                        var self = this;
                        $('').load(function() {
                            if ($(this).height() > $(self).height()) {
                                $(self).height($(this).height());
                            }
                        }).appendTo(this);
                    }
                })
            }
            setTimeout(checkScroll, settings.checkInterval);
        }

        checkScroll();
    };
})(jQuery);
<commit_msg>jquery.lazyload przestaje sprawdzać scrollTop, jeżeli element nie ma atrybutu 'lazyload:lastCheckedScrollTop'.<commit_after>(function($) {
    jQuery.fn.lazyload = function(pattern, options) {
        var settings = {
            threshold: 0,
            scrollThreshold: 300,
            placeholder: 'loading...',
            checkInterval: 2000
        };

        if (options) {
            $.extend(settings, options);
        }

        var container = this;
        container.data('lazyload:lastCheckedScrollTop', -10000);

        function aboveViewport(container, element, threshold) {
            return $(container).offset().top >= $(element).offset().top + $(element).height() + threshold;
        }

        function belowViewport(container, element, threshold) {
            return $(container).offset().top + $(container).height() + threshold <= $(element).offset().top;
        }

        function checkScroll() {
            if (container.data('lazyload:lastCheckedScrollTop') == undefined) {
                return;
            }
            if (Math.abs(container.scrollTop() - container.data('lazyload:lastCheckedScrollTop')) > settings.scrollThreshold) {
                container.data('lazyload:lastCheckedScrollTop', container.scrollTop());

                $(pattern, container).each(function() {
                    if (aboveViewport(container, this, settings.threshold)
                        || belowViewport(container, this, settings.threshold)) {
                        $(this).html(settings.placeholder);
                    } else {
                        $(this).html('');
                        var self = this;
                        $('').load(function() {
                            if ($(this).height() > $(self).height()) {
                                $(self).height($(this).height());
                            }
                        }).appendTo(this);
                    }
                })
            }
            setTimeout(checkScroll, settings.checkInterval);
        }

        checkScroll();
    };
})(jQuery);
<|endoftext|>


<commit_before>
module.exports = function(params) {

  var mongoose = params.mongoose;
  var Schema = mongoose.Schema;
  var ObjectId = Schema.ObjectId;

  var ServiceSchema = new Schema({
    userid: {type: Object, required: true},
    name: {type: String, required: true},
    role: {type: String},
    runlist: {type: Array , "default" : []},
    defaultattributes: {type: Schema.Types.Mixed},
    overrideattributes: {type: Schema.Types.Mixed},
    created: {type: Date, default: Date.now}
  });

  mongoose.model('Service', ServiceSchema);
  var Service = mongoose.model('Service');

  return Service;

};
<commit_msg>Added default backin<commit_after>
module.exports = function(params) {

  var mongoose = params.mongoose;
  var Schema = mongoose.Schema;
  var ObjectId = Schema.ObjectId;

  var ServiceSchema = new Schema({
    userid: {type: Object, required: true},
    name: {type: String, required: true},
    role: {type: String},
    runlist: {type: Array , "default" : []},
    defaultattributes: {type: Schema.Types.Mixed, "default" : []},
    overrideattributes: {type: Schema.Types.Mixed, "default" : []},
    created: {type: Date, default: Date.now}
  });

  mongoose.model('Service', ServiceSchema);
  var Service = mongoose.model('Service');

  return Service;

};
<|endoftext|>


<commit_before>var youAre = function(dbot) {
    var dbot = dbot;

    return {
        'listener': function(data) {
            var num = Math.floor(Math.random()*11);
            var key = data.message.match(/ is|are ([\d\w\s']*),?\.?and?/);

            if(num == 1 && key != undefined) {
                dbot.say(data.channel, data.user + ': You\'re ' + key[1]);
            }
        },

        'on': 'PRIVMSG'
    };
};

exports.fetch = function(dbot) {
    return youAre(dbot);
};
<commit_msg>ok the regex was broke<commit_after>var youAre = function(dbot) {
    var dbot = dbot;

    return {
        'listener': function(data) {
            var num = Math.floor(Math.random()*6);
            var key = data.message.match(/(is|are) ([\d\w\s']*),?\.?/);

            if(num == 1 && key != undefined) {
                if(key[2].indexOf('and') !== -1) {
                    key[2] = key[2].split('and')[0];
                } // TODO: fix the regex to do this. i hate regex

                dbot.say(data.channel, data.user + ': You\'re ' + key[2]);
            }
        },

        'on': 'PRIVMSG'
    };
};

exports.fetch = function(dbot) {
    return youAre(dbot);
};
<|endoftext|>


<commit_before>'use strict';

const fs = require('fs');
const Log = require('log');

const URL = require('./url');
const Codec = require('../codec');
const Client = require('../httpclient');
const MessageAgent = require('./message-agent');

const log = global.log || new Log(process.env.LOG_LEVEL || 'info');

const AppConfig = {
    clientid: 53999199,
    appid: 501004106
};

function writeFileAsync(filePath, data, options) {
    return new Promise((resolve, reject) => {
        fs.writeFile(filePath, data, options, error => {
            if (error) reject(error);
            resolve();
        });
    });
}

class QQ {
    constructor(...msgHandlers) {
        this.tokens = {
            uin: '',
            ptwebqq: '',
            vfwebqq: '',
            psessionid: ''
        };
        this.selfInfo = {};
        this.buddy = {};
        this.discu = {};
        this.group = {};
        this.buddyNameMap = new Map();
        this.discuNameMap = new Map();
        this.groupNameMap = new Map();
        this.client = new Client();
        this.messageAgent = null;
        this.msgHandlers = msgHandlers;
    }

    async run() {
        await this.login();
        await this.initInfo();
        await this.loopPoll();
    }

    async login() {
        log.info('(0/5) 开始登录，准备下载二维码');

        // Step0: prepare cookies, pgv_info and pgv_pvid
        // http://pingjs.qq.com/tcss.ping.js  tcss.run & _cookie.init
        let initCookies = {
            pgv_info: `ssid=s${Codec.randPgv()}`,
            pgv_pvid: Codec.randPgv()
        };
        this.client.setCookie(initCookies);
        await this.client.get(URL.loginPrepare);

        // Step1: download QRcode
        let qrCode = await this.client.get({ url: URL.qrcode, responseType: 'arraybuffer' });
        await writeFileAsync('/tmp/code.png', qrCode, 'binary');
        log.info('(1/5) 二维码下载完成，等待扫描');
        // open file, only for linux
        require('child_process').exec('xdg-open /tmp/code.png');

        // Step2:
        let scanSuccess = false;
        let quotRegxp = /'[^,]*'/g;
        let ptqrloginURL = URL.getPtqrloginURL(this.client.getCookie('qrsig'));
        let ptlogin4URL;
        do {
            let responseBody = await this.client.get({
                url: ptqrloginURL,
                headers: { Referer: URL.ptqrloginReferer },
            });
            log.debug(responseBody);
            let arr = responseBody.match(quotRegxp).map(i => i.substring(1, i.length - 1));
            if (arr[0] === '0') {
                scanSuccess = true;
                ptlogin4URL = arr[2];
            }
        } while (!scanSuccess);
        log.info('(2/5) 二维码扫描完成');
        // remove file, for linux(or macOS ?)
        require('child_process').exec('rm /tmp/code.png');

        // Step3: find token 'vfwebqq' in cookie
        // NOTICE: the request returns 302 when success. DO NOT REJECT 302.
        await this.client.get({
            url: ptlogin4URL,
            maxRedirects: 0,     // Axios follows redirect automatically, but we need to disable it here.
            validateStatus: status => status === 302,
            headers: { Referer: URL.ptlogin4Referer }
        });
        this.tokens.ptwebqq = this.client.getCookie('ptwebqq');
        log.info('(3/5) 获取 ptwebqq 成功');

        // Step4: request token 'vfwebqq'
        let vfwebqqResp = await this.client.get({
            url: URL.getVfwebqqURL(this.tokens.ptwebqq),
            headers: { Referer: URL.vfwebqqReferer }
        });
        log.debug(vfwebqqResp);
        this.tokens.vfwebqq = vfwebqqResp.result.vfwebqq;
        log.info('(4/5) 获取 vfwebqq 成功');

        // Step5: psessionid and uin
        let loginStat = await this.client.post({
            url: URL.login2,
            data: {
                ptwebqq: this.tokens.ptwebqq,
                clientid: AppConfig.clientid,
                psessionid: "",
                status: "online",
            },
            headers: {
                Origin: URL.login2Origin,
                Referer: URL.login2Referer
            }
        });
        log.debug(loginStat);
        this.tokens.uin = loginStat.result.uin;
        this.tokens.psessionid = loginStat.result.psessionid;
        this.messageAgent = new MessageAgent({
            psessionid: this.tokens.psessionid
        });
        log.info('(5/5) 获取 psessionid 和 uin 成功');
    }

    getSelfInfo() {
        log.info('开始获取用户信息');
        return this.client.get({
            url: URL.selfInfo,
            headers: { Referer: URL.referer130916 }
        });
    }

    getBuddy() {
        log.info('开始获取好友列表');
        return this.client.post({
            url: URL.getBuddy,
            headers: {
                Referer: URL.referer130916
            },
            data: {
                vfwebqq: this.tokens.vfwebqq,
                hash: Codec.hashU(this.tokens.uin, this.tokens.ptwebqq)
            }
        });
    }

    getOnlineBuddies() {
        log.info('开始获取好友在线状态');
        return this.client.get({
            url: URL.onlineBuddies(this.tokens.vfwebqq, this.tokens.psessionid),
            headers: {
                Referer: URL.referer151105
            }
        });
    }

    getGroup() {
        log.info('开始获取群列表');
        return this.client.post({
            url: URL.getGroup,
            headers: {
                Referer: URL.referer130916
            },
            data: {
                vfwebqq: this.tokens.vfwebqq,
                hash: Codec.hashU(this.tokens.uin, this.tokens.ptwebqq)
            }
        });
    }

    getDiscu() {
        log.info('开始获取讨论组列表');
        return this.client.post({
            url: URL.getDiscu(this.tokens.vfwebqq, this.tokens.psessionid),
            headers: {
                Referer: URL.referer130916
            },
            data: {
                vfwebqq: this.tokens.vfwebqq,
                hash: Codec.hashU(this.tokens.uin, this.tokens.ptwebqq)
            }
        });
    }

    async initInfo() {
        let manyInfo = await Promise.all([
            this.getSelfInfo(),
            this.getBuddy(),
            this.getOnlineBuddies(),
            this.getDiscu(),
            this.getGroup()
        ]);
        log.debug(JSON.stringify(manyInfo, null, 4));
        this.selfInfo = manyInfo[0].result;
        this.buddy = manyInfo[1].result;
        this.discu = manyInfo[3].result.dnamelist;
        this.group = manyInfo[4].result.gnamelist;
        let promises = this.group.map(async e => {
            let rawInfo = await this.getGroupInfo(e.code);
            e.info = rawInfo.result;
        });
        promises = promises.concat(this.discu.map(async e => {
            let rawInfo = await this.getDiscuInfo(e.did);
            e.info = rawInfo.result;
        }));
        manyInfo = await Promise.all(promises);
        log.debug(JSON.stringify(manyInfo, null, 4));
        log.info('信息初始化完成');
    }

    getBuddyName(uin) {
        let name = this.buddyNameMap.get(uin);
        if (name) return name;
        this.buddy.marknames.some(e => e.uin == uin ? name = e.markname : false);
        if (!name) this.buddy.info.some(e => e.uin == uin ? name = e.nick : false);
        this.buddyNameMap.set(uin, name);
        return name;
    }

    getDiscuName(did) {
        let name = this.discuNameMap.get(did);
        if (name) return name;
        this.discu.some(e => e.did == did ? name = e.name : false);
        this.discuNameMap.set(did, name);
        return name;
    }

    getDiscuInfo(did) {
        return this.client.get({
            url: URL.discuInfo(did, this.tokens.psessionid, this.tokens.vfwebqq),
            headers: { Referer: URL.referer151105 }
        });
    }

    getNameInDiscu(uin, did) {
        let nameKey = `${did}${uin}`;
        let name = this.discuNameMap.get(nameKey);
        if (name) return name;
        let discu;
        for (let d of this.discu) {
            if (d.did == did) {
                discu = d;
                break;
            }
        }
        discu.info.mem_info.some(i => i.uin == uin ? name = i.nick : false);
        this.discuNameMap.set(nameKey, name);
        return name;
    }

    getGroupName(groupCode) {
        let name = this.groupNameMap.get(groupCode);
        if (name) return name;
        this.group.some(e => e.gid == groupCode ? name = e.name : false);
        this.groupNameMap.set(groupCode, name);
        return name;
    }

    getGroupInfo(code) {
        return this.client.get({
            url: URL.groupInfo(code, this.tokens.vfwebqq),
            headers: { Referer: URL.referer130916 }
        });
    }

    getNameInGroup(uin, groupCode) {
        let nameKey = `${groupCode}${uin}`;
        let name = this.groupNameMap.get(nameKey);
        if (name) return name;
        let group;
        for (let g of this.group) {
            if (g.gid == groupCode) {
                group = g;
                break;
            }
        }
        group.info.cards.some(i => i.muin == uin ? name = i.card : false);
        if (!name) group.info.minfo.some(i => i.uin == uin ? name = i.nick : false);
        this.groupNameMap.set(nameKey, name);
        return name;
    }

    logMessage(msg) {
        let content = msg.result[0].value.content.filter(e => typeof e == 'string').join(' ');
        let { from_uin, send_uin } = msg.result[0].value;
        switch (msg.result[0].poll_type) {
            case 'message':
                log.info(`[新消息] ${this.getBuddyName(from_uin)} | ${content}`);
                break;
            case 'group_message':
                log.info(`[群消息] ${this.getGroupName(from_uin)} : ${this.getNameInGroup(send_uin, from_uin)} | ${content}`);
                break;
            case 'discu_message':
                log.info(`[讨论组] ${this.getDiscuName(from_uin)} : ${this.getNameInDiscu(send_uin, from_uin)} | ${content}`);
        }
    }

    handelMsgRecv(msg) {
        let content = msg.result[0].value.content.filter(e => typeof e == 'string').join(' ');
        let { from_uin, send_uin } = msg.result[0].value;
        let msgParsed = { content };
        switch (msg.result[0].poll_type) {
            case 'message':
                msgParsed.type = 'buddy';
                msgParsed.id = from_uin;
                msgParsed.name = this.getBuddyName(from_uin);
                break;
            case 'group_message':
                msgParsed.type = 'group';
                msgParsed.id = send_uin;
                msgParsed.name = this.getNameInGroup(send_uin, from_uin);
                msgParsed.groupId = from_uin;
                msgParsed.groupName = this.getGroupName(from_uin);
                break;
            case 'discu_message':
                msgParsed.type = 'discu';
                msgParsed.id = send_uin;
                msgParsed.name = this.getNameInDiscu(send_uin, from_uin);
                msgParsed.discuId = from_uin;
                msgParsed.discuName = this.getDiscuName(from_uin);
        }
        this.msgHandlers.forEach(handler => handler.tryHandle(msgParsed, this));
    }

    async loopPoll() {
        log.info('开始接收消息...');
        do {
            let msgContent = await this.client.post({
                url: URL.poll,
                data: {
                    ptwebqq: this.tokens.ptwebqq,
                    clientid: AppConfig.clientid,
                    psessionid: this.tokens.psessionid,
                    key: ''
                },
                headers: {
                    Origin: URL.msgOrigin,
                    Referer: URL.referer151105
                },
                responseType: 'text',
                validateStatus: status => status === 200 || status === 504
            });
            log.debug(msgContent);
            if (msgContent.result) {
                try {
                    this.logMessage(msgContent);
                    this.handelMsgRecv(msgContent);
                } catch (error) {
                    log.error(error);
                }
            }
        } while (true);
    }

    async innerSendMsg(url, key, id, content) {
        const resp = await this.client.post({
            url,
            data: this.messageAgent.build(key, id, content),
            headers: { Referer: URL.referer151105 }
        });
        log.debug(resp);
        /* it returns
         * { errmsg: 'error!!!', retcode: 100100 }
         * when success, i don't know why.
         * fxxk tencent
         */
        return resp;
    }

    async sendBuddyMsg(uin, content) {
        const resp = await this.innerSendMsg(URL.buddyMsg, 'buddy', uin, content);
        log.info(`发消息给好友 ${this.getBuddyName(uin)} : ${content}`);
        return resp;
    }

    async sendDiscuMsg(did, content) {
        const resp = await this.innerSendMsg(URL.discuMsg, 'discu', did, content);
        log.info(`发消息给讨论组 ${this.getDiscuName(did)} : ${content}`);
        return resp;
    }

    async sendGroupMsg(gid, content) {
        const resp = await this.innerSendMsg(URL.groupMsg, 'group', gid, content);
        log.info(`发消息给群 ${this.getGroupName(gid)} : ${content}`);
        return resp;
    }
}

module.exports = QQ;
<commit_msg>feature: login with previous cookie when exists<commit_after>'use strict';

const fs = require('fs');
const Log = require('log');
const childProcess = require('child_process');

const URL = require('./url');
const Codec = require('../codec');
const Client = require('../httpclient');
const MessageAgent = require('./message-agent');

const log = global.log || new Log(process.env.LOG_LEVEL || 'info');

const cookiePath = process.env.COOKIE_PATH || '/tmp/qq-bot.cookie';
const qrcodePath = process.env.QRCODE_PATH || '/tmp/code.png';

const AppConfig = {
    clientid: 53999199,
    appid: 501004106
};

function writeFileAsync(filePath, data, options) {
    return new Promise((resolve, reject) => {
        fs.writeFile(filePath, data, options, error => {
            if (error) reject(error);
            resolve();
        });
    });
}

class QQ {
    constructor(...msgHandlers) {
        this.tokens = {
            uin: '',
            ptwebqq: '',
            vfwebqq: '',
            psessionid: ''
        };
        this.selfInfo = {};
        this.buddy = {};
        this.discu = {};
        this.group = {};
        this.buddyNameMap = new Map();
        this.discuNameMap = new Map();
        this.groupNameMap = new Map();
        this.client = new Client();
        this.messageAgent = null;
        this.msgHandlers = msgHandlers;
    }

    async run() {
        await this.login();
        await this.initInfo();
        await this.loopPoll();
    }

    async login() {
        beforeGotVfwebqq: {
            if (fs.existsSync(cookiePath)) {
                try {
                    const cookieText = fs.readFileSync(cookiePath, 'utf-8').toString();
                    log.info('(-/5) 检测到 cookie 文件，尝试自动登录');
                    this.tokens.ptwebqq = cookieText.match(/ptwebqq=(.+?);/)[1];
                    this.client.setCookie(cookieText);
                    // skip this label if found cookie, goto Step4
                    // TODO: fallback normal login when cookie not valid
                    break beforeGotVfwebqq;
                } catch (err) {
                    this.tokens.ptwebqq = '';
                    childProcess.exec(`rm ${cookiePath}`);
                    log.info('(-/5) Cookie 文件非法，自动登录失败');
                }
            }
            log.info('(0/5) 开始登录，准备下载二维码');

            // Step0: prepare cookies, pgv_info and pgv_pvid
            // http://pingjs.qq.com/tcss.ping.js  tcss.run & _cookie.init
            let initCookies = {
                pgv_info: `ssid=s${Codec.randPgv()}`,
                pgv_pvid: Codec.randPgv()
            };
            this.client.setCookie(initCookies);
            await this.client.get(URL.loginPrepare);

            // Step1: download QRcode
            let qrCode = await this.client.get({ url: URL.qrcode, responseType: 'arraybuffer' });
            await writeFileAsync(qrcodePath, qrCode, 'binary');
            log.info('(1/5) 二维码下载完成，等待扫描');
            // open file, only for linux
            childProcess.exec(`xdg-open ${qrcodePath}`);

            // Step2:
            let scanSuccess = false;
            let quotRegxp = /'[^,]*'/g;
            let ptqrloginURL = URL.getPtqrloginURL(this.client.getCookie('qrsig'));
            let ptlogin4URL;
            do {
                let responseBody = await this.client.get({
                    url: ptqrloginURL,
                    headers: { Referer: URL.ptqrloginReferer },
                });
                log.debug(responseBody);
                let arr = responseBody.match(quotRegxp).map(i => i.substring(1, i.length - 1));
                if (arr[0] === '0') {
                    scanSuccess = true;
                    ptlogin4URL = arr[2];
                }
            } while (!scanSuccess);
            log.info('(2/5) 二维码扫描完成');
            // remove file, for linux(or macOS ?)
            childProcess.exec(`rm ${qrcodePath}`);

            // Step3: find token 'vfwebqq' in cookie
            // NOTICE: the request returns 302 when success. DO NOT REJECT 302.
            await this.client.get({
                url: ptlogin4URL,
                maxRedirects: 0,     // Axios follows redirect automatically, but we need to disable it here.
                validateStatus: status => status === 302,
                headers: { Referer: URL.ptlogin4Referer }
            });
            this.tokens.ptwebqq = this.client.getCookie('ptwebqq');
            log.info('(3/5) 获取 ptwebqq 成功');
        } // ========== label 'beforeGotVfwebqq' ends here ==========

        // Step4: request token 'vfwebqq'
        let vfwebqqResp = await this.client.get({
            url: URL.getVfwebqqURL(this.tokens.ptwebqq),
            headers: { Referer: URL.vfwebqqReferer }
        });
        log.debug(vfwebqqResp);
        this.tokens.vfwebqq = vfwebqqResp.result.vfwebqq;
        log.info('(4/5) 获取 vfwebqq 成功');

        // Step5: psessionid and uin
        let loginStat = await this.client.post({
            url: URL.login2,
            data: {
                ptwebqq: this.tokens.ptwebqq,
                clientid: AppConfig.clientid,
                psessionid: "",
                status: "online",
            },
            headers: {
                Origin: URL.login2Origin,
                Referer: URL.login2Referer
            }
        });
        log.debug(loginStat);
        this.tokens.uin = loginStat.result.uin;
        this.tokens.psessionid = loginStat.result.psessionid;
        this.messageAgent = new MessageAgent({
            psessionid: this.tokens.psessionid
        });
        log.info('(5/5) 获取 psessionid 和 uin 成功');
        const cookie = await this.client.getCookieString();
        fs.writeFile(cookiePath, cookie, 'utf-8', () => log.info(`保存 Cookie 到 ${cookiePath}`));
    }

    getSelfInfo() {
        log.info('开始获取用户信息');
        return this.client.get({
            url: URL.selfInfo,
            headers: { Referer: URL.referer130916 }
        });
    }

    getBuddy() {
        log.info('开始获取好友列表');
        return this.client.post({
            url: URL.getBuddy,
            headers: {
                Referer: URL.referer130916
            },
            data: {
                vfwebqq: this.tokens.vfwebqq,
                hash: Codec.hashU(this.tokens.uin, this.tokens.ptwebqq)
            }
        });
    }

    getOnlineBuddies() {
        log.info('开始获取好友在线状态');
        return this.client.get({
            url: URL.onlineBuddies(this.tokens.vfwebqq, this.tokens.psessionid),
            headers: {
                Referer: URL.referer151105
            }
        });
    }

    getGroup() {
        log.info('开始获取群列表');
        return this.client.post({
            url: URL.getGroup,
            headers: {
                Referer: URL.referer130916
            },
            data: {
                vfwebqq: this.tokens.vfwebqq,
                hash: Codec.hashU(this.tokens.uin, this.tokens.ptwebqq)
            }
        });
    }

    getDiscu() {
        log.info('开始获取讨论组列表');
        return this.client.post({
            url: URL.getDiscu(this.tokens.vfwebqq, this.tokens.psessionid),
            headers: {
                Referer: URL.referer130916
            },
            data: {
                vfwebqq: this.tokens.vfwebqq,
                hash: Codec.hashU(this.tokens.uin, this.tokens.ptwebqq)
            }
        });
    }

    async initInfo() {
        let manyInfo = await Promise.all([
            this.getSelfInfo(),
            this.getBuddy(),
            this.getOnlineBuddies(),
            this.getDiscu(),
            this.getGroup()
        ]);
        log.debug(JSON.stringify(manyInfo, null, 4));
        this.selfInfo = manyInfo[0].result;
        this.buddy = manyInfo[1].result;
        this.discu = manyInfo[3].result.dnamelist;
        this.group = manyInfo[4].result.gnamelist;
        let promises = this.group.map(async e => {
            let rawInfo = await this.getGroupInfo(e.code);
            e.info = rawInfo.result;
        });
        promises = promises.concat(this.discu.map(async e => {
            let rawInfo = await this.getDiscuInfo(e.did);
            e.info = rawInfo.result;
        }));
        manyInfo = await Promise.all(promises);
        log.debug(JSON.stringify(manyInfo, null, 4));
        log.info('信息初始化完成');
    }

    getBuddyName(uin) {
        let name = this.buddyNameMap.get(uin);
        if (name) return name;
        this.buddy.marknames.some(e => e.uin == uin ? name = e.markname : false);
        if (!name) this.buddy.info.some(e => e.uin == uin ? name = e.nick : false);
        this.buddyNameMap.set(uin, name);
        return name;
    }

    getDiscuName(did) {
        let name = this.discuNameMap.get(did);
        if (name) return name;
        this.discu.some(e => e.did == did ? name = e.name : false);
        this.discuNameMap.set(did, name);
        return name;
    }

    getDiscuInfo(did) {
        return this.client.get({
            url: URL.discuInfo(did, this.tokens.psessionid, this.tokens.vfwebqq),
            headers: { Referer: URL.referer151105 }
        });
    }

    getNameInDiscu(uin, did) {
        let nameKey = `${did}${uin}`;
        let name = this.discuNameMap.get(nameKey);
        if (name) return name;
        let discu;
        for (let d of this.discu) {
            if (d.did == did) {
                discu = d;
                break;
            }
        }
        discu.info.mem_info.some(i => i.uin == uin ? name = i.nick : false);
        this.discuNameMap.set(nameKey, name);
        return name;
    }

    getGroupName(groupCode) {
        let name = this.groupNameMap.get(groupCode);
        if (name) return name;
        this.group.some(e => e.gid == groupCode ? name = e.name : false);
        this.groupNameMap.set(groupCode, name);
        return name;
    }

    getGroupInfo(code) {
        return this.client.get({
            url: URL.groupInfo(code, this.tokens.vfwebqq),
            headers: { Referer: URL.referer130916 }
        });
    }

    getNameInGroup(uin, groupCode) {
        let nameKey = `${groupCode}${uin}`;
        let name = this.groupNameMap.get(nameKey);
        if (name) return name;
        let group;
        for (let g of this.group) {
            if (g.gid == groupCode) {
                group = g;
                break;
            }
        }
        group.info.cards.some(i => i.muin == uin ? name = i.card : false);
        if (!name) group.info.minfo.some(i => i.uin == uin ? name = i.nick : false);
        this.groupNameMap.set(nameKey, name);
        return name;
    }

    logMessage(msg) {
        let content = msg.result[0].value.content.filter(e => typeof e == 'string').join(' ');
        let { from_uin, send_uin } = msg.result[0].value;
        switch (msg.result[0].poll_type) {
            case 'message':
                log.info(`[新消息] ${this.getBuddyName(from_uin)} | ${content}`);
                break;
            case 'group_message':
                log.info(`[群消息] ${this.getGroupName(from_uin)} : ${this.getNameInGroup(send_uin, from_uin)} | ${content}`);
                break;
            case 'discu_message':
                log.info(`[讨论组] ${this.getDiscuName(from_uin)} : ${this.getNameInDiscu(send_uin, from_uin)} | ${content}`);
        }
    }

    handelMsgRecv(msg) {
        let content = msg.result[0].value.content.filter(e => typeof e == 'string').join(' ');
        let { from_uin, send_uin } = msg.result[0].value;
        let msgParsed = { content };
        switch (msg.result[0].poll_type) {
            case 'message':
                msgParsed.type = 'buddy';
                msgParsed.id = from_uin;
                msgParsed.name = this.getBuddyName(from_uin);
                break;
            case 'group_message':
                msgParsed.type = 'group';
                msgParsed.id = send_uin;
                msgParsed.name = this.getNameInGroup(send_uin, from_uin);
                msgParsed.groupId = from_uin;
                msgParsed.groupName = this.getGroupName(from_uin);
                break;
            case 'discu_message':
                msgParsed.type = 'discu';
                msgParsed.id = send_uin;
                msgParsed.name = this.getNameInDiscu(send_uin, from_uin);
                msgParsed.discuId = from_uin;
                msgParsed.discuName = this.getDiscuName(from_uin);
        }
        this.msgHandlers.forEach(handler => handler.tryHandle(msgParsed, this));
    }

    async loopPoll() {
        log.info('开始接收消息...');
        do {
            let msgContent = await this.client.post({
                url: URL.poll,
                data: {
                    ptwebqq: this.tokens.ptwebqq,
                    clientid: AppConfig.clientid,
                    psessionid: this.tokens.psessionid,
                    key: ''
                },
                headers: {
                    Origin: URL.msgOrigin,
                    Referer: URL.referer151105
                },
                responseType: 'text',
                validateStatus: status => status === 200 || status === 504
            });
            log.debug(msgContent);
            if (msgContent.result) {
                try {
                    this.logMessage(msgContent);
                    this.handelMsgRecv(msgContent);
                } catch (error) {
                    log.error(error);
                }
            }
        } while (true);
    }

    async innerSendMsg(url, key, id, content) {
        const resp = await this.client.post({
            url,
            data: this.messageAgent.build(key, id, content),
            headers: { Referer: URL.referer151105 }
        });
        log.debug(resp);
        /* it returns
         * { errmsg: 'error!!!', retcode: 100100 }
         * when success, i don't know why.
         * fxxk tencent
         */
        return resp;
    }

    async sendBuddyMsg(uin, content) {
        const resp = await this.innerSendMsg(URL.buddyMsg, 'buddy', uin, content);
        log.info(`发消息给好友 ${this.getBuddyName(uin)} : ${content}`);
        return resp;
    }

    async sendDiscuMsg(did, content) {
        const resp = await this.innerSendMsg(URL.discuMsg, 'discu', did, content);
        log.info(`发消息给讨论组 ${this.getDiscuName(did)} : ${content}`);
        return resp;
    }

    async sendGroupMsg(gid, content) {
        const resp = await this.innerSendMsg(URL.groupMsg, 'group', gid, content);
        log.info(`发消息给群 ${this.getGroupName(gid)} : ${content}`);
        return resp;
    }
}

module.exports = QQ;
<|endoftext|>


<commit_before>require('babel-regenerator-runtime')

import csp from 'js-csp'

function createStore(initialState, actions, optionals) {
	var currentState = initialState

	var actionFunc = {}
	var actionName = {}

	actions.forEach( action => {
		actionName[action.type] = action.type
		actionFunc[action.type] = action.fn
	})

	actionName["reactorxInit"] = "reactorxInit"
	actionFunc["reactorxInit"] = _ => currentState

	var callback = null

	var queue = csp.chan(623)

	var optionals = optionals

	function* reactor(queue) {
		for (;;) {
			var action = yield csp.take(queue)

			if (!actionFunc[action.type]) continue

			currentState = actionFunc[action.type]({state: currentState, actions: actionName, dispatch},  optionals, action.params)
			callback({state: currentState, actions: actionName, dispatch})
		}
	}

	function subscribe(cb) {
		callback = cb
		csp.go(reactor, [queue])
		dispatch("reactorxInit")
		// callback({state: currentState, actions: actionName, dispatch})
	}

	function dispatch(type, params) {
		csp.go(function* () {
			yield csp.put(queue, {type, params})
		})
	}

	return {
		subscribe,
		dispatch,
		actions: actionName
	}
}

module.exports = {
	createStore,
}

// function loadActions(folder) {
// 	var actionModules = require.context('./actions/', true, /\.js$/)

// 	let actions = []

// 	console.log("keys: ", actionModules.keys())

// 	actionModules.keys().forEach( module => {
// 		console.log('module: ', module)
// 		let normal = require("path").join("./actions/", __dirname, module)
// 		console.log('normal: ', normal)
// 		let testing = './actions/actions'
// 		let resolved = actionModules.resolve(module)
// 		console.log('resolved: ', resolved)
// 		actions = actions.concat(resolved)
// 	})

// 	return actions

// 	// var fs = require('fs')
// 	// let normalizedPath = require("path").join(__dirname, folder);

// 	// let actions = []

// 	// console.log('fs: ', fs)
// 	// console.log('normalizedPath: ', normalizedPath)

// 	// fs.readdirSync(normalizedPath).forEach(function(file) {
// 	// 	actions = actions.concat(require(folder + file))
// 	// })

// 	// return actions
// }
<commit_msg>- Check that callback is actually instantiated, before calling it - Remove old code (commented out)<commit_after>require('babel-regenerator-runtime')
import csp from 'js-csp'

module.exports = {
	createStore,
}

function createStore(initialState, actions, optionals) {
	var currentState = initialState

	var actionFunc = {}
	var actionName = {}

	actions.forEach( action => {
		actionName[action.type] = action.type
		actionFunc[action.type] = action.fn
	})

	actionName["reactorxInit"] = "reactorxInit"
	actionFunc["reactorxInit"] = _ => currentState

	var optionals = optionals

	var callback = null
	var queue = csp.chan(623) // this should probably be received as an argument

	function* reactor(queue) {
		for (;;) {
			var action = yield csp.take(queue)

			if (!actionFunc[action.type]) continue

			currentState = actionFunc[action.type]({state: currentState, actions: actionName, dispatch},  optionals, action.params)

			if (callback)
				callback({state: currentState, actions: actionName, dispatch})
		}
	}

	function subscribe(cb) {
		callback = cb
		csp.go(reactor, [queue])
		dispatch("reactorxInit")
		// callback({state: currentState, actions: actionName, dispatch})
	}

	function dispatch(type, params) {
		csp.go(function* () {
			yield csp.put(queue, {type, params})
		})
	}

	return {
		subscribe,
		dispatch,
		actions: actionName
	}
}<|endoftext|>


<commit_before>var remixLib = require('remix-lib')
var EventManager = remixLib.EventManager
var util = remixLib.util
var executionContext = require('./execution-context')
var async = require('async')

/**
  * Record transaction as long as the user create them.
  *
  *
  */
class Recorder {
  constructor (opts = {}) {
    var self = this
    self._api = opts.api
    self.event = new EventManager()
    self.data = { _listen: true, _replay: false, journal: [], _pendingTxs: {}, _createdContracts: {}, _createdContractsReverse: {}, _usedAccounts: {}, _abis: {} }
    opts.events.executioncontext.register('contextChanged', () => {
      self.clearAll()
    })

    opts.events.udapp.register('initiatingTransaction', (timestamp, tx) => {
      var { from, to, value, data, useCall } = tx
      var record = { value, data, useCall }

      this.data._pendingTxs[timestamp] = tx

      // convert to and from to tokens
      if (this.data._listen) {
        if (!to) {
          var selectedContract = self._api.getSelectedContract()
          if (selectedContract) {
            var abi = selectedContract.contract.object.abi
            var sha3 = util.sha3_256(JSON.stringify(abi))
            record.abi = sha3
            self.data._abis[sha3] = abi
          }
        } else {
          record.to = `created-contract{${this.data._createdContracts[to]}}`
        }

        self._api.getAccounts((error, accounts) => {
          if (error) return console.log(error)
          record.from = `account{${accounts.indexOf(from)}}`
          self.data._usedAccounts[record.from] = from
          self.append(timestamp, record)
        })
      }
    })

    opts.events.udapp.register('transactionExecuted', (error, from, to, data, call, txResult, timestamp) => {
      if (error) return console.log(error)
      var tx = this.data._pendingTxs[timestamp]

      var address = executionContext.isVM() ? txResult.result.createdAddress : tx.result.contractAddress
      if (!address) return // not a contract creation
      address = '0x' + address.toString('hex')
      // save back created addresses for the convertion from tokens to real adresses
      this.data._createdContracts[address] = timestamp
      this.data._createdContractsReverse[timestamp] = address
    })
  }

  /**
    * stop/start saving txs. If not listenning, is basically in replay mode
    *
    * @param {Bool} listen
    */
  setListen (listen) {
    this.data._listen = listen
    this.data._replay = !listen
  }

  /**
    * convert back from/to from tokens to real addresses
    *
    * @param {Object} record
    * @param {Object} accounts
    * @param {Object} options
    *
    */
  resolveAddress (record, accounts, options) {
    if (record.to) {
      var timestamp = /created-contract{(.*)}/g.exec(record.to)
      record.to = this.data._createdContractsReverse[timestamp[1]]
    }
    record.from = accounts[record.from]
    // @TODO: writing browser test
    return record
  }

  /**
    * save the given @arg record
    *
    * @param {Number/String} timestamp
    * @param {Object} record
    *
    */
  append (timestamp, record) {
    var self = this
    self.data.journal.push({ timestamp, record })
  }

  /**
    * basically return the records + associate values (like abis / accounts)
    *
    */
  getAll () {
    var self = this
    var records = [].concat(self.data.journal)
    return {
      accounts: self.data._usedAccounts,
      options: {
        useAccountList: true
      },
      transactions: records.sort((A, B) => {
        var stampA = A.timestamp
        var stampB = B.timestamp
        return stampA - stampB
      }),
      abis: self.data._abis
    }
  }

  /**
    * delete the seen transactions
    *
    */
  clearAll () {
    var self = this
    self.data.journal = []
    self.data._pendingTxs = {}
    self.data._createdContracts = {}
    self.data._createdContractsReverse = {}
    self.data._usedAccounts = {}
    self.data._abis = {}
  }

  /**
    * run the list of records
    *
    * @param {Object} accounts
    * @param {Object} options
    * @param {Object} abis
    * @param {Function} newContractFn
    *
    */
  run (records, accounts, options, abis, newContractFn) {
    var self = this
    self.setListen(false)
    async.eachSeries(records, function (tx, cb) {
      var record = self.resolveAddress(tx.record, accounts, options)
      self._api.udapp().rerunTx(record, function (err, txResult) {
        if (err) {
          console.error(err)
        } else {
          var address = executionContext.isVM() ? txResult.result.createdAddress : tx.result.contractAddress
          if (!address) return // not a contract creation
          address = '0x' + address.toString('hex')
          // save back created addresses for the convertion from tokens to real adresses
          self.data._createdContracts[address] = tx.timestamp
          self.data._createdContractsReverse[tx.timestamp] = address
          var abi = abis[tx.record.abi]
          if (abi) {
            newContractFn(abi, address, record.contractName)
          }
        }
        cb()
      })
    }, () => { self.setListen(true) })
  }
}

module.exports = Recorder
<commit_msg>fix contractName<commit_after>var remixLib = require('remix-lib')
var EventManager = remixLib.EventManager
var util = remixLib.util
var executionContext = require('./execution-context')
var async = require('async')

/**
  * Record transaction as long as the user create them.
  *
  *
  */
class Recorder {
  constructor (opts = {}) {
    var self = this
    self._api = opts.api
    self.event = new EventManager()
    self.data = { _listen: true, _replay: false, journal: [], _pendingTxs: {}, _createdContracts: {}, _createdContractsReverse: {}, _usedAccounts: {}, _abis: {} }
    opts.events.executioncontext.register('contextChanged', () => {
      self.clearAll()
    })

    opts.events.udapp.register('initiatingTransaction', (timestamp, tx) => {
      var { from, to, value, data, useCall } = tx
      var record = { value, data, useCall }

      this.data._pendingTxs[timestamp] = tx

      // convert to and from to tokens
      if (this.data._listen) {
        if (!to) {
          var selectedContract = self._api.getSelectedContract()
          if (selectedContract) {
            var abi = selectedContract.contract.object.abi
            var sha3 = util.sha3_256(JSON.stringify(abi))
            record.abi = sha3
            record.contractName = selectedContract.name
            self.data._abis[sha3] = abi
          }
        } else {
          record.to = `created-contract{${this.data._createdContracts[to]}}`
        }

        self._api.getAccounts((error, accounts) => {
          if (error) return console.log(error)
          record.from = `account{${accounts.indexOf(from)}}`
          self.data._usedAccounts[record.from] = from
          self.append(timestamp, record)
        })
      }
    })

    opts.events.udapp.register('transactionExecuted', (error, from, to, data, call, txResult, timestamp) => {
      if (error) return console.log(error)
      var tx = this.data._pendingTxs[timestamp]

      var address = executionContext.isVM() ? txResult.result.createdAddress : tx.result.contractAddress
      if (!address) return // not a contract creation
      address = '0x' + address.toString('hex')
      // save back created addresses for the convertion from tokens to real adresses
      this.data._createdContracts[address] = timestamp
      this.data._createdContractsReverse[timestamp] = address
    })
  }

  /**
    * stop/start saving txs. If not listenning, is basically in replay mode
    *
    * @param {Bool} listen
    */
  setListen (listen) {
    this.data._listen = listen
    this.data._replay = !listen
  }

  /**
    * convert back from/to from tokens to real addresses
    *
    * @param {Object} record
    * @param {Object} accounts
    * @param {Object} options
    *
    */
  resolveAddress (record, accounts, options) {
    if (record.to) {
      var timestamp = /created-contract{(.*)}/g.exec(record.to)
      record.to = this.data._createdContractsReverse[timestamp[1]]
    }
    record.from = accounts[record.from]
    // @TODO: writing browser test
    return record
  }

  /**
    * save the given @arg record
    *
    * @param {Number/String} timestamp
    * @param {Object} record
    *
    */
  append (timestamp, record) {
    var self = this
    self.data.journal.push({ timestamp, record })
  }

  /**
    * basically return the records + associate values (like abis / accounts)
    *
    */
  getAll () {
    var self = this
    var records = [].concat(self.data.journal)
    return {
      accounts: self.data._usedAccounts,
      options: {
        useAccountList: true
      },
      transactions: records.sort((A, B) => {
        var stampA = A.timestamp
        var stampB = B.timestamp
        return stampA - stampB
      }),
      abis: self.data._abis
    }
  }

  /**
    * delete the seen transactions
    *
    */
  clearAll () {
    var self = this
    self.data.journal = []
    self.data._pendingTxs = {}
    self.data._createdContracts = {}
    self.data._createdContractsReverse = {}
    self.data._usedAccounts = {}
    self.data._abis = {}
  }

  /**
    * run the list of records
    *
    * @param {Object} accounts
    * @param {Object} options
    * @param {Object} abis
    * @param {Function} newContractFn
    *
    */
  run (records, accounts, options, abis, newContractFn) {
    var self = this
    self.setListen(false)
    async.eachSeries(records, function (tx, cb) {
      var record = self.resolveAddress(tx.record, accounts, options)
      self._api.udapp().rerunTx(record, function (err, txResult) {
        if (err) {
          console.error(err)
        } else {
          var address = executionContext.isVM() ? txResult.result.createdAddress : tx.result.contractAddress
          if (!address) return // not a contract creation
          address = '0x' + address.toString('hex')
          // save back created addresses for the convertion from tokens to real adresses
          self.data._createdContracts[address] = tx.timestamp
          self.data._createdContractsReverse[tx.timestamp] = address
          var abi = abis[tx.record.abi]
          if (abi) {
            newContractFn(abi, address, record.contractName)
          }
        }
        cb()
      })
    }, () => { self.setListen(true) })
  }
}

module.exports = Recorder
<|endoftext|>


<commit_before>// @flow strict-local
import React, { useEffect, useState } from 'react'
import t from 'format-message'
import SRContent from './ScreenReaderContent'

const millisecondsInASecond = 1000
const updateFequencyInMilliseconds = 100
const dayStart = new Date('2019-01-01T06:00:00Z')
const dayEnd = new Date('2019-01-01T18:00:00Z')

function pad(value: number) {
  return value < 10 ? '0' + value : value
}

function getDatetimeValue(date: Date) {
  return (
    pad(date.getUTCHours()) +
    ':' +
    pad(date.getUTCMinutes()) +
    ':' +
    pad(date.getUTCSeconds())
  )
}

function getWorkDayTime(elapsedSeconds: number) {
  const workDayTime = new Date(dayStart)
  workDayTime.setSeconds(elapsedSeconds)
  return workDayTime < dayEnd ? workDayTime : dayEnd
}

type Props = {
  onDayEnd: () => void,
}

function WorkDayClock({ onDayEnd }: Props) {
  const gameSecondsSpeedFactor = 360
  const [elapsedGameSeconds, setElapsedGameSeconds] = useState(0)
  const workDayTime = getWorkDayTime(elapsedGameSeconds)
  const hasWorkDayEnded = workDayTime === dayEnd
  const previousActualTime = performance.now()
  useEffect(() => {
    function tick() {
      const elapsedMilliseconds = performance.now() - previousActualTime
      const elapsedSeconds = elapsedMilliseconds / millisecondsInASecond
      const additionalGameSeconds = elapsedSeconds * gameSecondsSpeedFactor
      setElapsedGameSeconds(elapsedGameSeconds + additionalGameSeconds)
    }
    if (!hasWorkDayEnded) {
      const id = setTimeout(tick, updateFequencyInMilliseconds)
      return () => clearTimeout(id)
    } else {
      onDayEnd()
    }
  })

  return (

      <SRContent>

{t('Work day clock')}

      </SRContent>
      <time dateTime={getDatetimeValue(workDayTime)}>
        {t.time(workDayTime, 'H:mm')}
      </time>

        <SRContent>

{t('Work day hours')}

        </SRContent>
        {t('6 AM - 6 PM')}

  )
}

export default WorkDayClock
<commit_msg>Improve WorkDayClock a bit<commit_after>// @flow strict-local
import React, { useEffect, useState } from 'react'
import t from 'format-message'
import SRContent from './ScreenReaderContent'

const updateFequencyInMilliseconds = 100
const dayStart = new Date('2019-01-01T06:00:00Z')
const dayEnd = new Date('2019-01-01T18:00:00Z')

function pad(value: number) {
  return value < 10 ? '0' + value : value
}

function getDatetimeValue(date: Date) {
  return (
    pad(date.getUTCHours()) +
    ':' +
    pad(date.getUTCMinutes()) +
    ':' +
    pad(date.getUTCSeconds())
  )
}

function getNewGameDate(previousGameDate: Date, timeToAdd: number) {
  const newGameDate = new Date(previousGameDate.getTime() + timeToAdd)
  return newGameDate < dayEnd ? newGameDate : dayEnd
}

type Props = {
  onDayEnd: () => void,
}

function WorkDayClock({ onDayEnd }: Props) {
  const gameSpeedFactor = 360
  const [gameDate, setGameDate] = useState(dayStart)
  const [timerTime, setTimerTime] = useState(performance.now())
  const hasWorkDayEnded = gameDate === dayEnd

  useEffect(() => {
    function tick() {
      const newTimerTime = performance.now()
      const elapsedTime = newTimerTime - timerTime
      const additionalGameTime = elapsedTime * gameSpeedFactor
      const newGameDate = getNewGameDate(gameDate, additionalGameTime)
      const didWorkDayJustEnd = newGameDate === dayEnd
      setGameDate(newGameDate)
      setTimerTime(newTimerTime)
      if (didWorkDayJustEnd) {
        onDayEnd()
      }
    }
    if (!hasWorkDayEnded) {
      const id = setTimeout(tick, updateFequencyInMilliseconds)
      return () => clearTimeout(id)
    }
  })

  return (

      <SRContent>

{t('Work day clock')}

      </SRContent>
      <time dateTime={getDatetimeValue(gameDate)}>
        {t.time(gameDate, 'H:mm')}
      </time>

        <SRContent>

{t('Work day hours')}

        </SRContent>
        {t('6 AM - 6 PM')}

  )
}

export default WorkDayClock
<|endoftext|>


<commit_before>define([
	"dojo/_base/declare",
	"dojo/parser",
	"dojo/_base/lang",
	"dijit/_WidgetBase"
], function(declare, parser, lang, _WidgetBase){
	/*=====
		declare = dojo.declare;
		_WidgetBase = dijit._WidgetBase;
	=====*/

	return declare("dojox.mvc._Container", [_WidgetBase], {

		// stopParser: [private] Boolean
		//		Flag to parser to not try and parse widgets declared inside the container.
		stopParser: true,

		// exprchar:  Character
		//		Character to use for a substitution expression, for a substitution string like ${this.index}
		exprchar: '$',

		// templateString: [private] String
		//		The template or content for this container. It is usually obtained from the
		//		body of the container and may be modified or repeated over a collection/array.
		//		In this simple implementation, attach points, attach events and WAI
		//		attributes are not supported in the template.
		templateString : "",

		// _containedWidgets: [protected] dijit._Widget[]
		//		The array of contained widgets at any given point in time within this container.
		_containedWidgets : [],

		////////////////////// PROTECTED METHODS ////////////////////////

		_createBody: function(){
			// summary:
			//		Parse the body of this MVC container widget.
			// description:
			//		The bodies of MVC containers may be model-bound views generated dynamically.
			//		Parse the body, start an contained widgets and attach template nodes for
			//		contained widgets as necessary.
			// tags:
			//		protected
			this._containedWidgets = parser.parse(this.srcNodeRef,{
				template: true,
				inherited: {dir: this.dir, lang: this.lang},
				propsThis: this,
				scope: "dojo"
			});
		},

		_destroyBody: function(){
			// summary:
			//		Destroy the body of this MVC container widget. Also destroys any
			//		contained widgets.
			// tags:
			//		protected
			if(this._containedWidgets && this._containedWidgets.length > 0){
				for(var n = this._containedWidgets.length - 1; n > -1; n--){
					var w = this._containedWidgets[n];
					if(w && !w._destroyed && w.destroy){
						w.destroy();
					}
				}
			}
		},

		////////////////////// PRIVATE METHODS ////////////////////////

		_exprRepl: function(tmpl){
			// summary:
			//		Does substitution of ${foo+bar} type expressions in template string.
			// tags:
			//		private
			var pThis = this, transform = function(value, key){
				if(!value){return "";}
				var exp = value.substr(2);
				exp = exp.substr(0, exp.length - 1);
				with (pThis) {return eval(exp);};
			};
			transform = lang.hitch(this, transform);
			return tmpl.replace(new RegExp(dojo.regexp.escapeString(this.exprchar)+"(\{.*?\})","g"),
				function(match, key, format){
					return transform(match, key).toString();
				});
		}
	});
});
<commit_msg>added missing dep from r25662 refs #13230 !strict<commit_after>define([
	"dojo/_base/declare",
	"dojo/parser",
	"dojo/_base/lang",
	"dijit/_WidgetBase",
	"dojo/regexp"
], function(declare, parser, lang, _WidgetBase, regexp){
	/*=====
		declare = dojo.declare;
		_WidgetBase = dijit._WidgetBase;
	=====*/

	return declare("dojox.mvc._Container", [_WidgetBase], {

		// stopParser: [private] Boolean
		//		Flag to parser to not try and parse widgets declared inside the container.
		stopParser: true,

		// exprchar:  Character
		//		Character to use for a substitution expression, for a substitution string like ${this.index}
		exprchar: '$',

		// templateString: [private] String
		//		The template or content for this container. It is usually obtained from the
		//		body of the container and may be modified or repeated over a collection/array.
		//		In this simple implementation, attach points, attach events and WAI
		//		attributes are not supported in the template.
		templateString : "",

		// _containedWidgets: [protected] dijit._Widget[]
		//		The array of contained widgets at any given point in time within this container.
		_containedWidgets : [],

		////////////////////// PROTECTED METHODS ////////////////////////

		_createBody: function(){
			// summary:
			//		Parse the body of this MVC container widget.
			// description:
			//		The bodies of MVC containers may be model-bound views generated dynamically.
			//		Parse the body, start an contained widgets and attach template nodes for
			//		contained widgets as necessary.
			// tags:
			//		protected
			this._containedWidgets = parser.parse(this.srcNodeRef,{
				template: true,
				inherited: {dir: this.dir, lang: this.lang},
				propsThis: this,
				scope: "dojo"
			});
		},

		_destroyBody: function(){
			// summary:
			//		Destroy the body of this MVC container widget. Also destroys any
			//		contained widgets.
			// tags:
			//		protected
			if(this._containedWidgets && this._containedWidgets.length > 0){
				for(var n = this._containedWidgets.length - 1; n > -1; n--){
					var w = this._containedWidgets[n];
					if(w && !w._destroyed && w.destroy){
						w.destroy();
					}
				}
			}
		},

		////////////////////// PRIVATE METHODS ////////////////////////

		_exprRepl: function(tmpl){
			// summary:
			//		Does substitution of ${foo+bar} type expressions in template string.
			// tags:
			//		private
			var pThis = this, transform = function(value, key){
				if(!value){return "";}
				var exp = value.substr(2);
				exp = exp.substr(0, exp.length - 1);
				with (pThis) {return eval(exp);};
			};
			transform = lang.hitch(this, transform);
			return tmpl.replace(new RegExp(regexp.escapeString(this.exprchar)+"(\{.*?\})","g"),
				function(match, key, format){
					return transform(match, key).toString();
				});
		}
	});
});
<|endoftext|>


<commit_before>import _ from 'underscore';
import Marionette from 'backbone.marionette';
import StateClass from './state-class';

/**
 * StateClass with an `initialize` / `start` / `stop` / `destroy` lifecycle.
 * Registers events while running and cleans them up at `onStop`
 *
 * @public
 * @class AbstractApp
 * @memberOf Toolkit
 * @memberOf Marionette
 */
var AbstractApp = StateClass.extend({

  /**
   * Internal flag indiciate when `App` has started but has not yet stopped.
   *
   * @private
   * @type {Boolean}
   * @default false
   */
  _isRunning: false,

  /**
   * Internal flag indiciate when `App` has been destroyed
   *
   * @private
   * @type {Boolean}
   * @default false
   */
  _isDestroyed: false,

  /**
   * Set to true if a parent `App` should not be able to destroy this `App`.
   *
   * @type {Boolean|Function}
   * @default false
   */
  preventDestroy: false,

  /**
   * Set to true if `App` should be started after it is initialized.
   *
   * @type {Boolean|Function}
   * @default false
   */
  startAfterInitialized: false,

  /**
   * Set to true if `App` should be started after its parent starts.
   *
   * @type {Boolean|Function}
   * @default false
   */
  startWithParent: false,

  /**
   * Set to false if `App` should not stop after its parent stops.
   *
   * @type {Boolean|Function}
   * @default true
   */
  stopWithParent: true,

  /**
   * @public
   * @constructs AbstractApp
   * @param {Object} [options] - Settings for the App.
   * @param {Boolean} [options.startWithParent]
   * @param {Boolean} [options.stopWithParent]
   * @param {Boolean} [options.startAfterInitialized]
   * @param {Boolean} [options.preventDestroy]
   */
  constructor: function(options) {
    options = options || {};

    _.bindAll(this, 'start', 'stop');

    var pickOptions = [
      'startWithParent',
      'stopWithParent',
      'startAfterInitialized',
      'preventDestroy'
    ];

    _.extend(this, _.pick(options, pickOptions));

    // Will call initialize
    StateClass.call(this, options);

    if(_.result(this, 'startAfterInitialized')) {
      this.start(options);
    }
  },

  /**
   * Internal helper to verify if `App` has been destroyed
   *
   * @private
   * @method _ensureAppIsIntact
   * @memberOf AbstractApp
   * @throws AppDestroyedError - Thrown if `App` has already been destroyed
   */
  _ensureAppIsIntact: function() {
    if(this._isDestroyed) {
      throw new Marionette.Error({
        name: 'AppDestroyedError',
        message: 'App has already been destroyed and cannot be used.'
      });
    }
  },

  /**
   * Gets the value of internal `_isRunning` flag
   *
   * @public
   * @method isRunning
   * @memberOf AbstractApp
   * @returns {Boolean}
   */
  isRunning: function() {
    return this._isRunning;
  },

  /**
   * Sets the app lifecycle to running.
   *
   * @public
   * @method start
   * @memberOf AbstractApp
   * @param {Object} [options] - Settings for the App passed through to events
   * @event AbstractApp#before:start - passes options
   * @event AbstractApp#start - passes options
   * @returns {AbstractApp}
   */
  start: function(options) {
    this._ensureAppIsIntact();

    if(this._isRunning) {
      return this;
    }

    this.triggerMethod('before:start', options);

    this._isRunning = true;

    this.triggerMethod('start', options);

    return this;
  },

  /**
   * Sets the app lifecycle to not running.
   * Removes any listeners added during the running state
   *
   * @public
   * @method stop
   * @memberOf AbstractApp
   * @param {Object} [options] - Settings for the App passed through to events
   * @event AbstractApp#before:stop - passes options
   * @event AbstractApp#stop - passes options
   * @returns {AbstractApp}
   */
  stop: function(options) {
    if(!this._isRunning) {
      return this;
    }

    this.triggerMethod('before:stop', options);

    this._isRunning = false;

    this.triggerMethod('stop', options);

    this._stopRunningListeners();
    this._stopRunningEvents();

    return this;
  },

  /**
   * Gets the value of internal `_isDestroyed` flag
   *
   * @public
   * @method isDestroyed
   * @memberOf AbstractApp
   * @returns {Boolean}
   */
  isDestroyed: function() {
    return this._isDestroyed;
  },

  /**
   * Stops the `App` and sets it destroyed.
   *
   * @public
   * @method destroy
   * @memberOf AbstractApp
   */
  destroy: function() {
    this.stop();

    this._isDestroyed = true;

    StateClass.prototype.destroy.apply(this, arguments);
  },

  /**
   * Internal method to stop any registered events.
   *
   * @private
   * @method _stopRunningEvents
   * @memberOf AbstractApp
   */
  _stopRunningEvents: function(){
    _.each(this._runningEvents, function(args) {
      this.off.apply(this, args);
    }, this);
  },

  /**
   * Internal method to stop any registered listeners.
   *
   * @private
   * @method _stopRunningListeners
   * @memberOf AbstractApp
   */
  _stopRunningListeners: function(){
    _.each(this._runningListeningTo, function(args) {
      this.stopListening.apply(this, args);
    }, this);
  },

  /**
   * Overrides `Backbone.Event.on()`
   * If this `App` is running it will register the event for removal `onStop`
   *
   * @public
   * @method on
   * @memberOf AbstractApp
   * @returns {AbstractApp}
   */
  on: function() {
    if(this._isRunning) {
      this._runningEvents = (this._runningEvents || []);
      this._runningEvents.push(arguments);
    }
    return StateClass.prototype.on.apply(this, arguments);
  },

  /**
   * Overrides `Backbone.Event.listenTo()`
   * If this `App` is running it will register the listener for removal `onStop`
   *
   * @public
   * @method listenTo
   * @memberOf AbstractApp
   * @returns {AbstractApp}
   */
  listenTo: function() {
    if(this._isRunning) {
      this._runningListeningTo = (this._runningListeningTo || []);
      this._runningListeningTo.push(arguments);
    }
    return StateClass.prototype.listenTo.apply(this, arguments);
  }

});

export default AbstractApp;
<commit_msg>Override listenToOnce<commit_after>import _ from 'underscore';
import Marionette from 'backbone.marionette';
import StateClass from './state-class';

/**
 * StateClass with an `initialize` / `start` / `stop` / `destroy` lifecycle.
 * Registers events while running and cleans them up at `onStop`
 *
 * @public
 * @class AbstractApp
 * @memberOf Toolkit
 * @memberOf Marionette
 */
var AbstractApp = StateClass.extend({

  /**
   * Internal flag indiciate when `App` has started but has not yet stopped.
   *
   * @private
   * @type {Boolean}
   * @default false
   */
  _isRunning: false,

  /**
   * Internal flag indiciate when `App` has been destroyed
   *
   * @private
   * @type {Boolean}
   * @default false
   */
  _isDestroyed: false,

  /**
   * Set to true if a parent `App` should not be able to destroy this `App`.
   *
   * @type {Boolean|Function}
   * @default false
   */
  preventDestroy: false,

  /**
   * Set to true if `App` should be started after it is initialized.
   *
   * @type {Boolean|Function}
   * @default false
   */
  startAfterInitialized: false,

  /**
   * Set to true if `App` should be started after its parent starts.
   *
   * @type {Boolean|Function}
   * @default false
   */
  startWithParent: false,

  /**
   * Set to false if `App` should not stop after its parent stops.
   *
   * @type {Boolean|Function}
   * @default true
   */
  stopWithParent: true,

  /**
   * @public
   * @constructs AbstractApp
   * @param {Object} [options] - Settings for the App.
   * @param {Boolean} [options.startWithParent]
   * @param {Boolean} [options.stopWithParent]
   * @param {Boolean} [options.startAfterInitialized]
   * @param {Boolean} [options.preventDestroy]
   */
  constructor: function(options) {
    options = options || {};

    _.bindAll(this, 'start', 'stop');

    var pickOptions = [
      'startWithParent',
      'stopWithParent',
      'startAfterInitialized',
      'preventDestroy'
    ];

    _.extend(this, _.pick(options, pickOptions));

    // Will call initialize
    StateClass.call(this, options);

    if(_.result(this, 'startAfterInitialized')) {
      this.start(options);
    }
  },

  /**
   * Internal helper to verify if `App` has been destroyed
   *
   * @private
   * @method _ensureAppIsIntact
   * @memberOf AbstractApp
   * @throws AppDestroyedError - Thrown if `App` has already been destroyed
   */
  _ensureAppIsIntact: function() {
    if(this._isDestroyed) {
      throw new Marionette.Error({
        name: 'AppDestroyedError',
        message: 'App has already been destroyed and cannot be used.'
      });
    }
  },

  /**
   * Gets the value of internal `_isRunning` flag
   *
   * @public
   * @method isRunning
   * @memberOf AbstractApp
   * @returns {Boolean}
   */
  isRunning: function() {
    return this._isRunning;
  },

  /**
   * Sets the app lifecycle to running.
   *
   * @public
   * @method start
   * @memberOf AbstractApp
   * @param {Object} [options] - Settings for the App passed through to events
   * @event AbstractApp#before:start - passes options
   * @event AbstractApp#start - passes options
   * @returns {AbstractApp}
   */
  start: function(options) {
    this._ensureAppIsIntact();

    if(this._isRunning) {
      return this;
    }

    this.triggerMethod('before:start', options);

    this._isRunning = true;

    this.triggerMethod('start', options);

    return this;
  },

  /**
   * Sets the app lifecycle to not running.
   * Removes any listeners added during the running state
   *
   * @public
   * @method stop
   * @memberOf AbstractApp
   * @param {Object} [options] - Settings for the App passed through to events
   * @event AbstractApp#before:stop - passes options
   * @event AbstractApp#stop - passes options
   * @returns {AbstractApp}
   */
  stop: function(options) {
    if(!this._isRunning) {
      return this;
    }

    this.triggerMethod('before:stop', options);

    this._isRunning = false;

    this.triggerMethod('stop', options);

    this._stopRunningListeners();
    this._stopRunningEvents();

    return this;
  },

  /**
   * Gets the value of internal `_isDestroyed` flag
   *
   * @public
   * @method isDestroyed
   * @memberOf AbstractApp
   * @returns {Boolean}
   */
  isDestroyed: function() {
    return this._isDestroyed;
  },

  /**
   * Stops the `App` and sets it destroyed.
   *
   * @public
   * @method destroy
   * @memberOf AbstractApp
   */
  destroy: function() {
    this.stop();

    this._isDestroyed = true;

    StateClass.prototype.destroy.apply(this, arguments);
  },

  /**
   * Internal method to stop any registered events.
   *
   * @private
   * @method _stopRunningEvents
   * @memberOf AbstractApp
   */
  _stopRunningEvents: function(){
    _.each(this._runningEvents, function(args) {
      this.off.apply(this, args);
    }, this);
  },

  /**
   * Internal method to stop any registered listeners.
   *
   * @private
   * @method _stopRunningListeners
   * @memberOf AbstractApp
   */
  _stopRunningListeners: function(){
    _.each(this._runningListeningTo, function(args) {
      this.stopListening.apply(this, args);
    }, this);
  },

  /**
   * Overrides `Backbone.Event.on()`
   * If this `App` is running it will register the event for removal `onStop`
   *
   * @public
   * @method on
   * @memberOf AbstractApp
   * @returns {AbstractApp}
   */
  on: function() {
    if(this._isRunning) {
      this._runningEvents = (this._runningEvents || []);
      this._runningEvents.push(arguments);
    }
    return StateClass.prototype.on.apply(this, arguments);
  },

  /**
   * Overrides `Backbone.Event.listenTo()`
   * If this `App` is running it will register the listener for removal `onStop`
   *
   * @public
   * @method listenTo
   * @memberOf AbstractApp
   * @returns {AbstractApp}
   */
  listenTo: function() {
    if(this._isRunning) {
      this._runningListeningTo = (this._runningListeningTo || []);
      this._runningListeningTo.push(arguments);
    }
    return StateClass.prototype.listenTo.apply(this, arguments);
  },

  /**
   * Overrides `Backbone.Event.listenToOnce()`
   * If this `App` is running it will register the listener for removal `onStop`
   *
   * @public
   * @method listenToOnce
   * @memberOf AbstractApp
   * @returns {AbstractApp}
   */
  listenToOnce: function(){
    if(this._isRunning) {
      this._runningListeningTo = (this._runningListeningTo || []);
      this._runningListeningTo.push(arguments);
    }

    return StateClass.prototype.listenToOnce.apply(this, arguments);
  }
});

export default AbstractApp;
<|endoftext|>