diff --git a/package.json b/package.json index 9a071aeb..f185e7f0 100644 --- a/package.json +++ b/package.json @@ -1,8 +1,8 @@ { - "name": "sudo-flix", + "name": "movie-web", "version": "4.6.5", "private": true, - "homepage": "https://sudo-flix.lol", + "homepage": "https://github.com/movie-web/movie-web", "scripts": { "dev": "vite", "build": "vite build", @@ -36,7 +36,6 @@ "@scure/bip39": "^1.3.0", "@sozialhelden/ietf-language-tags": "^5.4.2", "@types/node-forge": "^1.3.11", - "@vercel/analytics": "^1.2.2", "classnames": "^2.5.1", "core-js": "^3.36.1", "detect-browser": "^5.3.0", @@ -61,6 +60,7 @@ "react-google-recaptcha-v3": "^1.10.1", "react-helmet-async": "^2.0.4", "react-i18next": "^14.1.0", + "react-lazy-load-image-component": "^1.6.0", "react-lazy-with-preload": "^2.2.1", "react-router-dom": "^6.22.3", "react-sticky-el": "^2.1.0", @@ -75,7 +75,6 @@ "@babel/core": "^7.24.3", "@babel/preset-env": "^7.24.3", "@babel/preset-typescript": "^7.24.1", - "@nuxt/eslint-config": "^0.2.0", "@rollup/wasm-node": "^4.13.2", "@types/chromecast-caf-sender": "^1.0.9", "@types/crypto-js": "^4.2.2", @@ -86,9 +85,10 @@ "@types/lodash.throttle": "^4.1.9", "@types/node": "^20.12.2", "@types/pako": "^2.0.3", - "@types/react": "^18.2.73", + "@types/react": "^18.2.74", "@types/react-dom": "^18.2.23", "@types/react-helmet": "^6.1.11", + "@types/react-lazy-load-image-component": "^1.6.3", "@types/react-router": "^5.1.20", "@types/react-router-dom": "^5.3.3", "@types/react-stickynode": "^4.0.3", @@ -127,7 +127,8 @@ "vite-plugin-package-version": "^1.1.0", "vite-plugin-pwa": "^0.17.5", "vite-plugin-static-copy": "^1.0.2", - "vitest": "^1.4.0" + "vitest": "^1.4.0", + "workbox-window": "^7.0.0" }, "pnpm": { "overrides": { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 9b3de944..62948f97 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -42,9 +42,6 @@ dependencies: '@types/node-forge': specifier: ^1.3.11 version: 1.3.11 - '@vercel/analytics': - specifier: ^1.2.2 - version: 1.2.2(react@18.2.0) classnames: specifier: ^2.5.1 version: 2.5.1 @@ -117,6 +114,9 @@ dependencies: react-i18next: specifier: ^14.1.0 version: 14.1.0(i18next@23.10.1)(react-dom@18.2.0)(react@18.2.0) + react-lazy-load-image-component: + specifier: ^1.6.0 + version: 1.6.0(react-dom@18.2.0)(react@18.2.0) react-lazy-with-preload: specifier: ^2.2.1 version: 2.2.1 @@ -143,7 +143,7 @@ dependencies: version: 2.1.2 zustand: specifier: ^4.5.2 - version: 4.5.2(@types/react@18.2.73)(immer@10.0.4)(react@18.2.0) + version: 4.5.2(@types/react@18.2.74)(immer@10.0.4)(react@18.2.0) devDependencies: '@babel/core': @@ -155,9 +155,6 @@ devDependencies: '@babel/preset-typescript': specifier: ^7.24.1 version: 7.24.1(@babel/core@7.24.3) - '@nuxt/eslint-config': - specifier: ^0.2.0 - version: 0.2.0(eslint@8.57.0) '@rollup/wasm-node': specifier: ^4.13.2 version: 4.13.2 @@ -189,14 +186,17 @@ devDependencies: specifier: ^2.0.3 version: 2.0.3 '@types/react': - specifier: ^18.2.73 - version: 18.2.73 + specifier: ^18.2.74 + version: 18.2.74 '@types/react-dom': specifier: ^18.2.23 version: 18.2.23 '@types/react-helmet': specifier: ^6.1.11 version: 6.1.11 + '@types/react-lazy-load-image-component': + specifier: ^1.6.3 + version: 1.6.3 '@types/react-router': specifier: ^5.1.20 version: 5.1.20 @@ -314,6 +314,9 @@ devDependencies: vitest: specifier: ^1.4.0 version: 1.4.0(@types/node@20.12.2)(jsdom@23.2.0) + workbox-window: + specifier: ^7.0.0 + version: 7.0.0 packages: @@ -1939,21 +1942,6 @@ packages: fastq: 1.17.1 dev: true - /@nuxt/eslint-config@0.2.0(eslint@8.57.0): - resolution: {integrity: sha512-NeJX8TLcnNAjQFiDs3XhP+9CHKK8jaKsP7eUyCSrQdgY7nqWe7VJx64lwzx5FTT4cW3RHMEyH+Y0qzLGYYoa/A==} - peerDependencies: - eslint: ^8.48.0 - dependencies: - '@rushstack/eslint-patch': 1.10.1 - '@typescript-eslint/eslint-plugin': 6.21.0(@typescript-eslint/parser@6.21.0)(eslint@8.57.0)(typescript@5.4.3) - '@typescript-eslint/parser': 6.21.0(eslint@8.57.0)(typescript@5.4.3) - eslint: 8.57.0 - eslint-plugin-vue: 9.24.0(eslint@8.57.0) - typescript: 5.4.3 - transitivePeerDependencies: - - supports-color - dev: true - /@pkgjs/parseargs@0.11.0: resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==} engines: {node: '>=14'} @@ -2094,10 +2082,6 @@ packages: optionalDependencies: fsevents: 2.3.3 - /@rushstack/eslint-patch@1.10.1: - resolution: {integrity: sha512-S3Kq8e7LqxkA9s7HKLqXGTGck1uwis5vAXan3FnU5yw1Ec5hsSGnq4s/UCaSqABPOnOTg7zASLyst7+ohgWexg==} - dev: true - /@scure/base@1.1.6: resolution: {integrity: sha512-ok9AWwhcgYuGG3Zfhyqg+zwl+Wn5uE+dwC0NV/2qQkx4dABbb/bx96vWu8NSj+BNjjSjno+JRYRjle1jV08k3g==} dev: false @@ -2280,20 +2264,27 @@ packages: /@types/react-dom@18.2.23: resolution: {integrity: sha512-ZQ71wgGOTmDYpnav2knkjr3qXdAFu0vsk8Ci5w3pGAIdj7/kKAyn+VsQDhXsmzzzepAiI9leWMmubXz690AI/A==} dependencies: - '@types/react': 18.2.73 + '@types/react': 18.2.74 dev: true /@types/react-helmet@6.1.11: resolution: {integrity: sha512-0QcdGLddTERotCXo3VFlUSWO3ztraw8nZ6e3zJSgG7apwV5xt+pJUS8ewPBqT4NYB1optGLprNQzFleIY84u/g==} dependencies: - '@types/react': 18.2.73 + '@types/react': 18.2.74 + dev: true + + /@types/react-lazy-load-image-component@1.6.3: + resolution: {integrity: sha512-HsIsYz7yWWTh/bftdzGnijKD26JyofLRqM/RM80sxs7Gk13G83ew8R/ra2XzXuiZfjNEjAq/Va+NBHFF9ciwxA==} + dependencies: + '@types/react': 18.2.74 + csstype: 3.1.3 dev: true /@types/react-router-dom@5.3.3: resolution: {integrity: sha512-kpqnYK4wcdm5UaWI3fLcELopqLrHgLqNsdpHauzlQktfkHL3npOSwtj1Uz9oKBAzs7lFtVkV8j83voAz2D8fhw==} dependencies: '@types/history': 4.7.11 - '@types/react': 18.2.73 + '@types/react': 18.2.74 '@types/react-router': 5.1.20 dev: true @@ -2301,23 +2292,23 @@ packages: resolution: {integrity: sha512-jGjmu/ZqS7FjSH6owMcD5qpq19+1RS9DeVRqfl1FeBMxTDQAGwlMWOcs52NDoXaNKyG3d1cYQFMs9rCrb88o9Q==} dependencies: '@types/history': 4.7.11 - '@types/react': 18.2.73 + '@types/react': 18.2.74 dev: true /@types/react-stickynode@4.0.3: resolution: {integrity: sha512-K7YkwdhXQE4YVxIVweix4nkpdG4onm/dcnKK+qCj0vgUrNiKng+09zOfjF5AlOcC1HQkg5yxVLwp/0AzT84R0w==} dependencies: - '@types/react': 18.2.73 + '@types/react': 18.2.74 dev: true /@types/react-transition-group@4.4.10: resolution: {integrity: sha512-hT/+s0VQs2ojCX823m60m5f0sL5idt9SO6Tj6Dg+rdphGPIeJbJ6CxvBYkgkGKrYeDjvIpKTR38UzmtHJOGW3Q==} dependencies: - '@types/react': 18.2.73 + '@types/react': 18.2.74 dev: true - /@types/react@18.2.73: - resolution: {integrity: sha512-XcGdod0Jjv84HOC7N5ziY3x+qL0AfmubvKOZ9hJjJ2yd5EE+KYjWhdOjt387e9HPheHkdggF9atTifMRtyAaRA==} + /@types/react@18.2.74: + resolution: {integrity: sha512-9AEqNZZyBx8OdZpxzQlaFEVCSFUM2YXJH46yPOiOpm078k6ZLOCcuAzGum/zK8YBwY+dbahVNbHrbgrAwIRlqw==} dependencies: '@types/prop-types': 15.7.12 csstype: 3.1.3 @@ -2472,21 +2463,6 @@ packages: resolution: {integrity: sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==} dev: true - /@vercel/analytics@1.2.2(react@18.2.0): - resolution: {integrity: sha512-X0rctVWkQV1e5Y300ehVNqpOfSOufo7ieA5PIdna8yX/U7Vjz0GFsGf4qvAhxV02uQ2CVt7GYcrFfddXXK2Y4A==} - peerDependencies: - next: '>= 13' - react: ^18 || ^19 - peerDependenciesMeta: - next: - optional: true - react: - optional: true - dependencies: - react: 18.2.0 - server-only: 0.0.1 - dev: false - /@vitejs/plugin-react@4.2.1(vite@5.2.7): resolution: {integrity: sha512-oojO9IDc4nCUUi8qIR11KoQm0XFFLIwsRBwHRR4d/88IWghn1y6ckz/bJ8GHDCsYEJee8mDzqtJxh15/cisJNQ==} engines: {node: ^14.18.0 || >=16.0.0} @@ -2765,7 +2741,7 @@ packages: postcss: '>=8.4.31' dependencies: browserslist: 4.23.0 - caniuse-lite: 1.0.30001603 + caniuse-lite: 1.0.30001605 fraction.js: 4.3.7 normalize-range: 0.1.2 picocolors: 1.0.0 @@ -2843,6 +2819,7 @@ packages: /boolbase@1.0.0: resolution: {integrity: sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==} + dev: false /brace-expansion@1.1.11: resolution: {integrity: sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==} @@ -2868,7 +2845,7 @@ packages: engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} hasBin: true dependencies: - caniuse-lite: 1.0.30001603 + caniuse-lite: 1.0.30001605 electron-to-chromium: 1.4.723 node-releases: 2.0.14 update-browserslist-db: 1.0.13(browserslist@4.23.0) @@ -2908,8 +2885,8 @@ packages: engines: {node: '>= 6'} dev: true - /caniuse-lite@1.0.30001603: - resolution: {integrity: sha512-iL2iSS0eDILMb9n5yKQoTBim9jMZ0Yrk8g0N9K7UzYyWnfIKzXBZD5ngpM37ZcL/cv0Mli8XtVMRYMQAfFpi5Q==} + /caniuse-lite@1.0.30001605: + resolution: {integrity: sha512-nXwGlFWo34uliI9z3n6Qc0wZaf7zaZWA1CPZ169La5mV3I/gem7bst0vr5XQH5TJXZIMfDeZyOrZnSlVzKxxHQ==} /chai@4.4.1: resolution: {integrity: sha512-13sOfMv2+DWduEU+/xbun3LScLoqN17nBeTLUsmDfKdoiC1fr0n9PU4guu4AhRcOVFk/sW8LyZWHuhWtQZiF+g==} @@ -3785,25 +3762,6 @@ packages: string.prototype.matchall: 4.0.11 dev: true - /eslint-plugin-vue@9.24.0(eslint@8.57.0): - resolution: {integrity: sha512-9SkJMvF8NGMT9aQCwFc5rj8Wo1XWSMSHk36i7ZwdI614BU7sIOR28ZjuFPKp8YGymZN12BSEbiSwa7qikp+PBw==} - engines: {node: ^14.17.0 || >=16.0.0} - peerDependencies: - eslint: ^6.2.0 || ^7.0.0 || ^8.0.0 - dependencies: - '@eslint-community/eslint-utils': 4.4.0(eslint@8.57.0) - eslint: 8.57.0 - globals: 13.24.0 - natural-compare: 1.4.0 - nth-check: 2.1.1 - postcss-selector-parser: 6.0.16 - semver: 7.6.0 - vue-eslint-parser: 9.4.2(eslint@8.57.0) - xml-name-validator: 4.0.0 - transitivePeerDependencies: - - supports-color - dev: true - /eslint-scope@7.2.2: resolution: {integrity: sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} @@ -4923,7 +4881,6 @@ packages: /lodash.debounce@4.0.8: resolution: {integrity: sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==} - dev: true /lodash.isequal@4.5.0: resolution: {integrity: sha512-pDo3lu8Jhfjqls6GkMgpahsF9kCyayhgykjyLMNFTKWrpVdAQtYyB4muAMWozBB4ig/dtWAmsMxLEI8wuz+DYQ==} @@ -4940,6 +4897,10 @@ packages: resolution: {integrity: sha512-HDWXG8isMntAyRF5vZ7xKuEvOhT4AhlRt/3czTSjvGUxjYCBVRQY48ViDHyfYz9VIoBkW4TMGQNapx+l3RUwdA==} dev: true + /lodash.throttle@4.1.1: + resolution: {integrity: sha512-wIkUCfVKpVsWo3JSZlc+8MB5it+2AN5W8J7YVMST30UrvcQNZ1Okbj+rbVniijTWE6FGYy4XJq/rHkas8qJMLQ==} + dev: false + /lodash@4.17.21: resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==} dev: true @@ -5197,6 +5158,7 @@ packages: resolution: {integrity: sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==} dependencies: boolbase: 1.0.0 + dev: false /object-assign@4.1.1: resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} @@ -5710,6 +5672,18 @@ packages: resolution: {integrity: sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==} dev: true + /react-lazy-load-image-component@1.6.0(react-dom@18.2.0)(react@18.2.0): + resolution: {integrity: sha512-8KFkDTgjh+0+PVbH+cx0AgxLGbdTsxWMnxXzU5HEUztqewk9ufQAu8cstjZhyvtMIPsdMcPZfA0WAa7HtjQbBQ==} + peerDependencies: + react: ^15.x.x || ^16.x.x || ^17.x.x || ^18.x.x + react-dom: ^15.x.x || ^16.x.x || ^17.x.x || ^18.x.x + dependencies: + lodash.debounce: 4.0.8 + lodash.throttle: 4.1.1 + react: 18.2.0 + react-dom: 18.2.0(react@18.2.0) + dev: false + /react-lazy-with-preload@2.2.1: resolution: {integrity: sha512-ONSb8gizLE5jFpdHAclZ6EAAKuFX2JydnFXPPPjoUImZlLjGtKzyBS8SJgJq7CpLgsGKh9QCZdugJyEEOVC16Q==} dev: false @@ -5944,7 +5918,7 @@ packages: jest-worker: 26.6.2 rollup: /@rollup/wasm-node@4.13.2 serialize-javascript: 4.0.0 - terser: 5.30.1 + terser: 5.30.2 dev: true /rollup-plugin-visualizer@5.12.0(@rollup/wasm-node@4.13.2): @@ -6064,10 +6038,6 @@ packages: randombytes: 2.1.0 dev: true - /server-only@0.0.1: - resolution: {integrity: sha512-qepMx2JxAa5jjfzxG79yPPq+8BuFToHd1hm7kI+Z4zAq1ftQiP7HcxMhDDItrbtwVeLg/cY2JnKnrcFkmiswNA==} - dev: false - /set-cookie-parser@2.6.0: resolution: {integrity: sha512-RVnVQxTXuerk653XfuliOxBP81Sf0+qfQE73LIYKcyMYHG94AuH0kgrQpRDuTZnSmjpysHmzxJXKNfa6PjFhyQ==} dev: false @@ -6462,8 +6432,8 @@ packages: unique-string: 2.0.0 dev: true - /terser@5.30.1: - resolution: {integrity: sha512-PJhOnRttZqqmIujxOQOMu4QuFGvh43lR7Youln3k6OJvmxwZ5FxK5rbCEh8XABRCpLf7ZnhrZuclCNCASsScnA==} + /terser@5.30.2: + resolution: {integrity: sha512-vTDjRKYKip4dOFL5VizdoxHTYDfEXPdz5t+FbxCC5Rp2s+KbEO8w5wqMDPgj7CtFKZuzq7PXv28fZoXfqqBVuw==} engines: {node: '>=10'} hasBin: true dependencies: @@ -7033,24 +7003,6 @@ packages: resolution: {integrity: sha512-AyFQ0EVmsOZOlAnxoFOGOq1SQDWAB7C6aqMGS23svWAllfOaxbuFvcT8D1i8z3Gyn8fraVeZNNmN6e9bxxXkKw==} dev: true - /vue-eslint-parser@9.4.2(eslint@8.57.0): - resolution: {integrity: sha512-Ry9oiGmCAK91HrKMtCrKFWmSFWvYkpGglCeFAIqDdr9zdXmMMpJOmUJS7WWsW7fX81h6mwHmUZCQQ1E0PkSwYQ==} - engines: {node: ^14.17.0 || >=16.0.0} - peerDependencies: - eslint: '>=6.0.0' - dependencies: - debug: 4.3.4 - eslint: 8.57.0 - eslint-scope: 7.2.2 - eslint-visitor-keys: 3.4.3 - espree: 9.6.1 - esquery: 1.5.0 - lodash: 4.17.21 - semver: 7.6.0 - transitivePeerDependencies: - - supports-color - dev: true - /w3c-xmlserializer@5.0.0: resolution: {integrity: sha512-o8qghlI8NZHU1lLPrpi2+Uq7abh4GGPpYANlalzWxyWteJOCsr/P+oPBA49TOLu5FTZO4d3F9MnWJfiMo4BkmA==} engines: {node: '>=18'} @@ -7419,7 +7371,7 @@ packages: engines: {node: '>=12.20'} dev: true - /zustand@4.5.2(@types/react@18.2.73)(immer@10.0.4)(react@18.2.0): + /zustand@4.5.2(@types/react@18.2.74)(immer@10.0.4)(react@18.2.0): resolution: {integrity: sha512-2cN1tPkDVkwCy5ickKrI7vijSjPksFRfqS6237NzT0vqSsztTNnQdHw9mmN7uBdk3gceVXU0a+21jFzFzAc9+g==} engines: {node: '>=12.7.0'} peerDependencies: @@ -7434,7 +7386,7 @@ packages: react: optional: true dependencies: - '@types/react': 18.2.73 + '@types/react': 18.2.74 immer: 10.0.4 react: 18.2.0 use-sync-external-store: 1.2.0(react@18.2.0) diff --git a/public/config.js b/public/config.js new file mode 100644 index 00000000..8e7c0c40 --- /dev/null +++ b/public/config.js @@ -0,0 +1,20 @@ +window.__CONFIG__ = { + // The URL for the CORS proxy, the URL must NOT end with a slash! + // If not specified, the onboarding will not allow a "default setup". The user will have to use the extension or set up a proxy themselves + VITE_CORS_PROXY_URL: "https://sudo-proxy.up.railway.app", + + // The READ API key to access TMDB + VITE_TMDB_READ_API_KEY: "eyJhbGciOiJIUzI1NiJ9.eyJhdWQiOiJhZTljNGE2ZDE1ZDFiODZiNzdlMWQyYmI5ZGY0MzdmYyIsInN1YiI6IjY1YjNmMWI0NTk0Yzk0MDE2MzNkZDBjNSIsInNjb3BlcyI6WyJhcGlfcmVhZCJdLCJ2ZXJzaW9uIjoxfQ.kAX7TkbKuJkNty6IsjcCLnoENFicVZn6d6DkLQsy3p8", + + // The DMCA email displayed in the footer, null to hide the DMCA link + VITE_DMCA_EMAIL: null, + + // Whether to disable hash-based routing, leave this as false if you don't know what this is + VITE_NORMAL_ROUTER: true, + + // The backend URL to communicate with + VITE_BACKEND_URL: "https://backend.sudo-flix.lol", + + // A comma separated list of disallowed IDs in the case of a DMCA claim - in the format "series-" and "movie-" + VITE_DISALLOWED_IDS: "", +}; diff --git a/public/lightbar-images/dice.svg b/public/lightbar-images/dice.svg new file mode 100644 index 00000000..5cc0b902 --- /dev/null +++ b/public/lightbar-images/dice.svg @@ -0,0 +1 @@ + diff --git a/src/assets/locales/en.json b/src/assets/locales/en.json index be19e694..212c9e0a 100644 --- a/src/assets/locales/en.json +++ b/src/assets/locales/en.json @@ -109,8 +109,7 @@ "pages": { "about": "About", "dmca": "DMCA", - "topSources": "Top Sources", - "topFlix": "Top Flix", + "discover": "Discover", "support": "Support", "login": "Login", "onboarding": "Setup", diff --git a/src/backend/metadata/tmdb.ts b/src/backend/metadata/tmdb.ts index f53cf417..a8cbe002 100644 --- a/src/backend/metadata/tmdb.ts +++ b/src/backend/metadata/tmdb.ts @@ -152,7 +152,7 @@ const headers = { Authorization: `Bearer ${apiKey}`, }; -async function get(url: string, params?: object): Promise { +export async function get(url: string, params?: object): Promise { if (!apiKey) throw new Error("TMDB API key not set"); const res = await proxiedFetch(encodeURI(url), { diff --git a/src/pages/About.tsx b/src/pages/About.tsx index 4ecd0fc0..32e9601a 100644 --- a/src/pages/About.tsx +++ b/src/pages/About.tsx @@ -80,7 +80,7 @@ export function AboutPage() { className="py-px mt-8 box-content bg-buttons-secondary hover:bg-buttons-secondaryHover bg-opacity-90 text-buttons-secondaryText justify-center items-center" onClick={() => navigate("/flix")} > - Top Flix + Discover - - - + const [movieWidth, setMovieWidth] = useState( + window.innerWidth < 600 ? "150px" : "200px", + ); -
- - {getItemsForCurrentPage().map((item) => { - return ( - - {`${ - item.providerId.charAt(0).toUpperCase() + - item.providerId.slice(1) - }`}{" "} - - {`Views: `} - {parseInt(item.count, 10).toLocaleString()} - - ); - })} -
+ useEffect(() => { + const handleResize = () => { + setMovieWidth(window.innerWidth < 600 ? "150px" : "200px"); + }; + + window.addEventListener("resize", handleResize); + + return () => { + window.removeEventListener("resize", handleResize); + }; + }, []); + + useEffect(() => { + if (carouselRef.current && gradientRef.current) { + const carouselHeight = carouselRef.current.getBoundingClientRect().height; + gradientRef.current.style.top = `${carouselHeight}px`; + gradientRef.current.style.bottom = `${carouselHeight}px`; + } + }, [movieWidth]); // Added movieWidth to the dependency array + + function renderMovies(medias: Media[], category: string, isTVShow = false) { + const categorySlug = category.toLowerCase().replace(/ /g, "-"); // Convert the category to a slug + const displayCategory = + category === "Now Playing" + ? "In Cinemas" + : category.includes("Movie") + ? `${category}s` + : isTVShow + ? `${category} Programmes` + : `${category} Movies`; + return ( +
+

+ {displayCategory} +

{ + carouselRefs.current[categorySlug] = el; + }} + style={{ overflowX: "hidden" }} > - -
- {currentPage}/{maxPageCount} -
- + {medias.slice(0, 20).map((media) => ( + + + + ))}
- - + + +
+ ); + } + + const handleRandomMovieClick = () => { + const allMovies = Object.values(genreMovies).flat(); // Flatten all movie arrays + const uniqueTitles = new Set(); // Use a Set to store unique titles + allMovies.forEach((movie) => uniqueTitles.add(movie.title)); // Add each title to the Set + const uniqueTitlesArray = Array.from(uniqueTitles); // Convert the Set back to an array + const randomIndex = Math.floor(Math.random() * uniqueTitlesArray.length); + const selectedMovie = allMovies.find( + (movie) => movie.title === uniqueTitlesArray[randomIndex], + ); + + if (selectedMovie) { + setRandomMovie(selectedMovie); + + setCountdown(5); + + // Schedule navigation after 5 seconds + setTimeout(() => { + navigate( + `/media/tmdb-movie-${selectedMovie.id}-${selectedMovie.title}`, + ); + }, 5000); + } + }; + + // Fetch Movie genres + useEffect(() => { + const fetchGenres = async () => { + try { + const data = await get("/genre/movie/list", { + api_key: conf().TMDB_READ_API_KEY, + language: "en-US", + }); + + // Shuffle the array of genres + for (let i = data.genres.length - 1; i > 0; i -= 1) { + const j = Math.floor(Math.random() * (i + 1)); + [data.genres[i], data.genres[j]] = [data.genres[j], data.genres[i]]; + } + + // Fetch only the first 5 genres + setGenres(data.genres.slice(0, 5)); + } catch (error) { + console.error("Error fetching genres:", error); + } + }; + + fetchGenres(); + }, []); + + // Fetch movies for each genre + useEffect(() => { + const fetchMoviesForGenre = async (genreId: number) => { + try { + const movies: any[] = []; + for (let page = 1; page <= 5; page += 1) { + // Fetch only 5 pages + const data = await get("/discover/movie", { + api_key: conf().TMDB_READ_API_KEY, + with_genres: genreId.toString(), + language: "en-US", + page: page.toString(), + }); + + movies.push(...data.results); + } + setGenreMovies((prevGenreMovies) => ({ + ...prevGenreMovies, + [genreId]: movies, + })); + } catch (error) { + console.error(`Error fetching movies for genre ${genreId}:`, error); + } + }; + + genres.forEach((genre) => fetchMoviesForGenre(genre.id)); + }, [genres]); + + useEffect(() => { + let countdownInterval: NodeJS.Timeout; + if (countdown !== null && countdown > 0) { + countdownInterval = setInterval(() => { + setCountdown((prevCountdown) => + prevCountdown !== null ? prevCountdown - 1 : prevCountdown, + ); + }, 1000); + } + + return () => { + clearInterval(countdownInterval); + }; + }, [countdown]); + + return ( + +
+ + +
+
+

+ {t("global.pages.discover")} +

+
+
+
+
+ + <> +
+ +
+ {randomMovie && ( +
+

Now Playing {randomMovie.title}

+ {/* You can add additional details or play functionality here */} +
+ )} +
+ {categories.map((category) => ( +
+ {renderMovies( + categoryMovies[category.name] || [], + category.name, + )} +
+ ))} + {genres.map((genre) => ( +
+ {renderMovies(genreMovies[genre.id] || [], genre.name)} +
+ ))} + {tvGenres.map((genre) => ( +
+ {renderMovies(tvShowGenres[genre.id] || [], genre.name, true)} +
+ ))} +
+ +
+
); } diff --git a/src/pages/parts/settings/CaptionsPart.tsx b/src/pages/parts/settings/CaptionsPart.tsx index a6f8ddb5..1067afc6 100644 --- a/src/pages/parts/settings/CaptionsPart.tsx +++ b/src/pages/parts/settings/CaptionsPart.tsx @@ -99,7 +99,7 @@ export function CaptionsPart(props: { onChange={(v) => props.setStyling({ ...props.styling, backgroundBlur: v / 100 }) } - value={props.styling.backgroundBlur * 100} + value={props.styling.backgroundBlur * 1} textTransformer={(s) => `${s}%`} /> } /> {/* Top flix page */} } /> - } /> {/* Settings page */} } /> {/* Top flix page */} } /> - } /> {/* Settings page */} ); } + +export default App;