Team.vue (hoppscotch-2.2.1) | : | Team.vue (hoppscotch-3.0.0) | ||
---|---|---|---|---|
<template> | <template> | |||
<div | <div | |||
class="flex flex-col flex-1 border rounded border-divider" | class="flex flex-col flex-1 border rounded border-divider" | |||
@contextmenu.prevent="!compact ? options.tippy().show() : null" | @contextmenu.prevent="!compact ? options.tippy.show() : null" | |||
> | > | |||
<div | <div | |||
class="flex items-start flex-1" | class="flex items-start flex-1" | |||
:class=" | :class=" | |||
compact | compact | |||
? team.myRole === 'OWNER' | ? team.myRole === 'OWNER' | |||
? 'cursor-pointer hover:bg-primaryDark transition hover:border-divid erDark focus-visible:border-dividerDark' | ? 'cursor-pointer hover:bg-primaryDark transition hover:border-divid erDark focus-visible:border-dividerDark' | |||
: 'cursor-not-allowed bg-primaryLight' | : 'cursor-not-allowed bg-primaryLight' | |||
: '' | : '' | |||
" | " | |||
skipping to change at line 30 | skipping to change at line 30 | |||
: '' | : '' | |||
" | " | |||
> | > | |||
<div class="p-4"> | <div class="p-4"> | |||
<label | <label | |||
class="font-semibold text-secondaryDark" | class="font-semibold text-secondaryDark" | |||
:class="{ 'cursor-pointer': compact && team.myRole === 'OWNER' }" | :class="{ 'cursor-pointer': compact && team.myRole === 'OWNER' }" | |||
> | > | |||
{{ team.name || t("state.nothing_found") }} | {{ team.name || t("state.nothing_found") }} | |||
</label> | </label> | |||
<div class="flex mt-2 -space-x-1 overflow-hidden"> | <div class="flex mt-2 overflow-hidden -space-x-1"> | |||
<img | <div | |||
v-for="(member, index) in team.teamMembers" | v-for="(member, index) in team.teamMembers" | |||
:key="`member-${index}`" | :key="`member-${index}`" | |||
v-tippy="{ theme: 'tooltip' }" | v-tippy="{ theme: 'tooltip' }" | |||
:title="member.user.displayName" | :title="member.user.displayName" | |||
:src="member.user.photoURL || undefined" | class="inline-flex" | |||
:alt="member.user.displayName" | > | |||
class="inline-block w-5 h-5 rounded-full ring-primary ring-2" | <ProfilePicture | |||
loading="lazy" | v-if="member.user.photoURL" | |||
/> | :url="member.user.photoURL" | |||
:alt="member.user.displayName" | ||||
class="ring-primary ring-2" | ||||
/> | ||||
<ProfilePicture | ||||
v-else | ||||
:initial="member.user.displayName" | ||||
class="ring-primary ring-2" | ||||
/> | ||||
</div> | ||||
</div> | </div> | |||
</div> | </div> | |||
</div> | </div> | |||
<div v-if="!compact" class="flex items-end justify-between flex-shrink-0"> | <div v-if="!compact" class="flex items-end justify-between flex-shrink-0"> | |||
<span> | <span> | |||
<ButtonSecondary | <ButtonSecondary | |||
v-if="team.myRole === 'OWNER'" | v-if="team.myRole === 'OWNER'" | |||
svg="edit" | :icon="IconEdit" | |||
class="rounded-none" | class="rounded-none" | |||
:label="t('action.edit')" | :label="t('action.edit')" | |||
@click.native=" | @click=" | |||
() => { | () => { | |||
$emit('edit-team') | $emit('edit-team') | |||
} | } | |||
" | " | |||
/> | /> | |||
<ButtonSecondary | <ButtonSecondary | |||
v-if="team.myRole === 'OWNER'" | v-if="team.myRole === 'OWNER'" | |||
svg="user-plus" | :icon="IconUserPlus" | |||
class="rounded-none" | class="rounded-none" | |||
:label="t('team.invite')" | :label="t('team.invite')" | |||
@click.native=" | @click=" | |||
() => { | () => { | |||
emit('invite-team') | emit('invite-team') | |||
} | } | |||
" | " | |||
/> | /> | |||
</span> | </span> | |||
<span> | <span> | |||
<tippy | <tippy | |||
ref="options" | ref="options" | |||
interactive | interactive | |||
trigger="click" | trigger="click" | |||
theme="popover" | theme="popover" | |||
arrow | arrow | |||
:on-shown="() => tippyActions.focus()" | :on-shown="() => tippyActions.focus()" | |||
> | > | |||
<template #trigger> | <ButtonSecondary | |||
<ButtonSecondary | v-tippy="{ theme: 'tooltip' }" | |||
v-tippy="{ theme: 'tooltip' }" | :title="t('action.more')" | |||
:title="t('action.more')" | :icon="IconMoreVertical" | |||
svg="more-vertical" | /> | |||
/> | <template #content="{ hide }"> | |||
</template> | <div | |||
<div | ref="tippyActions" | |||
ref="tippyActions" | class="flex flex-col focus:outline-none" | |||
class="flex flex-col focus:outline-none" | tabindex="0" | |||
tabindex="0" | role="menu" | |||
@keyup.e="team.myRole === 'OWNER' ? edit.$el.click() : null" | @keyup.e="team.myRole === 'OWNER' ? edit.$el.click() : null" | |||
@keyup.x=" | @keyup.x=" | |||
!(team.myRole === 'OWNER' && team.ownersCount == 1) | !(team.myRole === 'OWNER' && team.ownersCount == 1) | |||
? exit.$el.click() | ? exit.$el.click() | |||
: null | : null | |||
" | ||||
@keyup.delete=" | ||||
team.myRole === 'OWNER' ? deleteAction.$el.click() : null | ||||
" | ||||
@keyup.escape="options.tippy().hide()" | ||||
> | ||||
<SmartItem | ||||
v-if="team.myRole === 'OWNER'" | ||||
ref="edit" | ||||
svg="edit" | ||||
:label="t('action.edit')" | ||||
:shortcut="['E']" | ||||
@click.native=" | ||||
() => { | ||||
$emit('edit-team') | ||||
options.tippy().hide() | ||||
} | ||||
" | ||||
/> | ||||
<SmartItem | ||||
v-if="!(team.myRole === 'OWNER' && team.ownersCount == 1)" | ||||
ref="exit" | ||||
svg="user-x" | ||||
:label="t('team.exit')" | ||||
:shortcut="['X']" | ||||
@click.native=" | ||||
() => { | ||||
confirmExit = true | ||||
options.tippy().hide() | ||||
} | ||||
" | " | |||
/> | @keyup.delete=" | |||
<SmartItem | team.myRole === 'OWNER' ? deleteAction.$el.click() : null | |||
v-if="team.myRole === 'OWNER'" | ||||
ref="deleteAction" | ||||
svg="trash-2" | ||||
:label="t('action.delete')" | ||||
:shortcut="['⌫']" | ||||
@click.native=" | ||||
() => { | ||||
confirmRemove = true | ||||
options.tippy().hide() | ||||
} | ||||
" | " | |||
/> | @keyup.escape="hide()" | |||
</div> | > | |||
<SmartItem | ||||
v-if="team.myRole === 'OWNER'" | ||||
ref="edit" | ||||
:icon="IconEdit" | ||||
:label="t('action.edit')" | ||||
:shortcut="['E']" | ||||
@click=" | ||||
() => { | ||||
$emit('edit-team') | ||||
hide() | ||||
} | ||||
" | ||||
/> | ||||
<SmartItem | ||||
v-if="!(team.myRole === 'OWNER' && team.ownersCount == 1)" | ||||
ref="exit" | ||||
:icon="IconUserX" | ||||
:label="t('team.exit')" | ||||
:shortcut="['X']" | ||||
@click=" | ||||
() => { | ||||
confirmExit = true | ||||
hide() | ||||
} | ||||
" | ||||
/> | ||||
<SmartItem | ||||
v-if="team.myRole === 'OWNER'" | ||||
ref="deleteAction" | ||||
:icon="IconTrash2" | ||||
:label="t('action.delete')" | ||||
:shortcut="['⌫']" | ||||
@click=" | ||||
() => { | ||||
confirmRemove = true | ||||
hide() | ||||
} | ||||
" | ||||
/> | ||||
</div> | ||||
</template> | ||||
</tippy> | </tippy> | |||
</span> | </span> | |||
</div> | </div> | |||
<SmartConfirmModal | <SmartConfirmModal | |||
:show="confirmRemove" | :show="confirmRemove" | |||
:title="t('confirm.remove_team')" | :title="t('confirm.remove_team')" | |||
@hide-modal="confirmRemove = false" | @hide-modal="confirmRemove = false" | |||
@resolve="deleteTeam()" | @resolve="deleteTeam()" | |||
/> | /> | |||
<SmartConfirmModal | <SmartConfirmModal | |||
:show="confirmExit" | :show="confirmExit" | |||
:title="t('confirm.exit_team')" | :title="t('confirm.exit_team')" | |||
@hide-modal="confirmExit = false" | @hide-modal="confirmExit = false" | |||
@resolve="exitTeam()" | @resolve="exitTeam()" | |||
/> | /> | |||
</div> | </div> | |||
</template> | </template> | |||
<script setup lang="ts"> | <script setup lang="ts"> | |||
import { ref } from "@nuxtjs/composition-api" | import { ref } from "vue" | |||
import { pipe } from "fp-ts/function" | import { pipe } from "fp-ts/function" | |||
import * as TE from "fp-ts/TaskEither" | import * as TE from "fp-ts/TaskEither" | |||
import { TeamMemberRole } from "~/helpers/backend/graphql" | import { TeamMemberRole } from "~/helpers/backend/graphql" | |||
import { | import { | |||
deleteTeam as backendDeleteTeam, | deleteTeam as backendDeleteTeam, | |||
leaveTeam, | leaveTeam, | |||
} from "~/helpers/backend/mutations/Team" | } from "~/helpers/backend/mutations/Team" | |||
import { useI18n, useToast } from "~/helpers/utils/composables" | ||||
import { useI18n } from "@composables/i18n" | ||||
import { useToast } from "@composables/toast" | ||||
import IconEdit from "~icons/lucide/edit" | ||||
import IconMoreVertical from "~icons/lucide/more-vertical" | ||||
import IconUserX from "~icons/lucide/user-x" | ||||
import IconUserPlus from "~icons/lucide/user-plus" | ||||
import IconTrash2 from "~icons/lucide/trash-2" | ||||
const t = useI18n() | const t = useI18n() | |||
const props = defineProps<{ | const props = defineProps<{ | |||
team: { | team: { | |||
name: string | name: string | |||
myRole: TeamMemberRole | myRole: TeamMemberRole | |||
ownersCount: number | ownersCount: number | |||
teamMembers: Array<{ | teamMembers: Array<{ | |||
user: { | user: { | |||
skipping to change at line 189 | skipping to change at line 207 | |||
photoURL: string | null | photoURL: string | null | |||
} | } | |||
}> | }> | |||
} | } | |||
teamID: string | teamID: string | |||
compact: boolean | compact: boolean | |||
}>() | }>() | |||
const emit = defineEmits<{ | const emit = defineEmits<{ | |||
(e: "edit-team"): void | (e: "edit-team"): void | |||
(e: "invite-team"): void | ||||
}>() | }>() | |||
const toast = useToast() | const toast = useToast() | |||
const confirmRemove = ref(false) | const confirmRemove = ref(false) | |||
const confirmExit = ref(false) | const confirmExit = ref(false) | |||
const deleteTeam = () => { | const deleteTeam = () => { | |||
pipe( | pipe( | |||
backendDeleteTeam(props.teamID), | backendDeleteTeam(props.teamID), | |||
End of changes. 13 change blocks. | ||||
74 lines changed or deleted | 93 lines changed or added |