Skip to content

Commit

Permalink
Merge pull request #643 from bitmovin/feature/complete-settings-menu
Browse files Browse the repository at this point in the history
Completed new Settings menu
  • Loading branch information
stonko1994 authored Oct 1, 2024
2 parents d1152b2 + 1ae93f8 commit 8bc0894
Show file tree
Hide file tree
Showing 12 changed files with 415 additions and 52 deletions.
3 changes: 3 additions & 0 deletions assets/skin-super-modern/images/angle-left.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 3 additions & 0 deletions assets/skin-super-modern/images/angle-right.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 3 additions & 0 deletions assets/skin-super-modern/images/check.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
7 changes: 4 additions & 3 deletions src/scss/skin-super-modern/components/_settingspanel.scss
Original file line number Diff line number Diff line change
Expand Up @@ -14,17 +14,18 @@
$background-color: transparentize($color-background-menu, .15);

background-color: $background-color;
border-radius: 6px;
border-radius: 4px;
bottom: 3.5em;
height: fit-content;
max-height: 60%;
min-width: 170px;
max-width: 140px;
min-width: fit-content;
overflow: hidden;
overflow-y: auto;
padding: 0;
position: absolute;
right: 1em;
width: 40%;
width: 35%;

> .#{$prefix}-container-wrapper {
overflow-y: auto;
Expand Down
62 changes: 60 additions & 2 deletions src/scss/skin-super-modern/components/_settingspanelpage.scss
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,61 @@
.#{$prefix}-container-wrapper > * {
&.#{$prefix}-ui-label {
display: inline-block;
font-size: .7em;
font-size: .6em;
font-weight: 600;
margin: 0;
text-align: justify;
width: 45%;
}

&.#{$prefix}-ui-label-setting-selected-option {
align-self: center;
font-weight: normal;
margin-left: auto;
width: fit-content;

&::after {
background-image: url('../../assets/skin-super-modern/images/angle-right.svg');
background-repeat: no-repeat;
background-size: 1.5em auto;
content: ' ';
display: inline-block;
height: 1.5em;
vertical-align: -.4em;
width: 1.5em;
}
}

&.#{$prefix}-option {
font-weight: normal;
}

&.#{$prefix}-heading {
&::before {
background-image: url('../../assets/skin-super-modern/images/angle-left.svg');
background-repeat: no-repeat;
background-size: 1.5em auto;
content: ' ';
display: inline-block;
height: 1.5em;
vertical-align: -.4em;
width: 1.5em;
}
}

&.#{$prefix}-selected {
&::before {
background-image: url('../../assets/skin-super-modern/images/check.svg');
background-repeat: no-repeat;
background-size: 1.5em auto;
content: ' ';
display: inline-block;
height: 1.5em;
vertical-align: -.4em;
width: 1.5em;
}
}

// Controls (e.g. selectbox)
&.#{$prefix}-ui-selectbox {
margin-left: 10%;
Expand All @@ -24,7 +74,8 @@
}

.#{$prefix}-ui-settings-panel-item {
padding: 0 .5em;
height: 20px;
padding: 0 .4em;
white-space: nowrap;

&:hover {
Expand All @@ -34,6 +85,13 @@
&.#{$prefix}-hidden {
display: none;
}

.#{$prefix}-container-wrapper {
align-items: center;
display: flex;
height: 100%;
width: 100%;
}
}
}

Expand Down
54 changes: 54 additions & 0 deletions src/ts/components/modernsettingspanel.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import { UIInstanceManager } from '../uimanager';
import { ModernSettingsPanelPage } from './modernsettingspanelpage';
import { PlayerAPI } from 'bitmovin-player';
import { SettingsPanel, SettingsPanelConfig, NavigationDirection } from './settingspanel';


export class ModernSettingsPanel extends SettingsPanel {
constructor(config: SettingsPanelConfig) {
super(config);

this.config = this.mergeConfig(config, {
cssClass: 'ui-settings-panel',
hideDelay: 3000,
pageTransitionAnimation: true,
} as SettingsPanelConfig, this.config);

(<ModernSettingsPanelPage>this.getActivePage()).onRequestsDisplaySubMenu.subscribe(this.handleShowSubPage);
}

configure(player: PlayerAPI, uimanager: UIInstanceManager): void {
super.configure(player, uimanager);

uimanager.onPreviewControlsHide.subscribe(() => {
this.hide();
});
}

private handleShowSubPage = (sender: ModernSettingsPanelPage, subPage: ModernSettingsPanelPage) => {
this.show();
this.addComponent(subPage);
this.updateComponents();
this.setActivePage(subPage);
}

private handleNavigateBack = (page: ModernSettingsPanelPage) => {
this.popSettingsPanelPage();
this.removeComponent(page);
this.updateComponents();
}

protected navigateToPage(
targetPage: ModernSettingsPanelPage,
sourcePage: ModernSettingsPanelPage,
direction: NavigationDirection,
skipAnimation: boolean,
): void {
super.navigateToPage(targetPage, sourcePage, direction, skipAnimation);

if (direction === NavigationDirection.Forwards) {
targetPage.onRequestsDisplaySubMenu.subscribe(this.handleShowSubPage);
targetPage.onRequestsNavigateBack.subscribe(() => this.handleNavigateBack(targetPage));
}
}
}
192 changes: 192 additions & 0 deletions src/ts/components/modernsettingspanelitem.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,192 @@
import { ContainerConfig} from './container';
import {Component, ComponentConfig} from './component';
import {Event as EDEvent, EventDispatcher, NoArgs} from '../eventdispatcher';
import { Label, LabelConfig } from './label';
import {UIInstanceManager} from '../uimanager';
import {SelectBox} from './selectbox';
import {ListBox} from './listbox';
import {VideoQualitySelectBox} from './videoqualityselectbox';
import {AudioQualitySelectBox} from './audioqualityselectbox';
import {PlaybackSpeedSelectBox} from './playbackspeedselectbox';
import { PlayerAPI } from 'bitmovin-player';
import { i18n, LocalizableText } from '../localization/i18n';
import { ModernSettingsPanelPage } from './modernsettingspanelpage';
import { ListSelector, ListSelectorConfig } from './listselector';
import { SubtitleSelectBox } from './subtitleselectbox';
import { SettingsPanelItem } from './settingspanelitem';

/**
* An item for a {@link ModernSettingsPanelPage},
* Containing an optional {@link Label} and a component that configures a setting.
* If the components is a {@link SelectBox} it will handle the logic of displaying it or not
*/
export class ModernSettingsPanelItem extends SettingsPanelItem {

/**
* If setting is null, that means that the item is not an option and does not
* have a submenu. So if setting is null we can assume that the item should be
* used as a back button
*/
private selectedOptionLabel: Label<LabelConfig>;
private isOption: Boolean;
private key: string;

private player: PlayerAPI;
private uimanager: UIInstanceManager;

private modernSettingsPanelItemEvents = {
onRequestSubPage: new EventDispatcher<ModernSettingsPanelItem, ModernSettingsPanelPage>(),
onRequestNavigateBack: new EventDispatcher<ModernSettingsPanelItem, NoArgs>(),
onItemSelect: new EventDispatcher<ModernSettingsPanelItem, string>(),
};

constructor(label: LocalizableText | Component<ComponentConfig>, setting: Component<ComponentConfig>, key: string = null, config: ContainerConfig = {}) {
super(label, setting, config, false);

this.isOption = Boolean(key);
this.key = key;

this.config = this.mergeConfig(config, {
cssClass: 'ui-settings-panel-item',
role: 'menuitem',
}, this.config);
}

configure(player: PlayerAPI, uimanager: UIInstanceManager): void {
this.player = player;
this.uimanager = uimanager;

if (this.setting !== null) {
this.setting.configure(this.player, this.uimanager);
}

if (!this.isOption && (this.setting instanceof SelectBox || this.setting instanceof ListBox)) {
this.setting.onItemSelected.subscribe(() => {
this.removeComponent(this.selectedOptionLabel);
const setting = this.setting as ListSelector<ListSelectorConfig>;
let selectedOptionLabel: LocalizableText = setting.getItemForKey(setting.getSelectedItem()).label;

if (this.setting instanceof SubtitleSelectBox) {
let availableSettings = setting.getItems().length;
selectedOptionLabel = selectedOptionLabel + ' (' + (availableSettings - 1) + ')';
}
this.selectedOptionLabel = new Label({ text: selectedOptionLabel, for: this.getConfig().id } as LabelConfig);
this.selectedOptionLabel.getDomElement().addClass(this.prefixCss('ui-label-setting-selected-option'));
this.addComponent(this.selectedOptionLabel);
this.updateComponents();
});

let handleConfigItemChanged = () => {
if (!(this.setting instanceof SelectBox) && !(this.setting instanceof ListBox)) {
return;
}
// The minimum number of items that must be available for the setting to be displayed
// By default, at least two items must be available, else a selection is not possible
let minItemsToDisplay = 2;
// Audio/video quality select boxes contain an additional 'auto' mode, which in combination with a single
// available quality also does not make sense
if ((this.setting instanceof VideoQualitySelectBox && this.setting.hasAutoItem())
|| this.setting instanceof AudioQualitySelectBox) {
minItemsToDisplay = 3;
}
if (this.setting.itemCount() < minItemsToDisplay) {
// Hide the setting if no meaningful choice is available
this.hide();
} else if (this.setting instanceof PlaybackSpeedSelectBox
&& !uimanager.getConfig().playbackSpeedSelectionEnabled) {
// Hide the PlaybackSpeedSelectBox if disabled in config
this.hide();
} else {
this.show();
}

// Visibility might have changed and therefore the active state might have changed so we fire the event
// TODO fire only when state has really changed (e.g. check if visibility has really changed)
this.onActiveChangedEvent();

this.getDomElement().attr('aria-haspopup', 'true');
};

this.setting.onItemAdded.subscribe(handleConfigItemChanged);
this.setting.onItemRemoved.subscribe(handleConfigItemChanged);

// Initialize hidden state
handleConfigItemChanged();
}
else if (this.isOption) {
this.show();
this.onActiveChangedEvent();
this.getLabel.getDomElement().addClass(this.prefixCss('option'));
}

const handleItemClick = (e: Event) => {
if (this.setting !== null) {
if (!this.isOption) {
this.displayItemsSubPage();
}

else {
if (this.setting instanceof SelectBox || this.setting instanceof ListBox) {
this.setting.selectItem(this.key);
this.modernSettingsPanelItemEvents.onItemSelect.dispatch(this, this.key);
this.getLabel.getDomElement().addClass(this.prefixCss('selected'));
}
}
} else {
this.modernSettingsPanelItemEvents.onRequestNavigateBack.dispatch(this);
}
};
const domElement = this.getDomElement();
domElement.on('click', (e) => handleItemClick(e));
}

private getSubPage(): ModernSettingsPanelPage {
if (this.setting instanceof SelectBox || this.setting instanceof ListBox) {
let menuOptions = this.setting.getItems();
let selectedItem = this.setting.getSelectedItem();
let page = new ModernSettingsPanelPage({});
let label = this.getLabel instanceof Label ? new Label({ text: this.getLabel.getConfig().text, for: page.getConfig().id } as LabelConfig) : new Label({ text: i18n.getLocalizer('back'), for: page.getConfig().id } as LabelConfig);
label.getDomElement().addClass(this.prefixCss('heading'));
let itemToAdd = new ModernSettingsPanelItem(label, null);
itemToAdd.configure(this.player, this.uimanager);
page.addComponent(itemToAdd);

for (let option of menuOptions) {
let itemToAdd = new ModernSettingsPanelItem(option.label, this.setting, option.key);
itemToAdd.configure(this.player, this.uimanager);

if (option.key === selectedItem) {
itemToAdd.getLabel.getDomElement().addClass(this.prefixCss('selected'));
}
page.addComponent(itemToAdd);
}
page.configure(this.player, this.uimanager);
return page;
}
}

public getSetting(): Component<ComponentConfig> {
return this.setting;
}

public displayItemsSubPage(): void {
let page = this.getSubPage();
this.modernSettingsPanelItemEvents.onRequestSubPage.dispatch(this, page);
}

/**
* Gets the event that is fired, when the SettingsPanelItem has been clicked
* and wants to display its sub menu on the {@link ModernSettingsPanel} as a seperate {@link ModernSettingsPanelPage}
*/
get getOnDisplaySubPage(): EDEvent<ModernSettingsPanelItem, NoArgs> {
return this.modernSettingsPanelItemEvents.onRequestSubPage.getEvent();
}

get getOnRequestNavigateBack(): EDEvent<ModernSettingsPanelItem, NoArgs> {
return this.modernSettingsPanelItemEvents.onRequestNavigateBack.getEvent();
}

get onItemSelect(): EDEvent<ModernSettingsPanelItem, string> {
return this.modernSettingsPanelItemEvents.onItemSelect.getEvent();
}
}
Loading

0 comments on commit 8bc0894

Please sign in to comment.