MOBILE-4322 bbb: Display all playbacks in recordings

main
Dani Palou 2023-05-02 16:27:47 +02:00
parent d9e2788f44
commit e49fa18261
7 changed files with 104 additions and 45 deletions

View File

@ -462,8 +462,6 @@
"addon.mod_bigbluebuttonbn.view_message_viewer": "bigbluebuttonbn", "addon.mod_bigbluebuttonbn.view_message_viewer": "bigbluebuttonbn",
"addon.mod_bigbluebuttonbn.view_message_viewers": "bigbluebuttonbn", "addon.mod_bigbluebuttonbn.view_message_viewers": "bigbluebuttonbn",
"addon.mod_bigbluebuttonbn.view_nojoin": "bigbluebuttonbn", "addon.mod_bigbluebuttonbn.view_nojoin": "bigbluebuttonbn",
"addon.mod_bigbluebuttonbn.view_recording_format_presentation": "bigbluebuttonbn",
"addon.mod_bigbluebuttonbn.view_recording_list_action_play": "bigbluebuttonbn",
"addon.mod_bigbluebuttonbn.view_section_title_recordings": "bigbluebuttonbn", "addon.mod_bigbluebuttonbn.view_section_title_recordings": "bigbluebuttonbn",
"addon.mod_book.errorchapter": "book", "addon.mod_book.errorchapter": "book",
"addon.mod_book.modulenameplural": "book", "addon.mod_book.modulenameplural": "book",

View File

@ -90,9 +90,8 @@
</ion-label> </ion-label>
</ion-item> </ion-item>
<ng-container *ngFor="let recording of recordings"> <ng-container *ngFor="let recording of recordings">
<ion-item *ngIf="recording.url" button class="addon-mod_bbb-recording-title" [attr.aria-expanded]="recording.expanded" <ion-item button class="addon-mod_bbb-recording-title" [attr.aria-expanded]="recording.expanded" (click)="toggle(recording)"
(click)="toggle(recording)" [attr.aria-label]="(recording.expanded ? 'core.collapse' : 'core.expand') | translate" [attr.aria-label]="(recording.expanded ? 'core.collapse' : 'core.expand') | translate" detail="false">
detail="false">
<ion-icon name="fas-chevron-right" flip-rtl slot="start" aria-hidden="true" class="expandable-status-icon" <ion-icon name="fas-chevron-right" flip-rtl slot="start" aria-hidden="true" class="expandable-status-icon"
[class.expandable-status-icon-expanded]="recording.expanded"> [class.expandable-status-icon-expanded]="recording.expanded">
</ion-icon> </ion-icon>
@ -100,12 +99,22 @@
<p>{{ recording.type }}</p> <p>{{ recording.type }}</p>
<p>{{ recording.name }}</p> <p>{{ recording.name }}</p>
</ion-label> </ion-label>
<ion-button slot="end" fill="clear" (click)="playRecording($event, recording)"
[attr.aria-label]="'addon.mod_bigbluebuttonbn.view_recording_list_action_play' | translate">
<ion-icon name="fas-circle-play" slot="icon-only" aria-hidden="true"></ion-icon>
</ion-button>
</ion-item> </ion-item>
<div [hidden]="!recording.expanded" class="addon-mod_bbb-recording-details"> <div [hidden]="!recording.expanded" class="addon-mod_bbb-recording-details">
<div *ngIf="recording.playbacks.length" class="addon-mod_bbb-recording-playbacks">
<ion-item class="ion-text-wrap addon-mod_bbb-recording-playback-title">
<ion-label>
<p class="item-heading">{{ recording.playbackLabel }}</p>
</ion-label>
</ion-item>
<ion-item *ngFor="let playback of recording.playbacks" button (click)="openPlayback($event, playback)"
class="ion-text-wrap addon-mod_bbb-recording-playback-item">
<ion-label>
<p>{{ playback.name }}</p>
</ion-label>
<ion-icon slot="end" [name]="playback.icon" aria-hidden="true"></ion-icon>
</ion-item>
</div>
<ion-item *ngFor="let data of recording.details" class="ion-text-wrap"> <ion-item *ngFor="let data of recording.details" class="ion-text-wrap">
<ion-label> <ion-label>
<p class="item-heading">{{ data.label }}</p> <p class="item-heading">{{ data.label }}</p>

View File

@ -10,6 +10,10 @@
font-size: 100%; font-size: 100%;
} }
.addon-mod_bbb-recording-playback-title ion-label {
margin-bottom: 0;
}
.addon-mod_bbb-recording-details { .addon-mod_bbb-recording-details {
border-top: 2px solid var(--recording-details-border); border-top: 2px solid var(--recording-details-border);
border-bottom: 2px solid var(--recording-details-border); border-bottom: 2px solid var(--recording-details-border);

View File

@ -25,7 +25,13 @@ import { CoreTextUtils } from '@services/utils/text';
import { CoreTimeUtils } from '@services/utils/time'; import { CoreTimeUtils } from '@services/utils/time';
import { CoreUtils } from '@services/utils/utils'; import { CoreUtils } from '@services/utils/utils';
import { Translate } from '@singletons'; import { Translate } from '@singletons';
import { AddonModBBB, AddonModBBBData, AddonModBBBMeetingInfo, AddonModBBBService } from '../../services/bigbluebuttonbn'; import {
AddonModBBB,
AddonModBBBData,
AddonModBBBMeetingInfo,
AddonModBBBRecordingPlaybackTypes,
AddonModBBBService,
} from '../../services/bigbluebuttonbn';
/** /**
* Component that displays a Big Blue Button activity. * Component that displays a Big Blue Button activity.
@ -43,7 +49,7 @@ export class AddonModBBBIndexComponent extends CoreCourseModuleMainActivityCompo
groupInfo?: CoreGroupInfo; groupInfo?: CoreGroupInfo;
groupId = 0; groupId = 0;
meetingInfo?: AddonModBBBMeetingInfo; meetingInfo?: AddonModBBBMeetingInfo;
recordings?: RecordingData[]; recordings?: Recording[];
constructor( constructor(
protected content?: IonContent, protected content?: IonContent,
@ -139,13 +145,17 @@ export class AddonModBBBIndexComponent extends CoreCourseModuleMainActivityCompo
const columns = CoreUtils.arrayToObject(recordingsTable.columns, 'key'); const columns = CoreUtils.arrayToObject(recordingsTable.columns, 'key');
this.recordings = recordingsTable.parsedData.map(recordingData => { this.recordings = recordingsTable.parsedData.map(recordingData => {
const playbackEl = CoreDomUtils.convertToElement(String(recordingData.playback)); const details: RecordingDetail[] = [];
const playbackAnchor = playbackEl.querySelector('a'); const playbacksEl = CoreDomUtils.convertToElement(String(recordingData.playback));
const details: RecordingDetailData[] = []; const playbacks: RecordingPlayback[] = Array.from(playbacksEl.querySelectorAll('a')).map(playbackAnchor => ({
name: playbackAnchor.textContent ?? '',
url: playbackAnchor.href,
icon: this.getPlaybackIcon(playbackAnchor),
}));
Object.entries(recordingData).forEach(([key, value]) => { Object.entries(recordingData).forEach(([key, value]) => {
const columnData = columns[key]; const columnData = columns[key];
if (!columnData || value === '' || key === 'actionbar') { if (!columnData || value === '' || key === 'actionbar' || key === 'playback') {
return; return;
} }
@ -158,10 +168,6 @@ export class AddonModBBBIndexComponent extends CoreCourseModuleMainActivityCompo
return; return;
} }
if (key === 'playback') {
// Remove HTML, we're only interested in the text.
value = (valueElement.textContent ?? '').trim();
} else {
// Treat "quick edit" buttons, they aren't supported in the app. // Treat "quick edit" buttons, they aren't supported in the app.
const quickEditLink = valueElement.querySelector('.quickeditlink'); const quickEditLink = valueElement.querySelector('.quickeditlink');
if (quickEditLink) { if (quickEditLink) {
@ -169,7 +175,6 @@ export class AddonModBBBIndexComponent extends CoreCourseModuleMainActivityCompo
value = (quickEditLink.querySelector('span')?.innerHTML ?? '').trim(); value = (quickEditLink.querySelector('span')?.innerHTML ?? '').trim();
} }
} }
}
details.push({ details.push({
label: columnData.label, label: columnData.label,
@ -179,16 +184,40 @@ export class AddonModBBBIndexComponent extends CoreCourseModuleMainActivityCompo
}); });
return { return {
type: playbackAnchor?.innerText ??
Translate.instant('addon.mod_bigbluebuttonbn.view_recording_format_presentation'),
name: CoreTextUtils.cleanTags(String(recordingData.recording), { singleLine: true }), name: CoreTextUtils.cleanTags(String(recordingData.recording), { singleLine: true }),
url: playbackAnchor?.href ?? '', playbackLabel: columns.playback.label,
playbacks,
details, details,
expanded: false, expanded: false,
}; };
}); });
} }
/**
* Get the playback icon.
*
* @param playbackAnchor Anchor element.
* @returns Icon name.
*/
protected getPlaybackIcon(playbackAnchor: HTMLAnchorElement): string {
const type = playbackAnchor.dataset.target;
switch (type) {
case AddonModBBBRecordingPlaybackTypes.NOTES:
return 'far-file-lines';
case AddonModBBBRecordingPlaybackTypes.PODCAST:
return 'fas-microphone-lines';
case AddonModBBBRecordingPlaybackTypes.SCREENSHARE:
return 'fas-display';
case AddonModBBBRecordingPlaybackTypes.STATISTICS:
return 'fas-chart-line';
case AddonModBBBRecordingPlaybackTypes.VIDEO:
return 'fas-video';
case AddonModBBBRecordingPlaybackTypes.PRESENTATION:
default:
return 'fas-circle-play';
}
}
/** /**
* @inheritdoc * @inheritdoc
*/ */
@ -323,21 +352,21 @@ export class AddonModBBBIndexComponent extends CoreCourseModuleMainActivityCompo
* *
* @param recording Recording. * @param recording Recording.
*/ */
toggle(recording: RecordingData): void { toggle(recording: Recording): void {
recording.expanded = !recording.expanded; recording.expanded = !recording.expanded;
} }
/** /**
* Play a recording. * Open a recording playback.
* *
* @param event Click event. * @param event Click event.
* @param recording Recording. * @param playback Playback.
*/ */
playRecording(event: MouseEvent, recording: RecordingData): void { openPlayback(event: MouseEvent, playback: RecordingPlayback): void {
event.preventDefault(); event.preventDefault();
event.stopPropagation(); event.stopPropagation();
CoreSites.getCurrentSite()?.openInBrowserWithAutoLogin(recording.url); CoreSites.getCurrentSite()?.openInBrowserWithAutoLogin(playback.url);
} }
} }
@ -345,19 +374,28 @@ export class AddonModBBBIndexComponent extends CoreCourseModuleMainActivityCompo
/** /**
* Recording data. * Recording data.
*/ */
type RecordingData = { type Recording = {
type: string;
name: string; name: string;
url: string;
expanded: boolean; expanded: boolean;
details: RecordingDetailData[]; playbackLabel: string;
playbacks: RecordingPlayback[];
details: RecordingDetail[];
}; };
/** /**
* Recording detail data. * Recording detail data.
*/ */
type RecordingDetailData = { type RecordingDetail = {
label: string; label: string;
value: string; value: string;
allowHTML: boolean; allowHTML: boolean;
}; };
/**
* Recording playback data.
*/
type RecordingPlayback = {
name: string;
url: string;
icon: string;
};

View File

@ -17,7 +17,5 @@
"view_message_viewer": "viewer", "view_message_viewer": "viewer",
"view_message_viewers": "viewers", "view_message_viewers": "viewers",
"view_nojoin": "You do not have a role that is allowed to join this session.", "view_nojoin": "You do not have a role that is allowed to join this session.",
"view_recording_format_presentation": "Presentation",
"view_recording_list_action_play": "Play",
"view_section_title_recordings": "Recordings" "view_section_title_recordings": "Recordings"
} }

View File

@ -548,3 +548,15 @@ export type AddonModBBBRecordingsWSTableData = {
export type AddonModBBBRecordingsTableData = AddonModBBBRecordingsWSTableData & { export type AddonModBBBRecordingsTableData = AddonModBBBRecordingsWSTableData & {
parsedData: Record<string, string|number|boolean>[]; parsedData: Record<string, string|number|boolean>[];
}; };
/**
* Recording playback types.
*/
export enum AddonModBBBRecordingPlaybackTypes {
NOTES = 'notes',
PODCAST = 'podcast',
PRESENTATION = 'presentation',
SCREENSHARE = 'screenshare',
STATISTICS = 'statistics',
VIDEO = 'video',
}

View File

@ -143,15 +143,15 @@ Feature: Test basic usage of BBB activity in app
| BBB | Recording 1 | Description 1 | 3 | | BBB | Recording 1 | Description 1 | 3 |
| BBB | Recording 2 | Description 2 | 3 | | BBB | Recording 2 | Description 2 | 3 |
And I entered the bigbluebuttonbn activity "BBB" on course "Course 1" as "student1" in the app And I entered the bigbluebuttonbn activity "BBB" on course "Course 1" as "student1" in the app
Then I should find "Presentation" in the app Then I should find "Recording 1" in the app
And I should find "Recording 1" in the app
And I should find "Recording 2" in the app And I should find "Recording 2" in the app
But I should not find "Description 1" in the app But I should not find "Description 1" in the app
And I should not find "Description 2" in the app And I should not find "Description 2" in the app
And I should not find "Presentation" in the app
When I press "Recording 1" in the app When I press "Recording 1" in the app
Then I should find "Description 1" in the app Then I should find "Description 1" in the app
And I should find "Presentation" within "Playback" "ion-item" in the app And I should find "Presentation" in the app
And I should find "Recording 1" within "Name" "ion-item" in the app And I should find "Recording 1" within "Name" "ion-item" in the app
And I should find "Date" in the app And I should find "Date" in the app
And I should find "3600" within "Duration" "ion-item" in the app And I should find "3600" within "Duration" "ion-item" in the app
@ -162,12 +162,12 @@ Feature: Test basic usage of BBB activity in app
When I press "Recording 2" in the app When I press "Recording 2" in the app
Then I should find "Description 2" in the app Then I should find "Description 2" in the app
And I should find "Presentation" within "Playback" "ion-item" in the app And I should find "Presentation" in the app
And I should find "Recording 2" within "Name" "ion-item" in the app And I should find "Recording 2" within "Name" "ion-item" in the app
But I should not find "Description 1" in the app But I should not find "Description 1" in the app
# Test play button, but the mock server doesn't support viewing recordings. # Test play button, but the mock server doesn't support viewing recordings.
When I press "Play" near "Recording 1" in the app When I press "Presentation" in the app
And I press "OK" in the app And I press "OK" in the app
And I switch to the browser tab opened by the app And I switch to the browser tab opened by the app
And I log in as "student1" And I log in as "student1"