diff --git a/apps/web/src/assets/i18n/ar.json b/apps/web/src/assets/i18n/ar.json
index 17814fd53..35d8f4088 100644
--- a/apps/web/src/assets/i18n/ar.json
+++ b/apps/web/src/assets/i18n/ar.json
@@ -567,7 +567,7 @@
"CHANGE_PIN": "Change PIN",
"CHANGE_PIN_DESCRIPTION": "Asks for the current PIN first.",
"HOW_TO_LOCK": "Choosing what to lock",
- "HOW_TO_LOCK_DESCRIPTION": "Open a source, then use the lock icons in the category list's manage dialog (Xtream, M3U groups) or the lock button above Stalker categories.",
+ "HOW_TO_LOCK_DESCRIPTION": "Open a source, then use “Manage categories” (the tune icon above the category list; “Manage groups” in an M3U playlist): every row has a lock toggle, and Stalker offers “Lock adult (18+)” for genres the portal flags. Right-click a category or group for a quick Lock / Unlock.",
"DISCLAIMER": "This is a child lock, not a security feature: the app's data files are readable by anyone with access to this computer.",
"ENABLED_TOAST": "Parental lock enabled",
"DISABLED_TOAST": "Parental lock disabled",
@@ -1918,6 +1918,8 @@
"LOCKED_COUNT": "Locked",
"LOCK_CATEGORY": "Lock category",
"UNLOCK_CATEGORY": "Unlock category",
+ "LOCK_GROUP": "Lock group",
+ "UNLOCK_GROUP": "Unlock group",
"MANAGE_LOCKS": "Lock categories",
"MANAGE_LOCKS_DESCRIPTION": "Locked categories and their channels disappear from the whole app until the parental PIN is entered.",
"LOCK_CENSORED": "Lock adult (18+)",
diff --git a/apps/web/src/assets/i18n/ary.json b/apps/web/src/assets/i18n/ary.json
index 9caf09696..ef0e13638 100644
--- a/apps/web/src/assets/i18n/ary.json
+++ b/apps/web/src/assets/i18n/ary.json
@@ -567,7 +567,7 @@
"CHANGE_PIN": "Change PIN",
"CHANGE_PIN_DESCRIPTION": "Asks for the current PIN first.",
"HOW_TO_LOCK": "Choosing what to lock",
- "HOW_TO_LOCK_DESCRIPTION": "Open a source, then use the lock icons in the category list's manage dialog (Xtream, M3U groups) or the lock button above Stalker categories.",
+ "HOW_TO_LOCK_DESCRIPTION": "Open a source, then use “Manage categories” (the tune icon above the category list; “Manage groups” in an M3U playlist): every row has a lock toggle, and Stalker offers “Lock adult (18+)” for genres the portal flags. Right-click a category or group for a quick Lock / Unlock.",
"DISCLAIMER": "This is a child lock, not a security feature: the app's data files are readable by anyone with access to this computer.",
"ENABLED_TOAST": "Parental lock enabled",
"DISABLED_TOAST": "Parental lock disabled",
@@ -1918,6 +1918,8 @@
"LOCKED_COUNT": "Locked",
"LOCK_CATEGORY": "Lock category",
"UNLOCK_CATEGORY": "Unlock category",
+ "LOCK_GROUP": "Lock group",
+ "UNLOCK_GROUP": "Unlock group",
"MANAGE_LOCKS": "Lock categories",
"MANAGE_LOCKS_DESCRIPTION": "Locked categories and their channels disappear from the whole app until the parental PIN is entered.",
"LOCK_CENSORED": "Lock adult (18+)",
diff --git a/apps/web/src/assets/i18n/by.json b/apps/web/src/assets/i18n/by.json
index 29e3e5009..0e743de81 100644
--- a/apps/web/src/assets/i18n/by.json
+++ b/apps/web/src/assets/i18n/by.json
@@ -567,7 +567,7 @@
"CHANGE_PIN": "Change PIN",
"CHANGE_PIN_DESCRIPTION": "Asks for the current PIN first.",
"HOW_TO_LOCK": "Choosing what to lock",
- "HOW_TO_LOCK_DESCRIPTION": "Open a source, then use the lock icons in the category list's manage dialog (Xtream, M3U groups) or the lock button above Stalker categories.",
+ "HOW_TO_LOCK_DESCRIPTION": "Open a source, then use “Manage categories” (the tune icon above the category list; “Manage groups” in an M3U playlist): every row has a lock toggle, and Stalker offers “Lock adult (18+)” for genres the portal flags. Right-click a category or group for a quick Lock / Unlock.",
"DISCLAIMER": "This is a child lock, not a security feature: the app's data files are readable by anyone with access to this computer.",
"ENABLED_TOAST": "Parental lock enabled",
"DISABLED_TOAST": "Parental lock disabled",
@@ -1918,6 +1918,8 @@
"LOCKED_COUNT": "Locked",
"LOCK_CATEGORY": "Lock category",
"UNLOCK_CATEGORY": "Unlock category",
+ "LOCK_GROUP": "Lock group",
+ "UNLOCK_GROUP": "Unlock group",
"MANAGE_LOCKS": "Lock categories",
"MANAGE_LOCKS_DESCRIPTION": "Locked categories and their channels disappear from the whole app until the parental PIN is entered.",
"LOCK_CENSORED": "Lock adult (18+)",
diff --git a/apps/web/src/assets/i18n/de.json b/apps/web/src/assets/i18n/de.json
index 9062e06fd..33b864548 100644
--- a/apps/web/src/assets/i18n/de.json
+++ b/apps/web/src/assets/i18n/de.json
@@ -567,7 +567,7 @@
"CHANGE_PIN": "PIN ändern",
"CHANGE_PIN_DESCRIPTION": "Fragt zuerst die aktuelle PIN ab.",
"HOW_TO_LOCK": "Was gesperrt wird",
- "HOW_TO_LOCK_DESCRIPTION": "Öffne eine Quelle und nutze die Schloss-Symbole im Verwaltungsdialog der Kategorienliste (Xtream, M3U-Gruppen) oder die Schloss-Schaltfläche über den Stalker-Kategorien.",
+ "HOW_TO_LOCK_DESCRIPTION": "Öffne eine Quelle und wähle „Kategorien verwalten“ (das Einstellungssymbol über der Kategorienliste; in einer M3U-Playlist „Gruppen verwalten“): jede Zeile hat einen Schloss-Schalter, und Stalker bietet „Erwachsene (18+) sperren“ für vom Portal markierte Genres. Rechtsklick auf eine Kategorie oder Gruppe für schnelles Sperren / Entsperren.",
"DISCLAIMER": "Das ist eine Kindersicherung, kein Sicherheitsmerkmal: Die Datendateien der App sind für jeden mit Zugriff auf diesen Computer lesbar.",
"ENABLED_TOAST": "Kindersicherung aktiviert",
"DISABLED_TOAST": "Kindersicherung deaktiviert",
@@ -1918,6 +1918,8 @@
"LOCKED_COUNT": "Gesperrt",
"LOCK_CATEGORY": "Kategorie sperren",
"UNLOCK_CATEGORY": "Kategorie entsperren",
+ "LOCK_GROUP": "Gruppe sperren",
+ "UNLOCK_GROUP": "Gruppe entsperren",
"MANAGE_LOCKS": "Kategorien sperren",
"MANAGE_LOCKS_DESCRIPTION": "Gesperrte Kategorien und ihre Sender verschwinden aus der gesamten App, bis die Kindersicherungs-PIN eingegeben wird.",
"LOCK_CENSORED": "18+ sperren",
diff --git a/apps/web/src/assets/i18n/el.json b/apps/web/src/assets/i18n/el.json
index fbc81610c..2825af325 100644
--- a/apps/web/src/assets/i18n/el.json
+++ b/apps/web/src/assets/i18n/el.json
@@ -567,7 +567,7 @@
"CHANGE_PIN": "Change PIN",
"CHANGE_PIN_DESCRIPTION": "Asks for the current PIN first.",
"HOW_TO_LOCK": "Choosing what to lock",
- "HOW_TO_LOCK_DESCRIPTION": "Open a source, then use the lock icons in the category list's manage dialog (Xtream, M3U groups) or the lock button above Stalker categories.",
+ "HOW_TO_LOCK_DESCRIPTION": "Open a source, then use “Manage categories” (the tune icon above the category list; “Manage groups” in an M3U playlist): every row has a lock toggle, and Stalker offers “Lock adult (18+)” for genres the portal flags. Right-click a category or group for a quick Lock / Unlock.",
"DISCLAIMER": "This is a child lock, not a security feature: the app's data files are readable by anyone with access to this computer.",
"ENABLED_TOAST": "Parental lock enabled",
"DISABLED_TOAST": "Parental lock disabled",
@@ -1918,6 +1918,8 @@
"LOCKED_COUNT": "Locked",
"LOCK_CATEGORY": "Lock category",
"UNLOCK_CATEGORY": "Unlock category",
+ "LOCK_GROUP": "Lock group",
+ "UNLOCK_GROUP": "Unlock group",
"MANAGE_LOCKS": "Lock categories",
"MANAGE_LOCKS_DESCRIPTION": "Locked categories and their channels disappear from the whole app until the parental PIN is entered.",
"LOCK_CENSORED": "Lock adult (18+)",
diff --git a/apps/web/src/assets/i18n/en.json b/apps/web/src/assets/i18n/en.json
index 48c41a1c1..048380dc5 100644
--- a/apps/web/src/assets/i18n/en.json
+++ b/apps/web/src/assets/i18n/en.json
@@ -567,7 +567,7 @@
"CHANGE_PIN": "Change PIN",
"CHANGE_PIN_DESCRIPTION": "Asks for the current PIN first.",
"HOW_TO_LOCK": "Choosing what to lock",
- "HOW_TO_LOCK_DESCRIPTION": "Open a source, then use the lock icons in the category list's manage dialog (Xtream, M3U groups) or the lock button above Stalker categories.",
+ "HOW_TO_LOCK_DESCRIPTION": "Open a source, then use “Manage categories” (the tune icon above the category list; “Manage groups” in an M3U playlist): every row has a lock toggle, and Stalker offers “Lock adult (18+)” for genres the portal flags. Right-click a category or group for a quick Lock / Unlock.",
"DISCLAIMER": "This is a child lock, not a security feature: the app's data files are readable by anyone with access to this computer.",
"ENABLED_TOAST": "Parental lock enabled",
"DISABLED_TOAST": "Parental lock disabled",
@@ -1918,6 +1918,8 @@
"LOCKED_COUNT": "Locked",
"LOCK_CATEGORY": "Lock category",
"UNLOCK_CATEGORY": "Unlock category",
+ "LOCK_GROUP": "Lock group",
+ "UNLOCK_GROUP": "Unlock group",
"MANAGE_LOCKS": "Lock categories",
"MANAGE_LOCKS_DESCRIPTION": "Locked categories and their channels disappear from the whole app until the parental PIN is entered.",
"LOCK_CENSORED": "Lock adult (18+)",
diff --git a/apps/web/src/assets/i18n/es.json b/apps/web/src/assets/i18n/es.json
index 841fe3066..bf61af682 100644
--- a/apps/web/src/assets/i18n/es.json
+++ b/apps/web/src/assets/i18n/es.json
@@ -567,7 +567,7 @@
"CHANGE_PIN": "Change PIN",
"CHANGE_PIN_DESCRIPTION": "Asks for the current PIN first.",
"HOW_TO_LOCK": "Choosing what to lock",
- "HOW_TO_LOCK_DESCRIPTION": "Open a source, then use the lock icons in the category list's manage dialog (Xtream, M3U groups) or the lock button above Stalker categories.",
+ "HOW_TO_LOCK_DESCRIPTION": "Open a source, then use “Manage categories” (the tune icon above the category list; “Manage groups” in an M3U playlist): every row has a lock toggle, and Stalker offers “Lock adult (18+)” for genres the portal flags. Right-click a category or group for a quick Lock / Unlock.",
"DISCLAIMER": "This is a child lock, not a security feature: the app's data files are readable by anyone with access to this computer.",
"ENABLED_TOAST": "Parental lock enabled",
"DISABLED_TOAST": "Parental lock disabled",
@@ -1918,6 +1918,8 @@
"LOCKED_COUNT": "Locked",
"LOCK_CATEGORY": "Lock category",
"UNLOCK_CATEGORY": "Unlock category",
+ "LOCK_GROUP": "Lock group",
+ "UNLOCK_GROUP": "Unlock group",
"MANAGE_LOCKS": "Lock categories",
"MANAGE_LOCKS_DESCRIPTION": "Locked categories and their channels disappear from the whole app until the parental PIN is entered.",
"LOCK_CENSORED": "Lock adult (18+)",
diff --git a/apps/web/src/assets/i18n/fr.json b/apps/web/src/assets/i18n/fr.json
index 31a58083b..156ff7303 100644
--- a/apps/web/src/assets/i18n/fr.json
+++ b/apps/web/src/assets/i18n/fr.json
@@ -567,7 +567,7 @@
"CHANGE_PIN": "Change PIN",
"CHANGE_PIN_DESCRIPTION": "Asks for the current PIN first.",
"HOW_TO_LOCK": "Choosing what to lock",
- "HOW_TO_LOCK_DESCRIPTION": "Open a source, then use the lock icons in the category list's manage dialog (Xtream, M3U groups) or the lock button above Stalker categories.",
+ "HOW_TO_LOCK_DESCRIPTION": "Open a source, then use “Manage categories” (the tune icon above the category list; “Manage groups” in an M3U playlist): every row has a lock toggle, and Stalker offers “Lock adult (18+)” for genres the portal flags. Right-click a category or group for a quick Lock / Unlock.",
"DISCLAIMER": "This is a child lock, not a security feature: the app's data files are readable by anyone with access to this computer.",
"ENABLED_TOAST": "Parental lock enabled",
"DISABLED_TOAST": "Parental lock disabled",
@@ -1918,6 +1918,8 @@
"LOCKED_COUNT": "Locked",
"LOCK_CATEGORY": "Lock category",
"UNLOCK_CATEGORY": "Unlock category",
+ "LOCK_GROUP": "Lock group",
+ "UNLOCK_GROUP": "Unlock group",
"MANAGE_LOCKS": "Lock categories",
"MANAGE_LOCKS_DESCRIPTION": "Locked categories and their channels disappear from the whole app until the parental PIN is entered.",
"LOCK_CENSORED": "Lock adult (18+)",
diff --git a/apps/web/src/assets/i18n/hu.json b/apps/web/src/assets/i18n/hu.json
index 6ac3de2df..47062710c 100644
--- a/apps/web/src/assets/i18n/hu.json
+++ b/apps/web/src/assets/i18n/hu.json
@@ -567,7 +567,7 @@
"CHANGE_PIN": "Change PIN",
"CHANGE_PIN_DESCRIPTION": "Asks for the current PIN first.",
"HOW_TO_LOCK": "Choosing what to lock",
- "HOW_TO_LOCK_DESCRIPTION": "Open a source, then use the lock icons in the category list's manage dialog (Xtream, M3U groups) or the lock button above Stalker categories.",
+ "HOW_TO_LOCK_DESCRIPTION": "Open a source, then use “Manage categories” (the tune icon above the category list; “Manage groups” in an M3U playlist): every row has a lock toggle, and Stalker offers “Lock adult (18+)” for genres the portal flags. Right-click a category or group for a quick Lock / Unlock.",
"DISCLAIMER": "This is a child lock, not a security feature: the app's data files are readable by anyone with access to this computer.",
"ENABLED_TOAST": "Parental lock enabled",
"DISABLED_TOAST": "Parental lock disabled",
@@ -1918,6 +1918,8 @@
"LOCKED_COUNT": "Locked",
"LOCK_CATEGORY": "Lock category",
"UNLOCK_CATEGORY": "Unlock category",
+ "LOCK_GROUP": "Lock group",
+ "UNLOCK_GROUP": "Unlock group",
"MANAGE_LOCKS": "Lock categories",
"MANAGE_LOCKS_DESCRIPTION": "Locked categories and their channels disappear from the whole app until the parental PIN is entered.",
"LOCK_CENSORED": "Lock adult (18+)",
diff --git a/apps/web/src/assets/i18n/it.json b/apps/web/src/assets/i18n/it.json
index 39ad57933..1cc0dc94c 100644
--- a/apps/web/src/assets/i18n/it.json
+++ b/apps/web/src/assets/i18n/it.json
@@ -567,7 +567,7 @@
"CHANGE_PIN": "Change PIN",
"CHANGE_PIN_DESCRIPTION": "Asks for the current PIN first.",
"HOW_TO_LOCK": "Choosing what to lock",
- "HOW_TO_LOCK_DESCRIPTION": "Open a source, then use the lock icons in the category list's manage dialog (Xtream, M3U groups) or the lock button above Stalker categories.",
+ "HOW_TO_LOCK_DESCRIPTION": "Open a source, then use “Manage categories” (the tune icon above the category list; “Manage groups” in an M3U playlist): every row has a lock toggle, and Stalker offers “Lock adult (18+)” for genres the portal flags. Right-click a category or group for a quick Lock / Unlock.",
"DISCLAIMER": "This is a child lock, not a security feature: the app's data files are readable by anyone with access to this computer.",
"ENABLED_TOAST": "Parental lock enabled",
"DISABLED_TOAST": "Parental lock disabled",
@@ -1918,6 +1918,8 @@
"LOCKED_COUNT": "Locked",
"LOCK_CATEGORY": "Lock category",
"UNLOCK_CATEGORY": "Unlock category",
+ "LOCK_GROUP": "Lock group",
+ "UNLOCK_GROUP": "Unlock group",
"MANAGE_LOCKS": "Lock categories",
"MANAGE_LOCKS_DESCRIPTION": "Locked categories and their channels disappear from the whole app until the parental PIN is entered.",
"LOCK_CENSORED": "Lock adult (18+)",
diff --git a/apps/web/src/assets/i18n/ja.json b/apps/web/src/assets/i18n/ja.json
index 5e9c9b29a..07a83653c 100644
--- a/apps/web/src/assets/i18n/ja.json
+++ b/apps/web/src/assets/i18n/ja.json
@@ -567,7 +567,7 @@
"CHANGE_PIN": "Change PIN",
"CHANGE_PIN_DESCRIPTION": "Asks for the current PIN first.",
"HOW_TO_LOCK": "Choosing what to lock",
- "HOW_TO_LOCK_DESCRIPTION": "Open a source, then use the lock icons in the category list's manage dialog (Xtream, M3U groups) or the lock button above Stalker categories.",
+ "HOW_TO_LOCK_DESCRIPTION": "Open a source, then use “Manage categories” (the tune icon above the category list; “Manage groups” in an M3U playlist): every row has a lock toggle, and Stalker offers “Lock adult (18+)” for genres the portal flags. Right-click a category or group for a quick Lock / Unlock.",
"DISCLAIMER": "This is a child lock, not a security feature: the app's data files are readable by anyone with access to this computer.",
"ENABLED_TOAST": "Parental lock enabled",
"DISABLED_TOAST": "Parental lock disabled",
@@ -1918,6 +1918,8 @@
"LOCKED_COUNT": "Locked",
"LOCK_CATEGORY": "Lock category",
"UNLOCK_CATEGORY": "Unlock category",
+ "LOCK_GROUP": "Lock group",
+ "UNLOCK_GROUP": "Unlock group",
"MANAGE_LOCKS": "Lock categories",
"MANAGE_LOCKS_DESCRIPTION": "Locked categories and their channels disappear from the whole app until the parental PIN is entered.",
"LOCK_CENSORED": "Lock adult (18+)",
diff --git a/apps/web/src/assets/i18n/ko.json b/apps/web/src/assets/i18n/ko.json
index bbb1fe503..36434beb2 100644
--- a/apps/web/src/assets/i18n/ko.json
+++ b/apps/web/src/assets/i18n/ko.json
@@ -567,7 +567,7 @@
"CHANGE_PIN": "Change PIN",
"CHANGE_PIN_DESCRIPTION": "Asks for the current PIN first.",
"HOW_TO_LOCK": "Choosing what to lock",
- "HOW_TO_LOCK_DESCRIPTION": "Open a source, then use the lock icons in the category list's manage dialog (Xtream, M3U groups) or the lock button above Stalker categories.",
+ "HOW_TO_LOCK_DESCRIPTION": "Open a source, then use “Manage categories” (the tune icon above the category list; “Manage groups” in an M3U playlist): every row has a lock toggle, and Stalker offers “Lock adult (18+)” for genres the portal flags. Right-click a category or group for a quick Lock / Unlock.",
"DISCLAIMER": "This is a child lock, not a security feature: the app's data files are readable by anyone with access to this computer.",
"ENABLED_TOAST": "Parental lock enabled",
"DISABLED_TOAST": "Parental lock disabled",
@@ -1918,6 +1918,8 @@
"LOCKED_COUNT": "Locked",
"LOCK_CATEGORY": "Lock category",
"UNLOCK_CATEGORY": "Unlock category",
+ "LOCK_GROUP": "Lock group",
+ "UNLOCK_GROUP": "Unlock group",
"MANAGE_LOCKS": "Lock categories",
"MANAGE_LOCKS_DESCRIPTION": "Locked categories and their channels disappear from the whole app until the parental PIN is entered.",
"LOCK_CENSORED": "Lock adult (18+)",
diff --git a/apps/web/src/assets/i18n/nl.json b/apps/web/src/assets/i18n/nl.json
index 65a400af7..573844467 100644
--- a/apps/web/src/assets/i18n/nl.json
+++ b/apps/web/src/assets/i18n/nl.json
@@ -567,7 +567,7 @@
"CHANGE_PIN": "Change PIN",
"CHANGE_PIN_DESCRIPTION": "Asks for the current PIN first.",
"HOW_TO_LOCK": "Choosing what to lock",
- "HOW_TO_LOCK_DESCRIPTION": "Open a source, then use the lock icons in the category list's manage dialog (Xtream, M3U groups) or the lock button above Stalker categories.",
+ "HOW_TO_LOCK_DESCRIPTION": "Open a source, then use “Manage categories” (the tune icon above the category list; “Manage groups” in an M3U playlist): every row has a lock toggle, and Stalker offers “Lock adult (18+)” for genres the portal flags. Right-click a category or group for a quick Lock / Unlock.",
"DISCLAIMER": "This is a child lock, not a security feature: the app's data files are readable by anyone with access to this computer.",
"ENABLED_TOAST": "Parental lock enabled",
"DISABLED_TOAST": "Parental lock disabled",
@@ -1918,6 +1918,8 @@
"LOCKED_COUNT": "Locked",
"LOCK_CATEGORY": "Lock category",
"UNLOCK_CATEGORY": "Unlock category",
+ "LOCK_GROUP": "Lock group",
+ "UNLOCK_GROUP": "Unlock group",
"MANAGE_LOCKS": "Lock categories",
"MANAGE_LOCKS_DESCRIPTION": "Locked categories and their channels disappear from the whole app until the parental PIN is entered.",
"LOCK_CENSORED": "Lock adult (18+)",
diff --git a/apps/web/src/assets/i18n/pl.json b/apps/web/src/assets/i18n/pl.json
index fea3cda97..50cb40e0e 100644
--- a/apps/web/src/assets/i18n/pl.json
+++ b/apps/web/src/assets/i18n/pl.json
@@ -567,7 +567,7 @@
"CHANGE_PIN": "Change PIN",
"CHANGE_PIN_DESCRIPTION": "Asks for the current PIN first.",
"HOW_TO_LOCK": "Choosing what to lock",
- "HOW_TO_LOCK_DESCRIPTION": "Open a source, then use the lock icons in the category list's manage dialog (Xtream, M3U groups) or the lock button above Stalker categories.",
+ "HOW_TO_LOCK_DESCRIPTION": "Open a source, then use “Manage categories” (the tune icon above the category list; “Manage groups” in an M3U playlist): every row has a lock toggle, and Stalker offers “Lock adult (18+)” for genres the portal flags. Right-click a category or group for a quick Lock / Unlock.",
"DISCLAIMER": "This is a child lock, not a security feature: the app's data files are readable by anyone with access to this computer.",
"ENABLED_TOAST": "Parental lock enabled",
"DISABLED_TOAST": "Parental lock disabled",
@@ -1918,6 +1918,8 @@
"LOCKED_COUNT": "Locked",
"LOCK_CATEGORY": "Lock category",
"UNLOCK_CATEGORY": "Unlock category",
+ "LOCK_GROUP": "Lock group",
+ "UNLOCK_GROUP": "Unlock group",
"MANAGE_LOCKS": "Lock categories",
"MANAGE_LOCKS_DESCRIPTION": "Locked categories and their channels disappear from the whole app until the parental PIN is entered.",
"LOCK_CENSORED": "Lock adult (18+)",
diff --git a/apps/web/src/assets/i18n/pt.json b/apps/web/src/assets/i18n/pt.json
index 1d30d7e82..b9af24bb1 100644
--- a/apps/web/src/assets/i18n/pt.json
+++ b/apps/web/src/assets/i18n/pt.json
@@ -567,7 +567,7 @@
"CHANGE_PIN": "Change PIN",
"CHANGE_PIN_DESCRIPTION": "Asks for the current PIN first.",
"HOW_TO_LOCK": "Choosing what to lock",
- "HOW_TO_LOCK_DESCRIPTION": "Open a source, then use the lock icons in the category list's manage dialog (Xtream, M3U groups) or the lock button above Stalker categories.",
+ "HOW_TO_LOCK_DESCRIPTION": "Open a source, then use “Manage categories” (the tune icon above the category list; “Manage groups” in an M3U playlist): every row has a lock toggle, and Stalker offers “Lock adult (18+)” for genres the portal flags. Right-click a category or group for a quick Lock / Unlock.",
"DISCLAIMER": "This is a child lock, not a security feature: the app's data files are readable by anyone with access to this computer.",
"ENABLED_TOAST": "Parental lock enabled",
"DISABLED_TOAST": "Parental lock disabled",
@@ -1918,6 +1918,8 @@
"LOCKED_COUNT": "Locked",
"LOCK_CATEGORY": "Lock category",
"UNLOCK_CATEGORY": "Unlock category",
+ "LOCK_GROUP": "Lock group",
+ "UNLOCK_GROUP": "Unlock group",
"MANAGE_LOCKS": "Lock categories",
"MANAGE_LOCKS_DESCRIPTION": "Locked categories and their channels disappear from the whole app until the parental PIN is entered.",
"LOCK_CENSORED": "Lock adult (18+)",
diff --git a/apps/web/src/assets/i18n/ru.json b/apps/web/src/assets/i18n/ru.json
index 988a7f428..05830c28e 100644
--- a/apps/web/src/assets/i18n/ru.json
+++ b/apps/web/src/assets/i18n/ru.json
@@ -567,7 +567,7 @@
"CHANGE_PIN": "Сменить PIN",
"CHANGE_PIN_DESCRIPTION": "Сначала запрашивается текущий PIN.",
"HOW_TO_LOCK": "Что блокировать",
- "HOW_TO_LOCK_DESCRIPTION": "Откройте источник и используйте значки замка в диалоге управления категориями (Xtream, группы M3U) или кнопку замка над категориями Stalker.",
+ "HOW_TO_LOCK_DESCRIPTION": "Откройте источник и нажмите «Управлять категориями» (иконка настройки над списком категорий; в плейлисте M3U — «Управление группами»): у каждой строки есть переключатель замка, а для Stalker доступно «Заблокировать 18+» для жанров, помеченных порталом. Правый клик по категории или группе — быстрое «Заблокировать / Разблокировать».",
"DISCLAIMER": "Это детская блокировка, а не защита: файлы данных приложения доступны любому, у кого есть доступ к этому компьютеру.",
"ENABLED_TOAST": "Родительский контроль включён",
"DISABLED_TOAST": "Родительский контроль выключен",
@@ -1918,6 +1918,8 @@
"LOCKED_COUNT": "Заблокировано",
"LOCK_CATEGORY": "Заблокировать категорию",
"UNLOCK_CATEGORY": "Разблокировать категорию",
+ "LOCK_GROUP": "Заблокировать группу",
+ "UNLOCK_GROUP": "Разблокировать группу",
"MANAGE_LOCKS": "Заблокировать категории",
"MANAGE_LOCKS_DESCRIPTION": "Заблокированные категории и их каналы исчезают из всего приложения, пока не введён родительский PIN.",
"LOCK_CENSORED": "Заблокировать 18+",
diff --git a/apps/web/src/assets/i18n/tr.json b/apps/web/src/assets/i18n/tr.json
index 56dd522a6..313bca312 100644
--- a/apps/web/src/assets/i18n/tr.json
+++ b/apps/web/src/assets/i18n/tr.json
@@ -567,7 +567,7 @@
"CHANGE_PIN": "Change PIN",
"CHANGE_PIN_DESCRIPTION": "Asks for the current PIN first.",
"HOW_TO_LOCK": "Choosing what to lock",
- "HOW_TO_LOCK_DESCRIPTION": "Open a source, then use the lock icons in the category list's manage dialog (Xtream, M3U groups) or the lock button above Stalker categories.",
+ "HOW_TO_LOCK_DESCRIPTION": "Open a source, then use “Manage categories” (the tune icon above the category list; “Manage groups” in an M3U playlist): every row has a lock toggle, and Stalker offers “Lock adult (18+)” for genres the portal flags. Right-click a category or group for a quick Lock / Unlock.",
"DISCLAIMER": "This is a child lock, not a security feature: the app's data files are readable by anyone with access to this computer.",
"ENABLED_TOAST": "Parental lock enabled",
"DISABLED_TOAST": "Parental lock disabled",
@@ -1918,6 +1918,8 @@
"LOCKED_COUNT": "Locked",
"LOCK_CATEGORY": "Lock category",
"UNLOCK_CATEGORY": "Unlock category",
+ "LOCK_GROUP": "Lock group",
+ "UNLOCK_GROUP": "Unlock group",
"MANAGE_LOCKS": "Lock categories",
"MANAGE_LOCKS_DESCRIPTION": "Locked categories and their channels disappear from the whole app until the parental PIN is entered.",
"LOCK_CENSORED": "Lock adult (18+)",
diff --git a/apps/web/src/assets/i18n/zh.json b/apps/web/src/assets/i18n/zh.json
index 727b5203c..f57e882d0 100644
--- a/apps/web/src/assets/i18n/zh.json
+++ b/apps/web/src/assets/i18n/zh.json
@@ -567,7 +567,7 @@
"CHANGE_PIN": "Change PIN",
"CHANGE_PIN_DESCRIPTION": "Asks for the current PIN first.",
"HOW_TO_LOCK": "Choosing what to lock",
- "HOW_TO_LOCK_DESCRIPTION": "Open a source, then use the lock icons in the category list's manage dialog (Xtream, M3U groups) or the lock button above Stalker categories.",
+ "HOW_TO_LOCK_DESCRIPTION": "Open a source, then use “Manage categories” (the tune icon above the category list; “Manage groups” in an M3U playlist): every row has a lock toggle, and Stalker offers “Lock adult (18+)” for genres the portal flags. Right-click a category or group for a quick Lock / Unlock.",
"DISCLAIMER": "This is a child lock, not a security feature: the app's data files are readable by anyone with access to this computer.",
"ENABLED_TOAST": "Parental lock enabled",
"DISABLED_TOAST": "Parental lock disabled",
@@ -1918,6 +1918,8 @@
"LOCKED_COUNT": "Locked",
"LOCK_CATEGORY": "Lock category",
"UNLOCK_CATEGORY": "Unlock category",
+ "LOCK_GROUP": "Lock group",
+ "UNLOCK_GROUP": "Unlock group",
"MANAGE_LOCKS": "Lock categories",
"MANAGE_LOCKS_DESCRIPTION": "Locked categories and their channels disappear from the whole app until the parental PIN is entered.",
"LOCK_CENSORED": "Lock adult (18+)",
diff --git a/apps/web/src/assets/i18n/zhtw.json b/apps/web/src/assets/i18n/zhtw.json
index cce2242dd..b99a23e93 100644
--- a/apps/web/src/assets/i18n/zhtw.json
+++ b/apps/web/src/assets/i18n/zhtw.json
@@ -567,7 +567,7 @@
"CHANGE_PIN": "Change PIN",
"CHANGE_PIN_DESCRIPTION": "Asks for the current PIN first.",
"HOW_TO_LOCK": "Choosing what to lock",
- "HOW_TO_LOCK_DESCRIPTION": "Open a source, then use the lock icons in the category list's manage dialog (Xtream, M3U groups) or the lock button above Stalker categories.",
+ "HOW_TO_LOCK_DESCRIPTION": "Open a source, then use “Manage categories” (the tune icon above the category list; “Manage groups” in an M3U playlist): every row has a lock toggle, and Stalker offers “Lock adult (18+)” for genres the portal flags. Right-click a category or group for a quick Lock / Unlock.",
"DISCLAIMER": "This is a child lock, not a security feature: the app's data files are readable by anyone with access to this computer.",
"ENABLED_TOAST": "Parental lock enabled",
"DISABLED_TOAST": "Parental lock disabled",
@@ -1918,6 +1918,8 @@
"LOCKED_COUNT": "Locked",
"LOCK_CATEGORY": "Lock category",
"UNLOCK_CATEGORY": "Unlock category",
+ "LOCK_GROUP": "Lock group",
+ "UNLOCK_GROUP": "Unlock group",
"MANAGE_LOCKS": "Lock categories",
"MANAGE_LOCKS_DESCRIPTION": "Locked categories and their channels disappear from the whole app until the parental PIN is entered.",
"LOCK_CENSORED": "Lock adult (18+)",
diff --git a/docs/architecture/category-management.md b/docs/architecture/category-management.md
index 2e75cdf95..aeb76b096 100644
--- a/docs/architecture/category-management.md
+++ b/docs/architecture/category-management.md
@@ -151,7 +151,10 @@ dialog renders a lock toggle per row and the whole dialog opens only after the
PIN. Locks are a separate concept from `hidden`: they live in the renderer's
lock store keyed by provider category id and are mirrored into
`categories.locked` through `DB_SET_CATEGORY_LOCKS`, so a refresh keeps them
-the same way it keeps `hidden`. Contract: [parental lock](parental-lock.md).
+the same way it keeps `hidden`. The dialog is the one entry point on every
+portal type (Stalker's "Manage categories" button opens its lock-only
+dialog); a right-click on a category row offers a single-row Lock/Unlock
+through the same lock store. Contract: [parental lock](parental-lock.md).
## Files Changed
diff --git a/docs/architecture/parental-lock.md b/docs/architecture/parental-lock.md
index c86bc4570..6a8c05b11 100644
--- a/docs/architecture/parental-lock.md
+++ b/docs/architecture/parental-lock.md
@@ -284,46 +284,47 @@ on either side.
disclaimer. Everything applies immediately (it gates on the PIN), outside
the shared dirty form; `createSettingsFromFormValue` carries both values
from the current settings so Save cannot undo them.
-- Lock toggles in the Xtream category-management dialog and the M3U group
- dialog (rendered only while the feature is on), plus a new Stalker
- `StalkerCategoryLockDialogComponent` reached from a lock button above the
- categories rail; it offers "Lock adult (18+)" for genres the portal flags
- `censored`. All three list the locked names and can rewrite the locks, so
- each opens only after `requestUnlock()` succeeds — and closes itself,
- discarding the draft, the moment `active` becomes true again (idle relock,
- Lock now), since the PIN gate covers only the opening. The M3U group
- dialog does this only while it carries lock toggles; the plain hide/show
- editor is not behind the PIN. The Xtream dialog loads
- its candidates through the capability-selected data source
- (`IXtreamDataSource.getAllCategories`, which the PWA source answers from
- its session cache or the API), so PWA users can set locks too; the
- hide/show checkboxes remain Electron-only. The relock timeout persists
- through the same undo-on-failure pattern as the switch
- (`setRelockMinutes` reverts the in-memory value and the facade shows the
- settings save-failure snackbar). The M3U dialog hands its lock
- list back to `ChannelListContainerComponent`, which awaits the write and
- reports a failed save in a snackbar (the dialog has closed by then). On
- Electron the `categories.locked` re-stamp (`setCategoryLocks`) clears and
- re-locks one playlist/type inside ONE transaction, so a failed restamp
- keeps the previous index instead of leaving every category unlocked. The
- store commits BEFORE that re-stamp, so a failed re-stamp rolls the store
- back to the previous locks AND re-stamps every touched type from them (a
- backup restore stamps three types, and the ones before the failing type
- already carry the new locks; a category must not be recorded and shown
- as locked while Electron reads, which filter by the index alone, still
- serve it), and the store revision consumers reload on is published only
- once every touched type is stamped, so a reload cannot read a later type
- through its old stamps; if the rollback write or its re-stamp fails too, the playlist is
- re-stamped on the next store access, and every launch re-derives the
- index from the store for each playlist that has locks — which is why a
- write that removes a playlist's LAST lock clears the index first and
- drops the key afterwards (the reconcile finds playlists only through
- their key; an interruption then leaves store-with-lock and index-without,
- which the next reconcile repairs toward locked; a store recovered by a
- later successful read marks its playlists stale the same way) — awaited inside
- the store's `load()`, so `readable` (and with it every catalog read the
- renderer gates) stays false until the index agrees with the store; a
- re-stamp that keeps failing keeps the session fail-closed.
+- One entry point per rail, the same on every portal type: the "Manage
+ categories" button (`tune` icon; "Manage groups" on an M3U playlist).
+ Xtream and M3U render lock toggles inside their existing hide/show
+ dialogs while the feature is on; Stalker has no hide/show dialog, so its
+ button — shown only while the feature is on — opens the lock-only
+ `StalkerCategoryLockDialogComponent`, which also offers "Lock adult
+ (18+)" for genres the portal flags `censored`. All three list the locked
+ names and can rewrite the locks, so each opens only after
+ `requestUnlock()` succeeds — and closes itself, discarding the draft, the
+ moment `active` becomes true again (idle relock, Lock now), since the
+ PIN gate covers only the opening. The M3U group dialog does this only
+ while it carries lock toggles; the plain hide/show editor is not behind
+ the PIN. The Xtream dialog loads its candidates through the
+ capability-selected data source (`IXtreamDataSource.getAllCategories`,
+ which the PWA source answers from its session cache or the API), so PWA
+ users can set locks too; the hide/show checkboxes remain Electron-only.
+ A deliberate non-choice: no dedicated lock button in the rail header
+ (the header already carries search, sort and manage, and the lock is a
+ category-management concern) and no keyword-based "adult" recognition
+ for Xtream/M3U (provider naming is arbitrary; the only automation is the
+ portal's own `censored` flag on Stalker).
+- Right-click accelerator: a `contextmenu` on a category row (Xtream and
+ Stalker rail, `WorkspaceContextCategoryViewComponent`) or an M3U group
+ row opens the shared `CategoryLockMenuComponent` (`libs/ui/components`)
+ with one "Lock / Unlock category (group)" item, only while the feature
+ is on. The portal rail persists through
+ `WorkspaceCategoryLockActionService` (PIN gate, then one-id edit of the
+ lock store, failure snackbar); the M3U rail emits the edited list to
+ `ChannelListContainerComponent`, which persists it like the dialog's
+ result. Bulk actions stay in the dialog, and while the session is locked
+ a locked category is not in the rail, so unlocking always goes through
+ the "N locked · Enter PIN to show" row or the dialog. The M3U dialog
+ hands its lock list back to `ChannelListContainerComponent`, which
+ awaits the write and reports a failed save in a snackbar (the dialog has
+ closed by then). On Electron the `categories.locked` re-stamp
+ (`setCategoryLocks`) clears and re-locks one playlist/type inside ONE
+ transaction, so a failed restamp keeps the previous index instead of
+ leaving every category unlocked. The relock timeout persists through the
+ same undo-on-failure pattern as the switch (`setRelockMinutes` reverts
+ the in-memory value and the facade shows the settings save-failure
+ snackbar).
- Header lock/unlock button and the `parental-lock-now` /
`parental-unlock` palette commands.
diff --git a/libs/ui/components/src/index.ts b/libs/ui/components/src/index.ts
index d31faf1d9..07e6ca0b3 100644
--- a/libs/ui/components/src/index.ts
+++ b/libs/ui/components/src/index.ts
@@ -32,3 +32,4 @@ export * from './lib/watched-badge/watched-badge.component';
export * from './lib/window-controls/window-controls.component';
export * from './lib/channel-scroll-focus/channel-scroll-focus.directive';
+export * from './lib/category-lock-menu/category-lock-menu.component';
diff --git a/libs/ui/components/src/lib/category-lock-menu/category-lock-menu.component.spec.ts b/libs/ui/components/src/lib/category-lock-menu/category-lock-menu.component.spec.ts
new file mode 100644
index 000000000..b9dbbf226
--- /dev/null
+++ b/libs/ui/components/src/lib/category-lock-menu/category-lock-menu.component.spec.ts
@@ -0,0 +1,49 @@
+import { Component, viewChild } from '@angular/core';
+import { TestBed } from '@angular/core/testing';
+import { NoopAnimationsModule } from '@angular/platform-browser/animations';
+import { TranslateModule } from '@ngx-translate/core';
+import { CategoryLockMenuComponent } from './category-lock-menu.component';
+
+@Component({
+ imports: [CategoryLockMenuComponent],
+ template: ``,
+})
+class HostComponent {
+ readonly menu = viewChild.required(CategoryLockMenuComponent);
+ readonly requested: boolean[] = [];
+}
+
+describe('CategoryLockMenuComponent', () => {
+ it('opens at the pointer and requests the opposite lock state', async () => {
+ await TestBed.configureTestingModule({
+ imports: [
+ HostComponent,
+ NoopAnimationsModule,
+ TranslateModule.forRoot(),
+ ],
+ }).compileComponents();
+ const fixture = TestBed.createComponent(HostComponent);
+ fixture.detectChanges();
+ const host = fixture.componentInstance;
+ const event = new MouseEvent('contextmenu', {
+ clientX: 40,
+ clientY: 60,
+ cancelable: true,
+ });
+
+ host.menu().open(event, true);
+ await Promise.resolve();
+ fixture.detectChanges();
+
+ expect(event.defaultPrevented).toBe(true);
+ expect(host.menu().position()).toEqual({ x: '40px', y: '60px' });
+ const item = document.querySelector(
+ '[data-test-id="category-lock-menu-toggle"]'
+ );
+ expect(item?.textContent).toContain('PARENTAL_LOCK.UNLOCK_CATEGORY');
+ item?.click();
+ expect(host.requested).toEqual([false]);
+ });
+});
diff --git a/libs/ui/components/src/lib/category-lock-menu/category-lock-menu.component.ts b/libs/ui/components/src/lib/category-lock-menu/category-lock-menu.component.ts
new file mode 100644
index 000000000..ee49c7e80
--- /dev/null
+++ b/libs/ui/components/src/lib/category-lock-menu/category-lock-menu.component.ts
@@ -0,0 +1,85 @@
+import {
+ ChangeDetectionStrategy,
+ Component,
+ input,
+ output,
+ signal,
+ viewChild,
+} from '@angular/core';
+import { MatIconModule } from '@angular/material/icon';
+import { MatMenuModule, MatMenuTrigger } from '@angular/material/menu';
+import { TranslatePipe } from '@ngx-translate/core';
+
+/**
+ * Right-click accelerator of the parental lock: one positioned context menu
+ * with a single "Lock … / Unlock …" item. Hosts (the portal category rail,
+ * the M3U groups rail) call `open()` from a `contextmenu` event and persist
+ * the emitted target state themselves — the menu knows nothing about
+ * playlists or lock stores. Bulk actions and the full list stay in the
+ * "Manage categories" dialog; this only shortens the single-category case.
+ */
+@Component({
+ selector: 'app-category-lock-menu',
+ imports: [MatIconModule, MatMenuModule, TranslatePipe],
+ changeDetection: ChangeDetectionStrategy.OnPush,
+ template: `
+
+
+
+
+ `,
+ styles: `
+ :host {
+ display: contents;
+ }
+ .category-lock-menu__anchor {
+ position: fixed;
+ width: 0;
+ height: 0;
+ pointer-events: none;
+ }
+ `,
+})
+export class CategoryLockMenuComponent {
+ readonly lockLabelKey = input('PARENTAL_LOCK.LOCK_CATEGORY');
+ readonly unlockLabelKey = input('PARENTAL_LOCK.UNLOCK_CATEGORY');
+ /** The desired state: `true` = lock the row, `false` = unlock it. */
+ readonly toggleRequested = output();
+
+ readonly locked = signal(false);
+ readonly position = signal({ x: '0px', y: '0px' });
+ private readonly trigger = viewChild.required('trigger');
+
+ /** Opens the menu at the pointer for a row whose lock state is `locked`. */
+ open(event: MouseEvent, locked: boolean): void {
+ event.preventDefault();
+ event.stopPropagation();
+ this.locked.set(locked);
+ this.position.set({
+ x: `${event.clientX}px`,
+ y: `${event.clientY}px`,
+ });
+ const trigger = this.trigger();
+ if (trigger.menuOpen) {
+ trigger.closeMenu();
+ }
+ queueMicrotask(() => this.trigger().openMenu());
+ }
+}
diff --git a/libs/ui/components/src/lib/channel-list-container/groups-view/groups-view.component.html b/libs/ui/components/src/lib/channel-list-container/groups-view/groups-view.component.html
index 94947c878..0311f79f8 100644
--- a/libs/ui/components/src/lib/channel-list-container/groups-view/groups-view.component.html
+++ b/libs/ui/components/src/lib/channel-list-container/groups-view/groups-view.component.html
@@ -127,6 +127,9 @@
: null
"
(click)="selectGroup(group.key)"
+ (contextmenu)="
+ onGroupContextMenu(group.key, $event)
+ "
>
@if (group.key) {
@@ -354,3 +357,10 @@
{{ 'CHANNELS.SHOW_DETAILS' | translate }}
+
+
diff --git a/libs/ui/components/src/lib/channel-list-container/groups-view/groups-view.component.spec.ts b/libs/ui/components/src/lib/channel-list-container/groups-view/groups-view.component.spec.ts
index c23f1bed1..e929dc5d2 100644
--- a/libs/ui/components/src/lib/channel-list-container/groups-view/groups-view.component.spec.ts
+++ b/libs/ui/components/src/lib/channel-list-container/groups-view/groups-view.component.spec.ts
@@ -537,6 +537,37 @@ describe('GroupsViewComponent', () => {
]);
});
+ it('locks and unlocks one group from the right-click menu behind the PIN gate', async () => {
+ const lockedGroupTitlesChanged = jest.fn();
+ component.lockedGroupTitlesChanged.subscribe(lockedGroupTitlesChanged);
+ fixture.componentRef.setInput('lockedGroupTitles', ['News']);
+ fixture.detectChanges();
+ const event = new MouseEvent('contextmenu', { cancelable: true });
+
+ component.onGroupContextMenu('Sports', event);
+ expect(event.defaultPrevented).toBe(true);
+ await component.onGroupLockToggle(true);
+ expect(lockedGroupTitlesChanged).toHaveBeenLastCalledWith([
+ 'News',
+ 'Sports',
+ ]);
+
+ component.onGroupContextMenu('News', event);
+ await component.onGroupLockToggle(false);
+ expect(lockedGroupTitlesChanged).toHaveBeenLastCalledWith([]);
+
+ parentalLock.requestUnlock.mockResolvedValueOnce(false);
+ component.onGroupContextMenu('Movies', event);
+ await component.onGroupLockToggle(true);
+ expect(lockedGroupTitlesChanged).toHaveBeenCalledTimes(2);
+ });
+
+ it('offers no group lock menu while the feature is off', () => {
+ const event = new MouseEvent('contextmenu', { cancelable: true });
+ component.onGroupContextMenu('Sports', event);
+ expect(event.defaultPrevented).toBe(false);
+ });
+
it('keeps the manage-groups dialog closed when the parental PIN is refused', async () => {
// The dialog names every locked group and can rewrite the locks.
parentalLock.requestUnlock.mockResolvedValueOnce(false);
diff --git a/libs/ui/components/src/lib/channel-list-container/groups-view/groups-view.component.ts b/libs/ui/components/src/lib/channel-list-container/groups-view/groups-view.component.ts
index cf1f2ecb9..cf507d525 100644
--- a/libs/ui/components/src/lib/channel-list-container/groups-view/groups-view.component.ts
+++ b/libs/ui/components/src/lib/channel-list-container/groups-view/groups-view.component.ts
@@ -41,6 +41,7 @@ import { resolveChannelLogo } from '../channel-logo-fallback.util';
import { EpgMappingDialogComponent } from '../epg-mapping-dialog/epg-mapping-dialog.component';
import { ChannelDetailsDialogComponent } from '../channel-details-dialog/channel-details-dialog.component';
import { ChannelListItemComponent } from '../channel-list-item/channel-list-item.component';
+import { CategoryLockMenuComponent } from '../../category-lock-menu/category-lock-menu.component';
import { ResizableDirective } from '../../resizable/resizable.directive';
import {
GroupManagementDialogComponent,
@@ -70,6 +71,7 @@ interface FilteredGroupView {
styleUrls: ['./groups-view.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
imports: [
+ CategoryLockMenuComponent,
ChannelScrollFocusDirective,
ChannelListItemComponent,
MatButtonModule,
@@ -188,6 +190,10 @@ export class GroupsViewComponent {
*/
readonly selectedGroupChange = output();
+ private readonly groupLockMenu =
+ viewChild('groupLockMenu');
+ private groupLockKey: string | null = null;
+
readonly isGroupSearchOpen = signal(false);
readonly localGroupSearchTerm = signal('');
readonly selectedGroupKey = signal(null);
@@ -489,6 +495,37 @@ export class GroupsViewComponent {
this.localGroupSearchTerm.set(value);
}
+ /**
+ * Right-click accelerator of the parental lock for one group; the host
+ * persists the emitted list exactly as it does for the dialog. No menu
+ * while the feature is off (`lockedGroupTitles` is null then).
+ */
+ onGroupContextMenu(groupKey: string, event: MouseEvent): void {
+ const locked = this.lockedGroupTitles();
+ if (locked === null) {
+ return;
+ }
+ this.groupLockKey = groupKey;
+ this.groupLockMenu()?.open(event, locked.includes(groupKey));
+ }
+
+ async onGroupLockToggle(lock: boolean): Promise {
+ const groupKey = this.groupLockKey;
+ this.groupLockKey = null;
+ const current = this.lockedGroupTitles();
+ if (groupKey === null || current === null) {
+ return;
+ }
+ if (!(await this.parentalLock.requestUnlock())) {
+ return;
+ }
+ this.lockedGroupTitlesChanged.emit(
+ lock
+ ? [...new Set([...current, groupKey])]
+ : current.filter((title) => title !== groupKey)
+ );
+ }
+
async openGroupManagement(): Promise {
// The dialog lists every group by name, locked ones included, and can
// rewrite the locks — so it sits behind the PIN like the portal
diff --git a/libs/workspace/shell/feature/src/lib/workspace-context-panel/components/workspace-context-category-view.component.html b/libs/workspace/shell/feature/src/lib/workspace-context-panel/components/workspace-context-category-view.component.html
index 96d1dcf0d..2d5df8b0b 100644
--- a/libs/workspace/shell/feature/src/lib/workspace-context-panel/components/workspace-context-category-view.component.html
+++ b/libs/workspace/shell/feature/src/lib/workspace-context-panel/components/workspace-context-category-view.component.html
@@ -23,6 +23,7 @@
[attr.aria-disabled]="!interactionEnabled()"
[disabled]="!interactionEnabled()"
(click)="onCategoryClick(item)"
+ (contextmenu)="onCategoryContextMenu(item, $event)"
>
{{
diff --git a/libs/workspace/shell/feature/src/lib/workspace-context-panel/components/workspace-context-category-view.component.ts b/libs/workspace/shell/feature/src/lib/workspace-context-panel/components/workspace-context-category-view.component.ts
index 72e1b2ccd..858e15495 100644
--- a/libs/workspace/shell/feature/src/lib/workspace-context-panel/components/workspace-context-category-view.component.ts
+++ b/libs/workspace/shell/feature/src/lib/workspace-context-panel/components/workspace-context-category-view.component.ts
@@ -12,7 +12,7 @@ import { MatListModule } from '@angular/material/list';
import { TranslatePipe } from '@ngx-translate/core';
import { WorkspaceContextErrorViewComponent } from './workspace-context-error-view.component';
-interface WorkspaceCategoryViewItem {
+export interface WorkspaceCategoryViewItem {
readonly category_id?: string | number;
readonly category_name?: string;
readonly count?: number;
@@ -28,7 +28,6 @@ interface WorkspaceCategoryViewItem {
styleUrl: './workspace-context-category-view.component.scss',
})
export class WorkspaceContextCategoryViewComponent {
-
readonly items = input>([]);
readonly selectedCategoryId = input();
readonly itemCounts = input