v3.6.0 - Bug fixes and number display mode for stats

- Fixed custom status fields not being sent to prompts or parsed
- Fixed date format selection not working beyond default format
- Fixed widget text overflow issues with minimal scrollbars
- Added ability to display stats as numbers with custom max values instead of percentages
- Enabled desktop strip widgets by default
- Removed icon from Desktop Collapsed Strip Widgets heading
This commit is contained in:
Spicy_Marinara
2026-01-13 13:52:18 +01:00
parent ce668c4793
commit e8edc42164
12 changed files with 340 additions and 64 deletions
+8 -7
View File
@@ -7,14 +7,15 @@ An immersive RPG extension for browsers that tracks character stats, scene infor
## 🆕 What's New ## 🆕 What's New
### v3.5.0 ### v3.6.0
- Various fixes and upgrades to the existing systems. - You can now choose whether stats are displayed as percentages or numbers.
- Repaired Auto-generate Avatars. - Added collapsed strip widgets for desktop.
- Fixed Dynami Weather on mobiles. - Added new effects for the dynamic weather.
- Added an option to decide where to display the weather effects (foreground or background). - Changed the displayed clock format in the Info Box.
- Unified CSS. - Fixed customized status field to work.
- Dice rolls are now sent with the prompt even if you don't have Attributes toggled on. - Fixed date format toggles.
- Minor CSS and bug fixes.
**Special thanks to all the other contributors for this project:** **Special thanks to all the other contributors for this project:**
Paperboygold, Munimunigamer, Subarashimo, Lilminzyu, Claude, IDeathByte, Chungchandev, Joenunezb, Amauragis, and Tomt610. Paperboygold, Munimunigamer, Subarashimo, Lilminzyu, Claude, IDeathByte, Chungchandev, Joenunezb, Amauragis, and Tomt610.
+1 -1
View File
@@ -6,6 +6,6 @@
"js": "index.js", "js": "index.js",
"css": "style.css", "css": "style.css",
"author": "Marinara", "author": "Marinara",
"version": "3.4.1", "version": "3.6.0",
"homePage": "https://github.com/SpicyMarinara/rpg-companion-sillytavern" "homePage": "https://github.com/SpicyMarinara/rpg-companion-sillytavern"
} }
+1 -1
View File
@@ -48,7 +48,7 @@
</div> </div>
<div style="margin-top: 10px; text-align: center; opacity: 0.6; font-size: 0.85em;"> <div style="margin-top: 10px; text-align: center; opacity: 0.6; font-size: 0.85em;">
v3.5.0 v3.6.0
</div> </div>
</div> </div>
</div> </div>
+45
View File
@@ -147,6 +147,12 @@ export function loadSettings() {
// Migrate to preset manager system if presets don't exist // Migrate to preset manager system if presets don't exist
migrateToPresetManager(); migrateToPresetManager();
// Initialize custom status fields
initializeCustomStatusFields();
// Ensure all stats have maxValue (for number display mode)
ensureStatsHaveMaxValue();
} catch (error) { } catch (error) {
console.error('[RPG Companion] Error loading settings:', error); console.error('[RPG Companion] Error loading settings:', error);
console.error('[RPG Companion] Error details:', error.message, error.stack); console.error('[RPG Companion] Error details:', error.message, error.stack);
@@ -694,6 +700,45 @@ export function migrateToPresetManager() {
} }
} }
/**
* Initializes custom status fields in userStats based on trackerConfig
* Ensures all defined custom status fields have a value in the userStats object
*/
function initializeCustomStatusFields() {
const customFields = extensionSettings.trackerConfig?.userStats?.statusSection?.customFields || [];
// Initialize each custom field if it doesn't exist
for (const fieldName of customFields) {
const fieldKey = fieldName.toLowerCase();
if (extensionSettings.userStats[fieldKey] === undefined) {
extensionSettings.userStats[fieldKey] = 'None';
// console.log(`[RPG Companion] Initialized custom status field: ${fieldKey}`);
}
}
}
/**
* Ensures all custom stats have a maxValue property
* This migration supports the number display mode feature
*/
function ensureStatsHaveMaxValue() {
const customStats = extensionSettings.trackerConfig?.userStats?.customStats || [];
for (const stat of customStats) {
if (stat && stat.maxValue === undefined) {
stat.maxValue = 100; // Default to 100 for backward compatibility
// console.log(`[RPG Companion] Added maxValue to stat: ${stat.id || stat.name}`);
}
}
// Ensure statsDisplayMode is set (default to percentage)
if (extensionSettings.trackerConfig?.userStats &&
extensionSettings.trackerConfig.userStats.statsDisplayMode === undefined) {
extensionSettings.trackerConfig.userStats.statsDisplayMode = 'percentage';
// console.log('[RPG Companion] Initialized statsDisplayMode to percentage');
}
}
/** /**
* Gets all available presets * Gets all available presets
* @returns {Object} Map of preset ID to preset data * @returns {Object} Map of preset ID to preset data
+8 -6
View File
@@ -86,7 +86,7 @@ export let extensionSettings = {
}, },
// Desktop strip widget display options (shown in collapsed panel strip) // Desktop strip widget display options (shown in collapsed panel strip)
desktopStripWidgets: { desktopStripWidgets: {
enabled: false, // Master toggle for strip widgets (disabled by default) enabled: true, // Master toggle for strip widgets (enabled by default)
weatherIcon: { enabled: true }, // Weather emoji (☀️, 🌧️, etc.) weatherIcon: { enabled: true }, // Weather emoji (☀️, 🌧️, etc.)
clock: { enabled: true }, // Current time display clock: { enabled: true }, // Current time display
date: { enabled: true }, // Date display date: { enabled: true }, // Date display
@@ -125,13 +125,15 @@ export let extensionSettings = {
// Tracker customization configuration // Tracker customization configuration
trackerConfig: { trackerConfig: {
userStats: { userStats: {
// Stats display mode: 'percentage' or 'number'
statsDisplayMode: 'percentage',
// Array of custom stats (allows add/remove/rename) // Array of custom stats (allows add/remove/rename)
customStats: [ customStats: [
{ id: 'health', name: 'Health', enabled: true, persistInHistory: false }, { id: 'health', name: 'Health', enabled: true, persistInHistory: false, maxValue: 100 },
{ id: 'satiety', name: 'Satiety', enabled: true, persistInHistory: false }, { id: 'satiety', name: 'Satiety', enabled: true, persistInHistory: false, maxValue: 100 },
{ id: 'energy', name: 'Energy', enabled: true, persistInHistory: false }, { id: 'energy', name: 'Energy', enabled: true, persistInHistory: false, maxValue: 100 },
{ id: 'hygiene', name: 'Hygiene', enabled: true, persistInHistory: false }, { id: 'hygiene', name: 'Hygiene', enabled: true, persistInHistory: false, maxValue: 100 },
{ id: 'arousal', name: 'Arousal', enabled: true, persistInHistory: false } { id: 'arousal', name: 'Arousal', enabled: true, persistInHistory: false, maxValue: 100 }
], ],
// RPG Attributes (customizable D&D-style attributes) // RPG Attributes (customizable D&D-style attributes)
showRPGAttributes: true, showRPGAttributes: true,
+26 -4
View File
@@ -28,6 +28,7 @@ export function buildUserStatsJSONInstruction() {
const trackerConfig = extensionSettings.trackerConfig; const trackerConfig = extensionSettings.trackerConfig;
const userStatsConfig = trackerConfig?.userStats; const userStatsConfig = trackerConfig?.userStats;
const enabledStats = userStatsConfig?.customStats?.filter(s => s && s.enabled && s.name) || []; const enabledStats = userStatsConfig?.customStats?.filter(s => s && s.enabled && s.name) || [];
const displayMode = userStatsConfig?.statsDisplayMode || 'percentage';
let instruction = '{\n'; let instruction = '{\n';
instruction += ' "stats": [\n'; instruction += ' "stats": [\n';
@@ -36,7 +37,12 @@ export function buildUserStatsJSONInstruction() {
for (let i = 0; i < enabledStats.length; i++) { for (let i = 0; i < enabledStats.length; i++) {
const stat = enabledStats[i]; const stat = enabledStats[i];
const comma = i < enabledStats.length - 1 ? ',' : ''; const comma = i < enabledStats.length - 1 ? ',' : '';
instruction += ` {"id": "${stat.id}", "name": "${stat.name}", "value": X}${comma}\n`; if (displayMode === 'number') {
const maxValue = stat.maxValue || 100;
instruction += ` {"id": "${stat.id}", "name": "${stat.name}", "value": X}${comma} // 0 to ${maxValue}\n`;
} else {
instruction += ` {"id": "${stat.id}", "name": "${stat.name}", "value": X}${comma} // 0 to 100 (percentage)\n`;
}
} }
instruction += ' ],\n'; instruction += ' ],\n';
@@ -45,9 +51,24 @@ export function buildUserStatsJSONInstruction() {
if (userStatsConfig?.statusSection?.enabled) { if (userStatsConfig?.statusSection?.enabled) {
instruction += ' "status": {\n'; instruction += ' "status": {\n';
if (userStatsConfig.statusSection.showMoodEmoji) { if (userStatsConfig.statusSection.showMoodEmoji) {
instruction += ' "mood": "Mood Emoji",\n'; instruction += ' "mood": "Mood Emoji"';
}
// Add all custom status fields
const customFields = userStatsConfig.statusSection.customFields || [];
if (customFields.length > 0) {
for (let i = 0; i < customFields.length; i++) {
const fieldName = customFields[i].toLowerCase();
const fieldKey = toSnakeCase(fieldName);
const comma = (i === customFields.length - 1 && !userStatsConfig.statusSection.showMoodEmoji) ? '' : (userStatsConfig.statusSection.showMoodEmoji || i < customFields.length - 1 ? ',\n' : '\n');
if (i === 0 && userStatsConfig.statusSection.showMoodEmoji) {
instruction += ',\n';
}
instruction += ` "${fieldKey}": "[${fieldName}1, ${fieldName}2]"${comma}`;
}
}
if (!userStatsConfig.statusSection.showMoodEmoji && customFields.length > 0) {
instruction += '\n';
} }
instruction += ' "conditions": "[Condition1, Condition2]"\n';
instruction += ' },\n'; instruction += ' },\n';
} }
@@ -105,7 +126,8 @@ export function buildInfoBoxJSONInstruction() {
let hasFields = false; let hasFields = false;
if (widgets.date?.enabled) { if (widgets.date?.enabled) {
instruction += ' "date": {"value": "Weekday, Month, Year"}'; const dateFormat = widgets.date.format || 'Weekday, Month, Year';
instruction += ` "date": {"value": "${dateFormat}"}`;
hasFields = true; hasFields = true;
} }
+34 -8
View File
@@ -547,9 +547,15 @@ export function parseUserStats(statsText) {
extensionSettings.userStats.mood = statsData.status.mood; extensionSettings.userStats.mood = statsData.status.mood;
// console.log('[RPG Parser] ✓ Set mood =', statsData.status.mood); // console.log('[RPG Parser] ✓ Set mood =', statsData.status.mood);
} }
if (statsData.status.conditions) { // Extract all custom status fields
extensionSettings.userStats.conditions = statsData.status.conditions; const trackerConfig = extensionSettings.trackerConfig;
// console.log('[RPG Parser] ✓ Set conditions =', statsData.status.conditions); const customFields = trackerConfig?.userStats?.statusSection?.customFields || [];
for (const fieldName of customFields) {
const fieldKey = fieldName.toLowerCase();
if (statsData.status[fieldKey]) {
extensionSettings.userStats[fieldKey] = statsData.status[fieldKey];
// console.log(`[RPG Parser] ✓ Set ${fieldKey} =`, statsData.status[fieldKey]);
}
} }
} }
@@ -679,6 +685,7 @@ export function parseUserStats(statsText) {
const statusConfig = trackerConfig?.userStats?.statusSection; const statusConfig = trackerConfig?.userStats?.statusSection;
if (statusConfig?.enabled) { if (statusConfig?.enabled) {
let moodMatch = null; let moodMatch = null;
const customFields = statusConfig.customFields || [];
// Try Status: format // Try Status: format
const statusMatch = statsText.match(/Status:\s*(.+)/i); const statusMatch = statsText.match(/Status:\s*(.+)/i);
@@ -691,14 +698,30 @@ export function parseUserStats(statsText) {
if (emoji) { if (emoji) {
extensionSettings.userStats.mood = emoji; extensionSettings.userStats.mood = emoji;
// Remaining text contains custom status fields // Remaining text contains custom status fields
if (text) { if (text && customFields.length > 0) {
extensionSettings.userStats.conditions = text; // For first custom field, use the remaining text
const firstFieldKey = customFields[0].toLowerCase();
extensionSettings.userStats[firstFieldKey] = text;
} }
moodMatch = true; moodMatch = true;
} }
} else { } else {
// No mood emoji, whole status is conditions // No mood emoji, whole status goes to first custom field
extensionSettings.userStats.conditions = statusContent; if (customFields.length > 0) {
const firstFieldKey = customFields[0].toLowerCase();
extensionSettings.userStats[firstFieldKey] = statusContent;
}
moodMatch = true;
}
}
// Try to extract individual custom status fields by name
for (const fieldName of customFields) {
const fieldKey = fieldName.toLowerCase();
const fieldRegex = new RegExp(`${fieldName}:\\s*(.+?)(?:,|$)`, 'i');
const fieldMatch = statsText.match(fieldRegex);
if (fieldMatch) {
extensionSettings.userStats[fieldKey] = fieldMatch[1].trim();
moodMatch = true; moodMatch = true;
} }
} }
@@ -706,7 +729,10 @@ export function parseUserStats(statsText) {
debugLog('[RPG Parser] Status match:', { debugLog('[RPG Parser] Status match:', {
found: !!moodMatch, found: !!moodMatch,
mood: extensionSettings.userStats.mood, mood: extensionSettings.userStats.mood,
conditions: extensionSettings.userStats.conditions customFields: customFields.map(f => ({
name: f,
value: extensionSettings.userStats[f.toLowerCase()]
}))
}); });
} }
+26 -7
View File
@@ -485,11 +485,22 @@ function formatTrackerDataForContext(jsonData, trackerType, userName) {
// Handle common object formats // Handle common object formats
if (field && typeof field === 'object') { if (field && typeof field === 'object') {
// Status object: {mood, conditions} // Status object: {mood, [customFields...]}
if ('mood' in field && 'conditions' in field) { if ('mood' in field) {
const statusParts = [];
const mood = getValue(field.mood); const mood = getValue(field.mood);
const conditions = getValue(field.conditions); if (mood) statusParts.push(mood);
return `${mood} - ${conditions}`;
// Add all other status fields (custom fields)
for (const [key, value] of Object.entries(field)) {
if (key !== 'mood') {
const fieldValue = getValue(value);
if (fieldValue && fieldValue !== 'None') {
statusParts.push(fieldValue);
}
}
}
return statusParts.join(' - ');
} }
// Skill/item/quest objects: {name}, {title}, {name, quantity} // Skill/item/quest objects: {name}, {title}, {name, quantity}
@@ -830,9 +841,17 @@ export function formatHistoricalTrackerData(trackerData, trackerConfig, userName
// Status section // Status section
if (shouldInclude(userStatsConfig.statusSection) && userStatsData.status) { if (shouldInclude(userStatsConfig.statusSection) && userStatsData.status) {
const mood = getValue(userStatsData.status.mood || userStatsData.status); const mood = getValue(userStatsData.status.mood || userStatsData.status);
const conditions = getValue(userStatsData.status.conditions); if (mood && userStatsConfig.statusSection.showMoodEmoji) statsFormatted += `Mood: ${mood}, `;
if (mood) statsFormatted += `Mood: ${mood}, `;
if (conditions && conditions !== 'None') statsFormatted += `Conditions: ${conditions}, `; // Add all custom status fields
const customFields = userStatsConfig.statusSection.customFields || [];
for (const fieldName of customFields) {
const fieldKey = fieldName.toLowerCase();
const fieldValue = getValue(userStatsData.status[fieldKey]);
if (fieldValue && fieldValue !== 'None') {
statsFormatted += `${fieldName}: ${fieldValue}, `;
}
}
} }
// Skills section // Skills section
+62 -19
View File
@@ -105,7 +105,8 @@ function updateUserStatsData() {
// Then, add any other numeric stats from extensionSettings that aren't in config // Then, add any other numeric stats from extensionSettings that aren't in config
// (these could be custom stats the AI added or disabled stats) // (these could be custom stats the AI added or disabled stats)
const excludeFields = new Set(['mood', 'conditions', 'inventory', 'skills', 'level']); const customFields = config.statusSection?.customFields || [];
const excludeFields = new Set(['mood', ...customFields.map(f => f.toLowerCase()), 'inventory', 'skills', 'level']);
Object.entries(stats).forEach(([key, value]) => { Object.entries(stats).forEach(([key, value]) => {
if (!processedIds.has(key) && !excludeFields.has(key) && typeof value === 'number') { if (!processedIds.has(key) && !excludeFields.has(key) && typeof value === 'number') {
statsArray.push({ statsArray.push({
@@ -118,12 +119,17 @@ function updateUserStatsData() {
jsonData.stats = statsArray; jsonData.stats = statsArray;
// Update status // Update status - include all custom status fields
jsonData.status = { jsonData.status = {
mood: stats.mood || '😐', mood: stats.mood || '😐'
conditions: stats.conditions || 'None'
}; };
// Add all custom status fields
for (const fieldName of customFields) {
const fieldKey = fieldName.toLowerCase();
jsonData.status[fieldKey] = stats[fieldKey] || 'None';
}
// Update inventory (convert to v3 format) // Update inventory (convert to v3 format)
const convertToV3Items = (itemString) => { const convertToV3Items = (itemString) => {
if (!itemString) return []; if (!itemString) return [];
@@ -276,16 +282,33 @@ export function renderUserStats() {
} }
html += '<div class="rpg-stats-grid">'; html += '<div class="rpg-stats-grid">';
const enabledStats = config.customStats.filter(stat => stat && stat.enabled && stat.name && stat.id); const enabledStats = config.customStats.filter(stat => stat && stat.enabled && stat.name && stat.id);
const displayMode = config.statsDisplayMode || 'percentage';
for (const stat of enabledStats) { for (const stat of enabledStats) {
const value = stats[stat.id] !== undefined ? stats[stat.id] : 100; const value = stats[stat.id] !== undefined ? stats[stat.id] : 100;
const maxValue = stat.maxValue || 100;
// Calculate percentage for bar fill
let percentage;
let displayValue;
if (displayMode === 'number') {
// In number mode, value is already the number (0 to maxValue)
percentage = maxValue > 0 ? (value / maxValue) * 100 : 100;
displayValue = `${value}/${maxValue}`;
} else {
// In percentage mode, value is 0-100
percentage = value;
displayValue = `${value}%`;
}
html += ` html += `
<div class="rpg-stat-row"> <div class="rpg-stat-row">
<span class="rpg-stat-label rpg-editable-stat-name" contenteditable="true" data-field="${stat.id}" title="Click to edit stat name">${stat.name}:</span> <span class="rpg-stat-label rpg-editable-stat-name" contenteditable="true" data-field="${stat.id}" title="Click to edit stat name">${stat.name}:</span>
<div class="rpg-stat-bar" style="background: ${gradient}"> <div class="rpg-stat-bar" style="background: ${gradient}">
<div class="rpg-stat-fill" style="width: ${100 - value}%"></div> <div class="rpg-stat-fill" style="width: ${100 - percentage}%"></div>
</div> </div>
<span class="rpg-stat-value rpg-editable-stat" contenteditable="true" data-field="${stat.id}" title="Click to edit">${value}%</span> <span class="rpg-stat-value rpg-editable-stat" contenteditable="true" data-field="${stat.id}" data-max="${maxValue}" data-mode="${displayMode}" title="Click to edit">${displayValue}</span>
</div> </div>
`; `;
} }
@@ -308,13 +331,15 @@ export function renderUserStats() {
// Render custom status fields // Render custom status fields
if (config.statusSection.customFields && config.statusSection.customFields.length > 0) { if (config.statusSection.customFields && config.statusSection.customFields.length > 0) {
// For now, use first field as "conditions" for backward compatibility for (const fieldName of config.statusSection.customFields) {
let conditionsValue = stats.conditions || 'None'; const fieldKey = fieldName.toLowerCase();
// Strip brackets if present (from JSON array format) let fieldValue = stats[fieldKey] || 'None';
if (typeof conditionsValue === 'string') { // Strip brackets if present (from JSON array format)
conditionsValue = conditionsValue.replace(/^\[|\]$/g, '').trim(); if (typeof fieldValue === 'string') {
fieldValue = fieldValue.replace(/^\[|\]$/g, '').trim();
}
html += `<div class="rpg-mood-conditions rpg-editable" contenteditable="true" data-field="${fieldKey}" title="Click to edit ${fieldName}">${fieldValue}</div>`;
} }
html += `<div class="rpg-mood-conditions rpg-editable" contenteditable="true" data-field="conditions" title="Click to edit conditions">${conditionsValue}</div>`;
} }
html += '</div>'; html += '</div>';
@@ -406,14 +431,31 @@ export function renderUserStats() {
// Add event listeners for editable stat values // Add event listeners for editable stat values
$('.rpg-editable-stat').on('blur', function() { $('.rpg-editable-stat').on('blur', function() {
const field = $(this).data('field'); const field = $(this).data('field');
const textValue = $(this).text().replace('%', '').trim(); const mode = $(this).data('mode');
let value = parseInt(textValue); const maxValue = parseInt($(this).data('max')) || 100;
const textValue = $(this).text().trim();
let value;
// Validate and clamp value between 0 and 100 if (mode === 'number') {
if (isNaN(value)) { // In number mode, parse "X/MAX" or just "X"
value = 0; const parts = textValue.split('/');
value = parseInt(parts[0]);
// Validate and clamp value between 0 and maxValue
if (isNaN(value)) {
value = 0;
}
value = Math.max(0, Math.min(maxValue, value));
} else {
// In percentage mode, parse "X%" or just "X"
value = parseInt(textValue.replace('%', ''));
// Validate and clamp value between 0 and 100
if (isNaN(value)) {
value = 0;
}
value = Math.max(0, Math.min(100, value));
} }
value = Math.max(0, Math.min(100, value));
// Update the setting // Update the setting
extensionSettings.userStats[field] = value; extensionSettings.userStats[field] = value;
@@ -445,7 +487,8 @@ export function renderUserStats() {
$('.rpg-mood-conditions.rpg-editable').on('blur', function() { $('.rpg-mood-conditions.rpg-editable').on('blur', function() {
const value = $(this).text().trim(); const value = $(this).text().trim();
extensionSettings.userStats.conditions = value || 'None'; const fieldKey = $(this).data('field');
extensionSettings.userStats[fieldKey] = value || 'None';
// Update userStats data (maintains JSON or text format) // Update userStats data (maintains JSON or text format)
updateUserStatsData(); updateUserStatsData();
+30 -4
View File
@@ -729,13 +729,27 @@ function renderUserStatsTab() {
// Custom Stats section // Custom Stats section
html += `<h4><i class="fa-solid fa-heart-pulse"></i> ${i18n.getTranslation('template.trackerEditorModal.userStatsTab.customStatsTitle')}</h4>`; html += `<h4><i class="fa-solid fa-heart-pulse"></i> ${i18n.getTranslation('template.trackerEditorModal.userStatsTab.customStatsTitle')}</h4>`;
// Stats display mode toggle
const statsDisplayMode = config.statsDisplayMode || 'percentage';
html += '<div class="rpg-editor-toggle-row">';
html += '<label>Display Mode:</label>';
html += '<div class="rpg-radio-group">';
html += `<label><input type="radio" name="stats-display-mode" value="percentage" ${statsDisplayMode === 'percentage' ? 'checked' : ''}> Percentage</label>`;
html += `<label><input type="radio" name="stats-display-mode" value="number" ${statsDisplayMode === 'number' ? 'checked' : ''}> Number</label>`;
html += '</div>';
html += '</div>';
html += '<div class="rpg-editor-stats-list" id="rpg-editor-stats-list">'; html += '<div class="rpg-editor-stats-list" id="rpg-editor-stats-list">';
config.customStats.forEach((stat, index) => { config.customStats.forEach((stat, index) => {
const showMaxValue = statsDisplayMode === 'number';
const maxValue = stat.maxValue || 100;
html += ` html += `
<div class="rpg-editor-stat-item" data-index="${index}"> <div class="rpg-editor-stat-item" data-index="${index}">
<input type="checkbox" ${stat.enabled ? 'checked' : ''} class="rpg-stat-toggle" data-index="${index}"> <input type="checkbox" ${stat.enabled ? 'checked' : ''} class="rpg-stat-toggle" data-index="${index}">
<input type="text" value="${stat.name}" class="rpg-stat-name" data-index="${index}" placeholder="Stat Name"> <input type="text" value="${stat.name}" class="rpg-stat-name" data-index="${index}" placeholder="Stat Name">
<input type="number" value="${maxValue}" class="rpg-stat-max ${showMaxValue ? '' : 'rpg-hidden'}" data-index="${index}" placeholder="Max" min="1" step="1" title="Maximum value">
<button class="rpg-stat-remove" data-index="${index}" title="Remove stat"><i class="fa-solid fa-trash"></i></button> <button class="rpg-stat-remove" data-index="${index}" title="Remove stat"><i class="fa-solid fa-trash"></i></button>
</div> </div>
`; `;
@@ -845,7 +859,8 @@ function setupUserStatsListeners() {
extensionSettings.trackerConfig.userStats.customStats.push({ extensionSettings.trackerConfig.userStats.customStats.push({
id: newId, id: newId,
name: 'New Stat', name: 'New Stat',
enabled: true enabled: true,
maxValue: 100
}); });
// Initialize value if doesn't exist // Initialize value if doesn't exist
if (extensionSettings.userStats[newId] === undefined) { if (extensionSettings.userStats[newId] === undefined) {
@@ -873,6 +888,19 @@ function setupUserStatsListeners() {
extensionSettings.trackerConfig.userStats.customStats[index].name = $(this).val(); extensionSettings.trackerConfig.userStats.customStats[index].name = $(this).val();
}); });
// Change stat max value
$('.rpg-stat-max').off('blur').on('blur', function() {
const index = $(this).data('index');
const value = parseInt($(this).val()) || 100;
extensionSettings.trackerConfig.userStats.customStats[index].maxValue = Math.max(1, value);
});
// Stats display mode toggle
$('input[name="stats-display-mode"]').off('change').on('change', function() {
extensionSettings.trackerConfig.userStats.statsDisplayMode = $(this).val();
renderUserStatsTab(); // Re-render to show/hide max value fields
});
// Add attribute // Add attribute
$('#rpg-add-attr').off('click').on('click', function() { $('#rpg-add-attr').off('click').on('click', function() {
// Ensure rpgAttributes array exists with defaults if needed // Ensure rpgAttributes array exists with defaults if needed
@@ -979,9 +1007,7 @@ function renderInfoBoxTab() {
html += `<label for="rpg-widget-date">${i18n.getTranslation('template.trackerEditorModal.infoBoxTab.dateWidget')}</label>`; html += `<label for="rpg-widget-date">${i18n.getTranslation('template.trackerEditorModal.infoBoxTab.dateWidget')}</label>`;
html += '<select id="rpg-date-format" class="rpg-select-mini">'; html += '<select id="rpg-date-format" class="rpg-select-mini">';
html += `<option value="Weekday, Month, Year" ${config.widgets.date.format === 'Weekday, Month, Year' ? 'selected' : ''}>Weekday, Month, Year</option>`; html += `<option value="Weekday, Month, Year" ${config.widgets.date.format === 'Weekday, Month, Year' ? 'selected' : ''}>Weekday, Month, Year</option>`;
html += `<option value="dd/mm/yyyy" ${config.widgets.date.format === 'dd/mm/yyyy' ? 'selected' : ''}>dd/mm/yyyy</option>`; html += `<option value="Day (Numerical), Month, Year" ${config.widgets.date.format === 'Day (Numerical), Month, Year' ? 'selected' : ''}>Day (Numerical), Month, Year</option>`;
html += `<option value="mm/dd/yyyy" ${config.widgets.date.format === 'mm/dd/yyyy' ? 'selected' : ''}>mm/dd/yyyy</option>`;
html += `<option value="yyyy-mm-dd" ${config.widgets.date.format === 'yyyy-mm-dd' ? 'selected' : ''}>yyyy-mm-dd</option>`;
html += '</select>'; html += '</select>';
html += '</div>'; html += '</div>';
+97 -5
View File
@@ -1393,7 +1393,8 @@ body:has(.rpg-panel.rpg-position-left) #sheld {
max-width: 100%; max-width: 100%;
line-height: 1.1; line-height: 1.1;
min-width: 0; min-width: 0;
overflow: hidden; overflow-y: auto;
overflow-x: hidden;
} }
.rpg-calendar-day-text { .rpg-calendar-day-text {
@@ -1418,6 +1419,24 @@ body:has(.rpg-panel.rpg-position-left) #sheld {
overflow: hidden; overflow: hidden;
} }
/* Minimal scrollbar styling for calendar day */
.rpg-calendar-day::-webkit-scrollbar {
width: 3px;
}
.rpg-calendar-day::-webkit-scrollbar-track {
background: transparent;
}
.rpg-calendar-day::-webkit-scrollbar-thumb {
background: rgba(255, 255, 255, 0.2);
border-radius: 2px;
}
.rpg-calendar-day::-webkit-scrollbar-thumb:hover {
background: rgba(255, 255, 255, 0.3);
}
/* Weather Widget */ /* Weather Widget */
.rpg-weather-widget { .rpg-weather-widget {
display: flex; display: flex;
@@ -1588,6 +1607,12 @@ body:has(.rpg-panel.rpg-position-left) #sheld {
justify-content: center; justify-content: center;
gap: 0.25em; gap: 0.25em;
margin-top: 0.25em; margin-top: 0.25em;
overflow-x: auto;
overflow-y: hidden;
max-width: 100%;
white-space: nowrap;
scrollbar-width: thin;
scrollbar-color: rgba(255, 255, 255, 0.2) transparent;
} }
.rpg-time-range .rpg-time-value { .rpg-time-range .rpg-time-value {
@@ -1601,6 +1626,24 @@ body:has(.rpg-panel.rpg-position-left) #sheld {
opacity: 0.7; opacity: 0.7;
} }
/* Minimal scrollbar styling for time range display */
.rpg-time-range::-webkit-scrollbar {
height: 3px;
}
.rpg-time-range::-webkit-scrollbar-track {
background: transparent;
}
.rpg-time-range::-webkit-scrollbar-thumb {
background: rgba(255, 255, 255, 0.2);
border-radius: 2px;
}
.rpg-time-range::-webkit-scrollbar-thumb:hover {
background: rgba(255, 255, 255, 0.3);
}
/* Location Widget - Map */ /* Location Widget - Map */
.rpg-map-bg { .rpg-map-bg {
width: 100%; width: 100%;
@@ -1650,10 +1693,27 @@ body:has(.rpg-panel.rpg-position-left) #sheld {
hyphens: auto; hyphens: auto;
flex: 1 1 auto; flex: 1 1 auto;
min-height: 0; min-height: 0;
overflow: hidden; overflow-y: auto;
display: -webkit-box; overflow-x: hidden;
-webkit-line-clamp: 2; max-height: 100%;
-webkit-box-orient: vertical; }
/* Minimal scrollbar styling for location text */
.rpg-location-text::-webkit-scrollbar {
width: 3px;
}
.rpg-location-text::-webkit-scrollbar-track {
background: transparent;
}
.rpg-location-text::-webkit-scrollbar-thumb {
background: rgba(255, 255, 255, 0.2);
border-radius: 2px;
}
.rpg-location-text::-webkit-scrollbar-thumb:hover {
background: rgba(255, 255, 255, 0.3);
} }
/* Row 3: Recent Events */ /* Row 3: Recent Events */
@@ -4203,6 +4263,34 @@ body:has(.rpg-panel.rpg-position-left) #sheld {
font-size: 0.95em; font-size: 0.95em;
} }
.rpg-stat-max {
width: 5em;
padding: 0.375em 0.5em;
background: var(--rpg-bg);
border: 1px solid var(--rpg-border);
border-radius: 0.25em;
color: var(--rpg-text);
font-size: 0.95em;
text-align: center;
}
.rpg-hidden {
display: none !important;
}
.rpg-radio-group {
display: flex;
gap: 1em;
align-items: center;
}
.rpg-radio-group label {
display: flex;
align-items: center;
gap: 0.375em;
cursor: pointer;
}
.rpg-stat-remove, .rpg-stat-remove,
.rpg-attr-remove, .rpg-attr-remove,
.rpg-remove-relationship { .rpg-remove-relationship {
@@ -5742,6 +5830,10 @@ body:has(.rpg-panel.rpg-mobile-open) .rpg-fab-widget-container {
.rpg-time-range { .rpg-time-range {
gap: 0.15em; gap: 0.15em;
overflow-x: auto;
overflow-y: hidden;
max-width: 100%;
white-space: nowrap;
} }
.rpg-time-separator { .rpg-time-separator {
+1 -1
View File
@@ -547,7 +547,7 @@
<!-- Desktop Strip Widgets Section --> <!-- Desktop Strip Widgets Section -->
<div class="rpg-settings-group"> <div class="rpg-settings-group">
<h4 data-i18n-key="template.settingsModal.desktopStripTitle"><i class="fa-solid fa-bars-staggered" aria-hidden="true"></i> Desktop Collapsed Strip Widgets</h4> <h4 data-i18n-key="template.settingsModal.desktopStripTitle">Desktop Collapsed Strip Widgets</h4>
<small class="notes" style="display: block; margin-bottom: 10px;" <small class="notes" style="display: block; margin-bottom: 10px;"
data-i18n-key="template.settingsModal.desktopStripNote"> data-i18n-key="template.settingsModal.desktopStripNote">
Show compact info widgets in the collapsed panel strip on desktop. Displays stats vertically without needing to expand the panel. Show compact info widgets in the collapsed panel strip on desktop. Displays stats vertically without needing to expand the panel.