mirror of
https://github.com/Memo-2023/mana-monorepo.git
synced 2026-05-15 01:01:09 +02:00
refactor(manacore/web): unify all module tags into shared globalTags
Migrate 7 modules (picture, photos, storage, memoro, times, uload, todo) from module-specific tag tables to the shared globalTags system. Each module now reads tags from the global pool and keeps only its junction table for entity-tag linking via createTagLinkOps. Removed: pictureTags, photoTags, storageTags, memoroTags, timeTags, uloadTags, labels tables from IndexedDB schema and SYNC_APP_MAP. Kept: all junction tables (imageTags, photoMediaTags, fileTags, etc.) pointing to globalTags IDs. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
48aac82bcb
commit
8c98dd871d
35 changed files with 149 additions and 623 deletions
|
|
@ -26,8 +26,7 @@ db.version(1).stores({
|
|||
tasks:
|
||||
'id, dueDate, isCompleted, priority, order, projectId, [isCompleted+order], [projectId+order]',
|
||||
todoProjects: 'id, order, isArchived, isDefault',
|
||||
labels: 'id',
|
||||
taskLabels: 'id, taskId, labelId',
|
||||
taskLabels: 'id, taskId, labelId', // junction to globalTags (labelId = tagId)
|
||||
reminders: 'id, taskId',
|
||||
boardViews: 'id, order, groupBy',
|
||||
|
||||
|
|
@ -47,8 +46,7 @@ db.version(1).stores({
|
|||
images: 'id, isFavorite, isPublic, archivedAt, prompt',
|
||||
boards: 'id, isPublic',
|
||||
boardItems: 'id, boardId, itemType, zIndex, [boardId+zIndex]',
|
||||
pictureTags: 'id, name',
|
||||
imageTags: 'id, imageId, tagId, [imageId+tagId]',
|
||||
imageTags: 'id, imageId, tagId, [imageId+tagId]', // junction to globalTags
|
||||
|
||||
// ─── Cards (appId: 'cards') ───
|
||||
cardDecks: 'id, isPublic',
|
||||
|
|
@ -68,8 +66,7 @@ db.version(1).stores({
|
|||
// ─── Storage (appId: 'storage') ───
|
||||
files: 'id, parentFolderId, mimeType, isFavorite, isDeleted, name',
|
||||
storageFolders: 'id, parentFolderId, path, depth, isFavorite, isDeleted',
|
||||
storageTags: 'id, name',
|
||||
fileTags: 'id, fileId, tagId, [fileId+tagId]',
|
||||
fileTags: 'id, fileId, tagId, [fileId+tagId]', // junction to globalTags
|
||||
|
||||
// ─── Presi (appId: 'presi') ───
|
||||
presiDecks: 'id, isPublic',
|
||||
|
|
@ -85,8 +82,7 @@ db.version(1).stores({
|
|||
albums: 'id, isAutoGenerated, name',
|
||||
albumItems: 'id, albumId, mediaId, sortOrder, [albumId+sortOrder]',
|
||||
photoFavorites: 'id, mediaId',
|
||||
photoTags: 'id, name',
|
||||
photoMediaTags: 'id, mediaId, tagId, [mediaId+tagId]',
|
||||
photoMediaTags: 'id, mediaId, tagId, [mediaId+tagId]', // junction to globalTags
|
||||
|
||||
// ─── SkillTree (appId: 'skilltree') ───
|
||||
skills: 'id, branch, parentId, level',
|
||||
|
|
@ -103,7 +99,6 @@ db.version(1).stores({
|
|||
timeProjects: 'id, clientId, isArchived, isBillable, guildId, visibility, order',
|
||||
timeEntries:
|
||||
'id, projectId, clientId, date, isRunning, [date+projectId], [date+clientId], guildId, visibility',
|
||||
timeTags: 'id, name, order',
|
||||
timeTemplates: 'id, usageCount, lastUsedAt, projectId',
|
||||
timeSettings: 'id',
|
||||
timeAlarms: 'id, enabled, time',
|
||||
|
|
@ -132,7 +127,6 @@ db.version(1).stores({
|
|||
|
||||
// ─── uLoad (appId: 'uload') ───
|
||||
links: 'id, shortCode, isActive, folderId, order, clickCount, [folderId+order], [isActive+order]',
|
||||
uloadTags: 'id, slug, name',
|
||||
uloadFolders: 'id, order',
|
||||
linkTags: 'id, linkId, tagId, [linkId+tagId]',
|
||||
|
||||
|
|
@ -147,8 +141,7 @@ db.version(1).stores({
|
|||
// ─── Memoro (appId: 'memoro') ───
|
||||
memos: 'id, processingStatus, isArchived, isPinned, language, [isArchived+createdAt]',
|
||||
memories: 'id, memoId',
|
||||
memoroTags: 'id, name, sortOrder',
|
||||
memoTags: 'id, memoId, tagId',
|
||||
memoTags: 'id, memoId, tagId', // junction to globalTags
|
||||
memoroSpaces: 'id, ownerId',
|
||||
spaceMembers: 'id, spaceId, userId',
|
||||
memoSpaces: 'id, memoId, spaceId',
|
||||
|
|
@ -177,25 +170,24 @@ db.version(1).stores({
|
|||
|
||||
export const SYNC_APP_MAP: Record<string, string[]> = {
|
||||
manacore: ['userSettings', 'dashboardConfigs'],
|
||||
todo: ['tasks', 'todoProjects', 'labels', 'taskLabels', 'reminders', 'boardViews'],
|
||||
todo: ['tasks', 'todoProjects', 'taskLabels', 'reminders', 'boardViews'],
|
||||
calendar: ['calendars', 'events'],
|
||||
contacts: ['contacts'],
|
||||
chat: ['conversations', 'messages', 'chatTemplates'],
|
||||
picture: ['images', 'boards', 'boardItems', 'pictureTags', 'imageTags'],
|
||||
picture: ['images', 'boards', 'boardItems', 'imageTags'],
|
||||
cards: ['cardDecks', 'cards'],
|
||||
zitare: ['zitareFavorites', 'zitareLists'],
|
||||
mukke: ['songs', 'mukkePlaylists', 'playlistSongs', 'mukkeProjects', 'markers'],
|
||||
storage: ['files', 'storageFolders', 'storageTags', 'fileTags'],
|
||||
storage: ['files', 'storageFolders', 'fileTags'],
|
||||
presi: ['presiDecks', 'slides'],
|
||||
inventar: ['invCollections', 'invItems', 'invLocations', 'invCategories'],
|
||||
photos: ['albums', 'albumItems', 'photoFavorites', 'photoTags', 'photoMediaTags'],
|
||||
photos: ['albums', 'albumItems', 'photoFavorites', 'photoMediaTags'],
|
||||
skilltree: ['skills', 'activities', 'achievements'],
|
||||
citycorners: ['cities', 'ccLocations', 'ccFavorites'],
|
||||
times: [
|
||||
'timeClients',
|
||||
'timeProjects',
|
||||
'timeEntries',
|
||||
'timeTags',
|
||||
'timeTemplates',
|
||||
'timeSettings',
|
||||
'timeAlarms',
|
||||
|
|
@ -206,18 +198,10 @@ export const SYNC_APP_MAP: Record<string, string[]> = {
|
|||
questions: ['qCollections', 'questions', 'answers'],
|
||||
nutriphi: ['meals', 'goals', 'nutriFavorites'],
|
||||
planta: ['plants', 'plantPhotos', 'wateringSchedules', 'wateringLogs'],
|
||||
uload: ['links', 'uloadTags', 'uloadFolders', 'linkTags'],
|
||||
uload: ['links', 'uloadFolders', 'linkTags'],
|
||||
calc: ['calculations', 'savedFormulas'],
|
||||
moodlit: ['moods', 'sequences'],
|
||||
memoro: [
|
||||
'memos',
|
||||
'memories',
|
||||
'memoroTags',
|
||||
'memoTags',
|
||||
'memoroSpaces',
|
||||
'spaceMembers',
|
||||
'memoSpaces',
|
||||
],
|
||||
memoro: ['memos', 'memories', 'memoTags', 'memoroSpaces', 'spaceMembers', 'memoSpaces'],
|
||||
guides: ['guides', 'sections', 'steps', 'guideCollections', 'runs'],
|
||||
tags: ['globalTags', 'tagGroups'],
|
||||
links: ['manaLinks'],
|
||||
|
|
@ -241,7 +225,6 @@ export const TABLE_TO_SYNC_NAME: Record<string, string> = {
|
|||
// chat
|
||||
chatTemplates: 'templates',
|
||||
// picture
|
||||
pictureTags: 'tags',
|
||||
// cards
|
||||
cardDecks: 'decks',
|
||||
// zitare
|
||||
|
|
@ -252,7 +235,6 @@ export const TABLE_TO_SYNC_NAME: Record<string, string> = {
|
|||
mukkeProjects: 'projects',
|
||||
// storage
|
||||
storageFolders: 'folders',
|
||||
storageTags: 'tags',
|
||||
// presi
|
||||
presiDecks: 'decks',
|
||||
// inventar
|
||||
|
|
@ -262,7 +244,6 @@ export const TABLE_TO_SYNC_NAME: Record<string, string> = {
|
|||
invCategories: 'categories',
|
||||
// photos
|
||||
photoFavorites: 'favorites',
|
||||
photoTags: 'tags',
|
||||
photoMediaTags: 'photoTags',
|
||||
// citycorners
|
||||
ccLocations: 'locations',
|
||||
|
|
@ -270,7 +251,6 @@ export const TABLE_TO_SYNC_NAME: Record<string, string> = {
|
|||
// times
|
||||
timeClients: 'clients',
|
||||
timeProjects: 'projects',
|
||||
timeTags: 'tags',
|
||||
timeTemplates: 'templates',
|
||||
timeSettings: 'settings',
|
||||
timeAlarms: 'alarms',
|
||||
|
|
@ -283,10 +263,8 @@ export const TABLE_TO_SYNC_NAME: Record<string, string> = {
|
|||
// nutriphi
|
||||
nutriFavorites: 'favorites',
|
||||
// memoro
|
||||
memoroTags: 'tags',
|
||||
memoroSpaces: 'spaces',
|
||||
// uload
|
||||
uloadTags: 'tags',
|
||||
uloadFolders: 'folders',
|
||||
// guides
|
||||
guideCollections: 'collections',
|
||||
|
|
|
|||
|
|
@ -75,7 +75,7 @@ export interface Category {
|
|||
export type ViewMode = 'list' | 'grid' | 'table';
|
||||
|
||||
export interface SortOption {
|
||||
field: 'name' | 'createdAt' | 'updatedAt' | 'status' | 'quantity';
|
||||
field: string;
|
||||
direction: 'asc' | 'desc';
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -8,7 +8,6 @@ import { db } from '$lib/data/database';
|
|||
import type {
|
||||
LocalMemo,
|
||||
LocalMemory,
|
||||
LocalTag,
|
||||
LocalMemoTag,
|
||||
LocalSpace,
|
||||
LocalSpaceMember,
|
||||
|
|
@ -19,7 +18,6 @@ import type {
|
|||
|
||||
export const memoTable = db.table<LocalMemo>('memos');
|
||||
export const memoryTable = db.table<LocalMemory>('memories');
|
||||
export const memoroTagTable = db.table<LocalTag>('memoroTags');
|
||||
export const memoTagTable = db.table<LocalMemoTag>('memoTags');
|
||||
export const memoroSpaceTable = db.table<LocalSpace>('memoroSpaces');
|
||||
export const spaceMemberTable = db.table<LocalSpaceMember>('spaceMembers');
|
||||
|
|
@ -55,29 +53,7 @@ export const MEMORO_GUEST_SEED = {
|
|||
'Memoro bietet Sprachaufnahme, automatische Transkription, KI-gestützte Zusammenfassungen und Tagging.',
|
||||
},
|
||||
],
|
||||
memoroTags: [
|
||||
{
|
||||
id: 'tag-ideen',
|
||||
name: 'Ideen',
|
||||
color: '#3b82f6',
|
||||
isPinned: true,
|
||||
sortOrder: 0,
|
||||
},
|
||||
{
|
||||
id: 'tag-notizen',
|
||||
name: 'Notizen',
|
||||
color: '#10b981',
|
||||
isPinned: false,
|
||||
sortOrder: 1,
|
||||
},
|
||||
],
|
||||
memoTags: [
|
||||
{
|
||||
id: 'mt-demo-1',
|
||||
memoId: DEMO_MEMO_ID,
|
||||
tagId: 'tag-notizen',
|
||||
},
|
||||
],
|
||||
memoTags: [] as Record<string, unknown>[],
|
||||
memoroSpaces: [] as Record<string, unknown>[],
|
||||
spaceMembers: [] as Record<string, unknown>[],
|
||||
memoSpaces: [] as Record<string, unknown>[],
|
||||
|
|
|
|||
|
|
@ -3,18 +3,23 @@
|
|||
*/
|
||||
|
||||
export { memosStore } from './stores/memos.svelte';
|
||||
export { tagsStore } from './stores/tags.svelte';
|
||||
export {
|
||||
tagMutations,
|
||||
useAllTags,
|
||||
getTagById,
|
||||
getTagsByIds,
|
||||
getTagColor,
|
||||
memoTagOps,
|
||||
} from './stores/tags.svelte';
|
||||
export { memoriesStore } from './stores/memories.svelte';
|
||||
export {
|
||||
useAllMemos,
|
||||
useArchivedMemos,
|
||||
useMemoriesByMemo,
|
||||
useAllTags,
|
||||
useAllMemoTags,
|
||||
useAllSpaces,
|
||||
toMemo,
|
||||
toMemory,
|
||||
toTag,
|
||||
toSpace,
|
||||
sortMemos,
|
||||
filterBySearch,
|
||||
|
|
@ -26,7 +31,6 @@ export {
|
|||
export {
|
||||
memoTable,
|
||||
memoryTable,
|
||||
memoroTagTable,
|
||||
memoTagTable,
|
||||
memoroSpaceTable,
|
||||
spaceMemberTable,
|
||||
|
|
@ -36,7 +40,6 @@ export {
|
|||
export type {
|
||||
LocalMemo,
|
||||
LocalMemory,
|
||||
LocalTag,
|
||||
LocalMemoTag,
|
||||
LocalSpace,
|
||||
LocalSpaceMember,
|
||||
|
|
|
|||
|
|
@ -7,12 +7,10 @@ import { db } from '$lib/data/database';
|
|||
import type {
|
||||
LocalMemo,
|
||||
LocalMemory,
|
||||
LocalTag,
|
||||
LocalMemoTag,
|
||||
LocalSpace,
|
||||
Memo,
|
||||
Memory,
|
||||
Tag,
|
||||
Space,
|
||||
} from './types';
|
||||
|
||||
|
|
@ -46,18 +44,6 @@ export function toMemory(local: LocalMemory): Memory {
|
|||
};
|
||||
}
|
||||
|
||||
export function toTag(local: LocalTag): Tag {
|
||||
return {
|
||||
id: local.id,
|
||||
name: local.name,
|
||||
color: local.color,
|
||||
isPinned: local.isPinned ?? false,
|
||||
sortOrder: local.sortOrder ?? 0,
|
||||
createdAt: local.createdAt ?? new Date().toISOString(),
|
||||
updatedAt: local.updatedAt ?? new Date().toISOString(),
|
||||
};
|
||||
}
|
||||
|
||||
export function toSpace(local: LocalSpace): Space {
|
||||
return {
|
||||
id: local.id,
|
||||
|
|
@ -98,16 +84,8 @@ export function useMemoriesByMemo(memoId: string) {
|
|||
});
|
||||
}
|
||||
|
||||
/** All tags, sorted by sortOrder. */
|
||||
export function useAllTags() {
|
||||
return liveQuery(async () => {
|
||||
const locals = await db.table<LocalTag>('memoroTags').toArray();
|
||||
return locals
|
||||
.filter((t) => !t.deletedAt)
|
||||
.map(toTag)
|
||||
.sort((a, b) => a.sortOrder - b.sortOrder);
|
||||
});
|
||||
}
|
||||
// Tags: use shared global tags from @manacore/shared-stores
|
||||
export { useAllTags } from '@manacore/shared-stores';
|
||||
|
||||
/** All memo-tag associations. */
|
||||
export function useAllMemoTags() {
|
||||
|
|
|
|||
|
|
@ -1,71 +1,19 @@
|
|||
/**
|
||||
* Tags Store — Mutations Only
|
||||
*
|
||||
* Reads come from liveQuery hooks in queries.ts.
|
||||
* Handles tag CRUD and memo-tag associations.
|
||||
* Memoro Tags — Uses shared global tags + module-specific junction table.
|
||||
*/
|
||||
|
||||
import { memoroTagTable, memoTagTable } from '../collections';
|
||||
import { toTag } from '../queries';
|
||||
import type { LocalTag, LocalMemoTag } from '../types';
|
||||
import { db } from '$lib/data/database';
|
||||
import { createTagLinkOps } from '@manacore/shared-stores';
|
||||
|
||||
export const tagsStore = {
|
||||
/** Create a new tag. */
|
||||
async create(data: { name: string; color?: string }) {
|
||||
const all = await memoroTagTable.toArray();
|
||||
const active = all.filter((t) => !t.deletedAt);
|
||||
const newLocal: LocalTag = {
|
||||
id: crypto.randomUUID(),
|
||||
name: data.name,
|
||||
color: data.color ?? null,
|
||||
isPinned: false,
|
||||
sortOrder: active.length,
|
||||
};
|
||||
await memoroTagTable.add(newLocal);
|
||||
return toTag(newLocal);
|
||||
},
|
||||
export {
|
||||
tagMutations,
|
||||
useAllTags,
|
||||
getTagById,
|
||||
getTagsByIds,
|
||||
getTagColor,
|
||||
} from '@manacore/shared-stores';
|
||||
|
||||
/** Update a tag. */
|
||||
async update(id: string, data: Partial<Pick<LocalTag, 'name' | 'color' | 'isPinned'>>) {
|
||||
await memoroTagTable.update(id, {
|
||||
...data,
|
||||
updatedAt: new Date().toISOString(),
|
||||
});
|
||||
},
|
||||
|
||||
/** Soft-delete a tag and its associations. */
|
||||
async delete(id: string) {
|
||||
const now = new Date().toISOString();
|
||||
await memoroTagTable.update(id, { deletedAt: now, updatedAt: now });
|
||||
// Soft-delete associations
|
||||
const allMT = await memoTagTable.where('tagId').equals(id).toArray();
|
||||
for (const mt of allMT) {
|
||||
await memoTagTable.update(mt.id, { deletedAt: now, updatedAt: now });
|
||||
}
|
||||
},
|
||||
|
||||
/** Add a tag to a memo. */
|
||||
async addToMemo(memoId: string, tagId: string) {
|
||||
// Check if association already exists
|
||||
const existing = await memoTagTable.toArray();
|
||||
if (existing.some((mt) => mt.memoId === memoId && mt.tagId === tagId && !mt.deletedAt)) {
|
||||
return;
|
||||
}
|
||||
const newMT: LocalMemoTag = {
|
||||
id: crypto.randomUUID(),
|
||||
memoId,
|
||||
tagId,
|
||||
};
|
||||
await memoTagTable.add(newMT);
|
||||
},
|
||||
|
||||
/** Remove a tag from a memo. */
|
||||
async removeFromMemo(memoId: string, tagId: string) {
|
||||
const all = await memoTagTable.toArray();
|
||||
const toRemove = all.find((mt) => mt.memoId === memoId && mt.tagId === tagId && !mt.deletedAt);
|
||||
if (toRemove) {
|
||||
const now = new Date().toISOString();
|
||||
await memoTagTable.update(toRemove.id, { deletedAt: now, updatedAt: now });
|
||||
}
|
||||
},
|
||||
};
|
||||
export const memoTagOps = createTagLinkOps({
|
||||
table: () => db.table('memoTags'),
|
||||
entityIdField: 'memoId',
|
||||
});
|
||||
|
|
|
|||
|
|
@ -51,14 +51,6 @@ export interface LocalMemory extends BaseRecord {
|
|||
metadata?: Record<string, unknown>;
|
||||
}
|
||||
|
||||
export interface LocalTag extends BaseRecord {
|
||||
name: string;
|
||||
color: string | null;
|
||||
userId?: string;
|
||||
isPinned?: boolean;
|
||||
sortOrder?: number;
|
||||
}
|
||||
|
||||
export interface LocalMemoTag extends BaseRecord {
|
||||
memoId: string;
|
||||
tagId: string;
|
||||
|
|
|
|||
|
|
@ -5,14 +5,13 @@
|
|||
*/
|
||||
|
||||
import { db } from '$lib/data/database';
|
||||
import type { LocalAlbum, LocalAlbumItem, LocalFavorite, LocalTag, LocalPhotoTag } from './types';
|
||||
import type { LocalAlbum, LocalAlbumItem, LocalFavorite, LocalPhotoTag } from './types';
|
||||
|
||||
// ─── Collection Accessors ──────────────────────────────────
|
||||
|
||||
export const albumTable = db.table<LocalAlbum>('albums');
|
||||
export const albumItemTable = db.table<LocalAlbumItem>('albumItems');
|
||||
export const photoFavoriteTable = db.table<LocalFavorite>('photoFavorites');
|
||||
export const photoTagTable = db.table<LocalTag>('photoTags');
|
||||
export const photoMediaTagTable = db.table<LocalPhotoTag>('photoMediaTags');
|
||||
|
||||
// ─── Guest Seed ────────────────────────────────────────────
|
||||
|
|
@ -26,21 +25,4 @@ export const PHOTOS_GUEST_SEED = {
|
|||
isAutoGenerated: false,
|
||||
},
|
||||
],
|
||||
photoTags: [
|
||||
{
|
||||
id: 'tag-nature',
|
||||
name: 'Natur',
|
||||
color: '#22c55e',
|
||||
},
|
||||
{
|
||||
id: 'tag-people',
|
||||
name: 'Menschen',
|
||||
color: '#3b82f6',
|
||||
},
|
||||
{
|
||||
id: 'tag-travel',
|
||||
name: 'Reisen',
|
||||
color: '#f59e0b',
|
||||
},
|
||||
],
|
||||
};
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@
|
|||
export { photoStore } from './stores/photos.svelte';
|
||||
export { albumMutations } from './stores/albums.svelte';
|
||||
export {
|
||||
useAllPhotoTags,
|
||||
useAllTags,
|
||||
getTagById,
|
||||
getTagsByIds,
|
||||
tagMutations,
|
||||
|
|
@ -28,7 +28,6 @@ export {
|
|||
albumTable,
|
||||
albumItemTable,
|
||||
photoFavoriteTable,
|
||||
photoTagTable,
|
||||
photoMediaTagTable,
|
||||
PHOTOS_GUEST_SEED,
|
||||
} from './collections';
|
||||
|
|
@ -36,7 +35,6 @@ export type {
|
|||
LocalAlbum,
|
||||
LocalAlbumItem,
|
||||
LocalFavorite,
|
||||
LocalTag,
|
||||
LocalPhotoTag,
|
||||
Photo,
|
||||
PhotoFilters,
|
||||
|
|
|
|||
|
|
@ -1,146 +1,19 @@
|
|||
/**
|
||||
* Photo Tag Store — Local-First via Dexie
|
||||
*
|
||||
* Tag CRUD and photo-tag junction table operations.
|
||||
* Photo Tags — Uses shared global tags + module-specific junction table.
|
||||
*/
|
||||
|
||||
import { liveQuery } from 'dexie';
|
||||
import { db } from '$lib/data/database';
|
||||
import type { LocalTag, LocalPhotoTag } from '../types';
|
||||
import { createTagLinkOps } from '@manacore/shared-stores';
|
||||
|
||||
// ─── Tag CRUD ─────────────────────────────────────────────
|
||||
export {
|
||||
tagMutations,
|
||||
useAllTags,
|
||||
getTagById,
|
||||
getTagsByIds,
|
||||
getTagColor,
|
||||
} from '@manacore/shared-stores';
|
||||
|
||||
export function useAllPhotoTags() {
|
||||
return liveQuery(async () => {
|
||||
const all = await db.table<LocalTag>('photoTags').toArray();
|
||||
return all.filter((t) => !t.deletedAt);
|
||||
});
|
||||
}
|
||||
|
||||
export function getTagById(tags: LocalTag[], id: string): LocalTag | undefined {
|
||||
return tags.find((t) => t.id === id);
|
||||
}
|
||||
|
||||
export function getTagsByIds(tags: LocalTag[], ids: string[]): LocalTag[] {
|
||||
const idSet = new Set(ids);
|
||||
return tags.filter((t) => idSet.has(t.id));
|
||||
}
|
||||
|
||||
export const tagMutations = {
|
||||
async createTag(data: { name: string; color?: string }): Promise<LocalTag | null> {
|
||||
try {
|
||||
const now = new Date().toISOString();
|
||||
const tag: LocalTag = {
|
||||
id: crypto.randomUUID(),
|
||||
name: data.name,
|
||||
color: data.color ?? null,
|
||||
createdAt: now,
|
||||
updatedAt: now,
|
||||
};
|
||||
await db.table('photoTags').add(tag);
|
||||
return tag;
|
||||
} catch (e) {
|
||||
console.error('Failed to create tag:', e);
|
||||
return null;
|
||||
}
|
||||
},
|
||||
|
||||
async deleteTag(id: string): Promise<boolean> {
|
||||
try {
|
||||
const now = new Date().toISOString();
|
||||
await db.table('photoTags').update(id, { deletedAt: now, updatedAt: now });
|
||||
// Also soft-delete photo-tag associations
|
||||
const associations = await db.table<LocalPhotoTag>('photoMediaTags').toArray();
|
||||
for (const a of associations.filter((pt) => pt.tagId === id)) {
|
||||
await db.table('photoMediaTags').update(a.id, { deletedAt: now, updatedAt: now });
|
||||
}
|
||||
return true;
|
||||
} catch (e) {
|
||||
console.error('Failed to delete tag:', e);
|
||||
return false;
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
// ─── Photo-Tag Junction ───────────────────────────────────
|
||||
|
||||
export const photoTagOps = {
|
||||
/** Get tags for a photo */
|
||||
async getPhotoTags(mediaId: string): Promise<string[]> {
|
||||
try {
|
||||
const all = await db.table<LocalPhotoTag>('photoMediaTags').toArray();
|
||||
return all.filter((pt) => pt.mediaId === mediaId && !pt.deletedAt).map((pt) => pt.tagId);
|
||||
} catch (e) {
|
||||
console.error('Failed to get photo tags:', e);
|
||||
return [];
|
||||
}
|
||||
},
|
||||
|
||||
/** Add tag to photo */
|
||||
async addTagToPhoto(mediaId: string, tagId: string) {
|
||||
try {
|
||||
const all = await db.table<LocalPhotoTag>('photoMediaTags').toArray();
|
||||
const exists = all.some(
|
||||
(pt) => pt.mediaId === mediaId && pt.tagId === tagId && !pt.deletedAt
|
||||
);
|
||||
if (exists) return true;
|
||||
|
||||
const now = new Date().toISOString();
|
||||
await db.table('photoMediaTags').add({
|
||||
id: crypto.randomUUID(),
|
||||
mediaId,
|
||||
tagId,
|
||||
createdAt: now,
|
||||
updatedAt: now,
|
||||
});
|
||||
return true;
|
||||
} catch (e) {
|
||||
console.error('Failed to add tag to photo:', e);
|
||||
return false;
|
||||
}
|
||||
},
|
||||
|
||||
/** Remove tag from photo */
|
||||
async removeTagFromPhoto(mediaId: string, tagId: string) {
|
||||
try {
|
||||
const all = await db.table<LocalPhotoTag>('photoMediaTags').toArray();
|
||||
const item = all.find((pt) => pt.mediaId === mediaId && pt.tagId === tagId && !pt.deletedAt);
|
||||
if (item) {
|
||||
const now = new Date().toISOString();
|
||||
await db.table('photoMediaTags').update(item.id, { deletedAt: now, updatedAt: now });
|
||||
}
|
||||
return true;
|
||||
} catch (e) {
|
||||
console.error('Failed to remove tag from photo:', e);
|
||||
return false;
|
||||
}
|
||||
},
|
||||
|
||||
/** Set all tags for a photo (replace) */
|
||||
async setPhotoTags(mediaId: string, tagIds: string[]) {
|
||||
try {
|
||||
const now = new Date().toISOString();
|
||||
// Soft-delete existing tags for this photo
|
||||
const all = await db.table<LocalPhotoTag>('photoMediaTags').toArray();
|
||||
const existing = all.filter((pt) => pt.mediaId === mediaId && !pt.deletedAt);
|
||||
for (const item of existing) {
|
||||
await db.table('photoMediaTags').update(item.id, { deletedAt: now, updatedAt: now });
|
||||
}
|
||||
|
||||
// Add new tags
|
||||
for (const tagId of tagIds) {
|
||||
await db.table('photoMediaTags').add({
|
||||
id: crypto.randomUUID(),
|
||||
mediaId,
|
||||
tagId,
|
||||
createdAt: now,
|
||||
updatedAt: now,
|
||||
});
|
||||
}
|
||||
return true;
|
||||
} catch (e) {
|
||||
console.error('Failed to set photo tags:', e);
|
||||
return false;
|
||||
}
|
||||
},
|
||||
};
|
||||
export const photoTagOps = createTagLinkOps({
|
||||
table: () => db.table('photoMediaTags'),
|
||||
entityIdField: 'mediaId',
|
||||
});
|
||||
|
|
|
|||
|
|
@ -23,11 +23,6 @@ export interface LocalFavorite extends BaseRecord {
|
|||
mediaId: string;
|
||||
}
|
||||
|
||||
export interface LocalTag extends BaseRecord {
|
||||
name: string;
|
||||
color?: string | null;
|
||||
}
|
||||
|
||||
export interface LocalPhotoTag extends BaseRecord {
|
||||
mediaId: string;
|
||||
tagId: string;
|
||||
|
|
|
|||
|
|
@ -5,20 +5,13 @@
|
|||
*/
|
||||
|
||||
import { db } from '$lib/data/database';
|
||||
import type {
|
||||
LocalImage,
|
||||
LocalBoard,
|
||||
LocalBoardItem,
|
||||
LocalPictureTag,
|
||||
LocalImageTag,
|
||||
} from './types';
|
||||
import type { LocalImage, LocalBoard, LocalBoardItem, LocalImageTag } from './types';
|
||||
|
||||
// ─── Collection Accessors ──────────────────────────────────
|
||||
|
||||
export const imageTable = db.table<LocalImage>('images');
|
||||
export const boardTable = db.table<LocalBoard>('boards');
|
||||
export const boardItemTable = db.table<LocalBoardItem>('boardItems');
|
||||
export const pictureTagTable = db.table<LocalPictureTag>('pictureTags');
|
||||
export const imageTagTable = db.table<LocalImageTag>('imageTags');
|
||||
|
||||
// ─── Guest Seed ────────────────────────────────────────────
|
||||
|
|
@ -75,9 +68,4 @@ export const PICTURE_GUEST_SEED = {
|
|||
properties: { fontFamily: 'Arial', fontWeight: 'normal', textAlign: 'center' },
|
||||
},
|
||||
] satisfies LocalBoardItem[],
|
||||
pictureTags: [
|
||||
{ id: 'tag-landscape', name: 'Landschaft', color: '#22c55e' },
|
||||
{ id: 'tag-portrait', name: 'Portrait', color: '#3b82f6' },
|
||||
{ id: 'tag-abstract', name: 'Abstrakt', color: '#a855f7' },
|
||||
] satisfies LocalPictureTag[],
|
||||
};
|
||||
|
|
|
|||
|
|
@ -24,7 +24,6 @@ export {
|
|||
imageTable,
|
||||
boardTable,
|
||||
boardItemTable,
|
||||
pictureTagTable,
|
||||
imageTagTable,
|
||||
PICTURE_GUEST_SEED,
|
||||
} from './collections';
|
||||
|
|
@ -32,7 +31,6 @@ export type {
|
|||
LocalImage,
|
||||
LocalBoard,
|
||||
LocalBoardItem,
|
||||
LocalPictureTag,
|
||||
LocalImageTag,
|
||||
ViewMode,
|
||||
Image,
|
||||
|
|
|
|||
|
|
@ -13,7 +13,6 @@ import type {
|
|||
LocalImage,
|
||||
LocalBoard,
|
||||
LocalBoardItem,
|
||||
LocalPictureTag,
|
||||
LocalImageTag,
|
||||
Image,
|
||||
Board,
|
||||
|
|
@ -113,13 +112,8 @@ export function useAllBoards() {
|
|||
}, [] as BoardWithCount[]);
|
||||
}
|
||||
|
||||
/** All picture tags. */
|
||||
export function useAllPictureTags() {
|
||||
return useLiveQueryWithDefault(async () => {
|
||||
const locals = await db.table<LocalPictureTag>('pictureTags').toArray();
|
||||
return locals.filter((t) => !t.deletedAt);
|
||||
}, [] as LocalPictureTag[]);
|
||||
}
|
||||
// Tags: use shared global tags from @manacore/shared-stores
|
||||
export { useAllTags as useAllPictureTags } from '@manacore/shared-stores';
|
||||
|
||||
/** All image-tag associations. */
|
||||
export function useAllImageTags() {
|
||||
|
|
|
|||
|
|
@ -55,11 +55,6 @@ export interface LocalBoardItem extends BaseRecord {
|
|||
properties: Record<string, unknown>;
|
||||
}
|
||||
|
||||
export interface LocalPictureTag extends BaseRecord {
|
||||
name: string;
|
||||
color?: string | null;
|
||||
}
|
||||
|
||||
export interface LocalImageTag extends BaseRecord {
|
||||
imageId: string;
|
||||
tagId: string;
|
||||
|
|
|
|||
|
|
@ -5,13 +5,12 @@
|
|||
*/
|
||||
|
||||
import { db } from '$lib/data/database';
|
||||
import type { LocalFile, LocalFolder, LocalTag, LocalFileTag } from './types';
|
||||
import type { LocalFile, LocalFolder, LocalFileTag } from './types';
|
||||
|
||||
// ─── Collection Accessors ──────────────────────────────────
|
||||
|
||||
export const fileTable = db.table<LocalFile>('files');
|
||||
export const storageFolderTable = db.table<LocalFolder>('storageFolders');
|
||||
export const storageTagTable = db.table<LocalTag>('storageTags');
|
||||
export const fileTagTable = db.table<LocalFileTag>('fileTags');
|
||||
|
||||
// ─── Guest Seed ────────────────────────────────────────────
|
||||
|
|
@ -49,21 +48,4 @@ export const STORAGE_GUEST_SEED = {
|
|||
isDeleted: false,
|
||||
},
|
||||
],
|
||||
storageTags: [
|
||||
{
|
||||
id: 'tag-important',
|
||||
name: 'Wichtig',
|
||||
color: '#ef4444',
|
||||
},
|
||||
{
|
||||
id: 'tag-work',
|
||||
name: 'Arbeit',
|
||||
color: '#3b82f6',
|
||||
},
|
||||
{
|
||||
id: 'tag-personal',
|
||||
name: 'Privat',
|
||||
color: '#22c55e',
|
||||
},
|
||||
],
|
||||
};
|
||||
|
|
|
|||
|
|
@ -3,7 +3,14 @@
|
|||
*/
|
||||
|
||||
export { filesStore } from './stores/files.svelte';
|
||||
export { storageTagStore } from './stores/tags.svelte';
|
||||
export {
|
||||
tagMutations,
|
||||
useAllTags,
|
||||
getTagById,
|
||||
getTagsByIds,
|
||||
getTagColor,
|
||||
fileTagOps,
|
||||
} from './stores/tags.svelte';
|
||||
export {
|
||||
useAllFiles,
|
||||
useAllFolders,
|
||||
|
|
@ -22,11 +29,5 @@ export {
|
|||
formatFileSize,
|
||||
} from './queries';
|
||||
export type { StorageFile, StorageFolder, StorageTag } from './queries';
|
||||
export {
|
||||
fileTable,
|
||||
storageFolderTable,
|
||||
storageTagTable,
|
||||
fileTagTable,
|
||||
STORAGE_GUEST_SEED,
|
||||
} from './collections';
|
||||
export type { LocalFile, LocalFolder, LocalTag, LocalFileTag } from './types';
|
||||
export { fileTable, storageFolderTable, fileTagTable, STORAGE_GUEST_SEED } from './collections';
|
||||
export type { LocalFile, LocalFolder, LocalFileTag } from './types';
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@
|
|||
|
||||
import { liveQuery } from 'dexie';
|
||||
import { db } from '$lib/data/database';
|
||||
import type { LocalFile, LocalFolder, LocalTag, LocalFileTag } from './types';
|
||||
import type { LocalFile, LocalFolder, LocalFileTag } from './types';
|
||||
|
||||
// ─── Shared Types (inline to avoid @storage/shared dependency) ───
|
||||
|
||||
|
|
@ -92,7 +92,12 @@ export function toFolder(local: LocalFolder): StorageFolder {
|
|||
};
|
||||
}
|
||||
|
||||
export function toTag(local: LocalTag): StorageTag {
|
||||
export function toTag(local: {
|
||||
id: string;
|
||||
name: string;
|
||||
color?: string | null;
|
||||
createdAt?: string;
|
||||
}): StorageTag {
|
||||
return {
|
||||
id: local.id,
|
||||
userId: 'local',
|
||||
|
|
@ -126,16 +131,8 @@ export function useAllFolders() {
|
|||
});
|
||||
}
|
||||
|
||||
/** All tags, sorted by name. Auto-updates on any change. */
|
||||
export function useAllStorageTags() {
|
||||
return liveQuery(async () => {
|
||||
const locals = await db.table<LocalTag>('storageTags').toArray();
|
||||
return locals
|
||||
.filter((t) => !t.deletedAt)
|
||||
.map(toTag)
|
||||
.sort((a, b) => a.name.localeCompare(b.name));
|
||||
});
|
||||
}
|
||||
// Tags: use shared global tags from @manacore/shared-stores
|
||||
export { useAllTags as useAllStorageTags } from '@manacore/shared-stores';
|
||||
|
||||
// ─── Pure Helper Functions (for $derived) ─────────────────
|
||||
|
||||
|
|
|
|||
|
|
@ -1,56 +1,19 @@
|
|||
/**
|
||||
* Storage Tag Store — Mutations Only
|
||||
*
|
||||
* Reads come from liveQuery hooks in queries.ts.
|
||||
* This store only handles writes to IndexedDB via the unified database.
|
||||
* Storage Tags — Uses shared global tags + module-specific junction table.
|
||||
*/
|
||||
|
||||
import { storageTagTable, fileTagTable } from '../collections';
|
||||
import type { LocalTag, LocalFileTag } from '../types';
|
||||
import { db } from '$lib/data/database';
|
||||
import { createTagLinkOps } from '@manacore/shared-stores';
|
||||
|
||||
export const storageTagStore = {
|
||||
async create(name: string, color?: string) {
|
||||
const newTag: LocalTag = {
|
||||
id: crypto.randomUUID(),
|
||||
name,
|
||||
color: color ?? null,
|
||||
};
|
||||
await storageTagTable.add(newTag);
|
||||
return newTag;
|
||||
},
|
||||
export {
|
||||
tagMutations,
|
||||
useAllTags,
|
||||
getTagById,
|
||||
getTagsByIds,
|
||||
getTagColor,
|
||||
} from '@manacore/shared-stores';
|
||||
|
||||
async update(id: string, data: Partial<Pick<LocalTag, 'name' | 'color'>>) {
|
||||
await storageTagTable.update(id, {
|
||||
...data,
|
||||
updatedAt: new Date().toISOString(),
|
||||
});
|
||||
},
|
||||
|
||||
async delete(id: string) {
|
||||
await storageTagTable.update(id, {
|
||||
deletedAt: new Date().toISOString(),
|
||||
updatedAt: new Date().toISOString(),
|
||||
});
|
||||
},
|
||||
|
||||
async tagFile(fileId: string, tagId: string) {
|
||||
const existing = await fileTagTable.where('[fileId+tagId]').equals([fileId, tagId]).first();
|
||||
if (existing) return;
|
||||
|
||||
const newFileTag: LocalFileTag = {
|
||||
id: crypto.randomUUID(),
|
||||
fileId,
|
||||
tagId,
|
||||
};
|
||||
await fileTagTable.add(newFileTag);
|
||||
},
|
||||
|
||||
async untagFile(fileId: string, tagId: string) {
|
||||
const existing = await fileTagTable.where('[fileId+tagId]').equals([fileId, tagId]).first();
|
||||
if (existing) {
|
||||
await fileTagTable.update(existing.id, {
|
||||
deletedAt: new Date().toISOString(),
|
||||
});
|
||||
}
|
||||
},
|
||||
};
|
||||
export const fileTagOps = createTagLinkOps({
|
||||
table: () => db.table('fileTags'),
|
||||
entityIdField: 'fileId',
|
||||
});
|
||||
|
|
|
|||
|
|
@ -30,11 +30,6 @@ export interface LocalFolder extends BaseRecord {
|
|||
isDeleted: boolean;
|
||||
}
|
||||
|
||||
export interface LocalTag extends BaseRecord {
|
||||
name: string;
|
||||
color?: string | null;
|
||||
}
|
||||
|
||||
export interface LocalFileTag extends BaseRecord {
|
||||
fileId: string;
|
||||
tagId: string;
|
||||
|
|
|
|||
|
|
@ -10,7 +10,6 @@ import type {
|
|||
LocalClient,
|
||||
LocalProject,
|
||||
LocalTimeEntry,
|
||||
LocalTag,
|
||||
LocalTemplate,
|
||||
LocalSettings,
|
||||
LocalAlarm,
|
||||
|
|
@ -23,7 +22,6 @@ import type {
|
|||
export const clientTable = db.table<LocalClient>('timeClients');
|
||||
export const projectTable = db.table<LocalProject>('timeProjects');
|
||||
export const timeEntryTable = db.table<LocalTimeEntry>('timeEntries');
|
||||
export const tagTable = db.table<LocalTag>('timeTags');
|
||||
export const templateTable = db.table<LocalTemplate>('timeTemplates');
|
||||
export const settingsTable = db.table<LocalSettings>('timeSettings');
|
||||
|
||||
|
|
@ -181,12 +179,6 @@ export const TIMES_GUEST_SEED = {
|
|||
source: { app: 'manual' as const },
|
||||
},
|
||||
],
|
||||
timeTags: [
|
||||
{ id: 'times-tag-design', name: 'design', color: '#f59e0b', order: 0 },
|
||||
{ id: 'times-tag-dev', name: 'development', color: '#3b82f6', order: 1 },
|
||||
{ id: 'times-tag-meeting', name: 'meeting', color: '#6b7280', order: 2 },
|
||||
{ id: 'times-tag-review', name: 'review', color: '#22c55e', order: 3 },
|
||||
],
|
||||
timeSettings: [
|
||||
{
|
||||
id: 'times-default-settings',
|
||||
|
|
|
|||
|
|
@ -25,7 +25,6 @@ export {
|
|||
toClient,
|
||||
toProject,
|
||||
toTimeEntry,
|
||||
toTag,
|
||||
toTemplate,
|
||||
toSettings,
|
||||
formatDuration,
|
||||
|
|
@ -67,7 +66,6 @@ export {
|
|||
clientTable,
|
||||
projectTable,
|
||||
timeEntryTable,
|
||||
tagTable,
|
||||
templateTable,
|
||||
settingsTable,
|
||||
TIMES_GUEST_SEED,
|
||||
|
|
@ -86,7 +84,6 @@ export type {
|
|||
LocalClient,
|
||||
LocalProject,
|
||||
LocalTimeEntry,
|
||||
LocalTag,
|
||||
LocalTemplate,
|
||||
LocalSettings,
|
||||
BillingRate,
|
||||
|
|
|
|||
|
|
@ -11,7 +11,6 @@ import type {
|
|||
LocalClient,
|
||||
LocalProject,
|
||||
LocalTimeEntry,
|
||||
LocalTag,
|
||||
LocalTemplate,
|
||||
LocalSettings,
|
||||
LocalAlarm,
|
||||
|
|
@ -88,17 +87,6 @@ export function toTimeEntry(local: LocalTimeEntry): TimeEntry {
|
|||
};
|
||||
}
|
||||
|
||||
export function toTag(local: LocalTag): Tag {
|
||||
return {
|
||||
id: local.id,
|
||||
name: local.name,
|
||||
color: local.color,
|
||||
order: local.order,
|
||||
createdAt: local.createdAt ?? new Date().toISOString(),
|
||||
updatedAt: local.updatedAt ?? new Date().toISOString(),
|
||||
};
|
||||
}
|
||||
|
||||
export function toTemplate(local: LocalTemplate): EntryTemplate {
|
||||
return {
|
||||
id: local.id,
|
||||
|
|
@ -200,12 +188,8 @@ export function useAllTimeEntries() {
|
|||
});
|
||||
}
|
||||
|
||||
export function useAllTags() {
|
||||
return liveQuery(async () => {
|
||||
const locals = await db.table<LocalTag>('timeTags').toArray();
|
||||
return locals.filter((t) => !t.deletedAt).map(toTag);
|
||||
});
|
||||
}
|
||||
// Tags: use shared global tags from @manacore/shared-stores
|
||||
export { useAllTags } from '@manacore/shared-stores';
|
||||
|
||||
export function useAllTemplates() {
|
||||
return liveQuery(async () => {
|
||||
|
|
|
|||
|
|
@ -33,8 +33,8 @@ export interface ProjectBudget {
|
|||
}
|
||||
|
||||
export interface SortOption {
|
||||
field: SortField;
|
||||
direction: SortDirection;
|
||||
field: string;
|
||||
direction: 'asc' | 'desc';
|
||||
}
|
||||
|
||||
export interface FilterCriteria {
|
||||
|
|
@ -195,12 +195,6 @@ export interface LocalTimeEntry extends BaseRecord {
|
|||
source?: EntrySourceRef | null;
|
||||
}
|
||||
|
||||
export interface LocalTag extends BaseRecord {
|
||||
name: string;
|
||||
color: string;
|
||||
order: number;
|
||||
}
|
||||
|
||||
export interface LocalTemplate extends BaseRecord {
|
||||
name: string;
|
||||
projectId?: string | null;
|
||||
|
|
|
|||
|
|
@ -7,8 +7,7 @@
|
|||
import { db } from '$lib/data/database';
|
||||
import type {
|
||||
LocalTask,
|
||||
LocalLabel,
|
||||
LocalTaskLabel,
|
||||
LocalTaskTag,
|
||||
LocalReminder,
|
||||
LocalBoardView,
|
||||
LocalTodoProject,
|
||||
|
|
@ -18,8 +17,7 @@ import type {
|
|||
|
||||
export const taskTable = db.table<LocalTask>('tasks');
|
||||
export const todoProjectTable = db.table<LocalTodoProject>('todoProjects');
|
||||
export const labelTable = db.table<LocalLabel>('labels');
|
||||
export const taskLabelTable = db.table<LocalTaskLabel>('taskLabels');
|
||||
export const taskTagTable = db.table<LocalTaskTag>('taskLabels'); // DB table still 'taskLabels' until schema migration
|
||||
export const reminderTable = db.table<LocalReminder>('reminders');
|
||||
export const boardViewTable = db.table<LocalBoardView>('boardViews');
|
||||
|
||||
|
|
@ -32,19 +30,6 @@ const nextWeek = new Date(now);
|
|||
nextWeek.setDate(nextWeek.getDate() + 7);
|
||||
|
||||
export const TODO_GUEST_SEED = {
|
||||
labels: [
|
||||
{
|
||||
id: 'label-important',
|
||||
name: 'Wichtig',
|
||||
color: '#ef4444',
|
||||
},
|
||||
{
|
||||
id: 'label-idea',
|
||||
name: 'Idee',
|
||||
color: '#f59e0b',
|
||||
},
|
||||
] satisfies LocalLabel[],
|
||||
|
||||
boardViews: [
|
||||
{
|
||||
id: 'view-kanban',
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
<script lang="ts">
|
||||
import { getContext } from 'svelte';
|
||||
import type { Observable } from 'dexie';
|
||||
import type { LocalLabel } from '../../types';
|
||||
import { TagSelector as SharedTagSelector } from '@manacore/shared-ui';
|
||||
import type { Tag } from '@manacore/shared-ui';
|
||||
|
||||
|
|
@ -12,14 +11,15 @@
|
|||
|
||||
let { selectedIds, onChange }: Props = $props();
|
||||
|
||||
const allLabels$: Observable<LocalLabel[]> = getContext('labels');
|
||||
let allLabels = $state<LocalLabel[]>([]);
|
||||
// Labels come from context (set in todo +layout.svelte via useAllLabels → shared useAllTags)
|
||||
const allLabels$: Observable<Array<{ id: string; name: string; color: string }>> =
|
||||
getContext('labels');
|
||||
let allLabels = $state<Array<{ id: string; name: string; color: string }>>([]);
|
||||
$effect(() => {
|
||||
const sub = allLabels$.subscribe((l) => (allLabels = l));
|
||||
const sub = allLabels$.subscribe((l) => (allLabels = l ?? []));
|
||||
return () => sub.unsubscribe();
|
||||
});
|
||||
|
||||
// Adapt LocalLabel[] to Tag[] for the shared component
|
||||
const tags: Tag[] = $derived(allLabels.map((l) => ({ id: l.id, name: l.name, color: l.color })));
|
||||
const selectedTags: Tag[] = $derived(
|
||||
selectedIds.map((id) => tags.find((t) => t.id === id)).filter((t): t is Tag => t != null)
|
||||
|
|
|
|||
|
|
@ -37,8 +37,7 @@ export {
|
|||
export {
|
||||
taskTable,
|
||||
todoProjectTable,
|
||||
labelTable,
|
||||
taskLabelTable,
|
||||
taskTagTable,
|
||||
reminderTable,
|
||||
boardViewTable,
|
||||
TODO_GUEST_SEED,
|
||||
|
|
@ -66,7 +65,7 @@ export { useTaskForm } from './composables/useTaskForm.svelte';
|
|||
export type {
|
||||
LocalTask,
|
||||
LocalLabel,
|
||||
LocalTaskLabel,
|
||||
LocalTaskTag,
|
||||
LocalReminder,
|
||||
LocalBoardView,
|
||||
LocalTodoProject,
|
||||
|
|
|
|||
|
|
@ -6,7 +6,6 @@ import { liveQuery } from 'dexie';
|
|||
import { db } from '$lib/data/database';
|
||||
import type {
|
||||
LocalTask,
|
||||
LocalLabel,
|
||||
LocalBoardView,
|
||||
LocalReminder,
|
||||
LocalTodoProject,
|
||||
|
|
@ -50,12 +49,8 @@ export function useAllTasks() {
|
|||
});
|
||||
}
|
||||
|
||||
export function useAllLabels() {
|
||||
return liveQuery(async () => {
|
||||
const locals = await db.table<LocalLabel>('labels').toArray();
|
||||
return locals.filter((l) => !l.deletedAt);
|
||||
});
|
||||
}
|
||||
// Labels/Tags: use shared global tags from @manacore/shared-stores
|
||||
export { useAllTags as useAllLabels } from '@manacore/shared-stores';
|
||||
|
||||
export function useAllBoardViews() {
|
||||
return liveQuery(async () => {
|
||||
|
|
|
|||
|
|
@ -1,32 +1,35 @@
|
|||
/**
|
||||
* Labels Store — Mutation-Only Service
|
||||
* Todo Tags (formerly Labels) — Uses shared global tags + module-specific junction table.
|
||||
*/
|
||||
|
||||
import { labelTable } from '../collections';
|
||||
import type { LocalLabel } from '../types';
|
||||
import { db } from '$lib/data/database';
|
||||
import { createTagLinkOps } from '@manacore/shared-stores';
|
||||
|
||||
export {
|
||||
tagMutations,
|
||||
useAllTags,
|
||||
getTagById,
|
||||
getTagsByIds,
|
||||
getTagColor,
|
||||
} from '@manacore/shared-stores';
|
||||
|
||||
export const taskTagOps = createTagLinkOps({
|
||||
table: () => db.table('taskLabels'), // DB table still 'taskLabels' until schema migration
|
||||
entityIdField: 'taskId',
|
||||
});
|
||||
|
||||
// Backward-compat alias
|
||||
export const labelsStore = {
|
||||
async createLabel(data: { name: string; color: string }) {
|
||||
const newLabel: LocalLabel = {
|
||||
id: crypto.randomUUID(),
|
||||
name: data.name,
|
||||
color: data.color,
|
||||
};
|
||||
await labelTable.add(newLabel);
|
||||
return newLabel;
|
||||
createLabel: async (data: { name: string; color: string }) => {
|
||||
const { tagMutations } = await import('@manacore/shared-stores');
|
||||
return tagMutations.createTag({ name: data.name, color: data.color });
|
||||
},
|
||||
|
||||
async updateLabel(id: string, data: Partial<Pick<LocalLabel, 'name' | 'color'>>) {
|
||||
await labelTable.update(id, {
|
||||
...data,
|
||||
updatedAt: new Date().toISOString(),
|
||||
});
|
||||
updateLabel: async (id: string, data: { name?: string; color?: string }) => {
|
||||
const { tagMutations } = await import('@manacore/shared-stores');
|
||||
return tagMutations.updateTag(id, data);
|
||||
},
|
||||
|
||||
async deleteLabel(id: string) {
|
||||
await labelTable.update(id, {
|
||||
deletedAt: new Date().toISOString(),
|
||||
updatedAt: new Date().toISOString(),
|
||||
});
|
||||
deleteLabel: async (id: string) => {
|
||||
const { tagMutations } = await import('@manacore/shared-stores');
|
||||
return tagMutations.deleteTag(id);
|
||||
},
|
||||
};
|
||||
|
|
|
|||
|
|
@ -3,6 +3,10 @@
|
|||
*/
|
||||
|
||||
import type { BaseRecord } from '@manacore/local-store';
|
||||
import type { Tag } from '@manacore/shared-tags';
|
||||
|
||||
/** @deprecated Use Tag from @manacore/shared-tags. Kept for backward compatibility. */
|
||||
export type LocalLabel = Tag;
|
||||
|
||||
// ─── Local Types (IndexedDB) ──────────────────────────────
|
||||
|
||||
|
|
@ -35,15 +39,9 @@ export interface LocalTask extends BaseRecord {
|
|||
metadata?: Record<string, unknown>;
|
||||
}
|
||||
|
||||
export interface LocalLabel extends BaseRecord {
|
||||
name: string;
|
||||
color: string;
|
||||
userId?: string;
|
||||
}
|
||||
|
||||
export interface LocalTaskLabel extends BaseRecord {
|
||||
export interface LocalTaskTag extends BaseRecord {
|
||||
taskId: string;
|
||||
labelId: string;
|
||||
tagId: string;
|
||||
}
|
||||
|
||||
export interface LocalReminder extends BaseRecord {
|
||||
|
|
|
|||
|
|
@ -5,12 +5,11 @@
|
|||
*/
|
||||
|
||||
import { db } from '$lib/data/database';
|
||||
import type { LocalLink, LocalTag, LocalFolder, LocalLinkTag } from './types';
|
||||
import type { LocalLink, LocalFolder, LocalLinkTag } from './types';
|
||||
|
||||
// ─── Collection Accessors ──────────────────────────────────
|
||||
|
||||
export const linkTable = db.table<LocalLink>('links');
|
||||
export const uloadTagTable = db.table<LocalTag>('uloadTags');
|
||||
export const uloadFolderTable = db.table<LocalFolder>('uloadFolders');
|
||||
export const linkTagTable = db.table<LocalLinkTag>('linkTags');
|
||||
|
||||
|
|
@ -31,35 +30,6 @@ export const ULOAD_GUEST_SEED = {
|
|||
order: 1,
|
||||
},
|
||||
] satisfies LocalFolder[],
|
||||
uloadTags: [
|
||||
{
|
||||
id: 'tag-social',
|
||||
name: 'Social Media',
|
||||
slug: 'social-media',
|
||||
color: '#8b5cf6',
|
||||
icon: null,
|
||||
isPublic: false,
|
||||
usageCount: 2,
|
||||
},
|
||||
{
|
||||
id: 'tag-docs',
|
||||
name: 'Dokumentation',
|
||||
slug: 'dokumentation',
|
||||
color: '#f59e0b',
|
||||
icon: null,
|
||||
isPublic: false,
|
||||
usageCount: 1,
|
||||
},
|
||||
{
|
||||
id: 'tag-marketing',
|
||||
name: 'Marketing',
|
||||
slug: 'marketing',
|
||||
color: '#ef4444',
|
||||
icon: null,
|
||||
isPublic: false,
|
||||
usageCount: 1,
|
||||
},
|
||||
] satisfies LocalTag[],
|
||||
links: [
|
||||
{
|
||||
id: 'link-welcome',
|
||||
|
|
@ -109,10 +79,5 @@ export const ULOAD_GUEST_SEED = {
|
|||
order: 1,
|
||||
},
|
||||
] satisfies LocalLink[],
|
||||
linkTags: [
|
||||
{ id: 'lt-1', linkId: 'link-github', tagId: 'tag-social' },
|
||||
{ id: 'lt-2', linkId: 'link-docs', tagId: 'tag-docs' },
|
||||
{ id: 'lt-3', linkId: 'link-welcome', tagId: 'tag-social' },
|
||||
{ id: 'lt-4', linkId: 'link-expired', tagId: 'tag-marketing' },
|
||||
] satisfies LocalLinkTag[],
|
||||
linkTags: [] as LocalLinkTag[],
|
||||
};
|
||||
|
|
|
|||
|
|
@ -23,15 +23,6 @@ export interface LocalLink extends BaseRecord {
|
|||
order: number;
|
||||
}
|
||||
|
||||
export interface LocalTag extends BaseRecord {
|
||||
name: string;
|
||||
slug: string;
|
||||
color?: string | null;
|
||||
icon?: string | null;
|
||||
isPublic: boolean;
|
||||
usageCount: number;
|
||||
}
|
||||
|
||||
export interface LocalFolder extends BaseRecord {
|
||||
name: string;
|
||||
color?: string | null;
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@
|
|||
import { getContext } from 'svelte';
|
||||
import { memosStore } from '$lib/modules/memoro/stores/memos.svelte';
|
||||
import { memoriesStore } from '$lib/modules/memoro/stores/memories.svelte';
|
||||
import { tagsStore } from '$lib/modules/memoro/stores/tags.svelte';
|
||||
import { memoTagOps } from '$lib/modules/memoro/stores/tags.svelte';
|
||||
import {
|
||||
useMemoriesByMemo,
|
||||
getTagsForMemo,
|
||||
|
|
@ -75,12 +75,12 @@
|
|||
}
|
||||
|
||||
async function handleAddTag(tagId: string) {
|
||||
await tagsStore.addToMemo(memoId, tagId);
|
||||
await memoTagOps.addTag(memoId, tagId);
|
||||
showTagPicker = false;
|
||||
}
|
||||
|
||||
async function handleRemoveTag(tagId: string) {
|
||||
await tagsStore.removeFromMemo(memoId, tagId);
|
||||
await memoTagOps.removeTag(memoId, tagId);
|
||||
}
|
||||
|
||||
// Available tags (not already assigned)
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
<script lang="ts">
|
||||
import { getContext } from 'svelte';
|
||||
import { tagsStore } from '$lib/modules/memoro/stores/tags.svelte';
|
||||
import type { Tag } from '$lib/modules/memoro/types';
|
||||
import { tagMutations } from '$lib/modules/memoro/stores/tags.svelte';
|
||||
import type { Tag } from '@manacore/shared-tags';
|
||||
import {
|
||||
ArrowLeft,
|
||||
Plus,
|
||||
|
|
@ -48,22 +48,18 @@
|
|||
async function handleSubmit() {
|
||||
if (!formName.trim()) return;
|
||||
if (editingId) {
|
||||
await tagsStore.update(editingId, { name: formName.trim(), color: formColor });
|
||||
await tagMutations.updateTag(editingId, { name: formName.trim(), color: formColor });
|
||||
} else {
|
||||
await tagsStore.create({ name: formName.trim(), color: formColor });
|
||||
await tagMutations.createTag({ name: formName.trim(), color: formColor });
|
||||
}
|
||||
showCreateForm = false;
|
||||
}
|
||||
|
||||
async function handleDelete(id: string) {
|
||||
if (confirm('Tag wirklich loschen?')) {
|
||||
await tagsStore.delete(id);
|
||||
await tagMutations.deleteTag(id);
|
||||
}
|
||||
}
|
||||
|
||||
async function handleTogglePin(tag: Tag) {
|
||||
await tagsStore.update(tag.id, { isPinned: !tag.isPinned });
|
||||
}
|
||||
</script>
|
||||
|
||||
<svelte:head>
|
||||
|
|
@ -123,17 +119,7 @@
|
|||
style="background-color: {tag.color || '#888'}"
|
||||
></span>
|
||||
<span class="flex-1 font-medium text-[hsl(var(--foreground))]">{tag.name}</span>
|
||||
{#if tag.isPinned}
|
||||
<PushPin size={14} weight="fill" class="text-[hsl(var(--primary))]" />
|
||||
{/if}
|
||||
<div class="flex items-center gap-1 opacity-0 transition-opacity group-hover:opacity-100">
|
||||
<button
|
||||
onclick={() => handleTogglePin(tag)}
|
||||
class="rounded p-1 text-[hsl(var(--muted-foreground))] hover:text-[hsl(var(--primary))]"
|
||||
title={tag.isPinned ? 'Loslosen' : 'Anpinnen'}
|
||||
>
|
||||
<PushPin size={16} weight={tag.isPinned ? 'fill' : 'regular'} />
|
||||
</button>
|
||||
<button
|
||||
onclick={() => openEditForm(tag)}
|
||||
class="rounded p-1 text-[hsl(var(--muted-foreground))] hover:text-[hsl(var(--foreground))]"
|
||||
|
|
|
|||
|
|
@ -3,7 +3,8 @@
|
|||
import { imagesStore } from '$lib/modules/picture/stores/images.svelte';
|
||||
import { pictureViewStore } from '$lib/modules/picture/stores/view.svelte';
|
||||
import { getFavoriteImages, getImagesByTags } from '$lib/modules/picture/queries';
|
||||
import type { Image, LocalPictureTag, LocalImageTag } from '$lib/modules/picture/types';
|
||||
import type { Image, LocalImageTag } from '$lib/modules/picture/types';
|
||||
import type { Tag } from '@manacore/shared-tags';
|
||||
import {
|
||||
Heart,
|
||||
SquaresFour,
|
||||
|
|
@ -16,7 +17,7 @@
|
|||
} from '@manacore/shared-icons';
|
||||
|
||||
const allImages: { value: Image[] } = getContext('allImages');
|
||||
const allPictureTags: { value: LocalPictureTag[] } = getContext('pictureTags');
|
||||
const allPictureTags: { value: Tag[] } = getContext('pictureTags');
|
||||
const allImageTags: { value: LocalImageTag[] } = getContext('allImageTags');
|
||||
|
||||
let searchQuery = $state('');
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue