feat: Add desktop collapsed strip widgets
- Add new desktopStripWidgets settings in state.js with toggles for weather, clock, date, location, stats, and attributes - Add strip widget container in template.html with animated clock face - Add CSS styles for strip widgets with wider collapsed panel (5rem), vertical layout, and theme support - Implement updateStripWidgets() in desktop.js to populate widgets from tracker data - Wire up settings handlers in index.js for all strip widget toggles - Call updateStripWidgets() on data updates in sillytavern.js integration - Trigger widget update when panel is collapsed in layout.js The strip widgets display compact stats/info in the collapsed panel strip on desktop, similar to mobile FAB widgets, eliminating the need to expand the panel to view basic data.
This commit is contained in:
@@ -131,7 +131,8 @@ import {
|
||||
} from './src/systems/ui/mobile.js';
|
||||
import {
|
||||
setupDesktopTabs,
|
||||
removeDesktopTabs
|
||||
removeDesktopTabs,
|
||||
updateStripWidgets
|
||||
} from './src/systems/ui/desktop.js';
|
||||
|
||||
// Feature modules
|
||||
@@ -726,6 +727,63 @@ async function initUI() {
|
||||
updateFabWidgets();
|
||||
});
|
||||
|
||||
// Desktop Strip Widget toggles
|
||||
$('#rpg-toggle-strip-widgets-enabled').on('change', function() {
|
||||
if (!extensionSettings.desktopStripWidgets) extensionSettings.desktopStripWidgets = {};
|
||||
extensionSettings.desktopStripWidgets.enabled = $(this).prop('checked');
|
||||
saveSettings();
|
||||
updateStripWidgets();
|
||||
$('#rpg-strip-widget-options').toggle(extensionSettings.desktopStripWidgets.enabled);
|
||||
});
|
||||
|
||||
$('#rpg-toggle-strip-weather-icon').on('change', function() {
|
||||
if (!extensionSettings.desktopStripWidgets) extensionSettings.desktopStripWidgets = {};
|
||||
if (!extensionSettings.desktopStripWidgets.weatherIcon) extensionSettings.desktopStripWidgets.weatherIcon = {};
|
||||
extensionSettings.desktopStripWidgets.weatherIcon.enabled = $(this).prop('checked');
|
||||
saveSettings();
|
||||
updateStripWidgets();
|
||||
});
|
||||
|
||||
$('#rpg-toggle-strip-clock').on('change', function() {
|
||||
if (!extensionSettings.desktopStripWidgets) extensionSettings.desktopStripWidgets = {};
|
||||
if (!extensionSettings.desktopStripWidgets.clock) extensionSettings.desktopStripWidgets.clock = {};
|
||||
extensionSettings.desktopStripWidgets.clock.enabled = $(this).prop('checked');
|
||||
saveSettings();
|
||||
updateStripWidgets();
|
||||
});
|
||||
|
||||
$('#rpg-toggle-strip-date').on('change', function() {
|
||||
if (!extensionSettings.desktopStripWidgets) extensionSettings.desktopStripWidgets = {};
|
||||
if (!extensionSettings.desktopStripWidgets.date) extensionSettings.desktopStripWidgets.date = {};
|
||||
extensionSettings.desktopStripWidgets.date.enabled = $(this).prop('checked');
|
||||
saveSettings();
|
||||
updateStripWidgets();
|
||||
});
|
||||
|
||||
$('#rpg-toggle-strip-location').on('change', function() {
|
||||
if (!extensionSettings.desktopStripWidgets) extensionSettings.desktopStripWidgets = {};
|
||||
if (!extensionSettings.desktopStripWidgets.location) extensionSettings.desktopStripWidgets.location = {};
|
||||
extensionSettings.desktopStripWidgets.location.enabled = $(this).prop('checked');
|
||||
saveSettings();
|
||||
updateStripWidgets();
|
||||
});
|
||||
|
||||
$('#rpg-toggle-strip-stats').on('change', function() {
|
||||
if (!extensionSettings.desktopStripWidgets) extensionSettings.desktopStripWidgets = {};
|
||||
if (!extensionSettings.desktopStripWidgets.stats) extensionSettings.desktopStripWidgets.stats = {};
|
||||
extensionSettings.desktopStripWidgets.stats.enabled = $(this).prop('checked');
|
||||
saveSettings();
|
||||
updateStripWidgets();
|
||||
});
|
||||
|
||||
$('#rpg-toggle-strip-attributes').on('change', function() {
|
||||
if (!extensionSettings.desktopStripWidgets) extensionSettings.desktopStripWidgets = {};
|
||||
if (!extensionSettings.desktopStripWidgets.attributes) extensionSettings.desktopStripWidgets.attributes = {};
|
||||
extensionSettings.desktopStripWidgets.attributes.enabled = $(this).prop('checked');
|
||||
saveSettings();
|
||||
updateStripWidgets();
|
||||
});
|
||||
|
||||
$('#rpg-manual-update').on('click', async function() {
|
||||
if (!extensionSettings.enabled) {
|
||||
// console.log('[RPG Companion] Extension is disabled. Please enable it in the Extensions tab.');
|
||||
@@ -963,6 +1021,18 @@ async function initUI() {
|
||||
// Toggle visibility of widget options based on master toggle
|
||||
$('#rpg-fab-widget-options').toggle(fabWidgets.enabled || false);
|
||||
|
||||
// Initialize Desktop Strip Widget checkboxes
|
||||
const stripWidgets = extensionSettings.desktopStripWidgets || {};
|
||||
$('#rpg-toggle-strip-widgets-enabled').prop('checked', stripWidgets.enabled || false);
|
||||
$('#rpg-toggle-strip-weather-icon').prop('checked', stripWidgets.weatherIcon?.enabled ?? true);
|
||||
$('#rpg-toggle-strip-clock').prop('checked', stripWidgets.clock?.enabled ?? true);
|
||||
$('#rpg-toggle-strip-date').prop('checked', stripWidgets.date?.enabled ?? true);
|
||||
$('#rpg-toggle-strip-location').prop('checked', stripWidgets.location?.enabled ?? true);
|
||||
$('#rpg-toggle-strip-stats').prop('checked', stripWidgets.stats?.enabled ?? true);
|
||||
$('#rpg-toggle-strip-attributes').prop('checked', stripWidgets.attributes?.enabled ?? true);
|
||||
// Toggle visibility of strip widget options based on master toggle
|
||||
$('#rpg-strip-widget-options').toggle(stripWidgets.enabled || false);
|
||||
|
||||
$('#rpg-stat-bar-color-low').val(extensionSettings.statBarColorLow);
|
||||
$('#rpg-stat-bar-color-high').val(extensionSettings.statBarColorHigh);
|
||||
$('#rpg-theme-select').val(extensionSettings.theme);
|
||||
@@ -1106,8 +1176,9 @@ jQuery(async () => {
|
||||
// Load chat-specific data for current chat
|
||||
try {
|
||||
loadChatData();
|
||||
// Initialize FAB widgets with any loaded data
|
||||
// Initialize FAB widgets and strip widgets with any loaded data
|
||||
updateFabWidgets();
|
||||
updateStripWidgets();
|
||||
} catch (error) {
|
||||
console.error('[RPG Companion] Chat data load failed, using defaults:', error);
|
||||
}
|
||||
|
||||
@@ -84,6 +84,16 @@ export let extensionSettings = {
|
||||
stats: { enabled: true, position: 5 }, // All stats as compact numbers
|
||||
attributes: { enabled: true, position: 6 } // Compact RPG attributes display
|
||||
},
|
||||
// Desktop strip widget display options (shown in collapsed panel strip)
|
||||
desktopStripWidgets: {
|
||||
enabled: false, // Master toggle for strip widgets (disabled by default)
|
||||
weatherIcon: { enabled: true }, // Weather emoji (☀️, 🌧️, etc.)
|
||||
clock: { enabled: true }, // Current time display
|
||||
date: { enabled: true }, // Date display
|
||||
location: { enabled: true }, // Location name
|
||||
stats: { enabled: true }, // All stats as compact numbers
|
||||
attributes: { enabled: true } // Compact RPG attributes display
|
||||
},
|
||||
userStats: JSON.stringify({
|
||||
stats: [
|
||||
{ id: 'health', name: 'Health', value: 100 },
|
||||
|
||||
@@ -45,6 +45,7 @@ import { getSafeThumbnailUrl } from '../../utils/avatars.js';
|
||||
|
||||
// UI
|
||||
import { setFabLoadingState, updateFabWidgets } from '../ui/mobile.js';
|
||||
import { updateStripWidgets } from '../ui/desktop.js';
|
||||
|
||||
// Chapter checkpoint
|
||||
import { updateAllCheckpointIndicators } from '../ui/checkpointUI.js';
|
||||
@@ -232,8 +233,9 @@ export async function onMessageReceived(data) {
|
||||
renderQuests();
|
||||
renderMusicPlayer($musicPlayerContainer[0]);
|
||||
|
||||
// Update FAB widgets with newly parsed data
|
||||
// Update FAB widgets and strip widgets with newly parsed data
|
||||
updateFabWidgets();
|
||||
updateStripWidgets();
|
||||
|
||||
// Then update the DOM to reflect the cleaned message
|
||||
// Using updateMessageBlock to perform macro substitutions + regex formatting
|
||||
@@ -266,9 +268,10 @@ export async function onMessageReceived(data) {
|
||||
if (extensionSettings.autoUpdate && isAwaitingNewMessage) {
|
||||
setTimeout(async () => {
|
||||
await updateRPGData(renderUserStats, renderInfoBox, renderThoughts, renderInventory);
|
||||
// Update FAB widgets after separate/external mode update completes
|
||||
// Update FAB widgets and strip widgets after separate/external mode update completes
|
||||
setFabLoadingState(false);
|
||||
updateFabWidgets();
|
||||
updateStripWidgets();
|
||||
}, 500);
|
||||
}
|
||||
}
|
||||
@@ -294,6 +297,7 @@ export async function onMessageReceived(data) {
|
||||
// Stop FAB loading state and update widgets
|
||||
setFabLoadingState(false);
|
||||
updateFabWidgets();
|
||||
updateStripWidgets();
|
||||
|
||||
// Re-apply checkpoint in case SillyTavern unhid messages during generation
|
||||
await restoreCheckpointOnLoad();
|
||||
@@ -332,8 +336,9 @@ export function onCharacterChanged() {
|
||||
renderQuests();
|
||||
renderMusicPlayer($musicPlayerContainer[0]);
|
||||
|
||||
// Update FAB widgets with loaded data
|
||||
// Update FAB widgets and strip widgets with loaded data
|
||||
updateFabWidgets();
|
||||
updateStripWidgets();
|
||||
|
||||
// Update chat thought overlays
|
||||
updateChatThoughts();
|
||||
@@ -501,8 +506,9 @@ export function onMessageDeleted(messageIndex) {
|
||||
renderQuests();
|
||||
renderMusicPlayer($musicPlayerContainer[0]);
|
||||
|
||||
// Update FAB widgets
|
||||
// Update FAB widgets and strip widgets
|
||||
updateFabWidgets();
|
||||
updateStripWidgets();
|
||||
|
||||
// Update chat thought overlays (removes any remaining)
|
||||
updateChatThoughts();
|
||||
@@ -555,8 +561,9 @@ export function onMessageDeleted(messageIndex) {
|
||||
renderQuests();
|
||||
renderMusicPlayer($musicPlayerContainer[0]);
|
||||
|
||||
// Update FAB widgets
|
||||
// Update FAB widgets and strip widgets
|
||||
updateFabWidgets();
|
||||
updateStripWidgets();
|
||||
|
||||
// Update chat thought overlays
|
||||
updateChatThoughts();
|
||||
@@ -591,8 +598,9 @@ export function onMessageDeleted(messageIndex) {
|
||||
renderQuests();
|
||||
renderMusicPlayer($musicPlayerContainer[0]);
|
||||
|
||||
// Update FAB widgets
|
||||
// Update FAB widgets and strip widgets
|
||||
updateFabWidgets();
|
||||
updateStripWidgets();
|
||||
|
||||
// Update chat thought overlays
|
||||
updateChatThoughts();
|
||||
|
||||
+265
-2
@@ -1,10 +1,273 @@
|
||||
/**
|
||||
* Desktop UI Module
|
||||
* Handles desktop-specific UI functionality: tab navigation
|
||||
* Handles desktop-specific UI functionality: tab navigation and strip widgets
|
||||
*/
|
||||
|
||||
import { i18n } from '../../core/i18n.js';
|
||||
import { extensionSettings } from '../../core/state.js';
|
||||
import { extensionSettings, lastGeneratedData, committedTrackerData } from '../../core/state.js';
|
||||
|
||||
/**
|
||||
* Helper to parse time string and calculate clock hand angles
|
||||
*/
|
||||
function parseTimeForClock(timeStr) {
|
||||
const timeMatch = timeStr.match(/(\d+):(\d+)/);
|
||||
if (timeMatch) {
|
||||
const hours = parseInt(timeMatch[1]);
|
||||
const minutes = parseInt(timeMatch[2]);
|
||||
const hourAngle = (hours % 12) * 30 + minutes * 0.5; // 30° per hour + 0.5° per minute
|
||||
const minuteAngle = minutes * 6; // 6° per minute
|
||||
return { hourAngle, minuteAngle };
|
||||
}
|
||||
return { hourAngle: 0, minuteAngle: 0 };
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the desktop strip widgets display based on current tracker data and settings.
|
||||
* Strip widgets are shown vertically in the collapsed panel strip.
|
||||
*/
|
||||
export function updateStripWidgets() {
|
||||
const $panel = $('#rpg-companion-panel');
|
||||
const $container = $('#rpg-strip-widget-container');
|
||||
|
||||
if ($panel.length === 0 || $container.length === 0) return;
|
||||
|
||||
// Check if strip widgets are enabled
|
||||
const widgetSettings = extensionSettings.desktopStripWidgets;
|
||||
if (!widgetSettings || !widgetSettings.enabled) {
|
||||
$panel.removeClass('rpg-strip-widgets-enabled');
|
||||
$container.find('.rpg-strip-widget').removeClass('rpg-strip-widget-visible');
|
||||
return;
|
||||
}
|
||||
|
||||
// Add enabled class to panel for CSS styling (wider collapsed width)
|
||||
$panel.addClass('rpg-strip-widgets-enabled');
|
||||
|
||||
// Get tracker data - use imported state directly
|
||||
const infoBox = lastGeneratedData?.infoBox || committedTrackerData?.infoBox;
|
||||
|
||||
// Parse infoBox if it's a string
|
||||
let infoData = null;
|
||||
if (infoBox) {
|
||||
try {
|
||||
infoData = typeof infoBox === 'string' ? JSON.parse(infoBox) : infoBox;
|
||||
} catch (e) {
|
||||
console.warn('[RPG Strip Widgets] Failed to parse infoBox:', e);
|
||||
}
|
||||
}
|
||||
|
||||
// Weather Icon Widget (with description)
|
||||
const $weatherWidget = $container.find('.rpg-strip-widget-weather');
|
||||
if (widgetSettings.weatherIcon?.enabled && infoData?.weather?.emoji) {
|
||||
$weatherWidget.find('.rpg-strip-widget-icon').text(infoData.weather.emoji);
|
||||
// Show weather description truncated
|
||||
const forecast = infoData.weather.forecast || '';
|
||||
const displayForecast = forecast.length > 12 ? forecast.substring(0, 10) + '…' : forecast;
|
||||
$weatherWidget.find('.rpg-strip-widget-desc').text(displayForecast);
|
||||
$weatherWidget.attr('title', forecast || 'Weather');
|
||||
$weatherWidget.addClass('rpg-strip-widget-visible');
|
||||
} else {
|
||||
$weatherWidget.removeClass('rpg-strip-widget-visible');
|
||||
}
|
||||
|
||||
// Clock Widget with animated face
|
||||
const $clockWidget = $container.find('.rpg-strip-widget-clock');
|
||||
if (widgetSettings.clock?.enabled && infoData?.time) {
|
||||
const timeStr = infoData.time.end || infoData.time.value || infoData.time.start || '';
|
||||
if (timeStr) {
|
||||
// Update clock hands
|
||||
const { hourAngle, minuteAngle } = parseTimeForClock(timeStr);
|
||||
$clockWidget.find('.rpg-strip-clock-hour').css('transform', `rotate(${hourAngle}deg)`);
|
||||
$clockWidget.find('.rpg-strip-clock-minute').css('transform', `rotate(${minuteAngle}deg)`);
|
||||
$clockWidget.find('.rpg-strip-widget-value').text(timeStr);
|
||||
$clockWidget.attr('title', `Time: ${timeStr}`);
|
||||
$clockWidget.addClass('rpg-strip-widget-visible');
|
||||
} else {
|
||||
$clockWidget.removeClass('rpg-strip-widget-visible');
|
||||
}
|
||||
} else {
|
||||
$clockWidget.removeClass('rpg-strip-widget-visible');
|
||||
}
|
||||
|
||||
// Date Widget
|
||||
const $dateWidget = $container.find('.rpg-strip-widget-date');
|
||||
if (widgetSettings.date?.enabled && infoData?.date?.value) {
|
||||
const dateVal = infoData.date.value;
|
||||
// Truncate long dates for display
|
||||
const displayDate = dateVal.length > 20 ? dateVal.substring(0, 18) + '…' : dateVal;
|
||||
$dateWidget.find('.rpg-strip-widget-value').text(displayDate);
|
||||
$dateWidget.attr('title', dateVal);
|
||||
$dateWidget.addClass('rpg-strip-widget-visible');
|
||||
} else {
|
||||
$dateWidget.removeClass('rpg-strip-widget-visible');
|
||||
}
|
||||
|
||||
// Location Widget
|
||||
const $locationWidget = $container.find('.rpg-strip-widget-location');
|
||||
if (widgetSettings.location?.enabled && infoData?.location?.value) {
|
||||
const loc = infoData.location.value;
|
||||
// Truncate long locations for display
|
||||
const displayLoc = loc.length > 15 ? loc.substring(0, 13) + '…' : loc;
|
||||
$locationWidget.find('.rpg-strip-widget-value').text(displayLoc);
|
||||
$locationWidget.attr('title', loc);
|
||||
$locationWidget.addClass('rpg-strip-widget-visible');
|
||||
} else {
|
||||
$locationWidget.removeClass('rpg-strip-widget-visible');
|
||||
}
|
||||
|
||||
// Stats Widget - get from lastGeneratedData or committedTrackerData first, fallback to extensionSettings
|
||||
const $statsWidget = $container.find('.rpg-strip-widget-stats');
|
||||
if (widgetSettings.stats?.enabled) {
|
||||
let allStats = [];
|
||||
|
||||
// Try to get stats from tracker data first (most current)
|
||||
const userStatsData = lastGeneratedData?.userStats || committedTrackerData?.userStats;
|
||||
if (userStatsData) {
|
||||
try {
|
||||
const parsedStats = typeof userStatsData === 'string' ? JSON.parse(userStatsData) : userStatsData;
|
||||
if (parsedStats?.stats) {
|
||||
allStats = parsedStats.stats;
|
||||
}
|
||||
} catch (e) {
|
||||
console.warn('[RPG Strip Widgets] Failed to parse tracker userStats:', e);
|
||||
}
|
||||
}
|
||||
|
||||
// Fallback to extensionSettings.userStats
|
||||
if (allStats.length === 0 && extensionSettings.userStats) {
|
||||
try {
|
||||
const userStatsJson = extensionSettings.userStats;
|
||||
const parsedUserStats = typeof userStatsJson === 'string' ? JSON.parse(userStatsJson) : userStatsJson;
|
||||
if (parsedUserStats?.stats) {
|
||||
allStats = parsedUserStats.stats;
|
||||
}
|
||||
} catch (e) {
|
||||
console.warn('[RPG Strip Widgets] Failed to parse extensionSettings.userStats:', e);
|
||||
}
|
||||
}
|
||||
|
||||
if (allStats.length > 0) {
|
||||
// Get enabled stats from trackerConfig
|
||||
const configuredStats = extensionSettings.trackerConfig?.userStats?.customStats || [];
|
||||
const enabledStatMap = new Map();
|
||||
configuredStats.forEach(s => {
|
||||
if (s.enabled !== false) {
|
||||
enabledStatMap.set(s.id?.toLowerCase(), true);
|
||||
enabledStatMap.set(s.name?.toLowerCase(), true);
|
||||
}
|
||||
});
|
||||
|
||||
const $statsList = $statsWidget.find('.rpg-strip-stats-list');
|
||||
$statsList.empty();
|
||||
|
||||
allStats.forEach(stat => {
|
||||
// Filter by config if available - but if no config, show all
|
||||
if (configuredStats.length > 0) {
|
||||
const statId = stat.id?.toLowerCase();
|
||||
const statName = stat.name?.toLowerCase();
|
||||
if (!enabledStatMap.has(statId) && !enabledStatMap.has(statName)) return;
|
||||
}
|
||||
|
||||
const value = typeof stat.value === 'number' ? stat.value : parseInt(stat.value) || 0;
|
||||
const color = getStatColor(value);
|
||||
const abbr = stat.name.substring(0, 3).toUpperCase();
|
||||
|
||||
const $item = $(`<div class="rpg-strip-stat-item" title="${stat.name}: ${value}">
|
||||
<span class="rpg-strip-stat-name">${abbr}</span>
|
||||
<span class="rpg-strip-stat-value" style="color: ${color};">${value}</span>
|
||||
</div>`);
|
||||
$statsList.append($item);
|
||||
});
|
||||
|
||||
if ($statsList.children().length > 0) {
|
||||
$statsWidget.addClass('rpg-strip-widget-visible');
|
||||
} else {
|
||||
$statsWidget.removeClass('rpg-strip-widget-visible');
|
||||
}
|
||||
} else {
|
||||
$statsWidget.removeClass('rpg-strip-widget-visible');
|
||||
}
|
||||
} else {
|
||||
$statsWidget.removeClass('rpg-strip-widget-visible');
|
||||
}
|
||||
|
||||
// Attributes Widget
|
||||
const $attrsWidget = $container.find('.rpg-strip-widget-attributes');
|
||||
if (widgetSettings.attributes?.enabled) {
|
||||
const showRPGAttributes = extensionSettings.trackerConfig?.userStats?.showRPGAttributes !== false;
|
||||
|
||||
if (showRPGAttributes && extensionSettings.classicStats) {
|
||||
// Get enabled attributes from trackerConfig
|
||||
const configuredAttrs = extensionSettings.trackerConfig?.userStats?.rpgAttributes || [];
|
||||
const enabledAttrIds = configuredAttrs.filter(a => a.enabled !== false).map(a => a.id);
|
||||
|
||||
const attrs = extensionSettings.classicStats;
|
||||
const $attrsGrid = $attrsWidget.find('.rpg-strip-attributes-grid');
|
||||
$attrsGrid.empty();
|
||||
|
||||
Object.entries(attrs).forEach(([key, value]) => {
|
||||
// Filter by config if available
|
||||
if (enabledAttrIds.length > 0 && !enabledAttrIds.includes(key.toLowerCase())) {
|
||||
return;
|
||||
}
|
||||
|
||||
const $item = $(`<div class="rpg-strip-attr-item" title="${key.toUpperCase()}: ${value}">
|
||||
<span class="rpg-strip-attr-name">${key.toUpperCase()}</span>
|
||||
<span class="rpg-strip-attr-value">${value}</span>
|
||||
</div>`);
|
||||
$attrsGrid.append($item);
|
||||
});
|
||||
|
||||
if ($attrsGrid.children().length > 0) {
|
||||
$attrsWidget.addClass('rpg-strip-widget-visible');
|
||||
} else {
|
||||
$attrsWidget.removeClass('rpg-strip-widget-visible');
|
||||
}
|
||||
} else {
|
||||
$attrsWidget.removeClass('rpg-strip-widget-visible');
|
||||
}
|
||||
} else {
|
||||
$attrsWidget.removeClass('rpg-strip-widget-visible');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a color interpolated between low and high based on stat value (0-100).
|
||||
* @param {number} value - The stat value (0-100)
|
||||
* @returns {string} CSS color value
|
||||
*/
|
||||
function getStatColor(value) {
|
||||
const lowColor = extensionSettings.statBarColorLow || '#cc3333';
|
||||
const highColor = extensionSettings.statBarColorHigh || '#33cc66';
|
||||
|
||||
// Simple linear interpolation between low and high colors
|
||||
const percent = Math.min(100, Math.max(0, value)) / 100;
|
||||
|
||||
// Parse colors
|
||||
const lowRGB = hexToRgb(lowColor);
|
||||
const highRGB = hexToRgb(highColor);
|
||||
|
||||
if (!lowRGB || !highRGB) return value > 50 ? highColor : lowColor;
|
||||
|
||||
const r = Math.round(lowRGB.r + (highRGB.r - lowRGB.r) * percent);
|
||||
const g = Math.round(lowRGB.g + (highRGB.g - lowRGB.g) * percent);
|
||||
const b = Math.round(lowRGB.b + (highRGB.b - lowRGB.b) * percent);
|
||||
|
||||
return `rgb(${r}, ${g}, ${b})`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a hex color to RGB object.
|
||||
* @param {string} hex - Hex color string (e.g., "#cc3333")
|
||||
* @returns {{r: number, g: number, b: number}|null}
|
||||
*/
|
||||
function hexToRgb(hex) {
|
||||
const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
|
||||
return result ? {
|
||||
r: parseInt(result[1], 16),
|
||||
g: parseInt(result[2], 16),
|
||||
b: parseInt(result[3], 16)
|
||||
} : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets up desktop tab navigation for organizing content.
|
||||
|
||||
@@ -19,7 +19,7 @@ import {
|
||||
} from '../../core/state.js';
|
||||
import { i18n } from '../../core/i18n.js';
|
||||
import { setupMobileTabs, removeMobileTabs } from './mobile.js';
|
||||
import { setupDesktopTabs, removeDesktopTabs } from './desktop.js';
|
||||
import { setupDesktopTabs, removeDesktopTabs, updateStripWidgets } from './desktop.js';
|
||||
|
||||
/**
|
||||
* Toggles the visibility of plot buttons based on settings.
|
||||
@@ -243,6 +243,9 @@ export function setupCollapseToggle() {
|
||||
} else if ($panel.hasClass('rpg-position-left')) {
|
||||
$icon.removeClass('fa-chevron-left').addClass('fa-chevron-right');
|
||||
}
|
||||
|
||||
// Update strip widgets when collapsing (they show in collapsed state)
|
||||
updateStripWidgets();
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -10961,3 +10961,314 @@ body:has(.rpg-panel[data-theme="light"]) .rpg-fab-widget:hover {
|
||||
border-color: rgba(52, 152, 219, 0.6);
|
||||
box-shadow: 0 3px 12px rgba(0, 0, 0, 0.15);
|
||||
}
|
||||
|
||||
/* ============================================
|
||||
DESKTOP STRIP WIDGETS (Collapsed Panel)
|
||||
Shows compact widgets when panel is collapsed
|
||||
============================================ */
|
||||
|
||||
/* Strip Widget Container - hidden by default, shown when collapsed with strip widgets enabled */
|
||||
.rpg-strip-widget-container {
|
||||
display: none;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
padding: 8px 4px;
|
||||
padding-top: 40px;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
overflow-y: auto;
|
||||
overflow-x: hidden;
|
||||
scrollbar-width: thin;
|
||||
scrollbar-color: var(--rpg-border) transparent;
|
||||
}
|
||||
|
||||
/* Show strip widgets when panel is collapsed and strip widgets are enabled */
|
||||
.rpg-panel.rpg-collapsed.rpg-strip-widgets-enabled .rpg-strip-widget-container {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
/* Wider collapsed state when strip widgets are enabled */
|
||||
.rpg-panel.rpg-collapsed.rpg-strip-widgets-enabled {
|
||||
max-width: 5rem !important;
|
||||
min-width: 5rem !important;
|
||||
}
|
||||
|
||||
/* Adjust collapse button position for wider strip */
|
||||
.rpg-panel.rpg-collapsed.rpg-strip-widgets-enabled .rpg-collapse-toggle {
|
||||
left: -2rem;
|
||||
}
|
||||
|
||||
.rpg-panel.rpg-position-left.rpg-collapsed.rpg-strip-widgets-enabled .rpg-collapse-toggle {
|
||||
left: auto;
|
||||
right: -2rem;
|
||||
}
|
||||
|
||||
/* Fix positioning for wider strip */
|
||||
.rpg-panel.rpg-position-right.rpg-collapsed.rpg-strip-widgets-enabled {
|
||||
left: calc(100vw - 5rem);
|
||||
}
|
||||
|
||||
.rpg-panel.rpg-position-left.rpg-collapsed.rpg-strip-widgets-enabled {
|
||||
right: calc(100vw - 5rem);
|
||||
}
|
||||
|
||||
/* Base Strip Widget Styles */
|
||||
.rpg-strip-widget {
|
||||
display: none; /* Hidden by default, shown via JS when enabled */
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: rgba(20, 20, 35, 0.85);
|
||||
border: 1px solid var(--rpg-border, rgba(100, 150, 255, 0.3));
|
||||
border-radius: 6px;
|
||||
padding: 4px;
|
||||
font-size: 10px;
|
||||
color: var(--rpg-text, #eaeaea);
|
||||
backdrop-filter: blur(8px);
|
||||
width: calc(100% - 8px);
|
||||
min-width: 0;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.rpg-strip-widget.rpg-strip-widget-visible {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.rpg-strip-widget:hover {
|
||||
border-color: var(--rpg-highlight, #4a90e2);
|
||||
box-shadow: 0 0 8px rgba(100, 150, 255, 0.3);
|
||||
}
|
||||
|
||||
/* Weather Icon Widget */
|
||||
.rpg-strip-widget-weather {
|
||||
font-size: 18px;
|
||||
padding: 6px 4px;
|
||||
gap: 2px;
|
||||
}
|
||||
|
||||
.rpg-strip-widget-weather .rpg-strip-widget-icon {
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.rpg-strip-widget-weather .rpg-strip-widget-desc {
|
||||
font-size: 8px;
|
||||
opacity: 0.8;
|
||||
text-align: center;
|
||||
word-break: break-word;
|
||||
line-height: 1.2;
|
||||
}
|
||||
|
||||
/* Clock Widget with animated face */
|
||||
.rpg-strip-widget-clock {
|
||||
font-family: 'Roboto Mono', 'Consolas', monospace;
|
||||
font-size: 10px;
|
||||
gap: 4px;
|
||||
padding: 6px 4px;
|
||||
}
|
||||
|
||||
/* Mini animated clock face for strip */
|
||||
.rpg-strip-clock-face {
|
||||
position: relative;
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
border: 2px solid var(--rpg-border, #4a7ba7);
|
||||
border-radius: 50%;
|
||||
background: var(--rpg-accent, rgba(22, 33, 62, 0.9));
|
||||
}
|
||||
|
||||
.rpg-strip-clock-hour {
|
||||
position: absolute;
|
||||
width: 2px;
|
||||
height: 8px;
|
||||
background: var(--rpg-text, #eaeaea);
|
||||
left: 50%;
|
||||
bottom: 50%;
|
||||
margin-left: -1px;
|
||||
transform-origin: bottom center;
|
||||
border-radius: 1px;
|
||||
}
|
||||
|
||||
.rpg-strip-clock-minute {
|
||||
position: absolute;
|
||||
width: 1.5px;
|
||||
height: 10px;
|
||||
background: var(--rpg-highlight, #4a90e2);
|
||||
left: 50%;
|
||||
bottom: 50%;
|
||||
margin-left: -0.75px;
|
||||
transform-origin: bottom center;
|
||||
border-radius: 1px;
|
||||
}
|
||||
|
||||
.rpg-strip-clock-center {
|
||||
position: absolute;
|
||||
width: 4px;
|
||||
height: 4px;
|
||||
background: var(--rpg-highlight, #4a90e2);
|
||||
border-radius: 50%;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
}
|
||||
|
||||
.rpg-strip-widget-clock .rpg-strip-widget-value {
|
||||
font-weight: 600;
|
||||
letter-spacing: 0.5px;
|
||||
font-size: 10px;
|
||||
}
|
||||
|
||||
/* Date Widget */
|
||||
.rpg-strip-widget-date {
|
||||
font-size: 9px;
|
||||
text-align: center;
|
||||
gap: 2px;
|
||||
}
|
||||
|
||||
.rpg-strip-widget-date i {
|
||||
font-size: 11px;
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
.rpg-strip-widget-date .rpg-strip-widget-value {
|
||||
word-break: break-word;
|
||||
line-height: 1.2;
|
||||
}
|
||||
|
||||
/* Location Widget */
|
||||
.rpg-strip-widget-location {
|
||||
font-size: 9px;
|
||||
text-align: center;
|
||||
gap: 2px;
|
||||
}
|
||||
|
||||
.rpg-strip-widget-location i {
|
||||
font-size: 11px;
|
||||
color: var(--rpg-highlight, #4a90e2);
|
||||
}
|
||||
|
||||
.rpg-strip-widget-location .rpg-strip-widget-value {
|
||||
word-break: break-word;
|
||||
line-height: 1.2;
|
||||
max-height: 3em;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
/* Stats Widget - Vertical list */
|
||||
.rpg-strip-widget-stats {
|
||||
padding: 4px 2px;
|
||||
}
|
||||
|
||||
.rpg-strip-stats-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 2px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.rpg-strip-stat-item {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
font-family: 'Roboto Mono', 'Consolas', monospace;
|
||||
font-size: 9px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.rpg-strip-stat-name {
|
||||
font-size: 7px;
|
||||
opacity: 0.6;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.5px;
|
||||
}
|
||||
|
||||
.rpg-strip-stat-value {
|
||||
font-size: 11px;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
/* Attributes Widget - 2-column grid */
|
||||
.rpg-strip-widget-attributes {
|
||||
padding: 4px 2px;
|
||||
}
|
||||
|
||||
.rpg-strip-attributes-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
gap: 2px 4px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.rpg-strip-attr-item {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.rpg-strip-attr-name {
|
||||
font-size: 7px;
|
||||
opacity: 0.6;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.3px;
|
||||
}
|
||||
|
||||
.rpg-strip-attr-value {
|
||||
font-size: 10px;
|
||||
font-weight: 700;
|
||||
color: var(--rpg-highlight, #6af);
|
||||
font-family: 'Roboto Mono', 'Consolas', monospace;
|
||||
}
|
||||
|
||||
/* ============================================
|
||||
STRIP WIDGET THEME VARIATIONS
|
||||
============================================ */
|
||||
|
||||
/* Sci-Fi Theme */
|
||||
body:has(.rpg-panel[data-theme="sci-fi"]) .rpg-strip-widget {
|
||||
background: rgba(10, 14, 39, 0.9);
|
||||
border-color: rgba(139, 0, 255, 0.5);
|
||||
color: #00fff9;
|
||||
}
|
||||
|
||||
/* Fantasy Theme */
|
||||
body:has(.rpg-panel[data-theme="fantasy"]) .rpg-strip-widget {
|
||||
background: rgba(43, 24, 16, 0.9);
|
||||
border-color: rgba(139, 105, 20, 0.6);
|
||||
color: #f4e8d0;
|
||||
}
|
||||
|
||||
/* Cyberpunk Theme */
|
||||
body:has(.rpg-panel[data-theme="cyberpunk"]) .rpg-strip-widget {
|
||||
background: rgba(15, 5, 25, 0.9);
|
||||
border-color: rgba(255, 0, 255, 0.4);
|
||||
color: #00ffff;
|
||||
}
|
||||
|
||||
/* Dark Theme */
|
||||
body:has(.rpg-panel[data-theme="dark"]) .rpg-strip-widget {
|
||||
background: rgba(25, 25, 35, 0.9);
|
||||
border-color: rgba(70, 70, 90, 0.6);
|
||||
color: #e0e0e0;
|
||||
}
|
||||
|
||||
/* Light Theme */
|
||||
body:has(.rpg-panel[data-theme="light"]) .rpg-strip-widget {
|
||||
background: rgba(255, 255, 255, 0.95);
|
||||
border-color: rgba(220, 220, 230, 0.8);
|
||||
color: #2c3e50;
|
||||
}
|
||||
|
||||
/* Hide strip widgets on mobile */
|
||||
@media (max-width: 768px) {
|
||||
.rpg-strip-widget-container {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
.rpg-panel.rpg-collapsed.rpg-strip-widgets-enabled {
|
||||
max-width: 2.5rem !important;
|
||||
min-width: 2.5rem !important;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,6 +4,42 @@
|
||||
<i class="fa-solid fa-chevron-right"></i>
|
||||
</button>
|
||||
|
||||
<!-- Strip Widget Container (shown when collapsed with strip widgets enabled) -->
|
||||
<div id="rpg-strip-widget-container" class="rpg-strip-widget-container">
|
||||
<!-- Weather Icon Widget -->
|
||||
<div class="rpg-strip-widget rpg-strip-widget-weather" data-widget="weatherIcon">
|
||||
<span class="rpg-strip-widget-icon"></span>
|
||||
<span class="rpg-strip-widget-desc"></span>
|
||||
</div>
|
||||
<!-- Clock Widget with animated face -->
|
||||
<div class="rpg-strip-widget rpg-strip-widget-clock" data-widget="clock">
|
||||
<div class="rpg-strip-clock-face">
|
||||
<div class="rpg-strip-clock-hour"></div>
|
||||
<div class="rpg-strip-clock-minute"></div>
|
||||
<div class="rpg-strip-clock-center"></div>
|
||||
</div>
|
||||
<span class="rpg-strip-widget-value"></span>
|
||||
</div>
|
||||
<!-- Date Widget -->
|
||||
<div class="rpg-strip-widget rpg-strip-widget-date" data-widget="date">
|
||||
<i class="fa-solid fa-calendar"></i>
|
||||
<span class="rpg-strip-widget-value"></span>
|
||||
</div>
|
||||
<!-- Location Widget -->
|
||||
<div class="rpg-strip-widget rpg-strip-widget-location" data-widget="location">
|
||||
<i class="fa-solid fa-location-dot"></i>
|
||||
<span class="rpg-strip-widget-value"></span>
|
||||
</div>
|
||||
<!-- Stats Widget -->
|
||||
<div class="rpg-strip-widget rpg-strip-widget-stats" data-widget="stats">
|
||||
<div class="rpg-strip-stats-list"></div>
|
||||
</div>
|
||||
<!-- Attributes Widget -->
|
||||
<div class="rpg-strip-widget rpg-strip-widget-attributes" data-widget="attributes">
|
||||
<div class="rpg-strip-attributes-grid"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Main Game Panel -->
|
||||
<div class="rpg-game-container">
|
||||
<!-- Header with Controls -->
|
||||
@@ -505,6 +541,56 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Desktop Strip Widgets Section -->
|
||||
<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>
|
||||
<small class="notes" style="display: block; margin-bottom: 10px;"
|
||||
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.
|
||||
</small>
|
||||
|
||||
<label class="checkbox_label">
|
||||
<input type="checkbox" id="rpg-toggle-strip-widgets-enabled" />
|
||||
<span data-i18n-key="template.settingsModal.desktopStrip.enabled">Enable Strip Widgets</span>
|
||||
</label>
|
||||
<small style="display: block; margin-left: 24px; margin-top: -8px; color: #888; font-size: 11px;"
|
||||
data-i18n-key="template.settingsModal.desktopStrip.enabledNote">
|
||||
Shows widgets in the collapsed panel strip for quick access to stats.
|
||||
</small>
|
||||
|
||||
<div id="rpg-strip-widget-options" style="margin-left: 10px; border-left: 2px solid var(--SmartThemeBorderColor); padding-left: 10px; margin-top: 8px; display: none;">
|
||||
<label class="checkbox_label">
|
||||
<input type="checkbox" id="rpg-toggle-strip-weather-icon" />
|
||||
<span data-i18n-key="template.settingsModal.desktopStrip.weatherIcon">Weather Icon</span>
|
||||
</label>
|
||||
|
||||
<label class="checkbox_label">
|
||||
<input type="checkbox" id="rpg-toggle-strip-clock" />
|
||||
<span data-i18n-key="template.settingsModal.desktopStrip.clock">Time/Clock</span>
|
||||
</label>
|
||||
|
||||
<label class="checkbox_label">
|
||||
<input type="checkbox" id="rpg-toggle-strip-date" />
|
||||
<span data-i18n-key="template.settingsModal.desktopStrip.date">Date</span>
|
||||
</label>
|
||||
|
||||
<label class="checkbox_label">
|
||||
<input type="checkbox" id="rpg-toggle-strip-location" />
|
||||
<span data-i18n-key="template.settingsModal.desktopStrip.location">Location</span>
|
||||
</label>
|
||||
|
||||
<label class="checkbox_label">
|
||||
<input type="checkbox" id="rpg-toggle-strip-stats" />
|
||||
<span data-i18n-key="template.settingsModal.desktopStrip.stats">Stats (Health, Energy, etc.)</span>
|
||||
</label>
|
||||
|
||||
<label class="checkbox_label">
|
||||
<input type="checkbox" id="rpg-toggle-strip-attributes" />
|
||||
<span data-i18n-key="template.settingsModal.desktopStrip.attributes">RPG Attributes (STR, DEX, etc.)</span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="rpg-settings-group">
|
||||
<h4 data-i18n-key="template.settingsModal.advancedTitle"><i class="fa-solid fa-sliders"
|
||||
aria-hidden="true"></i> Advanced</h4>
|
||||
|
||||
Reference in New Issue
Block a user