@@ -1,10 +1,11 @@
|
|||||||
import * as diff from 'diff'
|
import * as diff from 'diff'
|
||||||
import * as Prism from 'prismjs'
|
import * as Prism from 'prismjs'
|
||||||
import * as React from 'react'
|
import * as React from 'react'
|
||||||
|
import { CodeBlockColors, CodeBlockColorsBraceMonokai } from './CodeBlockColors'
|
||||||
|
import { selectTextWithCtrlA } from '../../utils/handleTextSelectWithCtrlA'
|
||||||
import { Theme, withStyles } from '@material-ui/core'
|
import { Theme, withStyles } from '@material-ui/core'
|
||||||
import 'prismjs/components/prism-json'
|
import 'prismjs/components/prism-json'
|
||||||
import 'prismjs/themes/prism-tomorrow.css'
|
import 'prismjs/themes/prism-tomorrow.css'
|
||||||
import { CodeBlockColors, CodeBlockColorsBraceMonokai } from './CodeBlockColors'
|
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
previous: string
|
previous: string
|
||||||
@@ -15,6 +16,8 @@ interface Props {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class CodeDiff extends React.Component<Props, {}> {
|
class CodeDiff extends React.Component<Props, {}> {
|
||||||
|
private handleCtrlA = selectTextWithCtrlA({ targetSelector: 'pre' })
|
||||||
|
|
||||||
constructor(props: Props) {
|
constructor(props: Props) {
|
||||||
super(props)
|
super(props)
|
||||||
}
|
}
|
||||||
@@ -58,23 +61,6 @@ class CodeDiff extends React.Component<Props, {}> {
|
|||||||
return this.props.classes.noChange
|
return this.props.classes.noChange
|
||||||
}
|
}
|
||||||
|
|
||||||
private selectText = (e: React.KeyboardEvent<HTMLDivElement>) => {
|
|
||||||
const isCtrlA = (e.metaKey || e.ctrlKey) && e.key === 'a'
|
|
||||||
|
|
||||||
if (isCtrlA && window.getSelection) {
|
|
||||||
e.persist()
|
|
||||||
e.preventDefault()
|
|
||||||
e.stopPropagation()
|
|
||||||
const selection = window.getSelection()
|
|
||||||
const range = document.createRange()
|
|
||||||
range.selectNodeContents((e.target as HTMLElement).getElementsByTagName('pre')[0])
|
|
||||||
if (selection) {
|
|
||||||
selection.removeAllRanges()
|
|
||||||
selection.addRange(range)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public render() {
|
public render() {
|
||||||
const changes = diff.diffLines(this.props.previous, this.props.current)
|
const changes = diff.diffLines(this.props.previous, this.props.current)
|
||||||
const styledLines = Prism.highlight(this.props.current, Prism.languages.json, 'json').split('\n')
|
const styledLines = Prism.highlight(this.props.current, Prism.languages.json, 'json').split('\n')
|
||||||
@@ -102,7 +88,7 @@ class CodeDiff extends React.Component<Props, {}> {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<div className={this.props.classes.gutters} tabIndex={0} onKeyDown={this.selectText}>
|
<div className={this.props.classes.gutters} tabIndex={0} onKeyDown={this.handleCtrlA}>
|
||||||
<pre className={`language-json ${this.props.classes.codeBlock}`}>
|
<pre className={`language-json ${this.props.classes.codeBlock}`}>
|
||||||
{code}
|
{code}
|
||||||
</pre>
|
</pre>
|
||||||
|
|||||||
@@ -1,9 +1,10 @@
|
|||||||
import * as React from 'react'
|
import * as React from 'react'
|
||||||
|
|
||||||
import { Badge, Typography } from '@material-ui/core'
|
import { Badge, Typography } from '@material-ui/core'
|
||||||
|
import { selectTextWithCtrlA } from '../../utils/handleTextSelectWithCtrlA'
|
||||||
import { Theme, withStyles } from '@material-ui/core/styles'
|
import { Theme, withStyles } from '@material-ui/core/styles'
|
||||||
|
|
||||||
interface HistoryItem {
|
interface HistoryItem {
|
||||||
|
key: string
|
||||||
title: JSX.Element | string
|
title: JSX.Element | string
|
||||||
value: string
|
value: string
|
||||||
selected?: boolean
|
selected?: boolean
|
||||||
@@ -33,6 +34,8 @@ class HistoryDrawer extends React.Component<Props, State> {
|
|||||||
this.setState({ collapsed: !this.state.collapsed })
|
this.setState({ collapsed: !this.state.collapsed })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private handleCtrlA = selectTextWithCtrlA({targetSelector: 'pre'})
|
||||||
|
|
||||||
public renderHistory() {
|
public renderHistory() {
|
||||||
const style = (element: HistoryItem) => ({
|
const style = (element: HistoryItem) => ({
|
||||||
backgroundColor: element.selected ? this.props.theme.palette.action.selected : this.props.theme.palette.action.hover,
|
backgroundColor: element.selected ? this.props.theme.palette.action.selected : this.props.theme.palette.action.hover,
|
||||||
@@ -44,9 +47,10 @@ class HistoryDrawer extends React.Component<Props, State> {
|
|||||||
const messageStyle: React.CSSProperties = { textOverflow: 'ellipsis', whiteSpace: 'nowrap', overflow: 'hidden' }
|
const messageStyle: React.CSSProperties = { textOverflow: 'ellipsis', whiteSpace: 'nowrap', overflow: 'hidden' }
|
||||||
const elements = this.props.items.map((element, index) => (
|
const elements = this.props.items.map((element, index) => (
|
||||||
<div
|
<div
|
||||||
key={index}
|
key={element.key}
|
||||||
style={style(element)}
|
style={style(element)}
|
||||||
onClick={(event: React.MouseEvent) => this.props.onClick && this.props.onClick(index, event.target)}
|
onClick={(event: React.MouseEvent) => this.props.onClick && this.props.onClick(index, event.target)}
|
||||||
|
tabIndex={0} onKeyDown={this.handleCtrlA}
|
||||||
>
|
>
|
||||||
<div><i>{element.title}</i></div>
|
<div><i>{element.title}</i></div>
|
||||||
<div style={messageStyle}>
|
<div style={messageStyle}>
|
||||||
|
|||||||
@@ -1,12 +1,22 @@
|
|||||||
import 'react-ace'
|
|
||||||
import 'brace/mode/json'
|
|
||||||
import 'brace/mode/text'
|
|
||||||
import 'brace/mode/xml'
|
|
||||||
import 'brace/theme/monokai'
|
|
||||||
import 'brace/theme/dawn'
|
|
||||||
|
|
||||||
import * as React from 'react'
|
|
||||||
import * as q from '../../../../../backend/src/Model'
|
import * as q from '../../../../../backend/src/Model'
|
||||||
|
import * as React from 'react'
|
||||||
|
import ClearAdornment from '../../helper/ClearAdornment'
|
||||||
|
import FormatAlignLeft from '@material-ui/icons/FormatAlignLeft'
|
||||||
|
import History from '../HistoryDrawer'
|
||||||
|
import Message from './Model/Message'
|
||||||
|
import Navigation from '@material-ui/icons/Navigation'
|
||||||
|
import { AppState } from '../../../reducers'
|
||||||
|
import { bindActionCreators } from 'redux'
|
||||||
|
import { connect } from 'react-redux'
|
||||||
|
import { default as AceEditor } from 'react-ace'
|
||||||
|
import { globalActions, publishActions } from '../../../actions'
|
||||||
|
import { TopicViewModel } from '../../../model/TopicViewModel'
|
||||||
|
import 'brace/mode/json'
|
||||||
|
import 'brace/theme/dawn'
|
||||||
|
import 'brace/theme/monokai'
|
||||||
|
import 'brace/mode/xml'
|
||||||
|
import 'brace/mode/text'
|
||||||
|
import 'react-ace'
|
||||||
|
|
||||||
import {
|
import {
|
||||||
Button,
|
Button,
|
||||||
@@ -25,17 +35,7 @@ import {
|
|||||||
withTheme,
|
withTheme,
|
||||||
} from '@material-ui/core'
|
} from '@material-ui/core'
|
||||||
|
|
||||||
import { default as AceEditor } from 'react-ace'
|
const sha1 = require('sha1')
|
||||||
import { AppState } from '../../../reducers'
|
|
||||||
import History from '../History'
|
|
||||||
import Message from './Model/Message'
|
|
||||||
import Navigation from '@material-ui/icons/Navigation'
|
|
||||||
import FormatAlignLeft from '@material-ui/icons/FormatAlignLeft'
|
|
||||||
import { bindActionCreators } from 'redux'
|
|
||||||
import { connect } from 'react-redux'
|
|
||||||
import { publishActions, globalActions } from '../../../actions'
|
|
||||||
import ClearAdornment from '../../helper/ClearAdornment'
|
|
||||||
import { TopicViewModel } from '../../../model/TopicViewModel'
|
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
node?: q.TreeNode<TopicViewModel>
|
node?: q.TreeNode<TopicViewModel>
|
||||||
@@ -276,10 +276,10 @@ class Publish extends React.Component<Props, State> {
|
|||||||
|
|
||||||
private history() {
|
private history() {
|
||||||
const items = this.state.history.reverse().map(message => ({
|
const items = this.state.history.reverse().map(message => ({
|
||||||
|
key: sha1(message.topic + message.payload),
|
||||||
title: message.topic,
|
title: message.topic,
|
||||||
value: message.payload || '',
|
value: message.payload || '',
|
||||||
}))
|
}))
|
||||||
|
|
||||||
return <History items={items} onClick={this.didSelectHistoryEntry} />
|
return <History items={items} onClick={this.didSelectHistoryEntry} />
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,9 +2,11 @@ import * as q from '../../../../../backend/src/Model'
|
|||||||
import * as React from 'react'
|
import * as React from 'react'
|
||||||
import BarChart from '@material-ui/icons/BarChart'
|
import BarChart from '@material-ui/icons/BarChart'
|
||||||
import DateFormatter from '../../helper/DateFormatter'
|
import DateFormatter from '../../helper/DateFormatter'
|
||||||
import History from '../History'
|
import History from '../HistoryDrawer'
|
||||||
import { TopicViewModel } from '../../../model/TopicViewModel'
|
import { TopicViewModel } from '../../../model/TopicViewModel'
|
||||||
import { Base64Message } from '../../../../../backend/src/Model/Base64Message'
|
import { Base64Message } from '../../../../../backend/src/Model/Base64Message'
|
||||||
|
import Copy from '../../helper/Copy';
|
||||||
|
import { selectTextWithCtrlA } from '../../../utils/handleTextSelectWithCtrlA';
|
||||||
|
|
||||||
const PlotHistory = React.lazy(() => import('./PlotHistory'))
|
const PlotHistory = React.lazy(() => import('./PlotHistory'))
|
||||||
|
|
||||||
@@ -59,10 +61,16 @@ class MessageHistory extends React.Component<Props, State> {
|
|||||||
|
|
||||||
const history = node.messageHistory.toArray()
|
const history = node.messageHistory.toArray()
|
||||||
let previousMessage: q.Message | undefined = node.message
|
let previousMessage: q.Message | undefined = node.message
|
||||||
const historyElements = history.reverse().map((message) => {
|
const historyElements = history.reverse().map((message, idx) => {
|
||||||
|
const value = message.value ? Base64Message.toUnicodeString(message.value) : ''
|
||||||
const element = {
|
const element = {
|
||||||
title: <span><DateFormatter date={message.received} /> {previousMessage ? <i>(-<DateFormatter date={message.received} intervalSince={previousMessage.received} />)</i> : null}</span>,
|
value,
|
||||||
value: message.value ? Base64Message.toUnicodeString(message.value) : '',
|
key: `${message.messageNumber}-${message.received}`,
|
||||||
|
title: (<span>
|
||||||
|
<DateFormatter date={message.received} />
|
||||||
|
{previousMessage ? <i>(-<DateFormatter date={message.received} intervalSince={previousMessage.received} />)</i> : null}
|
||||||
|
<div style={{ float: 'right' }}><Copy value={value} /></div>
|
||||||
|
</span>),
|
||||||
selected: message && message === this.props.selected,
|
selected: message && message === this.props.selected,
|
||||||
}
|
}
|
||||||
previousMessage = message
|
previousMessage = message
|
||||||
|
|||||||
22
app/src/utils/handleTextSelectWithCtrlA.ts
Normal file
22
app/src/utils/handleTextSelectWithCtrlA.ts
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
export const selectTextWithCtrlA = (options?: {targetSelector: string}) => (e: React.KeyboardEvent<HTMLDivElement>) => {
|
||||||
|
const isCtrlA = (e.metaKey || e.ctrlKey) && e.key === 'a'
|
||||||
|
|
||||||
|
if (isCtrlA && window.getSelection) {
|
||||||
|
e.persist()
|
||||||
|
e.preventDefault()
|
||||||
|
e.stopPropagation()
|
||||||
|
const selection = window.getSelection()
|
||||||
|
const range = document.createRange()
|
||||||
|
const eventTarget = (e.target as HTMLElement)
|
||||||
|
const target: HTMLElement | null = (options) ? eventTarget.querySelector(options.targetSelector) : eventTarget
|
||||||
|
|
||||||
|
if (!target) {
|
||||||
|
console.error('Could not find matching target for Ctrl+A Event')
|
||||||
|
}
|
||||||
|
if (selection && target) {
|
||||||
|
range.selectNodeContents(target)
|
||||||
|
selection.removeAllRanges()
|
||||||
|
selection.addRange(range)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -4,4 +4,5 @@ export interface Message {
|
|||||||
value?: Base64Message
|
value?: Base64Message
|
||||||
length: number
|
length: number
|
||||||
received: Date
|
received: Date
|
||||||
|
messageNumber: number
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,11 +1,8 @@
|
|||||||
import { Base64Message } from './Base64Message'
|
import { Base64Message } from './Base64Message'
|
||||||
import { Edge, Tree, TreeNode } from './'
|
import { Edge, Tree, TreeNode } from './'
|
||||||
|
|
||||||
interface HasLength {
|
|
||||||
length: number
|
|
||||||
}
|
|
||||||
|
|
||||||
export abstract class TreeNodeFactory {
|
export abstract class TreeNodeFactory {
|
||||||
|
private static messageCounter = 0
|
||||||
public static insertNodeAtPosition<ViewModel>(edgeNames: Array<string>, node: TreeNode<ViewModel>) {
|
public static insertNodeAtPosition<ViewModel>(edgeNames: Array<string>, node: TreeNode<ViewModel>) {
|
||||||
let currentNode: TreeNode<ViewModel> = new Tree()
|
let currentNode: TreeNode<ViewModel> = new Tree()
|
||||||
let edge
|
let edge
|
||||||
@@ -25,7 +22,9 @@ export abstract class TreeNodeFactory {
|
|||||||
value: value || undefined,
|
value: value || undefined,
|
||||||
length: value ? value.length : 0,
|
length: value ? value.length : 0,
|
||||||
received: new Date(),
|
received: new Date(),
|
||||||
|
messageNumber: this.messageCounter,
|
||||||
})
|
})
|
||||||
|
this.messageCounter += 1
|
||||||
|
|
||||||
this.insertNodeAtPosition<ViewModel>(edgeNames, node)
|
this.insertNodeAtPosition<ViewModel>(edgeNames, node)
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user