Compare commits
11 Commits
main
...
feat/share
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
502da61b8b | ||
|
|
f2777a9a75 | ||
|
|
4bd3e89f89 | ||
|
|
09e9601940 | ||
|
|
fd4a5c626f | ||
|
|
67cfef82af | ||
|
|
aa18249791 | ||
|
|
9d8fdb4d04 | ||
|
|
fbca9b06c3 | ||
|
|
bb0bf35164 | ||
|
|
5a35c098ec |
@@ -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",
|
||||||
@@ -431,8 +432,9 @@
|
|||||||
"close_unsaved_tab": "You have unsaved changes",
|
"close_unsaved_tab": "You have unsaved changes",
|
||||||
"collections": "Collections",
|
"collections": "Collections",
|
||||||
"confirm": "Confirm",
|
"confirm": "Confirm",
|
||||||
|
"customize_request": "Customize Request",
|
||||||
"edit_request": "Edit Request",
|
"edit_request": "Edit Request",
|
||||||
"share_request":"Share Request",
|
"share_request": "Share Request",
|
||||||
"import_export": "Import / Export"
|
"import_export": "Import / Export"
|
||||||
},
|
},
|
||||||
"mqtt": {
|
"mqtt": {
|
||||||
@@ -621,29 +623,30 @@
|
|||||||
"additional": "Additional Settings",
|
"additional": "Additional Settings",
|
||||||
"verify_email": "Verify email"
|
"verify_email": "Verify email"
|
||||||
},
|
},
|
||||||
"shared_requests":{
|
"shared_requests": {
|
||||||
"button":"Button",
|
"button": "Button",
|
||||||
"button_info": "Create a 'Run in Hoppscotch' button for your website, blog or a README.",
|
"button_info": "Create a 'Run in Hoppscotch' button for your website, blog or a README.",
|
||||||
"customize": "Customize",
|
"customize": "Customize",
|
||||||
"creating_widget": "Creating widget",
|
"creating_widget": "Creating widget",
|
||||||
"copy_html": "Copy HTML",
|
"copy_html": "Copy HTML",
|
||||||
"copy_link": "Copy Link",
|
"copy_link": "Copy Link",
|
||||||
"copy_markdown": "Copy Markdown",
|
"copy_markdown": "Copy Markdown",
|
||||||
"deleted":"Shared request deleted",
|
"deleted": "Shared request deleted",
|
||||||
"description": "Select a widget, you can change and customize this later",
|
"description": "Select a widget, you can change and customize this later",
|
||||||
"embed":"Embed",
|
"embed": "Embed",
|
||||||
"embed_info": "Add a mini 'Hoppscotch API Playground' to your website, blog or documentation.",
|
"embed_info": "Add a mini 'Hoppscotch API Playground' to your website, blog or documentation.",
|
||||||
"link":"Link",
|
"link": "Link",
|
||||||
"link_info": "Create a shareable link to share with anyone on the internet with view access.",
|
"link_info": "Create a shareable link to share with anyone on the internet with view access.",
|
||||||
"not_found":"Shared request not found",
|
"modified": "Shared request modified",
|
||||||
|
"not_found": "Shared request not found",
|
||||||
"open_new_tab": "Open in new tab",
|
"open_new_tab": "Open in new tab",
|
||||||
"preview":"Preview",
|
"preview": "Preview",
|
||||||
"run_in_hoppscotch":"Run in Hoppscotch",
|
"run_in_hoppscotch": "Run in Hoppscotch",
|
||||||
"theme":{
|
"theme": {
|
||||||
"dark":"Dark",
|
"dark": "Dark",
|
||||||
"light":"Light",
|
"light": "Light",
|
||||||
"system" :"System",
|
"system": "System",
|
||||||
"title":"Theme"
|
"title": "Theme"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"shortcut": {
|
"shortcut": {
|
||||||
@@ -689,7 +692,7 @@
|
|||||||
"save_to_collections": "Save to Collections",
|
"save_to_collections": "Save to Collections",
|
||||||
"send_request": "Send Request",
|
"send_request": "Send Request",
|
||||||
"show_code": "Generate code snippet",
|
"show_code": "Generate code snippet",
|
||||||
"share_request":"Share Request",
|
"share_request": "Share Request",
|
||||||
"title": "Request"
|
"title": "Request"
|
||||||
},
|
},
|
||||||
"response": {
|
"response": {
|
||||||
|
|||||||
@@ -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']
|
||||||
|
|||||||
212
packages/hoppscotch-common/src/components/embeds/index.vue
Normal file
212
packages/hoppscotch-common/src/components/embeds/index.vue
Normal file
@@ -0,0 +1,212 @@
|
|||||||
|
<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="https://hoppscotch.io/"
|
||||||
|
blank
|
||||||
|
/>
|
||||||
|
<div class="flex">
|
||||||
|
<HoppSmartItem
|
||||||
|
:label="t('app.open_in_hoppscotch')"
|
||||||
|
:to="sharedRequestURL"
|
||||||
|
blank
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<div class="flex-1">
|
||||||
|
<div
|
||||||
|
class="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">
|
||||||
|
<span
|
||||||
|
class="flex justify-center items-center w-26 cursor-pointer rounded-l bg-primaryLight px-4 py-2 font-semibold text-secondaryDark transition"
|
||||||
|
>
|
||||||
|
{{ tab.document.request.method }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="flex flex-1 whitespace-nowrap rounded-r border-l border-divider bg-primaryLight transition"
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
name="method"
|
||||||
|
:value="tab.document.request.endpoint"
|
||||||
|
class="flex-1 px-4 bg-primary"
|
||||||
|
disabled
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="mt-2 flex sm:mt-0">
|
||||||
|
<HoppButtonPrimary
|
||||||
|
id="send"
|
||||||
|
: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()"
|
||||||
|
/>
|
||||||
|
<HoppButtonSecondary
|
||||||
|
:title="`${t(
|
||||||
|
'request.save'
|
||||||
|
)} <kbd>${getSpecialKey()}</kbd><kbd>S</kbd>`"
|
||||||
|
:label="t('request.save')"
|
||||||
|
filled
|
||||||
|
:icon="IconSave"
|
||||||
|
class="flex-1 rounded rounded-r-none"
|
||||||
|
blank
|
||||||
|
:to="sharedRequestURL"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<HttpRequestOptions
|
||||||
|
v-model="tab.document.request"
|
||||||
|
v-model:option-tab="selectedOptionTab"
|
||||||
|
:properties="properties"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<HttpResponse :document="tab.document" :is-embed="true" />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { Ref } from "vue"
|
||||||
|
import { computed, useModel } from "vue"
|
||||||
|
import { ref } from "vue"
|
||||||
|
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"
|
||||||
|
import IconSave from "~icons/lucide/save"
|
||||||
|
const t = useI18n()
|
||||||
|
const toast = useToast()
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
modelTab: HoppTab<HoppRESTDocument>
|
||||||
|
properties: string[]
|
||||||
|
sharedRequestID: string
|
||||||
|
}>()
|
||||||
|
|
||||||
|
const tab = useModel(props, "modelTab")
|
||||||
|
|
||||||
|
const selectedOptionTab = ref(props.properties[0])
|
||||||
|
|
||||||
|
const requestCancelFunc: Ref<(() => void) | null> = ref(null)
|
||||||
|
|
||||||
|
const loading = ref(false)
|
||||||
|
|
||||||
|
const baseURL = import.meta.env.VITE_SHORTCODE_BASE_URL ?? "https://hopp.sh"
|
||||||
|
const sharedRequestURL = computed(() => {
|
||||||
|
return `${baseURL}/r/${props.sharedRequestID}`
|
||||||
|
})
|
||||||
|
|
||||||
|
const { subscribeToStream } = useStreamSubscriber()
|
||||||
|
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
</script>
|
||||||
@@ -5,13 +5,18 @@
|
|||||||
render-inactive-tabs
|
render-inactive-tabs
|
||||||
>
|
>
|
||||||
<HoppSmartTab
|
<HoppSmartTab
|
||||||
|
v-if="properties ? properties.includes('parameters') : true"
|
||||||
:id="'params'"
|
:id="'params'"
|
||||||
:label="`${t('tab.parameters')}`"
|
:label="`${t('tab.parameters')}`"
|
||||||
:info="`${newActiveParamsCount$}`"
|
:info="`${newActiveParamsCount$}`"
|
||||||
>
|
>
|
||||||
<HttpParameters v-model="request.params" />
|
<HttpParameters v-model="request.params" />
|
||||||
</HoppSmartTab>
|
</HoppSmartTab>
|
||||||
<HoppSmartTab :id="'bodyParams'" :label="`${t('tab.body')}`">
|
<HoppSmartTab
|
||||||
|
v-if="properties ? properties.includes('body') : true"
|
||||||
|
:id="'bodyParams'"
|
||||||
|
:label="`${t('tab.body')}`"
|
||||||
|
>
|
||||||
<HttpBody
|
<HttpBody
|
||||||
v-model:headers="request.headers"
|
v-model:headers="request.headers"
|
||||||
v-model:body="request.body"
|
v-model:body="request.body"
|
||||||
@@ -19,16 +24,22 @@
|
|||||||
/>
|
/>
|
||||||
</HoppSmartTab>
|
</HoppSmartTab>
|
||||||
<HoppSmartTab
|
<HoppSmartTab
|
||||||
|
v-if="properties ? properties.includes('headers') : true"
|
||||||
:id="'headers'"
|
:id="'headers'"
|
||||||
:label="`${t('tab.headers')}`"
|
:label="`${t('tab.headers')}`"
|
||||||
:info="`${newActiveHeadersCount$}`"
|
:info="`${newActiveHeadersCount$}`"
|
||||||
>
|
>
|
||||||
<HttpHeaders v-model="request" @change-tab="changeOptionTab" />
|
<HttpHeaders v-model="request" @change-tab="changeOptionTab" />
|
||||||
</HoppSmartTab>
|
</HoppSmartTab>
|
||||||
<HoppSmartTab :id="'authorization'" :label="`${t('tab.authorization')}`">
|
<HoppSmartTab
|
||||||
|
v-if="properties ? properties.includes('authorization') : true"
|
||||||
|
:id="'authorization'"
|
||||||
|
:label="`${t('tab.authorization')}`"
|
||||||
|
>
|
||||||
<HttpAuthorization v-model="request.auth" />
|
<HttpAuthorization v-model="request.auth" />
|
||||||
</HoppSmartTab>
|
</HoppSmartTab>
|
||||||
<HoppSmartTab
|
<HoppSmartTab
|
||||||
|
v-if="properties ? properties.includes('preRequestScript') : true"
|
||||||
:id="'preRequestScript'"
|
:id="'preRequestScript'"
|
||||||
:label="`${t('tab.pre_request_script')}`"
|
:label="`${t('tab.pre_request_script')}`"
|
||||||
:indicator="
|
:indicator="
|
||||||
@@ -40,6 +51,7 @@
|
|||||||
<HttpPreRequestScript v-model="request.preRequestScript" />
|
<HttpPreRequestScript v-model="request.preRequestScript" />
|
||||||
</HoppSmartTab>
|
</HoppSmartTab>
|
||||||
<HoppSmartTab
|
<HoppSmartTab
|
||||||
|
v-if="properties ? properties.includes('tests') : true"
|
||||||
:id="'tests'"
|
:id="'tests'"
|
||||||
:label="`${t('tab.tests')}`"
|
:label="`${t('tab.tests')}`"
|
||||||
:indicator="
|
:indicator="
|
||||||
@@ -76,6 +88,7 @@ const props = withDefaults(
|
|||||||
defineProps<{
|
defineProps<{
|
||||||
modelValue: HoppRESTRequest
|
modelValue: HoppRESTRequest
|
||||||
optionTab: RESTOptionTabs
|
optionTab: RESTOptionTabs
|
||||||
|
properties?: string[]
|
||||||
}>(),
|
}>(),
|
||||||
{
|
{
|
||||||
optionTab: "params",
|
optionTab: "params",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="relative flex flex-1 flex-col">
|
<div class="relative flex flex-1 flex-col">
|
||||||
<HttpResponseMeta :response="doc.response" />
|
<HttpResponseMeta :response="doc.response" :is-embed="isEmbed" />
|
||||||
<LensesResponseBodyRenderer
|
<LensesResponseBodyRenderer
|
||||||
v-if="!loading && hasResponse"
|
v-if="!loading && hasResponse"
|
||||||
v-model:document="doc"
|
v-model:document="doc"
|
||||||
@@ -15,6 +15,7 @@ import { HoppRESTDocument } from "~/helpers/rest/document"
|
|||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
document: HoppRESTDocument
|
document: HoppRESTDocument
|
||||||
|
isEmbed: boolean
|
||||||
}>()
|
}>()
|
||||||
|
|
||||||
const emit = defineEmits<{
|
const emit = defineEmits<{
|
||||||
|
|||||||
@@ -2,8 +2,20 @@
|
|||||||
<div
|
<div
|
||||||
class="sticky top-0 z-10 flex flex-shrink-0 items-center justify-center overflow-auto overflow-x-auto whitespace-nowrap bg-primary p-4"
|
class="sticky top-0 z-10 flex flex-shrink-0 items-center justify-center overflow-auto overflow-x-auto whitespace-nowrap bg-primary p-4"
|
||||||
>
|
>
|
||||||
<AppShortcutsPrompt v-if="response == null" class="flex-1" />
|
<AppShortcutsPrompt v-if="response == null && !isEmbed" class="flex-1" />
|
||||||
<div v-else class="flex flex-1 flex-col">
|
|
||||||
|
<div v-if="response == null && isEmbed">
|
||||||
|
<HoppButtonSecondary
|
||||||
|
:label="`${t('app.documentation')}`"
|
||||||
|
to="https://docs.hoppscotch.io/documentation/features/rest-api-testing#response"
|
||||||
|
:icon="IconExternalLink"
|
||||||
|
blank
|
||||||
|
outline
|
||||||
|
reverse
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div v-else-if="response" class="flex flex-1 flex-col">
|
||||||
<div
|
<div
|
||||||
v-if="response.type === 'loading'"
|
v-if="response.type === 'loading'"
|
||||||
class="flex flex-col items-center justify-center"
|
class="flex flex-col items-center justify-center"
|
||||||
@@ -105,6 +117,7 @@ import { getStatusCodeReasonPhrase } from "~/helpers/utils/statusCodes"
|
|||||||
import { useService } from "dioc/vue"
|
import { useService } from "dioc/vue"
|
||||||
import { InspectionService } from "~/services/inspection"
|
import { InspectionService } from "~/services/inspection"
|
||||||
import { RESTTabService } from "~/services/tab/rest"
|
import { RESTTabService } from "~/services/tab/rest"
|
||||||
|
import IconExternalLink from "~icons/lucide/external-link"
|
||||||
|
|
||||||
const t = useI18n()
|
const t = useI18n()
|
||||||
const colorMode = useColorMode()
|
const colorMode = useColorMode()
|
||||||
@@ -112,6 +125,7 @@ const tabs = useService(RESTTabService)
|
|||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
response: HoppRESTResponse | null | undefined
|
response: HoppRESTResponse | null | undefined
|
||||||
|
isEmbed?: boolean
|
||||||
}>()
|
}>()
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<template>
|
<template>
|
||||||
<div
|
<div
|
||||||
v-if="selectedWidget"
|
v-if="selectedWidget"
|
||||||
class="divide-y divide-divider rounded border border-divider"
|
class="border divide-y rounded divide-divider border-divider"
|
||||||
>
|
>
|
||||||
<div v-if="loading" class="px-4 py-2">
|
<div v-if="loading" class="px-4 py-2">
|
||||||
{{ t("shared_requests.creating_widget") }}
|
{{ t("shared_requests.creating_widget") }}
|
||||||
@@ -10,17 +10,17 @@
|
|||||||
{{ t("shared_requests.description") }}
|
{{ t("shared_requests.description") }}
|
||||||
</div>
|
</div>
|
||||||
<div class="flex flex-col divide-y divide-divider">
|
<div class="flex flex-col divide-y divide-divider">
|
||||||
<div class="flex flex-col space-y-4 p-4">
|
<div class="flex flex-col p-4 space-y-4">
|
||||||
<div
|
<div
|
||||||
v-for="widget in widgets"
|
v-for="widget in widgets"
|
||||||
:key="widget.value"
|
:key="widget.value"
|
||||||
class="flex cursor-pointer flex-col space-y-2 rounded border border-divider px-4 py-3 hover:bg-dividerLight"
|
class="flex flex-col p-4 border rounded cursor-pointer border-divider hover:bg-dividerLight"
|
||||||
:class="{
|
:class="{
|
||||||
'!border-accentLight': selectedWidget.value === widget.value,
|
'!border-accentLight': selectedWidget.value === widget.value,
|
||||||
}"
|
}"
|
||||||
@click="selectedWidget = widget"
|
@click="selectedWidget = widget"
|
||||||
>
|
>
|
||||||
<span class="text-md font-bold">
|
<span class="mb-1 font-bold text-secondaryDark">
|
||||||
{{ widget.label }}
|
{{ widget.label }}
|
||||||
</span>
|
</span>
|
||||||
<span class="text-tiny">
|
<span class="text-tiny">
|
||||||
@@ -28,9 +28,13 @@
|
|||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex flex-col divide-y divide-divider">
|
<div class="flex flex-col items-center justify-center p-4">
|
||||||
<div class="px-4 py-3">{{ t("shared_requests.preview") }}</div>
|
<span
|
||||||
<div class="flex flex-col items-center justify-center px-4 py-10">
|
class="flex justify-center flex-1 mb-2 text-secondaryLight text-tiny"
|
||||||
|
>
|
||||||
|
{{ t("shared_requests.preview") }}
|
||||||
|
</span>
|
||||||
|
<div class="w-full">
|
||||||
<ShareTemplatesEmbeds
|
<ShareTemplatesEmbeds
|
||||||
v-if="selectedWidget.value === 'embed'"
|
v-if="selectedWidget.value === 'embed'"
|
||||||
:endpoint="request?.endpoint"
|
:endpoint="request?.endpoint"
|
||||||
@@ -132,7 +136,7 @@ const embedOption = ref<EmbedOption>({
|
|||||||
{
|
{
|
||||||
value: "authorization",
|
value: "authorization",
|
||||||
label: t("tab.authorization"),
|
label: t("tab.authorization"),
|
||||||
enabled: true,
|
enabled: false,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
theme: "system",
|
theme: "system",
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<template>
|
<template>
|
||||||
<div
|
<div
|
||||||
v-if="selectedWidget"
|
v-if="selectedWidget"
|
||||||
class="divide-y divide-divider rounded border border-divider"
|
class="border divide-y rounded divide-divider border-divider"
|
||||||
>
|
>
|
||||||
<div v-if="loading" class="px-4 py-2">
|
<div v-if="loading" class="px-4 py-2">
|
||||||
{{ t("shared_requests.creating_widget") }}
|
{{ t("shared_requests.creating_widget") }}
|
||||||
@@ -14,7 +14,7 @@
|
|||||||
<span class="text-secondaryLight">{{ t("state.loading") }}</span>
|
<span class="text-secondaryLight">{{ t("state.loading") }}</span>
|
||||||
</div>
|
</div>
|
||||||
<div v-else class="flex flex-col divide-y divide-divider">
|
<div v-else class="flex flex-col divide-y divide-divider">
|
||||||
<div class="flex flex-col space-y-4 p-4">
|
<div class="flex flex-col p-2 space-y-2">
|
||||||
<HoppSmartRadioGroup
|
<HoppSmartRadioGroup
|
||||||
v-model="selectedWidget.value"
|
v-model="selectedWidget.value"
|
||||||
:radios="widgets"
|
:radios="widgets"
|
||||||
@@ -22,9 +22,9 @@
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex flex-col divide-y divide-divider">
|
<div class="flex flex-col divide-y divide-divider">
|
||||||
<div class="flex items-center justify-center px-4 py-8">
|
<div class="flex items-center justify-center px-6 py-4">
|
||||||
<div v-if="selectedWidget.value === 'embed'" class="w-full flex-1">
|
<div v-if="selectedWidget.value === 'embed'" class="w-full">
|
||||||
<div class="flex flex-col pb-8">
|
<div class="flex flex-col pb-4">
|
||||||
<div
|
<div
|
||||||
v-for="option in embedOptions.tabs"
|
v-for="option in embedOptions.tabs"
|
||||||
:key="option.value"
|
:key="option.value"
|
||||||
@@ -36,7 +36,8 @@
|
|||||||
<HoppSmartCheckbox
|
<HoppSmartCheckbox
|
||||||
:on="option.enabled"
|
:on="option.enabled"
|
||||||
@change="removeEmbedOption(option.value)"
|
@change="removeEmbedOption(option.value)"
|
||||||
/>
|
>
|
||||||
|
</HoppSmartCheckbox>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex items-center justify-between">
|
<div class="flex items-center justify-between">
|
||||||
<span>
|
<span>
|
||||||
@@ -49,13 +50,11 @@
|
|||||||
theme="popover"
|
theme="popover"
|
||||||
:on-shown="() => tippyActions!.focus()"
|
:on-shown="() => tippyActions!.focus()"
|
||||||
>
|
>
|
||||||
<span class="select-wrapper">
|
<HoppButtonSecondary
|
||||||
<HoppButtonSecondary
|
class="!py-2 !px-0 capitalize"
|
||||||
class="ml-2 rounded-none pr-8 capitalize"
|
:label="embedOptions.theme"
|
||||||
:label="embedOptions.theme"
|
:icon="embedThemeIcon"
|
||||||
:icon="embedThemeIcon"
|
/>
|
||||||
/>
|
|
||||||
</span>
|
|
||||||
<template #content="{ hide }">
|
<template #content="{ hide }">
|
||||||
<div
|
<div
|
||||||
ref="tippyActions"
|
ref="tippyActions"
|
||||||
@@ -102,14 +101,20 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<span
|
||||||
|
class="flex justify-center mb-2 text-secondaryLight text-tiny"
|
||||||
|
>
|
||||||
|
{{ t("shared_requests.preview") }}
|
||||||
|
</span>
|
||||||
<ShareTemplatesEmbeds
|
<ShareTemplatesEmbeds
|
||||||
:endpoint="request?.endpoint"
|
:endpoint="request?.endpoint"
|
||||||
:method="request?.method"
|
:method="request?.method"
|
||||||
:model-value="embedOptions"
|
:model-value="embedOptions"
|
||||||
/>
|
/>
|
||||||
<div class="flex items-center justify-center py-4">
|
<div class="flex items-center justify-center">
|
||||||
<HoppButtonSecondary
|
<HoppButtonSecondary
|
||||||
:label="t('shared_requests.copy_html')"
|
:label="t('shared_requests.copy_html')"
|
||||||
|
class="underline text-secondaryDark"
|
||||||
@click="
|
@click="
|
||||||
copyContent({
|
copyContent({
|
||||||
widget: 'embed',
|
widget: 'embed',
|
||||||
@@ -126,12 +131,18 @@
|
|||||||
<div
|
<div
|
||||||
v-for="variant in buttonVariants"
|
v-for="variant in buttonVariants"
|
||||||
:key="variant.id"
|
:key="variant.id"
|
||||||
class="flex flex-col space-y-4"
|
class="flex flex-col"
|
||||||
>
|
>
|
||||||
|
<span
|
||||||
|
class="flex justify-center mb-2 text-secondaryLight text-tiny"
|
||||||
|
>
|
||||||
|
{{ t("shared_requests.preview") }}
|
||||||
|
</span>
|
||||||
<ShareTemplatesButton :img="variant.img" />
|
<ShareTemplatesButton :img="variant.img" />
|
||||||
<div class="flex items-center justify-between">
|
<div class="flex items-center justify-between">
|
||||||
<HoppButtonSecondary
|
<HoppButtonSecondary
|
||||||
:label="t('shared_requests.copy_html')"
|
:label="t('shared_requests.copy_html')"
|
||||||
|
class="underline text-secondaryDark"
|
||||||
@click="
|
@click="
|
||||||
copyContent({
|
copyContent({
|
||||||
widget: 'button',
|
widget: 'button',
|
||||||
@@ -142,6 +153,7 @@
|
|||||||
/>
|
/>
|
||||||
<HoppButtonSecondary
|
<HoppButtonSecondary
|
||||||
:label="t('shared_requests.copy_markdown')"
|
:label="t('shared_requests.copy_markdown')"
|
||||||
|
class="underline text-secondaryDark"
|
||||||
@click="
|
@click="
|
||||||
copyContent({
|
copyContent({
|
||||||
widget: 'button',
|
widget: 'button',
|
||||||
@@ -157,12 +169,17 @@
|
|||||||
<div
|
<div
|
||||||
v-for="variant in linkVariants"
|
v-for="variant in linkVariants"
|
||||||
:key="variant.type"
|
:key="variant.type"
|
||||||
class="flex flex-col items-center justify-center space-y-2"
|
class="flex flex-col items-center justify-center"
|
||||||
>
|
>
|
||||||
|
<span
|
||||||
|
class="flex justify-center mb-2 text-secondaryLight text-tiny"
|
||||||
|
>
|
||||||
|
{{ t("shared_requests.preview") }}
|
||||||
|
</span>
|
||||||
<ShareTemplatesLink :link="variant.link" :label="variant.label" />
|
<ShareTemplatesLink :link="variant.link" :label="variant.label" />
|
||||||
|
|
||||||
<HoppButtonSecondary
|
<HoppButtonSecondary
|
||||||
:label="t(`shared_requests.copy_${variant.type}`)"
|
:label="t(`shared_requests.copy_${variant.type}`)"
|
||||||
|
class="underline text-secondaryDark"
|
||||||
@click="
|
@click="
|
||||||
copyContent({
|
copyContent({
|
||||||
widget: 'link',
|
widget: 'link',
|
||||||
@@ -205,6 +222,35 @@ const props = defineProps({
|
|||||||
type: Boolean,
|
type: Boolean,
|
||||||
default: false,
|
default: false,
|
||||||
},
|
},
|
||||||
|
embedOptions: {
|
||||||
|
type: Object as PropType<EmbedOption>,
|
||||||
|
default: () => ({
|
||||||
|
selectedTab: "parameters",
|
||||||
|
tabs: [
|
||||||
|
{
|
||||||
|
value: "parameters",
|
||||||
|
label: "shared_requests.parameters",
|
||||||
|
enabled: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: "body",
|
||||||
|
label: "shared_requests.body",
|
||||||
|
enabled: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: "headers",
|
||||||
|
label: "shared_requests.headers",
|
||||||
|
enabled: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: "authorization",
|
||||||
|
label: "shared_requests.authorization",
|
||||||
|
enabled: false,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
theme: "system",
|
||||||
|
}),
|
||||||
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
const emit = defineEmits<{
|
const emit = defineEmits<{
|
||||||
@@ -220,6 +266,7 @@ const emit = defineEmits<{
|
|||||||
}>()
|
}>()
|
||||||
|
|
||||||
const selectedWidget = useVModel(props, "modelValue")
|
const selectedWidget = useVModel(props, "modelValue")
|
||||||
|
const embedOptions = useVModel(props, "embedOptions")
|
||||||
|
|
||||||
type WidgetID = "embed" | "button" | "link"
|
type WidgetID = "embed" | "button" | "link"
|
||||||
|
|
||||||
@@ -254,34 +301,6 @@ type EmbedOption = {
|
|||||||
}[]
|
}[]
|
||||||
theme: "light" | "dark" | "system"
|
theme: "light" | "dark" | "system"
|
||||||
}
|
}
|
||||||
|
|
||||||
const embedOptions = ref<EmbedOption>({
|
|
||||||
selectedTab: "parameters",
|
|
||||||
tabs: [
|
|
||||||
{
|
|
||||||
value: "parameters",
|
|
||||||
label: t("tab.parameters"),
|
|
||||||
enabled: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
value: "body",
|
|
||||||
label: t("tab.body"),
|
|
||||||
enabled: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
value: "headers",
|
|
||||||
label: t("tab.headers"),
|
|
||||||
enabled: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
value: "authorization",
|
|
||||||
label: t("tab.authorization"),
|
|
||||||
enabled: true,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
theme: "system",
|
|
||||||
})
|
|
||||||
|
|
||||||
const embedThemeIcon = computed(() => {
|
const embedThemeIcon = computed(() => {
|
||||||
if (embedOptions.value.theme === "system") {
|
if (embedOptions.value.theme === "system") {
|
||||||
return IconMonitor
|
return IconMonitor
|
||||||
@@ -355,12 +374,7 @@ const linkVariants: LinkVariant[] = [
|
|||||||
const baseURL = import.meta.env.VITE_SHORTCODE_BASE_URL ?? "https://hopp.sh"
|
const baseURL = import.meta.env.VITE_SHORTCODE_BASE_URL ?? "https://hopp.sh"
|
||||||
|
|
||||||
const copyEmbed = () => {
|
const copyEmbed = () => {
|
||||||
const options = embedOptions.value
|
return `<iframe src="${baseURL}/e/${props.request?.id}' style='width: 100%; height: 500px; border: 0; border-radius: 4px; overflow: hidden;'></iframe>`
|
||||||
const enabledEmbedOptions = options.tabs
|
|
||||||
.filter((tab) => tab.enabled)
|
|
||||||
.map((tab) => tab.value)
|
|
||||||
.toString()
|
|
||||||
return `<iframe src="${baseURL}/e/${props.request?.id}/${enabledEmbedOptions}' style='width: 100%; height: 500px; border: 0; border-radius: 4px; overflow: hidden;'></iframe>`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const copyButton = (
|
const copyButton = (
|
||||||
@@ -410,7 +424,10 @@ const copyContent = ({
|
|||||||
} else {
|
} else {
|
||||||
content = copyEmbed()
|
content = copyEmbed()
|
||||||
}
|
}
|
||||||
const copyContent = { sharedRequestID: props.request?.id, content }
|
const copyContent = {
|
||||||
|
sharedRequestID: props.request?.id,
|
||||||
|
content,
|
||||||
|
}
|
||||||
emit("copy-shared-request", copyContent)
|
emit("copy-shared-request", copyContent)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,9 @@
|
|||||||
<HoppSmartModal
|
<HoppSmartModal
|
||||||
v-if="show"
|
v-if="show"
|
||||||
dialog
|
dialog
|
||||||
:title="t('modal.share_request')"
|
:title="
|
||||||
|
step === 1 ? t('modal.share_request') : t('modal.customize_request')
|
||||||
|
"
|
||||||
styles="sm:max-w-md"
|
styles="sm:max-w-md"
|
||||||
@close="hideModal"
|
@close="hideModal"
|
||||||
>
|
>
|
||||||
@@ -21,6 +23,7 @@
|
|||||||
<ShareCustomizeModal
|
<ShareCustomizeModal
|
||||||
v-else-if="step === 2"
|
v-else-if="step === 2"
|
||||||
v-model="selectedWidget"
|
v-model="selectedWidget"
|
||||||
|
v-model:embed-options="embedOptions"
|
||||||
:request="request"
|
:request="request"
|
||||||
:loading="loading"
|
:loading="loading"
|
||||||
@copy-shared-request="copySharedRequest"
|
@copy-shared-request="copySharedRequest"
|
||||||
@@ -28,19 +31,21 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<template #footer>
|
<template #footer>
|
||||||
<div v-if="step === 1" class="flex justify-end">
|
<div class="flex justify-start flex-1">
|
||||||
<HoppButtonPrimary
|
<HoppButtonPrimary
|
||||||
|
v-if="step === 1"
|
||||||
:label="t('action.create')"
|
:label="t('action.create')"
|
||||||
:loading="loading"
|
:loading="loading"
|
||||||
@click="createSharedRequest"
|
@click="createSharedRequest"
|
||||||
/>
|
/>
|
||||||
<HoppButtonSecondary
|
<HoppButtonSecondary
|
||||||
:label="t('action.cancel')"
|
:label="step === 1 ? t('action.cancel') : t('action.close')"
|
||||||
class="mr-2"
|
class="ml-2"
|
||||||
|
filled
|
||||||
|
outline
|
||||||
@click="hideModal"
|
@click="hideModal"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<HoppButtonPrimary v-else :label="t('action.close')" @click="hideModal" />
|
|
||||||
</template>
|
</template>
|
||||||
</HoppSmartModal>
|
</HoppSmartModal>
|
||||||
</template>
|
</template>
|
||||||
@@ -53,6 +58,18 @@ import { useI18n } from "~/composables/i18n"
|
|||||||
|
|
||||||
const t = useI18n()
|
const t = useI18n()
|
||||||
|
|
||||||
|
type EmbedTabs = "parameters" | "body" | "headers" | "authorization"
|
||||||
|
|
||||||
|
type EmbedOption = {
|
||||||
|
selectedTab: EmbedTabs
|
||||||
|
tabs: {
|
||||||
|
value: EmbedTabs
|
||||||
|
label: string
|
||||||
|
enabled: boolean
|
||||||
|
}[]
|
||||||
|
theme: "light" | "dark" | "system"
|
||||||
|
}
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
request: {
|
request: {
|
||||||
type: Object as PropType<HoppRESTRequest | null>,
|
type: Object as PropType<HoppRESTRequest | null>,
|
||||||
@@ -75,6 +92,35 @@ const props = defineProps({
|
|||||||
type: Number,
|
type: Number,
|
||||||
default: 1,
|
default: 1,
|
||||||
},
|
},
|
||||||
|
embedOptions: {
|
||||||
|
type: Object as PropType<EmbedOption>,
|
||||||
|
default: () => ({
|
||||||
|
selectedTab: "parameters",
|
||||||
|
tabs: [
|
||||||
|
{
|
||||||
|
value: "parameters",
|
||||||
|
label: "shared_requests.parameters",
|
||||||
|
enabled: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: "body",
|
||||||
|
label: "shared_requests.body",
|
||||||
|
enabled: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: "headers",
|
||||||
|
label: "shared_requests.headers",
|
||||||
|
enabled: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: "authorization",
|
||||||
|
label: "shared_requests.authorization",
|
||||||
|
enabled: false,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
theme: "system",
|
||||||
|
}),
|
||||||
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
type WidgetID = "embed" | "button" | "link"
|
type WidgetID = "embed" | "button" | "link"
|
||||||
@@ -86,6 +132,7 @@ type Widget = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const selectedWidget = useVModel(props, "modelValue")
|
const selectedWidget = useVModel(props, "modelValue")
|
||||||
|
const embedOptions = useVModel(props, "embedOptions")
|
||||||
|
|
||||||
const emit = defineEmits<{
|
const emit = defineEmits<{
|
||||||
(e: "create-shared-request", request: HoppRESTRequest | null): void
|
(e: "create-shared-request", request: HoppRESTRequest | null): void
|
||||||
@@ -94,7 +141,7 @@ const emit = defineEmits<{
|
|||||||
(e: "update:step", value: number): void
|
(e: "update:step", value: number): void
|
||||||
(
|
(
|
||||||
e: "copy-shared-request",
|
e: "copy-shared-request",
|
||||||
request: {
|
payload: {
|
||||||
sharedRequestID: string | undefined
|
sharedRequestID: string | undefined
|
||||||
content: string | undefined
|
content: string | undefined
|
||||||
}
|
}
|
||||||
@@ -105,11 +152,11 @@ const createSharedRequest = () => {
|
|||||||
emit("create-shared-request", props.request as HoppRESTRequest)
|
emit("create-shared-request", props.request as HoppRESTRequest)
|
||||||
}
|
}
|
||||||
|
|
||||||
const copySharedRequest = (request: {
|
const copySharedRequest = (payload: {
|
||||||
sharedRequestID: string | undefined
|
sharedRequestID: string | undefined
|
||||||
content: string | undefined
|
content: string | undefined
|
||||||
}) => {
|
}) => {
|
||||||
emit("copy-shared-request", request)
|
emit("copy-shared-request", payload)
|
||||||
}
|
}
|
||||||
|
|
||||||
const hideModal = () => {
|
const hideModal = () => {
|
||||||
|
|||||||
@@ -1,31 +1,31 @@
|
|||||||
<template>
|
<template>
|
||||||
<div
|
<div
|
||||||
class="group flex items-stretch"
|
class="flex items-stretch group"
|
||||||
@contextmenu.prevent="options!.tippy.show()"
|
@contextmenu.prevent="options!.tippy.show()"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
v-tippy="{ theme: 'tooltip', delay: [500, 20] }"
|
v-tippy="{ theme: 'tooltip', delay: [500, 20] }"
|
||||||
class="pointer-events-auto flex min-w-0 flex-1 cursor-pointer items-center justify-center py-2"
|
class="flex items-center justify-center flex-1 min-w-0 py-2 cursor-pointer pointer-events-auto"
|
||||||
:title="`${timeStamp}`"
|
:title="`${timeStamp}`"
|
||||||
@click="openInNewTab"
|
@click="openInNewTab"
|
||||||
>
|
>
|
||||||
<span
|
<span
|
||||||
class="pointer-events-none flex w-16 items-center justify-center truncate px-2"
|
class="flex items-center justify-center w-16 px-2 truncate pointer-events-none"
|
||||||
:style="{ color: requestLabelColor }"
|
:style="{ color: requestLabelColor }"
|
||||||
>
|
>
|
||||||
<span class="truncate text-tiny font-semibold">
|
<span class="font-semibold truncate text-tiny">
|
||||||
{{ parseRequest.method }}
|
{{ parseRequest.method }}
|
||||||
</span>
|
</span>
|
||||||
</span>
|
</span>
|
||||||
<span
|
<span
|
||||||
class="pointer-events-none flex min-w-0 flex-1 items-center pr-2 transition group-hover:text-secondaryDark"
|
class="flex items-center flex-1 min-w-0 pr-2 transition pointer-events-none group-hover:text-secondaryDark"
|
||||||
>
|
>
|
||||||
<span class="flex-1 truncate">
|
<span class="flex-1 truncate">
|
||||||
{{ parseRequest.endpoint }}
|
{{ parseRequest.endpoint }}
|
||||||
</span>
|
</span>
|
||||||
</span>
|
</span>
|
||||||
<span
|
<span
|
||||||
class="flex-1 truncate border-l border-dividerDark px-2 text-secondaryLight group-hover:text-secondaryDark"
|
class="flex px-2 truncate text-secondaryLight group-hover:text-secondaryDark"
|
||||||
>
|
>
|
||||||
{{ parseRequest.name }}
|
{{ parseRequest.name }}
|
||||||
</span>
|
</span>
|
||||||
@@ -69,7 +69,7 @@
|
|||||||
/>
|
/>
|
||||||
<HoppSmartItem
|
<HoppSmartItem
|
||||||
ref="customizeAction"
|
ref="customizeAction"
|
||||||
:icon="IconFileEdit"
|
:icon="IconCustomize"
|
||||||
:label="`${t('shared_requests.customize')}`"
|
:label="`${t('shared_requests.customize')}`"
|
||||||
:shortcut="['E']"
|
:shortcut="['E']"
|
||||||
@click="
|
@click="
|
||||||
@@ -110,7 +110,7 @@ import { getMethodLabelColorClassOf } from "~/helpers/rest/labelColoring"
|
|||||||
import { Shortcode } from "~/helpers/shortcode/Shortcode"
|
import { Shortcode } from "~/helpers/shortcode/Shortcode"
|
||||||
import IconArrowUpRight from "~icons/lucide/arrow-up-right-square"
|
import IconArrowUpRight from "~icons/lucide/arrow-up-right-square"
|
||||||
import IconMoreVertical from "~icons/lucide/more-vertical"
|
import IconMoreVertical from "~icons/lucide/more-vertical"
|
||||||
import IconFileEdit from "~icons/lucide/file-edit"
|
import IconCustomize from "~icons/lucide/settings-2"
|
||||||
import IconTrash2 from "~icons/lucide/trash-2"
|
import IconTrash2 from "~icons/lucide/trash-2"
|
||||||
import { shortDateTime } from "~/helpers/utils/date"
|
import { shortDateTime } from "~/helpers/utils/date"
|
||||||
|
|
||||||
@@ -121,7 +121,12 @@ const props = defineProps<{
|
|||||||
}>()
|
}>()
|
||||||
|
|
||||||
const emit = defineEmits<{
|
const emit = defineEmits<{
|
||||||
(e: "customize-shared-request", request: HoppRESTRequest, id: string): void
|
(
|
||||||
|
e: "customize-shared-request",
|
||||||
|
request: HoppRESTRequest,
|
||||||
|
id: string,
|
||||||
|
embedProperties?: string | null
|
||||||
|
): void
|
||||||
(e: "delete-shared-request", codeID: string): void
|
(e: "delete-shared-request", codeID: string): void
|
||||||
(e: "open-new-tab", request: HoppRESTRequest): void
|
(e: "open-new-tab", request: HoppRESTRequest): void
|
||||||
}>()
|
}>()
|
||||||
@@ -130,6 +135,7 @@ const tippyActions = ref<TippyComponent | null>(null)
|
|||||||
const openInNewTabAction = ref<HTMLButtonElement | null>(null)
|
const openInNewTabAction = ref<HTMLButtonElement | null>(null)
|
||||||
const customizeAction = ref<HTMLButtonElement | null>(null)
|
const customizeAction = ref<HTMLButtonElement | null>(null)
|
||||||
const deleteAction = ref<HTMLButtonElement | null>(null)
|
const deleteAction = ref<HTMLButtonElement | null>(null)
|
||||||
|
const options = ref<any | null>(null)
|
||||||
|
|
||||||
const parseRequest = computed(() =>
|
const parseRequest = computed(() =>
|
||||||
pipe(props.request.request, JSON.parse, translateToNewRequest)
|
pipe(props.request.request, JSON.parse, translateToNewRequest)
|
||||||
@@ -144,7 +150,13 @@ const openInNewTab = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const customizeSharedRequest = () => {
|
const customizeSharedRequest = () => {
|
||||||
emit("customize-shared-request", parseRequest.value, props.request.id)
|
const embedProperties = props.request.properties
|
||||||
|
emit(
|
||||||
|
"customize-shared-request",
|
||||||
|
parseRequest.value,
|
||||||
|
props.request.id,
|
||||||
|
embedProperties
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
const deleteSharedRequest = () => {
|
const deleteSharedRequest = () => {
|
||||||
|
|||||||
@@ -80,11 +80,12 @@
|
|||||||
/>
|
/>
|
||||||
<ShareModal
|
<ShareModal
|
||||||
v-model="selectedWidget"
|
v-model="selectedWidget"
|
||||||
|
v-model:embed-options="embedOptions"
|
||||||
|
:step="step"
|
||||||
:request="requestToShare"
|
:request="requestToShare"
|
||||||
:show="showShareRequestModal"
|
:show="showShareRequestModal"
|
||||||
:loading="shareRequestCreatingLoading"
|
:loading="shareRequestCreatingLoading"
|
||||||
:step="step"
|
@hide-modal="displayCustomizeRequestModal(false, null)"
|
||||||
@hide-modal="displayCustomizeRequestModal(false)"
|
|
||||||
@copy-shared-request="copySharedRequest"
|
@copy-shared-request="copySharedRequest"
|
||||||
@create-shared-request="createSharedRequest"
|
@create-shared-request="createSharedRequest"
|
||||||
/>
|
/>
|
||||||
@@ -105,6 +106,7 @@ import * as TE from "fp-ts/TaskEither"
|
|||||||
import {
|
import {
|
||||||
deleteShortcode as backendDeleteShortcode,
|
deleteShortcode as backendDeleteShortcode,
|
||||||
createShortcode,
|
createShortcode,
|
||||||
|
updateEmbedProperties,
|
||||||
} from "~/helpers/backend/mutations/Shortcode"
|
} from "~/helpers/backend/mutations/Shortcode"
|
||||||
import { GQLError } from "~/helpers/backend/GQLClient"
|
import { GQLError } from "~/helpers/backend/GQLClient"
|
||||||
import { useToast } from "~/composables/toast"
|
import { useToast } from "~/composables/toast"
|
||||||
@@ -114,6 +116,7 @@ import { copyToClipboard } from "~/helpers/utils/clipboard"
|
|||||||
import * as E from "fp-ts/Either"
|
import * as E from "fp-ts/Either"
|
||||||
import { RESTTabService } from "~/services/tab/rest"
|
import { RESTTabService } from "~/services/tab/rest"
|
||||||
import { useService } from "dioc/vue"
|
import { useService } from "dioc/vue"
|
||||||
|
import { watch } from "vue"
|
||||||
|
|
||||||
const t = useI18n()
|
const t = useI18n()
|
||||||
const colorMode = useColorMode()
|
const colorMode = useColorMode()
|
||||||
@@ -130,6 +133,70 @@ const shareRequestCreatingLoading = ref(false)
|
|||||||
|
|
||||||
const requestToShare = ref<HoppRESTRequest | null>(null)
|
const requestToShare = ref<HoppRESTRequest | null>(null)
|
||||||
|
|
||||||
|
const embedOptions = ref<EmbedOption>({
|
||||||
|
selectedTab: "parameters",
|
||||||
|
tabs: [
|
||||||
|
{
|
||||||
|
value: "parameters",
|
||||||
|
label: t("tab.parameters"),
|
||||||
|
enabled: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: "body",
|
||||||
|
label: t("tab.body"),
|
||||||
|
enabled: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: "headers",
|
||||||
|
label: t("tab.headers"),
|
||||||
|
enabled: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: "authorization",
|
||||||
|
label: t("tab.authorization"),
|
||||||
|
enabled: false,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
theme: "system",
|
||||||
|
})
|
||||||
|
|
||||||
|
const updateEmbedProperty = async (
|
||||||
|
shareRequestID: string,
|
||||||
|
properties: string
|
||||||
|
) => {
|
||||||
|
const customizeEmbedResult = await updateEmbedProperties(
|
||||||
|
shareRequestID,
|
||||||
|
properties
|
||||||
|
)()
|
||||||
|
|
||||||
|
if (E.isLeft(customizeEmbedResult)) {
|
||||||
|
toast.error(`${customizeEmbedResult.left.error}`)
|
||||||
|
toast.error(t("error.something_went_wrong"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => embedOptions.value,
|
||||||
|
() => {
|
||||||
|
if (
|
||||||
|
requestToShare.value &&
|
||||||
|
requestToShare.value.id &&
|
||||||
|
showShareRequestModal.value
|
||||||
|
) {
|
||||||
|
if (selectedWidget.value.value === "embed") {
|
||||||
|
const properties = {
|
||||||
|
options: embedOptions.value.tabs
|
||||||
|
.filter((tab) => tab.enabled)
|
||||||
|
.map((tab) => tab.value),
|
||||||
|
theme: embedOptions.value.theme,
|
||||||
|
}
|
||||||
|
updateEmbedProperty(requestToShare.value.id, JSON.stringify(properties))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{ deep: true }
|
||||||
|
)
|
||||||
|
|
||||||
const restTab = useService(RESTTabService)
|
const restTab = useService(RESTTabService)
|
||||||
|
|
||||||
const currentUser = useReadonlyStream(
|
const currentUser = useReadonlyStream(
|
||||||
@@ -139,6 +206,18 @@ const currentUser = useReadonlyStream(
|
|||||||
|
|
||||||
const step = ref(1)
|
const step = ref(1)
|
||||||
|
|
||||||
|
type EmbedTabs = "parameters" | "body" | "headers" | "authorization"
|
||||||
|
|
||||||
|
type EmbedOption = {
|
||||||
|
selectedTab: EmbedTabs
|
||||||
|
tabs: {
|
||||||
|
value: EmbedTabs
|
||||||
|
label: string
|
||||||
|
enabled: boolean
|
||||||
|
}[]
|
||||||
|
theme: "light" | "dark" | "system"
|
||||||
|
}
|
||||||
|
|
||||||
type WidgetID = "embed" | "button" | "link"
|
type WidgetID = "embed" | "button" | "link"
|
||||||
|
|
||||||
type Widget = {
|
type Widget = {
|
||||||
@@ -218,15 +297,73 @@ const displayShareRequestModal = (show: boolean) => {
|
|||||||
showShareRequestModal.value = show
|
showShareRequestModal.value = show
|
||||||
step.value = 1
|
step.value = 1
|
||||||
}
|
}
|
||||||
const displayCustomizeRequestModal = (show: boolean) => {
|
|
||||||
|
const displayCustomizeRequestModal = (
|
||||||
|
show: boolean,
|
||||||
|
embedProperties?: string | null
|
||||||
|
) => {
|
||||||
showShareRequestModal.value = show
|
showShareRequestModal.value = show
|
||||||
step.value = 2
|
step.value = 2
|
||||||
|
if (!embedProperties) {
|
||||||
|
selectedWidget.value = {
|
||||||
|
value: "button",
|
||||||
|
label: t("shared_requests.button"),
|
||||||
|
info: t("shared_requests.button_info"),
|
||||||
|
}
|
||||||
|
embedOptions.value = {
|
||||||
|
selectedTab: "parameters",
|
||||||
|
tabs: [
|
||||||
|
{
|
||||||
|
value: "parameters",
|
||||||
|
label: t("tab.parameters"),
|
||||||
|
enabled: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: "body",
|
||||||
|
label: t("tab.body"),
|
||||||
|
enabled: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: "headers",
|
||||||
|
label: t("tab.headers"),
|
||||||
|
enabled: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: "authorization",
|
||||||
|
label: t("tab.authorization"),
|
||||||
|
enabled: false,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
theme: "system",
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
const parsedEmbedProperties = JSON.parse(embedProperties)
|
||||||
|
embedOptions.value = {
|
||||||
|
selectedTab: parsedEmbedProperties.options[0],
|
||||||
|
tabs: embedOptions.value.tabs.map((tab) => {
|
||||||
|
return {
|
||||||
|
...tab,
|
||||||
|
enabled: parsedEmbedProperties.options.includes(tab.value),
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
theme: parsedEmbedProperties.theme,
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const createSharedRequest = async (request: HoppRESTRequest | null) => {
|
const createSharedRequest = async (request: HoppRESTRequest | null) => {
|
||||||
if (request && selectedWidget.value) {
|
if (request && selectedWidget.value) {
|
||||||
|
const properties = {
|
||||||
|
options: ["parameters", "body", "headers"],
|
||||||
|
theme: "system",
|
||||||
|
}
|
||||||
shareRequestCreatingLoading.value = true
|
shareRequestCreatingLoading.value = true
|
||||||
const sharedRequestResult = await createShortcode(request)()
|
const sharedRequestResult = await createShortcode(
|
||||||
|
request,
|
||||||
|
selectedWidget.value.value === "embed"
|
||||||
|
? JSON.stringify(properties)
|
||||||
|
: undefined
|
||||||
|
)()
|
||||||
|
|
||||||
platform.analytics?.logEvent({
|
platform.analytics?.logEvent({
|
||||||
type: "HOPP_SHORTCODE_CREATED",
|
type: "HOPP_SHORTCODE_CREATED",
|
||||||
@@ -243,6 +380,23 @@ const createSharedRequest = async (request: HoppRESTRequest | null) => {
|
|||||||
id: sharedRequestResult.right.createShortcode.id,
|
id: sharedRequestResult.right.createShortcode.id,
|
||||||
}
|
}
|
||||||
step.value = 2
|
step.value = 2
|
||||||
|
|
||||||
|
if (sharedRequestResult.right.createShortcode.properties) {
|
||||||
|
const parsedEmbedProperties = JSON.parse(
|
||||||
|
sharedRequestResult.right.createShortcode.properties
|
||||||
|
)
|
||||||
|
|
||||||
|
embedOptions.value = {
|
||||||
|
selectedTab: parsedEmbedProperties.options[0],
|
||||||
|
tabs: embedOptions.value.tabs.map((tab) => {
|
||||||
|
return {
|
||||||
|
...tab,
|
||||||
|
enabled: parsedEmbedProperties.options.includes(tab.value),
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
theme: parsedEmbedProperties.theme,
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -250,21 +404,22 @@ const createSharedRequest = async (request: HoppRESTRequest | null) => {
|
|||||||
|
|
||||||
const customizeSharedRequest = (
|
const customizeSharedRequest = (
|
||||||
request: HoppRESTRequest,
|
request: HoppRESTRequest,
|
||||||
shredRequestID: string
|
shredRequestID: string,
|
||||||
|
embedProperties?: string | null
|
||||||
) => {
|
) => {
|
||||||
requestToShare.value = {
|
requestToShare.value = {
|
||||||
...request,
|
...request,
|
||||||
id: shredRequestID,
|
id: shredRequestID,
|
||||||
}
|
}
|
||||||
displayCustomizeRequestModal(true)
|
displayCustomizeRequestModal(true, embedProperties)
|
||||||
}
|
}
|
||||||
|
|
||||||
const copySharedRequest = (request: {
|
const copySharedRequest = (payload: {
|
||||||
sharedRequestID: string | undefined
|
sharedRequestID: string | undefined
|
||||||
content: string | undefined
|
content: string | undefined
|
||||||
}) => {
|
}) => {
|
||||||
if (request.content) {
|
if (payload.content) {
|
||||||
copyToClipboard(request.content)
|
copyToClipboard(payload.content)
|
||||||
toast.success(t("state.copied_to_clipboard"))
|
toast.success(t("state.copied_to_clipboard"))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,10 +1,6 @@
|
|||||||
<template>
|
<template>
|
||||||
<div
|
<div class="flex flex-col items-center p-4 border rounded border-dividerDark">
|
||||||
class="flex items-center justify-center rounded border border-dotted border-dividerDark p-5"
|
<img :src="img" :alt="t('shared_requests.run_in_hoppscotch')" />
|
||||||
>
|
|
||||||
<a href="/" target="_blank">
|
|
||||||
<img :src="img" :alt="t('shared_requests.run_in_hoppscotch')" />
|
|
||||||
</a>
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
|||||||
@@ -1,32 +1,30 @@
|
|||||||
<template>
|
<template>
|
||||||
<div
|
<div
|
||||||
class="flex flex-col rounded border border-dotted border-divider p-5"
|
class="flex flex-col p-4 border rounded border-dividerDark"
|
||||||
:class="{
|
:class="{
|
||||||
'bg-accentContrast': isEmbedThemeLight,
|
'bg-accentContrast': isEmbedThemeLight,
|
||||||
}"
|
}"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="flex items-stretch space-x-4 rounded border-divider"
|
class="flex items-stretch space-x-2"
|
||||||
:class="{
|
:class="{
|
||||||
'bg-accentContrast': isEmbedThemeLight,
|
'bg-accentContrast': isEmbedThemeLight,
|
||||||
}"
|
}"
|
||||||
>
|
>
|
||||||
<span
|
<span class="flex items-center min-w-0 border rounded border-divider">
|
||||||
class="flex max-w-[4rem] items-center justify-center rounded border border-divider px-1 py-2 text-tiny"
|
|
||||||
:class="{
|
|
||||||
'!border-dividerLight bg-accentContrast text-primary':
|
|
||||||
isEmbedThemeLight,
|
|
||||||
}"
|
|
||||||
>
|
|
||||||
<span class="truncate">
|
|
||||||
{{ method }}
|
|
||||||
</span>
|
|
||||||
</span>
|
|
||||||
<span
|
|
||||||
class="flex max-w-46 items-center rounded border border-divider p-2"
|
|
||||||
>
|
|
||||||
<span
|
<span
|
||||||
class="min-w-0 truncate"
|
class="flex max-w-[4rem] rounded-l h-full items-center justify-center border-r border-divider text-tiny"
|
||||||
|
:class="{
|
||||||
|
'!border-dividerLight bg-accentContrast text-primary':
|
||||||
|
isEmbedThemeLight,
|
||||||
|
}"
|
||||||
|
>
|
||||||
|
<span class="px-3 truncate">
|
||||||
|
{{ method }}
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
<span
|
||||||
|
class="px-3 truncate"
|
||||||
:class="{
|
:class="{
|
||||||
'text-primary': isEmbedThemeLight,
|
'text-primary': isEmbedThemeLight,
|
||||||
}"
|
}"
|
||||||
@@ -35,7 +33,7 @@
|
|||||||
</span>
|
</span>
|
||||||
</span>
|
</span>
|
||||||
<button
|
<button
|
||||||
class="flex items-center justify-center rounded border border-dividerDark bg-primaryDark px-3 py-2 font-semibold text-secondary"
|
class="flex items-center justify-center flex-shrink-0 px-3 py-2 font-semibold border rounded border-dividerDark bg-primaryDark text-secondary"
|
||||||
:class="{
|
:class="{
|
||||||
'!bg-accentContrast text-primaryLight': isEmbedThemeLight,
|
'!bg-accentContrast text-primaryLight': isEmbedThemeLight,
|
||||||
}"
|
}"
|
||||||
@@ -44,10 +42,10 @@
|
|||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
class="flex border-divider"
|
class="flex"
|
||||||
:class="{
|
:class="{
|
||||||
'bg-accentContrast text-primary': isEmbedThemeLight,
|
'bg-accentContrast text-primary': isEmbedThemeLight,
|
||||||
'border-b pt-2 ': !noActiveTab,
|
'border-b border-divider pt-2': !noActiveTab,
|
||||||
}"
|
}"
|
||||||
>
|
>
|
||||||
<span
|
<span
|
||||||
@@ -57,7 +55,8 @@
|
|||||||
class="px-2 py-2"
|
class="px-2 py-2"
|
||||||
:class="{
|
:class="{
|
||||||
'border-b border-dividerDark':
|
'border-b border-dividerDark':
|
||||||
embedOptions.selectedTab === option.value,
|
embedOptions.tabs.filter((tab) => tab.enabled)[0]?.value ===
|
||||||
|
option.value,
|
||||||
}"
|
}"
|
||||||
>
|
>
|
||||||
{{ option.label }}
|
{{ option.label }}
|
||||||
|
|||||||
@@ -1,7 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
<div
|
<div class="flex flex-col items-center p-4 border rounded border-dividerDark">
|
||||||
class="flex items-center justify-center rounded border border-dotted border-dividerDark p-5"
|
|
||||||
>
|
|
||||||
<span
|
<span
|
||||||
:class="{
|
:class="{
|
||||||
'border-b border-secondary': label,
|
'border-b border-secondary': label,
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
mutation CreateShortcode($request: String!) {
|
mutation CreateShortcode($request: String!, $properties: String) {
|
||||||
createShortcode(request: $request) {
|
createShortcode(request: $request, properties: $properties) {
|
||||||
id
|
id
|
||||||
request
|
request
|
||||||
createdOn
|
createdOn
|
||||||
|
|||||||
@@ -0,0 +1,8 @@
|
|||||||
|
mutation UpdateEmbedProperties($code: ID!, $properties: String!) {
|
||||||
|
updateEmbedProperties(code: $code, properties: $properties) {
|
||||||
|
id
|
||||||
|
request
|
||||||
|
properties
|
||||||
|
createdOn
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,5 +2,6 @@ query ResolveShortcode($code: ID!) {
|
|||||||
shortcode(code: $code) {
|
shortcode(code: $code) {
|
||||||
id
|
id
|
||||||
request
|
request
|
||||||
|
properties
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,8 @@
|
|||||||
|
subscription ShortcodeUpdated {
|
||||||
|
myShortcodesUpdated {
|
||||||
|
id
|
||||||
|
request
|
||||||
|
createdOn
|
||||||
|
properties
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -7,15 +7,22 @@ import {
|
|||||||
DeleteShortcodeDocument,
|
DeleteShortcodeDocument,
|
||||||
DeleteShortcodeMutation,
|
DeleteShortcodeMutation,
|
||||||
DeleteShortcodeMutationVariables,
|
DeleteShortcodeMutationVariables,
|
||||||
|
UpdateEmbedPropertiesDocument,
|
||||||
|
UpdateEmbedPropertiesMutation,
|
||||||
|
UpdateEmbedPropertiesMutationVariables,
|
||||||
} from "../graphql"
|
} from "../graphql"
|
||||||
|
|
||||||
type DeleteShortcodeErrors = "shortcode/not_found"
|
type DeleteShortcodeErrors = "shortcode/not_found"
|
||||||
|
|
||||||
export const createShortcode = (request: HoppRESTRequest) =>
|
export const createShortcode = (
|
||||||
|
request: HoppRESTRequest,
|
||||||
|
properties?: string
|
||||||
|
) =>
|
||||||
runMutation<CreateShortcodeMutation, CreateShortcodeMutationVariables, "">(
|
runMutation<CreateShortcodeMutation, CreateShortcodeMutationVariables, "">(
|
||||||
CreateShortcodeDocument,
|
CreateShortcodeDocument,
|
||||||
{
|
{
|
||||||
request: JSON.stringify(request),
|
request: JSON.stringify(request),
|
||||||
|
properties,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -27,3 +34,13 @@ export const deleteShortcode = (code: string) =>
|
|||||||
>(DeleteShortcodeDocument, {
|
>(DeleteShortcodeDocument, {
|
||||||
code,
|
code,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
export const updateEmbedProperties = (code: string, properties: string) =>
|
||||||
|
runMutation<
|
||||||
|
UpdateEmbedPropertiesMutation,
|
||||||
|
UpdateEmbedPropertiesMutationVariables,
|
||||||
|
""
|
||||||
|
>(UpdateEmbedPropertiesDocument, {
|
||||||
|
code,
|
||||||
|
properties,
|
||||||
|
})
|
||||||
|
|||||||
@@ -4,6 +4,6 @@
|
|||||||
export interface Shortcode {
|
export interface Shortcode {
|
||||||
id: string
|
id: string
|
||||||
request: string
|
request: string
|
||||||
properties?: string | null | undefined
|
properties?: string | null
|
||||||
createdOn: Date
|
createdOn: Date
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import {
|
|||||||
GetUserShortcodesDocument,
|
GetUserShortcodesDocument,
|
||||||
ShortcodeCreatedDocument,
|
ShortcodeCreatedDocument,
|
||||||
ShortcodeDeletedDocument,
|
ShortcodeDeletedDocument,
|
||||||
|
ShortcodeUpdatedDocument,
|
||||||
} from "../backend/graphql"
|
} from "../backend/graphql"
|
||||||
import { BACKEND_PAGE_SIZE } from "../backend/helpers"
|
import { BACKEND_PAGE_SIZE } from "../backend/helpers"
|
||||||
import { Shortcode } from "./Shortcode"
|
import { Shortcode } from "./Shortcode"
|
||||||
@@ -25,9 +26,11 @@ export default class ShortcodeListAdapter {
|
|||||||
|
|
||||||
private shortcodeCreated: Subscription | null
|
private shortcodeCreated: Subscription | null
|
||||||
private shortcodeRevoked: Subscription | null
|
private shortcodeRevoked: Subscription | null
|
||||||
|
private shortcodeUpdated: Subscription | null
|
||||||
|
|
||||||
private shortcodeCreatedSub: WSubscription | null
|
private shortcodeCreatedSub: WSubscription | null
|
||||||
private shortcodeRevokedSub: WSubscription | null
|
private shortcodeRevokedSub: WSubscription | null
|
||||||
|
private shortcodeUpdatedSub: WSubscription | null
|
||||||
|
|
||||||
constructor(deferInit = false) {
|
constructor(deferInit = false) {
|
||||||
this.error$ = new BehaviorSubject<GQLError<string> | null>(null)
|
this.error$ = new BehaviorSubject<GQLError<string> | null>(null)
|
||||||
@@ -39,8 +42,10 @@ export default class ShortcodeListAdapter {
|
|||||||
this.isDispose = true
|
this.isDispose = true
|
||||||
this.shortcodeCreated = null
|
this.shortcodeCreated = null
|
||||||
this.shortcodeRevoked = null
|
this.shortcodeRevoked = null
|
||||||
|
this.shortcodeUpdated = null
|
||||||
this.shortcodeCreatedSub = null
|
this.shortcodeCreatedSub = null
|
||||||
this.shortcodeRevokedSub = null
|
this.shortcodeRevokedSub = null
|
||||||
|
this.shortcodeUpdatedSub = null
|
||||||
|
|
||||||
if (!deferInit) this.initialize()
|
if (!deferInit) this.initialize()
|
||||||
}
|
}
|
||||||
@@ -48,8 +53,10 @@ export default class ShortcodeListAdapter {
|
|||||||
unsubscribeSubscriptions() {
|
unsubscribeSubscriptions() {
|
||||||
this.shortcodeCreated?.unsubscribe()
|
this.shortcodeCreated?.unsubscribe()
|
||||||
this.shortcodeRevoked?.unsubscribe()
|
this.shortcodeRevoked?.unsubscribe()
|
||||||
|
this.shortcodeUpdated?.unsubscribe()
|
||||||
this.shortcodeCreatedSub?.unsubscribe()
|
this.shortcodeCreatedSub?.unsubscribe()
|
||||||
this.shortcodeRevokedSub?.unsubscribe()
|
this.shortcodeRevokedSub?.unsubscribe()
|
||||||
|
this.shortcodeUpdatedSub?.unsubscribe()
|
||||||
}
|
}
|
||||||
|
|
||||||
initialize() {
|
initialize() {
|
||||||
@@ -137,6 +144,14 @@ export default class ShortcodeListAdapter {
|
|||||||
this.shortcodes$.next(newShortcode)
|
this.shortcodes$.next(newShortcode)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private updateSharedRequest(shortcode: Shortcode) {
|
||||||
|
const newShortcode = this.shortcodes$.value.map((oldShortcode) =>
|
||||||
|
oldShortcode.id === shortcode.id ? shortcode : oldShortcode
|
||||||
|
)
|
||||||
|
|
||||||
|
this.shortcodes$.next(newShortcode)
|
||||||
|
}
|
||||||
|
|
||||||
private registerSubscriptions() {
|
private registerSubscriptions() {
|
||||||
const [shortcodeCreated$, shortcodeCreatedSub] = runAuthOnlyGQLSubscription(
|
const [shortcodeCreated$, shortcodeCreatedSub] = runAuthOnlyGQLSubscription(
|
||||||
{
|
{
|
||||||
@@ -169,5 +184,21 @@ export default class ShortcodeListAdapter {
|
|||||||
|
|
||||||
this.deleteSharedRequest(result.right.myShortcodesRevoked.id)
|
this.deleteSharedRequest(result.right.myShortcodesRevoked.id)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const [shortcodeUpdated$, shortcodeUpdatedSub] = runAuthOnlyGQLSubscription(
|
||||||
|
{
|
||||||
|
query: ShortcodeUpdatedDocument,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
this.shortcodeUpdatedSub = shortcodeUpdatedSub
|
||||||
|
this.shortcodeUpdated = shortcodeUpdated$.subscribe((result) => {
|
||||||
|
if (E.isLeft(result)) {
|
||||||
|
console.error(result.left)
|
||||||
|
throw new Error(`Shortcode Update Error ${result.left}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
this.updateSharedRequest(result.right.myShortcodesUpdated)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,107 @@
|
|||||||
<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"
|
||||||
|
:properties="properties"
|
||||||
|
:shared-request-i-d="sharedRequestID"
|
||||||
|
/>
|
||||||
</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 properties = ref([])
|
||||||
|
|
||||||
|
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")
|
||||||
|
}
|
||||||
|
properties.value = parsedProperties.options
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
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>
|
||||||
|
|||||||
@@ -75,6 +75,7 @@ const emit = defineEmits<{
|
|||||||
@apply w-4;
|
@apply w-4;
|
||||||
@apply mr-2;
|
@apply mr-2;
|
||||||
@apply transition;
|
@apply transition;
|
||||||
|
@apply empty:mr-0;
|
||||||
content: "";
|
content: "";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user