mirror of
https://github.com/MasterAcnolo/Freedom-Loader.git
synced 2026-09-27 20:11:15 +02:00
Compare commits
6 Commits
main
...
2c0f5b315b
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2c0f5b315b | ||
|
|
58b6637eeb | ||
|
|
8af5a4a383 | ||
|
|
23f04bafc3 | ||
|
|
35ffd7077e | ||
|
|
408df0e2f6 |
2
.env.example
Normal file
2
.env.example
Normal file
@@ -0,0 +1,2 @@
|
||||
BUG_REPORT_URL=
|
||||
BUG_REPORT_API_KEY=
|
||||
3
.gitignore
vendored
3
.gitignore
vendored
@@ -6,6 +6,9 @@
|
||||
/out
|
||||
/srpm-out
|
||||
|
||||
# .env
|
||||
*.env
|
||||
|
||||
# Config JSON File. Create it and fill it if you want to have dev only settings
|
||||
config/config.dev.json
|
||||
|
||||
|
||||
22
README.md
22
README.md
@@ -250,7 +250,7 @@ Freedom Loader can be configured either through the settings panel in the UI or
|
||||
| Option | Type | Default | Description |
|
||||
|------------------------------|---------|----------|------------------------------------------------------------|
|
||||
| `autoUpdate` | boolean | `true` | Enable automatic application updates |
|
||||
| `systemTray` | boolean | `false` | Allow app to minimize on close [EXPERIMENTAL] |
|
||||
| `systemTray` | boolean | `false` | Allow app to minimize on close [EXPERIMENTAL] |
|
||||
| `discordRPC` | boolean | `true` | Enable Discord Rich Presence integration |
|
||||
| `customTopBar` | boolean | `true` | Use custom application top bar |
|
||||
| `autoCheckInfo` | boolean | `true` | Automatically fetch video information on URL paste |
|
||||
@@ -367,13 +367,29 @@ Head over to the [Freedom Loader Workshop repository](https://github.com/MasterA
|
||||
|
||||
### Bug Reports
|
||||
|
||||
Use the GitHub Issues system and include:
|
||||
You can report bugs either using the built-in application system or via the GitHub Issues system.
|
||||
|
||||
#### Using the Built-in Report Tool
|
||||
|
||||
1. Open Freedom Loader.
|
||||
2. Trigger the bug report interface.
|
||||
3. Fill in the title and description of the issue.
|
||||
4. Choose whether to include today's logs automatically.
|
||||
5. Submit the report.
|
||||
|
||||
> [!NOTE]
|
||||
> If you need to attach extra context like screenshots or screen recordings, it is recommended to use the GitHub Issues
|
||||
system instead.
|
||||
|
||||
#### Using GitHub Issues
|
||||
|
||||
Open a new issue on the GitHub repository and include:
|
||||
|
||||
- Clear description of the issue
|
||||
- Steps to reproduce
|
||||
- Expected vs actual behavior
|
||||
- Relevant logs from `AppData\Local\FreedomLoader\logs\` or `~/.local/share/FreedomLoader/logs/`
|
||||
- Screenshots if applicable
|
||||
- Screenshots or recordings if applicable
|
||||
|
||||
### Feature Requests
|
||||
|
||||
|
||||
@@ -97,7 +97,7 @@ function startRPC() {
|
||||
details: `Open Source Download Tools - ${config.version}`,
|
||||
state: "masteracnolo.github.io/FreedomLoader",
|
||||
};
|
||||
rpc.clearActivity()
|
||||
rpc.clearActivity();
|
||||
rpc.setActivity(presence);
|
||||
|
||||
});
|
||||
|
||||
@@ -19,6 +19,7 @@ const config = require("../config");
|
||||
const { validateDownloadPath, getDefaultDownloadPath } = require("./pathValidator");
|
||||
const { userThemesPath } = require("../server/helpers/path.helpers");
|
||||
const { createSystemTray, destroyTray } = require("./tray");
|
||||
const {sendReport} = require("./sendReport");
|
||||
|
||||
/**
|
||||
* Security whitelist for feature flags that can be modified at runtime.
|
||||
@@ -117,11 +118,11 @@ function registerIpcHandlers(getMainWindow) {
|
||||
ipcMain.on("window-minimize", () => {
|
||||
// Minimize to tray
|
||||
if (configFeatures.systemTray) {
|
||||
getMainWindow()?.hide()
|
||||
getMainWindow()?.hide();
|
||||
logger.info("Window Minimized in SystemTray (Using Minimize Button)");
|
||||
} else {
|
||||
// Native minimize
|
||||
getMainWindow()?.minimize()
|
||||
getMainWindow()?.minimize();
|
||||
logger.info("Window minimized normally");
|
||||
}
|
||||
}
|
||||
@@ -180,8 +181,13 @@ function registerIpcHandlers(getMainWindow) {
|
||||
*/
|
||||
ipcMain.on("open-config", () => shell.openPath(configFolderPath));
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Front end logger
|
||||
*/
|
||||
ipcMain.on("log-error", (_, message) => logger.error(`[Frontend] ${message}`));
|
||||
ipcMain.on("log-info", (_, message) => logger.info(`[Frontend] ${message}`));
|
||||
ipcMain.on("log-warn", (_, message) => logger.warn(`[Frontend] ${message}`));
|
||||
|
||||
/**
|
||||
* Retrieves available themes from filesystem.
|
||||
*/
|
||||
@@ -199,6 +205,13 @@ function registerIpcHandlers(getMainWindow) {
|
||||
return await reloadThemes();
|
||||
});
|
||||
|
||||
/**
|
||||
* Send bug report
|
||||
*/
|
||||
ipcMain.handle("send-report", async (_, params) => {
|
||||
return await sendReport(params);
|
||||
});
|
||||
|
||||
/**
|
||||
* Updates a runtime feature flag and persists it to disk.
|
||||
*
|
||||
|
||||
54
app/sendReport.js
Normal file
54
app/sendReport.js
Normal file
@@ -0,0 +1,54 @@
|
||||
const fs = require("fs");
|
||||
const path = require("path");
|
||||
const {logger, logDir} = require("../server/logger");
|
||||
|
||||
async function sendReport(params) {
|
||||
try {
|
||||
const {title, description, includeLogs} = params;
|
||||
|
||||
if (!title || !description) {
|
||||
throw new Error("Title and description are required.");
|
||||
}
|
||||
|
||||
const formData = new FormData();
|
||||
formData.append("title", title);
|
||||
formData.append("context", description);
|
||||
|
||||
if (includeLogs === "yes" && logDir) {
|
||||
// YYYY-MM-DD
|
||||
const today = new Date().toISOString().split("T")[0];
|
||||
|
||||
const logFilePath = path.join(logDir, `LOGS-${today}.log`);
|
||||
|
||||
if (fs.existsSync(logFilePath)) {
|
||||
const logFileContent = fs.readFileSync(logFilePath);
|
||||
const blob = new Blob([logFileContent], {type: "text/plain"});
|
||||
formData.append("file", blob, `LOGS-${today}.log`);
|
||||
logger.info("Logs attached to bug report.");
|
||||
} else {
|
||||
logger.warn(`Log file not found for today: ${logFilePath}`);
|
||||
}
|
||||
}
|
||||
|
||||
const response = await fetch(process.env.BUG_REPORT_URL, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"X-Api-Key": process.env.BUG_REPORT_API_KEY
|
||||
},
|
||||
body: formData
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
logger.error(`Server returned an error: ${response.status}`);
|
||||
throw new Error(`Server responded with status ${response.status}`);
|
||||
}
|
||||
|
||||
logger.info("Bug report successfully sent.");
|
||||
return true;
|
||||
} catch (err) {
|
||||
logger.error(`Failed to send bug report: ${err.message}`);
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {sendReport};
|
||||
@@ -95,7 +95,7 @@ function createSystemTray(devMode) {
|
||||
} else {
|
||||
window.show();
|
||||
window.focus();
|
||||
logger.info("Window Restored from SystemTray (Double Click on Tray Icon)")
|
||||
logger.info("Window Restored from SystemTray (Double Click on Tray Icon)");
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
40
eslint.config.js
Normal file
40
eslint.config.js
Normal file
@@ -0,0 +1,40 @@
|
||||
const js = require("@eslint/js");
|
||||
const globals = require("globals");
|
||||
|
||||
module.exports = [
|
||||
js.configs.recommended,
|
||||
|
||||
{ignores: ["node_modules/**", "srpm-out/**", "dist/**", "test/**"]},
|
||||
|
||||
{
|
||||
languageOptions: {
|
||||
ecmaVersion: "latest",
|
||||
sourceType: "commonjs",
|
||||
},
|
||||
rules: {
|
||||
"no-unused-vars": ["error", {argsIgnorePattern: "^_"}],
|
||||
"no-console": "off",
|
||||
"semi": ["error", "always"]
|
||||
},
|
||||
|
||||
|
||||
},
|
||||
{
|
||||
files: ["app/**", "server/**", "main.js"],
|
||||
languageOptions: {
|
||||
globals: {...globals.node}
|
||||
}
|
||||
},
|
||||
{
|
||||
files: ["public/**"],
|
||||
languageOptions: {
|
||||
globals: {...globals.browser}
|
||||
}
|
||||
},
|
||||
{
|
||||
files: ["preload.js"],
|
||||
languageOptions: {
|
||||
globals: {...globals.node, ...globals.browser}
|
||||
}
|
||||
}
|
||||
];
|
||||
13
main.js
13
main.js
@@ -48,9 +48,9 @@ const { configFeatures, devMode} = require("./config");
|
||||
*/
|
||||
if (!configFeatures.enableHardwareAcceleration){
|
||||
app.disableHardwareAcceleration();
|
||||
logger.info("Disabled Hardware Acceleration")
|
||||
logger.info("Disabled Hardware Acceleration");
|
||||
} else {
|
||||
logger.info("Enable Hardware Acceleration")
|
||||
logger.info("Enable Hardware Acceleration");
|
||||
}
|
||||
|
||||
if(devMode){
|
||||
@@ -58,8 +58,8 @@ if(devMode){
|
||||
* Start devTron extensions - @see https://github.com/electron/devtron
|
||||
*/
|
||||
const { devtron } = require('@electron/devtron');
|
||||
devtron.install();
|
||||
logger.info("Loaded DevTron Extension")
|
||||
devtron.install();
|
||||
logger.info("Loaded DevTron Extension");
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -78,6 +78,11 @@ const { userThemesPath, initUserThemes, validateBinaries, defaultDownloadFolder
|
||||
const {createSystemTray, destroyTray} = require("./app/tray");
|
||||
const { stopServer } = require("./server/server");
|
||||
|
||||
/**
|
||||
* Expose .env in process.env
|
||||
*/
|
||||
require("dotenv").config();
|
||||
|
||||
/**
|
||||
* Global flag indicating if the application is intentionally shutting down.
|
||||
* Used across the app to bypass the "minimize to tray on close" behavior.
|
||||
|
||||
1078
package-lock.json
generated
1078
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -2,7 +2,7 @@
|
||||
"name": "freedom-loader",
|
||||
"desktopName": "com.masteracnolo.freedomloader",
|
||||
"productName": "Freedom Loader",
|
||||
"version": "1.6.3",
|
||||
"version": "1.6.4-preview",
|
||||
"author": "MasterAcnolo <MasterAcnolo@users.noreply.github.com>",
|
||||
"description": "Free and open-source GUI for yt-dlp — download video and audio from hundreds of platforms",
|
||||
"homepage": "https://masteracnolo.github.io/Freedom-Loader-Site/",
|
||||
@@ -32,12 +32,15 @@
|
||||
"release:dry-run": "bash scripts/release.sh --dry-run",
|
||||
"test": "jest",
|
||||
"test:unit": "jest test/unit",
|
||||
"lint": "eslint . --format pretty",
|
||||
"lint:fix": "eslint . --fix --format pretty",
|
||||
"update": "npm update"
|
||||
},
|
||||
"dependencies": {
|
||||
"chalk": "^4.1.2",
|
||||
"debug": "^4.4.1",
|
||||
"discord-rpc": "^4.0.1",
|
||||
"dotenv": "^17.4.2",
|
||||
"electron-updater": "^6.6.2",
|
||||
"express": "^5.1.0",
|
||||
"express-rate-limit": "^8.2.1",
|
||||
@@ -48,10 +51,14 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@electron/devtron": "^2.1.1",
|
||||
"@eslint/js": "^10.0.1",
|
||||
"@jest/globals": "^30.4.1",
|
||||
"@playwright/test": "^1.62.1",
|
||||
"electron": "^44.3.0",
|
||||
"electron-builder": "^26.15.3",
|
||||
"eslint": "^10.10.0",
|
||||
"eslint-formatter-pretty": "^7.1.0",
|
||||
"globals": "^17.12.0",
|
||||
"jest": "^30.4.2",
|
||||
"nodemon": "^3.1.14",
|
||||
"playwright": "^1.62.1"
|
||||
|
||||
29
preload.js
29
preload.js
@@ -9,6 +9,35 @@ const { contextBridge, ipcRenderer } = require("electron");
|
||||
*/
|
||||
contextBridge.exposeInMainWorld("electronAPI", {
|
||||
|
||||
/**
|
||||
* Sends error log message.
|
||||
*
|
||||
* @param {...any} args - Values to log
|
||||
*/
|
||||
logError: (...args) => ipcRenderer.send("log-error", args.map(arg => typeof arg === "object" ? JSON.stringify(arg) : arg).join(" ")),
|
||||
|
||||
/**
|
||||
* Sends info log message.
|
||||
*
|
||||
* @param {...any} args - Values to log
|
||||
*/
|
||||
logInfo: (...args) => ipcRenderer.send("log-info", args.map(arg => typeof arg === "object" ? JSON.stringify(arg) : arg).join(" ")),
|
||||
|
||||
/**
|
||||
* Sends warning log message.
|
||||
*
|
||||
* @param {...any} args - Values to log
|
||||
*/
|
||||
logWarn: (...args) => ipcRenderer.send("log-warn", args.map(arg => typeof arg === "object" ? JSON.stringify(arg) : arg).join(" ")),
|
||||
|
||||
/**
|
||||
* Send Bug Report
|
||||
*
|
||||
* @param params
|
||||
* @returns {Promise<any>}
|
||||
*/
|
||||
sendReport: (params) => ipcRenderer.invoke('send-report', params),
|
||||
|
||||
/**
|
||||
* Return process.platform to renderer
|
||||
*/
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
<link rel="stylesheet" href="styles/styles.css">
|
||||
<link rel="stylesheet" href="styles/layout/topbar.css">
|
||||
|
||||
<link href="https://fonts.googleapis.com/icon?family=Material+Icons+Round" rel="stylesheet">
|
||||
</head>
|
||||
<body>
|
||||
|
||||
@@ -21,6 +22,7 @@
|
||||
<div class="custom-controls">
|
||||
<button id="devtools-btn" title="Dev Tools">Tools</button>
|
||||
<button id="logs-btn" title="Logs">Logs</button>
|
||||
<button id="report-bug-btn" title="Report a bug">Report a Bug</button> <!-- TODO: Find a better place -->
|
||||
<button id="website-btn" title="Website">Website</button>
|
||||
<button id="wiki-btn" title="Wiki">Wiki</button>
|
||||
<button id="workshop-btn" title="Workshop">Theme Workshop</button>
|
||||
@@ -295,7 +297,6 @@
|
||||
|
||||
<span id="version-badge" class="version-badge"></span>
|
||||
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<!-- Toast Container -->
|
||||
@@ -314,6 +315,7 @@
|
||||
<script src="script/fetchinfo.js"></script>
|
||||
<script src="script/progressBar.js"></script>
|
||||
<script src="script/customthemes.js"></script>
|
||||
<script src="script/reportBug.js"></script>
|
||||
<script type="module" src="script/topbar.js"></script>
|
||||
|
||||
</body>
|
||||
|
||||
@@ -269,7 +269,7 @@ async function init() {
|
||||
infoDiv.classList.add("visible");
|
||||
});
|
||||
|
||||
})
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
|
||||
121
public/script/reportBug.js
Normal file
121
public/script/reportBug.js
Normal file
@@ -0,0 +1,121 @@
|
||||
class BugReportModal {
|
||||
constructor() {
|
||||
this.modal = null;
|
||||
this.form = null;
|
||||
this.init();
|
||||
}
|
||||
|
||||
init() {
|
||||
this.modal = document.createElement("div");
|
||||
this.modal.className = "bug-modal-overlay";
|
||||
this.modal.style.cssText = "display:none; position:fixed; top:0; left:0; width:100%; height:100%; background:rgba(0,0,0,0.7); justify-content:center; align-items:center; z-index:99999; font-family:sans-serif;";
|
||||
|
||||
this.modal.innerHTML = `
|
||||
<div class="bug-modal-content" style="background:#14141491 !important; color:#ffffff !important; padding:24px !important; border-radius:10px !important; width:420px !important; box-shadow: 0 10px 25px rgba(0,0,0,0.8) !important; display:flex !important; flex-direction:column !important; gap:16px !important; border: 1px solid #333 !important; box-sizing: border-box !important;">
|
||||
<h3 style="margin:0 !important; font-size:1.2rem !important; font-weight:600 !important; color:#ffffff !important;">Report a Bug</h3>
|
||||
<form id="bugReportForm" style="display:flex !important; flex-direction:column !important; gap:12px !important; width:unset !important; margin:0 !important; background:transparent !important; box-shadow: unset !important;">
|
||||
<div style="display:flex !important; flex-direction:column !important; gap:4px !important;">
|
||||
<label style="font-size:0.85rem !important; color:#aaaaaa !important;">Title <span style="color: red; font-weight: bold" >*</span> </label>
|
||||
<input type="text" id="bugTitle" required style="background:#2a2a2a !important; border:1px solid #444 !important; color:#ffffff !important; border-radius:6px !important; padding:8px 10px !important; outline:none !important; width:100% !important; box-sizing: border-box !important;" />
|
||||
</div>
|
||||
<div style="display:flex !important; flex-direction:column !important; gap:4px !important;">
|
||||
<label style="font-size:0.85rem !important; color:#aaaaaa !important;">Description <span style="color: red; font-weight: bold" >*</span></label>
|
||||
<textarea id="bugDescription" placeholder="Provide some details about the bug" required rows="4" style="background:#2a2a2a !important; border:1px solid #444 !important; color:#ffffff !important; border-radius:6px !important; padding:8px 10px !important; outline:none !important; resize:vertical !important; width:100% !important; box-sizing: border-box !important;"></textarea>
|
||||
</div>
|
||||
<div style="display:flex !important; flex-direction:column !important; gap:4px !important;">
|
||||
<label style="font-size:0.85rem !important; color:#aaaaaa !important;">Include Logs From Today</label>
|
||||
<select id="bugIncludeLogs" style="background:#2a2a2a !important; border:1px solid #444 !important; color:#ffffff !important; border-radius:6px !important; padding:8px 10px !important; outline:none !important; width:100% !important; box-sizing: border-box !important;">
|
||||
<option value="yes" style="background:#2a2a2a !important; color:#ffffff !important;">Yes</option>
|
||||
<option value="no" style="background:#2a2a2a !important; color:#ffffff !important;">No</option>
|
||||
</select>
|
||||
</div>
|
||||
<div style="display:flex !important; justify-content:flex-end !important; gap:8px !important; margin-top:8px !important;">
|
||||
<button type="button" id="bugCancelBtn" style="background:#333333 !important; border:none !important; color:#cccccc !important; padding:8px 14px !important; border-radius:6px !important; cursor:pointer !important;">Cancel</button>
|
||||
<button type="submit" style="background:#4f46e5 !important; border:none !important; color:#ffffff !important; padding:8px 16px !important; border-radius:6px !important; cursor:pointer !important; font-weight:500 !important;">Send</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
`;
|
||||
|
||||
document.body.appendChild(this.modal);
|
||||
this.form = this.modal.querySelector("#bugReportForm");
|
||||
|
||||
this.bindEvents();
|
||||
}
|
||||
|
||||
bindEvents() {
|
||||
const titleInput = this.modal.querySelector("#bugTitle");
|
||||
const descInput = this.modal.querySelector("#bugDescription");
|
||||
|
||||
titleInput.addEventListener("input", () => {
|
||||
localStorage.setItem("draft_bug_title", titleInput.value);
|
||||
});
|
||||
|
||||
descInput.addEventListener("input", () => {
|
||||
localStorage.setItem("draft_bug_desc", descInput.value);
|
||||
});
|
||||
|
||||
this.modal.querySelector("#bugCancelBtn").addEventListener("click", () => {
|
||||
this.close();
|
||||
});
|
||||
|
||||
this.modal.addEventListener("click", (e) => {
|
||||
if (e.target === this.modal) {
|
||||
this.close();
|
||||
}
|
||||
});
|
||||
|
||||
this.form.addEventListener("submit", async (e) => {
|
||||
e.preventDefault();
|
||||
const data = {
|
||||
title: titleInput.value,
|
||||
description: descInput.value,
|
||||
includeLogs: this.modal.querySelector("#bugIncludeLogs").value
|
||||
};
|
||||
|
||||
window.electronAPI.logInfo("Submitting:", data);
|
||||
|
||||
try {
|
||||
const success = await window.electronAPI.sendReport(data);
|
||||
|
||||
if (success) {
|
||||
localStorage.removeItem("draft_bug_title");
|
||||
localStorage.removeItem("draft_bug_desc");
|
||||
this.form.reset();
|
||||
this.close();
|
||||
window.showSuccess("Bug report sent successfully!");
|
||||
}
|
||||
} catch (err) {
|
||||
window.showError("Failed to send bug report.");
|
||||
window.electronAPI.logError("Failed to send bug report:", err.message);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
loadState() {
|
||||
const savedTitle = localStorage.getItem("draft_bug_title");
|
||||
const savedDesc = localStorage.getItem("draft_bug_desc");
|
||||
|
||||
if (savedTitle) {
|
||||
this.modal.querySelector("#bugTitle").value = savedTitle;
|
||||
}
|
||||
if (savedDesc) {
|
||||
this.modal.querySelector("#bugDescription").value = savedDesc;
|
||||
}
|
||||
}
|
||||
|
||||
open() {
|
||||
this.loadState();
|
||||
this.modal.style.display = "flex";
|
||||
}
|
||||
|
||||
close() {
|
||||
this.modal.style.display = "none";
|
||||
}
|
||||
}
|
||||
|
||||
window.bugModal = new BugReportModal();
|
||||
|
||||
document.getElementById("report-bug-btn").addEventListener("click", () => {
|
||||
window.bugModal.open();
|
||||
});
|
||||
@@ -17,18 +17,20 @@ window.showToast = function (message, type = "info", duration = 4000) {
|
||||
toast.classList.add("toast", type);
|
||||
|
||||
const icons = {
|
||||
success: "✓",
|
||||
error: "✕",
|
||||
warning: "⚠",
|
||||
info: "ℹ"
|
||||
success: "check_circle",
|
||||
error: "error",
|
||||
warning: "warning",
|
||||
info: "info"
|
||||
};
|
||||
|
||||
const icon = icons[type] || icons.info;
|
||||
|
||||
toast.style.setProperty("--toast-duration", `${duration}ms`);
|
||||
toast.innerHTML = `
|
||||
<span class="toast-icon">${icon}</span>
|
||||
<span class="toast-icon material-icons-round">${icon}</span>
|
||||
<span class="toast-message">${message}</span>
|
||||
<button class="toast-close" title="Close">×</button>
|
||||
<div class="toast-progress"></div>
|
||||
`;
|
||||
|
||||
const closeBtn = toast.querySelector(".toast-close");
|
||||
|
||||
@@ -5,73 +5,70 @@
|
||||
right: 20px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
gap: 12px;
|
||||
z-index: 9999;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
/* Toast */
|
||||
.toast {
|
||||
position: relative;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
padding: 10px 14px;
|
||||
background-color: rgba(50, 50, 50, 0.9);
|
||||
border-radius: 6px;
|
||||
color: #ffffff;
|
||||
font-size: 0.9rem;
|
||||
max-width: 220px;
|
||||
min-width: 100px;
|
||||
animation: slideIn 0.25s ease-out;
|
||||
gap: 12px;
|
||||
padding: 14px 16px;
|
||||
background-color: rgba(30, 30, 30, 0.9);
|
||||
backdrop-filter: blur(12px);
|
||||
-webkit-backdrop-filter: blur(12px);
|
||||
border: 1px solid rgba(255, 255, 255, 0.08);
|
||||
border-radius: 10px;
|
||||
color: #f3f4f6;
|
||||
font-family: inherit;
|
||||
font-size: 0.875rem;
|
||||
max-width: 320px;
|
||||
min-width: 220px;
|
||||
box-shadow: 0 10px 25px -5px rgba(0, 0, 0, 0.3), 0 8px 10px -6px rgba(0, 0, 0, 0.2);
|
||||
animation: slideIn 0.3s cubic-bezier(0.16, 1, 0.3, 1) forwards;
|
||||
pointer-events: auto;
|
||||
backdrop-filter: blur(8px);
|
||||
}
|
||||
|
||||
.toast.success {
|
||||
background-color: rgba(40, 167, 69, 0.15);
|
||||
}
|
||||
|
||||
.toast.error {
|
||||
background-color: rgba(220, 53, 69, 0.15);
|
||||
}
|
||||
|
||||
.toast.warning {
|
||||
background-color: rgba(255, 193, 7, 0.15);
|
||||
}
|
||||
|
||||
.toast.info {
|
||||
background-color: rgba(23, 162, 184, 0.15);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
/* Toast Icon */
|
||||
.toast-icon {
|
||||
flex-shrink: 0;
|
||||
font-size: 18px;
|
||||
font-size: 20px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.toast-icon svg {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.toast.success .toast-icon {
|
||||
color: #28a745;
|
||||
color: #22c55e;
|
||||
}
|
||||
|
||||
.toast.error .toast-icon {
|
||||
color: #dc3545;
|
||||
color: #ef4444;
|
||||
}
|
||||
|
||||
.toast.warning .toast-icon {
|
||||
color: #ffc107;
|
||||
color: #f59e0b;
|
||||
}
|
||||
|
||||
.toast.info .toast-icon {
|
||||
color: #17a2b8;
|
||||
color: #0ea5e9;
|
||||
}
|
||||
|
||||
/* Toast Message */
|
||||
.toast-message {
|
||||
flex: 1;
|
||||
font-weight: 500;
|
||||
font-weight: 400;
|
||||
line-height: 1.4;
|
||||
word-break: break-word;
|
||||
}
|
||||
|
||||
/* Toast Close Button */
|
||||
@@ -79,58 +76,87 @@
|
||||
flex-shrink: 0;
|
||||
background: none;
|
||||
border: none;
|
||||
color: currentColor;
|
||||
color: #9ca3af;
|
||||
cursor: pointer;
|
||||
font-size: 18px;
|
||||
padding: 0;
|
||||
font-size: 16px;
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
border-radius: 4px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
opacity: 0.5;
|
||||
transition: opacity 0.2s ease;
|
||||
transition: background-color 0.15s ease, color 0.15s ease;
|
||||
}
|
||||
|
||||
.toast-close:hover {
|
||||
opacity: 1;
|
||||
background-color: rgba(255, 255, 255, 0.1);
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
.toast-progress {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
height: 3px;
|
||||
width: 100%;
|
||||
background-color: rgba(255, 255, 255, 0.1);
|
||||
animation: progress var(--toast-duration, 4000ms) linear forwards;
|
||||
}
|
||||
|
||||
.toast.success .toast-progress {
|
||||
background-color: #22c55e;
|
||||
}
|
||||
|
||||
.toast.error .toast-progress {
|
||||
background-color: #ef4444;
|
||||
}
|
||||
|
||||
/* Animations */
|
||||
@keyframes slideIn {
|
||||
from {
|
||||
transform: translateX(350px);
|
||||
transform: translateY(-20px);
|
||||
opacity: 0;
|
||||
}
|
||||
to {
|
||||
transform: translateX(0);
|
||||
transform: translateY(0);
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes slideOut {
|
||||
from {
|
||||
transform: translateX(0);
|
||||
transform: translateY(0);
|
||||
opacity: 1;
|
||||
}
|
||||
to {
|
||||
transform: translateX(350px);
|
||||
transform: translateY(-20px);
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.toast.removing {
|
||||
animation: slideOut 0.25s ease-out;
|
||||
@keyframes progress {
|
||||
from {
|
||||
width: 100%;
|
||||
}
|
||||
to {
|
||||
width: 0%;
|
||||
}
|
||||
}
|
||||
|
||||
.toast.removing {
|
||||
animation: slideOut 0.25s ease-in forwards;
|
||||
}
|
||||
|
||||
/* Responsive */
|
||||
@media (max-width: 480px) {
|
||||
#toast-container {
|
||||
bottom: 12px;
|
||||
right: 12px;
|
||||
left: 12px;
|
||||
top: auto;
|
||||
bottom: 16px;
|
||||
right: 16px;
|
||||
left: 16px;
|
||||
}
|
||||
|
||||
.toast {
|
||||
max-width: none;
|
||||
min-width: auto;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user