mirror of
https://github.com/chenasraf/redar-browser.git
synced 2026-05-17 17:58:04 +00:00
Major reimplementation of object representations (no sorting/filtering)
This commit is contained in:
@@ -5,6 +5,7 @@
|
||||
"homepage": "https://redar.com/",
|
||||
"dependencies": {
|
||||
"@types/chrome": "^0.0.56",
|
||||
"@types/classnames": "^2.2.3",
|
||||
"@types/flux": "^3.1.4",
|
||||
"@types/jest": "^21.1.8",
|
||||
"@types/node": "^8.0.47",
|
||||
@@ -16,6 +17,7 @@
|
||||
"axios": "^0.17.1",
|
||||
"case-sensitive-paths-webpack-plugin": "2.1.1",
|
||||
"chalk": "1.1.3",
|
||||
"classnames": "^2.2.5",
|
||||
"copy-webpack-plugin": "^4.3.0",
|
||||
"css-loader": "0.28.4",
|
||||
"dotenv": "4.0.0",
|
||||
|
||||
@@ -9,13 +9,13 @@ class {{Name}} extends React.Component<I.IProps, I.IState> {
|
||||
}
|
||||
|
||||
render() {
|
||||
const classNames = [
|
||||
const className = [
|
||||
css.{{Name}},
|
||||
this.props.className
|
||||
].join(' ')
|
||||
|
||||
return (
|
||||
<div className={classNames}>
|
||||
<div className={className}>
|
||||
{{Name}} Component
|
||||
</div>
|
||||
)
|
||||
|
||||
@@ -4,16 +4,12 @@ import * as Immutable from 'immutable'
|
||||
|
||||
const ActionTypes = {
|
||||
UPDATE_RESPONSE: 'UPDATE_RESPONSE',
|
||||
UPDATE_TABLE: 'UPDATE_TABLE',
|
||||
UPDATE_COLUMNS: 'UPDATE_COLUMNS',
|
||||
UPDATE_VIEWKEY: 'UPDATE_VIEWKEY',
|
||||
UPDATE_REQ_TYPE: 'UPDATE_REQ_TYPE',
|
||||
}
|
||||
|
||||
const StoreKeys = {
|
||||
Response: 'RESPONSE',
|
||||
Table: 'TABLE',
|
||||
Columns: 'COLUMNS',
|
||||
ViewKey: 'VIEWKEY',
|
||||
RequestType: 'REQ_TYPE'
|
||||
}
|
||||
@@ -40,29 +36,43 @@ class AppStore extends ReduceStore<IState, IAction> {
|
||||
}
|
||||
|
||||
getInitialState() {
|
||||
return Immutable.OrderedMap<string, any>()
|
||||
const t = Immutable.OrderedMap<string, any>([
|
||||
[StoreKeys.ViewKey, localStorage.lastViewKey || '']
|
||||
])
|
||||
console.debug(t)
|
||||
return t
|
||||
}
|
||||
|
||||
reduce(state: IState, action: IAction) {
|
||||
switch (action.name) {
|
||||
case ActionTypes.UPDATE_COLUMNS:
|
||||
return state.set(StoreKeys.Columns, action.payload)
|
||||
case ActionTypes.UPDATE_TABLE:
|
||||
return state.set(StoreKeys.Table, action.payload)
|
||||
case ActionTypes.UPDATE_RESPONSE:
|
||||
let viewKey = state.get(StoreKeys.ViewKey)
|
||||
if (localStorage.lastViewKey !== '' && action.payload && !action.payload.hasOwnProperty(viewKey)) {
|
||||
viewKey = this.getViewKey(action.payload)
|
||||
state = state.set(StoreKeys.ViewKey, viewKey)
|
||||
}
|
||||
return state.set(StoreKeys.Response, action.payload)
|
||||
case ActionTypes.UPDATE_VIEWKEY:
|
||||
localStorage.lastViewKey = action.payload
|
||||
return state.set(StoreKeys.ViewKey, action.payload)
|
||||
default:
|
||||
return state
|
||||
}
|
||||
}
|
||||
|
||||
private getViewKey(data: any) {
|
||||
for (const k in data) {
|
||||
if (data.hasOwnProperty(k) && data[k] && data[k].constructor === Array) {
|
||||
return k
|
||||
}
|
||||
}
|
||||
|
||||
return ''
|
||||
}
|
||||
}
|
||||
|
||||
export function dispatch(name: TActionName | string, payload: any) {
|
||||
AppDispatcher.dispatch({
|
||||
name, payload
|
||||
})
|
||||
AppDispatcher.dispatch({ name, payload })
|
||||
}
|
||||
|
||||
function register(name: TActionName | string, callback: (payload: any) => void): string {
|
||||
0
src/common/Utils.ts
Normal file
0
src/common/Utils.ts
Normal file
@@ -1,46 +0,0 @@
|
||||
@import "../../_variables.css";
|
||||
|
||||
.Cell {
|
||||
/* */
|
||||
}
|
||||
|
||||
.pre, .pre-alt, .obj-container {
|
||||
overflow: hidden;
|
||||
font-family: monospace;
|
||||
max-width: 150px;
|
||||
background: #eee;
|
||||
margin: 4px 3px;
|
||||
border: 1px solid #aaa;
|
||||
padding: 4px;
|
||||
}
|
||||
|
||||
.pre {
|
||||
overflow-x: auto;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.pre-alt {
|
||||
display: inline-block;
|
||||
vertical-align: top;
|
||||
width: unset;
|
||||
}
|
||||
|
||||
.popover {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
}
|
||||
|
||||
.array {
|
||||
background: #ffb;
|
||||
}
|
||||
|
||||
.any {
|
||||
background: #f1d7ea;
|
||||
}
|
||||
|
||||
.string {
|
||||
background: #dfa
|
||||
}
|
||||
9
src/components/Cell/Cell.css.d.ts
vendored
9
src/components/Cell/Cell.css.d.ts
vendored
@@ -1,9 +0,0 @@
|
||||
export const Cell: string;
|
||||
export const cell: string;
|
||||
export const pre: string;
|
||||
export const preAlt: string;
|
||||
export const objContainer: string;
|
||||
export const popover: string;
|
||||
export const array: string;
|
||||
export const any: string;
|
||||
export const string: string;
|
||||
11
src/components/Cell/Cell.module.d.ts
vendored
11
src/components/Cell/Cell.module.d.ts
vendored
@@ -1,11 +0,0 @@
|
||||
export interface IProps {
|
||||
data: any
|
||||
depth?: number
|
||||
className?: string
|
||||
}
|
||||
|
||||
export interface IState {
|
||||
tableData: any
|
||||
depth: number
|
||||
dataVisible: boolean
|
||||
}
|
||||
@@ -1,104 +0,0 @@
|
||||
import * as React from 'react'
|
||||
import * as css from './Cell.css'
|
||||
import * as I from './Cell.module'
|
||||
import DataTable from 'components/DataTable/DataTable'
|
||||
import * as Repr from './Repr'
|
||||
|
||||
const MAX_DEPTH = 5
|
||||
|
||||
class Cell extends React.Component<I.IProps, I.IState> {
|
||||
constructor(props: I.IProps) {
|
||||
super(props)
|
||||
this.state = {
|
||||
tableData: props.data,
|
||||
dataVisible: false,
|
||||
depth: props.depth || 0,
|
||||
}
|
||||
}
|
||||
|
||||
private isRepresentable(obj?: any) {
|
||||
return (
|
||||
!obj ||
|
||||
obj.constructor === String ||
|
||||
obj.constructor === Boolean ||
|
||||
typeof obj === 'undefined' ||
|
||||
typeof obj === 'boolean' ||
|
||||
typeof obj === 'number'
|
||||
)
|
||||
}
|
||||
|
||||
private expandData() {
|
||||
this.setState({ dataVisible: true })
|
||||
}
|
||||
|
||||
private parse() {
|
||||
const obj = this.props.data
|
||||
const className = this.props.className || ''
|
||||
|
||||
if (this.state.depth >= MAX_DEPTH) {
|
||||
return (
|
||||
<span>…</span>
|
||||
)
|
||||
}
|
||||
|
||||
if (this.isRepresentable(obj)) {
|
||||
return (
|
||||
<Repr.JSON className={[className, css.pre, css.string].join(' ')}>
|
||||
{obj}
|
||||
</Repr.JSON>
|
||||
)
|
||||
}
|
||||
|
||||
if (obj.constructor === Array && (!obj.length || obj[0].constructor === {}.constructor)) {
|
||||
const items = obj.slice(0, 3)
|
||||
|
||||
return (
|
||||
<Repr.Any className={[className, css.preAlt, css.array].join(' ')}
|
||||
title={'Array (' + obj.length + ')'}>
|
||||
{items.map((item, i) => (
|
||||
<Cell key={'item-' + i} className={[css.objContainer].join(' ')}
|
||||
depth={this.state.depth + 1}
|
||||
data={item} />
|
||||
))}
|
||||
</Repr.Any>
|
||||
)
|
||||
}
|
||||
|
||||
const keys = Object.keys(obj)
|
||||
|
||||
return keys.length ? (
|
||||
<Repr.Any className={[className, css.preAlt].join(' ')}
|
||||
title={'Object (' + keys.length + ' keys)'}
|
||||
onClick={(e) => this.expandData()}>
|
||||
{keys.map((s, i) => (
|
||||
<Repr.Any key={'key-' + i}
|
||||
className={[css.preAlt, css.string].join(' ')}>
|
||||
{s}: {JSON.stringify(obj[s])}
|
||||
</Repr.Any>
|
||||
))}
|
||||
</Repr.Any>
|
||||
) : (
|
||||
<Repr.Any className={[className, css.preAlt].join(' ')}
|
||||
title="Empty Object" />
|
||||
)
|
||||
}
|
||||
|
||||
render() {
|
||||
const classNames = [
|
||||
css.Cell,
|
||||
].join(' ')
|
||||
|
||||
return (
|
||||
<div className={classNames}>
|
||||
{this.parse()}
|
||||
{this.state.dataVisible ? (
|
||||
<div className={css.popover}>
|
||||
{/* <DataTable data={this.state.tableData} static={true} store={null} /> */}
|
||||
</div>
|
||||
) : ''}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
export default Cell
|
||||
@@ -1,53 +0,0 @@
|
||||
import * as React from 'react'
|
||||
import * as css from './Cell.css'
|
||||
|
||||
interface ReprProps {
|
||||
className?: string
|
||||
preClassName?: string
|
||||
children?: any
|
||||
[prop: string]: any
|
||||
}
|
||||
|
||||
interface AnyProps {
|
||||
title?: string
|
||||
}
|
||||
|
||||
export function Any({children, className, preClassName, title, ...props}: ReprProps & AnyProps) {
|
||||
return (
|
||||
<div className={className} {...props}>
|
||||
{title ? <label>{title}</label> : ''}
|
||||
<div className={preClassName}>
|
||||
{children}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
function _JSON({children, className, preClassName, title, ...props}: ReprProps & AnyProps) {
|
||||
if (!children || children.constructor !== Array) {
|
||||
children = [children]
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={className} {...props}>
|
||||
{title ? <label>{title}</label> : ''}
|
||||
{children.map((child, i) => {
|
||||
let str = JSON.stringify(child, null, '\t').split(`\\`).join('')
|
||||
const maxLen = 400
|
||||
|
||||
if (str.length > maxLen) {
|
||||
str = str.slice(0, maxLen) + '\u2026'
|
||||
}
|
||||
|
||||
return (
|
||||
<div key={['child', 'i', Math.random().toString()].join('_')}
|
||||
className={preClassName}>
|
||||
{str}
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export { _JSON as JSON }
|
||||
@@ -1,80 +0,0 @@
|
||||
@import "../../_variables.css";
|
||||
|
||||
.DataTable {
|
||||
width: 100%;
|
||||
padding: 5px;
|
||||
border-spacing: 2px;
|
||||
border-collapse: separate;
|
||||
|
||||
& thead th {
|
||||
background: #acf;
|
||||
padding: 4px;
|
||||
}
|
||||
|
||||
& tbody td {
|
||||
padding: 4px;
|
||||
padding-right: 8px;
|
||||
}
|
||||
|
||||
& tr:nth-child(even) td {
|
||||
background: #eee;
|
||||
|
||||
&.col-id {
|
||||
background: #ddd;
|
||||
}
|
||||
}
|
||||
|
||||
& tbody tr:not(:last-child) td, & thead th {
|
||||
border-bottom: 1px solid #aaa;
|
||||
}
|
||||
|
||||
& tbody td, & thead th {
|
||||
vertical-align: top;
|
||||
|
||||
& label {
|
||||
display: block;
|
||||
text-transform: uppercase;
|
||||
color: #555;
|
||||
margin-bottom: 5px;
|
||||
font-weight: bold;
|
||||
font-size: 0.95em;
|
||||
font-family: $font-family;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.col-unknown {
|
||||
min-width: 80px;
|
||||
}
|
||||
|
||||
.col-name {
|
||||
color: $text-main;
|
||||
text-decoration: none;
|
||||
display: inline-block;
|
||||
margin-bottom: 5px;
|
||||
|
||||
&:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
&.sorting-by {
|
||||
font-weight: bold;
|
||||
}
|
||||
}
|
||||
|
||||
.filter-input {
|
||||
width: 100%;
|
||||
border: 1px solid #ccc;
|
||||
min-width: 80px;
|
||||
padding: 7px 4px;
|
||||
}
|
||||
|
||||
.sort-caret {
|
||||
vertical-align: middle;
|
||||
font-size: 1.3em;
|
||||
}
|
||||
|
||||
.col-id {
|
||||
min-width: 60px;
|
||||
background: #eee;
|
||||
}
|
||||
8
src/components/DataTable/DataTable.css.d.ts
vendored
8
src/components/DataTable/DataTable.css.d.ts
vendored
@@ -1,8 +0,0 @@
|
||||
export const DataTable: string;
|
||||
export const dataTable: string;
|
||||
export const colId: string;
|
||||
export const colUnknown: string;
|
||||
export const colName: string;
|
||||
export const sortingBy: string;
|
||||
export const filterInput: string;
|
||||
export const sortCaret: string;
|
||||
20
src/components/DataTable/DataTable.module.d.ts
vendored
20
src/components/DataTable/DataTable.module.d.ts
vendored
@@ -1,20 +0,0 @@
|
||||
export interface Props {
|
||||
store: any
|
||||
className?: string
|
||||
static?: boolean
|
||||
data?: any[]
|
||||
}
|
||||
|
||||
export interface Filters {
|
||||
[key: string]: string | undefined
|
||||
}
|
||||
|
||||
export type FilterFunc = (a: any, b: any) => boolean
|
||||
|
||||
export interface State {
|
||||
columns: string[]
|
||||
data: any[]
|
||||
filters: Filters
|
||||
sortKey: string
|
||||
sortDesc: boolean
|
||||
}
|
||||
@@ -1,216 +0,0 @@
|
||||
import * as React from 'react'
|
||||
import * as I from './DataTable.module'
|
||||
import * as css from './DataTable.css'
|
||||
import Dispatcher, { register, dispatch, ActionTypes, StoreKeys } from 'common/Dispatcher'
|
||||
import Cell from 'components/Cell/Cell'
|
||||
|
||||
class DataTable extends React.Component<I.Props, I.State> {
|
||||
private columns: string[]
|
||||
private listeners: string[]
|
||||
private filterFuncs: { [open: string]: I.FilterFunc } = {
|
||||
'>': (a, b) => parseFloat(a) > parseFloat(b),
|
||||
'>=': (a, b) => parseFloat(a) >= parseFloat(b),
|
||||
'<': (a, b) => parseFloat(a) < parseFloat(b),
|
||||
'<=': (a, b) => parseFloat(a) <= parseFloat(b),
|
||||
'=': (a, b) => JSON.stringify(a) === JSON.stringify(b),
|
||||
|
||||
// default: contains
|
||||
'_': (a, b) => String(a).toLowerCase().indexOf(String(b).toLowerCase()) > -1
|
||||
}
|
||||
|
||||
constructor(props: I.Props) {
|
||||
super(props)
|
||||
|
||||
const columns = props.store.get(StoreKeys.Columns, [])
|
||||
|
||||
this.state = {
|
||||
columns,
|
||||
data: props.data || props.store.get(StoreKeys.Table, []),
|
||||
filters: {},
|
||||
sortKey: columns[0],
|
||||
sortDesc: false
|
||||
}
|
||||
}
|
||||
|
||||
public componentWillMount() {
|
||||
if (this.props.static) {
|
||||
return
|
||||
}
|
||||
|
||||
this.listeners = [
|
||||
register(ActionTypes.UPDATE_COLUMNS, (columns: any) => {
|
||||
let { sortKey } = this.state
|
||||
if (columns.indexOf(sortKey) === -1) {
|
||||
sortKey = columns[0]
|
||||
}
|
||||
|
||||
this.setState({
|
||||
columns: columns || [],
|
||||
sortKey,
|
||||
})
|
||||
}),
|
||||
|
||||
register(ActionTypes.UPDATE_TABLE, (table: any) => {
|
||||
this.setState({
|
||||
data: this.parseData(table || [])
|
||||
})
|
||||
})
|
||||
]
|
||||
}
|
||||
|
||||
public componentWillUnmount() {
|
||||
if (this.props.static) {
|
||||
return
|
||||
}
|
||||
|
||||
this.listeners.forEach(l => Dispatcher.unregister(l))
|
||||
}
|
||||
|
||||
private updateData(data: any) {
|
||||
const parsed = this.parseData(data.data)
|
||||
this.setState({
|
||||
data: parsed,
|
||||
columns: data.columns
|
||||
})
|
||||
}
|
||||
|
||||
private parseData(data: any) {
|
||||
let parsed = data
|
||||
|
||||
if (!parsed) {
|
||||
return []
|
||||
}
|
||||
|
||||
parsed = parsed.map((row: any, i: number) => {
|
||||
row._id = row._id || row.id || i
|
||||
return row
|
||||
})
|
||||
|
||||
return parsed
|
||||
}
|
||||
|
||||
private getColumnRowData(row: any, i: number) {
|
||||
return this.state.columns.map((col, j) => {
|
||||
const s = (css as any)
|
||||
const camelCase = col
|
||||
.replace(/([^a-z]+[a-z0-9])/i, ($1) => $1.toUpperCase())
|
||||
.replace(/[^a-z]/i, '')
|
||||
|
||||
const asClsName = camelCase[0].toUpperCase() + camelCase.slice(1)
|
||||
|
||||
const cls = [
|
||||
s.hasOwnProperty('col' + asClsName) ? s['col' + asClsName] : css.colUnknown,
|
||||
['col', j].join('-'),
|
||||
['col', col].join('-')
|
||||
].join(' ')
|
||||
|
||||
return (
|
||||
<td key={[col, i, j].join('_')}
|
||||
className={cls}>
|
||||
<Cell data={row[col]} />
|
||||
</td>
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
private setFilter(key: string, value: string) {
|
||||
this.setState((cur: I.State) => {
|
||||
const filters = cur.filters
|
||||
filters[key] = value
|
||||
return { filters }
|
||||
})
|
||||
}
|
||||
|
||||
private filterAndSortData() {
|
||||
let { data, filters } = this.state
|
||||
|
||||
for (const key in filters) {
|
||||
if (filters.hasOwnProperty(key) && filters[key] && filters[key]!.length) {
|
||||
data = data.filter((value: any) => {
|
||||
return this.compareFilter(value[key], filters[key]!)
|
||||
})
|
||||
|
||||
if (data.length === 0) {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const skey = this.state.sortKey
|
||||
const cmp = (a, b) => a > b ? 1 : a === b ? 0 : -1
|
||||
const sort = (a, b) => !skey ? 1 : this.state.sortDesc ? cmp(b[skey], a[skey]) : cmp(a[skey], b[skey])
|
||||
data = data.sort(sort)
|
||||
|
||||
return data
|
||||
}
|
||||
|
||||
private sortBy(key: string) {
|
||||
const { sortKey, sortDesc } = this.state
|
||||
const desc = sortKey === key && !sortDesc
|
||||
this.setState({ sortKey: key, sortDesc: desc })
|
||||
}
|
||||
|
||||
private compareFilter(itemValue: any, filterStr: string) {
|
||||
const [ filterOper, filterValue ] = filterStr.split(/\b/).map(s => String(s).trim())
|
||||
const filterKey = this.filterFuncs.hasOwnProperty(filterOper) ? filterOper : '_' // default: contains
|
||||
return this.filterFuncs[filterOper](itemValue, filterValue)
|
||||
}
|
||||
|
||||
public render() {
|
||||
return (
|
||||
<div className={this.props.className || ''}>
|
||||
<table className={css.dataTable}>
|
||||
<thead>
|
||||
<tr>
|
||||
{this.state.columns.map(col => {
|
||||
const anchorCls = [
|
||||
css.colName,
|
||||
this.state.sortKey === col ? css.sortingBy : ''
|
||||
].join(' ')
|
||||
|
||||
const caretCls = [
|
||||
css.sortCaret,
|
||||
'material-icons'
|
||||
].join(' ')
|
||||
|
||||
return (
|
||||
<th key={col}>
|
||||
<a href="#"
|
||||
onClick={(e) => { e.preventDefault(); this.sortBy(col) }}
|
||||
className={anchorCls}>
|
||||
{col}
|
||||
<span className={caretCls}>
|
||||
{this.state.sortKey === col && this.state.sortDesc ?
|
||||
'keyboard_arrow_down' : 'keyboard_arrow_up'}
|
||||
</span>
|
||||
</a>
|
||||
<div>
|
||||
<input type="text"
|
||||
className={css.filterInput}
|
||||
value={this.state.filters[col] || ''}
|
||||
placeholder={`filter "${col}"`}
|
||||
onChange={(e) => {
|
||||
const { value } = e.target
|
||||
this.setFilter(col, value)
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</th>
|
||||
)
|
||||
})}
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{this.filterAndSortData().map((row, i) => (
|
||||
<tr key={`row_${i}`}>
|
||||
{this.getColumnRowData(row, i)}
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
export default DataTable
|
||||
@@ -105,11 +105,18 @@ class Header extends React.Component<I.IProps, I.IState> {
|
||||
}
|
||||
|
||||
private get requestPayload() {
|
||||
if (!this.state.requestType || !this.requestTypeMap.hasOwnProperty(this.state.requestType)) {
|
||||
if (!this.state.requestType
|
||||
|| !this.requestTypeMap.hasOwnProperty(this.state.requestType)
|
||||
|| !this.state.requestPayload.length) {
|
||||
return undefined
|
||||
}
|
||||
|
||||
return this.requestTypeMap[this.state.requestType](this.state.requestPayload)
|
||||
try {
|
||||
return this.requestTypeMap[this.state.requestType](this.state.requestPayload)
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
return "Can't parse response"
|
||||
}
|
||||
}
|
||||
|
||||
private getDataColumns(data?: any) {
|
||||
|
||||
@@ -21,16 +21,16 @@ class KeyList extends React.Component<I.IProps, I.IState> {
|
||||
public componentWillMount() {
|
||||
this.listeners = [
|
||||
register(ActionTypes.UPDATE_RESPONSE, (data: any) => {
|
||||
const viewKey = this.props.store.get(StoreKeys.ViewKey, '')
|
||||
|
||||
this.setState({
|
||||
keyList: this.keyListFromObject(data || {}),
|
||||
viewKey: this.getViewKey(data || {}),
|
||||
viewKey,
|
||||
})
|
||||
}),
|
||||
|
||||
register(ActionTypes.UPDATE_VIEWKEY, (data: any) => {
|
||||
this.setState({
|
||||
viewKey: data
|
||||
})
|
||||
this.setState({ viewKey: data })
|
||||
})
|
||||
]
|
||||
}
|
||||
@@ -43,54 +43,21 @@ class KeyList extends React.Component<I.IProps, I.IState> {
|
||||
return [''].concat(Object.keys(data))
|
||||
}
|
||||
|
||||
private getViewKey(data: any) {
|
||||
let viewKey = this.state.viewKey
|
||||
|
||||
if (viewKey && data.hasOwnProperty(viewKey)) {
|
||||
return viewKey
|
||||
}
|
||||
|
||||
for (const k in data) {
|
||||
if (data.hasOwnProperty(k) && data[k] && data[k].constructor === Array) {
|
||||
return k
|
||||
}
|
||||
}
|
||||
|
||||
return ''
|
||||
}
|
||||
|
||||
private selectItem(key: string) {
|
||||
const body = this.props.store.get(StoreKeys.Response, {})
|
||||
const tableData = key !== '' ? body[key] : [body]
|
||||
|
||||
this.setState({ viewKey: key }, () => {
|
||||
dispatch(ActionTypes.UPDATE_VIEWKEY, key)
|
||||
dispatch(ActionTypes.UPDATE_TABLE, tableData)
|
||||
dispatch(ActionTypes.UPDATE_COLUMNS, this.columnListFromRow(tableData && tableData.length ? tableData[0] : []))
|
||||
})
|
||||
}
|
||||
|
||||
private columnListFromRow(row: any) {
|
||||
const keys = ['_id']
|
||||
|
||||
Object.keys(row).sort().forEach(k => {
|
||||
k = k.toLowerCase()
|
||||
if (k !== '_id' && k !== 'id') {
|
||||
keys.push(k)
|
||||
}
|
||||
})
|
||||
|
||||
return keys
|
||||
}
|
||||
|
||||
private get keyListElements() {
|
||||
const fullData = this.props.store.get(StoreKeys.Response, {})
|
||||
const viewKey = this.state.viewKey
|
||||
|
||||
return this.state.keyList.map((key: string) => {
|
||||
const className = [
|
||||
css.item,
|
||||
key === '' || (fullData[key] && fullData[key].constructor === Array) ? css.valid : '',
|
||||
this.state.viewKey === key ? css.selected : ''
|
||||
viewKey === key ? css.selected : ''
|
||||
].join(' ')
|
||||
|
||||
return (
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
vertical-align: top;
|
||||
border: 1px solid #ccc;
|
||||
border-radius: 4px;
|
||||
min-width: 120px;
|
||||
|
||||
& > h3 {
|
||||
font-size: 0.8rem;
|
||||
|
||||
4
src/components/RObject/RObject.module.d.ts
vendored
4
src/components/RObject/RObject.module.d.ts
vendored
@@ -1,5 +1,7 @@
|
||||
export type ClassNameFunc = (index?: number, item?: string) => string
|
||||
|
||||
export interface IProps {
|
||||
className?: string
|
||||
className?: string | ClassNameFunc
|
||||
data: any
|
||||
}
|
||||
|
||||
|
||||
@@ -8,7 +8,8 @@ class RObject extends React.Component<I.IProps, I.IState> {
|
||||
this.state = {}
|
||||
}
|
||||
|
||||
private getCorrectRepr(obj: any, cls?: string) {
|
||||
private getCorrectRepr(obj: any) {
|
||||
let cls = this.props.className
|
||||
try {
|
||||
switch (typeof obj) {
|
||||
case 'number':
|
||||
@@ -19,12 +20,20 @@ class RObject extends React.Component<I.IProps, I.IState> {
|
||||
const isArray = obj && obj.constructor === Array
|
||||
|
||||
if (!keys.length) {
|
||||
return <span>{JSON.stringify(obj)}</span>
|
||||
if (typeof cls === 'function') {
|
||||
cls = cls()
|
||||
}
|
||||
return <span className={cls}>{JSON.stringify(obj)}</span>
|
||||
}
|
||||
|
||||
return keys.map((k: string) => {
|
||||
return keys.map((k: string, i: number) => {
|
||||
let tempCls = cls
|
||||
if (typeof tempCls === 'function') {
|
||||
tempCls = tempCls(i, k)
|
||||
}
|
||||
tempCls += ' ' + css.RObject
|
||||
return (
|
||||
<div className={cls} key={'obj-' + k}>
|
||||
<div className={tempCls as string} key={'obj-' + k}>
|
||||
<h3>{!isArray ? k : typeof obj}</h3>
|
||||
<RObject data={obj[k]} />
|
||||
</div>
|
||||
@@ -36,13 +45,8 @@ class RObject extends React.Component<I.IProps, I.IState> {
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
const classNames = [
|
||||
css.RObject,
|
||||
this.props.className
|
||||
].join(' ')
|
||||
|
||||
const repr = this.getCorrectRepr(this.props.data, classNames)
|
||||
render() {
|
||||
const repr = this.getCorrectRepr(this.props.data)
|
||||
return repr
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,6 +4,36 @@
|
||||
/* */
|
||||
}
|
||||
|
||||
.obj {
|
||||
// max-width: 11vw;
|
||||
.table {
|
||||
display: grid;
|
||||
grid-row-gap: 8px;
|
||||
padding: 10px;
|
||||
|
||||
& h3 {
|
||||
font-weight: bold;
|
||||
text-transform: uppercase;
|
||||
color: $text-light;
|
||||
font-size: 0.9em;
|
||||
}
|
||||
}
|
||||
|
||||
.cell {
|
||||
width: 100%;
|
||||
margin: 0 !important;
|
||||
border-left-width: 0px !important;
|
||||
border-radius: 0 !important;
|
||||
|
||||
&.row-start {
|
||||
border-radius: 5px 0 0 5px !important;
|
||||
border-left-width: 1px !important;
|
||||
margin-left: 0 !important;
|
||||
}
|
||||
|
||||
&.row-end {
|
||||
border-radius: 0 5px 5px 0 !important;
|
||||
}
|
||||
|
||||
& > h3 {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
export const ResponseRepr: string;
|
||||
export const responseRepr: string;
|
||||
export const obj: string;
|
||||
export const table: string;
|
||||
export const cell: string;
|
||||
export const rowStart: string;
|
||||
export const rowEnd: string;
|
||||
|
||||
@@ -3,6 +3,7 @@ import * as css from './ResponseRepr.css'
|
||||
import * as I from './ResponseRepr.module'
|
||||
import * as D from 'common/Dispatcher'
|
||||
import RObject from 'components/RObject/RObject'
|
||||
import * as classNames from 'classnames'
|
||||
|
||||
class ResponseRepr extends React.Component<I.IProps, I.IState> {
|
||||
private listeners: string[]
|
||||
@@ -16,6 +17,10 @@ class ResponseRepr extends React.Component<I.IProps, I.IState> {
|
||||
componentDidMount() {
|
||||
this.listeners = [
|
||||
D.register(D.ActionTypes.UPDATE_RESPONSE, (response) => {
|
||||
const viewKey = this.props.store.get(D.StoreKeys.ViewKey)
|
||||
if (viewKey && Object.keys(response).indexOf(viewKey) > -1) {
|
||||
response = response[viewKey]
|
||||
}
|
||||
this.setState({ response })
|
||||
}),
|
||||
|
||||
@@ -33,19 +38,45 @@ class ResponseRepr extends React.Component<I.IProps, I.IState> {
|
||||
|
||||
private getRObjectList() {
|
||||
const { response } = this.state
|
||||
let colAmt
|
||||
|
||||
if (response && response.constructor === Array) {
|
||||
return response.map((row, i) => {
|
||||
return (<RObject key={`row-${i}`} className={css.obj} data={row} />)
|
||||
})
|
||||
const keys = Object.keys(response[0] || {})
|
||||
colAmt = keys.length
|
||||
return (
|
||||
<div className={css.table}
|
||||
style={{gridTemplateColumns: `repeat(${colAmt}, auto)`}}>
|
||||
{keys.map((key) => <h3 key={`col-th-${key}`}>{key}</h3>)}
|
||||
{response.map((row, i) => {
|
||||
const cls = (j) => {
|
||||
return classNames(css.cell, {
|
||||
[css.rowStart]: j % colAmt === 0,
|
||||
[css.rowEnd]: j % colAmt === colAmt - 1,
|
||||
})
|
||||
}
|
||||
|
||||
return (
|
||||
<RObject className={cls}
|
||||
key={`row-${i}`}
|
||||
data={row} />
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
colAmt = Object.keys(response || {})
|
||||
|
||||
return (
|
||||
<RObject className={css.obj} data={response} />
|
||||
<div className={css.table}
|
||||
style={{gridTemplateRows: `repeat(${colAmt}, auto)`}}>
|
||||
<RObject data={response} />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
render() {
|
||||
const classNames = [
|
||||
const className = [
|
||||
css.ResponseRepr,
|
||||
this.props.className
|
||||
].join(' ')
|
||||
@@ -53,7 +84,7 @@ class ResponseRepr extends React.Component<I.IProps, I.IState> {
|
||||
const repr = this.getRObjectList()
|
||||
|
||||
return (
|
||||
<div className={classNames}>
|
||||
<div className={className}>
|
||||
{repr}
|
||||
</div>
|
||||
)
|
||||
|
||||
@@ -13,16 +13,18 @@
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
overflow: auto;
|
||||
display: grid;
|
||||
grid-template-columns: [sidebar] 20vw [main] 80vw;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.key-list {
|
||||
grid-column: sidebar;
|
||||
flex-shrink: 1;
|
||||
flex-grow: 0;
|
||||
min-width: 200px;
|
||||
max-width: 15vw;
|
||||
}
|
||||
|
||||
.table {
|
||||
grid-column: main;
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
.scrollable {
|
||||
|
||||
@@ -47,7 +47,7 @@
|
||||
"check-open-brace",
|
||||
"check-whitespace"
|
||||
],
|
||||
"quotemark": [true, "single", "jsx-double"],
|
||||
"quotemark": [true, "single", "jsx-double", "avoid-escape"],
|
||||
"radix": true,
|
||||
"semicolon": [true, "never"],
|
||||
"switch-default": true,
|
||||
|
||||
@@ -8,6 +8,10 @@
|
||||
dependencies:
|
||||
"@types/filesystem" "*"
|
||||
|
||||
"@types/classnames@^2.2.3":
|
||||
version "2.2.3"
|
||||
resolved "https://registry.yarnpkg.com/@types/classnames/-/classnames-2.2.3.tgz#3f0ff6873da793870e20a260cada55982f38a9e5"
|
||||
|
||||
"@types/events@*":
|
||||
version "1.1.0"
|
||||
resolved "https://registry.yarnpkg.com/@types/events/-/events-1.1.0.tgz#93b1be91f63c184450385272c47b6496fd028e02"
|
||||
@@ -910,6 +914,10 @@ clap@^1.0.9:
|
||||
dependencies:
|
||||
chalk "^1.1.3"
|
||||
|
||||
classnames@^2.2.5:
|
||||
version "2.2.5"
|
||||
resolved "https://registry.yarnpkg.com/classnames/-/classnames-2.2.5.tgz#fb3801d453467649ef3603c7d61a02bd129bde6d"
|
||||
|
||||
clean-css@4.1.x:
|
||||
version "4.1.9"
|
||||
resolved "https://registry.yarnpkg.com/clean-css/-/clean-css-4.1.9.tgz#35cee8ae7687a49b98034f70de00c4edd3826301"
|
||||
|
||||
Reference in New Issue
Block a user