Compare commits
2 Commits
2023.4.3
...
feat/env-s
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
37153b6401 | ||
|
|
f3f9891017 |
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "hoppscotch-backend",
|
||||
"version": "2023.4.3",
|
||||
"version": "2023.4.1",
|
||||
"description": "",
|
||||
"author": "",
|
||||
"private": true,
|
||||
|
||||
@@ -232,7 +232,7 @@ export class AuthService {
|
||||
template: 'code-your-own',
|
||||
variables: {
|
||||
inviteeEmail: email,
|
||||
magicLink: `${url}/enter?token=${generatedTokens.token}`,
|
||||
magicLink: `${url}/magic-link?token=${generatedTokens.token}`,
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
@@ -63,7 +63,7 @@ export const authCookieHandler = (
|
||||
});
|
||||
|
||||
if (!redirect) {
|
||||
return res.status(HttpStatus.OK).send();
|
||||
res.status(HttpStatus.OK).send();
|
||||
}
|
||||
|
||||
// check to see if redirectUrl is a whitelisted url
|
||||
@@ -72,7 +72,7 @@ export const authCookieHandler = (
|
||||
// if it is not redirect by default to REDIRECT_URL
|
||||
redirectUrl = process.env.REDIRECT_URL;
|
||||
|
||||
return res.status(HttpStatus.OK).redirect(redirectUrl);
|
||||
res.status(HttpStatus.OK).redirect(redirectUrl);
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
@@ -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 |
@@ -85,7 +85,6 @@ declare module '@vue/runtime-core' {
|
||||
HoppSmartItem: typeof import('@hoppscotch/ui')['HoppSmartItem']
|
||||
HoppSmartLink: typeof import('@hoppscotch/ui')['HoppSmartLink']
|
||||
HoppSmartModal: typeof import('@hoppscotch/ui')['HoppSmartModal']
|
||||
HoppSmartPicture: typeof import('@hoppscotch/ui')['HoppSmartPicture']
|
||||
HoppSmartProgressRing: typeof import('@hoppscotch/ui')['HoppSmartProgressRing']
|
||||
HoppSmartRadioGroup: typeof import('@hoppscotch/ui')['HoppSmartRadioGroup']
|
||||
HoppSmartSlideOver: typeof import('@hoppscotch/ui')['HoppSmartSlideOver']
|
||||
@@ -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']
|
||||
@@ -164,7 +165,6 @@ declare module '@vue/runtime-core' {
|
||||
SmartItem: typeof import('./../../hoppscotch-ui/src/components/smart/Item.vue')['default']
|
||||
SmartLink: typeof import('./../../hoppscotch-ui/src/components/smart/Link.vue')['default']
|
||||
SmartModal: typeof import('./../../hoppscotch-ui/src/components/smart/Modal.vue')['default']
|
||||
SmartPicture: typeof import('./../../hoppscotch-ui/src/components/smart/Picture.vue')['default']
|
||||
SmartProgressRing: typeof import('./../../hoppscotch-ui/src/components/smart/ProgressRing.vue')['default']
|
||||
SmartRadio: typeof import('./../../hoppscotch-ui/src/components/smart/Radio.vue')['default']
|
||||
SmartRadioGroup: typeof import('./../../hoppscotch-ui/src/components/smart/RadioGroup.vue')['default']
|
||||
|
||||
@@ -124,7 +124,7 @@
|
||||
theme="popover"
|
||||
:on-shown="() => tippyActions.focus()"
|
||||
>
|
||||
<HoppSmartPicture
|
||||
<ProfilePicture
|
||||
v-if="currentUser.photoURL"
|
||||
v-tippy="{
|
||||
theme: 'tooltip',
|
||||
@@ -144,7 +144,7 @@
|
||||
network.isOnline ? 'bg-green-500' : 'bg-red-500'
|
||||
"
|
||||
/>
|
||||
<HoppSmartPicture
|
||||
<ProfilePicture
|
||||
v-else
|
||||
v-tippy="{ theme: 'tooltip' }"
|
||||
:title="
|
||||
|
||||
@@ -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
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@@ -36,78 +36,94 @@
|
||||
? IconCheck
|
||||
: undefined
|
||||
"
|
||||
class="my-2"
|
||||
:active-info-icon="
|
||||
selectedEnvironmentIndex.type === 'NO_ENV_SELECTED'
|
||||
"
|
||||
@click="
|
||||
() => {
|
||||
selectedEnvironmentIndex = { type: 'NO_ENV_SELECTED' }
|
||||
setSelectedEnvironmentIndex({ 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"
|
||||
<HoppSmartTabs
|
||||
v-model="selectedEnvTab"
|
||||
styles="sticky overflow-x-auto flex-shrink-0 bg-primary z-10 top-0"
|
||||
render-inactive-tabs
|
||||
>
|
||||
<HoppSmartTab
|
||||
:id="'my-environments'"
|
||||
:label="`${t('environment.my_environments')}`"
|
||||
>
|
||||
<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">
|
||||
<hr v-if="myEnvironments.length > 0" />
|
||||
<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"
|
||||
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: 'TEAM_ENV',
|
||||
teamEnvID: gen.id,
|
||||
teamID: gen.teamID,
|
||||
environment: gen.environment,
|
||||
}
|
||||
setSelectedEnvironmentIndex({
|
||||
type: 'MY_ENV',
|
||||
index,
|
||||
})
|
||||
|
||||
hide()
|
||||
}
|
||||
"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
v-if="!teamEnvLoading && isAdapterError"
|
||||
class="flex flex-col items-center py-4"
|
||||
</HoppSmartTab>
|
||||
<HoppSmartTab
|
||||
:id="'team-environments'"
|
||||
:label="`${t('environment.team_environments')}`"
|
||||
:disabled="
|
||||
!isTeamSelected ||
|
||||
teamEnvLoading ||
|
||||
teamEnvironmentList.length === 0 ||
|
||||
environmentType === 'my-environments'
|
||||
"
|
||||
>
|
||||
<icon-lucide-help-circle class="mb-4 svg-icons" />
|
||||
{{ errorMessage }}
|
||||
</div>
|
||||
</div>
|
||||
<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="
|
||||
() => {
|
||||
setSelectedEnvironmentIndex({
|
||||
type: 'TEAM_ENV',
|
||||
teamEnvID: gen.id,
|
||||
teamID: gen.teamID,
|
||||
environment: gen.environment,
|
||||
})
|
||||
hide()
|
||||
}
|
||||
"
|
||||
/>
|
||||
</div>
|
||||
</HoppSmartTab>
|
||||
</HoppSmartTabs>
|
||||
</div>
|
||||
</template>
|
||||
</tippy>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { computed, ref } from "vue"
|
||||
import { computed, ref, watch } from "vue"
|
||||
import IconCheck from "~icons/lucide/check"
|
||||
import { TippyComponent } from "vue-tippy"
|
||||
import { useI18n } from "~/composables/i18n"
|
||||
@@ -134,13 +150,27 @@ const props = defineProps<{
|
||||
isTeamSelected: boolean
|
||||
}>()
|
||||
|
||||
const selectedEnvTab = ref<EnvironmentType>("my-environments")
|
||||
|
||||
const selectedEnvironmentIndex = useStream(
|
||||
selectedEnvironmentIndex$,
|
||||
{ type: "NO_ENV_SELECTED" },
|
||||
setSelectedEnvironmentIndex
|
||||
)
|
||||
|
||||
watch(
|
||||
() => props.environmentType,
|
||||
(newVal) => {
|
||||
if (newVal === "my-environments") {
|
||||
selectedEnvTab.value = "my-environments"
|
||||
} else {
|
||||
selectedEnvTab.value = "team-environments"
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
const selectedEnv = computed(() => {
|
||||
console.log("selectedEnvironmentIndex", selectedEnvironmentIndex.value)
|
||||
if (selectedEnvironmentIndex.value.type === "MY_ENV") {
|
||||
return {
|
||||
type: "MY_ENV",
|
||||
|
||||
@@ -109,7 +109,9 @@ watch(
|
||||
teamListFetched.value = true
|
||||
if (REMEMBERED_TEAM_ID.value && currentUser.value) {
|
||||
const team = newTeams.find((t) => t.id === REMEMBERED_TEAM_ID.value)
|
||||
if (team) updateSelectedTeam(team)
|
||||
if (team) {
|
||||
updateSelectedTeam(team)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -121,7 +123,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 +152,30 @@ 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") {
|
||||
switchToMyEnvironments()
|
||||
setSelectedEnvironmentIndex({
|
||||
type: "NO_ENV_SELECTED",
|
||||
})
|
||||
} else if (newWorkspace.type === "team") {
|
||||
const team = myTeams.value?.find((t) => t.id === newWorkspace.teamID)
|
||||
updateSelectedTeam(team)
|
||||
watch(
|
||||
() => workspace.value.teamID,
|
||||
(teamID) => {
|
||||
if (!teamID) {
|
||||
switchToMyEnvironments()
|
||||
|
||||
if (selectedEnvironmentIndex.value.type !== "MY_ENV") {
|
||||
setSelectedEnvironmentIndex({
|
||||
type: "NO_ENV_SELECTED",
|
||||
})
|
||||
// If the user selected a team environment, and then switch to personal workspace,
|
||||
// we need to reset the selected environment
|
||||
if (selectedEnvironmentIndex.value.type === "TEAM_ENV") {
|
||||
setSelectedEnvironmentIndex({
|
||||
type: "NO_ENV_SELECTED",
|
||||
})
|
||||
}
|
||||
} else if (teamID) {
|
||||
const team = myTeams.value?.find((t) => t.id === teamID)
|
||||
if (team) {
|
||||
updateSelectedTeam(team)
|
||||
setSelectedEnvironmentIndex({
|
||||
type: "NO_ENV_SELECTED",
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
)
|
||||
|
||||
watch(
|
||||
() => currentUser.value,
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -0,0 +1,87 @@
|
||||
<template>
|
||||
<button
|
||||
tabindex="0"
|
||||
class="relative flex items-center justify-center overflow-visible cursor-pointer focus:outline-none focus-visible:ring-2 focus-visible:ring-primaryDark"
|
||||
:class="[`rounded-${rounded}`, `w-${size} h-${size}`]"
|
||||
>
|
||||
<img
|
||||
v-if="url"
|
||||
class="absolute object-cover object-center transition bg-primaryDark"
|
||||
:class="[`rounded-${rounded}`, `w-${size} h-${size}`]"
|
||||
:src="url"
|
||||
:alt="alt"
|
||||
loading="lazy"
|
||||
/>
|
||||
<div
|
||||
v-else
|
||||
class="absolute flex items-center justify-center object-cover object-center transition bg-primaryDark text-accentContrast"
|
||||
:class="[`rounded-${rounded}`, `w-${size} h-${size}`]"
|
||||
:style="`background-color: ${initial ? toHex(initial) : '#480000'}`"
|
||||
>
|
||||
<template v-if="initial && initial.charAt(0).toUpperCase()">
|
||||
{{ initial.charAt(0).toUpperCase() }}
|
||||
</template>
|
||||
|
||||
<icon-lucide-user v-else></icon-lucide-user>
|
||||
</div>
|
||||
<span
|
||||
v-if="indicator"
|
||||
class="border-primary border-2 h-2.5 -top-0.5 -right-0.5 w-2.5 absolute"
|
||||
:class="[`rounded-${rounded}`, indicatorStyles]"
|
||||
></span>
|
||||
<!-- w-5 h-5 rounded-lg -->
|
||||
</button>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent, PropType } from "vue"
|
||||
|
||||
export default defineComponent({
|
||||
props: {
|
||||
url: {
|
||||
type: String,
|
||||
default: "",
|
||||
},
|
||||
alt: {
|
||||
type: String,
|
||||
default: "Profile picture",
|
||||
},
|
||||
indicator: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
indicatorStyles: {
|
||||
type: String,
|
||||
default: "bg-green-500",
|
||||
},
|
||||
rounded: {
|
||||
type: String,
|
||||
default: "full",
|
||||
},
|
||||
size: {
|
||||
type: String,
|
||||
default: "5",
|
||||
},
|
||||
initial: {
|
||||
type: String as PropType<string | undefined | null>,
|
||||
default: "",
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
toHex(initial: string) {
|
||||
let hash = 0
|
||||
if (initial.length === 0) return hash
|
||||
for (let i = 0; i < initial.length; i++) {
|
||||
hash = initial.charCodeAt(i) + ((hash << 5) - hash)
|
||||
hash = hash & hash
|
||||
}
|
||||
let color = "#"
|
||||
for (let i = 0; i < 3; i++) {
|
||||
const value = (hash >> (i * 8)) & 255
|
||||
color += `00${value.toString(16)}`.slice(-2)
|
||||
}
|
||||
return color
|
||||
},
|
||||
},
|
||||
})
|
||||
</script>
|
||||
@@ -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)
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
:key="`member-${index}`"
|
||||
class="inline-flex"
|
||||
>
|
||||
<HoppSmartPicture
|
||||
<ProfilePicture
|
||||
v-if="member.user.photoURL"
|
||||
v-tippy="{ theme: 'tooltip' }"
|
||||
:url="member.user.photoURL"
|
||||
@@ -14,7 +14,7 @@
|
||||
class="ring-primary ring-2"
|
||||
@click="handleClick()"
|
||||
/>
|
||||
<HoppSmartPicture
|
||||
<ProfilePicture
|
||||
v-else
|
||||
v-tippy="{ theme: 'tooltip' }"
|
||||
:title="getUserName(member)"
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
@@ -24,10 +24,7 @@
|
||||
>
|
||||
<Pane class="flex flex-1 !overflow-auto">
|
||||
<main class="flex flex-1 w-full" role="main">
|
||||
<RouterView
|
||||
v-slot="{ Component }"
|
||||
class="flex flex-1 min-w-0"
|
||||
>
|
||||
<RouterView v-slot="{ Component }" class="flex flex-1">
|
||||
<Transition name="fade" mode="out-in" appear>
|
||||
<component :is="Component" />
|
||||
</Transition>
|
||||
|
||||
@@ -37,27 +37,13 @@ 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,
|
||||
}
|
||||
return {
|
||||
selectedEnvironmentIndex,
|
||||
}
|
||||
},
|
||||
appendEnvironments(
|
||||
@@ -339,22 +325,21 @@ export const selectedEnvironmentIndex$ = environmentsStore.subject$.pipe(
|
||||
distinctUntilChanged()
|
||||
)
|
||||
|
||||
export const currentEnvironment$: Observable<Environment | undefined> =
|
||||
environmentsStore.subject$.pipe(
|
||||
map(({ environments, selectedEnvironmentIndex }) => {
|
||||
if (selectedEnvironmentIndex.type === "NO_ENV_SELECTED") {
|
||||
const env: Environment = {
|
||||
name: "No environment",
|
||||
variables: [],
|
||||
}
|
||||
return env
|
||||
} else if (selectedEnvironmentIndex.type === "MY_ENV") {
|
||||
return environments[selectedEnvironmentIndex.index]
|
||||
} else {
|
||||
return selectedEnvironmentIndex.environment
|
||||
export const currentEnvironment$ = environmentsStore.subject$.pipe(
|
||||
map(({ environments, selectedEnvironmentIndex }) => {
|
||||
if (selectedEnvironmentIndex.type === "NO_ENV_SELECTED") {
|
||||
const env: Environment = {
|
||||
name: "No environment",
|
||||
variables: [],
|
||||
}
|
||||
})
|
||||
)
|
||||
return env
|
||||
} else if (selectedEnvironmentIndex.type === "MY_ENV") {
|
||||
return environments[selectedEnvironmentIndex.index]
|
||||
} else {
|
||||
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 }) =>
|
||||
|
||||
@@ -34,7 +34,7 @@
|
||||
></div>
|
||||
<div class="flex flex-col justify-between px-4 space-y-8 md:flex-row">
|
||||
<div class="flex items-end">
|
||||
<HoppSmartPicture
|
||||
<ProfilePicture
|
||||
v-if="currentUser.photoURL"
|
||||
:url="currentUser.photoURL"
|
||||
:alt="
|
||||
@@ -44,7 +44,7 @@
|
||||
size="16"
|
||||
rounded="lg"
|
||||
/>
|
||||
<HoppSmartPicture
|
||||
<ProfilePicture
|
||||
v-else
|
||||
:initial="currentUser.displayName || currentUser.email"
|
||||
rounded="lg"
|
||||
|
||||
@@ -5,5 +5,4 @@ export type UIPlatformDef = {
|
||||
paddingTop?: Ref<string>
|
||||
paddingLeft?: Ref<string>
|
||||
}
|
||||
onCodemirrorInstanceMount?: (element: HTMLElement) => void
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@hoppscotch/selfhost-web",
|
||||
"private": true,
|
||||
"version": "2023.4.3",
|
||||
"version": "2023.4.1",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev:vite": "vite",
|
||||
|
||||
@@ -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:*",
|
||||
|
||||
@@ -28,7 +28,7 @@
|
||||
arrow
|
||||
:on-shown="() => tippyActions!.focus()"
|
||||
>
|
||||
<HoppSmartPicture
|
||||
<ProfilePicture
|
||||
v-if="currentUser.photoURL"
|
||||
v-tippy="{
|
||||
theme: 'tooltip',
|
||||
@@ -37,7 +37,7 @@
|
||||
:alt="currentUser.displayName ?? 'No Name'"
|
||||
:title="currentUser.displayName ?? currentUser.email ?? 'No Name'"
|
||||
/>
|
||||
<HoppSmartPicture
|
||||
<ProfilePicture
|
||||
v-else
|
||||
v-tippy="{ theme: 'tooltip' }"
|
||||
:title="currentUser.displayName ?? currentUser.email ?? 'No Name'"
|
||||
|
||||
@@ -33,40 +33,55 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
withDefaults(
|
||||
defineProps<{
|
||||
url: string
|
||||
alt: string
|
||||
indicator: boolean
|
||||
indicatorStyles: string
|
||||
rounded: string
|
||||
size: string
|
||||
initial: string | undefined | null
|
||||
}>(),
|
||||
{
|
||||
url: "",
|
||||
alt: "Profile picture",
|
||||
indicator: false,
|
||||
indicatorStyles: "bg-green-500",
|
||||
rounded: "full",
|
||||
size: "5",
|
||||
initial: "",
|
||||
}
|
||||
)
|
||||
<script lang="ts">
|
||||
import { defineComponent, PropType } from 'vue';
|
||||
|
||||
const toHex = (initial: string) => {
|
||||
let hash = 0
|
||||
if (initial.length === 0) return hash
|
||||
for (let i = 0; i < initial.length; i++) {
|
||||
hash = initial.charCodeAt(i) + ((hash << 5) - hash)
|
||||
hash = hash & hash
|
||||
}
|
||||
let color = "#"
|
||||
for (let i = 0; i < 3; i++) {
|
||||
const value = (hash >> (i * 8)) & 255
|
||||
color += `00${value.toString(16)}`.slice(-2)
|
||||
}
|
||||
return color
|
||||
}
|
||||
export default defineComponent({
|
||||
props: {
|
||||
url: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
alt: {
|
||||
type: String,
|
||||
default: 'Profile picture',
|
||||
},
|
||||
indicator: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
indicatorStyles: {
|
||||
type: String,
|
||||
default: 'bg-green-500',
|
||||
},
|
||||
rounded: {
|
||||
type: String,
|
||||
default: 'full',
|
||||
},
|
||||
size: {
|
||||
type: String,
|
||||
default: '5',
|
||||
},
|
||||
initial: {
|
||||
type: String as PropType<string | undefined | null>,
|
||||
default: '',
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
toHex(initial: string) {
|
||||
let hash = 0;
|
||||
if (initial.length === 0) return hash;
|
||||
for (let i = 0; i < initial.length; i++) {
|
||||
hash = initial.charCodeAt(i) + ((hash << 5) - hash);
|
||||
hash = hash & hash;
|
||||
}
|
||||
let color = '#';
|
||||
for (let i = 0; i < 3; i++) {
|
||||
const value = (hash >> (i * 8)) & 255;
|
||||
color += `00${value.toString(16)}`.slice(-2);
|
||||
}
|
||||
return color;
|
||||
},
|
||||
},
|
||||
});
|
||||
</script>
|
||||
@@ -6,7 +6,7 @@ const isAdmin = () => {
|
||||
return user ? user.isAdmin : false;
|
||||
};
|
||||
|
||||
const GUEST_ROUTES = ['index', 'enter'];
|
||||
const GUEST_ROUTES = ['index', 'magic-link'];
|
||||
|
||||
const isGuestRoute = (to: unknown) => GUEST_ROUTES.includes(to as string);
|
||||
|
||||
|
||||
@@ -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"
|
||||
>
|
||||
<span
|
||||
v-if="canAddNewTab"
|
||||
class="flex items-center justify-center h-full px-3 bg-primaryLight z-8"
|
||||
>
|
||||
<HoppButtonSecondary
|
||||
v-tippy="{ theme: 'tooltip' }"
|
||||
:title="newText ?? t?.('action.new') ?? 'New'"
|
||||
:icon="IconPlus"
|
||||
class="rounded create-new-tab !text-secondaryDark !p-1"
|
||||
filled
|
||||
@click="addTab"
|
||||
/>
|
||||
</span>
|
||||
<slot name="actions">
|
||||
<span
|
||||
v-if="canAddNewTab"
|
||||
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 !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 {
|
||||
|
||||
@@ -18,4 +18,3 @@ export { default as HoppSmartTabs } from "./Tabs.vue"
|
||||
export { default as HoppSmartToggle } from "./Toggle.vue"
|
||||
export { default as HoppSmartWindow } from "./Window.vue"
|
||||
export { default as HoppSmartWindows } from "./Windows.vue"
|
||||
export { default as HoppSmartPicture } from "./Picture.vue"
|
||||
|
||||
Reference in New Issue
Block a user