Skip to content
11 changes: 7 additions & 4 deletions components/CodingChallenge/EditableList.vue
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
<template>
<section>
<InputBtn class="my-7" full @click="openDialogAndAddNew()">{{
t("Buttons.AddNew")
}}</InputBtn>
<InputBtn class="my-7" full @click="openDialogAndAddNew()" :icon="PlusCircleIcon">
{{ t("Buttons.CreateChallenge") }}
</InputBtn>

<div v-if="codingChallenges.length">
<section
Expand Down Expand Up @@ -48,7 +48,7 @@

<div v-else-if="!codingChallenges.length">
<p class="border border-accent rounded-md w-full p-5 text-xl text-center">
{{ t("Headings.EmptyCodingChallenge") }}
{{ t("Headings.EmptyListInLecture", {"placeholder": t("Headings.CodingChallenges")}) }}
</p>
</div>

Expand Down Expand Up @@ -77,6 +77,7 @@ import {
LockOpenIcon,
EyeIcon,
PencilSquareIcon,
PlusCircleIcon,
} from "@heroicons/vue/24/outline";
import type { PropType } from "vue";
import { useDialogSlot } from "../../composables/dialogSlot";
Expand Down Expand Up @@ -146,6 +147,7 @@ export default {
propData,
openDialogAndAddNew,
user,
PlusCircleIcon,
};
},
components: {
Expand All @@ -155,6 +157,7 @@ export default {
LockOpenIcon,
EyeIcon,
PencilSquareIcon,
PlusCircleIcon,
},
};
</script>
Expand Down
4 changes: 4 additions & 0 deletions components/NavbarMenu.vue
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,10 @@ export default {
label: "Links.GetMorphcoins",
pathname: "/morphcoins",
},
{
label: "Links.CreateContent",
pathname: "/own-content",
},
];

const validTill = computed(() => {
Expand Down
9 changes: 8 additions & 1 deletion components/course/VideoMeta.vue
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,11 @@
>
<Btn secondary sm>{{ t("Buttons.AddTask") }}</Btn>
</NuxtLink>
<NuxtLink v-else to="/own-content">
<Tooltip heading="Headings.NotYetUnlocked" content="Body.YouHaveToReachLevel">
<Btn secondary sm :icon="LockClosedIcon">{{ t("Buttons.AddTask") }}</Btn>
</Tooltip>
</NuxtLink>
</template>

<Btn
Expand Down Expand Up @@ -73,12 +78,13 @@
<script lang="ts">
import { useI18n } from "vue-i18n";
import { defineComponent} from "vue";
import { CheckIcon, CheckBadgeIcon } from "@heroicons/vue/24/solid";
import { CheckIcon, CheckBadgeIcon, LockClosedIcon } from "@heroicons/vue/24/solid";

export default defineComponent({
components: {
CheckIcon,
CheckBadgeIcon,
LockClosedIcon
},
props: {
course: { type: Object as PropType<any>, default: null },
Expand Down Expand Up @@ -170,6 +176,7 @@ export default defineComponent({
user,
canCreate,
totalLevel,
LockClosedIcon,
};
},
});
Expand Down
90 changes: 65 additions & 25 deletions components/form/Quiz.vue
Original file line number Diff line number Diff line change
Expand Up @@ -13,21 +13,28 @@
@submit.prevent="onclickSubmitForm()"
ref="refForm"
>
<Input
:label="t('Inputs.Question')"
:placeholder="'Body.QuizDummyQuestion'"
v-model="form.question.value"
:class="canEdit ? '' : 'pointer-events-none opacity-60'"
@valid="form.question.valid = $event"
:rules="form.question.rules"
/>
<div class="flex items-center">
<Input
:label="t('Inputs.Question')"
:placeholder="'Body.QuizDummyQuestion'"
v-model="form.question.value"
:class="canEdit ? '' : 'pointer-events-none opacity-60'"
class="w-full mr-6"
@valid="form.question.valid = $event"
:rules="form.question.rules"
/>
<Input
:label="t('Inputs.XPForQuiz')"
:placeholder="t('Inputs.XPForQuiz')"
v-model="form.xp.value"
:class="canEdit ? '' : 'pointer-events-none opacity-60'"
@valid="form.xp.valid = $event"
:rules="form.xp.rules"
type="number"
/>

<Btn
:class="canEdit ? '' : 'pointer-events-none opacity-60'"
@click="onclickAddOption"
class="w-fit self-end"
>Add Option</Btn
>
<hr class="my-4" />
</div>

<article
:class="canEdit ? '' : 'pointer-events-none opacity-60'"
Expand All @@ -36,7 +43,7 @@
:key="option?.id ?? i"
>
<Input
:label="t('Inputs.Option')"
:label="t('Inputs.AnswerOption', { number: i + 1 })"
:placeholder="option?.placeholder"
v-model="option.answer"
@valid="option.valid = $event"
Expand All @@ -59,10 +66,20 @@
@click="onclickRemoveOption(i)"
/>
</article>
<div class="flex justify-end mb-4">
<Btn
:class="canEdit ? '' : 'pointer-events-none opacity-60'"
@click="onclickAddOption"
:icon="PlusCircleIcon"
secondary
sm
>{{ t("Buttons.AnswerOption") }}</Btn
>
</div>
</form>
<InputBtn
:loading="form.submitting"
class="self-center"
class="self-center mb-6"
@click="onclickSubmitForm()"
mt
v-if="!!user?.admin || !!!data"
Expand All @@ -78,15 +95,15 @@ import { defineComponent, ref } from "vue";
import type { PropType } from "vue";
import { useI18n } from "vue-i18n";
import type { IForm } from "~/types/form";
import { XMarkIcon } from "@heroicons/vue/24/solid";
import { XMarkIcon, PlusCircleIcon } from "@heroicons/vue/24/solid";
import {
updateSubTaskInQuizForUser,
updateSubTaskInQuizForAdmin,
getSubTaskAndSolutionInQuiz,
} from "~~/composables/quizzes";

export default defineComponent({
components: { XMarkIcon },
components: { XMarkIcon, PlusCircleIcon },
props: {
data: { type: Object, default: null },
taskId: { type: String, default: null },
Expand All @@ -99,6 +116,8 @@ export default defineComponent({
// ============================================================= refs
const refForm = ref<HTMLFormElement | null>(null);

const defaultQuestionXP = 5;

const canEdit = computed(() => {
if (props.data != null) {
if (user.value?.admin) {
Expand All @@ -118,6 +137,11 @@ export default defineComponent({
rules: [(v: string) => !!v || "Error.InputEmpty_Inputs.Question"],
},
single_choice: { value: false, valid: true },
xp: {
valid: false,
value: defaultQuestionXP,
rules: [(v: string) => !!v || "Error.InputEmpty_Inputs.XPForQuiz"],
},

submitting: false,
validate: () => {
Expand Down Expand Up @@ -195,10 +219,7 @@ export default defineComponent({
}

if (!isAllowed) {
openSnackbar(
"error",
"Please fill current option first before adding new option."
);
openSnackbar("error", "Error.FillCurrentOption");
return;
}

Expand All @@ -207,6 +228,7 @@ export default defineComponent({
valid: false,
rules: [(v: string) => !!v || "Error.InputEmpty_Inputs.Option"],
correct: false,
placeholder: "Headings.AnswerOption",
});
}

Expand Down Expand Up @@ -245,6 +267,7 @@ export default defineComponent({
if (!!!data) return;
form.question.value = data.question ?? "";
form.question.valid = !!form.question.value;
form.xp.value = data.xp ?? defaultQuestionXP;

if (data?.single_choice) {
selectedQuestionType.value = "Single Choice";
Expand Down Expand Up @@ -338,11 +361,26 @@ export default defineComponent({
return openSnackbar("error", "Error.OptionsCannotBeSame");
for (let i = 0; i < options.value.length; i++) {
if (options.value[i].answer.length > 256) {
return openSnackbar("error", t("Error.CannotHaveMoreCharacters", { input: t("Inputs.AnswerOption"), max: 256 }));
return openSnackbar(
"error",
t("Error.CannotHaveMoreCharacters", {
input: t("Inputs.AnswerOption"),
max: 256,
})
);
}
}
if (form.question.value.length > 4096) {
return openSnackbar("error", t("Error.CannotHaveMoreCharacters", { input: t("Inputs.Question"), max: 4096 }));
return openSnackbar(
"error",
t("Error.CannotHaveMoreCharacters", {
input: t("Inputs.Question"),
max: 4096,
})
);
}
if (form.xp.value < 1) {
return openSnackbar("error", "Error.XPValueCannotBeLessThanOne");
}
if (checkIsSingleChoice(options.value)) {
form.single_choice.value = true;
Expand All @@ -367,7 +405,7 @@ export default defineComponent({
answers: form.body().answers,
question: form.body().question,
coins: 0,
xp: 5,
xp: parseInt(form.body().xp),
single_choice: form.body().single_choice,
});
form.submitting = false;
Expand Down Expand Up @@ -405,6 +443,7 @@ export default defineComponent({
answers: form.body().answers,
question: form.body().question,
single_choice: form.body().single_choice,
xp: form.body().xp,
}
);
form.submitting = false;
Expand Down Expand Up @@ -448,6 +487,7 @@ export default defineComponent({
onclickAddOption,
options,
XMarkIcon,
PlusCircleIcon,
onclickRemoveOption,
selectedQuestionType,
setOptionCorrect,
Expand Down
8 changes: 4 additions & 4 deletions components/matching/EditableList.vue
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<template>
<div>
<InputBtn class="my-7" full @click="openDialogAndAddNew()">
{{ t("Buttons.AddNew") }}
<InputBtn class="my-7" full @click="openDialogAndAddNew()" :icon="PlusCircleIcon">
{{ t("Buttons.CreateMatching") }}
</InputBtn>
<section v-if="matchings?.length">
<div
Expand Down Expand Up @@ -32,7 +32,7 @@
class="border border-accent rounded-md w-full p-5 text-xl text-center"
v-else
>
{{ t("Headings.EmptyMatchings") }}
{{ t("Headings.EmptyListInLecture", {"placeholder": t("Headings.Matchings")}) }}
</p>
<div>
<DialogSlot
Expand All @@ -51,7 +51,7 @@
<script lang="ts" setup>
import { useDialogSlot } from "~~/composables/dialogSlot";
import { useI18n } from "vue-i18n";
import { TrashIcon, EyeIcon } from "@heroicons/vue/24/outline";
import { TrashIcon, EyeIcon, PlusCircleIcon } from "@heroicons/vue/24/outline";
import type { matching } from "~/types/matching";

const props = defineProps({
Expand Down
7 changes: 4 additions & 3 deletions components/quiz/SubTaskListEditable.vue
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<template>
<div>
<InputBtn class="my-7" full @click="openDialogAndAddNew()">
{{ t("Buttons.AddNew") }}
<InputBtn class="my-7" full @click="openDialogAndAddNew()" :icon="PlusCircleIcon">
{{ t("Buttons.CreateQuiz") }}
</InputBtn>
<section v-if="quizzes?.length">
<div
Expand Down Expand Up @@ -37,7 +37,7 @@
class="border border-accent rounded-md w-full p-5 text-xl text-center"
v-else
>
{{ t("Headings.EmptyQuizzes") }}
{{ t("Headings.EmptyListInLecture", {"placeholder": t("Headings.QuizQuestions")}) }}
</p>
<div>
<DialogSlot
Expand All @@ -60,6 +60,7 @@ import {
TrashIcon,
EyeIcon,
PencilSquareIcon,
PlusCircleIcon,
} from "@heroicons/vue/24/outline";

const props = defineProps({
Expand Down
4 changes: 2 additions & 2 deletions composables/matching.ts
Original file line number Diff line number Diff line change
Expand Up @@ -73,9 +73,9 @@ export async function getMatchingsInTask(task_id: any) {
}
}

export async function getMyMatchingsInTask(task_id: any, creator: any) {
export async function getMyMatchingsInTask(task_id: any, creator: any = "") {
try {
const response = await GET(`/challenges/tasks/${task_id}/matchings?creator=${creator}`);
const response = await GET(`/challenges/tasks/${task_id}/matchings${creator != "" ? `?creator=${creator}` : ""}`);
const myMatchings = useMyMatchings();
myMatchings.value = response;

Expand Down
Loading