backpack-viewer 0.2.6 → 0.2.8

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "backpack-viewer",
3
- "version": "0.2.6",
3
+ "version": "0.2.8",
4
4
  "description": "Web-based graph visualizer for backpack-ontology — Canvas 2D, force-directed layout, live reload",
5
5
  "license": "Apache-2.0",
6
6
  "author": "Noah Irzinger",
@@ -14,10 +14,10 @@
14
14
  ],
15
15
  "scripts": {
16
16
  "dev": "vite",
17
- "build": "tsc && vite build",
17
+ "build": "rm -rf dist && tsc && cp src/style.css dist/ && vite build",
18
18
  "preview": "vite preview",
19
19
  "serve": "node bin/serve.js",
20
- "prepublishOnly": "npm run build",
20
+ "prepare": "npm run build",
21
21
  "release:patch": "npm version patch && git push && git push --tags",
22
22
  "release:minor": "npm version minor && git push && git push --tags",
23
23
  "release:major": "npm version major && git push && git push --tags"
@@ -1 +0,0 @@
1
- (function(){const o=document.createElement("link").relList;if(o&&o.supports&&o.supports("modulepreload"))return;for(const p of document.querySelectorAll('link[rel="modulepreload"]'))n(p);new MutationObserver(p=>{for(const e of p)if(e.type==="childList")for(const c of e.addedNodes)c.tagName==="LINK"&&c.rel==="modulepreload"&&n(c)}).observe(document,{childList:!0,subtree:!0});function t(p){const e={};return p.integrity&&(e.integrity=p.integrity),p.referrerPolicy&&(e.referrerPolicy=p.referrerPolicy),p.crossOrigin==="use-credentials"?e.credentials="include":p.crossOrigin==="anonymous"?e.credentials="omit":e.credentials="same-origin",e}function n(p){if(p.ep)return;p.ep=!0;const e=t(p);fetch(p.href,e)}})();async function ne(){const a=await fetch("/api/ontologies");return a.ok?a.json():[]}async function oe(a){const o=await fetch(`/api/ontologies/${encodeURIComponent(a)}`);if(!o.ok)throw new Error(`Failed to load ontology: ${a}`);return o.json()}async function be(a,o){if(!(await fetch(`/api/ontologies/${encodeURIComponent(a)}`,{method:"PUT",headers:{"Content-Type":"application/json"},body:JSON.stringify(o)})).ok)throw new Error(`Failed to save ontology: ${a}`)}async function Ne(a,o){if(!(await fetch(`/api/ontologies/${encodeURIComponent(a)}/rename`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({name:o})})).ok)throw new Error(`Failed to rename ontology: ${a}`)}function we(a,o){const t=typeof o=="function"?{onSelect:o}:o,n=document.createElement("h2");n.textContent="Backpack Ontology Viewer";const p=document.createElement("input");p.type="text",p.placeholder="Filter...",p.id="filter";const e=document.createElement("ul");e.id="ontology-list";const c=document.createElement("div");c.className="sidebar-footer",c.innerHTML='<a href="mailto:support@backpackontology.com">support@backpackontology.com</a><span>Feedback & support</span>',a.appendChild(n),a.appendChild(p),a.appendChild(e),a.appendChild(c);let y=[],i="";return p.addEventListener("input",()=>{const u=p.value.toLowerCase();for(const d of y){const s=d.dataset.name??"";d.style.display=s.includes(u)?"":"none"}}),{setSummaries(u){e.innerHTML="",y=u.map(d=>{const s=document.createElement("li");s.className="ontology-item",s.dataset.name=d.name;const r=document.createElement("span");r.className="name",r.textContent=d.name;const N=document.createElement("span");if(N.className="stats",N.textContent=`${d.nodeCount} nodes, ${d.edgeCount} edges`,s.appendChild(r),s.appendChild(N),t.onRename){const x=document.createElement("button");x.className="sidebar-edit-btn",x.textContent="✎",x.title="Rename";const Y=t.onRename;x.addEventListener("click",w=>{w.stopPropagation();const I=document.createElement("input");I.type="text",I.className="sidebar-rename-input",I.value=d.name,r.textContent="",r.appendChild(I),x.style.display="none",I.focus(),I.select();const C=()=>{const f=I.value.trim();f&&f!==d.name?Y(d.name,f):(r.textContent=d.name,x.style.display="")};I.addEventListener("blur",C),I.addEventListener("keydown",f=>{f.key==="Enter"&&I.blur(),f.key==="Escape"&&(I.value=d.name,I.blur())})}),s.appendChild(x)}return s.addEventListener("click",()=>t.onSelect(d.name)),e.appendChild(s),s}),i&&this.setActive(i)},setActive(u){i=u;for(const d of y)d.classList.toggle("active",d.dataset.name===u)}}}const Le=5e3,Se=.005,he=150,de=.9,le=.01,re=30,se=50;function Me(a,o){for(const t of Object.values(a))if(typeof t=="string")return t;return o}function Ie(a){const o=Math.sqrt(a.nodes.length)*he*.5,t=new Map,n=a.nodes.map((e,c)=>{const y=2*Math.PI*c/a.nodes.length,i={id:e.id,x:Math.cos(y)*o,y:Math.sin(y)*o,vx:0,vy:0,label:Me(e.properties,e.id),type:e.type};return t.set(e.id,i),i}),p=a.edges.map(e=>({sourceId:e.sourceId,targetId:e.targetId,type:e.type}));return{nodes:n,edges:p,nodeMap:t}}function Te(a,o){const{nodes:t,edges:n,nodeMap:p}=a;for(let e=0;e<t.length;e++)for(let c=e+1;c<t.length;c++){const y=t[e],i=t[c];let u=i.x-y.x,d=i.y-y.y,s=Math.sqrt(u*u+d*d);s<re&&(s=re);const r=Le*o/(s*s),N=u/s*r,x=d/s*r;y.vx-=N,y.vy-=x,i.vx+=N,i.vy+=x}for(const e of n){const c=p.get(e.sourceId),y=p.get(e.targetId);if(!c||!y)continue;const i=y.x-c.x,u=y.y-c.y,d=Math.sqrt(i*i+u*u);if(d===0)continue;const s=Se*(d-he)*o,r=i/d*s,N=u/d*s;c.vx+=r,c.vy+=N,y.vx-=r,y.vy-=N}for(const e of t)e.vx-=e.x*le*o,e.vy-=e.y*le*o;for(const e of t){e.vx*=de,e.vy*=de;const c=Math.sqrt(e.vx*e.vx+e.vy*e.vy);c>se&&(e.vx=e.vx/c*se,e.vy=e.vy/c*se),e.x+=e.vx,e.y+=e.vy}return o*.995}const pe=["#d4a27f","#c17856","#b07a5e","#d4956b","#a67c5a","#cc9e7c","#c4866a","#cb8e6c","#b8956e","#a88a70","#d9b08c","#c4a882","#e8b898","#b5927a","#a8886e","#d1a990"],ue=new Map;function G(a){const o=ue.get(a);if(o)return o;let t=0;for(let p=0;p<a.length;p++)t=(t<<5)-t+a.charCodeAt(p)|0;const n=pe[Math.abs(t)%pe.length];return ue.set(a,n),n}function X(a){return getComputedStyle(document.documentElement).getPropertyValue(a).trim()}const K=20,Ae=.001;function ke(a,o){const t=a.querySelector("canvas"),n=t.getContext("2d"),p=window.devicePixelRatio||1;let e={x:0,y:0,scale:1},c=null,y=1,i=0,u=new Set,d=null,s=null,r=null;const N=300;function x(){t.width=t.clientWidth*p,t.height=t.clientHeight*p,C()}const Y=new ResizeObserver(x);Y.observe(a),x();function w(m,l){return[m/e.scale+e.x,l/e.scale+e.y]}function I(m,l){if(!c)return null;const[v,S]=w(m,l);for(let E=c.nodes.length-1;E>=0;E--){const B=c.nodes[E],R=v-B.x,H=S-B.y;if(R*R+H*H<=K*K)return B}return null}function C(){if(!c){n.clearRect(0,0,t.width,t.height);return}const m=X("--canvas-edge"),l=X("--canvas-edge-highlight"),v=X("--canvas-edge-dim"),S=X("--canvas-edge-label"),E=X("--canvas-edge-label-highlight"),B=X("--canvas-edge-label-dim"),R=X("--canvas-arrow"),H=X("--canvas-arrow-highlight"),z=X("--canvas-node-label"),F=X("--canvas-node-label-dim"),_=X("--canvas-type-badge"),ge=X("--canvas-type-badge-dim"),Ce=X("--canvas-selection-border"),ve=X("--canvas-node-border");n.save(),n.setTransform(p,0,0,p,0,0),n.clearRect(0,0,t.clientWidth,t.clientHeight),n.save(),n.translate(-e.x*e.scale,-e.y*e.scale),n.scale(e.scale,e.scale);for(const A of c.edges){const q=c.nodeMap.get(A.sourceId),W=c.nodeMap.get(A.targetId);if(!q||!W)continue;const te=d===null||d.has(A.sourceId),Q=d===null||d.has(A.targetId),J=te&&Q;if(d!==null&&!te&&!Q)continue;const U=u.size>0&&(u.has(A.sourceId)||u.has(A.targetId))||d!==null&&J,ie=d!==null&&!J;if(A.sourceId===A.targetId){L(q,A.type,U,m,l,S,E);continue}n.beginPath(),n.moveTo(q.x,q.y),n.lineTo(W.x,W.y),n.strokeStyle=U?l:ie?v:m,n.lineWidth=U?2.5:1.5,n.stroke(),f(q.x,q.y,W.x,W.y,U,R,H);const Ee=(q.x+W.x)/2,xe=(q.y+W.y)/2;n.fillStyle=U?E:ie?B:S,n.font="9px system-ui, sans-serif",n.textAlign="center",n.textBaseline="bottom",n.fillText(A.type,Ee,xe-4)}for(const A of c.nodes){const q=G(A.type),W=u.has(A.id),te=u.size>0&&c.edges.some(U=>u.has(U.sourceId)&&U.targetId===A.id||u.has(U.targetId)&&U.sourceId===A.id),Q=d!==null&&!d.has(A.id),J=Q||u.size>0&&!W&&!te;W&&(n.save(),n.shadowColor=q,n.shadowBlur=20,n.beginPath(),n.arc(A.x,A.y,K+3,0,Math.PI*2),n.fillStyle=q,n.globalAlpha=.3,n.fill(),n.restore()),n.beginPath(),n.arc(A.x,A.y,K,0,Math.PI*2),n.fillStyle=q,n.globalAlpha=Q?.1:J?.3:1,n.fill(),n.strokeStyle=W?Ce:ve,n.lineWidth=W?3:1.5,n.stroke();const ce=A.label.length>24?A.label.slice(0,22)+"...":A.label;n.fillStyle=J?F:z,n.font="11px system-ui, sans-serif",n.textAlign="center",n.textBaseline="top",n.fillText(ce,A.x,A.y+K+4),n.fillStyle=J?ge:_,n.font="9px system-ui, sans-serif",n.textBaseline="bottom",n.fillText(A.type,A.x,A.y-K-3),n.globalAlpha=1}n.restore(),n.restore()}function f(m,l,v,S,E,B,R){const H=Math.atan2(S-l,v-m),z=v-Math.cos(H)*K,F=S-Math.sin(H)*K,_=8;n.beginPath(),n.moveTo(z,F),n.lineTo(z-_*Math.cos(H-.4),F-_*Math.sin(H-.4)),n.lineTo(z-_*Math.cos(H+.4),F-_*Math.sin(H+.4)),n.closePath(),n.fillStyle=E?R:B,n.fill()}function L(m,l,v,S,E,B,R){const H=m.x+K+15,z=m.y-K-15;n.beginPath(),n.arc(H,z,15,0,Math.PI*2),n.strokeStyle=v?E:S,n.lineWidth=v?2.5:1.5,n.stroke(),n.fillStyle=v?R:B,n.font="9px system-ui, sans-serif",n.textAlign="center",n.fillText(l,H,z-18)}function k(){if(!s||!r)return;const m=performance.now()-r.time,l=Math.min(m/N,1),v=1-Math.pow(1-l,3);e.x=r.x+(s.x-r.x)*v,e.y=r.y+(s.y-r.y)*v,C(),l<1?requestAnimationFrame(k):(s=null,r=null)}function $(){!c||y<Ae||(y=Te(c,y),C(),i=requestAnimationFrame($))}let g=!1,M=!1,h=0,O=0;t.addEventListener("mousedown",m=>{g=!0,M=!1,h=m.clientX,O=m.clientY}),t.addEventListener("mousemove",m=>{if(!g)return;const l=m.clientX-h,v=m.clientY-O;(Math.abs(l)>2||Math.abs(v)>2)&&(M=!0),e.x-=l/e.scale,e.y-=v/e.scale,h=m.clientX,O=m.clientY,C()}),t.addEventListener("mouseup",m=>{if(g=!1,M)return;const l=t.getBoundingClientRect(),v=m.clientX-l.left,S=m.clientY-l.top,E=I(v,S),B=m.ctrlKey||m.metaKey;if(E){B?u.has(E.id)?u.delete(E.id):u.add(E.id):u.size===1&&u.has(E.id)?u.clear():(u.clear(),u.add(E.id));const R=[...u];o==null||o(R.length>0?R:null)}else u.clear(),o==null||o(null);C()}),t.addEventListener("mouseleave",()=>{g=!1}),t.addEventListener("wheel",m=>{m.preventDefault();const l=t.getBoundingClientRect(),v=m.clientX-l.left,S=m.clientY-l.top,[E,B]=w(v,S),R=m.ctrlKey?1-m.deltaY*.01:m.deltaY>0?.9:1.1;e.scale=Math.max(.05,Math.min(10,e.scale*R)),e.x=E-v/e.scale,e.y=B-S/e.scale,C()},{passive:!1});let T=[],D=0,j=1;t.addEventListener("touchstart",m=>{m.preventDefault(),T=Array.from(m.touches),T.length===2?(D=P(T[0],T[1]),j=e.scale):T.length===1&&(h=T[0].clientX,O=T[0].clientY)},{passive:!1}),t.addEventListener("touchmove",m=>{m.preventDefault();const l=Array.from(m.touches);if(l.length===2&&T.length===2){const S=P(l[0],l[1])/D;e.scale=Math.max(.05,Math.min(10,j*S)),C()}else if(l.length===1){const v=l[0].clientX-h,S=l[0].clientY-O;e.x-=v/e.scale,e.y-=S/e.scale,h=l[0].clientX,O=l[0].clientY,C()}T=l},{passive:!1});function P(m,l){const v=m.clientX-l.clientX,S=m.clientY-l.clientY;return Math.sqrt(v*v+S*S)}return{loadGraph(m){if(cancelAnimationFrame(i),c=Ie(m),y=1,u=new Set,d=null,e={x:0,y:0,scale:1},c.nodes.length>0){let l=1/0,v=1/0,S=-1/0,E=-1/0;for(const F of c.nodes)F.x<l&&(l=F.x),F.y<v&&(v=F.y),F.x>S&&(S=F.x),F.y>E&&(E=F.y);const B=(l+S)/2,R=(v+E)/2,H=t.clientWidth,z=t.clientHeight;e.x=B-H/2,e.y=R-z/2}$()},setFilteredNodeIds(m){d=m,C()},panToNode(m){if(!c)return;const l=c.nodeMap.get(m);if(!l)return;u=new Set([m]),o==null||o([m]);const v=t.clientWidth,S=t.clientHeight;r={x:e.x,y:e.y,time:performance.now()},s={x:l.x-v/(2*e.scale),y:l.y-S/(2*e.scale)},k()},destroy(){cancelAnimationFrame(i),Y.disconnect()}}}function Z(a){for(const o of Object.values(a.properties))if(typeof o=="string")return o;return a.id}const Oe="✎";function Pe(a,o){const t=document.createElement("div");t.id="info-panel",t.className="info-panel hidden",a.appendChild(t);function n(){t.classList.add("hidden"),t.innerHTML=""}function p(c,y){const i=y.nodes.find(g=>g.id===c);if(!i)return;const u=y.edges.filter(g=>g.sourceId===c||g.targetId===c);t.innerHTML="",t.classList.remove("hidden");const d=document.createElement("button");d.className="info-close",d.textContent="×",d.addEventListener("click",n),t.appendChild(d);const s=document.createElement("div");s.className="info-header";const r=document.createElement("span");if(r.className="info-type-badge",r.textContent=i.type,r.style.backgroundColor=G(i.type),o){r.classList.add("info-editable");const g=document.createElement("button");g.className="info-inline-edit",g.textContent=Oe,g.addEventListener("click",M=>{M.stopPropagation();const h=document.createElement("input");h.type="text",h.className="info-edit-inline-input",h.value=i.type,r.textContent="",r.appendChild(h),h.focus(),h.select();const O=()=>{const T=h.value.trim();T&&T!==i.type?o.onChangeNodeType(c,T):(r.textContent=i.type,r.appendChild(g))};h.addEventListener("blur",O),h.addEventListener("keydown",T=>{T.key==="Enter"&&h.blur(),T.key==="Escape"&&(h.value=i.type,h.blur())})}),r.appendChild(g)}const N=document.createElement("h3");N.className="info-label",N.textContent=Z(i);const x=document.createElement("span");x.className="info-id",x.textContent=i.id,s.appendChild(r),s.appendChild(N),s.appendChild(x),t.appendChild(s);const Y=Object.keys(i.properties),w=ee("Properties");if(Y.length>0){const g=document.createElement("dl");g.className="info-props";for(const M of Y){const h=document.createElement("dt");h.textContent=M;const O=document.createElement("dd");if(o){const T=ae(i.properties[M]),D=document.createElement("input");D.type="text",D.className="info-edit-input",D.value=T,D.addEventListener("keydown",P=>{P.key==="Enter"&&D.blur()}),D.addEventListener("blur",()=>{const P=D.value;P!==T&&o.onUpdateNode(c,{[M]:Be(P)})}),O.appendChild(D);const j=document.createElement("button");j.className="info-delete-prop",j.textContent="×",j.title=`Remove ${M}`,j.addEventListener("click",()=>{const P={...i.properties};delete P[M],o.onUpdateNode(c,P)}),O.appendChild(j)}else O.appendChild(De(i.properties[M]));g.appendChild(h),g.appendChild(O)}w.appendChild(g)}if(o){const g=document.createElement("button");g.className="info-add-btn",g.textContent="+ Add property",g.addEventListener("click",()=>{const M=document.createElement("div");M.className="info-add-row";const h=document.createElement("input");h.type="text",h.className="info-edit-input",h.placeholder="key";const O=document.createElement("input");O.type="text",O.className="info-edit-input",O.placeholder="value";const T=document.createElement("button");T.className="info-add-save",T.textContent="Add",T.addEventListener("click",()=>{h.value&&o.onAddProperty(c,h.value,O.value)}),M.appendChild(h),M.appendChild(O),M.appendChild(T),w.appendChild(M),h.focus()}),w.appendChild(g)}if(t.appendChild(w),u.length>0){const g=ee(`Connections (${u.length})`),M=document.createElement("ul");M.className="info-connections";for(const h of u){const O=h.sourceId===c,T=O?h.targetId:h.sourceId,D=y.nodes.find(E=>E.id===T),j=D?Z(D):T,P=document.createElement("li");if(P.className="info-connection",D){const E=document.createElement("span");E.className="info-target-dot",E.style.backgroundColor=G(D.type),P.appendChild(E)}const m=document.createElement("span");m.className="info-arrow",m.textContent=O?"→":"←";const l=document.createElement("span");l.className="info-edge-type",l.textContent=h.type;const v=document.createElement("span");v.className="info-target",v.textContent=j,P.appendChild(m),P.appendChild(l),P.appendChild(v);const S=Object.keys(h.properties);if(S.length>0){const E=document.createElement("div");E.className="info-edge-props";for(const B of S){const R=document.createElement("span");R.className="info-edge-prop",R.textContent=`${B}: ${ae(h.properties[B])}`,E.appendChild(R)}P.appendChild(E)}if(o){const E=document.createElement("button");E.className="info-delete-edge",E.textContent="×",E.title="Remove connection",E.addEventListener("click",B=>{B.stopPropagation(),o.onDeleteEdge(h.id)}),P.appendChild(E)}M.appendChild(P)}g.appendChild(M),t.appendChild(g)}const I=ee("Timestamps"),C=document.createElement("dl");C.className="info-props";const f=document.createElement("dt");f.textContent="created";const L=document.createElement("dd");L.textContent=me(i.createdAt);const k=document.createElement("dt");k.textContent="updated";const $=document.createElement("dd");if($.textContent=me(i.updatedAt),C.appendChild(f),C.appendChild(L),C.appendChild(k),C.appendChild($),I.appendChild(C),t.appendChild(I),o){const g=document.createElement("div");g.className="info-section info-danger";const M=document.createElement("button");M.className="info-delete-node",M.textContent="Delete node",M.addEventListener("click",()=>{o.onDeleteNode(c),n()}),g.appendChild(M),t.appendChild(g)}}function e(c,y){const i=new Set(c),u=y.nodes.filter(f=>i.has(f.id));if(u.length===0)return;const d=y.edges.filter(f=>i.has(f.sourceId)&&i.has(f.targetId));t.innerHTML="",t.classList.remove("hidden");const s=document.createElement("button");s.className="info-close",s.textContent="×",s.addEventListener("click",n),t.appendChild(s);const r=document.createElement("div");r.className="info-header";const N=document.createElement("h3");N.className="info-label",N.textContent=`${u.length} nodes selected`,r.appendChild(N);const x=document.createElement("div");x.style.cssText="display:flex;flex-wrap:wrap;gap:4px;margin-top:6px";const Y=new Map;for(const f of u)Y.set(f.type,(Y.get(f.type)??0)+1);for(const[f,L]of Y){const k=document.createElement("span");k.className="info-type-badge",k.style.backgroundColor=G(f),k.textContent=L>1?`${f} (${L})`:f,x.appendChild(k)}r.appendChild(x),t.appendChild(r);const w=ee("Selected Nodes"),I=document.createElement("ul");I.className="info-connections";for(const f of u){const L=document.createElement("li");L.className="info-connection";const k=document.createElement("span");k.className="info-target-dot",k.style.backgroundColor=G(f.type);const $=document.createElement("span");$.className="info-target",$.textContent=Z(f);const g=document.createElement("span");g.className="info-edge-type",g.textContent=f.type,L.appendChild(k),L.appendChild($),L.appendChild(g),I.appendChild(L)}w.appendChild(I),t.appendChild(w);const C=ee(d.length>0?`Connections Between Selected (${d.length})`:"Connections Between Selected");if(d.length===0){const f=document.createElement("p");f.style.cssText="font-size:12px;color:var(--text-dim)",f.textContent="No direct connections between selected nodes",C.appendChild(f)}else{const f=document.createElement("ul");f.className="info-connections";for(const L of d){const k=y.nodes.find(l=>l.id===L.sourceId),$=y.nodes.find(l=>l.id===L.targetId),g=k?Z(k):L.sourceId,M=$?Z($):L.targetId,h=document.createElement("li");if(h.className="info-connection",k){const l=document.createElement("span");l.className="info-target-dot",l.style.backgroundColor=G(k.type),h.appendChild(l)}const O=document.createElement("span");O.className="info-target",O.textContent=g;const T=document.createElement("span");T.className="info-arrow",T.textContent="→";const D=document.createElement("span");D.className="info-edge-type",D.textContent=L.type;const j=document.createElement("span");if(j.className="info-arrow",j.textContent="→",h.appendChild(O),h.appendChild(T),h.appendChild(D),h.appendChild(j),$){const l=document.createElement("span");l.className="info-target-dot",l.style.backgroundColor=G($.type),h.appendChild(l)}const P=document.createElement("span");P.className="info-target",P.textContent=M,h.appendChild(P);const m=Object.keys(L.properties);if(m.length>0){const l=document.createElement("div");l.className="info-edge-props";for(const v of m){const S=document.createElement("span");S.className="info-edge-prop",S.textContent=`${v}: ${ae(L.properties[v])}`,l.appendChild(S)}h.appendChild(l)}f.appendChild(h)}C.appendChild(f)}t.appendChild(C)}return{show(c,y){c.length===1?p(c[0],y):c.length>1&&e(c,y)},hide:n,get visible(){return!t.classList.contains("hidden")}}}function ee(a){const o=document.createElement("div");o.className="info-section";const t=document.createElement("h4");return t.className="info-section-title",t.textContent=a,o.appendChild(t),o}function De(a){if(Array.isArray(a)){const t=document.createElement("div");t.className="info-array";for(const n of a){const p=document.createElement("span");p.className="info-tag",p.textContent=String(n),t.appendChild(p)}return t}if(a!==null&&typeof a=="object"){const t=document.createElement("pre");return t.className="info-json",t.textContent=JSON.stringify(a,null,2),t}const o=document.createElement("span");return o.className="info-value",o.textContent=String(a??""),o}function ae(a){return Array.isArray(a)?a.map(String).join(", "):a!==null&&typeof a=="object"?JSON.stringify(a):String(a??"")}function Be(a){const o=a.trim();if(o==="true")return!0;if(o==="false")return!1;if(o!==""&&!isNaN(Number(o)))return Number(o);if(o.startsWith("[")&&o.endsWith("]")||o.startsWith("{")&&o.endsWith("}"))try{return JSON.parse(o)}catch{return a}return a}function me(a){try{return new Date(a).toLocaleString()}catch{return a}}function ye(a){for(const o of Object.values(a.properties))if(typeof o=="string")return o;return a.id}function fe(a,o){const t=o.toLowerCase();if(ye(a).toLowerCase().includes(t)||a.type.toLowerCase().includes(t))return!0;for(const n of Object.values(a.properties))if(typeof n=="string"&&n.toLowerCase().includes(t))return!0;return!1}function Re(a){let o=null,t=null,n=null,p=new Set,e=null;const c=document.createElement("div");c.className="search-overlay hidden";const y=document.createElement("div");y.className="search-input-wrap";const i=document.createElement("input");i.className="search-input",i.type="text",i.placeholder="Search nodes...",i.setAttribute("autocomplete","off"),i.setAttribute("spellcheck","false");const u=document.createElement("kbd");u.className="search-kbd",u.textContent="/",y.appendChild(i),y.appendChild(u);const d=document.createElement("ul");d.className="search-results hidden";const s=document.createElement("div");s.className="type-chips",c.appendChild(y),c.appendChild(d),c.appendChild(s),a.appendChild(c);function r(){if(s.innerHTML="",!o)return;const w=new Map;for(const C of o.nodes)w.set(C.type,(w.get(C.type)??0)+1);const I=[...w.keys()].sort();p=new Set;for(const C of I){const f=document.createElement("button");f.className="type-chip",f.dataset.type=C;const L=document.createElement("span");L.className="type-chip-dot",L.style.backgroundColor=G(C);const k=document.createElement("span");k.textContent=`${C} (${w.get(C)})`,f.appendChild(L),f.appendChild(k),f.addEventListener("click",()=>{p.has(C)?(p.delete(C),f.classList.remove("active")):(p.add(C),f.classList.add("active")),x()}),s.appendChild(f)}}function N(){if(!o)return null;const w=i.value.trim(),I=p.size===0,C=w.length===0;if(C&&I)return null;const f=new Set;for(const L of o.nodes)!I&&!p.has(L.type)||(C||fe(L,w))&&f.add(L.id);return f}function x(){const w=N();t==null||t(w),Y()}function Y(){d.innerHTML="";const w=i.value.trim();if(!o||w.length===0){d.classList.add("hidden");return}const I=p.size===0,C=[];for(const f of o.nodes)if(!(!I&&!p.has(f.type))&&fe(f,w)&&(C.push(f),C.length>=8))break;if(C.length===0){d.classList.add("hidden");return}for(const f of C){const L=document.createElement("li");L.className="search-result-item";const k=document.createElement("span");k.className="search-result-dot",k.style.backgroundColor=G(f.type);const $=document.createElement("span");$.className="search-result-label";const g=ye(f);$.textContent=g.length>36?g.slice(0,34)+"...":g;const M=document.createElement("span");M.className="search-result-type",M.textContent=f.type,L.appendChild(k),L.appendChild($),L.appendChild(M),L.addEventListener("click",()=>{n==null||n(f.id),i.value="",d.classList.add("hidden"),x()}),d.appendChild(L)}d.classList.remove("hidden")}return i.addEventListener("input",()=>{e&&clearTimeout(e),e=setTimeout(x,150)}),i.addEventListener("keydown",w=>{if(w.key==="Escape")i.value="",i.blur(),d.classList.add("hidden"),x();else if(w.key==="Enter"){const I=d.querySelector(".search-result-item");I==null||I.click()}}),document.addEventListener("click",w=>{c.contains(w.target)||d.classList.add("hidden")}),i.addEventListener("focus",()=>u.classList.add("hidden")),i.addEventListener("blur",()=>{i.value.length===0&&u.classList.remove("hidden")}),{setOntologyData(w){o=w,i.value="",d.classList.add("hidden"),o&&o.nodes.length>0?(c.classList.remove("hidden"),r()):c.classList.add("hidden")},onFilterChange(w){t=w},onNodeSelect(w){n=w},clear(){i.value="",d.classList.add("hidden"),p.clear(),t==null||t(null)},focus(){i.focus()}}}let V="",b=null;async function $e(){const a=document.getElementById("canvas-container"),o=window.matchMedia("(prefers-color-scheme: dark)"),n=localStorage.getItem("backpack-theme")??(o.matches?"dark":"light");document.documentElement.setAttribute("data-theme",n);const p=document.createElement("button");p.className="theme-toggle",p.textContent=n==="light"?"☾":"☼",p.title="Toggle light/dark mode",p.addEventListener("click",()=>{const r=document.documentElement.getAttribute("data-theme")==="light"?"dark":"light";document.documentElement.setAttribute("data-theme",r),localStorage.setItem("backpack-theme",r),p.textContent=r==="light"?"☾":"☼"}),a.appendChild(p);async function e(){if(!V||!b)return;b.metadata.updatedAt=new Date().toISOString(),await be(V,b),y.loadGraph(b),i.setOntologyData(b);const s=await ne();u.setSummaries(s)}const c=Pe(a,{onUpdateNode(s,r){if(!b)return;const N=b.nodes.find(x=>x.id===s);N&&(N.properties={...N.properties,...r},N.updatedAt=new Date().toISOString(),e().then(()=>c.show([s],b)))},onChangeNodeType(s,r){if(!b)return;const N=b.nodes.find(x=>x.id===s);N&&(N.type=r,N.updatedAt=new Date().toISOString(),e().then(()=>c.show([s],b)))},onDeleteNode(s){b&&(b.nodes=b.nodes.filter(r=>r.id!==s),b.edges=b.edges.filter(r=>r.sourceId!==s&&r.targetId!==s),e())},onDeleteEdge(s){var N;if(!b)return;const r=(N=b.edges.find(x=>x.id===s))==null?void 0:N.sourceId;b.edges=b.edges.filter(x=>x.id!==s),e().then(()=>{r&&b&&c.show([r],b)})},onAddProperty(s,r,N){if(!b)return;const x=b.nodes.find(Y=>Y.id===s);x&&(x.properties[r]=N,x.updatedAt=new Date().toISOString(),e().then(()=>c.show([s],b)))}}),y=ke(a,s=>{s&&s.length>0&&b?c.show(s,b):c.hide()}),i=Re(a);i.onFilterChange(s=>{y.setFilteredNodeIds(s)}),i.onNodeSelect(s=>{y.panToNode(s),b&&c.show([s],b)});const u=we(document.getElementById("sidebar"),{onSelect:async s=>{V=s,u.setActive(s),c.hide(),i.clear(),b=await oe(s),y.loadGraph(b),i.setOntologyData(b)},onRename:async(s,r)=>{await Ne(s,r),V===s&&(V=r);const N=await ne();u.setSummaries(N),u.setActive(V),V===r&&(b=await oe(r),y.loadGraph(b),i.setOntologyData(b))}}),d=await ne();u.setSummaries(d),d.length>0&&(V=d[0].name,u.setActive(V),b=await oe(V),y.loadGraph(b),i.setOntologyData(b)),document.addEventListener("keydown",s=>{s.target instanceof HTMLInputElement||s.target instanceof HTMLTextAreaElement||(s.key==="/"||s.key==="k"&&(s.metaKey||s.ctrlKey))&&(s.preventDefault(),i.focus())})}$e();
@@ -1 +0,0 @@
1
- *{margin:0;padding:0;box-sizing:border-box}:root{--bg: #141414;--bg-surface: #1a1a1a;--bg-hover: #222222;--bg-active: #2a2a2a;--bg-elevated: #1e1e1e;--bg-inset: #111111;--border: #2a2a2a;--text: #d4d4d4;--text-strong: #e5e5e5;--text-muted: #737373;--text-dim: #525252;--accent: #d4a27f;--accent-hover: #e8b898;--badge-text: #141414;--glass-bg: rgba(20, 20, 20, .85);--glass-border: rgba(255, 255, 255, .08);--chip-bg: rgba(42, 42, 42, .7);--chip-bg-active: rgba(42, 42, 42, .9);--chip-bg-hover: rgba(50, 50, 50, .9);--chip-border-active: rgba(255, 255, 255, .06);--shadow: rgba(0, 0, 0, .6);--shadow-strong: rgba(0, 0, 0, .5);--canvas-edge: rgba(255, 255, 255, .08);--canvas-edge-highlight: rgba(212, 162, 127, .5);--canvas-edge-dim: rgba(255, 255, 255, .03);--canvas-edge-label: rgba(255, 255, 255, .2);--canvas-edge-label-highlight: rgba(212, 162, 127, .7);--canvas-edge-label-dim: rgba(255, 255, 255, .05);--canvas-arrow: rgba(255, 255, 255, .12);--canvas-arrow-highlight: rgba(212, 162, 127, .5);--canvas-node-label: #a3a3a3;--canvas-node-label-dim: rgba(212, 212, 212, .2);--canvas-type-badge: rgba(115, 115, 115, .5);--canvas-type-badge-dim: rgba(115, 115, 115, .15);--canvas-selection-border: #d4d4d4;--canvas-node-border: rgba(255, 255, 255, .15)}[data-theme=light]{--bg: #f5f5f4;--bg-surface: #fafaf9;--bg-hover: #f0efee;--bg-active: #e7e5e4;--bg-elevated: #f0efee;--bg-inset: #e7e5e4;--border: #d6d3d1;--text: #292524;--text-strong: #1c1917;--text-muted: #78716c;--text-dim: #a8a29e;--accent: #c17856;--accent-hover: #b07a5e;--badge-text: #fafaf9;--glass-bg: rgba(250, 250, 249, .85);--glass-border: rgba(0, 0, 0, .08);--chip-bg: rgba(214, 211, 209, .5);--chip-bg-active: rgba(214, 211, 209, .8);--chip-bg-hover: rgba(200, 197, 195, .8);--chip-border-active: rgba(0, 0, 0, .08);--shadow: rgba(0, 0, 0, .1);--shadow-strong: rgba(0, 0, 0, .15);--canvas-edge: rgba(0, 0, 0, .1);--canvas-edge-highlight: rgba(193, 120, 86, .6);--canvas-edge-dim: rgba(0, 0, 0, .03);--canvas-edge-label: rgba(0, 0, 0, .25);--canvas-edge-label-highlight: rgba(193, 120, 86, .8);--canvas-edge-label-dim: rgba(0, 0, 0, .06);--canvas-arrow: rgba(0, 0, 0, .15);--canvas-arrow-highlight: rgba(193, 120, 86, .6);--canvas-node-label: #57534e;--canvas-node-label-dim: rgba(87, 83, 78, .2);--canvas-type-badge: rgba(87, 83, 78, .5);--canvas-type-badge-dim: rgba(87, 83, 78, .15);--canvas-selection-border: #292524;--canvas-node-border: rgba(0, 0, 0, .1)}body{font-family:system-ui,-apple-system,sans-serif;background:var(--bg);color:var(--text);overflow:hidden}#app{display:flex;height:100vh;width:100vw}#sidebar{width:280px;min-width:280px;background:var(--bg-surface);border-right:1px solid var(--border);display:flex;flex-direction:column;padding:16px;overflow-y:auto}#sidebar h2{font-size:13px;font-weight:600;text-transform:uppercase;letter-spacing:.05em;color:var(--text-muted);margin-bottom:14px}#sidebar input{width:100%;padding:8px 12px;border:1px solid var(--border);border-radius:6px;background:var(--bg);color:var(--text);font-size:13px;outline:none;margin-bottom:12px}#sidebar input:focus{border-color:var(--accent)}#sidebar input::placeholder{color:var(--text-dim)}#ontology-list{list-style:none;display:flex;flex-direction:column;gap:2px}.ontology-item{padding:10px 12px;border-radius:6px;cursor:pointer;transition:background .15s}.ontology-item:hover{background:var(--bg-hover)}.ontology-item.active{background:var(--bg-active)}.ontology-item .name{display:block;font-size:13px;font-weight:500;color:var(--text)}.ontology-item .stats{display:block;font-size:11px;color:var(--text-dim);margin-top:2px}.sidebar-edit-btn{position:absolute;right:8px;top:10px;background:none;border:none;color:var(--text-dim);font-size:11px;cursor:pointer;opacity:0;transition:opacity .1s}.ontology-item{position:relative}.ontology-item:hover .sidebar-edit-btn{opacity:.7}.sidebar-edit-btn:hover{opacity:1!important;color:var(--text)}.sidebar-rename-input{background:transparent;border:none;border-bottom:1px solid var(--accent);color:var(--text);font:inherit;font-size:13px;font-weight:500;outline:none;width:100%;padding:0}.sidebar-footer{margin-top:auto;padding-top:16px;border-top:1px solid var(--border);text-align:center}.sidebar-footer a{display:block;font-size:12px;font-weight:500;color:var(--accent);text-decoration:none;margin-bottom:4px}.sidebar-footer a:hover{color:var(--accent-hover)}.sidebar-footer span{display:block;font-size:10px;color:var(--text-dim)}.theme-toggle{position:absolute;top:16px;right:16px;z-index:30;background:var(--bg-surface);border:1px solid var(--border);border-radius:8px;color:var(--text-muted);font-size:18px;cursor:pointer;padding:6px 10px;line-height:1;transition:color .15s,border-color .15s,background .15s;box-shadow:0 2px 8px var(--shadow)}.theme-toggle:hover{color:var(--text);border-color:var(--text-muted);background:var(--bg-hover)}#canvas-container{flex:1;position:relative;overflow:hidden}#graph-canvas{position:absolute;top:0;left:0;width:100%;height:100%;cursor:grab}#graph-canvas:active{cursor:grabbing}.search-overlay{position:absolute;top:16px;left:50%;transform:translate(-50%);z-index:20;display:flex;flex-direction:column;align-items:center;gap:8px;max-height:calc(100vh - 48px);pointer-events:none}.search-overlay>*{pointer-events:auto}.search-overlay.hidden{display:none}.search-input-wrap{position:relative;width:380px;max-width:calc(100vw - 340px)}.search-input{width:100%;padding:10px 36px 10px 16px;border:1px solid var(--glass-border);border-radius:10px;background:var(--glass-bg);backdrop-filter:blur(12px);-webkit-backdrop-filter:blur(12px);color:var(--text);font-size:14px;outline:none;transition:border-color .15s,box-shadow .15s}.search-input:focus{border-color:#d4a27f66;box-shadow:0 0 0 3px #d4a27f1a}.search-input::placeholder{color:var(--text-dim)}.search-kbd{position:absolute;right:10px;top:50%;transform:translateY(-50%);padding:2px 7px;border:1px solid var(--border);border-radius:4px;background:var(--bg-surface);color:var(--text-dim);font-size:11px;font-family:monospace;pointer-events:none}.search-kbd.hidden{display:none}.search-results{list-style:none;width:380px;max-width:calc(100vw - 340px);background:var(--glass-bg);backdrop-filter:blur(12px);-webkit-backdrop-filter:blur(12px);border:1px solid var(--border);border-radius:10px;overflow:hidden;box-shadow:0 8px 32px var(--shadow-strong)}.search-results.hidden{display:none}.search-result-item{display:flex;align-items:center;gap:8px;padding:8px 14px;cursor:pointer;transition:background .1s}.search-result-item:hover{background:var(--bg-hover)}.search-result-dot{width:8px;height:8px;border-radius:50%;flex-shrink:0}.search-result-label{font-size:13px;color:var(--text);flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.search-result-type{font-size:11px;color:var(--text-dim);flex-shrink:0}.type-chips{display:flex;flex-wrap:wrap;gap:4px;justify-content:center;max-width:500px;max-height:200px;overflow-y:auto;padding:4px;border-radius:10px}.type-chip{display:flex;align-items:center;gap:4px;padding:3px 10px;border:1px solid transparent;border-radius:12px;background:var(--chip-bg);color:var(--text-dim);font-size:11px;cursor:pointer;transition:all .15s;white-space:nowrap}.type-chip.active{background:var(--chip-bg-active);color:var(--text-muted);border-color:var(--chip-border-active)}.type-chip:hover{background:var(--chip-bg-hover)}.type-chip-dot{width:6px;height:6px;border-radius:50%;flex-shrink:0}.type-chip:not(.active) .type-chip-dot{opacity:.3}.info-panel{position:absolute;top:56px;right:16px;width:360px;max-height:calc(100vh - 72px);background:var(--bg-surface);border:1px solid var(--border);border-radius:10px;overflow-y:auto;padding:20px;z-index:10;box-shadow:0 8px 32px var(--shadow)}.info-panel.hidden{display:none}.info-close{position:absolute;top:12px;right:14px;background:none;border:none;color:var(--text-muted);font-size:20px;cursor:pointer;line-height:1;padding:4px}.info-close:hover{color:var(--text)}.info-header{margin-bottom:16px}.info-type-badge{display:inline-block;padding:3px 10px;border-radius:12px;font-size:11px;font-weight:600;color:var(--badge-text);margin-bottom:8px}.info-label{font-size:18px;font-weight:600;color:var(--text-strong);margin-bottom:4px;word-break:break-word}.info-id{display:block;font-size:11px;color:var(--text-dim);font-family:monospace}.info-section{margin-bottom:16px}.info-section-title{font-size:11px;font-weight:600;text-transform:uppercase;letter-spacing:.05em;color:var(--text-muted);margin-bottom:8px;padding-bottom:4px;border-bottom:1px solid var(--border)}.info-props{display:grid;grid-template-columns:auto 1fr;gap:4px 12px}.info-props dt{font-size:12px;color:var(--text-muted);padding-top:2px}.info-props dd{font-size:12px;color:var(--text);word-break:break-word;display:flex;align-items:center;gap:4px}.info-value{white-space:pre-wrap}.info-array{display:flex;flex-wrap:wrap;gap:4px}.info-tag{display:inline-block;padding:2px 8px;background:var(--bg-hover);border-radius:4px;font-size:11px;color:var(--text-muted)}.info-json{font-size:11px;font-family:monospace;color:var(--text-muted);background:var(--bg-inset);padding:6px 8px;border-radius:4px;overflow-x:auto;white-space:pre}.info-connections{list-style:none;display:flex;flex-direction:column;gap:6px}.info-connection{display:flex;align-items:center;gap:6px;padding:6px 8px;background:var(--bg-elevated);border-radius:6px;font-size:12px;flex-wrap:wrap}.info-target-dot{width:8px;height:8px;border-radius:50%;flex-shrink:0}.info-arrow{color:var(--text-dim);font-size:14px;flex-shrink:0}.info-edge-type{color:var(--text-muted);font-size:11px;font-weight:500}.info-target{color:var(--text);font-weight:500}.info-edge-props{width:100%;padding-top:4px;padding-left:20px}.info-edge-prop{display:block;font-size:11px;color:var(--text-dim)}.info-editable{cursor:default;position:relative}.info-inline-edit{background:none;border:none;color:var(--badge-text);opacity:0;font-size:10px;cursor:pointer;margin-left:4px;transition:opacity .15s}.info-editable:hover .info-inline-edit{opacity:.8}.info-inline-edit:hover{opacity:1!important}.info-edit-inline-input{background:transparent;border:none;border-bottom:1px solid var(--accent);color:var(--badge-text);font:inherit;font-size:inherit;outline:none;width:100%;padding:0}.info-edit-input{background:var(--bg-inset);border:1px solid var(--border);border-radius:4px;padding:3px 6px;font-size:12px;color:var(--text);flex:1;min-width:0}.info-edit-input:focus{outline:none;border-color:var(--accent)}.info-delete-prop{background:none;border:none;color:var(--text-dim);font-size:14px;cursor:pointer;padding:0 2px;flex-shrink:0;opacity:0;transition:opacity .1s,color .1s}.info-props dd:hover .info-delete-prop{opacity:1}.info-delete-prop:hover{color:#ef4444}.info-add-btn{background:none;border:1px dashed var(--border);border-radius:4px;padding:6px 10px;font-size:12px;color:var(--text-dim);cursor:pointer;width:100%;margin-top:8px;transition:border-color .15s,color .15s}.info-add-btn:hover{border-color:var(--accent);color:var(--text)}.info-add-row{display:flex;gap:4px;margin-top:6px}.info-add-save{background:var(--accent);border:none;border-radius:4px;padding:3px 10px;font-size:12px;color:var(--badge-text);cursor:pointer;flex-shrink:0}.info-add-save:hover{background:var(--accent-hover)}.info-delete-edge{background:none;border:none;color:var(--text-dim);font-size:14px;cursor:pointer;margin-left:auto;padding:0 2px;opacity:0;transition:opacity .1s,color .1s}.info-connection:hover .info-delete-edge{opacity:1}.info-delete-edge:hover{color:#ef4444}.info-danger{margin-top:8px;padding-top:12px;border-top:1px solid var(--border)}.info-delete-node{background:none;border:1px solid rgba(239,68,68,.3);border-radius:6px;padding:6px 12px;font-size:12px;color:#ef4444;cursor:pointer;width:100%;transition:background .15s}.info-delete-node:hover{background:#ef44441a}