api-console-assets
Version:
This repo only exists to publish api console components to npm
363 lines (338 loc) • 10.9 kB
HTML
<!--
@license
Copyright 2016 The Advanced REST client authors <arc@mulesoft.com>
Licensed 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.
-->
<link rel="import" href="../polymer/polymer.html">
<link rel="import" href="../code-mirror/code-mirror.html">
<link rel="import" href="../paper-button/paper-button.html">
<link rel="import" href="../request-payload-editor-behavior/request-payload-editor-behavior.html">
<link rel="import" href="../paper-tooltip/paper-tooltip.html">
<link rel="import" href="../iron-resizable-behavior/iron-resizable-behavior.html">
<link rel="import" href="../code-mirror-linter/code-mirror-linter-json.html">
<link rel="import" href="../paper-toast/paper-toast.html">
<link rel="import" href="../events-target-behavior/events-target-behavior.html">
<!--
`<raw-payload-editor>` A raw payload input editor based on CodeMirror.
The element additionally shows Encode / Decode buttons if current content type value contains
"x-www-form-urlencoded".
The element listens for `content-type-changed` custom event and updates the `contentType` property
automatically. This event is commonly used in ARC elements.
### Example
```
<raw-payload-editor content-type="application/json"></raw-payload-editor>
```
### Styling
`<raw-payload-editor>` provides the following custom properties and mixins for styling:
Custom property | Description | Default
----------------|-------------|----------
`--raw-payload-editor` | Mixin applied to the element | `{}`
`--raw-payload-editor-encode-buttons` | Mixin applied to encode / decode buttons container | `{}`
@group UI Elements
@element raw-payload-editor
@demo demo/index.html
-->
<dom-module id="raw-payload-editor">
<template>
<style>
:host {
display: block;
@apply --raw-payload-editor;
}
.action-buttons {
margin: 8px 0;
@apply --raw-payload-editor-encode-buttons;
@apply --raw-payload-editor-action-buttons;
}
*[hidden] {
display: none ;
}
</style>
<template is="dom-if" if="[[encodeEnabled]]">
<div class="action-buttons" data-type="form">
<span>
<paper-button on-tap="encodeValue">Encode payload</paper-button>
<paper-tooltip animation-delay="200">Encodes payload to x-www-form-urlencoded data</paper-tooltip>
</span>
<span>
<paper-button on-tap="decodeValue">Decode payload</paper-button>
<paper-tooltip animation-delay="200">Decodes payload to human readable form</paper-tooltip>
</span>
</div>
</template>
<template is="dom-if" if="[[isJson]]">
<div class="action-buttons" data-type="json">
<span>
<paper-button on-tap="formatValue" data-action="format-json">Format JSON</paper-button>
<paper-tooltip animation-delay="200">Formats JSON input.</paper-tooltip>
</span>
<span>
<paper-button on-tap="minifyValue" data-action="minify-json">Minify JSON</paper-button>
<paper-tooltip animation-delay="200">Removed whitespaces from the input</paper-tooltip>
</span>
</div>
</template>
<code-mirror mode="application/json" on-value-changed="_editorValueChanged" on-paste="_onPaste" lint="[[_lintObject]]" gutters="[[_cmGutters]]" include-custom-styles="[[_customStylesList]]"></code-mirror>
<paper-toast id="invalidJsonToast">JSON value is invalid. Cannot parse value.</paper-toast>
</template>
<script>
/* global CodeMirror */
Polymer({
is: 'raw-payload-editor',
behaviors: [
ArcBehaviors.RequestPayloadEditorBehavior,
Polymer.IronResizableBehavior,
ArcBehaviors.EventsTargetBehavior
],
/**
* Event fire when the value of the editor change.
* This event is not fired if `attrForOpened` is set and corresponding value is not set.
*
* @event payload-value-changed
* @param {String} value Current payload value.
*/
properties: {
/**
* Content-Type header value. Determines current code mirror mode.
*/
contentType: {
type: String,
observer: '_onContentTypeChanged'
},
// Computed value, true if `contentType` contains `x-www-form-urlencoded`
encodeEnabled: {
type: Boolean,
computed: '_computeEncodeEnabled(contentType)',
value: false
},
// Computed value, true if `contentType` contains `/json`
isJson: {
type: Boolean,
computed: '_computeIsJson(contentType)',
value: false
},
_cmGutters: {
type: Array,
value: ['CodeMirror-lint-markers']
},
_lintObject: {
type: Object,
value: function() {
return false;
}
},
_customStylesList: {
type: Array
}
},
observers: [
'_valueChanged(value)',
'__isOpenedChanged(_isOpened)'
],
listeners: {
'iron-resize': 'refresh'
},
_attachListeners: function(node) {
this.listen(node, 'content-type-changed', '_contentTypeHandler');
},
_detachListeners: function(node) {
this.unlisten(node, 'content-type-changed', '_contentTypeHandler');
},
__isOpenedChanged: function(isOpened) {
if (isOpened) {
this.__editorValueChange = false;
this._valueChanged(this.value);
this.refresh();
}
},
/**
* Forces render code-mirror content.
* Should be used to when the element becomes visible after being hidden.
*/
refresh: function() {
if (!this._isOpened) {
return;
}
var elm = this.$$('code-mirror');
if (!elm) {
return; // DOM not ready
}
elm.editor.refresh();
},
/**
* Changes CodeMirror mode when the content type value is updated.
*/
_onContentTypeChanged: function(ct) {
this._setupLinter(ct);
if (!ct) {
return;
}
if (ct.indexOf && ct.indexOf(';') !== -1) {
ct = ct.substr(0, ct.indexOf(';'));
}
this.$$('code-mirror').mode = ct;
},
// Computes `encodeEnabled` based on content type.
_computeEncodeEnabled: function(ct) {
return this.__computeIs(ct, 'x-www-form-urlencoded');
},
_computeIsJson: function(ct) {
return this.__computeIs(ct, '/json');
},
__computeIs: function(ct, needle) {
if (!ct) {
return false;
}
if (ct.indexOf && ct.indexOf(needle) !== -1) {
return true;
}
return false;
},
// Handler for the `content-type-changed` event. Sets the `contentType` property.
_contentTypeHandler: function(e) {
var ev = Polymer.dom(e);
if (ev.rootTarget === this) {
return;
}
var ct = e.detail.value;
this.set('contentType', ct);
},
/**
* Handler for value change.
* If the element is opened then it will fire change event.
*/
_valueChanged: function(value) {
if (this._isOpened) {
if (this.__editorValueChange) {
this.fire('payload-value-changed', {
value: value
});
} else {
this.$$('code-mirror').value = value;
if (this.__internalChange) {
// from encoder / decoder
this.fire('payload-value-changed', {
value: value
});
}
}
}
},
// Called when the editor fires change event
_editorValueChanged: function(e) {
e.stopPropagation();
this.__editorValueChange = true;
this.set('value', e.detail.value);
this.__editorValueChange = false;
},
/**
* Formats JSON data on paste.
* It only formats the input if no selection is applied, whole value
* is selcted or input is empty.
*/
_onPaste: function(e) {
if (this.contentType !== 'application/json') {
return;
}
var len;
if (this.value) {
len = this.value.length;
}
if (this._cancelPaste(len)) {
return;
}
var data = e.clipboardData.getData('text');
try {
data = JSON.parse(data);
data = JSON.stringify(data, null, 2);
e.preventDefault();
this.set('value', data);
} catch (e) {}
},
/**
* Tests if text formatting on paste is allowed
*
* @param {Number} inputSize Size of current value
* @return {Boolean} True to disallow altering the value on paste.
*/
_cancelPaste: function(inputSize) {
if (!inputSize) {
return false;
}
var el = document.activeElement;
var start = 0;
var end = 0;
if (el) {
if (typeof el.selectionStart === 'number') {
start = el.selectionStart;
}
if (typeof el.selectionEnd === 'number') {
end = el.selectionEnd;
}
}
if (start === 0 && end === 0) {
var selection = window.getSelection();
if (selection.rangeCount === 0) {
return false;
}
var range = selection.getRangeAt(0);
start = range.startOffset;
end = range.endOffset;
}
if (start === 0 && end === inputSize) {
return false;
}
return true;
},
_setupLinter: function(ct) {
if (!ct || (ct.indexOf && ct.indexOf('/json') === -1)) {
this.set('_lintObject', false);
this.set('_customStylesList', undefined);
} else {
this.set('_lintObject', CodeMirror.lint.json);
this.set('_customStylesList', ['code-mirror-lint']);
}
var editor = this.$$('code-mirror').editor;
if (editor) {
editor.refresh();
}
},
/**
* Formats current value as it is a JSON object.
*/
formatValue: function() {
try {
var value = this.value;
value = JSON.parse(value);
value = JSON.stringify(value, null, 2);
this.set('value', value);
} catch (e) {
this.$.invalidJsonToast.opened = true;
console.log(e);
}
},
/**
* Minifies JSON value by removing whitespaces.
*/
minifyValue: function() {
try {
var value = this.value;
value = JSON.parse(value);
value = JSON.stringify(value);
this.set('value', value);
} catch (e) {
this.$.invalidJsonToast.opened = true;
console.log(e);
}
}
});
</script>
</dom-module>