This commit is contained in:
Liyas Thomas
2020-10-23 10:21:28 +05:30
33 changed files with 648 additions and 630 deletions

View File

@@ -326,7 +326,7 @@ docker run -p 3000:3000 postwoman:latest
1. [Clone this repo](https://help.github.com/en/articles/cloning-a-repository) with git. 1. [Clone this repo](https://help.github.com/en/articles/cloning-a-repository) with git.
2. Install dependencies by running `npm install` within the directory that you cloned (probably `hoppscotch`). 2. Install dependencies by running `npm install` within the directory that you cloned (probably `hoppscotch`).
3. Build the release files with `npm run build`. 3. Build the release files with `npm run generate`.
4. Find the built project in `./dist`. 4. Find the built project in `./dist`.
## **Contributing** ## **Contributing**

View File

@@ -8,6 +8,7 @@ $responsiveWidth: 768px;
:root { :root {
@apply antialiased; @apply antialiased;
font-variant-ligatures: common-ligatures; font-variant-ligatures: common-ligatures;
} }
@@ -54,6 +55,7 @@ body {
@apply transition; @apply transition;
@apply ease-in-out; @apply ease-in-out;
@apply duration-200; @apply duration-200;
-webkit-tap-highlight-color: transparent; -webkit-tap-highlight-color: transparent;
-webkit-touch-callout: none; -webkit-touch-callout: none;
} }
@@ -211,6 +213,7 @@ hr {
.tooltip-arrow { .tooltip-arrow {
@apply mt-0; @apply mt-0;
@apply mb-0; @apply mb-0;
border-width: 5px 5px 0 5px; border-width: 5px 5px 0 5px;
border-left-color: transparent !important; border-left-color: transparent !important;
border-right-color: transparent !important; border-right-color: transparent !important;
@@ -226,6 +229,7 @@ hr {
.tooltip-arrow { .tooltip-arrow {
@apply mt-0; @apply mt-0;
@apply mb-0; @apply mb-0;
border-width: 0 5px 5px 5px; border-width: 0 5px 5px 5px;
border-left-color: transparent !important; border-left-color: transparent !important;
border-right-color: transparent !important; border-right-color: transparent !important;
@@ -241,6 +245,7 @@ hr {
.tooltip-arrow { .tooltip-arrow {
@apply ml-0; @apply ml-0;
@apply mr-0; @apply mr-0;
border-width: 5px 5px 5px 0; border-width: 5px 5px 5px 0;
border-left-color: transparent !important; border-left-color: transparent !important;
border-top-color: transparent !important; border-top-color: transparent !important;
@@ -256,6 +261,7 @@ hr {
.tooltip-arrow { .tooltip-arrow {
@apply ml-0; @apply ml-0;
@apply mr-0; @apply mr-0;
border-width: 5px 0 5px 5px; border-width: 5px 0 5px 5px;
border-top-color: transparent !important; border-top-color: transparent !important;
border-right-color: transparent !important; border-right-color: transparent !important;
@@ -278,11 +284,14 @@ hr {
@apply rounded-lg; @apply rounded-lg;
@apply overflow-auto; @apply overflow-auto;
@apply shadow-lg; @apply shadow-lg;
max-height: 256px; max-height: 256px;
button { button {
@apply flex-1; @apply flex-1;
@apply m-0; @apply m-0;
@apply justify-start;
@apply text-left;
} }
div { div {
@@ -290,11 +299,6 @@ hr {
@apply items-stretch; @apply items-stretch;
@apply flex-col; @apply flex-col;
} }
button {
@apply justify-start;
@apply text-left;
}
} }
.popover-arrow { .popover-arrow {
@@ -362,6 +366,7 @@ button {
@apply text-actColor; @apply text-actColor;
@apply fill-current; @apply fill-current;
@apply outline-none; @apply outline-none;
box-shadow: inset 0 0 0 2px var(--fg-color); box-shadow: inset 0 0 0 2px var(--fg-color);
} }
@@ -423,6 +428,7 @@ button {
@apply tracking-normal; @apply tracking-normal;
@apply whitespace-no-wrap; @apply whitespace-no-wrap;
@apply antialiased; @apply antialiased;
word-wrap: normal; word-wrap: normal;
direction: ltr; direction: ltr;
text-rendering: optimizeLegibility; text-rendering: optimizeLegibility;
@@ -481,6 +487,7 @@ code {
@apply select-text; @apply select-text;
@apply resize-y; @apply resize-y;
@apply outline-none; @apply outline-none;
width: calc(100% - 16px); width: calc(100% - 16px);
&:not([readonly]):not(.ace_editor):hover, &:not([readonly]):not(.ace_editor):hover,
@@ -493,6 +500,7 @@ code {
.method { .method {
@apply cursor-pointer; @apply cursor-pointer;
@apply uppercase; @apply uppercase;
min-width: 128px; min-width: 128px;
&:hover, &:hover,
@@ -530,11 +538,12 @@ pre {
@apply w-full; @apply w-full;
} }
&:after { &::after {
@apply inline-block; @apply inline-block;
@apply absolute; @apply absolute;
@apply pointer-events-none; @apply pointer-events-none;
@apply font-icon; @apply font-icon;
content: "\e313"; content: "\e313";
top: 16px; top: 16px;
right: 16px; right: 16px;
@@ -544,6 +553,7 @@ pre {
select { select {
@apply cursor-pointer; @apply cursor-pointer;
@apply appearance-none; @apply appearance-none;
height: 40px; height: 40px;
&::-ms-expand { &::-ms-expand {
@@ -563,7 +573,7 @@ input[type="checkbox"] {
@apply align-middle; @apply align-middle;
@apply cursor-pointer; @apply cursor-pointer;
&:before { &::before {
@apply border; @apply border;
@apply border-fgColor; @apply border-fgColor;
@apply rounded-lg; @apply rounded-lg;
@@ -574,6 +584,7 @@ input[type="checkbox"] {
@apply transition; @apply transition;
@apply ease-in-out; @apply ease-in-out;
@apply duration-200; @apply duration-200;
content: "\2714"; content: "\2714";
height: 16px; height: 16px;
width: 16px; width: 16px;
@@ -581,7 +592,7 @@ input[type="checkbox"] {
} }
} }
&:checked + label:before { &:checked + label::before {
@apply bg-acColor; @apply bg-acColor;
@apply border-acColor; @apply border-acColor;
@apply text-actColor; @apply text-actColor;
@@ -616,6 +627,7 @@ ol {
@apply flex; @apply flex;
@apply p-0; @apply p-0;
@apply list-none; @apply list-none;
margin: 4px 0 4px; margin: 4px 0 4px;
ul, ul,
@@ -684,6 +696,7 @@ ol li {
textarea { textarea {
@apply m-0; @apply m-0;
@apply w-full; @apply w-full;
line-height: 1; line-height: 1;
} }
@@ -708,6 +721,7 @@ ol li {
@apply ease-in-out; @apply ease-in-out;
@apply duration-200; @apply duration-200;
@apply shadow-lg; @apply shadow-lg;
bottom: 86px; bottom: 86px;
left: 50%; left: 50%;
z-index: 10001; z-index: 10001;
@@ -753,6 +767,7 @@ section {
@apply flex; @apply flex;
@apply order-2; @apply order-2;
@apply ml-4; @apply ml-4;
width: 33%; width: 33%;
} }
@@ -807,15 +822,17 @@ section {
@apply text-sm; @apply text-sm;
@apply rounded-lg; @apply rounded-lg;
@apply shadow-lg; @apply shadow-lg;
padding: 16px !important; padding: 16px !important;
.action { .action {
@apply bg-gray-50; @apply bg-gray-50;
@apply text-gray-900; @apply text-gray-900;
@apply rounded-lg; @apply rounded-lg;
@apply font-bold;
text-transform: none !important; text-transform: none !important;
padding: 12px 16px !important; padding: 12px 16px !important;
font-weight: 500 !important;
font-size: 16px !important; font-size: 16px !important;
margin: 0 !important; margin: 0 !important;
margin-left: 8px !important; margin-left: 8px !important;

View File

@@ -1,6 +1,6 @@
@mixin baseTheme { @mixin baseTheme {
--font-body: "Poppins", "sans-serif"; --font-body: "Poppins", "sans-serif";
--font-mono: "Roboto Mono", "monoscpace"; --font-mono: "Roboto Mono", "monospace";
--font-icon: "Material Icons"; --font-icon: "Material Icons";
} }

View File

@@ -21,7 +21,7 @@
type="text" type="text"
v-model="name" v-model="name"
:placeholder="$t('my_new_folder')" :placeholder="$t('my_new_folder')"
@keyup.enter="addNewFolder" @keyup.enter="addFolder"
/> />
</li> </li>
</ul> </ul>
@@ -33,7 +33,7 @@
<button class="icon" @click="hideModal"> <button class="icon" @click="hideModal">
{{ $t("cancel") }} {{ $t("cancel") }}
</button> </button>
<button class="icon primary" @click="addNewFolder"> <button class="icon primary" @click="addFolder">
{{ $t("save") }} {{ $t("save") }}
</button> </button>
</span> </span>
@@ -52,7 +52,7 @@ export default {
}, },
props: { props: {
show: Boolean, show: Boolean,
collection: Object, folder: Object,
collectionIndex: Number, collectionIndex: Number,
}, },
data() { data() {
@@ -61,14 +61,6 @@ export default {
} }
}, },
methods: { methods: {
addNewFolder() {
this.$store.commit("postwoman/addNewFolder", {
folder: { name: this.$data.name },
collectionIndex: this.$props.collectionIndex,
})
this.hideModal()
this.syncCollections()
},
syncCollections() { syncCollections() {
if (fb.currentUser !== null) { if (fb.currentUser !== null) {
if (fb.currentSettings[0].value) { if (fb.currentSettings[0].value) {
@@ -76,6 +68,14 @@ export default {
} }
} }
}, },
addFolder() {
this.$store.commit("postwoman/addFolder", {
name: this.$data.name,
folder: this.$props.folder,
})
this.hideModal()
this.syncCollections()
},
hideModal() { hideModal() {
this.$emit("hide-modal") this.$emit("hide-modal")
}, },

View File

@@ -1,6 +1,14 @@
<template> <template>
<div> <div>
<div class="row-wrapper"> <div
:class="['row-wrapper', dragging ? 'drop-zone' : '']"
@dragover.prevent
@drop.prevent="dropEvent"
@dragover="dragging = true"
@drop="dragging = false"
@dragleave="dragging = false"
@dragend="dragging = false"
>
<button class="icon" @click="toggleShowChildren"> <button class="icon" @click="toggleShowChildren">
<i class="material-icons" v-show="!showChildren && !isFiltered">arrow_right</i> <i class="material-icons" v-show="!showChildren && !isFiltered">arrow_right</i>
<i class="material-icons" v-show="showChildren || isFiltered">arrow_drop_down</i> <i class="material-icons" v-show="showChildren || isFiltered">arrow_drop_down</i>
@@ -22,7 +30,11 @@
</button> </button>
<template slot="popover"> <template slot="popover">
<div> <div>
<button class="icon" @click="$emit('add-folder')" v-close-popover> <button
class="icon"
@click="$emit('add-folder', { folder: collection })"
v-close-popover
>
<i class="material-icons">create_new_folder</i> <i class="material-icons">create_new_folder</i>
<span>{{ $t("new_folder") }}</span> <span>{{ $t("new_folder") }}</span>
</button> </button>
@@ -53,18 +65,16 @@
> >
<folder <folder
:folder="folder" :folder="folder"
:folderIndex="index" :folder-index="index"
:collection-index="collectionIndex" :collection-index="collectionIndex"
:doc="doc" :doc="doc"
:isFiltered="isFiltered" :isFiltered="isFiltered"
@edit-folder="editFolder(collectionIndex, folder, index)" @add-folder="$emit('add-folder', $event)"
@edit-folder="$emit('edit-folder', $event)"
@edit-request="$emit('edit-request', $event)" @edit-request="$emit('edit-request', $event)"
/> />
</li> </li>
<li <li v-if="collection.folders.length === 0 && collection.requests.length === 0">
v-if="collection.folders.length === 0 && collection.requests.length === 0"
class="ml-8 border-l border-brdColor"
>
<label>{{ $t("collection_empty") }}</label> <label>{{ $t("collection_empty") }}</label>
</li> </li>
</ul> </ul>
@@ -78,16 +88,10 @@
:request="request" :request="request"
:collection-index="collectionIndex" :collection-index="collectionIndex"
:folder-index="-1" :folder-index="-1"
:folder-name="collection.name"
:request-index="index" :request-index="index"
:doc="doc" :doc="doc"
@edit-request=" @edit-request="$emit('edit-request', $event)"
$emit('edit-request', {
request,
collectionIndex,
folderIndex: undefined,
requestIndex: index,
})
"
/> />
</li> </li>
</ul> </ul>
@@ -111,6 +115,7 @@ export default {
data() { data() {
return { return {
showChildren: false, showChildren: false,
dragging: false,
selectedFolder: {}, selectedFolder: {},
} }
}, },
@@ -135,8 +140,22 @@ export default {
}) })
this.syncCollections() this.syncCollections()
}, },
editFolder(collectionIndex, folder, folderIndex) { dropEvent({ dataTransfer }) {
this.$emit("edit-folder", { collectionIndex, folder, folderIndex }) this.dragging = !this.dragging
const oldCollectionIndex = dataTransfer.getData("oldCollectionIndex")
const oldFolderIndex = dataTransfer.getData("oldFolderIndex")
const oldFolderName = dataTransfer.getData("oldFolderName")
const requestIndex = dataTransfer.getData("requestIndex")
this.$store.commit("postwoman/moveRequest", {
oldCollectionIndex,
newCollectionIndex: this.$props.collectionIndex,
newFolderIndex: -1,
newFolderName: this.$props.collection.name,
oldFolderIndex,
oldFolderName,
requestIndex,
})
this.syncCollections()
}, },
}, },
} }

View File

@@ -47,7 +47,6 @@ export default {
}, },
props: { props: {
show: Boolean, show: Boolean,
collection: Object,
collectionIndex: Number, collectionIndex: Number,
folder: Object, folder: Object,
folderIndex: Number, folderIndex: Number,
@@ -70,6 +69,7 @@ export default {
collectionIndex: this.$props.collectionIndex, collectionIndex: this.$props.collectionIndex,
folder: { ...this.$props.folder, name: this.$data.name }, folder: { ...this.$props.folder, name: this.$data.name },
folderIndex: this.$props.folderIndex, folderIndex: this.$props.folderIndex,
folderName: this.$props.folder.name,
}) })
this.hideModal() this.hideModal()
this.syncCollections() this.syncCollections()

View File

@@ -15,42 +15,14 @@
</ul> </ul>
</div> </div>
<div slot="body"> <div slot="body">
<ul> <label for="selectLabel">{{ $t("label") }}</label>
<li> <input
<label for="selectLabel">{{ $t("label") }}</label> type="text"
<input id="selectLabel"
type="text" v-model="requestUpdateData.name"
id="selectLabel" @keyup.enter="saveRequest"
v-model="requestUpdateData.name" :placeholder="request.name"
@keyup.enter="saveRequest" />
:placeholder="request.name"
/>
<label for="selectCollection">{{ $t("collection") }}</label>
<span class="select-wrapper">
<select type="text" id="selectCollection" v-model="requestUpdateData.collectionIndex">
<option :key="undefined" :value="undefined" hidden disabled selected>
{{ $t("current_collection") }}
</option>
<option
v-for="(collection, index) in $store.state.postwoman.collections"
:key="index"
:value="index"
>
{{ collection.name }}
</option>
</select>
</span>
<label for="selectFolder">{{ $t("folder") }}</label>
<span class="select-wrapper">
<select type="text" id="selectFolder" v-model="requestUpdateData.folderIndex">
<option :key="undefined" :value="undefined">/</option>
<option v-for="(folder, index) in folders" :key="index" :value="index">
{{ folder.name }}
</option>
</select>
</span>
</li>
</ul>
</div> </div>
<div slot="footer"> <div slot="footer">
<div class="row-wrapper"> <div class="row-wrapper">
@@ -80,6 +52,7 @@ export default {
show: Boolean, show: Boolean,
collectionIndex: Number, collectionIndex: Number,
folderIndex: Number, folderIndex: Number,
folderName: String,
request: Object, request: Object,
requestIndex: Number, requestIndex: Number,
}, },
@@ -87,27 +60,9 @@ export default {
return { return {
requestUpdateData: { requestUpdateData: {
name: undefined, name: undefined,
collectionIndex: undefined,
folderIndex: undefined,
}, },
} }
}, },
watch: {
"requestUpdateData.collectionIndex": function resetFolderIndex() {
// if user choosen some folder, than selected other collection, which doesn't have any folders
// than `requestUpdateData.folderIndex` won't be reseted
this.$data.requestUpdateData.folderIndex = undefined
},
},
computed: {
folders() {
const userSelectedAnyCollection = this.$data.requestUpdateData.collectionIndex !== undefined
if (!userSelectedAnyCollection) return []
return this.$store.state.postwoman.collections[this.$data.requestUpdateData.collectionIndex]
.folders
},
},
methods: { methods: {
syncCollections() { syncCollections() {
if (fb.currentUser !== null) { if (fb.currentUser !== null) {
@@ -117,26 +72,17 @@ export default {
} }
}, },
saveRequest() { saveRequest() {
const userSelectedAnyCollection = this.$data.requestUpdateData.collectionIndex !== undefined
const requestUpdated = { const requestUpdated = {
...this.$props.request, ...this.$props.request,
name: this.$data.requestUpdateData.name || this.$props.request.name, name: this.$data.requestUpdateData.name || this.$props.request.name,
collection: userSelectedAnyCollection
? this.$data.requestUpdateData.collectionIndex
: this.$props.collectionIndex,
folder: this.$data.requestUpdateData.folderIndex,
} }
// pass data separately to don't depend on request's collection, folder fields
// probably, they should be deprecated because they don't describe request itself
this.$store.commit("postwoman/editRequest", { this.$store.commit("postwoman/editRequest", {
requestOldCollectionIndex: this.$props.collectionIndex, requestCollectionIndex: this.$props.collectionIndex,
requestOldFolderIndex: this.$props.folderIndex, requestFolderName: this.$props.folderName,
requestOldIndex: this.$props.requestIndex, requestFolderIndex: this.$props.folderIndex,
requestNew: requestUpdated, requestNew: requestUpdated,
requestNewCollectionIndex: requestUpdated.collection, requestIndex: this.$props.requestIndex,
requestNewFolderIndex: requestUpdated.folder,
}) })
this.hideModal() this.hideModal()

View File

@@ -1,6 +1,14 @@
<template> <template>
<div> <div>
<div class="row-wrapper"> <div
:class="['row-wrapper', dragging ? 'drop-zone' : '']"
@dragover.prevent
@drop.prevent="dropEvent"
@dragover="dragging = true"
@drop="dragging = false"
@dragleave="dragging = false"
@dragend="dragging = false"
>
<div> <div>
<button class="icon" @click="toggleShowChildren"> <button class="icon" @click="toggleShowChildren">
<i class="material-icons" v-show="!showChildren && !isFiltered">arrow_right</i> <i class="material-icons" v-show="!showChildren && !isFiltered">arrow_right</i>
@@ -15,7 +23,17 @@
</button> </button>
<template slot="popover"> <template slot="popover">
<div> <div>
<button class="icon" @click="editFolder" v-close-popover> <button class="icon" @click="$emit('add-folder', { folder })" v-close-popover>
<i class="material-icons">create_new_folder</i>
<span>{{ $t("new_folder") }}</span>
</button>
</div>
<div>
<button
class="icon"
@click="$emit('edit-folder', { folder, folderIndex, collectionIndex })"
v-close-popover
>
<i class="material-icons">edit</i> <i class="material-icons">edit</i>
<span>{{ $t("edit") }}</span> <span>{{ $t("edit") }}</span>
</button> </button>
@@ -41,20 +59,24 @@
:request="request" :request="request"
:collection-index="collectionIndex" :collection-index="collectionIndex"
:folder-index="folderIndex" :folder-index="folderIndex"
:folder-name="folder.name"
:request-index="index" :request-index="index"
:doc="doc" :doc="doc"
@edit-request=" @edit-request="$emit('edit-request', $event)"
$emit('edit-request', {
request,
collectionIndex,
folderIndex,
requestIndex: index,
})
"
/> />
</li> </li>
<li v-if="folder.requests.length === 0" class="flex ml-8 border-l border-brdColor"> </ul>
<label>{{ $t("folder_empty") }}</label> <ul v-if="folder.folders && folder.folders.length" class="flex-col">
<li v-for="(subFolder, subFolderIndex) in folder.folders" :key="subFolder.name">
<folder
:folder="subFolder"
:folder-index="subFolderIndex"
:collection-index="collectionIndex"
:doc="doc"
@add-folder="$emit('add-folder', $event)"
@edit-folder="$emit('edit-folder', $event)"
@edit-request="$emit('edit-request', $event)"
/>
</li> </li>
</ul> </ul>
</div> </div>
@@ -67,16 +89,18 @@ import deleteIcon from "~/static/icons/delete-24px.svg?inline"
export default { export default {
components: { deleteIcon }, components: { deleteIcon },
name: "folder",
props: { props: {
folder: Object, folder: Object,
collectionIndex: Number,
folderIndex: Number, folderIndex: Number,
collectionIndex: Number,
doc: Boolean, doc: Boolean,
isFiltered: Boolean, isFiltered: Boolean,
}, },
data() { data() {
return { return {
showChildren: false, showChildren: false,
dragging: false,
} }
}, },
methods: { methods: {
@@ -90,22 +114,35 @@ export default {
toggleShowChildren() { toggleShowChildren() {
this.showChildren = !this.showChildren this.showChildren = !this.showChildren
}, },
selectRequest(request) {
this.$store.commit("postwoman/selectRequest", { request })
},
removeFolder() { removeFolder() {
if (!confirm(this.$t("are_you_sure_remove_folder"))) return if (!confirm(this.$t("are_you_sure_remove_folder"))) return
this.$store.commit("postwoman/removeFolder", { this.$store.commit("postwoman/removeFolder", {
collectionIndex: this.collectionIndex, collectionIndex: this.$props.collectionIndex,
folderIndex: this.folderIndex, folderName: this.$props.folder.name,
folderIndex: this.$props.folderIndex,
}) })
this.syncCollections() this.syncCollections()
this.$toast.error(this.$t("deleted"), { this.$toast.error(this.$t("deleted"), {
icon: "delete", icon: "delete",
}) })
}, },
editFolder() { dropEvent({ dataTransfer }) {
this.$emit("edit-folder") this.dragging = !this.dragging
const oldCollectionIndex = dataTransfer.getData("oldCollectionIndex")
const oldFolderIndex = dataTransfer.getData("oldFolderIndex")
const oldFolderName = dataTransfer.getData("oldFolderName")
const requestIndex = dataTransfer.getData("requestIndex")
this.$store.commit("postwoman/moveRequest", {
oldCollectionIndex,
newCollectionIndex: this.$props.collectionIndex,
newFolderIndex: this.$props.folderIndex,
newFolderName: this.$props.folder.name,
oldFolderIndex,
oldFolderName,
requestIndex,
})
this.syncCollections()
}, },
}, },
} }

View File

@@ -108,8 +108,8 @@ export default {
}, },
replaceWithJSON() { replaceWithJSON() {
let reader = new FileReader() let reader = new FileReader()
reader.onload = (event) => { reader.onload = ({ target }) => {
let content = event.target.result let content = target.result
let collections = JSON.parse(content) let collections = JSON.parse(content)
if (collections[0]) { if (collections[0]) {
let [name, folders, requests] = Object.keys(collections[0]) let [name, folders, requests] = Object.keys(collections[0])
@@ -117,7 +117,7 @@ export default {
// Do nothing // Do nothing
} }
} else if (collections.info && collections.info.schema.includes("v2.1.0")) { } else if (collections.info && collections.info.schema.includes("v2.1.0")) {
collections = this.parsePostmanCollection(collections) collections = [this.parsePostmanCollection(collections)]
} else { } else {
return this.failedImport() return this.failedImport()
} }
@@ -130,8 +130,8 @@ export default {
}, },
importFromJSON() { importFromJSON() {
let reader = new FileReader() let reader = new FileReader()
reader.onload = (event) => { reader.onload = ({ target }) => {
let content = event.target.result let content = target.result
let collections = JSON.parse(content) let collections = JSON.parse(content)
if (collections[0]) { if (collections[0]) {
let [name, folders, requests] = Object.keys(collections[0]) let [name, folders, requests] = Object.keys(collections[0])
@@ -141,8 +141,7 @@ export default {
} else if (collections.info && collections.info.schema.includes("v2.1.0")) { } else if (collections.info && collections.info.schema.includes("v2.1.0")) {
//replace the variables, postman uses {{var}}, Hoppscotch uses <<var>> //replace the variables, postman uses {{var}}, Hoppscotch uses <<var>>
collections = JSON.parse(content.replaceAll(/{{([a-z]+)}}/gi, "<<$1>>")) collections = JSON.parse(content.replaceAll(/{{([a-z]+)}}/gi, "<<$1>>"))
collections.item = this.flattenPostmanFolders(collections) collections = [this.parsePostmanCollection(collections)]
collections = this.parsePostmanCollection(collections)
} else { } else {
return this.failedImport() return this.failedImport()
} }
@@ -192,36 +191,30 @@ export default {
icon: "error", icon: "error",
}) })
}, },
parsePostmanCollection(collection, folders = true) { parsePostmanCollection({ info, name, item }) {
let postwomanCollection = folders let postwomanCollection = {
? [ name: "",
{ folders: [],
name: "", requests: [],
folders: [],
requests: [],
},
]
: {
name: "",
requests: [],
}
if (folders) {
//pick up collection name even when all children are folders
postwomanCollection[0].name = collection.info ? collection.info.name : ""
} }
for (let collectionItem of collection.item) {
if (collectionItem.request) { postwomanCollection.name = info ? info.name : name
if (postwomanCollection[0]) {
postwomanCollection[0].name = collection.info ? collection.info.name : "" if (item && item.length > 0) {
postwomanCollection[0].requests.push(this.parsePostmanRequest(collectionItem)) for (let collectionItem of item) {
if (collectionItem.request) {
if (postwomanCollection.hasOwnProperty("folders")) {
postwomanCollection.name = info ? info.name : name
postwomanCollection.requests.push(this.parsePostmanRequest(collectionItem))
} else {
postwomanCollection.name = name ? name : ""
postwomanCollection.requests.push(this.parsePostmanRequest(collectionItem))
}
} else if (this.hasFolder(collectionItem)) {
postwomanCollection.folders.push(this.parsePostmanCollection(collectionItem))
} else { } else {
postwomanCollection.name = collection.name ? collection.name : ""
postwomanCollection.requests.push(this.parsePostmanRequest(collectionItem)) postwomanCollection.requests.push(this.parsePostmanRequest(collectionItem))
} }
} else if (collectionItem.item) {
if (collectionItem.item[0]) {
postwomanCollection[0].folders.push(this.parsePostmanCollection(collectionItem, false))
}
} }
} }
return postwomanCollection return postwomanCollection
@@ -300,46 +293,8 @@ export default {
} }
return pwRequest return pwRequest
}, },
flattenPostmanFolders(collection) {
let items = []
for (let collectionItem of collection.item) {
if (this.hasFolder(collectionItem)) {
let newFolderItems = []
for (let folderItem of collectionItem.item) {
if (this.isSubFolder(folderItem)) {
newFolderItems = newFolderItems.concat(this.flattenPostmanItem(folderItem))
} else {
newFolderItems.push(folderItem)
}
}
collectionItem.item = newFolderItems
}
items.push(collectionItem)
}
return items
},
hasFolder(item) { hasFolder(item) {
return Object.prototype.hasOwnProperty.call(item, "item") return item.hasOwnProperty("item")
},
isSubFolder(item) {
return (
Object.prototype.hasOwnProperty.call(item, "_postman_isSubFolder") &&
item._postman_isSubFolder
)
},
flattenPostmanItem(subFolder, subFolderGlue = " -- ") {
delete subFolder._postman_isSubFolder
let flattenedItems = []
for (let subFolderItem of subFolder.item) {
subFolderItem.name = subFolder.name + subFolderGlue + subFolderItem.name
if (this.isSubFolder(subFolderItem)) {
flattenedItems = flattenedItems.concat(this.flattenPostmanItem(subFolderItem))
} else {
flattenedItems.push(subFolderItem)
}
}
return flattenedItems
}, },
}, },
} }

View File

@@ -14,30 +14,29 @@ TODO:
<add-collection :show="showModalAdd" @hide-modal="displayModalAdd(false)" /> <add-collection :show="showModalAdd" @hide-modal="displayModalAdd(false)" />
<edit-collection <edit-collection
:show="showModalEdit" :show="showModalEdit"
:editingCollection="editingCollection" :editing-collection="editingCollection"
:editingCollectionIndex="editingCollectionIndex" :editing-collection-index="editingCollectionIndex"
@hide-modal="displayModalEdit(false)" @hide-modal="displayModalEdit(false)"
/> />
<add-folder <add-folder
:show="showModalAddFolder" :show="showModalAddFolder"
:collection="editingCollection" :folder="editingFolder"
:collectionIndex="editingCollectionIndex"
@hide-modal="displayModalAddFolder(false)" @hide-modal="displayModalAddFolder(false)"
/> />
<edit-folder <edit-folder
:show="showModalEditFolder" :show="showModalEditFolder"
:collection="editingCollection" :collection-index="editingCollectionIndex"
:collectionIndex="editingCollectionIndex"
:folder="editingFolder" :folder="editingFolder"
:folderIndex="editingFolderIndex" :folder-index="editingFolderIndex"
@hide-modal="displayModalEditFolder(false)" @hide-modal="displayModalEditFolder(false)"
/> />
<edit-request <edit-request
:show="showModalEditRequest" :show="showModalEditRequest"
:collectionIndex="editingCollectionIndex" :collection-index="editingCollectionIndex"
:folderIndex="editingFolderIndex" :folder-index="editingFolderIndex"
:folder-name="editingFolderName"
:request="editingRequest" :request="editingRequest"
:requestIndex="editingRequestIndex" :request-index="editingRequestIndex"
@hide-modal="displayModalEditRequest(false)" @hide-modal="displayModalEditRequest(false)"
/> />
<import-export-collections <import-export-collections
@@ -74,12 +73,13 @@ TODO:
<ul class="flex-col"> <ul class="flex-col">
<li v-for="(collection, index) in filteredCollections" :key="collection.name"> <li v-for="(collection, index) in filteredCollections" :key="collection.name">
<collection <collection
:name="collection.name"
:collection-index="index" :collection-index="index"
:collection="collection" :collection="collection"
:doc="doc" :doc="doc"
:isFiltered="filterText.length > 0" :isFiltered="filterText.length > 0"
@edit-collection="editCollection(collection, index)" @edit-collection="editCollection(collection, index)"
@add-folder="addFolder(collection, index)" @add-folder="addFolder($event)"
@edit-folder="editFolder($event)" @edit-folder="editFolder($event)"
@edit-request="editRequest($event)" @edit-request="editRequest($event)"
@select-collection="$emit('use-collection', collection)" @select-collection="$emit('use-collection', collection)"
@@ -119,6 +119,7 @@ export default {
editingCollection: undefined, editingCollection: undefined,
editingCollectionIndex: undefined, editingCollectionIndex: undefined,
editingFolder: undefined, editingFolder: undefined,
editingFolderName: undefined,
editingFolderIndex: undefined, editingFolderIndex: undefined,
editingRequest: undefined, editingRequest: undefined,
editingRequestIndex: undefined, editingRequestIndex: undefined,
@@ -212,15 +213,14 @@ export default {
this.displayModalEdit(true) this.displayModalEdit(true)
this.syncCollections() this.syncCollections()
}, },
addFolder(collection, collectionIndex) { addFolder(payload) {
this.$data.editingCollection = collection const { folder } = payload
this.$data.editingCollectionIndex = collectionIndex this.$data.editingFolder = folder
this.displayModalAddFolder(true) this.displayModalAddFolder(true)
this.syncCollections() this.syncCollections()
}, },
editFolder(payload) { editFolder(payload) {
const { collection, collectionIndex, folder, folderIndex } = payload const { collectionIndex, folder, folderIndex } = payload
this.$data.editingCollection = collection
this.$data.editingCollectionIndex = collectionIndex this.$data.editingCollectionIndex = collectionIndex
this.$data.editingFolder = folder this.$data.editingFolder = folder
this.$data.editingFolderIndex = folderIndex this.$data.editingFolderIndex = folderIndex
@@ -228,9 +228,10 @@ export default {
this.syncCollections() this.syncCollections()
}, },
editRequest(payload) { editRequest(payload) {
const { request, collectionIndex, folderIndex, requestIndex } = payload const { collectionIndex, folderIndex, folderName, request, requestIndex } = payload
this.$data.editingCollectionIndex = collectionIndex this.$data.editingCollectionIndex = collectionIndex
this.$data.editingFolderIndex = folderIndex this.$data.editingFolderIndex = folderIndex
this.$data.editingFolderName = folderName
this.$data.editingRequest = request this.$data.editingRequest = request
this.$data.editingRequestIndex = requestIndex this.$data.editingRequestIndex = requestIndex
this.displayModalEditRequest(true) this.displayModalEditRequest(true)

View File

@@ -1,5 +1,12 @@
<template> <template>
<div class="row-wrapper"> <div
:class="['row-wrapper', dragging ? 'drag-el' : '']"
draggable="true"
@dragstart="dragStart"
@dragover.stop
@dragleave="dragging = false"
@dragend="dragging = false"
>
<div> <div>
<button <button
class="icon" class="icon"
@@ -16,7 +23,19 @@
</button> </button>
<template slot="popover"> <template slot="popover">
<div> <div>
<button class="icon" @click="$emit('edit-request')" v-close-popover> <button
class="icon"
@click="
$emit('edit-request', {
collectionIndex,
folderIndex,
folderName,
request,
requestIndex,
})
"
v-close-popover
>
<i class="material-icons">edit</i> <i class="material-icons">edit</i>
<span>{{ $t("edit") }}</span> <span>{{ $t("edit") }}</span>
</button> </button>
@@ -42,9 +61,15 @@ export default {
request: Object, request: Object,
collectionIndex: Number, collectionIndex: Number,
folderIndex: Number, folderIndex: Number,
folderName: String,
requestIndex: Number, requestIndex: Number,
doc: Boolean, doc: Boolean,
}, },
data() {
return {
dragging: false,
}
},
methods: { methods: {
syncCollections() { syncCollections() {
if (fb.currentUser !== null) { if (fb.currentUser !== null) {
@@ -56,12 +81,19 @@ export default {
selectRequest() { selectRequest() {
this.$store.commit("postwoman/selectRequest", { request: this.request }) this.$store.commit("postwoman/selectRequest", { request: this.request })
}, },
dragStart({ dataTransfer }) {
this.dragging = !this.dragging
dataTransfer.setData("oldCollectionIndex", this.$props.collectionIndex)
dataTransfer.setData("oldFolderIndex", this.$props.folderIndex)
dataTransfer.setData("oldFolderName", this.$props.folderName)
dataTransfer.setData("requestIndex", this.$props.requestIndex)
},
removeRequest() { removeRequest() {
if (!confirm(this.$t("are_you_sure_remove_request"))) return if (!confirm(this.$t("are_you_sure_remove_request"))) return
this.$store.commit("postwoman/removeRequest", { this.$store.commit("postwoman/removeRequest", {
collectionIndex: this.collectionIndex, collectionIndex: this.$props.collectionIndex,
folderIndex: this.folderIndex, folderName: this.$props.folderName,
requestIndex: this.requestIndex, requestIndex: this.$props.requestIndex,
}) })
this.$toast.error(this.$t("deleted"), { this.$toast.error(this.$t("deleted"), {
icon: "delete", icon: "delete",

View File

@@ -40,15 +40,13 @@
</option> </option>
</select> </select>
</span> </span>
<label for="selectFolder">{{ $t("folder") }}</label> <label>{{ $t("folder") }}</label>
<span class="select-wrapper"> <autocomplete
<select type="text" id="selectFolder" v-model="requestData.folderIndex"> :placeholder="$t('search')"
<option :key="undefined" :value="undefined">/</option> :source="folders"
<option v-for="(folder, index) in folders" :key="index" :value="index"> :spellcheck="false"
{{ folder.name }} v-model="requestData.folderName"
</option> />
</select>
</span>
<label for="selectRequest">{{ $t("request") }}</label> <label for="selectRequest">{{ $t("request") }}</label>
<span class="select-wrapper"> <span class="select-wrapper">
<select type="text" id="selectRequest" v-model="requestData.requestIndex"> <select type="text" id="selectRequest" v-model="requestData.requestIndex">
@@ -95,60 +93,62 @@ export default {
requestData: { requestData: {
name: undefined, name: undefined,
collectionIndex: undefined, collectionIndex: undefined,
folderIndex: undefined, folderName: undefined,
requestIndex: undefined, requestIndex: undefined,
}, },
} }
}, },
watch: { watch: {
"requestData.collectionIndex": function resetFolderAndRequestIndex() { "requestData.collectionIndex": function resetFolderAndRequestIndex() {
// if user choosen some folder, than selected other collection, which doesn't have any folders // if user has chosen some folder, than selected other collection, which doesn't have any folders
// than `requestUpdateData.folderIndex` won't be reseted // than `requestUpdateData.folderName` won't be reseted
this.$data.requestData.folderIndex = undefined this.$data.requestData.folderName = undefined
this.$data.requestData.requestIndex = undefined this.$data.requestData.requestIndex = undefined
}, },
"requestData.folderIndex": function resetRequestIndex() { "requestData.folderName": function resetRequestIndex() {
this.$data.requestData.requestIndex = undefined this.$data.requestData.requestIndex = undefined
}, },
editingRequest(request) { editingRequest({ label }) {
this.defaultRequestName = request.label || "My Request" this.defaultRequestName = label || "My Request"
}, },
}, },
computed: { computed: {
folders() { folders() {
const userSelectedAnyCollection = this.$data.requestData.collectionIndex !== undefined const collections = this.$store.state.postwoman.collections
const collectionIndex = this.$data.requestData.collectionIndex
const userSelectedAnyCollection = collectionIndex !== undefined
if (!userSelectedAnyCollection) return [] if (!userSelectedAnyCollection) return []
const noCollectionAvailable = const noCollectionAvailable = collections[collectionIndex] !== undefined
this.$store.state.postwoman.collections[this.$data.requestData.collectionIndex] !==
undefined
if (!noCollectionAvailable) return [] if (!noCollectionAvailable) return []
return this.$store.state.postwoman.collections[this.$data.requestData.collectionIndex].folders return getFolderNames(collections[collectionIndex].folders, [])
}, },
requests() { requests() {
const userSelectedAnyCollection = this.$data.requestData.collectionIndex !== undefined const collections = this.$store.state.postwoman.collections
if (!userSelectedAnyCollection) return [] const collectionIndex = this.$data.requestData.collectionIndex
const folderName = this.$data.requestData.folderName
const userSelectedAnyCollection = collectionIndex !== undefined
if (!userSelectedAnyCollection) {
return []
}
const userSelectedAnyFolder = folderName !== undefined && folderName !== ""
const userSelectedAnyFolder = this.$data.requestData.folderIndex !== undefined
if (userSelectedAnyFolder) { if (userSelectedAnyFolder) {
const collection = this.$store.state.postwoman.collections[ const collection = collections[collectionIndex]
this.$data.requestData.collectionIndex const folder = findFolder(folderName, collection)
] return folder.requests
const folder = collection.folders[this.$data.requestData.folderIndex]
const requests = folder.requests
return requests
} else { } else {
const collection = this.$store.state.postwoman.collections[ const collection = collections[collectionIndex]
this.$data.requestData.collectionIndex const noCollectionAvailable = collection !== undefined
]
const noCollectionAvailable =
this.$store.state.postwoman.collections[this.$data.requestData.collectionIndex] !==
undefined
if (!noCollectionAvailable) return []
const requests = collection.requests if (!noCollectionAvailable) {
return requests return []
}
return collection.requests
} }
}, },
}, },
@@ -178,7 +178,7 @@ export default {
this.$store.commit("postwoman/saveRequestAs", { this.$store.commit("postwoman/saveRequestAs", {
request: requestUpdated, request: requestUpdated,
collectionIndex: this.$data.requestData.collectionIndex, collectionIndex: this.$data.requestData.collectionIndex,
folderIndex: this.$data.requestData.folderIndex, folderName: this.$data.requestData.folderName,
requestIndex: this.$data.requestData.requestIndex, requestIndex: this.$data.requestData.requestIndex,
}) })
@@ -191,4 +191,36 @@ export default {
}, },
}, },
} }
function getFolderNames(folders, namesList) {
if (folders.length) {
folders.forEach((folder) => {
namesList.push(folder.name)
if (folder.folders && folder.folders.length) {
getFolderNames(folder.folders, namesList)
}
})
}
return namesList
}
function findFolder(folderName, currentFolder) {
let selectedFolder
let result
if (folderName === currentFolder.name) {
return currentFolder
}
for (let i = 0; i < currentFolder.folders.length; i++) {
selectedFolder = currentFolder.folders[i]
result = findFolder(folderName, selectedFolder)
if (result !== false) {
return result
}
}
return false
}
</script> </script>

View File

@@ -126,7 +126,7 @@ export default {
} }
}, },
watch: { watch: {
editingEnvironment: function (update) { editingEnvironment(update) {
this.name = this.name =
this.$props.editingEnvironment && this.$props.editingEnvironment.name this.$props.editingEnvironment && this.$props.editingEnvironment.name
? this.$props.editingEnvironment.name ? this.$props.editingEnvironment.name
@@ -151,13 +151,13 @@ export default {
} }
} }
}, },
clearContent(e) { clearContent({ target }) {
this.$store.commit("postwoman/removeVariables", []) this.$store.commit("postwoman/removeVariables", [])
e.target.innerHTML = this.doneButton target.innerHTML = this.doneButton
this.$toast.info(this.$t("cleared"), { this.$toast.info(this.$t("cleared"), {
icon: "clear_all", icon: "clear_all",
}) })
setTimeout(() => (e.target.innerHTML = '<i class="material-icons">clear_all</i>'), 1000) setTimeout(() => (target.innerHTML = '<i class="material-icons">clear_all</i>'), 1000)
}, },
addEnvironmentVariable() { addEnvironmentVariable() {
let value = { key: "", value: "" } let value = { key: "", value: "" }

View File

@@ -108,8 +108,8 @@ export default {
}, },
replaceWithJSON() { replaceWithJSON() {
let reader = new FileReader() let reader = new FileReader()
reader.onload = (event) => { reader.onload = ({ target }) => {
let content = event.target.result let content = target.result
let environments = JSON.parse(content) let environments = JSON.parse(content)
this.$store.commit("postwoman/replaceEnvironments", environments) this.$store.commit("postwoman/replaceEnvironments", environments)
} }
@@ -120,8 +120,8 @@ export default {
}, },
importFromJSON() { importFromJSON() {
let reader = new FileReader() let reader = new FileReader()
reader.onload = (event) => { reader.onload = ({ target }) => {
let content = event.target.result let content = target.result
let importFileObj = JSON.parse(content) let importFileObj = JSON.parse(content)
if ( if (
importFileObj["_postman_variable_scope"] === "environment" || importFileObj["_postman_variable_scope"] === "environment" ||
@@ -143,11 +143,9 @@ export default {
confirmation, confirmation,
}) })
}, },
importFromPostman(importFileObj) { importFromPostman({ name, values }) {
let environment = { name: importFileObj.name, variables: [] } let environment = { name, variables: [] }
importFileObj.values.forEach((element) => values.forEach(({ key, value }) => environment.variables.push({ key, value }))
environment.variables.push({ key: element.key, value: element.value })
)
let environments = [environment] let environments = [environment]
this.importFromPostwoman(environments) this.importFromPostwoman(environments)
}, },

View File

@@ -54,8 +54,8 @@ export default {
} }
}, },
methods: { methods: {
async deleteFeed(feed) { async deleteFeed({ id }) {
await fb.deleteFeed(feed.id) await fb.deleteFeed(id)
this.$toast.error(this.$t("deleted"), { this.$toast.error(this.$t("deleted"), {
icon: "delete", icon: "delete",
}) })

View File

@@ -35,9 +35,7 @@ export default {
}, },
methods: { methods: {
isFieldHighlighted({ field }) { isFieldHighlighted({ field }) {
return !!this.highlightedFields.find( return !!this.highlightedFields.find(({ name }) => name === field.name)
(highlightedField) => highlightedField.name === field.name
)
}, },
}, },
} }

View File

@@ -368,8 +368,8 @@ export default {
useHistory(entry) { useHistory(entry) {
this.$emit("useHistory", entry) this.$emit("useHistory", entry)
}, },
findEntryStatus(entry) { findEntryStatus({ status }) {
const foundStatusGroup = findStatusGroup(entry.status) const foundStatusGroup = findStatusGroup(status)
return ( return (
foundStatusGroup || { foundStatusGroup || {
className: "", className: "",

View File

@@ -359,34 +359,34 @@ export default {
}) })
} }
// let showAd = localStorage.getItem("showAd") === "no" let showAd = localStorage.getItem("showAd") === "no"
// if (!showAd) { if (!showAd) {
// setTimeout(() => { setTimeout(() => {
// this.$toast.clear() this.$toast.clear()
// this.$toast.show( this.$toast.show(
// "<span>Postwoman is now Hoppscotch 🎉<br><u><a href='https://dev.to/liyasthomas/postwoman-is-changing-name-igp' target='_blank' rel='noopener'>Read the announcement</a></u> →<br><sub>Whoosh this away to dismiss.</sub></span>", "<span><a href='https://github.com/sponsors/hoppscotch' target='_blank' rel='noopener'>Make a donation to support Hoppscotch open source project 🎉</a><br><sub>Whoosh this away to dismiss.</sub></span>",
// { {
// icon: "", icon: "",
// duration: 0, duration: 0,
// theme: "toasted-ad", theme: "toasted-ad",
// action: [ action: [
// { {
// text: "GitHub", text: "DONATE",
// icon: "chevron_right", icon: "favorite",
// onClick: (e, toastObject) => { onClick: (e, toastObject) => {
// // localStorage.setItem("showAd", "no") localStorage.setItem("showAd", "no")
// toastObject.goAway(0) toastObject.goAway(0)
// window.open("https://github.com/hoppscotch/hoppscotch") window.open("https://github.com/sponsors/hoppscotch")
// }, },
// }, },
// ], ],
// onComplete() { onComplete() {
// // localStorage.setItem("showAd", "no") localStorage.setItem("showAd", "no")
// }, },
// } }
// ) )
// }, 8000) }, 8000)
// } }
let showExtensionsToast = localStorage.getItem("showExtensionsToast") === "yes" let showExtensionsToast = localStorage.getItem("showExtensionsToast") === "yes"
@@ -452,11 +452,5 @@ export default {
} }
}, },
}, },
computed: {
availableLocales() {
return this.$i18n.locales.filter((i) => i.code !== this.$i18n.locale)
},
},
} }
</script> </script>

View File

@@ -73,6 +73,7 @@
</li> </li>
</ul> </ul>
</template> </template>
<script> <script>
import TextContentRendererMixin from "./mixins/TextContentRendererMixin" import TextContentRendererMixin from "./mixins/TextContentRendererMixin"

View File

@@ -50,8 +50,8 @@ export default {
const blob = new Blob([bytes.buffer]) const blob = new Blob([bytes.buffer])
const reader = new FileReader() const reader = new FileReader()
reader.onload = (e) => { reader.onload = ({ target }) => {
this.imageSource = e.target.result this.imageSource = target.result
} }
reader.readAsDataURL(blob) reader.readAsDataURL(blob)
}, },
@@ -65,8 +65,8 @@ export default {
const blob = new Blob([bytes.buffer]) const blob = new Blob([bytes.buffer])
const reader = new FileReader() const reader = new FileReader()
reader.onload = (e) => { reader.onload = ({ target }) => {
this.imageSource = e.target.result this.imageSource = target.result
} }
reader.readAsDataURL(blob) reader.readAsDataURL(blob)
}, },
@@ -78,7 +78,8 @@ export default {
const url = URL.createObjectURL(file) const url = URL.createObjectURL(file)
a.href = url a.href = url
// TODO get uri from meta // TODO get uri from meta
a.download = `response on ${Date()}`.replace(/\./g, "[dot]") a.download = `${url.split("/").pop().split("#")[0].split("?")[0]}.${this.responseType}`
// `response on ${Date()}`.replace(/\./g, "[dot]")
document.body.appendChild(a) document.body.appendChild(a)
a.click() a.click()
this.$refs.downloadResponse.innerHTML = this.doneButton this.$refs.downloadResponse.innerHTML = this.doneButton

View File

@@ -55,6 +55,7 @@
</li> </li>
</ul> </ul>
</template> </template>
<script> <script>
import { isJSONContentType } from "~/helpers/utils/contenttypes" import { isJSONContentType } from "~/helpers/utils/contenttypes"
import TextContentRendererMixin from "./mixins/TextContentRendererMixin" import TextContentRendererMixin from "./mixins/TextContentRendererMixin"

View File

@@ -55,6 +55,7 @@
</li> </li>
</ul> </ul>
</template> </template>
<script> <script>
import TextContentRendererMixin from "./mixins/TextContentRendererMixin" import TextContentRendererMixin from "./mixins/TextContentRendererMixin"

View File

@@ -47,7 +47,7 @@ export default {
methods: { methods: {
getSourcePrefix, getSourcePrefix,
}, },
updated: function () { updated() {
this.$nextTick(function () { this.$nextTick(function () {
if (this.$refs.log) { if (this.$refs.log) {
this.$refs.log.scrollBy(0, this.$refs.log.scrollHeight + 100) this.$refs.log.scrollBy(0, this.$refs.log.scrollHeight + 100)

View File

@@ -73,7 +73,7 @@ import Paho from "paho-mqtt"
import { wsValid } from "~/helpers/utils/valid" import { wsValid } from "~/helpers/utils/valid"
export default { export default {
data: function () { data() {
return { return {
url: "wss://test.mosquitto.org:8081", url: "wss://test.mosquitto.org:8081",
client: null, client: null,

View File

@@ -75,7 +75,7 @@ export default {
}) })
}, },
lang(value) { lang(value) {
this.editor.getSession().setMode("ace/mode/" + value) this.editor.getSession().setMode(`ace/mode/${value}`)
}, },
options(value) { options(value) {
this.editor.setOptions(value) this.editor.setOptions(value)

View File

@@ -10,7 +10,7 @@
@keydown="handleKeystroke" @keydown="handleKeystroke"
ref="acInput" ref="acInput"
:spellcheck="spellcheck" :spellcheck="spellcheck"
:autocapitalize="spellcheck" :autocapitalize="autocapitalize"
:autocorrect="spellcheck" :autocorrect="spellcheck"
/> />
<ul <ul
@@ -86,6 +86,12 @@ export default {
required: false, required: false,
}, },
autocapitalize: {
type: String,
default: "off",
required: false,
},
placeholder: { placeholder: {
type: String, type: String,
default: "", default: "",
@@ -190,12 +196,11 @@ export default {
return ( return (
this.source this.source
.filter((entry) => { .filter(
return ( (entry) =>
entry.toLowerCase().startsWith(input.toLowerCase()) && entry.toLowerCase().startsWith(input.toLowerCase()) &&
input.toLowerCase() !== entry.toLowerCase() input.toLowerCase() !== entry.toLowerCase()
) )
})
// Cut off the part that's already been typed. // Cut off the part that's already been typed.
.map((entry) => entry.substring(this.selectionStart)) .map((entry) => entry.substring(this.selectionStart))
// We only want the top 6 suggestions. // We only want the top 6 suggestions.

View File

@@ -121,35 +121,6 @@ beforeEach(async () => {
value: true, value: true,
}) })
await mocksdk
.firestore()
.collection("users")
.doc(testuser.uid)
.collection("settings")
.doc("syncTeams")
.set({
author: testuser.uid,
author_image: testuser.photoURL,
author_name: testuser.displayName,
name: "syncTeams",
updatedOn: new Date(1598703948000),
value: true,
})
await mocksdk
.firestore()
.collection("users")
.doc(testuser.uid)
.collection("teams")
.doc("sync")
.set({
author: testuser.uid,
author_image: testuser.photoURL,
author_name: testuser.displayName,
team: [],
updatedOn: new Date(1598703948000),
})
await mocksdk await mocksdk
.firestore() .firestore()
.collection("users") .collection("users")
@@ -1232,78 +1203,4 @@ describe("FirebaseInstance", () => {
) )
}) })
}) })
describe("writeTeams", () => {
test("resolves for proper authenticated request", async () => {
const fb = new FirebaseInstance(mocksdk)
signInUser()
await expect(fb.writeTeams([])).resolves.toBeUndefined()
})
test("rejects for non-authenticated request", async () => {
const fb = new FirebaseInstance(mocksdk)
signOutUser()
await expect(fb.writeTeams([])).rejects.toBeDefined()
})
test("stores data on firestore with proper structure", async () => {
const fb = new FirebaseInstance(mocksdk)
signInUser()
await fb.writeTeams([])
const doc = (
await mocksdk
.firestore()
.collection("users")
.doc(testuser.uid)
.collection("teams")
.doc("sync")
.get()
).data()
expect(doc).toEqual(
expect.objectContaining({
updatedOn: expect.any(Date),
author: expect.any(String),
author_name: expect.any(String),
author_image: expect.any(String),
team: expect.anything(),
})
)
})
test("stores data on firestore with fields having proper values", async () => {
const fb = new FirebaseInstance(mocksdk)
signInUser()
await fb.writeTeams([])
const doc = (
await mocksdk
.firestore()
.collection("users")
.doc(testuser.uid)
.collection("teams")
.doc("sync")
.get()
).data()
expect(doc).toEqual(
expect.objectContaining({
updatedOn: expect.any(Date),
author: testuser.uid,
author_name: testuser.displayName,
author_image: testuser.photoURL,
team: expect.anything(),
})
)
})
})
}) })

View File

@@ -32,7 +32,6 @@ export class FirebaseInstance {
this.currentHistory = [] this.currentHistory = []
this.currentCollections = [] this.currentCollections = []
this.currentEnvironments = [] this.currentEnvironments = []
this.currentTeams = []
this.app.auth().onAuthStateChanged((user) => { this.app.auth().onAuthStateChanged((user) => {
if (user) { if (user) {
@@ -121,19 +120,6 @@ export class FirebaseInstance {
this.currentEnvironments = environments[0].environment this.currentEnvironments = environments[0].environment
} }
}) })
this.usersCollection
.doc(this.currentUser.uid)
.collection("teams")
.onSnapshot((teamsRef) => {
const teams = []
teamsRef.forEach((doc) => {
const team = doc.data()
team.id = doc.id
teams.push(team)
})
this.currentTeams = teams[0].team
})
} else { } else {
this.currentUser = null this.currentUser = null
} }
@@ -302,24 +288,6 @@ export class FirebaseInstance {
throw e throw e
} }
} }
async writeTeams(team) {
const ev = {
updatedOn: new Date(),
author: this.currentUser.uid,
author_name: this.currentUser.displayName,
author_image: this.currentUser.photoURL,
team,
}
try {
await this.usersCollection.doc(this.currentUser.uid).collection("teams").doc("sync").set(ev)
} catch (e) {
console.error("error updating", ev, e)
throw e
}
}
} }
export const fb = new FirebaseInstance(firebase.initializeApp(firebaseConfig), authProviders) export const fb = new FirebaseInstance(firebase.initializeApp(firebaseConfig), authProviders)

View File

@@ -1,5 +1,4 @@
// Some helpful application constants. // Common options
// TODO: Use these when rendering the pages (rather than just for head/meta tags...)
export const options = { export const options = {
name: "Hoppscotch", name: "Hoppscotch",
shortDescription: "A free, fast and beautiful API request builder", shortDescription: "A free, fast and beautiful API request builder",
@@ -15,14 +14,20 @@ export const options = {
twitter: "@liyasthomas", twitter: "@liyasthomas",
}, },
} }
export default { export default {
// Disable server-side rendering (https://go.nuxtjs.dev/ssr-mode)
ssr: false, ssr: false,
// Target (https://go.nuxtjs.dev/config-target)
target: "static",
// Default: localhost
server: { server: {
host: "0.0.0.0", // default: localhost host: "0.0.0.0",
}, },
/*
** Headers of the page // Global page headers (https://go.nuxtjs.dev/config-head)
*/
head: { head: {
title: `${options.name}${options.shortDescription}`, title: `${options.name}${options.shortDescription}`,
meta: [ meta: [
@@ -81,66 +86,60 @@ export default {
}, },
], ],
}, },
/*
** Customize the progress-bar color // Customize the progress-bar color (https://nuxtjs.org/api/configuration-loading/#customizing-the-progress-bar)
*/
loading: { loading: {
color: options.loading.color, color: options.loading.color,
continuous: true, continuous: true,
}, },
/*
** Customize the loading indicator // Customize the loading indicator (https://nuxtjs.org/api/configuration-loading-indicator)
*/
loadingIndicator: { loadingIndicator: {
name: "pulse", name: "pulse",
color: options.loading.color, color: options.loading.color,
background: options.loading.background, background: options.loading.background,
}, },
/*
** Global CSS // Global CSS (https://go.nuxtjs.dev/config-css)
*/
css: ["~/assets/scss/styles.scss", "~/assets/scss/themes.scss", "~/assets/scss/fonts.scss"], css: ["~/assets/scss/styles.scss", "~/assets/scss/themes.scss", "~/assets/scss/fonts.scss"],
/*
** Plugins to load before mounting the App // Plugins to run before rendering page (https://go.nuxtjs.dev/config-plugins)
*/
plugins: ["~/plugins/vuex-persist", "~/plugins/v-tooltip"], plugins: ["~/plugins/vuex-persist", "~/plugins/v-tooltip"],
/*
** Auto import components // Auto import components (https://go.nuxtjs.dev/config-components)
** See https://nuxtjs.org/api/configuration-components
*/
components: true, components: true,
/*
** Nuxt.js dev-modules // Modules for dev and build (recommended) (https://go.nuxtjs.dev/config-modules)
*/
buildModules: [ buildModules: [
// https://pwa.nuxtjs.org // https://github.com/nuxt-community/pwa-module
"@nuxtjs/pwa", "@nuxtjs/pwa",
// Doc: https://github.com/nuxt-community/analytics-module // https://github.com/nuxt-community/analytics-module
"@nuxtjs/google-analytics", "@nuxtjs/google-analytics",
// Doc: https://github.com/nuxt-community/gtm-module // https://github.com/nuxt-community/gtm-module
"@nuxtjs/gtm", "@nuxtjs/gtm",
// Doc: https://github.com/nuxt-community/svg-module // https://github.com/nuxt-community/svg-module
"@nuxtjs/svg", "@nuxtjs/svg",
// Doc: https://tailwindcss.nuxtjs.org // https://github.com/nuxt-community/nuxt-tailwindcss
"@nuxtjs/tailwindcss", "@nuxtjs/tailwindcss",
// Doc: https://color-mode.nuxtjs.org // https://github.com/nuxt-community/color-mode-module
"@nuxtjs/color-mode", "@nuxtjs/color-mode",
], ],
/*
** Nuxt.js modules // Modules (https://go.nuxtjs.dev/config-modules)
*/
modules: [ modules: [
// https://axios.nuxtjs.org // https://github.com/nuxt-community/axios-module
"@nuxtjs/axios", "@nuxtjs/axios",
// https://github.com/nuxt-community/modules/tree/master/packages/toast // https://github.com/nuxt-community/modules/tree/master/packages/toast
"@nuxtjs/toast", "@nuxtjs/toast",
// Doc: https://github.com/nuxt-community/nuxt-i18n // https://github.com/nuxt-community/i18n-module
"nuxt-i18n", "nuxt-i18n",
// Doc: https://github.com/nuxt-community/robots-module // https://github.com/nuxt-community/robots-module
"@nuxtjs/robots", "@nuxtjs/robots",
// Doc: https://github.com/nuxt-community/sitemap-module // https://github.com/nuxt-community/sitemap-module
"@nuxtjs/sitemap", "@nuxtjs/sitemap",
], ],
// PWA module configuration (https://pwa.nuxtjs.org/setup)
pwa: { pwa: {
meta: { meta: {
ogHost: process.env.BASE_URL, ogHost: process.env.BASE_URL,
@@ -160,32 +159,46 @@ export default {
}, },
workbox: false, workbox: false,
}, },
// Toast module configuration (https://github.com/nuxt-community/modules/tree/master/packages/toast)
toast: { toast: {
position: "bottom-center", position: "bottom-center",
duration: 3000, duration: 3000,
theme: "bubble", theme: "bubble",
keepOnHover: true, keepOnHover: true,
}, },
// Google Analytics module configuration (https://github.com/nuxt-community/analytics-module)
googleAnalytics: { googleAnalytics: {
id: process.env.GA_ID, id: process.env.GA_ID,
}, },
// Google Tag Manager module configuration (https://github.com/nuxt-community/gtm-module)
gtm: { gtm: {
id: process.env.GTM_ID, id: process.env.GTM_ID,
}, },
// Sitemap module configuration (https://github.com/nuxt-community/sitemap-module)
sitemap: { sitemap: {
hostname: process.env.BASE_URL || "https://hoppscotch.io/", hostname: process.env.BASE_URL || "https://hoppscotch.io/",
}, },
// Robots module configuration (https://github.com/nuxt-community/robots-module)
robots: { robots: {
UserAgent: "*", UserAgent: "*",
Disallow: "", Disallow: "",
Allow: "/", Allow: "/",
Sitemap: `${process.env.BASE_URL}sitemap.xml`, Sitemap: `${process.env.BASE_URL}sitemap.xml`,
}, },
// Color Mode module configuration (https://github.com/nuxt-community/color-mode-module)
colorMode: { colorMode: {
classSuffix: "", classSuffix: "",
preference: "dark", preference: "dark",
fallback: "dark", fallback: "dark",
}, },
// i18n module configuration (https://github.com/nuxt-community/i18n-module)
i18n: { i18n: {
locales: [ locales: [
{ {
@@ -296,13 +309,10 @@ export default {
fallbackLocale: "en", fallbackLocale: "en",
}, },
}, },
/*
** Build configuration // Build Configuration (https://go.nuxtjs.dev/config-build)
*/
build: { build: {
/* // You can extend webpack config here
** You can extend webpack config here
*/
extend(config, ctx) { extend(config, ctx) {
// Sets webpack's mode to development if `isDev` is true. // Sets webpack's mode to development if `isDev` is true.
if (ctx.isDev) { if (ctx.isDev) {
@@ -316,17 +326,20 @@ export default {
cache: true, cache: true,
// hardSource: true, // hardSource: true,
}, },
/*
** Generate configuration // Generate configuration (https://nuxtjs.org/api/configuration-generate)
*/
generate: { generate: {
fallback: true, fallback: true,
}, },
// Public runtime configuration (https://nuxtjs.org/guide/runtime-config)
publicRuntimeConfig: { publicRuntimeConfig: {
GA_ID: process.env.GA_ID || "UA-61422507-4", GA_ID: process.env.GA_ID || "UA-61422507-4",
GTM_ID: process.env.GTM_ID || "GTM-NMKVBMV", GTM_ID: process.env.GTM_ID || "GTM-NMKVBMV",
BASE_URL: process.env.BASE_URL || "https://hoppscotch.io/", BASE_URL: process.env.BASE_URL || "https://hoppscotch.io/",
}, },
// Private runtime configuration (https://nuxtjs.org/guide/runtime-config)
privateRuntimeConfig: { privateRuntimeConfig: {
API_KEY: process.env.API_KEY, API_KEY: process.env.API_KEY,
AUTH_DOMAIN: process.env.AUTH_DOMAIN, AUTH_DOMAIN: process.env.AUTH_DOMAIN,

View File

@@ -11,15 +11,19 @@
type="url" type="url"
v-model="url" v-model="url"
spellcheck="false" spellcheck="false"
@keyup.enter="getSchema()" @keyup.enter="onPollSchemaClick()"
/> />
</li> </li>
<div> <div>
<li> <li>
<label for="get" class="hide-on-small-screen">&nbsp;</label> <label for="get" class="hide-on-small-screen">&nbsp;</label>
<button id="get" name="get" @click="getSchema"> <button id="get" name="get" @click="onPollSchemaClick">
{{ $t("get_schema") }} {{ !isPollingSchema ? $t("connect") : $t("disconnect") }}
<span><i class="material-icons">send</i></span> <span
><i class="material-icons">{{
!isPollingSchema ? "sync" : "sync_disabled"
}}</i></span
>
</button> </button>
</li> </li>
</div> </div>
@@ -356,6 +360,8 @@ export default {
expandResponse: false, expandResponse: false,
responseBodyMaxLines: 16, responseBodyMaxLines: 16,
graphqlFieldsFilterText: undefined, graphqlFieldsFilterText: undefined,
isPollingSchema: false,
timeoutSubscription: null,
settings: { settings: {
SCROLL_INTO_ENABLED: SCROLL_INTO_ENABLED:
@@ -455,6 +461,12 @@ export default {
this.getDocsFromSchema(gqlSchema) this.getDocsFromSchema(gqlSchema)
} }
}, },
beforeRouteLeave(_to, _from, next) {
this.isPollingSchema = false
if (this.timeoutSubscription) clearTimeout(this.timeoutSubscription)
next()
},
methods: { methods: {
isGqlTypeHighlighted({ gqlType }) { isGqlTypeHighlighted({ gqlType }) {
if (!this.graphqlFieldsFilterText) return false if (!this.graphqlFieldsFilterText) return false
@@ -471,12 +483,12 @@ export default {
if (!fields || fields.length === 0) return [] if (!fields || fields.length === 0) return []
return fields.filter((field) => { return fields.filter((field) =>
return this.isTextFoundInGraphqlFieldObject({ this.isTextFoundInGraphqlFieldObject({
text: this.graphqlFieldsFilterText, text: this.graphqlFieldsFilterText,
graphqlFieldObject: field, graphqlFieldObject: field,
}) })
}) )
}, },
isTextFoundInGraphqlFieldObject({ text, graphqlFieldObject }) { isTextFoundInGraphqlFieldObject({ text, graphqlFieldObject }) {
const normalizedText = text.toLowerCase() const normalizedText = text.toLowerCase()
@@ -491,9 +503,9 @@ export default {
getFilteredGraphqlFields({ filterText, fields }) { getFilteredGraphqlFields({ filterText, fields }) {
if (!filterText) return fields if (!filterText) return fields
return fields.filter((field) => { return fields.filter((field) =>
return this.isTextFoundInGraphqlFieldObject({ text: filterText, graphqlFieldObject: field }) this.isTextFoundInGraphqlFieldObject({ text: filterText, graphqlFieldObject: field })
}) )
}, },
getFilteredGraphqlTypes({ filterText, types }) { getFilteredGraphqlTypes({ filterText, types }) {
if (!filterText) return types if (!filterText) return types
@@ -509,12 +521,11 @@ export default {
} }
const isFilterTextMatchingAtLeastOneField = Object.values(type._fields || {}).some( const isFilterTextMatchingAtLeastOneField = Object.values(type._fields || {}).some(
(field) => { (field) =>
return this.isTextFoundInGraphqlFieldObject({ this.isTextFoundInGraphqlFieldObject({
text: filterText, text: filterText,
graphqlFieldObject: field, graphqlFieldObject: field,
}) })
}
) )
return isFilterTextMatchingAtLeastOneField return isFilterTextMatchingAtLeastOneField
@@ -682,6 +693,79 @@ export default {
} }
this.gqlTypes = types this.gqlTypes = types
}, },
async onPollSchemaClick() {
if (this.isPollingSchema) {
this.isPollingSchema = false
} else {
this.isPollingSchema = true
await this.getSchema()
this.pollSchema()
}
},
async pollSchema() {
if (!this.isPollingSchema) return
this.$nuxt.$loading.start()
try {
const query = JSON.stringify({
query: gql.getIntrospectionQuery(),
})
let headers = {}
this.headers.forEach(({ key, value }) => {
headers[key] = value
})
const reqOptions = {
method: "post",
url: this.url,
headers: {
...headers,
"content-type": "application/json",
},
data: query,
}
const data = await sendNetworkRequest(reqOptions, this.$store)
// HACK : Temporary trailing null character issue from the extension fix
const response = new TextDecoder("utf-8").decode(data.data).replace(/\0+$/, "")
const introspectResponse = JSON.parse(response)
const schema = gql.buildClientSchema(introspectResponse.data)
this.$store.commit("setGQLState", {
value: JSON.stringify(introspectResponse.data),
attribute: "schemaIntrospection",
})
this.schema = gql.printSchema(schema, {
commentDescriptions: true,
})
this.getDocsFromSchema(schema)
this.$refs.queryEditor.setValidationSchema(schema)
this.$nuxt.$loading.finish()
} catch (error) {
this.$nuxt.$loading.finish()
this.schema = `${error}. ${this.$t("check_console_details")}`
this.$toast.error(
`${this.$t("graphql_introspect_failed")} ${this.$t("check_graphql_valid")}`,
{
icon: "error",
}
)
console.log("Error", error)
}
this.$nuxt.$loading.finish()
if (this.isPollingSchema) this.timeoutSubscription = setTimeout(this.pollSchema, 7000)
},
async getSchema() { async getSchema() {
const startTime = Date.now() const startTime = Date.now()

View File

@@ -1361,7 +1361,7 @@ export default {
: true, : true,
}, },
currentMethodIndex: 0, currentMethodIndex: 0,
codegens: codegens, codegens,
methodMenuItems: [ methodMenuItems: [
"GET", "GET",
"HEAD", "HEAD",
@@ -1416,7 +1416,7 @@ export default {
this.setRouteQueryState() this.setRouteQueryState()
}, },
params: { params: {
handler: function (newValue) { handler(newValue) {
if (!this.paramsWatchEnabled) { if (!this.paramsWatchEnabled) {
this.paramsWatchEnabled = true this.paramsWatchEnabled = true
return return
@@ -1474,7 +1474,7 @@ export default {
? "application/json" ? "application/json"
: "" : ""
}, },
preRequestScript: function (val, oldVal) { preRequestScript(val, oldVal) {
this.uri = this.uri this.uri = this.uri
}, },
}, },
@@ -1826,7 +1826,7 @@ export default {
httpUser: this.httpUser, httpUser: this.httpUser,
httpPassword: this.httpPassword, httpPassword: this.httpPassword,
bearerToken: this.bearerToken, bearerToken: this.bearerToken,
headers: headers, headers,
rawInput: this.rawInput, rawInput: this.rawInput,
rawParams: this.rawParams, rawParams: this.rawParams,
rawRequestBody: this.rawRequestBody, rawRequestBody: this.rawRequestBody,
@@ -1860,7 +1860,7 @@ export default {
if (env.name === "Globals" || env.name === "globals") { if (env.name === "Globals" || env.name === "globals") {
preRequestScriptString += this.useSelectedEnvironment({ preRequestScriptString += this.useSelectedEnvironment({
environment: env, environment: env,
environments: environments, environments,
}) })
} }
} }
@@ -2390,6 +2390,7 @@ export default {
switch (name) { switch (name) {
case "bodyParams": case "bodyParams":
this.bodyParams = [] this.bodyParams = []
this.files = []
break break
case "rawParams": case "rawParams":
this.rawParams = "{}" this.rawParams = "{}"
@@ -2518,7 +2519,6 @@ export default {
icon: "attach_file", icon: "attach_file",
}) })
} }
this.$refs.attachment.value = ""
}, },
uploadPayload() { uploadPayload() {
this.rawInput = true this.rawInput = true

View File

@@ -257,9 +257,9 @@ export default {
watch: { watch: {
proxySettings: { proxySettings: {
deep: true, deep: true,
handler(value) { handler({ url, key }) {
this.applySetting("PROXY_URL", value.url) this.applySetting("PROXY_URL", url)
this.applySetting("PROXY_KEY", value.key) this.applySetting("PROXY_KEY", key)
}, },
}, },
}, },

View File

@@ -205,86 +205,70 @@ export const mutations = {
collections[collectionIndex] = collection collections[collectionIndex] = collection
}, },
addNewFolder({ collections }, payload) { addFolder({ collections }, payload) {
const { collectionIndex, folder } = payload const { name, folder } = payload
collections[collectionIndex].folders.push({
name: "", const newFolder = {
name: name,
requests: [], requests: [],
...folder, folders: [],
}) }
folder.folders.push(newFolder)
}, },
editFolder({ collections }, payload) { editFolder({ collections }, payload) {
const { collectionIndex, folder, folderIndex } = payload const { collectionIndex, folder, folderIndex, folderName } = payload
Vue.set(collections[collectionIndex].folders, folderIndex, folder) const collection = collections[collectionIndex]
let parentFolder = findFolder(folderName, collection, true)
if (parentFolder && parentFolder.folders) {
Vue.set(parentFolder.folders, folderIndex, folder)
}
}, },
removeFolder({ collections }, payload) { removeFolder({ collections }, payload) {
const { collectionIndex, folderIndex } = payload const { collectionIndex, folderIndex, folderName } = payload
collections[collectionIndex].folders.splice(folderIndex, 1) const collection = collections[collectionIndex]
},
addRequest({ collections }, payload) { let parentFolder = findFolder(folderName, collection, true)
const { request } = payload if (parentFolder && parentFolder.folders) {
parentFolder.folders.splice(folderIndex, 1)
// Request that is directly attached to collection
if (request.folder === -1) {
collections[request.collection].requests.push(request)
return
} }
collections[request.collection].folders[request.folder].requests.push(request)
}, },
editRequest({ collections }, payload) { editRequest({ collections }, payload) {
const { const {
requestOldCollectionIndex, requestCollectionIndex,
requestOldFolderIndex, requestFolderName,
requestOldIndex, requestFolderIndex,
requestNew, requestNew,
requestNewCollectionIndex, requestIndex,
requestNewFolderIndex,
} = payload } = payload
const changedCollection = requestOldCollectionIndex !== requestNewCollectionIndex let collection = collections[requestCollectionIndex]
const changedFolder = requestOldFolderIndex !== requestNewFolderIndex
const changedPlace = changedCollection || changedFolder
// set new request if (requestFolderIndex === -1) {
if (requestNewFolderIndex !== undefined) { Vue.set(collection.requests, requestIndex, requestNew)
Vue.set( return
collections[requestNewCollectionIndex].folders[requestNewFolderIndex].requests,
requestOldIndex,
requestNew
)
} else {
Vue.set(collections[requestNewCollectionIndex].requests, requestOldIndex, requestNew)
} }
// remove old request let folder = findFolder(requestFolderName, collection, false)
if (changedPlace) { Vue.set(folder.requests, requestIndex, requestNew)
if (requestOldFolderIndex !== undefined) {
collections[requestOldCollectionIndex].folders[requestOldFolderIndex].requests.splice(
requestOldIndex,
1
)
} else {
collections[requestOldCollectionIndex].requests.splice(requestOldIndex, 1)
}
}
}, },
saveRequestAs({ collections }, payload) { saveRequestAs({ collections }, payload) {
const { request, collectionIndex, folderIndex, requestIndex } = payload const { request, collectionIndex, folderName, requestIndex } = payload
const specifiedCollection = collectionIndex !== undefined const specifiedCollection = collectionIndex !== undefined
const specifiedFolder = folderIndex !== undefined const specifiedFolder = folderName !== undefined
const specifiedRequest = requestIndex !== undefined const specifiedRequest = requestIndex !== undefined
if (specifiedCollection && specifiedFolder && specifiedRequest) { if (specifiedCollection && specifiedFolder && specifiedRequest) {
Vue.set(collections[collectionIndex].folders[folderIndex].requests, requestIndex, request) const folder = findFolder(folderName, collections[collectionIndex])
Vue.set(folder.requests, requestIndex, request)
} else if (specifiedCollection && specifiedFolder && !specifiedRequest) { } else if (specifiedCollection && specifiedFolder && !specifiedRequest) {
const requests = collections[collectionIndex].folders[folderIndex].requests const folder = findFolder(folderName, collections[collectionIndex])
const requests = folder.requests
const lastRequestIndex = requests.length - 1 const lastRequestIndex = requests.length - 1
Vue.set(requests, lastRequestIndex + 1, request) Vue.set(requests, lastRequestIndex + 1, request)
} else if (specifiedCollection && !specifiedFolder && specifiedRequest) { } else if (specifiedCollection && !specifiedFolder && specifiedRequest) {
@@ -297,61 +281,53 @@ export const mutations = {
} }
}, },
saveRequest({ collections }, payload) {
const { request } = payload
// Remove the old request from collection
if (
Object.prototype.hasOwnProperty.call(request, "oldCollection") &&
request.oldCollection > -1
) {
const folder =
Object.prototype.hasOwnProperty.call(request, "oldFolder") && request.oldFolder >= -1
? request.oldFolder
: request.folder
if (folder > -1) {
collections[request.oldCollection].folders[folder].requests.splice(request.requestIndex, 1)
} else {
collections[request.oldCollection].requests.splice(request.requestIndex, 1)
}
} else if (
Object.prototype.hasOwnProperty.call(request, "oldFolder") &&
request.oldFolder !== -1
) {
collections[request.collection].folders[folder].requests.splice(request.requestIndex, 1)
}
delete request.oldCollection
delete request.oldFolder
// Request that is directly attached to collection
if (request.folder === -1) {
Vue.set(collections[request.collection].requests, request.requestIndex, request)
return
}
Vue.set(
collections[request.collection].folders[request.folder].requests,
request.requestIndex,
request
)
},
removeRequest({ collections }, payload) { removeRequest({ collections }, payload) {
const { collectionIndex, folderIndex, requestIndex } = payload const { collectionIndex, folderName, requestIndex } = payload
let collection = collections[collectionIndex]
// Request that is directly attached to collection if (collection.name === folderName) {
if (folderIndex === -1) { collection.requests.splice(requestIndex, 1)
collections[collectionIndex].requests.splice(requestIndex, 1)
return return
} }
let folder = findFolder(folderName, collection, false)
collections[collectionIndex].folders[folderIndex].requests.splice(requestIndex, 1) if (folder) {
folder.requests.splice(requestIndex, 1)
}
}, },
selectRequest(state, { request }) { selectRequest(state, { request }) {
state.selectedRequest = Object.assign({}, request) state.selectedRequest = Object.assign({}, request)
}, },
moveRequest({ collections }, payload) {
const {
oldCollectionIndex,
newCollectionIndex,
newFolderIndex,
newFolderName,
oldFolderName,
requestIndex,
} = payload
const isCollection = newFolderIndex === -1
const oldCollection = collections[oldCollectionIndex]
const newCollection = collections[newCollectionIndex]
const request = findRequest(oldFolderName, oldCollection, requestIndex)
if (isCollection) {
newCollection.requests.push(request)
return
}
if (!isCollection) {
const folder = findFolder(newFolderName, newCollection, false)
if (folder) {
folder.requests.push(request)
return
}
}
},
} }
function testValue(myValue) { function testValue(myValue) {
@@ -362,3 +338,45 @@ function testValue(myValue) {
return myValue return myValue
} }
} }
function findRequest(folderName, currentFolder, requestIndex) {
let selectedFolder, result
if (folderName === currentFolder.name) {
let request = currentFolder.requests[requestIndex]
currentFolder.requests.splice(requestIndex, 1)
return request
} else {
for (let i = 0; i < currentFolder.folders.length; i += 1) {
selectedFolder = currentFolder.folders[i]
result = findRequest(folderName, selectedFolder, requestIndex)
if (result !== false) {
return result
}
}
return false
}
}
function findFolder(folderName, currentFolder, returnParent, parentFolder) {
let selectedFolder, result
if (folderName === currentFolder.name && returnParent) {
return parentFolder
} else if (folderName === currentFolder.name && !returnParent) {
return currentFolder
} else {
for (let i = 0; i < currentFolder.folders.length; i++) {
selectedFolder = currentFolder.folders[i]
result = findFolder(folderName, selectedFolder, returnParent, currentFolder)
if (result !== false) {
return result
}
}
return false
}
}