Commit 66668704 authored by Curtis Adam's avatar Curtis Adam
Browse files

WIP: Rework Leitner Simulator

parent 878e5f17
......@@ -1017,6 +1017,7 @@
"simulator": {
"buttonModal": "Tagespensum festlegen",
"buttonSimulate": "Optimales Tagespensum berechnen",
"buttonUpdate": "Aktualisieren",
"title": "Leitner Simulator",
"errorRate": {
"description": "Maximale Anzahl nicht gewusster Karten von __count__ über den gesamten Lernverlauf.",
......
import {Meteor} from "meteor/meteor";
import {check} from "meteor/check";
import {Leitner} from "../subscriptions/leitner";
import {Cards} from "../subscriptions/cards";
import {Cardsets} from "../subscriptions/cardsets";
import {AnswerUtilities} from "../../util/answers";
import {LeitnerTasks} from "../subscriptions/leitnerTasks";
import {LeitnerHistory} from "../subscriptions/leitnerHistory";
import * as config from "../../config/leitner";
import {Workload} from "../subscriptions/workload";
Meteor.methods({
getCardAnswerContent: function (cardIds, cardsetId, disableAnswers) {
......@@ -18,36 +11,10 @@ Meteor.methods({
return AnswerUtilities.getAnswerContent(cardIds, cardsetId, disableAnswers);
},
nextMCCard: function (activeCardId, cardsetId, timestamps) {
let leitner = Leitner.findOne({
card_id: activeCardId,
user_id: Meteor.userId(),
cardset_id: cardsetId,
submitted: true,
active: true
});
let task = LeitnerTasks.findOne(
{cardset_id: cardsetId, user_id: Meteor.userId()}, {fields: {_id: 1}, sort: {session: -1, createdAt: -1}});
if (leitner !== undefined && task !== undefined) {
Leitner.update({
card_id: activeCardId,
user_id: Meteor.userId(),
cardset_id: cardsetId,
submitted: true
}, {$set: {
active: false
}});
check(activeCardId, String);
check(activeCardId, String);
LeitnerHistory.update({
card_id: activeCardId,
user_id: Meteor.userId(),
cardset_id: cardsetId,
task_id: task._id
}, {$set: {
timestamps: timestamps
}});
}
AnswerUtilities.nextMCCard(activeCardId, cardsetId, timestamps);
},
setMCAnswers: function (cardIds, activeCardId, cardsetId, userAnswers, timestamps) {
check(cardIds, [String]);
......@@ -55,70 +22,23 @@ Meteor.methods({
check(cardsetId, String);
check(userAnswers, [Number]);
let activeLeitner = Leitner.findOne({
card_id: activeCardId,
user_id: Meteor.userId(),
cardset_id: cardsetId
});
if (activeLeitner !== undefined && activeLeitner.submitted !== true) {
let task = LeitnerTasks.findOne(
{cardset_id: cardsetId, user_id: Meteor.userId()}, {fields: {_id: 1}, sort: {session: -1, createdAt: -1}});
let card = Cards.findOne({_id: activeCardId});
let cardset = Cardsets.findOne({_id: activeLeitner.cardset_id});
if (task !== undefined && card !== undefined && cardset !== undefined) {
userAnswers = userAnswers.sort();
// answer - 0 = known, 1 = not known
let answer = 0;
if (_.difference(userAnswers, card.answers.rightAnswers).length > 0 || userAnswers.length !== card.answers.rightAnswers.length) {
answer = 1;
}
let selectedBox = activeLeitner.box + 1;
let nextDate = new Date();
if (answer) {
if (config.wrongAnswerMode === 1) {
if (activeLeitner.box > 1) {
selectedBox = activeLeitner.box - 1;
} else {
selectedBox = 1;
}
} else {
selectedBox = 1;
}
}
let workload = Workload.findOne({cardset_id: activeLeitner.cardset_id, user_id: activeLeitner.user_id});
let lowestPriority = workload.leitner.nextLowestPriority;
let newPriority = lowestPriority[selectedBox - 1];
nextDate = new Date(nextDate.getTime() + cardset.learningInterval[selectedBox - 1] * 86400000);
Leitner.update({
_id: activeLeitner._id
}, {$set: {
box: selectedBox,
nextDate: nextDate,
currentDate: new Date(),
priority: newPriority,
submitted: true
}});
LeitnerHistory.update({
card_id: activeCardId,
user_id: Meteor.userId(),
cardset_id: cardsetId,
task_id: task._id
}, {$set: {
timestamps: timestamps,
answer: answer,
"mcAnswers.user": userAnswers,
"mcAnswers.card": card.answers.rightAnswers
}});
return AnswerUtilities.getAnswerContent(cardIds, cardsetId, true);
} else {
throw new Meteor.Error("Leitner Task not found");
}
} else {
return AnswerUtilities.getAnswerContent(cardIds, cardsetId, true);
return AnswerUtilities.setMCAnswers(cardIds, activeCardId, cardsetId, userAnswers, timestamps);
},
/** Function marks an active leitner card as learned
* @param {string} cardset_id - The cardset id from the card
* @param {string} card_id - The id from the card
* @param {boolean} answer - 0 = known, 1 = not known
* @param {Object} timestamps - Timestamps for viewing the question and viewing the answer
* */
updateLeitner: function (cardset_id, card_id, answer, timestamps) {
// Make sure the user is logged in
if (!Meteor.userId() || Roles.userIsInRole(this.userId, ["firstLogin", "blocked"])) {
throw new Meteor.Error("not-authorized");
}
check(cardset_id, String);
check(card_id, String);
check(answer, Boolean);
return AnswerUtilities.updateSimpleAnswer(cardset_id, card_id, answer, timestamps);
}
});
......@@ -4,115 +4,16 @@ import {Cardsets} from "../subscriptions/cardsets.js";
import {check} from "meteor/check";
import {UserPermissions} from "../../util/permissions";
import {CardsetUserlist} from "../../util/cardsetUserlist";
import * as config from "../../config/leitner";
import {LeitnerUtilities} from "../../util/leitner";
import {Leitner} from "../subscriptions/leitner.js";
import {LeitnerHistory} from "../subscriptions/leitnerHistory";
import {LeitnerTasks} from "../subscriptions/leitnerTasks";
import {Workload} from "../subscriptions/workload";
import {Wozniak} from "../subscriptions/wozniak";
import {CardType} from "../../util/cardTypes";
export const Learned = new Mongo.Collection("learned");
if (Meteor.isServer) {
Meteor.methods({
/** Function marks an active leitner card as learned
* @param {string} cardset_id - The cardset id from the card
* @param {string} card_id - The id from the card
* @param {boolean} answer - 0 = known, 1 = not known
* @param {Object} timestamps - Timestamps for viewing the question and viewing the answer
* */
updateLeitner: function (cardset_id, card_id, answer, timestamps) {
// Make sure the user is logged in
if (!Meteor.userId() || Roles.userIsInRole(this.userId, ["firstLogin", "blocked"])) {
throw new Meteor.Error("not-authorized");
}
check(cardset_id, String);
check(card_id, String);
check(answer, Boolean);
let cardset = Cardsets.findOne({_id: cardset_id});
if (cardset !== undefined) {
let query = {};
query.card_id = card_id;
query.cardset_id = cardset_id;
query.user_id = Meteor.userId();
query.active = true;
let currentLearned = Leitner.findOne(query);
if (currentLearned !== undefined) {
let selectedBox = currentLearned.box + 1;
let nextDate = new Date();
// Move card back as the answer was wrong "not known"
if (answer) {
if (config.wrongAnswerMode === 1) {
if (currentLearned.box > 1) {
selectedBox = currentLearned.box - 1;
} else {
selectedBox = 1;
}
} else {
selectedBox = 1;
}
} else {
let cardCardType;
if (cardset.shuffled) {
let cardCardset = Cardsets.findOne({_id: currentLearned.original_cardset_id});
cardCardType = cardCardset.cardType;
} else {
cardCardType = cardset.cardType;
}
if (CardType.gotNonRepeatingLeitner(cardCardType)) {
//Move to the last box if card Type got no repetition
selectedBox = 6;
}
}
let workload = Workload.findOne({cardset_id: currentLearned.cardset_id, user_id: currentLearned.user_id});
let lowestPriority = workload.leitner.nextLowestPriority;
let newPriority = lowestPriority[selectedBox - 1];
if (selectedBox === 6) {
lowestPriority = 0;
}
nextDate = new Date(nextDate.getTime() + cardset.learningInterval[selectedBox - 1] * 86400000);
Leitner.update(currentLearned._id, {
$set: {
box: selectedBox,
active: false,
nextDate: nextDate,
currentDate: new Date(),
priority: newPriority
}
});
let leitnerTask = LeitnerTasks.findOne({cardset_id: currentLearned.cardset_id, user_id: currentLearned.user_id}, {sort: {session: -1}});
if (leitnerTask !== undefined) {
delete query.active;
query.task_id = leitnerTask._id;
LeitnerHistory.update(query, {
$set: {
box: selectedBox,
answer: answer ? 1 : 0,
timestamps: timestamps
}
});
}
lowestPriority[selectedBox - 1] = newPriority - 1;
Workload.update({cardset_id: currentLearned.cardset_id, user_id: currentLearned.user_id}, {
$set: {
"leitner.nextLowestPriority": lowestPriority
}
});
}
LeitnerUtilities.updateLeitnerWorkload(cardset_id, Meteor.userId());
}
},
deleteLeitner: function (cardset_id) {
check(cardset_id, String);
......
......@@ -15,17 +15,7 @@ Meteor.methods({
initializeWorkloadData: function (cardset_id, user_id) {
check(cardset_id, String);
check(user_id, String);
let workload = Workload.findOne({user_id: user_id, cardset_id: cardset_id});
if (workload === undefined) {
Workload.insert({
cardset_id: cardset_id,
user_id: user_id,
leitner: {
bonus: false,
nextLowestPriority: [-1, -1, -1, -1, -1]
}
});
}
LeitnerUtilities.initialzeWorkload(cardset_id, user_id);
},
markLeitnerAutoPDF: function (cardset_id, card_id) {
check(cardset_id, String);
......
import "./intervals.html";
import {BonusForm} from "../../../../../../util/bonusForm";
import {LeitnerProgress} from "../../../../../../util/leitnerProgress";
import {LeitnerSimulator} from "../../../../../../util/leitnerSimulator";
/*
* ############################################################################
......@@ -11,7 +11,6 @@ import {LeitnerProgress} from "../../../../../../util/leitnerProgress";
Template.bonusFormIntervals.events({
"change input": function () {
BonusForm.adjustInterval();
BonusForm.initializeSimulatorData();
LeitnerProgress.updateGraph();
LeitnerSimulator.updateSimulator(false);
}
});
import "./maxWorkload.html";
import {BonusForm} from "../../../../../../util/bonusForm";
import {LeitnerProgress} from "../../../../../../util/leitnerProgress";
import {LeitnerSimulator} from "../../../../../../util/leitnerSimulator";
/*
* ############################################################################
......@@ -11,7 +11,6 @@ import {LeitnerProgress} from "../../../../../../util/leitnerProgress";
Template.bonusFormMaxWorkload.events({
"change input": function () {
BonusForm.adjustMaxWorkload();
BonusForm.initializeSimulatorData();
LeitnerProgress.updateGraph();
LeitnerSimulator.updateSimulator();
}
});
<template name="bonusFormOpenSimulator">
<div class="form-group row">
<div class="col-md-12">
<div class="btn btn-success btn-raised" data-toggle="modal" data-target="#cardsetLeitnerSimulatorModal">
<div class="btn btn-success btn-raised" id="openSimulatorModal" data-toggle="modal" data-target="#cardsetLeitnerSimulatorModal">
<span class="flex-content"><i class="fas fa-calculator"></i>&nbsp;{{_ "bonus.form.simulator.buttonModal"}}</span>
</div>
</div>
......
import "./openSimulator.html";
import {LeitnerSimulator} from "../../../../../../util/leitnerSimulator";
import {Session} from "meteor/session";
/*
* ############################################################################
* bonusFormOpenSimulator
* ############################################################################
*/
Template.bonusFormOpenSimulator.events({
"click #openSimulatorModal": function () {
Session.set('activeSimulatorSnapshotDate', 0);
LeitnerSimulator.createSnapshotDates();
LeitnerSimulator.updateSimulator();
}
});
import "./calculate.html";
import {BonusForm} from "../../../../../../../util/bonusForm";
import {LeitnerProgress} from "../../../../../../../util/leitnerProgress";
import {LeitnerSimulator} from "../../../../../../../util/leitnerSimulator";
/*
* ############################################################################
......@@ -10,8 +9,7 @@ import {LeitnerProgress} from "../../../../../../../util/leitnerProgress";
Template.bonusFormSimulatorCalculate.events({
'click .calculateWorkload': function () {
BonusForm.adjustErrorCount();
BonusForm.calculateWorkload(BonusForm.getMaxWorkload());
LeitnerProgress.updateGraph();
LeitnerSimulator.initializeSimulatorData(false);
LeitnerSimulator.calculateWorkload(LeitnerSimulator.getTempMaxWorkload());
}
});
import "./errorRate.html";
import {BonusForm} from "../../../../../../../util/bonusForm";
import {LeitnerProgress} from "../../../../../../../util/leitnerProgress";
/*
* ############################################################################
......@@ -17,7 +16,5 @@ Template.bonusFormSimulatorErrorRate.helpers({
Template.bonusFormSimulatorErrorRate.events({
'change input': function () {
BonusForm.adjustErrorCount();
BonusForm.initializeSimulatorData();
LeitnerProgress.updateGraph();
}
});
import "./intervals.html";
import {BonusForm} from "../../../../../../../util/bonusForm";
import {LeitnerProgress} from "../../../../../../../util/leitnerProgress";
/*
* ############################################################################
......@@ -11,7 +10,5 @@ import {LeitnerProgress} from "../../../../../../../util/leitnerProgress";
Template.bonusFormSimulatorIntervals.events({
"change input": function () {
BonusForm.adjustInterval(true);
BonusForm.initializeSimulatorData();
LeitnerProgress.updateGraph();
}
});
import "./maxWorkload.html";
import {BonusForm} from "../../../../../../../util/bonusForm";
import {LeitnerProgress} from "../../../../../../../util/leitnerProgress";
/*
* ############################################################################
......@@ -11,7 +10,5 @@ import {LeitnerProgress} from "../../../../../../../util/leitnerProgress";
Template.bonusFormSimulatorMaxWorkload.events({
"change input": function () {
BonusForm.adjustMaxWorkload();
BonusForm.initializeSimulatorData();
LeitnerProgress.updateGraph();
}
});
<template name="bonusFormSimulatorProgress">
<div class="hidden-xs hidden-sm">
{{#if isCalculating}}
{{> loadingScreen}}
{{currentDay}}
{{else}}
{{> progress type="simulator"}}
{{> bonusFormSimulatorSnapshots}}
{{/if}}
</div>
</template>
import "./progress.html";
import {LeitnerSimulator} from "../../../../../../../util/leitnerSimulator";
/*
* ############################################################################
* bonusFormSimulatorProgress
* ############################################################################
*/
Template.bonusFormSimulatorProgress.helpers({
isCalculating: function () {
return LeitnerSimulator.isCalculating();
},
currentDay: function () {
return LeitnerSimulator.getCurrentDay();
}
});
import "./snapshots.html";
import {Session} from "meteor/session";
import {BonusForm} from "../../../../../../../util/bonusForm";
import {LeitnerProgress} from "../../../../../../../util/leitnerProgress";
import {LeitnerSimulator} from "../../../../../../../util/leitnerSimulator";
/*
* ############################################################################
......@@ -13,7 +13,7 @@ Session.setDefault('activeSimulatorSnapshotDate', 0);
Template.bonusFormSimulatorSnapshots.helpers({
getSnapshots: function () {
return BonusForm.getSnapshotDates();
return LeitnerSimulator.getSnapshotDates();
},
isActive: function (index) {
if (Session.get('activeSimulatorSnapshotDate') === index) {
......
<template name="bonusFormSimulatorUpdate">
<div class="form-group row">
<div class="col-md-12">
<div class="btn btn-info btn-raised updateSimulator">
<span class="flex-content"><i class="fas fa-redo"></i>&nbsp;{{_ "bonus.form.simulator.buttonUpdate"}}</span>
</div>
</div>
</div>
</template>
import "./update.html";
import {LeitnerSimulator} from "../../../../../../../util/leitnerSimulator";
/*
* ############################################################################
* bonusFormSimulatorUpdate
* ############################################################################
*/
Template.bonusFormSimulatorCalculate.events({
'click .updateSimulator': function () {
LeitnerSimulator.updateSimulator();
}
});
......@@ -15,11 +15,9 @@
{{> bonusFormSimulatorMaxWorkload}}
{{> bonusFormSimulatorIntervals}}
{{> bonusFormSimulatorCalculate}}
{{> bonusFormSimulatorUpdate}}
{{> bonusFormSimulatorErrorRate}}
<div class="hidden-xs hidden-sm">
{{> progress type="simulator"}}
{{> bonusFormSimulatorSnapshots}}
</div>
{{> bonusFormSimulatorProgress}}
</div>
<div class="modal-footer">
{{> bonusFormSimulatorCancel}}
......
......@@ -7,12 +7,11 @@ import "./item/resetErrorRate.js";
import "./item/maxWorkload.js";
import "./item/snapshots.js";
import "./item/intervals.js";
import "./item/update.js";
import "./item/progress.js";
import {Template} from "meteor/templating";
import "./leitnerSimulator.html";
import {BonusForm} from "../../../../../../util/bonusForm";
import {Session} from "meteor/session";
import {LeitnerProgress} from "../../../../../../util/leitnerProgress";
import {FlowRouter} from "meteor/ostrio:flow-router-extra";
/*
* ############################################################################
......@@ -21,13 +20,6 @@ import {FlowRouter} from "meteor/ostrio:flow-router-extra";
*/
Template.cardsetLeitnerSimulatorForm.onRendered(function () {
$('#cardsetLeitnerSimulatorModal').on('show.bs.modal', function () {
LeitnerProgress.setupTempData(FlowRouter.getParam('_id'), '', 'simulator');
Session.set('activeSimulatorSnapshotDate', 0);
BonusForm.createSnapshotDates();
BonusForm.initializeSimulatorWorkload();
LeitnerProgress.updateGraph();
});
$('#cardsetLeitnerSimulatorModal').on('hidden.bs.modal', function () {
LeitnerProgress.clearTempData();
$('body').addClass('modal-open');
......
<template name="progress">
{{#if isProgressType 'cardset'}}
{{#if isShuffledCardset}}
{{#unless isProgressType 'user'}}
{{#unless isProgressType 'admin'}}
{{#if isShuffledCardset}}
<div class="row">
{{> graphCardsetFilter}}
</div>
{{/if}}
<div class="row">
{{> graphCardsetFilter}}
<div class="col-xs-12 ">{{{getMaxWorkload}}}{{#unless isShuffledCardset}}<span class="hidden-xs">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;{{_ "leitnerProgress.cardCount" cardCount=(getCardsetCardCount true)}}</span>{{/unless}}</div>
{{#unless isShuffledCardset}}<div class="col-xs-12 visible-xs">{{_ "leitnerProgress.cardCount" cardCount=(getCardsetCardCount true)}}</div>{{/unless}}
</div>
{{/if}}
<div class="row">
<div class="col-xs-12 ">{{{getMaxWorkload}}}{{#unless isShuffledCardset}}<span class="hidden-xs">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;{{_ "leitnerProgress.cardCount" cardCount=(getCardsetCardCount true)}}</span>{{/unless}}</div>
{{#unless isShuffledCardset}}<div class="col-xs-12 visible-xs">{{_ "leitnerProgress.cardCount" cardCount=(getCardsetCardCount true)}}</div>{{/unless}}
</div>
{{/if}}
{{/unless}}
{{/unless}}
{{#if isProgressType 'user'}}
<div class="col-xs-12">{{_ "leitnerProgress.cardCount" cardCount=getTotalLeitnerCardCountUser}}</div>
{{/if}}
......@@ -25,8 +27,8 @@
<div id="setLeitnerProgressCardsetFilter">
<div class="dropdown">
<div class="btn-group col-xs-12 col-sm-6 col-md-5">
<button id="setCardsetFilter" type="button"
class="btn btn-raised btn-default setCardsetFilterDropdown longButton"
<button type="button"
class="setCardsetFilter btn btn-raised btn-default setCardsetFilterDropdown longButton"
data-toggle="dropdown" aria-haspopup="true"
aria-expanded="false"
value="-1">{{_ "leitnerProgress.indexDefault" cardsetCount=(getCardsetCount false) cardCount=(getCardsetCardCount true)}}
......@@ -39,7 +41,7 @@
<span class="sr-only"></span></span>
</button>
<ul class="dropdown-menu btn-raised longButton">
<li class="cardset" value="-1" data-id="-1" data-count="{{getCardsetCardCount true}}" data-name="{{getCardsetCount}}">
<li class="cardset" value="-1" data-index="0" data-id="-1" data-count="{{getCardsetCardCount true}}" data-name="{{getCardsetCount}}">
<a href="#">
<div id="activeCardset">
{{_ "leitnerProgress.indexDefault" cardsetCount=(getCardsetCount false) cardCount=(getCardsetCardCount true)}}
......@@ -50,7 +52,7 @@
</a>
</li>
{{#each (getCardsetCardCount false)}}
<li class="cardset" value="{{this._id}}" data-id="{{this._id}}" data-count="{{this.quantity}}" data-name="{{this.name}}">
<li class="cardset" value="{{this._id}}" data-index="{{setIndex @index}}" data-id="{{this._id}}" data-count="{{this.quantity}}" data-name="{{this.name}}">
<a href="#">
{{this.name}}
<div class="col-xs-12 cardset-details">
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment