<commit_before>/**
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF 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, { PureComponent } from 'react';
import { Tag, List, Tabs, Card, Row, Col, Badge } from 'antd';
import * as d3 from 'd3';
import moment from 'moment';
import { formatDuration } from '../../utils/time';
import DescriptionList from '../../components/DescriptionList';
import styles from './index.less';

const { TabPane } = Tabs;
const { Description } = DescriptionList;

const colors = [
  '#1890FF',
  '#F04864',
  '#2FC25B',
  '#FACC14',
  '#13C2C2',
  '#8543E0',
];
const height = 36;
const margin = 10;
const offX = 15;
const offY = 6;
const timeFormat = 'YYYY-MM-DD HH:mm:ss.SSS';
class TraceStack extends PureComponent {
  state = {
    nodes: [],
    idMap: {},
    colorMap: {},
    bap: [],
    visible: false,
    span: {},
  }
  componentWillMount() {
    const { spans } = this.props;
    let colorIndex = 0;
    spans.forEach((span) => {
      const { colorMap } = this.state;
      if (!colorMap[span.applicationCode]) {
        colorMap[span.applicationCode] = colors[colorIndex];
        colorIndex = (colorIndex < colors.length - 1) ? (colorIndex + 1) : 0;
      }
      this.buildNode(span);
    });
    const { nodes } = this.state;
    const minStartTimeNode = nodes.reduce((acc, n) => (acc.startTime > n.startTime ? n : acc));
    this.state.nodes = nodes.map(n =>
      ({ ...n, startOffset: n.startTime - minStartTimeNode.startTime }));
  }
  componentDidMount() {
    this.state.width = this.axis.parentNode.clientWidth - 50;
    this.drawAxis();
    this.displayData();
    window.addEventListener('resize', this.resize);
  }
  buildNode = (span) => {
    const { nodes, idMap } = this.state;
    const node = {};
    node.applicationCode = span.applicationCode;
    node.startTime = span.startTime;
    node.endTime = span.endTime;
    node.duration = span.endTime - span.startTime;
    node.content = span.operationName;
    node.spanSegId = this.id(span.segmentId, span.spanId);
    node.parentSpanSegId = this.findParent(span);
    node.refs = span.refs;
    node.type = span.type;
    node.peer = span.peer;
    node.component = span.component;
    node.isError = span.isError;
    node.layer = span.layer;
    node.tags = span.tags;
    node.logs = span.logs;
    nodes.push(node);
    idMap[node.spanSegId] = nodes.length - 1;
  }
  id = (...seg) => seg.join();
  findParent = (span) => {
    const { spans } = this.props;
    if (span.refs) {
      const ref = span.refs.find(_ => spans.findIndex(s =>
        this.id(_.parentSegmentId, _.parentSpanId) === this.id(s.segmentId, s.spanId)) > -1);
      if (ref) {
        return this.id(ref.parentSegmentId, ref.parentSpanId);
      }
    }
    const result = this.id(span.segmentId, span.parentSpanId);
    if (spans.findIndex(s => result === this.id(s.segmentId, s.spanId)) > -1) {
      return result;
    }
    return null;
  }
  drawAxis = () => {
    const { width } = this.state;
    const { nodes, bap } = this.state;
    const dataSet = nodes.map(node => node.startOffset + node.duration);
    const bits = d3.max(dataSet).toString().length;
    const percentScale = Math.ceil(d3.max(dataSet) / (10 ** (bits - 2)));
    const axisHeight = 20;

    const svg = d3.select(this.axis).append('svg')
      .attr('width', width)
      .attr('height', axisHeight)
      .attr('style', 'overflow: visible');

    const xScale = d3.scaleLinear()
      .domain([0, d3.max(dataSet)])
      .range([0, width]);

    const axis = d3.axisTop(xScale).ticks(4).tickSize([(height * nodes.length) + 40])
      .tickFormat(formatDuration);

    svg.append('g')
      .attr('class', styles.axis)
      .attr('transform', `translate(0, ${axisHeight})`)
      .call(axis);

    bap.push(bits);
    bap.push(percentScale);
    return bap;
  }
  displayData = () => {
    const { nodes, bap, width, colorMap } = this.state;
    const svgContainer = d3.select(this.duration).append('svg').attr('height', height * nodes.length).attr('style', 'overflow: visible');
    const positionMap = {};
    nodes.forEach((node, index) => {
      const { startOffset: startTime, duration, content,
        applicationCode, spanSegId, parentSpanSegId } = node;

      const rectWith = ((duration * width) / (bap[1] * (10 ** (bap[0] - 4)))) / 100;
      const beginX = ((startTime * width) / (bap[1] * (10 ** (bap[0] - 4)))) / 100;
      const bar = svgContainer.append('g').attr('transform', (d, i) => `translate(0,${i * height})`);

      const beginY = index * height;
      positionMap[spanSegId] = { x: beginX, y: beginY };
      const rectHeight = height - margin;
      const container = bar.append('rect').attr('spanSegId', spanSegId).attr('x', -5).attr('y', beginY - 5)
        .attr('width', width + 10)
        .attr('height', rectHeight + 10)
        .attr('class', styles.backgroudHide)
        .on('mouseover', () => { this.selectTimeline(container, true); })
        .on('mouseout', () => { this.selectTimeline(container, false); })
        .on('click', () => { this.showSpanModal(node); });

      bar.append('rect').attr('x', beginX).attr('y', beginY).attr('width', rectWith)
        .attr('height', rectHeight)
        .on('mouseover', () => { this.selectTimeline(container, true); })
        .on('mouseout', () => { this.selectTimeline(container, false); })
        .on('click', () => { this.showSpanModal(node); })
        .style('fill', colorMap[applicationCode]);

      bar.append('text')
        .attr('x', beginX + 5)
        .attr('y', (index * height) + (height / 2))
        .attr('class', styles.rectText)
        .on('mouseover', () => { this.selectTimeline(container, true); })
        .on('mouseout', () => { this.selectTimeline(container, false); })
        .on('click', () => { this.showSpanModal(node); })
        .text(`${content} ${formatDuration(duration)}`);
      if (node.isError) {
        bar.append('svg:image')
          .attr('xlink:href', 'img/icon/error.png')
          .attr('x', width + (rectHeight / 2))
          .attr('y', beginY)
          .attr('width', rectHeight)
          .attr('height', rectHeight);
      }
      if (index > 0 && positionMap[parentSpanSegId]) {
        const parentX = positionMap[parentSpanSegId].x;
        const parentY = positionMap[parentSpanSegId].y;

        const defs = svgContainer.append('defs');
        const arrowMarker = defs.append('marker')
          .attr('id', 'arrow')
          .attr('markerUnits', 'strokeWidth')
          .attr('markerWidth', 5)
          .attr('markerHeight', 5)
          .attr('viewBox', '-5 -5 10 10')
          .attr('refX', 0)
          .attr('refY', 0)
          .attr('orient', 'auto');
        arrowMarker.append('path')
          .attr('d', 'M 0,0 m -5,-5 L 5,0 L -5,5 Z')
          .attr('fill', '#8543e0').attr('opacity', 0.8);

        const parentLeftBottomX = parentX;
        const parentLeftBottomY = (Number(parentY) + Number(height)) - Number(margin);
        const selfMiddleX = beginX;
        const selfMiddleY = beginY + ((height - margin) / 2);
        if ((beginX - parentX) < 10) {
          svgContainer.append('line').attr('x1', parentLeftBottomX - offX).attr('y1', parentLeftBottomY - offY).attr('class', styles.connlines)
            .on('mouseover', () => { this.selectTimeline(container, true); })
            .on('mouseout', () => { this.selectTimeline(container, false); })
            .on('click', () => { this.showSpanModal(node); })
            .attr('x2', parentLeftBottomX)
            .attr('y2', parentLeftBottomY - offY);

          svgContainer.append('line').attr('x1', parentLeftBottomX - offX).attr('y1', parentLeftBottomY - offY).attr('class', styles.connlines)
            .on('mouseover', () => { this.selectTimeline(container, true); })
            .on('mouseout', () => { this.selectTimeline(container, false); })
            .on('click', () => { this.showSpanModal(node); })
            .attr('x2', parentLeftBottomX - offX)
            .attr('y2', selfMiddleY);

          svgContainer.append('line').attr('x1', parentLeftBottomX - offX).attr('y1', selfMiddleY).attr('class', styles.connlines)
            .on('mouseover', () => { this.selectTimeline(container, true); })
            .on('mouseout', () => { this.selectTimeline(container, false); })
            .on('click', () => { this.showSpanModal(node); })
            .attr('x2', selfMiddleX - 5)
            .attr('y2', selfMiddleY)
            .attr('marker-end', 'url(#arrow)');
        } else {
          svgContainer.append('line').attr('x1', parentLeftBottomX).attr('y1', parentLeftBottomY).attr('class', styles.connlines)
            .on('mouseover', () => { this.selectTimeline(container, true); })
            .on('mouseout', () => { this.selectTimeline(container, false); })
            .on('click', () => { this.showSpanModal(node); })
            .attr('x2', parentLeftBottomX)
            .attr('y2', selfMiddleY);

          svgContainer.append('line').attr('x1', parentLeftBottomX).attr('y1', selfMiddleY).attr('class', styles.connlines)
            .on('mouseover', () => { this.selectTimeline(container, true); })
            .on('mouseout', () => { this.selectTimeline(container, false); })
            .on('click', () => { this.showSpanModal(node); })
            .attr('x2', selfMiddleX - 5)
            .attr('y2', selfMiddleY)
            .attr('marker-end', 'url(#arrow)');
        }
      }
    });
  }
  selectTimeline = (container, isOver) => {
    container.attr('class', isOver ? styles.backgroud : styles.backgroudHide);
  }
  showSpanModal = (span) => {
    this.setState({
      ...this.state,
      visible: true,
      span,
    });
  }
  resize = () => {
    if (!this.axis) {
      return;
    }
    this.state.width = this.axis.parentNode.clientWidth - 50;
    if (!this.axis || this.state.width <= 0) {
      return;
    }
    this.axis.innerHTML = '';
    this.duration.innerHTML = '';
    this.drawAxis();
    this.displayData();
  }
  renderTitle = (items) => {
    return (
      <Row type="flex" justify="start" gutter={15}>
        {
          items.map((_) => {
            return (
              <Col key={_.name}>
                {_.name}
                <Badge count={_.count} style={{ backgroundColor: '#1890FF', marginLeft: 5 }} />
              </Col>
            );
          })
        }
      </Row>
    );
  }
  render() {
    const { colorMap, span = {} } = this.state;
    const legendButtons = Object.keys(colorMap).map(key =>
      (<Tag color={colorMap[key]} key={key}>{key}</Tag>));
    let data;
    if (span.content) {
      const base = [
        {
          title: 'span type',
          content: span.type,
        },
        {
          title: 'component',
          content: span.component,
        },
        {
          title: 'peer',
          content: span.peer,
        },
        {
          title: 'is error',
          content: `${span.isError}`,
        },
      ];
      data = base.concat(span.tags.map(t => ({ title: t.key, content: t.value })));
    }
    const logs = span.logs ? (
      <TabPane tab="Logs" key={2}>
        <List
          itemLayout="horizontal"
          dataSource={span.logs}
          renderItem={log => (
            <List.Item>
              <List.Item.Meta
                size="small"
                title={moment(log.time).format('mm:ss.SSS')}
                description={
                  <DescriptionList layout="vertical">
                    {log.data.map(_ =>
                      <Description key={_.key} term={_.key}>{_.value}</Description>)}
                  </DescriptionList>
                }
              />
            </List.Item>
          )}
        />
      </TabPane>
    ) : null;
    const relatedTraces = (span.parentSpanSegId || !span.refs) ? null : (
      <TabPane tab="Related Trace" key={3}>
        <DescriptionList layout="vertical">
          {span.refs.map(_ => <Description key={_.type} term={_.type}>{_.traceId}</Description>)}
        </DescriptionList>
      </TabPane>
    );
    return (

          { legendButtons }

 { this.duration = el; }} />

 { this.axis = el; }} />
        {data ? (
          <Card
            type="inner"
            title={span.content}
          >
            {this.renderTitle([
              {
                name: 'Start Time',
                count: `${moment(span.startTime).format(timeFormat)}`,
              },
              {
                name: 'Duration',
                count: `${formatDuration(span.duration)}`,
              },
            ])}
            <Tabs defaultActiveKey="1">
              <TabPane tab="Tags" key="1">
                <DescriptionList layout="vertical">
                  {data.map(_ =>
                    <Description key={_.title} term={_.title}>{_.content}</Description>)}
                </DescriptionList>
              </TabPane>
              {logs}
              {relatedTraces}
            </Tabs>
          </Card>
        ) : null}

    );
  }
}

export default TraceStack;
<commit_msg>Fix #165 change trace log layout<commit_after>/**
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF 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, { PureComponent } from 'react';
import { Tag, List, Tabs, Card, Row, Col, Badge } from 'antd';
import * as d3 from 'd3';
import moment from 'moment';
import { formatDuration } from '../../utils/time';
import DescriptionList from '../../components/DescriptionList';
import styles from './index.less';

const { TabPane } = Tabs;
const { Description } = DescriptionList;

const colors = [
  '#1890FF',
  '#F04864',
  '#2FC25B',
  '#FACC14',
  '#13C2C2',
  '#8543E0',
];
const height = 36;
const margin = 10;
const offX = 15;
const offY = 6;
const timeFormat = 'YYYY-MM-DD HH:mm:ss.SSS';
class TraceStack extends PureComponent {
  state = {
    nodes: [],
    idMap: {},
    colorMap: {},
    bap: [],
    visible: false,
    span: {},
  }
  componentWillMount() {
    const { spans } = this.props;
    let colorIndex = 0;
    spans.forEach((span) => {
      const { colorMap } = this.state;
      if (!colorMap[span.applicationCode]) {
        colorMap[span.applicationCode] = colors[colorIndex];
        colorIndex = (colorIndex < colors.length - 1) ? (colorIndex + 1) : 0;
      }
      this.buildNode(span);
    });
    const { nodes } = this.state;
    const minStartTimeNode = nodes.reduce((acc, n) => (acc.startTime > n.startTime ? n : acc));
    this.state.nodes = nodes.map(n =>
      ({ ...n, startOffset: n.startTime - minStartTimeNode.startTime }));
  }
  componentDidMount() {
    this.state.width = this.axis.parentNode.clientWidth - 50;
    this.drawAxis();
    this.displayData();
    window.addEventListener('resize', this.resize);
  }
  buildNode = (span) => {
    const { nodes, idMap } = this.state;
    const node = {};
    node.applicationCode = span.applicationCode;
    node.startTime = span.startTime;
    node.endTime = span.endTime;
    node.duration = span.endTime - span.startTime;
    node.content = span.operationName;
    node.spanSegId = this.id(span.segmentId, span.spanId);
    node.parentSpanSegId = this.findParent(span);
    node.refs = span.refs;
    node.type = span.type;
    node.peer = span.peer;
    node.component = span.component;
    node.isError = span.isError;
    node.layer = span.layer;
    node.tags = span.tags;
    node.logs = span.logs;
    nodes.push(node);
    idMap[node.spanSegId] = nodes.length - 1;
  }
  id = (...seg) => seg.join();
  findParent = (span) => {
    const { spans } = this.props;
    if (span.refs) {
      const ref = span.refs.find(_ => spans.findIndex(s =>
        this.id(_.parentSegmentId, _.parentSpanId) === this.id(s.segmentId, s.spanId)) > -1);
      if (ref) {
        return this.id(ref.parentSegmentId, ref.parentSpanId);
      }
    }
    const result = this.id(span.segmentId, span.parentSpanId);
    if (spans.findIndex(s => result === this.id(s.segmentId, s.spanId)) > -1) {
      return result;
    }
    return null;
  }
  drawAxis = () => {
    const { width } = this.state;
    const { nodes, bap } = this.state;
    const dataSet = nodes.map(node => node.startOffset + node.duration);
    const bits = d3.max(dataSet).toString().length;
    const percentScale = Math.ceil(d3.max(dataSet) / (10 ** (bits - 2)));
    const axisHeight = 20;

    const svg = d3.select(this.axis).append('svg')
      .attr('width', width)
      .attr('height', axisHeight)
      .attr('style', 'overflow: visible');

    const xScale = d3.scaleLinear()
      .domain([0, d3.max(dataSet)])
      .range([0, width]);

    const axis = d3.axisTop(xScale).ticks(4).tickSize([(height * nodes.length) + 40])
      .tickFormat(formatDuration);

    svg.append('g')
      .attr('class', styles.axis)
      .attr('transform', `translate(0, ${axisHeight})`)
      .call(axis);

    bap.push(bits);
    bap.push(percentScale);
    return bap;
  }
  displayData = () => {
    const { nodes, bap, width, colorMap } = this.state;
    const svgContainer = d3.select(this.duration).append('svg').attr('height', height * nodes.length).attr('style', 'overflow: visible');
    const positionMap = {};
    nodes.forEach((node, index) => {
      const { startOffset: startTime, duration, content,
        applicationCode, spanSegId, parentSpanSegId } = node;

      const rectWith = ((duration * width) / (bap[1] * (10 ** (bap[0] - 4)))) / 100;
      const beginX = ((startTime * width) / (bap[1] * (10 ** (bap[0] - 4)))) / 100;
      const bar = svgContainer.append('g').attr('transform', (d, i) => `translate(0,${i * height})`);

      const beginY = index * height;
      positionMap[spanSegId] = { x: beginX, y: beginY };
      const rectHeight = height - margin;
      const container = bar.append('rect').attr('spanSegId', spanSegId).attr('x', -5).attr('y', beginY - 5)
        .attr('width', width + 10)
        .attr('height', rectHeight + 10)
        .attr('class', styles.backgroudHide)
        .on('mouseover', () => { this.selectTimeline(container, true); })
        .on('mouseout', () => { this.selectTimeline(container, false); })
        .on('click', () => { this.showSpanModal(node); });

      bar.append('rect').attr('x', beginX).attr('y', beginY).attr('width', rectWith)
        .attr('height', rectHeight)
        .on('mouseover', () => { this.selectTimeline(container, true); })
        .on('mouseout', () => { this.selectTimeline(container, false); })
        .on('click', () => { this.showSpanModal(node); })
        .style('fill', colorMap[applicationCode]);

      bar.append('text')
        .attr('x', beginX + 5)
        .attr('y', (index * height) + (height / 2))
        .attr('class', styles.rectText)
        .on('mouseover', () => { this.selectTimeline(container, true); })
        .on('mouseout', () => { this.selectTimeline(container, false); })
        .on('click', () => { this.showSpanModal(node); })
        .text(`${content} ${formatDuration(duration)}`);
      if (node.isError) {
        bar.append('svg:image')
          .attr('xlink:href', 'img/icon/error.png')
          .attr('x', width + (rectHeight / 2))
          .attr('y', beginY)
          .attr('width', rectHeight)
          .attr('height', rectHeight);
      }
      if (index > 0 && positionMap[parentSpanSegId]) {
        const parentX = positionMap[parentSpanSegId].x;
        const parentY = positionMap[parentSpanSegId].y;

        const defs = svgContainer.append('defs');
        const arrowMarker = defs.append('marker')
          .attr('id', 'arrow')
          .attr('markerUnits', 'strokeWidth')
          .attr('markerWidth', 5)
          .attr('markerHeight', 5)
          .attr('viewBox', '-5 -5 10 10')
          .attr('refX', 0)
          .attr('refY', 0)
          .attr('orient', 'auto');
        arrowMarker.append('path')
          .attr('d', 'M 0,0 m -5,-5 L 5,0 L -5,5 Z')
          .attr('fill', '#8543e0').attr('opacity', 0.8);

        const parentLeftBottomX = parentX;
        const parentLeftBottomY = (Number(parentY) + Number(height)) - Number(margin);
        const selfMiddleX = beginX;
        const selfMiddleY = beginY + ((height - margin) / 2);
        if ((beginX - parentX) < 10) {
          svgContainer.append('line').attr('x1', parentLeftBottomX - offX).attr('y1', parentLeftBottomY - offY).attr('class', styles.connlines)
            .on('mouseover', () => { this.selectTimeline(container, true); })
            .on('mouseout', () => { this.selectTimeline(container, false); })
            .on('click', () => { this.showSpanModal(node); })
            .attr('x2', parentLeftBottomX)
            .attr('y2', parentLeftBottomY - offY);

          svgContainer.append('line').attr('x1', parentLeftBottomX - offX).attr('y1', parentLeftBottomY - offY).attr('class', styles.connlines)
            .on('mouseover', () => { this.selectTimeline(container, true); })
            .on('mouseout', () => { this.selectTimeline(container, false); })
            .on('click', () => { this.showSpanModal(node); })
            .attr('x2', parentLeftBottomX - offX)
            .attr('y2', selfMiddleY);

          svgContainer.append('line').attr('x1', parentLeftBottomX - offX).attr('y1', selfMiddleY).attr('class', styles.connlines)
            .on('mouseover', () => { this.selectTimeline(container, true); })
            .on('mouseout', () => { this.selectTimeline(container, false); })
            .on('click', () => { this.showSpanModal(node); })
            .attr('x2', selfMiddleX - 5)
            .attr('y2', selfMiddleY)
            .attr('marker-end', 'url(#arrow)');
        } else {
          svgContainer.append('line').attr('x1', parentLeftBottomX).attr('y1', parentLeftBottomY).attr('class', styles.connlines)
            .on('mouseover', () => { this.selectTimeline(container, true); })
            .on('mouseout', () => { this.selectTimeline(container, false); })
            .on('click', () => { this.showSpanModal(node); })
            .attr('x2', parentLeftBottomX)
            .attr('y2', selfMiddleY);

          svgContainer.append('line').attr('x1', parentLeftBottomX).attr('y1', selfMiddleY).attr('class', styles.connlines)
            .on('mouseover', () => { this.selectTimeline(container, true); })
            .on('mouseout', () => { this.selectTimeline(container, false); })
            .on('click', () => { this.showSpanModal(node); })
            .attr('x2', selfMiddleX - 5)
            .attr('y2', selfMiddleY)
            .attr('marker-end', 'url(#arrow)');
        }
      }
    });
  }
  selectTimeline = (container, isOver) => {
    container.attr('class', isOver ? styles.backgroud : styles.backgroudHide);
  }
  showSpanModal = (span) => {
    this.setState({
      ...this.state,
      visible: true,
      span,
    });
  }
  resize = () => {
    if (!this.axis) {
      return;
    }
    this.state.width = this.axis.parentNode.clientWidth - 50;
    if (!this.axis || this.state.width <= 0) {
      return;
    }
    this.axis.innerHTML = '';
    this.duration.innerHTML = '';
    this.drawAxis();
    this.displayData();
  }
  renderTitle = (items) => {
    return (
      <Row type="flex" justify="start" gutter={15}>
        {
          items.map((_) => {
            return (
              <Col key={_.name}>
                {_.name}
                <Badge count={_.count} style={{ backgroundColor: '#1890FF', marginLeft: 5 }} />
              </Col>
            );
          })
        }
      </Row>
    );
  }
  render() {
    const { colorMap, span = {} } = this.state;
    const legendButtons = Object.keys(colorMap).map(key =>
      (<Tag color={colorMap[key]} key={key}>{key}</Tag>));
    let data;
    if (span.content) {
      const base = [
        {
          title: 'span type',
          content: span.type,
        },
        {
          title: 'component',
          content: span.component,
        },
        {
          title: 'peer',
          content: span.peer,
        },
        {
          title: 'is error',
          content: `${span.isError}`,
        },
      ];
      data = base.concat(span.tags.map(t => ({ title: t.key, content: t.value })));
    }
    const logs = span.logs ? (
      <TabPane tab="Logs" key={2}>
        <List
          itemLayout="horizontal"
          dataSource={span.logs}
          renderItem={log => (
            <List.Item>
              <List.Item.Meta
                size="small"
                title={moment(log.time).format('mm:ss.SSS')}
                description={
                  <DescriptionList layout="vertical" col={1}>
                    {log.data.map(_ =>
                      <Description key={_.key} term={_.key}>{_.value}</Description>)}
                  </DescriptionList>
                }
              />
            </List.Item>
          )}
        />
      </TabPane>
    ) : null;
    const relatedTraces = (span.parentSpanSegId || !span.refs) ? null : (
      <TabPane tab="Related Trace" key={3}>
        <DescriptionList layout="vertical">
          {span.refs.map(_ => <Description key={_.type} term={_.type}>{_.traceId}</Description>)}
        </DescriptionList>
      </TabPane>
    );
    return (

          { legendButtons }

 { this.duration = el; }} />

 { this.axis = el; }} />
        {data ? (
          <Card
            type="inner"
            title={span.content}
          >
            {this.renderTitle([
              {
                name: 'Start Time',
                count: `${moment(span.startTime).format(timeFormat)}`,
              },
              {
                name: 'Duration',
                count: `${formatDuration(span.duration)}`,
              },
            ])}
            <Tabs defaultActiveKey="1">
              <TabPane tab="Tags" key="1">
                <DescriptionList layout="vertical">
                  {data.map(_ =>
                    <Description key={_.title} term={_.title}>{_.content}</Description>)}
                </DescriptionList>
              </TabPane>
              {logs}
              {relatedTraces}
            </Tabs>
          </Card>
        ) : null}

    );
  }
}

export default TraceStack;
<|endoftext|>


<commit_before>/*!
 * 弹层
 * tommyshao <jinhong.shao@frontpay.cn>
 * Reference bootstrap.modal.js
 * API:
 *      // 监听打开
 *      $(element).on('show.ui.modal', function(e, obj){});
 *      $(element).on('shown.ui.modal', function(e, obj){});
 *
 *      // 监听关闭
 *      $(element).on('hide.ui.modal', function(){});
 *      $(element).on('hidden.ui.modal', function(){});

        // 绑定一个弹窗
 *      $(element).modal();
 *
 *      // 自定义弹窗
 *      $(id).modal({title: '提示', content: 'abc'});
        $(id).modal('setContent', 'cdfg');

        // loading
 */

+(function($) {
    'use strict';

    // 构造函数
    // ===============
    var Modal = function(element, options) {
        this.$el = $(element);
        this.options = options;
        this.$body = $(document.body);
        //this.$container = this.$el.parents('.modal-background');
        this.$dialog = this.$el.find('.modal-wrap');
        this.$backdrop = null;
        this.isShown = null;
        this.originalBodyPad = null;
        this.scrollbarWidth = 0;

        if(this.options.remote) {
            this.$el
                .find('.modal-body')
                .load(this.options.remote, $.proxy(function() {
                    this.$element.trigger('loaded.ui.modal');
                }, this))
        }
    };

    Modal.VERSION = '1.0.0';

    // 动画过渡时间
    Modal.TRANSITION_DURATION = 150;

    Modal.DEFAULTS = {
        backdrop: true,
        keyboard: true,
        show: true
    };

    // 自定义弹框模板
    Modal.TEMPLATE =[
                        '
',
                            '
',
                                '
',
                                    '
',
                                        '
',
                                            '{{title}}',
                                            '<button class="modal-close">',
                                            '',
                                            '</button>',
                                        '
',
                                        '
',
                                            '{{content}}',
                                        '
',
                                    '
',
                                '
',
                            '
',
                        '
'
                    ].join('');

    Modal.CreateModal = function(option) {
        var $body = $(document.body), element;
        if(option && typeof option == "object") {
            element = Modal.TEMPLATE.replace(/{{(\w*)}}/gi, function(match, key){
                if(option[key] && typeof option[key] == "string") return /^(\.|#)\w*/gi.test(option[key]) ? $(option[key]).html() : option[key];
                // dom元素
                if(option[key] && option[key].length && option.length > 0) return option[key].html();
            });

            element = $(element).hide().appendTo($body)
        }
        return element;
    };

    // 打开
    Modal.prototype.toggle = function (_relatedTarget) {
      return this.isShown ? this.hide() : this.show(_relatedTarget);
    };

    // 显示
    Modal.prototype.show = function(_relatedTarget) {
        var that = this;
        var e = $.Event('show.ui.modal', {relatedTarget: _relatedTarget});

        //this.$el.trigger(e);

        if(this.isShown || e.isDefaultPrevented()) return;

        this.isShown = true;
        this.checkScrollbar();
        this.setScrollbar();
        this.$body.addClass('modal-open');

        this.escape();
        this.resize();

        this.$el.on('click.dismiss.ui.modal', '[data-dismiss="modal"],.modal-close', $.proxy(this.hide, this));

        var transition = $.support.transition && that.$el.hasClass('fade');
        that.$el.show().scrollTop(0);

        that.adjustDialog();

        if(transition) {
            that.$el[0].offsetWidth;
        }

        that.enforceFocus();

        //var e = $.Event('shown.ui.modal', {relatedTarget: _relatedTarget});

        if(transition) {
            that.$el.addClass('in').attr('aria-hidden', false);
            that.$dialog.one('uiTransitionEnd', function(){
                that.$el.trigger('focus').trigger(e)
            }).emulateTransitionEnd(Modal.TRANSITION_DURATION)
        } else{
            that.$el.hide().addClass('in').attr('aria-hidden', false).fadeIn(Modal.TRANSITION_DURATION, function(){
                $(this).trigger('focus').trigger(e);
            }).attr('aria-hidden', false);
        }
    };

    // 隐藏
    Modal.prototype.hide = function(e){
        if(e) e.preventDefault();

        var $this = this;

        if(!this.$el.is(':visible') && !this.isShown) return;

        this.isShown = false;

        this.escape();
        this.resize();

        $(document).off('focusin.ui.modal');

        this.$el.removeClass('in').attr('aria-hidden', true).off('click.dismiss.ui.modal').off('mouseup.dismiss.ui.modal');

        this.$dialog.off('mousedown.dismiss.ui.modal');

        console.log('here');

        $.support.transition && this.$el.hasClass('fade') ?
            this.$el.one('uiTransitionEnd', $.proxy(this.hideModal, this)).emulateTransitionEnd(Modal.TRANSITION_DURATION)
            : (function(){
                $this.$el.fadeOut(Modal.TRANSITION_DURATION, function(){
                    $this.hideModal()
                })
              })();
    };

    Modal.prototype.close = function(id) {
        $(id).data('ui.modal').hide();
    };

    // esc关闭
    Modal.prototype.escape = function(){
        if(this.isShown && this.options.keyboard) {
            this.$el.on('keydown.dismiss.ui.modal', $.proxy(function(e) {
                e.which == 27 && this.hide()
            }, this))
        } else if(!this.isShown) {
            this.$el.off('keydown.dismiss.ui.modal');
        }
    };

    Modal.prototype.hideModal = function() {
        var that = this;
        var e = $.Event('hide.ui.modal', {relatedTarget: that.$el});
        that.$el.hide();
        that.$body.removeClass('modal-open');
        that.resetAdjustments();
        that.resetScrollbar();
        that.$el.trigger(e);
    };
    // 重新缩放
    Modal.prototype.resize = function(){};
    // 调整弹框位置
    Modal.prototype.handleUpdate = function() {
        this.adjustDialog();
    };
    Modal.prototype.adjustDialog = function(){
        return;
        var modalIsOverflowing = this.$el[0].scrollHeight > document.documentElement.clientHeight;

        this.$el.css({
            paddingLeft: !this.bodyIsOverflowing && modalIsOverflowing ? this.scrollbarWidth : '',
            paddingRight: this.bodyIsOverflowing && !modalIsOverflowing ? this.scrollbarWidth: ''
        })
    };
    Modal.prototype.resetAdjustments = function(){
        this.$el.css({
            paddingLeft: '',
            paddingRight: ''
        })
    };
    // 获取焦点
    Modal.prototype.enforceFocus = function(){
        $(document)
            .off('focusin.ui.modal')
            .on('focusin.ui.modal', $.proxy(function(e) {
                if(this.$el[0] !== e.target && !this.$el.has(e.target).length) {
                    this.$el.trigger('focus');
                }
            }, this))
    };

    // 滚动条
    Modal.prototype.checkScrollbar = function () {
        var fullWindowWidth = window.innerWidth; //$(window).width();
        if(!fullWindowWidth) {
            var documentElementRect = document.documentElement.getBoundingClientRect();
            fullWindowWidth = documentElementRect.right - Math.abs(documentElementRect.left)
        }

        this.bodyIsOverflowing = document.body.clientWidth < fullWindowWidth;
        this.scrollbarWidth = this.measureScrollbar();
    };

    Modal.prototype.setScrollbar = function() {
        var bodyPad = parseInt((this.$body.css('padding-right') || 0), 10);
        this.originalBodyPad = document.body.style.paddingRight || '';
        if (this.bodyIsOverflowing) this.$body.css('padding-right', bodyPad + this.scrollbarWidth);
    };

    Modal.prototype.resetScrollbar = function () {
        this.$body.css('padding-right', this.originalBodyPad);
    };

    Modal.prototype.measureScrollbar = function() {
        var scrollDiv = document.createElement('div');
        scrollDiv.className = 'modal-scrollbar-measure';
        this.$body.append(scrollDiv);
        var scrollbarWidth = scrollDiv.offsetWidth - scrollDiv.clientWidth;
        this.$body[0].removeChild(scrollDiv);
        return scrollbarWidth;
    };

    // 扩展方法
    Modal.prototype.setContent = function(content) {
        var $content = this.$el.find('.modal-body');
        $content.html(content || '');
    };

    // 插件定义
    //======================
    function Plugin(option, _relatedTarget) {
        if(!$(this).length && option && /^#(\w*)/gi.test($(this).selector)) { // js创建
            var data, fnName; //option = typeof option === 'string' ? {title: '\u6807\u9898', content: ''} : option;  //, uid = Math.random().toString(36).substring(2);
            //option.id = 'modal-'+uid;
            if(typeof option === 'string') {
                fnName = option;
                option = {title: '\u6807\u9898', content: ''};
            }
            option.mid = $(this).selector.replace(/^#/g, '');
            var $this = Modal.CreateModal(option);
            $this.data('mid', option.mid);
            var options = $.extend({}, Modal.DEFAULTS, typeof option== 'object' && option);
            $this.data('ui.modal', (data = new Modal($this, options)));

            if(fnName && typeof data[fnName] === 'function') {
                data[fnName](_relatedTarget);
            }

            if(option['callback']) option['callback'].call($this);

            //return data.show(_relatedTarget);
            data.show(_relatedTarget);
            return $(this);
        } else { // 模板
            return $(this).each(function () {
                var $this = $(this);
                var data = $this.data('ui.modal');
                var options = $.extend({}, Modal.DEFAULTS, $this.data(), typeof option== 'object' && option);
                if(!data) $this.data('ui.modal', (data = new Modal(this, options)));
                if(typeof option == 'string') data[option](_relatedTarget);
                else if(options.show) data.show(_relatedTarget);
            })
        }
    }

    // jQuery 插件扩展
    $.fn.modal = Plugin;
    $.fn.modal.Constructor = Modal;

    // ajaxLoading
    $.fn.showLoading = function(title, content){
        var $this, title = title || '处理中...', content = content || '请不要关闭浏览器，系统正在处理';
        if($(this).length) {
            if(title) $(this).find('.modal-body h3').html(title);
            if(content ) $(this).find('.loading-content').html(content);

            $(this).modal('show');
        } else {
            var template = ['
',
                                '
',
                                    '',
                                    '
'+ title +'
',
                                    '
'+ content +'
',
                                '
',
                            '
'].join('');
            $(this).modal({title: '提示', content: template , callback: function(){
                $(this).find('.modal-close').hide();
            }});
        }
    };

    $.fn.hideLoading = function(){
        $(this).length && $(this).modal('hide');
    }

    // alert,error,success
    $.fn.modalLayer = function(option) {
        var defaults = {
            icon: 'success',
            title: '成功',
            content: '',
            buttons : [
                {
                    text: '确定',
                    href: false,
                    style: 'btn primary',
                    target: false,
                    ok: $.noop
                }
            ]
        };

        var opt = $.extend({}, defaults, option);
        var $that = $(this);

        if($that.length) {
            $that.modal('show');
        } else { // 初始化
            var template = ['
',
                                '
',
                                    '',
                                    '
'+ opt.title +'
',
                                    '
'+ opt.content +'
',
                                '
',
                            '
',
                            '
',
                            '
'];
            var btnHtml = [], btns = opt.buttons;
            for(var i = 0; i < btns.length; i++) {
                if(btns[i].href) {
                    btnHtml.push(''+ btns[i].text +'');
                } else {
                    btnHtml.push('<button type="button" class="'+ btns[i].style +'" data-index="'+ i +'">'+ btns[i].text +'</button>');
                }
            }

            template.splice(-1, 0, btnHtml.join(''));
            $that = $(this).modal({title: '提示', content: template.join(''), callback: function(obj){
                $(this).on('click', '.in-modal-btns .btn' , function(){
                    var index = $(this).data('index');
                    if(opt.buttons.length && opt.buttons[index] && opt.buttons[index]['ok']) {
                        if(opt.buttons[index]['ok'] && typeof  opt.buttons[index]['ok'] === "function") {
                            opt.buttons[index]['ok'].call(null, $(this), index);
                        }
                        $($that.selector).modal('hide')
                    }
                })
            }});
        }
    };

    // 元素插件绑定
    // ====================
    var initModal = function(){
        $(document).on('click.ui.modal', '[data-toggle="modal"]', function(e) {
            var $this = $(this);
            var href = $(this).attr('href');
            var $target = $($this.attr('data-target') || (href && href.replace(/.*(?=#[^\s]+$)/, '')));
            var option = $target.data('ui.modal') ? 'toggle' : $.extend({ remote: !/#/.test(href) && href}, $target.data(), $this.data());

            // 实例化
            Plugin.call($target, option, this);
        });

        // 全局绑定，默认不显示
        //$('.modal-background:not(".display-none")').modal();
    };

    $(document).ready(initModal);
})(jQuery);
<commit_msg>弹层对话框<commit_after>/*!
 * 弹层
 * tommyshao <jinhong.shao@frontpay.cn>
 * Reference bootstrap.modal.js
 * API:
 *      // 监听打开
 *      $(element).on('show.ui.modal', function(e, obj){});
 *      $(element).on('shown.ui.modal', function(e, obj){});
 *
 *      // 监听关闭
 *      $(element).on('hide.ui.modal', function(){});
 *      $(element).on('hidden.ui.modal', function(){});

        // 绑定一个弹窗
 *      $(element).modal();
 *
 *      // 自定义弹窗
 *      $(id).modal({title: '提示', content: 'abc'});
        $(id).modal('setContent', 'cdfg');

        // loading
 */

+(function($) {
    'use strict';

    // 构造函数
    // ===============
    var Modal = function(element, options) {
        this.$el = $(element);
        this.options = options;
        this.$body = $(document.body);
        //this.$container = this.$el.parents('.modal-background');
        this.$dialog = this.$el.find('.modal-wrap');
        this.$backdrop = null;
        this.isShown = null;
        this.originalBodyPad = null;
        this.scrollbarWidth = 0;

        if(this.options.remote) {
            this.$el
                .find('.modal-body')
                .load(this.options.remote, $.proxy(function() {
                    this.$element.trigger('loaded.ui.modal');
                }, this))
        }
    };

    Modal.VERSION = '1.0.0';

    // 动画过渡时间
    Modal.TRANSITION_DURATION = 150;

    Modal.DEFAULTS = {
        backdrop: true,
        keyboard: true,
        show: true
    };

    // 自定义弹框模板
    Modal.TEMPLATE =[
                        '
',
                            '
',
                                '
',
                                    '
',
                                        '
',
                                            '{{title}}',
                                            '<button class="modal-close">',
                                            '',
                                            '</button>',
                                        '
',
                                        '
',
                                            '{{content}}',
                                        '
',
                                    '
',
                                '
',
                            '
',
                        '
'
                    ].join('');

    Modal.CreateModal = function(option) {
        var $body = $(document.body), element;
        if(option && typeof option == "object") {
            element = Modal.TEMPLATE.replace(/{{(\w*)}}/gi, function(match, key){
                if(option[key] && typeof option[key] == "string") return /^(\.|#)\w*/gi.test(option[key]) ? $(option[key]).html() : option[key];
                // dom元素
                if(option[key] && option[key].length && option.length > 0) return option[key].html();
            });

            element = $(element).hide().appendTo($body)
        }
        return element;
    };

    // 打开
    Modal.prototype.toggle = function (_relatedTarget) {
      return this.isShown ? this.hide() : this.show(_relatedTarget);
    };

    // 显示
    Modal.prototype.show = function(_relatedTarget) {
        var that = this;
        var e = $.Event('show.ui.modal', {relatedTarget: _relatedTarget});

        //this.$el.trigger(e);

        if(this.isShown || e.isDefaultPrevented()) return;

        this.isShown = true;
        this.checkScrollbar();
        this.setScrollbar();
        this.$body.addClass('modal-open');

        this.escape();
        this.resize();

        this.$el.on('click.dismiss.ui.modal', '[data-dismiss="modal"],.modal-close', $.proxy(this.hide, this));

        var transition = $.support.transition && that.$el.hasClass('fade');
        that.$el.show().scrollTop(0);

        that.adjustDialog();

        if(transition) {
            that.$el[0].offsetWidth;
        }

        that.enforceFocus();

        //var e = $.Event('shown.ui.modal', {relatedTarget: _relatedTarget});

        if(transition) {
            that.$el.addClass('in').attr('aria-hidden', false);
            that.$dialog.one('uiTransitionEnd', function(){
                that.$el.trigger('focus').trigger(e)
            }).emulateTransitionEnd(Modal.TRANSITION_DURATION)
        } else{
            that.$el.hide().addClass('in').attr('aria-hidden', false).fadeIn(Modal.TRANSITION_DURATION, function(){
                $(this).trigger('focus').trigger(e);
            }).attr('aria-hidden', false);
        }
    };

    // 隐藏
    Modal.prototype.hide = function(e){
        if(e) e.preventDefault();

        var $this = this;

        if(!this.$el.is(':visible') && !this.isShown) return;

        this.isShown = false;

        this.escape();
        this.resize();

        $(document).off('focusin.ui.modal');

        this.$el.removeClass('in').attr('aria-hidden', true).off('click.dismiss.ui.modal').off('mouseup.dismiss.ui.modal');

        this.$dialog.off('mousedown.dismiss.ui.modal');

        console.log('here');

        $.support.transition && this.$el.hasClass('fade') ?
            this.$el.one('uiTransitionEnd', $.proxy(this.hideModal, this)).emulateTransitionEnd(Modal.TRANSITION_DURATION)
            : (function(){
                $this.$el.fadeOut(Modal.TRANSITION_DURATION, function(){
                    $this.hideModal()
                })
              })();
    };

    Modal.prototype.close = function(id) {
        $(id).data('ui.modal').hide();
    };

    // esc关闭
    Modal.prototype.escape = function(){
        if(this.isShown && this.options.keyboard) {
            this.$el.on('keydown.dismiss.ui.modal', $.proxy(function(e) {
                e.which == 27 && this.hide()
            }, this))
        } else if(!this.isShown) {
            this.$el.off('keydown.dismiss.ui.modal');
        }
    };

    Modal.prototype.hideModal = function() {
        var that = this;
        var e = $.Event('hide.ui.modal', {relatedTarget: that.$el});
        that.$el.hide();
        that.$body.removeClass('modal-open');
        that.resetAdjustments();
        that.resetScrollbar();
        that.$el.trigger(e);
    };
    // 重新缩放
    Modal.prototype.resize = function(){};
    // 调整弹框位置
    Modal.prototype.handleUpdate = function() {
        this.adjustDialog();
    };
    Modal.prototype.adjustDialog = function(){
        return;
        var modalIsOverflowing = this.$el[0].scrollHeight > document.documentElement.clientHeight;

        this.$el.css({
            paddingLeft: !this.bodyIsOverflowing && modalIsOverflowing ? this.scrollbarWidth : '',
            paddingRight: this.bodyIsOverflowing && !modalIsOverflowing ? this.scrollbarWidth: ''
        })
    };
    Modal.prototype.resetAdjustments = function(){
        this.$el.css({
            paddingLeft: '',
            paddingRight: ''
        })
    };
    // 获取焦点
    Modal.prototype.enforceFocus = function(){
        $(document)
            .off('focusin.ui.modal')
            .on('focusin.ui.modal', $.proxy(function(e) {
                if(this.$el[0] !== e.target && !this.$el.has(e.target).length) {
                    this.$el.trigger('focus');
                }
            }, this))
    };

    // 滚动条
    Modal.prototype.checkScrollbar = function () {
        var fullWindowWidth = window.innerWidth; //$(window).width();
        if(!fullWindowWidth) {
            var documentElementRect = document.documentElement.getBoundingClientRect();
            fullWindowWidth = documentElementRect.right - Math.abs(documentElementRect.left)
        }

        this.bodyIsOverflowing = document.body.clientWidth < fullWindowWidth;
        this.scrollbarWidth = this.measureScrollbar();
    };

    Modal.prototype.setScrollbar = function() {
        var bodyPad = parseInt((this.$body.css('padding-right') || 0), 10);
        this.originalBodyPad = document.body.style.paddingRight || '';
        if (this.bodyIsOverflowing) this.$body.css('padding-right', bodyPad + this.scrollbarWidth);
    };

    Modal.prototype.resetScrollbar = function () {
        this.$body.css('padding-right', this.originalBodyPad);
    };

    Modal.prototype.measureScrollbar = function() {
        var scrollDiv = document.createElement('div');
        scrollDiv.className = 'modal-scrollbar-measure';
        this.$body.append(scrollDiv);
        var scrollbarWidth = scrollDiv.offsetWidth - scrollDiv.clientWidth;
        this.$body[0].removeChild(scrollDiv);
        return scrollbarWidth;
    };

    // 扩展方法
    Modal.prototype.setContent = function(content) {
        var $content = this.$el.find('.modal-body');
        $content.html(content || '');
    };

    // 插件定义
    //======================
    function Plugin(option, _relatedTarget) {
        if(!$(this).length && option && /^#(\w*)/gi.test($(this).selector)) { // js创建
            var data, fnName; //option = typeof option === 'string' ? {title: '\u6807\u9898', content: ''} : option;  //, uid = Math.random().toString(36).substring(2);
            //option.id = 'modal-'+uid;
            if(typeof option === 'string') {
                fnName = option;
                option = {title: '\u6807\u9898', content: ''};
            }
            option.mid = $(this).selector.replace(/^#/g, '');
            var $this = Modal.CreateModal(option);
            $this.data('mid', option.mid);
            var options = $.extend({}, Modal.DEFAULTS, typeof option== 'object' && option);
            $this.data('ui.modal', (data = new Modal($this, options)));

            if(fnName && typeof data[fnName] === 'function') {
                data[fnName](_relatedTarget);
            }

            if(option['callback']) option['callback'].call($this);

            //return data.show(_relatedTarget);
            data.show(_relatedTarget);
            return $(this);
        } else { // 模板
            return $(this).each(function () {
                var $this = $(this);
                var data = $this.data('ui.modal');
                var options = $.extend({}, Modal.DEFAULTS, $this.data(), typeof option== 'object' && option);
                if(!data) $this.data('ui.modal', (data = new Modal(this, options)));
                if(typeof option == 'string') data[option](_relatedTarget);
                else if(options.show) data.show(_relatedTarget);
            })
        }
    }

    // jQuery 插件扩展
    $.fn.modal = Plugin;
    $.fn.modal.Constructor = Modal;

    // ajaxLoading
    $.fn.showLoading = function(title, content){
        var $this, title = title || '处理中...', content = content || '请不要关闭浏览器，系统正在处理';
        if($(this).length) {
            if(title) $(this).find('.modal-body h3').html(title);
            if(content ) $(this).find('.loading-content').html(content);

            $(this).modal('show');
        } else {
            var template = ['
',
                                '
',
                                    '',
                                    '
'+ title +'
',
                                    '
'+ content +'
',
                                '
',
                            '
'].join('');
            $(this).modal({title: '提示', content: template , callback: function(){
                $(this).find('.modal-close').hide();
            }});
        }
    };

    $.fn.hideLoading = function(){
        $(this).length && $(this).modal('hide');
    }

    // alert,error,success
    $.fn.modalLayer = function(option) {
        var defaults = {
            icon: 'success',
            title: '成功',
            content: '',
            buttons : [
                {
                    text: '确定',
                    href: false,
                    style: 'btn primary',
                    target: false,
                    ok: $.noop
                }
            ]
        };

        var opt = $.extend({}, defaults, option);
        var $that = $(this);

        if($that.length) {
            $that.modal('show');
        } else { // 初始化
            var template = ['
',
                                '
',
                                    '',
                                    '
'+ opt.title +'
',
                                    '
'+ opt.content +'
',
                                '
',
                            '
',
                            '
',
                            '
'];
            var btnHtml = [], btns = opt.buttons;
            for(var i = 0; i < btns.length; i++) {
                if(btns[i].href) {
                    btnHtml.push(''+ btns[i].text +'');
                } else {
                    btnHtml.push('<button type="button" class="'+ (btns[i].style || 'btn primary') +'" data-index="'+ i +'">'+ btns[i].text +'</button>');
                }
            }

            template.splice(-1, 0, btnHtml.join(''));
            $that = $(this).modal({title: '提示', content: template.join(''), callback: function(obj){
                $(this).on('click', '.in-modal-btns .btn' , function(){
                    var index = $(this).data('index');
                    if(opt.buttons.length && opt.buttons[index] && opt.buttons[index]['ok']) {
                        if(opt.buttons[index]['ok'] && typeof  opt.buttons[index]['ok'] === "function") {
                            opt.buttons[index]['ok'].call(null, $(this), index);
                        }
                        $($that.selector).modal('hide')
                    }
                })
            }});
        }
    };

    // 元素插件绑定
    // ====================
    var initModal = function(){
        $(document).on('click.ui.modal', '[data-toggle="modal"]', function(e) {
            var $this = $(this);
            var href = $(this).attr('href');
            var $target = $($this.attr('data-target') || (href && href.replace(/.*(?=#[^\s]+$)/, '')));
            var option = $target.data('ui.modal') ? 'toggle' : $.extend({ remote: !/#/.test(href) && href}, $target.data(), $this.data());

            // 实例化
            Plugin.call($target, option, this);
        });

        // 全局绑定，默认不显示
        //$('.modal-background:not(".display-none")').modal();
    };

    $(document).ready(initModal);
})(jQuery);
<|endoftext|>


<commit_before>// Copyright 2015 The Vanadium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

var mercury = require('mercury');
var insertCss = require('insert-css');

var h = mercury.h;

var css = require('./index.css');

// TODO(aghassemi) we need a separate repo. Update Url when we do.
var QUERY_STRING = '?title=' + 'Vanadium Namespace Browser: ';
var BUG_REPORT_URL = 'https://github.com/veyron/release-issues/issues/new' +
  QUERY_STRING;

var BUG_REPORT_ICON = 'bug-report';

module.exports = create;
module.exports.render = render;
module.exports.BUG_REPORT_URL = BUG_REPORT_URL;
module.exports.BUG_REPORT_ICON = BUG_REPORT_ICON;

function create() {}

function render() {
  insertCss(css);

  var reportBugAction = h('core-tooltip', {
      'label': 'Report a bug or suggest features',
      'position': 'right'
    },
    h('a', {
      'href': BUG_REPORT_URL,
      'target': '_blank'
    }, h('paper-icon-button.icon', {
      attributes: {
        'icon': BUG_REPORT_ICON
      }
    }))
  );
  return h('div.bug-reporter', reportBugAction);
}
<commit_msg>namespace_browser: updating the Submit Issues to point to the vanadium issues project.<commit_after>// Copyright 2015 The Vanadium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

var mercury = require('mercury');
var insertCss = require('insert-css');

var h = mercury.h;

var css = require('./index.css');

// TODO(aghassemi) we need a separate repo. Update Url when we do.
var QUERY_STRING = '?title=' + 'Namespace Browser: ';
var BUG_REPORT_URL = 'https://github.com/vanadium/issues/issues/new' +
  QUERY_STRING;

var BUG_REPORT_ICON = 'bug-report';

module.exports = create;
module.exports.render = render;
module.exports.BUG_REPORT_URL = BUG_REPORT_URL;
module.exports.BUG_REPORT_ICON = BUG_REPORT_ICON;

function create() {}

function render() {
  insertCss(css);

  var reportBugAction = h('core-tooltip', {
      'label': 'Report a bug or suggest features',
      'position': 'right'
    },
    h('a', {
      'href': BUG_REPORT_URL,
      'target': '_blank'
    }, h('paper-icon-button.icon', {
      attributes: {
        'icon': BUG_REPORT_ICON
      }
    }))
  );
  return h('div.bug-reporter', reportBugAction);
}
<|endoftext|>


<commit_before>/**
 * DOM Utility functions
 *
 */
betterlink_user_interface.createModule("Util.DOM", function(api, apiInternal) {
	api.requireModules( ["Util", "Version Manager"] );

	apiInternal.util.dom = {

		// Designed to be executed when adding a top-level HTML element
		// to the DOM. In addition to adding the element to the DOM, we
		// will register the element internally. This provides later
		// access if we need to detach Betterlink.
		registerAndAppend: function(target, new_node) {
			apiInternal.versions.registerDomElements(new_node);
			target.appendChild(new_node);
		},

		// Designed to be executed when inserting a top-level HTML element
		// to the DOM. In addition to adding the element to the DOM, we
		// will register the element internally. This provides later
		// access if we need to detach Betterlink.
		registerAndInsertBefore: function(newElement, referenceElement) {
			apiInternal.versions.registerDomElements(newElement);
			referenceElement.parentNode.insertBefore(newElement, referenceElement);
		},

		// Designed to be executed when inserting a top-level HTML element
		// to the DOM. In addition to adding the element to the DOM, we
		// will register the element internally. This provides later
		// access if we need to detach Betterlink.
		registerAndInsertAfter: function(newElement, referenceElement) {
			apiInternal.versions.registerDomElements(newElement);
			referenceElement.parentNode.insertBefore(newElement, referenceElement.nextSibling);
		},

		// The result of this function is that the parent should
		// have a single child node (the one supplied by param)
		addOrReplaceChild: function(parent, new_node) {
			var node_count = parent.childNodes.length;
			if(node_count === 0) {
				parent.appendChild(new_node);
			}
			else if(node_count === 1) {
				parent.replaceChild(new_node, parent.childNodes[0]);
			}
			else {
				while(parent.hasChildNodes()) {
					parent.removeChild(parent.lastChild);
				}
				parent.appendChild(new_node);
			}
		},

		// Return a new anchor element (not yet appended to the DOM)
		createAnchorElement: function(link_text, href, opt_target) {
			var a = window.document.createElement("a");
			var text_node = document.createTextNode(link_text);
			a.appendChild(text_node);
			a.href = href;
			if(opt_target) {
				a.target = opt_target;
			}

			return a;
		},

		// Return a new style element that has been appeneded to the DOM
		addCssByClass: function(css_class, css_style, opt_element) {
			var css = '.' + css_class + ' ' + css_style;
			if(opt_element) { css = opt_element + css; }

			return apiInternal.util.dom.createAndAppendStyleElement(css);
		},

		// Return a new style element that has been appeneded to the DOM
		addCssById: function(element_id, css_style) {
			var css = '#' + element_id + ' ' + css_style;

			return apiInternal.util.dom.createAndAppendStyleElement(css);
		},

		// Creates and returns a new <style> element that has been added
		// to the DOM, containing the provided CSS text.
		createAndAppendStyleElement: function(css_text) {
			var head = document.head || document.getElementsByTagName('head')[0];
			var style = document.createElement('style');

			style.type = 'text/css';
			if (style.styleSheet){
				style.styleSheet.cssText = css_text;
			}
			else {
				style.appendChild(document.createTextNode(css_text));
			}

			apiInternal.util.dom.registerAndAppend(head, style);
			return style;
		},

		// Test if the provided element has the given class name
		elementHasClass: (function() {
			// Use a IIFE to keep a memoization cache of previously-generated
			// regular expressions.
			var cache = {};
			return function(element, className) {
				var hasClass = cache[className] ||
					(cache[className] = new RegExp('\\b' + className + '\\b'));
				return hasClass.test(element.className)
			};
		})(),

		// Apply the given class name to the provided element. Don't re-apply
		// if the class name is already applied.
		applyClassToElement: function(element, className) {
			if(!apiInternal.util.dom.elementHasClass(element, className)) {
				element.className = element.className + (element.className ? ' ' : '') + className;
			}
		},

		// If the provided element has the given class name, remove the class
		removeClassFromElement: (function() {
			// Use a IIFE to keep a memoization cache of previously-generated
			// regular expressions.
			var cache = {};
			return function(element, className) {
				if(element.className) {
					var hasClass = cache[className] ||
						(cache[className] = new RegExp('\\s*' + className + '\\b'));
					element.className = element.className.replace(hasClass, '');
				}
			};
		})(),

		// Private function for getElementsByClassName for IE8- compatibility
		// Derived from Eike Send, MIT License
		// https://gist.github.com/eikes/2299607
		getElementsByClassName: function(search) {
			if(document.getElementsByClassName) {
				return document.getElementsByClassName(search);
			}

			var d = document, elements, pattern, i, results = [];

			if (d.querySelectorAll) { // IE8
				return d.querySelectorAll("." + search);
			}
			if (d.evaluate) { // IE6, IE7
				pattern = ".//*[contains(concat(' ', @class, ' '), ' " + search + " ')]";
				elements = d.evaluate(pattern, d, null, 0, null);
				while ((i = elements.iterateNext())) {
					results.push(i);
				}
			} else {
				elements = d.getElementsByTagName("*");
				pattern = new RegExp("(^|\\s)" + search + "(\\s|$)");
				for (i = 0; i < elements.length; i++) {
					if ( pattern.test(elements[i].className) ) {
						results.push(elements[i]);
					}
				}
			}
			return results;
		}
	};
});
<commit_msg>support SVG in add/has/remove class methods<commit_after>/**
 * DOM Utility functions
 *
 */
betterlink_user_interface.createModule("Util.DOM", function(api, apiInternal) {
	api.requireModules( ["Util", "Version Manager"] );

	apiInternal.util.dom = {

		// Designed to be executed when adding a top-level HTML element
		// to the DOM. In addition to adding the element to the DOM, we
		// will register the element internally. This provides later
		// access if we need to detach Betterlink.
		registerAndAppend: function(target, new_node) {
			apiInternal.versions.registerDomElements(new_node);
			target.appendChild(new_node);
		},

		// Designed to be executed when inserting a top-level HTML element
		// to the DOM. In addition to adding the element to the DOM, we
		// will register the element internally. This provides later
		// access if we need to detach Betterlink.
		registerAndInsertBefore: function(newElement, referenceElement) {
			apiInternal.versions.registerDomElements(newElement);
			referenceElement.parentNode.insertBefore(newElement, referenceElement);
		},

		// Designed to be executed when inserting a top-level HTML element
		// to the DOM. In addition to adding the element to the DOM, we
		// will register the element internally. This provides later
		// access if we need to detach Betterlink.
		registerAndInsertAfter: function(newElement, referenceElement) {
			apiInternal.versions.registerDomElements(newElement);
			referenceElement.parentNode.insertBefore(newElement, referenceElement.nextSibling);
		},

		// The result of this function is that the parent should
		// have a single child node (the one supplied by param)
		addOrReplaceChild: function(parent, new_node) {
			var node_count = parent.childNodes.length;
			if(node_count === 0) {
				parent.appendChild(new_node);
			}
			else if(node_count === 1) {
				parent.replaceChild(new_node, parent.childNodes[0]);
			}
			else {
				while(parent.hasChildNodes()) {
					parent.removeChild(parent.lastChild);
				}
				parent.appendChild(new_node);
			}
		},

		// Return a new anchor element (not yet appended to the DOM)
		createAnchorElement: function(link_text, href, opt_target) {
			var a = window.document.createElement("a");
			var text_node = document.createTextNode(link_text);
			a.appendChild(text_node);
			a.href = href;
			if(opt_target) {
				a.target = opt_target;
			}

			return a;
		},

		// Return a new style element that has been appeneded to the DOM
		addCssByClass: function(css_class, css_style, opt_element) {
			var css = '.' + css_class + ' ' + css_style;
			if(opt_element) { css = opt_element + css; }

			return apiInternal.util.dom.createAndAppendStyleElement(css);
		},

		// Return a new style element that has been appeneded to the DOM
		addCssById: function(element_id, css_style) {
			var css = '#' + element_id + ' ' + css_style;

			return apiInternal.util.dom.createAndAppendStyleElement(css);
		},

		// Creates and returns a new <style> element that has been added
		// to the DOM, containing the provided CSS text.
		createAndAppendStyleElement: function(css_text) {
			var head = document.head || document.getElementsByTagName('head')[0];
			var style = document.createElement('style');

			style.type = 'text/css';
			if (style.styleSheet){
				style.styleSheet.cssText = css_text;
			}
			else {
				style.appendChild(document.createTextNode(css_text));
			}

			apiInternal.util.dom.registerAndAppend(head, style);
			return style;
		},

		// Test if the provided element has the given class name
		elementHasClass: (function() {
			// Use a IIFE to keep a memoization cache of previously-generated
			// regular expressions.
			var cache = {};
			return function(element, className) {
				var elementClass = apiInternal.util.dom.isSvg(element) ? element.className.baseVal : element.className;
				var hasClass = cache[className] ||
					(cache[className] = new RegExp('\\b' + className + '\\b'));
				return hasClass.test(elementClass);
			};
		})(),

		// Apply the given class name to the provided element. Don't re-apply
		// if the class name is already applied.
		applyClassToElement: function(element, className) {
			if(!apiInternal.util.dom.elementHasClass(element, className)) {
				if(!apiInternal.util.dom.isSvg(element)) {
					element.className = element.className + (element.className ? ' ' : '') + className;
				}
				else {
					element.className.baseVal = element.className.baseVal + (element.className.baseVal ? ' ' : '') + className;
				}
			}
		},

		// If the provided element has the given class name, remove the class
		removeClassFromElement: (function() {
			// Use a IIFE to keep a memoization cache of previously-generated
			// regular expressions.
			var cache = {};
			return function(element, className) {
				if(element.className) {
					var hasClass = cache[className] ||
						(cache[className] = new RegExp('\\s*' + className + '\\b'));
					if(!apiInternal.util.dom.isSvg(element)) {
						element.className = element.className.replace(hasClass, '');
					}
					else {
						element.className.baseVal = element.className.baseVal.replace(hasClass, '');
					}
				}
			};
		})(),

		// Return if the provided element is an SVG element
		isSvg: function(element) {
			var svgNamespace = "http://www.w3.org/2000/svg";
			return element.namespaceURI && element.namespaceURI === svgNamespace;
		},

		// Private function for getElementsByClassName for IE8- compatibility
		// Derived from Eike Send, MIT License
		// https://gist.github.com/eikes/2299607
		getElementsByClassName: function(search) {
			if(document.getElementsByClassName) {
				return document.getElementsByClassName(search);
			}

			var d = document, elements, pattern, i, results = [];

			if (d.querySelectorAll) { // IE8
				return d.querySelectorAll("." + search);
			}
			if (d.evaluate) { // IE6, IE7
				pattern = ".//*[contains(concat(' ', @class, ' '), ' " + search + " ')]";
				elements = d.evaluate(pattern, d, null, 0, null);
				while ((i = elements.iterateNext())) {
					results.push(i);
				}
			} else {
				elements = d.getElementsByTagName("*");
				pattern = new RegExp("(^|\\s)" + search + "(\\s|$)");
				for (i = 0; i < elements.length; i++) {
					if ( pattern.test(elements[i].className) ) {
						results.push(elements[i]);
					}
				}
			}
			return results;
		}
	};
});
<|endoftext|>