Major improve of object type handling, now always shows table

This commit is contained in:
Chen Asraf
2018-01-27 05:42:47 +02:00
parent a035ed4998
commit aa53b48914
16 changed files with 207 additions and 142 deletions

View File

@@ -13,6 +13,7 @@
"default_icon": "android-chrome-192x192.png"
},
"permissions": [
"tabs",
"http://*/",
"https://*/"
]

View File

@@ -1,8 +1,11 @@
import axios, { AxiosRequestConfig, AxiosResponse, AxiosError } from 'axios'
chrome.browserAction.onClicked.addListener((activeTab) => {
const newURL = chrome.extension.getURL('index.html')
chrome.tabs.create({ url: newURL })
console.debug('registering browser button click')
chrome.browserAction.onClicked.addListener(() => {
console.debug('firing browser button callback')
const newURL = chrome.extension.getURL('index.html')
// chrome.tabs.create({ url: newURL })
window.open(newURL)
})
interface Action {

View File

@@ -2,6 +2,8 @@ import { Dispatcher } from 'flux'
import { ReduceStore } from 'flux/utils'
import * as Immutable from 'immutable'
import axios, { AxiosResponse } from 'axios'
import * as Headers from 'common/Headers'
import * as Payload from 'common/Payload'
const ActionTypes = {
SEND_REQUEST: 'SEND_REQUEST',
@@ -50,9 +52,9 @@ class AppStore extends ReduceStore<IState, IAction> {
[StoreKeys.ViewKey, localStorage.lastViewKey || ''],
[StoreKeys.RequestType, localStorage.lastRequestType || 'JSON'],
[StoreKeys.RequestMethod, localStorage.lastMethod || 'GET'],
[StoreKeys.RequestPayload, localStorage.lastPayload || ''],
[StoreKeys.RequestPayload, localStorage.lastPayload || '""'],
[StoreKeys.RequestURL, localStorage.lastURL || ''],
[StoreKeys.RequestHeaders, this.parseHeaderList(localStorage.lastHeaders || '')],
[StoreKeys.RequestHeaders, Headers.parseHeaderList(localStorage.lastHeaders || '')],
])
}
@@ -69,7 +71,7 @@ class AppStore extends ReduceStore<IState, IAction> {
localStorage.lastViewKey = action.payload
return state.set(StoreKeys.ViewKey, action.payload)
case ActionTypes.UPDATE_REQ_HEADERS:
localStorage.lastHeaders = this.stringHeaders(action.payload)
localStorage.lastHeaders = Headers.stringHeaders(action.payload)
return state.set(StoreKeys.RequestHeaders, action.payload)
case ActionTypes.UPDATE_REQ_URL:
localStorage.lastURL = action.payload
@@ -85,8 +87,9 @@ class AppStore extends ReduceStore<IState, IAction> {
action.payload = {
url: state.get(StoreKeys.RequestURL),
method: state.get(StoreKeys.RequestMethod),
data: state.get(StoreKeys.RequestPayload),
headers: this.headerListToObject(state.get(StoreKeys.RequestHeaders, Immutable.List<[string, string]>())),
data: Payload.parsePayload(state.get(StoreKeys.RequestPayload), state.get(StoreKeys.RequestType)),
headers:
Headers.headerListToObject(state.get(StoreKeys.RequestHeaders, Immutable.List<[string, string]>())),
}
}
axios.request(action.payload)
@@ -99,56 +102,6 @@ class AppStore extends ReduceStore<IState, IAction> {
}
}
public parseHeaderList(headers: string) {
let headerMap = Immutable.List<[string, string]>()
headers.split('\n').forEach((header, idx) => {
const [ name, value ] = header.split(':').map(s => s.trim())
const finalName = name.split('-')
.map(s => this.capitalize(s))
.join('-')
if (finalName) {
headerMap = headerMap.set(idx, [finalName, value || ''])
}
})
return headerMap
}
public headerListToObject(headers: Immutable.List<[string, string]>) {
const headerObj = {}
headers.toJS().forEach((header) => {
let [ name, value ] = header
name = name.split('-').map(s => this.capitalize(s)).join('-')
headerObj[name] = value
})
return headerObj
}
public stringHeaders(headers: Immutable.List<[string, string]>) {
return headers.entrySeq()
.map((header) => {
if (!header) {
return
}
let [ name, value ] = header[1]
name = name.split('-').map(s => this.capitalize(s)).join('-')
return name ?
`${name}: ${value}`
: undefined
})
.filter(s => Boolean(s))
.join('\n')
}
private capitalize(str: string) {
if (!str) {
return ''
}
return str[0].toUpperCase() + str.slice(1).toLowerCase()
}
private getViewKey(data: any) {
for (const k in data) {
if (data.hasOwnProperty(k) && data[k] && data[k].constructor === Array) {

52
src/common/Headers.ts Normal file
View File

@@ -0,0 +1,52 @@
import * as Immutable from 'immutable'
export type Headers = Immutable.List<[string, string]>
export function stringHeaders(headers: Headers) {
return headers.entrySeq()
.map((header) => {
if (!header) {
return
}
let [ name, value ] = header[1]
name = name.split('-').map(s => capitalize(s)).join('-')
return name ?
`${name}: ${value}`
: undefined
})
.filter(s => Boolean(s))
.join('\n')
}
export function headerListToObject(headers: Headers) {
const headerObj = {}
headers.toJS().forEach((header) => {
let [ name, value ] = header
name = name.split('-').map(s => capitalize(s)).join('-').trim()
if (name.length) {
headerObj[name] = value
}
})
return headerObj
}
export function parseHeaderList(headers: string) {
let headerMap = Immutable.List<[string, string]>()
headers.split('\n').forEach((header, idx) => {
const [ name, value ] = header.split(':').map(s => s.trim())
const finalName = name.split('-')
.map(s => capitalize(s))
.join('-')
if (finalName) {
headerMap = headerMap.set(idx, [finalName, value || ''])
}
})
return headerMap
}
export function capitalize(str: string) {
return !str ? '' : str[0].toUpperCase() + str.slice(1).toLowerCase()
}

20
src/common/Payload.ts Normal file
View File

@@ -0,0 +1,20 @@
export const typeMap = {
JSON: JSON.parse,
Form: String
}
export type TypeMap = 'JSON' | 'Form'
export function parsePayload(payload: string, type: keyof TypeMap) {
try {
payload = JSON.parse(payload)
if (!type
|| !typeMap.hasOwnProperty(type)
|| !payload.length) {
return undefined
}
return typeMap[type](payload)
} catch (e) {
return undefined
}
}

View File

@@ -2,7 +2,7 @@ import * as React from 'react'
import * as css from './Header.css'
import * as I from './Header.module'
import AddressBar from 'components/AddressBar/AddressBar'
import RequestType from 'components/RequestType/RequestType'
import RequestPayload from 'components/RequestPayload/RequestPayload'
import SelectBox, { Option, styles as selectBoxStyle } from 'components/SelectBox/SelectBox'
import Button from 'components/Button/Button'
import axios, { AxiosResponse } from 'axios'
@@ -26,7 +26,7 @@ class Header extends React.Component<I.IProps, I.IState> {
<NavBar store={this.props.store} />
</div>
<div className={css.requestDataContainer}>
<RequestType store={this.props.store} />
<RequestPayload store={this.props.store} />
<RequestHeaders store={this.props.store} />
</div>
</div>

View File

@@ -54,7 +54,8 @@ class KeyList extends React.Component<I.IProps, I.IState> {
const newPath = relativePath ? [relativePath, key].join('.') : key
let children
if (typeof data[key] !== 'string' && typeof data[key] !== 'number' &&
if (data[key] !== null && data[key] !== undefined &&
typeof data[key] !== 'string' && typeof data[key] !== 'number' &&
data[key].constructor !== Array && Object.keys(data[key]).length) {
children = this.keyListFromObject(data[key], newPath)
}

View File

@@ -23,7 +23,7 @@
.simple {
display: inline-block;
max-width: 16vw;
max-width: 40vw;
overflow: hidden;
white-space: pre;
text-overflow: ellipsis;

View File

@@ -25,11 +25,20 @@ class RObject extends React.Component<I.IProps, I.IState> {
return <span className={css.simple}>{JSON.stringify(obj)}</span>
}
return keys.map((k: string, i: number) => {
return keys.sort((a, b) => {
if (a === 'key') {
return -1
} else if (b === 'key') {
return 1
}
return a > b ? 1 : a < b ? -1 : 0
}).map((k: string, i: number) => {
let tempCls = this.props.className
if (typeof tempCls === 'function') {
tempCls = tempCls(i, k)
}
cls = classNames(tempCls, css.RObject)
return (
<div className={cls} key={'obj-' + k}>

View File

@@ -1,30 +1,26 @@
import * as React from 'react'
import * as css from './RequestType.css'
import * as I from './RequestType.module'
import * as css from './RequestPayload.css'
import * as I from './RequestPayload.module'
import { StoreKeys, dispatch, ActionTypes, register } from 'common/Dispatcher'
import * as classNames from 'classnames'
import RequestTypeSelector from 'components/RequestTypeSelector/RequestTypeSelector'
class RequestType extends React.Component<I.IProps, I.IState> {
class RequestPayload extends React.Component<I.IProps, I.IState> {
private listeners: string[]
private typeMap = {
JSON: JSON.parse,
Form: String
}
constructor(props: I.IProps) {
super(props)
const type = props.store.get(StoreKeys.RequestType)
const payload = props.store.get(StoreKeys.RequestPayload, '""')
this.state = {
type: props.store.get(StoreKeys.RequestType),
payload: props.store.get(StoreKeys.RequestPayload),
type,
payload: JSON.parse(payload),
}
}
private onChange(str: string) {
this.setState({ payload: str }, () => {
const payload = this.requestPayload
dispatch(ActionTypes.UPDATE_REQ_PAYLOAD, payload)
private onChange(payload: string) {
this.setState({ payload }, () => {
dispatch(ActionTypes.UPDATE_REQ_PAYLOAD, JSON.stringify(payload))
if (this.props.onChange) {
this.props.onChange(payload)
}
@@ -39,21 +35,6 @@ class RequestType extends React.Component<I.IProps, I.IState> {
]
}
private get requestPayload() {
if (!this.state.type
|| !this.typeMap.hasOwnProperty(this.state.type)
|| !this.state.payload.length) {
return undefined
}
try {
return this.typeMap[this.state.type](this.state.payload)
} catch (e) {
console.error(e)
return "Can't parse response"
}
}
render() {
const className = classNames(css.RequestType, this.props.className)
@@ -72,4 +53,4 @@ class RequestType extends React.Component<I.IProps, I.IState> {
}
}
export default RequestType
export default RequestPayload

View File

@@ -55,3 +55,6 @@
opacity: 1;
}
}
.key-column {
}

View File

@@ -7,3 +7,4 @@ export const rowEnd: string;
export const filterInput: string;
export const sortIcon: string;
export const active: string;
export const keyColumn: string;

View File

@@ -58,35 +58,53 @@ class ResponseRepr extends React.Component<I.IProps, I.IState> {
sortDesc: Boolean(cur.sortKey === key && !cur.sortDesc)
}))
}
private columns() {
const keys = Object.keys(this.state.response[0] || {})
return keys.map((key) => {
const cls = classNames(css.sortIcon, 'material-icons', {
[css.active]: this.state.sortKey === key
})
return (
<h3 key={`col-th-${key}`}
onClick={() => this.sortBy(key)}>
{key}
<i className={cls}>
{this.state.sortKey === key && this.state.sortDesc ? 'arrow_drop_down' : 'arrow_drop_up'}
</i>
</h3>
)
})
}
private processedResponse() {
let response = this.state.response || []
if (!response.length) {
return []
const keys = Object.keys(response)
const isArray = response.constructor === Array
if (!keys.length) {
return [{ key: this.props.store.get(D.StoreKeys.ViewKey), value: JSON.stringify(response) }]
}
if (typeof response === 'string' || typeof response === 'number' && response) {
return [{ type: typeof response, value: JSON.stringify(response) }]
}
if (!isArray) {
let flag = false
keys.forEach((key) => {
const row = response[key]
if (!row || !Object.keys(row || {}).length) {
flag = true
}
})
response = keys.map((key) => {
let row = response[key]
let oldVal: any
if (typeof row !== 'string' && typeof row !== 'number' && row) {
oldVal = row
if (flag) {
row = { key: row._id || row.id || key, value: row }
} else {
row = { key: row._id || row.id || key, ...row }
}
} else {
flag = true
row = { key: key, value: row }
}
if (Object.keys(row).length < 2) {
row = { key: row.key, value: JSON.stringify(oldVal) }
}
return row
})
}
if (response[0].hasOwnProperty(this.state.sortKey)) {
const desc = this.state.sortDesc
const key = this.state.sortKey
response = response.sort((a, b) => {
// numbers are matching
if (typeof a[key] === 'number' && typeof b === 'number' ||
@@ -103,15 +121,19 @@ class ResponseRepr extends React.Component<I.IProps, I.IState> {
} else if (a[key] < b[key]) {
return desc ? 1 : -1
}
})
if (!a && !b) {
return 0
}
return a.valueOf() - b.valueOf()
})
}
if (this.state.filter.trim().length) {
const filter = this.state.filter.trim().replace(/\s{2,}/g, ' ')
try {
const filterOps = parse(filter)
console.debug({filterOps})
response = response.filter((row) => {
for (const key in filterOps) {
@@ -155,22 +177,59 @@ class ResponseRepr extends React.Component<I.IProps, I.IState> {
onChange={(e) => this.setState({ filter: e.target.value })} />
)
}
private columns(response: any[]) {
// const keyCollate = {}
// response.forEach((row) => {
// Object.keys(row).forEach((k) => keyCollate[k] = true)
// })
// const keys = Object.keys(keyCollate)
const keys = Object.keys(response[0] || { key: null, value: null })
return keys.sort((a, b) => {
if (a === 'key') {
return -1
} else if (b === 'key') {
return 1
}
return a > b ? 1 : a < b ? -1 : 0
}).map((key) => {
const cls = classNames(css.sortIcon, 'material-icons', {
[css.active]: this.state.sortKey === key
})
return (
<h3 key={`col-th-${key}`}
onClick={() => this.sortBy(key)}
className={classNames({ [css.keyColumn]: true })}>
{key}
<i className={cls}>
{this.state.sortKey === key && this.state.sortDesc ? 'arrow_drop_down' : 'arrow_drop_up'}
</i>
</h3>
)
})
}
private table() {
const keys = Object.keys(this.state.response && this.state.response[0] || {})
const colAmt = keys.length
const response = this.processedResponse()
const columns = this.columns(response)
const firstLine = response && response.length ? response[0] : {}
const colAmt = columns.length - 1
return (
<div>
{this.filterInput()}
<div className={css.table}
style={{gridTemplateColumns: `repeat(${colAmt}, auto)`}}>
{this.columns()}
{this.processedResponse().map((row, i) => {
style={{gridTemplateColumns: `min-content repeat(${colAmt}, auto)`}}>
{columns}
{(response).map((row, i) => {
const cls = (j) => {
return classNames(css.cell, {
[css.rowStart]: j % colAmt === 0,
[css.rowEnd]: j % colAmt === colAmt - 1,
[css.rowStart]: j % (colAmt + 1) === 0,
[css.rowEnd]: j % (colAmt + 1) === colAmt,
})
}
@@ -185,24 +244,6 @@ class ResponseRepr extends React.Component<I.IProps, I.IState> {
)
}
private getRObjectList() {
const { response } = this.state
let colAmt
if (response && response.constructor === Array) {
return this.table()
}
colAmt = Object.keys(response || {})
return (
<div className={css.table}
style={{gridTemplateRows: `repeat(${colAmt}, min-content)`}}>
<RObject data={response} />
</div>
)
}
render() {
const className = [
css.ResponseRepr,
@@ -211,7 +252,7 @@ class ResponseRepr extends React.Component<I.IProps, I.IState> {
return (
<div className={className}>
{this.getRObjectList()}
{this.table()}
</div>
)
}