diff --git a/src/extensionsIntegrated/Phoenix/guided-tour.js b/src/extensionsIntegrated/Phoenix/guided-tour.js index 75cb3f642..681397785 100644 --- a/src/extensionsIntegrated/Phoenix/guided-tour.js +++ b/src/extensionsIntegrated/Phoenix/guided-tour.js @@ -37,7 +37,7 @@ define(function (require, exports, module) { // All popup notifications will show immediately on boot, we don't want to interrupt user amidst his work // by showing it at a later point in time. const GENERAL_SURVEY_TIME = 1200000, // 20 min - POWER_USER_SURVEY_TIME = 10000, // 10 seconds to allow the survey to preload, but not + SURVEY_PRELOAD_DELAY = 10000, // 10 seconds to allow the survey to preload, but not // enough time to break user workflow ONE_MONTH_IN_DAYS = 30, POWER_USER_SURVEY_INTERVAL_DAYS = 35; @@ -212,27 +212,23 @@ define(function (require, exports, module) { } } - function _showGeneralSurvey(surveyURL, delayOverride) { + function _showFirstUseSurvey(surveyURL, delayOverride, title, useDialog) { let surveyVersion = 6; // increment this if you want to show this again if(userAlreadyDidAction.generalSurveyShownVersion === surveyVersion) { return; } - const $surveyFrame = addSurveyIframe(surveyURL); + let $surveyFrame; + if(useDialog){ + $surveyFrame = addSurveyIframe(surveyURL); + } setTimeout(()=>{ - const templateVars = { - Strings: Strings - }; - let positionObserver; - Metrics.countEvent(Metrics.EVENT_TYPE.USER, "survey", "generalShown", 1); - Dialogs.showModalDialogUsingTemplate(Mustache.render(SurveyTemplate, templateVars)).done(()=>{ - positionObserver && positionObserver.disconnect(); - $surveyFrame.remove(); - }); - setTimeout(()=>{ - const $surveyFrameContainer = $('#surveyFrameContainer'); - repositionIframe($surveyFrame, $surveyFrameContainer); - positionObserver = observerPositionChanges($surveyFrame, $surveyFrameContainer); - }, 200); + if(useDialog){ + Metrics.countEvent(Metrics.EVENT_TYPE.USER, "survey", "firstDialog", 1); + _showDialogSurvey($surveyFrame); + } else { + Metrics.countEvent(Metrics.EVENT_TYPE.USER, "survey", "firstNotification", 1); + _showSurveyNotification(surveyURL, title); + } userAlreadyDidAction.generalSurveyShownVersion = surveyVersion; PhStore.setItem(GUIDED_TOUR_LOCAL_STORAGE_KEY, JSON.stringify(userAlreadyDidAction)); }, delayOverride || GENERAL_SURVEY_TIME); @@ -275,38 +271,66 @@ define(function (require, exports, module) { return resizeObserver; } - function _showPowerUserSurvey(surveyURL, intervalOverride) { - if(Metrics.isPowerUser()) { - const intervalDays = intervalOverride || POWER_USER_SURVEY_INTERVAL_DAYS; - Metrics.countEvent(Metrics.EVENT_TYPE.USER, "power", "user", 1); - let lastShownDate = userAlreadyDidAction.lastShownPowerSurveyDate; - let nextShowDate = new Date(lastShownDate); - nextShowDate.setUTCDate(nextShowDate.getUTCDate() + intervalDays); - let currentDate = new Date(); - if(currentDate < nextShowDate){ - return; - } + function _showDialogSurvey($surveyFrame) { + const templateVars = { + Strings: Strings + }; + let positionObserver; + Dialogs.showModalDialogUsingTemplate(Mustache.render(SurveyTemplate, templateVars)) + .done(()=>{ + positionObserver && positionObserver.disconnect(); + $surveyFrame.remove(); + }); + const $surveyFrameContainer = $('#surveyFrameContainer'); + setTimeout(()=>{ + repositionIframe($surveyFrame, $surveyFrameContainer); + positionObserver = observerPositionChanges($surveyFrame, $surveyFrameContainer); + }, 200); + } + + function _showSurveyNotification(surveyUrl, title) { + NotificationUI.createToastFromTemplate( + title || Strings.SURVEY_TITLE_VOTE_FOR_FEATURES_YOU_WANT, + `
+
`, { + toastStyle: `${NotificationUI.NOTIFICATION_STYLES_CSS_CLASS.INFO} survey-notification-big forced-hidden`, + dismissOnClick: false + }); + setTimeout(()=>{ + $('.survey-notification-big').removeClass('forced-hidden'); + }, SURVEY_PRELOAD_DELAY); + } + + function _showRepeatUserSurvey(surveyURL, intervalOverride, title, useDialog) { + let nextPowerSurveyShowDate = userAlreadyDidAction.nextPowerSurveyShowDate; + if(!nextPowerSurveyShowDate){ + // first boot, we schedule the power user survey to happen in two weeks + let nextShowDate = new Date(); + nextShowDate.setUTCDate(nextShowDate.getUTCDate() + 14); // the first time repeat survey always shows up + // always after 2 weeks. + userAlreadyDidAction.nextPowerSurveyShowDate = nextShowDate.getTime(); + PhStore.setItem(GUIDED_TOUR_LOCAL_STORAGE_KEY, JSON.stringify(userAlreadyDidAction)); + return; + } + const intervalDays = intervalOverride || POWER_USER_SURVEY_INTERVAL_DAYS; + let nextShowDate = new Date(nextPowerSurveyShowDate); + let currentDate = new Date(); + if(currentDate < nextShowDate){ + return; + } + if(useDialog){ const $surveyFrame = addSurveyIframe(surveyURL); setTimeout(()=>{ - Metrics.countEvent(Metrics.EVENT_TYPE.USER, "survey", "powerShown", 1); - const templateVars = { - Strings: Strings - }; - let positionObserver; - Dialogs.showModalDialogUsingTemplate(Mustache.render(SurveyTemplate, templateVars)) - .done(()=>{ - positionObserver && positionObserver.disconnect(); - $surveyFrame.remove(); - }); - const $surveyFrameContainer = $('#surveyFrameContainer'); - setTimeout(()=>{ - repositionIframe($surveyFrame, $surveyFrameContainer); - positionObserver = observerPositionChanges($surveyFrame, $surveyFrameContainer); - }, 200); - userAlreadyDidAction.lastShownPowerSurveyDate = Date.now(); - PhStore.setItem(GUIDED_TOUR_LOCAL_STORAGE_KEY, JSON.stringify(userAlreadyDidAction)); - }, POWER_USER_SURVEY_TIME); + Metrics.countEvent(Metrics.EVENT_TYPE.USER, "survey", "powerDialog", 1); + _showDialogSurvey($surveyFrame); + }, SURVEY_PRELOAD_DELAY); + } else { + Metrics.countEvent(Metrics.EVENT_TYPE.USER, "survey", "powerNotification", 1); + _showSurveyNotification(surveyURL, title); } + nextShowDate.setUTCDate(nextShowDate.getUTCDate() + intervalDays); + userAlreadyDidAction.nextPowerSurveyShowDate = nextShowDate.getTime(); + PhStore.setItem(GUIDED_TOUR_LOCAL_STORAGE_KEY, JSON.stringify(userAlreadyDidAction)); } async function _showSurveys() { @@ -319,14 +343,20 @@ define(function (require, exports, module) { if(!Phoenix.isNativeApp && surveyJSON.browser) { surveyJSON = { newUser: surveyJSON.browser.newUser || surveyJSON.newUser, + newUserTitle: surveyJSON.browser.newUserTitle || surveyJSON.newUserTitle, newUserShowDelayMS: surveyJSON.browser.newUserShowDelayMS || surveyJSON.newUserShowDelayMS, + newUserUseDialog: surveyJSON.browser.newUserUseDialog || surveyJSON.newUserUseDialog, powerUser: surveyJSON.browser.powerUser || surveyJSON.powerUser, + powerUserTitle: surveyJSON.browser.powerUserTitle || surveyJSON.powerUserTitle, powerUserShowIntervalDays: surveyJSON.browser.powerUserShowIntervalDays - || surveyJSON.powerUserShowIntervalDays + || surveyJSON.powerUserShowIntervalDays, + powerUserUseDialog: surveyJSON.browser.powerUserUseDialog || surveyJSON.powerUserUseDialog }; } - surveyJSON.newUser && _showGeneralSurvey(surveyJSON.newUser, surveyJSON.newUserShowDelayMS); - surveyJSON.powerUser && _showPowerUserSurvey(surveyJSON.powerUser, surveyJSON.powerUserShowIntervalDays); + surveyJSON.newUser && _showFirstUseSurvey(surveyJSON.newUser, surveyJSON.newUserShowDelayMS, + surveyJSON.newUserTitle, surveyJSON.newUserUseDialog); + surveyJSON.powerUser && _showRepeatUserSurvey(surveyJSON.powerUser, surveyJSON.powerUserShowIntervalDays, + surveyJSON.powerUserTitle, surveyJSON.powerUserUseDialog); } catch (e) { console.error("Error fetching survey link", surveyLinksURL, e); Metrics.countEvent(Metrics.EVENT_TYPE.USER, "survey", "fetchError", 1); diff --git a/src/nls/root/strings.js b/src/nls/root/strings.js index c7bbda3a2..ba17a0015 100644 --- a/src/nls/root/strings.js +++ b/src/nls/root/strings.js @@ -1528,5 +1528,8 @@ define({ "ERROR_PUSHING_OPERATION": "Pushing operation failed", "ERROR_NO_REMOTE_SELECTED": "No remote has been selected for {0}!", "ERROR_BRANCH_LIST": "Getting branch list failed", - "ERROR_FETCH_REMOTE": "Fetching remote information failed" + "ERROR_FETCH_REMOTE": "Fetching remote information failed", + + // surveys + "SURVEY_TITLE_VOTE_FOR_FEATURES_YOU_WANT": "Vote for the features you want to see next!" }); diff --git a/src/styles/brackets.less b/src/styles/brackets.less index 7a23eee08..c8278aeaa 100644 --- a/src/styles/brackets.less +++ b/src/styles/brackets.less @@ -3267,7 +3267,7 @@ label input { } .notification-popup-close-button { - font-size: 16px; + font-size: 24px; font-weight: 900; position: absolute; top: 0; @@ -3557,3 +3557,11 @@ label input { flex-direction: column; justify-content: space-between } + +.notification-popup-container.survey-notification-big { + margin-left: -274px; + background-color: #f3f5f9; // match with survey planet + .notification-popup-close-button:hover { + color: @bc-primary-btn-border-focused-glow; + } +} \ No newline at end of file diff --git a/src/widgets/NotificationUI.js b/src/widgets/NotificationUI.js index eb098459f..9fd567aaa 100644 --- a/src/widgets/NotificationUI.js +++ b/src/widgets/NotificationUI.js @@ -316,10 +316,11 @@ define(function (require, exports, module) { * The message can either be a string or a jQuery object representing a DOM node that is *not* in the current DOM. * * Creating a toast notification popup + * + * ```js * // note that you can even provide an HTML Element node with * // custom event handlers directly here instead of HTML text. * let notification1 = NotificationUI.createToastFromTemplate( "Title here", - * ```js * "
Click me to locate the file in file tree
", { * dismissOnClick: false, * autoCloseTimeS: 300 // auto close the popup after 5 minutes