feat(canvas): folk-shape and canvas.html updates
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
0d8d42c49e
commit
851cc283ee
|
|
@ -114,6 +114,10 @@ const styles = css`
|
|||
transition: opacity 0.3s ease, filter 0.3s ease;
|
||||
}
|
||||
|
||||
:host(:state(locked)) {
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
[part="forgotten-tooltip"] {
|
||||
display: none;
|
||||
position: absolute;
|
||||
|
|
@ -319,6 +323,18 @@ export class FolkShape extends FolkElement {
|
|||
: this.#internals.states.delete("forgotten");
|
||||
}
|
||||
|
||||
#locked = false;
|
||||
get locked() {
|
||||
return this.#locked;
|
||||
}
|
||||
set locked(locked) {
|
||||
if (this.#locked === locked) return;
|
||||
this.#locked = locked;
|
||||
locked
|
||||
? this.#internals.states.add("locked")
|
||||
: this.#internals.states.delete("locked");
|
||||
}
|
||||
|
||||
#editing = false;
|
||||
get editing() {
|
||||
return this.#editing;
|
||||
|
|
@ -470,6 +486,9 @@ export class FolkShape extends FolkElement {
|
|||
// In feed mode, suppress all drag/resize interactions
|
||||
if (this.closest('#canvas.feed-mode')) return;
|
||||
|
||||
// Locked shapes cannot be moved or resized, but can still be interacted with
|
||||
if (this.#locked) return;
|
||||
|
||||
// Handle touch events for mobile drag support
|
||||
if (event instanceof TouchEvent) {
|
||||
const target = event.composedPath()[0] as HTMLElement;
|
||||
|
|
@ -871,6 +890,8 @@ export class FolkShape extends FolkElement {
|
|||
rotation: this.rotation,
|
||||
};
|
||||
|
||||
if (this.#locked) json.locked = true;
|
||||
|
||||
// Include port values when ports have data
|
||||
if (this.#ports.size > 0) {
|
||||
const portValues: Record<string, unknown> = {};
|
||||
|
|
@ -907,6 +928,7 @@ export class FolkShape extends FolkElement {
|
|||
if (typeof data.rotation === "number" && Number.isFinite(data.rotation)) {
|
||||
shape.rotation = data.rotation;
|
||||
}
|
||||
if (data.locked) shape.locked = true;
|
||||
|
||||
return shape;
|
||||
}
|
||||
|
|
@ -921,6 +943,7 @@ export class FolkShape extends FolkElement {
|
|||
if (this.width !== data.width) this.width = data.width;
|
||||
if (this.height !== data.height) this.height = data.height;
|
||||
if (this.rotation !== data.rotation) this.rotation = data.rotation;
|
||||
if (!!data.locked !== this.#locked) this.locked = !!data.locked;
|
||||
|
||||
// Restore port values without dispatching events (avoids sync loops)
|
||||
if (data.ports && typeof data.ports === "object") {
|
||||
|
|
|
|||
|
|
@ -2411,6 +2411,25 @@
|
|||
.shape-schedule-icon:hover {
|
||||
background: #2a2a3e; border-color: #818cf8;
|
||||
}
|
||||
.shape-lock-icon {
|
||||
position: fixed; z-index: 9999; width: 28px; height: 28px;
|
||||
border-radius: 50%; border: 1px solid #444;
|
||||
background: var(--rs-bg-surface, #1e1e2e); color: #e0e0e0;
|
||||
font-size: 14px; cursor: pointer; display: flex;
|
||||
align-items: center; justify-content: center;
|
||||
box-shadow: 0 2px 8px rgba(0,0,0,0.4);
|
||||
opacity: 0; transition: opacity 0.15s; pointer-events: none;
|
||||
padding: 0; line-height: 1;
|
||||
}
|
||||
.shape-lock-icon.visible {
|
||||
opacity: 1; pointer-events: auto;
|
||||
}
|
||||
.shape-lock-icon:hover {
|
||||
background: #2a2a3e; border-color: #818cf8;
|
||||
}
|
||||
.shape-lock-icon.locked {
|
||||
border-color: #f59e0b; color: #f59e0b;
|
||||
}
|
||||
</style>
|
||||
<div id="reminder-widget">
|
||||
<div class="rw-header"><span class="rw-header-icon">🔔</span> Remind me of this on:</div>
|
||||
|
|
@ -3156,6 +3175,7 @@
|
|||
}
|
||||
__miCanvasBridge.setSelection([...selectedShapeIds]);
|
||||
updateScheduleIcon();
|
||||
updateLockIcon();
|
||||
}
|
||||
|
||||
// ── Floating schedule icon on selected shape ──
|
||||
|
|
@ -3201,6 +3221,51 @@
|
|||
if (rwWidget) rwWidget.classList.remove("visible");
|
||||
}
|
||||
|
||||
// ── Floating lock icon on selected shape ──
|
||||
let lockIconEl = null;
|
||||
function updateLockIcon() {
|
||||
if (selectedShapeIds.size >= 1) {
|
||||
const id = [...selectedShapeIds][0];
|
||||
const el = document.getElementById(id);
|
||||
if (el) {
|
||||
if (!lockIconEl) {
|
||||
lockIconEl = document.createElement("button");
|
||||
lockIconEl.className = "shape-lock-icon";
|
||||
lockIconEl.textContent = "🔒";
|
||||
lockIconEl.title = "Lock/unlock shape";
|
||||
lockIconEl.addEventListener("click", (ev) => {
|
||||
ev.stopPropagation();
|
||||
// Toggle lock on all selected shapes
|
||||
const ids = [...selectedShapeIds];
|
||||
const firstEl = document.getElementById(ids[0]);
|
||||
const newLocked = !firstEl?.locked;
|
||||
for (const sid of ids) {
|
||||
const sel = document.getElementById(sid);
|
||||
if (sel) {
|
||||
sel.locked = newLocked;
|
||||
// Trigger sync update
|
||||
sel.dispatchEvent(new CustomEvent("content-change", { bubbles: true }));
|
||||
}
|
||||
}
|
||||
lockIconEl.textContent = newLocked ? "🔒" : "🔓";
|
||||
lockIconEl.classList.toggle("locked", newLocked);
|
||||
});
|
||||
document.body.appendChild(lockIconEl);
|
||||
}
|
||||
const rect = el.getBoundingClientRect();
|
||||
lockIconEl.style.left = (rect.right + 4) + "px";
|
||||
lockIconEl.style.top = (rect.top + 28) + "px"; // below schedule icon
|
||||
lockIconEl.classList.add("visible");
|
||||
// Reflect current lock state
|
||||
const isLocked = el.locked;
|
||||
lockIconEl.textContent = isLocked ? "🔒" : "🔓";
|
||||
lockIconEl.classList.toggle("locked", isLocked);
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (lockIconEl) lockIconEl.classList.remove("visible");
|
||||
}
|
||||
|
||||
function rectsOverlapScreen(sel, r) {
|
||||
return !(sel.left > r.right || sel.right < r.left ||
|
||||
sel.top > r.bottom || sel.bottom < r.top);
|
||||
|
|
@ -4809,7 +4874,7 @@
|
|||
}
|
||||
|
||||
// Disable shape interaction when drawing tools are active.
|
||||
// Eraser keeps canvasContent interactive so it can delete wb-drawing shapes.
|
||||
// Eraser keeps canvasContent interactive so it can erase any shape.
|
||||
if (wbTool && wbTool !== "eraser") {
|
||||
canvasContent.style.pointerEvents = "none";
|
||||
wbOverlay.style.pointerEvents = "all";
|
||||
|
|
@ -5224,12 +5289,14 @@
|
|||
}
|
||||
});
|
||||
|
||||
// Eraser for wb-drawing folk-shapes — capturing phase intercepts
|
||||
// Eraser for any folk-shape — capturing phase intercepts
|
||||
// before normal shape interaction (drag/select) kicks in
|
||||
canvasContent.addEventListener("pointerdown", (e) => {
|
||||
if (wbTool !== "eraser") return;
|
||||
const target = e.target.closest?.("[data-wb-drawing]");
|
||||
const target = e.target.closest?.("folk-shape");
|
||||
if (!target) return;
|
||||
// Locked shapes cannot be erased
|
||||
if (target.locked) return;
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
const state = sync.getShapeVisualState(target.id);
|
||||
|
|
@ -5891,6 +5958,7 @@
|
|||
// Update remote cursors to match new camera position
|
||||
presence.setCamera(panX, panY, scale);
|
||||
updateScheduleIcon();
|
||||
updateLockIcon();
|
||||
}
|
||||
|
||||
// Re-render canvas background when user changes preference
|
||||
|
|
|
|||
Loading…
Reference in New Issue