mirror of
https://github.com/zkldi/Tachi.git
synced 2026-10-07 22:38:02 +03:00
feat: clump successive goals
This commit is contained in:
@@ -9,7 +9,6 @@ import SessionRaiseBreakdown from "components/sessions/SessionRaiseBreakdown";
|
||||
import ScoreTable from "components/tables/scores/ScoreTable";
|
||||
import ApiError from "components/util/ApiError";
|
||||
import Divider from "components/util/Divider";
|
||||
import GoalLink from "components/util/GoalLink";
|
||||
import Icon from "components/util/Icon";
|
||||
import LinkButton from "components/util/LinkButton";
|
||||
import Loading from "components/util/Loading";
|
||||
@@ -31,6 +30,7 @@ import {
|
||||
ClumpedActivityScores,
|
||||
ClumpedActivitySession,
|
||||
} from "types/tachi";
|
||||
import { InnerQuestSectionGoal } from "components/targets/quests/Quest";
|
||||
|
||||
// Records activity for a group of users on a GPT. Also used for single users.
|
||||
export default function Activity({
|
||||
@@ -161,7 +161,7 @@ function ActivityInner({
|
||||
user={user}
|
||||
/>
|
||||
);
|
||||
case "GOAL_ACHIEVEMENT":
|
||||
case "GOAL_ACHIEVEMENTS":
|
||||
return (
|
||||
<GoalActivity
|
||||
shouldShowGame={shouldShowGame}
|
||||
@@ -209,9 +209,11 @@ function ActivityInner({
|
||||
case "SESSION":
|
||||
lastTimestamp = lastThing.timeStarted;
|
||||
break;
|
||||
case "GOAL_ACHIEVEMENT":
|
||||
case "GOAL_ACHIEVEMENTS":
|
||||
lastTimestamp = lastThing.goals[0]?.timeAchieved;
|
||||
break;
|
||||
case "QUEST_ACHIEVEMENT":
|
||||
lastTimestamp = lastThing.sub.timeAchieved!;
|
||||
lastTimestamp = lastThing.sub.timeAchieved;
|
||||
}
|
||||
|
||||
if (!lastTimestamp) {
|
||||
@@ -342,31 +344,74 @@ function GoalActivity({
|
||||
user: UserDocument;
|
||||
shouldShowGame: boolean;
|
||||
}) {
|
||||
const { game, playtype } = data.goal;
|
||||
const { game, playtype } = data.goals[0];
|
||||
|
||||
const prettyGame = shouldShowGame ? FormatGame(game, playtype) : "";
|
||||
const prettyGame = shouldShowGame ? `${FormatGame(game, playtype)} ` : "";
|
||||
|
||||
const [show, setShow] = useState(false);
|
||||
|
||||
let subMessage;
|
||||
let mutedText: string | null | undefined;
|
||||
|
||||
if (data.goals.length === 1) {
|
||||
const goal0 = data.goals[0];
|
||||
|
||||
subMessage = `${goal0.__related.goal.name}${
|
||||
shouldShowGame ? `in ${FormatGame(game, playtype)}` : ""
|
||||
}!`;
|
||||
} else {
|
||||
subMessage = `${data.goals.length} ${prettyGame}goals`;
|
||||
|
||||
mutedText = TruncateString(data.goals.map((e) => e.__related.goal.name).join(", "), 100);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="timeline-item timeline-hover my-4">
|
||||
<div className="timeline-badge bg-warning"></div>
|
||||
<div className="timeline-content">
|
||||
<div className="timeline-content-inner">
|
||||
<div className="timeline-content-inner" onClick={() => setShow(!show)}>
|
||||
<div className="timeline-content-title">
|
||||
<span style={{ fontSize: "1.15rem" }}>
|
||||
<Icon
|
||||
type={`chevron-${show ? "down" : "right"}`}
|
||||
style={{
|
||||
fontSize: "0.75rem",
|
||||
}}
|
||||
/>
|
||||
<span style={{ fontSize: "1.15rem" }} className="ml-2">
|
||||
<UGPTLink reqUser={user} game={game} playtype={playtype} /> achieved{" "}
|
||||
<GoalLink noPad goal={data.goal} />
|
||||
{prettyGame && ` in ${prettyGame}`}!
|
||||
{subMessage}!
|
||||
</span>
|
||||
{mutedText && (
|
||||
<>
|
||||
<br />
|
||||
<Muted>{mutedText}</Muted>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="timeline-content-timestamp">
|
||||
{MillisToSince(data.sub.timeAchieved ?? 0)}
|
||||
{MillisToSince(data.goals[0]?.timeAchieved ?? 0)}
|
||||
<br />
|
||||
<span className="text-muted font-italic text-right">
|
||||
{FormatTime(data.sub.timeAchieved ?? 0)}
|
||||
{FormatTime(data.goals[0]?.timeAchieved ?? 0)}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{show && (
|
||||
<>
|
||||
<Divider />
|
||||
<div className="pl-4">
|
||||
{data.goals.map((e) => (
|
||||
<InnerQuestSectionGoal
|
||||
goal={e.__related.goal}
|
||||
goalSubOverride={e}
|
||||
key={e.goalID}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -9,7 +9,13 @@ import Muted from "components/util/Muted";
|
||||
import { UserContext } from "context/UserContext";
|
||||
import React, { useContext, useState } from "react";
|
||||
import { Badge, Button } from "react-bootstrap";
|
||||
import { FormatGame, GoalDocument, QuestDocument, QuestSection } from "tachi-common";
|
||||
import {
|
||||
FormatGame,
|
||||
GoalDocument,
|
||||
GoalSubscriptionDocument,
|
||||
QuestDocument,
|
||||
QuestSection,
|
||||
} from "tachi-common";
|
||||
import { GamePT } from "types/react";
|
||||
import { TargetsContext } from "context/TargetsContext";
|
||||
import QuickTooltip from "components/layout/misc/QuickTooltip";
|
||||
@@ -195,14 +201,16 @@ export function InnerQuestSectionGoal({
|
||||
goal,
|
||||
note,
|
||||
dependencies,
|
||||
goalSubOverride,
|
||||
}: {
|
||||
goal: GoalDocument;
|
||||
note?: string;
|
||||
dependencies?: string[];
|
||||
goalSubOverride?: GoalSubscriptionDocument;
|
||||
}) {
|
||||
const { goalSubs } = useContext(TargetsContext);
|
||||
|
||||
const goalSub = goalSubs.get(goal.goalID);
|
||||
const goalSub = goalSubOverride ?? goalSubs.get(goal.goalID);
|
||||
|
||||
if (!goalSub) {
|
||||
return (
|
||||
|
||||
@@ -25,11 +25,10 @@ export type ClumpedActivityClassAchievement = {
|
||||
} & ClassAchievementDocument;
|
||||
|
||||
export type ClumpedActivityGoalAchievement = {
|
||||
type: "GOAL_ACHIEVEMENT";
|
||||
type: "GOAL_ACHIEVEMENTS";
|
||||
// redundant, but convenient.
|
||||
userID: integer;
|
||||
sub: GoalSubscriptionDocument;
|
||||
goal: GoalDocument;
|
||||
goals: Array<GoalSubscriptionDocument & { __related: { goal: GoalDocument } }>;
|
||||
};
|
||||
|
||||
export type ClumpedActivityQuestAchievement = {
|
||||
|
||||
+38
-11
@@ -1,5 +1,9 @@
|
||||
import { ActivityReturn, RecordActivityReturn } from "types/api-returns";
|
||||
import { ClumpedActivity, ClumpedActivityScores } from "types/tachi";
|
||||
import {
|
||||
ClumpedActivity,
|
||||
ClumpedActivityGoalAchievement,
|
||||
ClumpedActivityScores,
|
||||
} from "types/tachi";
|
||||
import { ONE_HOUR } from "./constants/time";
|
||||
import { CreateSongMap, CreateChartMap, CreateGoalMap } from "./data";
|
||||
import { CreateQuestMap } from "./misc";
|
||||
@@ -13,7 +17,8 @@ const SORT_ACTIVITY = NumericSOV((x: ClumpedActivity[0]) => {
|
||||
return x.scores[0]?.timeAchieved ?? -Infinity;
|
||||
case "CLASS_ACHIEVEMENT":
|
||||
return x.timeAchieved;
|
||||
case "GOAL_ACHIEVEMENT":
|
||||
case "GOAL_ACHIEVEMENTS":
|
||||
return x.goals[0]?.timeAchieved ?? -Infinity;
|
||||
case "QUEST_ACHIEVEMENT":
|
||||
return x.sub.timeAchieved ?? -Infinity;
|
||||
}
|
||||
@@ -95,24 +100,46 @@ export function ClumpActivity(data: ActivityReturn | RecordActivityReturn): Clum
|
||||
}
|
||||
|
||||
const goalMap = CreateGoalMap(data.goals);
|
||||
for (const sub of data.goalSubs) {
|
||||
|
||||
let goalClump: ClumpedActivityGoalAchievement["goals"] = [];
|
||||
// reset last clumps. I realise this is super copypasta, but we'll live.
|
||||
curUserID = null;
|
||||
lastTime = null;
|
||||
|
||||
for (const sub of data.goalSubs
|
||||
.slice(0)
|
||||
.sort(NumericSOV((x) => x.timeAchieved ?? -Infinity, true))) {
|
||||
const goal = goalMap.get(sub.goalID);
|
||||
|
||||
if (!goal) {
|
||||
console.warn(
|
||||
`Couldn't find parent goal for recently-achieved-sub '${sub.goalID}. Skipping.'`
|
||||
);
|
||||
console.warn(`Failed to resolve goal ${sub.goalID}. Skipping.`);
|
||||
continue;
|
||||
}
|
||||
|
||||
clumped.push({
|
||||
type: "GOAL_ACHIEVEMENT",
|
||||
userID: sub.userID,
|
||||
sub,
|
||||
goal,
|
||||
if (
|
||||
(curUserID !== sub.userID && curUserID !== null) ||
|
||||
(lastTime !== null && lastTime - (sub.timeAchieved ?? 0) > ONE_HOUR * 8)
|
||||
) {
|
||||
clumped.push({ type: "GOAL_ACHIEVEMENTS", userID: sub.userID, goals: goalClump });
|
||||
|
||||
goalClump = [];
|
||||
}
|
||||
|
||||
curUserID = sub.userID;
|
||||
lastTime = sub.timeAchieved;
|
||||
|
||||
goalClump.push({
|
||||
...sub,
|
||||
__related: {
|
||||
goal,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
if (goalClump.length !== 0) {
|
||||
clumped.push({ type: "GOAL_ACHIEVEMENTS", userID: goalClump[0].userID, goals: goalClump });
|
||||
}
|
||||
|
||||
const questMap = CreateQuestMap(data.quests);
|
||||
|
||||
for (const sub of data.questSubs) {
|
||||
|
||||
Reference in New Issue
Block a user