@bilalba/fig-mcp 1.1.2 → 1.1.3

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.
@@ -1,6 +1,7 @@
1
1
  /**
2
2
  * Build script for the web viewer client.
3
3
  * Bundles viewer.ts into a browser-compatible JavaScript file.
4
+ * Also copies static assets to dist/ for npm package.
4
5
  */
5
6
  export {};
6
7
  //# sourceMappingURL=build-client.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"build-client.d.ts","sourceRoot":"","sources":["../../src/web-viewer/build-client.ts"],"names":[],"mappings":"AAAA;;;GAGG"}
1
+ {"version":3,"file":"build-client.d.ts","sourceRoot":"","sources":["../../src/web-viewer/build-client.ts"],"names":[],"mappings":"AAAA;;;;GAIG"}
@@ -1,16 +1,35 @@
1
1
  /**
2
2
  * Build script for the web viewer client.
3
3
  * Bundles viewer.ts into a browser-compatible JavaScript file.
4
+ * Also copies static assets to dist/ for npm package.
4
5
  */
5
6
  import * as esbuild from "esbuild";
7
+ import fs from "node:fs";
6
8
  import path from "node:path";
7
9
  import { fileURLToPath } from "node:url";
8
10
  const __dirname = path.dirname(fileURLToPath(import.meta.url));
11
+ const srcClientDir = path.join(__dirname, "client");
12
+ const distClientDir = path.join(__dirname, "../../dist/web-viewer/client");
13
+ function copyStaticFiles() {
14
+ // Ensure dist directories exist
15
+ fs.mkdirSync(path.join(distClientDir, "dist"), { recursive: true });
16
+ // Copy static files to dist
17
+ const staticFiles = ["index.html", "styles.css"];
18
+ for (const file of staticFiles) {
19
+ const src = path.join(srcClientDir, file);
20
+ const dest = path.join(distClientDir, file);
21
+ if (fs.existsSync(src)) {
22
+ fs.copyFileSync(src, dest);
23
+ console.log(`Copied ${file} to dist/`);
24
+ }
25
+ }
26
+ }
9
27
  async function build() {
10
28
  const watch = process.argv.includes("--watch");
11
- const options = {
12
- entryPoints: [path.join(__dirname, "client/viewer.ts")],
13
- outfile: path.join(__dirname, "client/dist/viewer.js"),
29
+ // Build to src for dev, build to dist for production
30
+ const srcOptions = {
31
+ entryPoints: [path.join(srcClientDir, "viewer.ts")],
32
+ outfile: path.join(srcClientDir, "dist/viewer.js"),
14
33
  bundle: true,
15
34
  minify: !watch,
16
35
  sourcemap: watch,
@@ -20,12 +39,20 @@ async function build() {
20
39
  logLevel: "info",
21
40
  };
22
41
  if (watch) {
23
- const ctx = await esbuild.context(options);
42
+ const ctx = await esbuild.context(srcOptions);
24
43
  await ctx.watch();
25
44
  console.log("Watching for changes...");
26
45
  }
27
46
  else {
28
- await esbuild.build(options);
47
+ // Build to src/client/dist (for dev)
48
+ await esbuild.build(srcOptions);
49
+ // Also build to dist/web-viewer/client/dist (for npm package)
50
+ copyStaticFiles();
51
+ await esbuild.build({
52
+ ...srcOptions,
53
+ outfile: path.join(distClientDir, "dist/viewer.js"),
54
+ sourcemap: false,
55
+ });
29
56
  console.log("Build complete!");
30
57
  }
31
58
  }
@@ -1 +1 @@
1
- {"version":3,"file":"build-client.js","sourceRoot":"","sources":["../../src/web-viewer/build-client.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,OAAO,MAAM,SAAS,CAAC;AACnC,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAEzC,MAAM,SAAS,GAAG,IAAI,CAAC,OAAO,CAAC,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC;AAE/D,KAAK,UAAU,KAAK;IAClB,MAAM,KAAK,GAAG,OAAO,CAAC,IAAI,CAAC,QAAQ,CAAC,SAAS,CAAC,CAAC;IAE/C,MAAM,OAAO,GAAyB;QACpC,WAAW,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,kBAAkB,CAAC,CAAC;QACvD,OAAO,EAAE,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,uBAAuB,CAAC;QACtD,MAAM,EAAE,IAAI;QACZ,MAAM,EAAE,CAAC,KAAK;QACd,SAAS,EAAE,KAAK;QAChB,MAAM,EAAE,CAAC,WAAW,EAAE,YAAY,EAAE,UAAU,CAAC;QAC/C,MAAM,EAAE,MAAM;QACd,QAAQ,EAAE,SAAS;QACnB,QAAQ,EAAE,MAAM;KACjB,CAAC;IAEF,IAAI,KAAK,EAAE,CAAC;QACV,MAAM,GAAG,GAAG,MAAM,OAAO,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC;QAC3C,MAAM,GAAG,CAAC,KAAK,EAAE,CAAC;QAClB,OAAO,CAAC,GAAG,CAAC,yBAAyB,CAAC,CAAC;IACzC,CAAC;SAAM,CAAC;QACN,MAAM,OAAO,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;QAC7B,OAAO,CAAC,GAAG,CAAC,iBAAiB,CAAC,CAAC;IACjC,CAAC;AACH,CAAC;AAED,KAAK,EAAE,CAAC,KAAK,CAAC,CAAC,GAAG,EAAE,EAAE;IACpB,OAAO,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;IACnB,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC,CAAC,CAAC"}
1
+ {"version":3,"file":"build-client.js","sourceRoot":"","sources":["../../src/web-viewer/build-client.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,10 @@
1
+ "use strict";(()=>{var _=null,E=[],L=null,y=null,u=1,C="",V=null,f=[],T=null,g=null,r=e=>document.getElementById(e),i={fileName:r("file-name"),openBtn:r("open-btn"),search:r("search"),pagesList:r("pages-list"),tree:r("tree"),canvas:r("canvas"),canvasPlaceholder:r("canvas-placeholder"),zoomIn:r("zoom-in"),zoomOut:r("zoom-out"),zoomLevel:r("zoom-level"),zoomFit:r("zoom-fit"),noSelection:r("no-selection"),nodeDetails:r("node-details"),nodeId:r("node-id"),copyId:r("copy-id"),nodeType:r("node-type"),nodeName:r("node-name"),nodeX:r("node-x"),nodeY:r("node-y"),nodeWidth:r("node-width"),nodeHeight:r("node-height"),textSection:r("text-section"),nodeText:r("node-text"),nodeJson:r("node-json"),fileDialog:r("file-dialog"),filePathInput:r("file-path-input"),cancelOpen:r("cancel-open"),confirmOpen:r("confirm-open")};async function b(e,n){let t=await fetch(e,n);if(!t.ok){let o=await t.json().catch(()=>({error:t.statusText}));throw new Error(o.error||"Request failed")}return t.json()}async function j(){return b("/api/tree")}async function J(e){return b(`/api/node/${encodeURIComponent(e)}`)}async function Z(e){return b(`/api/node-raw/${encodeURIComponent(e)}`)}async function K(e){let n=await fetch(`/api/render/${encodeURIComponent(e)}`);if(!n.ok)throw new Error("Failed to render");return n.text()}async function Q(e){return(await b(`/api/flat-nodes/${encodeURIComponent(e)}`)).nodes}async function ee(e){await b("/api/open",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({filePath:e})})}function te(e){return{DOCUMENT:"D",CANVAS:"P",FRAME:"F",GROUP:"G",TEXT:"T",RECTANGLE:"R",ELLIPSE:"O",VECTOR:"V",LINE:"L",STAR:"*",REGULAR_POLYGON:"P",COMPONENT:"C",COMPONENT_SET:"S",INSTANCE:"I",BOOLEAN_OPERATION:"B",SLICE:"S",STICKY:"N",SHAPE_WITH_TEXT:"ST",CONNECTOR:"CN",SECTION:"SE"}[e]||"?"}function ne(){i.pagesList.innerHTML="";for(let e of E){let n=document.createElement("div");n.className="page-item",e.id===L&&n.classList.add("selected"),n.dataset.pageId=e.id;let t=document.createElement("span");t.className="page-icon",t.textContent="P",n.appendChild(t);let o=document.createElement("span");o.className="page-name",o.textContent=e.name||"(unnamed)",n.appendChild(o),n.addEventListener("click",()=>ie(e.id)),i.pagesList.appendChild(n)}}function ie(e){L=e,y=null,i.pagesList.querySelectorAll(".page-item").forEach(n=>{n.classList.toggle("selected",n.getAttribute("data-page-id")===e)}),I(),i.canvas.innerHTML='<div id="canvas-placeholder">Select a node to preview</div>',i.noSelection.classList.remove("hidden"),i.nodeDetails.classList.add("hidden")}function oe(e,n){if(!n)return!0;let t=n.toLowerCase();return e.name.toLowerCase().includes(t)||e.type.toLowerCase().includes(t)||e.id.toLowerCase().includes(t)}function G(e,n){return oe(e,n)?!0:e.children?e.children.some(t=>G(t,n)):!1}function N(e,n=0){let t=document.createElement("div");t.className="tree-node",t.dataset.nodeId=e.id;let o=e.children&&e.children.length>0,s=n<2;s&&o&&t.classList.add("expanded");let a=document.createElement("div");a.className="tree-node-row",a.style.paddingLeft=`${n*16+8}px`,e.id===y&&a.classList.add("selected");let l=document.createElement("span");l.className=`tree-toggle ${o?s?"expanded":"collapsed":""}`,o&&l.addEventListener("click",v=>{v.stopPropagation(),t.classList.toggle("expanded"),l.classList.toggle("collapsed"),l.classList.toggle("expanded")}),a.appendChild(l);let c=document.createElement("span");c.className=`tree-icon type-${e.type}`,c.textContent=te(e.type),a.appendChild(c);let d=document.createElement("span");d.className="tree-name",d.textContent=e.name||"(unnamed)",a.appendChild(d);let p=document.createElement("span");if(p.className="tree-type",p.textContent=e.type,a.appendChild(p),a.addEventListener("click",()=>O(e.id)),t.appendChild(a),o){let v=document.createElement("div");v.className="tree-children";for(let w of e.children)C&&!G(w,C)||v.appendChild(N(w,n+1));t.appendChild(v)}return t}function I(){i.tree.innerHTML="";let e=E.find(n=>n.id===L);if(!e){i.tree.innerHTML='<div style="padding: 20px; color: #999;">Select a page to view its contents</div>';return}if(C){let n=o=>{o.classList.add("expanded");let s=o.querySelector(".tree-toggle");s&&(s.classList.remove("collapsed"),s.classList.add("expanded"))},t=document.createDocumentFragment();t.appendChild(N(e)),i.tree.appendChild(t),i.tree.querySelectorAll(".tree-node").forEach(o=>{n(o)})}else i.tree.appendChild(N(e))}async function O(e,n){y=e,i.tree.querySelectorAll(".tree-node-row.selected").forEach(o=>{o.classList.remove("selected")});let t=i.tree.querySelector(`[data-node-id="${e}"] > .tree-node-row`);t&&t.classList.add("selected"),i.noSelection.classList.add("hidden"),i.nodeDetails.classList.remove("hidden"),i.nodeId.textContent=e;try{let[o,s]=await Promise.allSettled([J(e),Z(e)]);if(o.status!=="fulfilled")throw o.reason;let{node:a}=o.value,l=s.status==="fulfilled"?s.value.node:a;i.nodeType.textContent=a.type,i.nodeName.textContent=a.name||"(unnamed)",i.nodeX.textContent=a.x?.toFixed(1)??"-",i.nodeY.textContent=a.y?.toFixed(1)??"-",i.nodeWidth.textContent=a.width?.toFixed(1)??"-",i.nodeHeight.textContent=a.height?.toFixed(1)??"-",a.characters?(i.textSection.classList.remove("hidden"),i.nodeText.textContent=a.characters):i.textSection.classList.add("hidden"),i.nodeJson.textContent=JSON.stringify(l,null,2),n?.skipRender?M(e):await ae(e)}catch(o){console.error("Failed to load node details:",o)}}async function ae(e){let n=e!==V;try{let[t,o]=await Promise.all([K(e),Q(e)]);if(i.canvas.innerHTML=t,i.canvasPlaceholder?.remove(),V=e,f=o,o.length>0){let s=1/0,a=1/0,l=-1/0,c=-1/0;for(let d of o)s=Math.min(s,d.absX),a=Math.min(a,d.absY),l=Math.max(l,d.absX+d.width),c=Math.max(c,d.absY+d.height);g={minX:s,minY:a,width:l-s,height:c-a}}else g=null;if(re(),n){x=0,H=0,u=1;let s=F(),a=i.canvas.parentElement;s?(a.scrollLeft=0,a.scrollTop=0):A()}}catch(t){console.error("Failed to render preview:",t),i.canvas.innerHTML=`<div id="canvas-placeholder">Failed to render: ${t}</div>`,i.canvas.style.width="",i.canvas.style.height="",f=[],g=null}}var x=0,H=0;function F(){let n=i.canvas.parentElement.getBoundingClientRect();i.zoomLevel.textContent=`${Math.round(u*100)}%`,i.canvas.style.transform="";let t=i.canvas.querySelector("svg:not(#hover-overlay)");if(t){x===0&&(x=t.width.baseVal.value||parseFloat(t.getAttribute("width")||"100"),H=t.height.baseVal.value||parseFloat(t.getAttribute("height")||"100"));let o=x*u,s=H*u;t.setAttribute("width",String(o)),t.setAttribute("height",String(s));let a=i.canvas.querySelector("#hover-overlay");a&&(a.setAttribute("width",String(o)),a.setAttribute("height",String(s)));let l=80,c=o+l,d=s+l,p=c<=n.width&&d<=n.height;return p?(i.canvas.style.width="",i.canvas.style.height=""):(i.canvas.style.width=`${c}px`,i.canvas.style.height=`${d}px`),p}return!0}function P(e,n){let t=i.canvas.parentElement,o=t.getBoundingClientRect(),s=u,a=n||{x:o.width/2,y:o.height/2},l=40,c=(t.scrollLeft+a.x-l)/s,d=(t.scrollTop+a.y-l)/s;if(u=e,F())t.scrollLeft=0,t.scrollTop=0;else{let v=c*e+l-a.x,w=d*e+l-a.y,q=Math.max(0,t.scrollWidth-t.clientWidth),U=Math.max(0,t.scrollHeight-t.clientHeight);t.scrollLeft=Math.max(0,Math.min(q,v)),t.scrollTop=Math.max(0,Math.min(U,w))}}function Y(){let e=Math.min(u*1.15,10);P(e)}function X(){let e=Math.max(u/1.15,.1);P(e)}function se(e,n){let t=Math.max(-100,Math.min(100,e)),o=1+Math.abs(t)*.003,s;t<0?s=Math.min(u*o,10):s=Math.max(u/o,.1),P(s,n)}function A(){let e=i.canvas.querySelector("svg:not(#hover-overlay)");if(!e)return;let n=i.canvas.parentElement,t=n.getBoundingClientRect(),o=e.width.baseVal.value||parseFloat(e.getAttribute("width")||"100"),s=e.height.baseVal.value||parseFloat(e.getAttribute("height")||"100"),a=80,l=(t.width-a)/o,c=(t.height-a)/s;u=Math.min(l,c,1);let d=F();n.scrollLeft=0,n.scrollTop=0}var h=null,m=null;function re(){m&&m.remove();let e=i.canvas.querySelector("svg");e&&(m=document.createElement("div"),m.id="svg-wrapper",m.style.cssText=`
2
+ position: relative;
3
+ display: inline-block;
4
+ `,e.parentNode?.insertBefore(m,e),m.appendChild(e),h=document.createElementNS("http://www.w3.org/2000/svg","svg"),h.id="hover-overlay",h.style.cssText=`
5
+ position: absolute;
6
+ top: 0;
7
+ left: 0;
8
+ pointer-events: none;
9
+ overflow: visible;
10
+ `,m.appendChild(h))}function z(){return m?m.querySelector("svg:not(#hover-overlay)"):i.canvas.querySelector("svg:not(#hover-overlay)")}function M(e){if(!h||!g||(h.innerHTML="",!e))return;let n=f.find(d=>d.id===e);if(!n)return;let t=z();if(!t)return;let o=t.width.baseVal.value||parseFloat(t.getAttribute("width")||"0"),s=t.height.baseVal.value||parseFloat(t.getAttribute("height")||"0");h.setAttribute("viewBox",`0 0 ${o} ${s}`),h.setAttribute("width",String(o)),h.setAttribute("height",String(s));let a=n.absX-g.minX,l=n.absY-g.minY,c=document.createElementNS("http://www.w3.org/2000/svg","rect");c.setAttribute("x",String(a)),c.setAttribute("y",String(l)),c.setAttribute("width",String(n.width)),c.setAttribute("height",String(n.height)),c.setAttribute("fill","rgba(0, 120, 212, 0.1)"),c.setAttribute("stroke","#0078d4"),c.setAttribute("stroke-width","2"),h.appendChild(c)}function k(e,n){let t=z();if(!t||!g)return null;let o=t.getBoundingClientRect(),s=t.width.baseVal.value||parseFloat(t.getAttribute("width")||"0"),a=t.height.baseVal.value||parseFloat(t.getAttribute("height")||"0"),l=(e-o.left)/u,c=(n-o.top)/u,d=l+g.minX,p=c+g.minY;return{x:d,y:p}}function R(e,n){return f.filter(t=>t.visible&&e>=t.absX&&e<=t.absX+t.width&&n>=t.absY&&n<=t.absY+t.height).sort((t,o)=>o.depth-t.depth)}function D(e){return e.length===0?null:e[0]}function $(e){let n=i.tree.querySelector(`[data-node-id="${e}"]`);if(!n)return;let t=n.parentElement;for(;t;){if(t.classList?.contains("tree-node")){t.classList.add("expanded");let s=t.querySelector(":scope > .tree-node-row > .tree-toggle");s&&(s.classList.remove("collapsed"),s.classList.add("expanded"))}if(t.id==="tree")break;t=t.parentElement}let o=n.querySelector(":scope > .tree-node-row");o&&o.scrollIntoView({behavior:"smooth",block:"center"})}function le(e){if(f.length===0)return;let n=k(e.clientX,e.clientY);if(!n){T=null,M(null);return}let t=R(n.x,n.y),o=D(t);o?.id!==T&&(T=o?.id??null,M(T))}function ce(e){if(f.length===0)return;let n=k(e.clientX,e.clientY);if(!n)return;let t=R(n.x,n.y),o=D(t);if(o){$(o.id);let s=f.some(a=>a.id===o.id);O(o.id,{skipRender:s})}}function de(e){if(f.length===0)return;let n=k(e.clientX,e.clientY);if(!n)return;let t=R(n.x,n.y),o=D(t);o&&($(o.id),O(o.id))}function ue(){T=null,M(null)}function me(){i.fileDialog.classList.remove("hidden"),i.filePathInput.focus()}function S(){i.fileDialog.classList.add("hidden")}async function B(){let e=i.filePathInput.value.trim();if(e)try{i.confirmOpen.disabled=!0,i.confirmOpen.textContent="Loading...",await ee(e),S(),L=null,y=null,await W(),i.fileName.textContent=e.split("/").pop()||e}catch(n){alert(`Failed to open file: ${n}`)}finally{i.confirmOpen.disabled=!1,i.confirmOpen.textContent="Open"}}async function W(){try{let{tree:e,meta:n}=await j();_=e,E=[],e.children&&(E=e.children.filter(t=>t.type==="CANVAS")),E.length>0&&!L&&(L=E[0].id),ne(),I(),n?.name&&(i.fileName.textContent=n.name)}catch(e){console.error("Failed to load tree:",e),E=[],i.pagesList.innerHTML="",i.tree.innerHTML='<div style="padding: 20px; color: #999;">No file loaded. Click "Open File" to start.</div>'}}async function he(){if(y)try{await navigator.clipboard.writeText(y);let e=i.copyId.textContent;i.copyId.textContent="Copied!",setTimeout(()=>{i.copyId.textContent=e},1e3)}catch{let e=document.createElement("textarea");e.value=y,document.body.appendChild(e),e.select(),document.execCommand("copy"),document.body.removeChild(e)}}function pe(){i.zoomIn.addEventListener("click",Y),i.zoomOut.addEventListener("click",X),i.zoomFit.addEventListener("click",A),document.addEventListener("keydown",e=>{e.target instanceof HTMLInputElement||(e.key==="="||e.key==="+"?(e.preventDefault(),Y()):e.key==="-"?(e.preventDefault(),X()):e.key==="0"&&(e.preventDefault(),A()))}),i.canvas.parentElement?.addEventListener("wheel",e=>{if(e.ctrlKey||e.metaKey){e.preventDefault();let t=i.canvas.parentElement.getBoundingClientRect(),o={x:e.clientX-t.left,y:e.clientY-t.top};se(e.deltaY,o)}},{passive:!1}),i.canvas.addEventListener("mousemove",le),i.canvas.addEventListener("click",ce),i.canvas.addEventListener("dblclick",de),i.canvas.addEventListener("mouseleave",ue),i.search.addEventListener("input",e=>{C=e.target.value,I()}),i.openBtn.addEventListener("click",me),i.cancelOpen.addEventListener("click",S),i.confirmOpen.addEventListener("click",B),i.filePathInput.addEventListener("keydown",e=>{e.key==="Enter"?B():e.key==="Escape"&&S()}),i.fileDialog.addEventListener("click",e=>{e.target===i.fileDialog&&S()}),i.copyId.addEventListener("click",he),W()}document.addEventListener("DOMContentLoaded",pe);})();
@@ -0,0 +1,138 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>Fig Viewer</title>
7
+ <link rel="stylesheet" href="/styles.css">
8
+ </head>
9
+ <body>
10
+ <div id="app">
11
+ <!-- Header -->
12
+ <header id="header">
13
+ <h1>Fig Viewer</h1>
14
+ <div id="file-info">
15
+ <span id="file-name">No file loaded</span>
16
+ <button id="open-btn" title="Open .fig file">Open File</button>
17
+ </div>
18
+ </header>
19
+
20
+ <!-- Main layout -->
21
+ <div id="main">
22
+ <!-- Left panel: Tree view -->
23
+ <aside id="tree-panel">
24
+ <!-- Pages list -->
25
+ <div id="pages-section">
26
+ <div id="pages-header">
27
+ <span>Pages</span>
28
+ </div>
29
+ <div id="pages-list"></div>
30
+ </div>
31
+
32
+ <!-- Tree with search -->
33
+ <div id="tree-section">
34
+ <div id="tree-header">
35
+ <input type="text" id="search" placeholder="Search nodes..." />
36
+ </div>
37
+ <div id="tree-container">
38
+ <div id="tree"></div>
39
+ </div>
40
+ </div>
41
+ </aside>
42
+
43
+ <!-- Center: Canvas/Preview -->
44
+ <main id="canvas-panel">
45
+ <div id="canvas-toolbar">
46
+ <button id="zoom-in" title="Zoom in">+</button>
47
+ <span id="zoom-level">100%</span>
48
+ <button id="zoom-out" title="Zoom out">-</button>
49
+ <button id="zoom-fit" title="Fit to view">Fit</button>
50
+ </div>
51
+ <div id="canvas-container">
52
+ <div id="canvas">
53
+ <div id="canvas-placeholder">
54
+ Select a node to preview
55
+ </div>
56
+ </div>
57
+ </div>
58
+ </main>
59
+
60
+ <!-- Right panel: Node details -->
61
+ <aside id="details-panel">
62
+ <div id="details-header">
63
+ <h2>Node Details</h2>
64
+ </div>
65
+ <div id="details-content">
66
+ <div id="no-selection">Select a node to see details</div>
67
+ <div id="node-details" class="hidden">
68
+ <div class="detail-section">
69
+ <div class="detail-row">
70
+ <label>ID</label>
71
+ <div class="detail-value">
72
+ <code id="node-id"></code>
73
+ <button id="copy-id" title="Copy ID">Copy</button>
74
+ </div>
75
+ </div>
76
+ <div class="detail-row">
77
+ <label>Type</label>
78
+ <span id="node-type"></span>
79
+ </div>
80
+ <div class="detail-row">
81
+ <label>Name</label>
82
+ <span id="node-name"></span>
83
+ </div>
84
+ </div>
85
+
86
+ <div class="detail-section">
87
+ <h3>Bounds</h3>
88
+ <div class="detail-grid">
89
+ <div class="detail-cell">
90
+ <label>X</label>
91
+ <span id="node-x">-</span>
92
+ </div>
93
+ <div class="detail-cell">
94
+ <label>Y</label>
95
+ <span id="node-y">-</span>
96
+ </div>
97
+ <div class="detail-cell">
98
+ <label>W</label>
99
+ <span id="node-width">-</span>
100
+ </div>
101
+ <div class="detail-cell">
102
+ <label>H</label>
103
+ <span id="node-height">-</span>
104
+ </div>
105
+ </div>
106
+ </div>
107
+
108
+ <div class="detail-section" id="text-section">
109
+ <h3>Text</h3>
110
+ <div id="node-text"></div>
111
+ </div>
112
+
113
+ <div class="detail-section">
114
+ <h3>Raw JSON</h3>
115
+ <pre id="node-json"></pre>
116
+ </div>
117
+ </div>
118
+ </div>
119
+ </aside>
120
+ </div>
121
+ </div>
122
+
123
+ <!-- File picker dialog (hidden by default) -->
124
+ <div id="file-dialog" class="dialog hidden">
125
+ <div class="dialog-content">
126
+ <h2>Open .fig File</h2>
127
+ <p>Enter the path to a .fig file on your local machine:</p>
128
+ <input type="text" id="file-path-input" placeholder="/path/to/design.fig" />
129
+ <div class="dialog-buttons">
130
+ <button id="cancel-open">Cancel</button>
131
+ <button id="confirm-open" class="primary">Open</button>
132
+ </div>
133
+ </div>
134
+ </div>
135
+
136
+ <script src="/viewer.js"></script>
137
+ </body>
138
+ </html>
@@ -0,0 +1,561 @@
1
+ /* Fig Viewer Styles */
2
+
3
+ * {
4
+ box-sizing: border-box;
5
+ margin: 0;
6
+ padding: 0;
7
+ }
8
+
9
+ :root {
10
+ --bg-primary: #1e1e1e;
11
+ --bg-secondary: #252526;
12
+ --bg-tertiary: #2d2d2d;
13
+ --bg-hover: #3c3c3c;
14
+ --bg-selected: #094771;
15
+ --text-primary: #cccccc;
16
+ --text-secondary: #9d9d9d;
17
+ --text-muted: #6d6d6d;
18
+ --border-color: #3c3c3c;
19
+ --accent: #0078d4;
20
+ --accent-hover: #1a8cff;
21
+ --success: #4caf50;
22
+ --warning: #ff9800;
23
+ --error: #f44336;
24
+ --font-mono: 'SF Mono', 'Consolas', 'Monaco', monospace;
25
+ --font-sans: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
26
+ }
27
+
28
+ html, body {
29
+ height: 100%;
30
+ font-family: var(--font-sans);
31
+ font-size: 13px;
32
+ background: var(--bg-primary);
33
+ color: var(--text-primary);
34
+ }
35
+
36
+ #app {
37
+ display: flex;
38
+ flex-direction: column;
39
+ height: 100%;
40
+ }
41
+
42
+ /* Header */
43
+ #header {
44
+ display: flex;
45
+ align-items: center;
46
+ justify-content: space-between;
47
+ padding: 8px 16px;
48
+ background: var(--bg-secondary);
49
+ border-bottom: 1px solid var(--border-color);
50
+ }
51
+
52
+ #header h1 {
53
+ font-size: 14px;
54
+ font-weight: 600;
55
+ color: var(--text-primary);
56
+ }
57
+
58
+ #file-info {
59
+ display: flex;
60
+ align-items: center;
61
+ gap: 12px;
62
+ }
63
+
64
+ #file-name {
65
+ color: var(--text-secondary);
66
+ font-size: 12px;
67
+ }
68
+
69
+ button {
70
+ background: var(--bg-tertiary);
71
+ border: 1px solid var(--border-color);
72
+ color: var(--text-primary);
73
+ padding: 4px 12px;
74
+ border-radius: 4px;
75
+ cursor: pointer;
76
+ font-size: 12px;
77
+ transition: background 0.15s;
78
+ }
79
+
80
+ button:hover {
81
+ background: var(--bg-hover);
82
+ }
83
+
84
+ button.primary {
85
+ background: var(--accent);
86
+ border-color: var(--accent);
87
+ }
88
+
89
+ button.primary:hover {
90
+ background: var(--accent-hover);
91
+ }
92
+
93
+ /* Main layout */
94
+ #main {
95
+ display: flex;
96
+ flex: 1;
97
+ overflow: hidden;
98
+ }
99
+
100
+ /* Tree panel */
101
+ #tree-panel {
102
+ width: 280px;
103
+ min-width: 200px;
104
+ display: flex;
105
+ flex-direction: column;
106
+ background: var(--bg-secondary);
107
+ border-right: 1px solid var(--border-color);
108
+ }
109
+
110
+ /* Pages section */
111
+ #pages-section {
112
+ border-bottom: 1px solid var(--border-color);
113
+ }
114
+
115
+ #pages-header {
116
+ padding: 8px 12px;
117
+ font-size: 11px;
118
+ font-weight: 600;
119
+ color: var(--text-secondary);
120
+ text-transform: uppercase;
121
+ background: var(--bg-tertiary);
122
+ }
123
+
124
+ #pages-list {
125
+ max-height: 150px;
126
+ overflow-y: auto;
127
+ }
128
+
129
+ .page-item {
130
+ display: flex;
131
+ align-items: center;
132
+ padding: 6px 12px;
133
+ cursor: pointer;
134
+ font-size: 12px;
135
+ border-left: 3px solid transparent;
136
+ }
137
+
138
+ .page-item:hover {
139
+ background: var(--bg-hover);
140
+ }
141
+
142
+ .page-item.selected {
143
+ background: var(--bg-selected);
144
+ border-left-color: var(--accent);
145
+ }
146
+
147
+ .page-item .page-icon {
148
+ width: 16px;
149
+ height: 16px;
150
+ margin-right: 8px;
151
+ display: flex;
152
+ align-items: center;
153
+ justify-content: center;
154
+ font-size: 10px;
155
+ color: #94a3b8;
156
+ }
157
+
158
+ .page-item .page-name {
159
+ flex: 1;
160
+ overflow: hidden;
161
+ text-overflow: ellipsis;
162
+ white-space: nowrap;
163
+ }
164
+
165
+ /* Tree section */
166
+ #tree-section {
167
+ flex: 1;
168
+ display: flex;
169
+ flex-direction: column;
170
+ overflow: hidden;
171
+ }
172
+
173
+ #tree-header {
174
+ padding: 8px;
175
+ border-bottom: 1px solid var(--border-color);
176
+ }
177
+
178
+ #search {
179
+ width: 100%;
180
+ background: var(--bg-tertiary);
181
+ border: 1px solid var(--border-color);
182
+ color: var(--text-primary);
183
+ padding: 6px 10px;
184
+ border-radius: 4px;
185
+ font-size: 12px;
186
+ }
187
+
188
+ #search::placeholder {
189
+ color: var(--text-muted);
190
+ }
191
+
192
+ #tree-container {
193
+ flex: 1;
194
+ overflow: auto;
195
+ }
196
+
197
+ #tree {
198
+ padding: 4px 0;
199
+ }
200
+
201
+ .tree-node {
202
+ user-select: none;
203
+ }
204
+
205
+ .tree-node-row {
206
+ display: flex;
207
+ align-items: center;
208
+ padding: 2px 8px;
209
+ cursor: pointer;
210
+ white-space: nowrap;
211
+ }
212
+
213
+ .tree-node-row:hover {
214
+ background: var(--bg-hover);
215
+ }
216
+
217
+ .tree-node-row.selected {
218
+ background: var(--bg-selected);
219
+ }
220
+
221
+ .tree-toggle {
222
+ width: 16px;
223
+ height: 16px;
224
+ display: flex;
225
+ align-items: center;
226
+ justify-content: center;
227
+ color: var(--text-secondary);
228
+ flex-shrink: 0;
229
+ }
230
+
231
+ .tree-toggle.collapsed::before {
232
+ content: '▶';
233
+ font-size: 8px;
234
+ }
235
+
236
+ .tree-toggle.expanded::before {
237
+ content: '▼';
238
+ font-size: 8px;
239
+ }
240
+
241
+ .tree-icon {
242
+ width: 16px;
243
+ height: 16px;
244
+ margin-right: 6px;
245
+ display: flex;
246
+ align-items: center;
247
+ justify-content: center;
248
+ font-size: 10px;
249
+ color: var(--text-secondary);
250
+ flex-shrink: 0;
251
+ }
252
+
253
+ .tree-icon.type-FRAME { color: #a78bfa; }
254
+ .tree-icon.type-GROUP { color: #60a5fa; }
255
+ .tree-icon.type-TEXT { color: #34d399; }
256
+ .tree-icon.type-RECTANGLE { color: #fbbf24; }
257
+ .tree-icon.type-ELLIPSE { color: #f472b6; }
258
+ .tree-icon.type-VECTOR { color: #fb923c; }
259
+ .tree-icon.type-COMPONENT { color: #22d3ee; }
260
+ .tree-icon.type-INSTANCE { color: #a78bfa; }
261
+ .tree-icon.type-CANVAS { color: #94a3b8; }
262
+
263
+ .tree-name {
264
+ flex: 1;
265
+ overflow: hidden;
266
+ text-overflow: ellipsis;
267
+ font-size: 12px;
268
+ }
269
+
270
+ .tree-type {
271
+ font-size: 10px;
272
+ color: var(--text-muted);
273
+ margin-left: 8px;
274
+ }
275
+
276
+ .tree-children {
277
+ display: none;
278
+ }
279
+
280
+ .tree-node.expanded > .tree-children {
281
+ display: block;
282
+ }
283
+
284
+ /* Canvas panel */
285
+ #canvas-panel {
286
+ flex: 1;
287
+ display: flex;
288
+ flex-direction: column;
289
+ background: var(--bg-primary);
290
+ overflow: hidden;
291
+ }
292
+
293
+ #canvas-toolbar {
294
+ display: flex;
295
+ align-items: center;
296
+ gap: 8px;
297
+ padding: 8px 16px;
298
+ background: var(--bg-secondary);
299
+ border-bottom: 1px solid var(--border-color);
300
+ }
301
+
302
+ #zoom-level {
303
+ min-width: 50px;
304
+ text-align: center;
305
+ font-size: 12px;
306
+ color: var(--text-secondary);
307
+ }
308
+
309
+ #canvas-container {
310
+ flex: 1;
311
+ overflow: auto;
312
+ background:
313
+ linear-gradient(45deg, #2a2a2a 25%, transparent 25%),
314
+ linear-gradient(-45deg, #2a2a2a 25%, transparent 25%),
315
+ linear-gradient(45deg, transparent 75%, #2a2a2a 75%),
316
+ linear-gradient(-45deg, transparent 75%, #2a2a2a 75%);
317
+ background-size: 20px 20px;
318
+ background-position: 0 0, 0 10px, 10px -10px, -10px 0px;
319
+ }
320
+
321
+ #canvas {
322
+ min-width: 100%;
323
+ min-height: 100%;
324
+ display: flex;
325
+ align-items: center;
326
+ justify-content: center;
327
+ padding: 40px;
328
+ box-sizing: border-box;
329
+ position: relative;
330
+ cursor: pointer;
331
+ }
332
+
333
+ /* Hover overlay for node selection */
334
+ #hover-overlay {
335
+ position: absolute;
336
+ top: 0;
337
+ left: 0;
338
+ pointer-events: none;
339
+ z-index: 10;
340
+ }
341
+
342
+ #canvas-placeholder {
343
+ color: var(--text-muted);
344
+ font-size: 14px;
345
+ padding: 40px;
346
+ }
347
+
348
+ #canvas svg {
349
+ display: block;
350
+ max-width: none;
351
+ box-shadow: 0 4px 20px rgba(0, 0, 0, 0.3);
352
+ }
353
+
354
+ /* Details panel */
355
+ #details-panel {
356
+ width: 300px;
357
+ min-width: 200px;
358
+ display: flex;
359
+ flex-direction: column;
360
+ background: var(--bg-secondary);
361
+ border-left: 1px solid var(--border-color);
362
+ }
363
+
364
+ #details-header {
365
+ padding: 12px 16px;
366
+ border-bottom: 1px solid var(--border-color);
367
+ }
368
+
369
+ #details-header h2 {
370
+ font-size: 13px;
371
+ font-weight: 600;
372
+ }
373
+
374
+ #details-content {
375
+ flex: 1;
376
+ overflow: auto;
377
+ padding: 12px;
378
+ }
379
+
380
+ #no-selection {
381
+ color: var(--text-muted);
382
+ text-align: center;
383
+ padding: 40px 20px;
384
+ }
385
+
386
+ .hidden {
387
+ display: none !important;
388
+ }
389
+
390
+ .detail-section {
391
+ margin-bottom: 16px;
392
+ }
393
+
394
+ .detail-section h3 {
395
+ font-size: 11px;
396
+ font-weight: 600;
397
+ color: var(--text-secondary);
398
+ text-transform: uppercase;
399
+ margin-bottom: 8px;
400
+ }
401
+
402
+ .detail-row {
403
+ display: flex;
404
+ align-items: center;
405
+ margin-bottom: 8px;
406
+ }
407
+
408
+ .detail-row label {
409
+ width: 50px;
410
+ font-size: 11px;
411
+ color: var(--text-secondary);
412
+ flex-shrink: 0;
413
+ }
414
+
415
+ .detail-value {
416
+ display: flex;
417
+ align-items: center;
418
+ gap: 8px;
419
+ flex: 1;
420
+ min-width: 0;
421
+ }
422
+
423
+ .detail-value code {
424
+ font-family: var(--font-mono);
425
+ font-size: 11px;
426
+ background: var(--bg-tertiary);
427
+ padding: 2px 6px;
428
+ border-radius: 3px;
429
+ overflow: hidden;
430
+ text-overflow: ellipsis;
431
+ white-space: nowrap;
432
+ }
433
+
434
+ #copy-id {
435
+ padding: 2px 6px;
436
+ font-size: 10px;
437
+ }
438
+
439
+ .detail-grid {
440
+ display: grid;
441
+ grid-template-columns: repeat(4, 1fr);
442
+ gap: 8px;
443
+ }
444
+
445
+ .detail-cell {
446
+ background: var(--bg-tertiary);
447
+ padding: 6px 8px;
448
+ border-radius: 4px;
449
+ }
450
+
451
+ .detail-cell label {
452
+ display: block;
453
+ font-size: 10px;
454
+ color: var(--text-muted);
455
+ margin-bottom: 2px;
456
+ width: auto;
457
+ }
458
+
459
+ .detail-cell span {
460
+ font-family: var(--font-mono);
461
+ font-size: 11px;
462
+ }
463
+
464
+ #node-text {
465
+ background: var(--bg-tertiary);
466
+ padding: 8px;
467
+ border-radius: 4px;
468
+ font-size: 12px;
469
+ white-space: pre-wrap;
470
+ word-break: break-word;
471
+ max-height: 100px;
472
+ overflow: auto;
473
+ }
474
+
475
+ #node-json {
476
+ background: var(--bg-tertiary);
477
+ padding: 8px;
478
+ border-radius: 4px;
479
+ font-family: var(--font-mono);
480
+ font-size: 10px;
481
+ overflow: auto;
482
+ max-height: 300px;
483
+ white-space: pre-wrap;
484
+ word-break: break-word;
485
+ }
486
+
487
+ /* Dialog */
488
+ .dialog {
489
+ position: fixed;
490
+ top: 0;
491
+ left: 0;
492
+ right: 0;
493
+ bottom: 0;
494
+ background: rgba(0, 0, 0, 0.5);
495
+ display: flex;
496
+ align-items: center;
497
+ justify-content: center;
498
+ z-index: 1000;
499
+ }
500
+
501
+ .dialog-content {
502
+ background: var(--bg-secondary);
503
+ border: 1px solid var(--border-color);
504
+ border-radius: 8px;
505
+ padding: 24px;
506
+ width: 400px;
507
+ max-width: 90%;
508
+ }
509
+
510
+ .dialog-content h2 {
511
+ font-size: 16px;
512
+ margin-bottom: 12px;
513
+ }
514
+
515
+ .dialog-content p {
516
+ color: var(--text-secondary);
517
+ margin-bottom: 16px;
518
+ }
519
+
520
+ .dialog-content input {
521
+ width: 100%;
522
+ background: var(--bg-tertiary);
523
+ border: 1px solid var(--border-color);
524
+ color: var(--text-primary);
525
+ padding: 8px 12px;
526
+ border-radius: 4px;
527
+ font-size: 13px;
528
+ font-family: var(--font-mono);
529
+ margin-bottom: 16px;
530
+ }
531
+
532
+ .dialog-buttons {
533
+ display: flex;
534
+ justify-content: flex-end;
535
+ gap: 8px;
536
+ }
537
+
538
+ /* Loading state */
539
+ .loading {
540
+ opacity: 0.5;
541
+ pointer-events: none;
542
+ }
543
+
544
+ /* Scrollbar styling */
545
+ ::-webkit-scrollbar {
546
+ width: 10px;
547
+ height: 10px;
548
+ }
549
+
550
+ ::-webkit-scrollbar-track {
551
+ background: var(--bg-secondary);
552
+ }
553
+
554
+ ::-webkit-scrollbar-thumb {
555
+ background: var(--bg-hover);
556
+ border-radius: 5px;
557
+ }
558
+
559
+ ::-webkit-scrollbar-thumb:hover {
560
+ background: var(--text-muted);
561
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bilalba/fig-mcp",
3
- "version": "1.1.2",
3
+ "version": "1.1.3",
4
4
  "description": "MCP server for parsing .fig files",
5
5
  "keywords": [
6
6
  "fig",
@@ -36,7 +36,7 @@
36
36
  "test": "tests"
37
37
  },
38
38
  "scripts": {
39
- "build": "tsc",
39
+ "build": "tsc && tsx src/web-viewer/build-client.ts",
40
40
  "prepublishOnly": "npm run build",
41
41
  "dev": "tsx watch src/index.ts",
42
42
  "start": "node dist/index.js",