Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(FEC-13209): [Audio Player] adjust full size player UI to entries of audio media type #783

Merged
merged 14 commits into from
Sep 13, 2023
Merged
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@
"@babel/preset-flow": "^7.10.4",
"@babel/register": "^7.10.5",
"@playkit-js/playkit-js": "canary",
"@playkit-js/kaltura-player-js": "canary",
"@playkit-js/kaltura-player-js": "3.15.3-canary.0-51ca9be",
"babel-eslint": "^10.1.0",
"babel-loader": "^8.1.0",
"babel-plugin-istanbul": "^6.0.0",
Expand Down
64 changes: 64 additions & 0 deletions src/components/audio-entry-details/_audio-entry-details.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
.player {
.audio-entry-backdrop {
background: linear-gradient(180deg, rgba(0, 0, 0, 0) 0%, rgba(0, 0, 0, 0.7) 100%);
bottom: 0;
position: absolute;
width: 100%;
pointer-events: none;
display: flex;
flex-direction: column;
justify-content: flex-end;

.audio-entry-details {
margin: 128px 16px 60px 16px;
left: 16px;
color: white;
max-height: 100%;
pointer-events: auto;
z-index: 1;

&.audio-entry-l {
max-width: 600px;
}

&.audio-entry-m {
max-width: min(600px, 100%);
}

&.audio-entry-t {
max-width: 0px;

.audio-entry-description {
font-size: 0;
}
}

.audio-entry-title {
font-size: 32px;

&.audio-entry-title-trimmed {
-webkit-box-orient: vertical;
-webkit-line-clamp: 1;
display: -webkit-box;
overflow: hidden;
text-overflow: ellipsis;
}
}

.audio-entry-description {
font-size: 14px;
}
}

&.audio-entry-expanded {
overflow: auto;
background: rgba(0, 0, 0, 0.7);
height: 100%;

.audio-entry-details {
overflow: auto;
margin: 60px 16px;
}
}
}
}
116 changes: 116 additions & 0 deletions src/components/audio-entry-details/audio-entry-details.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
//@flow
import style from '../../styles/style.scss';

import {connect} from 'react-redux';

import {h} from 'preact';
import {useState, useLayoutEffect, useRef} from 'preact/hooks';

import {PLAYER_SIZE} from '../shell/shell';
import {withPlayer} from '../player';

import {ExpandableText} from '../expandable-text';
import {Scrollable} from '../scrollable';

interface AudioEntryDetailsProps {
player: any;
isAudio: boolean;
playerSize: typeof PLAYER_SIZE;
}

/**
* mapping state to props
* @param {*} state - redux store state
* @returns {Object} - mapped state to this component
*/
const mapStateToProps = state => ({
isAudio: state.engine.isAudio,
playerSize: state.shell.playerSize
});

const COMPONENT_NAME = 'AudioEntryDetails';

/**
* AudioEntryDetails component
*
* @class AudioEntryDetails
* @example <AudioEntryDetails />
* @extends {Component}
*/

const AudioEntryDetails = connect(mapStateToProps)(
withPlayer((props: AudioEntryDetailsProps) => {
// eslint-disable-next-line require-jsdoc
const getSizeClass = (playerSize: typeof PLAYER_SIZE) => {
switch (playerSize) {
case PLAYER_SIZE.EXTRA_LARGE:
case PLAYER_SIZE.LARGE:
return style.audioEntryL;
case PLAYER_SIZE.MEDIUM:
return style.audioEntryM;
default:
return style.audioEntryT;
}
};

const textRef = useRef(null);
const comparisonTextRef = useRef(null);

const [isFinalized, setIsFinalized] = useState(false);
const [isTitleTrimmed, setIsTitleTrimmed] = useState(false);
const [forceShowMore, setForceShowMore] = useState(false);

useLayoutEffect(() => {
if (textRef?.current && comparisonTextRef?.current) {
setIsFinalized(true);
setIsTitleTrimmed(textRef?.current?.getBoundingClientRect().height > comparisonTextRef?.current?.getBoundingClientRect().height);
}

if (!forceShowMore) {
setForceShowMore(textRef?.current?.getBoundingClientRect().height > comparisonTextRef?.current?.getBoundingClientRect().height);
}
});

if (!props.isAudio || !(props.player.sources?.metadata?.name || props.player.sources?.metadata.description)) {
return undefined;
}

const {name = '', description = ''} = props.player.sources.metadata;
const sizeClass = getSizeClass(props.playerSize);
const titleClass = `${style.audioEntryTitle} ${isTitleTrimmed ? style.audioEntryTitleTrimmed : ''}`;

let expandedClass = isTitleTrimmed ? '' : style.audioEntryExpanded;

// eslint-disable-next-line require-jsdoc
const onClick = () => {
setIsTitleTrimmed(!isTitleTrimmed);
};

return (
<div className={`${style.audioEntryBackdrop} ${expandedClass}`}>
<div className={`${style.audioEntryDetails} ${sizeClass}`}>
<Scrollable isVertical={true}>
<div className={style.audioEntryContent}>
<div ref={textRef} className={titleClass}>
{name}
</div>
{isFinalized ? undefined : (
<div ref={comparisonTextRef} className={`${style.audioEntryTitle} ${style.audioEntryTitleTrimmed}`}>
{name}
</div>
)}
<div className={style.audioEntryDescription}>
<ExpandableText text={description} lines={3} onClick={onClick} forceShowMore={forceShowMore}>
{description}
</ExpandableText>
</div>
</div>
</Scrollable>
</div>
</div>
);
})
);

AudioEntryDetails.displayName = COMPONENT_NAME;
export {AudioEntryDetails};
1 change: 1 addition & 0 deletions src/components/audio-entry-details/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export {AudioEntryDetails} from './audio-entry-details';
3 changes: 3 additions & 0 deletions src/components/engine-connector/engine-connector.js
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,9 @@ class EngineConnector extends Component {
this.props.updateIsLive(player.isLive());
this.props.updateIsVr(player.isVr());
this.props.updateIsImg(player.isUntimedImg());

this.props.updateIsAudio(player.isAudio());

this.props.updateIsInPictureInPicture(player.isInPictureInPicture());
if (player.config.playback.autoplay) {
this.props.updateLoadingSpinnerState(true);
Expand Down
16 changes: 16 additions & 0 deletions src/components/expandable-text/_expandable-text.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
.player {
.expandable-text {
text-overflow: ellipsis;
overflow: hidden;
-webkit-box-orient: vertical;
display: -webkit-box;
}

.more-button-text {
pointer-events: auto;
cursor: pointer;
position: relative;
z-index: 1;
width: fit-content;
}
}
82 changes: 82 additions & 0 deletions src/components/expandable-text/expandable-text.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
//@flow
import {h, ComponentChildren} from 'preact';
import {withText} from 'preact-i18n';

import {useState, useRef, useLayoutEffect} from 'preact/hooks';

import styles from '../../styles/style.scss';

const COMPONENT_NAME = 'ExpandableText';

interface ExpandableTextProps {
text: string;
lines: number;
forceShowMore: boolean;
onClick?: () => void;
readMoreLabel?: string;
readLessLabel?: string;
children: ComponentChildren;
}

const ExpandableText = withText({
readMoreLabel: 'controls.readMore',
readLessLabel: 'controls.readLess'
})((props: ExpandableTextProps) => {
const [isTextExpanded, setIsTextExpanded] = useState(false);
const [isTextTrimmed, setIsTextTrimmed] = useState(false);
const [isFinalized, setIsFinalized] = useState(false);

const comparisonTextRef = useRef(null);
const textRef = useRef(null);

useLayoutEffect(() => {
if (textRef?.current && comparisonTextRef?.current) {
setIsFinalized(true);
setIsTextTrimmed(textRef?.current?.getBoundingClientRect().height < comparisonTextRef?.current?.getBoundingClientRect().height);
}
}, [isFinalized]);

// eslint-disable-next-line require-jsdoc
const onClick = () => {
if (props.onClick) {
props.onClick();
}

setIsTextExpanded(!isTextExpanded);
};

if (!isTextTrimmed && !props.forceShowMore) {
return (
<div className={styles.contentText}>
<div className={styles.titleWrapper}>
<div ref={textRef} style={{'-webkit-line-clamp': props.lines}} className={styles.expandableText}>
{props.text}
</div>
{!isFinalized ? (
<div ref={comparisonTextRef} style={{'-webkit-line-clamp': props.lines + 1}} className={styles.expandableText}>
{props.text}
</div>
) : undefined}
</div>
</div>
);
}

return (
<div>
{isTextExpanded ? (
props.children
) : (
<div className={styles.expandableText} style={{'-webkit-line-clamp': props.lines}}>
{props.text}
</div>
)}
<div className={styles.moreButtonText} onClick={onClick}>
{isTextExpanded ? props.readLessLabel : props.readMoreLabel}
</div>
</div>
);
});

ExpandableText.displayName = COMPONENT_NAME;
export {ExpandableText};
1 change: 1 addition & 0 deletions src/components/expandable-text/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export {ExpandableText} from './expandable-text';
2 changes: 2 additions & 0 deletions src/components/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,8 @@ export {CaptionsMenu} from './captions-menu';
export {SpeedMenu} from './speed-menu';
export {QualityMenu, HeightResolution, getLabelBadgeType} from './quality-menu';
export {AdvancedAudioDescToggle} from './advanced-audio-desc-toggle';
export {ExpandableText} from './expandable-text';
export {Scrollable} from './scrollable';

export {PlayerArea, withPlayerPreset, Remove} from './player-area';
export {VideoArea} from './video-area';
Expand Down
1 change: 1 addition & 0 deletions src/components/scrollable/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export {Scrollable} from './scrollable.js';
53 changes: 53 additions & 0 deletions src/components/scrollable/scrollable.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
//@flow

import {ComponentChildren, h} from 'preact';
import {useState, useRef, useMemo} from 'preact/hooks';
import styles from '../../styles/style.scss';

const SCROLL_BAR_TIMEOUT = 250;

/**
* Wraps around child components and displays a styled scrollbar with vertical or horizontal orientation.
*
* @param {object} props Component props.
* @param {ComponentChildren} props.children Child components.
* @param {boolean} props.isVertical If true, scrollbar has vertical orientation, otherwise - it has horizontal orientation.
* @returns {any} Scrollable component
*/
const Scrollable = ({children, isVertical}: {children: ComponentChildren, isVertical: boolean}) => {
const ref = useRef(null);
const [scrolling, setScrolling] = useState(false);
const [scrollTimeoutId, setScrollTimeoutId] = useState(-1);

// eslint-disable-next-line require-jsdoc
const handleScroll = () => {
clearTimeout(scrollTimeoutId);
setScrolling(true);
setScrollTimeoutId(
window.setTimeout(() => {
setScrolling(false);
}, SCROLL_BAR_TIMEOUT)
);
};

// eslint-disable-next-line require-jsdoc
const handleWheel = (e: WheelEvent) => {
e.preventDefault();
if (ref?.current) {
ref.current.scrollLeft += e.deltaY;
handleScroll();
}
};

const scrollableParams = useMemo(() => (isVertical ? {onScroll: handleScroll} : {onWheel: handleWheel, ref}), [isVertical]);

return (
<div
className={`${styles.scrollable} ${scrolling ? styles.scrolling : ''} ${isVertical ? styles.vertical : styles.horizontal}`}
{...scrollableParams}>
{children}
</div>
);
};

export {Scrollable};
Loading