@shaxpir/duiduidui-models 1.7.3 → 1.7.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/models/Session.js +9 -0
- package/dist/models/Streaks.d.ts +14 -0
- package/dist/models/Streaks.js +165 -0
- package/dist/models/index.d.ts +1 -0
- package/dist/models/index.js +1 -0
- package/package.json +1 -1
package/dist/models/Session.js
CHANGED
|
@@ -9,6 +9,7 @@ const ContentKind_1 = require("./ContentKind");
|
|
|
9
9
|
const Metric_1 = require("./Metric");
|
|
10
10
|
const Operation_1 = require("./Operation");
|
|
11
11
|
const Progress_1 = require("./Progress");
|
|
12
|
+
const Streaks_1 = require("./Streaks");
|
|
12
13
|
const Workspace_1 = require("./Workspace");
|
|
13
14
|
class Session extends Content_1.Content {
|
|
14
15
|
constructor(doc, shouldAcquire, shareSync) {
|
|
@@ -209,6 +210,14 @@ class Session extends Content_1.Content {
|
|
|
209
210
|
const progressId = Progress_1.Progress.makeProgressId(userId);
|
|
210
211
|
const progress = await shareSync.acquire(ContentKind_1.ContentKind.PROGRESS, progressId);
|
|
211
212
|
progress.setTotalReviewCount(totalReviewCount);
|
|
213
|
+
// Calculate streaks based on metrics
|
|
214
|
+
const now = shaxpir_common_1.ClockService.getClock().now();
|
|
215
|
+
const today = shaxpir_common_1.Time.dateFrom(now.local_time);
|
|
216
|
+
// Use the Streaks class to calculate streak data
|
|
217
|
+
const streakData = Streaks_1.Streaks.calculateFromMetric(minutesStudyingMetric, today);
|
|
218
|
+
if (streakData) {
|
|
219
|
+
progress.setStreaks(streakData);
|
|
220
|
+
}
|
|
212
221
|
progress.release();
|
|
213
222
|
}
|
|
214
223
|
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { CompactDate } from "@shaxpir/shaxpir-common";
|
|
2
|
+
import { Metric } from './Metric';
|
|
3
|
+
import { StreakData } from './Progress';
|
|
4
|
+
export declare class Streaks {
|
|
5
|
+
/**
|
|
6
|
+
* Calculate streak data from a Metric containing activity dates.
|
|
7
|
+
* Returns daily, weekly, and monthly streak information.
|
|
8
|
+
*
|
|
9
|
+
* @param metric - The Metric model containing activity data
|
|
10
|
+
* @param today - The current date for streak calculation
|
|
11
|
+
* @returns StreakData object with streak information, or null if no activity
|
|
12
|
+
*/
|
|
13
|
+
static calculateFromMetric(metric: Metric, today: CompactDate): StreakData | null;
|
|
14
|
+
}
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.Streaks = void 0;
|
|
4
|
+
const shaxpir_common_1 = require("@shaxpir/shaxpir-common");
|
|
5
|
+
class Streaks {
|
|
6
|
+
/**
|
|
7
|
+
* Calculate streak data from a Metric containing activity dates.
|
|
8
|
+
* Returns daily, weekly, and monthly streak information.
|
|
9
|
+
*
|
|
10
|
+
* @param metric - The Metric model containing activity data
|
|
11
|
+
* @param today - The current date for streak calculation
|
|
12
|
+
* @returns StreakData object with streak information, or null if no activity
|
|
13
|
+
*/
|
|
14
|
+
static calculateFromMetric(metric, today) {
|
|
15
|
+
const minDate = metric.minDate;
|
|
16
|
+
const maxDate = metric.maxDate;
|
|
17
|
+
if (!minDate || !maxDate) {
|
|
18
|
+
return null;
|
|
19
|
+
}
|
|
20
|
+
// Get all dates between min and max using Metric's datesBetween
|
|
21
|
+
const allDates = shaxpir_common_1.Time.datesBetween(minDate, maxDate);
|
|
22
|
+
const activityDates = [];
|
|
23
|
+
// Collect dates with actual activity
|
|
24
|
+
for (const date of allDates) {
|
|
25
|
+
if (metric.forDate(date) > 0) {
|
|
26
|
+
activityDates.push(date);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
if (activityDates.length === 0) {
|
|
30
|
+
return null;
|
|
31
|
+
}
|
|
32
|
+
// Calculate daily streak
|
|
33
|
+
let dailyCurrent = 0;
|
|
34
|
+
let dailyLongest = 0;
|
|
35
|
+
let tempStreak = 0;
|
|
36
|
+
let lastDate = null;
|
|
37
|
+
for (const date of activityDates) {
|
|
38
|
+
if (!lastDate) {
|
|
39
|
+
tempStreak = 1;
|
|
40
|
+
}
|
|
41
|
+
else {
|
|
42
|
+
// Calculate days between dates
|
|
43
|
+
const datesBetween = shaxpir_common_1.Time.datesBetween(lastDate, date);
|
|
44
|
+
const daysBetween = datesBetween.length - 1; // Subtract 1 because datesBetween includes both dates
|
|
45
|
+
if (daysBetween === 1) {
|
|
46
|
+
tempStreak++;
|
|
47
|
+
}
|
|
48
|
+
else {
|
|
49
|
+
// Streak broken
|
|
50
|
+
dailyLongest = Math.max(dailyLongest, tempStreak);
|
|
51
|
+
tempStreak = 1;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
lastDate = date;
|
|
55
|
+
}
|
|
56
|
+
// Check if current streak is still active (includes today or yesterday)
|
|
57
|
+
if (lastDate) {
|
|
58
|
+
const datesSinceLastActivity = shaxpir_common_1.Time.datesBetween(lastDate, today);
|
|
59
|
+
const daysSinceLastActivity = datesSinceLastActivity.length - 1;
|
|
60
|
+
if (daysSinceLastActivity <= 1) {
|
|
61
|
+
dailyCurrent = tempStreak;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
dailyLongest = Math.max(dailyLongest, tempStreak);
|
|
65
|
+
// Calculate weekly streak (at least one activity per week)
|
|
66
|
+
let weeklyCurrent = 0;
|
|
67
|
+
let weeklyStreak = 0;
|
|
68
|
+
// Check for weekly continuity - if there's activity within each 7-day window
|
|
69
|
+
for (let i = activityDates.length - 1; i >= 0; i--) {
|
|
70
|
+
const currentDate = activityDates[i];
|
|
71
|
+
// For the most recent date, start counting
|
|
72
|
+
if (i === activityDates.length - 1) {
|
|
73
|
+
// Check if this was within the last 7 days
|
|
74
|
+
const daysFromToday = shaxpir_common_1.Time.datesBetween(currentDate, today).length - 1;
|
|
75
|
+
if (daysFromToday <= 7) {
|
|
76
|
+
weeklyStreak = 1;
|
|
77
|
+
weeklyCurrent = 1;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
else {
|
|
81
|
+
// Check if there's a gap of more than 7 days
|
|
82
|
+
const nextDate = activityDates[i + 1];
|
|
83
|
+
const daysBetween = shaxpir_common_1.Time.datesBetween(currentDate, nextDate).length - 1;
|
|
84
|
+
if (daysBetween <= 7) {
|
|
85
|
+
weeklyStreak++;
|
|
86
|
+
if (weeklyCurrent > 0) {
|
|
87
|
+
weeklyCurrent = weeklyStreak;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
else {
|
|
91
|
+
// Weekly streak broken
|
|
92
|
+
break;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
// Calculate monthly streak
|
|
97
|
+
// Extract YYYY-MM from CompactDate (format: YYYY-MM-DD)
|
|
98
|
+
const monthlyActivity = new Set();
|
|
99
|
+
for (const date of activityDates) {
|
|
100
|
+
const month = date.substring(0, 7); // Extract YYYY-MM
|
|
101
|
+
monthlyActivity.add(month);
|
|
102
|
+
}
|
|
103
|
+
const sortedMonths = Array.from(monthlyActivity).sort();
|
|
104
|
+
let monthlyCurrent = 0;
|
|
105
|
+
let monthlyStreak = 0;
|
|
106
|
+
let lastMonth = null;
|
|
107
|
+
for (const month of sortedMonths) {
|
|
108
|
+
if (!lastMonth) {
|
|
109
|
+
monthlyStreak = 1;
|
|
110
|
+
}
|
|
111
|
+
else {
|
|
112
|
+
// Check if consecutive month
|
|
113
|
+
const [lastYear, lastMonthNum] = lastMonth.split('-').map(Number);
|
|
114
|
+
const [currentYear, currentMonthNum] = month.split('-').map(Number);
|
|
115
|
+
if ((currentYear === lastYear && currentMonthNum === lastMonthNum + 1) ||
|
|
116
|
+
(currentYear === lastYear + 1 && lastMonthNum === 12 && currentMonthNum === 1)) {
|
|
117
|
+
monthlyStreak++;
|
|
118
|
+
}
|
|
119
|
+
else {
|
|
120
|
+
monthlyStreak = 1;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
lastMonth = month;
|
|
124
|
+
}
|
|
125
|
+
// Check if current month streak is active
|
|
126
|
+
const currentMonth = today.substring(0, 7); // Extract YYYY-MM
|
|
127
|
+
const lastActiveMonth = sortedMonths[sortedMonths.length - 1];
|
|
128
|
+
const lastMonthDate = shaxpir_common_1.Time.plus(today, -30, 'days');
|
|
129
|
+
const previousMonth = shaxpir_common_1.Time.dateFrom(lastMonthDate).substring(0, 7);
|
|
130
|
+
if (lastActiveMonth === currentMonth || lastActiveMonth === previousMonth) {
|
|
131
|
+
monthlyCurrent = monthlyStreak;
|
|
132
|
+
}
|
|
133
|
+
// Get week string for last activity (ISO week format)
|
|
134
|
+
const getWeekString = (date) => {
|
|
135
|
+
// Simple week calculation: YYYY-Www where ww is week of year
|
|
136
|
+
const [year, month, day] = date.split('-').map(Number);
|
|
137
|
+
const dateObj = new Date(year, month - 1, day);
|
|
138
|
+
const startOfYear = new Date(year, 0, 1);
|
|
139
|
+
const dayOfYear = Math.floor((dateObj.getTime() - startOfYear.getTime()) / (24 * 60 * 60 * 1000)) + 1;
|
|
140
|
+
const weekNumber = Math.ceil(dayOfYear / 7);
|
|
141
|
+
return `${year}-W${weekNumber.toString().padStart(2, '0')}`;
|
|
142
|
+
};
|
|
143
|
+
// We know lastDate has a value since activityDates.length > 0
|
|
144
|
+
const finalLastDate = lastDate || activityDates[activityDates.length - 1];
|
|
145
|
+
return {
|
|
146
|
+
daily: {
|
|
147
|
+
current: dailyCurrent,
|
|
148
|
+
longest: dailyLongest
|
|
149
|
+
},
|
|
150
|
+
weekly: {
|
|
151
|
+
current: weeklyCurrent
|
|
152
|
+
},
|
|
153
|
+
monthly: {
|
|
154
|
+
current: monthlyCurrent
|
|
155
|
+
},
|
|
156
|
+
last_activity: {
|
|
157
|
+
date: finalLastDate,
|
|
158
|
+
week: getWeekString(finalLastDate),
|
|
159
|
+
month: finalLastDate.substring(0, 7)
|
|
160
|
+
},
|
|
161
|
+
total_days: activityDates.length
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
exports.Streaks = Streaks;
|
package/dist/models/index.d.ts
CHANGED
package/dist/models/index.js
CHANGED
|
@@ -38,6 +38,7 @@ __exportStar(require("./Progress"), exports);
|
|
|
38
38
|
__exportStar(require("./Review"), exports);
|
|
39
39
|
__exportStar(require("./Session"), exports);
|
|
40
40
|
__exportStar(require("./Social"), exports);
|
|
41
|
+
__exportStar(require("./Streaks"), exports);
|
|
41
42
|
__exportStar(require("./Term"), exports);
|
|
42
43
|
__exportStar(require("./User"), exports);
|
|
43
44
|
__exportStar(require("./Workspace"), exports);
|