@hienlh/ppm 0.13.48 → 0.13.50
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/CHANGELOG.md +8 -0
- package/assets/skills/ppm/SKILL.md +1 -1
- package/assets/skills/ppm/references/http-api.md +1 -1
- package/dist/web/assets/{audio-preview-BFc4v5Tx.js → audio-preview-CILFIsuu.js} +2 -2
- package/dist/web/assets/{audio-preview-BFc4v5Tx.js.map → audio-preview-CILFIsuu.js.map} +1 -1
- package/dist/web/assets/{chat-tab-x1rBGHj5.js → chat-tab-DBYwH_Aa.js} +4 -4
- package/dist/web/assets/{chat-tab-x1rBGHj5.js.map → chat-tab-DBYwH_Aa.js.map} +1 -1
- package/dist/web/assets/{code-editor-BgyAZcQX.js → code-editor-MXnkYRLp.js} +3 -3
- package/dist/web/assets/{code-editor-BgyAZcQX.js.map → code-editor-MXnkYRLp.js.map} +1 -1
- package/dist/web/assets/{conflict-editor-Dhc1lem7.js → conflict-editor-C6wH5wV6.js} +2 -2
- package/dist/web/assets/{conflict-editor-Dhc1lem7.js.map → conflict-editor-C6wH5wV6.js.map} +1 -1
- package/dist/web/assets/{database-viewer-DnnjO38W.js → database-viewer-BjUruZLv.js} +2 -2
- package/dist/web/assets/{database-viewer-DnnjO38W.js.map → database-viewer-BjUruZLv.js.map} +1 -1
- package/dist/web/assets/{diff-viewer-B6q9wXD6.js → diff-viewer-B_nU7bQi.js} +2 -2
- package/dist/web/assets/{diff-viewer-B6q9wXD6.js.map → diff-viewer-B_nU7bQi.js.map} +1 -1
- package/dist/web/assets/{extension-webview-CMBEb4FF.js → extension-webview-B56ZfvoD.js} +2 -2
- package/dist/web/assets/{extension-webview-CMBEb4FF.js.map → extension-webview-B56ZfvoD.js.map} +1 -1
- package/dist/web/assets/{glide-data-grid-BLcBAxgp.js → glide-data-grid-D-qQqqp7.js} +2 -2
- package/dist/web/assets/{glide-data-grid-BLcBAxgp.js.map → glide-data-grid-D-qQqqp7.js.map} +1 -1
- package/dist/web/assets/{image-preview-YpWk7AOb.js → image-preview-Dc6AiqYX.js} +2 -2
- package/dist/web/assets/{image-preview-YpWk7AOb.js.map → image-preview-Dc6AiqYX.js.map} +1 -1
- package/dist/web/assets/{index-hxAGD2rx.js → index-8_rE2Q1-.js} +6 -6
- package/dist/web/assets/{index-hxAGD2rx.js.map → index-8_rE2Q1-.js.map} +1 -1
- package/dist/web/assets/keybindings-store-COJD5O6M.js +1 -0
- package/dist/web/assets/{markdown-renderer-BAAmsTL9.js → markdown-renderer-CNQ8I0Dk.js} +2 -2
- package/dist/web/assets/{markdown-renderer-BAAmsTL9.js.map → markdown-renderer-CNQ8I0Dk.js.map} +1 -1
- package/dist/web/assets/notification-store-BiZaLXop.js +1 -0
- package/dist/web/assets/{panel-store-Dy8-7E_g.js → panel-store-C8wwxBpn.js} +2 -2
- package/dist/web/assets/{panel-store-Dy8-7E_g.js.map → panel-store-C8wwxBpn.js.map} +1 -1
- package/dist/web/assets/{pdf-preview-DWe7ARXI.js → pdf-preview-zs9QdgDp.js} +2 -2
- package/dist/web/assets/{pdf-preview-DWe7ARXI.js.map → pdf-preview-zs9QdgDp.js.map} +1 -1
- package/dist/web/assets/{port-forwarding-tab-R5V7zkKO.js → port-forwarding-tab-sArYx1nt.js} +2 -2
- package/dist/web/assets/{port-forwarding-tab-R5V7zkKO.js.map → port-forwarding-tab-sArYx1nt.js.map} +1 -1
- package/dist/web/assets/{postgres-viewer-AxCUyDQP.js → postgres-viewer-khk7N7cd.js} +2 -2
- package/dist/web/assets/{postgres-viewer-AxCUyDQP.js.map → postgres-viewer-khk7N7cd.js.map} +1 -1
- package/dist/web/assets/{settings-tab-CJ6w6q2s.js → settings-tab-CGWhVzQm.js} +1 -1
- package/dist/web/assets/{sql-query-editor-DhZvNbKv.js → sql-query-editor-B5Ndypxp.js} +2 -2
- package/dist/web/assets/{sql-query-editor-DhZvNbKv.js.map → sql-query-editor-B5Ndypxp.js.map} +1 -1
- package/dist/web/assets/{sqlite-viewer-Qv8TlhPb.js → sqlite-viewer-BkpONSGa.js} +2 -2
- package/dist/web/assets/{sqlite-viewer-Qv8TlhPb.js.map → sqlite-viewer-BkpONSGa.js.map} +1 -1
- package/dist/web/assets/{tab-store-Dtg1_qL0.js → tab-store-CNas5Ny8.js} +2 -2
- package/dist/web/assets/{tab-store-Dtg1_qL0.js.map → tab-store-CNas5Ny8.js.map} +1 -1
- package/dist/web/assets/{terminal-tab-9hLQtUUT.js → terminal-tab-BgMCsdeN.js} +2 -2
- package/dist/web/assets/{terminal-tab-9hLQtUUT.js.map → terminal-tab-BgMCsdeN.js.map} +1 -1
- package/dist/web/assets/{video-preview-C-tj7tok.js → video-preview-w8ZAy8av.js} +2 -2
- package/dist/web/assets/{video-preview-C-tj7tok.js.map → video-preview-w8ZAy8av.js.map} +1 -1
- package/dist/web/index.html +3 -3
- package/dist/web/sw.js +1 -1
- package/package.json +1 -1
- package/src/services/db.service.ts +28 -0
- package/src/types/extension.ts +1 -1
- package/src/web/components/settings/extension-manager-section.tsx +2 -2
- package/src/web/stores/panel-store.ts +5 -0
- package/dist/web/assets/keybindings-store-Dn9ANcCK.js +0 -1
- package/dist/web/assets/notification-store-DyVQiPv9.js +0 -1
- package/packages/ext-database/package.json +0 -48
- package/packages/ext-database/src/connection-tree.ts +0 -201
- package/packages/ext-database/src/extension.ts +0 -578
- package/packages/ext-database/src/query-panel.ts +0 -133
- package/packages/ext-database/src/table-viewer-panel.ts +0 -420
- package/packages/ext-database/tsconfig.json +0 -8
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import"./vendor-markdown-0Mxgxy0L.js";import"./api-client-DIhJ5qVW.js";import{j as e}from"./index-8_rE2Q1-.js";export{e as useNotificationStore};
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{t as e}from"./react-DMIOAtcX.js";import{r as t}from"./utils-CQux7CsO.js";function n(){return`panel-${t()}`}function r(e=[],t=null){return{id:n(),tabs:e,activeTabId:t,tabHistory:t?[t]:[]}}function i(e){return e?1:3}function a(e,t){return e.map(e=>e.filter(e=>e!==t)).filter(e=>e.length>0)}function o(e,t){for(let n=0;n<e.length;n++){let r=e[n].indexOf(t);if(r!==-1)return{row:n,col:r}}return null}function s(e){let t=0;for(let n of Object.values(e))for(let e of n.tabs){let n=e.id.match(/^editor:untitled-(\d+)$/);n&&(t=Math.max(t,Number(n[1])))}return t+1}function c(e,n){switch(e){case`editor`:return n?.viewerKey?`editor:viewer:${n.viewerKey}`:n?.isUntitled?`editor:untitled-${n.untitledNumber??1}`:`editor:${n?.filePath??`untitled`}`;case`chat`:return`chat:${n?.providerId??`default`}/${n?.sessionId??t()}`;case`terminal`:return`terminal:${n?.terminalIndex??1}`;case`database`:return`database:${n?.connectionId??`default`}:${n?.tableName??``}`;case`sqlite`:return`sqlite:${n?.filePath??`default`}`;case`postgres`:return`postgres:${n?.connectionId??`default`}:${n?.tableName??``}`;case`extension`:return`extension:${String(n?.viewType??`unknown`).replace(/\.view$/,``)}`;case`git-diff`:return`git-diff:${n?.filePath??`unknown`}`;case`conflict-editor`:return`conflict-editor:${n?.filePath??`unknown`}`;case`settings`:return`settings`;case`ports`:return`ports`;default:return`${e}:${t()}`}}function l(e){let t={...e,panels:{...e.panels}};for(let[e,n]of Object.entries(t.panels)){let r=n.tabs.map(e=>{if(e.id.startsWith(`tab-`)){let t=c(e.type,e.metadata);return{...e,id:t}}return e}),i=new Map;n.tabs.forEach((e,t)=>{e.id!==r[t].id&&i.set(e.id,r[t].id)});let a=i.get(n.activeTabId??``)??n.activeTabId,o=n.tabHistory.map(e=>i.get(e)??e);t.panels[e]={...n,tabs:r,activeTabId:a,tabHistory:o}}return t}var u=`ppm-panels-`,d=`ppm-tabs-`;function f(e){return`${u}${e}`}function p(e,t){try{let n={...t,updatedAt:new Date().toISOString()};localStorage.setItem(f(e),JSON.stringify(n)),e!==`__global__`&&_(e,t)}catch{}}function m(e){try{let t=localStorage.getItem(f(e));if(t)return l(JSON.parse(t))}catch{}return y(e)}async function h(e){if(e===`__global__`)return null;try{let t={},n=localStorage.getItem(`ppm-auth-token`);n&&(t.Authorization=`Bearer ${n}`);let r=await fetch(`/api/project/${encodeURIComponent(e)}/workspace`,{headers:t});if(!r.ok)return null;let i=await r.json();return!i.ok||!i.data?null:{...i.data.layout,updatedAt:i.data.updatedAt}}catch{return null}}var g=new Map;function _(e,t){let n=g.get(e);n&&clearTimeout(n),g.set(e,setTimeout(async()=>{g.delete(e);try{let n={"Content-Type":`application/json`},r=localStorage.getItem(`ppm-auth-token`);r&&(n.Authorization=`Bearer ${r}`);let i=await fetch(`/api/project/${encodeURIComponent(e)}/workspace`,{method:`PUT`,headers:n,body:JSON.stringify({layout:t})});if(i.ok){let t=await i.json();if(t.data?.updatedAt){let n=`${u}${e}`,r=localStorage.getItem(n);if(r){let e=JSON.parse(r);e.updatedAt=t.data.updatedAt,localStorage.setItem(n,JSON.stringify(e))}}}}catch{}},1500))}function v(e,t){if(!e&&!t)return null;if(!e)return t;if(!t)return e;let n=new Date(e.updatedAt).getTime();return new Date(t.updatedAt).getTime()>=n?t:e}function y(e){try{let t=localStorage.getItem(`${d}${e}`);if(!t)return null;let n=JSON.parse(t);if(!n.tabs?.length)return null;let i=r(n.tabs,n.activeTabId),a={panels:{[i.id]:i},grid:[[i.id]],focusedPanelId:i.id};return p(e,a),localStorage.removeItem(`${d}${e}`),a}catch{return null}}var b=new Set([`settings`]),x=new Set([`projects`,`git-status`,`git-graph`]);function S(e,t){let n=e.filter(e=>e!==t);return n.push(t),n.length>50&&n.shift(),n}function C(){let e=r();return{panels:{[e.id]:e},grid:[[e.id]],focusedPanelId:e.id}}var w=e()((e,t)=>{function n(){let{currentProject:e,panels:n,grid:r,focusedPanelId:i}=t();if(!e)return;let a=new Set(r.flat()),o={};for(let[e,t]of Object.entries(n))a.has(e)&&(o[e]=t);p(e,{panels:o,grid:r,focusedPanelId:i})}function s(e){return Object.values(t().panels).find(t=>t.tabs.some(t=>t.id===e))}function l(e){return e??t().focusedPanelId}return{...C(),currentProject:null,projectGrids:{},projectFocused:{},switchProject:n=>{let{currentProject:i,panels:a,grid:o,focusedPanelId:s,projectGrids:c,projectFocused:l}=t();if(i===n)return;let u={...c},d={...l};if(i){u[i]=o,d[i]=s;let e=new Set(o.flat()),t={};for(let[n,r]of Object.entries(a))e.has(n)&&(t[n]=r);p(i,{panels:t,grid:o,focusedPanelId:s})}if(u[n]){let t=u[n];e({currentProject:n,grid:t,focusedPanelId:d[n]??t[0]?.[0]??``,projectGrids:u,projectFocused:d});return}let f=m(n);if(f&&Object.keys(f.panels).length>0){let t={};for(let[e,n]of Object.entries(f.panels)){let r=n.tabs.filter(e=>!x.has(e.type)),i=n.tabHistory.filter(e=>r.some(t=>t.id===e)),a=n.activeTabId&&r.some(e=>e.id===n.activeTabId)?n.activeTabId:i[i.length-1]??r[0]?.id??null;t[e]={...n,tabs:r,tabHistory:i,activeTabId:a}}let r={...a,...t};u[n]=f.grid,d[n]=f.focusedPanelId,e({currentProject:n,panels:r,grid:f.grid,focusedPanelId:f.focusedPanelId,projectGrids:u,projectFocused:d})}else{let t=r(),i=[[t.id]],o={...a,[t.id]:t};u[n]=i,d[n]=t.id,p(n,{panels:{[t.id]:t},grid:i,focusedPanelId:t.id}),e({currentProject:n,panels:o,grid:i,focusedPanelId:t.id,projectGrids:u,projectFocused:d})}},reloadProject:n=>{let{projectGrids:r,projectFocused:i,panels:a}=t(),o={...r},s={...i};delete o[n],delete s[n];let c=r[n],l=c?new Set(c.flat()):new Set,u={...a};for(let e of l)delete u[e];e({projectGrids:o,projectFocused:s,panels:u,currentProject:null}),t().switchProject(n)},setFocusedPanel:n=>{t().panels[n]&&e({focusedPanelId:n})},openTab:(r,i)=>{let a=t().isMobile(),o=a?t().grid[0]?.[0]??l(i):l(i);if(!t().panels[o])return``;if(r.type===`terminal`&&!r.metadata?.terminalIndex){let e=Object.values(t().panels).flatMap(e=>e.tabs).filter(e=>e.type===`terminal`).map(e=>{let t=e.id.match(/^terminal:(\d+)/);return t?parseInt(t[1],10):0}),n=e.length>0?Math.max(...e)+1:1;r={...r,metadata:{...r.metadata,terminalIndex:n}}}let s=c(r.type,r.metadata);if(b.has(r.type))for(let r of Object.values(t().panels)){let t=r.tabs.find(e=>e.id===s);if(t)return e(e=>({focusedPanelId:r.id,panels:{...e.panels,[r.id]:{...r,activeTabId:t.id,tabHistory:S(r.tabHistory,t.id)}}})),n(),t.id}if(a)for(let r of t().grid.flat()){let i=t().panels[r];if(!i)continue;let a=i.tabs.find(e=>e.id===s||e.id.startsWith(`${s}@`));if(a)return e(e=>({focusedPanelId:i.id,panels:{...e.panels,[i.id]:{...i,activeTabId:a.id,tabHistory:S(i.tabHistory,a.id)}}})),n(),a.id}let u=t().panels[o],d=u.tabs.find(e=>e.id===s);if(d)return e(e=>({panels:{...e.panels,[o]:{...u,activeTabId:d.id,tabHistory:S(u.tabHistory,d.id)}}})),n(),d.id;let f=Object.values(t().panels).some(e=>e.id!==o&&e.tabs.some(e=>e.id===s))?`${s}@${o}`:s,p={...r,id:f};return e(e=>{let t=e.panels[o];return{focusedPanelId:o,panels:{...e.panels,[o]:{...t,tabs:[...t.tabs,p],activeTabId:f,tabHistory:S(t.tabHistory,f)}}}}),n(),f},closeTab:(r,i)=>{let o=i?t().panels[i]:s(r);if(!o)return;let c=o.id;e(e=>{let t=e.panels[c],n=t.tabs.filter(e=>e.id!==r),i=t.tabHistory.filter(e=>e!==r),o=t.activeTabId;if(t.activeTabId===r){let e=i.length>0?i[i.length-1]:null;o=e&&n.some(t=>t.id===e)?e:n[n.length-1]?.id??null}let s=e.grid.flat().length;if(n.length===0&&s>1){let{[c]:t,...n}=e.panels;return{panels:n,grid:a(e.grid,c),focusedPanelId:e.focusedPanelId===c?Object.keys(n)[0]:e.focusedPanelId}}return{panels:{...e.panels,[c]:{...t,tabs:n,activeTabId:o,tabHistory:i}}}}),n()},setActiveTab:(r,i)=>{let a=i?t().panels[i]:s(r);if(!a)return;let o=a.id;e(e=>{let t=e.panels[o];return{focusedPanelId:o,panels:{...e.panels,[o]:{...t,activeTabId:r,tabHistory:S(t.tabHistory,r)}}}}),n()},updateTab:(t,r)=>{let i=s(t);i&&(e(e=>({panels:{...e.panels,[i.id]:{...i,tabs:i.tabs.map(e=>e.id===t?{...e,...r}:e)}}})),n())},reorderTab:(r,i,a)=>{let o=t().panels[i];if(!o)return;let s=o.tabs.findIndex(e=>e.id===r);if(s===-1||s===a)return;let c=[...o.tabs],[l]=c.splice(s,1);c.splice(a,0,l),e(e=>({panels:{...e.panels,[i]:{...o,tabs:c}}})),n()},moveTab:(r,i,o,s)=>{if(i===o)return;let c=t().panels[i],l=t().panels[o];if(!c||!l)return;let u=c.tabs.find(e=>e.id===r);if(!u)return;let d=c.tabs.filter(e=>e.id!==r),f=c.tabHistory.filter(e=>e!==r),p=c.activeTabId===r?f[f.length-1]??d[d.length-1]?.id??null:c.activeTabId,m=[...l.tabs];s===void 0?m.push(u):m.splice(s,0,u),e(e=>{let t=e.grid.flat().length;if(d.length===0&&t>1){let{[i]:t,...n}=e.panels;return{panels:{...n,[o]:{...l,tabs:m,activeTabId:r,tabHistory:S(l.tabHistory,r)}},grid:a(e.grid,i),focusedPanelId:o}}return{focusedPanelId:o,panels:{...e.panels,[i]:{...c,tabs:d,activeTabId:p,tabHistory:f},[o]:{...l,tabs:m,activeTabId:r,tabHistory:S(l.tabHistory,r)}}}}),n()},splitPanel:(s,c,l,u)=>{let{grid:d,panels:f}=t(),p=t().isMobile(),m=f[l];if(!m)return!1;let h=m.tabs.find(e=>e.id===c);if(!h)return!1;let g=o(d,u??l);if(!g)return!1;let _=s===`left`||s===`right`,v=s===`up`||s===`down`;if(_&&(d[g.row]?.length??0)>=i(p)||v&&d.length>=3)return!1;let y=r([h],h.id);y.tabHistory=[h.id];let b=m.tabs.filter(e=>e.id!==c),x=m.tabHistory.filter(e=>e!==c),S=m.activeTabId===c?x[x.length-1]??b[b.length-1]?.id??null:m.activeTabId,C;if(_)C=d.map((e,t)=>{if(t!==g.row)return e;let n=[...e],r=s===`right`?g.col+1:g.col;return n.splice(r,0,y.id),n});else{C=[...d];let e=s===`down`?g.row+1:g.row;C.splice(e,0,[y.id])}return e(e=>{let t=e.grid.flat().length,n={...e.panels,[y.id]:y};if(b.length===0&&t>1){let{[l]:e,...t}=n;n=t,C=a(C,l)}else n[l]={...m,tabs:b,activeTabId:S,tabHistory:x};return{panels:n,grid:C,focusedPanelId:y.id}}),n(),!0},closePanel:r=>{let{panels:i,grid:s}=t();if(s.flat().length<=1)return;let c=i[r];if(!c)return;o(s,r);let l=s.flat(),u=l.indexOf(r),d=u>0?l[u-1]:l[1],f=i[d];f&&(e(e=>{let{[r]:t,...n}=e.panels,i=[...f.tabs,...c.tabs],o=f.activeTabId??c.activeTabId;return{panels:{...n,[d]:{...f,tabs:i,activeTabId:o,tabHistory:[...f.tabHistory,...c.tabHistory]}},grid:a(e.grid,r),focusedPanelId:d}}),n())},getPanelForTab:e=>s(e),isMobile:()=>typeof window<`u`&&window.innerWidth<768}});export{s as a,o as i,r as n,i as o,h as r,v as s,w as t};
|
|
2
|
-
//# sourceMappingURL=panel-store-
|
|
1
|
+
import{t as e}from"./react-DMIOAtcX.js";import{r as t}from"./utils-CQux7CsO.js";function n(){return`panel-${t()}`}function r(e=[],t=null){return{id:n(),tabs:e,activeTabId:t,tabHistory:t?[t]:[]}}function i(e){return e?1:3}function a(e,t){return e.map(e=>e.filter(e=>e!==t)).filter(e=>e.length>0)}function o(e,t){for(let n=0;n<e.length;n++){let r=e[n].indexOf(t);if(r!==-1)return{row:n,col:r}}return null}function s(e){let t=0;for(let n of Object.values(e))for(let e of n.tabs){let n=e.id.match(/^editor:untitled-(\d+)$/);n&&(t=Math.max(t,Number(n[1])))}return t+1}function c(e,n){switch(e){case`editor`:return n?.viewerKey?`editor:viewer:${n.viewerKey}`:n?.isUntitled?`editor:untitled-${n.untitledNumber??1}`:`editor:${n?.filePath??`untitled`}`;case`chat`:return`chat:${n?.providerId??`default`}/${n?.sessionId??t()}`;case`terminal`:return`terminal:${n?.terminalIndex??1}`;case`database`:return`database:${n?.connectionId??`default`}:${n?.tableName??``}`;case`sqlite`:return`sqlite:${n?.filePath??`default`}`;case`postgres`:return`postgres:${n?.connectionId??`default`}:${n?.tableName??``}`;case`extension`:return`extension:${String(n?.viewType??`unknown`).replace(/\.view$/,``)}`;case`git-diff`:return`git-diff:${n?.filePath??`unknown`}`;case`conflict-editor`:return`conflict-editor:${n?.filePath??`unknown`}`;case`settings`:return`settings`;case`ports`:return`ports`;default:return`${e}:${t()}`}}function l(e){let t={...e,panels:{...e.panels}};for(let[e,n]of Object.entries(t.panels)){let r=n.tabs.map(e=>{if(e.id.startsWith(`tab-`)){let t=c(e.type,e.metadata);return{...e,id:t}}return e}),i=new Map;n.tabs.forEach((e,t)=>{e.id!==r[t].id&&i.set(e.id,r[t].id)});let a=i.get(n.activeTabId??``)??n.activeTabId,o=n.tabHistory.map(e=>i.get(e)??e);t.panels[e]={...n,tabs:r,activeTabId:a,tabHistory:o}}return t}var u=`ppm-panels-`,d=`ppm-tabs-`;function f(e){return`${u}${e}`}function p(e,t){try{let n={...t,updatedAt:new Date().toISOString()};localStorage.setItem(f(e),JSON.stringify(n)),e!==`__global__`&&_(e,t)}catch{}}function m(e){try{let t=localStorage.getItem(f(e));if(t)return l(JSON.parse(t))}catch{}return y(e)}async function h(e){if(e===`__global__`)return null;try{let t={},n=localStorage.getItem(`ppm-auth-token`);n&&(t.Authorization=`Bearer ${n}`);let r=await fetch(`/api/project/${encodeURIComponent(e)}/workspace`,{headers:t});if(!r.ok)return null;let i=await r.json();return!i.ok||!i.data?null:{...i.data.layout,updatedAt:i.data.updatedAt}}catch{return null}}var g=new Map;function _(e,t){let n=g.get(e);n&&clearTimeout(n),g.set(e,setTimeout(async()=>{g.delete(e);try{let n={"Content-Type":`application/json`},r=localStorage.getItem(`ppm-auth-token`);r&&(n.Authorization=`Bearer ${r}`);let i=await fetch(`/api/project/${encodeURIComponent(e)}/workspace`,{method:`PUT`,headers:n,body:JSON.stringify({layout:t})});if(i.ok){let t=await i.json();if(t.data?.updatedAt){let n=`${u}${e}`,r=localStorage.getItem(n);if(r){let e=JSON.parse(r);e.updatedAt=t.data.updatedAt,localStorage.setItem(n,JSON.stringify(e))}}}}catch{}},1500))}function v(e,t){if(!e&&!t)return null;if(!e)return t;if(!t)return e;let n=new Date(e.updatedAt).getTime();return new Date(t.updatedAt).getTime()>=n?t:e}function y(e){try{let t=localStorage.getItem(`${d}${e}`);if(!t)return null;let n=JSON.parse(t);if(!n.tabs?.length)return null;let i=r(n.tabs,n.activeTabId),a={panels:{[i.id]:i},grid:[[i.id]],focusedPanelId:i.id};return p(e,a),localStorage.removeItem(`${d}${e}`),a}catch{return null}}var b=new Set([`settings`]),x=new Set([`projects`,`git-status`,`git-graph`]);function S(e,t){let n=e.filter(e=>e!==t);return n.push(t),n.length>50&&n.shift(),n}function C(){let e=r();return{panels:{[e.id]:e},grid:[[e.id]],focusedPanelId:e.id}}var w=e()((e,t)=>{function n(){let{currentProject:e,panels:n,grid:r,focusedPanelId:i}=t();if(!e)return;let a=new Set(r.flat()),o={};for(let[e,t]of Object.entries(n))a.has(e)&&(o[e]=t);p(e,{panels:o,grid:r,focusedPanelId:i})}function s(e){return Object.values(t().panels).find(t=>t.tabs.some(t=>t.id===e))}function l(e){return e??t().focusedPanelId}return{...C(),currentProject:null,projectGrids:{},projectFocused:{},switchProject:n=>{let{currentProject:i,panels:a,grid:o,focusedPanelId:s,projectGrids:c,projectFocused:l}=t();if(i===n)return;let u={...c},d={...l};if(i){u[i]=o,d[i]=s;let e=new Set(o.flat()),t={};for(let[n,r]of Object.entries(a))e.has(n)&&(t[n]=r);p(i,{panels:t,grid:o,focusedPanelId:s})}if(u[n]){let t=u[n];e({currentProject:n,grid:t,focusedPanelId:d[n]??t[0]?.[0]??``,projectGrids:u,projectFocused:d});return}let f=m(n);if(f&&Object.keys(f.panels).length>0){let t={};for(let[e,n]of Object.entries(f.panels)){let r=n.tabs.filter(e=>!x.has(e.type)),i=n.tabHistory.filter(e=>r.some(t=>t.id===e)),a=n.activeTabId&&r.some(e=>e.id===n.activeTabId)?n.activeTabId:i[i.length-1]??r[0]?.id??null;t[e]={...n,tabs:r,tabHistory:i,activeTabId:a}}let r={...a,...t};u[n]=f.grid,d[n]=f.focusedPanelId,e({currentProject:n,panels:r,grid:f.grid,focusedPanelId:f.focusedPanelId,projectGrids:u,projectFocused:d})}else{let t=r(),i=[[t.id]],o={...a,[t.id]:t};u[n]=i,d[n]=t.id,p(n,{panels:{[t.id]:t},grid:i,focusedPanelId:t.id}),e({currentProject:n,panels:o,grid:i,focusedPanelId:t.id,projectGrids:u,projectFocused:d})}},reloadProject:n=>{let{projectGrids:r,projectFocused:i,panels:a}=t(),o={...r},s={...i};delete o[n],delete s[n];let c=r[n],l=c?new Set(c.flat()):new Set,u={...a};for(let e of l)delete u[e];e({projectGrids:o,projectFocused:s,panels:u,currentProject:null}),t().switchProject(n)},setFocusedPanel:n=>{t().panels[n]&&e({focusedPanelId:n})},openTab:(r,i)=>{let a=t().isMobile(),o=a?t().grid[0]?.[0]??l(i):l(i);if(!t().panels[o])return``;if(r.type===`terminal`&&!r.metadata?.terminalIndex){let e=Object.values(t().panels).flatMap(e=>e.tabs).filter(e=>e.type===`terminal`).map(e=>{let t=e.id.match(/^terminal:(\d+)/);return t?parseInt(t[1],10):0}),n=e.length>0?Math.max(...e)+1:1;r={...r,metadata:{...r.metadata,terminalIndex:n}}}let s=c(r.type,r.metadata);if(b.has(r.type))for(let r of Object.values(t().panels)){let t=r.tabs.find(e=>e.id===s);if(t)return e(e=>({focusedPanelId:r.id,panels:{...e.panels,[r.id]:{...r,activeTabId:t.id,tabHistory:S(r.tabHistory,t.id)}}})),n(),t.id}if(a)for(let r of t().grid.flat()){let i=t().panels[r];if(!i)continue;let a=i.tabs.find(e=>e.id===s||e.id.startsWith(`${s}@`));if(a)return e(e=>({focusedPanelId:i.id,panels:{...e.panels,[i.id]:{...i,activeTabId:a.id,tabHistory:S(i.tabHistory,a.id)}}})),n(),a.id}let u=t().panels[o],d=u.tabs.find(e=>e.id===s);if(d)return e(e=>({panels:{...e.panels,[o]:{...u,activeTabId:d.id,tabHistory:S(u.tabHistory,d.id)}}})),n(),d.id;let f=Object.values(t().panels).some(e=>e.id!==o&&e.tabs.some(e=>e.id===s))?`${s}@${o}`:s,p={...r,id:f};return e(e=>{let t=e.panels[o];return{focusedPanelId:o,panels:{...e.panels,[o]:{...t,tabs:[...t.tabs,p],activeTabId:f,tabHistory:S(t.tabHistory,f)}}}}),n(),f},closeTab:(r,i)=>{let o=i?t().panels[i]:s(r);if(!o)return;let c=o.id;if(r.startsWith(`terminal:`))try{localStorage.removeItem(`ppm:terminal-session:${r}`)}catch{}e(e=>{let t=e.panels[c],n=t.tabs.filter(e=>e.id!==r),i=t.tabHistory.filter(e=>e!==r),o=t.activeTabId;if(t.activeTabId===r){let e=i.length>0?i[i.length-1]:null;o=e&&n.some(t=>t.id===e)?e:n[n.length-1]?.id??null}let s=e.grid.flat().length;if(n.length===0&&s>1){let{[c]:t,...n}=e.panels;return{panels:n,grid:a(e.grid,c),focusedPanelId:e.focusedPanelId===c?Object.keys(n)[0]:e.focusedPanelId}}return{panels:{...e.panels,[c]:{...t,tabs:n,activeTabId:o,tabHistory:i}}}}),n()},setActiveTab:(r,i)=>{let a=i?t().panels[i]:s(r);if(!a)return;let o=a.id;e(e=>{let t=e.panels[o];return{focusedPanelId:o,panels:{...e.panels,[o]:{...t,activeTabId:r,tabHistory:S(t.tabHistory,r)}}}}),n()},updateTab:(t,r)=>{let i=s(t);i&&(e(e=>({panels:{...e.panels,[i.id]:{...i,tabs:i.tabs.map(e=>e.id===t?{...e,...r}:e)}}})),n())},reorderTab:(r,i,a)=>{let o=t().panels[i];if(!o)return;let s=o.tabs.findIndex(e=>e.id===r);if(s===-1||s===a)return;let c=[...o.tabs],[l]=c.splice(s,1);c.splice(a,0,l),e(e=>({panels:{...e.panels,[i]:{...o,tabs:c}}})),n()},moveTab:(r,i,o,s)=>{if(i===o)return;let c=t().panels[i],l=t().panels[o];if(!c||!l)return;let u=c.tabs.find(e=>e.id===r);if(!u)return;let d=c.tabs.filter(e=>e.id!==r),f=c.tabHistory.filter(e=>e!==r),p=c.activeTabId===r?f[f.length-1]??d[d.length-1]?.id??null:c.activeTabId,m=[...l.tabs];s===void 0?m.push(u):m.splice(s,0,u),e(e=>{let t=e.grid.flat().length;if(d.length===0&&t>1){let{[i]:t,...n}=e.panels;return{panels:{...n,[o]:{...l,tabs:m,activeTabId:r,tabHistory:S(l.tabHistory,r)}},grid:a(e.grid,i),focusedPanelId:o}}return{focusedPanelId:o,panels:{...e.panels,[i]:{...c,tabs:d,activeTabId:p,tabHistory:f},[o]:{...l,tabs:m,activeTabId:r,tabHistory:S(l.tabHistory,r)}}}}),n()},splitPanel:(s,c,l,u)=>{let{grid:d,panels:f}=t(),p=t().isMobile(),m=f[l];if(!m)return!1;let h=m.tabs.find(e=>e.id===c);if(!h)return!1;let g=o(d,u??l);if(!g)return!1;let _=s===`left`||s===`right`,v=s===`up`||s===`down`;if(_&&(d[g.row]?.length??0)>=i(p)||v&&d.length>=3)return!1;let y=r([h],h.id);y.tabHistory=[h.id];let b=m.tabs.filter(e=>e.id!==c),x=m.tabHistory.filter(e=>e!==c),S=m.activeTabId===c?x[x.length-1]??b[b.length-1]?.id??null:m.activeTabId,C;if(_)C=d.map((e,t)=>{if(t!==g.row)return e;let n=[...e],r=s===`right`?g.col+1:g.col;return n.splice(r,0,y.id),n});else{C=[...d];let e=s===`down`?g.row+1:g.row;C.splice(e,0,[y.id])}return e(e=>{let t=e.grid.flat().length,n={...e.panels,[y.id]:y};if(b.length===0&&t>1){let{[l]:e,...t}=n;n=t,C=a(C,l)}else n[l]={...m,tabs:b,activeTabId:S,tabHistory:x};return{panels:n,grid:C,focusedPanelId:y.id}}),n(),!0},closePanel:r=>{let{panels:i,grid:s}=t();if(s.flat().length<=1)return;let c=i[r];if(!c)return;o(s,r);let l=s.flat(),u=l.indexOf(r),d=u>0?l[u-1]:l[1],f=i[d];f&&(e(e=>{let{[r]:t,...n}=e.panels,i=[...f.tabs,...c.tabs],o=f.activeTabId??c.activeTabId;return{panels:{...n,[d]:{...f,tabs:i,activeTabId:o,tabHistory:[...f.tabHistory,...c.tabHistory]}},grid:a(e.grid,r),focusedPanelId:d}}),n())},getPanelForTab:e=>s(e),isMobile:()=>typeof window<`u`&&window.innerWidth<768}});export{s as a,o as i,r as n,i as o,h as r,v as s,w as t};
|
|
2
|
+
//# sourceMappingURL=panel-store-C8wwxBpn.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"panel-store-Dy8-7E_g.js","names":[],"sources":["../../../src/web/stores/panel-utils.ts","../../../src/web/stores/panel-store.ts"],"sourcesContent":["import { randomId } from \"@/lib/utils\";\nimport type { Tab, TabType } from \"./tab-store\";\n\n// ---------------------------------------------------------------------------\n// Panel types\n// ---------------------------------------------------------------------------\nexport interface Panel {\n id: string;\n tabs: Tab[];\n activeTabId: string | null;\n tabHistory: string[];\n}\n\nexport interface PanelLayout {\n panels: Record<string, Panel>;\n /** grid[row][col] = panelId */\n grid: string[][];\n focusedPanelId: string;\n}\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\nexport function generatePanelId(): string {\n return `panel-${randomId()}`;\n}\n\nexport function createPanel(tabs: Tab[] = [], activeTabId: string | null = null): Panel {\n return {\n id: generatePanelId(),\n tabs,\n activeTabId,\n tabHistory: activeTabId ? [activeTabId] : [],\n };\n}\n\n/** Max columns: 3 desktop, 1 mobile */\nexport function maxColumns(isMobile: boolean): number {\n return isMobile ? 1 : 3;\n}\n\n/** Max rows in the grid */\nexport const MAX_ROWS = 3;\n\n// ---------------------------------------------------------------------------\n// Grid manipulation\n// ---------------------------------------------------------------------------\n/** Add a new row to the grid (outer array) */\nexport function gridAddRow(grid: string[][], panelId: string): string[][] {\n return [...grid, [panelId]];\n}\n\n/** Add a column within an existing row (inner array) */\nexport function gridAddColumn(grid: string[][], rowIndex: number, panelId: string): string[][] {\n return grid.map((row, i) => (i === rowIndex ? [...row, panelId] : row));\n}\n\nexport function gridRemovePanel(grid: string[][], panelId: string): string[][] {\n return grid\n .map((col) => col.filter((id) => id !== panelId))\n .filter((col) => col.length > 0);\n}\n\nexport function findPanelPosition(grid: string[][], panelId: string): { row: number; col: number } | null {\n for (let r = 0; r < grid.length; r++) {\n const c = grid[r]!.indexOf(panelId);\n if (c !== -1) return { row: r, col: c };\n }\n return null;\n}\n\n// ---------------------------------------------------------------------------\n// Deterministic tab IDs\n// ---------------------------------------------------------------------------\n\n/** Get next untitled number by scanning all panels */\nexport function getNextUntitledNumber(panels: Record<string, Panel>): number {\n let max = 0;\n for (const panel of Object.values(panels)) {\n for (const tab of panel.tabs) {\n const match = tab.id.match(/^editor:untitled-(\\d+)$/);\n if (match) max = Math.max(max, Number(match[1]));\n }\n }\n return max + 1;\n}\n\n/** Derive deterministic tab ID from type + metadata */\nexport function deriveTabId(type: TabType, metadata?: Record<string, unknown>): string {\n switch (type) {\n case \"editor\":\n if (metadata?.viewerKey) return `editor:viewer:${metadata.viewerKey}`;\n if (metadata?.isUntitled) return `editor:untitled-${metadata.untitledNumber ?? 1}`;\n return `editor:${metadata?.filePath ?? \"untitled\"}`;\n case \"chat\": {\n const provider = metadata?.providerId ?? \"default\";\n return `chat:${provider}/${metadata?.sessionId ?? randomId()}`;\n }\n case \"terminal\":\n return `terminal:${metadata?.terminalIndex ?? 1}`;\n case \"database\":\n return `database:${metadata?.connectionId ?? \"default\"}:${metadata?.tableName ?? \"\"}`;\n case \"sqlite\":\n return `sqlite:${metadata?.filePath ?? \"default\"}`;\n case \"postgres\":\n return `postgres:${metadata?.connectionId ?? \"default\"}:${metadata?.tableName ?? \"\"}`;\n case \"extension\": {\n const vt = String(metadata?.viewType ?? \"unknown\").replace(/\\.view$/, \"\");\n return `extension:${vt}`;\n }\n case \"git-diff\":\n return `git-diff:${metadata?.filePath ?? \"unknown\"}`;\n case \"conflict-editor\":\n return `conflict-editor:${metadata?.filePath ?? \"unknown\"}`;\n case \"settings\":\n return \"settings\";\n case \"ports\":\n return \"ports\";\n default:\n return `${type}:${randomId()}`;\n }\n}\n\n/** Migrate old random tab IDs to deterministic IDs */\nexport function migrateTabIds(layout: PanelLayout): PanelLayout {\n const migrated = { ...layout, panels: { ...layout.panels } };\n for (const [panelId, panel] of Object.entries(migrated.panels)) {\n const newTabs = panel.tabs.map((tab) => {\n if (tab.id.startsWith(\"tab-\")) {\n const newId = deriveTabId(tab.type, tab.metadata);\n return { ...tab, id: newId };\n }\n return tab;\n });\n const idMap = new Map<string, string>();\n panel.tabs.forEach((old, i) => {\n if (old.id !== newTabs[i]!.id) idMap.set(old.id, newTabs[i]!.id);\n });\n const newActive = idMap.get(panel.activeTabId ?? \"\") ?? panel.activeTabId;\n const newHistory = panel.tabHistory.map((h) => idMap.get(h) ?? h);\n migrated.panels[panelId] = { ...panel, tabs: newTabs, activeTabId: newActive, tabHistory: newHistory };\n }\n return migrated;\n}\n\n// ---------------------------------------------------------------------------\n// Persistence\n// ---------------------------------------------------------------------------\nconst STORAGE_PREFIX = \"ppm-panels-\";\nconst OLD_STORAGE_PREFIX = \"ppm-tabs-\";\n\nfunction storageKey(projectName: string): string {\n return `${STORAGE_PREFIX}${projectName}`;\n}\n\nexport function savePanelLayout(projectName: string, layout: PanelLayout): void {\n try {\n const withTimestamp = { ...layout, updatedAt: new Date().toISOString() };\n localStorage.setItem(storageKey(projectName), JSON.stringify(withTimestamp));\n // Debounced server sync — skip virtual __global__ project (not a real server project)\n if (projectName !== \"__global__\") syncWorkspaceToServer(projectName, layout);\n } catch { /* ignore */ }\n}\n\nexport function loadPanelLayout(projectName: string): PanelLayout | null {\n try {\n const raw = localStorage.getItem(storageKey(projectName));\n if (raw) {\n const layout = JSON.parse(raw) as PanelLayout;\n return migrateTabIds(layout);\n }\n } catch { /* ignore */ }\n\n // Migrate from old tab-store format\n return migrateOldTabStore(projectName);\n}\n\n// ---------------------------------------------------------------------------\n// Server sync\n// ---------------------------------------------------------------------------\n\nexport interface PanelLayoutWithTimestamp extends PanelLayout {\n updatedAt: string;\n}\n\n/** Fetch workspace from server */\nexport async function fetchWorkspaceFromServer(\n projectName: string,\n): Promise<PanelLayoutWithTimestamp | null> {\n if (projectName === \"__global__\") return null;\n try {\n const headers: Record<string, string> = {};\n const token = localStorage.getItem(\"ppm-auth-token\");\n if (token) headers[\"Authorization\"] = `Bearer ${token}`;\n\n const res = await fetch(`/api/project/${encodeURIComponent(projectName)}/workspace`, { headers });\n if (!res.ok) return null;\n const json = await res.json();\n if (!json.ok || !json.data) return null;\n return { ...json.data.layout, updatedAt: json.data.updatedAt };\n } catch {\n return null;\n }\n}\n\n/** Save workspace to server (debounced per project) */\nconst syncTimers = new Map<string, ReturnType<typeof setTimeout>>();\n\nfunction syncWorkspaceToServer(projectName: string, layout: PanelLayout): void {\n const existing = syncTimers.get(projectName);\n if (existing) clearTimeout(existing);\n\n syncTimers.set(projectName, setTimeout(async () => {\n syncTimers.delete(projectName);\n try {\n const headers: Record<string, string> = { \"Content-Type\": \"application/json\" };\n const token = localStorage.getItem(\"ppm-auth-token\");\n if (token) headers[\"Authorization\"] = `Bearer ${token}`;\n\n const res = await fetch(`/api/project/${encodeURIComponent(projectName)}/workspace`, {\n method: \"PUT\",\n headers,\n body: JSON.stringify({ layout }),\n });\n if (res.ok) {\n const json = await res.json();\n if (json.data?.updatedAt) {\n const key = `${STORAGE_PREFIX}${projectName}`;\n const raw = localStorage.getItem(key);\n if (raw) {\n const local = JSON.parse(raw);\n local.updatedAt = json.data.updatedAt;\n localStorage.setItem(key, JSON.stringify(local));\n }\n }\n }\n } catch { /* silent fail — localStorage is still the cache */ }\n }, 1500));\n}\n\n/** Compare local vs server timestamps, return newer layout */\nexport function resolveWorkspaceConflict(\n local: PanelLayoutWithTimestamp | null,\n server: PanelLayoutWithTimestamp | null,\n): PanelLayoutWithTimestamp | null {\n if (!local && !server) return null;\n if (!local) return server!;\n if (!server) return local;\n\n const localTime = new Date(local.updatedAt).getTime();\n const serverTime = new Date(server.updatedAt).getTime();\n return serverTime >= localTime ? server : local;\n}\n\n// ---------------------------------------------------------------------------\n// Old format migration\n// ---------------------------------------------------------------------------\n\nfunction migrateOldTabStore(projectName: string): PanelLayout | null {\n try {\n const raw = localStorage.getItem(`${OLD_STORAGE_PREFIX}${projectName}`);\n if (!raw) return null;\n const old = JSON.parse(raw) as { tabs: Tab[]; activeTabId: string | null };\n if (!old.tabs?.length) return null;\n\n const panel = createPanel(old.tabs, old.activeTabId);\n const layout: PanelLayout = {\n panels: { [panel.id]: panel },\n grid: [[panel.id]],\n focusedPanelId: panel.id,\n };\n // Save new format and clean old\n savePanelLayout(projectName, layout);\n localStorage.removeItem(`${OLD_STORAGE_PREFIX}${projectName}`);\n return layout;\n } catch {\n return null;\n }\n}\n","import { create } from \"zustand\";\nimport type { Tab, TabType } from \"./tab-store\";\nimport {\n type Panel,\n type PanelLayout,\n createPanel,\n gridAddColumn,\n gridAddRow,\n gridRemovePanel,\n findPanelPosition,\n maxColumns,\n MAX_ROWS,\n savePanelLayout,\n loadPanelLayout,\n deriveTabId,\n} from \"./panel-utils\";\n\n/** Tab types that can only have 1 instance per project */\nconst SINGLETON_TYPES = new Set<TabType>([\"settings\"]);\n\n/** Tab types removed in a prior version — filter them out when loading persisted state */\nconst OBSOLETE_TAB_TYPES = new Set([\"projects\", \"git-status\", \"git-graph\"]);\n\nfunction pushHistory(history: string[], id: string): string[] {\n const filtered = history.filter((h) => h !== id);\n filtered.push(id);\n if (filtered.length > 50) filtered.shift();\n return filtered;\n}\n\n// ---------------------------------------------------------------------------\n// Store interface\n// ---------------------------------------------------------------------------\nexport interface PanelStore {\n panels: Record<string, Panel>;\n grid: string[][];\n focusedPanelId: string;\n currentProject: string | null;\n\n /** Keep-alive: per-project grid snapshots (for hidden workspaces) */\n projectGrids: Record<string, string[][]>;\n projectFocused: Record<string, string>;\n\n // Project lifecycle\n switchProject: (projectName: string) => void;\n reloadProject: (projectName: string) => void;\n\n // Panel focus\n setFocusedPanel: (panelId: string) => void;\n\n // Tab operations (operate on focused panel by default)\n openTab: (tab: Omit<Tab, \"id\">, panelId?: string) => string;\n closeTab: (tabId: string, panelId?: string) => void;\n setActiveTab: (tabId: string, panelId?: string) => void;\n updateTab: (tabId: string, updates: Partial<Omit<Tab, \"id\">>) => void;\n\n // Panel operations\n reorderTab: (tabId: string, panelId: string, newIndex: number) => void;\n moveTab: (tabId: string, fromPanelId: string, toPanelId: string, insertIndex?: number) => void;\n splitPanel: (direction: \"left\" | \"right\" | \"up\" | \"down\", tabId: string, sourcePanelId: string, targetPanelId?: string) => boolean;\n closePanel: (panelId: string) => void;\n\n // Helpers\n getPanelForTab: (tabId: string) => Panel | undefined;\n isMobile: () => boolean;\n}\n\nfunction defaultLayout(): { panels: Record<string, Panel>; grid: string[][]; focusedPanelId: string } {\n const panel = createPanel();\n return { panels: { [panel.id]: panel }, grid: [[panel.id]], focusedPanelId: panel.id };\n}\n\n// ---------------------------------------------------------------------------\n// Store\n// ---------------------------------------------------------------------------\nexport const usePanelStore = create<PanelStore>()((set, get) => {\n /** Save only the active project's panels to localStorage */\n function persist() {\n const { currentProject, panels, grid, focusedPanelId } = get();\n if (!currentProject) return;\n const panelIds = new Set(grid.flat());\n const projectPanels: Record<string, Panel> = {};\n for (const [id, p] of Object.entries(panels)) {\n if (panelIds.has(id)) projectPanels[id] = p;\n }\n savePanelLayout(currentProject, { panels: projectPanels, grid, focusedPanelId });\n }\n\n function findPanel(tabId: string): Panel | undefined {\n return Object.values(get().panels).find((p) => p.tabs.some((t) => t.id === tabId));\n }\n\n function resolvePanel(panelId?: string): string {\n return panelId ?? get().focusedPanelId;\n }\n\n return {\n ...defaultLayout(),\n currentProject: null,\n projectGrids: {},\n projectFocused: {},\n\n switchProject: (projectName) => {\n const { currentProject, panels, grid, focusedPanelId, projectGrids, projectFocused } = get();\n\n // No-op if same project\n if (currentProject === projectName) return;\n\n // Snapshot current project's state\n const newProjectGrids = { ...projectGrids };\n const newProjectFocused = { ...projectFocused };\n\n if (currentProject) {\n newProjectGrids[currentProject] = grid;\n newProjectFocused[currentProject] = focusedPanelId;\n // Persist to localStorage\n const panelIds = new Set(grid.flat());\n const currentPanels: Record<string, Panel> = {};\n for (const [id, p] of Object.entries(panels)) {\n if (panelIds.has(id)) currentPanels[id] = p;\n }\n savePanelLayout(currentProject, { panels: currentPanels, grid, focusedPanelId });\n }\n\n // Already in memory → restore from snapshot (no localStorage read)\n if (newProjectGrids[projectName]) {\n const restoredGrid = newProjectGrids[projectName]!;\n const restoredFocused = newProjectFocused[projectName] ?? restoredGrid[0]?.[0] ?? \"\";\n set({\n currentProject: projectName,\n grid: restoredGrid,\n focusedPanelId: restoredFocused,\n projectGrids: newProjectGrids,\n projectFocused: newProjectFocused,\n });\n return;\n }\n\n // Load from localStorage\n const loaded = loadPanelLayout(projectName);\n if (loaded && Object.keys(loaded.panels).length > 0) {\n // Migrate: remove obsolete tab types\n const migratedPanels: typeof loaded.panels = {};\n for (const [pid, panel] of Object.entries(loaded.panels)) {\n const filteredTabs = panel.tabs.filter((t) => !OBSOLETE_TAB_TYPES.has(t.type));\n const filteredHistory = panel.tabHistory.filter(\n (id) => filteredTabs.some((t) => t.id === id),\n );\n const activeTabId = panel.activeTabId && filteredTabs.some((t) => t.id === panel.activeTabId)\n ? panel.activeTabId\n : (filteredHistory[filteredHistory.length - 1] ?? filteredTabs[0]?.id ?? null);\n migratedPanels[pid] = { ...panel, tabs: filteredTabs, tabHistory: filteredHistory, activeTabId };\n }\n\n // Merge into flat panels map (keep-alive: old panels stay)\n const mergedPanels = { ...panels, ...migratedPanels };\n newProjectGrids[projectName] = loaded.grid;\n newProjectFocused[projectName] = loaded.focusedPanelId;\n set({\n currentProject: projectName,\n panels: mergedPanels,\n grid: loaded.grid,\n focusedPanelId: loaded.focusedPanelId,\n projectGrids: newProjectGrids,\n projectFocused: newProjectFocused,\n });\n } else {\n // Create empty layout — EmptyPanel will show quick-open buttons\n const p = createPanel();\n const newGrid = [[p.id]];\n\n // Merge into flat panels map\n const mergedPanels = { ...panels, [p.id]: p };\n newProjectGrids[projectName] = newGrid;\n newProjectFocused[projectName] = p.id;\n savePanelLayout(projectName, { panels: { [p.id]: p }, grid: newGrid, focusedPanelId: p.id });\n set({\n currentProject: projectName,\n panels: mergedPanels,\n grid: newGrid,\n focusedPanelId: p.id,\n projectGrids: newProjectGrids,\n projectFocused: newProjectFocused,\n });\n }\n },\n\n reloadProject: (projectName) => {\n const { projectGrids, projectFocused, panels } = get();\n // Clear in-memory cache so switchProject re-reads from localStorage\n const newGrids = { ...projectGrids };\n const newFocused = { ...projectFocused };\n delete newGrids[projectName];\n delete newFocused[projectName];\n\n // Remove old panels belonging to this project from flat map\n const oldGrid = projectGrids[projectName];\n const oldPanelIds = oldGrid ? new Set(oldGrid.flat()) : new Set<string>();\n const cleanedPanels = { ...panels };\n for (const id of oldPanelIds) delete cleanedPanels[id];\n\n set({ projectGrids: newGrids, projectFocused: newFocused, panels: cleanedPanels, currentProject: null });\n // Re-trigger full load from localStorage\n get().switchProject(projectName);\n },\n\n setFocusedPanel: (panelId) => {\n if (get().panels[panelId]) set({ focusedPanelId: panelId });\n },\n\n openTab: (tabDef, panelId?) => {\n const mobile = get().isMobile();\n // On mobile, always open in first panel (tabs merged in mobile nav)\n const pid = mobile\n ? (get().grid[0]?.[0] ?? resolvePanel(panelId))\n : resolvePanel(panelId);\n const panel = get().panels[pid];\n if (!panel) return \"\";\n\n // Terminal: compute next available index if not provided\n if (tabDef.type === \"terminal\" && !tabDef.metadata?.terminalIndex) {\n const allTabs = Object.values(get().panels).flatMap((p) => p.tabs);\n const terminalNums = allTabs\n .filter((t) => t.type === \"terminal\")\n .map((t) => {\n const match = t.id.match(/^terminal:(\\d+)/);\n return match ? parseInt(match[1]!, 10) : 0;\n });\n const nextIndex = terminalNums.length > 0 ? Math.max(...terminalNums) + 1 : 1;\n tabDef = { ...tabDef, metadata: { ...tabDef.metadata, terminalIndex: nextIndex } };\n }\n\n const baseId = deriveTabId(tabDef.type, tabDef.metadata);\n\n // Singleton check — focus existing across ALL panels\n if (SINGLETON_TYPES.has(tabDef.type)) {\n for (const p of Object.values(get().panels)) {\n const existing = p.tabs.find((t) => t.id === baseId);\n if (existing) {\n set((s) => ({\n focusedPanelId: p.id,\n panels: {\n ...s.panels,\n [p.id]: {\n ...p,\n activeTabId: existing.id,\n tabHistory: pushHistory(p.tabHistory, existing.id),\n },\n },\n }));\n persist();\n return existing.id;\n }\n }\n }\n\n // Mobile: dedup across all panels (merged tab bar shows all tabs)\n if (mobile) {\n for (const gpid of get().grid.flat()) {\n const p = get().panels[gpid];\n if (!p) continue;\n const existing = p.tabs.find((t) => t.id === baseId || t.id.startsWith(`${baseId}@`));\n if (existing) {\n set((s) => ({\n focusedPanelId: p.id,\n panels: {\n ...s.panels,\n [p.id]: { ...p, activeTabId: existing.id, tabHistory: pushHistory(p.tabHistory, existing.id) },\n },\n }));\n persist();\n return existing.id;\n }\n }\n }\n\n // Non-singleton: dedup within SAME panel only\n const currentPanel = get().panels[pid]!;\n const existingInPanel = currentPanel.tabs.find((t) => t.id === baseId);\n if (existingInPanel) {\n set((s) => ({\n panels: {\n ...s.panels,\n [pid]: {\n ...currentPanel,\n activeTabId: existingInPanel.id,\n tabHistory: pushHistory(currentPanel.tabHistory, existingInPanel.id),\n },\n },\n }));\n persist();\n return existingInPanel.id;\n }\n\n // Check if same base ID exists in OTHER panels (split case)\n const existsElsewhere = Object.values(get().panels).some(\n (p) => p.id !== pid && p.tabs.some((t) => t.id === baseId),\n );\n const id = existsElsewhere ? `${baseId}@${pid}` : baseId;\n\n const tab: Tab = { ...tabDef, id };\n set((s) => {\n const p = s.panels[pid]!;\n return {\n focusedPanelId: pid,\n panels: {\n ...s.panels,\n [pid]: {\n ...p,\n tabs: [...p.tabs, tab],\n activeTabId: id,\n tabHistory: pushHistory(p.tabHistory, id),\n },\n },\n };\n });\n persist();\n return id;\n },\n\n closeTab: (tabId, panelId?) => {\n const panel = panelId ? get().panels[panelId] : findPanel(tabId);\n if (!panel) return;\n const pid = panel.id;\n\n set((s) => {\n const p = s.panels[pid]!;\n const newTabs = p.tabs.filter((t) => t.id !== tabId);\n const newHistory = p.tabHistory.filter((h) => h !== tabId);\n let newActive = p.activeTabId;\n if (p.activeTabId === tabId) {\n const prevId = newHistory.length > 0 ? newHistory[newHistory.length - 1] : null;\n newActive = prevId && newTabs.some((t) => t.id === prevId)\n ? prevId\n : newTabs[newTabs.length - 1]?.id ?? null;\n }\n\n // Auto-close panel if empty and not the last one in current grid\n const gridPanelCount = s.grid.flat().length;\n if (newTabs.length === 0 && gridPanelCount > 1) {\n const { [pid]: _, ...rest } = s.panels;\n const newGrid = gridRemovePanel(s.grid, pid);\n const newFocused = s.focusedPanelId === pid ? Object.keys(rest)[0]! : s.focusedPanelId;\n return { panels: rest, grid: newGrid, focusedPanelId: newFocused };\n }\n\n return {\n panels: { ...s.panels, [pid]: { ...p, tabs: newTabs, activeTabId: newActive, tabHistory: newHistory } },\n };\n });\n persist();\n },\n\n setActiveTab: (tabId, panelId?) => {\n const panel = panelId ? get().panels[panelId] : findPanel(tabId);\n if (!panel) return;\n const pid = panel.id;\n set((s) => {\n const p = s.panels[pid]!;\n return {\n focusedPanelId: pid,\n panels: { ...s.panels, [pid]: { ...p, activeTabId: tabId, tabHistory: pushHistory(p.tabHistory, tabId) } },\n };\n });\n persist();\n },\n\n updateTab: (tabId, updates) => {\n const panel = findPanel(tabId);\n if (!panel) return;\n set((s) => ({\n panels: {\n ...s.panels,\n [panel.id]: { ...panel, tabs: panel.tabs.map((t) => (t.id === tabId ? { ...t, ...updates } : t)) },\n },\n }));\n persist();\n },\n\n reorderTab: (tabId, panelId, newIndex) => {\n const panel = get().panels[panelId];\n if (!panel) return;\n const oldIndex = panel.tabs.findIndex((t) => t.id === tabId);\n if (oldIndex === -1 || oldIndex === newIndex) return;\n const newTabs = [...panel.tabs];\n const [moved] = newTabs.splice(oldIndex, 1);\n newTabs.splice(newIndex, 0, moved!);\n set((s) => ({ panels: { ...s.panels, [panelId]: { ...panel, tabs: newTabs } } }));\n persist();\n },\n\n moveTab: (tabId, fromPanelId, toPanelId, insertIndex?) => {\n if (fromPanelId === toPanelId) return;\n const from = get().panels[fromPanelId];\n const to = get().panels[toPanelId];\n if (!from || !to) return;\n\n const tab = from.tabs.find((t) => t.id === tabId);\n if (!tab) return;\n\n const fromTabs = from.tabs.filter((t) => t.id !== tabId);\n const fromHistory = from.tabHistory.filter((h) => h !== tabId);\n const fromActive = from.activeTabId === tabId\n ? (fromHistory[fromHistory.length - 1] ?? fromTabs[fromTabs.length - 1]?.id ?? null)\n : from.activeTabId;\n\n const toTabs = [...to.tabs];\n if (insertIndex !== undefined) toTabs.splice(insertIndex, 0, tab);\n else toTabs.push(tab);\n\n set((s) => {\n const gridPanelCount = s.grid.flat().length;\n // Auto-close empty source panel if not last in current grid\n if (fromTabs.length === 0 && gridPanelCount > 1) {\n const { [fromPanelId]: _, ...rest } = s.panels;\n return {\n panels: {\n ...rest,\n [toPanelId]: { ...to, tabs: toTabs, activeTabId: tabId, tabHistory: pushHistory(to.tabHistory, tabId) },\n },\n grid: gridRemovePanel(s.grid, fromPanelId),\n focusedPanelId: toPanelId,\n };\n }\n\n return {\n focusedPanelId: toPanelId,\n panels: {\n ...s.panels,\n [fromPanelId]: { ...from, tabs: fromTabs, activeTabId: fromActive, tabHistory: fromHistory },\n [toPanelId]: { ...to, tabs: toTabs, activeTabId: tabId, tabHistory: pushHistory(to.tabHistory, tabId) },\n },\n };\n });\n persist();\n },\n\n splitPanel: (direction, tabId, sourcePanelId, targetPanelId?) => {\n const { grid, panels } = get();\n const mobile = get().isMobile();\n const source = panels[sourcePanelId];\n if (!source) return false;\n\n const tab = source.tabs.find((t) => t.id === tabId);\n if (!tab) return false;\n\n // Use target panel's position for grid insertion (where the drop happened)\n const positionPanelId = targetPanelId ?? sourcePanelId;\n const pos = findPanelPosition(grid, positionPanelId);\n if (!pos) return false;\n\n // Check constraints — grid is row-major: grid[row][col]\n const isHorizontal = direction === \"left\" || direction === \"right\";\n const isVertical = direction === \"up\" || direction === \"down\";\n if (isHorizontal && (grid[pos.row]?.length ?? 0) >= maxColumns(mobile)) return false;\n if (isVertical && grid.length >= MAX_ROWS) return false;\n\n const newPanel = createPanel([tab], tab.id);\n newPanel.tabHistory = [tab.id];\n\n // Remove tab from source\n const srcTabs = source.tabs.filter((t) => t.id !== tabId);\n const srcHistory = source.tabHistory.filter((h) => h !== tabId);\n const srcActive = source.activeTabId === tabId\n ? (srcHistory[srcHistory.length - 1] ?? srcTabs[srcTabs.length - 1]?.id ?? null)\n : source.activeTabId;\n\n let newGrid: string[][];\n if (isHorizontal) {\n // Add column within the same row\n newGrid = grid.map((row, r) => {\n if (r !== pos.row) return row;\n const newRow = [...row];\n const insertCol = direction === \"right\" ? pos.col + 1 : pos.col;\n newRow.splice(insertCol, 0, newPanel.id);\n return newRow;\n });\n } else {\n // Add new row to the grid\n newGrid = [...grid];\n const insertRow = direction === \"down\" ? pos.row + 1 : pos.row;\n newGrid.splice(insertRow, 0, [newPanel.id]);\n }\n\n set((s) => {\n const gridPanelCount = s.grid.flat().length;\n let updatedPanels = {\n ...s.panels,\n [newPanel.id]: newPanel,\n };\n\n // If source is now empty and not last panel in grid, remove it\n if (srcTabs.length === 0 && gridPanelCount > 1) {\n const { [sourcePanelId]: _, ...rest } = updatedPanels;\n updatedPanels = rest;\n newGrid = gridRemovePanel(newGrid, sourcePanelId);\n } else {\n updatedPanels[sourcePanelId] = { ...source, tabs: srcTabs, activeTabId: srcActive, tabHistory: srcHistory };\n }\n\n return { panels: updatedPanels, grid: newGrid, focusedPanelId: newPanel.id };\n });\n persist();\n return true;\n },\n\n closePanel: (panelId) => {\n const { panels, grid } = get();\n if (grid.flat().length <= 1) return;\n\n const panel = panels[panelId];\n if (!panel) return;\n\n // Find neighbor to merge tabs into\n const pos = findPanelPosition(grid, panelId);\n const allIds = grid.flat();\n const idx = allIds.indexOf(panelId);\n const neighborId = idx > 0 ? allIds[idx - 1]! : allIds[1]!;\n const neighbor = panels[neighborId];\n if (!neighbor) return;\n\n set((s) => {\n const { [panelId]: _, ...rest } = s.panels;\n const mergedTabs = [...neighbor.tabs, ...panel.tabs];\n const mergedActive = neighbor.activeTabId ?? panel.activeTabId;\n return {\n panels: {\n ...rest,\n [neighborId]: { ...neighbor, tabs: mergedTabs, activeTabId: mergedActive, tabHistory: [...neighbor.tabHistory, ...panel.tabHistory] },\n },\n grid: gridRemovePanel(s.grid, panelId),\n focusedPanelId: neighborId,\n };\n });\n persist();\n },\n\n getPanelForTab: (tabId) => findPanel(tabId),\n\n isMobile: () => typeof window !== \"undefined\" && window.innerWidth < 768,\n };\n});\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"panel-store-C8wwxBpn.js","names":[],"sources":["../../../src/web/stores/panel-utils.ts","../../../src/web/stores/panel-store.ts"],"sourcesContent":["import { randomId } from \"@/lib/utils\";\nimport type { Tab, TabType } from \"./tab-store\";\n\n// ---------------------------------------------------------------------------\n// Panel types\n// ---------------------------------------------------------------------------\nexport interface Panel {\n id: string;\n tabs: Tab[];\n activeTabId: string | null;\n tabHistory: string[];\n}\n\nexport interface PanelLayout {\n panels: Record<string, Panel>;\n /** grid[row][col] = panelId */\n grid: string[][];\n focusedPanelId: string;\n}\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\nexport function generatePanelId(): string {\n return `panel-${randomId()}`;\n}\n\nexport function createPanel(tabs: Tab[] = [], activeTabId: string | null = null): Panel {\n return {\n id: generatePanelId(),\n tabs,\n activeTabId,\n tabHistory: activeTabId ? [activeTabId] : [],\n };\n}\n\n/** Max columns: 3 desktop, 1 mobile */\nexport function maxColumns(isMobile: boolean): number {\n return isMobile ? 1 : 3;\n}\n\n/** Max rows in the grid */\nexport const MAX_ROWS = 3;\n\n// ---------------------------------------------------------------------------\n// Grid manipulation\n// ---------------------------------------------------------------------------\n/** Add a new row to the grid (outer array) */\nexport function gridAddRow(grid: string[][], panelId: string): string[][] {\n return [...grid, [panelId]];\n}\n\n/** Add a column within an existing row (inner array) */\nexport function gridAddColumn(grid: string[][], rowIndex: number, panelId: string): string[][] {\n return grid.map((row, i) => (i === rowIndex ? [...row, panelId] : row));\n}\n\nexport function gridRemovePanel(grid: string[][], panelId: string): string[][] {\n return grid\n .map((col) => col.filter((id) => id !== panelId))\n .filter((col) => col.length > 0);\n}\n\nexport function findPanelPosition(grid: string[][], panelId: string): { row: number; col: number } | null {\n for (let r = 0; r < grid.length; r++) {\n const c = grid[r]!.indexOf(panelId);\n if (c !== -1) return { row: r, col: c };\n }\n return null;\n}\n\n// ---------------------------------------------------------------------------\n// Deterministic tab IDs\n// ---------------------------------------------------------------------------\n\n/** Get next untitled number by scanning all panels */\nexport function getNextUntitledNumber(panels: Record<string, Panel>): number {\n let max = 0;\n for (const panel of Object.values(panels)) {\n for (const tab of panel.tabs) {\n const match = tab.id.match(/^editor:untitled-(\\d+)$/);\n if (match) max = Math.max(max, Number(match[1]));\n }\n }\n return max + 1;\n}\n\n/** Derive deterministic tab ID from type + metadata */\nexport function deriveTabId(type: TabType, metadata?: Record<string, unknown>): string {\n switch (type) {\n case \"editor\":\n if (metadata?.viewerKey) return `editor:viewer:${metadata.viewerKey}`;\n if (metadata?.isUntitled) return `editor:untitled-${metadata.untitledNumber ?? 1}`;\n return `editor:${metadata?.filePath ?? \"untitled\"}`;\n case \"chat\": {\n const provider = metadata?.providerId ?? \"default\";\n return `chat:${provider}/${metadata?.sessionId ?? randomId()}`;\n }\n case \"terminal\":\n return `terminal:${metadata?.terminalIndex ?? 1}`;\n case \"database\":\n return `database:${metadata?.connectionId ?? \"default\"}:${metadata?.tableName ?? \"\"}`;\n case \"sqlite\":\n return `sqlite:${metadata?.filePath ?? \"default\"}`;\n case \"postgres\":\n return `postgres:${metadata?.connectionId ?? \"default\"}:${metadata?.tableName ?? \"\"}`;\n case \"extension\": {\n const vt = String(metadata?.viewType ?? \"unknown\").replace(/\\.view$/, \"\");\n return `extension:${vt}`;\n }\n case \"git-diff\":\n return `git-diff:${metadata?.filePath ?? \"unknown\"}`;\n case \"conflict-editor\":\n return `conflict-editor:${metadata?.filePath ?? \"unknown\"}`;\n case \"settings\":\n return \"settings\";\n case \"ports\":\n return \"ports\";\n default:\n return `${type}:${randomId()}`;\n }\n}\n\n/** Migrate old random tab IDs to deterministic IDs */\nexport function migrateTabIds(layout: PanelLayout): PanelLayout {\n const migrated = { ...layout, panels: { ...layout.panels } };\n for (const [panelId, panel] of Object.entries(migrated.panels)) {\n const newTabs = panel.tabs.map((tab) => {\n if (tab.id.startsWith(\"tab-\")) {\n const newId = deriveTabId(tab.type, tab.metadata);\n return { ...tab, id: newId };\n }\n return tab;\n });\n const idMap = new Map<string, string>();\n panel.tabs.forEach((old, i) => {\n if (old.id !== newTabs[i]!.id) idMap.set(old.id, newTabs[i]!.id);\n });\n const newActive = idMap.get(panel.activeTabId ?? \"\") ?? panel.activeTabId;\n const newHistory = panel.tabHistory.map((h) => idMap.get(h) ?? h);\n migrated.panels[panelId] = { ...panel, tabs: newTabs, activeTabId: newActive, tabHistory: newHistory };\n }\n return migrated;\n}\n\n// ---------------------------------------------------------------------------\n// Persistence\n// ---------------------------------------------------------------------------\nconst STORAGE_PREFIX = \"ppm-panels-\";\nconst OLD_STORAGE_PREFIX = \"ppm-tabs-\";\n\nfunction storageKey(projectName: string): string {\n return `${STORAGE_PREFIX}${projectName}`;\n}\n\nexport function savePanelLayout(projectName: string, layout: PanelLayout): void {\n try {\n const withTimestamp = { ...layout, updatedAt: new Date().toISOString() };\n localStorage.setItem(storageKey(projectName), JSON.stringify(withTimestamp));\n // Debounced server sync — skip virtual __global__ project (not a real server project)\n if (projectName !== \"__global__\") syncWorkspaceToServer(projectName, layout);\n } catch { /* ignore */ }\n}\n\nexport function loadPanelLayout(projectName: string): PanelLayout | null {\n try {\n const raw = localStorage.getItem(storageKey(projectName));\n if (raw) {\n const layout = JSON.parse(raw) as PanelLayout;\n return migrateTabIds(layout);\n }\n } catch { /* ignore */ }\n\n // Migrate from old tab-store format\n return migrateOldTabStore(projectName);\n}\n\n// ---------------------------------------------------------------------------\n// Server sync\n// ---------------------------------------------------------------------------\n\nexport interface PanelLayoutWithTimestamp extends PanelLayout {\n updatedAt: string;\n}\n\n/** Fetch workspace from server */\nexport async function fetchWorkspaceFromServer(\n projectName: string,\n): Promise<PanelLayoutWithTimestamp | null> {\n if (projectName === \"__global__\") return null;\n try {\n const headers: Record<string, string> = {};\n const token = localStorage.getItem(\"ppm-auth-token\");\n if (token) headers[\"Authorization\"] = `Bearer ${token}`;\n\n const res = await fetch(`/api/project/${encodeURIComponent(projectName)}/workspace`, { headers });\n if (!res.ok) return null;\n const json = await res.json();\n if (!json.ok || !json.data) return null;\n return { ...json.data.layout, updatedAt: json.data.updatedAt };\n } catch {\n return null;\n }\n}\n\n/** Save workspace to server (debounced per project) */\nconst syncTimers = new Map<string, ReturnType<typeof setTimeout>>();\n\nfunction syncWorkspaceToServer(projectName: string, layout: PanelLayout): void {\n const existing = syncTimers.get(projectName);\n if (existing) clearTimeout(existing);\n\n syncTimers.set(projectName, setTimeout(async () => {\n syncTimers.delete(projectName);\n try {\n const headers: Record<string, string> = { \"Content-Type\": \"application/json\" };\n const token = localStorage.getItem(\"ppm-auth-token\");\n if (token) headers[\"Authorization\"] = `Bearer ${token}`;\n\n const res = await fetch(`/api/project/${encodeURIComponent(projectName)}/workspace`, {\n method: \"PUT\",\n headers,\n body: JSON.stringify({ layout }),\n });\n if (res.ok) {\n const json = await res.json();\n if (json.data?.updatedAt) {\n const key = `${STORAGE_PREFIX}${projectName}`;\n const raw = localStorage.getItem(key);\n if (raw) {\n const local = JSON.parse(raw);\n local.updatedAt = json.data.updatedAt;\n localStorage.setItem(key, JSON.stringify(local));\n }\n }\n }\n } catch { /* silent fail — localStorage is still the cache */ }\n }, 1500));\n}\n\n/** Compare local vs server timestamps, return newer layout */\nexport function resolveWorkspaceConflict(\n local: PanelLayoutWithTimestamp | null,\n server: PanelLayoutWithTimestamp | null,\n): PanelLayoutWithTimestamp | null {\n if (!local && !server) return null;\n if (!local) return server!;\n if (!server) return local;\n\n const localTime = new Date(local.updatedAt).getTime();\n const serverTime = new Date(server.updatedAt).getTime();\n return serverTime >= localTime ? server : local;\n}\n\n// ---------------------------------------------------------------------------\n// Old format migration\n// ---------------------------------------------------------------------------\n\nfunction migrateOldTabStore(projectName: string): PanelLayout | null {\n try {\n const raw = localStorage.getItem(`${OLD_STORAGE_PREFIX}${projectName}`);\n if (!raw) return null;\n const old = JSON.parse(raw) as { tabs: Tab[]; activeTabId: string | null };\n if (!old.tabs?.length) return null;\n\n const panel = createPanel(old.tabs, old.activeTabId);\n const layout: PanelLayout = {\n panels: { [panel.id]: panel },\n grid: [[panel.id]],\n focusedPanelId: panel.id,\n };\n // Save new format and clean old\n savePanelLayout(projectName, layout);\n localStorage.removeItem(`${OLD_STORAGE_PREFIX}${projectName}`);\n return layout;\n } catch {\n return null;\n }\n}\n","import { create } from \"zustand\";\nimport type { Tab, TabType } from \"./tab-store\";\nimport {\n type Panel,\n type PanelLayout,\n createPanel,\n gridAddColumn,\n gridAddRow,\n gridRemovePanel,\n findPanelPosition,\n maxColumns,\n MAX_ROWS,\n savePanelLayout,\n loadPanelLayout,\n deriveTabId,\n} from \"./panel-utils\";\n\n/** Tab types that can only have 1 instance per project */\nconst SINGLETON_TYPES = new Set<TabType>([\"settings\"]);\n\n/** Tab types removed in a prior version — filter them out when loading persisted state */\nconst OBSOLETE_TAB_TYPES = new Set([\"projects\", \"git-status\", \"git-graph\"]);\n\nfunction pushHistory(history: string[], id: string): string[] {\n const filtered = history.filter((h) => h !== id);\n filtered.push(id);\n if (filtered.length > 50) filtered.shift();\n return filtered;\n}\n\n// ---------------------------------------------------------------------------\n// Store interface\n// ---------------------------------------------------------------------------\nexport interface PanelStore {\n panels: Record<string, Panel>;\n grid: string[][];\n focusedPanelId: string;\n currentProject: string | null;\n\n /** Keep-alive: per-project grid snapshots (for hidden workspaces) */\n projectGrids: Record<string, string[][]>;\n projectFocused: Record<string, string>;\n\n // Project lifecycle\n switchProject: (projectName: string) => void;\n reloadProject: (projectName: string) => void;\n\n // Panel focus\n setFocusedPanel: (panelId: string) => void;\n\n // Tab operations (operate on focused panel by default)\n openTab: (tab: Omit<Tab, \"id\">, panelId?: string) => string;\n closeTab: (tabId: string, panelId?: string) => void;\n setActiveTab: (tabId: string, panelId?: string) => void;\n updateTab: (tabId: string, updates: Partial<Omit<Tab, \"id\">>) => void;\n\n // Panel operations\n reorderTab: (tabId: string, panelId: string, newIndex: number) => void;\n moveTab: (tabId: string, fromPanelId: string, toPanelId: string, insertIndex?: number) => void;\n splitPanel: (direction: \"left\" | \"right\" | \"up\" | \"down\", tabId: string, sourcePanelId: string, targetPanelId?: string) => boolean;\n closePanel: (panelId: string) => void;\n\n // Helpers\n getPanelForTab: (tabId: string) => Panel | undefined;\n isMobile: () => boolean;\n}\n\nfunction defaultLayout(): { panels: Record<string, Panel>; grid: string[][]; focusedPanelId: string } {\n const panel = createPanel();\n return { panels: { [panel.id]: panel }, grid: [[panel.id]], focusedPanelId: panel.id };\n}\n\n// ---------------------------------------------------------------------------\n// Store\n// ---------------------------------------------------------------------------\nexport const usePanelStore = create<PanelStore>()((set, get) => {\n /** Save only the active project's panels to localStorage */\n function persist() {\n const { currentProject, panels, grid, focusedPanelId } = get();\n if (!currentProject) return;\n const panelIds = new Set(grid.flat());\n const projectPanels: Record<string, Panel> = {};\n for (const [id, p] of Object.entries(panels)) {\n if (panelIds.has(id)) projectPanels[id] = p;\n }\n savePanelLayout(currentProject, { panels: projectPanels, grid, focusedPanelId });\n }\n\n function findPanel(tabId: string): Panel | undefined {\n return Object.values(get().panels).find((p) => p.tabs.some((t) => t.id === tabId));\n }\n\n function resolvePanel(panelId?: string): string {\n return panelId ?? get().focusedPanelId;\n }\n\n return {\n ...defaultLayout(),\n currentProject: null,\n projectGrids: {},\n projectFocused: {},\n\n switchProject: (projectName) => {\n const { currentProject, panels, grid, focusedPanelId, projectGrids, projectFocused } = get();\n\n // No-op if same project\n if (currentProject === projectName) return;\n\n // Snapshot current project's state\n const newProjectGrids = { ...projectGrids };\n const newProjectFocused = { ...projectFocused };\n\n if (currentProject) {\n newProjectGrids[currentProject] = grid;\n newProjectFocused[currentProject] = focusedPanelId;\n // Persist to localStorage\n const panelIds = new Set(grid.flat());\n const currentPanels: Record<string, Panel> = {};\n for (const [id, p] of Object.entries(panels)) {\n if (panelIds.has(id)) currentPanels[id] = p;\n }\n savePanelLayout(currentProject, { panels: currentPanels, grid, focusedPanelId });\n }\n\n // Already in memory → restore from snapshot (no localStorage read)\n if (newProjectGrids[projectName]) {\n const restoredGrid = newProjectGrids[projectName]!;\n const restoredFocused = newProjectFocused[projectName] ?? restoredGrid[0]?.[0] ?? \"\";\n set({\n currentProject: projectName,\n grid: restoredGrid,\n focusedPanelId: restoredFocused,\n projectGrids: newProjectGrids,\n projectFocused: newProjectFocused,\n });\n return;\n }\n\n // Load from localStorage\n const loaded = loadPanelLayout(projectName);\n if (loaded && Object.keys(loaded.panels).length > 0) {\n // Migrate: remove obsolete tab types\n const migratedPanels: typeof loaded.panels = {};\n for (const [pid, panel] of Object.entries(loaded.panels)) {\n const filteredTabs = panel.tabs.filter((t) => !OBSOLETE_TAB_TYPES.has(t.type));\n const filteredHistory = panel.tabHistory.filter(\n (id) => filteredTabs.some((t) => t.id === id),\n );\n const activeTabId = panel.activeTabId && filteredTabs.some((t) => t.id === panel.activeTabId)\n ? panel.activeTabId\n : (filteredHistory[filteredHistory.length - 1] ?? filteredTabs[0]?.id ?? null);\n migratedPanels[pid] = { ...panel, tabs: filteredTabs, tabHistory: filteredHistory, activeTabId };\n }\n\n // Merge into flat panels map (keep-alive: old panels stay)\n const mergedPanels = { ...panels, ...migratedPanels };\n newProjectGrids[projectName] = loaded.grid;\n newProjectFocused[projectName] = loaded.focusedPanelId;\n set({\n currentProject: projectName,\n panels: mergedPanels,\n grid: loaded.grid,\n focusedPanelId: loaded.focusedPanelId,\n projectGrids: newProjectGrids,\n projectFocused: newProjectFocused,\n });\n } else {\n // Create empty layout — EmptyPanel will show quick-open buttons\n const p = createPanel();\n const newGrid = [[p.id]];\n\n // Merge into flat panels map\n const mergedPanels = { ...panels, [p.id]: p };\n newProjectGrids[projectName] = newGrid;\n newProjectFocused[projectName] = p.id;\n savePanelLayout(projectName, { panels: { [p.id]: p }, grid: newGrid, focusedPanelId: p.id });\n set({\n currentProject: projectName,\n panels: mergedPanels,\n grid: newGrid,\n focusedPanelId: p.id,\n projectGrids: newProjectGrids,\n projectFocused: newProjectFocused,\n });\n }\n },\n\n reloadProject: (projectName) => {\n const { projectGrids, projectFocused, panels } = get();\n // Clear in-memory cache so switchProject re-reads from localStorage\n const newGrids = { ...projectGrids };\n const newFocused = { ...projectFocused };\n delete newGrids[projectName];\n delete newFocused[projectName];\n\n // Remove old panels belonging to this project from flat map\n const oldGrid = projectGrids[projectName];\n const oldPanelIds = oldGrid ? new Set(oldGrid.flat()) : new Set<string>();\n const cleanedPanels = { ...panels };\n for (const id of oldPanelIds) delete cleanedPanels[id];\n\n set({ projectGrids: newGrids, projectFocused: newFocused, panels: cleanedPanels, currentProject: null });\n // Re-trigger full load from localStorage\n get().switchProject(projectName);\n },\n\n setFocusedPanel: (panelId) => {\n if (get().panels[panelId]) set({ focusedPanelId: panelId });\n },\n\n openTab: (tabDef, panelId?) => {\n const mobile = get().isMobile();\n // On mobile, always open in first panel (tabs merged in mobile nav)\n const pid = mobile\n ? (get().grid[0]?.[0] ?? resolvePanel(panelId))\n : resolvePanel(panelId);\n const panel = get().panels[pid];\n if (!panel) return \"\";\n\n // Terminal: compute next available index if not provided\n if (tabDef.type === \"terminal\" && !tabDef.metadata?.terminalIndex) {\n const allTabs = Object.values(get().panels).flatMap((p) => p.tabs);\n const terminalNums = allTabs\n .filter((t) => t.type === \"terminal\")\n .map((t) => {\n const match = t.id.match(/^terminal:(\\d+)/);\n return match ? parseInt(match[1]!, 10) : 0;\n });\n const nextIndex = terminalNums.length > 0 ? Math.max(...terminalNums) + 1 : 1;\n tabDef = { ...tabDef, metadata: { ...tabDef.metadata, terminalIndex: nextIndex } };\n }\n\n const baseId = deriveTabId(tabDef.type, tabDef.metadata);\n\n // Singleton check — focus existing across ALL panels\n if (SINGLETON_TYPES.has(tabDef.type)) {\n for (const p of Object.values(get().panels)) {\n const existing = p.tabs.find((t) => t.id === baseId);\n if (existing) {\n set((s) => ({\n focusedPanelId: p.id,\n panels: {\n ...s.panels,\n [p.id]: {\n ...p,\n activeTabId: existing.id,\n tabHistory: pushHistory(p.tabHistory, existing.id),\n },\n },\n }));\n persist();\n return existing.id;\n }\n }\n }\n\n // Mobile: dedup across all panels (merged tab bar shows all tabs)\n if (mobile) {\n for (const gpid of get().grid.flat()) {\n const p = get().panels[gpid];\n if (!p) continue;\n const existing = p.tabs.find((t) => t.id === baseId || t.id.startsWith(`${baseId}@`));\n if (existing) {\n set((s) => ({\n focusedPanelId: p.id,\n panels: {\n ...s.panels,\n [p.id]: { ...p, activeTabId: existing.id, tabHistory: pushHistory(p.tabHistory, existing.id) },\n },\n }));\n persist();\n return existing.id;\n }\n }\n }\n\n // Non-singleton: dedup within SAME panel only\n const currentPanel = get().panels[pid]!;\n const existingInPanel = currentPanel.tabs.find((t) => t.id === baseId);\n if (existingInPanel) {\n set((s) => ({\n panels: {\n ...s.panels,\n [pid]: {\n ...currentPanel,\n activeTabId: existingInPanel.id,\n tabHistory: pushHistory(currentPanel.tabHistory, existingInPanel.id),\n },\n },\n }));\n persist();\n return existingInPanel.id;\n }\n\n // Check if same base ID exists in OTHER panels (split case)\n const existsElsewhere = Object.values(get().panels).some(\n (p) => p.id !== pid && p.tabs.some((t) => t.id === baseId),\n );\n const id = existsElsewhere ? `${baseId}@${pid}` : baseId;\n\n const tab: Tab = { ...tabDef, id };\n set((s) => {\n const p = s.panels[pid]!;\n return {\n focusedPanelId: pid,\n panels: {\n ...s.panels,\n [pid]: {\n ...p,\n tabs: [...p.tabs, tab],\n activeTabId: id,\n tabHistory: pushHistory(p.tabHistory, id),\n },\n },\n };\n });\n persist();\n return id;\n },\n\n closeTab: (tabId, panelId?) => {\n const panel = panelId ? get().panels[panelId] : findPanel(tabId);\n if (!panel) return;\n const pid = panel.id;\n\n // Clear persisted terminal session so reopening creates a fresh PTY\n if (tabId.startsWith(\"terminal:\")) {\n try { localStorage.removeItem(`ppm:terminal-session:${tabId}`); } catch { /* */ }\n }\n\n set((s) => {\n const p = s.panels[pid]!;\n const newTabs = p.tabs.filter((t) => t.id !== tabId);\n const newHistory = p.tabHistory.filter((h) => h !== tabId);\n let newActive = p.activeTabId;\n if (p.activeTabId === tabId) {\n const prevId = newHistory.length > 0 ? newHistory[newHistory.length - 1] : null;\n newActive = prevId && newTabs.some((t) => t.id === prevId)\n ? prevId\n : newTabs[newTabs.length - 1]?.id ?? null;\n }\n\n // Auto-close panel if empty and not the last one in current grid\n const gridPanelCount = s.grid.flat().length;\n if (newTabs.length === 0 && gridPanelCount > 1) {\n const { [pid]: _, ...rest } = s.panels;\n const newGrid = gridRemovePanel(s.grid, pid);\n const newFocused = s.focusedPanelId === pid ? Object.keys(rest)[0]! : s.focusedPanelId;\n return { panels: rest, grid: newGrid, focusedPanelId: newFocused };\n }\n\n return {\n panels: { ...s.panels, [pid]: { ...p, tabs: newTabs, activeTabId: newActive, tabHistory: newHistory } },\n };\n });\n persist();\n },\n\n setActiveTab: (tabId, panelId?) => {\n const panel = panelId ? get().panels[panelId] : findPanel(tabId);\n if (!panel) return;\n const pid = panel.id;\n set((s) => {\n const p = s.panels[pid]!;\n return {\n focusedPanelId: pid,\n panels: { ...s.panels, [pid]: { ...p, activeTabId: tabId, tabHistory: pushHistory(p.tabHistory, tabId) } },\n };\n });\n persist();\n },\n\n updateTab: (tabId, updates) => {\n const panel = findPanel(tabId);\n if (!panel) return;\n set((s) => ({\n panels: {\n ...s.panels,\n [panel.id]: { ...panel, tabs: panel.tabs.map((t) => (t.id === tabId ? { ...t, ...updates } : t)) },\n },\n }));\n persist();\n },\n\n reorderTab: (tabId, panelId, newIndex) => {\n const panel = get().panels[panelId];\n if (!panel) return;\n const oldIndex = panel.tabs.findIndex((t) => t.id === tabId);\n if (oldIndex === -1 || oldIndex === newIndex) return;\n const newTabs = [...panel.tabs];\n const [moved] = newTabs.splice(oldIndex, 1);\n newTabs.splice(newIndex, 0, moved!);\n set((s) => ({ panels: { ...s.panels, [panelId]: { ...panel, tabs: newTabs } } }));\n persist();\n },\n\n moveTab: (tabId, fromPanelId, toPanelId, insertIndex?) => {\n if (fromPanelId === toPanelId) return;\n const from = get().panels[fromPanelId];\n const to = get().panels[toPanelId];\n if (!from || !to) return;\n\n const tab = from.tabs.find((t) => t.id === tabId);\n if (!tab) return;\n\n const fromTabs = from.tabs.filter((t) => t.id !== tabId);\n const fromHistory = from.tabHistory.filter((h) => h !== tabId);\n const fromActive = from.activeTabId === tabId\n ? (fromHistory[fromHistory.length - 1] ?? fromTabs[fromTabs.length - 1]?.id ?? null)\n : from.activeTabId;\n\n const toTabs = [...to.tabs];\n if (insertIndex !== undefined) toTabs.splice(insertIndex, 0, tab);\n else toTabs.push(tab);\n\n set((s) => {\n const gridPanelCount = s.grid.flat().length;\n // Auto-close empty source panel if not last in current grid\n if (fromTabs.length === 0 && gridPanelCount > 1) {\n const { [fromPanelId]: _, ...rest } = s.panels;\n return {\n panels: {\n ...rest,\n [toPanelId]: { ...to, tabs: toTabs, activeTabId: tabId, tabHistory: pushHistory(to.tabHistory, tabId) },\n },\n grid: gridRemovePanel(s.grid, fromPanelId),\n focusedPanelId: toPanelId,\n };\n }\n\n return {\n focusedPanelId: toPanelId,\n panels: {\n ...s.panels,\n [fromPanelId]: { ...from, tabs: fromTabs, activeTabId: fromActive, tabHistory: fromHistory },\n [toPanelId]: { ...to, tabs: toTabs, activeTabId: tabId, tabHistory: pushHistory(to.tabHistory, tabId) },\n },\n };\n });\n persist();\n },\n\n splitPanel: (direction, tabId, sourcePanelId, targetPanelId?) => {\n const { grid, panels } = get();\n const mobile = get().isMobile();\n const source = panels[sourcePanelId];\n if (!source) return false;\n\n const tab = source.tabs.find((t) => t.id === tabId);\n if (!tab) return false;\n\n // Use target panel's position for grid insertion (where the drop happened)\n const positionPanelId = targetPanelId ?? sourcePanelId;\n const pos = findPanelPosition(grid, positionPanelId);\n if (!pos) return false;\n\n // Check constraints — grid is row-major: grid[row][col]\n const isHorizontal = direction === \"left\" || direction === \"right\";\n const isVertical = direction === \"up\" || direction === \"down\";\n if (isHorizontal && (grid[pos.row]?.length ?? 0) >= maxColumns(mobile)) return false;\n if (isVertical && grid.length >= MAX_ROWS) return false;\n\n const newPanel = createPanel([tab], tab.id);\n newPanel.tabHistory = [tab.id];\n\n // Remove tab from source\n const srcTabs = source.tabs.filter((t) => t.id !== tabId);\n const srcHistory = source.tabHistory.filter((h) => h !== tabId);\n const srcActive = source.activeTabId === tabId\n ? (srcHistory[srcHistory.length - 1] ?? srcTabs[srcTabs.length - 1]?.id ?? null)\n : source.activeTabId;\n\n let newGrid: string[][];\n if (isHorizontal) {\n // Add column within the same row\n newGrid = grid.map((row, r) => {\n if (r !== pos.row) return row;\n const newRow = [...row];\n const insertCol = direction === \"right\" ? pos.col + 1 : pos.col;\n newRow.splice(insertCol, 0, newPanel.id);\n return newRow;\n });\n } else {\n // Add new row to the grid\n newGrid = [...grid];\n const insertRow = direction === \"down\" ? pos.row + 1 : pos.row;\n newGrid.splice(insertRow, 0, [newPanel.id]);\n }\n\n set((s) => {\n const gridPanelCount = s.grid.flat().length;\n let updatedPanels = {\n ...s.panels,\n [newPanel.id]: newPanel,\n };\n\n // If source is now empty and not last panel in grid, remove it\n if (srcTabs.length === 0 && gridPanelCount > 1) {\n const { [sourcePanelId]: _, ...rest } = updatedPanels;\n updatedPanels = rest;\n newGrid = gridRemovePanel(newGrid, sourcePanelId);\n } else {\n updatedPanels[sourcePanelId] = { ...source, tabs: srcTabs, activeTabId: srcActive, tabHistory: srcHistory };\n }\n\n return { panels: updatedPanels, grid: newGrid, focusedPanelId: newPanel.id };\n });\n persist();\n return true;\n },\n\n closePanel: (panelId) => {\n const { panels, grid } = get();\n if (grid.flat().length <= 1) return;\n\n const panel = panels[panelId];\n if (!panel) return;\n\n // Find neighbor to merge tabs into\n const pos = findPanelPosition(grid, panelId);\n const allIds = grid.flat();\n const idx = allIds.indexOf(panelId);\n const neighborId = idx > 0 ? allIds[idx - 1]! : allIds[1]!;\n const neighbor = panels[neighborId];\n if (!neighbor) return;\n\n set((s) => {\n const { [panelId]: _, ...rest } = s.panels;\n const mergedTabs = [...neighbor.tabs, ...panel.tabs];\n const mergedActive = neighbor.activeTabId ?? panel.activeTabId;\n return {\n panels: {\n ...rest,\n [neighborId]: { ...neighbor, tabs: mergedTabs, activeTabId: mergedActive, tabHistory: [...neighbor.tabHistory, ...panel.tabHistory] },\n },\n grid: gridRemovePanel(s.grid, panelId),\n focusedPanelId: neighborId,\n };\n });\n persist();\n },\n\n getPanelForTab: (tabId) => findPanel(tabId),\n\n isMobile: () => typeof window !== \"undefined\" && window.innerWidth < 768,\n };\n});\n"],"mappings":"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"}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{o as e}from"./rolldown-runtime-FhOqtrmT.js";import{b as t,x as n}from"./vendor-markdown-0Mxgxy0L.js";import{t as r}from"./file-exclamation-point-BwzaQ50n.js";import"./api-client-DIhJ5qVW.js";import{q as i,tt as a}from"./index-
|
|
2
|
-
//# sourceMappingURL=pdf-preview-
|
|
1
|
+
import{o as e}from"./rolldown-runtime-FhOqtrmT.js";import{b as t,x as n}from"./vendor-markdown-0Mxgxy0L.js";import{t as r}from"./file-exclamation-point-BwzaQ50n.js";import"./api-client-DIhJ5qVW.js";import{q as i,tt as a}from"./index-8_rE2Q1-.js";import{t as o}from"./use-blob-url-DB4nNruT.js";var s=e(n(),1),c=t();function l({filePath:e,projectName:t}){let{blobUrl:n,error:l}=o(e,t,`application/pdf`),u=(0,s.useCallback)(()=>{n&&window.open(n,`_blank`)},[n]);return l?(0,c.jsxs)(`div`,{className:`flex flex-col items-center justify-center h-full gap-3 text-text-secondary`,children:[(0,c.jsx)(r,{className:`size-10 text-text-subtle`}),(0,c.jsx)(`p`,{className:`text-sm`,children:`Failed to load PDF.`})]}):n?(0,c.jsxs)(`div`,{className:`flex flex-col h-full`,children:[(0,c.jsxs)(`div`,{className:`flex items-center justify-between px-3 py-1.5 border-b border-border bg-background shrink-0`,children:[(0,c.jsx)(`span`,{className:`text-xs text-text-secondary truncate`,children:e}),(0,c.jsxs)(`button`,{onClick:u,className:`flex items-center gap-1 text-xs text-text-secondary hover:text-text-primary transition-colors`,children:[(0,c.jsx)(a,{className:`size-3`}),` Open in new tab`]})]}),(0,c.jsx)(`iframe`,{src:n,title:e,className:`flex-1 w-full border-none`})]}):(0,c.jsx)(`div`,{className:`flex items-center justify-center h-full`,children:(0,c.jsx)(i,{className:`size-5 animate-spin text-text-subtle`})})}export{l as PdfPreview};
|
|
2
|
+
//# sourceMappingURL=pdf-preview-zs9QdgDp.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"pdf-preview-
|
|
1
|
+
{"version":3,"file":"pdf-preview-zs9QdgDp.js","names":[],"sources":["../../../src/web/components/editor/pdf-preview.tsx"],"sourcesContent":["import { useCallback } from \"react\";\nimport { Loader2, FileWarning, ExternalLink } from \"lucide-react\";\nimport { useBlobUrl } from \"./use-blob-url\";\n\nexport function PdfPreview({ filePath, projectName }: { filePath: string; projectName: string }) {\n const { blobUrl, error } = useBlobUrl(filePath, projectName, \"application/pdf\");\n\n const openInNewTab = useCallback(() => { if (blobUrl) window.open(blobUrl, \"_blank\"); }, [blobUrl]);\n\n if (error) {\n return (\n <div className=\"flex flex-col items-center justify-center h-full gap-3 text-text-secondary\">\n <FileWarning className=\"size-10 text-text-subtle\" />\n <p className=\"text-sm\">Failed to load PDF.</p>\n </div>\n );\n }\n if (!blobUrl) {\n return <div className=\"flex items-center justify-center h-full\"><Loader2 className=\"size-5 animate-spin text-text-subtle\" /></div>;\n }\n return (\n <div className=\"flex flex-col h-full\">\n <div className=\"flex items-center justify-between px-3 py-1.5 border-b border-border bg-background shrink-0\">\n <span className=\"text-xs text-text-secondary truncate\">{filePath}</span>\n <button onClick={openInNewTab} className=\"flex items-center gap-1 text-xs text-text-secondary hover:text-text-primary transition-colors\">\n <ExternalLink className=\"size-3\" /> Open in new tab\n </button>\n </div>\n <iframe src={blobUrl} title={filePath} className=\"flex-1 w-full border-none\" />\n </div>\n );\n}\n"],"mappings":"0TAIA,SAAgB,EAAW,CAAE,WAAU,eAA0D,CAC/F,GAAM,CAAE,UAAS,SAAU,EAAW,EAAU,EAAa,kBAAkB,CAEzE,GAAA,EAAA,EAAA,iBAAiC,CAAM,GAAS,OAAO,KAAK,EAAS,SAAS,EAAK,CAAC,EAAQ,CAAC,CAanG,OAXI,GACF,EAAA,EAAA,MACG,MAAD,CAAK,UAAU,sFAAf,EAAA,EAAA,EAAA,KACG,EAAD,CAAa,UAAU,2BAA6B,CAAA,EAAA,EAAA,EAAA,KACnD,IAAD,CAAG,UAAU,mBAAU,sBAAuB,CAAA,CAC1C,GAGL,GAGL,EAAA,EAAA,MACG,MAAD,CAAK,UAAU,gCAAf,EAAA,EAAA,EAAA,MACG,MAAD,CAAK,UAAU,uGAAf,EAAA,EAAA,EAAA,KACG,OAAD,CAAM,UAAU,gDAAwC,EAAgB,CAAA,EAAA,EAAA,EAAA,MACvE,SAAD,CAAQ,QAAS,EAAc,UAAU,yGAAzC,EAAA,EAAA,EAAA,KACG,EAAD,CAAc,UAAU,SAAW,CAAA,CAAA,mBAC5B,GACL,aACL,SAAD,CAAQ,IAAK,EAAS,MAAO,EAAU,UAAU,4BAA8B,CAAA,CAC3E,IAXN,EAAA,EAAA,KAAQ,MAAD,CAAK,UAAU,6DAA2C,EAAD,CAAS,UAAU,uCAAyC,CAAA,CAAM,CAAA"}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{o as e}from"./rolldown-runtime-FhOqtrmT.js";import{b as t,x as n}from"./vendor-markdown-0Mxgxy0L.js";import"./vendor-ui-UXCWAcmi.js";import{t as r}from"./createLucideIcon-BjHrJDVb.js";import{n as i,t as a}from"./globe-B4Ilypbs.js";import{t as o}from"./api-client-DIhJ5qVW.js";import{B as s,F as c,nt as l,q as u,tt as d}from"./index-
|
|
2
|
-
//# sourceMappingURL=port-forwarding-tab-
|
|
1
|
+
import{o as e}from"./rolldown-runtime-FhOqtrmT.js";import{b as t,x as n}from"./vendor-markdown-0Mxgxy0L.js";import"./vendor-ui-UXCWAcmi.js";import{t as r}from"./createLucideIcon-BjHrJDVb.js";import{n as i,t as a}from"./globe-B4Ilypbs.js";import{t as o}from"./api-client-DIhJ5qVW.js";import{B as s,F as c,nt as l,q as u,tt as d}from"./index-8_rE2Q1-.js";var f=r(`wifi`,[[`path`,{d:`M12 20h.01`,key:`zekei9`}],[`path`,{d:`M2 8.82a15 15 0 0 1 20 0`,key:`dnpr2z`}],[`path`,{d:`M5 12.859a10 10 0 0 1 14 0`,key:`1x1e6c`}],[`path`,{d:`M8.5 16.429a5 5 0 0 1 7 0`,key:`1bycff`}]]),p=e(n(),1),m=t();function h(){let[e,t]=(0,p.useState)(``),[n,r]=(0,p.useState)([]),[h,g]=(0,p.useState)(!1),[_,v]=(0,p.useState)(null),[y,b]=(0,p.useState)(null),x=(0,p.useCallback)(async()=>{try{r(await o.get(`/api/preview/tunnels`))}catch(e){console.warn(`[ports] failed to fetch tunnels`,e)}},[]);(0,p.useEffect)(()=>{x();let e=setInterval(x,1e4);return()=>clearInterval(e)},[x]);let S=async e=>{let r=n.find(t=>t.port===e);if(r){window.open(r.url,`_blank`),t(``);return}g(!0),v(null);try{let n=await o.post(`/api/preview/tunnel`,{port:e});window.open(n.url,`_blank`),t(``),await x()}catch(t){v(t.message||`Failed to start tunnel for port ${e}`)}finally{g(!1)}},C=async e=>{try{await o.del(`/api/preview/tunnel/${e}`),await x()}catch(t){c.error(t.message||`Failed to stop tunnel for port ${e}`)}},w=(e,t)=>{navigator.clipboard.writeText(t).then(()=>{b(e),c.success(`URL copied`),setTimeout(()=>b(null),2e3)}).catch(()=>{c.error(`Failed to copy URL`)})};return(0,m.jsxs)(`div`,{className:`flex flex-col h-full w-full bg-background`,children:[(0,m.jsxs)(`div`,{className:`p-4 md:p-6 border-b border-border bg-surface`,children:[(0,m.jsxs)(`div`,{className:`flex items-center gap-2 mb-3`,children:[(0,m.jsx)(f,{className:`size-5 text-primary`}),(0,m.jsx)(`h2`,{className:`text-base font-medium text-text-primary`,children:`Port Forwarding`})]}),(0,m.jsxs)(`form`,{onSubmit:t=>{t.preventDefault();let n=parseInt(e,10);n>=1&&n<=65535?S(n):v(`Port must be 1-65535`)},className:`flex items-center gap-2`,children:[(0,m.jsxs)(`div`,{className:`flex-1 flex items-center gap-2 px-3 py-2.5 rounded-lg bg-background border border-border focus-within:border-primary/50 transition-colors`,children:[(0,m.jsx)(`span`,{className:`text-sm text-text-subtle shrink-0`,children:`localhost:`}),(0,m.jsx)(`input`,{type:`number`,value:e,onChange:e=>{t(e.target.value),v(null)},placeholder:`3000`,min:1,max:65535,className:`flex-1 bg-transparent text-sm text-text-primary outline-none placeholder:text-text-subtle min-w-0 [appearance:textfield] [&::-webkit-inner-spin-button]:appearance-none [&::-webkit-outer-spin-button]:appearance-none`})]}),(0,m.jsx)(`button`,{type:`submit`,disabled:h||!e,className:`px-4 py-2.5 rounded-lg bg-primary text-primary-foreground text-sm font-medium hover:bg-primary/90 disabled:opacity-50 transition-colors shrink-0 min-w-[72px] flex items-center justify-center`,children:h?(0,m.jsx)(u,{className:`size-4 animate-spin`}):`Forward`})]}),_&&(0,m.jsx)(`p`,{className:`text-sm text-red-400 mt-2`,children:_}),h&&(0,m.jsxs)(`div`,{className:`flex items-center gap-2 text-sm text-text-secondary mt-2`,children:[(0,m.jsx)(u,{className:`size-3.5 animate-spin`}),(0,m.jsx)(`span`,{children:`Starting tunnel...`})]})]}),(0,m.jsx)(`div`,{className:`flex-1 overflow-y-auto p-4 md:p-6`,children:n.length===0?(0,m.jsxs)(`div`,{className:`flex flex-col items-center justify-center h-full gap-3 text-center`,children:[(0,m.jsx)(a,{className:`size-10 text-text-subtle`}),(0,m.jsx)(`p`,{className:`text-sm text-text-secondary max-w-xs`,children:`No active ports. Forward a port to access your local dev server from anywhere.`})]}):(0,m.jsx)(`div`,{className:`space-y-2`,children:n.map(e=>(0,m.jsxs)(`div`,{className:`flex items-center gap-3 p-3 rounded-lg bg-surface border border-border`,children:[(0,m.jsxs)(`div`,{className:`shrink-0 px-2 py-1 rounded bg-primary/10 text-primary text-xs font-mono font-medium`,children:[`:`,e.port]}),(0,m.jsx)(`span`,{className:`flex-1 text-xs text-text-secondary truncate min-w-0`,children:e.url}),(0,m.jsxs)(`div`,{className:`flex items-center shrink-0`,children:[(0,m.jsx)(`button`,{onClick:()=>window.open(e.url,`_blank`),className:`p-2.5 rounded-md hover:bg-surface-elevated transition-colors`,title:`Open in browser`,children:(0,m.jsx)(d,{className:`size-4 text-text-secondary`})}),(0,m.jsx)(`button`,{onClick:()=>w(e.port,e.url),className:`p-2.5 rounded-md hover:bg-surface-elevated transition-colors`,title:`Copy URL`,children:y===e.port?(0,m.jsx)(i,{className:`size-4 text-green-400`}):(0,m.jsx)(l,{className:`size-4 text-text-secondary`})}),(0,m.jsx)(`button`,{onClick:()=>C(e.port),className:`p-2.5 rounded-md hover:bg-red-500/10 transition-colors`,title:`Stop tunnel`,children:(0,m.jsx)(s,{className:`size-4 text-red-400`})})]})]},e.port))})})]})}export{h as PortForwardingTab};
|
|
2
|
+
//# sourceMappingURL=port-forwarding-tab-sArYx1nt.js.map
|
package/dist/web/assets/{port-forwarding-tab-R5V7zkKO.js.map → port-forwarding-tab-sArYx1nt.js.map}
RENAMED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"port-forwarding-tab-R5V7zkKO.js","names":[],"sources":["../../../node_modules/.bun/lucide-react@0.577.0+b1ab299f0a400331/node_modules/lucide-react/dist/esm/icons/wifi.js","../../../src/web/components/ports/port-forwarding-tab.tsx"],"sourcesContent":["/**\n * @license lucide-react v0.577.0 - ISC\n *\n * This source code is licensed under the ISC license.\n * See the LICENSE file in the root directory of this source tree.\n */\n\nimport createLucideIcon from '../createLucideIcon.js';\n\nconst __iconNode = [\n [\"path\", { d: \"M12 20h.01\", key: \"zekei9\" }],\n [\"path\", { d: \"M2 8.82a15 15 0 0 1 20 0\", key: \"dnpr2z\" }],\n [\"path\", { d: \"M5 12.859a10 10 0 0 1 14 0\", key: \"1x1e6c\" }],\n [\"path\", { d: \"M8.5 16.429a5 5 0 0 1 7 0\", key: \"1bycff\" }]\n];\nconst Wifi = createLucideIcon(\"wifi\", __iconNode);\n\nexport { __iconNode, Wifi as default };\n//# sourceMappingURL=wifi.js.map\n","import { useState, useEffect, useCallback } from \"react\";\nimport { Check, Copy, ExternalLink, Globe, Loader2, Square, Wifi } from \"lucide-react\";\nimport { api } from \"@/lib/api-client\";\nimport { toast } from \"sonner\";\n\ninterface TunnelInfo {\n port: number;\n url: string;\n startedAt: number;\n}\n\nexport function PortForwardingTab() {\n const [portInput, setPortInput] = useState(\"\");\n const [tunnels, setTunnels] = useState<TunnelInfo[]>([]);\n const [loading, setLoading] = useState(false);\n const [error, setError] = useState<string | null>(null);\n const [copiedPort, setCopiedPort] = useState<number | null>(null);\n\n const fetchTunnels = useCallback(async () => {\n try {\n const list = await api.get<TunnelInfo[]>(\"/api/preview/tunnels\");\n setTunnels(list);\n } catch (e) {\n console.warn(\"[ports] failed to fetch tunnels\", e);\n }\n }, []);\n\n // Fetch tunnels on mount + poll every 10s\n useEffect(() => {\n fetchTunnels();\n const interval = setInterval(fetchTunnels, 10_000);\n return () => clearInterval(interval);\n }, [fetchTunnels]);\n\n const startTunnel = async (port: number) => {\n // Check if already forwarded\n const existing = tunnels.find((t) => t.port === port);\n if (existing) {\n window.open(existing.url, \"_blank\");\n setPortInput(\"\");\n return;\n }\n\n setLoading(true);\n setError(null);\n try {\n const res = await api.post<{ port: number; url: string }>(\"/api/preview/tunnel\", { port });\n window.open(res.url, \"_blank\");\n setPortInput(\"\");\n await fetchTunnels();\n } catch (e: any) {\n setError(e.message || `Failed to start tunnel for port ${port}`);\n } finally {\n setLoading(false);\n }\n };\n\n const stopTunnel = async (port: number) => {\n try {\n await api.del(`/api/preview/tunnel/${port}`);\n await fetchTunnels();\n } catch (e: any) {\n toast.error(e.message || `Failed to stop tunnel for port ${port}`);\n }\n };\n\n const copyUrl = (port: number, url: string) => {\n navigator.clipboard.writeText(url).then(() => {\n setCopiedPort(port);\n toast.success(\"URL copied\");\n setTimeout(() => setCopiedPort(null), 2000);\n }).catch(() => {\n toast.error(\"Failed to copy URL\");\n });\n };\n\n const handleSubmit = (e: React.FormEvent) => {\n e.preventDefault();\n const port = parseInt(portInput, 10);\n if (port >= 1 && port <= 65535) startTunnel(port);\n else setError(\"Port must be 1-65535\");\n };\n\n return (\n <div className=\"flex flex-col h-full w-full bg-background\">\n {/* Header + form */}\n <div className=\"p-4 md:p-6 border-b border-border bg-surface\">\n <div className=\"flex items-center gap-2 mb-3\">\n <Wifi className=\"size-5 text-primary\" />\n <h2 className=\"text-base font-medium text-text-primary\">Port Forwarding</h2>\n </div>\n <form onSubmit={handleSubmit} className=\"flex items-center gap-2\">\n <div className=\"flex-1 flex items-center gap-2 px-3 py-2.5 rounded-lg bg-background border border-border focus-within:border-primary/50 transition-colors\">\n <span className=\"text-sm text-text-subtle shrink-0\">localhost:</span>\n <input\n type=\"number\"\n value={portInput}\n onChange={(e) => { setPortInput(e.target.value); setError(null); }}\n placeholder=\"3000\"\n min={1}\n max={65535}\n className=\"flex-1 bg-transparent text-sm text-text-primary outline-none placeholder:text-text-subtle min-w-0 [appearance:textfield] [&::-webkit-inner-spin-button]:appearance-none [&::-webkit-outer-spin-button]:appearance-none\"\n />\n </div>\n <button\n type=\"submit\"\n disabled={loading || !portInput}\n className=\"px-4 py-2.5 rounded-lg bg-primary text-primary-foreground text-sm font-medium hover:bg-primary/90 disabled:opacity-50 transition-colors shrink-0 min-w-[72px] flex items-center justify-center\"\n >\n {loading ? <Loader2 className=\"size-4 animate-spin\" /> : \"Forward\"}\n </button>\n </form>\n {error && <p className=\"text-sm text-red-400 mt-2\">{error}</p>}\n {loading && (\n <div className=\"flex items-center gap-2 text-sm text-text-secondary mt-2\">\n <Loader2 className=\"size-3.5 animate-spin\" />\n <span>Starting tunnel...</span>\n </div>\n )}\n </div>\n\n {/* Tunnel list */}\n <div className=\"flex-1 overflow-y-auto p-4 md:p-6\">\n {tunnels.length === 0 ? (\n <div className=\"flex flex-col items-center justify-center h-full gap-3 text-center\">\n <Globe className=\"size-10 text-text-subtle\" />\n <p className=\"text-sm text-text-secondary max-w-xs\">\n No active ports. Forward a port to access your local dev server from anywhere.\n </p>\n </div>\n ) : (\n <div className=\"space-y-2\">\n {tunnels.map((t) => (\n <div\n key={t.port}\n className=\"flex items-center gap-3 p-3 rounded-lg bg-surface border border-border\"\n >\n {/* Port badge */}\n <div className=\"shrink-0 px-2 py-1 rounded bg-primary/10 text-primary text-xs font-mono font-medium\">\n :{t.port}\n </div>\n\n {/* URL - truncated */}\n <span className=\"flex-1 text-xs text-text-secondary truncate min-w-0\">\n {t.url}\n </span>\n\n {/* Actions — 44px touch targets */}\n <div className=\"flex items-center shrink-0\">\n <button\n onClick={() => window.open(t.url, \"_blank\")}\n className=\"p-2.5 rounded-md hover:bg-surface-elevated transition-colors\"\n title=\"Open in browser\"\n >\n <ExternalLink className=\"size-4 text-text-secondary\" />\n </button>\n <button\n onClick={() => copyUrl(t.port, t.url)}\n className=\"p-2.5 rounded-md hover:bg-surface-elevated transition-colors\"\n title=\"Copy URL\"\n >\n {copiedPort === t.port\n ? <Check className=\"size-4 text-green-400\" />\n : <Copy className=\"size-4 text-text-secondary\" />}\n </button>\n <button\n onClick={() => stopTunnel(t.port)}\n className=\"p-2.5 rounded-md hover:bg-red-500/10 transition-colors\"\n title=\"Stop tunnel\"\n >\n <Square className=\"size-4 text-red-400\" />\n </button>\n </div>\n </div>\n ))}\n </div>\n )}\n </div>\n </div>\n );\n}\n"],"x_google_ignoreList":[0],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"port-forwarding-tab-sArYx1nt.js","names":[],"sources":["../../../node_modules/.bun/lucide-react@0.577.0+b1ab299f0a400331/node_modules/lucide-react/dist/esm/icons/wifi.js","../../../src/web/components/ports/port-forwarding-tab.tsx"],"sourcesContent":["/**\n * @license lucide-react v0.577.0 - ISC\n *\n * This source code is licensed under the ISC license.\n * See the LICENSE file in the root directory of this source tree.\n */\n\nimport createLucideIcon from '../createLucideIcon.js';\n\nconst __iconNode = [\n [\"path\", { d: \"M12 20h.01\", key: \"zekei9\" }],\n [\"path\", { d: \"M2 8.82a15 15 0 0 1 20 0\", key: \"dnpr2z\" }],\n [\"path\", { d: \"M5 12.859a10 10 0 0 1 14 0\", key: \"1x1e6c\" }],\n [\"path\", { d: \"M8.5 16.429a5 5 0 0 1 7 0\", key: \"1bycff\" }]\n];\nconst Wifi = createLucideIcon(\"wifi\", __iconNode);\n\nexport { __iconNode, Wifi as default };\n//# sourceMappingURL=wifi.js.map\n","import { useState, useEffect, useCallback } from \"react\";\nimport { Check, Copy, ExternalLink, Globe, Loader2, Square, Wifi } from \"lucide-react\";\nimport { api } from \"@/lib/api-client\";\nimport { toast } from \"sonner\";\n\ninterface TunnelInfo {\n port: number;\n url: string;\n startedAt: number;\n}\n\nexport function PortForwardingTab() {\n const [portInput, setPortInput] = useState(\"\");\n const [tunnels, setTunnels] = useState<TunnelInfo[]>([]);\n const [loading, setLoading] = useState(false);\n const [error, setError] = useState<string | null>(null);\n const [copiedPort, setCopiedPort] = useState<number | null>(null);\n\n const fetchTunnels = useCallback(async () => {\n try {\n const list = await api.get<TunnelInfo[]>(\"/api/preview/tunnels\");\n setTunnels(list);\n } catch (e) {\n console.warn(\"[ports] failed to fetch tunnels\", e);\n }\n }, []);\n\n // Fetch tunnels on mount + poll every 10s\n useEffect(() => {\n fetchTunnels();\n const interval = setInterval(fetchTunnels, 10_000);\n return () => clearInterval(interval);\n }, [fetchTunnels]);\n\n const startTunnel = async (port: number) => {\n // Check if already forwarded\n const existing = tunnels.find((t) => t.port === port);\n if (existing) {\n window.open(existing.url, \"_blank\");\n setPortInput(\"\");\n return;\n }\n\n setLoading(true);\n setError(null);\n try {\n const res = await api.post<{ port: number; url: string }>(\"/api/preview/tunnel\", { port });\n window.open(res.url, \"_blank\");\n setPortInput(\"\");\n await fetchTunnels();\n } catch (e: any) {\n setError(e.message || `Failed to start tunnel for port ${port}`);\n } finally {\n setLoading(false);\n }\n };\n\n const stopTunnel = async (port: number) => {\n try {\n await api.del(`/api/preview/tunnel/${port}`);\n await fetchTunnels();\n } catch (e: any) {\n toast.error(e.message || `Failed to stop tunnel for port ${port}`);\n }\n };\n\n const copyUrl = (port: number, url: string) => {\n navigator.clipboard.writeText(url).then(() => {\n setCopiedPort(port);\n toast.success(\"URL copied\");\n setTimeout(() => setCopiedPort(null), 2000);\n }).catch(() => {\n toast.error(\"Failed to copy URL\");\n });\n };\n\n const handleSubmit = (e: React.FormEvent) => {\n e.preventDefault();\n const port = parseInt(portInput, 10);\n if (port >= 1 && port <= 65535) startTunnel(port);\n else setError(\"Port must be 1-65535\");\n };\n\n return (\n <div className=\"flex flex-col h-full w-full bg-background\">\n {/* Header + form */}\n <div className=\"p-4 md:p-6 border-b border-border bg-surface\">\n <div className=\"flex items-center gap-2 mb-3\">\n <Wifi className=\"size-5 text-primary\" />\n <h2 className=\"text-base font-medium text-text-primary\">Port Forwarding</h2>\n </div>\n <form onSubmit={handleSubmit} className=\"flex items-center gap-2\">\n <div className=\"flex-1 flex items-center gap-2 px-3 py-2.5 rounded-lg bg-background border border-border focus-within:border-primary/50 transition-colors\">\n <span className=\"text-sm text-text-subtle shrink-0\">localhost:</span>\n <input\n type=\"number\"\n value={portInput}\n onChange={(e) => { setPortInput(e.target.value); setError(null); }}\n placeholder=\"3000\"\n min={1}\n max={65535}\n className=\"flex-1 bg-transparent text-sm text-text-primary outline-none placeholder:text-text-subtle min-w-0 [appearance:textfield] [&::-webkit-inner-spin-button]:appearance-none [&::-webkit-outer-spin-button]:appearance-none\"\n />\n </div>\n <button\n type=\"submit\"\n disabled={loading || !portInput}\n className=\"px-4 py-2.5 rounded-lg bg-primary text-primary-foreground text-sm font-medium hover:bg-primary/90 disabled:opacity-50 transition-colors shrink-0 min-w-[72px] flex items-center justify-center\"\n >\n {loading ? <Loader2 className=\"size-4 animate-spin\" /> : \"Forward\"}\n </button>\n </form>\n {error && <p className=\"text-sm text-red-400 mt-2\">{error}</p>}\n {loading && (\n <div className=\"flex items-center gap-2 text-sm text-text-secondary mt-2\">\n <Loader2 className=\"size-3.5 animate-spin\" />\n <span>Starting tunnel...</span>\n </div>\n )}\n </div>\n\n {/* Tunnel list */}\n <div className=\"flex-1 overflow-y-auto p-4 md:p-6\">\n {tunnels.length === 0 ? (\n <div className=\"flex flex-col items-center justify-center h-full gap-3 text-center\">\n <Globe className=\"size-10 text-text-subtle\" />\n <p className=\"text-sm text-text-secondary max-w-xs\">\n No active ports. Forward a port to access your local dev server from anywhere.\n </p>\n </div>\n ) : (\n <div className=\"space-y-2\">\n {tunnels.map((t) => (\n <div\n key={t.port}\n className=\"flex items-center gap-3 p-3 rounded-lg bg-surface border border-border\"\n >\n {/* Port badge */}\n <div className=\"shrink-0 px-2 py-1 rounded bg-primary/10 text-primary text-xs font-mono font-medium\">\n :{t.port}\n </div>\n\n {/* URL - truncated */}\n <span className=\"flex-1 text-xs text-text-secondary truncate min-w-0\">\n {t.url}\n </span>\n\n {/* Actions — 44px touch targets */}\n <div className=\"flex items-center shrink-0\">\n <button\n onClick={() => window.open(t.url, \"_blank\")}\n className=\"p-2.5 rounded-md hover:bg-surface-elevated transition-colors\"\n title=\"Open in browser\"\n >\n <ExternalLink className=\"size-4 text-text-secondary\" />\n </button>\n <button\n onClick={() => copyUrl(t.port, t.url)}\n className=\"p-2.5 rounded-md hover:bg-surface-elevated transition-colors\"\n title=\"Copy URL\"\n >\n {copiedPort === t.port\n ? <Check className=\"size-4 text-green-400\" />\n : <Copy className=\"size-4 text-text-secondary\" />}\n </button>\n <button\n onClick={() => stopTunnel(t.port)}\n className=\"p-2.5 rounded-md hover:bg-red-500/10 transition-colors\"\n title=\"Stop tunnel\"\n >\n <Square className=\"size-4 text-red-400\" />\n </button>\n </div>\n </div>\n ))}\n </div>\n )}\n </div>\n </div>\n );\n}\n"],"x_google_ignoreList":[0],"mappings":"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"}
|