New platform dropdown (#33520)

* Removed old dropdown code from dev tools consle

* Removed old dropdown from vega tools

* Deleted dropdown directives from angular bootstrap

* Deleted ui.bootstrap.dropdown test (irrelevant, since directive was deleted)
This commit is contained in:
Liza Katz 2019-03-21 15:02:27 +02:00 committed by GitHub
parent e9c41c211c
commit 3cc70257b8
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
14 changed files with 494 additions and 338 deletions

View file

@ -32,6 +32,7 @@ require('./src/directives/sense_history');
require('./src/directives/sense_settings');
require('./src/directives/sense_help');
require('./src/directives/sense_welcome');
require('./src/directives/console_menu_directive');
uiRoutes.when('/dev_tools/console', {

View file

@ -11,42 +11,13 @@
<i class="fa fa-play"></i>
</button>
</kbn-tooltip>
<span dropdown>
<button
id="consoleRequestOptions"
class="conApp__editorActionButton"
dropdown-toggle
ng-click="getDocumentation()"
aria-label="{{:: 'console.requestOptionsButtonAriaLabel' | i18n: { defaultMessage: 'Request options' } }}"
>
<span class="kuiIcon fa-wrench"></span>
</button>
<ul
class="dropdown-menu"
role="menu"
aria-labelledby="consoleRequestOptions"
>
<li role="menuitem">
<button
id="ConCopyAsCurl"
i18n-id="console.requestOptions.copyAsUrlButtonLabel"
i18n-default-message="Copy as cURL"></button>
</li>
<li role="menuitem" ng-if="documentation">
<button
ng-click="openDocumentation(documentation)"
i18n-id="console.requestOptions.openDocumentationButtonLabel"
i18n-default-message="Open documentation"></button>
</li>
<li role="menuitem">
<button
ng-click="autoIndent($event)"
i18n-id="console.requestOptions.autoIndentButtonLabel"
i18n-default-message="Auto indent"></button>
</li>
</ul>
</span>
<console-menu
auto-indent="autoIndent"
get-documentation="getDocumentation"
open-documentation="openDocumentation"
get-curl="getRequestsAsCURL"
>
</console-menu>
</div>
<div class="conApp__editorContent" id="ConAppEditor" data-test-subj="request-editor">GET _search

View file

@ -0,0 +1,157 @@
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import PropTypes from 'prop-types';
import React, {
Component,
} from 'react';
import {
EuiButtonIcon,
EuiContextMenuPanel,
EuiContextMenuItem,
EuiPopover,
} from '@elastic/eui';
import { FormattedMessage } from '@kbn/i18n/react';
export class ConsoleMenu extends Component {
constructor(props) {
super(props);
this.state = {
curlCode: '',
isPopoverOpen: false,
};
}
mouseEnter = () => {
if (this.state.isPopoverOpen) return;
this.props.getCurl(text => {
this.setState({ curlCode: text });
});
}
copyAsCurl() {
this.copyText(this.state.curlCode);
}
copyText(text) {
const textField = document.createElement('textarea');
textField.innerText = text;
document.body.appendChild(textField);
textField.select();
document.execCommand('copy');
textField.remove();
}
onButtonClick = () => {
this.setState(prevState => ({
isPopoverOpen: !prevState.isPopoverOpen,
}));
};
closePopover = () => {
this.setState({
isPopoverOpen: false,
});
};
openDocs = () => {
this.closePopover();
this.props.getDocumentation();
this.props.openDocumentation();
}
render() {
const button = (
<EuiButtonIcon
iconType="wrench"
onClick={this.onButtonClick}
aria-label={
<FormattedMessage
id="console.requestOptionsButtonAriaLabel"
defaultMessage="Request options"
/>
}
/>
);
const items = [
(
<EuiContextMenuItem
key="Copy as cURL"
id="ConCopyAsCurl"
disabled={!document.queryCommandSupported('copy')}
onClick={() => { this.closePopover(); this.copyAsCurl(); }}
>
<FormattedMessage
id="console.requestOptions.copyAsUrlButtonLabel"
defaultMessage="Copy as cURL"
/>
</EuiContextMenuItem>
), (
<EuiContextMenuItem
key="Open documentation"
onClick={() => { this.openDocs(); }}
>
<FormattedMessage
id="console.requestOptions.openDocumentationButtonLabel"
defaultMessage="Open documentation"
/>
</EuiContextMenuItem>
), (
<EuiContextMenuItem
key="Auto indent"
onClick={(event) => { this.closePopover(); this.props.autoIndent(event); }}
>
<FormattedMessage
id="console.requestOptions.autoIndentButtonLabel"
defaultMessage="Auto indent"
/>
</EuiContextMenuItem>
)
];
return (
<span onMouseEnter={this.mouseEnter}>
<EuiPopover
id="contextMenu"
button={button}
isOpen={this.state.isPopoverOpen}
closePopover={this.closePopover}
panelPaddingSize="none"
anchorPosition="downLeft"
>
<EuiContextMenuPanel
items={items}
/>
</EuiPopover>
</span>
);
}
}
ConsoleMenu.propTypes = {
getCurl: PropTypes.func.isRequired,
openDocumentation: PropTypes.func.isRequired,
getDocumentation: PropTypes.func.isRequired,
autoIndent: PropTypes.func.isRequired,
};

View file

@ -53,6 +53,9 @@ module.controller('SenseController', function SenseController(Private, $scope, $
$scope.getDocumentation();
});
$scope.getDocumentation();
// expose method for React Consumption
$scope.getRequestsAsCURL = input.getRequestsAsCURL;
});
$scope.getDocumentation = () => {
input.getRequestsInRange(function (requests) {

View file

@ -0,0 +1,34 @@
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import 'ngreact';
import { wrapInI18nContext } from 'ui/i18n';
import { uiModules } from 'ui/modules';
const module = uiModules.get('apps/sense', ['react']);
import { ConsoleMenu } from '../console_menu';
module.directive('consoleMenu', function (reactDirective) {
return reactDirective(
wrapInI18nContext(ConsoleMenu),
undefined,
{ restrict: 'E' }
);
});

View file

@ -17,7 +17,6 @@
* under the License.
*/
const $ = require('jquery');
require('brace');
require('brace/ext/searchbox');
import Autocomplete from './autocomplete';
@ -66,37 +65,6 @@ export function initializeInput($el, $actionsEl, $copyAsCurlEl, output, openDocu
}
});
/**
* COPY AS CURL
*/
(function setupClipboard() {
function copyText(text) {
const node = $(`<textarea style="height:1px"></textarea`)
.val(text)
.appendTo(document.body)
.select();
document.execCommand('copy');
node.remove();
}
if (!document.queryCommandSupported('copy')) {
$copyAsCurlEl.hide();
return;
}
$copyAsCurlEl.click(() => {
copyText($copyAsCurlEl.attr('data-clipboard-text'));
});
input.$actions.on('mouseenter', function () {
if ($(this).hasClass('open')) return;
input.getRequestsAsCURL(text => {
$copyAsCurlEl.attr('data-clipboard-text', text);
});
});
}());
/**
* Setup the "send" shortcut
*/

View file

@ -0,0 +1,114 @@
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import PropTypes from 'prop-types';
import React, {
Component,
} from 'react';
import {
EuiButtonIcon,
EuiContextMenuPanel,
EuiContextMenuItem,
EuiPopover,
} from '@elastic/eui';
import { FormattedMessage } from '@kbn/i18n/react';
export class VegaActionsMenu extends Component {
constructor(props) {
super(props);
this.state = {
isPopoverOpen: false,
};
}
onButtonClick = () => {
this.setState(prevState => ({
isPopoverOpen: !prevState.isPopoverOpen,
}));
};
closePopover = () => {
this.setState({
isPopoverOpen: false,
});
};
render() {
const button = (
<EuiButtonIcon
iconType="wrench"
onClick={this.onButtonClick}
aria-label={
<FormattedMessage
id="vega.editor.vegaEditorOptionsButtonAriaLabel"
defaultMessage="Vega editor options"
/>
}
/>
);
const items = [
(
<EuiContextMenuItem
key="hjson"
onClick={(event) => { this.closePopover(); this.props.formatHJson(event); }}
>
<FormattedMessage
id="vega.editor.reformatAsHJSONButtonLabel"
defaultMessage="Reformat as HJSON"
/>
</EuiContextMenuItem>
), (
<EuiContextMenuItem
key="json"
onClick={(event) => { this.closePopover(); this.props.formatJson(event); }}
>
<FormattedMessage
id="vega.editor.reformatAsJSONButtonLabel"
defaultMessage="Reformat as JSON, delete comments"
/>
</EuiContextMenuItem>
)
];
return (
<EuiPopover
id="helpMenu"
button={button}
isOpen={this.state.isPopoverOpen}
closePopover={this.closePopover}
panelPaddingSize="none"
anchorPosition="downLeft"
>
<EuiContextMenuPanel
items={items}
/>
</EuiPopover>
);
}
}
VegaActionsMenu.propTypes = {
formatHJson: PropTypes.func.isRequired,
formatJson: PropTypes.func.isRequired,
};

View file

@ -0,0 +1,126 @@
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import React, {
Component,
} from 'react';
import {
EuiButtonIcon,
EuiContextMenuPanel,
EuiContextMenuItem,
EuiPopover,
} from '@elastic/eui';
import { FormattedMessage } from '@kbn/i18n/react';
export class VegaHelpMenu extends Component {
constructor(props) {
super(props);
this.state = {
isPopoverOpen: false,
};
}
onButtonClick = () => {
this.setState(prevState => ({
isPopoverOpen: !prevState.isPopoverOpen,
}));
};
closePopover = () => {
this.setState({
isPopoverOpen: false,
});
};
render() {
const button = (
<EuiButtonIcon
iconType="questionInCircle"
onClick={this.onButtonClick}
aria-label={
<FormattedMessage
id="vega.editor.vegaHelpButtonAriaLabel"
defaultMessage="Vega help"
/>
}
/>
);
const items = [
(
<EuiContextMenuItem
key="vegaHelp"
target="_blank"
rel="noopener noreferrer"
href="https://www.elastic.co/guide/en/kibana/master/vega-graph.html"
onClick={() => { this.closePopover(); }}
>
<FormattedMessage
id="vega.editor.vegaHelpLinkText"
defaultMessage="Kibana Vega Help"
/>
</EuiContextMenuItem>
), (
<EuiContextMenuItem
key="vegaLiteDocs"
target="_blank"
rel="noopener noreferrer"
href="https://vega.github.io/vega-lite/docs/"
onClick={() => { this.closePopover(); }}
>
<FormattedMessage
id="vega.editor.vegaLiteDocumentationLinkText"
defaultMessage="Vega-Lite Documentation"
/>
</EuiContextMenuItem>
), (
<EuiContextMenuItem
key="vegaDoc"
target="_blank"
rel="noopener noreferrer"
href="https://vega.github.io/vega/docs/"
onClick={() => { this.closePopover(); }}
>
<FormattedMessage
id="vega.editor.vegaDocumentationLinkText"
defaultMessage="Vega Documentation"
/>
</EuiContextMenuItem>
)
];
return (
<EuiPopover
id="helpMenu"
button={button}
isOpen={this.state.isPopoverOpen}
closePopover={this.closePopover}
panelPaddingSize="none"
anchorPosition="downLeft"
>
<EuiContextMenuPanel
items={items}
/>
</EuiPopover>
);
}
}

View file

@ -0,0 +1,44 @@
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import 'ngreact';
import { wrapInI18nContext } from 'ui/i18n';
import { uiModules } from 'ui/modules';
const module = uiModules.get('kibana/vega', ['react']);
import { VegaHelpMenu } from './vega_help_menu';
import { VegaActionsMenu } from './vega_action_menu';
module.directive('vegaActionsMenu', function (reactDirective) {
return reactDirective(
wrapInI18nContext(VegaActionsMenu),
undefined,
{ restrict: 'E' }
);
});
module.directive('vegaHelpMenu', function (reactDirective) {
return reactDirective(
wrapInI18nContext(VegaHelpMenu),
undefined,
{ restrict: 'E' }
);
});

View file

@ -19,79 +19,13 @@
></div>
<div class="vgaEditor__aceEditorActions">
<span dropdown>
<button
id="vegaHelp"
class="editor_action"
dropdown-toggle
aria-label="{{::'vega.editor.vegaHelpButtonAriaLabel' | i18n: {defaultMessage: 'Vega help'} }}"
>
<span class="kuiIcon fa-question-circle"></span>
</button>
<ul
class="dropdown-menu"
role="menu"
aria-labelledby="vegaHelp"
>
<li role="menuitem">
<a
target="_blank"
rel="noopener"
href="https://www.elastic.co/guide/en/kibana/master/vega-graph.html"
i18n-id="vega.editor.vegaHelpLinkText"
i18n-default-message="Kibana Vega Help"
></a>
</li>
<li role="menuitem">
<a
target="_blank"
rel="noopener noreferrer"
href="https://vega.github.io/vega-lite/docs/"
i18n-id="vega.editor.vegaLiteDocumentationLinkText"
i18n-default-message="Vega-Lite Documentation"
></a>
</li>
<li role="menuitem">
<a
target="_blank"
rel="noopener noreferrer"
href="https://vega.github.io/vega/docs/"
i18n-id="vega.editor.vegaDocumentationLinkText"
i18n-default-message="Vega Documentation"
></a>
</li>
</ul>
</span>
<span dropdown>
<button
id="vegaOptions"
class="editor_action"
dropdown-toggle
aria-label="{{::'vega.editor.vegaEditorOptionsButtonAriaLabel' | i18n: {defaultMessage: 'Vega editor options'} }}"
>
<span class="kuiIcon fa-wrench"></span>
</button>
<ul
class="dropdown-menu"
role="menu"
aria-labelledby="vegaOptions"
>
<li role="menuitem">
<button
ng-click="formatHJson($event)"
i18n-id="vega.editor.reformatAsHJSONButtonLabel"
i18n-default-message="Reformat as HJSON"
></button>
</li>
<li role="menuitem">
<button
ng-click="formatJson($event)"
i18n-id="vega.editor.reformatAsJSONButtonLabel"
i18n-default-message="Reformat as JSON, delete comments"
></button>
</li>
</ul>
</span>
<vega-help-menu
format-h-json="formatHJson"
format-json="formatJson"
>
</vega-help-menu>
<vega-actions-menu></vega-actions-menu>
</div>
</div>

View file

@ -31,6 +31,7 @@ import { VegaVisualizationProvider } from './vega_visualization';
import 'brace/mode/hjson';
import 'brace/ext/searchbox';
import './vega_editor_controller';
import './help_menus/vega_help_menu_directives';
import vegaEditorTemplate from './vega_editor_template.html';
import defaultSpec from '!!raw-loader!./default.spec.hjson';

View file

@ -1,161 +0,0 @@
angular.module('ui.bootstrap.dropdown', [])
.constant('dropdownConfig', {
openClass: 'open'
})
.service('dropdownService', ['$document', function($document) {
var openScope = null;
this.open = function( dropdownScope ) {
if ( !openScope ) {
$document.bind('click', closeDropdown);
$document.bind('keydown', escapeKeyBind);
}
if ( openScope && openScope !== dropdownScope ) {
openScope.isOpen = false;
}
openScope = dropdownScope;
};
this.close = function( dropdownScope ) {
if ( openScope === dropdownScope ) {
openScope = null;
$document.unbind('click', closeDropdown);
$document.unbind('keydown', escapeKeyBind);
}
};
var closeDropdown = function( evt ) {
// This method may still be called during the same mouse event that
// unbound this event handler. So check openScope before proceeding.
if (!openScope) { return; }
var toggleElement = openScope.getToggleElement();
if ( evt && toggleElement && toggleElement[0].contains(evt.target) ) {
return;
}
openScope.$apply(function() {
openScope.isOpen = false;
});
};
var escapeKeyBind = function( evt ) {
if ( evt.which === 27 ) {
openScope.focusToggleElement();
closeDropdown();
}
};
}])
.controller('DropdownController', ['$scope', '$attrs', '$parse', 'dropdownConfig', 'dropdownService', '$animate', function($scope, $attrs, $parse, dropdownConfig, dropdownService, $animate) {
var self = this,
scope = $scope.$new(), // create a child scope so we are not polluting original one
openClass = dropdownConfig.openClass,
getIsOpen,
setIsOpen = angular.noop,
toggleInvoker = $attrs.onToggle ? $parse($attrs.onToggle) : angular.noop;
this.init = function( element ) {
self.$element = element;
if ( $attrs.isOpen ) {
getIsOpen = $parse($attrs.isOpen);
setIsOpen = getIsOpen.assign;
$scope.$watch(getIsOpen, function(value) {
scope.isOpen = !!value;
});
}
};
this.toggle = function( open ) {
return scope.isOpen = arguments.length ? !!open : !scope.isOpen;
};
// Allow other directives to watch status
this.isOpen = function() {
return scope.isOpen;
};
scope.getToggleElement = function() {
return self.toggleElement;
};
scope.focusToggleElement = function() {
if ( self.toggleElement ) {
self.toggleElement[0].focus();
}
};
scope.$watch('isOpen', function( isOpen, wasOpen ) {
$animate[isOpen ? 'addClass' : 'removeClass'](self.$element, openClass);
if ( isOpen ) {
scope.focusToggleElement();
dropdownService.open( scope );
} else {
dropdownService.close( scope );
}
setIsOpen($scope, isOpen);
if (angular.isDefined(isOpen) && isOpen !== wasOpen) {
toggleInvoker($scope, { open: !!isOpen });
}
});
$scope.$on('$locationChangeSuccess', function() {
scope.isOpen = false;
});
$scope.$on('$destroy', function() {
scope.$destroy();
});
}])
.directive('dropdown', function() {
return {
controller: 'DropdownController',
link: function(scope, element, attrs, dropdownCtrl) {
dropdownCtrl.init( element );
}
};
})
.directive('dropdownToggle', function() {
return {
require: '?^dropdown',
link: function(scope, element, attrs, dropdownCtrl) {
if ( !dropdownCtrl ) {
return;
}
dropdownCtrl.toggleElement = element;
var toggleDropdown = function(event) {
event.preventDefault();
if ( !element.hasClass('disabled') && !attrs.disabled ) {
scope.$apply(function() {
dropdownCtrl.toggle();
});
}
};
element.bind('click', toggleDropdown);
// WAI-ARIA
element.attr({ 'aria-haspopup': true, 'aria-expanded': false });
scope.$watch(dropdownCtrl.isOpen, function( isOpen ) {
element.attr('aria-expanded', !!isOpen);
});
scope.$on('$destroy', function() {
element.unbind('click', toggleDropdown);
});
}
};
});

View file

@ -26,7 +26,6 @@ angular.module('ui.bootstrap', [
'ui.bootstrap.alert',
'ui.bootstrap.bindHtml',
'ui.bootstrap.position',
'ui.bootstrap.dropdown',
'ui.bootstrap.modal',
'ui.bootstrap.pagination',
'ui.bootstrap.tooltip',
@ -53,7 +52,6 @@ angular.module('ui.bootstrap.tpls', [
import './alert/alert';
import './bindHtml/bindHtml';
import './dropdown/dropdown';
import './modal/modal';
import './pagination/pagination';
import './position/position';

View file

@ -1,34 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
import ngMock from 'ng_mock';
import expect from 'expect.js';
describe('ML - Angular Bootstrap Patch - Dropdown Controller', () => {
beforeEach(() => {
ngMock.module('ui.bootstrap.dropdown');
});
it('Initialize Dropdown Controller', (done) => {
ngMock.inject(function ($rootScope, $controller) {
const scope = $rootScope.$new();
expect(scope.$$watchersCount).to.eql(0);
expect(() => {
$controller('DropdownController', {
$attrs: [],
$scope: scope
});
}).to.not.throwError();
expect(scope.$$watchersCount).to.eql(1);
done();
});
});
});