feat: added embeds components wip
This commit is contained in:
@@ -96,6 +96,7 @@
|
|||||||
"keyboard_shortcuts": "Keyboard shortcuts",
|
"keyboard_shortcuts": "Keyboard shortcuts",
|
||||||
"name": "Hoppscotch",
|
"name": "Hoppscotch",
|
||||||
"new_version_found": "New version found. Refresh to update.",
|
"new_version_found": "New version found. Refresh to update.",
|
||||||
|
"open_in_hoppscotch": "Open in Hoppscotch",
|
||||||
"options": "Options",
|
"options": "Options",
|
||||||
"proxy_privacy_policy": "Proxy privacy policy",
|
"proxy_privacy_policy": "Proxy privacy policy",
|
||||||
"reload": "Reload",
|
"reload": "Reload",
|
||||||
|
|||||||
@@ -61,6 +61,7 @@ declare module 'vue' {
|
|||||||
CollectionsTeamCollections: typeof import('./components/collections/TeamCollections.vue')['default']
|
CollectionsTeamCollections: typeof import('./components/collections/TeamCollections.vue')['default']
|
||||||
CookiesAllModal: typeof import('./components/cookies/AllModal.vue')['default']
|
CookiesAllModal: typeof import('./components/cookies/AllModal.vue')['default']
|
||||||
CookiesEditCookie: typeof import('./components/cookies/EditCookie.vue')['default']
|
CookiesEditCookie: typeof import('./components/cookies/EditCookie.vue')['default']
|
||||||
|
Embeds: typeof import('./components/embeds/index.vue')['default']
|
||||||
Environments: typeof import('./components/environments/index.vue')['default']
|
Environments: typeof import('./components/environments/index.vue')['default']
|
||||||
EnvironmentsAdd: typeof import('./components/environments/Add.vue')['default']
|
EnvironmentsAdd: typeof import('./components/environments/Add.vue')['default']
|
||||||
EnvironmentsImportExport: typeof import('./components/environments/ImportExport.vue')['default']
|
EnvironmentsImportExport: typeof import('./components/environments/ImportExport.vue')['default']
|
||||||
|
|||||||
259
packages/hoppscotch-common/src/components/embeds/index.vue
Normal file
259
packages/hoppscotch-common/src/components/embeds/index.vue
Normal file
@@ -0,0 +1,259 @@
|
|||||||
|
<template>
|
||||||
|
<div class="flex flex-1 flex-col">
|
||||||
|
<header
|
||||||
|
class="flex flex-1 flex-shrink-0 items-center justify-between space-x-2 overflow-x-auto overflow-y-hidden px-2 py-2"
|
||||||
|
>
|
||||||
|
<div class="flex flex-1 items-center justify-between space-x-2">
|
||||||
|
<HoppButtonSecondary
|
||||||
|
class="!font-bold uppercase tracking-wide !text-secondaryDark hover:bg-primaryDark focus-visible:bg-primaryDark"
|
||||||
|
:label="t('app.name')"
|
||||||
|
to="/"
|
||||||
|
/>
|
||||||
|
<div class="flex">
|
||||||
|
<HoppSmartItem :label="t('app.open_in_hoppscotch')" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<div class="flex-1">
|
||||||
|
<div
|
||||||
|
class="top-0 z-20 flex-none flex-shrink-0 bg-primary p-4 sm:flex sm:flex-shrink-0 sm:space-x-2"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="min-w-52 flex flex-1 whitespace-nowrap rounded border border-divider"
|
||||||
|
>
|
||||||
|
<div class="relative flex">
|
||||||
|
<label for="method">
|
||||||
|
<tippy
|
||||||
|
interactive
|
||||||
|
trigger="click"
|
||||||
|
theme="popover"
|
||||||
|
:on-shown="() => methodTippyActions?.focus()"
|
||||||
|
>
|
||||||
|
<span class="select-wrapper">
|
||||||
|
<input
|
||||||
|
id="method"
|
||||||
|
class="flex w-26 cursor-pointer rounded-l bg-primaryLight px-4 py-2 font-semibold text-secondaryDark transition"
|
||||||
|
:value="tab.document.request.method"
|
||||||
|
:readonly="!isCustomMethod"
|
||||||
|
:placeholder="`${t('request.method')}`"
|
||||||
|
@input="onSelectMethod($event)"
|
||||||
|
/>
|
||||||
|
</span>
|
||||||
|
<template #content="{ hide }">
|
||||||
|
<div
|
||||||
|
ref="methodTippyActions"
|
||||||
|
class="flex flex-col focus:outline-none"
|
||||||
|
tabindex="0"
|
||||||
|
@keyup.escape="hide()"
|
||||||
|
>
|
||||||
|
<HoppSmartItem
|
||||||
|
v-for="(method, index) in methods"
|
||||||
|
:key="`method-${index}`"
|
||||||
|
:label="method"
|
||||||
|
@click="
|
||||||
|
() => {
|
||||||
|
updateMethod(method)
|
||||||
|
hide()
|
||||||
|
}
|
||||||
|
"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</tippy>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="flex flex-1 whitespace-nowrap rounded-r border-l border-divider bg-primaryLight transition"
|
||||||
|
>
|
||||||
|
<SmartEnvInput
|
||||||
|
v-model="tab.document.request.endpoint"
|
||||||
|
:placeholder="`${t('request.url')}`"
|
||||||
|
@enter="newSendRequest"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="mt-2 flex sm:mt-0">
|
||||||
|
<HoppButtonPrimary
|
||||||
|
id="send"
|
||||||
|
v-tippy="{ theme: 'tooltip', delay: [500, 20], allowHTML: true }"
|
||||||
|
:title="`${t(
|
||||||
|
'action.send'
|
||||||
|
)} <kbd>${getSpecialKey()}</kbd><kbd>↩</kbd>`"
|
||||||
|
:label="`${!loading ? t('action.send') : t('action.cancel')}`"
|
||||||
|
class="min-w-20 flex-1"
|
||||||
|
@click="!loading ? newSendRequest() : cancelRequest()"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<HttpRequestOptions
|
||||||
|
v-model="tab.document.request"
|
||||||
|
v-model:option-tab="selectedOptionTab"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
{{ tab.document.response }}
|
||||||
|
<HttpResponse :document="tab.document" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { Ref } from "vue"
|
||||||
|
import { computed, useModel } from "vue"
|
||||||
|
import { ref } from "vue"
|
||||||
|
import { TippyComponent } from "vue-tippy"
|
||||||
|
import { useI18n } from "~/composables/i18n"
|
||||||
|
import { useToast } from "~/composables/toast"
|
||||||
|
import { getPlatformSpecialKey as getSpecialKey } from "~/helpers/platformutils"
|
||||||
|
import * as E from "fp-ts/Either"
|
||||||
|
import { useStreamSubscriber } from "~/composables/stream"
|
||||||
|
import { HoppRESTResponse } from "~/helpers/types/HoppRESTResponse"
|
||||||
|
import { runRESTRequest$ } from "~/helpers/RequestRunner"
|
||||||
|
import { HoppTab } from "~/services/tab"
|
||||||
|
import { HoppRESTDocument } from "~/helpers/rest/document"
|
||||||
|
|
||||||
|
const t = useI18n()
|
||||||
|
const toast = useToast()
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
modelTab: HoppTab<HoppRESTDocument>
|
||||||
|
}>()
|
||||||
|
|
||||||
|
const tab = useModel(props, "modelTab")
|
||||||
|
|
||||||
|
const selectedOptionTab = ref("params")
|
||||||
|
|
||||||
|
console.log("request", tab.value.document.request)
|
||||||
|
|
||||||
|
const requestCancelFunc: Ref<(() => void) | null> = ref(null)
|
||||||
|
|
||||||
|
const loading = ref(false)
|
||||||
|
|
||||||
|
const methods = [
|
||||||
|
"GET",
|
||||||
|
"POST",
|
||||||
|
"PUT",
|
||||||
|
"PATCH",
|
||||||
|
"DELETE",
|
||||||
|
"HEAD",
|
||||||
|
"CONNECT",
|
||||||
|
"OPTIONS",
|
||||||
|
"TRACE",
|
||||||
|
"CUSTOM",
|
||||||
|
]
|
||||||
|
|
||||||
|
const { subscribeToStream } = useStreamSubscriber()
|
||||||
|
|
||||||
|
const updateMethod = (method: string) => {
|
||||||
|
tab.value.document.request.method = 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.target.value)
|
||||||
|
}
|
||||||
|
|
||||||
|
const newSendRequest = async () => {
|
||||||
|
if (newEndpoint.value === "" || /^\s+$/.test(newEndpoint.value)) {
|
||||||
|
toast.error(`${t("empty.endpoint")}`)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
ensureMethodInEndpoint()
|
||||||
|
|
||||||
|
loading.value = true
|
||||||
|
|
||||||
|
const [cancel, streamPromise] = runRESTRequest$(tab)
|
||||||
|
const streamResult = await streamPromise
|
||||||
|
|
||||||
|
requestCancelFunc.value = cancel
|
||||||
|
if (E.isRight(streamResult)) {
|
||||||
|
subscribeToStream(
|
||||||
|
streamResult.right,
|
||||||
|
(responseState) => {
|
||||||
|
if (loading.value) {
|
||||||
|
// Check exists because, loading can be set to false
|
||||||
|
// when cancelled
|
||||||
|
updateRESTResponse(responseState)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
() => {
|
||||||
|
loading.value = false
|
||||||
|
},
|
||||||
|
() => {
|
||||||
|
// TODO: Change this any to a proper type
|
||||||
|
const result = (streamResult.right as any).value
|
||||||
|
if (
|
||||||
|
result.type === "network_fail" &&
|
||||||
|
result.error?.error === "NO_PW_EXT_HOOK"
|
||||||
|
) {
|
||||||
|
const errorResponse: HoppRESTResponse = {
|
||||||
|
type: "extension_error",
|
||||||
|
error: result.error.humanMessage.heading,
|
||||||
|
component: result.error.component,
|
||||||
|
req: result.req,
|
||||||
|
}
|
||||||
|
updateRESTResponse(errorResponse)
|
||||||
|
}
|
||||||
|
loading.value = false
|
||||||
|
}
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
loading.value = false
|
||||||
|
toast.error(`${t("error.script_fail")}`)
|
||||||
|
let error: Error
|
||||||
|
if (typeof streamResult.left === "string") {
|
||||||
|
error = { name: "RequestFailure", message: streamResult.left }
|
||||||
|
} else {
|
||||||
|
error = streamResult.left
|
||||||
|
}
|
||||||
|
updateRESTResponse({
|
||||||
|
type: "script_fail",
|
||||||
|
error,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const updateRESTResponse = (response: HoppRESTResponse | null) => {
|
||||||
|
tab.value.document.response = response
|
||||||
|
}
|
||||||
|
|
||||||
|
const newEndpoint = computed(() => {
|
||||||
|
return tab.value.document.request.endpoint
|
||||||
|
})
|
||||||
|
|
||||||
|
const ensureMethodInEndpoint = () => {
|
||||||
|
if (
|
||||||
|
!/^http[s]?:\/\//.test(newEndpoint.value) &&
|
||||||
|
!newEndpoint.value.startsWith("<<")
|
||||||
|
) {
|
||||||
|
const domain = newEndpoint.value.split(/[/:#?]+/)[0]
|
||||||
|
if (domain === "localhost" || /([0-9]+\.)*[0-9]/.test(domain)) {
|
||||||
|
tab.value.document.request.endpoint =
|
||||||
|
"http://" + tab.value.document.request.endpoint
|
||||||
|
} else {
|
||||||
|
tab.value.document.request.endpoint =
|
||||||
|
"https://" + tab.value.document.request.endpoint
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const cancelRequest = () => {
|
||||||
|
loading.value = false
|
||||||
|
requestCancelFunc.value?.()
|
||||||
|
|
||||||
|
updateRESTResponse(null)
|
||||||
|
}
|
||||||
|
|
||||||
|
const isCustomMethod = computed(() => {
|
||||||
|
return tab.value.document.request.method === "CUSTOM"
|
||||||
|
})
|
||||||
|
|
||||||
|
// Template refs
|
||||||
|
const methodTippyActions = ref<TippyComponent | null>(null)
|
||||||
|
</script>
|
||||||
@@ -1,7 +1,101 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="flex flex-col items-center justify-between p-8">
|
<div class="flex flex-col flex-1 w-full">
|
||||||
Temporary page for Embed till the feature is ready
|
<Embeds v-if="tab" v-model:modelTab="tab" />
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts"></script>
|
<script setup lang="ts">
|
||||||
|
import { ref } from "vue"
|
||||||
|
import { watch } from "vue"
|
||||||
|
import { useRoute } from "vue-router"
|
||||||
|
import { useGQLQuery } from "~/composables/graphql"
|
||||||
|
import {
|
||||||
|
ResolveShortcodeDocument,
|
||||||
|
ResolveShortcodeQuery,
|
||||||
|
ResolveShortcodeQueryVariables,
|
||||||
|
} from "~/helpers/backend/graphql"
|
||||||
|
import * as E from "fp-ts/Either"
|
||||||
|
import { onMounted } from "vue"
|
||||||
|
import {
|
||||||
|
getDefaultRESTRequest,
|
||||||
|
safelyExtractRESTRequest,
|
||||||
|
} from "@hoppscotch/data"
|
||||||
|
import { HoppTab } from "~/services/tab"
|
||||||
|
import { HoppRESTDocument } from "~/helpers/rest/document"
|
||||||
|
import { applySetting } from "~/newstore/settings"
|
||||||
|
|
||||||
|
const route = useRoute()
|
||||||
|
|
||||||
|
const sharedRequestID = ref("")
|
||||||
|
const invalidLink = ref(false)
|
||||||
|
|
||||||
|
const sharedRequestDetails = useGQLQuery<
|
||||||
|
ResolveShortcodeQuery,
|
||||||
|
ResolveShortcodeQueryVariables,
|
||||||
|
""
|
||||||
|
>({
|
||||||
|
query: ResolveShortcodeDocument,
|
||||||
|
variables: {
|
||||||
|
code: route.params.id.toString(),
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
const tab = ref<HoppTab<HoppRESTDocument>>({
|
||||||
|
id: "0",
|
||||||
|
document: {
|
||||||
|
request: getDefaultRESTRequest(),
|
||||||
|
response: null,
|
||||||
|
isDirty: false,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => sharedRequestDetails.data,
|
||||||
|
() => {
|
||||||
|
if (sharedRequestDetails.loading) return
|
||||||
|
|
||||||
|
const data = sharedRequestDetails.data
|
||||||
|
|
||||||
|
if (E.isRight(data)) {
|
||||||
|
if (!data.right.shortcode?.request) {
|
||||||
|
invalidLink.value = true
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const request: unknown = JSON.parse(
|
||||||
|
data.right.shortcode?.request as string
|
||||||
|
)
|
||||||
|
|
||||||
|
tab.value.document.request = safelyExtractRESTRequest(
|
||||||
|
request,
|
||||||
|
getDefaultRESTRequest()
|
||||||
|
)
|
||||||
|
|
||||||
|
if (data.right.shortcode && data.right.shortcode.properties) {
|
||||||
|
const parsedProperties = JSON.parse(data.right.shortcode.properties)
|
||||||
|
if (parsedProperties.theme === "dark") {
|
||||||
|
applySetting("BG_COLOR", "dark")
|
||||||
|
} else if (parsedProperties.theme === "light") {
|
||||||
|
applySetting("BG_COLOR", "light")
|
||||||
|
} else if (parsedProperties.theme === "auto") {
|
||||||
|
applySetting("BG_COLOR", "system")
|
||||||
|
}
|
||||||
|
console.log("properties", JSON.parse(data.right.shortcode.properties))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
if (typeof route.params.id === "string") {
|
||||||
|
sharedRequestID.value = route.params.id
|
||||||
|
sharedRequestDetails.execute()
|
||||||
|
}
|
||||||
|
invalidLink.value = !sharedRequestID.value
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<route lang="yaml">
|
||||||
|
meta:
|
||||||
|
layout: empty
|
||||||
|
</route>
|
||||||
|
|||||||
Reference in New Issue
Block a user