style: auto-format codebase with Prettier

Applied formatting to 1487+ files using pnpm format:write
  - TypeScript/JavaScript files
  - Svelte components
  - Astro pages
  - JSON configs
  - Markdown docs

  13 files still need manual review (Astro JSX comments)
This commit is contained in:
Wuesteon 2025-11-27 18:33:16 +01:00
parent 0241f5554c
commit d36b321d9d
3952 changed files with 661498 additions and 739751 deletions

View file

@ -6,411 +6,411 @@ import { useStore } from '~/store/store';
import { AudioChunk } from '~/types/database';
export interface AudioState {
isPlaying: boolean;
isLoading: boolean;
currentPosition: number;
duration: number;
currentChunk?: AudioChunk;
sound?: Audio.Sound;
playbackRate: number;
isPlaying: boolean;
isLoading: boolean;
currentPosition: number;
duration: number;
currentChunk?: AudioChunk;
sound?: Audio.Sound;
playbackRate: number;
}
export const useAudio = () => {
const { settings, updateSettings } = useStore();
const { updateText } = useTexts();
const { settings, updateSettings } = useStore();
const { updateText } = useTexts();
const [audioState, setAudioState] = useState<AudioState>({
isPlaying: false,
isLoading: false,
currentPosition: 0,
duration: 0,
playbackRate: settings.playbackRate || 1.0,
});
const [audioState, setAudioState] = useState<AudioState>({
isPlaying: false,
isLoading: false,
currentPosition: 0,
duration: 0,
playbackRate: settings.playbackRate || 1.0,
});
const [generationProgress, setGenerationProgress] = useState<AudioGenerationProgress | null>(
null
);
const [downloadProgress, setDownloadProgress] = useState<{
completed: number;
total: number;
currentChunk: string;
} | null>(null);
const [generationProgress, setGenerationProgress] = useState<AudioGenerationProgress | null>(
null
);
const [downloadProgress, setDownloadProgress] = useState<{
completed: number;
total: number;
currentChunk: string;
} | null>(null);
const { setCurrentText, setIsPlaying, setCurrentPosition } = useStore();
const audioService = AudioService.getInstance();
const { setCurrentText, setIsPlaying, setCurrentPosition } = useStore();
const audioService = AudioService.getInstance();
// Initialize audio session
useEffect(() => {
const initializeAudio = async () => {
try {
await Audio.setAudioModeAsync({
allowsRecordingIOS: false,
playsInSilentModeIOS: true,
shouldDuckAndroid: true,
staysActiveInBackground: true,
playThroughEarpieceAndroid: false,
});
} catch (error) {
console.error('Error initializing audio:', error);
}
};
// Initialize audio session
useEffect(() => {
const initializeAudio = async () => {
try {
await Audio.setAudioModeAsync({
allowsRecordingIOS: false,
playsInSilentModeIOS: true,
shouldDuckAndroid: true,
staysActiveInBackground: true,
playThroughEarpieceAndroid: false,
});
} catch (error) {
console.error('Error initializing audio:', error);
}
};
initializeAudio();
}, []);
initializeAudio();
}, []);
// Clean up audio when component unmounts
useEffect(() => {
return () => {
if (audioState.sound) {
audioState.sound.unloadAsync();
}
};
}, [audioState.sound]);
// Clean up audio when component unmounts
useEffect(() => {
return () => {
if (audioState.sound) {
audioState.sound.unloadAsync();
}
};
}, [audioState.sound]);
// Generate audio for a text
const generateAudio = useCallback(
async (
textId: string,
content: string,
voice: string = 'de-DE',
speed: number = 1.0,
currentText?: any
) => {
try {
setGenerationProgress({
chunksCompleted: 0,
totalChunks: 1,
currentChunk: 'Starting...',
isComplete: false,
});
// Generate audio for a text
const generateAudio = useCallback(
async (
textId: string,
content: string,
voice: string = 'de-DE',
speed: number = 1.0,
currentText?: any
) => {
try {
setGenerationProgress({
chunksCompleted: 0,
totalChunks: 1,
currentChunk: 'Starting...',
isComplete: false,
});
// Import migration helper
const { generateVersionId } = await import('~/utils/audioMigration');
const newVersionId = generateVersionId();
// Import migration helper
const { generateVersionId } = await import('~/utils/audioMigration');
const newVersionId = generateVersionId();
const result = await audioService.generateAudioForText(
textId,
content,
voice,
speed,
1000,
setGenerationProgress,
newVersionId
);
const result = await audioService.generateAudioForText(
textId,
content,
voice,
speed,
1000,
setGenerationProgress,
newVersionId
);
if (!result.success) {
throw new Error(result.error);
}
if (!result.success) {
throw new Error(result.error);
}
// Get current text to append to audioVersions
if (!currentText) {
throw new Error('Text must be provided to generate audio');
}
// Get current text to append to audioVersions
if (!currentText) {
throw new Error('Text must be provided to generate audio');
}
// Import migration helper for existing code
const { migrateAudioData } = await import('~/utils/audioMigration');
// Import migration helper for existing code
const { migrateAudioData } = await import('~/utils/audioMigration');
// Migrate old data if needed
const migratedData = migrateAudioData(currentText.data);
const newAudioVersion = {
id: newVersionId,
chunks: result.chunks || [],
settings: { voice, speed },
totalSize: result.chunks?.reduce((sum, chunk) => sum + chunk.size, 0) || 0,
hasLocalCache: false,
createdAt: new Date().toISOString(),
};
// Migrate old data if needed
const migratedData = migrateAudioData(currentText.data);
const newAudioVersion = {
id: newVersionId,
chunks: result.chunks || [],
settings: { voice, speed },
totalSize: result.chunks?.reduce((sum, chunk) => sum + chunk.size, 0) || 0,
hasLocalCache: false,
createdAt: new Date().toISOString(),
};
// Append new version to audioVersions
const updatedAudioVersions = [...(migratedData.audioVersions || []), newAudioVersion];
// Append new version to audioVersions
const updatedAudioVersions = [...(migratedData.audioVersions || []), newAudioVersion];
// Update text with new audio version
await updateText(textId, {
data: {
...migratedData,
audioVersions: updatedAudioVersions,
currentAudioVersion: newVersionId,
// Keep legacy audio field for backward compatibility
audio: {
hasLocalCache: false,
chunks: result.chunks || [],
totalSize: newAudioVersion.totalSize,
lastGenerated: newAudioVersion.createdAt,
settings: { voice, speed },
},
},
});
// Update text with new audio version
await updateText(textId, {
data: {
...migratedData,
audioVersions: updatedAudioVersions,
currentAudioVersion: newVersionId,
// Keep legacy audio field for backward compatibility
audio: {
hasLocalCache: false,
chunks: result.chunks || [],
totalSize: newAudioVersion.totalSize,
lastGenerated: newAudioVersion.createdAt,
settings: { voice, speed },
},
},
});
return result;
} catch (error) {
console.error('Error generating audio:', error);
throw error;
} finally {
setGenerationProgress(null);
}
},
[audioService, updateText]
);
return result;
} catch (error) {
console.error('Error generating audio:', error);
throw error;
} finally {
setGenerationProgress(null);
}
},
[audioService, updateText]
);
// Download audio chunks to local storage
const downloadAudio = useCallback(
async (textId: string, chunks: AudioChunk[]) => {
try {
setDownloadProgress({
completed: 0,
total: chunks.length,
currentChunk: 'Starting download...',
});
// Download audio chunks to local storage
const downloadAudio = useCallback(
async (textId: string, chunks: AudioChunk[]) => {
try {
setDownloadProgress({
completed: 0,
total: chunks.length,
currentChunk: 'Starting download...',
});
const result = await audioService.downloadAudioChunks(textId, chunks, setDownloadProgress);
const result = await audioService.downloadAudioChunks(textId, chunks, setDownloadProgress);
if (!result.success) {
throw new Error(result.error);
}
if (!result.success) {
throw new Error(result.error);
}
// Update text to mark as locally cached
await updateText(textId, {
data: {
audio: {
hasLocalCache: true,
chunks: result.localChunks || chunks,
totalSize: result.localChunks?.reduce((sum, chunk) => sum + chunk.size, 0) || 0,
lastGenerated: new Date().toISOString(),
},
},
});
// Update text to mark as locally cached
await updateText(textId, {
data: {
audio: {
hasLocalCache: true,
chunks: result.localChunks || chunks,
totalSize: result.localChunks?.reduce((sum, chunk) => sum + chunk.size, 0) || 0,
lastGenerated: new Date().toISOString(),
},
},
});
return result;
} catch (error) {
console.error('Error downloading audio:', error);
throw error;
} finally {
setDownloadProgress(null);
}
},
[audioService, updateText]
);
return result;
} catch (error) {
console.error('Error downloading audio:', error);
throw error;
} finally {
setDownloadProgress(null);
}
},
[audioService, updateText]
);
// Play audio from local cache
const playAudio = useCallback(
async (textId: string, chunks: AudioChunk[], startPosition: number = 0) => {
try {
setAudioState((prev) => ({ ...prev, isLoading: true }));
// Play audio from local cache
const playAudio = useCallback(
async (textId: string, chunks: AudioChunk[], startPosition: number = 0) => {
try {
setAudioState((prev) => ({ ...prev, isLoading: true }));
// Stop current audio if playing
if (audioState.sound) {
audioState.sound.unloadAsync();
}
// Stop current audio if playing
if (audioState.sound) {
audioState.sound.unloadAsync();
}
// Calculate total duration from all chunks
const totalDuration = chunks.reduce((sum, chunk) => sum + chunk.duration, 0) * 1000; // Convert to milliseconds
// Calculate total duration from all chunks
const totalDuration = chunks.reduce((sum, chunk) => sum + chunk.duration, 0) * 1000; // Convert to milliseconds
const result = await audioService.playAudioFromSupabase(textId, chunks, startPosition);
const result = await audioService.playAudioFromSupabase(textId, chunks, startPosition);
if (!result.sound) {
throw new Error(result.error);
}
if (!result.sound) {
throw new Error(result.error);
}
const currentChunk = result.chunk;
const allChunks = result.chunks || chunks;
const currentChunk = result.chunk;
const allChunks = result.chunks || chunks;
// Set up playback status update
result.sound.setOnPlaybackStatusUpdate((status) => {
if (status.isLoaded) {
// Calculate the actual position across all chunks
const chunkPosition = status.positionMillis || 0;
const overallPosition = currentChunk
? currentChunk.start + chunkPosition
: chunkPosition;
// Set up playback status update
result.sound.setOnPlaybackStatusUpdate((status) => {
if (status.isLoaded) {
// Calculate the actual position across all chunks
const chunkPosition = status.positionMillis || 0;
const overallPosition = currentChunk
? currentChunk.start + chunkPosition
: chunkPosition;
setAudioState((prev) => ({
...prev,
isPlaying: status.isPlaying,
currentPosition: overallPosition,
duration: totalDuration, // Keep using total duration
}));
setAudioState((prev) => ({
...prev,
isPlaying: status.isPlaying,
currentPosition: overallPosition,
duration: totalDuration, // Keep using total duration
}));
// Update global store
setIsPlaying(status.isPlaying);
setCurrentPosition(overallPosition);
}
});
// Update global store
setIsPlaying(status.isPlaying);
setCurrentPosition(overallPosition);
}
});
setAudioState((prev) => ({
...prev,
sound: result.sound,
isLoading: false,
isPlaying: true,
duration: totalDuration, // Set total duration of all chunks
currentChunk: currentChunk,
}));
setAudioState((prev) => ({
...prev,
sound: result.sound,
isLoading: false,
isPlaying: true,
duration: totalDuration, // Set total duration of all chunks
currentChunk: currentChunk,
}));
setCurrentText(textId);
setCurrentText(textId);
// Start playing
await result.sound.playAsync();
// Start playing
await result.sound.playAsync();
// Apply saved playback rate
if (audioState.playbackRate !== 1.0) {
await result.sound.setRateAsync(audioState.playbackRate, true);
}
} catch (error) {
console.error('Error playing audio:', error);
setAudioState((prev) => ({ ...prev, isLoading: false }));
throw error;
}
},
[
audioState.sound,
audioState.playbackRate,
audioService,
setCurrentText,
setIsPlaying,
setCurrentPosition,
]
);
// Apply saved playback rate
if (audioState.playbackRate !== 1.0) {
await result.sound.setRateAsync(audioState.playbackRate, true);
}
} catch (error) {
console.error('Error playing audio:', error);
setAudioState((prev) => ({ ...prev, isLoading: false }));
throw error;
}
},
[
audioState.sound,
audioState.playbackRate,
audioService,
setCurrentText,
setIsPlaying,
setCurrentPosition,
]
);
// Pause audio
const pauseAudio = useCallback(async () => {
if (audioState.sound) {
await audioState.sound.pauseAsync();
setAudioState((prev) => ({ ...prev, isPlaying: false }));
setIsPlaying(false);
}
}, [audioState.sound, setIsPlaying]);
// Pause audio
const pauseAudio = useCallback(async () => {
if (audioState.sound) {
await audioState.sound.pauseAsync();
setAudioState((prev) => ({ ...prev, isPlaying: false }));
setIsPlaying(false);
}
}, [audioState.sound, setIsPlaying]);
// Resume audio
const resumeAudio = useCallback(async () => {
if (audioState.sound) {
await audioState.sound.playAsync();
setAudioState((prev) => ({ ...prev, isPlaying: true }));
setIsPlaying(true);
}
}, [audioState.sound, setIsPlaying]);
// Resume audio
const resumeAudio = useCallback(async () => {
if (audioState.sound) {
await audioState.sound.playAsync();
setAudioState((prev) => ({ ...prev, isPlaying: true }));
setIsPlaying(true);
}
}, [audioState.sound, setIsPlaying]);
// Stop audio
const stopAudio = useCallback(async () => {
if (audioState.sound) {
await audioState.sound.pauseAsync();
await audioState.sound.unloadAsync();
setAudioState((prev) => ({
...prev,
sound: undefined,
isPlaying: false,
currentPosition: 0,
duration: 0,
}));
setCurrentText(null);
setIsPlaying(false);
setCurrentPosition(0);
}
}, [audioState.sound, setCurrentText, setIsPlaying, setCurrentPosition]);
// Stop audio
const stopAudio = useCallback(async () => {
if (audioState.sound) {
await audioState.sound.pauseAsync();
await audioState.sound.unloadAsync();
setAudioState((prev) => ({
...prev,
sound: undefined,
isPlaying: false,
currentPosition: 0,
duration: 0,
}));
setCurrentText(null);
setIsPlaying(false);
setCurrentPosition(0);
}
}, [audioState.sound, setCurrentText, setIsPlaying, setCurrentPosition]);
// Seek to position
const seekTo = useCallback(
async (position: number) => {
if (audioState.sound) {
await audioState.sound.setPositionAsync(position);
}
},
[audioState.sound]
);
// Seek to position
const seekTo = useCallback(
async (position: number) => {
if (audioState.sound) {
await audioState.sound.setPositionAsync(position);
}
},
[audioState.sound]
);
// Seek forward by seconds
const seekForward = useCallback(
async (seconds: number = 15) => {
if (audioState.sound && audioState.duration > 0) {
const newPosition = Math.min(
audioState.currentPosition + seconds * 1000,
audioState.duration
);
await audioState.sound.setPositionAsync(newPosition);
}
},
[audioState.sound, audioState.currentPosition, audioState.duration]
);
// Seek forward by seconds
const seekForward = useCallback(
async (seconds: number = 15) => {
if (audioState.sound && audioState.duration > 0) {
const newPosition = Math.min(
audioState.currentPosition + seconds * 1000,
audioState.duration
);
await audioState.sound.setPositionAsync(newPosition);
}
},
[audioState.sound, audioState.currentPosition, audioState.duration]
);
// Seek backward by seconds
const seekBackward = useCallback(
async (seconds: number = 15) => {
if (audioState.sound) {
const newPosition = Math.max(audioState.currentPosition - seconds * 1000, 0);
await audioState.sound.setPositionAsync(newPosition);
}
},
[audioState.sound, audioState.currentPosition]
);
// Seek backward by seconds
const seekBackward = useCallback(
async (seconds: number = 15) => {
if (audioState.sound) {
const newPosition = Math.max(audioState.currentPosition - seconds * 1000, 0);
await audioState.sound.setPositionAsync(newPosition);
}
},
[audioState.sound, audioState.currentPosition]
);
// Set playback speed
const setPlaybackSpeed = useCallback(
async (rate: number) => {
if (audioState.sound) {
try {
await audioState.sound.setRateAsync(rate, true);
setAudioState((prev) => ({ ...prev, playbackRate: rate }));
// Persist to store
updateSettings({ playbackRate: rate });
} catch (error) {
console.error('Error setting playback rate:', error);
}
} else {
// If no sound is playing, just update the state for next playback
setAudioState((prev) => ({ ...prev, playbackRate: rate }));
// Persist to store
updateSettings({ playbackRate: rate });
}
},
[audioState.sound, updateSettings]
);
// Set playback speed
const setPlaybackSpeed = useCallback(
async (rate: number) => {
if (audioState.sound) {
try {
await audioState.sound.setRateAsync(rate, true);
setAudioState((prev) => ({ ...prev, playbackRate: rate }));
// Persist to store
updateSettings({ playbackRate: rate });
} catch (error) {
console.error('Error setting playback rate:', error);
}
} else {
// If no sound is playing, just update the state for next playback
setAudioState((prev) => ({ ...prev, playbackRate: rate }));
// Persist to store
updateSettings({ playbackRate: rate });
}
},
[audioState.sound, updateSettings]
);
// Clear audio cache
const clearCache = useCallback(
async (textId: string, chunks: AudioChunk[]) => {
await audioService.clearAudioCache(textId, chunks);
// Clear audio cache
const clearCache = useCallback(
async (textId: string, chunks: AudioChunk[]) => {
await audioService.clearAudioCache(textId, chunks);
// Update text to mark as not cached
await updateText(textId, {
data: {
audio: {
hasLocalCache: false,
chunks,
totalSize: 0,
},
},
});
},
[audioService, updateText]
);
// Update text to mark as not cached
await updateText(textId, {
data: {
audio: {
hasLocalCache: false,
chunks,
totalSize: 0,
},
},
});
},
[audioService, updateText]
);
// Get cache size
const getCacheSize = useCallback(async () => {
return await audioService.getCacheSize();
}, [audioService]);
// Get cache size
const getCacheSize = useCallback(async () => {
return await audioService.getCacheSize();
}, [audioService]);
// Check if audio is cached
const isAudioCached = useCallback(
async (textId: string, chunks: AudioChunk[]) => {
return await audioService.isAudioCached(textId, chunks);
},
[audioService]
);
// Check if audio is cached
const isAudioCached = useCallback(
async (textId: string, chunks: AudioChunk[]) => {
return await audioService.isAudioCached(textId, chunks);
},
[audioService]
);
return {
audioState,
generationProgress,
downloadProgress,
generateAudio,
downloadAudio,
playAudio,
pauseAudio,
resumeAudio,
stopAudio,
seekTo,
seekForward,
seekBackward,
setPlaybackSpeed,
clearCache,
getCacheSize,
isAudioCached,
};
return {
audioState,
generationProgress,
downloadProgress,
generateAudio,
downloadAudio,
playAudio,
pauseAudio,
resumeAudio,
stopAudio,
seekTo,
seekForward,
seekBackward,
setPlaybackSpeed,
clearCache,
getCacheSize,
isAudioCached,
};
};

View file

@ -4,110 +4,110 @@ import { useStore } from '~/store/store';
import { Session } from '@supabase/supabase-js';
export const useAuth = () => {
const [session, setSession] = useState<Session | null>(null);
const [loading, setLoading] = useState(true);
const { setUser } = useStore();
const [session, setSession] = useState<Session | null>(null);
const [loading, setLoading] = useState(true);
const { setUser } = useStore();
useEffect(() => {
// Get initial session
supabase.auth.getSession().then(({ data: { session } }) => {
setSession(session);
if (session?.user) {
setUser({
id: session.user.id,
email: session.user.email!,
});
}
setLoading(false);
});
useEffect(() => {
// Get initial session
supabase.auth.getSession().then(({ data: { session } }) => {
setSession(session);
if (session?.user) {
setUser({
id: session.user.id,
email: session.user.email!,
});
}
setLoading(false);
});
// Listen for auth changes
const {
data: { subscription },
} = supabase.auth.onAuthStateChange((_event, session) => {
setSession(session);
if (session?.user) {
setUser({
id: session.user.id,
email: session.user.email!,
});
} else {
setUser(null);
}
});
// Listen for auth changes
const {
data: { subscription },
} = supabase.auth.onAuthStateChange((_event, session) => {
setSession(session);
if (session?.user) {
setUser({
id: session.user.id,
email: session.user.email!,
});
} else {
setUser(null);
}
});
return () => subscription.unsubscribe();
}, [setUser]);
return () => subscription.unsubscribe();
}, [setUser]);
const signUp = async (email: string, password: string) => {
try {
const { data, error } = await supabase.auth.signUp({
email,
password,
});
const signUp = async (email: string, password: string) => {
try {
const { data, error } = await supabase.auth.signUp({
email,
password,
});
if (error) throw error;
return { data, error: null };
} catch (error) {
return {
data: null,
error: error instanceof Error ? error.message : 'Fehler bei der Registrierung',
};
}
};
if (error) throw error;
return { data, error: null };
} catch (error) {
return {
data: null,
error: error instanceof Error ? error.message : 'Fehler bei der Registrierung',
};
}
};
const signIn = async (email: string, password: string) => {
try {
const { data, error } = await supabase.auth.signInWithPassword({
email,
password,
});
const signIn = async (email: string, password: string) => {
try {
const { data, error } = await supabase.auth.signInWithPassword({
email,
password,
});
if (error) throw error;
return { data, error: null };
} catch (error) {
return {
data: null,
error: error instanceof Error ? error.message : 'Fehler beim Anmelden',
};
}
};
if (error) throw error;
return { data, error: null };
} catch (error) {
return {
data: null,
error: error instanceof Error ? error.message : 'Fehler beim Anmelden',
};
}
};
const signOut = async () => {
try {
const { error } = await supabase.auth.signOut();
if (error) throw error;
return { error: null };
} catch (error) {
return {
error: error instanceof Error ? error.message : 'Fehler beim Abmelden',
};
}
};
const signOut = async () => {
try {
const { error } = await supabase.auth.signOut();
if (error) throw error;
return { error: null };
} catch (error) {
return {
error: error instanceof Error ? error.message : 'Fehler beim Abmelden',
};
}
};
const resetPassword = async (email: string) => {
try {
const { data, error } = await supabase.auth.resetPasswordForEmail(email, {
redirectTo: 'reader://reset-password',
});
const resetPassword = async (email: string) => {
try {
const { data, error } = await supabase.auth.resetPasswordForEmail(email, {
redirectTo: 'reader://reset-password',
});
if (error) throw error;
return { data, error: null };
} catch (error) {
return {
data: null,
error: error instanceof Error ? error.message : 'Fehler beim Zurücksetzen',
};
}
};
if (error) throw error;
return { data, error: null };
} catch (error) {
return {
data: null,
error: error instanceof Error ? error.message : 'Fehler beim Zurücksetzen',
};
}
};
return {
session,
user: session?.user ?? null,
loading,
signUp,
signIn,
signOut,
resetPassword,
};
return {
session,
user: session?.user ?? null,
loading,
signUp,
signIn,
signOut,
resetPassword,
};
};

View file

@ -3,175 +3,175 @@ import { supabase } from '~/utils/supabase';
import { Text, TextData } from '~/types/database';
export const useTexts = () => {
const [texts, setTexts] = useState<Text[]>([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
const [texts, setTexts] = useState<Text[]>([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
useEffect(() => {
fetchTexts();
useEffect(() => {
fetchTexts();
// Realtime Subscription
const subscription = supabase
.channel('texts_changes')
.on(
'postgres_changes',
{
event: '*',
schema: 'public',
table: 'texts',
},
(payload) => {
if (payload.eventType === 'INSERT') {
// Check if text already exists to avoid duplicates
setTexts((prev) => {
const exists = prev.some((text) => text.id === payload.new.id);
if (exists) return prev;
return [payload.new as Text, ...prev];
});
} else if (payload.eventType === 'UPDATE') {
setTexts((prev) =>
prev.map((text) => (text.id === payload.new.id ? (payload.new as Text) : text))
);
} else if (payload.eventType === 'DELETE') {
setTexts((prev) => prev.filter((text) => text.id !== payload.old.id));
}
}
)
.subscribe();
// Realtime Subscription
const subscription = supabase
.channel('texts_changes')
.on(
'postgres_changes',
{
event: '*',
schema: 'public',
table: 'texts',
},
(payload) => {
if (payload.eventType === 'INSERT') {
// Check if text already exists to avoid duplicates
setTexts((prev) => {
const exists = prev.some((text) => text.id === payload.new.id);
if (exists) return prev;
return [payload.new as Text, ...prev];
});
} else if (payload.eventType === 'UPDATE') {
setTexts((prev) =>
prev.map((text) => (text.id === payload.new.id ? (payload.new as Text) : text))
);
} else if (payload.eventType === 'DELETE') {
setTexts((prev) => prev.filter((text) => text.id !== payload.old.id));
}
}
)
.subscribe();
return () => {
subscription.unsubscribe();
};
}, []);
return () => {
subscription.unsubscribe();
};
}, []);
const fetchTexts = async () => {
try {
setLoading(true);
const { data, error } = await supabase
.from('texts')
.select('*')
.order('updated_at', { ascending: false });
const fetchTexts = async () => {
try {
setLoading(true);
const { data, error } = await supabase
.from('texts')
.select('*')
.order('updated_at', { ascending: false });
if (error) throw error;
setTexts(data || []);
} catch (err) {
setError(err instanceof Error ? err.message : 'Ein Fehler ist aufgetreten');
} finally {
setLoading(false);
}
};
if (error) throw error;
setTexts(data || []);
} catch (err) {
setError(err instanceof Error ? err.message : 'Ein Fehler ist aufgetreten');
} finally {
setLoading(false);
}
};
const createText = async (title: string, content: string, initialData?: Partial<TextData>) => {
try {
// Get current user
const {
data: { user },
} = await supabase.auth.getUser();
const createText = async (title: string, content: string, initialData?: Partial<TextData>) => {
try {
// Get current user
const {
data: { user },
} = await supabase.auth.getUser();
if (!user) {
throw new Error('Benutzer nicht eingeloggt');
}
if (!user) {
throw new Error('Benutzer nicht eingeloggt');
}
const { data, error } = await supabase
.from('texts')
.insert({
title,
content,
user_id: user.id, // Explicitly set user_id
data: {
tts: { speed: 1.0, voice: 'de-DE-Neural2-A' },
tags: [],
stats: { playCount: 0, totalTime: 0, completed: false },
...initialData,
},
})
.select()
.single();
const { data, error } = await supabase
.from('texts')
.insert({
title,
content,
user_id: user.id, // Explicitly set user_id
data: {
tts: { speed: 1.0, voice: 'de-DE-Neural2-A' },
tags: [],
stats: { playCount: 0, totalTime: 0, completed: false },
...initialData,
},
})
.select()
.single();
if (error) throw error;
if (error) throw error;
// Refresh the texts list to ensure we have the latest data
await fetchTexts();
// Refresh the texts list to ensure we have the latest data
await fetchTexts();
return { data, error: null };
} catch (err) {
return {
data: null,
error: err instanceof Error ? err.message : 'Fehler beim Erstellen',
};
}
};
return { data, error: null };
} catch (err) {
return {
data: null,
error: err instanceof Error ? err.message : 'Fehler beim Erstellen',
};
}
};
const updateText = async (textId: string, updates: Partial<Text>) => {
try {
const { data, error } = await supabase
.from('texts')
.update(updates)
.eq('id', textId)
.select()
.single();
const updateText = async (textId: string, updates: Partial<Text>) => {
try {
const { data, error } = await supabase
.from('texts')
.update(updates)
.eq('id', textId)
.select()
.single();
if (error) throw error;
return { data, error: null };
} catch (err) {
return {
data: null,
error: err instanceof Error ? err.message : 'Fehler beim Aktualisieren',
};
}
};
if (error) throw error;
return { data, error: null };
} catch (err) {
return {
data: null,
error: err instanceof Error ? err.message : 'Fehler beim Aktualisieren',
};
}
};
const deleteText = async (textId: string) => {
try {
const { error } = await supabase.from('texts').delete().eq('id', textId);
const deleteText = async (textId: string) => {
try {
const { error } = await supabase.from('texts').delete().eq('id', textId);
if (error) throw error;
return { error: null };
} catch (err) {
return {
error: err instanceof Error ? err.message : 'Fehler beim Löschen',
};
}
};
if (error) throw error;
return { error: null };
} catch (err) {
return {
error: err instanceof Error ? err.message : 'Fehler beim Löschen',
};
}
};
const updatePosition = async (textId: string, position: number) => {
const text = texts.find((t) => t.id === textId);
if (!text) return { error: 'Text nicht gefunden' };
const updatePosition = async (textId: string, position: number) => {
const text = texts.find((t) => t.id === textId);
if (!text) return { error: 'Text nicht gefunden' };
return updateText(textId, {
data: {
...text.data,
tts: {
...text.data.tts,
lastPosition: position,
lastPlayed: new Date().toISOString(),
},
},
});
};
return updateText(textId, {
data: {
...text.data,
tts: {
...text.data.tts,
lastPosition: position,
lastPlayed: new Date().toISOString(),
},
},
});
};
const getTextsByTag = (tag: string) => {
return texts.filter((text) => text.data.tags?.includes(tag));
};
const getTextsByTag = (tag: string) => {
return texts.filter((text) => text.data.tags?.includes(tag));
};
const getAllTags = () => {
const tagSet = new Set<string>();
texts.forEach((text) => {
text.data.tags?.forEach((tag) => tagSet.add(tag));
});
return Array.from(tagSet).sort();
};
const getAllTags = () => {
const tagSet = new Set<string>();
texts.forEach((text) => {
text.data.tags?.forEach((tag) => tagSet.add(tag));
});
return Array.from(tagSet).sort();
};
return {
texts,
loading,
error,
createText,
updateText,
deleteText,
updatePosition,
getTextsByTag,
getAllTags,
refetch: fetchTexts,
};
return {
texts,
loading,
error,
createText,
updateText,
deleteText,
updatePosition,
getTextsByTag,
getAllTags,
refetch: fetchTexts,
};
};

View file

@ -1,179 +1,179 @@
import { useStore } from '~/store/store';
export interface ThemeColors {
// Background colors
background: string;
surface: string;
surfaceSecondary: string;
// Background colors
background: string;
surface: string;
surfaceSecondary: string;
// Text colors
text: string;
textSecondary: string;
textTertiary: string;
// Text colors
text: string;
textSecondary: string;
textTertiary: string;
// Border colors
border: string;
borderSecondary: string;
// Border colors
border: string;
borderSecondary: string;
// Primary colors
primary: string;
primaryLight: string;
primaryDark: string;
// Primary colors
primary: string;
primaryLight: string;
primaryDark: string;
// Status colors
success: string;
successLight: string;
warning: string;
warningLight: string;
error: string;
errorLight: string;
// Status colors
success: string;
successLight: string;
warning: string;
warningLight: string;
error: string;
errorLight: string;
// Tab bar colors
tabBarBackground: string;
tabBarBorder: string;
tabBarActive: string;
tabBarInactive: string;
// Tab bar colors
tabBarBackground: string;
tabBarBorder: string;
tabBarActive: string;
tabBarInactive: string;
}
const lightTheme: ThemeColors = {
// Background colors
background: 'bg-gray-50',
surface: 'bg-white',
surfaceSecondary: 'bg-gray-100',
// Background colors
background: 'bg-gray-50',
surface: 'bg-white',
surfaceSecondary: 'bg-gray-100',
// Text colors
text: 'text-gray-900',
textSecondary: 'text-gray-600',
textTertiary: 'text-gray-500',
// Text colors
text: 'text-gray-900',
textSecondary: 'text-gray-600',
textTertiary: 'text-gray-500',
// Border colors
border: 'border-gray-200',
borderSecondary: 'border-gray-300',
// Border colors
border: 'border-gray-200',
borderSecondary: 'border-gray-300',
// Primary colors
primary: 'bg-blue-600',
primaryLight: 'bg-blue-50',
primaryDark: 'bg-blue-700',
// Primary colors
primary: 'bg-blue-600',
primaryLight: 'bg-blue-50',
primaryDark: 'bg-blue-700',
// Status colors
success: 'bg-green-600',
successLight: 'bg-green-100',
warning: 'bg-orange-600',
warningLight: 'bg-orange-100',
error: 'bg-red-600',
errorLight: 'bg-red-50',
// Status colors
success: 'bg-green-600',
successLight: 'bg-green-100',
warning: 'bg-orange-600',
warningLight: 'bg-orange-100',
error: 'bg-red-600',
errorLight: 'bg-red-50',
// Tab bar colors
tabBarBackground: '#ffffff',
tabBarBorder: '#e5e7eb',
tabBarActive: '#3B82F6',
tabBarInactive: '#6b7280',
// Tab bar colors
tabBarBackground: '#ffffff',
tabBarBorder: '#e5e7eb',
tabBarActive: '#3B82F6',
tabBarInactive: '#6b7280',
};
const darkTheme: ThemeColors = {
// Background colors
background: 'bg-gray-900',
surface: 'bg-gray-800',
surfaceSecondary: 'bg-gray-700',
// Background colors
background: 'bg-gray-900',
surface: 'bg-gray-800',
surfaceSecondary: 'bg-gray-700',
// Text colors
text: 'text-white',
textSecondary: 'text-gray-300',
textTertiary: 'text-gray-400',
// Text colors
text: 'text-white',
textSecondary: 'text-gray-300',
textTertiary: 'text-gray-400',
// Border colors
border: 'border-gray-600',
borderSecondary: 'border-gray-500',
// Border colors
border: 'border-gray-600',
borderSecondary: 'border-gray-500',
// Primary colors
primary: 'bg-blue-600',
primaryLight: 'bg-blue-900',
primaryDark: 'bg-blue-700',
// Primary colors
primary: 'bg-blue-600',
primaryLight: 'bg-blue-900',
primaryDark: 'bg-blue-700',
// Status colors
success: 'bg-green-600',
successLight: 'bg-green-900',
warning: 'bg-orange-600',
warningLight: 'bg-orange-900',
error: 'bg-red-600',
errorLight: 'bg-red-900',
// Status colors
success: 'bg-green-600',
successLight: 'bg-green-900',
warning: 'bg-orange-600',
warningLight: 'bg-orange-900',
error: 'bg-red-600',
errorLight: 'bg-red-900',
// Tab bar colors
tabBarBackground: '#1f2937',
tabBarBorder: '#374151',
tabBarActive: '#3B82F6',
tabBarInactive: '#9ca3af',
// Tab bar colors
tabBarBackground: '#1f2937',
tabBarBorder: '#374151',
tabBarActive: '#3B82F6',
tabBarInactive: '#9ca3af',
};
export const useTheme = () => {
const { settings } = useStore();
const isDark = settings.theme === 'dark';
const { settings } = useStore();
const isDark = settings.theme === 'dark';
const colors = isDark ? darkTheme : lightTheme;
const colors = isDark ? darkTheme : lightTheme;
return {
isDark,
colors,
theme: settings.theme,
};
return {
isDark,
colors,
theme: settings.theme,
};
};
// Text color utilities
export const useTextColors = () => {
const { colors } = useTheme();
const { colors } = useTheme();
return {
primary: colors.text,
secondary: colors.textSecondary,
tertiary: colors.textTertiary,
primaryText: colors.text.replace('text-', 'text-'),
secondaryText: colors.textSecondary.replace('text-', 'text-'),
tertiaryText: colors.textTertiary.replace('text-', 'text-'),
};
return {
primary: colors.text,
secondary: colors.textSecondary,
tertiary: colors.textTertiary,
primaryText: colors.text.replace('text-', 'text-'),
secondaryText: colors.textSecondary.replace('text-', 'text-'),
tertiaryText: colors.textTertiary.replace('text-', 'text-'),
};
};
// Background color utilities
export const useBackgroundColors = () => {
const { colors } = useTheme();
const { colors } = useTheme();
return {
main: colors.background,
surface: colors.surface,
surfaceSecondary: colors.surfaceSecondary,
};
return {
main: colors.background,
surface: colors.surface,
surfaceSecondary: colors.surfaceSecondary,
};
};
// Border color utilities
export const useBorderColors = () => {
const { colors } = useTheme();
const { colors } = useTheme();
return {
main: colors.border,
secondary: colors.borderSecondary,
};
return {
main: colors.border,
secondary: colors.borderSecondary,
};
};
// Status color utilities
export const useStatusColors = () => {
const { colors } = useTheme();
const { colors } = useTheme();
return {
success: colors.success,
successLight: colors.successLight,
warning: colors.warning,
warningLight: colors.warningLight,
error: colors.error,
errorLight: colors.errorLight,
};
return {
success: colors.success,
successLight: colors.successLight,
warning: colors.warning,
warningLight: colors.warningLight,
error: colors.error,
errorLight: colors.errorLight,
};
};
// Primary color utilities
export const usePrimaryColors = () => {
const { colors } = useTheme();
const { colors } = useTheme();
return {
main: colors.primary,
light: colors.primaryLight,
dark: colors.primaryDark,
};
return {
main: colors.primary,
light: colors.primaryLight,
dark: colors.primaryDark,
};
};