123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487 |
- /*
- * <<
- * Davinci
- * ==
- * Copyright (C) 2016 - 2017 EDP
- * ==
- * 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.
- * >>
- */
- import React from 'react'
- import { FormComponentProps } from 'antd/lib/form'
- import { Form, Input, Select, Button, Row, Col, Menu, Tabs } from 'antd'
- const FormItem = Form.Item
- const Option = Select.Option
- const Search = Input.Search
- const TabPane = Tabs.TabPane
- import { uuid } from 'utils/util'
- const codeMirror = require('codemirror/lib/codemirror')
- const utilStyles = require('assets/less/util.less')
- import sqlFunctions from 'assets/sqlFunctionName/sqlFns'
- import 'codemirror/lib/codemirror.css'
- import 'assets/override/codemirror_theme.css'
- require('codemirror/mode/javascript/javascript')
- import 'codemirror/addon/lint/lint.css'
- require('codemirror/addon/hint/javascript-hint')
- require('codemirror/addon/lint/json-lint')
- require('codemirror/addon/lint/css-lint')
- require('codemirror/addon/lint/lint')
- require('codemirror/addon/lint/javascript-lint')
- require('codemirror/addon/lint/json-lint')
- require('codemirror/addon/lint/css-lint')
- const styles = require('../Widget.less')
- interface IComputedConfigFormProps {
- form: any,
- onSave: (obj: any) => void,
- onClose: () => void,
- categories: any,
- queryInfo: any
- selectedComputed: object
- }
- interface IComputedConfigFormStates {
- variableNumber: number
- categories: any
- filterFunction: string
- queryInfo: string[]
- }
- export class ComputedConfigForm extends React.Component<IComputedConfigFormProps & FormComponentProps, IComputedConfigFormStates> {
- private Editor: any
- constructor(props) {
- super(props)
- this.state = {
- variableNumber: 1,
- queryInfo: [],
- categories: [],
- filterFunction: ''
- }
- this.Editor = false
- }
- public componentWillMount() {
- const { queryInfo, categories } = this.props
- this.setState({
- queryInfo,
- categories
- })
- }
- public componentDidMount() {
- const queryTextarea = document.querySelector('#sql_tmpl')
- this.handleTmplCodeMirror(queryTextarea)
- }
- public componentWillReceiveProps(nextProps) {
- const { selectedComputed } = nextProps
- if (selectedComputed && selectedComputed.sqlExpression) {
- this.Editor.doc.setValue(selectedComputed.sqlExpression)
- } else {
- this.Editor.doc.setValue('')
- }
- }
- private customDvSqlMode = () => {
- const { categories, queryInfo } = this.props
- const highLightFields = categories.map((cate) => `${cate.name}`)
- const hightLightQuery = [...queryInfo]
- codeMirror.defineMode('dvSqlMode', (e, t) => {
- function r(t, r) {
- const o = t.next()
- if (p[o]) {
- const i = p[o](t, r, '{' === o ? hightLightQuery : highLightFields)
- if (i !== !1) {
- return i
- }
- }
- if ((1 == m.nCharCast && ("n" == o || "N" == o) || 1 == m.charsetCast && "_" == o && t.match(/[a-z][a-z0-9]*/i)) && ("'" == t.peek() || '"' == t.peek()))
- return "keyword";
- if (/^[\(\),\;\[\]\{\}]/.test(o))
- return null;
- if ("." != o) {
- if (d.test(o))
- return t.eatWhile(d),
- "operator";
- if ("{" == o && (t.match(/^( )*(d|D|t|T|ts|TS)( )*'[^']*'( )*}/) || t.match(/^( )*(d|D|t|T|ts|TS)( )*"[^"]*"( )*}/)))
- return "number";
- t.eatWhile(/^[_\w\d]/);
- const h = t.current().toLowerCase();
- return f.hasOwnProperty(h) && (t.match(/^( )+'[^']*'/) || t.match(/^( )+"[^"]*"/)) ? "number" : l.hasOwnProperty(h) ? "atom" : u.hasOwnProperty(h) ? "keyword" : c.hasOwnProperty(h) ? "builtin" : s.hasOwnProperty(h) ? "string-2" : null
- }
- }
- function o(e, t, r) {
- t.context = {
- prev: t.context,
- indent: e.indentation(),
- col: e.column(),
- type: r
- }
- }
- function i(e) {
- e.indent = e.context.indent,
- e.context = e.context.prev
- }
- const s = t.client || {}
- const l = t.atoms || {
- false: !0,
- true: !0,
- null: !0
- }
- const c = t.builtin || {}
- const u = t.keywords || {}
- const d = t.operatorChars || /^[\/\*\+\-%<>!=&|~^]/
- const m = t.support || {}
- const p = t.hooks || {}
- const f = t.dateSQL || {
- date: !0,
- time: !0,
- timestamp: !0
- }
- return {
- startState: () => {
- return {
- tokenize: r,
- context: null
- }
- },
- token: (e, t) => {
- if (e.sol() && t.context && null == t.context.align && (t.context.align = !1),
- e.eatSpace()) {
- return null
- }
- const r = t.tokenize(e, t)
- if ('comment' === r) {
- return r
- }
- t.context && null == t.context.align && (t.context.align = !0)
- const n = e.current()
- return "(" == n ? o(e, t, ")") : "[" == n ? o(e, t, "]") : t.context && t.context.type == n && i(t),
- r
- }
- }
- })
- function n(e, t, r) {
- for (var n, a = ""; null != (n = e.next());) {
- if ("]" == n && !e.eat("]"))
- return r && r.indexOf(a) < 0 ? "error" : "variable-2";
- a += n
- }
- return null
- }
- function a(e, t, r) {
- for (var n, a = ""; null != (n = e.next());) {
- if ("}" == n && !e.eat("}"))
- return r && r.indexOf(a) < 0 ? "error" : "variable-2";
- a += n
- }
- return null
- }
- function s(e) {
- for (var t = {}, r = e.split(" "), n = 0; n < r.length; ++n)
- t[r[n]] = !0;
- return t
- }
- const l = 'sum avg count max min median stddev stdev_pop stddev_samp var_pop var_samp variance percentiles percentile_cont percentile_disc'
- codeMirror.defineMIME('text/x-dv-sql-mode', {
- name: 'dvSqlMode',
- keywords: s(l),
- hooks: {
- '[': n,
- '{': a
- }
- })
- const hintList = sqlFunctions.map((fn) => fn.name).concat(categories.map((cate) => cate.name)).concat(queryInfo)
- codeMirror.registerHelper('hint', 'dvSqlMode', (cm) => {
- const cur = cm.getCursor()
- const token = cm.getTokenAt(cur)
- const start = token.start
- const end = cur.ch
- const str = token.string
- const list = hintList.filter((item) => {
- return item.indexOf(str) === 0
- })
- if (list.length) {
- return {
- list,
- from: codeMirror.Pos(cur.line, start),
- to: codeMirror.Pos(cur.line, end)
- }
- }
- })
- }
- private handleTmplCodeMirror = (queryWrapperDOM) => {
- this.customDvSqlMode()
- if (!this.Editor) {
- this.Editor = codeMirror.fromTextArea(queryWrapperDOM, {
- mode: 'text/x-dv-sql-mode',
- theme: '3024-day',
- width: '100%',
- height: '80px',
- lineNumbers: true,
- lineWrapping: true,
- autoCloseBrackets: true,
- matchBrackets: true,
- foldGutter: true,
- gutters: ['CodeMirror-lint-markers', 'CodeMirror-linenumbers', 'CodeMirror-foldgutter']
- })
- this.Editor.addKeyMap({
- 'name': 'autoInsertParentheses',
- "'('": (cm) => {
- const cur = cm.getCursor()
- cm.replaceRange('()', cur, cur, '+insert')
- cm.doc.setCursor({ line: cur.line, ch: cur.ch + 1 })
- },
- "'['": (cm) => {
- const cur = cm.getCursor()
- cm.replaceRange('[]', cur, cur, '+insert')
- cm.doc.setCursor({ line: cur.line, ch: cur.ch + 1 })
- },
- "'{'": (cm) => {
- const cur = cm.getCursor()
- cm.replaceRange('{}', cur, cur, '+insert')
- cm.doc.setCursor({ line: cur.line, ch: cur.ch + 1 })
- }
- })
- this.Editor.on('change', function (editor, change) {
- if (change.origin === '+input') {
- const text = change.text
- setTimeout(function () { editor.execCommand('autocomplete') }, 50)
- }
- })
- }
- }
- private saveComputed = () => {
- this.props.form.validateFieldsAndScroll((err, values) => {
- if (!err) {
- const { selectedComputed } = this.props
- const sqlExpression = this.Editor.doc.getValue()
- const { id, name, visualType } = this.props.form.getFieldsValue()
- console.log({ id })
- console.log({ selectedComputed })
- this.props.onSave({
- id: id ? id : uuid(8, 16),
- name,
- visualType,
- sqlExpression,
- title: 'computedField',
- from: selectedComputed ? selectedComputed['from'] : ''
- })
- this.closePanel()
- }
- })
- }
- private closePanel = () => {
- this.resetForm()
- this.props.onClose()
- }
- private resetForm = () => {
- this.Editor.doc.setValue('')
- this.props.form.resetFields()
- }
- private filterFunction = (value) => {
- this.setState({
- filterFunction: value
- })
- }
- private filterModel = (value) => {
- this.setState({
- categories: this.props.categories.filter((cate) => {
- return cate.name.indexOf(value) >= 0
- })
- })
- }
- private filterQuery = (value) => {
- this.setState({
- queryInfo: this.props.queryInfo.filter((query) => {
- return query.indexOf(value) >= 0
- })
- })
- }
- private triggerMenuItem = (params) => ({ item, key, keyPath }) => {
- const defaultValue = this.Editor.doc.getValue()
- let currentValue = ''
- switch (params) {
- case 'fn':
- currentValue = `${defaultValue}${key}()`
- break
- case 'category':
- currentValue = `${defaultValue}[${key}]`
- break
- case 'query':
- currentValue = `${defaultValue}{${key}}`
- break
- default:
- break
- }
- this.Editor.doc.setValue(currentValue)
- }
- public render() {
- const {
- form
- } = this.props
- const {
- queryInfo,
- categories,
- variableNumber,
- filterFunction
- } = this.state
- const { getFieldDecorator } = form
- const controlTypeOptions = [
- { text: '文本', value: 'string' },
- { text: '数值', value: 'number' },
- { text: '日期', value: 'date' }
- ].map((o) => (
- <Option key={o.value} value={o.value}>{o.text}</Option>
- ))
- const functions = sqlFunctions.filter((func) => {
- return func.name.indexOf(filterFunction.toUpperCase()) >= 0
- })
- const functionSelectMenu = (
- <Menu onClick={this.triggerMenuItem('fn')}>
- {functions && functions.length ? functions.map((d, index) =>
- <Menu.Item key={`${d.name}`}>
- <a target="_blank" rel="noopener noreferrer" href="javascript:;">{d.name}</a>
- </Menu.Item>
- ) : []}
- </Menu>
- )
- const modelSelectMenu = (
- <Menu onClick={this.triggerMenuItem('category')}>
- {categories && categories.length ? categories.map((d, index) =>
- <Menu.Item key={`${d.name}`}>
- <a target="_blank" rel="noopener noreferrer" href="javascript:;">{d.name}</a>
- </Menu.Item>
- ) : []}
- </Menu>
- )
- const querySelectMenu = (
- <Menu onClick={this.triggerMenuItem('query')}>
- {queryInfo && queryInfo.length ? queryInfo.map((query, index) =>
- <Menu.Item key={`${query}`}>
- <a target="_blank" rel="noopener noreferrer" href="javascript:;">{query}</a>
- </Menu.Item>
- ) : []}
- </Menu>
- )
- return (
- <div className={styles.computedConfigForm}>
- <div className={styles.body}>
- <div className={styles.fields}>
- <div className={styles.fieldName}>
- <Form>
- <Row gutter={8}>
- <Col span={12}>
- <FormItem className={utilStyles.hide}>
- {getFieldDecorator('id', {})(
- <Input />
- )}
- </FormItem>
- <FormItem>
- {getFieldDecorator('name', {
- rules: [{
- required: true,
- message: '计算字段名称不能为空'
- }]
- })(
- <Input placeholder="计算字段名称" />
- )}
- </FormItem>
- </Col>
- <Col span={12} key="visualType">
- <FormItem>
- {getFieldDecorator('visualType', {
- rules: [{
- required: true,
- message: '计算字段类型不能为空'
- }]
- })(
- <Select placeholder="计算字段类型">
- {controlTypeOptions}
- </Select>
- )}
- </FormItem>
- </Col>
- </Row>
- </Form>
- </div>
- <div className={styles.tmplWrapper}>
- <textarea id="sql_tmpl" placeholder="输入SQL语句" />
- </div>
- </div>
- <div className={styles.options}>
- <div className={styles.cardContainer}>
- <Tabs defaultActiveKey="functions" size="small" tabPosition="right">
- <TabPane tab="函数" key="functions">
- <div className={styles.menuWrapper}>
- <Search
- placeholder="请选择"
- onSearch={this.filterFunction}
- />
- {functionSelectMenu}
- </div>
- </TabPane>
- <TabPane tab="字段" key="model">
- <div className={styles.menuWrapper}>
- <Search
- placeholder="请选择"
- onSearch={this.filterModel}
- />
- {modelSelectMenu}
- </div>
- </TabPane>
- <TabPane tab="变量" key="query">
- <div className={styles.menuWrapper}>
- <Search
- placeholder="请选择"
- onSearch={this.filterQuery}
- />
- {querySelectMenu}
- </div>
- </TabPane>
- </Tabs>
- </div>
- </div>
- </div>
- <div className={styles.footer}>
- <div className={styles.foot}>
- <Button onClick={this.closePanel}>取消</Button>
- <Button type="primary" onClick={this.saveComputed}>保存</Button>
- </div>
- </div>
- </div>
- )
- }
- }
- export default Form.create<IComputedConfigFormProps & FormComponentProps>()(ComputedConfigForm)
|