diff --git a/package-lock.json b/package-lock.json index 0986d2e18..bca5effec 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1833,9 +1833,9 @@ "integrity": "sha512-yT3No1gIWKLV2BhQIeSgG94EzXxmEqXJLulO+pFpziqWNUbmmEKeE+nRvW5wtoIK4SLy+v0bLd0b6HBH3KFfWw==" }, "@stremio/stremio-core-web": { - "version": "0.35.0", - "resolved": "https://registry.npmjs.org/@stremio/stremio-core-web/-/stremio-core-web-0.35.0.tgz", - "integrity": "sha512-MN5Mb+5yYV5MQXjzeShqJJeRXc4QLoLP/6TbE6ay6kq8PQ8bT7BreFiFVJunICVo/OmXjYNS3CiJhM5q1J4xZw==", + "version": "0.36.0", + "resolved": "https://registry.npmjs.org/@stremio/stremio-core-web/-/stremio-core-web-0.36.0.tgz", + "integrity": "sha512-A63yB+Pml/c6jtK4l5r7aacZ2Jm+ofaj60MfVCZfzSE4csBPMuoROYx6TM6nI6NU8iUDTcIU+OYoWcMfaT7v7A==", "requires": { "@babel/runtime": "7.15.4" }, diff --git a/package.json b/package.json index ad1a598d9..d40b607a4 100755 --- a/package.json +++ b/package.json @@ -15,7 +15,7 @@ "@babel/runtime": "7.16.0", "@sentry/browser": "6.13.3", "@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-video": "0.0.19-rc.1", "a-color-picker": "1.2.1", diff --git a/src/common/getVisibleChildrenRange.js b/src/common/getVisibleChildrenRange.js new file mode 100644 index 000000000..f389349e6 --- /dev/null +++ b/src/common/getVisibleChildrenRange.js @@ -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; diff --git a/src/common/index.js b/src/common/index.js index bcd66a99b..24868a2c7 100644 --- a/src/common/index.js +++ b/src/common/index.js @@ -24,6 +24,7 @@ const TextInput = require('./TextInput'); const { ToastProvider, useToast } = require('./Toast'); const comparatorWithPriorities = require('./comparatorWithPriorities'); const CONSTANTS = require('./CONSTANTS'); +const getVisibleChildrenRange = require('./getVisibleChildrenRange'); const languageNames = require('./languageNames'); const routesRegexp = require('./routesRegexp'); const sanitizeLocationPath = require('./sanitizeLocationPath'); @@ -65,6 +66,7 @@ module.exports = { useToast, comparatorWithPriorities, CONSTANTS, + getVisibleChildrenRange, languageNames, routesRegexp, sanitizeLocationPath, diff --git a/src/routes/Board/Board.js b/src/routes/Board/Board.js index fef7ae766..2cbe193c9 100644 --- a/src/routes/Board/Board.js +++ b/src/routes/Board/Board.js @@ -2,20 +2,43 @@ const React = require('react'); 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 useContinueWatchingPreview = require('./useContinueWatchingPreview'); const styles = require('./styles'); +const THRESHOLD = 300; + const Board = () => { - const board = useBoard(); const profile = useProfile(); const streamingServer = useStreamingServer(); 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 (
-
+
{ continueWatchingPreview.libraryItems.length > 0 ? { null } {board.catalogs.map((catalog, index) => { - switch (catalog.content.type) { + switch (catalog.content?.type) { case 'Ready': { return ( { /> ); } - case 'Loading': { + default: { return ( ({ @@ -9,6 +10,7 @@ const init = () => ({ }); const useBoard = () => { + const { core } = useServices(); const action = React.useMemo(() => ({ action: 'Load', args: { @@ -16,7 +18,17 @@ const useBoard = () => { 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; diff --git a/src/routes/Search/Search.js b/src/routes/Search/Search.js index 1bbd2492f..47fb15cba 100644 --- a/src/routes/Search/Search.js +++ b/src/routes/Search/Search.js @@ -3,13 +3,16 @@ const React = require('react'); const PropTypes = require('prop-types'); const classnames = require('classnames'); +const debounce = require('lodash.debounce'); 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 styles = require('./styles'); +const THRESHOLD = 100; + const Search = ({ queryParams }) => { - const search = useSearch(queryParams); + const [search, loadSearchRows] = useSearch(queryParams); const query = useDeepEqualMemo(() => { return search.selected !== null ? search.selected.extra.reduceRight((query, [name, value]) => { @@ -22,9 +25,26 @@ const Search = ({ queryParams }) => { : null; }, [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 ( -
+
{ query === null ?
@@ -49,7 +69,7 @@ const Search = ({ queryParams }) => {
: search.catalogs.map((catalog, index) => { - switch (catalog.content.type) { + switch (catalog.content?.type) { case 'Ready': { return ( { /> ); } - case 'Loading': { + default: { return ( { }; } }, [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; diff --git a/src/services/Chromecast/ChromecastTransport.js b/src/services/Chromecast/ChromecastTransport.js index 0d3f7041c..8cd84eeba 100644 --- a/src/services/Chromecast/ChromecastTransport.js +++ b/src/services/Chromecast/ChromecastTransport.js @@ -106,6 +106,9 @@ function ChromecastTransport() { this.off = function(name, listener) { events.off(name, listener); }; + this.removeAllListeners = function() { + events.removeAllListeners(); + }; this.getCastState = function() { return cast.framework.CastContext.getInstance().getCastState(); }; diff --git a/src/services/Core/CoreTransport.js b/src/services/Core/CoreTransport.js index 5a74eedd3..0e220c23d 100644 --- a/src/services/Core/CoreTransport.js +++ b/src/services/Core/CoreTransport.js @@ -31,6 +31,9 @@ function CoreTransport() { this.off = function(name, listener) { events.off(name, listener); }; + this.removeAllListeners = function() { + events.removeAllListeners(); + }; this.getState = function(field) { return get_state(field); };