Skip to content

Commit 43dd4fb

Browse files
committed
feat: enhance theme customization and sidebar management in design studio
- Updated LanguagePieChartRenderer to accept custom colors for themes. - Modified LanguageController and StatsController to handle additional color options from query parameters. - Introduced StudioController for a new design studio interface. - Added a new sidebar management utility for responsive behavior. - Implemented a design studio page with customizable SVG card templates. - Enhanced theme management with the ability to save and delete custom themes. - Updated CSS styles for improved UI/UX. - Added TypeScript definitions for new properties in CardOptions and themes. - Updated tsconfig to include DOM library for better type support.
1 parent 027dbed commit 43dd4fb

15 files changed

Lines changed: 941 additions & 11 deletions

File tree

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
"build:css": "npx @tailwindcss/cli -i ./src/styles/app.css -o ./dist/css/app.css --minify",
1010
"copy:view": "node -e \"require('fs').cpSync('src/views', 'dist/views', {recursive: true})\"",
1111
"build": "tsc && npm run copy:view && npm run build:css",
12-
"dev": "node --watch --no-warnings=ExperimentalWarning --loader ts-node/esm ./src/index.ts | npm run css",
12+
"dev": "node --watch --no-warnings=ExperimentalWarning --loader ts-node/esm ./src/index.ts | npm run css | tsc --watch ",
1313
"start": "node dist/index.js",
1414
"test": "jest"
1515
},

src/components/card-renderer.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,12 @@ export class CardRenderer {
5252
}
5353

5454
static generateStatsCard(stats: GitHubStats, options: CardOptions): string {
55-
const theme = getTheme(options.theme);
55+
const theme = getTheme(options.theme, {
56+
bgColor: options.bgColor,
57+
borderColor: options.borderColor,
58+
textColor: options.textColor,
59+
titleColor: options.titleColor,
60+
});
5661
const fontName = theme.fontName || 'Orbitron';
5762
const fontFamily = theme.fontFamily || `'${fontName}', 'Ubuntu', 'sans-serif'`;
5863
const fontUrl = theme.fontUrl || '/fonts/orbitron.woff2';

src/components/language-card.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,12 @@ import { getTheme } from '../utils/themes.js';
33

44
export class LanguageCardRenderer {
55
static generateLanguagesCard(languages: LanguageCount[], options: CardOptions): string {
6-
const theme = getTheme(options.theme);
6+
const theme = getTheme(options.theme, {
7+
bgColor: options.bgColor,
8+
borderColor: options.borderColor,
9+
textColor: options.textColor,
10+
titleColor: options.titleColor,
11+
});
712
const dataBorderStyle = options.dataBorderStyle || 'solid';
813
const dataBorderFramePosition = options.dataBorderFramePosition || 'out';
914
const showDataBorderStroke = dataBorderStyle === 'solid';

src/components/language-pie-chart.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,12 @@ import { getTheme } from '../utils/themes.js';
33

44
export class LanguagePieChartRenderer {
55
static generatePieChart(languages: LanguageCount[], options: CardOptions): string {
6-
const theme = getTheme(options.theme);
6+
const theme = getTheme(options.theme, {
7+
bgColor: options.bgColor,
8+
borderColor: options.borderColor,
9+
textColor: options.textColor,
10+
titleColor: options.titleColor,
11+
});
712
const fontName = theme.fontName || 'Orbitron';
813
const fontFamily = theme.fontFamily || `'${fontName}', 'Ubuntu', 'sans-serif'`;
914
const fontUrl = theme.fontUrl || '/fonts/orbitron.woff2';

src/controllers/languages.ts

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -68,13 +68,13 @@ export class LanguageController extends Controller {
6868

6969
static async getSvg(req: Request, res: Response) {
7070
try {
71-
const { username, theme = 'default', show_info, top, variant, type = 'card', format } = req.query;
71+
const { username, theme = 'default', show_info, top, variant, type = 'card', bgColor, borderColor, textColor, titleColor, format } = req.query;
7272

7373
if (!username || typeof username !== 'string') {
7474
return res.status(400).send('Username is required');
7575
}
7676

77-
const cacheKey = `languages-${username}-${theme}-${show_info}-${top}-${variant}-${type}`;
77+
const cacheKey = `languages-${username}-${theme}-${show_info}-${top}-${variant}-${type}-${bgColor || ''}-${borderColor || ''}-${textColor || ''}-${titleColor || ''}`;
7878
const cached = LanguageController.cache.get(cacheKey);
7979
if (cached && Date.now() - cached.timestamp < LanguageController.CACHE_DURATION) {
8080
res.setHeader('Content-Type', 'image/svg+xml');
@@ -90,6 +90,10 @@ export class LanguageController extends Controller {
9090
username,
9191
theme: theme as string,
9292
listLength: typeof top === 'string' ? Math.max(0, Number.parseInt(top, 10) || 8) : 8,
93+
bgColor: bgColor as string | undefined,
94+
borderColor: borderColor as string | undefined,
95+
textColor: textColor as string | undefined,
96+
titleColor: titleColor as string | undefined,
9397
});
9498
} else {
9599
svg = LanguageCardRenderer.generateLanguagesCard(languages, {
@@ -98,6 +102,10 @@ export class LanguageController extends Controller {
98102
showInfo: show_info !== 'false',
99103
listLength: typeof top === 'string' ? Math.max(0, Number.parseInt(top, 10) || 5) : 5,
100104
variant: variant as 'bubbles' | 'pie' | undefined,
105+
bgColor: bgColor as string | undefined,
106+
borderColor: borderColor as string | undefined,
107+
textColor: textColor as string | undefined,
108+
titleColor: titleColor as string | undefined,
101109
});
102110
}
103111

src/controllers/stats.ts

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,10 @@ export class StatsController extends Controller {
8888
custom_title,
8989
data_border_style = 'solid',
9090
data_border_frame = 'out',
91+
bgColor,
92+
borderColor,
93+
textColor,
94+
titleColor,
9195
format
9296
} = req.query;
9397

@@ -117,7 +121,11 @@ export class StatsController extends Controller {
117121
finalAvatarMode,
118122
custom_title || '',
119123
data_border_style || 'solid',
120-
data_border_frame || 'out'
124+
data_border_frame || 'out',
125+
bgColor || '',
126+
borderColor || '',
127+
textColor || '',
128+
titleColor || ''
121129
].join('|');
122130

123131
const getSvgCard = async () => {
@@ -146,6 +154,10 @@ export class StatsController extends Controller {
146154
customTitle: custom_title as string | undefined,
147155
dataBorderStyle: data_border_style as 'solid' | 'frame',
148156
dataBorderFramePosition: data_border_frame as 'in' | 'out',
157+
bgColor: bgColor as string | undefined,
158+
borderColor: borderColor as string | undefined,
159+
textColor: textColor as string | undefined,
160+
titleColor: titleColor as string | undefined,
149161
});
150162

151163
StatsController.cache.set(cacheKey, { data: card, timestamp: Date.now() });

src/controllers/studio.ts

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import { Request, Response } from 'express';
2+
import { themes } from '../utils/themes.js';
3+
import { Controller } from './controller.js';
4+
5+
export class StudioController extends Controller {
6+
static get(req: Request, response: Response) {
7+
const PORT = process.env.PORT || 3000;
8+
const APP_ENV = process.env.APP_ENV || 'development';
9+
const PROTOCOL = APP_ENV === 'production' ? 'https' : 'http';
10+
const host = req.get('host');
11+
const fullUrl = `${PROTOCOL}://${host}/studio`;
12+
13+
const payloads = {
14+
...Controller.defaultConfig,
15+
page: 'studio',
16+
port: PORT,
17+
fullUrl,
18+
themes: JSON.stringify(themes)
19+
};
20+
response.render('layouts/main', payloads);
21+
}
22+
}

src/index.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import { GitHubClient } from './utils/github-client.js';
55
import { HomeController } from './controllers/home.js';
66
import { StatsController } from './controllers/stats.js';
77
import { LanguageController } from './controllers/languages.js';
8+
import { StudioController } from './controllers/studio.js';
89
import path from 'path';
910
import { fileURLToPath } from 'url';
1011

@@ -18,6 +19,10 @@ app.use(cors());
1819
// Set up EJS as the view engine
1920
app.set('view engine', 'ejs');
2021
app.set('views', path.join(__dirname, 'views'));
22+
app.use(express.static('dist'));
23+
app.use(express.static(publicDir));
24+
app.use('/public', express.static(publicDir));
25+
app.use('/js', express.static("dist/views/pages"));
2126

2227
const PORT = process.env.PORT || 3000;
2328
const APP_ENV = process.env.APP_ENV || 'development';
@@ -50,11 +55,12 @@ app.get('/stats', StatsController.getSvg);
5055
app.get('/languages', LanguageController.getSvg);
5156

5257

53-
app.get('/view/stats', StatsController.get);
58+
// app.get('/view/stats', StatsController.get);
5459
// app.get('/preview/languages', LanguageController.get);
5560

5661
// UI
5762
app.get('/', HomeController.get);
63+
app.get('/studio', StudioController.get);
5864

5965
app.listen(PORT, () => {
6066
console.log(`🚀 GitHub Stats server running on ${PROTOCOL}://localhost:${PORT}`);

src/styles/app.css

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,4 +29,58 @@
2929
--card: 217 33% 17%;
3030
--primary: 150 79% 38%;
3131
}
32+
}
33+
34+
* {
35+
scrollbar-width: thin;
36+
scrollbar-color: #166392 transparent;
37+
}
38+
39+
::-webkit-scrollbar {
40+
width: 1px;
41+
}
42+
43+
::-webkit-scrollbar-track {
44+
background: transparent;
45+
}
46+
47+
::-webkit-scrollbar-thumb {
48+
background-color: transparent;
49+
border-radius: 20px;
50+
}
51+
52+
.button {
53+
@apply px-3 cursor-pointer py-2 transition-all text-xs bg-slate-800/50 hover:bg-slate-700 border border-slate-700/50 text-white rounded-lg ease-in-out flex items-center gap-1.5;
54+
}
55+
56+
.input {
57+
@apply flex-1 px-3 py-2 w-full outline-none text-xs rounded-lg bg-slate-800/50 border border-slate-700/50 text-white font-mono focus:border-primary/50 focus:ring-2 focus:ring-primary/20 transition-all;
58+
}
59+
60+
[data-load-template].active {
61+
@apply bg-primary/20 border-primary/50;
62+
}
63+
64+
.sidebar {
65+
@apply flex h-[calc(100vh-8rem)] w-60 shrink-0 flex-col space-y-4 transition-all ease-in-out duration-300;
66+
}
67+
68+
[data-content] {
69+
@apply w-full bg-card-dark/60 transition-all ease-in-out p-1 backdrop-blur-sm rounded-2xl border border-slate-800/50;
70+
}
71+
72+
.collapsed {
73+
@apply w-0;
74+
}
75+
76+
/* Responsive for screens smaller than 64rem (1024px) */
77+
@media (max-width: 63.9375rem) {
78+
.sidebar {
79+
@apply fixed h-[calc(100vh-8rem)] w-60 top-21.25 z-1 bg-transparent rounded-none border-none;
80+
}
81+
82+
83+
#rightSidebar {
84+
@apply right-0;
85+
}
3286
}

src/types.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,4 +43,8 @@ export interface CardOptions {
4343
avatarMode?: 'none' | 'avatar' | 'radar';
4444
dataBorderStyle?: 'solid' | 'frame';
4545
dataBorderFramePosition?: 'in' | 'out';
46+
bgColor?: string;
47+
borderColor?: string;
48+
textColor?: string;
49+
titleColor?: string;
4650
}

0 commit comments

Comments
 (0)