fix(ux): instant bulk-delete dialog + post-login space routing
Bulk delete dialog: switch from click to pointerdown for instant touch response, raise z-index above all overlays, add hover/active feedback and keyboard support. Post-login routing: reload page when logging in on a non-demo space so access gates clear and CRDT sync reconnects with auth. Silently provisions personal space in background. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
0c04d6bee1
commit
d458a00550
|
|
@ -264,9 +264,17 @@ function autoResolveSpace(token: string, username: string): void {
|
||||||
|
|
||||||
// Detect current space
|
// Detect current space
|
||||||
const currentSpace = _getCurrentSpace();
|
const currentSpace = _getCurrentSpace();
|
||||||
if (currentSpace !== "demo") return; // Already on a non-demo space
|
|
||||||
|
|
||||||
// Provision personal space and redirect
|
if (currentSpace !== "demo") {
|
||||||
|
// User followed a link to a specific space (e.g., orgname.rspace.online).
|
||||||
|
// Provision their personal space silently, then reload to apply the
|
||||||
|
// authenticated session (clears access gates, reconnects CRDT sync, etc.).
|
||||||
|
autoProvisionSpace(token);
|
||||||
|
window.location.reload();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// On demo/landing — provision personal space and redirect there
|
||||||
fetch("/api/spaces/auto-provision", {
|
fetch("/api/spaces/auto-provision", {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
headers: {
|
headers: {
|
||||||
|
|
|
||||||
|
|
@ -6685,31 +6685,53 @@
|
||||||
function showBulkDeleteConfirm(count) {
|
function showBulkDeleteConfirm(count) {
|
||||||
if (bulkDeleteOverlay) return; // prevent stacking from key repeat
|
if (bulkDeleteOverlay) return; // prevent stacking from key repeat
|
||||||
const overlay = document.createElement("div");
|
const overlay = document.createElement("div");
|
||||||
overlay.style.cssText = "position:fixed;inset:0;background:rgba(0,0,0,0.6);z-index:10000;display:flex;align-items:center;justify-content:center";
|
overlay.style.cssText = "position:fixed;inset:0;background:rgba(0,0,0,0.6);z-index:100002;display:flex;align-items:center;justify-content:center;touch-action:manipulation";
|
||||||
const dialog = document.createElement("div");
|
const dialog = document.createElement("div");
|
||||||
dialog.style.cssText = "background:var(--rs-bg-surface,#1e1b4b);border:1px solid var(--rs-border,#334155);border-radius:12px;padding:1.5rem 2rem;max-width:380px;text-align:center;color:var(--rs-text-primary,#e2e8f0);font-family:system-ui,sans-serif";
|
dialog.style.cssText = "background:var(--rs-bg-surface,#1e1b4b);border:1px solid var(--rs-border,#334155);border-radius:12px;padding:1.5rem 2rem;max-width:380px;text-align:center;color:var(--rs-text-primary,#e2e8f0);font-family:system-ui,sans-serif;user-select:none";
|
||||||
|
const btnBase = "padding:0.5rem 1.25rem;border-radius:8px;cursor:pointer;font-size:0.875rem;touch-action:manipulation;user-select:none;-webkit-tap-highlight-color:transparent;transition:filter 0.1s";
|
||||||
dialog.innerHTML = `
|
dialog.innerHTML = `
|
||||||
<h3 style="margin:0 0 0.5rem;font-size:1.125rem">Delete ${count} elements?</h3>
|
<h3 style="margin:0 0 0.5rem;font-size:1.125rem">Delete ${count} elements?</h3>
|
||||||
<p style="color:var(--rs-text-secondary,#94a3b8);font-size:0.875rem;margin:0 0 1.25rem;line-height:1.5">You are about to delete a large number of elements. This action cannot be easily undone.</p>
|
<p style="color:var(--rs-text-secondary,#94a3b8);font-size:0.875rem;margin:0 0 1.25rem;line-height:1.5">You are about to delete a large number of elements. This action cannot be easily undone.</p>
|
||||||
<div style="display:flex;gap:0.75rem;justify-content:center">
|
<div style="display:flex;gap:0.75rem;justify-content:center">
|
||||||
<button id="bulk-delete-cancel" style="padding:0.5rem 1.25rem;border-radius:8px;border:1px solid var(--rs-border,#334155);background:transparent;color:var(--rs-text-primary,#e2e8f0);cursor:pointer;font-size:0.875rem">Cancel</button>
|
<button id="bulk-delete-cancel" style="${btnBase};border:1px solid var(--rs-border,#334155);background:transparent;color:var(--rs-text-primary,#e2e8f0)">Cancel</button>
|
||||||
<button id="bulk-delete-confirm" style="padding:0.5rem 1.25rem;border-radius:8px;border:1px solid #dc2626;background:#dc2626;color:#fff;cursor:pointer;font-size:0.875rem;font-weight:600">DELETE</button>
|
<button id="bulk-delete-confirm" style="${btnBase};border:1px solid #dc2626;background:#dc2626;color:#fff;font-weight:600">DELETE</button>
|
||||||
</div>`;
|
</div>`;
|
||||||
overlay.appendChild(dialog);
|
overlay.appendChild(dialog);
|
||||||
document.body.appendChild(overlay);
|
document.body.appendChild(overlay);
|
||||||
bulkDeleteOverlay = overlay;
|
bulkDeleteOverlay = overlay;
|
||||||
|
|
||||||
overlay.addEventListener("click", (e) => { if (e.target === overlay) dismissBulkDelete(); });
|
const cancelBtn = dialog.querySelector("#bulk-delete-cancel");
|
||||||
dialog.querySelector("#bulk-delete-cancel").addEventListener("click", dismissBulkDelete);
|
const confirmBtn = dialog.querySelector("#bulk-delete-confirm");
|
||||||
dialog.querySelector("#bulk-delete-confirm").addEventListener("click", () => {
|
|
||||||
|
// Use pointerdown for instant response (no 300ms click delay on touch)
|
||||||
|
overlay.addEventListener("pointerdown", (e) => {
|
||||||
|
if (e.target === overlay) { e.stopPropagation(); dismissBulkDelete(); }
|
||||||
|
});
|
||||||
|
cancelBtn.addEventListener("pointerdown", (e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
dismissBulkDelete();
|
||||||
|
});
|
||||||
|
confirmBtn.addEventListener("pointerdown", (e) => {
|
||||||
|
e.stopPropagation();
|
||||||
dismissBulkDelete();
|
dismissBulkDelete();
|
||||||
doDeleteSelected();
|
doDeleteSelected();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Visual feedback on hover/active
|
||||||
|
[cancelBtn, confirmBtn].forEach(btn => {
|
||||||
|
btn.addEventListener("pointerenter", () => { btn.style.filter = "brightness(1.2)"; });
|
||||||
|
btn.addEventListener("pointerleave", () => { btn.style.filter = ""; });
|
||||||
|
btn.addEventListener("pointerdown", () => { btn.style.filter = "brightness(0.85)"; });
|
||||||
|
});
|
||||||
|
|
||||||
// Escape key closes dialog
|
// Escape key closes dialog
|
||||||
const escHandler = (e) => {
|
const escHandler = (e) => {
|
||||||
if (e.key === "Escape") { dismissBulkDelete(); document.removeEventListener("keydown", escHandler); }
|
if (e.key === "Escape") { dismissBulkDelete(); document.removeEventListener("keydown", escHandler); }
|
||||||
};
|
};
|
||||||
document.addEventListener("keydown", escHandler);
|
document.addEventListener("keydown", escHandler);
|
||||||
|
|
||||||
|
// Focus the confirm button so Enter also works
|
||||||
|
confirmBtn.focus();
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── Canvas pointer interaction: pan-first + hold-to-select ──
|
// ── Canvas pointer interaction: pan-first + hold-to-select ──
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue