All checks were successful
CI / container-job (push) Successful in 41s
- adonisrc.ts: Load official drive_provider and unload custom driver_provider. - packages.json: Add @headlessui/vue dependency for tab components. - AvatarController.ts: Rewrite avatar generation logic to always return the same avatar per user. - auth/UserController.ts: Add profile and profileUpdate methods to support user profile editing. - Submitter/datasetController.ts & app/models/file.ts: Adapt code to use the official drive_provider. - app/models/user.ts: Introduce “isAdmin” getter. - config/drive.ts: Create new configuration for the official drive_provider. - providers/vinejs_provider.ts: Adapt allowedExtensions control to use provided options or database enabled extensions. - resource/js/app.ts: Load default Head and Link components. - resources/js/menu.ts: Add settings-profile.edit menu point. - resources/js/Components/action-message.vue: Add new component for improved user feedback after form submissions. - New avatar-input.vue component: Enable profile picture selection. - Components/CardBox.vue: Alter layout to optionally show HeaderIcon in title bar. - FormControl.vue: Define a readonly prop for textareas. - Improve overall UI with updates to NavBar.vue, UserAvatar.vue, UserAvatarCurrentUser.vue, and add v-model support to password-meter.vue. - Remove profile editing logic from AccountInfo.vue and introduce new profile components (show.vue, update-password-form.vue, update-profile-information.vue). - app.edge: Modify page (add @inertiaHead tag) for better meta management. - routes.ts: Add new routes for editing user profiles. - General npm updates.
104 lines
3.7 KiB
TypeScript
104 lines
3.7 KiB
TypeScript
import type { HttpContext } from '@adonisjs/core/http';
|
|
import { StatusCodes } from 'http-status-codes';
|
|
|
|
const prefixes = ['von', 'van'];
|
|
|
|
export default class AvatarController {
|
|
public async generateAvatar({ request, response }: HttpContext) {
|
|
try {
|
|
const { name, size } = request.only(['name', 'size']);
|
|
|
|
const initials = this.getInitials(name);
|
|
|
|
const originalColor = this.getColorFromName(name);
|
|
const backgroundColor = this.lightenColor(originalColor, 60);
|
|
const textColor = this.darkenColor(originalColor);
|
|
|
|
const svgContent = `
|
|
<svg width="${size || 50}" height="${size || 50}" xmlns="http://www.w3.org/2000/svg">
|
|
<rect width="100%" height="100%" fill="#${backgroundColor}"/>
|
|
<text x="50%" y="50%" dominant-baseline="middle" text-anchor="middle" font-weight="bold" font-family="Arial, sans-serif" font-size="${
|
|
(size / 100) * 40 || 25
|
|
}" fill="#${textColor}">${initials}</text>
|
|
</svg>
|
|
`;
|
|
|
|
response.header('Content-type', 'image/svg+xml');
|
|
response.header('Cache-Control', 'no-cache');
|
|
response.header('Pragma', 'no-cache');
|
|
response.header('Expires', '0');
|
|
|
|
return response.send(svgContent);
|
|
} catch (error) {
|
|
return response.status(StatusCodes.OK).json({ error: error.message });
|
|
}
|
|
}
|
|
|
|
private getInitials(name: string) {
|
|
const parts = name.split(' ');
|
|
let initials = '';
|
|
|
|
if (parts.length >= 2) {
|
|
const firstName = parts[0];
|
|
const lastName = parts[parts.length - 1];
|
|
|
|
const firstInitial = firstName.charAt(0).toUpperCase();
|
|
const lastInitial = lastName.charAt(0).toUpperCase();
|
|
|
|
if (prefixes.includes(lastName.toLowerCase()) && lastName === lastName.toUpperCase()) {
|
|
initials = firstInitial + lastName.charAt(1).toUpperCase();
|
|
} else {
|
|
initials = firstInitial + lastInitial;
|
|
}
|
|
} else if (parts.length === 1) {
|
|
initials = parts[0].substring(0, 2).toUpperCase();
|
|
}
|
|
|
|
return initials;
|
|
}
|
|
|
|
private getColorFromName(name: string) {
|
|
let hash = 0;
|
|
for (let i = 0; i < name.length; i++) {
|
|
hash = name.charCodeAt(i) + ((hash << 5) - hash);
|
|
}
|
|
let color = '#';
|
|
for (let i = 0; i < 3; i++) {
|
|
const value = (hash >> (i * 8)) & 0xff;
|
|
color += ('00' + value.toString(16)).substr(-2);
|
|
}
|
|
return color.replace('#', '');
|
|
}
|
|
|
|
private lightenColor(hexColor: string, percent: number) {
|
|
let r = parseInt(hexColor.substring(0, 2), 16);
|
|
let g = parseInt(hexColor.substring(2, 4), 16);
|
|
let b = parseInt(hexColor.substring(4, 6), 16);
|
|
|
|
r = Math.floor((r * (100 + percent)) / 100);
|
|
g = Math.floor((g * (100 + percent)) / 100);
|
|
b = Math.floor((b * (100 + percent)) / 100);
|
|
|
|
r = r < 255 ? r : 255;
|
|
g = g < 255 ? g : 255;
|
|
b = b < 255 ? b : 255;
|
|
|
|
const lighterHex = ((r << 16) | (g << 8) | b).toString(16);
|
|
|
|
return lighterHex.padStart(6, '0');
|
|
}
|
|
|
|
private darkenColor(hexColor: string) {
|
|
const r = parseInt(hexColor.slice(0, 2), 16);
|
|
const g = parseInt(hexColor.slice(2, 4), 16);
|
|
const b = parseInt(hexColor.slice(4, 6), 16);
|
|
|
|
const darkerR = Math.round(r * 0.6);
|
|
const darkerG = Math.round(g * 0.6);
|
|
const darkerB = Math.round(b * 0.6);
|
|
|
|
const darkerColor = ((darkerR << 16) + (darkerG << 8) + darkerB).toString(16);
|
|
|
|
return darkerColor.padStart(6, '0');
|
|
}
|
|
}
|