Allow the quiz-builder picker to expand multiple topics at once

Sources could already draw from any number of topics, but the picker
UI only ever showed one topic's questions at a time, forcing a teacher
to lose their place switching between topics while assembling a quiz.
This commit is contained in:
danamir
2026-08-09 00:54:45 +03:00
parent b5921292db
commit 63bdd7dd74
3 changed files with 188 additions and 116 deletions

View File

@@ -175,34 +175,74 @@ let update (token: string option) (msg: Msg) (model: Model) : Model * Cmd<Msg> =
{ model with QuizForm = { model.QuizForm with ShuffleQuestions = value } }, Cmd.none
| SetQuizShuffleAnswers value ->
{ model with QuizForm = { model.QuizForm with ShuffleAnswers = value } }, Cmd.none
| SelectPickerTopic topicId ->
let cmd =
Cmd.OfAsync.either
(Api.listQuestionsInTopic token)
topicId
(function
| Ok questions -> PickerQuestionsLoaded questions
| Error err -> PickerQuestionsLoadFailed err)
(fun ex -> PickerQuestionsLoadFailed ex.Message)
| ToggleTopicInPicker topicId ->
let form = model.QuizForm
let existingPoolCount =
model.QuizForm.Sources
|> List.tryPick (function
| PoolDraft(tid, count) when tid = topicId -> Some count
| _ -> None)
if form.PickerTopicIds.Contains topicId then
// Deselecting a topic drops it from the quiz entirely its
// fixed picks and pool rule no longer apply.
let belongsToTopic qid =
form.PickerQuestions
|> List.tryFind (fun q -> q.Id = qid)
|> Option.map (fun q -> q.TopicId = topicId)
|> Option.defaultValue false
let sourcesWithoutTopic =
form.Sources
|> List.filter (function
| PoolDraft(tid, _) -> tid <> topicId
| FixedDraft qid -> not (belongsToTopic qid))
{ model with
QuizForm =
{ form with
PickerTopicIds = form.PickerTopicIds.Remove topicId
PickerQuestions = form.PickerQuestions |> List.filter (fun q -> q.TopicId <> topicId)
PoolCountTexts = form.PoolCountTexts.Remove topicId
Sources = sourcesWithoutTopic } },
Cmd.none
else
let cmd =
Cmd.OfAsync.either
(Api.listQuestionsInTopic token)
topicId
(function
| Ok questions -> PickerQuestionsLoaded(topicId, questions)
| Error err -> PickerQuestionsLoadFailed(topicId, err))
(fun ex -> PickerQuestionsLoadFailed(topicId, ex.Message))
let existingPoolCount =
form.Sources
|> List.tryPick (function
| PoolDraft(tid, count) when tid = topicId -> Some count
| _ -> None)
let poolCountTexts =
match existingPoolCount with
| Some count -> form.PoolCountTexts |> Map.add topicId (string count)
| None -> form.PoolCountTexts
{ model with
QuizForm =
{ form with
PickerTopicIds = form.PickerTopicIds.Add topicId
PickerLoadingTopics = form.PickerLoadingTopics.Add topicId
PoolCountTexts = poolCountTexts } },
cmd
| PickerQuestionsLoaded(topicId, questions) ->
{ model with
QuizForm =
{ model.QuizForm with
PickerTopicId = Some topicId
PickerLoading = true
PickerQuestions = []
PoolCountText = existingPoolCount |> Option.map string |> Option.defaultValue "" } },
cmd
| PickerQuestionsLoaded questions ->
{ model with QuizForm = { model.QuizForm with PickerQuestions = questions; PickerLoading = false } }, Cmd.none
| PickerQuestionsLoadFailed err ->
{ model with QuizForm = { model.QuizForm with PickerLoading = false; Error = Some err } }, Cmd.none
PickerQuestions = model.QuizForm.PickerQuestions @ questions
PickerLoadingTopics = model.QuizForm.PickerLoadingTopics.Remove topicId } },
Cmd.none
| PickerQuestionsLoadFailed(topicId, err) ->
{ model with
QuizForm =
{ model.QuizForm with
PickerLoadingTopics = model.QuizForm.PickerLoadingTopics.Remove topicId
Error = Some err } },
Cmd.none
| ToggleQuestionPick questionId ->
let form = model.QuizForm
@@ -216,7 +256,7 @@ let update (token: string option) (msg: Msg) (model: Model) : Model * Cmd<Msg> =
form.Sources @ [ FixedDraft questionId ]
{ model with QuizForm = { form with Sources = next } }, Cmd.none
| SelectAllInTopic ->
| SelectAllInTopic topicId ->
let form = model.QuizForm
let alreadySelected =
@@ -228,31 +268,32 @@ let update (token: string option) (msg: Msg) (model: Model) : Model * Cmd<Msg> =
let toAdd =
form.PickerQuestions
|> List.filter (fun q -> q.TopicId = topicId)
|> List.map (fun q -> q.Id)
|> List.filter (alreadySelected.Contains >> not)
|> List.map FixedDraft
{ model with QuizForm = { form with Sources = form.Sources @ toAdd } }, Cmd.none
| SetPoolCountText text -> { model with QuizForm = { model.QuizForm with PoolCountText = text } }, Cmd.none
| AddRandomPool ->
| SetPoolCountText(topicId, text) ->
{ model with QuizForm = { model.QuizForm with PoolCountTexts = model.QuizForm.PoolCountTexts |> Map.add topicId text } },
Cmd.none
| AddRandomPool topicId ->
let form = model.QuizForm
let text = form.PoolCountTexts |> Map.tryFind topicId |> Option.defaultValue ""
match form.PickerTopicId with
| None -> model, Cmd.none
| Some topicId ->
match System.Int32.TryParse form.PoolCountText with
| true, count when count > 0 ->
let withoutOldPool =
form.Sources
|> List.filter (function
| PoolDraft(tid, _) -> tid <> topicId
| _ -> true)
match System.Int32.TryParse text with
| true, count when count > 0 ->
let withoutOldPool =
form.Sources
|> List.filter (function
| PoolDraft(tid, _) -> tid <> topicId
| _ -> true)
{ model with QuizForm = { form with Sources = withoutOldPool @ [ PoolDraft(topicId, count) ]; Error = None } },
Cmd.none
| _ ->
{ model with QuizForm = { form with Error = Some "Количество случайных вопросов должно быть положительным числом" } },
Cmd.none
{ model with QuizForm = { form with Sources = withoutOldPool @ [ PoolDraft(topicId, count) ]; Error = None } },
Cmd.none
| _ ->
{ model with QuizForm = { form with Error = Some "Количество случайных вопросов должно быть положительным числом" } },
Cmd.none
| RemoveRandomPool topicId ->
let form = model.QuizForm
@@ -262,9 +303,7 @@ let update (token: string option) (msg: Msg) (model: Model) : Model * Cmd<Msg> =
| PoolDraft(tid, _) -> tid <> topicId
| _ -> true)
let poolCountText = if form.PickerTopicId = Some topicId then "" else form.PoolCountText
{ model with QuizForm = { form with Sources = next; PoolCountText = poolCountText } }, Cmd.none
{ model with QuizForm = { form with Sources = next; PoolCountTexts = form.PoolCountTexts.Remove topicId } }, Cmd.none
| SubmitQuizForm ->
match buildQuizFields model.QuizForm with
| Error err -> { model with QuizForm = { model.QuizForm with Error = Some err } }, Cmd.none