Merge pull request #468 from Stremio/Web-app-post-redesign-fixes

Web app post redesign fixes
This commit is contained in:
Tim 2023-10-17 15:37:15 +02:00 committed by GitHub
commit bd84e63a36
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
20 changed files with 317 additions and 126 deletions

1
.gitignore vendored
View file

@ -3,3 +3,4 @@
/yarn.lock
/npm-debug.log
.DS_Store
.prettierignore

2
package-lock.json generated
View file

@ -65,7 +65,7 @@
"terser-webpack-plugin": "5.2.4",
"webpack": "5.61.0",
"webpack-cli": "4.9.1",
"webpack-dev-server": "4.7.4",
"webpack-dev-server": "^4.7.4",
"workbox-webpack-plugin": "^6.5.3"
}
},

View file

@ -68,7 +68,7 @@
"terser-webpack-plugin": "5.2.4",
"webpack": "5.61.0",
"webpack-cli": "4.9.1",
"webpack-dev-server": "4.7.4",
"webpack-dev-server": "^4.7.4",
"workbox-webpack-plugin": "^6.5.3"
}
}

View file

@ -35,6 +35,7 @@
--tertiary-accent-color: rgba(246, 199, 0, 1);
--overlay-color: rgba(255, 255, 255, 0.05);
--modal-background-color: rgba(15, 13, 32, 1);
--outer-glow: 0px 0px 30px rgba(123, 91, 245, 0.37);
--border-radius: 0.75rem;
}
@ -126,6 +127,7 @@ html {
color: var(--primary-foreground-color);
border-radius: var(--border-radius);
background-color: var(--modal-background-color);
box-shadow: var(--outer-glow);
transition: opacity 0.25s ease-out;
}

View file

@ -30,4 +30,17 @@
color: var(--primary-foreground-color);
}
}
.uninstall-button {
background-color: var(--overlay-color);
&:hover {
outline: var(--focus-outline-size) solid var(--overlay-color);
background-color: transparent;
}
&:focus {
outline-color: var(--primary-foreground-color);
}
}
}

View file

@ -6,10 +6,17 @@ const classnames = require('classnames');
const { default: Icon } = require('@stremio/stremio-icons/react');
const Button = require('stremio/common/Button');
const styles = require('./styles');
const { Tooltip } = require('stremio/common/Tooltip');
const ActionButton = ({ className, icon, label, ...props }) => {
const ActionButton = ({ className, icon, label, tooltip, ...props }) => {
return (
<Button title={label} {...props} className={classnames(className, styles['action-button-container'], { 'wide': typeof label === 'string' })}>
<Button title={label} {...props} className={classnames(className, styles['action-button-container'], { 'wide': typeof label === 'string' && !tooltip })}>
{
tooltip === true ?
<Tooltip label={label} position={'top'} />
:
null
}
{
typeof icon === 'string' && icon.length > 0 ?
<div className={styles['icon-container']}>
@ -19,7 +26,7 @@ const ActionButton = ({ className, icon, label, ...props }) => {
null
}
{
typeof label === 'string' && label.length > 0 ?
!tooltip && typeof label === 'string' && label.length > 0 ?
<div className={styles['label-container']}>
<div className={styles['label']}>{label}</div>
</div>
@ -33,7 +40,8 @@ const ActionButton = ({ className, icon, label, ...props }) => {
ActionButton.propTypes = {
className: PropTypes.string,
icon: PropTypes.string,
label: PropTypes.string
label: PropTypes.string,
tooltip: PropTypes.bool
};
module.exports = ActionButton;

View file

@ -199,7 +199,8 @@ const MetaPreview = ({ className, compact, name, logo, background, runtime, rele
<ActionButton
className={styles['action-button']}
icon={inLibrary ? 'remove-from-library' : 'add-to-library'}
label={compact ? null : inLibrary ? t('REMOVE_FROM_LIB') : t('ADD_TO_LIB')}
label={inLibrary ? t('REMOVE_FROM_LIB') : t('ADD_TO_LIB')}
tooltip={compact}
tabIndex={compact ? -1 : 0}
onClick={toggleInLibrary}
/>
@ -211,9 +212,10 @@ const MetaPreview = ({ className, compact, name, logo, background, runtime, rele
<ActionButton
className={styles['action-button']}
icon={'trailer'}
label={compact ? null : t('TRAILER')}
label={t('TRAILER')}
tabIndex={compact ? -1 : 0}
href={trailerHref}
tooltip={compact}
/>
:
null
@ -221,7 +223,7 @@ const MetaPreview = ({ className, compact, name, logo, background, runtime, rele
{
typeof showHref === 'string' && compact ?
<ActionButton
className={styles['action-button']}
className={classnames(styles['action-button'], styles['show-button'])}
icon={'play'}
label={t('SHOW')}
tabIndex={compact ? -1 : 0}

View file

@ -156,9 +156,33 @@
align-self: stretch;
display: flex;
flex-direction: row;
align-items: flex-end;
max-height: 15rem;
flex-wrap: wrap;
max-height: 10rem;
padding-top: 3.5rem;
overflow: visible;
.label {
position: absolute;
top: -3rem;
left: 0;
opacity: 0;
transition: opacity 0.3s ease;
text-align: center;
color: var(--primary-foreground-color);
overflow: visible;
}
&:not(:last-child) {
margin-right: 1rem;
}
&:hover {
.label {
opacity: 0.7;
}
}
.action-button {
flex: none;
width: 4rem;
@ -174,6 +198,13 @@
&:not(:last-child) {
margin-right: 1rem;
}
&.show-button {
&:hover, &:focus {
background-color: var( --secondary-accent-color);
outline: none;
}
}
}
}
}
@ -198,16 +229,16 @@
}
}
.action-buttons-container {
flex-wrap: nowrap;
margin-top: 3rem;
overflow-x: visible;
scrollbar-width: none;
&::-webkit-scrollbar {
display: none;
.action-buttons-container {
flex-shrink: 0;
margin-top: 3rem;
overflow: visible;
scrollbar-width: none;
&::-webkit-scrollbar {
display: none;
}
}
}
}
.share-prompt {

View file

@ -19,6 +19,7 @@
padding: 0 2rem;
border-radius: var(--border-radius);
background-color: var(--modal-background-color);
box-shadow: var(--outer-glow);
.close-button-container {
position: absolute;
@ -33,11 +34,13 @@
display: block;
width: 100%;
height: 100%;
color: var(--overlay-color);
color: var(--primary-foreground-color);
opacity: 0.4;
}
&:hover, &:focus {
.icon {
opacity: 1;
color: var(--primary-foreground-color);
}
}
@ -61,6 +64,7 @@
flex: 1;
align-self: stretch;
overflow-y: auto;
padding: 2rem 0;
&:last-child {
margin-bottom: 2rem;
@ -80,7 +84,6 @@
}
}
}
.action-button {
flex: 1;
display: flex;
@ -93,8 +96,8 @@
background-color: var(--secondary-accent-color);
&:hover {
outline: var(--focus-outline-size) solid var(--secondary-accent-color);
background-color: transparent;
outline: var(--focus-outline-size) solid var(--secondary-accent-color);
}
&:focus {
@ -123,6 +126,7 @@
text-align: center;
color: var(--primary-foreground-color);
}
}
@media only screen and (max-width: @minimum) {

View file

@ -19,7 +19,7 @@ const HorizontalNavBar = React.memo(({ className, route, query, title, backButto
const [fullscreen, requestFullscreen, exitFullscreen] = useFullscreen();
const renderNavMenuLabel = React.useCallback(({ ref, className, onClick, children, }) => (
<Button ref={ref} className={classnames(className, styles['button-container'], styles['menu-button-container'])} tabIndex={-1} onClick={onClick}>
<Icon className={styles['icon']} name={'more-vertical'} />
<Icon className={styles['icon']} name={'person-outline'} />
{children}
</Button>
), []);
@ -55,7 +55,7 @@ const HorizontalNavBar = React.memo(({ className, route, query, title, backButto
{
addonsButton ?
<Button className={styles['button-container']} href={'#/addons'} title={t('ADDONS')} tabIndex={-1}>
<Icon className={styles['icon']} name={'addons'} />
<Icon className={styles['icon']} name={'addons-outline'} />
</Button>
:
null

View file

@ -71,7 +71,7 @@ const NavMenuContent = ({ onClick }) => {
<div className={styles['nav-menu-option-label']}>{ t('SETTINGS') }</div>
</Button>
<Button className={styles['nav-menu-option-container']} title={ t('ADDONS') } href={'#/addons'}>
<Icon className={styles['icon']} name={'addons'} />
<Icon className={styles['icon']} name={'addons-outline'} />
<div className={styles['nav-menu-option-label']}>{ t('ADDONS') }</div>
</Button>
<Button className={styles['nav-menu-option-container']} title={ t('PLAY_URL_MAGNET_LINK') } onClick={onPlayMagnetLinkClick}>

View file

@ -4,7 +4,7 @@ const React = require('react');
const PropTypes = require('prop-types');
const classnames = require('classnames');
const { default: Icon } = require('@stremio/stremio-icons/react');
const { Button, Image, PlayIconCircleCentered, useProfile, platform, useStreamingServer, useToast } = require('stremio/common');
const { Button, Image, useProfile, platform, useStreamingServer, useToast } = require('stremio/common');
const { useServices } = require('stremio/services');
const StreamPlaceholder = require('./StreamPlaceholder');
const styles = require('./styles');
@ -92,8 +92,13 @@ const Stream = ({ className, videoId, videoReleased, addonName, name, descriptio
<div className={styles['addon-name']}>{name || addonName}</div>
</div>
}
<div className={styles['info-container']} title={description}>{description}</div>
<PlayIconCircleCentered className={styles['play-icon']} />
{
typeof description === 'string' && description.length > 0 ?
<div className={styles['info-container']}>{description}</div>
:
null
}
<Icon className={styles['icon']} name={'play'} />
{
progress !== null && !isNaN(progress) && progress > 0 ?
<div className={styles['progress-bar-container']}>

View file

@ -3,11 +3,6 @@
@import (reference) '~@stremio/stremio-colors/less/stremio-colors.less';
@import (reference) '~stremio/common/screen-sizes.less';
:import('~stremio/common/PlayIconCircleCentered/styles.less') {
play-icon-circle-centered-background: background;
play-icon-circle-centered-icon: icon;
}
.stream-container {
display: flex;
flex-direction: row;
@ -22,21 +17,14 @@
}
&:hover, &:focus, &:global(.selected) {
.play-icon {
.icon {
opacity: 1;
.play-icon-circle-centered-background {
fill: var(--secondary-accent-color);
}
.play-icon-circle-centered-icon {
fill: var(--primary-foreground-color);
}
}
}
.thumbnail-container, .addon-name-container {
flex: none;
overflow: visible;
.thumbnail {
display: block;
@ -59,8 +47,7 @@
.addon-name {
width: 5rem;
max-height: 3.6em;
font-size: 1.1rem;
font-size: 0.9rem;
text-align: center;
color: var(--primary-foreground-color);
}
@ -68,28 +55,26 @@
.info-container {
flex: 1;
max-height: 3.6em;
padding: 0 0.5rem 0 1.5rem;
display: -webkit-box;
display: flex;
flex-direction: column;
-webkit-line-clamp: 3;
-webkit-box-orient: vertical;
color: var(--primary-foreground-color);
white-space: pre;
overflow-y: visible;
text-overflow: ellipsis;
color: var(--primary-foreground-color);
}
.play-icon {
.icon {
flex: none;
width: 3.5rem;
height: 5rem;
width: 3rem;
height: 3rem;
padding: 0.7rem;
border-radius: 50%;
opacity: 0;
.play-icon-circle-centered-background {
fill: none;
}
.play-icon-circle-centered-icon {
fill: var(--primary-foreground-color);
}
color: var(--primary-foreground-color);
background-color: var(--secondary-accent-color);
}
.progress-bar-container {
@ -103,6 +88,14 @@
}
}
@media only screen and (max-width: @small) {
.stream-container {
.info-container {
font-size: 0.9rem;
}
}
}
@media only screen and (max-width: @minimum) {
.stream-container {
.thumbnail-container, .addon-name-container {
@ -110,11 +103,5 @@
font-weight: 500;
}
}
.play-icon {
.play-icon-circle-centered-icon {
fill: var(--primary-foreground-color);
}
}
}
}

View file

@ -19,6 +19,9 @@ const StreamsList = ({ className, video, ...props }) => {
const onAddonSelected = React.useCallback((event) => {
setSelectedAddon(event.value);
}, []);
const backButtonOnClick = React.useCallback(() => {
window.history.back();
}, []);
const streamsByAddon = React.useMemo(() => {
return props.streams
.filter((streams) => streams.content.type === 'Ready')
@ -92,15 +95,30 @@ const StreamsList = ({ className, video, ...props }) => {
</div>
:
<React.Fragment>
{
Object.keys(streamsByAddon).length > 1 ?
<Multiselect
{...selectableOptions}
className={styles['select-input-container']}
/>
:
null
}
<div className={styles['select-choices-wrapper']}>
{
video ?
<React.Fragment>
<Button className={classnames(styles['button-container'], styles['back-button-container'])} tabIndex={-1} onClick={backButtonOnClick}>
<Icon className={styles['icon']} name={'chevron-back'} />
</Button>
<div className={styles['episode-title']}>
{`S${video?.season}E${video?.episode} ${(video?.title)}`}
</div>
</React.Fragment>
:
null
}
{
Object.keys(streamsByAddon).length > 1 ?
<Multiselect
{...selectableOptions}
className={styles['select-input-container']}
/>
:
null
}
</div>
<div className={styles['streams-container']}>
{filteredStreams.map((stream, index) => (
<Stream

View file

@ -41,26 +41,70 @@
}
}
.select-input-container {
flex: 0 0 auto;
.select-choices-wrapper {
display: flex;
align-items: center;
z-index: 1;
margin: 1em 1em 0 1em;
background: none;
gap: 0 0.5em;
overflow: visible;
&:hover, &:focus, &:global(.active) {
background-color: var(--overlay-color);
.back-button-container {
display: flex;
justify-content: center;
align-items: center;
border-radius: 0.5em;
padding: 1em;
max-height: 3em;
.icon {
width: 1.5em;
height: 1.5em;
color: var(--primary-foreground-color);
opacity: 0.6;
}
&:hover, &:global(.active) {
background-color: var(--overlay-color);
opacity: 1;
.icon {
color: var(--primary-foreground-color);
opacity: 0.8;
}
}
}
& >.multiselect-label {
.episode-title {
min-width: 45%;
color: var(--primary-foreground-color);
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
& >.multiselect-icon {
color: var(--primary-foreground-color);
}
.select-input-container {
min-width: 40%;
flex: 0 0 auto;
flex-grow: 1;
background: none;
.multiselect-menu-container {
max-height: calc(3.2rem * 7);
overflow: auto;
&:hover, &:focus, &:global(.active) {
background-color: var(--overlay-color);
}
& >.multiselect-label {
color: var(--primary-foreground-color);
}
& >.multiselect-icon {
color: var(--primary-foreground-color);
}
.multiselect-menu-container {
max-height: calc(3.2rem * 7);
overflow: auto;
}
}
}
@ -83,6 +127,7 @@
padding: 1.5rem 1rem;
border-radius: var(--border-radius);
background-color: var(--secondary-accent-color);
border-radius: 3rem;
&:hover {
outline: var(--focus-outline-size) solid var(--secondary-accent-color);

View file

@ -84,8 +84,8 @@
font-weight: 500;
white-space: nowrap;
text-overflow: ellipsis;
text-transform: uppercase;
color: var(--primary-foreground-color);
opacity: 0.44;
}
.upcoming-watched-container {

View file

@ -7,7 +7,19 @@
.buffering-loader {
flex: none;
width: 10rem;
height: 10rem;
max-width: 15rem;
max-height: 15rem;
animation: fadeInOut 2s infinite;
display: block;
width: auto;
height: auto;
}
}
}
@keyframes fadeInOut {
0% { opacity: 0.2; }
50% { opacity: 1; }
100% { opacity: 0.2; }
}

View file

@ -634,7 +634,7 @@ const Player = ({ urlParams, queryParams }) => {
/>
{
videoState.buffering ?
<BufferingLoader className={styles['layer']} />
<BufferingLoader className={styles['layer']} logo={player?.metaItem?.content?.logo} />
:
null
}

View file

@ -49,14 +49,27 @@ const Search = ({ queryParams }) => {
<div ref={scrollContainerRef} className={styles['search-content']} onScroll={onScroll}>
{
query === null ?
<div className={classnames(styles['search-hints-container'], 'animation-fade-in')}>
<div className={styles['search-hint-container']}>
<Icon className={styles['icon']} name={'movies'} />
<div className={styles['label']}>{ t('SEARCH_EXPLANATION_CONTENT') }</div>
<div className={classnames(styles['search-hints-wrapper'])}>
<div className={classnames(styles['search-hints-title-container'], 'animation-fade-in')}>
<div className={styles['search-hints-title']}>{t('SEARCH_ANYTHING')}</div>
</div>
<div className={styles['search-hint-container']}>
<Icon className={styles['icon']} name={'actors'} />
<div className={styles['label']}>{ t('SEARCH_EXPLANATION_PEOPLE') }</div>
<div className={classnames(styles['search-hints-container'], 'animation-fade-in')}>
<div className={styles['search-hint-container']}>
<Icon className={styles['icon']} name={'trailer'} />
<div className={styles['label']}>{t('SEARCH_CATEGORIES')}</div>
</div>
<div className={styles['search-hint-container']}>
<Icon className={styles['icon']} name={'actors'} />
<div className={styles['label']}>{t('SEARCH_PERSONS')}</div>
</div>
<div className={styles['search-hint-container']}>
<Icon className={styles['icon']} name={'link'} />
<div className={styles['label']}>{t('SEARCH_PROTOCOLS')}</div>
</div>
<div className={styles['search-hint-container']}>
<Icon className={styles['icon']} name={'imdb-outline'} />
<div className={styles['label']}>{t('SEARCH_TYPES')}</div>
</div>
</div>
</div>
:

View file

@ -25,41 +25,71 @@
margin: 4rem 2rem;
}
.search-hints-container {
.search-hints-wrapper {
display: flex;
flex-direction: row;
align-items: flex-start;
justify-content: flex-start;
align-content: flex-start;
flex-wrap: wrap;
padding: 4rem;
align-items: center;
justify-content: center;
flex-direction: column;
width: 100%;
height: 100%;
.search-hint-container {
flex: 0 0 50%;
.search-hints-title-container {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 0 2rem;
margin-bottom: 4rem;
align-items: center;
padding: 0 1rem;
.icon {
flex: none;
width: 6rem;
height: 6rem;
margin-bottom: 2rem;
color: @color-surface-light5-90;
}
.label {
.search-hints-title {
flex-grow: 0;
flex-shrink: 1;
flex-basis: auto;
font-size: 1.2rem;
font-size: 1.6rem;
color: @color-surface-light5-90;
text-align: center;
opacity: 0.4;
}
}
.search-hints-container {
display: flex;
flex-direction: row;
align-items: flex-start;
justify-content: center;
align-content: flex-start;
flex-wrap: wrap;
padding: 4rem;
max-width: 50%;
margin: 0 auto;
.search-hint-container {
flex: 0 0 25%;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 0 1rem;
margin-bottom: 4rem;
.icon {
flex: none;
width: 4rem;
height: 4rem;
margin-bottom: 2rem;
color: @color-surface-light5-90;
opacity: 0.4;
}
.label {
flex-grow: 0;
flex-shrink: 1;
flex-basis: auto;
font-size: 1.2rem;
color: @color-surface-light5-90;
text-align: center;
opacity: 0.4;
}
}
}
}
.message-container {
@ -135,6 +165,11 @@
@media only screen and (max-width: @medium) {
.search-container {
.search-content {
.search-hints-wrapper {
.search-hints-container {
max-width: 70%;
}
}
.search-row-poster, .search-row-square {
.meta-item, .meta-item-placeholder {
&:nth-child(n+8) {
@ -157,6 +192,11 @@
@media only screen and (max-width: @small) {
.search-container {
.search-content {
.search-hints-wrapper {
.search-hints-container {
max-width: 90%;
}
}
.search-row-poster, .search-row-square {
.meta-item, .meta-item-placeholder {
&:nth-child(n+7) {
@ -201,6 +241,14 @@
@media only screen and (max-width: @xxsmall) {
.search-container {
.search-content {
.search-hints-wrapper {
.search-hints-container {
max-width: 100%;
.search-hint-container {
flex: 0 0 50%;
}
}
}
.search-row-poster, .search-row-square {
.meta-item, .meta-item-placeholder {
&:nth-child(n+5) {
@ -235,11 +283,13 @@
}
}
.search-hints-container {
padding: 4rem 2rem;
.search-hint-container {
padding: 0 1.5rem;
.search-hints-wrapper {
margin-top: 4rem;
.search-hints-container {
padding: 4rem 2rem;
.search-hint-container {
padding: 0 1.5rem;
}
}
}
}