fix(picture): resolve critical issues — dead stores, type-check, test coverage

Store cleanup:
- Remove 12 dead duplicate .svelte.ts store files (1,306 lines of unused code)
- All components use the .ts writable stores, runes versions were never imported

Type-check re-enabled:
- Fix Modal prop mismatch: open→visible, size→maxWidth in ImagePickerModal and board page
- Fix __BUILD_TIME__/__BUILD_HASH__ declarations in app.d.ts (declare global block)
- Fix vite.config.ts plugin type incompatibility with `as any` casts
- Re-enable type-check script (was skipped since shared-ui component fixes)

Test coverage:
- Add generate.service.spec.ts with 35 tests covering:
  checkGenerationAccess, generateImage, checkStatus, cancelGeneration, handleWebhook
- Tests cover credit logic, sync/async modes, ownership checks, error handling

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Till JS 2026-03-21 15:27:58 +01:00
parent c0c11c325a
commit c26a48e16a
17 changed files with 1073 additions and 1315 deletions

File diff suppressed because it is too large Load diff

View file

@ -1,9 +1,9 @@
declare const __BUILD_HASH__: string;
declare const __BUILD_TIME__: string;
// See https://svelte.dev/docs/kit/types#app.d.ts
// for information about these interfaces
declare global {
declare const __BUILD_HASH__: string;
declare const __BUILD_TIME__: string;
namespace App {
// interface Error {}
// interface Locals {}

View file

@ -132,7 +132,7 @@
);
</script>
<Modal {open} {onClose} size="large">
<Modal visible={open} {onClose} maxWidth="3xl">
<div class="flex h-[80vh] flex-col p-6">
<!-- Header -->
<div class="mb-6">

View file

@ -1,70 +0,0 @@
/**
* Archive Store - Svelte 5 Runes Version
*/
import type { Image } from '$lib/api/images';
// State using Svelte 5 runes
let archivedImages = $state<Image[]>([]);
let isLoadingArchive = $state(false);
let hasMoreArchive = $state(true);
let currentArchivePage = $state(1);
export const archiveStore = {
get images() {
return archivedImages;
},
get isLoading() {
return isLoadingArchive;
},
get hasMore() {
return hasMoreArchive;
},
get currentPage() {
return currentArchivePage;
},
setImages(images: Image[]) {
archivedImages = images;
},
appendImages(images: Image[]) {
archivedImages = [...archivedImages, ...images];
},
addImage(image: Image) {
archivedImages = [image, ...archivedImages];
},
removeImage(id: string) {
archivedImages = archivedImages.filter((img) => img.id !== id);
},
setLoading(loading: boolean) {
isLoadingArchive = loading;
},
setHasMore(more: boolean) {
hasMoreArchive = more;
},
setCurrentPage(page: number) {
currentArchivePage = page;
},
incrementPage() {
currentArchivePage++;
},
reset() {
archivedImages = [];
isLoadingArchive = false;
hasMoreArchive = true;
currentArchivePage = 1;
},
};
// Export individual getters for backwards compatibility
export function getArchivedImages() {
return archivedImages;
}

View file

@ -1,147 +0,0 @@
/**
* Boards Store - Svelte 5 Runes Version
*/
import type { Board, BoardWithCount } from '$lib/api/boards';
// State using Svelte 5 runes
let boards = $state<BoardWithCount[]>([]);
let currentBoard = $state<Board | null>(null);
let isLoadingBoards = $state(false);
let isLoadingBoard = $state(false);
let currentBoardsPage = $state(1);
let hasBoardsMore = $state(true);
let selectedBoard = $state<Board | null>(null);
let showCreateBoardModal = $state(false);
let showShareBoardModal = $state(false);
let shareBoardId = $state<string | null>(null);
// Derived state
const boardSettings = $derived({
width: currentBoard?.canvasWidth || 2000,
height: currentBoard?.canvasHeight || 1500,
backgroundColor: currentBoard?.backgroundColor || '#ffffff',
});
export const boardsStore = {
get boards() {
return boards;
},
get currentBoard() {
return currentBoard;
},
get isLoadingBoards() {
return isLoadingBoards;
},
get isLoadingBoard() {
return isLoadingBoard;
},
get currentBoardsPage() {
return currentBoardsPage;
},
get hasBoardsMore() {
return hasBoardsMore;
},
get selectedBoard() {
return selectedBoard;
},
get showCreateBoardModal() {
return showCreateBoardModal;
},
get showShareBoardModal() {
return showShareBoardModal;
},
get shareBoardId() {
return shareBoardId;
},
get boardSettings() {
return boardSettings;
},
setBoards(newBoards: BoardWithCount[]) {
boards = newBoards;
},
appendBoards(newBoards: BoardWithCount[]) {
boards = [...boards, ...newBoards];
},
addBoard(board: BoardWithCount) {
boards = [board, ...boards];
},
updateBoardInList(boardId: string, updates: Partial<Board>) {
boards = boards.map((board) => (board.id === boardId ? { ...board, ...updates } : board));
},
removeBoardFromList(boardId: string) {
boards = boards.filter((board) => board.id !== boardId);
},
incrementBoardItemCount(boardId: string) {
boards = boards.map((board) =>
board.id === boardId ? { ...board, itemCount: board.itemCount + 1 } : board
);
},
decrementBoardItemCount(boardId: string) {
boards = boards.map((board) =>
board.id === boardId ? { ...board, itemCount: Math.max(0, board.itemCount - 1) } : board
);
},
setCurrentBoard(board: Board | null) {
currentBoard = board;
},
setLoadingBoards(loading: boolean) {
isLoadingBoards = loading;
},
setLoadingBoard(loading: boolean) {
isLoadingBoard = loading;
},
setCurrentBoardsPage(page: number) {
currentBoardsPage = page;
},
setHasBoardsMore(more: boolean) {
hasBoardsMore = more;
},
setSelectedBoard(board: Board | null) {
selectedBoard = board;
},
setShowCreateBoardModal(show: boolean) {
showCreateBoardModal = show;
},
setShowShareBoardModal(show: boolean) {
showShareBoardModal = show;
},
setShareBoardId(id: string | null) {
shareBoardId = id;
},
resetBoardsState() {
boards = [];
currentBoardsPage = 1;
hasBoardsMore = true;
},
};
// Export individual getters for backwards compatibility
export function getBoards() {
return boards;
}
export function getCurrentBoard() {
return currentBoard;
}
export function getBoardSettings() {
return boardSettings;
}

View file

@ -1,349 +0,0 @@
/**
* Canvas Store - Svelte 5 Runes Version
*/
import type { BoardItem, BoardImageItem, BoardTextItem } from '$lib/api/boardItems';
import { isImageItem, isTextItem } from '$lib/api/boardItems';
// Canvas items (images and texts on the board)
let canvasItems = $state<BoardItem[]>([]);
// Selected items on canvas
let selectedItemIds = $state<string[]>([]);
// Canvas view state
let canvasZoom = $state(1);
let canvasPan = $state({ x: 0, y: 0 });
// Canvas interaction mode
export type CanvasMode = 'select' | 'pan' | 'draw';
let canvasMode = $state<CanvasMode>('select');
// Canvas tools
let showGrid = $state(true);
let snapToGrid = $state(false);
let gridSize = $state(20);
// UI state
let showPropertiesPanel = $state(false);
// Text editing state
let editingTextId = $state<string | null>(null);
// Loading state
let isLoadingCanvasItems = $state(false);
// History for undo/redo
interface HistoryState {
items: BoardItem[];
timestamp: number;
}
let canvasHistory = $state<HistoryState[]>([]);
let canvasHistoryIndex = $state(-1);
// Derived states
const selectedItems = $derived(canvasItems.filter((item) => selectedItemIds.includes(item.id)));
const selectedTextItems = $derived(selectedItems.filter(isTextItem));
const selectedImageItems = $derived(selectedItems.filter(isImageItem));
const hasMixedSelection = $derived(selectedTextItems.length > 0 && selectedImageItems.length > 0);
const hasSelection = $derived(selectedItemIds.length > 0);
const isEditingText = $derived(editingTextId !== null);
const canUndo = $derived(canvasHistoryIndex > 0);
const canRedo = $derived(canvasHistoryIndex < canvasHistory.length - 1);
export const canvasStore = {
// Getters
get items() {
return canvasItems;
},
get selectedItemIds() {
return selectedItemIds;
},
get selectedItems() {
return selectedItems;
},
get selectedTextItems() {
return selectedTextItems;
},
get selectedImageItems() {
return selectedImageItems;
},
get hasMixedSelection() {
return hasMixedSelection;
},
get hasSelection() {
return hasSelection;
},
get zoom() {
return canvasZoom;
},
get pan() {
return canvasPan;
},
get mode() {
return canvasMode;
},
get showGrid() {
return showGrid;
},
get snapToGrid() {
return snapToGrid;
},
get gridSize() {
return gridSize;
},
get showPropertiesPanel() {
return showPropertiesPanel;
},
get editingTextId() {
return editingTextId;
},
get isEditingText() {
return isEditingText;
},
get isLoading() {
return isLoadingCanvasItems;
},
get canUndo() {
return canUndo;
},
get canRedo() {
return canRedo;
},
// Setters
setItems(items: BoardItem[]) {
canvasItems = items;
},
setLoading(loading: boolean) {
isLoadingCanvasItems = loading;
},
setMode(mode: CanvasMode) {
canvasMode = mode;
},
setShowGrid(show: boolean) {
showGrid = show;
},
setSnapToGrid(snap: boolean) {
snapToGrid = snap;
},
setGridSize(size: number) {
gridSize = size;
},
setShowPropertiesPanel(show: boolean) {
showPropertiesPanel = show;
},
// Item management
addItem(item: BoardItem) {
canvasItems = [...canvasItems, item];
saveToHistory();
},
updateItem(id: string, updates: Partial<BoardItem>) {
canvasItems = canvasItems.map((item) =>
item.id === id ? ({ ...item, ...updates } as BoardItem) : item
);
saveToHistory();
},
removeItem(id: string) {
canvasItems = canvasItems.filter((item) => item.id !== id);
selectedItemIds = selectedItemIds.filter((itemId) => itemId !== id);
saveToHistory();
},
removeSelectedItems() {
const ids = selectedItemIds;
canvasItems = canvasItems.filter((item) => !ids.includes(item.id));
selectedItemIds = [];
saveToHistory();
},
// Selection management
selectItem(id: string, multi = false) {
if (multi) {
if (selectedItemIds.includes(id)) {
selectedItemIds = selectedItemIds.filter((itemId) => itemId !== id);
} else {
selectedItemIds = [...selectedItemIds, id];
}
} else {
selectedItemIds = [id];
}
},
selectAll() {
selectedItemIds = canvasItems.map((item) => item.id);
},
deselectAll() {
selectedItemIds = [];
},
// Text editing
startEditingText(id: string) {
editingTextId = id;
},
stopEditingText() {
editingTextId = null;
},
// Z-index management
bringToFront(id: string) {
const maxZIndex = Math.max(...canvasItems.map((item) => item.zIndex));
canvasStore.updateItem(id, { zIndex: maxZIndex + 1 });
},
sendToBack(id: string) {
const minZIndex = Math.min(...canvasItems.map((item) => item.zIndex));
canvasStore.updateItem(id, { zIndex: minZIndex - 1 });
},
moveForward(id: string) {
const item = canvasItems.find((i) => i.id === id);
if (!item) return;
const itemsAbove = canvasItems.filter((i) => i.zIndex > item.zIndex);
if (itemsAbove.length === 0) return;
const nextZIndex = Math.min(...itemsAbove.map((i) => i.zIndex));
canvasStore.updateItem(id, { zIndex: nextZIndex + 0.5 });
},
moveBackward(id: string) {
const item = canvasItems.find((i) => i.id === id);
if (!item) return;
const itemsBelow = canvasItems.filter((i) => i.zIndex < item.zIndex);
if (itemsBelow.length === 0) return;
const prevZIndex = Math.max(...itemsBelow.map((i) => i.zIndex));
canvasStore.updateItem(id, { zIndex: prevZIndex - 0.5 });
},
// Zoom functions
zoomIn() {
canvasZoom = Math.min(canvasZoom * 1.2, 5);
},
zoomOut() {
canvasZoom = Math.max(canvasZoom / 1.2, 0.1);
},
setZoom(zoom: number) {
canvasZoom = zoom;
},
setPan(pan: { x: number; y: number }) {
canvasPan = pan;
},
zoomToFit(
containerWidth: number,
containerHeight: number,
boardWidth: number,
boardHeight: number
) {
const scaleX = containerWidth / boardWidth;
const scaleY = containerHeight / boardHeight;
const scale = Math.min(scaleX, scaleY) * 0.9;
canvasZoom = scale;
canvasPan = { x: 0, y: 0 };
},
resetZoom() {
canvasZoom = 1;
canvasPan = { x: 0, y: 0 };
},
// History management
undo() {
if (canvasHistoryIndex <= 0) return;
const prevState = canvasHistory[canvasHistoryIndex - 1];
canvasItems = JSON.parse(JSON.stringify(prevState.items));
canvasHistoryIndex--;
},
redo() {
if (canvasHistoryIndex >= canvasHistory.length - 1) return;
const nextState = canvasHistory[canvasHistoryIndex + 1];
canvasItems = JSON.parse(JSON.stringify(nextState.items));
canvasHistoryIndex++;
},
clearHistory() {
canvasHistory = [];
canvasHistoryIndex = -1;
},
// Reset
reset() {
canvasItems = [];
selectedItemIds = [];
canvasZoom = 1;
canvasPan = { x: 0, y: 0 };
canvasMode = 'select';
editingTextId = null;
canvasStore.clearHistory();
},
// Grid snapping
snapPositionToGrid(x: number, y: number): { x: number; y: number } {
if (!snapToGrid) return { x, y };
return {
x: Math.round(x / gridSize) * gridSize,
y: Math.round(y / gridSize) * gridSize,
};
},
};
// Internal helper
function saveToHistory() {
// Remove any history after current index
const newHistory = canvasHistory.slice(0, canvasHistoryIndex + 1);
// Add current state
newHistory.push({
items: JSON.parse(JSON.stringify(canvasItems)),
timestamp: Date.now(),
});
// Limit history to 50 states
if (newHistory.length > 50) {
newHistory.shift();
}
canvasHistory = newHistory;
canvasHistoryIndex = newHistory.length - 1;
}
// Export for backwards compatibility
export function getCanvasItems() {
return canvasItems;
}
export function getSelectedItemIds() {
return selectedItemIds;
}
export function getCanvasZoom() {
return canvasZoom;
}

View file

@ -1,110 +0,0 @@
/**
* Context Menu Store - Svelte 5 Runes Version
*/
import type { Image } from '$lib/api/images';
interface ContextMenuState {
visible: boolean;
x: number;
y: number;
image: Image | null;
showTagSubmenu: boolean;
submenuX: number;
submenuY: number;
}
const initialState: ContextMenuState = {
visible: false,
x: 0,
y: 0,
image: null,
showTagSubmenu: false,
submenuX: 0,
submenuY: 0,
};
let contextMenuState = $state<ContextMenuState>({ ...initialState });
export const contextMenuStore = {
get state() {
return contextMenuState;
},
get visible() {
return contextMenuState.visible;
},
get x() {
return contextMenuState.x;
},
get y() {
return contextMenuState.y;
},
get image() {
return contextMenuState.image;
},
get showTagSubmenu() {
return contextMenuState.showTagSubmenu;
},
get submenuX() {
return contextMenuState.submenuX;
},
get submenuY() {
return contextMenuState.submenuY;
},
show(x: number, y: number, image: Image) {
contextMenuState = {
visible: true,
x,
y,
image,
showTagSubmenu: false,
submenuX: 0,
submenuY: 0,
};
},
hide() {
contextMenuState = { ...initialState };
},
openTagSubmenu(x: number, y: number) {
contextMenuState = {
...contextMenuState,
showTagSubmenu: true,
submenuX: x,
submenuY: y,
};
},
hideTagSubmenu() {
contextMenuState = {
...contextMenuState,
showTagSubmenu: false,
};
},
};
// Export for backwards compatibility
export function showContextMenu(x: number, y: number, image: Image) {
contextMenuStore.show(x, y, image);
}
export function hideContextMenu() {
contextMenuStore.hide();
}
export function showTagSubmenu(x: number, y: number) {
contextMenuStore.openTagSubmenu(x, y);
}
export function hideTagSubmenu() {
contextMenuStore.hideTagSubmenu();
}
export function getContextMenu() {
return contextMenuState;
}
// Re-export for compatibility
export { contextMenuState as contextMenu };

View file

@ -1,105 +0,0 @@
/**
* Explore Store - Svelte 5 Runes Version
*/
import type { Image } from '$lib/api/images';
// State using Svelte 5 runes
let exploreImages = $state<Image[]>([]);
let isLoadingExplore = $state(false);
let hasMoreExplore = $state(true);
let currentExplorePage = $state(1);
let exploreSortBy = $state<'recent' | 'popular' | 'trending'>('recent');
let exploreSearchQuery = $state('');
let showExploreFavoritesOnly = $state(false);
export const exploreStore = {
get images() {
return exploreImages;
},
get isLoading() {
return isLoadingExplore;
},
get hasMore() {
return hasMoreExplore;
},
get currentPage() {
return currentExplorePage;
},
get sortBy() {
return exploreSortBy;
},
get searchQuery() {
return exploreSearchQuery;
},
get showFavoritesOnly() {
return showExploreFavoritesOnly;
},
setImages(images: Image[]) {
exploreImages = images;
},
appendImages(images: Image[]) {
exploreImages = [...exploreImages, ...images];
},
setLoading(loading: boolean) {
isLoadingExplore = loading;
},
setHasMore(more: boolean) {
hasMoreExplore = more;
},
setCurrentPage(page: number) {
currentExplorePage = page;
},
incrementPage() {
currentExplorePage++;
},
setSortBy(sort: 'recent' | 'popular' | 'trending') {
exploreSortBy = sort;
// Reset when changing sort
exploreImages = [];
currentExplorePage = 1;
hasMoreExplore = true;
},
setSearchQuery(query: string) {
exploreSearchQuery = query;
// Reset when changing search
exploreImages = [];
currentExplorePage = 1;
hasMoreExplore = true;
},
setShowFavoritesOnly(favoritesOnly: boolean) {
showExploreFavoritesOnly = favoritesOnly;
// Reset when changing filter
exploreImages = [];
currentExplorePage = 1;
hasMoreExplore = true;
},
reset() {
exploreImages = [];
isLoadingExplore = false;
hasMoreExplore = true;
currentExplorePage = 1;
exploreSortBy = 'recent';
exploreSearchQuery = '';
showExploreFavoritesOnly = false;
},
};
// Export individual getters for backwards compatibility
export function getExploreImages() {
return exploreImages;
}
export function getExploreSortBy() {
return exploreSortBy;
}

View file

@ -1,73 +0,0 @@
/**
* Generate Store - Svelte 5 Runes Version
*/
// State using Svelte 5 runes
let isGenerating = $state(false);
let generationProgress = $state('');
let generationError = $state('');
let currentGenerationId = $state<string | null>(null);
export const generateStore = {
get isGenerating() {
return isGenerating;
},
get generationProgress() {
return generationProgress;
},
get generationError() {
return generationError;
},
get currentGenerationId() {
return currentGenerationId;
},
startGeneration(generationId?: string) {
isGenerating = true;
generationProgress = 'Starting...';
generationError = '';
currentGenerationId = generationId || null;
},
updateProgress(progress: string) {
generationProgress = progress;
},
setError(error: string) {
generationError = error;
isGenerating = false;
},
completeGeneration() {
isGenerating = false;
generationProgress = 'Complete!';
currentGenerationId = null;
},
cancelGeneration() {
isGenerating = false;
generationProgress = '';
generationError = '';
currentGenerationId = null;
},
reset() {
isGenerating = false;
generationProgress = '';
generationError = '';
currentGenerationId = null;
},
};
// Export individual getters for backwards compatibility
export function getIsGenerating() {
return isGenerating;
}
export function getGenerationProgress() {
return generationProgress;
}
export function getGenerationError() {
return generationError;
}

View file

@ -1,102 +0,0 @@
/**
* Images Store - Svelte 5 Runes Version
*/
import type { Image } from '$lib/api/images';
// State using Svelte 5 runes
let images = $state<Image[]>([]);
let selectedImage = $state<Image | null>(null);
let isLoading = $state(false);
let hasMore = $state(true);
let currentPage = $state(1);
let showFavoritesOnly = $state(false);
export const imagesStore = {
get images() {
return images;
},
get selectedImage() {
return selectedImage;
},
get isLoading() {
return isLoading;
},
get hasMore() {
return hasMore;
},
get currentPage() {
return currentPage;
},
get showFavoritesOnly() {
return showFavoritesOnly;
},
setImages(newImages: Image[]) {
images = newImages;
},
appendImages(newImages: Image[]) {
images = [...images, ...newImages];
},
addImage(image: Image) {
images = [image, ...images];
},
updateImage(id: string, updates: Partial<Image>) {
images = images.map((img) => (img.id === id ? { ...img, ...updates } : img));
},
removeImage(id: string) {
images = images.filter((img) => img.id !== id);
if (selectedImage?.id === id) {
selectedImage = null;
}
},
selectImage(image: Image | null) {
selectedImage = image;
},
setLoading(loading: boolean) {
isLoading = loading;
},
setHasMore(more: boolean) {
hasMore = more;
},
setCurrentPage(page: number) {
currentPage = page;
},
incrementPage() {
currentPage++;
},
setShowFavoritesOnly(favoritesOnly: boolean) {
showFavoritesOnly = favoritesOnly;
},
reset() {
images = [];
selectedImage = null;
isLoading = false;
hasMore = true;
currentPage = 1;
},
};
// Export individual getters for backwards compatibility
export function getImages() {
return images;
}
export function getSelectedImage() {
return selectedImage;
}
export function getIsLoading() {
return isLoading;
}

View file

@ -1,61 +0,0 @@
/**
* Models Store - Svelte 5 Runes Version
*/
import type { Model } from '$lib/api/models';
// State using Svelte 5 runes
let models = $state<Model[]>([]);
let selectedModel = $state<Model | null>(null);
let isLoadingModels = $state(false);
export const modelsStore = {
get models() {
return models;
},
get selectedModel() {
return selectedModel;
},
get isLoadingModels() {
return isLoadingModels;
},
setModels(newModels: Model[]) {
models = newModels;
// Auto-select default model if no model selected
if (!selectedModel && newModels.length > 0) {
const defaultModel = newModels.find((m) => m.isDefault) || newModels[0];
selectedModel = defaultModel;
}
},
selectModel(model: Model | null) {
selectedModel = model;
},
selectModelById(id: string) {
const model = models.find((m) => m.id === id);
if (model) {
selectedModel = model;
}
},
setLoading(loading: boolean) {
isLoadingModels = loading;
},
reset() {
models = [];
selectedModel = null;
isLoadingModels = false;
},
};
// Export individual getters for backwards compatibility
export function getModels() {
return models;
}
export function getSelectedModel() {
return selectedModel;
}

View file

@ -1,65 +0,0 @@
/**
* Sidebar Store - Svelte 5 Runes Version
*/
import { browser } from '$app/environment';
const SIDEBAR_KEY = 'picture_sidebar_collapsed';
function loadInitialState(): boolean {
if (!browser) return false;
const saved = localStorage.getItem(SIDEBAR_KEY);
return saved === 'true';
}
let isSidebarCollapsed = $state(loadInitialState());
export const sidebarStore = {
get isCollapsed() {
return isSidebarCollapsed;
},
toggle() {
isSidebarCollapsed = !isSidebarCollapsed;
if (browser) {
localStorage.setItem(SIDEBAR_KEY, String(isSidebarCollapsed));
}
},
setCollapsed(collapsed: boolean) {
isSidebarCollapsed = collapsed;
if (browser) {
localStorage.setItem(SIDEBAR_KEY, String(collapsed));
}
},
expand() {
isSidebarCollapsed = false;
if (browser) {
localStorage.setItem(SIDEBAR_KEY, 'false');
}
},
collapse() {
isSidebarCollapsed = true;
if (browser) {
localStorage.setItem(SIDEBAR_KEY, 'true');
}
},
};
// Export for backwards compatibility
export function getIsSidebarCollapsed() {
return isSidebarCollapsed;
}
export function toggleSidebar() {
sidebarStore.toggle();
}
export function setSidebarCollapsed(collapsed: boolean) {
sidebarStore.setCollapsed(collapsed);
}
// Re-export the writable-like interface for backward compatibility
export { isSidebarCollapsed };

View file

@ -1,84 +0,0 @@
/**
* Tags Store - Svelte 5 Runes Version
*/
import type { Tag } from '$lib/api/tags';
// State using Svelte 5 runes
let tags = $state<Tag[]>([]);
let selectedTags = $state<string[]>([]);
let isLoadingTags = $state(false);
export const tagsStore = {
get tags() {
return tags;
},
get selectedTags() {
return selectedTags;
},
get isLoadingTags() {
return isLoadingTags;
},
setTags(newTags: Tag[]) {
tags = newTags;
},
addTag(tag: Tag) {
tags = [...tags, tag];
},
updateTag(id: string, updates: Partial<Tag>) {
tags = tags.map((tag) => (tag.id === id ? { ...tag, ...updates } : tag));
},
removeTag(id: string) {
tags = tags.filter((tag) => tag.id !== id);
selectedTags = selectedTags.filter((tagId) => tagId !== id);
},
selectTag(tagId: string) {
if (!selectedTags.includes(tagId)) {
selectedTags = [...selectedTags, tagId];
}
},
deselectTag(tagId: string) {
selectedTags = selectedTags.filter((id) => id !== tagId);
},
toggleTag(tagId: string) {
if (selectedTags.includes(tagId)) {
selectedTags = selectedTags.filter((id) => id !== tagId);
} else {
selectedTags = [...selectedTags, tagId];
}
},
setSelectedTags(tagIds: string[]) {
selectedTags = tagIds;
},
clearSelectedTags() {
selectedTags = [];
},
setLoading(loading: boolean) {
isLoadingTags = loading;
},
reset() {
tags = [];
selectedTags = [];
isLoadingTags = false;
},
};
// Export individual getters for backwards compatibility
export function getTags() {
return tags;
}
export function getSelectedTags() {
return selectedTags;
}

View file

@ -1,73 +0,0 @@
/**
* UI Store - Svelte 5 Runes Version
*/
import { browser } from '$app/environment';
const UI_VISIBLE_KEY = 'picture_ui_visible';
function loadInitialState(): boolean {
if (!browser) return true;
const saved = localStorage.getItem(UI_VISIBLE_KEY);
return saved !== 'false'; // Default to true
}
let isUIVisible = $state(loadInitialState());
let showKeyboardShortcuts = $state(false);
export const uiStore = {
get isVisible() {
return isUIVisible;
},
get showKeyboardShortcuts() {
return showKeyboardShortcuts;
},
toggle() {
isUIVisible = !isUIVisible;
if (browser) {
localStorage.setItem(UI_VISIBLE_KEY, String(isUIVisible));
}
},
setVisible(visible: boolean) {
isUIVisible = visible;
if (browser) {
localStorage.setItem(UI_VISIBLE_KEY, String(visible));
}
},
show() {
isUIVisible = true;
if (browser) {
localStorage.setItem(UI_VISIBLE_KEY, 'true');
}
},
hide() {
isUIVisible = false;
if (browser) {
localStorage.setItem(UI_VISIBLE_KEY, 'false');
}
},
setShowKeyboardShortcuts(show: boolean) {
showKeyboardShortcuts = show;
},
toggleKeyboardShortcuts() {
showKeyboardShortcuts = !showKeyboardShortcuts;
},
};
// Export for backwards compatibility
export function toggleUI() {
uiStore.toggle();
}
export function getIsUIVisible() {
return isUIVisible;
}
// Re-export for compatibility
export { isUIVisible, showKeyboardShortcuts };

View file

@ -1,67 +0,0 @@
/**
* View Store - Svelte 5 Runes Version
*/
import { browser } from '$app/environment';
export type ViewMode = 'single' | 'grid3' | 'grid5';
const VIEW_MODE_KEY = 'picture_view_mode';
function loadInitialViewMode(): ViewMode {
if (!browser) {
return 'grid3';
}
const saved = localStorage.getItem(VIEW_MODE_KEY) as ViewMode | null;
return saved || 'grid3';
}
let viewMode = $state<ViewMode>(loadInitialViewMode());
export const viewStore = {
get mode() {
return viewMode;
},
set(mode: ViewMode) {
viewMode = mode;
if (browser) {
localStorage.setItem(VIEW_MODE_KEY, mode);
}
},
cycle() {
const modes: ViewMode[] = ['single', 'grid3', 'grid5'];
const currentIndex = modes.indexOf(viewMode);
const nextMode = modes[(currentIndex + 1) % modes.length];
viewStore.set(nextMode);
},
setSingle() {
viewStore.set('single');
},
setGrid3() {
viewStore.set('grid3');
},
setGrid5() {
viewStore.set('grid5');
},
};
// Export for backwards compatibility
export function setViewMode(mode: ViewMode) {
viewStore.set(mode);
}
export function cycleViewMode() {
viewStore.cycle();
}
export function getViewMode() {
return viewMode;
}
// Re-export for compatibility
export { viewMode };

View file

@ -280,7 +280,7 @@
</div>
<!-- Create Board Modal -->
<Modal open={$showCreateBoardModal} onClose={() => showCreateBoardModal.set(false)}>
<Modal visible={$showCreateBoardModal} onClose={() => showCreateBoardModal.set(false)}>
<div class="p-6">
<h2 class="text-2xl font-bold text-gray-900 dark:text-gray-100">Neues Board erstellen</h2>
@ -339,7 +339,7 @@
</Modal>
<!-- Delete Confirmation Modal -->
<Modal open={showDeleteModal} onClose={() => (showDeleteModal = false)}>
<Modal visible={showDeleteModal} onClose={() => (showDeleteModal = false)}>
<div class="p-6">
<h2 class="text-2xl font-bold text-gray-900 dark:text-gray-100">Board löschen?</h2>
<p class="mt-4 text-gray-600 dark:text-gray-400">

View file

@ -7,8 +7,8 @@ import { MANACORE_SHARED_PACKAGES, getBuildDefines } from '@manacore/shared-vite
export default defineConfig({
plugins: [
tailwindcss(),
sveltekit(),
tailwindcss() as any,
sveltekit() as any,
SvelteKitPWA(
createPWAConfig({
name: 'Picture - KI Bildgenerator',
@ -16,7 +16,7 @@ export default defineConfig({
description: 'KI-gestützte Bildgenerierung',
themeColor: '#ec4899',
})
),
) as any,
],
server: {
port: 5175,