Merge branch 'main' of lokilocker.com:SomeGuy/sessioncommunities.online

pull/29/head
mdPlusPlus 3 years ago
commit dfd2d463af

@ -14,7 +14,7 @@ export const dom = {
export const COLUMN = {
IDENTIFIER: 0, LANGUAGE: 1, NAME: 2,
DESCRIPTION: 3, USERS: 4, PREVIEW: 5,
QR_CODE: 6, JOIN_URL: 7
QR_CODE: 6, SERVER_ICON: 7, JOIN_URL: 8
};
// Reverse enum.
@ -33,7 +33,7 @@ export const ATTRIBUTES = {
ACTIVE: 'data-sort',
ASCENDING: 'data-sort-asc',
COLUMN: 'data-sorted-by',
COLUMN_LITERAL: 'sorted-by'
// COLUMN_LITERAL: 'sorted-by'
}
};
@ -41,20 +41,33 @@ export function columnAscendingByDefault(column) {
return column != COLUMN.USERS;
}
export function columnIsSortable(column) { return column != COLUMN.QR_CODE; }
export function columnNeedsCasefold(column) {
return [
COLUMN.IDENTIFIER,
COLUMN.NAME,
COLUMN.DESCRIPTION
export function columnIsSortable(column) {
return ![
COLUMN.QR_CODE,
COLUMN.PREVIEW,
// Join URL contents are not guaranteed to have visible text.
COLUMN.JOIN_URL
].includes(column);
}
export function columnIsNumeric(column) {
return [
COLUMN.USERS
].includes(column);
/**
* @type {Record<string, (el: HTMLTableCellElement) => any>}
*/
const TRANSFORMATION = {
numeric: (el) => parseInt(el.innerText),
casefold: (el) => el.innerText.toLowerCase().trim(),
tokenData: (el) => el.getAttribute("data-token")
}
/**
* @type {Dictionary<number, (el: HTMLTableCellElement) => any>}
*/
export const COLUMN_TRANSFORMATION = {
[COLUMN.USERS]: TRANSFORMATION.numeric,
[COLUMN.IDENTIFIER]: TRANSFORMATION.casefold,
[COLUMN.NAME]: TRANSFORMATION.casefold,
[COLUMN.DESCRIPTION]: TRANSFORMATION.casefold,
[COLUMN.SERVER_ICON]: TRANSFORMATION.tokenData
}
/**
@ -64,22 +77,22 @@ export function columnIsNumeric(column) {
* @returns {HTMLElement}
*/
function createElement(tag, ...args) {
const element = document.createElement(tag);
if (args.length === 0) return element;
const propsCandidate = args[0];
if (typeof propsCandidate !== "string" && !(propsCandidate instanceof Element)) {
// args[0] is not child element or text node
// must be props object
Object.assign(element, propsCandidate);
args.shift();
}
element.append(...args);
return element;
const element = document.createElement(tag);
if (args.length === 0) return element;
const propsCandidate = args[0];
if (typeof propsCandidate !== "string" && !(propsCandidate instanceof Element)) {
// args[0] is not child element or text node
// must be props object
Object.assign(element, propsCandidate);
args.shift();
}
element.append(...args);
return element;
}
export const element = new Proxy({}, {
get(_, key) {
return (...args) => createElement(key, ...args)
}
get(_, key) {
return (...args) => createElement(key, ...args)
}
});

@ -16,8 +16,7 @@
// Import magic numbers and data
import {
dom, COLUMN, COLUMN_LITERAL, COMPARISON, ATTRIBUTES,
columnAscendingByDefault, columnIsSortable, columnNeedsCasefold,
columnIsNumeric, element
columnAscendingByDefault, columnIsSortable, COLUMN_TRANSFORMATION, element
} from './js/constants.js';
// Hidden communities for transparency.
@ -192,21 +191,12 @@ function makeRowComparer(column, ascending) {
}
// Callback to obtain sortable content from cell text.
let contentToSortable = (text) => text.trim();
if (columnNeedsCasefold(column)) {
// Make certain columns sort regardless of casing.
contentToSortable = (text) => text.toLowerCase().trim();
}
else if (columnIsNumeric(column)) {
// Make certain columns sort on parsed numeric value instead of text.
contentToSortable = (text) => parseInt(text);
}
const columnToSortable = COLUMN_TRANSFORMATION[column] ?? ((el) => el.innerText.trim());
// Construct comparer using derived property to determine sort order.
const rowComparer = compareProp(
ascending ? compareAscending : compareDescending,
row => contentToSortable(row.children[column].innerText)
row => columnToSortable(row.children[column])
);
return rowComparer;
@ -248,17 +238,22 @@ function setSortState(table, { ascending, column }) {
}
table.setAttribute(ATTRIBUTES.SORTING.ASCENDING, ascending);
table.setAttribute(ATTRIBUTES.SORTING.COLUMN, column);
// This can be used to style column headers in a consistent way, i.e.
// #tbl_communities[data-sort-asc=true][sorted-by=name]::after #th_name, ...
table.setAttribute(ATTRIBUTES.SORTING.COLUMN_LITERAL, COLUMN_LITERAL[column]);
// No way around this for brief CSS.
const headers = table.querySelectorAll("th");
headers.forEach((th, colno) => {
th.removeAttribute(ATTRIBUTES.SORTING.ACTIVE);
});
headers[column].setAttribute(ATTRIBUTES.SORTING.ACTIVE, true);
}
// This is best done in JS, as it would require <noscript> styles otherwise.
function markSortableColumns() {
const table = dom.tbl_communities();
for (const th of table.querySelectorAll('th')) {
if (th.id.includes("qr_code")) continue;
th.classList.add('sortable');
const header_cells = table.querySelectorAll('th');
for (let colno = 0; colno < header_cells.length; colno++) {
if (!columnIsSortable(colno)) continue;
header_cells[colno].classList.add('sortable');
}
}

@ -32,7 +32,48 @@ header {
flex-grow: 1;
}
#tbl_communities { width:100%; }
#tbl_communities {
/* Browser defaults. */
--cell-padding-h: 1px;
--cell-padding-v: 1px;
width:100%;
}
#tbl_communities th {
white-space: nowrap;
}
#tbl_communities :is(th, td) {
padding: var(--cell-padding-v) var(--cell-padding-h);
}
#tbl_communities th.sortable {
position: relative;
padding-right: calc( 1.5em + var(--cell-padding-h) );
}
#tbl_communities th.sortable::after {
position: absolute;
right: 0.25em;
top: 50%;
transform: translateY(-50%);
/* content: "\25C7"; */ /* White diamond */
/* content: "\2195"; */ /* Up-down arrow */
/* content: "\25A1"; */ /* White square */
/* content: "\25B8"; */ /* Small right pointing triangle */
content: "\2B25"; /* Black medium diamond */
color: grey;
}
#tbl_communities[data-sort-asc=true] th[data-sort=true]::after {
content: "\25B2"; /* Black up pointing triangle */
color: initial;
}
#tbl_communities[data-sort-asc=false] th[data-sort=true]::after {
content: "\25BC"; /* Black up pointing triangle */
color: initial;
}
#toggle-show-room-ids:not(:checked)
~ #tbl_communities :is(#th_identifier, .td_identifier) {
@ -43,6 +84,10 @@ header {
font-family: monospace;
}
.td_language {
text-align: center;
font-size: 1.25em;
}
.td_language:empty::after {
content: "\2753";
}
@ -59,6 +104,27 @@ header {
.td_users { text-align: right; }
.td_preview { text-align: center; }
.td_server_icon { text-align: center; }
.td_server_icon-circle {
display: flex;
align-items: center;
justify-content: center;
width: 2em;
height: 2em;
border-radius: 2em;
font-family: sans-serif;
margin: 0 auto;
color: white;
text-shadow: 0 0 0.5em #000;
box-shadow: 0 0 0.05em #777;
}
.td_server_icon-circle span {
position: relative;
top: 0.05em;
}
.td_join_url {
font-family: monospace;
white-space: nowrap;
@ -84,7 +150,7 @@ header {
display: flex;
flex-direction: row;
align-items: center;
justify-content: space-between;
justify-content: space-around;
}
.copy_button { font-size: inherit }
@ -232,3 +298,4 @@ label[for=toggle-show-room-ids]::after {
to {bottom: 0; opacity: 0;}
}

@ -3,14 +3,15 @@
// Once handlers are attached in JS, this check ceases to be useful.
function column_sortable($id) {
return $id != "qr";
// Join URL contents are not guaranteed to have visible text.
return $id != "qr" && $id != "preview" && $id != "join_url";
}
function sort_onclick($colno) {
global $TABLE_COLUMNS;
$column = $TABLE_COLUMNS[$colno];
if (!column_sortable($column['id'])) return "";
return " onclick='sortTable($colno)'";
return " onclick='sortTable($colno)' title='Click to sort this column'";
}
$TABLE_COLUMNS = [
@ -21,6 +22,7 @@
['id' => "users", 'name' => "Users"],
['id' => "preview", 'name' => "Preview"],
['id' => "qr", 'name' => "QR"],
['id' => "server_icon", 'name' => "Server"],
['id' => "join_url", 'name' => "Join URL"],
];
?>
@ -35,6 +37,22 @@
<?php endforeach; ?>
</tr>
<?php foreach ($rooms as $id => $room): ?>
<?php
// Get the server public key.
// FIXME:
// ! This is bad practice.
// However, the fetching code hides component data
// and this is a low risk use case.
$token = explode("=", $room->join_link)[1];
$icon_hue = hexdec($token[2] . $token[2]);
$icon_color = "hsl($icon_hue, 80%, 50%)";
$hostname = explode("//", $room->join_link)[1];
$hostname = explode("/", $hostname)[0];
?>
<tr id="<?=$id?>">
<td class="td_identifier"><?=$id?></td>
<td class="td_language"><?=$room->language?></td>
@ -60,6 +78,14 @@
alt="Pictogram of a QR code"
>
</td>
<td class="td_server_icon"
data-token="<?=$token?>"
title="<?=$hostname?> (<?=$token?>)"
>
<div class="td_server_icon-circle" style="background-color: <?=$icon_color?>">
<span><?=strtoupper($token[0] . $token[1])?></span>
</div>
</td>
<td class="td_join_url">
<div class="join_url_container" data-url="<?=$room->join_link?>">
<a class="join_url show-from-w5" title="<?=$room->join_link?>"

Loading…
Cancel
Save