Compare commits
No commits in common. "dev" and "master" have entirely different histories.
|
|
@ -1,2 +0,0 @@
|
||||||
VITE_API_BASE_URL=/api
|
|
||||||
#VITE_API_BASE_URL=http://localhost:8008/api
|
|
||||||
|
|
@ -18,7 +18,6 @@
|
||||||
"bootstrap": "^5.3.3",
|
"bootstrap": "^5.3.3",
|
||||||
"pinia": "^2.1.7",
|
"pinia": "^2.1.7",
|
||||||
"vue": "^3.4.21",
|
"vue": "^3.4.21",
|
||||||
"vue-draggable-plus": "^0.5.3",
|
|
||||||
"vue-i18n": "^9.13.1",
|
"vue-i18n": "^9.13.1",
|
||||||
"vue-router": "^4.3.0"
|
"vue-router": "^4.3.0"
|
||||||
},
|
},
|
||||||
|
|
@ -1848,12 +1847,6 @@
|
||||||
"undici-types": "~5.26.4"
|
"undici-types": "~5.26.4"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@types/sortablejs": {
|
|
||||||
"version": "1.15.8",
|
|
||||||
"resolved": "https://registry.npmjs.org/@types/sortablejs/-/sortablejs-1.15.8.tgz",
|
|
||||||
"integrity": "sha512-b79830lW+RZfwaztgs1aVPgbasJ8e7AXtZYHTELNXZPsERt4ymJdjV4OccDbHQAvHrCcFpbF78jkm0R6h/pZVg==",
|
|
||||||
"license": "MIT"
|
|
||||||
},
|
|
||||||
"node_modules/@types/tough-cookie": {
|
"node_modules/@types/tough-cookie": {
|
||||||
"version": "4.0.5",
|
"version": "4.0.5",
|
||||||
"resolved": "https://registry.npmjs.org/@types/tough-cookie/-/tough-cookie-4.0.5.tgz",
|
"resolved": "https://registry.npmjs.org/@types/tough-cookie/-/tough-cookie-4.0.5.tgz",
|
||||||
|
|
@ -1862,17 +1855,17 @@
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/@typescript-eslint/eslint-plugin": {
|
"node_modules/@typescript-eslint/eslint-plugin": {
|
||||||
"version": "7.18.0",
|
"version": "7.13.0",
|
||||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-7.18.0.tgz",
|
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-7.13.0.tgz",
|
||||||
"integrity": "sha512-94EQTWZ40mzBc42ATNIBimBEDltSJ9RQHCC8vc/PDbxi4k8dVwUAv4o98dk50M1zB+JGFxp43FP7f8+FP8R6Sw==",
|
"integrity": "sha512-FX1X6AF0w8MdVFLSdqwqN/me2hyhuQg4ykN6ZpVhh1ij/80pTvDKclX1sZB9iqex8SjQfVhwMKs3JtnnMLzG9w==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@eslint-community/regexpp": "^4.10.0",
|
"@eslint-community/regexpp": "^4.10.0",
|
||||||
"@typescript-eslint/scope-manager": "7.18.0",
|
"@typescript-eslint/scope-manager": "7.13.0",
|
||||||
"@typescript-eslint/type-utils": "7.18.0",
|
"@typescript-eslint/type-utils": "7.13.0",
|
||||||
"@typescript-eslint/utils": "7.18.0",
|
"@typescript-eslint/utils": "7.13.0",
|
||||||
"@typescript-eslint/visitor-keys": "7.18.0",
|
"@typescript-eslint/visitor-keys": "7.13.0",
|
||||||
"graphemer": "^1.4.0",
|
"graphemer": "^1.4.0",
|
||||||
"ignore": "^5.3.1",
|
"ignore": "^5.3.1",
|
||||||
"natural-compare": "^1.4.0",
|
"natural-compare": "^1.4.0",
|
||||||
|
|
@ -1896,16 +1889,16 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@typescript-eslint/parser": {
|
"node_modules/@typescript-eslint/parser": {
|
||||||
"version": "7.18.0",
|
"version": "7.13.0",
|
||||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-7.18.0.tgz",
|
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-7.13.0.tgz",
|
||||||
"integrity": "sha512-4Z+L8I2OqhZV8qA132M4wNL30ypZGYOQVBfMgxDH/K5UX0PNqTu1c6za9ST5r9+tavvHiTWmBnKzpCJ/GlVFtg==",
|
"integrity": "sha512-EjMfl69KOS9awXXe83iRN7oIEXy9yYdqWfqdrFAYAAr6syP8eLEFI7ZE4939antx2mNgPRW/o1ybm2SFYkbTVA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "BSD-2-Clause",
|
"license": "BSD-2-Clause",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@typescript-eslint/scope-manager": "7.18.0",
|
"@typescript-eslint/scope-manager": "7.13.0",
|
||||||
"@typescript-eslint/types": "7.18.0",
|
"@typescript-eslint/types": "7.13.0",
|
||||||
"@typescript-eslint/typescript-estree": "7.18.0",
|
"@typescript-eslint/typescript-estree": "7.13.0",
|
||||||
"@typescript-eslint/visitor-keys": "7.18.0",
|
"@typescript-eslint/visitor-keys": "7.13.0",
|
||||||
"debug": "^4.3.4"
|
"debug": "^4.3.4"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
|
|
@ -1925,14 +1918,14 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@typescript-eslint/scope-manager": {
|
"node_modules/@typescript-eslint/scope-manager": {
|
||||||
"version": "7.18.0",
|
"version": "7.13.0",
|
||||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-7.18.0.tgz",
|
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-7.13.0.tgz",
|
||||||
"integrity": "sha512-jjhdIE/FPF2B7Z1uzc6i3oWKbGcHb87Qw7AWj6jmEqNOfDFbJWtjt/XfwCpvNkpGWlcJaog5vTR+VV8+w9JflA==",
|
"integrity": "sha512-ZrMCe1R6a01T94ilV13egvcnvVJ1pxShkE0+NDjDzH4nvG1wXpwsVI5bZCvE7AEDH1mXEx5tJSVR68bLgG7Dng==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@typescript-eslint/types": "7.18.0",
|
"@typescript-eslint/types": "7.13.0",
|
||||||
"@typescript-eslint/visitor-keys": "7.18.0"
|
"@typescript-eslint/visitor-keys": "7.13.0"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": "^18.18.0 || >=20.0.0"
|
"node": "^18.18.0 || >=20.0.0"
|
||||||
|
|
@ -1943,14 +1936,14 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@typescript-eslint/type-utils": {
|
"node_modules/@typescript-eslint/type-utils": {
|
||||||
"version": "7.18.0",
|
"version": "7.13.0",
|
||||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-7.18.0.tgz",
|
"resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-7.13.0.tgz",
|
||||||
"integrity": "sha512-XL0FJXuCLaDuX2sYqZUUSOJ2sG5/i1AAze+axqmLnSkNEVMVYLF+cbwlB2w8D1tinFuSikHmFta+P+HOofrLeA==",
|
"integrity": "sha512-xMEtMzxq9eRkZy48XuxlBFzpVMDurUAfDu5Rz16GouAtXm0TaAoTFzqWUFPPuQYXI/CDaH/Bgx/fk/84t/Bc9A==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@typescript-eslint/typescript-estree": "7.18.0",
|
"@typescript-eslint/typescript-estree": "7.13.0",
|
||||||
"@typescript-eslint/utils": "7.18.0",
|
"@typescript-eslint/utils": "7.13.0",
|
||||||
"debug": "^4.3.4",
|
"debug": "^4.3.4",
|
||||||
"ts-api-utils": "^1.3.0"
|
"ts-api-utils": "^1.3.0"
|
||||||
},
|
},
|
||||||
|
|
@ -1971,9 +1964,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@typescript-eslint/types": {
|
"node_modules/@typescript-eslint/types": {
|
||||||
"version": "7.18.0",
|
"version": "7.13.0",
|
||||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-7.18.0.tgz",
|
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-7.13.0.tgz",
|
||||||
"integrity": "sha512-iZqi+Ds1y4EDYUtlOOC+aUmxnE9xS/yCigkjA7XpTKV6nCBd3Hp/PRGGmdwnfkV2ThMyYldP1wRpm/id99spTQ==",
|
"integrity": "sha512-QWuwm9wcGMAuTsxP+qz6LBBd3Uq8I5Nv8xb0mk54jmNoCyDspnMvVsOxI6IsMmway5d1S9Su2+sCKv1st2l6eA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
|
|
@ -1985,14 +1978,14 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@typescript-eslint/typescript-estree": {
|
"node_modules/@typescript-eslint/typescript-estree": {
|
||||||
"version": "7.18.0",
|
"version": "7.13.0",
|
||||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-7.18.0.tgz",
|
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-7.13.0.tgz",
|
||||||
"integrity": "sha512-aP1v/BSPnnyhMHts8cf1qQ6Q1IFwwRvAQGRvBFkWlo3/lH29OXA3Pts+c10nxRxIBrDnoMqzhgdwVe5f2D6OzA==",
|
"integrity": "sha512-cAvBvUoobaoIcoqox1YatXOnSl3gx92rCZoMRPzMNisDiM12siGilSM4+dJAekuuHTibI2hVC2fYK79iSFvWjw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "BSD-2-Clause",
|
"license": "BSD-2-Clause",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@typescript-eslint/types": "7.18.0",
|
"@typescript-eslint/types": "7.13.0",
|
||||||
"@typescript-eslint/visitor-keys": "7.18.0",
|
"@typescript-eslint/visitor-keys": "7.13.0",
|
||||||
"debug": "^4.3.4",
|
"debug": "^4.3.4",
|
||||||
"globby": "^11.1.0",
|
"globby": "^11.1.0",
|
||||||
"is-glob": "^4.0.3",
|
"is-glob": "^4.0.3",
|
||||||
|
|
@ -2014,16 +2007,16 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@typescript-eslint/utils": {
|
"node_modules/@typescript-eslint/utils": {
|
||||||
"version": "7.18.0",
|
"version": "7.13.0",
|
||||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-7.18.0.tgz",
|
"resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-7.13.0.tgz",
|
||||||
"integrity": "sha512-kK0/rNa2j74XuHVcoCZxdFBMF+aq/vH83CXAOHieC+2Gis4mF8jJXT5eAfyD3K0sAxtPuwxaIOIOvhwzVDt/kw==",
|
"integrity": "sha512-jceD8RgdKORVnB4Y6BqasfIkFhl4pajB1wVxrF4akxD2QPM8GNYjgGwEzYS+437ewlqqrg7Dw+6dhdpjMpeBFQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@eslint-community/eslint-utils": "^4.4.0",
|
"@eslint-community/eslint-utils": "^4.4.0",
|
||||||
"@typescript-eslint/scope-manager": "7.18.0",
|
"@typescript-eslint/scope-manager": "7.13.0",
|
||||||
"@typescript-eslint/types": "7.18.0",
|
"@typescript-eslint/types": "7.13.0",
|
||||||
"@typescript-eslint/typescript-estree": "7.18.0"
|
"@typescript-eslint/typescript-estree": "7.13.0"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": "^18.18.0 || >=20.0.0"
|
"node": "^18.18.0 || >=20.0.0"
|
||||||
|
|
@ -2037,13 +2030,13 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@typescript-eslint/visitor-keys": {
|
"node_modules/@typescript-eslint/visitor-keys": {
|
||||||
"version": "7.18.0",
|
"version": "7.13.0",
|
||||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-7.18.0.tgz",
|
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-7.13.0.tgz",
|
||||||
"integrity": "sha512-cDF0/Gf81QpY3xYyJKDV14Zwdmid5+uuENhjH2EqFaF0ni+yAyq/LzMaIJdhNJXZI7uLzwIlA+V7oWoyn6Curg==",
|
"integrity": "sha512-nxn+dozQx+MK61nn/JP+M4eCkHDSxSLDpgE3WcQo0+fkjEolnaB5jswvIKC4K56By8MMgIho7f1PVxERHEo8rw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@typescript-eslint/types": "7.18.0",
|
"@typescript-eslint/types": "7.13.0",
|
||||||
"eslint-visitor-keys": "^3.4.3"
|
"eslint-visitor-keys": "^3.4.3"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
|
|
@ -2760,9 +2753,9 @@
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/axios": {
|
"node_modules/axios": {
|
||||||
"version": "1.7.5",
|
"version": "1.7.3",
|
||||||
"resolved": "https://registry.npmjs.org/axios/-/axios-1.7.5.tgz",
|
"resolved": "https://registry.npmjs.org/axios/-/axios-1.7.3.tgz",
|
||||||
"integrity": "sha512-fZu86yCo+svH3uqJ/yTdQ0QHpQu5oL+/QE+QPSv6BZSkDAoky9vytxp7u5qk83OJFS3kEBcesWni9WTZAv3tSw==",
|
"integrity": "sha512-Ar7ND9pU99eJ9GpoGQKhKf58GpUOgnzuaB7ueNQ5BMi0p+LZ5oaEnfF999fAArcTIBwXTCHAmGcHOZJaWPq9Nw==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"follow-redirects": "^1.15.6",
|
"follow-redirects": "^1.15.6",
|
||||||
|
|
@ -6452,23 +6445,6 @@
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/vue-draggable-plus": {
|
|
||||||
"version": "0.5.3",
|
|
||||||
"resolved": "https://registry.npmjs.org/vue-draggable-plus/-/vue-draggable-plus-0.5.3.tgz",
|
|
||||||
"integrity": "sha512-dwKDzZ8io3y7k2iuIwVwiGrdiq5C0S7Et7nt5Gz5KjpBS9MtZGFP+L4FJPWGSLYleOT8HmVuCXTZYjGet7wC0g==",
|
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
|
||||||
"@types/sortablejs": "^1.15.8"
|
|
||||||
},
|
|
||||||
"peerDependencies": {
|
|
||||||
"@types/sortablejs": "^1.15.0"
|
|
||||||
},
|
|
||||||
"peerDependenciesMeta": {
|
|
||||||
"@vue/composition-api": {
|
|
||||||
"optional": true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/vue-eslint-parser": {
|
"node_modules/vue-eslint-parser": {
|
||||||
"version": "9.4.3",
|
"version": "9.4.3",
|
||||||
"resolved": "https://registry.npmjs.org/vue-eslint-parser/-/vue-eslint-parser-9.4.3.tgz",
|
"resolved": "https://registry.npmjs.org/vue-eslint-parser/-/vue-eslint-parser-9.4.3.tgz",
|
||||||
|
|
|
||||||
|
|
@ -23,7 +23,6 @@
|
||||||
"bootstrap": "^5.3.3",
|
"bootstrap": "^5.3.3",
|
||||||
"pinia": "^2.1.7",
|
"pinia": "^2.1.7",
|
||||||
"vue": "^3.4.21",
|
"vue": "^3.4.21",
|
||||||
"vue-draggable-plus": "^0.5.3",
|
|
||||||
"vue-i18n": "^9.13.1",
|
"vue-i18n": "^9.13.1",
|
||||||
"vue-router": "^4.3.0"
|
"vue-router": "^4.3.0"
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
import { provide, ref } from 'vue';
|
import { provide, ref } from 'vue';
|
||||||
import { RouterView } from 'vue-router';
|
import { RouterView } from 'vue-router';
|
||||||
|
|
||||||
import { infoModalShowFnKey, navbarKey } from './services/UtilService';
|
import { infoModalShowFnKey } from './services/UtilService';
|
||||||
|
|
||||||
import NavBar from '@/components/blocks/NavBar.vue';
|
import NavBar from '@/components/blocks/NavBar.vue';
|
||||||
import GenericInfoModal from '@/components/modals/GenericInfoModal.vue';
|
import GenericInfoModal from '@/components/modals/GenericInfoModal.vue';
|
||||||
|
|
@ -30,13 +30,11 @@ function showInfoModal( title: string, text: string ): void {
|
||||||
|
|
||||||
provide( infoModalShowFnKey, showInfoModal );
|
provide( infoModalShowFnKey, showInfoModal );
|
||||||
|
|
||||||
const navbar = ref<InstanceType<typeof NavBar> | undefined>(undefined);
|
|
||||||
provide( navbarKey, navbar);
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="vh-100 overflow-y-scroll overflow-x-hidden">
|
<div class="vh-100 overflow-y-scroll overflow-x-hidden">
|
||||||
<NavBar ref="navbar" :userLoading="userLoading" />
|
<NavBar :userLoading="userLoading" />
|
||||||
|
|
||||||
<RouterView />
|
<RouterView />
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -4,8 +4,4 @@
|
||||||
|
|
||||||
.pointer {
|
.pointer {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
|
||||||
|
|
||||||
.cursor-move{
|
|
||||||
cursor: move;
|
|
||||||
}
|
}
|
||||||
|
|
@ -46,15 +46,11 @@ $body-secondary-bg: $light-accented;
|
||||||
$dropdown-link-hover-bg: $dark-accented;
|
$dropdown-link-hover-bg: $dark-accented;
|
||||||
|
|
||||||
$modal-fade-transform: scale(.75);
|
$modal-fade-transform: scale(.75);
|
||||||
$breadcrumb-divider: quote(">");
|
|
||||||
|
|
||||||
// 3. Include remainder of required Bootstrap stylesheets (including any separate color mode stylesheets)
|
// 3. Include remainder of required Bootstrap stylesheets (including any separate color mode stylesheets)
|
||||||
@import "bootstrap/scss/variables";
|
@import "bootstrap/scss/variables";
|
||||||
@import "bootstrap/scss/variables-dark";
|
@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 */
|
/* Bootstrap Color Map adjustments */
|
||||||
$custom-colors: (
|
$custom-colors: (
|
||||||
|
|
@ -93,36 +89,6 @@ $utilities: map-merge(
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
"rounded-bottom-end": (
|
|
||||||
property: border-bottom-right-radius,
|
|
||||||
class: rounded-bottom-end,
|
|
||||||
values: (
|
|
||||||
null: var(--#{$prefix}border-radius),
|
|
||||||
0: 0,
|
|
||||||
1: var(--#{$prefix}border-radius-sm),
|
|
||||||
2: var(--#{$prefix}border-radius),
|
|
||||||
3: var(--#{$prefix}border-radius-lg),
|
|
||||||
4: var(--#{$prefix}border-radius-xl),
|
|
||||||
5: var(--#{$prefix}border-radius-xxl),
|
|
||||||
circle: 50%,
|
|
||||||
pill: var(--#{$prefix}border-radius-pill)
|
|
||||||
)
|
|
||||||
),
|
|
||||||
"rounded-top-end": (
|
|
||||||
property: border-top-right-radius,
|
|
||||||
class: rounded-top-end,
|
|
||||||
values: (
|
|
||||||
null: var(--#{$prefix}border-radius),
|
|
||||||
0: 0,
|
|
||||||
1: var(--#{$prefix}border-radius-sm),
|
|
||||||
2: var(--#{$prefix}border-radius),
|
|
||||||
3: var(--#{$prefix}border-radius-lg),
|
|
||||||
4: var(--#{$prefix}border-radius-xl),
|
|
||||||
5: var(--#{$prefix}border-radius-xxl),
|
|
||||||
circle: 50%,
|
|
||||||
pill: var(--#{$prefix}border-radius-pill)
|
|
||||||
)
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,97 +0,0 @@
|
||||||
<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="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 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>
|
|
||||||
|
|
||||||
<style lang="css" scoped>
|
|
||||||
.object-contain{
|
|
||||||
object-fit: contain;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|
@ -1,10 +1,6 @@
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref } from 'vue';
|
import { ref } from 'vue';
|
||||||
import { useRouter } from 'vue-router';
|
|
||||||
import { FontAwesomeIcon } from '@fortawesome/vue-fontawesome';
|
|
||||||
|
|
||||||
const router = useRouter();
|
|
||||||
|
|
||||||
const boards = ref([{
|
const boards = ref([{
|
||||||
id: 1,
|
id: 1,
|
||||||
|
|
@ -19,10 +15,6 @@ const boards = ref([{
|
||||||
id: 4,
|
id: 4,
|
||||||
boardName: "Mocka Board 2",
|
boardName: "Mocka Board 2",
|
||||||
}]);
|
}]);
|
||||||
|
|
||||||
function createNewBoard(){
|
|
||||||
router.push( { name: 'create' } );
|
|
||||||
}
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
|
@ -52,11 +44,5 @@ function createNewBoard(){
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<div class="col-4 mb-3">
|
|
||||||
<button class="btn btn-outline-primary w-100 h-100 d-flex flex-column justify-content-center" @click="createNewBoard">
|
|
||||||
Create new Board
|
|
||||||
<FontAwesomeIcon :icon="['fas', 'plus']" size="2x"/>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
@ -1,68 +0,0 @@
|
||||||
<script setup lang="ts">
|
|
||||||
import type { Board } from '@/models/board/Board';
|
|
||||||
import { useI18n } from 'vue-i18n';
|
|
||||||
|
|
||||||
const { t } = useI18n();
|
|
||||||
|
|
||||||
const props = defineProps<{
|
|
||||||
board: Board,
|
|
||||||
}>();
|
|
||||||
|
|
||||||
const emit = defineEmits<{
|
|
||||||
categorySelected: [index: number],
|
|
||||||
boardEntrySelected: [cIndex: number, bEIndex: number],
|
|
||||||
}>();
|
|
||||||
|
|
||||||
function categorySelected(cIndex: number){
|
|
||||||
emit("categorySelected", cIndex);
|
|
||||||
}
|
|
||||||
function boardEntrySelected(cIndex: number, bEIndex: number){
|
|
||||||
emit("boardEntrySelected", cIndex, bEIndex);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<div class="mx-3 h-100 d-flex flex-column">
|
|
||||||
<div class="row">
|
|
||||||
<div class="col text-center p-3">
|
|
||||||
<h2>
|
|
||||||
{{ board.name }}
|
|
||||||
</h2>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="row h-100">
|
|
||||||
<template v-for="(category, categoryIndex) in props.board.categories " :key="category.name">
|
|
||||||
<div class="col pb-2">
|
|
||||||
<div class="d-flex flex-column h-100">
|
|
||||||
<button class="flex-fill board-card-max-height card bg-primary w-100 my-1" @click="categorySelected(categoryIndex)" :title="board.categories[categoryIndex].description">
|
|
||||||
<div class="card-body d-flex align-items-center justify-content-center">
|
|
||||||
{{ category.name }}
|
|
||||||
</div>
|
|
||||||
</button>
|
|
||||||
<template v-for=" (boardEntry, boardEntryIndex) in category.boardEntries " :key="boardEntry.name">
|
|
||||||
<button class="flex-fill board-card-max-height card bg-body-secondary w-100 my-1" @click="boardEntrySelected(categoryIndex, boardEntryIndex)">
|
|
||||||
<div class="card-body d-flex align-items-center justify-content-center">
|
|
||||||
<template v-if="board.pointsAreTitle">
|
|
||||||
{{ boardEntry.points }}
|
|
||||||
</template>
|
|
||||||
<template v-else>
|
|
||||||
{{ boardEntry.name }}
|
|
||||||
</template>
|
|
||||||
</div>
|
|
||||||
</button>
|
|
||||||
</template>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<style lang="css" scoped>
|
|
||||||
.board-card-max-height{
|
|
||||||
max-height: 20%;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|
@ -1,93 +0,0 @@
|
||||||
<script setup lang="ts">
|
|
||||||
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 } );
|
|
||||||
|
|
||||||
function editBoard(){
|
|
||||||
emit("editBoard");
|
|
||||||
}
|
|
||||||
function editCategory(cIndex: number){
|
|
||||||
emit("editCategory", cIndex);
|
|
||||||
}
|
|
||||||
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-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">
|
|
||||||
<div class="flex-grow-1 me-1">
|
|
||||||
<button class="btn btn-primary w-100">
|
|
||||||
Save
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
<div class="flex-grow-1 ms-1">
|
|
||||||
<button class="btn btn-danger w-100">
|
|
||||||
Exit
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<style lang="css" scoped>
|
|
||||||
.draggable-ghost {
|
|
||||||
opacity: .75;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|
@ -1,201 +0,0 @@
|
||||||
<script setup lang="ts">
|
|
||||||
import { computed, inject, ref } from 'vue';
|
|
||||||
import { useI18n } from 'vue-i18n';
|
|
||||||
import { FontAwesomeIcon } from '@fortawesome/vue-fontawesome';
|
|
||||||
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();
|
|
||||||
|
|
||||||
const board = defineModel<Board>( { required: true } );
|
|
||||||
|
|
||||||
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 newQuestionText = ref( '' );
|
|
||||||
const newQuestionType = ref<QuestionType | null>( (questionTypes ?? [null])[0] );
|
|
||||||
function addQuestion() {
|
|
||||||
if( boardEntry.value.questions.length >= 10 || newQuestionType.value === null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const newQuestion = new Question(newQuestionText.value, newQuestionType.value, boardEntry.value );
|
|
||||||
boardEntry.value.questions.push( newQuestion );
|
|
||||||
}
|
|
||||||
|
|
||||||
function deleteQuestion( index: number ) {
|
|
||||||
boardEntry.value.questions.splice( index, 1 );
|
|
||||||
}
|
|
||||||
|
|
||||||
function moveQuestionUp( index: number ) {
|
|
||||||
if( index === 0 || boardEntry.value.questions.length <= 1 ) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const tmp = boardEntry.value.questions[index];
|
|
||||||
boardEntry.value.questions[index] = boardEntry.value.questions[index - 1];
|
|
||||||
boardEntry.value.questions[index - 1] = tmp;
|
|
||||||
}
|
|
||||||
|
|
||||||
function moveQuestionDown( index: number ) {
|
|
||||||
if( index === boardEntry.value.questions.length - 1 || boardEntry.value.questions.length <= 1 ) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const tmp = boardEntry.value.questions[index];
|
|
||||||
boardEntry.value.questions[index] = boardEntry.value.questions[index + 1];
|
|
||||||
boardEntry.value.questions[index + 1] = tmp;
|
|
||||||
}
|
|
||||||
|
|
||||||
function openBoard() {
|
|
||||||
emit("editBoard");
|
|
||||||
}
|
|
||||||
function openCategory(categoryIndex: number) {
|
|
||||||
emit("editCategory", categoryIndex);
|
|
||||||
}
|
|
||||||
function openQuestion(categoryIndex: number, boardEntryIndex: number, questionIndex: number) {
|
|
||||||
emit("editQuestion", categoryIndex, boardEntryIndex, questionIndex);
|
|
||||||
}
|
|
||||||
</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="openCategory(props.categoryIndex)">
|
|
||||||
<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 active text-truncate" aria-current="page">
|
|
||||||
{{ boardEntry.name }}
|
|
||||||
</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.boardentry.infos" ) }}</h2>
|
|
||||||
</div>
|
|
||||||
<label for="category-name">{{ t( 'board.boardentry.name' ) }}</label>
|
|
||||||
<input type="text" id="category-name" class="form-control mb-2" v-model="boardEntry.name" :placeholder="t( 'board.boardentry.name' )">
|
|
||||||
<label for="board-entry-points">{{ t( 'board.boardentry.points' ) }}</label>
|
|
||||||
<input type="text" id="board-entry-points" class="form-control" v-model="boardEntry.points" :placeholder="t( 'board.boardentry.points' )">
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="row mt-3">
|
|
||||||
<div class="col">
|
|
||||||
<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>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<style lang="css" scoped>
|
|
||||||
.draggable-ghost {
|
|
||||||
opacity: .75;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|
@ -1,131 +0,0 @@
|
||||||
<script setup lang="ts">
|
|
||||||
import type { Board } from '@/models/board/Board';
|
|
||||||
import { Category } from '@/models/board/Category';
|
|
||||||
import { ref } from 'vue';
|
|
||||||
import { useI18n } from 'vue-i18n';
|
|
||||||
import { VueDraggable } from 'vue-draggable-plus';
|
|
||||||
import { FontAwesomeIcon } from '@fortawesome/vue-fontawesome';
|
|
||||||
|
|
||||||
const { t } = useI18n();
|
|
||||||
|
|
||||||
const emit = defineEmits<{
|
|
||||||
editCategory: [index: number],
|
|
||||||
}>()
|
|
||||||
|
|
||||||
const board = defineModel<Board>( { required: true } );
|
|
||||||
|
|
||||||
const newCategoryName = ref( '' );
|
|
||||||
function addCategory() {
|
|
||||||
if( board.value.categories.length >= 12 ) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const newCategory = new Category( newCategoryName.value, '', board.value );
|
|
||||||
board.value.categories.push( newCategory );
|
|
||||||
}
|
|
||||||
|
|
||||||
function deleteCategory( index: number ) {
|
|
||||||
board.value.categories.splice( index, 1 );
|
|
||||||
}
|
|
||||||
|
|
||||||
function moveCategoryUp( index: number ) {
|
|
||||||
if( index === 0 || board.value.categories.length <= 1 ) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const tmp = board.value.categories[index];
|
|
||||||
board.value.categories[index] = board.value.categories[index - 1];
|
|
||||||
board.value.categories[index - 1] = tmp;
|
|
||||||
}
|
|
||||||
|
|
||||||
function moveCategoryDown( index: number ) {
|
|
||||||
if( index === board.value.categories.length - 1 || board.value.categories.length <= 1 ) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const tmp = board.value.categories[index];
|
|
||||||
board.value.categories[index] = board.value.categories[index + 1];
|
|
||||||
board.value.categories[index + 1] = tmp;
|
|
||||||
}
|
|
||||||
|
|
||||||
function openCategory( index: number ) {
|
|
||||||
emit("editCategory", index);
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<div class="d-flex flex-column h-100">
|
|
||||||
<div class="flex-grow-1 p-2">
|
|
||||||
<div class="row">
|
|
||||||
<div class="col">
|
|
||||||
<h2 class="border-bottom border-3 border-primary">{{ t( "board.infos" ) }}</h2>
|
|
||||||
<label for="board-name">{{ t( 'board.name' ) }}</label>
|
|
||||||
<input type="text" id="board-name" class="form-control" v-model="board.name" :placeholder="t( 'board.name' )">
|
|
||||||
<div class="form-check mt-2">
|
|
||||||
<input class="form-check-input" type="checkbox" id="points-for-title" v-model="board.pointsAreTitle">
|
|
||||||
<label for="points-for-title" class="form-check-label">{{ t( 'board.options.pointsForTitle' ) }}</label>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="row mt-3">
|
|
||||||
<div class="col">
|
|
||||||
<h4>{{ t( "board.category.label", 2 ) }}</h4>
|
|
||||||
<template v-if=" board.categories.length === 0 ">
|
|
||||||
{{ t( "board.category.label", 0 ) }}
|
|
||||||
</template>
|
|
||||||
<template v-else>
|
|
||||||
<VueDraggable v-model="board.categories" item-key="id" :animation="150" ghost-class="draggable-ghost"
|
|
||||||
tag="ul" class="list-group" handle=".drag-handle">
|
|
||||||
<template v-for="( category, categoryIndex) of board.categories" :key="category.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 g-3 justify-content-start">
|
|
||||||
<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>
|
|
||||||
<input type="text" :id="`category-name-${categoryIndex}`" v-model="category.name"
|
|
||||||
class="form-control rounded-0 h-100">
|
|
||||||
</div>
|
|
||||||
<div class="d-flex">
|
|
||||||
<button class="btn btn-primary rounded-0" @click="openCategory( categoryIndex )">
|
|
||||||
<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="moveCategoryUp( categoryIndex )"
|
|
||||||
:disabled="categoryIndex === 0">
|
|
||||||
<FontAwesomeIcon :icon="['fas', 'angle-up']" size="xs" />
|
|
||||||
</button>
|
|
||||||
<button class="btn btn-primary rounded-0 py-0 lh-sm" @click="moveCategoryDown( categoryIndex )"
|
|
||||||
:disabled="categoryIndex === board.categories.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': categoryIndex === board.categories.length - 1 }, { 'rounded-top-end': categoryIndex === 0 }]"
|
|
||||||
@click="deleteCategory( categoryIndex )">
|
|
||||||
<FontAwesomeIcon :icon="['fas', 'trash']" size="sm" />
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</li>
|
|
||||||
</template>
|
|
||||||
</VueDraggable>
|
|
||||||
</template>
|
|
||||||
<label class="mt-2" for="new-category-name">{{ t( "board.category.add" ) }}</label>
|
|
||||||
<div class="input-group mb-3">
|
|
||||||
<input type="text" id="new-category-name" class="form-control" v-model="newCategoryName"
|
|
||||||
:placeholder="t( 'board.category.name' )" @keyup.enter="addCategory">
|
|
||||||
<button class="btn btn-primary" @click="addCategory">
|
|
||||||
<FontAwesomeIcon :icon="['fas', 'plus']" />
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<style lang="css" scoped>
|
|
||||||
.draggable-ghost {
|
|
||||||
opacity: .75;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|
@ -1,148 +0,0 @@
|
||||||
<script setup lang="ts">
|
|
||||||
import type { Board } from '@/models/board/Board';
|
|
||||||
import { ref } from 'vue';
|
|
||||||
import { useI18n } from 'vue-i18n';
|
|
||||||
import { VueDraggable } from 'vue-draggable-plus';
|
|
||||||
import { FontAwesomeIcon } from '@fortawesome/vue-fontawesome';
|
|
||||||
import { BoardEntry } from '@/models/board/BoardEntry';
|
|
||||||
import { Answer } from '@/models/board/Answer';
|
|
||||||
|
|
||||||
const { t } = useI18n();
|
|
||||||
|
|
||||||
const board = defineModel<Board>( { required: true } );
|
|
||||||
|
|
||||||
const props = defineProps<{
|
|
||||||
categoryIndex: number,
|
|
||||||
}>();
|
|
||||||
|
|
||||||
const emit = defineEmits<{
|
|
||||||
editBoardEntry: [categoryIndex: number, boardEntryIndex: number],
|
|
||||||
editBoard: [],
|
|
||||||
}>()
|
|
||||||
|
|
||||||
|
|
||||||
const newBoardEntryName = ref( '' );
|
|
||||||
function addBoardEntry() {
|
|
||||||
if( board.value.categories[props.categoryIndex].boardEntries.length >= 10 ) {
|
|
||||||
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 );
|
|
||||||
}
|
|
||||||
|
|
||||||
function deleteBoardEntry( index: number ) {
|
|
||||||
board.value.categories[props.categoryIndex].boardEntries.splice( index, 1 );
|
|
||||||
}
|
|
||||||
|
|
||||||
function moveBoardEntryUp( index: number ) {
|
|
||||||
if( index === 0 || board.value.categories[props.categoryIndex].boardEntries.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;
|
|
||||||
}
|
|
||||||
|
|
||||||
function moveBoardEntryDown( index: number ) {
|
|
||||||
if( index === board.value.categories[props.categoryIndex].boardEntries.length - 1 || board.value.categories[props.categoryIndex].boardEntries.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;
|
|
||||||
}
|
|
||||||
|
|
||||||
function openBoardEntry( index: number ) {
|
|
||||||
emit("editBoardEntry", props.categoryIndex, index);
|
|
||||||
}
|
|
||||||
function openBoard() {
|
|
||||||
emit("editBoard");
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<div class="d-flex flex-column h-100">
|
|
||||||
<div class="flex-grow-1 p-2">
|
|
||||||
<div class="row">
|
|
||||||
<div class="col">
|
|
||||||
<div class="d-flex border-bottom border-3 border-primary align-items-center mb-2">
|
|
||||||
<button class="btn btn-sm btn-outline-primary mb-1" :title="t('board.category.back')" @click="openBoard">
|
|
||||||
<FontAwesomeIcon :icon="['fas', 'angle-left']"/>
|
|
||||||
</button>
|
|
||||||
<h2 class="flex-grow-1 ms-2">{{ t( "board.category.infos" ) }}</h2>
|
|
||||||
</div>
|
|
||||||
<label for="category-name">{{ t( 'board.category.name' ) }}</label>
|
|
||||||
<input type="text" id="category-name" class="form-control mb-2" v-model="board.categories[props.categoryIndex].name" :placeholder="t( 'board.category.name' )">
|
|
||||||
<label for="category-description">{{ t( 'board.category.description' ) }}</label>
|
|
||||||
<textarea id="category-description" class="form-control" v-model="board.categories[props.categoryIndex].description" :placeholder="t( 'board.category.description' )">
|
|
||||||
</textarea>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="row mt-3">
|
|
||||||
<div class="col">
|
|
||||||
<h4>{{ t( "board.boardentry.label", 2 ) }}</h4>
|
|
||||||
<template v-if=" board.categories[props.categoryIndex].boardEntries.length === 0 ">
|
|
||||||
<p>
|
|
||||||
{{ t( "board.boardentry.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="(boardEntry, boardEntryIndex) of board.categories[props.categoryIndex].boardEntries" :key="boardEntry.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 g-3 justify-content-start">
|
|
||||||
<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>
|
|
||||||
<input type="text" :id="`category-name-${boardEntryIndex}`" v-model="boardEntry.name"
|
|
||||||
class="form-control rounded-0 h-100">
|
|
||||||
</div>
|
|
||||||
<div class="d-flex">
|
|
||||||
<button class="btn btn-primary rounded-0" @click="openBoardEntry( boardEntryIndex )">
|
|
||||||
<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="moveBoardEntryUp( boardEntryIndex )"
|
|
||||||
:disabled="boardEntryIndex === 0">
|
|
||||||
<FontAwesomeIcon :icon="['fas', 'angle-up']" size="xs" />
|
|
||||||
</button>
|
|
||||||
<button class="btn btn-primary rounded-0 py-0 lh-sm" @click="moveBoardEntryDown( boardEntryIndex )"
|
|
||||||
:disabled="boardEntryIndex === board.categories[props.categoryIndex].boardEntries.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': boardEntryIndex === board.categories[props.categoryIndex].boardEntries.length - 1 }, { 'rounded-top-end': boardEntryIndex === 0 }]"
|
|
||||||
@click="deleteBoardEntry( boardEntryIndex )">
|
|
||||||
<FontAwesomeIcon :icon="['fas', 'trash']" size="sm" />
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</li>
|
|
||||||
</template>
|
|
||||||
</VueDraggable>
|
|
||||||
</template>
|
|
||||||
<label class="mt-2" for="new-category-name">{{ t( "board.boardentry.add" ) }}</label>
|
|
||||||
<div class="input-group mb-3">
|
|
||||||
<input type="text" id="new-category-name" class="form-control" v-model="newBoardEntryName"
|
|
||||||
:placeholder="t( 'board.boardentry.name' )" @keyup.enter="addBoardEntry">
|
|
||||||
<button class="btn btn-primary" @click="addBoardEntry">
|
|
||||||
<FontAwesomeIcon :icon="['fas', 'plus']" />
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<style lang="css" scoped>
|
|
||||||
.draggable-ghost {
|
|
||||||
opacity: .75;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|
@ -1,129 +0,0 @@
|
||||||
<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>
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { onMounted, ref } from 'vue';
|
import { ref } from 'vue';
|
||||||
import { RouterLink, useRoute, useRouter } from 'vue-router';
|
import { RouterLink, useRoute, useRouter } from 'vue-router';
|
||||||
import { useI18n } from 'vue-i18n';
|
import { useI18n } from 'vue-i18n';
|
||||||
|
|
||||||
|
|
@ -8,7 +8,6 @@ import ThemeChanger from '@/components/blocks/ThemeChanger.vue';
|
||||||
import LocaleChanger from '@/components/blocks/LocaleChanger.vue';
|
import LocaleChanger from '@/components/blocks/LocaleChanger.vue';
|
||||||
import { useUserStore } from '@/stores/UserStore';
|
import { useUserStore } from '@/stores/UserStore';
|
||||||
import { authService } from '@/services/AuthService';
|
import { authService } from '@/services/AuthService';
|
||||||
import { FontAwesomeIcon } from '@fortawesome/vue-fontawesome';
|
|
||||||
|
|
||||||
const navNames = {
|
const navNames = {
|
||||||
HOME: "home",
|
HOME: "home",
|
||||||
|
|
@ -22,15 +21,6 @@ const route = useRoute();
|
||||||
|
|
||||||
const userStore = useUserStore();
|
const userStore = useUserStore();
|
||||||
|
|
||||||
const navbar = ref<HTMLElement>();
|
|
||||||
defineExpose({
|
|
||||||
navElement: navbar
|
|
||||||
});
|
|
||||||
|
|
||||||
onMounted( () => {
|
|
||||||
console.log(typeof navbar.value);
|
|
||||||
})
|
|
||||||
|
|
||||||
const isActiveNav = ( navName: string ) => {
|
const isActiveNav = ( navName: string ) => {
|
||||||
switch( navName ) {
|
switch( navName ) {
|
||||||
case navNames.HOME:
|
case navNames.HOME:
|
||||||
|
|
@ -64,10 +54,10 @@ userStore.userCheckPromise
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<nav ref="navbar" id="navbar-main" class="navbar navbar-expand-lg bg-dark-accented">
|
<nav id="navbar-main" class="navbar navbar-expand-lg bg-dark-accented">
|
||||||
<div class="container px-5">
|
<div class="container px-5">
|
||||||
|
|
||||||
<div class="position-absolute start-0 top-50 translate-middle-y d-flex ms-3 gap-3 z-2">
|
<div class="position-absolute start-0 top-50 translate-middle-y d-flex ms-3 gap-3">
|
||||||
<ThemeChanger />
|
<ThemeChanger />
|
||||||
<LocaleChanger />
|
<LocaleChanger />
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -100,13 +90,13 @@ userStore.userCheckPromise
|
||||||
|
|
||||||
<div class="position-absolute end-0 top-50 translate-middle-y d-flex me-3 align-items-center">
|
<div class="position-absolute end-0 top-50 translate-middle-y d-flex me-3 align-items-center">
|
||||||
<template v-if=" userCheckLoading ">
|
<template v-if=" userCheckLoading ">
|
||||||
<FontAwesomeIcon :icon="['fas', 'spinner']" spin />
|
|
||||||
</template>
|
</template>
|
||||||
<template v-else-if=" userStore.loggedIn ">
|
<template v-else-if=" userStore.loggedIn ">
|
||||||
<button class="dropdown-toggle pointer bg-dark py-1 px-2 btn" data-bs-toggle="dropdown" aria-expanded="false">
|
<div class="dropdown-toggle pointer" data-bs-toggle="dropdown" aria-expanded="false">
|
||||||
<img class="pfp-sizing rounded-circle border border-1 border-primary" :src="userStore.pfpSource"
|
<img class="pfp-sizing rounded-circle border border-1 border-primary" :src="userStore.pfpSource"
|
||||||
alt="The Profile Pic of the user" />
|
alt="The Profile Pic of the user" />
|
||||||
</button>
|
</div>
|
||||||
<ul class="dropdown-menu dropdown-menu-end">
|
<ul class="dropdown-menu dropdown-menu-end">
|
||||||
<li>
|
<li>
|
||||||
<p class="dropdown-header fs-5 pt-0 text-primary fw-semibold">{{ userStore.getUserOutput }}</p>
|
<p class="dropdown-header fs-5 pt-0 text-primary fw-semibold">{{ userStore.getUserOutput }}</p>
|
||||||
|
|
|
||||||
|
|
@ -1,101 +0,0 @@
|
||||||
<script setup lang="ts">
|
|
||||||
import { computed, inject, provide, ref, type Ref } from 'vue';
|
|
||||||
import { useI18n } from 'vue-i18n';
|
|
||||||
|
|
||||||
import { navbarKey, questionTypesKey } from '@/services/UtilService';
|
|
||||||
import { Board } from '@/models/board/Board';
|
|
||||||
import type NavBar from '@/components/blocks/NavBar.vue';
|
|
||||||
|
|
||||||
import BoardView from '@/components/blocks/BoardView.vue';
|
|
||||||
import CreatePanel from '@/components/blocks/CreatePanel.vue';
|
|
||||||
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 navbarHeight = computed( () => {
|
|
||||||
return navbar?.value?.navElement?.clientHeight;
|
|
||||||
} );
|
|
||||||
const restHeight = computed( () => {
|
|
||||||
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", 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 questionIndex = ref<number | null>( null );
|
|
||||||
|
|
||||||
function showQuestion( cIndex: number, bEIndex: number, qIndex: number ) {
|
|
||||||
categoryIndex.value = cIndex;
|
|
||||||
boardEntryIndex.value = bEIndex;
|
|
||||||
questionIndex.value = qIndex;
|
|
||||||
}
|
|
||||||
|
|
||||||
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() {
|
|
||||||
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 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"
|
|
||||||
:questionIndex="questionIndex"
|
|
||||||
@editBoard="showBoard"
|
|
||||||
@editCategory="showCategory"
|
|
||||||
@editBoardEntry="showBoardEntry"
|
|
||||||
@editQuestion="showQuestion"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
@ -52,7 +52,6 @@
|
||||||
"save": "Save",
|
"save": "Save",
|
||||||
"saveAndExit": "Save and Exit"
|
"saveAndExit": "Save and Exit"
|
||||||
},
|
},
|
||||||
"back": "Back",
|
|
||||||
"error": {
|
"error": {
|
||||||
"generic": "Error"
|
"generic": "Error"
|
||||||
}
|
}
|
||||||
|
|
@ -69,41 +68,6 @@
|
||||||
"alreadyHostedGome": "Wanna create a board and host a game yourself?",
|
"alreadyHostedGome": "Wanna create a board and host a game yourself?",
|
||||||
"textCode": "Wanna create a board and host a game yourself?"
|
"textCode": "Wanna create a board and host a game yourself?"
|
||||||
},
|
},
|
||||||
"board": {
|
|
||||||
"label": "Board",
|
|
||||||
"name": "Board Name",
|
|
||||||
"infos": "Board Infos",
|
|
||||||
"options": {
|
|
||||||
"pointsForTitle": "Show points amount instead of entry name"
|
|
||||||
},
|
|
||||||
"category": {
|
|
||||||
"infos": "Category Infos",
|
|
||||||
"label": "No Category | Category | Categories | {count} Categories",
|
|
||||||
"name": "Category Name",
|
|
||||||
"description": "Category Description",
|
|
||||||
"add": "Add Category",
|
|
||||||
"back": "Back to Board"
|
|
||||||
},
|
|
||||||
"boardentry": {
|
|
||||||
"infos":"Entry Infos",
|
|
||||||
"points": "Point | Points | {count} Points",
|
|
||||||
"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": {
|
"theme": {
|
||||||
"dark": {
|
"dark": {
|
||||||
"name": "Dark"
|
"name": "Dark"
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,7 @@ import '@/assets/scss/customized_bootstrap.scss';
|
||||||
|
|
||||||
import { FontAwesomeIcon } from '@fortawesome/vue-fontawesome';
|
import { FontAwesomeIcon } from '@fortawesome/vue-fontawesome';
|
||||||
import { library } from '@fortawesome/fontawesome-svg-core';
|
import { library } from '@fortawesome/fontawesome-svg-core';
|
||||||
import { faSun, faMoon, faCircleHalfStroke, faEdit, faPlay, faSpinner, faLanguage, faGlobe, faPlus, faTrash, faGripLines, faAngleUp, faAngleDown, faAngleLeft } from '@fortawesome/free-solid-svg-icons';
|
import { faSun, faMoon, faCircleHalfStroke, faEdit, faPlay, faSpinner, faLanguage, faGlobe } from '@fortawesome/free-solid-svg-icons';
|
||||||
|
|
||||||
import enMessages from './locales/en.json';
|
import enMessages from './locales/en.json';
|
||||||
import deMessages from './locales/de.json';
|
import deMessages from './locales/de.json';
|
||||||
|
|
@ -33,12 +33,6 @@ library.add(
|
||||||
faSpinner,
|
faSpinner,
|
||||||
faLanguage,
|
faLanguage,
|
||||||
faGlobe,
|
faGlobe,
|
||||||
faPlus,
|
|
||||||
faTrash,
|
|
||||||
faGripLines,
|
|
||||||
faAngleUp,
|
|
||||||
faAngleDown,
|
|
||||||
faAngleLeft,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const app = createApp( App );
|
const app = createApp( App );
|
||||||
|
|
|
||||||
|
|
@ -1,10 +0,0 @@
|
||||||
import type { BoardEntry } from './BoardEntry';
|
|
||||||
|
|
||||||
export class Answer{
|
|
||||||
constructor(
|
|
||||||
public text: string,
|
|
||||||
public boardEntry: BoardEntry | undefined,
|
|
||||||
public image: URL | undefined = undefined,
|
|
||||||
public id: number | undefined = undefined,
|
|
||||||
){}
|
|
||||||
}
|
|
||||||
|
|
@ -1,12 +0,0 @@
|
||||||
import type { User } from '../user/User';
|
|
||||||
import type { Category } from './Category';
|
|
||||||
|
|
||||||
export class Board{
|
|
||||||
constructor(
|
|
||||||
public name: string,
|
|
||||||
public categories: Array<Category> = [],
|
|
||||||
public pointsAreTitle: boolean = false,
|
|
||||||
public owner: User | undefined = undefined,
|
|
||||||
public id: number | undefined = undefined,
|
|
||||||
){}
|
|
||||||
}
|
|
||||||
|
|
@ -1,14 +0,0 @@
|
||||||
import type { Answer } from './Answer';
|
|
||||||
import type { Category } from './Category';
|
|
||||||
import type { Question } from './Question';
|
|
||||||
|
|
||||||
export class BoardEntry{
|
|
||||||
constructor(
|
|
||||||
public name: string,
|
|
||||||
public category: Category,
|
|
||||||
public answer: Answer,
|
|
||||||
public questions: Array<Question>,
|
|
||||||
public points: number = 100,
|
|
||||||
public id: number | undefined = undefined,
|
|
||||||
){}
|
|
||||||
}
|
|
||||||
|
|
@ -1,12 +0,0 @@
|
||||||
import type { Board } from './Board';
|
|
||||||
import type { BoardEntry } from './BoardEntry';
|
|
||||||
|
|
||||||
export class Category{
|
|
||||||
constructor(
|
|
||||||
public name: string,
|
|
||||||
public description: string,
|
|
||||||
public board: Board,
|
|
||||||
public boardEntries: Array<BoardEntry> = [],
|
|
||||||
public id: number | undefined = undefined,
|
|
||||||
){}
|
|
||||||
}
|
|
||||||
|
|
@ -1,13 +0,0 @@
|
||||||
import type { BoardEntry } from './BoardEntry';
|
|
||||||
import type { QuestionType } from './QuestionType';
|
|
||||||
|
|
||||||
export class Question{
|
|
||||||
constructor(
|
|
||||||
public text: string,
|
|
||||||
public questionType: QuestionType,
|
|
||||||
public boardEntry: BoardEntry,
|
|
||||||
public fontScaling: number = 3,
|
|
||||||
public image: string | undefined = undefined,
|
|
||||||
public id: number | undefined = undefined,
|
|
||||||
) {}
|
|
||||||
}
|
|
||||||
|
|
@ -1,8 +0,0 @@
|
||||||
export class QuestionType {
|
|
||||||
constructor(
|
|
||||||
public title: string,
|
|
||||||
public description: string,
|
|
||||||
public active: boolean,
|
|
||||||
public id: number | undefined = undefined,
|
|
||||||
){}
|
|
||||||
}
|
|
||||||
|
|
@ -1,5 +1,4 @@
|
||||||
export type User = {
|
export type User = {
|
||||||
username: string,
|
username: string,
|
||||||
profilePictureFilename: string | undefined,
|
profilePictureFilename: string | undefined,
|
||||||
id: number | undefined,
|
|
||||||
}
|
}
|
||||||
|
|
@ -1,14 +1,11 @@
|
||||||
import { createRouter, createWebHistory } from 'vue-router';
|
import { createRouter, createWebHistory } from 'vue-router';
|
||||||
|
|
||||||
import { useUserStore } from '@/stores/UserStore';
|
|
||||||
|
|
||||||
import HomePage from '@/components/pages/HomePage.vue';
|
import HomePage from '@/components/pages/HomePage.vue';
|
||||||
import AboutPage from '@/components/pages/AboutPage.vue';
|
import AboutPage from '@/components/pages/AboutPage.vue';
|
||||||
import LoginPage from '@/components/pages/LoginPage.vue';
|
import LoginPage from '@/components/pages/LoginPage.vue';
|
||||||
import SignupPage from '@/components/pages/SignupPage.vue';
|
import SignupPage from '@/components/pages/SignupPage.vue';
|
||||||
import GamePage from '@/components/pages/GamePage.vue';
|
import GamePage from '@/components/pages/GamePage.vue';
|
||||||
import ProfilePage from '@/components/pages/ProfilePage.vue';
|
import ProfilePage from '@/components/pages/ProfilePage.vue';
|
||||||
import CreatePage from '@/components/pages/CreatePage.vue';
|
import { useUserStore } from '@/stores/UserStore';
|
||||||
|
|
||||||
const router = createRouter( {
|
const router = createRouter( {
|
||||||
history: createWebHistory( import.meta.env.BASE_URL ),
|
history: createWebHistory( import.meta.env.BASE_URL ),
|
||||||
|
|
@ -62,9 +59,9 @@ const router = createRouter( {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: '/create',
|
path: '/board',
|
||||||
name: 'create',
|
name: 'board',
|
||||||
component: CreatePage,
|
component: ProfilePage,
|
||||||
meta: {
|
meta: {
|
||||||
requiresAuth: true,
|
requiresAuth: true,
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,3 @@
|
||||||
import type { InjectionKey, Ref } from 'vue';
|
import type { InjectionKey } 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 infoModalShowFnKey = Symbol() as InjectionKey<Function>;
|
||||||
export const navbarKey = Symbol() as InjectionKey<Ref<InstanceType<typeof NavBar> | undefined>>;
|
|
||||||
export const questionTypesKey = Symbol() as InjectionKey<Array<QuestionType>>;
|
|
||||||
|
|
@ -5,22 +5,25 @@ import { authService } from '@/services/AuthService';
|
||||||
import { AxiosError } from 'axios';
|
import { AxiosError } from 'axios';
|
||||||
|
|
||||||
export const useUserStore = defineStore( 'user', () => {
|
export const useUserStore = defineStore( 'user', () => {
|
||||||
const loggedIn = computed( () => {
|
const username = ref( '' );
|
||||||
return user.value !== null;
|
const profilePicture = ref<null | string>( null );
|
||||||
})
|
const loggedIn = ref( false );
|
||||||
const user = ref<User | null>(null);
|
|
||||||
|
|
||||||
const getUserOutput = computed( () => `${user.value?.username}` );
|
const getUserOutput = computed( () => `${username.value}` );
|
||||||
const pfpSource = computed( () => {
|
const pfpSource = computed( () => {
|
||||||
return user.value?.profilePictureFilename ?? "/src/assets/images/PFP_BearHead.svg"
|
return profilePicture.value ?? "/src/assets/images/PFP_BearHead.svg"
|
||||||
})
|
})
|
||||||
|
|
||||||
function loginUser( userParam: User ) {
|
function loginUser( user: User ) {
|
||||||
user.value = userParam;
|
username.value = user.username;
|
||||||
|
profilePicture.value = user.profilePictureFilename ?? null;
|
||||||
|
loggedIn.value = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
function logoutUser() {
|
function logoutUser() {
|
||||||
user.value = null;
|
username.value = '';
|
||||||
|
profilePicture.value = null;
|
||||||
|
loggedIn.value = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
function getCheckUser(): Promise<User> {
|
function getCheckUser(): Promise<User> {
|
||||||
|
|
@ -40,9 +43,8 @@ export const useUserStore = defineStore( 'user', () => {
|
||||||
|
|
||||||
return {
|
return {
|
||||||
//Refs
|
//Refs
|
||||||
// username,
|
username,
|
||||||
// profilePicture,
|
profilePicture,
|
||||||
user,
|
|
||||||
loggedIn,
|
loggedIn,
|
||||||
userCheckPromise,
|
userCheckPromise,
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -18,5 +18,5 @@ export default defineConfig({
|
||||||
build:{
|
build:{
|
||||||
outDir: '../resources/static',
|
outDir: '../resources/static',
|
||||||
emptyOutDir: true,
|
emptyOutDir: true,
|
||||||
},
|
}
|
||||||
});
|
});
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue