Files
rpg-companion-sillytavern/src/systems/dashboard/widgets/userMoodWidget.js
T
Lucas 'Paperboy' Rose-Winters 04bb52ed71 feat(dashboard): improve mood widget readability with balanced two-line layout
- Increase conditions font size from 0.45rem to 0.6rem (33% larger)
- Reduce mood emoji size from 1rem to 0.9rem for better proportion
- Add font-weight: 600 to mood for better hierarchy
- Improve line-height from 1 to 1.2 for readability
- Reduce -webkit-line-clamp from 3 to 2 lines for conditions
- Add slight opacity (0.9) to conditions for visual separation
- Update onResize scaling to maintain balanced proportions (1.4rem / 0.9rem for larger widgets)
- Increase mobile conditions size to 0.7rem with 1.3 line-height

Result: Conditions text is now much more readable while maintaining good visual balance
with the mood emoji in the compact 1x1 widget.
2025-10-26 21:23:11 +11:00

217 lines
7.4 KiB
JavaScript

/**
* User Mood Widget
*
* Displays user's current mood emoji and active conditions.
* Compact widget showing emotional state and status effects.
*
* Features:
* - Large mood emoji (editable)
* - Conditions/status effects text (editable)
* - Responsive layout
*/
/**
* Register User Mood Widget
* @param {WidgetRegistry} registry - Widget registry instance
* @param {Object} dependencies - External dependencies
* @param {Function} dependencies.getExtensionSettings - Get extension settings
* @param {Function} dependencies.onStatsChange - Callback when stats change
*/
export function registerUserMoodWidget(registry, dependencies) {
const {
getExtensionSettings,
onStatsChange
} = dependencies;
registry.register('userMood', {
name: 'User Mood',
icon: '😊',
description: 'Mood emoji and active conditions',
category: 'user',
minSize: { w: 1, h: 1 },
defaultSize: { w: 1, h: 1 },
maxAutoSize: { w: 1, h: 1 }, // Max size for auto-arrange expansion - stays compact in top right
requiresSchema: false,
/**
* Render widget content
* @param {HTMLElement} container - Widget container
* @param {Object} config - Widget configuration
*/
render(container, config = {}) {
const settings = getExtensionSettings();
const stats = settings.userStats;
// Merge default config
const finalConfig = {
showMoodEmoji: true,
showConditions: true,
...config
};
// Build HTML
const html = `
<div class="rpg-mood">
${finalConfig.showMoodEmoji ? `<div class="rpg-mood-emoji rpg-editable" contenteditable="true" data-field="mood" title="Click to edit emoji">${stats.mood}</div>` : ''}
${finalConfig.showConditions ? `<div class="rpg-mood-conditions rpg-editable" contenteditable="true" data-field="conditions" title="Click to edit conditions">${stats.conditions}</div>` : ''}
</div>
`;
container.innerHTML = html;
// Attach event handlers
attachEventHandlers(container, settings, onStatsChange);
},
/**
* Get configuration options
* @returns {Object} Configuration schema
*/
getConfig() {
return {
showMoodEmoji: {
type: 'boolean',
label: 'Show Mood Emoji',
default: true
},
showConditions: {
type: 'boolean',
label: 'Show Conditions',
default: true
}
};
},
/**
* Handle configuration changes
* @param {HTMLElement} container - Widget container
* @param {Object} newConfig - New configuration
*/
onConfigChange(container, newConfig) {
this.render(container, newConfig);
},
/**
* Handle widget resize
* @param {HTMLElement} container - Widget container
* @param {number} newW - New width
* @param {number} newH - New height
*/
onResize(container, newW, newH) {
const mood = container.querySelector('.rpg-mood');
const emoji = container.querySelector('.rpg-mood-emoji');
const conditions = container.querySelector('.rpg-mood-conditions');
if (!mood || !emoji || !conditions) return;
// Scale based on widget size with balanced proportions
if (newW >= 2 && newH >= 2) {
// Larger widget: scale up proportionally
emoji.style.fontSize = '1.4rem';
conditions.style.fontSize = '0.9rem';
} else {
// Compact 1x1: use CSS defaults (0.9rem / 0.6rem)
emoji.style.fontSize = '';
conditions.style.fontSize = '';
}
}
});
}
/**
* Attach event handlers to widget
* @private
*/
function attachEventHandlers(container, settings, onStatsChange) {
// Handle mood emoji editing
const moodEmoji = container.querySelector('.rpg-mood-emoji.rpg-editable');
if (moodEmoji) {
let originalMood = moodEmoji.textContent.trim();
moodEmoji.addEventListener('focus', () => {
originalMood = moodEmoji.textContent.trim();
const range = document.createRange();
range.selectNodeContents(moodEmoji);
const selection = window.getSelection();
selection.removeAllRanges();
selection.addRange(range);
});
moodEmoji.addEventListener('blur', () => {
const value = moodEmoji.textContent.trim() || '😐';
moodEmoji.textContent = value;
if (value !== originalMood) {
settings.userStats.mood = value;
if (onStatsChange) {
onStatsChange('userStats', 'mood', value);
}
}
});
moodEmoji.addEventListener('keydown', (e) => {
if (e.key === 'Enter') {
e.preventDefault();
moodEmoji.blur();
}
if (e.key === 'Escape') {
e.preventDefault();
moodEmoji.textContent = originalMood;
moodEmoji.blur();
}
});
// Prevent paste with formatting
moodEmoji.addEventListener('paste', (e) => {
e.preventDefault();
const text = (e.clipboardData || window.clipboardData).getData('text/plain');
document.execCommand('insertText', false, text);
});
}
// Handle conditions editing
const moodConditions = container.querySelector('.rpg-mood-conditions.rpg-editable');
if (moodConditions) {
let originalConditions = moodConditions.textContent.trim();
moodConditions.addEventListener('focus', () => {
originalConditions = moodConditions.textContent.trim();
const range = document.createRange();
range.selectNodeContents(moodConditions);
const selection = window.getSelection();
selection.removeAllRanges();
selection.addRange(range);
});
moodConditions.addEventListener('blur', () => {
const value = moodConditions.textContent.trim() || 'None';
moodConditions.textContent = value;
if (value !== originalConditions) {
settings.userStats.conditions = value;
if (onStatsChange) {
onStatsChange('userStats', 'conditions', value);
}
}
});
moodConditions.addEventListener('keydown', (e) => {
if (e.key === 'Enter') {
e.preventDefault();
moodConditions.blur();
}
if (e.key === 'Escape') {
e.preventDefault();
moodConditions.textContent = originalConditions;
moodConditions.blur();
}
});
// Prevent paste with formatting
moodConditions.addEventListener('paste', (e) => {
e.preventDefault();
const text = (e.clipboardData || window.clipboardData).getData('text/plain');
document.execCommand('insertText', false, text);
});
}
}