@floless/app 0.40.0 → 0.41.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
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.41.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.41.0" : void 0 });
|
|
52870
52870
|
}
|
|
52871
52871
|
|
|
52872
52872
|
// oauth-presets.ts
|
|
@@ -151,6 +151,7 @@
|
|
|
151
151
|
text.dimtx{fill:#e2e8f0;font-weight:600;font-family:system-ui;text-anchor:middle;dominant-baseline:central;pointer-events:none}
|
|
152
152
|
line.dim.dimsel{stroke:#f59e0b} line.dimwit.dimsel{stroke:#f59e0b;opacity:1} rect.dimchip.dimsel{stroke:#f59e0b} circle.dimend.dimsel{fill:#f59e0b}
|
|
153
153
|
circle.dimhandle{fill:#22d3ee;stroke:#0b1220;stroke-width:2;cursor:grab}
|
|
154
|
+
body.dimsplit line.member,body.dimsplit line.seg,body.dimsplit line.dim,body.dimsplit line.dimwit,body.dimsplit image{cursor:crosshair}
|
|
154
155
|
.dimprev{opacity:.85} line.dim.dimprev{stroke-dasharray:7 4}
|
|
155
156
|
body.dimon line.member,body.dimon line.seg,body.dimon image{cursor:crosshair}
|
|
156
157
|
body.csaxison #svg,body.csaxison line.member,body.csaxison line.seg,body.csaxison image{cursor:crosshair}
|
|
@@ -424,6 +425,7 @@ let mode='sel', drag=null, picking=false, pickKind='profile', pickEnd=null, geoM
|
|
|
424
425
|
let dimMode=false, dimDraft=null, dimAxis='free', selDimIds=new Set(), dimsVisible=true; // Dimension tool: armed flag, in-progress {a,b,axis}, sticky axis, selected dim ids (multi-select), show/hide
|
|
425
426
|
let csaxisMode=false, csDraft=null; // Local coordinate system "set axes" tool: armed flag + in-progress origin [x,y] (null until click 1). P.frame={o,u} holds the committed local frame (null = global).
|
|
426
427
|
let dimChain=false, dimChainPrev=null, dimSeq=0; // chained "continuous" dimensioning: toggle, the running {point,axis,off,rot}, and a counter for unique ids on rapid clicks
|
|
428
|
+
let dimSplitMode=false; // "add split point" mode on a selected dim — each click inserts a point and splits the dim segment under it into two
|
|
427
429
|
let selIds=new Set();
|
|
428
430
|
let undo=[], redo=[];
|
|
429
431
|
const byId=id=>P.members.find(m=>m.id===id);
|
|
@@ -770,6 +772,7 @@ const VARIES=Symbol('varies');
|
|
|
770
772
|
function agg(list,get){if(!list.length)return undefined;const f=get(list[0]);for(let i=1;i<list.length;i++)if(get(list[i])!==f)return VARIES;return f;}
|
|
771
773
|
function panel(){
|
|
772
774
|
const p=document.getElementById('panel');
|
|
775
|
+
if(!selDimIds.size||!dimsVisible)dimSplitMode=false;document.body.classList.toggle('dimsplit',dimSplitMode); // split mode is meaningless without a (visible) dim selected — also disarms when dims are hidden
|
|
773
776
|
if(csaxisMode){p.innerHTML=`<span class=badge>Set local axes</span>
|
|
774
777
|
<div class=hint id=csHint style="margin-top:8px">${csDraft?'Click the <b>second</b> point — the local-X direction. Y follows at 90°.':'Click the <b>origin</b> point to start.'}</div>
|
|
775
778
|
<div class=hint style="margin-top:8px">Two clicks define the frame; snaps to member ends & grid (<b>Alt</b> off). <b>Esc</b> cancels.</div>`;return;}
|
|
@@ -785,6 +788,14 @@ function panel(){
|
|
|
785
788
|
document.getElementById('dimAxY').onclick=()=>{dimSetAxis('y');dimRefreshPrev();panel();};
|
|
786
789
|
document.getElementById('dimChainB').onclick=()=>toggleDimChain();
|
|
787
790
|
return;}
|
|
791
|
+
if(selDimIds.size&&!selIds.size&&!dimMode&&mode==='sel'){const one=selDimIds.size===1?P.dims.find(x=>x.id===[...selDimIds][0]):null;
|
|
792
|
+
p.innerHTML=`<span class=badge>Dimension${selDimIds.size>1?'s · '+selDimIds.size:''}</span>
|
|
793
|
+
${one?`<div class=hint style="margin-top:8px">Length <b>${esc(dimValueText(dimGeo(one.a,one.b,one.axis,one.off).px))}</b> · ${({free:'Free (aligned)',x:'X (horizontal)',y:'Y (vertical)'})[one.axis]}</div>`:''}
|
|
794
|
+
<div class=sect style="margin-top:12px">Edit</div>
|
|
795
|
+
<div class=seg2><button id=dimSplitB class="${dimSplitMode?'on':''}" title="Add split point (S) — click points along the dimension to insert extra dimension points; each click splits the segment under it. Esc ends.">✂ Add split point</button></div>
|
|
796
|
+
<div class=hint style="margin-top:8px">${dimSplitMode?'Click points along the dimension to split it — keep clicking to add more. <b>Alt</b> = no snap · <b>Esc</b> ends.':'Drag an end handle to re-measure · <b>Del</b> removes · <b>Esc</b> deselects.'}</div>`;
|
|
797
|
+
document.getElementById('dimSplitB').onclick=()=>toggleDimSplit();
|
|
798
|
+
return;}
|
|
788
799
|
if(mode==='add'){
|
|
789
800
|
p.innerHTML=`<span class=badge>Add member</span>
|
|
790
801
|
<div class=sect>Current profile</div>
|
|
@@ -858,7 +869,7 @@ function panel(){
|
|
|
858
869
|
document.getElementById('verifyBtn').onclick=()=>edit(()=>{const v=!allVerified;for(const m of selArr())m.verified=v;});
|
|
859
870
|
{const _gel=document.getElementById('geoEL');if(_gel)_gel.onclick=()=>{geoMode=(geoMode==='el'?null:'el');setGeo();render();};}
|
|
860
871
|
{const _sw=document.getElementById('swapEnds');if(_sw)_sw.onclick=()=>edit(()=>{for(const m of selArr())swapMemberEnds(m);});} // reverse start↔end for the whole selection (2D + 3D via render+sync3D)
|
|
861
|
-
document.getElementById('del').onclick=()=>edit(()=>{P.members=P.members.filter(m=>!selIds.has(m.id));selIds.clear();});
|
|
872
|
+
document.getElementById('del').onclick=()=>edit(()=>{P.members=P.members.filter(m=>!selIds.has(m.id));selIds.clear();if(selDimIds.size){P.dims=P.dims.filter(d=>!selDimIds.has(d.id));selDimIds.clear();}}); // a mixed marquee selection also holds dims — remove them too (matches keyboard Delete)
|
|
862
873
|
{const sp=v=>edit(()=>{for(const m of selArr())if(m.role!=='column')m.position=v;});
|
|
863
874
|
const _pt=document.getElementById('posTop'),_pm=document.getElementById('posMid'),_pb=document.getElementById('posBot');
|
|
864
875
|
if(_pt)_pt.onclick=()=>sp('top');if(_pm)_pm.onclick=()=>sp('middle');if(_pb)_pb.onclick=()=>sp('bottom');}
|
|
@@ -934,7 +945,7 @@ function panel(){
|
|
|
934
945
|
wireDet('dtB',m.ends[1]);
|
|
935
946
|
document.getElementById('matchEnds').onclick=()=>edit(()=>{m.ends[1]={tos:m.ends[0].tos,note:m.ends[0].note,tosDef:m.ends[0].tosDef,detail:m.ends[0].detail};});
|
|
936
947
|
}
|
|
937
|
-
document.getElementById('del').onclick=()=>edit(()=>{P.members=P.members.filter(x=>x.id!==m.id);selIds.clear();});
|
|
948
|
+
document.getElementById('del').onclick=()=>edit(()=>{P.members=P.members.filter(x=>x.id!==m.id);selIds.clear();if(selDimIds.size){P.dims=P.dims.filter(d=>!selDimIds.has(d.id));selDimIds.clear();}}); // also drop any dims a mixed marquee selected
|
|
938
949
|
{const vb=document.getElementById('verifyBtn');if(vb)vb.onclick=()=>edit(()=>{m.verified=!m.verified;});}
|
|
939
950
|
}
|
|
940
951
|
function addFromSeg(sid){const sg=P.segments.find(s=>s.id===sid);if(!sg)return;const prof=addProfile;const id='m'+Date.now();const pv=snapshot();
|
|
@@ -1018,6 +1029,26 @@ function dimHintText(){if(dimChainPrev)return 'Chaining — click the next point
|
|
|
1018
1029
|
const a={free:'Free (aligned)',x:loc?'X (local)':'X (horizontal)',y:loc?'Y (local)':'Y (vertical)'}[dimDraft?dimDraft.axis:dimAxis];
|
|
1019
1030
|
return 'Dimension — '+a+(dimChain?' · Chain ON':'')+(loc?' · local axes':'')+'. Click 1: start · Click 2: end · Click 3: place. Shift=lock axis · X/Y/F force · Alt=no snap · Esc=cancel.';}
|
|
1020
1031
|
function toggleDimChain(){dimChain=!dimChain;if(!dimChain){dimChainPrev=null;dimPrevClear();}render();} // Chain (C): toggle continuous dimensioning; turning it off ends any running chain (render() repaints the panel)
|
|
1032
|
+
function toggleDimSplit(){dimSplitMode=!dimSplitMode;snapClear();render();} // S / "Add split point": arm splitting on the selected dim(s); each click then inserts a point
|
|
1033
|
+
// Split the SELECTED dim nearest the click into two at a point ON its baseline (same axis + offset).
|
|
1034
|
+
// Picks the closest selected dim within a screen tolerance whose interior the click projects into; the
|
|
1035
|
+
// split point is the perpendicular foot, so a free (oblique) dim never kinks and the total is preserved.
|
|
1036
|
+
// Keeps both halves selected so you can keep adding points. No-op (with a hint) if the click isn't on a dim.
|
|
1037
|
+
function dimSplitAt(m){
|
|
1038
|
+
const tol=16/zoom;let best=null;
|
|
1039
|
+
for(const d of P.dims){if(!selDimIds.has(d.id))continue;
|
|
1040
|
+
const pr=projPt(m,d.a,d.b); // foot on the a→b baseline + parameter t (clamped 0..1)
|
|
1041
|
+
if(pr.t<=0.02||pr.t>=0.98)continue; // not interior → can't split here
|
|
1042
|
+
const dist=Math.hypot(m[0]-pr.pt[0],m[1]-pr.pt[1]); // how far the click is from this dim's line
|
|
1043
|
+
if(dist<=tol&&(!best||dist<best.dist))best={d,pt:pr.pt,dist};}
|
|
1044
|
+
if(!best){toast('Click on the dimension (between its ends) to split it');return;}
|
|
1045
|
+
const d=best.d,mid=best.pt; // split point ON the baseline (no kink)
|
|
1046
|
+
const id1='d'+Date.now()+'_'+(dimSeq++),id2='d'+Date.now()+'_'+(dimSeq++);
|
|
1047
|
+
const d1={id:id1,a:d.a.slice(),b:mid.slice(),axis:d.axis,off:d.off,rot:d.rot},d2={id:id2,a:mid.slice(),b:d.b.slice(),axis:d.axis,off:d.off,rot:d.rot}; // halves inherit the parent's baked local-frame rotation (undefined = global)
|
|
1048
|
+
const pv=snapshot();P.dims=P.dims.filter(x=>x.id!==d.id);P.dims.push(d1,d2);
|
|
1049
|
+
selDimIds.delete(d.id);selDimIds.add(id1);selDimIds.add(id2); // both halves stay selected → keep adding points
|
|
1050
|
+
pushUndo(pv);render();sync3D();
|
|
1051
|
+
}
|
|
1021
1052
|
let dimLastPtr=null; // last canvas pointer during placement, for axis-key live refresh
|
|
1022
1053
|
function dimRefreshPrev(){if(!dimMode||!dimDraft||!dimLastPtr)return;
|
|
1023
1054
|
const g=document.getElementById('dimPrevG');if(!g)return;
|
|
@@ -1080,6 +1111,7 @@ function toast(msg){let t=document.getElementById('toast');
|
|
|
1080
1111
|
svg.addEventListener('pointerdown',e=>{if(e.button!==0)return;const t=e.target;
|
|
1081
1112
|
if(csaxisMode){csClick(e);e.preventDefault();return;} // set-local-axes armed → clicks define origin then X-direction
|
|
1082
1113
|
if(dimMode){dimClick(e);e.preventDefault();return;} // tool armed → all clicks place dimension points
|
|
1114
|
+
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
|
|
1083
1115
|
if(t.dataset.dimend!=null&&mode==='sel'){const id=t.dataset.dim,end=+t.dataset.dimend;buildSnap(null);drag={type:'dimend',id,end,pre:snapshot()};svg.setPointerCapture(e.pointerId);e.preventDefault();return;} // drag a selected dim's anchor handle → re-measure (snaps)
|
|
1084
1116
|
if(t.dataset.dim&&mode==='sel'){const did=t.dataset.dim;selIds.clear(); // a dim click is member-exclusive (clears any member selection)
|
|
1085
1117
|
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
|
|
@@ -1121,6 +1153,7 @@ svg.addEventListener('pointerdown',e=>{if(e.button!==0)return;const t=e.target;
|
|
|
1121
1153
|
svg.addEventListener('pointermove',e=>{
|
|
1122
1154
|
if(csaxisMode){csPrev(e);return;}
|
|
1123
1155
|
if(dimMode){dimPrev(e);return;}
|
|
1156
|
+
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
|
|
1124
1157
|
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]);
|
|
1125
1158
|
const d=Math.hypot(q.x-raw.pt[0],q.y-raw.pt[1]);
|
|
1126
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;}
|
|
@@ -1196,6 +1229,7 @@ addEventListener('keydown',e=>{
|
|
|
1196
1229
|
if(e.key==='Escape'&&csaxisMode){if(csDraft){csDraft=null;csPrevClear();}else{csaxisMode=false;setCsMode();}render();return;} // 1st Esc drops the in-progress origin, 2nd exits set-axes
|
|
1197
1230
|
if(e.key==='Enter'&&dimMode&&dimChainPrev){e.preventDefault();dimChainPrev=null;dimPrevClear();render();return;} // Enter ends a running chain
|
|
1198
1231
|
if(e.key==='Escape'&&dimMode){if(dimChainPrev){dimChainPrev=null;dimPrevClear();}else if(dimDraft){dimDraft=null;dimPrevClear();}else{dimMode=false;setDimMode();}render();return;} // Esc: end the chain → drop the in-progress dim → exit the tool
|
|
1232
|
+
if(e.key==='Escape'&&selDimIds.size){if(dimSplitMode){dimSplitMode=false;snapClear();render();}else{selDimIds.clear();render();}return;} // Esc on a selected dim: end split mode → else deselect
|
|
1199
1233
|
if(e.key==='Escape'){if(geoMode){geoMode=null;setGeo();render();return;}if(mode==='add'){mode='sel';setMode();}picking=false;pickKind='profile';pickEnd=null;selIds.clear();render();return;}
|
|
1200
1234
|
if((e.key==='Delete'||e.key==='Backspace')&&(selDimIds.size||selIds.size)&&!inForm){e.preventDefault();edit(()=>{ // deletes everything selected — dims and/or members (a marquee can hold both)
|
|
1201
1235
|
if(selDimIds.size){P.dims=P.dims.filter(d=>!selDimIds.has(d.id));selDimIds.clear();}
|
|
@@ -1204,10 +1238,11 @@ addEventListener('keydown',e=>{
|
|
|
1204
1238
|
if(dimMode&&!inForm&&!e.ctrlKey&&!e.metaKey&&!e.altKey){const dk=e.key.toLowerCase();
|
|
1205
1239
|
if(dk==='x'||dk==='y'||dk==='f'){e.preventDefault();dimSetAxis(dk==='f'?'free':dk);dimDraft&&svg.querySelector('#dimPrevG')&&dimRefreshPrev();return;}
|
|
1206
1240
|
if(dk==='c'){e.preventDefault();toggleDimChain();return;}} // C — toggle chained (continuous) dimensioning
|
|
1241
|
+
if(!inForm&&selDimIds.size>=1&&!selIds.size&&!e.ctrlKey&&!e.metaKey&&!e.altKey&&e.key.toLowerCase()==='s'){e.preventDefault();toggleDimSplit();return;} // S — toggle "add split point" on the selected dimension(s)
|
|
1207
1242
|
if(!inForm&&selIds.size>=1&&!e.ctrlKey&&!e.metaKey&&!e.altKey){const kk=e.key.toLowerCase();
|
|
1208
1243
|
if(kk==='e'){e.preventDefault();geoMode=(geoMode==='el'?null:'el');setGeo();render();return;} // Extend/Trim — any selection
|
|
1209
1244
|
if(kk==='p'){e.preventDefault();edit(()=>{for(const m of selArr())swapMemberEnds(m);});return;} // P — swap start↔end for the whole selection (2D + 3D)
|
|
1210
|
-
if(kk==='s'&&selIds.size===1){e.preventDefault();geoMode=(geoMode==='split'?null:'split');setGeo();render();return;}} // Split — single only
|
|
1245
|
+
if(kk==='s'&&selIds.size===1&&!selDimIds.size){e.preventDefault();geoMode=(geoMode==='split'?null:'split');setGeo();render();return;}} // member Split — single member only, and not while dims are also selected (mixed → ambiguous, so neither split fires)
|
|
1211
1246
|
if(!(e.ctrlKey||e.metaKey))return;const k=e.key.toLowerCase();
|
|
1212
1247
|
if(k==='z'&&!e.shiftKey){e.preventDefault();doUndo();}
|
|
1213
1248
|
else if(k==='y'||(k==='z'&&e.shiftKey)){e.preventDefault();doRedo();}
|
|
@@ -1625,7 +1660,7 @@ function setPlan(i){C.active=i;P=C.plans[i];
|
|
|
1625
1660
|
profs=[...new Set([...P.members.map(m=>m.profile), ...Object.keys(WT)])].sort();
|
|
1626
1661
|
undo=P.undo||(P.undo=[]);redo=P.redo||(P.redo=[]);
|
|
1627
1662
|
selIds=new Set();picking=false;pickKind='profile';pickEnd=null;mode='sel';geoMode=null;
|
|
1628
|
-
dimMode=false;dimChain=false;selDimIds=new Set();setDimMode(); // Dimension tool resets per plan (incl. chain); setDimMode syncs the button/body.dimon classes + clears any draft/preview/chain (dimsVisible persists across plans)
|
|
1663
|
+
dimMode=false;dimChain=false;dimSplitMode=false;selDimIds=new Set();setDimMode(); // Dimension tool resets per plan (incl. chain + split); setDimMode syncs the button/body.dimon classes + clears any draft/preview/chain (dimsVisible persists across plans)
|
|
1629
1664
|
csaxisMode=false;setCsMode(); // set-axes tool resets per plan; P.frame itself is per-plan data (persisted), so it stays
|
|
1630
1665
|
defaultTOS=(P.default_tos!=null?P.default_tos:198);
|
|
1631
1666
|
P.default_tos=defaultTOS; // make the default explicit so the 3D scene + bake use the SAME TOS the editor/dots do (contractToScene falls back to 0, not 198 — keeping them in sync stops the end dots floating off the steel)
|