mirror of
https://github.com/captbaritone/webamp.git
synced 2026-01-23 02:15:01 +00:00
Improve landscape screens for scroll
This commit is contained in:
parent
f3054192e6
commit
608242b200
5 changed files with 210 additions and 4 deletions
|
|
@ -27,7 +27,7 @@ export async function logUserEvent(sessionId: string, event: UserEvent) {
|
|||
}
|
||||
}
|
||||
|
||||
type UserEvent =
|
||||
export type UserEvent =
|
||||
| {
|
||||
type: "session_start";
|
||||
}
|
||||
|
|
@ -35,6 +35,13 @@ type UserEvent =
|
|||
type: "session_end";
|
||||
reason: "unmount" | "before_unload";
|
||||
}
|
||||
/**
|
||||
* @deprecated
|
||||
*/
|
||||
| {
|
||||
type: "skin_view";
|
||||
skinMd5: string;
|
||||
}
|
||||
| {
|
||||
type: "skin_view_start";
|
||||
skinMd5: string;
|
||||
|
|
|
|||
|
|
@ -138,9 +138,16 @@ export default function SkinScroller({
|
|||
ref={setContainerRef}
|
||||
style={{
|
||||
height: "100vh",
|
||||
width: "100vw",
|
||||
maxWidth: "56.25vh", // 9:16 aspect ratio (100vh * 9/16)
|
||||
margin: "0 auto",
|
||||
overflowY: "scroll",
|
||||
scrollSnapType: "y mandatory",
|
||||
scrollbarWidth: "none", // Firefox
|
||||
msOverflowStyle: "none", // IE and Edge
|
||||
WebkitOverflowScrolling: "touch", // Smooth scrolling on iOS
|
||||
}}
|
||||
className="hide-scrollbar"
|
||||
>
|
||||
{skins.map((skin, i) => {
|
||||
return (
|
||||
|
|
|
|||
|
|
@ -1,14 +1,16 @@
|
|||
body {
|
||||
margin: 0; /* Remove default margin */
|
||||
height: 100vh; /* Set body height to viewport height */
|
||||
background-color: rgb(0, 0, 0);
|
||||
background-color: #1a1a1a; /* Dark charcoal instead of pure black */
|
||||
}
|
||||
|
||||
.scroller::-webkit-scrollbar {
|
||||
.scroller::-webkit-scrollbar,
|
||||
.hide-scrollbar::-webkit-scrollbar {
|
||||
display: none; /* Chrome, Safari, Edge */
|
||||
}
|
||||
|
||||
.scroller {
|
||||
.scroller,
|
||||
.hide-scrollbar {
|
||||
-ms-overflow-style: none; /* IE and Edge */
|
||||
scrollbar-width: none; /* Firefox */
|
||||
}
|
||||
|
|
|
|||
|
|
@ -36,6 +36,7 @@ import * as S3 from "./s3";
|
|||
import { generateDescription } from "./services/openAi";
|
||||
import KeyValue from "./data/KeyValue";
|
||||
import { postToBluesky } from "./tasks/bluesky";
|
||||
import { computeSkinRankings } from "./tasks/computeScrollRanking";
|
||||
|
||||
async function withHandler(
|
||||
cb: (handler: DiscordEventHandler) => Promise<void>
|
||||
|
|
@ -308,6 +309,14 @@ program
|
|||
console.table([await Skins.getStats()]);
|
||||
});
|
||||
|
||||
program
|
||||
.command("compute-scroll-ranking")
|
||||
.description("Analyze user event data and compute skin ranking scores.")
|
||||
.action(async () => {
|
||||
const rankings = await computeSkinRankings();
|
||||
console.log(JSON.stringify(rankings, null, 2));
|
||||
});
|
||||
|
||||
program
|
||||
.command("process-uploads")
|
||||
.description("Process any unprocessed user uploads.")
|
||||
|
|
|
|||
181
packages/skin-database/tasks/computeScrollRanking.ts
Normal file
181
packages/skin-database/tasks/computeScrollRanking.ts
Normal file
|
|
@ -0,0 +1,181 @@
|
|||
import { knex } from "../db";
|
||||
import type { UserEvent } from "../app/(modern)/scroll/Events";
|
||||
|
||||
const WEIGHTS = {
|
||||
viewDurationPerSecond: 0.1,
|
||||
like: 10,
|
||||
download: 5,
|
||||
share: 15,
|
||||
readmeExpand: 2,
|
||||
};
|
||||
|
||||
interface SessionAggregate {
|
||||
skinViewDurations: Map<string, number>;
|
||||
skinsLiked: Set<string>;
|
||||
skinsDownloaded: Set<string>;
|
||||
readmesExpanded: Set<string>;
|
||||
sharesSucceeded: Set<string>;
|
||||
}
|
||||
|
||||
interface SkinRanking {
|
||||
skinMd5: string;
|
||||
totalViewDurationMs: number;
|
||||
viewCount: number;
|
||||
averageViewDurationMs: number;
|
||||
likeCount: number;
|
||||
downloadCount: number;
|
||||
shareCount: number;
|
||||
readmeExpandCount: number;
|
||||
rankingScore: number;
|
||||
}
|
||||
|
||||
async function main() {
|
||||
try {
|
||||
const rankings = await computeSkinRankings();
|
||||
console.log(JSON.stringify(rankings, null, 2));
|
||||
} catch (error) {
|
||||
console.error("Error during aggregation:", error);
|
||||
throw error;
|
||||
} finally {
|
||||
await knex.destroy();
|
||||
}
|
||||
}
|
||||
|
||||
if (require.main === module) {
|
||||
main();
|
||||
}
|
||||
|
||||
export async function computeSkinRankings(): Promise<SkinRanking[]> {
|
||||
const sessionMap = await buildSessionAggregates();
|
||||
|
||||
const skinDataMap = new Map<
|
||||
string,
|
||||
{
|
||||
viewDurations: number[];
|
||||
likes: number;
|
||||
downloads: number;
|
||||
shares: number;
|
||||
readmeExpands: number;
|
||||
}
|
||||
>();
|
||||
|
||||
function getSkinData(skinMd5: string) {
|
||||
if (!skinDataMap.has(skinMd5)) {
|
||||
skinDataMap.set(skinMd5, {
|
||||
viewDurations: [],
|
||||
likes: 0,
|
||||
downloads: 0,
|
||||
shares: 0,
|
||||
readmeExpands: 0,
|
||||
});
|
||||
}
|
||||
return skinDataMap.get(skinMd5)!;
|
||||
}
|
||||
|
||||
for (const session of sessionMap.values()) {
|
||||
for (const [skinMd5, duration] of session.skinViewDurations) {
|
||||
getSkinData(skinMd5).viewDurations.push(duration);
|
||||
}
|
||||
for (const skinMd5 of session.skinsLiked) {
|
||||
getSkinData(skinMd5).likes++;
|
||||
}
|
||||
for (const skinMd5 of session.skinsDownloaded) {
|
||||
getSkinData(skinMd5).downloads++;
|
||||
}
|
||||
for (const skinMd5 of session.sharesSucceeded) {
|
||||
getSkinData(skinMd5).shares++;
|
||||
}
|
||||
for (const skinMd5 of session.readmesExpanded) {
|
||||
getSkinData(skinMd5).readmeExpands++;
|
||||
}
|
||||
}
|
||||
|
||||
const rankings: SkinRanking[] = [];
|
||||
|
||||
for (const [skinMd5, data] of skinDataMap) {
|
||||
const totalViewDurationMs = data.viewDurations.reduce(
|
||||
(sum, duration) => sum + duration,
|
||||
0
|
||||
);
|
||||
const viewCount = data.viewDurations.length;
|
||||
const averageViewDurationMs =
|
||||
viewCount > 0 ? totalViewDurationMs / viewCount : 0;
|
||||
|
||||
const rankingScore =
|
||||
(averageViewDurationMs / 1000) * WEIGHTS.viewDurationPerSecond +
|
||||
data.likes * WEIGHTS.like +
|
||||
data.downloads * WEIGHTS.download +
|
||||
data.shares * WEIGHTS.share +
|
||||
data.readmeExpands * WEIGHTS.readmeExpand;
|
||||
|
||||
rankings.push({
|
||||
skinMd5,
|
||||
totalViewDurationMs,
|
||||
viewCount,
|
||||
averageViewDurationMs,
|
||||
likeCount: data.likes,
|
||||
downloadCount: data.downloads,
|
||||
shareCount: data.shares,
|
||||
readmeExpandCount: data.readmeExpands,
|
||||
rankingScore,
|
||||
});
|
||||
}
|
||||
|
||||
rankings.sort((a, b) => b.rankingScore - a.rankingScore);
|
||||
return rankings;
|
||||
}
|
||||
|
||||
async function buildSessionAggregates(): Promise<
|
||||
Map<string, SessionAggregate>
|
||||
> {
|
||||
const events = await knex("user_log_events")
|
||||
.select("session_id", "timestamp", "metadata")
|
||||
.orderBy("timestamp", "asc");
|
||||
|
||||
const sessionMap = new Map<string, SessionAggregate>();
|
||||
|
||||
function getSession(sessionId: string): SessionAggregate {
|
||||
if (!sessionMap.has(sessionId)) {
|
||||
sessionMap.set(sessionId, {
|
||||
skinViewDurations: new Map(),
|
||||
skinsLiked: new Set(),
|
||||
skinsDownloaded: new Set(),
|
||||
readmesExpanded: new Set(),
|
||||
sharesSucceeded: new Set(),
|
||||
});
|
||||
}
|
||||
return sessionMap.get(sessionId)!;
|
||||
}
|
||||
|
||||
for (const row of events) {
|
||||
const event: UserEvent = JSON.parse(row.metadata);
|
||||
const session = getSession(row.session_id);
|
||||
|
||||
switch (event.type) {
|
||||
case "skin_view_end":
|
||||
session.skinViewDurations.set(event.skinMd5, event.durationMs);
|
||||
break;
|
||||
case "readme_expand":
|
||||
session.readmesExpanded.add(event.skinMd5);
|
||||
break;
|
||||
case "skin_download":
|
||||
session.skinsDownloaded.add(event.skinMd5);
|
||||
break;
|
||||
case "skin_like":
|
||||
if (event.liked) {
|
||||
session.skinsLiked.add(event.skinMd5);
|
||||
} else {
|
||||
session.skinsLiked.delete(event.skinMd5);
|
||||
}
|
||||
break;
|
||||
case "share_success":
|
||||
session.sharesSucceeded.add(event.skinMd5);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return sessionMap;
|
||||
}
|
||||
|
||||
export { buildSessionAggregates };
|
||||
export type { SessionAggregate };
|
||||
Loading…
Add table
Add a link
Reference in a new issue