From 17399324f429e25a075ba5a4cee4938379ba856f Mon Sep 17 00:00:00 2001 From: fatadel Date: Fri, 24 Jul 2026 10:49:18 +0200 Subject: [PATCH] Improve discoverability of downloading a local profile MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The profile menu was labeled "Upload Local Profile" with an upload icon, so the Download button tucked inside its dialog was easy to miss for users who just wanted to save a profile to disk. Rename the menu button to "Save This Profile…" and give it a save icon. In the panel, present the Download and Upload actions as two destination cards instead of a pair of buttons, and add an "applies to both" badge to the include-options heading to make clear those choices affect either destination. The upload card reads "Re-upload" when the profile is already published. Closes #3620 --- locales/en-US/app.ftl | 22 +- res/img/svg/save-light.svg | 6 + res/img/svg/save.svg | 6 + src/components/app/MenuButtons/Publish.css | 136 +++++++++--- src/components/app/MenuButtons/Publish.tsx | 141 +++++++++--- src/components/app/MenuButtons/index.tsx | 13 +- src/test/components/MenuButtons.test.tsx | 8 +- .../__snapshots__/MenuButtons.test.tsx.snap | 200 +++++++++++++----- 8 files changed, 393 insertions(+), 139 deletions(-) create mode 100644 res/img/svg/save-light.svg create mode 100644 res/img/svg/save.svg diff --git a/locales/en-US/app.ftl b/locales/en-US/app.ftl index 3697baf81e..65c246b244 100644 --- a/locales/en-US/app.ftl +++ b/locales/en-US/app.ftl @@ -576,15 +576,12 @@ MenuButtons--index--metaInfo-button = MenuButtons--index--full-view = Full View MenuButtons--index--cancel-upload = Cancel Upload -MenuButtons--index--share-upload = - .label = Upload Local Profile - -MenuButtons--index--share-re-upload = - .label = Re-upload - MenuButtons--index--share-error-uploading = .label = Error uploading +MenuButtons--index--save-profile = + .label = Save This Profile… + MenuButtons--index--revert = Revert to Original Profile MenuButtons--permalink--button = @@ -733,13 +730,9 @@ MenuButtons--publish--renderCheckbox-label-preference = Include preference value MenuButtons--publish--renderCheckbox-label-private-browsing = Include the data from private browsing windows MenuButtons--publish--renderCheckbox-label-private-browsing-warning-image = .title = This profile contains private browsing data -MenuButtons--publish--reupload-performance-profile = Re-upload Performance Profile -MenuButtons--publish--share-performance-profile = Share Performance Profile -MenuButtons--publish--info-description = Upload your profile and make it accessible to anyone with the link. MenuButtons--publish--info-description-default = By default, your personal data is removed. MenuButtons--publish--info-description-firefox-nightly2 = This profile is from { -firefox-nightly-brand-name }, so by default most information is included. MenuButtons--publish--include-additional-data = Include additional data that may be identifiable -MenuButtons--publish--button-upload = Upload MenuButtons--publish--upload-title = Uploading profile… MenuButtons--publish--cancel-upload = Cancel Upload MenuButtons--publish--message-something-went-wrong = Uh oh, something went wrong when uploading the profile. @@ -747,6 +740,15 @@ MenuButtons--publish--message-try-again = Try again MenuButtons--publish--download = Download MenuButtons--publish--compressing = Compressing… MenuButtons--publish--error-while-compressing = Error while compressing, try unchecking some checkboxes to reduce the profile size. +MenuButtons--publish--save-profile-title = Save This Profile +MenuButtons--publish--save-profile-description = Pick what to include, then choose a destination below. +# Refers to the two destinations offered in this panel: downloading the profile to a file and uploading it to share. +MenuButtons--publish--applies-to-both = applies to both +MenuButtons--publish--destination-download-title = Download +MenuButtons--publish--destination-download-description = Saved to your computer +MenuButtons--publish--destination-upload-title = Upload +MenuButtons--publish--destination-reupload-title = Re-upload +MenuButtons--publish--destination-upload-description = Accessible to anyone with the link ## NetworkSettings ## This is used in the network chart. diff --git a/res/img/svg/save-light.svg b/res/img/svg/save-light.svg new file mode 100644 index 0000000000..63441dde69 --- /dev/null +++ b/res/img/svg/save-light.svg @@ -0,0 +1,6 @@ + + + + diff --git a/res/img/svg/save.svg b/res/img/svg/save.svg new file mode 100644 index 0000000000..d6453da456 --- /dev/null +++ b/res/img/svg/save.svg @@ -0,0 +1,6 @@ + + + + diff --git a/src/components/app/MenuButtons/Publish.css b/src/components/app/MenuButtons/Publish.css index 60429e8b08..76b1ff539d 100644 --- a/src/components/app/MenuButtons/Publish.css +++ b/src/components/app/MenuButtons/Publish.css @@ -2,22 +2,25 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -/* These two background images build on the generic styles defined in - * MenuButtons/index.css when using an icon. */ -.menuButtonsShareButtonButton { - --internal-share-icon: url(../../../../res/img/svg/share-dark-12.svg); +/* The animated icon shown on the "Cancel Upload" button while an upload is in + * progress. It builds on the generic icon styles in MenuButtons/index.css. */ +.menuButtonsUploadingButton { --internal-uploading-icon: url(../../../../res/img/svg/sharing-animated-dark-12.svg); } -.menuButtonsShareButtonButton::before { - background-image: var(--internal-share-icon); +.menuButtonsUploadingButton::before { + background-image: var(--internal-uploading-icon); } -.menuButtonsShareButtonButton-uploading::before { - background-image: var(--internal-uploading-icon); +.menuButtonsSaveButtonButton { + --internal-save-icon: url(../../../../res/img/svg/save.svg); +} + +.menuButtonsSaveButtonButton::before { + background-image: var(--internal-save-icon); } -.menuButtonsShareButtonError { +.menuButtonsSaveButtonError { --internal-error-foreground-color: white; --internal-error-background-color: var(--red-60); --internal-error-hover-background-color: var(--red-70); @@ -28,22 +31,22 @@ color: var(--internal-error-foreground-color); } -.menuButtonsShareButtonError:hover { +.menuButtonsSaveButtonError:hover { background-color: var(--internal-error-hover-background-color); } -.buttonWithPanel.open > .menuButtonsShareButtonError, -.menuButtonsShareButtonError:hover:active { +.buttonWithPanel.open > .menuButtonsSaveButtonError, +.menuButtonsSaveButtonError:hover:active { background-color: var(--internal-error-active-background-color); } -.menuButtonsShareButtonError::before { +.menuButtonsSaveButtonError::before { background-image: var(--internal-error-icon); } .publishPanelPanel { --width: 510px; - --internal-info-icon: url(../../../../res/img/svg/info.svg); + --internal-muted-foreground-color: var(--grey-50); --internal-button-disabled-hover-background-color: var(--grey-90-a10); --internal-upload-icon: url(../../../../res/img/svg/upload.svg); --internal-download-icon: url(../../../../res/img/svg/download.svg); @@ -53,17 +56,16 @@ } :root.dark-mode { - .menuButtonsShareButtonButton { - --internal-share-icon: url(../../../../res/img/svg/share-light-12.svg); - --internal-uploading-icon: url(../../../../res/img/svg/sharing-animated-dark-12.svg); + .menuButtonsSaveButtonButton { + --internal-save-icon: url(../../../../res/img/svg/save-light.svg); } - .menuButtonsShareButtonError { + .menuButtonsSaveButtonError { --internal-error-foreground-color: var(--grey-20); } .publishPanelPanel { - --internal-info-icon: url(../../../../res/img/svg/info-light.svg); + --internal-muted-foreground-color: var(--grey-40); --internal-button-disabled-hover-background-color: var(--grey-10-a10); --internal-download-icon: url(../../../../res/img/svg/download-light.svg); --internal-upload-percentage-foreground-color: var(--blue-60); @@ -74,22 +76,14 @@ .publishPanelContent { position: relative; - - /* This aligns all content, except the big icon. */ - padding-left: 70px; } .publishPanelTitle { - /* "60px" This is the value to put the background image at the right location. - * This background image is 44x44, so this puts it 16px left of the text. */ - padding-left: 60px; - margin: 0 0 0 -60px; - background: var(--internal-info-icon) left center no-repeat; - line-height: 44px; /* This is the height of the background image */ + margin: 0 0 10px; + line-height: normal; } .publishPanelInfoDescription { - flex: 1; margin-bottom: 1em; line-height: 1.5; } @@ -160,6 +154,90 @@ font-size: 11px; } +.publishPanelDataChoicesHeader { + display: flex; + align-items: center; + gap: 8px; +} + +.publishPanelAppliesToBoth { + display: inline-block; + padding: 1px 8px; + border-radius: 10px; + background: var(--clickable-background-color); + color: var(--clickable-foreground-color); + font-size: 11px; + font-weight: normal; +} + +.saveDestCards { + display: flex; + margin-top: 18px; + gap: 12px; +} + +.saveDest { + display: flex; + box-sizing: border-box; + flex: 1; + flex-direction: column; + align-items: center; + padding: 16px 12px; + border: 1.5px solid var(--base-border-color); + border-radius: 8px; + background: none; + color: inherit; + cursor: default; + font: inherit; + gap: 3px; + text-align: center; + text-decoration: none; +} + +.saveDest:hover:not(.saveDestDisabled) { + border-color: var(--clickable-border-color); + box-shadow: 0 1px 4px var(--base-shadow-color); +} + +.saveDestDisabled { + opacity: 0.6; +} + +.saveDestIcon { + width: 26px; + height: 26px; + margin-bottom: 4px; +} + +.saveDestIconDownload { + background: var(--internal-download-icon) center / 22px no-repeat; +} + +.saveDestIconUpload { + background: url(../../../../res/img/svg/cloud-dark-12.svg) center / 24px + no-repeat; +} + +:root.dark-mode .saveDestIconUpload { + background-image: url(../../../../res/img/svg/cloud-light-12.svg); +} + +.saveDestTitle { + font-size: 14px; + font-weight: 600; +} + +.saveDestSub { + color: var(--internal-muted-foreground-color); + font-size: 12px; +} + +.saveDestMeta { + margin-top: 4px; + color: var(--internal-muted-foreground-color); + font-size: 11px; +} + .publishPanelUpload { position: relative; padding: 10px 0; diff --git a/src/components/app/MenuButtons/Publish.tsx b/src/components/app/MenuButtons/Publish.tsx index b464465a33..596468fa09 100644 --- a/src/components/app/MenuButtons/Publish.tsx +++ b/src/components/app/MenuButtons/Publish.tsx @@ -119,14 +119,109 @@ class PublishPanelImpl extends React.PureComponent { this.props.attemptToPublish(this._inflightEncoding); }; + _renderDownloadCard() { + const { sanitizedProfileEncodingState, downloadFileName } = this.props; + const cardBody = (meta: React.ReactNode) => ( + <> + + + + Download + + + + + Saved to your computer + + + {meta} + + ); + + switch (sanitizedProfileEncodingState.phase) { + case 'DONE': { + const { profileData } = sanitizedProfileEncodingState; + return ( + + {cardBody( + + {prettyBytes(profileData.size)} + + )} + + ); + } + case 'ERROR': + return ( + + ); + case 'INITIAL': + case 'ENCODING': + return ( + + ); + default: + throw assertExhaustiveCheck(sanitizedProfileEncodingState); + } + } + + _renderUploadCard() { + const { isRepublish, sanitizedProfileEncodingState } = this.props; + return ( + + ); + } + _renderPublishPanel() { const { shouldShowPreferenceOption, profileContainsPrivateBrowsingInformation, sanitizedProfileEncodingState, - downloadFileName, shouldSanitizeByDefault, - isRepublish, } = this.props; return ( @@ -136,20 +231,13 @@ class PublishPanelImpl extends React.PureComponent { onSubmit={this._onSubmit} >

- {isRepublish ? ( - - Re-upload Performance Profile - - ) : ( - - Share Performance Profile - - )} + + Save This Profile +

- - Upload your profile and make it accessible to anyone with the - link. + + Pick what to include, then choose a destination below. {' '} {shouldSanitizeByDefault ? ( @@ -162,10 +250,15 @@ class PublishPanelImpl extends React.PureComponent { )}

-

+

Include additional data that may be identifiable + + + applies to both + +

{this._renderCheckbox( @@ -221,21 +314,9 @@ class PublishPanelImpl extends React.PureComponent {
) : null} -
- - +
+ {this._renderDownloadCard()} + {this._renderUploadCard()}
diff --git a/src/components/app/MenuButtons/index.tsx b/src/components/app/MenuButtons/index.tsx index 92c75be4e6..0383c36894 100644 --- a/src/components/app/MenuButtons/index.tsx +++ b/src/components/app/MenuButtons/index.tsx @@ -254,7 +254,7 @@ class MenuButtonsImpl extends React.PureComponent { return (
matches the snapshot for a compression error

- Share Performance Profile + Save This Profile

- Upload your profile and make it accessible to anyone with the link. + Pick what to include, then choose a destination below. By default, your personal data is removed.

Include additional data that may be identifiable + + applies to both +

matches the snapshot for a compression error
@@ -2022,10 +2048,10 @@ exports[`app/MenuButtons matches the snapshot for the closed state 1`] >
matches the snapshot for the menu buttons and >
matches the snapshot for the menu buttons and

- Re-upload Performance Profile + Save This Profile

- Upload your profile and make it accessible to anyone with the link. + Pick what to include, then choose a destination below. By default, your personal data is removed.

Include additional data that may be identifiable + + applies to both +

matches the snapshot for the menu buttons and
- Download - + Download + + + Saved to your computer + + - ( 1.58 kB - )
@@ -2212,19 +2258,24 @@ exports[`app/MenuButtons matches the snapshot for the opened panel for

- Share Performance Profile + Save This Profile

- Upload your profile and make it accessible to anyone with the link. + Pick what to include, then choose a destination below. This profile is from ⁨Firefox Nightly⁩, so by default most information is included.

Include additional data that may be identifiable + + applies to both +

matches the snapshot for the opened panel for
- Download - + Download + + + Saved to your computer + + - ( 1.58 kB - )
@@ -2330,19 +2396,24 @@ exports[`app/MenuButtons matches the snapshot for the opened panel for

- Share Performance Profile + Save This Profile

- Upload your profile and make it accessible to anyone with the link. + Pick what to include, then choose a destination below. By default, your personal data is removed.

Include additional data that may be identifiable + + applies to both +

matches the snapshot for the opened panel for
- Download - + Download + + + Saved to your computer + + - ( 1.58 kB - )