slight improvements in activity ui

This commit is contained in:
Hikmet 2024-02-08 23:31:00 +03:00
parent c959753077
commit 1254ff3b80
11 changed files with 363 additions and 406 deletions

View File

@ -5,7 +5,7 @@ $board-item-height: 1.8rem;
.exercise-content {
display: flex;
flex-direction: column;
row-gap: 2.5rem;
row-gap: 3rem;
}
.board-items-board {

View File

@ -1,5 +1,5 @@
import { DndContext } from "@dnd-kit/core";
import { ReactNode, useEffect } from "react";
import { ReactNode } from "react";
import { BoardItemsBoard } from "./draggables_board";
import styles from "./styles.module.scss";
import IDraggable from "@/lib/utils/dnd_setting/draggable";

View File

@ -1,4 +1,4 @@
.exercise-body {
padding-inline: 9%;
padding-block: 4rem;
margin-inline: 9%;
margin-block: 4rem;
}

View File

@ -1,6 +1,5 @@
import { WrapperDndContext } from "@/core/components/dnd/wrapper_dnd_context";
import { Droppable } from "@/core/components/dnd/droppable";
import activityStyles from "../../activity.module.scss";
import { Draggable } from "@/core/components/dnd/draggable";
import useViewModelContext from "../../view_model";
import IFillInBlanksExercise from "@/lib/exercise/fill_in_blanks_exercise/fill_in_blanks_exercise";
@ -19,45 +18,53 @@ export function DragIntoBlanksExercise() {
if (!dndBoard || !replies) return null;
return (
<article>
<div className={activityStyles["exercise-body"]}>
<WrapperDndContext
disabled={isSolved}
board={dndBoard}
startDragging={handleStartDragging}
stopDragging={handleStopDragging}
>
<section aria-label="sorular">
<ol className={`simple composite-question-list`}>
{(activityData.exercise as IFillInBlanksExercise).template.map(
(item, index) => {
return (
<li key={index}>
<section aria-label="soru içeriği">
{item.atoms.map((piece) => {
const { id, type } = piece;
if (type === "text") {
return (
<p
key={id}
dangerouslySetInnerHTML={{
__html: piece.value,
}}
/>
);
} else if (type === "blank") {
const reply = ExerciseServices.getReply(
id,
replies
);
const isCorrect = ExerciseServices.checkReply(
id,
activityData.exercise,
replies
);
<WrapperDndContext
disabled={isSolved}
board={dndBoard}
startDragging={handleStartDragging}
stopDragging={handleStopDragging}
>
<section aria-label="sorular">
<ol className={`simple composite-question-list`}>
{(activityData.exercise as IFillInBlanksExercise).template.map(
(item, index) => {
return (
<li key={index}>
<section aria-label="soru içeriği">
{item.atoms.map((piece) => {
const { id, type } = piece;
if (type === "text") {
return (
<p
key={id}
dangerouslySetInnerHTML={{
__html: piece.value,
}}
/>
);
} else if (type === "blank") {
const reply = ExerciseServices.getReply(id, replies);
const isCorrect = ExerciseServices.checkReply(
id,
activityData.exercise,
replies
);
return (
<Droppable
return (
<Droppable
status={
isSolved
? isCorrect
? "success"
: "error"
: "neutral"
}
disabled={isSolved}
key={id}
blankId={id}
>
{reply?.value && reply?.id && (
<Draggable
status={
isSolved
? isCorrect
@ -66,38 +73,23 @@ export function DragIntoBlanksExercise() {
: "neutral"
}
disabled={isSolved}
key={id}
blankId={id}
>
{reply?.value && reply?.id && (
<Draggable
status={
isSolved
? isCorrect
? "success"
: "error"
: "neutral"
}
disabled={isSolved}
item={{
id: reply.id,
value: reply.value,
}}
/>
)}
</Droppable>
);
}
})}
</section>
</li>
);
}
)}
</ol>
</section>
</WrapperDndContext>
</div>
</article>
item={{
id: reply.id,
value: reply.value,
}}
/>
)}
</Droppable>
);
}
})}
</section>
</li>
);
}
)}
</ol>
</section>
</WrapperDndContext>
);
}

View File

@ -1,3 +1,4 @@
import { ReactNode } from "react";
import useViewModelContext from "../view_model";
import { DragIntoBlanksExercise } from "./drag_into_blanks_exercise";
import { ActivityBody } from "./layout/activity_body";
@ -6,6 +7,7 @@ import { MultipleChoiceExercise } from "./multiple_choice_exercise";
import { PairTextsWithImagesExercise } from "./pair_texts_with_images_exercise";
import { TrueFalseExercise } from "./true_false_exercise";
import { TypeInBlanksExercise } from "./type_in_blanks_exercise";
import activityStyles from "../activity.module.scss";
export function View() {
return (
@ -20,26 +22,29 @@ export function View() {
export function Exercise() {
const { activityData } = useViewModelContext()!;
const { type } = activityData;
let exercise: ReactNode = null;
if (type === "true-false") {
return <TrueFalseExercise />;
exercise = <TrueFalseExercise />;
}
if (type === "drag-into-blanks") {
return <DragIntoBlanksExercise />;
exercise = <DragIntoBlanksExercise />;
}
if (type === "multiple-choice") {
return <MultipleChoiceExercise />;
exercise = <MultipleChoiceExercise />;
}
if (type === "pair-texts-with-images") {
return <PairTextsWithImagesExercise />;
exercise = <PairTextsWithImagesExercise />;
}
if (type === "type-in-blanks") {
return <TypeInBlanksExercise />;
exercise = <TypeInBlanksExercise />;
}
return null;
return (
<article className={activityStyles["exercise-body"]}>{exercise}</article>
);
}

View File

@ -1,4 +1,3 @@
import { ReactNode } from "react";
import YouTube from "react-youtube";
import getYouTubeID from "get-youtube-id";
import styles from "./styles.module.scss";
@ -10,50 +9,48 @@ export function ActivityBody() {
activityData;
return (
<section className={styles["container"]} aria-label="aktivite">
<>
<header className={styles["header"]}>
<h2 className={styles["title"]}>{title}</h2>
</header>
<div className={styles["main"]}>
{youtubeVideoUrl && (
<div className={styles["yt-container-container"]}>
<YouTube
iframeClassName={styles["yt-iframe"]}
className={styles["yt-container"]}
opts={{ width: "100%", height: "100%" }}
videoId={getYouTubeID(youtubeVideoUrl) ?? undefined}
/>
</div>
)}
{explanation && (
<div className={styles["explanation"]}>
<h3 className={styles["text"]}>{explanation}</h3>
<div className={styles["line"]} />
</div>
)}
<div className={styles["content"]}>
{image && (
<img
className={styles["image"]}
alt="Aktivite ek fotoğraf"
src={`${process.env.NEXT_PUBLIC_AWS_CLOUDFRONT_IMAGE_BASE_URL}/${image}`}
/>
)}
{textContent && (
<p
className={styles["text-content"]}
dangerouslySetInnerHTML={{ __html: textContent }}
/>
)}
{audio && (
<audio
className={styles["audio"]}
controls
src={`${process.env.NEXT_PUBLIC_AWS_CLOUDFRONT_AUDIO_BASE_URL}/${audio}`}
/>
)}
{explanation && (
<div className={styles["explanation"]}>
<h3 className={styles["text"]}>{explanation}</h3>
<div className={styles["line"]} />
</div>
)}
{youtubeVideoUrl && (
<div className={styles["yt-container-container"]}>
<YouTube
iframeClassName={styles["yt-iframe"]}
className={styles["yt-container"]}
opts={{ width: "100%", height: "100%" }}
videoId={getYouTubeID(youtubeVideoUrl) ?? undefined}
/>
</div>
)}
<div className={styles["content"]}>
{image && (
<img
className={styles["image"]}
alt="Aktivite ek fotoğraf"
src={`${process.env.NEXT_PUBLIC_AWS_CLOUDFRONT_IMAGE_BASE_URL}/${image}`}
/>
)}
{textContent && (
<p
className={styles["text-content"]}
dangerouslySetInnerHTML={{ __html: textContent }}
/>
)}
{audio && (
<audio
className={styles["audio"]}
controls
src={`${process.env.NEXT_PUBLIC_AWS_CLOUDFRONT_AUDIO_BASE_URL}/${audio}`}
/>
)}
</div>
</section>
</>
);
}

View File

@ -1,7 +1,6 @@
.container {
display: flex;
flex-direction: column;
row-gap: 3rem;
}
.header {
@ -17,63 +16,65 @@
}
}
.main {
div.explanation {
width: fit-content;
font-size: 1.4rem;
padding-inline: 9%;
div.explanation {
margin-block: 3rem;
width: fit-content;
font-size: 1.4rem;
padding-inline: 9%;
div.line {
margin-top: 1rem;
background-color: black;
height: 0.3rem;
width: 35%;
div.line {
margin-top: 1rem;
background-color: black;
height: 0.3rem;
width: 35%;
}
}
.yt-container-container {
display: flex;
justify-content: center;
width: 100%;
margin-block: 3rem;
margin-top: 4rem;
.yt-container {
margin: 0;
height: 20rem;
width: 75%;
.yt-iframe {
display: block;
}
}
}
.yt-container-container {
display: flex;
justify-content: center;
.content:not(:empty) {
display: flex;
flex-direction: column;
row-gap: 2rem;
margin-block: 3rem;
padding-inline: 12.5%;
.image {
width: 100%;
background-color: #ccc;
.yt-container {
margin: 0;
height: 20rem;
width: 75%;
.yt-iframe {
display: block;
}
}
max-height: 18rem;
object-fit: contain;
margin-top: 1rem;
}
.content:not(:empty) {
display: flex;
flex-direction: column;
row-gap: 1.5rem;
margin-top: 2.5rem;
padding-inline: 12.5%;
.audio {
margin-top: 1rem;
width: 100%;
}
.image {
width: 100%;
max-height: 18rem;
object-fit: contain;
}
.audio {
width: 100%;
}
.text-content {
margin: 0;
}
.text-content {
margin: 0;
}
}
.footer {
display: flex;
justify-content: flex-end;
padding-inline: 2rem;
padding-bottom: 2rem;
margin-inline: 2rem;
margin-bottom: 2rem;
}

View File

@ -1,5 +1,4 @@
import IMultipleChoiceExercise from "@/lib/exercise/multiple_choice_exercise/multiple_choice_exercise";
import activityStyles from "../../activity.module.scss";
import useViewModelContext from "../../view_model";
import * as ExerciseServices from "@/lib/exercise/exercise_services";
@ -10,62 +9,52 @@ export function MultipleChoiceExercise() {
if (!replies) return null;
return (
<article>
<div className={activityStyles["exercise-body"]}>
<ol className={`simple multiple-choice`}>
{(activityData.exercise as IMultipleChoiceExercise).template.map(
({ id, questionText, choices }, index) => {
const reply = ExerciseServices.getReply(id, replies);
const isCorrect = ExerciseServices.checkReply(
id,
activityData.exercise,
replies
);
<ol className={`simple multiple-choice`}>
{(activityData.exercise as IMultipleChoiceExercise).template.map(
({ id, questionText, choices }, index) => {
const reply = ExerciseServices.getReply(id, replies);
const isCorrect = ExerciseServices.checkReply(
id,
activityData.exercise,
replies
);
return (
<li
key={id}
className={`${
isSolved ? (isCorrect ? "success" : "error") : ""
}`}
>
<section aria-label="soru">
<p dangerouslySetInnerHTML={{ __html: questionText }} />
<div className={"choices"}>
{choices.map((choice, i) => {
return (
<div key={`${choice}-${i}`} className={"choice"}>
<input
disabled={isSolved}
onChange={() =>
setReplies(
ExerciseServices.reply(
id,
i.toString(),
replies
)
)
}
checked={reply && reply.value === i.toString()}
type="radio"
id={`choice-${index}-${i}`}
name={`choice-${index}`}
/>
<label
htmlFor={`choice-${index}-${i}`}
dangerouslySetInnerHTML={{ __html: choice }}
/>
</div>
);
})}
</div>
</section>
</li>
);
}
)}
</ol>
</div>
</article>
return (
<li
key={id}
className={`${isSolved ? (isCorrect ? "success" : "error") : ""}`}
>
<section aria-label="soru">
<p dangerouslySetInnerHTML={{ __html: questionText }} />
<div className={"choices"}>
{choices.map((choice, i) => {
return (
<div key={`${choice}-${i}`} className={"choice"}>
<input
disabled={isSolved}
onChange={() =>
setReplies(
ExerciseServices.reply(id, i.toString(), replies)
)
}
checked={reply && reply.value === i.toString()}
type="radio"
id={`choice-${index}-${i}`}
name={`choice-${index}`}
/>
<label
htmlFor={`choice-${index}-${i}`}
dangerouslySetInnerHTML={{ __html: choice }}
/>
</div>
);
})}
</div>
</section>
</li>
);
}
)}
</ol>
);
}

View File

@ -1,5 +1,4 @@
import styles from "./styles.module.scss";
import activityStyles from "../../activity.module.scss";
import { Droppable } from "@/core/components/dnd/droppable";
import { WrapperDndContext } from "@/core/components/dnd/wrapper_dnd_context";
import { Draggable } from "@/core/components/dnd/draggable";
@ -20,36 +19,40 @@ export function PairTextsWithImagesExercise() {
if (!replies || !dndBoard) return null;
return (
<article>
<div className={activityStyles["exercise-body"]}>
<WrapperDndContext
disabled={isSolved}
board={dndBoard}
startDragging={handleStartDragging}
stopDragging={handleStopDragging}
>
<section
aria-label="soru kartları"
className={styles["question-cards"]}
>
{(activityData.exercise as IQAExercise).template.map(
({ id, questionText }) => {
const reply = ExerciseServices.getReply(id, replies);
const isCorrect = ExerciseServices.checkReply(
id,
activityData.exercise,
replies
);
<WrapperDndContext
disabled={isSolved}
board={dndBoard}
startDragging={handleStartDragging}
stopDragging={handleStopDragging}
>
<section aria-label="soru kartları" className={styles["question-cards"]}>
{(activityData.exercise as IQAExercise).template.map(
({ id, questionText }) => {
const reply = ExerciseServices.getReply(id, replies);
const isCorrect = ExerciseServices.checkReply(
id,
activityData.exercise,
replies
);
return (
<div className={`simple-card`} key={id}>
<img
alt="soru fotoğrafı"
src={`${process.env.NEXT_PUBLIC_AWS_CLOUDFRONT_IMAGE_BASE_URL}/${questionText}`}
/>
{
<Droppable
className="full-width"
return (
<div className={`simple-card`} key={id}>
<img
alt="soru fotoğrafı"
src={`${process.env.NEXT_PUBLIC_AWS_CLOUDFRONT_IMAGE_BASE_URL}/${questionText}`}
/>
{
<Droppable
className="full-width"
status={
isSolved ? (isCorrect ? "success" : "error") : "neutral"
}
disabled={isSolved}
key={id}
blankId={id}
>
{reply?.value && reply?.id && (
<Draggable
status={
isSolved
? isCorrect
@ -57,34 +60,19 @@ export function PairTextsWithImagesExercise() {
: "error"
: "neutral"
}
styleChip={{ width: "100%" }}
styleContainer={{ width: "100%" }}
disabled={isSolved}
key={id}
blankId={id}
>
{reply?.value && reply?.id && (
<Draggable
status={
isSolved
? isCorrect
? "success"
: "error"
: "neutral"
}
styleChip={{ width: "100%" }}
styleContainer={{ width: "100%" }}
disabled={isSolved}
item={{ id: reply.id, value: reply.value }}
/>
)}
</Droppable>
}
</div>
);
}
)}
</section>
</WrapperDndContext>
</div>
</article>
item={{ id: reply.id, value: reply.value }}
/>
)}
</Droppable>
}
</div>
);
}
)}
</section>
</WrapperDndContext>
);
}

View File

@ -1,5 +1,4 @@
import styles from "./styles.module.scss";
import activityStyles from "../../activity.module.scss";
import useViewModelContext from "../../view_model";
import * as ExerciseServices from "@/lib/exercise/exercise_services";
import IQAExercise from "@/lib/exercise/qa_exercise/qa_exercise";
@ -11,72 +10,67 @@ export function TrueFalseExercise() {
if (!replies) return null;
return (
<section
className={styles["container"]}
aria-label="doğru yanlış egzersizi"
>
<div className={activityStyles["exercise-body"]}>
<header>
<span>Doğru</span>
<span>Yanlış</span>
</header>
<ol className={`simple ${styles["questions"]}`}>
{(activityData.exercise as IQAExercise).template.map(
({ id, questionText }) => {
const reply = ExerciseServices.getReply(id, replies);
const isCorrect = ExerciseServices.checkReply(
id,
activityData.exercise,
replies
);
<div className={`${styles["container"]}`}>
<header>
<span>Doğru</span>
<span>Yanlış</span>
</header>
<ol className={`simple ${styles["questions"]}`}>
{(activityData.exercise as IQAExercise).template.map(
({ id, questionText }) => {
const reply = ExerciseServices.getReply(id, replies);
const isCorrect = ExerciseServices.checkReply(
id,
activityData.exercise,
replies
);
return (
<li
className={`${
isSolved ? styles[isCorrect ? "success" : "error"] : ""
}`}
key={id}
>
<section aria-label="soru">
<p
className={styles["question"]}
dangerouslySetInnerHTML={{ __html: questionText }}
/>
<div className={styles["radio-buttons"]}>
<div className={styles["radio-button"]}>
<input
type="radio"
name={`true-false-${id}`}
disabled={isSolved}
checked={reply && reply.value === "true"}
onChange={() =>
setReplies(
ExerciseServices.reply(id, "true", replies)
)
}
/>
</div>
<div className={styles["radio-button"]}>
<input
type="radio"
name={`true-false-${id}`}
disabled={isSolved}
checked={reply && reply.value === "false"}
onChange={() =>
setReplies(
ExerciseServices.reply(id, "false", replies)
)
}
/>
</div>
return (
<li
className={`${
isSolved ? styles[isCorrect ? "success" : "error"] : ""
}`}
key={id}
>
<section aria-label="soru">
<p
className={styles["question"]}
dangerouslySetInnerHTML={{ __html: questionText }}
/>
<div className={styles["radio-buttons"]}>
<div className={styles["radio-button"]}>
<input
type="radio"
name={`true-false-${id}`}
disabled={isSolved}
checked={reply && reply.value === "true"}
onChange={() =>
setReplies(
ExerciseServices.reply(id, "true", replies)
)
}
/>
</div>
</section>
</li>
);
}
)}
</ol>
</div>
</section>
<div className={styles["radio-button"]}>
<input
type="radio"
name={`true-false-${id}`}
disabled={isSolved}
checked={reply && reply.value === "false"}
onChange={() =>
setReplies(
ExerciseServices.reply(id, "false", replies)
)
}
/>
</div>
</div>
</section>
</li>
);
}
)}
</ol>
</div>
);
}

View File

@ -1,5 +1,4 @@
import IFillInBlanksExercise from "@/lib/exercise/fill_in_blanks_exercise/fill_in_blanks_exercise";
import activityStyles from "../../activity.module.scss";
import useViewModelContext from "../../view_model";
import * as ExerciseServices from "@/lib/exercise/exercise_services";
@ -10,62 +9,54 @@ export function TypeInBlanksExercise() {
if (!replies) return null;
return (
<article>
<div className={activityStyles["exercise-body"]}>
<ol className={`simple composite-question-list`}>
{(activityData.exercise as IFillInBlanksExercise).template.map(
(item, index) => {
return (
<li key={index}>
<section aria-label="soru">
{item.atoms.map((piece) => {
const { id, type } = piece;
<ol className={`simple composite-question-list`}>
{(activityData.exercise as IFillInBlanksExercise).template.map(
(item, index) => {
return (
<li key={index}>
<section aria-label="soru">
{item.atoms.map((piece) => {
const { id, type } = piece;
if (type === "text") {
return (
<p
key={id}
dangerouslySetInnerHTML={{ __html: piece.value }}
/>
);
if (type === "text") {
return (
<p
key={id}
dangerouslySetInnerHTML={{ __html: piece.value }}
/>
);
}
const reply = ExerciseServices.getReply(id, replies);
const isCorrect = ExerciseServices.checkReply(
id,
activityData.exercise,
replies
);
return (
<input
type="text"
style={{ width: "10rem" }}
className={`basic ${
isSolved ? (isCorrect ? "success" : "error") : ""
}`}
key={id}
disabled={isSolved}
value={reply?.value ?? ""}
onChange={(e) =>
setReplies(
ExerciseServices.reply(id, e.target.value, replies)
)
}
const reply = ExerciseServices.getReply(id, replies);
const isCorrect = ExerciseServices.checkReply(
id,
activityData.exercise,
replies
);
return (
<input
type="text"
style={{ width: "10rem" }}
className={`basic ${
isSolved ? (isCorrect ? "success" : "error") : ""
}`}
key={id}
disabled={isSolved}
value={reply?.value ?? ""}
onChange={(e) =>
setReplies(
ExerciseServices.reply(
id,
e.target.value,
replies
)
)
}
/>
);
})}
</section>
</li>
);
}
)}
</ol>
</div>
</article>
/>
);
})}
</section>
</li>
);
}
)}
</ol>
);
}