Skip to content

Commit

Permalink
Analysis, popper ux, keyword, and copy tweaks
Browse files Browse the repository at this point in the history
  • Loading branch information
justinsilvestre committed Nov 25, 2024
1 parent b171c97 commit 011a964
Show file tree
Hide file tree
Showing 15 changed files with 489 additions and 320 deletions.
49 changes: 46 additions & 3 deletions app/components/FigurePopover.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
import clsx from "clsx";
import { PropsWithChildren, useEffect, createElement, useState } from "react";
import {
PropsWithChildren,
useEffect,
createElement,
useState,
useRef,
} from "react";
import { createPortal } from "react-dom";
import { useFetcher } from "react-router";

Expand Down Expand Up @@ -79,6 +85,31 @@ export function useFigurePopover({
const popper = usePaddedPopper();
const { setReferenceElement, isOpen, open, close, update } = popper;

const [isClosing, setClosing] = useState(false);
const closeWarnTimer = useRef<number | null>(null);
const closeTimer = useRef<number | null>(null);
const scheduleClose = () => {
cancelClose();

closeWarnTimer.current = window.setTimeout(() => {
setClosing(true);
closeTimer.current = window.setTimeout(() => {
close();
}, 2000);
}, 15000);
};
const cancelClose = () => {
if (closeWarnTimer.current) {
window.clearTimeout(closeWarnTimer.current);
closeWarnTimer.current = null;
}
if (closeTimer.current) {
window.clearTimeout(closeTimer.current);
closeTimer.current = null;
}
setClosing(false);
};

const { fetcher, loadFigure, badgeProps } =
usePopoverFigureFetcher(initialBadgeProps);

Expand Down Expand Up @@ -108,10 +139,17 @@ export function useFigurePopover({
open();
}
},
onMouseLeave: () => {
scheduleClose();
},
onMouseOver: () => {
cancelClose();
},
});

return {
figure: fetchedFigure,
isClosing,
loadFigure,
fetcher,
badgeProps,
Expand All @@ -127,12 +165,14 @@ export function FigurePopoverWindow({
figure,
popper,
fetcher,
isClosing,
}: {
badgeProps?: BadgeProps | null;
loadFigure: ReturnType<typeof usePopoverFigureFetcher>["loadFigure"];
figure: PopoverFigure | undefined;
popper: ReturnType<typeof usePaddedPopper>;
fetcher: ReturnType<typeof usePopoverFigureFetcher>["fetcher"];
isClosing?: boolean;
}) {
const firstClassComponents = figure?.firstClassComponents;
const headingsMeanings = figure ? getHeadingsMeanings(figure) : null;
Expand All @@ -145,7 +185,7 @@ export function FigurePopoverWindow({

return (
// eslint-disable-next-line jsx-a11y/no-static-element-interactions, jsx-a11y/click-events-have-key-events
(<div
<div
ref={setPopperElement}
style={styles.popper}
{...attributes.popper}
Expand All @@ -154,6 +194,9 @@ export function FigurePopoverWindow({
<div
className={clsx(
popoverFadeinStyles.fadeIn,
isClosing
? "opacity-0 [transition:opacity_2s] hover:opacity-100 hover:[transition:opacity_.2s]"
: "",
`[border:2px inset #afafaf33] pointer-events-auto max-w-xl bg-gray-50/90 p-3 shadow-xl shadow-black/30 backdrop-blur-sm [border-radius:0.3em] [box-sizing:border-box] [max-height:88v] `,
)}
>
Expand Down Expand Up @@ -234,7 +277,7 @@ export function FigurePopoverWindow({
/>
</div>
</div>
</div>)
</div>
);
}

Expand Down
11 changes: 2 additions & 9 deletions app/components/fadeInOut.module.css
Original file line number Diff line number Diff line change
Expand Up @@ -2,26 +2,19 @@
0% {
opacity: 0;
pointer-events: none;
transform: scaleY(0%) scaleX(0%)
}

80% {
opacity: 1;
pointer-events: none;
transform: scaleY(1)

}
81% {
pointer-events: all;
}
100% {
opacity: 1;

}
}

.fadeIn {
animation: fadeIn 0.4s;

animation: fadeIn 0.3s;
}

.fadeOut {
Expand Down
5 changes: 2 additions & 3 deletions app/components/popoverFadeIn.module.css
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
@keyframes fadeIn {
from {
opacity: 0;
transform: scaleY(0) translateY(-.25em);
transform: scaleY(0) translateY(-3em);
}
to {
opacity: 1;
Expand All @@ -11,7 +11,6 @@

.fadeIn {
transform-origin: top;
transition: opacity;
animation-name: fadeIn;
animation-duration: .5s;
animation-duration: .3s;
}
145 changes: 19 additions & 126 deletions app/features/browse/getAtomicFigureBadgeFigures.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,8 @@
import { PrismaClient } from "@prisma/client";
import type { PrismaClient } from "@prisma/client";

import {
BadgeProps,
badgeFigureSelect,
getBadgeProps,
} from "~/features/dictionary/badgeFigure";
import {
FIGURES_VERSION,
FigureKey,
getLatestFigureId,
parseFigureId,
} from "~/models/figure";
import { FIGURES_VERSION } from "~/models/figure";

import { getBadgeFiguresByPriorityGroupedWithVariants } from "./getBadgeFiguresByPriorityGroupedWithVariants";

const isPriorityComponentWhere = {
isPriority: true,
Expand All @@ -25,120 +17,21 @@ const isPriorityComponentWhere = {
};

export async function getAtomicFigureBadgeFigures(prisma: PrismaClient) {
const priorityAtomicComponents = await prisma.kanjisenseFigure.findMany({
select: {
...badgeFigureSelect,
keyword: true,
mnemonicKeyword: true,
image: true,
},
orderBy: { aozoraAppearances: "desc" },
where: {
version: FIGURES_VERSION,
OR: [
{
...isPriorityComponentWhere,
componentsTree: { equals: [] },
},
{
listsAsCharacter: { isEmpty: false },
componentsTree: { equals: [] },
},
],
},
});

type QueriedFigure = (typeof priorityAtomicComponents)[number];

const atomicFiguresMap: Record<FigureKey, QueriedFigure> = {};
const nonAtomicVariantsMap: Record<FigureKey, QueriedFigure> = {};
const variantGroupHeads = new Set<string>();

const primaryVariantToRanking: Record<FigureKey, number> = {};

for (const figure of priorityAtomicComponents) {
atomicFiguresMap[figure.key] = figure;
if (figure.variantGroupId) variantGroupHeads.add(figure.variantGroupId);
else primaryVariantToRanking[figure.key] = figure.aozoraAppearances ?? 0;
}
const variantGroupsRankings = await prisma.kanjisenseFigure.groupBy({
by: ["variantGroupId"],
where: {
variantGroupId: { in: [...variantGroupHeads] },
},
_sum: { aozoraAppearances: true },
});
for (const group of variantGroupsRankings) {
const variantGroupKey = parseFigureId(group.variantGroupId!).key;
const appearances = group._sum?.aozoraAppearances ?? 0;
primaryVariantToRanking[variantGroupKey] = appearances ?? 0;
}

const variantFigures = await prisma.kanjisenseVariantGroup.findMany({
where: { id: { in: [...variantGroupHeads] } },
include: {
figures: {
where: {
version: FIGURES_VERSION,
key: {
notIn: [...priorityAtomicComponents.map((figure) => figure.key)],
},
listsAsComponent: { isEmpty: false },
},
select: {
...badgeFigureSelect,
keyword: true,
mnemonicKeyword: true,
image: true,
},
return await getBadgeFiguresByPriorityGroupedWithVariants(prisma, {
version: FIGURES_VERSION,
OR: [
{
...isPriorityComponentWhere,
componentsTree: { equals: [] },
},
},
{
listsAsCharacter: { isEmpty: false },
componentsTree: { equals: [] },
},
// {
// isStandaloneCharacter: true,
// componentsTree: { equals: [] },
// },
],
});
for (const group of variantFigures) {
for (const figure of group.figures) {
nonAtomicVariantsMap[figure.key] = figure;
}
}

const groups: {
key: string;
appearances: number;
keyword: string;
mnemonicKeyword: string | null;
figures: {
isAtomic: boolean;
figure: BadgeProps;
}[];
}[] = [];
for (const [key, ranking] of Object.entries(primaryVariantToRanking)) {
const isGroup = variantGroupHeads.has(getLatestFigureId(key));
const figures = isGroup
? variantFigures
.find((group) => group.key === key)!
.variants.flatMap(
(v) => atomicFiguresMap[v] || nonAtomicVariantsMap[v] || [],
)
: [atomicFiguresMap[key] || nonAtomicVariantsMap[key]];

const groupHead = figures[0];
if (!groupHead) console.error("No group head for", key);

groups.push({
key,
appearances: ranking,
keyword: groupHead.keyword,
mnemonicKeyword: groupHead.mnemonicKeyword,
figures: figures.map((figure) => ({
isAtomic: Boolean(atomicFiguresMap[figure.key]),
figure: getBadgeProps(figure),
})),
});
}

return {
atomicComponentsAndVariants: groups.sort(
(a, b) => b.appearances - a.appearances,
),
totalAtomicComponents: priorityAtomicComponents.length,
};
}
Loading

0 comments on commit 011a964

Please sign in to comment.