Merge branch 'development' of https://github.com/Stremio/stremio-web into feat/player-local-subtitles

This commit is contained in:
Tim 2025-01-20 10:21:07 +01:00
commit 00dedd8c73
14 changed files with 219 additions and 54 deletions

View file

@ -14,9 +14,19 @@
}
// iOS pads the bottom inset more than needed, so we deduce the actual inset size when using the webapp
@calculated-bottom-safe-inset: ~"min(env(safe-area-inset-bottom, 0rem), max(1rem, calc(100lvh - 100svh - env(safe-area-inset-top, 0rem))))";
@html-width: ~"calc(max(100svw, 100dvw))";
@html-height: ~"calc(max(100svh, 100dvh))";
@calculated-bottom-safe-inset: ~"min(env(safe-area-inset-bottom, 0rem), max(1rem, calc(var(--viewport-height-diff) - env(safe-area-inset-top, 0rem))))";
// Viewport sizes
@viewport-width: ~"100vw";
@viewport-height: ~"100vh";
// HTML sizes
@html-width: ~"calc(max(var(--small-viewport-width), var(--dynamic-viewport-width)))";
@html-height: ~"calc(max(var(--small-viewport-height), var(--dynamic-viewport-height)))";
@html-standalone-width: ~"calc(max(100%, var(--large-viewport-width)))";
@html-standalone-height: ~"calc(max(100%, var(--large-viewport-height)))";
// Safe area insets
@safe-area-inset-top: env(safe-area-inset-top, 0rem);
@safe-area-inset-right: env(safe-area-inset-right, 0rem);
@safe-area-inset-bottom: env(safe-area-inset-bottom, 0rem);
@ -63,6 +73,32 @@
--safe-area-inset-right: @safe-area-inset-right;
--safe-area-inset-bottom: @safe-area-inset-bottom;
--safe-area-inset-left: @safe-area-inset-left;
--dynamic-viewport-width: @viewport-width;
--dynamic-viewport-height: @viewport-height;
--large-viewport-width: @viewport-width;
--large-viewport-height: @viewport-height;
--small-viewport-width: @viewport-width;
--small-viewport-height: @viewport-height;
--viewport-height-diff: calc(100vh - 100vh);
@supports (height: 100dvh) {
--dynamic-viewport-width: 100dvw;
--dynamic-viewport-height: 100dvh;
}
@supports (height: 100lvh) {
--large-viewport-width: 100lvw;
--large-viewport-height: 100lvh;
}
@supports (height: 100svh) {
--small-viewport-width: 100svw;
--small-viewport-height: 100svh;
}
@supports (height: 100lvh) and (height: 100svh) {
--viewport-height-diff: calc(100lvh - 100svh);
}
}
* {
@ -110,8 +146,6 @@ svg {
html {
width: @html-width;
height: @html-height;
min-width: 640px;
min-height: 480px;
font-family: 'PlusJakartaSans', 'sans-serif';
overflow: auto;
overscroll-behavior: none;
@ -119,6 +153,12 @@ html {
touch-action: manipulation;
-webkit-tap-highlight-color: transparent;
@media (display-mode: standalone) {
width: @html-standalone-width;
height: @html-standalone-height;
}
body {
width: 100%;
height: 100%;
@ -216,9 +256,6 @@ html {
@media only screen and (max-width: @xsmall) {
html {
min-width: inherit;
min-height: inherit;
body {
:global(#app) {
.toasts-container {

View file

@ -6,13 +6,29 @@
}
animation-timing-function: ease-in-out;
animation-duration: 100ms;
animation-duration: 350ms;
@media (prefers-reduced-motion) {
:local {
animation-name: fade-in-no-motion;
}
}
}
@keyframes fade-in {
0% {
opacity: 0;
transform: translateY(4px);
}
40% {
opacity: 0;
transform: translateY(4px);
}
70% {
opacity: 0.6;
transform: translateY(0.2vh);
transform: translateY(2px);
}
100% {
@ -51,4 +67,22 @@
.slide-left-exit {
transform: translateX(100%);
}
@keyframes fade-in-no-motion {
0% {
opacity: 0;
}
40% {
opacity: 0;
}
70% {
opacity: 0.6;
}
100% {
opacity: 1;
}
}

View file

@ -9,4 +9,17 @@
@large: 2200px;
@xlarge: 2500px;
@xxlarge: 2800px;
@xxxlarge: 3800px;
@xxxlarge: 3800px;
@small-phone-landscape-size: 400px;
@small-phone-portrait-size: 700px;
@phone-landscape-size: 500px;
@phone-portrait-size: 1000px;
@small-phone-landscape: ~"screen and (max-width: @{small-phone-portrait-size}) and (max-height: @{small-phone-landscape-size}) and (orientation: landscape)";
@small-phone-portrait: ~"screen and (max-width: @{small-phone-landscape-size}) and (max-height: @{small-phone-portrait-size}) and (orientation: portrait)";
@phone-landscape: ~"screen and (max-width: @{phone-portrait-size}) and (max-height: @{phone-landscape-size}) and (orientation: landscape)";
@phone-portrait: ~"screen and (max-width: @{phone-portrait-size}) and (max-height: @{phone-portrait-size}) and (orientation: portrait)";

View file

@ -51,6 +51,14 @@
}
}
@media @phone-landscape {
.action-button-container {
.label-container {
display: none;
}
}
}
@media only screen and (max-width: @minimum) {
.action-button-container {
flex-direction: row;

View file

@ -213,6 +213,30 @@
width: 30rem;
}
@media @phone-landscape {
.meta-preview-container {
.meta-info-container {
.logo {
height: 5rem;
margin-bottom: 1rem;
}
}
.action-buttons-container {
padding-top: 1.5rem;
gap: 0.5rem;
.action-button {
padding: 0 1.5rem !important;
margin-right: 0rem !important;
height: 3rem;
border-radius: 2rem;
}
}
}
}
@media only screen and (max-width: @minimum) {
.meta-preview-container {
.meta-info-container {
@ -229,16 +253,16 @@
}
}
.action-buttons-container {
flex-shrink: 0;
margin-top: 3rem;
overflow: 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

@ -7,7 +7,8 @@ const debounce = require('lodash.debounce');
const { useTranslation } = require('react-i18next');
const { default: Icon } = require('@stremio/stremio-icons/react');
const { useRouteFocused } = require('stremio-router');
const { Button, TextInput } = require('stremio/components');
const Button = require('stremio/components/Button').default;
const TextInput = require('stremio/components/TextInput').default;
const useTorrent = require('stremio/common/useTorrent');
const { withCoreSuspender } = require('stremio/common/CoreSuspender');
const useSearchHistory = require('./useSearchHistory');

View file

@ -69,7 +69,7 @@
width: 3.5rem;
height: 3.5rem;
border-radius: 0.75rem;
opacity: 0.4;
opacity: 0.6;
.icon {
flex: none;
@ -79,7 +79,7 @@
opacity: 0.6;
}
&:hover, &:global(.active) {
.active() {
background-color: var(--overlay-color);
opacity: 1;
@ -88,6 +88,16 @@
opacity: 0.8;
}
}
&:global(.active) {
.active();
}
@media (pointer: fine) {
&:hover {
.active();
}
}
}
}

View file

@ -18,12 +18,21 @@
.label {
opacity: 0.6;
}
&:global(.selected) {
.label {
opacity: 1;
}
}
}
}
&:global(.selected) {
.icon {
opacity: 1;
}
.icon, .label {
color: var(--primary-accent-color);
}
}
@ -37,7 +46,7 @@
.icon {
color: var(--primary-foreground-color);
opacity: 0.2;
opacity: 0.35;
}
.label {
@ -61,12 +70,12 @@
@media only screen and (max-width: @minimum) {
.nav-tab-button-container {
.label {
opacity: 0.2;
opacity: 0.6;
}
&:global(.selected) {
.label {
opacity: 0.6;
opacity: 1;
}
}
}

View file

@ -14,7 +14,7 @@
gap: 0.5rem;
width: 100%;
height: 100%;
padding: 0 0 2rem 2rem;
padding: 0 0 calc(1.5rem + var(--safe-area-inset-bottom)) 2rem;
.main {
flex: auto;
@ -37,7 +37,7 @@
@media only screen and (max-width: @small) and (orientation: landscape) {
.calendar {
.content {
padding: 0 0 0 1rem;
padding: 0 0 calc(1.5rem + var(--safe-area-inset-bottom)) 1rem;
}
}
}

View file

@ -11,6 +11,7 @@ import Placeholder from './Placeholder';
import useCalendar from './useCalendar';
import useCalendarDate from './useCalendarDate';
import styles from './Calendar.less';
import classNames from 'classnames';
type Props = {
urlParams: UrlParams,
@ -34,7 +35,7 @@ const Calendar = ({ urlParams }: Props) => {
<MainNavBars className={styles['calendar']} route={'calendar'}>
{
profile.auth !== null ?
<div className={styles['content']}>
<div className={classNames(styles['content'], 'animation-fade-in')}>
<div className={styles['main']}>
<Selector
selected={calendar.selected}

View file

@ -55,7 +55,7 @@
display: flex;
flex-direction: row;
gap: 1rem;
padding: 0 1rem 1rem 1rem;
padding: 0 0.5rem 0.5rem 0.5rem;
.item {
flex: none;
@ -134,7 +134,7 @@
}
}
@media only screen and (orientation: portrait) {
@media only screen and (max-height: @minimum) and (orientation: portrait) {
.cell {
.heading {
justify-content: center;
@ -150,23 +150,11 @@
}
}
@media only screen and (max-width: @small) and (orientation: landscape) {
@media only screen and (max-height: @xxsmall) and (orientation: landscape) {
.cell {
flex-direction: row;
align-items: center;
.items {
display: none;
}
.more {
display: flex;
}
}
}
@media only screen and (max-height: @xxsmall) and (orientation: landscape) {
.cell {
.items {
display: none;
}
@ -176,3 +164,26 @@
}
}
}
@media only screen and (max-height: @xsmall) and (max-width: @xsmall) {
.cell {
gap: 0;
.heading {
height: 2rem;
.day {
font-size: 0.875rem;
}
}
.items {
padding: 0.25rem;
.item {
pointer-events: none;
border-radius: calc(var(--border-radius) / 2);
}
}
}
}

View file

@ -1,6 +1,6 @@
// Copyright (C) 2017-2024 Smart code 203358507
import React, { useMemo } from 'react';
import React, { useCallback, useMemo, MouseEvent } from 'react';
import Icon from '@stremio/stremio-icons/react';
import classNames from 'classnames';
import { Button, HorizontalScroll, Image } from 'stremio/components';
@ -24,6 +24,10 @@ const Cell = ({ selected, monthInfo, date, items, onClick }: Props) => {
onClick && onClick(date);
};
const onPosterClick = useCallback((event: MouseEvent<HTMLDivElement>) => {
event.stopPropagation();
}, []);
return (
<Button
className={classNames(styles['cell'], { [styles['active']]: active, [styles['today']]: today })}
@ -37,7 +41,7 @@ const Cell = ({ selected, monthInfo, date, items, onClick }: Props) => {
<HorizontalScroll className={styles['items']}>
{
items.map(({ id, name, poster, deepLinks }) => (
<Button key={id} className={styles['item']} href={deepLinks.metaDetailsStreams} tabIndex={-1}>
<Button key={id} className={styles['item']} href={deepLinks.metaDetailsStreams} tabIndex={-1} onClick={onPosterClick}>
<Icon className={styles['icon']} name={'play'} />
<Image
className={styles['poster']}

View file

@ -125,6 +125,16 @@
}
}
@media @phone-landscape {
.metadetails-container {
.metadetails-content {
.meta-preview {
padding: 0 1.5rem 1rem;
}
}
}
}
@media only screen and (max-width: @minimum) {
.metadetails-container {
.metadetails-content {

View file

@ -97,14 +97,17 @@ const Search = ({ queryParams }) => {
);
}
case 'Err': {
return (
<MetaRow
key={index}
className={classnames(styles['search-row'], 'animation-fade-in')}
catalog={catalog}
message={catalog.content.content}
/>
);
if (catalog.content.content !== 'EmptyContent') {
return (
<MetaRow
key={index}
className={classnames(styles['search-row'], 'animation-fade-in')}
catalog={catalog}
message={catalog.content.content}
/>
);
}
return null;
}
default: {
return (