diff --git a/src/components/library/subscriptions/subscriptions.component.html b/src/components/library/subscriptions/subscriptions.component.html
index 9019ad0..f299aa8 100644
--- a/src/components/library/subscriptions/subscriptions.component.html
+++ b/src/components/library/subscriptions/subscriptions.component.html
@@ -82,7 +82,7 @@
Aucune suggestion : chaque thème a moins de 2 chaînes, ou tout est déjà rangé.
0" class="mt-3 space-y-2">
-
+
{{ s.icon }}
@@ -138,7 +138,7 @@
0; else noGroups" class="grid gap-4 sm:grid-cols-2 xl:grid-cols-3">
@@ -215,7 +215,7 @@
Tous
— Choisir un groupe —
- {{ group.icon }} {{ group.name }}
+ {{ group.icon }} {{ group.name }}
0; else emptyState">
@@ -404,7 +404,7 @@
Créer un groupe
{{ items().length }}
-
+
{
if (this.suggestBusy()) return Promise.resolve();
this.suggestBusy.set(true);
- const assignAll = (groupId: string): Promise => {
- const calls = s.channelIds
- .filter((sid) => !(this.members()[String(sid)] || []).includes(groupId))
- .map((sid) => new Promise((resolve) => {
- const current = this.members()[String(sid)] || [];
- this.subs.setSubscriptionGroups(sid, [...current, groupId]).subscribe({ next: () => resolve(), error: () => resolve() });
- }));
- return Promise.all(calls).then(() => undefined);
- };
const done = () => this.suggestBusy.set(false);
+ const run = (gid: string): Promise => new Promise((resolve) => {
+ this.subs.setGroupMembers(gid, this.memberIdsOf(gid, s)).subscribe({ next: () => resolve(), error: () => resolve() });
+ });
if (s.kind === 'complete' && s.existingGroupId) {
- return assignAll(s.existingGroupId).finally(done);
+ return run(s.existingGroupId).finally(done);
}
return new Promise((resolve) => {
- this.subs.createGroup(s.name, s.color, s.icon).subscribe((g) => resolve(g?.id ? assignAll(g.id) : undefined));
+ this.subs.createGroup(s.name, s.color, s.icon).subscribe((g) => {
+ if (g?.id) run(g.id).then(() => resolve());
+ else resolve();
+ });
}).finally(done);
}
+ /** Ids déjà dans le groupe + nouveaux (union), pour l'assignation en lot. */
+ private memberIdsOf(groupId: string, s: GroupSuggestion): number[] {
+ const members = this.members();
+ const current = Object.keys(members)
+ .filter((k) => (members[k] || []).includes(groupId))
+ .map(Number);
+ return [...new Set([...current, ...s.channelIds])];
+ }
+
/** Applique toutes les suggestions d'un coup, dans l'ordre. */
async applyAllSuggestions(): Promise {
if (this.suggestBusy()) return;
@@ -213,6 +219,19 @@ export class SubscriptionsComponent {
}
}
+ // --- trackBy : DOM stable (pas de re-rendu complet + recharge d'images à chaque tick) ---
+ trackBySubscriptionId(_idx: number, item: SubscriptionItem): number {
+ return item.subscriptionId;
+ }
+
+ trackByGroupId(_idx: number, group: SubscriptionGroup): string {
+ return group.id;
+ }
+
+ trackBySuggestion(_idx: number, s: GroupSuggestion): string {
+ return `${s.kind}:${s.existingGroupId || s.name}`;
+ }
+
readonly items = computed(() => this.subs.items());
readonly groups = computed(() => this.subs.subscriptionGroups());
readonly members = computed(() => this.subs.groupMembers());
diff --git a/src/services/subscriptions.service.ts b/src/services/subscriptions.service.ts
index e935e33..4a5d382 100644
--- a/src/services/subscriptions.service.ts
+++ b/src/services/subscriptions.service.ts
@@ -546,6 +546,40 @@ export class SubscriptionsService {
);
}
+ /**
+ * Remplace les chaînes d'un groupe en UNE seule requête (assignation en lot).
+ * Évite la tempête de PUT par chaîne (rate-limit + re-rendus en cascade).
+ */
+ setGroupMembers(groupId: string, subscriptionIds: number[]): Observable {
+ const ids = [...new Set((subscriptionIds || []).map(Number).filter(Number.isFinite))];
+ return this.http.put(`/api/subscription-groups/${groupId}/members`, { subscriptionIds: ids }).pipe(
+ tap(() => {
+ const wanted = new Set(ids.map(String));
+ const members = { ...this.members() };
+ for (const key of Object.keys(members)) {
+ const cur = members[key] || [];
+ const has = cur.includes(groupId);
+ const want = wanted.has(key);
+ if (has === want) continue;
+ members[key] = want ? [...cur, groupId] : cur.filter((gid) => gid !== groupId);
+ }
+ for (const sid of wanted) {
+ const key = String(sid);
+ if (!(key in members)) members[key] = [groupId];
+ }
+ this.members.set(members);
+ this.refreshGroupCounts();
+ }),
+ map(() => true),
+ catchError((err) => {
+ console.error('Failed to set group members', err);
+ this.lastErrorMsg.set('Assignation en lot impossible. Réessayez.');
+ this.listGroups().subscribe();
+ return of(false);
+ })
+ );
+ }
+
/** Recalcule les compteurs locaux sans appel réseau. */
private refreshGroupCounts(): void {
const members = this.members();