mirror of
https://github.com/Memo-2023/mana-monorepo.git
synced 2026-05-14 21:21:10 +02:00
fix(picture): resolve all TypeScript type errors
- Migrate stores from Database types to API types (camelCase) - Fix snake_case to camelCase property access across components - Remove deprecated userId params from API calls (now auth-based) - Fix Konva type comparisons in BoardCanvas - Fix async onMount return type issues - Add emoji property to ThemeVariantDefinition type - Fix Input autocomplete type to use AutoFill - Remove invalid 'downloading' status checks Reduces type errors from 120 to 0. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
parent
8a4cc298f6
commit
b9608bd5d2
16 changed files with 70 additions and 81 deletions
|
|
@ -146,7 +146,8 @@
|
|||
// Stage click to deselect
|
||||
stage.on('click', (e) => {
|
||||
console.log('[Canvas] Stage clicked, target:', e.target.getType());
|
||||
if (e.target === stage || e.target === backgroundLayer) {
|
||||
const targetType = e.target.getType();
|
||||
if (targetType === 'Stage' || e.target.name() === 'background') {
|
||||
console.log('[Canvas] Deselecting all');
|
||||
deselectAll();
|
||||
transformer.nodes([]);
|
||||
|
|
@ -437,7 +438,7 @@
|
|||
textarea.style.width = `${textNode.width() * textNode.scaleX()}px`;
|
||||
textarea.style.fontSize = `${textNode.fontSize()}px`;
|
||||
textarea.style.fontFamily = textNode.fontFamily();
|
||||
textarea.style.color = textNode.fill();
|
||||
textarea.style.color = String(textNode.fill());
|
||||
textarea.style.border = '2px solid #4A90E2';
|
||||
textarea.style.padding = '4px';
|
||||
textarea.style.margin = '0px';
|
||||
|
|
|
|||
|
|
@ -46,15 +46,12 @@
|
|||
const hasSelection = $derived($selectedItemIds.length > 0);
|
||||
|
||||
async function handleExport() {
|
||||
// Get the stage element
|
||||
const stage = document.querySelector('.konvajs-content canvas') as HTMLCanvasElement;
|
||||
if (!stage) return;
|
||||
// Get the canvas element
|
||||
const canvas = document.querySelector('.konvajs-content canvas') as HTMLCanvasElement;
|
||||
if (!canvas) return;
|
||||
|
||||
// Create download link
|
||||
const dataURL = stage.toDataURL({
|
||||
pixelRatio: 2, // Retina quality
|
||||
mimeType: 'image/png',
|
||||
});
|
||||
// Create download link using standard canvas API
|
||||
const dataURL = canvas.toDataURL('image/png');
|
||||
|
||||
const link = document.createElement('a');
|
||||
link.download = `${boardName}.png`;
|
||||
|
|
|
|||
|
|
@ -103,8 +103,6 @@
|
|||
generationProgress.set(`Warte auf Verarbeitung... (${i + 1}/${totalImages})`);
|
||||
} else if (progress.status === 'processing') {
|
||||
generationProgress.set(`Verarbeite Bild ${i + 1}/${totalImages}...`);
|
||||
} else if (progress.status === 'downloading') {
|
||||
generationProgress.set(`Speichere Bild ${i + 1}/${totalImages}...`);
|
||||
} else if (progress.status === 'completed') {
|
||||
unsubscribe();
|
||||
resolve();
|
||||
|
|
|
|||
|
|
@ -68,8 +68,6 @@
|
|||
generationProgress.set('Queued for processing...');
|
||||
} else if (progress.status === 'processing') {
|
||||
generationProgress.set('Processing...');
|
||||
} else if (progress.status === 'downloading') {
|
||||
generationProgress.set('Saving image...');
|
||||
} else if (progress.status === 'completed') {
|
||||
unsubscribe();
|
||||
resolve();
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@
|
|||
import { images } from '$lib/stores/images';
|
||||
import { archivedImages } from '$lib/stores/archive';
|
||||
import { showToast } from '$lib/stores/toast';
|
||||
import type { Database } from '@picture/shared/types';
|
||||
import type { Tag } from '$lib/api/tags';
|
||||
import {
|
||||
DownloadSimple,
|
||||
Link,
|
||||
|
|
@ -26,21 +26,19 @@
|
|||
Check,
|
||||
} from '@manacore/shared-icons';
|
||||
|
||||
type TagType = Database['public']['Tables']['tags']['Row'];
|
||||
|
||||
let tagSubmenuElement = $state<HTMLElement | null>(null);
|
||||
let imageTags = $state<TagType[]>([]);
|
||||
let imageTags = $state<Tag[]>([]);
|
||||
|
||||
// Check if current image is archived
|
||||
const isArchived = $derived(
|
||||
$contextMenu.image?.archived_at !== null && $contextMenu.image?.archived_at !== undefined
|
||||
$contextMenu.image?.archivedAt !== null && $contextMenu.image?.archivedAt !== undefined
|
||||
);
|
||||
|
||||
// Check if current image is favorite
|
||||
const isFavorite = $derived($contextMenu.image?.is_favorite === true);
|
||||
const isFavorite = $derived($contextMenu.image?.isFavorite === true);
|
||||
|
||||
// Check if current image belongs to current user
|
||||
const isOwnImage = $derived($contextMenu.image?.user_id === authStore.user?.id);
|
||||
const isOwnImage = $derived($contextMenu.image?.userId === authStore.user?.id);
|
||||
|
||||
type IconName = 'download' | 'link' | 'heart' | 'tag' | 'archive' | 'restore' | 'trash';
|
||||
|
||||
|
|
@ -72,7 +70,7 @@
|
|||
showTagSubmenu(rect.right, rect.top);
|
||||
}
|
||||
|
||||
async function handleAddTag(tag: TagType) {
|
||||
async function handleAddTag(tag: Tag) {
|
||||
if (!$contextMenu.image) return;
|
||||
|
||||
try {
|
||||
|
|
@ -85,7 +83,7 @@
|
|||
}
|
||||
}
|
||||
|
||||
async function handleRemoveTag(tag: TagType) {
|
||||
async function handleRemoveTag(tag: Tag) {
|
||||
if (!$contextMenu.image) return;
|
||||
|
||||
try {
|
||||
|
|
@ -99,10 +97,10 @@
|
|||
}
|
||||
|
||||
function handleDownload() {
|
||||
if (!$contextMenu.image?.public_url) return;
|
||||
if (!$contextMenu.image?.publicUrl) return;
|
||||
|
||||
const link = document.createElement('a');
|
||||
link.href = $contextMenu.image.public_url;
|
||||
link.href = $contextMenu.image.publicUrl;
|
||||
link.download = $contextMenu.image.filename || 'image.png';
|
||||
link.click();
|
||||
hideContextMenu();
|
||||
|
|
@ -110,9 +108,9 @@
|
|||
}
|
||||
|
||||
function handleCopyLink() {
|
||||
if (!$contextMenu.image?.public_url) return;
|
||||
if (!$contextMenu.image?.publicUrl) return;
|
||||
|
||||
navigator.clipboard.writeText($contextMenu.image.public_url);
|
||||
navigator.clipboard.writeText($contextMenu.image.publicUrl);
|
||||
hideContextMenu();
|
||||
showToast('Link kopiert', 'success');
|
||||
}
|
||||
|
|
@ -171,12 +169,12 @@
|
|||
// Update in all stores
|
||||
images.update((current) =>
|
||||
current.map((img) =>
|
||||
img.id === $contextMenu.image?.id ? { ...img, is_favorite: newFavoriteStatus } : img
|
||||
img.id === $contextMenu.image?.id ? { ...img, isFavorite: newFavoriteStatus } : img
|
||||
)
|
||||
);
|
||||
archivedImages.update((current) =>
|
||||
current.map((img) =>
|
||||
img.id === $contextMenu.image?.id ? { ...img, is_favorite: newFavoriteStatus } : img
|
||||
img.id === $contextMenu.image?.id ? { ...img, isFavorite: newFavoriteStatus } : img
|
||||
)
|
||||
);
|
||||
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@
|
|||
class?: string;
|
||||
id?: string;
|
||||
name?: string;
|
||||
autocomplete?: string;
|
||||
autocomplete?: AutoFill;
|
||||
onchange?: (e: Event) => void;
|
||||
oninput?: (e: Event) => void;
|
||||
}
|
||||
|
|
@ -26,7 +26,7 @@
|
|||
class: className = '',
|
||||
id = '',
|
||||
name = '',
|
||||
autocomplete = '',
|
||||
autocomplete,
|
||||
onchange,
|
||||
oninput,
|
||||
}: Props = $props();
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
<script lang="ts">
|
||||
import { viewMode, cycleViewMode, type ViewMode } from '$lib/stores/view';
|
||||
import { List, SquaresFour, Squares } from '@manacore/shared-icons';
|
||||
import { List, SquaresFour, Square } from '@manacore/shared-icons';
|
||||
|
||||
function getLabel(mode: ViewMode) {
|
||||
switch (mode) {
|
||||
|
|
@ -24,7 +24,7 @@
|
|||
{:else if $viewMode === 'grid3'}
|
||||
<SquaresFour size={20} />
|
||||
{:else}
|
||||
<Squares size={20} />
|
||||
<Square size={20} />
|
||||
{/if}
|
||||
<span class="hidden sm:inline">{getLabel($viewMode)}</span>
|
||||
</button>
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@
|
|||
|
||||
let { children, data } = $props();
|
||||
|
||||
onMount(async () => {
|
||||
onMount(() => {
|
||||
// Initialize theme (applies CSS variables and loads from localStorage)
|
||||
const cleanupTheme = theme.initialize();
|
||||
|
||||
|
|
@ -22,14 +22,14 @@
|
|||
initPostHog();
|
||||
|
||||
// Initialize auth with Mana Core
|
||||
await authStore.initialize();
|
||||
|
||||
// Identify user in PostHog if logged in
|
||||
if (authStore.user) {
|
||||
analytics.identify(authStore.user.id, {
|
||||
email: authStore.user.email,
|
||||
});
|
||||
}
|
||||
authStore.initialize().then(() => {
|
||||
// Identify user in PostHog if logged in
|
||||
if (authStore.user) {
|
||||
analytics.identify(authStore.user.id, {
|
||||
email: authStore.user.email,
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
return () => {
|
||||
cleanupTheme();
|
||||
|
|
|
|||
|
|
@ -6,16 +6,13 @@
|
|||
hasMoreArchive,
|
||||
currentArchivePage,
|
||||
} from '$lib/stores/archive';
|
||||
import { getImages } from '$lib/api/images';
|
||||
import { getImages, type Image } from '$lib/api/images';
|
||||
import ArchivedImageCard from '$lib/components/archive/ArchivedImageCard.svelte';
|
||||
import ArchivedImageModal from '$lib/components/archive/ArchivedImageModal.svelte';
|
||||
import ImageSkeleton from '$lib/components/ui/ImageSkeleton.svelte';
|
||||
import ContextMenu from '$lib/components/ui/ContextMenu.svelte';
|
||||
import { Archive } from '@manacore/shared-icons';
|
||||
import { onMount } from 'svelte';
|
||||
import type { Database } from '@picture/shared/types';
|
||||
|
||||
type Image = Database['public']['Tables']['images']['Row'];
|
||||
|
||||
let loadingMore = $state(false);
|
||||
let observer: IntersectionObserver | null = null;
|
||||
|
|
@ -52,7 +49,7 @@
|
|||
|
||||
isLoadingArchive.set(true);
|
||||
try {
|
||||
const data = await getImages({ userId: authStore.user.id, page: 1, archived: true });
|
||||
const data = await getImages({ page: 1, archived: true });
|
||||
archivedImages.set(data);
|
||||
currentArchivePage.set(1);
|
||||
hasMoreArchive.set(data.length === 20);
|
||||
|
|
@ -70,7 +67,7 @@
|
|||
const nextPage = $currentArchivePage + 1;
|
||||
|
||||
try {
|
||||
const newImages = await getImages({ userId: authStore.user.id, page: nextPage, archived: true });
|
||||
const newImages = await getImages({ page: nextPage, archived: true });
|
||||
if (newImages.length > 0) {
|
||||
archivedImages.update((current) => [...current, ...newImages]);
|
||||
currentArchivePage.set(nextPage);
|
||||
|
|
|
|||
|
|
@ -32,9 +32,9 @@
|
|||
let showDeleteModal = $state(false);
|
||||
let deletingBoard = $state<string | null>(null);
|
||||
|
||||
onMount(async () => {
|
||||
onMount(() => {
|
||||
resetBoardsState();
|
||||
await loadInitialBoards();
|
||||
loadInitialBoards();
|
||||
|
||||
// Setup Intersection Observer for infinite scroll
|
||||
observer = new IntersectionObserver(
|
||||
|
|
@ -63,7 +63,7 @@
|
|||
|
||||
isLoadingBoards.set(true);
|
||||
try {
|
||||
const data = await getBoards({ userId: authStore.user.id, page: 1 });
|
||||
const data = await getBoards({ page: 1 });
|
||||
boards.set(data);
|
||||
currentBoardsPage.set(1);
|
||||
hasBoardsMore.set(data.length === 20);
|
||||
|
|
@ -82,7 +82,7 @@
|
|||
const nextPage = $currentBoardsPage + 1;
|
||||
|
||||
try {
|
||||
const newBoards = await getBoards({ userId: authStore.user.id, page: nextPage });
|
||||
const newBoards = await getBoards({ page: nextPage });
|
||||
if (newBoards.length > 0) {
|
||||
boards.update((current) => [...current, ...newBoards]);
|
||||
currentBoardsPage.set(nextPage);
|
||||
|
|
@ -104,11 +104,10 @@
|
|||
try {
|
||||
const { createBoard } = await import('$lib/api/boards');
|
||||
const newBoard = await createBoard({
|
||||
user_id: authStore.user.id,
|
||||
name: boardName,
|
||||
description: boardDescription || null,
|
||||
description: boardDescription || undefined,
|
||||
});
|
||||
addBoard({ ...newBoard, item_count: 0 });
|
||||
addBoard({ ...newBoard, itemCount: 0 });
|
||||
showCreateBoardModal.set(false);
|
||||
boardName = '';
|
||||
boardDescription = '';
|
||||
|
|
@ -140,8 +139,8 @@
|
|||
if (!authStore.user) return;
|
||||
|
||||
try {
|
||||
const newBoard = await duplicateBoard(boardId, authStore.user.id);
|
||||
addBoard({ ...newBoard, item_count: 0 });
|
||||
const newBoard = await duplicateBoard(boardId);
|
||||
addBoard({ ...newBoard, itemCount: 0 });
|
||||
showToast('Board dupliziert', 'success');
|
||||
} catch (error) {
|
||||
console.error('Error duplicating board:', error);
|
||||
|
|
@ -214,11 +213,11 @@
|
|||
<button
|
||||
onclick={() => openBoard(board.id)}
|
||||
class="block w-full overflow-hidden bg-gray-100 dark:bg-gray-700"
|
||||
style="aspect-ratio: 4/3; background-color: {board.background_color || '#ffffff'}"
|
||||
style="aspect-ratio: 4/3; background-color: {board.backgroundColor || '#ffffff'}"
|
||||
>
|
||||
{#if board.thumbnail_url}
|
||||
{#if board.thumbnailUrl}
|
||||
<img
|
||||
src={board.thumbnail_url}
|
||||
src={board.thumbnailUrl}
|
||||
alt={board.name}
|
||||
class="h-full w-full object-cover transition-transform group-hover:scale-105"
|
||||
/>
|
||||
|
|
@ -245,8 +244,8 @@
|
|||
<div
|
||||
class="mt-3 flex items-center justify-between text-sm text-gray-500 dark:text-gray-400"
|
||||
>
|
||||
<span>{board.item_count} {board.item_count === 1 ? 'Bild' : 'Bilder'}</span>
|
||||
<span>{new Date(board.updated_at).toLocaleDateString('de-DE')}</span>
|
||||
<span>{board.itemCount} {board.itemCount === 1 ? 'Bild' : 'Bilder'}</span>
|
||||
<span>{new Date(board.updatedAt).toLocaleDateString('de-DE')}</span>
|
||||
</div>
|
||||
|
||||
<!-- Actions -->
|
||||
|
|
|
|||
|
|
@ -26,13 +26,13 @@
|
|||
let showImagePicker = $state(false);
|
||||
let isLoading = $state(true);
|
||||
|
||||
onMount(async () => {
|
||||
onMount(() => {
|
||||
if (!boardId) {
|
||||
goto('/app/board');
|
||||
return;
|
||||
}
|
||||
|
||||
await loadBoard();
|
||||
loadBoard();
|
||||
|
||||
return () => {
|
||||
resetCanvasState();
|
||||
|
|
@ -41,7 +41,7 @@
|
|||
});
|
||||
|
||||
async function loadBoard() {
|
||||
if (!authStore.user) return;
|
||||
if (!authStore.user || !boardId) return;
|
||||
|
||||
isLoading = true;
|
||||
try {
|
||||
|
|
@ -49,7 +49,7 @@
|
|||
const board = await getBoardById(boardId);
|
||||
|
||||
// Check if user has access
|
||||
if (board.user_id !== authStore.user.id && !board.is_public) {
|
||||
if (board.userId !== authStore.user.id && !board.isPublic) {
|
||||
showToast('Zugriff verweigert', 'error');
|
||||
goto('/app/board');
|
||||
return;
|
||||
|
|
|
|||
|
|
@ -18,11 +18,9 @@
|
|||
import ContextMenu from '$lib/components/ui/ContextMenu.svelte';
|
||||
import { MagnifyingGlass, X, Heart } from '@manacore/shared-icons';
|
||||
import { onMount } from 'svelte';
|
||||
import type { Database } from '@picture/shared/types';
|
||||
import type { Image } from '$lib/api/images';
|
||||
import type { ViewMode } from '$lib/stores/view';
|
||||
|
||||
type Image = Database['public']['Tables']['images']['Row'];
|
||||
|
||||
let loadingMore = $state(false);
|
||||
let observer: IntersectionObserver | null = null;
|
||||
let loadMoreTrigger = $state<HTMLElement | null>(null);
|
||||
|
|
|
|||
|
|
@ -26,9 +26,10 @@
|
|||
let observer: IntersectionObserver | null = null;
|
||||
let loadMoreTrigger = $state<HTMLElement | null>(null);
|
||||
|
||||
onMount(async () => {
|
||||
await loadTags();
|
||||
loadInitialImages();
|
||||
onMount(() => {
|
||||
loadTags().then(() => {
|
||||
loadInitialImages();
|
||||
});
|
||||
|
||||
// Setup Intersection Observer for infinite scroll
|
||||
observer = new IntersectionObserver(
|
||||
|
|
@ -74,7 +75,6 @@
|
|||
isLoading.set(true);
|
||||
try {
|
||||
const data = await getImages({
|
||||
userId: authStore.user.id,
|
||||
page: 1,
|
||||
tagIds: $selectedTags.length > 0 ? $selectedTags : undefined,
|
||||
favoritesOnly: $showFavoritesOnly,
|
||||
|
|
@ -97,7 +97,6 @@
|
|||
|
||||
try {
|
||||
const newImages = await getImages({
|
||||
userId: authStore.user.id,
|
||||
page: nextPage,
|
||||
tagIds: $selectedTags.length > 0 ? $selectedTags : undefined,
|
||||
favoritesOnly: $showFavoritesOnly,
|
||||
|
|
|
|||
|
|
@ -1,16 +1,13 @@
|
|||
<script lang="ts">
|
||||
import { onMount } from 'svelte';
|
||||
import { tags, isLoadingTags } from '$lib/stores/tags';
|
||||
import { getAllTags, createTag, updateTag, deleteTag } from '$lib/api/tags';
|
||||
import { getAllTags, createTag, updateTag, deleteTag, type Tag } from '$lib/api/tags';
|
||||
import { showToast } from '$lib/stores/toast';
|
||||
import { Plus, Tag as TagIcon, PencilSimple, Trash } from '@manacore/shared-icons';
|
||||
import type { Database } from '@picture/shared/types';
|
||||
|
||||
type TagType = Database['public']['Tables']['tags']['Row'];
|
||||
|
||||
let showCreateModal = $state(false);
|
||||
let showEditModal = $state(false);
|
||||
let editingTag = $state<TagType | null>(null);
|
||||
let editingTag = $state<Tag | null>(null);
|
||||
let newTagName = $state('');
|
||||
let newTagColor = $state('#3B82F6');
|
||||
let editTagName = $state('');
|
||||
|
|
@ -63,7 +60,7 @@
|
|||
}
|
||||
}
|
||||
|
||||
function openEditModal(tag: TagType) {
|
||||
function openEditModal(tag: Tag) {
|
||||
editingTag = tag;
|
||||
editTagName = tag.name;
|
||||
editTagColor = tag.color || '#3B82F6';
|
||||
|
|
@ -158,7 +155,7 @@
|
|||
<div>
|
||||
<h3 class="font-medium text-gray-900 dark:text-gray-100">{tag.name}</h3>
|
||||
<p class="mt-1 text-xs text-gray-500 dark:text-gray-400">
|
||||
{tag.created_at ? new Date(tag.created_at).toLocaleDateString('de-DE') : ''}
|
||||
{tag.createdAt ? new Date(tag.createdAt).toLocaleDateString('de-DE') : ''}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -202,6 +202,7 @@ export const THEME_DEFINITIONS: Record<ThemeVariant, ThemeVariantDefinition> = {
|
|||
name: 'lume',
|
||||
label: 'Lume',
|
||||
emoji: '✨',
|
||||
icon: 'sparkle',
|
||||
hue: 47,
|
||||
light: lumeLight,
|
||||
dark: lumeDark,
|
||||
|
|
@ -210,6 +211,7 @@ export const THEME_DEFINITIONS: Record<ThemeVariant, ThemeVariantDefinition> = {
|
|||
name: 'nature',
|
||||
label: 'Nature',
|
||||
emoji: '🌿',
|
||||
icon: 'leaf',
|
||||
hue: 122,
|
||||
light: natureLight,
|
||||
dark: natureDark,
|
||||
|
|
@ -218,6 +220,7 @@ export const THEME_DEFINITIONS: Record<ThemeVariant, ThemeVariantDefinition> = {
|
|||
name: 'stone',
|
||||
label: 'Stone',
|
||||
emoji: '🪨',
|
||||
icon: 'hexagon',
|
||||
hue: 200,
|
||||
light: stoneLight,
|
||||
dark: stoneDark,
|
||||
|
|
@ -226,6 +229,7 @@ export const THEME_DEFINITIONS: Record<ThemeVariant, ThemeVariantDefinition> = {
|
|||
name: 'ocean',
|
||||
label: 'Ocean',
|
||||
emoji: '🌊',
|
||||
icon: 'waves',
|
||||
hue: 199,
|
||||
light: oceanLight,
|
||||
dark: oceanDark,
|
||||
|
|
|
|||
|
|
@ -77,7 +77,10 @@ export interface ThemeColors {
|
|||
export interface ThemeVariantDefinition {
|
||||
name: string;
|
||||
label: string;
|
||||
/** Emoji representation of the theme */
|
||||
emoji: string;
|
||||
/** Icon name for the theme (e.g., 'sparkle', 'leaf', 'hexagon', 'waves') */
|
||||
icon: string;
|
||||
/** The primary hue for this variant (used for accent colors) */
|
||||
hue: number;
|
||||
light: ThemeColors;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue