Compare commits

..

6 Commits

Author SHA1 Message Date
Mir Arif Hasan
aab52a1fdb fix: url of the magic link 2023-05-09 15:55:30 +05:30
Mir Arif Hasan
00fcc78f85 fix: returning response from authCookieHandler (#3025) 2023-05-09 15:55:01 +05:30
Anwarul Islam
81e090bbba feat: picture component moved to hoppscotch-ui (#3032) 2023-05-09 00:32:54 +05:30
Anwarul Islam
87ba02053b Fix issue with disappearing tab when opening request tabs with long text in body/script (#3030)
Co-authored-by: Liyas Thomas <liyascthomas@gmail.com>
2023-05-09 00:30:27 +05:30
Akash K
fb08147c66 fix: update the hoppscotch-sh-admin magic link route to match hoppscotch-app (#3029) 2023-05-03 23:12:50 +05:30
Nivedin
d129676cd6 fix: pane layout broken when wrap line is off (#3027)
Co-authored-by: Liyas Thomas <liyascthomas@gmail.com>
2023-05-03 20:39:22 +05:30
18 changed files with 286 additions and 346 deletions

View File

@@ -1,6 +1,6 @@
{
"name": "hoppscotch-backend",
"version": "2023.4.3",
"version": "2023.4.1",
"description": "",
"author": "",
"private": true,

View File

@@ -1,7 +1,7 @@
{
"name": "@hoppscotch/common",
"private": true,
"version": "2023.4.3",
"version": "2023.4.1",
"scripts": {
"dev": "pnpm exec npm-run-all -p -l dev:*",
"dev:vite": "vite",

Binary file not shown.

Before

Width:  |  Height:  |  Size: 666 KiB

After

Width:  |  Height:  |  Size: 595 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 358 KiB

After

Width:  |  Height:  |  Size: 331 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 382 KiB

After

Width:  |  Height:  |  Size: 352 KiB

View File

@@ -57,7 +57,6 @@ declare module '@vue/runtime-core' {
EnvironmentsMy: typeof import('./components/environments/my/index.vue')['default']
EnvironmentsMyDetails: typeof import('./components/environments/my/Details.vue')['default']
EnvironmentsMyEnvironment: typeof import('./components/environments/my/Environment.vue')['default']
EnvironmentsSelector: typeof import('./components/environments/Selector.vue')['default']
EnvironmentsTeams: typeof import('./components/environments/teams/index.vue')['default']
EnvironmentsTeamsDetails: typeof import('./components/environments/teams/Details.vue')['default']
EnvironmentsTeamsEnvironment: typeof import('./components/environments/teams/Environment.vue')['default']
@@ -131,6 +130,7 @@ declare module '@vue/runtime-core' {
IconLucideListEnd: typeof import('~icons/lucide/list-end')['default']
IconLucideMinus: typeof import('~icons/lucide/minus')['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']
LensesHeadersRendererEntry: typeof import('./components/lenses/HeadersRendererEntry.vue')['default']
@@ -141,6 +141,7 @@ declare module '@vue/runtime-core' {
LensesRenderersRawLensRenderer: typeof import('./components/lenses/renderers/RawLensRenderer.vue')['default']
LensesRenderersXMLLensRenderer: typeof import('./components/lenses/renderers/XMLLensRenderer.vue')['default']
LensesResponseBodyRenderer: typeof import('./components/lenses/ResponseBodyRenderer.vue')['default']
ProfilePicture: typeof import('./components/profile/Picture.vue')['default']
ProfileShortcode: typeof import('./components/profile/Shortcode.vue')['default']
ProfileShortcodes: typeof import('./components/profile/Shortcodes.vue')['default']
ProfileUserDelete: typeof import('./components/profile/UserDelete.vue')['default']

View File

@@ -136,11 +136,11 @@ const requestName = ref(
)
watch(
() => [currentActiveTab.value, gqlRequestName.value],
() => [currentActiveTab.value.document.request.name, gqlRequestName.value],
() => {
if (props.mode === "rest") {
requestName.value = currentActiveTab.value?.document.request.name ?? ""
} else requestName.value = gqlRequestName.value
if (props.mode === "rest")
requestName.value = currentActiveTab.value.document.request.name
else requestName.value = gqlRequestName.value
}
)

View File

@@ -1,173 +0,0 @@
<template>
<tippy
interactive
trigger="click"
theme="popover"
:on-shown="() => tippyActions!.focus()"
>
<span
v-tippy="{ theme: 'tooltip' }"
:title="`${t('environment.select')}`"
class="bg-transparent border-b border-dividerLight select-wrapper"
>
<HoppButtonSecondary
v-if="selectedEnv.type !== 'NO_ENV_SELECTED'"
:label="selectedEnv.name"
class="flex-1 !justify-start pr-8 rounded-none"
/>
<HoppButtonSecondary
v-else
:label="`${t('environment.select')}`"
class="flex-1 !justify-start pr-8 rounded-none"
/>
</span>
<template #content="{ hide }">
<div
ref="tippyActions"
role="menu"
class="flex flex-col focus:outline-none"
tabindex="0"
@keyup.escape="hide()"
>
<HoppSmartItem
:label="`${t('environment.no_environment')}`"
:info-icon="
selectedEnvironmentIndex.type === 'NO_ENV_SELECTED'
? IconCheck
: undefined
"
:active-info-icon="
selectedEnvironmentIndex.type === 'NO_ENV_SELECTED'
"
@click="
() => {
selectedEnvironmentIndex = { type: 'NO_ENV_SELECTED' }
hide()
}
"
/>
<div v-if="environmentType === 'my-environments'" class="flex flex-col">
<hr v-if="myEnvironments.length > 0" />
<HoppSmartItem
v-for="(gen, index) in myEnvironments"
:key="`gen-${index}`"
:label="gen.name"
:info-icon="index === selectedEnv.index ? IconCheck : undefined"
:active-info-icon="index === selectedEnv.index"
@click="
() => {
selectedEnvironmentIndex = { type: 'MY_ENV', index: index }
hide()
}
"
/>
</div>
<div v-else class="flex flex-col">
<div
v-if="teamEnvLoading"
class="flex flex-col items-center justify-center p-4"
>
<HoppSmartSpinner class="my-4" />
<span class="text-secondaryLight">{{ t("state.loading") }}</span>
</div>
<hr v-if="teamEnvironmentList.length > 0" />
<div v-if="isTeamSelected" class="flex flex-col">
<HoppSmartItem
v-for="(gen, index) in teamEnvironmentList"
:key="`gen-team-${index}`"
:label="gen.environment.name"
:info-icon="
gen.id === selectedEnv.teamEnvID ? IconCheck : undefined
"
:active-info-icon="gen.id === selectedEnv.teamEnvID"
@click="
() => {
selectedEnvironmentIndex = {
type: 'TEAM_ENV',
teamEnvID: gen.id,
teamID: gen.teamID,
environment: gen.environment,
}
hide()
}
"
/>
</div>
<div
v-if="!teamEnvLoading && isAdapterError"
class="flex flex-col items-center py-4"
>
<icon-lucide-help-circle class="mb-4 svg-icons" />
{{ errorMessage }}
</div>
</div>
</div>
</template>
</tippy>
</template>
<script lang="ts" setup>
import { computed, ref } from "vue"
import IconCheck from "~icons/lucide/check"
import { TippyComponent } from "vue-tippy"
import { useI18n } from "~/composables/i18n"
import { GQLError } from "~/helpers/backend/GQLClient"
import { Environment } from "@hoppscotch/data"
import { TeamEnvironment } from "~/helpers/teams/TeamEnvironment"
import { useStream } from "~/composables/stream"
import {
selectedEnvironmentIndex$,
setSelectedEnvironmentIndex,
} from "~/newstore/environments"
const t = useI18n()
type EnvironmentType = "my-environments" | "team-environments"
const props = defineProps<{
environmentType: EnvironmentType
myEnvironments: Environment[]
teamEnvironmentList: TeamEnvironment[]
teamEnvLoading: boolean
isAdapterError: boolean
errorMessage: GQLError<string>
isTeamSelected: boolean
}>()
const selectedEnvironmentIndex = useStream(
selectedEnvironmentIndex$,
{ type: "NO_ENV_SELECTED" },
setSelectedEnvironmentIndex
)
const selectedEnv = computed(() => {
if (selectedEnvironmentIndex.value.type === "MY_ENV") {
return {
type: "MY_ENV",
index: selectedEnvironmentIndex.value.index,
name: props.myEnvironments[selectedEnvironmentIndex.value.index].name,
}
} else if (selectedEnvironmentIndex.value.type === "TEAM_ENV") {
const teamEnv = props.teamEnvironmentList.find(
(env) =>
env.id ===
(selectedEnvironmentIndex.value.type === "TEAM_ENV" &&
selectedEnvironmentIndex.value.teamEnvID)
)
if (teamEnv) {
return {
type: "TEAM_ENV",
name: teamEnv.environment.name,
teamEnvID: selectedEnvironmentIndex.value.teamEnvID,
}
} else {
return { type: "NO_ENV_SELECTED" }
}
} else {
return { type: "NO_ENV_SELECTED" }
}
})
// Template refs
const tippyActions = ref<TippyComponent | null>(null)
</script>

View File

@@ -4,15 +4,153 @@
class="sticky top-0 z-10 flex flex-col flex-shrink-0 overflow-x-auto bg-primary"
>
<WorkspaceCurrent :section="t('tab.environments')" />
<EnvironmentsSelector
:environment-type="environmentType.type"
:my-environments="myEnvironments"
:team-env-loading="loading"
:team-environment-list="teamEnvironmentList"
:is-adapter-error="adapterError !== null"
:error-message="adapterError ? getErrorMessage(adapterError) : ''"
:is-team-selected="environmentType.selectedTeam !== undefined"
<tippy
v-if="environmentType.type === 'my-environments'"
interactive
trigger="click"
theme="popover"
:on-shown="() => tippyActions!.focus()"
>
<span
v-tippy="{ theme: 'tooltip' }"
:title="`${t('environment.select')}`"
class="bg-transparent border-b border-dividerLight select-wrapper"
>
<HoppButtonSecondary
v-if="
selectedEnv.type === 'MY_ENV' && selectedEnv.index !== undefined
"
:label="myEnvironments[selectedEnv.index].name"
class="flex-1 !justify-start pr-8 rounded-none"
/>
<HoppButtonSecondary
v-else
:label="`${t('environment.select')}`"
class="flex-1 !justify-start pr-8 rounded-none"
/>
</span>
<template #content="{ hide }">
<div
ref="tippyActions"
role="menu"
class="flex flex-col focus:outline-none"
tabindex="0"
@keyup.escape="hide()"
>
<HoppSmartItem
:label="`${t('environment.no_environment')}`"
:info-icon="
selectedEnvironmentIndex.type !== 'MY_ENV'
? IconCheck
: undefined
"
:active-info-icon="selectedEnvironmentIndex.type !== 'MY_ENV'"
@click="
() => {
selectedEnvironmentIndex = { type: 'NO_ENV_SELECTED' }
hide()
}
"
/>
<hr v-if="myEnvironments.length > 0" />
<HoppSmartItem
v-for="(gen, index) in myEnvironments"
:key="`gen-${index}`"
:label="gen.name"
:info-icon="index === selectedEnv.index ? IconCheck : undefined"
:active-info-icon="index === selectedEnv.index"
@click="
() => {
selectedEnvironmentIndex = { type: 'MY_ENV', index: index }
hide()
}
"
/>
</div>
</template>
</tippy>
<tippy v-else interactive trigger="click" theme="popover">
<span
v-tippy="{ theme: 'tooltip' }"
:title="`${t('environment.select')}`"
class="bg-transparent border-b border-dividerLight select-wrapper"
>
<HoppButtonSecondary
v-if="selectedEnv.name"
:label="selectedEnv.name"
class="flex-1 !justify-start pr-8 rounded-none"
/>
<HoppButtonSecondary
v-else
:label="`${t('environment.select')}`"
class="flex-1 !justify-start pr-8 rounded-none"
/>
</span>
<template #content="{ hide }">
<div
class="flex flex-col"
role="menu"
tabindex="0"
@keyup.escape="hide()"
>
<HoppSmartItem
:label="`${t('environment.no_environment')}`"
:info-icon="
selectedEnvironmentIndex.type !== 'TEAM_ENV'
? IconCheck
: undefined
"
:active-info-icon="selectedEnvironmentIndex.type !== 'TEAM_ENV'"
@click="
() => {
selectedEnvironmentIndex = { type: 'NO_ENV_SELECTED' }
hide()
}
"
/>
<div
v-if="loading"
class="flex flex-col items-center justify-center p-4"
>
<HoppSmartSpinner class="my-4" />
<span class="text-secondaryLight">{{ t("state.loading") }}</span>
</div>
<hr v-if="teamEnvironmentList.length > 0" />
<div
v-if="environmentType.selectedTeam !== undefined"
class="flex flex-col"
>
<HoppSmartItem
v-for="(gen, index) in teamEnvironmentList"
:key="`gen-team-${index}`"
:label="gen.environment.name"
:info-icon="
gen.id === selectedEnv.teamEnvID ? IconCheck : undefined
"
:active-info-icon="gen.id === selectedEnv.teamEnvID"
@click="
() => {
selectedEnvironmentIndex = {
type: 'TEAM_ENV',
teamEnvID: gen.id,
teamID: gen.teamID,
environment: gen.environment,
}
hide()
}
"
/>
</div>
<div
v-if="!loading && adapterError"
class="flex flex-col items-center py-4"
>
<icon-lucide-help-circle class="mb-4 svg-icons" />
{{ getErrorMessage(adapterError) }}
</div>
</div>
</template>
</tippy>
<EnvironmentsMyEnvironment
environment-index="Global"
:environment="globalEnvironment"
@@ -53,6 +191,8 @@ import {
} from "~/newstore/environments"
import TeamEnvironmentAdapter from "~/helpers/teams/TeamEnvironmentAdapter"
import { GQLError } from "~/helpers/backend/GQLClient"
import IconCheck from "~icons/lucide/check"
import { TippyComponent } from "vue-tippy"
import { defineActionHandler } from "~/helpers/actions"
import { workspaceStatus$ } from "~/newstore/workspace"
import TeamListAdapter from "~/helpers/teams/TeamListAdapter"
@@ -121,7 +261,7 @@ const switchToMyEnvironments = () => {
adapter.changeTeamID(undefined)
}
const updateSelectedTeam = (newSelectedTeam: SelectedTeam | undefined) => {
const updateSelectedTeam = (newSelectedTeam: SelectedTeam) => {
if (newSelectedTeam) {
environmentType.value.selectedTeam = newSelectedTeam
REMEMBERED_TEAM_ID.value = newSelectedTeam.id
@@ -150,23 +290,19 @@ const workspace = useReadonlyStream(workspaceStatus$, { type: "personal" })
// Used to switch environment type and team when user switch workspace in the global workspace switcher
// Check if there is a teamID in the workspace, if yes, switch to team environment and select the team
// If there is no teamID, switch to my environment
watch(workspace, (newWorkspace) => {
if (newWorkspace.type === "personal") {
watch(
() => workspace.value.teamID,
(teamID) => {
if (!teamID) {
switchToMyEnvironments()
setSelectedEnvironmentIndex({
type: "NO_ENV_SELECTED",
})
} else if (newWorkspace.type === "team") {
const team = myTeams.value?.find((t) => t.id === newWorkspace.teamID)
} else if (teamID) {
const team = myTeams.value?.find((t) => t.id === teamID)
if (team) {
updateSelectedTeam(team)
if (selectedEnvironmentIndex.value.type !== "MY_ENV") {
setSelectedEnvironmentIndex({
type: "NO_ENV_SELECTED",
})
}
}
})
}
)
watch(
() => currentUser.value,
@@ -252,6 +388,33 @@ watch(
{ deep: true }
)
const selectedEnv = computed(() => {
if (selectedEnvironmentIndex.value.type === "MY_ENV") {
return {
type: "MY_ENV",
index: selectedEnvironmentIndex.value.index,
}
} else if (selectedEnvironmentIndex.value.type === "TEAM_ENV") {
const teamEnv = teamEnvironmentList.value.find(
(env) =>
env.id ===
(selectedEnvironmentIndex.value.type === "TEAM_ENV" &&
selectedEnvironmentIndex.value.teamEnvID)
)
if (teamEnv) {
return {
type: "TEAM_ENV",
name: teamEnv.environment.name,
teamEnvID: selectedEnvironmentIndex.value.teamEnvID,
}
} else {
return { type: "NO_ENV_SELECTED" }
}
} else {
return { type: "NO_ENV_SELECTED" }
}
})
const getErrorMessage = (err: GQLError<string>) => {
if (err.type === "network_error") {
return t("error.network_error")
@@ -264,4 +427,7 @@ const getErrorMessage = (err: GQLError<string>) => {
}
}
}
// Template refs
const tippyActions = ref<TippyComponent | null>(null)
</script>

View File

@@ -229,10 +229,11 @@ import { useI18n } from "@composables/i18n"
import { useSetting } from "@composables/settings"
import { useStreamSubscriber } from "@composables/stream"
import { useToast } from "@composables/toast"
import { completePageProgress, startPageProgress } from "@modules/loadingbar"
import { refAutoReset, useVModel } from "@vueuse/core"
import * as E from "fp-ts/Either"
import { isLeft, isRight } from "fp-ts/lib/Either"
import { computed, onBeforeUnmount, ref } from "vue"
import { computed, ref, watch } from "vue"
import { defineActionHandler } from "~/helpers/actions"
import { runMutation } from "~/helpers/backend/GQLClient"
import { UpdateRequestDocument } from "~/helpers/backend/graphql"
@@ -310,6 +311,39 @@ const clearAll = ref<any | null>(null)
const copyRequestAction = ref<any | null>(null)
const saveRequestAction = ref<any | null>(null)
// Update Nuxt Loading bar
watch(loading, () => {
if (loading.value) {
startPageProgress()
} else {
completePageProgress()
}
})
// TODO: make this oAuthURL() work
// function oAuthURL() {
// const auth = useReadonlyStream(props.request.auth$, {
// authType: "none",
// authActive: true,
// })
// const oauth2Token = pluckRef(auth as Ref<HoppRESTAuthOAuth2>, "token")
// onBeforeMount(async () => {
// try {
// const tokenInfo = await oauthRedirect()
// if (Object.prototype.hasOwnProperty.call(tokenInfo, "access_token")) {
// if (typeof tokenInfo === "object") {
// oauth2Token.value = tokenInfo.access_token
// }
// }
// // eslint-disable-next-line no-empty
// } catch (_) {}
// })
// }
const newSendRequest = async () => {
if (newEndpoint.value === "" || /^\s+$/.test(newEndpoint.value)) {
toast.error(`${t("empty.endpoint")}`)
@@ -540,10 +574,6 @@ const saveRequest = () => {
}
}
onBeforeUnmount(() => {
if (loading.value) cancelRequest()
})
defineActionHandler("request.send-cancel", () => {
if (!loading.value) newSendRequest()
else cancelRequest()

View File

@@ -10,7 +10,8 @@
</template>
<script setup lang="ts">
import { computed, ref } from "vue"
import { computed, ref, watch } from "vue"
import { startPageProgress, completePageProgress } from "@modules/loadingbar"
import { HoppRESTTab } from "~/helpers/rest/tab"
import { useVModel } from "@vueuse/core"
@@ -33,4 +34,9 @@ const hasResponse = computed(
)
const loading = computed(() => tab.value.response?.type === "loading")
watch(loading, (isLoading) => {
if (isLoading) startPageProgress()
else completePageProgress()
})
</script>

View File

@@ -34,7 +34,6 @@ import { inputTheme } from "~/helpers/editor/themes/baseTheme"
import { HoppReactiveEnvPlugin } from "~/helpers/editor/extensions/HoppEnvironment"
import { useReadonlyStream } from "@composables/stream"
import { AggregateEnvironment, aggregateEnvs$ } from "~/newstore/environments"
import { platform } from "~/platform"
const props = withDefaults(
defineProps<{
@@ -220,7 +219,6 @@ onMounted(() => {
if (editor.value) {
if (!view.value) initView(editor.value)
if (props.selectTextOnMount) triggerTextSelection()
platform.ui?.onCodemirrorInstanceMount?.(editor.value)
}
})

View File

@@ -38,7 +38,6 @@ import {
baseHighlightStyle,
} from "@helpers/editor/themes/baseTheme"
import { HoppEnvironmentPlugin } from "@helpers/editor/extensions/HoppEnvironment"
import { platform } from "~/platform"
// TODO: Migrate from legacy mode
type ExtendedEditorConfig = {
@@ -268,7 +267,6 @@ export function useCodemirror(
onMounted(() => {
if (el.value) {
if (!view.value) initView(el.value)
platform.ui?.onCodemirrorInstanceMount?.(el.value)
}
})

View File

@@ -37,28 +37,14 @@ type EnvironmentStore = typeof defaultEnvironmentsState
const dispatchers = defineDispatchers({
setSelectedEnvironmentIndex(
store: EnvironmentStore,
_: EnvironmentStore,
{
selectedEnvironmentIndex,
}: { selectedEnvironmentIndex: SelectedEnvironmentIndex }
) {
if (selectedEnvironmentIndex.type === "MY_ENV") {
if (store.environments[selectedEnvironmentIndex.index]) {
return {
selectedEnvironmentIndex,
}
} else {
return {
selectedEnvironmentIndex: {
type: "NO_ENV_SELECTED",
},
}
}
} else {
return {
selectedEnvironmentIndex,
}
}
},
appendEnvironments(
{ environments }: EnvironmentStore,
@@ -339,8 +325,7 @@ export const selectedEnvironmentIndex$ = environmentsStore.subject$.pipe(
distinctUntilChanged()
)
export const currentEnvironment$: Observable<Environment | undefined> =
environmentsStore.subject$.pipe(
export const currentEnvironment$ = environmentsStore.subject$.pipe(
map(({ environments, selectedEnvironmentIndex }) => {
if (selectedEnvironmentIndex.type === "NO_ENV_SELECTED") {
const env: Environment = {
@@ -354,7 +339,7 @@ export const currentEnvironment$: Observable<Environment | undefined> =
return selectedEnvironmentIndex.environment
}
})
)
)
export type AggregateEnvironment = {
key: string
@@ -373,7 +358,7 @@ export const aggregateEnvs$: Observable<AggregateEnvironment[]> = combineLatest(
map(([selectedEnv, globalVars]) => {
const results: AggregateEnvironment[] = []
selectedEnv?.variables.forEach(({ key, value }) =>
selectedEnv.variables.forEach(({ key, value }) =>
results.push({ key, value, sourceEnv: selectedEnv.name })
)
globalVars.forEach(({ key, value }) =>

View File

@@ -5,5 +5,4 @@ export type UIPlatformDef = {
paddingTop?: Ref<string>
paddingLeft?: Ref<string>
}
onCodemirrorInstanceMount?: (element: HTMLElement) => void
}

View File

@@ -1,7 +1,7 @@
{
"name": "@hoppscotch/selfhost-web",
"private": true,
"version": "2023.4.3",
"version": "2023.4.1",
"type": "module",
"scripts": {
"dev:vite": "vite",

View File

@@ -1,7 +1,7 @@
{
"name": "hoppscotch-sh-admin",
"private": true,
"version": "2023.4.3",
"version": "2023.4.1",
"type": "module",
"scripts": {
"dev": "pnpm exec npm-run-all -p -l dev:*",

View File

@@ -1,14 +1,14 @@
<template>
<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 divide-x divide-dividerLight bg-primaryLight tabs group-tabs"
class="relative sticky top-0 z-10 flex-shrink-0 overflow-x-auto tabs bg-primaryLight group-tabs"
>
<div
class="flex flex-1 flex-shrink-0 w-0 overflow-x-auto"
ref="scrollContainer"
>
<div
class="flex justify-between divide-x divide-dividerLight"
class="flex justify-between divide-x divide-divider"
@wheel.prevent="scroll"
>
<div class="flex">
@@ -23,8 +23,7 @@
<template #item="{ element: [tabID, tabMeta] }">
<button
:key="`removable-tab-${tabID}`"
:id="`removable-tab-${tabID}`"
class="px-2 tab group"
class="tab group px-2"
:class="[{ active: modelValue === tabID }]"
:aria-label="tabMeta.label || ''"
role="button"
@@ -40,14 +39,14 @@
<div
v-if="!tabMeta.tabhead"
class="w-full px-2 text-left truncate"
class="truncate w-full text-left px-2"
>
<span class="truncate">
{{ tabMeta.label }}
</span>
</div>
<div v-else class="w-full text-left truncate">
<div v-else class="truncate w-full text-left">
<component :is="tabMeta.tabhead" />
</div>
@@ -73,7 +72,7 @@
},
'close',
]"
class="rounded !p-0.25"
class="!p-0.25 rounded"
@click.stop="emit('removeTab', tabID)"
/>
</button>
@@ -81,33 +80,33 @@
</draggable>
</div>
<div
class="sticky right-0 flex items-center justify-center flex-shrink-0 overflow-x-auto z-14"
class="sticky right-0 flex items-center justify-center flex-shrink-0 overflow-x-auto z-8"
>
<slot name="actions">
<span
v-if="canAddNewTab"
class="flex items-center justify-center h-full px-3 bg-primaryLight z-8"
class="flex items-center justify-center px-3 bg-primaryLight z-8 h-full"
>
<HoppButtonSecondary
v-tippy="{ theme: 'tooltip' }"
:title="newText ?? t?.('action.new') ?? 'New'"
:icon="IconPlus"
class="rounded create-new-tab !text-secondaryDark !p-1"
class="rounded !text-secondaryDark !p-1"
filled
@click="addTab"
/>
</span>
</slot>
</div>
</div>
</div>
<slot name="actions" />
<input
type="range"
min="1"
:max="MAX_SCROLL_VALUE"
v-model="thumbPosition"
class="absolute bottom-0 left-0 hidden slider"
class="slider absolute bottom-0 hidden left-0"
:class="{
'!block': scrollThumb.show,
}"
@@ -132,15 +131,7 @@ import { pipe } from "fp-ts/function"
import { not } from "fp-ts/Predicate"
import * as A from "fp-ts/Array"
import * as O from "fp-ts/Option"
import {
ref,
ComputedRef,
computed,
provide,
inject,
watch,
nextTick,
} from "vue"
import { ref, ComputedRef, computed, provide, inject, watch } from "vue"
import { useElementSize } from "@vueuse/core"
import type { Slot } from "vue"
import draggable from "vuedraggable-es"
@@ -195,10 +186,9 @@ const throwError = (message: string): never => {
throw new Error(message)
}
const TAB_WIDTH = 184
const tabEntries = ref<Array<[string, TabMeta]>>([])
const tabStyles = computed(() => ({
maxWidth: `${tabEntries.value.length * TAB_WIDTH}px`,
maxWidth: `${tabEntries.value.length * 184}px`,
width: "100%",
minWidth: "0px",
// transition: "max-width 0.2s",
@@ -302,49 +292,6 @@ watch(thumbPosition, (newVal) => {
const maxScroll = scrollWidth - clientWidth
scrollContainer.value!.scrollLeft = maxScroll * (newVal / MAX_SCROLL_VALUE)
})
/*
* Watch TabID changes
* and scroll to the tab if it's not visible
*/
watch(
() => props.modelValue,
(tabID) => {
nextTick(() => {
const element = document.getElementById(`removable-tab-${tabID}`)
const changeThumbPosition: IntersectionObserverCallback = (
entries,
observer
) => {
entries.forEach((entry) => {
if (entry.target === element && entry.intersectionRatio >= 1.0) {
// Element is visible now. Stop listening for intersection changes
observer.disconnect()
// We still need setTimeout here because the element might not be fully in position yet
setTimeout(() => {
const { scrollWidth, clientWidth, scrollLeft } =
scrollContainer.value!
const maxScroll = scrollWidth - clientWidth
thumbPosition.value = (scrollLeft / maxScroll) * MAX_SCROLL_VALUE
}, 300)
}
})
}
let observer = new IntersectionObserver(changeThumbPosition, {
root: null,
rootMargin: "0px",
threshold: 1.0,
})
observer.observe(element!)
element?.scrollIntoView({ behavior: "smooth", inline: "center" })
})
},
{ immediate: true }
)
</script>
<style scoped lang="scss">
@@ -389,13 +336,6 @@ watch(
@apply text-secondaryDark;
@apply bg-primary;
@apply before: bg-accent;
@apply after: absolute;
@apply after: inset-x-0;
@apply after: bottom-0;
@apply after: bg-primary;
@apply after: z-12;
@apply after: h-0.25;
@apply after: content-DEFAULT;
}
.close {
@@ -408,16 +348,6 @@ watch(
}
}
.create-new-tab {
@apply after: absolute;
@apply after: inset-x-0;
@apply after: bottom-0;
@apply after: bg-dividerLight;
@apply after: z-14;
@apply after: h-0.25;
@apply after: content-DEFAULT;
}
$slider-height: 4px;
.slider {