feat: global workspace selector (#2922)
Co-authored-by: Liyas Thomas <liyascthomas@gmail.com>
This commit is contained in:
@@ -1,169 +0,0 @@
|
||||
<template>
|
||||
<div>
|
||||
<HoppSmartTabs
|
||||
:id="'environments_tab'"
|
||||
v-model="selectedEnvironmentTab"
|
||||
render-inactive-tabs
|
||||
>
|
||||
<HoppSmartTab
|
||||
:id="'my-environments'"
|
||||
:label="`${t('environment.my_environments')}`"
|
||||
/>
|
||||
<HoppSmartTab
|
||||
:id="'team-environments'"
|
||||
:label="`${t('environment.team_environments')}`"
|
||||
>
|
||||
<HoppSmartIntersection @intersecting="onTeamSelectIntersect">
|
||||
<tippy
|
||||
interactive
|
||||
trigger="click"
|
||||
theme="popover"
|
||||
placement="bottom"
|
||||
:on-shown="() => tippyActions.focus()"
|
||||
>
|
||||
<span
|
||||
v-tippy="{ theme: 'tooltip' }"
|
||||
:title="`${t('collection.select_team')}`"
|
||||
class="bg-transparent border-b border-dividerLight select-wrapper"
|
||||
>
|
||||
<HoppButtonSecondary
|
||||
v-if="environmentType.selectedTeam"
|
||||
:icon="IconUsers"
|
||||
:label="environmentType.selectedTeam.name"
|
||||
class="flex-1 !justify-start pr-8 rounded-none"
|
||||
/>
|
||||
<HoppButtonSecondary
|
||||
v-else
|
||||
:label="`${t('collection.select_team')}`"
|
||||
class="flex-1 !justify-start pr-8 rounded-none"
|
||||
/>
|
||||
</span>
|
||||
<template #content="{ hide }">
|
||||
<div
|
||||
ref="tippyActions"
|
||||
class="flex flex-col focus:outline-none"
|
||||
tabindex="0"
|
||||
@keyup.escape="hide()"
|
||||
>
|
||||
<HoppSmartItem
|
||||
v-for="(team, index) in myTeams"
|
||||
:key="`team-${index}`"
|
||||
:label="team.name"
|
||||
:info-icon="
|
||||
team.id === environmentType.selectedTeam?.id
|
||||
? IconDone
|
||||
: undefined
|
||||
"
|
||||
:active-info-icon="
|
||||
team.id === environmentType.selectedTeam?.id
|
||||
"
|
||||
:icon="IconUsers"
|
||||
@click="
|
||||
() => {
|
||||
updateSelectedTeam(team)
|
||||
hide()
|
||||
}
|
||||
"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
</tippy>
|
||||
</HoppSmartIntersection>
|
||||
</HoppSmartTab>
|
||||
</HoppSmartTabs>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { nextTick, ref, watch } from "vue"
|
||||
import { GetMyTeamsQuery } from "~/helpers/backend/graphql"
|
||||
import { onLoggedIn } from "@composables/auth"
|
||||
import { platform } from "~/platform"
|
||||
import TeamListAdapter from "~/helpers/teams/TeamListAdapter"
|
||||
import { useReadonlyStream } from "@composables/stream"
|
||||
import { useLocalState } from "~/newstore/localstate"
|
||||
import { useI18n } from "@composables/i18n"
|
||||
import IconDone from "~icons/lucide/check"
|
||||
import IconUsers from "~icons/lucide/users"
|
||||
import { invokeAction } from "~/helpers/actions"
|
||||
|
||||
const t = useI18n()
|
||||
|
||||
type TeamData = GetMyTeamsQuery["myTeams"][number]
|
||||
|
||||
type SelectedTeam = TeamData | undefined
|
||||
|
||||
type EnvironmentTabs = "my-environments" | "team-environments"
|
||||
|
||||
// Template refs
|
||||
const tippyActions = ref<any | null>(null)
|
||||
const selectedEnvironmentTab = ref<EnvironmentTabs>("my-environments")
|
||||
|
||||
defineProps<{
|
||||
environmentType: {
|
||||
type: "my-environments" | "team-environments"
|
||||
selectedTeam: SelectedTeam
|
||||
}
|
||||
}>()
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: "update-environment-type", tabID: EnvironmentTabs): void
|
||||
(e: "update-selected-team", team: SelectedTeam): void
|
||||
}>()
|
||||
|
||||
const currentUser = useReadonlyStream(
|
||||
platform.auth.getCurrentUserStream(),
|
||||
platform.auth.getCurrentUser()
|
||||
)
|
||||
|
||||
const adapter = new TeamListAdapter(true)
|
||||
const myTeams = useReadonlyStream(adapter.teamList$, null)
|
||||
const REMEMBERED_TEAM_ID = useLocalState("REMEMBERED_TEAM_ID")
|
||||
let teamListFetched = false
|
||||
|
||||
watch(myTeams, (teams) => {
|
||||
if (teams && !teamListFetched) {
|
||||
teamListFetched = true
|
||||
if (REMEMBERED_TEAM_ID.value && currentUser.value) {
|
||||
const team = teams.find((t) => t.id === REMEMBERED_TEAM_ID.value)
|
||||
if (team) updateSelectedTeam(team)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
watch(
|
||||
() => currentUser.value,
|
||||
(user) => {
|
||||
if (!user) {
|
||||
selectedEnvironmentTab.value = "my-environments"
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
onLoggedIn(() => {
|
||||
try {
|
||||
adapter.initialize()
|
||||
} catch (e) {}
|
||||
})
|
||||
|
||||
const onTeamSelectIntersect = () => {
|
||||
// Load team data as soon as intersection
|
||||
adapter.fetchList()
|
||||
}
|
||||
|
||||
const updateEnvironmentType = (tabID: EnvironmentTabs) => {
|
||||
emit("update-environment-type", tabID)
|
||||
}
|
||||
|
||||
const updateSelectedTeam = (team: SelectedTeam) => {
|
||||
REMEMBERED_TEAM_ID.value = team?.id
|
||||
emit("update-selected-team", team)
|
||||
}
|
||||
|
||||
watch(selectedEnvironmentTab, (newValue: EnvironmentTabs) => {
|
||||
if (newValue === "team-environments" && !currentUser.value) {
|
||||
invokeAction("modals.login.toggle")
|
||||
nextTick(() => (selectedEnvironmentTab.value = "my-environments"))
|
||||
} else updateEnvironmentType(newValue)
|
||||
})
|
||||
</script>
|
||||
@@ -1,8 +1,9 @@
|
||||
<template>
|
||||
<div>
|
||||
<div
|
||||
class="sticky top-0 z-10 flex flex-col flex-shrink-0 overflow-x-auto rounded-t bg-primary"
|
||||
class="sticky top-0 z-10 flex flex-col flex-shrink-0 overflow-x-auto bg-primary"
|
||||
>
|
||||
<WorkspaceCurrent :section="t('tab.environments')" />
|
||||
<tippy
|
||||
v-if="environmentType.type === 'my-environments'"
|
||||
interactive
|
||||
@@ -144,7 +145,7 @@
|
||||
v-if="!loading && adapterError"
|
||||
class="flex flex-col items-center py-4"
|
||||
>
|
||||
<i class="mb-4 material-icons">help_outline</i>
|
||||
<icon-lucide-help-circle class="mb-4 svg-icons" />
|
||||
{{ getErrorMessage(adapterError) }}
|
||||
</div>
|
||||
</div>
|
||||
@@ -156,11 +157,6 @@
|
||||
class="border-b border-dividerLight"
|
||||
@edit-environment="editEnvironment('Global')"
|
||||
/>
|
||||
<EnvironmentsChooseType
|
||||
:environment-type="environmentType"
|
||||
@update-environment-type="updateEnvironmentType"
|
||||
@update-selected-team="updateSelectedTeam"
|
||||
/>
|
||||
</div>
|
||||
<EnvironmentsMy v-if="environmentType.type === 'my-environments'" />
|
||||
<EnvironmentsTeams
|
||||
@@ -184,7 +180,7 @@
|
||||
import { computed, ref, watch } from "vue"
|
||||
import { isEqual } from "lodash-es"
|
||||
import { platform } from "~/platform"
|
||||
import { Team } from "~/helpers/backend/graphql"
|
||||
import { GetMyTeamsQuery } from "~/helpers/backend/graphql"
|
||||
import { useReadonlyStream, useStream } from "@composables/stream"
|
||||
import { useI18n } from "~/composables/i18n"
|
||||
import {
|
||||
@@ -198,12 +194,16 @@ 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"
|
||||
import { useLocalState } from "~/newstore/localstate"
|
||||
import { onLoggedIn } from "~/composables/auth"
|
||||
|
||||
const t = useI18n()
|
||||
|
||||
type EnvironmentType = "my-environments" | "team-environments"
|
||||
|
||||
type SelectedTeam = Team | undefined
|
||||
type SelectedTeam = GetMyTeamsQuery["myTeams"][number] | undefined
|
||||
|
||||
type EnvironmentsChooseType = {
|
||||
type: EnvironmentType
|
||||
@@ -227,12 +227,11 @@ const currentUser = useReadonlyStream(
|
||||
platform.auth.getCurrentUser()
|
||||
)
|
||||
|
||||
const updateSelectedTeam = (newSelectedTeam: SelectedTeam) => {
|
||||
environmentType.value.selectedTeam = newSelectedTeam
|
||||
}
|
||||
const updateEnvironmentType = (newEnvironmentType: EnvironmentType) => {
|
||||
environmentType.value.type = newEnvironmentType
|
||||
}
|
||||
// TeamList-Adapter
|
||||
const teamListAdapter = new TeamListAdapter(true)
|
||||
const myTeams = useReadonlyStream(teamListAdapter.teamList$, null)
|
||||
const teamListFetched = ref(false)
|
||||
const REMEMBERED_TEAM_ID = useLocalState("REMEMBERED_TEAM_ID")
|
||||
|
||||
const adapter = new TeamEnvironmentAdapter(undefined)
|
||||
const adapterLoading = useReadonlyStream(adapter.loading$, false)
|
||||
@@ -243,6 +242,45 @@ const loading = computed(
|
||||
() => adapterLoading.value && teamEnvironmentList.value.length === 0
|
||||
)
|
||||
|
||||
watch(
|
||||
() => myTeams.value,
|
||||
(newTeams) => {
|
||||
if (newTeams && !teamListFetched.value) {
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
watch(
|
||||
() => environmentType.value.selectedTeam,
|
||||
(newTeam) => {
|
||||
if (newTeam) {
|
||||
adapter.changeTeamID(newTeam.id)
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
const switchToMyEnvironments = () => {
|
||||
environmentType.value.selectedTeam = undefined
|
||||
updateEnvironmentType("my-environments")
|
||||
adapter.changeTeamID(undefined)
|
||||
}
|
||||
|
||||
const updateSelectedTeam = (newSelectedTeam: SelectedTeam) => {
|
||||
if (newSelectedTeam) {
|
||||
environmentType.value.selectedTeam = newSelectedTeam
|
||||
REMEMBERED_TEAM_ID.value = newSelectedTeam.id
|
||||
updateEnvironmentType("team-environments")
|
||||
}
|
||||
}
|
||||
const updateEnvironmentType = (newEnvironmentType: EnvironmentType) => {
|
||||
environmentType.value.type = newEnvironmentType
|
||||
}
|
||||
|
||||
watch(
|
||||
() => environmentType.value.selectedTeam?.id,
|
||||
(newTeamID) => {
|
||||
@@ -254,7 +292,30 @@ watch(
|
||||
() => currentUser.value,
|
||||
(newValue) => {
|
||||
if (!newValue) {
|
||||
updateEnvironmentType("my-environments")
|
||||
switchToMyEnvironments()
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
onLoggedIn(() => {
|
||||
!teamListAdapter.isInitialized && teamListAdapter.initialize()
|
||||
})
|
||||
|
||||
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.value.teamID,
|
||||
(teamID) => {
|
||||
if (!teamID) {
|
||||
switchToMyEnvironments()
|
||||
} else if (teamID) {
|
||||
const team = myTeams.value?.find((t) => t.id === teamID)
|
||||
if (team) {
|
||||
updateSelectedTeam(team)
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
@@ -94,7 +94,7 @@
|
||||
v-if="!loading && adapterError"
|
||||
class="flex flex-col items-center py-4"
|
||||
>
|
||||
<i class="mb-4 material-icons">help_outline</i>
|
||||
<icon-lucide-help-circle class="mb-4 svg-icons" />
|
||||
{{ getErrorMessage(adapterError) }}
|
||||
</div>
|
||||
<EnvironmentsTeamsDetails
|
||||
|
||||
Reference in New Issue
Block a user