feat(rflows): fix text-click drag, smart source labels, Pay by buttons
- Prevent foreignObject HTML clicks from starting node drag (select + inline edit instead)
- New source nodes get personalized "{username}'s stream to {flowName}" label
- Replace source type <select> dropdowns with clickable "Pay by" button grid
in ICP panel, editor panel, and source modal
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
a28eb88140
commit
f24fee942b
|
|
@ -576,6 +576,30 @@
|
||||||
.source-type-btn:hover { border-color: var(--rs-bg-surface-raised); background: var(--rs-bg-surface); }
|
.source-type-btn:hover { border-color: var(--rs-bg-surface-raised); background: var(--rs-bg-surface); }
|
||||||
.source-type-btn--active { border-color: #10b981; background: var(--rflows-source-hover-bg, #064e3b); color: var(--rflows-source-text, #6ee7b7); }
|
.source-type-btn--active { border-color: #10b981; background: var(--rflows-source-hover-bg, #064e3b); color: var(--rflows-source-text, #6ee7b7); }
|
||||||
|
|
||||||
|
/* Pay-by button grid (ICP - compact) */
|
||||||
|
.icp-pay-by { display: grid; grid-template-columns: repeat(3, 1fr); gap: 4px; margin-top: 4px; }
|
||||||
|
.icp-pay-btn {
|
||||||
|
display: flex; align-items: center; justify-content: center; gap: 4px;
|
||||||
|
padding: 6px 4px; border-radius: 6px; border: 1.5px solid var(--rs-border-strong);
|
||||||
|
background: var(--rs-bg-page); color: var(--rs-text-secondary); cursor: pointer;
|
||||||
|
transition: all 0.15s; font-size: 10px; font-weight: 500;
|
||||||
|
font-family: system-ui, -apple-system, sans-serif;
|
||||||
|
}
|
||||||
|
.icp-pay-btn:hover { border-color: var(--rs-bg-surface-raised); background: var(--rs-bg-surface); }
|
||||||
|
.icp-pay-btn--active { border-color: #10b981; background: var(--rflows-source-hover-bg, #064e3b); color: var(--rflows-source-text, #6ee7b7); }
|
||||||
|
|
||||||
|
/* Pay-by button grid (editor panel - larger) */
|
||||||
|
.editor-pay-by { display: grid; grid-template-columns: repeat(3, 1fr); gap: 6px; margin-top: 4px; }
|
||||||
|
.editor-pay-btn {
|
||||||
|
display: flex; flex-direction: column; align-items: center; gap: 4px;
|
||||||
|
padding: 10px 6px; border-radius: 8px; border: 2px solid var(--rs-border-strong);
|
||||||
|
background: var(--rs-bg-page); color: var(--rs-text-secondary); cursor: pointer;
|
||||||
|
transition: all 0.15s; font-size: 11px; font-weight: 500;
|
||||||
|
font-family: system-ui, -apple-system, sans-serif;
|
||||||
|
}
|
||||||
|
.editor-pay-btn:hover { border-color: var(--rs-bg-surface-raised); background: var(--rs-bg-surface); }
|
||||||
|
.editor-pay-btn--active { border-color: #10b981; background: var(--rflows-source-hover-bg, #064e3b); color: var(--rflows-source-text, #6ee7b7); }
|
||||||
|
|
||||||
/* Node hover tooltip */
|
/* Node hover tooltip */
|
||||||
.flows-node-tooltip {
|
.flows-node-tooltip {
|
||||||
position: absolute; z-index: 30; pointer-events: none;
|
position: absolute; z-index: 30; pointer-events: none;
|
||||||
|
|
|
||||||
|
|
@ -1343,6 +1343,13 @@ class FolkFlowsApp extends HTMLElement {
|
||||||
this.selectedEdgeKey = null;
|
this.selectedEdgeKey = null;
|
||||||
this.updateSelectionHighlight();
|
this.updateSelectionHighlight();
|
||||||
|
|
||||||
|
// If click originated from HTML inside foreignObject, open inline edit but skip drag
|
||||||
|
const target = e.target as Element;
|
||||||
|
if (target instanceof HTMLElement && target.closest("foreignObject")) {
|
||||||
|
this.enterInlineEdit(nodeId);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// Prepare drag (but don't start until threshold exceeded)
|
// Prepare drag (but don't start until threshold exceeded)
|
||||||
nodeDragStarted = false;
|
nodeDragStarted = false;
|
||||||
this.draggingNodeId = nodeId;
|
this.draggingNodeId = nodeId;
|
||||||
|
|
@ -2769,10 +2776,12 @@ class FolkFlowsApp extends HTMLElement {
|
||||||
<input class="icp-input" data-icp-field="label" value="${this.esc(d.label)}"/></div>
|
<input class="icp-input" data-icp-field="label" value="${this.esc(d.label)}"/></div>
|
||||||
<div class="icp-field"><label class="icp-label">Flow Rate ($/mo)</label>
|
<div class="icp-field"><label class="icp-label">Flow Rate ($/mo)</label>
|
||||||
<input class="icp-input" data-icp-field="flowRate" type="number" value="${d.flowRate}"/></div>
|
<input class="icp-input" data-icp-field="flowRate" type="number" value="${d.flowRate}"/></div>
|
||||||
<div class="icp-field"><label class="icp-label">Source Type</label>
|
<div class="icp-field"><label class="icp-label">Pay by</label>
|
||||||
<select class="icp-select" data-icp-field="sourceType">
|
<div class="icp-pay-by">
|
||||||
${["card", "safe_wallet", "ridentity", "unconfigured"].map((t) => `<option value="${t}" ${d.sourceType === t ? "selected" : ""}>${t}</option>`).join("")}
|
<button class="icp-pay-btn ${d.sourceType === "card" ? "icp-pay-btn--active" : ""}" data-icp-source-type="card">💳 Card</button>
|
||||||
</select></div>`;
|
<button class="icp-pay-btn ${d.sourceType === "safe_wallet" ? "icp-pay-btn--active" : ""}" data-icp-source-type="safe_wallet">🔒 Wallet</button>
|
||||||
|
<button class="icp-pay-btn ${d.sourceType === "ridentity" ? "icp-pay-btn--active" : ""}" data-icp-source-type="ridentity">👤 EncryptID</button>
|
||||||
|
</div></div>`;
|
||||||
if (d.sourceType === "card") {
|
if (d.sourceType === "card") {
|
||||||
html += `<button class="icp-fund-btn" data-icp-action="fund">Fund with Card</button>`;
|
html += `<button class="icp-fund-btn" data-icp-action="fund">Fund with Card</button>`;
|
||||||
}
|
}
|
||||||
|
|
@ -3124,6 +3133,19 @@ class FolkFlowsApp extends HTMLElement {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Pay-by buttons (source nodes)
|
||||||
|
overlay.querySelectorAll("[data-icp-source-type]").forEach((btn) => {
|
||||||
|
btn.addEventListener("click", (e: Event) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
(node.data as SourceNodeData).sourceType = (btn as HTMLElement).dataset.icpSourceType as any;
|
||||||
|
const body = overlay.querySelector(".icp-body") as HTMLElement;
|
||||||
|
if (body) body.innerHTML = this.renderInlineConfigContent(node);
|
||||||
|
this.attachInlineConfigFieldListeners(overlay, node);
|
||||||
|
this.redrawNodeOnly(node);
|
||||||
|
this.redrawEdges();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
// Range sliders
|
// Range sliders
|
||||||
overlay.querySelectorAll("[data-icp-range]").forEach((el) => {
|
overlay.querySelectorAll("[data-icp-range]").forEach((el) => {
|
||||||
const input = el as HTMLInputElement;
|
const input = el as HTMLInputElement;
|
||||||
|
|
@ -3326,10 +3348,12 @@ class FolkFlowsApp extends HTMLElement {
|
||||||
<input class="editor-input" data-field="label" value="${this.esc(d.label)}"/></div>
|
<input class="editor-input" data-field="label" value="${this.esc(d.label)}"/></div>
|
||||||
<div class="editor-field"><label class="editor-label">Flow Rate ($/mo)</label>
|
<div class="editor-field"><label class="editor-label">Flow Rate ($/mo)</label>
|
||||||
<input class="editor-input" data-field="flowRate" type="number" value="${d.flowRate}"/></div>
|
<input class="editor-input" data-field="flowRate" type="number" value="${d.flowRate}"/></div>
|
||||||
<div class="editor-field"><label class="editor-label">Source Type</label>
|
<div class="editor-field"><label class="editor-label">Pay by</label>
|
||||||
<select class="editor-select" data-field="sourceType">
|
<div class="editor-pay-by">
|
||||||
${["card", "safe_wallet", "ridentity", "unconfigured"].map((t) => `<option value="${t}" ${d.sourceType === t ? "selected" : ""}>${t}</option>`).join("")}
|
<button class="editor-pay-btn ${d.sourceType === "card" ? "editor-pay-btn--active" : ""}" data-editor-source-type="card">💳<span>Card</span></button>
|
||||||
</select></div>`;
|
<button class="editor-pay-btn ${d.sourceType === "safe_wallet" ? "editor-pay-btn--active" : ""}" data-editor-source-type="safe_wallet">🔒<span>Wallet</span></button>
|
||||||
|
<button class="editor-pay-btn ${d.sourceType === "ridentity" ? "editor-pay-btn--active" : ""}" data-editor-source-type="ridentity">👤<span>EncryptID</span></button>
|
||||||
|
</div></div>`;
|
||||||
if (d.sourceType === "card") {
|
if (d.sourceType === "card") {
|
||||||
html += `<div class="editor-field" style="margin-top:12px">
|
html += `<div class="editor-field" style="margin-top:12px">
|
||||||
<button class="editor-btn fund-card-btn" data-action="fund-with-card"
|
<button class="editor-btn fund-card-btn" data-action="fund-with-card"
|
||||||
|
|
@ -3765,6 +3789,16 @@ class FolkFlowsApp extends HTMLElement {
|
||||||
this.openUserOnRamp(node.id);
|
this.openUserOnRamp(node.id);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Pay-by buttons (source editor)
|
||||||
|
panel.querySelectorAll("[data-editor-source-type]").forEach((btn) => {
|
||||||
|
btn.addEventListener("click", () => {
|
||||||
|
(node.data as SourceNodeData).sourceType = (btn as HTMLElement).dataset.editorSourceType as any;
|
||||||
|
this.drawCanvasContent();
|
||||||
|
this.scheduleSave();
|
||||||
|
this.openEditor(node.id);
|
||||||
|
});
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// ─── Node hover tooltip ──────────────────────────────
|
// ─── Node hover tooltip ──────────────────────────────
|
||||||
|
|
@ -4051,7 +4085,7 @@ class FolkFlowsApp extends HTMLElement {
|
||||||
<button class="flows-modal__close" data-modal-action="close">×</button>
|
<button class="flows-modal__close" data-modal-action="close">×</button>
|
||||||
</div>
|
</div>
|
||||||
<div style="margin-bottom:16px">
|
<div style="margin-bottom:16px">
|
||||||
<div style="font-size:11px;font-weight:600;color:var(--rs-text-secondary);text-transform:uppercase;letter-spacing:0.05em;margin-bottom:8px">Source Type</div>
|
<div style="font-size:11px;font-weight:600;color:var(--rs-text-secondary);text-transform:uppercase;letter-spacing:0.05em;margin-bottom:8px">Pay by</div>
|
||||||
<div class="source-type-grid">
|
<div class="source-type-grid">
|
||||||
${["card", "safe_wallet", "ridentity"].map((t) => `
|
${["card", "safe_wallet", "ridentity"].map((t) => `
|
||||||
<button class="source-type-btn ${d.sourceType === t ? "source-type-btn--active" : ""}" data-source-type="${t}">
|
<button class="source-type-btn ${d.sourceType === t ? "source-type-btn--active" : ""}" data-source-type="${t}">
|
||||||
|
|
@ -4152,7 +4186,11 @@ class FolkFlowsApp extends HTMLElement {
|
||||||
const id = `${type}-${Date.now().toString(36)}`;
|
const id = `${type}-${Date.now().toString(36)}`;
|
||||||
let data: any;
|
let data: any;
|
||||||
if (type === "source") {
|
if (type === "source") {
|
||||||
data = { label: "New Source", flowRate: 1000, sourceType: "card", targetAllocations: [] } as SourceNodeData;
|
const username = getUsername();
|
||||||
|
const defaultLabel = username
|
||||||
|
? `${username}'s stream to ${this.flowName || "Flow"}`
|
||||||
|
: `Stream to ${this.flowName || "Flow"}`;
|
||||||
|
data = { label: defaultLabel, flowRate: 1000, sourceType: "card", targetAllocations: [] } as SourceNodeData;
|
||||||
} else if (type === "funnel") {
|
} else if (type === "funnel") {
|
||||||
data = {
|
data = {
|
||||||
label: "New Funnel", currentValue: 0, desiredOutflow: 5000,
|
label: "New Funnel", currentValue: 0, desiredOutflow: 5000,
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue