mirror of
https://github.com/MarekWo/mc-webui.git
synced 2026-08-08 17:53:00 +02:00
feat: add Settings FAB button, drag-and-drop positioning, and size/spacing controls
- Add Settings quick-access button to both main chat and DM views - Make FAB container draggable via toggle button with position saved to localStorage - Add button size and spacing sliders in Settings > Appearance tab - Use CSS custom properties for dynamic FAB sizing Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
+27
-15
@@ -1038,19 +1038,20 @@ main {
|
||||
z-index: 900; /* Lower than offcanvas (1045) but higher than content */
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
gap: var(--fab-custom-gap, 12px);
|
||||
touch-action: none; /* Prevent scroll during drag */
|
||||
}
|
||||
|
||||
.fab {
|
||||
position: relative; /* For badge positioning */
|
||||
width: 56px;
|
||||
height: 56px;
|
||||
width: var(--fab-custom-size, 56px);
|
||||
height: var(--fab-custom-size, 56px);
|
||||
border-radius: 50%;
|
||||
border: none;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 1.5rem;
|
||||
font-size: calc(var(--fab-custom-size, 56px) * 0.43);
|
||||
cursor: pointer;
|
||||
box-shadow: var(--fab-shadow);
|
||||
transition: transform 0.2s ease, box-shadow 0.2s ease;
|
||||
@@ -1110,14 +1111,19 @@ main {
|
||||
transition: transform 0.2s ease;
|
||||
}
|
||||
|
||||
/* FAB toggle button (smaller, semi-transparent) */
|
||||
/* FAB toggle button (smaller, semi-transparent) — not affected by custom size */
|
||||
.fab-toggle {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
width: 32px !important;
|
||||
height: 32px !important;
|
||||
background: rgba(108, 117, 125, 0.6);
|
||||
color: white;
|
||||
font-size: 0.85rem;
|
||||
font-size: 0.85rem !important;
|
||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
|
||||
cursor: grab;
|
||||
}
|
||||
|
||||
.fab-toggle:active {
|
||||
cursor: grabbing;
|
||||
}
|
||||
|
||||
.fab-toggle:hover {
|
||||
@@ -1157,19 +1163,19 @@ main {
|
||||
.fab-container {
|
||||
right: 12px;
|
||||
top: 70px;
|
||||
gap: 10px;
|
||||
gap: var(--fab-custom-gap, 10px);
|
||||
}
|
||||
|
||||
.fab {
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
font-size: 1.25rem;
|
||||
width: var(--fab-custom-size, 48px);
|
||||
height: var(--fab-custom-size, 48px);
|
||||
font-size: calc(var(--fab-custom-size, 48px) * 0.43);
|
||||
}
|
||||
|
||||
.fab-toggle {
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
font-size: 0.75rem;
|
||||
width: 28px !important;
|
||||
height: 28px !important;
|
||||
font-size: 0.75rem !important;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1485,6 +1491,12 @@ main {
|
||||
color: white;
|
||||
}
|
||||
|
||||
/* Settings FAB button (warm orange) */
|
||||
.fab-settings {
|
||||
background: linear-gradient(135deg, #fd7e14 0%, #e06b0a 100%);
|
||||
color: white;
|
||||
}
|
||||
|
||||
/* Filter bar overlay - slides down from top of chat area */
|
||||
.filter-bar {
|
||||
position: absolute;
|
||||
|
||||
@@ -3931,6 +3931,20 @@ function initializeFabToggle() {
|
||||
const isCollapsed = container.classList.contains('collapsed');
|
||||
toggle.title = isCollapsed ? 'Show buttons' : 'Hide buttons';
|
||||
});
|
||||
|
||||
// Drag-and-drop support
|
||||
initFabDrag('fabContainer', 'fabToggle', 'mc-webui-fab-pos');
|
||||
|
||||
// Listen for settings open request from DM iframe
|
||||
window.addEventListener('message', (e) => {
|
||||
if (e.data && e.data.type === 'openSettings') {
|
||||
const modal = document.getElementById('settingsModal');
|
||||
if (modal) {
|
||||
const bsModal = bootstrap.Modal.getOrCreateInstance(modal);
|
||||
bsModal.show();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
|
||||
@@ -236,6 +236,16 @@ document.addEventListener('DOMContentLoaded', async function() {
|
||||
// Initialize FAB toggle
|
||||
initializeDmFabToggle();
|
||||
|
||||
// Settings FAB - open parent's settings modal
|
||||
const dmSettingsFab = document.getElementById('dmSettingsFab');
|
||||
if (dmSettingsFab) {
|
||||
dmSettingsFab.addEventListener('click', () => {
|
||||
if (window.parent && window.parent !== window) {
|
||||
window.parent.postMessage({type: 'openSettings'}, '*');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Load auto-retry config
|
||||
loadAutoRetryConfig();
|
||||
|
||||
@@ -1778,6 +1788,9 @@ function initializeDmFabToggle() {
|
||||
const isCollapsed = container.classList.contains('collapsed');
|
||||
toggle.title = isCollapsed ? 'Show buttons' : 'Hide buttons';
|
||||
});
|
||||
|
||||
// Drag-and-drop support
|
||||
initFabDrag('dmFabContainer', 'dmFabToggle', 'mc-webui-fab-pos-dm');
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -0,0 +1,154 @@
|
||||
// =============================================================================
|
||||
// FAB Container — Drag-and-Drop & Customization Utilities
|
||||
// =============================================================================
|
||||
|
||||
/**
|
||||
* Make a FAB container draggable via its toggle button.
|
||||
* Short click = toggle collapse, drag = reposition.
|
||||
* Position is persisted to localStorage.
|
||||
*
|
||||
* @param {string} containerId - e.g. 'fabContainer' or 'dmFabContainer'
|
||||
* @param {string} toggleId - e.g. 'fabToggle' or 'dmFabToggle'
|
||||
* @param {string} storageKey - localStorage key for position
|
||||
*/
|
||||
function initFabDrag(containerId, toggleId, storageKey) {
|
||||
const container = document.getElementById(containerId);
|
||||
const toggle = document.getElementById(toggleId);
|
||||
if (!container || !toggle) return;
|
||||
|
||||
const DRAG_THRESHOLD = 5; // px – movement before drag starts
|
||||
let dragging = false;
|
||||
let startX, startY, origLeft, origTop;
|
||||
let didDrag = false;
|
||||
|
||||
// --- Restore saved position ---
|
||||
restoreFabPosition(container, storageKey);
|
||||
|
||||
// --- Pointer events on toggle ---
|
||||
toggle.addEventListener('pointerdown', onPointerDown);
|
||||
|
||||
function onPointerDown(e) {
|
||||
// Only primary button
|
||||
if (e.button !== 0) return;
|
||||
|
||||
e.preventDefault();
|
||||
toggle.setPointerCapture(e.pointerId);
|
||||
|
||||
const rect = container.getBoundingClientRect();
|
||||
startX = e.clientX;
|
||||
startY = e.clientY;
|
||||
origLeft = rect.left;
|
||||
origTop = rect.top;
|
||||
dragging = false;
|
||||
didDrag = false;
|
||||
|
||||
toggle.addEventListener('pointermove', onPointerMove);
|
||||
toggle.addEventListener('pointerup', onPointerUp);
|
||||
}
|
||||
|
||||
function onPointerMove(e) {
|
||||
const dx = e.clientX - startX;
|
||||
const dy = e.clientY - startY;
|
||||
|
||||
if (!dragging && (Math.abs(dx) > DRAG_THRESHOLD || Math.abs(dy) > DRAG_THRESHOLD)) {
|
||||
dragging = true;
|
||||
didDrag = true;
|
||||
// Switch container to left/top positioning for drag
|
||||
container.style.right = 'auto';
|
||||
}
|
||||
|
||||
if (dragging) {
|
||||
const newLeft = origLeft + dx;
|
||||
const newTop = origTop + dy;
|
||||
container.style.left = newLeft + 'px';
|
||||
container.style.top = newTop + 'px';
|
||||
}
|
||||
}
|
||||
|
||||
function onPointerUp(e) {
|
||||
toggle.removeEventListener('pointermove', onPointerMove);
|
||||
toggle.removeEventListener('pointerup', onPointerUp);
|
||||
|
||||
if (didDrag) {
|
||||
// Clamp to viewport
|
||||
clampFabPosition(container);
|
||||
// Save
|
||||
saveFabPosition(container, storageKey);
|
||||
}
|
||||
// If it was not a drag, let the click event fire naturally (toggle collapse)
|
||||
// If it was a drag, suppress the click
|
||||
if (didDrag) {
|
||||
toggle.addEventListener('click', suppressClick, {once: true, capture: true});
|
||||
}
|
||||
}
|
||||
|
||||
function suppressClick(e) {
|
||||
e.stopImmediatePropagation();
|
||||
e.preventDefault();
|
||||
}
|
||||
}
|
||||
|
||||
function clampFabPosition(container) {
|
||||
const rect = container.getBoundingClientRect();
|
||||
const vw = window.innerWidth;
|
||||
const vh = window.innerHeight;
|
||||
|
||||
let left = rect.left;
|
||||
let top = rect.top;
|
||||
|
||||
// Keep at least 20px of the container visible on each edge
|
||||
if (left + rect.width < 20) left = 20 - rect.width;
|
||||
if (left > vw - 20) left = vw - 20;
|
||||
if (top < 0) top = 0;
|
||||
if (top > vh - 20) top = vh - 20;
|
||||
|
||||
container.style.left = left + 'px';
|
||||
container.style.top = top + 'px';
|
||||
}
|
||||
|
||||
function saveFabPosition(container, storageKey) {
|
||||
const rect = container.getBoundingClientRect();
|
||||
localStorage.setItem(storageKey, JSON.stringify({
|
||||
left: rect.left,
|
||||
top: rect.top
|
||||
}));
|
||||
}
|
||||
|
||||
function restoreFabPosition(container, storageKey) {
|
||||
const saved = localStorage.getItem(storageKey);
|
||||
if (!saved) return;
|
||||
|
||||
try {
|
||||
const pos = JSON.parse(saved);
|
||||
container.style.right = 'auto';
|
||||
container.style.left = pos.left + 'px';
|
||||
container.style.top = pos.top + 'px';
|
||||
// Re-clamp in case viewport changed
|
||||
clampFabPosition(container);
|
||||
} catch (e) {
|
||||
localStorage.removeItem(storageKey);
|
||||
}
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// FAB Size & Spacing — apply from localStorage
|
||||
// =============================================================================
|
||||
|
||||
/**
|
||||
* Apply saved FAB appearance settings (size, gap).
|
||||
* Called on page load from both main and DM pages.
|
||||
*/
|
||||
function applyFabAppearance() {
|
||||
const size = localStorage.getItem('mc-webui-fab-size');
|
||||
const gap = localStorage.getItem('mc-webui-fab-gap');
|
||||
|
||||
if (size) {
|
||||
document.documentElement.style.setProperty('--fab-custom-size', size + 'px');
|
||||
}
|
||||
if (gap) {
|
||||
document.documentElement.style.setProperty('--fab-custom-gap', gap + 'px');
|
||||
}
|
||||
}
|
||||
|
||||
// Auto-apply on load
|
||||
document.addEventListener('DOMContentLoaded', applyFabAppearance);
|
||||
Reference in New Issue
Block a user