@floless/app 0.42.0 → 0.43.0
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 +2 -2
- package/dist/web/steel-editor.html +40 -18
- package/package.json +1 -1
package/dist/floless-server.cjs
CHANGED
|
@@ -52856,7 +52856,7 @@ function appVersion() {
|
|
|
52856
52856
|
return resolveVersion({
|
|
52857
52857
|
isSea: isSea2(),
|
|
52858
52858
|
sqVersionXml: readSqVersionXml(),
|
|
52859
|
-
define: true ? "0.
|
|
52859
|
+
define: true ? "0.43.0" : void 0,
|
|
52860
52860
|
pkgVersion: readPkgVersion()
|
|
52861
52861
|
});
|
|
52862
52862
|
}
|
|
@@ -52866,7 +52866,7 @@ function resolveChannel(s) {
|
|
|
52866
52866
|
return "dev";
|
|
52867
52867
|
}
|
|
52868
52868
|
function appChannel() {
|
|
52869
|
-
return resolveChannel({ isSea: isSea2(), define: true ? "0.
|
|
52869
|
+
return resolveChannel({ isSea: isSea2(), define: true ? "0.43.0" : void 0 });
|
|
52870
52870
|
}
|
|
52871
52871
|
|
|
52872
52872
|
// oauth-presets.ts
|
|
@@ -143,7 +143,7 @@
|
|
|
143
143
|
#lbView.drag{cursor:grabbing}
|
|
144
144
|
#lbImg{position:absolute;left:50%;top:50%;max-width:86vw;max-height:80vh;transform-origin:center;user-select:none;-webkit-user-drag:none}
|
|
145
145
|
line.draw{stroke:var(--brand);stroke-width:6;stroke-linecap:round;stroke-dasharray:8 5;opacity:.85;pointer-events:none}
|
|
146
|
-
|
|
146
|
+
.snapmk{fill:none;stroke:#22d3ee;stroke-width:2;vector-effect:non-scaling-stroke;pointer-events:none}
|
|
147
147
|
line.dim{stroke:#22d3ee;stroke-width:2.5;vector-effect:non-scaling-stroke;pointer-events:stroke;cursor:pointer}
|
|
148
148
|
line.dimwit{stroke:#22d3ee;stroke-width:1;opacity:.85;vector-effect:non-scaling-stroke;pointer-events:none}
|
|
149
149
|
circle.dimend{fill:#22d3ee;pointer-events:none}
|
|
@@ -960,24 +960,34 @@ function rectHit(p0,p1,r){
|
|
|
960
960
|
const c=[[r[0],r[1]],[r[2],r[1]],[r[2],r[3]],[r[0],r[3]]];
|
|
961
961
|
for(let i=0;i<4;i++) if(segSeg(p0,p1,c[i],c[(i+1)%4])) return true;
|
|
962
962
|
return false;}
|
|
963
|
-
// --- snap to existing member/segment endpoints (members lie on grids → snaps to grid intersections
|
|
964
|
-
const SNAP_PX=9; let snapPts=[], snapSegs=[];
|
|
963
|
+
// --- snap to existing member/segment/dimension endpoints + MIDPOINTS (△); members lie on grids → snaps to grid intersections; Alt = off ---
|
|
964
|
+
const SNAP_PX=9; let snapPts=[], snapSegs=[], snapMids=[], lastSnapKind='end'; // lastSnapKind drives the marker glyph: 'mid' → triangle, else circle
|
|
965
|
+
const _mid=(a,b)=>[(a[0]+b[0])/2,(a[1]+b[1])/2];
|
|
965
966
|
function buildSnap(except){const ex=except instanceof Set?except:(except!=null?new Set([except]):new Set()); // id or Set of ids to skip
|
|
966
|
-
snapPts=[];snapSegs=[];
|
|
967
|
-
for(const m of P.members){if(ex.has(m.id))continue;snapPts.push(m.wp[0],m.wp[1]);snapSegs.push([m.wp[0],m.wp[1]]);}
|
|
968
|
-
for(const s of P.segments){snapPts.push(s.a,s.b);snapSegs.push([s.a,s.b]);}
|
|
967
|
+
snapPts=[];snapSegs=[];snapMids=[];
|
|
968
|
+
for(const m of P.members){if(ex.has(m.id))continue;snapPts.push(m.wp[0],m.wp[1]);snapSegs.push([m.wp[0],m.wp[1]]);snapMids.push(_mid(m.wp[0],m.wp[1]));}
|
|
969
|
+
for(const s of P.segments){snapPts.push(s.a,s.b);snapSegs.push([s.a,s.b]);snapMids.push(_mid(s.a,s.b));}
|
|
970
|
+
if(dimsVisible&&Array.isArray(P.dims))for(const d of P.dims){if(ex.has(d.id))continue; // snap to a dimension's VISIBLE (offset) line — its ends, midpoint, and onto-line (dimGeo already gives g.mid)
|
|
971
|
+
const g=dimGeo(d.a,d.b,d.axis,d.off,d.rot);snapPts.push(g.p1,g.p2);snapSegs.push([g.p1,g.p2]);snapMids.push(g.mid);}}
|
|
969
972
|
// nearest point lying ON another member/segment LINE (perpendicular foot, clamped to the segment) within tol
|
|
970
973
|
function nearestOnLine(x,y,tol){let bp=null,bd=tol;
|
|
971
974
|
for(const sg of snapSegs){const pr=projPt([x,y],sg[0],sg[1]);const d=Math.hypot(pr.pt[0]-x,pr.pt[1]-y);if(d<bd){bd=d;bp=pr.pt;}}
|
|
972
975
|
return bp?{x:bp[0],y:bp[1],d:bd}:null;}
|
|
973
|
-
function snap(x,y){const tol=SNAP_PX/zoom;let bp=null,bd=tol;
|
|
976
|
+
function snap(x,y){const tol=SNAP_PX/zoom;lastSnapKind='end';let bp=null,bd=tol;
|
|
974
977
|
for(const q of snapPts){const d=Math.hypot(q[0]-x,q[1]-y);if(d<bd){bd=d;bp=q;}}
|
|
975
|
-
if(bp)return {x:bp[0],y:bp[1],hit:true};
|
|
976
|
-
|
|
977
|
-
|
|
978
|
+
if(bp)return {x:bp[0],y:bp[1],hit:true,kind:'end'}; // 1) endpoint / grid intersection (highest priority)
|
|
979
|
+
let mp=null,md=tol;for(const q of snapMids){const d=Math.hypot(q[0]-x,q[1]-y);if(d<md){md=d;mp=q;}}
|
|
980
|
+
if(mp){lastSnapKind='mid';return {x:mp[0],y:mp[1],hit:true,kind:'mid'};} // 2) midpoint of a member/segment/dimension line (△)
|
|
981
|
+
const ln=nearestOnLine(x,y,tol);if(ln)return {x:ln.x,y:ln.y,hit:true,kind:'line'}; // 3) else snap ONTO a line (not only its endpoints)
|
|
982
|
+
let sx=x,sy=y,bx=tol,by=tol; // 4) else axis-align to a nearby point
|
|
978
983
|
for(const q of snapPts){const dx=Math.abs(q[0]-x);if(dx<bx){bx=dx;sx=q[0];}const dy=Math.abs(q[1]-y);if(dy<by){by=dy;sy=q[1];}}
|
|
979
|
-
return {x:sx,y:sy,hit:false};}
|
|
980
|
-
|
|
984
|
+
return {x:sx,y:sy,hit:false,kind:null};}
|
|
985
|
+
// one persistent marker element; kind 'mid' draws an upward triangle, everything else the original circle. Defaults to the last snap()'s kind.
|
|
986
|
+
function snapMark(x,y,kind){kind=kind||lastSnapKind;snapClear();
|
|
987
|
+
const ns='http://www.w3.org/2000/svg';let el;
|
|
988
|
+
if(kind==='mid'){const r=8/zoom;el=document.createElementNS(ns,'path');el.setAttribute('d',`M ${x} ${y-r} L ${x+0.866*r} ${y+0.5*r} L ${x-0.866*r} ${y+0.5*r} Z`);} // apex-up equilateral, centroid ON the snap point
|
|
989
|
+
else{el=document.createElementNS(ns,'circle');el.setAttribute('cx',x);el.setAttribute('cy',y);el.setAttribute('r',6/zoom);}
|
|
990
|
+
el.id='snapMark';el.setAttribute('class','snapmk');svg.appendChild(el);}
|
|
981
991
|
function snapClear(){const c=document.getElementById('snapMark');if(c)c.remove();}
|
|
982
992
|
// --- Dimension tool: armed mode + 3-click placement (anchor, anchor, offset). Shares the editor's
|
|
983
993
|
// buildSnap/snap/snapMark stack. The in-progress preview lives in its own <g> (render() rebuilds
|
|
@@ -1112,7 +1122,7 @@ svg.addEventListener('pointerdown',e=>{if(e.button!==0)return;const t=e.target;
|
|
|
1112
1122
|
if(csaxisMode){csClick(e);e.preventDefault();return;} // set-local-axes armed → clicks define origin then X-direction
|
|
1113
1123
|
if(dimMode){dimClick(e);e.preventDefault();return;} // tool armed → all clicks place dimension points
|
|
1114
1124
|
if(dimSplitMode&&selDimIds.size&&dimsVisible&&mode==='sel'){let q=toSvg(e),x=q.x,y=q.y;if(!e.altKey){buildSnap(null);const sn=snap(x,y);x=sn.x;y=sn.y;}snapClear();dimSplitAt([x,y]);e.preventDefault();return;} // split mode → each click inserts a point into the selected dim under it
|
|
1115
|
-
if(t.dataset.dimend!=null&&mode==='sel'){const id=t.dataset.dim,end=+t.dataset.dimend;buildSnap(
|
|
1125
|
+
if(t.dataset.dimend!=null&&mode==='sel'){const id=t.dataset.dim,end=+t.dataset.dimend;buildSnap(id);drag={type:'dimend',id,end,pre:snapshot()};svg.setPointerCapture(e.pointerId);e.preventDefault();return;} // drag a selected dim's anchor handle → re-measure (snaps; exclude this dim so the end can't snap to its own line/midpoint)
|
|
1116
1126
|
if(t.dataset.dim&&mode==='sel'){const did=t.dataset.dim;selIds.clear(); // a dim click is member-exclusive (clears any member selection)
|
|
1117
1127
|
if(e.ctrlKey||e.metaKey){selDimIds.has(did)?selDimIds.delete(did):selDimIds.add(did);render();return;} // Ctrl+click toggles this dim within the dim selection
|
|
1118
1128
|
selDimIds=new Set([did]);const d=P.dims.find(x=>x.id===did); // plain click → exactly this dim (collapses any multi-selection, so Delete removes only it)
|
|
@@ -1156,7 +1166,7 @@ svg.addEventListener('pointermove',e=>{
|
|
|
1156
1166
|
if(dimSplitMode&&dimsVisible){let q=toSvg(e),x=q.x,y=q.y;if(!e.altKey){buildSnap(null);const sn=snap(x,y);sn.hit?snapMark(sn.x,sn.y):snapClear();}else snapClear();return;} // split mode → show the snap marker for the prospective split point
|
|
1157
1167
|
if(geoMode==='split'&&selIds.size===1&&!drag){const m=byId([...selIds][0]);if(m){const q=toSvg(e),raw=projPt([q.x,q.y],m.wp[0],m.wp[1]);
|
|
1158
1168
|
const d=Math.hypot(q.x-raw.pt[0],q.y-raw.pt[1]);
|
|
1159
|
-
if(d<16/zoom&&raw.t>0.03&&raw.t<0.97){let cut=raw.pt;if(!e.altKey){buildSnap(m.id);const sn=snap(raw.pt[0],raw.pt[1]);if(sn.hit){const p2=projPt([sn.x,sn.y],m.wp[0],m.wp[1]);if(p2.t>0.02&&p2.t<0.98)cut=p2.pt;}}snapMark(cut[0],cut[1]);}else snapClear();}return;}
|
|
1169
|
+
if(d<16/zoom&&raw.t>0.03&&raw.t<0.97){let cut=raw.pt;if(!e.altKey){buildSnap(m.id);const sn=snap(raw.pt[0],raw.pt[1]);if(sn.hit){const p2=projPt([sn.x,sn.y],m.wp[0],m.wp[1]);if(p2.t>0.02&&p2.t<0.98)cut=p2.pt;}}snapMark(cut[0],cut[1],'end');}else snapClear();}return;} // the cut lands ON the member → always the neutral circle, never a stale midpoint △ (e.g. Alt held = no snap() this frame)
|
|
1160
1170
|
if(!drag)return;const p=toSvg(e);
|
|
1161
1171
|
if(drag.type==='dimend'){const d=P.dims.find(x=>x.id===drag.id);if(d){let x=p.x,y=p.y;if(!e.altKey){const sn=snap(x,y);x=sn.x;y=sn.y;sn.hit?snapMark(x,y):snapClear();}else snapClear();if(drag.end===0)d.a=[x,y];else d.b=[x,y];render();}return;} // dragging a dim anchor → snap + re-measure live
|
|
1162
1172
|
if(drag.type==='dim'){const d=P.dims.find(x=>x.id===drag.id);if(d){d.off=dimOffFromPointer(d.a,d.b,d.axis,[p.x,p.y],d.rot);render();}return;}
|
|
@@ -1168,11 +1178,12 @@ svg.addEventListener('pointermove',e=>{
|
|
|
1168
1178
|
drag.rect.setAttribute('x',x);drag.rect.setAttribute('y',y);drag.rect.setAttribute('width',w);drag.rect.setAttribute('height',h);drag.cur=[x,y,x+w,y+h];return;}
|
|
1169
1179
|
if(drag.type==='move'){let dx=p.x-drag.start[0],dy=p.y-drag.start[1];
|
|
1170
1180
|
if(e.shiftKey){[dx,dy]=orthoLock(0,0,dx,dy);snapClear();} // ortho move — lock the delta to the local frame (or screen H/V)
|
|
1171
|
-
else if(!e.altKey){let best=null,bd=SNAP_PX/zoom; // shift the group so a moving endpoint lands on a snap target (endpoint OR line)
|
|
1181
|
+
else if(!e.altKey){let best=null,bd=SNAP_PX/zoom; // shift the group so a moving endpoint lands on a snap target (endpoint, midpoint △, OR line)
|
|
1172
1182
|
for(const it of drag.items)for(const o of [it.o0,it.o1]){const nx=o[0]+dx,ny=o[1]+dy;
|
|
1173
|
-
for(const q of snapPts){const d=Math.hypot(q[0]-nx,q[1]-ny);if(d<bd){bd=d;best={q,cx:q[0]-nx,cy:q[1]-ny};}}
|
|
1174
|
-
const
|
|
1175
|
-
|
|
1183
|
+
for(const q of snapPts){const d=Math.hypot(q[0]-nx,q[1]-ny);if(d<bd){bd=d;best={q,cx:q[0]-nx,cy:q[1]-ny,kind:'end'};}}
|
|
1184
|
+
for(const q of snapMids){const d=Math.hypot(q[0]-nx,q[1]-ny);if(d<bd){bd=d;best={q,cx:q[0]-nx,cy:q[1]-ny,kind:'mid'};}}
|
|
1185
|
+
const ln=nearestOnLine(nx,ny,bd);if(ln){bd=ln.d;best={q:[ln.x,ln.y],cx:ln.x-nx,cy:ln.y-ny,kind:'line'};}} // also snap onto other lines
|
|
1186
|
+
if(best){dx+=best.cx;dy+=best.cy;snapMark(best.q[0],best.q[1],best.kind);}else snapClear();}
|
|
1176
1187
|
else snapClear();
|
|
1177
1188
|
for(const it of drag.items){const m=byId(it.id);if(!m)continue;
|
|
1178
1189
|
m.wp[0]=[it.o0[0]+dx,it.o0[1]+dy];m.wp[1]=[it.o1[0]+dx,it.o1[1]+dy];updateLine(m);}
|
|
@@ -1704,6 +1715,17 @@ if(new URLSearchParams(location.search).get('selftest')==='1'){(function(){
|
|
|
1704
1715
|
{const tz={frame:{o:[0,0],u:[0,0]}};sanitizeFrame(tz);ok(tz.frame===null,'sanitizeFrame drops zero-length u');}
|
|
1705
1716
|
{const tb={frame:{o:[0,0],u:[1]}};sanitizeFrame(tb);ok(tb.frame===null,'sanitizeFrame drops malformed u');}
|
|
1706
1717
|
P.frame=sf; // restore
|
|
1718
|
+
// 6) snap — midpoint tier (△) + dimension lines as snap targets + self-snap exclusion
|
|
1719
|
+
{const sm=P.members,ss=P.segments,sd=P.dims,sdv=dimsVisible,sz=zoom;
|
|
1720
|
+
zoom=1;P.members=[{id:'tm',wp:[[0,0],[100,0]]}];P.segments=[];P.dims=[];dimsVisible=true;
|
|
1721
|
+
buildSnap(null);
|
|
1722
|
+
const e1=snap(1,1);ok(e1.hit&&e1.kind==='end'&&pnear([e1.x,e1.y],[0,0]),'snap → endpoint wins near a corner');
|
|
1723
|
+
const m1=snap(50,3);ok(m1.hit&&m1.kind==='mid'&&pnear([m1.x,m1.y],[50,0]),'snap → member midpoint (△)');
|
|
1724
|
+
P.dims=[{id:'td',a:[0,0],b:[100,0],axis:'free',off:20,rot:0}]; // visible dim line runs (0,20)→(100,20), mid (50,20)
|
|
1725
|
+
buildSnap(null);
|
|
1726
|
+
const d1=snap(50,18);ok(d1.hit&&d1.kind==='mid'&&pnear([d1.x,d1.y],[50,20]),'snap → dimension-line midpoint (△)');
|
|
1727
|
+
buildSnap('td');const d2=snap(50,18);ok(!d2.hit,'snap → excluded dim cannot self-snap');
|
|
1728
|
+
P.members=sm;P.segments=ss;P.dims=sd;dimsVisible=sdv;zoom=sz;}
|
|
1707
1729
|
const msg=fails.length?('SELFTEST FAIL: '+fails.join(' | ')):'SELFTEST PASS (local-frame math)';
|
|
1708
1730
|
console[fails.length?'error':'log'](msg);try{toast(msg);}catch(_){}
|
|
1709
1731
|
})();}
|