"use strict"; /* eslint-disable indent */ // // Example Vue App // // Created by Milad Nazeri on 2018-10-11 // Modified from AppUi.js by Howard Stearns on 2 Nov 2016 // Copyright 2016 High Fidelity, Inc. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // /* globals Tablet, Script, HMD, Controller, AccountServices, Menu */ (function () { // BEGIN LOCAL_SCOPE // Dependencies // ///////////////////////////////////////////////////////////////////////// var AppUi = Script.require('./appUi.js?v12311'); // UTC month 0 = January // UTC month 10 = November var UNLOAD_DATE = new Date(Date.UTC(2018, 10, 21, 22, 45)); var BUTTON_NAME = "VOTEAPP"; var VOTE_APP_NAME = "voteApp.js"; var UNLOAD_TIME = 10000; // need UTC TIME FOR FINAL // Consts // ///////////////////////////////////////////////////////////////////////// var URL = Script.resolvePath("./html/Tablet.html?1"), EVENT_BRIDGE_OPEN_MESSAGE = "eventBridgeOpen"; var GOOGLE_SCRIPTS_URL = "https://script.google.com/macros/s/AKfycbyCnTK2w8gAuPmNzLUnAE4dMJNRtUarjlHFrjKPFBRRQsiy5Q/exec"; var EVENT_NAME = "Futvrelands_11_17_2018"; var DOMAIN = "domain"; var AVATAR = "avatar"; var setupNotLoggedIn = false; // DATA MANAGEMENT var domainsInfo = {}; // will use Object.keys to get array var avatarsInfo = {}; // array for now var firstLoad = true; var VOTE_APP_SETTINGS_NAME = "Futvrelands_voteApp"; var DEFAULT_VOTE_APP_SETTINGS = { domains: [], voted: { avatar: "", domain: "" } }; // types var VOTE_AVATARS_GOOGLE = "voteAvatars"; var VOTE_DOMAINS_GOOGLE = "voteDomains"; var GET_INFO_GOOGLE = "getInfo"; var DEBUG = false; // Init // ///////////////////////////////////////////////////////////////////////// var ui; var dataStore = { unload: false, loading: true, loggedin: true, voted: { domain: false, avatar: false }, openPolls: { avatar: false, domain: false, }, visitedAllDomains: false, domains: [], avatars: [] }; // Constructors // ///////////////////////////////////////////////////////////////////////// // Collections // ///////////////////////////////////////////////////////////////////////// function shuffle(a) { var j, x, i; for (i = a.length - 1; i > 0; i--) { j = Math.floor(Math.random() * (i + 1)); x = a[i]; a[i] = a[j]; a[j] = x; } return a; } // GOOGLE SHEET FUNCTIONS function getGoogleData() { // REQUEST list of domains var params = encodeURLParams({ type: GET_INFO_GOOGLE }); var onComplete = function (request) { if (DEBUG) { print("recieved google info"); } // print ("GOOGLE INFO IS :", JSON.stringify(request)); try { var gInfo = JSON.parse(request.responseText); } catch (e) { console.error("Error parsing request in sendDomainInfoRequest"); } print ("GOOGLE INFO IS :", JSON.stringify(gInfo)); compareGoogleData(gInfo); }; var onError = function () { if (DEBUG) { print("Error in VoteApp.js: Issue in getGoogleData()"); } }; sendRequest(GOOGLE_SCRIPTS_URL, params, onComplete, onError); } function hasUserVoted(type) { // var voted = false; var voteAppSettings = getVoteAppSettings(); print ("Settings says:", voteAppSettings.voted[type], voteAppSettings.voted[type] !== ""); return voteAppSettings.voted[type] !== ""; // if (!voteAppSettings.voted[type]) { // return false; // } else { // return voteAppSettings.voted[type]; // } // var entryArray = type === DOMAIN ? dataStore.domains : dataStore.avatars; // entryArray.forEach(function (entry) { // if (entry.voted === true) { // voted for this entry // voted = true; // } // }); // return voted; } function sendAvatarVote(name) { if (DEBUG) { print("HELLO 100"); } avatarsInfo[name.toLowerCase()].voted = true; dataStore.voted.avatar = true; if (DEBUG) { print("Checking 0", JSON.stringify(dataStore.voted.avatar)); } ui.updateUI(dataStore); var params = encodeURLParams({ type: VOTE_AVATARS_GOOGLE, date: Date.now(), uuid: MyAvatar.sessionUUID, username: AccountServices.username, avatar: name, event: EVENT_NAME }); var onComplete = function (request) { var voteAppSettings = getVoteAppSettings(); if (DEBUG) { print("Checking 1", JSON.stringify(voteAppSettings)); } voteAppSettings.voted[AVATAR] = name.toLowerCase(); Settings.setValue(VOTE_APP_SETTINGS_NAME, voteAppSettings); // TODO *** voted // var avatarName = name.toLowerCase(); // avatarsInfo[avatarName].voted = true; // dataStore.voted.avatar = true; // ui.updateUI(dataStore); }; var onError = function () { if (DEBUG) { print("Error in VoteApp.js: Issue in sendAvatarVote()"); } avatarsInfo[name.toLowerCase()].voted = false; dataStore.voted.avatar = false; ui.updateUI(dataStore); }; sendRequest(GOOGLE_SCRIPTS_URL, params, onComplete, onError); } function sendDomainVote(name) { if (DEBUG) { print("HELLO 100"); } domainsInfo[name.toLowerCase()].voted = true; dataStore.voted.domain = true; if (DEBUG) { print("Checking 0", JSON.stringify(dataStore.voted.domain)); } ui.updateUI(dataStore); var params = encodeURLParams({ type: VOTE_DOMAINS_GOOGLE, date: Date.now(), uuid: MyAvatar.sessionUUID, username: AccountServices.username, domain: name, event: EVENT_NAME }); var onComplete = function (request) { var voteAppSettings = getVoteAppSettings(); voteAppSettings.voted[DOMAIN] = name.toLowerCase(); Settings.setValue(VOTE_APP_SETTINGS_NAME, voteAppSettings); if (DEBUG) { print("Checking 2", JSON.stringify(voteAppSettings)); } // TODO *** voted // if not vote or request failed // var domainName = name.toLowerCase(); // domainsInfo[domainName].voted = true; // dataStore.voted.domain = true; // ui.updateUI(dataStore); }; var onError = function () { if (DEBUG) { print("Error in VoteApp.js: Issue in sendDomainVote()"); } domainsInfo[name.toLowerCase()].voted = false; dataStore.voted.domain = false; ui.updateUI(dataStore); }; sendRequest(GOOGLE_SCRIPTS_URL, params, onComplete, onError); } // Helper Functions // ///////////////////////////////////////////////////////////////////////// function getVoteAppSettings () { var voteAppSettings = Settings.getValue(VOTE_APP_SETTINGS_NAME, DEFAULT_VOTE_APP_SETTINGS); if (Array.isArray(voteAppSettings)) { // to correct old format var settings = { domains: voteAppSettings, voted: { avatar: "", domain: "" } }; voteAppSettings = settings; } return voteAppSettings; } function onHostChanged(host) { if (DEBUG) { print("Host changed to: " + host); } if (DEBUG) { print("location is visitable: ", host, isLocationVisitable(host)); } var domainName = host.toLowerCase(); if (domainsInfo[domainName]) { domainsInfo[domainName].visited = true; // set value in settings to save it var voteAppSettings = getVoteAppSettings(); clientVisitedList = voteAppSettings.domains; clientVisitedList.push(domainName); voteAppSettings.domains = clientVisitedList; Settings.setValue(VOTE_APP_SETTINGS_NAME, voteAppSettings); dataStore.visitedAllDomains = checkVisitedAllDomains(clientVisitedList); ui.updateUI(dataStore); } } function compareGoogleData(gData) { // { // openPolls: { // avatar: true, // domain: true // }, // domains: [ "TheSpot", "Studio" ], // avatars: [ // { name: "Robin", image: "hello" } // ] // }; if (DEBUG) { print("CHANGED GOOGLE DATA"); } dataStore.loading = true; ui.updateUI(dataStore); var changedPolls = setPollsOpen(gData.openPolls); var changedDomains = setDomains(gData.domains); var changedAvatars = setAvatars(gData.avatars); var voteAppSettings = getVoteAppSettings(); dataStore.visitedAllDomains = checkVisitedAllDomains(voteAppSettings.domains); dataStore.voted.avatar = hasUserVoted(AVATAR); dataStore.voted.domain = hasUserVoted(DOMAIN); var needsUpdateUI = changedPolls && changedAvatars && changedDomains; if (firstLoad) { shuffle(dataStore.avatars); } dataStore.loading = false; ui.updateUI(dataStore); } function setPollsOpen(gPolls) { // { // avatar: true, // domain: truex // }, if (DEBUG) { print("polls are ", gPolls.avatar, gPolls.domain); } var changed = false; if (dataStore.openPolls.avatar !== gPolls.avatar || dataStore.openPolls.domain !== gPolls.domain) { changed = true; dataStore.openPolls.avatar = gPolls.avatar; dataStore.openPolls.domain = gPolls.domain; } return changed; } function setDomains(gDomains) { // [ "TheSpot", "Studio" ], // add domains to the location array if (DEBUG) { print("voting 3"); } var changed = false; var existingDomains = Object.keys(domainsInfo); if (DEBUG) { print("EXISTING ", JSON.stringify(existingDomains)); } if (DEBUG) { print("NEW GOOGLE ", JSON.stringify(gDomains)); } gDomains.forEach(function (domainName) { var lowercase = domainName.toLowerCase(); var voteAppSettings = getVoteAppSettings(); var votedName = voteAppSettings.voted[DOMAIN]; if (votedName !== "") { dataStore.voted.domain = true; } var existingIndex = existingDomains.indexOf(lowercase); if (existingIndex !== -1) { existingDomains.splice(existingIndex, 1); if (DEBUG) { print("INDEX EXISTING ", JSON.stringify(existingDomains), JSON.stringify(existingIndex)); } } if (firstLoad || !domainsInfo[lowercase]) { // need to get all domain info because of first load // or new domain encountered changed = true; domainsInfo[lowercase] = { name: domainName, image: "", visited: false, index: -1, voted: votedName && lowercase === votedName ? true : false }; sendDomainInfoRequest(lowercase); } }); if (DEBUG) { print(" EXISTING LENGTH", JSON.stringify(existingDomains.length)); } if (existingDomains.length > 0) { if (DEBUG) { print("INSIDE DELETE 1"); } // found domains to delete removeItems (existingDomains, DOMAIN); } if (changed === true) { Script.setTimeout(function () { setDataStoreDomainsInfo(); ui.updateUI(dataStore); }, 500); } return changed; } function sendDomainInfoRequest(domainName) { // domainName is lowercase if (DEBUG) { print("voting 4"); } var url = "https://metaverse.highfidelity.com/api/v1/places/" + domainName; var paramString = ""; var onComplete = function (request) { if (DEBUG) { print("voting 5"); } try { var response = JSON.parse(request.responseText); } catch (e) { console.error("Error parsing request in sendDomainInfoRequest"); } var image = response.data.place.previews ? response.data.place.previews.thumbnail : "http://img.youtube.com/vi/kEJDqO7WrKY/hqdefault.jpg"; // url to image domainsInfo[domainName].image = image; ui.updateUI(dataStore); if (DEBUG) { print(JSON.stringify(image)); } }; var onError = function () { if (DEBUG) { print("Error in VoteApp.js: Issue in sendDomainInfoRequest()"); } }; sendRequest(url, paramString, onComplete, onError); } function setDataStoreDomainsInfo() { if (DEBUG) { print("voting 6"); } if (firstLoad) { var domains = []; var domainKeys = Object.keys(domainsInfo); domainKeys.forEach(function (domainKey, index) { domainsInfo[domainKey].index = index; domainsInfo[domainKey].visited = hasUserVisitedDomain(domainKey); domains.push(domainsInfo[domainKey]); }); shuffle(domains); firstLoad = false; } else { } dataStore.domains = domains; } function hasUserVisitedDomain(domainName) { if (DEBUG) { print("voting 7"); } // domainName is lowercase var visitedDomainList = getVoteAppSettings().domains; var visited = visitedDomainList.indexOf(domainName) !== -1; return visited; } function setAvatars(gAvatars) { // { name: "Robin", image: "hello" } // update avatar images if necessary var changed = false; var existingAvatars = Object.keys(avatarsInfo); var voteAppSettings = getVoteAppSettings(); var votedName = voteAppSettings.voted[AVATAR]; if (votedName !== "") { dataStore.voted.avatar = true; } gAvatars.forEach(function (avatar, index) { if (DEBUG) { print("Avatar ", index, JSON.stringify(avatar)); } var lowercase = avatar.name.toLowerCase(); var existingIndex = existingAvatars.indexOf(lowercase); if (existingIndex !== -1) { existingAvatars.splice(existingIndex, 1); } if (firstLoad || !avatarsInfo[lowercase]) { // need to get all domain info because of first load // or new domain encountered changed = true; avatarsInfo[lowercase] = { name: avatar.name, image: avatar.image, index: dataStore.avatars.length - 1, // not using this yet voted: votedName && lowercase === votedName ? true : false }; dataStore.avatars.push(avatarsInfo[lowercase]); } else if (avatarsInfo[lowercase].image !== avatar.image) { // image changed, update to new image avatarsInfo[lowercase].image = avatar.image; } }); if (existingAvatars.length > 0) { // found avatars to delete removeItems(existingAvatars, AVATAR); } Script.setTimeout(function () { ui.updateUI(dataStore); }, 500); return changed; } function removeItems(listToDelete, type) { if (DEBUG) { print("REMOVE ITEM", type, JSON.stringify(listToDelete)); } var list = type === DOMAIN ? dataStore.domains : dataStore.avatars; var infoStore = type === DOMAIN ? domainsInfo : avatarsInfo; if (DEBUG) { print("INSIDE DELETE 2", JSON.stringify(list)); } listToDelete.forEach(function (nameToDelete) { // for every avatar to delete var deleteIndex = -1; for (var i = 0; i < list.length; i++) { // search var current = list[i]; var lowercase = current.name.toLowerCase(); if (DEBUG) { print("COMPARE ", nameToDelete, lowercase); } if (nameToDelete === lowercase) { // found deleteIndex = i; if (DEBUG) { print("INSIDE DELETE 3", JSON.stringify(deleteIndex)); } break; } } if (deleteIndex !== -1) { if (DEBUG) { print("INSIDE DELETE 4", JSON.stringify(deleteIndex)); } // remove from dataStore list and the avatar object itself list.splice(deleteIndex, 1); delete infoStore[nameToDelete]; } }); ui.updateUI(dataStore); } function checkVisitedAllDomains(clientVisitedList) { if (DEBUG) { print("voting 8"); } if (DEBUG) { print("Domain info: ", JSON.stringify(domainsInfo)); } if (DEBUG) { print("ClientVisitedList info: ", JSON.stringify(clientVisitedList)); } var visitedAllDomains = Object.keys(domainsInfo).reduce(function (visitedAll, domainName) { var wasVisited = clientVisitedList.indexOf(domainName) !== -1; return visitedAll && wasVisited; }, true); return visitedAllDomains; } function isLocationVisitable(host) { var domainName = host.toLowerCase(); var isCurrentLocationVisitable = Object.keys(domainsInfo).indexOf(domainName) !== -1; // currentLocation is on list return isCurrentLocationVisitable; } // Takes an object as argument and creates the paramString for sendRequest() to send along with a GET request // Example argument { username: "MyUsername", displayName: "MyDisplayName" } function encodeURLParams(params) { var paramPairs = []; for (var key in params) { paramPairs.push(key + "=" + params[key]); } return paramPairs.join("&"); } // url - the request url // paramString - the encodedURLParams as arguments to be sent, if not needed can be null or "" // onComplete - the callback after requst completes see defaultOnComplete for standards // onError - the callback after an error function sendRequest(url, paramString, onComplete, onError) { var defaultOnComplete = function (request) { if (DEBUG) { print("sendRequest() is complete"); } try { var info = JSON.parse(request.responseText); } catch (e) { console.error("Error parsing response in defaultOnComplete"); } if (DEBUG) { print ("Response info is: ", JSON.stringify(info)); } } var defaultOnError = function () { console.error("sendRequest() timed out or there was another error."); } // Set request callbacks or assign to the default var onCompleteCallback = onComplete ? onComplete : defaultOnComplete; var onErrorCallback = onError ? onError : defaultOnError; // Create the request var request = new XMLHttpRequest(); // If paramString is truthy (exists and is not an empty string) append the param string to the url // For GET requests, this is how we send in arguments var requestURL = paramString ? url + "?" + paramString : url; if (DEBUG) { print("REQUEST URL IS: "); print(requestURL); } request.open('GET', requestURL); request.timeout = 10000; request.ontimeout = onErrorCallback; request.onreadystatechange = function () { // request.readyState === 4 indicates the request was complete and returned if (request.readyState === 4) { onCompleteCallback(request); } }; request.send(); } // Tablet // ///////////////////////////////////////////////////////////////////////// function startup() { ui = new AppUi({ buttonName: BUTTON_NAME, home: URL, onMessage: onMessage, graphicsDirectory: Script.resolvePath("./icons/"), onOpened: onOpened }); location.hostChanged.connect(onHostChanged); checkDateToUnload(); // onOpened(); } function onOpened() { var isUnloading = checkDateToUnload(); if (isUnloading) { return; } var isLoggedIn = AccountServices.isLoggedIn(); if (isLoggedIn) { print("ROBIN: is logged in"); dataStore.loggedin = true; getGoogleData(); // asynchronous and will call compareGoogleData(gData) once complete } else { // is not logged in print("ROBIN: is NOT logged in"); handleNotLoggedInStatus(); } } function handleNotLoggedInStatus() { if (!setupNotLoggedIn) { print("Robin: setup is not logged in"); dataStore.loggedin = false; AccountServices.loggedInChanged.connect(loggedIn); setupNotLoggedIn = true; dataStore.loading = false; ui.updateUI(); } function loggedIn() { print("Robin: Logging In!!"); AccountServices.loggedInChanged.disconnect(loggedIn); setupNotLoggedIn = false; dataStore.loggedin = true; } } function unload() { if (DEBUG) { print("WATCH OUT!"); } location.hostChanged.disconnect(onHostChanged); } function checkDateToUnload() { if (DEBUG) { print("CHECK DATE"); } var now = new Date(); if (now > UNLOAD_DATE) { var scriptList = ScriptDiscoveryService.getRunning(); var url; scriptList.forEach(function (scriptInfo) { if (scriptInfo.name === VOTE_APP_NAME ) { url = scriptInfo.url; dataStore.unload = true; dataStore.loading = false; ui.updateUI(dataStore); } }); if (DEBUG) { print("UNLOAD DATE"); } Script.setTimeout(function() { ScriptDiscoveryService.stopScript(url); }, UNLOAD_TIME); return true; } return false; } // *** web event actions var GOTO = "goto", VOTE_AVATAR = "vote_avatar", VOTE_DOMAIN = "vote_domain"; function onMessage(data) { // EventBridge message from HTML script. switch (data.type) { case EVENT_BRIDGE_OPEN_MESSAGE: ui.updateUI(dataStore); break; case GOTO: Window.location = "hifi://" + data.value; break; case VOTE_AVATAR: console.log(hasUserVoted(AVATAR)); if (hasUserVoted(AVATAR) === false) { sendAvatarVote(data.value); } break; case VOTE_DOMAIN: console.log(hasUserVoted(DOMAIN)); if (hasUserVoted(DOMAIN) === false) { sendDomainVote(data.value); } break; default: } } // Main // ///////////////////////////////////////////////////////////////////////// startup(); Script.scriptEnding.connect(unload); }()); // END LOCAL_SCOPE