Bugs in settings fixed, profile added
This commit is contained in:
@@ -3,10 +3,10 @@
|
||||
<div class="logo"><img src="/images/icons/logo_color.png"></div>
|
||||
<div class="window-bar">
|
||||
<button v-for="dialog in openDialogs" :key="dialog.dialog.name" class="dialog-button"
|
||||
@click="toggleDialogMinimize(dialog.dialog.name)" :title="dialog.dialog.title">
|
||||
@click="toggleDialogMinimize(dialog.dialog.name)" :title="dialog.dialog.localTitle">
|
||||
<img v-if="dialog.dialog.icon" :src="'/images/icons/' + dialog.dialog.icon" />
|
||||
<span class="button-text">{{ dialog.dialog.isTitleTranslated ? $t(dialog.dialog.title) : dialog.dialog.title
|
||||
}}</span>
|
||||
<span class="button-text">{{ dialog.dialog.isTitleTranslated ? $t(dialog.dialog.localTitle) :
|
||||
dialog.dialog.localTitle }}</span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="static-block">
|
||||
|
||||
@@ -60,6 +60,7 @@ nav>ul {
|
||||
flex-direction: row;
|
||||
margin: 0;
|
||||
cursor: pointer;
|
||||
z-index: 999;
|
||||
}
|
||||
|
||||
ul {
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
<span v-if="icon" class="dialog-icon">
|
||||
<img :src="'/images/icons/' + icon" alt="Icon" />
|
||||
</span>
|
||||
<span class="dialog-title">{{ isTitleTranslated ? $t(title) : title }}</span>
|
||||
<span class="dialog-title">{{ localIsTitleTranslated ? $t(localTitle) : localTitle }}</span>
|
||||
<span v-if="!modal" class="dialog-minimize" @click="minimize">_</span>
|
||||
<span v-if="showClose" class="dialog-close" @click="close">✖</span>
|
||||
</div>
|
||||
@@ -73,12 +73,13 @@ export default {
|
||||
isDragging: false,
|
||||
dragOffsetX: 0,
|
||||
dragOffsetY: 0,
|
||||
localTitle: this.title,
|
||||
localIsTitleTranslated: this.isTitleTranslated,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
dialogWidth() {
|
||||
const val = this.width || '70%';
|
||||
console.log(val);
|
||||
return val;
|
||||
},
|
||||
dialogHeight() {
|
||||
@@ -90,6 +91,9 @@ export default {
|
||||
if (!newValue) {
|
||||
this.minimized = false;
|
||||
}
|
||||
},
|
||||
title(newValue) {
|
||||
this.updateTitle(newValue);
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
@@ -107,9 +111,13 @@ export default {
|
||||
this.$store.dispatch('dialogs/removeOpenDialog', this.name);
|
||||
},
|
||||
buttonClick(action) {
|
||||
this.$emit(action);
|
||||
if (action === 'close') {
|
||||
this.close();
|
||||
if (typeof action === 'function') {
|
||||
action(); // Wenn action eine Funktion ist, rufe sie direkt auf
|
||||
} else {
|
||||
this.$emit(action);
|
||||
if (action === 'close') {
|
||||
this.close();
|
||||
}
|
||||
}
|
||||
},
|
||||
handleOverlayClick() {
|
||||
@@ -131,7 +139,6 @@ export default {
|
||||
this.dragOffsetY = event.clientY - dialog.offsetTop;
|
||||
document.addEventListener('mousemove', this.onDrag);
|
||||
document.addEventListener('mouseup', this.stopDragging);
|
||||
console.log('dragging started');
|
||||
},
|
||||
onDrag(event) {
|
||||
if (!this.isDragging) return;
|
||||
@@ -143,13 +150,15 @@ export default {
|
||||
document.removeEventListener('mousemove', this.onDrag);
|
||||
document.removeEventListener('mouseup', this.stopDragging);
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
this.$store.subscribe((mutation) => {
|
||||
if (mutation.type === 'dialogs/toggleDialogMinimize' && mutation.payload === this.name) {
|
||||
this.minimized = !this.minimized;
|
||||
}
|
||||
});
|
||||
updateTitle(newTitle, newIsTitleTranslated) {
|
||||
this.localTitle = newTitle;
|
||||
this.localIsTitleTranslated = newIsTitleTranslated;
|
||||
this.$store.dispatch('dialogs/updateDialogTitle', {
|
||||
name: this.name,
|
||||
newTitle: newTitle,
|
||||
isTitleTranslated: this.localIsTitleTranslated
|
||||
});
|
||||
},
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
76
frontend/src/components/QuillEditor.vue
Normal file
76
frontend/src/components/QuillEditor.vue
Normal file
@@ -0,0 +1,76 @@
|
||||
<template>
|
||||
<div ref="quillEditor" class="quill-editor"></div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Quill from 'quill/core';
|
||||
import Toolbar from 'quill/modules/toolbar';
|
||||
import Snow from 'quill/themes/snow';
|
||||
import Bold from 'quill/formats/bold';
|
||||
import Italic from 'quill/formats/italic';
|
||||
import Underline from 'quill/formats/underline';
|
||||
import List from 'quill/formats/list';
|
||||
|
||||
Quill.register({
|
||||
'modules/toolbar': Toolbar,
|
||||
'themes/snow': Snow,
|
||||
'formats/bold': Bold,
|
||||
'formats/italic': Italic,
|
||||
'formats/underline': Underline,
|
||||
'formats/list': List
|
||||
});
|
||||
|
||||
export default {
|
||||
name: 'QuillEditor',
|
||||
props: {
|
||||
content: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
placeholder: {
|
||||
type: String,
|
||||
default: 'Compose an epic...'
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
editor: null,
|
||||
};
|
||||
},
|
||||
mounted() {
|
||||
this.editor = new Quill(this.$refs.quillEditor, {
|
||||
theme: 'snow',
|
||||
placeholder: this.placeholder,
|
||||
modules: {
|
||||
toolbar: [
|
||||
[{ 'header': [1, 2, false] }],
|
||||
['bold', 'italic', 'underline'],
|
||||
['link', 'image'],
|
||||
[{ 'list': 'ordered' }, { 'list': 'bullet' }],
|
||||
[{ 'align': [] }],
|
||||
[{ 'color': [] }, { 'background': [] }],
|
||||
['clean']
|
||||
]
|
||||
}
|
||||
});
|
||||
|
||||
this.editor.on('text-change', () => {
|
||||
this.$emit('update:content', this.editor.root.innerHTML);
|
||||
});
|
||||
|
||||
this.editor.root.innerHTML = this.content;
|
||||
},
|
||||
beforeUnmount() {
|
||||
if (this.editor) {
|
||||
this.editor.off('text-change');
|
||||
this.editor = null;
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.quill-editor {
|
||||
min-height: 200px;
|
||||
}
|
||||
</style>
|
||||
@@ -1,30 +1,58 @@
|
||||
<template>
|
||||
<div class="settings-widget">
|
||||
<template v-for="setting in settings">
|
||||
<InputStringWidget v-if="setting.datatype == 'string'" :labelTr="`settings.personal.label.${setting.name}`"
|
||||
:tooltipTr="`settings.personal.tooltip.${setting.name}`" :value=setting.value :list="languagesList()"
|
||||
@input="handleInput(setting.id, $event)" />
|
||||
<DateInputWidget v-else-if="setting.datatype == 'date'" :labelTr="`settings.personal.label.${setting.name}`"
|
||||
:tooltipTr="`settings.personal.tooltip.${setting.name}`" :value=setting.value
|
||||
@input="handleInput(setting.id, $event)" />
|
||||
<SelectDropdownWidget v-else-if="setting.datatype == 'singleselect'"
|
||||
:labelTr="`settings.personal.label.${setting.name}`"
|
||||
:tooltipTr="`settings.personal.tooltip.${setting.name}`" :value=setting.value
|
||||
:list="getSettingOptions(setting.name, setting.options)" @input="handleInput(setting.id, $event)" />
|
||||
<InputNumberWidget v-else-if="setting.datatype == 'int'"
|
||||
:labelTr="`settings.personal.label.${setting.name}`"
|
||||
:tooltipTr="`settings.personal.tooltip.${setting.name}`" :value="convertToInt(setting.value)" min="0"
|
||||
max="200" @input="handleInput(setting.id, $event)" />
|
||||
<FloatInputWidget v-else-if="setting.datatype == 'float'"
|
||||
:labelTr="`settings.personal.label.${setting.name}`"
|
||||
:tooltipTr="`settings.personal.tooltip.${setting.name}`" :value="convertToFloat(setting.value)"
|
||||
@input="handleInput(setting.id, $event)" />
|
||||
<CheckboxWidget v-else-if="setting.datatype == 'bool'" :labelTr="`settings.personal.label.${setting.name}`"
|
||||
:tooltipTr="`settings.personal.tooltip.${setting.name}`" :value="convertToBool(setting.value)"
|
||||
@input="handleInput(setting.id, $event)" />
|
||||
<div v-else>{{ setting }}
|
||||
</div>
|
||||
</template>
|
||||
<table>
|
||||
<tr v-for="setting in settings" :key="setting.id">
|
||||
<td>
|
||||
<InputStringWidget v-if="setting.datatype == 'string'"
|
||||
:labelTr="`settings.personal.label.${setting.name}`"
|
||||
:tooltipTr="`settings.personal.tooltip.${setting.name}`" :value=setting.value
|
||||
:list="languagesList()" @input="handleInput(setting.id, $event)" />
|
||||
|
||||
<DateInputWidget v-else-if="setting.datatype == 'date'"
|
||||
:labelTr="`settings.personal.label.${setting.name}`"
|
||||
:tooltipTr="`settings.personal.tooltip.${setting.name}`" :value=setting.value
|
||||
@input="handleInput(setting.id, $event)" />
|
||||
|
||||
<SelectDropdownWidget v-else-if="setting.datatype == 'singleselect'"
|
||||
:labelTr="`settings.personal.label.${setting.name}`"
|
||||
:tooltipTr="`settings.personal.tooltip.${setting.name}`" :value=setting.value
|
||||
:list="getSettingOptions(setting.name, setting.options)"
|
||||
@input="handleInput(setting.id, $event)" />
|
||||
|
||||
<InputNumberWidget v-else-if="setting.datatype == 'int'"
|
||||
:labelTr="`settings.personal.label.${setting.name}`"
|
||||
:tooltipTr="`settings.personal.tooltip.${setting.name}`" :value="convertToInt(setting.value)"
|
||||
min="0" max="200" @input="handleInput(setting.id, $event)" />
|
||||
|
||||
<FloatInputWidget v-else-if="setting.datatype == 'float'"
|
||||
:labelTr="`settings.personal.label.${setting.name}`"
|
||||
:tooltipTr="`settings.personal.tooltip.${setting.name}`" :value="convertToFloat(setting.value)"
|
||||
@input="handleInput(setting.id, $event)" />
|
||||
|
||||
<CheckboxWidget v-else-if="setting.datatype == 'bool'"
|
||||
:labelTr="`settings.personal.label.${setting.name}`"
|
||||
:tooltipTr="`settings.personal.tooltip.${setting.name}`" :value="convertToBool(setting.value)"
|
||||
@input="handleInput(setting.id, $event)" />
|
||||
|
||||
<MultiselectWidget v-else-if="setting.datatype == 'multiselect'"
|
||||
:labelTr="`settings.personal.label.${setting.name}`"
|
||||
:tooltipTr="`settings.personal.tooltip.${setting.name}`" :value="setting.value"
|
||||
:list="getSettingOptions(setting.name, setting.options)"
|
||||
@input="handleInput(setting.id, $event)" />
|
||||
|
||||
<div v-else>{{ setting }}</div>
|
||||
<span v-if="setting.unit"> {{ setting.unit }}</span>
|
||||
</td>
|
||||
<td>
|
||||
<select v-model="setting.visibility.id"
|
||||
@change="handleVisibilityChange(setting.id, setting.visibility.id)">
|
||||
<option v-for="visibility in possibleVisibilities" :key="visibility.id" :value="visibility.id">
|
||||
{{ $t(`settings.visibility.${visibility.description}`) }}
|
||||
</option>
|
||||
</select>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -33,10 +61,11 @@ import apiClient from '@/utils/axios.js';
|
||||
import { mapGetters } from 'vuex';
|
||||
import InputStringWidget from '@/components/form/InputStringWidget.vue';
|
||||
import DateInputWidget from '@/components/form/DateInputWidget.vue';
|
||||
import SelectDropdownWidget from '@/components/form/SelectDropdownWidget';
|
||||
import InputNumberWidget from '@/components/form/InputNumberWidget';
|
||||
import FloatInputWidget from '@/components/form/FloatInputWidget';
|
||||
import CheckboxWidget from '@/components/form/CheckboxWidget';
|
||||
import SelectDropdownWidget from '@/components/form/SelectDropdownWidget.vue';
|
||||
import InputNumberWidget from '@/components/form/InputNumberWidget.vue';
|
||||
import FloatInputWidget from '@/components/form/FloatInputWidget.vue';
|
||||
import CheckboxWidget from '@/components/form/CheckboxWidget.vue';
|
||||
import MultiselectWidget from '@/components/form/MultiselectWidget.vue';
|
||||
|
||||
export default {
|
||||
name: "SettingsWidget",
|
||||
@@ -46,7 +75,8 @@ export default {
|
||||
SelectDropdownWidget,
|
||||
InputNumberWidget,
|
||||
FloatInputWidget,
|
||||
CheckboxWidget
|
||||
CheckboxWidget,
|
||||
MultiselectWidget
|
||||
},
|
||||
props: {
|
||||
settingsType: {
|
||||
@@ -54,6 +84,10 @@ export default {
|
||||
required: true
|
||||
}
|
||||
},
|
||||
data: {
|
||||
settings: [],
|
||||
possibleVisibilities: [],
|
||||
},
|
||||
computed: {
|
||||
...mapGetters(['user']),
|
||||
},
|
||||
@@ -64,6 +98,8 @@ export default {
|
||||
async fetchSettings() {
|
||||
if (this.user && this.user.id) {
|
||||
try {
|
||||
const visibilityResponse = await apiClient.get('/api/settings/visibilities');
|
||||
this.possibleVisibilities = visibilityResponse.data;
|
||||
const userid = this.user.id;
|
||||
const response = await apiClient.post('/api/settings/filter', {
|
||||
userid: userid,
|
||||
@@ -73,7 +109,7 @@ export default {
|
||||
} catch (err) {
|
||||
this.settings = [];
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
getSettingOptions(fieldName, options) {
|
||||
return options.map((option) => {
|
||||
@@ -94,6 +130,7 @@ export default {
|
||||
settingId: settingId,
|
||||
value: value
|
||||
});
|
||||
this.fetchSettings();
|
||||
} catch (err) {
|
||||
console.error('Error updating setting:', err);
|
||||
}
|
||||
@@ -120,7 +157,17 @@ export default {
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
},
|
||||
async handleVisibilityChange(settingId, visibilityId) {
|
||||
try {
|
||||
await apiClient.post('/api/settings/update-visibility', {
|
||||
userParamTypeId: settingId,
|
||||
visibilityId: visibilityId
|
||||
});
|
||||
} catch (err) {
|
||||
console.error('Error updating visibility:', err);
|
||||
}
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
@@ -129,3 +176,9 @@ export default {
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
label {
|
||||
float: left;
|
||||
}
|
||||
</style>
|
||||
@@ -30,7 +30,7 @@ export default {
|
||||
},
|
||||
methods: {
|
||||
updateValue(checked) {
|
||||
this.$emit("input", checked);
|
||||
this.$emit("input", checked || false);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
<label>
|
||||
<span :style="{ width: width + 'em' }">{{ $t(labelTr) }}</span>
|
||||
<input type="number" :value="formattedValue" :placeholder="$t(labelTr)" :title="$t(tooltipTr)"
|
||||
@input="updateValue($event.target.value)" :step="step" />
|
||||
@change="updateValue($event.target.value)" :step="step" />
|
||||
<span v-if="postfix">{{ postfix }}</span>
|
||||
</label>
|
||||
</template>
|
||||
@@ -41,7 +41,7 @@ export default {
|
||||
},
|
||||
computed: {
|
||||
formattedValue() {
|
||||
return this.value != null && typeof this.value === 'float' ? this.value.toFixed(this.decimals) : '';
|
||||
return this.value != null ? this.value.toFixed(this.decimals) : '';
|
||||
},
|
||||
step() {
|
||||
return Math.pow(10, -this.decimals);
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
<template>
|
||||
<label>
|
||||
<span :style="{ width: width + 'em' }">{{ $t(labelTr) }}</span>
|
||||
<input type="number" :value="value" :title="$t(tooltipTr)" :min="min" :max="max"
|
||||
@input="updateValue($event.target.value)" />
|
||||
<input type="number" :value="value" :title="$t(tooltipTr)" :min="min" :max="max"
|
||||
@change="updateValue($event.target.value)" />
|
||||
</label>
|
||||
</template>
|
||||
|
||||
@@ -38,7 +38,8 @@ export default {
|
||||
},
|
||||
methods: {
|
||||
updateValue(value) {
|
||||
this.$emit("input", parseFloat(value));
|
||||
console.log('changed to ', value)
|
||||
this.$emit("input", parseInt(value));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
<label>
|
||||
<span :style="{ width: width + 'em' }">{{ $t(labelTr) }}</span>
|
||||
<input type="text" :value="value" :placeholder="$t(labelTr)" :title="$t(tooltipTr)"
|
||||
@input="validateAndUpdate($event.target.value)" />
|
||||
@change="validateAndUpdate($event.target.value)" />
|
||||
</label>
|
||||
</template>
|
||||
|
||||
|
||||
139
frontend/src/components/form/MultiselectWidget.vue
Normal file
139
frontend/src/components/form/MultiselectWidget.vue
Normal file
@@ -0,0 +1,139 @@
|
||||
<template>
|
||||
<label>
|
||||
<span :style="{ width: width + 'em' }">{{ $t(labelTr) }}</span>
|
||||
<Multiselect
|
||||
v-model="selectedOptions"
|
||||
:options="validList"
|
||||
:multiple="true"
|
||||
:close-on-select="false"
|
||||
:clear-on-select="false"
|
||||
:preserve-search="true"
|
||||
:placeholder="$t('select_option')"
|
||||
:track-by="'value'"
|
||||
>
|
||||
<template #option="{ option }">
|
||||
<span v-if="option && option.value">Option: {{ getTranslation(option) }}</span>
|
||||
</template>
|
||||
<template #tag="{ option, remove }">
|
||||
<span v-if="option && option.captionTr" class="custom-tag">
|
||||
{{ $t(option.captionTr) }}
|
||||
<span @click="remove(option)">×</span>
|
||||
</span>
|
||||
<span v-else>@e</span>
|
||||
</template>
|
||||
</Multiselect>
|
||||
</label>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Multiselect from 'vue-multiselect';
|
||||
import 'vue-multiselect/dist/vue-multiselect.min.css';
|
||||
|
||||
export default {
|
||||
name: "MultiselectWidget",
|
||||
components: { Multiselect },
|
||||
props: {
|
||||
labelTr: { type: String, required: true },
|
||||
value: { type: String, required: false, default: '[]' },
|
||||
tooltipTr: { type: String, required: true },
|
||||
width: { type: Number, required: false, default: 10 },
|
||||
list: {
|
||||
type: Array,
|
||||
required: true,
|
||||
default: () => [] // Standardwert hinzufügen, um undefined zu vermeiden
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
internalValues: this.stringToArray(this.value), // Speichert nur die IDs (Werte)
|
||||
selectedOptions: this.getOptionsFromIds(this.stringToArray(this.value)) // Hilfsvariable, speichert die vollständigen Objekte
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
validList() {
|
||||
return this.validatedList(); // Immer ein Array zurückgeben
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
value(newValue) {
|
||||
const ids = this.stringToArray(newValue);
|
||||
this.internalValues = ids; // Nur die IDs speichern
|
||||
this.selectedOptions = this.getOptionsFromIds(ids); // Optionen basierend auf IDs setzen
|
||||
},
|
||||
selectedOptions(newOptions) {
|
||||
this.internalValues = newOptions.map(option => option.value); // Nur die IDs extrahieren
|
||||
this.updateValue();
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
stringToArray(str) {
|
||||
try {
|
||||
const array = JSON.parse(str);
|
||||
return array.filter(item => item !== null && item !== undefined);
|
||||
} catch (error) {
|
||||
console.error('Invalid JSON string in value:', str);
|
||||
return [];
|
||||
}
|
||||
},
|
||||
updateValue() {
|
||||
const stringValue = JSON.stringify(this.internalValues); // In JSON-String umwandeln
|
||||
this.$emit("input", stringValue); // String an das Parent-Element übermitteln
|
||||
},
|
||||
getTranslation(option) {
|
||||
return option.captionTr ? this.$t(option.captionTr) : option.caption;
|
||||
},
|
||||
findOption(optionId) {
|
||||
return this.validatedList().find(opt => opt.value === optionId);
|
||||
},
|
||||
getOptionsFromIds(ids) {
|
||||
return ids.map(id => this.findOption(id)).filter(option => option); // Vollständige Objekte basierend auf IDs abrufen
|
||||
},
|
||||
validatedList() {
|
||||
// Überprüfen, ob die Liste valide ist
|
||||
if (!this.list || !Array.isArray(this.list)) {
|
||||
return [];
|
||||
}
|
||||
return this.list.filter(option => option && option.value !== null && option.value !== undefined && (option.captionTr || option.caption));
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
label {
|
||||
display: block;
|
||||
margin-bottom: 1em;
|
||||
}
|
||||
|
||||
label>span {
|
||||
display: inline-block;
|
||||
margin-bottom: 0.5em;
|
||||
}
|
||||
|
||||
.multiselect {
|
||||
margin-left: 0.5em;
|
||||
}
|
||||
|
||||
.custom-tag {
|
||||
background-color: #f0f0f0;
|
||||
border: 1px solid #ccc;
|
||||
padding: 5px;
|
||||
border-radius: 4px;
|
||||
margin-right: 5px;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.custom-tag span {
|
||||
margin-left: 8px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.multiselect {
|
||||
display: inline-block;
|
||||
width: 7em;
|
||||
}
|
||||
|
||||
.multiselect__tags {
|
||||
white-space: nowrap;
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user