From 7025815098af94ecccdb5f63a41525ba8fefa9ad Mon Sep 17 00:00:00 2001 From: abose Date: Thu, 28 Dec 2023 11:49:49 +0530 Subject: [PATCH 1/3] feat: add PhStore.removeItem api and tests --- src/brackets.js | 3 +- src/main.js | 130 ++++++++++++++++---------------- src/storage.js | 15 +++- src/utils/Global.js | 8 +- src/utils/Metrics.js | 12 +-- test/spec/Storage-integ-test.js | 39 +++++++++- 6 files changed, 128 insertions(+), 79 deletions(-) diff --git a/src/brackets.js b/src/brackets.js index 6060ce166..0df9ef5f9 100644 --- a/src/brackets.js +++ b/src/brackets.js @@ -406,8 +406,7 @@ define(function (require, exports, module) { // Load default languages and preferences Async.waitForAll([LanguageManager.ready, PreferencesManager.ready]).always(function () { - Promise.all([window._phoenixfsAppDirsCreatePromise, window.PhStore.storageReadyPromise]) - .finally(_startupBrackets); + window._phoenixfsAppDirsCreatePromise.finally(_startupBrackets); }); } diff --git a/src/main.js b/src/main.js index 7400a2585..c3add6272 100644 --- a/src/main.js +++ b/src/main.js @@ -19,52 +19,6 @@ * */ -/** - * The bootstrapping module for brackets. This module sets up the require - * configuration and loads the brackets module. - */ -require.config({ - paths: { - "text": "thirdparty/text/text", - "i18n": "thirdparty/i18n/i18n", - - // The file system implementation. Change this value to use different - // implementations (e.g. cloud-based storage). - "fileSystemImpl": "filesystem/impls/appshell/AppshellFileSystem", - "preact-compat": "thirdparty/preact-compat/preact-compat.min", - "preact": "thirdparty/preact/preact" - }, - map: { - "*": { - "thirdparty/CodeMirror2": "thirdparty/CodeMirror", - "thirdparty/preact": "preact-compat", - "view/PanelManager": "view/WorkspaceManager" // For extension compatibility - } - }, - waitSeconds: 60 -}); - -if (window.location.search.indexOf("testEnvironment") > -1) { - require.config({ - paths: { - "preferences/PreferencesImpl": "../test/TestPreferencesImpl" - }, - locale: "en" // force English (US) - }); -} else { - /** - * hack for r.js optimization, move locale to another config call - * - * Use custom brackets property until CEF sets the correct navigator.language - * NOTE: When we change to navigator.language here, we also should change to - * navigator.language in ExtensionLoader (when making require contexts for each - * extension). - */ - require.config({ - locale: window.localStorage.getItem("locale") || window.navigator.language - }); -} - /** * global util to convert jquery/js promise to a js promise. This can be used as an adapter when you do not know if the * promise in hand is a js or jquery deferred promise. This function will always return a normal js promise. @@ -132,25 +86,73 @@ window.scriptObserver = new MutationObserver(callback); // Start observing the target node for configured mutations window.scriptObserver.observe(mainScripts, config); -define(function (require) { +window.PhStore.storageReadyPromise + .finally(()=>{ + /** + * The bootstrapping module for brackets. This module sets up the require + * configuration and loads the brackets module. + */ + require.config({ + paths: { + "text": "thirdparty/text/text", + "i18n": "thirdparty/i18n/i18n", + // The file system implementation. Change this value to use different + // implementations (e.g. cloud-based storage). + "fileSystemImpl": "filesystem/impls/appshell/AppshellFileSystem", + "preact-compat": "thirdparty/preact-compat/preact-compat.min", + "preact": "thirdparty/preact/preact" + }, + map: { + "*": { + "thirdparty/CodeMirror2": "thirdparty/CodeMirror", + "thirdparty/preact": "preact-compat", + "view/PanelManager": "view/WorkspaceManager" // For extension compatibility + } + }, + waitSeconds: 60 + }); - // Load compatibility shims--these need to load early, be careful moving this - // Event dispatcher must be loaded before worker comm https://github.com/phcode-dev/phoenix/pull/678 - require(["utils/Metrics", "utils/Compatibility", "utils/EventDispatcher"], function () { - window.Metrics = require("utils/Metrics"); - // Load the brackets module. This is a self-running module that loads and runs the entire application. - try{ - require(["brackets"]); - } catch (err) { - // try a cache refresh (not a full reset). this will happen in the service worker in the background - window.refreshServiceWorkerCache && window.refreshServiceWorkerCache(); - // metrics api might not be available here as we were seeing no metrics raised. Only bugsnag there. - window.logger && window.logger.reportError(err, - 'Critical error when loading brackets. Trying to reload again.'); - // wait for 3 seconds for bugsnag to send report. - setTimeout(window.location.reload, 3000); + if (window.location.search.indexOf("testEnvironment") > -1) { + require.config({ + paths: { + "preferences/PreferencesImpl": "../test/TestPreferencesImpl" + }, + locale: "en" // force English (US) + }); + } else { + /** + * hack for r.js optimization, move locale to another config call + * + * Use custom brackets property until CEF sets the correct navigator.language + * NOTE: When we change to navigator.language here, we also should change to + * navigator.language in ExtensionLoader (when making require contexts for each + * extension). + */ + require.config({ + locale: window.PhStore.getItem("locale") || window.navigator.language + }); } - }); -}); + define(function (require) { + + + // Load compatibility shims--these need to load early, be careful moving this + // Event dispatcher must be loaded before worker comm https://github.com/phcode-dev/phoenix/pull/678 + require(["utils/Metrics", "utils/Compatibility", "utils/EventDispatcher"], function () { + window.Metrics = require("utils/Metrics"); + // Load the brackets module. This is a self-running module that loads and runs the entire application. + try{ + require(["brackets"]); + } catch (err) { + // try a cache refresh (not a full reset). this will happen in the service worker in the background + window.refreshServiceWorkerCache && window.refreshServiceWorkerCache(); + // metrics api might not be available here as we were seeing no metrics raised. Only bugsnag there. + window.logger && window.logger.reportError(err, + 'Critical error when loading brackets. Trying to reload again.'); + // wait for 3 seconds for bugsnag to send report. + setTimeout(window.location.reload, 3000); + } + }); + }); + }); diff --git a/src/storage.js b/src/storage.js index d929f2977..9e51fe630 100644 --- a/src/storage.js +++ b/src/storage.js @@ -130,7 +130,7 @@ * Retrieves the value associated with the specified key from the browser's local storage. * * @param {string} key - The key to retrieve the value for. - * @returns {object|null} - The value associated with the specified key. Returns null if the key does not exist. + * @returns {string|number|boolean|object|null} - The value associated with the specified key. Returns null if the key does not exist. */ function getItem(key) { let cachedResult = cache[key]; @@ -162,7 +162,7 @@ * Sets the value of a specified key in the localStorage. * * @param {string} key - The key to set the value for. - * @param {*} value - The value to be stored. Can be any valid JSON serializable data type. + * @param {string|number|boolean|object} value - The value to be stored. Can be any valid JSON serializable data type. * */ function setItem(key, value) { @@ -190,6 +190,16 @@ PhStore.trigger(key, CHANGE_TYPE_INTERNAL); } + /** + * Removes an item from storage. This will trigger a change notification on removal if watchers are attached. + * Watchers are unaffected on removal, you will still get notifications if the key gets created in the future. + * + * @param {string} key - The key to remove + */ + function removeItem(key) { + setItem(key, null); + } + /** * Enables best effort monitoring of external changes to the specified key when there are multiple Phoenix * windows/instances. By default, PhStore.on(, fn) only triggers for key value changes within @@ -263,6 +273,7 @@ const PhStore = { getItem, setItem, + removeItem, flushDB, watchExternalChanges, unwatchExternalChanges, diff --git a/src/utils/Global.js b/src/utils/Global.js index 704da936c..ba44641e5 100644 --- a/src/utils/Global.js +++ b/src/utils/Global.js @@ -67,19 +67,19 @@ define(function (require, exports, module) { // Locale-related APIs global.brackets.isLocaleDefault = function () { - return !global.localStorage.getItem("locale"); + return !global.PhStore.getItem("locale"); }; global.brackets.getLocale = function () { // By default use the locale that was determined in brackets.js - return params.get("testEnvironment") ? "en" : (global.localStorage.getItem("locale") || global.require.s.contexts._.config.locale); + return params.get("testEnvironment") ? "en" : (global.PhStore.getItem("locale") || global.require.s.contexts._.config.locale); }; global.brackets.setLocale = function (locale) { if (locale) { - global.localStorage.setItem("locale", locale); + global.PhStore.setItem("locale", locale); } else { - global.localStorage.removeItem("locale"); + global.PhStore.removeItem("locale"); } }; diff --git a/src/utils/Metrics.js b/src/utils/Metrics.js index 740ea5a73..740d73f29 100644 --- a/src/utils/Metrics.js +++ b/src/utils/Metrics.js @@ -47,13 +47,13 @@ define(function (require, exports, module) { let isFirstUseDay; function _setFirstDayFlag() { - let firstUseDay= localStorage.getItem("healthData.firstUseDay"); - if(!firstUseDay){ - firstUseDay = new Date(); - localStorage.setItem("healthData.firstUseDay", `${firstUseDay.getTime()}`); - } else { - firstUseDay = new Date(parseInt(firstUseDay)); + const firstUseDayKey = "healthData.firstUseDay"; + let firstBootTime = window.PhStore.getItem(firstUseDayKey); + if(!firstBootTime){ + firstBootTime = Date.now(); + window.PhStore.setItem(firstUseDayKey, firstBootTime); } + let firstUseDay= new Date(firstBootTime); let dayAfterFirstUse = new Date(firstUseDay); dayAfterFirstUse.setUTCDate(firstUseDay.getUTCDate() + 1); let today = new Date(); diff --git a/test/spec/Storage-integ-test.js b/test/spec/Storage-integ-test.js index f866c630a..83500e81c 100644 --- a/test/spec/Storage-integ-test.js +++ b/test/spec/Storage-integ-test.js @@ -71,12 +71,21 @@ define(function (require, exports, module) { it("Should be able to get and set different value types", async function () { expectSetGetSuccess(1); expectSetGetSuccess(""); + expectSetGetSuccess(null); expectSetGetSuccess(0); expectSetGetSuccess("hello"); expectSetGetSuccess({hello: {message: "world"}}); expectSetGetSuccess([1, "3"]); }); + it("Should be able to remove item", async function () { + const value = "hello"; + PhStore.setItem(testKey, value); + expect(PhStore.getItem(testKey)).toEql(value); + PhStore.removeItem(testKey); + expect(PhStore.getItem(testKey)).toEql(null); + }); + it("Should be able to get and set with lmdb node connector in tauri", async function () { if(!Phoenix.browser.isTauri){ return; @@ -181,7 +190,7 @@ define(function (require, exports, module) { }); const newValue = "hello"; - await awaits(500);// let time pass as the lowest resolution for time check in browser is 1 ms + await awaits(500);// let time pass testWindow.PhStore.setItem(testKey, newValue); // set in phoenix, it should eventually come to this window expect(testWindow.PhStore.getItem(testKey)).toEql(newValue); await awaitsFor(function () { @@ -191,5 +200,33 @@ define(function (require, exports, module) { expect(changedValue).toEql(newValue); }); + it("Should get changed notification in this window, if removing watched item in external window", async function () { + const currentWinVal = "externalRemove"; + PhStore.watchExternalChanges(testKey); + testWindow.PhStore.watchExternalChanges(testKey); + + PhStore.setItem(testKey, currentWinVal); + expect(PhStore.getItem(testKey)).toEql(currentWinVal); + await awaitsFor(function () { + return testWindow.PhStore.getItem(testKey) === currentWinVal; + }); + + // now both window has set the value. remove it and see if we get notifications + + let changeType, changedValue = undefined; + PhStore.on(testKey, (_event, type)=>{ + changeType = type; + changedValue = PhStore.getItem(testKey); // the new key should be updated when you get the event + }); + + testWindow.PhStore.removeItem(testKey); // remove in phoenix, it should eventually come to this window + expect(testWindow.PhStore.getItem(testKey)).toEql(null); + await awaitsFor(function () { + return changedValue === null; + }); + expect(changeType).toEql("External"); + expect(changedValue).toEql(null); + }); + }); }); From 4c1d2cbfb04f1a846bd2c39d527ae118529f24c8 Mon Sep 17 00:00:00 2001 From: abose Date: Thu, 28 Dec 2023 15:06:08 +0530 Subject: [PATCH 2/3] fix: cut copy paste files across different tauri windows working and localstorage refactor --- .eslintrc.js | 1 + .../new-project/assets/js/code-editor.js | 10 +++---- src/assets/new-project/assets/js/phoenix.js | 1 + src/extensibility/Package.js | 4 +-- src/extensions/default/Phoenix/guided-tour.js | 22 +++++++------- src/extensions/default/Phoenix/new-project.js | 2 +- src/extensions/default/Phoenix/serverSync.js | 17 +++++++++-- src/node-loader.js | 3 ++ src/project/ProjectManager.js | 30 +++++++------------ 9 files changed, 50 insertions(+), 40 deletions(-) diff --git a/.eslintrc.js b/.eslintrc.js index 227bb68b4..29c066306 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -74,6 +74,7 @@ module.exports = { "$": false, "brackets": false, "Phoenix": false, + "PhStore": false, "iconv": false, "Buffer": true, "clearTimeout": false, diff --git a/src/assets/new-project/assets/js/code-editor.js b/src/assets/new-project/assets/js/code-editor.js index 9a502f309..2ec1ac432 100644 --- a/src/assets/new-project/assets/js/code-editor.js +++ b/src/assets/new-project/assets/js/code-editor.js @@ -122,7 +122,7 @@ function removeProject(fullPath) { } function _showFirstTimeExperience() { - let shownBefore = localStorage.getItem('notification.defaultProject.Shown'); + let shownBefore = PhStore.getItem('notification.defaultProject.Shown'); if(!shownBefore){ createNotificationFromTemplate(Strings.DEFAULT_PROJECT_NOTIFICATION, "defaultProjectButton", { @@ -130,12 +130,12 @@ function _showFirstTimeExperience() { autoCloseTimeS: 15, dismissOnClick: true }); - localStorage.setItem('notification.defaultProject.Shown', 'true'); + PhStore.setItem('notification.defaultProject.Shown', 'true'); } } function _updateDropdown() { - let shouldShowWelcome = localStorage.getItem("new-project.showWelcomeScreen") || 'Y'; + let shouldShowWelcome = PhStore.getItem("new-project.showWelcomeScreen") || 'Y'; if(shouldShowWelcome === 'Y') { document.getElementById("showWelcomeIndicator").style = "visibility: visible"; } else { @@ -158,9 +158,9 @@ function _attachSettingBtnEventListeners() { }); document.getElementById("showWelcome").addEventListener('click', (event)=>{ - let shouldShowWelcome = localStorage.getItem("new-project.showWelcomeScreen") || 'Y'; + let shouldShowWelcome = PhStore.getItem("new-project.showWelcomeScreen") || 'Y'; shouldShowWelcome = shouldShowWelcome === 'Y'? 'N' : 'Y'; - localStorage.setItem("new-project.showWelcomeScreen", shouldShowWelcome); + PhStore.setItem("new-project.showWelcomeScreen", shouldShowWelcome); }); document.getElementById("showAbout").addEventListener('click', (event)=>{ diff --git a/src/assets/new-project/assets/js/phoenix.js b/src/assets/new-project/assets/js/phoenix.js index eddca6250..872631e70 100644 --- a/src/assets/new-project/assets/js/phoenix.js +++ b/src/assets/new-project/assets/js/phoenix.js @@ -28,6 +28,7 @@ const RECENT_PROJECTS_INTERFACE = "Extn.Phoenix.recentProjects"; const selfFileName = location.href.split('?')[0].split('/').pop(); window.Strings = window.parent.Strings; window.path = window.parent.path; +window.PhStore = window.parent.PhStore; window.parent.ExtensionInterface.waitAndGetExtensionInterface(NEW_PROJECT_EXTENSION_INTERFACE) .then(interfaceObj => { window.newProjectExtension = interfaceObj; diff --git a/src/extensibility/Package.js b/src/extensibility/Package.js index 587236465..4f8214a43 100644 --- a/src/extensibility/Package.js +++ b/src/extensibility/Package.js @@ -367,14 +367,14 @@ define(function (require, exports, module) { * function manages state weather an extension is enabled or disabled */ function _toggleDisabledExtension(path, enabled) { - let arr = JSON.parse(localStorage.getItem(DISABLED_EXTENSIONS_KEY) || "[]"); + let arr = JSON.parse(PhStore.getItem(DISABLED_EXTENSIONS_KEY) || "[]"); const io = arr.indexOf(path); if (enabled === true && io !== -1) { arr.splice(io, 1); } else if (enabled === false && io === -1) { arr.push(path); } - localStorage.setItem(DISABLED_EXTENSIONS_KEY, JSON.stringify(arr)); + PhStore.setItem(DISABLED_EXTENSIONS_KEY, JSON.stringify(arr)); } /** diff --git a/src/extensions/default/Phoenix/guided-tour.js b/src/extensions/default/Phoenix/guided-tour.js index da7dbcfb1..a80928a8b 100644 --- a/src/extensions/default/Phoenix/guided-tour.js +++ b/src/extensions/default/Phoenix/guided-tour.js @@ -45,8 +45,8 @@ define(function (require, exports, module) { TWO_WEEKS_IN_DAYS = 14, USAGE_COUNTS_KEY = "healthDataUsage"; // private to phoenix, set from health data extension - const userAlreadyDidAction = localStorage.getItem(GUIDED_TOUR_LOCAL_STORAGE_KEY) - ? JSON.parse(localStorage.getItem(GUIDED_TOUR_LOCAL_STORAGE_KEY)) : { + const userAlreadyDidAction = PhStore.getItem(GUIDED_TOUR_LOCAL_STORAGE_KEY) + ? JSON.parse(PhStore.getItem(GUIDED_TOUR_LOCAL_STORAGE_KEY)) : { version: 1, newProjectShown: false, beautifyCodeShown: false, @@ -85,7 +85,7 @@ define(function (require, exports, module) { let keyboardShortcut = KeyBindingManager.getKeyBindings(Commands.EDIT_BEAUTIFY_CODE); keyboardShortcut = (keyboardShortcut && keyboardShortcut[0]) ? keyboardShortcut[0].displayKey : "-"; userAlreadyDidAction.beautifyCodeShown = true; - localStorage.setItem(GUIDED_TOUR_LOCAL_STORAGE_KEY, JSON.stringify(userAlreadyDidAction)); + PhStore.setItem(GUIDED_TOUR_LOCAL_STORAGE_KEY, JSON.stringify(userAlreadyDidAction)); Metrics.countEvent(Metrics.EVENT_TYPE.UI, "guide", "beautify"); currentlyShowingNotification = NotificationUI.createFromTemplate( StringUtils.format(Strings.BEAUTIFY_CODE_NOTIFICATION, keyboardShortcut), @@ -115,7 +115,7 @@ define(function (require, exports, module) { return; } userAlreadyDidAction.newProjectShown = true; - localStorage.setItem(GUIDED_TOUR_LOCAL_STORAGE_KEY, JSON.stringify(userAlreadyDidAction)); + PhStore.setItem(GUIDED_TOUR_LOCAL_STORAGE_KEY, JSON.stringify(userAlreadyDidAction)); Metrics.countEvent(Metrics.EVENT_TYPE.UI, "guide", "newProj"); currentlyShowingNotification = NotificationUI.createFromTemplate(Strings.NEW_PROJECT_NOTIFICATION, "newProject", { @@ -138,7 +138,7 @@ define(function (require, exports, module) { function _showNotification() { // legacy key. cant change without triggering the user base let notificationKey = 'livePreviewPopoutShown', version = "v1"; - let popoutMessageShown = localStorage.getItem(notificationKey); + let popoutMessageShown = PhStore.getItem(notificationKey); if(popoutMessageShown === version){ // already shown LiveDevelopment.off(LiveDevelopment.EVENT_LIVE_PREVIEW_CLICKED, _showNotification); @@ -158,7 +158,7 @@ define(function (require, exports, module) { currentlyShowingNotification.done(()=>{ currentlyShowingNotification = null; }); - localStorage.setItem(notificationKey, version); + PhStore.setItem(notificationKey, version); } LiveDevelopment.off(LiveDevelopment.EVENT_LIVE_PREVIEW_CLICKED, _showNotification); } @@ -171,7 +171,7 @@ define(function (require, exports, module) { function _showLivePreviewNotification() { // legacy reasons live preview notification is called new project notification. const livePreviewNotificationKey = "newProjectNotificationShown"; - const livePreviewNotificationShown = localStorage.getItem(livePreviewNotificationKey); + const livePreviewNotificationShown = PhStore.getItem(livePreviewNotificationKey); if(livePreviewNotificationShown){ return; } @@ -185,7 +185,7 @@ define(function (require, exports, module) { autoCloseTimeS: 15, dismissOnClick: true} ); - localStorage.setItem(livePreviewNotificationKey, "true"); + PhStore.setItem(livePreviewNotificationKey, "true"); currentlyShowingNotification.done(()=>{ currentlyShowingNotification = null; }); @@ -262,7 +262,7 @@ define(function (require, exports, module) { Metrics.countEvent(Metrics.EVENT_TYPE.USER, "notify", "star", 1); _openStarsPopup(); userAlreadyDidAction.lastShownGithubStarsDate = Date.now(); - localStorage.setItem(GUIDED_TOUR_LOCAL_STORAGE_KEY, JSON.stringify(userAlreadyDidAction)); + PhStore.setItem(GUIDED_TOUR_LOCAL_STORAGE_KEY, JSON.stringify(userAlreadyDidAction)); }, GITHUB_STARS_POPUP_TIME); } } @@ -278,7 +278,7 @@ define(function (require, exports, module) { Metrics.countEvent(Metrics.EVENT_TYPE.USER, "survey", "generalShown", 1); Dialogs.showModalDialogUsingTemplate(Mustache.render(SurveyTemplate, templateVars)); userAlreadyDidAction.generalSurveyShownVersion = surveyVersion; - localStorage.setItem(GUIDED_TOUR_LOCAL_STORAGE_KEY, JSON.stringify(userAlreadyDidAction)); + PhStore.setItem(GUIDED_TOUR_LOCAL_STORAGE_KEY, JSON.stringify(userAlreadyDidAction)); } }, GENERAL_SURVEY_TIME); } @@ -326,7 +326,7 @@ define(function (require, exports, module) { $content.find("a").click(_openPowerUserSurvey); NotificationUI.createToastFromTemplate(Strings.POWER_USER_POPUP_TITLE, $content); userAlreadyDidAction.lastShownPowerSurveyDate = Date.now(); - localStorage.setItem(GUIDED_TOUR_LOCAL_STORAGE_KEY, JSON.stringify(userAlreadyDidAction)); + PhStore.setItem(GUIDED_TOUR_LOCAL_STORAGE_KEY, JSON.stringify(userAlreadyDidAction)); }, POWER_USER_SURVEY_TIME); } } diff --git a/src/extensions/default/Phoenix/new-project.js b/src/extensions/default/Phoenix/new-project.js index 52249cdf5..187d18091 100644 --- a/src/extensions/default/Phoenix/new-project.js +++ b/src/extensions/default/Phoenix/new-project.js @@ -96,7 +96,7 @@ define(function (require, exports, module) { function init() { _addMenuEntries(); - const shouldShowWelcome = localStorage.getItem("new-project.showWelcomeScreen") || 'Y'; + const shouldShowWelcome = PhStore.getItem("new-project.showWelcomeScreen") || 'Y'; if(shouldShowWelcome !== 'Y') { Metrics.countEvent(Metrics.EVENT_TYPE.NEW_PROJECT, "dialogue", "disabled"); guidedTour.startTourIfNeeded(); diff --git a/src/extensions/default/Phoenix/serverSync.js b/src/extensions/default/Phoenix/serverSync.js index 702235a9e..4ac2eb49f 100644 --- a/src/extensions/default/Phoenix/serverSync.js +++ b/src/extensions/default/Phoenix/serverSync.js @@ -45,11 +45,24 @@ define(function (require, exports, module) { let projectSyncCompleted = false; let previewURL; + function _fixLegacyUserContext() { + // In earlier versions, user context data was stored in the browser's localStorage. We have since + // transitioned to using PhStore for this purpose. This function helps in migrating the user context + // from localStorage to PhStore. This ensures that existing users don't lose access to their published projects. + // Note: Published projects are retained for only 30 days. This function is primarily intended to support + // users during the transition period and is safe to remove 3 months after the date of this implementation, + // as users are informed that their published projects are kept for a 30-day period only. + if (!PhStore.getItem(USER_CONTEXT) && localStorage.getItem(USER_CONTEXT)) { + PhStore.setItem(USER_CONTEXT, localStorage.getItem(USER_CONTEXT)); + } + } + function _setupUserContext() { - userContext = localStorage.getItem(USER_CONTEXT); + _fixLegacyUserContext(); + userContext = PhStore.getItem(USER_CONTEXT); if(!userContext){ userContext = "p-" + Math.round( Math.random()*10000000000000).toString(16); - localStorage.setItem(USER_CONTEXT, userContext); + PhStore.setItem(USER_CONTEXT, userContext); } } diff --git a/src/node-loader.js b/src/node-loader.js index 9453a8f42..c15c199f5 100644 --- a/src/node-loader.js +++ b/src/node-loader.js @@ -592,11 +592,14 @@ function nodeLoader() { let commandID = 0, pendingCommands = {}; const PHNODE_PREFERENCES_KEY = "PhNode.Prefs"; function setInspectEnabled(enabled) { + // cannot use PhStore instead of localStorage here as this is required at boot. Should be fine + // as this to use non-persistent local storage(due to safari ITP) here as this is a debug flag. const prefs = JSON.parse(localStorage.getItem(PHNODE_PREFERENCES_KEY) || "{}"); prefs.inspectEnabled = enabled; localStorage.setItem(PHNODE_PREFERENCES_KEY, JSON.stringify(prefs)); } function isInspectEnabled() { + // cannot use PhStore instead of localStorage here as this is required at boot. const prefs = JSON.parse(localStorage.getItem(PHNODE_PREFERENCES_KEY) || "{}"); return !!prefs.inspectEnabled; } diff --git a/src/project/ProjectManager.js b/src/project/ProjectManager.js index 35293c2f7..ad1e6fe2a 100644 --- a/src/project/ProjectManager.js +++ b/src/project/ProjectManager.js @@ -44,10 +44,10 @@ define(function (require, exports, module) { require("utils/Global"); - var _ = require("thirdparty/lodash"); + const _ = require("thirdparty/lodash"); // Load dependent modules - let AppInit = require("utils/AppInit"), + const AppInit = require("utils/AppInit"), Async = require("utils/Async"), PreferencesDialogs = require("preferences/PreferencesDialogs"), PreferencesManager = require("preferences/PreferencesManager"), @@ -92,6 +92,8 @@ define(function (require, exports, module) { EventDispatcher.setLeakThresholdForEvent(EVENT_PROJECT_OPEN, 25); + const CLIPBOARD_SYNC_KEY = "phoenix.clipboard"; + /** * @private * Filename to use for project settings files. @@ -1521,22 +1523,12 @@ define(function (require, exports, module) { const OPERATION_CUT = 'cut', OPERATION_COPY = 'copy'; - function _addTextToSystemClipboard(text) { - if (!navigator.clipboard) { - console.warn('Browser doesnt support clipboard control. system cut/copy/paste may not work'); - return; - } - navigator.clipboard.writeText(text).catch(function(err) { - console.error('System clipboard error: Could not copy text: ', err); - }); - } - function _registerPathWithClipboard(path, operation) { - _addTextToSystemClipboard(window.path.basename(path)); - localStorage.setItem("phoenix.clipboard", JSON.stringify({ + Phoenix.app.copyToClipboard(window.path.basename(path)); + PhStore.setItem(CLIPBOARD_SYNC_KEY, { operation: operation, path: path - })); + }); } /** @@ -1575,8 +1567,8 @@ define(function (require, exports, module) { if(fullPath){ let projectRoot = getProjectRoot().fullPath; let relativePath = window.path.relative(projectRoot, fullPath); - _addTextToSystemClipboard(relativePath); - localStorage.setItem("phoenix.clipboard", JSON.stringify({})); + Phoenix.app.copyToClipboard(relativePath); + PhStore.setItem(CLIPBOARD_SYNC_KEY, {}); } } @@ -1691,11 +1683,10 @@ define(function (require, exports, module) { if(context){ targetPath = context.fullPath; } - let clipboard = localStorage.getItem("phoenix.clipboard"); + const clipboard = PhStore.getItem(CLIPBOARD_SYNC_KEY); if(!clipboard){ return; } - clipboard = JSON.parse(clipboard); switch (clipboard.operation) { case OPERATION_CUT: _performCut(clipboard.path, targetPath); break; case OPERATION_COPY: _performCopy(clipboard.path, targetPath); break; @@ -1705,6 +1696,7 @@ define(function (require, exports, module) { // Initialize variables and listeners that depend on the HTML DOM AppInit.htmlReady(function () { + PhStore.watchExternalChanges(CLIPBOARD_SYNC_KEY); $projectTreeContainer = $("#project-files-container"); $projectTreeContainer.addClass("jstree jstree-brackets"); $projectTreeContainer.css("overflow", "auto"); From 5be1fc92570ab22d2548d2c63f4eae82f14312aa Mon Sep 17 00:00:00 2001 From: abose Date: Thu, 28 Dec 2023 15:27:20 +0530 Subject: [PATCH 3/3] refactor: featureGate uses PhStore instead of localstorage --- src/utils/ExtensionUtils.js | 2 +- src/utils/FeatureGate.js | 13 ++++++++++++- test/spec/FeatureGate-test.js | 19 +++++++------------ 3 files changed, 20 insertions(+), 14 deletions(-) diff --git a/src/utils/ExtensionUtils.js b/src/utils/ExtensionUtils.js index d050e5ee9..4d4255ac1 100644 --- a/src/utils/ExtensionUtils.js +++ b/src/utils/ExtensionUtils.js @@ -243,7 +243,7 @@ define(function (require, exports, module) { // we should still create an empty one, so we can attach // disabled property on it in case it's disabled let disabled, - defaultDisabled = JSON.parse(localStorage.getItem(Package.DEFAULT_DISABLED_EXTENSIONS_KEY) || "[]"); + defaultDisabled = JSON.parse(PhStore.getItem(Package.DEFAULT_DISABLED_EXTENSIONS_KEY) || "[]"); if (Array.isArray(defaultDisabled) && defaultDisabled.indexOf(baseExtensionUrl) !== -1) { console.warn("Extension has been disabled on startup: " + baseExtensionUrl); disabled = true; diff --git a/src/utils/FeatureGate.js b/src/utils/FeatureGate.js index 452512434..c55f4738c 100644 --- a/src/utils/FeatureGate.js +++ b/src/utils/FeatureGate.js @@ -106,7 +106,7 @@ define(function (require, exports, module) { * @type {function} */ function isFeatureEnabled(featureName) { - let userOverRide = localStorage.getItem(`${featureName}`); + let userOverRide = PhStore.getItem(`FeatureGate-${featureName}`); if(userOverRide === ENABLED){ return true; } else if(userOverRide === DISABLED){ @@ -115,11 +115,22 @@ define(function (require, exports, module) { return _FeatureGateMap[featureName] === true; } + /** + * Sets the enabled state of a specific feature in the application. + * + * @param {string} featureName - The name of the feature to be modified. + * @param {boolean} isEnabled - A boolean flag indicating whether the feature should be enabled (true) or disabled (false). + */ + function setFeatureEnabled(featureName, isEnabled) { + PhStore.setItem(`FeatureGate-${featureName}`, isEnabled ? ENABLED : DISABLED); + } + EventDispatcher.makeEventDispatcher(exports); // Public API exports.registerFeatureGate = registerFeatureGate; exports.getAllRegisteredFeatures = getAllRegisteredFeatures; exports.isFeatureEnabled = isFeatureEnabled; + exports.setFeatureEnabled = setFeatureEnabled; // Events exports.FEATURE_REGISTERED = FEATURE_REGISTERED; }); diff --git a/test/spec/FeatureGate-test.js b/test/spec/FeatureGate-test.js index 1a50d5227..68043a08f 100644 --- a/test/spec/FeatureGate-test.js +++ b/test/spec/FeatureGate-test.js @@ -43,19 +43,14 @@ define(function (require, exports, module) { }, "Feature gate registration notification"); }); - it("user should be able to override feature in localstorage", function () { - const FEATURENAME = "feature3", - STORAGE_KEY = `${FEATURENAME}`; - localStorage.removeItem(STORAGE_KEY); - FeatureGate.registerFeatureGate(FEATURENAME, true); + it("user should be able to override feature enable/disable in FeatureGate", function () { + const FEATURE_NAME = "feature3"; + FeatureGate.registerFeatureGate(FEATURE_NAME, true); - localStorage.setItem(STORAGE_KEY, "enabled"); - expect(FeatureGate.isFeatureEnabled(FEATURENAME)).toEqual(true); - localStorage.setItem(STORAGE_KEY, "disabled"); - expect(FeatureGate.isFeatureEnabled(FEATURENAME)).toEqual(false); - localStorage.setItem(STORAGE_KEY, "invalidValueWillHonorDefaultValue"); - expect(FeatureGate.isFeatureEnabled(FEATURENAME)).toEqual(true); - localStorage.removeItem(STORAGE_KEY); + FeatureGate.setFeatureEnabled(FEATURE_NAME, true); + expect(FeatureGate.isFeatureEnabled(FEATURE_NAME)).toEqual(true); + FeatureGate.setFeatureEnabled(FEATURE_NAME, false); + expect(FeatureGate.isFeatureEnabled(FEATURE_NAME)).toEqual(false); }); }); });