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 Sep 3, 2024
2 parents db8d94e + 6a9f03b commit 907b3a1
Show file tree
Hide file tree
Showing 103 changed files with 5,910 additions and 679 deletions.
2 changes: 2 additions & 0 deletions assets/src/apps/Components.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import { registerApplication } from 'apps/app';
import { globalStore } from 'state/store';
import { VideoPlayer } from '../components/video_player/VideoPlayer';
import { OfflineDetector } from './OfflineDetector';
import ActivityBank from './bank/ActivityBank';
import { References } from './bibliography/References';

registerApplication('ModalDisplay', ModalDisplay, globalStore);
Expand All @@ -41,3 +42,4 @@ registerApplication('OfflineDetector', OfflineDetector, globalStore);
registerApplication('RichTextEditor', RichTextEditor, globalStore);
registerApplication('VegaLiteRenderer', VegaLiteRenderer, globalStore);
registerApplication('LikertReportRenderer', LikertReportRenderer, globalStore);
registerApplication('ActivityBank', ActivityBank, globalStore);
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
import React, { Fragment, useEffect, useState } from 'react';
import { useDispatch } from 'react-redux';
import Form from '@rjsf/bootstrap-4';
import { UiSchema } from '@rjsf/core';
import { diff } from 'deep-object-diff';
import { JSONSchema7 } from 'json-schema';
import { at } from 'lodash';
import { setCurrentPartPropertyFocus } from 'apps/authoring/store/parts/slice';
import ColorPickerWidget from './custom/ColorPickerWidget';
import CustomCheckbox from './custom/CustomCheckbox';
import { DropdownOptionsEditor } from './custom/DropdownOptionsEditor';
Expand Down Expand Up @@ -51,7 +53,7 @@ const PropertyEditor: React.FC<PropertyEditorProps> = ({
onfocusHandler,
}) => {
const [formData, setFormData] = useState<any>(value);

const dispatch = useDispatch();
const findDiffType = (changedProp: any): string => {
const diffType: Record<string, unknown>[] = Object.values(changedProp);
if (typeof diffType[0] === 'object') {
Expand Down Expand Up @@ -99,8 +101,10 @@ const PropertyEditor: React.FC<PropertyEditorProps> = ({
if (onfocusHandler) {
onfocusHandler(false);
}
dispatch(setCurrentPartPropertyFocus({ focus: false }));
}}
onBlur={(key, changed) => {
dispatch(setCurrentPartPropertyFocus({ focus: true }));
// key will look like root_Position_x
// changed will be the new value
// formData will be the current state of the form
Expand Down
27 changes: 24 additions & 3 deletions assets/src/apps/delivery/Delivery.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
import React, { useEffect, useMemo } from 'react';
import React, { useEffect, useMemo, useState } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import useWindowSize from 'components/hooks/useWindowSize';
import { getModeFromLocalStorage } from 'components/misc/DarkModeSelector';
import { janus_std } from 'adaptivity/janus-scripts/builtin_functions';
import { defaultGlobalEnv, evalScript } from 'adaptivity/scripting';
import { isDarkMode } from 'utils/browser';
import PreviewTools from './components/PreviewTools';
import { DeadlineTimer } from './layouts/deck/DeadlineTimer';
import DeckLayoutView from './layouts/deck/DeckLayoutView';
Expand Down Expand Up @@ -78,6 +80,7 @@ const Delivery: React.FC<DeliveryProps> = ({
const screenIdleExpirationTime = useSelector(selectScreenIdleExpirationTime);
const screenIdleTimeOutTriggered = useSelector(selectScreenIdleTimeOutTriggered);

const [currentTheme, setCurrentTheme] = useState('auto');
// Gives us the deadline for this assessment to be completed by.
// We subtract out the server time and add in our local time in case the client system clock is off.
// Measured in milliseconds-epoch to match Date.now()
Expand Down Expand Up @@ -107,8 +110,26 @@ const Delivery: React.FC<DeliveryProps> = ({
return () => clearTimeout(timer);
}, [screenIdleExpirationTime]);

const handleUserThemePreferende = () => {
switch (getModeFromLocalStorage()) {
case 'dark':
setCurrentTheme('dark');
break;
case 'auto':
if (isDarkMode()) {
setCurrentTheme('dark');
} else {
setCurrentTheme('light');
}
break;
case 'light':
setCurrentTheme('light');
break;
}
};
useEffect(() => {
setInitialPageState();
handleUserThemePreferende();
}, []);

const setInitialPageState = () => {
Expand Down Expand Up @@ -154,11 +175,11 @@ const Delivery: React.FC<DeliveryProps> = ({
const dialogMessage = content?.custom?.logoutMessage;
const fullscreen = !content?.displayApplicationChrome;

// this is something SS does....
// this is something SS does.....
const { width: windowWidth } = useWindowSize();
const isLessonEnded = useSelector(selectLessonEnd);
return (
<div className={`${parentDivClasses.join(' ')} ${localStorage?.theme || ''}`}>
<div className={`${parentDivClasses.join(' ')} ${currentTheme}`}>
{previewMode && <PreviewTools model={content?.model} />}
<div className="mainView" role="main" style={{ width: windowWidth }}>
<LayoutView pageTitle={pageTitle} previewMode={previewMode} pageContent={content} />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@ import guid from 'utils/guid';
import { AuthoringElement, AuthoringElementProps } from '../AuthoringElement';
import { AuthoringElementProvider, useAuthoringElementContext } from '../AuthoringElementProvider';
import { Explanation } from '../common/explanation/ExplanationAuthoring';
import { ActivityScoring } from '../common/responses/ActivityScoring';
import * as ActivityTypes from '../types';
import { MediaItemRequest } from '../types';
import { ICActions } from './actions';
Expand Down Expand Up @@ -193,7 +192,6 @@ const ImageCoding = (props: AuthoringElementProps<ImageCodingModelSchema>) => {
<TabbedNavigation.Tabs>
<TabbedNavigation.Tab label="Answer Key">
{solutionParameters()}
<ActivityScoring partId={model.authoring.parts[0].id} />
<Feedback
{...sharedProps}
projectSlug={props.projectSlug}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import React from 'react';
import { useAuthoringElementContext } from 'components/activities/AuthoringElementProvider';
import { AuthoringButtonConnected } from 'components/activities/common/authoring/AuthoringButton';
import { GradingApproachDropdown } from 'components/activities/common/authoring/GradingApproachDropdown';
import { MCActions } from 'components/activities/common/authoring/actions/multipleChoiceActions';
import { usesCustomScoring } from 'components/activities/common/authoring/actions/scoringActions';
import { ChoicesDelivery } from 'components/activities/common/choices/delivery/ChoicesDelivery';
Expand All @@ -17,12 +18,14 @@ import {
MultiInputSchema,
} from 'components/activities/multi_input/schema';
import { getCorrectChoice } from 'components/activities/multiple_choice/utils';
import { ShortAnswerActions } from 'components/activities/short_answer/actions';
import { InputEntry } from 'components/activities/short_answer/sections/InputEntry';
import { getTargetedResponses } from 'components/activities/short_answer/utils';
import { Response, RichText, makeResponse } from 'components/activities/types';
import { GradingApproach, Response, RichText, makeResponse } from 'components/activities/types';
import { Radio } from 'components/misc/icons/radio/Radio';
import { getCorrectResponse } from 'data/activities/model/responses';
import { containsRule, eqRule, equalsRule } from 'data/activities/model/rules';
import { getPartById } from 'data/activities/model/utils';
import { defaultWriterContext } from 'data/content/writers/context';
import { MultiInputScoringMethod } from '../MultiInputScoringMethod';

Expand Down Expand Up @@ -96,8 +99,19 @@ export const AnswerKeyTab: React.FC<Props> = (props) => {
</>
);
}

// else text/numeric input. Allow manual grading as for short answers
return (
<div className="d-flex flex-column mb-2">
<GradingApproachDropdown
editMode={editMode}
selected={
getPartById(model, props.input.partId)?.gradingApproach || GradingApproach.automatic
}
onChange={(gradingApproach) =>
dispatch(ShortAnswerActions.setGradingApproach(gradingApproach, props.input.partId))
}
/>
<InputEntry
key={getCorrectResponse(model, props.input.partId).id}
inputType={props.input.inputType}
Expand Down
4 changes: 2 additions & 2 deletions assets/src/components/activities/short_answer/actions.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { Responses } from 'data/activities/model/responses';
import { getPartById } from 'data/activities/model/utils';
import { GradingApproach } from '../types';
import { GradingApproach, HasParts } from '../types';
import { InputType, ShortAnswerModelSchema } from './schema';

export const ShortAnswerActions = {
Expand All @@ -20,7 +20,7 @@ export const ShortAnswerActions = {
};
},
setGradingApproach(gradingApproach: GradingApproach, partId: string) {
return (model: ShortAnswerModelSchema) => {
return (model: HasParts) => {
getPartById(model, partId).gradingApproach = gradingApproach;
};
},
Expand Down
33 changes: 33 additions & 0 deletions lib/oli/activities/realizer/query/bank_entry.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
defmodule Oli.Activities.Realizer.Query.BankEntry do
@moduledoc """
Meta data for each activity bank entry.
"""

defstruct [
:resource_id,
:tags,
:objectives,
:activity_type_id
]

@type t() :: %__MODULE__{
resource_id: integer(),
tags: list(),
objectives: list(),
activity_type_id: integer()
}

def from_map(m) do
objectives =
Map.values(m.objectives)
|> List.flatten()
|> MapSet.new()

%Oli.Activities.Realizer.Query.BankEntry{
resource_id: m.resource_id,
tags: MapSet.new(m.tags),
objectives: objectives,
activity_type_id: m.activity_type_id
}
end
end
5 changes: 3 additions & 2 deletions lib/oli/activities/realizer/query/source.ex
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,12 @@ defmodule Oli.Activities.Realizer.Query.Source do
"""

@enforce_keys [:publication_id, :blacklisted_activity_ids, :section_slug]
defstruct [:publication_id, :blacklisted_activity_ids, :section_slug]
defstruct [:publication_id, :blacklisted_activity_ids, :section_slug, :bank]

@type t() :: %__MODULE__{
publication_id: integer(),
blacklisted_activity_ids: [integer()],
section_slug: String.t()
section_slug: String.t(),
bank: list()
}
end
80 changes: 79 additions & 1 deletion lib/oli/activities/realizer/selection.ex
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ defmodule Oli.Activities.Realizer.Selection do
alias Oli.Activities.Realizer.Query.Executor
alias Oli.Activities.Realizer.Query.Paging
alias Oli.Activities.Realizer.Query.Result
alias Oli.Activities.Realizer.Logic.Expression

@type t() :: %__MODULE__{
id: String.t(),
Expand Down Expand Up @@ -49,10 +50,87 @@ defmodule Oli.Activities.Realizer.Selection do
Returns {:error, e} on a failure to execute the query.
"""
def fulfill(%Selection{count: count} = selection, %Source{} = source) do
def fulfill(%Selection{count: count} = selection, %Source{bank: nil} = source) do
run(selection, source, %Paging{limit: count, offset: 0})
end

def fulfill(%Selection{count: count} = selection, %Source{} = source) do
{all, _} = fulfill_from_bank(selection, source)
returned_count = Enum.count(all)

if returned_count < count do
{:partial, %Result{rows: all, rowCount: returned_count, totalCount: returned_count}}
else
{:ok, %Result{rows: all, rowCount: returned_count, totalCount: returned_count}}
end
end

def fulfill_from_bank(%Selection{count: count} = selection, %Source{bank: bank} = source) do
blacklisted = MapSet.new(source.blacklisted_activity_ids)

expressions =
case selection.logic.conditions do
nil -> []
%Logic.Clause{children: children} -> children
%Logic.Expression{} = e -> [e]
end

Enum.reduce_while(bank, {[], 1}, fn activity, {all, total} ->
case !MapSet.member?(blacklisted, activity.resource_id) and
Enum.all?(expressions, &evaluate_expression(&1, activity)) do
true ->
if total == count do
{:halt, {[activity | all], total + 1}}
else
{:cont, {[activity | all], total + 1}}
end

false ->
{:cont, {all, total}}
end
end)
end

defp evaluate_expression(%Expression{fact: :tags} = e, activity) do
do_evaluate_expression(e, activity, :tags)
end

defp evaluate_expression(%Expression{fact: :objectives} = e, activity) do
do_evaluate_expression(e, activity, :objectives)
end

defp evaluate_expression(%Expression{fact: :type, operator: operator, value: value}, activity) do
case operator do
:contains ->
MapSet.new([activity.activity_type_id]) |> MapSet.subset?(MapSet.new(value))

:does_not_contain ->
MapSet.new([activity.activity_type_id]) |> MapSet.subset?(MapSet.new(value)) |> Kernel.!()

:equals ->
value == activity.activity_type_id

:does_not_equal ->
value != activity.activity_type_id
end
end

defp do_evaluate_expression(%Expression{operator: operator, value: value}, activity, field) do
case operator do
:contains ->
MapSet.new(value) |> MapSet.subset?(Map.get(activity, field))

:does_not_contain ->
MapSet.new(value) |> MapSet.subset?(Map.get(activity, field)) |> Kernel.!()

:equals ->
MapSet.equal?(Map.get(activity, field), MapSet.new(value))

:does_not_equal ->
!MapSet.equal?(Map.get(activity, field), MapSet.new(value))
end
end

@doc """
Tests the fulfillment of a selection by querying the database for matching activities.
Expand Down
3 changes: 2 additions & 1 deletion lib/oli/analytics/by_activity.ex
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ defmodule Oli.Analytics.ByActivity do
number_of_attempts: analytics.number_of_attempts,
relative_difficulty: analytics.relative_difficulty
},
preload: [:resource_type]
preload: [:resource_type],
distinct: [activity]
end
end
3 changes: 2 additions & 1 deletion lib/oli/analytics/by_page.ex
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,8 @@ defmodule Oli.Analytics.ByPage do
number_of_attempts: analytics.number_of_attempts,
relative_difficulty: analytics.relative_difficulty
},
preload: [:resource_type]
preload: [:resource_type],
distinct: [activity]
)
end

Expand Down
6 changes: 3 additions & 3 deletions lib/oli/authoring/clone.ex
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@ defmodule Oli.Authoring.Clone do
alias Oli.Authoring.Authors.AuthorProject
alias Oli.Delivery.Sections.Blueprint

def clone_bluprints(blueprints) do
Enum.map(blueprints, &Blueprint.duplicate/1)
def clone_blueprints(blueprints, cloned_from_project_publication_ids) do
Enum.map(blueprints, &Blueprint.duplicate(&1, %{}, cloned_from_project_publication_ids))
end

def clone_project(project_slug, author, opts \\ []) do
Expand Down Expand Up @@ -54,7 +54,7 @@ defmodule Oli.Authoring.Clone do
_ <- clone_all_project_activity_registrations(base_project.id, cloned_project.id),
Blueprint.get_blueprint_by_base_project(base_project)
|> Enum.map(fn blueprint -> %{blueprint | base_project_id: cloned_project.id} end)
|> clone_bluprints() do
|> clone_blueprints({base_project.id, cloned_publication.id}) do
cloned_project
else
{:error, error} -> Repo.rollback(error)
Expand Down
1 change: 1 addition & 0 deletions lib/oli/authoring/editing/container_editor.ex
Original file line number Diff line number Diff line change
Expand Up @@ -394,6 +394,7 @@ defmodule Oli.Authoring.Editing.ContainerEditor do
|> Map.drop([:slug, :inserted_at, :updated_at, :resource_id, :resource])
|> Map.put(:title, "#{original_page.title} (copy)")
|> Map.put(:content, nil)
|> Map.put(:previous_revision_id, nil)
|> then(fn map ->
if is_nil(map.legacy) do
map
Expand Down
Loading

0 comments on commit 907b3a1

Please sign in to comment.