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:
@@ -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
@@ -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
@@ -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>
|
||||||
|
|||||||
@@ -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
@@ -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,
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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()]
|
||||||
|
}))
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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();
|
||||||
|
|||||||
@@ -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>';
|
||||||
|
|
||||||
|
|||||||
@@ -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 {
|
||||||
@@ -11303,7 +11395,7 @@ body:has(.rpg-panel[data-theme="light"]) .rpg-strip-widget {
|
|||||||
.rpg-strip-widget-container {
|
.rpg-strip-widget-container {
|
||||||
display: none !important;
|
display: none !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.rpg-panel.rpg-collapsed.rpg-strip-widgets-enabled {
|
.rpg-panel.rpg-collapsed.rpg-strip-widgets-enabled {
|
||||||
max-width: 2.5rem !important;
|
max-width: 2.5rem !important;
|
||||||
min-width: 2.5rem !important;
|
min-width: 2.5rem !important;
|
||||||
|
|||||||
+1
-1
@@ -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.
|
||||||
|
|||||||
Reference in New Issue
Block a user