feat(rflows): floating play button, auto-start demo, minimizable panels
- Add prominent floating Play/Pause FAB button (bottom center) with glow effect and pulse animation while running - Auto-start simulation for demo and sim-demo flows on load - Analytics panel now has a minimize button (◀/▶) to collapse to a narrow strip, preserving screen space - Keep existing toolbar Play button for discoverability Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
99131df914
commit
2264267ded
|
|
@ -1282,3 +1282,102 @@
|
|||
color: var(--rs-text-muted, #64748b);
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
/* ── Floating Play/Pause FAB ── */
|
||||
.flows-fab-play {
|
||||
position: absolute;
|
||||
bottom: 24px;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
z-index: 30;
|
||||
width: 56px;
|
||||
height: 56px;
|
||||
border-radius: 50%;
|
||||
border: 2px solid rgba(6, 182, 212, 0.5);
|
||||
background: linear-gradient(135deg, rgba(6, 182, 212, 0.2), rgba(139, 92, 246, 0.2));
|
||||
backdrop-filter: blur(12px);
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
box-shadow: 0 4px 20px rgba(6, 182, 212, 0.3);
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
.flows-fab-play:hover {
|
||||
transform: translateX(-50%) scale(1.1);
|
||||
box-shadow: 0 6px 28px rgba(6, 182, 212, 0.5);
|
||||
border-color: rgba(6, 182, 212, 0.8);
|
||||
}
|
||||
.flows-fab-play.playing {
|
||||
border-color: rgba(139, 92, 246, 0.6);
|
||||
background: linear-gradient(135deg, rgba(139, 92, 246, 0.25), rgba(236, 72, 153, 0.15));
|
||||
box-shadow: 0 4px 20px rgba(139, 92, 246, 0.3);
|
||||
animation: fabPulse 2s ease-in-out infinite;
|
||||
}
|
||||
.flows-fab-play__icon {
|
||||
font-size: 22px;
|
||||
line-height: 1;
|
||||
}
|
||||
@keyframes fabPulse {
|
||||
0%, 100% { box-shadow: 0 4px 20px rgba(139, 92, 246, 0.3); }
|
||||
50% { box-shadow: 0 4px 28px rgba(139, 92, 246, 0.55); }
|
||||
}
|
||||
|
||||
/* ── Minimizable panels ── */
|
||||
.flows-analytics-panel .analytics-minimize {
|
||||
background: none;
|
||||
border: none;
|
||||
color: var(--rs-text-secondary);
|
||||
font-size: 16px;
|
||||
cursor: pointer;
|
||||
padding: 2px 6px;
|
||||
margin-left: 4px;
|
||||
}
|
||||
.flows-analytics-panel .analytics-minimize:hover { color: var(--rs-text-primary); }
|
||||
.flows-analytics-panel.minimized {
|
||||
width: 44px;
|
||||
min-width: 44px;
|
||||
overflow: hidden;
|
||||
}
|
||||
.flows-analytics-panel.minimized .analytics-content,
|
||||
.flows-analytics-panel.minimized .analytics-tabs,
|
||||
.flows-analytics-panel.minimized .analytics-title,
|
||||
.flows-analytics-panel.minimized .analytics-close {
|
||||
display: none;
|
||||
}
|
||||
.flows-analytics-panel.minimized .analytics-header {
|
||||
flex-direction: column;
|
||||
padding: 8px 4px;
|
||||
border-bottom: none;
|
||||
}
|
||||
.flows-analytics-panel.minimized .analytics-minimize {
|
||||
writing-mode: vertical-rl;
|
||||
text-orientation: mixed;
|
||||
padding: 8px 2px;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
/* Panel collapse tab — visible when panel is closed */
|
||||
.flows-panel-tab {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
z-index: 19;
|
||||
width: 24px;
|
||||
height: 64px;
|
||||
border-radius: 0 8px 8px 0;
|
||||
background: var(--rs-bg-surface);
|
||||
border: 1px solid var(--rs-border-strong);
|
||||
border-left: none;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: var(--rs-text-secondary);
|
||||
font-size: 14px;
|
||||
transition: background 0.15s;
|
||||
}
|
||||
.flows-panel-tab:hover { background: var(--rs-bg-surface-raised); color: var(--rs-text-primary); }
|
||||
.flows-panel-tab--left { left: 0; }
|
||||
.flows-panel-tab--left.panel-open { left: 380px; transition: left 0.25s ease; }
|
||||
.flows-panel-tab--right { right: 0; border-radius: 8px 0 0 8px; border: 1px solid var(--rs-border-strong); border-right: none; }
|
||||
|
|
|
|||
|
|
@ -1043,6 +1043,9 @@ class FolkFlowsApp extends HTMLElement {
|
|||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none"><path d="M2 6V3a1 1 0 011-1h3M10 2h3a1 1 0 011 1v3M14 10v3a1 1 0 01-1 1h-3M6 14H3a1 1 0 01-1-1v-3" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/></svg>
|
||||
</button>
|
||||
</div>
|
||||
<button class="flows-fab-play" id="fab-play" data-canvas-action="sim" title="Play / Pause simulation">
|
||||
<span class="flows-fab-play__icon" id="fab-play-icon">${this.isSimulating ? "⏸" : "▶"}</span>
|
||||
</button>
|
||||
<div class="flows-sim-speed" id="sim-speed-container" style="display:${this.isSimulating ? "flex" : "none"}">
|
||||
<input type="range" class="flows-speed-slider" id="sim-speed-slider" min="20" max="1000" value="${this.simSpeedMs}" step="10"/>
|
||||
<span class="flows-speed-label" id="sim-speed-label">${this.simSpeedMs}ms</span>
|
||||
|
|
@ -1108,8 +1111,13 @@ class FolkFlowsApp extends HTMLElement {
|
|||
if (!this.canvasInitialized) {
|
||||
this.canvasInitialized = true;
|
||||
requestAnimationFrame(() => this.fitView());
|
||||
// Auto-start tour on first visit
|
||||
if (!localStorage.getItem("rflows_tour_done")) {
|
||||
// Auto-start simulation for demo flows
|
||||
const isDemo = this.currentFlowId === 'demo' || this.currentFlowId === 'sim-demo' || this.isDemo;
|
||||
if (isDemo && !this.isSimulating) {
|
||||
setTimeout(() => this.toggleSimulation(), 600);
|
||||
}
|
||||
// Auto-start tour on first visit (skip for demos that auto-play)
|
||||
else if (!localStorage.getItem("rflows_tour_done")) {
|
||||
setTimeout(() => this.startTour(), 1200);
|
||||
}
|
||||
}
|
||||
|
|
@ -4654,7 +4662,13 @@ class FolkFlowsApp extends HTMLElement {
|
|||
private toggleSimulation() {
|
||||
this.isSimulating = !this.isSimulating;
|
||||
const btn = this.shadow.getElementById("sim-btn");
|
||||
if (btn) btn.textContent = this.isSimulating ? "Pause" : "Play";
|
||||
if (btn) btn.textContent = this.isSimulating ? "⏸ Pause" : "▶ Play";
|
||||
|
||||
// Update floating play button
|
||||
const fabIcon = this.shadow.getElementById("fab-play-icon");
|
||||
if (fabIcon) fabIcon.textContent = this.isSimulating ? "⏸" : "▶";
|
||||
const fab = this.shadow.getElementById("fab-play");
|
||||
if (fab) fab.classList.toggle("playing", this.isSimulating);
|
||||
|
||||
// Show/hide speed slider and timeline
|
||||
const speedContainer = this.shadow.getElementById("sim-speed-container");
|
||||
|
|
@ -4810,6 +4824,7 @@ class FolkFlowsApp extends HTMLElement {
|
|||
<button class="analytics-tab ${this.analyticsTab === "overview" ? "analytics-tab--active" : ""}" data-analytics-tab="overview">Overview</button>
|
||||
<button class="analytics-tab ${this.analyticsTab === "transactions" ? "analytics-tab--active" : ""}" data-analytics-tab="transactions">Transactions</button>
|
||||
</div>
|
||||
<button class="analytics-minimize" data-analytics-minimize title="Minimize panel">◀</button>
|
||||
<button class="analytics-close" data-analytics-close>×</button>
|
||||
</div>
|
||||
<div class="analytics-content">
|
||||
|
|
@ -4835,6 +4850,15 @@ class FolkFlowsApp extends HTMLElement {
|
|||
const closeBtn = this.shadow.querySelector("[data-analytics-close]");
|
||||
closeBtn?.addEventListener("click", () => this.toggleAnalytics());
|
||||
|
||||
const minimizeBtn = this.shadow.querySelector("[data-analytics-minimize]");
|
||||
minimizeBtn?.addEventListener("click", () => {
|
||||
const panel = this.shadow.getElementById("analytics-panel");
|
||||
if (panel) {
|
||||
const isMin = panel.classList.toggle("minimized");
|
||||
if (minimizeBtn) (minimizeBtn as HTMLElement).textContent = isMin ? "▶" : "◀";
|
||||
}
|
||||
});
|
||||
|
||||
this.shadow.querySelectorAll("[data-analytics-tab]").forEach((el) => {
|
||||
el.addEventListener("click", () => {
|
||||
const tab = (el as HTMLElement).dataset.analyticsTab as "overview" | "transactions";
|
||||
|
|
|
|||
Loading…
Reference in New Issue