diff --git a/.vite/deps/_metadata.json b/.vite/deps/_metadata.json new file mode 100644 index 0000000000000000000000000000000000000000..8aa7f0889b01e8cec656ea43a320c74cd2f84a88 --- /dev/null +++ b/.vite/deps/_metadata.json @@ -0,0 +1,8 @@ +{ + "hash": "8bbe2421", + "configHash": "994f273f", + "lockfileHash": "5ae3b81b", + "browserHash": "aa3147a0", + "optimized": {}, + "chunks": {} +} \ No newline at end of file diff --git a/.vite/deps/package.json b/.vite/deps/package.json new file mode 100644 index 0000000000000000000000000000000000000000..3dbc1ca591c0557e35b6004aeba250e6a70b56e3 --- /dev/null +++ b/.vite/deps/package.json @@ -0,0 +1,3 @@ +{ + "type": "module" +} diff --git a/src/components/SideMenu.vue b/src/components/SideMenu.vue index f1b3b9586944aa50cf85fbec7bfab832739d8590..6ad90b238a9c3f89be1b1034c3ca5d471dffcb06 100644 --- a/src/components/SideMenu.vue +++ b/src/components/SideMenu.vue @@ -45,7 +45,7 @@ align-items: center; width: 15vw; - height: 100vh; + height: 100%; z-index: 1; background-color: white; diff --git a/src/components/TopMenu.vue b/src/components/TopMenu.vue index c1d51c8f434e7d38b7c602e7b7c4a14370341f80..d4d5e19bd7bc4940b7e6964e2b31bdf2dced232d 100644 --- a/src/components/TopMenu.vue +++ b/src/components/TopMenu.vue @@ -22,7 +22,9 @@ const toggleMenu = () => { <input type="text" placeholder="Search..." v-model="searchQuery" @input="search"> </div> <div class="top-items"> - <img src="/src/components/icons/NewTrivio.png" alt="NewTrivio" width="40" height="40"> + <router-link to="/homepage/create-trivio"> + <img src="/src/components/icons/NewTrivio.png" alt="NewTrivio" width="40" height="40"> + </router-link> <img alt="Profile" src="../assets/user.svg" width="40" height="40" @click="toggleMenu"> <div v-if="isMenuOpen" class="curtain-menu"> <!-- Content of your curtain menu goes here --> diff --git a/src/components/TrivioQuestion.vue b/src/components/TrivioQuestion.vue new file mode 100644 index 0000000000000000000000000000000000000000..241f569b2be9dd4496cc6aa6a7dd1df58b3b7238 --- /dev/null +++ b/src/components/TrivioQuestion.vue @@ -0,0 +1,284 @@ +<script setup lang="ts"> +import { ref, defineProps, defineEmits, watch } from 'vue' + +const props = defineProps<{ + index:number; + questionId:number; +}>(); + +const emit = defineEmits(['delete-question', 'save-question']); +const questionType = ref<string>('multiple'); +const question = ref<string>(''); +const answers = ref<Answer[]>([ + { answer: '', correct: false }, + { answer: '', correct: false }, + { answer: '', correct: false }, + { answer: '', correct: false} +]); +const tags = ref<Tag[]>([]) + +interface Answer { + answer: string; + correct: boolean; +} + +interface Tag{ + tag: String; +} + +const addTag = () => { + tags.value.push({tag: ''}) +}; + +const deleteTag = (index: number) => { + tags.value.splice(index, 1); +}; + +const deleteThisQuestion = () => { + emit('delete-question', props.questionId); +}; + +const updateCorrectness = (index: number) => { + answers.value.forEach((answer, i) => { + if (i !== index) { + answer.correct = false; // Set other answers to false + } + }); +}; + +const preventSpace = (event: KeyboardEvent) => { + if (event.key === ' ') { + event.preventDefault(); + } +}; +const saveQuestionData = () => { + const questionData = { + questionId: props.questionId, + question: question.value, + questionType: questionType.value, + answers: answers.value, + tags: tags.value + }; + emit('save-question', questionData); +}; + +watch(question, saveQuestionData); +watch(questionType, (newQuestionType) => { + if (newQuestionType === 'trueFalse') { + answers.value = [ + { answer: 'True', correct:false }, + { answer: 'False', correct:false } + ] + } else { + answers.value = [ + { answer: '', correct:false }, + { answer: '', correct: false }, + { answer: '', correct:false }, + { answer: '', correct: false } + ] + } + saveQuestionData(); // Save question data after updating answer values +}); +watch(answers, saveQuestionData, { deep: true }); +watch(tags, saveQuestionData, {deep: true}); + +</script> +<template> + <div class="trivio-question"> + <div class="top"> + <h2>{{index}}</h2> + <img alt="Profile" src="../components/icons/delete.png" width="25" height="25" @click="deleteThisQuestion"> + </div> + <div class="input"> + <div class="left"> + <input type="text" v-model="question" placeholder="Question" class="question"> + <template v-if="questionType === 'multiple'"> + <div v-for="(answer, index) in answers" :key="index" class="answer-box"> + <input type="checkbox" v-model="answer.correct" :id="'correct-answer-' + index"> + <input type="text" v-model="answer.answer" :placeholder="'Answer ' + (index + 1)" class="answer"> + </div> + </template> + <template v-else> + <div class="answer-box"> + <input type="radio" :id="'true-answer' + questionId" :name="'answer-' + questionId" class="radio" v-model="answers[0].correct" value='true' @click="updateCorrectness(0)"> + <label :for="'true-answer'+questionId" class="true-false"> True </label> + <input type="radio" :id="'false-answer' + questionId" :name="'answer-' + questionId" class="radio" v-model="answers[1].correct" value="true" @click="updateCorrectness(1)"> + <label :for="'false-answer'+questionId" class="true-false"> False </label> + </div> + </template> + <div class="tag-section"> + <button @click="addTag" class="addTag">Add Tag +</button> + <div class="tags"> + <div v-for="(tag, index) in tags" :key="index" class="tag-box"> + <input type="text" v-model="tag.tag" :placeholder="'Tag ' + (index + 1)" class="tag" @keypress="preventSpace"> + <img src="@/components/icons/RemoveTag.png" alt="removeTag" @click="deleteTag(index)" width="20px" height="20px"> + </div> + </div> + </div> + </div> + <div class="right"> + <div class="question-type"> + <h3>Question type:</h3> + <select v-model="questionType" class="box"> + <option value="multiple">Multiple Choice</option> + <option value="trueFalse">True/False</option> + </select> + </div> + <div class="image-box"> + <h1>Add image</h1> + <img src="/src/components/icons/AddImage.png" alt="Image" width="50px" height="50px"> + </div> + </div> + </div> + </div> +</template> + +<style scoped> +.trivio-question{ + display: flex; + flex-direction: column; + width: 100%; + height: 100%; + border: 2px solid darkgray; + border-radius: 15px; + box-sizing: border-box; + font-size: 15px; + padding: 10px; + background-color: white; +} + +.top{ + display: flex; + flex-direction: row; + gap: 97%; +} + +.input{ + display: flex; + flex-direction: row; + gap: 10px; +} + +.left{ + display: flex; + flex-direction: column; + gap: 10px; +} + +.question{ + width: 100%; + height: 50px; + border: 2px solid darkgray; + border-radius: 15px; + box-sizing: border-box; + font-size: 20px; + padding: 10px; + +} + +.box{ + height: 30px; +} + +.answer-box{ + width: 60vw; + display: flex; + flex-direction: row; + align-items: center; + place-items: center; + gap: 10px; +} + +.answer{ + width: 100%; + height: 50px; + border: 2px solid darkgray; + border-radius: 15px; + box-sizing: border-box; + font-size: 20px; + padding: 10px; +} + +.radio { + margin-right: 2px; +} + +.true-false{ + width: 100%; + height: 50px; + border: 2px solid darkgray; + border-radius: 15px; + box-sizing: border-box; + font-size: 20px; + padding: 10px; +} + +.tag-section{ + display: flex; + flex-direction: row; + width: 100%; + gap: 15px; +} + +.addTag{ + height: 35px; + width: 94px; + font-size: medium; +} + +.tags{ + display: flex; + flex-wrap: wrap; + place-items: center; + align-items: center; + gap: 10px; + width: 90%; +} + +.tag-box{ + display: flex; + flex-direction: row; + align-items: center; + gap: 2px; +} +.tag{ + width: 95px; + height: 35px; + border-radius: 5px; + border: 2px solid darkgray; + text-align: center; + font-size: medium; +} + +.right{ + display: flex; + flex-direction: column; + width: 100%; + gap: 10px; +} + +.question-type{ + height: 50px; + display: flex; + flex-direction: row; + align-items: center; + place-items: center; + gap: 10px; +} + +.image-box{ + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + width: 100%; + height: 230px; + border: 2px solid darkgray; + border-radius: 15px; + box-sizing: border-box; + font-size: 12px; + color: darkgray; + padding: 10px; + gap: 10px; +} +</style> diff --git a/src/components/icons/AddImage.png b/src/components/icons/AddImage.png new file mode 100644 index 0000000000000000000000000000000000000000..22dba638edadae42ce0b61eaef911a4b5b326643 Binary files /dev/null and b/src/components/icons/AddImage.png differ diff --git a/src/components/icons/RemoveTag.png b/src/components/icons/RemoveTag.png new file mode 100644 index 0000000000000000000000000000000000000000..c1407a25d161d2ba757764d2706339222d7e5415 Binary files /dev/null and b/src/components/icons/RemoveTag.png differ diff --git a/src/components/icons/delete.png b/src/components/icons/delete.png new file mode 100644 index 0000000000000000000000000000000000000000..81e7c491dffc3c5c406ff8b63671e4b9a512eece Binary files /dev/null and b/src/components/icons/delete.png differ diff --git a/src/router/index.ts b/src/router/index.ts index bda4c057c03fdfdf8dd0e767d624fdf7292d2b22..066dba87d014803c4e6c8c9893fc909d95237ce6 100644 --- a/src/router/index.ts +++ b/src/router/index.ts @@ -2,15 +2,13 @@ import { createRouter, createWebHistory } from 'vue-router' import LoginView from '@/views/frontpage/LoginView.vue' import FrontPageView from '@/views/FrontPageView.vue' import SignUpView from '@/views/frontpage/SignUpView.vue' -import MainPageView from '@/views/MainPageView.vue' import ProfileView from '@/views/mainpage/ProfileView.vue' import DiscoveryView from '@/views/mainpage/DiscoveryView.vue' import MyTriviosView from '@/views/mainpage/MyTriviosView.vue' import ContactView from '@/views/mainpage/ContactView.vue' import HistoryView from '@/views/mainpage/HistoryView.vue' -import StartView from '@/views/mainpage/StartView.vue' -import PlayView from '@/views/mainpage/PlayView.vue' - +import CreateTrivio from '@/views/mainpage/CreateTrivio.vue' +import MainPageView from '@/views/MainPageView.vue' const router = createRouter({ history: createWebHistory(import.meta.env.BASE_URL), @@ -44,8 +42,12 @@ const router = createRouter({ component: ProfileView }, { - path: 'discovery', - component: DiscoveryView + path: 'create-trivio', + component: CreateTrivio + }, + { + path: 'home', + component: HomeView }, { path: 'trivios', diff --git a/src/views/mainpage/CreateTrivio.vue b/src/views/mainpage/CreateTrivio.vue new file mode 100644 index 0000000000000000000000000000000000000000..490c4e08eb66fb9feec3ed92d108bfb7efdb7d3b --- /dev/null +++ b/src/views/mainpage/CreateTrivio.vue @@ -0,0 +1,318 @@ +<script setup lang="ts"> +import { ref, watch} from 'vue' +import TrivioQuestion from '@/components/TrivioQuestion.vue' + +// Reactive variables +const title = ref<string>('') +const description = ref<string>('') +const difficulty = ref<string>('easy') +const category = ref<string>('random') +const visibility = ref<string>('private') +const questions = ref<Question[]>([]) +const numberOfQuestions = ref<number[]>([]) +const userID = 1 +let uniqueComponentId = 0; + +interface Answer { + answer: string; + correct: boolean; +} + +interface Question { + questionId: number; + question: string; + questionType: string; + answers: Answer[]; // Adjust the type of inputFields as needed + tags: String[]; +} + +// Methods +const addQuestion = () => { + const newQuestionId = uniqueComponentId + numberOfQuestions.value.push(newQuestionId) + questions.value.push({ + questionId: newQuestionId, + question: '', + questionType: '', + answers: [], + tags: [] + }) + uniqueComponentId ++; + console.log(uniqueComponentId) +} + +const deleteQuestion = (questionId: number) => { + const index = questions.value.findIndex((q: Question) => q.questionId === questionId); + numberOfQuestions.value.splice(index, 1) + questions.value.splice(index,1) +} + +const saveQuestion = (questionData: Question) => { + const index = questions.value.findIndex((q: Question) => q.questionId === questionData.questionId); + if (index !== -1) { + questions.value[index] = questionData; + console.log(questions) + } else { + questions.value.push(questionData); + console.log(questions.value.map(question => ({ + question: question.question, + questionType: question.questionType, + answers: question.answers, + tags: question.tags + }))) + } +}; + +watch(questions, (newQuestions) => { + console.log('All questions:', newQuestions) +}) + +const logInfo = () =>{ + console.log(questions.value) + console.log(numberOfQuestions.value) +} + +const saveQuizToBackend = async () => { + try { + const response = await fetch('http://localhost:8080/trivios/create', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + "trivio": { + "title": title.value, + "description": description.value, + "difficulty": difficulty.value, + "visibility": visibility.value, + "multimedia_url": "cat.png" + }, + "questionsWithAnswers": questions.value.map(question => ({ + "question": { + "question": question.question, + "questionType": question.questionType, + "tags": question.tags.map(tag => tag.tag) + }, + "answers": question.answers.map(answer => ({ + "answer": answer.answer, + "correct": answer.correct + })), + })), + "userId": userID + }) + }); + // Quiz saved successfully, handle response if needed + const responseData = await response.text(); + console.log('Quiz saved successfully:', responseData); + } catch (error:any) { + console.error('Error saving quiz:', error.message); + // Handle error, show error message to user, etc. + } +}; + +</script> + + +<template> + <div class="create-trivio"> + <div class="header"> + <h1 class="title">Create new quiz</h1> + + <div class="options"> + <h3>Difficulty: </h3> + <select v-model="difficulty" class="option"> + <option value="easy">Easy</option> + <option value="medium">Medium</option> + <option value="hard">Hard</option> + </select> + <h3>Category: </h3> + <select v-model="category" class="option"> + <option value="random">Random</option> + <option value="sport">Sport</option> + <option value="music">Music</option> + </select> + <h3>Visibility: </h3> + <select v-model="visibility" class="option"> + <option value="private">Private</option> + <option value="public">Public</option> + </select> + </div> + + <div class="buttons"> + <button class="top-button" @click="logInfo">Save</button> + <button class="top-button" @click="saveQuizToBackend">Create</button> + </div> + </div> + + <div class="input-boxes"> + <div class="left"> + <input type="text" v-model="title" placeholder="Enter title" class="trivio-title"> + <textarea v-model="description" placeholder="Add description" class="description"></textarea> + </div> + <div class="right"> + <div class="image-box"> + <h1>Add image</h1> + <img src="/src/components/icons/AddImage.png" alt="Image" width="50px" height="50px"> + </div> + </div> + </div> + + <div class="questions"> + <TrivioQuestion + v-for="(questionId, index) in numberOfQuestions" + :key="questionId" + :index="index+1" + :question-id="questionId" + @delete-question="deleteQuestion(questionId)" + @save-question="saveQuestion" + ></TrivioQuestion> + </div> + + <div class="add-box"> + <button class="add-question" @click="addQuestion"> + <img src="/src/components/icons/NewTrivio.png" alt="Image" width="50px" height="50px"> + </button> + </div> + + </div> +</template> +<style scoped> +.create-trivio{ + display: flex; + flex-direction: column; + place-items: start; + width: 100%; + height: 100%; + padding: 20px; + gap:20px; + overflow-x: scroll; + background-color: rgba(196, 201, 220, 0.15); + +} + +.header{ + display: flex; + flex-direction: row; + place-items: center; + align-items: center; + align-content: center; + + width: 100%; +} + +.title{ + width: 30%; + font-weight: bold; +} + +.options{ + display: flex; + flex-direction: row; + place-content: center; + align-content: center; + place-items: center; + align-items: center; + gap: 10px; +} + +.option{ + width: 100px; + height: 30px; + +} + +.buttons{ + display: flex; + flex-direction: row; + place-content: end; + align-content: center; + align-items: center; + gap: 20px; + width: 40%; +} + +.top-button{ + width: 150px; + height: 30px; + border-radius: 5px; +} + +.left{ + display: flex; + flex-direction: column; + place-items: start; + gap: 10px; +} + +.input-boxes{ + display: flex; + flex-direction: row;; + width: 100%; + gap: 10px; +} + +.trivio-title{ + width: calc(40vw - 2px); /* Subtracting border width from the width */ + height: 50px; + border: 2px solid darkgray; /* Setting border instead of outline */ + border-radius: 15px; + box-sizing: border-box; + font-size: 20px; + padding: 10px; +} + +.description { + width: calc(40vw - 2px); /* Subtracting border width from the width */ + height: 200px; + border: 2px solid darkgray; /* Setting border instead of outline */ + border-radius: 15px; + box-sizing: border-box; + font-size: 15px; + padding: 10px; + resize: none; +} + +.right{ + width: 100%; +} + +.image-box{ + display: flex; + flex-direction: column; + justify-content: center; /* Center horizontally */ + align-items: center; /* Center vertically */ + + width: 100%; + height: 262px; + + border: 2px solid darkgray; + border-radius: 15px; + box-sizing: border-box; + + font-size: 12px; + color: darkgray; + padding: 10px; + gap: 10px; + background-color: white; +} + +.questions{ + display: flex; + flex-direction: column; + gap: 20px; + width: 100%; +} + +.add-box{ + display: flex; + width: 100%; + align-content: center; + place-content: center; +} + +.add-question{ + border-radius: 50px; +} + + +</style> \ No newline at end of file