-
Notifications
You must be signed in to change notification settings - Fork 586
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
Add Tags to Catalog Criteria #10007
Add Tags to Catalog Criteria #10007
Changes from all commits
ea2a2af
1dd5a1a
13d306d
add5f06
afc4850
5d8408a
8dbd82c
0ae19f3
a2183f5
4044655
f981ff3
280b416
4a6be94
3da27ac
8ba07dc
ee69a7d
ad42cbf
28fdf3a
c10516b
5f15a26
a58b3a3
c169068
213491a
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -6,11 +6,15 @@ import { getCatalogCriteria } from "../state/helpers"; | |
import { ReadOnlyCriteriaDisplay } from "./ReadonlyCriteriaDisplay"; | ||
import { Strings } from "../constants"; | ||
import { Button } from "react-common/components/controls/Button"; | ||
import { getReadableCriteriaTemplate, makeToast } from "../utils"; | ||
import { Accordion } from "react-common/components/controls/Accordion"; | ||
import { getReadableCriteriaTemplate } from "../utils"; | ||
import { setCatalogOpen } from "../transforms/setCatalogOpen"; | ||
import { classList } from "react-common/components/util"; | ||
import { announceToScreenReader } from "../transforms/announceToScreenReader"; | ||
import { FocusTrap } from "react-common/components/controls/FocusTrap"; | ||
import { logError } from "../services/loggingService"; | ||
import { ErrorCode } from "../types/errorCode"; | ||
import { addExpandedCatalogTag, getExpandedCatalogTags, removeExpandedCatalogTag } from "../services/storageService"; | ||
import css from "./styling/CatalogOverlay.module.scss"; | ||
|
||
interface CatalogHeaderProps { | ||
|
@@ -67,16 +71,59 @@ const CatalogItemLabel: React.FC<CatalogItemLabelProps> = ({ catalogCriteria, is | |
); | ||
}; | ||
|
||
interface CatalogItemProps { | ||
catalogCriteria: CatalogCriteria; | ||
recentlyAddedIds: pxsim.Map<NodeJS.Timeout>; | ||
onItemClicked: (c: CatalogCriteria) => void; | ||
} | ||
const CatalogItem: React.FC<CatalogItemProps> = ({ catalogCriteria, recentlyAddedIds, onItemClicked }) => { | ||
const { state: teacherTool } = useContext(AppStateContext); | ||
|
||
const existingInstanceCount = teacherTool.checklist.criteria.filter( | ||
i => i.catalogCriteriaId === catalogCriteria.id | ||
).length; | ||
const isMaxed = catalogCriteria.maxCount !== undefined && existingInstanceCount >= catalogCriteria.maxCount; | ||
return catalogCriteria.template ? ( | ||
<Button | ||
title={getReadableCriteriaTemplate(catalogCriteria)} | ||
key={catalogCriteria.id} | ||
className={css["catalog-item"]} | ||
label={ | ||
<CatalogItemLabel | ||
catalogCriteria={catalogCriteria} | ||
isMaxed={isMaxed} | ||
recentlyAdded={recentlyAddedIds[catalogCriteria.id] !== undefined} | ||
/> | ||
} | ||
onClick={() => onItemClicked(catalogCriteria)} | ||
disabled={isMaxed} | ||
/> | ||
) : null; | ||
}; | ||
|
||
const CatalogList: React.FC = () => { | ||
const { state: teacherTool } = useContext(AppStateContext); | ||
|
||
const recentlyAddedWindowMs = 500; | ||
const [recentlyAddedIds, setRecentlyAddedIds] = useState<pxsim.Map<NodeJS.Timeout>>({}); | ||
|
||
const criteria = useMemo<CatalogCriteria[]>( | ||
() => getCatalogCriteria(teacherTool), | ||
[teacherTool.catalog, teacherTool.checklist] | ||
); | ||
// For now, we only look at the first tag of each criteria. | ||
const criteriaGroupedByTag = useMemo<pxt.Map<CatalogCriteria[]>>(() => { | ||
const grouped: pxt.Map<CatalogCriteria[]> = {}; | ||
getCatalogCriteria(teacherTool)?.forEach(c => { | ||
if (!c.tags || c.tags.length === 0) { | ||
logError(ErrorCode.missingTag, { message: "Catalog criteria missing tag", criteria: c }); | ||
return; | ||
} | ||
|
||
const tag = c.tags[0]; | ||
if (!grouped[tag]) { | ||
grouped[tag] = []; | ||
} | ||
grouped[tag].push(c); | ||
}); | ||
return grouped; | ||
}, [teacherTool.catalog]); | ||
|
||
function updateRecentlyAddedValue(id: string, value: NodeJS.Timeout | undefined) { | ||
setRecentlyAddedIds(prevState => { | ||
|
@@ -106,34 +153,55 @@ const CatalogList: React.FC = () => { | |
announceToScreenReader(lf("Added '{0}' to checklist.", getReadableCriteriaTemplate(c))); | ||
} | ||
|
||
function getItemIdForTag(tag: string) { | ||
return `accordion-item-${tag}`; | ||
} | ||
|
||
function onTagExpandToggled(tag: string, expanded: boolean) { | ||
if (expanded) { | ||
addExpandedCatalogTag(tag); | ||
} else { | ||
removeExpandedCatalogTag(tag); | ||
} | ||
} | ||
|
||
const tags = Object.keys(criteriaGroupedByTag); | ||
if (tags.length === 0) { | ||
logError(ErrorCode.noCatalogCriteria); | ||
return null; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. To clarify, does this mean that the overlay will be empty if there were no tags defined? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes. It'd mean there were no criteria, since every criteria (once loaded into state) should have at least one tag. Should never happen, but better this than some random error, imo. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Gotcha, makes sense. Maybe we should log something, too? Makes sense that it should never happen, but I feel like if there was a blank overlay and someone looked at the console and no problems were reported, it would feel very buggy. Although I'm guessing there should be other errors in the console if that were to happen? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Added error log |
||
} | ||
|
||
let expandedTags = getExpandedCatalogTags(); | ||
if (!expandedTags) { | ||
// If we haven't saved an expanded set, default expand the first one. | ||
addExpandedCatalogTag(tags[0]); | ||
expandedTags = [tags[0]]; | ||
} | ||
|
||
const expandedIds = expandedTags.map(t => getItemIdForTag(t)); | ||
return ( | ||
<div className={css["catalog-list"]}> | ||
{criteria.map(c => { | ||
const existingInstanceCount = teacherTool.checklist.criteria.filter( | ||
i => i.catalogCriteriaId === c.id | ||
).length; | ||
const isMaxed = c.maxCount !== undefined && existingInstanceCount >= c.maxCount; | ||
<Accordion className={css["catalog-list"]} multiExpand={true} defaultExpandedIds={expandedIds}> | ||
{tags.map(tag => { | ||
return ( | ||
c.template && ( | ||
<Button | ||
id={`criteria_${c.id}`} | ||
title={getReadableCriteriaTemplate(c)} | ||
key={c.id} | ||
className={css["catalog-item"]} | ||
label={ | ||
<CatalogItemLabel | ||
<Accordion.Item | ||
itemId={getItemIdForTag(tag)} | ||
onExpandToggled={expanded => onTagExpandToggled(tag, expanded)} | ||
key={getItemIdForTag(tag)} | ||
> | ||
<Accordion.Header>{tag}</Accordion.Header> | ||
srietkerk marked this conversation as resolved.
Show resolved
Hide resolved
|
||
<Accordion.Panel> | ||
{criteriaGroupedByTag[tag].map(c => ( | ||
<CatalogItem | ||
catalogCriteria={c} | ||
isMaxed={isMaxed} | ||
recentlyAdded={recentlyAddedIds[c.id] !== undefined} | ||
recentlyAddedIds={recentlyAddedIds} | ||
onItemClicked={onItemClicked} | ||
/> | ||
} | ||
onClick={() => onItemClicked(c)} | ||
disabled={isMaxed} | ||
/> | ||
) | ||
))} | ||
</Accordion.Panel> | ||
</Accordion.Item> | ||
); | ||
})} | ||
</div> | ||
</Accordion> | ||
); | ||
}; | ||
|
||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this being async seems dangerous. looking at the implementation below of these two functions, it seems like hammering on this button could cause some issues with multiple copies of the same tag being added to the state
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah true...I'll just remove the async stuff. Most of our local storage stuff isn't async anyway.