chore: merge hoppscotch/staging into self-hosted/main

This commit is contained in:
Andrew Bastin
2023-04-05 21:49:59 +05:30
9 changed files with 273 additions and 142 deletions

View File

@@ -16,7 +16,6 @@ declare module '@vue/runtime-core' {
AppHeader: typeof import('./components/app/Header.vue')['default'] AppHeader: typeof import('./components/app/Header.vue')['default']
AppInterceptor: typeof import('./components/app/Interceptor.vue')['default'] AppInterceptor: typeof import('./components/app/Interceptor.vue')['default']
AppLogo: typeof import('./components/app/Logo.vue')['default'] AppLogo: typeof import('./components/app/Logo.vue')['default']
AppNavigation: typeof import('./components/app/Navigation.vue')['default']
AppOptions: typeof import('./components/app/Options.vue')['default'] AppOptions: typeof import('./components/app/Options.vue')['default']
AppPaneLayout: typeof import('./components/app/PaneLayout.vue')['default'] AppPaneLayout: typeof import('./components/app/PaneLayout.vue')['default']
AppPowerSearch: typeof import('./components/app/PowerSearch.vue')['default'] AppPowerSearch: typeof import('./components/app/PowerSearch.vue')['default']
@@ -99,6 +98,20 @@ declare module '@vue/runtime-core' {
HttpTestResultReport: typeof import('./components/http/TestResultReport.vue')['default'] HttpTestResultReport: typeof import('./components/http/TestResultReport.vue')['default']
HttpTests: typeof import('./components/http/Tests.vue')['default'] HttpTests: typeof import('./components/http/Tests.vue')['default']
HttpURLEncodedParams: typeof import('./components/http/URLEncodedParams.vue')['default'] HttpURLEncodedParams: typeof import('./components/http/URLEncodedParams.vue')['default']
IconLucideArrowLeft: typeof import('~icons/lucide/arrow-left')['default']
IconLucideBrush: typeof import('~icons/lucide/brush')['default']
IconLucideCheckCircle: typeof import('~icons/lucide/check-circle')['default']
IconLucideChevronRight: typeof import('~icons/lucide/chevron-right')['default']
IconLucideGlobe: typeof import('~icons/lucide/globe')['default']
IconLucideHelpCircle: typeof import('~icons/lucide/help-circle')['default']
IconLucideInbox: typeof import('~icons/lucide/inbox')['default']
IconLucideInfo: typeof import('~icons/lucide/info')['default']
IconLucideLayers: typeof import('~icons/lucide/layers')['default']
IconLucideMinus: typeof import('~icons/lucide/minus')['default']
IconLucideRss: typeof import('~icons/lucide/rss')['default']
IconLucideSearch: typeof import('~icons/lucide/search')['default']
IconLucideUser: typeof import('~icons/lucide/user')['default']
IconLucideUsers: typeof import('~icons/lucide/users')['default']
LensesHeadersRenderer: typeof import('./components/lenses/HeadersRenderer.vue')['default'] LensesHeadersRenderer: typeof import('./components/lenses/HeadersRenderer.vue')['default']
LensesHeadersRendererEntry: typeof import('./components/lenses/HeadersRendererEntry.vue')['default'] LensesHeadersRendererEntry: typeof import('./components/lenses/HeadersRendererEntry.vue')['default']
LensesRenderersHTMLLensRenderer: typeof import('./components/lenses/renderers/HTMLLensRenderer.vue')['default'] LensesRenderersHTMLLensRenderer: typeof import('./components/lenses/renderers/HTMLLensRenderer.vue')['default']

View File

@@ -16,7 +16,8 @@
<div <div
class="absolute bg-accent opacity-0 pointer-events-none inset-0 z-1 transition" class="absolute bg-accent opacity-0 pointer-events-none inset-0 z-1 transition"
:class="{ :class="{
'opacity-25': dragging && notSameDestination, 'opacity-25':
dragging && notSameDestination && notSameParentDestination,
}" }"
></div> ></div>
<div <div
@@ -308,7 +309,7 @@ const currentReorderingStatus = useReadonlyStream(currentReorderingStatus$, {
watch( watch(
() => dragging.value, () => dragging.value,
(val) => { (val) => {
if (val && notSameDestination.value) { if (val && notSameDestination.value && notSameParentDestination.value) {
emit("dragging", true) emit("dragging", true)
} else { } else {
emit("dragging", false) emit("dragging", false)
@@ -338,6 +339,10 @@ watch(
} }
) )
const notSameParentDestination = computed(() => {
return currentReorderingStatus.value.parentID !== props.id
})
const isRequestDragging = computed(() => { const isRequestDragging = computed(() => {
return currentReorderingStatus.value.type === "request" return currentReorderingStatus.value.type === "request"
}) })
@@ -392,7 +397,8 @@ const handleDragOver = (e: DragEvent) => {
e.offsetY > 18 && e.offsetY > 18 &&
notSameDestination.value && notSameDestination.value &&
!isRequestDragging.value && !isRequestDragging.value &&
isSameParent.value isSameParent.value &&
props.isLastItem
) { ) {
orderingLastItem.value = true orderingLastItem.value = true
dragging.value = false dragging.value = false
@@ -409,7 +415,7 @@ const handelDrop = (e: DragEvent) => {
} else if (orderingLastItem.value) { } else if (orderingLastItem.value) {
updateLastItemOrder(e) updateLastItemOrder(e)
} else { } else {
dropEvent(e) notSameParentDestination.value ? dropEvent(e) : e.stopPropagation()
} }
} }

View File

@@ -504,45 +504,41 @@ const emit = defineEmits<{
const refFilterCollection = toRef(props, "filteredCollections") const refFilterCollection = toRef(props, "filteredCollections")
const pathToIndex = computed(() => { const pathToIndex = (path: string) => {
return (path: string) => { const pathArr = path.split("/")
const pathArr = path.split("/") return pathArr[pathArr.length - 1]
return pathArr[pathArr.length - 1] }
}
})
const isSelected = computed(() => { const isSelected = ({
return ({ collectionIndex,
collectionIndex, folderPath,
folderPath, requestIndex,
requestIndex, }: {
}: { collectionIndex?: number | undefined
collectionIndex?: number | undefined folderPath?: string | undefined
folderPath?: string | undefined requestIndex?: number | undefined
requestIndex?: number | undefined }) => {
}) => { if (collectionIndex !== undefined) {
if (collectionIndex !== undefined) { return (
return ( props.picked &&
props.picked && props.picked.pickedType === "my-collection" &&
props.picked.pickedType === "my-collection" && props.picked.collectionIndex === collectionIndex
props.picked.collectionIndex === collectionIndex )
) } else if (requestIndex !== undefined && folderPath !== undefined) {
} else if (requestIndex !== undefined && folderPath !== undefined) { return (
return ( props.picked &&
props.picked && props.picked.pickedType === "my-request" &&
props.picked.pickedType === "my-request" && props.picked.folderPath === folderPath &&
props.picked.folderPath === folderPath && props.picked.requestIndex === requestIndex
props.picked.requestIndex === requestIndex )
) } else {
} else { return (
return ( props.picked &&
props.picked && props.picked.pickedType === "my-folder" &&
props.picked.pickedType === "my-folder" && props.picked.folderPath === folderPath
props.picked.folderPath === folderPath )
)
}
} }
}) }
const active = computed(() => currentActiveTab.value.document.saveContext) const active = computed(() => currentActiveTab.value.document.saveContext)
@@ -706,7 +702,10 @@ class MyCollectionsAdapter implements SmartTreeAdapter<MyCollectionNode> {
...item.folders.map((folder, index) => ({ ...item.folders.map((folder, index) => ({
id: `${id}/${index}`, id: `${id}/${index}`,
data: { data: {
isLastItem: index === item.folders.length - 1, isLastItem:
item.folders && item.folders.length > 1
? index === item.folders.length - 1
: false,
type: "folders", type: "folders",
data: { data: {
parentIndex: id, parentIndex: id,
@@ -717,7 +716,10 @@ class MyCollectionsAdapter implements SmartTreeAdapter<MyCollectionNode> {
...item.requests.map((requests, index) => ({ ...item.requests.map((requests, index) => ({
id: `${id}/${index}`, id: `${id}/${index}`,
data: { data: {
isLastItem: index === item.requests.length - 1, isLastItem:
item.requests && item.requests.length > 1
? index === item.requests.length - 1
: false,
type: "requests", type: "requests",
data: { data: {
parentIndex: id, parentIndex: id,

View File

@@ -517,54 +517,50 @@ const hasNoTeamAccess = computed(
props.collectionsType.selectedTeam.myRole === "VIEWER") props.collectionsType.selectedTeam.myRole === "VIEWER")
) )
const isSelected = computed(() => { const isSelected = ({
return ({ collectionID,
collectionID, folderID,
folderID, requestID,
requestID, }: {
}: { collectionID?: string | undefined
collectionID?: string | undefined folderID?: string | undefined
folderID?: string | undefined requestID?: string | undefined
requestID?: string | undefined }) => {
}) => { if (collectionID !== undefined) {
if (collectionID !== undefined) { return (
return ( props.picked &&
props.picked && props.picked.pickedType === "teams-collection" &&
props.picked.pickedType === "teams-collection" && props.picked.collectionID === collectionID
props.picked.collectionID === collectionID )
) } else if (requestID !== undefined) {
} else if (requestID !== undefined) { return (
return ( props.picked &&
props.picked && props.picked.pickedType === "teams-request" &&
props.picked.pickedType === "teams-request" && props.picked.requestID === requestID
props.picked.requestID === requestID )
) } else {
} else { return (
return ( props.picked &&
props.picked && props.picked.pickedType === "teams-folder" &&
props.picked.pickedType === "teams-folder" && props.picked.folderID === folderID
props.picked.folderID === folderID )
)
}
} }
}) }
const active = computed(() => currentActiveTab.value.document.saveContext) const active = computed(() => currentActiveTab.value.document.saveContext)
const isActiveRequest = computed(() => { const isActiveRequest = (requestID: string) => {
return (requestID: string) => { return pipe(
return pipe( active.value,
active.value, O.fromNullable,
O.fromNullable, O.filter(
O.filter( (active) =>
(active) => active.originLocation === "team-collection" &&
active.originLocation === "team-collection" && active.requestID === requestID
active.requestID === requestID ),
), O.isSome
O.isSome )
) }
}
})
const selectRequest = (data: { const selectRequest = (data: {
request: HoppRESTRequest request: HoppRESTRequest
@@ -580,7 +576,7 @@ const selectRequest = (data: {
emit("select-request", { emit("select-request", {
request: request, request: request,
requestIndex: requestIndex, requestIndex: requestIndex,
isActive: isActiveRequest.value(requestIndex), isActive: isActiveRequest(requestIndex),
}) })
} }
} }
@@ -749,7 +745,10 @@ class TeamCollectionsAdapter implements SmartTreeAdapter<TeamCollectionNode> {
? items.children.map((item, index) => ({ ? items.children.map((item, index) => ({
id: `${id}/${item.id}`, id: `${id}/${item.id}`,
data: { data: {
isLastItem: index === items.children.length - 1, isLastItem:
items.children && items.children.length > 1
? index === items.children.length - 1
: false,
type: "folders", type: "folders",
data: { data: {
parentIndex: parsedID, parentIndex: parsedID,
@@ -762,7 +761,10 @@ class TeamCollectionsAdapter implements SmartTreeAdapter<TeamCollectionNode> {
? items.requests.map((item, index) => ({ ? items.requests.map((item, index) => ({
id: `${id}/${item.id}`, id: `${id}/${item.id}`,
data: { data: {
isLastItem: index === items.requests.length - 1, isLastItem:
items.requests && items.requests.length > 1
? index === items.requests.length - 1
: false,
type: "requests", type: "requests",
data: { data: {
parentIndex: parsedID, parentIndex: parsedID,

View File

@@ -1395,6 +1395,27 @@ const checkIfCollectionIsAParentOfTheChildren = (
return false return false
} }
const isMoveToSameLocation = (
draggedItemPath: string,
destinationPath: string
) => {
const draggedItemPathArr = pathToIndex(draggedItemPath)
const destinationPathArr = pathToIndex(destinationPath)
if (draggedItemPathArr.length > 0) {
const draggedItemParentPathArr = draggedItemPathArr.slice(
0,
draggedItemPathArr.length - 1
)
if (isEqual(draggedItemParentPathArr, destinationPathArr)) {
return true
} else {
return false
}
}
}
/** /**
* This function is called when the user moves the collection * This function is called when the user moves the collection
* to a different collection or folder * to a different collection or folder
@@ -1419,6 +1440,13 @@ const dropCollection = (payload: {
return return
} }
//check if the collection is being moved to its own parent
if (
isMoveToSameLocation(collectionIndexDragged, destinationCollectionIndex)
) {
return
}
const parentFolder = collectionIndexDragged const parentFolder = collectionIndexDragged
.split("/") .split("/")
.slice(0, -1) .slice(0, -1)
@@ -1556,10 +1584,14 @@ const isSameSameParent = (
draggedItemIndex.length === 1 draggedItemIndex.length === 1
) { ) {
return draggedItemIndex[0] === destinationCollectionIndex return draggedItemIndex[0] === destinationCollectionIndex
} else if (destinationItemPath === null && draggedItemIndex.length !== 1) { } else if (
destinationItemPath === null &&
draggedItemIndex.length !== 1 &&
destinationCollectionIndex !== null
) {
const dragedItemParent = draggedItemIndex.slice(0, -1) const dragedItemParent = draggedItemIndex.slice(0, -1)
return dragedItemParent[0] === destinationCollectionIndex return dragedItemParent.join("/") === destinationCollectionIndex
} else { } else {
if (destinationItemPath === null) return false if (destinationItemPath === null) return false
const destinationItemIndex = pathToIndex(destinationItemPath) const destinationItemIndex = pathToIndex(destinationItemPath)

View File

@@ -16,19 +16,33 @@
:key="tab.id" :key="tab.id"
:label="tab.document.request.name" :label="tab.document.request.name"
:is-removable="tabs.length > 1" :is-removable="tabs.length > 1"
:close-visibility="'hover'"
> >
<template #tabhead> <template #tabhead>
<span <div
class="font-semibold truncate text-tiny w-10" v-tippy="{ theme: 'tooltip', delay: [500, 20] }"
:class="getMethodLabelColorClassOf(tab.document.request)" :title="tab.document.request.name"
class="truncate px-2"
> >
{{ tab.document.request.method }} <span
</span> class="font-semibold text-tiny"
<span class="text-green-600 mr-1" v-if="tab.document.isDirty"> :class="getMethodLabelColorClassOf(tab.document.request)"
>
</span> {{ tab.document.request.method }}
<span class="truncate flex-1"> </span>
{{ tab.document.request.name }} <span class="leading-8 px-2">
{{ tab.document.request.name }}
</span>
</div>
</template>
<template #suffix>
<span
class="text-green-600 text-[8px] group-hover:hidden w-4"
v-if="tab.document.isDirty"
>
<svg viewBox="0 0 24 24" width="1.2em" height="1.2em">
<circle cx="12" cy="12" r="10" fill="currentColor"></circle>
</svg>
</span> </span>
</template> </template>
<HttpRequestTab <HttpRequestTab

View File

@@ -21,23 +21,32 @@ import { TabMeta, TabProvider } from "./Windows.vue"
const slots = useSlots() const slots = useSlots()
const props = defineProps({ const props = withDefaults(
label: { type: String, default: null }, defineProps<{
info: { type: String, default: null }, label: string | null
id: { type: String, default: null, required: true }, info: string | null
isRemovable: { type: Boolean, default: true }, id: string
selected: { isRemovable: boolean
type: Boolean, closeVisibility: "hover" | "always" | "never"
default: false, selected: boolean
}, }>(),
}) {
label: null,
info: null,
isRemovable: true,
closeVisibility: "always",
selected: false,
}
)
const tabMeta = computed<TabMeta>(() => ({ const tabMeta = computed<TabMeta>(() => ({
info: props.info, info: props.info,
label: props.label, label: props.label,
isRemovable: props.isRemovable, isRemovable: props.isRemovable,
icon: slots.icon, icon: slots.icon,
tabhead: slots.tabhead suffix: slots.suffix,
tabhead: slots.tabhead,
closeVisibility: props.closeVisibility,
})) }))
const { const {

View File

@@ -1,43 +1,95 @@
<template> <template>
<div class="flex flex-col flex-1 h-auto overflow-y-hidden flex-nowrap"> <div class="flex flex-col flex-1 h-auto overflow-y-hidden flex-nowrap">
<div class="relative sticky top-0 z-10 flex-shrink-0 overflow-x-auto tabs bg-primaryLight"> <div
<div class="flex flex-1 flex-shrink-0 w-0 overflow-x-auto" ref="scrollContainer"> class="relative sticky top-0 z-10 flex-shrink-0 overflow-x-auto tabs bg-primaryLight"
<div class="flex justify-between divide-x divide-dividerLight" @wheel="scrollOnWindows"> >
<div
class="flex flex-1 flex-shrink-0 w-0 overflow-x-auto"
ref="scrollContainer"
>
<div
class="flex justify-between divide-x divide-dividerLight"
@wheel.prevent="scroll"
>
<div class="flex"> <div class="flex">
<draggable v-bind="dragOptions" :list="tabEntries" :style="tabStyles" :item-key="'window-'" <draggable
class="flex flex-shrink-0 overflow-x-auto transition divide-x divide-dividerLight" @sort="sortTabs"> v-bind="dragOptions"
:list="tabEntries"
:style="tabStyles"
:item-key="'window-'"
class="flex flex-shrink-0 overflow-x-auto transition divide-x divide-dividerLight"
@sort="sortTabs"
>
<template #item="{ element: [tabID, tabMeta] }"> <template #item="{ element: [tabID, tabMeta] }">
<button :key="`removable-tab-${tabID}`" class="tab" :class="[{ active: modelValue === tabID }]" <button
:aria-label="tabMeta.label || ''" role="button" @keyup.enter="selectTab(tabID)" :key="`removable-tab-${tabID}`"
@click="selectTab(tabID)"> class="tab group px-2"
:class="[{ active: modelValue === tabID }]"
:aria-label="tabMeta.label || ''"
role="button"
@keyup.enter="selectTab(tabID)"
@click="selectTab(tabID)"
>
<span
v-if="tabMeta.icon"
class="flex items-center justify-center cursor-pointer"
>
<component :is="tabMeta.icon" class="w-4 h-4 svg-icons" />
</span>
<div v-if="!tabMeta.tabhead" class="flex items-stretch truncate"> <div
<span v-if="tabMeta.icon" class="flex items-center justify-center mx-4 cursor-pointer"> v-if="!tabMeta.tabhead"
<component :is="tabMeta.icon" class="w-4 h-4 svg-icons" /> class="truncate w-full text-left px-2"
</span> >
<span class="truncate pl-4"> <span class="truncate">
{{ tabMeta.label }} {{ tabMeta.label }}
</span> </span>
</div> </div>
<div v-else class="truncate flex items-center justify-start mx-4"> <div v-else class="truncate w-full text-left">
<component :is="tabMeta.tabhead" /> <component :is="tabMeta.tabhead" />
</div> </div>
<HoppButtonSecondary v-tippy="{ theme: 'tooltip', delay: [500, 20] }" :icon="IconX" :style="{ <component v-if="tabMeta.suffix" :is="tabMeta.suffix" />
display: tabMeta.isRemovable ? 'flex' : 'none',
}" :title="closeText ?? t?.('action.close') ?? 'Close'" <HoppButtonSecondary
:class="[{ active: modelValue === tabID }, 'close']" class="mx-2 !p-0.5" v-if="tabMeta.isRemovable"
@click.stop="emit('removeTab', tabID)" /> v-tippy="{ theme: 'tooltip', delay: [500, 20] }"
:icon="IconX"
:title="closeText ?? t?.('action.close') ?? 'Close'"
:class="[
{ active: modelValue === tabID },
{
flex: tabMeta.closeVisibility === 'always',
'group-hover:flex hidden':
tabMeta.closeVisibility === 'hover',
hidden: tabMeta.closeVisibility === 'never',
},
'close',
]"
class="!p-0.5"
@click.stop="emit('removeTab', tabID)"
/>
</button> </button>
</template> </template>
</draggable> </draggable>
</div> </div>
<div class="sticky right-0 flex items-center justify-center flex-shrink-0 overflow-x-auto z-8"> <div
class="sticky right-0 flex items-center justify-center flex-shrink-0 overflow-x-auto z-8"
>
<slot name="actions"> <slot name="actions">
<span v-if="canAddNewTab" class="flex items-center justify-center px-2 py-1.5 bg-primaryLight z-8"> <span
<HoppButtonSecondary v-tippy="{ theme: 'tooltip' }" :title="newText ?? t?.('action.new') ?? 'New'" v-if="canAddNewTab"
:icon="IconPlus" class="rounded !p-1" filled @click="addTab" /> class="flex items-center justify-center px-2 py-1.5 bg-primaryLight z-8 h-full"
>
<HoppButtonSecondary
v-tippy="{ theme: 'tooltip' }"
:title="newText ?? t?.('action.new') ?? 'New'"
:icon="IconPlus"
class="rounded !p-1"
filled
@click="addTab"
/>
</span> </span>
</slot> </slot>
</div> </div>
@@ -66,9 +118,11 @@ import { HoppUIPluginOptions, HOPP_UI_OPTIONS } from "./../../index"
export type TabMeta = { export type TabMeta = {
label: string | null label: string | null
icon: Slot | undefined icon: Slot | undefined
suffix: Slot | undefined
tabhead: Slot | undefined tabhead: Slot | undefined
info: string | null info: string | null
isRemovable: boolean isRemovable: boolean
closeVisibility: "hover" | "always" | "never"
} }
export type TabProvider = { export type TabProvider = {
// Whether inactive tabs should remain rendered // Whether inactive tabs should remain rendered
@@ -187,12 +241,11 @@ const addTab = () => {
emit("addTab") emit("addTab")
} }
const scrollContainer = ref<HTMLElement|null>(null) const scrollContainer = ref<HTMLElement | null>(null)
const scrollOnWindows = (event: WheelEvent) => { const scroll = (e: WheelEvent) => {
event.preventDefault() scrollContainer.value!.scrollLeft += e.deltaY
if(scrollContainer.value) scrollContainer.value!.scrollLeft += e.deltaX
scrollContainer.value.scrollLeft += event.deltaY
} }
</script> </script>