blob: a8fc70326f5f03a912d2661ed07714f157005c36 [file] [log] [blame]
// Copyright 2016 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
/**
* @fileoverview 'settings-cups-printers' is a component for showing CUPS
* Printer settings subpage (chrome://settings/cupsPrinters). It is used to
* set up legacy & non-CloudPrint printers on ChromeOS by leveraging CUPS (the
* unix printing system) and the many open source drivers built for CUPS.
*/
// TODO(xdai): Rename it to 'settings-cups-printers-page'.
import 'chrome://resources/cr_elements/cr_button/cr_button.m.js';
import 'chrome://resources/cr_elements/cr_icon_button/cr_icon_button.js';
import 'chrome://resources/cr_elements/cr_toast/cr_toast.js';
import 'chrome://resources/cr_elements/policy/cr_policy_pref_indicator.m.js';
import 'chrome://resources/js/action_link.js';
import 'chrome://resources/cr_elements/action_link_css.m.js';
import 'chrome://resources/polymer/v3_0/iron-flex-layout/iron-flex-layout-classes.js';
import 'chrome://resources/polymer/v3_0/iron-icon/iron-icon.js';
import './cups_settings_add_printer_dialog.js';
import './cups_edit_printer_dialog.js';
import './cups_enterprise_printers.js';
import './cups_printer_shared_css.js';
import './cups_saved_printers.js';
import './cups_nearby_printers.js';
import 'chrome://resources/cr_components/localized_link/localized_link.js';
import '../../icons.html.js';
import {MojoInterfaceProvider, MojoInterfaceProviderImpl} from 'chrome://resources/cr_components/chromeos/network/mojo_interface_provider.m.js';
import {NetworkListenerBehavior, NetworkListenerBehaviorInterface} from 'chrome://resources/cr_components/chromeos/network/network_listener_behavior.m.js';
import {assert, assertNotReached} from 'chrome://resources/js/assert.m.js';
import {addWebUIListener, removeWebUIListener, sendWithPromise, WebUIListener} from 'chrome://resources/js/cr.m.js';
import {focusWithoutInk} from 'chrome://resources/js/cr/ui/focus_without_ink.m.js';
import {loadTimeData} from 'chrome://resources/js/load_time_data.m.js';
import {WebUIListenerBehavior, WebUIListenerBehaviorInterface} from 'chrome://resources/js/web_ui_listener_behavior.m.js';
import {afterNextRender, html, mixinBehaviors, PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
import {Setting} from '../../mojom-webui/setting.mojom-webui.js';
import {Route} from '../../router.js';
import {DeepLinkingBehavior, DeepLinkingBehaviorInterface} from '../deep_linking_behavior.js';
import {routes} from '../os_route.js';
import {RouteObserverBehavior, RouteObserverBehaviorInterface} from '../route_observer_behavior.js';
import {PrinterListEntry, PrinterType} from './cups_printer_types.js';
import {CupsPrinterInfo, CupsPrintersBrowserProxy, CupsPrintersBrowserProxyImpl, CupsPrintersList, PrinterSetupResult} from './cups_printers_browser_proxy.js';
import {CupsPrintersEntryManager} from './cups_printers_entry_manager.js';
/**
* @constructor
* @extends {PolymerElement}
* @implements {DeepLinkingBehaviorInterface}
* @implements {NetworkListenerBehaviorInterface}
* @implements {RouteObserverBehaviorInterface}
* @implements {WebUIListenerBehaviorInterface}
*/
const SettingsCupsPrintersElementBase = mixinBehaviors(
[
DeepLinkingBehavior,
NetworkListenerBehavior,
RouteObserverBehavior,
WebUIListenerBehavior,
],
PolymerElement);
/** @polymer */
class SettingsCupsPrintersElement extends SettingsCupsPrintersElementBase {
static get is() {
return 'settings-cups-printers';
}
static get template() {
return html`{__html_template__}`;
}
static get properties() {
return {
/** @type {!Array<!CupsPrinterInfo>} */
printers: {
type: Array,
notify: true,
},
prefs: Object,
/** @type {?CupsPrinterInfo} */
activePrinter: {
type: Object,
notify: true,
},
/** @private {?WebUIListener} */
onPrintersChangedListener_: {
type: Object,
value: null,
},
/** @private {?WebUIListener} */
onEnterprisePrintersChangedListener_: {
type: Object,
value: null,
},
searchTerm: {
type: String,
},
/** This is also used as an attribute for css styling. */
canAddPrinter: {
type: Boolean,
reflectToAttribute: true,
},
/**
* @type {!Array<!PrinterListEntry>}
* @private
*/
savedPrinters_: {
type: Array,
value: () => [],
},
/**
* @type {!Array<!PrinterListEntry>}
* @private
*/
enterprisePrinters_: {
type: Array,
value: () => [],
},
/** @private */
attemptedLoadingPrinters_: {
type: Boolean,
value: false,
},
/** @private */
showCupsEditPrinterDialog_: Boolean,
/**@private */
addPrinterResultText_: String,
/**@private */
nearbyPrintersAriaLabel_: {
type: String,
computed: 'getNearbyPrintersAriaLabel_(nearbyPrinterCount_)',
},
/**@private */
savedPrintersAriaLabel_: {
type: String,
computed: 'getSavedPrintersAriaLabel_(savedPrinterCount_)',
},
/**@private */
enterprisePrintersAriaLabel_: {
type: String,
computed: 'getEnterprisePrintersAriaLabel_(enterprisePrinterCount_)',
},
/**@private */
nearbyPrinterCount_: {
type: Number,
value: 0,
},
/**@private */
savedPrinterCount_: {
type: Number,
value: 0,
},
/** @private */
enterprisePrinterCount_: {
type: Number,
value: 0,
},
/**
* Used by DeepLinkingBehavior to focus this page's deep links.
* @type {!Set<!Setting>}
*/
supportedSettingIds: {
type: Object,
value: () => new Set([
Setting.kAddPrinter,
Setting.kSavedPrinters,
]),
},
};
}
/** @override */
constructor() {
super();
/** @private {!chromeos.networkConfig.mojom.CrosNetworkConfigRemote} */
this.networkConfig_ =
MojoInterfaceProviderImpl.getInstance().getMojoServiceRemote();
/** @private {!CupsPrintersEntryManager} */
this.entryManager_ = CupsPrintersEntryManager.getInstance();
/** @private */
this.addPrintServerResultText_ = '';
}
/** @override */
connectedCallback() {
super.connectedCallback();
this.networkConfig_
.getNetworkStateList({
filter: chromeos.networkConfig.mojom.FilterType.kActive,
networkType: chromeos.networkConfig.mojom.NetworkType.kAll,
limit: chromeos.networkConfig.mojom.NO_LIMIT,
})
.then((responseParams) => {
this.onActiveNetworksChanged(responseParams.result);
});
}
/** @override */
ready() {
super.ready();
this.updateCupsPrintersList_();
this.addEventListener(
'edit-cups-printer-details', this.onShowCupsEditPrinterDialog_);
this.addEventListener('show-cups-printer-toast', (event) => {
this.openResultToast_(
/**
* @type {!CustomEvent<!{
* resultCode: PrinterSetupResult,
* printerName: string
* }>}
*/
(event));
});
this.addEventListener('add-print-server-and-show-toast', (event) => {
this.addPrintServerAndShowResultToast_(
/** @type {!CustomEvent<!{printers: !CupsPrintersList}>} */ (event));
});
this.addEventListener(
'open-manufacturer-model-dialog-for-specified-printer', (event) => {
this.openManufacturerModelDialogForSpecifiedPrinter_(
/** @type {!CustomEvent<{item: !CupsPrinterInfo}>} */ (event));
});
}
/**
* Overridden from DeepLinkingBehavior.
* @param {!Setting} settingId
* @return {boolean}
*/
beforeDeepLinkAttempt(settingId) {
// Manually show the deep links for settings nested within elements.
if (settingId !== Setting.kSavedPrinters) {
// Continue with deep link attempt.
return true;
}
afterNextRender(this, () => {
const savedPrinters = this.shadowRoot.querySelector('#savedPrinters');
const printerEntry =
savedPrinters && savedPrinters.$$('settings-cups-printers-entry');
const deepLinkElement = printerEntry && printerEntry.$$('#moreActions');
if (!deepLinkElement || deepLinkElement.hidden) {
console.warn(`Element with deep link id ${settingId} not focusable.`);
return;
}
this.showDeepLinkElement(deepLinkElement);
});
// Stop deep link attempt since we completed it manually.
return false;
}
/**
* RouteObserverBehavior
* @param {!Route} route
* @protected
*/
currentRouteChanged(route) {
if (route !== routes.CUPS_PRINTERS) {
if (this.onPrintersChangedListener_) {
removeWebUIListener(
/** @type {WebUIListener} */ (this.onPrintersChangedListener_));
this.onPrintersChangedListener_ = null;
}
this.entryManager_.removeWebUIListeners();
return;
}
this.entryManager_.addWebUIListeners();
this.onPrintersChangedListener_ = addWebUIListener(
'on-saved-printers-changed', this.onSavedPrintersChanged_.bind(this));
this.onEnterprisePrintersChangedListener_ = addWebUIListener(
'on-enterprise-printers-changed',
this.onEnterprisePrintersChanged_.bind(this));
this.updateCupsPrintersList_();
this.attemptDeepLink();
}
/**
* CrosNetworkConfigObserver impl
* @param {!Array<chromeos.networkConfig.mojom.NetworkStateProperties>}
* networks
* @private
*/
onActiveNetworksChanged(networks) {
this.canAddPrinter = networks.some((network) => {
// Note: Check for kOnline rather than using
// OncMojo.connectionStateIsConnected() since the latter could return true
// for networks without connectivity (e.g., captive portals).
return network.connectionState ===
chromeos.networkConfig.mojom.ConnectionStateType.kOnline;
});
}
/**
* @param {!CustomEvent<!{
* resultCode: PrinterSetupResult,
* printerName: string
* }>} event
* @private
*/
openResultToast_(event) {
const printerName = event.detail.printerName;
switch (event.detail.resultCode) {
case PrinterSetupResult.SUCCESS:
this.addPrinterResultText_ = loadTimeData.getStringF(
'printerAddedSuccessfulMessage', printerName);
break;
case PrinterSetupResult.EDIT_SUCCESS:
this.addPrinterResultText_ = loadTimeData.getStringF(
'printerEditedSuccessfulMessage', printerName);
break;
case PrinterSetupResult.PRINTER_UNREACHABLE:
this.addPrinterResultText_ =
loadTimeData.getStringF('printerUnavailableMessage', printerName);
break;
default:
assertNotReached();
}
this.$.errorToast.show();
}
/**
* @param {!CustomEvent<!{
* printers: !CupsPrintersList
* }>} event
* @private
*/
addPrintServerAndShowResultToast_(event) {
this.entryManager_.addPrintServerPrinters(event.detail.printers);
const length = event.detail.printers.printerList.length;
if (length === 0) {
this.addPrintServerResultText_ =
loadTimeData.getString('printServerFoundZeroPrinters');
} else if (length === 1) {
this.addPrintServerResultText_ =
loadTimeData.getString('printServerFoundOnePrinter');
} else {
this.addPrintServerResultText_ =
loadTimeData.getStringF('printServerFoundManyPrinters', length);
}
this.$.printServerErrorToast.show();
}
/**
* @param {!CustomEvent<{item: !CupsPrinterInfo}>} e
* @private
*/
openManufacturerModelDialogForSpecifiedPrinter_(e) {
const item = e.detail.item;
this.$.addPrinterDialog.openManufacturerModelDialogForSpecifiedPrinter(
item);
}
/** @private */
updateCupsPrintersList_() {
CupsPrintersBrowserProxyImpl.getInstance().getCupsSavedPrintersList().then(
this.onSavedPrintersChanged_.bind(this));
CupsPrintersBrowserProxyImpl.getInstance()
.getCupsEnterprisePrintersList()
.then(this.onEnterprisePrintersChanged_.bind(this));
}
/**
* @param {!CupsPrintersList} cupsPrintersList
* @private
*/
onSavedPrintersChanged_(cupsPrintersList) {
this.savedPrinters_ = cupsPrintersList.printerList.map(
printer => /** @type {!PrinterListEntry} */ (
{printerInfo: printer, printerType: PrinterType.SAVED}));
this.entryManager_.setSavedPrintersList(this.savedPrinters_);
// Used to delay rendering nearby and add printer sections to prevent
// "Add Printer" flicker when clicking "Printers" in settings page.
this.attemptedLoadingPrinters_ = true;
}
/**
* @param {!CupsPrintersList} cupsPrintersList
* @private
*/
onEnterprisePrintersChanged_(cupsPrintersList) {
this.enterprisePrinters_ = cupsPrintersList.printerList.map(
printer => /** @type {!PrinterListEntry} */ (
{printerInfo: printer, printerType: PrinterType.ENTERPRISE}));
this.entryManager_.setEnterprisePrintersList(this.enterprisePrinters_);
}
/** @private */
onAddPrinterTap_() {
this.$.addPrinterDialog.open();
}
/** @private */
onAddPrinterDialogClose_() {
focusWithoutInk(
assert(this.shadowRoot.querySelector('#addManualPrinterIcon')));
}
/** @private */
onShowCupsEditPrinterDialog_() {
this.showCupsEditPrinterDialog_ = true;
}
/** @private */
onEditPrinterDialogClose_() {
this.showCupsEditPrinterDialog_ = false;
}
/**
* @param {string} searchTerm
* @return {boolean} If the 'no-search-results-found' string should be shown.
* @private
*/
showNoSearchResultsMessage_(searchTerm) {
if (!searchTerm || !this.printers.length) {
return false;
}
searchTerm = searchTerm.toLowerCase();
return !this.printers.some(printer => {
return printer.printerName.toLowerCase().includes(searchTerm);
});
}
/**
* @param {boolean} connectedToNetwork Whether the device is connected to
a network.
* @param {boolean} userPrintersAllowed Whether users are allowed to
configure their own native printers.
* @return {boolean} Whether the 'Add Printer' button is active.
* @private
*/
addPrinterButtonActive_(connectedToNetwork, userPrintersAllowed) {
return connectedToNetwork && userPrintersAllowed;
}
/**
* @return {boolean} Whether |savedPrinters_| is empty.
* @private
*/
doesAccountHaveSavedPrinters_() {
return !!this.savedPrinters_.length;
}
/**
* @return {boolean} Whether |enterprisePrinters_| is empty.
* @private
*/
doesAccountHaveEnterprisePrinters_() {
return !!this.enterprisePrinters_.length;
}
/** @private */
getSavedPrintersAriaLabel_() {
let printerLabel = '';
if (this.savedPrinterCount_ === 0) {
printerLabel = 'savedPrintersCountNone';
} else if (this.savedPrinterCount_ === 1) {
printerLabel = 'savedPrintersCountOne';
} else {
printerLabel = 'savedPrintersCountMany';
}
return loadTimeData.getStringF(printerLabel, this.savedPrinterCount_);
}
/** @private */
getNearbyPrintersAriaLabel_() {
let printerLabel = '';
if (this.nearbyPrinterCount_ === 0) {
printerLabel = 'nearbyPrintersCountNone';
} else if (this.nearbyPrinterCount_ === 1) {
printerLabel = 'nearbyPrintersCountOne';
} else {
printerLabel = 'nearbyPrintersCountMany';
}
return loadTimeData.getStringF(printerLabel, this.nearbyPrinterCount_);
}
/** @private */
getEnterprisePrintersAriaLabel_() {
let printerLabel = '';
if (this.enterprisePrinterCount_ === 0) {
printerLabel = 'enterprisePrintersCountNone';
} else if (this.enterprisePrinterCount_ === 1) {
printerLabel = 'enterprisePrintersCountOne';
} else {
printerLabel = 'enterprisePrintersCountMany';
}
return loadTimeData.getStringF(printerLabel, this.enterprisePrinterCount_);
}
}
customElements.define(
SettingsCupsPrintersElement.is, SettingsCupsPrintersElement);