Features and Localization Updates for 1.5.2

Enhances user experience with increased contrast option for titlebar buttons.

Adds localization option for the PDF preview page, improving accessibility for international users.

Includes localization option and upgrade message for the InnoSetup installer, ensuring a smoother and more informative installation process.
This commit is contained in:
Flavio Fois
2026-02-10 09:21:33 +01:00
parent 4b6f2d727c
commit 4c99c14be7
12 changed files with 184 additions and 46 deletions

1
.gitignore vendored
View File

@@ -46,3 +46,4 @@ extra/*.dll
*.eml *.eml
*.msg *.msg
frontend/bun.lock

View File

@@ -1,7 +1,7 @@
[EMLy] [EMLy]
SDK_DECODER_SEMVER = 1.3.2 SDK_DECODER_SEMVER = 1.3.2
SDK_DECODER_RELEASE_CHANNEL = stable SDK_DECODER_RELEASE_CHANNEL = stable
GUI_SEMVER = 1.5.0 GUI_SEMVER = 1.5.2
GUI_RELEASE_CHANNEL = beta GUI_RELEASE_CHANNEL = beta
LANGUAGE = it LANGUAGE = it
UPDATE_CHECK_ENABLED = false UPDATE_CHECK_ENABLED = false

4
frontend/.gitignore vendored
View File

@@ -28,3 +28,7 @@ project.inlang/cache/
# Wails # Wails
/src/lib/wailsjs /src/lib/wailsjs
bun.lock
bun.lockb

View File

@@ -201,5 +201,19 @@
"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.", "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.",
"settings_reduce_motion_label": "Reduce Motion", "settings_reduce_motion_label": "Reduce Motion",
"settings_reduce_motion_hint": "Disable transition animations for interface elements like the sidebar.", "settings_reduce_motion_hint": "Disable transition animations for interface elements like the sidebar.",
"settings_reduce_motion_info": "Info: When enabled, animations such as the sidebar slide transition will be removed for a snappier feel or to reduce visual distractions." "settings_reduce_motion_info": "Info: When enabled, animations such as the sidebar slide transition will be removed for a snappier feel or to reduce visual distractions.",
"settings_window_buttons_contrast_label": "Increase window buttons contrast",
"settings_window_buttons_contrast_hint": "Makes the window control buttons (minimize, maximize, close) more visible by increasing their contrast.",
"pdf_viewer_title": "PDF Viewer",
"pdf_loading": "Loading PDF...",
"pdf_zoom_in": "Zoom In",
"pdf_zoom_out": "Zoom Out",
"pdf_rotate_left": "Rotate Left",
"pdf_rotate_right": "Rotate Right",
"pdf_fit_width": "Fit to Width",
"pdf_error_no_data": "No PDF data provided",
"pdf_error_no_data_desc": "No PDF data provided. Please open this window from the main EMLy application.",
"pdf_error_timeout": "Timeout loading PDF. The worker might have failed to initialize.",
"pdf_error_parsing": "Error parsing PDF: ",
"pdf_error_rendering": "Error rendering page: "
} }

View File

@@ -201,5 +201,19 @@
"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.", "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.",
"settings_reduce_motion_label": "Riduci Movimento", "settings_reduce_motion_label": "Riduci Movimento",
"settings_reduce_motion_hint": "Disabilita le animazioni di transizione per gli elementi dell'interfaccia come la barra laterale.", "settings_reduce_motion_hint": "Disabilita le animazioni di transizione per gli elementi dell'interfaccia come la barra laterale.",
"settings_reduce_motion_info": "Info: Quando abilitato, le animazioni come la transizione della barra laterale verranno rimosse per un'esperienza più reattiva o per ridurre le distrazioni visive." "settings_reduce_motion_info": "Info: Quando abilitato, le animazioni come la transizione della barra laterale verranno rimosse per un'esperienza più reattiva o per ridurre le distrazioni visive.",
"settings_window_buttons_contrast_label": "Aumenta contrasto pulsanti finestra",
"settings_window_buttons_contrast_hint": "Rende i pulsanti di controllo della finestra (minimizza, massimizza, chiudi) più visibili aumentando il loro contrasto.",
"pdf_viewer_title": "Visualizzatore PDF",
"pdf_loading": "Caricamento PDF...",
"pdf_zoom_in": "Ingrandisci",
"pdf_zoom_out": "Riduci",
"pdf_rotate_left": "Ruota a sinistra",
"pdf_rotate_right": "Ruota a destra",
"pdf_fit_width": "Adatta alla larghezza",
"pdf_error_no_data": "Nessun dato PDF fornito",
"pdf_error_no_data_desc": "Nessun dato PDF fornito. Apri questa finestra dall'applicazione principale EMLy.",
"pdf_error_timeout": "Timeout caricamento PDF. Il worker potrebbe non essersi inizializzato correttamente.",
"pdf_error_parsing": "Errore nel parsing del PDF: ",
"pdf_error_rendering": "Errore nel rendering della pagina: "
} }

View File

@@ -17,6 +17,7 @@ const defaults: EMLy_GUI_Settings = {
musicInspirationEnabled: false, musicInspirationEnabled: false,
reduceMotion: false, reduceMotion: false,
theme: "dark", theme: "dark",
increaseWindowButtonsContrast: false,
}; };
class SettingsStore { class SettingsStore {

View File

@@ -13,6 +13,7 @@ interface EMLy_GUI_Settings {
musicInspirationEnabled?: boolean; musicInspirationEnabled?: boolean;
reduceMotion?: boolean; reduceMotion?: boolean;
theme?: "light" | "dark"; theme?: "light" | "dark";
increaseWindowButtonsContrast?: boolean;
} }
type SupportedLanguages = "en" | "it"; type SupportedLanguages = "en" | "it";

View File

@@ -304,7 +304,7 @@
</div> </div>
</div> </div>
<div class="controls"> <div class="controls" class:high-contrast={settingsStore.settings.increaseWindowButtonsContrast}>
<button class="btn" onclick={minimize}>─</button> <button class="btn" onclick={minimize}>─</button>
<button class="btn" onclick={toggleMaximize}> <button class="btn" onclick={toggleMaximize}>
@@ -694,6 +694,10 @@
opacity: 0.5; opacity: 0.5;
} }
.controls.high-contrast {
opacity: 1;
}
.btn { .btn {
width: 46px; width: 46px;
height: 100%; height: 100%;

View File

@@ -43,6 +43,7 @@
enableUpdateChecker: false, enableUpdateChecker: false,
reduceMotion: false, reduceMotion: false,
theme: "dark", theme: "dark",
increaseWindowButtonsContrast: false,
}; };
async function setLanguage( async function setLanguage(
@@ -80,6 +81,7 @@
: (s.enableUpdateChecker ?? defaults.enableUpdateChecker ?? true), : (s.enableUpdateChecker ?? defaults.enableUpdateChecker ?? true),
reduceMotion: s.reduceMotion ?? defaults.reduceMotion ?? false, reduceMotion: s.reduceMotion ?? defaults.reduceMotion ?? false,
theme: s.theme || defaults.theme || "light", theme: s.theme || defaults.theme || "light",
increaseWindowButtonsContrast: s.increaseWindowButtonsContrast ?? defaults.increaseWindowButtonsContrast ?? false,
}; };
} }
@@ -93,6 +95,7 @@
!!a.enableUpdateChecker === !!b.enableUpdateChecker && !!a.enableUpdateChecker === !!b.enableUpdateChecker &&
!!a.reduceMotion === !!b.reduceMotion && !!a.reduceMotion === !!b.reduceMotion &&
(a.theme ?? "light") === (b.theme ?? "light") && (a.theme ?? "light") === (b.theme ?? "light") &&
!!a.increaseWindowButtonsContrast === !!b.increaseWindowButtonsContrast &&
JSON.stringify(a.previewFileSupportedTypes?.sort()) === JSON.stringify(a.previewFileSupportedTypes?.sort()) ===
JSON.stringify(b.previewFileSupportedTypes?.sort()) JSON.stringify(b.previewFileSupportedTypes?.sort())
); );
@@ -467,6 +470,45 @@
<p class="text-xs text-muted-foreground mt-2"> <p class="text-xs text-muted-foreground mt-2">
{m.settings_reduce_motion_info()} {m.settings_reduce_motion_info()}
</p> </p>
<Separator />
<div
class="flex items-center justify-between gap-4 rounded-lg border bg-card p-4"
>
<div>
<div class="font-medium">
{m.settings_window_buttons_contrast_label()}
</div>
<div class="text-sm text-muted-foreground">
{m.settings_window_buttons_contrast_hint()}
</div>
</div>
<Switch
bind:checked={form.increaseWindowButtonsContrast}
class="cursor-pointer hover:cursor-pointer"
/>
</div>
<div
class="flex items-center justify-between gap-4 rounded-lg border bg-card p-4"
>
<div>
<div class="font-medium">
{m.settings_email_dark_viewer_label()}
</div>
<div class="text-sm text-muted-foreground">
{m.settings_email_dark_viewer_hint()}
</div>
</div>
<Switch
bind:checked={form.useDarkEmailViewer}
class="cursor-pointer hover:cursor-pointer"
/>
</div>
<p class="text-xs text-muted-foreground mt-2">
{m.settings_email_dark_viewer_info()}
</p>
</div> </div>
</Card.Content> </Card.Content>
</Card.Root> </Card.Root>
@@ -495,7 +537,7 @@
{m.settings_export_button()} {m.settings_export_button()}
</Button> </Button>
</div> </div>
<Separator />
<div <div
class="flex items-center justify-between gap-4 rounded-lg border bg-card p-4" class="flex items-center justify-between gap-4 rounded-lg border bg-card p-4"
> >
@@ -640,29 +682,6 @@
{m.settings_preview_pdf_builtin_info()} {m.settings_preview_pdf_builtin_info()}
</p> </p>
</div> </div>
<Separator />
<div class="space-y-3">
<div
class="flex items-center justify-between gap-4 rounded-lg border bg-card p-4"
>
<div>
<div class="font-medium">
{m.settings_email_dark_viewer_label()}
</div>
<div class="text-sm text-muted-foreground">
{m.settings_email_dark_viewer_hint()}
</div>
</div>
<Switch
bind:checked={form.useDarkEmailViewer}
class="cursor-pointer hover:cursor-pointer"
/>
</div>
<p class="text-xs text-muted-foreground mt-2">
{m.settings_email_dark_viewer_info()}
</p>
</div>
</Card.Content> </Card.Content>
</Card.Root> </Card.Root>

View File

@@ -7,6 +7,7 @@
Quit, Quit,
} from "$lib/wailsjs/runtime/runtime"; } from "$lib/wailsjs/runtime/runtime";
import type { LayoutProps } from "./$types"; import type { LayoutProps } from "./$types";
import { settingsStore } from "$lib/stores/settings.svelte.js";
let { data, children }: LayoutProps = $props(); let { data, children }: LayoutProps = $props();
@@ -57,7 +58,7 @@
> >
<div class="title">EMLy PDF Viewer</div> <div class="title">EMLy PDF Viewer</div>
<div class="controls"> <div class="controls" class:high-contrast={settingsStore.settings.increaseWindowButtonsContrast}>
<button class="btn" onclick={minimize}>─</button> <button class="btn" onclick={minimize}>─</button>
<button class="btn" onclick={toggleMaximize}> <button class="btn" onclick={toggleMaximize}>
{#if isMaximized} {#if isMaximized}
@@ -120,6 +121,10 @@
opacity: 0.5; opacity: 0.5;
} }
.controls.high-contrast {
opacity: 1;
}
.btn { .btn {
width: 46px; width: 46px;
height: 100%; height: 100%;

View File

@@ -10,6 +10,7 @@
} from "@lucide/svelte"; } from "@lucide/svelte";
import { sidebarOpen } from "$lib/stores/app"; import { sidebarOpen } from "$lib/stores/app";
import { toast } from "svelte-sonner"; import { toast } from "svelte-sonner";
import * as m from "$lib/paraglide/messages.js";
import * as pdfjsLib from "pdfjs-dist"; import * as pdfjsLib from "pdfjs-dist";
import pdfWorker from "pdfjs-dist/build/pdf.worker.min.mjs?url"; import pdfWorker from "pdfjs-dist/build/pdf.worker.min.mjs?url";
@@ -63,9 +64,8 @@
await loadPDF(); await loadPDF();
} else { } else {
toast.error("No PDF data provided"); toast.error(m.pdf_error_no_data());
error = error = m.pdf_error_no_data_desc();
"No PDF data provided. Please open this window from the main EMLy application.";
loading = false; loading = false;
} }
} catch (e) { } catch (e) {
@@ -81,8 +81,7 @@
const timeout = setTimeout(() => { const timeout = setTimeout(() => {
if (loading) { if (loading) {
loading = false; loading = false;
error = error = m.pdf_error_timeout();
"Timeout loading PDF. The worker might have failed to initialize.";
toast.error(error); toast.error(error);
} }
}, 10000); }, 10000);
@@ -96,7 +95,7 @@
loading = false; loading = false;
} catch (e) { } catch (e) {
console.error(e); console.error(e);
error = "Error parsing PDF: " + e; error = m.pdf_error_parsing() + e;
loading = false; loading = false;
} finally { } finally {
clearTimeout(timeout); clearTimeout(timeout);
@@ -135,7 +134,7 @@
} catch (e: any) { } catch (e: any) {
if (e.name !== "RenderingCancelledException") { if (e.name !== "RenderingCancelledException") {
console.error(e); console.error(e);
toast.error("Error rendering page: " + e.message); toast.error(m.pdf_error_rendering() + e.message);
} }
} }
} }
@@ -189,7 +188,7 @@
{#if loading} {#if loading}
<div class="loading-overlay"> <div class="loading-overlay">
<div class="spinner"></div> <div class="spinner"></div>
<div>Loading PDF...</div> <div>{m.pdf_loading()}</div>
</div> </div>
{/if} {/if}
@@ -200,24 +199,24 @@
{/if} {/if}
<div class="toolbar"> <div class="toolbar">
<h1 class="title" title={filename}>{filename || "Image Viewer"}</h1> <h1 class="title" title={filename}>{filename || m.pdf_viewer_title()}</h1>
<div class="controls"> <div class="controls">
<button class="btn" onclick={() => zoom(0.1)} title="Zoom In"> <button class="btn" onclick={() => zoom(0.1)} title={m.pdf_zoom_in()}>
<ZoomIn size="16" /> <ZoomIn size="16" />
</button> </button>
<button class="btn" onclick={() => zoom(-0.1)} title="Zoom Out"> <button class="btn" onclick={() => zoom(-0.1)} title={m.pdf_zoom_out()}>
<ZoomOut size="16" /> <ZoomOut size="16" />
</button> </button>
<div class="separator"></div> <div class="separator"></div>
<button class="btn" onclick={() => rotate(-90)} title="Rotate Left"> <button class="btn" onclick={() => rotate(-90)} title={m.pdf_rotate_left()}>
<RotateCcw size="16" /> <RotateCcw size="16" />
</button> </button>
<button class="btn" onclick={() => rotate(90)} title="Rotate Right"> <button class="btn" onclick={() => rotate(90)} title={m.pdf_rotate_right()}>
<RotateCw size="16" /> <RotateCw size="16" />
</button> </button>
<div class="separator"></div> <div class="separator"></div>
<button class="btn" onclick={fitToWidth} title="Reset"> <button class="btn" onclick={fitToWidth} title={m.pdf_fit_width()}>
<AlignHorizontalSpaceAround size="16" /> <AlignHorizontalSpaceAround size="16" />
</button> </button>
</div> </div>

View File

@@ -1,6 +1,23 @@
#define ApplicationName 'EMLy' #define ApplicationName 'EMLy'
#define ApplicationVersion GetVersionNumbersString('EMLy.exe') #define ApplicationVersion GetVersionNumbersString('EMLy.exe')
#define ApplicationVersion '1.5.0' #define ApplicationVersion '1.5.2'
[Languages]
Name: "english"; MessagesFile: "compiler:Default.isl"
Name: "italian"; MessagesFile: "compiler:Languages\Italian.isl"
[CustomMessages]
; English messages
english.UpgradeDetected=A previous version of {#ApplicationName} (v%1) has been detected.
english.UpgradeMessage=This installer will upgrade your installation to version {#ApplicationVersion}.%n%nYour settings and preferences will be preserved.%n%nDo you want to continue?
english.FreshInstall=Welcome to {#ApplicationName} {#ApplicationVersion} Setup
english.FreshInstallMessage=This will install {#ApplicationName} on your computer.
; Italian messages
italian.UpgradeDetected=È stata rilevata una versione precedente di {#ApplicationName} (v%1).
italian.UpgradeMessage=Questo installer aggiornerà la tua installazione alla versione {#ApplicationVersion}.%n%nLe tue impostazioni e preferenze saranno preservate.%n%nVuoi continuare?
italian.FreshInstall=Benvenuto nell'installazione di {#ApplicationName} {#ApplicationVersion}
italian.FreshInstallMessage=Questo installerà {#ApplicationName} sul tuo computer.
[Setup] [Setup]
AppName={#ApplicationName} AppName={#ApplicationName}
@@ -59,6 +76,65 @@ Root: HKA; Subkey: "Software\Classes\{#ApplicationName}.MSG\shell\open"; ValueTy
Name: "{autoprograms}\{#ApplicationName}"; Filename: "{app}\{#ApplicationName}.exe" Name: "{autoprograms}\{#ApplicationName}"; Filename: "{app}\{#ApplicationName}.exe"
[Code] [Code]
var
PreviousVersion: String;
IsUpgrade: Boolean;
// Check if a previous version is installed
function GetPreviousVersion(): String;
var
RegPath: String;
Version: String;
begin
Result := '';
// Check HKLM (system-wide installation)
RegPath := 'Software\Microsoft\Windows\CurrentVersion\Uninstall\{#ApplicationName}_is1';
if RegQueryStringValue(HKLM, RegPath, 'DisplayVersion', Version) then
begin
Result := Version;
Exit;
end;
// Check HKCU (user installation)
if RegQueryStringValue(HKCU, RegPath, 'DisplayVersion', Version) then
begin
Result := Version;
Exit;
end;
end;
// Initialize setup and detect upgrade
function InitializeSetup(): Boolean;
var
Message: String;
begin
Result := True;
PreviousVersion := GetPreviousVersion();
IsUpgrade := (PreviousVersion <> '');
if IsUpgrade then
begin
// Show upgrade message
Message := FmtMessage(CustomMessage('UpgradeDetected'), [PreviousVersion]) + #13#10#13#10 +
CustomMessage('UpgradeMessage');
if MsgBox(Message, mbInformation, MB_YESNO) = IDNO then
begin
Result := False;
end;
end;
end;
// Show appropriate welcome message
procedure InitializeWizard();
begin
if not IsUpgrade then
begin
WizardForm.WelcomeLabel2.Caption := CustomMessage('FreshInstallMessage');
end;
end;
// Override default directory based on installation mode // Override default directory based on installation mode
function GetDefaultDirName(Param: string): string; function GetDefaultDirName(Param: string): string;
begin begin