🎨 Minor UI update

This commit is contained in:
Liyas Thomas
2019-09-02 13:08:49 +05:30
parent 7645d0d2c9
commit 16d9e1e34a
5 changed files with 93 additions and 106 deletions

View File

@@ -86,12 +86,20 @@ button {
font-weight: 700; font-weight: 700;
font-size: 16px; font-size: 16px;
cursor: pointer; cursor: pointer;
transition: all .2s;
&[disabled], &[disabled],
&.disabled { &.disabled {
opacity: 0.7; opacity: 0.7;
cursor: default; cursor: default;
} }
&:hover,
&:focus {
background-color: var(--act-color);
box-shadow: inset 0 0 0 2px var(--ac-color);
color: var(--ac-color);
}
} }
fieldset { fieldset {

View File

@@ -7,33 +7,33 @@
</li> </li>
</ul> </ul>
<virtual-list class="virtual-list" :class="{filled: filteredHistory.length}" :size="89" :remain="Math.min(5, filteredHistory.length)"> <virtual-list class="virtual-list" :class="{filled: filteredHistory.length}" :size="89" :remain="Math.min(5, filteredHistory.length)">
<ul v-for="entry in filteredHistory" :key="entry.millis" class="entry"> <ul v-for="entry in filteredHistory" :key="entry.time" class="entry">
<li> <li>
<label :for="'time#' + entry.millis">Time</label> <label :for="'time#' + entry.time">Time</label>
<input :id="'time#' + entry.millis" type="text" readonly :value="entry.time" :title="entry.date"> <input :id="'time#' + entry.time" type="text" readonly :value="entry.time" :title="entry.date">
</li> </li>
<li class="method-list-item"> <li class="method-list-item">
<label :for="'time#' + entry.millis">Method</label> <label :for="'time#' + entry.time">Method</label>
<input :id="'method#' + entry.millis" type="text" readonly :value="entry.method" :class="findEntryStatus(entry).className" :style="{'--status-code': entry.status}"> <input :id="'method#' + entry.time" type="text" readonly :value="entry.method" :class="findEntryStatus(entry).className" :style="{'--status-code': entry.status}">
<span class="entry-status-code">{{entry.status}}</span> <span class="entry-status-code">{{entry.status}}</span>
</li> </li>
<li> <li>
<label :for="'url#' + entry.millis">URL</label> <label :for="'url#' + entry.time">URL</label>
<input :id="'url#' + entry.millis" type="text" readonly :value="entry.url"> <input :id="'url#' + entry.time" type="text" readonly :value="entry.url">
</li> </li>
<li> <li>
<label :for="'path#' + entry.millis">Path</label> <label :for="'path#' + entry.time">Path</label>
<input :id="'path#' + entry.millis" type="text" readonly :value="entry.path"> <input :id="'path#' + entry.time" type="text" readonly :value="entry.path">
</li> </li>
<li> <li>
<label :for="'delete-button#' + entry.millis" class="hide-on-small-screen">&nbsp;</label> <label :for="'delete-button#' + entry.time" class="hide-on-small-screen">&nbsp;</label>
<button :id="'delete-button#' + entry.millis" :disabled="isClearingHistory" @click="deleteHistory(entry)"> <button :id="'delete-button#' + entry.time" :disabled="isClearingHistory" @click="deleteHistory(entry)">
Delete Delete
</button> </button>
</li> </li>
<li> <li>
<label :for="'use-button#' + entry.millis" class="hide-on-small-screen">&nbsp;</label> <label :for="'use-button#' + entry.time" class="hide-on-small-screen">&nbsp;</label>
<button :id="'use-button#' + entry.millis" :disabled="isClearingHistory" @click="useHistory(entry)"> <button :id="'use-button#' + entry.time" :disabled="isClearingHistory" @click="useHistory(entry)">
Use Use
</button> </button>
</li> </li>
@@ -67,11 +67,13 @@
</pw-section> </pw-section>
</template> </template>
<script> <script>
import VirtualList from 'vue-virtual-scroll-list' import VirtualList from 'vue-virtual-scroll-list'
import section from "./section"; import section from "./section";
import {findStatusGroup} from "../pages/index"; import {
findStatusGroup
} from "../pages/index";
const updateOnLocalStorage = (propertyName, property) => window.localStorage.setItem(propertyName, JSON.stringify(property)); const updateOnLocalStorage = (propertyName, property) => window.localStorage.setItem(propertyName, JSON.stringify(property));
export default { export default {
components: { components: {
'pw-section': section, 'pw-section': section,
@@ -126,7 +128,7 @@
updateOnLocalStorage('history', this.history); updateOnLocalStorage('history', this.history);
}, },
enableHistoryClearing() { enableHistoryClearing() {
if (!this.history || !this.history.length) return; if (!this.history || !this.history.length) return;
this.isClearingHistory = true; this.isClearingHistory = true;
}, },
disableHistoryClearing() { disableHistoryClearing() {

2
package-lock.json generated
View File

@@ -1,6 +1,6 @@
{ {
"name": "postwoman", "name": "postwoman",
"version": "1.0.0", "version": "0.1.0",
"lockfileVersion": 1, "lockfileVersion": 1,
"requires": true, "requires": true,
"dependencies": { "dependencies": {

View File

@@ -5,14 +5,12 @@
"author": "liyasthomas", "author": "liyasthomas",
"private": true, "private": true,
"scripts": { "scripts": {
"predev": "node build.js --dev", "predev": "node build.js --dev",
"dev": "nuxt", "dev": "nuxt",
"prebuild": "node build.js",
"prebuild": "node build.js",
"build": "nuxt build", "build": "nuxt build",
"start": "nuxt start", "start": "nuxt start",
"pregenerate": "node build.js",
"pregenerate": "node build.js",
"generate": "nuxt generate" "generate": "nuxt generate"
}, },
"dependencies": { "dependencies": {

View File

@@ -24,7 +24,7 @@
</li> </li>
<li> <li>
<label for="action" class="hide-on-small-screen">&nbsp;</label> <label for="action" class="hide-on-small-screen">&nbsp;</label>
<button id="action" class="show" name="action" @click="sendRequest" :disabled="!isValidURL" ref="sendButton">Send <span id="hidden-message">Again</span></button> <button id="action" class="show" name="action" @click="sendRequest" :disabled="!isValidURL" ref="sendButton">Send <span id="hidden-message">Again</span></button>
</li> </li>
</ul> </ul>
</pw-section> </pw-section>
@@ -74,6 +74,35 @@
<textarea v-model="rawParams" v-textarea-auto-height="rawParams" style="font-family: monospace;" rows="16" @keydown="formatRawParams"></textarea> <textarea v-model="rawParams" v-textarea-auto-height="rawParams" style="font-family: monospace;" rows="16" @keydown="formatRawParams"></textarea>
</div> </div>
</pw-section> </pw-section>
<pw-section class="purple" label="Response" id="response" ref="response">
<ul>
<li>
<label for="status">status</label>
<input name="status" type="text" readonly :value="response.status || '(waiting to send request)'" :class="statusCategory ? statusCategory.className : ''">
</li>
</ul>
<ul v-for="(value, key) in response.headers">
<li>
<label for="value">{{key}}</label>
<input name="value" :value="value" readonly>
</li>
</ul>
<ul>
<li>
<div class="flex-wrap">
<label for="body">response</label>
<button v-if="response.body" name="action" @click="copyResponse">Copy Response</button>
</div>
<div id="response-details-wrapper">
<textarea name="body" rows="16" id="response-details" readonly>{{response.body || '(waiting to send request)'}}</textarea>
<iframe src="about:blank" class="covers-response" ref="previewFrame" :class="{hidden: !previewEnabled}"></iframe>
</div>
<div v-if="response.body && responseType === 'text/html'" class="align-right">
<button @click.prevent="togglePreview">{{ previewEnabled ? 'Hide Preview' : 'Preview HTML' }}</button>
</div>
</li>
</ul>
</pw-section>
<pw-section class="green" label="Authentication" collapsed> <pw-section class="green" label="Authentication" collapsed>
<ul> <ul>
<li> <li>
@@ -158,45 +187,14 @@
</li> </li>
</ul> </ul>
</pw-section> </pw-section>
<pw-section class="purple" label="Response" id="response" ref="response">
<ul>
<li>
<label for="status">status</label>
<input name="status" type="text" readonly :value="response.status || '(waiting to send request)'" :class="statusCategory ? statusCategory.className : ''">
</li>
</ul>
<ul v-for="(value, key) in response.headers">
<li>
<label for="value">{{key}}</label>
<input name="value" :value="value" readonly>
</li>
</ul>
<ul>
<li>
<div class="flex-wrap">
<label for="body">response</label>
<button v-if="response.body" name="action" @click="copyResponse">Copy Response</button>
</div>
<div id="response-details-wrapper">
<textarea name="body" rows="16" id="response-details" readonly>{{response.body || '(waiting to send request)'}}</textarea>
<iframe src="about:blank" class="covers-response" ref="previewFrame" :class="{hidden: !previewEnabled}"></iframe>
</div>
<div v-if="response.body && responseType === 'text/html'" class="align-right">
<button @click.prevent="togglePreview">{{ previewEnabled ? 'Hide Preview' : 'Preview HTML' }}</button>
</div>
</li>
</ul>
</pw-section>
<history @useHistory="handleUseHistory" ref="historyComponent" /> <history @useHistory="handleUseHistory" ref="historyComponent" />
</div> </div>
</template> </template>
<script> <script>
import history from "../components/history"; import history from "../components/history";
import section from "../components/section"; import section from "../components/section";
import textareaAutoHeight from "../directives/textareaAutoHeight"; import textareaAutoHeight from "../directives/textareaAutoHeight";
const statusCategories = [{
const statusCategories = [{
name: 'informational', name: 'informational',
statusCodeRegex: new RegExp(/[1][0-9]+/), statusCodeRegex: new RegExp(/[1][0-9]+/),
className: 'info-response' className: 'info-response'
@@ -238,14 +236,12 @@
headerMap[header] = value headerMap[header] = value
}); });
return headerMap return headerMap
}; };
export const findStatusGroup = responseStatus => statusCategories.find(status => status.statusCodeRegex.test(responseStatus)); export const findStatusGroup = responseStatus => statusCategories.find(status => status.statusCodeRegex.test(responseStatus));
export default { export default {
middleware: 'parsedefaulturl', // calls middleware before loading the page middleware: 'parsedefaulturl', // calls middleware before loading the page
directives: { directives: {
textareaAutoHeight textareaAutoHeight
}, },
components: { components: {
'pw-section': section, 'pw-section': section,
@@ -355,54 +351,44 @@
alert('Please check the formatting of the URL.'); alert('Please check the formatting of the URL.');
return; return;
} }
// Start showing the loading bar as soon as possible. // Start showing the loading bar as soon as possible.
// The nuxt axios module will hide it when the request is made. // The nuxt axios module will hide it when the request is made.
this.$nuxt.$loading.start(); this.$nuxt.$loading.start();
if (this.$refs.response.$el.classList.contains('hidden')) { if (this.$refs.response.$el.classList.contains('hidden')) {
this.$refs.response.$el.classList.toggle('hidden') this.$refs.response.$el.classList.toggle('hidden')
} }
this.$refs.response.$el.scrollIntoView({ this.$refs.request.$el.scrollIntoView({
behavior: 'smooth' behavior: 'smooth'
}); });
this.previewEnabled = false; this.previewEnabled = false;
this.response.status = 'Fetching...'; this.response.status = 'Fetching...';
this.response.body = 'Loading...'; this.response.body = 'Loading...';
const auth = this.auth === 'Basic' ? { const auth = this.auth === 'Basic' ? {
username: this.httpUser, username: this.httpUser,
password: this.httpPassword password: this.httpPassword
} : null; } : null;
let headers = {}; let headers = {};
// If the request has a request body, we want to ensure Content-Length and // If the request has a request body, we want to ensure Content-Length and
// Content-Type are sent. // Content-Type are sent.
let requestBody; let requestBody;
if (this.hasRequestBody) { if (this.hasRequestBody) {
requestBody = this.rawInput ? this.rawParams : this.rawRequestBody; requestBody = this.rawInput ? this.rawParams : this.rawRequestBody;
Object.assign(headers, { Object.assign(headers, {
'Content-Length': requestBody.length, 'Content-Length': requestBody.length,
'Content-Type': `${this.contentType}; charset=utf-8` 'Content-Type': `${this.contentType}; charset=utf-8`
}); });
} }
// If the request uses a token for auth, we want to make sure it's sent here. // If the request uses a token for auth, we want to make sure it's sent here.
if (this.auth === 'Bearer Token') headers['Authorization'] = `Bearer ${this.bearerToken}`; if (this.auth === 'Bearer Token') headers['Authorization'] = `Bearer ${this.bearerToken}`;
headers = Object.assign( headers = Object.assign(
// Clone the app headers object first, we don't want to // Clone the app headers object first, we don't want to
// mutate it with the request headers added by default. // mutate it with the request headers added by default.
Object.assign({}, this.headers), Object.assign({}, this.headers),
// We make our temporary headers object the source so // We make our temporary headers object the source so
// that you can override the added headers if you // that you can override the added headers if you
// specify them. // specify them.
headers headers
); );
try { try {
const payload = await this.$axios({ const payload = await this.$axios({
method: this.method, method: this.method,
@@ -411,17 +397,13 @@
headers, headers,
data: requestBody data: requestBody
}); });
(() => { (() => {
const status = this.response.status = payload.status; const status = this.response.status = payload.status;
const headers = this.response.headers = payload.headers; const headers = this.response.headers = payload.headers;
// We don't need to bother parsing JSON, axios already handles it for us! // We don't need to bother parsing JSON, axios already handles it for us!
const body = this.response.body = payload.data; const body = this.response.body = payload.data;
const date = new Date().toLocaleDateString(); const date = new Date().toLocaleDateString();
const time = new Date().toLocaleTimeString(); const time = new Date().toLocaleTimeString();
// Addition of an entry to the history component. // Addition of an entry to the history component.
const entry = { const entry = {
status, status,
@@ -438,7 +420,6 @@
this.response.headers = error.response.headers; this.response.headers = error.response.headers;
this.response.status = error.response.status; this.response.status = error.response.status;
this.response.body = error.response.data; this.response.body = error.response.data;
// Addition of an entry to the history component. // Addition of an entry to the history component.
const entry = { const entry = {
status: this.response.status, status: this.response.status,
@@ -451,12 +432,10 @@
this.$refs.historyComponent.addEntry(entry); this.$refs.historyComponent.addEntry(entry);
return; return;
} }
this.response.status = error.message; this.response.status = error.message;
this.response.body = "See JavaScript console (F12) for details."; this.response.body = "See JavaScript console (F12) for details.";
} }
}, },
addRequestHeader() { addRequestHeader() {
this.headers.push({ this.headers.push({
key: '', key: '',
@@ -533,23 +512,22 @@
} }
} }
}, },
setRouteQueryState () { setRouteQueryState() {
const flat = key => this[key] !== '' ? `${key}=${this[key]}&` : '' const flat = key => this[key] !== '' ? `${key}=${this[key]}&` : ''
const deep = key => { const deep = key => {
const haveItems = [...this[key]].length const haveItems = [...this[key]].length
if(haveItems && this[key]['value'] !== '') { if (haveItems && this[key]['value'] !== '') {
return `${key}=${JSON.stringify(this[key])}&` return `${key}=${JSON.stringify(this[key])}&`
} } else return ''
else return ''
} }
let flats = [ 'method', 'url', 'path', 'auth', 'httpUser', 'httpPassword', 'bearerToken','contentType'].map(item => flat(item)) let flats = ['method', 'url', 'path', 'auth', 'httpUser', 'httpPassword', 'bearerToken', 'contentType'].map(item => flat(item))
let deeps = ['headers', 'params', 'bodyParams'].map(item => deep(item)) let deeps = ['headers', 'params', 'bodyParams'].map(item => deep(item))
this.$router.replace('/?'+ flats.concat(deeps).join('').slice(0,-1)) this.$router.replace('/?' + flats.concat(deeps).join('').slice(0, -1))
}, },
setRouteQueries(queries) { setRouteQueries(queries) {
if(typeof(queries) !== 'object') throw new Error('Route query parameters must be a Object') if (typeof(queries) !== 'object') throw new Error('Route query parameters must be a Object')
for (const key in queries) { for (const key in queries) {
if(key === 'headers' || key === 'params' || key ==='bodyParams') this[key] = JSON.parse(queries[key]) if (key === 'headers' || key === 'params' || key === 'bodyParams') this[key] = JSON.parse(queries[key])
else if (typeof(this[key]) === 'string') this[key] = queries[key]; else if (typeof(this[key]) === 'string') this[key] = queries[key];
} }
}, },
@@ -558,31 +536,32 @@
const sendButtonElement = this.$refs.sendButton; const sendButtonElement = this.$refs.sendButton;
const observer = new IntersectionObserver((entries, observer) => { const observer = new IntersectionObserver((entries, observer) => {
entries.forEach(entry => { entries.forEach(entry => {
sendButtonElement.classList.toggle('show'); sendButtonElement.classList.toggle('show');
}); });
}, { threshold: 1 }); }, {
threshold: 1
});
observer.observe(requestElement); observer.observe(requestElement);
} }
}, },
mounted() { mounted() {
this.observeRequestButton(); this.observeRequestButton();
}, },
created() { created() {
if (Object.keys(this.$route.query).length) this.setRouteQueries(this.$route.query); if (Object.keys(this.$route.query).length) this.setRouteQueries(this.$route.query);
this.$watch(vm => [ this.$watch(vm => [
vm.method, vm.method,
vm.url, vm.url,
vm.auth, vm.auth,
vm.path, vm.path,
vm.httpUser, vm.httpUser,
vm.httpPassword, vm.httpPassword,
vm.bearerToken, vm.bearerToken,
vm.headers, vm.headers,
vm.params, vm.params,
vm.bodyParams, vm.bodyParams,
vm.contentType vm.contentType
], val => { ], val => {
this.setRouteQueryState() this.setRouteQueryState()
}) })
} }