fix: kiosk toggle sync, load more animation, remove nsis target
This commit is contained in:
56
README.md
56
README.md
@@ -19,7 +19,7 @@ npm run dev
|
|||||||
## Сборка
|
## Сборка
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Windows (zip)
|
# Windows (installer + zip)
|
||||||
npm run build:win
|
npm run build:win
|
||||||
|
|
||||||
# Linux (AppImage + deb)
|
# Linux (AppImage + deb)
|
||||||
@@ -32,18 +32,56 @@ npm run build:linux
|
|||||||
|
|
||||||
## Настройка
|
## Настройка
|
||||||
|
|
||||||
В настройках приложения (шестерёнка):
|
Настройки открываются кнопкой в левом верхнем углу приложения.
|
||||||
|
|
||||||
- **Список приложений** — сайты, которые отображаются на главном экране
|
### Приложения
|
||||||
- **TMDB API Key** — для поиска и обзора фильмов, получить на [themoviedb.org](https://www.themoviedb.org/settings/api)
|
|
||||||
|
Список сайтов, которые отображаются на главном экране в виде карточек. Для каждого можно указать:
|
||||||
|
|
||||||
|
- **Название** — отображается под иконкой
|
||||||
|
- **URL** — адрес сайта, открывается в отдельном WebContentsView
|
||||||
|
- **URL иконки** — картинка для карточки (необязательно)
|
||||||
|
- **Прокси** — использовать ли прокси для этого сайта (переключатель включается отдельно для каждого)
|
||||||
|
|
||||||
|
### Прокси
|
||||||
|
|
||||||
|
Приложение поддерживает HTTP/HTTPS/SOCKS5 прокси. Настраивается в разделе "Прокси" — указываешь хост и порт. Прокси применяется не глобально, а поприложенно: для каждого сайта в списке есть отдельный переключатель. Это позволяет, например, открывать заблокированные сайты через прокси, а остальные — напрямую.
|
||||||
|
|
||||||
|
Конфигурация прокси сохраняется в файл `~/.ESH-Media.json` и применяется при следующем запуске автоматически.
|
||||||
|
|
||||||
|
### Поиск фильмов
|
||||||
|
|
||||||
|
- **TMDB API Key** — ключ для поиска метаданных, постеров и обзора по фильтрам. Получить бесплатно на [themoviedb.org](https://www.themoviedb.org/settings/api). Поддерживаются как обычные API-ключи, так и Bearer-токены.
|
||||||
|
- **Сайты** — список фильмовых сайтов, на которых будет производиться поиск после выбора фильма из TMDB. Поддерживаются движки DLE (kinogo, lordfilm и зеркала), HDRezka, Filmix. Тип определяется автоматически по домену.
|
||||||
|
|
||||||
|
Если раздел "Сайты" пустой, приложение попробует использовать подходящие сайты из раздела "Приложения".
|
||||||
|
|
||||||
|
## Конфиг
|
||||||
|
|
||||||
|
Хранится в домашней директории пользователя: `~/.ESH-Media.json`.
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"apps": [...],
|
||||||
|
"proxy": { "host": "127.0.0.1", "port": "7890" },
|
||||||
|
"movieSites": [...],
|
||||||
|
"tmdbApiKey": "...",
|
||||||
|
"bookmarks": [...]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
## Структура
|
## Структура
|
||||||
|
|
||||||
```
|
```
|
||||||
main.js — main process
|
main.js — main process
|
||||||
preload.js — preload / IPC bridge
|
preload.js — preload / IPC bridge
|
||||||
|
index.html — точка входа основного UI
|
||||||
|
loader.html — экран загрузки
|
||||||
|
dialog-error.html — диалог ошибки
|
||||||
|
dialog-confirm.html — диалог подтверждения
|
||||||
src/
|
src/
|
||||||
components/ — React components
|
entries/ — entry points для Vite (loader, dialogs)
|
||||||
pages/ — pages
|
components/ — React компоненты
|
||||||
assets/ — styles
|
pages/ — страницы
|
||||||
|
styles/ — стили
|
||||||
```
|
```
|
||||||
|
|||||||
12
dialog-confirm.html
Normal file
12
dialog-confirm.html
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="ru">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
<title>Confirm</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="root"></div>
|
||||||
|
<script type="module" src="/src/entries/dialog-confirm.tsx"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
12
dialog-error.html
Normal file
12
dialog-error.html
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="ru">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
<title>Error</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="root"></div>
|
||||||
|
<script type="module" src="/src/entries/dialog-error.tsx"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
12
loader.html
Normal file
12
loader.html
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="ru">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
<title>Loading</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="root"></div>
|
||||||
|
<script type="module" src="/src/entries/loader.tsx"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
107
main.js
107
main.js
@@ -163,6 +163,8 @@ async function createWindow() {
|
|||||||
mainWindow = new BrowserWindow({
|
mainWindow = new BrowserWindow({
|
||||||
width: 1280,
|
width: 1280,
|
||||||
height: 800,
|
height: 800,
|
||||||
|
kiosk: true,
|
||||||
|
autoHideMenuBar: true,
|
||||||
webPreferences: {
|
webPreferences: {
|
||||||
preload: PRELOAD_PATH,
|
preload: PRELOAD_PATH,
|
||||||
contextIsolation: true,
|
contextIsolation: true,
|
||||||
@@ -214,17 +216,11 @@ function setLoader() {
|
|||||||
loaderView = new WebContentsView({ webPreferences: { contextIsolation: true, nodeIntegration: false } });
|
loaderView = new WebContentsView({ webPreferences: { contextIsolation: true, nodeIntegration: false } });
|
||||||
addChild(loaderView);
|
addChild(loaderView);
|
||||||
loaderView.setBounds({ x: 0, y: HEADER_H, width, height: height - HEADER_H });
|
loaderView.setBounds({ x: 0, y: HEADER_H, width, height: height - HEADER_H });
|
||||||
const html = `<html><head><style>
|
if (isDev) {
|
||||||
*{margin:0;padding:0;box-sizing:border-box}
|
loaderView.webContents.loadURL(`${RENDERER_URL}/loader.html`);
|
||||||
body{background:#111;display:flex;align-items:center;justify-content:center;height:100vh;transition:opacity 0.25s ease;opacity:0}
|
} else {
|
||||||
body.visible{opacity:1}
|
loaderView.webContents.loadFile(path.join(__dirname, 'dist', 'loader.html'));
|
||||||
.spinner{width:36px;height:36px;border:3px solid rgba(255,255,255,0.1);border-top-color:#E50914;border-radius:50%;animation:spin 0.7s linear infinite}
|
}
|
||||||
@keyframes spin{to{transform:rotate(360deg)}}
|
|
||||||
</style></head><body>
|
|
||||||
<div class="spinner"></div>
|
|
||||||
<script>requestAnimationFrame(()=>requestAnimationFrame(()=>document.body.classList.add('visible')))</script>
|
|
||||||
</body></html>`;
|
|
||||||
loaderView.webContents.loadURL('data:text/html;charset=utf-8,' + encodeURIComponent(html));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function removeLoader() {
|
function removeLoader() {
|
||||||
@@ -237,41 +233,6 @@ function removeLoader() {
|
|||||||
|
|
||||||
// --- Dialogs (WebContentsView overlays) ---
|
// --- Dialogs (WebContentsView overlays) ---
|
||||||
|
|
||||||
const DIALOG_STYLES = `
|
|
||||||
*{margin:0;padding:0;box-sizing:border-box}
|
|
||||||
body{
|
|
||||||
background:rgba(0,0,0,0);
|
|
||||||
display:flex;align-items:center;justify-content:center;
|
|
||||||
height:100vh;
|
|
||||||
font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',sans-serif;
|
|
||||||
transition:background 0.22s ease;
|
|
||||||
}
|
|
||||||
body.visible{background:rgba(0,0,0,0.78)}
|
|
||||||
body.hiding{background:rgba(0,0,0,0)}
|
|
||||||
.card{
|
|
||||||
background:#1c1c1c;border:1px solid rgba(255,255,255,0.1);
|
|
||||||
border-radius:12px;padding:32px 36px;text-align:center;
|
|
||||||
min-width:300px;max-width:420px;box-shadow:0 24px 64px rgba(0,0,0,0.8);
|
|
||||||
opacity:0;transform:scale(0.92) translateY(10px);
|
|
||||||
transition:opacity 0.22s ease,transform 0.22s cubic-bezier(0.34,1.56,0.64,1);
|
|
||||||
}
|
|
||||||
body.visible .card{opacity:1;transform:scale(1) translateY(0)}
|
|
||||||
body.hiding .card{opacity:0;transform:scale(0.95) translateY(6px)}
|
|
||||||
.title{font-size:17px;font-weight:700;color:#fff;margin-bottom:10px}
|
|
||||||
.msg{font-size:13px;color:#999;line-height:1.5;margin-bottom:26px}
|
|
||||||
.btns{display:flex;gap:10px;justify-content:center}
|
|
||||||
button{padding:10px 26px;border:none;border-radius:7px;font-size:13px;font-weight:600;cursor:pointer;transition:opacity 0.15s,transform 0.1s}
|
|
||||||
button:hover{opacity:0.85} button:active{transform:scale(0.97)}
|
|
||||||
.btn-yes{background:#E50914;color:#fff}
|
|
||||||
.btn-no,.btn-ok{background:rgba(255,255,255,0.1);color:#ccc}
|
|
||||||
`;
|
|
||||||
|
|
||||||
function dialogFadeIn(view) {
|
|
||||||
view.webContents.executeJavaScript(
|
|
||||||
`requestAnimationFrame(()=>requestAnimationFrame(()=>document.body.classList.add('visible')))`
|
|
||||||
).catch(() => {});
|
|
||||||
}
|
|
||||||
|
|
||||||
function dialogFadeOut(view, cb) {
|
function dialogFadeOut(view, cb) {
|
||||||
view.webContents.executeJavaScript(
|
view.webContents.executeJavaScript(
|
||||||
`document.body.classList.remove('visible');document.body.classList.add('hiding')`
|
`document.body.classList.remove('visible');document.body.classList.add('hiding')`
|
||||||
@@ -292,15 +253,13 @@ function makeDialogView() {
|
|||||||
function setError(title, text) {
|
function setError(title, text) {
|
||||||
const view = makeDialogView();
|
const view = makeDialogView();
|
||||||
errorViews.push(view);
|
errorViews.push(view);
|
||||||
const html = `<html><head><style>${DIALOG_STYLES}</style></head><body>
|
const query = new URLSearchParams({ title: title || '', text: text || '' }).toString();
|
||||||
<div class="card">
|
view.webContents.once('did-finish-load', () => { addChild(view); });
|
||||||
<div class="title">${title}</div>
|
if (isDev) {
|
||||||
<div class="msg">${text}</div>
|
view.webContents.loadURL(`${RENDERER_URL}/dialog-error.html?${query}`);
|
||||||
<div class="btns"><button class="btn-ok" onclick="window.electron&&window.electron.handleAction('error')">Закрыть</button></div>
|
} else {
|
||||||
</div>
|
view.webContents.loadFile(path.join(__dirname, 'dist', 'dialog-error.html'), { query: { title: title || '', text: text || '' } });
|
||||||
</body></html>`;
|
}
|
||||||
view.webContents.once('did-finish-load', () => { addChild(view); dialogFadeIn(view); });
|
|
||||||
view.webContents.loadURL('data:text/html;charset=utf-8,' + encodeURIComponent(html));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function removeError() {
|
function removeError() {
|
||||||
@@ -312,17 +271,13 @@ function removeError() {
|
|||||||
function setConfirm(text, actionOnYes) {
|
function setConfirm(text, actionOnYes) {
|
||||||
const view = makeDialogView();
|
const view = makeDialogView();
|
||||||
confirmViews.push({ view, actionOnYes });
|
confirmViews.push({ view, actionOnYes });
|
||||||
const html = `<html><head><style>${DIALOG_STYLES}</style></head><body>
|
const query = new URLSearchParams({ text: text || '' }).toString();
|
||||||
<div class="card">
|
view.webContents.once('did-finish-load', () => { addChild(view); });
|
||||||
<div class="msg">${text}</div>
|
if (isDev) {
|
||||||
<div class="btns">
|
view.webContents.loadURL(`${RENDERER_URL}/dialog-confirm.html?${query}`);
|
||||||
<button class="btn-yes" onclick="window.electron&&window.electron.handleAction('confirmYes')">Да</button>
|
} else {
|
||||||
<button class="btn-no" onclick="window.electron&&window.electron.handleAction('confirmNo')">Нет</button>
|
view.webContents.loadFile(path.join(__dirname, 'dist', 'dialog-confirm.html'), { query: { text: text || '' } });
|
||||||
</div>
|
}
|
||||||
</div>
|
|
||||||
</body></html>`;
|
|
||||||
view.webContents.once('did-finish-load', () => { addChild(view); dialogFadeIn(view); });
|
|
||||||
view.webContents.loadURL('data:text/html;charset=utf-8,' + encodeURIComponent(html));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function removeConfirm() {
|
function removeConfirm() {
|
||||||
@@ -835,7 +790,21 @@ ipcMain.on('write-config', (_event, data) => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
ipcMain.on('confirm', (_event, text, actionOnYes) => setConfirm(text, actionOnYes));
|
ipcMain.handle('is-kiosk', () => mainWindow.isKiosk());
|
||||||
|
|
||||||
|
ipcMain.handle('toggle-kiosk', () => {
|
||||||
|
if (mainWindow.isKiosk()) {
|
||||||
|
mainWindow.setKiosk(false);
|
||||||
|
mainWindow.maximize();
|
||||||
|
mainWindow.setMenuBarVisibility(false);
|
||||||
|
return false;
|
||||||
|
} else {
|
||||||
|
mainWindow.setKiosk(true);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
ipcMain.on('confirm',(_event, text, actionOnYes) => setConfirm(text, actionOnYes));
|
||||||
|
|
||||||
ipcMain.on('action', (_event, action) => {
|
ipcMain.on('action', (_event, action) => {
|
||||||
if (action === 'error') {
|
if (action === 'error') {
|
||||||
@@ -889,8 +858,8 @@ app.whenReady().then(async () => {
|
|||||||
} catch (_) {}
|
} catch (_) {}
|
||||||
|
|
||||||
enableBlockingInSession(session.defaultSession);
|
enableBlockingInSession(session.defaultSession);
|
||||||
enableBlockingInSession(session.fromPartition('persist:proxy'));
|
getProxySession();
|
||||||
enableBlockingInSession(session.fromPartition('persist:direct'));
|
getDirectSession();
|
||||||
await loadExtensions();
|
await loadExtensions();
|
||||||
await createWindow();
|
await createWindow();
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -46,9 +46,15 @@
|
|||||||
"extensions/**/*"
|
"extensions/**/*"
|
||||||
],
|
],
|
||||||
"win": {
|
"win": {
|
||||||
"target": "zip",
|
"target": ["zip"],
|
||||||
"icon": "public/favicon.ico"
|
"icon": "public/favicon.ico"
|
||||||
},
|
},
|
||||||
|
"nsis": {
|
||||||
|
"oneClick": false,
|
||||||
|
"allowToChangeInstallationDirectory": true,
|
||||||
|
"installerLanguages": ["Russian", "English"],
|
||||||
|
"language": "1049"
|
||||||
|
},
|
||||||
"linux": {
|
"linux": {
|
||||||
"target": ["AppImage", "deb"],
|
"target": ["AppImage", "deb"],
|
||||||
"icon": "public/logo.png",
|
"icon": "public/logo.png",
|
||||||
|
|||||||
@@ -26,4 +26,7 @@ contextBridge.exposeInMainWorld('electron', {
|
|||||||
searchMovies: (query, sites) => ipcRenderer.invoke('search-movies', query, sites),
|
searchMovies: (query, sites) => ipcRenderer.invoke('search-movies', query, sites),
|
||||||
searchTmdb: (query, apiKey) => ipcRenderer.invoke('search-tmdb', query, apiKey),
|
searchTmdb: (query, apiKey) => ipcRenderer.invoke('search-tmdb', query, apiKey),
|
||||||
discoverTmdb: (params) => ipcRenderer.invoke('discover-tmdb', params),
|
discoverTmdb: (params) => ipcRenderer.invoke('discover-tmdb', params),
|
||||||
|
toggleKiosk: () => ipcRenderer.invoke('toggle-kiosk'),
|
||||||
|
isKiosk: () => ipcRenderer.invoke('is-kiosk'),
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,13 +0,0 @@
|
|||||||
{
|
|
||||||
"files": {
|
|
||||||
"main.css": "/static/css/main.e6c6eccb.css",
|
|
||||||
"main.js": "/static/js/main.e2d60cfb.js",
|
|
||||||
"index.html": "/index.html",
|
|
||||||
"main.e6c6eccb.css.map": "/static/css/main.e6c6eccb.css.map",
|
|
||||||
"main.e2d60cfb.js.map": "/static/js/main.e2d60cfb.js.map"
|
|
||||||
},
|
|
||||||
"entrypoints": [
|
|
||||||
"static/css/main.e6c6eccb.css",
|
|
||||||
"static/js/main.e2d60cfb.js"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
<!doctype html><html lang="en"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>Loader Animation</title><script defer="defer" src="/static/js/main.e2d60cfb.js"></script><link href="/static/css/main.e6c6eccb.css" rel="stylesheet"></head><body><div id="root"></div></body></html>
|
|
||||||
@@ -1,2 +0,0 @@
|
|||||||
.confirm{position:relative}.confirm div{border-radius:50%;opacity:1;position:absolute}h1,h2{color:#fff}.confirm-container{background-color:#626262;border:0 solid #000;border-radius:20px;display:flex;left:50%;max-height:600px;min-height:200px;min-width:400px;padding:15px;position:fixed;top:50%;transform:translate(-50%,-50%);z-index:9999}.confirm-button-container{bottom:40px;color:#fff;font-size:medium;position:absolute;right:40px;width:100}.confirm-button-container button{background:#0f00;border:none;border-radius:4px;color:#fff;cursor:pointer;font-size:20px;height:50px;margin-right:20px;transition:background .3s ease;width:70px}.confirm-button:hover{background:#717171}body{align-items:center;animation:fadeIn .5s forwards;display:flex;height:100vh;justify-content:center;margin:0;opacity:0;overflow:hidden;padding:0}@keyframes fadeIn{0%{opacity:0}to{background:#000000b3;opacity:.9}}
|
|
||||||
/*# sourceMappingURL=main.e6c6eccb.css.map*/
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
{"version":3,"file":"static/css/main.e6c6eccb.css","mappings":"AACA,SACI,iBACF,CAEA,aAGE,iBAAkB,CADlB,SAAU,CADV,iBAIF,CACA,MACE,UAEF,CCbF,mBAKI,wBAAiC,CACjC,mBAAuB,CACvB,kBAAmB,CACnB,YAAa,CALb,QAAS,CAUT,gBAAiB,CADjB,gBAAiB,CADjB,eAAgB,CAFhB,YAAa,CARb,cAAc,CACd,OAAQ,CAER,8BAAgC,CAMhC,YAIF,CAEA,0BAKE,WAAY,CAHZ,UAAY,CACZ,gBAAiB,CACjB,iBAAkB,CAElB,UAAW,CALX,SAMF,CAEA,iCAGE,gBAA8B,CAC9B,WAAY,CAGZ,iBAAkB,CAElB,UAAY,CAHZ,cAAe,CADf,cAAe,CAHf,WAAY,CAQZ,iBAAkB,CAFlB,8BAAgC,CAPhC,UAUF,CAEA,sBACE,kBACF,CCxCF,KAOI,kBAAmB,CAEnB,6BAA+B,CAJ/B,YAAa,CADb,YAAa,CAEb,sBAAuB,CALvB,QAAS,CAOT,SAAU,CALV,eAAgB,CADhB,SAQF,CAEA,kBACE,GACE,SACF,CACA,GAEE,oBAA8B,CAD9B,UAEF,CACF","sources":["components/Confirm.css","components/ConfirmContainer.css","App.css"],"sourcesContent":["/* src/components/Loader.css */\r\n.confirm {\r\n position: relative;\r\n }\r\n \r\n .confirm div {\r\n position: absolute;\r\n opacity: 1;\r\n border-radius: 50%;\r\n /* animation: loader-animation 1.5s infinite ease-in-out; */\r\n }\r\n h1, h2 {\r\n color: white;\r\n /* animation: loader-animation 1.5s infinite ease-in-out; */\r\n }\r\n /* .error div:nth-child(2) {\r\n animation-delay: -1.2s;\r\n }\r\n \r\n @keyframes error-animation {\r\n 0%,\r\n 100% {\r\n width: 0;\r\n height: 0;\r\n top: 50px;\r\n left: 50px;\r\n opacity: 0.5;\r\n }\r\n 50% {\r\n width: 100px;\r\n height: 100px;\r\n top: 0;\r\n left: 0;\r\n opacity: 0;\r\n }\r\n }\r\n */","/* src/components/LoaderContainer.css */\r\n.confirm-container {\r\n position:fixed; /* Fixed position to keep it centered relative to the viewport */\r\n top: 50%; /* Center vertically */\r\n left: 50%; /* Center horizontally */\r\n transform: translate(-50%, -50%); /* Adjust for the element's size */\r\n background-color: rgb(98, 98, 98);\r\n border: 0px solid black;\r\n border-radius: 20px;\r\n display: flex;\r\n padding: 15px;\r\n z-index: 9999; /* Ensure it's on top of other content */\r\n min-width: 400px; /* Width of the loader */\r\n min-height: 200px; /* Height of the loader */\r\n max-height: 600px; /* Height of the loader */\r\n }\r\n \r\n .confirm-button-container {\r\n width: 100;\r\n color: white;\r\n font-size: medium;\r\n position: absolute;\r\n bottom: 40px; \r\n right: 40px;\r\n }\r\n \r\n .confirm-button-container button {\r\n width: 70px;\r\n height: 50px;\r\n background: rgba(0, 255, 0, 0); /* Transparent background */\r\n border: none;\r\n font-size: 20px;\r\n cursor: pointer;\r\n border-radius: 4px;\r\n transition: background 0.3s ease;\r\n color: white;\r\n margin-right: 20px;\r\n }\r\n \r\n .confirm-button:hover {\r\n background: rgb(113, 113, 113); /* Light green with opacity 0.5 */\r\n } \r\n ","/* src/App.css */\r\nbody {\r\n margin: 0;\r\n padding: 0;\r\n overflow: hidden;\r\n height: 100vh;\r\n display: flex;\r\n justify-content: center;\r\n align-items: center;\r\n opacity: 0;\r\n animation: fadeIn 0.5s forwards;\r\n }\r\n \r\n @keyframes fadeIn {\r\n from {\r\n opacity: 0;\r\n }\r\n to {\r\n opacity: 0.9;\r\n background: rgba(0, 0, 0, 0.7);\r\n }\r\n }\r\n "],"names":[],"sourceRoot":""}
|
|
||||||
File diff suppressed because one or more lines are too long
@@ -1,39 +0,0 @@
|
|||||||
/**
|
|
||||||
* @license React
|
|
||||||
* react-dom.production.min.js
|
|
||||||
*
|
|
||||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
|
||||||
*
|
|
||||||
* This source code is licensed under the MIT license found in the
|
|
||||||
* LICENSE file in the root directory of this source tree.
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @license React
|
|
||||||
* react-jsx-runtime.production.min.js
|
|
||||||
*
|
|
||||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
|
||||||
*
|
|
||||||
* This source code is licensed under the MIT license found in the
|
|
||||||
* LICENSE file in the root directory of this source tree.
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @license React
|
|
||||||
* react.production.min.js
|
|
||||||
*
|
|
||||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
|
||||||
*
|
|
||||||
* This source code is licensed under the MIT license found in the
|
|
||||||
* LICENSE file in the root directory of this source tree.
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @license React
|
|
||||||
* scheduler.production.min.js
|
|
||||||
*
|
|
||||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
|
||||||
*
|
|
||||||
* This source code is licensed under the MIT license found in the
|
|
||||||
* LICENSE file in the root directory of this source tree.
|
|
||||||
*/
|
|
||||||
File diff suppressed because one or more lines are too long
@@ -1,101 +0,0 @@
|
|||||||
<html lang="en" class="focus-outline-visible">
|
|
||||||
|
|
||||||
<head>
|
|
||||||
<meta charset="UTF-8">
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
||||||
<title>Loader Animation</title>
|
|
||||||
<style>
|
|
||||||
body {
|
|
||||||
margin: 0;
|
|
||||||
padding: 0;
|
|
||||||
overflow: hidden;
|
|
||||||
height: 100vh;
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
opacity: 0;
|
|
||||||
animation: fadeIn 0.5s forwards;
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes fadeIn {
|
|
||||||
from {
|
|
||||||
opacity: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
to {
|
|
||||||
opacity: 0.9;
|
|
||||||
background: rgba(0, 0, 0, 0.7);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.loader-container {
|
|
||||||
position: absolute;
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.loader {
|
|
||||||
position: relative;
|
|
||||||
width: 100px;
|
|
||||||
height: 100px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.loader div {
|
|
||||||
position: absolute;
|
|
||||||
border: 4px solid gray;
|
|
||||||
opacity: 1;
|
|
||||||
border-radius: 50%;
|
|
||||||
animation: loader-animation 1.5s infinite ease-in-out;
|
|
||||||
}
|
|
||||||
|
|
||||||
.loader div:nth-child(2) {
|
|
||||||
animation-delay: -1.2s;
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes loader-animation {
|
|
||||||
|
|
||||||
0%,
|
|
||||||
100% {
|
|
||||||
width: 0;
|
|
||||||
height: 0;
|
|
||||||
top: 50px;
|
|
||||||
left: 50px;
|
|
||||||
opacity: 0.5;
|
|
||||||
}
|
|
||||||
|
|
||||||
50% {
|
|
||||||
width: 100px;
|
|
||||||
height: 100px;
|
|
||||||
top: 0;
|
|
||||||
left: 0;
|
|
||||||
opacity: 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
</head>
|
|
||||||
|
|
||||||
<body>
|
|
||||||
<div class="loader-container">
|
|
||||||
<div class="loader">
|
|
||||||
<div></div>
|
|
||||||
<div></div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
|
|
||||||
<div class="loader-container">
|
|
||||||
<div class="loader">
|
|
||||||
<div></div>
|
|
||||||
<div></div>
|
|
||||||
</div>
|
|
||||||
<div class="loader">
|
|
||||||
<div></div>
|
|
||||||
<div></div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</body>
|
|
||||||
|
|
||||||
</html>
|
|
||||||
@@ -1,40 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "torrent_client",
|
|
||||||
"version": "1.0.0",
|
|
||||||
"private": true,
|
|
||||||
"proxy": "http://localhost:3001/",
|
|
||||||
"dependencies": {
|
|
||||||
"react": "^18.0.0",
|
|
||||||
"react-dom": "^18.0.0",
|
|
||||||
"react-scripts": "5.0.1"
|
|
||||||
},
|
|
||||||
"scripts": {
|
|
||||||
"start": "react-scripts start",
|
|
||||||
"build": "react-scripts build",
|
|
||||||
"test": "react-scripts test",
|
|
||||||
"eject": "react-scripts eject",
|
|
||||||
"serv": "node ./serv.js"
|
|
||||||
},
|
|
||||||
"eslintConfig": {
|
|
||||||
"extends": [
|
|
||||||
"react-app",
|
|
||||||
"react-app/jest"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"browserslist": {
|
|
||||||
"production": [
|
|
||||||
">0.2%",
|
|
||||||
"not dead",
|
|
||||||
"not op_mini all"
|
|
||||||
],
|
|
||||||
"development": [
|
|
||||||
"last 1 chrome version",
|
|
||||||
"last 1 firefox version",
|
|
||||||
"last 1 safari version"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"main": "index.js",
|
|
||||||
"author": "",
|
|
||||||
"license": "ISC",
|
|
||||||
"description": ""
|
|
||||||
}
|
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html lang="en">
|
|
||||||
<head>
|
|
||||||
<meta charset="UTF-8">
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
||||||
<title>Loader Animation</title>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<!-- This is where your React app will be injected -->
|
|
||||||
<div id="root"></div>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
@@ -1,16 +0,0 @@
|
|||||||
const express = require('express');
|
|
||||||
const path = require('path');
|
|
||||||
|
|
||||||
// const __filename = fileURLToPath(import.meta.url);
|
|
||||||
// const __dirname = dirname(__filename);
|
|
||||||
|
|
||||||
const app = express();
|
|
||||||
app.use(express.static(path.join(__dirname, 'build')));
|
|
||||||
app.use(express.static(path.join(__dirname, 'build')));
|
|
||||||
app.use(express.static(path.join(__dirname, 'build')));
|
|
||||||
app.get('/*', function (req, res) {
|
|
||||||
|
|
||||||
res.sendFile(path.join(__dirname, 'build', 'index.html'));
|
|
||||||
});
|
|
||||||
|
|
||||||
app.listen(3001, () => console.log('Example app is listening on port 3001.'));
|
|
||||||
@@ -1,23 +0,0 @@
|
|||||||
/* src/App.css */
|
|
||||||
body {
|
|
||||||
margin: 0;
|
|
||||||
padding: 0;
|
|
||||||
overflow: hidden;
|
|
||||||
height: 100vh;
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
opacity: 0;
|
|
||||||
animation: fadeIn 0.5s forwards;
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes fadeIn {
|
|
||||||
from {
|
|
||||||
opacity: 0;
|
|
||||||
}
|
|
||||||
to {
|
|
||||||
opacity: 0.9;
|
|
||||||
background: rgba(0, 0, 0, 0.7);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@@ -1,22 +0,0 @@
|
|||||||
// src/App.tsx
|
|
||||||
import React from "react";
|
|
||||||
import ConfirmContainer from "./components/ConfirmContainer";
|
|
||||||
import "./App.css";
|
|
||||||
|
|
||||||
declare global {
|
|
||||||
interface Window {
|
|
||||||
electron?: {
|
|
||||||
handleAction: (action: string) => void;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const App: React.FC = () => {
|
|
||||||
return (
|
|
||||||
<div>
|
|
||||||
<ConfirmContainer />
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default App;
|
|
||||||
@@ -1,37 +0,0 @@
|
|||||||
/* src/components/Loader.css */
|
|
||||||
.confirm {
|
|
||||||
position: relative;
|
|
||||||
}
|
|
||||||
|
|
||||||
.confirm div {
|
|
||||||
position: absolute;
|
|
||||||
opacity: 1;
|
|
||||||
border-radius: 50%;
|
|
||||||
/* animation: loader-animation 1.5s infinite ease-in-out; */
|
|
||||||
}
|
|
||||||
h1, h2 {
|
|
||||||
color: white;
|
|
||||||
/* animation: loader-animation 1.5s infinite ease-in-out; */
|
|
||||||
}
|
|
||||||
/* .error div:nth-child(2) {
|
|
||||||
animation-delay: -1.2s;
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes error-animation {
|
|
||||||
0%,
|
|
||||||
100% {
|
|
||||||
width: 0;
|
|
||||||
height: 0;
|
|
||||||
top: 50px;
|
|
||||||
left: 50px;
|
|
||||||
opacity: 0.5;
|
|
||||||
}
|
|
||||||
50% {
|
|
||||||
width: 100px;
|
|
||||||
height: 100px;
|
|
||||||
top: 0;
|
|
||||||
left: 0;
|
|
||||||
opacity: 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
@@ -1,31 +0,0 @@
|
|||||||
// src/components/Loader.tsx
|
|
||||||
import React, { useEffect, useState } from "react";
|
|
||||||
import "./Confirm.css";
|
|
||||||
|
|
||||||
interface ErrorDataProps {
|
|
||||||
title: string;
|
|
||||||
text: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface ErrorProps {
|
|
||||||
errorData: ErrorDataProps;
|
|
||||||
setCustomData: (errorData: ErrorDataProps) => void;
|
|
||||||
}
|
|
||||||
|
|
||||||
const Error: React.FC = () => {
|
|
||||||
const [errorData, setCustomData] = useState<ErrorDataProps>();
|
|
||||||
useEffect(() => {
|
|
||||||
fetch('http://localhost:3001/api/custom-data')
|
|
||||||
.then(response => response.json())
|
|
||||||
.then(errorData => setCustomData(errorData ?? {title: "Uncaught", text: "error"}))
|
|
||||||
.catch(error => console.error('Error fetching custom text:', error));
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="confirm">
|
|
||||||
<h2>{errorData?.text}</h2>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default Error;
|
|
||||||
@@ -1,43 +0,0 @@
|
|||||||
/* src/components/LoaderContainer.css */
|
|
||||||
.confirm-container {
|
|
||||||
position:fixed; /* Fixed position to keep it centered relative to the viewport */
|
|
||||||
top: 50%; /* Center vertically */
|
|
||||||
left: 50%; /* Center horizontally */
|
|
||||||
transform: translate(-50%, -50%); /* Adjust for the element's size */
|
|
||||||
background-color: rgb(98, 98, 98);
|
|
||||||
border: 0px solid black;
|
|
||||||
border-radius: 20px;
|
|
||||||
display: flex;
|
|
||||||
padding: 15px;
|
|
||||||
z-index: 9999; /* Ensure it's on top of other content */
|
|
||||||
min-width: 400px; /* Width of the loader */
|
|
||||||
min-height: 200px; /* Height of the loader */
|
|
||||||
max-height: 600px; /* Height of the loader */
|
|
||||||
}
|
|
||||||
|
|
||||||
.confirm-button-container {
|
|
||||||
width: 100;
|
|
||||||
color: white;
|
|
||||||
font-size: medium;
|
|
||||||
position: absolute;
|
|
||||||
bottom: 40px;
|
|
||||||
right: 40px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.confirm-button-container button {
|
|
||||||
width: 70px;
|
|
||||||
height: 50px;
|
|
||||||
background: rgba(0, 255, 0, 0); /* Transparent background */
|
|
||||||
border: none;
|
|
||||||
font-size: 20px;
|
|
||||||
cursor: pointer;
|
|
||||||
border-radius: 4px;
|
|
||||||
transition: background 0.3s ease;
|
|
||||||
color: white;
|
|
||||||
margin-right: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.confirm-button:hover {
|
|
||||||
background: rgb(113, 113, 113); /* Light green with opacity 0.5 */
|
|
||||||
}
|
|
||||||
|
|
||||||
@@ -1,39 +0,0 @@
|
|||||||
// src/components/LoaderContainer.tsx
|
|
||||||
import React, { useEffect, useState } from "react";
|
|
||||||
import Error from "./Confirm";
|
|
||||||
import "./ConfirmContainer.css";
|
|
||||||
|
|
||||||
const ConfirmContainer: React.FC = () => {
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="confirm-container">
|
|
||||||
<Error />
|
|
||||||
<div className="confirm-button-container">
|
|
||||||
<button
|
|
||||||
className="confirm-button"
|
|
||||||
onClick={() =>
|
|
||||||
{
|
|
||||||
window?.electron &&
|
|
||||||
window.electron.handleAction("confirmNo");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<strong>Нет</strong>
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
className="confirm-button"
|
|
||||||
onClick={() =>
|
|
||||||
{
|
|
||||||
window?.electron &&
|
|
||||||
window.electron.handleAction("confirmYes");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<strong>Да</strong>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default ConfirmContainer;
|
|
||||||
@@ -1,22 +0,0 @@
|
|||||||
// src/index.tsx
|
|
||||||
import React from "react";
|
|
||||||
import ReactDOM from "react-dom/client";
|
|
||||||
import "./index.css";
|
|
||||||
import App from "./App";
|
|
||||||
import { createBrowserRouter, RouterProvider } from "react-router-dom";
|
|
||||||
|
|
||||||
// Get the root element from the DOM
|
|
||||||
const rootElement = document.getElementById("root");
|
|
||||||
|
|
||||||
if (rootElement) {
|
|
||||||
// Create a root and render the app
|
|
||||||
|
|
||||||
const root = ReactDOM.createRoot(rootElement);
|
|
||||||
root.render(
|
|
||||||
<React.StrictMode>
|
|
||||||
<App />
|
|
||||||
</React.StrictMode>
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
console.error("Root element not found");
|
|
||||||
}
|
|
||||||
@@ -1,21 +0,0 @@
|
|||||||
{
|
|
||||||
"compilerOptions": {
|
|
||||||
"target": "es5",
|
|
||||||
"lib": ["dom", "dom.iterable", "esnext"],
|
|
||||||
"allowJs": true,
|
|
||||||
"skipLibCheck": true,
|
|
||||||
"esModuleInterop": true,
|
|
||||||
"allowSyntheticDefaultImports": true,
|
|
||||||
"strict": true,
|
|
||||||
"forceConsistentCasingInFileNames": true,
|
|
||||||
"noFallthroughCasesInSwitch": true,
|
|
||||||
"module": "esnext",
|
|
||||||
"moduleResolution": "node",
|
|
||||||
"resolveJsonModule": true,
|
|
||||||
"isolatedModules": false,
|
|
||||||
"jsx": "react-jsx",
|
|
||||||
"noEmit": true
|
|
||||||
},
|
|
||||||
"include": ["src"]
|
|
||||||
}
|
|
||||||
|
|
||||||
@@ -1,13 +0,0 @@
|
|||||||
{
|
|
||||||
"files": {
|
|
||||||
"main.css": "/static/css/main.a5182e31.css",
|
|
||||||
"main.js": "/static/js/main.ab661a74.js",
|
|
||||||
"index.html": "/index.html",
|
|
||||||
"main.a5182e31.css.map": "/static/css/main.a5182e31.css.map",
|
|
||||||
"main.ab661a74.js.map": "/static/js/main.ab661a74.js.map"
|
|
||||||
},
|
|
||||||
"entrypoints": [
|
|
||||||
"static/css/main.a5182e31.css",
|
|
||||||
"static/js/main.ab661a74.js"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
<!doctype html><html lang="en"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>Loader Animation</title><script defer="defer" src="/static/js/main.ab661a74.js"></script><link href="/static/css/main.a5182e31.css" rel="stylesheet"></head><body><div id="root"></div></body></html>
|
|
||||||
@@ -1,2 +0,0 @@
|
|||||||
.error{position:relative}.error div{border-radius:50%;opacity:1;position:absolute}h1,h2{color:#fff}.error-container{background-color:#626262;border:0 solid #000;border-radius:20px;display:flex;left:50%;max-height:600px;min-height:200px;min-width:400px;padding:15px;position:fixed;top:50%;transform:translate(-50%,-50%);z-index:9999}.error-button{background:#0f00;border:none;border-radius:4px;bottom:40px;color:#fff;cursor:pointer;font-size:medium;font-size:16px;padding:10px 20px;position:absolute;right:40px;transition:background .3s ease}.error-button strong{color:#fff}.error-button:hover{background:#717171}body{align-items:center;animation:fadeIn .5s forwards;display:flex;height:100vh;justify-content:center;margin:0;opacity:0;overflow:hidden;padding:0}@keyframes fadeIn{0%{opacity:0}to{background:#000000b3;opacity:.9}}
|
|
||||||
/*# sourceMappingURL=main.a5182e31.css.map*/
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
{"version":3,"file":"static/css/main.a5182e31.css","mappings":"AACA,OACI,iBACF,CAEA,WAGE,iBAAkB,CADlB,SAAU,CADV,iBAIF,CACA,MACE,UAEF,CCbF,iBAKI,wBAAiC,CACjC,mBAAuB,CACvB,kBAAmB,CACnB,YAAa,CALb,QAAS,CAUT,gBAAiB,CADjB,gBAAiB,CADjB,eAAgB,CAFhB,YAAa,CARb,cAAc,CACd,OAAQ,CAER,8BAAgC,CAMhC,YAIF,CAEA,cAME,gBAA8B,CAC9B,WAAY,CAIZ,iBAAkB,CAPlB,WAAY,CAHZ,UAAY,CASZ,cAAe,CARf,gBAAiB,CAOjB,cAAe,CADf,iBAAkB,CALlB,iBAAkB,CAElB,UAAW,CAOX,8BACF,CAEA,qBACE,UACF,CAEA,oBACE,kBACF,CCrCF,KAOI,kBAAmB,CAEnB,6BAA+B,CAJ/B,YAAa,CADb,YAAa,CAEb,sBAAuB,CALvB,QAAS,CAOT,SAAU,CALV,eAAgB,CADhB,SAQF,CAEA,kBACE,GACE,SACF,CACA,GAEE,oBAA8B,CAD9B,UAEF,CACF","sources":["components/Error.css","components/ErrorContainer.css","App.css"],"sourcesContent":["/* src/components/Loader.css */\r\n.error {\r\n position: relative;\r\n }\r\n \r\n .error div {\r\n position: absolute;\r\n opacity: 1;\r\n border-radius: 50%;\r\n /* animation: loader-animation 1.5s infinite ease-in-out; */\r\n }\r\n h1, h2 {\r\n color: white;\r\n /* animation: loader-animation 1.5s infinite ease-in-out; */\r\n }\r\n /* .error div:nth-child(2) {\r\n animation-delay: -1.2s;\r\n }\r\n \r\n @keyframes error-animation {\r\n 0%,\r\n 100% {\r\n width: 0;\r\n height: 0;\r\n top: 50px;\r\n left: 50px;\r\n opacity: 0.5;\r\n }\r\n 50% {\r\n width: 100px;\r\n height: 100px;\r\n top: 0;\r\n left: 0;\r\n opacity: 0;\r\n }\r\n }\r\n */","/* src/components/LoaderContainer.css */\r\n.error-container {\r\n position:fixed; /* Fixed position to keep it centered relative to the viewport */\r\n top: 50%; /* Center vertically */\r\n left: 50%; /* Center horizontally */\r\n transform: translate(-50%, -50%); /* Adjust for the element's size */\r\n background-color: rgb(98, 98, 98);\r\n border: 0px solid black;\r\n border-radius: 20px;\r\n display: flex;\r\n padding: 15px;\r\n z-index: 9999; /* Ensure it's on top of other content */\r\n min-width: 400px; /* Width of the loader */\r\n min-height: 200px; /* Height of the loader */\r\n max-height: 600px; /* Height of the loader */\r\n }\r\n \r\n .error-button {\r\n color: white;\r\n font-size: medium;\r\n position: absolute;\r\n bottom: 40px; \r\n right: 40px;\r\n background: rgba(0, 255, 0, 0); /* Transparent background */\r\n border: none;\r\n padding: 10px 20px;\r\n font-size: 16px;\r\n cursor: pointer;\r\n border-radius: 4px;\r\n transition: background 0.3s ease;\r\n }\r\n \r\n .error-button strong {\r\n color: white;\r\n }\r\n \r\n .error-button:hover {\r\n background: rgb(113, 113, 113); /* Light green with opacity 0.5 */\r\n } \r\n ","/* src/App.css */\r\nbody {\r\n margin: 0;\r\n padding: 0;\r\n overflow: hidden;\r\n height: 100vh;\r\n display: flex;\r\n justify-content: center;\r\n align-items: center;\r\n opacity: 0;\r\n animation: fadeIn 0.5s forwards;\r\n }\r\n \r\n @keyframes fadeIn {\r\n from {\r\n opacity: 0;\r\n }\r\n to {\r\n opacity: 0.9;\r\n background: rgba(0, 0, 0, 0.7);\r\n }\r\n }\r\n "],"names":[],"sourceRoot":""}
|
|
||||||
File diff suppressed because one or more lines are too long
@@ -1,39 +0,0 @@
|
|||||||
/**
|
|
||||||
* @license React
|
|
||||||
* react-dom.production.min.js
|
|
||||||
*
|
|
||||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
|
||||||
*
|
|
||||||
* This source code is licensed under the MIT license found in the
|
|
||||||
* LICENSE file in the root directory of this source tree.
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @license React
|
|
||||||
* react-jsx-runtime.production.min.js
|
|
||||||
*
|
|
||||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
|
||||||
*
|
|
||||||
* This source code is licensed under the MIT license found in the
|
|
||||||
* LICENSE file in the root directory of this source tree.
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @license React
|
|
||||||
* react.production.min.js
|
|
||||||
*
|
|
||||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
|
||||||
*
|
|
||||||
* This source code is licensed under the MIT license found in the
|
|
||||||
* LICENSE file in the root directory of this source tree.
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @license React
|
|
||||||
* scheduler.production.min.js
|
|
||||||
*
|
|
||||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
|
||||||
*
|
|
||||||
* This source code is licensed under the MIT license found in the
|
|
||||||
* LICENSE file in the root directory of this source tree.
|
|
||||||
*/
|
|
||||||
File diff suppressed because one or more lines are too long
@@ -1,101 +0,0 @@
|
|||||||
<html lang="en" class="focus-outline-visible">
|
|
||||||
|
|
||||||
<head>
|
|
||||||
<meta charset="UTF-8">
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
||||||
<title>Loader Animation</title>
|
|
||||||
<style>
|
|
||||||
body {
|
|
||||||
margin: 0;
|
|
||||||
padding: 0;
|
|
||||||
overflow: hidden;
|
|
||||||
height: 100vh;
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
opacity: 0;
|
|
||||||
animation: fadeIn 0.5s forwards;
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes fadeIn {
|
|
||||||
from {
|
|
||||||
opacity: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
to {
|
|
||||||
opacity: 0.9;
|
|
||||||
background: rgba(0, 0, 0, 0.7);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.loader-container {
|
|
||||||
position: absolute;
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.loader {
|
|
||||||
position: relative;
|
|
||||||
width: 100px;
|
|
||||||
height: 100px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.loader div {
|
|
||||||
position: absolute;
|
|
||||||
border: 4px solid gray;
|
|
||||||
opacity: 1;
|
|
||||||
border-radius: 50%;
|
|
||||||
animation: loader-animation 1.5s infinite ease-in-out;
|
|
||||||
}
|
|
||||||
|
|
||||||
.loader div:nth-child(2) {
|
|
||||||
animation-delay: -1.2s;
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes loader-animation {
|
|
||||||
|
|
||||||
0%,
|
|
||||||
100% {
|
|
||||||
width: 0;
|
|
||||||
height: 0;
|
|
||||||
top: 50px;
|
|
||||||
left: 50px;
|
|
||||||
opacity: 0.5;
|
|
||||||
}
|
|
||||||
|
|
||||||
50% {
|
|
||||||
width: 100px;
|
|
||||||
height: 100px;
|
|
||||||
top: 0;
|
|
||||||
left: 0;
|
|
||||||
opacity: 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
</head>
|
|
||||||
|
|
||||||
<body>
|
|
||||||
<div class="loader-container">
|
|
||||||
<div class="loader">
|
|
||||||
<div></div>
|
|
||||||
<div></div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
|
|
||||||
<div class="loader-container">
|
|
||||||
<div class="loader">
|
|
||||||
<div></div>
|
|
||||||
<div></div>
|
|
||||||
</div>
|
|
||||||
<div class="loader">
|
|
||||||
<div></div>
|
|
||||||
<div></div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</body>
|
|
||||||
|
|
||||||
</html>
|
|
||||||
@@ -1,40 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "torrentClient",
|
|
||||||
"version": "1.0.0",
|
|
||||||
"private": true,
|
|
||||||
"proxy": "http://localhost:3001/",
|
|
||||||
"dependencies": {
|
|
||||||
"react": "^18.0.0",
|
|
||||||
"react-dom": "^18.0.0",
|
|
||||||
"react-scripts": "5.0.1"
|
|
||||||
},
|
|
||||||
"scripts": {
|
|
||||||
"start": "react-scripts start",
|
|
||||||
"build": "react-scripts build",
|
|
||||||
"test": "react-scripts test",
|
|
||||||
"eject": "react-scripts eject",
|
|
||||||
"serv": "node ./serv.js"
|
|
||||||
},
|
|
||||||
"eslintConfig": {
|
|
||||||
"extends": [
|
|
||||||
"react-app",
|
|
||||||
"react-app/jest"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"browserslist": {
|
|
||||||
"production": [
|
|
||||||
">0.2%",
|
|
||||||
"not dead",
|
|
||||||
"not op_mini all"
|
|
||||||
],
|
|
||||||
"development": [
|
|
||||||
"last 1 chrome version",
|
|
||||||
"last 1 firefox version",
|
|
||||||
"last 1 safari version"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"main": "index.js",
|
|
||||||
"author": "",
|
|
||||||
"license": "ISC",
|
|
||||||
"description": ""
|
|
||||||
}
|
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html lang="en">
|
|
||||||
<head>
|
|
||||||
<meta charset="UTF-8">
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
||||||
<title>Loader Animation</title>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<!-- This is where your React app will be injected -->
|
|
||||||
<div id="root"></div>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
@@ -1,16 +0,0 @@
|
|||||||
const express = require('express');
|
|
||||||
const path = require('path');
|
|
||||||
|
|
||||||
// const __filename = fileURLToPath(import.meta.url);
|
|
||||||
// const __dirname = dirname(__filename);
|
|
||||||
|
|
||||||
const app = express();
|
|
||||||
app.use(express.static(path.join(__dirname, 'build')));
|
|
||||||
app.use(express.static(path.join(__dirname, 'build')));
|
|
||||||
app.use(express.static(path.join(__dirname, 'build')));
|
|
||||||
app.get('/*', function (req, res) {
|
|
||||||
|
|
||||||
res.sendFile(path.join(__dirname, 'build', 'index.html'));
|
|
||||||
});
|
|
||||||
|
|
||||||
app.listen(3001, () => console.log('Example app is listening on port 3001.'));
|
|
||||||
@@ -1,23 +0,0 @@
|
|||||||
/* src/App.css */
|
|
||||||
body {
|
|
||||||
margin: 0;
|
|
||||||
padding: 0;
|
|
||||||
overflow: hidden;
|
|
||||||
height: 100vh;
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
opacity: 0;
|
|
||||||
animation: fadeIn 0.5s forwards;
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes fadeIn {
|
|
||||||
from {
|
|
||||||
opacity: 0;
|
|
||||||
}
|
|
||||||
to {
|
|
||||||
opacity: 0.9;
|
|
||||||
background: rgba(0, 0, 0, 0.7);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@@ -1,22 +0,0 @@
|
|||||||
// src/App.tsx
|
|
||||||
import React from "react";
|
|
||||||
import ErrorContainer from "./components/ErrorContainer";
|
|
||||||
import "./App.css";
|
|
||||||
|
|
||||||
declare global {
|
|
||||||
interface Window {
|
|
||||||
electron?: {
|
|
||||||
handleAction: (action: string, isCancel?: boolean) => void;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const App: React.FC = () => {
|
|
||||||
return (
|
|
||||||
<div>
|
|
||||||
<ErrorContainer />
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default App;
|
|
||||||
@@ -1,37 +0,0 @@
|
|||||||
/* src/components/Loader.css */
|
|
||||||
.error {
|
|
||||||
position: relative;
|
|
||||||
}
|
|
||||||
|
|
||||||
.error div {
|
|
||||||
position: absolute;
|
|
||||||
opacity: 1;
|
|
||||||
border-radius: 50%;
|
|
||||||
/* animation: loader-animation 1.5s infinite ease-in-out; */
|
|
||||||
}
|
|
||||||
h1, h2 {
|
|
||||||
color: white;
|
|
||||||
/* animation: loader-animation 1.5s infinite ease-in-out; */
|
|
||||||
}
|
|
||||||
/* .error div:nth-child(2) {
|
|
||||||
animation-delay: -1.2s;
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes error-animation {
|
|
||||||
0%,
|
|
||||||
100% {
|
|
||||||
width: 0;
|
|
||||||
height: 0;
|
|
||||||
top: 50px;
|
|
||||||
left: 50px;
|
|
||||||
opacity: 0.5;
|
|
||||||
}
|
|
||||||
50% {
|
|
||||||
width: 100px;
|
|
||||||
height: 100px;
|
|
||||||
top: 0;
|
|
||||||
left: 0;
|
|
||||||
opacity: 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
@@ -1,32 +0,0 @@
|
|||||||
// src/components/Loader.tsx
|
|
||||||
import React, { useEffect, useState } from "react";
|
|
||||||
import "./Error.css";
|
|
||||||
|
|
||||||
interface ErrorDataProps {
|
|
||||||
title: string;
|
|
||||||
text: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface ErrorProps {
|
|
||||||
errorData: ErrorDataProps;
|
|
||||||
setCustomData: (errorData: ErrorDataProps) => void;
|
|
||||||
}
|
|
||||||
|
|
||||||
const Error: React.FC = () => {
|
|
||||||
const [errorData, setCustomData] = useState<ErrorDataProps>();
|
|
||||||
useEffect(() => {
|
|
||||||
fetch('http://localhost:3001/api/custom-data')
|
|
||||||
.then(response => response.json())
|
|
||||||
.then(errorData => setCustomData(errorData ?? {title: "Uncaught", text: "error"}))
|
|
||||||
.catch(error => console.error('Error fetching custom text:', error));
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="error">
|
|
||||||
<h1>{errorData?.title}</h1>
|
|
||||||
<h2>{errorData?.text}</h2>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default Error;
|
|
||||||
@@ -1,40 +0,0 @@
|
|||||||
/* src/components/LoaderContainer.css */
|
|
||||||
.error-container {
|
|
||||||
position:fixed; /* Fixed position to keep it centered relative to the viewport */
|
|
||||||
top: 50%; /* Center vertically */
|
|
||||||
left: 50%; /* Center horizontally */
|
|
||||||
transform: translate(-50%, -50%); /* Adjust for the element's size */
|
|
||||||
background-color: rgb(98, 98, 98);
|
|
||||||
border: 0px solid black;
|
|
||||||
border-radius: 20px;
|
|
||||||
display: flex;
|
|
||||||
padding: 15px;
|
|
||||||
z-index: 9999; /* Ensure it's on top of other content */
|
|
||||||
min-width: 400px; /* Width of the loader */
|
|
||||||
min-height: 200px; /* Height of the loader */
|
|
||||||
max-height: 600px; /* Height of the loader */
|
|
||||||
}
|
|
||||||
|
|
||||||
.error-button {
|
|
||||||
color: white;
|
|
||||||
font-size: medium;
|
|
||||||
position: absolute;
|
|
||||||
bottom: 40px;
|
|
||||||
right: 40px;
|
|
||||||
background: rgba(0, 255, 0, 0); /* Transparent background */
|
|
||||||
border: none;
|
|
||||||
padding: 10px 20px;
|
|
||||||
font-size: 16px;
|
|
||||||
cursor: pointer;
|
|
||||||
border-radius: 4px;
|
|
||||||
transition: background 0.3s ease;
|
|
||||||
}
|
|
||||||
|
|
||||||
.error-button strong {
|
|
||||||
color: white;
|
|
||||||
}
|
|
||||||
|
|
||||||
.error-button:hover {
|
|
||||||
background: rgb(113, 113, 113); /* Light green with opacity 0.5 */
|
|
||||||
}
|
|
||||||
|
|
||||||
@@ -1,27 +0,0 @@
|
|||||||
// src/components/LoaderContainer.tsx
|
|
||||||
import React, { useEffect, useState } from "react";
|
|
||||||
import Error from "./Error";
|
|
||||||
import "./ErrorContainer.css";
|
|
||||||
|
|
||||||
const ErrorContainer: React.FC = () => {
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="error-container">
|
|
||||||
<Error />
|
|
||||||
|
|
||||||
<button
|
|
||||||
className="error-button"
|
|
||||||
onClick={() =>
|
|
||||||
{
|
|
||||||
window?.electron &&
|
|
||||||
window.electron.handleAction("error");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<strong>OK</strong>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default ErrorContainer;
|
|
||||||
@@ -1,22 +0,0 @@
|
|||||||
// src/index.tsx
|
|
||||||
import React from "react";
|
|
||||||
import ReactDOM from "react-dom/client";
|
|
||||||
import "./index.css";
|
|
||||||
import App from "./App";
|
|
||||||
import { createBrowserRouter, RouterProvider } from "react-router-dom";
|
|
||||||
|
|
||||||
// Get the root element from the DOM
|
|
||||||
const rootElement = document.getElementById("root");
|
|
||||||
|
|
||||||
if (rootElement) {
|
|
||||||
// Create a root and render the app
|
|
||||||
|
|
||||||
const root = ReactDOM.createRoot(rootElement);
|
|
||||||
root.render(
|
|
||||||
<React.StrictMode>
|
|
||||||
<App />
|
|
||||||
</React.StrictMode>
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
console.error("Root element not found");
|
|
||||||
}
|
|
||||||
@@ -1,21 +0,0 @@
|
|||||||
{
|
|
||||||
"compilerOptions": {
|
|
||||||
"target": "es5",
|
|
||||||
"lib": ["dom", "dom.iterable", "esnext"],
|
|
||||||
"allowJs": true,
|
|
||||||
"skipLibCheck": true,
|
|
||||||
"esModuleInterop": true,
|
|
||||||
"allowSyntheticDefaultImports": true,
|
|
||||||
"strict": true,
|
|
||||||
"forceConsistentCasingInFileNames": true,
|
|
||||||
"noFallthroughCasesInSwitch": true,
|
|
||||||
"module": "esnext",
|
|
||||||
"moduleResolution": "node",
|
|
||||||
"resolveJsonModule": true,
|
|
||||||
"isolatedModules": false,
|
|
||||||
"jsx": "react-jsx",
|
|
||||||
"noEmit": true
|
|
||||||
},
|
|
||||||
"include": ["src"]
|
|
||||||
}
|
|
||||||
|
|
||||||
@@ -1,13 +0,0 @@
|
|||||||
{
|
|
||||||
"files": {
|
|
||||||
"main.css": "/static/css/main.4d16bcf2.css",
|
|
||||||
"main.js": "/static/js/main.bc0cdbdb.js",
|
|
||||||
"index.html": "/index.html",
|
|
||||||
"main.4d16bcf2.css.map": "/static/css/main.4d16bcf2.css.map",
|
|
||||||
"main.bc0cdbdb.js.map": "/static/js/main.bc0cdbdb.js.map"
|
|
||||||
},
|
|
||||||
"entrypoints": [
|
|
||||||
"static/css/main.4d16bcf2.css",
|
|
||||||
"static/js/main.bc0cdbdb.js"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
<!doctype html><html lang="en"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>Loader Animation</title><script defer="defer" src="/static/js/main.bc0cdbdb.js"></script><link href="/static/css/main.4d16bcf2.css" rel="stylesheet"></head><body><div id="root"></div></body></html>
|
|
||||||
@@ -1,2 +0,0 @@
|
|||||||
.loader{height:100px;position:relative;width:100px}.loader div{animation:loader-animation 1.5s ease-in-out infinite;border:4px solid gray;border-radius:50%;opacity:1;position:absolute}.loader div:nth-child(2){animation-delay:-1.2s}@keyframes loader-animation{0%,to{height:0;left:50px;opacity:.5;top:50px;width:0}50%{height:100px;left:0;opacity:0;top:0;width:100px}}.loader-container{height:100px;left:50%;position:fixed;top:50%;transform:translate(-50%,-50%);width:100px;z-index:9999}.loader-container,body{align-items:center;display:flex;justify-content:center}body{animation:fadeIn .5s forwards;height:100vh;margin:0;opacity:0;overflow:hidden;padding:0}@keyframes fadeIn{0%{opacity:0}to{background:#000000b3;opacity:.9}}
|
|
||||||
/*# sourceMappingURL=main.4d16bcf2.css.map*/
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
{"version":3,"file":"static/css/main.4d16bcf2.css","mappings":"AACA,QAGI,YAAa,CAFb,iBAAkB,CAClB,WAEF,CAEA,YAKE,oDAAqD,CAHrD,qBAAsB,CAEtB,iBAAkB,CADlB,SAAU,CAFV,iBAKF,CAEA,yBACE,qBACF,CAEA,4BACE,MAGE,QAAS,CAET,SAAU,CACV,UAAY,CAFZ,QAAS,CAFT,OAKF,CACA,IAEE,YAAa,CAEb,MAAO,CACP,SAAU,CAFV,KAAM,CAFN,WAKF,CACF,CClCF,kBAUI,YAAa,CAPb,QAAS,CAFT,cAAe,CACf,OAAQ,CAER,8BAAgC,CAKhC,WAAY,CADZ,YAGF,CCXF,uBDOI,kBAAmB,CAFnB,YAAa,CACb,sBCIF,CAVF,KASI,6BAA+B,CAL/B,YAAa,CAHb,QAAS,CAOT,SAAU,CALV,eAAgB,CADhB,SAQF,CAEA,kBACE,GACE,SACF,CACA,GAEE,oBAA8B,CAD9B,UAEF,CACF","sources":["components/Loader.css","components/LoaderContainer.css","App.css"],"sourcesContent":["/* src/components/Loader.css */\r\n.loader {\r\n position: relative;\r\n width: 100px;\r\n height: 100px;\r\n }\r\n \r\n .loader div {\r\n position: absolute;\r\n border: 4px solid gray;\r\n opacity: 1;\r\n border-radius: 50%;\r\n animation: loader-animation 1.5s infinite ease-in-out;\r\n }\r\n \r\n .loader div:nth-child(2) {\r\n animation-delay: -1.2s;\r\n }\r\n \r\n @keyframes loader-animation {\r\n 0%,\r\n 100% {\r\n width: 0;\r\n height: 0;\r\n top: 50px;\r\n left: 50px;\r\n opacity: 0.5;\r\n }\r\n 50% {\r\n width: 100px;\r\n height: 100px;\r\n top: 0;\r\n left: 0;\r\n opacity: 0;\r\n }\r\n }\r\n ","/* src/components/LoaderContainer.css */\r\n.loader-container {\r\n position: fixed; /* Fixed position to keep it centered relative to the viewport */\r\n top: 50%; /* Center vertically */\r\n left: 50%; /* Center horizontally */\r\n transform: translate(-50%, -50%); /* Adjust for the element's size */\r\n display: flex;\r\n justify-content: center;\r\n align-items: center;\r\n z-index: 9999; /* Ensure it's on top of other content */\r\n width: 100px; /* Width of the loader */\r\n height: 100px; /* Height of the loader */\r\n }\r\n ","/* src/App.css */\r\nbody {\r\n margin: 0;\r\n padding: 0;\r\n overflow: hidden;\r\n height: 100vh;\r\n display: flex;\r\n justify-content: center;\r\n align-items: center;\r\n opacity: 0;\r\n animation: fadeIn 0.5s forwards;\r\n }\r\n \r\n @keyframes fadeIn {\r\n from {\r\n opacity: 0;\r\n }\r\n to {\r\n opacity: 0.9;\r\n background: rgba(0, 0, 0, 0.7);\r\n }\r\n }\r\n "],"names":[],"sourceRoot":""}
|
|
||||||
File diff suppressed because one or more lines are too long
@@ -1,39 +0,0 @@
|
|||||||
/**
|
|
||||||
* @license React
|
|
||||||
* react-dom.production.min.js
|
|
||||||
*
|
|
||||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
|
||||||
*
|
|
||||||
* This source code is licensed under the MIT license found in the
|
|
||||||
* LICENSE file in the root directory of this source tree.
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @license React
|
|
||||||
* react-jsx-runtime.production.min.js
|
|
||||||
*
|
|
||||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
|
||||||
*
|
|
||||||
* This source code is licensed under the MIT license found in the
|
|
||||||
* LICENSE file in the root directory of this source tree.
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @license React
|
|
||||||
* react.production.min.js
|
|
||||||
*
|
|
||||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
|
||||||
*
|
|
||||||
* This source code is licensed under the MIT license found in the
|
|
||||||
* LICENSE file in the root directory of this source tree.
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @license React
|
|
||||||
* scheduler.production.min.js
|
|
||||||
*
|
|
||||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
|
||||||
*
|
|
||||||
* This source code is licensed under the MIT license found in the
|
|
||||||
* LICENSE file in the root directory of this source tree.
|
|
||||||
*/
|
|
||||||
File diff suppressed because one or more lines are too long
@@ -1,101 +0,0 @@
|
|||||||
<html lang="en" class="focus-outline-visible">
|
|
||||||
|
|
||||||
<head>
|
|
||||||
<meta charset="UTF-8">
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
||||||
<title>Loader Animation</title>
|
|
||||||
<style>
|
|
||||||
body {
|
|
||||||
margin: 0;
|
|
||||||
padding: 0;
|
|
||||||
overflow: hidden;
|
|
||||||
height: 100vh;
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
opacity: 0;
|
|
||||||
animation: fadeIn 0.5s forwards;
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes fadeIn {
|
|
||||||
from {
|
|
||||||
opacity: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
to {
|
|
||||||
opacity: 0.9;
|
|
||||||
background: rgba(0, 0, 0, 0.7);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.loader-container {
|
|
||||||
position: absolute;
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.loader {
|
|
||||||
position: relative;
|
|
||||||
width: 100px;
|
|
||||||
height: 100px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.loader div {
|
|
||||||
position: absolute;
|
|
||||||
border: 4px solid gray;
|
|
||||||
opacity: 1;
|
|
||||||
border-radius: 50%;
|
|
||||||
animation: loader-animation 1.5s infinite ease-in-out;
|
|
||||||
}
|
|
||||||
|
|
||||||
.loader div:nth-child(2) {
|
|
||||||
animation-delay: -1.2s;
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes loader-animation {
|
|
||||||
|
|
||||||
0%,
|
|
||||||
100% {
|
|
||||||
width: 0;
|
|
||||||
height: 0;
|
|
||||||
top: 50px;
|
|
||||||
left: 50px;
|
|
||||||
opacity: 0.5;
|
|
||||||
}
|
|
||||||
|
|
||||||
50% {
|
|
||||||
width: 100px;
|
|
||||||
height: 100px;
|
|
||||||
top: 0;
|
|
||||||
left: 0;
|
|
||||||
opacity: 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
</head>
|
|
||||||
|
|
||||||
<body>
|
|
||||||
<div class="loader-container">
|
|
||||||
<div class="loader">
|
|
||||||
<div></div>
|
|
||||||
<div></div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
|
|
||||||
<div class="loader-container">
|
|
||||||
<div class="loader">
|
|
||||||
<div></div>
|
|
||||||
<div></div>
|
|
||||||
</div>
|
|
||||||
<div class="loader">
|
|
||||||
<div></div>
|
|
||||||
<div></div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</body>
|
|
||||||
|
|
||||||
</html>
|
|
||||||
@@ -1,41 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "loader",
|
|
||||||
"version": "1.0.0",
|
|
||||||
"private": true,
|
|
||||||
"proxy": "http://localhost:3001/",
|
|
||||||
"dependencies": {
|
|
||||||
"react": "^18.0.0",
|
|
||||||
"react-dom": "^18.0.0",
|
|
||||||
"react-scripts": "5.0.1"
|
|
||||||
},
|
|
||||||
"scripts": {
|
|
||||||
"d": "react-scripts build && react-scripts start",
|
|
||||||
"start": "react-scripts start",
|
|
||||||
"build": "react-scripts build",
|
|
||||||
"test": "react-scripts test",
|
|
||||||
"eject": "react-scripts eject",
|
|
||||||
"serv": "node ./serv.js"
|
|
||||||
},
|
|
||||||
"eslintConfig": {
|
|
||||||
"extends": [
|
|
||||||
"react-app",
|
|
||||||
"react-app/jest"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"browserslist": {
|
|
||||||
"production": [
|
|
||||||
">0.2%",
|
|
||||||
"not dead",
|
|
||||||
"not op_mini all"
|
|
||||||
],
|
|
||||||
"development": [
|
|
||||||
"last 1 chrome version",
|
|
||||||
"last 1 firefox version",
|
|
||||||
"last 1 safari version"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"main": "index.js",
|
|
||||||
"author": "",
|
|
||||||
"license": "ISC",
|
|
||||||
"description": ""
|
|
||||||
}
|
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html lang="en">
|
|
||||||
<head>
|
|
||||||
<meta charset="UTF-8">
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
||||||
<title>Loader Animation</title>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<!-- This is where your React app will be injected -->
|
|
||||||
<div id="root"></div>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
@@ -1,16 +0,0 @@
|
|||||||
const express = require('express');
|
|
||||||
const path = require('path');
|
|
||||||
|
|
||||||
// const __filename = fileURLToPath(import.meta.url);
|
|
||||||
// const __dirname = dirname(__filename);
|
|
||||||
|
|
||||||
const app = express();
|
|
||||||
app.use(express.static(path.join(__dirname, 'build')));
|
|
||||||
app.use(express.static(path.join(__dirname, 'build')));
|
|
||||||
app.use(express.static(path.join(__dirname, 'build')));
|
|
||||||
app.get('/*', function (req, res) {
|
|
||||||
|
|
||||||
res.sendFile(path.join(__dirname, 'build', 'index.html'));
|
|
||||||
});
|
|
||||||
|
|
||||||
app.listen(3001, () => console.log('Example app is listening on port 3001.'));
|
|
||||||
@@ -1,23 +0,0 @@
|
|||||||
/* src/App.css */
|
|
||||||
body {
|
|
||||||
margin: 0;
|
|
||||||
padding: 0;
|
|
||||||
overflow: hidden;
|
|
||||||
height: 100vh;
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
opacity: 0;
|
|
||||||
animation: fadeIn 0.5s forwards;
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes fadeIn {
|
|
||||||
from {
|
|
||||||
opacity: 0;
|
|
||||||
}
|
|
||||||
to {
|
|
||||||
opacity: 0.9;
|
|
||||||
background: rgba(0, 0, 0, 0.7);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@@ -1,22 +0,0 @@
|
|||||||
// src/App.tsx
|
|
||||||
import React from "react";
|
|
||||||
import LoaderContainer from "./components/LoaderContainer";
|
|
||||||
import "./App.css";
|
|
||||||
|
|
||||||
declare global {
|
|
||||||
interface Window {
|
|
||||||
electron?: {
|
|
||||||
handleAction: (action: string, isCancel?: boolean) => void;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const App: React.FC = () => {
|
|
||||||
return (
|
|
||||||
<div>
|
|
||||||
<LoaderContainer />
|
|
||||||
<LoaderContainer />
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
export default App;
|
|
||||||
@@ -1,37 +0,0 @@
|
|||||||
/* src/components/Loader.css */
|
|
||||||
.loader {
|
|
||||||
position: relative;
|
|
||||||
width: 100px;
|
|
||||||
height: 100px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.loader div {
|
|
||||||
position: absolute;
|
|
||||||
border: 4px solid gray;
|
|
||||||
opacity: 1;
|
|
||||||
border-radius: 50%;
|
|
||||||
animation: loader-animation 1.5s infinite ease-in-out;
|
|
||||||
}
|
|
||||||
|
|
||||||
.loader div:nth-child(2) {
|
|
||||||
animation-delay: -1.2s;
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes loader-animation {
|
|
||||||
0%,
|
|
||||||
100% {
|
|
||||||
width: 0;
|
|
||||||
height: 0;
|
|
||||||
top: 50px;
|
|
||||||
left: 50px;
|
|
||||||
opacity: 0.5;
|
|
||||||
}
|
|
||||||
50% {
|
|
||||||
width: 100px;
|
|
||||||
height: 100px;
|
|
||||||
top: 0;
|
|
||||||
left: 0;
|
|
||||||
opacity: 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
// src/components/Loader.tsx
|
|
||||||
import React from "react";
|
|
||||||
import "./Loader.css";
|
|
||||||
|
|
||||||
const Loader: React.FC = () => {
|
|
||||||
return (
|
|
||||||
<div className="loader">
|
|
||||||
<div></div>
|
|
||||||
<div></div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default Loader;
|
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
/* src/components/LoaderContainer.css */
|
|
||||||
.loader-container {
|
|
||||||
position: fixed; /* Fixed position to keep it centered relative to the viewport */
|
|
||||||
top: 50%; /* Center vertically */
|
|
||||||
left: 50%; /* Center horizontally */
|
|
||||||
transform: translate(-50%, -50%); /* Adjust for the element's size */
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
z-index: 9999; /* Ensure it's on top of other content */
|
|
||||||
width: 100px; /* Width of the loader */
|
|
||||||
height: 100px; /* Height of the loader */
|
|
||||||
}
|
|
||||||
|
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
// src/components/LoaderContainer.tsx
|
|
||||||
import React from "react";
|
|
||||||
import Loader from "./Loader";
|
|
||||||
import "./LoaderContainer.css";
|
|
||||||
|
|
||||||
const LoaderContainer: React.FC = () => {
|
|
||||||
return (
|
|
||||||
<div className="loader-container">
|
|
||||||
<Loader />
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default LoaderContainer;
|
|
||||||
@@ -1,22 +0,0 @@
|
|||||||
// src/index.tsx
|
|
||||||
import React from "react";
|
|
||||||
import ReactDOM from "react-dom/client";
|
|
||||||
import "./index.css";
|
|
||||||
import App from "./App";
|
|
||||||
import { createBrowserRouter, RouterProvider } from "react-router-dom";
|
|
||||||
|
|
||||||
// Get the root element from the DOM
|
|
||||||
const rootElement = document.getElementById("root");
|
|
||||||
|
|
||||||
if (rootElement) {
|
|
||||||
// Create a root and render the app
|
|
||||||
|
|
||||||
const root = ReactDOM.createRoot(rootElement);
|
|
||||||
root.render(
|
|
||||||
<React.StrictMode>
|
|
||||||
<App />
|
|
||||||
</React.StrictMode>
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
console.error("Root element not found");
|
|
||||||
}
|
|
||||||
@@ -1,21 +0,0 @@
|
|||||||
{
|
|
||||||
"compilerOptions": {
|
|
||||||
"target": "es5",
|
|
||||||
"lib": ["dom", "dom.iterable", "esnext"],
|
|
||||||
"allowJs": true,
|
|
||||||
"skipLibCheck": true,
|
|
||||||
"esModuleInterop": true,
|
|
||||||
"allowSyntheticDefaultImports": true,
|
|
||||||
"strict": true,
|
|
||||||
"forceConsistentCasingInFileNames": true,
|
|
||||||
"noFallthroughCasesInSwitch": true,
|
|
||||||
"module": "esnext",
|
|
||||||
"moduleResolution": "node",
|
|
||||||
"resolveJsonModule": true,
|
|
||||||
"isolatedModules": false,
|
|
||||||
"jsx": "react-jsx",
|
|
||||||
"noEmit": true
|
|
||||||
},
|
|
||||||
"include": ["src"]
|
|
||||||
}
|
|
||||||
|
|
||||||
12
sidebar.html
Normal file
12
sidebar.html
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="ru">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
<title>Sidebar</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="root"></div>
|
||||||
|
<script type="module" src="/src/entries/sidebar.tsx"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@@ -105,6 +105,16 @@ const Header: React.FC<HeaderProps> = ({ activeApp, setActiveApp, onAppsChange,
|
|||||||
const appOpen = activeApp !== 'home' && activeApp !== 'movie-search'
|
const appOpen = activeApp !== 'home' && activeApp !== 'movie-search'
|
||||||
const showSearchIcon = activeApp === 'home' || activeApp === 'movie-search'
|
const showSearchIcon = activeApp === 'home' || activeApp === 'movie-search'
|
||||||
|
|
||||||
|
const [isKiosk, setIsKiosk] = useState(true)
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
window.electron?.isKiosk().then(k => setIsKiosk(k))
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
const toggleKiosk = () => {
|
||||||
|
window.electron?.toggleKiosk().then((newState: boolean) => setIsKiosk(newState))
|
||||||
|
}
|
||||||
|
|
||||||
const [isBookmarked, setIsBookmarked] = useState(false)
|
const [isBookmarked, setIsBookmarked] = useState(false)
|
||||||
|
|
||||||
const handleBookmark = () => {
|
const handleBookmark = () => {
|
||||||
@@ -147,6 +157,25 @@ const Header: React.FC<HeaderProps> = ({ activeApp, setActiveApp, onAppsChange,
|
|||||||
{(!isCollapsed || isHovered) && (
|
{(!isCollapsed || isHovered) && (
|
||||||
<>
|
<>
|
||||||
<div className="header-left">
|
<div className="header-left">
|
||||||
|
<div className="header-btn" onClick={toggleKiosk} title={isKiosk ? 'Выйти из режима киоска' : 'Режим киоска'}>
|
||||||
|
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="#aaa" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
|
||||||
|
{isKiosk ? (
|
||||||
|
<>
|
||||||
|
<polyline points="4 14 10 14 10 20" />
|
||||||
|
<polyline points="20 10 14 10 14 4" />
|
||||||
|
<line x1="10" y1="14" x2="3" y2="21" />
|
||||||
|
<line x1="21" y1="3" x2="14" y2="10" />
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<polyline points="15 3 21 3 21 9" />
|
||||||
|
<polyline points="9 21 3 21 3 15" />
|
||||||
|
<line x1="21" y1="3" x2="14" y2="10" />
|
||||||
|
<line x1="3" y1="21" x2="10" y2="14" />
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
<div className="header-btn" onClick={openSettings} title="Настройки">
|
<div className="header-btn" onClick={openSettings} title="Настройки">
|
||||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#aaa" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
|
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#aaa" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
|
||||||
<circle cx="12" cy="12" r="3" />
|
<circle cx="12" cy="12" r="3" />
|
||||||
|
|||||||
@@ -129,6 +129,32 @@ const StarIcon = () => (
|
|||||||
</svg>
|
</svg>
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const MovieCard: React.FC<{ movie: TmdbMovie; idx: number; baseIdx: number; onSelect: (m: TmdbMovie) => void }> = ({ movie, idx, baseIdx, onSelect }) => (
|
||||||
|
<div
|
||||||
|
className="movie-result-card"
|
||||||
|
style={{ animationDelay: `${Math.max(0, idx - baseIdx) * 30}ms` }}
|
||||||
|
onClick={() => onSelect(movie)}
|
||||||
|
>
|
||||||
|
<div className="movie-result-poster">
|
||||||
|
{movie.poster
|
||||||
|
? <img src={movie.poster} alt={movie.title} onError={e => { (e.target as HTMLImageElement).style.display = 'none'; (e.target as HTMLImageElement).nextElementSibling?.removeAttribute('style') }} />
|
||||||
|
: null
|
||||||
|
}
|
||||||
|
<div className="movie-result-poster-placeholder" style={movie.poster ? { display: 'none' } : undefined}>
|
||||||
|
{movie.title.charAt(0).toUpperCase()}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="movie-result-info">
|
||||||
|
<span className="movie-result-title">{movie.title}</span>
|
||||||
|
<div className="ms-card-meta">
|
||||||
|
{movie.year && <span className="movie-result-year">{movie.year}</span>}
|
||||||
|
{movie.rating && <span className="ms-card-rating"><StarIcon /> {movie.rating}</span>}
|
||||||
|
</div>
|
||||||
|
{movie.mediaType === 'tv' && <span className="ms-card-type">Сериал</span>}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
|
||||||
const MovieSearch: React.FC<MovieSearchProps> = ({ onOpenUrl, onBookmark, initialQuery = '' }) => {
|
const MovieSearch: React.FC<MovieSearchProps> = ({ onOpenUrl, onBookmark, initialQuery = '' }) => {
|
||||||
const [query, setQuery] = useState(initialQuery)
|
const [query, setQuery] = useState(initialQuery)
|
||||||
const [mediaType, setMediaType] = useState<'movie' | 'tv'>('movie')
|
const [mediaType, setMediaType] = useState<'movie' | 'tv'>('movie')
|
||||||
@@ -147,6 +173,7 @@ const MovieSearch: React.FC<MovieSearchProps> = ({ onOpenUrl, onBookmark, initia
|
|||||||
const [tmdbLoading, setTmdbLoading] = useState(false)
|
const [tmdbLoading, setTmdbLoading] = useState(false)
|
||||||
const [sitesLoading, setSitesLoading] = useState(false)
|
const [sitesLoading, setSitesLoading] = useState(false)
|
||||||
const [loadingMore, setLoadingMore] = useState(false)
|
const [loadingMore, setLoadingMore] = useState(false)
|
||||||
|
const [cardBase, setCardBase] = useState(0)
|
||||||
const [message, setMessage] = useState('')
|
const [message, setMessage] = useState('')
|
||||||
const [apiKey, setApiKey] = useState('')
|
const [apiKey, setApiKey] = useState('')
|
||||||
const [sites, setSites] = useState<MovieSite[]>([])
|
const [sites, setSites] = useState<MovieSite[]>([])
|
||||||
@@ -295,6 +322,7 @@ const MovieSearch: React.FC<MovieSearchProps> = ({ onOpenUrl, onBookmark, initia
|
|||||||
const handleLoadMore = () => {
|
const handleLoadMore = () => {
|
||||||
const nextPage = page + 1
|
const nextPage = page + 1
|
||||||
setPage(nextPage)
|
setPage(nextPage)
|
||||||
|
setCardBase(tmdbResults.length)
|
||||||
doDiscover(apiKey, nextPage, true)
|
doDiscover(apiKey, nextPage, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -313,31 +341,6 @@ const MovieSearch: React.FC<MovieSearchProps> = ({ onOpenUrl, onBookmark, initia
|
|||||||
|
|
||||||
const loading = tmdbLoading || sitesLoading
|
const loading = tmdbLoading || sitesLoading
|
||||||
|
|
||||||
const MovieCard = ({ movie, idx }: { movie: TmdbMovie; idx: number }) => (
|
|
||||||
<div
|
|
||||||
className="movie-result-card"
|
|
||||||
style={{ animationDelay: `${idx * 30}ms` }}
|
|
||||||
onClick={() => handleSelectMovie(movie)}
|
|
||||||
>
|
|
||||||
<div className="movie-result-poster">
|
|
||||||
{movie.poster
|
|
||||||
? <img src={movie.poster} alt={movie.title} onError={e => { (e.target as HTMLImageElement).style.display = 'none'; (e.target as HTMLImageElement).nextElementSibling?.removeAttribute('style') }} />
|
|
||||||
: null
|
|
||||||
}
|
|
||||||
<div className="movie-result-poster-placeholder" style={movie.poster ? { display: 'none' } : undefined}>
|
|
||||||
{movie.title.charAt(0).toUpperCase()}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className="movie-result-info">
|
|
||||||
<span className="movie-result-title">{movie.title}</span>
|
|
||||||
<div className="ms-card-meta">
|
|
||||||
{movie.year && <span className="movie-result-year">{movie.year}</span>}
|
|
||||||
{movie.rating && <span className="ms-card-rating"><StarIcon /> {movie.rating}</span>}
|
|
||||||
</div>
|
|
||||||
{movie.mediaType === 'tv' && <span className="ms-card-type">Сериал</span>}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="movie-search">
|
<div className="movie-search">
|
||||||
@@ -495,7 +498,7 @@ const MovieSearch: React.FC<MovieSearchProps> = ({ onOpenUrl, onBookmark, initia
|
|||||||
{tmdbResults.length > 0 && (
|
{tmdbResults.length > 0 && (
|
||||||
<>
|
<>
|
||||||
<div className="movie-results">
|
<div className="movie-results">
|
||||||
{tmdbResults.map((movie, i) => <MovieCard key={`${movie.id}-${i}`} movie={movie} idx={i} />)}
|
{tmdbResults.map((movie, i) => <MovieCard key={movie.id} movie={movie} idx={i} baseIdx={cardBase} onSelect={handleSelectMovie} />)}
|
||||||
</div>
|
</div>
|
||||||
{!isSearchMode && page < totalPages && (
|
{!isSearchMode && page < totalPages && (
|
||||||
<button className="ms-load-more" onClick={handleLoadMore} disabled={loadingMore}>
|
<button className="ms-load-more" onClick={handleLoadMore} disabled={loadingMore}>
|
||||||
|
|||||||
40
src/entries/dialog-confirm.tsx
Normal file
40
src/entries/dialog-confirm.tsx
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
import React, { useEffect, useState } from 'react'
|
||||||
|
import ReactDOM from 'react-dom/client'
|
||||||
|
import '../styles/dialogs.css'
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
interface Window {
|
||||||
|
electron?: { handleAction: (action: string) => void }
|
||||||
|
__dialogData?: { text?: string }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const ConfirmDialog = () => {
|
||||||
|
const [visible, setVisible] = useState(false)
|
||||||
|
const params = new URLSearchParams(window.location.search)
|
||||||
|
const text = params.get('text') || window.__dialogData?.text || ''
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
requestAnimationFrame(() => requestAnimationFrame(() => setVisible(true)))
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (visible) document.body.classList.add('visible')
|
||||||
|
}, [visible])
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="card">
|
||||||
|
{text && <div className="msg">{text}</div>}
|
||||||
|
<div className="btns">
|
||||||
|
<button className="btn-yes" onClick={() => window.electron?.handleAction('confirmYes')}>
|
||||||
|
Да
|
||||||
|
</button>
|
||||||
|
<button className="btn-no" onClick={() => window.electron?.handleAction('confirmNo')}>
|
||||||
|
Нет
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
ReactDOM.createRoot(document.getElementById('root')!).render(<ConfirmDialog />)
|
||||||
39
src/entries/dialog-error.tsx
Normal file
39
src/entries/dialog-error.tsx
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
import React, { useEffect, useState } from 'react'
|
||||||
|
import ReactDOM from 'react-dom/client'
|
||||||
|
import '../styles/dialogs.css'
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
interface Window {
|
||||||
|
electron?: { handleAction: (action: string) => void }
|
||||||
|
__dialogData?: { title?: string; text?: string }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const ErrorDialog = () => {
|
||||||
|
const [visible, setVisible] = useState(false)
|
||||||
|
const params = new URLSearchParams(window.location.search)
|
||||||
|
const title = params.get('title') || window.__dialogData?.title || 'Ошибка'
|
||||||
|
const text = params.get('text') || window.__dialogData?.text || ''
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
requestAnimationFrame(() => requestAnimationFrame(() => setVisible(true)))
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (visible) document.body.classList.add('visible')
|
||||||
|
}, [visible])
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="card">
|
||||||
|
{title && <div className="title">{title}</div>}
|
||||||
|
{text && <div className="msg">{text}</div>}
|
||||||
|
<div className="btns">
|
||||||
|
<button className="btn-ok" onClick={() => window.electron?.handleAction('error')}>
|
||||||
|
Закрыть
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
ReactDOM.createRoot(document.getElementById('root')!).render(<ErrorDialog />)
|
||||||
40
src/entries/loader.tsx
Normal file
40
src/entries/loader.tsx
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
import React, { useEffect } from 'react'
|
||||||
|
import ReactDOM from 'react-dom/client'
|
||||||
|
|
||||||
|
const css = `
|
||||||
|
* { margin: 0; padding: 0; box-sizing: border-box; }
|
||||||
|
body {
|
||||||
|
background: #111;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
height: 100vh;
|
||||||
|
transition: opacity 0.25s ease;
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
body.visible { opacity: 1; }
|
||||||
|
.spinner {
|
||||||
|
width: 36px;
|
||||||
|
height: 36px;
|
||||||
|
border: 3px solid rgba(255,255,255,0.1);
|
||||||
|
border-top-color: #E50914;
|
||||||
|
border-radius: 50%;
|
||||||
|
animation: spin 0.7s linear infinite;
|
||||||
|
}
|
||||||
|
@keyframes spin { to { transform: rotate(360deg); } }
|
||||||
|
`
|
||||||
|
|
||||||
|
const Loader = () => {
|
||||||
|
useEffect(() => {
|
||||||
|
const style = document.createElement('style')
|
||||||
|
style.textContent = css
|
||||||
|
document.head.appendChild(style)
|
||||||
|
requestAnimationFrame(() => requestAnimationFrame(() => {
|
||||||
|
document.body.classList.add('visible')
|
||||||
|
}))
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
return <div className="spinner" />
|
||||||
|
}
|
||||||
|
|
||||||
|
ReactDOM.createRoot(document.getElementById('root')!).render(<Loader />)
|
||||||
99
src/entries/sidebar.tsx
Normal file
99
src/entries/sidebar.tsx
Normal file
@@ -0,0 +1,99 @@
|
|||||||
|
import React, { useState, useEffect, useRef } from 'react'
|
||||||
|
import ReactDOM from 'react-dom/client'
|
||||||
|
import '../styles/main.css'
|
||||||
|
|
||||||
|
interface OpenedApp {
|
||||||
|
name: string
|
||||||
|
imageUrl: string
|
||||||
|
url: string
|
||||||
|
}
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
interface Window {
|
||||||
|
electron?: {
|
||||||
|
on: (channel: string, fn: (...args: any[]) => void) => () => void
|
||||||
|
hideView: () => void
|
||||||
|
showView: (name: string) => void
|
||||||
|
adjustView: (expanded: boolean) => void
|
||||||
|
getSidebarState: () => Promise<{ openedApps: OpenedApp[]; activeApp: string }>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const SidebarApp = () => {
|
||||||
|
const [openedApps, setOpenedApps] = useState<OpenedApp[]>([])
|
||||||
|
const [activeApp, setActiveApp] = useState('home')
|
||||||
|
const [expanded, setExpanded] = useState(false)
|
||||||
|
const timeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null)
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
window.electron?.getSidebarState().then(data => {
|
||||||
|
if (!data) return
|
||||||
|
setOpenedApps(data.openedApps || [])
|
||||||
|
setActiveApp(data.activeApp || 'home')
|
||||||
|
})
|
||||||
|
const off = window.electron?.on('sidebar-update', (data: { openedApps: OpenedApp[]; activeApp: string }) => {
|
||||||
|
setOpenedApps(data.openedApps || [])
|
||||||
|
setActiveApp(data.activeApp || 'home')
|
||||||
|
})
|
||||||
|
return () => off?.()
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
const handleMouseEnter = () => {
|
||||||
|
if (timeoutRef.current) clearTimeout(timeoutRef.current)
|
||||||
|
timeoutRef.current = setTimeout(() => {
|
||||||
|
setExpanded(true)
|
||||||
|
window.electron?.adjustView(true)
|
||||||
|
}, 150)
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleMouseLeave = () => {
|
||||||
|
if (timeoutRef.current) clearTimeout(timeoutRef.current)
|
||||||
|
timeoutRef.current = setTimeout(() => {
|
||||||
|
setExpanded(false)
|
||||||
|
window.electron?.adjustView(false)
|
||||||
|
}, 150)
|
||||||
|
}
|
||||||
|
|
||||||
|
const goHome = () => {
|
||||||
|
window.electron?.hideView()
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className={`sidebar ${expanded ? 'expanded' : 'collapsed'}`}
|
||||||
|
onMouseEnter={handleMouseEnter}
|
||||||
|
onMouseLeave={handleMouseLeave}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className={`sidebar-item ${activeApp === 'home' ? 'active' : ''}`}
|
||||||
|
onClick={goHome}
|
||||||
|
>
|
||||||
|
<svg className="sidebar-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
|
||||||
|
<path d="M3 9l9-7 9 7v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z" />
|
||||||
|
<polyline points="9 22 9 12 15 12 15 22" />
|
||||||
|
</svg>
|
||||||
|
<span>Домой</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{openedApps.map(app => (
|
||||||
|
<div
|
||||||
|
key={app.name}
|
||||||
|
className={`sidebar-item ${activeApp === app.name ? 'active' : ''}`}
|
||||||
|
onClick={() => window.electron?.showView(app.name)}
|
||||||
|
>
|
||||||
|
{app.imageUrl ? (
|
||||||
|
<img src={app.imageUrl} alt={app.name} className="sidebar-app-icon" />
|
||||||
|
) : (
|
||||||
|
<div className="sidebar-app-icon sidebar-app-icon-placeholder">
|
||||||
|
{app.name.charAt(0).toUpperCase()}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
<span>{app.name}</span>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
ReactDOM.createRoot(document.getElementById('root')!).render(<SidebarApp />)
|
||||||
@@ -25,6 +25,8 @@ declare global {
|
|||||||
searchMovies: (query: string, sites: any[]) => Promise<any[]>
|
searchMovies: (query: string, sites: any[]) => Promise<any[]>
|
||||||
searchTmdb: (query: string, apiKey: string) => Promise<{ results: any[]; error?: string }>
|
searchTmdb: (query: string, apiKey: string) => Promise<{ results: any[]; error?: string }>
|
||||||
discoverTmdb: (params: any) => Promise<{ results: any[]; totalPages: number; error?: string }>
|
discoverTmdb: (params: any) => Promise<{ results: any[]; totalPages: number; error?: string }>
|
||||||
|
toggleKiosk: () => void
|
||||||
|
isKiosk: () => Promise<boolean>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
52
src/styles/dialogs.css
Normal file
52
src/styles/dialogs.css
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
* { margin: 0; padding: 0; box-sizing: border-box; }
|
||||||
|
|
||||||
|
body {
|
||||||
|
background: rgba(0, 0, 0, 0);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
height: 100vh;
|
||||||
|
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
|
||||||
|
transition: background 0.22s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
body.visible { background: rgba(0, 0, 0, 0.78); }
|
||||||
|
body.hiding { background: rgba(0, 0, 0, 0); }
|
||||||
|
|
||||||
|
.card {
|
||||||
|
background: #1c1c1c;
|
||||||
|
border: 1px solid rgba(255, 255, 255, 0.1);
|
||||||
|
border-radius: 12px;
|
||||||
|
padding: 32px 36px;
|
||||||
|
text-align: center;
|
||||||
|
min-width: 300px;
|
||||||
|
max-width: 420px;
|
||||||
|
box-shadow: 0 24px 64px rgba(0, 0, 0, 0.8);
|
||||||
|
opacity: 0;
|
||||||
|
transform: scale(0.92) translateY(10px);
|
||||||
|
transition: opacity 0.22s ease, transform 0.22s cubic-bezier(0.34, 1.56, 0.64, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
body.visible .card { opacity: 1; transform: scale(1) translateY(0); }
|
||||||
|
body.hiding .card { opacity: 0; transform: scale(0.95) translateY(6px); }
|
||||||
|
|
||||||
|
.title { font-size: 17px; font-weight: 700; color: #fff; margin-bottom: 10px; }
|
||||||
|
.msg { font-size: 13px; color: #999; line-height: 1.5; margin-bottom: 26px; }
|
||||||
|
|
||||||
|
.btns { display: flex; gap: 10px; justify-content: center; }
|
||||||
|
|
||||||
|
button {
|
||||||
|
padding: 10px 26px;
|
||||||
|
border: none;
|
||||||
|
border-radius: 7px;
|
||||||
|
font-size: 13px;
|
||||||
|
font-weight: 600;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: opacity 0.15s, transform 0.1s;
|
||||||
|
}
|
||||||
|
button:hover { opacity: 0.85; }
|
||||||
|
button:active { transform: scale(0.97); }
|
||||||
|
|
||||||
|
.btn-yes { background: #E50914; color: #fff; }
|
||||||
|
.btn-no,
|
||||||
|
.btn-ok { background: rgba(255, 255, 255, 0.1); color: #ccc; }
|
||||||
@@ -1,19 +1,29 @@
|
|||||||
import { defineConfig } from 'vite'
|
import { defineConfig } from 'vite'
|
||||||
import react from '@vitejs/plugin-react'
|
import react from '@vitejs/plugin-react'
|
||||||
|
import { resolve } from 'path'
|
||||||
|
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
plugins: [react()],
|
plugins: [react()],
|
||||||
base: './',
|
base: './',
|
||||||
build: {
|
build: {
|
||||||
outDir: 'dist',
|
outDir: 'dist',
|
||||||
|
rollupOptions: {
|
||||||
|
input: {
|
||||||
|
main: resolve(__dirname, 'index.html'),
|
||||||
|
loader: resolve(__dirname, 'loader.html'),
|
||||||
|
'dialog-error': resolve(__dirname, 'dialog-error.html'),
|
||||||
|
'dialog-confirm': resolve(__dirname, 'dialog-confirm.html'),
|
||||||
|
sidebar: resolve(__dirname, 'sidebar.html'),
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
server: {
|
server: {
|
||||||
port: 5173,
|
port: 5173,
|
||||||
watch: {
|
watch: {
|
||||||
ignored: ['**/extensions/**', '**/node_modules/**'],
|
ignored: ['**/extensions/**', '**/node_modules/**', '**/release/**', '**/dist/**'],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
optimizeDeps: {
|
optimizeDeps: {
|
||||||
entries: ['index.html', 'src/**/*.tsx', 'src/**/*.ts'],
|
entries: ['index.html', 'loader.html', 'dialog-error.html', 'dialog-confirm.html', 'src/**/*.tsx', 'src/**/*.ts'],
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|||||||
Reference in New Issue
Block a user