Enhance usability and localization across components: Update USABILITY_CONCEPT.md with new focus areas, improve user feedback in AppFooter and FamilyView components, and refine text in various UI elements for better clarity and consistency. Replace console logs with user-friendly messages, correct German translations, and streamline interaction logic in multiple components.

This commit is contained in:
Torsten Schulz (local)
2026-03-20 09:41:03 +01:00
parent 1774d7df88
commit c7d33525ff
48 changed files with 1161 additions and 481 deletions

View File

@@ -37,6 +37,7 @@
<script>
import { mapGetters, mapState } from 'vuex';
import { showInfo } from '@/utils/feedback.js';
export default {
name: 'AppFooter',
@@ -71,10 +72,10 @@ export default {
},
// Daemon WebSocket deaktiviert - diese Funktionen sind nicht mehr verfügbar
async showFalukantDaemonStatus() {
console.log('⚠️ Daemon WebSocket deaktiviert - Status nicht verfügbar');
showInfo(this, 'Der Systemstatus ist in dieser Ansicht derzeit nicht direkt verfügbar.');
},
handleDaemonMessage(event) {
console.log('⚠️ Daemon WebSocket deaktiviert - keine Nachrichten verarbeitet');
handleDaemonMessage() {
// Status-Events werden hier bewusst nicht verarbeitet.
}
}
};

View File

@@ -10,7 +10,7 @@
class="app-section-bar__back"
@click="navigateBack"
>
Zurueck
Zurück
</button>
</section>
</template>
@@ -25,13 +25,13 @@ const SECTION_LABELS = [
{ test: (path) => path.startsWith('/settings'), label: 'Einstellungen' },
{ test: (path) => path.startsWith('/admin'), label: 'Administration' },
{ test: (path) => path.startsWith('/minigames'), label: 'Minispiele' },
{ test: (path) => path.startsWith('/personal'), label: 'Persoenlich' },
{ test: (path) => path.startsWith('/personal'), label: 'Persönlich' },
{ test: (path) => path.startsWith('/blogs'), label: 'Blog' }
];
const TITLE_MAP = {
Friends: 'Freunde',
Guestbook: 'Gaestebuch',
Guestbook: 'Gästebuch',
'Search users': 'Suche',
Gallery: 'Galerie',
Forum: 'Forum',
@@ -46,7 +46,7 @@ const TITLE_MAP = {
VocabCourse: 'Kurs',
VocabLesson: 'Lektion',
FalukantCreate: 'Charakter erstellen',
FalukantOverview: 'Uebersicht',
FalukantOverview: 'Übersicht',
BranchView: 'Niederlassung',
MoneyHistoryView: 'Geldverlauf',
FalukantFamily: 'Familie',
@@ -60,9 +60,9 @@ const TITLE_MAP = {
HealthView: 'Gesundheit',
PoliticsView: 'Politik',
UndergroundView: 'Untergrund',
'Personal settings': 'Persoenliche Daten',
'Personal settings': 'Persönliche Daten',
'View settings': 'Ansicht',
'Sexuality settings': 'Sexualitaet',
'Sexuality settings': 'Sexualität',
'Flirt settings': 'Flirt',
'Account settings': 'Account',
Interests: 'Interessen',
@@ -132,11 +132,19 @@ export default {
return '/admin/users';
}
if (window.history.length > 1) {
return '__history_back__';
}
return null;
}
},
methods: {
navigateBack() {
if (this.backTarget === '__history_back__') {
this.$router.back();
return;
}
if (this.backTarget) {
this.$router.push(this.backTarget);
}

View File

@@ -17,23 +17,33 @@ import { getApiBaseURL } from '@/utils/axios.js';
/** Backend-Route: GET /api/models/3d/falukant/characters/:filename (Proxy mit Draco-Optimierung) */
const MODELS_API_PATH = '/api/models/3d/falukant/characters';
let threeRuntimePromise = null;
let threeLoadersPromise = null;
let threeModelRuntimePromise = null;
async function loadThreeRuntime() {
if (!threeRuntimePromise) {
threeRuntimePromise = Promise.all([
import('three'),
import('three/addons/loaders/GLTFLoader.js'),
import('three/addons/loaders/DRACOLoader.js')
]).then(([THREE, { GLTFLoader }, { DRACOLoader }]) => ({
THREE,
GLTFLoader,
DRACOLoader
}));
threeRuntimePromise = import('@/utils/threeRuntime.js');
}
return threeRuntimePromise;
}
async function loadThreeLoaders() {
if (!threeLoadersPromise) {
threeLoadersPromise = import('@/utils/threeLoaders.js');
}
return threeLoadersPromise;
}
async function loadThreeModelRuntime() {
if (!threeModelRuntimePromise) {
threeModelRuntimePromise = import('@/utils/threeModelRuntime.js');
}
return threeModelRuntimePromise;
}
export default {
name: 'Character3D',
props: {
@@ -65,7 +75,9 @@ export default {
clock: null,
baseYPosition: 0,
showFallback: false,
threeRuntime: null
threeRuntime: null,
threeLoaders: null,
threeModelRuntime: null
};
},
computed: {
@@ -149,49 +161,65 @@ export default {
return this.threeRuntime;
},
async ensureThreeLoaders() {
if (!this.threeLoaders) {
this.threeLoaders = markRaw(await loadThreeLoaders());
}
return this.threeLoaders;
},
async ensureThreeModelRuntime() {
if (!this.threeModelRuntime) {
this.threeModelRuntime = markRaw(await loadThreeModelRuntime());
}
return this.threeModelRuntime;
},
async init3D() {
const container = this.$refs.container;
if (!container) return;
this.showFallback = false;
const { THREE } = await this.ensureThreeRuntime();
this.clock = markRaw(new THREE.Clock());
const runtime = await this.ensureThreeRuntime();
this.clock = markRaw(new runtime.Clock());
// Scene erstellen - markRaw verwenden, um Vue's Reactivity zu vermeiden
this.scene = markRaw(new THREE.Scene());
this.scene = markRaw(new runtime.Scene());
if (!this.noBackground) {
this.scene.background = new THREE.Color(0xf0f0f0);
this.scene.background = new runtime.Color(0xf0f0f0);
await this.loadBackground();
}
// Camera erstellen
const aspect = container.clientWidth / container.clientHeight;
this.camera = markRaw(new THREE.PerspectiveCamera(50, aspect, 0.1, 1000));
this.camera = markRaw(new runtime.PerspectiveCamera(50, aspect, 0.1, 1000));
this.camera.position.set(0, 1.5, 3);
this.camera.lookAt(0, 1, 0);
// Renderer erstellen
this.renderer = markRaw(new THREE.WebGLRenderer({ antialias: true, alpha: true }));
this.renderer = markRaw(new runtime.WebGLRenderer({ antialias: true, alpha: true }));
this.renderer.setSize(container.clientWidth, container.clientHeight);
this.renderer.setPixelRatio(window.devicePixelRatio);
container.appendChild(this.renderer.domElement);
// Verbesserte Beleuchtung für hellere Modelle
// Mehr ambient light für gleichmäßigere Ausleuchtung
const ambientLight = new THREE.AmbientLight(0xffffff, 1.0);
const ambientLight = new runtime.AmbientLight(0xffffff, 1.0);
this.scene.add(ambientLight);
// Hauptlicht von vorne oben - stärker
const directionalLight = new THREE.DirectionalLight(0xffffff, 1.2);
const directionalLight = new runtime.DirectionalLight(0xffffff, 1.2);
directionalLight.position.set(5, 10, 5);
this.scene.add(directionalLight);
// Zusätzliches Licht von hinten - heller
const backLight = new THREE.DirectionalLight(0xffffff, 0.75);
const backLight = new runtime.DirectionalLight(0xffffff, 0.75);
backLight.position.set(-5, 5, -5);
this.scene.add(backLight);
// Zusätzliches Seitenlicht für mehr Tiefe
const sideLight = new THREE.DirectionalLight(0xffffff, 0.5);
const sideLight = new runtime.DirectionalLight(0xffffff, 0.5);
sideLight.position.set(-5, 5, 5);
this.scene.add(sideLight);
@@ -200,13 +228,13 @@ export default {
},
async loadBackground() {
const { THREE } = await this.ensureThreeRuntime();
const runtime = await this.ensureThreeRuntime();
// Optimierte Versionen (512×341, ~130 KB); Originale ~3 MB
const backgrounds = ['bg1_opt.png', 'bg2_opt.png'];
const randomBg = backgrounds[Math.floor(Math.random() * backgrounds.length)];
const bgPath = `/images/falukant/backgrounds/${randomBg}`;
const loader = new THREE.TextureLoader();
const loader = new runtime.TextureLoader();
loader.load(
bgPath,
(texture) => {
@@ -220,7 +248,7 @@ export default {
console.warn('Fehler beim Laden des Hintergrunds:', error);
// Fallback auf Standardfarbe bei Fehler
if (this.scene) {
this.scene.background = new THREE.Color(0xf0f0f0);
this.scene.background = new runtime.Color(0xf0f0f0);
}
}
);
@@ -228,7 +256,8 @@ export default {
async loadModel() {
if (!this.scene) return;
const { THREE, GLTFLoader, DRACOLoader } = await this.ensureThreeRuntime();
const modelRuntime = await this.ensureThreeModelRuntime();
const loaders = await this.ensureThreeLoaders();
// Altes Modell entfernen
if (this.model) {
@@ -252,9 +281,9 @@ export default {
}
try {
const dracoLoader = new DRACOLoader();
const dracoLoader = new loaders.DRACOLoader();
dracoLoader.setDecoderPath('/draco/gltf/');
const loader = new GLTFLoader();
const loader = new loaders.GLTFLoader();
loader.setDRACOLoader(dracoLoader);
const base = getApiBaseURL();
@@ -273,12 +302,12 @@ export default {
// Versuche zuerst genaues Alter
try {
gltf = await loader.loadAsync(exactAgePath);
console.log(`Loaded exact age model: ${exactAgePath}`);
console.debug(`Loaded exact age model: ${exactAgePath}`);
} catch (exactAgeError) {
// Falls genaues Alter nicht existiert, versuche Altersbereich
try {
gltf = await loader.loadAsync(ageGroupPath);
console.log(`Loaded age group model: ${ageGroupPath}`);
console.debug(`Loaded age group model: ${ageGroupPath}`);
} catch (ageGroupError) {
// Falls Altersbereich nicht existiert, verwende Basis-Modell
console.warn(`Could not load ${ageGroupPath}, trying fallback model`);
@@ -293,8 +322,8 @@ export default {
this.model = markRaw(gltf.scene);
// Initiale Bounding Box für Größenberechnung (vor Skalierung)
const initialBox = new THREE.Box3().setFromObject(this.model);
const initialSize = initialBox.getSize(new THREE.Vector3());
const initialBox = new modelRuntime.Box3().setFromObject(this.model);
const initialSize = initialBox.getSize(new modelRuntime.Vector3());
// Skalierung basierend auf Alter
const age = this.actualAge;
@@ -318,8 +347,8 @@ export default {
this.model.scale.set(modelScale, modelScale, modelScale);
// Bounding Box NACH dem Skalieren neu berechnen
const scaledBox = new THREE.Box3().setFromObject(this.model);
const scaledCenter = scaledBox.getCenter(new THREE.Vector3());
const scaledBox = new modelRuntime.Box3().setFromObject(this.model);
const scaledCenter = scaledBox.getCenter(new modelRuntime.Vector3());
// Modell zentrieren basierend auf der skalierten Bounding Box
// Position direkt setzen statt zu subtrahieren, um Proxy-Probleme zu vermeiden
@@ -331,7 +360,7 @@ export default {
// Animationen laden falls vorhanden
if (gltf.animations && gltf.animations.length > 0) {
this.mixer = markRaw(new THREE.AnimationMixer(this.model));
this.mixer = markRaw(new modelRuntime.AnimationMixer(this.model));
gltf.animations.forEach((clip) => {
this.mixer.clipAction(clip).play();
});

View File

@@ -173,7 +173,7 @@ export default {
this.fetchSettings();
} catch (err) {
console.error('Error updating setting:', err);
showApiError(this, err, 'Aenderung konnte nicht gespeichert werden.');
showApiError(this, err, 'Änderung konnte nicht gespeichert werden.');
}
},
languagesList() {

View File

@@ -279,7 +279,6 @@ export default {
},
openNewDirectorDialog() {
console.log('openNewDirectorDialog');
this.$refs.newDirectorDialog.open(this.branchId);
},

View File

@@ -43,7 +43,6 @@ export default {
},
methods: {
updateValue(value) {
console.log('changed to ', value)
this.$emit("input", parseInt(value));
}
}