ContextMenu.js 7.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263
  1. export class Item{constructor(contextMenu,type,label,disabled,checked){this._type=type;this._label=label;this._disabled=disabled;this._checked=checked;this._contextMenu=contextMenu;if(type==='item'||type==='checkbox'){this._id=contextMenu?contextMenu._nextId():0;}}
  2. id(){return this._id;}
  3. type(){return this._type;}
  4. isEnabled(){return!this._disabled;}
  5. setEnabled(enabled){this._disabled=!enabled;}
  6. _buildDescriptor(){switch(this._type){case'item':const result={type:'item',id:this._id,label:this._label,enabled:!this._disabled};if(this._customElement){result.element=this._customElement;}
  7. if(this._shortcut){result.shortcut=this._shortcut;}
  8. return result;case'separator':return{type:'separator'};case'checkbox':return{type:'checkbox',id:this._id,label:this._label,checked:!!this._checked,enabled:!this._disabled};}
  9. throw new Error('Invalid item type:'+this._type);}
  10. setShortcut(shortcut){this._shortcut=shortcut;}}
  11. export class Section{constructor(contextMenu){this._contextMenu=contextMenu;this._items=[];}
  12. appendItem(label,handler,disabled){const item=new Item(this._contextMenu,'item',label,disabled);this._items.push(item);this._contextMenu._setHandler(item.id(),handler);return item;}
  13. appendCustomItem(element){const item=new Item(this._contextMenu,'item','<custom>');item._customElement=element;this._items.push(item);return item;}
  14. appendSeparator(){const item=new UI.ContextMenuItem(this._contextMenu,'separator');this._items.push(item);return item;}
  15. appendAction(actionId,label,optional){const action=UI.actionRegistry.action(actionId);if(!action){if(!optional){console.error(`Action ${actionId} was not defined`);}
  16. return;}
  17. if(!label){label=action.title();}
  18. const result=this.appendItem(label,action.execute.bind(action));const shortcut=UI.shortcutRegistry.shortcutTitleForAction(actionId);if(shortcut){result.setShortcut(shortcut);}}
  19. appendSubMenuItem(label,disabled){const item=new SubMenu(this._contextMenu,label,disabled);item._init();this._items.push(item);return item;}
  20. appendCheckboxItem(label,handler,checked,disabled){const item=new Item(this._contextMenu,'checkbox',label,disabled,checked);this._items.push(item);this._contextMenu._setHandler(item.id(),handler);return item;}}
  21. class SubMenu extends Item{constructor(contextMenu,label,disabled){super(contextMenu,'subMenu',label,disabled);this._sections=new Map();this._sectionList=[];}
  22. _init(){_groupWeights.forEach(name=>this.section(name));}
  23. section(name){let section=name?this._sections.get(name):null;if(!section){section=new Section(this._contextMenu);if(name){this._sections.set(name,section);this._sectionList.push(section);}else{this._sectionList.splice(ContextMenu._groupWeights.indexOf('default'),0,section);}}
  24. return section;}
  25. headerSection(){return this.section('header');}
  26. newSection(){return this.section('new');}
  27. revealSection(){return this.section('reveal');}
  28. clipboardSection(){return this.section('clipboard');}
  29. editSection(){return this.section('edit');}
  30. debugSection(){return this.section('debug');}
  31. viewSection(){return this.section('view');}
  32. defaultSection(){return this.section('default');}
  33. saveSection(){return this.section('save');}
  34. footerSection(){return this.section('footer');}
  35. _buildDescriptor(){const result={type:'subMenu',label:this._label,enabled:!this._disabled,subItems:[]};const nonEmptySections=this._sectionList.filter(section=>!!section._items.length);for(const section of nonEmptySections){for(const item of section._items){result.subItems.push(item._buildDescriptor());}
  36. if(section!==nonEmptySections.peekLast()){result.subItems.push({type:'separator'});}}
  37. return result;}
  38. appendItemsAtLocation(location){for(const extension of self.runtime.extensions('context-menu-item')){const itemLocation=extension.descriptor()['location']||'';if(!itemLocation.startsWith(location+'/')){continue;}
  39. const section=itemLocation.substr(location.length+1);if(!section||section.includes('/')){continue;}
  40. this.section(section).appendAction(extension.descriptor()['actionId']);}}}
  41. Item._uniqueSectionName=0;export default class ContextMenu extends SubMenu{constructor(event,useSoftMenu,x,y){super(null);this._contextMenu=this;super._init();this._defaultSection=this.defaultSection();this._pendingPromises=[];this._pendingTargets=[];this._event=event;this._useSoftMenu=!!useSoftMenu;this._x=x===undefined?event.x:x;this._y=y===undefined?event.y:y;this._handlers={};this._id=0;const target=event.deepElementFromPoint();if(target){this.appendApplicableItems((target));}}
  42. static initialize(){Host.InspectorFrontendHost.events.addEventListener(Host.InspectorFrontendHostAPI.Events.SetUseSoftMenu,setUseSoftMenu);function setUseSoftMenu(event){ContextMenu._useSoftMenu=(event.data);}}
  43. static installHandler(doc){doc.body.addEventListener('contextmenu',handler,false);function handler(event){const contextMenu=new ContextMenu(event);contextMenu.show();}}
  44. _nextId(){return this._id++;}
  45. show(){Promise.all(this._pendingPromises).then(populate.bind(this)).then(this._innerShow.bind(this));ContextMenu._pendingMenu=this;function populate(appendCallResults){if(ContextMenu._pendingMenu!==this){return;}
  46. delete ContextMenu._pendingMenu;for(let i=0;i<appendCallResults.length;++i){const providers=appendCallResults[i];const target=this._pendingTargets[i];for(let j=0;j<providers.length;++j){const provider=(providers[j]);provider.appendApplicableItems(this._event,this,target);}}
  47. this._pendingPromises=[];this._pendingTargets=[];}
  48. this._event.consume(true);}
  49. discard(){if(this._softMenu){this._softMenu.discard();}}
  50. _innerShow(){const menuObject=this._buildMenuDescriptors();if(this._useSoftMenu||ContextMenu._useSoftMenu||Host.InspectorFrontendHost.isHostedMode()){this._softMenu=new UI.SoftContextMenu(menuObject,this._itemSelected.bind(this));this._softMenu.show(this._event.target.ownerDocument,new AnchorBox(this._x,this._y,0,0));}else{Host.InspectorFrontendHost.showContextMenuAtPoint(this._x,this._y,menuObject,this._event.target.ownerDocument);function listenToEvents(){Host.InspectorFrontendHost.events.addEventListener(Host.InspectorFrontendHostAPI.Events.ContextMenuCleared,this._menuCleared,this);Host.InspectorFrontendHost.events.addEventListener(Host.InspectorFrontendHostAPI.Events.ContextMenuItemSelected,this._onItemSelected,this);}
  51. setImmediate(listenToEvents.bind(this));}}
  52. setX(x){this._x=x;}
  53. setY(y){this._y=y;}
  54. _setHandler(id,handler){if(handler){this._handlers[id]=handler;}}
  55. _buildMenuDescriptors(){return(super._buildDescriptor().subItems);}
  56. _onItemSelected(event){this._itemSelected((event.data));}
  57. _itemSelected(id){if(this._handlers[id]){this._handlers[id].call(this);}
  58. this._menuCleared();}
  59. _menuCleared(){Host.InspectorFrontendHost.events.removeEventListener(Host.InspectorFrontendHostAPI.Events.ContextMenuCleared,this._menuCleared,this);Host.InspectorFrontendHost.events.removeEventListener(Host.InspectorFrontendHostAPI.Events.ContextMenuItemSelected,this._onItemSelected,this);}
  60. containsTarget(target){return this._pendingTargets.indexOf(target)>=0;}
  61. appendApplicableItems(target){this._pendingPromises.push(self.runtime.allInstances(Provider,target));this._pendingTargets.push(target);}}
  62. export const _groupWeights=['header','new','reveal','edit','clipboard','debug','view','default','save','footer'];export class Provider{appendApplicableItems(event,contextMenu,target){}}
  63. self.UI=self.UI||{};UI=UI||{};UI.ContextMenu=ContextMenu;ContextMenu._groupWeights=_groupWeights;UI.ContextMenuItem=Item;UI.ContextMenuSection=Section;UI.ContextMenu.Provider=Provider;