-
-
Notifications
You must be signed in to change notification settings - Fork 622
chore: update to angular v20 #613
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Conversation
- Removed unnecessary imports from header, account-info, loading-overlay, navigation-bar, season-container, serial-details, and vod-details components. - Updated template syntax to use structural directives more efficiently in mpv-player-bar, account-info, navigation-bar, season-container, serial-details, and vod-details components. - Enhanced readability and maintainability of the code by organizing imports and using Angular's async pipe and structural directives appropriately. - Adjusted TypeScript configuration to use "bundler" for module resolution for better compatibility with modern build tools.
The latest updates on your projects. Learn more about Vercel for GitHub.
|
Warning
|
Cohort / File(s) | Summary |
---|---|
Tooling & Configpackage.json , tsconfig.json , angular.json |
Bump Angular to 20.x and TypeScript to 5.9; switch moduleResolution to "bundler"; extend schematics with type/typeSeparator entries. |
Bootstrapsrc/main.ts |
Use platformBrowser() instead of platformBrowserDynamic(); remove Tauri detection and service worker registration. |
App Rootsrc/app/app.component.html , src/app/app.component.ts |
Switch *ngIf to @if; change modals type from ModalWindow[] to any[] and drop type import. |
Homesrc/app/home/home.component.html , src/app/home/home.component.ts |
Replace *ngIf with @if; remove NgIf from standalone imports. |
Recent Playlistssrc/app/home/recent-playlists/recent-playlists.component.html , .../recent-playlists.component.ts |
Migrate *ngIf/*ngFor/ng-template to @if/@for; add readonly ghostElements; make searchQuery readonly; drop NgIf/NgFor imports. |
Headersrc/app/shared/components/header/header.component.html , .../header.component.ts , .../header.component.scss |
Rewrite menus/modals with @if/@for; adjust menu structure; remove NgIf import; change .active-sort background color. |
Service Typessrc/app/services/whats-new.service.ts |
Remove ModalWindow return types and related import (commented). |
Player Components (imports cleanup)src/app/player/components/art-player/art-player.component.ts , .../d-player/d-player.component.ts , .../epg-list/epg-item-description/epg-item-description.component.ts , src/app/xtream-tauri/loading-overlay/loading-overlay.component.ts , src/app/xtream/navigation-bar/navigation-bar.component.ts , src/app/xtream/serial-details/serial-details.component.ts , src/app/xtream/vod-details/vod-details.component.ts , src/app/xtream/season-container/season-container.component.ts , src/app/xtream-tauri/account-info/account-info.component.ts |
Remove CommonModule/NgIf from standalone imports; no logic changes. |
EPG Item Description (template)src/app/player/components/epg-list/epg-item-description/epg-item-description.component.html |
Convert *ngIf blocks to @if; keep content and bindings equivalent. |
Multi-EPGsrc/app/player/components/multi-epg/multi-epg-container.component.html , .../multi-epg-container.component.ts |
Migrate loops/conditions to @for/@if; adjust track strategies; change trackBy to stringify title in key. |
Audio Playersrc/app/player/components/audio-player/audio-player.component.ts |
Switch template conditions to @if; remove NgIf import; make url input required via @input({ required: true }). |
Video Playersrc/app/player/components/video-player/video-player.component.html , .../video-player.component.scss |
Replace defer/ngIf with @defer/@loading/@placeholder/@if; restructure player branches; adjust container positioning (remove top: 0). |
Web Player Viewsrc/app/portals/web-player-view/web-player-view.component.html , .../web-player-view.component.ts |
Use @if for html5 branch; remove NgIf from imports. |
MPV Player Barsrc/app/shared/components/mpv-player-bar/mpv-player-bar.component.html |
Convert *ngIf/*ngFor to @if/@for; explicit thumbnail else path. |
Settings UIsrc/app/settings/settings.component.html |
Major template migration to @if/@for; reflow EPG URL management into dynamic list; reorganize sections. |
Stalker Resources (API refactor)src/app/stalker/stalker.store.ts , src/app/stalker/stalker-search/stalker-search.component.ts , src/app/stalker/stalker-favorites/stalker-favorites.component.ts , src/app/stalker/recently-viewed/recently-viewed.component.ts |
Replace rxResource request/loader with params/stream; update all internal references and IPC param construction accordingly. |
Xtream Views (templates)src/app/xtream/navigation-bar/navigation-bar.component.html , src/app/xtream/season-container/season-container.component.html , src/app/xtream/serial-details/serial-details.component.html , src/app/xtream/vod-details/vod-details.component.html , src/app/xtream-tauri/account-info/account-info.component.ts |
Convert *ngIf/*ngFor/ng-template to @if/@for and @else; minor structure additions for fields and placeholders. |
Category Content Viewsrc/app/xtream-tauri/category-content-view/category-content-view.component.ts |
Make pageSizeOptions depend on isStalker (stalker → [14], else → [10,25,50,100]). |
Xtream Main Containersrc/app/xtream/xtream-main-container.component.ts |
Add maxWidth and maxHeight to PlayerDialogComponent open calls; formatting tweak. |
Player Dialog Stylesrc/app/xtream-tauri/player-dialog/player-dialog.component.scss |
Set .link-input width to 100%; newline at EOF. |
Sequence Diagram(s)
sequenceDiagram
autonumber
actor User
participant App as Angular App
participant Browser as PlatformBrowser
Note over App,Browser: Bootstrap flow (updated)
User->>Browser: Load index.html
Browser->>App: platformBrowser().bootstrapModule(AppModule)
App-->>Browser: App initialized (no SW registration)
sequenceDiagram
autonumber
participant UI as Component
participant RxRes as rxResource
participant Svc as Playlist/IPC Service
Note over UI,RxRes: Resource flow refactor (request→params, loader→stream)
UI->>RxRes: params(): { ... }
alt Stream-based
RxRes->>Svc: stream(): fetch$(params)
Svc-->>RxRes: Observable<Data>
RxRes-->>UI: value()/status()
end
Estimated code review effort
🎯 4 (Complex) | ⏱️ ~60 minutes
Poem
Hoppity hop through templates I go,
Swapping stars for @ifs in a tidy new flow.
Bundler winds whisper, browsers hum bright,
Params feed streams in the Stalker’s night.
With dialogs sized and players anew—
I thump my review stamp: v20, woohoo! 🐇✨
Pre-merge checks and finishing touches
✅ Passed checks (3 passed)
Check name | Status | Explanation |
---|---|---|
Description Check | ✅ Passed | Check skipped - CodeRabbit’s high-level summary is enabled. |
Title Check | ✅ Passed | The title "chore: update to angular v20" is concise and accurately reflects the PR's primary change—upgrading Angular and related tooling/configuration to version 20—so a reviewer scanning history will understand the main intent. It avoids noise and clearly signals a maintenance chore rather than a feature or bugfix. |
Docstring Coverage | ✅ Passed | No functions found in the changes. Docstring coverage check skipped. |
✨ Finishing touches
- 📝 Generate Docstrings
🧪 Generate unit tests
- Create PR with unit tests
- Post copyable unit tests in a comment
- Commit unit tests in branch
chore/update-angular-to-v20
Tip
👮 Agentic pre-merge checks are now available in preview!
Pro plan users can now enable pre-merge checks in their settings to enforce checklists before merging PRs.
- Built-in checks – Quickly apply ready-made checks to enforce title conventions, require pull request descriptions that follow templates, validate linked issues for compliance, and more.
- Custom agentic checks – Define your own rules using CodeRabbit’s advanced agentic capabilities to enforce organization-specific policies and workflows. For example, you can instruct CodeRabbit’s agent to verify that API documentation is updated whenever API schema files are modified in a PR. Note: Upto 5 custom checks are currently allowed during the preview period. Pricing for this feature will be announced in a few weeks.
Please see the documentation for more information.
Example:
reviews:
pre_merge_checks:
custom_checks:
- name: "Undocumented Breaking Changes"
mode: "warning"
instructions: |
Pass/fail criteria: All breaking changes to public APIs, CLI flags, environment variables, configuration keys, database schemas, or HTTP/GraphQL endpoints must be documented in the "Breaking Change" section of the PR description and in CHANGELOG.md. Exclude purely internal or private changes (e.g., code not exported from package entry points or explicitly marked as internal).
Please share your feedback with us on this Discord post.
Comment @coderabbitai help
to get the list of available commands and usage tips.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 13
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (8)
src/app/stalker/stalker.store.ts (2)
186-186
: Null check order bug: response.js is accessed before verifying response.Move patchState inside the response check and harden the error path.
- patchState(store, { totalCount: response.js.total_items }); - - if (response) { + if (response) { + patchState(store, { totalCount: response.js?.total_items ?? 0 }); const newItems = response.js.data.map((item) => ({ ...item, cover: item.screenshot_uri, })); @@ - } else { - throw new Error( - `Error: ${response.message} (Status: ${response.status})` - ); - } + } + else { + throw new Error('STALKER_REQUEST returned no response for content list'); + }Also applies to: 216-220
141-146
: Error construction dereferences undefined response.Throw a fixed message (or use optional chaining) when response is falsy.
- } else { - throw new Error( - `Error: ${response.message} (Status: ${response.status})` - ); - } + } else { + throw new Error('STALKER_REQUEST returned no response for categories'); + }Apply the same pattern to the getContentResource error block (lines 217–220).
Also applies to: 217-220
src/app/home/home.component.ts (1)
14-24
: Add standalone: true; imports require standalone components.This component declares an imports array but isn’t marked standalone. In Angular, imports on @component are only valid for standalone components. Add standalone: true to avoid a compile error and ensure the listed imports are honored. Also verify that TranslatePipe is available as a standalone import in your ngx-translate version; if not, import TranslateModule instead. (v18.angular.dev)
Apply this diff:
@Component({ selector: 'app-home', templateUrl: './home.component.html', styleUrls: ['./home.component.scss'], + standalone: true, imports: [ HeaderComponent, MatProgressBarModule, RecentPlaylistsComponent, TranslatePipe ], })
src/app/player/components/audio-player/audio-player.component.ts (1)
107-111
: Avoid using ViewChild in ngOnChanges; guard and don’t reassign src imperatively.
@ViewChild
may not be ready for the firstngOnChanges
. Also, with[src]
bound, you don’t need to set it manually.- ngOnChanges(changes: SimpleChanges): void { - this.audio.nativeElement.src = changes.url.currentValue; - this.audio.nativeElement.load(); - this.play(); - } + ngOnChanges(changes: SimpleChanges): void { + if ('url' in changes && this.audio?.nativeElement) { + this.audio.nativeElement.load(); + this.play(); + } + }src/app/player/components/video-player/video-player.component.html (1)
30-39
: Avoid “undefined” in stream URL when epgParams is missing.Concatenation can produce
...undefined
. DefaultepgParams
to empty string.- [options]="{ - sources: [ - { - src: - activeChannel?.url + - activeChannel?.epgParams, - type: 'application/x-mpegURL', - }, - ], - }" + [options]="{ + sources: [ + { + src: (activeChannel?.url ?? '') + (activeChannel?.epgParams ?? ''), + type: 'application/x-mpegURL' + } + ] + }"src/app/player/components/multi-epg/multi-epg-container.component.ts (1)
60-68
: Unsubscribed input subscription can leak.Use takeUntilDestroyed() for the playlistChannels subscription.
+import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; ... @Input() set playlistChannels(value: Observable<Channel[]>) { if (value) { - value.subscribe((channels) => { + value.pipe(takeUntilDestroyed()).subscribe((channels) => { this._playlistChannels = channels; this.initializeVisibleChannels(); this.requestPrograms(); }); } }src/app/shared/components/header/header.component.ts (1)
165-173
: Rename opedAddPlaylistDialog → openAddPlaylistDialog and update call sites.Rename the method in src/app/shared/components/header/header.component.ts and update the template bindings below so the click handlers point to the new name.
Locations to update:
- src/app/shared/components/header/header.component.ts: lines ~165-173 (method).
- src/app/shared/components/header/header.component.html: lines 192, 200, 208, 217, 227 (click handlers).
Diff (method rename):
- opedAddPlaylistDialog(type: PlaylistType) { + openAddPlaylistDialog(type: PlaylistType) { this.dialog.open<AddPlaylistDialogComponent, { type: PlaylistType }>( AddPlaylistDialogComponent, { width: '600px', data: { type }, } ); }src/app/xtream/vod-details/vod-details.component.ts (1)
40-42
: Guard against potential null parent routeAccessing
this.route.parent.snapshot
can throw whenparent
is null. Use optional chaining.Apply this diff:
- private portalId = - this.route.snapshot.paramMap.get('id') ?? - this.route.parent.snapshot.params.id; + private portalId = + this.route.snapshot.paramMap.get('id') ?? + this.route.parent?.snapshot?.params?.id;
🧹 Nitpick comments (49)
src/app/xtream-tauri/player-dialog/player-dialog.component.scss (1)
8-12
: Guard against overflow with 100% width in dialog inputsBumping to 100% can introduce horizontal scroll in some layouts (borders/padding, flex containers). Add safe constraints so it never exceeds the container.
.link-input { - width: 100%; + width: 100%; + max-width: 100%; + min-width: 0; // avoids flex overflow + box-sizing: border-box; // ensures borders/padding don’t spill padding-top: 5px; @include mat.form-field-density(-5); }src/app/xtream/season-container/season-container.component.html (3)
3-11
: Use stable track expression and ensure numeric season ordering
- Tracking by the whole
season
object is unstable withkeyvalue
(new entries are new object refs). Track byseason.key
to avoid DOM churn.- If season keys are numeric strings (e.g., "10", "2"), default
keyvalue
ordering is lexicographic. Provide a numeric comparator to prevent misordered seasons.Apply this diff:
- @if (!selectedSeason) { - @for (season of seasons | keyvalue; track season) { + @if (!selectedSeason) { + @for (season of (seasons | keyvalue: seasonNumberCompare); track season.key) {Add in the component TS:
// In season-container.component.ts readonly seasonNumberCompare = (a: {key: string|number}, b: {key: string|number}) => Number(a.key) - Number(b.key);
13-15
: Make the “Back to seasons” card accessible
mat-card
isn’t a button. Add semantic/keyboard affordances or switch to a real button to satisfy a11y.- <mat-card class="episode-item" (click)="selectedSeason = undefined"> + <mat-card class="episode-item" role="button" tabindex="0" + (click)="selectedSeason = undefined" + (keydown.enter)="selectedSeason = undefined" + (keydown.space)="selectedSeason = undefined; $event.preventDefault()"> <div class="episode-title">< Back to seasons</div> </mat-card>
16-30
: Track episodes by a stable id and harden null‑safety/a11y on image
track episode
risks re-renders; track by a stable key (e.g.,episode.id
orepisode_num
).- Guard optional paths in
@if
to avoid runtime errors ifinfo
is missing.- Add
alt
andloading="lazy"
to the image for a11y and perf.- @for (episode of seasons[selectedSeason]; track episode) { + @for (episode of seasons[selectedSeason]; track episode.id ?? episode.episode_num) { <mat-card class="episode-item" (click)="selectEpisode(episode)" > - @if (episode.info.movie_image) { + @if (episode?.info?.movie_image) { <mat-card-content - ><img [src]="episode.info.movie_image" class="episode-cover" - /></mat-card-content> + ><img [src]="episode?.info?.movie_image" + class="episode-cover" + loading="lazy" + [alt]="episode?.title || ('Episode ' + episode?.episode_num)" /></mat-card-content> } <div class="episode-title"> {{ episode.episode_num }}. {{ episode.title }} </div> </mat-card> }src/app/stalker/stalker.store.ts (1)
88-96
: Minor: prefer params() returning undefined to idle the resource.Instead of checking currentPlaylist in the loader, compute params as undefined when playlist is missing; loader won’t run and status stays idle. This matches Angular’s resource docs. (angular.dev)
- params: () => ({ - contentType: store.selectedContentType(), - action: StalkerPortalActions.GetCategories, - currentPlaylist: store.currentPlaylist(), - }), - loader: async ({ params }) => { - if (params.currentPlaylist === undefined) return; + params: () => { + const cp = store.currentPlaylist(); + if (!cp) return undefined; + return { + contentType: store.selectedContentType(), + action: StalkerPortalActions.GetCategories, + currentPlaylist: cp, + }; + }, + loader: async ({ params }) => {src/app/xtream-tauri/category-content-view/category-content-view.component.ts (1)
40-40
: Namespace the page-size localStorage key to avoid cross-feature bleed.
category-content-view.component.ts sets 'xtream-page-size' (src/app/xtream-tauri/category-content-view/category-content-view.component.ts:58) and xtream.store.ts reads it for limit (src/app/xtream-tauri/xtream.store.ts:50); use distinct keys per API/mode (e.g., 'xtream:{apiId}:page-size' or 'stalker-page-size').src/app/player/components/video-player/video-player.component.scss (1)
1-6
: Ensure.main-container
spans full height: Re-addtop: 0
or specifyheight: 100%
/100vh
for the absolute<mat-drawer-container class="main-container">
; without it, the container will size to its content and may collapse.src/app/home/home.component.ts (1)
86-90
: Use undefined (or omit) the snack-bar action instead of null.MatSnackBar.open’s second parameter is an optional string. Passing null can trip strict typing; use undefined or omit the arg. (v12.material.angular.io)
- this.snackBar.open(message, null, { + this.snackBar.open(message, undefined, { duration, });src/main.ts (1)
10-14
: preserveWhitespaces here has no effect with AOT bootstrap.With platformBrowser().bootstrapModule, preserveWhitespaces in CompilerOptions only affected JIT. Either remove it or configure whitespace at build time (tsconfig/angular.json) if needed.
platformBrowser() - .bootstrapModule(AppModule, { - preserveWhitespaces: false, - }) + .bootstrapModule(AppModule) .catch((err) => console.error(err));src/app/xtream-tauri/account-info/account-info.component.ts (2)
25-139
: Localize remaining static strings.Most of the dialog content is static English. Consider piping these through translate for consistency with the Close button.
Example:
-<h2 mat-dialog-title>Account Information</h2> +<h2 mat-dialog-title>{{ 'ACCOUNT_INFO.TITLE' | translate }}</h2>
170-195
: Remove debug logs and harden date parsing.Drop console.log calls and parse epoch strings with an explicit radix (or Number) to avoid NaN in edge cases.
- console.log(playlist); + // noop @@ - console.log(this.accountInfo); if (this.accountInfo) { - this.formattedExpDate = new Date( - parseInt(this.accountInfo.user_info.exp_date) * 1000 - ).toLocaleDateString(); - this.formattedCreatedDate = new Date( - parseInt(this.accountInfo.user_info.created_at) * 1000 - ).toLocaleDateString(); + const exp = Number.parseInt(this.accountInfo.user_info.exp_date, 10); + const created = Number.parseInt(this.accountInfo.user_info.created_at, 10); + this.formattedExpDate = new Date(exp * 1000).toLocaleDateString(); + this.formattedCreatedDate = new Date(created * 1000).toLocaleDateString(); }src/app/xtream-tauri/loading-overlay/loading-overlay.component.ts (1)
12-20
: Prefer determinate when total > 0 (show 0% instead of indeterminate).Rendering an indeterminate bar when current is 0 can feel jumpy. Gate determinate solely on total() > 0 and compute 0% naturally.
- @if (current() !== 0 && total() !== 0) { + @if (total() > 0) { <mat-progress-bar mode="determinate" [value]="(current() / total()) * 100" /> <p>{{ current() }} / {{ total() }}</p>src/app/shared/components/mpv-player-bar/mpv-player-bar.component.html (3)
1-4
: Avoid double async subscriptions; alias once.Use @if (... | async; as processes) and iterate over processes to prevent re-subscribing and re-evaluating the observable. (angular.dev)
-@if ((activeProcesses$ | async)?.length) { - <div class="mpv-player-bar"> - @for (process of activeProcesses$ | async; track process.id) { +@if (activeProcesses$ | async; as processes) { + @if (processes?.length) { + <div class="mpv-player-bar"> + @for (process of processes; track process.id) {
7-15
: Move fallback image logic into a method to avoid direct DOM mutation in templates.Inline assignment to $event.target.src is brittle. Delegate to a handler.
- <img + <img [src]="process.thumbnail" [alt]="process.title" class="media-thumbnail" - (error)=" - $event.target.src = - './assets/images/default-poster.png' - " + (error)="onImgError($event)" />Add in the component TS:
onImgError(e: Event) { (e.target as HTMLImageElement).src = './assets/images/default-poster.png'; }
41-47
: Localize the button title.For consistency with the rest of the app, consider translating the Close tooltip.
- title="Close" + [title]="'CLOSE' | translate"src/app/xtream/serial-details/serial-details.component.html (2)
12-14
: Add alt/decoding/lazy to cover image for a11y and perf.- <img [src]="item.info.cover" /> + <img [src]="item.info.cover" [attr.alt]="item.info.name || 'cover'" decoding="async" loading="lazy" />
41-46
: Prefer semantic containers over label elements.
<label>
without a control hurts a11y. Use<p>
or<div>
.Also applies to: 48-51, 54-60
src/app/player/components/audio-player/audio-player.component.ts (1)
137-139
: Remove console log or gate behind env.Leftover
console.log
in production code.- console.log(direction); this.store.dispatch(setAdjacentChannelAsActive({ direction }));
src/app/player/components/epg-list/epg-item-description/epg-item-description.component.html (1)
10-16
: Don’t render empty language row when lang is missing.Move the
<p data-test="lang">
inside the@if (epgProgram.title[0].lang)
block.- @if (epgProgram.title[0].lang) { - <div class="subheading-2"> - {{ 'EPG.PROGRAM_DIALOG.LANGUAGE' | translate }} - </div> - } - <p data-test="lang">{{ epgProgram.title[0].lang }}</p> + @if (epgProgram.title[0].lang) { + <div class="subheading-2"> + {{ 'EPG.PROGRAM_DIALOG.LANGUAGE' | translate }} + </div> + <p data-test="lang">{{ epgProgram.title[0].lang }}</p> + }src/app/player/components/video-player/video-player.component.html (2)
72-74
: Add alt text to illustrative images.- <img src="./assets/images/custom-player.png" /> + <img src="./assets/images/custom-player.png" alt="External player illustration" />Also applies to: 92-93
21-26
: Consider boolean coercion for radio flag.If
radio
can be'true' | 'false' | boolean
, coerce once in the controller and use a boolean in the template.src/app/xtream/navigation-bar/navigation-bar.component.html (2)
15-23
: Track function stability.Using a function call in
track
can create unnecessary churn. Prefer a stable key:track item.contentType
or a unique id.- @for ( - item of contentTypeNavigationItems; track trackByValue($index, - item)) { + @for (item of contentTypeNavigationItems; track item.contentType) {
93-99
: Translate the search placeholder.Hardcoded placeholder breaks i18n consistency.
- placeholder="Search by name" + [placeholder]="'CHANNELS.SEARCH_BY_NAME' | translate"package.json (1)
92-103
: Align Angular packages between deps and devDeps.Core Angular libs (
@angular/core/common/router/...
) are in devDependencies while others are in dependencies. For clarity and to avoid packaging pitfalls (Electron/Tauri), keep Angular runtime libs aligned (same major and group). Consider moving all@angular/*
to devDependencies or dependencies consistently.Also applies to: 37-49
src/app/settings/settings.component.html (9)
43-46
: Remove stray matLine attributes from buttons.matLine is for list items, not mat-icon-button; drop it to avoid unknown-attr noise.
- <button - matLine + <button mat-icon-button color="accent" ... - <button - mat-icon-button - matLine + <button + mat-icon-button color="accent"Also applies to: 54-57
57-60
: Fix tooltip text for “Remove EPG source”.Tooltip reuses REFRESH_EPG; use a remove-specific key.
- [matTooltip]="'SETTINGS.REFRESH_EPG' | translate" + [matTooltip]="'SETTINGS.REMOVE_EPG_SOURCE' | translate"Please confirm the i18n key exists (or provide the correct one).
48-51
: Harden disabled condition (trim whitespace).Prevents “Refresh” enabled when input has only spaces.
- [disabled]="epgField.value === ''" + [disabled]="!epgField.value?.trim()"
113-115
: Remove unsupported “for” on mat-label.mat-label doesn’t support “for”; rely on form-field semantics or aria-labelledby.
- <mat-label for="mpvPlayerPath">{{ 'SETTINGS.MPV_PLAYER_PATH' | translate }}</mat-label> + <mat-label>{{ 'SETTINGS.MPV_PLAYER_PATH' | translate }}</mat-label> ... - <mat-label for="vlcPlayerPath">{{ 'SETTINGS.VLC_PLAYER_PATH' | translate }}</mat-label> + <mat-label>{{ 'SETTINGS.VLC_PLAYER_PATH' | translate }}</mat-label>Also applies to: 136-138
92-99
: Use stable, primitive track expressions.Track by id/value to reduce DOM churn when arrays reallocate.
- @for (player of players; track player) { + @for (player of players; track player.id) { ... - @for (streamFormat of streamFormatEnum | keyvalue; track streamFormat) { + @for (streamFormat of streamFormatEnum | keyvalue; track streamFormat.value) { ... - @for (language of languageEnum | keyvalue; track language) { + @for (language of languageEnum | keyvalue; track language.value) { ... - @for (theme of themeEnum | keyvalue; track theme) { + @for (theme of themeEnum | keyvalue; track theme.value) {Also applies to: 164-170, 190-198, 218-226
42-52
: Add aria-labels to icon buttons.Improves a11y for screen readers.
- <button + <button mat-icon-button color="accent" [matTooltip]="'SETTINGS.REFRESH_EPG' | translate" type="button" [disabled]="!epgField.value?.trim()" (click)="refreshEpg(epgUrl.value[i])" + [attr.aria-label]="'SETTINGS.REFRESH_EPG' | translate" > ... - <button + <button mat-icon-button color="accent" - [matTooltip]="'SETTINGS.REFRESH_EPG' | translate" + [matTooltip]="'SETTINGS.REMOVE_EPG_SOURCE' | translate" type="button" (click)="removeEpgSource(i)" + [attr.aria-label]="'SETTINGS.REMOVE_EPG_SOURCE' | translate" >Also applies to: 53-62
157-159
: Placeholders reference VIDEO_PLAYER key; use specific keys for each select.Avoids confusing UX/i18n.
- <mat-label>{{ 'SETTINGS.VIDEO_PLAYER_PLACEHOLDER' | translate }}</mat-label> + <mat-label>{{ 'SETTINGS.STREAM_FORMAT_PLACEHOLDER' | translate }}</mat-label> ... - <mat-label>{{ 'SETTINGS.VIDEO_PLAYER_PLACEHOLDER' | translate }}</mat-label> + <mat-label>{{ 'SETTINGS.LANGUAGE_PLACEHOLDER' | translate }}</mat-label> ... - <mat-label>{{ 'SETTINGS.VIDEO_PLAYER_PLACEHOLDER' | translate }}</mat-label> + <mat-label>{{ 'SETTINGS.THEME_PLACEHOLDER' | translate }}</mat-label>Please confirm these keys exist (or provide the intended ones).
Also applies to: 183-185, 211-213
277-283
: Port input should be numeric with sensible bounds.Tighten validation to avoid invalid ports.
- <input - matInput - type="text" + <input + matInput + type="number" + inputmode="numeric" + min="1024" + max="65535" id="remoteControlPort" formControlName="remoteControlPort" />
355-357
: Open external link safely in a new tab.Add target and rel to prevent tab-napping.
- <a href="https://github.com/4gray/iptvnator/releases">{{ + <a href="https://github.com/4gray/iptvnator/releases" target="_blank" rel="noopener noreferrer">{{src/app/shared/components/header/header.component.ts (1)
114-117
: Avoid bare subscribe; add automatic teardown.Prevents leaks on component destroy.
+import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; ... - this.sortService.getSortOptions().subscribe((options) => { + this.sortService + .getSortOptions() + .pipe(takeUntilDestroyed()) + .subscribe((options) => { this.currentSortOptions = options; - }); + });src/app/player/components/multi-epg/multi-epg-container.component.ts (2)
213-221
: Date parsing robustness.new Date(dateStr) is format/locale-dependent; prefer date-fns parse with an explicit pattern and TZ, with a safe fallback.
// Example: import { parse } from 'date-fns'; // parse '20250101123000 +0000' as 'yyyyMMddHHmmss xx' const safeParse = (s: string) => parse(s, 'yyyyMMddHHmmss xx', new Date());Apply in getCachedDate() and when creating start/stop dates.
138-140
: Replace console.log with a logger or guard by env.Noisy logs in production degrade UX.
if (!ngDevMode) { /* skip logs */ } // or use an injected LoggerService with levelsAlso applies to: 167-171, 181-189
src/app/app.component.ts (1)
49-49
: Avoid any[] for modals; restore/define a typed shape.Losing types weakens safety; either import the interface (as type-only) or define a local Modal-like type exposed by WhatsNewService.
- modals: any[] = []; + // If ngx-whats-new exports a type: + // import type { ModalWindow } from 'ngx-whats-new'; + // modals: ModalWindow[] = []; + // Otherwise define a minimal local shape: + type ModalItem = { title: string; content: string; [k: string]: unknown }; + modals: ModalItem[] = [];src/app/portals/web-player-view/web-player-view.component.ts (1)
37-40
: Type cast on toSignal is too narrow.settings emits asynchronously; allow undefined in the Signal type.
- ) as Signal<Settings>; + ) as Signal<Settings | undefined>;src/app/portals/web-player-view/web-player-view.component.html (1)
3-6
: Remove redundant nested @if (player === 'html5').Outer branch already guards html5; inner check is duplicate.
} @else if (player === 'html5') { - @if (player === 'html5') { - <app-html-video-player [channel]="channel" /> - } + <app-html-video-player [channel]="channel" /> }src/app/xtream/vod-details/vod-details.component.ts (1)
51-76
: One-off read should complete subscriptionIf
getPortalFavorites
returns a long-lived stream, this leaks. Take a single emission.Apply this diff:
- this.playlistService - .getPortalFavorites(this.portalId) - .subscribe((favorites) => { + this.playlistService + .getPortalFavorites(this.portalId) + .pipe(take(1)) + .subscribe((favorites) => {Add import:
-import { ActivatedRoute } from '@angular/router'; +import { ActivatedRoute } from '@angular/router'; +import { take } from 'rxjs';src/app/services/whats-new.service.ts (3)
2-2
: Restore explicit typing with type-only import (keeps bundler-friendly deep-import avoidance)Commenting the type import and dropping return types weakens API guarantees.
Apply this diff:
-/* import { ModalWindow } from 'ngx-whats-new/lib/modal-window.interface'; */ +import type { ModalWindow } from 'ngx-whats-new';If
ngx-whats-new
does not re-exportModalWindow
, define a local minimal type used by your templates instead of deep-importing.
303-305
: Add explicit return typeKeep method contract stable.
Apply this diff:
- getModalsByVersion(version: string) { + getModalsByVersion(version: string): ModalWindow[] { return this.modals[version] || []; }
310-314
: Add explicit return typeSame reasoning as above.
Apply this diff:
- getLatestChanges() { + getLatestChanges(): ModalWindow[] { const modalsLength = Object.keys(this.modals).length; const lastVersion = Object.keys(this.modals)[modalsLength - 1]; return this.modals[lastVersion]; }src/app/xtream/vod-details/vod-details.component.html (2)
13-18
: Add alt text for poster imageImprove a11y and LCP hints.
Apply this diff:
- <img - [src]="item.info?.movie_image" + <img + [src]="item.info?.movie_image" + [attr.alt]="item.info?.name || 'Poster'" (error)=" $event.target.src = './assets/images/default-poster.png' " />
122-122
: Deprecated attribute
frameborder
is obsolete in HTML5.Apply this diff:
- frameborder="0"
src/app/home/recent-playlists/recent-playlists.component.html (1)
4-12
: Add an explicit aria-label to the search inputPlaceholder text isn’t a label; improve screen-reader support.
Apply this diff:
<input matInput #searchQuery type="search" spellcheck="false" autocomplete="off" [placeholder]="'HOME.PLAYLISTS.SEARCH_PLAYLISTS' | translate" + [attr.aria-label]="'HOME.PLAYLISTS.SEARCH_PLAYLISTS' | translate" (input)="onSearchQueryUpdate(searchQuery.value)" />
src/app/player/components/multi-epg/multi-epg-container.component.html (1)
34-49
: Hard-coded English labels in navigation buttonsLocalize these strings for consistency.
Apply this diff:
- <mat-icon>keyboard_arrow_up</mat-icon> Previous channels + <mat-icon>keyboard_arrow_up</mat-icon> {{ 'EPG.PREVIOUS_CHANNELS' | translate }} ... - <mat-icon>keyboard_arrow_down</mat-icon> Next channels + <mat-icon>keyboard_arrow_down</mat-icon> {{ 'EPG.NEXT_CHANNELS' | translate }}And likewise replace
matTooltip="Previous channels"
/"Next channels"
with translated keys.src/app/shared/components/header/header.component.html (2)
73-75
: Consider translating the GitHub labelAll other items use i18n; keep this consistent if a key exists.
Apply this diff (assuming
MENU.GITHUB
exists):- <span>GitHub</span> + <span>{{ 'MENU.GITHUB' | translate }}</span>
189-234
: Minor naming nit: opedAddPlaylistDialogMethod name has a typo and is used in multiple actions. Consider renaming to
openAddPlaylistDialog
and updating call sites.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (1)
package-lock.json
is excluded by!**/package-lock.json
📒 Files selected for processing (44)
angular.json
(1 hunks)package.json
(1 hunks)src/app/app.component.html
(1 hunks)src/app/app.component.ts
(1 hunks)src/app/home/home.component.html
(1 hunks)src/app/home/home.component.ts
(2 hunks)src/app/home/recent-playlists/recent-playlists.component.html
(1 hunks)src/app/home/recent-playlists/recent-playlists.component.ts
(2 hunks)src/app/player/components/art-player/art-player.component.ts
(2 hunks)src/app/player/components/audio-player/audio-player.component.ts
(2 hunks)src/app/player/components/d-player/d-player.component.ts
(2 hunks)src/app/player/components/epg-list/epg-item-description/epg-item-description.component.html
(1 hunks)src/app/player/components/epg-list/epg-item-description/epg-item-description.component.ts
(2 hunks)src/app/player/components/multi-epg/multi-epg-container.component.html
(1 hunks)src/app/player/components/multi-epg/multi-epg-container.component.ts
(1 hunks)src/app/player/components/video-player/video-player.component.html
(2 hunks)src/app/player/components/video-player/video-player.component.scss
(1 hunks)src/app/portals/web-player-view/web-player-view.component.html
(1 hunks)src/app/portals/web-player-view/web-player-view.component.ts
(2 hunks)src/app/services/whats-new.service.ts
(2 hunks)src/app/settings/settings.component.html
(1 hunks)src/app/shared/components/header/header.component.html
(1 hunks)src/app/shared/components/header/header.component.scss
(1 hunks)src/app/shared/components/header/header.component.ts
(1 hunks)src/app/shared/components/mpv-player-bar/mpv-player-bar.component.html
(1 hunks)src/app/stalker/recently-viewed/recently-viewed.component.ts
(1 hunks)src/app/stalker/stalker-favorites/stalker-favorites.component.ts
(1 hunks)src/app/stalker/stalker-search/stalker-search.component.ts
(2 hunks)src/app/stalker/stalker.store.ts
(6 hunks)src/app/xtream-tauri/account-info/account-info.component.ts
(2 hunks)src/app/xtream-tauri/category-content-view/category-content-view.component.ts
(1 hunks)src/app/xtream-tauri/loading-overlay/loading-overlay.component.ts
(1 hunks)src/app/xtream-tauri/player-dialog/player-dialog.component.scss
(2 hunks)src/app/xtream/navigation-bar/navigation-bar.component.html
(1 hunks)src/app/xtream/navigation-bar/navigation-bar.component.ts
(2 hunks)src/app/xtream/season-container/season-container.component.html
(1 hunks)src/app/xtream/season-container/season-container.component.ts
(2 hunks)src/app/xtream/serial-details/serial-details.component.html
(1 hunks)src/app/xtream/serial-details/serial-details.component.ts
(2 hunks)src/app/xtream/vod-details/vod-details.component.html
(2 hunks)src/app/xtream/vod-details/vod-details.component.ts
(2 hunks)src/app/xtream/xtream-main-container.component.ts
(3 hunks)src/main.ts
(1 hunks)tsconfig.json
(1 hunks)
🧰 Additional context used
🧬 Code graph analysis (3)
src/main.ts (4)
src/environments/environment.dev.ts (1)
AppConfig
(6-11)src/environments/environment.prod.ts (1)
AppConfig
(1-6)src/environments/environment.web.ts (1)
AppConfig
(1-6)src/environments/environment.ts (1)
AppConfig
(1-6)
src/app/stalker/stalker-search/stalker-search.component.ts (1)
src/app/stalker/stalker-content-types.ts (1)
StalkerContentTypes
(3-32)
src/app/stalker/stalker.store.ts (2)
shared/ipc-commands.ts (1)
STALKER_REQUEST
(55-55)src/app/stalker/stalker-content-types.ts (1)
StalkerContentTypes
(3-32)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
- GitHub Check: Codacy Static Code Analysis
- GitHub Check: e2e-tests (macos-latest)
🔇 Additional comments (29)
src/app/xtream-tauri/player-dialog/player-dialog.component.scss (1)
22-22
: EOF newline/hygiene looks goodNo functional change; keeping a trailing newline is a good practice.
src/app/stalker/stalker.store.ts (1)
83-148
: API shape looks correct for Angular 20 resources (params/loader).The migration from request→params and loader({params}) aligns with current Angular docs. No issues on the new API surface. (angular.dev)
Also applies to: 149-156
src/app/stalker/stalker-search/stalker-search.component.ts (1)
91-98
: API migration to params/loader is correct for Angular 20 resources.The new shape matches Angular’s resource API. (angular.dev)
src/app/stalker/recently-viewed/recently-viewed.component.ts (1)
47-51
: Good migration to rxResource params/stream.This matches the current rxResource API; using params to trigger refresh via refreshTimestamp is idiomatic.
src/app/stalker/stalker-favorites/stalker-favorites.component.ts (1)
47-51
: Good migration to rxResource params/stream.API usage looks correct; refreshTimestamp drives re-subscription.
src/app/shared/components/header/header.component.scss (1)
60-62
: LGTM!The styling change from translucent black to solid dark gray for the active sort state aligns with Angular 20's modern control flow syntax migration. The updated color ensures better visual contrast and opaque appearance in the new UI.
tsconfig.json (1)
9-9
: Appropriate upgrade to bundler module resolution.The change from "node" to "bundler" module resolution aligns with the broader TypeScript 5.9 upgrade and improves compatibility with modern build tools. This configuration supports Angular 20's build optimizations.
src/app/player/components/d-player/d-player.component.ts (1)
15-16
: LGTM!The removal of CommonModule import and setting imports to an empty array is consistent with the Angular 20 migration pattern observed across multiple components. Since this component uses an inline template without structural directives, no additional changes are needed.
src/app/player/components/art-player/art-player.component.ts (1)
15-17
: LGTM!The removal of CommonModule import aligns with the new control flow syntax that reduces runtime footprint and improves bundle size. This component's inline template doesn't use structural directives, so the change is safe.
src/app/home/home.component.html (1)
7-9
: LGTM! Clean migration to new control flow syntax.The migration from
*ngIf="isLoading"
to@if (isLoading)
follows Angular 20's new control flow syntax. This provides more ergonomic and intuitive syntax that's closer to JavaScript.src/app/xtream/season-container/season-container.component.ts (1)
1-10
: Confirmed: template uses Angular 20 control-flow (@if/@for)
src/app/xtream/season-container/season-container.component.html contains @if/@for (lines 3, 4, 16, 21) and no *ngIf/*ngFor.src/app/xtream/navigation-bar/navigation-bar.component.ts (1)
46-58
: Confirmed: template updated to Angular 20 control-flow (@if/@for). src/app/xtream/navigation-bar/navigation-bar.component.html uses @if/@for (lines 7,15,49,54,64,76,88 from rg output); no *ngIf/*ngFor were found.src/main.ts (1)
2-2
: Good move: platformBrowser over platformBrowserDynamic.platformBrowserDynamic is deprecated in favor of platformBrowser; this aligns with Angular’s guidance. If you still rely on JIT in non-CLI contexts, ensure @angular/compiler is present (unlikely here). (angular.dev)
src/app/xtream-tauri/account-info/account-info.component.ts (1)
140-140
: styleUrl usage is fine on Angular 17+.Using styleUrl (singular) is supported since Angular 17; keep as-is. (blog.ninja-squad.com)
Confirm the project is actually building against Angular ≥17 (this PR targets v20), otherwise switch to styleUrls.
src/app/player/components/epg-list/epg-item-description/epg-item-description.component.html (1)
6-10
: Confirm the type of epgProgram.title; likely need the first item’s value.If
title
is an array, rendering{{ epgProgram.title }}
will print[object Object]
. Considertitle[0]?.value
(or model’s actual field).[suggest_minor_issue]
src/app/player/components/epg-list/epg-item-description/epg-item-description.component.ts (1)
12-13
: Imports tidy-up looks good.Removal of NgIf import aligns with new control flow syntax.
angular.json (1)
21-43
: Config LGTM — serviceWorker set to "ngsw-config.json"; local build required to verify.jq returned "ngsw-config.json"; sandbox build failed with "sh: 1: ng: not found". Run locally (npm run build:prod or ng build -c production) and confirm the build succeeds and the SW config is picked up.
package.json (1)
86-104
: Verify builder/test toolchain compatibility with Angular 20
- Findings — @angular-builders/jest v17.0.0 confirmed;
npm ci
emitted no peerDependency warnings but showed multiple deprecation notices and 23 vulnerabilities;jq
failed to enumerate @angular/* from package.json during verification.- Action — Inspect package.json (lines 86–104) to confirm TypeScript (expect TS 5.9) and ESLint plugin versions, run the full test suite/CI, and if tests or peer warnings appear, upgrade or pin @angular-builders/jest / ESLint / TS to releases known to be compatible with Angular 20.
src/app/shared/components/header/header.component.ts (1)
1-1
: LGTM: dropping NgIf import aligns with @if blocks.No issues spotted; AsyncPipe remains available.
src/app/portals/web-player-view/web-player-view.component.ts (1)
25-29
: LGTM: imports narrowed to player components.Matches template’s @if branches.
src/app/xtream/serial-details/serial-details.component.ts (1)
21-25
: LGTM: removed NgIf from imports.Template should now rely on @if blocks.
src/app/app.component.html (1)
3-9
: *LGTM: migrated ngIf to @if for ngx-whats-new.Bindings unchanged; looks correct.
src/app/xtream/vod-details/vod-details.component.ts (2)
24-29
: NgIf removal in standalone imports looks correctTemplate has migrated to the new control-flow; dropping NgIf from imports is appropriate.
80-83
: ID type for removeFromFavorites
removeFromFavoritesClicked
isEventEmitter<number>
, but fallback(this.item as any)?.id
could be non-numeric in stalker mode. Please verify types end-to-end.src/app/xtream/xtream-main-container.component.ts (3)
111-111
: Trailing comma in importsNo behavioral change; fine.
360-364
: Dialog bounds for VOD playerAdding
maxWidth
/maxHeight
is a good UX guard on large screens.
390-392
: Dialog bounds for episode playerSame here—sensible limits.
src/app/home/recent-playlists/recent-playlists.component.ts (2)
2-2
: CommonModule structural directives removal is fineTemplate migrated to new control-flow; keeping only AsyncPipe here is correct.
67-70
: Readonly BehaviorSubject + skeleton source look goodMarking
searchQuery
readonly prevents accidental reassignment;ghostElements
enables cheap placeholder loops.
"@angular-builders/jest": "17.0.0", | ||
"@angular/animations": "19.2.0", | ||
"@angular/cdk": "20.2.4", | ||
"@angular/material": "20.2.4", | ||
"@ngrx/component-store": "19.0.1", | ||
"@ngrx/effects": "19.0.1", | ||
"@ngrx/entity": "19.0.1", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Angular version skew: @angular/animations is still v19.
This will violate peer deps with v20 core/material.
- "@angular/animations": "19.2.0",
+ "@angular/animations": "20.3.1",
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
"@angular-builders/jest": "17.0.0", | |
"@angular/animations": "19.2.0", | |
"@angular/cdk": "20.2.4", | |
"@angular/material": "20.2.4", | |
"@ngrx/component-store": "19.0.1", | |
"@ngrx/effects": "19.0.1", | |
"@ngrx/entity": "19.0.1", | |
"@angular-builders/jest": "17.0.0", | |
"@angular/animations": "20.3.1", | |
"@angular/cdk": "20.2.4", | |
"@angular/material": "20.2.4", | |
"@ngrx/component-store": "19.0.1", | |
"@ngrx/effects": "19.0.1", | |
"@ngrx/entity": "19.0.1", |
🤖 Prompt for AI Agents
In package.json around lines 38 to 44, @angular/animations is pinned to v19
while @angular/cdk and @angular/material are v20, causing Angular version skew
and peer dependency conflicts; update @angular/animations to the matching v20.x
(e.g., 20.2.4) so all Angular packages share the same major/minor version, then
run npm/yarn install (and update the lockfile) and run the test/build to verify
no further peer dep issues.
"tslib": "2.3.0", | ||
"typescript": "5.9.2", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
tslib too old for TS 5.9 / Angular 20.
Pin to >=2.6.2 to avoid build/runtime issues.
- "tslib": "2.3.0",
+ "tslib": "^2.6.2",
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
"tslib": "2.3.0", | |
"typescript": "5.9.2", | |
"tslib": "^2.6.2", | |
"typescript": "5.9.2", |
🤖 Prompt for AI Agents
In package.json around lines 141-142, the tslib version is pinned too low for
TypeScript 5.9/Angular 20; update the tslib dependency spec to require >=2.6.2
(e.g. change the value to ">=2.6.2" or a compatible semver like "^2.6.2"), save
package.json, run your package manager to update node_modules and regenerate the
lockfile (npm install or yarn install), then run the build/tests to ensure there
are no runtime/build issues.
[matTooltip]="'EPG.NEXT_DAY' | translate" | ||
mat-icon-button | ||
(click)="switchDay('prev')" | ||
> | ||
<mat-icon>navigate_before</mat-icon> | ||
</button> | ||
<button | ||
[matTooltip]="'EPG.PREVIOUS_DAY' | translate" | ||
mat-icon-button | ||
(click)="switchDay('next')" | ||
<mat-icon>navigate_before</mat-icon> | ||
</button> | ||
<button | ||
[matTooltip]="'EPG.PREVIOUS_DAY' | translate" | ||
mat-icon-button | ||
(click)="switchDay('next')" | ||
> | ||
<mat-icon>navigate_next</mat-icon> | ||
</button> | ||
<div class="today-date"> | ||
{{ today | momentDate: 'YYYYMMDD' : 'MMMM Do, dddd' }} | ||
</div> | ||
<button mat-icon-button (click)="zoomIn()" [disabled]="(hourWidth$ | async) >= 800"> | ||
<mat-icon>zoom_in</mat-icon> | ||
<mat-icon>navigate_next</mat-icon> | ||
</button> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Prev/Next day controls are reversed
Tooltip labels don’t match the click handlers. Swap tooltips to align with actions.
Apply this diff:
- <button
- [matTooltip]="'EPG.NEXT_DAY' | translate"
+ <button
+ [matTooltip]="'EPG.PREVIOUS_DAY' | translate"
mat-icon-button
(click)="switchDay('prev')"
>
<mat-icon>navigate_before</mat-icon>
</button>
<button
- [matTooltip]="'EPG.PREVIOUS_DAY' | translate"
+ [matTooltip]="'EPG.NEXT_DAY' | translate"
mat-icon-button
(click)="switchDay('next')"
>
<mat-icon>navigate_next</mat-icon>
</button>
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
[matTooltip]="'EPG.NEXT_DAY' | translate" | |
mat-icon-button | |
(click)="switchDay('prev')" | |
> | |
<mat-icon>navigate_before</mat-icon> | |
</button> | |
<button | |
[matTooltip]="'EPG.PREVIOUS_DAY' | translate" | |
mat-icon-button | |
(click)="switchDay('next')" | |
<mat-icon>navigate_before</mat-icon> | |
</button> | |
<button | |
[matTooltip]="'EPG.PREVIOUS_DAY' | translate" | |
mat-icon-button | |
(click)="switchDay('next')" | |
> | |
<mat-icon>navigate_next</mat-icon> | |
</button> | |
<div class="today-date"> | |
{{ today | momentDate: 'YYYYMMDD' : 'MMMM Do, dddd' }} | |
</div> | |
<button mat-icon-button (click)="zoomIn()" [disabled]="(hourWidth$ | async) >= 800"> | |
<mat-icon>zoom_in</mat-icon> | |
<mat-icon>navigate_next</mat-icon> | |
</button> | |
<button | |
[matTooltip]="'EPG.PREVIOUS_DAY' | translate" | |
mat-icon-button | |
(click)="switchDay('prev')" | |
> | |
<mat-icon>navigate_before</mat-icon> | |
</button> | |
<button | |
[matTooltip]="'EPG.NEXT_DAY' | translate" | |
mat-icon-button | |
(click)="switchDay('next')" | |
> | |
<mat-icon>navigate_next</mat-icon> | |
</button> |
🤖 Prompt for AI Agents
In src/app/player/components/multi-epg/multi-epg-container.component.html around
lines 11 to 23, the tooltip labels are reversed relative to the click handlers;
change the first button's [matTooltip] to "'EPG.PREVIOUS_DAY' | translate" and
the second button's [matTooltip] to "'EPG.NEXT_DAY' | translate" so the tooltips
match the (click)="switchDay('prev')" and (click)="switchDay('next')" actions
respectively.
trackByProgram(_: number, program: EnrichedProgram): string { | ||
return program.start + program.title; | ||
return program.start + program.title.toString(); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Possible runtime error: .toString() on undefined/null.
program.title may be absent; .toString() would throw.
- trackByProgram(_: number, program: EnrichedProgram): string {
- return program.start + program.title.toString();
- }
+ trackByProgram(_: number, program: EnrichedProgram): string {
+ return `${program.start}|${program.title ?? ''}`;
+ }
If a stable id exists (e.g., program.id), prefer that.
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
trackByProgram(_: number, program: EnrichedProgram): string { | |
return program.start + program.title; | |
return program.start + program.title.toString(); | |
} | |
trackByProgram(_: number, program: EnrichedProgram): string { | |
return `${program.start}|${program.title ?? ''}`; | |
} |
🤖 Prompt for AI Agents
In src/app/player/components/multi-epg/multi-epg-container.component.ts around
lines 153 to 155, trackByProgram currently does program.start +
program.title.toString() which can throw if program.title is null/undefined;
update it to return a stable string id: prefer a stable identifier like
program.id (return String(program.id) when present), otherwise safely construct
a string using program.start plus a guarded title (e.g., use program.title ?? ''
or String(program.title)) so the function never calls toString on undefined and
always returns a string.
params: () => ({ | ||
contentType: store.selectedContentType(), | ||
category: store.selectedCategoryId(), | ||
action: StalkerPortalActions.GetOrderedList, | ||
search: store.searchPhrase(), | ||
pageIndex: store.page() + 1, | ||
}), | ||
loader: async ({ request }) => { | ||
loader: async ({ params }) => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Missing reactivity/guard for currentPlaylist in getContentResource.
The loader reads currentPlaylist() but it's not part of params; it won’t rerun when playlist changes and may crash when undefined. Include playlist in params and use it in the loader.
- params: () => ({
- contentType: store.selectedContentType(),
- category: store.selectedCategoryId(),
- action: StalkerPortalActions.GetOrderedList,
- search: store.searchPhrase(),
- pageIndex: store.page() + 1,
- }),
+ params: () => {
+ const playlist = store.currentPlaylist();
+ const category = store.selectedCategoryId();
+ if (!playlist || !category) return undefined;
+ return {
+ contentType: store.selectedContentType(),
+ category,
+ action: StalkerPortalActions.GetOrderedList,
+ search: store.searchPhrase(),
+ pageIndex: store.page() + 1,
+ currentPlaylist: playlist,
+ };
+ },
@@
- url: currentPlaylist().portalUrl,
- macAddress: currentPlaylist().macAddress,
+ url: params.currentPlaylist.portalUrl,
+ macAddress: params.currentPlaylist.macAddress,
Also applies to: 180-183
🤖 Prompt for AI Agents
In src/app/stalker/stalker.store.ts around lines 149-156 (and similarly
180-183), the loader reads currentPlaylist() but that value is not included in
the params so the loader won’t rerun when the playlist changes and may access
undefined; add currentPlaylist (or currentPlaylistId) to the params function so
the loader is reactive to playlist changes, and update the loader to guard
against undefined (e.g., return early or throw a controlled error if
params.playlist is missing) before using the playlist value.
params: () => ({ | ||
itemId: store.selectedSerialId(), | ||
}), | ||
loader: async ({ request }) => { | ||
loader: async ({ params }) => { | ||
const { portalUrl, macAddress } = store.currentPlaylist(); | ||
const params = { | ||
const queryParams = { | ||
action: StalkerContentTypes.series.getContentAction, | ||
type: 'series', | ||
movie_id: request.itemId, | ||
movie_id: params.itemId, | ||
}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
serialSeasonsResource: add playlist to params and guard response.
Ensures reactivity to playlist changes and avoids NPEs.
- params: () => ({
- itemId: store.selectedSerialId(),
- }),
- loader: async ({ params }) => {
- const { portalUrl, macAddress } = store.currentPlaylist();
+ params: () => {
+ const playlist = store.currentPlaylist();
+ const itemId = store.selectedSerialId();
+ if (!playlist || !itemId) return undefined;
+ return { itemId, currentPlaylist: playlist };
+ },
+ loader: async ({ params }) => {
+ const { portalUrl, macAddress } = params.currentPlaylist;
const queryParams = {
action: StalkerContentTypes.series.getContentAction,
type: 'series',
- movie_id: params.itemId,
+ movie_id: params.itemId,
};
const response = await dataService.sendIpcEvent(
STALKER_REQUEST,
{
url: portalUrl,
macAddress,
params: queryParams,
}
);
- return sortByNumericValue(response.js.data);
+ if (!response) throw new Error('STALKER_REQUEST returned no response for seasons');
+ return sortByNumericValue(response.js?.data ?? []);
Also applies to: 237-243
🤖 Prompt for AI Agents
In src/app/stalker/stalker.store.ts around lines 224-233 (and also apply same
fix to 237-243), the serialSeasonsResource params only include itemId and the
loader assumes currentPlaylist exists; update params to include the whole
currentPlaylist (or at least portalUrl and macAddress) so the resource reacts to
playlist changes, and in the loader guard the response by checking
currentPlaylist is defined before using its properties and validating the
fetched response (e.g., ensure data exists and has expected shape) before
returning — if missing, return a safe default (empty array/object) or throw a
handled error to avoid NPEs.
@Component({ | ||
selector: 'app-account-info', | ||
imports: [ | ||
CommonModule, | ||
MatButton, | ||
MatDialogModule, | ||
MatCardModule, | ||
MatListModule, | ||
TranslateModule, | ||
], | ||
MatButton, | ||
MatDialogModule, | ||
MatCardModule, | ||
MatListModule, | ||
TranslateModule | ||
], |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Add standalone: true; required when using imports on components.
Like other updated components, this one uses imports but lacks standalone: true. Add it to avoid compilation errors and to make MatButton/MatDialogModule/etc. available. (v18.angular.dev)
@Component({
selector: 'app-account-info',
+ standalone: true,
imports: [
MatButton,
MatDialogModule,
MatCardModule,
MatListModule,
TranslateModule
],
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
@Component({ | |
selector: 'app-account-info', | |
imports: [ | |
CommonModule, | |
MatButton, | |
MatDialogModule, | |
MatCardModule, | |
MatListModule, | |
TranslateModule, | |
], | |
MatButton, | |
MatDialogModule, | |
MatCardModule, | |
MatListModule, | |
TranslateModule | |
], | |
@Component({ | |
selector: 'app-account-info', | |
standalone: true, | |
imports: [ | |
MatButton, | |
MatDialogModule, | |
MatCardModule, | |
MatListModule, | |
TranslateModule | |
], |
🤖 Prompt for AI Agents
In src/app/xtream-tauri/account-info/account-info.component.ts around lines 13
to 21, the @Component decorator uses the imports array but is missing
standalone: true which is required when declaring component-level imports;
update the @Component metadata to include standalone: true alongside the
existing imports so the MatButton, MatDialogModule, MatCardModule, MatListModule
and TranslateModule are available and the component compiles as a standalone
component.
@Component({ | ||
selector: 'app-loading-overlay', | ||
imports: [MatProgressBarModule, CommonModule], | ||
imports: [MatProgressBarModule], | ||
template: ` |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Mark component as standalone to use imports.
Same pattern: imports is present but standalone is missing. Add standalone: true. (v18.angular.dev)
@Component({
selector: 'app-loading-overlay',
- imports: [MatProgressBarModule],
+ standalone: true,
+ imports: [MatProgressBarModule],
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
@Component({ | |
selector: 'app-loading-overlay', | |
imports: [MatProgressBarModule, CommonModule], | |
imports: [MatProgressBarModule], | |
template: ` | |
@Component({ | |
selector: 'app-loading-overlay', | |
standalone: true, | |
imports: [MatProgressBarModule], | |
template: ` |
🤖 Prompt for AI Agents
In src/app/xtream-tauri/loading-overlay/loading-overlay.component.ts around
lines 5 to 8, the @Component decorator includes an imports array but is missing
standalone: true; update the component metadata to add standalone: true (keeping
the existing imports: [MatProgressBarModule]) so the component can use the
provided imports as a standalone component.
<div class="image"> | ||
@if (item.info.cover) { | ||
<img [src]="item.info.cover" /> | ||
} | ||
</div> | ||
<div class="details"> | ||
<h2>{{ item.info.name }}</h2> | ||
@if (item.info.plot) { | ||
<label> | ||
{{ item.info.plot }} | ||
</label> | ||
} | ||
@if (item.info.releaseDate) { | ||
<label> | ||
<div class="label">{{ 'XTREAM.RELEASE_DATE' | translate }}:</div> | ||
{{ item.info.releaseDate }} | ||
</label> | ||
} | ||
@if (item.info.genre) { | ||
<label> | ||
<div class="label">{{ 'XTREAM.GENRE' | translate }}:</div> | ||
{{ item.info.genre }} | ||
</label> | ||
} | ||
@if (item.info.rating) { | ||
<label> | ||
<div class="label">{{ 'XTREAM.RATING' | translate }}:</div> | ||
{{ item.info.rating }} | ||
</label> | ||
} | ||
@if (item.info.cast) { | ||
<div> | ||
<div class="label">{{ 'XTREAM.CAST' | translate }}:</div> | ||
{{ item.info.cast }} | ||
</div> | ||
} | ||
@if (item.info.director) { | ||
<label> | ||
<div class="label">{{ 'XTREAM.DIRECTOR' | translate }}:</div> | ||
{{ item.info.director }} | ||
</label> | ||
} | ||
@if (item.info.episode_run_time) { | ||
<label> | ||
<div class="label"> | ||
{{ 'XTREAM.EPISODE_RUN_TIME' | translate }}: | ||
</div> | ||
<label *ngIf="item.info.director"> | ||
<div class="label">{{ 'XTREAM.DIRECTOR' | translate }}:</div> | ||
{{ item.info.director }} | ||
</label> | ||
<label *ngIf="item.info.episode_run_time"> | ||
<div class="label"> | ||
{{ 'XTREAM.EPISODE_RUN_TIME' | translate }}: | ||
</div> | ||
{{ item.info.episode_run_time }} | ||
</label> | ||
<div class="action-buttons"> | ||
<button | ||
*ngIf="!isFavorite; else removeFromFavoritesButton" | ||
mat-stroked-button | ||
color="accent" | ||
(click)="toggleFavorite()" | ||
> | ||
<mat-icon>star_outline</mat-icon> | ||
{{ 'PORTALS.ADD_TO_FAVORITES' | translate }} | ||
</button> | ||
{{ item.info.episode_run_time }} | ||
</label> | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Guard against undefined item/info before dereferencing.
Multiple expressions use item.info.*
without a parent guard. Wrap the image/details/actions in a single @if (item?.info)
(or as info
) block to prevent runtime errors when item
or item.info
is null.
Apply:
- <div class="image">
- @if (item.info.cover) {
+ @if (item?.info) {
+ <div class="image">
+ @if (item.info.cover) {
<img [src]="item.info.cover" />
}
</div>
<div class="details">
<h2>{{ item.info.name }}</h2>
...
- </div>
- </div>
+ </div>
+ </div>
+ }
Also applies to: 82-84
🤖 Prompt for AI Agents
In src/app/xtream/serial-details/serial-details.component.html around lines 11
to 60 (and also apply the same fix at lines 82-84), the template dereferences
item.info properties directly which can throw if item or item.info is undefined;
wrap the sections that access item.info (the image, details and related actions)
in a single parent guard such as @if (item?.info) { ... } or use @if (item?.info
as info) { } and then replace inner references with info.* so all accesses are
safe and the runtime errors are prevented.
loading-overlay, navigation-bar, season-container, serial-details, and
vod-details components.
Summary by CodeRabbit