Skip to content

Commit 669f3ab

Browse files
committed
Merge branch 'redesign-2024' into redesign/general-improvements-9
2 parents a440189 + 015bf60 commit 669f3ab

21 files changed

+173
-56
lines changed
Lines changed: 6 additions & 0 deletions
Loading
Lines changed: 6 additions & 0 deletions
Loading

src/app/components/content/IsaacInlineRegion.tsx

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -84,20 +84,21 @@ export const useInlineRegionPart = (pageQuestions: AppQuestionDTO[] | undefined)
8484
};
8585
};
8686

87-
export const submitInlineRegion = (inlineContext: ContextType<typeof InlineContext>, currentGameboard: GameboardDTO | undefined, currentUser: any, pageQuestions: AppQuestionDTO[] | undefined, dispatch: any, hidingAttempts : boolean) => {
87+
export const submitInlineRegion = async (inlineContext: ContextType<typeof InlineContext>, currentGameboard: GameboardDTO | undefined, currentUser: any, pageQuestions: AppQuestionDTO[] | undefined, dispatch: any, hidingAttempts : boolean) => {
8888
if (inlineContext && inlineContext.docId && pageQuestions) {
8989
inlineContext.setSubmitting(true);
90+
inlineContext.canShowWarningToast = true;
91+
if (Object.keys(inlineContext.elementToQuestionMap).length > 1) inlineContext.setFeedbackIndex(0);
92+
9093
const inlineQuestions = pageQuestions.filter(q => inlineContext.docId && q.id?.startsWith(inlineContext.docId) && q.id.includes("|inline-question:"));
9194
// we submit all modified answers, and those with undefined values. we must submit this latter group to get a validation response at the same time as the other answers
9295
const modifiedInlineQuestions = inlineQuestions.filter(q => (q.id && inlineContext.modifiedQuestionIds.includes(q.id)) || (q.currentAttempt?.value === undefined && (q.bestAttempt === undefined || hidingAttempts)));
9396
for (const inlineQuestion of modifiedInlineQuestions) {
94-
submitCurrentAttempt(
97+
await submitCurrentAttempt(
9598
{currentAttempt: inlineQuestion.currentAttempt},
9699
inlineQuestion.id as string, inlineQuestion.type as string, currentGameboard, currentUser, dispatch, inlineContext
97100
);
98101
}
99-
inlineContext.canShowWarningToast = true;
100-
if (Object.keys(inlineContext.elementToQuestionMap).length > 1) inlineContext.setFeedbackIndex(0);
101102
}
102103
};
103104

src/app/components/elements/PageTitle.tsx

Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -63,8 +63,25 @@ function AudienceViewer({audienceViews}: {audienceViews: ViewingContext[]}) {
6363
);
6464
}
6565

66+
interface IconPlaceholderProps extends React.HTMLAttributes<HTMLDivElement> {
67+
width: string;
68+
height: string;
69+
}
70+
71+
export const placeholderIcon = (props: IconPlaceholderProps): TitleIconProps => {
72+
const {width, height} = props;
73+
return {
74+
type: "placeholder",
75+
icon: undefined,
76+
height,
77+
width,
78+
};
79+
};
80+
6681
export interface TitleIconProps extends PhyHexIconProps {
67-
type: "img" | "hex";
82+
type: "img" | "hex" | "placeholder";
83+
height?: string;
84+
width?: string;
6885
}
6986

7087
export interface PageTitleProps {
@@ -121,8 +138,11 @@ export const PageTitle = ({currentPageTitle, displayTitleOverride, subTitle, des
121138
<div className="me-auto">
122139
<div className={classNames(siteSpecific("d-flex", "d-sm-flex"), "align-items-center")}>
123140
{icon && (
124-
icon.type === "img" ? <img src={icon.icon} alt="" className="me-3"/>
125-
: icon.type === "hex" ? <PhyHexIcon icon={icon.icon} subject={icon.subject}/> : undefined)}
141+
icon.type === "img" ? <img src={icon.icon} alt="" height={icon.height} width={icon.width} className="me-3"/>
142+
: icon.type === "hex" ? <PhyHexIcon icon={icon.icon} subject={icon.subject} style={{"height": icon.height, "width": icon.width}}/>
143+
: icon.type === "placeholder" ? <div style={{width: icon.width, height: icon.height}}/>
144+
: undefined
145+
)}
126146
<div className="me-auto" data-testid={"main-heading"}>
127147
{formatPageTitle(displayTitleOverride ?? currentPageTitle, disallowLaTeX)}
128148
{subTitle && <span className="h-subtitle d-none d-sm-block">{subTitle}</span>}

src/app/components/elements/TeacherDashboard.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -160,7 +160,7 @@ const BooksPanel = () => {
160160
</Col>)}
161161
</div>
162162
<Spacer/>
163-
<Link to="/publications" className="d-inline panel-link">See all books</Link>
163+
<Link to="/books" className="d-inline panel-link">See all books</Link>
164164
</div>;
165165
};
166166

src/app/components/elements/list-groups/AbstractListViewItem.tsx

Lines changed: 27 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -44,13 +44,18 @@ export interface ListViewTagProps extends HTMLAttributes<HTMLElement> {
4444
url?: string;
4545
}
4646

47-
const LinkTags = ({linkTags}: {linkTags: ListViewTagProps[];}) => {
47+
export interface LinkTagProps {
48+
linkTags: ListViewTagProps[];
49+
disabled?: boolean;
50+
}
51+
52+
const LinkTags = ({linkTags, disabled}: LinkTagProps) => {
4853
return <>
4954
{linkTags.map(t => {
5055
const {url, tag, ...rest} = t;
51-
return url ?
56+
return url && !disabled ?
5257
<Link {...rest} to={url} className="card-tag" key={tag}>{tag}</Link> :
53-
<div {...rest} className="card-tag" key={tag}>{tag}</div>;
58+
<div {...rest} className={classNames("card-tag", {"disabled": disabled})} key={tag}>{tag}</div>;
5459
})}
5560
</>;
5661
};
@@ -64,6 +69,12 @@ const QuizLinks = (props: React.HTMLAttributes<HTMLSpanElement> & {previewQuizUr
6469
{quizButton}
6570
</span>;
6671
};
72+
73+
export enum AbstractListViewItemState {
74+
COMING_SOON = "coming-soon",
75+
DISABLED = "disabled",
76+
}
77+
6778
export interface AbstractListViewItemProps extends ListGroupItemProps {
6879
title?: string;
6980
icon?: TitleIconProps;
@@ -81,11 +92,13 @@ export interface AbstractListViewItemProps extends ListGroupItemProps {
8192
quizButton?: JSX.Element;
8293
isCard?: boolean;
8394
fullWidth?: boolean;
95+
state?: AbstractListViewItemState;
8496
}
8597

86-
export const AbstractListViewItem = ({icon, title, subject, subtitle, breadcrumb, status, tags, supersededBy, linkTags, quizTag, url, audienceViews, previewQuizUrl, quizButton, isCard, fullWidth, ...rest}: AbstractListViewItemProps) => {
98+
export const AbstractListViewItem = ({icon, title, subject, subtitle, breadcrumb, status, tags, supersededBy, linkTags, quizTag, url, audienceViews, previewQuizUrl, quizButton, isCard, fullWidth, state, ...rest}: AbstractListViewItemProps) => {
8799
const deviceSize = useDeviceSize();
88100
const isQuiz: boolean = !!(previewQuizUrl || quizButton);
101+
const isDisabled = state && [AbstractListViewItemState.COMING_SOON, AbstractListViewItemState.DISABLED].includes(state);
89102

90103
fullWidth = fullWidth || below["sm"](deviceSize) || ((status || audienceViews || previewQuizUrl || quizButton) ? false : true);
91104
const cardBody =
@@ -94,8 +107,10 @@ export const AbstractListViewItem = ({icon, title, subject, subtitle, breadcrumb
94107
<div className="position-relative">
95108
{icon && (
96109
icon.type === "img" ? <img src={icon.icon} alt="" className="me-3"/>
97-
: icon.type === "hex" ? <PhyHexIcon icon={icon.icon} subject={icon.subject} size={icon.size}/> : undefined)
98-
}
110+
: icon.type === "hex" ? <PhyHexIcon icon={icon.icon} subject={icon.subject} size={icon.size}/>
111+
: icon.type === "placeholder" ? <div style={{width: icon.width, height: icon.height}}/>
112+
: undefined
113+
)}
99114
{status && status === CompletionState.ALL_CORRECT && <div className="list-view-status-indicator">
100115
<StatusDisplay status={status} showText={false} />
101116
</div>}
@@ -148,8 +163,12 @@ export const AbstractListViewItem = ({icon, title, subject, subtitle, breadcrumb
148163
}
149164
</div>;
150165

151-
return <ListGroupItem {...rest} className={classNames("content-summary-item", {"correct": status === CompletionState.ALL_CORRECT}, rest.className)} data-bs-theme={subject}>
152-
{url
166+
return <ListGroupItem
167+
{...rest}
168+
className={classNames("content-summary-item", {"correct": status === CompletionState.ALL_CORRECT}, rest.className, state)}
169+
data-bs-theme={subject && !isDisabled ? subject : "neutral"}
170+
>
171+
{url && !isDisabled
153172
? <Link to={url} className="w-100 h-100 align-items-start"> {cardBody} </Link>
154173
: cardBody
155174
}

src/app/components/elements/list-groups/ListView.tsx

Lines changed: 18 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
11
import React from "react";
2-
import { AbstractListViewItem, AbstractListViewItemProps, ListViewTagProps } from "./AbstractListViewItem";
2+
import { AbstractListViewItem, AbstractListViewItemProps, AbstractListViewItemState, ListViewTagProps } from "./AbstractListViewItem";
33
import { ShortcutResponse, ViewingContext } from "../../../../IsaacAppTypes";
44
import { determineAudienceViews } from "../../../services/userViewingContext";
55
import { DOCUMENT_TYPE, documentTypePathPrefix, getThemeFromContextAndTags, PATHS, SEARCH_RESULT_TYPE, siteSpecific, Subject, TAG_ID, TAG_LEVEL, tags } from "../../../services";
66
import { ListGroup, ListGroupItem, ListGroupProps } from "reactstrap";
77
import { TitleIconProps } from "../PageTitle";
88
import { AffixButton } from "../AffixButton";
9-
import { QuizSummaryDTO } from "../../../../IsaacApiTypes";
9+
import { GameboardDTO, QuizSummaryDTO } from "../../../../IsaacApiTypes";
1010
import { Link } from "react-router-dom";
1111
import { selectors, showQuizSettingModal, useAppDispatch, useAppSelector } from "../../../state";
1212
import classNames from "classnames";
@@ -16,16 +16,18 @@ export interface ListViewCardProps extends Omit<AbstractListViewItemProps, "icon
1616
icon?: TitleIconProps;
1717
subject?: Subject;
1818
linkTags?: ListViewTagProps[];
19+
state?: AbstractListViewItemState;
1920
url?: string;
2021
}
2122

22-
export const ListViewCard = ({item, icon, subject, linkTags, ...rest}: ListViewCardProps) => {
23+
export const ListViewCard = ({item, icon, subject, linkTags, state, ...rest}: ListViewCardProps) => {
2324
return <AbstractListViewItem
2425
icon={icon}
2526
title={item.title ?? ""}
2627
subject={subject}
2728
subtitle={item.subtitle}
2829
linkTags={linkTags}
30+
state={state}
2931
isCard
3032
{...rest}
3133
/>;
@@ -123,19 +125,27 @@ export const QuizListViewItem = ({item, isQuizSetter, useViewQuizLink, ...rest}:
123125
};
124126

125127
interface QuestionDeckListViewItemProps extends Omit<AbstractListViewItemProps, "icon" | "title" | "subject" | "subtitle" | "breadcrumb" | "url"> {
126-
item: ShortcutResponse;
128+
item: GameboardDTO;
127129
}
128130

129131
export const QuestionDeckListViewItem = ({item, ...rest}: QuestionDeckListViewItemProps) => {
130-
const breadcrumb = tags.getByIdsAsHierarchy((item.tags || []) as TAG_ID[]).map(tag => tag.title);
131-
const itemSubject = tags.getSpecifiedTag(TAG_LEVEL.subject, item.tags as TAG_ID[])?.id as Subject;
132+
const questionTagsCountMap = item.contents?.filter(c => c.contentType === "isaacQuestionPage").map(q => q.tags as TAG_ID[]).reduce((acc, tags) => {
133+
tags?.forEach(tag => {
134+
acc[tag] = (acc[tag] || 0) + 1;
135+
});
136+
return acc;
137+
}, {} as Record<TAG_ID, number>);
138+
139+
const questionSubjects = tags.allSubjectTags.filter(s => Object.keys(questionTagsCountMap || {}).includes(s.id));
140+
const questionTags = Object.entries(questionTagsCountMap || {}).filter(([tagId]) => tags.allTopicTags.includes(tags.getById(tagId as TAG_ID))).sort((a, b) => b[1] - a[1]).map(([tagId]) => tagId);
141+
142+
const breadcrumb = questionTags.map(tagId => tags.getById(tagId as TAG_ID)?.title).slice(0, 3);
132143
const url = `${PATHS.GAMEBOARD}#${item.id}`;
133144

134145
return <AbstractListViewItem
135146
icon={{type: "hex", icon: "icon-question-deck", size: "lg"}}
136147
title={item.title ?? ""}
137-
subject={itemSubject}
138-
subtitle={item.subtitle}
148+
subject={questionSubjects.length === 1 ? questionSubjects[0].id as Subject : undefined}
139149
breadcrumb={breadcrumb}
140150
url={url}
141151
{...rest}

src/app/components/elements/svg/PhyHexIcon.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import classNames from "classnames";
33
import { isPhy, Subject } from "../../../services";
44

55
export interface PhyHexIconProps extends React.HTMLAttributes<HTMLDivElement> {
6-
icon: string;
6+
icon: string | undefined;
77
subject?: Subject;
88
size?: "lg" | "xl";
99
}

src/app/components/pages/Generic.tsx

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,11 @@ export const Generic = withRouter(({pageIdOverride, match: {params}}: GenericPag
8181
const sidebar = React.cloneElement(PHY_SIDEBAR.has(pageId) ? PHY_SIDEBAR.get(pageId)!() : <GenericPageSidebar/>, { optionBar });
8282

8383
return <Container data-bs-theme={doc.subjectId}>
84-
<TitleAndBreadcrumb currentPageTitle={doc.title as string} subTitle={doc.subtitle} /> {/* TODO add page icon, replace main title with "General"?? */}
84+
<TitleAndBreadcrumb
85+
currentPageTitle={doc.title as string}
86+
subTitle={doc.subtitle}
87+
icon={{type: "hex", icon: "icon-generic"}}
88+
/>
8589
<MetaDescription description={doc.summary} />
8690
<SidebarLayout>
8791
{sidebar}

src/app/components/pages/SubjectLandingPage.tsx

Lines changed: 18 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,10 @@ import { ShowLoadingQuery } from "../handlers/ShowLoadingQuery";
1212
import { searchQuestions, useAppDispatch, useAppSelector, useGetNewsPodListQuery, useLazyGetEventsQuery } from "../../state";
1313
import { EventCard } from "../elements/cards/EventCard";
1414
import debounce from "lodash/debounce";
15-
import { Loading } from "../handlers/IsaacSpinner";
15+
import { IsaacSpinner } from "../handlers/IsaacSpinner";
1616
import classNames from "classnames";
1717
import { NewsCard } from "../elements/cards/NewsCard";
18+
import { placeholderIcon } from "../elements/PageTitle";
1819

1920

2021
const RandomQuestionBanner = ({context}: {context?: PageContextState}) => {
@@ -53,10 +54,6 @@ const RandomQuestionBanner = ({context}: {context?: PageContextState}) => {
5354
searchDebounce();
5455
}, [searchDebounce]);
5556

56-
if (!context || !isFullyDefinedContext(context) || !isSingleStageContext(context)) {
57-
return null;
58-
}
59-
6057
const question = questions?.[0];
6158

6259
return <div className="py-4 container-override random-question-panel">
@@ -67,7 +64,7 @@ const RandomQuestionBanner = ({context}: {context?: PageContextState}) => {
6764
<i className="icon icon-refresh icon-color-black"/>
6865
</button>
6966
</div>
70-
<Card className="w-100 px-0">
67+
<Card className="w-100 px-0 hf-6">
7168
{question
7269
? <ListView items={[{
7370
type: DOCUMENT_TYPE.QUESTION,
@@ -76,7 +73,10 @@ const RandomQuestionBanner = ({context}: {context?: PageContextState}) => {
7673
id: question.id,
7774
audience: question.audience,
7875
}]}/>
79-
: <Loading />}
76+
: <div className="w-100 d-flex justify-content-center">
77+
<IsaacSpinner size="sm" />
78+
</div>
79+
}
8080
</Card>
8181
</div>;
8282
};
@@ -114,7 +114,7 @@ export const LandingPageFooter = ({context}: {context: PageContextState}) => {
114114
</span>
115115
</div>
116116
</Link>)}
117-
{books.length > 2 && <Button tag={Link} color="keyline" to={`/publications`} className="btn mt-4 mx-5">View more books</Button>}
117+
{books.length > 2 && <Button tag={Link} color="keyline" to={`/books`} className="btn mt-4 mx-5">View more books</Button>}
118118
</Col>
119119
</>
120120
: <>
@@ -170,15 +170,19 @@ export const SubjectLandingPage = withRouter((props: RouteComponentProps) => {
170170
icon={pageContext?.subject ? {
171171
type: "img",
172172
subject: pageContext.subject,
173-
icon: `/assets/phy/icons/redesign/subject-${pageContext.subject}.svg`
174-
} : undefined}
173+
icon: `/assets/phy/icons/redesign/subject-${pageContext.subject}.svg`,
174+
width: "70px",
175+
height: "81px",
176+
} : placeholderIcon({width: "70px", height: "81px"})}
175177
/>
176178

177-
<RandomQuestionBanner context={pageContext} />
178-
179-
<ListViewCards cards={getLandingPageCardsForContext(pageContext, below['md'](deviceSize))} showBlanks={!below['md'](deviceSize)} className="my-5" />
179+
{pageContext && isSingleStageContext(pageContext) && <>
180+
<RandomQuestionBanner context={pageContext} />
180181

181-
<LandingPageFooter context={pageContext} />
182+
<ListViewCards cards={getLandingPageCardsForContext(pageContext, below['md'](deviceSize))} showBlanks={!below['md'](deviceSize)} className="my-5" />
183+
184+
<LandingPageFooter context={pageContext} />
185+
</>}
182186

183187

184188
</Container>;

src/app/components/pages/subjectLandingPageComponents.ts

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { PageContextState } from "../../../IsaacAppTypes";
22
import { BookInfo, getHumanContext, interleave, ISAAC_BOOKS, ISAAC_BOOKS_BY_TAG, isFullyDefinedContext, isSingleStageContext, PHY_NAV_SUBJECTS } from "../../services";
3-
import { ListViewTagProps } from "../elements/list-groups/AbstractListViewItem";
3+
import { AbstractListViewItemState, ListViewTagProps } from "../elements/list-groups/AbstractListViewItem";
44
import { ListViewCardProps } from "../elements/list-groups/ListView";
55

66
export const extendUrl = (context: NonNullable<Required<PageContextState>>, page: string) => {
@@ -57,7 +57,8 @@ const LessonsAndRevisionCard = (context: NonNullable<Required<PageContextState>>
5757
},
5858
icon: {type: "hex", icon: "icon-revision"},
5959
subject: context.subject,
60-
linkTags: [{tag: "List of revision areas", url: extendUrl(context, 'lessons')}]
60+
linkTags: [{tag: "List of revision areas", url: extendUrl(context, 'revision')}],
61+
state: AbstractListViewItemState.COMING_SOON,
6162
});
6263

6364
const CoreSkillsCard = (context: NonNullable<Required<PageContextState>>): ListViewCardProps => ({
@@ -93,14 +94,15 @@ const BookCard = (book: BookInfo, description: string) => (context: NonNullable<
9394
const StepIntoPhyCard = BookCard(ISAAC_BOOKS_BY_TAG["phys_book_step_into"], "Discover secondary physics ideas and interesting experiments. Aimed at students in years 7 and 8.");
9495
const StepUpPhyCard = BookCard(ISAAC_BOOKS_BY_TAG["phys_book_step_up"], "Build a strong foundation in physics. Aimed at students in year 9.");
9596

96-
const ArbitraryPageLinkCard = (title: string, subtitle: string, linkTags: ListViewTagProps[]) => (context: NonNullable<Required<PageContextState>>): ListViewCardProps => ({
97+
const ArbitraryPageLinkCard = (title: string, subtitle: string, linkTags: ListViewTagProps[], state?: AbstractListViewItemState) => (context: NonNullable<Required<PageContextState>>): ListViewCardProps => ({
9798
item: {
9899
title,
99100
subtitle
100101
},
101102
icon: {type: "hex", icon: "icon-revision"},
102103
subject: context.subject,
103-
linkTags
104+
linkTags,
105+
state,
104106
});
105107

106108
const AnvilAppsCard = (context: NonNullable<Required<PageContextState>>): ListViewCardProps => {
@@ -132,7 +134,7 @@ const BiologyExtensionQuestionsCard = (context: NonNullable<Required<PageContext
132134
};
133135

134136
const MathsUniCard = (context: NonNullable<Required<PageContextState>>): ListViewCardProps => {
135-
return ArbitraryPageLinkCard(context.subject === "maths" ? "Revision" : `Maths revision for ${context.subject}`, `Refresh your maths skills in preparation for ${context.subject} at university.`, [{tag: "List of revision areas", url: extendUrl(context, "")}])(context);
137+
return ArbitraryPageLinkCard(context.subject === "maths" ? "Revision" : `Maths revision for ${context.subject}`, `Refresh your maths skills in preparation for ${context.subject} at university.`, [{tag: "List of revision areas", url: extendUrl(context, "")}], AbstractListViewItemState.COMING_SOON)(context);
136138
};
137139

138140
const subjectSpecificCardsMap: {[subject in keyof typeof PHY_NAV_SUBJECTS]: {[stage in typeof PHY_NAV_SUBJECTS[subject][number]]: (LandingPageCard | null)[]}} = {

0 commit comments

Comments
 (0)