WIP CreatePage; BoardView/BoardEntryView implementation
This commit is contained in:
parent
d2a0551ddd
commit
c5d6075c01
|
|
@ -52,6 +52,9 @@ $breadcrumb-divider: quote(">");
|
|||
@import "bootstrap/scss/variables";
|
||||
@import "bootstrap/scss/variables-dark";
|
||||
|
||||
$form-file-button-bg: var(--#{$prefix}secondary-bg);
|
||||
$form-file-button-hover-bg: var(--#{$prefix}tertiary-bg);
|
||||
|
||||
|
||||
/* Bootstrap Color Map adjustments */
|
||||
$custom-colors: (
|
||||
|
|
|
|||
|
|
@ -1,25 +1,97 @@
|
|||
<script setup lang="ts">
|
||||
import type { BoardEntry } from '@/models/board/BoardEntry';
|
||||
import { computed } from 'vue';
|
||||
|
||||
const QUESTION_TYPE_SIMPLE_TEXT_ID = 1;
|
||||
const QUESTION_TYPE_IMAGE_ID = 2;
|
||||
|
||||
const props = defineProps<{
|
||||
boardEntry: BoardEntry,
|
||||
selectedQuestionIndex: number,
|
||||
isAnswerShown: boolean,
|
||||
}>();
|
||||
|
||||
const emit = defineEmits<{
|
||||
questionSelected: [questionIndex: number],
|
||||
}>();
|
||||
|
||||
const question = computed( () => {
|
||||
return props.boardEntry.questions[props.selectedQuestionIndex ?? 0];
|
||||
});
|
||||
|
||||
function selectQuestionIndex( qIndex: number ) {
|
||||
emit( "questionSelected", qIndex );
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="mx-3 h-100 d-flex flex-column">
|
||||
<div class="row">
|
||||
<div class="col text-center p-3">
|
||||
<h2>
|
||||
{{ boardEntry.name }}
|
||||
</h2>
|
||||
<div class="h-100 d-flex flex-column">
|
||||
<div class="row h-100">
|
||||
<div class="col h-100 mx-3 overflow-y-auto">
|
||||
<div class="ratio ratio-16x9">
|
||||
<div class="w-100 h-100 d-flex justify-content-center align-items-center">
|
||||
<span v-if="boardEntry.questions.length === 0" class="fs-1">
|
||||
No Question to show
|
||||
</span>
|
||||
<span v-else-if="question.questionType.id === QUESTION_TYPE_SIMPLE_TEXT_ID" class="text-center preserve-breaks" :style="`font-size: ${question.fontScaling}em`">
|
||||
{{ question.text }}
|
||||
</span>
|
||||
<template v-else-if="question.questionType.id === QUESTION_TYPE_IMAGE_ID">
|
||||
<div class="d-flex flex-column justify-content-center align-items-center h-100 w-100">
|
||||
<span class="text-center preserve-breaks" :style="`font-size: ${question.fontScaling}em`">
|
||||
{{ question.text }}
|
||||
</span>
|
||||
<div class="h-75 w-100 d-flex justify-content-center align-items-center">
|
||||
<img
|
||||
v-if="question.image"
|
||||
:src="question.image"
|
||||
alt="User uploaded - No caption available"
|
||||
class="h-100 w-100 object-contain"
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<!-- Category Name -->
|
||||
<div class="position-absolute top-0 start-0 mt-2">
|
||||
<span class="fs-2">
|
||||
{{ boardEntry.category.name }}
|
||||
</span>
|
||||
</div>
|
||||
<!-- Points -->
|
||||
<div class="position-absolute bottom-0 end-0">
|
||||
<span class="fs-2">
|
||||
{{ boardEntry.points }}
|
||||
</span>
|
||||
</div>
|
||||
<!-- Answer -->
|
||||
<div v-if="isAnswerShown" class="position-absolute bottom-0 start-50 translate-middle-x mb-2">
|
||||
<div class="bg-primary p-2 rounded bg-opacity-50 fs-4 text-center">
|
||||
Answer:<br>{{ boardEntry.answer.text }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row h-100">
|
||||
<div class="col">
|
||||
Entry
|
||||
</div>
|
||||
<div v-if=" props.boardEntry.questions.length > 1 " class="position-absolute bottom-0 start-0 mb-3 ms-3">
|
||||
<template v-for="( question, questionIndex) in props.boardEntry.questions" :key="question.id">
|
||||
<button class="btn me-2"
|
||||
:class="[
|
||||
{ 'btn-primary': props.selectedQuestionIndex === questionIndex },
|
||||
{ 'btn-outline-primary': props.selectedQuestionIndex !== questionIndex }
|
||||
]"
|
||||
:disabled="props.selectedQuestionIndex === questionIndex"
|
||||
@click="selectQuestionIndex( questionIndex )">
|
||||
{{ questionIndex + 1 }}
|
||||
</button>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</template>
|
||||
|
||||
<style lang="css" scoped>
|
||||
.object-contain{
|
||||
object-fit: contain;
|
||||
}
|
||||
</style>
|
||||
|
|
@ -4,16 +4,19 @@ import type { Board } from '@/models/board/Board';
|
|||
import EditCategoryPanel from '@/components/blocks/EditCategoryPanel.vue';
|
||||
import EditBoardPanel from '@/components/blocks/EditBoardPanel.vue';
|
||||
import EditBoardEntryPanel from '@/components/blocks/EditBoardEntryPanel.vue';
|
||||
import EditQuestionPanel from './EditQuestionPanel.vue';
|
||||
|
||||
const props = defineProps<{
|
||||
categoryIndex: number | null,
|
||||
boardEntryIndex: number | null,
|
||||
questionIndex: number | null,
|
||||
}>()
|
||||
|
||||
const emit = defineEmits<{
|
||||
editBoard: [],
|
||||
editCategory: [cIndex: number],
|
||||
editBoardEntry: [cIndex: number, bEIndex: number],
|
||||
editQuestion: [cIndex: number, bEIndex: number, qIndex: number],
|
||||
}>()
|
||||
|
||||
const board = defineModel<Board>( { required: true } );
|
||||
|
|
@ -27,14 +30,44 @@ function editCategory(cIndex: number){
|
|||
function editBoardEntry(cIndex: number, bEIndex: number){
|
||||
emit("editBoardEntry", cIndex, bEIndex);
|
||||
}
|
||||
function editQuestion(cIndex: number, bEIndex: number, qIndex: number){
|
||||
emit("editQuestion", cIndex, bEIndex, qIndex);
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="d-flex flex-column h-100">
|
||||
<div class="flex-grow-1 overflow-y-auto p-2">
|
||||
<EditBoardPanel v-model="board" v-if="props.categoryIndex === null && props.boardEntryIndex === null" @editCategory="editCategory" />
|
||||
<EditCategoryPanel v-model="board" :categoryIndex="props.categoryIndex" v-else-if="props.categoryIndex !== null && props.boardEntryIndex === null" @editBoard="editBoard" @editBoardEntry="editBoardEntry"/>
|
||||
<EditBoardEntryPanel v-model="board" :categoryIndex="props.categoryIndex" :boardEntryIndex="props.boardEntryIndex" v-else-if="props.categoryIndex !== null && props.boardEntryIndex !== null" @editCategory="editCategory" @editBoard="editBoard" />
|
||||
<EditBoardPanel
|
||||
v-if="props.categoryIndex === null && props.boardEntryIndex === null && props.questionIndex === null"
|
||||
v-model="board"
|
||||
@editCategory="editCategory"
|
||||
/>
|
||||
<EditCategoryPanel
|
||||
v-else-if="props.categoryIndex !== null && props.boardEntryIndex === null && props.questionIndex === null"
|
||||
v-model="board"
|
||||
:categoryIndex="props.categoryIndex"
|
||||
@editBoard="editBoard"
|
||||
@editBoardEntry="editBoardEntry"
|
||||
/>
|
||||
<EditBoardEntryPanel
|
||||
v-else-if="props.categoryIndex !== null && props.boardEntryIndex !== null && props.questionIndex === null"
|
||||
v-model="board" :categoryIndex="props.categoryIndex"
|
||||
:boardEntryIndex="props.boardEntryIndex"
|
||||
@editQuestion="editQuestion"
|
||||
@editCategory="editCategory"
|
||||
@editBoard="editBoard"
|
||||
/>
|
||||
<EditQuestionPanel
|
||||
v-else-if="props.categoryIndex !== null && props.boardEntryIndex !== null && props.questionIndex !== null"
|
||||
v-model="board"
|
||||
:categoryIndex="props.categoryIndex"
|
||||
:boardEntryIndex="props.boardEntryIndex"
|
||||
:questionIndex="props.questionIndex"
|
||||
@editBoardEntry="editBoardEntry"
|
||||
@editCategory="editCategory"
|
||||
@editBoard="editBoard"
|
||||
/>
|
||||
</div>
|
||||
<div class="border-top border-2 border-primary p-2">
|
||||
<div class="d-flex align-items-center">
|
||||
|
|
|
|||
|
|
@ -1,10 +1,12 @@
|
|||
<script setup lang="ts">
|
||||
import type { Board } from '@/models/board/Board';
|
||||
import { computed, ref } from 'vue';
|
||||
import { computed, inject, ref } from 'vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { FontAwesomeIcon } from '@fortawesome/vue-fontawesome';
|
||||
import { BoardEntry } from '@/models/board/BoardEntry';
|
||||
import { Answer } from '@/models/board/Answer';
|
||||
import { VueDraggable } from 'vue-draggable-plus';
|
||||
import type { Board } from '@/models/board/Board';
|
||||
import { Question } from '@/models/board/Question';
|
||||
import { QuestionType } from '@/models/board/QuestionType';
|
||||
import { questionTypesKey } from '@/services/UtilService';
|
||||
|
||||
const { t } = useI18n();
|
||||
|
||||
|
|
@ -14,46 +16,48 @@ const props = defineProps<{
|
|||
categoryIndex: number,
|
||||
boardEntryIndex: number,
|
||||
}>();
|
||||
const questionTypes = inject(questionTypesKey);
|
||||
|
||||
const emit = defineEmits<{
|
||||
editBoard: [],
|
||||
editCategory: [categoryIndex: number],
|
||||
editQuestion: [categoryIndex: number, boardEntryIndex: number, questionIndex: number],
|
||||
}>()
|
||||
|
||||
const boardEntry = computed( () => {
|
||||
return board.value.categories[props.categoryIndex].boardEntries[props.boardEntryIndex];
|
||||
})
|
||||
});
|
||||
|
||||
const newBoardEntryName = ref( '' );
|
||||
function addBoardEntry() {
|
||||
if( board.value.categories[props.categoryIndex].boardEntries.length >= 10 ) {
|
||||
const newQuestionText = ref( '' );
|
||||
const newQuestionType = ref<QuestionType | null>( (questionTypes ?? [null])[0] );
|
||||
function addQuestion() {
|
||||
if( boardEntry.value.questions.length >= 10 || newQuestionType.value === null) {
|
||||
return;
|
||||
}
|
||||
const answer = new Answer('', undefined);
|
||||
const newBoardEntry = new BoardEntry( newBoardEntryName.value, 0, board.value.categories[props.categoryIndex], answer, [] );
|
||||
board.value.categories[props.categoryIndex].boardEntries.push( newBoardEntry );
|
||||
const newQuestion = new Question(newQuestionText.value, newQuestionType.value, boardEntry.value );
|
||||
boardEntry.value.questions.push( newQuestion );
|
||||
}
|
||||
|
||||
function deleteBoardEntry( index: number ) {
|
||||
board.value.categories[props.categoryIndex].boardEntries.splice( index, 1 );
|
||||
function deleteQuestion( index: number ) {
|
||||
boardEntry.value.questions.splice( index, 1 );
|
||||
}
|
||||
|
||||
function moveBoardEntryUp( index: number ) {
|
||||
if( index === 0 || board.value.categories[props.categoryIndex].boardEntries.length <= 1 ) {
|
||||
function moveQuestionUp( index: number ) {
|
||||
if( index === 0 || boardEntry.value.questions.length <= 1 ) {
|
||||
return;
|
||||
}
|
||||
const tmp = board.value.categories[props.categoryIndex].boardEntries[index];
|
||||
board.value.categories[props.categoryIndex].boardEntries[index] = board.value.categories[props.categoryIndex].boardEntries[index - 1];
|
||||
board.value.categories[props.categoryIndex].boardEntries[index - 1] = tmp;
|
||||
const tmp = boardEntry.value.questions[index];
|
||||
boardEntry.value.questions[index] = boardEntry.value.questions[index - 1];
|
||||
boardEntry.value.questions[index - 1] = tmp;
|
||||
}
|
||||
|
||||
function moveBoardEntryDown( index: number ) {
|
||||
if( index === board.value.categories[props.categoryIndex].boardEntries.length - 1 || board.value.categories[props.categoryIndex].boardEntries.length <= 1 ) {
|
||||
function moveQuestionDown( index: number ) {
|
||||
if( index === boardEntry.value.questions.length - 1 || boardEntry.value.questions.length <= 1 ) {
|
||||
return;
|
||||
}
|
||||
const tmp = board.value.categories[props.categoryIndex].boardEntries[index];
|
||||
board.value.categories[props.categoryIndex].boardEntries[index] = board.value.categories[props.categoryIndex].boardEntries[index + 1];
|
||||
board.value.categories[props.categoryIndex].boardEntries[index + 1] = tmp;
|
||||
const tmp = boardEntry.value.questions[index];
|
||||
boardEntry.value.questions[index] = boardEntry.value.questions[index + 1];
|
||||
boardEntry.value.questions[index + 1] = tmp;
|
||||
}
|
||||
|
||||
function openBoard() {
|
||||
|
|
@ -62,6 +66,9 @@ function openBoard() {
|
|||
function openCategory(categoryIndex: number) {
|
||||
emit("editCategory", categoryIndex);
|
||||
}
|
||||
function openQuestion(categoryIndex: number, boardEntryIndex: number, questionIndex: number) {
|
||||
emit("editQuestion", categoryIndex, boardEntryIndex, questionIndex);
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
|
@ -70,7 +77,7 @@ function openCategory(categoryIndex: number) {
|
|||
<div class="row">
|
||||
<div class="col">
|
||||
<div class="d-flex align-items-center mb-2 overflow-x-auto pb-2">
|
||||
<button class="btn btn-sm btn-outline-primary" :title="t('board.category.back')" @click="openCategory(props.categoryIndex)">
|
||||
<button class="btn btn-sm btn-outline-primary" :title="t('common.back')" @click="openCategory(props.categoryIndex)">
|
||||
<FontAwesomeIcon :icon="['fas', 'angle-left']"/>
|
||||
</button>
|
||||
<nav class="flex-grow-1 ms-2" aria-label="breadcrumb">
|
||||
|
|
@ -106,7 +113,81 @@ function openCategory(categoryIndex: number) {
|
|||
</div>
|
||||
<div class="row mt-3">
|
||||
<div class="col">
|
||||
Questions
|
||||
<h4>{{ t( "board.question.label", 2 ) }}</h4>
|
||||
<template v-if=" boardEntry.questions.length === 0 ">
|
||||
<p>
|
||||
{{ t( "board.question.label", 0 ) }}
|
||||
</p>
|
||||
</template>
|
||||
<template v-else>
|
||||
<VueDraggable v-model="board.categories[props.categoryIndex].boardEntries" item-key="id" :animation="150" ghost-class="draggable-ghost"
|
||||
tag="ul" class="list-group" handle=".drag-handle">
|
||||
<template v-for="(question, questionIndex) of boardEntry.questions" :key="question.id">
|
||||
<li class="list-group-item bg-body-secondary p-0">
|
||||
<div class="d-flex justify-content-between w-100">
|
||||
<div class="flex-grow-1 d-flex align-items-center text-truncate">
|
||||
<div
|
||||
class="drag-handle h-100 d-flex justify-content-center align-items-center cursor-move border-end border-1 bg-primary bg-opacity-10">
|
||||
<FontAwesomeIcon :icon="['fas', 'grip-lines']" class="px-2" />
|
||||
</div>
|
||||
<div class="text-truncate">
|
||||
<span class="ms-2">
|
||||
{{ question.questionType.title }}
|
||||
<span class="fw-light">
|
||||
({{ question.text.length === 0 ? 'No Text yet' : question.text }})
|
||||
</span>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="d-flex">
|
||||
<button class="btn btn-primary rounded-0" @click="openQuestion( props.categoryIndex, props.boardEntryIndex, questionIndex )">
|
||||
<FontAwesomeIcon :icon="['fas', 'edit']" size="sm" />
|
||||
</button>
|
||||
<div class="d-flex flex-column justify-content-center align-items-center h-100" role="group"
|
||||
aria-label="Vertical button group">
|
||||
<button class="btn btn-primary rounded-0 py-0 lh-sm" @click="moveQuestionUp( questionIndex )"
|
||||
:disabled="questionIndex === 0">
|
||||
<FontAwesomeIcon :icon="['fas', 'angle-up']" size="xs" />
|
||||
</button>
|
||||
<button class="btn btn-primary rounded-0 py-0 lh-sm" @click="moveQuestionDown( questionIndex )"
|
||||
:disabled="questionIndex === boardEntry.questions.length - 1">
|
||||
<FontAwesomeIcon :icon="['fas', 'angle-down']" size="xs" />
|
||||
</button>
|
||||
</div>
|
||||
<button class="btn btn-danger rounded-start-0 rounded-0"
|
||||
:class="[{ 'rounded-bottom-end': questionIndex === boardEntry.questions.length - 1 }, { 'rounded-top-end': questionIndex === 0 }]"
|
||||
@click="deleteQuestion( questionIndex )">
|
||||
<FontAwesomeIcon :icon="['fas', 'trash']" size="sm" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
</template>
|
||||
</VueDraggable>
|
||||
</template>
|
||||
<label class="mt-2" for="new-category-name">{{ t( "board.question.add" ) }}</label>
|
||||
<div class="row mb-2">
|
||||
<div class="col">
|
||||
<select id="type-for-new-question" v-model="newQuestionType" class="form-select">
|
||||
<template v-for="questionType in questionTypes" :key="questionType.id">
|
||||
<option :value="questionType" :title="questionType.description">{{ questionType.title }}</option>
|
||||
</template>
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-auto">
|
||||
<button class="btn btn-primary" @click="addQuestion">
|
||||
<FontAwesomeIcon :icon="['fas', 'plus']" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<h4>{{ t('board.answer.label', 2) }}</h4>
|
||||
<label for="answer-text">{{ t( 'board.answer.text' ) }}</label>
|
||||
<textarea id="answer-text" v-model="boardEntry.answer.text" class="form-control mb-2"
|
||||
:placeholder="t( 'board.answer.text' )"></textarea>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,129 @@
|
|||
<script setup lang="ts">
|
||||
import type { Board } from '@/models/board/Board';
|
||||
import { computed, inject, ref } from 'vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { FontAwesomeIcon } from '@fortawesome/vue-fontawesome';
|
||||
import { questionTypesKey } from '@/services/UtilService';
|
||||
|
||||
const QUESTION_TYPE_IMAGE_ID = 2;
|
||||
|
||||
const { t } = useI18n();
|
||||
|
||||
const board = defineModel<Board>( { required: true } );
|
||||
|
||||
const props = defineProps<{
|
||||
categoryIndex: number,
|
||||
boardEntryIndex: number,
|
||||
questionIndex: number;
|
||||
}>();
|
||||
const questionTypes = inject( questionTypesKey );
|
||||
|
||||
const emit = defineEmits<{
|
||||
editBoard: [],
|
||||
editCategory: [categoryIndex: number],
|
||||
editBoardEntry: [categoryIndex: number, boardEntryIndex: number],
|
||||
}>();
|
||||
|
||||
const question = computed( () => {
|
||||
return board.value.categories[props.categoryIndex].boardEntries[props.boardEntryIndex].questions[props.questionIndex];
|
||||
} );
|
||||
|
||||
function openBoard() {
|
||||
emit( "editBoard" );
|
||||
}
|
||||
function openCategory( categoryIndex: number ) {
|
||||
emit( "editCategory", categoryIndex );
|
||||
}
|
||||
function openBoardEntry( categoryIndex: number, boardEntryIndex: number ) {
|
||||
emit( "editBoardEntry", categoryIndex, boardEntryIndex );
|
||||
}
|
||||
|
||||
const imageInput = ref<File | null>( null );
|
||||
function newImageUploaded( event: Event ) {
|
||||
const element = event.currentTarget as HTMLInputElement;
|
||||
let files = element.files;
|
||||
if( files === null || files.length === 0 ) {
|
||||
return;
|
||||
}
|
||||
imageInput.value = files[0];
|
||||
question.value.image = URL.createObjectURL(files[0]);
|
||||
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="d-flex flex-column h-100">
|
||||
<div class="flex-grow-1 px-2">
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<div class="d-flex align-items-center mb-2 overflow-x-auto pb-2">
|
||||
<button class="btn btn-sm btn-outline-primary" :title="t( 'common.back' )"
|
||||
@click="openBoardEntry( props.categoryIndex, props.boardEntryIndex )">
|
||||
<FontAwesomeIcon :icon="['fas', 'angle-left']" />
|
||||
</button>
|
||||
<nav class="flex-grow-1 ms-2" aria-label="breadcrumb">
|
||||
<ol class="breadcrumb mb-0 flex-nowrap">
|
||||
<li class="breadcrumb-item text-truncate">
|
||||
<a href="#" @click="openBoard">
|
||||
{{ board.name }}
|
||||
</a>
|
||||
</li>
|
||||
<li class="breadcrumb-item text-truncate">
|
||||
<a href="#" @click="openCategory( props.categoryIndex )">
|
||||
{{ board.categories[props.categoryIndex].name }}
|
||||
</a>
|
||||
</li>
|
||||
<li class="breadcrumb-item text-truncate">
|
||||
<a href="#" @click="openBoardEntry( props.categoryIndex, props.boardEntryIndex )">
|
||||
{{ board.categories[props.categoryIndex].boardEntries[props.boardEntryIndex].name }}
|
||||
</a>
|
||||
</li>
|
||||
<li class="breadcrumb-item active text-truncate" style="max-width: 6em;" aria-current="page">
|
||||
{{ question.text.length !== 0 ? question.text : question.questionType.title }}
|
||||
</li>
|
||||
</ol>
|
||||
</nav>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<div class="d-flex border-bottom border-3 border-primary align-items-center mb-2">
|
||||
<h2 class="flex-grow-1 ms-2">{{ t( "board.question.infos" ) }}</h2>
|
||||
</div>
|
||||
|
||||
<label for="question-text">{{ t( 'board.question.text' ) }}</label>
|
||||
<textarea id="question-text" v-model="question.text" class="form-control mb-2"
|
||||
:placeholder="t( 'board.question.text' )"></textarea>
|
||||
|
||||
<label for="question-font-size-input">{{ t( 'board.question.fontsize' ) }}</label>
|
||||
<input type="number" id="question-font-size-input" v-model="question.fontScaling" class="form-control mb-2">
|
||||
|
||||
<label for="board-entry-points">{{ t( 'board.question.type' ) }}</label>
|
||||
<select id="question-type" v-model="question.questionType" class="form-select mb-2" aria-label="Question Type">
|
||||
<template v-for=" questionType in questionTypes " :key="questionType.id">
|
||||
<option :value="questionType" :title="questionType.description">{{ questionType.title }}</option>
|
||||
</template>
|
||||
</select>
|
||||
|
||||
<template v-if=" question.questionType.id === QUESTION_TYPE_IMAGE_ID ">
|
||||
<label for="question-image-input">{{ t( 'board.question.upload.image' ) }}</label>
|
||||
<input
|
||||
id="question-image-input"
|
||||
type="file"
|
||||
class="form-control mb-2"
|
||||
@change="newImageUploaded"
|
||||
accept="image/png, image/jpeg"
|
||||
>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="css" scoped>
|
||||
.draggable-ghost {
|
||||
opacity: .75;
|
||||
}
|
||||
</style>
|
||||
|
|
@ -67,7 +67,7 @@ userStore.userCheckPromise
|
|||
<nav ref="navbar" id="navbar-main" class="navbar navbar-expand-lg bg-dark-accented">
|
||||
<div class="container px-5">
|
||||
|
||||
<div class="position-absolute start-0 top-50 translate-middle-y d-flex ms-3 gap-3">
|
||||
<div class="position-absolute start-0 top-50 translate-middle-y d-flex ms-3 gap-3 z-2">
|
||||
<ThemeChanger />
|
||||
<LocaleChanger />
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
<script setup lang="ts">
|
||||
import { computed, inject, ref, type Ref } from 'vue';
|
||||
import { computed, inject, provide, ref, type Ref } from 'vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
|
||||
import { navbarKey } from '@/services/UtilService';
|
||||
import { navbarKey, questionTypesKey } from '@/services/UtilService';
|
||||
import { Board } from '@/models/board/Board';
|
||||
import type NavBar from '@/components/blocks/NavBar.vue';
|
||||
|
||||
|
|
@ -12,51 +12,89 @@ import { Category } from '@/models/board/Category';
|
|||
import BoardEntryView from '../blocks/BoardEntryView.vue';
|
||||
import { BoardEntry } from '@/models/board/BoardEntry';
|
||||
import { Answer } from '@/models/board/Answer';
|
||||
import { QuestionType } from '@/models/board/QuestionType';
|
||||
|
||||
const { t } = useI18n();
|
||||
|
||||
const navbar = inject<Ref<InstanceType<typeof NavBar> | null>>(navbarKey);
|
||||
const navbar = inject<Ref<InstanceType<typeof NavBar> | null>>( navbarKey );
|
||||
const navbarHeight = computed( () => {
|
||||
return navbar?.value?.navElement?.clientHeight;
|
||||
});
|
||||
} );
|
||||
const restHeight = computed( () => {
|
||||
return { height: `calc(100vh - ${navbarHeight.value}px)`};
|
||||
})
|
||||
return { height: `calc(100vh - ${navbarHeight.value}px)` };
|
||||
} );
|
||||
|
||||
const board = ref<Board>(new Board("New Board"));
|
||||
const board1 = ref<Board>(new Board("New Board", [new Category("Test1", "", board.value as Board),new Category("Test2", "", board.value as Board)]));
|
||||
const answer = new Answer('', undefined);
|
||||
const newBoardEntry = new BoardEntry( "Test Entry 1", 0, board1.value.categories[0] as Category, answer, [] );
|
||||
const board = ref<Board>( new Board( "New Board" ) );
|
||||
const board1 = ref<Board>( new Board( "New Board", [new Category( "Test1", "", board.value as Board ), new Category( "Test2", "", board.value as Board )] ) );
|
||||
const answer = new Answer( '', undefined );
|
||||
const newBoardEntry = new BoardEntry( "Test Entry 1", board1.value.categories[0] as Category, answer, [] );
|
||||
board1.value.categories[0].boardEntries.push( newBoardEntry );
|
||||
|
||||
const categoryIndex = ref<number | null>(null);
|
||||
const boardEntryIndex = ref<number | null>(null);
|
||||
const categoryIndex = ref<number | null>( null );
|
||||
const boardEntryIndex = ref<number | null>( null );
|
||||
const questionIndex = ref<number | null>( null );
|
||||
|
||||
function showBoardEntry(cIndex: number, bEIndex: number){
|
||||
function showQuestion( cIndex: number, bEIndex: number, qIndex: number ) {
|
||||
categoryIndex.value = cIndex;
|
||||
boardEntryIndex.value = bEIndex;
|
||||
questionIndex.value = qIndex;
|
||||
}
|
||||
|
||||
function showCategory(cIndex: number){
|
||||
function showBoardEntry( cIndex: number, bEIndex: number ) {
|
||||
categoryIndex.value = cIndex;
|
||||
boardEntryIndex.value = bEIndex;
|
||||
questionIndex.value = null;
|
||||
}
|
||||
|
||||
function showCategory( cIndex: number ) {
|
||||
categoryIndex.value = cIndex;
|
||||
boardEntryIndex.value = null;
|
||||
questionIndex.value = null;
|
||||
}
|
||||
|
||||
function showBoard(){
|
||||
function showBoard() {
|
||||
categoryIndex.value = null;
|
||||
boardEntryIndex.value = null;
|
||||
questionIndex.value = null;
|
||||
}
|
||||
|
||||
const questionTypes = [
|
||||
new QuestionType("Simple Text", "A simple question with just text", true, 1),
|
||||
new QuestionType("Image Question", "A question with text and an image", true, 2),
|
||||
new QuestionType("Audio Question", "A question with text and some audio", true, 3),
|
||||
];
|
||||
|
||||
provide(questionTypesKey, questionTypes);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div :style="restHeight">
|
||||
<div class="row h-100">
|
||||
<div class="col-9 pe-0">
|
||||
<BoardEntryView v-if="categoryIndex !== null && boardEntryIndex !== null" :boardEntry="(board1.categories[categoryIndex].boardEntries[boardEntryIndex] as BoardEntry)" />
|
||||
<BoardView v-else :board="(board1 as Board)" @categorySelected="showCategory" @boardEntrySelected="showBoardEntry" />
|
||||
<div class="col-9 h-100 pe-0">
|
||||
<BoardEntryView
|
||||
v-if=" categoryIndex !== null && boardEntryIndex !== null "
|
||||
:boardEntry="( board1.categories[categoryIndex].boardEntries[boardEntryIndex] as BoardEntry )"
|
||||
:selectedQuestionIndex="questionIndex ?? 0"
|
||||
:isAnswerShown="true"
|
||||
@questionSelected="(qIndex) => showQuestion(categoryIndex!, boardEntryIndex!, qIndex)"
|
||||
/>
|
||||
<BoardView
|
||||
v-else
|
||||
:board="( board1 as Board )"
|
||||
@categorySelected="showCategory"
|
||||
@boardEntrySelected="showBoardEntry" />
|
||||
</div>
|
||||
<div class="col-3 ps-0 h-100 overflow-auto border-start border-2 border-primary">
|
||||
<CreatePanel v-model="(board1 as Board)" :categoryIndex="categoryIndex" :boardEntryIndex="boardEntryIndex" @editBoard="showBoard" @editCategory="showCategory" @editBoardEntry="showBoardEntry"/>
|
||||
<CreatePanel
|
||||
v-model="( board1 as Board )"
|
||||
:categoryIndex="categoryIndex"
|
||||
:boardEntryIndex="boardEntryIndex"
|
||||
:questionIndex="questionIndex"
|
||||
@editBoard="showBoard"
|
||||
@editCategory="showCategory"
|
||||
@editBoardEntry="showBoardEntry"
|
||||
@editQuestion="showQuestion"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -52,6 +52,7 @@
|
|||
"save": "Save",
|
||||
"saveAndExit": "Save and Exit"
|
||||
},
|
||||
"back": "Back",
|
||||
"error": {
|
||||
"generic": "Error"
|
||||
}
|
||||
|
|
@ -89,6 +90,18 @@
|
|||
"label": "No Entries | Entry | Entries | {count} Entries",
|
||||
"add": "Add Entry",
|
||||
"name": "Entry Name"
|
||||
},
|
||||
"question": {
|
||||
"label": "No Question Layers | Question Layer | Question Layers | {count} Question Layers",
|
||||
"infos": "Question Layer Infos",
|
||||
"add": "Add Question Layer",
|
||||
"text": "Question Text",
|
||||
"type": "Question Type",
|
||||
"fontsize": "Font Size",
|
||||
"upload": {
|
||||
"image": "Upload an image",
|
||||
"audio": "Upload an audio file"
|
||||
}
|
||||
}
|
||||
},
|
||||
"theme": {
|
||||
|
|
|
|||
|
|
@ -5,6 +5,6 @@ export class Answer{
|
|||
public text: string,
|
||||
public boardEntry: BoardEntry | undefined,
|
||||
public image: URL | undefined = undefined,
|
||||
id: number | undefined = undefined,
|
||||
public id: number | undefined = undefined,
|
||||
){}
|
||||
}
|
||||
|
|
@ -7,6 +7,6 @@ export class Board{
|
|||
public categories: Array<Category> = [],
|
||||
public pointsAreTitle: boolean = false,
|
||||
public owner: User | undefined = undefined,
|
||||
private id: number | undefined = undefined,
|
||||
public id: number | undefined = undefined,
|
||||
){}
|
||||
}
|
||||
|
|
@ -5,10 +5,10 @@ import type { Question } from './Question';
|
|||
export class BoardEntry{
|
||||
constructor(
|
||||
public name: string,
|
||||
public points: number,
|
||||
public category: Category,
|
||||
public answer: Answer,
|
||||
public questions: Array<Question>,
|
||||
id: number | undefined = undefined,
|
||||
public points: number = 100,
|
||||
public id: number | undefined = undefined,
|
||||
){}
|
||||
}
|
||||
|
|
@ -7,6 +7,6 @@ export class Category{
|
|||
public description: string,
|
||||
public board: Board,
|
||||
public boardEntries: Array<BoardEntry> = [],
|
||||
id: number | undefined = undefined,
|
||||
public id: number | undefined = undefined,
|
||||
){}
|
||||
}
|
||||
|
|
@ -6,7 +6,8 @@ export class Question{
|
|||
public text: string,
|
||||
public questionType: QuestionType,
|
||||
public boardEntry: BoardEntry,
|
||||
public image: URL | undefined,
|
||||
id: number | undefined = undefined,
|
||||
public fontScaling: number = 3,
|
||||
public image: string | undefined = undefined,
|
||||
public id: number | undefined = undefined,
|
||||
) {}
|
||||
}
|
||||
|
|
@ -3,6 +3,6 @@ export class QuestionType {
|
|||
public title: string,
|
||||
public description: string,
|
||||
public active: boolean,
|
||||
id: number | undefined = undefined,
|
||||
public id: number | undefined = undefined,
|
||||
){}
|
||||
}
|
||||
|
|
@ -1,6 +1,7 @@
|
|||
import type NavBar from '@/components/blocks/NavBar.vue';
|
||||
import type { InjectionKey, Ref } from 'vue';
|
||||
import type NavBar from '@/components/blocks/NavBar.vue';
|
||||
import type { QuestionType } from '@/models/board/QuestionType';
|
||||
|
||||
export const infoModalShowFnKey = Symbol() as InjectionKey<Function>;
|
||||
export const navbarKey = Symbol() as InjectionKey<Ref<InstanceType<typeof NavBar> | undefined>>;
|
||||
// export const navbarKey = Symbol() as InjectionKey<Ref<InstanceType<typeof NavBar> | null>>;
|
||||
export const questionTypesKey = Symbol() as InjectionKey<Array<QuestionType>>;
|
||||
Loading…
Reference in New Issue