forked from mirrors/gecko-dev
Bug 1828673 - Add support for overview page in synced tabs. r=desktop-theme-reviewers,fxview-reviewers,jsudiaman,dao,sfoster,fluent-reviewers,flod
Differential Revision: https://phabricator.services.mozilla.com/D184414
This commit is contained in:
parent
b4e65f0c36
commit
ae0fa36393
6 changed files with 201 additions and 95 deletions
|
|
@ -98,6 +98,9 @@
|
|||
<div>
|
||||
<view-recentlyclosed slot="recentlyclosed"></view-recentlyclosed>
|
||||
</div>
|
||||
<div>
|
||||
<view-syncedtabs slot="syncedtabs"></view-syncedtabs>
|
||||
</div>
|
||||
</view-overview>
|
||||
<view-history name="history"></view-history>
|
||||
<view-opentabs name="opentabs"></view-opentabs>
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ import {
|
|||
html,
|
||||
ifDefined,
|
||||
styleMap,
|
||||
classMap,
|
||||
when,
|
||||
} from "chrome://global/content/vendor/lit.all.mjs";
|
||||
import { MozLitElement } from "chrome://global/content/lit-utils.mjs";
|
||||
|
|
@ -213,9 +214,19 @@ export default class FxviewTabList extends MozLitElement {
|
|||
role="list"
|
||||
@keydown=${this.handleFocusElementInRow}
|
||||
>
|
||||
${tabItems.map(
|
||||
(tabItem, i) =>
|
||||
html`
|
||||
${tabItems.map((tabItem, i) => {
|
||||
let time;
|
||||
if (tabItem.time || tabItem.closedAt) {
|
||||
let stringTime = (tabItem.time || tabItem.closedAt).toString();
|
||||
// Different APIs return time in different units, so we use
|
||||
// the length to decide if it's milliseconds or nanoseconds.
|
||||
if (stringTime.length === 16) {
|
||||
time = (tabItem.time || tabItem.closedAt) / 1000;
|
||||
} else {
|
||||
time = tabItem.time || tabItem.closedAt;
|
||||
}
|
||||
}
|
||||
return html`
|
||||
<fxview-tab-row
|
||||
exportparts="secondary-button"
|
||||
?active=${i == activeIndex}
|
||||
|
|
@ -224,24 +235,21 @@ export default class FxviewTabList extends MozLitElement {
|
|||
.currentActiveElementId=${currentActiveElementId}
|
||||
.dateTimeFormat=${dateTimeFormat}
|
||||
.favicon=${tabItem.icon}
|
||||
.primaryL10nId=${tabItem.primaryL10nId}
|
||||
.primaryL10nId=${ifDefined(tabItem.primaryL10nId)}
|
||||
.primaryL10nArgs=${ifDefined(tabItem.primaryL10nArgs)}
|
||||
role="listitem"
|
||||
.secondaryL10nId=${tabItem.secondaryL10nId}
|
||||
.secondaryL10nId=${ifDefined(tabItem.secondaryL10nId)}
|
||||
.secondaryL10nArgs=${ifDefined(tabItem.secondaryL10nArgs)}
|
||||
.closedId=${ifDefined(tabItem.closedId || tabItem.closedId)}
|
||||
.tabElement=${ifDefined(tabItem.tabElement)}
|
||||
.time=${(tabItem.time || tabItem.closedAt).toString().length ===
|
||||
16
|
||||
? (tabItem.time || tabItem.closedAt) / 1000
|
||||
: tabItem.time || tabItem.closedAt}
|
||||
.time=${ifDefined(time)}
|
||||
.timeMsPref=${ifDefined(this.timeMsPref)}
|
||||
.title=${tabItem.title}
|
||||
.url=${tabItem.url}
|
||||
.url=${ifDefined(tabItem.url)}
|
||||
>
|
||||
</fxview-tab-row>
|
||||
`
|
||||
)}
|
||||
`;
|
||||
})}
|
||||
</div>
|
||||
<slot name="menu"></slot>
|
||||
`;
|
||||
|
|
@ -336,7 +344,7 @@ export class FxviewTabRow extends MozLitElement {
|
|||
|
||||
dateFluentId(timestamp, dateTimeFormat, _nowThresholdMs = NOW_THRESHOLD_MS) {
|
||||
if (!timestamp) {
|
||||
return "";
|
||||
return null;
|
||||
}
|
||||
if (dateTimeFormat === "relative") {
|
||||
const elapsed = Date.now() - timestamp;
|
||||
|
|
@ -447,14 +455,17 @@ export class FxviewTabRow extends MozLitElement {
|
|||
/>
|
||||
<link rel="stylesheet" href=${this.constructor.stylesheetUrl} />
|
||||
<a
|
||||
href=${this.url}
|
||||
class="fxview-tab-row-main"
|
||||
.href=${ifDefined(this.url)}
|
||||
class=${classMap({
|
||||
"fxview-tab-row-main": true,
|
||||
"fxview-tab-row-header": !this.url,
|
||||
})}
|
||||
id="fxview-tab-row-main"
|
||||
tabindex=${this.active &&
|
||||
this.currentActiveElementId === "fxview-tab-row-main"
|
||||
? "0"
|
||||
: "-1"}
|
||||
data-l10n-id=${this.primaryL10nId}
|
||||
data-l10n-id=${ifDefined(this.primaryL10nId)}
|
||||
data-l10n-args=${ifDefined(this.primaryL10nArgs)}
|
||||
@click=${this.primaryActionHandler}
|
||||
@keydown=${this.primaryActionHandler}
|
||||
|
|
@ -492,9 +503,9 @@ export class FxviewTabRow extends MozLitElement {
|
|||
class="fxview-tab-row-time"
|
||||
id="fxview-tab-row-time"
|
||||
?hidden=${this.compact || !timeString}
|
||||
data-timestamp=${this.time}
|
||||
data-timestamp=${ifDefined(this.time)}
|
||||
data-l10n-id=${ifDefined(timeString)}
|
||||
data-l10n-args=${timeArgs}
|
||||
data-l10n-args=${ifDefined(timeArgs)}
|
||||
>
|
||||
</span>
|
||||
</a>
|
||||
|
|
|
|||
|
|
@ -51,6 +51,18 @@
|
|||
background-color: var(--fxviewtabrow-element-background-active);
|
||||
}
|
||||
|
||||
.fxview-tab-row-header {
|
||||
margin-top: 8px;
|
||||
cursor: inherit;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.fxview-tab-row-header:hover,
|
||||
.fxview-tab-row-header:hover:active {
|
||||
color: inherit;
|
||||
background-color: inherit;
|
||||
}
|
||||
|
||||
@media (prefers-contrast) {
|
||||
.fxview-tab-row-main,
|
||||
.fxview-tab-row-main:hover,
|
||||
|
|
@ -81,7 +93,7 @@
|
|||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.fxview-tab-row-main:hover .fxview-tab-row-title {
|
||||
.fxview-tab-row-main:hover:not(.fxview-tab-row-header) .fxview-tab-row-title {
|
||||
text-decoration-line: underline;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -15,7 +15,11 @@ const { TabsSetupFlowManager } = ChromeUtils.importESModule(
|
|||
"resource:///modules/firefox-view-tabs-setup-manager.sys.mjs"
|
||||
);
|
||||
|
||||
import { html, ifDefined } from "chrome://global/content/vendor/lit.all.mjs";
|
||||
import {
|
||||
html,
|
||||
ifDefined,
|
||||
when,
|
||||
} from "chrome://global/content/vendor/lit.all.mjs";
|
||||
import { ViewPage } from "./viewpage.mjs";
|
||||
|
||||
const SYNCED_TABS_CHANGED = "services.sync.tabs.changed";
|
||||
|
|
@ -31,7 +35,7 @@ class SyncedTabsInView extends ViewPage {
|
|||
this._id = Math.floor(Math.random() * 10e6);
|
||||
this.currentSyncedTabs = [];
|
||||
if (this.overview) {
|
||||
this.maxTabsLength = 5;
|
||||
this.maxTabsLength = 6; // 5 tabs plus the device row
|
||||
} else {
|
||||
// Setting maxTabsLength to -1 for no max
|
||||
this.maxTabsLength = -1;
|
||||
|
|
@ -218,6 +222,7 @@ class SyncedTabsInView extends ViewPage {
|
|||
.descriptionLink=${ifDefined(descriptionLink)}
|
||||
class="empty-state synced-tabs"
|
||||
?isSelectedTab=${this.selectedTab}
|
||||
?isInnerCard=${this.overview}
|
||||
mainImageUrl="${ifDefined(mainImageUrl)}"
|
||||
headerIconUrl="${ifDefined(headerIconUrl)}"
|
||||
>
|
||||
|
|
@ -284,15 +289,74 @@ class SyncedTabsInView extends ViewPage {
|
|||
}
|
||||
|
||||
noDeviceTabsTemplate(deviceName, deviceType) {
|
||||
if (this.overview) {
|
||||
return html` ${this.deviceTemplate(deviceName, deviceType, [])}
|
||||
<div
|
||||
class="blackbox notabs"
|
||||
data-l10n-id="firefoxview-syncedtabs-device-notabs"
|
||||
></div>`;
|
||||
}
|
||||
return html`<card-container>
|
||||
<h2 slot="header">
|
||||
<div class="icon ${deviceType}" role="presentation"></div>
|
||||
<span class="icon ${deviceType}" role="presentation"></span>
|
||||
${deviceName}
|
||||
</h2>
|
||||
<div slot="main" class="blackbox notabs">No tabs open on this device</div>
|
||||
<div
|
||||
slot="main"
|
||||
class="blackbox notabs"
|
||||
data-l10n-id="firefoxview-syncedtabs-device-notabs"
|
||||
></div>
|
||||
</card-container>`;
|
||||
}
|
||||
|
||||
deviceTemplate(deviceName, deviceType, tabs) {
|
||||
let tabItems = this.getTabItems(tabs);
|
||||
if (this.overview) {
|
||||
/* Insert device at the beginning of the tabs array */
|
||||
let icon;
|
||||
switch (deviceType) {
|
||||
case "phone":
|
||||
icon = "chrome://browser/skin/device-phone.svg";
|
||||
break;
|
||||
case "desktop":
|
||||
icon = "chrome://browser/skin/device-desktop.svg";
|
||||
break;
|
||||
case "tablet":
|
||||
icon = "chrome://browser/skin/device-tablet.svg";
|
||||
break;
|
||||
}
|
||||
tabItems.unshift({
|
||||
icon,
|
||||
title: deviceName,
|
||||
});
|
||||
}
|
||||
return html`${when(
|
||||
!this.overview,
|
||||
() => html`<h2 slot="header">
|
||||
<span class="icon ${deviceType}" role="presentation"></span>
|
||||
${deviceName}
|
||||
</h2>`
|
||||
)}
|
||||
<fxview-tab-list
|
||||
slot="main"
|
||||
class="syncedtabs"
|
||||
hasPopup="menu"
|
||||
.tabItems=${ifDefined(tabItems)}
|
||||
maxTabsLength=${this.maxTabsLength}
|
||||
@fxview-tab-list-primary-action=${this.onOpenLink}
|
||||
@fxview-tab-list-secondary-action=${this.onContextMenu}
|
||||
>
|
||||
${when(
|
||||
this.overview,
|
||||
() => html`<h2 slot="header">
|
||||
<span class="icon ${deviceType}" role="presentation"></span>
|
||||
${deviceName}
|
||||
</h2>`
|
||||
)}
|
||||
${this.panelListTemplate()}
|
||||
</fxview-tab-list>`;
|
||||
}
|
||||
|
||||
generateTabList() {
|
||||
let renderArray = [];
|
||||
let renderInfo = {};
|
||||
|
|
@ -320,26 +384,25 @@ class SyncedTabsInView extends ViewPage {
|
|||
|
||||
for (let id in renderInfo) {
|
||||
if (renderInfo[id].tabs.length) {
|
||||
renderArray.push(html`<card-container>
|
||||
<h2 slot="header">
|
||||
<div
|
||||
class="icon ${renderInfo[id].deviceType}"
|
||||
role="presentation"
|
||||
></div>
|
||||
${renderInfo[id].name}
|
||||
</h2>
|
||||
<fxview-tab-list
|
||||
slot="main"
|
||||
class="syncedtabs"
|
||||
hasPopup="menu"
|
||||
.tabItems=${ifDefined(this.getTabItems(renderInfo[id].tabs))}
|
||||
maxTabsLength=${this.maxTabsLength}
|
||||
@fxview-tab-list-primary-action=${this.onOpenLink}
|
||||
@fxview-tab-list-secondary-action=${this.onContextMenu}
|
||||
>
|
||||
${this.panelListTemplate()}
|
||||
</fxview-tab-list>
|
||||
</card-container>`);
|
||||
if (this.overview) {
|
||||
renderArray.push(
|
||||
this.deviceTemplate(
|
||||
renderInfo[id].name,
|
||||
renderInfo[id].deviceType,
|
||||
renderInfo[id].tabs
|
||||
)
|
||||
);
|
||||
} else {
|
||||
renderArray.push(
|
||||
html`<card-container
|
||||
>${this.deviceTemplate(
|
||||
renderInfo[id].name,
|
||||
renderInfo[id].deviceType,
|
||||
renderInfo[id].tabs
|
||||
)}</card-container
|
||||
>`
|
||||
);
|
||||
}
|
||||
} else {
|
||||
renderArray.push(
|
||||
this.noDeviceTabsTemplate(
|
||||
|
|
@ -351,9 +414,35 @@ class SyncedTabsInView extends ViewPage {
|
|||
}
|
||||
return renderArray;
|
||||
}
|
||||
render() {
|
||||
const stateIndex = this._currentSetupStateIndex;
|
||||
|
||||
generateCardContent() {
|
||||
switch (this._currentSetupStateIndex) {
|
||||
case 0 /* error-state */:
|
||||
if (this.errorState) {
|
||||
return this.generateMessageCard({ error: true });
|
||||
}
|
||||
break;
|
||||
case 1 /* not-signed-in */:
|
||||
if (Services.prefs.prefHasUserValue("services.sync.lastversion")) {
|
||||
// If this pref is set, the user has signed out of sync.
|
||||
// This path is also taken if we are disconnected from sync. See bug 1784055
|
||||
return this.generateMessageCard({
|
||||
error: true,
|
||||
errorState: "signed-out",
|
||||
});
|
||||
}
|
||||
return this.generateMessageCard({ action: "sign-in" });
|
||||
case 2 /* connect-secondary-device*/:
|
||||
return this.generateMessageCard({ action: "add-device" });
|
||||
case 3 /* disabled-tab-sync */:
|
||||
return this.generateMessageCard({ action: "sync-tabs-disabled" });
|
||||
case 4 /* synced-tabs-loaded*/:
|
||||
return this.generateTabList();
|
||||
}
|
||||
return html``;
|
||||
}
|
||||
|
||||
render() {
|
||||
this.open =
|
||||
!TabsSetupFlowManager.isTabSyncSetupComplete ||
|
||||
Services.prefs.getBoolPref(UI_OPEN_STATE, true);
|
||||
|
|
@ -376,34 +465,23 @@ class SyncedTabsInView extends ViewPage {
|
|||
</div>`);
|
||||
}
|
||||
|
||||
switch (stateIndex) {
|
||||
case 0 /* error-state */:
|
||||
if (this.errorState) {
|
||||
renderArray.push(this.generateMessageCard({ error: true }));
|
||||
}
|
||||
break;
|
||||
case 1 /* not-signed-in */:
|
||||
if (Services.prefs.prefHasUserValue("services.sync.lastversion")) {
|
||||
// If this pref is set, the user has signed out of sync.
|
||||
// This path is also taken if we are disconnected from sync. See bug 1784055
|
||||
if (this.overview) {
|
||||
renderArray.push(
|
||||
this.generateMessageCard({ error: true, errorState: "signed-out" })
|
||||
html`<card-container
|
||||
preserveCollapseState
|
||||
viewAllPage=${this._currentSetupStateIndex == 4 ? "syncedtabs" : null}
|
||||
>
|
||||
>
|
||||
<h2
|
||||
slot="header"
|
||||
data-l10n-id="firefoxview-synced-tabs-header"
|
||||
class="overview-header"
|
||||
></h2>
|
||||
<div slot="main">${this.generateCardContent()}</div>
|
||||
</card-container>`
|
||||
);
|
||||
} else {
|
||||
renderArray.push(this.generateMessageCard({ action: "sign-in" }));
|
||||
}
|
||||
break;
|
||||
case 2 /* connect-secondary-device*/:
|
||||
renderArray.push(this.generateMessageCard({ action: "add-device" }));
|
||||
break;
|
||||
case 3 /* disabled-tab-sync */:
|
||||
renderArray.push(
|
||||
this.generateMessageCard({ action: "sync-tabs-disabled" })
|
||||
);
|
||||
break;
|
||||
case 4 /* synced-tabs-loaded*/:
|
||||
renderArray = renderArray.concat(this.generateTabList());
|
||||
break;
|
||||
renderArray.push(this.generateCardContent());
|
||||
}
|
||||
return renderArray;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -74,17 +74,17 @@ You'll need to pass along some of the following properties:
|
|||
* `hasPopup` (**Optional**): The optional aria-haspopup attribute for the secondary action, if required
|
||||
* `maxTabsLength` (**Optional**): The max number of tabs you want to display in the tabs list. The default value will be `25` if no max value is given. You may use any negative number such as `-1` to indicate no max.
|
||||
* `tabItems` (**Required**): An array of tab data such as History nodes, Bookmark nodes, Synced Tabs, etc.
|
||||
* The component is expecting to receive the following properties within each `tabItems` object (you may need to do some normalizing for this):
|
||||
* The component is expecting to receive the following properties within each `tabItems` object (you may need to do some normalizing for this). If you just pass a title and an icon, it creates a header row that is not clickable.
|
||||
* `icon` (**Required**) - The location string for the favicon. Will fallback to default favicon if none is found.
|
||||
* `primaryL10nId` (**Required**) - The l10n id to be used for the primary action element. This fluent string should ONLY define a `.title` attribute to describe the link element in each row.
|
||||
* `primaryL10nId` (**Optional**) - The l10n id to be used for the primary action element. This fluent string should ONLY define a `.title` attribute to describe the link element in each row.
|
||||
* `primaryL10nArgs` (**Optional**) - The l10n args you can optionally pass for the primary action element
|
||||
* `secondaryL10nId` (**Optional**) - The l10n id to be used for the secondary action button. This fluent string should ONLY define a `.title` attribute to describe the secondary button in each row.
|
||||
* `secondaryL10nArgs` (**Optional**) - The l10n args you can optionally pass for the secondary action button
|
||||
* `tabElement` (**Optional**) - The MozTabbrowserTab element for the tab item.
|
||||
* `tabid` (**Optional**) - Optional property expected for Recently Closed tab data
|
||||
* `time` (**Required**) - The time in milliseconds for expected last interaction with the tab (Ex: `lastUsed` for SyncedTabs tabs, `closedAt` for RecentlyClosed tabs, etc.)
|
||||
* `time` (**Optional**) - The time in milliseconds for expected last interaction with the tab (Ex: `lastUsed` for SyncedTabs tabs, `closedAt` for RecentlyClosed tabs, etc.)
|
||||
* `title` (**Required**) - The title for the tab
|
||||
* `url` (**Required**) - The full URL for the tab
|
||||
* `url` (**Optional**) - The full URL for the tab
|
||||
|
||||
|
||||
### Notes
|
||||
|
|
|
|||
|
|
@ -230,4 +230,6 @@ firefoxview-recentlyclosed-empty-header = Closed a tab too soon?
|
|||
firefoxview-recentlyclosed-empty-description = Here you’ll find the tabs you recently closed, so you can reopen any of them quickly.
|
||||
firefoxview-recentlyclosed-empty-description-two = To find tabs from longer ago, view your <a data-l10n-name="history-url">browsing history</a>.
|
||||
|
||||
##
|
||||
## This message is displayed below the name of another connected device when it doesn't have any open tabs.
|
||||
|
||||
firefoxview-syncedtabs-device-notabs = No tabs open on this device
|
||||
|
|
|
|||
Loading…
Reference in a new issue