feat(ui): track virtual play key by uid rather than index
Some checks failed
Publish Docker image to Dockerhub / test (push) Has been cancelled
Publish Docker image to Dockerhub / Build OCI Images (push) Has been cancelled
Publish Docker image to Dockerhub / Build OCI Images-1 (push) Has been cancelled
Publish Docker image to Dockerhub / Merge OCI Images and Push (push) Has been cancelled

Should prevent pop-in and false update-indicator triggering
This commit is contained in:
FoxxMD 2026-07-02 21:32:48 +00:00
parent bf7fb4f558
commit 5c6edefc56
3 changed files with 40 additions and 4 deletions

View file

@ -7,6 +7,7 @@ import { PlayApiCommon, PlayApiCommonDetailed, SortPlaysByProps } from '../../..
import { QueryPlaysOpts, QueryPlaysOptsJson } from '../../../backend/common/database/drizzle/repositories/PlayRepository.js';
import { VscDebugRestart } from 'react-icons/vsc';
import { sortByNewestDate } from '../../../core/PlayUtils.js';
import { getAllIndexes } from '../../../core/DataUtils.js';
dayjs.extend(doy);
@ -106,10 +107,32 @@ export const generateGroupPlays = (data: PlayApiCommon[]): GroupData[] => {
}
export const generateFlatItems = (data: PlayApiCommon[]) => {
// ensure there are no duplicates
// this may happen if a play is "bumped" from one "page" to another, based on offset,
// when new plays are inserted out of order (playedAt)
const allIds: string[] = [];
const dupes: string[] = [];
for(const d of data) {
if(allIds.includes(d.uid)) {
console.warn(`Duplicate ID detected ${d.uid}`);
dupes.push(d.uid);
} else {
allIds.push(d.uid);
}
}
for(const uid of dupes) {
const indexes = getAllIndexes(data, (d) => d.uid === uid);
// keep only the first one since its likely the freshest
const oldIndexes = indexes.slice(1);
for(const old of oldIndexes) {
data.splice(old, 1);
}
}
const groups = generateGroupPlays(data);
groups.sort((a, b) => sortByNewestDate(a.date, b.date));
return groups.map((x) => {
x.plays.sort((a, b) => sortByNewestDate(a.playedAt, b.playedAt));
return [{count: x.plays.length, date: x.date, uid: x.date.toISOString()}, ...x.plays];
return [{count: x.plays.length, date: x.date, uid: `${x.date.toISOString()}-${x.plays.length}`}, ...x.plays];
}).flat(1);
}

View file

@ -56,7 +56,7 @@ export const VirtualizedListDynamic = (props: ActivityLogProps & Pick<UseInfinit
}, [data]);
const getItemKey = useCallback((index) => {
return items[index].uid;
return items[index] !== undefined ? items[index].uid : index;
},[items]);
const parentRef = React.useRef(null)
@ -65,7 +65,7 @@ export const VirtualizedListDynamic = (props: ActivityLogProps & Pick<UseInfinit
const virtualizer = useVirtualizer({
count: items.length + 1,
getScrollElement: () => parentRef.current,
//getItemKey: getItemKey,
getItemKey: getItemKey,
estimateSize: () => 85,
directDomUpdates: true,
//directDomUpdatesMode: 'position',

View file

@ -122,4 +122,17 @@ export const asErrorSerializedObject = <T, U>(obj: T): U => {
}
});
return cloned as unknown as U;
};
};
/**
* Get indexes of all elements in array that make the function return true
*
* @see https://stackoverflow.com/a/20798567/1469797
*/
export const getAllIndexes = <T>(arr: T[], truthyFunc: (val: T) => boolean) => {
var indexes = [], i: number;
for(i = 0; i < arr.length; i++)
if (truthyFunc(arr[i]))
indexes.push(i);
return indexes;
}