mirror of
https://github.com/sussy-code/smov.git
synced 2024-12-29 16:07:40 +01:00
commit
518308c336
25 changed files with 332 additions and 196 deletions
|
@ -1,7 +1,7 @@
|
|||
# Setting up the client
|
||||
|
||||
## Vercel - Recommended
|
||||
[![Deploy with Vercel](https://vercel.com/button)](https://vercel.com/new/clone?repository-url=https%3A%2F%2Fgithub.com%2Fmovie-web%2Fmovie-web&env=VITE_CORS_PROXY_URL,VITE_TMDB_READ_API_KEY)
|
||||
[![Deploy with Vercel](https://vercel.com/button)](https://vercel.com/new/clone?repository-url=https%3A%2F%2Fgithub.com%2Fmovie-web%2Fmovie-web%2Ftree%2Fmaster&env=VITE_CORS_PROXY_URL,VITE_TMDB_READ_API_KEY)
|
||||
1. Click the Deploy button.
|
||||
1. Sign in using either a GitHub, GitLab, or Bitbucket.
|
||||
1. Follow the instructions to create a repository for movie-web.
|
||||
|
|
11
.github/logo-dark.svg
vendored
Normal file
11
.github/logo-dark.svg
vendored
Normal file
|
@ -0,0 +1,11 @@
|
|||
<svg width="2147" height="1121" viewBox="0 0 2147 1121" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M1663.06 591.678H1719.49C1745.2 591.678 1763.85 595.357 1775.42 602.716C1787.08 609.992 1792.91 621.609 1792.91 637.566C1792.91 648.398 1790.35 657.286 1785.22 664.231C1780.18 671.177 1773.44 675.352 1765.01 676.758V677.998C1776.5 680.561 1784.77 685.357 1789.81 692.385C1794.94 699.413 1797.5 708.756 1797.5 720.414C1797.5 736.951 1791.51 749.849 1779.52 759.109C1767.61 768.37 1751.4 773 1730.9 773H1663.06V591.678ZM1701.51 663.487H1723.83C1734.25 663.487 1741.77 661.875 1746.4 658.65C1751.12 655.426 1753.47 650.093 1753.47 642.651C1753.47 635.706 1750.91 630.745 1745.78 627.769C1740.74 624.709 1732.72 623.18 1721.72 623.18H1701.51V663.487ZM1701.51 693.997V741.25H1726.56C1737.14 741.25 1744.96 739.224 1750 735.173C1755.04 731.121 1757.56 724.92 1757.56 716.569C1757.56 701.521 1746.82 693.997 1725.32 693.997H1701.51Z" fill="white"/>
|
||||
<path d="M1625.11 773H1520.68V591.678H1625.11V623.18H1559.13V662.991H1620.52V694.493H1559.13V741.25H1625.11V773Z" fill="white"/>
|
||||
<path d="M1451.72 773H1407.94L1383.39 677.75C1382.48 674.36 1380.91 667.373 1378.67 656.79C1376.52 646.124 1375.28 638.972 1374.95 635.334C1374.46 639.799 1373.22 646.992 1371.23 656.914C1369.25 666.753 1367.72 673.781 1366.64 677.998L1342.21 773H1298.55L1252.29 591.678H1290.12L1313.31 690.648C1317.36 708.921 1320.3 724.755 1322.12 738.149C1322.61 733.437 1323.73 726.16 1325.47 716.321C1327.29 706.399 1328.98 698.71 1330.55 693.253L1356.97 591.678H1393.31L1419.72 693.253C1420.88 697.8 1422.33 704.746 1424.07 714.089C1425.8 723.432 1427.12 731.452 1428.03 738.149C1428.86 731.7 1430.18 723.68 1432 714.089C1433.82 704.415 1435.48 696.602 1436.96 690.648L1460.03 591.678H1497.86L1451.72 773Z" fill="white"/>
|
||||
<path d="M1178 720.414V689.408H1244.6V720.414H1178Z" fill="white"/>
|
||||
<path d="M1155.31 773H1050.88V591.678H1155.31V623.18H1089.33V662.991H1150.72V694.493H1089.33V741.25H1155.31V773Z" fill="white"/>
|
||||
<path d="M966.791 773V591.678H1005.24V773H966.791Z" fill="white"/>
|
||||
<path d="M905.027 591.678H943.847L882.207 773H840.287L778.771 591.678H817.591L851.697 699.578C853.599 705.945 855.542 713.386 857.526 721.902C859.593 730.336 860.875 736.206 861.371 739.514C862.281 731.907 865.381 718.595 870.673 699.578L905.027 591.678Z" fill="white"/>
|
||||
<path d="M764.012 682.091C764.012 712.104 756.57 735.173 741.688 751.296C726.805 767.419 705.473 775.48 677.691 775.48C649.91 775.48 628.578 767.419 613.695 751.296C598.812 735.173 591.371 712.022 591.371 681.843C591.371 651.664 598.812 628.637 613.695 612.762C628.661 596.804 650.076 588.825 677.939 588.825C705.803 588.825 727.094 596.845 741.812 612.886C756.612 628.926 764.012 651.994 764.012 682.091ZM631.679 682.091C631.679 702.348 635.523 717.603 643.213 727.855C650.902 738.108 662.395 743.234 677.691 743.234C708.367 743.234 723.704 722.853 723.704 682.091C723.704 641.246 708.449 620.823 677.939 620.823C662.643 620.823 651.109 625.991 643.337 636.326C635.565 646.579 631.679 661.834 631.679 682.091Z" fill="white"/>
|
||||
<path d="M436.591 773L392.935 630.745H391.818C393.389 659.684 394.175 678.99 394.175 688.664V773H359.82V591.678H412.158L455.07 730.336H455.814L501.331 591.678H553.669V773H517.826V687.176C517.826 683.124 517.868 678.453 517.95 673.161C518.116 667.869 518.694 653.813 519.687 630.993H518.57L471.813 773H436.591Z" fill="white"/>
|
||||
</svg>
|
After Width: | Height: | Size: 3.3 KiB |
11
.github/logo-light.svg
vendored
Normal file
11
.github/logo-light.svg
vendored
Normal file
|
@ -0,0 +1,11 @@
|
|||
<svg width="2147" height="1121" viewBox="0 0 2147 1121" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M1663.06 599.678H1719.49C1745.2 599.678 1763.85 603.357 1775.42 610.716C1787.08 617.992 1792.91 629.609 1792.91 645.566C1792.91 656.398 1790.35 665.286 1785.22 672.231C1780.18 679.177 1773.44 683.352 1765.01 684.758V685.998C1776.5 688.561 1784.77 693.357 1789.81 700.385C1794.94 707.413 1797.5 716.756 1797.5 728.414C1797.5 744.951 1791.51 757.849 1779.52 767.109C1767.61 776.37 1751.4 781 1730.9 781H1663.06V599.678ZM1701.51 671.487H1723.83C1734.25 671.487 1741.77 669.875 1746.4 666.65C1751.12 663.426 1753.47 658.093 1753.47 650.651C1753.47 643.706 1750.91 638.745 1745.78 635.769C1740.74 632.709 1732.72 631.18 1721.72 631.18H1701.51V671.487ZM1701.51 701.997V749.25H1726.56C1737.14 749.25 1744.96 747.224 1750 743.173C1755.04 739.121 1757.56 732.92 1757.56 724.569C1757.56 709.521 1746.82 701.997 1725.32 701.997H1701.51Z" fill="black"/>
|
||||
<path d="M1625.11 781H1520.68V599.678H1625.11V631.18H1559.13V670.991H1620.52V702.493H1559.13V749.25H1625.11V781Z" fill="black"/>
|
||||
<path d="M1451.72 781H1407.94L1383.39 685.75C1382.48 682.36 1380.91 675.373 1378.67 664.79C1376.52 654.124 1375.28 646.972 1374.95 643.334C1374.46 647.799 1373.22 654.992 1371.23 664.914C1369.25 674.753 1367.72 681.781 1366.64 685.998L1342.21 781H1298.55L1252.29 599.678H1290.12L1313.31 698.648C1317.36 716.921 1320.3 732.755 1322.12 746.149C1322.61 741.437 1323.73 734.16 1325.47 724.321C1327.29 714.399 1328.98 706.71 1330.55 701.253L1356.97 599.678H1393.31L1419.72 701.253C1420.88 705.8 1422.33 712.746 1424.07 722.089C1425.8 731.432 1427.12 739.452 1428.03 746.149C1428.86 739.7 1430.18 731.68 1432 722.089C1433.82 712.415 1435.48 704.602 1436.96 698.648L1460.03 599.678H1497.86L1451.72 781Z" fill="black"/>
|
||||
<path d="M1178 728.414V697.408H1244.6V728.414H1178Z" fill="black"/>
|
||||
<path d="M1155.31 781H1050.88V599.678H1155.31V631.18H1089.33V670.991H1150.72V702.493H1089.33V749.25H1155.31V781Z" fill="black"/>
|
||||
<path d="M966.791 781V599.678H1005.24V781H966.791Z" fill="black"/>
|
||||
<path d="M905.027 599.678H943.847L882.207 781H840.287L778.771 599.678H817.591L851.697 707.578C853.599 713.945 855.542 721.386 857.526 729.902C859.593 738.336 860.875 744.206 861.371 747.514C862.281 739.907 865.381 726.595 870.673 707.578L905.027 599.678Z" fill="black"/>
|
||||
<path d="M764.012 690.091C764.012 720.104 756.57 743.173 741.688 759.296C726.805 775.419 705.473 783.48 677.691 783.48C649.91 783.48 628.578 775.419 613.695 759.296C598.812 743.173 591.371 720.022 591.371 689.843C591.371 659.664 598.812 636.637 613.695 620.762C628.661 604.804 650.076 596.825 677.939 596.825C705.803 596.825 727.094 604.845 741.812 620.886C756.612 636.926 764.012 659.994 764.012 690.091ZM631.679 690.091C631.679 710.348 635.523 725.603 643.213 735.855C650.902 746.108 662.395 751.234 677.691 751.234C708.367 751.234 723.704 730.853 723.704 690.091C723.704 649.246 708.449 628.823 677.939 628.823C662.643 628.823 651.109 633.991 643.337 644.326C635.565 654.579 631.679 669.834 631.679 690.091Z" fill="black"/>
|
||||
<path d="M436.591 781L392.935 638.745H391.818C393.389 667.684 394.175 686.99 394.175 696.664V781H359.82V599.678H412.158L455.07 738.336H455.814L501.331 599.678H553.669V781H517.826V695.176C517.826 691.124 517.868 686.453 517.95 681.161C518.116 675.869 518.694 661.813 519.687 638.993H518.57L471.813 781H436.591Z" fill="black"/>
|
||||
</svg>
|
After Width: | Height: | Size: 3.3 KiB |
117
README.md
117
README.md
|
@ -1,82 +1,93 @@
|
|||
<h1>movie-web</h1>
|
||||
|
||||
<p align="center"><img align="center" width="280" src="./.github/logo-dark.svg#gh-dark-mode-only"/></p>
|
||||
<p align="center"><img align="center" width="280" src="./.github/logo-light.svg#gh-light-mode-only"/></p>
|
||||
<p align="center">
|
||||
<a href="https://github.com/movie-web/movie-web/actions"><img alt="GitHub Workflow Status" src="https://img.shields.io/github/actions/workflow/status/movie-web/movie-web/deploying.yml?branch=master&style=flat-square"></a>
|
||||
<a href="https://github.com/movie-web/movie-web/blob/master/LICENSE.md"><img alt="GitHub license" src="https://img.shields.io/github/license/movie-web/movie-web?style=flat-square"></a>
|
||||
<a href="https://github.com/movie-web/movie-web/network"><img alt="GitHub forks" src="https://img.shields.io/github/forks/movie-web/movie-web?style=flat-square"></a>
|
||||
<a href="https://github.com/movie-web/movie-web/stargazers"><img alt="GitHub stars" src="https://img.shields.io/github/stars/movie-web/movie-web?style=flat-square"></a>
|
||||
<a href="https://github.com/movie-web/movie-web/stargazers"><img src="https://weblate.movie-web.app/widget/movie-web/movie-web/svg-badge.svg"></a><br />
|
||||
<a href="https://discord.movie-web.app"><img src="https://discord.com/api/guilds/871713465100816424/widget.png?style=banner2" alt="Discord Server"></a>
|
||||
<img src="https://skillicons.dev/icons?i=react,vite,ts" />
|
||||
<br/>
|
||||
<a href="https://discord.movie-web.app"><kbd>🔵 discord</kbd></a> <a href="https://movie-web.app"><kbd>🟢 website</kbd></a>
|
||||
</p>
|
||||
<br/><br/>
|
||||
|
||||
movie-web is a web app for watching movies easily. Check it out at **[movie-web.app](https://movie-web.app)**.
|
||||
# ⚡What is movie-web?
|
||||
|
||||
movie-web is a web app for watching movies easily. Check it out at <a href="https://movie-web.app"><kbd>movie-web.app</kbd></a>.
|
||||
|
||||
This service works by displaying video files from third-party providers inside an intuitive and aesthetic user interface.
|
||||
|
||||
Features include:
|
||||
# 🔥Features
|
||||
|
||||
- 🕑 Saving of your progress so you can come back to a video at any time!
|
||||
- 🔖 Bookmarks to keep track of videos you would like to watch.
|
||||
- 🎞️ Easy switching between seasons and episodes for a TV series; binge away!
|
||||
- ✖️ Supports multiple types of content including movies, TV shows and Anime (coming soon™️)
|
||||
- Automatic saving of progress - optionally synced to an account.
|
||||
- Bookmark shows or movies, keep track of what you want to watch.
|
||||
- Minimalistic interface that only shows whats required - no algorithm to consume you.
|
||||
|
||||
## Goals of movie-web
|
||||
## 🍄 Philosophy
|
||||
|
||||
- No ads
|
||||
- No BS: just a search bar and a video player
|
||||
- No responsibility on the hoster, no databases or api's hosted by us, just a static site
|
||||
This project is meant to be simple and easy to use. Keep features minimal but polished.
|
||||
We do not want this project to be yet another bulky streaming site, instead it aims for minimalism.
|
||||
|
||||
## Self-hosting
|
||||
On top of that, hosting should be as cheap and simple as possible. Just a static website with a proxy, with an optional backend if you want cross-device syncing.
|
||||
|
||||
A simple guide has been written to assist in hosting your own instance of movie-web.
|
||||
Content is fetched from third parties and scraping is done fully done on the client. This means that the hoster has no files or media on their server. All files are streamed directly from the third parties.
|
||||
|
||||
Check it out here: [https://github.com/movie-web/movie-web/blob/dev/SELFHOSTING.md](https://github.com/movie-web/movie-web/blob/dev/SELFHOSTING.md)
|
||||
## ⚠️ Limitations
|
||||
|
||||
## Running locally for development
|
||||
- Due to being a static site, there can be no SSR
|
||||
- To keep it cheap to host, amount of proxied requests need to be kept to a minimum
|
||||
- Also to keep it cheap, no content must ever be streamed through the proxy. So only streams not protected by CORS headers.
|
||||
|
||||
To run this project locally for contributing or testing, run the following commands:
|
||||
<h5><b>note: must use pnpm to install packages and run NodeJS 16 (install with `npm i -g pnpm`)</b></h5>
|
||||
# 🧬 Running locally for development
|
||||
|
||||
To run locally, you must first clone the repository. After that run the following commands in the root of the repository:
|
||||
```bash
|
||||
git clone https://github.com/movie-web/movie-web
|
||||
cd movie-web
|
||||
pnpm install
|
||||
pnpm run dev
|
||||
```
|
||||
|
||||
To build production files, simply run `pnpm run build`.
|
||||
To build production files, run:
|
||||
```bash
|
||||
pnpm build
|
||||
```
|
||||
|
||||
You'll need to deploy a cloudflare service worker as well. Check the [selfhosting guide](https://github.com/movie-web/movie-web/blob/dev/SELFHOSTING.md) on how to run the service worker. Afterwards you can make a `.env` file and put in the URL. (see `example.env` for an example)
|
||||
> [!TIP]
|
||||
> You must use pnpm (`npm i -g pnpm`) and run NodeJS 20
|
||||
|
||||
<h2>Contributing - <a href="https://github.com/movie-web/movie-web/issues"><img alt="GitHub issues" src="https://img.shields.io/github/issues/movie-web/movie-web?style=flat-square"></a>
|
||||
<a href="https://github.com/movie-web/movie-web/pulls"><img alt="GitHub pull requests" src="https://img.shields.io/github/issues-pr/movie-web/movie-web?style=flat-square"></a></h2>
|
||||
# 🥔 Selfhosting
|
||||
|
||||
Check out [this project's issues](https://github.com/movie-web/movie-web/issues) for inspiration for contribution. Pull requests are always welcome.
|
||||
A simple guide has been written to assist in hosting your own instance of movie-web. Check it out below
|
||||
|
||||
**All pull requests must be merged into the `dev` branch. it will then be deployed with the next version**
|
||||
|[Selfhosting guide](https://docs.movie-web.app/self-hosting/self-hosting)|
|
||||
|---|
|
||||
|
||||
## Credits
|
||||
|
||||
# 🤝 Contributors
|
||||
|
||||
This project would not be possible without our amazing contributors and the community.
|
||||
|
||||
<a href="https://github.com/movie-web/movie-web/graphs/contributors"><img alt="GitHub contributors" src="https://img.shields.io/github/contributors/movie-web/movie-web?style=flat-square"></a>
|
||||
|
||||
<div style="display:flex;align-items:center;grid-gap:10px">
|
||||
<img src="https://github.com/JamesHawkinss.png?size=20" width="20"><span><a href="https://github.com/JamesHawkinss">@JamesHawkinss</a> for original concept.</span>
|
||||
</div>
|
||||
|
||||
<div style="display:flex;align-items:center;grid-gap:10px">
|
||||
<img src="https://github.com/JipFr.png?size=20" width="20"><span><a href="https://github.com/JipFr">@JipFr</a> for initial work on <a href="https://github.com/JipFr/movie-cli">movie-cli</a>.</span>
|
||||
</div>
|
||||
|
||||
<div style="display:flex;align-items:center;grid-gap:10px">
|
||||
<img src="https://github.com/mrjvs.png?size=20" width="20"><span><a href="https://github.com/mrjvs">@mrjvs</a> for leading the port to React, and for the beautiful design.</span>
|
||||
</div>
|
||||
|
||||
<div style="display:flex;align-items:center;grid-gap:10px">
|
||||
<img src="https://github.com/binaryoverload.png?size=20" width="20"><span><a href="https://github.com/binaryoverload">@binaryoverload</a> for help rewriting the application into React and making the README look ✨ pretty ✨.</span>
|
||||
</div>
|
||||
|
||||
<div style="display:flex;align-items:center;grid-gap:10px">
|
||||
<img src="https://github.com/lem6ns.png?size=20" width="20"><span><a href="https://github.com/lem6ns">@lem6ns</a> for helpfully implementing extra scrapers.</span>
|
||||
</div>
|
||||
<table>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td align="center" valign="top" width="100px">
|
||||
<img src="https://images.weserv.nl/?url=https://github.com/JamesHawkinss.png&mask=circle"/><br />
|
||||
<sub><a href="https://github.com/JamesHawkinss">@JamesHawkinss</a></sub>
|
||||
</td>
|
||||
<td align="center" valign="top" width="100px">
|
||||
<img src="https://images.weserv.nl/?url=https://github.com/JipFr.png&mask=circle"/><br />
|
||||
<sub><a href="https://github.com/JipFr">@JipFr</a></sub>
|
||||
</td>
|
||||
<td align="center" valign="top" width="100px">
|
||||
<img src="https://images.weserv.nl/?url=https://github.com/mrjvs.png&mask=circle"/><br />
|
||||
<sub><a href="https://github.com/mrjvs">@mrjvs</a></sub>
|
||||
</td>
|
||||
<td align="center" valign="top" width="100px">
|
||||
<img src="https://images.weserv.nl/?url=https://github.com/binaryoverload.png&mask=circle"/><br />
|
||||
<sub><a href="https://github.com/binaryoverload">@binaryoverload</a></sub>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center" valign="top" width="100px">
|
||||
<img src="https://images.weserv.nl/?url=https://github.com/lem6ns.png&mask=circle"/><br />
|
||||
<sub><a href="https://github.com/lem6ns">@lem6ns</a></sub>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
|
|
@ -1,53 +1,56 @@
|
|||
{
|
||||
"auth": {
|
||||
"deviceNameLabel": "Device name",
|
||||
"deviceNamePlaceholder": "Muad'Dib's Nintendo Switch",
|
||||
"deviceNamePlaceholder": "Personal phone",
|
||||
"register": {
|
||||
"information": {
|
||||
"title": "Account information",
|
||||
"color1": "First color",
|
||||
"color2": "Second color",
|
||||
"color1": "Profile color one",
|
||||
"color2": "Profile color two",
|
||||
"icon": "User icon",
|
||||
"header": "Enter a name for your device and choose a user icon and colours"
|
||||
"header": "Enter a name for your device and pick colours and a user icon of your choosing",
|
||||
"next": "Next"
|
||||
}
|
||||
},
|
||||
"login": {
|
||||
"title": "Login to your account",
|
||||
"description": "Oh, you're asking for the key to my top-secret lair, also known as The Fortress of Wordsmithery, accessed only by reciting the sacred incantation of the 12-word passphrase!",
|
||||
"description": "Please enter your passphrase to login to your account",
|
||||
"validationError": "Invalid or incomplete passphrase",
|
||||
"submit": "Login",
|
||||
"passphraseLabel": "12-Word Passphrase",
|
||||
"passphraseLabel": "12-Word passphrase",
|
||||
"passphrasePlaceholder": "Passphrase"
|
||||
},
|
||||
"generate": {
|
||||
"title": "Your passphrase",
|
||||
"description": "If you lose this, you're a silly goose and will be posted on the wall of shame™️"
|
||||
"next": "I have saved my passphrase",
|
||||
"description": "Your passphase acts as your username and password. Make sure to keep it safe as you will need to enter it to login to your account"
|
||||
},
|
||||
"trust": {
|
||||
"title": "Do you trust this host?",
|
||||
"host": "Do you trust <0>{{hostname}}</0>?",
|
||||
"title": "Do you trust this server?",
|
||||
"host": "You are connecting to <0>{{hostname}}</0> - please confirm you trust it before making an account",
|
||||
"failed": {
|
||||
"title": "Failed to reach backend",
|
||||
"title": "Failed to reach server",
|
||||
"text": "Did you configure it correctly?"
|
||||
},
|
||||
"yes": "Trust",
|
||||
"yes": "I trust this server",
|
||||
"no": "Go back"
|
||||
},
|
||||
"verify": {
|
||||
"title": "Enter your passphrase",
|
||||
"description": "If you've already lost it, how will you ever be able to take care of a child?",
|
||||
"title": "Confirm your passphrase",
|
||||
"description": "Please enter your passphrase from earlier to confirm you have saved it and to create your account",
|
||||
"invalidData": "Data is not valid",
|
||||
"noMatch": "Passphrase doesn't match",
|
||||
"recaptchaFailed": "ReCaptcha validation failed",
|
||||
"passphraseLabel": "Your passphrase",
|
||||
"register": "Register"
|
||||
"passphraseLabel": "Your 12-word passphrase",
|
||||
"register": "Create account"
|
||||
}
|
||||
},
|
||||
"errors": {
|
||||
"details": "Error details",
|
||||
"reloadPage": "Reload the page",
|
||||
"showError": "Show error details",
|
||||
"badge": "It broke",
|
||||
"title": "That's an error boss"
|
||||
"title": "We encountered an error!"
|
||||
},
|
||||
"notFound": {
|
||||
"badge": "Not found",
|
||||
|
@ -77,9 +80,10 @@
|
|||
"scraping": {
|
||||
"notFound": {
|
||||
"badge": "Not found",
|
||||
"title": "Goo goo gaa gaa",
|
||||
"text": "Oh, my apowogies, sweetie! The itty-bitty movie-web did its utmost bestest, but alas, no wucky videos to be spotted anywhere (´⊙ω⊙`) Please don't be angwy, wittle movie-web ish twying so hard. Can you find it in your heart to forgive? UwU 💖",
|
||||
"homeButton": "Go home"
|
||||
"title": "We couldn't find that",
|
||||
"text": "We have searched through our providers and cannot find the media you are looking for! We do not host the media and have no control over what is available. Please click 'Show details' below for more details.",
|
||||
"homeButton": "Go home",
|
||||
"detailsButton": "Show details"
|
||||
},
|
||||
"items": {
|
||||
"pending": "Checking for videos...",
|
||||
|
@ -88,16 +92,16 @@
|
|||
}
|
||||
},
|
||||
"playbackError": {
|
||||
"badge": "Not found",
|
||||
"title": "Whoops, it broke!",
|
||||
"text": "Oh, my apowogies, sweetie! The itty-bitty movie-web did its utmost bestest, but alas, no wucky videos to be spotted anywhere (´⊙ω⊙`) Please don't be angwy, wittle movie-web ish twying so hard. Can you find it in your heart to forgive? UwU 💖",
|
||||
"badge": "Playback error",
|
||||
"title": "Failed to play video!",
|
||||
"text": "There was an error trying to play the media. Please try again.",
|
||||
"homeButton": "Go home",
|
||||
"errors": {
|
||||
"errorAborted": "The fetching of the associated resource was aborted by the user's request.",
|
||||
"errorAborted": "The fetching of the media was aborted by the user's request.",
|
||||
"errorNetwork": "Some kind of network error occurred which prevented the media from being successfully fetched, despite having previously been available.",
|
||||
"errorDecode": "Despite having previously been determined to be usable, an error occurred while trying to decode the media resource, resulting in an error.",
|
||||
"errorNotSupported": "The associated resource or media provider object has been found to be unsuitable.",
|
||||
"errorGenericMedia": "Unknown media error occured"
|
||||
"errorNotSupported": "The media or media provider object is not supported.",
|
||||
"errorGenericMedia": "Unknown media error occured."
|
||||
}
|
||||
},
|
||||
"metadata": {
|
||||
|
@ -109,8 +113,8 @@
|
|||
},
|
||||
"failed": {
|
||||
"badge": "Failed",
|
||||
"title": "Failed to load meta data",
|
||||
"text": "Oh, my apowogies, sweetie! The itty-bitty movie-web did its utmost bestest, but alas, no wucky videos to be spotted anywhere (´⊙ω⊙`) Please don't be angwy, wittle movie-web ish twying so hard. Can you find it in your heart to forgive? UwU 💖",
|
||||
"title": "Failed to load metadata",
|
||||
"text": "Could not load the media's metadata from TMDB. Please check whether TMDB is down or blocked on your internet connection.",
|
||||
"homeButton": "Go home"
|
||||
}
|
||||
},
|
||||
|
@ -131,8 +135,8 @@
|
|||
"menus": {
|
||||
"settings": {
|
||||
"videoSection": "Video settings",
|
||||
"experienceSection": "Viewing Experience",
|
||||
"enableCaptions": "Enable Captions",
|
||||
"experienceSection": "Viewing experience",
|
||||
"enableCaptions": "Enable captions",
|
||||
"captionItem": "Caption settings",
|
||||
"sourceItem": "Video sources",
|
||||
"playbackItem": "Playback settings",
|
||||
|
@ -156,11 +160,11 @@
|
|||
},
|
||||
"noEmbeds": {
|
||||
"title": "No embeds found",
|
||||
"text": "We were unable to find any embeds for this source, please try another."
|
||||
"text": "We were unable to find any embeds, please try a different source."
|
||||
},
|
||||
"failed": {
|
||||
"title": "Failed to scrape",
|
||||
"text": "We were unable to find any videos for this source. Don't come bitchin' to us about it, just try another source."
|
||||
"text": "There was an error while trying to find any videos, please try a different source."
|
||||
}
|
||||
},
|
||||
"captions": {
|
||||
|
@ -170,28 +174,28 @@
|
|||
"fixCapitals": "Fix capitalization",
|
||||
"delay": "Caption delay"
|
||||
},
|
||||
"customChoice": "Upload captions",
|
||||
"customChoice": "Select caption from file",
|
||||
"offChoice": "Off",
|
||||
"unknownLanguage": "Unknown"
|
||||
},
|
||||
"downloads": {
|
||||
"title": "Download",
|
||||
"disclaimer": "Downloads are taken directly from the provider. movie-web does not have control over how the downloads are provided.",
|
||||
"hlsExplanation": "Insert explanation for why you can't download HLS here",
|
||||
"hlsExplanation": "This media is a HLS stream which cannot be downloaded on movie-web.",
|
||||
"downloadVideo": "Download video",
|
||||
"downloadCaption": "Download current caption",
|
||||
"onPc": {
|
||||
"1": "On PC, right click the video and select <bold>Save video as</bold>",
|
||||
"1": "On PC, click the download button then, on the new page, right click the video and select <bold>Save video as</bold>",
|
||||
"title": "Downloading on PC",
|
||||
"shortTitle": "Download / PC"
|
||||
},
|
||||
"onAndroid": {
|
||||
"1": "To download on Android, <bold>tap and hold</bold> on the video, then select <bold>save</bold>.",
|
||||
"1": "To download on Android, click the download button then, on the new page, <bold>tap and hold</bold> on the video, then select <bold>save</bold>.",
|
||||
"title": "Downloading on Android",
|
||||
"shortTitle": "Download / Android"
|
||||
},
|
||||
"onIos": {
|
||||
"1": "To download on iOS, click <bold><ios_share /></bold>, then <bold>Save to Files <ios_files /></bold>. All that's left to do now is to pick a nice and cozy folder for your video!",
|
||||
"1": "To download on iOS, click the download button then, on the new page, click <bold><ios_share /></bold>, then <bold>Save to Files <ios_files /></bold>.",
|
||||
"title": "Downloading on iOS",
|
||||
"shortTitle": "Download / iOS"
|
||||
}
|
||||
|
@ -240,7 +244,7 @@
|
|||
"loadingApp": "Loading application",
|
||||
"loadingUserError": {
|
||||
"text": "Failed to load your profile",
|
||||
"textWithReset": "Failed to load your profile from your custom server, want to reset back to default?",
|
||||
"textWithReset": "Failed to load your profile from your custom server, want to reset back to the default server?",
|
||||
"reset": "Reset custom server"
|
||||
},
|
||||
"migration": {
|
||||
|
@ -249,7 +253,7 @@
|
|||
},
|
||||
"dmca": {
|
||||
"title": "DMCA",
|
||||
"text": "In an effort to address the copyright concerns associated with the website known as \"movie-web,\" the DMCA, or Digital Millennium Copyright Act, has been initiated to safeguard the intellectual property rights of content creators by reporting infringements on this platform, thereby adhering to legal protocols for takedown requests, which, like, you know, it's all about, like, maintaining the integrity of intellectual property, and, um, making sure, like, creators get their fair share, but then, it's, like, this intricate dance of digital legalities, where you have to, uh, like, navigate this labyrinth of code and bytes and, uh, send, you know, these, like, electronic documents that, um, point out the, uh, alleged infringement, and it's, like, this whole, like, teeter-totter of legality, where you're, like, balancing, um, the rights of the, you know, creators and the, um, operation of this, like, online, uh, entity, and, like, the DMCA, it's, like, this, um, powerful tool, but, uh, it's also, like, this, um, complex puzzle, where, you know, you're, like, seeking justice in the digital wilderness, and, uh, striving for harmony amidst the chaos of the internet, and, um, yeah, that's, like, the whole, like, DMCA-ing thing with movie-web, you know?"
|
||||
"text": "Welcome to movie-web's DMCA contact page! We respect intellectual property rights and want to address any copyright concerns swiftly. If you believe your copyrighted work has been improperly used on our platform, please send a detailed DMCA notice to the email below. Please include a description of the copyrighted material, your contact details, and a statement of good faith belief. We're committed to resolving these matters promptly and appreciate your cooperation in keeping movie-web a place that respects creativity and copyrights."
|
||||
}
|
||||
},
|
||||
"navigation": {
|
||||
|
@ -266,8 +270,7 @@
|
|||
},
|
||||
"actions": {
|
||||
"copy": "Copy",
|
||||
"copied": "Copied",
|
||||
"next": "Next"
|
||||
"copied": "Copied"
|
||||
},
|
||||
"settings": {
|
||||
"unsaved": "You have unsaved changes",
|
||||
|
@ -279,7 +282,7 @@
|
|||
"hostname": "Hostname",
|
||||
"backendUrl": "Backend URL",
|
||||
"userId": "User ID",
|
||||
"notLoggedIn": "Not logged in",
|
||||
"notLoggedIn": "You are not logged in",
|
||||
"appVersion": "App version",
|
||||
"backendVersion": "Backend version",
|
||||
"unknownVersion": "Unknown",
|
||||
|
@ -302,13 +305,13 @@
|
|||
"title": "Account",
|
||||
"register": {
|
||||
"title": "Sync to the cloud",
|
||||
"text": "Instantly share your watch progress between devices and keep them synced.",
|
||||
"text": "Share your watch progress between devices and keep them synced.",
|
||||
"cta": "Get started"
|
||||
},
|
||||
"profile": {
|
||||
"title": "Edit profile picture",
|
||||
"firstColor": "First color",
|
||||
"secondColor": "Second color",
|
||||
"firstColor": "Profile color one",
|
||||
"secondColor": "Profile color two",
|
||||
"userIcon": "User icon",
|
||||
"finish": "Finish editing"
|
||||
},
|
||||
|
@ -321,7 +324,7 @@
|
|||
"accountDetails": {
|
||||
"editProfile": "Edit",
|
||||
"deviceNameLabel": "Device name",
|
||||
"deviceNamePlaceholder": "Fremen tablet",
|
||||
"deviceNamePlaceholder": "Personal phone",
|
||||
"logoutButton": "Log out"
|
||||
},
|
||||
"actions": {
|
||||
|
@ -360,7 +363,7 @@
|
|||
},
|
||||
"server": {
|
||||
"label": "Custom server",
|
||||
"description": "To make the application function, all traffic is routed through proxies. Enable this if you want to bring your own workers.",
|
||||
"description": "If you would like to connect to a custom backend to store your data, enable this and provide the URL.",
|
||||
"urlLabel": "Custom server URL"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -81,20 +81,10 @@ export function UserAvatar(props: {
|
|||
);
|
||||
}
|
||||
|
||||
export function NoUserAvatar(props: {
|
||||
sizeClass?: string;
|
||||
iconClass?: string;
|
||||
}) {
|
||||
export function NoUserAvatar(props: { iconClass?: string }) {
|
||||
return (
|
||||
<div className="relative inline-block">
|
||||
<div
|
||||
className={classNames(
|
||||
props.sizeClass ?? "w-[2rem] h-[2rem]",
|
||||
"rounded-full overflow-hidden flex items-center justify-center text-type-dimmed hover:text-type-secondary bg-pill-background bg-opacity-50 hover:bg-opacity-100 transition-colors duration-100"
|
||||
)}
|
||||
>
|
||||
<Icon className={props.iconClass ?? "text-xl"} icon={Icons.MENU} />
|
||||
</div>
|
||||
<div className="relative inline-block p-1 text-type-dimmed">
|
||||
<Icon className={props.iconClass ?? "text-xl"} icon={Icons.MENU} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -109,7 +109,7 @@ export function LinksDropdown(props: { children: React.ReactNode }) {
|
|||
return (
|
||||
<div className="relative is-dropdown">
|
||||
<div
|
||||
className="cursor-pointer tabbable rounded-full flex gap-2 text-white items-center py-2 px-3 bg-pill-background bg-opacity-50"
|
||||
className="cursor-pointer tabbable rounded-full flex gap-2 text-white items-center py-2 px-3 bg-pill-background bg-opacity-50 hover:bg-pill-backgroundHover transition-[background,transform] duration-100 hover:scale-105"
|
||||
tabIndex={0}
|
||||
onClick={toggleOpen}
|
||||
onKeyUp={(evt) => evt.key === "Enter" && toggleOpen()}
|
||||
|
|
|
@ -99,6 +99,8 @@ export function Button(props: Props) {
|
|||
);
|
||||
}
|
||||
|
||||
// Sometimes you can't use normal button, due to not having access to a useHistory context
|
||||
// When that happens, use this!
|
||||
interface ButtonPlainProps {
|
||||
onClick?: () => void;
|
||||
children?: ReactNode;
|
||||
|
|
|
@ -21,7 +21,7 @@ export function Dropdown(props: DropdownProps) {
|
|||
<Listbox value={props.selectedItem} onChange={props.setSelectedItem}>
|
||||
{() => (
|
||||
<>
|
||||
<Listbox.Button className="relative w-full cursor-default rounded-lg bg-dropdown-background py-3 pl-3 pr-10 text-left text-white shadow-md focus:outline-none tabbable">
|
||||
<Listbox.Button className="relative w-full rounded-lg bg-dropdown-background py-3 pl-3 pr-10 text-left text-white shadow-md focus:outline-none tabbable cursor-pointer">
|
||||
<span className="flex gap-4 items-center truncate">
|
||||
{props.selectedItem.leftIcon
|
||||
? props.selectedItem.leftIcon
|
||||
|
@ -45,7 +45,7 @@ export function Dropdown(props: DropdownProps) {
|
|||
{props.options.map((opt) => (
|
||||
<Listbox.Option
|
||||
className={({ active }) =>
|
||||
`flex gap-4 items-center relative cursor-default select-none py-3 pl-4 pr-4 ${
|
||||
`cursor-pointer flex gap-4 items-center relative select-none py-3 pl-4 pr-4 ${
|
||||
active
|
||||
? "bg-background-secondaryHover text-type-link"
|
||||
: "text-white"
|
||||
|
|
|
@ -24,7 +24,7 @@ export const SearchBarInput = forwardRef<HTMLInputElement, SearchBarProps>(
|
|||
return (
|
||||
<Flare.Base
|
||||
className={c({
|
||||
"hover:flare-enabled group relative flex flex-col rounded-[28px] transition-colors sm:flex-row sm:items-center":
|
||||
"hover:flare-enabled group flex flex-col rounded-[28px] transition-colors sm:flex-row sm:items-center relative":
|
||||
true,
|
||||
"bg-search-background": !focused,
|
||||
"bg-search-focused": focused,
|
||||
|
@ -40,7 +40,6 @@ export const SearchBarInput = forwardRef<HTMLInputElement, SearchBarProps>(
|
|||
"bg-search-focused": focused,
|
||||
})}
|
||||
/>
|
||||
|
||||
<Flare.Child className="flex flex-1 flex-col">
|
||||
<div className="pointer-events-none absolute bottom-0 left-5 top-0 flex max-h-14 items-center text-search-icon">
|
||||
<Icon icon={Icons.SEARCH} />
|
||||
|
|
|
@ -55,7 +55,7 @@ export function Footer() {
|
|||
</h3>
|
||||
<p className="mt-3">{t("footer.legal.disclaimerText")}</p>
|
||||
</div>
|
||||
<div className="space-x-[0.5rem] -ml-3">
|
||||
<div className="flex flex-wrap gap-[0.5rem] -ml-3">
|
||||
<FooterLink icon={Icons.GITHUB} href={conf().GITHUB_LINK}>
|
||||
{t("footer.links.github")}
|
||||
</FooterLink>
|
||||
|
|
|
@ -59,6 +59,7 @@ export function Navigation(props: NavigationProps) {
|
|||
<BlurEllipsis positionClass="absolute" />
|
||||
</div>
|
||||
) : null}
|
||||
<div className="opacity-0 absolute inset-0 block h-20 pointer-events-auto" />
|
||||
<div
|
||||
className={`${
|
||||
props.bg ? "opacity-100" : "opacity-0"
|
||||
|
@ -77,8 +78,8 @@ export function Navigation(props: NavigationProps) {
|
|||
}}
|
||||
>
|
||||
<div className={classNames("fixed left-0 right-0 flex items-center")}>
|
||||
<div className="pointer-events-auto px-7 py-5 relative z-[60] flex flex-1 items-center">
|
||||
<div className="flex items-center flex-1 space-x-3">
|
||||
<div className="px-7 py-5 relative z-[60] flex flex-1 items-center justify-between">
|
||||
<div className="flex items-center space-x-3 pointer-events-auto">
|
||||
<Link className="block tabbable rounded-full" to="/">
|
||||
<BrandPill clickable />
|
||||
</Link>
|
||||
|
@ -99,7 +100,7 @@ export function Navigation(props: NavigationProps) {
|
|||
<IconPatch icon={Icons.GITHUB} clickable downsized />
|
||||
</a>
|
||||
</div>
|
||||
<div className="relative">
|
||||
<div className="relative pointer-events-auto">
|
||||
<LinksDropdown>
|
||||
{loggedIn ? <UserAvatar withName /> : <NoUserAvatar />}
|
||||
</LinksDropdown>
|
||||
|
|
|
@ -23,7 +23,7 @@ export function AutoPlayStart() {
|
|||
return (
|
||||
<div
|
||||
onClick={handleClick}
|
||||
className="group pointer-events-auto flex h-16 w-16 cursor-pointer items-center justify-center rounded-full text-white transition-[background-color,transform] hover:scale-125 active:scale-100"
|
||||
className="group pointer-events-auto flex h-16 w-16 cursor-pointer items-center justify-center bg-video-autoPlay-background hover:bg-video-autoPlay-hover rounded-full text-white transition-[background-color,transform] hover:scale-125 active:scale-100"
|
||||
>
|
||||
<Icon
|
||||
icon={Icons.PLAY}
|
||||
|
|
|
@ -2,7 +2,7 @@ import { Icons } from "@/components/Icon";
|
|||
import { VideoPlayerButton } from "@/components/player/internals/Button";
|
||||
import { usePlayerStore } from "@/stores/player/store";
|
||||
|
||||
export function Pause(props: { iconSizeClass?: string }) {
|
||||
export function Pause(props: { iconSizeClass?: string; className?: string }) {
|
||||
const display = usePlayerStore((s) => s.display);
|
||||
const { isPaused } = usePlayerStore((s) => s.mediaPlaying);
|
||||
|
||||
|
@ -13,6 +13,7 @@ export function Pause(props: { iconSizeClass?: string }) {
|
|||
|
||||
return (
|
||||
<VideoPlayerButton
|
||||
className={props.className}
|
||||
iconSizeClass={props.iconSizeClass}
|
||||
onClick={toggle}
|
||||
icon={isPaused ? Icons.PLAY : Icons.PAUSE}
|
||||
|
|
|
@ -230,7 +230,7 @@ export function CaptionSettingsView({ id }: { id: string }) {
|
|||
</Menu.BackLink>
|
||||
<Menu.Section className="space-y-6">
|
||||
<CaptionSetting
|
||||
label={t("player.menus.captions.settings.fixCapitals")}
|
||||
label={t("player.menus.captions.settings.delay")}
|
||||
max={10}
|
||||
min={-10}
|
||||
onChange={(v) => setDelay(v)}
|
||||
|
@ -241,7 +241,7 @@ export function CaptionSettingsView({ id }: { id: string }) {
|
|||
/>
|
||||
<div className="flex justify-between items-center">
|
||||
<Menu.FieldTitle>
|
||||
{t("player.menus.captions.settings.delay")}
|
||||
{t("player.menus.captions.settings.fixCapitals")}
|
||||
</Menu.FieldTitle>
|
||||
<div className="flex justify-center items-center">
|
||||
<Toggle
|
||||
|
|
|
@ -78,7 +78,7 @@ export function AccountCreatePart(props: AccountCreatePartProps) {
|
|||
</div>
|
||||
<LargeCardButtons>
|
||||
<Button theme="purple" onClick={() => nextStep()}>
|
||||
{t("actions.next")}
|
||||
{t("auth.register.information.next")}
|
||||
</Button>
|
||||
</LargeCardButtons>
|
||||
</LargeCard>
|
||||
|
|
|
@ -31,7 +31,7 @@ export function PassphraseGeneratePart(props: PassphraseGeneratePartProps) {
|
|||
|
||||
<LargeCardButtons>
|
||||
<Button theme="purple" onClick={() => props.onNext?.(mnemonic)}>
|
||||
{t("actions.next")}
|
||||
{t("auth.generate.next")}
|
||||
</Button>
|
||||
</LargeCardButtons>
|
||||
</LargeCard>
|
||||
|
|
|
@ -3,10 +3,13 @@ import { useTranslation } from "react-i18next";
|
|||
|
||||
import { Button } from "@/components/buttons/Button";
|
||||
import { Icon, Icons } from "@/components/Icon";
|
||||
import { Modal } from "@/components/overlays/Modal";
|
||||
import { DisplayError } from "@/components/player/display/displayInterface";
|
||||
|
||||
export function ErrorCard(props: { error: DisplayError | string }) {
|
||||
const [showErrorCard, setShowErrorCard] = useState(true);
|
||||
export function ErrorCard(props: {
|
||||
error: DisplayError | string;
|
||||
onClose: () => void;
|
||||
}) {
|
||||
const [hasCopied, setHasCopied] = useState(false);
|
||||
const hasCopiedUnsetDebounce = useRef<ReturnType<typeof setTimeout> | null>(
|
||||
null
|
||||
|
@ -32,8 +35,6 @@ export function ErrorCard(props: { error: DisplayError | string }) {
|
|||
hasCopiedUnsetDebounce.current = setTimeout(() => setHasCopied(false), 2e3);
|
||||
}
|
||||
|
||||
if (!showErrorCard) return null;
|
||||
|
||||
return (
|
||||
// I didn't put a <Transition> here because it'd fade out, then jump height weirdly
|
||||
<div className="w-full bg-errors-card p-6 rounded-lg">
|
||||
|
@ -60,7 +61,7 @@ export function ErrorCard(props: { error: DisplayError | string }) {
|
|||
<Button
|
||||
theme="secondary"
|
||||
padding="p-2 md:px-2"
|
||||
onClick={() => setShowErrorCard(false)}
|
||||
onClick={props.onClose}
|
||||
>
|
||||
<Icon icon={Icons.X} className="text-2xl" />
|
||||
</Button>
|
||||
|
@ -72,3 +73,35 @@ export function ErrorCard(props: { error: DisplayError | string }) {
|
|||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// use plain modal version if there is no access to history api (like in error boundary)
|
||||
export function ErrorCardInPlainModal(props: {
|
||||
error?: DisplayError | string;
|
||||
onClose: () => void;
|
||||
show?: boolean;
|
||||
}) {
|
||||
if (!props.show || !props.error) return null;
|
||||
return (
|
||||
<div className="fixed inset-0 w-full h-full bg-black bg-opacity-30 flex justify-center items-center p-12">
|
||||
<div className="max-w-2xl">
|
||||
<ErrorCard error={props.error} onClose={props.onClose} />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export function ErrorCardInModal(props: {
|
||||
error?: DisplayError | string;
|
||||
id: string;
|
||||
onClose: () => void;
|
||||
}) {
|
||||
if (!props.error) return null;
|
||||
|
||||
return (
|
||||
<Modal id={props.id}>
|
||||
<div className="max-w-2xl pointer-events-auto">
|
||||
<ErrorCard error={props.error} onClose={props.onClose} />
|
||||
</div>
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -1,27 +1,24 @@
|
|||
import { useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
import { ButtonPlain } from "@/components/buttons/Button";
|
||||
import { Icons } from "@/components/Icon";
|
||||
import { IconPill } from "@/components/layout/IconPill";
|
||||
import { DisplayError } from "@/components/player/display/displayInterface";
|
||||
import { Title } from "@/components/text/Title";
|
||||
import { Paragraph } from "@/components/utils/Text";
|
||||
import { ErrorContainer, ErrorLayout } from "@/pages/layouts/ErrorLayout";
|
||||
import { ErrorCard } from "@/pages/parts/errors/ErrorCard";
|
||||
import { ErrorCardInPlainModal } from "@/pages/parts/errors/ErrorCard";
|
||||
|
||||
export function ErrorPart(props: { error: any; errorInfo: any }) {
|
||||
const { t } = useTranslation();
|
||||
const [showErrorCard, setShowErrorCard] = useState(false);
|
||||
|
||||
const maxLineCount = 5;
|
||||
const errorLines = (props.errorInfo.componentStack || "")
|
||||
.split("\n")
|
||||
.slice(0, maxLineCount);
|
||||
|
||||
const error: DisplayError = {
|
||||
errorName: "What does this do",
|
||||
type: "global",
|
||||
message: errorLines.join("\n"),
|
||||
};
|
||||
const error = `${props.error.toString()}\n${errorLines.join("\n")}`;
|
||||
|
||||
return (
|
||||
<div className="relative flex flex-1 flex-col min-h-screen">
|
||||
|
@ -30,15 +27,30 @@ export function ErrorPart(props: { error: any; errorInfo: any }) {
|
|||
<ErrorContainer maxWidth="max-w-2xl">
|
||||
<IconPill icon={Icons.EYE_SLASH}>{t("errors.badge")}</IconPill>
|
||||
<Title>{t("errors.title")}</Title>
|
||||
|
||||
<Paragraph>{props.error.toString()}</Paragraph>
|
||||
<ErrorCard error={error} />
|
||||
<ButtonPlain
|
||||
theme="purple"
|
||||
className="mt-6 md:px-12 p-2.5"
|
||||
onClick={() => window.location.reload()}
|
||||
>
|
||||
{t("errors.reloadPage")}
|
||||
</ButtonPlain>
|
||||
<ErrorCardInPlainModal
|
||||
show={showErrorCard}
|
||||
onClose={() => setShowErrorCard(false)}
|
||||
error={error}
|
||||
/>
|
||||
|
||||
<div className="flex gap-3">
|
||||
<ButtonPlain
|
||||
theme="secondary"
|
||||
className="mt-6 md:px-12 p-2.5"
|
||||
onClick={() => window.location.reload()}
|
||||
>
|
||||
{t("errors.reloadPage")}
|
||||
</ButtonPlain>
|
||||
<ButtonPlain
|
||||
theme="purple"
|
||||
className="mt-6 md:px-12 p-2.5"
|
||||
onClick={() => setShowErrorCard(true)}
|
||||
>
|
||||
{t("errors.showError")}
|
||||
</ButtonPlain>
|
||||
</div>
|
||||
</ErrorContainer>
|
||||
</ErrorLayout>
|
||||
</div>
|
||||
|
|
|
@ -3,16 +3,18 @@ import { useTranslation } from "react-i18next";
|
|||
import { Button } from "@/components/buttons/Button";
|
||||
import { Icons } from "@/components/Icon";
|
||||
import { IconPill } from "@/components/layout/IconPill";
|
||||
import { useModal } from "@/components/overlays/Modal";
|
||||
import { Paragraph } from "@/components/text/Paragraph";
|
||||
import { Title } from "@/components/text/Title";
|
||||
import { ErrorContainer, ErrorLayout } from "@/pages/layouts/ErrorLayout";
|
||||
import { usePlayerStore } from "@/stores/player/store";
|
||||
|
||||
import { ErrorCard } from "../errors/ErrorCard";
|
||||
import { ErrorCardInModal } from "../errors/ErrorCard";
|
||||
|
||||
export function PlaybackErrorPart() {
|
||||
const { t } = useTranslation();
|
||||
const playbackError = usePlayerStore((s) => s.interface.error);
|
||||
const modal = useModal("error");
|
||||
|
||||
return (
|
||||
<ErrorLayout>
|
||||
|
@ -20,19 +22,31 @@ export function PlaybackErrorPart() {
|
|||
<IconPill icon={Icons.WAND}>{t("player.playbackError.badge")}</IconPill>
|
||||
<Title>{t("player.playbackError.title")}</Title>
|
||||
<Paragraph>{t("player.playbackError.text")}</Paragraph>
|
||||
<Button
|
||||
href="/"
|
||||
theme="purple"
|
||||
padding="md:px-12 p-2.5"
|
||||
className="mt-6"
|
||||
>
|
||||
{t("player.playbackError.homeButton")}
|
||||
</Button>
|
||||
</ErrorContainer>
|
||||
<ErrorContainer maxWidth="max-w-[45rem]">
|
||||
{/* Error */}
|
||||
{playbackError ? <ErrorCard error={playbackError} /> : null}
|
||||
<div className="flex gap-3">
|
||||
<Button
|
||||
href="/"
|
||||
theme="secondary"
|
||||
padding="md:px-12 p-2.5"
|
||||
className="mt-6"
|
||||
>
|
||||
{t("player.playbackError.homeButton")}
|
||||
</Button>
|
||||
<Button
|
||||
onClick={() => modal.show()}
|
||||
theme="purple"
|
||||
padding="md:px-12 p-2.5"
|
||||
className="mt-6"
|
||||
>
|
||||
{t("errors.showError")}
|
||||
</Button>
|
||||
</div>
|
||||
</ErrorContainer>
|
||||
{/* Error */}
|
||||
<ErrorCardInModal
|
||||
onClose={() => modal.hide()}
|
||||
error={playbackError}
|
||||
id={modal.id}
|
||||
/>
|
||||
</ErrorLayout>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -18,6 +18,7 @@ export function PlayerPart(props: PlayerPartProps) {
|
|||
const { showTargets, showTouchTargets } = useShouldShowControls();
|
||||
const status = usePlayerStore((s) => s.status);
|
||||
const { isMobile } = useIsMobile();
|
||||
const isLoading = usePlayerStore((s) => s.mediaPlaying.isLoading);
|
||||
|
||||
return (
|
||||
<Player.Container onLoad={props.onLoad} showingControls={showTargets}>
|
||||
|
@ -38,10 +39,13 @@ export function PlayerPart(props: PlayerPartProps) {
|
|||
|
||||
<Player.CenterMobileControls
|
||||
className="text-white"
|
||||
show={showTouchTargets}
|
||||
show={showTouchTargets && status === playerStatus.PLAYING}
|
||||
>
|
||||
<Player.SkipBackward iconSizeClass="text-3xl" />
|
||||
<Player.Pause iconSizeClass="text-5xl" />
|
||||
<Player.Pause
|
||||
iconSizeClass="text-5xl"
|
||||
className={isLoading ? "opacity-0" : "opacity-100"}
|
||||
/>
|
||||
<Player.SkipForward iconSizeClass="text-3xl" />
|
||||
</Player.CenterMobileControls>
|
||||
|
||||
|
@ -60,38 +64,54 @@ export function PlayerPart(props: PlayerPartProps) {
|
|||
<BrandPill />
|
||||
</div>
|
||||
<div className="flex sm:hidden items-center justify-end">
|
||||
<Player.Airplay />
|
||||
<Player.Chromecast />
|
||||
{status === playerStatus.PLAYING ? (
|
||||
<>
|
||||
<Player.Airplay />
|
||||
<Player.Chromecast />
|
||||
</>
|
||||
) : null}
|
||||
</div>
|
||||
</div>
|
||||
</Player.TopControls>
|
||||
|
||||
<Player.BottomControls show={showTargets}>
|
||||
<div className="flex items-center space-x-3">
|
||||
{isMobile ? <Player.Time short /> : null}
|
||||
<Player.ProgressBar />
|
||||
{status === playerStatus.PLAYING ? (
|
||||
<>
|
||||
{isMobile ? <Player.Time short /> : null}
|
||||
<Player.ProgressBar />
|
||||
</>
|
||||
) : null}
|
||||
</div>
|
||||
<div className="hidden lg:flex justify-between">
|
||||
<Player.LeftSideControls>
|
||||
<Player.Pause />
|
||||
<Player.SkipBackward />
|
||||
<Player.SkipForward />
|
||||
<Player.Volume />
|
||||
<Player.Time />
|
||||
{status === playerStatus.PLAYING ? (
|
||||
<>
|
||||
<Player.Pause />
|
||||
<Player.SkipBackward />
|
||||
<Player.SkipForward />
|
||||
<Player.Volume />
|
||||
<Player.Time />
|
||||
</>
|
||||
) : null}
|
||||
</Player.LeftSideControls>
|
||||
<div className="flex items-center space-x-3">
|
||||
<Player.Episodes />
|
||||
<Player.Pip />
|
||||
<Player.Airplay />
|
||||
<Player.Chromecast />
|
||||
<Player.Settings />
|
||||
{status === playerStatus.PLAYING ? (
|
||||
<>
|
||||
<Player.Pip />
|
||||
<Player.Airplay />
|
||||
<Player.Chromecast />
|
||||
<Player.Settings />
|
||||
</>
|
||||
) : null}
|
||||
<Player.Fullscreen />
|
||||
</div>
|
||||
</div>
|
||||
<div className="grid grid-cols-[2.5rem,1fr,2.5rem] gap-3 lg:hidden">
|
||||
<div />
|
||||
<div className="flex justify-center space-x-3">
|
||||
<Player.Pip />
|
||||
{status === playerStatus.PLAYING ? <Player.Pip /> : null}
|
||||
<Player.Episodes />
|
||||
<Player.Settings />
|
||||
</div>
|
||||
|
|
|
@ -4,12 +4,13 @@ import { useTranslation } from "react-i18next";
|
|||
import { Button } from "@/components/buttons/Button";
|
||||
import { Icons } from "@/components/Icon";
|
||||
import { IconPill } from "@/components/layout/IconPill";
|
||||
import { useModal } from "@/components/overlays/Modal";
|
||||
import { Paragraph } from "@/components/text/Paragraph";
|
||||
import { Title } from "@/components/text/Title";
|
||||
import { ScrapingItems, ScrapingSegment } from "@/hooks/useProviderScrape";
|
||||
import { ErrorContainer, ErrorLayout } from "@/pages/layouts/ErrorLayout";
|
||||
|
||||
import { ErrorCard } from "../errors/ErrorCard";
|
||||
import { ErrorCardInModal } from "../errors/ErrorCard";
|
||||
|
||||
export interface ScrapeErrorPartProps {
|
||||
data: {
|
||||
|
@ -20,6 +21,8 @@ export interface ScrapeErrorPartProps {
|
|||
|
||||
export function ScrapeErrorPart(props: ScrapeErrorPartProps) {
|
||||
const { t } = useTranslation();
|
||||
const modal = useModal("error");
|
||||
|
||||
const error = useMemo(() => {
|
||||
const data = props.data;
|
||||
const amountError = Object.values(data.sources).filter(
|
||||
|
@ -43,19 +46,32 @@ export function ScrapeErrorPart(props: ScrapeErrorPartProps) {
|
|||
</IconPill>
|
||||
<Title>{t("player.scraping.notFound.title")}</Title>
|
||||
<Paragraph>{t("player.scraping.notFound.text")}</Paragraph>
|
||||
<Button
|
||||
href="/"
|
||||
theme="purple"
|
||||
padding="md:px-12 p-2.5"
|
||||
className="mt-6"
|
||||
>
|
||||
{t("player.scraping.notFound.homeButton")}
|
||||
</Button>
|
||||
</ErrorContainer>
|
||||
<ErrorContainer maxWidth="max-w-[45rem]">
|
||||
{/* Error */}
|
||||
{error ? <ErrorCard error={error} /> : null}
|
||||
<div className="flex gap-3">
|
||||
<Button
|
||||
href="/"
|
||||
theme="secondary"
|
||||
padding="md:px-12 p-2.5"
|
||||
className="mt-6"
|
||||
>
|
||||
{t("player.scraping.notFound.homeButton")}
|
||||
</Button>
|
||||
<Button
|
||||
onClick={() => modal.show()}
|
||||
theme="purple"
|
||||
padding="md:px-12 p-2.5"
|
||||
className="mt-6"
|
||||
>
|
||||
{t("player.scraping.notFound.detailsButton")}
|
||||
</Button>
|
||||
</div>
|
||||
</ErrorContainer>
|
||||
{error ? (
|
||||
<ErrorCardInModal
|
||||
id={modal.id}
|
||||
onClose={() => modal.hide()}
|
||||
error={error}
|
||||
/>
|
||||
) : null}
|
||||
</ErrorLayout>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import classNames from "classnames";
|
||||
import { useState } from "react";
|
||||
import { Helmet } from "react-helmet-async";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
import { Icon, Icons } from "@/components/Icon";
|
||||
|
@ -29,6 +30,11 @@ export function CaptionPreview(props: {
|
|||
"fixed inset-0 z-[60]": props.fullscreen,
|
||||
})}
|
||||
>
|
||||
{props.fullscreen && props.show ? (
|
||||
<Helmet>
|
||||
<html data-no-scroll />
|
||||
</Helmet>
|
||||
) : null}
|
||||
<Transition animation="fade" show={props.show}>
|
||||
<div
|
||||
className="absolute inset-0 pointer-events-auto"
|
||||
|
|
|
@ -115,10 +115,10 @@ function BackendEdit({ backendUrl, setBackendUrl }: BackendEditProps) {
|
|||
<div className="flex justify-between items-center gap-4">
|
||||
<div className="my-3">
|
||||
<p className="text-white font-bold mb-3">
|
||||
{t("settings.connections.workers.label")}
|
||||
{t("settings.connections.server.label")}
|
||||
</p>
|
||||
<p className="max-w-[20rem] font-medium">
|
||||
{t("settings.connections.workers.description")}
|
||||
{t("settings.connections.server.description")}
|
||||
</p>
|
||||
</div>
|
||||
<div>
|
||||
|
@ -132,7 +132,7 @@ function BackendEdit({ backendUrl, setBackendUrl }: BackendEditProps) {
|
|||
<>
|
||||
<Divider marginClass="my-6 px-8 box-content -mx-8" />
|
||||
<p className="text-white font-bold mb-3">
|
||||
{t("settings.connections.workers.urlLabel")}
|
||||
{t("settings.connections.server.urlLabel")}
|
||||
</p>
|
||||
<AuthInputBox onChange={setBackendUrl} value={backendUrl ?? ""} />
|
||||
</>
|
||||
|
|
|
@ -9,8 +9,8 @@ export const defaultTheme = {
|
|||
|
||||
// Branding
|
||||
pill: {
|
||||
background: "#1C1C36",
|
||||
backgroundHover: "#1C1C36",
|
||||
background: "#2e2e4d",
|
||||
backgroundHover: "#3d3d61",
|
||||
highlight: "#714C97",
|
||||
},
|
||||
|
||||
|
@ -97,7 +97,7 @@ export const defaultTheme = {
|
|||
dropdown: {
|
||||
background: "#171728",
|
||||
altBackground: "#151525",
|
||||
highlight: "#FCEC61",
|
||||
highlight: "#afa349",
|
||||
highlightHover: "#FCEC61",
|
||||
text: "#846D95",
|
||||
secondary: "#73739D",
|
||||
|
@ -179,6 +179,11 @@ export const defaultTheme = {
|
|||
video: {
|
||||
buttonBackground: "#444B5C",
|
||||
|
||||
autoPlay: {
|
||||
background: "#161C26",
|
||||
hover: "#252533"
|
||||
},
|
||||
|
||||
scraping: {
|
||||
card: "#161620",
|
||||
error: "#E44F4F",
|
||||
|
@ -211,6 +216,7 @@ export const defaultTheme = {
|
|||
active: "#0D1317",
|
||||
},
|
||||
|
||||
|
||||
type: {
|
||||
main: "#617A8A",
|
||||
secondary: "#374A56",
|
||||
|
|
Loading…
Reference in a new issue