Skip to content

Commit 51618f1

Browse files
committed
✨ Improve UI/UX: better visual feedback, toast notifications, animations
- Add toast notification system instead of alerts - Improve error handling and user feedback - Add loading animation for processing - Enhance upload area visibility and size - Fix theme toggle positioning - Add smooth scrollbar styling - Improve accessibility with focus states - Add empty state styling - Better mobile responsiveness
1 parent b27038b commit 51618f1

File tree

2 files changed

+169
-24
lines changed

2 files changed

+169
-24
lines changed

docs/assets/app.js

Lines changed: 49 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -240,9 +240,9 @@ class App {
240240
const fileItem = document.createElement('div');
241241
fileItem.className = 'file-item';
242242
fileItem.innerHTML = `
243-
<span class="file-name">${file.name}</span>
243+
<span class="file-name">${this.escapeHtml(file.name)}</span>
244244
<span class="file-size">${this.formatFileSize(file.size)}</span>
245-
<button class="remove-file" data-index="${index}">×</button>
245+
<button class="remove-file" data-index="${index}" aria-label="Remove file">×</button>
246246
`;
247247

248248
fileItem.querySelector('.remove-file').addEventListener('click', () => {
@@ -331,20 +331,20 @@ class App {
331331
if (!result.success) {
332332
html += `
333333
<div class="result-card error">
334-
<h4>${result.fileName}</h4>
335-
<p class="error-message">Error: ${result.error}</p>
334+
<h4>${this.escapeHtml(result.fileName)}</h4>
335+
<p class="error-message">Error: ${this.escapeHtml(result.error)}</p>
336336
</div>
337337
`;
338338
return;
339339
}
340340

341341
const isDryRun = this.dryRunCheck.checked;
342-
const displayText = isDryRun ? result.cleanedText : result.cleanedText;
342+
const displayText = result.cleanedText;
343343

344344
html += `
345345
<div class="result-card">
346346
<div class="result-header">
347-
<h4>${result.fileName}</h4>
347+
<h4>${this.escapeHtml(result.fileName)}</h4>
348348
<div class="result-stats">
349349
<span>${result.stats.linesRemoved} lines removed</span>
350350
<span>${result.stats.duplicatesCollapsed} duplicates</span>
@@ -400,12 +400,26 @@ class App {
400400

401401
copyToClipboard(text) {
402402
navigator.clipboard.writeText(text).then(() => {
403-
alert('Text copied to clipboard!');
403+
// Show toast notification instead of alert
404+
this.showToast('Text copied to clipboard!');
404405
}).catch(() => {
405-
alert('Failed to copy to clipboard');
406+
this.showToast('Failed to copy to clipboard', 'error');
406407
});
407408
}
408409

410+
showToast(message, type = 'success') {
411+
const toast = document.createElement('div');
412+
toast.className = `toast toast-${type}`;
413+
toast.textContent = message;
414+
document.body.appendChild(toast);
415+
416+
setTimeout(() => toast.classList.add('show'), 10);
417+
setTimeout(() => {
418+
toast.classList.remove('show');
419+
setTimeout(() => toast.remove(), 300);
420+
}, 3000);
421+
}
422+
409423
clearAll() {
410424
this.files = [];
411425
this.results = [];
@@ -429,21 +443,14 @@ class App {
429443
}
430444
}
431445

432-
// Load JSZip library dynamically
433-
function loadJSZip() {
434-
return new Promise((resolve, reject) => {
435-
const script = document.createElement('script');
436-
script.src = 'https://cdnjs.cloudflare.com/ajax/libs/jszip/3.10.1/jszip.min.js';
437-
script.onload = resolve;
438-
script.onerror = reject;
439-
document.head.appendChild(script);
440-
});
441-
}
442-
443446
// Theme Management
444447
class ThemeManager {
445448
constructor() {
446449
this.themeToggle = document.getElementById('themeToggle');
450+
if (!this.themeToggle) {
451+
console.error('Theme toggle button not found');
452+
return;
453+
}
447454
this.currentTheme = localStorage.getItem('theme') || 'light';
448455
this.init();
449456
}
@@ -463,10 +470,27 @@ class ThemeManager {
463470

464471
updateIcon() {
465472
const icon = this.themeToggle.querySelector('.theme-icon');
466-
icon.textContent = this.currentTheme === 'light' ? '🌙' : '☀️';
473+
if (icon) {
474+
icon.textContent = this.currentTheme === 'light' ? '🌙' : '☀️';
475+
}
467476
}
468477
}
469478

479+
// Load JSZip library dynamically
480+
function loadJSZip() {
481+
return new Promise((resolve, reject) => {
482+
if (window.JSZip) {
483+
resolve();
484+
return;
485+
}
486+
const script = document.createElement('script');
487+
script.src = 'https://cdnjs.cloudflare.com/ajax/libs/jszip/3.10.1/jszip.min.js';
488+
script.onload = resolve;
489+
script.onerror = reject;
490+
document.head.appendChild(script);
491+
});
492+
}
493+
470494
// Initialize app when DOM is ready
471495
async function init() {
472496
try {
@@ -478,8 +502,11 @@ async function init() {
478502
window.JSZip = JSZip;
479503
new App();
480504
} catch (error) {
481-
console.error('Failed to load JSZip:', error);
482-
alert('Failed to initialize app. Please refresh the page.');
505+
console.error('Failed to initialize:', error);
506+
const errorDiv = document.createElement('div');
507+
errorDiv.className = 'error-banner';
508+
errorDiv.innerHTML = 'Failed to initialize app. Please refresh the page.';
509+
document.body.insertBefore(errorDiv, document.body.firstChild);
483510
}
484511
}
485512

@@ -488,4 +515,3 @@ if (document.readyState === 'loading') {
488515
} else {
489516
init();
490517
}
491-

docs/assets/style.css

Lines changed: 120 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ body {
6060
justify-content: space-between;
6161
align-items: center;
6262
gap: 2rem;
63+
position: relative;
6364
}
6465

6566
.header-main {
@@ -92,6 +93,9 @@ body {
9293
font-size: 1.5rem;
9394
transition: all 0.3s ease;
9495
backdrop-filter: blur(10px);
96+
flex-shrink: 0;
97+
position: relative;
98+
z-index: 10;
9599
}
96100

97101
.theme-toggle:hover {
@@ -144,10 +148,11 @@ body {
144148
transition: all 0.3s ease;
145149
background: var(--upload-bg);
146150
margin-bottom: 1.5rem;
147-
min-height: 200px;
151+
min-height: 220px;
148152
display: flex;
149153
align-items: center;
150154
justify-content: center;
155+
position: relative;
151156
}
152157

153158
.upload-area:hover {
@@ -736,6 +741,114 @@ body {
736741
}
737742
}
738743

744+
/* Toast Notifications */
745+
.toast {
746+
position: fixed;
747+
bottom: 2rem;
748+
right: 2rem;
749+
background: var(--background);
750+
color: var(--text);
751+
padding: 1rem 1.5rem;
752+
border-radius: 12px;
753+
box-shadow: 0 4px 20px var(--shadow-lg);
754+
border: 1px solid var(--border);
755+
z-index: 1000;
756+
opacity: 0;
757+
transform: translateY(20px);
758+
transition: all 0.3s ease;
759+
max-width: 300px;
760+
font-weight: 500;
761+
}
762+
763+
.toast.show {
764+
opacity: 1;
765+
transform: translateY(0);
766+
}
767+
768+
.toast-success {
769+
border-left: 4px solid var(--success);
770+
}
771+
772+
.toast-error {
773+
border-left: 4px solid var(--error);
774+
}
775+
776+
/* Error Banner */
777+
.error-banner {
778+
background: var(--error);
779+
color: white;
780+
padding: 1rem;
781+
text-align: center;
782+
font-weight: 500;
783+
}
784+
785+
/* Loading Animation */
786+
.loading {
787+
display: flex;
788+
align-items: center;
789+
justify-content: center;
790+
gap: 1rem;
791+
}
792+
793+
.loading::after {
794+
content: '';
795+
width: 20px;
796+
height: 20px;
797+
border: 3px solid var(--border);
798+
border-top-color: var(--primary-color);
799+
border-radius: 50%;
800+
animation: spin 0.8s linear infinite;
801+
}
802+
803+
@keyframes spin {
804+
to { transform: rotate(360deg); }
805+
}
806+
807+
/* Scrollbar Styling */
808+
.text-preview::-webkit-scrollbar {
809+
width: 8px;
810+
}
811+
812+
.text-preview::-webkit-scrollbar-track {
813+
background: var(--surface);
814+
border-radius: 4px;
815+
}
816+
817+
.text-preview::-webkit-scrollbar-thumb {
818+
background: var(--border);
819+
border-radius: 4px;
820+
}
821+
822+
.text-preview::-webkit-scrollbar-thumb:hover {
823+
background: var(--text-light);
824+
}
825+
826+
/* Smooth Scroll */
827+
html {
828+
scroll-behavior: smooth;
829+
}
830+
831+
/* Focus States for Accessibility */
832+
.btn:focus,
833+
.theme-toggle:focus,
834+
.remove-file:focus {
835+
outline: 2px solid var(--primary-color);
836+
outline-offset: 2px;
837+
}
838+
839+
/* Empty State */
840+
.empty-state {
841+
text-align: center;
842+
padding: 3rem;
843+
color: var(--text-light);
844+
}
845+
846+
.empty-state-icon {
847+
font-size: 4rem;
848+
margin-bottom: 1rem;
849+
opacity: 0.5;
850+
}
851+
739852
@media (max-width: 480px) {
740853
.upload-card h2 {
741854
font-size: 2rem;
@@ -750,4 +863,10 @@ body {
750863
flex-direction: column;
751864
gap: 0.25rem;
752865
}
866+
867+
.toast {
868+
right: 1rem;
869+
left: 1rem;
870+
max-width: none;
871+
}
753872
}

0 commit comments

Comments
 (0)