Compare commits
4 Commits
86e33d6189
...
18c256ebf9
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
18c256ebf9 | ||
|
|
3eb95cca7f | ||
|
|
6f373dd9ab | ||
|
|
eac7a12cd4 |
35
CHANGELOG.md
Normal file
35
CHANGELOG.md
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
# Changelog EMLy
|
||||||
|
|
||||||
|
## 1.5.4 (2026-02-10)
|
||||||
|
1) Aggiunti i pulsanti "Download" al MailViewer, PDF e Image viewer, per scaricare il file invece di aprirlo direttamente.2
|
||||||
|
2) Refactor del sistema di bug report.
|
||||||
|
3) Rimosso temporaneamente il fetching dei dati macchina all'apertura della pagine delle impostazioni, per evitare problemi di performance.
|
||||||
|
4) Fixato un bug dove, nel Bug Reporting, non si disattivaa il pulsante di invio, se tutti i campi erano compilati.
|
||||||
|
5) Aggiunto il supprto all'allegare i file di localStorage e config.ini al Bug Report, per investigare meglio i problemi legati all'ambiente dell'utente.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## 1.5.3 (2026-02-10)
|
||||||
|
1) Sistemato un bug dove, al primo avvio, il tema chiaro era applicato insieme all'opzioni del tema scuro sul contenuto mail, causando un contrasto eccessivo.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## 1.5.2 (2026-02-10)
|
||||||
|
1) Supporto tema chiaro/scuro.
|
||||||
|
2) Internazionalizzazione completa (Italiano/Inglese).
|
||||||
|
3) Opzioni di accessibilità (riduzione animazioni, contrasto).
|
||||||
|
|
||||||
|
|
||||||
|
## 1.5.1 (2026-02-09)
|
||||||
|
1) Sistemato un bug del primo avvio, con mismatch della lingua.
|
||||||
|
2) Aggiunto il supporto all'installazione sotto AppData/Local
|
||||||
|
|
||||||
|
|
||||||
|
## 1.5.0 (2026-02-08)
|
||||||
|
1) Sistema di aggiornamento automatico self-hosted (ancora non attivo di default).
|
||||||
|
2) Sistema di bug report integrato.
|
||||||
|
|
||||||
|
|
||||||
|
## 1.4.1 (2026-02-06)
|
||||||
|
1) Export/Import impostazioni.
|
||||||
|
2) Aggiornamento configurazione installer.
|
||||||
2
TODO.md
2
TODO.md
@@ -5,7 +5,7 @@
|
|||||||
|
|
||||||
# Existing Features
|
# Existing Features
|
||||||
- [ ] Add seperated "Updater" binary, that will start on User login (via Scheduled Task), with a silent install mode.
|
- [ ] Add seperated "Updater" binary, that will start on User login (via Scheduled Task), with a silent install mode.
|
||||||
- [ ] Attach localStorage, config file to the "Bug Reporter" ZIP file, to investigate the issue with the user enviroment.
|
- [x] Attach localStorage, config file to the "Bug Reporter" ZIP file, to investigate the issue with the user enviroment.
|
||||||
- [ ] Auto-send the "Bug Reporter" ZIP file to the support team, to investigate the issue with the user enviroment.
|
- [ ] Auto-send the "Bug Reporter" ZIP file to the support team, to investigate the issue with the user enviroment.
|
||||||
|
|
||||||
# Bugs
|
# Bugs
|
||||||
|
|||||||
@@ -38,6 +38,10 @@ type BugReportInput struct {
|
|||||||
Description string `json:"description"`
|
Description string `json:"description"`
|
||||||
// ScreenshotData is the base64-encoded PNG screenshot (captured before dialog opens)
|
// ScreenshotData is the base64-encoded PNG screenshot (captured before dialog opens)
|
||||||
ScreenshotData string `json:"screenshotData"`
|
ScreenshotData string `json:"screenshotData"`
|
||||||
|
// LocalStorageData is the JSON-encoded localStorage data
|
||||||
|
LocalStorageData string `json:"localStorageData"`
|
||||||
|
// ConfigData is the JSON-encoded config.ini data
|
||||||
|
ConfigData string `json:"configData"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// SubmitBugReportResult contains the result of submitting a bug report.
|
// SubmitBugReportResult contains the result of submitting a bug report.
|
||||||
@@ -120,10 +124,12 @@ func (a *App) CreateBugReportFolder() (*BugReportResult, error) {
|
|||||||
// - User-provided description (report.txt)
|
// - User-provided description (report.txt)
|
||||||
// - Screenshot (captured before dialog opens)
|
// - Screenshot (captured before dialog opens)
|
||||||
// - Currently loaded mail file (if any)
|
// - Currently loaded mail file (if any)
|
||||||
|
// - localStorage data (localStorage.json)
|
||||||
|
// - Config.ini data (config.json)
|
||||||
// - System information (hostname, OS version, hardware ID)
|
// - System information (hostname, OS version, hardware ID)
|
||||||
//
|
//
|
||||||
// Parameters:
|
// Parameters:
|
||||||
// - input: User-provided bug report details including pre-captured screenshot
|
// - input: User-provided bug report details including pre-captured screenshot, localStorage, and config data
|
||||||
//
|
//
|
||||||
// Returns:
|
// Returns:
|
||||||
// - *SubmitBugReportResult: Paths to the zip file and folder
|
// - *SubmitBugReportResult: Paths to the zip file and folder
|
||||||
@@ -168,6 +174,22 @@ func (a *App) SubmitBugReport(input BugReportInput) (*SubmitBugReportResult, err
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Save localStorage data if provided
|
||||||
|
if input.LocalStorageData != "" {
|
||||||
|
localStoragePath := filepath.Join(bugReportFolder, "localStorage.json")
|
||||||
|
if err := os.WriteFile(localStoragePath, []byte(input.LocalStorageData), 0644); err != nil {
|
||||||
|
Log("Failed to save localStorage data:", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Save config data if provided
|
||||||
|
if input.ConfigData != "" {
|
||||||
|
configPath := filepath.Join(bugReportFolder, "config.json")
|
||||||
|
if err := os.WriteFile(configPath, []byte(input.ConfigData), 0644); err != nil {
|
||||||
|
Log("Failed to save config data:", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Create the report.txt file with user's description
|
// Create the report.txt file with user's description
|
||||||
reportContent := fmt.Sprintf(`EMLy Bug Report
|
reportContent := fmt.Sprintf(`EMLy Bug Report
|
||||||
================
|
================
|
||||||
|
|||||||
309
frontend/src/lib/components/BugReportDialog.svelte
Normal file
309
frontend/src/lib/components/BugReportDialog.svelte
Normal file
@@ -0,0 +1,309 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import { bugReportDialogOpen } from "$lib/stores/app";
|
||||||
|
import * as m from "$lib/paraglide/messages.js";
|
||||||
|
import * as Dialog from "$lib/components/ui/dialog/index.js";
|
||||||
|
import { Button, buttonVariants } from "$lib/components/ui/button/index.js";
|
||||||
|
import { Input } from "$lib/components/ui/input/index.js";
|
||||||
|
import { Label } from "$lib/components/ui/label/index.js";
|
||||||
|
import { Textarea } from "$lib/components/ui/textarea/index.js";
|
||||||
|
import { CheckCircle, Copy, FolderOpen, Camera, Loader2 } from "@lucide/svelte";
|
||||||
|
import { toast } from "svelte-sonner";
|
||||||
|
import { TakeScreenshot, SubmitBugReport, OpenFolderInExplorer, GetConfig } from "$lib/wailsjs/go/main/App";
|
||||||
|
import { browser } from "$app/environment";
|
||||||
|
|
||||||
|
// Bug report form state
|
||||||
|
let userName = $state("");
|
||||||
|
let userEmail = $state("");
|
||||||
|
let bugDescription = $state("");
|
||||||
|
|
||||||
|
// Bug report screenshot state
|
||||||
|
let screenshotData = $state("");
|
||||||
|
let isCapturing = $state(false);
|
||||||
|
|
||||||
|
// Bug report system data
|
||||||
|
let localStorageData = $state("");
|
||||||
|
let configData = $state("");
|
||||||
|
|
||||||
|
// Bug report UI state
|
||||||
|
let isSubmitting = $state(false);
|
||||||
|
let isSuccess = $state(false);
|
||||||
|
let resultZipPath = $state("");
|
||||||
|
let canSubmit: boolean = $derived(
|
||||||
|
bugDescription.trim().length > 0 && userName.trim().length > 0 && userEmail.trim().length > 0 && !isSubmitting && !isCapturing
|
||||||
|
);
|
||||||
|
|
||||||
|
// Bug report dialog effects
|
||||||
|
$effect(() => {
|
||||||
|
if ($bugReportDialogOpen) {
|
||||||
|
// Capture screenshot immediately when dialog opens
|
||||||
|
captureScreenshot();
|
||||||
|
// Capture localStorage data
|
||||||
|
captureLocalStorage();
|
||||||
|
// Capture config.ini data
|
||||||
|
captureConfig();
|
||||||
|
} else {
|
||||||
|
// Reset form when dialog closes
|
||||||
|
resetBugReportForm();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
async function captureScreenshot() {
|
||||||
|
isCapturing = true;
|
||||||
|
try {
|
||||||
|
const result = await TakeScreenshot();
|
||||||
|
screenshotData = result.data;
|
||||||
|
console.log("Screenshot captured:", result.width, "x", result.height);
|
||||||
|
} catch (err) {
|
||||||
|
console.error("Failed to capture screenshot:", err);
|
||||||
|
} finally {
|
||||||
|
isCapturing = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function captureLocalStorage() {
|
||||||
|
if (!browser) return;
|
||||||
|
try {
|
||||||
|
const data: Record<string, string> = {};
|
||||||
|
for (let i = 0; i < localStorage.length; i++) {
|
||||||
|
const key = localStorage.key(i);
|
||||||
|
if (key) {
|
||||||
|
data[key] = localStorage.getItem(key) || "";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
localStorageData = JSON.stringify(data, null, 2);
|
||||||
|
console.log("localStorage data captured");
|
||||||
|
} catch (err) {
|
||||||
|
console.error("Failed to capture localStorage:", err);
|
||||||
|
localStorageData = "Error capturing localStorage";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function captureConfig() {
|
||||||
|
try {
|
||||||
|
const config = await GetConfig();
|
||||||
|
configData = JSON.stringify(config, null, 2);
|
||||||
|
console.log("Config data captured");
|
||||||
|
} catch (err) {
|
||||||
|
console.error("Failed to capture config:", err);
|
||||||
|
configData = "Error capturing config";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function resetBugReportForm() {
|
||||||
|
userName = "";
|
||||||
|
userEmail = "";
|
||||||
|
bugDescription = "";
|
||||||
|
screenshotData = "";
|
||||||
|
localStorageData = "";
|
||||||
|
configData = "";
|
||||||
|
isCapturing = false;
|
||||||
|
isSubmitting = false;
|
||||||
|
isSuccess = false;
|
||||||
|
resultZipPath = "";
|
||||||
|
}
|
||||||
|
|
||||||
|
async function handleBugReportSubmit(event: Event) {
|
||||||
|
event.preventDefault();
|
||||||
|
|
||||||
|
if (!bugDescription.trim()) {
|
||||||
|
toast.error("Please provide a bug description.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
isSubmitting = true;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const result = await SubmitBugReport({
|
||||||
|
name: userName,
|
||||||
|
email: userEmail,
|
||||||
|
description: bugDescription,
|
||||||
|
screenshotData: screenshotData,
|
||||||
|
localStorageData: localStorageData,
|
||||||
|
configData: configData
|
||||||
|
});
|
||||||
|
|
||||||
|
resultZipPath = result.zipPath;
|
||||||
|
isSuccess = true;
|
||||||
|
console.log("Bug report created:", result.zipPath);
|
||||||
|
} catch (err) {
|
||||||
|
console.error("Failed to create bug report:", err);
|
||||||
|
toast.error(m.bugreport_error());
|
||||||
|
} finally {
|
||||||
|
isSubmitting = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function copyBugReportPath() {
|
||||||
|
try {
|
||||||
|
await navigator.clipboard.writeText(resultZipPath);
|
||||||
|
toast.success(m.bugreport_copied());
|
||||||
|
} catch (err) {
|
||||||
|
console.error("Failed to copy path:", err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function openBugReportFolder() {
|
||||||
|
try {
|
||||||
|
const folderPath = resultZipPath.replace(/\.zip$/, "");
|
||||||
|
await OpenFolderInExplorer(folderPath);
|
||||||
|
} catch (err) {
|
||||||
|
console.error("Failed to open folder:", err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function closeBugReportDialog() {
|
||||||
|
$bugReportDialogOpen = false;
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<Dialog.Root bind:open={$bugReportDialogOpen}>
|
||||||
|
<Dialog.Content class="sm:max-w-125 w-full max-h-[80vh] overflow-y-auto custom-scrollbar">
|
||||||
|
{#if isSuccess}
|
||||||
|
<!-- Success State -->
|
||||||
|
<Dialog.Header>
|
||||||
|
<Dialog.Title class="flex items-center gap-2">
|
||||||
|
<CheckCircle class="h-5 w-5 text-green-500" />
|
||||||
|
{m.bugreport_success_title()}
|
||||||
|
</Dialog.Title>
|
||||||
|
<Dialog.Description>
|
||||||
|
{m.bugreport_success_message()}
|
||||||
|
</Dialog.Description>
|
||||||
|
</Dialog.Header>
|
||||||
|
|
||||||
|
<div class="grid gap-4 py-4">
|
||||||
|
<div class="bg-muted rounded-md p-3">
|
||||||
|
<code class="text-xs break-all select-all">{resultZipPath}</code>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="flex gap-2">
|
||||||
|
<Button variant="outline" class="flex-1" onclick={copyBugReportPath}>
|
||||||
|
<Copy class="h-4 w-4 mr-2" />
|
||||||
|
{m.bugreport_copy_path()}
|
||||||
|
</Button>
|
||||||
|
<Button variant="outline" class="flex-1" onclick={openBugReportFolder}>
|
||||||
|
<FolderOpen class="h-4 w-4 mr-2" />
|
||||||
|
{m.bugreport_open_folder()}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Dialog.Footer>
|
||||||
|
<Button onclick={closeBugReportDialog}>
|
||||||
|
{m.bugreport_close()}
|
||||||
|
</Button>
|
||||||
|
</Dialog.Footer>
|
||||||
|
{:else}
|
||||||
|
<!-- Form State -->
|
||||||
|
<form onsubmit={handleBugReportSubmit}>
|
||||||
|
<Dialog.Header>
|
||||||
|
<Dialog.Title>{m.bugreport_title()}</Dialog.Title>
|
||||||
|
<Dialog.Description>
|
||||||
|
{m.bugreport_description()}
|
||||||
|
</Dialog.Description>
|
||||||
|
</Dialog.Header>
|
||||||
|
|
||||||
|
<div class="grid gap-4 py-4">
|
||||||
|
<div class="grid gap-2">
|
||||||
|
<Label for="bug-name">{m.bugreport_name_label()}</Label>
|
||||||
|
<Input
|
||||||
|
id="bug-name"
|
||||||
|
placeholder={m.bugreport_name_placeholder()}
|
||||||
|
bind:value={userName}
|
||||||
|
disabled={isSubmitting}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="grid gap-2">
|
||||||
|
<Label for="bug-email">{m.bugreport_email_label()}</Label>
|
||||||
|
<Input
|
||||||
|
id="bug-email"
|
||||||
|
type="email"
|
||||||
|
placeholder={m.bugreport_email_placeholder()}
|
||||||
|
bind:value={userEmail}
|
||||||
|
disabled={isSubmitting}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="grid gap-2">
|
||||||
|
<Label for="bug-description">{m.bugreport_text_label()}</Label>
|
||||||
|
<Textarea
|
||||||
|
id="bug-description"
|
||||||
|
placeholder={m.bugreport_text_placeholder()}
|
||||||
|
bind:value={bugDescription}
|
||||||
|
disabled={isSubmitting}
|
||||||
|
class="min-h-30"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Screenshot Preview -->
|
||||||
|
<div class="grid gap-2">
|
||||||
|
<Label class="flex items-center gap-2">
|
||||||
|
<Camera class="h-4 w-4" />
|
||||||
|
{m.bugreport_screenshot_label()}
|
||||||
|
</Label>
|
||||||
|
{#if isCapturing}
|
||||||
|
<div class="flex items-center gap-2 text-muted-foreground text-sm">
|
||||||
|
<Loader2 class="h-4 w-4 animate-spin" />
|
||||||
|
Capturing...
|
||||||
|
</div>
|
||||||
|
{:else if screenshotData}
|
||||||
|
<div class="border rounded-md overflow-hidden">
|
||||||
|
<img
|
||||||
|
src="data:image/png;base64,{screenshotData}"
|
||||||
|
alt="Screenshot preview"
|
||||||
|
class="w-full h-32 object-cover object-top opacity-80 hover:opacity-100 transition-opacity cursor-pointer"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
{:else}
|
||||||
|
<div class="text-muted-foreground text-sm">
|
||||||
|
No screenshot available
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<p class="text-muted-foreground text-sm">
|
||||||
|
{m.bugreport_info()}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Dialog.Footer>
|
||||||
|
<button type="button" class={buttonVariants({ variant: "outline" })} disabled={isSubmitting} onclick={closeBugReportDialog}>
|
||||||
|
{m.bugreport_cancel()}
|
||||||
|
</button>
|
||||||
|
<Button type="submit" disabled={!canSubmit}>
|
||||||
|
{#if isSubmitting}
|
||||||
|
<Loader2 class="h-4 w-4 mr-2 animate-spin" />
|
||||||
|
{m.bugreport_submitting()}
|
||||||
|
{:else}
|
||||||
|
{m.bugreport_submit()}
|
||||||
|
{/if}
|
||||||
|
</Button>
|
||||||
|
</Dialog.Footer>
|
||||||
|
</form>
|
||||||
|
{/if}
|
||||||
|
</Dialog.Content>
|
||||||
|
</Dialog.Root>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
:global(.custom-scrollbar::-webkit-scrollbar) {
|
||||||
|
width: 6px;
|
||||||
|
height: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
:global(.custom-scrollbar::-webkit-scrollbar-track) {
|
||||||
|
background: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
:global(.custom-scrollbar::-webkit-scrollbar-thumb) {
|
||||||
|
background: var(--border);
|
||||||
|
border-radius: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
:global(.custom-scrollbar::-webkit-scrollbar-thumb:hover) {
|
||||||
|
background: var(--muted-foreground);
|
||||||
|
}
|
||||||
|
|
||||||
|
:global(.custom-scrollbar::-webkit-scrollbar-corner) {
|
||||||
|
background: transparent;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -17,22 +17,14 @@
|
|||||||
House,
|
House,
|
||||||
Settings,
|
Settings,
|
||||||
Bug,
|
Bug,
|
||||||
Loader2,
|
|
||||||
Copy,
|
|
||||||
FolderOpen,
|
|
||||||
CheckCircle,
|
|
||||||
Camera,
|
|
||||||
Heart,
|
Heart,
|
||||||
Info,
|
Info,
|
||||||
Music
|
Music
|
||||||
} from "@lucide/svelte";
|
} from "@lucide/svelte";
|
||||||
import { Separator } from "$lib/components/ui/separator/index.js";
|
import { Separator } from "$lib/components/ui/separator/index.js";
|
||||||
import { toast } from "svelte-sonner";
|
import { toast } from "svelte-sonner";
|
||||||
import { Button, buttonVariants } from "$lib/components/ui/button/index.js";
|
import { buttonVariants } from "$lib/components/ui/button/index.js";
|
||||||
import * as Dialog from "$lib/components/ui/dialog/index.js";
|
import BugReportDialog from "$lib/components/BugReportDialog.svelte";
|
||||||
import { Input } from "$lib/components/ui/input/index.js";
|
|
||||||
import { Label } from "$lib/components/ui/label/index.js";
|
|
||||||
import { Textarea } from "$lib/components/ui/textarea/index.js";
|
|
||||||
|
|
||||||
import {
|
import {
|
||||||
WindowMinimise,
|
WindowMinimise,
|
||||||
@@ -44,7 +36,7 @@
|
|||||||
EventsOff,
|
EventsOff,
|
||||||
} from "$lib/wailsjs/runtime/runtime";
|
} from "$lib/wailsjs/runtime/runtime";
|
||||||
import { RefreshCcwDot } from "@lucide/svelte";
|
import { RefreshCcwDot } from "@lucide/svelte";
|
||||||
import { IsDebuggerRunning, QuitApp, TakeScreenshot, SubmitBugReport, OpenFolderInExplorer } from "$lib/wailsjs/go/main/App";
|
import { IsDebuggerRunning, QuitApp } from "$lib/wailsjs/go/main/App";
|
||||||
import { settingsStore } from "$lib/stores/settings.svelte.js";
|
import { settingsStore } from "$lib/stores/settings.svelte.js";
|
||||||
|
|
||||||
let versionInfo: utils.Config | null = $state(null);
|
let versionInfo: utils.Config | null = $state(null);
|
||||||
@@ -52,20 +44,6 @@
|
|||||||
let isDebugerOn: boolean = $state(false);
|
let isDebugerOn: boolean = $state(false);
|
||||||
let isDebbugerProtectionOn: boolean = $state(true);
|
let isDebbugerProtectionOn: boolean = $state(true);
|
||||||
|
|
||||||
// Bug report form state
|
|
||||||
let userName = $state("");
|
|
||||||
let userEmail = $state("");
|
|
||||||
let bugDescription = $state("");
|
|
||||||
|
|
||||||
// Bug report screenshot state
|
|
||||||
let screenshotData = $state("");
|
|
||||||
let isCapturing = $state(false);
|
|
||||||
|
|
||||||
// Bug report UI state
|
|
||||||
let isSubmitting = $state(false);
|
|
||||||
let isSuccess = $state(false);
|
|
||||||
let resultZipPath = $state("");
|
|
||||||
|
|
||||||
async function syncMaxState() {
|
async function syncMaxState() {
|
||||||
isMaximized = await WindowIsMaximised();
|
isMaximized = await WindowIsMaximised();
|
||||||
}
|
}
|
||||||
@@ -155,17 +133,6 @@
|
|||||||
applyTheme(stored === "light" ? "light" : "dark");
|
applyTheme(stored === "light" ? "light" : "dark");
|
||||||
});
|
});
|
||||||
|
|
||||||
// Bug report dialog effects
|
|
||||||
$effect(() => {
|
|
||||||
if ($bugReportDialogOpen) {
|
|
||||||
// Capture screenshot immediately when dialog opens
|
|
||||||
captureScreenshot();
|
|
||||||
} else {
|
|
||||||
// Reset form when dialog closes
|
|
||||||
resetBugReportForm();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Listen for automatic update notifications
|
// Listen for automatic update notifications
|
||||||
$effect(() => {
|
$effect(() => {
|
||||||
if (!browser) return;
|
if (!browser) return;
|
||||||
@@ -186,81 +153,6 @@
|
|||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
async function captureScreenshot() {
|
|
||||||
isCapturing = true;
|
|
||||||
try {
|
|
||||||
const result = await TakeScreenshot();
|
|
||||||
screenshotData = result.data;
|
|
||||||
console.log("Screenshot captured:", result.width, "x", result.height);
|
|
||||||
} catch (err) {
|
|
||||||
console.error("Failed to capture screenshot:", err);
|
|
||||||
} finally {
|
|
||||||
isCapturing = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function resetBugReportForm() {
|
|
||||||
userName = "";
|
|
||||||
userEmail = "";
|
|
||||||
bugDescription = "";
|
|
||||||
screenshotData = "";
|
|
||||||
isCapturing = false;
|
|
||||||
isSubmitting = false;
|
|
||||||
isSuccess = false;
|
|
||||||
resultZipPath = "";
|
|
||||||
}
|
|
||||||
|
|
||||||
async function handleBugReportSubmit(event: Event) {
|
|
||||||
event.preventDefault();
|
|
||||||
|
|
||||||
if (!bugDescription.trim()) {
|
|
||||||
toast.error("Please provide a bug description.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
isSubmitting = true;
|
|
||||||
|
|
||||||
try {
|
|
||||||
const result = await SubmitBugReport({
|
|
||||||
name: userName,
|
|
||||||
email: userEmail,
|
|
||||||
description: bugDescription,
|
|
||||||
screenshotData: screenshotData
|
|
||||||
});
|
|
||||||
|
|
||||||
resultZipPath = result.zipPath;
|
|
||||||
isSuccess = true;
|
|
||||||
console.log("Bug report created:", result.zipPath);
|
|
||||||
} catch (err) {
|
|
||||||
console.error("Failed to create bug report:", err);
|
|
||||||
toast.error(m.bugreport_error());
|
|
||||||
} finally {
|
|
||||||
isSubmitting = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async function copyBugReportPath() {
|
|
||||||
try {
|
|
||||||
await navigator.clipboard.writeText(resultZipPath);
|
|
||||||
toast.success(m.bugreport_copied());
|
|
||||||
} catch (err) {
|
|
||||||
console.error("Failed to copy path:", err);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async function openBugReportFolder() {
|
|
||||||
try {
|
|
||||||
const folderPath = resultZipPath.replace(/\.zip$/, "");
|
|
||||||
await OpenFolderInExplorer(folderPath);
|
|
||||||
} catch (err) {
|
|
||||||
console.error("Failed to open folder:", err);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function closeBugReportDialog() {
|
|
||||||
$bugReportDialogOpen = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
syncMaxState();
|
syncMaxState();
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@@ -435,134 +327,7 @@
|
|||||||
{/each}
|
{/each}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Bug Report Dialog -->
|
<BugReportDialog />
|
||||||
<Dialog.Root bind:open={$bugReportDialogOpen}>
|
|
||||||
<Dialog.Content class="sm:max-w-[500px] w-full max-h-[80vh] overflow-y-auto custom-scrollbar">
|
|
||||||
{#if isSuccess}
|
|
||||||
<!-- Success State -->
|
|
||||||
<Dialog.Header>
|
|
||||||
<Dialog.Title class="flex items-center gap-2">
|
|
||||||
<CheckCircle class="h-5 w-5 text-green-500" />
|
|
||||||
{m.bugreport_success_title()}
|
|
||||||
</Dialog.Title>
|
|
||||||
<Dialog.Description>
|
|
||||||
{m.bugreport_success_message()}
|
|
||||||
</Dialog.Description>
|
|
||||||
</Dialog.Header>
|
|
||||||
|
|
||||||
<div class="grid gap-4 py-4">
|
|
||||||
<div class="bg-muted rounded-md p-3">
|
|
||||||
<code class="text-xs break-all select-all">{resultZipPath}</code>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="flex gap-2">
|
|
||||||
<Button variant="outline" class="flex-1" onclick={copyBugReportPath}>
|
|
||||||
<Copy class="h-4 w-4 mr-2" />
|
|
||||||
{m.bugreport_copy_path()}
|
|
||||||
</Button>
|
|
||||||
<Button variant="outline" class="flex-1" onclick={openBugReportFolder}>
|
|
||||||
<FolderOpen class="h-4 w-4 mr-2" />
|
|
||||||
{m.bugreport_open_folder()}
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<Dialog.Footer>
|
|
||||||
<Button onclick={closeBugReportDialog}>
|
|
||||||
{m.bugreport_close()}
|
|
||||||
</Button>
|
|
||||||
</Dialog.Footer>
|
|
||||||
{:else}
|
|
||||||
<!-- Form State -->
|
|
||||||
<form onsubmit={handleBugReportSubmit}>
|
|
||||||
<Dialog.Header>
|
|
||||||
<Dialog.Title>{m.bugreport_title()}</Dialog.Title>
|
|
||||||
<Dialog.Description>
|
|
||||||
{m.bugreport_description()}
|
|
||||||
</Dialog.Description>
|
|
||||||
</Dialog.Header>
|
|
||||||
|
|
||||||
<div class="grid gap-4 py-4">
|
|
||||||
<div class="grid gap-2">
|
|
||||||
<Label for="bug-name">{m.bugreport_name_label()}</Label>
|
|
||||||
<Input
|
|
||||||
id="bug-name"
|
|
||||||
placeholder={m.bugreport_name_placeholder()}
|
|
||||||
bind:value={userName}
|
|
||||||
disabled={isSubmitting}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="grid gap-2">
|
|
||||||
<Label for="bug-email">{m.bugreport_email_label()}</Label>
|
|
||||||
<Input
|
|
||||||
id="bug-email"
|
|
||||||
type="email"
|
|
||||||
placeholder={m.bugreport_email_placeholder()}
|
|
||||||
bind:value={userEmail}
|
|
||||||
disabled={isSubmitting}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="grid gap-2">
|
|
||||||
<Label for="bug-description">{m.bugreport_text_label()}</Label>
|
|
||||||
<Textarea
|
|
||||||
id="bug-description"
|
|
||||||
placeholder={m.bugreport_text_placeholder()}
|
|
||||||
bind:value={bugDescription}
|
|
||||||
disabled={isSubmitting}
|
|
||||||
class="min-h-[120px]"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Screenshot Preview -->
|
|
||||||
<div class="grid gap-2">
|
|
||||||
<Label class="flex items-center gap-2">
|
|
||||||
<Camera class="h-4 w-4" />
|
|
||||||
{m.bugreport_screenshot_label()}
|
|
||||||
</Label>
|
|
||||||
{#if isCapturing}
|
|
||||||
<div class="flex items-center gap-2 text-muted-foreground text-sm">
|
|
||||||
<Loader2 class="h-4 w-4 animate-spin" />
|
|
||||||
Capturing...
|
|
||||||
</div>
|
|
||||||
{:else if screenshotData}
|
|
||||||
<div class="border rounded-md overflow-hidden">
|
|
||||||
<img
|
|
||||||
src="data:image/png;base64,{screenshotData}"
|
|
||||||
alt="Screenshot preview"
|
|
||||||
class="w-full h-32 object-cover object-top opacity-80 hover:opacity-100 transition-opacity cursor-pointer"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
{:else}
|
|
||||||
<div class="text-muted-foreground text-sm">
|
|
||||||
No screenshot available
|
|
||||||
</div>
|
|
||||||
{/if}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<p class="text-muted-foreground text-sm">
|
|
||||||
{m.bugreport_info()}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<Dialog.Footer>
|
|
||||||
<button type="button" class={buttonVariants({ variant: "outline" })} disabled={isSubmitting} onclick={closeBugReportDialog}>
|
|
||||||
{m.bugreport_cancel()}
|
|
||||||
</button>
|
|
||||||
<Button type="submit" disabled={isSubmitting || isCapturing}>
|
|
||||||
{#if isSubmitting}
|
|
||||||
<Loader2 class="h-4 w-4 mr-2 animate-spin" />
|
|
||||||
{m.bugreport_submitting()}
|
|
||||||
{:else}
|
|
||||||
{m.bugreport_submit()}
|
|
||||||
{/if}
|
|
||||||
</Button>
|
|
||||||
</Dialog.Footer>
|
|
||||||
</form>
|
|
||||||
{/if}
|
|
||||||
</Dialog.Content>
|
|
||||||
</Dialog.Root>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
@@ -808,26 +573,4 @@
|
|||||||
transform: rotate(360deg);
|
transform: rotate(360deg);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
:global(.custom-scrollbar::-webkit-scrollbar) {
|
|
||||||
width: 6px;
|
|
||||||
height: 6px;
|
|
||||||
}
|
|
||||||
|
|
||||||
:global(.custom-scrollbar::-webkit-scrollbar-track) {
|
|
||||||
background: transparent;
|
|
||||||
}
|
|
||||||
|
|
||||||
:global(.custom-scrollbar::-webkit-scrollbar-thumb) {
|
|
||||||
background: var(--border);
|
|
||||||
border-radius: 6px;
|
|
||||||
}
|
|
||||||
|
|
||||||
:global(.custom-scrollbar::-webkit-scrollbar-thumb:hover) {
|
|
||||||
background: var(--muted-foreground);
|
|
||||||
}
|
|
||||||
|
|
||||||
:global(.custom-scrollbar::-webkit-scrollbar-corner) {
|
|
||||||
background: transparent;
|
|
||||||
}
|
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -1,25 +1,18 @@
|
|||||||
import type { PageLoad } from './$types';
|
import type { PageLoad } from './$types';
|
||||||
import { GetMachineData, GetConfig } from "$lib/wailsjs/go/main/App";
|
import { GetConfig } from "$lib/wailsjs/go/main/App";
|
||||||
import { browser } from '$app/environment';
|
import { browser } from '$app/environment';
|
||||||
import { dangerZoneEnabled } from "$lib/stores/app";
|
|
||||||
import { get } from "svelte/store";
|
|
||||||
|
|
||||||
export const load = (async () => {
|
export const load = (async () => {
|
||||||
if (!browser) return { machineData: null, config: null };
|
if (!browser) return { config: null };
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const [machineData, configRoot] = await Promise.all([
|
const configRoot = await GetConfig();
|
||||||
get(dangerZoneEnabled) ? GetMachineData() : Promise.resolve(null),
|
|
||||||
GetConfig()
|
|
||||||
]);
|
|
||||||
return {
|
return {
|
||||||
machineData,
|
|
||||||
config: configRoot.EMLy
|
config: configRoot.EMLy
|
||||||
};
|
};
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error("Failed to load settings data", e);
|
console.error("Failed to load settings data", e);
|
||||||
return {
|
return {
|
||||||
machineData: null,
|
|
||||||
config: null
|
config: null
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user