From e52de82fffcc14222e6b21cc30cdf182b41df5d2 Mon Sep 17 00:00:00 2001 From: Jacob Brown Date: Thu, 13 Mar 2025 09:55:02 +0000 Subject: [PATCH 1/7] Improve concepts listing sidebar Fix spacing issues, incorrect count for subjects, and All Filters checkbox when no fields exist --- .../elements/inputs/UserContextPicker.tsx | 7 ++-- .../elements/layout/SidebarLayout.tsx | 41 ++++++++++++------- src/app/components/pages/Concepts.tsx | 4 +- 3 files changed, 32 insertions(+), 20 deletions(-) diff --git a/src/app/components/elements/inputs/UserContextPicker.tsx b/src/app/components/elements/inputs/UserContextPicker.tsx index c55766dd70..77f3a5efbd 100644 --- a/src/app/components/elements/inputs/UserContextPicker.tsx +++ b/src/app/components/elements/inputs/UserContextPicker.tsx @@ -17,6 +17,7 @@ import { useUserViewingContext } from "../../../services"; import {selectors, transientUserContextSlice, useAppDispatch, useAppSelector,} from "../../../state"; +import classNames from "classnames"; const contextExplanationMap: {[key in CONTEXT_SOURCE]: string} = { [CONTEXT_SOURCE.TRANSIENT]: "these context picker settings", @@ -62,7 +63,7 @@ export const UserContextPicker = ({className, hideLabels = true}: {className?: s return {/* Stage Selector */} - +
{!hideLabels && } {!userContext.hasDefaultPreferences && (userContext.explanation.stage == CONTEXT_SOURCE.TRANSIENT || userContext.explanation.examBoard == CONTEXT_SOURCE.TRANSIENT) &&
-
+
; } diff --git a/src/app/components/elements/layout/SidebarLayout.tsx b/src/app/components/elements/layout/SidebarLayout.tsx index f699058795..c97ec80ad6 100644 --- a/src/app/components/elements/layout/SidebarLayout.tsx +++ b/src/app/components/elements/layout/SidebarLayout.tsx @@ -226,12 +226,17 @@ const FilterCheckbox = (props : FilterCheckboxProps) => { />; }; -const AllFiltersCheckbox = (props: Omit) => { - const { conceptFilters, setConceptFilters, tagCounts, baseTag, ...rest } = props; +const AllFiltersCheckbox = (props: Omit & {forceEnabled?: boolean}) => { + const { conceptFilters, setConceptFilters, tagCounts, baseTag, forceEnabled, ...rest } = props; const [previousFilters, setPreviousFilters] = useState(baseTag ? [baseTag] : []); return a + b, 0)} + id="all" checked={forceEnabled || baseTag ? conceptFilters.length === 1 && conceptFilters[0] === baseTag : !conceptFilters.length} + checkboxTitle="All" count={tagCounts && (baseTag ? tagCounts[baseTag.id] : Object.values(tagCounts).reduce((a, b) => a + b, 0))} onInputChange={(e) => { + if (forceEnabled) { + setConceptFilters(baseTag ? [baseTag] : []); + return; + } if (e.target.checked) { setPreviousFilters(conceptFilters); setConceptFilters(baseTag ? [baseTag] : []); @@ -272,19 +277,25 @@ export const SubjectSpecificConceptListSidebar = (props: ConceptListSidebarProps
Filter by topic
- + !isDefined(tagCounts) || tagCounts[tag.id] > 0).length === 0} + />
- {applicableTags.map(tag => - - )} + {applicableTags + .filter(tag => !isDefined(tagCounts) || tagCounts[tag.id] > 0) + .map(tag => + + ) + }
diff --git a/src/app/components/pages/Concepts.tsx b/src/app/components/pages/Concepts.tsx index 0cb450792f..9a42295633 100644 --- a/src/app/components/pages/Concepts.tsx +++ b/src/app/components/pages/Concepts.tsx @@ -29,7 +29,7 @@ export const Concepts = withRouter((props: RouteComponentProps) => { }; const applicableTags = pageContext?.subject ? tags.getDirectDescendents(subjectToTagMap[pageContext.subject]) : tags.allFieldTags; - const tagCounts : Record = applicableTags.reduce((acc, t) => ({...acc, [t.id]: concepts?.filter(c => c.tags?.includes(t.id)).length || 0}), {}); + const tagCounts : Record = [...applicableTags, ...(pageContext?.subject ? [tags.getById(pageContext?.subject as TAG_ID)] : [])].reduce((acc, t) => ({...acc, [t.id]: concepts?.filter(c => c.tags?.includes(t.id)).length || 0}), {}); useEffect(() => { if (pageContext) { @@ -105,7 +105,7 @@ export const Concepts = withRouter((props: RouteComponentProps) => { : } - {isPhy &&
+ {isPhy &&
{shortcutAndFilteredSearchResults &&
Showing {shortcutAndFilteredSearchResults.length} results
} From 0159ad449f2322555ee89b28f73c929fae10d320 Mon Sep 17 00:00:00 2001 From: Jacob Brown Date: Thu, 13 Mar 2025 09:55:15 +0000 Subject: [PATCH 2/7] Remove duplicate related content underneath concepts --- src/app/components/pages/Concept.tsx | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/app/components/pages/Concept.tsx b/src/app/components/pages/Concept.tsx index 3b0c6a15ce..9c7c73e953 100644 --- a/src/app/components/pages/Concept.tsx +++ b/src/app/components/pages/Concept.tsx @@ -102,8 +102,6 @@ export const Concept = withRouter(({match: {params}, location: {search}, concept {isAda && doc.relatedContent && } - - {isPhy && doc.relatedContent && } From 330ca6a730d8e2b659ac4654c45a02bebd3d2403 Mon Sep 17 00:00:00 2001 From: Jacob Brown Date: Thu, 13 Mar 2025 09:58:13 +0000 Subject: [PATCH 3/7] Add lightbulb icon after in-content concept links Note that this only works on live! `localLink` is false on dev as the content links to the live site. --- .../components/elements/markup/markdownRendering.ts | 3 ++- src/scss/phy/typography.scss | 11 +++++++++++ 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/src/app/components/elements/markup/markdownRendering.ts b/src/app/components/elements/markup/markdownRendering.ts index 9c2872a4f1..f343f2f156 100644 --- a/src/app/components/elements/markup/markdownRendering.ts +++ b/src/app/components/elements/markup/markdownRendering.ts @@ -6,8 +6,9 @@ MARKDOWN_RENDERER.renderer.rules.link_open = function(tokens: Remarkable.LinkOpe const href = utils.escapeHtml(tokens[idx].href || ""); const localLink = href.startsWith(window.location.origin) || href.startsWith("/") || href.startsWith("mailto:") || href.startsWith("#"); const title = tokens[idx].title ? (' title="' + utils.escapeHtml(utils.replaceEntities(tokens[idx].title || "")) + '"') : ''; + const conceptIcon = ""; if (localLink) { - return ``; + return `${isPhy && href.includes("/concepts/") ? conceptIcon : ""}`; } else { return ``; } diff --git a/src/scss/phy/typography.scss b/src/scss/phy/typography.scss index ee261b2647..f384a769f6 100644 --- a/src/scss/phy/typography.scss +++ b/src/scss/phy/typography.scss @@ -246,5 +246,16 @@ a { clear: left; text-decoration: none; } + + &.a-link:has(> i.icon) { + display: inline-flex; + align-items: center; + font-weight: bold; + gap: 4px; + > i.icon { + order: 1; + margin-top: 1px; + } + } } From 5ebe7f80401357524b864bba3c5e7f69d25cc8ad Mon Sep 17 00:00:00 2001 From: Jacob Brown Date: Mon, 17 Mar 2025 10:54:12 +0000 Subject: [PATCH 4/7] Redesign action buttons for both sites --- src/app/components/elements/PrintButton.tsx | 6 +- src/app/components/elements/ShareLink.tsx | 25 ++++---- src/app/components/pages/Concept.tsx | 39 ++++++++++--- src/scss/common/scroll-button.scss | 5 +- src/scss/cs/button.scss | 36 ------------ src/scss/cs/questions.scss | 62 +++++++++++++++++--- src/scss/phy/button.scss | 64 ++++++++++----------- src/scss/phy/questions.scss | 28 --------- 8 files changed, 132 insertions(+), 133 deletions(-) diff --git a/src/app/components/elements/PrintButton.tsx b/src/app/components/elements/PrintButton.tsx index 3de261165c..fe617cdf30 100644 --- a/src/app/components/elements/PrintButton.tsx +++ b/src/app/components/elements/PrintButton.tsx @@ -14,8 +14,8 @@ export const PrintButton = ({questionPage}: PrintProps ) => { const dispatch = useAppDispatch(); return questionPage ? - <> - {questionPrintOpen &&
+
+ {questionPrintOpen &&
: siteSpecific( clickAwayClose && setShowShareLink(false), [setShowShareLink]); const buttonAriaLabel = showShareLink ? "Hide share link" : "Get share link"; - const linkWidth = isMobile() || reducedWidthLink ? 192 : (shareUrl.length * siteSpecific(9, 6)); + const linkWidth = isMobile() || reducedWidthLink ? siteSpecific(256, 192) : (shareUrl.length * siteSpecific(9, 6)); const showDuplicateAndEdit = gameboardId && isTutorOrAbove(user); - return
+ return
+
+ e.preventDefault()} aria-label="Share URL" /> +
+ {showShareLink && showDuplicateAndEdit &&
} {siteSpecific( { e.preventDefault(); toggleShareLink(); }} />, -
; }; diff --git a/src/app/components/pages/Concept.tsx b/src/app/components/pages/Concept.tsx index 9c7c73e953..78fb28ab99 100644 --- a/src/app/components/pages/Concept.tsx +++ b/src/app/components/pages/Concept.tsx @@ -5,7 +5,7 @@ import {Col, Container, Row} from "reactstrap"; import {ShowLoading} from "../handlers/ShowLoading"; import {IsaacContent} from "../content/IsaacContent"; import {IsaacConceptPageDTO} from "../../../IsaacApiTypes"; -import {DOCUMENT_TYPE, Subject, above, below, usePreviousPageContext, isAda, isPhy, useDeviceSize, useNavigation} from "../../services"; +import {DOCUMENT_TYPE, Subject, above, below, usePreviousPageContext, isAda, isPhy, useDeviceSize, useNavigation, siteSpecific} from "../../services"; import {DocumentSubject, GameboardContext} from "../../../IsaacAppTypes"; import {RelatedContent} from "../elements/RelatedContent"; import {WithFigureNumbering} from "../elements/WithFigureNumbering"; @@ -60,9 +60,9 @@ export const Concept = withRouter(({match: {params}, location: {search}, concept @@ -73,14 +73,35 @@ export const Concept = withRouter(({match: {params}, location: {search}, concept - + {isPhy && <> +
+
+

{doc.title as string}

+ {doc.subtitle &&
{doc.subtitle}
} +
+
+ + + +
+
- {below["sm"](deviceSize) && } +
-
- - {above["md"](deviceSize) && } -
+
+ + +
+ } + + {isAda && <> + {below["sm"](deviceSize) && } + +
+ + {above["md"](deviceSize) && } +
+ } diff --git a/src/scss/common/scroll-button.scss b/src/scss/common/scroll-button.scss index b2c2975a13..be2d972e37 100644 --- a/src/scss/common/scroll-button.scss +++ b/src/scss/common/scroll-button.scss @@ -1,5 +1,6 @@ -button.btn.scroll-btn { +button.scroll-btn.scroll-btn { position: fixed; + display: flex; opacity: 0; height: 45px; width: 45px; @@ -15,8 +16,6 @@ button.btn.scroll-btn { &.is-sticky { opacity: 1; - display: flex; - // TODO: animate opacity on destruction } > img { diff --git a/src/scss/cs/button.scss b/src/scss/cs/button.scss index 71a864b992..398891db33 100644 --- a/src/scss/cs/button.scss +++ b/src/scss/cs/button.scss @@ -178,42 +178,6 @@ position: relative; z-index: 2; } - .share-link { - z-index: 1; - top: 3px; - font-family: $secondary-font !important; - display: none; - position: absolute; - border: 1px solid gray; - transform: translate(calc(30px - 100%)); - height: 42px; - width: 70%; - - input { - z-index: 1; - padding-right: 40px; - padding-left: 10px; - white-space: nowrap; - text-overflow: ellipsis; - user-select: all; - -webkit-user-select: all; - -moz-user-select: all; - -ms-user-select: all; - width: 100%; - height: 100%; - } - - &.double-height { - height: auto; - padding-bottom: 10px; - background: #fff; - text-align: center; - input { - height: 42px; - margin-bottom: 8px; - } - } - } } .print-icon { diff --git a/src/scss/cs/questions.scss b/src/scss/cs/questions.scss index dfc4a52556..cff03f80d6 100644 --- a/src/scss/cs/questions.scss +++ b/src/scss/cs/questions.scss @@ -1,19 +1,18 @@ @import "../common/questions"; -.question-actions-link-box { - $x-translation: 24px; - margin-left: -$x-translation; - float: left; - transform: translateX(7.5px + $x-translation); - height: 48px; // height of an icon button, should be a variable probably +.action-buttons-popup-container { + position: absolute; + height: 100%; + right: 30px; display: flex; flex-direction: column; justify-content: center; border: 3px solid $secondary; - padding-left: 15px; - padding-right: 32px; + padding-left: 1.5rem; + padding-right: 2rem; border-top-left-radius: 50px; border-bottom-left-radius: 50px; + z-index: 2; .question-actions-link { margin-top: 2px; // Just to vertically center the links nicely color: $secondary; @@ -22,6 +21,53 @@ margin-right: 7px; } } + + &:has(> input + a) { + flex-direction: row; + + input { + width: auto; + } + } + + a { + flex-grow: 1; + align-self: center; + } + + input { + flex-grow: 1; + white-space: nowrap; + text-overflow: ellipsis; + user-select: all; + -webkit-user-select: all; + -moz-user-select: all; + -ms-user-select: all; + width: 100%; + height: 100%; + background: none; + border: none; + outline: none; + } + + & ~ .duplicate-and-edit { + position: absolute; + right: 30px; + padding: 24px 24px 0; + top: 24px; + text-align: center; + background: white; + border: 1px solid $gray-118; + border-radius: 8px; + border-top-left-radius: 0; + font-size: small; + z-index: 1; + } + + &.double-height { + background: #fff; + text-align: center; + } } // Validation response diff --git a/src/scss/phy/button.scss b/src/scss/phy/button.scss index 7e71f3ea05..e8d2802f23 100644 --- a/src/scss/phy/button.scss +++ b/src/scss/phy/button.scss @@ -376,43 +376,41 @@ a.btn { background-color: transparent; } -.share-link-icon { - - button { +.action-buttons-popup-container { + position: absolute; + display: flex; + flex-direction: column; + justify-content: space-evenly; + width: max-content; + height: 100%; + right: 50%; + align-items: center; + border: 1px solid $color-neutral-200; + border-radius: 0.5rem; + padding: 0 1.5rem 0 1rem; + z-index: 15; + background: white; + + & ~ button { position: relative; - z-index: 12; + z-index: 15; } - .share-link { - z-index: 11; - font-family: $secondary-font !important; - display: none; - position: absolute; - padding: 4px 32px 4px 8px; - transform: translate(calc(25px - 100%), -3px); - border: 1px solid $color-neutral-300; - background: #fff; - height: 58px; - width: 70%; - - input { - position: relative; - top: 9px; - white-space: nowrap; - text-overflow: ellipsis; - user-select: all; - -webkit-user-select: all; - -moz-user-select: all; - -ms-user-select: all; - width: 100%; - padding-left: 2px; - border: none; - border-bottom: 2px solid $color-neutral-200; - } + input { + white-space: nowrap; + text-overflow: ellipsis; + user-select: all; + -webkit-user-select: all; + -moz-user-select: all; + -ms-user-select: all; + width: 100%; + height: max-content; + border: none; + border-bottom: 2px solid $color-neutral-200; + } - &.double-height { - height: 118px; - } + &.double-height { + height: calc(100% + 38px); } } diff --git a/src/scss/phy/questions.scss b/src/scss/phy/questions.scss index 03c8ebe6d2..4db12a892a 100644 --- a/src/scss/phy/questions.scss +++ b/src/scss/phy/questions.scss @@ -1,33 +1,5 @@ @import "../common/questions"; -.question-actions { - .question-actions-icon { - height: 3rem; - &:focus { - outline: none; - } - } - .question-actions-link-box { - float:right; - border: 1px solid gray; - height: 3.6rem; - border-right: none; - padding-right: 2.5rem; - display: flex; - flex-direction: column; - justify-content: center; - margin-right: -1.9rem; - background: white; - - .question-actions-link { - padding-left: 3px; - padding-right: 3px; - margin-left: 1rem; - font-style: italic; - } - } -} - .question-panel > .content-chunk > .content-value, .question-component .question-content { font-family: $primary-font; From 63ea74de29613aecab58ac00dd97ecb174b0a15a Mon Sep 17 00:00:00 2001 From: Jacob Brown Date: Tue, 18 Mar 2025 15:30:33 +0000 Subject: [PATCH 5/7] Prevent printing of sidebars; remove hints from concept print button --- src/app/components/elements/layout/SidebarLayout.tsx | 6 +++--- src/app/components/pages/Concept.tsx | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/app/components/elements/layout/SidebarLayout.tsx b/src/app/components/elements/layout/SidebarLayout.tsx index c97ec80ad6..2e14f9dc81 100644 --- a/src/app/components/elements/layout/SidebarLayout.tsx +++ b/src/app/components/elements/layout/SidebarLayout.tsx @@ -67,7 +67,7 @@ const NavigationSidebar = (props: SidebarProps) => { if (isAda) return <>; const { className, ...rest } = props; - return ; + return ; }; interface ContentSidebarProps extends SidebarProps { @@ -86,9 +86,9 @@ const ContentSidebar = (props: ContentSidebarProps) => { const { className, buttonTitle, ...rest } = props; return <> {above['lg'](deviceSize) - ? + ? : <> -
+
- +
From ff4c96ddb9ed4e7a64a45232e4e356c1025e97ef Mon Sep 17 00:00:00 2001 From: Jacob Brown Date: Tue, 18 Mar 2025 17:24:22 +0000 Subject: [PATCH 6/7] Correctly manage clicking a partial checkbox --- src/app/components/elements/layout/SidebarLayout.tsx | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/app/components/elements/layout/SidebarLayout.tsx b/src/app/components/elements/layout/SidebarLayout.tsx index 2e14f9dc81..fab778e601 100644 --- a/src/app/components/elements/layout/SidebarLayout.tsx +++ b/src/app/components/elements/layout/SidebarLayout.tsx @@ -33,6 +33,8 @@ const QuestionLink = (props: React.HTMLAttributes & {question: Qu const { question, ...rest } = props; const subject = useAppSelector(selectors.pageContext.subject); const audienceFields = filterAudienceViewsByProperties(determineAudienceViews(question.audience), AUDIENCE_DISPLAY_FIELDS); + + console.log(question.bestAttempt?.correct); return
  • @@ -196,12 +198,13 @@ interface FilterCheckboxProps extends React.HTMLAttributes { incompatibleTags?: Tag[]; // tags that are removed when this tag is added dependentTags?: Tag[]; // tags that are removed when this tag is removed baseTag?: Tag; // tag to add when all tags are removed + partiallySelected?: boolean; checkboxStyle?: "tab" | "button"; bsSize?: "sm" | "lg"; } const FilterCheckbox = (props : FilterCheckboxProps) => { - const {tag, conceptFilters, setConceptFilters, tagCounts, checkboxStyle, incompatibleTags, dependentTags, baseTag, ...rest} = props; + const {tag, conceptFilters, setConceptFilters, tagCounts, checkboxStyle, incompatibleTags, dependentTags, baseTag, partiallySelected, ...rest} = props; const [checked, setChecked] = useState(conceptFilters.includes(tag)); useEffect(() => { @@ -210,7 +213,7 @@ const FilterCheckbox = (props : FilterCheckboxProps) => { const handleCheckboxChange = (checked: boolean) => { const newConceptFilters = checked - ? [...conceptFilters.filter(c => !incompatibleTags?.includes(c)), tag] + ? [...conceptFilters.filter(c => !incompatibleTags?.includes(c)), ...(!partiallySelected ? [tag] : [])] : conceptFilters.filter(c => ![tag, ...(dependentTags ?? [])].includes(c)); setConceptFilters(newConceptFilters.length > 0 ? newConceptFilters : (baseTag ? [baseTag] : [])); }; @@ -342,6 +345,7 @@ export const GenericConceptsSidebar = (props: ConceptListSidebarProps) => { conceptFilters.includes(tag))} // not quite isPartial; this is also true if all descendents selected className={classNames({"icon-checkbox-off": !isSelected, "icon icon-checkbox-partial-alt": isSelected && isPartial, "icon-checkbox-selected": isSelected && !isPartial})} /> {isSelected &&
    From d71d879a24c478a3b1589c52da223c969a0b398a Mon Sep 17 00:00:00 2001 From: Sol Dubock <94075844+sjd210@users.noreply.github.com> Date: Wed, 19 Mar 2025 11:37:50 +0000 Subject: [PATCH 7/7] Remove extraneous log --- src/app/components/elements/layout/SidebarLayout.tsx | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/app/components/elements/layout/SidebarLayout.tsx b/src/app/components/elements/layout/SidebarLayout.tsx index fab778e601..26a26ec41f 100644 --- a/src/app/components/elements/layout/SidebarLayout.tsx +++ b/src/app/components/elements/layout/SidebarLayout.tsx @@ -33,8 +33,6 @@ const QuestionLink = (props: React.HTMLAttributes & {question: Qu const { question, ...rest } = props; const subject = useAppSelector(selectors.pageContext.subject); const audienceFields = filterAudienceViewsByProperties(determineAudienceViews(question.audience), AUDIENCE_DISPLAY_FIELDS); - - console.log(question.bestAttempt?.correct); return