-
Notifications
You must be signed in to change notification settings - Fork 581
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
Code eval tool: use toast upon deletion instead of alert #10165
base: master
Are you sure you want to change the base?
Changes from 5 commits
1cb2d56
1d1e11c
66ea6a2
641a31f
98a0d0f
275732b
7f5298d
10f135f
7cf2545
02d42a8
0935b20
6a0d7b5
5031335
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 |
---|---|---|
|
@@ -18,6 +18,8 @@ import { Button } from "react-common/components/controls/Button"; | |
import { setEvalResult } from "../transforms/setEvalResult"; | ||
import { showToast } from "../transforms/showToast"; | ||
import { makeToast } from "../utils"; | ||
import { readdCriteriaToChecklist } from "../transforms/readdCriteriaToChecklist"; | ||
import { dismissToast } from "../state/actions"; | ||
|
||
interface CriteriaResultNotesProps { | ||
criteriaId: string; | ||
|
@@ -76,6 +78,25 @@ const CriteriaResultError: React.FC<CriteriaResultErrorProps> = ({ criteriaInsta | |
); | ||
}; | ||
|
||
const UndoDeleteCriteriaButton: React.FC<{ criteriaId: string, toastId: string }> = ({ criteriaId, toastId }) => { | ||
const { dispatch } = useContext(AppStateContext); | ||
const handleUndoClicked = () => { | ||
readdCriteriaToChecklist(criteriaId); | ||
if (toastId) { | ||
dispatch(dismissToast(toastId)); | ||
} | ||
} | ||
|
||
return ( | ||
<Button | ||
className="undo-button" | ||
title={Strings.Undo} | ||
label={Strings.Undo} | ||
onClick={handleUndoClicked} | ||
/> | ||
) | ||
} | ||
|
||
const CriteriaResultToolbarTray: React.FC<{ criteriaId: string }> = ({ criteriaId }) => { | ||
const { state: teacherTool } = useContext(AppStateContext); | ||
|
||
|
@@ -91,9 +112,10 @@ const CriteriaResultToolbarTray: React.FC<{ criteriaId: string }> = ({ criteriaI | |
} | ||
|
||
async function handleDeleteClickedAsync() { | ||
if (confirm(Strings.ConfirmDeleteCriteriaInstance)) { | ||
removeCriteriaFromChecklist(criteriaId); | ||
} | ||
removeCriteriaFromChecklist(criteriaId); | ||
const toast = makeToast("info", Strings.CriteriaDeleted); | ||
toast.jsx = <UndoDeleteCriteriaButton criteriaId={criteriaId} toastId={toast.id} />; | ||
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. In 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. I originally just had the 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. I see. I would probably lean towards removing it in that case, at least for now. Either way is fine though. |
||
showToast(toast); | ||
} | ||
|
||
return ( | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -122,6 +122,7 @@ const CriteriaWithResultsTable: React.FC = () => { | |
<div className={css["results-list"]}> | ||
{Object.values(criteria).map(criteriaInstance => { | ||
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. For code clarity: Use helper to get active criteria, then render them. |
||
return ( | ||
!criteriaInstance.deleted && | ||
<CriteriaResultEntry criteriaId={criteriaInstance.instanceId} key={criteriaInstance.instanceId} /> | ||
); | ||
})} | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -75,3 +75,14 @@ | |
.pass > .common-button.common-dropdown-button { | ||
border: 3px solid var(--pxt-success-accent); | ||
} | ||
|
||
.undo-button { | ||
width: 3rem; | ||
height: 2rem; | ||
display: flex; | ||
flex-direction: column; | ||
align-items: center; | ||
justify-content: center; | ||
background-color: var(--pxt-info-accent-darkened); | ||
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. We should probably change these colors based on the type of toast containing them (error, info, etc...). I believe there are different css classes for each type of toast we can reference. 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. Yeah, I think that's a good idea. Can this be done in a follow-up? 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. Sure |
||
color: var(--pxt-button-secondary-foreground); | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
import { stateAndDispatch } from "../state"; | ||
import { logDebug } from "../services/loggingService"; | ||
import { setChecklist } from "./setChecklist"; | ||
import { Ticks } from "../constants"; | ||
import { getCriteriaInstanceWithId } from "../state/helpers"; | ||
|
||
export function readdCriteriaToChecklist(criteriaInstanceId: string) { | ||
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. Over in 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. An alternative might be to permanently delete the criteria instance once the toast disappears. 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. I think permanently deleting is probably for the best with this approach. Though see #10165 (comment) for some thoughts on a slightly different approach that I think would lessen the importance of it (would still ultimately be good to have, though). 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. Just for clarity, we are now permanently deleting the criteria after the toast goes away. I also wanted to add since this is related to the 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. nit: I saw this as a typo of "read" instead of "re-add". Perhaps "reAdd" or "undelete" would be clearer? |
||
const { state: teacherTool } = stateAndDispatch(); | ||
|
||
logDebug(`Removing criteria with id: ${criteriaInstanceId}`); | ||
srietkerk marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
const instance = getCriteriaInstanceWithId(teacherTool, criteriaInstanceId); | ||
const catalogCriteriaId = instance?.catalogCriteriaId; | ||
const allCriteria = [...teacherTool.checklist.criteria]; | ||
const criteriaIndex = allCriteria.findIndex(c => c.instanceId === criteriaInstanceId); | ||
allCriteria[criteriaIndex].deleted = false; | ||
|
||
const newChecklist = { | ||
...teacherTool.checklist, | ||
criteria: allCriteria, | ||
}; | ||
|
||
setChecklist(newChecklist); | ||
|
||
if (catalogCriteriaId) { | ||
pxt.tickEvent(Ticks.RemoveCriteria, { catalogCriteriaId }); | ||
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. Is removeCriteria the correct tick to send in this case? |
||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -11,10 +11,14 @@ export function removeCriteriaFromChecklist(criteriaInstanceId: string) { | |
|
||
const instance = getCriteriaInstanceWithId(teacherTool, criteriaInstanceId); | ||
const catalogCriteriaId = instance?.catalogCriteriaId; | ||
const allCriteria = [...teacherTool.checklist.criteria]; | ||
const criteriaIndex = allCriteria.findIndex(c => c.instanceId === criteriaInstanceId); | ||
allCriteria[criteriaIndex].deleted = true; | ||
|
||
|
||
const newChecklist = { | ||
...teacherTool.checklist, | ||
criteria: teacherTool.checklist.criteria.filter(c => c.instanceId !== criteriaInstanceId), | ||
criteria: allCriteria, | ||
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. So since we're just setting this flag but never removing the criteria, it this means that when a teacher exports their checklist, it will contain all the deleted checklist items which is somewhat wasteful since there's no way to get them back after the toast is dismissed. One way to avoid this, which I think would ultimately be simpler all around, would be to move criteria out of the main checklist and into a separate 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. Also, if we do a separate list, it's probably still good to remove items once the toast is dismissed, but not quite as important (assuming it clears when the user closes the session). 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. A downside to a separate list for deleted items is that undoing the deletion wouldn't preserve original location in the active criteria list (unless we stored the original index). If undeletion appended to the active set, it may confuse the user, especially if the end of the list is off the page. 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. Hmm, that's true, but I still think it'd be simpler to store a separate list of { criteria + index } objects than have to filter based on deleted everywhere (or remember to call the appropriate helper function) 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. A helper to get active criteria should mitigate this. I agree we don't want to include deleted criteria in the exported JSON. 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. Even with a helper, you have to remember to call it instead of getting the value from state :) Separate list makes it a non-issue as long as we can sort out the index thing. I don't feel that strongly, so I'm happy for Sarah to decide. But personally, I still think a separate list would be slightly cleaner. 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. I would like to continue with the flag approach and use the "get active criteria" helper function. I have been thinking A LOT about the pros and cons of each approach, and I think both have really strong merit. The selling point for me is for the removing and readding scenario of criteria. Both approaches have the same amount of state operations for full removing and newly adding criteria. However, in the new scenario that we are supporting here (removing and readding), the "having a separate list for deleted criteria" would need four state operations along with list operations (two for removing, two for readding) while the flag approach only needs two state operations and no list operations are needed. 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. That's fine, as long as we can get the helper in place and filter deleted criteria out of the serialization. |
||
}; | ||
|
||
setChecklist(newChecklist); | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -20,6 +20,7 @@ export interface CriteriaInstance { | |
instanceId: string; | ||
params: CriteriaParameterValue[] | undefined; | ||
userFeedback?: UserFeedback; | ||
deleted?: boolean; | ||
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. I think we should still ensure deleted criteria get filtered out of export/serialization. It's not as big of an issue since it gets removed once the toast goes away, but the user could still export while the toast is around (or more likely, close the browser/tab, so it gets serialized and stored in browser storage), at which point the deleted criteria would be there forever since the delete code won't run on it anymore. |
||
} | ||
|
||
// Represents a parameter definition in a catalog criteria. | ||
|
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.
As much as I like the sleekness of this design, something to keep in mind is from an accessibility perspective, how can a user navigate to this button without a mouse? If this means we need to treat toasts like modals and have them steal focus, that could be somewhat confusing/irritating. Maybe we could do that only if the toast is "interactive" in some way? Or there may be other workarounds.
Not necessarily a blocker, but good to keep it in mind.
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.
Good point. I wonder what Outlook does here.