Compare commits

..

9 Commits

Author SHA1 Message Date
MasterAcnolo
b319a1e445 fix(ipc): send report IPC API 2026-09-16 22:16:59 +02:00
MasterAcnolo
4a64a40226 fix(front): settings menu transition 2026-09-16 20:17:42 +02:00
MasterAcnolo
2c0f5b315b refactor: toast notification style 2026-09-13 15:53:21 +02:00
MasterAcnolo
58b6637eeb feat: implement linter with config file 2026-09-13 12:06:33 +02:00
MasterAcnolo
8af5a4a383 update: bump app version to 1.6.4-preview 2026-09-13 11:56:03 +02:00
Axel Nicolas
23f04bafc3 Merge pull request #56 from MasterAcnolo/feature/send-logs
Feature/send logs
2026-09-13 11:54:43 +02:00
MasterAcnolo
35ffd7077e fix: onClick attribute on Report a Bug
(#53)
2026-09-13 11:44:37 +02:00
MasterAcnolo
408df0e2f6 feat: send logs feature that send logs to a distant service. Full integration in ui
+ expose logger method to frontEnd (#53)
2026-09-13 11:41:09 +02:00
MasterAcnolo
7dee94da6f refactor: extract CHANGELOG into a separated file. Should avoid characters issue 2026-09-11 11:01:12 +02:00
21 changed files with 1494 additions and 83 deletions

2
.env.example Normal file
View File

@@ -0,0 +1,2 @@
BUG_REPORT_URL=
BUG_REPORT_API_KEY=

3
.gitignore vendored
View File

@@ -6,6 +6,9 @@
/out /out
/srpm-out /srpm-out
# .env
*.env
# Config JSON File. Create it and fill it if you want to have dev only settings # Config JSON File. Create it and fill it if you want to have dev only settings
config/config.dev.json config/config.dev.json

View File

@@ -250,7 +250,7 @@ Freedom Loader can be configured either through the settings panel in the UI or
| Option | Type | Default | Description | | Option | Type | Default | Description |
|------------------------------|---------|----------|------------------------------------------------------------| |------------------------------|---------|----------|------------------------------------------------------------|
| `autoUpdate` | boolean | `true` | Enable automatic application updates | | `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 | | `discordRPC` | boolean | `true` | Enable Discord Rich Presence integration |
| `customTopBar` | boolean | `true` | Use custom application top bar | | `customTopBar` | boolean | `true` | Use custom application top bar |
| `autoCheckInfo` | boolean | `true` | Automatically fetch video information on URL paste | | `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 ### 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 - Clear description of the issue
- Steps to reproduce - Steps to reproduce
- Expected vs actual behavior - Expected vs actual behavior
- Relevant logs from `AppData\Local\FreedomLoader\logs\` or `~/.local/share/FreedomLoader/logs/` - Relevant logs from `AppData\Local\FreedomLoader\logs\` or `~/.local/share/FreedomLoader/logs/`
- Screenshots if applicable - Screenshots or recordings if applicable
### Feature Requests ### Feature Requests

View File

@@ -97,7 +97,7 @@ function startRPC() {
details: `Open Source Download Tools - ${config.version}`, details: `Open Source Download Tools - ${config.version}`,
state: "masteracnolo.github.io/FreedomLoader", state: "masteracnolo.github.io/FreedomLoader",
}; };
rpc.clearActivity() rpc.clearActivity();
rpc.setActivity(presence); rpc.setActivity(presence);
}); });

View File

@@ -19,6 +19,7 @@ const config = require("../config");
const { validateDownloadPath, getDefaultDownloadPath } = require("./pathValidator"); const { validateDownloadPath, getDefaultDownloadPath } = require("./pathValidator");
const { userThemesPath } = require("../server/helpers/path.helpers"); const { userThemesPath } = require("../server/helpers/path.helpers");
const { createSystemTray, destroyTray } = require("./tray"); const { createSystemTray, destroyTray } = require("./tray");
const {sendReport} = require("./sendReport");
/** /**
* Security whitelist for feature flags that can be modified at runtime. * Security whitelist for feature flags that can be modified at runtime.
@@ -117,11 +118,11 @@ function registerIpcHandlers(getMainWindow) {
ipcMain.on("window-minimize", () => { ipcMain.on("window-minimize", () => {
// Minimize to tray // Minimize to tray
if (configFeatures.systemTray) { if (configFeatures.systemTray) {
getMainWindow()?.hide() getMainWindow()?.hide();
logger.info("Window Minimized in SystemTray (Using Minimize Button)"); logger.info("Window Minimized in SystemTray (Using Minimize Button)");
} else { } else {
// Native minimize // Native minimize
getMainWindow()?.minimize() getMainWindow()?.minimize();
logger.info("Window minimized normally"); logger.info("Window minimized normally");
} }
} }
@@ -180,7 +181,12 @@ function registerIpcHandlers(getMainWindow) {
*/ */
ipcMain.on("open-config", () => shell.openPath(configFolderPath)); 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. * Retrieves available themes from filesystem.
@@ -199,6 +205,13 @@ function registerIpcHandlers(getMainWindow) {
return await reloadThemes(); 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. * Updates a runtime feature flag and persists it to disk.
* *

54
app/sendReport.js Normal file
View 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};

View File

@@ -95,7 +95,7 @@ function createSystemTray(devMode) {
} else { } else {
window.show(); window.show();
window.focus(); 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
View 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}
}
}
];

11
main.js
View File

@@ -48,9 +48,9 @@ const { configFeatures, devMode} = require("./config");
*/ */
if (!configFeatures.enableHardwareAcceleration){ if (!configFeatures.enableHardwareAcceleration){
app.disableHardwareAcceleration(); app.disableHardwareAcceleration();
logger.info("Disabled Hardware Acceleration") logger.info("Disabled Hardware Acceleration");
} else { } else {
logger.info("Enable Hardware Acceleration") logger.info("Enable Hardware Acceleration");
} }
if(devMode){ if(devMode){
@@ -59,7 +59,7 @@ if(devMode){
*/ */
const { devtron } = require('@electron/devtron'); const { devtron } = require('@electron/devtron');
devtron.install(); devtron.install();
logger.info("Loaded DevTron Extension") logger.info("Loaded DevTron Extension");
} }
/** /**
@@ -78,6 +78,11 @@ const { userThemesPath, initUserThemes, validateBinaries, defaultDownloadFolder
const {createSystemTray, destroyTray} = require("./app/tray"); const {createSystemTray, destroyTray} = require("./app/tray");
const { stopServer } = require("./server/server"); const { stopServer } = require("./server/server");
/**
* Expose .env in process.env
*/
require("dotenv").config();
/** /**
* Global flag indicating if the application is intentionally shutting down. * Global flag indicating if the application is intentionally shutting down.
* Used across the app to bypass the "minimize to tray on close" behavior. * Used across the app to bypass the "minimize to tray on close" behavior.

1078
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -2,7 +2,7 @@
"name": "freedom-loader", "name": "freedom-loader",
"desktopName": "com.masteracnolo.freedomloader", "desktopName": "com.masteracnolo.freedomloader",
"productName": "Freedom Loader", "productName": "Freedom Loader",
"version": "1.6.3", "version": "1.6.4-preview",
"author": "MasterAcnolo <MasterAcnolo@users.noreply.github.com>", "author": "MasterAcnolo <MasterAcnolo@users.noreply.github.com>",
"description": "Free and open-source GUI for yt-dlp — download video and audio from hundreds of platforms", "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/", "homepage": "https://masteracnolo.github.io/Freedom-Loader-Site/",
@@ -32,12 +32,15 @@
"release:dry-run": "bash scripts/release.sh --dry-run", "release:dry-run": "bash scripts/release.sh --dry-run",
"test": "jest", "test": "jest",
"test:unit": "jest test/unit", "test:unit": "jest test/unit",
"lint": "eslint . --format pretty",
"lint:fix": "eslint . --fix --format pretty",
"update": "npm update" "update": "npm update"
}, },
"dependencies": { "dependencies": {
"chalk": "^4.1.2", "chalk": "^4.1.2",
"debug": "^4.4.1", "debug": "^4.4.1",
"discord-rpc": "^4.0.1", "discord-rpc": "^4.0.1",
"dotenv": "^17.4.2",
"electron-updater": "^6.6.2", "electron-updater": "^6.6.2",
"express": "^5.1.0", "express": "^5.1.0",
"express-rate-limit": "^8.2.1", "express-rate-limit": "^8.2.1",
@@ -48,10 +51,14 @@
}, },
"devDependencies": { "devDependencies": {
"@electron/devtron": "^2.1.1", "@electron/devtron": "^2.1.1",
"@eslint/js": "^10.0.1",
"@jest/globals": "^30.4.1", "@jest/globals": "^30.4.1",
"@playwright/test": "^1.62.1", "@playwright/test": "^1.62.1",
"electron": "^44.3.0", "electron": "^44.3.0",
"electron-builder": "^26.15.3", "electron-builder": "^26.15.3",
"eslint": "^10.10.0",
"eslint-formatter-pretty": "^7.1.0",
"globals": "^17.12.0",
"jest": "^30.4.2", "jest": "^30.4.2",
"nodemon": "^3.1.14", "nodemon": "^3.1.14",
"playwright": "^1.62.1" "playwright": "^1.62.1"

View File

@@ -9,6 +9,27 @@ const { contextBridge, ipcRenderer } = require("electron");
*/ */
contextBridge.exposeInMainWorld("electronAPI", { 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(" ")),
/** /**
* Return process.platform to renderer * Return process.platform to renderer
*/ */
@@ -126,4 +147,12 @@ contextBridge.exposeInMainWorld("topbarAPI", {
* Opens configuration/settings panel. * Opens configuration/settings panel.
*/ */
openConfig: () => ipcRenderer.send("open-config"), openConfig: () => ipcRenderer.send("open-config"),
/**
* Send Bug Report
*
* @param params
* @returns {Promise<any>}
*/
sendReport: (params) => ipcRenderer.invoke('send-report', params),
}); });

View File

@@ -13,6 +13,7 @@
<link rel="stylesheet" href="styles/styles.css"> <link rel="stylesheet" href="styles/styles.css">
<link rel="stylesheet" href="styles/layout/topbar.css"> <link rel="stylesheet" href="styles/layout/topbar.css">
<link href="https://fonts.googleapis.com/icon?family=Material+Icons+Round" rel="stylesheet">
</head> </head>
<body> <body>
@@ -21,6 +22,7 @@
<div class="custom-controls"> <div class="custom-controls">
<button id="devtools-btn" title="Dev Tools">Tools</button> <button id="devtools-btn" title="Dev Tools">Tools</button>
<button id="logs-btn" title="Logs">Logs</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="website-btn" title="Website">Website</button>
<button id="wiki-btn" title="Wiki">Wiki</button> <button id="wiki-btn" title="Wiki">Wiki</button>
<button id="workshop-btn" title="Workshop">Theme Workshop</button> <button id="workshop-btn" title="Workshop">Theme Workshop</button>
@@ -295,7 +297,6 @@
<span id="version-badge" class="version-badge"></span> <span id="version-badge" class="version-badge"></span>
</div>
</main> </main>
<!-- Toast Container --> <!-- Toast Container -->
@@ -314,6 +315,7 @@
<script src="script/fetchinfo.js"></script> <script src="script/fetchinfo.js"></script>
<script src="script/progressBar.js"></script> <script src="script/progressBar.js"></script>
<script src="script/customthemes.js"></script> <script src="script/customthemes.js"></script>
<script src="script/reportBug.js"></script>
<script type="module" src="script/topbar.js"></script> <script type="module" src="script/topbar.js"></script>
</body> </body>

View File

@@ -269,7 +269,7 @@ async function init() {
infoDiv.classList.add("visible"); infoDiv.classList.add("visible");
}); });
}) });
} }

121
public/script/reportBug.js Normal file
View 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();
});

View File

@@ -16,8 +16,7 @@ let isOpen = false;
* Toggles visibility of the settings panel when clicking the settings button. * Toggles visibility of the settings panel when clicking the settings button.
*/ */
settingsButton.addEventListener("click", () => { settingsButton.addEventListener("click", () => {
const currentlyOpen = settingsPanel.style.display === "block"; settingsPanel.classList.toggle("open");
settingsPanel.style.display = currentlyOpen ? "none" : "block";
}); });
/** /**
@@ -25,7 +24,7 @@ settingsButton.addEventListener("click", () => {
*/ */
function closePanel() { function closePanel() {
isOpen = false; isOpen = false;
settingsPanel.style.display = "none"; settingsPanel.classList.remove("open");
} }
/** /**

View File

@@ -17,18 +17,20 @@ window.showToast = function (message, type = "info", duration = 4000) {
toast.classList.add("toast", type); toast.classList.add("toast", type);
const icons = { const icons = {
success: "✓", success: "check_circle",
error: "✕", error: "error",
warning: "⚠", warning: "warning",
info: "ℹ" info: "info"
}; };
const icon = icons[type] || icons.info; const icon = icons[type] || icons.info;
toast.style.setProperty("--toast-duration", `${duration}ms`);
toast.innerHTML = ` toast.innerHTML = `
<span class="toast-icon">${icon}</span> <span class="toast-icon material-icons-round">${icon}</span>
<span class="toast-message">${message}</span> <span class="toast-message">${message}</span>
<button class="toast-close" title="Close">×</button> <button class="toast-close" title="Close">×</button>
<div class="toast-progress"></div>
`; `;
const closeBtn = toast.querySelector(".toast-close"); const closeBtn = toast.querySelector(".toast-close");

View File

@@ -5,73 +5,70 @@
right: 20px; right: 20px;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
gap: 8px; gap: 12px;
z-index: 9999; z-index: 9999;
pointer-events: none; pointer-events: none;
} }
/* Toast */ /* Toast */
.toast { .toast {
position: relative;
display: flex; display: flex;
align-items: center; align-items: center;
gap: 10px; gap: 12px;
padding: 10px 14px; padding: 14px 16px;
background-color: rgba(50, 50, 50, 0.9); background-color: rgba(30, 30, 30, 0.9);
border-radius: 6px; backdrop-filter: blur(12px);
color: #ffffff; -webkit-backdrop-filter: blur(12px);
font-size: 0.9rem; border: 1px solid rgba(255, 255, 255, 0.08);
max-width: 220px; border-radius: 10px;
min-width: 100px; color: #f3f4f6;
animation: slideIn 0.25s ease-out; 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; pointer-events: auto;
backdrop-filter: blur(8px); overflow: hidden;
}
.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);
} }
/* Toast Icon */ /* Toast Icon */
.toast-icon { .toast-icon {
flex-shrink: 0; flex-shrink: 0;
font-size: 18px; font-size: 20px;
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
} }
.toast-icon svg {
width: 100%;
height: 100%;
}
.toast.success .toast-icon { .toast.success .toast-icon {
color: #28a745; color: #22c55e;
} }
.toast.error .toast-icon { .toast.error .toast-icon {
color: #dc3545; color: #ef4444;
} }
.toast.warning .toast-icon { .toast.warning .toast-icon {
color: #ffc107; color: #f59e0b;
} }
.toast.info .toast-icon { .toast.info .toast-icon {
color: #17a2b8; color: #0ea5e9;
} }
/* Toast Message */ /* Toast Message */
.toast-message { .toast-message {
flex: 1; flex: 1;
font-weight: 500; font-weight: 400;
line-height: 1.4;
word-break: break-word;
} }
/* Toast Close Button */ /* Toast Close Button */
@@ -79,54 +76,83 @@
flex-shrink: 0; flex-shrink: 0;
background: none; background: none;
border: none; border: none;
color: currentColor; color: #9ca3af;
cursor: pointer; cursor: pointer;
font-size: 18px; font-size: 16px;
padding: 0; width: 24px;
height: 24px;
border-radius: 4px;
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
opacity: 0.5; transition: background-color 0.15s ease, color 0.15s ease;
transition: opacity 0.2s ease;
} }
.toast-close:hover { .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 */ /* Animations */
@keyframes slideIn { @keyframes slideIn {
from { from {
transform: translateX(350px); transform: translateY(-20px);
opacity: 0; opacity: 0;
} }
to { to {
transform: translateX(0); transform: translateY(0);
opacity: 1; opacity: 1;
} }
} }
@keyframes slideOut { @keyframes slideOut {
from { from {
transform: translateX(0); transform: translateY(0);
opacity: 1; opacity: 1;
} }
to { to {
transform: translateX(350px); transform: translateY(-20px);
opacity: 0; opacity: 0;
} }
} }
.toast.removing { @keyframes progress {
animation: slideOut 0.25s ease-out; from {
width: 100%;
}
to {
width: 0%;
}
}
.toast.removing {
animation: slideOut 0.25s ease-in forwards;
} }
/* Responsive */
@media (max-width: 480px) { @media (max-width: 480px) {
#toast-container { #toast-container {
bottom: 12px; top: auto;
right: 12px; bottom: 16px;
left: 12px; right: 16px;
left: 16px;
} }
.toast { .toast {

View File

@@ -45,11 +45,22 @@
border-radius: 12px; border-radius: 12px;
padding: 20px; padding: 20px;
box-shadow: 0 8px 24px rgba(0,0,0,0.4); box-shadow: 0 8px 24px rgba(0,0,0,0.4);
display: none;
overflow-y: auto; overflow-y: auto;
z-index: 9999; z-index: 9999;
animation: slideIn 0.2s ease-out; /*animation: slideIn 0.2s ease-out; */
color: var(--settings-text-color); color: var(--settings-text-color);
display: block;
opacity: 0;
pointer-events: none;
transform: translateX(20px);
transition: opacity 0.2s ease-out, transform 0.2s ease-out;
}
.settings-panel.open {
opacity: 1;
pointer-events: auto;
transform: translateX(0);
} }
.settings-panel::-webkit-scrollbar { .settings-panel::-webkit-scrollbar {

1
scripts/CHANGELOG.md Normal file
View File

@@ -0,0 +1 @@
[//]: # (This file is used for the release CHANGELOG. Please use Markdown to describe changes.)

View File

@@ -5,7 +5,7 @@ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
ROOT_DIR="$(cd "$SCRIPT_DIR/.." && pwd)" ROOT_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
VERSION=$(node -p "require('$ROOT_DIR/package.json').version") VERSION=$(node -p "require('$ROOT_DIR/package.json').version")
f [[ "$VERSION" == *"-preview"* ]]; then if [[ "$VERSION" == *"-preview"* ]]; then
echo "Error: The current version ($VERSION) is a 'preview" echo "Error: The current version ($VERSION) is a 'preview"
echo "Unable to launch release pipeline. Please update the package.json with a stable version.." echo "Unable to launch release pipeline. Please update the package.json with a stable version.."
exit 1 exit 1
@@ -64,6 +64,16 @@ echo "[0/5] Cleaning workspace..."
rm -rf "$ROOT_DIR/dist" "$ROOT_DIR/srpm-out" rm -rf "$ROOT_DIR/dist" "$ROOT_DIR/srpm-out"
echo "Workspace cleaned (dist/ and srpm-out/ removed)" echo "Workspace cleaned (dist/ and srpm-out/ removed)"
# ------------------------------------------------------------
# Step 0.3 - Retrieve Changelog
# ------------------------------------------------------------
if [ ! -f "$ROOT_DIR/CHANGELOG.md" ]; then
echo "Warning: CHANGELOG.md not found, release notes will be empty."
CHANGELOG=""
else
CHANGELOG="$(cat "$ROOT_DIR/CHANGELOG.md")"
echo "Changelog retrieved successfully"
fi
# ------------------------------------------------------------ # ------------------------------------------------------------
# Step 1 - Build Linux packages (AppImage, deb, snap) # Step 1 - Build Linux packages (AppImage, deb, snap)
# ------------------------------------------------------------ # ------------------------------------------------------------
@@ -105,6 +115,10 @@ PREV_TAG=$(git rev-list --tags --skip=1 --max-count=1 | xargs git describe --tag
RELEASE_NOTES="# Freedom Loader - $VERSION RELEASE_NOTES="# Freedom Loader - $VERSION
## Changelog
$CHANGELOG
## Found a bug or issue? ## Found a bug or issue?
Please report it in the [GitHub Issues](https://github.com/MasterAcnolo/Freedom-Loader/issues) section. Please report it in the [GitHub Issues](https://github.com/MasterAcnolo/Freedom-Loader/issues) section.