feat: rest revamp (#2918)
Co-authored-by: Liyas Thomas <liyascthomas@gmail.com> Co-authored-by: Nivedin <53208152+nivedin@users.noreply.github.com> Co-authored-by: Andrew Bastin <andrewbastin.k@gmail.com>
This commit is contained in:
@@ -22,6 +22,7 @@
|
||||
<slot name="primary" />
|
||||
</Pane>
|
||||
<Pane
|
||||
v-if="hasSecondary"
|
||||
:size="PANE_MAIN_BOTTOM_SIZE"
|
||||
class="flex flex-col !overflow-auto"
|
||||
>
|
||||
@@ -62,6 +63,7 @@ const SIDEBAR = useSetting("SIDEBAR")
|
||||
const slots = useSlots()
|
||||
|
||||
const hasSidebar = computed(() => !!slots.sidebar)
|
||||
const hasSecondary = computed(() => !!slots.secondary)
|
||||
|
||||
const props = defineProps({
|
||||
layoutId: {
|
||||
|
||||
@@ -43,7 +43,7 @@
|
||||
import { ref, watch } from "vue"
|
||||
import { useI18n } from "@composables/i18n"
|
||||
import { useToast } from "@composables/toast"
|
||||
import { getRESTRequest } from "~/newstore/RESTSession"
|
||||
import { currentActiveTab } from "~/helpers/rest/tab"
|
||||
|
||||
const toast = useToast()
|
||||
const t = useI18n()
|
||||
@@ -70,7 +70,7 @@ watch(
|
||||
() => props.show,
|
||||
(show) => {
|
||||
if (show) {
|
||||
name.value = getRESTRequest().name
|
||||
name.value = currentActiveTab.value.document.request.name
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
@@ -203,7 +203,7 @@ const props = defineProps({
|
||||
parentID: {
|
||||
type: String as PropType<string | null>,
|
||||
default: null,
|
||||
required: true,
|
||||
required: false,
|
||||
},
|
||||
data: {
|
||||
type: Object as PropType<HoppCollection<HoppRESTRequest> | TeamCollection>,
|
||||
|
||||
@@ -298,11 +298,10 @@ import { GetMyTeamsQuery } from "~/helpers/backend/graphql"
|
||||
import { ChildrenResult, SmartTreeAdapter } from "~/helpers/treeAdapter"
|
||||
import { useI18n } from "@composables/i18n"
|
||||
import { useColorMode } from "@composables/theming"
|
||||
import { useReadonlyStream } from "~/composables/stream"
|
||||
import { restSaveContext$ } from "~/newstore/RESTSession"
|
||||
import { pipe } from "fp-ts/function"
|
||||
import * as O from "fp-ts/Option"
|
||||
import { Picked } from "~/helpers/types/HoppPicked.js"
|
||||
import { currentActiveTab } from "~/helpers/rest/tab"
|
||||
|
||||
export type Collection = {
|
||||
type: "collections"
|
||||
@@ -508,23 +507,21 @@ const isSelected = computed(() => {
|
||||
}
|
||||
})
|
||||
|
||||
const active = useReadonlyStream(restSaveContext$, null)
|
||||
const active = computed(() => currentActiveTab.value.document.saveContext)
|
||||
|
||||
const isActiveRequest = computed(() => {
|
||||
return (folderPath: string, requestIndex: number) => {
|
||||
return pipe(
|
||||
active.value,
|
||||
O.fromNullable,
|
||||
O.filter(
|
||||
(active) =>
|
||||
active.originLocation === "user-collection" &&
|
||||
active.folderPath === folderPath &&
|
||||
active.requestIndex === requestIndex
|
||||
),
|
||||
O.isSome
|
||||
)
|
||||
}
|
||||
})
|
||||
const isActiveRequest = (folderPath: string, requestIndex: number) => {
|
||||
return pipe(
|
||||
active.value,
|
||||
O.fromNullable,
|
||||
O.filter(
|
||||
(active) =>
|
||||
active.originLocation === "user-collection" &&
|
||||
active.folderPath === folderPath &&
|
||||
active.requestIndex === requestIndex
|
||||
),
|
||||
O.isSome
|
||||
)
|
||||
}
|
||||
|
||||
const selectRequest = (data: {
|
||||
request: HoppRESTRequest
|
||||
@@ -532,6 +529,7 @@ const selectRequest = (data: {
|
||||
requestIndex: string
|
||||
}) => {
|
||||
const { request, folderPath, requestIndex } = data
|
||||
|
||||
if (props.saveRequest) {
|
||||
emit("select", {
|
||||
pickedType: "my-request",
|
||||
@@ -543,7 +541,7 @@ const selectRequest = (data: {
|
||||
request,
|
||||
folderPath,
|
||||
requestIndex,
|
||||
isActive: isActiveRequest.value(folderPath, parseInt(requestIndex)),
|
||||
isActive: isActiveRequest(folderPath, parseInt(requestIndex)),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -152,14 +152,12 @@ import { ref, PropType, watch, computed } from "vue"
|
||||
import { HoppRESTRequest } from "@hoppscotch/data"
|
||||
import { useI18n } from "@composables/i18n"
|
||||
import { TippyComponent } from "vue-tippy"
|
||||
import { pipe } from "fp-ts/function"
|
||||
import * as RR from "fp-ts/ReadonlyRecord"
|
||||
import * as O from "fp-ts/Option"
|
||||
import {
|
||||
changeCurrentReorderStatus,
|
||||
currentReorderingStatus$,
|
||||
} from "~/newstore/reordering"
|
||||
import { useReadonlyStream } from "~/composables/stream"
|
||||
import { getMethodLabelColorClassOf } from "~/helpers/rest/labelColoring"
|
||||
|
||||
type CollectionType = "my-collections" | "team-collections"
|
||||
|
||||
@@ -242,20 +240,8 @@ const currentReorderingStatus = useReadonlyStream(currentReorderingStatus$, {
|
||||
parentID: "",
|
||||
})
|
||||
|
||||
const requestMethodLabels = {
|
||||
get: "text-green-500",
|
||||
post: "text-yellow-500",
|
||||
put: "text-blue-500",
|
||||
delete: "text-red-500",
|
||||
default: "text-gray-500",
|
||||
} as const
|
||||
|
||||
const requestLabelColor = computed(() =>
|
||||
pipe(
|
||||
requestMethodLabels,
|
||||
RR.lookup(props.request.method.toLowerCase()),
|
||||
O.getOrElseW(() => requestMethodLabels.default)
|
||||
)
|
||||
getMethodLabelColorClassOf(props.request)
|
||||
)
|
||||
|
||||
watch(
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
<!-- eslint-disable prettier/prettier -->
|
||||
<template>
|
||||
<HoppSmartModal
|
||||
v-if="show"
|
||||
@@ -61,8 +62,8 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { useI18n } from "@composables/i18n"
|
||||
import { useToast } from "@composables/toast"
|
||||
import { reactive, ref, watch } from "vue"
|
||||
import { cloneDeep } from "lodash-es"
|
||||
import {
|
||||
HoppGQLRequest,
|
||||
HoppRESTRequest,
|
||||
@@ -70,8 +71,6 @@ import {
|
||||
} from "@hoppscotch/data"
|
||||
import { pipe } from "fp-ts/function"
|
||||
import * as TE from "fp-ts/TaskEither"
|
||||
import { cloneDeep } from "lodash-es"
|
||||
import { reactive, ref, watch } from "vue"
|
||||
import { GetMyTeamsQuery } from "~/helpers/backend/graphql"
|
||||
import {
|
||||
createRequestInCollection,
|
||||
@@ -79,11 +78,8 @@ import {
|
||||
} from "~/helpers/backend/mutations/TeamRequest"
|
||||
import { Picked } from "~/helpers/types/HoppPicked"
|
||||
import { getGQLSession, useGQLRequestName } from "~/newstore/GQLSession"
|
||||
import {
|
||||
getRESTRequest,
|
||||
setRESTSaveContext,
|
||||
useRESTRequestName,
|
||||
} from "~/newstore/RESTSession"
|
||||
import { useI18n } from "@composables/i18n"
|
||||
import { useToast } from "@composables/toast"
|
||||
import {
|
||||
editGraphqlRequest,
|
||||
editRESTRequest,
|
||||
@@ -91,6 +87,8 @@ import {
|
||||
saveRESTRequestAs,
|
||||
} from "~/newstore/collections"
|
||||
import { GQLError } from "~/helpers/backend/GQLClient"
|
||||
import { computedWithControl } from "@vueuse/core"
|
||||
import { currentActiveTab } from "~/helpers/rest/tab"
|
||||
|
||||
const t = useI18n()
|
||||
const toast = useToast()
|
||||
@@ -127,8 +125,13 @@ const emit = defineEmits<{
|
||||
(e: "hide-modal"): void
|
||||
}>()
|
||||
|
||||
const requestName = ref(
|
||||
props.mode === "rest" ? useRESTRequestName() : useGQLRequestName()
|
||||
const gqlRequestName = useGQLRequestName()
|
||||
const requestName = computedWithControl(
|
||||
() => [currentActiveTab.value, gqlRequestName.value],
|
||||
() =>
|
||||
props.mode === "rest"
|
||||
? currentActiveTab.value.document.request.name
|
||||
: gqlRequestName.value
|
||||
)
|
||||
|
||||
const requestData = reactive({
|
||||
@@ -186,7 +189,7 @@ const saveRequestAs = async () => {
|
||||
|
||||
const requestUpdated =
|
||||
props.mode === "rest"
|
||||
? cloneDeep(getRESTRequest())
|
||||
? cloneDeep(currentActiveTab.value.document.request)
|
||||
: cloneDeep(getGQLSession().request)
|
||||
|
||||
if (picked.value.pickedType === "my-collection") {
|
||||
@@ -198,12 +201,15 @@ const saveRequestAs = async () => {
|
||||
requestUpdated
|
||||
)
|
||||
|
||||
setRESTSaveContext({
|
||||
originLocation: "user-collection",
|
||||
folderPath: `${picked.value.collectionIndex}`,
|
||||
requestIndex: insertionIndex,
|
||||
req: requestUpdated,
|
||||
})
|
||||
currentActiveTab.value.document = {
|
||||
request: requestUpdated,
|
||||
isDirty: false,
|
||||
saveContext: {
|
||||
originLocation: "user-collection",
|
||||
folderPath: `${picked.value.collectionIndex}`,
|
||||
requestIndex: insertionIndex,
|
||||
},
|
||||
}
|
||||
|
||||
requestSaved()
|
||||
} else if (picked.value.pickedType === "my-folder") {
|
||||
@@ -215,12 +221,15 @@ const saveRequestAs = async () => {
|
||||
requestUpdated
|
||||
)
|
||||
|
||||
setRESTSaveContext({
|
||||
originLocation: "user-collection",
|
||||
folderPath: picked.value.folderPath,
|
||||
requestIndex: insertionIndex,
|
||||
req: requestUpdated,
|
||||
})
|
||||
currentActiveTab.value.document = {
|
||||
request: requestUpdated,
|
||||
isDirty: false,
|
||||
saveContext: {
|
||||
originLocation: "user-collection",
|
||||
folderPath: picked.value.folderPath,
|
||||
requestIndex: insertionIndex,
|
||||
},
|
||||
}
|
||||
|
||||
requestSaved()
|
||||
} else if (picked.value.pickedType === "my-request") {
|
||||
@@ -233,12 +242,15 @@ const saveRequestAs = async () => {
|
||||
requestUpdated
|
||||
)
|
||||
|
||||
setRESTSaveContext({
|
||||
originLocation: "user-collection",
|
||||
folderPath: picked.value.folderPath,
|
||||
requestIndex: picked.value.requestIndex,
|
||||
req: requestUpdated,
|
||||
})
|
||||
currentActiveTab.value.document = {
|
||||
request: requestUpdated,
|
||||
isDirty: false,
|
||||
saveContext: {
|
||||
originLocation: "user-collection",
|
||||
folderPath: picked.value.folderPath,
|
||||
requestIndex: picked.value.requestIndex,
|
||||
},
|
||||
}
|
||||
|
||||
requestSaved()
|
||||
} else if (picked.value.pickedType === "teams-collection") {
|
||||
@@ -341,13 +353,17 @@ const updateTeamCollectionOrFolder = (
|
||||
(result) => {
|
||||
const { createRequestInCollection } = result
|
||||
|
||||
setRESTSaveContext({
|
||||
originLocation: "team-collection",
|
||||
requestID: createRequestInCollection.id,
|
||||
collectionID: createRequestInCollection.collection.id,
|
||||
teamID: createRequestInCollection.collection.team.id,
|
||||
req: requestUpdated,
|
||||
})
|
||||
currentActiveTab.value.document = {
|
||||
request: requestUpdated,
|
||||
isDirty: false,
|
||||
saveContext: {
|
||||
originLocation: "team-collection",
|
||||
requestID: createRequestInCollection.id,
|
||||
collectionID: createRequestInCollection.collection.id,
|
||||
teamID: createRequestInCollection.collection.team.id,
|
||||
},
|
||||
}
|
||||
|
||||
modalLoadingState.value = false
|
||||
requestSaved()
|
||||
}
|
||||
|
||||
@@ -316,11 +316,10 @@ import { TeamRequest } from "~/helpers/teams/TeamRequest"
|
||||
import { ChildrenResult, SmartTreeAdapter } from "~/helpers/treeAdapter"
|
||||
import { cloneDeep } from "lodash-es"
|
||||
import { HoppRESTRequest } from "@hoppscotch/data"
|
||||
import { useReadonlyStream } from "~/composables/stream"
|
||||
import { restSaveContext$ } from "~/newstore/RESTSession"
|
||||
import { pipe } from "fp-ts/function"
|
||||
import * as O from "fp-ts/Option"
|
||||
import { Picked } from "~/helpers/types/HoppPicked.js"
|
||||
import { currentActiveTab } from "~/helpers/rest/tab"
|
||||
|
||||
const t = useI18n()
|
||||
const colorMode = useColorMode()
|
||||
@@ -520,7 +519,7 @@ const isSelected = computed(() => {
|
||||
}
|
||||
})
|
||||
|
||||
const active = useReadonlyStream(restSaveContext$, null)
|
||||
const active = computed(() => currentActiveTab.value.document.saveContext)
|
||||
|
||||
const isActiveRequest = computed(() => {
|
||||
return (requestID: string) => {
|
||||
|
||||
@@ -146,18 +146,6 @@
|
||||
@import-to-teams="importToTeams"
|
||||
@hide-modal="displayModalImportExport(false)"
|
||||
/>
|
||||
<HttpReqChangeConfirmModal
|
||||
:show="confirmChangeToRequest"
|
||||
:loading="modalLoadingState"
|
||||
@hide-modal="confirmChangeToRequest = false"
|
||||
@save-change="saveRequestChange"
|
||||
@discard-change="discardRequestChange"
|
||||
/>
|
||||
<CollectionsSaveRequest
|
||||
mode="rest"
|
||||
:show="showSaveRequestModal"
|
||||
@hide-modal="showSaveRequestModal = false"
|
||||
/>
|
||||
<TeamsAdd
|
||||
:show="showTeamModalAdd"
|
||||
@hide-modal="displayTeamModalAdd(false)"
|
||||
@@ -166,7 +154,7 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed, PropType, reactive, ref, watch } from "vue"
|
||||
import { computed, PropType, ref, watch } from "vue"
|
||||
import { useToast } from "@composables/toast"
|
||||
import { useI18n } from "@composables/i18n"
|
||||
import { Picked } from "~/helpers/types/HoppPicked"
|
||||
@@ -183,7 +171,6 @@ import {
|
||||
editRESTCollection,
|
||||
editRESTFolder,
|
||||
editRESTRequest,
|
||||
moveRESTFolder,
|
||||
moveRESTRequest,
|
||||
removeRESTCollection,
|
||||
removeRESTFolder,
|
||||
@@ -192,23 +179,14 @@ import {
|
||||
saveRESTRequestAs,
|
||||
updateRESTRequestOrder,
|
||||
updateRESTCollectionOrder,
|
||||
moveRESTFolder,
|
||||
} from "~/newstore/collections"
|
||||
import TeamCollectionAdapter from "~/helpers/teams/TeamCollectionAdapter"
|
||||
import {
|
||||
HoppCollection,
|
||||
HoppRESTRequest,
|
||||
isEqualHoppRESTRequest,
|
||||
makeCollection,
|
||||
safelyExtractRESTRequest,
|
||||
translateToNewRequest,
|
||||
} from "@hoppscotch/data"
|
||||
import {
|
||||
getDefaultRESTRequest,
|
||||
getRESTRequest,
|
||||
getRESTSaveContext,
|
||||
setRESTRequest,
|
||||
setRESTSaveContext,
|
||||
} from "~/newstore/RESTSession"
|
||||
import { cloneDeep, isEqual } from "lodash-es"
|
||||
import { GQLError } from "~/helpers/backend/GQLClient"
|
||||
import {
|
||||
@@ -234,12 +212,26 @@ import {
|
||||
getTeamCollectionJSON,
|
||||
teamCollToHoppRESTColl,
|
||||
} from "~/helpers/backend/helpers"
|
||||
import { HoppRequestSaveContext } from "~/helpers/types/HoppRequestSaveContext"
|
||||
import * as E from "fp-ts/Either"
|
||||
import { platform } from "~/platform"
|
||||
import { createCollectionGists } from "~/helpers/gist"
|
||||
import { workspaceStatus$ } from "~/newstore/workspace"
|
||||
import IconListEnd from "~icons/lucide/list-end"
|
||||
import {
|
||||
createNewTab,
|
||||
currentActiveTab,
|
||||
currentTabID,
|
||||
getTabRefWithSaveContext,
|
||||
} from "~/helpers/rest/tab"
|
||||
import {
|
||||
getRequestsByPath,
|
||||
resolveSaveContextOnRequestReorder,
|
||||
} from "~/helpers/collection/request"
|
||||
import {
|
||||
getFoldersByPath,
|
||||
resolveSaveContextOnCollectionReorder,
|
||||
updateSaveContextForAffectedRequests,
|
||||
} from "~/helpers/collection/collection"
|
||||
|
||||
const t = useI18n()
|
||||
const toast = useToast()
|
||||
@@ -314,15 +306,6 @@ const exportingTeamCollections = ref(false)
|
||||
const creatingGistCollection = ref(false)
|
||||
const importingMyCollections = ref(false)
|
||||
|
||||
// Confirm Change to request modal
|
||||
const confirmChangeToRequest = ref(false)
|
||||
const showSaveRequestModal = ref(false)
|
||||
const clickedRequest = reactive({
|
||||
folderPath: "" as string | undefined,
|
||||
requestIndex: null as string | null,
|
||||
request: null as HoppRESTRequest | null,
|
||||
})
|
||||
|
||||
// TeamList-Adapter
|
||||
const teamListAdapter = new TeamListAdapter(true)
|
||||
const myTeams = useReadonlyStream(teamListAdapter.teamList$, null)
|
||||
@@ -637,7 +620,7 @@ const addRequest = (payload: {
|
||||
|
||||
const onAddRequest = (requestName: string) => {
|
||||
const newRequest = {
|
||||
...cloneDeep(getRESTRequest()),
|
||||
...cloneDeep(currentActiveTab.value.document.request),
|
||||
name: requestName,
|
||||
}
|
||||
|
||||
@@ -646,10 +629,14 @@ const onAddRequest = (requestName: string) => {
|
||||
if (!path) return
|
||||
const insertionIndex = saveRESTRequestAs(path, newRequest)
|
||||
|
||||
setRESTRequest(newRequest, {
|
||||
originLocation: "user-collection",
|
||||
folderPath: path,
|
||||
requestIndex: insertionIndex,
|
||||
createNewTab({
|
||||
request: newRequest,
|
||||
isDirty: false,
|
||||
saveContext: {
|
||||
originLocation: "user-collection",
|
||||
folderPath: path,
|
||||
requestIndex: insertionIndex,
|
||||
},
|
||||
})
|
||||
|
||||
displayModalAddRequest(false)
|
||||
@@ -677,12 +664,17 @@ const onAddRequest = (requestName: string) => {
|
||||
(result) => {
|
||||
const { createRequestInCollection } = result
|
||||
|
||||
setRESTRequest(newRequest, {
|
||||
originLocation: "team-collection",
|
||||
requestID: createRequestInCollection.id,
|
||||
collectionID: createRequestInCollection.collection.id,
|
||||
teamID: createRequestInCollection.collection.team.id,
|
||||
createNewTab({
|
||||
request: newRequest,
|
||||
isDirty: false,
|
||||
saveContext: {
|
||||
originLocation: "team-collection",
|
||||
requestID: createRequestInCollection.id,
|
||||
collectionID: createRequestInCollection.collection.id,
|
||||
teamID: createRequestInCollection.collection.team.id,
|
||||
},
|
||||
})
|
||||
|
||||
modalLoadingState.value = false
|
||||
displayModalAddRequest(false)
|
||||
}
|
||||
@@ -873,27 +865,22 @@ const updateEditingRequest = (newName: string) => {
|
||||
...request,
|
||||
name: newName || request.name,
|
||||
}
|
||||
|
||||
const saveCtx = getRESTSaveContext()
|
||||
|
||||
if (collectionsType.value.type === "my-collections") {
|
||||
const folderPath = editingFolderPath.value
|
||||
const requestIndex = editingRequestIndex.value
|
||||
|
||||
if (folderPath === null || requestIndex === null) return
|
||||
|
||||
const possibleActiveTab = getTabRefWithSaveContext({
|
||||
originLocation: "user-collection",
|
||||
requestIndex,
|
||||
folderPath,
|
||||
})
|
||||
|
||||
editRESTRequest(folderPath, requestIndex, requestUpdated)
|
||||
|
||||
if (
|
||||
saveCtx &&
|
||||
saveCtx.originLocation === "user-collection" &&
|
||||
saveCtx.requestIndex === editingRequestIndex.value &&
|
||||
saveCtx.folderPath === editingFolderPath.value
|
||||
) {
|
||||
setRESTRequest({
|
||||
...getRESTRequest(),
|
||||
name: requestUpdated.name,
|
||||
})
|
||||
if (possibleActiveTab) {
|
||||
possibleActiveTab.value.document.request.name = requestUpdated.name
|
||||
}
|
||||
|
||||
displayModalEditRequest(false)
|
||||
@@ -925,15 +912,13 @@ const updateEditingRequest = (newName: string) => {
|
||||
)
|
||||
)()
|
||||
|
||||
if (
|
||||
saveCtx &&
|
||||
saveCtx.originLocation === "team-collection" &&
|
||||
saveCtx.requestID === editingRequestID.value
|
||||
) {
|
||||
setRESTRequest({
|
||||
...getRESTRequest(),
|
||||
name: requestName,
|
||||
})
|
||||
const possibleTab = getTabRefWithSaveContext({
|
||||
originLocation: "team-collection",
|
||||
requestID,
|
||||
})
|
||||
|
||||
if (possibleTab) {
|
||||
possibleTab.value.document.request.name = requestName
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1030,6 +1015,13 @@ const onRemoveCollection = () => {
|
||||
|
||||
removeRESTCollection(collectionIndex)
|
||||
|
||||
resolveSaveContextOnCollectionReorder({
|
||||
lastIndex: collectionIndex,
|
||||
newIndex: -1,
|
||||
folderPath: "", // root folder
|
||||
length: myCollections.value.length,
|
||||
})
|
||||
|
||||
toast.success(t("state.deleted"))
|
||||
displayConfirmModal(false)
|
||||
} else if (hasTeamWriteAccess.value) {
|
||||
@@ -1074,6 +1066,14 @@ const onRemoveFolder = () => {
|
||||
|
||||
removeRESTFolder(folderPath)
|
||||
|
||||
const parentFolder = folderPath.split("/").slice(0, -1).join("/") // remove last folder to get parent folder
|
||||
resolveSaveContextOnCollectionReorder({
|
||||
lastIndex: pathToLastIndex(folderPath),
|
||||
newIndex: -1,
|
||||
folderPath: parentFolder,
|
||||
length: getFoldersByPath(myCollections.value, parentFolder).length,
|
||||
})
|
||||
|
||||
toast.success(t("state.deleted"))
|
||||
displayConfirmModal(false)
|
||||
} else if (hasTeamWriteAccess.value) {
|
||||
@@ -1124,8 +1124,28 @@ const onRemoveRequest = () => {
|
||||
emit("select", null)
|
||||
}
|
||||
|
||||
const possibleTab = getTabRefWithSaveContext({
|
||||
originLocation: "user-collection",
|
||||
folderPath,
|
||||
requestIndex,
|
||||
})
|
||||
|
||||
// If there is a tab attached to this request, dissociate its state and mark it dirty
|
||||
if (possibleTab) {
|
||||
possibleTab.value.document.saveContext = null
|
||||
possibleTab.value.document.isDirty = true
|
||||
}
|
||||
|
||||
removeRESTRequest(folderPath, requestIndex)
|
||||
|
||||
// the same function is used to reorder requests since after removing, it's basically doing reorder
|
||||
resolveSaveContextOnRequestReorder({
|
||||
lastIndex: requestIndex,
|
||||
newIndex: -1,
|
||||
folderPath,
|
||||
length: getRequestsByPath(myCollections.value, folderPath).length,
|
||||
})
|
||||
|
||||
toast.success(t("state.deleted"))
|
||||
displayConfirmModal(false)
|
||||
} else if (hasTeamWriteAccess.value) {
|
||||
@@ -1157,6 +1177,17 @@ const onRemoveRequest = () => {
|
||||
}
|
||||
)
|
||||
)()
|
||||
|
||||
// If there is a tab attached to this request, dissociate its state and mark it dirty
|
||||
const possibleTab = getTabRefWithSaveContext({
|
||||
originLocation: "team-collection",
|
||||
requestID,
|
||||
})
|
||||
|
||||
if (possibleTab) {
|
||||
possibleTab.value.document.saveContext = undefined
|
||||
possibleTab.value.document.isDirty = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1165,41 +1196,6 @@ const selectPicked = (payload: Picked | null) => {
|
||||
emit("select", payload)
|
||||
}
|
||||
|
||||
// select request change modal functions
|
||||
const noChangeSetRESTRequest = () => {
|
||||
const folderPath = clickedRequest.folderPath
|
||||
const requestIndex = clickedRequest.requestIndex
|
||||
const request = clickedRequest.request
|
||||
|
||||
let newContext: HoppRequestSaveContext | null = null
|
||||
if (collectionsType.value.type === "my-collections") {
|
||||
if (!folderPath || !requestIndex || !request) return
|
||||
|
||||
newContext = {
|
||||
originLocation: "user-collection",
|
||||
requestIndex: parseInt(requestIndex),
|
||||
folderPath,
|
||||
req: cloneDeep(request),
|
||||
}
|
||||
} else if (collectionsType.value.type === "team-collections") {
|
||||
if (!requestIndex || !request) return
|
||||
newContext = {
|
||||
originLocation: "team-collection",
|
||||
requestID: requestIndex,
|
||||
req: cloneDeep(request),
|
||||
}
|
||||
}
|
||||
setRESTRequest(
|
||||
cloneDeep(
|
||||
safelyExtractRESTRequest(
|
||||
translateToNewRequest(request),
|
||||
getDefaultRESTRequest()
|
||||
)
|
||||
),
|
||||
newContext
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* This function is called when the user clicks on a request
|
||||
* @param selectedRequest The request that the user clicked on emited from the collection tree
|
||||
@@ -1210,131 +1206,51 @@ const selectRequest = (selectedRequest: {
|
||||
requestIndex: string
|
||||
isActive: boolean
|
||||
}) => {
|
||||
const { request, folderPath, requestIndex, isActive } = selectedRequest
|
||||
// If the request is already active, then we reset the save context
|
||||
if (isActive) {
|
||||
setRESTSaveContext(null)
|
||||
return
|
||||
}
|
||||
const { request, folderPath, requestIndex } = selectedRequest
|
||||
|
||||
const currentRESTRequest = getRESTRequest()
|
||||
// If there is a request with this save context, switch into it
|
||||
let possibleTab = null
|
||||
|
||||
const currentRESTSaveContext = getRESTSaveContext()
|
||||
|
||||
clickedRequest.folderPath = folderPath
|
||||
clickedRequest.requestIndex = requestIndex
|
||||
clickedRequest.request = request
|
||||
|
||||
// If there is no active context,
|
||||
if (!currentRESTSaveContext) {
|
||||
// Check if the use is clicking on the same request
|
||||
if (isEqualHoppRESTRequest(currentRESTRequest, request)) {
|
||||
noChangeSetRESTRequest()
|
||||
if (collectionsType.value.type === "team-collections") {
|
||||
possibleTab = getTabRefWithSaveContext({
|
||||
originLocation: "team-collection",
|
||||
requestID: requestIndex,
|
||||
})
|
||||
if (possibleTab) {
|
||||
currentTabID.value = possibleTab.value.id
|
||||
} else {
|
||||
// can show the save change modal here since there is change in the request
|
||||
// and the user is clicking on the different request
|
||||
// and currently we dont have any active context
|
||||
|
||||
confirmChangeToRequest.value = true
|
||||
}
|
||||
} else {
|
||||
if (isEqualHoppRESTRequest(currentRESTRequest, request)) {
|
||||
noChangeSetRESTRequest()
|
||||
} else {
|
||||
const currentReqWithNoChange = currentRESTSaveContext.req
|
||||
// now we compare the current request
|
||||
// with the request inside the active context
|
||||
if (
|
||||
currentReqWithNoChange &&
|
||||
isEqualHoppRESTRequest(currentReqWithNoChange, currentRESTRequest)
|
||||
) {
|
||||
noChangeSetRESTRequest()
|
||||
} else {
|
||||
// there is change in the request
|
||||
// so we can show the save change modal here
|
||||
confirmChangeToRequest.value = true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This function is called when the user clicks on the save button in the confirm change modal
|
||||
* There are two cases
|
||||
* 1. There is no active context
|
||||
* 2. There is active context
|
||||
* In the first case, we can show the save request as modal and user can select the location to save the request
|
||||
* In the second case, we can save the request in the same location and update the request
|
||||
*/
|
||||
const saveRequestChange = () => {
|
||||
const currentRESTSaveContext = getRESTSaveContext()
|
||||
|
||||
if (!currentRESTSaveContext) {
|
||||
showSaveRequestModal.value = true
|
||||
confirmChangeToRequest.value = false
|
||||
return
|
||||
}
|
||||
|
||||
const currentRESTRequest = getRESTRequest()
|
||||
|
||||
if (currentRESTSaveContext.originLocation === "user-collection") {
|
||||
const folderPath = currentRESTSaveContext.folderPath
|
||||
const requestIndex = currentRESTSaveContext.requestIndex
|
||||
|
||||
editRESTRequest(folderPath, requestIndex, currentRESTRequest)
|
||||
|
||||
// after saving the request, we need to change the context
|
||||
// to the new request (clicked request)
|
||||
noChangeSetRESTRequest()
|
||||
|
||||
toast.success(`${t("request.saved")}`)
|
||||
confirmChangeToRequest.value = false
|
||||
} else {
|
||||
modalLoadingState.value = true
|
||||
|
||||
const requestID = currentRESTSaveContext.requestID
|
||||
|
||||
const data = {
|
||||
request: JSON.stringify(currentRESTRequest),
|
||||
title: currentRESTRequest.name,
|
||||
}
|
||||
|
||||
pipe(
|
||||
updateTeamRequest(requestID, data),
|
||||
TE.match(
|
||||
(err: GQLError<string>) => {
|
||||
toast.error(`${getErrorMessage(err)}`)
|
||||
confirmChangeToRequest.value = false
|
||||
showSaveRequestModal.value = true
|
||||
modalLoadingState.value = false
|
||||
createNewTab({
|
||||
request: cloneDeep(request),
|
||||
isDirty: false,
|
||||
saveContext: {
|
||||
originLocation: "team-collection",
|
||||
requestID: requestIndex,
|
||||
},
|
||||
() => {
|
||||
toast.success(`${t("request.saved")}`)
|
||||
modalLoadingState.value = false
|
||||
confirmChangeToRequest.value = false
|
||||
|
||||
const clickedRequestID = clickedRequest.requestIndex
|
||||
|
||||
if (!clickedRequestID) return
|
||||
|
||||
noChangeSetRESTRequest()
|
||||
}
|
||||
)
|
||||
)()
|
||||
})
|
||||
}
|
||||
} else {
|
||||
possibleTab = getTabRefWithSaveContext({
|
||||
originLocation: "user-collection",
|
||||
requestIndex: parseInt(requestIndex),
|
||||
folderPath: folderPath!,
|
||||
})
|
||||
if (possibleTab) {
|
||||
currentTabID.value = possibleTab.value.id
|
||||
} else {
|
||||
// If not, open the request in a new tab
|
||||
createNewTab({
|
||||
request: cloneDeep(request),
|
||||
isDirty: false,
|
||||
saveContext: {
|
||||
originLocation: "user-collection",
|
||||
folderPath: folderPath!,
|
||||
requestIndex: parseInt(requestIndex),
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This function is called when the user clicks on the
|
||||
* don't save button in the confirm change modal
|
||||
* This function will change the request to the clicked request
|
||||
* without saving the changes
|
||||
*/
|
||||
const discardRequestChange = () => {
|
||||
noChangeSetRESTRequest()
|
||||
confirmChangeToRequest.value = false
|
||||
}
|
||||
|
||||
/**
|
||||
* Used to get the index of the request from the path
|
||||
* @param path The path of the request
|
||||
@@ -1355,13 +1271,42 @@ const dropRequest = (payload: {
|
||||
destinationCollectionIndex: string
|
||||
}) => {
|
||||
const { folderPath, requestIndex, destinationCollectionIndex } = payload
|
||||
|
||||
if (!requestIndex || !destinationCollectionIndex) return
|
||||
|
||||
if (collectionsType.value.type === "my-collections" && folderPath) {
|
||||
moveRESTRequest(
|
||||
folderPath,
|
||||
pathToLastIndex(requestIndex),
|
||||
destinationCollectionIndex
|
||||
)
|
||||
|
||||
const possibleTab = getTabRefWithSaveContext({
|
||||
originLocation: "user-collection",
|
||||
folderPath,
|
||||
requestIndex: pathToLastIndex(requestIndex),
|
||||
})
|
||||
|
||||
// If there is a tab attached to this request, change save its save context
|
||||
if (possibleTab) {
|
||||
possibleTab.value.document.saveContext = {
|
||||
originLocation: "user-collection",
|
||||
folderPath: destinationCollectionIndex,
|
||||
requestIndex: getRequestsByPath(
|
||||
myCollections.value,
|
||||
destinationCollectionIndex
|
||||
).length,
|
||||
}
|
||||
}
|
||||
|
||||
// When it's drop it's basically getting deleted from last folder. reordering last folder accordingly
|
||||
resolveSaveContextOnRequestReorder({
|
||||
lastIndex: pathToLastIndex(requestIndex),
|
||||
newIndex: -1, // being deleted from last folder
|
||||
folderPath,
|
||||
length: getRequestsByPath(myCollections.value, folderPath).length,
|
||||
})
|
||||
|
||||
toast.success(`${t("request.moved")}`)
|
||||
draggingToRoot.value = false
|
||||
} else if (hasTeamWriteAccess.value) {
|
||||
@@ -1384,6 +1329,18 @@ const dropRequest = (payload: {
|
||||
requestMoveLoading.value.indexOf(requestIndex),
|
||||
1
|
||||
)
|
||||
|
||||
const possibleTab = getTabRefWithSaveContext({
|
||||
originLocation: "team-collection",
|
||||
requestID: requestIndex,
|
||||
})
|
||||
|
||||
if (possibleTab) {
|
||||
possibleTab.value.document.saveContext = {
|
||||
originLocation: "team-collection",
|
||||
requestID: requestIndex,
|
||||
}
|
||||
}
|
||||
toast.success(`${t("request.moved")}`)
|
||||
}
|
||||
)
|
||||
@@ -1440,6 +1397,7 @@ const dropCollection = (payload: {
|
||||
const { collectionIndexDragged, destinationCollectionIndex } = payload
|
||||
if (!collectionIndexDragged || !destinationCollectionIndex) return
|
||||
if (collectionIndexDragged === destinationCollectionIndex) return
|
||||
|
||||
if (collectionsType.value.type === "my-collections") {
|
||||
if (
|
||||
checkIfCollectionIsAParentOfTheChildren(
|
||||
@@ -1450,7 +1408,32 @@ const dropCollection = (payload: {
|
||||
toast.error(`${t("team.parent_coll_move")}`)
|
||||
return
|
||||
}
|
||||
|
||||
const parentFolder = collectionIndexDragged
|
||||
.split("/")
|
||||
.slice(0, -1)
|
||||
.join("/") // remove last folder to get parent folder
|
||||
const totalFoldersOfDestinationCollection =
|
||||
getFoldersByPath(myCollections.value, destinationCollectionIndex).length -
|
||||
(parentFolder === destinationCollectionIndex ? 1 : 0)
|
||||
|
||||
moveRESTFolder(collectionIndexDragged, destinationCollectionIndex)
|
||||
|
||||
resolveSaveContextOnCollectionReorder(
|
||||
{
|
||||
lastIndex: pathToLastIndex(collectionIndexDragged),
|
||||
newIndex: -1,
|
||||
folderPath: parentFolder,
|
||||
length: getFoldersByPath(myCollections.value, parentFolder).length,
|
||||
},
|
||||
"drop"
|
||||
)
|
||||
|
||||
updateSaveContextForAffectedRequests(
|
||||
collectionIndexDragged,
|
||||
`${destinationCollectionIndex}/${totalFoldersOfDestinationCollection}`
|
||||
)
|
||||
|
||||
draggingToRoot.value = false
|
||||
toast.success(`${t("collection.moved")}`)
|
||||
} else if (hasTeamWriteAccess.value) {
|
||||
@@ -1600,6 +1583,11 @@ const updateRequestOrder = (payload: {
|
||||
pathToLastIndex(destinationRequestIndex),
|
||||
destinationCollectionIndex
|
||||
)
|
||||
resolveSaveContextOnRequestReorder({
|
||||
lastIndex: pathToLastIndex(dragedRequestIndex),
|
||||
newIndex: pathToLastIndex(destinationRequestIndex),
|
||||
folderPath: destinationCollectionIndex,
|
||||
})
|
||||
toast.success(`${t("request.order_changed")}`)
|
||||
}
|
||||
} else if (hasTeamWriteAccess.value) {
|
||||
@@ -1654,6 +1642,11 @@ const updateCollectionOrder = (payload: {
|
||||
dragedCollectionIndex,
|
||||
destinationCollectionIndex
|
||||
)
|
||||
resolveSaveContextOnCollectionReorder({
|
||||
lastIndex: pathToLastIndex(dragedCollectionIndex),
|
||||
newIndex: pathToLastIndex(destinationCollectionIndex),
|
||||
folderPath: dragedCollectionIndex.split("/").slice(0, -1).join("/"),
|
||||
})
|
||||
toast.success(`${t("collection.order_changed")}`)
|
||||
}
|
||||
} else if (hasTeamWriteAccess.value) {
|
||||
|
||||
@@ -148,17 +148,6 @@
|
||||
@hide-modal="confirmRemove = false"
|
||||
@resolve="clearHistory"
|
||||
/>
|
||||
<HttpReqChangeConfirmModal
|
||||
:show="confirmChange"
|
||||
@hide-modal="confirmChange = false"
|
||||
@save-change="saveRequestChange"
|
||||
@discard-change="discardRequestChange"
|
||||
/>
|
||||
<CollectionsSaveRequest
|
||||
mode="rest"
|
||||
:show="showSaveRequestModal"
|
||||
@hide-modal="showSaveRequestModal = false"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -169,17 +158,11 @@ import IconTrash from "~icons/lucide/trash"
|
||||
import IconFilter from "~icons/lucide/filter"
|
||||
import { computed, ref, Ref, toRaw } from "vue"
|
||||
import { useColorMode } from "@composables/theming"
|
||||
import {
|
||||
HoppGQLRequest,
|
||||
HoppRESTRequest,
|
||||
isEqualHoppRESTRequest,
|
||||
safelyExtractRESTRequest,
|
||||
} from "@hoppscotch/data"
|
||||
import { HoppGQLRequest, HoppRESTRequest } from "@hoppscotch/data"
|
||||
import { groupBy, escapeRegExp, filter } from "lodash-es"
|
||||
import { useTimeAgo } from "@vueuse/core"
|
||||
import { pipe } from "fp-ts/function"
|
||||
import * as A from "fp-ts/Array"
|
||||
import * as E from "fp-ts/Either"
|
||||
import { useI18n } from "@composables/i18n"
|
||||
import { useReadonlyStream } from "@composables/stream"
|
||||
import { useToast } from "@composables/toast"
|
||||
@@ -195,20 +178,10 @@ import {
|
||||
RESTHistoryEntry,
|
||||
GQLHistoryEntry,
|
||||
} from "~/newstore/history"
|
||||
import {
|
||||
getDefaultRESTRequest,
|
||||
getRESTRequest,
|
||||
getRESTSaveContext,
|
||||
setRESTRequest,
|
||||
setRESTSaveContext,
|
||||
} from "~/newstore/RESTSession"
|
||||
import { editRESTRequest } from "~/newstore/collections"
|
||||
import { runMutation } from "~/helpers/backend/GQLClient"
|
||||
import { UpdateRequestDocument } from "~/helpers/backend/graphql"
|
||||
import { HoppRequestSaveContext } from "~/helpers/types/HoppRequestSaveContext"
|
||||
|
||||
import HistoryRestCard from "./rest/Card.vue"
|
||||
import HistoryGraphqlCard from "./graphql/Card.vue"
|
||||
import { createNewTab } from "~/helpers/rest/tab"
|
||||
|
||||
type HistoryEntry = GQLHistoryEntry | RESTHistoryEntry
|
||||
|
||||
@@ -229,10 +202,6 @@ const filterText = ref("")
|
||||
const showMore = ref(false)
|
||||
const confirmRemove = ref(false)
|
||||
|
||||
const clickedHistory = ref<HistoryEntry | null>(null)
|
||||
const confirmChange = ref(false)
|
||||
const showSaveRequestModal = ref(false)
|
||||
|
||||
const history = useReadonlyStream<RESTHistoryEntry[] | GQLHistoryEntry[]>(
|
||||
props.page === "rest" ? restHistory$ : graphqlHistory$,
|
||||
[]
|
||||
@@ -326,111 +295,13 @@ const clearHistory = () => {
|
||||
toast.success(`${t("state.history_deleted")}`)
|
||||
}
|
||||
|
||||
const setRestReq = (request: HoppRESTRequest | null | undefined) => {
|
||||
setRESTRequest(safelyExtractRESTRequest(request, getDefaultRESTRequest()))
|
||||
}
|
||||
|
||||
// NOTE: For GQL, the HistoryGraphqlCard component already implements useEntry
|
||||
// (That is not a really good behaviour tho ¯\_(ツ)_/¯)
|
||||
const useHistory = (entry: RESTHistoryEntry) => {
|
||||
const currentFullReq = getRESTRequest()
|
||||
|
||||
const currentReqWithNoChange = getRESTSaveContext()?.req
|
||||
|
||||
// checks if the current request is the same as the save context request if present
|
||||
if (
|
||||
currentReqWithNoChange &&
|
||||
isEqualHoppRESTRequest(currentReqWithNoChange, currentFullReq)
|
||||
) {
|
||||
props.page === "rest" && setRestReq(entry.request)
|
||||
clickedHistory.value = entry
|
||||
}
|
||||
// Initial state trigers a popup
|
||||
else if (!clickedHistory.value) {
|
||||
clickedHistory.value = entry
|
||||
confirmChange.value = true
|
||||
return
|
||||
}
|
||||
// Checks if there are any change done in current request and the history request
|
||||
else if (
|
||||
!isEqualHoppRESTRequest(
|
||||
currentFullReq,
|
||||
clickedHistory.value.request as HoppRESTRequest
|
||||
)
|
||||
) {
|
||||
clickedHistory.value = entry
|
||||
confirmChange.value = true
|
||||
} else {
|
||||
props.page === "rest" && setRestReq(entry.request)
|
||||
clickedHistory.value = entry
|
||||
}
|
||||
}
|
||||
|
||||
/** Save current request to the collection */
|
||||
const saveRequestChange = () => {
|
||||
const saveCtx = getRESTSaveContext()
|
||||
saveCurrentRequest(saveCtx)
|
||||
confirmChange.value = false
|
||||
}
|
||||
|
||||
/** Discard changes and change the current request and remove the collection context */
|
||||
const discardRequestChange = () => {
|
||||
const saveCtx = getRESTSaveContext()
|
||||
if (saveCtx) {
|
||||
setRESTSaveContext(null)
|
||||
}
|
||||
clickedHistory.value &&
|
||||
setRestReq(clickedHistory.value.request as HoppRESTRequest)
|
||||
confirmChange.value = false
|
||||
}
|
||||
|
||||
const saveCurrentRequest = (saveCtx: HoppRequestSaveContext | null) => {
|
||||
if (!saveCtx) {
|
||||
showSaveRequestModal.value = true
|
||||
return
|
||||
}
|
||||
if (saveCtx.originLocation === "user-collection") {
|
||||
try {
|
||||
editRESTRequest(
|
||||
saveCtx.folderPath,
|
||||
saveCtx.requestIndex,
|
||||
getRESTRequest()
|
||||
)
|
||||
clickedHistory.value &&
|
||||
setRestReq(clickedHistory.value.request as HoppRESTRequest)
|
||||
setRESTSaveContext(null)
|
||||
toast.success(`${t("request.saved")}`)
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
setRESTSaveContext(null)
|
||||
saveCurrentRequest(null)
|
||||
}
|
||||
} else if (saveCtx.originLocation === "team-collection") {
|
||||
const req = getRESTRequest()
|
||||
try {
|
||||
runMutation(UpdateRequestDocument, {
|
||||
requestID: saveCtx.requestID,
|
||||
data: {
|
||||
title: req.name,
|
||||
request: JSON.stringify(req),
|
||||
},
|
||||
})().then((result) => {
|
||||
if (E.isLeft(result)) {
|
||||
toast.error(`${t("profile.no_permission")}`)
|
||||
} else {
|
||||
toast.success(`${t("request.saved")}`)
|
||||
}
|
||||
})
|
||||
clickedHistory.value &&
|
||||
setRestReq(clickedHistory.value.request as HoppRESTRequest)
|
||||
setRESTSaveContext(null)
|
||||
} catch (error) {
|
||||
showSaveRequestModal.value = true
|
||||
toast.error(`${t("error.something_went_wrong")}`)
|
||||
console.error(error)
|
||||
setRESTSaveContext(null)
|
||||
}
|
||||
}
|
||||
createNewTab({
|
||||
request: entry.request,
|
||||
isDirty: false,
|
||||
})
|
||||
}
|
||||
|
||||
const isRESTHistoryEntry = (
|
||||
|
||||
@@ -32,7 +32,7 @@
|
||||
:active="authName === 'None'"
|
||||
@click="
|
||||
() => {
|
||||
authType = 'none'
|
||||
auth.authType = 'none'
|
||||
hide()
|
||||
}
|
||||
"
|
||||
@@ -43,7 +43,7 @@
|
||||
:active="authName === 'Basic Auth'"
|
||||
@click="
|
||||
() => {
|
||||
authType = 'basic'
|
||||
auth.authType = 'basic'
|
||||
hide()
|
||||
}
|
||||
"
|
||||
@@ -54,7 +54,7 @@
|
||||
:active="authName === 'Bearer'"
|
||||
@click="
|
||||
() => {
|
||||
authType = 'bearer'
|
||||
auth.authType = 'bearer'
|
||||
hide()
|
||||
}
|
||||
"
|
||||
@@ -65,7 +65,7 @@
|
||||
:active="authName === 'OAuth 2.0'"
|
||||
@click="
|
||||
() => {
|
||||
authType = 'oauth-2'
|
||||
auth.authType = 'oauth-2'
|
||||
hide()
|
||||
}
|
||||
"
|
||||
@@ -76,7 +76,7 @@
|
||||
:active="authName === 'API key'"
|
||||
@click="
|
||||
() => {
|
||||
authType = 'api-key'
|
||||
auth.authType = 'api-key'
|
||||
hide()
|
||||
}
|
||||
"
|
||||
@@ -114,7 +114,7 @@
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
v-if="authType === 'none'"
|
||||
v-if="auth.authType === 'none'"
|
||||
class="flex flex-col items-center justify-center p-4 text-secondaryLight"
|
||||
>
|
||||
<img
|
||||
@@ -136,91 +136,22 @@
|
||||
</div>
|
||||
<div v-else class="flex flex-1 border-b border-dividerLight">
|
||||
<div class="w-2/3 border-r border-dividerLight">
|
||||
<div v-if="authType === 'basic'">
|
||||
<div v-if="auth.authType === 'basic'">
|
||||
<HttpAuthorizationBasic v-model="auth" />
|
||||
</div>
|
||||
<div v-if="auth.authType === 'bearer'">
|
||||
<div class="flex flex-1 border-b border-dividerLight">
|
||||
<SmartEnvInput
|
||||
v-model="basicUsername"
|
||||
:placeholder="t('authorization.username')"
|
||||
/>
|
||||
</div>
|
||||
<div class="flex flex-1 border-b border-dividerLight">
|
||||
<SmartEnvInput
|
||||
v-model="basicPassword"
|
||||
:placeholder="t('authorization.password')"
|
||||
/>
|
||||
<SmartEnvInput v-model="auth.token" placeholder="Token" />
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="authType === 'bearer'">
|
||||
<div v-if="auth.authType === 'oauth-2'">
|
||||
<div class="flex flex-1 border-b border-dividerLight">
|
||||
<SmartEnvInput v-model="bearerToken" placeholder="Token" />
|
||||
<SmartEnvInput v-model="auth.token" placeholder="Token" />
|
||||
</div>
|
||||
<HttpOAuth2Authorization v-model="auth" />
|
||||
</div>
|
||||
<div v-if="authType === 'oauth-2'">
|
||||
<div class="flex flex-1 border-b border-dividerLight">
|
||||
<SmartEnvInput v-model="oauth2Token" placeholder="Token" />
|
||||
</div>
|
||||
<HttpOAuth2Authorization />
|
||||
</div>
|
||||
<div v-if="authType === 'api-key'">
|
||||
<div class="flex flex-1 border-b border-dividerLight">
|
||||
<SmartEnvInput v-model="apiKey" placeholder="Key" />
|
||||
</div>
|
||||
<div class="flex flex-1 border-b border-dividerLight">
|
||||
<SmartEnvInput v-model="apiValue" placeholder="Value" />
|
||||
</div>
|
||||
<div class="flex items-center border-b border-dividerLight">
|
||||
<span class="flex items-center">
|
||||
<label class="ml-4 text-secondaryLight">
|
||||
{{ t("authorization.pass_key_by") }}
|
||||
</label>
|
||||
<tippy
|
||||
interactive
|
||||
trigger="click"
|
||||
theme="popover"
|
||||
:on-shown="() => authTippyActions.focus()"
|
||||
>
|
||||
<span class="select-wrapper">
|
||||
<HoppButtonSecondary
|
||||
:label="addTo || t('state.none')"
|
||||
class="pr-8 ml-2 rounded-none"
|
||||
/>
|
||||
</span>
|
||||
<template #content="{ hide }">
|
||||
<div
|
||||
ref="authTippyActions"
|
||||
class="flex flex-col focus:outline-none"
|
||||
tabindex="0"
|
||||
@keyup.escape="hide()"
|
||||
>
|
||||
<HoppSmartItem
|
||||
:icon="addTo === 'Headers' ? IconCircleDot : IconCircle"
|
||||
:active="addTo === 'Headers'"
|
||||
:label="'Headers'"
|
||||
@click="
|
||||
() => {
|
||||
addTo = 'Headers'
|
||||
hide()
|
||||
}
|
||||
"
|
||||
/>
|
||||
<HoppSmartItem
|
||||
:icon="
|
||||
addTo === 'Query params' ? IconCircleDot : IconCircle
|
||||
"
|
||||
:active="addTo === 'Query params'"
|
||||
:label="'Query params'"
|
||||
@click="
|
||||
() => {
|
||||
addTo = 'Query params'
|
||||
hide()
|
||||
}
|
||||
"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
</tippy>
|
||||
</span>
|
||||
</div>
|
||||
<div v-if="auth.authType === 'api-key'">
|
||||
<HttpAuthorizationApiKey v-model="auth" />
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
@@ -248,49 +179,40 @@ import IconTrash2 from "~icons/lucide/trash-2"
|
||||
import IconExternalLink from "~icons/lucide/external-link"
|
||||
import IconCircleDot from "~icons/lucide/circle-dot"
|
||||
import IconCircle from "~icons/lucide/circle"
|
||||
import { computed, ref, Ref } from "vue"
|
||||
import {
|
||||
HoppRESTAuthBasic,
|
||||
HoppRESTAuthBearer,
|
||||
HoppRESTAuthOAuth2,
|
||||
HoppRESTAuthAPIKey,
|
||||
} from "@hoppscotch/data"
|
||||
import { computed, ref } from "vue"
|
||||
import { HoppRESTAuth } from "@hoppscotch/data"
|
||||
import { pluckRef } from "@composables/ref"
|
||||
import { useStream } from "@composables/stream"
|
||||
import { useI18n } from "@composables/i18n"
|
||||
import { useColorMode } from "@composables/theming"
|
||||
import { restAuth$, setRESTAuth } from "~/newstore/RESTSession"
|
||||
import { useVModel } from "@vueuse/core"
|
||||
|
||||
const t = useI18n()
|
||||
|
||||
const colorMode = useColorMode()
|
||||
|
||||
const auth = useStream(
|
||||
restAuth$,
|
||||
{ authType: "none", authActive: true },
|
||||
setRESTAuth
|
||||
)
|
||||
const props = defineProps<{
|
||||
modelValue: HoppRESTAuth
|
||||
}>()
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: "update:modelValue", value: HoppRESTAuth): void
|
||||
}>()
|
||||
|
||||
const auth = useVModel(props, "modelValue", emit)
|
||||
|
||||
const AUTH_KEY_NAME = {
|
||||
basic: "Basic Auth",
|
||||
bearer: "Bearer",
|
||||
"oauth-2": "OAuth 2.0",
|
||||
"api-key": "API key",
|
||||
none: "None",
|
||||
} as const
|
||||
|
||||
const authType = pluckRef(auth, "authType")
|
||||
const authName = computed(() => {
|
||||
if (authType.value === "basic") return "Basic Auth"
|
||||
else if (authType.value === "bearer") return "Bearer"
|
||||
else if (authType.value === "oauth-2") return "OAuth 2.0"
|
||||
else if (authType.value === "api-key") return "API key"
|
||||
else return "None"
|
||||
})
|
||||
const authName = computed(() =>
|
||||
AUTH_KEY_NAME[authType.value] ? AUTH_KEY_NAME[authType.value] : "None"
|
||||
)
|
||||
const authActive = pluckRef(auth, "authActive")
|
||||
const basicUsername = pluckRef(auth as Ref<HoppRESTAuthBasic>, "username")
|
||||
const basicPassword = pluckRef(auth as Ref<HoppRESTAuthBasic>, "password")
|
||||
const bearerToken = pluckRef(auth as Ref<HoppRESTAuthBearer>, "token")
|
||||
const oauth2Token = pluckRef(auth as Ref<HoppRESTAuthOAuth2>, "token")
|
||||
const apiKey = pluckRef(auth as Ref<HoppRESTAuthAPIKey>, "key")
|
||||
const apiValue = pluckRef(auth as Ref<HoppRESTAuthAPIKey>, "value")
|
||||
const addTo = pluckRef(auth as Ref<HoppRESTAuthAPIKey>, "addTo")
|
||||
if (typeof addTo.value === "undefined") {
|
||||
addTo.value = "Headers"
|
||||
apiKey.value = ""
|
||||
apiValue.value = ""
|
||||
}
|
||||
|
||||
const clearContent = () => {
|
||||
auth.value = {
|
||||
@@ -301,5 +223,4 @@ const clearContent = () => {
|
||||
|
||||
// Template refs
|
||||
const tippyActions = ref<any | null>(null)
|
||||
const authTippyActions = ref<any | null>(null)
|
||||
</script>
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
>
|
||||
<span class="select-wrapper">
|
||||
<HoppButtonSecondary
|
||||
:label="contentType || t('state.none')"
|
||||
:label="body.contentType || t('state.none')"
|
||||
class="pr-8 ml-2 rounded-none"
|
||||
/>
|
||||
</span>
|
||||
@@ -28,11 +28,11 @@
|
||||
>
|
||||
<HoppSmartItem
|
||||
:label="t('state.none')"
|
||||
:info-icon="contentType === null ? IconDone : null"
|
||||
:active-info-icon="contentType === null"
|
||||
:info-icon="(body.contentType === null ? IconDone : null) as any"
|
||||
:active-info-icon="body.contentType === null"
|
||||
@click="
|
||||
() => {
|
||||
contentType = null
|
||||
body.contentType = null
|
||||
hide()
|
||||
}
|
||||
"
|
||||
@@ -57,12 +57,12 @@
|
||||
:key="`contentTypeItem-${contentTypeIndex}`"
|
||||
:label="contentTypeItem"
|
||||
:info-icon="
|
||||
contentTypeItem === contentType ? IconDone : null
|
||||
contentTypeItem === body.contentType ? IconDone : null
|
||||
"
|
||||
:active-info-icon="contentTypeItem === contentType"
|
||||
:active-info-icon="contentTypeItem === body.contentType"
|
||||
@click="
|
||||
() => {
|
||||
contentType = contentTypeItem
|
||||
body.contentType = contentTypeItem
|
||||
hide()
|
||||
}
|
||||
"
|
||||
@@ -93,13 +93,17 @@
|
||||
/>
|
||||
</span>
|
||||
</div>
|
||||
<HttpBodyParameters v-if="contentType === 'multipart/form-data'" />
|
||||
<HttpURLEncodedParams
|
||||
v-else-if="contentType === 'application/x-www-form-urlencoded'"
|
||||
<HttpBodyParameters
|
||||
v-if="body.contentType === 'multipart/form-data'"
|
||||
v-model="body"
|
||||
/>
|
||||
<HttpRawBody v-else-if="contentType !== null" :content-type="contentType" />
|
||||
<HttpURLEncodedParams
|
||||
v-else-if="body.contentType === 'application/x-www-form-urlencoded'"
|
||||
v-model="body"
|
||||
/>
|
||||
<HttpRawBody v-else-if="body.contentType !== null" v-model="body" />
|
||||
<div
|
||||
v-if="contentType == null"
|
||||
v-if="body.contentType == null"
|
||||
class="flex flex-col items-center justify-center p-4 text-secondaryLight"
|
||||
>
|
||||
<img
|
||||
@@ -123,38 +127,37 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import IconDone from "~icons/lucide/check"
|
||||
import IconInfo from "~icons/lucide/info"
|
||||
import IconRefreshCW from "~icons/lucide/refresh-cw"
|
||||
import IconExternalLink from "~icons/lucide/external-link"
|
||||
import { computed, ref } from "vue"
|
||||
import { pipe } from "fp-ts/function"
|
||||
import * as A from "fp-ts/Array"
|
||||
import * as O from "fp-ts/Option"
|
||||
import { RequestOptionTabs } from "./RequestOptions.vue"
|
||||
import { useStream } from "@composables/stream"
|
||||
import { useI18n } from "@composables/i18n"
|
||||
import { useColorMode } from "@composables/theming"
|
||||
import { HoppRESTHeader, HoppRESTReqBody } from "@hoppscotch/data"
|
||||
import { useVModel } from "@vueuse/core"
|
||||
import * as A from "fp-ts/Array"
|
||||
import { pipe } from "fp-ts/function"
|
||||
import * as O from "fp-ts/Option"
|
||||
import { computed, ref } from "vue"
|
||||
import { segmentedContentTypes } from "~/helpers/utils/contenttypes"
|
||||
import {
|
||||
restContentType$,
|
||||
restHeaders$,
|
||||
setRESTContentType,
|
||||
setRESTHeaders,
|
||||
addRESTHeader,
|
||||
} from "~/newstore/RESTSession"
|
||||
import IconDone from "~icons/lucide/check"
|
||||
import IconExternalLink from "~icons/lucide/external-link"
|
||||
import IconInfo from "~icons/lucide/info"
|
||||
import IconRefreshCW from "~icons/lucide/refresh-cw"
|
||||
import { RequestOptionTabs } from "./RequestOptions.vue"
|
||||
|
||||
const colorMode = useColorMode()
|
||||
const t = useI18n()
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: "change-tab", value: string): void
|
||||
const props = defineProps<{
|
||||
body: HoppRESTReqBody
|
||||
headers: HoppRESTHeader[]
|
||||
}>()
|
||||
|
||||
const contentType = useStream(restContentType$, null, setRESTContentType)
|
||||
const emit = defineEmits<{
|
||||
(e: "change-tab", value: RequestOptionTabs): void
|
||||
(e: "update:headers", value: HoppRESTHeader[]): void
|
||||
(e: "update:body", value: HoppRESTReqBody): void
|
||||
}>()
|
||||
|
||||
// The functional headers list (the headers actually in the system)
|
||||
const headers = useStream(restHeaders$, [], setRESTHeaders)
|
||||
const headers = useVModel(props, "headers", emit)
|
||||
const body = useVModel(props, "body", emit)
|
||||
|
||||
const overridenContentType = computed(() =>
|
||||
pipe(
|
||||
@@ -168,7 +171,9 @@ const overridenContentType = computed(() =>
|
||||
const contentTypeOverride = (tab: RequestOptionTabs) => {
|
||||
emit("change-tab", tab)
|
||||
if (!isContentTypeAlreadyExist()) {
|
||||
addRESTHeader({
|
||||
// TODO: Fix this
|
||||
|
||||
headers.value.push({
|
||||
key: "Content-Type",
|
||||
value: "",
|
||||
active: true,
|
||||
|
||||
@@ -186,14 +186,26 @@ import { ref, watch } from "vue"
|
||||
import { flow, pipe } from "fp-ts/function"
|
||||
import * as O from "fp-ts/Option"
|
||||
import * as A from "fp-ts/Array"
|
||||
import { FormDataKeyValue } from "@hoppscotch/data"
|
||||
import { FormDataKeyValue, HoppRESTReqBody } from "@hoppscotch/data"
|
||||
import { isEqual, clone } from "lodash-es"
|
||||
import draggable from "vuedraggable-es"
|
||||
import { pluckRef } from "@composables/ref"
|
||||
import { useI18n } from "@composables/i18n"
|
||||
import { useToast } from "@composables/toast"
|
||||
import { useColorMode } from "@composables/theming"
|
||||
import { useRESTRequestBody } from "~/newstore/RESTSession"
|
||||
import { useVModel } from "@vueuse/core"
|
||||
|
||||
type Body = HoppRESTReqBody & { contentType: "multipart/form-data" }
|
||||
|
||||
const props = defineProps<{
|
||||
modelValue: Body
|
||||
}>()
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: "update:modelValue", val: Body): void
|
||||
}>()
|
||||
|
||||
const body = useVModel(props, "modelValue", emit)
|
||||
|
||||
type WorkingFormDataKeyValue = { id: number; entry: FormDataKeyValue }
|
||||
|
||||
@@ -206,7 +218,7 @@ const idTicker = ref(0)
|
||||
|
||||
const deletionToast = ref<{ goAway: (delay: number) => void } | null>(null)
|
||||
|
||||
const bodyParams = pluckRef<any, any>(useRESTRequestBody(), "body")
|
||||
const bodyParams = pluckRef(body, "body")
|
||||
|
||||
// The UI representation of the parameters list (has the empty end param)
|
||||
const workingParams = ref<WorkingFormDataKeyValue[]>([
|
||||
@@ -355,7 +367,7 @@ const clearContent = () => {
|
||||
const setRequestAttachment = (
|
||||
index: number,
|
||||
entry: FormDataKeyValue,
|
||||
event: InputEvent
|
||||
event: InputEvent | Event
|
||||
) => {
|
||||
// check if file exists or not
|
||||
if ((event.target as HTMLInputElement).files?.length === 0) {
|
||||
|
||||
@@ -148,7 +148,6 @@ import {
|
||||
resolvesEnvsInBody,
|
||||
} from "~/helpers/utils/EffectiveURL"
|
||||
import { getAggregateEnvs } from "~/newstore/environments"
|
||||
import { getRESTRequest } from "~/newstore/RESTSession"
|
||||
import { useI18n } from "@composables/i18n"
|
||||
import { useToast } from "@composables/toast"
|
||||
import {
|
||||
@@ -164,6 +163,8 @@ import {
|
||||
import IconCopy from "~icons/lucide/copy"
|
||||
import IconCheck from "~icons/lucide/check"
|
||||
import IconWrapText from "~icons/lucide/wrap-text"
|
||||
import { currentActiveTab } from "~/helpers/rest/tab"
|
||||
import cloneDeep from "lodash-es/cloneDeep"
|
||||
|
||||
const t = useI18n()
|
||||
|
||||
@@ -177,7 +178,7 @@ const emit = defineEmits<{
|
||||
|
||||
const toast = useToast()
|
||||
|
||||
const request = ref(getRESTRequest())
|
||||
const request = ref(cloneDeep(currentActiveTab.value.document.request))
|
||||
const codegenType = ref<CodegenName>("shell-curl")
|
||||
const errorState = ref(false)
|
||||
|
||||
@@ -246,7 +247,7 @@ watch(
|
||||
() => props.show,
|
||||
(goingToShow) => {
|
||||
if (goingToShow) {
|
||||
request.value = getRESTRequest()
|
||||
request.value = cloneDeep(currentActiveTab.value.document.request)
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
@@ -48,7 +48,7 @@
|
||||
<div v-else>
|
||||
<draggable
|
||||
v-model="workingHeaders"
|
||||
:item-key="(header) => `header-${header.id}`"
|
||||
:item-key="(header: WorkingHeader) => `header-${header.id}`"
|
||||
animation="250"
|
||||
handle=".draggable-handle"
|
||||
draggable=".draggable-content"
|
||||
@@ -240,10 +240,11 @@ import IconEyeOff from "~icons/lucide/eye-off"
|
||||
import IconArrowUpRight from "~icons/lucide/arrow-up-right"
|
||||
import IconWrapText from "~icons/lucide/wrap-text"
|
||||
import { useColorMode } from "@composables/theming"
|
||||
import { computed, reactive, Ref, ref, watch } from "vue"
|
||||
import { computed, reactive, ref, watch } from "vue"
|
||||
import { isEqual, cloneDeep } from "lodash-es"
|
||||
import {
|
||||
HoppRESTHeader,
|
||||
HoppRESTRequest,
|
||||
parseRawKeyValueEntriesE,
|
||||
rawKeyValueEntriesToString,
|
||||
RawKeyValueEntry,
|
||||
@@ -256,15 +257,9 @@ import * as A from "fp-ts/Array"
|
||||
import draggable from "vuedraggable-es"
|
||||
import { RequestOptionTabs } from "./RequestOptions.vue"
|
||||
import { useCodemirror } from "@composables/codemirror"
|
||||
import {
|
||||
getRESTRequest,
|
||||
restHeaders$,
|
||||
restRequest$,
|
||||
setRESTHeaders,
|
||||
} from "~/newstore/RESTSession"
|
||||
import { commonHeaders } from "~/helpers/headers"
|
||||
import { useI18n } from "@composables/i18n"
|
||||
import { useReadonlyStream, useStream } from "@composables/stream"
|
||||
import { useReadonlyStream } from "@composables/stream"
|
||||
import { useToast } from "@composables/toast"
|
||||
import linter from "~/helpers/editor/linting/rawKeyValue"
|
||||
import { throwError } from "~/helpers/functional/error"
|
||||
@@ -274,6 +269,7 @@ import {
|
||||
getComputedHeaders,
|
||||
} from "~/helpers/utils/EffectiveURL"
|
||||
import { aggregateEnvs$, getAggregateEnvs } from "~/newstore/environments"
|
||||
import { useVModel } from "@vueuse/core"
|
||||
|
||||
const t = useI18n()
|
||||
const toast = useToast()
|
||||
@@ -288,10 +284,16 @@ const linewrapEnabled = ref(true)
|
||||
|
||||
const deletionToast = ref<{ goAway: (delay: number) => void } | null>(null)
|
||||
|
||||
// v-model integration with props and emit
|
||||
const props = defineProps<{ modelValue: HoppRESTRequest }>()
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: "change-tab", value: RequestOptionTabs): void
|
||||
(e: "update:modelValue", value: HoppRESTRequest): void
|
||||
}>()
|
||||
|
||||
const request = useVModel(props, "modelValue", emit)
|
||||
|
||||
useCodemirror(
|
||||
bulkEditor,
|
||||
bulkHeaders,
|
||||
@@ -307,13 +309,10 @@ useCodemirror(
|
||||
})
|
||||
)
|
||||
|
||||
// The functional headers list (the headers actually in the system)
|
||||
const headers = useStream(restHeaders$, [], setRESTHeaders) as Ref<
|
||||
HoppRESTHeader[]
|
||||
>
|
||||
type WorkingHeader = HoppRESTHeader & { id: number }
|
||||
|
||||
// The UI representation of the headers list (has the empty end headers)
|
||||
const workingHeaders = ref<Array<HoppRESTHeader & { id: number }>>([
|
||||
const workingHeaders = ref<Array<WorkingHeader>>([
|
||||
{
|
||||
id: idTicker.value++,
|
||||
key: "",
|
||||
@@ -339,7 +338,7 @@ watch(workingHeaders, (headersList) => {
|
||||
|
||||
// Sync logic between headers and working/bulk headers
|
||||
watch(
|
||||
headers,
|
||||
request.value.headers,
|
||||
(newHeadersList) => {
|
||||
// Sync should overwrite working headers
|
||||
const filteredWorkingHeaders = pipe(
|
||||
@@ -388,8 +387,8 @@ watch(workingHeaders, (newWorkingHeaders) => {
|
||||
)
|
||||
)
|
||||
|
||||
if (!isEqual(headers.value, fixedHeaders)) {
|
||||
headers.value = cloneDeep(fixedHeaders)
|
||||
if (!isEqual(request.value.headers, fixedHeaders)) {
|
||||
request.value.headers = cloneDeep(fixedHeaders)
|
||||
}
|
||||
})
|
||||
|
||||
@@ -405,8 +404,8 @@ watch(bulkHeaders, (newBulkHeaders) => {
|
||||
E.getOrElse(() => [] as RawKeyValueEntry[])
|
||||
)
|
||||
|
||||
if (!isEqual(headers.value, filteredBulkHeaders)) {
|
||||
headers.value = filteredBulkHeaders
|
||||
if (!isEqual(props.modelValue, filteredBulkHeaders)) {
|
||||
request.value.headers = filteredBulkHeaders
|
||||
}
|
||||
})
|
||||
|
||||
@@ -481,11 +480,10 @@ const clearContent = () => {
|
||||
bulkHeaders.value = ""
|
||||
}
|
||||
|
||||
const restRequest = useReadonlyStream(restRequest$, getRESTRequest())
|
||||
const aggregateEnvs = useReadonlyStream(aggregateEnvs$, getAggregateEnvs())
|
||||
|
||||
const computedHeaders = computed(() =>
|
||||
getComputedHeaders(restRequest.value, aggregateEnvs.value).map(
|
||||
getComputedHeaders(request.value, aggregateEnvs.value).map(
|
||||
(header, index) => ({
|
||||
id: `header-${index}`,
|
||||
...header,
|
||||
|
||||
@@ -81,7 +81,6 @@
|
||||
import { reactive, ref, watch } from "vue"
|
||||
import { refAutoReset } from "@vueuse/core"
|
||||
import { useCodemirror } from "@composables/codemirror"
|
||||
import { setRESTRequest } from "~/newstore/RESTSession"
|
||||
import { useI18n } from "@composables/i18n"
|
||||
import { useToast } from "@composables/toast"
|
||||
import { parseCurlToHoppRESTReq } from "~/helpers/curl"
|
||||
@@ -94,6 +93,7 @@ import IconWrapText from "~icons/lucide/wrap-text"
|
||||
import IconClipboard from "~icons/lucide/clipboard"
|
||||
import IconCheck from "~icons/lucide/check"
|
||||
import IconTrash2 from "~icons/lucide/trash-2"
|
||||
import { currentActiveTab } from "~/helpers/rest/tab"
|
||||
|
||||
const t = useI18n()
|
||||
|
||||
@@ -144,7 +144,7 @@ const handleImport = () => {
|
||||
try {
|
||||
const req = parseCurlToHoppRESTReq(text)
|
||||
|
||||
setRESTRequest(req)
|
||||
currentActiveTab.value.document.request = req
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
toast.error(`${t("error.curl_invalid_format")}`)
|
||||
|
||||
@@ -31,89 +31,72 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { Ref, defineComponent } from "vue"
|
||||
<script setup lang="ts">
|
||||
import { ref, watch } from "vue"
|
||||
import { HoppRESTAuthOAuth2, parseTemplateString } from "@hoppscotch/data"
|
||||
import { pluckRef } from "@composables/ref"
|
||||
import { useI18n } from "@composables/i18n"
|
||||
import { useStream } from "@composables/stream"
|
||||
import { useToast } from "@composables/toast"
|
||||
import { restAuth$, setRESTAuth } from "~/newstore/RESTSession"
|
||||
import { tokenRequest } from "~/helpers/oauth"
|
||||
import { getCombinedEnvVariables } from "~/helpers/preRequest"
|
||||
|
||||
export default defineComponent({
|
||||
setup() {
|
||||
const t = useI18n()
|
||||
const toast = useToast()
|
||||
const t = useI18n()
|
||||
const toast = useToast()
|
||||
|
||||
const auth = useStream(
|
||||
restAuth$,
|
||||
{ authType: "none", authActive: true },
|
||||
setRESTAuth
|
||||
)
|
||||
const props = defineProps<{
|
||||
modelValue: HoppRESTAuthOAuth2
|
||||
}>()
|
||||
|
||||
const oidcDiscoveryURL = pluckRef(
|
||||
auth as Ref<HoppRESTAuthOAuth2>,
|
||||
"oidcDiscoveryURL"
|
||||
)
|
||||
const emit = defineEmits<{
|
||||
(e: "update:modelValue", value: HoppRESTAuthOAuth2): void
|
||||
}>()
|
||||
|
||||
const authURL = pluckRef(auth as Ref<HoppRESTAuthOAuth2>, "authURL")
|
||||
const auth = ref(props.modelValue)
|
||||
|
||||
const accessTokenURL = pluckRef(
|
||||
auth as Ref<HoppRESTAuthOAuth2>,
|
||||
"accessTokenURL"
|
||||
)
|
||||
watch(
|
||||
() => auth.value,
|
||||
(val) => {
|
||||
emit("update:modelValue", val)
|
||||
}
|
||||
)
|
||||
|
||||
const clientID = pluckRef(auth as Ref<HoppRESTAuthOAuth2>, "clientID")
|
||||
const oidcDiscoveryURL = pluckRef(auth, "oidcDiscoveryURL")
|
||||
|
||||
const clientSecret = pluckRef(
|
||||
auth as Ref<HoppRESTAuthOAuth2>,
|
||||
"clientSecret"
|
||||
)
|
||||
const authURL = pluckRef(auth, "authURL")
|
||||
|
||||
const scope = pluckRef(auth as Ref<HoppRESTAuthOAuth2>, "scope")
|
||||
const accessTokenURL = pluckRef(auth, "accessTokenURL")
|
||||
|
||||
const handleAccessTokenRequest = async () => {
|
||||
if (
|
||||
oidcDiscoveryURL.value === "" &&
|
||||
(authURL.value === "" || accessTokenURL.value === "")
|
||||
) {
|
||||
toast.error(`${t("error.incomplete_config_urls")}`)
|
||||
return
|
||||
}
|
||||
const envs = getCombinedEnvVariables()
|
||||
const envVars = [...envs.selected, ...envs.global]
|
||||
const clientID = pluckRef(auth, "clientID")
|
||||
|
||||
try {
|
||||
const tokenReqParams = {
|
||||
grantType: "code",
|
||||
oidcDiscoveryUrl: parseTemplateString(
|
||||
oidcDiscoveryURL.value,
|
||||
envVars
|
||||
),
|
||||
authUrl: parseTemplateString(authURL.value, envVars),
|
||||
accessTokenUrl: parseTemplateString(accessTokenURL.value, envVars),
|
||||
clientId: parseTemplateString(clientID.value, envVars),
|
||||
clientSecret: parseTemplateString(clientSecret.value, envVars),
|
||||
scope: parseTemplateString(scope.value, envVars),
|
||||
}
|
||||
await tokenRequest(tokenReqParams)
|
||||
} catch (e) {
|
||||
toast.error(`${e}`)
|
||||
}
|
||||
// TODO: Fix this type error. currently there is no type for clientSecret
|
||||
const clientSecret = pluckRef(auth, "clientSecret" as any)
|
||||
|
||||
const scope = pluckRef(auth, "scope")
|
||||
|
||||
const handleAccessTokenRequest = async () => {
|
||||
if (
|
||||
oidcDiscoveryURL.value === "" &&
|
||||
(authURL.value === "" || accessTokenURL.value === "")
|
||||
) {
|
||||
toast.error(`${t("error.incomplete_config_urls")}`)
|
||||
return
|
||||
}
|
||||
const envs = getCombinedEnvVariables()
|
||||
const envVars = [...envs.selected, ...envs.global]
|
||||
|
||||
try {
|
||||
const tokenReqParams = {
|
||||
grantType: "code",
|
||||
oidcDiscoveryUrl: parseTemplateString(oidcDiscoveryURL.value, envVars),
|
||||
authUrl: parseTemplateString(authURL.value, envVars),
|
||||
accessTokenUrl: parseTemplateString(accessTokenURL.value, envVars),
|
||||
clientId: parseTemplateString(clientID.value, envVars),
|
||||
clientSecret: parseTemplateString(clientSecret.value, envVars),
|
||||
scope: parseTemplateString(scope.value, envVars),
|
||||
}
|
||||
|
||||
return {
|
||||
oidcDiscoveryURL,
|
||||
authURL,
|
||||
accessTokenURL,
|
||||
clientID,
|
||||
clientSecret,
|
||||
scope,
|
||||
handleAccessTokenRequest,
|
||||
t,
|
||||
}
|
||||
},
|
||||
})
|
||||
await tokenRequest(tokenReqParams)
|
||||
} catch (e) {
|
||||
toast.error(`${e}`)
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -179,7 +179,7 @@ import IconCheckCircle from "~icons/lucide/check-circle"
|
||||
import IconCircle from "~icons/lucide/circle"
|
||||
import IconTrash from "~icons/lucide/trash"
|
||||
import IconWrapText from "~icons/lucide/wrap-text"
|
||||
import { reactive, Ref, ref, watch } from "vue"
|
||||
import { reactive, ref, watch } from "vue"
|
||||
import { flow, pipe } from "fp-ts/function"
|
||||
import * as O from "fp-ts/Option"
|
||||
import * as A from "fp-ts/Array"
|
||||
@@ -198,10 +198,9 @@ import { useCodemirror } from "@composables/codemirror"
|
||||
import { useColorMode } from "@composables/theming"
|
||||
import { useI18n } from "@composables/i18n"
|
||||
import { useToast } from "@composables/toast"
|
||||
import { useStream } from "@composables/stream"
|
||||
import { restParams$, setRESTParams } from "~/newstore/RESTSession"
|
||||
import { throwError } from "@functional/error"
|
||||
import { objRemoveKey } from "@functional/object"
|
||||
import { useVModel } from "@vueuse/core"
|
||||
|
||||
const colorMode = useColorMode()
|
||||
|
||||
@@ -232,8 +231,16 @@ useCodemirror(
|
||||
})
|
||||
)
|
||||
|
||||
const props = defineProps<{
|
||||
modelValue: HoppRESTParam[]
|
||||
}>()
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: "update:modelValue", value: Array<HoppRESTParam>): void
|
||||
}>()
|
||||
|
||||
// The functional parameters list (the parameters actually applied to the session)
|
||||
const params = useStream(restParams$, [], setRESTParams) as Ref<HoppRESTParam[]>
|
||||
const params = useVModel(props, "modelValue", emit)
|
||||
|
||||
// The UI representation of the parameters list (has the empty end param)
|
||||
const workingParams = ref<Array<HoppRESTParam & { id: number }>>([
|
||||
|
||||
@@ -66,16 +66,23 @@ import IconHelpCircle from "~icons/lucide/help-circle"
|
||||
import IconWrapText from "~icons/lucide/wrap-text"
|
||||
import IconTrash2 from "~icons/lucide/trash-2"
|
||||
import { reactive, ref } from "vue"
|
||||
import { usePreRequestScript } from "~/newstore/RESTSession"
|
||||
import snippets from "@helpers/preRequestScriptSnippets"
|
||||
import { useCodemirror } from "@composables/codemirror"
|
||||
import linter from "~/helpers/editor/linting/preRequest"
|
||||
import completer from "~/helpers/editor/completion/preRequest"
|
||||
import { useI18n } from "@composables/i18n"
|
||||
import { useVModel } from "@vueuse/core"
|
||||
|
||||
const t = useI18n()
|
||||
|
||||
const preRequestScript = usePreRequestScript()
|
||||
const props = defineProps<{
|
||||
modelValue: string
|
||||
}>()
|
||||
const emit = defineEmits<{
|
||||
(e: "update:modelValue", value: string): void
|
||||
}>()
|
||||
|
||||
const preRequestScript = useVModel(props, "modelValue", emit)
|
||||
|
||||
const preRequestEditor = ref<any | null>(null)
|
||||
const linewrapEnabled = ref(true)
|
||||
|
||||
@@ -35,7 +35,7 @@
|
||||
'application/hal+json',
|
||||
'application/vnd.api+json',
|
||||
'application/xml',
|
||||
].includes(contentType)
|
||||
].includes(body.contentType)
|
||||
"
|
||||
v-tippy="{ theme: 'tooltip' }"
|
||||
:title="t('action.prettify')"
|
||||
@@ -74,16 +74,14 @@ import IconInfo from "~icons/lucide/info"
|
||||
import { computed, reactive, Ref, ref, watch } from "vue"
|
||||
import * as TO from "fp-ts/TaskOption"
|
||||
import { pipe } from "fp-ts/function"
|
||||
import { ValidContentTypes } from "@hoppscotch/data"
|
||||
import { refAutoReset } from "@vueuse/core"
|
||||
import { HoppRESTReqBody, ValidContentTypes } from "@hoppscotch/data"
|
||||
import { refAutoReset, useVModel } from "@vueuse/core"
|
||||
import { useCodemirror } from "@composables/codemirror"
|
||||
import { getEditorLangForMimeType } from "@helpers/editorutils"
|
||||
import { pluckRef } from "@composables/ref"
|
||||
import { useI18n } from "@composables/i18n"
|
||||
import { useToast } from "@composables/toast"
|
||||
import { isJSONContentType } from "~/helpers/utils/contenttypes"
|
||||
import { useRESTRequestBody } from "~/newstore/RESTSession"
|
||||
|
||||
import jsonLinter from "~/helpers/editor/linting/json"
|
||||
import { readFileAsText } from "~/helpers/functional/files"
|
||||
|
||||
@@ -92,27 +90,35 @@ type PossibleContentTypes = Exclude<
|
||||
"multipart/form-data" | "application/x-www-form-urlencoded"
|
||||
>
|
||||
|
||||
type Body = HoppRESTReqBody & { contentType: PossibleContentTypes }
|
||||
|
||||
const props = defineProps<{
|
||||
modelValue: Body
|
||||
}>()
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: "update:modelValue", val: Body): void
|
||||
}>()
|
||||
|
||||
const body = useVModel(props, "modelValue", emit)
|
||||
|
||||
const t = useI18n()
|
||||
|
||||
const payload = ref<HTMLInputElement | null>(null)
|
||||
|
||||
const props = defineProps<{
|
||||
contentType: PossibleContentTypes
|
||||
}>()
|
||||
|
||||
const toast = useToast()
|
||||
|
||||
const rawParamsBody = pluckRef(useRESTRequestBody(), "body")
|
||||
const rawParamsBody = pluckRef(body, "body")
|
||||
|
||||
const prettifyIcon = refAutoReset<
|
||||
typeof IconWand2 | typeof IconCheck | typeof IconInfo
|
||||
>(IconWand2, 1000)
|
||||
|
||||
const rawInputEditorLang = computed(() =>
|
||||
getEditorLangForMimeType(props.contentType)
|
||||
getEditorLangForMimeType(body.value.contentType)
|
||||
)
|
||||
const langLinter = computed(() =>
|
||||
isJSONContentType(props.contentType) ? jsonLinter : null
|
||||
isJSONContentType(body.value.contentType) ? jsonLinter : null
|
||||
)
|
||||
|
||||
const linewrapEnabled = ref(true)
|
||||
@@ -175,10 +181,10 @@ const uploadPayload = async (e: Event) => {
|
||||
const prettifyRequestBody = () => {
|
||||
let prettifyBody = ""
|
||||
try {
|
||||
if (props.contentType.endsWith("json")) {
|
||||
if (body.value.contentType.endsWith("json")) {
|
||||
const jsonObj = JSON.parse(rawParamsBody.value as string)
|
||||
prettifyBody = JSON.stringify(jsonObj, null, 2)
|
||||
} else if (props.contentType == "application/xml") {
|
||||
} else if (body.value.contentType == "application/xml") {
|
||||
prettifyBody = prettifyXML(rawParamsBody.value as string)
|
||||
}
|
||||
rawParamsBody.value = prettifyBody
|
||||
|
||||
@@ -17,10 +17,10 @@
|
||||
<input
|
||||
id="method"
|
||||
class="flex px-4 py-2 font-semibold transition rounded-l cursor-pointer text-secondaryDark w-26 bg-primaryLight"
|
||||
:value="newMethod"
|
||||
:value="tab.document.request.method"
|
||||
:readonly="!isCustomMethod"
|
||||
:placeholder="`${t('request.method')}`"
|
||||
@input="onSelectMethod($event.target.value)"
|
||||
@input="onSelectMethod($event)"
|
||||
/>
|
||||
</span>
|
||||
<template #content="{ hide }">
|
||||
@@ -36,7 +36,7 @@
|
||||
:label="method"
|
||||
@click="
|
||||
() => {
|
||||
onSelectMethod(method)
|
||||
updateMethod(method)
|
||||
hide()
|
||||
}
|
||||
"
|
||||
@@ -50,7 +50,7 @@
|
||||
class="flex flex-1 overflow-auto transition border-l rounded-r border-divider bg-primaryLight whitespace-nowrap"
|
||||
>
|
||||
<SmartEnvInput
|
||||
v-model="newEndpoint"
|
||||
v-model="tab.document.request.endpoint"
|
||||
:placeholder="`${t('request.url')}`"
|
||||
@enter="newSendRequest()"
|
||||
@paste="onPasteUrl($event)"
|
||||
@@ -161,12 +161,11 @@
|
||||
ref="saveTippyActions"
|
||||
class="flex flex-col focus:outline-none"
|
||||
tabindex="0"
|
||||
@keyup.s="saveRequestAction.$el.click()"
|
||||
@keyup.escape="hide()"
|
||||
>
|
||||
<input
|
||||
id="request-name"
|
||||
v-model="requestName"
|
||||
v-model="tab.document.request.name"
|
||||
:placeholder="`${t('request.name')}`"
|
||||
name="request-name"
|
||||
type="text"
|
||||
@@ -195,7 +194,6 @@
|
||||
ref="saveRequestAction"
|
||||
:label="`${t('request.save_as')}`"
|
||||
:icon="IconFolderPlus"
|
||||
:shortcut="['S']"
|
||||
@click="
|
||||
() => {
|
||||
showSaveRequestModal = true
|
||||
@@ -227,55 +225,40 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import IconShare2 from "~icons/lucide/share-2"
|
||||
import IconCopy from "~icons/lucide/copy"
|
||||
import IconCheck from "~icons/lucide/check"
|
||||
import IconFileCode from "~icons/lucide/file-code"
|
||||
import IconCode2 from "~icons/lucide/code-2"
|
||||
import IconRotateCCW from "~icons/lucide/rotate-ccw"
|
||||
import IconSave from "~icons/lucide/save"
|
||||
import IconChevronDown from "~icons/lucide/chevron-down"
|
||||
import IconLink2 from "~icons/lucide/link-2"
|
||||
import IconFolderPlus from "~icons/lucide/folder-plus"
|
||||
import { computed, ref, watch } from "vue"
|
||||
import { isLeft, isRight } from "fp-ts/lib/Either"
|
||||
import * as E from "fp-ts/Either"
|
||||
import { cloneDeep } from "lodash-es"
|
||||
import { refAutoReset } from "@vueuse/core"
|
||||
import {
|
||||
updateRESTResponse,
|
||||
restEndpoint$,
|
||||
setRESTEndpoint,
|
||||
restMethod$,
|
||||
updateRESTMethod,
|
||||
resetRESTRequest,
|
||||
useRESTRequestName,
|
||||
getRESTSaveContext,
|
||||
getRESTRequest,
|
||||
restRequest$,
|
||||
setRESTSaveContext,
|
||||
} from "~/newstore/RESTSession"
|
||||
import { editRESTRequest } from "~/newstore/collections"
|
||||
import { runRESTRequest$ } from "~/helpers/RequestRunner"
|
||||
import {
|
||||
useStream,
|
||||
useStreamSubscriber,
|
||||
useReadonlyStream,
|
||||
} from "@composables/stream"
|
||||
import { useI18n } from "@composables/i18n"
|
||||
import { useToast } from "@composables/toast"
|
||||
import { useSetting } from "@composables/settings"
|
||||
import { startPageProgress, completePageProgress } from "@modules/loadingbar"
|
||||
import { useStreamSubscriber } from "@composables/stream"
|
||||
import { useToast } from "@composables/toast"
|
||||
import { completePageProgress, startPageProgress } from "@modules/loadingbar"
|
||||
import { refAutoReset, useVModel } from "@vueuse/core"
|
||||
import * as E from "fp-ts/Either"
|
||||
import { isLeft, isRight } from "fp-ts/lib/Either"
|
||||
import { computed, ref, watch } from "vue"
|
||||
import { defineActionHandler } from "~/helpers/actions"
|
||||
import { copyToClipboard } from "~/helpers/utils/clipboard"
|
||||
import { createShortcode } from "~/helpers/backend/mutations/Shortcode"
|
||||
import { runMutation } from "~/helpers/backend/GQLClient"
|
||||
import { UpdateRequestDocument } from "~/helpers/backend/graphql"
|
||||
import { createShortcode } from "~/helpers/backend/mutations/Shortcode"
|
||||
import { getPlatformSpecialKey as getSpecialKey } from "~/helpers/platformutils"
|
||||
import {
|
||||
cancelRunningExtensionRequest,
|
||||
hasExtensionInstalled,
|
||||
} from "~/helpers/strategies/ExtensionStrategy"
|
||||
import { runRESTRequest$ } from "~/helpers/RequestRunner"
|
||||
import { HoppRESTResponse } from "~/helpers/types/HoppRESTResponse"
|
||||
import { copyToClipboard } from "~/helpers/utils/clipboard"
|
||||
import { editRESTRequest } from "~/newstore/collections"
|
||||
import IconCheck from "~icons/lucide/check"
|
||||
import IconChevronDown from "~icons/lucide/chevron-down"
|
||||
import IconCode2 from "~icons/lucide/code-2"
|
||||
import IconCopy from "~icons/lucide/copy"
|
||||
import IconFileCode from "~icons/lucide/file-code"
|
||||
import IconFolderPlus from "~icons/lucide/folder-plus"
|
||||
import IconLink2 from "~icons/lucide/link-2"
|
||||
import IconRotateCCW from "~icons/lucide/rotate-ccw"
|
||||
import IconSave from "~icons/lucide/save"
|
||||
import IconShare2 from "~icons/lucide/share-2"
|
||||
import { HoppRESTTab } from "~/helpers/rest/tab"
|
||||
import { getDefaultRESTRequest } from "~/helpers/rest/default"
|
||||
|
||||
const t = useI18n()
|
||||
|
||||
@@ -296,9 +279,19 @@ const toast = useToast()
|
||||
|
||||
const { subscribeToStream } = useStreamSubscriber()
|
||||
|
||||
const newEndpoint = useStream(restEndpoint$, "", setRESTEndpoint)
|
||||
const props = defineProps<{ modelValue: HoppRESTTab }>()
|
||||
const emit = defineEmits(["update:modelValue"])
|
||||
|
||||
const tab = useVModel(props, "modelValue", emit)
|
||||
|
||||
const newEndpoint = computed(() => {
|
||||
return tab.value.document.request.endpoint
|
||||
})
|
||||
const newMethod = computed(() => {
|
||||
return tab.value.document.request.method
|
||||
})
|
||||
|
||||
const curlText = ref("")
|
||||
const newMethod = useStream(restMethod$, "", updateRESTMethod)
|
||||
|
||||
const loading = ref(false)
|
||||
|
||||
@@ -327,6 +320,30 @@ watch(loading, () => {
|
||||
}
|
||||
})
|
||||
|
||||
// TODO: make this oAuthURL() work
|
||||
|
||||
// function oAuthURL() {
|
||||
// const auth = useReadonlyStream(props.request.auth$, {
|
||||
// authType: "none",
|
||||
// authActive: true,
|
||||
// })
|
||||
|
||||
// const oauth2Token = pluckRef(auth as Ref<HoppRESTAuthOAuth2>, "token")
|
||||
|
||||
// onBeforeMount(async () => {
|
||||
// try {
|
||||
// const tokenInfo = await oauthRedirect()
|
||||
// if (Object.prototype.hasOwnProperty.call(tokenInfo, "access_token")) {
|
||||
// if (typeof tokenInfo === "object") {
|
||||
// oauth2Token.value = tokenInfo.access_token
|
||||
// }
|
||||
// }
|
||||
|
||||
// // eslint-disable-next-line no-empty
|
||||
// } catch (_) {}
|
||||
// })
|
||||
// }
|
||||
|
||||
const newSendRequest = async () => {
|
||||
if (newEndpoint.value === "" || /^\s+$/.test(newEndpoint.value)) {
|
||||
toast.error(`${t("empty.endpoint")}`)
|
||||
@@ -335,10 +352,14 @@ const newSendRequest = async () => {
|
||||
|
||||
ensureMethodInEndpoint()
|
||||
|
||||
console.log("Sending request", newEndpoint.value)
|
||||
|
||||
loading.value = true
|
||||
|
||||
// Double calling is because the function returns a TaskEither than should be executed
|
||||
const streamResult = await runRESTRequest$()()
|
||||
const streamResult = await runRESTRequest$(tab)()
|
||||
|
||||
console.log("Stream result", streamResult)
|
||||
|
||||
if (isRight(streamResult)) {
|
||||
subscribeToStream(
|
||||
@@ -380,9 +401,11 @@ const ensureMethodInEndpoint = () => {
|
||||
) {
|
||||
const domain = newEndpoint.value.split(/[/:#?]+/)[0]
|
||||
if (domain === "localhost" || /([0-9]+\.)*[0-9]/.test(domain)) {
|
||||
setRESTEndpoint("http://" + newEndpoint.value)
|
||||
tab.value.document.request.endpoint =
|
||||
"http://" + tab.value.document.request.endpoint
|
||||
} else {
|
||||
setRESTEndpoint("https://" + newEndpoint.value)
|
||||
tab.value.document.request.endpoint =
|
||||
"https://" + tab.value.document.request.endpoint
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -395,7 +418,7 @@ const onPasteUrl = (e: { pastedValue: string; prevValue: string }) => {
|
||||
if (isCURL(pastedData)) {
|
||||
showCurlImportModal.value = true
|
||||
curlText.value = pastedData
|
||||
newEndpoint.value = e.prevValue
|
||||
tab.value.document.request.endpoint = e.prevValue
|
||||
}
|
||||
}
|
||||
|
||||
@@ -412,15 +435,21 @@ const cancelRequest = () => {
|
||||
}
|
||||
|
||||
const updateMethod = (method: string) => {
|
||||
updateRESTMethod(method)
|
||||
tab.value.document.request.method = method
|
||||
}
|
||||
|
||||
const onSelectMethod = (method: string) => {
|
||||
updateMethod(method)
|
||||
const onSelectMethod = (e: Event | any) => {
|
||||
// type any because of value property not being recognized by TS in the event.target object. It is a valid property though.
|
||||
updateMethod(e.value)
|
||||
}
|
||||
|
||||
const clearContent = () => {
|
||||
resetRESTRequest()
|
||||
tab.value.document.request = getDefaultRESTRequest()
|
||||
}
|
||||
|
||||
const updateRESTResponse = (response: HoppRESTResponse | null) => {
|
||||
tab.value.response = response
|
||||
console.log("Updating response", response)
|
||||
}
|
||||
|
||||
const copyLinkIcon = refAutoReset<
|
||||
@@ -440,20 +469,13 @@ const shareButtonText = computed(() => {
|
||||
}
|
||||
})
|
||||
|
||||
const request = useReadonlyStream(restRequest$, getRESTRequest())
|
||||
|
||||
watch(request, () => {
|
||||
shareLink.value = null
|
||||
})
|
||||
|
||||
const copyRequest = async () => {
|
||||
if (shareLink.value) {
|
||||
copyShareLink(shareLink.value)
|
||||
} else {
|
||||
shareLink.value = ""
|
||||
fetchingShareLink.value = true
|
||||
const request = getRESTRequest()
|
||||
const shortcodeResult = await createShortcode(request)()
|
||||
const shortcodeResult = await createShortcode(tab.value.document.request)()
|
||||
if (E.isLeft(shortcodeResult)) {
|
||||
toast.error(`${shortcodeResult.left.error}`)
|
||||
shareLink.value = `${t("error.something_went_wrong")}`
|
||||
@@ -511,33 +533,26 @@ const cycleDownMethod = () => {
|
||||
}
|
||||
|
||||
const saveRequest = () => {
|
||||
const saveCtx = getRESTSaveContext()
|
||||
const saveCtx = tab.value.document.saveContext
|
||||
|
||||
if (!saveCtx) {
|
||||
showSaveRequestModal.value = true
|
||||
return
|
||||
}
|
||||
if (saveCtx.originLocation === "user-collection") {
|
||||
const req = getRESTRequest()
|
||||
const req = tab.value.document.request
|
||||
|
||||
try {
|
||||
editRESTRequest(
|
||||
saveCtx.folderPath,
|
||||
saveCtx.requestIndex,
|
||||
getRESTRequest()
|
||||
)
|
||||
setRESTSaveContext({
|
||||
originLocation: "user-collection",
|
||||
folderPath: saveCtx.folderPath,
|
||||
requestIndex: saveCtx.requestIndex,
|
||||
req: cloneDeep(req),
|
||||
})
|
||||
editRESTRequest(saveCtx.folderPath, saveCtx.requestIndex, req)
|
||||
|
||||
tab.value.document.isDirty = false
|
||||
toast.success(`${t("request.saved")}`)
|
||||
} catch (e) {
|
||||
setRESTSaveContext(null)
|
||||
tab.value.document.saveContext = undefined
|
||||
saveRequest()
|
||||
}
|
||||
} else if (saveCtx.originLocation === "team-collection") {
|
||||
const req = getRESTRequest()
|
||||
const req = tab.value.document.request
|
||||
|
||||
// TODO: handle error case (NOTE: overwriteRequestTeams is async)
|
||||
try {
|
||||
@@ -551,11 +566,8 @@ const saveRequest = () => {
|
||||
if (E.isLeft(result)) {
|
||||
toast.error(`${t("profile.no_permission")}`)
|
||||
} else {
|
||||
setRESTSaveContext({
|
||||
originLocation: "team-collection",
|
||||
requestID: saveCtx.requestID,
|
||||
req: cloneDeep(req),
|
||||
})
|
||||
tab.value.document.isDirty = false
|
||||
|
||||
toast.success(`${t("request.saved")}`)
|
||||
}
|
||||
})
|
||||
@@ -587,10 +599,11 @@ defineActionHandler("request.method.delete", () => updateMethod("DELETE"))
|
||||
defineActionHandler("request.method.head", () => updateMethod("HEAD"))
|
||||
|
||||
const isCustomMethod = computed(() => {
|
||||
return newMethod.value === "CUSTOM" || !methods.includes(newMethod.value)
|
||||
return (
|
||||
tab.value.document.request.method === "CUSTOM" ||
|
||||
!methods.includes(newMethod.value)
|
||||
)
|
||||
})
|
||||
|
||||
const requestName = useRESTRequestName()
|
||||
|
||||
const COLUMN_LAYOUT = useSetting("COLUMN_LAYOUT")
|
||||
</script>
|
||||
|
||||
@@ -9,51 +9,52 @@
|
||||
:label="`${t('tab.parameters')}`"
|
||||
:info="`${newActiveParamsCount$}`"
|
||||
>
|
||||
<HttpParameters />
|
||||
<HttpParameters v-model="request.params" />
|
||||
</HoppSmartTab>
|
||||
<HoppSmartTab :id="'bodyParams'" :label="`${t('tab.body')}`">
|
||||
<HttpBody @change-tab="changeTab" />
|
||||
<HttpBody
|
||||
v-model:headers="request.headers"
|
||||
v-model:body="request.body"
|
||||
@change-tab="changeTab"
|
||||
/>
|
||||
</HoppSmartTab>
|
||||
<HoppSmartTab
|
||||
:id="'headers'"
|
||||
:label="`${t('tab.headers')}`"
|
||||
:info="`${newActiveHeadersCount$}`"
|
||||
>
|
||||
<HttpHeaders @change-tab="changeTab" />
|
||||
<HttpHeaders v-model="request" @change-tab="changeTab" />
|
||||
</HoppSmartTab>
|
||||
<HoppSmartTab :id="'authorization'" :label="`${t('tab.authorization')}`">
|
||||
<HttpAuthorization />
|
||||
<HttpAuthorization v-model="request.auth" />
|
||||
</HoppSmartTab>
|
||||
<HoppSmartTab
|
||||
:id="'preRequestScript'"
|
||||
:label="`${t('tab.pre_request_script')}`"
|
||||
:indicator="
|
||||
preRequestScript && preRequestScript.length > 0 ? true : false
|
||||
request.preRequestScript && request.preRequestScript.length > 0
|
||||
? true
|
||||
: false
|
||||
"
|
||||
>
|
||||
<HttpPreRequestScript />
|
||||
<HttpPreRequestScript v-model="request.preRequestScript" />
|
||||
</HoppSmartTab>
|
||||
<HoppSmartTab
|
||||
:id="'tests'"
|
||||
:label="`${t('tab.tests')}`"
|
||||
:indicator="testScript && testScript.length > 0 ? true : false"
|
||||
:indicator="
|
||||
request.testScript && request.testScript.length > 0 ? true : false
|
||||
"
|
||||
>
|
||||
<HttpTests />
|
||||
<HttpTests v-model="request.testScript" />
|
||||
</HoppSmartTab>
|
||||
</HoppSmartTabs>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref } from "vue"
|
||||
import { map } from "rxjs/operators"
|
||||
import { useReadonlyStream } from "@composables/stream"
|
||||
import {
|
||||
restActiveHeadersCount$,
|
||||
restActiveParamsCount$,
|
||||
usePreRequestScript,
|
||||
useTestScript,
|
||||
} from "~/newstore/RESTSession"
|
||||
import { useI18n } from "@composables/i18n"
|
||||
import { HoppRESTRequest } from "@hoppscotch/data"
|
||||
import { computed, ref, watch } from "vue"
|
||||
|
||||
export type RequestOptionTabs =
|
||||
| "params"
|
||||
@@ -63,33 +64,43 @@ export type RequestOptionTabs =
|
||||
|
||||
const t = useI18n()
|
||||
|
||||
// v-model integration with props and emit
|
||||
const props = defineProps<{ modelValue: HoppRESTRequest }>()
|
||||
const emit = defineEmits<{
|
||||
(e: "update:modelValue", value: HoppRESTRequest): void
|
||||
}>()
|
||||
|
||||
const request = ref(props.modelValue)
|
||||
|
||||
watch(
|
||||
() => request.value,
|
||||
(newVal) => {
|
||||
emit("update:modelValue", newVal)
|
||||
},
|
||||
{ deep: true }
|
||||
)
|
||||
|
||||
const selectedRealtimeTab = ref<RequestOptionTabs>("params")
|
||||
|
||||
const changeTab = (e: RequestOptionTabs) => {
|
||||
selectedRealtimeTab.value = e
|
||||
}
|
||||
|
||||
const newActiveParamsCount$ = useReadonlyStream(
|
||||
restActiveParamsCount$.pipe(
|
||||
map((e) => {
|
||||
if (e === 0) return null
|
||||
return `${e}`
|
||||
})
|
||||
),
|
||||
null
|
||||
)
|
||||
const newActiveParamsCount$ = computed(() => {
|
||||
const e = request.value.params.filter(
|
||||
(x) => x.active && (x.key !== "" || x.value !== "")
|
||||
).length
|
||||
|
||||
const newActiveHeadersCount$ = useReadonlyStream(
|
||||
restActiveHeadersCount$.pipe(
|
||||
map((e) => {
|
||||
if (e === 0) return null
|
||||
return `${e}`
|
||||
})
|
||||
),
|
||||
null
|
||||
)
|
||||
if (e === 0) return null
|
||||
return `${e}`
|
||||
})
|
||||
|
||||
const preRequestScript = usePreRequestScript()
|
||||
const newActiveHeadersCount$ = computed(() => {
|
||||
const e = request.value.headers.filter(
|
||||
(x) => x.active && (x.key !== "" || x.value !== "")
|
||||
).length
|
||||
|
||||
const testScript = useTestScript()
|
||||
if (e === 0) return null
|
||||
return `${e}`
|
||||
})
|
||||
</script>
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
<template>
|
||||
<AppPaneLayout layout-id="rest-primary">
|
||||
<template #primary>
|
||||
<HttpRequest v-model="tab" />
|
||||
<HttpRequestOptions v-model="tab.document.request" />
|
||||
</template>
|
||||
<template #secondary>
|
||||
<HttpResponse v-model:tab="tab" />
|
||||
</template>
|
||||
</AppPaneLayout>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { watch } from "vue"
|
||||
import { useVModel } from "@vueuse/core"
|
||||
import { HoppRESTTab } from "~/helpers/rest/tab"
|
||||
import { cloneDeep } from "lodash-es"
|
||||
import { isEqualHoppRESTRequest } from "@hoppscotch/data"
|
||||
|
||||
// TODO: Move Response and Request execution code to over here
|
||||
|
||||
const props = defineProps<{ modelValue: HoppRESTTab }>()
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: "update:modelValue", val: HoppRESTTab): void
|
||||
}>()
|
||||
|
||||
const tab = useVModel(props, "modelValue", emit)
|
||||
|
||||
// TODO: Come up with a better dirty check
|
||||
let oldRequest = cloneDeep(tab.value.document.request)
|
||||
watch(
|
||||
() => tab.value.document.request,
|
||||
(updatedValue) => {
|
||||
if (
|
||||
!tab.value.document.isDirty &&
|
||||
!isEqualHoppRESTRequest(oldRequest, updatedValue)
|
||||
) {
|
||||
tab.value.document.isDirty = true
|
||||
}
|
||||
|
||||
oldRequest = cloneDeep(updatedValue)
|
||||
},
|
||||
{ deep: true }
|
||||
)
|
||||
</script>
|
||||
@@ -1,34 +1,42 @@
|
||||
<template>
|
||||
<div class="flex flex-col flex-1">
|
||||
<HttpResponseMeta :response="response" />
|
||||
<HttpResponseMeta :response="tab.response" />
|
||||
<LensesResponseBodyRenderer
|
||||
v-if="!loading && hasResponse"
|
||||
v-model:selected-tab-preference="selectedTabPreference"
|
||||
:response="response"
|
||||
v-model:tab="tab"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, computed, watch } from "vue"
|
||||
import { computed, ref, watch } from "vue"
|
||||
import { startPageProgress, completePageProgress } from "@modules/loadingbar"
|
||||
import { useReadonlyStream } from "@composables/stream"
|
||||
import { restResponse$ } from "~/newstore/RESTSession"
|
||||
import { HoppRESTTab } from "~/helpers/rest/tab"
|
||||
import { useVModel } from "@vueuse/core"
|
||||
|
||||
const props = defineProps<{
|
||||
tab: HoppRESTTab
|
||||
}>()
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: "update:tab", val: HoppRESTTab): void
|
||||
}>()
|
||||
|
||||
const tab = useVModel(props, "tab", emit)
|
||||
|
||||
const selectedTabPreference = ref<string | null>(null)
|
||||
|
||||
const response = useReadonlyStream(restResponse$, null)
|
||||
|
||||
const hasResponse = computed(
|
||||
() => response.value?.type === "success" || response.value?.type === "fail"
|
||||
() =>
|
||||
tab.value.response?.type === "success" ||
|
||||
tab.value.response?.type === "fail"
|
||||
)
|
||||
|
||||
const loading = computed(
|
||||
() => response.value === null || response.value.type === "loading"
|
||||
)
|
||||
const loading = computed(() => tab.value.response?.type === "loading")
|
||||
|
||||
watch(response, () => {
|
||||
if (response.value?.type === "loading") startPageProgress()
|
||||
watch(loading, (isLoading) => {
|
||||
if (isLoading) startPageProgress()
|
||||
else completePageProgress()
|
||||
})
|
||||
</script>
|
||||
|
||||
@@ -107,7 +107,7 @@ const t = useI18n()
|
||||
const colorMode = useColorMode()
|
||||
|
||||
const props = defineProps<{
|
||||
response: HoppRESTResponse | null
|
||||
response: HoppRESTResponse | null | undefined
|
||||
}>()
|
||||
|
||||
/**
|
||||
@@ -119,6 +119,7 @@ const props = defineProps<{
|
||||
const readableResponseSize = computed(() => {
|
||||
if (
|
||||
props.response === null ||
|
||||
props.response === undefined ||
|
||||
props.response.type === "loading" ||
|
||||
props.response.type === "network_fail" ||
|
||||
props.response.type === "script_fail" ||
|
||||
@@ -137,6 +138,7 @@ const readableResponseSize = computed(() => {
|
||||
const statusCategory = computed(() => {
|
||||
if (
|
||||
props.response === null ||
|
||||
props.response === undefined ||
|
||||
props.response.type === "loading" ||
|
||||
props.response.type === "network_fail" ||
|
||||
props.response.type === "script_fail" ||
|
||||
|
||||
@@ -5,13 +5,6 @@
|
||||
vertical
|
||||
render-inactive-tabs
|
||||
>
|
||||
<HoppSmartTab
|
||||
:id="'history'"
|
||||
:icon="IconClock"
|
||||
:label="`${t('tab.history')}`"
|
||||
>
|
||||
<History :page="'rest'" />
|
||||
</HoppSmartTab>
|
||||
<HoppSmartTab
|
||||
:id="'collections'"
|
||||
:icon="IconFolder"
|
||||
@@ -26,6 +19,13 @@
|
||||
>
|
||||
<Environments />
|
||||
</HoppSmartTab>
|
||||
<HoppSmartTab
|
||||
:id="'history'"
|
||||
:icon="IconClock"
|
||||
:label="`${t('tab.history')}`"
|
||||
>
|
||||
<History :page="'rest'" />
|
||||
</HoppSmartTab>
|
||||
</HoppSmartTabs>
|
||||
</template>
|
||||
|
||||
@@ -40,5 +40,5 @@ const t = useI18n()
|
||||
|
||||
type RequestOptionTabs = "history" | "collections" | "env"
|
||||
|
||||
const selectedNavigationTab = ref<RequestOptionTabs>("history")
|
||||
const selectedNavigationTab = ref<RequestOptionTabs>("collections")
|
||||
</script>
|
||||
|
||||
@@ -216,7 +216,6 @@ import {
|
||||
setGlobalEnvVariables,
|
||||
setSelectedEnvironmentIndex,
|
||||
} from "~/newstore/environments"
|
||||
import { restTestResults$, setRESTTestResults } from "~/newstore/RESTSession"
|
||||
import { HoppTestResult } from "~/helpers/types/HoppTestResult"
|
||||
|
||||
import IconTrash2 from "~icons/lucide/trash-2"
|
||||
@@ -226,6 +225,17 @@ import IconCheck from "~icons/lucide/check"
|
||||
import IconClose from "~icons/lucide/x"
|
||||
|
||||
import { useColorMode } from "~/composables/theming"
|
||||
import { useVModel } from "@vueuse/core"
|
||||
|
||||
const props = defineProps<{
|
||||
modelValue: HoppTestResult | null | undefined
|
||||
}>()
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: "update:modelValue", val: HoppTestResult | null | undefined): void
|
||||
}>()
|
||||
|
||||
const testResults = useVModel(props, "modelValue", emit)
|
||||
|
||||
const t = useI18n()
|
||||
const colorMode = useColorMode()
|
||||
@@ -236,11 +246,6 @@ const displayModalAdd = (shouldDisplay: boolean) => {
|
||||
showModalDetails.value = shouldDisplay
|
||||
}
|
||||
|
||||
const testResults = useReadonlyStream(
|
||||
restTestResults$,
|
||||
null
|
||||
) as Ref<HoppTestResult | null>
|
||||
|
||||
/**
|
||||
* Get the "addition" environment variables
|
||||
* @returns Array of objects with key-value pairs of arguments
|
||||
@@ -250,7 +255,9 @@ const getAdditionVars = () =>
|
||||
? testResults.value.envDiff.selected.additions
|
||||
: []
|
||||
|
||||
const clearContent = () => setRESTTestResults(null)
|
||||
const clearContent = () => {
|
||||
testResults.value = null
|
||||
}
|
||||
|
||||
const haveEnvVariables = computed(() => {
|
||||
if (!testResults.value) return false
|
||||
|
||||
@@ -66,17 +66,20 @@ import IconHelpCircle from "~icons/lucide/help-circle"
|
||||
import IconWrapText from "~icons/lucide/wrap-text"
|
||||
import IconTrash2 from "~icons/lucide/trash-2"
|
||||
import { reactive, ref } from "vue"
|
||||
import { useTestScript } from "~/newstore/RESTSession"
|
||||
import testSnippets from "~/helpers/testSnippets"
|
||||
import { useCodemirror } from "@composables/codemirror"
|
||||
import linter from "~/helpers/editor/linting/testScript"
|
||||
import completer from "~/helpers/editor/completion/testScript"
|
||||
import { useI18n } from "@composables/i18n"
|
||||
import { useVModel } from "@vueuse/core"
|
||||
|
||||
const t = useI18n()
|
||||
|
||||
const testScript = useTestScript()
|
||||
|
||||
const props = defineProps<{
|
||||
modelValue: string
|
||||
}>()
|
||||
const emit = defineEmits(["update:modelValue"])
|
||||
const testScript = useVModel(props, "modelValue", emit)
|
||||
const testScriptEditor = ref<any | null>(null)
|
||||
const linewrapEnabled = ref(true)
|
||||
|
||||
|
||||
@@ -181,6 +181,7 @@ import IconWrapText from "~icons/lucide/wrap-text"
|
||||
import { computed, reactive, ref, watch } from "vue"
|
||||
import { isEqual, cloneDeep } from "lodash-es"
|
||||
import {
|
||||
HoppRESTReqBody,
|
||||
parseRawKeyValueEntries,
|
||||
parseRawKeyValueEntriesE,
|
||||
rawKeyValueEntriesToString,
|
||||
@@ -194,13 +195,27 @@ import * as E from "fp-ts/Either"
|
||||
import draggable from "vuedraggable-es"
|
||||
import { useCodemirror } from "@composables/codemirror"
|
||||
import linter from "~/helpers/editor/linting/rawKeyValue"
|
||||
import { useRESTRequestBody } from "~/newstore/RESTSession"
|
||||
import { pluckRef } from "@composables/ref"
|
||||
import { useI18n } from "@composables/i18n"
|
||||
import { useToast } from "@composables/toast"
|
||||
import { useColorMode } from "@composables/theming"
|
||||
import { objRemoveKey } from "~/helpers/functional/object"
|
||||
import { throwError } from "~/helpers/functional/error"
|
||||
import { useVModel } from "@vueuse/core"
|
||||
|
||||
type Body = HoppRESTReqBody & {
|
||||
contentType: "application/x-www-form-urlencoded"
|
||||
}
|
||||
|
||||
const props = defineProps<{
|
||||
modelValue: Body
|
||||
}>()
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: "update:modelValue", val: Body): void
|
||||
}>()
|
||||
|
||||
const body = useVModel(props, "modelValue", emit)
|
||||
|
||||
const t = useI18n()
|
||||
const toast = useToast()
|
||||
@@ -231,7 +246,7 @@ useCodemirror(
|
||||
)
|
||||
|
||||
// The functional urlEncodedParams list (the urlEncodedParams actually in the system)
|
||||
const urlEncodedParamsRaw = pluckRef(useRESTRequestBody(), "body")
|
||||
const urlEncodedParamsRaw = pluckRef(body, "body")
|
||||
|
||||
const urlEncodedParams = computed<RawKeyValueEntry[]>({
|
||||
get() {
|
||||
|
||||
@@ -0,0 +1,82 @@
|
||||
<template>
|
||||
<div class="flex flex-1 border-b border-dividerLight">
|
||||
<SmartEnvInput v-model="auth.key" placeholder="Key" />
|
||||
</div>
|
||||
<div class="flex flex-1 border-b border-dividerLight">
|
||||
<SmartEnvInput v-model="auth.value" placeholder="Value" />
|
||||
</div>
|
||||
<div class="flex items-center border-b border-dividerLight">
|
||||
<span class="flex items-center">
|
||||
<label class="ml-4 text-secondaryLight">
|
||||
{{ t("authorization.pass_key_by") }}
|
||||
</label>
|
||||
<tippy
|
||||
interactive
|
||||
trigger="click"
|
||||
theme="popover"
|
||||
:on-shown="() => authTippyActions.focus()"
|
||||
>
|
||||
<span class="select-wrapper">
|
||||
<HoppButtonSecondary
|
||||
:label="auth.addTo || t('state.none')"
|
||||
class="pr-8 ml-2 rounded-none"
|
||||
/>
|
||||
</span>
|
||||
<template #content="{ hide }">
|
||||
<div
|
||||
ref="authTippyActions"
|
||||
class="flex flex-col focus:outline-none"
|
||||
tabindex="0"
|
||||
@keyup.escape="hide()"
|
||||
>
|
||||
<HoppSmartItem
|
||||
:icon="auth.addTo === 'Headers' ? IconCircleDot : IconCircle"
|
||||
:active="auth.addTo === 'Headers'"
|
||||
:label="'Headers'"
|
||||
@click="
|
||||
() => {
|
||||
auth.addTo = 'Headers'
|
||||
hide()
|
||||
}
|
||||
"
|
||||
/>
|
||||
<HoppSmartItem
|
||||
:icon="auth.addTo === 'Query params' ? IconCircleDot : IconCircle"
|
||||
:active="auth.addTo === 'Query params'"
|
||||
:label="'Query params'"
|
||||
@click="
|
||||
() => {
|
||||
auth.addTo = 'Query params'
|
||||
hide()
|
||||
}
|
||||
"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
</tippy>
|
||||
</span>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import IconCircle from "~icons/lucide/circle"
|
||||
import IconCircleDot from "~icons/lucide/circle-dot"
|
||||
import { useI18n } from "@composables/i18n"
|
||||
import { HoppRESTAuthAPIKey } from "@hoppscotch/data"
|
||||
import { useVModel } from "@vueuse/core"
|
||||
import { ref } from "vue"
|
||||
|
||||
const t = useI18n()
|
||||
|
||||
const props = defineProps<{
|
||||
modelValue: HoppRESTAuthAPIKey
|
||||
}>()
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: "update:modelValue", value: HoppRESTAuthAPIKey): void
|
||||
}>()
|
||||
|
||||
const auth = useVModel(props, "modelValue", emit)
|
||||
|
||||
const authTippyActions = ref<any | null>(null)
|
||||
</script>
|
||||
@@ -0,0 +1,32 @@
|
||||
<template>
|
||||
<div class="flex flex-1 border-b border-dividerLight">
|
||||
<SmartEnvInput
|
||||
v-model="auth.username"
|
||||
:placeholder="t('authorization.username')"
|
||||
/>
|
||||
</div>
|
||||
<div class="flex flex-1 border-b border-dividerLight">
|
||||
<SmartEnvInput
|
||||
v-model="auth.password"
|
||||
:placeholder="t('authorization.password')"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { useI18n } from "@composables/i18n"
|
||||
import { HoppRESTAuthBasic } from "@hoppscotch/data"
|
||||
import { useVModel } from "@vueuse/core"
|
||||
|
||||
const t = useI18n()
|
||||
|
||||
const props = defineProps<{
|
||||
modelValue: HoppRESTAuthBasic
|
||||
}>()
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: "update:modelValue", value: HoppRESTAuthBasic): void
|
||||
}>()
|
||||
|
||||
const auth = useVModel(props, "modelValue", emit)
|
||||
</script>
|
||||
@@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<HoppSmartTabs
|
||||
v-if="response"
|
||||
v-if="tab.response"
|
||||
v-model="selectedLensTab"
|
||||
styles="sticky overflow-x-auto flex-shrink-0 z-10 bg-primary top-lowerPrimaryStickyFold"
|
||||
>
|
||||
@@ -11,7 +11,10 @@
|
||||
:label="t(lens.lensName)"
|
||||
class="flex flex-col flex-1 w-full h-full"
|
||||
>
|
||||
<component :is="lensRendererFor(lens.renderer)" :response="response" />
|
||||
<component
|
||||
:is="lensRendererFor(lens.renderer)"
|
||||
:response="tab.response"
|
||||
/>
|
||||
</HoppSmartTab>
|
||||
<HoppSmartTab
|
||||
v-if="maybeHeaders"
|
||||
@@ -26,18 +29,18 @@
|
||||
id="results"
|
||||
:label="t('test.results')"
|
||||
:indicator="
|
||||
testResults &&
|
||||
(testResults.expectResults.length ||
|
||||
testResults.tests.length ||
|
||||
testResults.envDiff.selected.additions.length ||
|
||||
testResults.envDiff.selected.updations.length ||
|
||||
testResults.envDiff.global.updations.length)
|
||||
tab.testResults &&
|
||||
(tab.testResults.expectResults.length ||
|
||||
tab.testResults.tests.length ||
|
||||
tab.testResults.envDiff.selected.additions.length ||
|
||||
tab.testResults.envDiff.selected.updations.length ||
|
||||
tab.testResults.envDiff.global.updations.length)
|
||||
? true
|
||||
: false
|
||||
"
|
||||
class="flex flex-col flex-1"
|
||||
>
|
||||
<HttpTestResult />
|
||||
<HttpTestResult v-model="tab.testResults" />
|
||||
</HoppSmartTab>
|
||||
</HoppSmartTabs>
|
||||
</template>
|
||||
@@ -49,44 +52,48 @@ import {
|
||||
getLensRenderers,
|
||||
Lens,
|
||||
} from "~/helpers/lenses/lenses"
|
||||
import { useReadonlyStream } from "@composables/stream"
|
||||
import { useI18n } from "@composables/i18n"
|
||||
import type { HoppRESTResponse } from "~/helpers/types/HoppRESTResponse"
|
||||
import { restTestResults$ } from "~/newstore/RESTSession"
|
||||
import { useVModel } from "@vueuse/core"
|
||||
import { HoppRESTTab } from "~/helpers/rest/tab"
|
||||
|
||||
const props = defineProps<{
|
||||
response: HoppRESTResponse | null
|
||||
tab: HoppRESTTab
|
||||
selectedTabPreference: string | null
|
||||
}>()
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: "update:tab", val: HoppRESTTab): void
|
||||
(e: "update:selectedTabPreference", newTab: string): void
|
||||
}>()
|
||||
|
||||
const tab = useVModel(props, "tab", emit)
|
||||
const selectedTabPreference = useVModel(props, "selectedTabPreference", emit)
|
||||
|
||||
const allLensRenderers = getLensRenderers()
|
||||
|
||||
function lensRendererFor(name: string) {
|
||||
return allLensRenderers[name]
|
||||
}
|
||||
|
||||
const testResults = useReadonlyStream(restTestResults$, null)
|
||||
|
||||
const t = useI18n()
|
||||
|
||||
const selectedLensTab = ref("")
|
||||
|
||||
const maybeHeaders = computed(() => {
|
||||
if (
|
||||
!props.response ||
|
||||
!(props.response.type === "success" || props.response.type === "fail")
|
||||
!tab.value.response ||
|
||||
!(
|
||||
tab.value.response.type === "success" ||
|
||||
tab.value.response.type === "fail"
|
||||
)
|
||||
)
|
||||
return null
|
||||
return props.response.headers
|
||||
return tab.value.response.headers
|
||||
})
|
||||
|
||||
const validLenses = computed(() => {
|
||||
if (!props.response) return []
|
||||
return getSuitableLenses(props.response)
|
||||
if (!tab.value.response) return []
|
||||
return getSuitableLenses(tab.value.response)
|
||||
})
|
||||
|
||||
watch(
|
||||
@@ -101,10 +108,10 @@ watch(
|
||||
]
|
||||
|
||||
if (
|
||||
props.selectedTabPreference &&
|
||||
validRenderers.includes(props.selectedTabPreference)
|
||||
selectedTabPreference.value &&
|
||||
validRenderers.includes(selectedTabPreference.value)
|
||||
) {
|
||||
selectedLensTab.value = props.selectedTabPreference
|
||||
selectedLensTab.value = selectedTabPreference.value
|
||||
} else {
|
||||
selectedLensTab.value = newLenses[0].renderer
|
||||
}
|
||||
@@ -113,6 +120,6 @@ watch(
|
||||
)
|
||||
|
||||
watch(selectedLensTab, (newLensID) => {
|
||||
emit("update:selectedTabPreference", newLensID)
|
||||
selectedTabPreference.value = newLensID
|
||||
})
|
||||
</script>
|
||||
|
||||
@@ -19,6 +19,12 @@
|
||||
v-if="!loading && myTeams.length === 0"
|
||||
class="flex flex-col items-center justify-center flex-1 p-4 text-secondaryLight"
|
||||
>
|
||||
<img
|
||||
:src="`/images/states/${colorMode.value}/add_group.svg`"
|
||||
loading="lazy"
|
||||
class="inline-flex flex-col object-contain object-center w-16 h-16 mb-8"
|
||||
:alt="`${t('empty.teams')}`"
|
||||
/>
|
||||
<span class="mb-4 text-center">
|
||||
{{ t("empty.teams") }}
|
||||
</span>
|
||||
@@ -78,12 +84,14 @@ import { useI18n } from "@composables/i18n"
|
||||
import IconUser from "~icons/lucide/user"
|
||||
import IconUsers from "~icons/lucide/users"
|
||||
import IconPlus from "~icons/lucide/plus"
|
||||
import { useColorMode } from "@composables/theming"
|
||||
import { changeWorkspace, workspaceStatus$ } from "~/newstore/workspace"
|
||||
import { GetMyTeamsQuery } from "~/helpers/backend/graphql"
|
||||
import IconDone from "~icons/lucide/check"
|
||||
import { useLocalState } from "~/newstore/localstate"
|
||||
|
||||
const t = useI18n()
|
||||
const colorMode = useColorMode()
|
||||
|
||||
const showModalAdd = ref(false)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user