feat: Add profile images
This commit is contained in:
@@ -0,0 +1,99 @@
|
||||
export const PROFILE_AVATAR_ALLOWED_MIME_TYPES = [
|
||||
'image/webp',
|
||||
'image/gif',
|
||||
'image/jpeg'
|
||||
] as const;
|
||||
|
||||
export const PROFILE_AVATAR_ACCEPT_ATTRIBUTE = '.webp,.gif,.jpg,.jpeg,image/webp,image/gif,image/jpeg';
|
||||
export const PROFILE_AVATAR_OUTPUT_SIZE = 256;
|
||||
export const PROFILE_AVATAR_EDITOR_FRAME_SIZE = 224;
|
||||
export const PROFILE_AVATAR_MIN_ZOOM = 1;
|
||||
export const PROFILE_AVATAR_MAX_ZOOM = 4;
|
||||
|
||||
export interface ProfileAvatarDimensions {
|
||||
width: number;
|
||||
height: number;
|
||||
}
|
||||
|
||||
export interface EditableProfileAvatarSource extends ProfileAvatarDimensions {
|
||||
file: File;
|
||||
objectUrl: string;
|
||||
mime: string;
|
||||
name: string;
|
||||
preservesAnimation: boolean;
|
||||
}
|
||||
|
||||
export interface ProfileAvatarTransform {
|
||||
zoom: number;
|
||||
offsetX: number;
|
||||
offsetY: number;
|
||||
}
|
||||
|
||||
export interface ProfileAvatarUpdates {
|
||||
avatarUrl: string;
|
||||
avatarHash: string;
|
||||
avatarMime: string;
|
||||
avatarUpdatedAt: number;
|
||||
}
|
||||
|
||||
export interface ProcessedProfileAvatar extends ProfileAvatarUpdates, ProfileAvatarDimensions {
|
||||
base64: string;
|
||||
blob: Blob;
|
||||
}
|
||||
|
||||
export function resolveProfileAvatarStorageFileName(mime: string | null | undefined): string {
|
||||
switch (mime?.toLowerCase()) {
|
||||
case 'image/gif':
|
||||
return 'profile.gif';
|
||||
|
||||
case 'image/jpeg':
|
||||
case 'image/jpg':
|
||||
return 'profile.jpg';
|
||||
|
||||
default:
|
||||
return 'profile.webp';
|
||||
}
|
||||
}
|
||||
|
||||
export function clampProfileAvatarZoom(zoom: number): number {
|
||||
if (!Number.isFinite(zoom)) {
|
||||
return PROFILE_AVATAR_MIN_ZOOM;
|
||||
}
|
||||
|
||||
return Math.min(Math.max(zoom, PROFILE_AVATAR_MIN_ZOOM), PROFILE_AVATAR_MAX_ZOOM);
|
||||
}
|
||||
|
||||
export function resolveProfileAvatarBaseScale(
|
||||
source: ProfileAvatarDimensions,
|
||||
frameSize = PROFILE_AVATAR_EDITOR_FRAME_SIZE
|
||||
): number {
|
||||
return Math.max(frameSize / source.width, frameSize / source.height);
|
||||
}
|
||||
|
||||
export function clampProfileAvatarTransform(
|
||||
source: ProfileAvatarDimensions,
|
||||
transform: ProfileAvatarTransform,
|
||||
frameSize = PROFILE_AVATAR_EDITOR_FRAME_SIZE
|
||||
): ProfileAvatarTransform {
|
||||
const zoom = clampProfileAvatarZoom(transform.zoom);
|
||||
const renderedWidth = source.width * resolveProfileAvatarBaseScale(source, frameSize) * zoom;
|
||||
const renderedHeight = source.height * resolveProfileAvatarBaseScale(source, frameSize) * zoom;
|
||||
const maxOffsetX = Math.max(0, (renderedWidth - frameSize) / 2);
|
||||
const maxOffsetY = Math.max(0, (renderedHeight - frameSize) / 2);
|
||||
|
||||
return {
|
||||
zoom,
|
||||
offsetX: clampOffset(transform.offsetX, maxOffsetX),
|
||||
offsetY: clampOffset(transform.offsetY, maxOffsetY)
|
||||
};
|
||||
}
|
||||
|
||||
function clampOffset(value: number, maxMagnitude: number): number {
|
||||
if (!Number.isFinite(value)) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
const nextValue = Math.min(Math.max(value, -maxMagnitude), maxMagnitude);
|
||||
|
||||
return Object.is(nextValue, -0) ? 0 : nextValue;
|
||||
}
|
||||
Reference in New Issue
Block a user