diff --git a/src/plugin/flashcard/Controller/FlashcardDeckController.php b/src/plugin/flashcard/Controller/FlashcardDeckController.php
index 9a6b95973cc..51fbec9bc9b 100644
--- a/src/plugin/flashcard/Controller/FlashcardDeckController.php
+++ b/src/plugin/flashcard/Controller/FlashcardDeckController.php
@@ -93,7 +93,6 @@ public function updateProgressionAction(Flashcard $card, User $user, Request $re
$node = $deck->getResourceNode();
$resourceUserEvaluation = $this->evaluationManager->getResourceUserEvaluation($node, $user);
- // Mise à jour de la progression de la carte
$this->evaluationManager->updateCardDrawnProgression($card, $user, $request->get('isSuccessful'));
$attempt = $this->resourceEvalRepo->findOneInProgress($node, $user);
diff --git a/src/plugin/flashcard/Entity/FlashcardDeck.php b/src/plugin/flashcard/Entity/FlashcardDeck.php
index 0ec07112d11..f0d347a01e5 100644
--- a/src/plugin/flashcard/Entity/FlashcardDeck.php
+++ b/src/plugin/flashcard/Entity/FlashcardDeck.php
@@ -51,6 +51,11 @@ class FlashcardDeck extends AbstractResource
*/
private ?string $wrongButtonLabel = null;
+ /**
+ * @ORM\Column(type="boolean")
+ */
+ private bool $showLeitnerRules = false;
+
public function __construct()
{
parent::__construct();
@@ -142,4 +147,14 @@ public function setWrongButtonLabel(?string $wrongButtonLabel): void
{
$this->wrongButtonLabel = $wrongButtonLabel;
}
+
+ public function getShowLeitnerRules(): bool
+ {
+ return $this->showLeitnerRules;
+ }
+
+ public function setShowLeitnerRules(bool $showLeitnerRules): void
+ {
+ $this->showLeitnerRules = $showLeitnerRules;
+ }
}
diff --git a/src/plugin/flashcard/Installation/Migrations/Version20231127105116.php b/src/plugin/flashcard/Installation/Migrations/Version20231127105116.php
new file mode 100644
index 00000000000..f6c86f8fa5e
--- /dev/null
+++ b/src/plugin/flashcard/Installation/Migrations/Version20231127105116.php
@@ -0,0 +1,30 @@
+addSql('
+ ALTER TABLE claro_flashcard_deck
+ ADD showLeitnerRules TINYINT(1) NOT NULL
+ ');
+ }
+
+ public function down(Schema $schema): void
+ {
+ $this->addSql('
+ ALTER TABLE claro_flashcard_deck
+ DROP showLeitnerRules
+ ');
+ }
+}
diff --git a/src/plugin/flashcard/Manager/EvaluationManager.php b/src/plugin/flashcard/Manager/EvaluationManager.php
index cfd8afd1799..a9c43014fc0 100644
--- a/src/plugin/flashcard/Manager/EvaluationManager.php
+++ b/src/plugin/flashcard/Manager/EvaluationManager.php
@@ -82,7 +82,6 @@ public function update(ResourceEvaluation $attempt, $attemptCards): ResourceEval
}
}
- // Pas de cartes restantes pour cette session, on passe a la session suivante
if (0 === count($cardsArray)) {
++$session;
}
diff --git a/src/plugin/flashcard/Manager/FlashcardManager.php b/src/plugin/flashcard/Manager/FlashcardManager.php
index 9a51ba6a44a..591ff67985b 100644
--- a/src/plugin/flashcard/Manager/FlashcardManager.php
+++ b/src/plugin/flashcard/Manager/FlashcardManager.php
@@ -101,7 +101,6 @@ public function calculateSession(?ResourceEvaluation $attempt, FlashcardDeck $de
$cardsSessionIds = $attempt ? $attempt->getData()['cardsSessionIds'] ?? [] : [];
$cardsAnsweredIds = $attempt ? $attempt->getData()['cardsAnsweredIds'] ?? [] : [];
- // Récup des entités cartes de la tentative
$cards = array_map(function ($card) {
return $this->om->getRepository(CardDrawnProgression::class)->findOneBy([
'id' => $card['id'],
@@ -113,7 +112,6 @@ public function calculateSession(?ResourceEvaluation $attempt, FlashcardDeck $de
]);
}, $cardsAnsweredIds);
- // Création d'une nouvelle tentative si nécessaire
if (!$attempt) {
$attempt = $this->resourceEvalManager->createAttempt($node, $user, [
'status' => AbstractEvaluation::STATUS_OPENED,
@@ -127,7 +125,10 @@ public function calculateSession(?ResourceEvaluation $attempt, FlashcardDeck $de
]);
}
- // Récupération des cartes de la tentative
+ if (0 === count($deck->getCards())) {
+ return $attempt;
+ }
+
$attemptCards = self::shuffleCardByAttempt($deck->getCards(), $attempt, $deck->getDraw());
foreach ($attemptCards as $card) {
$progression = $this->om->getRepository(CardDrawnProgression::class)->findOneBy([
@@ -149,7 +150,6 @@ public function calculateSession(?ResourceEvaluation $attempt, FlashcardDeck $de
}
}
- // Filtrage des cartes pour la session
$cardsSession = [];
foreach ($cards as $cardProgression) {
if ($this->keepCardInSession($session, $cardProgression)) {
@@ -157,7 +157,6 @@ public function calculateSession(?ResourceEvaluation $attempt, FlashcardDeck $de
}
}
- // Ajout des cartes de la session 1
if (0 === count($cardsSession) && 1 === $session) {
$attempt = $this->resourceEvalManager->updateAttempt($attempt, [
'status' => AbstractEvaluation::STATUS_OPENED,
@@ -177,7 +176,6 @@ public function calculateSession(?ResourceEvaluation $attempt, FlashcardDeck $de
]);
}
- // Suppression des cartes répondues de la liste des cartes de la session
foreach ($cardsAnswered as $cardProgression) {
foreach ($cardsSession as $key => $cardSession) {
if ($cardProgression->getId() === $cardSession->getId()) {
@@ -187,14 +185,10 @@ public function calculateSession(?ResourceEvaluation $attempt, FlashcardDeck $de
}
}
- // Incrément de la session et réinitialisation des cartes si nécessaire
while (0 === count($cardsSession) && $session < 7) {
++$session;
-
$cardsSession = [];
$cardsAnswered = [];
-
- // Filtrage des cartes pour la nouvelle session
foreach ($cards as $cardProgression) {
if ($this->keepCardInSession($session, $cardProgression)) {
$cardsSession[] = $cardProgression;
@@ -202,7 +196,6 @@ public function calculateSession(?ResourceEvaluation $attempt, FlashcardDeck $de
}
}
- // Calcul de la progression
$successfulCards = 0;
$totalCards = count($cardsSession) + count($cardsAnswered);
foreach ($cardsAnswered as $cardProgression) {
@@ -211,11 +204,9 @@ public function calculateSession(?ResourceEvaluation $attempt, FlashcardDeck $de
if (0 === $totalCards) {
$progression = 0;
} else {
- // Transformation du résultat en integer car round() retourne un float
$progression = (int) min(round($successfulCards / $totalCards * 100), 100);
}
- // Vérification du status
if (7 === $session && 0 === count($cardsSession)) {
if (100 === $progression) {
$status = AbstractEvaluation::STATUS_COMPLETED;
@@ -226,7 +217,6 @@ public function calculateSession(?ResourceEvaluation $attempt, FlashcardDeck $de
$status = AbstractEvaluation::STATUS_INCOMPLETE;
}
- // Mise à jour de la tentative
$attempt = $this->resourceEvalManager->updateAttempt($attempt, [
'status' => $status,
'progression' => $progression,
@@ -244,9 +234,7 @@ public function calculateSession(?ResourceEvaluation $attempt, FlashcardDeck $de
],
]);
- // Si on n'a pas de page de début ni de fin
if (!$deck->getShowOverview() && !$deck->getShowEndPage()) {
- // On recrée une nouvelle tentative directement une fois la précédente terminée pour enchainer
if (AbstractEvaluation::STATUS_COMPLETED === $status || AbstractEvaluation::STATUS_FAILED === $status) {
$attempt = $this->calculateSession(null, $deck, $user);
}
@@ -261,7 +249,6 @@ public function answerSessionCard(ResourceEvaluation $attempt, CardDrawnProgress
$cardsSessionIds = $attempt->getData()['cardsSessionIds'] ?? [];
$cardsAnsweredIds = $attempt->getData()['cardsAnsweredIds'] ?? [];
- // Deserialization des cartes de la tentative
$cards = array_map(function ($card) {
return $this->om->getRepository(CardDrawnProgression::class)->findOneBy([
'id' => $card['id'],
@@ -272,7 +259,6 @@ public function answerSessionCard(ResourceEvaluation $attempt, CardDrawnProgress
$cardsAnsweredIds[] = $cardProgression->getId();
}
- // Mise à jour de la tentative
return $this->resourceEvalManager->updateAttempt($attempt, [
'status' => $attempt->getStatus(),
'progression' => $attempt->getProgression(),
diff --git a/src/plugin/flashcard/Resources/modules/resources/flashcard/components/card.jsx b/src/plugin/flashcard/Resources/modules/resources/flashcard/components/card.jsx
index 32fa30225ee..e79fd47cd63 100644
--- a/src/plugin/flashcard/Resources/modules/resources/flashcard/components/card.jsx
+++ b/src/plugin/flashcard/Resources/modules/resources/flashcard/components/card.jsx
@@ -81,7 +81,7 @@ Card.propTypes = {
actions: T.func,
card: T.shape(
CardTypes.propTypes
- ).isRequired
+ )
}
export {
diff --git a/src/plugin/flashcard/Resources/modules/resources/flashcard/components/info.jsx b/src/plugin/flashcard/Resources/modules/resources/flashcard/components/info.jsx
deleted file mode 100644
index 9f5956fd961..00000000000
--- a/src/plugin/flashcard/Resources/modules/resources/flashcard/components/info.jsx
+++ /dev/null
@@ -1,40 +0,0 @@
-import React from 'react'
-
-import {trans} from '#/main/app/intl/translation'
-import {ContentCounter} from '#/main/app/content/components/counter'
-import {schemeCategory20c} from '#/main/theme/color/utils'
-
-const FlashcardInfo = (props) => {
- const getLength = (items) => items ? items.length : 0
- const countSuccess = (items) => items ? items.filter((item) => item && item.successCount > 0).length : 0
- const countFail = (items) => items ? items.filter((item) => item && item.successCount === 0).length : 0
-
- return (
-
- )
-}
-
-export {
- FlashcardInfo
-}
diff --git a/src/plugin/flashcard/Resources/modules/resources/flashcard/components/menu.jsx b/src/plugin/flashcard/Resources/modules/resources/flashcard/components/menu.jsx
index 2891f871e02..5ba998d8397 100644
--- a/src/plugin/flashcard/Resources/modules/resources/flashcard/components/menu.jsx
+++ b/src/plugin/flashcard/Resources/modules/resources/flashcard/components/menu.jsx
@@ -11,7 +11,6 @@ const FlashcardMenu = props =>
/>
FlashcardMenu.propTypes = {
- // from menu
opened: T.bool.isRequired,
toggle: T.func.isRequired,
autoClose: T.func.isRequired
diff --git a/src/plugin/flashcard/Resources/modules/resources/flashcard/components/overview.jsx b/src/plugin/flashcard/Resources/modules/resources/flashcard/components/overview.jsx
index dfcae753554..8e5a4ebf07d 100644
--- a/src/plugin/flashcard/Resources/modules/resources/flashcard/components/overview.jsx
+++ b/src/plugin/flashcard/Resources/modules/resources/flashcard/components/overview.jsx
@@ -6,7 +6,9 @@ import {trans} from '#/main/app/intl/translation'
import {LINK_BUTTON} from '#/main/app/buttons'
import {ResourceOverview} from '#/main/core/resource/components/overview'
import {ResourceEvaluation as ResourceEvaluationTypes} from '#/main/evaluation/resource/prop-types'
-import {FlashcardInfo} from '#/plugin/flashcard/resources/flashcard/components/info'
+
+import {Timeline} from '#/plugin/flashcard/resources/flashcard/components/timeline'
+import {LeitnerRules} from '#/plugin/flashcard/resources/flashcard/components/rules'
import {FlashcardDeck as FlashcardDeckTypes} from '#/plugin/flashcard/resources/flashcard/prop-types'
const Overview = (props) => {
@@ -57,9 +59,19 @@ const Overview = (props) => {
[trans('session_status', {}, 'flashcard'), statusText]
]}
>
-
+
+ {props.flashcardDeck.showLeitnerRules &&
+
+ }
)
}
diff --git a/src/plugin/flashcard/Resources/modules/resources/flashcard/components/rules.jsx b/src/plugin/flashcard/Resources/modules/resources/flashcard/components/rules.jsx
new file mode 100644
index 00000000000..ae5ed5980c1
--- /dev/null
+++ b/src/plugin/flashcard/Resources/modules/resources/flashcard/components/rules.jsx
@@ -0,0 +1,57 @@
+import React from 'react'
+import classes from 'classnames'
+import {PropTypes as T} from 'prop-types'
+
+import {trans} from '#/main/app/intl'
+import {getIcon, getRule} from '#/plugin/flashcard/resources/flashcard/utils'
+
+const SessionRule= props => {
+ const icon = getIcon(props.index)
+ const rule = getRule(props.index)
+
+ let classList = ['icon-with-text-right']
+ if (props.index < props.session || (props.index === props.session && props.completed)) {
+ classList.push('session-rule-done')
+ }
+
+ return (
+
+
+ {icon}
+
+
+ Session {props.index} : {trans('session_cards_start', {}, 'flashcard') + ' ' + rule}.
+
+
+ )
+}
+
+SessionRule.propTypes = {
+ session: T.number,
+ rule: T.number,
+ completed: T.bool,
+ index: T.number
+}
+
+const LeitnerRules = (props) => (
+
+ {Array.from({length: 7}, (_, index) => (
+
+ ))}
+
+)
+
+LeitnerRules.propTypes = {
+ session: T.number,
+ completed: T.bool
+}
+
+export {
+ LeitnerRules
+}
diff --git a/src/plugin/flashcard/Resources/modules/resources/flashcard/components/timeline.jsx b/src/plugin/flashcard/Resources/modules/resources/flashcard/components/timeline.jsx
new file mode 100644
index 00000000000..961b66c7493
--- /dev/null
+++ b/src/plugin/flashcard/Resources/modules/resources/flashcard/components/timeline.jsx
@@ -0,0 +1,70 @@
+import React from 'react'
+import classes from 'classnames'
+import {PropTypes as T} from 'prop-types'
+
+import {ProgressBar} from '#/main/app/content/components/progress-bar'
+import {TooltipOverlay} from '#/main/app/overlays/tooltip/components/overlay'
+import {getRule, getLabel, getClassList, getProgression} from '#/plugin/flashcard/resources/flashcard/utils'
+
+const SessionStep = props => {
+ const rule = getRule(props.index)
+ const label = getLabel(props.index, props.session, props.started, props.completed)
+ const classList = getClassList(props.index, props.session, props.started, props.completed)
+
+ return (
+
+
+
+ {label}
+
+
+
+ )
+}
+
+SessionStep.propTypes = {
+ session: T.number,
+ index: T.number,
+ started: T.bool,
+ completed: T.bool
+}
+
+const Timeline = (props) => {
+ const progression = getProgression(props.session, props.started, props.completed, props.end)
+
+ return (
+
+
+
+ {Array.from({ length: 7 }, (_, index) => (
+
+ ))}
+
+
+ )
+}
+
+Timeline.propTypes = {
+ session: T.number,
+ started: T.bool,
+ completed: T.bool,
+ end: T.bool
+}
+
+export {
+ Timeline
+}
diff --git a/src/plugin/flashcard/Resources/modules/resources/flashcard/containers/overview.jsx b/src/plugin/flashcard/Resources/modules/resources/flashcard/containers/overview.jsx
index ec765efc0ed..e1695e17c86 100644
--- a/src/plugin/flashcard/Resources/modules/resources/flashcard/containers/overview.jsx
+++ b/src/plugin/flashcard/Resources/modules/resources/flashcard/containers/overview.jsx
@@ -2,8 +2,8 @@ import {connect} from 'react-redux'
import {selectors as resourceSelectors} from '#/main/core/resource/store/selectors'
-import {Overview as OverviewComponent} from '#/plugin/flashcard/resources/flashcard/components/overview'
import {selectors} from '#/plugin/flashcard/resources/flashcard/store'
+import {Overview as OverviewComponent} from '#/plugin/flashcard/resources/flashcard/components/overview'
const Overview = connect(
(state) => ({
diff --git a/src/plugin/flashcard/Resources/modules/resources/flashcard/containers/resource.jsx b/src/plugin/flashcard/Resources/modules/resources/flashcard/containers/resource.jsx
index 1192eddfa6f..a75018f6107 100644
--- a/src/plugin/flashcard/Resources/modules/resources/flashcard/containers/resource.jsx
+++ b/src/plugin/flashcard/Resources/modules/resources/flashcard/containers/resource.jsx
@@ -1,12 +1,11 @@
import {connect} from 'react-redux'
import {withRouter} from '#/main/app/router'
-
-import {selectors as resourceSelectors} from '#/main/core/resource/store'
import {hasPermission} from '#/main/app/security/permissions'
+import {selectors as resourceSelectors} from '#/main/core/resource/store'
-import {FlashcardResource as FlashcardResourceComponent} from '#/plugin/flashcard/resources/flashcard/components/resource'
import {actions, selectors} from '#/plugin/flashcard/resources/flashcard/store'
+import {FlashcardResource as FlashcardResourceComponent} from '#/plugin/flashcard/resources/flashcard/components/resource'
const FlashcardResource = withRouter(
connect(
diff --git a/src/plugin/flashcard/Resources/modules/resources/flashcard/editor/components/editor.jsx b/src/plugin/flashcard/Resources/modules/resources/flashcard/editor/components/editor.jsx
index dba7afc8726..b4c590989b8 100644
--- a/src/plugin/flashcard/Resources/modules/resources/flashcard/editor/components/editor.jsx
+++ b/src/plugin/flashcard/Resources/modules/resources/flashcard/editor/components/editor.jsx
@@ -81,6 +81,10 @@ const EditorComponent = props =>
displayed: (flashcardDeck) => get(flashcardDeck, 'customButtons')
}
]
+ }, {
+ name: 'showLeitnerRules',
+ type: 'boolean',
+ label: trans('show_leitner_rules', {}, 'flashcard')
}
]
},{
diff --git a/src/plugin/flashcard/Resources/modules/resources/flashcard/index.js b/src/plugin/flashcard/Resources/modules/resources/flashcard/index.js
index fc0e9c95ccd..7b6e6a77514 100644
--- a/src/plugin/flashcard/Resources/modules/resources/flashcard/index.js
+++ b/src/plugin/flashcard/Resources/modules/resources/flashcard/index.js
@@ -1,6 +1,6 @@
-import {FlashcardResource} from '#/plugin/flashcard/resources/flashcard/containers/resource'
import {reducer} from '#/plugin/flashcard/resources/flashcard/store'
import {FlashcardMenu} from '#/plugin/flashcard/resources/flashcard/components/menu'
+import {FlashcardResource} from '#/plugin/flashcard/resources/flashcard/containers/resource'
/**
* Flashcard resource application.
diff --git a/src/plugin/flashcard/Resources/modules/resources/flashcard/player/components/end.jsx b/src/plugin/flashcard/Resources/modules/resources/flashcard/player/components/end.jsx
index 463e925e2f9..69e1b251710 100644
--- a/src/plugin/flashcard/Resources/modules/resources/flashcard/player/components/end.jsx
+++ b/src/plugin/flashcard/Resources/modules/resources/flashcard/player/components/end.jsx
@@ -1,20 +1,21 @@
import React from 'react'
+import get from 'lodash/get'
import {connect} from 'react-redux'
import {PropTypes as T} from 'prop-types'
-import get from 'lodash/get'
import {trans} from '#/main/app/intl'
import {LINK_BUTTON} from '#/main/app/buttons'
import {ResourceEnd} from '#/main/core/resource/components/end'
import {selectors} from '#/plugin/flashcard/resources/flashcard/store'
-import {FlashcardDeck as FlashcardDeckTypes} from '#/plugin/flashcard/resources/flashcard/prop-types'
-import {FlashcardInfo} from '#/plugin/flashcard/resources/flashcard/components/info'
+import {Timeline} from '#/plugin/flashcard/resources/flashcard/components/timeline'
import {ResourceEvaluation as ResourceEvaluationTypes} from '#/main/evaluation/resource/prop-types'
+import {FlashcardDeck as FlashcardDeckTypes} from '#/plugin/flashcard/resources/flashcard/prop-types'
const PlayerEndComponent = (props) => {
const attemptData = props.attempt && props.attempt.data
let session = attemptData ? attemptData.session : 1
+
let action = action = {
type: LINK_BUTTON,
label: trans('start', {}, 'actions'),
@@ -39,8 +40,10 @@ const PlayerEndComponent = (props) => {
[trans('session_status', {}, 'flashcard'), trans('session_status_completed', {}, 'flashcard')]
]}
>
-
)
diff --git a/src/plugin/flashcard/Resources/modules/resources/flashcard/player/components/player.jsx b/src/plugin/flashcard/Resources/modules/resources/flashcard/player/components/player.jsx
index 0b51c357d65..f42341b0982 100644
--- a/src/plugin/flashcard/Resources/modules/resources/flashcard/player/components/player.jsx
+++ b/src/plugin/flashcard/Resources/modules/resources/flashcard/player/components/player.jsx
@@ -2,11 +2,11 @@ import React, { useState, useCallback } from 'react'
import { useHistory, useRouteMatch } from 'react-router-dom'
import { PropTypes as T } from 'prop-types'
-import { trans } from '#/main/app/intl/translation'
import {Toolbar} from '#/main/app/action'
import {CALLBACK_BUTTON} from '#/main/app/buttons'
-import {ContentPlaceholder} from '#/main/app/content/components/placeholder'
+import { trans } from '#/main/app/intl/translation'
import {ProgressBar} from '#/main/app/content/components/progress-bar'
+import {ContentPlaceholder} from '#/main/app/content/components/placeholder'
import {Card} from '#/plugin/flashcard/resources/flashcard/components/card'
import {FlashcardDeck as FlashcardDeckTypes} from '#/plugin/flashcard/resources/flashcard/prop-types'
@@ -17,7 +17,7 @@ const Player = props => {
const [isFlipped, setIsFlipped] = useState(false)
const [isAnswering, setIsAnswering] = useState(false)
const currentCardIndex = props.attempt.data.cardsAnsweredIds.length
- const maxCards = props.draw > 0 ? Math.min(props.draw, props.attempt.data.cards.length) : props.attempt.data.cards.length
+ const maxCards = props.attempt.data.cardsSessionIds.length + props.attempt.data.cardsAnsweredIds.length
const currentCardId = props.attempt.data.cardsSessionIds[0]
const currentCardProgression = props.attempt.data.cards.find(card => card.id === currentCardId)
const currentCard = currentCardProgression ? currentCardProgression.flashcard : null
diff --git a/src/plugin/flashcard/Resources/modules/resources/flashcard/player/containers/player.jsx b/src/plugin/flashcard/Resources/modules/resources/flashcard/player/containers/player.jsx
index f3fcece1757..1ad0b185f48 100644
--- a/src/plugin/flashcard/Resources/modules/resources/flashcard/player/containers/player.jsx
+++ b/src/plugin/flashcard/Resources/modules/resources/flashcard/player/containers/player.jsx
@@ -1,7 +1,7 @@
import {connect} from 'react-redux'
-import {Player as PlayerComponent} from '#/plugin/flashcard/resources/flashcard/player/components/player'
import {selectors, actions} from '#/plugin/flashcard/resources/flashcard/store'
+import {Player as PlayerComponent} from '#/plugin/flashcard/resources/flashcard/player/components/player'
const Player = connect(
(state) => ({
diff --git a/src/plugin/flashcard/Resources/modules/resources/flashcard/prop-types.js b/src/plugin/flashcard/Resources/modules/resources/flashcard/prop-types.js
index c56e0862f4e..f04523561b6 100644
--- a/src/plugin/flashcard/Resources/modules/resources/flashcard/prop-types.js
+++ b/src/plugin/flashcard/Resources/modules/resources/flashcard/prop-types.js
@@ -23,6 +23,10 @@ const FlashcardDeck = {
id: T.string,
name: T.string,
showProgression: T.bool,
+ showLeitnerRules: T.bool,
+ customButtons: T.bool,
+ rightButtonLabel: T.string,
+ wrongButtonLabel: T.string,
draw: T.number,
overview: T.shape({
display: T.bool,
diff --git a/src/plugin/flashcard/Resources/modules/resources/flashcard/store/actions.js b/src/plugin/flashcard/Resources/modules/resources/flashcard/store/actions.js
index 648cfd9d5ac..8a407e387c2 100644
--- a/src/plugin/flashcard/Resources/modules/resources/flashcard/store/actions.js
+++ b/src/plugin/flashcard/Resources/modules/resources/flashcard/store/actions.js
@@ -1,5 +1,5 @@
-import {makeActionCreator} from '#/main/app/store/actions'
import { API_REQUEST } from '#/main/app/api'
+import {makeActionCreator} from '#/main/app/store/actions'
import {actions as resourceActions} from '#/main/core/resource/store'
@@ -13,6 +13,7 @@ actions.updateCardProgression = makeActionCreator(FLASHCARD_UPDATE_PROGRESSION,
actions.updateProgression = (cardId, isSuccessful) => ({
[API_REQUEST]: {
+ silent: true,
url: ['apiv2_flashcard_progression_update', {id: cardId, isSuccessful: isSuccessful}],
request: {
method: 'PUT'
diff --git a/src/plugin/flashcard/Resources/modules/resources/flashcard/store/index.js b/src/plugin/flashcard/Resources/modules/resources/flashcard/store/index.js
index fe7bc0ecda0..e49b5a04283 100644
--- a/src/plugin/flashcard/Resources/modules/resources/flashcard/store/index.js
+++ b/src/plugin/flashcard/Resources/modules/resources/flashcard/store/index.js
@@ -1,9 +1,9 @@
import {reducer} from '#/plugin/flashcard/resources/flashcard/store/reducer'
-import {selectors} from '#/plugin/flashcard/resources/flashcard/store/selectors'
import {actions} from '#/plugin/flashcard/resources/flashcard/store/actions'
+import {selectors} from '#/plugin/flashcard/resources/flashcard/store/selectors'
export {
reducer,
- selectors,
- actions
+ actions,
+ selectors
}
diff --git a/src/plugin/flashcard/Resources/modules/resources/flashcard/store/reducer.js b/src/plugin/flashcard/Resources/modules/resources/flashcard/store/reducer.js
index cf3a50e503c..f8f3b7ae44e 100644
--- a/src/plugin/flashcard/Resources/modules/resources/flashcard/store/reducer.js
+++ b/src/plugin/flashcard/Resources/modules/resources/flashcard/store/reducer.js
@@ -3,8 +3,8 @@ import cloneDeep from 'lodash/cloneDeep'
import {makeInstanceAction} from '#/main/app/store/actions'
import {combineReducers, makeReducer} from '#/main/app/store/reducer'
-import {FORM_SUBMIT_SUCCESS} from '#/main/app/content/form/store/actions'
import {RESOURCE_LOAD} from '#/main/core/resource/store/actions'
+import {FORM_SUBMIT_SUCCESS} from '#/main/app/content/form/store/actions'
import {ATTEMPT_LOAD} from '#/plugin/flashcard/resources/flashcard/store/actions'
import {selectors as editorSelectors, reducer as editorReducer} from '#/plugin/flashcard/resources/flashcard/editor/store'
diff --git a/src/plugin/flashcard/Resources/modules/resources/flashcard/store/selectors.js b/src/plugin/flashcard/Resources/modules/resources/flashcard/store/selectors.js
index 6178c5ea38a..6cf9fc62fbb 100644
--- a/src/plugin/flashcard/Resources/modules/resources/flashcard/store/selectors.js
+++ b/src/plugin/flashcard/Resources/modules/resources/flashcard/store/selectors.js
@@ -1,5 +1,5 @@
-import {createSelector} from 'reselect'
import get from 'lodash/get'
+import {createSelector} from 'reselect'
const STORE_NAME = 'flashcard'
diff --git a/src/plugin/flashcard/Resources/modules/resources/flashcard/utils.js b/src/plugin/flashcard/Resources/modules/resources/flashcard/utils.js
new file mode 100644
index 00000000000..80518bd17e4
--- /dev/null
+++ b/src/plugin/flashcard/Resources/modules/resources/flashcard/utils.js
@@ -0,0 +1,51 @@
+import React from 'react'
+import { trans } from '#/main/app/intl'
+
+export const getIcon = (index) => {
+ if (index > 1 && index < 7) {
+ return
+ } else if (index === 7) {
+ return
+ }
+ return
+}
+
+export const getRule = (index) => {
+ switch (index) {
+ case 1: return trans('session_cards_all', {}, 'flashcard')
+ case 2:
+ case 4: return trans('session_cards_1x', {}, 'flashcard') + ' + ' + trans('session_cards_failed', {}, 'flashcard')
+ case 3: return trans('session_cards_2x', {}, 'flashcard') + ' + ' + trans('session_cards_failed', {}, 'flashcard')
+ case 5: return trans('session_cards_failed', {}, 'flashcard')
+ case 6: return trans('session_cards_1x', {}, 'flashcard') + ' + ' + trans('session_cards_2x', {}, 'flashcard') + ' + ' + trans('session_cards_failed', {}, 'flashcard')
+ case 7: return trans('session_cards_3x', {}, 'flashcard') + ' + ' + trans('session_cards_failed', {}, 'flashcard')
+ default: return index
+ }
+}
+
+export const getClassList = (index, session, started, completed) => {
+ return [
+ 'flashcard-timeline-step',
+ index < session ? 'flashcard-timeline-step-done' : '',
+ session === index && completed ? 'flashcard-timeline-step-done' : '',
+ session === index && started && !completed ? 'flashcard-timeline-step-current' : ''
+ ]
+}
+
+export const getLabel = (index, session, started, completed) => {
+ if (index < session || (session === index && completed)) {
+ return
+ } else if (session === index && started) {
+ return
+ }
+ return index
+}
+
+export const getProgression = (session, started, completed, end) => {
+ if (end) {
+ return (session - 2) * (100 / 6)
+ } else if (started || completed) {
+ return (session - 1) * (100 / 6)
+ }
+ return (session - 1.5) * (100 / 6)
+}
diff --git a/src/plugin/flashcard/Resources/styles/_variables.scss b/src/plugin/flashcard/Resources/styles/_variables.scss
index 30406af1824..613e47e0586 100644
--- a/src/plugin/flashcard/Resources/styles/_variables.scss
+++ b/src/plugin/flashcard/Resources/styles/_variables.scss
@@ -17,3 +17,8 @@ $flashcard-box-shadow: $box-shadow-sm !default;
$flashcard-height: 300px !default;
$flashcard-color: $card-color !default;
$flashcard-bg: $card-bg !default;
+
+$flashcard-timeline-icon-size: $font-size-lg*3;
+$flashcard-timeline-lines-width: 8px !default;
+$flashcard-timeline-lines-color: $gray-lighter !default;
+$flashcard-timeline-endpoint-size: $font-size-lg*2 !default;
diff --git a/src/plugin/flashcard/Resources/styles/resources/flashcard.scss b/src/plugin/flashcard/Resources/styles/resources/flashcard.scss
index 7ce4a86a615..da5ffcdae7c 100644
--- a/src/plugin/flashcard/Resources/styles/resources/flashcard.scss
+++ b/src/plugin/flashcard/Resources/styles/resources/flashcard.scss
@@ -12,7 +12,7 @@
justify-content: center;
list-style: none;
padding: 0;
- margin: -(map-get($spacers, 1)); // absorb items padding
+ margin: -(map-get($spacers, 1));
> li {
padding: map-get($spacers, 1);
@@ -21,10 +21,6 @@
}
.flashcard-hoverable {
- //width: calc(50% - 30px);
-
- //padding: 5px;
- //position: relative;
transition: all linear 185ms;
&:hover {
@@ -34,22 +30,16 @@
}
}
-/*.flashcards-info {
- display: flex;
-}*/
-
.flashcard-actions {
position: absolute;
top: 0;
right: 0;
- //margin: 2px;
}
.flashcard-card {
display: flex;
flex-direction: column;
height: $flashcard-height;
- //height: 100%;
width: 100%;
position: relative;
@@ -68,14 +58,6 @@
align-items: center
}
-.flashcard-player {
- /*display: flex;
- align-items: center;
- justify-content: center;
-
- position: relative;*/
-}
-
.flashcard-counter {
display: flex;
justify-content: space-between;
@@ -84,28 +66,13 @@
.flashcard-deck {
position: relative;
- //max-width: 500px;
- //width: 80vw;
height: 300px;
perspective: 1000px;
- //background-color: transparent;
-}
-
-.flashcard-buttons {
- /*display: flex;
- justify-content: center;
- margin: auto;*/
-
- /*& > button {
- margin-left: 20px;
- }*/
}
.flashcard-media {
max-width: 100%;
max-height: 100%;
-
- //@include border-radius($flashcard-inner-border-radius);
}
.flashcard-video {
@@ -116,45 +83,25 @@
.flashcard-question {
color: $flashcard-title-color;
margin-bottom: $flashcard-title-spacer-y;
- //font-weight: bold;
text-align: center;
}
.flashcard-content {
overflow-y: hidden;
- //height: 100%;
flex: 1;
display: flex;
flex-direction: column;
align-items: center;
-
- /*audio, video {
- display: block;
- margin: auto;
- }*/
}
.flashcard {
position: absolute;
- //max-width: 500px;
- //width: 80vw;
width: 100%;
height: 100%;
- //margin: 10px;
transition: transform 500ms;
transform-style: preserve-3d;
- /*display: flex;
- flex-wrap: nowrap;
- align-items: center;
- justify-content: center;
- flex-direction: column;*/
overflow: hidden;
- /*.flashcard-video {
- width: 100%;
- border-radius: $flashcard-inner-border-radius;
- }*/
-
&-preview
{
position: static;
@@ -176,3 +123,131 @@
transform: rotateY(180deg) scaleX(-1);
}
}
+
+.flashcard-timeline {
+ margin: 40px 33px;
+
+ @media (max-width: 768px) {
+ position: relative;
+ height: 350px;
+
+ .progress {
+ position: absolute;
+ left: 50%; right: 50%;
+ transform: rotate(90deg);
+ transform-origin: left;
+ width: 350px;
+ margin: auto;
+ }
+ }
+}
+
+.flashcard-timeline-steps {
+ margin-left: -10px;
+ margin-right: -10px;
+ margin-top: -5px;
+ display: flex;
+ justify-content: space-between;
+ list-style: none;
+ padding: 0;
+
+ @media (max-width: 768px) {
+ height: 102%;
+ flex-direction: column;
+ flex-wrap: wrap;
+ }
+}
+
+.flashcard-timeline-step {
+ position: relative;
+}
+
+.flashcard-timeline-step::before {
+ content: '';
+ width: 50px;
+ height: 50px;
+ background-color: $flashcard-timeline-lines-color;
+ display: block;
+ border-radius: 100%;
+ position: absolute;
+ top: 50%;
+ left: 50%;
+ transform: translate(-50%, -50%);
+}
+
+.flashcard-timeline-step-done {
+ color: white;
+}
+
+.flashcard-timeline-step-done::before {
+ background-color: $learning;
+}
+
+.flashcard-timeline-step-current::before {
+ background-color: $primary;
+}
+
+.flashcard-timeline-heading {
+ position: absolute;
+ top: 50%;
+ left: 50%;
+ transform: translate(-50%, -50%);
+ font-size: 1.5rem;
+ font-weight: bold;
+}
+
+.flashcard-timeline-status {
+ position: absolute;
+ top: 40px;
+ left: 50%;
+ transform: translateX(-50%);
+ background-color: #ddd;
+ width: 200px;
+ padding: 5px 10px;
+ border-radius: 5px;
+ text-align: center;
+}
+
+.flashcard-timeline-status::before {
+ content: '';
+ width: 10px;
+ height: 10px;
+ background-color: #ddd;
+ display: block;
+ position: absolute;
+ top: -8px;
+ margin-left: -2px;
+ left: 50%;
+ transform: rotate(-45deg) translateX(-50%);
+}
+
+.sessions-rules {
+ list-style-type: none;
+ padding: 0;
+ margin-bottom: $component-margin-bottom;
+}
+
+.session-rule {
+ display: flex;
+ background: #e9ecef;
+ padding: 10px $panel-body-padding;
+ border: 1px solid transparent;
+ border-radius: $border-radius-sm;
+
+ + .session-rule {
+ margin-top: 5px;
+ }
+
+ .session-rule-done {
+ color: $learning;
+ }
+
+ div {
+ align-self: center;
+ }
+}
+
+@media (max-width: 768px) {
+ .flashcard-timeline {
+ }
+}
diff --git a/src/plugin/flashcard/Resources/translations/flashcard.en.json b/src/plugin/flashcard/Resources/translations/flashcard.en.json
index 386d09a009a..bf203cd443b 100644
--- a/src/plugin/flashcard/Resources/translations/flashcard.en.json
+++ b/src/plugin/flashcard/Resources/translations/flashcard.en.json
@@ -6,6 +6,7 @@
"custom_button_labels": "Custom button labels",
"right_button_label": "Right button label",
"wrong_button_label": "Wrong button label",
+ "show_leitner_rules": "Show session rules",
"flashcard_options": "Cards options",
"draw_options": "Number of cards to draw",
"options_desc":"Defines number of cards to draw for each game. Leave empty to play with all the cards.",
@@ -37,8 +38,12 @@
"session_status_completed": "Completed",
"session_status_next": "Next",
- "check": "successful",
- "fail": "failed",
+ "session_cards_start" : "We present",
+ "session_cards_all" : "all cards",
+ "session_cards_1x" : "cards successful once",
+ "session_cards_2x" : "cards successful twice",
+ "session_cards_3x" : "cards successful three times",
+ "session_cards_failed" : "cards failed",
"start_disabled_empty": "Flashcard resource is empty."
}
diff --git a/src/plugin/flashcard/Resources/translations/flashcard.fr.json b/src/plugin/flashcard/Resources/translations/flashcard.fr.json
index 77a2ccd7c37..258253089ab 100644
--- a/src/plugin/flashcard/Resources/translations/flashcard.fr.json
+++ b/src/plugin/flashcard/Resources/translations/flashcard.fr.json
@@ -6,6 +6,7 @@
"custom_button_labels": "Personnaliser le label des boutons de réponse",
"right_button_label": "Label du bouton de réponse correct",
"wrong_button_label": "Label du bouton de réponse incorrect",
+ "show_leitner_rules": "Afficher les règles des sessions",
"flashcard_options": "Options des cartes",
"draw_options": "Nombre de cartes à tirer",
"options_desc": "Permet de définir le nombre de cartes à tirer pour chaque partie. Laissez vide pour jouer avec toutes les cartes.",
@@ -37,8 +38,12 @@
"session_status_completed": "Terminée",
"session_status_next": "À venir",
- "check": "réussies",
- "fail": "échouées",
+ "session_cards_start": "On présente",
+ "session_cards_all" : "toutes les cartes",
+ "session_cards_1x" : "les cartes réussies 1 fois",
+ "session_cards_2x" : "les cartes réussies 2 fois",
+ "session_cards_3x" : "les cartes réussies 3 fois",
+ "session_cards_failed" : "les cartes échouées",
"start_disabled_empty": "La resource cartes-mémoire est vide."
}
diff --git a/src/plugin/flashcard/Serializer/FlashcardDeckSerializer.php b/src/plugin/flashcard/Serializer/FlashcardDeckSerializer.php
index 1c60b4f52a4..5bb7950ca71 100644
--- a/src/plugin/flashcard/Serializer/FlashcardDeckSerializer.php
+++ b/src/plugin/flashcard/Serializer/FlashcardDeckSerializer.php
@@ -43,6 +43,7 @@ public function serialize(FlashcardDeck $flashcardDeck): array
'rightButtonLabel' => $flashcardDeck->getRightButtonLabel(),
'wrongButtonLabel' => $flashcardDeck->getWrongButtonLabel(),
'draw' => $flashcardDeck->getDraw(),
+ 'showLeitnerRules' => $flashcardDeck->getShowLeitnerRules(),
'overview' => [
'display' => $flashcardDeck->getShowOverview(),
'message' => $flashcardDeck->getOverviewMessage(),
@@ -89,6 +90,7 @@ public function deserialize(array $data, FlashcardDeck $flashcardDeck): Flashcar
$this->sipe('rightButtonLabel', 'setRightButtonLabel', $data, $flashcardDeck);
$this->sipe('wrongButtonLabel', 'setWrongButtonLabel', $data, $flashcardDeck);
$this->sipe('draw', 'setDraw', $data, $flashcardDeck);
+ $this->sipe('showLeitnerRules', 'setShowLeitnerRules', $data, $flashcardDeck);
if (!empty($data['overview'])) {
$this->sipe('overview.display', 'setShowOverview', $data, $flashcardDeck);