/* ***** BEGIN LICENSE BLOCK ***** * Version: MPL 1.1/GPL 2.0/LGPL 2.1 * * The contents of this file are subject to the Mozilla Public License Version * 1.1 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * http://www.mozilla.org/MPL/ * * Software distributed under the License is distributed on an "AS IS" basis, * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License * for the specific language governing rights and limitations under the * License. * * The Original Code is Test Pilot. * * The Initial Developer of the Original Code is Mozilla. * Portions created by the Initial Developer are Copyright (C) 2007 * the Initial Developer. All Rights Reserved. * * Contributor(s): * Jono X * Raymond Lee * * Alternatively, the contents of this file may be used under the terms of * either the GNU General Public License Version 2 or later (the "GPL"), or * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), * in which case the provisions of the GPL or the LGPL are applicable instead * of those above. If you wish to allow use of your version of this file only * under the terms of either the GPL or the LGPL, and not to allow others to * use your version of this file under the terms of the MPL, indicate your * decision by deleting the provisions above and replace them with the notice * and other provisions required by the GPL or the LGPL. If you do not delete * the provisions above, a recipient may use your version of this file under * the terms of any one of the MPL, the GPL or the LGPL. * * ***** END LICENSE BLOCK ***** */ // TODO Show individual status page in new chromeless window as html with // background color set to "moz-dialog". const NO_STUDIES_IMG = "chrome://testpilot/skin/testPilot_200x200.png"; const PROPOSE_STUDY_URL = "https://wiki.mozilla.org/Labs/Test_Pilot#For_researchers"; var TestPilotXulWindow = { _stringBundle : null, onSubmitButton: function(experimentId) { Components.utils.import("resource://testpilot/modules/setup.js"); let task = TestPilotSetup.getTaskById(experimentId); let button = document.getElementById("submit-button-" + task.id); // Hide the upload button so it doesn't get clicked again... let parent = button.parentNode; while (parent.firstChild) { parent.removeChild(parent.firstChild); } // Replace it with a message: this.addLabel( parent, this._stringBundle.getString("testpilot.studiesWindow.uploading")); let self = this; task.upload( function(success) { while (parent.firstChild) { parent.removeChild(parent.firstChild); } if (success) { self.addThanksMessage(parent); // TODO or should we move it to 'finished studies' immediately? } else { // TODO a better error message? self.addLabel( parent, self._stringBundle.getString( "testpilot.studiesWindow.unableToReachServer")); } }); }, addThanksMessage: function(container) { // Fill in status box with icon and message to show success let hbox = document.createElement("hbox"); container.appendChild(this.makeSpacer()); container.appendChild(hbox); this.addLabel( container, this._stringBundle.getString( "testpilot.studiesWindow.thanksForContributing")); container.appendChild(this.makeSpacer()); hbox.appendChild(this.makeSpacer()); this.addImg(hbox, "study-submitted"); hbox.appendChild(this.makeSpacer()); }, addXulLink: function (container, text, url, openInTab) { let linkContainer = document.createElement("hbox"); let link = document.createElement("label"); let spacer = document.createElement("spacer"); link.setAttribute("value", text); link.setAttribute("class", "text-link"); if (openInTab) { link.setAttribute( "onclick", "if (event.button==0) { " + "TestPilotWindowUtils.openInTab('" + url + "'); }"); } else { link.setAttribute( "onclick", "if (event.button==0) { " + "TestPilotWindowUtils.openChromeless('" + url + "'); }"); } linkContainer.appendChild(link); spacer.setAttribute("flex", "1"); linkContainer.appendChild(spacer); container.appendChild(linkContainer); }, addLabel: function(container, text, styleClass) { let label = document.createElement("label"); label.setAttribute("value", text); if (styleClass) { label.setAttribute("class", styleClass); } container.appendChild(label); }, addImg: function(container, iconClass) { let newImg = document.createElement("image"); newImg.setAttribute("class", iconClass); container.appendChild(newImg); }, makeSpacer: function() { let spacer = document.createElement("spacer"); spacer.setAttribute("flex", "1"); return spacer; }, addThumbnail: function(container, imgUrl) { let boundingBox = document.createElement("vbox"); boundingBox.setAttribute("class", "results-thumbnail"); let bBox2 = document.createElement("hbox"); boundingBox.appendChild(this.makeSpacer()); boundingBox.appendChild(bBox2); boundingBox.appendChild(this.makeSpacer()); bBox2.appendChild(this.makeSpacer()); let newImg = document.createElement("image"); newImg.setAttribute("src", imgUrl); newImg.setAttribute("class", "results-thumbnail"); bBox2.appendChild(newImg); bBox2.appendChild(this.makeSpacer()); container.appendChild(boundingBox); }, addProgressBar: function(container, percent) { let progBar = document.createElement("progressmeter"); progBar.setAttribute("mode", "determined"); progBar.setAttribute("value", Math.ceil(percent).toString()); container.appendChild(progBar); }, addDescription: function(container, title, paragraph) { let desc = document.createElement("description"); desc.setAttribute("class", "study-title"); let txtNode = document.createTextNode(title); desc.appendChild(txtNode); container.appendChild(desc); desc = document.createElement("description"); desc.setAttribute("class", "study-description"); desc.setAttribute("crop", "none"); txtNode = document.createTextNode(paragraph); desc.appendChild(txtNode); container.appendChild(desc); }, addButton: function(container, label, id, onClickHandler) { let button = document.createElement("button"); button.setAttribute("label", label); button.setAttribute("id", id); button.setAttribute("oncommand", onClickHandler); container.appendChild(button); }, _sortNewestFirst: function(experiments) { experiments.sort( function sortFunc(a, b) { if (a.endDate && b.endDate) { return b.endDate - a.endDate; } if (a.publishDate && b.publishDate) { if (isNaN(a.publishDate) || isNaN(b.publishDate)) { return 0; } return b.publishDate - a.publishDate; } return 0; }); return experiments; }, onLoad: function () { Components.utils.import("resource://testpilot/modules/Observers.js"); Components.utils.import("resource://testpilot/modules/setup.js"); Components.utils.import("resource://testpilot/modules/tasks.js"); this._stringBundle = document.getElementById("testpilot-stringbundle"); this.sizeWindow(); this._init(false); Observers.add("testpilot:task:changed", this._onTaskStatusChanged, this); }, onUnload: function() { document.getElementById("settings-pane").writePreferences(true); Observers.remove("testpilot:task:changed", this._onTaskStatusChanged, this); }, _onTaskStatusChanged : function() { this._init(true); }, onTakeSurveyButton: function(taskId) { let task = TestPilotSetup.getTaskById(taskId); TestPilotWindowUtils.openChromeless(task.defaultUrl); task.onDetailPageOpened(); }, _init: function(aReload) { let experiments; let ready = false; // Are we done loading tasks? if (TestPilotSetup.startupComplete) { experiments = TestPilotSetup.getAllTasks(); if (experiments.length > 0 ) { ready = true; } } if (!ready) { // If you opened the window before tasks are done loading, exit now // but try again in a few seconds. window.setTimeout( function() { TestPilotXulWindow._init(aReload); }, 2000); return; } let numFinishedStudies = 0; let numCurrentStudies = 0; /* Remove 'loading' message */ let msg = window.document.getElementById("still-loading-msg"); msg.setAttribute("hidden", "true"); if (aReload) { /* If we're reloading, start by clearing out any old stuff already * present in the listboxes. */ let listboxIds = ["current-studies-listbox", "finished-studies-listbox", "study-results-listbox"]; for (let i = 0; i < listboxIds.length; i++) { let listbox = document.getElementById(listboxIds[i]); while (listbox.lastChild) { listbox.removeChild(listbox.lastChild); } } } experiments = this._sortNewestFirst(experiments); for (let i = 0; i < experiments.length; i++) { let task = experiments[i]; let newRow = document.createElement("richlistitem"); newRow.setAttribute("class", "tp-study-list"); this.addThumbnail(newRow, task.thumbnail); let textVbox = document.createElement("vbox"); newRow.appendChild(textVbox); let openInTab = (task.taskType == TaskConstants.TYPE_LEGACY); this.addDescription(textVbox, task.title, task.summary); this.addXulLink( textVbox, this._stringBundle.getString("testpilot.moreInfo"), task.defaultUrl, openInTab); // Create the rightmost status area, depending on status: let statusVbox = document.createElement("vbox"); if (task.status == TaskConstants.STATUS_FINISHED) { this.addLabel( statusVbox, this._stringBundle.getFormattedString( "testpilot.studiesWindow.finishedOn", [(new Date(task.endDate)).toLocaleDateString()])); this.addButton(statusVbox, this._stringBundle.getString("testpilot.submit"), "submit-button-" + task.id, "TestPilotXulWindow.onSubmitButton(" + task.id + ");"); } if (task.status == TaskConstants.STATUS_CANCELLED) { let hbox = document.createElement("hbox"); newRow.setAttribute("class", "tp-opted-out"); statusVbox.appendChild(this.makeSpacer()); statusVbox.appendChild(hbox); this.addLabel( statusVbox, this._stringBundle.getString("testpilot.studiesWindow.canceledStudy")); statusVbox.appendChild(this.makeSpacer()); hbox.appendChild(this.makeSpacer()); this.addImg(hbox, "study-canceled"); hbox.appendChild(this.makeSpacer()); } if (task.status == TaskConstants.STATUS_NEW || task.status == TaskConstants.STATUS_PENDING ) { newRow.setAttribute("class", "tp-new-results"); if (task.taskType == TaskConstants.TYPE_SURVEY) { this.addButton( statusVbox, this._stringBundle.getString("testpilot.takeSurvey"), "survey-button", "TestPilotXulWindow.onTakeSurveyButton('" + task.id + "');"); } else if (task.taskType == TaskConstants.TYPE_EXPERIMENT) { if (task.startDate) { this.addLabel( statusVbox, this._stringBundle.getFormattedString( "testpilot.studiesWindow.willStart", [(new Date(task.startDate)).toLocaleDateString()])); } } } if (task.status == TaskConstants.STATUS_IN_PROGRESS || task.status == TaskConstants.STATUS_STARTING) { if (task.taskType == TaskConstants.TYPE_SURVEY) { this.addButton( statusVbox, this._stringBundle.getString("testpilot.takeSurvey"), "survey-button", "TestPilotXulWindow.onTakeSurveyButton('" + task.id + "');"); } else if (task.taskType == TaskConstants.TYPE_EXPERIMENT) { this.addLabel( statusVbox, this._stringBundle.getString( "testpilot.studiesWindow.gatheringData")); let now = (new Date()).getTime(); let progress = 100 * (now - task.startDate) / (task.endDate - task.startDate); this.addProgressBar(statusVbox, progress); this.addLabel( statusVbox, this._stringBundle.getFormattedString( "testpilot.studiesWindow.willFinish", [(new Date(task.endDate)).toLocaleDateString()])); } } if (task.status >= TaskConstants.STATUS_SUBMITTED) { if (task.taskType == TaskConstants.TYPE_RESULTS) { let maintask = TestPilotSetup.getTaskById(task.relatedStudyId); if (maintask && maintask.status >= TaskConstants.STATUS_SUBMITTED) { this.addThanksMessage(statusVbox); } } else { if (task.status == TaskConstants.STATUS_MISSED) { // Icon for missed studies let hbox = document.createElement("hbox"); newRow.setAttribute("class", "tp-opted-out"); statusVbox.appendChild(this.makeSpacer()); statusVbox.appendChild(hbox); this.addLabel( statusVbox, this._stringBundle.getString("testpilot.studiesWindow.missedStudy")); statusVbox.appendChild(this.makeSpacer()); hbox.appendChild(this.makeSpacer()); this.addImg(hbox, "study-missed"); hbox.appendChild(this.makeSpacer()); } else { this.addThanksMessage(statusVbox); numFinishedStudies ++; } } } let spacer = document.createElement("spacer"); spacer.setAttribute("flex", "1"); newRow.appendChild(spacer); newRow.appendChild(statusVbox); // Use status to decide which panel to add this to: let rowset; if (task.taskType == TaskConstants.TYPE_RESULTS) { rowset = document.getElementById("study-results-listbox"); } else if (task.status > TaskConstants.STATUS_FINISHED) { rowset = document.getElementById("finished-studies-listbox"); } else { rowset = document.getElementById("current-studies-listbox"); numCurrentStudies++; } // TODO further distinguish by background colors. rowset.appendChild(newRow); } // If there are no current studies, show a message about upcoming // studies: if (numCurrentStudies == 0) { let newRow = document.createElement("richlistitem"); newRow.setAttribute("class", "tp-study-list"); this.addThumbnail(newRow, NO_STUDIES_IMG); let textVbox = document.createElement("vbox"); textVbox.setAttribute("class", "pilot-largetext"); newRow.appendChild(textVbox); this.addDescription( textVbox, "", this._stringBundle.getString("testpilot.studiesWindow.noStudies")); this.addXulLink( textVbox, this._stringBundle.getString("testpilot.studiesWindow.proposeStudy"), PROPOSE_STUDY_URL, true); document.getElementById("current-studies-listbox").appendChild(newRow); } // Show number of studies the user finished on badge: document.getElementById("num-finished-badge").setAttribute( "value", numFinishedStudies); }, sizeWindow: function() { // Size listboxes based on available screen size, then size window to fit // list boxes. let currList = document.getElementById("current-studies-listbox"); let finList = document.getElementById("finished-studies-listbox"); let resultsList = document.getElementById("study-results-listbox"); let screenWidth = window.screen.availWidth; let screenHeight = window.screen.availHeight; let width = screenWidth >= 800 ? 700 : screenWidth - 100; let height = screenHeight >= 800 ? 700 : screenHeight - 100; height -= 130; // Or whatever is height of title bar plus windowdragbox currList.width = width; currList.height = height; finList.width = width; finList.height = height; resultsList.width = width; resultsList.height = height; window.sizeToContent(); }, focusPane: function(paneIndex) { document.getElementById("tp-xulwindow-deck").selectedIndex = paneIndex; // When you focus the 'study findings' tab, any results there which // are still marked "new" should have their status changed as the user // is considered to have seen them. if (paneIndex == 2) { Components.utils.import("resource://testpilot/modules/setup.js"); Components.utils.import("resource://testpilot/modules/tasks.js"); let experiments = TestPilotSetup.getAllTasks(); for each (let experiment in experiments) { if (experiment.taskType == TaskConstants.TYPE_RESULTS) { if (experiment.status == TaskConstants.STATUS_NEW) { experiment.changeStatus(TaskConstants.STATUS_ARCHIVED, true); } } } } } };