Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 9 additions & 5 deletions apps/signage-manager/USER_STORIES.md
Original file line number Diff line number Diff line change
Expand Up @@ -134,11 +134,12 @@ These stories cover the current app workflows:

- The playlists page shows a searchable playlist list.
- Playlist rows show thumbnail previews when available.
- Playlist rows show disabled, expired, pending, awaiting review, and approval-required states.
- Playlist rows show disabled, expired, pending, awaiting review, and approval-required states. A playlist is expired when its end date has passed or all its schedules have ended. Playlist lists on the media, zone, and display pages show the same states.
- Additional playlists load as the user scrolls.
- Users with create permission can create a new playlist.
- Users with create permission can duplicate a playlist. The copy has the same settings, items, and item schedules. It is not approved and is not assigned to displays or zones.
- Selecting a playlist opens its items and details.
- A link to a playlist opens it, also when the loaded list does not include it. When the playlist cannot load, a warning shows and no playlist is selected.

---

Expand All @@ -150,14 +151,15 @@ These stories cover the current app workflows:

**Acceptance Criteria:**

- Users can view media items in the selected playlist.
- Users can view media items in the selected playlist. When the items cannot load, the list shows an error with a retry action.
- Non-distribution playlists show the item count and the time to play each item once. The time uses the same fallbacks as the player: item play time, video length, playlist default, then 15 seconds.
- Users can preview a playlist item.
- Users with update permission can reorder playlist items by drag and drop.
- Users with update permission can reorder playlist items by drag and drop, or with the move up and move down actions in the item menu.
- Distribution playlists cannot be reordered from the item list.
- Users with update permission can remove media from the playlist.
- Adding media to a distribution playlist always asks for the item schedule first. When adding media fails, an error shows.
- Users can expand, collapse, and edit item schedules.
- Keyboard selection is supported for playlist items.
- Keyboard selection is supported for playlist items. Enter and Space on the checkbox or the actions button of a row operate that control.

---

Expand All @@ -170,7 +172,7 @@ These stories cover the current app workflows:
**Acceptance Criteria:**

- Users can view playlist item count, enabled state, description, validity dates, animation, schedules, and next play sessions.
- Users with update permission can edit playlist details.
- Users with update permission can edit playlist details. Clearing a validity date removes it.
- Users with update permission can add or remove display assignments.
- Users with update permission can add or remove zone assignments.
- Users with share permission can share playlists.
Expand All @@ -192,6 +194,8 @@ These stories cover the current app workflows:
- Users without approval permission can request approval for a selected playlist.
- Approval request actions show a loading state while submitting.
- Approval preview shows changed media versions and allows media preview.
- Approval applies only to the version that the reviewer saw. When the playlist changed after the preview loaded, the preview shows the new version with a warning, and the playlist is not approved.
- A playlist that is awaiting review keeps that state when the user selects it.

---

Expand Down
31 changes: 18 additions & 13 deletions apps/signage-manager/src/app/displays/display-content.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,10 @@ import { SignagePlaylistService } from '../playlists/signage-playlist.service';
import { PlaylistThumbnailComponent } from '../shared/playlist-thumbnail.component';
import { TemplateMappingsComponent } from '../shared/template-mappings.component';
import { SignageContextService } from '../signage-context.service';
import { playlistStatus } from '../signage-playlist.util';
import { DisplayScheduleComponent } from './display-schedule.component';
import { SignageDisplayService } from './signage-display.service';

type PlaylistStatus = 'expired' | 'pending' | 'awaiting_approval' | null;

@Component({
selector: 'display-content',
template: `
Expand Down Expand Up @@ -166,12 +165,22 @@ type PlaylistStatus = 'expired' | 'pending' | 'awaiting_approval' | null;
}}
</span>
}
@case ('awaiting_review') {
<span
class="bg-warning text-warning-content shrink-0 rounded px-1.5 py-0.5 text-[10px] font-bold uppercase"
>
{{
'SIGNAGE_MANAGER.STATUS_AWAITING_REVIEW'
| translate
}}
</span>
}
@case ('awaiting_approval') {
<span
class="bg-secondary text-secondary-content shrink-0 rounded px-1.5 py-0.5 text-[10px] font-bold uppercase"
>
{{
'SIGNAGE_MANAGER.STATUS_AWAITING_APPROVAL'
'COMMON.APPROVAL_REQUIRED'
| translate
}}
</span>
Expand Down Expand Up @@ -441,15 +450,11 @@ export class DisplayContentComponent {
);
}

public getStatus(playlist: SignagePlaylist): PlaylistStatus {
const now_s = Math.floor(Date.now() / 1000);
if (playlist.valid_until && playlist.valid_until < now_s)
return 'expired';
if (playlist.valid_from && playlist.valid_from > now_s)
return 'pending';
const approvals = this.playlist_approval_status();
if (playlist.id in approvals && !approvals[playlist.id])
return 'awaiting_approval';
return null;
public getStatus(playlist: SignagePlaylist) {
return playlistStatus(
playlist,
this.playlist_approval_status(),
this._playlist_service.playlist_approval_requested_status(),
);
}
}
31 changes: 7 additions & 24 deletions apps/signage-manager/src/app/media/playlist-sidebar.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,7 @@ import { SignagePlaylistService } from '../playlists/signage-playlist.service';
import { IntersectDirective } from '../shared/intersect.directive';
import { PlaylistThumbnailComponent } from '../shared/playlist-thumbnail.component';
import { SignageContextService } from '../signage-context.service';

type PlaylistStatus =
| 'expired'
| 'pending'
| 'awaiting_approval'
| 'awaiting_review'
| null;
import { playlistStatus } from '../signage-playlist.util';

@Component({
selector: 'playlist-sidebar',
Expand Down Expand Up @@ -269,22 +263,11 @@ export class PlaylistSidebarComponent {
);
}

public getStatus(playlist: SignagePlaylist): PlaylistStatus {
const now_s = Math.floor(Date.now() / 1000);
if (playlist.valid_until && playlist.valid_until < now_s)
return 'expired';
if (playlist.valid_from && playlist.valid_from > now_s)
return 'pending';
const approvals = this.playlist_approval_status();
const approval_requests = this.playlist_approval_requested_status();
if (
playlist.id in approvals &&
!approvals[playlist.id] &&
approval_requests[playlist.id]
)
return 'awaiting_review';
if (playlist.id in approvals && !approvals[playlist.id])
return 'awaiting_approval';
return null;
public getStatus(playlist: SignagePlaylist) {
return playlistStatus(
playlist,
this.playlist_approval_status(),
this.playlist_approval_requested_status(),
);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,19 @@ import {
MediaDurationPipe,
TranslatePipe,
} from '@placeos/components';
import { MediaAnimation, SignagePlaylist } from '@placeos/ts-client';
import {
MediaAnimation,
PlaceSystem,
PlaceZone,
SignagePlaylist,
} from '@placeos/ts-client';
import { SignageDisplayService } from '../displays/signage-display.service';
import { playlistSchedules } from '../schedules/signage-schedule.util';
import { SignageSharedWithComponent } from '../shared/signage-shared-with.component';
import { SignageContextService } from '../signage-context.service';
import { SignageInventoryService } from '../signage-inventory.service';
import {
playlistAnimation,
playlistNextPlayLabels,
playlistScheduleExpiryTooltip,
playlistScheduleLabel,
Expand Down Expand Up @@ -643,7 +649,12 @@ export class PlaylistItemDetailsComponent {
loader: () => this._inventory_service.loadSignageInventory(),
});

public readonly item_count = computed(() => this._items().length);
/** Number of items. A dash while the items cannot load. */
public readonly item_count = computed(() =>
this._playlist_service.playlist_media_error()
? '—'
: this._items().length,
);
public readonly can_update = this._context.can_update;
public readonly selected_group_id = computed(
() => this._context.selected_group()?.group.id || '',
Expand All @@ -670,7 +681,7 @@ export class PlaylistItemDetailsComponent {
public readonly animation_label = computed(() => {
const pl = this.playlist();
if (!pl) return 'COMMON.DEFAULT';
switch (pl.default_animation) {
switch (playlistAnimation(pl)) {
case MediaAnimation.Cut:
return 'SIGNAGE_MANAGER.ANIM_CUT';
case MediaAnimation.CrossFade:
Expand Down Expand Up @@ -732,15 +743,15 @@ export class PlaylistItemDetailsComponent {
if (playlist) this._zone_service.addZoneToPlaylist(playlist);
}

public removeDisplay(event: Event, display: any) {
public removeDisplay(event: Event, display: PlaceSystem) {
event.preventDefault();
event.stopPropagation();
const playlist = this.playlist();
if (playlist)
this._display_service.removeDisplayFromPlaylist(playlist, display);
}

public removeZone(event: Event, zone: any) {
public removeZone(event: Event, zone: PlaceZone) {
event.preventDefault();
event.stopPropagation();
const playlist = this.playlist();
Expand Down
Loading
Loading