diff --git a/NoThumbnail.psd b/NoThumbnail.psd deleted file mode 100644 index 5329b31..0000000 Binary files a/NoThumbnail.psd and /dev/null differ diff --git a/README.md b/README.md index 9ca30b2..a1019e9 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,11 @@ -# Allsky Website ![v2022-03-01+](https://img.shields.io/badge/Version-2022.03.01+-green.svg) -The "allsky-website" package allows you to display your images on a website, either on your Pi or on another machine. It can optionally add an overlay of constellations and other objects, as well as optional Aurora data from NOAA. +# Allsky Website ![v2023-05-01](https://img.shields.io/badge/Version-2023.05.01-green.svg) +The "allsky-website" package allows you to display your images on a website, either on your Pi or on another machine. It can optionally add an overlay of constellations and other objects, as well as optional Aurora data. ## Update -This version of the website works best with Allsky camera software version **v2022-03-01** or newer. +This version of the Website **requires** Allsky software version **v2023.05.01** or newer. ## Installation The installation script for installing on a Pi is documented on the [main Allsky GitHub page](https://github.com/thomasjacquin/allsky). -Once installed, there are several [configuration variables](https://github.com/thomasjacquin/allsky/wiki/allsky-website-Settings) you'll want to change. +Once installed, there are several configuration variables you'll need to change. Click on the "Documentation" link in the WebUI, then the link for Settings -> Allsky Website. diff --git a/allsky-font.css b/allsky-font.css index b2a92d2..cb0edae 100755 --- a/allsky-font.css +++ b/allsky-font.css @@ -28,3 +28,33 @@ content: "\e900"; } +@font-face { + font-family: 'mini-timelapse'; + src: url('fonts/mini-timelapse.eot?dynh3c'); + src: url('fonts/mini-timelapse.eot?dynh3c#iefix') format('embedded-opentype'), + url('fonts/mini-timelapse.ttf?dynh3c') format('truetype'), + url('fonts/mini-timelapse.woff?dynh3c') format('woff'), + url('fonts/mini-timelapse.svg?dynh3c#mini-timelapse') format('svg'); + font-weight: normal; + font-style: normal; + font-display: block; +} + +[class^="icon-"], [class*=" icon-"] { + /* use !important to prevent issues with browser extensions that change fonts */ + font-family: 'mini-timelapse' !important; + speak: never; + font-style: normal; + font-weight: normal; + font-variant: normal; + text-transform: none; + line-height: 1; + + /* Better Font Rendering =========== */ + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} + +.icon-mini-timelapse:before { + content: "\ea15"; +} diff --git a/allsky-logo.png b/allsky-logo.png index dcb202e..c9f9ac7 100644 Binary files a/allsky-logo.png and b/allsky-logo.png differ diff --git a/allsky.css b/allsky.css index f978ae2..4478b58 100755 --- a/allsky.css +++ b/allsky.css @@ -2,9 +2,19 @@ body { color:white; font-family: Arial, 'Helvetica Neue', Helvetica, sans-serif; background-color: black; - max-width: 960px; /* Optionally change to change size of image */ + max-width: 960px; margin: auto; } +img.current { + width: 100%; + max-width: 960px; +} + +#starmap_container { + position: absolute; + overflow: hidden; +} + .header { display:block; @@ -27,7 +37,13 @@ body { height: 35px; } +.personalLink { + text-align: center; + font-size: 150%; +} + .info { + font-size: 90%; padding: 8px; position: fixed; top: 150px; @@ -37,11 +53,12 @@ body { background-color: #333; border: 2px solid #888; border-right: none; + z-index: 1; } .info ul { list-style: none; - padding-left: 10px; + padding-left: 2px; margin-bottom: 0; } @@ -49,11 +66,10 @@ body { margin-right: 3px; } -#sidebar { +#leftSidebar { position: fixed; left: 0; top: 150px; - padding: 0; padding: 5px; border-top-right-radius: 5px; border-bottom-right-radius: 5px; @@ -63,39 +79,27 @@ body { z-index: 2; } -#sidebar li { +#leftSidebar li { list-style: none; padding: 2px 0 } -#sidebar li i { +#leftSidebar li i { cursor: pointer; color: #888; margin: 2px 0; } -#sidebar li i:hover, -#sidebar li i.active { +#leftSidebar li i:hover, +#leftSidebar li i.active { color: orange; } -.notification { - margin-left: 10px; -} - #imageContainer { - text-align: center; + margin: auto; } - -img.current { - width: 100%; - max-width: 960px; /* Optionally change to change size of image */ -} - -#starmap_container { - position: absolute; - overflow: hidden; - height: 720px; /* Optionally change or delete to change size of image */ +.imageContainer { + border: 1px solid #5a5a5a; } .starmap_credit { @@ -106,16 +110,25 @@ img.current { position: fixed; bottom: 10px; right: 10px; + opacity: 0.5; +} + +.diy:hover { + opacity: 1; } .diy a { color: white; } +.diy i { + margin-right: 5px; +} + .noImages { text-align: center; font-size: 200%; - color: yellow; + color: #ffc107; border: 2px solid gray; margin: 4px; } @@ -199,24 +212,29 @@ img.current { margin: 0 3px; } -.forecast .Low { +.forecast .Very_Quite, +.forecast .Quiet { color: green; } +.forecast .Unsettled, .forecast .Active { - color: yellow; + color: #ffc107; } -.forecast .High { +.forecast .Minor_Storm, +.forecast .Moderate_Storm { color: darkorange; } -.forecast .Extreme { - color: red; +.forecast .Strong_Storm, +.forecast .Severe_Storm, +.forecast .Extreme_Storm { + color: #dc3545; } .forecast .WARNING { /* for Aurora activity */ - color: yellow; + color: #ffc107; font-weight: bold; font-size: 125%; } @@ -227,6 +245,40 @@ img.current { margin-top: 30px; } +.virtualsky_help { + color: black; +} + .thumbnailError { - color: red; + color: #dc3545; } + +/* Messages on the home page */ +.msg { + background-color: #222; + text-align: center; + font-size: 145%; + font-weight: bold; + margin: 10px 0 20px 0; + padding: 20px 0 20px 0; + border-radius: 10px; +} +.error-msg { + color: #dc3545; + border: 3px dashed #dc3545; +} +.warning-msg { + color: #ffc107; + border: 3px dashed #ffc107; +} +.notice-msg { + color: white; + border: 3px solid white; +} + +@media screen and (max-width: 480px) { + .msg { + font-size: 100%; + } +} + diff --git a/config.js b/config.js deleted file mode 100644 index f711ad2..0000000 --- a/config.js +++ /dev/null @@ -1,21 +0,0 @@ -var config = { - comment: "See https://github.com/thomasjacquin/allsky/wiki/allsky-website-Settings for a description of these settings", - title: "XX_need_to_update_XX", - imageName: "/current/tmp/image.jpg", - location: "XX_need_to_update_XX", - latitude_longitude_comment: "latitude and longitude must be decimal numbers with OUT the trailing N, S, E, and W.", - latitude: 0, - longitude: 0, - az: 0, - camera: "XX_need_to_update_XX", - lens: "XX_need_to_update_XX", - computer: "XX_need_to_update_XX", - owner: "XX_need_to_update_XX", - overlaySize: 875, - overlayOffsetLeft: 0, - overlayOffsetTop: 0, - showOverlayAtStartup: false, - auroraForecast: false, - auroraMap: "XX_need_to_update_XX", - intervalSeconds: 5, -} diff --git a/controller.js b/controller.js index 4661d99..b619c50 100755 --- a/controller.js +++ b/controller.js @@ -1,43 +1,175 @@ -var usingNewVirtualSky = typeof S != "undefined" && typeof S.virtualsky == "function"; -console.log("usingNewVirtualSky=" + usingNewVirtualSky); - var app = angular.module('allsky', ['ngLodash']); +var overlayBuilt = false; // has the overlay been built yet? + +var virtualSkyData = null; +var sunData = "data.json"; // contains sunrise/sunset times and related data +var configData = "configuration.json" // contains web configuration data + +// This returns the height INCLUDING the border: $("#imageContainer").css('height') +// This returns the height NOT including the border: $("#imageContainer").height() + +// These two are used by virtualsky.js to set the overlay width and height, +// if there was a difference. +var overlayWidth = 0, overlayHeight = 0; +var overlayWidthMax = 0, overlayHeightMax = 0; +var starmapWidth = 0, starmapHeight = 0; +var wasDiff = true; +var last_s_iW = 0, last_s_iH = 0; +var icWidth = 0; +var icHeight = 0; +var icImageAspectRatio = 0; +var overlayAspectRatio = 0; +var myLatitude = 0, myLongitude = 0; + $(window).resize(function () { - buildOverlay(); + if (overlayBuilt) { // only rebuild if already built once + var newW = $("#imageContainer").width(); + var newH = $("#imageContainer").height() +// console.log("#imageContainer newW=" + newW + ", newH=" + newH); + + $("#starmap_container").css("width", newW + "px").css("height", newH + "px"); + + var diffW = newW - icWidth; + // Scale the height based on the aspect ratio of the image. +// console.log("newW=" + newW + ", icWidth=" + icWidth); +//x var diffH = (newH - icHeight) * overlayAspectRatio; + var diffH = (newH - icHeight); + icWidth = newW; + icHeight = newH; + + if (diffW == 0 && diffH == 0) { + wasDiff = false; +// console.log(">>> No change in image size."); + return; + } + + wasDiff = true; + + // TODO: probably also need to adjust #stamap's margin-left and margin-right. + + // This holds the starmap button, so needs to resize + starmapWidth += diffW; + starmapHeight += diffH; + $("#starmap").css("width", starmapWidth + "px").css("height", starmapHeight + "px"); + + // Shrinking the window makes the overlay shrink too fast for some reason. + // Got the fudge factor by trial and error. + if (diffW < 0) { + var fudge = 0.95; + diffW *= fudge; +// console.log("diffH=" + diffH + ", overlayAspectRatio=" + overlayAspectRatio); + diffH = (diffH / overlayAspectRatio) * fudge; + } + +// console.log("== diffW= " + diffW + ", diffH= " + diffH); + overlayWidth += diffW; + if (overlayWidth > overlayWidthMax) overlayWidth = overlayWidthMax; + overlayHeight += diffH; + if (overlayHeight > overlayHeightMax) overlayHeight = overlayHeightMax; +// console.log("== setting overlayWidth= " + overlayWidth + ", overlayHeight= " + overlayHeight); + $("#starmap_inner") + .css("width", overlayWidth + "px") + .css("height", overlayHeight + "px"); + } }); function buildOverlay(){ - var planetarium; - $.ajax({ - url: "virtualsky.json" + '?_ts=' + new Date().getTime(), - cache: false - }).done( - function (data) { - // This is to scale the overlay when the window is resized - // Newer versions support both width and height. - var width; - if (config.overlayWidth) { - width = config.overlayWidth; - } else { - width = config.overlaySize; + if (overlayBuilt) { + S.virtualsky(virtualSkyData); + } else { + $.ajax({ + // No need for ?_ts= since $.ajax adds one + url: configData, + cache: false + }).done( + function (data) { + var c = data.config; + // "config" was defined in index.php to include ALL the variables we need, + // including ones not in the "config" section of the configuration file. + // However, "array" types like "colour" aren't handled in index.php. + + // TODO: I tried not doing the ajax call, but the overlay wouldn't show. + // It's a shame - there's no reason to re-read the file. + + virtualSkyData = c; + virtualSkyData.latitude = myLatitude; + virtualSkyData.longitude = myLongitude; + + // These variables have different names in virtualsky.js and our config file. + virtualSkyData.width = c.overlayWidth; + virtualSkyData.height = c.overlayHeight; + + S.virtualsky(virtualSkyData); // Creates overlay + overlayBuilt = true; + + // Offset of overlay + $("#starmap") + .css("margin-top", c.overlayOffsetTop + "px") + .css("margin-left", c.overlayOffsetLeft + "px"); + + // max-width of #imageContainer set in index.php based on width user specified (imageWidth) + icWidth = $("#imageContainer").width(); + icHeight = $("#imageContainer").height(); + icImageAspectRatio = icWidth / icHeight; + + $("#starmap_container").css("width", icWidth + "px").css("height", icHeight + "px"); + + overlayWidth = c.overlayWidth; + overlayHeight = c.overlayHeight; + overlayAspectRatio = overlayWidth / overlayHeight; +// console.log("overlay aspect ratio=" + overlayAspectRatio); + + overlayHeightMax = overlayHeight; // never go larger than what user specified + overlayWidthMax = overlayWidth; + + starmapWidth = $("#starmap").width(); + starmapHeight = $("#starmap").height(); + + // TODO: this assumes the border is 1px on each side. + var imageWidth = c.imageWidth - (config.imageBorder ? 2 : 0); + if (icWidth < imageWidth) { + // The actual image on the screen is smaller than the imageWidth requested by the user. + // Determine the percent smaller, then make the overlay that percent smaller. +console.log("icWidth=" + icWidth + ", imageWidth=" + imageWidth); + var percentSmaller = icWidth / c.imageWidth; + + // #starmap holds the starmap button, so needs to resize it as well. + var w = starmapWidth * percentSmaller; + var h = w / overlayAspectRatio; + $("#starmap") + .css("width", Math.round(w, 0) + "px") + .css("height", Math.round(h, 0) + "px"); + starmapWidth = w; + starmapHeight = h; + + // TODO: probably also need to adjust #stamap's margin-left and margin-right if + + // percentSmaller makes the overlay TOO small, so change it. + percentSmaller *= 1.04; +console.log("== Decreasing overlay by " + percentSmaller*100 + " percent" + " (overlayWidth was " + overlayWidth + ")"); + overlayWidth = overlayWidth * percentSmaller; + overlayHeight = overlayWidth / overlayAspectRatio; + $("#starmap_inner") + .css("width", Math.round(overlayWidth, 0) + "px") + .css("height", Math.round(overlayHeight, 0) + "px"); + + } + + // id="live_container" is where the image goes. + var image_w = c.imageWidth; + var image_h = Math.round((image_w / icImageAspectRatio), 0); +// console.log("icHeight=" + icHeight + ", icWidth=" + icWidth); +// console.log("overlayHeight=" + overlayHeight + ", overlayWidth=" + overlayWidth); +// console.log("image_h=" + image_h + ", image_w=" + image_w); + + // Keep track of the sizes. virtualsky.js seems to change them, + // so we need to change them based on our last known sizes. + last_s_iW = $("#starmap_inner").width(); + last_s_iH = $("#starmap_inner").height(); } - data.width = window.innerWidth < width ? window.innerWidth : width; - if (config.overlayHeight) - data.height = config.overlayHeight; - else - data.height = data.width; // default is square - data.latitude = config.latitude; - data.longitude = config.longitude; - data.az = config.az; - if (usingNewVirtualSky) - planetarium = S.virtualsky(data); - else - planetarium = typeof $.virtualsky == "undefined" ? undefined : $.virtualsky(data); - $("#starmap").css("margin-top", config.overlayOffsetTop + "px"); - $("#starmap").css("margin-left", config.overlayOffsetLeft + "px"); - } - ); + ); + } }; function compile($compile) { @@ -63,40 +195,109 @@ function compile($compile) { }; } -var configNotSet = false; // Has the config.js file been updated by the user? +var configNotSet = false; // Has the configuration file been updated by the user? +var needToUpdate = "XX_NEED_TO_UPDATE_XX"; // must match what's in configData + +function convertLatitude(sc, lat) { // sc == scope + var convertToString = false; + var len, direction; + + if (typeof lat === "string") { + sc.s_latitude = lat; // string version + + len = lat.length; + direction = lat.substr(len-1, 1).toUpperCase(); + if (direction == "N") + sc.latitude = lat.substr(0, len-2) * 1; + else if (direction == "S") + sc.latitude = lat.substr(0, len-2) * -1; + else { + // a number with quotes around it which is treated as a string + sc.latitude = lat * 1; + convertToString = true; + } + } else { + sc.latitude = lat; + convertToString = true; + } + + if (convertToString) { + if (lat >= 0) + sc.s_latitude = lat + "N"; + else + sc.s_latitude = -lat + "S"; + } + + return sc.latitude; +} + +function convertLongitude(sc, lon) { + var convertToString = false; + var len, direction; + + if (typeof lon === "string") { + sc.s_longitude = lon; + + len = config.longitude.length; + direction = lon.substr(len-1, 1).toUpperCase(); + if (direction == "E") + sc.longitude = lon.substr(0, len-2) * 1; + else if (direction == "W") + sc.longitude = lon.substr(0, len-2) * -1; + else { + // a number with quotes around it which is treated as a string + sc.longitude = lon * 1; + convertToString = true; + } + } else { + sc.longitude = lon; + convertToString = true; + } + + if (convertToString) { + if (config.longitude >= 0) + sc.s_longitude = lon + "E"; + else + sc.s_longitude = -lon + "W"; + } + + return sc.longitude; +} function AppCtrl($scope, $timeout, $http, _) { - var overlayBuilt = false; // has the overlay been built yet? - if (! usingNewVirtualSky) { - overlayBuilt = true; - buildOverlay(); - } + // Allow latitude and longitude to have or not have N, S, E, W, + // but in the popout, always use the letters for consistency. + // virtualsky.js expects decimal numbers so we need both. + // Need to convert them before building the overlay. + $scope.latitude = convertLatitude($scope, config.latitude); + myLatitude = $scope.latitude; + $scope.longitude = convertLongitude($scope, config.longitude); + myLongitude = $scope.longitude; - $scope.imageURL = "loading.jpg"; + $scope.imageURL = config.loadingImage; $scope.showInfo = false; $scope.showOverlay = config.showOverlayAtStartup; - if ($scope.showOverlay && usingNewVirtualSky) { - overlayBuilt = true; - console.log("@@ Building overlay..."); + if ($scope.showOverlay) { + console.log("@@ Building overlay at startup for showOverlay..."); buildOverlay(); } $scope.notification = ""; - $scope.title = config.title; - if ($scope.title == "XX_need_to_update_XX") { - // Could (or should?) check other variables for not being set. - // Or assume if the title is set, everything else is too. + if (config.title == needToUpdate) { + // Assume if the title isn't set, nothing else is either. configNotSet = true; + $scope.notification = formatMessage("Please update the '" + configData + "' file.
Replace the '" + needToUpdate + "' entries and check all other entries.
Refresh your browser when done.", msgType="error"); + return; } $scope.location = config.location; - $scope.latitude = config.latitude; - $scope.longitude = config.longitude; $scope.camera = config.camera; $scope.lens = config.lens; $scope.computer = config.computer; $scope.owner = config.owner; $scope.auroraForecast = config.auroraForecast; $scope.imageName = config.imageName; + $scope.AllskyVersion = config.AllskyVersion; + $scope.AllskyWebsiteVersion = config.AllskyWebsiteVersion; function getHiddenProp() { var prefixes = ['webkit', 'moz', 'ms', 'o']; @@ -113,34 +314,19 @@ function AppCtrl($scope, $timeout, $http, _) { // otherwise it's not supported return null; } + var hiddenProperty = getHiddenProp(); function isHidden() { - var prop = getHiddenProp(); - if (!prop) return false; -//return false; // xxxxxxxxx for testing, uncomment to make never hidden - - return document[prop]; + if (! hiddenProperty) return false; + return document[hiddenProperty]; } - // If the data.json file wasn't found, or for some reason "sunset" isn't in it, - // the routine that reads data.json will set "dataMissingMessage" so display it. + // If the "sunData" file wasn't found, or for some reason "sunset" isn't in it, + // the routine that reads "sunData" will set "dataMissingMessage" so display it. var dataMissingMessage = ""; function formatMessage(msg, msgType) { - if (msgType === "error") { - textColor = "red"; - borderColor = "red"; - borderStyle = "dashed"; - } else if (msgType === "warning") { - textColor = "yellow"; - borderColor = "yellow"; - borderStyle = "dashed"; - } else { - textColor = "white"; - borderColor = "white"; - borderStyle = "solid"; - } - return("
" + msg + "
"); + return("
" + msg + "
"); } // How old should the data file be, or the sunset time be, in order to warn the user? @@ -150,9 +336,7 @@ function AppCtrl($scope, $timeout, $http, _) { // The defaultInterval should ideally be based on the time between day and night images - why // check every 5 seconds if new images only appear once a minute? - var defaultInterval = (5 * 1000); // Time to wait between normal images. - if (config.intervalSeconds) defaultInterval = config.intervalSeconds * 1000; - + var defaultInterval = (config.intervalSeconds * 1000); // Time to wait between normal images. var intervalTimer = defaultInterval; // Amount of time we're currently waiting // If we're not taking pictures during the day, we don't need to check for updated images as often. @@ -161,7 +345,7 @@ function AppCtrl($scope, $timeout, $http, _) { // there's no need to check until nightfall. // However, in case the image DOES change, check every minute. Seems like a good compromise. // Also, in both cases, if we wait too long, when the user returns to the web page after - // it being hidden, they'll have to wait a long time for the page to update. + // it's been hidden, they'll have to wait a long time for the page to update. var auroraIntervalTimer = (60 * 1000); // seconds var auroraIntervalTimerShortened = (15 * 1000); // seconds var nonAuroraIntervalTimer = (60 * 1000); // seconds @@ -173,12 +357,16 @@ function AppCtrl($scope, $timeout, $http, _) { var lastType = ""; var loggedTimes = false; var numImagesRead = 0; + var numCalls = 0; $scope.getImage = function () { var url= ""; var imageClass= ""; - if (! isHidden()) { + // Go through the loop occassionally even when hidden so we re-read the sunData file + // if needed. + if (! isHidden() || ++numCalls % 5 == 0) { if (configNotSet) { - $scope.notification = formatMessage("Please update the 'config.js' file.
Replace the 'XX_need_to_update_XX' entries and check all other entries.
Refresh your browser when done.", msgType="error"); +// xxxxxxxxx test deleting the "if" portion + $scope.notification = formatMessage("Please update the '" + configData + "' file.
Replace the '" + needToUpdate + "' entries and check all other entries.
Refresh your browser when done.", msgType="error"); } else if (dataMissingMessage !== "") { $scope.notification = formatMessage(dataMissingMessage, msgType = dataFileIsOld ? "warning": "error"); } else { @@ -208,8 +396,8 @@ function AppCtrl($scope, $timeout, $http, _) { if (! dataFileIsOld) { //console.log("DEBUG: sunset daysOld=" + daysOld); if (daysOld > oldDataLimit) { - var oldMsg = "WARNING: sunset is " + daysOld + " days old."; - $scope.notification = formatMessage(oldMsg + "
Check Allsky log file if 'postData.sh' has been running successfully at the end of nighttime.", msgType="warning"); + var oldMsg = "WARNING: sunset data is " + daysOld + " days old."; + $scope.notification = formatMessage(oldMsg + "
See the 'Troubleshooting > Allsky Website' documentation page for how to resolve this.", msgType="warning"); } } @@ -229,7 +417,7 @@ function AppCtrl($scope, $timeout, $http, _) { } // The sunrise and sunset times change every day, and the user may have changed - // streamDaytime, so re-read the data.json file when something changes. + // streamDaytime, so re-read the "sunData" file when something changes. if (is_nighttime) { // Only add to the console log once per message type if (lastType !== "nighttime") { @@ -314,13 +502,15 @@ function AppCtrl($scope, $timeout, $http, _) { console.log(" m_now = " + m_now.format("YYYY-MM-DD HH:mm:ss")); if (oldMsg !== "") console.log(" > " + oldMsg); - console.log(" Times:"); console.log(" m_now="+m_nowTime + ", m_sunrise="+m_sunriseTime + ", m_sunset="+m_sunsetTime); console.log(" beforeSunriseTime = " + beforeSunriseTime); console.log(" afterSunsetTime = " + afterSunsetTime); } - var img = $("").attr('src', url + '?_ts=' + new Date().getTime()).addClass(imageClass) +// TODO: Is there a way to specify not to cache this without using "?_ts" ? + var img = $("") + .attr('src', url + '?_ts=' + new Date().getTime()) + .addClass(imageClass) .on('load', function() { if (!this.complete || typeof this.naturalWidth === "undefined" || this.naturalWidth === 0) { alert('broken image!'); @@ -334,22 +524,17 @@ function AppCtrl($scope, $timeout, $http, _) { // Don't re-read after the 1st image of this period since we read it right before the image. if (rereadSunriseSunset && numImagesRead > 1) { - // console.log("XXX Re-reading data.json"); $scope.getSunRiseSet(); - } else if (rereadSunriseSunset) { - console.log("XXX Not rereading data.json, numImagesRead=" + numImagesRead); - } else { - // console.log("XXX rereadSunriseSunset=" + rereadSunriseSunset); } - } + } // if (! isHidden())) }; - // Set a default sunrise if we can't get it from data.json. + // Set a default sunrise if we can't get it from "sunData". var usingDefaultSunrise = false; function getDefaultSunrise(today) { return(moment(new Date(today.getFullYear(), today.getMonth(), today.getDate(), 6, 0, 0))); } - // Set a default sunset if we can't get it from data.json. + // Set a default sunset if we can't get it from "sunData". var usingDefaultSunset = false; function getDefaultSunset(today) { return(moment(new Date(today.getFullYear(), today.getMonth(), today.getDate(), 18, 0, 0))); @@ -368,8 +553,10 @@ function AppCtrl($scope, $timeout, $http, _) { $scope.getSunRiseSet = function () { dataFileIsOld = false; now = new Date(); - console.log("Read data.json at " + moment(now).format("MM-DD h:mm:ss a") + ":"); - var url = "data.json" + '?_ts=' + now.getTime(); + var url = sunData; +// TODO: is ?_ts needed if we are not cache'ing ? + url += '?_ts=' + now.getTime(); + console.log("Read " + sunData + " on " + moment(now).format("MM-DD h:mm:ss a") + ":"); $http.get(url, { cache: false }).then( @@ -378,26 +565,29 @@ function AppCtrl($scope, $timeout, $http, _) { $scope.sunrise = moment(data.data.sunrise); usingDefaultSunrise = false; } else if (! usingDefaultSunrise) { +// TODO: Is this needed with the new Allsky Website, given that it only works with the new Allsky? // Older versions of allsky/scripts/postData.sh didn't include sunrise. $scope.sunrise = getDefaultSunrise(now); usingDefaultSunrise = true; - console.log(" ********** WARNING: 'sunrise' not defined in data.json"); + console.log(" ********** WARNING: 'sunrise' not defined in " + sunData); } if (data.data.sunset) { $scope.sunset = moment(data.data.sunset); usingDefaultSunset = false; dataMissingMessage = ""; } else if (! usingDefaultSunset) { +// TODO: Is this needed with the new Allsky Website, given that it only works with the new Allsky? $scope.sunset = getDefaultSunset(now); usingDefaultSunset = true; - dataMissingMessage = "ERROR: 'sunset' not defined in 'data.json', using " + $scope.sunset.format("h:mm a") + ".
Run 'allsky/scripts/postData.sh'.
Refresh your browser when done."; - console.log(" ********** WARNING: 'sunset' not defined in data.json"); + dataMissingMessage = "ERROR: 'sunset' not defined in '" + sunData + "', using " + $scope.sunset.format("h:mm a") + ".
Run 'allsky/scripts/postData.sh'.
Refresh your browser when done."; + console.log(" ********** ERROR: 'sunset' not defined in " + sunData); } if (data.data.streamDaytime) { $scope.streamDaytime = data.data.streamDaytime === "true"; } else { +// TODO: Is this needed with the new Allsky Website, given that it only works with the new Allsky? $scope.streamDaytime = true; - console.log(" ********** WARNING: 'streamDaytime' not defined in data.json"); + console.log(" ********** WARNING: 'streamDaytime' not defined in " + sunData); } // Get when the file was last modified so we can warn if it's old @@ -419,16 +609,16 @@ function AppCtrl($scope, $timeout, $http, _) { if (typeof x === "object") { // success - "x" is a Date object lastModifiedSunriseSunsetFile = moment(x); var duration = moment.duration(moment(now).diff(lastModifiedSunriseSunsetFile)); -// console.log("DEBUG: data.json is " + duration.days() + " days old"); +// console.log("DEBUG: " + sunData + " is " + duration.days() + " days old"); if (duration.days() > oldDataLimit) { dataFileIsOld = true; - var msg = "WARNING: data.json is " + duration.days() + " days old."; + var msg = "WARNING: " + sunData + " is " + duration.days() + " days old."; console.log(msg); dataMissingMessage = msg + "
Check Allsky log file if 'postData.sh' has been running successfully at the end of nighttime."; } } else { - console.log("fetchHeader(" + url + ") returned " + x); + console.log("fetchHeader(" + sunData + ") returned " + x); } writeSunriseSunsetToConsole(); @@ -442,8 +632,8 @@ function AppCtrl($scope, $timeout, $http, _) { usingDefaultSunset = true; $scope.streamDaytime = true; - dataMissingMessage = "ERROR: 'data.json' file not found, using " + $scope.sunset.format("h:mm a") + " for sunset.
Set 'POST_END_OF_NIGHT_DATA=true' in config.sh then run 'allsky/scripts/postData.sh'.
Refresh your browser when done."; - console.log(" *** Unable to read file"); + dataMissingMessage = "ERROR: '" + sunData + " file not found, using " + $scope.sunset.format("h:mm a") + " for sunset.
Run 'allsky/scripts/postData.sh'.
Refresh your browser when done."; + console.log(" *** Unable to read '" + sunData + "' file"); writeSunriseSunsetToConsole(); $scope.getImage() @@ -467,29 +657,28 @@ function AppCtrl($scope, $timeout, $http, _) { $scope.toggleOverlay = function () { $scope.showOverlay = !$scope.showOverlay; - if (usingNewVirtualSky && ! overlayBuilt && $scope.showOverlay) { - console.log("@@@@ Building overlay..."); - overlayBuilt = true; - // The new 0.7.7 version of VirtualSky doesn't show the overlay unless buildOverlay() is called here. - buildOverlay(); - } + if (! overlayBuilt && $scope.showOverlay) { + console.log("@@@@ Building overlay from toggle..."); + // Version 0.7.7 of VirtualSky doesn't show the overlay unless buildOverlay() is called. + buildOverlay(); + } $('.options').fadeToggle(); $('#starmap_container').fadeToggle(); }; - $scope.getScale = function (index) { + $scope.getScale = function (index) { // based mostly on https://auroraforecast.is/kp-index/ var scale = { - 0: "Low", - 1: "Low", - 2: "Low", - 3: "Active", - 4: "High", - 5: "Extreme", - 6: "Extreme", - 7: "Extreme", - 8: "Extreme", - 9: "Extreme", + 0: "Extremely_Quiet", + 1: "Very_Quiet", + 2: "Quiet", + 3: "Unsettled", + 4: "Active", + 5: "Minor_storm", + 6: "Moderate_storm", + 7: "Strong_storm", + 8: "Severe_storm", + 9: "Extreme_storm", 100: "WARNING" }; return scale[index]; @@ -502,7 +691,7 @@ function AppCtrl($scope, $timeout, $http, _) { var total = _.sumBy(data, function (row) { return parseInt(row[field]); }); - return Math.round(total / 7); + return Math.round(total / data.length); // return average } function getDay(number) { @@ -537,3 +726,4 @@ angular .directive('compile', ['$compile', compile]) .controller("AppCtrl", ['$scope', '$timeout', '$http', 'lodash', AppCtrl]) ; + diff --git a/fonts/mini-timelapse.eot b/fonts/mini-timelapse.eot new file mode 100644 index 0000000..9ab213b Binary files /dev/null and b/fonts/mini-timelapse.eot differ diff --git a/fonts/mini-timelapse.svg b/fonts/mini-timelapse.svg new file mode 100644 index 0000000..40cec60 --- /dev/null +++ b/fonts/mini-timelapse.svg @@ -0,0 +1,11 @@ + + + +Generated by IcoMoon + + + + + + + diff --git a/fonts/mini-timelapse.ttf b/fonts/mini-timelapse.ttf new file mode 100644 index 0000000..dfb5c8a Binary files /dev/null and b/fonts/mini-timelapse.ttf differ diff --git a/fonts/mini-timelapse.woff b/fonts/mini-timelapse.woff new file mode 100644 index 0000000..100de7b Binary files /dev/null and b/fonts/mini-timelapse.woff differ diff --git a/functions.php b/functions.php index 1c38d8b..e6ceb15 100644 --- a/functions.php +++ b/functions.php @@ -3,22 +3,65 @@ // On Pi's, this placeholder gets replaced with ${ALLSKY_CONFIG}. // On other machines it won't and references to it will silently fail. define('ALLSKY_CONFIG', 'XX_ALLSKY_CONFIG_XX'); -// The issue is how to determine if we're on a Pi without using -// the exec() function which is often disabled on remote machines. -// And we can't do @exec() to see if it works because that can -// display a message in the user's browser window. -// Checking if exec() is disabled doesn't always work, for example on a user's NAS -// the function isn't disabled, but the website isn't on a Pi. - -// If on a Pi, check that the placholder was replaced. -$isarm = preg_match("/(arm|aarch)/",php_uname()); -if ($isarm && ALLSKY_CONFIG == "XX_ALLSKY_CONFIG" . "_XX") { + +// Look for $var in the $a array and return its value. +// If not found, return $default. +// If the value is a boolean and is false, an empty string is returned (not a 0). +// A true boolean value returns 1. We want to return 0 if false. +// Ditto for $default. +function v($var, $default, $a) { + if (isset($a[$var])) { + $value = $a[$var]; + if (gettype($value) === "boolean" && $value == "") + return(0); + else + return($value); + } else if (gettype($default) === "boolean" && $default == "") { + return(0); + }else { + return($default); + } +} + +$configurationFileName = "configuration.json"; +// Read the configuration file. +// Some settings impact this page, some impact the constellation overlay. +if (! isset($configFilePrefix)) + $configFilePrefix = ""; +$configuration_file = $configFilePrefix . $configurationFileName; +if (! file_exists($configuration_file)) { + echo "

"; + echo "ERROR: Missing configuration file '$configuration_file'. Cannot continue."; + echo "

"; + exit; +} +$settings_str = file_get_contents($configuration_file, true); +$settings_array = json_decode($settings_str, true); +if ($settings_array == null) { + echo "

"; + echo "ERROR: Bad configuration file '$configurationFileName'. Cannot continue."; + echo "
Check for missing quotes or commas at the end of every line (except the last one)."; + echo "

"; + echo "
$settings_str
"; + exit; +} +$onPi = v("onPi", true, $settings_array['homePage']); + +// If on a Pi, check that the placeholder was replaced. +if ($onPi && ALLSKY_CONFIG == "XX_ALLSKY_CONFIG" . "_XX") { // This file hasn't been updated yet after installation. echo "
"; echo ""; - echo "Please run the following from the 'allsky' directory before using the Website:"; + echo "If this Website is running on a Pi, please run the following:"; + echo ""; + echo "
"; + echo "        cd ~/allsky"; + echo "
        website/install.sh --update"; + echo "
"; + + echo ""; + echo "

If instead, this Website is being run on a remote server, change the onPi variable in the '$configurationFileName' configuration file to false."; echo "
"; - echo "

website/install.sh --update"; echo "
"; exit; } @@ -88,9 +131,14 @@ function get_variable($file, $searchfor, $default) // Format: [stuff]$searchfor=$value or [stuff]$searchfor="$value" // Need to delete [stuff]$searchfor= and optional double quotes - $last = $matches[0][$num_matches - 1]; // get the last one - $last = explode( '=', $last)[1]; // get everything after equal sign - $last = str_replace($double_quote, "", $last); + $last = $matches[0][$num_matches - 1]; // get the last one + $both = explode( '=', $last); + if (isset($both[1])) { + $last = $both[1]; // everything after equal sign + $last = str_replace($double_quote, "", $last); + } else { + return($default); // nothing after "=" + } return($last); } else { return($default); @@ -198,6 +246,7 @@ function make_thumb_from_video($src, $dest, $desired_width, $attempts) else $sec = "00"; $command = "ffmpeg -loglevel warning -ss 00:00:$sec -i '$src' -filter:v scale='$desired_width:-1' -frames:v 1 '$dest' 2>&1"; + $output = array(); exec($command, $output); if (file_exists($dest)) { if (filesize($dest) === 0) { @@ -264,7 +313,7 @@ function display_thumbnails($dir, $file_prefix, $title) echo "
$back_button $title
"; echo "
\n"; - $thumbnailSizeX = get_variable(ALLSKY_CONFIG .'/config.sh', 'THUMBNAILSIZE_X=', '100'); + $thumbnailSizeX = get_variable(ALLSKY_CONFIG .'/config.sh', 'THUMBNAIL_SIZE_X=', '100'); foreach ($files as $file) { // The thumbnail should be a .jpg. $thumbnail = preg_replace($ext, ".jpg", "$dir/thumbnails/$file"); @@ -295,4 +344,39 @@ function display_thumbnails($dir, $file_prefix, $title) echo "
"; // clears "float" from archived-files echo "

"; } + +// Read and decode a json file, returning the decoded results or null. +// On error, display the specified error message +function get_decoded_json_file($file, $associative, $errorMsg) { + if (! file_exists($file)) { + echo "
"; + echo "$errorMsg:"; + echo "
File '$file' missing!"; + echo "
"; + return null; + } + + $str = file_get_contents($file, true); + if ($str === "") { + echo "
"; + echo "$errorMsg:"; + echo "
File '$file' is empty!"; + echo "
"; + return null; + } + + $str_array = json_decode($str, $associative); + if ($str_array == null) { + echo "
"; + echo "$errorMsg:"; + echo "
" . json_last_error_msg(); + $cmd = "json_pp < $file 2>&1"; + exec($cmd, $output); + echo "
" . implode("
", $output); + echo "
"; + return null; + } + return $str_array; +} + ?> diff --git a/index.php b/index.php index 1dfdc81..58b20e0 100644 --- a/index.php +++ b/index.php @@ -1,81 +1,254 @@ - + Making"; + if (! mkdir($thumb_dir, 0775, true)) { // true == recursive + $ok = false; + $last_error = error_get_last(); + echo "\nUnable to make '$thumb_dir' directory: " . $last_error["message"]; + } + } + } + + // TODO: add other checks here + + if (! $ok) exit; + + if ($debug) echo "\n"; // ends any lines already output + echo "SUCCESS\n"; + exit; + } +?> - Allsky - {{title}} + + + config = {\n"; + foreach ($config as $var => $val) { // ok to have comma after last entry + echo "\t\t$var: "; + if ($val === true || $val === false || $val === null || is_numeric($val)) { + echo var_export($val, true) . ",\n"; + } else if (is_array($val)) { + echo '"[array]",' . "\n"; + } else { + echo '"' . str_replace('"', '\"', $val) . '",' . "\n"; + } + } + // Add additional variable(s) from $homePage that are needed in controller.js. + echo "\t\timageBorder: $imageBorder,\n"; + echo "\t\ttitle: " . '"' . $title . '",' . "\n"; + echo "\t\tloadingImage: " . '"' . $loadingImage . '"'; + + echo "\n\t}"; + echo "\n\t\n"; + ?> + + <?php echo $title ?> + - - - - + + + + + + - + + - - + + - - - + + + - + + - +>
-
{{title}}
-
+
+
Aurora activity: {{key}}: {{getScale(val)}}
-
-
-
-
    -
  •   Location: {{location}}
  • -
  • Latitude: {{latitude < 0 ? latitude * -1 + 'S' : latitude + 'N'}}
  • -
  • Longitude: {{longitude < 0 ? longitude * -1 + 'W' : longitude + 'E'}}
  • -
  •   Camera: {{camera}}
  • -
  •   Lens: {{lens}}
  • -
  •   Computer: {{computer}}
  • -
  •   Owner: {{owner}}
  • -
+
+"; + if ($personalLink_prelink !== "") echo "$personalLink_prelink"; + echo "$personalLink_message"; + echo "
"; + } +?> +
+ 0) { + echo "\t
\n"; + echo "\t\t\n"; + echo "\t
\n"; + } +?> -