mirror of
https://github.com/ksyasuda/SubMiner.git
synced 2026-04-10 16:19:24 -07:00
feat(stats): drop seek markers from session timeline
Seek-forward / seek-backward events cluttered sessions with lots of rewinds — a single episode could show dozens of << and >> icons overlapping the card and pause markers. Stop requesting them from the backend, drop them from buildSessionChartEvents, remove the seek variant from SessionChartMarker, and strip the matching ReferenceLines, overlay marker, popover branch, and legend entry from SessionDetail.
This commit is contained in:
@@ -8,3 +8,4 @@ area: stats
|
|||||||
- Episode detail hides card events whose Anki notes have been deleted, instead of showing phantom mining activity.
|
- Episode detail hides card events whose Anki notes have been deleted, instead of showing phantom mining activity.
|
||||||
- Trend and watch-time charts share a unified theme with horizontal gridlines and larger ticks for legibility.
|
- Trend and watch-time charts share a unified theme with horizontal gridlines and larger ticks for legibility.
|
||||||
- Overview, Library, Trends, Sessions, and Vocabulary now use generic "title" wording so YouTube videos and anime live comfortably side by side in the dashboard.
|
- Overview, Library, Trends, Sessions, and Vocabulary now use generic "title" wording so YouTube videos and anime live comfortably side by side in the dashboard.
|
||||||
|
- Session timeline no longer plots seek-forward/seek-backward markers — they were too noisy on sessions with lots of rewinds.
|
||||||
|
|||||||
@@ -125,14 +125,13 @@ export function SessionDetail({ session }: SessionDetailProps) {
|
|||||||
const knownWordsMap = buildKnownWordsLookup(knownWordsTimeline);
|
const knownWordsMap = buildKnownWordsLookup(knownWordsTimeline);
|
||||||
const hasKnownWords = knownWordsMap.size > 0;
|
const hasKnownWords = knownWordsMap.size > 0;
|
||||||
|
|
||||||
const { cardEvents, seekEvents, yomitanLookupEvents, pauseRegions, markers } =
|
const { cardEvents, yomitanLookupEvents, pauseRegions, markers } =
|
||||||
buildSessionChartEvents(events);
|
buildSessionChartEvents(events);
|
||||||
const lookupRate = buildLookupRateDisplay(
|
const lookupRate = buildLookupRateDisplay(
|
||||||
session.yomitanLookupCount,
|
session.yomitanLookupCount,
|
||||||
getSessionDisplayWordCount(session),
|
getSessionDisplayWordCount(session),
|
||||||
);
|
);
|
||||||
const pauseCount = events.filter((e) => e.eventType === EventType.PAUSE_START).length;
|
const pauseCount = events.filter((e) => e.eventType === EventType.PAUSE_START).length;
|
||||||
const seekCount = seekEvents.length;
|
|
||||||
const cardEventCount = cardEvents.length;
|
const cardEventCount = cardEvents.length;
|
||||||
const activeMarkerKey = resolveActiveSessionMarkerKey(hoveredMarkerKey, pinnedMarkerKey);
|
const activeMarkerKey = resolveActiveSessionMarkerKey(hoveredMarkerKey, pinnedMarkerKey);
|
||||||
const activeMarker = useMemo<SessionChartMarker | null>(
|
const activeMarker = useMemo<SessionChartMarker | null>(
|
||||||
@@ -230,7 +229,6 @@ export function SessionDetail({ session }: SessionDetailProps) {
|
|||||||
sorted={sorted}
|
sorted={sorted}
|
||||||
knownWordsMap={knownWordsMap}
|
knownWordsMap={knownWordsMap}
|
||||||
cardEvents={cardEvents}
|
cardEvents={cardEvents}
|
||||||
seekEvents={seekEvents}
|
|
||||||
yomitanLookupEvents={yomitanLookupEvents}
|
yomitanLookupEvents={yomitanLookupEvents}
|
||||||
pauseRegions={pauseRegions}
|
pauseRegions={pauseRegions}
|
||||||
markers={markers}
|
markers={markers}
|
||||||
@@ -242,7 +240,6 @@ export function SessionDetail({ session }: SessionDetailProps) {
|
|||||||
loadingNoteIds={loadingNoteIds}
|
loadingNoteIds={loadingNoteIds}
|
||||||
onOpenNote={handleOpenNote}
|
onOpenNote={handleOpenNote}
|
||||||
pauseCount={pauseCount}
|
pauseCount={pauseCount}
|
||||||
seekCount={seekCount}
|
|
||||||
cardEventCount={cardEventCount}
|
cardEventCount={cardEventCount}
|
||||||
lookupRate={lookupRate}
|
lookupRate={lookupRate}
|
||||||
session={session}
|
session={session}
|
||||||
@@ -254,7 +251,6 @@ export function SessionDetail({ session }: SessionDetailProps) {
|
|||||||
<FallbackView
|
<FallbackView
|
||||||
sorted={sorted}
|
sorted={sorted}
|
||||||
cardEvents={cardEvents}
|
cardEvents={cardEvents}
|
||||||
seekEvents={seekEvents}
|
|
||||||
yomitanLookupEvents={yomitanLookupEvents}
|
yomitanLookupEvents={yomitanLookupEvents}
|
||||||
pauseRegions={pauseRegions}
|
pauseRegions={pauseRegions}
|
||||||
markers={markers}
|
markers={markers}
|
||||||
@@ -266,7 +262,6 @@ export function SessionDetail({ session }: SessionDetailProps) {
|
|||||||
loadingNoteIds={loadingNoteIds}
|
loadingNoteIds={loadingNoteIds}
|
||||||
onOpenNote={handleOpenNote}
|
onOpenNote={handleOpenNote}
|
||||||
pauseCount={pauseCount}
|
pauseCount={pauseCount}
|
||||||
seekCount={seekCount}
|
|
||||||
cardEventCount={cardEventCount}
|
cardEventCount={cardEventCount}
|
||||||
lookupRate={lookupRate}
|
lookupRate={lookupRate}
|
||||||
session={session}
|
session={session}
|
||||||
@@ -280,7 +275,6 @@ function RatioView({
|
|||||||
sorted,
|
sorted,
|
||||||
knownWordsMap,
|
knownWordsMap,
|
||||||
cardEvents,
|
cardEvents,
|
||||||
seekEvents,
|
|
||||||
yomitanLookupEvents,
|
yomitanLookupEvents,
|
||||||
pauseRegions,
|
pauseRegions,
|
||||||
markers,
|
markers,
|
||||||
@@ -292,7 +286,6 @@ function RatioView({
|
|||||||
loadingNoteIds,
|
loadingNoteIds,
|
||||||
onOpenNote,
|
onOpenNote,
|
||||||
pauseCount,
|
pauseCount,
|
||||||
seekCount,
|
|
||||||
cardEventCount,
|
cardEventCount,
|
||||||
lookupRate,
|
lookupRate,
|
||||||
session,
|
session,
|
||||||
@@ -300,7 +293,6 @@ function RatioView({
|
|||||||
sorted: TimelineEntry[];
|
sorted: TimelineEntry[];
|
||||||
knownWordsMap: Map<number, number>;
|
knownWordsMap: Map<number, number>;
|
||||||
cardEvents: SessionEvent[];
|
cardEvents: SessionEvent[];
|
||||||
seekEvents: SessionEvent[];
|
|
||||||
yomitanLookupEvents: SessionEvent[];
|
yomitanLookupEvents: SessionEvent[];
|
||||||
pauseRegions: Array<{ startMs: number; endMs: number }>;
|
pauseRegions: Array<{ startMs: number; endMs: number }>;
|
||||||
markers: SessionChartMarker[];
|
markers: SessionChartMarker[];
|
||||||
@@ -312,7 +304,6 @@ function RatioView({
|
|||||||
loadingNoteIds: Set<number>;
|
loadingNoteIds: Set<number>;
|
||||||
onOpenNote: (noteId: number) => void;
|
onOpenNote: (noteId: number) => void;
|
||||||
pauseCount: number;
|
pauseCount: number;
|
||||||
seekCount: number;
|
|
||||||
cardEventCount: number;
|
cardEventCount: number;
|
||||||
lookupRate: ReturnType<typeof buildLookupRateDisplay>;
|
lookupRate: ReturnType<typeof buildLookupRateDisplay>;
|
||||||
session: SessionSummary;
|
session: SessionSummary;
|
||||||
@@ -450,22 +441,6 @@ function RatioView({
|
|||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
|
|
||||||
{seekEvents.map((e, i) => {
|
|
||||||
const isBackward = e.eventType === EventType.SEEK_BACKWARD;
|
|
||||||
const stroke = isBackward ? '#f5bde6' : '#8bd5ca';
|
|
||||||
return (
|
|
||||||
<ReferenceLine
|
|
||||||
key={`seek-${i}`}
|
|
||||||
yAxisId="pct"
|
|
||||||
x={e.tsMs}
|
|
||||||
stroke={stroke}
|
|
||||||
strokeWidth={1.5}
|
|
||||||
strokeOpacity={0.75}
|
|
||||||
strokeDasharray="4 3"
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
|
|
||||||
{/* Yomitan lookup markers */}
|
{/* Yomitan lookup markers */}
|
||||||
{yomitanLookupEvents.map((e, i) => (
|
{yomitanLookupEvents.map((e, i) => (
|
||||||
<ReferenceLine
|
<ReferenceLine
|
||||||
@@ -549,7 +524,6 @@ function RatioView({
|
|||||||
<StatsBar
|
<StatsBar
|
||||||
hasKnownWords
|
hasKnownWords
|
||||||
pauseCount={pauseCount}
|
pauseCount={pauseCount}
|
||||||
seekCount={seekCount}
|
|
||||||
cardEventCount={cardEventCount}
|
cardEventCount={cardEventCount}
|
||||||
session={session}
|
session={session}
|
||||||
lookupRate={lookupRate}
|
lookupRate={lookupRate}
|
||||||
@@ -563,7 +537,6 @@ function RatioView({
|
|||||||
function FallbackView({
|
function FallbackView({
|
||||||
sorted,
|
sorted,
|
||||||
cardEvents,
|
cardEvents,
|
||||||
seekEvents,
|
|
||||||
yomitanLookupEvents,
|
yomitanLookupEvents,
|
||||||
pauseRegions,
|
pauseRegions,
|
||||||
markers,
|
markers,
|
||||||
@@ -575,14 +548,12 @@ function FallbackView({
|
|||||||
loadingNoteIds,
|
loadingNoteIds,
|
||||||
onOpenNote,
|
onOpenNote,
|
||||||
pauseCount,
|
pauseCount,
|
||||||
seekCount,
|
|
||||||
cardEventCount,
|
cardEventCount,
|
||||||
lookupRate,
|
lookupRate,
|
||||||
session,
|
session,
|
||||||
}: {
|
}: {
|
||||||
sorted: TimelineEntry[];
|
sorted: TimelineEntry[];
|
||||||
cardEvents: SessionEvent[];
|
cardEvents: SessionEvent[];
|
||||||
seekEvents: SessionEvent[];
|
|
||||||
yomitanLookupEvents: SessionEvent[];
|
yomitanLookupEvents: SessionEvent[];
|
||||||
pauseRegions: Array<{ startMs: number; endMs: number }>;
|
pauseRegions: Array<{ startMs: number; endMs: number }>;
|
||||||
markers: SessionChartMarker[];
|
markers: SessionChartMarker[];
|
||||||
@@ -594,7 +565,6 @@ function FallbackView({
|
|||||||
loadingNoteIds: Set<number>;
|
loadingNoteIds: Set<number>;
|
||||||
onOpenNote: (noteId: number) => void;
|
onOpenNote: (noteId: number) => void;
|
||||||
pauseCount: number;
|
pauseCount: number;
|
||||||
seekCount: number;
|
|
||||||
cardEventCount: number;
|
cardEventCount: number;
|
||||||
lookupRate: ReturnType<typeof buildLookupRateDisplay>;
|
lookupRate: ReturnType<typeof buildLookupRateDisplay>;
|
||||||
session: SessionSummary;
|
session: SessionSummary;
|
||||||
@@ -680,20 +650,6 @@ function FallbackView({
|
|||||||
strokeOpacity={0.8}
|
strokeOpacity={0.8}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
{seekEvents.map((e, i) => {
|
|
||||||
const isBackward = e.eventType === EventType.SEEK_BACKWARD;
|
|
||||||
const stroke = isBackward ? '#f5bde6' : '#8bd5ca';
|
|
||||||
return (
|
|
||||||
<ReferenceLine
|
|
||||||
key={`seek-${i}`}
|
|
||||||
x={e.tsMs}
|
|
||||||
stroke={stroke}
|
|
||||||
strokeWidth={1.5}
|
|
||||||
strokeOpacity={0.75}
|
|
||||||
strokeDasharray="4 3"
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
{yomitanLookupEvents.map((e, i) => (
|
{yomitanLookupEvents.map((e, i) => (
|
||||||
<ReferenceLine
|
<ReferenceLine
|
||||||
key={`yomitan-${i}`}
|
key={`yomitan-${i}`}
|
||||||
@@ -735,7 +691,6 @@ function FallbackView({
|
|||||||
<StatsBar
|
<StatsBar
|
||||||
hasKnownWords={false}
|
hasKnownWords={false}
|
||||||
pauseCount={pauseCount}
|
pauseCount={pauseCount}
|
||||||
seekCount={seekCount}
|
|
||||||
cardEventCount={cardEventCount}
|
cardEventCount={cardEventCount}
|
||||||
session={session}
|
session={session}
|
||||||
lookupRate={lookupRate}
|
lookupRate={lookupRate}
|
||||||
@@ -749,14 +704,12 @@ function FallbackView({
|
|||||||
function StatsBar({
|
function StatsBar({
|
||||||
hasKnownWords,
|
hasKnownWords,
|
||||||
pauseCount,
|
pauseCount,
|
||||||
seekCount,
|
|
||||||
cardEventCount,
|
cardEventCount,
|
||||||
session,
|
session,
|
||||||
lookupRate,
|
lookupRate,
|
||||||
}: {
|
}: {
|
||||||
hasKnownWords: boolean;
|
hasKnownWords: boolean;
|
||||||
pauseCount: number;
|
pauseCount: number;
|
||||||
seekCount: number;
|
|
||||||
cardEventCount: number;
|
cardEventCount: number;
|
||||||
session: SessionSummary;
|
session: SessionSummary;
|
||||||
lookupRate: ReturnType<typeof buildLookupRateDisplay>;
|
lookupRate: ReturnType<typeof buildLookupRateDisplay>;
|
||||||
@@ -791,12 +744,7 @@ function StatsBar({
|
|||||||
{pauseCount !== 1 ? 's' : ''}
|
{pauseCount !== 1 ? 's' : ''}
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
{seekCount > 0 && (
|
{pauseCount > 0 && <span className="text-ctp-surface2">|</span>}
|
||||||
<span className="text-ctp-overlay2">
|
|
||||||
<span className="text-ctp-teal">{seekCount}</span> seek{seekCount !== 1 ? 's' : ''}
|
|
||||||
</span>
|
|
||||||
)}
|
|
||||||
{(pauseCount > 0 || seekCount > 0) && <span className="text-ctp-surface2">|</span>}
|
|
||||||
|
|
||||||
{/* Group 3: Learning events */}
|
{/* Group 3: Learning events */}
|
||||||
<span className="flex items-center gap-1.5">
|
<span className="flex items-center gap-1.5">
|
||||||
|
|||||||
@@ -33,8 +33,6 @@ function markerLabel(marker: SessionChartMarker): string {
|
|||||||
switch (marker.kind) {
|
switch (marker.kind) {
|
||||||
case 'pause':
|
case 'pause':
|
||||||
return '||';
|
return '||';
|
||||||
case 'seek':
|
|
||||||
return marker.direction === 'backward' ? '<<' : '>>';
|
|
||||||
case 'card':
|
case 'card':
|
||||||
return '\u26CF';
|
return '\u26CF';
|
||||||
}
|
}
|
||||||
@@ -44,10 +42,6 @@ function markerColors(marker: SessionChartMarker): { border: string; bg: string;
|
|||||||
switch (marker.kind) {
|
switch (marker.kind) {
|
||||||
case 'pause':
|
case 'pause':
|
||||||
return { border: '#f5a97f', bg: 'rgba(245,169,127,0.16)', text: '#f5a97f' };
|
return { border: '#f5a97f', bg: 'rgba(245,169,127,0.16)', text: '#f5a97f' };
|
||||||
case 'seek':
|
|
||||||
return marker.direction === 'backward'
|
|
||||||
? { border: '#f5bde6', bg: 'rgba(245,189,230,0.16)', text: '#f5bde6' }
|
|
||||||
: { border: '#8bd5ca', bg: 'rgba(139,213,202,0.16)', text: '#8bd5ca' };
|
|
||||||
case 'card':
|
case 'card':
|
||||||
return { border: '#a6da95', bg: 'rgba(166,218,149,0.16)', text: '#a6da95' };
|
return { border: '#a6da95', bg: 'rgba(166,218,149,0.16)', text: '#a6da95' };
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -41,35 +41,6 @@ test('SessionEventPopover renders formatted card-mine details with fetched note
|
|||||||
assert.match(markup, /Open in Anki/);
|
assert.match(markup, /Open in Anki/);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('SessionEventPopover renders seek metadata compactly', () => {
|
|
||||||
const marker: SessionChartMarker = {
|
|
||||||
key: 'seek-3000',
|
|
||||||
kind: 'seek',
|
|
||||||
anchorTsMs: 3_000,
|
|
||||||
eventTsMs: 3_000,
|
|
||||||
direction: 'backward',
|
|
||||||
fromMs: 5_000,
|
|
||||||
toMs: 1_500,
|
|
||||||
};
|
|
||||||
|
|
||||||
const markup = renderToStaticMarkup(
|
|
||||||
<SessionEventPopover
|
|
||||||
marker={marker}
|
|
||||||
noteInfos={new Map()}
|
|
||||||
loading={false}
|
|
||||||
pinned={false}
|
|
||||||
onTogglePinned={() => {}}
|
|
||||||
onClose={() => {}}
|
|
||||||
onOpenNote={() => {}}
|
|
||||||
/>,
|
|
||||||
);
|
|
||||||
|
|
||||||
assert.match(markup, /Seek backward/);
|
|
||||||
assert.match(markup, /5\.0s/);
|
|
||||||
assert.match(markup, /1\.5s/);
|
|
||||||
assert.match(markup, /3\.5s/);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('SessionEventPopover renders a cleaner fallback when AnkiConnect provides no preview fields', () => {
|
test('SessionEventPopover renders a cleaner fallback when AnkiConnect provides no preview fields', () => {
|
||||||
const marker: SessionChartMarker = {
|
const marker: SessionChartMarker = {
|
||||||
key: 'card-9000',
|
key: 'card-9000',
|
||||||
|
|||||||
@@ -31,18 +31,12 @@ export function SessionEventPopover({
|
|||||||
onClose,
|
onClose,
|
||||||
onOpenNote,
|
onOpenNote,
|
||||||
}: SessionEventPopoverProps) {
|
}: SessionEventPopoverProps) {
|
||||||
const seekDurationLabel =
|
|
||||||
marker.kind === 'seek' && marker.fromMs !== null && marker.toMs !== null
|
|
||||||
? formatEventSeconds(Math.abs(marker.toMs - marker.fromMs))?.replace(/\.0s$/, 's')
|
|
||||||
: null;
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="relative z-50 w-64 rounded-xl border border-ctp-surface2 bg-ctp-surface0/95 p-3 shadow-2xl shadow-black/30 backdrop-blur-sm">
|
<div className="relative z-50 w-64 rounded-xl border border-ctp-surface2 bg-ctp-surface0/95 p-3 shadow-2xl shadow-black/30 backdrop-blur-sm">
|
||||||
<div className="mb-2 flex items-start justify-between gap-3">
|
<div className="mb-2 flex items-start justify-between gap-3">
|
||||||
<div>
|
<div>
|
||||||
<div className="text-xs font-semibold text-ctp-text">
|
<div className="text-xs font-semibold text-ctp-text">
|
||||||
{marker.kind === 'pause' && 'Paused'}
|
{marker.kind === 'pause' && 'Paused'}
|
||||||
{marker.kind === 'seek' && `Seek ${marker.direction}`}
|
|
||||||
{marker.kind === 'card' && 'Card mined'}
|
{marker.kind === 'card' && 'Card mined'}
|
||||||
</div>
|
</div>
|
||||||
<div className="text-[10px] text-ctp-overlay1">{formatEventTime(marker.eventTsMs)}</div>
|
<div className="text-[10px] text-ctp-overlay1">{formatEventTime(marker.eventTsMs)}</div>
|
||||||
@@ -72,7 +66,6 @@ export function SessionEventPopover({
|
|||||||
) : null}
|
) : null}
|
||||||
<div className="text-sm">
|
<div className="text-sm">
|
||||||
{marker.kind === 'pause' && '||'}
|
{marker.kind === 'pause' && '||'}
|
||||||
{marker.kind === 'seek' && (marker.direction === 'backward' ? '<<' : '>>')}
|
|
||||||
{marker.kind === 'card' && '\u26CF'}
|
{marker.kind === 'card' && '\u26CF'}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -84,19 +77,6 @@ export function SessionEventPopover({
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{marker.kind === 'seek' && (
|
|
||||||
<div className="space-y-1 text-xs text-ctp-subtext0">
|
|
||||||
<div>
|
|
||||||
From{' '}
|
|
||||||
<span className="text-ctp-teal">{formatEventSeconds(marker.fromMs) ?? '\u2014'}</span>{' '}
|
|
||||||
to <span className="text-ctp-teal">{formatEventSeconds(marker.toMs) ?? '\u2014'}</span>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
Length <span className="text-ctp-peach">{seekDurationLabel ?? '\u2014'}</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{marker.kind === 'card' && (
|
{marker.kind === 'card' && (
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<div className="text-xs text-ctp-cards-mined">
|
<div className="text-xs text-ctp-cards-mined">
|
||||||
|
|||||||
@@ -46,9 +46,10 @@ test('buildSessionChartEvents keeps only chart-relevant events and pairs pause r
|
|||||||
{ eventType: EventType.LOOKUP, tsMs: 8_000, payload: '{"hit":true}' },
|
{ eventType: EventType.LOOKUP, tsMs: 8_000, payload: '{"hit":true}' },
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
// Seek events are intentionally dropped from the chart — they were too noisy.
|
||||||
assert.deepEqual(
|
assert.deepEqual(
|
||||||
chartEvents.seekEvents.map((event) => event.eventType),
|
chartEvents.markers.filter((marker) => marker.kind !== 'pause' && marker.kind !== 'card'),
|
||||||
[EventType.SEEK_FORWARD, EventType.SEEK_BACKWARD],
|
[],
|
||||||
);
|
);
|
||||||
assert.deepEqual(
|
assert.deepEqual(
|
||||||
chartEvents.cardEvents.map((event) => event.tsMs),
|
chartEvents.cardEvents.map((event) => event.tsMs),
|
||||||
|
|||||||
@@ -29,25 +29,20 @@ test('buildSessionChartEvents produces typed hover markers with parsed payload m
|
|||||||
{ eventType: EventType.YOMITAN_LOOKUP, tsMs: 7_000, payload: null },
|
{ eventType: EventType.YOMITAN_LOOKUP, tsMs: 7_000, payload: null },
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
// Seek events are intentionally dropped — too noisy on the session chart.
|
||||||
assert.deepEqual(
|
assert.deepEqual(
|
||||||
chartEvents.markers.map((marker) => marker.kind),
|
chartEvents.markers.map((marker) => marker.kind),
|
||||||
['seek', 'pause', 'card'],
|
['pause', 'card'],
|
||||||
);
|
);
|
||||||
|
|
||||||
const seekMarker = chartEvents.markers[0]!;
|
const pauseMarker = chartEvents.markers[0]!;
|
||||||
assert.equal(seekMarker.kind, 'seek');
|
|
||||||
assert.equal(seekMarker.direction, 'forward');
|
|
||||||
assert.equal(seekMarker.fromMs, 1_000);
|
|
||||||
assert.equal(seekMarker.toMs, 5_500);
|
|
||||||
|
|
||||||
const pauseMarker = chartEvents.markers[1]!;
|
|
||||||
assert.equal(pauseMarker.kind, 'pause');
|
assert.equal(pauseMarker.kind, 'pause');
|
||||||
assert.equal(pauseMarker.startMs, 2_000);
|
assert.equal(pauseMarker.startMs, 2_000);
|
||||||
assert.equal(pauseMarker.endMs, 5_000);
|
assert.equal(pauseMarker.endMs, 5_000);
|
||||||
assert.equal(pauseMarker.durationMs, 3_000);
|
assert.equal(pauseMarker.durationMs, 3_000);
|
||||||
assert.equal(pauseMarker.anchorTsMs, 3_500);
|
assert.equal(pauseMarker.anchorTsMs, 3_500);
|
||||||
|
|
||||||
const cardMarker = chartEvents.markers[2]!;
|
const cardMarker = chartEvents.markers[1]!;
|
||||||
assert.equal(cardMarker.kind, 'card');
|
assert.equal(cardMarker.kind, 'card');
|
||||||
assert.deepEqual(cardMarker.noteIds, [11, 22]);
|
assert.deepEqual(cardMarker.noteIds, [11, 22]);
|
||||||
assert.equal(cardMarker.cardsDelta, 2);
|
assert.equal(cardMarker.cardsDelta, 2);
|
||||||
|
|||||||
@@ -2,8 +2,6 @@ import { EventType, type SessionEvent } from '../types/stats';
|
|||||||
|
|
||||||
export const SESSION_CHART_EVENT_TYPES = [
|
export const SESSION_CHART_EVENT_TYPES = [
|
||||||
EventType.CARD_MINED,
|
EventType.CARD_MINED,
|
||||||
EventType.SEEK_FORWARD,
|
|
||||||
EventType.SEEK_BACKWARD,
|
|
||||||
EventType.PAUSE_START,
|
EventType.PAUSE_START,
|
||||||
EventType.PAUSE_END,
|
EventType.PAUSE_END,
|
||||||
EventType.YOMITAN_LOOKUP,
|
EventType.YOMITAN_LOOKUP,
|
||||||
@@ -16,7 +14,6 @@ export interface PauseRegion {
|
|||||||
|
|
||||||
export interface SessionChartEvents {
|
export interface SessionChartEvents {
|
||||||
cardEvents: SessionEvent[];
|
cardEvents: SessionEvent[];
|
||||||
seekEvents: SessionEvent[];
|
|
||||||
yomitanLookupEvents: SessionEvent[];
|
yomitanLookupEvents: SessionEvent[];
|
||||||
pauseRegions: PauseRegion[];
|
pauseRegions: PauseRegion[];
|
||||||
markers: SessionChartMarker[];
|
markers: SessionChartMarker[];
|
||||||
@@ -58,15 +55,6 @@ export type SessionChartMarker =
|
|||||||
endMs: number;
|
endMs: number;
|
||||||
durationMs: number;
|
durationMs: number;
|
||||||
}
|
}
|
||||||
| {
|
|
||||||
key: string;
|
|
||||||
kind: 'seek';
|
|
||||||
anchorTsMs: number;
|
|
||||||
eventTsMs: number;
|
|
||||||
direction: 'forward' | 'backward';
|
|
||||||
fromMs: number | null;
|
|
||||||
toMs: number | null;
|
|
||||||
}
|
|
||||||
| {
|
| {
|
||||||
key: string;
|
key: string;
|
||||||
kind: 'card';
|
kind: 'card';
|
||||||
@@ -295,7 +283,6 @@ export function projectSessionMarkerLeftPx({
|
|||||||
|
|
||||||
export function buildSessionChartEvents(events: SessionEvent[]): SessionChartEvents {
|
export function buildSessionChartEvents(events: SessionEvent[]): SessionChartEvents {
|
||||||
const cardEvents: SessionEvent[] = [];
|
const cardEvents: SessionEvent[] = [];
|
||||||
const seekEvents: SessionEvent[] = [];
|
|
||||||
const yomitanLookupEvents: SessionEvent[] = [];
|
const yomitanLookupEvents: SessionEvent[] = [];
|
||||||
const pauseRegions: PauseRegion[] = [];
|
const pauseRegions: PauseRegion[] = [];
|
||||||
const markers: SessionChartMarker[] = [];
|
const markers: SessionChartMarker[] = [];
|
||||||
@@ -317,22 +304,6 @@ export function buildSessionChartEvents(events: SessionEvent[]): SessionChartEve
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case EventType.SEEK_FORWARD:
|
|
||||||
case EventType.SEEK_BACKWARD:
|
|
||||||
seekEvents.push(event);
|
|
||||||
{
|
|
||||||
const payload = parsePayload(event.payload);
|
|
||||||
markers.push({
|
|
||||||
key: `seek-${event.tsMs}-${event.eventType}`,
|
|
||||||
kind: 'seek',
|
|
||||||
anchorTsMs: event.tsMs,
|
|
||||||
eventTsMs: event.tsMs,
|
|
||||||
direction: event.eventType === EventType.SEEK_BACKWARD ? 'backward' : 'forward',
|
|
||||||
fromMs: readNumberField(payload?.fromMs),
|
|
||||||
toMs: readNumberField(payload?.toMs),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case EventType.YOMITAN_LOOKUP:
|
case EventType.YOMITAN_LOOKUP:
|
||||||
yomitanLookupEvents.push(event);
|
yomitanLookupEvents.push(event);
|
||||||
break;
|
break;
|
||||||
@@ -376,7 +347,6 @@ export function buildSessionChartEvents(events: SessionEvent[]): SessionChartEve
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
cardEvents,
|
cardEvents,
|
||||||
seekEvents,
|
|
||||||
yomitanLookupEvents,
|
yomitanLookupEvents,
|
||||||
pauseRegions,
|
pauseRegions,
|
||||||
markers,
|
markers,
|
||||||
|
|||||||
Reference in New Issue
Block a user