3a1da1482c
- FastAPI + SQLite + Alpine.js + HTMX PWA for Bingo Bango Bongo golf scoring - Passwordless magic link auth with email (aiosmtplib) and admin invite system - Real-time score entry via WebSocket with drag-and-drop player ordering - Course management with per-hole par and stroke index - Scorecard with golf score markers (birdie, eagle, bogey, etc.) - Two-step new game form with tee selection per player - Dashboard with stats, live games social stream, and recent game history - Game summary view (read-only scorecard with leaderboard) - User profile pages with win stats - PWA install support with generated PNG icons - Admin panel for users, courses, games, and invitations Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
73 lines
2.1 KiB
JavaScript
73 lines
2.1 KiB
JavaScript
function avatarUpload(formAction, redirectTo) {
|
|
return {
|
|
tab: 'emoji',
|
|
showModal: false,
|
|
previewUrl: null,
|
|
croppedBlob: null,
|
|
cropper: null,
|
|
|
|
openFile(e) {
|
|
const file = e.target.files[0];
|
|
if (!file) return;
|
|
const reader = new FileReader();
|
|
reader.onload = (ev) => {
|
|
this.previewUrl = ev.target.result;
|
|
this.showModal = true;
|
|
this.$nextTick(() => this._initCropper());
|
|
};
|
|
reader.readAsDataURL(file);
|
|
// Clear the input so re-selecting the same file triggers change again
|
|
e.target.value = '';
|
|
},
|
|
|
|
_initCropper() {
|
|
if (this.cropper) { this.cropper.destroy(); this.cropper = null; }
|
|
const img = this.$refs.cropImg;
|
|
this.cropper = new Cropper(img, {
|
|
aspectRatio: 1,
|
|
viewMode: 1,
|
|
dragMode: 'move',
|
|
autoCropArea: 1,
|
|
cropBoxMovable: false,
|
|
cropBoxResizable: false,
|
|
toggleDragModeOnDblclick: false,
|
|
background: false,
|
|
});
|
|
},
|
|
|
|
confirmCrop() {
|
|
const canvas = this.cropper.getCroppedCanvas({ width: 256, height: 256 });
|
|
canvas.toBlob((blob) => {
|
|
this.croppedBlob = blob;
|
|
this.previewUrl = canvas.toDataURL('image/jpeg');
|
|
this.showModal = false;
|
|
this.cropper.destroy();
|
|
this.cropper = null;
|
|
}, 'image/jpeg', 0.88);
|
|
},
|
|
|
|
cancelCrop() {
|
|
this.showModal = false;
|
|
if (this.cropper) { this.cropper.destroy(); this.cropper = null; }
|
|
if (!this.croppedBlob) this.previewUrl = null;
|
|
},
|
|
|
|
async submitForm(e) {
|
|
e.preventDefault();
|
|
const form = e.target;
|
|
const fd = new FormData(form);
|
|
|
|
if (this.tab === 'upload' && this.croppedBlob) {
|
|
fd.delete('avatar_file');
|
|
fd.set('avatar_file', this.croppedBlob, 'avatar.jpg');
|
|
fd.set('avatar_emoji', '');
|
|
} else if (this.tab === 'emoji') {
|
|
fd.delete('avatar_file');
|
|
}
|
|
|
|
const resp = await fetch(formAction, { method: 'POST', body: fd, redirect: 'follow' });
|
|
window.location.href = resp.url || redirectTo;
|
|
},
|
|
};
|
|
}
|