Files
Rocketmc/packages/ui/src/components/project/ProjectSidebarCreators.vue
Prospector c39bb78e38 App redesign (#2946)
* Start of app redesign

* format

* continue progress

* Content page nearly done

* Fix recursion issues with content page

* Fix update all alignment

* Discover page progress

* Settings progress

* Removed unlocked-size hack that breaks web

* Revamp project page, refactor web project page to share code with app, fixed loading bar, misc UI/UX enhancements, update ko-fi logo, update arrow icons, fix web issues caused by floating-vue migration, fix tooltip issues, update web tooltips, clean up web hydration issues

* Ads + run prettier

* Begin auth refactor, move common messages to ui lib, add i18n extraction to all apps, begin Library refactor

* fix ads not hiding when plus log in

* rev lockfile changes/conflicts

* Fix sign in page

* Add generated

* (mostly) Data driven search

* Fix search mobile issue

* profile fixes

* Project versions page, fix typescript on UI lib and misc fixes

* Remove unused gallery component

* Fix linkfunction err

* Search filter controls at top, localization for locked filters

* Fix provided filter names

* Fix navigating from instance browse to main browse

* Friends frontend (#2995)

* Friends system frontend

* (almost) finish frontend

* finish friends, fix lint

* Fix lint

---------

Signed-off-by: Geometrically <18202329+Geometrically@users.noreply.github.com>

* Refresh macOS app icon

* Update web search UI more

* Fix link opens

* Fix frontend build

---------

Signed-off-by: Geometrically <18202329+Geometrically@users.noreply.github.com>
Co-authored-by: Jai A <jaiagr+gpg@pm.me>
Co-authored-by: Geometrically <18202329+Geometrically@users.noreply.github.com>
2024-12-11 19:54:18 -08:00

119 lines
3.7 KiB
Vue

<template>
<div class="flex flex-col gap-3">
<h2 class="text-lg m-0">{{ formatMessage(messages.title) }}</h2>
<div class="flex flex-col gap-3 font-semibold">
<template v-if="organization">
<AutoLink
class="flex gap-2 items-center w-fit text-primary leading-[1.2] group"
:to="orgLink(organization.slug)"
:target="linkTarget ?? null"
>
<Avatar :src="organization.icon_url" :alt="organization.name" size="32px" />
<div class="flex flex-col flex-nowrap justify-center">
<span class="group-hover:underline">
{{ organization.name }}
</span>
<span class="text-secondary text-sm font-medium flex items-center gap-1"
><OrganizationIcon /> Organization</span
>
</div>
</AutoLink>
<hr v-if="sortedMembers.length > 0" class="w-full border-button-border my-0.5" />
</template>
<AutoLink
v-for="member in sortedMembers"
:key="`member-${member.id}`"
class="flex gap-2 items-center w-fit text-primary leading-[1.2] group"
:to="userLink(member.user.username)"
:target="linkTarget ?? null"
>
<Avatar :src="member.user.avatar_url" :alt="member.user.username" size="32px" circle />
<div class="flex flex-col">
<span class="flex flex-row flex-nowrap items-center gap-1 group-hover:underline">
{{ member.user.username }}
<CrownIcon
v-if="member.is_owner"
v-tooltip="formatMessage(messages.owner)"
class="text-brand-orange"
/>
<ExternalIcon v-if="linkTarget === '_blank'" />
</span>
<span class="text-secondary text-sm font-medium">{{ member.role }}</span>
</div>
</AutoLink>
</div>
</div>
</template>
<script setup lang="ts">
import { CrownIcon, ExternalIcon, OrganizationIcon } from '@modrinth/assets'
import { useVIntl, defineMessages } from '@vintl/vintl'
import Avatar from '../base/Avatar.vue'
import AutoLink from '../base/AutoLink.vue'
import { computed } from 'vue'
const { formatMessage } = useVIntl()
type TeamMember = {
id: string
role: string
is_owner: boolean
accepted: boolean
user: {
id: string
username: string
avatar_url: string
}
}
const props = defineProps<{
organization?: {
id: string
slug: string
name: string
icon_url: string
avatar_url: string
members: TeamMember[]
} | null
members: TeamMember[]
orgLink: (slug: string) => string
userLink: (username: string) => string
linkTarget?: string
}>()
// Members should be an array of all members, without the accepted ones, and with the user with the Owner role at the start
// The rest of the members should be sorted by role, then by name
const sortedMembers = computed(() => {
const acceptedMembers = props.members.filter((x) => x.accepted === undefined || x.accepted)
const owner = acceptedMembers.find((x) =>
props.organization
? props.organization.members.some(
(orgMember) => orgMember.user.id === x.user.id && orgMember.is_owner,
)
: x.is_owner,
)
const rest = acceptedMembers.filter((x) => !owner || x.user.id !== owner.user.id) || []
rest.sort((a, b) => {
if (a.role === b.role) {
return a.user.username.localeCompare(b.user.username)
} else {
return a.role.localeCompare(b.role)
}
})
return owner ? [owner, ...rest] : rest
})
const messages = defineMessages({
title: {
id: 'project.about.creators.title',
defaultMessage: 'Creators',
},
owner: {
id: 'project.about.creators.owner',
defaultMessage: 'Project owner',
},
})
</script>