Skip to content

Commit 55eff62

Browse files
committed
feat: Add playground to navbar
1 parent d99e008 commit 55eff62

File tree

9 files changed

+233
-80
lines changed

9 files changed

+233
-80
lines changed

public/locales/en/components/navigationbar.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
"documentation": "Documentation",
44
"plugins": "Plugins",
55
"themes": "Themes",
6+
"playground": "Playground",
67
"theme_builder": "Theme Builder",
78
"about_us": "About Us",
89
"about_us.our_team": "Our Team",

public/locales/zh/components/navigationbar.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
"documentation": "文档",
44
"plugins": "插件",
55
"themes": "主题",
6+
"playground": "测试",
67
"theme_builder": "主题构建器",
78
"about_us": "关于我们",
89
"about_us.our_team": "我们的团队",

src/components/NavigationBar/NavigationBar.tsx

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,10 @@
1-
import { DescriptionOutlined, ExtensionOutlined, InfoOutlined, PaletteOutlined } from '@mui/icons-material';
1+
import {
2+
DescriptionOutlined,
3+
ExtensionOutlined,
4+
InfoOutlined,
5+
PaletteOutlined,
6+
PsychologyOutlined,
7+
} from '@mui/icons-material';
28
import AccountCircleIcon from '@mui/icons-material/AccountCircle';
39
import CloseIcon from '@mui/icons-material/Close';
410
// import CodeIcon from '@mui/icons-material/Code';
@@ -167,6 +173,9 @@ const NavigationBar: React.FC<{
167173
<Button component={Link} to="/themes" sx={generalNavLinkSx}>
168174
{t('navigation_bar.themes')}
169175
</Button>
176+
<Button component={Link} to="/playground" sx={generalNavLinkSx}>
177+
{t('navigation_bar.playground')}
178+
</Button>
170179
<Box>
171180
<Button
172181
onClick={(event) => setAboutMenuAnchor((prev) => (prev ? null : event.currentTarget))}
@@ -435,6 +444,10 @@ const NavigationBar: React.FC<{
435444
<PaletteOutlined sx={{ mr: 1 }} />
436445
<ListItemText primary={t('navigation_bar.themes')} />
437446
</ListItem>
447+
<ListItem component={Link} to="/playground" sx={generalNavLinkSx}>
448+
<PsychologyOutlined sx={{ mr: 1 }} />
449+
<ListItemText primary={t('navigation_bar.playground')} />
450+
</ListItem>
438451
{/* <ListItem component={Link} to="/theme-builder" sx={generalNavLinkSx}>
439452
<CodeIcon sx={{ mr: 1 }} />
440453
<ListItemText primary={t('navigation_bar.theme_builder')} />

src/interfaces/Plugin.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,4 +15,5 @@ export type Plugin = {
1515
userId: string;
1616
github: string;
1717
packageUrl: string;
18+
isDataMissing?: boolean;
1819
};

src/interfaces/Theme.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,4 +19,5 @@ export type Theme = {
1919
tags: string[];
2020
github: string;
2121
content: ThemeContent;
22+
isDataMissing?: boolean;
2223
};

src/services/plugins/apiService.ts

Lines changed: 30 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import { galleryApiFetch } from '@/utils';
77
import { resetPluginsCache } from '../plugins/cacheService';
88
import { getNpmPluginData } from './npmService';
99

10-
const fetchPluginsFromApi = async (url: string): Promise<Plugin[]> => {
10+
const fetchPluginsFromApi = async (url: string): Promise<(Plugin | Partial<Plugin>)[]> => {
1111
try {
1212
let apiPlugins = null;
1313

@@ -35,9 +35,35 @@ const fetchPluginsFromApi = async (url: string): Promise<Plugin[]> => {
3535
/**
3636
* Fetches plugins information from npm.
3737
*/
38-
const fetchPluginsFromNpm = async (apiPlugins: ApiPlugin[]): Promise<Plugin[]> => {
39-
// todo: good to cache themes already fetched to reduce calls to cdn
40-
return await Promise.all(apiPlugins.map((apiPlugin) => getNpmPluginData(apiPlugin)));
38+
const fetchPluginsFromNpm = async (apiPlugins: ApiPlugin[]): Promise<(Plugin | Partial<Plugin>)[]> => {
39+
const settledResults = await Promise.allSettled(apiPlugins.map((apiPlugin) => getNpmPluginData(apiPlugin)));
40+
41+
return settledResults.map((result, index) => {
42+
const apiPlugin = apiPlugins[index]; // Get the original apiPlugin for fallback data
43+
44+
if (result.status === 'fulfilled') {
45+
return result.value;
46+
} else {
47+
// Handle rejected promise from getNpmPluginData
48+
// This case should be rare if getNpmPluginData's try/catch is effective
49+
console.error(`Error processing plugin ${apiPlugin.id} after NPM fetch attempt:`, result.reason);
50+
return {
51+
id: apiPlugin.id,
52+
name: apiPlugin.name || apiPlugin.id || 'Plugin Name Unavailable',
53+
authorName: 'N/A',
54+
description: 'Plugin details are currently unavailable due to an unexpected error.',
55+
version: 'N/A',
56+
isDataMissing: true,
57+
favoritesCount: apiPlugin.favoritesCount,
58+
isFavorite: apiPlugin.isFavorite ?? false,
59+
userId: apiPlugin.userId,
60+
createdAt: apiPlugin.createdAt,
61+
updatedAt: apiPlugin.updatedAt,
62+
keywords: [],
63+
packageUrl: apiPlugin.packageUrl,
64+
};
65+
}
66+
});
4167
};
4268

4369
const addPluginToFavorites = async (plugin: Plugin) => {

src/services/plugins/npmService.ts

Lines changed: 66 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { ApiPlugin } from '@/interfaces/ApiPlugin';
2+
import { Plugin } from '@/interfaces/Plugin';
23

34
import { Endpoints } from '../../constants/Endpoints';
45

@@ -7,36 +8,75 @@ import { Endpoints } from '../../constants/Endpoints';
78
*
89
* @param themeId id of theme to fetch data for
910
*/
10-
const getNpmPluginData = async (plugin: ApiPlugin) => {
11+
const getNpmPluginData = async (apiPlugin: ApiPlugin): Promise<Plugin | Partial<Plugin>> => {
1112
// check if plugin is valid
12-
if (!plugin.id || typeof plugin.id !== 'string') {
13-
throw new Error('Invalid plugin ID provided');
13+
if (!apiPlugin.id || typeof apiPlugin.id !== 'string') {
14+
// This case should ideally not happen if data is validated upstream
15+
// but as a safeguard:
16+
console.error('Invalid plugin ID provided to getNpmPluginData:', apiPlugin);
17+
return {
18+
id: apiPlugin.id || 'unknown-id',
19+
name: apiPlugin.name || apiPlugin.id || 'Plugin Name Unavailable',
20+
authorName: 'N/A',
21+
description: apiPlugin.description || 'No description available.',
22+
version: 'N/A',
23+
isDataMissing: true,
24+
favoritesCount: apiPlugin.favoritesCount,
25+
isFavorite: apiPlugin.isFavorite ?? false,
26+
userId: apiPlugin.userId,
27+
createdAt: apiPlugin.createdAt,
28+
updatedAt: apiPlugin.updatedAt,
29+
keywords: [],
30+
packageUrl: apiPlugin.packageUrl,
31+
};
1432
}
1533

16-
// fetch package data from npm registry
17-
const response = await fetch(`${Endpoints.fetchNpmPlugins}/${encodeURIComponent(plugin.id)}/latest`);
18-
const pluginData = await response.json();
34+
try {
35+
// fetch package data from npm registry
36+
const response = await fetch(`${Endpoints.fetchNpmPlugins}/${encodeURIComponent(apiPlugin.id)}/latest`);
37+
if (!response.ok) {
38+
throw new Error(`NPM API request failed with status ${response.status}`);
39+
}
40+
const pluginData = await response.json();
41+
const authorImg = `https://avatars.githubusercontent.com/${pluginData.author.name}`;
42+
const imageUrl = pluginData.pluginLogo || null; // Assuming pluginLogo can be null/undefined
1943

20-
const authorImg = `https://avatars.githubusercontent.com/${pluginData.author.name}`;
21-
const imageUrl = pluginData.pluginLogo || '';
22-
23-
return {
24-
authorImg,
25-
authorName: pluginData.author.name,
26-
createdAt: plugin.createdAt,
27-
description: pluginData.description,
28-
favoritesCount: plugin.favoritesCount,
29-
github: pluginData.repository,
30-
id: plugin.id,
31-
imageUrl,
32-
isFavorite: plugin.isFavorite ?? false,
33-
keywords: pluginData.keywords,
34-
name: pluginData.name,
35-
packageUrl: plugin.packageUrl,
36-
updatedAt: plugin.updatedAt,
37-
userId: plugin.userId,
38-
version: pluginData.version,
39-
};
44+
return {
45+
id: apiPlugin.id,
46+
name: pluginData.name || apiPlugin.name || apiPlugin.id, // Use fetched name, fallback to apiPlugin name then id
47+
authorName: pluginData.author?.name || 'N/A',
48+
description: pluginData.description || 'No description available.',
49+
version: pluginData.version || 'N/A',
50+
isDataMissing: false,
51+
favoritesCount: apiPlugin.favoritesCount,
52+
isFavorite: apiPlugin.isFavorite ?? false,
53+
userId: apiPlugin.userId,
54+
createdAt: apiPlugin.createdAt,
55+
updatedAt: apiPlugin.updatedAt,
56+
imageUrl,
57+
authorImg,
58+
github: pluginData.repository?.url || pluginData.repository || null, // Handle repository being object or string
59+
keywords: pluginData.keywords || [],
60+
packageUrl: apiPlugin.packageUrl,
61+
};
62+
} catch (error) {
63+
console.error(`Error fetching plugin data from NPM for ${apiPlugin.id}:`, error);
64+
return {
65+
id: apiPlugin.id,
66+
name: apiPlugin.name || apiPlugin.id || 'Plugin Name Unavailable',
67+
authorName: 'N/A',
68+
description: 'Plugin details are currently unavailable due to an external service issue.',
69+
version: 'N/A',
70+
isDataMissing: true,
71+
favoritesCount: apiPlugin.favoritesCount,
72+
isFavorite: apiPlugin.isFavorite ?? false,
73+
userId: apiPlugin.userId,
74+
createdAt: apiPlugin.createdAt,
75+
updatedAt: apiPlugin.updatedAt,
76+
keywords: [],
77+
packageUrl: apiPlugin.packageUrl,
78+
};
79+
}
4080
};
4181

4282
export { getNpmPluginData };

src/services/themes/apiService.ts

Lines changed: 36 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import { galleryApiFetch } from '@/utils';
77
import { resetThemesCache } from '../themes/cacheService';
88
import { getGitHubThemeData } from '../themes/gitHubService';
99

10-
const fetchThemesFromApi = async (url: string): Promise<Theme[]> => {
10+
const fetchThemesFromApi = async (url: string): Promise<(Theme | Partial<Theme>)[]> => {
1111
try {
1212
let apiThemes = null;
1313

@@ -35,9 +35,41 @@ const fetchThemesFromApi = async (url: string): Promise<Theme[]> => {
3535
/**
3636
* Fetches themes information from github (or more accurately, jsdelivr cache).
3737
*/
38-
const fetchThemesFromGitHub = async (apiThemes: ApiTheme[]): Promise<Theme[]> => {
39-
// todo: good to cache themes already fetched to reduce calls to cdn
40-
return await Promise.all(apiThemes.map((apiTheme) => getGitHubThemeData(apiTheme)));
38+
const fetchThemesFromGitHub = async (apiThemes: ApiTheme[]): Promise<(Theme | Partial<Theme>)[]> => {
39+
const settledResults = await Promise.allSettled(apiThemes.map((apiTheme) => getGitHubThemeData(apiTheme)));
40+
41+
return settledResults.map((result, index) => {
42+
const apiTheme = apiThemes[index]; // Get the original apiTheme for fallback data
43+
44+
if (result.status === 'fulfilled') {
45+
return result.value;
46+
} else {
47+
// Handle rejected promise from getGitHubThemeData
48+
// This case should be rare if getGitHubThemeData's try/catch is effective
49+
console.error(`Error processing theme ${apiTheme.id} after GitHub fetch attempt:`, result.reason);
50+
// Construct a partial theme, similar to how getGitHubThemeData would in its catch block
51+
return {
52+
id: apiTheme.id,
53+
name: apiTheme.name || apiTheme.id || 'Theme Name Unavailable',
54+
authorName: 'N/A',
55+
description: 'Theme details are currently unavailable due to an unexpected error.',
56+
version: 'N/A',
57+
isDataMissing: true,
58+
favoritesCount: apiTheme.favoritesCount || 0,
59+
isFavorite: apiTheme.isFavorite ?? false,
60+
github: 'N/A',
61+
tags: [],
62+
content: {
63+
cssStyles: '',
64+
inlineStyles: '',
65+
settings: '',
66+
},
67+
// Include other fields from apiTheme if necessary and available
68+
createdAt: apiTheme.createdAt,
69+
updatedAt: apiTheme.updatedAt,
70+
};
71+
}
72+
});
4173
};
4274

4375
const addThemeToFavorites = async (theme: Theme) => {

0 commit comments

Comments
 (0)