mirror of
https://github.com/PluralKit/PluralKit.git
synced 2026-02-04 13:06:50 +00:00
feat(dashboard): add text only list view
This commit is contained in:
parent
5083a181de
commit
e3dde36d0e
5 changed files with 184 additions and 23 deletions
|
|
@ -14,6 +14,7 @@
|
|||
import type { ListOptions, PageOptions } from './types';
|
||||
import { createShortList, filterList, getPageAmount, paginateList } from './functions';
|
||||
import TinyView from './TinyView.svelte';
|
||||
import TextView from './TextView.svelte';
|
||||
|
||||
$: memberList = getContext<Writable<Member[]>>("members")
|
||||
$: groupList = getContext<Writable<Group[]>>("groups")
|
||||
|
|
@ -89,6 +90,8 @@
|
|||
<CardView {pageOptions} currentList={currentGroups} />
|
||||
{:else if pageOptions.view === "tiny"}
|
||||
<TinyView {pageOptions} currentList={currentGroups} />
|
||||
{:else if pageOptions.view === "text"}
|
||||
<TextView {pageOptions} listOptions={options} currentList={currentGroups} />
|
||||
{:else}
|
||||
<ListView currentList={currentGroups} {pageOptions} {options} fullListLength={groups.length} />
|
||||
{/if}
|
||||
|
|
|
|||
|
|
@ -46,26 +46,11 @@ const dispatch = createEventDispatcher();
|
|||
function onViewChange(e: any) {
|
||||
resetPage();
|
||||
if (e.target?.value === 'card') {
|
||||
switch (pageOptions.itemsPerPage) {
|
||||
case 10: pageOptions.itemsPerPage = 12;
|
||||
break;
|
||||
case 25: pageOptions.itemsPerPage = 24;
|
||||
break;
|
||||
case 50: pageOptions.itemsPerPage = 60;
|
||||
break;
|
||||
default: pageOptions.itemsPerPage = 24;
|
||||
break;
|
||||
}
|
||||
} else if (e.target?.value === 'list') {
|
||||
switch (pageOptions.itemsPerPage) {
|
||||
case 12: pageOptions.itemsPerPage = 10;
|
||||
break;
|
||||
case 24: pageOptions.itemsPerPage = 25;
|
||||
break;
|
||||
case 60: pageOptions.itemsPerPage = 50;
|
||||
break;
|
||||
default: pageOptions.itemsPerPage = 25
|
||||
}
|
||||
pageOptions.itemsPerPage = 24
|
||||
} else if (e.target?.value === 'tiny') {
|
||||
pageOptions.itemsPerPage = 36
|
||||
} else {
|
||||
pageOptions.itemsPerPage = 25
|
||||
}
|
||||
dispatch("viewChange", e.target.value);
|
||||
}
|
||||
|
|
@ -179,11 +164,31 @@ function resetPage() {
|
|||
<option value="list">List</option>
|
||||
<option value="card">Cards</option>
|
||||
<option value="tiny">Tiny</option>
|
||||
<option value="text">Text</option>
|
||||
</Input>
|
||||
</InputGroup>
|
||||
</Col>
|
||||
<Col xs={12} md={6} lg={4} class="mb-2">
|
||||
<Link to={getRandomizerUrl()}><Button class="w-100" color="secondary" tabindex={-1} aria-label={`randomize ${pageOptions.type}s`}>Random {capitalizeFirstLetter(pageOptions.type)}</Button></Link>
|
||||
{#if pageOptions.view === "text"}
|
||||
<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>
|
||||
<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>
|
||||
{/if}
|
||||
</Col>
|
||||
</Row>
|
||||
<hr/>
|
||||
|
|
@ -452,5 +457,12 @@ function resetPage() {
|
|||
{/if}
|
||||
</Row>
|
||||
{/if}
|
||||
<hr/>
|
||||
<Row>
|
||||
<Col></Col>
|
||||
<Col xs={12} md={4} lg={3} class="mb-2">
|
||||
<Link to={getRandomizerUrl()}><Button class="w-100" color="secondary" tabindex={-1} aria-label={`randomize ${pageOptions.type}s`}>Random {capitalizeFirstLetter(pageOptions.type)}</Button></Link>
|
||||
</Col>
|
||||
</Row>
|
||||
</CardBody>
|
||||
</Card>
|
||||
|
|
@ -14,6 +14,7 @@
|
|||
import type { ListOptions, List, PageOptions } from './types';
|
||||
import { createShortList, filterList, getPageAmount, paginateList } from './functions';
|
||||
import TinyView from './TinyView.svelte';
|
||||
import TextView from './TextView.svelte';
|
||||
|
||||
$: memberList = getContext<Writable<Member[]>>("members")
|
||||
$: groupList = getContext<Writable<Group[]>>("groups")
|
||||
|
|
@ -89,6 +90,8 @@
|
|||
<CardView {pageOptions} currentList={currentMembers} />
|
||||
{:else if pageOptions.view === "tiny"}
|
||||
<TinyView {pageOptions} currentList={currentMembers} />
|
||||
{:else if pageOptions.view === "text"}
|
||||
<TextView {pageOptions} listOptions={options} currentList={currentMembers} />
|
||||
{:else}
|
||||
<ListView currentList={currentMembers} {pageOptions} {options} fullListLength={members.length} />
|
||||
{/if}
|
||||
|
|
|
|||
140
dashboard/src/components/list/TextView.svelte
Normal file
140
dashboard/src/components/list/TextView.svelte
Normal file
|
|
@ -0,0 +1,140 @@
|
|||
<script lang="ts">
|
||||
import { Col, Row, Tooltip } from "sveltestrap";
|
||||
import type { Member, Group } from "../../api/types"
|
||||
import type { ListOptions, PageOptions } from "./types";
|
||||
import { useLocation } from "svelte-navigator";
|
||||
import FaLock from "svelte-icons/fa/FaLock.svelte";
|
||||
import FaUserCircle from "svelte-icons/fa/FaUserCircle.svelte"
|
||||
import FaUsers from "svelte-icons/fa/FaUsers.svelte"
|
||||
import moment from 'moment';
|
||||
import parseMarkdown from '../../api/parse-markdown';
|
||||
import AwaitHtml from "../common/AwaitHtml.svelte";
|
||||
|
||||
export let currentList: Member[] & Group[]
|
||||
export let pageOptions: PageOptions
|
||||
export let listOptions: ListOptions
|
||||
|
||||
let copiedItems = {};
|
||||
|
||||
function getShortLink(id: string) {
|
||||
let url = "https://pk.mt"
|
||||
|
||||
if (pageOptions.type === "member") url += "/m/"
|
||||
else if (pageOptions.type === "group") url += "/g/"
|
||||
|
||||
url += id;
|
||||
|
||||
return url;
|
||||
}
|
||||
|
||||
async function copyShortLink(index: string, id: string, event?) {
|
||||
if (event) {
|
||||
if (event.key !== "Tab") event.preventDefault();
|
||||
event.stopPropagation();
|
||||
|
||||
let ctrlDown = event.ctrlKey||event.metaKey; // mac support
|
||||
if (!(ctrlDown && event.key === "c") && event.key !== "Enter") return;
|
||||
}
|
||||
try {
|
||||
await navigator.clipboard.writeText(getShortLink(id));
|
||||
|
||||
copiedItems[index] = copiedItems[index] || false;
|
||||
copiedItems[index] = true;
|
||||
await new Promise(resolve => setTimeout(resolve, 2000));
|
||||
copiedItems[index] = false;
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
}
|
||||
}
|
||||
|
||||
let location = useLocation()
|
||||
let pathName = $location.pathname;
|
||||
|
||||
function getItemPageUrl(item: Member|Group) {
|
||||
let str: string;
|
||||
if (pathName.startsWith("/dash")) str = "/dash";
|
||||
else str = "/profile";
|
||||
|
||||
str += pageOptions.type === "group" ? "/g" : "/m"
|
||||
str += `/${item.id}`;
|
||||
|
||||
return str;
|
||||
}
|
||||
</script>
|
||||
|
||||
<ol start={pageOptions.itemsPerPage * (pageOptions.currentPage - 1) + 1}>
|
||||
{#each currentList as item (item.uuid)}
|
||||
<li style="padding-left: 0.75rem">
|
||||
<Row class="justify-content-between">
|
||||
<Col xs={12} md="auto" class="d-flex align-items-center">
|
||||
<button class="button-reset" style="width: 1.1em; height: auto; cursor: pointer; display: flex; align-items: center;" 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 />
|
||||
{:else if pageOptions.type === "group"}
|
||||
<FaUsers />
|
||||
{:else}
|
||||
<FaUserCircle />
|
||||
{/if}
|
||||
</button>
|
||||
<span><a class="list-link" href={getItemPageUrl(item)}><b>{item.name}</b></a> ({item.id})</span>
|
||||
<Tooltip placement="top" target={`${pageOptions.type}-copy-${item.uuid}`}>{copiedItems[item.uuid] ? "Copied!" : "Copy public link"}</Tooltip>
|
||||
</Col>
|
||||
{#if listOptions.extra && item[listOptions.extra]}
|
||||
<Col xs={12} md="auto" class="mt-2 mt-md-0">
|
||||
<div class="align-text">
|
||||
{#if ["avatar_url", "webhook_avatar_url", "icon", "banner"].some(i => i === listOptions.extra)}
|
||||
<a href={item[listOptions.extra]}>{item[listOptions.extra].slice(0, 50)}...</a>
|
||||
{:else if listOptions.extra === "birthday"}
|
||||
birthday - {moment(item.birthday, "YYYY-MM-DD").format("MMM D, YYYY").replace(', 0004', '')}
|
||||
{:else if listOptions.extra === "created"}
|
||||
created on {moment(item.created, "YYYY-MM-DD").format("MMM D, YYYY").replace(', 0004', '')}
|
||||
{:else if ["pronouns", "display_name"].some(i => i === listOptions.extra)}
|
||||
<AwaitHtml htmlPromise={parseMarkdown(item[listOptions.extra], { embed: true, parseTimestamps: true})}/>
|
||||
{:else if listOptions.extra === "color"}
|
||||
<div class="d-flex align-items-center">
|
||||
#{item.color}
|
||||
<div style={`width: 1em; height: 1em; border-radius: 2px; margin-left: 0.5rem; background-color: #${item.color}`}></div>
|
||||
</div>
|
||||
{:else}
|
||||
{item[listOptions.extra]}
|
||||
{/if}
|
||||
</div>
|
||||
</Col>
|
||||
{/if}
|
||||
</Row>
|
||||
<hr class="my-2"/>
|
||||
</li>
|
||||
{/each}
|
||||
</ol>
|
||||
|
||||
<style>
|
||||
.list-link {
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.list-link:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.button-reset {
|
||||
background: none;
|
||||
color: inherit;
|
||||
border: none;
|
||||
padding: 0;
|
||||
font: inherit;
|
||||
cursor: pointer;
|
||||
outline: inherit;
|
||||
display: inline-block;
|
||||
margin-right: 0.75rem;
|
||||
}
|
||||
|
||||
.align-text {
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
@media (min-width: 768px) {
|
||||
.align-text {
|
||||
text-align: right;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
@ -57,7 +57,9 @@ export interface ListOptions {
|
|||
sort: 'name'|'description'|'birthday'|'pronouns'|'display_name'|'id'|'none'|'created' | 'color',
|
||||
order: "ascending"|"descending",
|
||||
show: "all"|"private"|"public",
|
||||
// so we can change the key for duplicate members on the randomize page
|
||||
|
||||
// text only view options
|
||||
extra: keyof Member | keyof Group | null
|
||||
}
|
||||
|
||||
export interface PageOptions {
|
||||
|
|
@ -125,7 +127,8 @@ export const defaultListOptions: ListOptions = {
|
|||
},
|
||||
sort: 'name',
|
||||
order: 'ascending',
|
||||
show: 'all'
|
||||
show: 'all',
|
||||
extra: 'display_name'
|
||||
}
|
||||
|
||||
export const defaultPageOptions: PageOptions = {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue