Skip to content

Commit c34ab2b

Browse files
committed
feat: complete random wall brushing feature
- Add frontend RandomPaintModal component - Integrate random paint button in CalendarControls - Connect frontend with backend GenerateRandomContributions API - Support density, time range, and weekend exclusion parameters
1 parent 455abfa commit c34ab2b

File tree

7 files changed

+513
-1
lines changed

7 files changed

+513
-1
lines changed

app.go

Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import (
66
"encoding/json"
77
"fmt"
88
"io"
9+
"math/rand"
910
"net/http"
1011
"os"
1112
"os/exec"
@@ -57,6 +58,25 @@ type GenerateRepoRequest struct {
5758
RemoteRepo *RemoteRepoOptions `json:"remoteRepo,omitempty"`
5859
}
5960

61+
// RandomPaintRequest 随机刷墙请求参数
62+
type RandomPaintRequest struct {
63+
StartDate string `json:"startDate"` // 格式: "2024-01-01"
64+
EndDate string `json:"endDate"` // 格式: "2024-12-31"
65+
Density float64 `json:"density"` // 0.0-1.0,活跃天数比例
66+
MinPerDay int `json:"minPerDay"` // 每天最少提交数
67+
MaxPerDay int `json:"maxPerDay"` // 每天最多提交数
68+
ExcludeWeekend bool `json:"excludeWeekend"` // 是否排除周末
69+
RandomSeed int64 `json:"randomSeed"` // 随机种子,0表示使用时间戳
70+
}
71+
72+
// RandomPaintResponse 随机刷墙响应
73+
type RandomPaintResponse struct {
74+
Contributions []ContributionDay `json:"contributions"`
75+
TotalDays int `json:"totalDays"` // 总天数
76+
ActiveDays int `json:"activeDays"` // 活跃天数
77+
TotalCommits int `json:"totalCommits"` // 总提交数
78+
}
79+
6080
type GenerateRepoResponse struct {
6181
RepoPath string `json:"repoPath"`
6282
CommitCount int `json:"commitCount"`
@@ -477,6 +497,107 @@ func (a *App) ImportContributions() (*ImportContributionsResponse, error) {
477497
return &ImportContributionsResponse{Contributions: contributions}, nil
478498
}
479499

500+
// 在 ExportContributions 或 ImportContributions 函数之后添加
501+
502+
// GenerateRandomContributions 生成随机贡献数据
503+
func (a *App) GenerateRandomContributions(req RandomPaintRequest) (*RandomPaintResponse, error) {
504+
// 参数验证
505+
startDate, err := time.Parse("2006-01-02", req.StartDate)
506+
if err != nil {
507+
return nil, fmt.Errorf("invalid start date: %w", err)
508+
}
509+
510+
endDate, err := time.Parse("2006-01-02", req.EndDate)
511+
if err != nil {
512+
return nil, fmt.Errorf("invalid end date: %w", err)
513+
}
514+
515+
if startDate.After(endDate) {
516+
return nil, fmt.Errorf("start date must be before end date")
517+
}
518+
519+
if req.Density < 0 || req.Density > 1 {
520+
return nil, fmt.Errorf("density must be between 0 and 1")
521+
}
522+
523+
if req.MinPerDay < 0 {
524+
return nil, fmt.Errorf("minPerDay must be positive")
525+
}
526+
527+
if req.MaxPerDay < req.MinPerDay {
528+
return nil, fmt.Errorf("maxPerDay must be greater than or equal to minPerDay")
529+
}
530+
531+
// 初始化随机数生成器
532+
var seed int64
533+
if req.RandomSeed == 0 {
534+
seed = time.Now().UnixNano()
535+
} else {
536+
seed = req.RandomSeed
537+
}
538+
rng := rand.New(rand.NewSource(seed))
539+
540+
// 生成贡献数据
541+
contributions := []ContributionDay{}
542+
totalCommits := 0
543+
activeDays := 0
544+
545+
currentDate := startDate
546+
for !currentDate.After(endDate) {
547+
// 检查是否排除周末
548+
if req.ExcludeWeekend {
549+
weekday := currentDate.Weekday()
550+
if weekday == time.Saturday || weekday == time.Sunday {
551+
currentDate = currentDate.AddDate(0, 0, 1)
552+
continue
553+
}
554+
}
555+
556+
dateStr := currentDate.Format("2006-01-02")
557+
558+
// 根据密度决定今天是否有贡献
559+
if rng.Float64() <= req.Density {
560+
// 生成当天的提交次数
561+
var commitsToday int
562+
if req.MaxPerDay == req.MinPerDay {
563+
commitsToday = req.MinPerDay
564+
} else {
565+
commitsToday = req.MinPerDay + rng.Intn(req.MaxPerDay-req.MinPerDay+1)
566+
}
567+
568+
// 确保至少有一次提交
569+
if commitsToday < 1 {
570+
commitsToday = 1
571+
}
572+
573+
contributions = append(contributions, ContributionDay{
574+
Date: dateStr,
575+
Count: commitsToday,
576+
})
577+
578+
totalCommits += commitsToday
579+
activeDays++
580+
}
581+
582+
currentDate = currentDate.AddDate(0, 0, 1)
583+
}
584+
585+
// 按日期排序
586+
sort.Slice(contributions, func(i, j int) bool {
587+
return contributions[i].Date < contributions[j].Date
588+
})
589+
590+
// 计算总天数
591+
totalDays := int(endDate.Sub(startDate).Hours()/24) + 1
592+
593+
return &RandomPaintResponse{
594+
Contributions: contributions,
595+
TotalDays: totalDays,
596+
ActiveDays: activeDays,
597+
TotalCommits: totalCommits,
598+
}, nil
599+
}
600+
480601
func sanitiseRepoName(input string) string {
481602
input = strings.TrimSpace(input)
482603
if input == "" {

frontend/src/components/CalendarControls.tsx

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,8 @@ type Props = {
3030
// 画笔模式
3131
penMode?: 'manual' | 'auto';
3232
onPenModeChange?: (mode: 'manual' | 'auto') => void;
33+
// 新增:随机刷墙回调
34+
onOpenRandomModal?: () => void;
3335
};
3436

3537
export const CalendarControls: React.FC<Props> = ({
@@ -56,6 +58,8 @@ export const CalendarControls: React.FC<Props> = ({
5658
// 画笔模式
5759
penMode = 'manual',
5860
onPenModeChange,
61+
// 新增:随机刷墙回调
62+
onOpenRandomModal,
5963
}) => {
6064
const { t } = useTranslations();
6165
const [yearInput, setYearInput] = React.useState<string>(() =>
@@ -321,7 +325,7 @@ export const CalendarControls: React.FC<Props> = ({
321325
</div>
322326

323327
<div className="flex w-full flex-col gap-2 md:flex-1">
324-
<div className="grid grid-cols-1 gap-2 sm:grid-cols-2">
328+
<div className="grid grid-cols-1 gap-2 sm:grid-cols-3">
325329
<button
326330
type="button"
327331
onClick={onExportContributions}
@@ -338,6 +342,15 @@ export const CalendarControls: React.FC<Props> = ({
338342
>
339343
{t('buttons.import')}
340344
</button>
345+
{/* 新增:随机刷墙按钮 */}
346+
<button
347+
type="button"
348+
onClick={onOpenRandomModal}
349+
className="w-full rounded-none border border-purple-600 bg-purple-600 px-4 py-2 text-sm font-medium text-white transition-colors duration-200 hover:bg-purple-700"
350+
title="随机生成贡献数据"
351+
>
352+
🎲 随机刷墙
353+
</button>
341354
</div>
342355
</div>
343356
</div>

frontend/src/components/ContributionCalendar.tsx

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import clsx from 'clsx';
33
import styles from './ContributionCalendar.module.scss';
44
import { CalendarControls } from './CalendarControls';
55
import RemoteRepoModal, { RemoteRepoPayload } from './RemoteRepoModal';
6+
import RandomPaintModal from './RandomPaintModal'; // 新增导入
67
import { GenerateRepo, ExportContributions, ImportContributions } from '../../wailsjs/go/main/App';
78
import { main } from '../../wailsjs/go/models';
89
import { useTranslations } from '../i18n';
@@ -97,6 +98,7 @@ function ContributionCalendar({
9798
const [lastHoveredDate, setLastHoveredDate] = React.useState<string | null>(null);
9899
const [isGeneratingRepo, setIsGeneratingRepo] = React.useState<boolean>(false);
99100
const [isRemoteModalOpen, setIsRemoteModalOpen] = React.useState<boolean>(false);
101+
const [isRandomModalOpen, setIsRandomModalOpen] = React.useState<boolean>(false); // 新增:随机刷墙模态框状态
100102
const [isMaximized, setIsMaximized] = React.useState<boolean>(false);
101103
const containerRef = React.useRef<HTMLDivElement | null>(null);
102104
const [containerVars, setContainerVars] = React.useState<ContainerVars>({});
@@ -259,6 +261,41 @@ function ContributionCalendar({
259261
setPastePreviewDates(new Set());
260262
}, []);
261263

264+
// 打开随机刷墙模态框
265+
const handleOpenRandomModal = React.useCallback(() => {
266+
setIsRandomModalOpen(true);
267+
}, []);
268+
269+
// 关闭随机刷墙模态框
270+
const handleCloseRandomModal = React.useCallback(() => {
271+
setIsRandomModalOpen(false);
272+
}, []);
273+
274+
// 应用随机数据
275+
const handleApplyRandomContributions = React.useCallback(
276+
(newContributions: { date: string; count: number }[]) => {
277+
console.log('收到随机数据:', newContributions.length);
278+
279+
if (!newContributions || newContributions.length === 0) {
280+
alert('没有收到随机数据');
281+
return;
282+
}
283+
284+
const updatedMap = new Map(userContributions);
285+
286+
newContributions.forEach((day) => {
287+
if (day.date && day.count > 0) {
288+
updatedMap.set(day.date, day.count);
289+
}
290+
});
291+
292+
setUserContributions(updatedMap);
293+
alert('成功应用 ' + newContributions.length + ' 天随机数据');
294+
setIsRandomModalOpen(false);
295+
},
296+
[userContributions]
297+
);
298+
262299
// helper: convert date -> {row, col}
263300
const getDateCoord = React.useCallback(
264301
(dateStr: string) => {
@@ -1121,6 +1158,8 @@ function ContributionCalendar({
11211158
return next;
11221159
});
11231160
}}
1161+
// 随机刷墙相关 - 新增
1162+
onOpenRandomModal={handleOpenRandomModal}
11241163
/>
11251164
</aside>
11261165
<aside className="workbench__panel">
@@ -1138,6 +1177,14 @@ function ContributionCalendar({
11381177
onSubmit={handleRemoteModalSubmit}
11391178
/>
11401179
)}
1180+
{/* 随机刷墙模态框 - 新增 */}
1181+
{isRandomModalOpen && (
1182+
<RandomPaintModal
1183+
open={isRandomModalOpen}
1184+
onClose={handleCloseRandomModal}
1185+
onApply={handleApplyRandomContributions}
1186+
/>
1187+
)}
11411188
</div>
11421189
);
11431190
}

0 commit comments

Comments
 (0)