feat(dashboard): add text only list view

This commit is contained in:
Jake Fulmine 2023-08-20 20:02:03 +02:00
parent 5083a181de
commit e3dde36d0e
5 changed files with 184 additions and 23 deletions

View file

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

View file

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

View file

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

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

View file

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