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", "name": "hoppscotch-backend",
"version": "2023.4.3", "version": "2023.4.1",
"description": "", "description": "",
"author": "", "author": "",
"private": true, "private": true,

View File

@@ -1,7 +1,7 @@
{ {
"name": "@hoppscotch/common", "name": "@hoppscotch/common",
"private": true, "private": true,
"version": "2023.4.3", "version": "2023.4.1",
"scripts": { "scripts": {
"dev": "pnpm exec npm-run-all -p -l dev:*", "dev": "pnpm exec npm-run-all -p -l dev:*",
"dev:vite": "vite", "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'] EnvironmentsMy: typeof import('./components/environments/my/index.vue')['default']
EnvironmentsMyDetails: typeof import('./components/environments/my/Details.vue')['default'] EnvironmentsMyDetails: typeof import('./components/environments/my/Details.vue')['default']
EnvironmentsMyEnvironment: typeof import('./components/environments/my/Environment.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'] EnvironmentsTeams: typeof import('./components/environments/teams/index.vue')['default']
EnvironmentsTeamsDetails: typeof import('./components/environments/teams/Details.vue')['default'] EnvironmentsTeamsDetails: typeof import('./components/environments/teams/Details.vue')['default']
EnvironmentsTeamsEnvironment: typeof import('./components/environments/teams/Environment.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'] IconLucideListEnd: typeof import('~icons/lucide/list-end')['default']
IconLucideMinus: typeof import('~icons/lucide/minus')['default'] IconLucideMinus: typeof import('~icons/lucide/minus')['default']
IconLucideSearch: typeof import('~icons/lucide/search')['default'] IconLucideSearch: typeof import('~icons/lucide/search')['default']
IconLucideUser: typeof import('~icons/lucide/user')['default']
IconLucideUsers: typeof import('~icons/lucide/users')['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']
@@ -141,6 +141,7 @@ declare module '@vue/runtime-core' {
LensesRenderersRawLensRenderer: typeof import('./components/lenses/renderers/RawLensRenderer.vue')['default'] LensesRenderersRawLensRenderer: typeof import('./components/lenses/renderers/RawLensRenderer.vue')['default']
LensesRenderersXMLLensRenderer: typeof import('./components/lenses/renderers/XMLLensRenderer.vue')['default'] LensesRenderersXMLLensRenderer: typeof import('./components/lenses/renderers/XMLLensRenderer.vue')['default']
LensesResponseBodyRenderer: typeof import('./components/lenses/ResponseBodyRenderer.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'] ProfileShortcode: typeof import('./components/profile/Shortcode.vue')['default']
ProfileShortcodes: typeof import('./components/profile/Shortcodes.vue')['default'] ProfileShortcodes: typeof import('./components/profile/Shortcodes.vue')['default']
ProfileUserDelete: typeof import('./components/profile/UserDelete.vue')['default'] ProfileUserDelete: typeof import('./components/profile/UserDelete.vue')['default']

View File

@@ -136,11 +136,11 @@ const requestName = ref(
) )
watch( watch(
() => [currentActiveTab.value, gqlRequestName.value], () => [currentActiveTab.value.document.request.name, gqlRequestName.value],
() => { () => {
if (props.mode === "rest") { if (props.mode === "rest")
requestName.value = currentActiveTab.value?.document.request.name ?? "" requestName.value = currentActiveTab.value.document.request.name
} else requestName.value = gqlRequestName.value 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" class="sticky top-0 z-10 flex flex-col flex-shrink-0 overflow-x-auto bg-primary"
> >
<WorkspaceCurrent :section="t('tab.environments')" /> <WorkspaceCurrent :section="t('tab.environments')" />
<EnvironmentsSelector <tippy
:environment-type="environmentType.type" v-if="environmentType.type === 'my-environments'"
:my-environments="myEnvironments" interactive
:team-env-loading="loading" trigger="click"
:team-environment-list="teamEnvironmentList" theme="popover"
:is-adapter-error="adapterError !== null" :on-shown="() => tippyActions!.focus()"
:error-message="adapterError ? getErrorMessage(adapterError) : ''" >
:is-team-selected="environmentType.selectedTeam !== undefined" <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 <EnvironmentsMyEnvironment
environment-index="Global" environment-index="Global"
:environment="globalEnvironment" :environment="globalEnvironment"
@@ -53,6 +191,8 @@ import {
} from "~/newstore/environments" } from "~/newstore/environments"
import TeamEnvironmentAdapter from "~/helpers/teams/TeamEnvironmentAdapter" import TeamEnvironmentAdapter from "~/helpers/teams/TeamEnvironmentAdapter"
import { GQLError } from "~/helpers/backend/GQLClient" import { GQLError } from "~/helpers/backend/GQLClient"
import IconCheck from "~icons/lucide/check"
import { TippyComponent } from "vue-tippy"
import { defineActionHandler } from "~/helpers/actions" import { defineActionHandler } from "~/helpers/actions"
import { workspaceStatus$ } from "~/newstore/workspace" import { workspaceStatus$ } from "~/newstore/workspace"
import TeamListAdapter from "~/helpers/teams/TeamListAdapter" import TeamListAdapter from "~/helpers/teams/TeamListAdapter"
@@ -121,7 +261,7 @@ const switchToMyEnvironments = () => {
adapter.changeTeamID(undefined) adapter.changeTeamID(undefined)
} }
const updateSelectedTeam = (newSelectedTeam: SelectedTeam | undefined) => { const updateSelectedTeam = (newSelectedTeam: SelectedTeam) => {
if (newSelectedTeam) { if (newSelectedTeam) {
environmentType.value.selectedTeam = newSelectedTeam environmentType.value.selectedTeam = newSelectedTeam
REMEMBERED_TEAM_ID.value = newSelectedTeam.id 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 // 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 // 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 // If there is no teamID, switch to my environment
watch(workspace, (newWorkspace) => { watch(
if (newWorkspace.type === "personal") { () => workspace.value.teamID,
(teamID) => {
if (!teamID) {
switchToMyEnvironments() switchToMyEnvironments()
setSelectedEnvironmentIndex({ } else if (teamID) {
type: "NO_ENV_SELECTED", const team = myTeams.value?.find((t) => t.id === teamID)
}) if (team) {
} else if (newWorkspace.type === "team") {
const team = myTeams.value?.find((t) => t.id === newWorkspace.teamID)
updateSelectedTeam(team) updateSelectedTeam(team)
if (selectedEnvironmentIndex.value.type !== "MY_ENV") {
setSelectedEnvironmentIndex({
type: "NO_ENV_SELECTED",
})
} }
} }
}) }
)
watch( watch(
() => currentUser.value, () => currentUser.value,
@@ -252,6 +388,33 @@ watch(
{ deep: true } { 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>) => { const getErrorMessage = (err: GQLError<string>) => {
if (err.type === "network_error") { if (err.type === "network_error") {
return t("error.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> </script>

View File

@@ -229,10 +229,11 @@ import { useI18n } from "@composables/i18n"
import { useSetting } from "@composables/settings" import { useSetting } from "@composables/settings"
import { useStreamSubscriber } from "@composables/stream" import { useStreamSubscriber } from "@composables/stream"
import { useToast } from "@composables/toast" import { useToast } from "@composables/toast"
import { completePageProgress, startPageProgress } from "@modules/loadingbar"
import { refAutoReset, useVModel } from "@vueuse/core" import { refAutoReset, useVModel } from "@vueuse/core"
import * as E from "fp-ts/Either" import * as E from "fp-ts/Either"
import { isLeft, isRight } from "fp-ts/lib/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 { defineActionHandler } from "~/helpers/actions"
import { runMutation } from "~/helpers/backend/GQLClient" import { runMutation } from "~/helpers/backend/GQLClient"
import { UpdateRequestDocument } from "~/helpers/backend/graphql" import { UpdateRequestDocument } from "~/helpers/backend/graphql"
@@ -310,6 +311,39 @@ const clearAll = ref<any | null>(null)
const copyRequestAction = ref<any | null>(null) const copyRequestAction = ref<any | null>(null)
const saveRequestAction = 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 () => { const newSendRequest = async () => {
if (newEndpoint.value === "" || /^\s+$/.test(newEndpoint.value)) { if (newEndpoint.value === "" || /^\s+$/.test(newEndpoint.value)) {
toast.error(`${t("empty.endpoint")}`) toast.error(`${t("empty.endpoint")}`)
@@ -540,10 +574,6 @@ const saveRequest = () => {
} }
} }
onBeforeUnmount(() => {
if (loading.value) cancelRequest()
})
defineActionHandler("request.send-cancel", () => { defineActionHandler("request.send-cancel", () => {
if (!loading.value) newSendRequest() if (!loading.value) newSendRequest()
else cancelRequest() else cancelRequest()

View File

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

View File

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

View File

@@ -38,7 +38,6 @@ import {
baseHighlightStyle, baseHighlightStyle,
} from "@helpers/editor/themes/baseTheme" } from "@helpers/editor/themes/baseTheme"
import { HoppEnvironmentPlugin } from "@helpers/editor/extensions/HoppEnvironment" import { HoppEnvironmentPlugin } from "@helpers/editor/extensions/HoppEnvironment"
import { platform } from "~/platform"
// TODO: Migrate from legacy mode // TODO: Migrate from legacy mode
type ExtendedEditorConfig = { type ExtendedEditorConfig = {
@@ -268,7 +267,6 @@ export function useCodemirror(
onMounted(() => { onMounted(() => {
if (el.value) { if (el.value) {
if (!view.value) initView(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({ const dispatchers = defineDispatchers({
setSelectedEnvironmentIndex( setSelectedEnvironmentIndex(
store: EnvironmentStore, _: EnvironmentStore,
{ {
selectedEnvironmentIndex, selectedEnvironmentIndex,
}: { selectedEnvironmentIndex: SelectedEnvironmentIndex } }: { selectedEnvironmentIndex: SelectedEnvironmentIndex }
) { ) {
if (selectedEnvironmentIndex.type === "MY_ENV") {
if (store.environments[selectedEnvironmentIndex.index]) {
return { return {
selectedEnvironmentIndex, selectedEnvironmentIndex,
} }
} else {
return {
selectedEnvironmentIndex: {
type: "NO_ENV_SELECTED",
},
}
}
} else {
return {
selectedEnvironmentIndex,
}
}
}, },
appendEnvironments( appendEnvironments(
{ environments }: EnvironmentStore, { environments }: EnvironmentStore,
@@ -339,8 +325,7 @@ export const selectedEnvironmentIndex$ = environmentsStore.subject$.pipe(
distinctUntilChanged() distinctUntilChanged()
) )
export const currentEnvironment$: Observable<Environment | undefined> = export const currentEnvironment$ = environmentsStore.subject$.pipe(
environmentsStore.subject$.pipe(
map(({ environments, selectedEnvironmentIndex }) => { map(({ environments, selectedEnvironmentIndex }) => {
if (selectedEnvironmentIndex.type === "NO_ENV_SELECTED") { if (selectedEnvironmentIndex.type === "NO_ENV_SELECTED") {
const env: Environment = { const env: Environment = {
@@ -354,7 +339,7 @@ export const currentEnvironment$: Observable<Environment | undefined> =
return selectedEnvironmentIndex.environment return selectedEnvironmentIndex.environment
} }
}) })
) )
export type AggregateEnvironment = { export type AggregateEnvironment = {
key: string key: string
@@ -373,7 +358,7 @@ export const aggregateEnvs$: Observable<AggregateEnvironment[]> = combineLatest(
map(([selectedEnv, globalVars]) => { map(([selectedEnv, globalVars]) => {
const results: AggregateEnvironment[] = [] const results: AggregateEnvironment[] = []
selectedEnv?.variables.forEach(({ key, value }) => selectedEnv.variables.forEach(({ key, value }) =>
results.push({ key, value, sourceEnv: selectedEnv.name }) results.push({ key, value, sourceEnv: selectedEnv.name })
) )
globalVars.forEach(({ key, value }) => globalVars.forEach(({ key, value }) =>

View File

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

View File

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

View File

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

View File

@@ -1,14 +1,14 @@
<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 <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 <div
class="flex flex-1 flex-shrink-0 w-0 overflow-x-auto" class="flex flex-1 flex-shrink-0 w-0 overflow-x-auto"
ref="scrollContainer" ref="scrollContainer"
> >
<div <div
class="flex justify-between divide-x divide-dividerLight" class="flex justify-between divide-x divide-divider"
@wheel.prevent="scroll" @wheel.prevent="scroll"
> >
<div class="flex"> <div class="flex">
@@ -23,8 +23,7 @@
<template #item="{ element: [tabID, tabMeta] }"> <template #item="{ element: [tabID, tabMeta] }">
<button <button
:key="`removable-tab-${tabID}`" :key="`removable-tab-${tabID}`"
:id="`removable-tab-${tabID}`" class="tab group px-2"
class="px-2 tab group"
:class="[{ active: modelValue === tabID }]" :class="[{ active: modelValue === tabID }]"
:aria-label="tabMeta.label || ''" :aria-label="tabMeta.label || ''"
role="button" role="button"
@@ -40,14 +39,14 @@
<div <div
v-if="!tabMeta.tabhead" v-if="!tabMeta.tabhead"
class="w-full px-2 text-left truncate" class="truncate w-full text-left px-2"
> >
<span class="truncate"> <span class="truncate">
{{ tabMeta.label }} {{ tabMeta.label }}
</span> </span>
</div> </div>
<div v-else class="w-full text-left truncate"> <div v-else class="truncate w-full text-left">
<component :is="tabMeta.tabhead" /> <component :is="tabMeta.tabhead" />
</div> </div>
@@ -73,7 +72,7 @@
}, },
'close', 'close',
]" ]"
class="rounded !p-0.25" class="!p-0.25 rounded"
@click.stop="emit('removeTab', tabID)" @click.stop="emit('removeTab', tabID)"
/> />
</button> </button>
@@ -81,33 +80,33 @@
</draggable> </draggable>
</div> </div>
<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 <span
v-if="canAddNewTab" 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 <HoppButtonSecondary
v-tippy="{ theme: 'tooltip' }" v-tippy="{ theme: 'tooltip' }"
:title="newText ?? t?.('action.new') ?? 'New'" :title="newText ?? t?.('action.new') ?? 'New'"
:icon="IconPlus" :icon="IconPlus"
class="rounded create-new-tab !text-secondaryDark !p-1" class="rounded !text-secondaryDark !p-1"
filled filled
@click="addTab" @click="addTab"
/> />
</span> </span>
</slot>
</div> </div>
</div> </div>
</div> </div>
<slot name="actions" />
<input <input
type="range" type="range"
min="1" min="1"
:max="MAX_SCROLL_VALUE" :max="MAX_SCROLL_VALUE"
v-model="thumbPosition" v-model="thumbPosition"
class="absolute bottom-0 left-0 hidden slider" class="slider absolute bottom-0 hidden left-0"
:class="{ :class="{
'!block': scrollThumb.show, '!block': scrollThumb.show,
}" }"
@@ -132,15 +131,7 @@ import { pipe } from "fp-ts/function"
import { not } from "fp-ts/Predicate" import { not } from "fp-ts/Predicate"
import * as A from "fp-ts/Array" import * as A from "fp-ts/Array"
import * as O from "fp-ts/Option" import * as O from "fp-ts/Option"
import { import { ref, ComputedRef, computed, provide, inject, watch } from "vue"
ref,
ComputedRef,
computed,
provide,
inject,
watch,
nextTick,
} from "vue"
import { useElementSize } from "@vueuse/core" import { useElementSize } from "@vueuse/core"
import type { Slot } from "vue" import type { Slot } from "vue"
import draggable from "vuedraggable-es" import draggable from "vuedraggable-es"
@@ -195,10 +186,9 @@ const throwError = (message: string): never => {
throw new Error(message) throw new Error(message)
} }
const TAB_WIDTH = 184
const tabEntries = ref<Array<[string, TabMeta]>>([]) const tabEntries = ref<Array<[string, TabMeta]>>([])
const tabStyles = computed(() => ({ const tabStyles = computed(() => ({
maxWidth: `${tabEntries.value.length * TAB_WIDTH}px`, maxWidth: `${tabEntries.value.length * 184}px`,
width: "100%", width: "100%",
minWidth: "0px", minWidth: "0px",
// transition: "max-width 0.2s", // transition: "max-width 0.2s",
@@ -302,49 +292,6 @@ watch(thumbPosition, (newVal) => {
const maxScroll = scrollWidth - clientWidth const maxScroll = scrollWidth - clientWidth
scrollContainer.value!.scrollLeft = maxScroll * (newVal / MAX_SCROLL_VALUE) 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> </script>
<style scoped lang="scss"> <style scoped lang="scss">
@@ -389,13 +336,6 @@ watch(
@apply text-secondaryDark; @apply text-secondaryDark;
@apply bg-primary; @apply bg-primary;
@apply before: bg-accent; @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 { .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-height: 4px;
.slider { .slider {