@floless/app 0.48.1 → 0.49.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.49.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.49.0" : void 0 });
|
|
52870
52870
|
}
|
|
52871
52871
|
|
|
52872
52872
|
// workflow-update.ts
|
|
@@ -223,6 +223,9 @@ Important optional fields:
|
|
|
223
223
|
"weights": { "W16X26": 26, "HSS6X6X3/8": 27.48 }, // profile → plf; null for unresolved
|
|
224
224
|
"moment_frames": [ … ], // from Frame Elevation sheets
|
|
225
225
|
"detail_bubbles": { "S-504": { "5": [0.32, 0.71] } }, // sheet → { number → [fx,fy] }
|
|
226
|
+
"connections": [ // vendor-neutral connection library
|
|
227
|
+
{ "id": "cx-m1", "type": "moment", "detail": "5-S504", "targets": { "tekla": "146" }, "source": "drawing" }
|
|
228
|
+
],
|
|
226
229
|
"plans": [
|
|
227
230
|
{
|
|
228
231
|
"sheet": "S-202",
|
|
@@ -245,7 +248,7 @@ Important optional fields:
|
|
|
245
248
|
"rfi": false,
|
|
246
249
|
"mf": false,
|
|
247
250
|
"ends": [
|
|
248
|
-
{ "tos": 198, "note": "moment", "tosDef": false, "detail": "5-S504" },
|
|
251
|
+
{ "tos": 198, "note": "moment", "tosDef": false, "detail": "5-S504", "conn": "cx-m1" },
|
|
249
252
|
{ "tos": 198, "note": "shear", "tosDef": true, "detail": "" }
|
|
250
253
|
]
|
|
251
254
|
},
|
|
@@ -282,6 +285,17 @@ Key field rules:
|
|
|
282
285
|
moment-frame member (persistent; survives AISC resolution).
|
|
283
286
|
- `raster_b64` is a base64 JPEG of the framing area (title block excluded). It is
|
|
284
287
|
**machine-local** and **never committed**.
|
|
288
|
+
- **Connection library (`connections[]`) + per-end refs.** A vendor-neutral top-level
|
|
289
|
+
`connections: [{ id, type, detail, targets:{ tekla:"146", … }, source }]` maps a connection
|
|
290
|
+
TYPE → the firm's design detail# → per-platform component ids. A member end references a row by id
|
|
291
|
+
via `ends[].conn` (or `col.conn` for a column); the legacy `note`/`detail` stay as the fallback
|
|
292
|
+
when `conn` is empty. `source` is `drawing` (read from a callout — best evidence), `company-rules`
|
|
293
|
+
(the firm's standard), or `user`. Prefer a `conn` reference over free text when the connection is
|
|
294
|
+
known — the editor's per-end picker and the Connections confidence card both key on it.
|
|
295
|
+
- **Company-rules pre-fill (§5.5).** If a firm-standard knowledge file is present (e.g.
|
|
296
|
+
`company-rules.md`: "moment → 50S504 → Tekla 146"), read it and emit those rows in `connections[]`
|
|
297
|
+
with `source: "company-rules"`, then PUT. The user reviews/edits them in the editor's
|
|
298
|
+
**Connections** library panel and assigns a row per member end. No new agent verb — it's contract data.
|
|
285
299
|
|
|
286
300
|
### 5. Write to the contract store
|
|
287
301
|
|
|
@@ -68,7 +68,7 @@
|
|
|
68
68
|
rect.dethot{cursor:pointer;fill:rgba(168,85,247,.16);stroke:#a855f7;stroke-width:1;stroke-dasharray:3 3;vector-effect:non-scaling-stroke}
|
|
69
69
|
rect.dethot:hover{fill:rgba(168,85,247,.34)}
|
|
70
70
|
text.dettx{fill:#ddd6fe;font:bold 11px system-ui;text-anchor:middle;pointer-events:none}
|
|
71
|
-
#detailsModal,#framesModal,#rfiModal,#confModal,#askAiModal{position:fixed;inset:0;z-index:20;display:none;align-items:center;justify-content:center}
|
|
71
|
+
#detailsModal,#framesModal,#rfiModal,#confModal,#askAiModal,#connModal{position:fixed;inset:0;z-index:20;display:none;align-items:center;justify-content:center}
|
|
72
72
|
#askAiDrop:hover{border-color:var(--brand);color:var(--text)} #askAiDrop.has{border-style:solid;border-color:var(--brand)}
|
|
73
73
|
.aithumb{position:relative;display:inline-flex} .aithumb img{height:60px;border-radius:4px;border:1px solid var(--line);background:#fff;display:block}
|
|
74
74
|
.aithumb button{position:absolute;top:-5px;right:-5px;width:16px;height:16px;padding:0;border-radius:8px;font-size:10px;line-height:16px;text-align:center;background:#7f1d1d;border-color:#991b1b;color:#fecaca}
|
|
@@ -93,7 +93,11 @@
|
|
|
93
93
|
.chip{display:inline-block;padding:1px 6px;border-radius:4px;font-size:10px;border:1px solid var(--line);color:var(--mut);margin:0 3px 3px 0}
|
|
94
94
|
.chip.assumed,.chip.weak{border-color:#713f12;color:#fbbf24} .chip.fail{border-color:#7f1d1d;color:#fca5a5}
|
|
95
95
|
tr.confrow{cursor:pointer} .conf-expand{color:var(--mut);font-size:12px;line-height:1.6;background:#0b1220}
|
|
96
|
-
.ftab td .combo{padding:4px 6px} .ftab .rea{color:var(--mut);font-size:12px}
|
|
96
|
+
.ftab td .combo{padding:4px 6px} .ftab .rea{color:var(--mut);font-size:12px}
|
|
97
|
+
.ftab-inp{background:#0f172a;color:var(--text);border:1px solid #475569;border-radius:4px;padding:4px 6px;width:100%;font:13px system-ui}
|
|
98
|
+
.ftab-inp:focus{outline:none;border-color:var(--brand)}
|
|
99
|
+
.ftab .cdel{padding:3px 7px;font-size:11px} .ftab .csrc{white-space:nowrap}
|
|
100
|
+
.connset{opacity:.55} .rfichip{display:inline-block;min-width:18px;height:18px;line-height:18px;text-align:center;background:#7f1d1d;color:#fecaca;border-radius:9px;font-size:11px;padding:0 5px}
|
|
97
101
|
.mbackdrop{position:absolute;inset:0;background:rgba(2,8,23,.62)}
|
|
98
102
|
.mpanel{position:relative;background:var(--panel);border:1px solid var(--line);border-radius:10px;width:min(880px,92vw);max-height:86vh;display:flex;flex-direction:column;box-shadow:0 20px 60px rgba(0,0,0,.6)}
|
|
99
103
|
.mhead{display:flex;align-items:center;justify-content:space-between;padding:12px 16px;border-bottom:1px solid var(--line)}
|
|
@@ -223,6 +227,7 @@
|
|
|
223
227
|
<hr>
|
|
224
228
|
<div class=mlabel>Reference</div>
|
|
225
229
|
<button id=detailsBtn>Details</button>
|
|
230
|
+
<button id=connBtn title="Connection library — map each connection type to its design detail# and per-platform component id; reference a row from each member end">Connections</button>
|
|
226
231
|
<button id=framesBtn>Frames</button>
|
|
227
232
|
<hr>
|
|
228
233
|
<div class=mlabel>Session</div>
|
|
@@ -272,6 +277,12 @@
|
|
|
272
277
|
<div class=hint id=dnHint>For anything the auto‑detection missed. The image stays on your machine.</div>
|
|
273
278
|
</div>
|
|
274
279
|
<div class=mgrid id=detailsGrid></div></div></div>
|
|
280
|
+
<div id=connModal><div class=mbackdrop id=connBackdrop></div>
|
|
281
|
+
<div class=mpanel><div class=mhead><b>Connection library</b><div style="display:flex;gap:8px;align-items:center"><button id=connNew>+ New row</button><button id=connClose>✕</button></div></div>
|
|
282
|
+
<div class=conf-filter><span class=glab>Platform</span><div class=grp id=connPlatGrp><button class=on data-plat=tekla>Tekla</button><button data-plat=sds2>SDS2</button><button data-plat=revit>Revit</button></div><span class=hint style="margin:0">The design detail# is platform-neutral; the component id is per-platform.</span></div>
|
|
283
|
+
<div style="overflow:auto;flex:1;padding:14px;scrollbar-width:thin">
|
|
284
|
+
<table class=ftab id=connTable><thead><tr><th style="width:24%">Type</th><th style="width:24%">Detail #</th><th id=connCompHead style="width:24%">Tekla id</th><th style="width:18%">Source</th><th style="width:10%"></th></tr></thead><tbody id=connBody></tbody></table>
|
|
285
|
+
</div></div></div>
|
|
275
286
|
<div id=framesModal><div class=mbackdrop id=framesBackdrop></div>
|
|
276
287
|
<div class=mpanel><div class=mhead><b>Moment frame schedule</b><button id=framesClose>✕</button></div>
|
|
277
288
|
<div id=framesGrid style="padding:14px;overflow:auto"></div></div></div>
|
|
@@ -748,7 +759,7 @@ function render(){
|
|
|
748
759
|
s+=`<circle class=numbg ${d} cx="${x}" cy="${y}" r="${R}"/><text class=numtx ${d} x="${x}" y="${y}" style="font-size:${F}px">${it.idx+1}</text>`;});}}
|
|
749
760
|
s+=renderDims();
|
|
750
761
|
if(P.frame)s+=axisGlyphSvg(P.frame.o,P.frame.u,false); // local-axes glyph at the origin (only when a frame is set; removed on reset)
|
|
751
|
-
svg.innerHTML=s; document.getElementById('profiles').innerHTML=profs.map(p=>`<option value="${esc(p)}">`).join(''); document.getElementById('details').innerHTML=(P.details||[]).map(d=>`<option value="${esc(d.text)}">`).join(''); stats(); panel(); updUR(); updDup(); updConf(); updCS();
|
|
762
|
+
svg.innerHTML=s; document.getElementById('profiles').innerHTML=profs.map(p=>`<option value="${esc(p)}">`).join(''); document.getElementById('details').innerHTML=(P.details||[]).map(d=>`<option value="${esc(d.text)}">`).join(''); stats(); panel(); updUR(); updDup(); updConf(); updCS(); updConnBtn();
|
|
752
763
|
if(view3d&&window.Steel3DView)window.Steel3DView.setSelection(selIds); // keep the 3D highlight in sync with the 2D selection
|
|
753
764
|
}
|
|
754
765
|
function updDup(){const n=redundantDups().length;
|
|
@@ -901,12 +912,20 @@ function panel(){
|
|
|
901
912
|
return `<div class=elabrow><span class=elab>${label}</span><label class=defck><input type=checkbox id=${id}_ck ${def?'checked':''}>default</label></div><input id=${id} inputmode=decimal placeholder="5 3/4" · 1'-0 1/4"" value="${esc(fmtFtIn(v))}"${def?' disabled':''}><span class=edec>${esc(fmtDecIn(v))}</span>`;};
|
|
902
913
|
const nin=(id,v)=>`<input id=${id} class="f combo" data-src=conntypes placeholder="connection / note" value="${esc(v||'')}" autocomplete=off>`;
|
|
903
914
|
const dFld=(id,o)=>{const hasPrev=o.detail&&previewFor(o.detail);return `<div class=elab>Connection detail</div><div style="display:flex;gap:6px"><input id=${id} class=combo data-src=details placeholder="e.g. 5-S504" value="${esc(o.detail||'')}" style="flex:1" autocomplete=off>${hasPrev?`<button id=${id}_open class=ghost title="Open detail ${esc(o.detail)}">⤢</button>`:''}<button id=${id}_pk class="ghost${(picking&&pickKind==='detail'&&pickEnd===o)?' on':''}" title="Pick a detail callout from the drawing">⌖</button></div>`;};
|
|
915
|
+
// Per-end connection-library picker (sets o.conn = a connections[] row id). When set, the row's
|
|
916
|
+
// type/detail# are the source of truth (shown read-only below); the free-text note/detail dim to a
|
|
917
|
+
// fallback. Readonly combo → pick only, no free typing; the connrows source shows every row.
|
|
918
|
+
const cFld=(id,o)=>{const row=o.conn?connRowById(o.conn):null;
|
|
919
|
+
const cap=row?`<span class=edec style="text-align:left">${esc(row.type||'?')}${row.detail?' · '+esc(row.detail):''} — ${esc(row.source||'user')}</span>`
|
|
920
|
+
:(o.conn?'<span class=edec style="text-align:left;color:#fca5a5">references a missing row — re-pick</span>':'');
|
|
921
|
+
return `<div class=elab>Connection</div><input id=${id} class=combo data-src=connrows readonly placeholder="— none — (pick from the library)" value="${esc(row?connLabel(row):'')}" autocomplete=off>${cap}`;};
|
|
922
|
+
const fb=(o,html)=>`<div class="${o.conn?'connset':''}">${html}</div>`; // dim the note/detail fallback when a library row is referenced
|
|
904
923
|
const dotS='<span class=epdot style="background:#facc15"></span>',dotE='<span class=epdot style="background:#f472b6"></span>'; // start=yellow, end=magenta — matches the canvas end markers
|
|
905
924
|
const elev = col
|
|
906
|
-
? `<div class="sect f">Elevation</div>${ftFld('bos',dotS+'Bottom (BOS)',m.col.bos)}${tosFld('tosC',dotE+'Top (TOS)',m.col)}${nin('ntC',m.col.note)
|
|
907
|
-
: `<div class="sect f">${dotS}Start</div>${tosFld('tosA','TOS',m.ends[0])}${nin('ntA',m.ends[0].note)
|
|
925
|
+
? `<div class="sect f">Elevation</div>${ftFld('bos',dotS+'Bottom (BOS)',m.col.bos)}${tosFld('tosC',dotE+'Top (TOS)',m.col)}${cFld('cnC',m.col)}${fb(m.col,nin('ntC',m.col.note)+dFld('dtC',m.col))}`
|
|
926
|
+
: `<div class="sect f">${dotS}Start</div>${tosFld('tosA','TOS',m.ends[0])}${cFld('cnA',m.ends[0])}${fb(m.ends[0],nin('ntA',m.ends[0].note)+dFld('dtA',m.ends[0]))}
|
|
908
927
|
<div class=divrow><hr><button class=ghost id=matchEnds>Match → both ends</button><hr></div>
|
|
909
|
-
<div class=sect>${dotE}End</div>${tosFld('tosB','TOS',m.ends[1])}${nin('ntB',m.ends[1].note)
|
|
928
|
+
<div class=sect>${dotE}End</div>${tosFld('tosB','TOS',m.ends[1])}${cFld('cnB',m.ends[1])}${fb(m.ends[1],nin('ntB',m.ends[1].note)+dFld('dtB',m.ends[1]))}`;
|
|
910
929
|
p.innerHTML=`<h3>Member ${esc(m.id)}</h3>
|
|
911
930
|
<div class=row><label>Profile</label><div style="display:flex;gap:6px"><input id=pf class=combo data-src=profiles value="${esc(m.profile)}" style="flex:1" autocomplete=off><button id=pickProf class="ghost${(picking&&pickKind==='profile')?' on':''}" title="Pick profile by clicking a label in the drawing">⌖ pick</button></div>${(picking&&pickKind==='profile')?'<div class="hint" style="margin-top:4px;font-style:italic;color:var(--brand)">Click a profile label in the drawing…</div>':(picking&&pickKind==='detail')?'<div class="hint" style="margin-top:4px;font-style:italic;color:#a855f7">Click a detail callout in the drawing…</div>':''}</div>
|
|
912
931
|
<div class="seg2 f"><button id=rBeam class="${col?'':'on'}">Beam</button><button id=rCol class="${col?'on':''}">Column</button></div>
|
|
@@ -939,18 +958,19 @@ function panel(){
|
|
|
939
958
|
const wireDet=(id,o)=>{const i=document.getElementById(id);if(i)i.onchange=e=>edit(()=>{o.detail=e.target.value.trim();});
|
|
940
959
|
const pk=document.getElementById(id+'_pk');if(pk)pk.onclick=()=>{if(picking&&pickKind==='detail'&&pickEnd===o){picking=false;pickEnd=null;}else{picking=true;pickKind='detail';pickEnd=o;}render();};
|
|
941
960
|
const op=document.getElementById(id+'_open');if(op)op.onclick=()=>openPreview(o.detail);};
|
|
961
|
+
const wireConn=(id,o)=>{const i=document.getElementById(id);if(!i)return;i.onchange=e=>edit(()=>{const v=(e.target.value||'').trim();o.conn=(!v||v==='— none —')?'':v.split(' · ')[0];});};
|
|
942
962
|
if(col){
|
|
943
963
|
document.getElementById('bos').onchange=e=>edit(()=>{m.col.bos=parseLen(e.target.value);});
|
|
944
964
|
wireTos('tosC',m.col);
|
|
945
965
|
document.getElementById('ntC').onchange=e=>edit(()=>{m.col.note=e.target.value;});
|
|
946
|
-
wireDet('dtC',m.col);
|
|
966
|
+
wireDet('dtC',m.col);wireConn('cnC',m.col);
|
|
947
967
|
}else{
|
|
948
968
|
wireTos('tosA',m.ends[0]);
|
|
949
969
|
document.getElementById('ntA').onchange=e=>edit(()=>{m.ends[0].note=e.target.value;});
|
|
950
|
-
wireDet('dtA',m.ends[0]);
|
|
970
|
+
wireDet('dtA',m.ends[0]);wireConn('cnA',m.ends[0]);
|
|
951
971
|
wireTos('tosB',m.ends[1]);
|
|
952
972
|
document.getElementById('ntB').onchange=e=>edit(()=>{m.ends[1].note=e.target.value;});
|
|
953
|
-
wireDet('dtB',m.ends[1]);
|
|
973
|
+
wireDet('dtB',m.ends[1]);wireConn('cnB',m.ends[1]);
|
|
954
974
|
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,conn:m.ends[0].conn,connVerified:m.ends[0].connVerified};});
|
|
955
975
|
}
|
|
956
976
|
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
|
|
@@ -1240,6 +1260,7 @@ addEventListener('keydown',e=>{
|
|
|
1240
1260
|
if(e.key==='Escape'&&lightboxOpen()){closeLightbox();return;}
|
|
1241
1261
|
if(e.key==='Escape'&&askAiIsOpen()){askAiClose();return;}
|
|
1242
1262
|
if(e.key==='Escape'&&detailsOpen()){closeDetails();return;}
|
|
1263
|
+
if(e.key==='Escape'&&connLibOpen()){closeConnLib();return;}
|
|
1243
1264
|
if(e.key==='Escape'&&framesOpen()){closeFrames();return;}
|
|
1244
1265
|
if(e.key==='Escape'&&rfiOpen()){closeRFI();return;}
|
|
1245
1266
|
if(e.key==='Escape'&&confOpen()){closeConf();return;}
|
|
@@ -1428,6 +1449,37 @@ function detailsOpen(){return document.getElementById('detailsModal').style.disp
|
|
|
1428
1449
|
document.getElementById('detailsBtn').onclick=openDetails;
|
|
1429
1450
|
document.getElementById('detailsClose').onclick=closeDetails;
|
|
1430
1451
|
document.getElementById('detailsBackdrop').onclick=closeDetails;
|
|
1452
|
+
// --- connection library: edit connections[] (type → design detail# → per-platform component id) ---
|
|
1453
|
+
// The library is the vendor-neutral source; member ends reference a row via ends[].conn / col.conn.
|
|
1454
|
+
// connPlat scopes the editable "component id" column to one platform; the detail# is platform-neutral.
|
|
1455
|
+
let connPlat='tekla';const PLAT_LABEL={tekla:'Tekla',sds2:'SDS2',revit:'Revit'};
|
|
1456
|
+
function openConnLib(){if(!Array.isArray(C.connections))C.connections=[];renderConnTable();document.getElementById('connModal').style.display='flex';}
|
|
1457
|
+
function closeConnLib(){document.getElementById('connModal').style.display='none';}
|
|
1458
|
+
function connLibOpen(){return document.getElementById('connModal').style.display==='flex';}
|
|
1459
|
+
function updConnBtn(){const b=document.getElementById('connBtn');if(b)b.textContent='Connections'+((C.connections||[]).length?' ('+C.connections.length+')':'');}
|
|
1460
|
+
function renderConnTable(){const tb=document.getElementById('connBody');if(!tb)return;
|
|
1461
|
+
document.getElementById('connCompHead').textContent=(PLAT_LABEL[connPlat]||connPlat)+' id';
|
|
1462
|
+
const rows=C.connections||[];
|
|
1463
|
+
if(!rows.length){tb.innerHTML='<tr><td colspan=5 style="text-align:center;padding:32px;color:var(--mut);font-size:12px">No connections defined — click + New row to add the first.</td></tr>';updConnBtn();return;}
|
|
1464
|
+
const srcChip=s=>s==='drawing'?'<span class="chip assumed">drawing</span>':s==='company-rules'?'<span class=chip>company-rules</span>':'<span class=chip style="border-color:#1d4ed8;color:#93c5fd">user</span>';
|
|
1465
|
+
tb.innerHTML=rows.map(r=>`<tr data-id="${esc(r.id)}">`
|
|
1466
|
+
+`<td><input class="combo ftab-inp" data-src=conntypes data-f=type value="${esc(r.type||'')}" placeholder="moment" autocomplete=off></td>`
|
|
1467
|
+
+`<td><input class="combo ftab-inp" data-src=details data-f=detail value="${esc(r.detail||'')}" placeholder="e.g. 50S504" autocomplete=off></td>`
|
|
1468
|
+
+`<td><input class=ftab-inp data-f=target value="${esc((r.targets&&r.targets[connPlat])||'')}" placeholder="e.g. 146" autocomplete=off></td>`
|
|
1469
|
+
+`<td class=csrc>${srcChip(r.source||'user')}</td>`
|
|
1470
|
+
+`<td><button class="danger cdel" title="Remove this row">✕</button></td></tr>`).join('');
|
|
1471
|
+
tb.querySelectorAll('tr[data-id]').forEach(tr=>{const id=tr.dataset.id;const row=connRowById(id);if(!row)return;
|
|
1472
|
+
tr.querySelector('[data-f=type]').onchange=e=>edit(()=>{row.type=e.target.value.trim();});
|
|
1473
|
+
tr.querySelector('[data-f=detail]').onchange=e=>edit(()=>{row.detail=e.target.value.trim();});
|
|
1474
|
+
tr.querySelector('[data-f=target]').onchange=e=>edit(()=>{if(!row.targets)row.targets={};const v=e.target.value.trim();if(v)row.targets[connPlat]=v;else delete row.targets[connPlat];});
|
|
1475
|
+
tr.querySelector('.cdel').onclick=()=>{edit(()=>{C.connections=C.connections.filter(x=>x.id!==id);
|
|
1476
|
+
for(const pl of (C.plans||[]))for(const m of (pl.members||[])){if(m.col&&m.col.conn===id)m.col.conn='';for(const en of (m.ends||[]))if(en&&en.conn===id)en.conn='';}});renderConnTable();};});
|
|
1477
|
+
updConnBtn();}
|
|
1478
|
+
document.getElementById('connBtn').onclick=openConnLib;
|
|
1479
|
+
document.getElementById('connClose').onclick=closeConnLib;
|
|
1480
|
+
document.getElementById('connBackdrop').onclick=closeConnLib;
|
|
1481
|
+
document.getElementById('connNew').onclick=()=>{edit(()=>{if(!Array.isArray(C.connections))C.connections=[];C.connections.push({id:'conn'+Date.now(),type:'moment',detail:'',targets:{[connPlat]:''},source:'user'});});renderConnTable();const tb=document.getElementById('connBody');if(tb.lastElementChild)tb.lastElementChild.scrollIntoView({block:'nearest'});};
|
|
1482
|
+
document.getElementById('connPlatGrp').querySelectorAll('button').forEach(b=>b.onclick=()=>{connPlat=b.dataset.plat;document.getElementById('connPlatGrp').querySelectorAll('button').forEach(x=>x.classList.toggle('on',x===b));renderConnTable();});
|
|
1431
1483
|
// --- create your own detail (name + screenshot) — for anything the auto-detect missed ---
|
|
1432
1484
|
function readImageCompressed(file,cb){const r=new FileReader();r.onload=()=>{const im=new Image();im.onload=()=>{
|
|
1433
1485
|
const max=1400;let w=im.naturalWidth,h=im.naturalHeight;const sc=Math.min(1,max/Math.max(w,h));w=Math.max(1,Math.round(w*sc));h=Math.max(1,Math.round(h*sc));
|
|
@@ -1678,12 +1730,18 @@ lbView.addEventListener('dblclick',lbReset);
|
|
|
1678
1730
|
const COMBO_SRC={
|
|
1679
1731
|
profiles:()=>profs,
|
|
1680
1732
|
conntypes:()=>['moment','drag','shear','braced','pinned','fixed'],
|
|
1681
|
-
details:()=>{const s=new Set();(P.details||[]).forEach(d=>s.add(d.text));Object.keys(C.custom_details).forEach(n=>s.add(n));return [...s].sort((a,b)=>String(a).localeCompare(String(b),undefined,{numeric:true}));}
|
|
1733
|
+
details:()=>{const s=new Set();(P.details||[]).forEach(d=>s.add(d.text));Object.keys(C.custom_details).forEach(n=>s.add(n));return [...s].sort((a,b)=>String(a).localeCompare(String(b),undefined,{numeric:true}));},
|
|
1734
|
+
connrows:()=>['— none —',...(C.connections||[]).map(r=>connLabel(r))]
|
|
1682
1735
|
};
|
|
1736
|
+
// A connection-library row's one-line label for the per-end picker. The id is the prefix (parsed
|
|
1737
|
+
// back on select); type/detail follow for the human. Kept here so COMBO_SRC + cFld + wireConn agree.
|
|
1738
|
+
function connLabel(r){return r.id+' · '+(r.type||'?')+(r.detail?' · '+r.detail:'');}
|
|
1739
|
+
function connRowById(id){return (C.connections||[]).find(r=>r&&r.id===id)||null;}
|
|
1683
1740
|
const comboPop=document.createElement('div');comboPop.id='comboPop';document.body.appendChild(comboPop);
|
|
1684
1741
|
let comboInput=null,comboIdx=-1;
|
|
1685
1742
|
function showCombo(inp){const src=COMBO_SRC[inp.dataset.src];if(!src)return;comboInput=inp;
|
|
1686
|
-
|
|
1743
|
+
// connrows is a row PICKER (readonly), not a type-to-filter field — always offer every row.
|
|
1744
|
+
const q=inp.dataset.src==='connrows'?'':(inp.value||'').trim().toUpperCase();const opts=src().filter(o=>String(o).toUpperCase().includes(q));
|
|
1687
1745
|
if(!opts.length){comboPop.style.display='none';return;}
|
|
1688
1746
|
comboPop.innerHTML=opts.map(o=>`<div class=opt data-v="${esc(o)}">${esc(o)}</div>`).join('');
|
|
1689
1747
|
const r=inp.getBoundingClientRect();comboPop.style.left=Math.round(r.left)+'px';comboPop.style.top=Math.round(r.bottom+2)+'px';comboPop.style.minWidth=Math.round(r.width)+'px';
|