mirror of
https://github.com/Stremio/stremio-web.git
synced 2026-04-21 07:32:02 +00:00
Merge branch 'development' of github.com:Stremio/stremio-web into mobile
This commit is contained in:
commit
211f478e8a
12 changed files with 121 additions and 34 deletions
6
package-lock.json
generated
6
package-lock.json
generated
|
|
@ -1833,9 +1833,9 @@
|
||||||
"integrity": "sha512-yT3No1gIWKLV2BhQIeSgG94EzXxmEqXJLulO+pFpziqWNUbmmEKeE+nRvW5wtoIK4SLy+v0bLd0b6HBH3KFfWw=="
|
"integrity": "sha512-yT3No1gIWKLV2BhQIeSgG94EzXxmEqXJLulO+pFpziqWNUbmmEKeE+nRvW5wtoIK4SLy+v0bLd0b6HBH3KFfWw=="
|
||||||
},
|
},
|
||||||
"@stremio/stremio-core-web": {
|
"@stremio/stremio-core-web": {
|
||||||
"version": "0.35.0",
|
"version": "0.36.0",
|
||||||
"resolved": "https://registry.npmjs.org/@stremio/stremio-core-web/-/stremio-core-web-0.35.0.tgz",
|
"resolved": "https://registry.npmjs.org/@stremio/stremio-core-web/-/stremio-core-web-0.36.0.tgz",
|
||||||
"integrity": "sha512-MN5Mb+5yYV5MQXjzeShqJJeRXc4QLoLP/6TbE6ay6kq8PQ8bT7BreFiFVJunICVo/OmXjYNS3CiJhM5q1J4xZw==",
|
"integrity": "sha512-A63yB+Pml/c6jtK4l5r7aacZ2Jm+ofaj60MfVCZfzSE4csBPMuoROYx6TM6nI6NU8iUDTcIU+OYoWcMfaT7v7A==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"@babel/runtime": "7.15.4"
|
"@babel/runtime": "7.15.4"
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,7 @@
|
||||||
"@babel/runtime": "7.16.0",
|
"@babel/runtime": "7.16.0",
|
||||||
"@sentry/browser": "6.13.3",
|
"@sentry/browser": "6.13.3",
|
||||||
"@stremio/stremio-colors": "4.0.1",
|
"@stremio/stremio-colors": "4.0.1",
|
||||||
"@stremio/stremio-core-web": "0.35.0",
|
"@stremio/stremio-core-web": "0.36.0",
|
||||||
"@stremio/stremio-icons": "3.0.5",
|
"@stremio/stremio-icons": "3.0.5",
|
||||||
"@stremio/stremio-video": "0.0.19-rc.1",
|
"@stremio/stremio-video": "0.0.19-rc.1",
|
||||||
"a-color-picker": "1.2.1",
|
"a-color-picker": "1.2.1",
|
||||||
|
|
|
||||||
30
src/common/getVisibleChildrenRange.js
Normal file
30
src/common/getVisibleChildrenRange.js
Normal file
|
|
@ -0,0 +1,30 @@
|
||||||
|
// Copyright (C) 2017-2022 Smart code 203358507
|
||||||
|
|
||||||
|
const isChildVisible = (container, element, threshold) => {
|
||||||
|
const elementTop = element.offsetTop;
|
||||||
|
const elementBottom = element.offsetTop + element.clientHeight;
|
||||||
|
const containerTop = container.scrollTop - threshold;
|
||||||
|
const containerBottom = container.scrollTop + container.clientHeight + threshold;
|
||||||
|
return (elementTop >= containerTop && elementBottom <= containerBottom) ||
|
||||||
|
(elementTop < containerTop && containerTop < elementBottom) ||
|
||||||
|
(elementTop < containerBottom && containerBottom < elementBottom);
|
||||||
|
};
|
||||||
|
|
||||||
|
const getVisibleChildrenRange = (container, threshold) => {
|
||||||
|
return Array.from(container.children).reduce((result, child, index) => {
|
||||||
|
if (isChildVisible(container, child, threshold)) {
|
||||||
|
if (result === null) {
|
||||||
|
result = {
|
||||||
|
start: index,
|
||||||
|
end: index
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
result.end = index;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}, null);
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = getVisibleChildrenRange;
|
||||||
|
|
@ -24,6 +24,7 @@ const TextInput = require('./TextInput');
|
||||||
const { ToastProvider, useToast } = require('./Toast');
|
const { ToastProvider, useToast } = require('./Toast');
|
||||||
const comparatorWithPriorities = require('./comparatorWithPriorities');
|
const comparatorWithPriorities = require('./comparatorWithPriorities');
|
||||||
const CONSTANTS = require('./CONSTANTS');
|
const CONSTANTS = require('./CONSTANTS');
|
||||||
|
const getVisibleChildrenRange = require('./getVisibleChildrenRange');
|
||||||
const languageNames = require('./languageNames');
|
const languageNames = require('./languageNames');
|
||||||
const routesRegexp = require('./routesRegexp');
|
const routesRegexp = require('./routesRegexp');
|
||||||
const sanitizeLocationPath = require('./sanitizeLocationPath');
|
const sanitizeLocationPath = require('./sanitizeLocationPath');
|
||||||
|
|
@ -65,6 +66,7 @@ module.exports = {
|
||||||
useToast,
|
useToast,
|
||||||
comparatorWithPriorities,
|
comparatorWithPriorities,
|
||||||
CONSTANTS,
|
CONSTANTS,
|
||||||
|
getVisibleChildrenRange,
|
||||||
languageNames,
|
languageNames,
|
||||||
routesRegexp,
|
routesRegexp,
|
||||||
sanitizeLocationPath,
|
sanitizeLocationPath,
|
||||||
|
|
|
||||||
|
|
@ -2,20 +2,43 @@
|
||||||
|
|
||||||
const React = require('react');
|
const React = require('react');
|
||||||
const classnames = require('classnames');
|
const classnames = require('classnames');
|
||||||
const { MainNavBars, MetaRow, LibItem, MetaItem, StreamingServerWarning, useProfile, useStreamingServer } = require('stremio/common');
|
const debounce = require('lodash.debounce');
|
||||||
|
const { MainNavBars, MetaRow, LibItem, MetaItem, StreamingServerWarning, useProfile, useStreamingServer, getVisibleChildrenRange } = require('stremio/common');
|
||||||
const useBoard = require('./useBoard');
|
const useBoard = require('./useBoard');
|
||||||
const useContinueWatchingPreview = require('./useContinueWatchingPreview');
|
const useContinueWatchingPreview = require('./useContinueWatchingPreview');
|
||||||
const styles = require('./styles');
|
const styles = require('./styles');
|
||||||
|
|
||||||
|
const THRESHOLD = 300;
|
||||||
|
|
||||||
const Board = () => {
|
const Board = () => {
|
||||||
const board = useBoard();
|
|
||||||
const profile = useProfile();
|
const profile = useProfile();
|
||||||
const streamingServer = useStreamingServer();
|
const streamingServer = useStreamingServer();
|
||||||
const continueWatchingPreview = useContinueWatchingPreview();
|
const continueWatchingPreview = useContinueWatchingPreview();
|
||||||
|
const [board, loadBoardRows] = useBoard();
|
||||||
|
const boardCatalogsOffset = continueWatchingPreview.libraryItems.length > 0 ? 1 : 0;
|
||||||
|
const scrollContainerRef = React.useRef();
|
||||||
|
const onVisibleRangeChange = React.useCallback(() => {
|
||||||
|
const range = getVisibleChildrenRange(scrollContainerRef.current, THRESHOLD);
|
||||||
|
if (range === null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const start = Math.max(0, range.start - boardCatalogsOffset);
|
||||||
|
const end = range.end - boardCatalogsOffset;
|
||||||
|
if (end < start) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
loadBoardRows({ start, end });
|
||||||
|
}, [boardCatalogsOffset]);
|
||||||
|
const onScroll = React.useCallback(debounce(onVisibleRangeChange, 250), [onVisibleRangeChange]);
|
||||||
|
React.useLayoutEffect(() => {
|
||||||
|
onVisibleRangeChange();
|
||||||
|
}, [board.catalogs, onVisibleRangeChange]);
|
||||||
return (
|
return (
|
||||||
<div className={styles['board-container']}>
|
<div className={styles['board-container']}>
|
||||||
<MainNavBars className={styles['board-content-container']} route={'board'}>
|
<MainNavBars className={styles['board-content-container']} route={'board'}>
|
||||||
<div className={styles['board-content']}>
|
<div ref={scrollContainerRef} className={styles['board-content']} onScroll={onScroll}>
|
||||||
{
|
{
|
||||||
continueWatchingPreview.libraryItems.length > 0 ?
|
continueWatchingPreview.libraryItems.length > 0 ?
|
||||||
<MetaRow
|
<MetaRow
|
||||||
|
|
@ -29,7 +52,7 @@ const Board = () => {
|
||||||
null
|
null
|
||||||
}
|
}
|
||||||
{board.catalogs.map((catalog, index) => {
|
{board.catalogs.map((catalog, index) => {
|
||||||
switch (catalog.content.type) {
|
switch (catalog.content?.type) {
|
||||||
case 'Ready': {
|
case 'Ready': {
|
||||||
return (
|
return (
|
||||||
<MetaRow
|
<MetaRow
|
||||||
|
|
@ -53,7 +76,7 @@ const Board = () => {
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
case 'Loading': {
|
default: {
|
||||||
return (
|
return (
|
||||||
<MetaRow.Placeholder
|
<MetaRow.Placeholder
|
||||||
key={index}
|
key={index}
|
||||||
|
|
|
||||||
|
|
@ -28,15 +28,7 @@
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
|
|
||||||
.board-row {
|
.board-row {
|
||||||
margin: 4rem 2rem;
|
padding: 2rem;
|
||||||
|
|
||||||
&:first-child {
|
|
||||||
margin-top: 2rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
&:last-child {
|
|
||||||
margin-bottom: 2rem;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
// Copyright (C) 2017-2022 Smart code 203358507
|
// Copyright (C) 2017-2022 Smart code 203358507
|
||||||
|
|
||||||
const React = require('react');
|
const React = require('react');
|
||||||
|
const { useServices } = require('stremio/services');
|
||||||
const { useModelState } = require('stremio/common');
|
const { useModelState } = require('stremio/common');
|
||||||
|
|
||||||
const init = () => ({
|
const init = () => ({
|
||||||
|
|
@ -9,6 +10,7 @@ const init = () => ({
|
||||||
});
|
});
|
||||||
|
|
||||||
const useBoard = () => {
|
const useBoard = () => {
|
||||||
|
const { core } = useServices();
|
||||||
const action = React.useMemo(() => ({
|
const action = React.useMemo(() => ({
|
||||||
action: 'Load',
|
action: 'Load',
|
||||||
args: {
|
args: {
|
||||||
|
|
@ -16,7 +18,17 @@ const useBoard = () => {
|
||||||
args: { extra: [] }
|
args: { extra: [] }
|
||||||
}
|
}
|
||||||
}), []);
|
}), []);
|
||||||
return useModelState({ model: 'board', timeout: 1500, action, init });
|
const loadRange = React.useCallback((range) => {
|
||||||
|
core.transport.dispatch({
|
||||||
|
action: 'CatalogsWithExtra',
|
||||||
|
args: {
|
||||||
|
action: 'LoadRange',
|
||||||
|
args: range
|
||||||
|
}
|
||||||
|
}, 'board');
|
||||||
|
}, []);
|
||||||
|
const board = useModelState({ model: 'board', timeout: 1500, action, init });
|
||||||
|
return [board, loadRange];
|
||||||
};
|
};
|
||||||
|
|
||||||
module.exports = useBoard;
|
module.exports = useBoard;
|
||||||
|
|
|
||||||
|
|
@ -3,13 +3,16 @@
|
||||||
const React = require('react');
|
const React = require('react');
|
||||||
const PropTypes = require('prop-types');
|
const PropTypes = require('prop-types');
|
||||||
const classnames = require('classnames');
|
const classnames = require('classnames');
|
||||||
|
const debounce = require('lodash.debounce');
|
||||||
const Icon = require('@stremio/stremio-icons/dom');
|
const Icon = require('@stremio/stremio-icons/dom');
|
||||||
const { Image, MainNavBars, MetaRow, MetaItem, useDeepEqualMemo } = require('stremio/common');
|
const { Image, MainNavBars, MetaRow, MetaItem, useDeepEqualMemo, getVisibleChildrenRange } = require('stremio/common');
|
||||||
const useSearch = require('./useSearch');
|
const useSearch = require('./useSearch');
|
||||||
const styles = require('./styles');
|
const styles = require('./styles');
|
||||||
|
|
||||||
|
const THRESHOLD = 100;
|
||||||
|
|
||||||
const Search = ({ queryParams }) => {
|
const Search = ({ queryParams }) => {
|
||||||
const search = useSearch(queryParams);
|
const [search, loadSearchRows] = useSearch(queryParams);
|
||||||
const query = useDeepEqualMemo(() => {
|
const query = useDeepEqualMemo(() => {
|
||||||
return search.selected !== null ?
|
return search.selected !== null ?
|
||||||
search.selected.extra.reduceRight((query, [name, value]) => {
|
search.selected.extra.reduceRight((query, [name, value]) => {
|
||||||
|
|
@ -22,9 +25,26 @@ const Search = ({ queryParams }) => {
|
||||||
:
|
:
|
||||||
null;
|
null;
|
||||||
}, [search.selected]);
|
}, [search.selected]);
|
||||||
|
const scrollContainerRef = React.useRef();
|
||||||
|
const onVisibleRangeChange = React.useCallback(() => {
|
||||||
|
if (search.catalogs.length === 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const range = getVisibleChildrenRange(scrollContainerRef.current, THRESHOLD);
|
||||||
|
if (range === null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
loadSearchRows(range);
|
||||||
|
}, [search.catalogs]);
|
||||||
|
const onScroll = React.useCallback(debounce(onVisibleRangeChange, 250), [onVisibleRangeChange]);
|
||||||
|
React.useLayoutEffect(() => {
|
||||||
|
onVisibleRangeChange();
|
||||||
|
}, [search.catalogs, onVisibleRangeChange]);
|
||||||
return (
|
return (
|
||||||
<MainNavBars className={styles['search-container']} route={'search'} query={query}>
|
<MainNavBars className={styles['search-container']} route={'search'} query={query}>
|
||||||
<div className={styles['search-content']}>
|
<div ref={scrollContainerRef} className={styles['search-content']} onScroll={onScroll}>
|
||||||
{
|
{
|
||||||
query === null ?
|
query === null ?
|
||||||
<div className={styles['search-hints-container']}>
|
<div className={styles['search-hints-container']}>
|
||||||
|
|
@ -49,7 +69,7 @@ const Search = ({ queryParams }) => {
|
||||||
</div>
|
</div>
|
||||||
:
|
:
|
||||||
search.catalogs.map((catalog, index) => {
|
search.catalogs.map((catalog, index) => {
|
||||||
switch (catalog.content.type) {
|
switch (catalog.content?.type) {
|
||||||
case 'Ready': {
|
case 'Ready': {
|
||||||
return (
|
return (
|
||||||
<MetaRow
|
<MetaRow
|
||||||
|
|
@ -73,7 +93,7 @@ const Search = ({ queryParams }) => {
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
case 'Loading': {
|
default: {
|
||||||
return (
|
return (
|
||||||
<MetaRow.Placeholder
|
<MetaRow.Placeholder
|
||||||
key={index}
|
key={index}
|
||||||
|
|
|
||||||
|
|
@ -22,15 +22,7 @@
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
|
|
||||||
.search-row {
|
.search-row {
|
||||||
margin: 4rem 2rem;
|
padding: 2rem;
|
||||||
|
|
||||||
&:first-child {
|
|
||||||
margin-top: 2rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
&:last-child {
|
|
||||||
margin-bottom: 2rem;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.search-hints-container {
|
.search-hints-container {
|
||||||
|
|
|
||||||
|
|
@ -54,7 +54,17 @@ const useSearch = (queryParams) => {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}, [queryParams]);
|
}, [queryParams]);
|
||||||
return useModelState({ model: 'search', action, init });
|
const loadRange = React.useCallback((range) => {
|
||||||
|
core.transport.dispatch({
|
||||||
|
action: 'CatalogsWithExtra',
|
||||||
|
args: {
|
||||||
|
action: 'LoadRange',
|
||||||
|
args: range
|
||||||
|
}
|
||||||
|
}, 'search');
|
||||||
|
}, []);
|
||||||
|
const search = useModelState({ model: 'search', action, init });
|
||||||
|
return [search, loadRange];
|
||||||
};
|
};
|
||||||
|
|
||||||
module.exports = useSearch;
|
module.exports = useSearch;
|
||||||
|
|
|
||||||
|
|
@ -106,6 +106,9 @@ function ChromecastTransport() {
|
||||||
this.off = function(name, listener) {
|
this.off = function(name, listener) {
|
||||||
events.off(name, listener);
|
events.off(name, listener);
|
||||||
};
|
};
|
||||||
|
this.removeAllListeners = function() {
|
||||||
|
events.removeAllListeners();
|
||||||
|
};
|
||||||
this.getCastState = function() {
|
this.getCastState = function() {
|
||||||
return cast.framework.CastContext.getInstance().getCastState();
|
return cast.framework.CastContext.getInstance().getCastState();
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -31,6 +31,9 @@ function CoreTransport() {
|
||||||
this.off = function(name, listener) {
|
this.off = function(name, listener) {
|
||||||
events.off(name, listener);
|
events.off(name, listener);
|
||||||
};
|
};
|
||||||
|
this.removeAllListeners = function() {
|
||||||
|
events.removeAllListeners();
|
||||||
|
};
|
||||||
this.getState = function(field) {
|
this.getState = function(field) {
|
||||||
return get_state(field);
|
return get_state(field);
|
||||||
};
|
};
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue