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

Open
wants to merge 9 commits into
base: redesign-2024
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all 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
70 changes: 61 additions & 9 deletions src/app/components/pages/SubjectOverviewPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,21 @@ import { RouteComponentProps, withRouter } from "react-router";
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, useDeviceSize } from "../../services";
import { PageContextState, ShortcutResponse } from "../../../IsaacAppTypes";
import { ListView, ListViewCardProps, ListViewCards } from "../elements/list-groups/ListView";
import { LandingPageFooter } from "./SubjectLandingPage";
import { DifficultyIcon } from "../elements/svg/DifficultyIcons";
import { AbstractListViewItemState } from "../elements/list-groups/AbstractListViewItem";

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 All @@ -25,6 +29,7 @@ const SubjectCards = ({context}: { context: PageContextState }) => {
},
url: `/${context.subject}/11_14`,
stage: LEARNING_STAGE["11_TO_14"],
subject: context.subject,
},
{
item: {
Expand All @@ -37,6 +42,8 @@ const SubjectCards = ({context}: { context: PageContextState }) => {
},
url: `/${context.subject}/gcse`,
stage: LEARNING_STAGE.GCSE,
subject: context.subject,
state: context.subject === "biology" ? AbstractListViewItemState.COMING_SOON : undefined,
},
{
item: {
Expand All @@ -49,6 +56,7 @@ const SubjectCards = ({context}: { context: PageContextState }) => {
},
url: `/${context.subject}/a_level`,
stage: LEARNING_STAGE.A_LEVEL,
subject: context.subject,
},
{
item: {
Expand All @@ -61,14 +69,43 @@ const SubjectCards = ({context}: { context: PageContextState }) => {
},
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
subject: context.subject,
},
].map(({stage, ...card}) => (PHY_NAV_SUBJECTS[context.subject as Subject] as readonly LearningStage[])?.includes(stage) || card.state === AbstractListViewItemState.COMING_SOON ? card : null);

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, className }: { subject: Subject, className: string }) => {
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 className={className} items={items[subject]} /> : null;
};

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

Expand Down Expand Up @@ -112,15 +149,30 @@ export const SubjectOverviewPage = withRouter((props: RouteComponentProps) => {

<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/> */}
<div className="d-flex justify-content-center mt-4">
<ExampleQuestions className="w-100 w-md-75" subject={pageContext.subject} />
</div>

<LandingPageFooter context={pageContext} />
</div>}
Expand Down
5 changes: 5 additions & 0 deletions src/scss/phy/list-groups.scss
Original file line number Diff line number Diff line change
Expand Up @@ -147,4 +147,9 @@
margin-left: 0.5rem;
}
}

img {
// For items that use images rather than icons (e.g. subject logos), to remove the need to include a seperate greyscale image
filter: grayscale(100%);
}
}
Loading