Added Swagger

This commit is contained in:
2020-06-10 08:25:21 +02:00
parent 5c6f37eaf7
commit af76cbca87
257 changed files with 48861 additions and 12 deletions
+13
View File
@@ -0,0 +1,13 @@
export const JUMP_TO_LINE = "jump_to_line"
export function jumpToLine(line) {
return {
type: JUMP_TO_LINE,
payload: line
}
}
// This is a hook. Will have editor instance
// It needs to be an async-function, to avoid dispatching an object to the reducer
export const onLoad = () => () => {}
@@ -0,0 +1,6 @@
/* global ace */
ace.define("ace/snippets/yaml",
["require","exports","module"], function(e,t,n){ // eslint-disable-line no-unused-vars
t.snippetText=undefined
t.scope="yaml"
})
@@ -0,0 +1,65 @@
import React from "react"
import PropTypes from "prop-types"
export default class EditorContainer extends React.Component {
// This is already debounced by editor.jsx
onChange = (value) => {
this.props.onChange(value)
}
render() {
let { specSelectors, getComponent, errSelectors, fn, editorSelectors, configsSelectors } = this.props
let Editor = getComponent("Editor")
let wrapperClasses = ["editor-wrapper"]
const readOnly = !!configsSelectors.get("readOnly")
if(readOnly) {
wrapperClasses.push("read-only")
}
let propsForEditor = this.props
const editorOptions = {
enableLiveAutocompletion: configsSelectors.get("editorLiveAutocomplete"),
readOnly: readOnly,
highlightActiveLine: !readOnly,
highlightGutterLine: !readOnly,
}
return (
<div id='editor-wrapper' className={wrapperClasses.join(" ")}>
{ readOnly ? <h2 className="editor-readonly-watermark">Read Only</h2> : null }
<Editor
{...propsForEditor}
value={specSelectors.specStr()}
origin={specSelectors.specOrigin()}
editorOptions={editorOptions}
specObject={specSelectors.specJson().toJS()}
errors={errSelectors.allErrors()}
onChange={this.onChange}
goToLine={editorSelectors.gotoLine()}
AST={fn.AST}
/>
</div>
)
}
}
EditorContainer.defaultProps = {
onChange: Function.prototype
}
EditorContainer.propTypes = {
specActions: PropTypes.object.isRequired,
configsSelectors: PropTypes.object.isRequired,
onChange: PropTypes.func,
fn: PropTypes.object,
specSelectors: PropTypes.object.isRequired,
errSelectors: PropTypes.object.isRequired,
editorSelectors: PropTypes.object.isRequired,
getComponent: PropTypes.func.isRequired,
}
@@ -0,0 +1,326 @@
import React from "react"
import PropTypes from "prop-types"
import AceEditor from "react-ace"
import editorPluginsHook from "../editor-plugins/hook"
import { placeMarkerDecorations } from "../editor-helpers/marker-placer"
import Im, { fromJS } from "immutable"
import ImPropTypes from "react-immutable-proptypes"
import win from "src/window"
import isUndefined from "lodash/isUndefined"
import omit from "lodash/omit"
import isEqual from "lodash/isEqual"
import debounce from "lodash/debounce"
import ace from "brace"
import "brace/mode/yaml"
import "brace/theme/tomorrow_night_eighties"
import "brace/ext/language_tools"
import "brace/ext/searchbox"
import "./brace-snippets-yaml"
const NOOP = Function.prototype // Apparently the best way to no-op
export default function makeEditor({ editorPluginsToRun }) {
class Editor extends React.Component {
constructor(props, context) {
super(props, context)
this.editor = null
this.debouncedOnChange = props.debounce > 0
? debounce(props.onChange, props.debounce)
: props.onChange
}
static propTypes = {
specId: PropTypes.string,
value: PropTypes.string,
editorOptions: PropTypes.object,
origin: PropTypes.string,
debounce: PropTypes.number,
onChange: PropTypes.func,
onMarkerLineUpdate: PropTypes.func,
markers: PropTypes.object,
goToLine: PropTypes.object,
specObject: PropTypes.object.isRequired,
editorActions: PropTypes.object,
AST: PropTypes.object.isRequired,
errors: ImPropTypes.list,
}
static defaultProps = {
value: "",
specId: "--unknown--",
origin: "not-editor",
onChange: NOOP,
onMarkerLineUpdate: NOOP,
markers: {},
goToLine: {},
errors: fromJS([]),
editorActions: {onLoad(){}},
editorOptions: {},
debounce: 800 // 0.5 imperial seconds™
}
checkForSilentOnChange = (value) => {
if(!this.silent) {
this.debouncedOnChange(value)
}
}
onLoad = (editor) => {
const { props } = this
const { AST, specObject } = props
const langTools = ace.acequire("ace/ext/language_tools")
const session = editor.getSession()
this.editor = editor
// fixes a warning, see https://github.com/ajaxorg/ace/issues/2499
editor.$blockScrolling = Infinity
session.setUseWrapMode(true)
session.on("changeScrollLeft", xPos => { // eslint-disable-line no-unused-vars
session.setScrollLeft(0)
})
// TODO Remove this in favour of editorActions.onLoad
editorPluginsHook(editor, props, editorPluginsToRun || [], {
langTools, AST, specObject
})
editor.setHighlightActiveLine(false)
editor.setHighlightActiveLine(true)
this.syncOptionsFromState(props.editorOptions)
if(props.editorActions && props.editorActions.onLoad)
props.editorActions.onLoad({...props, langTools, editor})
this.updateMarkerAnnotations(this.props)
}
onResize = () => {
const { editor } = this
if(editor) {
let session = editor.getSession()
editor.resize()
let wrapLimit = session.getWrapLimit()
editor.setPrintMarginColumn(wrapLimit)
}
}
onClick = () => {
// onClick is deferred by 40ms, to give element resizes time to settle.
setTimeout(() => {
if(this.getWidth() !== this.width) {
this.onResize()
this.width = this.getWidth()
}
}, 40)
}
getWidth = () => {
let el = win.document.getElementById("editor-wrapper")
return el ? el.getBoundingClientRect().width : null
}
updateErrorAnnotations = (nextProps) => {
if(this.editor && nextProps.errors) {
let editorAnnotations = nextProps.errors.toJS().map(err => {
// Create annotation objects that ACE can use
return {
row: err.line - 1,
column: 0,
type: err.level,
text: err.message
}
})
this.editor.getSession().setAnnotations(editorAnnotations)
}
}
updateMarkerAnnotations = (props) => {
const { editor } = this
const markers = Im.Map.isMap(props.markers) ? props.markers.toJS() : {}
this._removeMarkers = placeMarkerDecorations({
editor,
markers,
onMarkerLineUpdate: props.onMarkerLineUpdate,
})
}
removeMarkers = () => {
if(this._removeMarkers) {
this._removeMarkers()
this._removeMarkers = null
}
}
shouldUpdateYaml = (props) => {
// No editor instance
if(!this.editor)
return false
// Origin is editor
if(props.origin === "editor")
return false
// Redundant
if(this.editor.getValue() === props.value)
return false
// Value and origin are same, no update.
if(this.props.value === props.value
&& this.props.origin === props.origin)
return false
return true
}
shouldUpdateMarkers = (props) => {
const { markers } = props
if(Im.Map.isMap(markers)) {
return !Im.is(markers, this.props.markers) // Different from previous?
}
return true // Not going to do a deep compare of object-like markers
}
updateYamlAndMarkers = (props) => {
// If we update the yaml, we need to "lift" the yaml first
if(this.shouldUpdateYaml(props)) {
this.removeMarkers()
this.updateYaml(props)
this.updateMarkerAnnotations(props)
} else if (this.shouldUpdateMarkers(props)) {
this.removeMarkers()
this.updateMarkerAnnotations(props)
}
}
updateYaml = (props) => {
if (props.origin === "insert") {
// Don't clobber the undo stack in this case.
this.editor.session.doc.setValue(props.value)
this.editor.selection.clearSelection()
} else {
// session.setValue does not trigger onChange, nor add to undo stack.
// Neither of which we want here.
this.editor.session.setValue(props.value)
}
}
syncOptionsFromState = (editorOptions={}) => {
const { editor } = this
if(!editor) {
return
}
const setOptions = omit(editorOptions, ["readOnly"])
editor.setOptions(setOptions)
const readOnly = isUndefined(editorOptions.readOnly)
? false
: editorOptions.readOnly // If its undefined, default to false.
editor.setReadOnly(readOnly)
}
componentWillMount() {
// add user agent info to document
// allows our custom Editor styling for IE10 to take effect
var doc = win.document.documentElement
doc.setAttribute("data-useragent", win.navigator.userAgent)
this.syncOptionsFromState(this.props.editorOptions)
}
componentDidMount() {
// eslint-disable-next-line react/no-did-mount-set-state
this.width = this.getWidth()
win.document.addEventListener("click", this.onClick)
}
componentWillUnmount() {
win.document.removeEventListener("click", this.onClick)
}
componentWillReceiveProps(nextProps) {
let hasChanged = (k) => !isEqual(nextProps[k], this.props[k])
const editor = this.editor
// Change the debounce value/func
if(this.props.debounce !== nextProps.debounce) {
if(this.debouncedOnChange.flush)
this.debouncedOnChange.flush()
this.debouncedOnChange = nextProps.debounce > 0
? debounce(nextProps.onChange, nextProps.debounce)
: nextProps.onChange
}
this.updateYamlAndMarkers(nextProps)
this.updateErrorAnnotations(nextProps)
if(hasChanged("editorOptions")) {
this.syncOptionsFromState(nextProps.editorOptions)
}
if(editor && nextProps.goToLine && nextProps.goToLine.line && hasChanged("goToLine")) {
editor.gotoLine(nextProps.goToLine.line)
nextProps.editorActions.jumpToLine(null)
}
}
shouldComponentUpdate() {
return false // Never update, see: componentWillRecieveProps and this.updateYaml for where we update things.
}
render() {
// NOTE: we're manually managing the value lifecycle, outside of react render
// This will only render once.
return (
<AceEditor
mode="yaml"
theme="tomorrow_night_eighties"
value={this.props.value /* This will only load once, thereafter it'll be via updateYaml */}
onLoad={this.onLoad}
onChange={this.checkForSilentOnChange}
name="ace-editor"
width="100%"
height="100%"
tabSize={2}
fontSize={14}
useSoftTabs="true"
wrapEnabled={true}
editorProps={{
"display_indent_guides": true,
folding: "markbeginandend"
}}
setOptions={{
cursorStyle: "smooth",
wrapBehavioursEnabled: true
}}
/>
)
}
}
return Editor
}
@@ -0,0 +1,51 @@
// This code is registered as a helper, not a plugin, because its lifecycle is
// unique to the needs of the marker placement logic.
import countBy from "lodash/countBy"
import map from "lodash/map"
let removers = []
function setRemovers(arr) {
removers.forEach(fn => fn()) // remove existing anchors & gutters
removers = arr // use parent scope to persist reference
}
export function placeMarkerDecorations({editor, markers, onMarkerLineUpdate}) {
if(typeof editor !== "object") {
return
}
let markerLines = countBy(Object.values(markers), "position")
let removeFns = map(markerLines, (count, line) => {
let className = `editor-marker-${count > 8 ? "9-plus" : count}`
let s = editor.getSession()
let anchor = s.getDocument().createAnchor(+line, 0)
anchor.setPosition(+line, 0) // noClip = true
s.addGutterDecoration(+line, className)
anchor.on("change", function (e) {
var oldLine = e.old.row
var newLine = e.value.row
s.removeGutterDecoration(oldLine, className)
s.addGutterDecoration(newLine, className)
onMarkerLineUpdate([oldLine, newLine, line])
})
return function () {
// // Remove the anchor & decoration
let currentLine = +anchor.getPosition().row
editor.getSession().removeGutterDecoration(currentLine, className)
anchor.detach()
}
})
setRemovers(removeFns)
// To manually remove them
return () => setRemovers([])
}
@@ -0,0 +1,16 @@
import isFunction from "lodash/isFunction"
export default function(editor, { onGutterClick }) {
editor.on("guttermousedown", (e) => {
let editor = e.editor
let line = e.getDocumentPosition().row
let region = editor.renderer.$gutterLayer.getRegion(e)
e.stop()
if(isFunction(onGutterClick)) {
onGutterClick({ region, line })
}
})
}
@@ -0,0 +1,22 @@
// TODO: Turn these into actions, that we can override
import GutterClick from "./gutter-click"
import JsonToYaml from "./json-to-yaml"
import TabHandler from "./tab-handler"
const plugins = [
{fn: GutterClick, name: "gutterClick"},
{fn: JsonToYaml, name: "jsonToYaml"},
{fn: TabHandler, name: "tabHandler"},
]
export default function (editor, props = {}, editorPluginsToRun = [], helpers = {}) {
plugins
.filter(plugin => ~editorPluginsToRun.indexOf(plugin.name))
.forEach( plugin => {
try {
plugin.fn(editor, props, helpers)
} catch(e) {
console.error(`${plugin.name || ""} plugin error:`, e)
}
})
}
@@ -0,0 +1,53 @@
import YAML from "js-yaml"
export default function(editor) {
editor.on("paste", e => {
const originalStr = e.text
if (!isJSON(originalStr)) {
return
}
let yamlString
try {
yamlString = YAML.safeDump(YAML.safeLoad(originalStr), {
lineWidth: -1 // don't generate line folds
})
} catch (e) {
return
}
if (!confirm("Would you like to convert your JSON into YAML?")) {
return
}
// using SelectionRange instead of CursorPosition, because:
// SR.start|end === CP when there's no selection
// and it catches indentation edge cases when there is one
const padding = makePadding(editor.getSelectionRange().start.column)
// update the pasted content
e.text = yamlString
.split("\n")
.map((line, i) => i == 0 ? line : padding + line) // don't pad first line, it's already indented
.join("\n")
.replace(/\t/g, " ") // tabs -> spaces, just to be sure
})
}
function isJSON (str){
// basic test: "does this look like JSON?"
let regex = /^[ \r\n\t]*[{\[]/
return regex.test(str)
}
function makePadding(len) {
let str = ""
while(str.length < len) {
str += " "
}
return str
}
@@ -0,0 +1,7 @@
export default function(editor) {
// NOTE: react-ace has an onPaste prop.. we could refactor to that.
editor.on("paste", e => {
// replace all U+0009 tabs in pasted string with two spaces
e.text = e.text.replace(/\t/g, " ")
})
}
+23
View File
@@ -0,0 +1,23 @@
import makeEditor from "./components/editor"
import EditorContainer from "./components/editor-container"
import * as actions from "./actions"
import reducers from "./reducers"
import * as selectors from "./selectors"
import EditorSpecPlugin from "./spec"
let Editor = makeEditor({
editorPluginsToRun: ["gutterClick", "jsonToYaml", "pasteHandler"]
})
export default function () {
return [EditorSpecPlugin, {
components: { Editor, EditorContainer },
statePlugins: {
editor: {
reducers,
actions,
selectors
}
}
}]
}
+9
View File
@@ -0,0 +1,9 @@
import {
JUMP_TO_LINE
} from "./actions"
export default {
[JUMP_TO_LINE]: (state, { payload } ) =>{
return state.set("gotoLine", { line: payload })
}
}
+13
View File
@@ -0,0 +1,13 @@
import { createSelector } from "reselect"
import Im from "immutable"
const state = state => {
return state || Im.Map()
}
export const gotoLine = createSelector(
state,
state => {
return state.get("gotoLine") || null
}
)
+34
View File
@@ -0,0 +1,34 @@
const SPEC_UPDATE_ORIGIN = "spec_update_spec_origin"
// wraps updateSpec to include the "origin" parameter, defaulting to "not-editor"
// Includes a selector to get the origin, specSelectors.specOrigin
export default function EditorSpecPlugin() {
return {
statePlugins: {
spec: {
wrapActions: {
updateSpec: (ori, system) => (specStr, origin) => {
system.specActions.updateSpecOrigin(origin)
ori(specStr)
}
},
reducers: {
[SPEC_UPDATE_ORIGIN]: (state, action) => {
return state.set("specOrigin", action.payload)
}
},
selectors: {
specOrigin: (state) => state.get("specOrigin") || "not-editor"
},
actions: {
updateSpecOrigin(origin="not-editor") {
return {
payload: origin+"",
type: SPEC_UPDATE_ORIGIN,
}
}
}
}
}
}
}