refactor: headers system to respect data model

This commit is contained in:
Andrew Bastin
2021-12-31 15:28:47 +05:30
parent 50e30ee4ea
commit ca131697b6
2 changed files with 139 additions and 79 deletions

View File

@@ -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>

View File

@@ -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",