Skip to content

Subject Overview Page cleanup #1475

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 9 commits into from
May 28, 2025
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@ export const AbstractListViewItem = ({icon, title, subject, subtitle, breadcrumb
fullWidth = fullWidth || below["sm"](deviceSize) || ((status || audienceViews || previewQuizUrl || quizButton) ? false : true);
const cardBody =
<div className="w-100 d-flex flex-row">
<Col className={classNames("d-flex flex-grow-1", {"mt-3": isCard && linkTags?.length, "mb-3": isCard && !linkTags?.length})}>
<Col className={classNames("d-flex flex-grow-1", {"mt-3": isCard, "mb-3": isCard && !linkTags?.length})}>
<div className="position-relative">
{icon && (
icon.type === "img" ? <img src={icon.icon} alt="" className="me-3"/>
Expand Down
33 changes: 28 additions & 5 deletions src/app/components/elements/svg/DifficultyIcons.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import {difficultyLabelMap, difficultyShortLabelMap, isAda, isPhy, siteSpecific}
import classnames from "classnames";
import {Rectangle} from "./Rectangle";
import {Circle} from "./Circle";
import classNames from "classnames";

// Difficulty icon proportions
const difficultyIconWidth = siteSpecific(15, 25);
Expand All @@ -13,6 +14,8 @@ const yPadding = 2;
const difficultyCategoryLevels = siteSpecific([1, 2, 3], [1, 2]);
const miniHexagon = calculateHexagonProportions(difficultyIconWidth / 2, 0);
const miniSquare = {width: difficultyIconWidth, height: difficultyIconWidth};
const largeHexagon = calculateHexagonProportions(difficultyIconWidth, 0);
const largeSquare = {width: difficultyIconWidth * 2, height: difficultyIconWidth * 2};

const squareOffset = ((miniHexagon.quarterHeight * 4 + 2 * yPadding) - difficultyIconWidth) / 2 - 1; // ${yPadding + (difficultyCategory === "P" && isPhy ? 0 : 2)}

Expand All @@ -21,16 +24,19 @@ interface DifficultyIconShapeProps {
difficultyCategoryLevel: number;
active: boolean;
blank?: boolean;
size?: "sm" | "lg";
}
function SingleDifficultyIconShape({difficultyCategory, difficultyCategoryLevel, active, blank}: DifficultyIconShapeProps) {

function SingleDifficultyIconShape({difficultyCategory, difficultyCategoryLevel, active, blank, size}: DifficultyIconShapeProps) {
const iconWidth = size === "lg" ? difficultyIconWidth * 2 : difficultyIconWidth;
// FIXME the calculations here need refactoring, had to rush them to get it done
return <g transform={`translate(${(difficultyCategoryLevel - 1) * (difficultyIconWidth + 2 * difficultyIconXPadding) + siteSpecific(0, 1)}, ${isAda ? yPadding + 2 : difficultyCategory === "P" ? 0 : squareOffset})`}>
return <g transform={`translate(${(difficultyCategoryLevel - 1) * (iconWidth + 2 * difficultyIconXPadding) + siteSpecific(0, 1)}, ${isAda ? yPadding + 2 : difficultyCategory === "P" ? 0 : squareOffset})`}>
{difficultyCategory === "P" ?
siteSpecific(
<Hexagon {...miniHexagon} className={"hex difficulty practice " + classnames({active})} />,
<Circle radius={difficultyIconWidth / 2} className={"hex difficulty practice " + classnames({active})} />
<Hexagon {...(size === "lg" ? largeHexagon : miniHexagon)} className={"hex difficulty practice " + classnames({active})} />,
<Circle radius={iconWidth / 2} className={"hex difficulty practice " + classnames({active})} />
) :
<Rectangle {...miniSquare} className={"square difficulty challenge " + classnames({active})} />
<Rectangle {...(size === "lg" ? largeSquare : miniSquare)} className={"square difficulty challenge " + classnames({active})} />
}
{/* {<foreignObject width={difficultyIconWidth} height={difficultyIconWidth + (difficultyCategory === "P" && isPhy ? yPadding + 2 : siteSpecific(0, 1))}>
<div aria-hidden={"true"} className={`difficulty-title difficulty-icon-title ${classnames({active})} difficulty-${difficultyCategoryLevel}`}>
Expand Down Expand Up @@ -64,3 +70,20 @@ export function DifficultyIcons({difficulty, blank, className} : {difficulty: Di
</svg>
</div>;
}

export function DifficultyIcon({difficultyCategory, className} : {difficultyCategory: string, className?: string}) {
return <div className={classNames(className, "d-inline-flex ps-1 pe-1")}>
<svg
className="d-flex"
role={"img"}
width={`${difficultyIconWidth * 2}px`}
height={`${largeHexagon.quarterHeight * 4 + 2 * yPadding}px`}
{...(isPhy && {viewBox: `0 0 ${difficultyIconWidth * 2} ${largeHexagon.quarterHeight * 4}`})}
transform="translate(0,5)"
>
<SingleDifficultyIconShape
difficultyCategoryLevel={1} active difficultyCategory={difficultyCategory} size="lg"
/>
</svg>
</div>;
}
2 changes: 1 addition & 1 deletion src/app/components/pages/SubjectLandingPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ export const LandingPageFooter = ({context}: {context: PageContextState}) => {
// TODO: are we going to make subject-specific news?
const {data: news} = useGetNewsPodListQuery({subject: "physics"});

return <Row className={classNames("mt-5 py-4 row-cols-1 row-cols-md-2")}>
return <Row className={classNames("mt-2 py-4 row-cols-1 row-cols-md-2")}>
<div className="d-flex flex-column mt-3">
{/* if there are books, display books. otherwise, display news */}
{books.length > 0
Expand Down
76 changes: 67 additions & 9 deletions src/app/components/pages/SubjectOverviewPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,20 @@
import { Container } from "reactstrap";
import { TitleAndBreadcrumb } from "../elements/TitleAndBreadcrumb";
import { useUrlPageTheme } from "../../services/pageContext";
import { HUMAN_SUBJECTS, isDefined, LEARNING_STAGE, LearningStage, PHY_NAV_SUBJECTS, Subject } from "../../services";
import { PageContextState } from "../../../IsaacAppTypes";
import { ListViewCards } from "../elements/list-groups/ListView";
import { above, HUMAN_SUBJECTS, isDefined, LEARNING_STAGE, LearningStage, PHY_NAV_SUBJECTS, SEARCH_RESULT_TYPE, subject, Subject, SUBJECTS, useDeviceSize } from "../../services";
import { PageContextState, ShortcutResponse } from "../../../IsaacAppTypes";
import { ListView, ListViewCardProps, ListViewCards, QuestionDeckListViewItem } from "../elements/list-groups/ListView";
import { LandingPageFooter } from "./SubjectLandingPage";
import { DifficultyIcon } from "../elements/svg/DifficultyIcons";

const SubjectCards = ({context}: { context: PageContextState }) => {
const deviceSize = useDeviceSize();

if (!isDefined(context?.subject)) return null;

const humanSubject = context?.subject && HUMAN_SUBJECTS[context.subject];

return <ListViewCards showBlanks cards={[
const cards: (ListViewCardProps | null)[] = [
{
item: {
title: "11-14",
Expand Down Expand Up @@ -61,14 +64,56 @@
},
url: `/${context.subject}/university`,
stage: LEARNING_STAGE.UNIVERSITY,
}
]
.map(({stage, ...card}) => (PHY_NAV_SUBJECTS[context.subject as Subject] as readonly LearningStage[])?.includes(stage) ? card : null)
.filter((x, i, a) => x || (i % 2 === 0 ? a[i + 1] : a[i - 1])) // remove pairs of nulls
},
].map(({stage, ...card}) => (PHY_NAV_SUBJECTS[context.subject as Subject] as readonly LearningStage[])?.includes(stage) ? card : null);

if (context.subject === "biology") {
cards.push({
item: {
title: "GCSE (COMING SOON)",
subtitle: `Our GCSE ${humanSubject} resources develop the ${humanSubject} knowledge needed at GCSE through the use of questions, concepts and books.`
},
className: "disabled",
icon: {
type: "img" as const,
icon: `/assets/phy/icons/redesign/subject-${context.subject}.svg`,
}
});
}

return <ListViewCards showBlanks={above["lg"](deviceSize)} cards={cards
.sort((a, b) => a ? (b ? 0 : -1) : 1) // put nulls at the end
.filter((x, i, a) => x || (i % 2 === 0 ? a[i + 1] : a[i - 1])) // remove pairs of nulls
} />;
};

const ExampleQuestions = ({ subject }: { subject: Subject }) => {
const items: { [key in Subject]: ShortcutResponse[] } = {
maths: [{
title: "Sample Maths Questions",
type: SEARCH_RESULT_TYPE.GAMEBOARD,
id: "sample_maths_questions",
}],
physics: [/*{
title: "Sample Physics Questions",
type: SEARCH_RESULT_TYPE.GAMEBOARD,
id: "sample_phy_questions",
}*/], // Uncomment when physics questions are available
chemistry: [{
title: "Sample Chemistry Questions",
type: SEARCH_RESULT_TYPE.GAMEBOARD,
id: "sample_chem_questions",
}],
biology: [{
title: "Sample Biology Questions",
type: SEARCH_RESULT_TYPE.GAMEBOARD,
id: "sample_bio_questions",
}],
};

return items[subject].length > 0 ? <ListView items={items[subject]} /> : null;
};

export const SubjectOverviewPage = withRouter((props: RouteComponentProps) => {
const pageContext = useUrlPageTheme();

Expand Down Expand Up @@ -112,15 +157,28 @@

<p className="mt-3">
All Isaac Science questions are classed as either &quot;Practice&quot; or &quot;Challenge&quot; – indicated by the symbols below.
</p>

<div className="d-flex flex-row w-100 justify-content-center">
<div className="d-flex flex-column me-3 align-items-center">
<DifficultyIcon difficultyCategory="P"/>
<span>Practice</span>
</div>
<div className="d-flex flex-column align-items-center">
<DifficultyIcon difficultyCategory="C"/>
<span>Challenge</span>
</div>
</div>

<p className="mt-3">
In Isaac {humanSubject},
<ul>
<li>Practice questions are those that require one concept or equation to solve.</li>
<li>Challenge questions are those that require one or more concepts, or require more creativity to solve the problem, helping to develop important problem solving skills. </li>
</ul>
</p>

{/* <ExampleQuestions/> */}
<ExampleQuestions subject={pageContext.subject} />

<LandingPageFooter context={pageContext} />
</div>}
Expand Down
24 changes: 24 additions & 0 deletions src/scss/phy/list-groups.scss
Original file line number Diff line number Diff line change
Expand Up @@ -24,13 +24,25 @@
border-radius: 0;
background-color: transparent;

&.disabled {
background-color: $color-neutral-200;
}

@include media-breakpoint-down(lg) {
&:not(:last-child) {
border-bottom: 1px solid var(--border-light);
}
&:not(:first-child) {
border-top: 1px solid var(--border-light);
}
&:nth-child(1) {
border-top-left-radius: inherit;
border-top-right-radius: inherit;
}
&:nth-last-child(1) {
border-bottom-left-radius: inherit;
border-bottom-right-radius: inherit;
}
}

@include media-breakpoint-up(lg) {
Expand All @@ -46,6 +58,18 @@
&:nth-last-child(n+3) {
border-bottom: 1px solid var(--border-light);
}
&:nth-child(1) {
border-top-left-radius: inherit;
}
&:nth-child(2) {
border-top-right-radius: inherit;
}
&:nth-last-child(2) {
border-bottom-left-radius: inherit;
}
&:nth-last-child(1) {
border-bottom-right-radius: inherit;
}
}
}
}
Expand Down
Loading