mirror of
https://github.com/4gray/iptvnator.git
synced 2026-10-10 18:36:15 -08:00
fix(portal): address category sort review feedback
This commit is contained in:
1 parent
daff96d90b
commit
c5815650c2
24 files changed
+131
-32
No files matched your search
@@ -38,6 +38,10 @@ export async function getCategories(
|
||||
playlistId: string,
|
||||
type: 'live' | 'movies' | 'series'
|
||||
) {
|
||||
// Xtream categories are inserted once in provider order and existing
|
||||
// xtream IDs are preserved, so row id order represents server order.
|
||||
// If partial category re-inserts are added later, persist a provider
|
||||
// sort index instead of relying on the insertion id.
|
||||
return db
|
||||
.select()
|
||||
.from(schema.categories)
|
||||
|
||||
@@ -792,6 +792,8 @@
|
||||
"SORT_NAME_ASC": "الاسم (أ-ي)",
|
||||
"SORT_NAME_DESC": "الاسم (ي-أ)",
|
||||
"SORT_LABEL": "ترتيب: ",
|
||||
"SORT_SERVER": "ترتيب الخادم",
|
||||
"CATEGORY_SORT_ARIA": "ترتيب الفئات",
|
||||
"DASHBOARD": {
|
||||
"CONTINUE_WATCHING": "متابعة المشاهدة",
|
||||
"UNTITLED_SOURCE": "مصدر بدون عنوان",
|
||||
|
||||
@@ -792,6 +792,8 @@
|
||||
"SORT_NAME_ASC": "الاسم أ-ي",
|
||||
"SORT_NAME_DESC": "الاسم ي-أ",
|
||||
"SORT_LABEL": "ترتيب: ",
|
||||
"SORT_SERVER": "ترتيب السيرفر",
|
||||
"CATEGORY_SORT_ARIA": "ترتيب التصنيفات",
|
||||
"DASHBOARD": {
|
||||
"CONTINUE_WATCHING": "كمل المشاهدة",
|
||||
"UNTITLED_SOURCE": "مصدر بلا عنوان",
|
||||
|
||||
@@ -792,6 +792,8 @@
|
||||
"SORT_NAME_ASC": "Назва А-Я",
|
||||
"SORT_NAME_DESC": "Назва Я-А",
|
||||
"SORT_LABEL": "Сартаваць: ",
|
||||
"SORT_SERVER": "Парадак сервера",
|
||||
"CATEGORY_SORT_ARIA": "Сартаваць катэгорыі",
|
||||
"DASHBOARD": {
|
||||
"CONTINUE_WATCHING": "Працягнуць прагляд",
|
||||
"UNTITLED_SOURCE": "Крыніца без назвы",
|
||||
|
||||
@@ -792,6 +792,8 @@
|
||||
"SORT_NAME_ASC": "Name A-Z",
|
||||
"SORT_NAME_DESC": "Name Z-A",
|
||||
"SORT_LABEL": "Sortierung: ",
|
||||
"SORT_SERVER": "Serverreihenfolge",
|
||||
"CATEGORY_SORT_ARIA": "Kategorien sortieren",
|
||||
"DASHBOARD": {
|
||||
"CONTINUE_WATCHING": "Weiter schauen",
|
||||
"UNTITLED_SOURCE": "Unbenannte Quelle",
|
||||
|
||||
@@ -792,6 +792,8 @@
|
||||
"SORT_NAME_ASC": "Όνομα A-Z",
|
||||
"SORT_NAME_DESC": "Όνομα Z-A",
|
||||
"SORT_LABEL": "Ταξινόμηση: ",
|
||||
"SORT_SERVER": "Σειρά διακομιστή",
|
||||
"CATEGORY_SORT_ARIA": "Ταξινόμηση κατηγοριών",
|
||||
"DASHBOARD": {
|
||||
"CONTINUE_WATCHING": "Συνέχιση παρακολούθησης",
|
||||
"UNTITLED_SOURCE": "Πηγή χωρίς τίτλο",
|
||||
|
||||
@@ -798,6 +798,8 @@
|
||||
"SORT_NAME_ASC": "Name A-Z",
|
||||
"SORT_NAME_DESC": "Name Z-A",
|
||||
"SORT_LABEL": "Sort: ",
|
||||
"SORT_SERVER": "Server sorting",
|
||||
"CATEGORY_SORT_ARIA": "Sort categories",
|
||||
"DASHBOARD": {
|
||||
"CONTINUE_WATCHING": "Continue Watching",
|
||||
"UNTITLED_SOURCE": "Untitled source",
|
||||
|
||||
@@ -792,6 +792,8 @@
|
||||
"SORT_NAME_ASC": "Nombre A-Z",
|
||||
"SORT_NAME_DESC": "Nombre Z-A",
|
||||
"SORT_LABEL": "Ordenar: ",
|
||||
"SORT_SERVER": "Orden del servidor",
|
||||
"CATEGORY_SORT_ARIA": "Ordenar categorías",
|
||||
"DASHBOARD": {
|
||||
"CONTINUE_WATCHING": "Continuar viendo",
|
||||
"UNTITLED_SOURCE": "Fuente sin título",
|
||||
|
||||
@@ -792,6 +792,8 @@
|
||||
"SORT_NAME_ASC": "Nom A-Z",
|
||||
"SORT_NAME_DESC": "Nom Z-A",
|
||||
"SORT_LABEL": "Trier : ",
|
||||
"SORT_SERVER": "Ordre du serveur",
|
||||
"CATEGORY_SORT_ARIA": "Trier les catégories",
|
||||
"DASHBOARD": {
|
||||
"CONTINUE_WATCHING": "Reprendre la lecture",
|
||||
"UNTITLED_SOURCE": "Source sans titre",
|
||||
|
||||
@@ -792,6 +792,8 @@
|
||||
"SORT_NAME_ASC": "Nome A-Z",
|
||||
"SORT_NAME_DESC": "Nome Z-A",
|
||||
"SORT_LABEL": "Ordina: ",
|
||||
"SORT_SERVER": "Ordine del server",
|
||||
"CATEGORY_SORT_ARIA": "Ordina categorie",
|
||||
"DASHBOARD": {
|
||||
"CONTINUE_WATCHING": "Continua a guardare",
|
||||
"UNTITLED_SOURCE": "Sorgente senza titolo",
|
||||
|
||||
@@ -792,6 +792,8 @@
|
||||
"SORT_NAME_ASC": "名前 A-Z",
|
||||
"SORT_NAME_DESC": "名前 Z-A",
|
||||
"SORT_LABEL": "並び替え:",
|
||||
"SORT_SERVER": "サーバー順",
|
||||
"CATEGORY_SORT_ARIA": "カテゴリを並び替え",
|
||||
"DASHBOARD": {
|
||||
"CONTINUE_WATCHING": "視聴を再開",
|
||||
"UNTITLED_SOURCE": "無題のソース",
|
||||
|
||||
@@ -792,6 +792,8 @@
|
||||
"SORT_NAME_ASC": "이름 A-Z",
|
||||
"SORT_NAME_DESC": "이름 Z-A",
|
||||
"SORT_LABEL": "정렬: ",
|
||||
"SORT_SERVER": "서버 순서",
|
||||
"CATEGORY_SORT_ARIA": "카테고리 정렬",
|
||||
"DASHBOARD": {
|
||||
"CONTINUE_WATCHING": "이어서 시청",
|
||||
"UNTITLED_SOURCE": "제목 없는 소스",
|
||||
|
||||
@@ -792,6 +792,8 @@
|
||||
"SORT_NAME_ASC": "Naam A-Z",
|
||||
"SORT_NAME_DESC": "Naam Z-A",
|
||||
"SORT_LABEL": "Sorteren: ",
|
||||
"SORT_SERVER": "Servervolgorde",
|
||||
"CATEGORY_SORT_ARIA": "Categorieën sorteren",
|
||||
"DASHBOARD": {
|
||||
"CONTINUE_WATCHING": "Verder kijken",
|
||||
"UNTITLED_SOURCE": "Naamloze bron",
|
||||
|
||||
@@ -792,6 +792,8 @@
|
||||
"SORT_NAME_ASC": "Nazwa A-Z",
|
||||
"SORT_NAME_DESC": "Nazwa Z-A",
|
||||
"SORT_LABEL": "Sortuj: ",
|
||||
"SORT_SERVER": "Kolejność serwera",
|
||||
"CATEGORY_SORT_ARIA": "Sortuj kategorie",
|
||||
"DASHBOARD": {
|
||||
"CONTINUE_WATCHING": "Kontynuuj oglądanie",
|
||||
"UNTITLED_SOURCE": "Źródło bez tytułu",
|
||||
|
||||
@@ -792,6 +792,8 @@
|
||||
"SORT_NAME_ASC": "Nome A-Z",
|
||||
"SORT_NAME_DESC": "Nome Z-A",
|
||||
"SORT_LABEL": "Ordenar: ",
|
||||
"SORT_SERVER": "Ordem do servidor",
|
||||
"CATEGORY_SORT_ARIA": "Ordenar categorias",
|
||||
"DASHBOARD": {
|
||||
"CONTINUE_WATCHING": "Continuar assistindo",
|
||||
"UNTITLED_SOURCE": "Fonte sem título",
|
||||
|
||||
@@ -796,6 +796,8 @@
|
||||
"SORT_NAME_ASC": "Имя (А-Я)",
|
||||
"SORT_NAME_DESC": "Имя (Я-А)",
|
||||
"SORT_LABEL": "Сортировка: ",
|
||||
"SORT_SERVER": "Порядок сервера",
|
||||
"CATEGORY_SORT_ARIA": "Сортировать категории",
|
||||
"DASHBOARD": {
|
||||
"CONTINUE_WATCHING": "Продолжить просмотр",
|
||||
"UNTITLED_SOURCE": "Источник без названия",
|
||||
|
||||
@@ -792,6 +792,8 @@
|
||||
"SORT_NAME_ASC": "İsim A-Z",
|
||||
"SORT_NAME_DESC": "İsim Z-A",
|
||||
"SORT_LABEL": "Sırala: ",
|
||||
"SORT_SERVER": "Sunucu sıralaması",
|
||||
"CATEGORY_SORT_ARIA": "Kategorileri sırala",
|
||||
"DASHBOARD": {
|
||||
"CONTINUE_WATCHING": "İzlemeye Devam Et",
|
||||
"UNTITLED_SOURCE": "Adsız kaynak",
|
||||
|
||||
@@ -792,6 +792,8 @@
|
||||
"SORT_NAME_ASC": "名称 A-Z",
|
||||
"SORT_NAME_DESC": "名称 Z-A",
|
||||
"SORT_LABEL": "排序:",
|
||||
"SORT_SERVER": "服务器排序",
|
||||
"CATEGORY_SORT_ARIA": "排序分类",
|
||||
"DASHBOARD": {
|
||||
"CONTINUE_WATCHING": "继续观看",
|
||||
"UNTITLED_SOURCE": "未命名源",
|
||||
|
||||
@@ -792,6 +792,8 @@
|
||||
"SORT_NAME_ASC": "名稱 A-Z",
|
||||
"SORT_NAME_DESC": "名稱 Z-A",
|
||||
"SORT_LABEL": "排序:",
|
||||
"SORT_SERVER": "伺服器排序",
|
||||
"CATEGORY_SORT_ARIA": "排序分類",
|
||||
"DASHBOARD": {
|
||||
"CONTINUE_WATCHING": "繼續觀看",
|
||||
"UNTITLED_SOURCE": "未命名的來源",
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import {
|
||||
DEFAULT_PORTAL_CATEGORY_SORT_MODE,
|
||||
WORKSPACE_CATEGORY_SORT_STORAGE_KEY,
|
||||
getPortalCategorySortModeLabel,
|
||||
isPortalCategorySortMode,
|
||||
persistPortalCategorySortMode,
|
||||
restorePortalCategorySortMode,
|
||||
@@ -31,15 +30,11 @@ describe('portal category sort', () => {
|
||||
expect(restorePortalCategorySortMode()).toBe('name-desc');
|
||||
});
|
||||
|
||||
it('recognizes valid modes and labels them for the category menu', () => {
|
||||
it('recognizes valid category sort modes', () => {
|
||||
expect(isPortalCategorySortMode('server')).toBe(true);
|
||||
expect(isPortalCategorySortMode('name-asc')).toBe(true);
|
||||
expect(isPortalCategorySortMode('name-desc')).toBe(true);
|
||||
expect(isPortalCategorySortMode('date-desc')).toBe(false);
|
||||
|
||||
expect(getPortalCategorySortModeLabel('server')).toBe('Server sorting');
|
||||
expect(getPortalCategorySortModeLabel('name-asc')).toBe('A-Z');
|
||||
expect(getPortalCategorySortModeLabel('name-desc')).toBe('Z-A');
|
||||
});
|
||||
|
||||
it('preserves input order for server sorting and supports A-Z/Z-A sorting', () => {
|
||||
|
||||
@@ -32,20 +32,6 @@ export function persistPortalCategorySortMode(
|
||||
localStorage.setItem(storageKey, mode);
|
||||
}
|
||||
|
||||
export function getPortalCategorySortModeLabel(
|
||||
mode: PortalCategorySortMode
|
||||
): string {
|
||||
if (mode === 'name-asc') {
|
||||
return 'A-Z';
|
||||
}
|
||||
|
||||
if (mode === 'name-desc') {
|
||||
return 'Z-A';
|
||||
}
|
||||
|
||||
return 'Server sorting';
|
||||
}
|
||||
|
||||
export function sortPortalCategoryItems<T>(
|
||||
items: readonly T[],
|
||||
mode: PortalCategorySortMode,
|
||||
|
||||
+11
-4
@@ -27,10 +27,15 @@
|
||||
categorySortMode() !== 'server'
|
||||
"
|
||||
[matMenuTriggerFor]="categorySortMenu"
|
||||
[matTooltip]="'Sort: ' + categorySortLabel()"
|
||||
aria-label="Sort categories"
|
||||
[matTooltip]="
|
||||
('WORKSPACE.SORT_LABEL' | translate) +
|
||||
(categorySortLabelKey() | translate)
|
||||
"
|
||||
[attr.aria-label]="
|
||||
'WORKSPACE.CATEGORY_SORT_ARIA' | translate
|
||||
"
|
||||
>
|
||||
<mat-icon>sort_by_alpha</mat-icon>
|
||||
<mat-icon>{{ categorySortIcon() }}</mat-icon>
|
||||
</button>
|
||||
<mat-menu #categorySortMenu="matMenu">
|
||||
@for (
|
||||
@@ -42,7 +47,9 @@
|
||||
(click)="setCategorySortMode(option.mode)"
|
||||
>
|
||||
<mat-icon>{{ option.icon }}</mat-icon>
|
||||
<span>{{ option.label }}</span>
|
||||
<span>{{
|
||||
option.translationKey | translate
|
||||
}}</span>
|
||||
@if (categorySortMode() === option.mode) {
|
||||
<mat-icon class="context-sort-check"
|
||||
>check</mat-icon
|
||||
|
||||
+54
@@ -18,6 +18,11 @@ const translations: Record<string, string> = {
|
||||
'WORKSPACE.CONTEXT.RADIO_CATEGORIES': 'Radio Categories',
|
||||
'WORKSPACE.CONTEXT.XTREAM_SYNCING_LIVE': 'Syncing live categories...',
|
||||
'WORKSPACE.CONTEXT.XTREAM_SYNCING_MOVIES': 'Syncing movie categories...',
|
||||
'WORKSPACE.CATEGORY_SORT_ARIA': 'Sort categories',
|
||||
'WORKSPACE.SORT_LABEL': 'Sort: ',
|
||||
'WORKSPACE.SORT_NAME_ASC': 'Name A-Z',
|
||||
'WORKSPACE.SORT_NAME_DESC': 'Name Z-A',
|
||||
'WORKSPACE.SORT_SERVER': 'Server sorting',
|
||||
'WORKSPACE.SHELL.XTREAM_IMPORT_LOADING':
|
||||
'Fetching playlist data from source...',
|
||||
};
|
||||
@@ -302,6 +307,55 @@ describe('WorkspaceContextPanelComponent', () => {
|
||||
);
|
||||
});
|
||||
|
||||
it('uses translated category sort labels and distinct mode icons', () => {
|
||||
fixture.componentRef.setInput('section', 'vod');
|
||||
xtreamSelectedTypeContentState.set('ready');
|
||||
fixture.detectChanges();
|
||||
|
||||
const sortButton = Array.from(
|
||||
fixture.nativeElement.querySelectorAll('.context-header__action')
|
||||
).at(1) as HTMLButtonElement | undefined;
|
||||
|
||||
expect(sortButton?.getAttribute('aria-label')).toBe('Sort categories');
|
||||
expect(fixture.componentInstance.categorySortLabelKey()).toBe(
|
||||
'WORKSPACE.SORT_SERVER'
|
||||
);
|
||||
expect(fixture.componentInstance.categorySortIcon()).toBe('dns');
|
||||
expect(
|
||||
fixture.componentInstance.categorySortOptions.map((option) => ({
|
||||
mode: option.mode,
|
||||
translationKey: option.translationKey,
|
||||
icon: option.icon,
|
||||
}))
|
||||
).toEqual([
|
||||
{
|
||||
mode: 'server',
|
||||
translationKey: 'WORKSPACE.SORT_SERVER',
|
||||
icon: 'dns',
|
||||
},
|
||||
{
|
||||
mode: 'name-asc',
|
||||
translationKey: 'WORKSPACE.SORT_NAME_ASC',
|
||||
icon: 'sort_by_alpha',
|
||||
},
|
||||
{
|
||||
mode: 'name-desc',
|
||||
translationKey: 'WORKSPACE.SORT_NAME_DESC',
|
||||
icon: 'arrow_downward',
|
||||
},
|
||||
]);
|
||||
|
||||
fixture.componentInstance.setCategorySortMode('name-desc');
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(fixture.componentInstance.categorySortLabelKey()).toBe(
|
||||
'WORKSPACE.SORT_NAME_DESC'
|
||||
);
|
||||
expect(fixture.componentInstance.categorySortIcon()).toBe(
|
||||
'arrow_downward'
|
||||
);
|
||||
});
|
||||
|
||||
it('applies the category sort menu modes to stalker categories', () => {
|
||||
fixture.componentRef.setInput('context', {
|
||||
provider: 'stalker',
|
||||
|
||||
+25
-8
@@ -20,7 +20,6 @@ import { TranslatePipe, TranslateService } from '@ngx-translate/core';
|
||||
import { StalkerStore } from '@iptvnator/portal/stalker/data-access';
|
||||
import {
|
||||
PortalCategorySortMode,
|
||||
getPortalCategorySortModeLabel,
|
||||
persistPortalCategorySortMode,
|
||||
restorePortalCategorySortMode,
|
||||
sortPortalCategoryItems,
|
||||
@@ -157,30 +156,36 @@ export class WorkspaceContextPanelComponent {
|
||||
readonly categorySortMode = signal<PortalCategorySortMode>(
|
||||
restorePortalCategorySortMode()
|
||||
);
|
||||
readonly categorySortLabel = computed(() =>
|
||||
getPortalCategorySortModeLabel(this.categorySortMode())
|
||||
readonly categorySortLabelKey = computed(() =>
|
||||
this.getCategorySortLabelKey(this.categorySortMode())
|
||||
);
|
||||
readonly categorySortOptions: ReadonlyArray<{
|
||||
mode: PortalCategorySortMode;
|
||||
label: string;
|
||||
translationKey: string;
|
||||
icon: string;
|
||||
}> = [
|
||||
{
|
||||
mode: 'server',
|
||||
label: 'Server sorting',
|
||||
translationKey: 'WORKSPACE.SORT_SERVER',
|
||||
icon: 'dns',
|
||||
},
|
||||
{
|
||||
mode: 'name-asc',
|
||||
label: 'A-Z',
|
||||
translationKey: 'WORKSPACE.SORT_NAME_ASC',
|
||||
icon: 'sort_by_alpha',
|
||||
},
|
||||
{
|
||||
mode: 'name-desc',
|
||||
label: 'Z-A',
|
||||
icon: 'sort_by_alpha',
|
||||
translationKey: 'WORKSPACE.SORT_NAME_DESC',
|
||||
icon: 'arrow_downward',
|
||||
},
|
||||
];
|
||||
readonly categorySortIcon = computed(
|
||||
() =>
|
||||
this.categorySortOptions.find(
|
||||
(option) => option.mode === this.categorySortMode()
|
||||
)?.icon ?? 'dns'
|
||||
);
|
||||
|
||||
readonly canSearchCategories = computed(
|
||||
() =>
|
||||
@@ -450,6 +455,18 @@ export class WorkspaceContextPanelComponent {
|
||||
}
|
||||
}
|
||||
|
||||
private getCategorySortLabelKey(mode: PortalCategorySortMode): string {
|
||||
switch (mode) {
|
||||
case 'name-asc':
|
||||
return 'WORKSPACE.SORT_NAME_ASC';
|
||||
case 'name-desc':
|
||||
return 'WORKSPACE.SORT_NAME_DESC';
|
||||
case 'server':
|
||||
default:
|
||||
return 'WORKSPACE.SORT_SERVER';
|
||||
}
|
||||
}
|
||||
|
||||
private getCategoryLabel(category: WorkspaceCategoryLike): string {
|
||||
return category.category_name ?? category.name ?? '';
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user