1
0
mirror of https://github.com/hrfee/jfa-go.git synced 2024-09-19 19:00:11 +00:00

Compare commits

...

8 Commits

Author SHA1 Message Date
bf12016315
accounts: make filter names translatable 2023-06-14 19:59:38 +01:00
b544931ee5
accounts: fix id filtering, make string translatable 2023-06-14 18:57:30 +01:00
9cef626b28
accounts: filter dropdown appears over announce one 2023-06-14 18:52:33 +01:00
708d382a3f
accounts: fix hiding of search options header for default sort 2023-06-14 18:43:46 +01:00
f24ea4a5f8
accounts: fix sizing of sorting by button 2023-06-14 18:41:47 +01:00
6ddd09ff1f
accounts: add header to "actions" and "search options" 2023-06-14 18:38:12 +01:00
ddc560e862
accounts: move filter button, add clear search
filter button now on left due to the dropdown being huge.
2023-06-14 17:36:41 +01:00
6f452c62de
accounts: fix search bugs, adjust top bar layout
search bar is now massive with a small filter button next to it.
Action buttons are on their own row.
Also fixed dealing with going from a search with filters in to an empty
one, search() is now called for any change at all to the input.
2023-06-14 17:15:24 +01:00
4 changed files with 110 additions and 70 deletions

View File

@ -490,10 +490,10 @@ a:hover:not(.lang-link):not(.\~urge), a:active:not(.lang-link):not(.\~urge) {
text-align: center; text-align: center;
} }
.search { /* .search {
max-width: 15rem; max-width: 15rem;
min-width: 10rem; min-width: 10rem;
} } */
td.img-circle { td.img-circle {
width: 32px; width: 32px;
@ -548,6 +548,11 @@ div.card:contains(section.banner.footer) {
padding-bottom: 0px !important padding-bottom: 0px !important
} }
.mx-0i {
margin-left: 0px !important;
margin-right: 0px !important
}
.text-center-i { .text-center-i {
text-align: center !important; text-align: center !important;
} }

View File

@ -580,49 +580,50 @@
</div> </div>
<div id="tab-accounts" class="unfocused"> <div id="tab-accounts" class="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">
<div class="flex-expand row"> <div class="flex-expand align-middle">
<div class="row w-6/12"> <span class="text-3xl font-bold mr-4">{{ .strings.accounts }}</span>
<span class="text-3xl font-bold mr-2 col">{{ .strings.accounts }}</span> <div id="accounts-filter-dropdown" class="dropdown z-10" tabindex="0">
<input type="search" class="col sm field ~neutral @low input search ml-2 mr-2" id="accounts-search" placeholder="{{ .strings.search }}"> <span class="h-100 button ~neutral @low center" id="accounts-filter-button">{{ .strings.filters }}</span>
<div id="accounts-filter-dropdown" class="col sm dropdown pb-0i" tabindex="0"> <div class="dropdown-display">
<span class="h-100 sm button ~neutral @low center mb-2" id="accounts-filter-button">{{ .strings.filters }}</span> <div class="card ~neutral @low mt-2" id="accounts-filter-list">
<div class="dropdown-display"> <p class="supra pb-2">{{ .strings.filters }}</p>
<div class="card ~neutral @low mt-2" id="accounts-filter-list">
<p class="supra pb-2">{{ .strings.filters }}</p>
</div>
</div> </div>
</div> </div>
</div> </div>
<div class="row"> <input type="search" class="field ~neutral @low input search ml-2 mr-2" id="accounts-search" placeholder="{{ .strings.search }}">
<span class="col sm button ~neutral @low center mb-2" id="accounts-add-user">{{ .quantityStrings.addUser.Singular }}</span> <span class="button ~neutral @low center -ml-8" id="accounts-search-clear" aria-label="{{ .strings.clearSearch }}" text="{{ .strings.clearSearch }}"><i class="ri-close-line"></i></span>
<div id="accounts-announce-dropdown" class="col sm dropdown pb-0i" tabindex="0">
<span class="h-100 sm button ~info @low center mb-2" id="accounts-announce">{{ .strings.announce }}</span>
<div class="dropdown-display">
<div class="card ~neutral @low">
<span class="supra sm">{{ .strings.templates }}</span>
<div id="accounts-announce-templates"></div>
</div>
</div>
</div>
<span class="col sm button ~urge @low center mb-2" id="accounts-modify-user">{{ .strings.modifySettings }}</span>
<span class="col sm button ~warning @low center mb-2" id="accounts-extend-expiry">{{ .strings.extendExpiry }}</span>
<div id="accounts-disable-enable-dropdown" class="col sm dropdown manual pb-0i" tabindex="0">
<span class="h-100 sm button ~positive @low center mb-2" id="accounts-disable-enable">{{ .strings.disable }}</span>
<div class="dropdown-display">
<div class="card ~neutral @low">
<span class="button ~urge sm full-width accounts-announce-template-button" id="accounts-enable-expiry">{{ .strings.setExpiry }}</span>
</div>
</div>
</div>
<span class="col sm button ~info @low center mb-2 unfocused" id="accounts-send-pwr">{{ .strings.sendPWR }}</span>
<span class="col sm button ~critical @low center mb-2" id="accounts-delete-user">{{ .quantityStrings.deleteUser.Singular }}</span>
</div>
</div> </div>
<div class="row"> <div class="supra py-1 sm hidden" id="accounts-search-options-header">{{ .strings.searchOptions }}</div>
<button type="button" class="button ~neutral @low center ml-2 mr-2 hidden"><span id="accounts-sort-by-field"></span> <span class="ml-2">&times;</span></button> <div class="row -mx-2">
<button type="button" class="button ~neutral @low center m-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> <span id="accounts-filter-area"></span>
</div> </div>
<div class="card @low accounts-header table-responsive mt-8"> <div class="supra py-1 sm">{{ .strings.actions }}</div>
<div class="row -mx-2">
<span class="col button ~neutral @low center max-w-[20%]" id="accounts-add-user">{{ .quantityStrings.addUser.Singular }}</span>
<div id="accounts-announce-dropdown" class="col dropdown pb-0i max-w-[20%]" tabindex="0">
<span class="w-100 button ~info @low center" id="accounts-announce">{{ .strings.announce }}</span>
<div class="dropdown-display">
<div class="card ~neutral @low">
<span class="supra sm">{{ .strings.templates }}</span>
<div id="accounts-announce-templates"></div>
</div>
</div>
</div>
<span class="col button ~urge @low center max-w-[20%]" id="accounts-modify-user">{{ .strings.modifySettings }}</span>
<span class="col button ~warning @low center max-w-[20%]" id="accounts-extend-expiry">{{ .strings.extendExpiry }}</span>
<div id="accounts-disable-enable-dropdown" class="col dropdown manual pb-0i max-w-[20%]" tabindex="0">
<span class="w-100 button ~positive @low center" id="accounts-disable-enable">{{ .strings.disable }}</span>
<div class="dropdown-display">
<div class="card ~neutral @low">
<span class="button ~urge full-width accounts-announce-template-button" id="accounts-enable-expiry">{{ .strings.setExpiry }}</span>
</div>
</div>
</div>
<span class="col button ~info @low center unfocused max-w-[20%]" id="accounts-send-pwr">{{ .strings.sendPWR }}</span>
<span class="col button ~critical @low center max-w-[20%]" id="accounts-delete-user">{{ .quantityStrings.deleteUser.Singular }}</span>
</div>
<div class="card @low accounts-header table-responsive mt-2">
<table class="table text-base leading-4"> <table class="table text-base leading-4">
<thead> <thead>
<tr> <tr>

View File

@ -119,7 +119,12 @@
"accessJFASettings": "Cannot be changed as either \"Admin Only\" or \"Allow All\" has been set in Settings > General.", "accessJFASettings": "Cannot be changed as either \"Admin Only\" or \"Allow All\" has been set in Settings > General.",
"sortingBy": "Sorting By", "sortingBy": "Sorting By",
"filters": "Filters", "filters": "Filters",
"clickToRemoveFilter": "Click to remove this filter." "clickToRemoveFilter": "Click to remove this filter.",
"clearSearch": "Clear search",
"actions": "Actions",
"searchOptions": "Search Options",
"matchText": "Match Text",
"jellyfinID": "Jellyfin ID"
}, },
"notifications": { "notifications": {
"changedEmailAddress": "Changed email address of {n}.", "changedEmailAddress": "Changed email address of {n}.",

View File

@ -39,6 +39,7 @@ interface announcementTemplate {
var addDiscord: (passData: string) => void; var addDiscord: (passData: string) => void;
class user implements User { class user implements User {
private _id = "";
private _row: HTMLTableRowElement; private _row: HTMLTableRowElement;
private _check: HTMLInputElement; private _check: HTMLInputElement;
private _username: HTMLSpanElement; private _username: HTMLSpanElement;
@ -67,7 +68,6 @@ class user implements User {
private _userLabel: string; private _userLabel: string;
private _labelEditButton: HTMLElement; private _labelEditButton: HTMLElement;
private _accounts_admin: HTMLInputElement private _accounts_admin: HTMLInputElement
id = "";
private _selected: boolean; private _selected: boolean;
lastNotifyMethod = (): string => { lastNotifyMethod = (): string => {
@ -690,6 +690,9 @@ class user implements User {
} }
}); });
get id() { return this._id; }
set id(v: string) { this._id = v; }
update = (user: User) => { update = (user: User) => {
this.id = user.id; this.id = user.id;
@ -768,6 +771,8 @@ export class accountsList {
private _activeSortColumn: string; private _activeSortColumn: string;
private _sortingByButton = document.getElementById("accounts-sort-by-field") as HTMLButtonElement; private _sortingByButton = document.getElementById("accounts-sort-by-field") as HTMLButtonElement;
private _filterArea = document.getElementById("accounts-filter-area");
private _searchOptionsHeader = document.getElementById("accounts-search-options-header");
// Whether the "Extend expiry" is extending or setting an expiry. // Whether the "Extend expiry" is extending or setting an expiry.
private _settingExpiry = false; private _settingExpiry = false;
@ -790,30 +795,42 @@ export class accountsList {
} }
} }
showHideSearchOptionsHeader = () => {
const sortingBy = !(this._sortingByButton.parentElement.classList.contains("hidden"));
const hasFilters = this._filterArea.textContent != "";
console.log("sortingBy", sortingBy, "hasFilters", hasFilters);
if (sortingBy || hasFilters) {
this._searchOptionsHeader.classList.remove("hidden");
} else {
this._searchOptionsHeader.classList.add("hidden");
}
}
private _queries: { [field: string]: { name: string, getter: string, bool: boolean, string: boolean, date: boolean, dependsOnTableHeader?: string, show?: boolean }} = { private _queries: { [field: string]: { name: string, getter: string, bool: boolean, string: boolean, date: boolean, dependsOnTableHeader?: string, show?: boolean }} = {
"id": { "id": {
name: "Jellyfin ID", // We don't use a translation here to circumvent the name substitution feature.
name: "Jellyfin/Emby ID",
getter: "id", getter: "id",
bool: false, bool: false,
string: true, string: true,
date: false date: false
}, },
"label": { "label": {
name: "Label", name: window.lang.strings("label"),
getter: "label", getter: "label",
bool: true, bool: true,
string: true, string: true,
date: false date: false
}, },
"username": { "username": {
name: "Username", name: window.lang.strings("username"),
getter: "name", getter: "name",
bool: false, bool: false,
string: true, string: true,
date: false date: false
}, },
"name": { "name": {
name: "Username", name: window.lang.strings("username"),
getter: "name", getter: "name",
bool: false, bool: false,
string: true, string: true,
@ -821,21 +838,21 @@ export class accountsList {
show: false show: false
}, },
"admin": { "admin": {
name: "Admin", name: window.lang.strings("admin"),
getter: "admin", getter: "admin",
bool: true, bool: true,
string: false, string: false,
date: false date: false
}, },
"disabled": { "disabled": {
name: "Disabled", name: window.lang.strings("disabled"),
getter: "disabled", getter: "disabled",
bool: true, bool: true,
string: false, string: false,
date: false date: false
}, },
"access-jfa": { "access-jfa": {
name: "Access jfa-go", name: window.lang.strings("accessJFA"),
getter: "accounts_admin", getter: "accounts_admin",
bool: true, bool: true,
string: false, string: false,
@ -843,7 +860,7 @@ export class accountsList {
dependsOnTableHeader: "accounts-header-access-jfa" dependsOnTableHeader: "accounts-header-access-jfa"
}, },
"email": { "email": {
name: "Email", name: window.lang.strings("emailAddress"),
getter: "email", getter: "email",
bool: true, bool: true,
string: true, string: true,
@ -875,7 +892,7 @@ export class accountsList {
dependsOnTableHeader: "accounts-header-discord" dependsOnTableHeader: "accounts-header-discord"
}, },
"expiry": { "expiry": {
name: "Expiry", name: window.lang.strings("expiry"),
getter: "expiry", getter: "expiry",
bool: true, bool: true,
string: false, string: false,
@ -883,7 +900,7 @@ export class accountsList {
dependsOnTableHeader: "accounts-header-expiry" dependsOnTableHeader: "accounts-header-expiry"
}, },
"last-active": { "last-active": {
name: "Last Active", name: window.lang.strings("lastActiveTime"),
getter: "last_active", getter: "last_active",
bool: true, bool: true,
string: false, string: false,
@ -892,8 +909,8 @@ export class accountsList {
} }
search = (query: String): string[] => { search = (query: String): string[] => {
const filterArea = document.getElementById("accounts-filter-area"); console.log(this._queries);
filterArea.textContent = ""; this._filterArea.textContent = "";
query = query.toLowerCase(); query = query.toLowerCase();
let result: string[] = [...this._ordering]; let result: string[] = [...this._ordering];
@ -970,7 +987,7 @@ export class accountsList {
// FIXME: Generate filter card for each filter class // FIXME: Generate filter card for each filter class
const filterCard = document.createElement("span"); const filterCard = document.createElement("span");
filterCard.ariaLabel = window.lang.strings("clickToRemoveFilter"); filterCard.ariaLabel = window.lang.strings("clickToRemoveFilter");
filterCard.classList.add("button", "~" + (boolState ? "positive" : "critical"), "@high", "center", "ml-2", "mr-2"); filterCard.classList.add("button", "~" + (boolState ? "positive" : "critical"), "@high", "center", "m-2");
filterCard.innerHTML = ` filterCard.innerHTML = `
<span class="font-bold mr-2">${queryFormat.name}</span> <span class="font-bold mr-2">${queryFormat.name}</span>
<i class="text-2xl ri-${boolState? "checkbox" : "close"}-circle-fill"></i> <i class="text-2xl ri-${boolState? "checkbox" : "close"}-circle-fill"></i>
@ -983,7 +1000,7 @@ export class accountsList {
this._search.oninput((null as Event)); this._search.oninput((null as Event));
}) })
filterArea.appendChild(filterCard); this._filterArea.appendChild(filterCard);
// console.log("is bool, state", boolState); // console.log("is bool, state", boolState);
// So removing elements doesn't affect us // So removing elements doesn't affect us
@ -1004,7 +1021,7 @@ export class accountsList {
if (queryFormat.string) { if (queryFormat.string) {
const filterCard = document.createElement("span"); const filterCard = document.createElement("span");
filterCard.ariaLabel = window.lang.strings("clickToRemoveFilter"); filterCard.ariaLabel = window.lang.strings("clickToRemoveFilter");
filterCard.classList.add("button", "~neutral", "@low", "center", "ml-2", "mr-2", "h-full"); filterCard.classList.add("button", "~neutral", "@low", "center", "m-2", "h-full");
filterCard.innerHTML = ` filterCard.innerHTML = `
<span class="font-bold mr-2">${queryFormat.name}:</span> "${split[1]}" <span class="font-bold mr-2">${queryFormat.name}:</span> "${split[1]}"
`; `;
@ -1017,7 +1034,7 @@ export class accountsList {
this._search.oninput((null as Event)); this._search.oninput((null as Event));
}) })
filterArea.appendChild(filterCard); this._filterArea.appendChild(filterCard);
let cachedResult = [...result]; let cachedResult = [...result];
for (let id of cachedResult) { for (let id of cachedResult) {
@ -1048,7 +1065,7 @@ export class accountsList {
const filterCard = document.createElement("span"); const filterCard = document.createElement("span");
filterCard.ariaLabel = window.lang.strings("clickToRemoveFilter"); filterCard.ariaLabel = window.lang.strings("clickToRemoveFilter");
filterCard.classList.add("button", "~neutral", "@low", "center", "ml-2", "mr-2", "h-full"); filterCard.classList.add("button", "~neutral", "@low", "center", "m-2", "h-full");
filterCard.innerHTML = ` filterCard.innerHTML = `
<span class="font-bold mr-2">${queryFormat.name}:</span> ${(compareType == 1) ? window.lang.strings("after")+" " : ((compareType == -1) ? window.lang.strings("before")+" " : "")}${split[1]} <span class="font-bold mr-2">${queryFormat.name}:</span> ${(compareType == 1) ? window.lang.strings("after")+" " : ((compareType == -1) ? window.lang.strings("before")+" " : "")}${split[1]}
`; `;
@ -1062,7 +1079,7 @@ export class accountsList {
this._search.oninput((null as Event)); this._search.oninput((null as Event));
}) })
filterArea.appendChild(filterCard); this._filterArea.appendChild(filterCard);
let cachedResult = [...result]; let cachedResult = [...result];
for (let id of cachedResult) { for (let id of cachedResult) {
@ -1138,10 +1155,10 @@ export class accountsList {
this._modifySettings.classList.add("unfocused"); this._modifySettings.classList.add("unfocused");
this._deleteUser.classList.add("unfocused"); this._deleteUser.classList.add("unfocused");
if (window.emailEnabled || window.telegramEnabled) { if (window.emailEnabled || window.telegramEnabled) {
this._announceButton.classList.add("unfocused"); this._announceButton.parentElement.classList.add("unfocused");
} }
this._extendExpiry.classList.add("unfocused"); this._extendExpiry.classList.add("unfocused");
this._disableEnable.classList.add("unfocused"); this._disableEnable.parentElement.classList.add("unfocused");
this._sendPWR.classList.add("unfocused"); this._sendPWR.classList.add("unfocused");
} else { } else {
let visibleCount = 0; let visibleCount = 0;
@ -1161,7 +1178,7 @@ export class accountsList {
this._deleteUser.classList.remove("unfocused"); this._deleteUser.classList.remove("unfocused");
this._deleteUser.textContent = window.lang.quantity("deleteUser", list.length); this._deleteUser.textContent = window.lang.quantity("deleteUser", list.length);
if (window.emailEnabled || window.telegramEnabled) { if (window.emailEnabled || window.telegramEnabled) {
this._announceButton.classList.remove("unfocused"); this._announceButton.parentElement.classList.remove("unfocused");
} }
let anyNonExpiries = list.length == 0 ? true : false; let anyNonExpiries = list.length == 0 ? true : false;
let allNonExpiries = true; let allNonExpiries = true;
@ -1179,7 +1196,7 @@ export class accountsList {
} }
if (showDisableEnable && this._users[id].disabled != this._shouldEnable) { if (showDisableEnable && this._users[id].disabled != this._shouldEnable) {
showDisableEnable = false; showDisableEnable = false;
this._disableEnable.classList.add("unfocused"); this._disableEnable.parentElement.classList.add("unfocused");
} }
if (!showDisableEnable && anyNonExpiries) { break; } if (!showDisableEnable && anyNonExpiries) { break; }
if (!this._users[id].lastNotifyMethod()) { if (!this._users[id].lastNotifyMethod()) {
@ -1215,7 +1232,7 @@ export class accountsList {
this._disableEnable.classList.add("~warning"); this._disableEnable.classList.add("~warning");
this._disableEnable.classList.remove("~positive"); this._disableEnable.classList.remove("~positive");
} }
this._disableEnable.classList.remove("unfocused"); this._disableEnable.parentElement.classList.remove("unfocused");
this._disableEnable.textContent = message; this._disableEnable.textContent = message;
} }
} }
@ -1764,13 +1781,13 @@ export class accountsList {
this._deleteUser.classList.add("unfocused"); this._deleteUser.classList.add("unfocused");
this._announceButton.onclick = this.announce; this._announceButton.onclick = this.announce;
this._announceButton.classList.add("unfocused"); this._announceButton.parentElement.classList.add("unfocused");
this._extendExpiry.onclick = () => { this.extendExpiry(); }; this._extendExpiry.onclick = () => { this.extendExpiry(); };
this._extendExpiry.classList.add("unfocused"); this._extendExpiry.classList.add("unfocused");
this._disableEnable.onclick = this.enableDisableUsers; this._disableEnable.onclick = this.enableDisableUsers;
this._disableEnable.classList.add("unfocused"); this._disableEnable.parentElement.classList.add("unfocused");
this._enableExpiry.onclick = () => { this.extendExpiry(true); }; this._enableExpiry.onclick = () => { this.extendExpiry(true); };
this._enableExpiryNotify.onchange = () => { this._enableExpiryNotify.onchange = () => {
@ -1796,17 +1813,26 @@ export class accountsList {
this._deleteNotify.checked = false; this._deleteNotify.checked = false;
}*/ }*/
this._search.oninput = () => { const onchange = () => {
const query = this._search.value; const query = this._search.value;
if (!query) { if (!query) {
this.setVisibility(this._ordering, true); // this.setVisibility(this._ordering, true);
this._inSearch = false; this._inSearch = false;
} else { } else {
this._inSearch = true; this._inSearch = true;
this.setVisibility(this.search(query), true); // this.setVisibility(this.search(query), true);
} }
this.setVisibility(this.search(query), true);
this._checkCheckCount(); this._checkCheckCount();
this.showHideSearchOptionsHeader();
}; };
this._search.oninput = onchange;
const clearSearchButton = document.getElementById("accounts-search-clear") as HTMLSpanElement;
clearSearchButton.addEventListener("click", () => {
this._search.value = "";
onchange();
});
this._announceTextarea.onkeyup = this.loadPreview; this._announceTextarea.onkeyup = this.loadPreview;
addDiscord = newDiscordSearch(window.lang.strings("linkDiscord"), window.lang.strings("searchDiscordUser"), window.lang.strings("add"), (user: DiscordUser, id: string) => { addDiscord = newDiscordSearch(window.lang.strings("linkDiscord"), window.lang.strings("searchDiscordUser"), window.lang.strings("add"), (user: DiscordUser, id: string) => {
@ -1846,6 +1872,7 @@ export class accountsList {
this._columns[this._activeSortColumn].ascending = true; this._columns[this._activeSortColumn].ascending = true;
this._columns[this._activeSortColumn].hideIcon(); this._columns[this._activeSortColumn].hideIcon();
this._sortingByButton.parentElement.classList.add("hidden"); this._sortingByButton.parentElement.classList.add("hidden");
this.showHideSearchOptionsHeader();
}; };
this._sortingByButton.parentElement.addEventListener("click", defaultSort); this._sortingByButton.parentElement.addEventListener("click", defaultSort);
@ -1861,9 +1888,11 @@ export class accountsList {
} else { } else {
this.setVisibility(this.search(this._search.value), true); this.setVisibility(this.search(this._search.value), true);
} }
this.showHideSearchOptionsHeader();
}); });
defaultSort(); defaultSort();
this.showHideSearchOptionsHeader();
const filterList = document.getElementById("accounts-filter-list"); const filterList = document.getElementById("accounts-filter-list");
@ -1910,7 +1939,7 @@ export class accountsList {
const button = document.createElement("button") as HTMLButtonElement; const button = document.createElement("button") as HTMLButtonElement;
button.type = "button"; button.type = "button";
button.classList.add("button", "~urge", "ml-2"); button.classList.add("button", "~urge", "ml-2");
button.innerHTML = `<i class="ri-equal-line mr-2"></i>Match Text`; button.innerHTML = `<i class="ri-equal-line mr-2"></i>${window.lang.strings("matchText")}`;
// Position cursor between quotes // Position cursor between quotes
button.addEventListener("click", () => fillInFilter(queryName, `""`, -1)); button.addEventListener("click", () => fillInFilter(queryName, `""`, -1));