refactor: better logic for graphql headers key-value <=> bulk editor

This commit is contained in:
liyasthomas
2021-12-31 23:47:48 +05:30
parent 4ecd69cc5a
commit e56dcf5d7a

View File

@@ -131,7 +131,7 @@
v-tippy="{ theme: 'tooltip' }"
:title="t('action.clear_all')"
svg="trash-2"
@click.native="clearHeaders()"
@click.native="clearContent()"
/>
<ButtonSecondary
v-tippy="{ theme: 'tooltip' }"
@@ -145,14 +145,14 @@
:title="t('add.new')"
svg="plus"
:disabled="bulkMode"
@click.native="addRequestHeader"
@click.native="addHeader"
/>
</div>
</div>
<div v-if="bulkMode" ref="bulkEditor"></div>
<div v-else>
<div
v-for="(header, index) in headers"
v-for="(header, index) in workingHeaders"
:key="`header-${String(index)}`"
class="flex border-b divide-x divide-dividerLight border-dividerLight"
>
@@ -172,7 +172,7 @@
"
class="flex-1 !flex"
@input="
updateRequestHeader(index, {
updateHeader(index, {
key: $event,
value: header.value,
active: header.active,
@@ -186,7 +186,7 @@
:value="header.value"
autofocus
@change="
updateRequestHeader(index, {
updateHeader(index, {
key: header.key,
value: $event.target.value,
active: header.active,
@@ -212,7 +212,7 @@
"
color="green"
@click.native="
updateRequestHeader(index, {
updateHeader(index, {
key: header.key,
value: header.value,
active: !header.active,
@@ -226,12 +226,12 @@
:title="t('action.remove')"
svg="trash"
color="red"
@click.native="removeRequestHeader(index)"
@click.native="deleteHeader(index)"
/>
</span>
</div>
<div
v-if="headers.length === 0"
v-if="workingHeaders.length === 0"
class="flex flex-col items-center justify-center p-4 text-secondaryLight"
>
<img
@@ -248,7 +248,7 @@
filled
svg="plus"
class="mb-4"
@click.native="addRequestHeader"
@click.native="addHeader"
/>
</div>
</div>
@@ -263,16 +263,11 @@
</template>
<script setup lang="ts">
import {
computed,
onMounted,
reactive,
ref,
watch,
} from "@nuxtjs/composition-api"
import { Ref, computed, reactive, ref, watch } from "@nuxtjs/composition-api"
import clone from "lodash/clone"
import * as gql from "graphql"
import { GQLHeader, makeGQLRequest } from "@hoppscotch/data"
import isEqual from "lodash/isEqual"
import { copyToClipboard } from "~/helpers/utils/clipboard"
import {
useNuxt,
@@ -282,18 +277,15 @@ import {
useToast,
} from "~/helpers/utils/composables"
import {
addGQLHeader,
gqlHeaders$,
gqlQuery$,
gqlResponse$,
gqlURL$,
gqlVariables$,
removeGQLHeader,
setGQLHeaders,
setGQLQuery,
setGQLResponse,
setGQLVariables,
updateGQLHeader,
} from "~/newstore/GQLSession"
import { commonHeaders } from "~/helpers/headers"
import { GQLConnection } from "~/helpers/GQLConnection"
@@ -314,33 +306,18 @@ const props = defineProps<{
}>()
const toast = useToast()
const nuxt = useNuxt()
const bulkMode = ref(false)
const bulkHeaders = ref("")
watch(bulkHeaders, () => {
try {
const transformation = bulkHeaders.value.split("\n").map((item) => ({
key: item.substring(0, item.indexOf(":")).trim().replace(/^#/, ""),
value: item.substring(item.indexOf(":") + 1).trim(),
active: !item.trim().startsWith("#"),
}))
setGQLHeaders(transformation as GQLHeader[])
} catch (e) {
toast.error(`${t("error.something_went_wrong")}`)
console.error(e)
}
})
const url = useReadonlyStream(gqlURL$, "")
const gqlQueryString = useStream(gqlQuery$, "", setGQLQuery)
const variableString = useStream(gqlVariables$, "", setGQLVariables)
const headers = useStream(gqlHeaders$, [], setGQLHeaders)
const bulkMode = ref(false)
const bulkHeaders = ref("")
const bulkEditor = ref<any | null>(null)
const deletionToast = ref<{ goAway: (delay: number) => void } | null>(null)
useCodemirror(bulkEditor, bulkHeaders, {
extendedEditorConfig: {
mode: "text/x-yaml",
@@ -351,6 +328,166 @@ useCodemirror(bulkEditor, bulkHeaders, {
environmentHighlights: false,
})
// The functional headers list (the headers actually in the system)
const headers = useStream(gqlHeaders$, [], setGQLHeaders) as Ref<GQLHeader[]>
// The UI representation of the headers list (has the empty end header)
const workingHeaders = ref<GQLHeader[]>([
{
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, () => {
try {
const transformation = bulkHeaders.value
.split("\n")
.filter((x) => x.trim().length > 0 && x.includes(":"))
.map((item) => ({
key: item.substring(0, item.indexOf(":")).trimLeft().replace(/^#/, ""),
value: item.substring(item.indexOf(":") + 1).trimLeft(),
active: !item.trim().startsWith("#"),
}))
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) => {
// If we are in bulk mode, don't apply direct changes
if (bulkMode.value) return
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) {
toast.error(`${t("error.something_went_wrong")}`)
console.error(e)
}
})
const addHeader = () => {
workingHeaders.value.push({
key: "",
value: "",
active: true,
})
}
const updateHeader = (index: number, header: GQLHeader) => {
workingHeaders.value = workingHeaders.value.map((h, i) =>
i === index ? header : h
)
}
const deleteHeader = (index: number) => {
const headersBeforeDeletion = clone(workingHeaders.value)
if (
!(
headersBeforeDeletion.length > 0 &&
index === headersBeforeDeletion.length - 1
)
) {
if (deletionToast.value) {
deletionToast.value.goAway(0)
deletionToast.value = null
}
deletionToast.value = toast.success(`${t("state.deleted")}`, {
action: [
{
text: `${t("action.undo")}`,
onClick: (_, toastObject) => {
workingHeaders.value = headersBeforeDeletion
toastObject.goAway(0)
deletionToast.value = null
},
},
],
onComplete: () => {
deletionToast.value = null
},
})
}
workingHeaders.value.splice(index, 1)
}
const clearContent = () => {
// set headers list to the initial state
workingHeaders.value = [
{
key: "",
value: "",
active: true,
},
]
bulkHeaders.value = ""
}
const activeGQLHeadersCount = computed(
() =>
headers.value.filter((x) => x.active && (x.key !== "" || x.value !== ""))
@@ -395,42 +532,6 @@ const prettifyVariablesIcon = ref("wand")
const showSaveRequestModal = ref(false)
watch(
headers,
() => {
if (!bulkMode.value)
if (
(headers.value[headers.value.length - 1]?.key !== "" ||
headers.value[headers.value.length - 1]?.value !== "") &&
headers.value.length
)
addRequestHeader()
},
{ deep: true }
)
const editBulkHeadersLine = (index: number, item?: GQLHeader | 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]
}, [] as string[])
.join("\n")
}
const clearBulkEditor = () => {
bulkHeaders.value = ""
}
onMounted(() => {
if (!headers.value?.length) {
addRequestHeader()
}
})
const copyQuery = () => {
copyToClipboard(gqlQueryString.value)
copyQueryIcon.value = "check"
@@ -535,50 +636,6 @@ const prettifyVariableString = () => {
setTimeout(() => (prettifyVariablesIcon.value = "wand"), 1000)
}
const addRequestHeader = () => {
const empty = { key: "", value: "", active: true }
const index = headers.value.length
addGQLHeader(empty)
editBulkHeadersLine(index, empty)
}
const updateRequestHeader = (
index: number,
item: { key: string; value: string; active: boolean }
) => {
updateGQLHeader(index, item)
editBulkHeadersLine(index, item)
}
const removeRequestHeader = (index: number) => {
const headersBeforeDeletion = headers.value
removeGQLHeader(index)
editBulkHeadersLine(index, null)
const deletedItem = headersBeforeDeletion[index]
if (deletedItem.key || deletedItem.value) {
toast.success(`${t("state.deleted")}`, {
action: [
{
text: `${t("action.undo")}`,
onClick: (_, toastObject) => {
setGQLHeaders(headersBeforeDeletion as GQLHeader[])
editBulkHeadersLine(index, deletedItem)
toastObject.goAway(0)
},
},
],
})
}
}
const clearHeaders = () => {
headers.value = []
clearBulkEditor()
}
const clearGQLQuery = () => {
gqlQueryString.value = ""
}