feat(dashboard): allow selecting which avatar to show in list

This commit is contained in:
Jake Fulmine 2023-09-21 13:23:44 +02:00
parent 94e848ee4c
commit 1777070694
14 changed files with 120 additions and 24 deletions

View file

@ -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);

View file

@ -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 />

View file

@ -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>

View file

@ -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 />

View file

@ -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}

View file

@ -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>

View file

@ -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"}

View file

@ -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 = {

View file

@ -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>

View file

@ -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`;

View file

@ -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}`} >

View file

@ -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>

View file

@ -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}

View file

@ -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}