refactor(dashboard): revamp how lists work entirely

This commit is contained in:
Jake Fulmine
2022-11-27 19:27:07 +01:00
parent a62b6d53e2
commit 39e18704a6
19 changed files with 1090 additions and 613 deletions

View File

@@ -6,8 +6,9 @@
import SystemMain from '../../components/system/Main.svelte';
import List from '../../components/list/List.svelte';
import type { System } from '../../api/types';
import type { System, Member, Group } from '../../api/types';
import api from '../../api';
import { defaultListOptions, defaultPageOptions, type List as Lists, type ListOptions, type PageOptions } from '../../components/list/types';
// get the state from the navigator so that we know which tab to start on
let location = useLocation();
@@ -69,9 +70,38 @@
}
}
// some values that get passed from the member to the group components and vice versa
let members = [];
let groups = [];
let memberList: Lists<Member> = {
rawList: [],
processedList: [],
currentPage: [],
shortGroups: [],
shortMembers: [],
}
let groupList: Lists<Group> = {
rawList: [],
processedList: [],
currentPage: [],
shortGroups: [],
shortMembers: [],
}
let groupListOptions: ListOptions = JSON.parse(JSON.stringify(defaultListOptions));
let memberListOptions: ListOptions = JSON.parse(JSON.stringify(defaultListOptions));
let memberListPageOptions: PageOptions = {...defaultPageOptions,
view: listView,
isPublic: false,
type: 'member'
};
let groupListPageOptions: PageOptions = {...defaultPageOptions,
view: listView,
isPublic: false,
type: 'group'
};
</script>
@@ -89,11 +119,11 @@
<SystemMain bind:user={user} isPublic={false} />
</TabPane>
<TabPane tabId="members" tab="Members" active={tabPane === "members"}>
<List on:viewChange={(e) => navigateTo("members", e.detail)} bind:groups={groups} bind:members={members} isPublic={false} itemType={"member"} bind:view={listView} isDash={true} />
<List on:viewChange={(e) => navigateTo("members", e.detail)} bind:otherList={groupList} bind:lists={memberList} bind:pageOptions={memberListPageOptions} bind:options={memberListOptions} />
</TabPane>
<TabPane tabId="groups" tab="Groups" active={tabPane === "groups"}>
<List on:viewChange={(e) => navigateTo("members", e.detail)} bind:members={members} bind:groups={groups} isPublic={false} itemType={"group"} bind:view={listView} isDash={true} />
</TabPane>
<List on:viewChange={(e) => navigateTo("members", e.detail)} bind:otherList={memberList} bind:lists={groupList} bind:pageOptions={groupListPageOptions} bind:options={groupListOptions} />
</TabPane>
</TabContent>
</Col>
</Row>

View File

@@ -7,10 +7,13 @@
import type { Member, Group } from "../../../api/types";
import CardsHeader from "../../../components/common/CardsHeader.svelte";
import FaUsers from 'svelte-icons/fa/FaUsers.svelte';
import FaList from 'svelte-icons/fa/FaList.svelte';
import ListPagination from '../../../components/common/ListPagination.svelte';
import ListView from '../../../components/list/ListView.svelte';
import CardView from '../../../components/list/CardView.svelte';
import type { List as Lists, ListOptions, PageOptions } from '../../../components/list/types';
import { defaultListOptions, defaultPageOptions } from '../../../components/list/types';
import { filterList, paginateList, getPageAmount } from '../../../components/list/functions';
import PageControl from "../../../components/list/PageControl.svelte";
// get the state from the navigator so that we know which tab to start on
let location = useLocation();
@@ -24,27 +27,16 @@
let err = "";
let memberErr = "";
let group: Group;
let members: Member[] = [];
let systemMembers: Group[] = [];
let isDeleted = false;
let notOwnSystem = false;
let copied = false;
let pageAmount = 1;
const isPage = true;
export let isPublic = true;
let settings = JSON.parse(localStorage.getItem("pk-settings"));
let currentPage = 1;
let itemsPerPage = listView === "card" ? 12 : settings && settings.accessibility && settings.accessibility.expandedcards ? 5 : 10;
$: indexOfLastItem = currentPage * itemsPerPage;
$: indexOfFirstItem = indexOfLastItem - itemsPerPage;
$: pageAmount = Math.ceil(members.length / itemsPerPage);
$: orderedMembers = members.sort((a, b) => a.name.localeCompare(b.name));
$: slicedMembers = orderedMembers.slice(indexOfFirstItem, indexOfLastItem);
if (!isPublic && isPage) {
if (!isPublic) {
let user = localStorage.getItem("pk-user");
if (!user) navigate("/");
}
@@ -68,7 +60,6 @@
title = isPublic ? group.name : `${group.name} (dash)`;
}
memberLoading = true;
await new Promise(resolve => setTimeout(resolve, 1000));
fetchMembers();
} catch (error) {
console.log(error);
@@ -79,10 +70,9 @@
async function fetchMembers() {
try {
members = await api().groups($params.id).members().get({auth: !isPublic});
group.members = members.map(function(member) {return member.uuid});
lists.rawList = await api().groups($params.id).members().get({auth: !isPublic});
group.members = lists.rawList.map(function(member) {return member.uuid});
if (!isPublic) {
await new Promise(resolve => setTimeout(resolve, 1000));
systemMembers = await api().systems("@me").members.get({ auth: true });
}
memberErr = "";
@@ -94,25 +84,33 @@
}
}
async function updateMembers() {
memberLoading = true;
await new Promise(resolve => setTimeout(resolve, 500));
fetchMembers();
let lists: Lists<Member> = {
rawList: [],
processedList: [],
currentPage: [],
shortGroups: [],
shortMembers: [],
}
function updateDelete() {
isDeleted = true;
let nope: Lists<Group> = {
rawList: [],
processedList: [],
currentPage: [],
shortGroups: [],
shortMembers: [],
}
let listOptions: ListOptions = {...defaultListOptions};
function updateMemberList(event: any) {
members = members.map(member => member.id !== event.detail.id ? member : event.detail);
systemMembers = systemMembers.map(member => member.id !== event.detail.id ? member : event.detail);
}
function deleteMemberFromList(event: any) {
members = members.filter(member => member.id !== event.detail);
systemMembers = systemMembers.filter(member => member.id !== event.detail);
}
let pageOptions: PageOptions = {...defaultPageOptions,
view: listView,
isPublic: isPublic,
type: 'member',
isMain: false,
itemsPerPage: listView === 'card' ? 24 : 25
};
async function copyShortLink(event?) {
if (event) {
@@ -128,6 +126,19 @@
console.log(error);
}
}
$: lists.processedList = filterList(lists.rawList, listOptions);
$: lists.currentPage = paginateList(lists.processedList, pageOptions);
$: pageAmount = getPageAmount(lists.processedList, pageOptions);
function updateDelete(event: any) {
lists.rawList = lists.rawList.filter(m => m.id !== event.detail);
}
function update(event: any) {
lists.rawList = lists.rawList.map(m => m.id === event.detail.id ? m = event.detail : m);
}
</script>
{#if settings && settings.appearance.color_background && !notOwnSystem}
@@ -163,7 +174,7 @@
<Tooltip placement="top" target={`group-copy-${group.id}`}>{copied ? "Copied!" : "Copy public link"}</Tooltip>
</CardHeader>
<CardBody>
<Body on:deletion={updateDelete} on:updateMembers={updateMembers} bind:members={systemMembers} bind:group={group} isPage={isPage} isPublic={isPublic}/>
<Body bind:members={systemMembers} bind:group={group} isPage={true} isPublic={isPublic}/>
</CardBody>
</Card>
{/if}
@@ -171,23 +182,15 @@
<Alert color="primary"><Spinner size="sm" /> Fetching members...</Alert>
{:else if memberErr}
<Alert color="danger">{memberErr}</Alert>
{:else if members && members.length > 0}
<Card class="mb-2">
<CardHeader>
<CardTitle style="margin-top: 8px; outline: none;">
<div class="icon d-inline-block">
<FaList />
</div> Group list
</CardTitle>
</CardHeader>
</Card>
<ListPagination bind:currentPage bind:pageAmount />
{#if listView === "card"}
<CardView list={slicedMembers} {isPublic} itemType="member" isDash={false} />
{:else if lists.rawList && lists.rawList.length > 0}
<PageControl bind:options={listOptions} bind:pageOptions />
<ListPagination bind:currentPage={pageOptions.currentPage} {pageAmount} />
{#if pageOptions.view === "card"}
<CardView {pageOptions} {lists} otherList={nope} on:update={update} />
{:else}
<ListView on:deletion={(e) => deleteMemberFromList(e)} bind:list={slicedMembers} isPublic={isPublic} itemType="member" itemsPerPage={itemsPerPage} currentPage={currentPage} fullLength={members.length} />
<ListPagination bind:currentPage bind:pageAmount />
<ListView {pageOptions} {lists} otherList={nope} on:update={update} on:deletion={updateDelete} />
{/if}
<ListPagination bind:currentPage={pageOptions.currentPage} {pageAmount} />
{/if}
{/if}
</Col>

View File

@@ -8,9 +8,12 @@
import type { Member, Group } from "../../../api/types";
import CardsHeader from "../../../components/common/CardsHeader.svelte";
import FaAddressCard from 'svelte-icons/fa/FaAddressCard.svelte'
import FaList from 'svelte-icons/fa/FaList.svelte'
import ListPagination from '../../../components/common/ListPagination.svelte';
import CardView from '../../../components/list/CardView.svelte';
import type { List as Lists, ListOptions, PageOptions } from '../../../components/list/types';
import { defaultListOptions, defaultPageOptions } from '../../../components/list/types';
import { filterList, getPageAmount, paginateList } from '../../../components/list/functions';
import PageControl from "../../../components/list/PageControl.svelte";
// get the state from the navigator so that we know which tab to start on
let location = useLocation();
@@ -24,28 +27,16 @@
let err = "";
let groupErr = "";
let member: Member;
let groups: Group[] = [];
let systemGroups: Group[] = [];
let systemMembers: Member[] = [];
let isDeleted = false;
let notOwnSystem = false;
let copied = false;
let pageAmount = 1;
const isPage = true;
export let isPublic = true;
let settings = JSON.parse(localStorage.getItem("pk-settings"));
let currentPage = 1;
let itemsPerPage = listView === "card" ? 12 : settings && settings.accessibility && settings.accessibility.expandedcards ? 5 : 10;
$: indexOfLastItem = currentPage * itemsPerPage;
$: indexOfFirstItem = indexOfLastItem - itemsPerPage;
$: pageAmount = Math.ceil(groups.length / itemsPerPage);
$: orderedGroups = groups.sort((a, b) => a.name.localeCompare(b.name));
$: slicedGroups = orderedGroups.slice(indexOfFirstItem, indexOfLastItem);
if (!isPublic && isPage) {
if (!isPublic) {
let user = localStorage.getItem("pk-user");
if (!user) navigate("/");
}
@@ -69,7 +60,6 @@
title = isPublic ? member.name : `${member.name} (dash)`;
}
groupLoading = true;
await new Promise(resolve => setTimeout(resolve, 1000));
fetchGroups();
} catch (error) {
console.log(error);
@@ -80,19 +70,12 @@
async function fetchGroups() {
try {
groups = await api().members($params.id).groups().get({auth: !isPublic, query: { with_members: !isPublic } });
lists.rawList = await api().members($params.id).groups().get({auth: !isPublic, query: { with_members: !isPublic } });
if (!isPublic) {
await new Promise(resolve => setTimeout(resolve, 1000));
systemGroups = await api().systems("@me").groups.get({ auth: true, query: { with_members: true } });
}
groupErr = "";
groupLoading = false;
// we can't use with_members from a group list from a member endpoint yet, but I'm leaving this in in case we do
// (this is needed for editing a group member list from the member page)
/* if (!isPublic) {
await new Promise(resolve => setTimeout(resolve, 1000));
systemMembers = await api().systems("@me").members.get({auth: true});
} */
} catch (error) {
console.log(error);
groupErr = error.message;
@@ -100,20 +83,33 @@
}
}
async function updateGroups() {
groupLoading = true;
await new Promise(resolve => setTimeout(resolve, 500));
fetchGroups();
let lists: Lists<Group> = {
rawList: [],
processedList: [],
currentPage: [],
shortGroups: [],
shortMembers: [],
}
function updateDelete() {
isDeleted = true;
let nope: Lists<Member> = {
rawList: [],
processedList: [],
currentPage: [],
shortGroups: [],
shortMembers: [],
}
let listOptions: ListOptions = {...defaultListOptions};
function deleteGroupFromList(event: any) {
groups = groups.filter(group => group.id !== event.detail);
systemGroups = systemGroups.filter(group => group.id !== event.detail);
}
let pageOptions: PageOptions = {...defaultPageOptions,
view: listView,
isPublic: isPublic,
type: 'group',
isMain: false,
itemsPerPage: listView === 'card' ? 24 : 25
};
async function copyShortLink(event?) {
if (event) {
@@ -129,6 +125,18 @@
console.log(error);
}
}
$: lists.processedList = filterList(lists.rawList, listOptions);
$: lists.currentPage = paginateList(lists.processedList, pageOptions);
$: pageAmount = getPageAmount(lists.processedList, pageOptions);
function updateDelete(event: any) {
lists.rawList = lists.rawList.filter(m => m.id !== event.detail);
}
function update(event: any) {
lists.rawList = lists.rawList.map(m => m.id === event.detail.id ? m = event.detail : m);
}
</script>
{#if settings && settings.appearance.color_background && !notOwnSystem}
@@ -164,7 +172,7 @@
<Tooltip placement="top" target={`member-copy-${member.id}`}>{copied ? "Copied!" : "Copy public link"}</Tooltip>
</CardHeader>
<CardBody>
<Body on:deletion={updateDelete} on:updateGroups={updateGroups} bind:groups={systemGroups} bind:member={member} isPage={isPage} isPublic={isPublic}/>
<Body bind:groups={systemGroups} bind:member={member} isPage={true} isPublic={isPublic}/>
</CardBody>
</Card>
{/if}
@@ -172,23 +180,15 @@
<Alert color="primary"><Spinner size="sm" /> Fetching groups...</Alert>
{:else if groupErr}
<Alert color="danger">{groupErr}</Alert>
{:else if groups && groups.length > 0}
<Card class="mb-2">
<CardHeader>
<CardTitle style="margin-top: 8px; outline: none;">
<div class="icon d-inline-block">
<FaList />
</div> Member groups
</CardTitle>
</CardHeader>
</Card>
<ListPagination bind:currentPage bind:pageAmount />
{#if listView === "card"}
<CardView list={slicedGroups} {isPublic} itemType="group" isDash={false} />
{:else if lists.rawList && lists.rawList.length > 0}
<PageControl bind:options={listOptions} bind:pageOptions />
<ListPagination bind:currentPage={pageOptions.currentPage} {pageAmount} />
{#if pageOptions.view === "card"}
<CardView {pageOptions} {lists} otherList={nope} on:update={update} />
{:else}
<ListView on:deletion={(e) => deleteGroupFromList(e)} bind:list={slicedGroups} isPublic={isPublic} itemType="group" itemsPerPage={itemsPerPage} currentPage={currentPage} fullLength={groups.length} />
<ListPagination bind:currentPage bind:pageAmount />
<ListView {pageOptions} {lists} otherList={nope} on:update={update} on:deletion={updateDelete} />
{/if}
<ListPagination bind:currentPage={pageOptions.currentPage} {pageAmount} />
{/if}
{/if}
</Col>

View File

@@ -5,16 +5,14 @@
import SystemMain from '../../components/system/Main.svelte';
import List from '../../components/list/List.svelte';
import { defaultListOptions, defaultPageOptions, type List as Lists, type ListOptions, type PageOptions } from '../../components/list/types';
import type{ System } from '../../api/types';
import type{ Group, Member, System } from '../../api/types';
import api from '../../api';
let user: System = {};
let settings = JSON.parse(localStorage.getItem("pk-settings"));
let members = [];
let groups = [];
let params = useParams();
$: id = $params.id;
@@ -55,6 +53,40 @@
}
}
let memberList: Lists<Member> = {
rawList: [],
processedList: [],
currentPage: [],
shortGroups: [],
shortMembers: [],
}
let groupList: Lists<Group> = {
rawList: [],
processedList: [],
currentPage: [],
shortGroups: [],
shortMembers: [],
}
let groupListOptions: ListOptions = defaultListOptions;
let memberListOptions: ListOptions = defaultListOptions;
let pageOptions: PageOptions = defaultPageOptions;
let memberListPageOptions: PageOptions = {...pageOptions, ...{
view: listView,
isPublic: true,
type: 'member'
}};
let groupListPageOptions: PageOptions = {...pageOptions, ...{
view: listView,
isPublic: true,
type: 'group'
}};
</script>
<!-- display the banner if there's a banner set, and if the current settings allow for it-->
@@ -75,14 +107,14 @@
<Alert color="info" aria-hidden>You are currently <b>viewing</b> a system.</Alert>
<TabContent class="mt-3" on:tab={(e) => navigateTo(e.detail, listView)}>
<TabPane tabId="system" tab="System" active={tabPane === "system"}>
<SystemMain bind:user isPublic={true} />
<SystemMain bind:user={user} isPublic={false} />
</TabPane>
<TabPane tabId="members" tab="Members" active={tabPane === "members"}>
<List on:viewChange={(e) => navigateTo("members", e.detail)} members={members} groups={groups} isPublic={true} itemType={"member"} bind:view={listView} isDash={false}/>
<List on:viewChange={(e) => navigateTo("members", e.detail)} bind:otherList={groupList} bind:lists={memberList} bind:pageOptions={memberListPageOptions} bind:options={memberListOptions} />
</TabPane>
<TabPane tabId="groups" tab="Groups" active={tabPane === "groups"}>
<List on:viewChange={(e) => navigateTo("groups", e.detail)} members={members} groups={groups} isPublic={true} itemType={"group"} bind:view={listView} isDash={false} />
</TabPane>
<List on:viewChange={(e) => navigateTo("members", e.detail)} bind:otherList={memberList} bind:lists={groupList} bind:pageOptions={groupListPageOptions} bind:options={groupListOptions} />
</TabPane>
</TabContent>
{/if}
</Col>

View File

@@ -4,16 +4,19 @@
import { Alert, Col, Container, Row, Card, CardBody, CardHeader, CardTitle, Input, Label, Button, Accordion, AccordionHeader, AccordionItem } from 'sveltestrap';
import FaRandom from 'svelte-icons/fa/FaRandom.svelte'
import CardsList from '../../components/list/ListView.svelte';
import ListView from '../../components/list/ListView.svelte';
import api from '../../api';
import type { Group, Member } from '../../api/types';
import { defaultListOptions, type List as Lists, type ListOptions, type PageOptions } from '../../components/list/types';
import { defaultPageOptions } from '../../components/list/types';
import { filterList, paginateList } from '../../components/list/functions';
import CardView from '../../components/list/CardView.svelte';
export let isPublic: boolean = false;
export let type: string = "member";
export let pickFromGroup: boolean = false;
let list: Member[]|Group[] = [];
let randomList: Member[]|Group[] = [];
let loading = true;
let err: string;
@@ -21,7 +24,7 @@
let params = useParams();
$: id = $params.id;
$: groupId = $params.groupId;
let location = useLocation();
let searchParams = $location.search && new URLSearchParams($location.search);
@@ -41,10 +44,10 @@
let allowDoubles = false;
if (searchParams && searchParams.get("doubles") === "true") allowDoubles = true;
let listView = 'list';
if (searchParams && searchParams.get('view')) listView = searchParams.get('view');
// just a hidden option to expand the cards by default regardless of your global settings
let openByDefault = false;
if (searchParams && searchParams.get("open") === "true") openByDefault = true;
let rollCounter = 1;
@@ -70,7 +73,7 @@
list = res;
}
else throw new Error(`Unknown list type ${type}`);
randomList = randomizeList(amount, usePrivateMembers, allowDoubles);
lists.rawList = randomizeList(amount, usePrivateMembers, allowDoubles);
} catch (error) {
console.log(error);
err = error.message;
@@ -103,14 +106,15 @@
function rerollList() {
let amount = parseInt(optionAmount);
let paramArray = [];
if (amount > 1) paramArray.push(`view=${pageOptions.view}`);
if (amount > 1) paramArray.push(`amount=${amount}`);
if (optionAllowDoubles === "true") paramArray.push("doubles=true");
if (optionUsePrivateItems === "true") paramArray.push("all=true");
if (openByDefault === true) paramArray.push("open=true");
randomList = randomizeList(parseInt(optionAmount), optionUsePrivateItems, optionAllowDoubles);
lists.rawList = randomizeList(parseInt(optionAmount), optionUsePrivateItems, optionAllowDoubles);
navigate(`${path}${paramArray.length > 0 ? `?${paramArray.join('&')}` : ""}`);
rollCounter ++;
pageOptions.currentPage = rollCounter;
}
function capitalizeFirstLetter(string: string) {
@@ -125,20 +129,6 @@
let optionAllowDoubles = "false";
if (allowDoubles === true) optionAllowDoubles = "true";
function getItemLink(item: Member | Group): string {
let url: string;
if (isPublic) url = "/dash/";
else url = "/profile/";
if (type === "member") url += "m/";
else if (type === "group") url += "g/";
url += item.id;
return url;
}
function getBackUrl() {
let str: string;
if (isPublic) {
@@ -150,6 +140,39 @@
return str;
}
let lists: Lists<Group> = {
rawList: [],
processedList: [],
currentPage: [],
shortGroups: [],
shortMembers: [],
}
let nope: Lists<Member> = {
rawList: [],
processedList: [],
currentPage: [],
shortGroups: [],
shortMembers: [],
}
let pageOptions: PageOptions = {...defaultPageOptions,
isPublic: true,
type: type === 'group' ? 'group' : 'member',
isMain: false,
itemsPerPage: 5,
randomized: true,
view: listView === 'card' ? 'card' : 'list'
};
let listOptions: ListOptions = {...defaultListOptions,
sort: 'none',
};
$: lists.currentPage = lists.rawList;
</script>
<Container>
@@ -163,8 +186,8 @@
</div>Randomize {capitalizeFirstLetter(type)}s {isPublic && id ? `(${id})` : pickFromGroup ? `(${groupId})` : ""}</CardTitle>
</CardHeader>
<CardBody>
<Row>
<Col xs={12} lg={4} class="mb-2">
<Row class="mb-3">
<Col xs={12} md={6} lg={4} class="mb-2">
<Label>Amount:</Label>
<Input bind:value={optionAmount} type="select" aria-label="amount">
<option default={amount === 1}>1</option>
@@ -174,15 +197,22 @@
<option default={amount === 5}>5</option>
</Input>
</Col>
<Col xs={12} lg={4} class="mb-2">
<Col xs={12} md={6} lg={4} class="mb-2">
<Label>Allow duplicates:</Label>
<Input bind:value={optionAllowDoubles} type="select" aria-label="allow duplicates">
<option value="false" default={allowDoubles === false}>no</option>
<option value="true" default={allowDoubles === true}>yes</option>
</Input>
</Col>
<Col xs={12} md={6} lg={4} class="mb-2">
<Label>View:</Label>
<Input bind:value={pageOptions.view} type="select" aria-label="amount">
<option value="list">List</option>
<option value="card">Cards</option>
</Input>
</Col>
{#if !isPublic}
<Col xs={12} lg={4} class="mb-2">
<Col xs={12} md={6} lg={4} class="mb-2">
<Label>Use all {type}s:</Label>
<Input bind:value={optionUsePrivateItems} type="select" aria-label="include private members">
<option value="false" default={usePrivateItems === false}>no (only public {type}s)</option>
@@ -206,7 +236,11 @@
{:else if err}
<Alert color="danger">{err}</Alert>
{:else}
<CardsList openByDefault={openByDefault} bind:list={randomList} isPublic={true} itemType={type} itemsPerPage={5} currentPage={rollCounter} fullLength={5 * rollCounter - (5 - randomList.length)} />
{#if pageOptions.view === 'card'}
<CardView {lists} {pageOptions} otherList={nope} />
{:else}
<ListView {lists} {pageOptions} otherList={nope}/>
{/if}
{/if}
</Col>
</Row>