diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 000000000..c1da1247e --- /dev/null +++ b/.dockerignore @@ -0,0 +1,2 @@ +screenshots/* +screenshots* diff --git a/Dockerfile b/Dockerfile index 4eeb450cf..2febdd65f 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,24 +1,31 @@ -# Stremio Node 14.x -FROM stremio/node-base:fermium - -# Meta -LABEL Description="Stremio Web" Vendor="Smart Code OOD" Version="1.0.0" - -# Update GitHub remote host key -RUN echo "github.com ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBEmKSENjQEezOmxkZMy7opKgwFB9nkt5YRrYMjNuG5N87uRgg6CLrbo5wAdT/y6v0mKV0U2w0WZ2YB/++Tpockg=" >> ~/.ssh/known_hosts - -# Create app directory -RUN mkdir -p /var/www/stremio-web - -# Install app dependencies -WORKDIR /var/www/stremio-web -COPY . /var/www/stremio-web -RUN npm install - -# Bundle app source -WORKDIR /var/www/stremio-web - -RUN npm run build - -EXPOSE 8080 -CMD ["node", "http_server.js"] +# Stremio Node 14.x +# the node version for running Stremio Web +ARG NODE_VERSION=15-alpine +FROM node:$NODE_VERSION AS base + +# Meta +LABEL Description="Stremio Web" Vendor="Smart Code OOD" Version="1.0.0" + +RUN mkdir -p /var/www/stremio-web +WORKDIR /var/www/stremio-web + +# Install app dependencies +FROM base AS prebuild + +RUN apk update && apk upgrade && \ + apk add --no-cache git +WORKDIR /var/www/stremio-web +COPY . . +RUN npm install +RUN npm run build + +# Bundle app source +FROM base AS final + +WORKDIR /var/www/stremio-web +COPY . . +COPY --from=prebuild /var/www/stremio-web/node_modules ./node_modules +COPY --from=prebuild /var/www/stremio-web/build ./build + +EXPOSE 8080 +CMD ["node", "http_server.js"] diff --git a/README.md b/README.md index 20f3f6413..06a5428ae 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ # Stremio - Freedom to Stream ![Build](https://github.com/stremio/stremio-web/workflows/Build/badge.svg?branch=development) -[![Github Page](https://img.shields.io/website?down_message=offline&label=Page&logo=data%3Aimage%2Fsvg%2Bxml%3Bbase64%2CPHN2ZyB3aWR0aD0iOTgiIGhlaWdodD0iOTYiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI%2BPHBhdGggZmlsbC1ydWxlPSJldmVub2RkIiBjbGlwLXJ1bGU9ImV2ZW5vZGQiIGQ9Ik00OC44NTQgMEMyMS44MzkgMCAwIDIyIDAgNDkuMjE3YzAgMjEuNzU2IDEzLjk5MyA0MC4xNzIgMzMuNDA1IDQ2LjY5IDIuNDI3LjQ5IDMuMzE2LTEuMDU5IDMuMzE2LTIuMzYyIDAtMS4xNDEtLjA4LTUuMDUyLS4wOC05LjEyNy0xMy41OSAyLjkzNC0xNi40Mi01Ljg2Ny0xNi40Mi01Ljg2Ny0yLjE4NC01LjcwNC01LjQyLTcuMTctNS40Mi03LjE3LTQuNDQ4LTMuMDE1LjMyNC0zLjAxNS4zMjQtMy4wMTUgNC45MzQuMzI2IDcuNTIzIDUuMDUyIDcuNTIzIDUuMDUyIDQuMzY3IDcuNDk2IDExLjQwNCA1LjM3OCAxNC4yMzUgNC4wNzQuNDA0LTMuMTc4IDEuNjk5LTUuMzc4IDMuMDc0LTYuNi0xMC44MzktMS4xNDEtMjIuMjQzLTUuMzc4LTIyLjI0My0yNC4yODMgMC01LjM3OCAxLjk0LTkuNzc4IDUuMDE0LTEzLjItLjQ4NS0xLjIyMi0yLjE4NC02LjI3NS40ODYtMTMuMDM4IDAgMCA0LjEyNS0xLjMwNCAxMy40MjYgNS4wNTJhNDYuOTcgNDYuOTcgMCAwIDEgMTIuMjE0LTEuNjNjNC4xMjUgMCA4LjMzLjU3MSAxMi4yMTMgMS42MyA5LjMwMi02LjM1NiAxMy40MjctNS4wNTIgMTMuNDI3LTUuMDUyIDIuNjcgNi43NjMuOTcgMTEuODE2LjQ4NSAxMy4wMzggMy4xNTUgMy40MjIgNS4wMTUgNy44MjIgNS4wMTUgMTMuMiAwIDE4LjkwNS0xMS40MDQgMjMuMDYtMjIuMzI0IDI0LjI4MyAxLjc4IDEuNTQ4IDMuMzE2IDQuNDgxIDMuMzE2IDkuMTI2IDAgNi42LS4wOCAxMS44OTctLjA4IDEzLjUyNiAwIDEuMzA0Ljg5IDIuODUzIDMuMzE2IDIuMzY0IDE5LjQxMi02LjUyIDMzLjQwNS0yNC45MzUgMzMuNDA1LTQ2LjY5MUM5Ny43MDcgMjIgNzUuNzg4IDAgNDguODU0IDB6IiBmaWxsPSIjZmZmIi8%2BPC9zdmc%2B&up_message=online&url=https%3A%2F%2Fstremio.github.io%2Fstremio-web%2F)](https://stremio.github.io/stremio-web/) +[![Github Page](https://img.shields.io/website?down_message=offline&label=Page&logo=data%3Aimage%2Fsvg%2Bxml%3Bbase64%2CPHN2ZyB3aWR0aD0iOTgiIGhlaWdodD0iOTYiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI%2BPHBhdGggZmlsbC1ydWxlPSJldmVub2RkIiBjbGlwLXJ1bGU9ImV2ZW5vZGQiIGQ9Ik00OC44NTQgMEMyMS44MzkgMCAwIDIyIDAgNDkuMjE3YzAgMjEuNzU2IDEzLjk5MyA0MC4xNzIgMzMuNDA1IDQ2LjY5IDIuNDI3LjQ5IDMuMzE2LTEuMDU5IDMuMzE2LTIuMzYyIDAtMS4xNDEtLjA4LTUuMDUyLS4wOC05LjEyNy0xMy41OSAyLjkzNC0xNi40Mi01Ljg2Ny0xNi40Mi01Ljg2Ny0yLjE4NC01LjcwNC01LjQyLTcuMTctNS40Mi03LjE3LTQuNDQ4LTMuMDE1LjMyNC0zLjAxNS4zMjQtMy4wMTUgNC45MzQuMzI2IDcuNTIzIDUuMDUyIDcuNTIzIDUuMDUyIDQuMzY3IDcuNDk2IDExLjQwNCA1LjM3OCAxNC4yMzUgNC4wNzQuNDA0LTMuMTc4IDEuNjk5LTUuMzc4IDMuMDc0LTYuNi0xMC44MzktMS4xNDEtMjIuMjQzLTUuMzc4LTIyLjI0My0yNC4yODMgMC01LjM3OCAxLjk0LTkuNzc4IDUuMDE0LTEzLjItLjQ4NS0xLjIyMi0yLjE4NC02LjI3NS40ODYtMTMuMDM4IDAgMCA0LjEyNS0xLjMwNCAxMy40MjYgNS4wNTJhNDYuOTcgNDYuOTcgMCAwIDEgMTIuMjE0LTEuNjNjNC4xMjUgMCA4LjMzLjU3MSAxMi4yMTMgMS42MyA5LjMwMi02LjM1NiAxMy40MjctNS4wNTIgMTMuNDI3LTUuMDUyIDIuNjcgNi43NjMuOTcgMTEuODE2LjQ4NSAxMy4wMzggMy4xNTUgMy40MjIgNS4wMTUgNy44MjIgNS4wMTUgMTMuMiAwIDE4LjkwNS0xMS40MDQgMjMuMDYtMjIuMzI0IDI0LjI4MyAxLjc4IDEuNTQ4IDMuMzE2IDQuNDgxIDMuMzE2IDkuMTI2IDAgNi42LS4wOCAxMS44OTctLjA4IDEzLjUyNiAwIDEuMzA0Ljg5IDIuODUzIDMuMzE2IDIuMzY0IDE5LjQxMi02LjUyIDMzLjQwNS0yNC45MzUgMzMuNDA1LTQ2LjY5MUM5Ny43MDcgMjIgNzUuNzg4IDAgNDguODU0IDB6IiBmaWxsPSIjZmZmIi8%2BPC9zdmc%2B&up_message=online&url=https%3A%2F%2Fstremio.github.io%2Fstremio-web%2F)](https://stremio.github.io/stremio-web/development) Stremio is a modern media center that's a one-stop solution for your video entertainment. You discover, watch and organize video content from easy to install addons. @@ -46,4 +46,4 @@ npm run build ## License -Stremio is copyright 2017-2022 Smart code and available under GPLv2 license. See the [LICENSE](/LICENSE.md) file in the project for more information. +Stremio is copyright 2017-2023 Smart code and available under GPLv2 license. See the [LICENSE](/LICENSE.md) file in the project for more information. diff --git a/images/icon_x192.png b/images/icon_x192.png index ca1a5a722..17b06916a 100644 Binary files a/images/icon_x192.png and b/images/icon_x192.png differ diff --git a/images/icon_x512.png b/images/icon_x512.png index c33705169..4807007a0 100644 Binary files a/images/icon_x512.png and b/images/icon_x512.png differ diff --git a/package-lock.json b/package-lock.json index f1b15e61d..401948d6e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12,9 +12,9 @@ "@babel/runtime": "7.16.0", "@sentry/browser": "6.13.3", "@stremio/stremio-colors": "5.0.1", - "@stremio/stremio-core-web": "0.44.23", + "@stremio/stremio-core-web": "0.44.25", "@stremio/stremio-icons": "5.0.0-beta.3", - "@stremio/stremio-video": "0.0.24", + "@stremio/stremio-video": "0.0.25-rc.2", "a-color-picker": "1.2.1", "bowser": "2.11.0", "buffer": "6.0.3", @@ -2704,9 +2704,9 @@ "integrity": "sha512-Dt3PYmy1DZ473QNs99KYXVWQPHtpIl37VUY0+gCEvvuCqE1fRrZIJtZ9KbysUKonvO7WwdQDztgcW0iGoc1dEA==" }, "node_modules/@stremio/stremio-core-web": { - "version": "0.44.23", - "resolved": "https://registry.npmjs.org/@stremio/stremio-core-web/-/stremio-core-web-0.44.23.tgz", - "integrity": "sha512-I3t0jxZdyNTLIE0w2O1fJIQ562BJc2iN26xL056KiD5FIuGfTAJ/vdHTiPAbcmEXsO3TPxX9fOGsntNEhnO2cA==", + "version": "0.44.25", + "resolved": "https://registry.npmjs.org/@stremio/stremio-core-web/-/stremio-core-web-0.44.25.tgz", + "integrity": "sha512-kW/AAh+c1qnfV5xFbfM+VFFvuRXp4M1pQuUj94O6my2kC39zKkZnFMQRDNbzoodoUQY0fusDM1K7rIw3DmlqEg==", "dependencies": { "@babel/runtime": "7.16.0" } @@ -2717,9 +2717,9 @@ "integrity": "sha512-K+jDsizEgxpBC+b0HExCUg+bnsWPZnx96qUfkdQ9nBDVAN/kzcP24Jq/4KwkiEyqDEcvC5l+xBzOLzkhdLwTMw==" }, "node_modules/@stremio/stremio-video": { - "version": "0.0.24", - "resolved": "https://registry.npmjs.org/@stremio/stremio-video/-/stremio-video-0.0.24.tgz", - "integrity": "sha512-k9bSyQBbZQnLll62P/enc/gy2eILZrW0xuU/MRxxTm9gtjo+8JLp/77H0a4tQFFHjJ9WPuNj4K6Lo2UnGem3kg==", + "version": "0.0.25-rc.2", + "resolved": "https://registry.npmjs.org/@stremio/stremio-video/-/stremio-video-0.0.25-rc.2.tgz", + "integrity": "sha512-OXNimBgPpkin5gX39Bsx/w6M+5ifP71amZEYlvsZz3CDKxGHkQTPLzl4z6RrbVr8wz8nt6eiWIj8BKxa4/u6nQ==", "dependencies": { "buffer": "6.0.3", "color": "4.2.3", @@ -16834,9 +16834,9 @@ "integrity": "sha512-Dt3PYmy1DZ473QNs99KYXVWQPHtpIl37VUY0+gCEvvuCqE1fRrZIJtZ9KbysUKonvO7WwdQDztgcW0iGoc1dEA==" }, "@stremio/stremio-core-web": { - "version": "0.44.23", - "resolved": "https://registry.npmjs.org/@stremio/stremio-core-web/-/stremio-core-web-0.44.23.tgz", - "integrity": "sha512-I3t0jxZdyNTLIE0w2O1fJIQ562BJc2iN26xL056KiD5FIuGfTAJ/vdHTiPAbcmEXsO3TPxX9fOGsntNEhnO2cA==", + "version": "0.44.25", + "resolved": "https://registry.npmjs.org/@stremio/stremio-core-web/-/stremio-core-web-0.44.25.tgz", + "integrity": "sha512-kW/AAh+c1qnfV5xFbfM+VFFvuRXp4M1pQuUj94O6my2kC39zKkZnFMQRDNbzoodoUQY0fusDM1K7rIw3DmlqEg==", "requires": { "@babel/runtime": "7.16.0" } @@ -16847,9 +16847,9 @@ "integrity": "sha512-K+jDsizEgxpBC+b0HExCUg+bnsWPZnx96qUfkdQ9nBDVAN/kzcP24Jq/4KwkiEyqDEcvC5l+xBzOLzkhdLwTMw==" }, "@stremio/stremio-video": { - "version": "0.0.24", - "resolved": "https://registry.npmjs.org/@stremio/stremio-video/-/stremio-video-0.0.24.tgz", - "integrity": "sha512-k9bSyQBbZQnLll62P/enc/gy2eILZrW0xuU/MRxxTm9gtjo+8JLp/77H0a4tQFFHjJ9WPuNj4K6Lo2UnGem3kg==", + "version": "0.0.25-rc.2", + "resolved": "https://registry.npmjs.org/@stremio/stremio-video/-/stremio-video-0.0.25-rc.2.tgz", + "integrity": "sha512-OXNimBgPpkin5gX39Bsx/w6M+5ifP71amZEYlvsZz3CDKxGHkQTPLzl4z6RrbVr8wz8nt6eiWIj8BKxa4/u6nQ==", "requires": { "buffer": "6.0.3", "color": "4.2.3", diff --git a/package.json b/package.json index c26a659d9..20c139e20 100755 --- a/package.json +++ b/package.json @@ -15,9 +15,9 @@ "@babel/runtime": "7.16.0", "@sentry/browser": "6.13.3", "@stremio/stremio-colors": "5.0.1", - "@stremio/stremio-core-web": "0.44.23", + "@stremio/stremio-core-web": "0.44.25", "@stremio/stremio-icons": "5.0.0-beta.3", - "@stremio/stremio-video": "0.0.24", + "@stremio/stremio-video": "0.0.25-rc.2", "a-color-picker": "1.2.1", "bowser": "2.11.0", "buffer": "6.0.3", diff --git a/screenshots/board.png b/screenshots/board.png index 10359e455..1fa83799a 100644 Binary files a/screenshots/board.png and b/screenshots/board.png differ diff --git a/screenshots/discover.png b/screenshots/discover.png index 554328045..eab8b5a2e 100644 Binary files a/screenshots/discover.png and b/screenshots/discover.png differ diff --git a/screenshots/metadetails.png b/screenshots/metadetails.png index 898c5c9cd..9aec4a302 100644 Binary files a/screenshots/metadetails.png and b/screenshots/metadetails.png differ diff --git a/src/App/App.js b/src/App/App.js index be59767b7..de443d9d4 100644 --- a/src/App/App.js +++ b/src/App/App.js @@ -6,7 +6,7 @@ const { useTranslation } = require('react-i18next'); const { Router } = require('stremio-router'); const { Core, Shell, Chromecast, DragAndDrop, KeyboardShortcuts, ServicesProvider } = require('stremio/services'); const { NotFound } = require('stremio/routes'); -const { ToastProvider, CONSTANTS, withCoreSuspender } = require('stremio/common'); +const { ToastProvider, TooltipProvider, CONSTANTS, withCoreSuspender } = require('stremio/common'); const ServicesToaster = require('./ServicesToaster'); const DeepLinkHandler = require('./DeepLinkHandler'); const ErrorDialog = require('./ErrorDialog'); @@ -159,13 +159,15 @@ const App = () => { : - - - + + + + + :
diff --git a/src/App/styles.less b/src/App/styles.less index f2a96b1f3..038f6cf56 100644 --- a/src/App/styles.less +++ b/src/App/styles.less @@ -117,6 +117,19 @@ html { } } + .tooltip-container { + height: 2.5rem; + display: flex; + align-items: center; + justify-content: center; + padding: 0 1.5rem; + font-size: 1rem; + color: var(--primary-foreground-color); + border-radius: var(--border-radius); + background-color: var(--modal-background-color); + transition: opacity 0.25s ease-out; + } + .router { width: 100%; height: 100%; diff --git a/src/common/NavBar/HorizontalNavBar/NavMenu/NavMenuContent.js b/src/common/NavBar/HorizontalNavBar/NavMenu/NavMenuContent.js index ab130e62c..9492ed7fc 100644 --- a/src/common/NavBar/HorizontalNavBar/NavMenu/NavMenuContent.js +++ b/src/common/NavBar/HorizontalNavBar/NavMenu/NavMenuContent.js @@ -90,9 +90,14 @@ const NavMenuContent = ({ onClick }) => { - + { + profile.auth !== null ? + + : + null + }
); diff --git a/src/common/Tooltip/Tooltip.js b/src/common/Tooltip/Tooltip.js new file mode 100644 index 000000000..91a11ef5c --- /dev/null +++ b/src/common/Tooltip/Tooltip.js @@ -0,0 +1,63 @@ +// Copyright (C) 2017-2023 Smart code 203358507 + +const React = require('react'); +const PropTypes = require('prop-types'); +const useTooltip = require('./useTooltip'); +const styles = require('./styles'); + +const createId = () => (Math.random() + 1).toString(36).substring(7); + +const Tooltip = ({ label, position, margin }) => { + const tooltip = useTooltip(); + + const id = React.useRef(createId()); + const element = React.useRef(null); + + const onMouseEnter = () => { + tooltip.toggle(id.current, true); + }; + + const onMouseLeave = () => { + tooltip.toggle(id.current, false); + }; + + React.useEffect(() => { + if (element.current && element.current.parentElement) { + const parentElement = element.current.parentElement; + tooltip.add({ + id: id.current, + label, + position, + margin, + parent: parentElement, + }); + + parentElement.addEventListener('mouseenter', onMouseEnter); + parentElement.addEventListener('mouseleave', onMouseLeave); + } + + return () => { + if (element.current && element.current.parentElement) { + const parentElement = element.current.parentElement; + parentElement.removeEventListener('mouseenter', onMouseEnter); + parentElement.removeEventListener('mouseleave', onMouseLeave); + + tooltip.remove(id.current); + } + }; + }, []); + + return ( +
+ ); +}; + +Tooltip.displayName = 'Tooltip'; + +Tooltip.propTypes = { + label: PropTypes.string.isRequired, + position: PropTypes.string.isRequired, + margin: PropTypes.number, +}; + +module.exports = Tooltip; diff --git a/src/common/Tooltip/TooltipContext.js b/src/common/Tooltip/TooltipContext.js new file mode 100644 index 000000000..f4da8a082 --- /dev/null +++ b/src/common/Tooltip/TooltipContext.js @@ -0,0 +1,8 @@ +// Copyright (C) 2017-2023 Smart code 203358507 + +const { createContext } = require('react'); + +const TooltipContext = createContext(null); + +module.exports = TooltipContext; + diff --git a/src/common/Tooltip/TooltipProvider.js b/src/common/Tooltip/TooltipProvider.js new file mode 100644 index 000000000..c19780248 --- /dev/null +++ b/src/common/Tooltip/TooltipProvider.js @@ -0,0 +1,106 @@ +// Copyright (C) 2017-2023 Smart code 203358507 + +const React = require('react'); +const { useState, createRef } = require('react'); +const PropTypes = require('prop-types'); +const classNames = require('classnames'); +const TooltipContext = require('./TooltipContext'); +const styles = require('./styles'); + +const TooltipProvider = ({ children, className }) => { + const [tooltips, setTooltips] = useState([]); + + const add = ({ id, label, position, margin = 15, parent }) => { + const ref = createRef(null); + + const tooltip = { + ref, + id, + label, + position, + margin: margin, + active: false, + parent, + }; + + setTooltips((tooltips) => ([ + ...tooltips, + tooltip, + ])); + }; + + const remove = (id) => { + setTooltips((tooltips) => ( + tooltips.filter((tooltip) => tooltip.id !== id) + )); + }; + + const toggle = (id, state) => { + setTooltips((tooltips) => ( + tooltips.map((tooltip) => { + if (tooltip.id === id) { + tooltip.active = state; + } + return tooltip; + }) + )); + }; + + const style = (ref, position, margin, active, parent) => { + if (!active) return {}; + + const tooltipHeight = ref.current?.offsetHeight ?? 0; + const tooltipWidth = ref.current?.offsetWidth ?? 0; + const parentBounds = parent.getBoundingClientRect(); + + switch (position) { + case 'top': + return { + top: `${parentBounds.top - tooltipHeight - margin}px`, + left: `${(parentBounds.left + (parentBounds.width / 2)) - (tooltipWidth / 2)}px`, + }; + case 'bottom': + return { + top: `${parentBounds.top + parentBounds.height + margin}px`, + left: `${(parentBounds.left + (parentBounds.width / 2)) - (tooltipWidth / 2)}px`, + }; + case 'left': + return { + top: `${parentBounds.top + (parentBounds.height / 2) - (tooltipHeight / 2)}px`, + left: `${(parentBounds.left - tooltipWidth - margin)}px`, + }; + case 'right': + return { + top: `${parentBounds.top + (parentBounds.height / 2) - (tooltipHeight / 2)}px`, + left: `${(parentBounds.left + parentBounds.width + margin)}px`, + }; + } + }; + + return ( + + { children } +
+ { + tooltips.map(({ ref, id, label, position, margin, active, parent }) => ( +
+ { label } +
+ )) + } +
+
+ ); +}; + +TooltipProvider.propTypes = { + children: PropTypes.node, + className: PropTypes.string, +}; + +module.exports = TooltipProvider; diff --git a/src/common/Tooltip/index.js b/src/common/Tooltip/index.js new file mode 100644 index 000000000..82356a28f --- /dev/null +++ b/src/common/Tooltip/index.js @@ -0,0 +1,9 @@ +// Copyright (C) 2017-2023 Smart code 203358507 + +const TooltipProvider = require('./TooltipProvider'); +const Tooltip = require('./Tooltip'); + +module.exports = { + TooltipProvider, + Tooltip, +}; diff --git a/src/common/Tooltip/styles.less b/src/common/Tooltip/styles.less new file mode 100644 index 000000000..d69825b71 --- /dev/null +++ b/src/common/Tooltip/styles.less @@ -0,0 +1,22 @@ +// Copyright (C) 2017-2023 Smart code 203358507 + +.tooltip { + z-index: -1; + visibility: hidden; + position: absolute; + top: 0; + bottom: 0; + left: 0; + right: 0; +} + +.tooltip-container { + position: fixed; + visibility: hidden; + opacity: 0; + + &:global(.active) { + visibility: visible; + opacity: 1; + } +} \ No newline at end of file diff --git a/src/common/Tooltip/useTooltip.js b/src/common/Tooltip/useTooltip.js new file mode 100644 index 000000000..d787dca39 --- /dev/null +++ b/src/common/Tooltip/useTooltip.js @@ -0,0 +1,10 @@ +// Copyright (C) 2017-2023 Smart code 203358507 + +const React = require('react'); +const TooltipContext = require('./TooltipContext'); + +const useTooltip = () => { + return React.useContext(TooltipContext); +}; + +module.exports = useTooltip; diff --git a/src/common/index.js b/src/common/index.js index f267be575..700fd432b 100644 --- a/src/common/index.js +++ b/src/common/index.js @@ -23,6 +23,7 @@ const SharePrompt = require('./SharePrompt'); const Slider = require('./Slider'); const TextInput = require('./TextInput'); const { ToastProvider, useToast } = require('./Toast'); +const { TooltipProvider, Tooltip } = require('./Tooltip'); const comparatorWithPriorities = require('./comparatorWithPriorities'); const CONSTANTS = require('./CONSTANTS'); const { withCoreSuspender, useCoreSuspender } = require('./CoreSuspender'); @@ -70,6 +71,8 @@ module.exports = { TextInput, ToastProvider, useToast, + TooltipProvider, + Tooltip, comparatorWithPriorities, CONSTANTS, withCoreSuspender, diff --git a/src/routes/Player/Player.js b/src/routes/Player/Player.js index cfdbaf4ce..602ae1516 100644 --- a/src/routes/Player/Player.js +++ b/src/routes/Player/Player.js @@ -33,6 +33,28 @@ const Player = ({ urlParams, queryParams }) => { queryParams.has('maxAudioChannels') ? parseInt(queryParams.get('maxAudioChannels'), 10) : null ]; }, [queryParams]); + const [player, videoParamsChanged, timeChanged, pausedChanged, ended] = usePlayer(urlParams); + const [settings, updateSettings] = useSettings(); + const streamingServer = useStreamingServer(); + const routeFocused = useRouteFocused(); + const toast = useToast(); + const [, , , toggleFullscreen] = useFullscreen(); + const [casting, setCasting] = React.useState(() => { + return chromecast.active && chromecast.transport.getCastState() === cast.framework.CastState.CONNECTED; + }); + const [immersed, setImmersed] = React.useState(true); + const setImmersedDebounced = React.useCallback(debounce(setImmersed, 3000), []); + const [optionsMenuOpen, , closeOptionsMenu, toggleOptionsMenu] = useBinaryState(false); + const [subtitlesMenuOpen, , closeSubtitlesMenu, toggleSubtitlesMenu] = useBinaryState(false); + const [infoMenuOpen, , closeInfoMenu, toggleInfoMenu] = useBinaryState(false); + const [speedMenuOpen, , closeSpeedMenu, toggleSpeedMenu] = useBinaryState(false); + const [videosMenuOpen, , closeVideosMenu, toggleVideosMenu] = useBinaryState(false); + const [nextVideoPopupOpen, openNextVideoPopup, closeNextVideoPopup] = useBinaryState(false); + const [statisticsMenuOpen, , closeStatisticsMenu, toggleStatisticsMenu] = useBinaryState(false); + const nextVideoPopupDismissed = React.useRef(false); + const defaultSubtitlesSelected = React.useRef(false); + const defaultAudioTrackSelected = React.useRef(false); + const [error, setError] = React.useState(null); const [videoState, setVideoState] = React.useReducer( (videoState, nextVideoState) => ({ ...videoState, ...nextVideoState }), { @@ -66,28 +88,6 @@ const Player = ({ urlParams, queryParams }) => { extraSubtitlesOutlineColor: null } ); - const [player, timeChanged, pausedChanged, ended] = usePlayer(urlParams, videoState.videoParams); - const [settings, updateSettings] = useSettings(); - const streamingServer = useStreamingServer(); - const routeFocused = useRouteFocused(); - const toast = useToast(); - const [, , , toggleFullscreen] = useFullscreen(); - const [casting, setCasting] = React.useState(() => { - return chromecast.active && chromecast.transport.getCastState() === cast.framework.CastState.CONNECTED; - }); - const [immersed, setImmersed] = React.useState(true); - const setImmersedDebounced = React.useCallback(debounce(setImmersed, 3000), []); - const [optionsMenuOpen, , closeOptionsMenu, toggleOptionsMenu] = useBinaryState(false); - const [subtitlesMenuOpen, , closeSubtitlesMenu, toggleSubtitlesMenu] = useBinaryState(false); - const [infoMenuOpen, , closeInfoMenu, toggleInfoMenu] = useBinaryState(false); - const [speedMenuOpen, , closeSpeedMenu, toggleSpeedMenu] = useBinaryState(false); - const [videosMenuOpen, , closeVideosMenu, toggleVideosMenu] = useBinaryState(false); - const [nextVideoPopupOpen, openNextVideoPopup, closeNextVideoPopup] = useBinaryState(false); - const [statisticsMenuOpen, , closeStatisticsMenu, toggleStatisticsMenu] = useBinaryState(false); - const nextVideoPopupDismissed = React.useRef(false); - const defaultSubtitlesSelected = React.useRef(false); - const defaultAudioTrackSelected = React.useRef(false); - const [error, setError] = React.useState(null); const videoRef = React.useRef(null); const dispatch = React.useCallback((action, options) => { if (videoRef.current !== null) { @@ -353,6 +353,9 @@ const Player = ({ urlParams, queryParams }) => { pausedChanged(videoState.paused); } }, [videoState.paused]); + React.useEffect(() => { + videoParamsChanged(videoState.videoParams); + }, [videoState.videoParams]); React.useEffect(() => { if (!!settings.bingeWatching && player.nextVideo !== null && !nextVideoPopupDismissed.current) { if (videoState.time !== null && videoState.duration !== null && videoState.time < videoState.duration && (videoState.duration - videoState.time) <= settings.nextVideoNotificationDuration) { diff --git a/src/routes/Player/usePlayer.js b/src/routes/Player/usePlayer.js index 53aa84fe4..d530d78db 100644 --- a/src/routes/Player/usePlayer.js +++ b/src/routes/Player/usePlayer.js @@ -32,7 +32,7 @@ const map = (player) => ({ player.metaItem, }); -const usePlayer = (urlParams, videoParams) => { +const usePlayer = (urlParams) => { const { core } = useServices(); const { decodeStream } = useCoreSuspender(); const stream = decodeStream(urlParams.stream); @@ -44,7 +44,6 @@ const usePlayer = (urlParams, videoParams) => { model: 'Player', args: { stream, - videoParams, streamRequest: typeof urlParams.streamTransportUrl === 'string' && typeof urlParams.type === 'string' && typeof urlParams.videoId === 'string' ? { base: urlParams.streamTransportUrl, @@ -86,7 +85,16 @@ const usePlayer = (urlParams, videoParams) => { action: 'Unload' }; } - }, [urlParams, videoParams]); + }, [urlParams]); + const videoParamsChanged = React.useCallback((videoParams) => { + core.transport.dispatch({ + action: 'Player', + args: { + action: 'VideoParamsChanged', + args: { videoParams } + } + }, 'player'); + }, []); const timeChanged = React.useCallback((time, duration, device) => { core.transport.dispatch({ action: 'Player', @@ -114,7 +122,7 @@ const usePlayer = (urlParams, videoParams) => { }, 'player'); }, []); const player = useModelState({ model: 'player', action, map }); - return [player, timeChanged, pausedChanged, ended]; + return [player, videoParamsChanged, timeChanged, pausedChanged, ended]; }; module.exports = usePlayer; diff --git a/src/routes/Settings/Settings.js b/src/routes/Settings/Settings.js index 2a2fdf721..25c2dcb27 100644 --- a/src/routes/Settings/Settings.js +++ b/src/routes/Settings/Settings.js @@ -300,6 +300,26 @@ const Settings = () => {
{ t('PRIVACY_POLICY') }
+ { + profile.auth !== null && profile.auth.user !== null && typeof profile.auth.user.email === 'string' ? +
+ +
+ : + null + } + { + profile.auth !== null && profile.auth.user !== null ? +
+ +
+ : + null + }
{ t('SETTINGS_NAV_PLAYER') }
@@ -437,8 +457,8 @@ const Settings = () => { streamingServer.settings.type === 'Ready' ? t('SETTINGS_SERVER_STATUS_ONLINE') : - streamingServer.settings.type === 'Error' ? - `${t('SETTINGS_SERVER_STATUS_ERROR')}: (${streamingServer.settings.content})` + streamingServer.settings.type === 'Err' ? + t('SETTINGS_SERVER_STATUS_ERROR') : streamingServer.settings.type }