ConsolePinPane.js 6.7 KB

1234567891011121314151617181920212223242526272829303132333435
  1. const _PinSymbol=Symbol('pinSymbol');export default class ConsolePinPane extends UI.ThrottledWidget{constructor(liveExpressionButton){super(true,250);this._liveExpressionButton=liveExpressionButton;this.registerRequiredCSS('console/consolePinPane.css');this.registerRequiredCSS('object_ui/objectValue.css');this.contentElement.classList.add('console-pins','monospace');this.contentElement.addEventListener('contextmenu',this._contextMenuEventFired.bind(this),false);this._pins=new Set();this._pinsSetting=Common.settings.createLocalSetting('consolePins',[]);for(const expression of this._pinsSetting.get()){this.addPin(expression);}}
  2. willHide(){for(const pin of this._pins){pin.setHovered(false);}}
  3. _savePins(){const toSave=Array.from(this._pins).map(pin=>pin.expression());this._pinsSetting.set(toSave);}
  4. _contextMenuEventFired(event){const contextMenu=new UI.ContextMenu(event);const target=event.deepElementFromPoint();if(target){const targetPinElement=target.enclosingNodeOrSelfWithClass('console-pin');if(targetPinElement){const targetPin=targetPinElement[_PinSymbol];contextMenu.editSection().appendItem(ls`Edit expression`,targetPin.focus.bind(targetPin));contextMenu.editSection().appendItem(ls`Remove expression`,this._removePin.bind(this,targetPin));targetPin.appendToContextMenu(contextMenu);}}
  5. contextMenu.editSection().appendItem(ls`Remove all expressions`,this._removeAllPins.bind(this));contextMenu.show();}
  6. _removeAllPins(){for(const pin of this._pins){this._removePin(pin);}}
  7. _removePin(pin){pin.element().remove();this._pins.delete(pin);this._savePins();this._liveExpressionButton.element.focus();}
  8. addPin(expression,userGesture){const pin=new ConsolePin(expression,this);this.contentElement.appendChild(pin.element());this._pins.add(pin);this._savePins();if(userGesture){pin.focus();}
  9. this.update();}
  10. doUpdate(){if(!this._pins.size||!this.isShowing()){return Promise.resolve();}
  11. if(this.isShowing()){this.update();}
  12. const updatePromises=Array.from(this._pins,pin=>pin.updatePreview());return Promise.all(updatePromises).then(this._updatedForTest.bind(this));}
  13. _updatedForTest(){}}
  14. export class ConsolePin extends Common.Object{constructor(expression,pinPane){super();const deletePinIcon=UI.Icon.create('smallicon-cross','console-delete-pin');self.onInvokeElement(deletePinIcon,event=>{pinPane._removePin(this);event.consume(true);});deletePinIcon.tabIndex=0;UI.ARIAUtils.setAccessibleName(deletePinIcon,ls`Remove expression`);UI.ARIAUtils.markAsButton(deletePinIcon);const fragment=UI.Fragment.build`
  15. <div class='console-pin'>
  16. ${deletePinIcon}
  17. <div class='console-pin-name' $='name'></div>
  18. <div class='console-pin-preview' $='preview'>${ls`not available`}</div>
  19. </div>`;this._pinElement=fragment.element();this._pinPreview=fragment.$('preview');const nameElement=fragment.$('name');nameElement.title=expression;this._pinElement[_PinSymbol]=this;this._lastResult=null;this._lastExecutionContext=null;this._editor=null;this._committedExpression=expression;this._hovered=false;this._lastNode=null;this._pinPreview.addEventListener('mouseenter',this.setHovered.bind(this,true),false);this._pinPreview.addEventListener('mouseleave',this.setHovered.bind(this,false),false);this._pinPreview.addEventListener('click',event=>{if(this._lastNode){Common.Revealer.reveal(this._lastNode);event.consume();}},false);this._editorPromise=self.runtime.extension(UI.TextEditorFactory).instance().then(factory=>{this._editor=factory.createEditor({devtoolsAccessibleName:ls`Live expression editor`,lineNumbers:false,lineWrapping:true,mimeType:'javascript',autoHeight:true,placeholder:ls`Expression`});this._editor.configureAutocomplete(ObjectUI.JavaScriptAutocompleteConfig.createConfigForEditor(this._editor));this._editor.widget().show(nameElement);this._editor.widget().element.classList.add('console-pin-editor');this._editor.widget().element.tabIndex=-1;this._editor.setText(expression);this._editor.widget().element.addEventListener('keydown',event=>{if(event.key==='Tab'&&!this._editor.text()){event.consume();return;}
  20. if(event.keyCode===UI.KeyboardShortcut.Keys.Esc.code){this._editor.setText(this._committedExpression);}},true);this._editor.widget().element.addEventListener('focusout',event=>{const text=this._editor.text();const trimmedText=text.trim();if(text.length!==trimmedText.length){this._editor.setText(trimmedText);}
  21. this._committedExpression=trimmedText;pinPane._savePins();this._editor.setSelection(TextUtils.TextRange.createFromLocation(Infinity,Infinity));});});}
  22. setHovered(hovered){if(this._hovered===hovered){return;}
  23. this._hovered=hovered;if(!hovered&&this._lastNode){SDK.OverlayModel.hideDOMNodeHighlight();}}
  24. expression(){return this._committedExpression;}
  25. element(){return this._pinElement;}
  26. async focus(){await this._editorPromise;this._editor.widget().focus();this._editor.setSelection(TextUtils.TextRange.createFromLocation(Infinity,Infinity));}
  27. appendToContextMenu(contextMenu){if(this._lastResult&&this._lastResult.object){contextMenu.appendApplicableItems(this._lastResult.object);this._lastResult=null;}}
  28. async updatePreview(){if(!this._editor){return;}
  29. const text=this._editor.textWithCurrentSuggestion().trim();const isEditing=this._pinElement.hasFocus();const throwOnSideEffect=isEditing&&text!==this._committedExpression;const timeout=throwOnSideEffect?250:undefined;const executionContext=UI.context.flavor(SDK.ExecutionContext);const{preview,result}=await ObjectUI.JavaScriptREPL.evaluateAndBuildPreview(text,throwOnSideEffect,timeout,!isEditing,'console');if(this._lastResult&&this._lastExecutionContext){this._lastExecutionContext.runtimeModel.releaseEvaluationResult(this._lastResult);}
  30. this._lastResult=result||null;this._lastExecutionContext=executionContext||null;const previewText=preview.deepTextContent();if(!previewText||previewText!==this._pinPreview.deepTextContent()){this._pinPreview.removeChildren();if(result&&SDK.RuntimeModel.isSideEffectFailure(result)){const sideEffectLabel=this._pinPreview.createChild('span','object-value-calculate-value-button');sideEffectLabel.textContent=`(...)`;sideEffectLabel.title=ls`Evaluate, allowing side effects`;}else if(previewText){this._pinPreview.appendChild(preview);}else if(!isEditing){this._pinPreview.createTextChild(ls`not available`);}
  31. this._pinPreview.title=previewText;}
  32. let node=null;if(result&&result.object&&result.object.type==='object'&&result.object.subtype==='node'){node=result.object;}
  33. if(this._hovered){if(node){SDK.OverlayModel.highlightObjectAsDOMNode(node);}else if(this._lastNode){SDK.OverlayModel.hideDOMNodeHighlight();}}
  34. this._lastNode=node||null;const isError=result&&result.exceptionDetails&&!SDK.RuntimeModel.isSideEffectFailure(result);this._pinElement.classList.toggle('error-level',!!isError);}}
  35. self.Console=self.Console||{};Console=Console||{};Console.ConsolePinPane=ConsolePinPane;Console.ConsolePin=ConsolePin;