mirror of
https://github.com/PluralKit/PluralKit.git
synced 2026-02-04 04:56:49 +00:00
feat(dashboard): allow selecting which avatar to show in list
This commit is contained in:
parent
94e848ee4c
commit
1777070694
14 changed files with 120 additions and 24 deletions
|
|
@ -9,6 +9,8 @@
|
|||
|
||||
export let item: any;
|
||||
export let searchBy: string = null;
|
||||
export let type: "member"|"group"|"system"
|
||||
export let avatarUsed: "proxy"|"avatar"|"proxy_only"|"avatar_only"
|
||||
export let sortBy: string = null;
|
||||
|
||||
let htmlNamePromise: Promise<string>;
|
||||
|
|
@ -24,8 +26,20 @@
|
|||
if (nameElement) twemoji.parse(nameElement, { base: 'https://cdn.jsdelivr.net/gh/twitter/twemoji@14.0.2/assets/' });
|
||||
}
|
||||
|
||||
$: icon_url = item.avatar_url ? item.avatar_url : item.icon ? item.icon : default_avatar;
|
||||
$: icon_url_resized = resizeMedia(icon_url)
|
||||
let icon_url: string = default_avatar
|
||||
$: if (type === "group") {
|
||||
if (item.icon) icon_url = item.icon
|
||||
} else if (avatarUsed === "proxy" || avatarUsed === "proxy_only") {
|
||||
if (item.webhook_avatar_url) icon_url = item.webhook_avatar_url
|
||||
else if (avatarUsed === "proxy_only") icon_url = default_avatar
|
||||
else icon_url = item.avatar_url || default_avatar
|
||||
} else {
|
||||
if (item.avatar_url) icon_url = item.avatar_url
|
||||
else if (avatarUsed === "avatar_only") icon_url = default_avatar
|
||||
else icon_url = item.webhook_avatar_url ?? default_avatar
|
||||
}
|
||||
|
||||
$: icon_url_resized = icon_url ? resizeMedia(icon_url) : default_avatar
|
||||
|
||||
let avatarOpen = false;
|
||||
const toggleAvatarModal = () => (avatarOpen = !avatarOpen);
|
||||
|
|
|
|||
|
|
@ -7,10 +7,11 @@
|
|||
import type { Member, Group } from '../../api/types';
|
||||
import MemberCard from '../member/CardView.svelte';
|
||||
import GroupCard from '../group/CardView.svelte';
|
||||
import type { PageOptions } from './types';
|
||||
import type { ListOptions, PageOptions } from './types';
|
||||
|
||||
export let pageOptions: PageOptions;
|
||||
export let currentList: Member[]|Group[];
|
||||
export let listOptions: ListOptions;
|
||||
|
||||
let copiedItems = {};
|
||||
|
||||
|
|
@ -50,7 +51,7 @@
|
|||
{#if pageOptions.type === "member"}
|
||||
{#each currentList as item (item.uuid)}
|
||||
<div class="col-12 col-md-6 col-lg-4 col-xxl-3 mx-auto mx-sm-0 dont-squish">
|
||||
<MemberCard on:update member={item} searchBy="name" sortBy="name" isPublic={pageOptions.isPublic} isDash={pageOptions.isMain}>
|
||||
<MemberCard on:update member={item} searchBy="name" sortBy="name" isPublic={pageOptions.isPublic} isDash={pageOptions.isMain} avatarUsed={listOptions.pfp}>
|
||||
<button class="button-reset" slot="icon" style="width: auto; height: 1em; cursor: pointer;" id={`${pageOptions.type}-copy-${item.uuid}`} on:click|stopPropagation={() => copyShortLink(item.uuid, item.id)} on:keydown={(e) => copyShortLink(item.uuid, item.id, e)} tabindex={0} >
|
||||
{#if item.privacy && item.privacy.visibility === "private"}
|
||||
<FaLock />
|
||||
|
|
|
|||
|
|
@ -173,7 +173,6 @@ function resetPage() {
|
|||
<InputGroup>
|
||||
<InputGroupText>Extra Info</InputGroupText>
|
||||
<Input bind:value={options.extra} type="select" aria-label="view mode" on:change={(e) => onViewChange(e)} >
|
||||
<option value={null}>None</option>
|
||||
<option value="display_name">Display Name</option>
|
||||
{#if pageOptions.type === "member"}
|
||||
<option value="avatar_url">Avatar Url</option>
|
||||
|
|
@ -188,6 +187,16 @@ function resetPage() {
|
|||
<option value="created">Created</option>
|
||||
</Input>
|
||||
</InputGroup>
|
||||
{:else if pageOptions.type === "member"}
|
||||
<InputGroup>
|
||||
<InputGroupText>Avatar Used</InputGroupText>
|
||||
<Input bind:value={options.pfp} type="select" aria-label="view mode" >
|
||||
<option value="proxy">Proxy (fall back to main)</option>
|
||||
<option value="avatar">Main (fall back to proxy)</option>
|
||||
<option value="proxy_only">Proxy only</option>
|
||||
<option value="avatar_only">Main only</option>
|
||||
</Input>
|
||||
</InputGroup>
|
||||
{/if}
|
||||
</Col>
|
||||
</Row>
|
||||
|
|
|
|||
|
|
@ -118,7 +118,7 @@
|
|||
<Card style="border-radius: 0;">
|
||||
<h2 class="accordion-header">
|
||||
<button class="w-100 accordion-button collapsed bg-transparent" id={`${pageOptions.type}-card-${indexStart + index}`} on:click={() => toggleCard(item.uuid, index)} on:keydown={(e) => skipToNextItem(e, indexStart + index)}>
|
||||
<CardsHeader {item} sortBy={options.sort}>
|
||||
<CardsHeader {item} sortBy={options.sort} type={pageOptions.type} avatarUsed={options.pfp}>
|
||||
<button class="button-reset" slot="icon" style="cursor: pointer;" id={`${pageOptions.type}-copy-${item.id}-${indexStart + index}`} on:click|stopPropagation={() => copyShortLink(indexStart + index, item.id)} on:keydown={(e) => copyShortLink(indexStart + index, item.id, e)} tabindex={0} >
|
||||
{#if item.privacy && item.privacy.visibility === "private"}
|
||||
<FaLock />
|
||||
|
|
@ -149,7 +149,7 @@
|
|||
<Card class="mb-3">
|
||||
<h2 class="accordion-header card-header">
|
||||
<button class="w-100 accordion-button collapsed bg-transparent" id={`${pageOptions.type}-card-${indexStart + index}`} on:keydown={(e) => skipToNextItem(e, indexStart + index)} tabindex={0}>
|
||||
<CardsHeader {item} sortBy={options.sort}>
|
||||
<CardsHeader {item} sortBy={options.sort} type={pageOptions.type} avatarUsed={options.pfp}>
|
||||
<button class="button-reset" slot="icon" style="cursor: pointer;" id={`${pageOptions.type}-copy-${item.id}-${indexStart + index}`} on:click|stopPropagation={() => copyShortLink(indexStart + index, item.id)} on:keydown|stopPropagation={(e) => copyShortLink(indexStart + index, item.id, e)} tabindex={0} >
|
||||
{#if item.privacy && item.privacy.visibility === "private"}
|
||||
<FaLock />
|
||||
|
|
@ -177,7 +177,7 @@
|
|||
{#each currentList as item, index(pageOptions.randomized ? item.uuid + '-' + index : item.uuid)}
|
||||
<Card style="border-radius: 0;">
|
||||
<a class="accordion-button p-3 collapsed bg-transparent" style="text-decoration: none;" href={getItemLink(item)} id={`${pageOptions.type}-card-${indexStart + index}`} on:keydown={(e) => skipToNextItem(e, indexStart + index)} use:link >
|
||||
<CardsHeader {item} sortBy={options.sort}>
|
||||
<CardsHeader {item} sortBy={options.sort} type={pageOptions.type} avatarUsed={options.pfp}>
|
||||
<button class="button-reset" slot="icon" style="cursor: pointer;" id={`${pageOptions.type}-copy-${item.id}-${indexStart + index}`} on:click|stopPropagation={() => copyShortLink(indexStart + index, item.id)} on:keydown|stopPropagation={(e) => copyShortLink(indexStart + index, item.id, e)} tabindex={0} >
|
||||
{#if item.privacy && item.privacy.visibility === "private"}
|
||||
<FaLock />
|
||||
|
|
|
|||
|
|
@ -87,9 +87,9 @@
|
|||
<NewMember />
|
||||
{/if}
|
||||
{#if pageOptions.view === "card"}
|
||||
<CardView {pageOptions} currentList={currentMembers} />
|
||||
<CardView {pageOptions} currentList={currentMembers} listOptions={options} />
|
||||
{:else if pageOptions.view === "tiny"}
|
||||
<TinyView {pageOptions} currentList={currentMembers} />
|
||||
<TinyView {pageOptions} currentList={currentMembers} listOptions={options} />
|
||||
{:else if pageOptions.view === "text"}
|
||||
<TextView {pageOptions} listOptions={options} currentList={currentMembers} />
|
||||
{:else}
|
||||
|
|
|
|||
|
|
@ -96,6 +96,37 @@
|
|||
</Input>
|
||||
</InputGroup>
|
||||
</Col>
|
||||
<Col xs={12} md={6} lg={4} class="mb-2">
|
||||
{#if pageOptions.view === "text"}
|
||||
<InputGroup>
|
||||
<InputGroupText>Extra Info</InputGroupText>
|
||||
<Input bind:value={options.extra} type="select" aria-label="view mode" >
|
||||
<option value="display_name">Display Name</option>
|
||||
{#if pageOptions.type === "member"}
|
||||
<option value="avatar_url">Avatar Url</option>
|
||||
<option value="webhook_avatar_url">Proxy Avatar Url</option>
|
||||
<option value="pronouns">Pronouns</option>
|
||||
<option value="birthday">Birthday</option>
|
||||
{:else if pageOptions.type === "group"}
|
||||
<option value="icon">Icon Url</option>
|
||||
{/if}
|
||||
<option value="banner">Banner Url</option>
|
||||
<option value="color">Color</option>
|
||||
<option value="created">Created</option>
|
||||
</Input>
|
||||
</InputGroup>
|
||||
{:else if pageOptions.type === "member"}
|
||||
<InputGroup>
|
||||
<InputGroupText>Avatar Used</InputGroupText>
|
||||
<Input bind:value={options.pfp} type="select" aria-label="view mode" >
|
||||
<option value="proxy">Proxy (fall back to main)</option>
|
||||
<option value="avatar">Main (fall back to proxy)</option>
|
||||
<option value="proxy_only">Proxy only</option>
|
||||
<option value="avatar_only">Main only</option>
|
||||
</Input>
|
||||
</InputGroup>
|
||||
{/if}
|
||||
</Col>
|
||||
</Row>
|
||||
<hr/>
|
||||
<Row>
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
<script lang="ts">
|
||||
import { Card, CardImg, Row, Col } from "sveltestrap";
|
||||
import type { Group, Member } from "../../api/types";
|
||||
import type { PageOptions } from "./types";
|
||||
import type { ListOptions, PageOptions } from "./types";
|
||||
import default_avatar from '../../assets/default_avatar.png';
|
||||
import resizeMedia from "../../api/resize-media";
|
||||
import TinyMemberView from "../member/TinyMemberView.svelte";
|
||||
|
|
@ -9,6 +9,7 @@
|
|||
|
||||
export let pageOptions: PageOptions;
|
||||
export let currentList: Member[]|Group[];
|
||||
export let listOptions: ListOptions;
|
||||
$: memberList = currentList as Member[]
|
||||
$: groupList = currentList as Group[]
|
||||
</script>
|
||||
|
|
@ -17,7 +18,7 @@
|
|||
{#if pageOptions.type === "member"}
|
||||
{#each memberList as item (item.uuid)}
|
||||
<Col xs={6} md={4} lg={3} xl={2} class="d-flex flex-col">
|
||||
<TinyMemberView member={item} />
|
||||
<TinyMemberView member={item} avatarUsed={listOptions.pfp} />
|
||||
</Col>
|
||||
{/each}
|
||||
{:else if pageOptions.type === "group"}
|
||||
|
|
|
|||
|
|
@ -60,6 +60,7 @@ export interface ListOptions {
|
|||
|
||||
// text only view options
|
||||
extra: keyof Member | keyof Group | null
|
||||
pfp: "proxy"|"avatar"|"proxy_only"|"avatar_only"
|
||||
}
|
||||
|
||||
export interface PageOptions {
|
||||
|
|
@ -128,7 +129,8 @@ export const defaultListOptions: ListOptions = {
|
|||
sort: 'name',
|
||||
order: 'ascending',
|
||||
show: 'all',
|
||||
extra: 'display_name'
|
||||
extra: 'display_name',
|
||||
pfp: "proxy"
|
||||
}
|
||||
|
||||
export const defaultPageOptions: PageOptions = {
|
||||
|
|
|
|||
|
|
@ -48,6 +48,9 @@
|
|||
let bannerOpen = false;
|
||||
const toggleBannerModal = () => (bannerOpen = !bannerOpen);
|
||||
|
||||
let avatarOpen = false;
|
||||
const toggleAvatarModal = () => (avatarOpen = !avatarOpen);
|
||||
|
||||
let proxyAvatarOpen = false;
|
||||
const toggleProxyAvatarModal = () => (proxyAvatarOpen = !proxyAvatarOpen);
|
||||
|
||||
|
|
@ -134,6 +137,16 @@
|
|||
<b>Color:</b> {member.color}
|
||||
</Col>
|
||||
{/if}
|
||||
{#if member.avatar_url}
|
||||
<Col xs={12} lg={4} class="mb-2">
|
||||
<b>Avatar:</b> <Button size="sm" color="secondary" on:click={toggleAvatarModal} aria-label="view member avatar">View</Button>
|
||||
<Modal isOpen={avatarOpen} toggle={toggleAvatarModal}>
|
||||
<div slot="external" on:click={toggleAvatarModal} style="height: 100%; width: max-content; max-width: 100%; margin-left: auto; margin-right: auto; display: flex;">
|
||||
<img class="img-thumbnail d-block m-auto" src={member.avatar_url} tabindex={0} alt={`Member ${member.name} avatar (full size)`} use:focus/>
|
||||
</div>
|
||||
</Modal>
|
||||
</Col>
|
||||
{/if}
|
||||
{#if member.webhook_avatar_url}
|
||||
<Col xs={12} lg={4} class="mb-2">
|
||||
<b>Proxy Avatar:</b> <Button size="sm" color="secondary" on:click={toggleProxyAvatarModal} aria-label="view member proxy avatar">View</Button>
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@
|
|||
export let member: Member;
|
||||
export let searchBy: string;
|
||||
export let sortBy: string;
|
||||
export let avatarUsed: "proxy"|"avatar"|"proxy_only"|"avatar_only" = "proxy"
|
||||
export let isPublic = false;
|
||||
export let isDash = false;
|
||||
|
||||
|
|
@ -55,10 +56,21 @@
|
|||
if (prnsElement) twemoji.parse(prnsElement, { base: 'https://cdn.jsdelivr.net/gh/twitter/twemoji@14.0.2/assets/' });
|
||||
}
|
||||
|
||||
let icon_url = null
|
||||
|
||||
$: if (avatarUsed === "proxy" || avatarUsed === "proxy_only") {
|
||||
if (member.webhook_avatar_url) icon_url = member.webhook_avatar_url
|
||||
else if (avatarUsed === "proxy_only") icon_url = default_avatar
|
||||
else icon_url = member.avatar_url || default_avatar
|
||||
} else {
|
||||
if (member.avatar_url) icon_url = member.avatar_url
|
||||
else if (avatarUsed === "avatar_only") icon_url = default_avatar
|
||||
else icon_url = member.webhook_avatar_url ?? default_avatar
|
||||
}
|
||||
|
||||
let avatarOpen = false;
|
||||
const toggleAvatarModal = () => (avatarOpen = !avatarOpen);
|
||||
|
||||
$: icon_url = member.avatar_url ? member.avatar_url : default_avatar;
|
||||
$: icon_url_resized = resizeMedia(icon_url);
|
||||
|
||||
let altText = `member ${member.name} avatar`;
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@
|
|||
import FaUserCircle from "svelte-icons/fa/FaUserCircle.svelte"
|
||||
|
||||
export let member: Member
|
||||
export let avatarUsed: "proxy"|"avatar"|"proxy_only"|"avatar_only" = "proxy"
|
||||
|
||||
let location = useLocation()
|
||||
let pathName = $location.pathname;
|
||||
|
|
@ -21,11 +22,23 @@
|
|||
|
||||
return str;
|
||||
}
|
||||
|
||||
let icon_url = null
|
||||
|
||||
$: if (avatarUsed === "proxy" || avatarUsed === "proxy_only") {
|
||||
if (member.webhook_avatar_url) icon_url = member.webhook_avatar_url
|
||||
else if (avatarUsed === "proxy_only") icon_url = default_avatar
|
||||
else icon_url = member.avatar_url || default_avatar
|
||||
} else {
|
||||
if (member.avatar_url) icon_url = member.avatar_url
|
||||
else if (avatarUsed === "avatar_only") icon_url = default_avatar
|
||||
else icon_url = member.webhook_avatar_url ?? default_avatar
|
||||
}
|
||||
</script>
|
||||
|
||||
<a href={getMemberPageUrl()} class="card-link rounded flex-1 mb-3" >
|
||||
<Card style={`border: 3px solid #${member.color}`} class="h-100" >
|
||||
<CardImg style="border-bottom-right-radius: 0; border-bottom-left-radius: 0;" src={member.avatar_url ? resizeMedia(member.avatar_url, [256, 256], "webp") : default_avatar} />
|
||||
<CardImg style="border-bottom-right-radius: 0; border-bottom-left-radius: 0;" src={icon_url ? resizeMedia(icon_url, [512, 512], "webp") : default_avatar} />
|
||||
<CardBody class="text-center p-2 d-flex flex-col align-items-center justify-content-center">
|
||||
<h3>
|
||||
<button class="button-reset" style="width: auto; height: 1em; cursor: pointer; margin-right: 0.25em;" id={`m-copy-${member.uuid}`} >
|
||||
|
|
|
|||
|
|
@ -32,7 +32,7 @@
|
|||
|
||||
<Card class="mb-4">
|
||||
<CardHeader>
|
||||
<CardsHeader bind:item={user}>
|
||||
<CardsHeader bind:item={user} type="system" avatarUsed="avatar_only">
|
||||
<div slot="icon" style="cursor: pointer;" id={`copy-${user.id}`} on:click|stopPropagation={() => copyShortLink()} on:keydown|stopPropagation={(e) => copyShortLink(e)} tabindex={0} >
|
||||
<FaAddressCard />
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -165,7 +165,7 @@
|
|||
{:else if group && group.id}
|
||||
<Card class="mb-4">
|
||||
<CardHeader>
|
||||
<CardsHeader item={group}>
|
||||
<CardsHeader item={group} type="group" avatarUsed="avatar">
|
||||
<div slot="icon" style="cursor: pointer;" id={`group-copy-${group.id}`} on:click|stopPropagation={() => copyShortLink()} on:keydown={(e) => copyShortLink(e)} tabindex={0} >
|
||||
<FaUsers slot="icon" />
|
||||
</div>
|
||||
|
|
@ -186,13 +186,13 @@
|
|||
<span class="itemcounter">{processedList.length} {pageOptions.type}s ({currentPage.length} shown)</span>
|
||||
<ListPagination bind:currentPage={pageOptions.currentPage} {pageAmount} />
|
||||
{#if pageOptions.view === "card"}
|
||||
<CardView {pageOptions} currentList={currentPage} />
|
||||
<CardView {pageOptions} currentList={currentPage} {listOptions} />
|
||||
{:else if pageOptions.view === "tiny"}
|
||||
<TinyView {pageOptions} currentList={currentPage} />
|
||||
<TinyView {pageOptions} currentList={currentPage} {listOptions} />
|
||||
{:else if pageOptions.view === "text"}
|
||||
<TextView {pageOptions} currentList={currentPage} {listOptions} />
|
||||
{:else}
|
||||
<ListView {pageOptions} currentList={currentPage} fullListLength={groupMembers.length}/>
|
||||
<ListView {pageOptions} currentList={currentPage} fullListLength={groupMembers.length} options={listOptions}/>
|
||||
{/if}
|
||||
<ListPagination bind:currentPage={pageOptions.currentPage} {pageAmount} />
|
||||
{/if}
|
||||
|
|
|
|||
|
|
@ -167,7 +167,7 @@
|
|||
{:else if member && member.id}
|
||||
<Card class="mb-4">
|
||||
<CardHeader>
|
||||
<CardsHeader item={member}>
|
||||
<CardsHeader item={member} avatarUsed="avatar" type="member">
|
||||
<div slot="icon" style="cursor: pointer;" id={`member-copy-${member.id}`} on:click|stopPropagation={() => copyShortLink()} on:keydown={(e) => copyShortLink(e)} tabindex={0} >
|
||||
<FaAddressCard slot="icon" />
|
||||
</div>
|
||||
|
|
@ -188,13 +188,13 @@
|
|||
<span class="itemcounter">{processedList.length} {pageOptions.type}s ({currentPage.length} shown)</span>
|
||||
<ListPagination bind:currentPage={pageOptions.currentPage} {pageAmount} />
|
||||
{#if pageOptions.view === "card"}
|
||||
<CardView {pageOptions} currentList={currentPage} />
|
||||
<CardView {pageOptions} currentList={currentPage} {listOptions} />
|
||||
{:else if pageOptions.view === "tiny"}
|
||||
<TinyView {pageOptions} currentList={currentPage} />
|
||||
<TinyView {pageOptions} currentList={currentPage} {listOptions} />
|
||||
{:else if pageOptions.view === "text"}
|
||||
<TextView {pageOptions} currentList={currentPage} {listOptions} />
|
||||
{:else}
|
||||
<ListView {pageOptions} currentList={currentPage} fullListLength={memberGroups.length}/>
|
||||
<ListView {pageOptions} currentList={currentPage} fullListLength={memberGroups.length} options={listOptions} />
|
||||
{/if}
|
||||
<ListPagination bind:currentPage={pageOptions.currentPage} {pageAmount} />
|
||||
{/if}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue