list: fix activities sort, css improvements

fixed activities sorting.
less oddities from me not knowing how to use flex boxes. Also kinda
standardised a button-inside-input thing, so added a .gap-<n> >
.inside-input selector which works on gap-1 and gap-2.
This commit is contained in:
Harvey Tindall
2025-05-26 21:28:46 +01:00
parent 699cbee240
commit f6044578c0
9 changed files with 119 additions and 72 deletions

View File

@@ -470,3 +470,19 @@ section.section:not(.\~neutral) {
}
}
:root {
/* seems to be the sweet spot */
--inside-input-base: -2.6rem;
/* thought --spacing would do the trick but apparently not */
--tailwind-spacing: 0.25rem;
}
/* places buttons inside a sibling input element (hopefully), based on the flex gap of the parent. */
.gap-1 > .button.inside-input {
margin-left: calc(var(--inside-input-base) - 1.0*var(--tailwind-spacing));
}
.gap-2 > .button.inside-input {
margin-left: calc(var(--inside-input-base) - 2.0*var(--tailwind-spacing));
}

View File

@@ -715,7 +715,7 @@
</div>
</div>
<div id="tab-accounts" class="flex flex-col gap-4 unfocused">
<div class="card @low dark:~d_neutral accounts mb-4 overflow-visible">
<div class="card @low dark:~d_neutral accounts mb-4 overflow-visible flex flex-col gap-2">
<div id="accounts-filter-dropdown" class="dropdown manual z-10 w-full">
<div class="flex flex-col md:flex-row align-middle gap-2">
<div class="flex flex-row align-middle justify-between md:justify-normal">
@@ -724,7 +724,7 @@
</div>
<div class="flex flex-row align-middle w-full gap-2">
<input type="search" class="field ~neutral @low input search mr-2" id="accounts-search" placeholder="{{ .strings.search }}">
<span class="button ~neutral @low center ml-[-2.64rem] rounded-s-none accounts-search-clear" aria-label="{{ .strings.clearSearch }}" text="{{ .strings.clearSearch }}"><i class="ri-close-line"></i></span>
<span class="button ~neutral @low center inside-input rounded-s-none accounts-search-clear" aria-label="{{ .strings.clearSearch }}" text="{{ .strings.clearSearch }}"><i class="ri-close-line"></i></span>
<div class="tooltip left">
<button class="button ~info @low center h-full accounts-search-server flex flex-row gap-1" aria-label="{{ .strings.searchAllRecords }}" text="{{ .strings.searchAllRecords }}">
<i class="ri-search-line"></i>
@@ -741,14 +741,16 @@
</div>
</div>
</div>
<div class="supra py-1 sm hidden" id="accounts-search-options-header">{{ .strings.searchOptions }}</div>
<div class="row -mx-2 mb-2">
<button type="button" class="button ~neutral @low center mx-2 hidden"><span id="accounts-sort-by-field"></span> <i class="ri-close-line ml-2 text-2xl"></i></button>
<span id="accounts-filter-area"></span>
<div class="flex flex-row justify-between">
<div class="supra sm hidden" id="accounts-search-options-header">{{ .strings.searchOptions }}</div>
<div class="supra sm flex flex-row gap-2" id="accounts-record-counter"></div>
</div>
<div class="supra sm flex flex-row gap-2" id="accounts-record-counter"></div>
<div class="supra pt-1 pb-2 sm">{{ .strings.actions }}</div>
<div class="flex flex-row flex-wrap gap-3 mb-4">
<div class="flex flex-row gap-2 flex-wrap">
<div id="accounts-sort-by-field"></div>
<span id="accounts-filter-area" class="flex flex-row gap-2 flex-wrap"></span>
</div>
<div class="supra sm">{{ .strings.actions }}</div>
<div class="flex flex-row flex-wrap gap-3">
<span class="button ~neutral @low center " id="accounts-add-user">{{ .quantityStrings.addUser.Singular }}</span>
<div id="accounts-announce-dropdown" class="dropdown pb-0i " tabindex="0">
<span class="w-full button ~info @low center items-baseline" id="accounts-announce">{{ .strings.announce }}</span>
@@ -783,7 +785,7 @@
<span class="button ~info @low center unfocused " id="accounts-send-pwr">{{ .strings.sendPWR }}</span>
<span class="button ~critical @low center " id="accounts-delete-user">{{ .quantityStrings.deleteUser.Singular }}</span>
</div>
<div class="card @low accounts-header table-responsive mt-2">
<div class="card @low accounts-header table-responsive">
<table class="table text-base leading-5">
<thead>
<tr>
@@ -817,13 +819,13 @@
<span class="text-2xl font-medium italic text-center">{{ .strings.noResultsFound }}</span>
<span class="text-sm font-light italic unfocused text-center" id="accounts-no-local-results">{{ .strings.noResultsFoundLocally }}</span>
<div class="flex flex-row">
<button class="button ~neutral @low accounts-search-clear">
<span class="mr-2">{{ .strings.clearSearch }}</span><i class="ri-close-line"></i>
<button class="button ~neutral @low accounts-search-clear flex flex-row gap-2">
<span>{{ .strings.clearSearch }}</span><i class="ri-close-line"></i>
</button>
</div>
</div>
</div>
<div class="flex flex-row gap-2 m-2 justify-center">
<div class="flex flex-row gap-2 justify-center">
<button class="button ~neutral @low" id="accounts-load-more">{{ .strings.loadMore }}</button>
<button class="button ~neutral @low" id="accounts-load-all">{{ .strings.loadAll }}</button>
<button class="button ~info @low center accounts-search-server flex flex-row gap-1" aria-label="{{ .strings.searchAllRecords }}" text="{{ .strings.searchAllRecords }}">
@@ -835,7 +837,7 @@
</div>
</div>
<div id="tab-activity" class="flex flex-col gap-4 unfocused">
<div class="card @low dark:~d_neutral activity mb-4 overflow-visible">
<div class="card @low dark:~d_neutral activity mb-4 overflow-visible flex flex-col gap-2">
<div id="activity-filter-dropdown" class="dropdown manual z-10 w-full" tabindex="0">
<div class="flex flex-col md:flex-row align-middle gap-2">
<div class="flex flex-row align-middle justify-between md:justify-normal">
@@ -847,9 +849,12 @@
</div>
<div class="flex flex-row align-middle w-full gap-2">
<input type="search" class="field ~neutral @low input search mr-2" id="activity-search" placeholder="{{ .strings.search }}">
<span class="button ~neutral @low center ml-[-2.64rem] rounded-s-none activity-search-clear" aria-label="{{ .strings.clearSearch }}" text="{{ .strings.clearSearch }}"><i class="ri-close-line"></i></span>
<span class="button ~neutral @low center inside-input rounded-s-none activity-search-clear" aria-label="{{ .strings.clearSearch }}" text="{{ .strings.clearSearch }}"><i class="ri-close-line"></i></span>
<div class="tooltip left">
<button class="button ~info @low center h-full activity-search-server" aria-label="{{ .strings.searchAllRecords }}" text="{{ .strings.searchAllRecords }}"><i class="ri-search-line"></i></button>
<button class="button ~info @low center h-full activity-search-server flex flex-row gap-1" aria-label="{{ .strings.searchAllRecords }}" text="{{ .strings.searchAllRecords }}">
<i class="ri-search-line"></i>
<span>{{ .strings.searchAll }}</span>
</button>
<span class="content sm">{{ .strings.searchAllRecords }}</span>
</div>
<button class="button ~info @low" id="activity-refresh" aria-label="{{ .strings.refresh }}" disabled><i class="ri-refresh-line"></i></button>
@@ -861,37 +866,34 @@
</div>
</div>
</div>
<div class="flex flex-row justify-between pt-3 pb-2">
<div class="flex flex-row justify-between">
<div class="supra sm hidden" id="activity-search-options-header">{{ .strings.searchOptions }}</div>
<div class="supra sm flex flex-row gap-2" id="activity-record-counter"></div>
</div>
<div class="row -mx-2 mb-2">
<button type="button" class="button ~neutral @low center mx-2 hidden"><span id="activity-sort-by-field"></span> <i class="ri-close-line ml-2 text-2xl"></i></button>
<span id="activity-filter-area"></span>
<div class="flex flex-row gap-2 flex-wrap">
<span id="activity-filter-area" class="flex flex-row gap-2 flex-wrap"></span>
</div>
<div class="my-2">
<div id="activity-card-list"></div>
<div id="activity-loader"></div>
<div class="unfocused h-[100%] my-3" id="activity-not-found">
<div class="flex flex-col gap-2 h-[100%] justify-center items-center">
<span class="text-2xl font-medium italic mb-3 text-center">{{ .strings.noResultsFound }}</span>
<span class="text-sm font-light italic unfocused text-center" id="activity-no-local-results">{{ .strings.noResultsFoundLocally }}</span>
<div class="flex flex-row">
<button class="button ~neutral @low activity-search-clear">
<span class="mr-2">{{ .strings.clearSearch }}</span><i class="ri-close-line"></i>
</button>
<button class="button ~neutral @low unfocused" id="activity-keep-searching">{{ .strings.keepSearching }}</button>
</div>
<div class="unfocused h-[100%]" id="activity-not-found">
<div class="flex flex-col gap-2 h-[100%] justify-center items-center">
<span class="text-2xl font-medium italic text-center">{{ .strings.noResultsFound }}</span>
<span class="text-sm font-light italic unfocused text-center" id="activity-no-local-results">{{ .strings.noResultsFoundLocally }}</span>
<div class="flex flex-row">
<button class="button ~neutral @low activity-search-clear flex flex-row gap-2">
<span>{{ .strings.clearSearch }}</span><i class="ri-close-line"></i>
</button>
<button class="button ~neutral @low unfocused" id="activity-keep-searching">{{ .strings.keepSearching }}</button>
</div>
</div>
<div class="flex flex-row gap-2 m-2 justify-center">
<button class="button ~neutral @low" id="activity-load-more">{{ .strings.loadMore }}</button>
<button class="button ~neutral @low" id="activity-load-all">{{ .strings.loadAll }}</button>
<button class="button ~info @low center activity-search-server flex flex-row gap-1" aria-label="{{ .strings.searchAllRecords }}" text="{{ .strings.searchAllRecords }}">
<i class="ri-search-line"></i>
<span>{{ .strings.searchAllRecords }}</span>
</button>
</div>
</div>
<div id="activity-card-list"></div>
<div id="activity-loader"></div>
<div class="flex flex-row gap-2 justify-center">
<button class="button ~neutral @low" id="activity-load-more">{{ .strings.loadMore }}</button>
<button class="button ~neutral @low" id="activity-load-all">{{ .strings.loadAll }}</button>
<button class="button ~info @low center activity-search-server flex flex-row gap-1" aria-label="{{ .strings.searchAllRecords }}" text="{{ .strings.searchAllRecords }}">
<i class="ri-search-line"></i>
<span>{{ .strings.searchAllRecords }}</span>
</button>
</div>
</div>
</div>

View File

@@ -7,7 +7,6 @@ const logNormal = document.getElementById("log-normal") as HTMLInputElement;
const logSanitized = document.getElementById("log-sanitized") as HTMLInputElement;
const buttonChange = (type: string) => {
console.log("RUN");
if (type == "normal") {
logSanitized.classList.add("unfocused");
logNormal.classList.remove("unfocused");

View File

@@ -1062,7 +1062,7 @@ export class accountsList extends PaginatedList {
const profileSpan = this._enableReferralsProfile.nextElementSibling as HTMLSpanElement;
const inviteSpan = this._enableReferralsInvite.nextElementSibling as HTMLSpanElement;
const checkReferralSource = () => {
console.log("States:", this._enableReferralsProfile.checked, this._enableReferralsInvite.checked);
console.debug("States:", this._enableReferralsProfile.checked, this._enableReferralsInvite.checked);
if (this._enableReferralsProfile.checked) {
this._referralsInviteSelect.parentElement.classList.add("unfocused");
this._referralsProfileSelect.parentElement.classList.remove("unfocused")
@@ -1199,11 +1199,11 @@ export class accountsList extends PaginatedList {
document.dispatchEvent(new CustomEvent("header-click", { detail: this._c.defaultSortField }));
this._columns[this._c.defaultSortField].ascending = this._c.defaultSortAscending;
this._columns[this._c.defaultSortField].hideIcon();
this._sortingByButton.parentElement.classList.add("hidden");
this._sortingByButton.classList.add("hidden");
this._search.showHideSearchOptionsHeader();
};
this._sortingByButton.parentElement.addEventListener("click", defaultSort);
this._sortingByButton.addEventListener("click", defaultSort);
document.addEventListener("header-click", (event: CustomEvent) => {
this._search.setOrdering(
@@ -1211,8 +1211,8 @@ export class accountsList extends PaginatedList {
event.detail,
this._columns[event.detail].ascending
);
this._sortingByButton.innerHTML = this._columns[event.detail].buttonContent;
this._sortingByButton.parentElement.classList.remove("hidden");
this._sortingByButton.replaceChildren(this._columns[event.detail].asElement());
this._sortingByButton.classList.remove("hidden");
// console.log("ordering by", event.detail, ": ", this._ordering);
if (this._search.inSearch) {
this._search.onSearchBoxChange();
@@ -1436,7 +1436,7 @@ export class accountsList extends PaginatedList {
window.notifications.customSuccess("addUser", window.lang.var("notifications", "userCreated", `"${send['username']}"`));
if (!req.response["email"]) {
window.notifications.customError("sendWelcome", window.lang.notif("errorSendWelcomeEmail"));
console.log("User created, but welcome email failed");
console.error("User created, but welcome email failed");
}
} else {
let msg = window.lang.var("notifications", "errorUserCreated", `"${send['username']}"`);
@@ -1447,7 +1447,7 @@ export class accountsList extends PaginatedList {
window.notifications.customError("addUser", msg);
}
if (req.response["error"] as String) {
console.log(req.response["error"]);
console.error(req.response["error"]);
}
this.reload();
@@ -2081,7 +2081,7 @@ export class accountsList extends PaginatedList {
}
focusAccount = (userID: string) => {
console.log("focusing user", userID);
console.debug("focusing user", userID);
this._c.searchBox.value = `id:"${userID}"`;
this._search.onSearchBoxChange();
if (userID in this.users) this.users[userID].focus();
@@ -2116,6 +2116,8 @@ type Getter = () => GetterReturnType;
// Listen for broadcast event from others, check its not us by comparing the header className in the message, then hide the arrow icon
class Column {
private _header: HTMLTableCellElement;
private _card: HTMLElement;
private _cardSortingByIcon: HTMLElement;
private _name: string;
private _headerContent: string;
private _getter: Getter;
@@ -2133,10 +2135,8 @@ class Column {
this._header.addEventListener("click", () => {
// If we are the active sort column, a click means to switch between ascending/descending.
if (this._active) {
this._ascending = !this._ascending;
console.log("was already active, switching direction to", this._ascending ? "ascending" : "descending");
} else {
console.log("wasn't active keeping direction as", this._ascending ? "ascending" : "descending");
this.ascending = !this.ascending;
return;
}
this._active = true;
this._header.setAttribute("aria-sort", this._headerContent);
@@ -2150,6 +2150,15 @@ class Column {
this.hideIcon();
}
});
this._card = document.createElement("button");
this._card.classList.add("button", "~neutral", "@low", "center", "flex", "flex-row", "gap-1");
this._card.innerHTML = `
<i class="sorting-by-direction ri-arrow-up-s-line"></i>
<span class="font-bold">${window.lang.strings("sortingBy")}: </span><span>${this._headerContent}</span>
<i class="ri-close-line text-2xl"></i>
`;
this._cardSortingByIcon = this._card.querySelector(".sorting-by-direction");
}
hideIcon = () => {
@@ -2163,14 +2172,18 @@ class Column {
`;
}
// Returns the inner HTML to show in the "Sorting By" button.
get buttonContent() {
return `<i class="ri-arrow-${this.ascending ? "up" : "down"}-s-line mr-2"></i><span class="font-bold">` + window.lang.strings("sortingBy") + ": " + `</span>` + this._headerContent;
}
asElement = () => { return this._card };
get ascending() { return this._ascending; }
set ascending(v: boolean) {
this._ascending = v;
if (v) {
this._cardSortingByIcon.classList.add("ri-arrow-up-s-line");
this._cardSortingByIcon.classList.remove("ri-arrow-down-s-line");
} else {
this._cardSortingByIcon.classList.add("ri-arrow-down-s-line");
this._cardSortingByIcon.classList.remove("ri-arrow-up-s-line");
}
if (!this._active) return;
this.updateHeader();
this._header.setAttribute("aria-sort", this._headerContent);

View File

@@ -482,7 +482,6 @@ interface ActivitiesDTO extends paginatedDTO {
export class activityList extends PaginatedList {
protected _container: HTMLElement;
// protected _sortingByButton = document.getElementById("activity-sort-by-field") as HTMLButtonElement;
protected _sortDirection = document.getElementById("activity-sort-direction") as HTMLButtonElement;
protected _ascending: boolean;
@@ -580,14 +579,23 @@ export class activityList extends PaginatedList {
);
}
get ascending(): boolean { return this._ascending; }
get ascending(): boolean {
return this._ascending;
}
set ascending(v: boolean) {
this._ascending = v;
// Setting default sort makes sense, since this is the only sort ever being done.
this._c.defaultSortAscending = this.ascending;
this._sortDirection.innerHTML = `${window.lang.strings("sortDirection")} <i class="ri-arrow-${v ? "up" : "down"}-s-line ml-2"></i>`;
// FIXME?: We don't actually re-sort the list here, instead just use setOrdering to apply this.ascending before a reload.
this._search.setOrdering(this._search.ordering, this._c.defaultSortField, this.ascending);
if (this._hasLoaded) {
this.reload();
if (this._search.inServerSearch) {
// Re-run server search as new, since we changed the sort.
this._search.searchServer(true);
} else {
this.reload();
}
}
}

View File

@@ -286,7 +286,7 @@ export abstract class PaginatedList {
if (this._search.timeSearches) {
const totalTime = performance.now() - timer;
console.log(`setVisibility took ${totalTime}ms`);
console.debug(`setVisibility took ${totalTime}ms`);
}
}

View File

@@ -53,7 +53,7 @@ export class Login {
}
}, false, (req: XMLHttpRequest) => {
if (req.readyState == 4 && req.status == 404 && tryAgain) {
console.log("trying without URL Base...");
console.warn("logout failed, trying without URL Base...");
logoutFunc(this._endpoint, false);
}
});

View File

@@ -67,6 +67,7 @@ export abstract class Query {
protected _subject: QueryType;
protected _operator: QueryOperator;
protected _card: HTMLElement;
public type: string;
constructor(subject: QueryType | null, operator: QueryOperator) {
this._subject = subject;
@@ -110,8 +111,9 @@ export class BoolQuery extends Query {
protected _value: boolean;
constructor(subject: QueryType, value: boolean) {
super(subject, QueryOperator.Equal);
this.type = "bool";
this._value = value;
this._card.classList.add("button", "~" + (this._value ? "positive" : "critical"), "@high", "center", "mx-2", "h-full");
this._card.classList.add("button", "~" + (this._value ? "positive" : "critical"), "@high", "center");
this._card.innerHTML = `
<span class="font-bold mr-2">${subject.name}</span>
<i class="text-2xl ri-${this._value? "checkbox" : "close"}-circle-fill"></i>
@@ -151,8 +153,9 @@ export class StringQuery extends Query {
protected _value: string;
constructor(subject: QueryType, value: string) {
super(subject, QueryOperator.Equal);
this.type = "string";
this._value = value.toLowerCase();
this._card.classList.add("button", "~neutral", "@low", "center", "mx-2", "h-full");
this._card.classList.add("button", "~neutral", "@low", "center");
this._card.innerHTML = `
<span class="font-bold mr-2">${subject.name}:</span> "${this._value}"
`;
@@ -211,8 +214,9 @@ export class DateQuery extends Query {
constructor(subject: QueryType, operator: QueryOperator, value: ParsedDate) {
super(subject, operator);
this.type = "date";
this._value = value;
this._card.classList.add("button", "~neutral", "@low", "center", "m-2", "h-full");
this._card.classList.add("button", "~neutral", "@low", "center");
let dateText = QueryOperatorToDateText(operator);
this._card.innerHTML = `
<span class="font-bold mr-2">${subject.name}:</span> ${dateText != "" ? dateText+" " : ""}${value.text}
@@ -450,7 +454,7 @@ export class Search {
if (this.inServerSearch && !(q.localOnly)) continue;
let cachedResult = [...result];
if (q.subject.bool) {
if (q.type == "bool") {
for (let id of cachedResult) {
const u = this.items[id];
// Remove from result if not matching query
@@ -459,7 +463,7 @@ export class Search {
result.splice(result.indexOf(id), 1);
}
}
} else if (q.subject.string) {
} else if (q.type == "string") {
for (let id of cachedResult) {
const u = this.items[id];
// We want to compare case-insensitively, so we get value, lower-case it then compare,
@@ -469,7 +473,7 @@ export class Search {
result.splice(result.indexOf(id), 1);
}
}
} else if(q.subject.date) {
} else if (q.type == "date") {
for (let id of cachedResult) {
const u = this.items[id];
// Getter here returns a unix timestamp rather than a date, so we can't use compareItem.
@@ -503,7 +507,7 @@ export class Search {
if (this.timeSearches) {
const totalTime = performance.now() - timer;
console.log(`Search took ${totalTime}ms`);
console.debug(`Search took ${totalTime}ms`);
}
return result;
}
@@ -515,7 +519,7 @@ export class Search {
showHideSearchOptionsHeader = () => {
let sortingBy = false;
if (this._c.sortingByButton) sortingBy = !(this._c.sortingByButton.parentElement.classList.contains("hidden"));
if (this._c.sortingByButton) sortingBy = !(this._c.sortingByButton.classList.contains("hidden"));
const hasFilters = this._c.filterArea.textContent != "";
if (sortingBy || hasFilters) {
this._c.searchOptionsHeader.classList.remove("hidden");
@@ -672,9 +676,14 @@ export class Search {
onServerSearch = () => {
const newServerSearch = !this.inServerSearch;
this.inServerSearch = true;
this.searchServer(newServerSearch);
}
searchServer = (newServerSearch: boolean) => {
this._c.searchServer(this.serverSearchParams(this._searchTerms, this._queries), newServerSearch);
}
serverSearchParams = (searchTerms: string[], queries: Query[]): PaginatedReqDTO => {
let req: ServerSearchReqDTO = {
searchTerms: searchTerms,

View File

@@ -749,7 +749,7 @@ const setCardOrder = (messageCard: HTMLElement) => {
// addValue += side.length;
}
console.log("Shortest order:", minHeightPerm);
console.debug("Shortest order:", minHeightPerm);
};
const login = new Login(window.modals.login as Modal, "/my/", "opaque");