blob: 3e901381d3ac9d840d08874655072cb187bbc328 [file] [log] [blame]
// Copyright 2022 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import {CrActionMenuElement} from '//resources/cr_elements/cr_action_menu/cr_action_menu.js';
import {PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
import {AppInfo, ClickEvent, RunOnOsLoginMode} from './app_home.mojom-webui.js';
import {AppHomeUserAction, recordUserAction} from './app_home_utils.js';
import {getTemplate} from './app_item.html.js';
import {BrowserProxy} from './browser_proxy.js';
import {UserDisplayMode} from './user_display_mode.mojom-webui.js';
export interface AppItemElement {
$: {
menu: CrActionMenuElement,
iconContainer: HTMLElement,
};
}
export class AppItemElement extends PolymerElement {
static get is() {
return 'app-item';
}
static get template() {
return getTemplate();
}
static get properties() {
return {
appInfo: Object,
};
}
appInfo: AppInfo;
override ready() {
super.ready();
this.addEventListener('contextmenu', this.handleContextMenu_);
this.addEventListener('click', this.handleClick_);
this.addEventListener('auxclick', this.handleClick_);
}
closeContextMenu() {
if (!this.$.menu.open) {
return;
}
this.$.menu.close();
this.fire_('on-menu-closed', {appItem: this});
}
private handleContextMenu_(e: MouseEvent) {
this.fire_('on-menu-open-triggered', {
appItem: this,
});
this.$.menu.showAtPosition({top: e.clientY, left: e.clientX});
recordUserAction(AppHomeUserAction.CONTEXT_MENU_TRIGGERED);
e.preventDefault();
e.stopPropagation();
}
private handleClick_(e: MouseEvent) {
// We want to capture left-click `0` and aux-click `1` (aka
// middle-mouse-button click). Other clicks (right-click, etc) should not
// trigger a launch.
if (e.button > 1) {
return;
}
const clickEvent: ClickEvent = {
button: e.button,
altKey: e.altKey,
ctrlKey: e.ctrlKey,
metaKey: e.metaKey,
shiftKey: e.shiftKey,
};
BrowserProxy.getInstance().handler.launchApp(this.appInfo.id, clickEvent);
e.preventDefault();
e.stopPropagation();
}
private fire_(eventName: string, detail?: any) {
this.dispatchEvent(
new CustomEvent(eventName, {bubbles: true, composed: true, detail}));
}
// The CrActionMenuElement is a modal that does not listen to any other
// events other than mousedown on right click when it is open. This allows
// us to listen to changes on the dom even when the menu is showing.
private onMenuMousedown_(e: Event) {
// Do not listen to the mousedown event if not triggered from a
// CrActionMenuElement, i.e. one without a dialog element covering the dom.
if ((e.composedPath()[0] as HTMLElement).tagName !== 'DIALOG') {
return;
}
this.closeContextMenu();
}
private isLocallyInstalled_() {
return this.appInfo.isLocallyInstalled;
}
private isLaunchOnStartupHidden_() {
return !this.appInfo.mayShowRunOnOsLoginMode;
}
private isLaunchOnStartupDisabled_() {
return !this.appInfo.mayToggleRunOnOsLoginMode;
}
private isLaunchOnStartUp_() {
return (this.appInfo.runOnOsLoginMode !== RunOnOsLoginMode.kNotRun);
}
private onOpenInWindowItemClick_() {
if (this.appInfo.openInWindow) {
BrowserProxy.getInstance().handler.setUserDisplayMode(
this.appInfo.id, UserDisplayMode.kBrowser);
recordUserAction(AppHomeUserAction.OPEN_IN_WINDOW_UNCHECKED);
} else {
BrowserProxy.getInstance().handler.setUserDisplayMode(
this.appInfo.id, UserDisplayMode.kStandalone);
recordUserAction(AppHomeUserAction.OPEN_IN_WINDOW_CHECKED);
}
this.closeContextMenu();
}
// Changing the app's launch mode.
private onLaunchOnStartupItemClick_() {
if (this.isLaunchOnStartupDisabled_()) {
return;
}
if (this.isLaunchOnStartUp_()) {
BrowserProxy.getInstance().handler.setRunOnOsLoginMode(
this.appInfo.id, RunOnOsLoginMode.kNotRun);
recordUserAction(AppHomeUserAction.LAUNCH_AT_STARTUP_UNCHECKED);
} else {
BrowserProxy.getInstance().handler.setRunOnOsLoginMode(
this.appInfo.id, RunOnOsLoginMode.kWindowed);
recordUserAction(AppHomeUserAction.LAUNCH_AT_STARTUP_CHECKED);
}
this.closeContextMenu();
}
private onCreateShortcutItemClick_() {
if (this.appInfo.id) {
BrowserProxy.getInstance().handler.createAppShortcut(this.appInfo.id);
recordUserAction(AppHomeUserAction.CREATE_SHORTCUT);
}
this.closeContextMenu();
}
private onInstallLocallyItemClick_() {
if (this.appInfo.id) {
BrowserProxy.getInstance().handler.installAppLocally(this.appInfo.id);
recordUserAction(AppHomeUserAction.INSTALL_APP_LOCALLY);
}
this.closeContextMenu();
}
private onUninstallItemClick_() {
if (this.appInfo.id) {
BrowserProxy.getInstance().handler.uninstallApp(this.appInfo.id);
recordUserAction(AppHomeUserAction.UNINSTALL);
}
this.closeContextMenu();
}
private onAppSettingsItemClick_() {
if (this.appInfo.id) {
BrowserProxy.getInstance().handler.showAppSettings(this.appInfo.id);
recordUserAction(AppHomeUserAction.OPEN_APP_SETTINGS);
}
this.closeContextMenu();
}
private getIconUrl_() {
const url = new URL(this.appInfo.iconUrl.url);
// For web app, the backend serves grayscale image when the app is not
// locally installed automatically and doesn't recognize this query param,
// but we add a query param here to force browser to refetch the image.
if (!this.isLocallyInstalled_()) {
url.searchParams.append('grayscale', 'true');
}
return url;
}
}
declare global {
interface HTMLElementTagNameMap {
'app-item': AppItemElement;
}
}
customElements.define(AppItemElement.is, AppItemElement);