mirror of
https://github.com/chenasraf/redar-browser.git
synced 2026-05-18 01:59:00 +00:00
Major improve of object type handling, now always shows table
This commit is contained in:
@@ -13,6 +13,7 @@
|
||||
"default_icon": "android-chrome-192x192.png"
|
||||
},
|
||||
"permissions": [
|
||||
"tabs",
|
||||
"http://*/",
|
||||
"https://*/"
|
||||
]
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
52
src/common/Headers.ts
Normal 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
20
src/common/Payload.ts
Normal 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
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
|
||||
.simple {
|
||||
display: inline-block;
|
||||
max-width: 16vw;
|
||||
max-width: 40vw;
|
||||
overflow: hidden;
|
||||
white-space: pre;
|
||||
text-overflow: ellipsis;
|
||||
|
||||
@@ -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}>
|
||||
|
||||
@@ -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
|
||||
@@ -55,3 +55,6 @@
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.key-column {
|
||||
}
|
||||
|
||||
@@ -7,3 +7,4 @@ export const rowEnd: string;
|
||||
export const filterInput: string;
|
||||
export const sortIcon: string;
|
||||
export const active: string;
|
||||
export const keyColumn: string;
|
||||
|
||||
@@ -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>
|
||||
)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user