mirror of
https://github.com/Memo-2023/mana-monorepo.git
synced 2026-05-26 14:54:39 +02:00
refactor: restructure
monorepo with apps/ and services/ directories
This commit is contained in:
parent
25824ed0ac
commit
ff80aeec1f
4062 changed files with 2592 additions and 1278 deletions
96
apps/picture/apps/mobile/utils/blurhash.ts
Normal file
96
apps/picture/apps/mobile/utils/blurhash.ts
Normal file
|
|
@ -0,0 +1,96 @@
|
|||
/**
|
||||
* BlurHash utility functions
|
||||
*
|
||||
* BlurHash is a compact representation of an image placeholder that can be
|
||||
* decoded into a blurred version of the original image.
|
||||
*/
|
||||
|
||||
import { encode } from 'blurhash';
|
||||
|
||||
/**
|
||||
* Generate a BlurHash from an image URL
|
||||
*
|
||||
* @param imageUrl - URL of the image to generate BlurHash for
|
||||
* @param componentX - Number of horizontal components (default: 4)
|
||||
* @param componentY - Number of vertical components (default: 3)
|
||||
* @returns BlurHash string or null on error
|
||||
*/
|
||||
export async function generateBlurHash(
|
||||
imageUrl: string,
|
||||
componentX: number = 4,
|
||||
componentY: number = 3
|
||||
): Promise<string | null> {
|
||||
try {
|
||||
// Download the image
|
||||
const response = await fetch(imageUrl);
|
||||
if (!response.ok) {
|
||||
console.error('Failed to fetch image for BlurHash generation');
|
||||
return null;
|
||||
}
|
||||
|
||||
const blob = await response.blob();
|
||||
|
||||
// Create image bitmap for pixel data extraction
|
||||
const imageBitmap = await createImageBitmap(blob);
|
||||
|
||||
// Create canvas to get pixel data
|
||||
const canvas = document.createElement('canvas');
|
||||
canvas.width = imageBitmap.width;
|
||||
canvas.height = imageBitmap.height;
|
||||
|
||||
const context = canvas.getContext('2d');
|
||||
if (!context) {
|
||||
console.error('Could not get canvas context');
|
||||
return null;
|
||||
}
|
||||
|
||||
context.drawImage(imageBitmap, 0, 0);
|
||||
|
||||
// Get pixel data
|
||||
const imageData = context.getImageData(0, 0, canvas.width, canvas.height);
|
||||
|
||||
// Generate BlurHash
|
||||
const blurHash = encode(
|
||||
imageData.data,
|
||||
imageData.width,
|
||||
imageData.height,
|
||||
componentX,
|
||||
componentY
|
||||
);
|
||||
|
||||
return blurHash;
|
||||
} catch (error) {
|
||||
console.error('Error generating BlurHash:', error);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate BlurHash from Image Uint8Array (for Edge Functions)
|
||||
*
|
||||
* This is a server-side compatible version that works with Deno
|
||||
*/
|
||||
export async function generateBlurHashFromBuffer(
|
||||
imageBuffer: Uint8Array,
|
||||
width: number,
|
||||
height: number,
|
||||
componentX: number = 4,
|
||||
componentY: number = 3
|
||||
): Promise<string | null> {
|
||||
try {
|
||||
// This would need an image decoding library in Deno
|
||||
// For now, we return a placeholder
|
||||
// TODO: Implement proper image decoding in Deno environment
|
||||
console.log('BlurHash generation from buffer not yet implemented for Deno');
|
||||
return null;
|
||||
} catch (error) {
|
||||
console.error('Error generating BlurHash from buffer:', error);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Default BlurHash fallback
|
||||
* A neutral gray blur that works well for most images
|
||||
*/
|
||||
export const DEFAULT_BLURHASH = 'L5H2EC=PM+yV0g-mq.wG9c010J}I';
|
||||
66
apps/picture/apps/mobile/utils/image.ts
Normal file
66
apps/picture/apps/mobile/utils/image.ts
Normal file
|
|
@ -0,0 +1,66 @@
|
|||
/**
|
||||
* Image utility functions for handling Supabase Storage transformations
|
||||
*/
|
||||
|
||||
export type ThumbnailSize = 'tiny' | 'small' | 'medium' | 'full';
|
||||
|
||||
/**
|
||||
* Get the appropriate thumbnail URL based on the view mode or size requirement
|
||||
*
|
||||
* Supabase Storage Transformations: https://supabase.com/docs/guides/storage/serving/image-transformations
|
||||
* Format: {storage-url}/render/image/authenticated/{bucket}/{path}?width={w}&height={h}
|
||||
*/
|
||||
export function getThumbnailUrl(
|
||||
publicUrl: string | null,
|
||||
size: ThumbnailSize = 'medium'
|
||||
): string | null {
|
||||
if (!publicUrl) return null;
|
||||
|
||||
// If it's already a full URL with transformations, return as is
|
||||
if (publicUrl.includes('?width=') || publicUrl.includes('&width=')) {
|
||||
return publicUrl;
|
||||
}
|
||||
|
||||
// Determine dimensions based on size
|
||||
const dimensions: Record<ThumbnailSize, number> = {
|
||||
tiny: 100, // For grid5 view
|
||||
small: 200, // For grid3 view
|
||||
medium: 400, // For single view
|
||||
full: 0, // No transformation, use original
|
||||
};
|
||||
|
||||
const targetSize = dimensions[size];
|
||||
|
||||
// If full resolution requested, return original URL
|
||||
if (targetSize === 0) {
|
||||
return publicUrl;
|
||||
}
|
||||
|
||||
// Parse the Supabase Storage URL
|
||||
// Expected format: https://{project}.supabase.co/storage/v1/object/public/{bucket}/{path}
|
||||
const url = new URL(publicUrl);
|
||||
|
||||
// Add transformation parameters
|
||||
url.searchParams.set('width', targetSize.toString());
|
||||
url.searchParams.set('height', targetSize.toString());
|
||||
url.searchParams.set('resize', 'cover'); // Crop to fill the dimensions
|
||||
url.searchParams.set('quality', '80'); // Good balance between quality and file size
|
||||
|
||||
return url.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get thumbnail size based on view mode
|
||||
*/
|
||||
export function getSizeForViewMode(viewMode: 'single' | 'grid3' | 'grid5'): ThumbnailSize {
|
||||
switch (viewMode) {
|
||||
case 'grid5':
|
||||
return 'tiny';
|
||||
case 'grid3':
|
||||
return 'small';
|
||||
case 'single':
|
||||
return 'medium';
|
||||
default:
|
||||
return 'medium';
|
||||
}
|
||||
}
|
||||
91
apps/picture/apps/mobile/utils/logger.ts
Normal file
91
apps/picture/apps/mobile/utils/logger.ts
Normal file
|
|
@ -0,0 +1,91 @@
|
|||
/**
|
||||
* Centralized logging utility
|
||||
* - Development: Logs to console
|
||||
* - Production: Can be integrated with Sentry, LogRocket, etc.
|
||||
*/
|
||||
|
||||
const isDevelopment = __DEV__;
|
||||
|
||||
export const logger = {
|
||||
/**
|
||||
* Log debug information (only in development)
|
||||
*/
|
||||
debug: (...args: any[]) => {
|
||||
if (isDevelopment) {
|
||||
console.log('[DEBUG]', ...args);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Log general information
|
||||
*/
|
||||
info: (...args: any[]) => {
|
||||
if (isDevelopment) {
|
||||
console.log('[INFO]', ...args);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Log warnings (always logged)
|
||||
*/
|
||||
warn: (...args: any[]) => {
|
||||
console.warn('[WARN]', ...args);
|
||||
// TODO: Send to error tracking service in production
|
||||
},
|
||||
|
||||
/**
|
||||
* Log errors (always logged)
|
||||
*/
|
||||
error: (...args: any[]) => {
|
||||
console.error('[ERROR]', ...args);
|
||||
// TODO: Send to error tracking service (Sentry, etc.)
|
||||
},
|
||||
|
||||
/**
|
||||
* Log success messages (only in development)
|
||||
*/
|
||||
success: (...args: any[]) => {
|
||||
if (isDevelopment) {
|
||||
console.log('[SUCCESS] ✅', ...args);
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* Performance logger for measuring execution time
|
||||
*/
|
||||
export const perfLogger = {
|
||||
start: (label: string) => {
|
||||
if (isDevelopment) {
|
||||
console.time(label);
|
||||
}
|
||||
},
|
||||
|
||||
end: (label: string) => {
|
||||
if (isDevelopment) {
|
||||
console.timeEnd(label);
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* Network request logger
|
||||
*/
|
||||
export const networkLogger = {
|
||||
request: (url: string, method: string, body?: any) => {
|
||||
if (isDevelopment) {
|
||||
console.log(`[NETWORK] ${method} ${url}`, body ? { body } : '');
|
||||
}
|
||||
},
|
||||
|
||||
response: (url: string, status: number, data?: any) => {
|
||||
if (isDevelopment) {
|
||||
console.log(`[NETWORK] Response ${status} from ${url}`, data ? { data } : '');
|
||||
}
|
||||
},
|
||||
|
||||
error: (url: string, error: any) => {
|
||||
console.error(`[NETWORK] Error from ${url}`, error);
|
||||
// TODO: Send to error tracking
|
||||
},
|
||||
};
|
||||
9
apps/picture/apps/mobile/utils/polyfills.web.js
Normal file
9
apps/picture/apps/mobile/utils/polyfills.web.js
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
// Polyfills for web platform
|
||||
// This file handles compatibility issues when running in web browser
|
||||
|
||||
// Export a mock for @supabase/node-fetch that uses browser fetch
|
||||
const nodeFetch = (...args) => fetch(...args);
|
||||
nodeFetch.default = nodeFetch;
|
||||
|
||||
export default nodeFetch;
|
||||
export { nodeFetch as fetch };
|
||||
56
apps/picture/apps/mobile/utils/supabase.ts
Normal file
56
apps/picture/apps/mobile/utils/supabase.ts
Normal file
|
|
@ -0,0 +1,56 @@
|
|||
import AsyncStorage from '@react-native-async-storage/async-storage';
|
||||
import { createClient } from '@supabase/supabase-js';
|
||||
import { Platform } from 'react-native';
|
||||
import type { Database } from '@picture/shared/types';
|
||||
|
||||
const supabaseUrl = process.env.EXPO_PUBLIC_SUPABASE_URL || 'https://mjuvnnjxwfwlmxjsgkqu.supabase.co';
|
||||
const supabaseAnonKey = process.env.EXPO_PUBLIC_SUPABASE_ANON_KEY || 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6Im1qdXZubmp4d2Z3bG14anNna3F1Iiwicm9sZSI6ImFub24iLCJpYXQiOjE3NTYyNTg5NTUsImV4cCI6MjA3MTgzNDk1NX0.EeOKzyPnZ42zpFl7oi54qDcAZSW-XGoB0tSNwUiX9GU';
|
||||
|
||||
// Create a storage adapter that works for both web and mobile
|
||||
const createStorage = () => {
|
||||
if (Platform.OS === 'web') {
|
||||
// For web, use a simple localStorage wrapper
|
||||
return {
|
||||
getItem: async (key: string) => {
|
||||
try {
|
||||
if (typeof window !== 'undefined' && window.localStorage) {
|
||||
const item = window.localStorage.getItem(key);
|
||||
return item;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error getting item from localStorage:', error);
|
||||
}
|
||||
return null;
|
||||
},
|
||||
setItem: async (key: string, value: string) => {
|
||||
try {
|
||||
if (typeof window !== 'undefined' && window.localStorage) {
|
||||
window.localStorage.setItem(key, value);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error setting item in localStorage:', error);
|
||||
}
|
||||
},
|
||||
removeItem: async (key: string) => {
|
||||
try {
|
||||
if (typeof window !== 'undefined' && window.localStorage) {
|
||||
window.localStorage.removeItem(key);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error removing item from localStorage:', error);
|
||||
}
|
||||
},
|
||||
};
|
||||
}
|
||||
// For mobile, use AsyncStorage
|
||||
return AsyncStorage;
|
||||
};
|
||||
|
||||
export const supabase = createClient<Database>(supabaseUrl, supabaseAnonKey, {
|
||||
auth: {
|
||||
storage: createStorage(),
|
||||
autoRefreshToken: true,
|
||||
persistSession: true,
|
||||
detectSessionInUrl: Platform.OS === 'web',
|
||||
},
|
||||
});
|
||||
Loading…
Add table
Add a link
Reference in a new issue