diff --git a/public/manifest.json b/public/manifest.json index 8ac82e0..2565736 100644 --- a/public/manifest.json +++ b/public/manifest.json @@ -13,6 +13,7 @@ "default_icon": "android-chrome-192x192.png" }, "permissions": [ + "tabs", "http://*/", "https://*/" ] diff --git a/src/background.tsx b/src/background.tsx index e971873..8aaa333 100644 --- a/src/background.tsx +++ b/src/background.tsx @@ -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 { diff --git a/src/common/Dispatcher.ts b/src/common/Dispatcher.ts index 75f01ad..cad0011 100644 --- a/src/common/Dispatcher.ts +++ b/src/common/Dispatcher.ts @@ -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 { [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 { 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 { 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 { } } - 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) { diff --git a/src/common/Headers.ts b/src/common/Headers.ts new file mode 100644 index 0000000..cccc597 --- /dev/null +++ b/src/common/Headers.ts @@ -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() +} diff --git a/src/common/Payload.ts b/src/common/Payload.ts new file mode 100644 index 0000000..1ac9cf2 --- /dev/null +++ b/src/common/Payload.ts @@ -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 + } +} diff --git a/src/components/Header/Header.tsx b/src/components/Header/Header.tsx index 2f06ffb..7b9c83f 100644 --- a/src/components/Header/Header.tsx +++ b/src/components/Header/Header.tsx @@ -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 {
- +
diff --git a/src/components/KeyList/KeyList.tsx b/src/components/KeyList/KeyList.tsx index a7570f5..760bfdd 100644 --- a/src/components/KeyList/KeyList.tsx +++ b/src/components/KeyList/KeyList.tsx @@ -54,7 +54,8 @@ class KeyList extends React.Component { 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) } diff --git a/src/components/RObject/RObject.css b/src/components/RObject/RObject.css index ad1dfdb..854b498 100644 --- a/src/components/RObject/RObject.css +++ b/src/components/RObject/RObject.css @@ -23,7 +23,7 @@ .simple { display: inline-block; - max-width: 16vw; + max-width: 40vw; overflow: hidden; white-space: pre; text-overflow: ellipsis; diff --git a/src/components/RObject/RObject.tsx b/src/components/RObject/RObject.tsx index 9c70dba..e97a882 100644 --- a/src/components/RObject/RObject.tsx +++ b/src/components/RObject/RObject.tsx @@ -25,11 +25,20 @@ class RObject extends React.Component { return {JSON.stringify(obj)} } - 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 (
diff --git a/src/components/RequestType/RequestType.css b/src/components/RequestPayload/RequestPayload.css similarity index 100% rename from src/components/RequestType/RequestType.css rename to src/components/RequestPayload/RequestPayload.css diff --git a/src/components/RequestType/RequestType.css.d.ts b/src/components/RequestPayload/RequestPayload.css.d.ts similarity index 100% rename from src/components/RequestType/RequestType.css.d.ts rename to src/components/RequestPayload/RequestPayload.css.d.ts diff --git a/src/components/RequestType/RequestType.module.d.ts b/src/components/RequestPayload/RequestPayload.module.d.ts similarity index 100% rename from src/components/RequestType/RequestType.module.d.ts rename to src/components/RequestPayload/RequestPayload.module.d.ts diff --git a/src/components/RequestType/RequestType.tsx b/src/components/RequestPayload/RequestPayload.tsx similarity index 55% rename from src/components/RequestType/RequestType.tsx rename to src/components/RequestPayload/RequestPayload.tsx index 84ae3d7..3710557 100644 --- a/src/components/RequestType/RequestType.tsx +++ b/src/components/RequestPayload/RequestPayload.tsx @@ -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 { +class RequestPayload extends React.Component { 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 { ] } - 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 { } } -export default RequestType +export default RequestPayload diff --git a/src/components/ResponseRepr/ResponseRepr.css b/src/components/ResponseRepr/ResponseRepr.css index 1fc13d1..67f0564 100644 --- a/src/components/ResponseRepr/ResponseRepr.css +++ b/src/components/ResponseRepr/ResponseRepr.css @@ -55,3 +55,6 @@ opacity: 1; } } + +.key-column { +} diff --git a/src/components/ResponseRepr/ResponseRepr.css.d.ts b/src/components/ResponseRepr/ResponseRepr.css.d.ts index 68323c3..2801dcb 100644 --- a/src/components/ResponseRepr/ResponseRepr.css.d.ts +++ b/src/components/ResponseRepr/ResponseRepr.css.d.ts @@ -7,3 +7,4 @@ export const rowEnd: string; export const filterInput: string; export const sortIcon: string; export const active: string; +export const keyColumn: string; diff --git a/src/components/ResponseRepr/ResponseRepr.tsx b/src/components/ResponseRepr/ResponseRepr.tsx index c461e82..2417456 100644 --- a/src/components/ResponseRepr/ResponseRepr.tsx +++ b/src/components/ResponseRepr/ResponseRepr.tsx @@ -58,35 +58,53 @@ class ResponseRepr extends React.Component { 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 ( -

this.sortBy(key)}> - {key} - - {this.state.sortKey === key && this.state.sortDesc ? 'arrow_drop_down' : 'arrow_drop_up'} - -

- ) - }) - } 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 { } 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 { 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 ( +

this.sortBy(key)} + className={classNames({ [css.keyColumn]: true })}> + {key} + + {this.state.sortKey === key && this.state.sortDesc ? 'arrow_drop_down' : 'arrow_drop_up'} + +

+ ) + }) + } 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 (
{this.filterInput()} +
- {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 { ) } - private getRObjectList() { - const { response } = this.state - let colAmt - - if (response && response.constructor === Array) { - return this.table() - } - - colAmt = Object.keys(response || {}) - - return ( -
- -
- ) - } - render() { const className = [ css.ResponseRepr, @@ -211,7 +252,7 @@ class ResponseRepr extends React.Component { return (
- {this.getRObjectList()} + {this.table()}
) }