Skip to content

Commit c36f6cb

Browse files
committed
Moved entries and linting
1 parent 46ab9b4 commit c36f6cb

14 files changed

Lines changed: 254 additions & 234 deletions

.vscode/mcp.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
},
77
"my-development-projects": {
88
"command": "npx",
9-
"args": ["-y", "@modelcontextprotocol/server-filesystem", "X:\\dev\\*"]
9+
"args": ["-y", "@modelcontextprotocol/server-filesystem", "X:\\dev\\"]
1010
}
1111
}
1212
}

.vscode/saga-abilities.code-workspace

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,8 @@
1010
"typescript.tsdk": "./node_modules/typescript/lib",
1111
"typescript.enablePromptUseWorkspaceTsdk": true,
1212
"nxConsole.generateAiAgentRules": true,
13-
"nxConsole.nxWorkspacePath": ""
13+
"nxConsole.nxWorkspacePath": "",
14+
"cSpell.words": ["tabler"]
1415
},
1516
"tasks": {
1617
"version": "2.0.0",

src/components/AbilityCardWide.tsx

Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
import { Card, Text, Badge, Group, ActionIcon, Tooltip, Stack, Box, useMantineColorScheme, Grid } from '@mantine/core';
2+
import { IconEye, IconBooks, IconX, IconTags } from '@tabler/icons-react';
3+
import { Ability } from '../models/abilities.zod';
4+
import { useTheme } from '../context/ThemeContext';
5+
import { useAbilityTags, ProcessedAbilityTag } from '../hooks/useAbilityTags';
6+
import { useMemo } from 'react';
7+
8+
interface AbilityCardWideProps {
9+
readonly ability: Ability;
10+
readonly onViewDetails: (ability: Ability) => void;
11+
readonly onAddToAbilityManual?: (ability: Ability) => void;
12+
readonly onRemoveFromAbilityManual?: (abilityName: string) => void;
13+
readonly showRemoveButton?: boolean;
14+
}
15+
16+
export function AbilityCardWide({
17+
ability,
18+
onViewDetails,
19+
onAddToAbilityManual,
20+
onRemoveFromAbilityManual,
21+
showRemoveButton = false,
22+
}: AbilityCardWideProps) {
23+
const { colors } = useTheme();
24+
const { colorScheme } = useMantineColorScheme();
25+
const isDark = colorScheme === 'dark';
26+
const { data: abilityTagsData } = useAbilityTags();
27+
28+
// Find all tags that include this ability
29+
const abilityTags = useMemo(() => {
30+
if (!abilityTagsData?.tags) return [];
31+
32+
return abilityTagsData.tags
33+
.filter(tag => tag.abilities?.includes(ability.abilityName))
34+
.slice(0, 5); // We can show more tags in the wider layout
35+
}, [ability.abilityName, abilityTagsData?.tags]);
36+
37+
// Generate card styles based on theme
38+
const cardStyles = {
39+
borderRadius: `${colors.borderRadius}px`,
40+
fontSize: `${colors.fontScale}rem`,
41+
border: `1px solid ${isDark ? 'var(--mantine-color-dark-4)' : 'var(--mantine-color-gray-3)'}`,
42+
};
43+
44+
return (
45+
<Card
46+
withBorder
47+
shadow="sm"
48+
padding="md"
49+
radius="md"
50+
style={cardStyles}
51+
bg={isDark ? 'dark.6' : 'white'}
52+
>
53+
<Card.Section withBorder inheritPadding py="xs" bg={isDark ? 'dark.7' : colors.primaryColor}>
54+
<Group justify="space-between">
55+
<Text fw={700} c="white">{ability.abilityName}</Text>
56+
<Group gap={5}>
57+
<Tooltip label="View Details">
58+
<ActionIcon
59+
variant="subtle"
60+
color={isDark ? 'blue.4' : 'blue.6'}
61+
onClick={() => onViewDetails(ability)}
62+
>
63+
<IconEye size={16} />
64+
</ActionIcon>
65+
</Tooltip>
66+
67+
{onAddToAbilityManual && (
68+
<Tooltip label="Add to Ability Manual">
69+
<ActionIcon
70+
variant="subtle"
71+
color={isDark ? 'green.4' : 'green.6'}
72+
onClick={() => onAddToAbilityManual(ability)}
73+
>
74+
<IconBooks size={16} />
75+
</ActionIcon>
76+
</Tooltip>
77+
)}
78+
{showRemoveButton && onRemoveFromAbilityManual && (
79+
<Tooltip label="Remove from Ability Manual">
80+
<ActionIcon
81+
variant="subtle"
82+
color={isDark ? 'red.4' : 'red.6'}
83+
onClick={() => onRemoveFromAbilityManual(ability.abilityName)}
84+
>
85+
<IconX size={16} />
86+
</ActionIcon>
87+
</Tooltip>
88+
)}
89+
</Group>
90+
</Group>
91+
</Card.Section>
92+
93+
<Grid mt="md" gutter="xs">
94+
<Grid.Col span={{ base: 12, sm: 4 }}>
95+
<Stack gap="xs">
96+
<Group gap="xs">
97+
<Badge color={isDark ? 'blue.4' : 'blue.6'} size="sm">{ability.abilityLevel}</Badge>
98+
<Badge color={isDark ? 'teal.4' : 'teal.6'} size="sm">{ability.abilityDiscipline}</Badge>
99+
</Group>
100+
101+
<Group gap="md">
102+
<Text size="sm" fw={500} c={isDark ? 'gray.3' : 'dark.8'}>CP: {ability.abilityCp}</Text>
103+
<Text size="sm" fw={500} c={isDark ? 'gray.3' : 'dark.8'}>Type: {ability.abilityType}</Text>
104+
</Group>
105+
106+
{abilityTags.length > 0 && (
107+
<Group gap="xs">
108+
<IconTags size={14} color={isDark ? 'var(--mantine-color-gray-4)' : 'var(--mantine-color-gray-7)'} />
109+
{abilityTags.map((tag: ProcessedAbilityTag) => (
110+
<Badge
111+
key={tag.tag}
112+
color={isDark ? 'grape.4' : 'grape.6'}
113+
size="xs"
114+
variant="light"
115+
>
116+
{tag.name}
117+
</Badge>
118+
))}
119+
</Group>
120+
)}
121+
</Stack>
122+
</Grid.Col>
123+
124+
<Grid.Col span={{ base: 12, sm: 8 }}>
125+
<Box>
126+
<Text size="sm" fw={500} c={isDark ? 'gray.1' : 'dark.9'}>
127+
{ability.abilityDescription}
128+
</Text>
129+
</Box>
130+
</Grid.Col>
131+
</Grid>
132+
</Card>
133+
);
134+
}

src/components/Prefetcher.tsx

Lines changed: 4 additions & 118 deletions
Original file line numberDiff line numberDiff line change
@@ -1,124 +1,10 @@
1-
import { useEffect, useState } from 'react';
2-
import { useLocation } from 'react-router-dom';
3-
4-
// Define a map of routes to their respective page components
5-
type RouteImportMap = {
6-
[key: string]: () => Promise<any>;
7-
}
8-
91
/**
102
* Component for prefetching important data and resources for performance optimization
3+
* Note: This is a simplified implementation to fix linting issues
114
*/
12-
export function Prefetcher() {
13-
const location = useLocation();
14-
const [prefetchedRoutes, setPrefetchedRoutes] = useState<Set<string>>(new Set());
15-
16-
// Map routes to their respective lazy-loaded components
17-
const routeImportMap: RouteImportMap = {
18-
'/': () => import('../pages/AbilitiesPage'),
19-
'/AbilityManuals': () => import('../pages/AbilityManualsPage'),
20-
'/AbilityManuals/': () => import('../pages/AbilityManualDetailPage'),
21-
};
22-
23-
// Prefetch critical resources on initial render
24-
// useEffect(() => {
25-
// prefetchInitialResources();
26-
// }, [prefetchInitialResources]);
27-
28-
// Prefetch adjacent routes when location changes
29-
// useEffect(() => {
30-
// if (process.env.NODE_ENV === 'production') {
31-
// prefetchAdjacentRoutes();
32-
// }
33-
// }, [location.pathname, prefetchAdjacentRoutes]);
34-
35-
// Function to prefetch initial critical resources
36-
const prefetchInitialResources = async () => {
37-
try {
38-
// Prefetch ability data with priority hints
39-
fetch('/abilities.json', { priority: 'high' });
40-
41-
// Prefetch ability tags data
42-
fetch('/abilities.tags.json', { priority: 'high' });
43-
44-
// Prefetch critical images
45-
prefetchCriticalImages();
46-
47-
// Setup preconnect links
48-
setupPreconnect();
49-
50-
// When not in development, prefetch next probable routes
51-
if (process.env.NODE_ENV === 'production') {
52-
prefetchAdjacentRoutes();
53-
}
54-
} catch (error) {
55-
console.error('Error prefetching resources:', error);
56-
}
57-
};
58-
59-
// Function to prefetch critical images
60-
const prefetchCriticalImages = () => {
61-
const criticalImages = ['/assets/img/parchment1.png'];
62-
63-
criticalImages.forEach(src => {
64-
const img = new Image();
65-
img.src = src;
66-
});
67-
};
68-
69-
// Function to set up preconnect for external resources
70-
const setupPreconnect = () => {
71-
const domains = ['https://fonts.googleapis.com', 'https://fonts.gstatic.com'];
72-
73-
domains.forEach(url => {
74-
const link = document.createElement('link');
75-
link.rel = 'preconnect';
76-
link.href = url;
77-
link.crossOrigin = 'anonymous';
78-
document.head.appendChild(link);
79-
});
80-
};
81-
82-
// Function to prefetch adjacent routes based on current location
83-
const prefetchAdjacentRoutes = () => {
84-
const currentRoute = location.pathname;
85-
const routesToPrefetch: string[] = [];
86-
87-
if (currentRoute === '/') {
88-
routesToPrefetch.push('/AbilityManuals');
89-
} else if (currentRoute.startsWith('/AbilityManuals') && !currentRoute.includes('/')) {
90-
routesToPrefetch.push('/AbilityManuals/');
91-
} else if (currentRoute.includes('/AbilityManuals/')) {
92-
routesToPrefetch.push('/AbilityManuals');
93-
94-
// Prefetch PDF export on AbilityManual detail pages
95-
if (import.meta.env.PROD) {
96-
setTimeout(() => {
97-
import('../lib/pdfExport.enhanced').catch(console.error);
98-
}, 2000);
99-
}
100-
}
101-
102-
// Process routes to prefetch
103-
for (const route of routesToPrefetch) {
104-
if (!prefetchedRoutes.has(route) && routeImportMap[route]) {
105-
prefetchRoute(route);
106-
}
107-
}
108-
};
109-
110-
// Function to prefetch a single route
111-
const prefetchRoute = (route: string) => {
112-
setTimeout(() => {
113-
routeImportMap[route]()
114-
.then(() => {
115-
setPrefetchedRoutes(prev => new Set([...prev, route]));
116-
})
117-
.catch(error => {
118-
console.error(`Error prefetching route ${route}:`, error);
119-
});
120-
}, 1000);
121-
};
5+
export function Prefetcher(): null {
6+
// This component will be implemented in the future
7+
// Current implementation is a placeholder to fix linting issues
1228

1239
// This component doesn't render anything
12410
return null;

src/components/ThemeCustomizer.tsx

Lines changed: 20 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
import React, { useState } from 'react';
2-
import {
3-
Button,
4-
Stack,
5-
Title,
2+
import {
3+
Button,
4+
Stack,
5+
Title,
66
Group,
77
Paper,
88
Accordion,
@@ -31,8 +31,7 @@ export const ThemeCustomizer: React.FC<ThemeCustomizerProps> = ({ opened }) => {
3131
fontScale: colors.fontScale,
3232
imageEnabled: colors.imageEnabled
3333
});
34-
35-
const handleChange = (name: string, value: any) => {
34+
const handleChange = (name: string, value: unknown) => {
3635
setFormValues(prev => ({
3736
...prev,
3837
[name]: value,
@@ -55,23 +54,23 @@ export const ThemeCustomizer: React.FC<ThemeCustomizerProps> = ({ opened }) => {
5554
];
5655

5756
return (
58-
<Paper
59-
p="md"
60-
withBorder
61-
shadow="sm"
62-
style={{
63-
position: 'fixed',
64-
bottom: '20px',
65-
right: '20px',
66-
zIndex: 1000,
57+
<Paper
58+
p="md"
59+
withBorder
60+
shadow="sm"
61+
style={{
62+
position: 'fixed',
63+
bottom: '20px',
64+
right: '20px',
65+
zIndex: 1000,
6766
maxWidth: '400px',
6867
display: opened ? 'block' : 'none'
6968
}}
7069
>
7170
<Group justify="space-between" mb="md">
7271
<Title order={4}>Theme Settings</Title>
7372
<Group>
74-
<Switch
73+
<Switch
7574
checked={colorScheme === 'dark'}
7675
onChange={() => toggleColorScheme()}
7776
size="md"
@@ -89,14 +88,14 @@ export const ThemeCustomizer: React.FC<ThemeCustomizerProps> = ({ opened }) => {
8988
<Accordion.Item value="colors">
9089
<Accordion.Control>Colors</Accordion.Control>
9190
<Accordion.Panel>
92-
<Stack gap="xs">
91+
<Stack gap="xs">
9392
<Select
9493
label="Primary Color"
9594
value={formValues.primaryColor}
9695
onChange={(value) => handleChange('primaryColor', value ?? 'blue.6')}
9796
data={primaryColorOptions}
9897
/>
99-
98+
10099
<Select
101100
label="Accent Color"
102101
value={formValues.accentColor}
@@ -120,7 +119,7 @@ export const ThemeCustomizer: React.FC<ThemeCustomizerProps> = ({ opened }) => {
120119
]}
121120
fullWidth
122121
/>
123-
122+
124123
<div>
125124
<p>Border Radius</p>
126125
<Slider
@@ -137,7 +136,7 @@ export const ThemeCustomizer: React.FC<ThemeCustomizerProps> = ({ opened }) => {
137136
]}
138137
/>
139138
</div>
140-
139+
141140
<div>
142141
<p>Font Size</p>
143142
<Slider
@@ -158,7 +157,7 @@ export const ThemeCustomizer: React.FC<ThemeCustomizerProps> = ({ opened }) => {
158157
</Accordion.Panel>
159158
</Accordion.Item>
160159
</Accordion>
161-
160+
162161
<Button onClick={handleSave} mt="md">
163162
Apply Changes
164163
</Button>

0 commit comments

Comments
 (0)