From f627f05fc369283ef2fb8510be41cde9bde64594 Mon Sep 17 00:00:00 2001 From: abose Date: Mon, 20 Jan 2025 19:20:50 +0530 Subject: [PATCH 1/3] docs: add docs for std libs available --- docs/02-List-of-Built-In-Libraries.md | 144 ++++++++++++++++++++++++++ 1 file changed, 144 insertions(+) create mode 100644 docs/02-List-of-Built-In-Libraries.md diff --git a/docs/02-List-of-Built-In-Libraries.md b/docs/02-List-of-Built-In-Libraries.md new file mode 100644 index 0000000000..9e66501d61 --- /dev/null +++ b/docs/02-List-of-Built-In-Libraries.md @@ -0,0 +1,144 @@ + + +# Built-in Libraries + +This document lists the standard libraries that are pre-integrated into @codebase and available for use in your extensions and projects. + +## Icon Libraries + +### Font Awesome +Font Awesome provides scalable vector icons that can be customized with CSS. + +**Import:** +Font Awesome is globally available + +```js +// Add a Font Awesome icon to an element +element.innerHTML = ''; +``` + + +### Devicons +Developer-focused icons for programming languages and development tools. + +**Import:** +Devicons are available globally. + +```js +// Add a Devicon to an element +element.innerHTML = ''; +``` + + +### Octicons +GitHub's icon set. + +**Import:** +Octicons are available globally. + +```js +// Add an Octicon to an element +element.innerHTML = ''; +``` + +## Template Engines + +### Mustache +Logic-less templates. + +**Import:** +Mustache is available globally. + +```js +const Mustache = brackets.getModule("thirdparty/mustache/mustache"); +// example +const template = "Hello {{name}}!"; +const data = { name: "World" }; +const output = Mustache.render(template, data); +``` + + +## Utility Libraries + +### Lodash +A modern JavaScript utility library delivering modularity, performance & extras. + +**Import:** +Lodash is available globally. + +```js +const _ = brackets.getModule("thirdparty/lodash"); +``` + + +### Marked +A markdown parser and compiler. + +**Import:** +Marked is available globally. + +```js +const marked = brackets.getModule('thirdparty/marked.min'); +const html = marked("# I am using __markdown__."); +``` + + +## Phoenix-specific Libraries + +These libraries are available through the Phoenix.libs namespace: + +### LRU Cache +Least Recently Used (LRU) cache implementation. + +**Import:** +```js +const { LRUCache } = Phoenix.libs; +// example +const cache = new LRUCache(100); // Create cache with max 100 items +cache.set('key', 'value'); +const value = cache.get('key'); +``` + + +### Highlight.js +Syntax highlighting for code. + +**Import:** + +```js +const { hljs } = Phoenix.libs; +// see hilight js docs for usage +``` + + +### iconv +Character encoding conversion. + +**Import:** +```js +const { iconv } = Phoenix.libs; +// Example +const buffer = iconv.encode("Hello", 'utf8'); +const text = iconv.decode(buffer, 'utf8'); +``` + +### picomatch +Glob matching and pattern matching. + +**Import:** +```js +const { picomatch } = Phoenix.libs; +// Example +const isMatch = picomatch('.js'); +console.log(isMatch('file.js')); // true +console.log(isMatch('file.css')); // false +``` + + + +## Notes + +- All libraries are pre-loaded when your extension starts up +- Some libraries are available globally through the `window` object +- Phoenix-specific libraries are accessed through `Phoenix.libs` +- Version information for each library can be found in the package dependencies From 43270aa17eeba0b51f58e264544ea96434d49db6 Mon Sep 17 00:00:00 2001 From: abose Date: Tue, 21 Jan 2025 11:32:00 +0530 Subject: [PATCH 2/3] refactor: isPowerUser APi to metrics from health extension --- .../default/HealthData/HealthDataManager.js | 26 ++++++++++++- .../Phoenix/guided-tour.js | 24 +----------- src/utils/Metrics.js | 37 ++++++++++++++++--- 3 files changed, 58 insertions(+), 29 deletions(-) diff --git a/src/extensions/default/HealthData/HealthDataManager.js b/src/extensions/default/HealthData/HealthDataManager.js index 42b79f7953..45515e36a5 100644 --- a/src/extensions/default/HealthData/HealthDataManager.js +++ b/src/extensions/default/HealthData/HealthDataManager.js @@ -36,9 +36,29 @@ define(function (require, exports, module) { TEN_SECOND = 10 * ONE_SECOND, ONE_MINUTE = 60000, MAX_DAYS_TO_KEEP_COUNTS = 60, - // 'healthDataUsage' key is used in other places tho private to phoenix. search for other usage before rename USAGE_COUNTS_KEY = "healthDataUsage"; + /** + * A power user is someone who has used Phoenix at least 3 days or 8 hours in the last two weeks + * @returns {boolean} + */ + function isPowerUser() { + let usageData = PreferencesManager.getViewState(USAGE_COUNTS_KEY) || {}, + dateKeys = Object.keys(usageData), + dateBefore14Days = new Date(), + totalUsageMinutes = 0, + totalUsageDays = 0; + dateBefore14Days.setUTCDate(dateBefore14Days.getUTCDate()-14); + for(let dateKey of dateKeys){ + let date = new Date(dateKey); + if(date >= dateBefore14Days) { + totalUsageDays ++; + totalUsageMinutes = totalUsageMinutes + usageData[dateKey]; + } + } + return totalUsageDays >= 3 || (totalUsageMinutes/60) >= 8; + } + let healthDataDisabled; prefs.definePreference("healthDataTracking", "boolean", true, { @@ -85,7 +105,9 @@ define(function (require, exports, module) { } AppInit.appReady(function () { - Metrics.init(); + Metrics.init({ + isPowerUserFn: isPowerUser + }); healthDataDisabled = !prefs.get("healthDataTracking"); Metrics.setDisabled(healthDataDisabled); SendToAnalytics.sendPlatformMetrics(); diff --git a/src/extensionsIntegrated/Phoenix/guided-tour.js b/src/extensionsIntegrated/Phoenix/guided-tour.js index abcf0c2cae..75cb3f6426 100644 --- a/src/extensionsIntegrated/Phoenix/guided-tour.js +++ b/src/extensionsIntegrated/Phoenix/guided-tour.js @@ -28,7 +28,6 @@ define(function (require, exports, module) { Metrics = require("utils/Metrics"), Dialogs = require("widgets/Dialogs"), Mustache = require("thirdparty/mustache/mustache"), - PreferencesManager = require("preferences/PreferencesManager"), SurveyTemplate = require("text!./html/survey-template.html"), NOTIFICATION_BACKOFF = 10000, GUIDED_TOUR_LOCAL_STORAGE_KEY = "guidedTourActions"; @@ -41,8 +40,7 @@ define(function (require, exports, module) { POWER_USER_SURVEY_TIME = 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, - USAGE_COUNTS_KEY = "healthDataUsage"; // private to phoenix, set from health data extension + POWER_USER_SURVEY_INTERVAL_DAYS = 35; const userAlreadyDidAction = PhStore.getItem(GUIDED_TOUR_LOCAL_STORAGE_KEY) ? JSON.parse(PhStore.getItem(GUIDED_TOUR_LOCAL_STORAGE_KEY)) : { @@ -240,24 +238,6 @@ define(function (require, exports, module) { }, delayOverride || GENERAL_SURVEY_TIME); } - // a power user is someone who has used Phoenix at least 3 days or 8 hours in the last two weeks - function _isPowerUser() { - let usageData = PreferencesManager.getViewState(USAGE_COUNTS_KEY) || {}, - dateKeys = Object.keys(usageData), - dateBefore14Days = new Date(), - totalUsageMinutes = 0, - totalUsageDays = 0; - dateBefore14Days.setUTCDate(dateBefore14Days.getUTCDate()-14); - for(let dateKey of dateKeys){ - let date = new Date(dateKey); - if(date >= dateBefore14Days) { - totalUsageDays ++; - totalUsageMinutes = totalUsageMinutes + usageData[dateKey]; - } - } - return totalUsageDays >= 3 || (totalUsageMinutes/60) >= 8; - } - function addSurveyIframe(surveyURL) { const $surveyFrame = $('`, { + 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 c7bbda3a20..ba17a00152 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 7a23eee08c..c8278aeaa4 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 eb098459f6..9fd567aaa5 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