chore: refactoring tag coloring
This commit is contained in:
@@ -20,7 +20,7 @@ import { GitDiffView } from '../GitDiffView/GitDiffView';
|
||||
import { DocumentationView } from '../DocumentationView/DocumentationView';
|
||||
import { SiteValidationView } from '../SiteValidationView';
|
||||
import { ScriptsView } from '../ScriptsView/ScriptsView';
|
||||
import { AutoSaveManager, getContrastColor } from '../../utils';
|
||||
import { AutoSaveManager, getContrastColor, loadTagColorMap } from '../../utils';
|
||||
import { InsertModal } from '../InsertModal';
|
||||
import { AISuggestionsModal, AISuggestions } from '../AISuggestionsModal/AISuggestionsModal';
|
||||
import { openEntityTab } from '../../navigation/tabPolicy';
|
||||
@@ -1476,12 +1476,6 @@ interface CategoryCount {
|
||||
count: number;
|
||||
}
|
||||
|
||||
interface TagDataWithColor {
|
||||
id: string;
|
||||
name: string;
|
||||
color?: string;
|
||||
}
|
||||
|
||||
const Dashboard: React.FC = () => {
|
||||
const { t: tr, language } = useI18n();
|
||||
const { posts, media } = useAppStore();
|
||||
@@ -1500,26 +1494,18 @@ const Dashboard: React.FC = () => {
|
||||
useEffect(() => {
|
||||
const loadStats = async () => {
|
||||
try {
|
||||
const [ds, ym, tc, cc, allTagsData] = await Promise.all([
|
||||
const [ds, ym, tc, cc, colorMap] = await Promise.all([
|
||||
window.electronAPI?.posts.getDashboardStats(),
|
||||
window.electronAPI?.posts.getByYearMonth(),
|
||||
window.electronAPI?.posts.getTagsWithCounts(),
|
||||
window.electronAPI?.posts.getCategoriesWithCounts(),
|
||||
window.electronAPI?.tags.getAll(),
|
||||
loadTagColorMap(),
|
||||
]);
|
||||
if (ds) setStats(ds);
|
||||
if (ym) setYearMonthData(ym);
|
||||
if (tc) setTagCounts(tc);
|
||||
if (cc) setCategoryCounts(cc);
|
||||
if (allTagsData) {
|
||||
const colorMap = new Map<string, string>();
|
||||
for (const tag of allTagsData as TagDataWithColor[]) {
|
||||
if (tag.color) {
|
||||
colorMap.set(tag.name, tag.color);
|
||||
}
|
||||
}
|
||||
setTagColors(colorMap);
|
||||
}
|
||||
setTagColors(colorMap);
|
||||
} catch (e) {
|
||||
console.error('Failed to load dashboard stats:', e);
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import React, { useState, useEffect, useCallback, useMemo, useRef } from 'react';
|
||||
import { useAppStore, PostData, MediaData } from '../../store';
|
||||
import { showToast } from '../Toast';
|
||||
import { getContrastColor, groupPostsByStatus } from '../../utils';
|
||||
import { getContrastColor, groupPostsByStatus, loadTagColorMap } from '../../utils';
|
||||
import type { ChatConversation, ImportDefinitionData } from '../../types/electron';
|
||||
import { GitSidebar } from '../GitSidebar/GitSidebar';
|
||||
import { scrollToSettingsSection, SettingsCategory } from '../SettingsView/SettingsView';
|
||||
@@ -27,13 +27,6 @@ function getMediaDisplayName(media: MediaData): string {
|
||||
return media.originalName;
|
||||
}
|
||||
|
||||
// Tag data with color information
|
||||
interface TagData {
|
||||
id: string;
|
||||
name: string;
|
||||
color?: string;
|
||||
}
|
||||
|
||||
const UI_DATE_LOCALE: Record<string, string> = {
|
||||
en: 'en-US',
|
||||
de: 'de-DE',
|
||||
@@ -537,10 +530,10 @@ const PostsList: React.FC<PostsListProps> = ({ mode, isActive }) => {
|
||||
// Load available tags with colors and categories
|
||||
useEffect(() => {
|
||||
const loadFilters = async () => {
|
||||
const [tags, categories, allTagsData] = await Promise.all([
|
||||
const [tags, categories, colorMap] = await Promise.all([
|
||||
window.electronAPI?.posts.getTags(),
|
||||
window.electronAPI?.posts.getCategories(),
|
||||
window.electronAPI?.tags?.getAll?.(),
|
||||
loadTagColorMap(),
|
||||
]);
|
||||
if (tags) setAvailableTags(tags as string[]);
|
||||
if (categories) {
|
||||
@@ -551,15 +544,7 @@ const PostsList: React.FC<PostsListProps> = ({ mode, isActive }) => {
|
||||
: allCategories
|
||||
);
|
||||
}
|
||||
if (allTagsData) {
|
||||
const colorMap = new Map<string, string>();
|
||||
for (const tag of allTagsData as TagData[]) {
|
||||
if (tag.color) {
|
||||
colorMap.set(tag.name, tag.color);
|
||||
}
|
||||
}
|
||||
setTagColors(colorMap);
|
||||
}
|
||||
setTagColors(colorMap);
|
||||
};
|
||||
loadFilters();
|
||||
}, [posts]);
|
||||
@@ -1005,20 +990,12 @@ const MediaList: React.FC = () => {
|
||||
// Load available tags with colors
|
||||
useEffect(() => {
|
||||
const loadTags = async () => {
|
||||
const [tags, allTagsData] = await Promise.all([
|
||||
const [tags, colorMap] = await Promise.all([
|
||||
window.electronAPI?.media.getTags(),
|
||||
window.electronAPI?.tags?.getAll?.(),
|
||||
loadTagColorMap(),
|
||||
]);
|
||||
if (tags) setAvailableTags(tags as string[]);
|
||||
if (allTagsData) {
|
||||
const colorMap = new Map<string, string>();
|
||||
for (const tag of allTagsData as TagData[]) {
|
||||
if (tag.color) {
|
||||
colorMap.set(tag.name, tag.color);
|
||||
}
|
||||
}
|
||||
setTagColors(colorMap);
|
||||
}
|
||||
setTagColors(colorMap);
|
||||
};
|
||||
loadTags();
|
||||
}, [media]);
|
||||
|
||||
@@ -3,3 +3,4 @@ export { getContrastColor } from './color';
|
||||
export { unescapeMacroSyntax } from './markdownEscape';
|
||||
export { groupPostsByStatus, type GroupedPosts, type PostStatus } from './postGrouping';
|
||||
export { loadTabsForProject, saveTabsForProject } from './tabPersistence';
|
||||
export { buildTagColorMap, loadTagColorMap } from './tagColors';
|
||||
|
||||
21
src/renderer/utils/tagColors.ts
Normal file
21
src/renderer/utils/tagColors.ts
Normal file
@@ -0,0 +1,21 @@
|
||||
interface TagColorSource {
|
||||
name: string;
|
||||
color?: string | null;
|
||||
}
|
||||
|
||||
export function buildTagColorMap(tags: TagColorSource[] | null | undefined): Map<string, string> {
|
||||
const colorMap = new Map<string, string>();
|
||||
|
||||
for (const tag of tags ?? []) {
|
||||
if (tag.color) {
|
||||
colorMap.set(tag.name, tag.color);
|
||||
}
|
||||
}
|
||||
|
||||
return colorMap;
|
||||
}
|
||||
|
||||
export async function loadTagColorMap(): Promise<Map<string, string>> {
|
||||
const allTagsData = await window.electronAPI?.tags?.getAll?.();
|
||||
return buildTagColorMap(allTagsData as TagColorSource[] | null | undefined);
|
||||
}
|
||||
49
tests/renderer/utils/tagColors.test.ts
Normal file
49
tests/renderer/utils/tagColors.test.ts
Normal file
@@ -0,0 +1,49 @@
|
||||
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
||||
import { buildTagColorMap, loadTagColorMap } from '../../../src/renderer/utils/tagColors';
|
||||
|
||||
describe('tagColors utils', () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
});
|
||||
|
||||
it('buildTagColorMap includes only tags with colors', () => {
|
||||
const map = buildTagColorMap([
|
||||
{ id: '1', name: 'alpha', color: '#111111' },
|
||||
{ id: '2', name: 'beta' },
|
||||
{ id: '3', name: 'gamma', color: '#333333' },
|
||||
{ id: '4', name: 'delta', color: null },
|
||||
]);
|
||||
|
||||
expect(map.get('alpha')).toBe('#111111');
|
||||
expect(map.get('gamma')).toBe('#333333');
|
||||
expect(map.has('beta')).toBe(false);
|
||||
expect(map.has('delta')).toBe(false);
|
||||
});
|
||||
|
||||
it('loadTagColorMap resolves colors from electron tags api', async () => {
|
||||
(window as any).electronAPI = {
|
||||
...(window as any).electronAPI,
|
||||
tags: {
|
||||
getAll: vi.fn().mockResolvedValue([
|
||||
{ id: '1', name: 'blue', color: '#0000ff' },
|
||||
{ id: '2', name: 'red', color: '#ff0000' },
|
||||
]),
|
||||
},
|
||||
};
|
||||
|
||||
const map = await loadTagColorMap();
|
||||
|
||||
expect(map.get('blue')).toBe('#0000ff');
|
||||
expect(map.get('red')).toBe('#ff0000');
|
||||
});
|
||||
|
||||
it('loadTagColorMap returns empty map when api is unavailable', async () => {
|
||||
(window as any).electronAPI = {
|
||||
...(window as any).electronAPI,
|
||||
tags: undefined,
|
||||
};
|
||||
|
||||
const map = await loadTagColorMap();
|
||||
expect(map.size).toBe(0);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user