├── .env.development
├── .env.production
├── .eslintrc.json
├── .gitignore
├── .prettierrc.json
├── LICENSE
├── README.md
├── config-overrides.js
├── electron
├── electron-builder.json
├── icons
│ └── 512x512.ico
├── index.ts
└── preload.ts
├── package-lock.json
├── package.json
├── public
├── favicon.ico
└── index.html
├── src
├── App.tsx
├── MuiClassNameSetup.ts
├── assets
│ ├── disconnected.png
│ ├── error.png
│ ├── logo-full-dark.svg
│ ├── logo-full-light.svg
│ └── no-image-poster.svg
├── components
│ ├── DBottomBar
│ │ ├── index.tsx
│ │ └── styles.ts
│ ├── DBottomFilter
│ │ └── index.tsx
│ ├── DButton
│ │ ├── index.tsx
│ │ └── styles.ts
│ ├── DCarousel
│ │ ├── index.tsx
│ │ └── styles.ts
│ ├── DCircularRating
│ │ ├── index.tsx
│ │ └── styles.ts
│ ├── DColorSelector
│ │ └── index.tsx
│ ├── DEpisodeCard
│ │ ├── index.tsx
│ │ └── styles.ts
│ ├── DExternalModal
│ │ ├── index.tsx
│ │ └── styles.ts
│ ├── DHelmet
│ │ ├── HelmetConstants.js
│ │ ├── HelmetUtils.js
│ │ ├── index.js
│ │ └── sideEffect.js
│ ├── DInfoModal
│ │ ├── index.tsx
│ │ └── styles.ts
│ ├── DItemCard
│ │ ├── index.tsx
│ │ └── styles.ts
│ ├── DItemLogo
│ │ ├── index.tsx
│ │ └── styles.ts
│ ├── DLoader
│ │ ├── index.tsx
│ │ └── styles.ts
│ ├── DNavbar
│ │ ├── index.tsx
│ │ └── styles.ts
│ ├── DPersonCard
│ │ ├── index.tsx
│ │ └── styles.ts
│ ├── DReview
│ │ ├── DReviewCard
│ │ │ ├── index.tsx
│ │ │ └── styles.ts
│ │ ├── index.tsx
│ │ └── styles.ts
│ ├── DSearchStyles
│ │ └── index.tsx
│ ├── DSeasonCard
│ │ ├── index.tsx
│ │ └── styles.ts
│ ├── DSelect
│ │ ├── index.tsx
│ │ └── styles.ts
│ ├── DSlider
│ │ ├── index.tsx
│ │ └── styles.ts
│ ├── DSpacer
│ │ ├── index.tsx
│ │ └── styles.ts
│ ├── DStreamModal
│ │ ├── index.tsx
│ │ └── styles.ts
│ ├── DTextField
│ │ ├── index.tsx
│ │ └── styles.ts
│ ├── DVideoCard
│ │ ├── index.tsx
│ │ └── styles.ts
│ ├── DVideoModal
│ │ ├── index.tsx
│ │ └── styles.ts
│ └── DWebPlayer
│ │ ├── DWebPlayerBase.tsx
│ │ ├── DWebPlaylist.tsx
│ │ └── index.tsx
├── config.ts
├── decleration.d.ts
├── index.tsx
├── pages
│ ├── Browse
│ │ ├── index.tsx
│ │ └── styles.ts
│ ├── Callback
│ │ └── index.tsx
│ ├── Disconnected
│ │ ├── index.tsx
│ │ └── styles.ts
│ ├── Episode
│ │ └── index.tsx
│ ├── Home
│ │ ├── index.tsx
│ │ └── styles.ts
│ ├── Movie
│ │ ├── index.tsx
│ │ └── styles.ts
│ ├── NotFound
│ │ ├── index.tsx
│ │ └── styles.ts
│ ├── Season
│ │ ├── index.tsx
│ │ └── styles.ts
│ ├── Series
│ │ ├── index.tsx
│ │ └── styles.ts
│ ├── Settings
│ │ ├── assets
│ │ │ ├── logo-full-dark.svg
│ │ │ └── logo-full-light.svg
│ │ ├── components
│ │ │ └── NavBar.tsx
│ │ ├── index.tsx
│ │ ├── pages
│ │ │ ├── App
│ │ │ │ └── index.tsx
│ │ │ ├── Auth0
│ │ │ │ └── index.tsx
│ │ │ ├── Categories
│ │ │ │ ├── Category.tsx
│ │ │ │ └── index.tsx
│ │ │ ├── Dev
│ │ │ │ └── index.tsx
│ │ │ ├── Interface
│ │ │ │ └── index.tsx
│ │ │ ├── Other
│ │ │ │ └── index.tsx
│ │ │ └── Providers
│ │ │ │ ├── GDriveTokenGeneratorPage.tsx
│ │ │ │ ├── OneDriveTokenGeneratorPage.tsx
│ │ │ │ └── index.tsx
│ │ ├── styles
│ │ │ └── globals.css
│ │ └── utilities
│ │ │ └── tokens.ts
│ └── Setup
│ │ └── index.tsx
├── styles
│ ├── DCarousel.module.css
│ └── globals.css
├── theme
│ ├── darkTheme.ts
│ └── lightTheme.ts
└── utilities
│ ├── electronServer.ts
│ ├── getOs.ts
│ ├── guid.ts
│ ├── human.ts
│ ├── isElectron.ts
│ ├── requests.ts
│ ├── useBreakpoint.ts
│ └── useNetworkStatus.ts
└── tsconfig.json
/.env.development:
--------------------------------------------------------------------------------
1 | REACT_APP_SERVER_URL=http://localhost:35500
2 | PORT=35510
3 | HOST=0.0.0.0
4 |
--------------------------------------------------------------------------------
/.env.production:
--------------------------------------------------------------------------------
1 | REACT_APP_SERVER_URL=
2 |
--------------------------------------------------------------------------------
/.eslintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "env": {
3 | "browser": true,
4 | "es2021": true
5 | },
6 | "extends": [
7 | "eslint:recommended",
8 | "plugin:react/recommended",
9 | "plugin:@typescript-eslint/recommended",
10 | "prettier"
11 | ],
12 | "parser": "@typescript-eslint/parser",
13 | "parserOptions": {
14 | "ecmaFeatures": {
15 | "jsx": true
16 | },
17 | "ecmaVersion": "latest",
18 | "sourceType": "module"
19 | },
20 | "plugins": ["react", "@typescript-eslint", "prettier"],
21 | "rules": {
22 | "@typescript-eslint/no-explicit-any": "off",
23 | "indent": "off",
24 | "quotes": ["error", "single"],
25 | "semi": ["error", "always"],
26 | "no-var": 0,
27 | "react/prop-types": 0
28 | },
29 | "settings": {
30 | "import/resolver": {
31 | "typescript": {}
32 | },
33 | "react": {
34 | "version": "18"
35 | }
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 | /.pnp
6 | .pnp.js
7 |
8 | # testing
9 | /coverage
10 |
11 | # production
12 | /build
13 | /dist
14 |
15 | # misc
16 | .DS_Store
17 | .env
18 | .env.local
19 | .env.development.local
20 | .env.test.local
21 | .env.production.local
22 |
23 | npm-debug.log*
24 | yarn-debug.log*
25 | yarn-error.log*
26 |
--------------------------------------------------------------------------------
/.prettierrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "semi": true,
3 | "tabWidth": 4,
4 | "printWidth": 100,
5 | "importOrder": ["^components/(.*)$", "^[./]"],
6 | "importOrderSeparation": true,
7 | "importOrderSortSpecifiers": true,
8 | "singleQuote": true,
9 | "trailingComma": "all",
10 | "jsxSingleQuote": true,
11 | "bracketSpacing": true
12 | }
13 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2022 Dester
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Getting Started with Create React App
2 |
3 | This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app).
4 |
5 | ## Available Scripts
6 |
7 | In the project directory, you can run:
8 |
9 | ### `npm start`
10 |
11 | Runs the app in the development mode.\
12 | Open [http://localhost:3000](http://localhost:3000) to view it in the browser.
13 |
14 | The page will reload if you make edits.\
15 | You will also see any lint errors in the console.
16 |
17 | ### `npm test`
18 |
19 | Launches the test runner in the interactive watch mode.\
20 | See the section about [running tests](https://facebook.github.io/create-react-app/docs/running-tests) for more information.
21 |
22 | ### `npm run build`
23 |
24 | Builds the app for production to the `build` folder.\
25 | It correctly bundles React in production mode and optimizes the build for the best performance.
26 |
27 | The build is minified and the filenames include the hashes.\
28 | Your app is ready to be deployed!
29 |
30 | See the section about [deployment](https://facebook.github.io/create-react-app/docs/deployment) for more information.
31 |
32 | ### `npm run eject`
33 |
34 | **Note: this is a one-way operation. Once you `eject`, you can’t go back!**
35 |
36 | If you aren’t satisfied with the build tool and configuration choices, you can `eject` at any time. This command will remove the single build dependency from your project.
37 |
38 | Instead, it will copy all the configuration files and the transitive dependencies (webpack, Babel, ESLint, etc) right into your project so you have full control over them. All of the commands except `eject` will still work, but they will point to the copied scripts so you can tweak them. At this point you’re on your own.
39 |
40 | You don’t have to ever use `eject`. The curated feature set is suitable for small and middle deployments, and you shouldn’t feel obligated to use this feature. However we understand that this tool wouldn’t be useful if you couldn’t customize it when you are ready for it.
41 |
42 | ## Learn More
43 |
44 | You can learn more in the [Create React App documentation](https://facebook.github.io/create-react-app/docs/getting-started).
45 |
46 | To learn React, check out the [React documentation](https://reactjs.org/).
47 |
--------------------------------------------------------------------------------
/config-overrides.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable */
2 |
3 | module.exports = function override(config, env) {
4 | let loaders = config.resolve;
5 | loaders.fallback = {
6 | path: require.resolve('path-browserify'),
7 | };
8 | return config;
9 | };
10 |
--------------------------------------------------------------------------------
/electron/electron-builder.json:
--------------------------------------------------------------------------------
1 | {
2 | "appId": "gq.dester.desktop",
3 | "productName": "Dester Desktop",
4 | "executableName": "${productName}",
5 | "artifactName": "Dester.Desktop.v${version}.${ext}",
6 | "copyright": "Copyright © 2022 Dester",
7 | "win": {
8 | "icon": "electron/icons/512x512.ico",
9 | "target": {
10 | "target": "nsis",
11 | "arch": ["x64"]
12 | }
13 | },
14 | "files": ["**/*", "./build/**/*", "./electron/*"],
15 | "extraResources": [{ "from": "./node_modules/@desterlib/mpv/dist", "to": "libs" }]
16 | }
17 |
--------------------------------------------------------------------------------
/electron/icons/512x512.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DesterLib/Frontend/e309a5ed01d065477942bcfc13daff74afbd4844/electron/icons/512x512.ico
--------------------------------------------------------------------------------
/electron/index.ts:
--------------------------------------------------------------------------------
1 | /* eslint-disable */
2 |
3 | const { app, BrowserWindow, protocol } = require('electron');
4 | const path = require('path');
5 | const url = require('url');
6 |
7 | const PLUGIN_MIME_TYPE = 'application/x-mpv';
8 |
9 | function containsNonASCII(str) {
10 | for (let i = 0; i < str.length; i++) {
11 | if (str.charCodeAt(i) > 255) {
12 | return true;
13 | }
14 | }
15 | return false;
16 | }
17 |
18 | function getPluginEntry(pluginDir, pluginName = 'mpv.node') {
19 | const fullPluginPath = path.join(pluginDir, pluginName);
20 | let pluginPath = path.relative(process.cwd(), fullPluginPath);
21 | if (path.dirname(pluginPath) === '.') {
22 | if (process.platform === 'linux') {
23 | pluginPath = `.${path.sep}${pluginPath}`;
24 | }
25 | } else {
26 | if (process.platform === 'win32') {
27 | pluginPath = fullPluginPath;
28 | }
29 | }
30 | if (containsNonASCII(pluginPath)) {
31 | if (containsNonASCII(fullPluginPath)) {
32 | throw new Error('Non-ASCII plugin path is not supported');
33 | } else {
34 | pluginPath = fullPluginPath;
35 | }
36 | }
37 | return `${pluginPath};${PLUGIN_MIME_TYPE}`;
38 | }
39 |
40 | const pluginDir =
41 | process.env.NODE_ENV === 'development'
42 | ? path.join(path.dirname(require.resolve('@desterlib/mpv')), 'dist') // @ts-ignore
43 | : path.join(process.resourcesPath, 'libs');
44 |
45 | if (process.platform !== 'linux') {
46 | process.chdir(pluginDir);
47 | }
48 |
49 | app.commandLine.appendSwitch('no-sandbox');
50 | app.commandLine.appendSwitch('ignore-gpu-blacklist');
51 | app.commandLine.appendSwitch('register-pepper-plugins', getPluginEntry(pluginDir));
52 |
53 | function createWindow() {
54 | const mainWindow = new BrowserWindow({
55 | title: 'Dester Desktop',
56 | show: false,
57 | frame: true,
58 | backgroundColor: '#00151C',
59 | webPreferences: {
60 | preload: path.join(__dirname, 'preload.ts'),
61 | enableRemoteModule: true,
62 | plugins: true,
63 | },
64 | });
65 |
66 | const appURL = app.isPackaged
67 | ? url.format({
68 | pathname: path.join(__dirname, '..', 'build', 'index.html'),
69 | protocol: 'file:',
70 | slashes: true,
71 | })
72 | : 'http://localhost:35510';
73 | mainWindow.loadURL(appURL);
74 | mainWindow.maximize();
75 | if (!app.isPackaged) {
76 | mainWindow.setIcon(path.join(__dirname, 'icons', '512x512.ico'));
77 | mainWindow.webContents.openDevTools();
78 | }
79 | }
80 |
81 | function setupLocalFilesNormalizerProxy() {
82 | protocol.registerHttpProtocol(
83 | 'file',
84 | (request, callback) => {
85 | const url = request.url.substr(8);
86 | callback({ path: path.normalize(`${__dirname}/${url}`) });
87 | },
88 | (error) => {
89 | if (error) console.error('Failed to register protocol');
90 | },
91 | );
92 | }
93 |
94 | app.whenReady().then(() => {
95 | createWindow();
96 | setupLocalFilesNormalizerProxy();
97 | app.on('activate', function () {
98 | if (BrowserWindow.getAllWindows().length === 0) {
99 | createWindow();
100 | }
101 | });
102 | });
103 |
104 | app.on('window-all-closed', function () {
105 | if (process.platform !== 'darwin') {
106 | app.quit();
107 | }
108 | });
109 |
110 | const allowedNavigationDestinations = 'https://dester.gq';
111 | app.on('web-contents-created', (event, contents) => {
112 | contents.on('will-navigate', (event, navigationUrl) => {
113 | const parsedUrl = new URL(navigationUrl);
114 |
115 | if (!allowedNavigationDestinations.includes(parsedUrl.origin)) {
116 | event.preventDefault();
117 | }
118 | });
119 | });
120 |
--------------------------------------------------------------------------------
/electron/preload.ts:
--------------------------------------------------------------------------------
1 | /* eslint-disable */
2 | // @ts-nocheck
3 |
4 | const { remote, shell } = require('electron');
5 | const fs = require('fs');
6 |
7 | window.remote = remote;
8 | window.openExternal = shell.openExternal;
9 | window.openPath = shell.openPath;
10 | window.directory = __dirname;
11 | window.access = fs.access;
12 | window.electron = true;
13 | window.os = process.platform;
14 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@desterlib/frontend",
3 | "version": "0.9.0",
4 | "description": "Dester's frontend",
5 | "license": "MIT",
6 | "scripts": {
7 | "start": "react-app-rewired start",
8 | "build": "react-app-rewired build",
9 | "electron:start": "concurrently -k \"cross-env BROWSER=none npm start\" \"wait-on http://localhost:35510 && cross-env NODE_ENV=development electronmon .\"",
10 | "electron:mon": "cross-env NODE_ENV=development electronmon .",
11 | "electron:build": "npm run build && rm -rf dist && electron-builder --config electron/electron-builder.json --win --x64",
12 | "lint:check": "eslint src/**/*.{js,jsx,ts,tsx} electron/*.{js,jsx,ts,tsx}",
13 | "lint:fix": "eslint --fix src/**/*.{js,jsx,ts,tsx} electron/*.{js,jsx,ts,tsx}",
14 | "lint:format": "prettier --write src/**/*.{js,jsx,ts,tsx,css,md,json} electron/*.{js,jsx,ts,tsx,css,md,json} .eslintrc.json .prettierrc.json package.json tsconfig.json --config .prettierrc.json"
15 | },
16 | "dependencies": {
17 | "@desterlib/dplayer": "^0.0.21",
18 | "@emotion/react": "^11.9.3",
19 | "@emotion/styled": "^11.9.3",
20 | "@mui/icons-material": "^5.8.4",
21 | "@mui/material": "^5.8.5",
22 | "@mui/styled-engine": "^5.8.0",
23 | "@types/node": "^18.0.0",
24 | "@types/react": "^18.0.14",
25 | "@types/react-dom": "^18.0.5",
26 | "artplayer": "^4.5.2",
27 | "buffer": "^6.0.3",
28 | "html-react-parser": "^2.0.0",
29 | "lodash": "^4.17.21",
30 | "path-browserify": "^1.0.1",
31 | "react": "^18.2.0",
32 | "react-color": "^2.19.3",
33 | "react-colorful": "^5.5.1",
34 | "react-dom": "^18.2.0",
35 | "react-fast-compare": "^3.2.0",
36 | "react-router-dom": "^6.3.0",
37 | "sweetalert2": "^11.4.19",
38 | "swiper": "^8.2.4",
39 | "typescript": "^4.7.4",
40 | "use-resize-observer": "^9.0.2"
41 | },
42 | "devDependencies": {
43 | "@trivago/prettier-plugin-sort-imports": "^3.2.0",
44 | "@types/lodash": "^4.14.182",
45 | "@typescript-eslint/eslint-plugin": "^5.29.0",
46 | "@typescript-eslint/parser": "^5.29.0",
47 | "concurrently": "^7.2.2",
48 | "cross-env": "^7.0.3",
49 | "electron": "^19.0.6",
50 | "electron-builder": "^23.1.0",
51 | "electronmon": "^2.0.2",
52 | "eslint": "^8.18.0",
53 | "eslint-config-prettier": "^8.5.0",
54 | "eslint-plugin-prettier": "^4.0.0",
55 | "eslint-plugin-react": "^7.30.0",
56 | "prettier": "^2.7.1",
57 | "react-app-rewired": "^2.2.1",
58 | "wait-on": "^6.0.1"
59 | },
60 | "author": "Dester Team",
61 | "contributors": [
62 | {
63 | "name": "Alken D",
64 | "email": "desteralken@gmail.com",
65 | "url": "https://github.com/AlkenD"
66 | },
67 | {
68 | "name": "Elias Benbourenane",
69 | "email": "me@elias.eu.org",
70 | "url": "https://elias.eu.org"
71 | }
72 | ],
73 | "main": "./electron/index.ts",
74 | "browserslist": {
75 | "production": [
76 | ">0.2%",
77 | "not dead",
78 | "not op_mini all"
79 | ],
80 | "development": [
81 | "last 1 chrome version",
82 | "last 1 firefox version",
83 | "last 1 safari version"
84 | ]
85 | }
86 | }
87 |
--------------------------------------------------------------------------------
/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DesterLib/Frontend/e309a5ed01d065477942bcfc13daff74afbd4844/public/favicon.ico
--------------------------------------------------------------------------------
/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
10 | Loading...
11 |
12 |
13 |
14 |
18 |
22 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
--------------------------------------------------------------------------------
/src/App.tsx:
--------------------------------------------------------------------------------
1 | import { CssBaseline, PaletteMode } from '@mui/material';
2 | import { ThemeProvider, createTheme } from '@mui/material/styles';
3 | import React, { createContext, useEffect, useMemo, useState } from 'react';
4 | import { BrowserRouter, HashRouter, Route, Routes } from 'react-router-dom';
5 |
6 | import './MuiClassNameSetup';
7 | import DBottomBar from './components/DBottomBar';
8 | import DNavbar from './components/DNavbar';
9 | import { APP_IS_ELECTRON } from './config';
10 | import { APP_NAME } from './config';
11 | import BrowsePage from './pages/Browse';
12 | import CallbackPage from './pages/Callback';
13 | import DisconnectedPage from './pages/Disconnected';
14 | import EpisodePage from './pages/Episode';
15 | import HomePage from './pages/Home';
16 | import MoviePage from './pages/Movie';
17 | import NotFoundPage from './pages/NotFound';
18 | import SeasonPage from './pages/Season';
19 | import SeriePage from './pages/Series';
20 | import Settings from './pages/Settings';
21 | import SetupPage from './pages/Setup';
22 | import darkTheme from './theme/darkTheme';
23 | import lightTheme from './theme/lightTheme';
24 | import electronServer from './utilities/electronServer';
25 |
26 | // eslint-disable-next-line
27 | const ColorModeContext = createContext({ toggleColorMode: () => {} });
28 |
29 | const App = () => {
30 | electronServer();
31 | const [mode, setMode] = useState('dark');
32 |
33 | useEffect(() => {
34 | document.title = APP_NAME;
35 | let localTheme = localStorage.getItem('theme') || 'dark';
36 | localTheme = localTheme === 'light' ? 'light' : 'dark';
37 | setMode(localTheme as PaletteMode);
38 | }, []);
39 |
40 | const colorMode = useMemo(
41 | () => ({
42 | toggleColorMode: () => {
43 | let localTheme = localStorage.getItem('theme') || 'dark';
44 | localTheme = localTheme === 'light' ? 'dark' : 'light';
45 | localStorage.setItem('theme', localTheme);
46 | setMode((prevMode: string) => (prevMode === 'light' ? 'dark' : 'light'));
47 | },
48 | }),
49 | [],
50 | );
51 |
52 | const theme = useMemo(
53 | () =>
54 | createTheme({
55 | palette: {
56 | mode,
57 | ...(mode === 'light' ? lightTheme.palette : darkTheme.palette),
58 | },
59 | typography: {
60 | fontFamily: `${
61 | mode === 'light'
62 | ? lightTheme.typography.fontFamily
63 | : darkTheme.typography.fontFamily
64 | }`,
65 | button: {
66 | textTransform: 'none',
67 | },
68 | },
69 | shape: {
70 | borderRadius:
71 | mode === 'light'
72 | ? lightTheme.shape.borderRadius
73 | : darkTheme.shape.borderRadius,
74 | },
75 | components: {
76 | MuiMenu: {
77 | styleOverrides: {
78 | root: {
79 | padding: '20px',
80 | maxHeight: '400px',
81 | cursor: 'pointer',
82 | },
83 | paper: {
84 | margin: '0px',
85 | padding: '8px',
86 | background: `${
87 | mode === 'light'
88 | ? lightTheme.palette.background.paper
89 | : darkTheme.palette.background.paper
90 | } !important`,
91 | },
92 | list: {
93 | padding: 0,
94 | },
95 | },
96 | },
97 | MuiMenuItem: {
98 | styleOverrides: {
99 | root: {
100 | borderRadius:
101 | mode === 'light'
102 | ? lightTheme.shape.borderRadius
103 | : darkTheme.shape.borderRadius,
104 | transition: '0.2s ease-out',
105 | },
106 | },
107 | },
108 | },
109 | }),
110 | [mode],
111 | );
112 |
113 | const Router = ({ children }: any) => {
114 | return APP_IS_ELECTRON ? (
115 | {children}
116 | ) : (
117 | {children}
118 | );
119 | };
120 |
121 | return (
122 |
123 |
124 |
125 |
126 |
127 |
128 | } />
129 | } />
130 | } />
131 | } />
132 | } />
133 | }
136 | />
137 | }
140 | />
141 |
145 | }
146 | />
147 | } />
148 | } />
149 | } />
150 |
151 |
152 |
153 |
154 |
155 | );
156 | };
157 |
158 | export default App;
159 |
--------------------------------------------------------------------------------
/src/MuiClassNameSetup.ts:
--------------------------------------------------------------------------------
1 | import { unstable_ClassNameGenerator as ClassNameGenerator } from '@mui/material/className';
2 |
3 | ClassNameGenerator.configure((componentName) => `${componentName.replace('Mui', 'Dester-')}`);
4 |
--------------------------------------------------------------------------------
/src/assets/disconnected.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DesterLib/Frontend/e309a5ed01d065477942bcfc13daff74afbd4844/src/assets/disconnected.png
--------------------------------------------------------------------------------
/src/assets/error.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DesterLib/Frontend/e309a5ed01d065477942bcfc13daff74afbd4844/src/assets/error.png
--------------------------------------------------------------------------------
/src/assets/logo-full-dark.svg:
--------------------------------------------------------------------------------
1 |
49 |
--------------------------------------------------------------------------------
/src/assets/no-image-poster.svg:
--------------------------------------------------------------------------------
1 |
5 |
--------------------------------------------------------------------------------
/src/components/DBottomBar/index.tsx:
--------------------------------------------------------------------------------
1 | import { Box, Typography } from '@mui/material';
2 | import BottomNavigation from '@mui/material/BottomNavigation';
3 | import ButtonBase from '@mui/material/ButtonBase';
4 | import { useTheme } from '@mui/system';
5 | import React, { useState } from 'react';
6 | import { useNavigate } from 'react-router-dom';
7 |
8 | import { BottomNavMenuItem, DBottomBarWrapper } from './styles';
9 |
10 | const DBottomBar = () => {
11 | const navigate = useNavigate();
12 | const [value, setValue] = useState(0);
13 | const theme = useTheme();
14 |
15 | return (
16 |
17 |
18 | {
22 | setValue(val);
23 | if (val == 0) {
24 | navigate('/');
25 | } else if (val == 1) {
26 | navigate('/browse');
27 | } else if (val == 2) {
28 | navigate('/settings');
29 | }
30 | }}
31 | sx={{ height: 'fit-content' }}
32 | >
33 |
37 |
38 | home
39 |
40 | Home
41 |
42 | }
43 | />
44 |
48 |
49 | explore
50 |
51 | Browse
52 |
53 | }
54 | />
55 |
59 |
60 | more_horiz
61 |
62 | Settings
63 |
64 | }
65 | />
66 |
67 |
68 |
69 | );
70 | };
71 |
72 | export default DBottomBar;
73 |
--------------------------------------------------------------------------------
/src/components/DBottomBar/styles.ts:
--------------------------------------------------------------------------------
1 | import BottomNavigationAction from '@mui/material/BottomNavigationAction';
2 | import Box, { BoxProps } from '@mui/material/Box';
3 | import { styled } from '@mui/material/styles';
4 |
5 | export const DBottomBarWrapper = styled(Box)(({ theme }) => ({
6 | position: 'fixed',
7 | bottom: 0,
8 | left: 0,
9 | right: 0,
10 | zIndex: 1000,
11 | [theme.breakpoints.up('md')]: {
12 | display: 'none',
13 | },
14 | '& .Dester-BottomNavigation-root': {
15 | backgroundColor: theme.palette.background.paper,
16 | backdropFilter: 'blur(10px)',
17 | },
18 | }));
19 |
20 | export const BottomNavMenuItem = styled(BottomNavigationAction)(({ theme }) => ({
21 | borderRadius: '10px',
22 | padding: '5px 0px',
23 | transition: '0.2s ease-out',
24 | '.Dester-BottomNavigationAction-root': {
25 | color: theme.palette.text.primary,
26 | },
27 | '&.Dester-BottomNavigationAction-root div div': {
28 | transition: '0.2s ease-out',
29 | backgroundColor: theme.palette.background.paper,
30 | },
31 | '.material-symbols-rounded': {
32 | padding: '4px 15px',
33 | borderRadius: '25px',
34 | },
35 | '&.Mui-selected': {
36 | color: theme.palette.text.primary,
37 | },
38 | '&.Mui-selected div div': {
39 | borderRadius: '25px',
40 | opacity: '1',
41 | backgroundColor: theme.palette.background.default,
42 | },
43 | }));
44 |
--------------------------------------------------------------------------------
/src/components/DBottomFilter/index.tsx:
--------------------------------------------------------------------------------
1 | import { useTheme } from '@mui/material';
2 | import Box from '@mui/material/Box';
3 | import List from '@mui/material/List';
4 | import ListItem from '@mui/material/ListItem';
5 | import SwipeableDrawer from '@mui/material/SwipeableDrawer';
6 | import * as React from 'react';
7 |
8 | import DButton from '../DButton';
9 | import DSelect from '../DSelect';
10 |
11 | type Anchor = 'top' | 'left' | 'bottom' | 'right';
12 |
13 | export default function DBottomFilter({
14 | genres,
15 | years,
16 | sortings,
17 | handleChangeGenre,
18 | handleChangeYear,
19 | handleChangeCategory,
20 | handleChangeSort,
21 | }) {
22 | const [isSideBarOpen, setIsSideBarOpen] = React.useState(false);
23 |
24 | const theme = useTheme();
25 |
26 | const toggleDrawer = (open: boolean) => (event: React.KeyboardEvent | React.MouseEvent) => {
27 | if (
28 | event.type === 'keydown' &&
29 | ((event as React.KeyboardEvent).key === 'Tab' ||
30 | (event as React.KeyboardEvent).key === 'Shift')
31 | ) {
32 | return;
33 | }
34 |
35 | setIsSideBarOpen(!isSideBarOpen);
36 | };
37 |
38 | const list = (anchor: Anchor) => (
39 |
47 |
48 |
49 |
56 |
57 |
58 |
65 |
66 |
67 |
74 |
75 |
76 |
83 |
84 |
85 |
92 |
93 |
94 |
95 | );
96 |
97 | return (
98 |
113 | filter_list}
116 | sx={{
117 | height: '50px',
118 | width: '50px',
119 | display: 'flex',
120 | marginRight: '10px',
121 | borderRadius: '50%',
122 | padding: '13px',
123 | minWidth: '0px',
124 | textAlign: 'center',
125 | }}
126 | onClick={toggleDrawer(true)}
127 | />
128 |
134 | {list('bottom')}
135 |
136 |
137 | );
138 | }
139 |
--------------------------------------------------------------------------------
/src/components/DButton/index.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | import { DButtonEndIcon, DButtonPrimary, DButtonSecondary, DButtonStartIcon } from './styles';
4 |
5 | // function generateTextColor(hexcolor: any) {
6 | // hexcolor = hexcolor.replace('#', '');
7 | // var r = parseInt(hexcolor.substr(0, 2), 16);
8 | // var g = parseInt(hexcolor.substr(2, 2), 16);
9 | // var b = parseInt(hexcolor.substr(4, 2), 16);
10 | // var yiq = (r * 299 + g * 587 + b * 114) / 1000;
11 | // return yiq >= 128 ? 'black' : 'white';
12 | // }
13 |
14 | const DButton = ({
15 | startIcon,
16 | endIcon,
17 | color,
18 | children,
19 | variant,
20 | onClick,
21 | disabled,
22 | fullwidth,
23 | sx,
24 | }: any) => {
25 | return (
26 | <>
27 | {color === 'primary' || color === undefined ? (
28 |
29 | {startIcon && (
30 |
35 | {startIcon}
36 |
37 | )}
38 | {children}
39 | {endIcon && (
40 |
41 | {endIcon}
42 |
43 | )}
44 |
45 | ) : null}
46 | {color === 'secondary' ? (
47 |
54 | {startIcon && (
55 |
60 | {startIcon}
61 |
62 | )}
63 | {children}
64 | {endIcon && (
65 |
66 | {endIcon}
67 |
68 | )}
69 |
70 | ) : null}
71 | {color === 'warning' ? (
72 |
79 | {startIcon && (
80 |
85 | {startIcon}
86 |
87 | )}
88 | {children}
89 | {endIcon && (
90 |
91 | {endIcon}
92 |
93 | )}
94 |
95 | ) : null}
96 | {color === 'danger' ? (
97 |
104 | {startIcon && (
105 |
110 | {startIcon}
111 |
112 | )}
113 | {children}
114 | {endIcon && (
115 |
116 | {endIcon}
117 |
118 | )}
119 |
120 | ) : null}
121 | >
122 | );
123 | };
124 |
125 | export default DButton;
126 |
--------------------------------------------------------------------------------
/src/components/DButton/styles.ts:
--------------------------------------------------------------------------------
1 | import ButtonUnstyled from '@mui/base/ButtonUnstyled';
2 | import Box from '@mui/material/Box';
3 | import { alpha, styled } from '@mui/material/styles';
4 |
5 | type DButtonProps = {
6 | iconcolor?: boolean;
7 | breakpoint?: string;
8 | };
9 |
10 | type DButtonStartIcon = {
11 | color?: string;
12 | variant?: string;
13 | };
14 |
15 | type DButtonEndIcon = {
16 | color?: string;
17 | variant?: string;
18 | };
19 |
20 | const ButtonBaseStyles = {
21 | border: '0px',
22 | cursor: 'pointer',
23 | padding: '12px 12px',
24 | transition: '0.2s ease-out',
25 | lineHeight: '1',
26 | display: 'flex',
27 | margin: '2px',
28 | alignItems: 'center',
29 | fontFamily: '"Rubik", sans-serif',
30 | fontWeight: '500',
31 | };
32 |
33 | export const DButtonPrimary = styled(ButtonUnstyled)(({ theme, fullwidth }) => ({
34 | ...ButtonBaseStyles,
35 | color: theme.palette.primary.contrastText,
36 | borderRadius: (theme.shape.borderRadius as number) + 5,
37 | width: fullwidth ? '100%' : 'auto',
38 | background: `linear-gradient(45deg, ${theme.palette.primary.dark} 0%, ${theme.palette.primary.main} 100%)`,
39 | boxShadow: `0px 0px 0px 1px ${alpha(theme.palette.primary.main, 0.7)}`,
40 | '&:hover': {
41 | backgroundColor: theme.palette.primary.main,
42 | boxShadow: `0px 0px 0px 2px ${alpha(theme.palette.primary.main, 0.7)}`,
43 | },
44 | '&:focus': {
45 | backgroundColor: theme.palette.primary.dark,
46 | boxShadow: `0px 0px 0px 2px ${alpha(theme.palette.primary.dark, 0.7)}`,
47 | },
48 | '&:disabled, [disabled]': {
49 | backgroundColor: alpha(theme.palette.primary.main, 0.5),
50 | boxShadow: `0px 0px 0px 2px ${alpha(theme.palette.primary.main, 0.7)}`,
51 | },
52 | }));
53 |
54 | export const DButtonSecondary = styled(ButtonUnstyled)(({ theme }) => ({
55 | ...ButtonBaseStyles,
56 | color: theme.palette.secondary.contrastText,
57 | borderRadius: (theme.shape.borderRadius as number) + 5,
58 | background: `linear-gradient(45deg, ${theme.palette.secondary.dark} 0%, ${theme.palette.secondary.main} 100%)`,
59 | boxShadow: `0px 0px 0px 1px ${alpha(theme.palette.secondary.main, 0.7)}`,
60 | '&:hover': {
61 | transition: '0.2s ease-out',
62 | backgroundColor: theme.palette.secondary.main,
63 | boxShadow: `0px 0px 0px 2px ${alpha(theme.palette.secondary.main, 0.7)}`,
64 | },
65 | '&:focus': {
66 | backgroundColor: theme.palette.secondary.dark,
67 | boxShadow: `0px 0px 0px 2px ${alpha(theme.palette.secondary.dark, 0.7)}`,
68 | },
69 | '&:hover .DButton-startIcon': {
70 | color: theme.palette.primary.main,
71 | },
72 | '&:hover .DButton-endIcon': {
73 | color: theme.palette.primary.main,
74 | },
75 | '&:disabled, [disabled]': {
76 | cursor: 'default',
77 | background: `${alpha(theme.palette.secondary.main, 0.2)}`,
78 | boxShadow: `0px 0px 0px 2px ${alpha(theme.palette.secondary.main, 0.7)}`,
79 | },
80 | }));
81 |
82 | export const DButtonStartIcon = styled(Box)(({ theme, variant, color }) => ({
83 | border: '0px',
84 | marginRight: variant === 'icon' ? '0px' : '8px',
85 | fontSize: '20px',
86 | display: 'flex',
87 | alignItems: 'center',
88 | justifyContent: variant === 'icon' ? 'center' : 'inherit',
89 | transition: '0.2s ease-out',
90 | color: `${
91 | (variant === 'icon' && theme.palette.primary.main,
92 | (color === 'primary' || color === undefined) && 'inherit')
93 | }`,
94 | }));
95 |
96 | export const DButtonEndIcon = styled(Box)(({ theme, variant, color }) => ({
97 | border: '0px',
98 | marginLeft: variant === 'icon' ? '0px' : '8px',
99 | fontSize: '20px',
100 | display: 'flex',
101 | alignItems: 'center',
102 | transition: '0.2s ease-out',
103 | color: `${
104 | (variant === 'icon' && theme.palette.primary.main,
105 | (color === 'primary' || color === undefined) && 'inherit')
106 | }`,
107 | }));
108 |
--------------------------------------------------------------------------------
/src/components/DCarousel/styles.ts:
--------------------------------------------------------------------------------
1 | import Box, { BoxProps } from '@mui/material/Box';
2 | import Chip, { ChipProps } from '@mui/material/Chip';
3 | import Grid, { GridProps } from '@mui/material/Grid';
4 | import Typography, { TypographyProps } from '@mui/material/Typography';
5 | import { alpha, styled } from '@mui/material/styles';
6 |
7 | export const StyledChip = styled(Chip)(({ theme }) => ({
8 | padding: '0px 5px',
9 | marginRight: '5px',
10 | background: `linear-gradient(45deg, ${theme.palette.error.dark} 0%, ${theme.palette.error.light} 100%)`,
11 | color: '#ffffff',
12 | }));
13 |
14 | export const ItemBackground = styled(Box)(({ theme }) => ({
15 | width: '100%',
16 | position: 'relative',
17 | [theme.breakpoints.up('md')]: {
18 | paddingBottom: '40.25%',
19 | width: '100% !important',
20 | },
21 | [theme.breakpoints.down('md')]: {
22 | paddingBottom: '150%',
23 | },
24 | }));
25 |
26 | export const HeaderImage = styled('img')(() => ({
27 | width: '100%',
28 | height: '100%',
29 | objectFit: 'cover',
30 | zIndex: '5',
31 | position: 'absolute',
32 | top: 0,
33 | left: 0,
34 | bottom: 0,
35 | right: 0,
36 | }));
37 |
38 | export const LinearGradient = styled(Box)(({ theme }) => ({
39 | position: 'absolute',
40 | top: 0,
41 | left: 0,
42 | bottom: 0,
43 | right: 0,
44 | width: '100%',
45 | height: '100%',
46 | zIndex: '10',
47 | [theme.breakpoints.up('md')]: {
48 | background: `linear-gradient( 45deg, ${theme.palette.background.default} 20%, ${alpha(
49 | theme.palette.background.paper,
50 | 0.5,
51 | )} 70%, rgba(0, 0, 0, 0) 100% )`,
52 | },
53 | [theme.breakpoints.down('md')]: {
54 | background: `linear-gradient( 45deg, ${theme.palette.background.default} 20%, ${alpha(
55 | theme.palette.background.paper,
56 | 0.5,
57 | )} 70%, rgba(0, 0, 0, 0) 100% ), linear-gradient( 325deg, ${
58 | theme.palette.background.paper
59 | } 20%, ${alpha(theme.palette.background.paper, 0.5)} 70%, rgba(0, 0, 0, 0) 100% )`,
60 | },
61 | }));
62 |
63 | export const ItemContentWrapper = styled(Box)(({ theme }) => ({
64 | position: 'absolute',
65 | zIndex: '20',
66 | display: 'flex',
67 | height: 'fit-content',
68 | flexDirection: 'column',
69 | top: 0,
70 | left: 0,
71 | right: 0,
72 | bottom: 0,
73 | marginTop: 'auto',
74 | marginLeft: '10%',
75 | marginBottom: '30px',
76 | [theme.breakpoints.down('md')]: {
77 | alignItems: 'center',
78 | marginLeft: 'auto',
79 | marginBottom: '10px',
80 | },
81 | }));
82 |
83 | export const ItemContentTitleDown = styled(Typography)(({ theme }) => ({
84 | fontWeight: '400',
85 | width: '90%',
86 | marginBottom: '10px',
87 | [theme.breakpoints.down('md')]: {
88 | textAlign: 'center',
89 | },
90 | }));
91 |
92 | export const ItemContentTitleUp = styled(Typography)(({ theme }) => ({
93 | fontWeight: '500',
94 | width: '90%',
95 | marginBottom: '10px',
96 | [theme.breakpoints.down('md')]: {
97 | textAlign: 'center',
98 | },
99 | }));
100 |
101 | export const ItemContentDescription = styled(Typography)(({ theme }) => ({
102 | width: '80%',
103 | [theme.breakpoints.down('md')]: {
104 | display: 'none',
105 | },
106 | }));
107 |
108 | export const StyledGridContainerParent = styled(Grid)(({ theme }) => ({
109 | marginTop: '20px',
110 | [theme.breakpoints.down('md')]: {
111 | justifyContent: 'center',
112 | },
113 | }));
114 |
115 | export const StyledGridContainerChild = styled(Grid)(({ theme }) => ({
116 | marginBottom: '20px',
117 | [theme.breakpoints.down('md')]: {
118 | justifyContent: 'center',
119 | },
120 | }));
121 |
122 | export const CarouselWrapper = styled(Box)(() => ({
123 | width: '100%',
124 | position: 'relative',
125 | }));
126 |
--------------------------------------------------------------------------------
/src/components/DCircularRating/index.tsx:
--------------------------------------------------------------------------------
1 | import CircularProgress, {
2 | CircularProgressProps,
3 | circularProgressClasses,
4 | } from '@mui/material/CircularProgress';
5 | import Typography from '@mui/material/Typography';
6 | import React from 'react';
7 |
8 | import { CircularRatingWrapper, RatingTypographyWrapper } from './styles';
9 |
10 | const CircularRating = (props: CircularProgressProps & { value: number }) => {
11 | return (
12 |
13 |
22 |
23 | {`${Math.round(
24 | props.value,
25 | )}%`}
26 |
27 |
28 | );
29 | };
30 |
31 | export default CircularRating;
32 |
--------------------------------------------------------------------------------
/src/components/DCircularRating/styles.ts:
--------------------------------------------------------------------------------
1 | import Box, { BoxProps } from '@mui/material/Box';
2 | import { styled } from '@mui/material/styles';
3 |
4 | export const CircularRatingWrapper = styled(Box)(({ theme }) => ({
5 | position: 'relative',
6 | display: 'inline-flex',
7 | backgroundColor: theme.palette.background.paper,
8 | borderRadius: '50%',
9 | padding: '2px',
10 | }));
11 |
12 | export const RatingTypographyWrapper = styled(Box)(() => ({
13 | top: 0,
14 | left: 0,
15 | bottom: 0,
16 | right: 0,
17 | position: 'absolute',
18 | display: 'flex',
19 | alignItems: 'center',
20 | justifyContent: 'center',
21 | }));
22 |
--------------------------------------------------------------------------------
/src/components/DColorSelector/index.tsx:
--------------------------------------------------------------------------------
1 | import { Box, IconButton, TextField } from '@mui/material';
2 | import InputAdornment from '@mui/material/InputAdornment';
3 | import React from 'react';
4 | import { HexColorPicker } from 'react-colorful';
5 |
6 | const ColorSelector = ({ defaultColor, title }: any) => {
7 | const [color, setColor] = React.useState(defaultColor);
8 |
9 | const wrapperRef = React.useRef(null);
10 | const [isVisible, setIsVisible] = React.useState(false);
11 |
12 | React.useEffect(() => {
13 | document.addEventListener('click', handleClickOutside, false);
14 | return () => {
15 | document.removeEventListener('click', handleClickOutside, false);
16 | };
17 | }, [color]);
18 |
19 | const handleClickOutside = (event: any) => {
20 | if (wrapperRef.current && !wrapperRef.current.contains(event.target)) {
21 | setIsVisible(false);
22 | }
23 | };
24 |
25 | const handleOpen = () => setIsVisible(true);
26 |
27 | return (
28 |
44 |
52 |
53 | ),
54 | endAdornment: (
55 |
56 |
63 | {isVisible ? (
64 |
65 | ) : (
66 |
67 | )}
68 | {isVisible && (
69 |
76 |
77 |
78 | )}
79 |
80 |
81 | ),
82 | }}
83 | >
84 | );
85 | };
86 |
87 | export default ColorSelector;
88 |
--------------------------------------------------------------------------------
/src/components/DEpisodeCard/index.tsx:
--------------------------------------------------------------------------------
1 | import React, { useState } from 'react';
2 | import { Link } from 'react-router-dom';
3 |
4 | import { APP_NO_IMAGE_POSTER } from '../../config';
5 | import { APP_API_PATH, APP_API_VERSION_PATH, APP_THUMBNAIL_QUALITY } from '../../config';
6 | import DInfoModal from '../DInfoModal';
7 | import {
8 | BottomButtonWrapper,
9 | Button,
10 | Card,
11 | CardTitle,
12 | CardWrapper,
13 | EpisodeTag,
14 | ImageWrapper,
15 | ItemImage,
16 | PlayButton,
17 | TopContentWrapper,
18 | } from './styles';
19 |
20 | const DEpisodeCard = ({ data, item, season_index }: any) => {
21 | const [openModalState, setOpenModalState] = useState(false);
22 |
23 | const openInfoModal = (event: any) => {
24 | event.preventDefault();
25 | setOpenModalState(true);
26 | };
27 | const closeInfoModal = () => {
28 | setOpenModalState(false);
29 | };
30 |
31 | return (
32 |
33 |
34 |
35 |
45 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
66 |
69 |
70 |
71 |
72 | {(item && item.title && (item.title || null)) ||
73 | (item && item.name && (item.name || null))}
74 |
75 |
81 |
82 | );
83 | };
84 |
85 | export default DEpisodeCard;
86 |
--------------------------------------------------------------------------------
/src/components/DEpisodeCard/styles.ts:
--------------------------------------------------------------------------------
1 | import Box, { BoxProps } from '@mui/material/Box';
2 | import Chip, { ChipProps } from '@mui/material/Chip';
3 | import IconButton, { IconButtonProps } from '@mui/material/IconButton';
4 | import Typography, { TypographyProps } from '@mui/material/Typography';
5 | import { alpha, styled } from '@mui/material/styles';
6 |
7 | export const PlayButton = styled(Box)(({ theme }) => ({
8 | position: 'absolute',
9 | top: '0',
10 | bottom: '0',
11 | left: '0',
12 | right: '0',
13 | margin: 'auto',
14 | width: '70px',
15 | height: '70px',
16 | borderRadius: '50%',
17 | backgroundColor: alpha(theme.palette.background.default, 0.9),
18 | boxShadow: 'rgba(100, 100, 111, 0.2) 0px 7px 29px 0px',
19 | transition: '0.2s ease-out',
20 | opacity: '0',
21 | '& i': {
22 | color: theme.palette.text.primary,
23 | fontSize: '40px',
24 | display: 'flex',
25 | justifyContent: 'center',
26 | alignItems: 'center',
27 | width: '100%',
28 | height: '100%',
29 | },
30 | }));
31 |
32 | export const ImageWrapper = styled(Box)(({ theme }) => ({
33 | position: 'relative',
34 | width: '100%',
35 | paddingBottom: '60%',
36 | borderRadius: theme.shape.borderRadius,
37 | overflow: 'hidden',
38 | backgroundColor: theme.palette.primary.contrastText,
39 | transition: '0.2s ease-out',
40 | }));
41 |
42 | export const TopContentWrapper = styled(Box)(() => ({
43 | position: 'absolute',
44 | top: '0',
45 | padding: '10px',
46 | display: 'flex',
47 | justifyContent: 'start',
48 | transition: '0.2s ease-out',
49 | width: '100%',
50 | opacity: '1',
51 | }));
52 |
53 | export const BottomButtonWrapper = styled(Box)(({ theme }) => ({
54 | position: 'absolute',
55 | bottom: '2px',
56 | padding: '10px',
57 | display: 'flex',
58 | justifyContent: 'space-between',
59 | borderRadius: theme.shape.borderRadius,
60 | background: `linear-gradient(0deg, ${theme.palette.background.paper} 0%, #ffffff00 100%)`,
61 | transition: '0.2s ease-out',
62 | width: 'calc(100% - 4px)',
63 | opacity: '0',
64 | }));
65 |
66 | export const Button = styled(IconButton)(({ theme }) => ({
67 | height: '40px',
68 | width: '40px',
69 | backgroundColor: alpha(theme.palette.background.default, 0.8),
70 | backdropFilter: 'blur(10px)',
71 | transition: '0.2s ease-out',
72 | color: theme.palette.text.primary,
73 | boxShadow: 'rgba(100, 100, 111, 0.2) 0px 7px 29px 0px',
74 | '&:hover': {
75 | backgroundColor: theme.palette.background.default,
76 | color: '#FF007A',
77 | },
78 | }));
79 |
80 | export const EpisodeTag = styled(Chip)(({ theme }) => ({
81 | color: '#ffffff',
82 | marginRight: '10px',
83 | fontWeight: '500',
84 | fontSize: '16px',
85 | padding: '5px',
86 | background: `linear-gradient(45deg, ${theme.palette.error.dark} 0%, ${theme.palette.error.light} 100%)`,
87 | borderRadius: theme.shape.borderRadius,
88 | boxShadow: '0px 0px 70px 20px rgba(0,0,0,0.75)',
89 | }));
90 |
91 | export const CardTitle = styled(Typography)(() => ({
92 | padding: '10px',
93 | }));
94 |
95 | export const Card = styled(Box)(({ theme }) => ({
96 | position: 'relative',
97 | boxSizing: 'border-box',
98 | transition: '0.2s ease-out',
99 | '&:hover .imageWrapper': {
100 | boxShadow: `0px 0px 0px 2px ${alpha(theme.palette.primary.dark, 1)}`,
101 | },
102 | '&:hover .imageWrapper .playButton': {
103 | opacity: '1',
104 | },
105 | '&:hover .imageWrapper .image': {
106 | opacity: '0.5',
107 | },
108 | '&:hover .bottomButtonWrapper': {
109 | opacity: '1',
110 | color: theme.palette.secondary.main,
111 | },
112 | '&:hover .topContentWrapper': {
113 | opacity: '1',
114 | },
115 | }));
116 |
117 | export const CardWrapper = styled(Box)(({ theme }) => ({
118 | position: 'relative',
119 | width: '100%',
120 | overflow: 'hidden',
121 | borderRadius: theme.shape.borderRadius,
122 | padding: '2px',
123 | }));
124 |
125 | export const ItemImage = styled('img')(() => ({
126 | position: 'absolute',
127 | top: '0',
128 | left: '0',
129 | bottom: '0',
130 | right: '0',
131 | boxSizing: 'border-box',
132 | padding: '0',
133 | border: 'none',
134 | margin: 'auto',
135 | display: 'block',
136 | width: '0',
137 | height: '0',
138 | minWidth: '100%',
139 | maxWidth: '100%',
140 | minHeight: '100%',
141 | maxHeight: '100%',
142 | objectFit: 'cover',
143 | transition: '0.2s ease-out',
144 | }));
145 |
--------------------------------------------------------------------------------
/src/components/DExternalModal/index.tsx:
--------------------------------------------------------------------------------
1 | import { Grid } from '@mui/material';
2 | import DialogContent from '@mui/material/DialogContent';
3 | import React, { useEffect, useState } from 'react';
4 |
5 | import { APP_OS } from '../../config';
6 | import DButton from '../DButton';
7 | import { InfoModal } from './styles';
8 |
9 | const DExternalModal = ({ videoData, currentState, closeInfoModal }: any) => {
10 | const [mobileUrl, setMobileUrl] = useState('');
11 |
12 | useEffect(() => {
13 | if (APP_OS === 'Android') {
14 | const streamUrl = new URL(videoData.url);
15 | const scheme = streamUrl.protocol.slice(0, -1);
16 | streamUrl.hash = `Intent;action=android.intent.action.VIEW;scheme=${scheme};type=video/x-matroska;S.title=${encodeURIComponent(
17 | videoData.title,
18 | )};end`;
19 | streamUrl.protocol = 'intent';
20 | setMobileUrl(streamUrl.toString());
21 | } else if (APP_OS === 'iOS') {
22 | const streamUrl = new URL(videoData.url);
23 | streamUrl.host = 'x-callback-url';
24 | streamUrl.port = '';
25 | streamUrl.pathname = 'stream';
26 | streamUrl.search = `url=${videoData.url}`;
27 | streamUrl.protocol = 'vlc-x-callback';
28 | setMobileUrl(streamUrl.toString());
29 | }
30 | }, [videoData]);
31 |
32 | const handleShare = () => {
33 | navigator
34 | .share({
35 | title: window.document.title,
36 | url: window.location.origin,
37 | })
38 | .then(() => {
39 | console.log('Thanks for sharing!');
40 | })
41 | .catch(console.error);
42 | };
43 |
44 | return (
45 |
52 |
53 |
54 |
55 |
56 | }>
57 | Download
58 |
59 |
60 |
61 |
62 | }
65 | >
66 | Share
67 |
68 |
69 | {APP_OS === 'Windows' && (
70 |
71 |
72 | }>
73 | PotPlayer
74 |
75 |
76 |
77 | )}
78 | {mobileUrl !== '' && (
79 |
80 |
81 | }>
82 | Player
83 |
84 |
85 |
86 | )}
87 |
88 |
89 |
90 | );
91 | };
92 |
93 | export default DExternalModal;
94 |
--------------------------------------------------------------------------------
/src/components/DExternalModal/styles.ts:
--------------------------------------------------------------------------------
1 | import Dialog, { DialogProps } from '@mui/material/Dialog';
2 | import { styled } from '@mui/material/styles';
3 |
4 | export const InfoModal = styled(Dialog)(({ theme }) => ({
5 | '& .Dester-DialogContent-root': {
6 | padding: theme.spacing(2),
7 | backgroundColor: theme.palette.background.default,
8 | border: '0px',
9 | },
10 | '& .Dester-Dialog-container .Dester-Dialog-paper': {
11 | boxShadow: `0px 0px 0px 2px ${theme.palette.background.default}`,
12 | borderRadius: theme.shape.borderRadius,
13 | },
14 | '& .Dester-DialogActions-root': {
15 | padding: theme.spacing(1),
16 | },
17 | }));
18 |
--------------------------------------------------------------------------------
/src/components/DHelmet/HelmetConstants.js:
--------------------------------------------------------------------------------
1 | export const ATTRIBUTE_NAMES = {
2 | BODY: 'bodyAttributes',
3 | HTML: 'htmlAttributes',
4 | TITLE: 'titleAttributes',
5 | };
6 |
7 | export const TAG_NAMES = {
8 | BASE: 'base',
9 | BODY: 'body',
10 | HEAD: 'head',
11 | HTML: 'html',
12 | LINK: 'link',
13 | META: 'meta',
14 | NOSCRIPT: 'noscript',
15 | SCRIPT: 'script',
16 | STYLE: 'style',
17 | TITLE: 'title',
18 | };
19 |
20 | export const VALID_TAG_NAMES = Object.values(TAG_NAMES);
21 |
22 | export const TAG_PROPERTIES = {
23 | CHARSET: 'charset',
24 | CSS_TEXT: 'cssText',
25 | HREF: 'href',
26 | HTTPEQUIV: 'http-equiv',
27 | INNER_HTML: 'innerHTML',
28 | ITEM_PROP: 'itemprop',
29 | NAME: 'name',
30 | PROPERTY: 'property',
31 | REL: 'rel',
32 | SRC: 'src',
33 | TARGET: 'target',
34 | };
35 |
36 | export const REACT_TAG_MAP = {
37 | accesskey: 'accessKey',
38 | charset: 'charSet',
39 | class: 'className',
40 | contenteditable: 'contentEditable',
41 | contextmenu: 'contextMenu',
42 | 'http-equiv': 'httpEquiv',
43 | itemprop: 'itemProp',
44 | tabindex: 'tabIndex',
45 | };
46 |
47 | export const HELMET_PROPS = {
48 | DEFAULT_TITLE: 'defaultTitle',
49 | DEFER: 'defer',
50 | ENCODE_SPECIAL_CHARACTERS: 'encodeSpecialCharacters',
51 | ON_CHANGE_CLIENT_STATE: 'onChangeClientState',
52 | TITLE_TEMPLATE: 'titleTemplate',
53 | };
54 |
55 | export const HTML_TAG_MAP = Object.keys(REACT_TAG_MAP).reduce((obj, key) => {
56 | obj[REACT_TAG_MAP[key]] = key;
57 | return obj;
58 | }, {});
59 |
60 | export const SELF_CLOSING_TAGS = [TAG_NAMES.NOSCRIPT, TAG_NAMES.SCRIPT, TAG_NAMES.STYLE];
61 |
62 | export const HELMET_ATTRIBUTE = 'data-react-helmet';
63 |
--------------------------------------------------------------------------------
/src/components/DHelmet/sideEffect.js:
--------------------------------------------------------------------------------
1 | import React, { PureComponent } from 'react';
2 |
3 | const canUseDOM = !!(
4 | typeof window !== 'undefined' &&
5 | window.document &&
6 | window.document.createElement
7 | );
8 |
9 | export default function withSideEffect(
10 | reducePropsToState,
11 | handleStateChangeOnClient,
12 | mapStateOnServer,
13 | ) {
14 | if (typeof reducePropsToState !== 'function') {
15 | throw new Error('Expected reducePropsToState to be a function.');
16 | }
17 | if (typeof handleStateChangeOnClient !== 'function') {
18 | throw new Error('Expected handleStateChangeOnClient to be a function.');
19 | }
20 | if (typeof mapStateOnServer !== 'undefined' && typeof mapStateOnServer !== 'function') {
21 | throw new Error('Expected mapStateOnServer to either be undefined or a function.');
22 | }
23 |
24 | function getDisplayName(WrappedComponent) {
25 | return WrappedComponent.displayName || WrappedComponent.name || 'Component';
26 | }
27 |
28 | return function wrap(WrappedComponent) {
29 | if (typeof WrappedComponent !== 'function') {
30 | throw new Error('Expected WrappedComponent to be a React component.');
31 | }
32 |
33 | let mountedInstances = [];
34 | let state;
35 |
36 | function emitChange() {
37 | state = reducePropsToState(
38 | mountedInstances.map(function (instance) {
39 | return instance.props;
40 | }),
41 | );
42 |
43 | if (SideEffect.canUseDOM) {
44 | handleStateChangeOnClient(state);
45 | } else if (mapStateOnServer) {
46 | state = mapStateOnServer(state);
47 | }
48 | }
49 |
50 | class SideEffect extends PureComponent {
51 | // Try to use displayName of wrapped component
52 | static displayName = `SideEffect(${getDisplayName(WrappedComponent)})`;
53 |
54 | // Expose canUseDOM so tests can monkeypatch it
55 | static canUseDOM = canUseDOM;
56 |
57 | static peek() {
58 | return state;
59 | }
60 |
61 | static rewind() {
62 | if (SideEffect.canUseDOM) {
63 | throw new Error(
64 | 'You may only call rewind() on the server. Call peek() to read the current state.',
65 | );
66 | }
67 |
68 | let recordedState = state;
69 | state = undefined;
70 | mountedInstances = [];
71 | return recordedState;
72 | }
73 |
74 | UNSAFE_componentWillMount() {
75 | mountedInstances.push(this);
76 | emitChange();
77 | }
78 |
79 | componentDidUpdate() {
80 | emitChange();
81 | }
82 |
83 | componentWillUnmount() {
84 | const index = mountedInstances.indexOf(this);
85 | mountedInstances.splice(index, 1);
86 | emitChange();
87 | }
88 |
89 | render() {
90 | return ;
91 | }
92 | }
93 |
94 | return SideEffect;
95 | };
96 | }
97 |
--------------------------------------------------------------------------------
/src/components/DInfoModal/styles.ts:
--------------------------------------------------------------------------------
1 | import Box, { BoxProps } from '@mui/material/Box';
2 | import Chip, { ChipProps } from '@mui/material/Chip';
3 | import Dialog from '@mui/material/Dialog';
4 | import IconButton from '@mui/material/IconButton';
5 | import { alpha, styled } from '@mui/material/styles';
6 |
7 | export const InfoModal = styled(Dialog)(({ theme }) => ({
8 | '& .Dester-DialogContent-root': {
9 | padding: theme.spacing(2),
10 | backgroundColor: alpha('#000000', 1),
11 | border: '0px',
12 | },
13 | '& .Dester-Dialog-container .Dester-Dialog-paper': {
14 | boxShadow: `0px 0px 0px 2px ${theme.palette.background.default}`,
15 | borderRadius: theme.shape.borderRadius,
16 | },
17 | '& .Dester-DialogActions-root': {
18 | padding: theme.spacing(1),
19 | },
20 | }));
21 |
22 | export const InfoModalBackdrop = styled(Box)(() => ({
23 | width: '100%',
24 | paddingBottom: '40%',
25 | position: 'relative',
26 | }));
27 |
28 | export const ButtonWrapper = styled(Box)(() => ({
29 | marginBottom: '20px',
30 | display: 'flex',
31 | alignItems: 'center',
32 | }));
33 |
34 | export const InfoModalBackdropWrapper = styled(Box)(() => ({
35 | background: 'linear-gradient(0deg, rgba(0,0,0,1) 0%, rgba(0,0,0,0.06) 85%, rgba(0,0,0,0) 100%)',
36 | position: 'absolute',
37 | width: '100%',
38 | height: '100%',
39 | }));
40 |
41 | export const CloseButton = styled(IconButton)(({ theme }) => ({
42 | position: 'absolute',
43 | right: '8px',
44 | top: '8px',
45 | backgroundColor: alpha(theme.palette.background.default, 0.5),
46 | backdropFilter: 'blur(10)',
47 | color: '#ffffff',
48 | '&:hover': {
49 | color: '#ffffff',
50 | backgroundColor: alpha(theme.palette.background.default, 1),
51 | },
52 | }));
53 |
54 | export const ModalImage = styled('img')(() => ({
55 | position: 'absolute',
56 | inset: '0px',
57 | boxSizing: 'border-box',
58 | padding: '0px',
59 | border: 'none',
60 | margin: 'auto',
61 | display: 'block',
62 | width: '0px',
63 | height: '0px',
64 | minWidth: '100%',
65 | maxWidth: '100%',
66 | minHeight: '100%',
67 | maxHeight: '100%',
68 | objectFit: 'cover',
69 | }));
70 |
71 | export const ChipContainer = styled(Box)(() => ({
72 | marginRight: '0px',
73 | marginLeft: 'auto',
74 | }));
75 |
76 | export const StyledChip = styled(Chip)(({ theme }) => ({
77 | padding: '0px 5px',
78 | marginLeft: '5px',
79 | backgroundColor: alpha(theme.palette.secondary.main, 0.8),
80 | color: '#ffffff',
81 | }));
82 |
--------------------------------------------------------------------------------
/src/components/DItemCard/index.tsx:
--------------------------------------------------------------------------------
1 | import React, { useState } from 'react';
2 | import { Link } from 'react-router-dom';
3 |
4 | import {
5 | APP_API_PATH,
6 | APP_API_VERSION_PATH,
7 | APP_NO_IMAGE_POSTER,
8 | APP_POSTER_QUALITY,
9 | } from '../../config';
10 | import CircularRating from '../DCircularRating';
11 | import DInfoModal from '../DInfoModal';
12 | import {
13 | BottomButtonWrapper,
14 | Button,
15 | Card,
16 | CardTitle,
17 | CardWrapper,
18 | ImageWrapper,
19 | ItemImage,
20 | PlayButton,
21 | TopButtonWrapper,
22 | } from './styles';
23 |
24 | const DItemCard = ({ item, type }: any) => {
25 | const [openModalState, setOpenModalState] = useState(false);
26 | const openInfoModal = (event: any) => {
27 | event.preventDefault();
28 | setOpenModalState(true);
29 | };
30 | const closeInfoModal = () => {
31 | setOpenModalState(false);
32 | };
33 |
34 | return (
35 |
36 |
37 |
38 |
39 |
40 |
41 |
51 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
69 |
72 |
73 |
74 | {item.title || item.name}
75 |
81 |
82 | );
83 | };
84 |
85 | export default DItemCard;
86 |
--------------------------------------------------------------------------------
/src/components/DItemCard/styles.ts:
--------------------------------------------------------------------------------
1 | import Box, { BoxProps } from '@mui/material/Box';
2 | import IconButton, { IconButtonProps } from '@mui/material/IconButton';
3 | import Typography, { TypographyProps } from '@mui/material/Typography';
4 | import { alpha, styled } from '@mui/material/styles';
5 |
6 | export const PlayButton = styled(Box)(({ theme }) => ({
7 | position: 'absolute',
8 | top: '0',
9 | bottom: '0',
10 | left: '0',
11 | right: '0',
12 | margin: 'auto',
13 | width: '70px',
14 | height: '70px',
15 | borderRadius: '50%',
16 | backgroundColor: alpha(theme.palette.background.default, 0.9),
17 | boxShadow: 'rgba(100, 100, 111, 0.2) 0px 7px 29px 0px',
18 | transition: '0.2s ease-out',
19 | opacity: '0',
20 | '& i': {
21 | color: theme.palette.text.primary,
22 | fontSize: '40px',
23 | display: 'flex',
24 | justifyContent: 'center',
25 | alignItems: 'center',
26 | width: '100%',
27 | height: '100%',
28 | },
29 | }));
30 |
31 | export const ImageWrapper = styled(Box)(({ theme }) => ({
32 | position: 'relative',
33 | width: '100%',
34 | paddingBottom: '150%',
35 | borderRadius: theme.shape.borderRadius,
36 | overflow: 'hidden',
37 | backgroundColor: theme.palette.primary.contrastText,
38 | transition: '0.2s ease-out',
39 | }));
40 |
41 | export const BottomButtonWrapper = styled(Box)(({ theme }) => ({
42 | position: 'absolute',
43 | bottom: '0',
44 | padding: '10px',
45 | display: 'flex',
46 | justifyContent: 'space-between',
47 | borderRadius: theme.shape.borderRadius,
48 | background: `linear-gradient(0deg, ${theme.palette.background.paper} 0%, #ffffff00 100%)`,
49 | transition: '0.2s ease-out',
50 | width: '100%',
51 | opacity: '0',
52 | }));
53 |
54 | export const TopButtonWrapper = styled(Box)(() => ({
55 | position: 'absolute',
56 | top: '0',
57 | padding: '5px',
58 | display: 'flex',
59 | justifyContent: 'space-between',
60 | transition: '0.2s ease-out',
61 | width: '100%',
62 | zIndex: '1',
63 | }));
64 |
65 | export const Button = styled(IconButton)(({ theme }) => ({
66 | height: '40px',
67 | width: '40px',
68 | backgroundColor: alpha(theme.palette.background.default, 0.8),
69 | backdropFilter: 'blur(10px)',
70 | transition: '0.2s ease-out',
71 | color: theme.palette.text.primary,
72 | boxShadow: 'rgba(100, 100, 111, 0.2) 0px 7px 29px 0px',
73 | '&:hover': {
74 | backgroundColor: theme.palette.background.default,
75 | color: '#FF007A',
76 | },
77 | }));
78 |
79 | export const CardTitle = styled(Typography)(() => ({
80 | padding: '10px',
81 | overflow: 'hidden',
82 | whiteSpace: 'nowrap',
83 | textOverflow: 'ellipsis',
84 | }));
85 |
86 | export const Card = styled(Box)(({ theme }) => ({
87 | position: 'relative',
88 | boxSizing: 'border-box',
89 | transition: '0.2s ease-out',
90 | '&:hover .imageWrapper': {
91 | boxShadow: `0px 0px 0px 2px ${alpha(theme.palette.primary.dark, 1)}`,
92 | },
93 | '&:hover .imageWrapper .playButton': {
94 | opacity: '1',
95 | },
96 | '&:hover .imageWrapper .image': {
97 | opacity: '0.5',
98 | },
99 | '&:hover .bottomButtonWrapper': {
100 | opacity: '1',
101 | transition: '0.2s ease-out',
102 | color: theme.palette.secondary.main,
103 | },
104 | }));
105 |
106 | export const CardWrapper = styled(Box)(() => ({
107 | position: 'relative',
108 | width: '100%',
109 | }));
110 |
111 | export const ItemImage = styled('img')(() => ({
112 | position: 'absolute',
113 | top: '0',
114 | left: '0',
115 | bottom: '0',
116 | right: '0',
117 | boxSizing: 'border-box',
118 | padding: '0',
119 | border: 'none',
120 | margin: 'auto',
121 | display: 'block',
122 | width: '0',
123 | height: '0',
124 | minWidth: '100%',
125 | maxWidth: '100%',
126 | minHeight: '100%',
127 | maxHeight: '100%',
128 | objectFit: 'cover',
129 | transition: '0.2s ease-out',
130 | }));
131 |
--------------------------------------------------------------------------------
/src/components/DItemLogo/index.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | import { LogoImage } from './styles';
4 |
5 | const DItemLogo = ({ src }: any) => {
6 | return ;
7 | };
8 |
9 | export default DItemLogo;
10 |
--------------------------------------------------------------------------------
/src/components/DItemLogo/styles.ts:
--------------------------------------------------------------------------------
1 | import { styled } from '@mui/material/styles';
2 |
3 | export const LogoImage = styled('img')(({ theme }) => ({
4 | boxsizing: 'border-box',
5 | padding: '20px',
6 | border: 'none',
7 | width: 'auto',
8 | height: 'auto',
9 | minWidth: '100px',
10 | maxWidth: '300px',
11 | maxHeight: '180px',
12 | objectFit: 'contain',
13 | [theme.breakpoints.down('lg')]: {
14 | maxWidth: '200px',
15 | maxHeight: '80px',
16 | },
17 | [theme.breakpoints.down('md')]: {
18 | maxWidth: '80%',
19 | maxHeight: '150px',
20 | },
21 | }));
22 |
--------------------------------------------------------------------------------
/src/components/DLoader/index.tsx:
--------------------------------------------------------------------------------
1 | import CircularProgress, { circularProgressClasses } from '@mui/material/CircularProgress';
2 | import React from 'react';
3 |
4 | import { MainWrapper } from './styles';
5 |
6 | const DLoader = () => {
7 | return (
8 |
9 |
18 |
19 | );
20 | };
21 |
22 | export default DLoader;
23 |
--------------------------------------------------------------------------------
/src/components/DLoader/styles.ts:
--------------------------------------------------------------------------------
1 | import Box, { BoxProps } from '@mui/material/Box';
2 | import { styled } from '@mui/material/styles';
3 |
4 | export const MainWrapper = styled(Box)(() => ({
5 | width: '100%',
6 | height: '100vh',
7 | display: 'flex',
8 | justifyContent: 'center',
9 | alignItems: 'center',
10 | }));
11 |
--------------------------------------------------------------------------------
/src/components/DNavbar/styles.ts:
--------------------------------------------------------------------------------
1 | import AppBar from '@mui/material/AppBar';
2 | import Box from '@mui/material/Box';
3 | import IconButton from '@mui/material/IconButton';
4 | import { styled } from '@mui/material/styles';
5 |
6 | export const LogoImage = styled('img')(() => ({
7 | position: 'absolute',
8 | top: '0',
9 | left: '0',
10 | bottom: '0',
11 | right: '0',
12 | boxsizing: 'border-box',
13 | padding: '0',
14 | border: 'none',
15 | margin: 'auto',
16 | display: 'block',
17 | width: '0',
18 | height: '0',
19 | minWidth: '100%',
20 | maxWidth: '100%',
21 | minHeight: '100%',
22 | maxHeight: '100%',
23 | objectFit: 'contain',
24 | }));
25 |
26 | export const StyledAppBar = styled(AppBar)(({ theme }) => ({
27 | backgroundColor: theme.palette.background.default,
28 | backgroundImage: `linear-gradient(90deg, rgba(255, 255, 255, 0.09) 0%, ${theme.palette.background.default} 100%)`,
29 | boxShadow: '0px 50px 100px -25px rgba(0,0,0)',
30 | backdropFilter: 'blur(10px)',
31 | width: 'calc(100% - 20px)',
32 | marginTop: '10px',
33 | left: '0',
34 | right: '0',
35 | marginLeft: 'auto',
36 | marginRight: 'auto',
37 | borderRadius: theme.shape.borderRadius,
38 | color: theme.palette.primary.main,
39 | [theme.breakpoints.down('md')]: {
40 | marginTop: '0px',
41 | width: '100%',
42 | borderRadius: '0px',
43 | },
44 | }));
45 |
46 | export const LeftMenuToggle = styled(IconButton)(() => ({
47 | padding: '10px',
48 | fontSize: '24px',
49 | height: '45px',
50 | width: '45px',
51 | marginLeft: '0px',
52 | marginRight: '10px',
53 | }));
54 |
55 | export const LogoWrapper = styled(Box)(() => ({
56 | margin: '0px',
57 | fontSize: '0px',
58 | position: 'relative',
59 | width: '200px',
60 | height: '60px',
61 | minWidth: '160px',
62 | minHeight: '60px',
63 | }));
64 |
65 | export const AvatarButtonWrapper = styled(Box)(() => ({
66 | padding: '0px',
67 | }));
68 |
--------------------------------------------------------------------------------
/src/components/DPersonCard/index.tsx:
--------------------------------------------------------------------------------
1 | import Typography from '@mui/material/Typography';
2 | import { alpha } from '@mui/material/styles';
3 | import { useTheme } from '@mui/system';
4 | import React from 'react';
5 |
6 | import { APP_API_PATH, APP_API_VERSION_PATH, APP_AVATAR_QUALITY } from '../../config';
7 | import {
8 | Container,
9 | InfoWrapper,
10 | PersonAvatarImg,
11 | PersonAvatarWrapper,
12 | VoiceInfoTag,
13 | } from './styles';
14 |
15 | /*
16 | const DPersonTooltip = styled(({ className, ...props }: TooltipProps) => (
17 |
18 | ))(({ theme }) => ({
19 | '& .Dester-Tooltip-tooltip': {
20 | borderRadius: theme.shape.borderRadius,
21 | backgroundColor: theme.palette.background.paper,
22 | color: theme.palette.text.primary,
23 | border: `2px solid ${alpha(theme.palette.background.default, 0.3)}`,
24 | fontSize: 11,
25 | textAlign: 'center',
26 | padding: '5px 15px',
27 | },
28 | }));
29 |
30 |
33 | {item.name}
34 | as
35 | {item.character}
36 |
37 | }
38 | >
39 | */
40 |
41 | const DPersonCard = ({ item }: any) => {
42 | const theme = useTheme();
43 | return (
44 |
45 |
54 |
55 |
65 |
66 |
67 |
68 |
69 | {item.name}
70 |
71 |
76 | As
77 |
78 |
79 | {item.character}
80 |
81 |
82 |
83 |
84 |
85 |
86 | );
87 | };
88 |
89 | export default DPersonCard;
90 |
--------------------------------------------------------------------------------
/src/components/DPersonCard/styles.ts:
--------------------------------------------------------------------------------
1 | import Avatar, { AvatarProps } from '@mui/material/Avatar';
2 | import Box, { BoxProps } from '@mui/material/Box';
3 | import { alpha, styled } from '@mui/material/styles';
4 |
5 | export const Container = styled(Box)(() => ({
6 | maxWidth: '220px',
7 | padding: '10px',
8 | }));
9 |
10 | export const PersonAvatarWrapper = styled(Box)(() => ({
11 | aspectRatio: '1/1',
12 | boxShadow: '0px 0px 0px 3px rgba(0,255,179,1)',
13 | borderRadius: '50%',
14 | margin: '0 auto 15px auto',
15 | padding: '3px',
16 | }));
17 |
18 | export const PersonAvatarImg = styled(Avatar)(() => ({
19 | height: '100%',
20 | width: '100%',
21 | }));
22 |
23 | export const InfoWrapper = styled(Box)(({ theme }) => ({
24 | padding: '5px 10px',
25 | backgroundColor: alpha(theme.palette.background.paper, 0.5),
26 | borderRadius: theme.shape.borderRadius,
27 | border: `2px solid ${alpha(theme.palette.background.paper, 0.3)}`,
28 | textAlign: 'center',
29 | position: 'relative',
30 | overflow: 'hidden',
31 | }));
32 |
33 | export const StyledAvatar = styled(Avatar)(() => ({
34 | width: '100%',
35 | height: '100%',
36 | display: 'flex',
37 | justifyContent: 'center',
38 | alignItems: 'center',
39 | }));
40 |
41 | export const VoiceInfoTag = styled(Box)(({ theme }) => ({
42 | position: 'absolute',
43 | bottom: '0px',
44 | right: '0px',
45 | padding: '0px 5px',
46 | borderRadius: '10px 0px 0px 0px',
47 | backgroundColor: alpha(theme.palette.primary.main, 0.1),
48 | color: theme.palette.primary.main,
49 | }));
50 |
--------------------------------------------------------------------------------
/src/components/DReview/DReviewCard/index.tsx:
--------------------------------------------------------------------------------
1 | import Typography from '@mui/material/Typography';
2 | import React from 'react';
3 |
4 | import { APP_API_PATH, APP_API_VERSION_PATH, APP_POSTER_QUALITY } from '../../../config';
5 | import {
6 | AvatarContainer,
7 | AvatarImg,
8 | AvatarTitle,
9 | AvatarWrapper,
10 | ContentWrapper,
11 | DateWrapper,
12 | FeaturedTitleWrapper,
13 | ListItemWrapper,
14 | MainContainer,
15 | SubContainer,
16 | } from './styles';
17 |
18 | const DReviewCard = ({ item }: any) => {
19 | var avatar_path = '';
20 | if (item.author_details.avatar_path && item.author_details.avatar_path.startsWith('/')) {
21 | avatar_path = (item.author_details.avatar_path || '/').substring(1);
22 | }
23 | if (avatar_path.length && !avatar_path.startsWith('https://')) {
24 | avatar_path =
25 | APP_API_PATH +
26 | APP_API_VERSION_PATH +
27 | '/assets/image/' +
28 | APP_POSTER_QUALITY +
29 | item.author_details.avatar_path;
30 | }
31 |
32 | return (
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 | {item.author_details.name || item.author || item.author_details.username}
43 |
44 |
45 |
49 |
50 | {item.author_details.rating}
51 |
52 |
53 |
54 |
55 | {item.content}
56 |
57 |
58 |
59 | {new Date(
60 | Date.parse(item.updated_at || item.created_at),
61 | ).toLocaleDateString('en-US')}
62 |
63 |
64 |
65 |
66 | );
67 | };
68 |
69 | export default DReviewCard;
70 |
--------------------------------------------------------------------------------
/src/components/DReview/DReviewCard/styles.ts:
--------------------------------------------------------------------------------
1 | import Avatar, { AvatarProps } from '@mui/material/Avatar';
2 | import Box, { BoxProps } from '@mui/material/Box';
3 | import Typography, { TypographyProps } from '@mui/material/Typography';
4 | import { alpha, styled } from '@mui/material/styles';
5 |
6 | export const ListItemWrapper = styled(Box)(({ theme }) => ({
7 | display: 'flex',
8 | marginBottom: '10px',
9 | padding: '5px',
10 | borderRadius: theme.shape.borderRadius,
11 | [theme.breakpoints.down('md')]: {
12 | backgroundColor: alpha(theme.palette.background.paper, 0.4),
13 | },
14 | }));
15 |
16 | export const AvatarContainer = styled(Box)(({ theme }) => ({
17 | width: 'fit-content',
18 | height: '100%',
19 | padding: '10px',
20 | textAlign: 'center',
21 | marginRight: '20px',
22 | [theme.breakpoints.down('md')]: {
23 | marginRight: '10px',
24 | },
25 | [theme.breakpoints.down('sm')]: {
26 | marginRight: '0px',
27 | },
28 | }));
29 |
30 | export const AvatarWrapper = styled(Box)(({ theme }) => ({
31 | width: 'fit-content',
32 | height: '100%',
33 | marginBottom: '10px',
34 | textAlign: 'center',
35 | boxShadow: '0px 0px 0px 3px #FF3396',
36 | borderRadius: '50%',
37 | padding: '2px',
38 | [theme.breakpoints.down('sm')]: {
39 | boxShadow: '0px 0px 0px 2px #FF3396',
40 | },
41 | }));
42 |
43 | export const MainContainer = styled(Box)(({ theme }) => ({
44 | borderRadius: theme.shape.borderRadius,
45 | [theme.breakpoints.up('md')]: {
46 | backgroundColor: alpha(theme.palette.background.paper, 0.4),
47 | padding: '10px 20px',
48 | },
49 | [theme.breakpoints.down('md')]: {
50 | padding: '10px 10px',
51 | },
52 | }));
53 |
54 | export const SubContainer = styled(Box)(() => ({
55 | display: 'flex',
56 | justifyContent: 'space-between',
57 | alignItems: 'center',
58 | paddingBottom: '20px',
59 | }));
60 |
61 | export const FeaturedTitleWrapper = styled(Box)(({ theme }) => ({
62 | padding: '5px 10px',
63 | width: 'fit-content',
64 | borderRadius: theme.shape.borderRadius,
65 | display: 'flex',
66 | alignItems: 'center',
67 | [theme.breakpoints.down('md')]: {
68 | padding: '0px 5px',
69 | },
70 | }));
71 |
72 | export const ContentWrapper = styled(Box)(() => ({
73 | overflow: 'hidden',
74 | maxHeight: 'auto',
75 | wordBreak: 'break-all',
76 | paddingRight: '20px',
77 | }));
78 |
79 | export const DateWrapper = styled(Box)(() => ({
80 | paddingTop: '20px',
81 | textAlign: 'left',
82 | }));
83 |
84 | export const AvatarTitle = styled(Typography)(({ theme }) => ({
85 | color: `${theme.palette.mode === 'light' ? '#000000' : theme.palette.primary.main}`,
86 | }));
87 |
88 | export const AvatarImg = styled(Avatar)(({ theme }) => ({
89 | width: '100px',
90 | height: '100px',
91 | [theme.breakpoints.down('md')]: {
92 | width: '80px',
93 | height: '80px',
94 | },
95 | [theme.breakpoints.down('sm')]: {
96 | width: '50px',
97 | height: '50px',
98 | },
99 | }));
100 |
--------------------------------------------------------------------------------
/src/components/DReview/index.tsx:
--------------------------------------------------------------------------------
1 | import Box from '@mui/material/Box';
2 | import React from 'react';
3 |
4 | import DReviewCard from './DReviewCard';
5 | import { ChipIndex, Heading, ListWrapper, ShowMoreButton } from './styles';
6 |
7 | const DReviewList = ({ title, itemData }: any) => {
8 | const [numberOfitemsShown, setNumberOfItemsToShown] = React.useState(3);
9 |
10 | const showMore = () => {
11 | if (numberOfitemsShown + 3 <= itemData.length) {
12 | setNumberOfItemsToShown(numberOfitemsShown + 3);
13 | } else {
14 | setNumberOfItemsToShown(itemData.length);
15 | }
16 | };
17 |
18 | const itemsToShow = React.useMemo(() => {
19 | if (itemData && itemData.length > 0) {
20 | return itemData
21 | .slice(0, numberOfitemsShown)
22 | .map((item: any, index: any) => (
23 |
24 | ));
25 | } else {
26 | return [];
27 | }
28 | }, [itemData, numberOfitemsShown]);
29 |
30 | return (
31 |
32 | {itemData && itemData.length > 0 && (
33 | <>
34 | {title || null}
35 |
36 | {itemsToShow.length ? itemsToShow : 'Loading...'}
37 |
38 | {itemData.length - numberOfitemsShown > 0 ? (
39 |
40 |
41 | More
42 |
43 |
44 |
45 | ) : null}
46 | >
47 | )}
48 |
49 | );
50 | };
51 |
52 | export default DReviewList;
53 |
--------------------------------------------------------------------------------
/src/components/DReview/styles.ts:
--------------------------------------------------------------------------------
1 | import Box, { BoxProps } from '@mui/material/Box';
2 | import Chip, { ChipProps } from '@mui/material/Chip';
3 | import Typography, { TypographyProps } from '@mui/material/Typography';
4 | import { styled } from '@mui/material/styles';
5 |
6 | import DButton from '../DButton';
7 |
8 | export const ShowMoreButton = styled(DButton)(() => ({
9 | padding: '5px 5px 5px 20px',
10 | }));
11 |
12 | export const ListWrapper = styled(Box)(() => ({
13 | maxWidth: '900px',
14 | padding: '10px',
15 | }));
16 |
17 | export const ChipIndex = styled(Chip)(({ theme }) => ({
18 | backgroundColor: '#171E22',
19 | color: '#ffffff',
20 | borderRadius: theme.shape.borderRadius,
21 | padding: '0px 5px',
22 | marginLeft: '10px',
23 | marginTop: '0px',
24 | border: '2px solid #33544A',
25 | }));
26 |
27 | export const Heading = styled(Typography)(() => ({
28 | padding: '0px 20px',
29 | display: 'flex',
30 | alignItems: 'center',
31 | }));
32 |
--------------------------------------------------------------------------------
/src/components/DSeasonCard/index.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Link } from 'react-router-dom';
3 |
4 | import {
5 | APP_API_PATH,
6 | APP_API_VERSION_PATH,
7 | APP_NO_IMAGE_POSTER,
8 | APP_POSTER_QUALITY,
9 | } from '../../config';
10 | // import DInfoModal from '../DInfoModal';
11 | import { Card, CardTitle, CardWrapper, ImageWrapper, ItemImage, PlayButton } from './styles';
12 |
13 | const DSeasonCard = ({ data, type, seasonKey }: any) => {
14 | /*
15 | const [openModalState, setOpenModalState] = useState(false);
16 | const openInfoModal = (event: any) => {
17 | event.preventDefault();
18 | setOpenModalState(true);
19 | };
20 | const closeInfoModal = () => {
21 | setOpenModalState(false);
22 | };
23 |
24 |
25 |
28 |
29 | */
30 | const item = data.seasons[seasonKey];
31 |
32 | return (
33 |
34 |
35 |
36 |
46 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 | {(item && item.title && (item.title || null)) ||
60 | (item && item.name && (item.name || null))}
61 |
62 |
63 | );
64 | };
65 |
66 | export default DSeasonCard;
67 |
--------------------------------------------------------------------------------
/src/components/DSeasonCard/styles.ts:
--------------------------------------------------------------------------------
1 | import Box, { BoxProps } from '@mui/material/Box';
2 | import IconButton, { IconButtonProps } from '@mui/material/IconButton';
3 | import Typography, { TypographyProps } from '@mui/material/Typography';
4 | import { alpha, styled } from '@mui/material/styles';
5 |
6 | export const PlayButton = styled(Box)(() => ({
7 | position: 'absolute',
8 | top: '0',
9 | bottom: '0',
10 | left: '0',
11 | right: '0',
12 | margin: 'auto',
13 | width: '70px',
14 | height: '70px',
15 | borderRadius: '50%',
16 | backgroundColor: 'rgba(0, 0, 0, 0.8)',
17 | transition: '0.2s ease-out',
18 | opacity: '0',
19 | '& i': {
20 | color: '#ffffff',
21 | fontSize: '40px',
22 | display: 'flex',
23 | justifyContent: 'center',
24 | alignItems: 'center',
25 | width: '100%',
26 | height: '100%',
27 | },
28 | }));
29 |
30 | export const ImageWrapper = styled(Box)(({ theme }) => ({
31 | position: 'relative',
32 | width: '100%',
33 | paddingBottom: '150%',
34 | borderRadius: theme.shape.borderRadius,
35 | overflow: 'hidden',
36 | backgroundColor: theme.palette.background.default,
37 | transition: '0.2s ease-out',
38 | }));
39 |
40 | export const BottomButtonWrapper = styled(Box)(({ theme }) => ({
41 | position: 'absolute',
42 | bottom: '0',
43 | padding: '10px',
44 | display: 'flex',
45 | justifyContent: 'right',
46 | borderRadius: theme.shape.borderRadius,
47 | background: `linear-gradient(0deg, ${theme.palette.background.paper} 0%, #ffffff00 100%)`,
48 | transition: '0.2s ease-out',
49 | width: '100%',
50 | opacity: '0',
51 | }));
52 |
53 | export const Button = styled(IconButton)(({ theme }) => ({
54 | height: '40px',
55 | width: '40px',
56 | backgroundColor: alpha(theme.palette.background.default, 0.8),
57 | backdropFilter: 'blur(10px)',
58 | transition: '0.2s ease-out',
59 | color: theme.palette.text.primary,
60 | boxShadow: 'rgba(100, 100, 111, 0.2) 0px 7px 29px 0px',
61 | '&:hover': {
62 | backgroundColor: theme.palette.background.default,
63 | color: '#FF007A',
64 | },
65 | }));
66 |
67 | export const CardTitle = styled(Typography)(() => ({
68 | padding: '10px',
69 | }));
70 |
71 | export const Card = styled('div')(({ theme }) => ({
72 | position: 'relative',
73 | boxSizing: 'border-box',
74 | transition: '0.2s ease-out',
75 | '&:hover .imageWrapper': {
76 | boxShadow: `0px 0px 0px 2px ${alpha(theme.palette.primary.main, 0.8)}`,
77 | },
78 | '&:hover .imageWrapper .playButton': {
79 | opacity: '1',
80 | },
81 | '&:hover .imageWrapper .image': {
82 | opacity: '0.2',
83 | },
84 | '&:hover .bottomButtonWrapper': {
85 | opacity: '1',
86 | color: theme.palette.primary.main,
87 | },
88 | }));
89 |
90 | export const CardWrapper = styled(Box)(() => ({
91 | position: 'relative',
92 | width: '100%',
93 | }));
94 |
95 | export const ItemImage = styled('img')(() => ({
96 | position: 'absolute',
97 | top: '0',
98 | left: '0',
99 | bottom: '0',
100 | right: '0',
101 | boxSizing: 'border-box',
102 | padding: '0',
103 | border: 'none',
104 | margin: 'auto',
105 | display: 'block',
106 | width: '0',
107 | height: '0',
108 | minWidth: '100%',
109 | maxWidth: '100%',
110 | minHeight: '100%',
111 | maxHeight: '100%',
112 | objectFit: 'cover',
113 | transition: '0.2s ease-out',
114 | }));
115 |
--------------------------------------------------------------------------------
/src/components/DSelect/index.tsx:
--------------------------------------------------------------------------------
1 | import FormControl from '@mui/material/FormControl';
2 | import FormHelperText from '@mui/material/FormHelperText';
3 | import Select, { SelectChangeEvent } from '@mui/material/Select';
4 | import { alpha, useTheme } from '@mui/material/styles';
5 | import React from 'react';
6 |
7 | import { DropDownSelectItem } from './styles';
8 |
9 | const DSelect = (props: any) => {
10 | const {
11 | title,
12 | options,
13 | currentOption,
14 | width,
15 | fullWidth,
16 | fixedValue,
17 | fontSize,
18 | onChange,
19 | style,
20 | }: any = props;
21 | const [option, setOption] = React.useState(currentOption);
22 |
23 | const handleChange = (event: SelectChangeEvent) => {
24 | setOption(event.target.value as string);
25 | onChange ? onChange(event) : null;
26 | };
27 |
28 | const theme = useTheme();
29 |
30 | return (
31 |
37 |
45 | {title}
46 |
47 |
102 |
103 | );
104 | };
105 |
106 | export default DSelect;
107 |
--------------------------------------------------------------------------------
/src/components/DSelect/styles.ts:
--------------------------------------------------------------------------------
1 | import MenuItem, { MenuItemProps } from '@mui/material/MenuItem';
2 | import { styled } from '@mui/material/styles';
3 |
4 | export const DropDownSelectItem = styled(MenuItem)(() => ({
5 | // padding: '0px',
6 | }));
7 |
--------------------------------------------------------------------------------
/src/components/DSlider/styles.ts:
--------------------------------------------------------------------------------
1 | import Box, { BoxProps } from '@mui/material/Box';
2 | import Typography, { TypographyProps } from '@mui/material/Typography';
3 | import { styled } from '@mui/material/styles';
4 | import { Swiper } from 'swiper/react';
5 |
6 | export const DSwiper = styled(Swiper)(() => ({
7 | padding: '20px',
8 | '--swiper-navigation-size': '10px',
9 | '& .swiper-button-prev': {
10 | top: '0px',
11 | },
12 | '& .swiper-button-next': {
13 | top: '0px',
14 | },
15 | }));
16 |
17 | export const MainContainer = styled(Box)(() => ({
18 | padding: '10px',
19 | }));
20 |
21 | export const SubContainer = styled(Box)(() => ({
22 | display: 'flex',
23 | justifyContent: 'space-between',
24 | }));
25 |
26 | export const DSliderHeading = styled(Typography)(() => ({
27 | padding: '0px 20px',
28 | display: 'flex',
29 | alignItems: 'center',
30 | }));
31 |
--------------------------------------------------------------------------------
/src/components/DSpacer/index.tsx:
--------------------------------------------------------------------------------
1 | import { DSpacer } from './styles';
2 |
3 | export default DSpacer;
4 |
--------------------------------------------------------------------------------
/src/components/DSpacer/styles.ts:
--------------------------------------------------------------------------------
1 | import Box, { BoxProps } from '@mui/material/Box';
2 | import { styled } from '@mui/material/styles';
3 |
4 | export const DSpacer = styled(Box)(({ theme }) => ({
5 | borderRadius: '50%',
6 | backgroundColor: theme.palette.primary.main,
7 | height: '5px',
8 | width: '5px',
9 | margin: '8px',
10 | }));
11 |
--------------------------------------------------------------------------------
/src/components/DStreamModal/index.tsx:
--------------------------------------------------------------------------------
1 | import DPlayer from '@desterlib/dplayer';
2 | import Box from '@mui/material/Box';
3 | import DialogContent from '@mui/material/DialogContent';
4 | import React from 'react';
5 |
6 | import { APP_IS_ELECTRON } from '../../config';
7 | import WPlayer from '../DWebPlayer';
8 | import { CloseButton, InfoModal } from './styles';
9 |
10 | const DStreamModal = ({ videoData, currentState, closeInfoModal }: any) => {
11 | return (
12 |
19 | {closeInfoModal ? (
20 |
21 |
22 |
23 | ) : null}
24 |
25 |
26 | {APP_IS_ELECTRON ? (
27 |
33 | ) : (
34 |
35 | )}
36 |
37 |
38 |
39 | );
40 | };
41 |
42 | export default DStreamModal;
43 |
--------------------------------------------------------------------------------
/src/components/DStreamModal/styles.ts:
--------------------------------------------------------------------------------
1 | import Dialog, { DialogProps } from '@mui/material/Dialog';
2 | import IconButton, { IconButtonProps } from '@mui/material/IconButton';
3 | import { alpha, styled } from '@mui/material/styles';
4 |
5 | export const InfoModal = styled(Dialog)(({ theme }) => ({
6 | '& .Dester-DialogContent-root': {
7 | padding: theme.spacing(2),
8 | backgroundColor: theme.palette.background.default,
9 | border: '0px',
10 | },
11 | '& .Dester-Dialog-container .Dester-Dialog-paper': {
12 | boxShadow: `0px 0px 0px 2px ${theme.palette.background.default}`,
13 | borderRadius: theme.shape.borderRadius,
14 | },
15 | '& .Dester-DialogActions-root': {
16 | padding: theme.spacing(1),
17 | },
18 | }));
19 |
20 | export const CloseButton = styled(IconButton)(({ theme }) => ({
21 | position: 'absolute',
22 | right: '8px',
23 | top: '8px',
24 | backgroundColor: alpha(theme.palette.text.primary, 0.5),
25 | backdropFilter: 'blur(10)',
26 | color: theme.palette.background.default,
27 | '&:hover': {
28 | color: theme.palette.background.default,
29 | backgroundColor: alpha(theme.palette.text.primary, 1),
30 | },
31 | }));
32 |
--------------------------------------------------------------------------------
/src/components/DTextField/index.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | import { StyledTextField } from './styles';
4 |
5 | const DTextField = ({ ...props }) => {
6 | return ;
7 | };
8 |
9 | export default DTextField;
10 |
--------------------------------------------------------------------------------
/src/components/DTextField/styles.ts:
--------------------------------------------------------------------------------
1 | import TextField, { TextFieldProps } from '@mui/material/TextField';
2 | import { alpha, styled } from '@mui/material/styles';
3 |
4 | export const StyledTextField = styled(TextField)(({ theme }) => ({
5 | '.Dester-InputBase-root': {
6 | color: theme.palette.text.primary,
7 | border: '0px',
8 | borderRadius: theme.shape.borderRadius,
9 | transition: '0.2s ease-out',
10 | backgroundColor: alpha(theme.palette.background.default, 0.7),
11 | backgroundImage: 'linear-gradient(rgba(255, 255, 255, 0.09), rgba(255, 255, 255, 0.09))',
12 | },
13 | '.Dester-InputBase-root:hover': {
14 | backgroundColor: alpha(theme.palette.background.paper, 1),
15 | boxShadow: `0px 0px 0px 2px ${alpha(theme.palette.background.paper, 0.5)}`,
16 | },
17 | '.Dester-OutlinedInput-notchedOutline': {
18 | border: '0px',
19 | },
20 | '.Dester-InputLabel-root.Mui-focused': {
21 | display: 'none',
22 | },
23 | }));
24 |
--------------------------------------------------------------------------------
/src/components/DVideoCard/index.tsx:
--------------------------------------------------------------------------------
1 | import React, { useState } from 'react';
2 |
3 | import { APP_NO_IMAGE_POSTER } from '../../config';
4 | import DVideoModal from '../DVideoModal';
5 | import { Card, CardWrapper, ImageContainer, ItemImage, PlayButton } from './styles';
6 |
7 | const DVideoCard = ({ item }: any) => {
8 | const [openModalState, setOpenModalState] = useState(false);
9 | // eslint-disable-next-line
10 | const openInfoModal = (event: any) => {
11 | event.preventDefault();
12 | setOpenModalState(true);
13 | };
14 | const closeInfoModal = () => {
15 | setOpenModalState(false);
16 | };
17 |
18 | return (
19 |
20 |
21 |
22 |
32 |
33 |
34 |
35 |
36 |
37 |
42 |
43 | );
44 | };
45 |
46 | export default DVideoCard;
47 |
--------------------------------------------------------------------------------
/src/components/DVideoCard/styles.ts:
--------------------------------------------------------------------------------
1 | import Box, { BoxProps } from '@mui/material/Box';
2 | import Button, { ButtonProps } from '@mui/material/Button';
3 | import { alpha, styled } from '@mui/material/styles';
4 |
5 | export const PlayButton = styled(Button)(() => ({
6 | position: 'absolute',
7 | top: '0',
8 | bottom: '0',
9 | left: '0',
10 | right: '0',
11 | margin: 'auto',
12 | width: '70px',
13 | height: '70px',
14 | borderRadius: '50%',
15 | backgroundColor: 'rgba(0, 0, 0, 0.8)',
16 | transition: '0.2s ease-out',
17 | opacity: '0',
18 | '& i': {
19 | color: '#ffffff',
20 | fontSize: '40px',
21 | display: 'flex',
22 | justifyContent: 'center',
23 | alignItems: 'center',
24 | width: '100%',
25 | height: '100%',
26 | },
27 | }));
28 |
29 | export const ImageContainer = styled(Box)(({ theme }) => ({
30 | position: 'relative',
31 | width: '100%',
32 | paddingBottom: '60%',
33 | borderRadius: theme.shape.borderRadius,
34 | overflow: 'hidden',
35 | backgroundColor: '#000000',
36 | transition: '0.2s ease-out',
37 | }));
38 |
39 | export const Card = styled(Box)(({ theme }) => ({
40 | position: 'relative',
41 | boxSizing: 'border-box',
42 | transition: '0.2s ease-out',
43 | '&:hover .imageWrapper': {
44 | boxShadow: `0px 0px 0px 2px ${alpha(theme.palette.primary.main, 0.8)}`,
45 | },
46 | '&:hover .imageWrapper .playButton': {
47 | opacity: '1',
48 | },
49 | '&:hover .imageWrapper .image': {
50 | opacity: '0.2',
51 | },
52 | '&:hover .bottomButtonWrapper': {
53 | opacity: '1',
54 | color: theme.palette.primary.main,
55 | },
56 | }));
57 |
58 | export const CardWrapper = styled(Box)(() => ({
59 | position: 'relative',
60 | width: '100%',
61 | }));
62 |
63 | export const ItemImage = styled('img')(() => ({
64 | position: 'absolute',
65 | top: '0',
66 | left: '0',
67 | bottom: '0',
68 | right: '0',
69 | boxSizing: 'border-box',
70 | padding: '0',
71 | border: 'none',
72 | margin: 'auto',
73 | display: 'block',
74 | width: '0',
75 | height: '0',
76 | minWidth: '100%',
77 | maxWidth: '100%',
78 | minHeight: '100%',
79 | maxHeight: '100%',
80 | objectFit: 'cover',
81 | transition: '0.2s ease-out',
82 | }));
83 |
--------------------------------------------------------------------------------
/src/components/DVideoModal/index.tsx:
--------------------------------------------------------------------------------
1 | import Box from '@mui/material/Box';
2 | import DialogContent from '@mui/material/DialogContent';
3 | import React from 'react';
4 |
5 | import { CloseButton, InfoModal } from './styles';
6 |
7 | const DVideoModal = ({ item, currentState, closeInfoModal }: any) => {
8 | return (
9 |
16 | {closeInfoModal ? (
17 |
18 |
19 |
20 | ) : null}
21 |
22 |
23 |
30 |
31 |
32 |
33 | );
34 | };
35 |
36 | export default DVideoModal;
37 |
--------------------------------------------------------------------------------
/src/components/DVideoModal/styles.ts:
--------------------------------------------------------------------------------
1 | import Dialog, { DialogProps } from '@mui/material/Dialog';
2 | import IconButton, { IconButtonProps } from '@mui/material/IconButton';
3 | import { alpha, styled } from '@mui/material/styles';
4 |
5 | export const InfoModal = styled(Dialog)(({ theme }) => ({
6 | '& .Dester-DialogContent-root': {
7 | padding: theme.spacing(2),
8 | backgroundColor: theme.palette.background.default,
9 | border: '0px',
10 | },
11 | '& .Dester-Dialog-container .Dester-Dialog-paper': {
12 | boxShadow: `0px 0px 0px 2px ${theme.palette.background.default}`,
13 | borderRadius: theme.shape.borderRadius,
14 | },
15 | '& .Dester-DialogActions-root': {
16 | padding: theme.spacing(1),
17 | },
18 | }));
19 |
20 | export const CloseButton = styled(IconButton)(({ theme }) => ({
21 | position: 'absolute',
22 | right: '8px',
23 | top: '8px',
24 | backgroundColor: alpha(theme.palette.text.primary, 0.5),
25 | backdropFilter: 'blur(10)',
26 | color: theme.palette.background.default,
27 | '&:hover': {
28 | color: theme.palette.background.default,
29 | backgroundColor: alpha(theme.palette.text.primary, 1),
30 | },
31 | }));
32 |
--------------------------------------------------------------------------------
/src/components/DWebPlayer/DWebPlayerBase.tsx:
--------------------------------------------------------------------------------
1 | import Box from '@mui/material/Box';
2 | import Artplayer from 'artplayer';
3 | import React, { useEffect, useRef } from 'react';
4 |
5 | const DPlayerBase = React.memo(
6 | function ({ src, subtitle, settings, getInstance, style }: any) {
7 | const artRef = useRef();
8 |
9 | useEffect(() => {
10 | const art = new Artplayer({
11 | container: artRef.current,
12 | url: src || '',
13 | subtitle: {
14 | url: subtitle || '',
15 | type: 'srt',
16 | encoding: 'utf-8',
17 | },
18 | ...settings,
19 | });
20 |
21 | if (getInstance && typeof getInstance === 'function') {
22 | getInstance(art);
23 | }
24 |
25 | return () => {
26 | if (art && art.destroy) {
27 | art.destroy(false);
28 | }
29 | };
30 | }, [settings, getInstance]);
31 |
32 | return ;
33 | },
34 | () => true,
35 | );
36 |
37 | DPlayerBase.displayName = 'DPlayerBase';
38 |
39 | export default DPlayerBase;
40 |
--------------------------------------------------------------------------------
/src/components/DWebPlayer/index.tsx:
--------------------------------------------------------------------------------
1 | import Box from '@mui/material/Box';
2 | import Typography from '@mui/material/Typography';
3 | import React, { useState } from 'react';
4 | import { createPortal } from 'react-dom';
5 |
6 | import useBreakpoint from '../../utilities/useBreakpoint';
7 | import DPlayerBase from './DWebPlayerBase';
8 | import DPlaylist from './DWebPlaylist';
9 |
10 | const DPlayer = ({ videoData, aspectRatio }: any) => {
11 | const [isOpen, setIsOpen] = useState(false);
12 | const [art, setArt] = useState({});
13 | const breakpoint = useBreakpoint();
14 |
15 | function handleShitchUrl(url: any, title: any) {
16 | art.switchUrl(url, title);
17 | }
18 | const handlePlaylistOpen = () => {
19 | setIsOpen(true);
20 | };
21 |
22 | const settings = {
23 | fullscreen: true,
24 | setting: true,
25 | loop: true,
26 | flip: true,
27 | playbackRate: true,
28 | aspectRatio: true,
29 | whitelist: ['*'],
30 | layers: [
31 | {
32 | html: '',
33 | name: 'title',
34 | },
35 | ],
36 | controls:
37 | videoData && videoData.playlist && videoData.playlist.length > 0
38 | ? [
39 | {
40 | position: 'right',
41 | html: 'playlist_play',
42 | tooltip: 'Playlist',
43 | click: function () {
44 | handlePlaylistOpen();
45 | },
46 | },
47 | ]
48 | : [],
49 | icons: {
50 | play: 'play_arrow',
51 | pause: 'pause',
52 | volume: 'volume_up',
53 | volumeClose: 'volume_off',
54 | fullscreenOn: 'fullscreen',
55 | fullscreenOff: 'fullscreen_exit',
56 | // loading: '
',
57 | // state: '
',
58 | check: 'closed_caption',
59 | subtitle: 'closed_caption',
60 | screenshot: 'photo_camera',
61 | setting: 'settings',
62 | pip: '',
63 | arrowLeft: '',
64 | arrowRight: '',
65 | playbackRate: '',
66 | aspectRatio: '',
67 | config: '',
68 | lock: '',
69 | unlock: '',
70 | indicator: '',
71 | },
72 | };
73 | return (
74 |
75 |
84 | {
92 | art.on('ready', () => setArt(art));
93 | }}
94 | />
95 |
111 | {breakpoint !== 'xs' && breakpoint !== 'sm' && (
112 |
119 | )}
120 | {/* prettier-ignore */}
121 | {Object.keys(art).length !== 0 &&
122 | createPortal(
123 |
124 | {videoData.title}
125 | {videoData.subTitle}
126 | ,
127 | art.layers.title,
128 | )}
129 |
130 |
131 |
132 | {(breakpoint === 'xs' || breakpoint === 'sm') && (
133 |
134 |
141 |
142 | )}
143 |
144 |
145 | );
146 | };
147 |
148 | export default DPlayer;
149 |
--------------------------------------------------------------------------------
/src/config.ts:
--------------------------------------------------------------------------------
1 | import disconnectedImage from './assets/disconnected.png';
2 | import errorImage from './assets/error.png';
3 | import LogoFullDark from './assets/logo-full-dark.svg';
4 | import LogoFullLight from './assets/logo-full-light.svg';
5 | import noImagePoster from './assets/no-image-poster.svg';
6 | import getOs from './utilities/getOs';
7 | import isElectron from './utilities/isElectron';
8 |
9 | const APP_IS_ELECTRON = isElectron();
10 | const APP_IS_SEPERATE = process.env.REACT_APP_SEPERATE === 'true';
11 | const APP_LOGO_LIGHT = LogoFullLight;
12 | const APP_LOGO_DARK = LogoFullDark;
13 | const APP_NAME = localStorage.getItem('APP_NAME') || 'Dester';
14 | const APP_DESCRIPTION =
15 | localStorage.getItem('APP_DESCRIPTION') ||
16 | 'Dester is a powerful and lightweight media solution to interface your movie and TV libraries in a goddamn gorgeous way';
17 | const APP_VERSION = '';
18 | const APP_API_PATH =
19 | APP_IS_ELECTRON || APP_IS_SEPERATE
20 | ? localStorage.getItem('SERVER_URL')
21 | : process.env.REACT_APP_SERVER_URL || '';
22 | const APP_API_VERSION_PATH = '/api/v1';
23 | const APP_NO_IMAGE_POSTER = noImagePoster;
24 | const APP_POSTER_QUALITY = 'w300';
25 | const APP_AVATAR_QUALITY = 'w400';
26 | const APP_LOGO_QUALITY = 'w500';
27 | const APP_THUMBNAIL_QUALITY = 'w500';
28 | const APP_BACKDROP_QUALITY = 'w1280';
29 | const APP_ERROR_IMAGE = errorImage;
30 | const APP_DISCONNECTED_IMAGE = disconnectedImage;
31 | const APP_OS = getOs();
32 |
33 | export {
34 | APP_IS_ELECTRON,
35 | APP_IS_SEPERATE,
36 | APP_LOGO_LIGHT,
37 | APP_LOGO_DARK,
38 | APP_NAME,
39 | APP_DESCRIPTION,
40 | APP_VERSION,
41 | APP_API_PATH,
42 | APP_API_VERSION_PATH,
43 | APP_NO_IMAGE_POSTER,
44 | APP_POSTER_QUALITY,
45 | APP_AVATAR_QUALITY,
46 | APP_LOGO_QUALITY,
47 | APP_THUMBNAIL_QUALITY,
48 | APP_BACKDROP_QUALITY,
49 | APP_ERROR_IMAGE,
50 | APP_DISCONNECTED_IMAGE,
51 | APP_OS,
52 | };
53 |
--------------------------------------------------------------------------------
/src/decleration.d.ts:
--------------------------------------------------------------------------------
1 | declare module '*.css';
2 | declare module '*.jpeg';
3 | declare module '*.jpg';
4 | declare module '*.png';
5 | declare module '*.svg';
6 | declare module 'react-color';
7 |
--------------------------------------------------------------------------------
/src/index.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom/client';
3 | import 'swiper/css';
4 | import 'swiper/css/effect-fade';
5 | import 'swiper/css/navigation';
6 | import 'swiper/css/pagination';
7 | import 'swiper/css/scrollbar';
8 |
9 | import App from './App';
10 | import './styles/globals.css';
11 |
12 | ReactDOM.createRoot(document.getElementById('root') as HTMLElement).render();
13 |
--------------------------------------------------------------------------------
/src/pages/Browse/styles.ts:
--------------------------------------------------------------------------------
1 | import Box, { BoxProps } from '@mui/material/Box';
2 | import { styled } from '@mui/material/styles';
3 |
4 | export const MainContainer = styled(Box)(({ theme }) => ({
5 | width: '100%',
6 | height: '100%',
7 | padding: '30px 60px',
8 | [theme.breakpoints.down('md')]: {
9 | padding: '20px 20px',
10 | },
11 | }));
12 |
--------------------------------------------------------------------------------
/src/pages/Callback/index.tsx:
--------------------------------------------------------------------------------
1 | import React, { useEffect } from 'react';
2 | import { useNavigate, useSearchParams } from 'react-router-dom';
3 |
4 | const CallbackPage = () => {
5 | const navigate = useNavigate();
6 | const [searchParams] = useSearchParams();
7 | const state = searchParams.get('state');
8 |
9 | useEffect(() => {
10 | if (state) {
11 | const stateConfig = JSON.parse(state);
12 | navigate({
13 | pathname: stateConfig.setup ? '/setup' : '/settings/providers',
14 | search: window.location.search,
15 | });
16 | } else {
17 | navigate('/');
18 | }
19 | }, [state]);
20 | return ;
21 | };
22 |
23 | export default CallbackPage;
24 |
--------------------------------------------------------------------------------
/src/pages/Disconnected/index.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | import DButton from '../../components/DButton';
4 | import { APP_DISCONNECTED_IMAGE } from '../../config';
5 | import {
6 | DetailsContainer,
7 | ErrorText,
8 | Image,
9 | ImageWrapper,
10 | MainContainer,
11 | NavigationContainer,
12 | } from './styles';
13 |
14 | const DisconnectedPage = () => {
15 | return (
16 |
17 |
18 |
19 |
20 |
21 | 404 - Cannot Connect to the Server/Backend
22 |
23 |
32 | }
33 | >
34 | REFRESH
35 |
36 |
37 |
38 |
39 | );
40 | };
41 |
42 | export default DisconnectedPage;
43 |
--------------------------------------------------------------------------------
/src/pages/Disconnected/styles.ts:
--------------------------------------------------------------------------------
1 | import Box, { BoxProps } from '@mui/material/Box';
2 | import Typography, { TypographyProps } from '@mui/material/Typography';
3 | import { styled } from '@mui/material/styles';
4 |
5 | export const MainContainer = styled(Box)(() => ({
6 | width: '100%',
7 | height: '100vh',
8 | display: 'flex',
9 | justifyContent: 'center',
10 | alignItems: 'center',
11 | flexDirection: 'column',
12 | padding: '20px',
13 | }));
14 |
15 | export const ImageWrapper = styled(Box)(() => ({
16 | padding: '20px',
17 | }));
18 |
19 | export const Image = styled('img')(() => ({
20 | width: '100%',
21 | maxWidth: '150px',
22 | }));
23 |
24 | export const DetailsContainer = styled(Box)(({ theme }) => ({
25 | backgroundColor: '#174453',
26 | borderRadius: theme.shape.borderRadius,
27 | padding: '15px 20px',
28 | maxWidth: '500px',
29 | width: '100%',
30 | }));
31 |
32 | export const NavigationContainer = styled(Box)(() => ({
33 | display: 'flex',
34 | justifyContent: 'center',
35 | paddingBottom: '5px',
36 | }));
37 |
38 | export const ErrorText = styled(Typography)(() => ({
39 | textAlign: 'center',
40 | background: '-webkit-linear-gradient(#14dca0, #03d7fc)',
41 | '-webkit-background-clip': 'text',
42 | '-webkit-text-fill-color': 'transparent',
43 | paddingBottom: '10px',
44 | }));
45 |
--------------------------------------------------------------------------------
/src/pages/Episode/index.tsx:
--------------------------------------------------------------------------------
1 | import DPlayer from '@desterlib/dplayer';
2 | import { Box, Toolbar } from '@mui/material';
3 | import React, { useEffect, useState } from 'react';
4 | import { useLocation, useNavigate, useParams } from 'react-router-dom';
5 |
6 | import DLoader from '../../components/DLoader';
7 | import WPlayer from '../../components/DWebPlayer';
8 | import { APP_API_PATH, APP_API_VERSION_PATH, APP_IS_ELECTRON } from '../../config';
9 | import useBreakpoint from '../../utilities/useBreakpoint';
10 |
11 | const EpisodePage = () => {
12 | const { seriesId, seasonNumber, episodeNumber }: any = useParams();
13 | const [videoData, setVideoData] = useState({});
14 | const [isLoaded, setIsLoaded] = useState(false);
15 | const navigate = useNavigate();
16 | const location: any = useLocation();
17 | const breakpoint = useBreakpoint();
18 |
19 | useEffect(() => {
20 | if (location.state) {
21 | const seriesData = location.state.data;
22 | const episodeData = location.state.item;
23 | const playlist: any[] = [];
24 | for (let i = 0; i < seriesData.seasons.length; i++) {
25 | const currSeason = seriesData.seasons[i];
26 | const episodes: any[] = [];
27 | for (let x = 0; x < currSeason.episodes.length; x++) {
28 | const currEpisode = currSeason.episodes[x];
29 | episodes.push({
30 | episode: currEpisode.episode_number,
31 | title: currEpisode.name,
32 | description: currEpisode.description,
33 | thumbnail_path: currEpisode.thumbnail_path,
34 | src: [
35 | `${APP_API_PATH}${APP_API_VERSION_PATH}/stream/${seriesData.rclone_index}/${currEpisode.path}`,
36 | ],
37 | });
38 | }
39 | playlist.push({
40 | season: currSeason.season_number,
41 | title: currSeason.name,
42 | description: currSeason.description,
43 | episodes: episodes,
44 | });
45 | }
46 | console.log(playlist);
47 | const videoData = {
48 | id: 1,
49 | title: seriesData.title,
50 | subTitle: '',
51 | url: `${APP_API_PATH}${APP_API_VERSION_PATH}/stream/${seriesData.rclone_index}/${episodeData.path}`,
52 | playlist: playlist,
53 | };
54 | setVideoData(videoData);
55 | setIsLoaded(true);
56 | } else {
57 | navigate(`/series/${seriesId}/season/${seasonNumber}`);
58 | }
59 | }, [seriesId, seasonNumber, episodeNumber]);
60 |
61 | return isLoaded ? (
62 |
63 |
64 |
65 | {APP_IS_ELECTRON ? (
66 |
72 | ) : (
73 |
74 | )}
75 |
76 |
77 | ) : (
78 |
79 | );
80 | };
81 |
82 | export default EpisodePage;
83 |
--------------------------------------------------------------------------------
/src/pages/Home/index.tsx:
--------------------------------------------------------------------------------
1 | import React, { Fragment, useEffect, useState } from 'react';
2 | import { useNavigate } from 'react-router-dom';
3 |
4 | import DCarousel from '../../components/DCarousel';
5 | import { Helmet } from '../../components/DHelmet';
6 | import DLoader from '../../components/DLoader';
7 | import DSlider from '../../components/DSlider';
8 | import { APP_DESCRIPTION, APP_NAME } from '../../config';
9 | import { get } from '../../utilities/requests';
10 | import { MainContainer, MainWrapper } from './styles';
11 |
12 | // import useNetworkStatus from '../utilities/useNetworkStatus';
13 |
14 | const HomePage = () => {
15 | const [isLoaded, setIsLoaded] = useState(false);
16 | const [data, setData] = useState({});
17 | const [requestInfo, setRequestInfo] = useState({});
18 | const navigate = useNavigate();
19 |
20 | useEffect(() => {
21 | get('/home', setData, setRequestInfo, setIsLoaded);
22 | }, []);
23 |
24 | if (requestInfo.code === 428) {
25 | navigate('/setup');
26 | }
27 |
28 | return isLoaded ? (
29 |
30 |
31 | {APP_NAME}
32 |
33 |
34 |
35 |
36 |
37 |
43 |
49 |
55 |
61 |
67 |
73 |
79 |
80 |
81 |
82 | ) : (
83 |
84 | );
85 | };
86 |
87 | export default HomePage;
88 |
--------------------------------------------------------------------------------
/src/pages/Home/styles.ts:
--------------------------------------------------------------------------------
1 | import Box, { BoxProps } from '@mui/material/Box';
2 | import { styled } from '@mui/material/styles';
3 |
4 | export const MainContainer = styled(Box)(() => ({}));
5 |
6 | export const MainWrapper = styled(Box)(({ theme }) => ({
7 | marginTop: 'calc(60px + 20px)',
8 | marginBottom: '100px',
9 | [theme.breakpoints.down('md')]: {
10 | marginTop: '60px',
11 | },
12 | }));
13 |
--------------------------------------------------------------------------------
/src/pages/Movie/styles.ts:
--------------------------------------------------------------------------------
1 | import Box, { BoxProps } from '@mui/material/Box';
2 | import Chip, { ChipProps } from '@mui/material/Chip';
3 | import { alpha, styled } from '@mui/material/styles';
4 |
5 | export const StyledChipInfo = styled(Chip)(({ theme }) => ({
6 | padding: '0px 5px',
7 | margin: theme.spacing(0.5),
8 | background: `linear-gradient(45deg, ${theme.palette.error.dark} 0%, ${theme.palette.error.light} 100%)`,
9 | color: '#ffffff',
10 | }));
11 |
12 | export const StyledChipGenre = styled(Chip)(({ theme }) => ({
13 | padding: '0px 5px',
14 | margin: theme.spacing(0.5),
15 | background: `linear-gradient(45deg, ${theme.palette.info.dark} 0%, ${theme.palette.info.light} 100%)`,
16 | color: '#ffffff',
17 | }));
18 |
19 | export const ItemBackground = styled(Box)(({ theme }) => ({
20 | width: '100%',
21 | position: 'relative',
22 | [theme.breakpoints.up('md')]: {
23 | paddingBottom: '40.25%',
24 | width: '100% !important',
25 | },
26 | [theme.breakpoints.down('md')]: {
27 | paddingBottom: '150%',
28 | marginTop: '48px',
29 | },
30 | }));
31 |
32 | export const HeaderImage = styled('img')(() => ({
33 | width: '100%',
34 | height: '100%',
35 | objectFit: 'cover',
36 | zIndex: '5',
37 | position: 'absolute',
38 | top: 0,
39 | left: 0,
40 | bottom: 0,
41 | right: 0,
42 | }));
43 |
44 | export const PosterImage = styled('img')(({ theme }) => ({
45 | width: '100%',
46 | borderRadius: theme.shape.borderRadius,
47 | maxWidth: '200px',
48 | }));
49 |
50 | export const LinearGradient = styled(Box)(({ theme }) => ({
51 | position: 'absolute',
52 | top: 0,
53 | left: 0,
54 | bottom: 0,
55 | right: 0,
56 | width: '100%',
57 | height: '100%',
58 | zIndex: '10',
59 | [theme.breakpoints.up('md')]: {
60 | background: `linear-gradient( 45deg, ${theme.palette.background.default} 20%, ${alpha(
61 | theme.palette.background.paper,
62 | 0.5,
63 | )} 70%, rgba(0, 0, 0, 0) 100% )`,
64 | },
65 | [theme.breakpoints.down('md')]: {
66 | background: `linear-gradient( 45deg, ${theme.palette.background.default} 20%, ${alpha(
67 | theme.palette.background.paper,
68 | 0.5,
69 | )} 70%, rgba(0, 0, 0, 0) 100% ), linear-gradient( 325deg, ${
70 | theme.palette.background.paper
71 | } 20%, ${alpha(theme.palette.background.paper, 0.5)} 70%, rgba(0, 0, 0, 0) 100% )`,
72 | },
73 | }));
74 |
--------------------------------------------------------------------------------
/src/pages/NotFound/index.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Link } from 'react-router-dom';
3 |
4 | import DButton from '../../components/DButton';
5 | import { APP_ERROR_IMAGE } from '../../config';
6 | import {
7 | DetailsContainer,
8 | ErrorText,
9 | Image,
10 | ImageWrapper,
11 | MainContainer,
12 | NavigationContainer,
13 | } from './styles';
14 |
15 | const NotFoundPage = () => {
16 | return (
17 |
18 |
19 |
20 |
21 |
22 | 404 - You Lost ?
23 |
24 |
25 |
33 | }
34 | >
35 | Go Home
36 |
37 |
38 |
47 | }
48 | >
49 | Search
50 |
51 |
52 |
53 |
54 | );
55 | };
56 |
57 | export default NotFoundPage;
58 |
--------------------------------------------------------------------------------
/src/pages/NotFound/styles.ts:
--------------------------------------------------------------------------------
1 | import Box, { BoxProps } from '@mui/material/Box';
2 | import Typography, { TypographyProps } from '@mui/material/Typography';
3 | import { styled } from '@mui/material/styles';
4 |
5 | export const MainContainer = styled(Box)(() => ({
6 | width: '100%',
7 | height: '100vh',
8 | display: 'flex',
9 | justifyContent: 'center',
10 | alignItems: 'center',
11 | flexDirection: 'column',
12 | padding: '20px',
13 | }));
14 |
15 | export const ImageWrapper = styled(Box)(() => ({}));
16 |
17 | export const Image = styled('img')(() => ({
18 | width: '100%',
19 | maxWidth: '500px',
20 | }));
21 |
22 | export const DetailsContainer = styled(Box)(({ theme }) => ({
23 | backgroundColor: '#174453',
24 | borderRadius: theme.shape.borderRadius,
25 | padding: '15px 20px',
26 | maxWidth: '500px',
27 | width: '100%',
28 | }));
29 |
30 | export const NavigationContainer = styled(Box)(() => ({
31 | display: 'flex',
32 | justifyContent: 'space-between',
33 | paddingBottom: '5px',
34 | }));
35 |
36 | export const ErrorText = styled(Typography)(() => ({
37 | textAlign: 'center',
38 | background: '-webkit-linear-gradient(#14dca0, #03d7fc)',
39 | '-webkit-background-clip': 'text',
40 | '-webkit-text-fill-color': 'transparent',
41 | paddingBottom: '10px',
42 | }));
43 |
--------------------------------------------------------------------------------
/src/pages/Season/styles.ts:
--------------------------------------------------------------------------------
1 | const Hello = 'Hello';
2 |
3 | export default Hello;
4 |
--------------------------------------------------------------------------------
/src/pages/Series/styles.ts:
--------------------------------------------------------------------------------
1 | import Box, { BoxProps } from '@mui/material/Box';
2 | import Chip, { ChipProps } from '@mui/material/Chip';
3 | import { alpha, styled } from '@mui/material/styles';
4 |
5 | export const StyledChipInfo = styled(Chip)(({ theme }) => ({
6 | padding: '0px 5px',
7 | margin: theme.spacing(0.5),
8 | background: `linear-gradient(45deg, ${theme.palette.error.dark} 0%, ${theme.palette.error.light} 100%)`,
9 | color: '#ffffff',
10 | }));
11 |
12 | export const StyledChipGenre = styled(Chip)(({ theme }) => ({
13 | padding: '0px 5px',
14 | margin: theme.spacing(0.5),
15 | background: `linear-gradient(45deg, ${theme.palette.info.dark} 0%, ${theme.palette.info.light} 100%)`,
16 | color: '#ffffff',
17 | }));
18 |
19 | export const ItemBackground = styled(Box)(({ theme }) => ({
20 | width: '100%',
21 | position: 'relative',
22 | [theme.breakpoints.up('md')]: {
23 | paddingBottom: '40.25%',
24 | width: '100% !important',
25 | },
26 | [theme.breakpoints.down('md')]: {
27 | paddingBottom: '150%',
28 | marginTop: '48px',
29 | },
30 | }));
31 |
32 | export const HeaderImage = styled('img')(() => ({
33 | width: '100%',
34 | height: '100%',
35 | objectFit: 'cover',
36 | zIndex: '5',
37 | position: 'absolute',
38 | top: 0,
39 | left: 0,
40 | bottom: 0,
41 | right: 0,
42 | }));
43 |
44 | export const PosterImage = styled('img')(({ theme }) => ({
45 | width: '100%',
46 | borderRadius: theme.shape.borderRadius,
47 | maxWidth: '200px',
48 | }));
49 |
50 | export const LinearGradient = styled(Box)(({ theme }) => ({
51 | position: 'absolute',
52 | top: 0,
53 | left: 0,
54 | bottom: 0,
55 | right: 0,
56 | width: '100%',
57 | height: '100%',
58 | zIndex: '10',
59 | [theme.breakpoints.up('md')]: {
60 | background: `linear-gradient( 45deg, ${theme.palette.background.default} 20%, ${alpha(
61 | theme.palette.background.paper,
62 | 0.5,
63 | )} 70%, rgba(0, 0, 0, 0) 100% )`,
64 | },
65 | [theme.breakpoints.down('md')]: {
66 | background: `linear-gradient( 45deg, ${theme.palette.background.default} 20%, ${alpha(
67 | theme.palette.background.paper,
68 | 0.5,
69 | )} 70%, rgba(0, 0, 0, 0) 100% ), linear-gradient( 325deg, ${
70 | theme.palette.background.paper
71 | } 20%, ${alpha(theme.palette.background.paper, 0.5)} 70%, rgba(0, 0, 0, 0) 100% )`,
72 | },
73 | }));
74 |
--------------------------------------------------------------------------------
/src/pages/Settings/assets/logo-full-dark.svg:
--------------------------------------------------------------------------------
1 |
49 |
--------------------------------------------------------------------------------
/src/pages/Settings/pages/App/index.tsx:
--------------------------------------------------------------------------------
1 | import { Box, useTheme } from '@mui/material';
2 | import React, { useState } from 'react';
3 |
4 | import DTextField from '../../../../components/DTextField';
5 |
6 | const AppPage = (props: any) => {
7 | const textFiledStyles = {
8 | marginBottom: '20px',
9 | };
10 | const [refresh, setRefresh] = useState(0);
11 |
12 | const { config, updateConfig } = props;
13 |
14 | const handleChangeName = (event: any) => {
15 | var newConfig = config;
16 | newConfig['name'] = event.target.value;
17 | updateConfig(newConfig);
18 | setRefresh(refresh + 1);
19 | };
20 |
21 | const handleChangeTitle = (event: any) => {
22 | var newConfig = config;
23 | newConfig['title'] = event.target.value;
24 | updateConfig(newConfig);
25 | setRefresh(refresh + 1);
26 | };
27 |
28 | const handleChangeDescription = (event: any) => {
29 | var newConfig = config;
30 | newConfig['description'] = event.target.value;
31 | updateConfig(newConfig);
32 | setRefresh(refresh + 1);
33 | };
34 |
35 | const handleChangeDomain = (event: any) => {
36 | var newConfig = config;
37 | newConfig['domain'] = event.target.value;
38 | updateConfig(newConfig);
39 | setRefresh(refresh + 1);
40 | };
41 |
42 | const handleChangeSecretKey = (event: any) => {
43 | var newConfig = config;
44 | newConfig['secret_key'] = event.target.value;
45 | updateConfig(newConfig);
46 | setRefresh(refresh + 1);
47 | };
48 |
49 | const theme = useTheme();
50 |
51 | const boxContainer = {
52 | padding: '20px',
53 | borderRadius: theme.shape.borderRadius,
54 | maxWidth: '1000px',
55 | margin: 'auto auto',
56 | marginTop: '40px',
57 | };
58 |
59 | return (
60 |
61 |
70 |
79 |
88 |
97 |
106 |
107 | );
108 | };
109 |
110 | export default AppPage;
111 |
--------------------------------------------------------------------------------
/src/pages/Settings/pages/Auth0/index.tsx:
--------------------------------------------------------------------------------
1 | import { Box, useTheme } from '@mui/material';
2 | import React, { useState } from 'react';
3 |
4 | import DTextField from '../../../../components/DTextField';
5 |
6 | const Auth0Page = (props: any) => {
7 | const textFiledStyles = {
8 | marginBottom: '20px',
9 | };
10 | const [refresh, setRefresh] = useState(0);
11 |
12 | const { config, updateConfig } = props;
13 |
14 | const handleChangeDomain = (event: any) => {
15 | var newConfig = config;
16 | newConfig['domain'] = event.target.value;
17 | updateConfig(newConfig);
18 | setRefresh(refresh + 1);
19 | };
20 |
21 | const handleChangeClientId = (event: any) => {
22 | var newConfig = config;
23 | newConfig['client_id'] = event.target.value;
24 | updateConfig(newConfig);
25 | setRefresh(refresh + 1);
26 | };
27 |
28 | const handleChangeClientSecret = (event: any) => {
29 | var newConfig = config;
30 | newConfig['client_secret'] = event.target.value;
31 | updateConfig(newConfig);
32 | setRefresh(refresh + 1);
33 | };
34 |
35 | const theme = useTheme();
36 |
37 | const boxContainer = {
38 | padding: '20px',
39 | borderRadius: theme.shape.borderRadius,
40 | maxWidth: '1000px',
41 | margin: 'auto auto',
42 | marginTop: '40px',
43 | };
44 |
45 | return (
46 |
47 |
56 |
65 |
74 |
75 | );
76 | };
77 |
78 | export default Auth0Page;
79 |
--------------------------------------------------------------------------------
/src/pages/Settings/pages/Categories/index.tsx:
--------------------------------------------------------------------------------
1 | import AddRoundedIcon from '@mui/icons-material/AddRounded';
2 | import CancelRoundedIcon from '@mui/icons-material/CancelRounded';
3 | import DeleteRoundedIcon from '@mui/icons-material/DeleteRounded';
4 | import { Button } from '@mui/material';
5 | import { Box } from '@mui/system';
6 | import React, { useState } from 'react';
7 |
8 | import guid from '../../../../utilities/guid';
9 | import Category from './Category';
10 |
11 | const CategoriesPage = (props: any) => {
12 | const { config, updateConfig } = props;
13 |
14 | var tempCategories: any = [];
15 | for (let i = 0; i < config.length; i++) {
16 | const item = config[i];
17 | tempCategories.push(
18 | ,
25 | );
26 | }
27 |
28 | var index = tempCategories.length;
29 |
30 | const [categories, setCategories] = useState(tempCategories);
31 | const [refresh, setRefresh] = useState(0);
32 |
33 | const handleAddCategory = () => {
34 | setCategories(
35 | categories.concat(
36 | ,
37 | ),
38 | );
39 | index += 1;
40 | var newConfig = config;
41 | newConfig.push({});
42 | updateConfig(newConfig);
43 | setRefresh(refresh + 1);
44 | };
45 |
46 | const handleRemoveCategory = () => {
47 | setCategories(categories.slice(0, -1));
48 | index -= 1;
49 | var newConfig = config;
50 | newConfig.pop();
51 | updateConfig(newConfig);
52 | setRefresh(refresh + 1);
53 | };
54 |
55 | const handleClearAll = () => {
56 | setCategories([]);
57 | updateConfig([]);
58 | setRefresh(refresh + 1);
59 | };
60 |
61 | return (
62 |
63 |
64 | }
68 | onClick={handleClearAll}
69 | >
70 | Clear All Categories
71 |
72 |
73 | {categories}
74 |
75 | }
77 | sx={{ marginRight: '20px', marginBottom: '20px' }}
78 | variant='contained'
79 | onClick={handleAddCategory}
80 | >
81 | Add Category
82 |
83 | }
86 | sx={{ marginRight: '20px', marginBottom: '20px' }}
87 | variant='outlined'
88 | onClick={handleRemoveCategory}
89 | >
90 | Remove Category
91 |
92 |
93 |
94 | );
95 | };
96 |
97 | export default CategoriesPage;
98 |
--------------------------------------------------------------------------------
/src/pages/Settings/pages/Dev/index.tsx:
--------------------------------------------------------------------------------
1 | import ConstructionIcon from '@mui/icons-material/Construction';
2 | import Box from '@mui/material/Box';
3 | import Paper from '@mui/material/Paper';
4 | import React, { useEffect, useState } from 'react';
5 |
6 | import DButton from '../../../../components/DButton';
7 | import { APP_API_PATH, APP_API_VERSION_PATH } from '../../../../config';
8 |
9 | const DevPage = () => {
10 | const [refresh, setRefresh] = useState(0);
11 | const [data, setData] = useState('');
12 |
13 | useEffect(() => {
14 | var xhr = new XMLHttpRequest();
15 | xhr.open('GET', `${APP_API_PATH}/api/v1/logs/live`);
16 | xhr.send();
17 | const getData = async () => {
18 | setData(xhr.responseText);
19 | };
20 |
21 | setInterval(() => {
22 | getData();
23 | }, 1000);
24 | }, []);
25 |
26 | const handleRebuild = () => {
27 | fetch(`${APP_API_PATH}${APP_API_VERSION_PATH}/rebuild`);
28 | setRefresh(refresh + 1);
29 | };
30 | return (
31 |
32 |
33 |
42 |
43 |
44 |
45 | }
49 | onClick={handleRebuild}
50 | >
51 | Rebuild Metadata
52 |
53 |
54 | );
55 | };
56 |
57 | export default DevPage;
58 |
--------------------------------------------------------------------------------
/src/pages/Settings/pages/Interface/index.tsx:
--------------------------------------------------------------------------------
1 | import { Box, Typography } from '@mui/material';
2 | import React from 'react';
3 |
4 | import ColorSelector from '../../../../components/DColorSelector';
5 | import lightTheme from '../../../../theme/lightTheme';
6 |
7 | const lightThemeUIConfig = {
8 | title: 'Light Theme',
9 | primary: {
10 | title: 'Primary Color',
11 | colors: [
12 | {
13 | id: 'primaryMain',
14 | title: 'Primary Main Color',
15 | value: lightTheme.palette.primary.main,
16 | },
17 | {
18 | id: 'primaryLight',
19 | title: 'Primary Light Color',
20 | value: lightTheme.palette.primary.light,
21 | },
22 | {
23 | id: 'primaryDark',
24 | title: 'Primary Dark Color',
25 | value: lightTheme.palette.primary.dark,
26 | },
27 | ],
28 | },
29 | secondary: {
30 | title: 'Secondary Color',
31 | colors: [
32 | {
33 | id: 'secondaryMain',
34 | title: 'Secondary Main Color',
35 | value: lightTheme.palette.secondary.main,
36 | },
37 | {
38 | id: 'secondaryLight',
39 | title: 'Secondary Light Color',
40 | value: lightTheme.palette.secondary.light,
41 | },
42 | {
43 | id: 'secondaryDark',
44 | title: 'Secondary Dark Color',
45 | value: lightTheme.palette.secondary.dark,
46 | },
47 | ],
48 | },
49 | };
50 |
51 | const InterfacePage = (props: any) => {
52 | const { config, updateConfig } = props;
53 | console.log(config, updateConfig);
54 |
55 | return (
56 |
57 | {lightThemeUIConfig && lightThemeUIConfig.primary && lightThemeUIConfig.primary.colors && (
58 |
59 | {lightThemeUIConfig.primary.title}
60 | {lightThemeUIConfig.primary.colors.map((color: any) => (
61 |
66 | ))}
67 |
68 | )}
69 | {lightThemeUIConfig &&
70 | lightThemeUIConfig.secondary &&
71 | lightThemeUIConfig.secondary.colors && (
72 |
73 |
74 | {lightThemeUIConfig.secondary.title}
75 |
76 | {lightThemeUIConfig.secondary.colors.map((color: any) => (
77 |
82 | ))}
83 |
84 | )}
85 |
86 | );
87 | };
88 |
89 | export default InterfacePage;
90 |
--------------------------------------------------------------------------------
/src/pages/Settings/pages/Other/index.tsx:
--------------------------------------------------------------------------------
1 | import {
2 | Box,
3 | FormControl,
4 | FormControlLabel,
5 | FormGroup,
6 | Switch,
7 | TextField,
8 | useTheme,
9 | } from '@mui/material';
10 | import React, { useState } from 'react';
11 |
12 | const OtherPage = (props: any) => {
13 | const [refresh, setRefresh] = useState(0);
14 |
15 | const { tmdb, subtitles, build, updateTmdb, updateSubtitles, updateBuild } = props;
16 |
17 | const handleChangeTmdbKey = (event: any) => {
18 | var newTmdb = tmdb;
19 | newTmdb['api_key'] = event.target.value;
20 | updateTmdb(newTmdb);
21 | setRefresh(refresh + 1);
22 | };
23 |
24 | const handleChangeBuildCron = (event: any) => {
25 | var newBuild = build;
26 | newBuild['cron'] = event.target.value;
27 | updateBuild(newBuild);
28 | setRefresh(refresh + 1);
29 | };
30 |
31 | const handleChangeOpenSubtitlesKey = (event: any) => {
32 | var newSubtitles = subtitles;
33 | newSubtitles['api_key'] = event.target.value;
34 | updateSubtitles(newSubtitles);
35 | setRefresh(refresh + 1);
36 | };
37 |
38 | const handleChangeLocalSubtitles = () => {
39 | var newSubtitles = subtitles;
40 | newSubtitles['local'] = !newSubtitles.local;
41 | updateSubtitles(newSubtitles);
42 | setRefresh(refresh + 1);
43 | };
44 |
45 | const textFiledStyles = {
46 | marginBottom: '20px',
47 | };
48 |
49 | const theme = useTheme();
50 |
51 | const boxContainer = {
52 | padding: '20px',
53 | borderRadius: theme.shape.borderRadius,
54 | maxWidth: '1000px',
55 | margin: 'auto auto',
56 | marginTop: '40px',
57 | };
58 |
59 | return (
60 |
61 |
70 |
79 |
88 |
89 |
90 |
97 | }
98 | label='Local Subtitles'
99 | labelPlacement='start'
100 | />
101 |
102 |
103 |
104 | );
105 | };
106 |
107 | export default OtherPage;
108 |
--------------------------------------------------------------------------------
/src/pages/Settings/pages/Providers/GDriveTokenGeneratorPage.tsx:
--------------------------------------------------------------------------------
1 | import { Box, Button, Typography, useTheme } from '@mui/material';
2 | import TextField from '@mui/material/TextField';
3 | import React, { useState } from 'react';
4 |
5 | import guid from '../../../../utilities/guid';
6 |
7 | const GDriveTokenGenerator = (props: any) => {
8 | const { config, updateConfig } = props;
9 | const theme = useTheme();
10 |
11 | const [refresh, setRefresh] = useState(0);
12 |
13 | const objToFormEncoded = (object: object) => {
14 | return Object.entries(object)
15 | .map(([key, value]) => `${encodeURIComponent(key)}=${encodeURIComponent(value)}`)
16 | .join('&');
17 | };
18 |
19 | const getAuthCode = async (event: any) => {
20 | event.preventDefault();
21 | const state = {
22 | uid: guid(),
23 | provider: 'gdrive',
24 | setup: window.location.pathname.includes('setup'),
25 | };
26 | const query = objToFormEncoded({
27 | response_type: 'code',
28 | redirect_uri: `${window.location.origin}/callback`,
29 | client_id: config.gdrive.client_id,
30 | scope: 'https://www.googleapis.com/auth/drive',
31 | access_type: 'offline',
32 | prompt: 'consent',
33 | state: JSON.stringify(state),
34 | });
35 | sessionStorage.setItem(state.uid, JSON.stringify(config));
36 | window.location.href = `https://accounts.google.com/o/oauth2/v2/auth?${query}`;
37 | };
38 |
39 | const handleChangeClientId = (event: any) => {
40 | const tempConfig = config;
41 | tempConfig.gdrive.client_id = event.target.value;
42 | updateConfig(tempConfig);
43 | setRefresh(refresh + 1);
44 | };
45 |
46 | const handleChangeClientSecret = (event: any) => {
47 | const tempConfig = config;
48 | console.log(tempConfig);
49 | tempConfig.gdrive.client_secret = event.target.value;
50 | updateConfig(tempConfig);
51 | setRefresh(refresh + 1);
52 | };
53 |
54 | const boxContainer = {
55 | padding: '20px',
56 | borderRadius: theme.shape.borderRadius,
57 | maxWidth: '1000px',
58 | margin: 'auto auto',
59 | marginTop: '40px',
60 | };
61 |
62 | const textFieldStyles = {
63 | marginBottom: '20px',
64 | };
65 |
66 | return (
67 |
68 |
69 | Generate Google Drive Tokens
70 |
71 |
100 |
101 | );
102 | };
103 |
104 | export default GDriveTokenGenerator;
105 |
--------------------------------------------------------------------------------
/src/pages/Settings/pages/Providers/OneDriveTokenGeneratorPage.tsx:
--------------------------------------------------------------------------------
1 | import { Box, Button, Typography, useTheme } from '@mui/material';
2 | import TextField from '@mui/material/TextField';
3 | import React, { useState } from 'react';
4 |
5 | import guid from '../../../../utilities/guid';
6 |
7 | const OneDriveTokenGenerator = (props: any) => {
8 | const { config } = props;
9 | const theme = useTheme();
10 |
11 | const [clientId, setClientId] = useState(config.onedrive.client_id || '');
12 |
13 | const objToFormEncoded = (object: object) => {
14 | return Object.entries(object)
15 | .map(([key, value]) => `${encodeURIComponent(key)}=${encodeURIComponent(value)}`)
16 | .join('&');
17 | };
18 |
19 | const getAuthCode = async (event: any) => {
20 | event.preventDefault();
21 | const state = {
22 | uid: guid(),
23 | provider: 'onedrive',
24 | setup: window.location.pathname.includes('setup'),
25 | };
26 | const query = objToFormEncoded({
27 | response_type: 'code',
28 | redirect_uri: `${window.location.origin}/callback`,
29 | client_id: clientId,
30 | scope: 'Files.Read Files.ReadWrite Files.Read.All Files.ReadWrite.All Sites.Read.All offline_access',
31 | code_challenge: 'ehti8jnG5bgwl38yzORUxLr3c9FJdFv37ScKK6SJDV8',
32 | code_challenge_method: 'S256',
33 | access_type: 'offline',
34 | prompt: 'consent',
35 | state: JSON.stringify(state),
36 | });
37 | sessionStorage.setItem(state.uid, JSON.stringify(config));
38 | window.location.href = `https://login.microsoftonline.com/common/oauth2/v2.0/authorize?${query}`;
39 | };
40 |
41 | const handleChangeClientId = (event: any) => {
42 | setClientId(event.target.value);
43 | };
44 |
45 | const boxContainer = {
46 | padding: '20px',
47 | borderRadius: theme.shape.borderRadius,
48 | maxWidth: '1000px',
49 | margin: 'auto auto',
50 | marginTop: '40px',
51 | };
52 |
53 | const textFieldStyles = {
54 | marginBottom: '20px',
55 | };
56 |
57 | return (
58 |
59 |
60 | Generate OneDrive Tokens
61 |
62 |
83 |
84 | );
85 | };
86 |
87 | export default OneDriveTokenGenerator;
88 |
--------------------------------------------------------------------------------
/src/pages/Settings/styles/globals.css:
--------------------------------------------------------------------------------
1 | .json-string {
2 | color: rgb(255, 167, 60);
3 | }
4 | .json-number {
5 | color: rgb(60, 161, 255);
6 | }
7 | .json-boolean {
8 | color: rgb(138, 138, 255);
9 | }
10 | .json-null {
11 | color: rgb(255, 0, 0);
12 | }
13 | .json-key {
14 | color: rgb(20, 220, 160);
15 | }
16 | .json-quotes {
17 | color: rgb(255, 167, 60);
18 | }
19 |
20 | * {
21 | scrollbar-width: auto;
22 | scrollbar-color: #14dca0 #06131c;
23 | }
24 |
25 | *::-webkit-scrollbar {
26 | width: 11px;
27 | }
28 |
29 | *::-webkit-scrollbar-track {
30 | background: #06131c;
31 | }
32 |
33 | *::-webkit-scrollbar-thumb {
34 | background-color: #14dca0;
35 | border-radius: 8px;
36 | border: 1.5px outset #06131c;
37 | }
38 |
--------------------------------------------------------------------------------
/src/pages/Settings/utilities/tokens.ts:
--------------------------------------------------------------------------------
1 | const objToFormEncoded = (object: object) => {
2 | return Object.entries(object)
3 | .map(([key, value]) => `${encodeURIComponent(key)}=${encodeURIComponent(value)}`)
4 | .join('&');
5 | };
6 |
7 | const GDriveToken = async (
8 | handleChangeAccessToken: any,
9 | handleChangeRefreshToken: any,
10 | client_id: string,
11 | client_secret: string,
12 | authCode: string,
13 | ) => {
14 | const body = {
15 | grant_type: 'authorization_code',
16 | redirect_uri: `${window.location.origin}/callback`,
17 | code: authCode,
18 | };
19 | const requestTokens = async (body: any) => {
20 | console.log(`${client_id}:${client_secret}`);
21 | const response = await fetch('https://accounts.google.com/o/oauth2/token', {
22 | method: 'POST',
23 | headers: {
24 | Authorization: `Basic ${btoa(`${client_id}:${client_secret}`)}`,
25 | 'Content-Type': 'application/x-www-form-urlencoded',
26 | },
27 | body: objToFormEncoded(body),
28 | });
29 | if (!response.ok) {
30 | const responseText = await response.text();
31 | console.error(responseText);
32 | }
33 | try {
34 | const { access_token, refresh_token } = await response.json();
35 | handleChangeAccessToken({ target: { value: access_token } }, 'gdrive');
36 | handleChangeRefreshToken({ target: { value: refresh_token } }, 'gdrive');
37 | } catch (err) {
38 | const responseText = await response.text();
39 | console.error(responseText);
40 | }
41 | };
42 | await requestTokens(body);
43 | };
44 |
45 | const OneDriveToken = async (
46 | handleChangeAccessToken: any,
47 | handleChangeRefreshToken: any,
48 | client_id: string,
49 | authCode: string,
50 | ) => {
51 | const body = {
52 | grant_type: 'authorization_code',
53 | redirect_uri: `${window.location.origin}/callback`,
54 | code: authCode,
55 | client_id: client_id,
56 | code_verifier:
57 | 'E23_dcz27YmnY3Y9c~RZ.TYMXS_PKR2WtwLX-mE8-3TAD04.ONStig4._jha8FzTulLlau9fT_0qfhYpA2uCaqEo9MMsPd9NZ5Uler1fjNUoT2vwCbPkL--Dt5KJsAQE',
58 | };
59 | const requestTokens = async (body: any) => {
60 | const response = await fetch('https://login.microsoftonline.com/common/oauth2/v2.0/token', {
61 | method: 'POST',
62 | headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
63 | body: objToFormEncoded(body),
64 | });
65 | if (!response.ok) {
66 | const responseText = await response.text();
67 | console.error(responseText);
68 | }
69 | try {
70 | const { access_token, refresh_token } = await response.json();
71 | handleChangeAccessToken({ target: { value: access_token } }, 'onedrive');
72 | handleChangeRefreshToken({ target: { value: refresh_token } }, 'onedrive');
73 | } catch (err) {
74 | const responseText = await response.text();
75 | console.error(responseText);
76 | }
77 | };
78 | await requestTokens(body);
79 | };
80 |
81 | export { GDriveToken, OneDriveToken };
82 |
--------------------------------------------------------------------------------
/src/styles/DCarousel.module.css:
--------------------------------------------------------------------------------
1 | .carousel {
2 | --swiper-navigation-size: 20px;
3 | margin-bottom: 20px;
4 | }
5 |
6 | .carouselSlideWrapper .carouselItem {
7 | display: flex;
8 | position: relative;
9 | min-height: 500px;
10 | background-color: #02161f;
11 | }
12 |
13 | .carouselSlideWrapper .carouselItem .itemInfo {
14 | width: 100%;
15 | background: #02161f;
16 | background: linear-gradient(
17 | 90deg,
18 | rgba(2, 22, 31, 1) 6%,
19 | rgba(1, 9, 12, 0.5032606792717087) 70%,
20 | rgba(0, 0, 0, 0) 100%
21 | );
22 | padding: 200px;
23 | display: flex;
24 | flex-direction: column;
25 | justify-content: center;
26 | align-items: left;
27 | z-index: 10;
28 | }
29 |
30 | .carouselSlideWrapper .carouselItem .itemInfo .logo {
31 | padding: 10px 0px;
32 | max-width: 300px;
33 | max-height: 100px;
34 | width: 300px;
35 | height: 100px;
36 | position: relative;
37 | }
38 |
39 | .carouselSlideWrapper .carouselItem .itemInfo .info {
40 | display: flex;
41 | flex-direction: column;
42 | justify-content: center;
43 | align-items: center;
44 | position: absolute;
45 | bottom: 40px;
46 | }
47 |
48 | .carouselSlideWrapper .carouselItem .itemInfo .info .buttons {
49 | margin-top: 30px;
50 | }
51 |
52 | .carouselSlideWrapper .carouselItem .itemInfo .info .title {
53 | padding: 10px;
54 | color: #00ffb3;
55 | }
56 |
57 | .carouselSlideWrapper .carouselItem .itemInfo .info .mainInfo {
58 | display: flex;
59 | justify-content: center;
60 | align-items: center;
61 | }
62 |
63 | @media only screen and (max-width: 991px) {
64 | .carouselSlideWrapper .carouselItem .itemInfo {
65 | width: 100%;
66 | background: rgb(2, 22, 31);
67 | background: linear-gradient(
68 | 0deg,
69 | rgba(2, 22, 31, 1) 40%,
70 | rgba(2, 16, 22, 0.7721682422969187) 68%,
71 | rgba(1, 9, 12, 0.5032606792717087) 80%,
72 | rgba(0, 0, 0, 0) 100%
73 | );
74 | padding: 20px;
75 | align-items: center;
76 | }
77 |
78 | .carouselSlideWrapper .carouselItem .itemInfo .info {
79 | position: absolute;
80 | bottom: 40px;
81 | }
82 |
83 | .carouselSlideWrapper .carouselItem .itemBg {
84 | background-size: 100%;
85 | background-repeat: no-repeat;
86 | }
87 |
88 | .carouselSlideWrapper .carouselItem .itemInfo .logo {
89 | width: 140px;
90 | padding-bottom: 100px;
91 | }
92 |
93 | .carouselSlideWrapper .carouselItem .itemInfo .info .title {
94 | visibility: visible;
95 | }
96 |
97 | /* .carousel > div:nth-child(1),
98 | .carousel > div:nth-child(2) {
99 | display: none;
100 | } */
101 | }
102 |
--------------------------------------------------------------------------------
/src/styles/globals.css:
--------------------------------------------------------------------------------
1 | @import url('https://fonts.googleapis.com/css2?family=Rubik:wght@300;400;500;600;700;800;900&display=swap');
2 |
3 | body {
4 | overflow-x: hidden;
5 | }
6 |
7 | .swiper-button-next,
8 | .swiper-button-prev {
9 | background-color: rgba(41, 41, 41, 0.5) !important;
10 | backdrop-filter: blur(10px);
11 | color: #00ffb3 !important;
12 | border-radius: 50%;
13 | width: 50px !important;
14 | height: 50px !important;
15 | padding: 10px !important;
16 | }
17 |
18 | @media only screen and (max-width: 991px) {
19 | .swiper-button-next,
20 | .swiper-button-prev {
21 | display: none;
22 | }
23 | }
24 |
25 | * {
26 | scrollbar-width: auto;
27 | scrollbar-color: #33ffc2 #06131c;
28 | }
29 | *::-webkit-scrollbar {
30 | width: 3px;
31 | }
32 | *::-webkit-scrollbar-track {
33 | background: #06131c;
34 | }
35 | *::-webkit-scrollbar-thumb {
36 | background-color: #33ffc2;
37 | }
38 |
--------------------------------------------------------------------------------
/src/theme/darkTheme.ts:
--------------------------------------------------------------------------------
1 | const darkTheme = {
2 | palette: {
3 | primary: {
4 | main: '#00FFB3',
5 | light: '#3FFFB3',
6 | dark: '#00DBAF',
7 | contrastText: '#000000',
8 | },
9 | secondary: {
10 | main: '#0E4D51',
11 | light: '#399694',
12 | dark: '#0A3D45',
13 | contrastText: '#ffffff',
14 | },
15 | error: {
16 | main: '#FF3564',
17 | light: '#FF677C',
18 | dark: '#DB2662',
19 | contrastText: '#000000',
20 | },
21 | warning: {
22 | main: '#FF9E16',
23 | light: '#FFBD50',
24 | dark: '#DB7E10',
25 | contrastText: '#000000',
26 | },
27 | info: {
28 | main: '#286CFF',
29 | light: '#5D95FF',
30 | dark: '#1D53DB',
31 | contrastText: '#000000',
32 | },
33 | success: {
34 | main: '#84EF6A',
35 | light: '#4FE53B',
36 | dark: '#30C42B',
37 | contrastText: '#000000',
38 | },
39 | background: {
40 | paper: '#07272b',
41 | default: '#00151C',
42 | },
43 | },
44 | shape: {
45 | borderRadius: 10,
46 | },
47 | typography: {
48 | fontFamily: '"Rubik", sans-serif',
49 | },
50 | };
51 |
52 | export default darkTheme;
53 |
--------------------------------------------------------------------------------
/src/theme/lightTheme.ts:
--------------------------------------------------------------------------------
1 | const lightTheme = {
2 | palette: {
3 | primary: {
4 | main: '#00FFB3',
5 | light: '#3FFFB3',
6 | dark: '#00DBAF',
7 | contrastText: '#ffffff',
8 | },
9 | secondary: {
10 | main: '#2A3235',
11 | light: '#708185',
12 | dark: '#1E282D',
13 | contrastText: '#ffffff',
14 | },
15 | error: {
16 | main: '#FF3564',
17 | light: '#FF677C',
18 | dark: '#DB2662',
19 | contrastText: '#ffffff',
20 | },
21 | warning: {
22 | main: '#FF9E16',
23 | light: '#FFBD50',
24 | dark: '#DB7E10',
25 | contrastText: '#ffffff',
26 | },
27 | info: {
28 | main: '#286CFF',
29 | light: '#5D95FF',
30 | dark: '#1D53DB',
31 | contrastText: '#ffffff',
32 | },
33 | success: {
34 | main: '#84EF6A',
35 | light: '#4FE53B',
36 | dark: '#30C42B',
37 | contrastText: '#ffffff',
38 | },
39 | background: {
40 | paper: '#e9e9e9',
41 | default: '#ffffff',
42 | },
43 | },
44 | shape: {
45 | borderRadius: 10,
46 | },
47 | typography: {
48 | fontFamily: '"Rubik", sans-serif',
49 | },
50 | };
51 |
52 | export default lightTheme;
53 |
--------------------------------------------------------------------------------
/src/utilities/electronServer.ts:
--------------------------------------------------------------------------------
1 | import { useTheme } from '@mui/material';
2 | import Swal from 'sweetalert2';
3 |
4 | import { APP_API_PATH, APP_IS_ELECTRON, APP_IS_SEPERATE } from '../config';
5 |
6 | const electronServer = async () => {
7 | if ((APP_IS_ELECTRON || APP_IS_SEPERATE) && !APP_API_PATH) {
8 | const theme = useTheme();
9 | const { value: serverUrl } = await Swal.fire({
10 | title: 'Server URL',
11 | input: 'text',
12 | inputLabel: 'Your server URL. For example, https://dester.gq',
13 | inputValue: '',
14 | confirmButtonColor: theme.palette.primary.dark,
15 | });
16 | if (serverUrl && serverUrl.startsWith('http')) {
17 | localStorage.setItem('SERVER_URL', serverUrl.replace(/\/+$/g, ''));
18 | window.location.reload();
19 | } else {
20 | window.location.reload();
21 | }
22 | }
23 | };
24 |
25 | export default electronServer;
26 |
--------------------------------------------------------------------------------
/src/utilities/getOs.ts:
--------------------------------------------------------------------------------
1 | const getOs = () => {
2 | var userAgent = window.navigator.userAgent;
3 | var platform = window.navigator.platform;
4 | var macosPlatforms = ['Macintosh', 'MacIntel', 'MacPPC', 'Mac68K'];
5 | var windowsPlatforms = ['Win32', 'Win64', 'Windows', 'WinCE'];
6 | var iosPlatforms = ['iPhone', 'iPad', 'iPod'];
7 | var os = 'Unknown';
8 | if (macosPlatforms.indexOf(platform) !== -1) {
9 | os = 'Mac OS';
10 | } else if (iosPlatforms.indexOf(platform) !== -1) {
11 | os = 'iOS';
12 | } else if (windowsPlatforms.indexOf(platform) !== -1) {
13 | os = 'Windows';
14 | } else if (/Android/.test(userAgent)) {
15 | os = 'Android';
16 | } else if (/Linux/.test(platform)) {
17 | os = 'Linux';
18 | }
19 | return os;
20 | };
21 |
22 | export default getOs;
23 |
--------------------------------------------------------------------------------
/src/utilities/guid.ts:
--------------------------------------------------------------------------------
1 | export default function guid() {
2 | function _p8(s?: any) {
3 | const p = `${Math.random().toString(16)}000000000`.substr(2, 8);
4 | return s ? `-${p.substr(0, 4)}-${p.substr(4, 4)}` : p;
5 | }
6 | return _p8() + _p8(true) + _p8(true) + _p8();
7 | }
8 |
--------------------------------------------------------------------------------
/src/utilities/human.ts:
--------------------------------------------------------------------------------
1 | export const humanSize = (bytes: number) => {
2 | const thresh = 1024;
3 | if (Math.abs(bytes) < thresh) {
4 | return bytes + ' B';
5 | }
6 | const units = ['kB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'];
7 | let u = -1;
8 | const r = 10;
9 | do {
10 | bytes /= thresh;
11 | ++u;
12 | } while (Math.round(Math.abs(bytes) * r) / r >= thresh && u < units.length - 1);
13 |
14 | return bytes.toFixed(1) + ' ' + units[u];
15 | };
16 |
17 | export const humanTime = (minutes: number) => {
18 | const subHours = Math.floor(minutes / 60);
19 | const subMins = minutes % 60;
20 | let result = '';
21 | if (subHours > 0) {
22 | result += subHours.toString() + 'h';
23 | }
24 | if (subMins > 0) {
25 | if (result) {
26 | result += ' ' + subMins.toString() + 'm';
27 | } else {
28 | result += subMins.toString() + 'm';
29 | }
30 | }
31 | if (result) {
32 | return result;
33 | } else {
34 | return '0m';
35 | }
36 | };
37 |
--------------------------------------------------------------------------------
/src/utilities/isElectron.ts:
--------------------------------------------------------------------------------
1 | const isElectron = () => {
2 | if (
3 | typeof navigator === 'object' &&
4 | typeof navigator.userAgent === 'string' &&
5 | navigator.userAgent.indexOf('Electron') >= 0
6 | ) {
7 | return true;
8 | }
9 | if (
10 | typeof process !== 'undefined' &&
11 | typeof process.versions === 'object' &&
12 | !!process.versions.electron
13 | ) {
14 | return true;
15 | }
16 | return false;
17 | };
18 |
19 | export default isElectron;
20 |
--------------------------------------------------------------------------------
/src/utilities/requests.ts:
--------------------------------------------------------------------------------
1 | import { APP_API_PATH, APP_API_VERSION_PATH, APP_DESCRIPTION, APP_NAME } from '../config';
2 |
3 | export const get = async (path: string, setData: any, setRequestInfo: any, setIsLoaded: any) => {
4 | const res = await fetch(`${APP_API_PATH}${APP_API_VERSION_PATH}${path}`);
5 | const data = (await res.json()) || {
6 | code: null,
7 | message: 'The server could not be reached.',
8 | ok: false,
9 | result: null,
10 | time_taken: 0,
11 | title: APP_NAME,
12 | description: APP_DESCRIPTION,
13 | };
14 | const info = {
15 | code: data.code,
16 | message: data.message,
17 | ok: data.ok,
18 | time_taken: data.time_taken,
19 | title: data.title,
20 | description: data.description,
21 | };
22 | if (info.title !== localStorage.getItem('APP_NAME')) {
23 | localStorage.setItem('APP_NAME', info.title);
24 | }
25 | if (info.description !== localStorage.getItem('APP_DESCRIPTION')) {
26 | localStorage.setItem('APP_DESCRIPTION', info.description);
27 | }
28 | setData(data.result);
29 | setRequestInfo(info);
30 | setIsLoaded(true);
31 | };
32 |
--------------------------------------------------------------------------------
/src/utilities/useBreakpoint.ts:
--------------------------------------------------------------------------------
1 | import { useEffect, useState } from 'react';
2 |
3 | const breakpoints = {
4 | 0: 'xs',
5 | 600: 'sm',
6 | 900: 'md',
7 | 1200: 'lg',
8 | 1536: 'xl',
9 | };
10 |
11 | const useBreakpoint = () => {
12 | const [breakpoint, setBreakPoint] = useState('');
13 | const [windowSize, setWindowSize] = useState({
14 | width: undefined,
15 | height: undefined,
16 | });
17 |
18 | const handleResize = () => {
19 | setWindowSize({
20 | width: window.innerWidth,
21 | height: window.innerHeight,
22 | });
23 | };
24 |
25 | useEffect(() => {
26 | window.addEventListener('resize', handleResize);
27 | handleResize();
28 |
29 | if (0 < windowSize.width && windowSize.width < 600) {
30 | setBreakPoint(breakpoints[0]);
31 | }
32 | if (600 < windowSize.width && windowSize.width < 900) {
33 | setBreakPoint(breakpoints[600]);
34 | }
35 | if (900 < windowSize.width && windowSize.width < 1200) {
36 | setBreakPoint(breakpoints[900]);
37 | }
38 | if (1200 < windowSize.width && windowSize.width < 1536) {
39 | setBreakPoint(breakpoints[1200]);
40 | }
41 | if (windowSize.width >= 1536) {
42 | setBreakPoint(breakpoints[1536]);
43 | }
44 |
45 | return () => window.removeEventListener('resize', handleResize);
46 | }, [windowSize.width]);
47 | return breakpoint;
48 | };
49 |
50 | export default useBreakpoint;
51 |
--------------------------------------------------------------------------------
/src/utilities/useNetworkStatus.ts:
--------------------------------------------------------------------------------
1 | import { useEffect, useState } from 'react';
2 |
3 | function getNetworkConnection() {
4 | return navigator['connection'] || null;
5 | }
6 | function getNetworkConnectionInfo() {
7 | const connection = getNetworkConnection();
8 | console.log(connection);
9 | if (!connection) {
10 | return {};
11 | }
12 | return {
13 | status: connection,
14 | };
15 | }
16 | function useNetworkStatus() {
17 | const [state, setState] = useState(() => {
18 | return {
19 | since: undefined,
20 | online: navigator.onLine,
21 | ...getNetworkConnectionInfo(),
22 | };
23 | });
24 | useEffect(() => {
25 | const handleOnline = () => {
26 | setState((prevState: any) => ({
27 | ...prevState,
28 | online: true,
29 | since: new Date().toString(),
30 | }));
31 | };
32 | const handleOffline = () => {
33 | setState((prevState: any) => ({
34 | ...prevState,
35 | online: false,
36 | since: new Date().toString(),
37 | }));
38 | };
39 | const handleConnectionChange = () => {
40 | setState((prevState) => ({
41 | ...prevState,
42 | ...getNetworkConnectionInfo(),
43 | }));
44 | };
45 | window.addEventListener('online', handleOnline);
46 | window.addEventListener('offline', handleOffline);
47 | const connection = getNetworkConnection();
48 | connection?.addEventListener('change', handleConnectionChange);
49 | return () => {
50 | window.removeEventListener('online', handleOnline);
51 | window.removeEventListener('offline', handleOffline);
52 | connection?.removeEventListener('change', handleConnectionChange);
53 | };
54 | }, []);
55 | return state;
56 | }
57 | export default useNetworkStatus;
58 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "es5",
4 | "lib": ["dom", "dom.iterable", "esnext"],
5 | "allowJs": true,
6 | "skipLibCheck": true,
7 | "esModuleInterop": true,
8 | "allowSyntheticDefaultImports": true,
9 | "strict": true,
10 | "forceConsistentCasingInFileNames": true,
11 | "noFallthroughCasesInSwitch": true,
12 | "module": "esnext",
13 | "moduleResolution": "node",
14 | "resolveJsonModule": true,
15 | "isolatedModules": true,
16 | "noImplicitAny": false,
17 | "noEmit": true,
18 | "jsx": "react-jsx"
19 | },
20 | "include": ["src"]
21 | }
22 |
--------------------------------------------------------------------------------