fix(rsocials): proper canvas sizing, SVG zoom icons, category-styled nodes
- Fix height to account for shell header (92px not 60px) - Add min-height:0 on flex children to prevent overflow - Replace text zoom buttons with SVG icons matching rFlows pattern - Add fit-to-view icon (corner brackets) with separators - Add category class per node (trigger/delay/condition/action) with tinted backgrounds and category badge chips - Add keyboard shortcuts: F=fit, +/-=zoom Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
78284e448c
commit
456d0de9c1
|
|
@ -1,7 +1,7 @@
|
||||||
/* rSocials Campaign Workflow — n8n-style workflow builder */
|
/* rSocials Campaign Workflow — n8n-style workflow builder */
|
||||||
folk-campaign-workflow {
|
folk-campaign-workflow {
|
||||||
display: block;
|
display: block;
|
||||||
height: calc(100vh - 60px);
|
height: calc(100vh - 92px);
|
||||||
}
|
}
|
||||||
|
|
||||||
.cw-root {
|
.cw-root {
|
||||||
|
|
@ -22,6 +22,7 @@ folk-campaign-workflow {
|
||||||
border-bottom: 1px solid var(--rs-border, #2d2d44);
|
border-bottom: 1px solid var(--rs-border, #2d2d44);
|
||||||
background: var(--rs-bg-surface, #1a1a2e);
|
background: var(--rs-bg-surface, #1a1a2e);
|
||||||
z-index: 10;
|
z-index: 10;
|
||||||
|
flex-shrink: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.cw-toolbar__title {
|
.cw-toolbar__title {
|
||||||
|
|
@ -106,6 +107,7 @@ folk-campaign-workflow {
|
||||||
display: flex;
|
display: flex;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
position: relative;
|
position: relative;
|
||||||
|
min-height: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ── Left sidebar — node palette ── */
|
/* ── Left sidebar — node palette ── */
|
||||||
|
|
@ -172,6 +174,7 @@ folk-campaign-workflow {
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
cursor: grab;
|
cursor: grab;
|
||||||
background: var(--rs-canvas-bg, #0f0f23);
|
background: var(--rs-canvas-bg, #0f0f23);
|
||||||
|
min-width: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.cw-canvas.grabbing {
|
.cw-canvas.grabbing {
|
||||||
|
|
@ -330,42 +333,57 @@ folk-campaign-workflow {
|
||||||
/* ── Zoom controls ── */
|
/* ── Zoom controls ── */
|
||||||
.cw-zoom-controls {
|
.cw-zoom-controls {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
bottom: 12px;
|
bottom: 14px;
|
||||||
right: 12px;
|
right: 14px;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 4px;
|
gap: 0;
|
||||||
background: var(--rs-bg-surface, #1a1a2e);
|
background: var(--rs-bg-surface, #1a1a2e);
|
||||||
border: 1px solid var(--rs-border-strong, #3d3d5c);
|
border: 1px solid var(--rs-border, #2d2d44);
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
padding: 4px 6px;
|
box-shadow: 0 2px 8px rgba(0,0,0,0.12);
|
||||||
|
overflow: hidden;
|
||||||
z-index: 5;
|
z-index: 5;
|
||||||
}
|
}
|
||||||
|
|
||||||
.cw-zoom-btn {
|
.cw-zoom-btn {
|
||||||
width: 28px;
|
width: 32px;
|
||||||
height: 28px;
|
height: 32px;
|
||||||
border: none;
|
border: none;
|
||||||
border-radius: 6px;
|
border-radius: 0;
|
||||||
background: transparent;
|
background: transparent;
|
||||||
color: var(--rs-text-primary, #e2e8f0);
|
color: var(--rs-text-primary, #e2e8f0);
|
||||||
font-size: 16px;
|
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
transition: background 0.15s;
|
transition: background 0.15s;
|
||||||
|
padding: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.cw-zoom-btn:hover {
|
.cw-zoom-btn:hover {
|
||||||
background: var(--rs-bg-surface-raised, #252545);
|
background: var(--rs-bg-surface-raised, #252545);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.cw-zoom-btn svg {
|
||||||
|
width: 16px;
|
||||||
|
height: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cw-zoom-sep {
|
||||||
|
width: 1px;
|
||||||
|
height: 18px;
|
||||||
|
background: var(--rs-border, #2d2d44);
|
||||||
|
margin: 0 2px;
|
||||||
|
}
|
||||||
|
|
||||||
.cw-zoom-level {
|
.cw-zoom-level {
|
||||||
font-size: 11px;
|
font-size: 11px;
|
||||||
color: var(--rs-text-muted, #94a3b8);
|
font-weight: 600;
|
||||||
min-width: 36px;
|
color: var(--rs-text-secondary, #94a3b8);
|
||||||
|
min-width: 40px;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
|
user-select: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ── Node styles in SVG ── */
|
/* ── Node styles in SVG ── */
|
||||||
|
|
@ -388,6 +406,24 @@ folk-campaign-workflow {
|
||||||
border-color: #4f46e5 !important;
|
border-color: #4f46e5 !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Category-specific node backgrounds */
|
||||||
|
.cw-node--trigger foreignObject > div {
|
||||||
|
border-color: #3b82f644;
|
||||||
|
background: linear-gradient(135deg, #1a1a2e 0%, #1e2a3e 100%);
|
||||||
|
}
|
||||||
|
.cw-node--delay foreignObject > div {
|
||||||
|
border-color: #a855f744;
|
||||||
|
background: linear-gradient(135deg, #1a1a2e 0%, #251e3e 100%);
|
||||||
|
}
|
||||||
|
.cw-node--condition foreignObject > div {
|
||||||
|
border-color: #f59e0b44;
|
||||||
|
background: linear-gradient(135deg, #1a1a2e 0%, #2e2a1e 100%);
|
||||||
|
}
|
||||||
|
.cw-node--action foreignObject > div {
|
||||||
|
border-color: #10b98144;
|
||||||
|
background: linear-gradient(135deg, #1a1a2e 0%, #1e2e24 100%);
|
||||||
|
}
|
||||||
|
|
||||||
.cw-node-header {
|
.cw-node-header {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
|
@ -411,6 +447,20 @@ folk-campaign-workflow {
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.cw-node-cat {
|
||||||
|
font-size: 9px;
|
||||||
|
font-weight: 600;
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 0.04em;
|
||||||
|
padding: 1px 5px;
|
||||||
|
border-radius: 3px;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
.cw-node-cat--trigger { color: #60a5fa; background: #3b82f622; }
|
||||||
|
.cw-node-cat--delay { color: #c084fc; background: #a855f722; }
|
||||||
|
.cw-node-cat--condition { color: #fbbf24; background: #f59e0b22; }
|
||||||
|
.cw-node-cat--action { color: #34d399; background: #10b98122; }
|
||||||
|
|
||||||
.cw-node-status {
|
.cw-node-status {
|
||||||
width: 8px;
|
width: 8px;
|
||||||
height: 8px;
|
height: 8px;
|
||||||
|
|
@ -494,6 +544,10 @@ folk-campaign-workflow {
|
||||||
|
|
||||||
/* ── Mobile ── */
|
/* ── Mobile ── */
|
||||||
@media (max-width: 768px) {
|
@media (max-width: 768px) {
|
||||||
|
folk-campaign-workflow {
|
||||||
|
height: calc(100vh - 56px);
|
||||||
|
}
|
||||||
|
|
||||||
.cw-palette {
|
.cw-palette {
|
||||||
width: 160px;
|
width: 160px;
|
||||||
min-width: 160px;
|
min-width: 160px;
|
||||||
|
|
|
||||||
|
|
@ -348,10 +348,19 @@ class FolkCampaignWorkflow extends HTMLElement {
|
||||||
</g>
|
</g>
|
||||||
</svg>
|
</svg>
|
||||||
<div class="cw-zoom-controls">
|
<div class="cw-zoom-controls">
|
||||||
<button class="cw-zoom-btn" id="zoom-out">-</button>
|
<button class="cw-zoom-btn" id="zoom-out" title="Zoom out">
|
||||||
|
<svg width="16" height="16" viewBox="0 0 16 16" fill="none"><path d="M3 8h10" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/></svg>
|
||||||
|
</button>
|
||||||
|
<span class="cw-zoom-sep"></span>
|
||||||
<span class="cw-zoom-level" id="zoom-level">${Math.round(this.canvasZoom * 100)}%</span>
|
<span class="cw-zoom-level" id="zoom-level">${Math.round(this.canvasZoom * 100)}%</span>
|
||||||
<button class="cw-zoom-btn" id="zoom-in">+</button>
|
<span class="cw-zoom-sep"></span>
|
||||||
<button class="cw-zoom-btn" id="zoom-fit" title="Fit view">⇄</button>
|
<button class="cw-zoom-btn" id="zoom-in" title="Zoom in">
|
||||||
|
<svg width="16" height="16" viewBox="0 0 16 16" fill="none"><path d="M8 3v10M3 8h10" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/></svg>
|
||||||
|
</button>
|
||||||
|
<span class="cw-zoom-sep"></span>
|
||||||
|
<button class="cw-zoom-btn" id="zoom-fit" title="Fit to view">
|
||||||
|
<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>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
@ -400,12 +409,13 @@ class FolkCampaignWorkflow extends HTMLElement {
|
||||||
}
|
}
|
||||||
|
|
||||||
return `
|
return `
|
||||||
<g class="cw-node ${isSelected ? 'selected' : ''}" data-node-id="${node.id}">
|
<g class="cw-node cw-node--${def.category} ${isSelected ? 'selected' : ''}" data-node-id="${node.id}">
|
||||||
<foreignObject x="${node.position.x}" y="${node.position.y}" width="${NODE_WIDTH}" height="${NODE_HEIGHT}">
|
<foreignObject x="${node.position.x}" y="${node.position.y}" width="${NODE_WIDTH}" height="${NODE_HEIGHT}">
|
||||||
<div xmlns="http://www.w3.org/1999/xhtml" style="width:100%;height:100%">
|
<div xmlns="http://www.w3.org/1999/xhtml" style="width:100%;height:100%">
|
||||||
<div class="cw-node-header" style="border-left: 3px solid ${catColor}">
|
<div class="cw-node-header" style="border-left: 3px solid ${catColor}">
|
||||||
<span class="cw-node-icon">${def.icon}</span>
|
<span class="cw-node-icon">${def.icon}</span>
|
||||||
<span class="cw-node-label">${esc(node.label)}</span>
|
<span class="cw-node-label">${esc(node.label)}</span>
|
||||||
|
<span class="cw-node-cat cw-node-cat--${def.category}">${def.category}</span>
|
||||||
<span class="cw-node-status ${status}"></span>
|
<span class="cw-node-status ${status}"></span>
|
||||||
</div>
|
</div>
|
||||||
<div class="cw-node-ports">
|
<div class="cw-node-ports">
|
||||||
|
|
@ -960,13 +970,28 @@ class FolkCampaignWorkflow extends HTMLElement {
|
||||||
}
|
}
|
||||||
|
|
||||||
private handleKeyDown(e: KeyboardEvent) {
|
private handleKeyDown(e: KeyboardEvent) {
|
||||||
|
const tag = (e.target as Element)?.tagName;
|
||||||
|
const isEditing = tag === 'INPUT' || tag === 'TEXTAREA' || tag === 'SELECT';
|
||||||
|
|
||||||
if (e.key === 'Escape') {
|
if (e.key === 'Escape') {
|
||||||
if (this.wiringActive) this.cancelWiring();
|
if (this.wiringActive) this.cancelWiring();
|
||||||
}
|
}
|
||||||
if ((e.key === 'Delete' || e.key === 'Backspace') && this.selectedNodeId) {
|
if ((e.key === 'Delete' || e.key === 'Backspace') && this.selectedNodeId) {
|
||||||
if ((e.target as Element)?.tagName === 'INPUT' || (e.target as Element)?.tagName === 'TEXTAREA') return;
|
if (isEditing) return;
|
||||||
this.deleteNode(this.selectedNodeId);
|
this.deleteNode(this.selectedNodeId);
|
||||||
}
|
}
|
||||||
|
if (isEditing) return;
|
||||||
|
if (e.key === 'f' || e.key === 'F') {
|
||||||
|
this.fitView();
|
||||||
|
}
|
||||||
|
if (e.key === '=' || e.key === '+') {
|
||||||
|
const svg = this.shadow.getElementById('cw-svg');
|
||||||
|
if (svg) { const r = svg.getBoundingClientRect(); this.zoomAt(r.width / 2, r.height / 2, 1.2); }
|
||||||
|
}
|
||||||
|
if (e.key === '-') {
|
||||||
|
const svg = this.shadow.getElementById('cw-svg');
|
||||||
|
if (svg) { const r = svg.getBoundingClientRect(); this.zoomAt(r.width / 2, r.height / 2, 0.8); }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue