Add "Controller-friendly UI" option

This commit is contained in:
redphx 2024-07-15 20:54:35 +07:00
parent 66120d6970
commit 394dc68ece
9 changed files with 155 additions and 73 deletions

File diff suppressed because one or more lines are too long

View File

@ -83,24 +83,24 @@
overflow: hidden;
white-space: nowrap;
}
}
&.bx-focusable {
position: relative;
.bx-focusable {
position: relative;
&::after {
border: 2px solid transparent;
border-radius: 4px;
}
&::after {
border: 2px solid transparent;
border-radius: 4px;
}
&:focus::after {
content: '';
border-color: white;
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
}
&:focus::after {
content: '';
border-color: white;
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
}
}

View File

@ -100,11 +100,16 @@
position: relative;
label {
flex: 1;
align-self: center;
margin-bottom: 0;
}
.bx-setting-control {
flex: 1;
display: flex;
justify-content: right;
}
&:hover, &:focus-within {
background-color: #242424;

View File

@ -1,19 +1,25 @@
.bx-select {
display: flex;
align-items: center;
select {
display: none;
}
> div {
display: inline-block;
> div, button.bx-select-value {
min-width: 110px;
text-align: center;
margin: 0 10px;
margin: 0 8px;
line-height: 24px;
vertical-align: middle;
background: #fff;
color: #000;
border-radius: 4px;
padding: 2px 4px;
padding: 2px 8px;
}
> div {
display: inline-block;
input {
display: inline-block;
@ -22,23 +28,55 @@
label {
margin-bottom: 0;
font-size: 14px;
}
}
button {
button.bx-select-value {
border: none;
display: inline-flex;
cursor: pointer;
height: 30px;
align-items: center;
span {
flex: 1;
text-align: center;
display: inline-block;
}
input {
margin: 0 4px;
accent-color: var(--bx-primary-button-color);
}
&:hover,
&:focus {
input {
accent-color: var(--bx-danger-button-color);
}
&::after {
border-color: #4d4d4d !important;
}
}
}
button.bx-button {
border: none;
height: 30px;
width: 24px;
height: 24px;
line-height: 24px;
padding: 0;
line-height: 30px;
color: #fff;
border-radius: 4px;
font-weight: bold;
font-size: 14px;
font-size: 12px;
font-family: var(--bx-monospaced-font);
&.bx-inactive {
pointer-events: none;
opacity: 0.2;
opacity: 0.1;
}
span {

View File

@ -11,7 +11,6 @@ import { SoundShortcut } from "../shortcuts/shortcut-sound";
import { TouchController } from "../touch-controller";
import { VibrationManager } from "../vibration-manager";
import { StreamStats } from "./stream-stats";
import { BX_FLAGS } from "@/utils/bx-flags";
import { BxSelectElement } from "@/web-components/bx-select";
import { onChangeVideoPlayerType, updateVideoPlayer } from "./stream-settings-utils";
@ -362,7 +361,8 @@ export class StreamSettings {
} else if (!setting.unsupported) {
$control = toPrefElement(pref, setting.onChange, setting.params);
if ($control instanceof HTMLSelectElement && BX_FLAGS.ScriptUi === 'tv') {
// Replace <select> with controller-friendly one
if ($control instanceof HTMLSelectElement && getPref(PrefKey.UI_CONTROLLER_FRIENDLY)) {
$control = BxSelectElement.wrap($control);
}
}

View File

@ -7,7 +7,6 @@ import { getPref, Preferences, PrefKey, setPref, toPrefElement } from "@utils/pr
import { t, Translations } from "@utils/translation";
import { PatcherCache } from "../patcher";
import { UserAgentProfile } from "@enums/user-agent";
import { BX_FLAGS } from "@/utils/bx-flags";
import { BxSelectElement } from "@/web-components/bx-select";
import { StreamSettings } from "../stream/stream-settings";
@ -16,6 +15,7 @@ const SETTINGS_UI = {
items: [
PrefKey.BETTER_XCLOUD_LOCALE,
PrefKey.SERVER_BYPASS_RESTRICTION,
PrefKey.UI_CONTROLLER_FRIENDLY,
PrefKey.REMOTE_PLAY_ENABLED,
],
},
@ -218,8 +218,8 @@ export function setupSettingsUi() {
Translations.refreshCurrentLocale();
await Translations.updateTranslations();
// Don't refresh the page on TV
if (BX_FLAGS.ScriptUi !== 'tv') {
// Don't refresh the page when using controller-friendly UI
if (!getPref(PrefKey.UI_CONTROLLER_FRIENDLY)) {
$btnReload.textContent = t('settings-reloading');
$btnReload.click();
}
@ -388,15 +388,16 @@ export function setupSettingsUi() {
let $elm: HTMLElement;
if ($control instanceof HTMLSelectElement && BX_FLAGS.ScriptUi === 'tv') {
if ($control instanceof HTMLSelectElement && getPref(PrefKey.UI_CONTROLLER_FRIENDLY)) {
// Controller-friendly <select>
$elm = CE('div', {'class': 'bx-settings-row'},
$label,
BxSelectElement.wrap($control),
CE('div', {class: 'bx-setting-control', 'data-group': 0}, BxSelectElement.wrap($control)),
);
} else {
$elm = CE('div', {'class': 'bx-settings-row'},
$elm = CE('div', {'class': 'bx-settings-row', 'data-group': 0},
$label,
$control,
CE('div', {class: 'bx-setting-control'}, $control),
);
}

View File

@ -10,8 +10,6 @@ type BxFlags = Partial<{
ForceNativeMkbTitles: string[];
FeatureGates: {[key: string]: boolean} | null,
ScriptUi: 'default' | 'tv',
IsSupportedTvBrowser: boolean,
}>
@ -27,8 +25,6 @@ const DEFAULT_FLAGS: BxFlags = {
ForceNativeMkbTitles: [],
FeatureGates: null,
ScriptUi: 'default',
}
export const BX_FLAGS: BxFlags = Object.assign(DEFAULT_FLAGS, window.BX_FLAGS || {});

View File

@ -1,5 +1,5 @@
import { CE } from "@utils/html";
import { SUPPORTED_LANGUAGES, t} from "@utils/translation";
import { SUPPORTED_LANGUAGES, t, ut} from "@utils/translation";
import { SettingElement, SettingElementType } from "@utils/settings";
import { UserAgent } from "@utils/user-agent";
import { StreamStat } from "@modules/stream/stream-stats";
@ -72,6 +72,7 @@ export enum PrefKey {
UI_LOADING_SCREEN_WAIT_TIME = 'ui_loading_screen_wait_time',
UI_LOADING_SCREEN_ROCKET = 'ui_loading_screen_rocket',
UI_CONTROLLER_FRIENDLY = 'ui_controller_friendly',
UI_LAYOUT = 'ui_layout',
UI_SCROLLBAR_HIDE = 'ui_scrollbar_hide',
UI_HIDE_SECTIONS = 'ui_hide_sections',
@ -553,6 +554,12 @@ export class Preferences {
hide: t('rocket-always-hide'),
},
},
[PrefKey.UI_CONTROLLER_FRIENDLY]: {
label: ut('Controller-friendly UI'),
default: false,
},
[PrefKey.UI_LAYOUT]: {
label: t('layout'),
default: 'default',

View File

@ -23,20 +23,32 @@ export class BxSelectElement {
let $checkBox: HTMLInputElement;
let $label: HTMLElement;
const $content = CE('div', {},
$checkBox = CE('input', {type: 'checkbox', id: $select.id + '_checkbox'}),
$label = CE('label', {for: $select.id + '_checkbox'}, ''),
);
let $content;
isMultiple && $checkBox.addEventListener('input', e => {
const $option = getOptionAtIndex(visibleIndex);
$option && ($option.selected = (e.target as HTMLInputElement).checked);
if (isMultiple) {
$content = CE('button', {
class: 'bx-select-value bx-focusable',
tabindex: 0,
},
$checkBox = CE('input', {type: 'checkbox'}),
$label = CE('span', {}, ''),
);
$select.dispatchEvent(new Event('input'));
});
$content.addEventListener('click', e => {
$checkBox.click();
});
// Only show checkbox in "multiple" <select>
$checkBox.classList.toggle('bx-gone', !isMultiple);
$checkBox.addEventListener('input', e => {
const $option = getOptionAtIndex(visibleIndex);
$option && ($option.selected = (e.target as HTMLInputElement).checked);
$select.dispatchEvent(new Event('input'));
});
} else {
$content = CE('div', {},
$label = CE('label', {for: $select.id + '_checkbox'}, ''),
);
}
const getOptionAtIndex = (index: number): HTMLOptionElement | undefined => {
return $select.querySelector(`option:nth-of-type(${visibleIndex + 1})`) as HTMLOptionElement;
@ -51,22 +63,28 @@ export class BxSelectElement {
let content = '';
if ($option) {
content = $option.textContent || '';
if (content && $option.parentElement!.tagName === 'OPTGROUP') {
content = ($option.parentElement as HTMLOptionElement).label + ' ' + content;
}
}
$label.textContent = content;
// Hide checkbox when the selection is empty
isMultiple && ($checkBox.checked = $option?.selected || false);
$checkBox.classList.toggle('bx-gone', !isMultiple || !content);
if (isMultiple) {
$checkBox.checked = $option?.selected || false;
$checkBox.classList.toggle('bx-gone', !content);
}
const disablePrev = visibleIndex <= 0;
const disableNext = visibleIndex === $select.querySelectorAll('option').length - 1;
$btnPrev.classList.toggle('bx-inactive', disablePrev);
disablePrev && $btnNext.focus();
disablePrev && document.activeElement === $btnPrev && $btnNext.focus();
$btnNext.classList.toggle('bx-inactive', disableNext);
disableNext && $btnPrev.focus();
disableNext && document.activeElement === $btnNext &&$btnPrev.focus();
}
const normalizeIndex = (index: number): number => {