feat: Added light mode plus various bug fixes
- Added click handler for Easter egg that enables music inspiration feature. - Updated credits page to include new icons and handle click events. - Enhanced inspiration page to fetch and display Spotify track embed HTML. - Refactored inspiration loading logic to include track data. - Introduced theme selection in settings with light and dark modes. - Updated settings page to reflect new theme options and improve toast messages. - Refined layout styles across various pages for consistent theming. - Bumped application version to 1.5.0 in installer script.
This commit is contained in:
@@ -1,8 +1,8 @@
|
||||
[EMLy]
|
||||
SDK_DECODER_SEMVER = 1.3.2
|
||||
SDK_DECODER_RELEASE_CHANNEL = beta
|
||||
GUI_SEMVER = 1.4.0
|
||||
GUI_RELEASE_CHANNEL = stable
|
||||
SDK_DECODER_RELEASE_CHANNEL = stable
|
||||
GUI_SEMVER = 1.5.0
|
||||
GUI_RELEASE_CHANNEL = beta
|
||||
LANGUAGE = it
|
||||
UPDATE_CHECK_ENABLED = false
|
||||
UPDATE_PATH =
|
||||
|
||||
@@ -50,7 +50,9 @@
|
||||
"settings_danger_reset_dialog_continue": "Continue",
|
||||
"settings_danger_warning": "Warning: This action is irreversible. Please ensure you have backed up any important data before proceeding.",
|
||||
"settings_danger_alert_title": "Advanced options enabled",
|
||||
"settings_danger_alert_description": "You're about to access EMLy's advanced options. Modifying such options may cause instability, including crashes, freezes, or security software alerts. For support or troubleshooting, contact @lyzcoote on Discord.",
|
||||
"settings_danger_alert_description_part1": "You're about to access EMLy's advanced options.",
|
||||
"settings_danger_alert_description_part2": "Modifying such options may cause instability, including crashes, freezes, or security software alerts.",
|
||||
"settings_danger_alert_description_part3": "For support or troubleshooting, contact your TL/RDS.",
|
||||
"settings_danger_alert_understood": "Understood",
|
||||
"settings_toast_reverted": "Reverted to last saved settings.",
|
||||
"settings_toast_save_failed": "Failed to save settings.",
|
||||
@@ -98,6 +100,12 @@
|
||||
"bugreport_email_placeholder": "your.email@example.com",
|
||||
"bugreport_text_label": "Bug Description",
|
||||
"bugreport_text_placeholder": "Describe the bug in detail...",
|
||||
"settings_appearance_title": "Appearance",
|
||||
"settings_appearance_description": "Customize the application theme.",
|
||||
"settings_theme_label": "Theme",
|
||||
"settings_theme_hint": "Choose between light and dark mode.",
|
||||
"settings_theme_light": "Light",
|
||||
"settings_theme_dark": "Dark",
|
||||
"bugreport_info": "Your message, email file (if loaded), screenshot, and system information will be included in the report.",
|
||||
"bugreport_screenshot_label": "Attached Screenshot:",
|
||||
"bugreport_cancel": "Cancel",
|
||||
@@ -160,5 +168,35 @@
|
||||
"credits_lib_dompurify": "XSS sanitizer for HTML content",
|
||||
"credits_license_title": "License & Source",
|
||||
"credits_license_text": "EMLy is proprietary software developed by 3gIT. All rights reserved. The application uses various open source libraries, each governed by their respective licenses.",
|
||||
"credits_copyright": "All rights reserved."
|
||||
"credits_copyright": "All rights reserved.",
|
||||
"settings_updates_title": "Updates",
|
||||
"settings_updates_description": "Check for and install application updates from your network share.",
|
||||
"settings_updates_current_version": "Current Version",
|
||||
"settings_updates_available": "Update Available",
|
||||
"settings_updates_check_failed": "Check failed",
|
||||
"settings_updates_no_updates": "No updates found",
|
||||
"settings_updates_check_label": "Check for Updates",
|
||||
"settings_updates_last_checked": "Last checked: {time}",
|
||||
"settings_updates_click_check": "Click to check for available updates",
|
||||
"settings_updates_checking": "Checking...",
|
||||
"settings_updates_check_now": "Check Now",
|
||||
"settings_updates_version_available": "Version {version} Available",
|
||||
"settings_updates_downloading": "Downloading... {progress}%",
|
||||
"settings_updates_click_download": "Click to download the update",
|
||||
"settings_updates_download_button": "Download",
|
||||
"settings_updates_ready_title": "Update Ready to Install",
|
||||
"settings_updates_ready_ref": "Version {version} has been downloaded and verified",
|
||||
"settings_updates_install_button": "Install Now",
|
||||
"settings_updates_info_message": "Updates are checked from your configured network share path.",
|
||||
"settings_updates_current_path": "Current path:",
|
||||
"settings_updates_no_path": "No update path configured",
|
||||
"settings_toast_update_available": "Update available: {version}",
|
||||
"settings_toast_latest_version": "You're on the latest version",
|
||||
"settings_toast_check_failed": "Failed to check for updates",
|
||||
"settings_toast_download_success": "Update downloaded successfully",
|
||||
"settings_toast_download_failed": "Failed to download update",
|
||||
"settings_toast_install_failed": "Failed to launch installer",
|
||||
"settings_danger_update_checker_label": "Enable Update Checker",
|
||||
"settings_danger_update_checker_hint": "Check for application updates from network share",
|
||||
"settings_danger_update_checker_info": "Info: When enabled, the app will check for updates from your configured network share. Disable this if you manage updates manually or don't have network access."
|
||||
}
|
||||
|
||||
@@ -50,7 +50,9 @@
|
||||
"settings_danger_reset_dialog_continue": "Continua",
|
||||
"settings_danger_warning": "Attenzione: Questa azione è irreversibile. Assicurati di aver effettuato il backup di tutti i dati importanti prima di procedere.",
|
||||
"settings_danger_alert_title": "Opzioni avanzate abilitate",
|
||||
"settings_danger_alert_description": "Stai per accedere alle opzioni avanzate di EMLy. Modificare tali opzioni può causare instabilità, inclusi crash, blocchi o avvisi del software di sicurezza. Per supporto o risoluzione dei problemi, contatta @lyzcoote su Discord.",
|
||||
"settings_danger_alert_description_part1": "Stai per accedere alle opzioni avanzate di EMLy.",
|
||||
"settings_danger_alert_description_part2": "Modificare tali opzioni può causare instabilità, inclusi crash, blocchi o avvisi del software di sicurezza.",
|
||||
"settings_danger_alert_description_part3": "Per supporto o risoluzione dei problemi, contatta il proprio TL/RDS.",
|
||||
"settings_danger_alert_understood": "Capito",
|
||||
"settings_toast_reverted": "Ripristinato alle ultime impostazioni salvate.",
|
||||
"settings_toast_save_failed": "Impossibile salvare le impostazioni.",
|
||||
@@ -98,6 +100,12 @@
|
||||
"bugreport_email_placeholder": "tua.email@esempio.com",
|
||||
"bugreport_text_label": "Descrizione del Bug",
|
||||
"bugreport_text_placeholder": "Descrivi il bug in dettaglio...",
|
||||
"settings_appearance_title": "Aspetto",
|
||||
"settings_appearance_description": "Personalizza il tema dell'applicazione.",
|
||||
"settings_theme_label": "Tema",
|
||||
"settings_theme_hint": "Scegli tra modalità chiara e scura.",
|
||||
"settings_theme_light": "Chiaro",
|
||||
"settings_theme_dark": "Scuro",
|
||||
"bugreport_info": "Il tuo messaggio, il file email (se caricato), lo screenshot e le informazioni di sistema saranno inclusi nella segnalazione.",
|
||||
"bugreport_screenshot_label": "Screenshot Allegato:",
|
||||
"bugreport_cancel": "Annulla",
|
||||
@@ -160,5 +168,35 @@
|
||||
"credits_lib_dompurify": "Sanitizzatore XSS per contenuti HTML",
|
||||
"credits_license_title": "Licenza e Sorgente",
|
||||
"credits_license_text": "EMLy è un software proprietario sviluppato da 3gIT. Tutti i diritti riservati. L'applicazione utilizza varie librerie open source, ciascuna governata dalle rispettive licenze.",
|
||||
"credits_copyright": "Tutti i diritti riservati."
|
||||
"credits_copyright": "Tutti i diritti riservati.",
|
||||
"settings_updates_title": "Aggiornamenti",
|
||||
"settings_updates_description": "Controlla e installa gli aggiornamenti dell'applicazione dalla condivisione di rete.",
|
||||
"settings_updates_current_version": "Versione corrente",
|
||||
"settings_updates_available": "Aggiornamento disponibile",
|
||||
"settings_updates_check_failed": "Controllo fallito",
|
||||
"settings_updates_no_updates": "Nessun aggiornamento trovato",
|
||||
"settings_updates_check_label": "Controlla aggiornamenti",
|
||||
"settings_updates_last_checked": "Ultimo controllo: {time}",
|
||||
"settings_updates_click_check": "Clicca per cercare aggiornamenti disponibili",
|
||||
"settings_updates_checking": "Controllo in corso...",
|
||||
"settings_updates_check_now": "Controlla ora",
|
||||
"settings_updates_version_available": "Versione {version} disponibile",
|
||||
"settings_updates_downloading": "Download in corso... {progress}%",
|
||||
"settings_updates_click_download": "Clicca per scaricare l'aggiornamento",
|
||||
"settings_updates_download_button": "Scarica",
|
||||
"settings_updates_ready_title": "Aggiornamento pronto per l'installazione",
|
||||
"settings_updates_ready_ref": "La versione {version} è stata scaricata e verificata",
|
||||
"settings_updates_install_button": "Installa ora",
|
||||
"settings_updates_info_message": "Gli aggiornamenti vengono controllati dal percorso di rete configurato.",
|
||||
"settings_updates_current_path": "Percorso attuale:",
|
||||
"settings_updates_no_path": "Nessun percorso di aggiornamento configurato",
|
||||
"settings_toast_update_available": "Aggiornamento disponibile: {version}",
|
||||
"settings_toast_latest_version": "Sei sull'ultima versione",
|
||||
"settings_toast_check_failed": "Impossibile controllare gli aggiornamenti",
|
||||
"settings_toast_download_success": "Aggiornamento scaricato con successo",
|
||||
"settings_toast_download_failed": "Impossibile scaricare l'aggiornamento",
|
||||
"settings_toast_install_failed": "Impossibile avviare l'installazione",
|
||||
"settings_danger_update_checker_label": "Abilita controllo aggiornamenti",
|
||||
"settings_danger_update_checker_hint": "Controlla aggiornamenti applicazione dalla condivisione di rete",
|
||||
"settings_danger_update_checker_info": "Info: Quando abilitato, l'app controllerà gli aggiornamenti dal percorso di rete configurato. Disabilitalo se gestisci gli aggiornamenti manualmente o non hai accesso alla rete."
|
||||
}
|
||||
|
||||
@@ -36,6 +36,7 @@
|
||||
"vite-plugin-devtools-json": "^1.0.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"@rollup/rollup-win32-arm64-msvc": "^4.57.1",
|
||||
"@types/html2canvas": "^1.0.0",
|
||||
"dompurify": "^3.3.1",
|
||||
"html2canvas": "^1.4.1",
|
||||
|
||||
@@ -14,11 +14,11 @@
|
||||
try {
|
||||
const key = "emly_theme";
|
||||
const stored = localStorage.getItem(key);
|
||||
const theme = stored === "light" || stored === "dark" ? stored : "dark";
|
||||
const theme = stored === "light" || stored === "dark" ? stored : "light";
|
||||
document.documentElement.classList.toggle("dark", theme === "dark");
|
||||
} catch {
|
||||
// If storage is blocked, default to dark.
|
||||
document.documentElement.classList.add("dark");
|
||||
// If storage is blocked, default to light.
|
||||
document.documentElement.classList.remove("dark");
|
||||
}
|
||||
})();
|
||||
</script>
|
||||
@@ -67,8 +67,21 @@
|
||||
<body data-sveltekit-preload-data="hover">
|
||||
<div id="app-loading">
|
||||
<div class="loader-spinner"></div>
|
||||
<div>Loading, please wait...</div>
|
||||
<div id="loading-text">Loading...</div>
|
||||
</div>
|
||||
<script>
|
||||
(() => {
|
||||
try {
|
||||
const settings = JSON.parse(localStorage.getItem("emly_gui_settings") || "{}");
|
||||
const lang = settings.selectedLanguage || "en";
|
||||
const text = lang === "it" ? "Caricamento..." : "Loading...";
|
||||
const el = document.getElementById("loading-text");
|
||||
if (el) el.textContent = text;
|
||||
} catch (e) {
|
||||
console.error("Failed to localize loading text", e);
|
||||
}
|
||||
})();
|
||||
</script>
|
||||
<div style="display: contents;">%sveltekit.body%</div>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -403,8 +403,8 @@
|
||||
}
|
||||
|
||||
.panel {
|
||||
background: rgba(255, 255, 255, 0.04);
|
||||
border: 1px solid rgba(255, 255, 255, 0.1);
|
||||
background: var(--card);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 14px;
|
||||
overflow: hidden;
|
||||
}
|
||||
@@ -423,20 +423,20 @@
|
||||
height: 34px;
|
||||
padding: 0 12px;
|
||||
border-radius: 10px;
|
||||
border: 1px solid rgba(255, 255, 255, 0.12);
|
||||
background: rgba(255, 255, 255, 0.06);
|
||||
color: inherit;
|
||||
border: 1px solid var(--border);
|
||||
background: var(--muted);
|
||||
color: var(--muted-foreground);
|
||||
cursor: pointer;
|
||||
user-select: none;
|
||||
font-size: 11px;
|
||||
font-weight: 700;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.05em;
|
||||
color: rgba(255, 255, 255, 0.5);
|
||||
}
|
||||
|
||||
.btn:hover {
|
||||
background: rgba(255, 255, 255, 0.09);
|
||||
background: var(--accent);
|
||||
color: var(--accent-foreground);
|
||||
}
|
||||
|
||||
.events {
|
||||
@@ -454,16 +454,16 @@
|
||||
}
|
||||
|
||||
.email-header-content {
|
||||
background: rgba(255, 255, 255, 0.05);
|
||||
background: var(--card);
|
||||
padding: 16px;
|
||||
border-bottom: 1px solid rgba(255, 255, 255, 0.08);
|
||||
border-bottom: 1px solid var(--border);
|
||||
}
|
||||
|
||||
.email-subject {
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
line-height: 1.25;
|
||||
color: inherit;
|
||||
color: var(--foreground);
|
||||
min-width: 0;
|
||||
overflow-wrap: break-word;
|
||||
}
|
||||
@@ -496,21 +496,21 @@
|
||||
|
||||
.email-meta-grid .label {
|
||||
text-align: right;
|
||||
color: rgba(255, 255, 255, 0.5);
|
||||
color: var(--muted-foreground);
|
||||
margin-right: 8px;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.email-meta-grid .value {
|
||||
color: rgba(255, 255, 255, 0.9);
|
||||
color: var(--foreground);
|
||||
word-break: break-all;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.email-attachments {
|
||||
padding: 10px 16px;
|
||||
border-bottom: 1px solid rgba(255, 255, 255, 0.08);
|
||||
background: rgba(255, 255, 255, 0.03);
|
||||
border-bottom: 1px solid var(--border);
|
||||
background: var(--muted);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
@@ -522,7 +522,7 @@
|
||||
font-weight: 700;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.05em;
|
||||
color: rgba(255, 255, 255, 0.5);
|
||||
color: var(--muted-foreground);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
@@ -538,9 +538,9 @@
|
||||
height: 28px;
|
||||
padding: 0 10px;
|
||||
border-radius: 6px;
|
||||
border: 1px solid rgba(255, 255, 255, 0.15);
|
||||
border: 1px solid var(--border);
|
||||
background: transparent;
|
||||
color: rgba(255, 255, 255, 0.8);
|
||||
color: var(--foreground);
|
||||
font-size: 12px;
|
||||
cursor: pointer;
|
||||
text-decoration: none;
|
||||
@@ -548,8 +548,8 @@
|
||||
}
|
||||
|
||||
.att-btn:hover {
|
||||
background: rgba(255, 255, 255, 0.05);
|
||||
color: #fff;
|
||||
background: var(--accent);
|
||||
color: var(--accent-foreground);
|
||||
}
|
||||
|
||||
.att-btn.image {
|
||||
@@ -628,10 +628,10 @@
|
||||
justify-content: center;
|
||||
height: 36px;
|
||||
padding: 0 16px;
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
border: 1px solid rgba(255, 255, 255, 0.15);
|
||||
background: var(--muted);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 8px;
|
||||
color: white;
|
||||
color: var(--foreground);
|
||||
font-size: 13px;
|
||||
font-weight: 500;
|
||||
cursor: pointer;
|
||||
@@ -639,8 +639,8 @@
|
||||
}
|
||||
|
||||
.browse-btn:hover {
|
||||
background: rgba(255, 255, 255, 0.15);
|
||||
border-color: rgba(255, 255, 255, 0.25);
|
||||
background: var(--accent);
|
||||
border-color: var(--accent-foreground);
|
||||
}
|
||||
|
||||
.browse-btn:disabled,
|
||||
@@ -660,12 +660,12 @@
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb {
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
background: var(--border);
|
||||
border-radius: 6px;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb:hover {
|
||||
background: rgba(255, 255, 255, 0.2);
|
||||
background: var(--muted-foreground);
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-corner {
|
||||
@@ -674,7 +674,7 @@
|
||||
|
||||
.att-empty {
|
||||
font-size: 11px;
|
||||
color: rgba(255, 255, 255, 0.4);
|
||||
color: var(--muted-foreground);
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
|
||||
@@ -56,8 +56,8 @@
|
||||
<Sidebar.Root style="opacity: 0.8;">
|
||||
<Sidebar.Header>
|
||||
<div
|
||||
class="sidebar-title items-center justify-center p-3 border-b border-white/10"
|
||||
style="padding: 12px; border-bottom: 1px solid rgba(255, 255, 255, 0.08); display: flex; justify-content: center;"
|
||||
class="sidebar-title items-center justify-center p-3 border-b border-border flex"
|
||||
style="padding: 12px; display: flex; justify-content: center;"
|
||||
>
|
||||
<img src="/appicon.png" alt="Logo" width="64" height="64" />
|
||||
<span
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
let { onSave, onReset } = $props();
|
||||
</script>
|
||||
|
||||
<div class="flex items-center gap-4 rounded-lg border bg-background px-4 py-3 shadow-lg w-full max-w-md">
|
||||
<div class="flex items-center gap-4 rounded-lg border bg-card px-4 py-3 shadow-lg w-full max-w-md">
|
||||
<span class="text-sm text-muted-foreground flex-1">
|
||||
{m.settings_unsaved_toast_message()}
|
||||
</span>
|
||||
@@ -19,9 +19,3 @@
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.bg-background {
|
||||
background-color: oklch(0.205 0 0);
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import { browser } from "$app/environment";
|
||||
import type { EMLy_GUI_Settings } from "$lib/types";
|
||||
import { getFromLocalStorage, saveToLocalStorage } from "$lib/utils/localStorageHelper";
|
||||
import { applyTheme, getStoredTheme } from "$lib/utils/theme";
|
||||
import { setLocale } from "$lib/paraglide/runtime";
|
||||
|
||||
const STORAGE_KEY = "emly_gui_settings";
|
||||
|
||||
@@ -11,7 +13,9 @@ const defaults: EMLy_GUI_Settings = {
|
||||
previewFileSupportedTypes: ["jpg", "jpeg", "png"],
|
||||
enableAttachedDebuggerProtection: true,
|
||||
useDarkEmailViewer: true,
|
||||
enableUpdateChecker: true,
|
||||
enableUpdateChecker: false,
|
||||
musicInspirationEnabled: false,
|
||||
theme: "dark",
|
||||
};
|
||||
|
||||
class SettingsStore {
|
||||
@@ -33,6 +37,37 @@ class SettingsStore {
|
||||
console.error("Failed to load settings", e);
|
||||
}
|
||||
}
|
||||
|
||||
// Migration: Check for legacy musicInspirationEnabled key
|
||||
const legacyMusic = getFromLocalStorage("musicInspirationEnabled");
|
||||
if (legacyMusic !== null) {
|
||||
this.settings.musicInspirationEnabled = legacyMusic === "true";
|
||||
localStorage.removeItem("musicInspirationEnabled");
|
||||
this.save(); // Save immediately to persist the migration
|
||||
}
|
||||
|
||||
// Sync theme from localStorage key used in app.html
|
||||
const storedTheme = getStoredTheme();
|
||||
if (!this.settings.theme) {
|
||||
this.settings.theme = storedTheme;
|
||||
} else if (this.settings.theme !== storedTheme) {
|
||||
// If there's a mismatch, prioritize the theme from emly_theme key
|
||||
this.settings.theme = storedTheme;
|
||||
}
|
||||
|
||||
// Apply the theme
|
||||
applyTheme(this.settings.theme);
|
||||
|
||||
// Apply the language
|
||||
if (this.settings.selectedLanguage) {
|
||||
setLocale(this.settings.selectedLanguage);
|
||||
}
|
||||
|
||||
// Save defaults/merged settings to storage if they didn't exist or were updated during load
|
||||
if (!stored) {
|
||||
this.save();
|
||||
}
|
||||
|
||||
this.hasHydrated = true;
|
||||
}
|
||||
|
||||
@@ -43,11 +78,20 @@ class SettingsStore {
|
||||
|
||||
update(newSettings: Partial<EMLy_GUI_Settings>) {
|
||||
this.settings = { ...this.settings, ...newSettings };
|
||||
|
||||
// Apply theme if it changed
|
||||
if (newSettings.theme && this.settings.theme) {
|
||||
applyTheme(this.settings.theme);
|
||||
}
|
||||
|
||||
this.save();
|
||||
}
|
||||
|
||||
reset() {
|
||||
this.settings = { ...defaults };
|
||||
if (this.settings.theme) {
|
||||
applyTheme(this.settings.theme);
|
||||
}
|
||||
this.save();
|
||||
}
|
||||
}
|
||||
|
||||
2
frontend/src/lib/types.d.ts
vendored
2
frontend/src/lib/types.d.ts
vendored
@@ -10,6 +10,8 @@ interface EMLy_GUI_Settings {
|
||||
enableAttachedDebuggerProtection?: boolean;
|
||||
useDarkEmailViewer?: boolean;
|
||||
enableUpdateChecker?: boolean;
|
||||
musicInspirationEnabled?: boolean;
|
||||
theme?: "light" | "dark";
|
||||
}
|
||||
|
||||
type SupportedLanguages = "en" | "it";
|
||||
|
||||
45
frontend/src/lib/utils/theme.ts
Normal file
45
frontend/src/lib/utils/theme.ts
Normal file
@@ -0,0 +1,45 @@
|
||||
import { browser } from "$app/environment";
|
||||
|
||||
const THEME_KEY = "emly_theme";
|
||||
|
||||
export type Theme = "light" | "dark";
|
||||
|
||||
/**
|
||||
* Applies the theme to the document element and saves it to localStorage
|
||||
*/
|
||||
export function applyTheme(theme: Theme) {
|
||||
if (!browser) return;
|
||||
|
||||
const isDark = theme === "dark";
|
||||
document.documentElement.classList.toggle("dark", isDark);
|
||||
|
||||
try {
|
||||
localStorage.setItem(THEME_KEY, theme);
|
||||
} catch (e) {
|
||||
console.error("Failed to save theme to localStorage:", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the current theme from localStorage or returns the default
|
||||
*/
|
||||
export function getStoredTheme(): Theme {
|
||||
if (!browser) return "light";
|
||||
|
||||
try {
|
||||
const stored = localStorage.getItem(THEME_KEY);
|
||||
return stored === "light" || stored === "dark" ? stored : "light";
|
||||
} catch {
|
||||
return "light";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Toggles between light and dark theme
|
||||
*/
|
||||
export function toggleTheme(): Theme {
|
||||
const current = getStoredTheme();
|
||||
const newTheme: Theme = current === "dark" ? "light" : "dark";
|
||||
applyTheme(newTheme);
|
||||
return newTheme;
|
||||
}
|
||||
@@ -3,8 +3,7 @@
|
||||
import { page, navigating } from "$app/state";
|
||||
import { beforeNavigate, goto } from "$app/navigation";
|
||||
import { locales, localizeHref } from "$lib/paraglide/runtime";
|
||||
import { unsavedChanges, sidebarOpen, bugReportDialogOpen } from "$lib/stores/app";
|
||||
import "../layout.css";
|
||||
import { unsavedChanges, sidebarOpen, bugReportDialogOpen, dangerZoneEnabled } from "$lib/stores/app";
|
||||
import { onMount } from "svelte";
|
||||
import * as m from "$lib/paraglide/messages.js";
|
||||
import type { utils } from "$lib/wailsjs/go/models";
|
||||
@@ -100,6 +99,7 @@
|
||||
}
|
||||
|
||||
onMount(async () => {
|
||||
if(dev) dangerZoneEnabled.set(true);
|
||||
if (browser && isDebbugerProtectionOn) {
|
||||
detectDebugging();
|
||||
setInterval(detectDebugging, 1000);
|
||||
@@ -391,15 +391,17 @@
|
||||
style="cursor: pointer; opacity: 0.7;"
|
||||
class="hover:opacity-100 transition-opacity"
|
||||
/>
|
||||
<Music
|
||||
size="16"
|
||||
onclick={() => {
|
||||
if (page.url.pathname !== "/inspiration" && page.url.pathname !== "/inspiration/")
|
||||
goto("/inspiration");
|
||||
}}
|
||||
style="cursor: pointer; opacity: 0.7;"
|
||||
class="hover:opacity-100 transition-opacity"
|
||||
/>
|
||||
{#if settingsStore.settings.musicInspirationEnabled}
|
||||
<Music
|
||||
size="16"
|
||||
onclick={() => {
|
||||
if (page.url.pathname !== "/inspiration" && page.url.pathname !== "/inspiration/")
|
||||
goto("/inspiration");
|
||||
}}
|
||||
style="cursor: pointer; opacity: 0.7;"
|
||||
class="hover:opacity-100 transition-opacity"
|
||||
/>
|
||||
{/if}
|
||||
|
||||
<a
|
||||
data-sveltekit-reload
|
||||
@@ -568,8 +570,6 @@
|
||||
<style>
|
||||
:global(body) {
|
||||
margin: 0;
|
||||
background: oklch(0 0 0);
|
||||
color: #eaeaea;
|
||||
font-family: system-ui, sans-serif;
|
||||
}
|
||||
|
||||
@@ -578,11 +578,13 @@
|
||||
flex-direction: column;
|
||||
height: 100vh;
|
||||
overflow: hidden;
|
||||
background: var(--background);
|
||||
color: var(--foreground);
|
||||
}
|
||||
|
||||
.titlebar {
|
||||
height: 32px;
|
||||
background: oklch(0 0 0);
|
||||
background: var(--background);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
@@ -592,11 +594,12 @@
|
||||
flex: 0 0 32px;
|
||||
z-index: 50;
|
||||
position: relative;
|
||||
border-bottom: 1px solid var(--border);
|
||||
}
|
||||
|
||||
.footerbar {
|
||||
height: 32px;
|
||||
background: oklch(0 0 0);
|
||||
background: var(--background);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: flex-start;
|
||||
@@ -604,28 +607,28 @@
|
||||
padding: 0 12px;
|
||||
user-select: none;
|
||||
flex: 0 0 32px;
|
||||
border-top: 1px solid rgba(255, 255, 255, 0.08);
|
||||
border-top: 1px solid var(--border);
|
||||
}
|
||||
|
||||
.title {
|
||||
font-size: 13px;
|
||||
opacity: 0.9;
|
||||
color: gray;
|
||||
color: var(--muted-foreground);
|
||||
}
|
||||
|
||||
.title bold {
|
||||
font-weight: 600;
|
||||
color: white;
|
||||
color: var(--foreground);
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
.title version {
|
||||
color: rgb(228, 221, 221);
|
||||
opacity: 0.4;
|
||||
color: var(--muted-foreground);
|
||||
opacity: 0.6;
|
||||
}
|
||||
|
||||
.title version debug {
|
||||
color: #e11d48;
|
||||
color: var(--destructive);
|
||||
opacity: 1;
|
||||
font-weight: 600;
|
||||
}
|
||||
@@ -642,8 +645,9 @@
|
||||
position: absolute;
|
||||
top: 100%;
|
||||
left: 0;
|
||||
background-color: #111;
|
||||
border: 1px solid rgba(255, 255, 255, 0.1);
|
||||
background-color: var(--popover);
|
||||
color: var(--popover-foreground);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 6px;
|
||||
padding: 8px 12px;
|
||||
z-index: 1000;
|
||||
@@ -673,16 +677,16 @@
|
||||
}
|
||||
|
||||
.tooltip-item .label {
|
||||
color: #9ca3af;
|
||||
color: var(--muted-foreground);
|
||||
}
|
||||
|
||||
.tooltip-item .value {
|
||||
color: #f3f4f6;
|
||||
color: var(--foreground);
|
||||
font-family: monospace;
|
||||
}
|
||||
|
||||
.tooltip-item .channel {
|
||||
color: #6b7280;
|
||||
color: var(--muted-foreground);
|
||||
font-size: 10px;
|
||||
}
|
||||
|
||||
@@ -697,20 +701,20 @@
|
||||
height: 100%;
|
||||
border: none;
|
||||
background: transparent;
|
||||
color: white;
|
||||
color: var(--foreground);
|
||||
font-size: 14px;
|
||||
cursor: pointer;
|
||||
-webkit-app-region: no-drag;
|
||||
}
|
||||
|
||||
.btn:hover {
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
background: var(--accent);
|
||||
}
|
||||
|
||||
.btn:disabled {
|
||||
opacity: 0.5;
|
||||
cursor: not-allowed;
|
||||
background: rgba(255, 255, 255, 0.02);
|
||||
background: var(--muted);
|
||||
}
|
||||
|
||||
.close:hover {
|
||||
@@ -721,7 +725,7 @@
|
||||
flex: 1 1 auto;
|
||||
min-height: 0;
|
||||
display: flex;
|
||||
background: oklch(0 0 0);
|
||||
background: var(--background);
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
}
|
||||
@@ -758,12 +762,12 @@
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb {
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
background: var(--border);
|
||||
border-radius: 6px;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb:hover {
|
||||
background: rgba(255, 255, 255, 0.2);
|
||||
background: var(--muted-foreground);
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-corner {
|
||||
@@ -779,14 +783,14 @@
|
||||
gap: 10px;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: oklch(0 0 0);
|
||||
background: var(--background);
|
||||
}
|
||||
|
||||
.spinner {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
border: 2px solid rgba(255, 255, 255, 0.1);
|
||||
border-top-color: rgba(255, 255, 255, 0.8);
|
||||
border: 2px solid var(--border);
|
||||
border-top-color: var(--primary);
|
||||
border-radius: 50%;
|
||||
animation: spin 0.6s linear infinite;
|
||||
}
|
||||
@@ -807,12 +811,12 @@
|
||||
}
|
||||
|
||||
:global(.custom-scrollbar::-webkit-scrollbar-thumb) {
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
background: var(--border);
|
||||
border-radius: 6px;
|
||||
}
|
||||
|
||||
:global(.custom-scrollbar::-webkit-scrollbar-thumb:hover) {
|
||||
background: rgba(255, 255, 255, 0.2);
|
||||
background: var(--muted-foreground);
|
||||
}
|
||||
|
||||
:global(.custom-scrollbar::-webkit-scrollbar-corner) {
|
||||
|
||||
@@ -1,15 +1,48 @@
|
||||
<script lang="ts">
|
||||
import { goto } from "$app/navigation";
|
||||
import { goto, preloadData } from "$app/navigation";
|
||||
import { Button } from "$lib/components/ui/button";
|
||||
import * as Card from "$lib/components/ui/card";
|
||||
import { Separator } from "$lib/components/ui/separator";
|
||||
import { ChevronLeft, Heart, Code, Package, Globe, Github, Mail, BadgeInfo } from "@lucide/svelte";
|
||||
import { ChevronLeft, Heart, Code, Package, Globe, Github, Mail, BadgeInfo, Music, PartyPopper } from "@lucide/svelte";
|
||||
import * as m from "$lib/paraglide/messages";
|
||||
import { OpenURLInBrowser } from "$lib/wailsjs/go/main/App";
|
||||
import { dangerZoneEnabled } from "$lib/stores/app";
|
||||
import { settingsStore } from "$lib/stores/settings.svelte";
|
||||
import { toast } from "svelte-sonner";
|
||||
|
||||
let { data } = $props();
|
||||
let config = $derived(data.config);
|
||||
|
||||
// Easter Egg State
|
||||
const REQUIRED_CLICKS = 10;
|
||||
const CLICK_WINDOW_MS = 4000;
|
||||
let recentClicks: number[] = [];
|
||||
|
||||
function handleEasterEggClick(_event: MouseEvent) {
|
||||
console.log("clicked")
|
||||
// Only proceed if danger zone is already enabled
|
||||
if (!$dangerZoneEnabled) return;
|
||||
|
||||
// If already enabled, do nothing to avoid spam
|
||||
if (settingsStore.settings.musicInspirationEnabled) return;
|
||||
|
||||
const now = Date.now();
|
||||
|
||||
// Clean old clicks
|
||||
recentClicks = recentClicks.filter(t => now - t < CLICK_WINDOW_MS);
|
||||
recentClicks.push(now);
|
||||
|
||||
if (recentClicks.length >= REQUIRED_CLICKS) {
|
||||
recentClicks = [];
|
||||
try {
|
||||
settingsStore.update({ musicInspirationEnabled: true });
|
||||
preloadData("/inspiration");
|
||||
} catch (e) {
|
||||
console.error("Failed to enable music inspiration:", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Open external URL in default browser
|
||||
async function openUrl(url: string) {
|
||||
try {
|
||||
@@ -67,7 +100,7 @@
|
||||
];
|
||||
</script>
|
||||
|
||||
<div class="min-h-[calc(100vh-1rem)] from-background to-muted/30">
|
||||
<div class="min-h-[calc(100vh-1rem)] bg-gradient-to-b from-background to-muted/30">
|
||||
<div
|
||||
class="mx-auto flex max-w-3xl flex-col gap-4 px-4 py-6 sm:px-6 sm:py-10 opacity-80"
|
||||
>
|
||||
@@ -132,19 +165,29 @@
|
||||
</Card.Header>
|
||||
<Card.Content class="space-y-4">
|
||||
{#each team as member}
|
||||
<div class="flex items-start gap-4 rounded-lg border bg-card p-4">
|
||||
<!-- svelte-ignore a11y_click_events_have_key_events -->
|
||||
<!-- svelte-ignore a11y_no_static_element_interactions -->
|
||||
<div
|
||||
class="flex items-start gap-4 rounded-lg border bg-card p-4 relative overflow-hidden"
|
||||
onclick={member.username === "FOISX" ? handleEasterEggClick : undefined}
|
||||
>
|
||||
<!-- Selectable trigger area overlay for cleaner interaction -->
|
||||
{#if member.username === "FOISX" && $dangerZoneEnabled && !settingsStore.settings.musicInspirationEnabled}
|
||||
<div class="absolute inset-0 cursor-pointer z-10 opacity-0 bg-transparent"></div>
|
||||
{/if}
|
||||
|
||||
<img
|
||||
src={gravatarUrls[member.email]}
|
||||
alt={member.name}
|
||||
class="h-14 w-14 rounded-full border-2 border-primary/20"
|
||||
class="h-14 w-14 rounded-full border-2 border-primary/20 z-0 select-none"
|
||||
/>
|
||||
<div class="flex-1">
|
||||
<div class="flex-1 z-0">
|
||||
<div class="font-medium">{member.username} ({member.name})</div>
|
||||
<div class="text-sm text-primary/80">{member.role}</div>
|
||||
<div class="text-sm text-muted-foreground mt-1">{member.description}</div>
|
||||
<a
|
||||
href="mailto:{member.email}"
|
||||
class="inline-flex items-center gap-1 text-xs text-muted-foreground hover:text-primary mt-2 transition-colors"
|
||||
class="inline-flex items-center gap-1 text-xs text-muted-foreground hover:text-primary mt-2 transition-colors relative z-20"
|
||||
>
|
||||
<Mail class="size-3" />
|
||||
{member.email}
|
||||
|
||||
@@ -6,69 +6,11 @@
|
||||
import { ChevronLeft, Music, ExternalLink } from "@lucide/svelte";
|
||||
import * as m from "$lib/paraglide/messages";
|
||||
import { OpenURLInBrowser } from "$lib/wailsjs/go/main/App";
|
||||
import type { SpotifyTrack } from "./+page";
|
||||
|
||||
let { data } = $props();
|
||||
let config = $derived(data.config);
|
||||
|
||||
interface SpotifyTrack {
|
||||
name: string;
|
||||
artist: string;
|
||||
albumArt?: string;
|
||||
spotifyUrl: string;
|
||||
embedUrl: string;
|
||||
}
|
||||
|
||||
// Music that inspired this project
|
||||
const inspirationTracks: SpotifyTrack[] = [
|
||||
{
|
||||
name: "Strays",
|
||||
artist: "Ivycomb, Stephanafro",
|
||||
spotifyUrl: "https://open.spotify.com/track/1aXATIo34e5ZZvFcavePpy",
|
||||
embedUrl: "https://open.spotify.com/embed/track/1aXATIo34e5ZZvFcavePpy?utm_source=generator"
|
||||
},
|
||||
{
|
||||
name: "Headlock",
|
||||
artist: "Imogen Heap",
|
||||
spotifyUrl: "https://open.spotify.com/track/63Pi2NAx5yCgeLhCTOrEou",
|
||||
embedUrl: "https://open.spotify.com/embed/track/63Pi2NAx5yCgeLhCTOrEou?utm_source=generator"
|
||||
},
|
||||
{
|
||||
name: "I Still Create",
|
||||
artist: "YonKaGor",
|
||||
spotifyUrl: "https://open.spotify.com/track/0IqTgwWU2syiSYbdBEromt",
|
||||
embedUrl: "https://open.spotify.com/embed/track/0IqTgwWU2syiSYbdBEromt?utm_source=generator"
|
||||
},
|
||||
{
|
||||
name: "Raised by Aliens",
|
||||
artist: "ivy comb, Stephanafro",
|
||||
spotifyUrl: "https://open.spotify.com/track/5ezyCaoc5XiVdkpRYWeyG5",
|
||||
embedUrl: "https://open.spotify.com/embed/track/5ezyCaoc5XiVdkpRYWeyG5?utm_source=generator"
|
||||
},
|
||||
{
|
||||
name: "VENOMOUS",
|
||||
artist: "passengerprincess",
|
||||
spotifyUrl: "https://open.spotify.com/track/4rPKifkzrhIYAsl1njwmjd",
|
||||
embedUrl: "https://open.spotify.com/embed/track/4rPKifkzrhIYAsl1njwmjd?utm_source=generator"
|
||||
},
|
||||
{
|
||||
name: "PREY",
|
||||
artist: "passengerprincess",
|
||||
spotifyUrl: "https://open.spotify.com/track/510m8qwFCHgzi4zsQnjLUX",
|
||||
embedUrl: "https://open.spotify.com/embed/track/510m8qwFCHgzi4zsQnjLUX?utm_source=generator"
|
||||
},
|
||||
{
|
||||
name: "Dracula",
|
||||
artist: "Tame Impala",
|
||||
spotifyUrl: "https://open.spotify.com/track/1NXbNEAcPvY5G1xvfN57aA",
|
||||
embedUrl: "https://open.spotify.com/embed/track/1NXbNEAcPvY5G1xvfN57aA?utm_source=generator"
|
||||
},
|
||||
{
|
||||
name: "Electric love",
|
||||
artist: "When Snakes Sing",
|
||||
spotifyUrl: "https://open.spotify.com/track/1nDkT2Cn13qDnFegF93UHi",
|
||||
embedUrl: "https://open.spotify.com/embed/track/1nDkT2Cn13qDnFegF93UHi?utm_source=generator"
|
||||
}
|
||||
];
|
||||
let tracks: SpotifyTrack[] = $derived(data.tracks ?? []);
|
||||
|
||||
// Open external URL in default browser
|
||||
async function openUrl(url: string) {
|
||||
@@ -80,7 +22,7 @@
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="min-h-[calc(100vh-1rem)] from-background to-muted/30">
|
||||
<div class="min-h-[calc(100vh-1rem)] bg-gradient-to-b from-background to-muted/30">
|
||||
<div
|
||||
class="mx-auto flex max-w-4xl flex-col gap-4 px-4 py-6 sm:px-6 sm:py-10 opacity-80"
|
||||
>
|
||||
@@ -119,37 +61,25 @@
|
||||
</Card.Header>
|
||||
<Card.Content>
|
||||
<div class="grid gap-4 sm:grid-cols-2 lg:grid-cols-2">
|
||||
{#each inspirationTracks as track}
|
||||
{#each tracks as track}
|
||||
<div class="group relative">
|
||||
<div class="overflow-hidden rounded-lg bg-muted" style="height: 352px;">
|
||||
<iframe
|
||||
src={track.embedUrl}
|
||||
width="100%"
|
||||
height="352"
|
||||
frameborder="0"
|
||||
allow="autoplay; clipboard-write; encrypted-media; fullscreen; picture-in-picture"
|
||||
loading="lazy"
|
||||
title={`${track.artist} - ${track.name}`}
|
||||
class="rounded-lg"
|
||||
></iframe>
|
||||
</div>
|
||||
<div class="mt-2 flex items-start justify-between gap-2">
|
||||
<div class="min-w-0 flex-1">
|
||||
<p class="truncate text-sm font-medium">{track.name}</p>
|
||||
<p class="truncate text-xs text-muted-foreground">
|
||||
{track.artist}
|
||||
</p>
|
||||
</div>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
class="size-8 shrink-0 opacity-70 hover:opacity-100"
|
||||
onclick={() => openUrl(track.spotifyUrl)}
|
||||
title="Open in Spotify"
|
||||
>
|
||||
<ExternalLink class="size-4" />
|
||||
</Button>
|
||||
<div class="overflow-hidden rounded-lg bg-muted">
|
||||
{#if track.embedHtml}
|
||||
{@html track.embedHtml}
|
||||
{:else}
|
||||
<iframe
|
||||
src={track.embedUrl}
|
||||
width="100%"
|
||||
height="352"
|
||||
frameborder="0"
|
||||
allow="autoplay; clipboard-write; encrypted-media; fullscreen; picture-in-picture"
|
||||
loading="lazy"
|
||||
title={`${track.artist} - ${track.name}`}
|
||||
class="rounded-lg"
|
||||
></iframe>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
@@ -184,10 +114,6 @@
|
||||
and
|
||||
<span class="text-red-500">♥</span>
|
||||
</p>
|
||||
<p class="mt-1">
|
||||
GUI: {config ? `v${config.GUISemver}` : "N/A"} •
|
||||
SDK: {config ? `v${config.SDKDecoderSemver}` : "N/A"}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -2,18 +2,98 @@ import type { PageLoad } from './$types';
|
||||
import { GetConfig } from "$lib/wailsjs/go/main/App";
|
||||
import { browser } from '$app/environment';
|
||||
|
||||
export const load = (async () => {
|
||||
if (!browser) return { config: null };
|
||||
export interface SpotifyTrack {
|
||||
name: string;
|
||||
artist: string;
|
||||
spotifyUrl: string;
|
||||
embedUrl: string;
|
||||
embedHtml?: string;
|
||||
}
|
||||
|
||||
// Music that inspired this project
|
||||
const inspirationTracks: SpotifyTrack[] = [
|
||||
{
|
||||
name: "Strays",
|
||||
artist: "Ivycomb, Stephanafro",
|
||||
spotifyUrl: "https://open.spotify.com/track/1aXATIo34e5ZZvFcavePpy",
|
||||
embedUrl: "https://open.spotify.com/embed/track/1aXATIo34e5ZZvFcavePpy?utm_source=generator"
|
||||
},
|
||||
{
|
||||
name: "Headlock",
|
||||
artist: "Imogen Heap",
|
||||
spotifyUrl: "https://open.spotify.com/track/63Pi2NAx5yCgeLhCTOrEou",
|
||||
embedUrl: "https://open.spotify.com/embed/track/63Pi2NAx5yCgeLhCTOrEou?utm_source=generator"
|
||||
},
|
||||
{
|
||||
name: "I Still Create",
|
||||
artist: "YonKaGor",
|
||||
spotifyUrl: "https://open.spotify.com/track/0IqTgwWU2syiSYbdBEromt",
|
||||
embedUrl: "https://open.spotify.com/embed/track/0IqTgwWU2syiSYbdBEromt?utm_source=generator"
|
||||
},
|
||||
{
|
||||
name: "Raised by Aliens",
|
||||
artist: "ivy comb, Stephanafro",
|
||||
spotifyUrl: "https://open.spotify.com/track/5ezyCaoc5XiVdkpRYWeyG5",
|
||||
embedUrl: "https://open.spotify.com/embed/track/5ezyCaoc5XiVdkpRYWeyG5?utm_source=generator"
|
||||
},
|
||||
{
|
||||
name: "VENOMOUS",
|
||||
artist: "passengerprincess",
|
||||
spotifyUrl: "https://open.spotify.com/track/4rPKifkzrhIYAsl1njwmjd",
|
||||
embedUrl: "https://open.spotify.com/embed/track/4rPKifkzrhIYAsl1njwmjd?utm_source=generator"
|
||||
},
|
||||
{
|
||||
name: "PREY",
|
||||
artist: "passengerprincess",
|
||||
spotifyUrl: "https://open.spotify.com/track/510m8qwFCHgzi4zsQnjLUX",
|
||||
embedUrl: "https://open.spotify.com/embed/track/510m8qwFCHgzi4zsQnjLUX?utm_source=generator"
|
||||
},
|
||||
{
|
||||
name: "Dracula",
|
||||
artist: "Tame Impala",
|
||||
spotifyUrl: "https://open.spotify.com/track/1NXbNEAcPvY5G1xvfN57aA",
|
||||
embedUrl: "https://open.spotify.com/embed/track/1NXbNEAcPvY5G1xvfN57aA?utm_source=generator"
|
||||
},
|
||||
{
|
||||
name: "Electric love",
|
||||
artist: "When Snakes Sing",
|
||||
spotifyUrl: "https://open.spotify.com/track/1nDkT2Cn13qDnFegF93UHi",
|
||||
embedUrl: "https://open.spotify.com/embed/track/1nDkT2Cn13qDnFegF93UHi?utm_source=generator"
|
||||
}
|
||||
];
|
||||
|
||||
async function fetchEmbedHtml(track: SpotifyTrack, fetch: typeof globalThis.fetch): Promise<SpotifyTrack> {
|
||||
try {
|
||||
const oEmbedUrl = `https://open.spotify.com/oembed?url=${encodeURIComponent(track.spotifyUrl)}`;
|
||||
const res = await fetch(oEmbedUrl);
|
||||
if (res.ok) {
|
||||
const data = await res.json();
|
||||
return { ...track, embedHtml: data.html };
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(`Failed to fetch oEmbed for ${track.name}:`, e);
|
||||
}
|
||||
return track;
|
||||
}
|
||||
|
||||
export const load = (async ({fetch}) => {
|
||||
if (!browser) return { config: null, tracks: inspirationTracks };
|
||||
|
||||
try {
|
||||
const configRoot = await GetConfig();
|
||||
const [configRoot, ...tracks] = await Promise.all([
|
||||
GetConfig(),
|
||||
...inspirationTracks.map(t => fetchEmbedHtml(t, fetch))
|
||||
]);
|
||||
|
||||
return {
|
||||
config: configRoot.EMLy
|
||||
config: configRoot.EMLy,
|
||||
tracks
|
||||
};
|
||||
} catch (e) {
|
||||
console.error("Failed to load config for inspiration", e);
|
||||
console.error("Failed to load data for inspiration", e);
|
||||
return {
|
||||
config: null
|
||||
config: null,
|
||||
tracks: inspirationTracks
|
||||
};
|
||||
}
|
||||
}) satisfies PageLoad;
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
import { Label } from "$lib/components/ui/label";
|
||||
import { Separator } from "$lib/components/ui/separator";
|
||||
import { Switch } from "$lib/components/ui/switch";
|
||||
import { ChevronLeft, Flame, Download, Upload, RefreshCw, CheckCircle2, AlertCircle } from "@lucide/svelte";
|
||||
import { ChevronLeft, Flame, Download, Upload, RefreshCw, CheckCircle2, AlertCircle, Sun, Moon } from "@lucide/svelte";
|
||||
import type { EMLy_GUI_Settings } from "$lib/types";
|
||||
import { toast } from "svelte-sonner";
|
||||
import { It, Us } from "svelte-flags";
|
||||
@@ -40,7 +40,8 @@
|
||||
previewFileSupportedTypes: ["jpg", "jpeg", "png"],
|
||||
enableAttachedDebuggerProtection: true,
|
||||
useDarkEmailViewer: true,
|
||||
enableUpdateChecker: true,
|
||||
enableUpdateChecker: false,
|
||||
theme: "dark",
|
||||
};
|
||||
|
||||
async function setLanguage(
|
||||
@@ -73,8 +74,10 @@
|
||||
s.enableAttachedDebuggerProtection ?? defaults.enableAttachedDebuggerProtection ?? true,
|
||||
useDarkEmailViewer:
|
||||
s.useDarkEmailViewer ?? defaults.useDarkEmailViewer ?? true,
|
||||
enableUpdateChecker:
|
||||
s.enableUpdateChecker ?? defaults.enableUpdateChecker ?? true,
|
||||
enableUpdateChecker: runningInDevMode
|
||||
? false
|
||||
: (s.enableUpdateChecker ?? defaults.enableUpdateChecker ?? true),
|
||||
theme: s.theme || defaults.theme || "light",
|
||||
};
|
||||
}
|
||||
|
||||
@@ -86,6 +89,7 @@
|
||||
!!a.enableAttachedDebuggerProtection === !!b.enableAttachedDebuggerProtection &&
|
||||
!!a.useDarkEmailViewer === !!b.useDarkEmailViewer &&
|
||||
!!a.enableUpdateChecker === !!b.enableUpdateChecker &&
|
||||
(a.theme ?? "light") === (b.theme ?? "light") &&
|
||||
JSON.stringify(a.previewFileSupportedTypes?.sort()) ===
|
||||
JSON.stringify(b.previewFileSupportedTypes?.sort())
|
||||
);
|
||||
@@ -278,25 +282,25 @@
|
||||
updateStatus = status;
|
||||
|
||||
if (status.updateAvailable) {
|
||||
toast.success(`Update available: ${status.availableVersion}`);
|
||||
toast.success(m.settings_toast_update_available({ version: status.availableVersion }));
|
||||
} else if (!status.errorMessage) {
|
||||
toast.info("You're on the latest version");
|
||||
toast.info(m.settings_toast_latest_version());
|
||||
} else {
|
||||
toast.error(status.errorMessage);
|
||||
}
|
||||
} catch (err) {
|
||||
console.error("Failed to check for updates:", err);
|
||||
toast.error("Failed to check for updates");
|
||||
toast.error(m.settings_toast_check_failed());
|
||||
}
|
||||
}
|
||||
|
||||
async function downloadUpdate() {
|
||||
try {
|
||||
await DownloadUpdate();
|
||||
toast.success("Update downloaded successfully");
|
||||
toast.success(m.settings_toast_download_success());
|
||||
} catch (err) {
|
||||
console.error("Failed to download update:", err);
|
||||
toast.error("Failed to download update");
|
||||
toast.error(m.settings_toast_download_failed());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -306,7 +310,7 @@
|
||||
// App will quit, so no toast needed
|
||||
} catch (err) {
|
||||
console.error("Failed to install update:", err);
|
||||
toast.error("Failed to launch installer");
|
||||
toast.error(m.settings_toast_install_failed());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -324,7 +328,7 @@
|
||||
});
|
||||
</script>
|
||||
|
||||
<div class="min-h-[calc(100vh-1rem)] from-background to-muted/30">
|
||||
<div class="min-h-[calc(100vh-1rem)] bg-gradient-to-b from-background to-muted/30">
|
||||
<div
|
||||
class="mx-auto flex max-w-3xl flex-col gap-4 px-4 py-6 sm:px-6 sm:py-10 opacity-80"
|
||||
>
|
||||
@@ -393,6 +397,52 @@
|
||||
</Card.Content>
|
||||
</Card.Root>
|
||||
|
||||
<Card.Root>
|
||||
<Card.Header class="space-y-1">
|
||||
<Card.Title>{m.settings_appearance_title()}</Card.Title>
|
||||
<Card.Description>{m.settings_appearance_description()}</Card.Description>
|
||||
</Card.Header>
|
||||
<Card.Content>
|
||||
<RadioGroup.Root
|
||||
bind:value={form.theme}
|
||||
class="flex flex-col gap-3"
|
||||
>
|
||||
<div class="flex items-center space-x-2">
|
||||
<RadioGroup.Item
|
||||
value="light"
|
||||
id="theme-light"
|
||||
class="cursor-pointer hover:cursor-pointer"
|
||||
/>
|
||||
<Label
|
||||
for="theme-light"
|
||||
class="flex items-center gap-2 cursor-pointer hover:cursor-pointer"
|
||||
>
|
||||
<Sun class="size-4" />
|
||||
{m.settings_theme_light()}
|
||||
</Label>
|
||||
</div>
|
||||
<div class="flex items-center space-x-2">
|
||||
<RadioGroup.Item
|
||||
value="dark"
|
||||
id="theme-dark"
|
||||
class="cursor-pointer hover:cursor-pointer"
|
||||
/>
|
||||
<Label
|
||||
for="theme-dark"
|
||||
class="flex items-center gap-2 cursor-pointer hover:cursor-pointer"
|
||||
>
|
||||
<Moon class="size-4" />
|
||||
{m.settings_theme_dark()}
|
||||
</Label>
|
||||
</div>
|
||||
</RadioGroup.Root>
|
||||
<div class="text-xs text-muted-foreground mt-4">
|
||||
<strong>Info:</strong>
|
||||
{m.settings_theme_hint()}
|
||||
</div>
|
||||
</Card.Content>
|
||||
</Card.Root>
|
||||
|
||||
<Card.Root>
|
||||
<Card.Header class="space-y-1">
|
||||
<Card.Title>{m.settings_export_import_title()}</Card.Title>
|
||||
@@ -592,14 +642,14 @@
|
||||
{#if form.enableUpdateChecker}
|
||||
<Card.Root>
|
||||
<Card.Header class="space-y-1">
|
||||
<Card.Title>Updates</Card.Title>
|
||||
<Card.Description>Check for and install application updates from your network share</Card.Description>
|
||||
<Card.Title>{m.settings_updates_title()}</Card.Title>
|
||||
<Card.Description>{m.settings_updates_description()}</Card.Description>
|
||||
</Card.Header>
|
||||
<Card.Content class="space-y-4">
|
||||
<!-- Current Version -->
|
||||
<div class="flex items-center justify-between gap-4 rounded-lg border bg-card p-4">
|
||||
<div>
|
||||
<div class="font-medium">Current Version</div>
|
||||
<div class="font-medium">{m.settings_updates_current_version()}</div>
|
||||
<div class="text-sm text-muted-foreground">
|
||||
{updateStatus.currentVersion} ({config?.GUIReleaseChannel || "stable"})
|
||||
</div>
|
||||
@@ -607,17 +657,17 @@
|
||||
{#if updateStatus.updateAvailable}
|
||||
<div class="flex items-center gap-2 text-sm font-medium text-green-600 dark:text-green-400">
|
||||
<AlertCircle class="size-4" />
|
||||
Update Available
|
||||
{m.settings_updates_available()}
|
||||
</div>
|
||||
{:else if updateStatus.errorMessage && updateStatus.lastCheckTime}
|
||||
<div class="flex items-center gap-2 text-sm text-destructive">
|
||||
<AlertCircle class="size-4" />
|
||||
Check failed
|
||||
{m.settings_updates_check_failed()}
|
||||
</div>
|
||||
{:else if updateStatus.lastCheckTime}
|
||||
<div class="flex items-center gap-2 text-sm text-muted-foreground">
|
||||
<CheckCircle2 class="size-4" />
|
||||
No updates found
|
||||
{m.settings_updates_no_updates()}
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
@@ -627,12 +677,12 @@
|
||||
<!-- Check for Updates -->
|
||||
<div class="flex items-center justify-between gap-4 rounded-lg border bg-card p-4">
|
||||
<div>
|
||||
<div class="font-medium">Check for Updates</div>
|
||||
<div class="font-medium">{m.settings_updates_check_label()}</div>
|
||||
<div class="text-sm text-muted-foreground">
|
||||
{#if updateStatus.lastCheckTime}
|
||||
Last checked: {updateStatus.lastCheckTime}
|
||||
{m.settings_updates_last_checked({ time: updateStatus.lastCheckTime })}
|
||||
{:else}
|
||||
Click to check for available updates
|
||||
{m.settings_updates_click_check()}
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
@@ -643,7 +693,7 @@
|
||||
disabled={updateStatus.checking || updateStatus.downloading}
|
||||
>
|
||||
<RefreshCw class="size-4 mr-2 {updateStatus.checking ? 'animate-spin' : ''}" />
|
||||
{updateStatus.checking ? "Checking..." : "Check Now"}
|
||||
{updateStatus.checking ? m.settings_updates_checking() : m.settings_updates_check_now()}
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
@@ -652,12 +702,12 @@
|
||||
<Separator />
|
||||
<div class="flex items-center justify-between gap-4 rounded-lg border border-blue-500/30 bg-blue-500/10 p-4">
|
||||
<div>
|
||||
<div class="font-medium">Version {updateStatus.availableVersion} Available</div>
|
||||
<div class="font-medium">{m.settings_updates_version_available({ version: updateStatus.availableVersion })}</div>
|
||||
<div class="text-sm text-muted-foreground">
|
||||
{#if updateStatus.downloading}
|
||||
Downloading... {updateStatus.downloadProgress}%
|
||||
{m.settings_updates_downloading({ progress: updateStatus.downloadProgress })}
|
||||
{:else}
|
||||
Click to download the update
|
||||
{m.settings_updates_click_download()}
|
||||
{/if}
|
||||
</div>
|
||||
{#if updateStatus.releaseNotes}
|
||||
@@ -673,7 +723,7 @@
|
||||
disabled={updateStatus.downloading}
|
||||
>
|
||||
<Download class="size-4 mr-2" />
|
||||
{updateStatus.downloading ? `${updateStatus.downloadProgress}%` : "Download"}
|
||||
{updateStatus.downloading ? `${updateStatus.downloadProgress}%` : m.settings_updates_download_button()}
|
||||
</Button>
|
||||
</div>
|
||||
{/if}
|
||||
@@ -683,9 +733,9 @@
|
||||
<Separator />
|
||||
<div class="flex items-center justify-between gap-4 rounded-lg border border-green-500/30 bg-green-500/10 p-4">
|
||||
<div>
|
||||
<div class="font-medium">Update Ready to Install</div>
|
||||
<div class="font-medium">{m.settings_updates_ready_title()}</div>
|
||||
<div class="text-sm text-muted-foreground">
|
||||
Version {updateStatus.availableVersion} has been downloaded and verified
|
||||
{m.settings_updates_ready_ref({ version: updateStatus.availableVersion })}
|
||||
</div>
|
||||
</div>
|
||||
<Button
|
||||
@@ -694,7 +744,7 @@
|
||||
onclick={installUpdate}
|
||||
>
|
||||
<CheckCircle2 class="size-4 mr-2" />
|
||||
Install Now
|
||||
{m.settings_updates_install_button()}
|
||||
</Button>
|
||||
</div>
|
||||
{/if}
|
||||
@@ -713,11 +763,11 @@
|
||||
|
||||
<!-- Info about update path -->
|
||||
<div class="text-xs text-muted-foreground">
|
||||
<strong>Info:</strong> Updates are checked from your configured network share path.
|
||||
<strong>Info:</strong> {m.settings_updates_info_message()}
|
||||
{#if (config as any)?.UpdatePath}
|
||||
Current path: <code class="text-xs bg-muted px-1 py-0.5 rounded">{(config as any).UpdatePath}</code>
|
||||
{m.settings_updates_current_path()} <code class="text-xs bg-muted px-1 py-0.5 rounded">{(config as any).UpdatePath}</code>
|
||||
{:else}
|
||||
<span class="text-amber-600 dark:text-amber-400">No update path configured</span>
|
||||
<span class="text-amber-600 dark:text-amber-400">{m.settings_updates_no_path()}</span>
|
||||
{/if}
|
||||
</div>
|
||||
</Card.Content>
|
||||
@@ -843,18 +893,19 @@
|
||||
class="flex items-center justify-between gap-4 rounded-lg border bg-card p-4 border-destructive/30"
|
||||
>
|
||||
<div class="space-y-1">
|
||||
<Label class="text-sm">Enable Update Checker</Label>
|
||||
<Label class="text-sm">{m.settings_danger_update_checker_label()}</Label>
|
||||
<div class="text-sm text-muted-foreground">
|
||||
Check for application updates from network share
|
||||
{m.settings_danger_update_checker_hint()}
|
||||
</div>
|
||||
</div>
|
||||
<Switch
|
||||
bind:checked={form.enableUpdateChecker}
|
||||
class="cursor-pointer hover:cursor-pointer"
|
||||
disabled={runningInDevMode}
|
||||
/>
|
||||
</div>
|
||||
<div class="text-xs text-muted-foreground">
|
||||
<strong>Info:</strong> When enabled, the app will check for updates from your configured network share. Disable this if you manage updates manually or don't have network access.
|
||||
{m.settings_danger_update_checker_info()}
|
||||
</div>
|
||||
<Separator />
|
||||
|
||||
@@ -871,6 +922,7 @@
|
||||
</Card.Root>
|
||||
{/if}
|
||||
|
||||
{#if !runningInDevMode}
|
||||
<AlertDialog.Root bind:open={dangerWarningOpen}>
|
||||
<AlertDialog.Content>
|
||||
<AlertDialog.Header>
|
||||
@@ -878,7 +930,11 @@
|
||||
>{m.settings_danger_alert_title()}</AlertDialog.Title
|
||||
>
|
||||
<AlertDialog.Description>
|
||||
{m.settings_danger_alert_description()}
|
||||
{m.settings_danger_alert_description_part1()}
|
||||
<br />
|
||||
{m.settings_danger_alert_description_part2()}
|
||||
<br />
|
||||
{m.settings_danger_alert_description_part3()}
|
||||
</AlertDialog.Description>
|
||||
</AlertDialog.Header>
|
||||
<AlertDialog.Footer>
|
||||
@@ -888,5 +944,6 @@
|
||||
</AlertDialog.Footer>
|
||||
</AlertDialog.Content>
|
||||
</AlertDialog.Root>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
<script lang="ts">
|
||||
import { onMount } from 'svelte';
|
||||
import { setupConsoleLogger } from '$lib/utils/logger-hook';
|
||||
import "./layout.css";
|
||||
|
||||
let { children } = $props();
|
||||
|
||||
|
||||
@@ -72,7 +72,8 @@
|
||||
<style>
|
||||
:global(body) {
|
||||
margin: 0;
|
||||
background: #000;
|
||||
background: var(--background);
|
||||
color: var(--foreground);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
@@ -81,11 +82,13 @@
|
||||
flex-direction: column;
|
||||
height: 100vh;
|
||||
overflow: hidden;
|
||||
background: var(--background);
|
||||
color: var(--foreground);
|
||||
}
|
||||
|
||||
.titlebar {
|
||||
height: 32px;
|
||||
background: #000;
|
||||
background: var(--background);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
@@ -94,13 +97,13 @@
|
||||
user-select: none;
|
||||
flex: 0 0 32px;
|
||||
z-index: 50;
|
||||
border-bottom: 1px solid rgba(255, 255, 255, 0.08);
|
||||
border-bottom: 1px solid var(--border);
|
||||
}
|
||||
|
||||
.title {
|
||||
font-size: 13px;
|
||||
font-weight: 500;
|
||||
color: rgba(255, 255, 255, 0.7);
|
||||
color: var(--muted-foreground);
|
||||
}
|
||||
|
||||
.controls {
|
||||
@@ -114,7 +117,7 @@
|
||||
height: 100%;
|
||||
border: none;
|
||||
background: transparent;
|
||||
color: white;
|
||||
color: var(--foreground);
|
||||
font-size: 14px;
|
||||
cursor: pointer;
|
||||
-webkit-app-region: no-drag;
|
||||
@@ -124,16 +127,18 @@
|
||||
}
|
||||
|
||||
.btn:hover {
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
background: var(--accent);
|
||||
}
|
||||
|
||||
.close:hover {
|
||||
background: #e81123;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.content {
|
||||
flex: 1;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
background: var(--background);
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -177,7 +177,7 @@
|
||||
<style>
|
||||
:global(body) {
|
||||
margin: 0;
|
||||
background: #000;
|
||||
background: var(--background);
|
||||
}
|
||||
|
||||
.page-container {
|
||||
@@ -185,15 +185,15 @@
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
background: #000;
|
||||
color: white;
|
||||
background: var(--background);
|
||||
color: var(--foreground);
|
||||
font-family: system-ui, -apple-system, sans-serif;
|
||||
}
|
||||
|
||||
.toolbar {
|
||||
height: 50px;
|
||||
background: rgba(255, 255, 255, 0.04);
|
||||
border-bottom: 1px solid rgba(255, 255, 255, 0.08);
|
||||
background: var(--card);
|
||||
border-bottom: 1px solid var(--border);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
@@ -221,7 +221,7 @@
|
||||
.separator {
|
||||
width: 1px;
|
||||
height: 18px;
|
||||
background: rgba(255, 255, 255, 0.15);
|
||||
background: var(--border);
|
||||
margin: 0 4px;
|
||||
}
|
||||
|
||||
@@ -233,21 +233,21 @@
|
||||
height: 32px;
|
||||
padding: 0;
|
||||
border-radius: 8px;
|
||||
border: 1px solid rgba(255, 255, 255, 0.12);
|
||||
background: rgba(255, 255, 255, 0.06);
|
||||
color: rgba(255, 255, 255, 0.85);
|
||||
border: 1px solid var(--border);
|
||||
background: var(--muted);
|
||||
color: var(--foreground);
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
|
||||
.btn:hover {
|
||||
background: rgba(255, 255, 255, 0.12);
|
||||
color: #fff;
|
||||
background: var(--accent);
|
||||
color: var(--accent-foreground);
|
||||
}
|
||||
|
||||
.image-area {
|
||||
flex: 1;
|
||||
background: rgba(0, 0, 0, 0.5);
|
||||
background: var(--muted);
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
@@ -275,16 +275,16 @@
|
||||
}
|
||||
|
||||
.loading {
|
||||
color: rgba(255, 255, 255, 0.5);
|
||||
color: var(--muted-foreground);
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.error-message {
|
||||
color: #f87171;
|
||||
background: rgba(248, 113, 113, 0.1);
|
||||
color: var(--destructive);
|
||||
background: var(--destructive-foreground);
|
||||
padding: 12px 16px;
|
||||
border-radius: 8px;
|
||||
border: 1px solid rgba(248, 113, 113, 0.2);
|
||||
border: 1px solid var(--destructive);
|
||||
font-size: 14px;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -79,7 +79,8 @@
|
||||
<style>
|
||||
:global(body) {
|
||||
margin: 0;
|
||||
background: #000;
|
||||
background: var(--background);
|
||||
color: var(--foreground);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
@@ -88,11 +89,13 @@
|
||||
flex-direction: column;
|
||||
height: 100vh;
|
||||
overflow: hidden;
|
||||
background: var(--background);
|
||||
color: var(--foreground);
|
||||
}
|
||||
|
||||
.titlebar {
|
||||
height: 32px;
|
||||
background: #000;
|
||||
background: var(--background);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
@@ -101,13 +104,14 @@
|
||||
user-select: none;
|
||||
flex: 0 0 32px;
|
||||
z-index: 50;
|
||||
border-bottom: 1px solid rgba(255, 255, 255, 0.08);
|
||||
border-bottom: 1px solid var(--border);
|
||||
}
|
||||
|
||||
.title {
|
||||
font-size: 13px;
|
||||
opacity: 0.9;
|
||||
color: white;
|
||||
color: var(--muted-foreground);
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.controls {
|
||||
@@ -121,24 +125,25 @@
|
||||
height: 100%;
|
||||
border: none;
|
||||
background: transparent;
|
||||
color: white;
|
||||
color: var(--foreground);
|
||||
font-size: 14px;
|
||||
cursor: pointer;
|
||||
-webkit-app-region: no-drag;
|
||||
}
|
||||
|
||||
.btn:hover {
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
background: var(--accent);
|
||||
}
|
||||
|
||||
.btn:disabled {
|
||||
opacity: 0.5;
|
||||
cursor: not-allowed;
|
||||
background: rgba(255, 255, 255, 0.02);
|
||||
background: var(--muted);
|
||||
}
|
||||
|
||||
.close:hover {
|
||||
background: #e81123;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.content {
|
||||
@@ -147,6 +152,6 @@
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
background: #111;
|
||||
background: var(--background);
|
||||
}
|
||||
</style>
|
||||
@@ -233,9 +233,10 @@
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
background: #1e1e1e;
|
||||
background: var(--background);
|
||||
position: relative;
|
||||
user-select: none;
|
||||
color: var(--foreground);
|
||||
}
|
||||
|
||||
.loading-overlay {
|
||||
@@ -247,15 +248,15 @@
|
||||
gap: 10px;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: #111;
|
||||
color: white;
|
||||
background: var(--background);
|
||||
color: var(--foreground);
|
||||
}
|
||||
|
||||
.spinner {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
border: 2px solid rgba(255, 255, 255, 0.1);
|
||||
border-top-color: rgba(255, 255, 255, 0.8);
|
||||
border: 2px solid var(--border);
|
||||
border-top-color: var(--primary);
|
||||
border-radius: 50%;
|
||||
animation: spin 0.6s linear infinite;
|
||||
}
|
||||
@@ -273,14 +274,14 @@
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: #111;
|
||||
color: #ef4444;
|
||||
background: var(--background);
|
||||
color: var(--destructive);
|
||||
}
|
||||
|
||||
.toolbar {
|
||||
height: 50px;
|
||||
background: #000;
|
||||
border-bottom: 1px solid rgba(255, 255, 255, 0.08);
|
||||
background: var(--card);
|
||||
border-bottom: 1px solid var(--border);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
@@ -308,7 +309,7 @@
|
||||
.separator {
|
||||
width: 1px;
|
||||
height: 18px;
|
||||
background: rgba(255, 255, 255, 0.15);
|
||||
background: var(--border);
|
||||
margin: 0 4px;
|
||||
}
|
||||
|
||||
@@ -320,22 +321,16 @@
|
||||
height: 32px;
|
||||
padding: 0;
|
||||
border-radius: 8px;
|
||||
border: 1px solid rgba(255, 255, 255, 0.12);
|
||||
background: rgba(255, 255, 255, 0.06);
|
||||
color: rgba(255, 255, 255, 0.85);
|
||||
border: 1px solid var(--border);
|
||||
background: var(--muted);
|
||||
color: var(--foreground);
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
|
||||
.btn:hover {
|
||||
background: rgba(255, 255, 255, 0.12);
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.separator {
|
||||
width: 1px;
|
||||
height: 24px;
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
background: var(--accent);
|
||||
color: var(--accent-foreground);
|
||||
}
|
||||
|
||||
.canvas-container {
|
||||
@@ -345,7 +340,7 @@
|
||||
justify-content: center;
|
||||
align-items: flex-start; /* scroll from top */
|
||||
padding: 20px;
|
||||
background: #333; /* Dark background for contrast */
|
||||
background: var(--muted);
|
||||
}
|
||||
|
||||
canvas {
|
||||
@@ -363,12 +358,12 @@
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb {
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
background: var(--border);
|
||||
border-radius: 6px;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb:hover {
|
||||
background: rgba(255, 255, 255, 0.2);
|
||||
background: var(--muted-foreground);
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-corner {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#define ApplicationName 'EMLy'
|
||||
#define ApplicationVersion GetVersionNumbersString('EMLy.exe')
|
||||
#define ApplicationVersion '1.4.1'
|
||||
#define ApplicationVersion '1.5.0'
|
||||
|
||||
[Setup]
|
||||
AppName={#ApplicationName}
|
||||
@@ -20,6 +20,8 @@ PrivilegesRequiredOverridesAllowed=dialog
|
||||
SetupIconFile=..\build\windows\icon.ico
|
||||
UninstallDisplayIcon={app}\{#ApplicationName}.exe
|
||||
AppVerName={#ApplicationName} {#ApplicationVersion}
|
||||
WizardStyle=modern dynamic includetitlebar
|
||||
|
||||
|
||||
[Files]
|
||||
; Source path relative to this .iss file (assuming it is in the "installer" folder and build is in "../build")
|
||||
|
||||
Reference in New Issue
Block a user