Merge pull request #3654 from dpalou/MOBILE-4322

MOBILE-4322 bbb: Display all playbacks in recordings
main
Pau Ferrer Ocaña 2023-05-04 09:59:45 +02:00 committed by GitHub
commit d8f6aaed84
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
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_viewers": "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_book.errorchapter": "book",
"addon.mod_book.modulenameplural": "book",

View File

@ -90,9 +90,8 @@
</ion-label>
</ion-item>
<ng-container *ngFor="let recording of recordings">
<ion-item *ngIf="recording.url" button class="addon-mod_bbb-recording-title" [attr.aria-expanded]="recording.expanded"
(click)="toggle(recording)" [attr.aria-label]="(recording.expanded ? 'core.collapse' : 'core.expand') | translate"
detail="false">
<ion-item button class="addon-mod_bbb-recording-title" [attr.aria-expanded]="recording.expanded" (click)="toggle(recording)"
[attr.aria-label]="(recording.expanded ? 'core.collapse' : 'core.expand') | translate" detail="false">
<ion-icon name="fas-chevron-right" flip-rtl slot="start" aria-hidden="true" class="expandable-status-icon"
[class.expandable-status-icon-expanded]="recording.expanded">
</ion-icon>
@ -100,12 +99,22 @@
<p>{{ recording.type }}</p>
<p>{{ recording.name }}</p>
</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>
<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-label>
<p class="item-heading">{{ data.label }}</p>

View File

@ -10,6 +10,10 @@
font-size: 100%;
}
.addon-mod_bbb-recording-playback-title ion-label {
margin-bottom: 0;
}
.addon-mod_bbb-recording-details {
border-top: 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 { CoreUtils } from '@services/utils/utils';
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.
@ -43,7 +49,7 @@ export class AddonModBBBIndexComponent extends CoreCourseModuleMainActivityCompo
groupInfo?: CoreGroupInfo;
groupId = 0;
meetingInfo?: AddonModBBBMeetingInfo;
recordings?: RecordingData[];
recordings?: Recording[];
constructor(
protected content?: IonContent,
@ -139,13 +145,17 @@ export class AddonModBBBIndexComponent extends CoreCourseModuleMainActivityCompo
const columns = CoreUtils.arrayToObject(recordingsTable.columns, 'key');
this.recordings = recordingsTable.parsedData.map(recordingData => {
const playbackEl = CoreDomUtils.convertToElement(String(recordingData.playback));
const playbackAnchor = playbackEl.querySelector('a');
const details: RecordingDetailData[] = [];
const details: RecordingDetail[] = [];
const playbacksEl = CoreDomUtils.convertToElement(String(recordingData.playback));
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]) => {
const columnData = columns[key];
if (!columnData || value === '' || key === 'actionbar') {
if (!columnData || value === '' || key === 'actionbar' || key === 'playback') {
return;
}
@ -158,16 +168,11 @@ export class AddonModBBBIndexComponent extends CoreCourseModuleMainActivityCompo
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.
const quickEditLink = valueElement.querySelector('.quickeditlink');
if (quickEditLink) {
// The first span in quick edit link contains the actual HTML, use it.
value = (quickEditLink.querySelector('span')?.innerHTML ?? '').trim();
}
// Treat "quick edit" buttons, they aren't supported in the app.
const quickEditLink = valueElement.querySelector('.quickeditlink');
if (quickEditLink) {
// The first span in quick edit link contains the actual HTML, use it.
value = (quickEditLink.querySelector('span')?.innerHTML ?? '').trim();
}
}
@ -179,16 +184,40 @@ export class AddonModBBBIndexComponent extends CoreCourseModuleMainActivityCompo
});
return {
type: playbackAnchor?.innerText ??
Translate.instant('addon.mod_bigbluebuttonbn.view_recording_format_presentation'),
name: CoreTextUtils.cleanTags(String(recordingData.recording), { singleLine: true }),
url: playbackAnchor?.href ?? '',
playbackLabel: columns.playback.label,
playbacks,
details,
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
*/
@ -323,21 +352,21 @@ export class AddonModBBBIndexComponent extends CoreCourseModuleMainActivityCompo
*
* @param recording Recording.
*/
toggle(recording: RecordingData): void {
toggle(recording: Recording): void {
recording.expanded = !recording.expanded;
}
/**
* Play a recording.
* Open a recording playback.
*
* @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.stopPropagation();
CoreSites.getCurrentSite()?.openInBrowserWithAutoLogin(recording.url);
CoreSites.getCurrentSite()?.openInBrowserWithAutoLogin(playback.url);
}
}
@ -345,19 +374,28 @@ export class AddonModBBBIndexComponent extends CoreCourseModuleMainActivityCompo
/**
* Recording data.
*/
type RecordingData = {
type: string;
type Recording = {
name: string;
url: string;
expanded: boolean;
details: RecordingDetailData[];
playbackLabel: string;
playbacks: RecordingPlayback[];
details: RecordingDetail[];
};
/**
* Recording detail data.
*/
type RecordingDetailData = {
type RecordingDetail = {
label: string;
value: string;
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_viewers": "viewers",
"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 File

@ -548,3 +548,15 @@ export type AddonModBBBRecordingsWSTableData = {
export type AddonModBBBRecordingsTableData = AddonModBBBRecordingsWSTableData & {
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 2 | Description 2 | 3 |
And I entered the bigbluebuttonbn activity "BBB" on course "Course 1" as "student1" in the app
Then I should find "Presentation" in the app
And I should find "Recording 1" in the app
Then I should find "Recording 1" in the app
And I should find "Recording 2" 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 "Presentation" in the app
When I press "Recording 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 "Date" 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
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
But I should not find "Description 1" in the app
# 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 switch to the browser tab opened by the app
And I log in as "student1"