chore: refactoring tag coloring
This commit is contained in:
@@ -20,7 +20,7 @@ import { GitDiffView } from '../GitDiffView/GitDiffView';
|
|||||||
import { DocumentationView } from '../DocumentationView/DocumentationView';
|
import { DocumentationView } from '../DocumentationView/DocumentationView';
|
||||||
import { SiteValidationView } from '../SiteValidationView';
|
import { SiteValidationView } from '../SiteValidationView';
|
||||||
import { ScriptsView } from '../ScriptsView/ScriptsView';
|
import { ScriptsView } from '../ScriptsView/ScriptsView';
|
||||||
import { AutoSaveManager, getContrastColor } from '../../utils';
|
import { AutoSaveManager, getContrastColor, loadTagColorMap } from '../../utils';
|
||||||
import { InsertModal } from '../InsertModal';
|
import { InsertModal } from '../InsertModal';
|
||||||
import { AISuggestionsModal, AISuggestions } from '../AISuggestionsModal/AISuggestionsModal';
|
import { AISuggestionsModal, AISuggestions } from '../AISuggestionsModal/AISuggestionsModal';
|
||||||
import { openEntityTab } from '../../navigation/tabPolicy';
|
import { openEntityTab } from '../../navigation/tabPolicy';
|
||||||
@@ -1476,12 +1476,6 @@ interface CategoryCount {
|
|||||||
count: number;
|
count: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface TagDataWithColor {
|
|
||||||
id: string;
|
|
||||||
name: string;
|
|
||||||
color?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
const Dashboard: React.FC = () => {
|
const Dashboard: React.FC = () => {
|
||||||
const { t: tr, language } = useI18n();
|
const { t: tr, language } = useI18n();
|
||||||
const { posts, media } = useAppStore();
|
const { posts, media } = useAppStore();
|
||||||
@@ -1500,26 +1494,18 @@ const Dashboard: React.FC = () => {
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const loadStats = async () => {
|
const loadStats = async () => {
|
||||||
try {
|
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.getDashboardStats(),
|
||||||
window.electronAPI?.posts.getByYearMonth(),
|
window.electronAPI?.posts.getByYearMonth(),
|
||||||
window.electronAPI?.posts.getTagsWithCounts(),
|
window.electronAPI?.posts.getTagsWithCounts(),
|
||||||
window.electronAPI?.posts.getCategoriesWithCounts(),
|
window.electronAPI?.posts.getCategoriesWithCounts(),
|
||||||
window.electronAPI?.tags.getAll(),
|
loadTagColorMap(),
|
||||||
]);
|
]);
|
||||||
if (ds) setStats(ds);
|
if (ds) setStats(ds);
|
||||||
if (ym) setYearMonthData(ym);
|
if (ym) setYearMonthData(ym);
|
||||||
if (tc) setTagCounts(tc);
|
if (tc) setTagCounts(tc);
|
||||||
if (cc) setCategoryCounts(cc);
|
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) {
|
} catch (e) {
|
||||||
console.error('Failed to load dashboard stats:', e);
|
console.error('Failed to load dashboard stats:', e);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import React, { useState, useEffect, useCallback, useMemo, useRef } from 'react';
|
import React, { useState, useEffect, useCallback, useMemo, useRef } from 'react';
|
||||||
import { useAppStore, PostData, MediaData } from '../../store';
|
import { useAppStore, PostData, MediaData } from '../../store';
|
||||||
import { showToast } from '../Toast';
|
import { showToast } from '../Toast';
|
||||||
import { getContrastColor, groupPostsByStatus } from '../../utils';
|
import { getContrastColor, groupPostsByStatus, loadTagColorMap } from '../../utils';
|
||||||
import type { ChatConversation, ImportDefinitionData } from '../../types/electron';
|
import type { ChatConversation, ImportDefinitionData } from '../../types/electron';
|
||||||
import { GitSidebar } from '../GitSidebar/GitSidebar';
|
import { GitSidebar } from '../GitSidebar/GitSidebar';
|
||||||
import { scrollToSettingsSection, SettingsCategory } from '../SettingsView/SettingsView';
|
import { scrollToSettingsSection, SettingsCategory } from '../SettingsView/SettingsView';
|
||||||
@@ -27,13 +27,6 @@ function getMediaDisplayName(media: MediaData): string {
|
|||||||
return media.originalName;
|
return media.originalName;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Tag data with color information
|
|
||||||
interface TagData {
|
|
||||||
id: string;
|
|
||||||
name: string;
|
|
||||||
color?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
const UI_DATE_LOCALE: Record<string, string> = {
|
const UI_DATE_LOCALE: Record<string, string> = {
|
||||||
en: 'en-US',
|
en: 'en-US',
|
||||||
de: 'de-DE',
|
de: 'de-DE',
|
||||||
@@ -537,10 +530,10 @@ const PostsList: React.FC<PostsListProps> = ({ mode, isActive }) => {
|
|||||||
// Load available tags with colors and categories
|
// Load available tags with colors and categories
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const loadFilters = async () => {
|
const loadFilters = async () => {
|
||||||
const [tags, categories, allTagsData] = await Promise.all([
|
const [tags, categories, colorMap] = await Promise.all([
|
||||||
window.electronAPI?.posts.getTags(),
|
window.electronAPI?.posts.getTags(),
|
||||||
window.electronAPI?.posts.getCategories(),
|
window.electronAPI?.posts.getCategories(),
|
||||||
window.electronAPI?.tags?.getAll?.(),
|
loadTagColorMap(),
|
||||||
]);
|
]);
|
||||||
if (tags) setAvailableTags(tags as string[]);
|
if (tags) setAvailableTags(tags as string[]);
|
||||||
if (categories) {
|
if (categories) {
|
||||||
@@ -551,15 +544,7 @@ const PostsList: React.FC<PostsListProps> = ({ mode, isActive }) => {
|
|||||||
: allCategories
|
: 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();
|
loadFilters();
|
||||||
}, [posts]);
|
}, [posts]);
|
||||||
@@ -1005,20 +990,12 @@ const MediaList: React.FC = () => {
|
|||||||
// Load available tags with colors
|
// Load available tags with colors
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const loadTags = async () => {
|
const loadTags = async () => {
|
||||||
const [tags, allTagsData] = await Promise.all([
|
const [tags, colorMap] = await Promise.all([
|
||||||
window.electronAPI?.media.getTags(),
|
window.electronAPI?.media.getTags(),
|
||||||
window.electronAPI?.tags?.getAll?.(),
|
loadTagColorMap(),
|
||||||
]);
|
]);
|
||||||
if (tags) setAvailableTags(tags as string[]);
|
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();
|
loadTags();
|
||||||
}, [media]);
|
}, [media]);
|
||||||
|
|||||||
@@ -3,3 +3,4 @@ export { getContrastColor } from './color';
|
|||||||
export { unescapeMacroSyntax } from './markdownEscape';
|
export { unescapeMacroSyntax } from './markdownEscape';
|
||||||
export { groupPostsByStatus, type GroupedPosts, type PostStatus } from './postGrouping';
|
export { groupPostsByStatus, type GroupedPosts, type PostStatus } from './postGrouping';
|
||||||
export { loadTabsForProject, saveTabsForProject } from './tabPersistence';
|
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