Skip to content

Commit

Permalink
Merge branch 'Simon-Initiative:master' into master
Browse files Browse the repository at this point in the history
  • Loading branch information
dtiwarATS committed Apr 11, 2024
2 parents c656b05 + a20b503 commit be7c5fe
Show file tree
Hide file tree
Showing 35 changed files with 2,039 additions and 377 deletions.
1 change: 1 addition & 0 deletions .github/workflows/deploy.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ on:
- tokamak.oli.cmu.edu
- stellarator.oli.cmu.edu
- heliotron.oli.cmu.edu
- neutron.oli.cmu.edu
- loadtest.oli.cmu.edu
- proton.oli.cmu.edu

Expand Down
4 changes: 4 additions & 0 deletions assets/css/app.css
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,10 @@ https://tailwindcss.com/docs/utility-first
@apply mb-[20px] text-2xl tracking-[0.02px] font-light dark:text-white;
}

.truncate-form-control p {
@apply truncate;
}

.scrollbar-hide::-webkit-scrollbar {
display: none;
}
Expand Down
2 changes: 2 additions & 0 deletions assets/src/apps/Components.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { Navbar } from 'components/common/Navbar';
import { SelectTimezone } from 'components/common/SelectTimezone';
import { TechSupportButton } from 'components/common/TechSupportButton';
import { UserAccountMenu } from 'components/common/UserAccountMenu';
import { RichTextEditor } from 'components/content/RichTextEditor';
import { AlternativesPreferenceSelector } from 'components/delivery/AlternativesPreferenceSelector';
import { CourseContentOutline } from 'components/delivery/CourseContentOutline';
import { SurveyControls } from 'components/delivery/SurveyControls';
Expand Down Expand Up @@ -35,3 +36,4 @@ registerApplication('SelectTimezone', SelectTimezone, globalStore);
registerApplication('YoutubePlayer', YoutubePlayer, globalStore);
registerApplication('TechSupportButton', TechSupportButton, globalStore);
registerApplication('OfflineDetector', OfflineDetector, globalStore);
registerApplication('RichTextEditor', RichTextEditor, globalStore);
Original file line number Diff line number Diff line change
Expand Up @@ -113,10 +113,10 @@ const LessonFinishedDialog: React.FC<LessonFinishedDialogProps> = ({
aria-hidden={!isOpen}
style={{
display: isOpen ? 'block' : 'none',
minHeight: '350px',
minHeight: '250px',
height: 'unset',
width: '500px',
top: '20%',
top: '25%',
backgroundImage: imageUrl ? `url('${imageUrl}')` : '',
left: '50%',
}}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -109,14 +109,15 @@ const ReviewModeHistoryPanel: React.FC<ReviewModeHistoryPanelProps> = ({ items }
<div className="title screenListTitle">
Lesson History
<a
className="review-mode-panel-close-button"
onClick={() => handleToggleHistory(false)}
style={{ float: 'right', color: 'white', cursor: 'pointer' }}
>
<span className="fa fa-times">&nbsp;</span>
</a>
</div>
<div
className="fixed top-0 w-72 text-black bg-white p-2 border-gray-500 border-2 max-h-[80vh] overflow-auto"
className="review-mode-screen-list fixed top-0 w-72 text-black bg-white p-2 border-gray-500 border-2 max-h-[80vh] overflow-auto"
style={{
transform: 'translate(-50%, 110px)',
}}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ import { InputEntry } from 'components/activities/short_answer/sections/InputEnt
import { getTargetedResponses } from 'components/activities/short_answer/utils';
import { Response, RichText, makeResponse } from 'components/activities/types';
import { Radio } from 'components/misc/icons/radio/Radio';
import { getCorrectResponse, hasCustomScoring } from 'data/activities/model/responses';
import { getCorrectResponse } from 'data/activities/model/responses';
import { containsRule, eqRule, equalsRule } from 'data/activities/model/rules';
import { defaultWriterContext } from 'data/content/writers/context';
import { MultiInputScoringMethod } from '../MultiInputScoringMethod';
Expand Down Expand Up @@ -127,7 +127,7 @@ export const AnswerKeyTab: React.FC<Props> = (props) => {
updateScore={(_id, score) =>
dispatch(ResponseActions.editResponseScore(response.id, score))
}
customScoring={hasCustomScoring(model, props.input.partId)}
customScoring={model.customScoring}
removeResponse={(id) => dispatch(ResponseActions.removeResponse(id))}
key={response.id}
>
Expand Down
25 changes: 25 additions & 0 deletions assets/src/components/content/RichTextEditor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,12 @@ type Props = {
onChangeTextDirection?: (textDirection: 'ltr' | 'rtl') => void;
onEdit: (value: Descendant[], editor: SlateEditor, operations: Operation[]) => void;
onRequestMedia?: (request: MediaItemRequest) => Promise<string | boolean>;
// the name of the event to be pushed back to the liveview (or live_component) when rendered with React.component
onEditEvent?: string;
// the name of the event target element (if the target is a live_component, ex: "#my-live-component-id")
onEditTarget?: string;
pushEvent?: (event: string, payload: any) => void;
pushEventTo?: (selectorOrTarget: string, event: string, payload: any) => void;
};
export const RichTextEditor: React.FC<Props> = ({
projectSlug,
Expand All @@ -42,10 +48,29 @@ export const RichTextEditor: React.FC<Props> = ({
children,
textDirection,
onChangeTextDirection,
onEditEvent,
onEditTarget,
pushEvent,
pushEventTo,
}) => {
// Support content persisted when RichText had a `model` property.
value = (value as any).model ? (value as any).model : value;

// Support for rendering the component within a LiveView or a LiveComponent:
// if onEditEvent is not null it means this react component is rendered within a LiveView or a live_component
// using the React.component wrapper
// If so, events need to be pushed back to the LiveView or the live_component (the optional onEditTarget is used to target the event to a live_component)

if (onEditEvent && pushEvent && pushEventTo) {
onEdit = (values) => {
if (onEditTarget) {
pushEventTo(onEditTarget, onEditEvent, { values: values });
} else {
pushEvent(onEditEvent, { values: values });
}
};
}

return (
<div className={classNames('rich-text-editor', fixedToolbar && 'fixed-toolbar', className)}>
<ErrorBoundary>
Expand Down
13 changes: 8 additions & 5 deletions assets/src/hooks/point_markers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,14 @@ export const PointMarkers = {
mounted() {
const el = this.el as HTMLElement;

const pageHeaderOffset =
document.getElementById('page_header')?.getBoundingClientRect().height || 0;

const UPDATE_DEBOUNCE_INTERVAL = 200;
const updatePointMarkers = debounce(() => {
this.pushEvent('update_point_markers', { ['point_markers']: queryPointMarkers(el) });
this.pushEvent('update_point_markers', {
['point_markers']: queryPointMarkers(el, pageHeaderOffset),
});
}, UPDATE_DEBOUNCE_INTERVAL);

// update the marker positions immediately when the page is mounted
Expand Down Expand Up @@ -46,14 +51,12 @@ export const PointMarkers = {
},
};

function queryPointMarkers(el: HTMLElement) {
function queryPointMarkers(el: HTMLElement, pageHeaderOffset: number) {
const markerElements = el.querySelectorAll('[data-point-marker]');

const OFFSET_TOP = 110;

return Array.from(markerElements).map((markerEl) => ({
id: markerEl.getAttribute('data-point-marker'),
top: markerEl.getBoundingClientRect().top - el.getBoundingClientRect().top + OFFSET_TOP,
top: markerEl.getBoundingClientRect().top - el.getBoundingClientRect().top + pageHeaderOffset,
}));
}

Expand Down
40 changes: 20 additions & 20 deletions assets/yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -7054,21 +7054,21 @@ bn.js@^5.0.0, bn.js@^5.1.1:
resolved "https://registry.npmjs.org/bn.js/-/bn.js-5.2.1.tgz"
integrity sha512-eXRvHzWyYPBuB4NBy0cmYQjGitUrtqwbvlzP3G6VFnNRbsZQIxQ10PbKKHt8gZ/HW/D/747aDl+QkDqg3KQLMQ==

[email protected].1:
version "1.20.1"
resolved "https://registry.npmjs.org/body-parser/-/body-parser-1.20.1.tgz"
integrity sha512-jWi7abTbYwajOytWCQc37VulmWiRae5RyTpaCyDcS5/lMdtwSz5lOpDE67srw/HYe35f1z3fDQw+3txg7gNtWw==
[email protected].2:
version "1.20.2"
resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.20.2.tgz#6feb0e21c4724d06de7ff38da36dad4f57a747fd"
integrity sha512-ml9pReCu3M61kGlqoTm2umSXTlRTuGTx0bfYj+uIUKKYycG5NtSbeetV3faSU6R7ajOPw0g/J1PvK4qNy7s5bA==
dependencies:
bytes "3.1.2"
content-type "~1.0.4"
content-type "~1.0.5"
debug "2.6.9"
depd "2.0.0"
destroy "1.2.0"
http-errors "2.0.0"
iconv-lite "0.4.24"
on-finished "2.4.1"
qs "6.11.0"
raw-body "2.5.1"
raw-body "2.5.2"
type-is "~1.6.18"
unpipe "1.0.0"

Expand Down Expand Up @@ -7972,7 +7972,7 @@ [email protected]:
dependencies:
safe-buffer "5.2.1"

content-type@~1.0.4:
content-type@~1.0.4, content-type@~1.0.5:
version "1.0.5"
resolved "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz"
integrity sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==
Expand All @@ -7989,10 +7989,10 @@ [email protected]:
resolved "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz"
integrity sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==

cookie@0.5.0:
version "0.5.0"
resolved "https://registry.npmjs.org/cookie/-/cookie-0.5.0.tgz"
integrity sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==
cookie@0.6.0:
version "0.6.0"
resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.6.0.tgz#2798b04b071b0ecbff0dbb62a505a8efa4e19051"
integrity sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==

copy-concurrently@^1.0.0:
version "1.0.5"
Expand Down Expand Up @@ -9945,16 +9945,16 @@ expect@^26.6.2:
jest-regex-util "^26.0.0"

express@^4.17.1:
version "4.18.2"
resolved "https://registry.npmjs.org/express/-/express-4.18.2.tgz"
integrity sha512-5/PsL6iGPdfQ/lKM1UuielYgv3BUoJfz1aUwU9vHZ+J7gyvwdQXFEBIEIaxeGf0GIcreATNyBExtalisDbuMqQ==
version "4.19.2"
resolved "https://registry.yarnpkg.com/express/-/express-4.19.2.tgz#e25437827a3aa7f2a827bc8171bbbb664a356465"
integrity sha512-5T6nhjsT+EOMzuck8JjBHARTHfMht0POzlA60WV2pMD3gyXw2LZnZ+ueGdNxG+0calOJcWKbpFcuzLZ91YWq9Q==
dependencies:
accepts "~1.3.8"
array-flatten "1.1.1"
body-parser "1.20.1"
body-parser "1.20.2"
content-disposition "0.5.4"
content-type "~1.0.4"
cookie "0.5.0"
cookie "0.6.0"
cookie-signature "1.0.6"
debug "2.6.9"
depd "2.0.0"
Expand Down Expand Up @@ -15630,10 +15630,10 @@ range-parser@^1.2.1, range-parser@~1.2.1:
resolved "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz"
integrity sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==

[email protected].1:
version "2.5.1"
resolved "https://registry.npmjs.org/raw-body/-/raw-body-2.5.1.tgz"
integrity sha512-qqJBtEyVgS0ZmPGdCFPWJ3FreoqvG4MVQln/kCgF7Olq95IbOp0/BWyMwbdtn4VTvkM8Y7khCQ2Xgk/tcrCXig==
[email protected].2:
version "2.5.2"
resolved "https://registry.yarnpkg.com/raw-body/-/raw-body-2.5.2.tgz#99febd83b90e08975087e8f1f9419a149366b68a"
integrity sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==
dependencies:
bytes "3.1.2"
http-errors "2.0.0"
Expand Down
6 changes: 3 additions & 3 deletions lib/oli/activities/realizer/query/builder.ex
Original file line number Diff line number Diff line change
Expand Up @@ -169,7 +169,7 @@ defmodule Oli.Activities.Realizer.Query.Builder do

:does_not_equal ->
[
"(NOT ((objectives_count = #{length(value)} AND (#{build_objectives_conjunction(value)})))"
"NOT (objectives_count = #{length(value)} AND #{build_objectives_conjunction(value)})"
]
end

Expand Down Expand Up @@ -206,14 +206,14 @@ defmodule Oli.Activities.Realizer.Query.Builder do
Enum.map(objective_ids, fn id -> "@ == #{id}" end)
|> Enum.join(" || ")

"jsonb_path_match(objectives, 'exists($.** ? (#{id_filter}))')"
"jsonb_path_exists(objectives, '$.** ? (#{id_filter})')"
end

defp build_objectives_conjunction(objective_ids) do
clauses =
Enum.map(objective_ids, fn id -> build_objectives_disjunction([id]) end)
|> Enum.join(" AND ")

"(#{clauses})"
"#{clauses}"
end
end
55 changes: 55 additions & 0 deletions lib/oli/delivery/settings.ex
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,61 @@ defmodule Oli.Delivery.Settings do
end)
end

@doc """
For a course section id and user id, return a map of resource_id to student exception settings.
The third argument allows to specific the field/s to be returned in the map.
If no fields are specified, all fields from the Oli.Delivery.Settings.Combined struct are returned.
If the are no student exception for a specific resource id, that resource id won't be included in the map.
(so if there are no student exceptions for any resources, an empty map will be returned)
Example:
```
iex> Oli.Delivery.Settings.get_student_exception_setting_for_all_resources(1, 2)
%{
22433 => %{
max_attempts: nil,
password: nil,
end_date: ~U[2024-05-25 13:41:00Z],
time_limit: 30,
collab_space_config: nil,
start_date: nil,
resource_id: 22433,
retake_mode: nil,
late_submit: nil,
late_start: nil,
grace_period: nil,
scoring_strategy_id: nil,
review_submission: nil,
feedback_mode: nil,
feedback_scheduled_date: nil,
explanation_strategy: nil
}
}
iex> Oli.Delivery.Settings.get_student_exception_setting_for_all_resources(1, 2, [:end_date, :time_limit])
%{22433 => %{end_date: ~U[2024-05-25 13:41:00Z], time_limit: 30}}
iex> Oli.Delivery.Settings.get_student_exception_setting_for_all_resources(1, 5)
%{}
"""

def get_student_exception_setting_for_all_resources(section_id, user_id, fields \\ nil)

def get_student_exception_setting_for_all_resources(section_id, user_id, nil) do
fields = %Oli.Delivery.Settings.StudentException{} |> Map.from_struct() |> Map.keys()

get_all_student_exceptions(section_id, user_id)
|> Enum.reduce(%{}, fn se, acc -> Map.put(acc, se.resource_id, Map.take(se, fields)) end)
end

def get_student_exception_setting_for_all_resources(section_id, user_id, fields)
when is_list(fields) do
get_all_student_exceptions(section_id, user_id)
|> Enum.reduce(%{}, fn se, acc -> Map.put(acc, se.resource_id, Map.take(se, fields)) end)
end

defp get_page_resources_with_settings(section_slug) do
page_id = Oli.Resources.ResourceType.id_for_page()

Expand Down
Loading

0 comments on commit be7c5fe

Please sign in to comment.