Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 2 additions & 5 deletions src/lib/components/fallback/Decrypt.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -104,11 +104,8 @@
},
})

// pg-js auto-unwraps `data:`-mode payloads (single-entry zip
// with `data.bin`) into DecryptDataResult.plaintext. Email
// fallback uploads are always `data:` mode, so this branch is
// the expected one. If we ever receive a multi-file result
// here, fall back to the first entry's bytes.
// Email fallback uploads are `data:` mode → plaintext;
// `files[0]` is a defensive fallback for unexpected shapes.
const plaintext =
result.plaintext ??
new Uint8Array(await result.files[0].blob.arrayBuffer())
Expand Down
10 changes: 7 additions & 3 deletions src/lib/components/filesharing/DecryptionProgress.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
</script>

<div class="container">
<p class="label" id="decryption-progress-label">
<p class="label" id="decryption-progress-label" role="status">
{$_('filesharing.decryptpanel.downloadingAndDecrypting')}
</p>
{#if determinate}
Expand All @@ -35,8 +35,6 @@
class="bar-track"
role="progressbar"
aria-labelledby="decryption-progress-label"
aria-valuemin="0"
aria-valuemax="100"
>
<div class="bar-indeterminate"></div>
</div>
Expand Down Expand Up @@ -106,4 +104,10 @@
font-family: var(--pg-font-family);
font-size: var(--pg-font-size-sm);
}

@media (prefers-reduced-motion: reduce) {
.bar-indeterminate {
animation: none;
}
}
</style>
109 changes: 109 additions & 0 deletions src/lib/components/filesharing/ReportErrorButton.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
<script lang="ts">
import { _, locale } from 'svelte-i18n'
import { APP_VERSION } from '$lib/env'
import { reportError, isErrorReportingEnabled } from '$lib/errorReporting'

interface props {
error: unknown
context?: Record<string, unknown>
}

let { error, context = {} }: props = $props()

type SendState = 'idle' | 'sending' | 'sent' | 'failed'
let sendState: SendState = $state('idle')

let reportingEnabled = isErrorReportingEnabled()

async function sendReport() {
if (sendState === 'sending' || sendState === 'sent') return
sendState = 'sending'
try {
const ok = await reportError(error, {
appVersion: APP_VERSION,
uiLocale: $locale,
...context,
})
sendState = ok ? 'sent' : 'failed'
} catch {
sendState = 'failed'
}
}
</script>

{#if reportingEnabled}
<div class="report-wrapper">
<button
class="report-btn"
type="button"
disabled={sendState === 'sending' || sendState === 'sent'}
onclick={sendReport}
>
{#if sendState === 'sending'}
{$_('filesharing.crash.sending')}
{:else if sendState === 'sent'}
{$_('filesharing.crash.sentButton')}
{:else}
{$_('filesharing.crash.report')}
{/if}
</button>

<!-- Stable live-region wrapper so AT announces the failed-state
message when it appears (WCAG 4.1.3). -->
<div role="status" aria-live="polite" class="status-region">
{#if sendState === 'failed'}
<p class="status error">{$_('filesharing.crash.failed')}</p>
{/if}
</div>
</div>
{/if}

<style>
.report-wrapper {
display: flex;
flex-direction: column;
align-items: center;
gap: 0.5rem;
}

.report-btn {
padding: 0.65rem 1.25rem;
background-color: var(--pg-text);
color: var(--pg-general-background);
border: 1px solid transparent;
border-radius: var(--pg-border-radius-sm);
cursor: pointer;
font-size: var(--pg-font-size-base);
font-family: var(--pg-font-family);
font-weight: var(--pg-font-weight-medium);
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.15);
transition: transform 0.2s ease;
/* Lock width so swapping labels doesn't reflow the row. */
min-width: 14rem;
}

.report-btn:disabled {
opacity: 0.6;
cursor: not-allowed;
}

.report-btn:not(:disabled):hover {
transform: translateY(-1px);
}

.report-btn:focus-visible {
outline: 2px solid var(--pg-text);
outline-offset: 2px;
}

.status {
margin: 0;
font-size: var(--pg-font-size-sm);
font-family: var(--pg-font-family);
text-align: center;
}

.status.error {
color: var(--pg-input-error);
}
</style>
Loading