blob: ff508f77c90a64a2cb4e2df4c0e2f7918d3b16ec [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.
import 'chrome://resources/cr_elements/cr_icon_button/cr_icon_button.m.js';
import 'chrome://resources/cr_elements/cr_icons_css.m.js';
import 'chrome://resources/cr_elements/shared_vars_css.m.js';
import './shared_style.js';
import './strings.m.js';
import {assert} from 'chrome://resources/js/assert.m.js';
import {isMac} from 'chrome://resources/js/cr.m.js';
import {focusWithoutInk} from 'chrome://resources/js/cr/ui/focus_without_ink.m.js';
import {getFaviconForPageURL} from 'chrome://resources/js/icon.m.js';
import {loadTimeData} from 'chrome://resources/js/load_time_data.m.js';
import {html, Polymer} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
import {selectItem} from './actions.js';
import {CommandManager} from './command_manager.js';
import {Command, MenuSource} from './constants.js';
import {StoreClient} from './store_client.js';
import {BookmarkNode} from './types.js';
Polymer({
is: 'bookmarks-item',
_template: html`{__html_template__}`,
behaviors: [
StoreClient,
],
properties: {
itemId: {
type: String,
observer: 'onItemIdChanged_',
},
ironListTabIndex: Number,
/** @private {BookmarkNode} */
item_: {
type: Object,
observer: 'onItemChanged_',
},
/** @private */
isSelectedItem_: {
type: Boolean,
reflectToAttribute: true,
},
/** @private */
isMultiSelect_: Boolean,
/** @private */
isFolder_: Boolean,
/** @private */
lastTouchPoints_: Number,
},
observers: [
'updateFavicon_(item_.url)',
],
listeners: {
'click': 'onClick_',
'dblclick': 'onDblClick_',
'contextmenu': 'onContextMenu_',
'keydown': 'onKeydown_',
'auxclick': 'onMiddleClick_',
'mousedown': 'cancelMiddleMouseBehavior_',
'mouseup': 'cancelMiddleMouseBehavior_',
'touchstart': 'onTouchStart_',
},
/** @override */
attached() {
this.watch('item_', store => store.nodes[this.itemId]);
this.watch(
'isSelectedItem_', store => store.selection.items.has(this.itemId));
this.watch('isMultiSelect_', store => store.selection.items.size > 1);
this.updateFromStore();
},
focusMenuButton() {
focusWithoutInk(this.$.menuButton);
},
/** @return {BookmarksItemElement} */
getDropTarget() {
return this;
},
/**
* @param {Event} e
* @private
*/
onContextMenu_(e) {
e.preventDefault();
e.stopPropagation();
// Prevent context menu from appearing after a drag, but allow opening the
// context menu through 2 taps
if (e.sourceCapabilities && e.sourceCapabilities.firesTouchEvents &&
this.lastTouchPoints_ !== 2) {
return;
}
this.focus();
if (!this.isSelectedItem_) {
this.selectThisItem_();
}
this.fire('open-command-menu', {
x: e.clientX,
y: e.clientY,
source: MenuSource.ITEM,
});
},
/**
* @param {Event} e
* @private
*/
onMenuButtonClick_(e) {
e.stopPropagation();
e.preventDefault();
// Skip selecting the item if this item is part of a multi-selected group.
if (!this.isMultiSelectMenu_()) {
this.selectThisItem_();
}
this.fire('open-command-menu', {
targetElement: e.target,
source: MenuSource.ITEM,
});
},
/** @private */
selectThisItem_() {
this.dispatch(selectItem(this.itemId, this.getState(), {
clear: true,
range: false,
toggle: false,
}));
},
/** @private */
onItemIdChanged_() {
// TODO(tsergeant): Add a histogram to measure whether this assertion fails
// for real users.
assert(this.getState().nodes[this.itemId]);
this.updateFromStore();
},
/** @private */
onItemChanged_() {
this.isFolder_ = !this.item_.url;
this.setAttribute(
'aria-label',
this.item_.title || this.item_.url ||
loadTimeData.getString('folderLabel'));
},
/**
* @param {MouseEvent} e
* @private
*/
onClick_(e) {
// Ignore double clicks so that Ctrl double-clicking an item won't deselect
// the item before opening.
if (e.detail !== 2) {
const addKey = isMac ? e.metaKey : e.ctrlKey;
this.dispatch(selectItem(this.itemId, this.getState(), {
clear: !addKey,
range: e.shiftKey,
toggle: addKey && !e.shiftKey,
}));
}
e.stopPropagation();
e.preventDefault();
},
/**
* @private
* @param {KeyboardEvent} e
*/
onKeydown_(e) {
if (e.key === 'ArrowLeft') {
this.focus();
} else if (e.key === 'ArrowRight') {
this.$.menuButton.focus();
}
},
/**
* @param {MouseEvent} e
* @private
*/
onDblClick_(e) {
if (!this.isSelectedItem_) {
this.selectThisItem_();
}
const commandManager = CommandManager.getInstance();
const itemSet = this.getState().selection.items;
if (commandManager.canExecute(Command.OPEN, itemSet)) {
commandManager.handle(Command.OPEN, itemSet);
}
},
/**
* @param {MouseEvent} e
* @private
*/
onMiddleClick_(e) {
if (e.button !== 1) {
return;
}
this.selectThisItem_();
if (this.isFolder_) {
return;
}
const commandManager = CommandManager.getInstance();
const itemSet = this.getState().selection.items;
const command = e.shiftKey ? Command.OPEN : Command.OPEN_NEW_TAB;
if (commandManager.canExecute(command, itemSet)) {
commandManager.handle(command, itemSet);
}
},
/**
* @param {TouchEvent} e
* @private
*/
onTouchStart_(e) {
this.lastTouchPoints_ = e.touches.length;
},
/**
* Prevent default middle-mouse behavior. On Windows, this prevents autoscroll
* (during mousedown), and on Linux this prevents paste (during mouseup).
* @param {MouseEvent} e
* @private
*/
cancelMiddleMouseBehavior_(e) {
if (e.button === 1) {
e.preventDefault();
}
},
/**
* @param {string} url
* @private
*/
updateFavicon_(url) {
this.$.icon.className = url ? 'website-icon' : 'folder-icon';
this.$.icon.style.backgroundImage =
url ? getFaviconForPageURL(url, false) : '';
},
/**
* @return {string}
* @private
*/
getButtonAriaLabel_() {
if (!this.item_) {
return ''; // Item hasn't loaded, skip for now.
}
if (this.isMultiSelectMenu_()) {
return loadTimeData.getStringF('moreActionsMultiButtonAxLabel');
}
return loadTimeData.getStringF(
'moreActionsButtonAxLabel', this.item_.title);
},
/**
* This item is part of a group selection.
* @return {boolean}
* @private
*/
isMultiSelectMenu_() {
return this.isSelectedItem_ && this.isMultiSelect_;
},
});