chore: merge hoppscotch/staging into self-hosted/main
This commit is contained in:
15
packages/hoppscotch-common/src/components.d.ts
vendored
15
packages/hoppscotch-common/src/components.d.ts
vendored
@@ -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']
|
||||||
|
|||||||
@@ -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()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user