refactor: headers system to respect data model
This commit is contained in:
@@ -39,7 +39,7 @@
|
|||||||
<div v-if="bulkMode" ref="bulkEditor"></div>
|
<div v-if="bulkMode" ref="bulkEditor"></div>
|
||||||
<div v-else>
|
<div v-else>
|
||||||
<div
|
<div
|
||||||
v-for="(header, index) in headers$"
|
v-for="(header, index) in workingHeaders"
|
||||||
:key="`header-${index}`"
|
:key="`header-${index}`"
|
||||||
class="flex border-b divide-x divide-dividerLight border-dividerLight"
|
class="flex border-b divide-x divide-dividerLight border-dividerLight"
|
||||||
>
|
>
|
||||||
@@ -106,9 +106,7 @@
|
|||||||
updateHeader(index, {
|
updateHeader(index, {
|
||||||
key: header.key,
|
key: header.key,
|
||||||
value: header.value,
|
value: header.value,
|
||||||
active: header.hasOwnProperty('active')
|
active: !header.active,
|
||||||
? !header.active
|
|
||||||
: false,
|
|
||||||
})
|
})
|
||||||
"
|
"
|
||||||
/>
|
/>
|
||||||
@@ -124,8 +122,8 @@
|
|||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
v-if="headers$.length === 0"
|
v-if="workingHeaders.length === 0"
|
||||||
class="flex flex-col items-center justify-center p-4 text-secondaryLight"
|
class="flex flex-col text-secondaryLight p-4 items-center justify-center"
|
||||||
>
|
>
|
||||||
<img
|
<img
|
||||||
:src="`/images/states/${$colorMode.value}/add_category.svg`"
|
:src="`/images/states/${$colorMode.value}/add_category.svg`"
|
||||||
@@ -149,32 +147,24 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { onBeforeUpdate, ref, watch } from "@nuxtjs/composition-api"
|
import { Ref, ref, watch } from "@nuxtjs/composition-api"
|
||||||
|
import isEqual from "lodash/isEqual"
|
||||||
|
import clone from "lodash/clone"
|
||||||
import { HoppRESTHeader } from "@hoppscotch/data"
|
import { HoppRESTHeader } from "@hoppscotch/data"
|
||||||
import { useCodemirror } from "~/helpers/editor/codemirror"
|
import { useCodemirror } from "~/helpers/editor/codemirror"
|
||||||
import {
|
import { restHeaders$, setRESTHeaders } from "~/newstore/RESTSession"
|
||||||
addRESTHeader,
|
|
||||||
deleteAllRESTHeaders,
|
|
||||||
deleteRESTHeader,
|
|
||||||
restHeaders$,
|
|
||||||
setRESTHeaders,
|
|
||||||
updateRESTHeader,
|
|
||||||
} from "~/newstore/RESTSession"
|
|
||||||
import { commonHeaders } from "~/helpers/headers"
|
import { commonHeaders } from "~/helpers/headers"
|
||||||
import {
|
import { useI18n, useStream, useToast } from "~/helpers/utils/composables"
|
||||||
useReadonlyStream,
|
|
||||||
useI18n,
|
|
||||||
useToast,
|
|
||||||
} from "~/helpers/utils/composables"
|
|
||||||
|
|
||||||
const t = useI18n()
|
const t = useI18n()
|
||||||
|
|
||||||
const toast = useToast()
|
const toast = useToast()
|
||||||
|
|
||||||
const bulkMode = ref(false)
|
const bulkMode = ref(false)
|
||||||
const bulkHeaders = ref("")
|
const bulkHeaders = ref("")
|
||||||
const bulkEditor = ref<any | null>(null)
|
const bulkEditor = ref<any | null>(null)
|
||||||
|
|
||||||
|
const deletionToast = ref<{ goAway: (delay: number) => void } | null>(null)
|
||||||
|
|
||||||
useCodemirror(bulkEditor, bulkHeaders, {
|
useCodemirror(bulkEditor, bulkHeaders, {
|
||||||
extendedEditorConfig: {
|
extendedEditorConfig: {
|
||||||
mode: "text/x-yaml",
|
mode: "text/x-yaml",
|
||||||
@@ -185,92 +175,162 @@ useCodemirror(bulkEditor, bulkHeaders, {
|
|||||||
environmentHighlights: true,
|
environmentHighlights: true,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// The functional headers list (the headers actually in the system)
|
||||||
|
const headers = useStream(restHeaders$, [], setRESTHeaders) as Ref<
|
||||||
|
HoppRESTHeader[]
|
||||||
|
>
|
||||||
|
|
||||||
|
// The UI representation of the headers list (has the empty end header)
|
||||||
|
const workingHeaders = ref<HoppRESTHeader[]>([
|
||||||
|
{
|
||||||
|
key: "",
|
||||||
|
value: "",
|
||||||
|
active: true,
|
||||||
|
},
|
||||||
|
])
|
||||||
|
|
||||||
|
// Rule: Working Headers always have one empty header or the last element is always an empty header
|
||||||
|
watch(workingHeaders, (headersList) => {
|
||||||
|
if (
|
||||||
|
headersList.length > 0 &&
|
||||||
|
headersList[headersList.length - 1].key !== ""
|
||||||
|
) {
|
||||||
|
workingHeaders.value.push({
|
||||||
|
key: "",
|
||||||
|
value: "",
|
||||||
|
active: true,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// Sync logic between headers and working headers
|
||||||
|
watch(
|
||||||
|
headers,
|
||||||
|
(newHeadersList) => {
|
||||||
|
// Sync should overwrite working headers
|
||||||
|
const filteredWorkingHeaders = workingHeaders.value.filter(
|
||||||
|
(e) => e.key !== ""
|
||||||
|
)
|
||||||
|
|
||||||
|
if (!isEqual(newHeadersList, filteredWorkingHeaders)) {
|
||||||
|
workingHeaders.value = newHeadersList
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{ immediate: true }
|
||||||
|
)
|
||||||
|
|
||||||
|
watch(workingHeaders, (newWorkingHeaders) => {
|
||||||
|
const fixedHeaders = newWorkingHeaders.filter((e) => e.key !== "")
|
||||||
|
if (!isEqual(headers.value, fixedHeaders)) {
|
||||||
|
headers.value = fixedHeaders
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// Bulk Editor Syncing with Working Headers
|
||||||
watch(bulkHeaders, () => {
|
watch(bulkHeaders, () => {
|
||||||
try {
|
try {
|
||||||
const transformation = bulkHeaders.value.split("\n").map((item) => ({
|
const transformation = bulkHeaders.value
|
||||||
key: item.substring(0, item.indexOf(":")).trim().replace(/^#/, ""),
|
.split("\n")
|
||||||
value: item.substring(item.indexOf(":") + 1).trim(),
|
.filter((x) => x.trim().length > 0 && x.includes(":"))
|
||||||
active: !item.trim().startsWith("#"),
|
.map((item) => ({
|
||||||
|
key: item
|
||||||
|
.substring(0, item.indexOf(":"))
|
||||||
|
.trimLeft()
|
||||||
|
.replace(/^\/\//, ""),
|
||||||
|
value: item.substring(item.indexOf(":") + 1).trimLeft(),
|
||||||
|
active: !item.trim().startsWith("//"),
|
||||||
}))
|
}))
|
||||||
setRESTHeaders(transformation as HoppRESTHeader[])
|
|
||||||
|
const filteredHeaders = workingHeaders.value.filter((x) => x.key !== "")
|
||||||
|
|
||||||
|
if (!isEqual(filteredHeaders, transformation)) {
|
||||||
|
workingHeaders.value = transformation
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
toast.error(`${t("error.something_went_wrong")}`)
|
||||||
|
console.error(e)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
watch(workingHeaders, (newHeadersList) => {
|
||||||
|
try {
|
||||||
|
const currentBulkHeaders = bulkHeaders.value.split("\n").map((item) => ({
|
||||||
|
key: item.substring(0, item.indexOf(":")).trimLeft().replace(/^\/\//, ""),
|
||||||
|
value: item.substring(item.indexOf(":") + 1).trimLeft(),
|
||||||
|
active: !item.trim().startsWith("//"),
|
||||||
|
}))
|
||||||
|
|
||||||
|
const filteredHeaders = newHeadersList.filter((x) => x.key !== "")
|
||||||
|
|
||||||
|
if (!isEqual(currentBulkHeaders, filteredHeaders)) {
|
||||||
|
bulkHeaders.value = filteredHeaders
|
||||||
|
.map((header) => {
|
||||||
|
return `${header.active ? "" : "//"}${header.key}: ${header.value}`
|
||||||
|
})
|
||||||
|
.join("\n")
|
||||||
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
toast.error(`${t("error.something_went_wrong")}`)
|
toast.error(`${t("error.something_went_wrong")}`)
|
||||||
console.error(e)
|
console.error(e)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
const headers$ = useReadonlyStream(restHeaders$, [])
|
|
||||||
|
|
||||||
watch(
|
|
||||||
headers$,
|
|
||||||
(newValue) => {
|
|
||||||
if (!bulkMode.value)
|
|
||||||
if (
|
|
||||||
(newValue[newValue.length - 1]?.key !== "" ||
|
|
||||||
newValue[newValue.length - 1]?.value !== "") &&
|
|
||||||
newValue.length
|
|
||||||
)
|
|
||||||
addHeader()
|
|
||||||
},
|
|
||||||
{ deep: true }
|
|
||||||
)
|
|
||||||
|
|
||||||
onBeforeUpdate(() => editBulkHeadersLine(-1, null))
|
|
||||||
|
|
||||||
const editBulkHeadersLine = (index: number, item?: HoppRESTHeader | null) => {
|
|
||||||
bulkHeaders.value = headers$.value
|
|
||||||
.reduce((all, header, pIndex) => {
|
|
||||||
const current =
|
|
||||||
index === pIndex && item != null
|
|
||||||
? `${item.active ? "" : "#"}${item.key}: ${item.value}`
|
|
||||||
: `${header.active ? "" : "#"}${header.key}: ${header.value}`
|
|
||||||
return [...all, current]
|
|
||||||
}, [])
|
|
||||||
.join("\n")
|
|
||||||
}
|
|
||||||
|
|
||||||
const clearBulkEditor = () => {
|
|
||||||
bulkHeaders.value = ""
|
|
||||||
}
|
|
||||||
|
|
||||||
const addHeader = () => {
|
const addHeader = () => {
|
||||||
const empty = { key: "", value: "", active: true }
|
workingHeaders.value.push({
|
||||||
const index = headers$.value.length
|
key: "",
|
||||||
|
value: "",
|
||||||
addRESTHeader(empty)
|
active: true,
|
||||||
editBulkHeadersLine(index, empty)
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
const updateHeader = (index: number, item: HoppRESTHeader) => {
|
const updateHeader = (index: number, header: HoppRESTHeader) => {
|
||||||
updateRESTHeader(index, item)
|
workingHeaders.value = workingHeaders.value.map((h, i) =>
|
||||||
editBulkHeadersLine(index, item)
|
i === index ? header : h
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
const deleteHeader = (index: number) => {
|
const deleteHeader = (index: number) => {
|
||||||
const headersBeforeDeletion = headers$.value
|
const headersBeforeDeletion = clone(workingHeaders.value)
|
||||||
|
|
||||||
deleteRESTHeader(index)
|
if (
|
||||||
editBulkHeadersLine(index, null)
|
!(
|
||||||
|
headersBeforeDeletion.length > 0 &&
|
||||||
|
index === headersBeforeDeletion.length - 1
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
if (deletionToast.value) {
|
||||||
|
deletionToast.value.goAway(0)
|
||||||
|
deletionToast.value = null
|
||||||
|
}
|
||||||
|
|
||||||
const deletedItem = headersBeforeDeletion[index]
|
deletionToast.value = toast.success(`${t("state.deleted")}`, {
|
||||||
if (deletedItem.key || deletedItem.value) {
|
|
||||||
toast.success(`${t("state.deleted")}`, {
|
|
||||||
action: [
|
action: [
|
||||||
{
|
{
|
||||||
text: `${t("action.undo")}`,
|
text: `${t("action.undo")}`,
|
||||||
onClick: (_, toastObject) => {
|
onClick: (_, toastObject) => {
|
||||||
setRESTHeaders(headersBeforeDeletion as HoppRESTHeader[])
|
workingHeaders.value = headersBeforeDeletion
|
||||||
editBulkHeadersLine(index, deletedItem)
|
|
||||||
toastObject.goAway(0)
|
toastObject.goAway(0)
|
||||||
|
deletionToast.value = null
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
|
||||||
|
onComplete: () => {
|
||||||
|
deletionToast.value = null
|
||||||
|
},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
workingHeaders.value.splice(index, 1)
|
||||||
}
|
}
|
||||||
|
|
||||||
const clearContent = () => {
|
const clearContent = () => {
|
||||||
deleteAllRESTHeaders()
|
// set headers list to the initial state
|
||||||
clearBulkEditor()
|
workingHeaders.value = [
|
||||||
|
{
|
||||||
|
key: "",
|
||||||
|
value: "",
|
||||||
|
active: true,
|
||||||
|
},
|
||||||
|
]
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ export const defaultRESTRequest: HoppRESTRequest = {
|
|||||||
endpoint: "https://echo.hoppscotch.io",
|
endpoint: "https://echo.hoppscotch.io",
|
||||||
name: "Untitled request",
|
name: "Untitled request",
|
||||||
params: [{ key: "", value: "", active: true }],
|
params: [{ key: "", value: "", active: true }],
|
||||||
headers: [{ key: "", value: "", active: true }],
|
headers: [],
|
||||||
method: "GET",
|
method: "GET",
|
||||||
auth: {
|
auth: {
|
||||||
authType: "none",
|
authType: "none",
|
||||||
|
|||||||
Reference in New Issue
Block a user