@floless/app 0.66.0 → 0.66.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/floless-server.cjs
CHANGED
|
@@ -53022,7 +53022,7 @@ function appVersion() {
|
|
|
53022
53022
|
return resolveVersion({
|
|
53023
53023
|
isSea: isSea2(),
|
|
53024
53024
|
sqVersionXml: readSqVersionXml(),
|
|
53025
|
-
define: true ? "0.66.
|
|
53025
|
+
define: true ? "0.66.1" : void 0,
|
|
53026
53026
|
pkgVersion: readPkgVersion()
|
|
53027
53027
|
});
|
|
53028
53028
|
}
|
|
@@ -53032,7 +53032,7 @@ function resolveChannel(s) {
|
|
|
53032
53032
|
return "dev";
|
|
53033
53033
|
}
|
|
53034
53034
|
function appChannel() {
|
|
53035
|
-
return resolveChannel({ isSea: isSea2(), define: true ? "0.66.
|
|
53035
|
+
return resolveChannel({ isSea: isSea2(), define: true ? "0.66.1" : void 0 });
|
|
53036
53036
|
}
|
|
53037
53037
|
|
|
53038
53038
|
// workflow-update.ts
|
|
@@ -2146,8 +2146,8 @@ function onMove(e) {
|
|
|
2146
2146
|
if (pending.epDrag) { onMoveEndpoint(e); return; }
|
|
2147
2147
|
if (!pending.grab || !pending.origMm) return;
|
|
2148
2148
|
if (!dragging && Math.hypot(e.clientX - downXY[0], e.clientY - downXY[1]) <= DRAG_TOL_PX) return;
|
|
2149
|
-
const canDrag =
|
|
2150
|
-
if (!canDrag) { if (!pending._hinted) { pending._hinted = true; if (api && api.dragMoveHint) api.dragMoveHint(); } return; } // drag-move OFF
|
|
2149
|
+
const canDrag = api && api.dragMoveEnabled && api.dragMoveEnabled(); // the toggle gates BOTH gestures — plain drag (move) AND Ctrl+drag (copy)
|
|
2150
|
+
if (!canDrag) { if (!pending._hinted) { pending._hinted = true; if (api && api.dragMoveHint) api.dragMoveHint(); } return; } // drag-move/copy OFF → never translate; onUp selects the grabbed member (dragging stays false)
|
|
2151
2151
|
if (!dragging) pending.mode = pending.copy ? 'copy' : (e.altKey ? 'vertical' : 'plan'); // lock the mode at drag start (copy is plan-only; Alt = vertical/elevation)
|
|
2152
2152
|
dragging = pending;
|
|
2153
2153
|
if (pending.mode === 'vertical') { onMoveVertical(e); return; }
|
|
@@ -2244,7 +2244,7 @@ function onUp(e) {
|
|
|
2244
2244
|
return;
|
|
2245
2245
|
}
|
|
2246
2246
|
if (!wasDragging) {
|
|
2247
|
-
if (p.id && !p.geo && !p.epDrag &&
|
|
2247
|
+
if (p.id && !p.geo && !p.epDrag && moved > DRAG_TOL_PX && api && api.onSelect) { api.onSelect(p.id, p.ctrl); return; } // a blocked drag (drag-move/copy OFF) acts on the GRABBED member, not whatever's under the release point (Ctrl toggles it)
|
|
2248
2248
|
clickSelect(e.clientX, e.clientY, p.ctrl); return; // click → cycle-pick: member, or a derived part stacked on it at a joint (Ctrl+click = additive select)
|
|
2249
2249
|
}
|
|
2250
2250
|
if (p.copy) { // Ctrl+drag → commit a copy at the plan delta (the editor clones + selects); the ghost was the preview
|
|
@@ -2343,10 +2343,10 @@ function updateStatusChip() {
|
|
|
2343
2343
|
else if (isolatedIds) txt = `Isolated: ${isolatedIds.size} · Show all to restore`;
|
|
2344
2344
|
// A read-only host (the vector 3D viewer) supplies its own idle hint — the default speaks editing
|
|
2345
2345
|
// verbs (move/stretch/elevate) a viewer's no-op api would silently ignore. Otherwise the default
|
|
2346
|
-
// tells the truth about the drag-move toggle (
|
|
2346
|
+
// tells the truth about the drag-move/copy toggle (ON = drag-move + Ctrl-drag-copy; OFF = click-select).
|
|
2347
2347
|
else if (api && typeof api.statusHint === 'function' && api.statusHint()) txt = api.statusHint();
|
|
2348
2348
|
else { const canDrag = api && api.dragMoveEnabled && api.dragMoveEnabled();
|
|
2349
|
-
txt = (canDrag ? 'Drag to move · ' : 'Click to select ·
|
|
2349
|
+
txt = (canDrag ? 'Drag to move · Ctrl+drag to copy · ' : 'Click to select · ') + 'ends to stretch · Alt+drag elevation · right-drag orbit · dbl-click to zoom in'; }
|
|
2350
2350
|
hoverChip.textContent = txt;
|
|
2351
2351
|
hoverChip.style.left = (rect.left + rect.width / 2) + 'px'; hoverChip.style.transform = 'translateX(-50%)';
|
|
2352
2352
|
hoverChip.style.top = (rect.bottom - 30) + 'px';
|
|
@@ -98,10 +98,10 @@
|
|
|
98
98
|
#moreMenu .msnap-sect{display:none} #moreMenu .msnap-sect.open{display:block}
|
|
99
99
|
#moreMenu button.msnap{display:flex;align-items:center;gap:0}
|
|
100
100
|
#moreMenu button.msnap.on{color:var(--text)} /* the switch carries the state — don't also brand the text (reads as an armed tool elsewhere in this menu) */
|
|
101
|
-
#moreMenu .mck{position:relative;width:26px;height:14px;margin-right:9px;border-radius:7px;border:1px solid var(--line);background:#0b1220;flex:none;transition:background-color .15s,border-color .15s}
|
|
102
|
-
#moreMenu .mck::after{content:'';position:absolute;top:1px;left:1px;width:10px;height:10px;border-radius:50%;background:var(--mut);transition:transform .15s,background-color .15s}
|
|
103
|
-
#moreMenu button.msnap.on .mck{background:rgba(59,130,246,.28);border-color:var(--brand)}
|
|
104
|
-
#moreMenu button.msnap.on .mck::after{transform:translateX(12px);background:var(--brand)}
|
|
101
|
+
#moreMenu .mck,.cmmenu .mck{position:relative;width:26px;height:14px;margin-right:9px;border-radius:7px;border:1px solid var(--line);background:#0b1220;flex:none;transition:background-color .15s,border-color .15s} /* delicate CSS-only slider switch — shared by the ⋯ Snapping rows and the Move/Copy → Drag-to-move/copy toggle */
|
|
102
|
+
#moreMenu .mck::after,.cmmenu .mck::after{content:'';position:absolute;top:1px;left:1px;width:10px;height:10px;border-radius:50%;background:var(--mut);transition:transform .15s,background-color .15s}
|
|
103
|
+
#moreMenu button.msnap.on .mck,.cmmenu #dragMoveB.on .mck{background:rgba(59,130,246,.28);border-color:var(--brand)}
|
|
104
|
+
#moreMenu button.msnap.on .mck::after,.cmmenu #dragMoveB.on .mck::after{transform:translateX(12px);background:var(--brand)}
|
|
105
105
|
#moreMenu button.msnap .sg{display:inline-block;width:17px;color:#22d3ee;opacity:.5;flex:none;transition:opacity .15s}
|
|
106
106
|
#moreMenu button.msnap.on .sg{opacity:1}
|
|
107
107
|
/* Quick-access snap bar — always-expanded row of glyph toggles, bottom-right of the canvas (both views); reuses the brand-fill "on" toolbar language */
|
|
@@ -161,9 +161,9 @@
|
|
|
161
161
|
.cmwrap{position:relative;display:inline-flex}
|
|
162
162
|
#xfB{white-space:nowrap}
|
|
163
163
|
#xfB.on{background:var(--brand);border-color:var(--brand);color:#fff} /* a transform tool is armed */
|
|
164
|
-
|
|
165
|
-
.cmmenu #dragMoveB
|
|
166
|
-
.cmmenu #dragMoveB.on{color:var(--text)}
|
|
164
|
+
/* Drag-to-move/copy menu item wears the shared .mck slider switch (rules above, shared with ⋯ Snapping). */
|
|
165
|
+
.cmmenu #dragMoveB{display:flex;align-items:center;justify-content:flex-start;gap:0}
|
|
166
|
+
.cmmenu #dragMoveB.on{color:var(--text)} /* the switch carries the state — don't also brand the text (reads as an armed tool) */
|
|
167
167
|
.cmmenu{display:none;position:absolute;left:0;top:calc(100% + 6px);min-width:200px;background:var(--panel);border:1px solid #475569;border-radius:8px;box-shadow:0 8px 24px rgba(0,0,0,.5);padding:4px 0;z-index:30}
|
|
168
168
|
.cmmenu.open{display:block}
|
|
169
169
|
.cmmenu button{display:flex;justify-content:space-between;gap:12px;width:100%;text-align:left;background:transparent;border:0;border-radius:0;padding:7px 12px;color:var(--text);white-space:nowrap}
|
|
@@ -369,7 +369,7 @@
|
|
|
369
369
|
<button id=cpArrB>Copy array…</button>
|
|
370
370
|
<button id=cpLevelB>Copy to level…</button>
|
|
371
371
|
<div class=mlabel>Dragging</div>
|
|
372
|
-
<button id=dragMoveB role=menuitemcheckbox aria-checked=false title="When ON, left-dragging a member moves it. When OFF (default), a click only selects
|
|
372
|
+
<button id=dragMoveB role=menuitemcheckbox aria-checked=false title="When ON, left-dragging a member moves it and Ctrl+drag copies it. When OFF (default), a drag does neither — a click only selects, so members can't be nudged by accident (the Move and Copy tools still work). Applies to 2D and 3D."><span class=mck aria-hidden=true></span>Drag to move/copy</button>
|
|
373
373
|
</div>
|
|
374
374
|
</div>
|
|
375
375
|
<button id=askAiBtn>Ask AI ▸</button>
|
|
@@ -1809,12 +1809,13 @@ function dimRefreshPrev(){if(!dimMode||!dimDraft||!dimLastPtr)return;
|
|
|
1809
1809
|
// array N×M. Same shell as the Dimension tool: armed flag routes the pointer handlers, the preview
|
|
1810
1810
|
// lives in its own <g> (no render() until commit), commits go through edit()/pushUndo. ---
|
|
1811
1811
|
let cmTool=null,cmArrayMode=false,cmDraft=null,cmAxis='free',cmCount=1,cmCountA=2,cmCountB=2,cmLastPtr=null,cmLastEvt=null;
|
|
1812
|
-
// Drag-to-move: OFF by default so a click only selects (no accidental nudges). Persisted
|
|
1813
|
-
// global (a UI pref, not per-app).
|
|
1812
|
+
// Drag-to-move/copy: OFF by default so a click only selects (no accidental nudges). Persisted
|
|
1813
|
+
// per-browser, global (a UI pref, not per-app). Gates BOTH drag gestures — left-drag=move AND
|
|
1814
|
+
// Ctrl+drag=copy; only Ctrl+CLICK (a click, not a drag) still toggles selection when off.
|
|
1814
1815
|
let dragMoveOn=false; try{dragMoveOn=localStorage.getItem('steel:dragmove:v1')==='1';}catch(_){}
|
|
1815
1816
|
let dragHintShown=false; try{dragHintShown=localStorage.getItem('steel:dragmovehint:v1')==='1';}catch(_){} // one-time "drag-move is off" teach on the first blocked drag
|
|
1816
1817
|
function setDragMove(on){dragMoveOn=!!on;document.body.classList.toggle('dragmove',dragMoveOn);
|
|
1817
|
-
const b=document.getElementById('dragMoveB');if(b){b.classList.toggle('on',dragMoveOn);b.setAttribute('aria-checked',String(dragMoveOn));
|
|
1818
|
+
const b=document.getElementById('dragMoveB');if(b){b.classList.toggle('on',dragMoveOn);b.setAttribute('aria-checked',String(dragMoveOn));} // the .mck slider carries the on/off state
|
|
1818
1819
|
try{localStorage.setItem('steel:dragmove:v1',dragMoveOn?'1':'0');}catch(_){}}
|
|
1819
1820
|
// cmDraft: {base:[x,y], vA:null} — array mode sets vA=[dx,dy,0] once direction A is picked/typed
|
|
1820
1821
|
function setCmUi(){document.body.classList.toggle('cmon',!!cmTool);
|
|
@@ -2093,7 +2094,7 @@ svg.addEventListener('pointerdown',e=>{if(e.button!==0)return;const t=e.target;
|
|
|
2093
2094
|
// Plain click selects on down (so a no-drag click still selects even with drag-move off). Ctrl defers
|
|
2094
2095
|
// its select to up — a ctrl-DRAG copies, a ctrl-CLICK toggles selection (today's behavior).
|
|
2095
2096
|
if(!ctrl&&!selIds.has(id)){selIds=new Set([id]);render();}
|
|
2096
|
-
const canDrag=
|
|
2097
|
+
const canDrag=dragMoveOn; // the toggle gates BOTH gestures: left-drag=move and Ctrl+drag=copy (Ctrl+CLICK still toggles selection below — a click, not a drag)
|
|
2097
2098
|
const srcIds=(ctrl&&!selIds.has(id))?new Set([id]):new Set(selArr().map(m=>m.id)); // ctrl on an unselected member copies just it; else the whole selection
|
|
2098
2099
|
const items=[...srcIds].map(mid=>{const m=byId(mid);return{id:mid,o0:m.wp[0].slice(),o1:m.wp[1].slice()};});
|
|
2099
2100
|
buildSnap(srcIds); // snap the dragged line(s) to OTHER members'/segments' endpoints
|
|
@@ -2200,7 +2201,7 @@ svg.addEventListener('pointerup',()=>{if(!drag)return;snapClear();
|
|
|
2200
2201
|
drag=null;render();return;}
|
|
2201
2202
|
if(!drag.armed){ // dragged but drag-move OFF → nothing moved; teach the toggle once
|
|
2202
2203
|
drag=null;render();
|
|
2203
|
-
if(!dragHintShown){dragHintShown=true;try{localStorage.setItem('steel:dragmovehint:v1','1');}catch(_){}toast('Drag
|
|
2204
|
+
if(!dragHintShown){dragHintShown=true;try{localStorage.setItem('steel:dragmovehint:v1','1');}catch(_){}toast('Drag to move/copy is off — turn it on in the Move / Copy menu, or use the Move (M) / Copy (C) tools');}
|
|
2204
2205
|
return;}
|
|
2205
2206
|
if(drag.pre&&snapshot()!==drag.pre)pushUndo(drag.pre); // armed move/copy committed (copy always changed geometry via its clones)
|
|
2206
2207
|
const wasCopy=drag.copy;if(!anyToolActive())snapOnlyClear2d();drag=null;render();if(wasCopy)toast('Copied '+selIds.size+' member'+(selIds.size===1?'':'s'));return;} // a grab move/copy was the operation → single-shot override clears
|
|
@@ -2242,7 +2243,7 @@ document.getElementById('mrgB').onclick=()=>{const r=mergeCollinearJS(P.members)
|
|
|
2242
2243
|
toast('Merged '+r.mergedAway+' segment'+(r.mergedAway===1?'':'s')+' into '+r.runs+' member'+(r.runs===1?'':'s'));};
|
|
2243
2244
|
document.getElementById('undoB').onclick=doUndo;
|
|
2244
2245
|
document.getElementById('redoB').onclick=doRedo;
|
|
2245
|
-
document.getElementById('dragMoveB').onclick=()=>{setDragMove(!dragMoveOn);toast(dragMoveOn?'Drag to move: ON —
|
|
2246
|
+
document.getElementById('dragMoveB').onclick=()=>{setDragMove(!dragMoveOn);toast(dragMoveOn?'Drag to move/copy: ON — drag moves, Ctrl+drag copies':'Drag to move/copy: OFF — a click only selects');};
|
|
2246
2247
|
setDragMove(dragMoveOn); // reflect the persisted state on load (body class + pill)
|
|
2247
2248
|
addEventListener('keydown',e=>{
|
|
2248
2249
|
const inForm=/^(INPUT|SELECT|TEXTAREA)$/.test((document.activeElement||{}).tagName);
|
|
@@ -2357,7 +2358,7 @@ const view3dApi={
|
|
|
2357
2358
|
geoMode:()=>geoMode, // 'el' | 'split' | null — armed Trim/Extend/Split state
|
|
2358
2359
|
onMoveMember:(id,newWp)=>{edit(()=>{const m=byId(id);if(m)m.wp=newWp;});}, // 3D drag → write wp (undoable, autosaves, syncs 2D/chip/BOM)
|
|
2359
2360
|
dragMoveEnabled:()=>dragMoveOn, // the 3D view gates its plain member-drag on this
|
|
2360
|
-
dragMoveHint:()=>{if(dragHintShown)return;dragHintShown=true;try{localStorage.setItem('steel:dragmovehint:v1','1');}catch(_){}toast('Drag
|
|
2361
|
+
dragMoveHint:()=>{if(dragHintShown)return;dragHintShown=true;try{localStorage.setItem('steel:dragmovehint:v1','1');}catch(_){}toast('Drag to move/copy is off — turn it on in the Move / Copy menu, or use the Move (M) / Copy (C) tools');},
|
|
2361
2362
|
onCopyDrag3d:(ids,dxMm,dyMm)=>{const k=304.8/((P&&P.pt_per_ft>0)?P.pt_per_ft:1),r6=n=>Math.round(n*1e6)/1e6; // Ctrl+drag copy: mm delta → plan px (Y FLIPPED), clone + translate + select
|
|
2362
2363
|
const dpx=r6(dxMm/k),dpy=r6(-dyMm/k);
|
|
2363
2364
|
edit(()=>{const ns=new Set();for(const id of (ids||[])){const src=byId(id);if(!src)continue;const c=cloneMember(src);translateMembers([c],[dpx,dpy,0]);P.members.push(c);ns.add(c.id);}selIds=ns;});
|